Support for associating and disassociating neutron floating IPs

This adds support for creating and removing DNS A records when
floating IPs are associated and disassociated in neutron.
novajoin-install and functional tests are enhanced to test it.

Change-Id: I82c83ad9e8c84ddfd4ecfc4d5c3b31a418af97a7
This commit is contained in:
Grzegorz Grasza 2018-11-22 11:57:39 +01:00
parent 7fa5789e51
commit 4d997dddc6
5 changed files with 85 additions and 26 deletions

View File

@ -511,16 +511,27 @@ class IPAClient(IPANovaJoinBase):
LOG.debug('IPA is not configured')
return
params = [{"__dns_name__": get_domain() + "."},
{"__dns_name__": hostname}]
kw = {'a_part_ip_address': floating_ip}
params = [six.text_type(get_domain() + '.'),
six.text_type(hostname)]
kw = {'a_part_ip_address': six.text_type(floating_ip)}
try:
self._call_ipa('dnsrecord_add', *params, **kw)
except (errors.DuplicateEntry, errors.ValidationError):
pass
def remove_ip(self, hostname, floating_ip):
def find_record(self, floating_ip):
"""Find DNS A record for floating IP address"""
LOG.debug('looking up host for floating ip' + floating_ip)
params = [six.text_type(get_domain() + '.')]
service_args = {'arecord': six.text_type(floating_ip)}
result = self._call_ipa('dnsrecord_find', *params, **service_args)
if result['count'] == 0:
return
assert(result['count'] == 1)
return result['result'][0]['idnsname'][0].to_unicode()
def remove_ip(self, floating_ip):
"""Remove a floating IP from a given hostname."""
LOG.debug('In remove_ip')
@ -528,4 +539,12 @@ class IPAClient(IPANovaJoinBase):
LOG.debug('IPA is not configured')
return
LOG.debug('Current a no-op')
hostname = self.find_record(floating_ip)
if not hostname:
LOG.debug('floating IP record not found')
return
params = [six.text_type(get_domain() + '.'), hostname]
service_args = {'arecord': six.text_type(floating_ip)}
self._call_ipa('dnsrecord_del', *params, **service_args)

View File

@ -172,13 +172,7 @@ class NotificationEndpoint(object):
floating_ip = payload.get('floating_ip')
LOG.info("Disassociate floating IP %s" % floating_ip)
ipa = ipaclient()
nova = novaclient()
server = nova.servers.get(payload.get('instance_id'))
if server:
ipa.remove_ip(server.name, floating_ip)
else:
LOG.error("Could not resolve %s into a hostname",
payload.get('instance_id'))
ipa.remove_ip(floating_ip)
@event_handlers('floatingip.update.end')
def floating_ip_update(self, payload):
@ -186,20 +180,24 @@ class NotificationEndpoint(object):
floatingip = payload.get('floatingip')
floating_ip = floatingip.get('floating_ip_address')
port_id = floatingip.get('port_id')
LOG.info("Neutron floating IP associate: %s" % floating_ip)
ipa = ipaclient()
nova = novaclient()
neutron = neutronclient()
search_opts = {'id': port_id}
ports = neutron.list_ports(**search_opts).get('ports')
if len(ports) == 1:
device_id = ports[0].get('device_id')
if device_id:
server = nova.servers.get(device_id)
if server:
ipa.add_ip(server.name, floating_ip)
if port_id:
LOG.info("Neutron floating IP associate: %s" % floating_ip)
nova = novaclient()
neutron = neutronclient()
search_opts = {'id': port_id}
ports = neutron.list_ports(**search_opts).get('ports')
if len(ports) == 1:
device_id = ports[0].get('device_id')
if device_id:
server = nova.servers.get(device_id)
if server:
ipa.add_ip(server.name, floating_ip)
else:
LOG.error("Expected 1 port, got %d", len(ports))
else:
LOG.error("Expected 1 port, got %d", len(ports))
LOG.info("Neutron floating IP disassociate: %s" % floating_ip)
ipa.remove_ip(floating_ip)
def delete_subhosts(self, ipa, hostname_short, metadata):
"""Delete subhosts and remove VIPs if possible.

View File

@ -97,10 +97,16 @@ class TestEnrollment(testtools.TestCase):
metadata = {"ipa_enroll": "True"})
server = self.conn.compute.wait_for_server(self._server)
self.conn.compute.add_floating_ip_to_server(
server, self._ip.floating_ip_address)
return server
def _associate_floating_ip(self):
self.conn.compute.add_floating_ip_to_server(
self._server, self._ip.floating_ip_address)
def _disassociate_floating_ip(self):
self.conn.compute.remove_floating_ip_from_server(
self._server, self._ip.floating_ip_address)
def _delete_server(self):
if self._server:
self.conn.compute.delete_server(self._server)
@ -116,8 +122,25 @@ class TestEnrollment(testtools.TestCase):
self.assertFalse(
self.ipaclient.find_host(TEST_INSTANCE + EXAMPLE_DOMAIN))
@loopingcall.RetryDecorator(50, 5, 5, (AssertionError,))
def _check_ip_record_added(self):
self.assertTrue(
self.ipaclient.find_record(self._ip.floating_ip_address))
@loopingcall.RetryDecorator(50, 5, 5, (AssertionError,))
def _check_ip_record_removed(self):
self.assertFalse(
self.ipaclient.find_record(self._ip.floating_ip_address))
def test_enroll_server(self):
self._create_server()
self._associate_floating_ip()
self._check_ipa_client_created()
self._check_ip_record_added()
self._disassociate_floating_ip()
self._check_ip_record_removed()
self._associate_floating_ip()
self._check_ip_record_added()
self._delete_server()
self._check_ipa_client_deleted()
self._check_ip_record_removed()

View File

@ -36,6 +36,10 @@
become: true
become_user: stack
- name: Restart neutron services
command: systemctl restart devstack@q-*
become: true
- name: Restart nova services
command: systemctl restart devstack@n-*
become: true

View File

@ -33,6 +33,7 @@ from urllib3.util import parse_url
IPACONF = '/etc/ipa/default.conf'
NOVACONF = '/etc/nova/nova.conf'
NOVACPUCONF = '/etc/nova/nova-cpu.conf'
NEUTRONCONF = '/etc/neutron/neutron.conf'
JOINCONF = '/etc/novajoin/join.conf'
@ -202,6 +203,17 @@ def install(opts):
with open(conf, 'w') as f:
config.write(f)
config = SafeConfigParser()
config.read(opts.neutron_conf)
config.set('oslo_messaging_notifications',
'driver',
'messagingv2')
config.set('oslo_messaging_notifications',
'topics',
'notifications,novajoin_notifications')
with open(opts.neutron_conf, 'w') as f:
config.write(f)
if transport_url:
join_config = SafeConfigParser()
@ -241,6 +253,9 @@ def parse_args():
parser.add_argument('--nova-cpu-conf', dest='nova_cpu_conf',
help='nova compute configuration file',
default=NOVACPUCONF)
parser.add_argument('--neutron-conf', dest='neutron_conf',
help='neutron configuration file',
default=NEUTRONCONF)
parser.add_argument('--novajoin-conf', dest='novajoin_conf',
help='novajoin configuration file',
default=JOINCONF)