Inserting Azure plugin 40/7440/3
authorseryio <siyito.lpgc@gmail.com>
Thu, 23 May 2019 12:50:49 +0000 (14:50 +0200)
committerseryio <siyito.lpgc@gmail.com>
Thu, 23 May 2019 12:52:05 +0000 (14:52 +0200)
Change-Id: I21254a385791747c3138255c1f1e2476d5147823
Signed-off-by: seryio <siyito.lpgc@gmail.com>
osm_ro/vimconn_azure.py [new file with mode: 0755]

diff --git a/osm_ro/vimconn_azure.py b/osm_ro/vimconn_azure.py
new file mode 100755 (executable)
index 0000000..88b50c5
--- /dev/null
@@ -0,0 +1,419 @@
+# -*- coding: utf-8 -*-
+
+__author__='Sergio Gonzalez'
+__date__ ='$18-apr-2019 23:59:59$'
+
+import vimconn
+import logging
+
+from os import getenv
+from uuid import uuid4
+
+from azure.common.credentials import ServicePrincipalCredentials
+from azure.mgmt.resource import ResourceManagementClient
+from azure.mgmt.network import NetworkManagementClient
+from azure.mgmt.compute import ComputeManagementClient
+
+class vimconnector(vimconn.vimconnector):
+
+    def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None, log_level=None,
+                 config={}, persistent_info={}):
+
+        vimconn.vimconnector.__init__(self, uuid, name, tenant_id, tenant_name, url, url_admin, user, passwd, log_level,
+                                      config, persistent_info)
+
+        # LOGGER
+        self.logger = logging.getLogger('openmano.vim.azure')
+        if log_level:
+            logging.basicConfig()
+            self.logger.setLevel(getattr(logging, log_level))
+
+        # CREDENTIALS 
+        self.credentials = ServicePrincipalCredentials(
+            client_id= user,
+            secret= passwd,
+            tenant= tenant_id
+        )
+               
+        #SUBSCRIPTION
+        if 'subscription_id' in config:
+            self.subscription_id = config.get('subscription_id')
+            self.logger.debug('Setting subscription '+str(self.subscription_id))
+        else:
+            raise vimconn.vimconnException('Subscription not specified')
+        #REGION
+        if 'region_name' in config:
+            self.region = config.get('region_name')
+        else:
+            raise vimconn.vimconnException('Azure region_name is not specified at config')
+        #RESOURCE_GROUP
+        if 'resource_group' in config:
+            self.resource_group = config.get('resource_group')
+        else:
+            raise vimconn.vimconnException('Azure resource_group is not specified at config')
+        # VNET_NAME
+        if 'vnet_name' in config:
+            self.vnet_name = config["vnet_name"]
+            
+        # public ssh key
+        self.pub_key = config.get('pub_key')
+            
+    def _reload_connection(self):
+        '''Sets connections to work with Azure service APIs
+        '''
+        self.logger.debug('Reloading API Connection')
+        try:
+            self.conn = ResourceManagementClient(self.credentials, self.subscription_id)
+            self.conn_compute = ComputeManagementClient(self.credentials, self.subscription_id)
+            self.conn_vnet = NetworkManagementClient(self.credentials, self.subscription_id)
+        except Exception as e:
+            self.format_vimconn_exception(e)            
+
+    def _get_resource_name_from_resource_id(self, resource_id):
+        return str(resource_id.split('/')[-1])
+
+    def _get_location_from_resource_group(self, resource_group_name):
+        return self.conn.resource_groups.get(resource_group_name).location
+        
+    def _get_resource_group_name_from_resource_id(self, resource_id):
+        return str(resource_id.split('/')[4])
+
+    def _check_subnets_for_vm(self, net_list):
+        #All subnets must belong to the same resource group and vnet
+        if len(set(self._get_resource_group_name_from_resource_id(net['id'])+self._get_resource_name_from_resource_id(net['id']) for net in net_list)) != 1:
+            raise self.format_vimconn_exception('Azure VMs can only attach to subnets in same VNET')
+
+    def format_vimconn_exception(self, e):
+        '''Params: an Exception object
+        Returns: Raises the exception 'e' passed in mehtod parameters
+        '''
+        self.conn = None
+        self.conn_vnet = None
+        raise vimconn.vimconnConnectionException(type(e).__name__ + ': ' + str(e))        
+
+    def _check_or_create_resource_group(self):
+        '''Creates a resource group in indicated region
+        '''
+        self.logger.debug('Creating RG {} in location {}'.format(self.resource_group, self.region))
+        self.conn.resource_groups.create_or_update(self.resource_group, {'location':self.region})
+
+    def new_network(self, net_name, net_type, ip_profile=None, shared=False, vlan=None):
+        '''Adds a tenant network to VIM
+        Params:
+            'net_name': name of the network
+            'ip_profile': is a dict containing the IP parameters of the network (Currently only IPv4 is implemented)
+                'ip-version': can be one of ['IPv4','IPv6']
+                'subnet-address': ip_prefix_schema, that is X.X.X.X/Y
+                'gateway-address': (Optional) ip_schema, that is X.X.X.X
+                'dns-address': (Optional) ip_schema,
+                'dhcp': (Optional) dict containing
+                    'enabled': {'type': 'boolean'},
+                    'start-address': ip_schema, first IP to grant
+                    'count': number of IPs to grant.
+        Returns a tuple with the network identifier and created_items, or raises an exception on error
+            created_items can be None or a dictionary where this method can include key-values that will be passed to
+            the method delete_network. Can be used to store created segments, created l2gw connections, etc.
+            Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
+            as not present.
+        '''
+        return self._new_subnet(net_name, ip_profile)
+
+    def _new_subnet(self, net_name, ip_profile):
+        '''Adds a tenant network to VIM
+            It creates a new VNET with a single subnet
+        '''
+        self.logger.debug('Adding a subnet to VNET '+self.vnet_name)
+        self._reload_connection()
+        self._check_or_create_resource_group()
+
+        if ip_profile is None:
+            # TODO get a non used vnet ip range /24 and allocate automatically
+            raise vimconn.vimconnException('Azure cannot create VNET with no CIDR')
+
+        try:
+            vnet_params= {
+                'location': self.region,
+                'address_space': {
+                    'address_prefixes': [ip_profile['subnet_address']]
+                },
+                'subnets': [
+                    {
+                        'name': "{}-{}".format(net_name[:24], uuid4()),
+                        'address_prefix': ip_profile['subnet_address']
+                    }
+                ]
+            }
+            self.conn_vnet.virtual_networks.create_or_update(self.resource_group, self.vnet_name, vnet_params)
+            # TODO return a tuple (subnet-ID, None)
+        except Exception as e:
+            self.format_vimconn_exception(e)
+
+    def _create_nic(self, subnet_id, nic_name, static_ip=None):
+        self._reload_connection()
+        
+        resource_group_name=self._get_resource_group_name_from_resource_id(subnet_id)
+        location = self._get_location_from_resource_group(resource_group_name)
+            
+        if static_ip:
+            async_nic_creation = self.conn_vnet.network_interfaces.create_or_update(
+                resource_group_name,
+                nic_name,
+                {
+                    'location': location,
+                    'ip_configurations': [{
+                        'name': nic_name + 'ipconfiguration',
+                        'privateIPAddress': static_ip,
+                        'privateIPAllocationMethod': 'Static',
+                        'subnet': {
+                            'id': subnet_id
+                        }
+                    }]
+                }
+            )
+        else:
+            async_nic_creation = self.conn_vnet.network_interfaces.create_or_update(
+                resource_group_name,
+                nic_name,
+                {
+                    'location': location,
+                    'ip_configurations': [{
+                        'name': nic_name + 'ipconfiguration',
+                        'subnet': {
+                            'id': subnet_id
+                        }
+                    }]
+                }
+            )
+
+        return async_nic_creation.result()
+
+    def get_network_list(self, filter_dict={}):
+        '''Obtain tenant networks of VIM
+        Filter_dict can be:
+            name: network name
+            id: network uuid
+            shared: boolean
+            tenant_id: tenant
+            admin_state_up: boolean
+            status: 'ACTIVE'
+        Returns the network list of dictionaries
+        '''
+        self.logger.debug('Getting all subnets from VIM')
+        try:
+            self._reload_connection()
+            vnet = self.conn_vnet.virtual_networks.get(self.config["resource_group"], self.vnet_name)
+            subnet_list = []
+            
+            for subnet in vnet.subnets:
+                # TODO implement filter_dict
+                if filter_dict:
+                    if filter_dict.get("id") and str(subnet.id) != filter_dict["id"]:
+                        continue
+                    if filter_dict.get("name") and \
+                            self._get_resource_name_from_resource_id(subnet.id) != filter_dict["name"]:
+                        continue
+
+                subnet_list.append({
+                    'id': str(subnet.id),
+                     'name': self._get_resource_name_from_resource_id(subnet.id),
+                     'status': str(vnet.provisioning_state),  # TODO Does subnet contains status???
+                     'cidr_block': str(subnet.address_prefix)
+                    }
+                )
+            return subnet_list
+        except Exception as e:
+            self.format_vimconn_exception(e)
+
+    def new_vminstance(self, vm_name, description, start, image_id, flavor_id, net_list, cloud_config=None,
+                       disk_list=None, availability_zone_index=None, availability_zone_list=None):
+
+        return self._new_vminstance(vm_name, image_id, flavor_id, net_list)
+        
+    def _new_vminstance(self, vm_name, image_id, flavor_id, net_list, cloud_config=None, disk_list=None, availability_zone_index=None, availability_zone_list=None):               
+        #Create NICs
+        self._check_subnets_for_vm(net_list)
+        vm_nics = []
+        for idx, net in enumerate(net_list):
+            subnet_id=net['subnet_id']
+            nic_name = vm_name + '-nic-'+str(idx)
+            vm_nic = self._create_nic(subnet_id, nic_name)
+            vm_nics.append({ 'id': str(vm_nic.id)})
+
+        try:
+            vm_parameters = {
+                'location': self.region,
+                'os_profile': {
+                    'computer_name': vm_name,  # TODO if vm_name cannot be repeated add uuid4() suffix
+                    'admin_username': 'sergio',  # TODO is it mandatory???
+                    'linuxConfiguration': {
+                        'disablePasswordAuthentication': 'true',
+                        'ssh': {
+                          'publicKeys': [
+                            {
+                              'path': '/home/sergio/.ssh/authorized_keys',
+                              'keyData': self.pub_key
+                            }
+                          ]
+                        }
+                    }                    
+                    
+                },
+                'hardware_profile': {
+                    'vm_size':flavor_id
+                },
+                'storage_profile': {
+                    'image_reference': image_id
+                },
+                'network_profile': {
+                    'network_interfaces': [
+                        vm_nics[0]
+                    ]
+                }
+            }
+            creation_result = self.conn_compute.virtual_machines.create_or_update(
+                self.resource_group, 
+                vm_name, 
+                vm_parameters
+            )
+            
+            run_command_parameters = {
+                'command_id': 'RunShellScript', # For linux, don't change it
+                'script': [
+                'date > /home/sergio/test.txt'
+                ]
+            }
+            poller = self.conn_compute.virtual_machines.run_command(
+                self.resource_group, 
+                vm_name, 
+                run_command_parameters
+            )
+            # TODO return a tuple (vm-ID, None)
+        except Exception as e:
+            self.format_vimconn_exception(e)
+
+    def get_flavor_id_from_data(self, flavor_dict):
+               self.logger.debug("Getting flavor id from data")
+               self._reload_connection()
+               vm_sizes_list = [vm_size.serialize() for vm_size in self.conn_compute.virtual_machine_sizes.list(self.region)]
+               
+               cpus = flavor_dict['vcpus']
+               memMB = flavor_dict['ram']
+               
+               filteredSizes = [size for size in vm_sizes_list if size['numberOfCores'] > cpus and size['memoryInMB'] > memMB]
+               listedFilteredSizes = sorted(filteredSizes, key=lambda k: k['numberOfCores']) 
+               
+               return listedFilteredSizes[0]['name']
+
+    def check_vim_connectivity():
+        try:
+            self._reload_connection()
+            return true
+        except:
+            raise vimconn.vimconnException("Connectivity issue with Azure API")
+
+    def get_network(self, net_id):
+        resGroup= self._get_resource_group_name_from_resource_id(net_id)
+        resName = self._get_resource_name_from_resource_id(net_id)
+        
+        self._reload_connection()
+        vnet = self.conn_vnet.virtual_networks.get(resGroup, resName)
+
+        return vnet
+
+    def delete_network(self, net_id):
+        resGroup= self._get_resource_group_name_from_resource_id(net_id)
+        resName = self._get_resource_name_from_resource_id(net_id)
+        
+        self._reload_connection()
+        self.conn_vnet.virtual_networks.delete(resGroup, resName)
+
+    def delete_vminstance(self, vm_id):
+        resGroup= self._get_resource_group_name_from_resource_id(net_id)
+        resName = self._get_resource_name_from_resource_id(net_id)
+        
+        self._reload_connection()
+        self.conn_compute.virtual_machines.delete(resGroup, resName)
+
+    def get_vminstance(self, vm_id):
+        resGroup= self._get_resource_group_name_from_resource_id(net_id)
+        resName = self._get_resource_name_from_resource_id(net_id)
+        
+        self._reload_connection()
+        vm=self.conn_compute.virtual_machines.get(resGroup, resName)
+
+        return vm
+
+    def get_flavor(self, flavor_id):
+        self._reload_connection()
+        for vm_size in self.conn_compute.virtual_machine_sizes.list(self.region):
+            if vm_size.name == flavor_id :
+                return vm_size
+
+
+# TODO refresh_nets_status ver estado activo
+# TODO PRIORITARY get_image_list
+# TODO refresh_vms_status  ver estado activo
+# TODO get_vminstance_console  for getting console
+
+if __name__ == "__main__":
+
+    # Making some basic test
+    vim_id='azure'
+    vim_name='azure'
+    needed_test_params = {
+        "client_id": "AZURE_CLIENT_ID",    # TODO delete private information  
+        "secret": "AZURE_SECRET",        # TODO delete private information  
+        "tenant": "AZURE_TENANT",        # TODO delete private information
+        "resource_group": "AZURE_RESOURCE_GROUP",  # TODO delete private information # 'testOSMlive2',
+                                                         # TODO maybe it should be created a resouce_group per VNFD
+        "subscription_id": "AZURE_SUBSCRIPTION_ID",   # TODO delete private information  'ca3d18ab-d373-4afb-a5d6-7c44f098d16a'
+               "vnet_name": "AZURE_VNET_NAME",
+    }
+    test_params = {}
+
+    for param, env_var in needed_test_params.items():
+        value = getenv(env_var)
+        if not value:
+            raise Exception("Provide a valid value for env '{}'".format(env_var))
+        test_params[param] = value
+
+    config = {
+            'region_name': getenv("AZURE_REGION_NAME", 'westeurope'),
+            'resource_group': getenv("AZURE_RESOURCE_GROUP"),
+            'subscription_id': getenv("AZURE_SUBSCRIPTION_ID"),
+            'pub_key': getenv("AZURE_PUB_KEY", None),  # TODO delete private information 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDKlMlDqCEYmtD3NzHTzQXcu9Oj3U+CKYCU4D+kwEN5BuKs5J9lPFA9B2MsK9MYsyXoG4Gkt3ENHyzY+dgCN3eLdyiyOAtpHKddqO+5CG3mZoTlONTSofZm2pbnCoWh8UdKlBUvD467gFbw+HcBnXXY89zhdBIkhjQELcuZc0je8XsYrw++9DEJW9GBlREE8E/RustYlF5/MsNHvIxZqKNhBocX4Cj/nUdV+aGxTMa4pEnFi8gDA8xuYK9mDA/GNFd47TMa6kd+YLlojlfzp1GGDiwDK1px1TpjjzXan/dMMFbCsL5dgpuFul34U0yOdg7iEgoAUUwTGvHQsMyIl+BJ sergio@MININT-SCP2P2V',
+            'vnet_name': getenv("AZURE_VNET_NAME", 'myNetwork'),
+    }
+       
+    virtualMachine = {
+        'name':'sergio',
+        'description':'new VM',
+        'status': 'running',
+        'image': {
+            'publisher': 'Canonical',
+            'offer': 'UbuntuServer',
+            'sku': '16.04.0-LTS',
+            'version': 'latest'
+        },
+        'hardware_profile': {
+            'vm_size': 'Standard_DS1_v2'
+        },
+        'networks': [
+            'sergio'
+        ]
+    }
+
+    vnet_config = {
+        'subnet_address': '10.1.2.0/24',
+        #'subnet_name': 'subnet-oam'
+    }
+    ###########################
+
+    azure=vimconnector(vim_id, vim_name, tenant_id=test_params["tenant"], tenant_name=None, url=None, url_admin=None,
+                       user=test_params["client_id"], passwd=test_params["secret"], log_level=None, config=config)
+
+    #azure.get_flavor_id_from_data("here")
+    #subnets=azure.get_network_list()
+    #azure.new_vminstance(virtualMachine['name'], virtualMachine['description'], virtualMachine['status'], virtualMachine['image'], virtualMachine['hardware_profile']['vm_size'], subnets)
+    #
+    azure.get_flavor("Standard_A11")
\ No newline at end of file