From 22cb85ff543701b47764afefc1d0477e139aa5da Mon Sep 17 00:00:00 2001 From: velandy Date: Thu, 26 Jan 2017 17:01:13 +0000 Subject: [PATCH] Bug-180: Floating IP reuse Signed-off-by: velandy --- .../rift/rwcal/openstack/openstack_drv.py | 56 +++++-- .../rift/rwcal/openstack/prepare_vm.py | 143 +++++++++++++++--- .../vala/rwcal_openstack/rwcal_openstack.py | 70 ++++----- rwcal/plugins/yang/rwcal.yang | 5 + 4 files changed, 203 insertions(+), 71 deletions(-) diff --git a/rwcal/plugins/vala/rwcal_openstack/rift/rwcal/openstack/openstack_drv.py b/rwcal/plugins/vala/rwcal_openstack/rift/rwcal/openstack/openstack_drv.py index ae12b134..191a06f4 100644 --- a/rwcal/plugins/vala/rwcal_openstack/rift/rwcal/openstack/openstack_drv.py +++ b/rwcal/plugins/vala/rwcal_openstack/rift/rwcal/openstack/openstack_drv.py @@ -219,7 +219,7 @@ class KeystoneDriverV2(KeystoneDriver): """ Driver class for keystoneclient V2 APIs """ - def __init__(self, username, password, auth_url,tenant_name, insecure): + def __init__(self, username, password, auth_url,tenant_name, insecure, region): """ Constructor for KeystoneDriverV3 class Arguments: @@ -227,7 +227,7 @@ class KeystoneDriverV2(KeystoneDriver): password (string) : Password auth_url (string) : Authentication URL tenant_name(string): Tenant Name - + region (string) : Region name Returns: None """ self._username = username @@ -235,6 +235,7 @@ class KeystoneDriverV2(KeystoneDriver): self._auth_url = auth_url self._tenant_name = tenant_name self._insecure = insecure + self._region = region super(KeystoneDriverV2, self).__init__(ksclientv2.Client) def _get_keystone_credentials(self): @@ -248,6 +249,7 @@ class KeystoneDriverV2(KeystoneDriver): creds['auth_url'] = self._auth_url creds['tenant_name'] = self._tenant_name creds['insecure'] = self.get_security_mode() + creds['region_name'] = self._region return creds def get_auth_token(self): @@ -277,7 +279,14 @@ class KeystoneDriverV3(KeystoneDriver): """ Driver class for keystoneclient V3 APIs """ - def __init__(self, username, password, auth_url,tenant_name, insecure, user_domain_name = None, project_domain_name = None): + def __init__(self, username, + password, + auth_url, + tenant_name, + insecure, + user_domain_name = None, + project_domain_name = None, + region = None): """ Constructor for KeystoneDriverV3 class Arguments: @@ -285,7 +294,9 @@ class KeystoneDriverV3(KeystoneDriver): password (string) : Password auth_url (string) : Authentication URL tenant_name(string): Tenant Name - + user_domain_name (string) : User domain name + project_domain_name (string): Project domain name + region (string) : Region name Returns: None """ self._username = username @@ -295,6 +306,7 @@ class KeystoneDriverV3(KeystoneDriver): self._insecure = insecure self._user_domain_name = user_domain_name self._project_domain_name = project_domain_name + self._region = region super(KeystoneDriverV3, self).__init__(ksclientv3.Client) def _get_keystone_credentials(self): @@ -309,6 +321,7 @@ class KeystoneDriverV3(KeystoneDriver): creds['insecure'] = self._insecure creds['user_domain_name'] = self._user_domain_name creds['project_domain_name'] = self._project_domain_name + creds['region_name'] = self._region return creds def get_user_domain_name(self): @@ -369,8 +382,8 @@ class NovaDriver(object): creds['project_id'] = self.ks_drv.get_tenant_name() creds['auth_token'] = self.ks_drv.get_auth_token() creds['insecure'] = self.ks_drv.get_security_mode() - creds['user_domain_name'] = self.ks_drv.get_user_domain_name() - creds['project_domain_name'] = self.ks_drv.get_project_domain_name() + #creds['user_domain_name'] = self.ks_drv.get_user_domain_name() + #creds['project_domain_name'] = self.ks_drv.get_project_domain_name() return creds @@ -1673,7 +1686,15 @@ class OpenstackDriver(object): """ Driver for openstack nova, neutron, glance, keystone, swift, cinder services """ - def __init__(self, username, password, auth_url, tenant_name, mgmt_network = None, cert_validate = False, user_domain_name = None, project_domain_name = None): + def __init__(self, username, + password, + auth_url, + tenant_name, + mgmt_network = None, + cert_validate = False, + user_domain_name = None, + project_domain_name = None, + region = None): """ OpenstackDriver Driver constructor Arguments: @@ -1684,18 +1705,33 @@ class OpenstackDriver(object): mgmt_network(string, optional) : Management network name. Each VM created with this cloud-account will have a default interface into management network. cert_validate (boolean, optional) : In case of SSL/TLS connection if certificate validation is required or not. - + user_domain_name : Domain name for user + project_domain_name : Domain name for project + region : Region name """ insecure = not cert_validate if auth_url.find('/v3') != -1: - self.ks_drv = KeystoneDriverV3(username, password, auth_url, tenant_name, insecure, user_domain_name, project_domain_name) + self.ks_drv = KeystoneDriverV3(username, + password, + auth_url, + tenant_name, + insecure, + user_domain_name, + project_domain_name, + region) self.glance_drv = GlanceDriverV2(self.ks_drv) self.nova_drv = NovaDriverV21(self.ks_drv) self.neutron_drv = NeutronDriverV2(self.ks_drv) self.ceilo_drv = CeilometerDriverV2(self.ks_drv) self.cinder_drv = CinderDriverV2(self.ks_drv) elif auth_url.find('/v2') != -1: - self.ks_drv = KeystoneDriverV2(username, password, auth_url, tenant_name, insecure) + + self.ks_drv = KeystoneDriverV2(username, + password, + auth_url, + tenant_name, + insecure, + region) self.glance_drv = GlanceDriverV2(self.ks_drv) self.nova_drv = NovaDriverV2(self.ks_drv) self.neutron_drv = NeutronDriverV2(self.ks_drv) diff --git a/rwcal/plugins/vala/rwcal_openstack/rift/rwcal/openstack/prepare_vm.py b/rwcal/plugins/vala/rwcal_openstack/rift/rwcal/openstack/prepare_vm.py index efcf1f55..06b65fc5 100644 --- a/rwcal/plugins/vala/rwcal_openstack/rift/rwcal/openstack/prepare_vm.py +++ b/rwcal/plugins/vala/rwcal_openstack/rift/rwcal/openstack/prepare_vm.py @@ -22,6 +22,9 @@ import argparse import sys, os, time import rwlogger import yaml +import random +import fcntl + logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger() @@ -31,29 +34,93 @@ rwlog_handler = rwlogger.RwLogger(category="rw-cal-log", logger.addHandler(rwlog_handler) #logger.setLevel(logging.DEBUG) +class FileLock: + FILE_LOCK = '/tmp/_openstack_prepare_vm.lock' + def __init__(self): + # This will create it if it does not exist already + self.filename = FileLock.FILE_LOCK + self.handle = None + + # Bitwise OR fcntl.LOCK_NB if you need a non-blocking lock + def acquire(self): + logger.info(" Attempting to acquire log." %os.getpid()) + self.handle = open(self.filename, 'w') + fcntl.flock(self.handle, fcntl.LOCK_EX) + logger.info(" Lock successfully acquired." %os.getpid()) + + def release(self): + fcntl.flock(self.handle, fcntl.LOCK_UN) + self.handle.close() + logger.info(" Released lock." %os.getpid()) + + def __del__(self): + if self.handle and self.handle.closed == False: + self.handle.close() + + +def allocate_floating_ip(drv, argument): + #### Allocate a floating_ip + available_ip = [ ip for ip in drv.nova_floating_ip_list() if ip.instance_id == None ] + + if argument.pool_name: + ### Filter further based on IP address + available_ip = [ ip for ip in available_ip if ip.pool == argument.pool_name ] + + if not available_ip: + logger.info(" No free floating_ips available. Allocating fresh from pool: %s" %(os.getpid(), argument.pool_name)) + pool_name = argument.pool_name if argument.pool_name is not None else None + floating_ip = drv.nova_floating_ip_create(pool_name) + else: + floating_ip = random.choice(available_ip) + logger.info(" Selected floating_ip: %s from available free pool" %(os.getpid(), floating_ip)) + + return floating_ip + +def handle_floating_ip_assignment(drv, server, argument, management_ip): + lock = FileLock() + ### Try 3 time (<<>>) + RETRY = 3 + for attempt in range(RETRY): + try: + lock.acquire() + floating_ip = allocate_floating_ip(drv, argument) + logger.info("Assigning the floating_ip: %s to VM: %s" %(floating_ip, server['name'])) + drv.nova_floating_ip_assign(argument.server_id, + floating_ip, + management_ip) + logger.info("Assigned floating_ip: %s to management_ip: %s" %(floating_ip, management_ip)) + except Exception as e: + logger.error("Could not assign floating_ip: %s to VM: %s. Exception: %s" %(floating_ip, server['name'], str(e))) + lock.release() + if attempt == (RETRY -1): + logger.error("Max attempts %d reached for floating_ip allocation. Giving up" %attempt) + raise + else: + logger.error("Retrying floating ip allocation. Current retry count: %d" %attempt) + else: + lock.release() + return + + def assign_floating_ip_address(drv, argument): if not argument.floating_ip: return server = drv.nova_server_get(argument.server_id) - logger.info("Assigning the floating_ip: %s to VM: %s" %(argument.floating_ip, server['name'])) - + for i in range(120): server = drv.nova_server_get(argument.server_id) for network_name,network_info in server['addresses'].items(): - if network_info: - if network_name == argument.mgmt_network: - for n_info in network_info: - if 'OS-EXT-IPS:type' in n_info and n_info['OS-EXT-IPS:type'] == 'fixed': - management_ip = n_info['addr'] - drv.nova_floating_ip_assign(argument.server_id, - argument.floating_ip, - management_ip) - logger.info("Assigned floating_ip: %s to management_ip: %s" %(argument.floating_ip, management_ip)) + if network_info and network_name == argument.mgmt_network: + for n_info in network_info: + if 'OS-EXT-IPS:type' in n_info and n_info['OS-EXT-IPS:type'] == 'fixed': + management_ip = n_info['addr'] + handle_floating_ip_assignment(drv, server, argument, management_ip) return - logger.info("Waiting for management_ip to be assigned to server: %s" %(server['name'])) - time.sleep(1) + else: + logger.info("Waiting for management_ip to be assigned to server: %s" %(server['name'])) + time.sleep(1) else: logger.info("No management_ip IP available to associate floating_ip for server: %s" %(server['name'])) return @@ -186,6 +253,27 @@ def main(): type = str, help = "Tenant name openstack installation") + parser.add_argument('--user_domain', + action = "store", + dest = "user_domain", + default = None, + type = str, + help = "User domain name for openstack installation") + + parser.add_argument('--project_domain', + action = "store", + dest = "project_domain", + default = None, + type = str, + help = "Project domain name for openstack installation") + + parser.add_argument('--region', + action = "store", + dest = "region", + default = "RegionOne", + type = str, + help = "Region name for openstack installation") + parser.add_argument('--mgmt_network', action = "store", dest = "mgmt_network", @@ -199,10 +287,17 @@ def main(): help = "Server ID on which boot operations needs to be performed") parser.add_argument('--floating_ip', - action = "store", + action = "store_true", dest = "floating_ip", + default = False, + help = "Floating IP assignment required") + + parser.add_argument('--pool_name', + action = "store", + dest = "pool_name", type = str, - help = "Floating IP to be assigned") + help = "Floating IP pool name") + parser.add_argument('--port_metadata', action = "store_true", @@ -250,7 +345,6 @@ def main(): else: logger.info("Using Server ID : %s" %(argument.server_id)) - try: pid = os.fork() if pid > 0: @@ -259,12 +353,17 @@ def main(): except OSError as e: logger.error("fork failed: %d (%s)\n" % (e.errno, e.strerror)) sys.exit(2) - - drv = openstack_drv.OpenstackDriver(username = argument.username, - password = argument.password, - auth_url = argument.auth_url, - tenant_name = argument.tenant_name, - mgmt_network = argument.mgmt_network) + + + drv = openstack_drv.OpenstackDriver(username = argument.username, + password = argument.password, + auth_url = argument.auth_url, + tenant_name = argument.tenant_name, + mgmt_network = argument.mgmt_network, + user_domain_name = argument.user_domain, + project_domain_name = argument.project_domain, + region = argument.region) + prepare_vm_after_boot(drv, argument) sys.exit(0) diff --git a/rwcal/plugins/vala/rwcal_openstack/rwcal_openstack.py b/rwcal/plugins/vala/rwcal_openstack/rwcal_openstack.py index 3794ce5a..2be896a5 100644 --- a/rwcal/plugins/vala/rwcal_openstack/rwcal_openstack.py +++ b/rwcal/plugins/vala/rwcal_openstack/rwcal_openstack.py @@ -40,7 +40,7 @@ from gi.repository import ( RwTypes, RwcalYang) -PREPARE_VM_CMD = "prepare_vm.py --auth_url {auth_url} --username {username} --password {password} --tenant_name {tenant_name} --mgmt_network {mgmt_network} --server_id {server_id} --port_metadata" +PREPARE_VM_CMD = "prepare_vm.py --auth_url {auth_url} --username {username} --password {password} --tenant_name {tenant_name} --region {region} --user_domain {user_domain} --project_domain {project_domain} --mgmt_network {mgmt_network} --server_id {server_id} --port_metadata " rwstatus_exception_map = { IndexError: RwTypes.RwStatus.NOTFOUND, KeyError: RwTypes.RwStatus.NOTFOUND, @@ -95,7 +95,8 @@ class RwcalOpenstackPlugin(GObject.Object, RwCal.Cloud): mgmt_network = account.openstack.mgmt_network, cert_validate = account.openstack.cert_validate, user_domain_name = account.openstack.user_domain, - project_domain_name = account.openstack.project_domain) + project_domain_name = account.openstack.project_domain, + region = account.openstack.region) except (KeystoneExceptions.Unauthorized, KeystoneExceptions.AuthorizationFailure, NeutronException.NotFound) as e: raise @@ -389,14 +390,12 @@ class RwcalOpenstackPlugin(GObject.Object, RwCal.Cloud): with self._use_driver(account) as drv: ### If floating_ip is required and we don't have one, better fail before any further allocation + pool_name = None + floating_ip = False if vminfo.has_field('allocate_public_address') and vminfo.allocate_public_address: if account.openstack.has_field('floating_ip_pool'): pool_name = account.openstack.floating_ip_pool - else: - pool_name = None - floating_ip = self._allocate_floating_ip(drv, pool_name) - else: - floating_ip = None + floating_ip = True if vminfo.has_field('cloud_init') and vminfo.cloud_init.has_field('userdata'): kwargs['userdata'] = vminfo.cloud_init.userdata @@ -1266,13 +1265,13 @@ class RwcalOpenstackPlugin(GObject.Object, RwCal.Cloud): vdu.public_ip = interface['addr'] # Look for any metadata - for key, value in vm_info['metadata'].items(): - if key == 'node_id': - vdu.node_id = value - else: - custommetadata = vdu.supplemental_boot_data.custom_meta_data.add() - custommetadata.name = key - custommetadata.value = str(value) +# for key, value in vm_info['metadata'].items(): +# if key == 'node_id': +# vdu.node_id = value +# else: +# custommetadata = vdu.supplemental_boot_data.custom_meta_data.add() +# custommetadata.name = key +# custommetadata.value = str(value) # Look for config_drive if ('config_drive' in vm_info): @@ -1920,14 +1919,12 @@ class RwcalOpenstackPlugin(GObject.Object, RwCal.Cloud): with self._use_driver(account) as drv: ### If floating_ip is required and we don't have one, better fail before any further allocation + floating_ip = False + pool_name = None if vduinfo.has_field('allocate_public_address') and vduinfo.allocate_public_address: if account.openstack.has_field('floating_ip_pool'): pool_name = account.openstack.floating_ip_pool - else: - pool_name = None - floating_ip = self._allocate_floating_ip(drv, pool_name) - else: - floating_ip = None + floating_ip = True if vduinfo.has_field('vdu_init') and vduinfo.vdu_init.has_field('userdata'): kwargs['userdata'] = vduinfo.vdu_init.userdata @@ -2017,7 +2014,7 @@ class RwcalOpenstackPlugin(GObject.Object, RwCal.Cloud): with self._use_driver(account) as drv: vm_id = drv.nova_server_create(**kwargs) if floating_ip: - self.prepare_vdu_on_boot(account, vm_id, floating_ip, vduinfo.volumes) + self.prepare_vdu_on_boot(account, vm_id, floating_ip, pool_name, vduinfo.volumes) return vm_id @@ -2068,15 +2065,6 @@ class RwcalOpenstackPlugin(GObject.Object, RwCal.Cloud): mgmt_network_id = None with self._use_driver(account) as drv: mgmt_network_id = drv._mgmt_network_id - ### If floating_ip is required and we don't have one, better fail before any further allocation - if vdu_init.has_field('allocate_public_address') and vdu_init.allocate_public_address: - if account.openstack.has_field('floating_ip_pool'): - pool_name = account.openstack.floating_ip_pool - else: - pool_name = None - floating_ip = self._allocate_floating_ip(drv, pool_name) - else: - floating_ip = None port_list = [] network_list = [] @@ -2227,16 +2215,20 @@ class RwcalOpenstackPlugin(GObject.Object, RwCal.Cloud): - def prepare_vdu_on_boot(self, account, server_id, floating_ip, volumes=None): - cmd = PREPARE_VM_CMD.format(auth_url = account.openstack.auth_url, - username = account.openstack.key, - password = account.openstack.secret, - tenant_name = account.openstack.tenant, - mgmt_network = account.openstack.mgmt_network, - server_id = server_id) - - if floating_ip is not None: - cmd += (" --floating_ip "+ floating_ip.ip) + def prepare_vdu_on_boot(self, account, server_id, floating_ip, pool_name, volumes=None): + cmd = PREPARE_VM_CMD.format(auth_url = account.openstack.auth_url, + username = account.openstack.key, + password = account.openstack.secret, + tenant_name = account.openstack.tenant, + region = account.openstack.region, + user_domain = account.openstack.user_domain, + project_domain = account.openstack.project_domain, + mgmt_network = account.openstack.mgmt_network, + server_id = server_id) + if floating_ip: + cmd += " --floating_ip" + if pool_name: + cmd += (" --pool_name " + pool_name) vol_metadata = False if volumes is not None: diff --git a/rwcal/plugins/yang/rwcal.yang b/rwcal/plugins/yang/rwcal.yang index 310c8f52..9daefb10 100644 --- a/rwcal/plugins/yang/rwcal.yang +++ b/rwcal/plugins/yang/rwcal.yang @@ -247,6 +247,11 @@ module rwcal mandatory true; } + leaf region { + type string; + default "RegionOne"; + } + leaf admin { type boolean; default false; -- 2.25.1