1 # -*- coding: utf-8 -*-
3 __author__
='Sergio Gonzalez'
4 __date__
='$18-apr-2019 23:59:59$'
10 from uuid
import uuid4
12 from azure
.common
.credentials
import ServicePrincipalCredentials
13 from azure
.mgmt
.resource
import ResourceManagementClient
14 from azure
.mgmt
.network
import NetworkManagementClient
15 from azure
.mgmt
.compute
import ComputeManagementClient
18 class vimconnector(vimconn
.vimconnector
):
20 def __init__(self
, uuid
, name
, tenant_id
, tenant_name
, url
, url_admin
=None, user
=None, passwd
=None, log_level
=None,
21 config
={}, persistent_info
={}):
23 vimconn
.vimconnector
.__init
__(self
, uuid
, name
, tenant_id
, tenant_name
, url
, url_admin
, user
, passwd
, log_level
,
24 config
, persistent_info
)
27 self
.logger
= logging
.getLogger('openmano.vim.azure')
30 self
.logger
.setLevel(getattr(logging
, log_level
))
33 self
.credentials
= ServicePrincipalCredentials(
36 tenant
=(tenant_id
or tenant_name
)
40 if 'subscription_id' in config
:
41 self
.subscription_id
= config
.get('subscription_id')
42 self
.logger
.debug('Setting subscription '+str(self
.subscription_id
))
44 raise vimconn
.vimconnException('Subscription not specified')
46 if 'region_name' in config
:
47 self
.region
= config
.get('region_name')
49 raise vimconn
.vimconnException('Azure region_name is not specified at config')
51 if 'resource_group' in config
:
52 self
.resource_group
= config
.get('resource_group')
54 raise vimconn
.vimconnException('Azure resource_group is not specified at config')
56 if 'vnet_name' in config
:
57 self
.vnet_name
= config
["vnet_name"]
60 self
.pub_key
= config
.get('pub_key')
62 def _reload_connection(self
):
64 Sets connections to work with Azure service APIs
67 self
.logger
.debug('Reloading API Connection')
69 self
.conn
= ResourceManagementClient(self
.credentials
, self
.subscription_id
)
70 self
.conn_compute
= ComputeManagementClient(self
.credentials
, self
.subscription_id
)
71 self
.conn_vnet
= NetworkManagementClient(self
.credentials
, self
.subscription_id
)
72 self
._check
_or
_create
_resource
_group
()
73 self
._check
_or
_create
_vnet
()
74 except Exception as e
:
75 self
.format_vimconn_exception(e
)
77 def _get_resource_name_from_resource_id(self
, resource_id
):
78 return str(resource_id
.split('/')[-1])
80 def _get_location_from_resource_group(self
, resource_group_name
):
81 return self
.conn
.resource_groups
.get(resource_group_name
).location
83 def _get_resource_group_name_from_resource_id(self
, resource_id
):
84 return str(resource_id
.split('/')[4])
86 def _check_subnets_for_vm(self
, net_list
):
87 # All subnets must belong to the same resource group and vnet
88 if len(set(self
._get
_resource
_group
_name
_from
_resource
_id
(net
['id']) +
89 self
._get
_resource
_name
_from
_resource
_id
(net
['id']) for net
in net_list
)) != 1:
90 raise self
.format_vimconn_exception('Azure VMs can only attach to subnets in same VNET')
92 def format_vimconn_exception(self
, e
):
94 Params: an Exception object
96 :return: Raises the proper vimconnException
100 raise vimconn
.vimconnConnectionException(type(e
).__name
__ + ': ' + str(e
))
102 def _check_or_create_resource_group(self
):
104 Creates a resource group in indicated region
107 self
.logger
.debug('Creating RG {} in location {}'.format(self
.resource_group
, self
.region
))
108 self
.conn
.resource_groups
.create_or_update(self
.resource_group
, {'location': self
.region
})
110 def _check_or_create_vnet(self
):
113 'location': self
.region
,
115 'address_prefixes': "10.0.0.0/8"
118 self
.conn_vnet
.virtual_networks
.create_or_update(self
.resource_group
, self
.vnet_name
, vnet_params
)
119 except Exception as e
:
120 self
.format_vimconn_exception(e
)
122 def new_network(self
, net_name
, net_type
, ip_profile
=None, shared
=False, vlan
=None):
124 Adds a tenant network to VIM
125 :param net_name: name of the network
127 :param ip_profile: is a dict containing the IP parameters of the network (Currently only IPv4 is implemented)
128 'ip-version': can be one of ['IPv4','IPv6']
129 'subnet-address': ip_prefix_schema, that is X.X.X.X/Y
130 'gateway-address': (Optional) ip_schema, that is X.X.X.X
131 'dns-address': (Optional) ip_schema,
132 'dhcp': (Optional) dict containing
133 'enabled': {'type': 'boolean'},
134 'start-address': ip_schema, first IP to grant
135 'count': number of IPs to grant.
138 :return: a tuple with the network identifier and created_items, or raises an exception on error
139 created_items can be None or a dictionary where this method can include key-values that will be passed to
140 the method delete_network. Can be used to store created segments, created l2gw connections, etc.
141 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
145 return self
._new
_subnet
(net_name
, ip_profile
)
147 def _new_subnet(self
, net_name
, ip_profile
):
149 Adds a tenant network to VIM. It creates a new VNET with a single subnet
154 self
.logger
.debug('Adding a subnet to VNET '+self
.vnet_name
)
155 self
._reload
_connection
()
157 if ip_profile
is None:
158 # TODO get a non used vnet ip range /24 and allocate automatically
159 raise vimconn
.vimconnException('Azure cannot create VNET with no CIDR')
163 'location': self
.region
,
165 'address_prefixes': [ip_profile
['subnet_address']]
169 'name': "{}-{}".format(net_name
[:24], uuid4()),
170 'address_prefix': ip_profile
['subnet_address']
174 self
.conn_vnet
.virtual_networks
.create_or_update(self
.resource_group
, self
.vnet_name
, vnet_params
)
175 # TODO return a tuple (subnet-ID, None)
176 except Exception as e
:
177 self
.format_vimconn_exception(e
)
179 def _create_nic(self
, subnet_id
, nic_name
, static_ip
=None):
180 self
._reload
_connection
()
182 resource_group_name
=self
._get
_resource
_group
_name
_from
_resource
_id
(subnet_id
)
183 location
= self
._get
_location
_from
_resource
_group
(resource_group_name
)
186 async_nic_creation
= self
.conn_vnet
.network_interfaces
.create_or_update(
190 'location': location
,
191 'ip_configurations': [{
192 'name': nic_name
+ 'ipconfiguration',
193 'privateIPAddress': static_ip
,
194 'privateIPAllocationMethod': 'Static',
202 async_nic_creation
= self
.conn_vnet
.network_interfaces
.create_or_update(
206 'location': location
,
207 'ip_configurations': [{
208 'name': nic_name
+ 'ipconfiguration',
216 return async_nic_creation
.result()
218 def get_image_list(self
, filter_dict
={}):
220 The urn contains for marketplace 'publisher:offer:sku:version'
227 self
._reload
_connection
()
228 if filter_dict
.get("name"):
229 params
= filter_dict
["name"].split(":")
231 publisher
= params
[0]
237 images
= self
.conn_compute
.virtual_machine_images
.list(self
.region
, publisher
, offer
, sku
)
240 image_version
= str(image
.id).split("/")[-1]
241 if image_version
!= version
:
245 'name': self
._get
_resource
_name
_from
_resource
_id
(image
.id)
249 images
= self
.conn_compute
.virtual_machine_images
.list()
252 # TODO implement filter_dict
254 if filter_dict
.get("id") and str(image
.id) != filter_dict
["id"]:
256 if filter_dict
.get("name") and \
257 self
._get
_resource
_name
_from
_resource
_id
(image
.id) != filter_dict
["name"]:
262 'name': self
._get
_resource
_name
_from
_resource
_id
(image
.id),
266 def get_network_list(self
, filter_dict
={}):
267 """Obtain tenant networks of VIM
273 admin_state_up: boolean
275 Returns the network list of dictionaries
277 self
.logger
.debug('Getting all subnets from VIM')
279 self
._reload
_connection
()
280 vnet
= self
.conn_vnet
.virtual_networks
.get(self
.config
["resource_group"], self
.vnet_name
)
283 for subnet
in vnet
.subnets
:
284 # TODO implement filter_dict
286 if filter_dict
.get("id") and str(subnet
.id) != filter_dict
["id"]:
288 if filter_dict
.get("name") and \
289 self
._get
_resource
_name
_from
_resource
_id
(subnet
.id) != filter_dict
["name"]:
293 'id': str(subnet
.id),
294 'name': self
._get
_resource
_name
_from
_resource
_id
(subnet
.id),
295 'status': str(vnet
.provisioning_state
), # TODO Does subnet contains status???
296 'cidr_block': str(subnet
.address_prefix
)
300 except Exception as e
:
301 self
.format_vimconn_exception(e
)
303 def new_vminstance(self
, vm_name
, description
, start
, image_id
, flavor_id
, net_list
, cloud_config
=None,
304 disk_list
=None, availability_zone_index
=None, availability_zone_list
=None):
306 return self
._new
_vminstance
(vm_name
, image_id
, flavor_id
, net_list
)
308 def _new_vminstance(self
, vm_name
, image_id
, flavor_id
, net_list
, cloud_config
=None, disk_list
=None,
309 availability_zone_index
=None, availability_zone_list
=None):
311 self
._check
_subnets
_for
_vm
(net_list
)
313 for idx
, net
in enumerate(net_list
):
314 subnet_id
=net
['subnet_id']
315 nic_name
= vm_name
+ '-nic-'+str(idx
)
316 vm_nic
= self
._create
_nic
(subnet_id
, nic_name
)
317 vm_nics
.append({ 'id': str(vm_nic
.id)})
321 'location': self
.region
,
323 'computer_name': vm_name
, # TODO if vm_name cannot be repeated add uuid4() suffix
324 'admin_username': 'sergio', # TODO is it mandatory???
325 'linuxConfiguration': {
326 'disablePasswordAuthentication': 'true',
330 'path': '/home/sergio/.ssh/authorized_keys',
331 'keyData': self
.pub_key
338 'hardware_profile': {
342 'image_reference': image_id
345 'network_interfaces': [
350 creation_result
= self
.conn_compute
.virtual_machines
.create_or_update(
356 run_command_parameters
= {
357 'command_id': 'RunShellScript', # For linux, don't change it
359 'date > /home/sergio/test.txt'
362 poller
= self
.conn_compute
.virtual_machines
.run_command(
365 run_command_parameters
367 # TODO return a tuple (vm-ID, None)
368 except Exception as e
:
369 self
.format_vimconn_exception(e
)
371 def get_flavor_id_from_data(self
, flavor_dict
):
372 self
.logger
.debug("Getting flavor id from data")
373 self
._reload
_connection
()
374 vm_sizes_list
= [vm_size
.serialize() for vm_size
in self
.conn_compute
.virtual_machine_sizes
.list(self
.region
)]
376 cpus
= flavor_dict
['vcpus']
377 memMB
= flavor_dict
['ram']
379 filteredSizes
= [size
for size
in vm_sizes_list
if size
['numberOfCores'] > cpus
and size
['memoryInMB'] > memMB
]
380 listedFilteredSizes
= sorted(filteredSizes
, key
=lambda k
: k
['numberOfCores'])
382 return listedFilteredSizes
[0]['name']
384 def check_vim_connectivity(self
):
386 self
._reload
_connection
()
388 except Exception as e
:
389 raise vimconn
.vimconnException("Connectivity issue with Azure API: {}".format(e
))
391 def get_network(self
, net_id
):
392 resGroup
= self
._get
_resource
_group
_name
_from
_resource
_id
(net_id
)
393 resName
= self
._get
_resource
_name
_from
_resource
_id
(net_id
)
395 self
._reload
_connection
()
396 vnet
= self
.conn_vnet
.virtual_networks
.get(resGroup
, resName
)
400 def delete_network(self
, net_id
):
401 resGroup
= self
._get
_resource
_group
_name
_from
_resource
_id
(net_id
)
402 resName
= self
._get
_resource
_name
_from
_resource
_id
(net_id
)
404 self
._reload
_connection
()
405 self
.conn_vnet
.virtual_networks
.delete(resGroup
, resName
)
407 def delete_vminstance(self
, vm_id
):
408 resGroup
= self
._get
_resource
_group
_name
_from
_resource
_id
(net_id
)
409 resName
= self
._get
_resource
_name
_from
_resource
_id
(net_id
)
411 self
._reload
_connection
()
412 self
.conn_compute
.virtual_machines
.delete(resGroup
, resName
)
414 def get_vminstance(self
, vm_id
):
415 resGroup
= self
._get
_resource
_group
_name
_from
_resource
_id
(net_id
)
416 resName
= self
._get
_resource
_name
_from
_resource
_id
(net_id
)
418 self
._reload
_connection
()
419 vm
=self
.conn_compute
.virtual_machines
.get(resGroup
, resName
)
423 def get_flavor(self
, flavor_id
):
424 self
._reload
_connection
()
425 for vm_size
in self
.conn_compute
.virtual_machine_sizes
.list(self
.region
):
426 if vm_size
.name
== flavor_id
:
430 # TODO refresh_nets_status ver estado activo
431 # TODO refresh_vms_status ver estado activo
432 # TODO get_vminstance_console for getting console
434 if __name__
== "__main__":
436 # Making some basic test
439 needed_test_params
= {
440 "client_id": "AZURE_CLIENT_ID",
441 "secret": "AZURE_SECRET",
442 "tenant": "AZURE_TENANT",
443 "resource_group": "AZURE_RESOURCE_GROUP",
444 "subscription_id": "AZURE_SUBSCRIPTION_ID",
445 "vnet_name": "AZURE_VNET_NAME",
449 for param
, env_var
in needed_test_params
.items():
450 value
= getenv(env_var
)
452 raise Exception("Provide a valid value for env '{}'".format(env_var
))
453 test_params
[param
] = value
456 'region_name': getenv("AZURE_REGION_NAME", 'westeurope'),
457 'resource_group': getenv("AZURE_RESOURCE_GROUP"),
458 'subscription_id': getenv("AZURE_SUBSCRIPTION_ID"),
459 'pub_key': getenv("AZURE_PUB_KEY", None),
460 'vnet_name': getenv("AZURE_VNET_NAME", 'myNetwork'),
465 'description': 'new VM',
468 'publisher': 'Canonical',
469 'offer': 'UbuntuServer',
470 'sku': '16.04.0-LTS',
473 'hardware_profile': {
474 'vm_size': 'Standard_DS1_v2'
482 'subnet_address': '10.1.2.0/24',
483 #'subnet_name': 'subnet-oam'
485 ###########################
487 azure
= vimconnector(vim_id
, vim_name
, tenant_id
=test_params
["tenant"], tenant_name
=None, url
=None, url_admin
=None,
488 user
=test_params
["client_id"], passwd
=test_params
["secret"], log_level
=None, config
=config
)
490 # azure.get_flavor_id_from_data("here")
491 # subnets=azure.get_network_list()
492 # azure.new_vminstance(virtualMachine['name'], virtualMachine['description'], virtualMachine['status'],
493 # virtualMachine['image'], virtualMachine['hardware_profile']['vm_size'], subnets)
495 azure
.get_flavor("Standard_A11")