1111from functools import partial
1212from re import search
1313
14+ import openstack
1415import pytz
1516from cinderclient import exceptions as cinder_exceptions
1617from cinderclient .v3 import client as cinderclient
2324from novaclient import client as osclient
2425from novaclient import exceptions as os_exceptions
2526from novaclient .client import SessionClient
26- from novaclient .v2 .floating_ips import FloatingIP
2727from requests .exceptions import Timeout
2828from swiftclient import client as swiftclient
2929from swiftclient .exceptions import ClientException as SwiftException
@@ -229,11 +229,13 @@ def assign_floating_ip(self, floating_ip_pool, safety_timer=5):
229229 if len (free_ips ) > 1 :
230230 # There are 2 and more ips, so we will take the first one (eldest)
231231 ip = free_ips [0 ]
232- self .logger .info ("Reusing %s from pool %s" , ip .ip , floating_ip_pool )
232+ self .logger .info (
233+ "Reusing %s from pool %s" , ip .floating_ip_address , floating_ip_pool
234+ )
233235 else :
234236 # There is one or none, so create one.
235237 try :
236- ip = self ._api . floating_ips . create (floating_ip_pool )
238+ ip = self .system . _create_floating_ip (floating_ip_pool )
237239 except allowed_exceptions as e :
238240 self .logger .error ("Probably no more FIP slots available: %s" , str (e ))
239241 free_ips = self .system .free_fips (floating_ip_pool )
@@ -243,21 +245,24 @@ def assign_floating_ip(self, floating_ip_pool, safety_timer=5):
243245 ip = free_ips [0 ]
244246 self .logger .info (
245247 "Reused %s from pool %s because no more free spaces for new ips" ,
246- ip .ip ,
248+ ip .floating_ip_address ,
247249 floating_ip_pool ,
248250 )
249251 else :
250252 # Nothing can be done
251253 raise NoMoreFloatingIPs (f"Provider { self .system .auth_url } ran out of FIPs" )
252- self .logger .info ("Created %s in pool %s" , ip .ip , floating_ip_pool )
253- instance .add_floating_ip (ip )
254+ self .logger .info ("Created %s in pool %s" , ip .floating_ip_address , floating_ip_pool )
255+ # Use openstacksdk to associate floating IP with server
256+ self .system .openstack_conn .compute .add_floating_ip_to_server (
257+ instance , ip .floating_ip_address
258+ )
254259
255260 # Now the grace period in which a FIP theft could happen
256261 time .sleep (safety_timer )
257262
258- self .logger .info ("Instance %s got a floating IP %s" , self .name , ip .ip )
259- assert self .ip == ip .ip , "Current IP does not match reserved floating IP!"
260- return ip .ip
263+ self .logger .info ("Instance %s got a floating IP %s" , self .name , ip .floating_ip_address )
264+ assert self .ip == ip .floating_ip_address , "Current IP does not match reserved floating IP!"
265+ return ip .floating_ip_address
261266
262267 def unassign_floating_ip (self ):
263268 """Disassociates the floating IP (if present) from VM.
@@ -269,14 +274,20 @@ def unassign_floating_ip(self):
269274 ip_addr = self .ip
270275 if ip_addr is None :
271276 return None
272- floating_ips = self ._api . floating_ips . findall (ip = ip_addr )
277+ floating_ips = self .system . _list_floating_ips (ip = ip_addr )
273278 if not floating_ips :
274279 return None
275280 floating_ip = floating_ips [0 ]
276281 self .logger .info (
277- "Detaching floating IP %s/%s from %s" , floating_ip .id , floating_ip .ip , instance .name
282+ "Detaching floating IP %s/%s from %s" ,
283+ floating_ip .id ,
284+ floating_ip .floating_ip_address ,
285+ instance .name ,
286+ )
287+ # Use openstacksdk to disassociate floating IP from server
288+ self .system .openstack_conn .compute .remove_floating_ip_from_server (
289+ instance , floating_ip .floating_ip_address
278290 )
279- instance .remove_floating_ip (floating_ip )
280291 wait_for (lambda : self .ip is None , delay = 1 , timeout = "1m" )
281292 return floating_ip
282293
@@ -629,6 +640,7 @@ def __init__(self, tenant, username, password, auth_url, **kwargs):
629640 self ._sapi = None
630641 self ._tenant_api = None
631642 self ._stackapi = None
643+ self ._openstack_conn = None
632644
633645 @property
634646 def _identifying_attrs (self ):
@@ -700,6 +712,28 @@ def napi(self):
700712 self ._napi = neutronclient .Client (session = self .session )
701713 return self ._napi
702714
715+ @property
716+ def openstack_conn (self ):
717+ """OpenStack SDK connection for unified API access."""
718+ if not self ._openstack_conn :
719+ auth_kwargs = dict (
720+ auth_url = self .auth_url ,
721+ username = self .username ,
722+ password = self .password ,
723+ project_name = self .tenant ,
724+ )
725+ if self .keystone_version == 3 :
726+ auth_kwargs .update (
727+ dict (
728+ user_domain_id = self .domain_id ,
729+ user_domain_name = self .domain_name ,
730+ project_domain_id = self .domain_id ,
731+ project_domain_name = self .domain_name ,
732+ )
733+ )
734+ self ._openstack_conn = openstack .connect (** auth_kwargs , verify = False )
735+ return self ._openstack_conn
736+
703737 @property
704738 def tenant_api (self ):
705739 if not self ._tenant_api :
@@ -736,6 +770,48 @@ def stackapi(self):
736770 def info (self ):
737771 return f"{ self .api .client .service_type } { self .api .client .version } "
738772
773+ def _list_floating_ips (self , ** filters ):
774+ """List floating IPs using openstacksdk with optional filters."""
775+ openstack_filters = {}
776+
777+ if "ip" in filters :
778+ openstack_filters ["floating_ip_address" ] = filters ["ip" ]
779+ if "fixed_ip" in filters :
780+ if filters ["fixed_ip" ] is not None :
781+ openstack_filters ["fixed_ip_address" ] = filters ["fixed_ip" ]
782+ if "pool" in filters and filters ["pool" ]:
783+ # Pool in nova corresponds to network in openstack sdk
784+ networks = list (
785+ self .openstack_conn .network .networks (name = filters ["pool" ], is_router_external = True )
786+ )
787+ if networks :
788+ openstack_filters ["floating_network_id" ] = networks [0 ].id
789+
790+ floating_ips = list (self .openstack_conn .network .floating_ips (** openstack_filters ))
791+
792+ # Post-process for fixed_ip=None case (unassigned floating IPs)
793+ if "fixed_ip" in filters and filters ["fixed_ip" ] is None :
794+ floating_ips = [
795+ fip
796+ for fip in floating_ips
797+ if fip .fixed_ip_address is None or fip .fixed_ip_address == ""
798+ ]
799+
800+ return floating_ips
801+
802+ def _create_floating_ip (self , pool_name ):
803+ """Create floating IP using openstacksdk."""
804+ # Find the external network by name
805+ networks = list (
806+ self .openstack_conn .network .networks (name = pool_name , is_router_external = True )
807+ )
808+ if not networks :
809+ raise Exception (f"External network '{ pool_name } ' not found" )
810+
811+ network_id = networks [0 ].id
812+ floating_ip = self .openstack_conn .network .create_floating_ip (floating_network_id = network_id )
813+ return floating_ip
814+
739815 def _get_tenants (self ):
740816 if self .keystone_version == 3 :
741817 return self .tenant_api .list ()
@@ -1067,13 +1143,15 @@ def volume_attachments(self, volume_id):
10671143
10681144 def free_fips (self , pool ):
10691145 """Returns list of free floating IPs sorted by ip address."""
1070- return sorted (self .api .floating_ips .findall (fixed_ip = None , pool = pool ), key = lambda ip : ip .ip )
1146+ return sorted (
1147+ self ._list_floating_ips (fixed_ip = None , pool = pool ), key = lambda ip : ip .floating_ip_address
1148+ )
10711149
10721150 def delete_floating_ip (self , floating_ip ):
10731151 """Deletes an existing FIP.
10741152
10751153 Args:
1076- floating_ip: FloatingIP object or an IP address of the FIP.
1154+ floating_ip: OpenStack SDK FloatingIP object or an IP address of the FIP.
10771155
10781156 Returns:
10791157 True if it deleted a FIP, False if it did not delete it, most probably because it
@@ -1082,15 +1160,18 @@ def delete_floating_ip(self, floating_ip):
10821160 if floating_ip is None :
10831161 # To be able to chain with unassign_floating_ip, which can return None
10841162 return False
1085- if not isinstance (floating_ip , FloatingIP ):
1086- floating_ip = self .api .floating_ips .findall (ip = floating_ip )
1087- if not floating_ip :
1163+ if isinstance (floating_ip , str ):
1164+ # If floating_ip is a string (IP address), find the floating IP object
1165+ floating_ip_list = self ._list_floating_ips (ip = floating_ip )
1166+ if not floating_ip_list :
10881167 return False
1089- floating_ip = floating_ip [0 ]
1090- self .logger .info ("Deleting floating IP %s/%s" , floating_ip .id , floating_ip .ip )
1091- floating_ip .delete ()
1168+ floating_ip = floating_ip_list [0 ]
1169+ self .logger .info (
1170+ "Deleting floating IP %s/%s" , floating_ip .id , floating_ip .floating_ip_address
1171+ )
1172+ self .openstack_conn .network .delete_floating_ip (floating_ip )
10921173 wait_for (
1093- lambda : len (self .api . floating_ips . findall (ip = floating_ip .ip )) == 0 ,
1174+ lambda : len (self ._list_floating_ips (ip = floating_ip .floating_ip_address )) == 0 ,
10941175 delay = 1 ,
10951176 timeout = "1m" ,
10961177 )
@@ -1108,7 +1189,7 @@ def get_first_floating_ip(self, pool=None):
11081189 pool_name = getattr (pool , "name" , pool ) # obj attr, or passed thing (string) otherwise
11091190 fip = None
11101191 try :
1111- fip = self .api . floating_ips . create (pool_name )
1192+ fip = self ._create_floating_ip (pool_name )
11121193 except os_exceptions .NotFound :
11131194 self .logger .exception ("Exception while creating FIP for pool: %s" , pool_name )
11141195 else :
@@ -1120,11 +1201,11 @@ def get_first_floating_ip(self, pool=None):
11201201 pool_name ,
11211202 )
11221203 try :
1123- fip = next (ip for ip in self .api . floating_ips . list () if ip .instance_id is None )
1204+ fip = next (ip for ip in self ._list_floating_ips () if ip .fixed_ip_address is None )
11241205 except StopIteration :
11251206 self .logger .error ("No more Floating IPs available" )
11261207 return None
1127- return fip .ip
1208+ return fip .floating_ip_address
11281209
11291210 def stack_exist (self , stack_name ):
11301211 stack = self .stackapi .stacks .get (stack_name )
0 commit comments