Browse Source

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
Grzegorz Grasza 5 months ago
parent
commit
4d997dddc6

+ 24
- 5
novajoin/ipa.py View File

@@ -511,16 +511,27 @@ class IPAClient(IPANovaJoinBase):
511 511
             LOG.debug('IPA is not configured')
512 512
             return
513 513
 
514
-        params = [{"__dns_name__": get_domain() + "."},
515
-                  {"__dns_name__": hostname}]
516
-        kw = {'a_part_ip_address': floating_ip}
514
+        params = [six.text_type(get_domain() + '.'),
515
+                  six.text_type(hostname)]
516
+        kw = {'a_part_ip_address': six.text_type(floating_ip)}
517 517
 
518 518
         try:
519 519
             self._call_ipa('dnsrecord_add', *params, **kw)
520 520
         except (errors.DuplicateEntry, errors.ValidationError):
521 521
             pass
522 522
 
523
-    def remove_ip(self, hostname, floating_ip):
523
+    def find_record(self, floating_ip):
524
+        """Find DNS A record for floating IP address"""
525
+        LOG.debug('looking up host for floating ip' + floating_ip)
526
+        params = [six.text_type(get_domain() + '.')]
527
+        service_args = {'arecord': six.text_type(floating_ip)}
528
+        result = self._call_ipa('dnsrecord_find', *params, **service_args)
529
+        if result['count'] == 0:
530
+            return
531
+        assert(result['count'] == 1)
532
+        return result['result'][0]['idnsname'][0].to_unicode()
533
+
534
+    def remove_ip(self, floating_ip):
524 535
         """Remove a floating IP from a given hostname."""
525 536
         LOG.debug('In remove_ip')
526 537
 
@@ -528,4 +539,12 @@ class IPAClient(IPANovaJoinBase):
528 539
             LOG.debug('IPA is not configured')
529 540
             return
530 541
 
531
-        LOG.debug('Current a no-op')
542
+        hostname = self.find_record(floating_ip)
543
+        if not hostname:
544
+            LOG.debug('floating IP record not found')
545
+            return
546
+
547
+        params = [six.text_type(get_domain() + '.'), hostname]
548
+        service_args = {'arecord': six.text_type(floating_ip)}
549
+
550
+        self._call_ipa('dnsrecord_del', *params, **service_args)

+ 17
- 19
novajoin/notifications.py View File

@@ -172,13 +172,7 @@ class NotificationEndpoint(object):
172 172
         floating_ip = payload.get('floating_ip')
173 173
         LOG.info("Disassociate floating IP %s" % floating_ip)
174 174
         ipa = ipaclient()
175
-        nova = novaclient()
176
-        server = nova.servers.get(payload.get('instance_id'))
177
-        if server:
178
-            ipa.remove_ip(server.name, floating_ip)
179
-        else:
180
-            LOG.error("Could not resolve %s into a hostname",
181
-                      payload.get('instance_id'))
175
+        ipa.remove_ip(floating_ip)
182 176
 
183 177
     @event_handlers('floatingip.update.end')
184 178
     def floating_ip_update(self, payload):
@@ -186,20 +180,24 @@ class NotificationEndpoint(object):
186 180
         floatingip = payload.get('floatingip')
187 181
         floating_ip = floatingip.get('floating_ip_address')
188 182
         port_id = floatingip.get('port_id')
189
-        LOG.info("Neutron floating IP associate: %s" % floating_ip)
190 183
         ipa = ipaclient()
191
-        nova = novaclient()
192
-        neutron = neutronclient()
193
-        search_opts = {'id': port_id}
194
-        ports = neutron.list_ports(**search_opts).get('ports')
195
-        if len(ports) == 1:
196
-            device_id = ports[0].get('device_id')
197
-            if device_id:
198
-                server = nova.servers.get(device_id)
199
-                if server:
200
-                    ipa.add_ip(server.name, floating_ip)
184
+        if port_id:
185
+            LOG.info("Neutron floating IP associate: %s" % floating_ip)
186
+            nova = novaclient()
187
+            neutron = neutronclient()
188
+            search_opts = {'id': port_id}
189
+            ports = neutron.list_ports(**search_opts).get('ports')
190
+            if len(ports) == 1:
191
+                device_id = ports[0].get('device_id')
192
+                if device_id:
193
+                    server = nova.servers.get(device_id)
194
+                    if server:
195
+                        ipa.add_ip(server.name, floating_ip)
196
+            else:
197
+                LOG.error("Expected 1 port, got %d", len(ports))
201 198
         else:
202
-            LOG.error("Expected 1 port, got %d", len(ports))
199
+            LOG.info("Neutron floating IP disassociate: %s" % floating_ip)
200
+            ipa.remove_ip(floating_ip)
203 201
 
