--- /dev/null
+# -*- 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