Bug-180: Floating IP reuse 35/1035/1
authorvelandy <rajesh.velandy@riftio.com>
Thu, 26 Jan 2017 17:01:13 +0000 (17:01 +0000)
committervelandy <rajesh.velandy@riftio.com>
Thu, 26 Jan 2017 17:01:33 +0000 (17:01 +0000)
Signed-off-by: velandy <rajesh.velandy@riftio.com>
rwcal/plugins/vala/rwcal_openstack/rift/rwcal/openstack/openstack_drv.py
rwcal/plugins/vala/rwcal_openstack/rift/rwcal/openstack/prepare_vm.py
rwcal/plugins/vala/rwcal_openstack/rwcal_openstack.py
rwcal/plugins/yang/rwcal.yang

index ae12b13..191a06f 100644 (file)
@@ -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)
index efcf1f5..06b65fc 100644 (file)
@@ -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("<PID: %d> Attempting to acquire log." %os.getpid())
+        self.handle = open(self.filename, 'w')
+        fcntl.flock(self.handle, fcntl.LOCK_EX)
+        logger.info("<PID: %d> Lock successfully acquired." %os.getpid())
+        
+    def release(self):
+        fcntl.flock(self.handle, fcntl.LOCK_UN)
+        self.handle.close()
+        logger.info("<PID: %d> 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("<PID: %d> 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("<PID: %d> 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 (<<<magic number>>>)
+    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)
     
index 3794ce5..2be896a 100644 (file)
@@ -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:
index 310c8f5..9daefb1 100644 (file)
@@ -247,6 +247,11 @@ module rwcal
           mandatory true;
         }
 
+        leaf region {
+          type string;
+          default "RegionOne";
+        }
+        
         leaf admin {
           type boolean;
           default false;