204 202
     def delete_subhosts(self, ipa, hostname_short, metadata):
205 203
         """Delete subhosts and remove VIPs if possible.

+ 25
- 2
novajoin/tests/functional/test_enrollment.py View File

@@ -97,10 +97,16 @@ class TestEnrollment(testtools.TestCase):
97 97
             metadata = {"ipa_enroll": "True"})
98 98
 
99 99
         server = self.conn.compute.wait_for_server(self._server)
100
-        self.conn.compute.add_floating_ip_to_server(
101
-            server, self._ip.floating_ip_address)
102 100
         return server
103 101
 
102
+    def _associate_floating_ip(self):
103
+        self.conn.compute.add_floating_ip_to_server(
104
+            self._server, self._ip.floating_ip_address)
105
+
106
+    def _disassociate_floating_ip(self):
107
+        self.conn.compute.remove_floating_ip_from_server(
108
+            self._server, self._ip.floating_ip_address)
109
+
104 110
     def _delete_server(self):
105 111
         if self._server:
106 112
             self.conn.compute.delete_server(self._server)
@@ -116,8 +122,25 @@ class TestEnrollment(testtools.TestCase):
116 122
         self.assertFalse(
117 123
             self.ipaclient.find_host(TEST_INSTANCE + EXAMPLE_DOMAIN))
118 124
 
125
+    @loopingcall.RetryDecorator(50, 5, 5, (AssertionError,))
126
+    def _check_ip_record_added(self):
127
+        self.assertTrue(
128
+            self.ipaclient.find_record(self._ip.floating_ip_address))
129
+
130
+    @loopingcall.RetryDecorator(50, 5, 5, (AssertionError,))
131
+    def _check_ip_record_removed(self):
132
+        self.assertFalse(
133
+            self.ipaclient.find_record(self._ip.floating_ip_address))
134
+
119 135
     def test_enroll_server(self):
120 136
         self._create_server()
137
+        self._associate_floating_ip()
121 138
         self._check_ipa_client_created()
139
+        self._check_ip_record_added()
140
+        self._disassociate_floating_ip()
141
+        self._check_ip_record_removed()
142
+        self._associate_floating_ip()
143
+        self._check_ip_record_added()
122 144
         self._delete_server()
123 145
         self._check_ipa_client_deleted()
146
+        self._check_ip_record_removed()

+ 4
- 0
roles/configure-novajoin/tasks/main.yaml View File

@@ -36,6 +36,10 @@
36 36
   become: true
37 37
   become_user: stack
38 38
 
39
+- name: Restart neutron services
40
+  command: systemctl restart devstack@q-*
41
+  become: true
42
+
39 43
 - name: Restart nova services
40 44
   command: systemctl restart devstack@n-*
41 45
   become: true

+ 15
- 0
scripts/novajoin-install View File

@@ -33,6 +33,7 @@ from urllib3.util import parse_url
33 33
 IPACONF = '/etc/ipa/default.conf'
34 34
 NOVACONF = '/etc/nova/nova.conf'
35 35
 NOVACPUCONF = '/etc/nova/nova-cpu.conf'
36
+NEUTRONCONF = '/etc/neutron/neutron.conf'
36 37
 JOINCONF = '/etc/novajoin/join.conf'
37 38
 
38 39
 
@@ -202,6 +203,17 @@ def install(opts):
202 203
         with open(conf, 'w') as f:
203 204
             config.write(f)
204 205
 
206
+    config = SafeConfigParser()
207
+    config.read(opts.neutron_conf)
208
+    config.set('oslo_messaging_notifications',
209
+               'driver',
210
+               'messagingv2')
211
+    config.set('oslo_messaging_notifications',
212
+               'topics',
213
+               'notifications,novajoin_notifications')
214
+    with open(opts.neutron_conf, 'w') as f:
215
+        config.write(f)
216
+
205 217
 
206 218
     if transport_url:
207 219
         join_config = SafeConfigParser()
@@ -241,6 +253,9 @@ def parse_args():
241 253
     parser.add_argument('--nova-cpu-conf', dest='nova_cpu_conf',
242 254
                         help='nova compute configuration file',
243 255
                         default=NOVACPUCONF)
256
+    parser.add_argument('--neutron-conf', dest='neutron_conf',
257
+                        help='neutron configuration file',
258
+                        default=NEUTRONCONF)
244 259
     parser.add_argument('--novajoin-conf', dest='novajoin_conf',
245 260
                         help='novajoin configuration file',
246 261
                         default=JOINCONF)

Loading…
Cancel
Save