Ver Fonte

Switch OpenStack floating-IP attach to Neutron + fix listing/visibility

Three changes prompted by integration-test failures against a DevStack
stable/2025.1 target:

* `add_floating_ip` / `remove_floating_ip` now bind the FIP via the
  Neutron API (set port_id on the floating IP). Nova's
  add_floating_ip_to_server / remove_floating_ip_from_server server
  actions were removed in microversion 2.44 (Pike); modern Nova
  returns 404 for those actions. A `_primary_port` helper resolves the
  instance's first Neutron port and is used by both attach + detach.

* With the FIP attach now flowing only through Neutron, the address
  doesn't immediately appear in Nova's `server.addresses` blob —
  Nova's info_cache is refreshed by a periodic task on a ~60s cadence
  and is not re-queried on a plain server-show. `OpenStackInstance`
  gains an `_all_addresses` helper that unions Nova's view with a
  live Neutron query for FIPs bound to the instance's ports, and
  `public_ips` / `private_ips` filter from that.

* `OpenStackVolumeService.list` uses openstacksdk `volumes()` with a
  `marker` argument for pagination. Cinder returns 404 when the marker
  volume has since been deleted (e.g., by a concurrent test, or
  between page-N and page-(N+1) of the same caller's traversal). The
  list path now catches the 404 and falls back to a fresh listing.
Nuwan Goonasekera há 10 horas atrás
pai
commit
b3b7ccaa32

+ 57 - 12
cloudbridge/providers/openstack/resources.py

@@ -323,6 +323,36 @@ class OpenStackInstance(BaseInstance):
         self._os_instance.name = value
         self._os_instance.update(name=value or "cb-inst")
 
+    def _all_addresses(self):
+        """All IP addresses associated with this instance.
+
+        Combines the addresses Nova reports (via server.addresses /
+        ``_os_instance.networks``, populated from Nova's info_cache) with
+        any floating IPs Neutron currently has bound to the instance's
+        ports. Nova's info_cache is refreshed by a periodic task on a
+        ~60s cadence and is not re-synced on a plain server-show, so a
+        FIP attached via the Neutron API (as add_floating_ip does)
+        otherwise wouldn't show up until the next sync.
+        """
+        addrs = set()
+        for _, network_addrs in self._os_instance.networks.items():
+            for address in network_addrs:
+                addrs.add(address)
+        # Query Neutron for any floating IPs bound to this instance's
+        # ports — these may not yet be reflected in Nova's cached view.
+        try:
+            for port in self._provider.os_conn.network.ports(
+                    device_id=self.id):
+                for fip in self._provider.os_conn.network.ips(
+                        port_id=port.id):
+                    if fip.floating_ip_address:
+                        addrs.add(fip.floating_ip_address)
+        except Exception as e:
+            log.debug(
+                "Could not enumerate floating IPs for instance %s: %s",
+                self.id, e)
+        return addrs
+
     @property
     def public_ips(self):
         """
@@ -332,20 +362,16 @@ class OpenStackInstance(BaseInstance):
         # public or private, since the returned IPs are grouped by an arbitrary
         # network label. Therefore, it's necessary to parse the address and
         # determine whether it's public or private
-        return [address
-                for _, addresses in self._os_instance.networks.items()
-                for address in addresses
-                if not ipaddress.ip_address(address).is_private]
+        return [a for a in self._all_addresses()
+                if not ipaddress.ip_address(a).is_private]
 
     @property
     def private_ips(self):
         """
         Get all the private IP addresses for this instance.
         """
-        return [address
-                for _, addresses in self._os_instance.networks.items()
-                for address in addresses
-                if ipaddress.ip_address(address).is_private]
+        return [a for a in self._all_addresses()
+                if ipaddress.ip_address(a).is_private]
 
     @property
     def vm_type_id(self):
@@ -460,25 +486,44 @@ class OpenStackInstance(BaseInstance):
         """Get a floating IP object based on the supplied ID."""
         return self._provider.networking._floating_ips.get(None, floating_ip)
 
+    def _primary_port(self):
+        """Return the first Neutron port on this instance, or None."""
+        # pylint:disable=protected-access
+        return next(
+            iter(self._provider.os_conn.network.ports(device_id=self.id)),
+            None)
+
     def add_floating_ip(self, floating_ip):
         """
         Add a floating IP address to this instance.
+
+        Nova's add_floating_ip server action was removed in microversion
+        2.44 (Pike). The supported path is to set the FIP's port_id to
+        one of the server's Neutron ports.
         """
         log.debug("Adding floating IP adress: %s", floating_ip)
         fip = (floating_ip if isinstance(floating_ip, OpenStackFloatingIP)
                else self._get_fip(floating_ip))
-        self._provider.os_conn.compute.add_floating_ip_to_server(
-            self.id, fip.public_ip)
+        port = self._primary_port()
+        if not port:
+            raise Exception(
+                "Cannot add floating IP: instance {0} has no network port"
+                .format(self.id))
+        # pylint:disable=protected-access
+        self._provider.os_conn.network.update_ip(fip._ip, port_id=port.id)
 
     def remove_floating_ip(self, floating_ip):
         """
         Remove a floating IP address from this instance.
+
+        Same rationale as add_floating_ip; the Nova action endpoint is
+        gone, so detach by clearing port_id on the Neutron FIP.
         """
         log.debug("Removing floating IP adress: %s", floating_ip)
         fip = (floating_ip if isinstance(floating_ip, OpenStackFloatingIP)
                else self._get_fip(floating_ip))
-        self._provider.os_conn.compute.remove_floating_ip_from_server(
-            self.id, fip.public_ip)
+        # pylint:disable=protected-access
+        self._provider.os_conn.network.update_ip(fip._ip, port_id=None)
 
     def add_vm_firewall(self, firewall):
         """

+ 15 - 5
cloudbridge/providers/openstack/services.py

@@ -419,12 +419,22 @@ class OpenStackVolumeService(BaseVolumeService):
     @dispatch(event="provider.storage.volumes.list",
               priority=BaseVolumeService.STANDARD_EVENT_PRIORITY)
     def list(self, limit=None, marker=None):
-        cb_vols = [
-            OpenStackVolume(self.provider, vol)
-            for vol in self.provider.os_conn.block_storage.volumes(
+        try:
+            os_vols = list(self.provider.os_conn.block_storage.volumes(
                 limit=oshelpers.os_result_limit(self.provider, limit),
-                marker=marker)
-            if vol.availability_zone == self.provider.service_zone_name(self)]
+                marker=marker))
+        except NotFoundException:
+            # Cinder returns 404 when the supplied pagination marker
+            # refers to a volume that has since been deleted (e.g.,
+            # between the time a caller saw the volume in page N and
+            # asked for page N+1, or when a concurrent test deletes it).
+            # Fall back to a fresh listing.
+            if marker is None:
+                raise
+            os_vols = list(self.provider.os_conn.block_storage.volumes(
+                limit=oshelpers.os_result_limit(self.provider, limit)))
+        cb_vols = [OpenStackVolume(self.provider, vol) for vol in os_vols
+                   if vol.availability_zone == self.provider.service_zone_name(self)]
         return oshelpers.to_server_paged_list(self.provider, cb_vols, limit)
 
     @dispatch(event="provider.storage.volumes.create",