Azure Connector Improvemets
[osm/RO.git] / osm_ro / vimconn_azure.py
1 # -*- coding: utf-8 -*-
2
3 __author__='Sergio Gonzalez'
4 __date__ ='$18-apr-2019 23:59:59$'
5
6 import base64
7
8 import vimconn
9 import logging
10 import netaddr
11
12 from os import getenv
13 from uuid import uuid4
14
15 from azure.common.credentials import ServicePrincipalCredentials
16 from azure.mgmt.resource import ResourceManagementClient
17 from azure.mgmt.network import NetworkManagementClient
18 from azure.mgmt.compute import ComputeManagementClient
19
20
21
22 from msrestazure.azure_exceptions import CloudError
23
24 if getenv('OSMRO_PDB_DEBUG'):
25 import sys
26 print(sys.path)
27 import pdb
28 pdb.set_trace()
29
30
31 class vimconnector(vimconn.vimconnector):
32
33 provision_state2osm = {
34 "Deleting": "INACTIVE",
35 "Failed": "ERROR",
36 "Succeeded": "ACTIVE",
37 "Updating": "BUILD",
38 }
39
40 def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None, log_level=None,
41 config={}, persistent_info={}):
42
43 vimconn.vimconnector.__init__(self, uuid, name, tenant_id, tenant_name, url, url_admin, user, passwd, log_level,
44 config, persistent_info)
45
46 self.vnet_address_space = None
47 # LOGGER
48 self.logger = logging.getLogger('openmano.vim.azure')
49 if log_level:
50 logging.basicConfig()
51 self.logger.setLevel(getattr(logging, log_level))
52
53 # CREDENTIALS
54 self.credentials = ServicePrincipalCredentials(
55 client_id=user,
56 secret=passwd,
57 tenant=(tenant_id or tenant_name)
58 )
59
60 self.tenant=(tenant_id or tenant_name)
61
62 # SUBSCRIPTION
63 if 'subscription_id' in config:
64 self.subscription_id = config.get('subscription_id')
65 self.logger.debug('Setting subscription '+str(self.subscription_id))
66 else:
67 raise vimconn.vimconnException('Subscription not specified')
68 # REGION
69 if 'region_name' in config:
70 self.region = config.get('region_name')
71 else:
72 raise vimconn.vimconnException('Azure region_name is not specified at config')
73 # RESOURCE_GROUP
74 if 'resource_group' in config:
75 self.resource_group = config.get('resource_group')
76 else:
77 raise vimconn.vimconnException('Azure resource_group is not specified at config')
78 # VNET_NAME
79 if 'vnet_name' in config:
80 self.vnet_name = config["vnet_name"]
81
82 # public ssh key
83 self.pub_key = config.get('pub_key')
84
85 def _reload_connection(self):
86 """
87 Sets connections to work with Azure service APIs
88 :return:
89 """
90 self.logger.debug('Reloading API Connection')
91 try:
92 self.conn = ResourceManagementClient(self.credentials, self.subscription_id)
93 self.conn_compute = ComputeManagementClient(self.credentials, self.subscription_id)
94 self.conn_vnet = NetworkManagementClient(self.credentials, self.subscription_id)
95 self._check_or_create_resource_group()
96 self._check_or_create_vnet()
97 except Exception as e:
98 self.format_vimconn_exception(e)
99
100 def _get_resource_name_from_resource_id(self, resource_id):
101
102 try:
103 resource=str(resource_id.split('/')[-1])
104 return resource
105 except Exception as e:
106 raise vimconn.vimconnNotFoundException("Resource name '{}' not found".format(resource_id))
107
108 def _get_location_from_resource_group(self, resource_group_name):
109
110 try:
111 location=self.conn.resource_groups.get(resource_group_name).location
112 return location
113 except Exception as e:
114 raise vimconn.vimconnNotFoundException("Location '{}' not found".format(resource_group_name))
115
116
117 def _get_resource_group_name_from_resource_id(self, resource_id):
118
119 try:
120 rg=str(resource_id.split('/')[4])
121 return rg
122 except Exception as e:
123 raise vimconn.vimconnNotFoundException("Resource group '{}' not found".format(resource_id))
124
125
126 def _get_net_name_from_resource_id(self, resource_id):
127
128 try:
129 net_name=str(resource_id.split('/')[8])
130 return net_name
131 except Exception as e:
132 raise vimconn.vimconnNotFoundException("Net name '{}' not found".format(resource_id))
133
134
135 def _check_subnets_for_vm(self, net_list):
136 # All subnets must belong to the same resource group and vnet
137 # ERROR
138 # File "/root/RO/build/osm_ro/vimconn_azure.py", line 110, in <genexpr>
139 # self._get_resource_name_from_resource_id(net['id']) for net in net_list)) != 1:
140 #if len(set(self._get_resource_group_name_from_resource_id(net['net_id']) +
141 # self._get_resource_name_from_resource_id(net['net_id']) for net in net_list)) != 2:
142 # raise self.format_vimconn_exception('Azure VMs can only attach to subnets in same VNET')
143 self.logger.debug('Checking subnets for VM')
144 num_elem_set = len(set(self._get_resource_group_name_from_resource_id(net['net_id']) +
145 self._get_resource_name_from_resource_id(net['net_id']) for net in net_list))
146
147 if ( num_elem_set != 1 ):
148 raise self.format_vimconn_exception('Azure VMs can only attach to subnets in same VNET')
149
150 def format_vimconn_exception(self, e):
151 """
152 Params: an Exception object
153 :param e:
154 :return: Raises the proper vimconnException
155 """
156 self.conn = None
157 self.conn_vnet = None
158 raise vimconn.vimconnException(type(e).__name__ + ': ' + str(e))
159
160 def _check_or_create_resource_group(self):
161 """
162 Creates a resource group in indicated region
163 :return: None
164 """
165 self.logger.debug('Creating RG {} in location {}'.format(self.resource_group, self.region))
166 self.conn.resource_groups.create_or_update(self.resource_group, {'location': self.region})
167
168 def _check_or_create_vnet(self):
169
170 try:
171 vnet = self.conn_vnet.virtual_networks.get(self.resource_group, self.vnet_name)
172 self.vnet_address_space = vnet.address_space.address_prefixes[0]
173 self.vnet_id = vnet.id
174
175 return
176 except CloudError as e:
177 if e.error.error == "ResourceNotFound":
178 pass
179 else:
180 raise
181 # if not exist, creates it
182 try:
183 vnet_params = {
184 'location': self.region,
185 'address_space': {
186 'address_prefixes': ["10.0.0.0/8"]
187 },
188 }
189 self.vnet_address_space = "10.0.0.0/8"
190
191 self.conn_vnet.virtual_networks.create_or_update(self.resource_group, self.vnet_name, vnet_params)
192 vnet = self.conn_vnet.virtual_networks.get(self.resource_group, self.vnet_name)
193 self.vnet_id = vnet.id
194 except Exception as e:
195 self.format_vimconn_exception(e)
196
197 def new_network(self, net_name, net_type, ip_profile=None, shared=False, vlan=None):
198 """
199 Adds a tenant network to VIM
200 :param net_name: name of the network
201 :param net_type:
202 :param ip_profile: is a dict containing the IP parameters of the network (Currently only IPv4 is implemented)
203 'ip-version': can be one of ['IPv4','IPv6']
204 'subnet-address': ip_prefix_schema, that is X.X.X.X/Y
205 'gateway-address': (Optional) ip_schema, that is X.X.X.X
206 'dns-address': (Optional) ip_schema,
207 'dhcp': (Optional) dict containing
208 'enabled': {'type': 'boolean'},
209 'start-address': ip_schema, first IP to grant
210 'count': number of IPs to grant.
211 :param shared:
212 :param vlan:
213 :return: a tuple with the network identifier and created_items, or raises an exception on error
214 created_items can be None or a dictionary where this method can include key-values that will be passed to
215 the method delete_network. Can be used to store created segments, created l2gw connections, etc.
216 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
217 as not present.
218 """
219 return self._new_subnet(net_name, ip_profile)
220
221 def _new_subnet(self, net_name, ip_profile):
222 """
223 Adds a tenant network to VIM. It creates a new VNET with a single subnet
224 :param net_name:
225 :param ip_profile:
226 :return:
227 """
228 self.logger.debug('Adding a subnet to VNET '+self.vnet_name)
229 self._reload_connection()
230
231 if ip_profile is None:
232 # get a non used vnet ip range /24 and allocate automatically inside the range self.vnet_address_space
233 used_subnets = self.get_network_list()
234 for ip_range in netaddr.IPNetwork(self.vnet_address_space).subnet(24):
235 for used_subnet in used_subnets:
236 subnet_range = netaddr.IPNetwork(used_subnet["cidr_block"])
237 if subnet_range in ip_range or ip_range in subnet_range:
238 # this range overlaps with an existing subnet ip range. Breaks and look for another
239 break
240 else:
241 ip_profile = {"subnet_address": str(ip_range)}
242 self.logger.debug('ip_profile: ' + str(ip_range))
243 break
244 else:
245 vimconn.vimconnException("Cannot find a non-used subnet range in {}".format(self.vnet_address_space))
246 else:
247 ip_profile = {"subnet_address": ip_profile['subnet_address']}
248
249 try:
250 #subnet_name = "{}-{}".format(net_name[:24], uuid4())
251 subnet_name = net_name[:24]
252 subnet_params= {
253 'address_prefix': ip_profile['subnet_address']
254 }
255 self.logger.debug('subnet_name : {}'.format(subnet_name))
256 async_creation=self.conn_vnet.subnets.create_or_update(self.resource_group, self.vnet_name, subnet_name, subnet_params)
257 async_creation.wait()
258
259 #return "{}/subnet/{}".format(self.vnet_id, subnet_name), None
260 return "{}/subnets/{}".format(self.vnet_id, subnet_name), None
261 except Exception as e:
262 self.format_vimconn_exception(e)
263
264 def _create_nic(self, net, nic_name, static_ip=None):
265
266 self._reload_connection()
267
268 subnet_id = net['net_id']
269 location = self._get_location_from_resource_group(self.resource_group)
270
271 try:
272 if static_ip:
273 async_nic_creation = self.conn_vnet.network_interfaces.create_or_update(
274 self.resource_group,
275 nic_name,
276 {
277 'location': location,
278 'ip_configurations': [{
279 'name': nic_name + '-ipconfiguration',
280 'privateIPAddress': static_ip,
281 'privateIPAllocationMethod': 'Static',
282 'subnet': {
283 'id': subnet_id
284 }
285 }]
286 }
287 )
288 async_nic_creation.wait()
289 else:
290 ip_configuration_name = nic_name + '-ipconfiguration'
291 self.logger.debug('Create NIC')
292 async_nic_creation = self.conn_vnet.network_interfaces.create_or_update(
293 self.resource_group,
294 nic_name,
295 {
296 'location': location,
297 'ip_configurations': [{
298 'name': ip_configuration_name,
299 'subnet': {
300 'id': subnet_id
301 }
302 }]
303 }
304 )
305 async_nic_creation.wait()
306
307 public_ip = net.get('floating_ip')
308 if public_ip and public_ip == True:
309 self.logger.debug('Creating PUBLIC IP')
310 public_ip_addess_params = {
311 'location': location,
312 'public_ip_allocation_method': 'Dynamic'
313 }
314 public_ip_name = nic_name + '-public-ip'
315 public_ip = self.conn_vnet.public_ip_addresses.create_or_update(
316 self.resource_group,
317 public_ip_name,
318 public_ip_addess_params
319 )
320 self.logger.debug('Create PUBLIC IP: {}'.format(public_ip.result()))
321
322 # Asociate NIC to Public IP
323 self.logger.debug('Getting NIC DATA')
324 nic_data = self.conn_vnet.network_interfaces.get(
325 self.resource_group,
326 nic_name)
327
328 nic_data.ip_configurations[0].public_ip_address = public_ip.result()
329
330 self.logger.debug('Updating NIC with public IP')
331 self.conn_vnet.network_interfaces.create_or_update(
332 self.resource_group,
333 nic_name,
334 nic_data)
335
336 except Exception as e:
337 self.format_vimconn_exception(e)
338
339 result = async_nic_creation.result()
340 return async_nic_creation.result()
341
342 def new_flavor(self, flavor_data):
343
344 if flavor_data:
345 flavor_id = self.get_flavor_id_from_data(flavor_data)
346
347 if flavor_id != []:
348 return flavor_id
349 else:
350 raise vimconn.vimconnNotFoundException("flavor '{}' not found".format(flavor_data))
351 else:
352 vimconn.vimconnException("There is no data in the flavor_data input parameter")
353
354 def new_tenant(self,tenant_name,tenant_description):
355
356 raise vimconn.vimconnAuthException("It is not possible to create a TENANT in AZURE")
357
358 def new_image(self, image_dict):
359
360 self._reload_connection()
361
362 try:
363 self.logger.debug('new_image - image_dict - {}'.format(image_dict))
364
365 if image_dict.get("name"):
366 image_name = image_dict.get("name")
367 else:
368 raise vimconn.vimconnException("There is no name in the image input data")
369
370 if image_dict.get("location"):
371 params = image_dict["location"].split(":")
372 if len(params) >= 4:
373 publisher = params[0]
374 offer = params[1]
375 sku = params[2]
376 version = params[3]
377 #image_params = {'location': self.region, 'publisher': publisher, 'offer': offer, 'sku': sku, 'version': version }
378 image_params = {'location': self.region}
379
380 self.conn_compute.images.create_or_update()
381 async_creation=self.conn_compute.images.create_or_update(self.resource_group, image_name, image_params)
382 image_id = async_creation.result().id
383 else:
384 raise vimconn.vimconnException("The image location is not correct: {}".format(image_dict["location"]))
385 return image_id
386
387 except Exception as e:
388 self.format_vimconn_exception(e)
389
390 def get_image_id_from_path(self, path):
391 """Get the image id from image path in the VIM database.
392 Returns the image_id or raises a vimconnNotFoundException
393 """
394
395 def get_image_list(self, filter_dict={}):
396 """Obtain tenant images from VIM
397 Filter_dict can be:
398 name: image name
399 id: image uuid
400 checksum: image checksum
401 location: image path
402 Returns the image list of dictionaries:
403 [{<the fields at Filter_dict plus some VIM specific>}, ...]
404 List can be empty
405 """
406 self._reload_connection()
407
408 image_list = []
409 if filter_dict.get("name"):
410 params = filter_dict["name"].split(":")
411 if len(params) >= 3:
412 publisher = params[0]
413 offer = params[1]
414 sku = params[2]
415 version = None
416 if len(params) == 4:
417 version = params[3]
418 images = self.conn_compute.virtual_machine_images.list(self.region, publisher, offer, sku)
419 for image in images:
420 if version:
421 image_version = str(image.id).split("/")[-1]
422 if image_version != version:
423 continue
424 image_list.append({
425 'id': str(image.id),
426 'name': self._get_resource_name_from_resource_id(image.id)
427 })
428
429 return image_list
430
431 def get_network_list(self, filter_dict={}):
432 """Obtain tenant networks of VIM
433 Filter_dict can be:
434 name: network name
435 id: network uuid
436 shared: boolean
437 tenant_id: tenant
438 admin_state_up: boolean
439 status: 'ACTIVE'
440 Returns the network list of dictionaries
441 """
442 self.logger.debug('Getting all subnets from VIM')
443 try:
444 self._reload_connection()
445
446 vnet = self.conn_vnet.virtual_networks.get(self.resource_group, self.vnet_name)
447 subnet_list = []
448
449 for subnet in vnet.subnets:
450
451 if filter_dict:
452 if filter_dict.get("id") and str(subnet.id) != filter_dict["id"]:
453 continue
454 if filter_dict.get("name") and \
455 str(subnet.id) != filter_dict["name"]:
456 continue
457
458 name = self._get_resource_name_from_resource_id(subnet.id)
459
460 subnet_list.append({
461 'id': str(subnet.id),
462 'name': self._get_resource_name_from_resource_id(subnet.id),
463 'status' : self.provision_state2osm[subnet.provisioning_state],
464 'cidr_block': str(subnet.address_prefix),
465 'type': 'bridge',
466 'shared': False
467 }
468 )
469
470 return subnet_list
471 except Exception as e:
472 self.format_vimconn_exception(e)
473
474 def new_vminstance(self, vm_name, description, start, image_id, flavor_id, net_list, cloud_config=None,
475 disk_list=None, availability_zone_index=None, availability_zone_list=None):
476
477 return self._new_vminstance(vm_name, image_id, flavor_id, net_list)
478
479 #def _new_vminstance(self, vm_name, image_id, flavor_id, net_list, cloud_config=None, disk_list=None,
480 # availability_zone_index=None, availability_zone_list=None):
481 def new_vminstance(self, name, description, start, image_id, flavor_id, net_list, cloud_config=None,
482 disk_list=None,
483 availability_zone_index=None, availability_zone_list=None):
484
485 self._check_subnets_for_vm(net_list)
486 vm_nics = []
487 for idx, net in enumerate(net_list):
488 # Fault with subnet_id
489 # subnet_id=net['subnet_id']
490 # subnet_id=net['net_id']
491
492 nic_name = name + '-nic-'+str(idx)
493 vm_nic = self._create_nic(net, nic_name)
494 vm_nics.append({ 'id': str(vm_nic.id)})
495
496 try:
497 # image_id are several fields of the image_id
498 image_reference = self.get_image_reference(image_id)
499
500 # The virtual machine name must have less or 64 characters and it can not have the following
501 # characters: (~ ! @ # $ % ^ & * ( ) = + _ [ ] { } \ | ; : ' " , < > / ?.)
502 vm_name_aux = self.check_vm_name(name)
503
504 # cloud-init configuration
505 # cloud config
506 if cloud_config:
507 config_drive, userdata = self._create_user_data(cloud_config)
508 custom_data = base64.b64encode(userdata.encode('utf-8')).decode('latin-1')
509 os_profile = {
510 'computer_name': vm_name_aux, # TODO if vm_name cannot be repeated add uuid4() suffix
511 'admin_username': 'osm', # TODO is it mandatory???
512 'admin_password': 'Osm-osm', # TODO is it mandatory???
513 'custom_data': custom_data
514 }
515 else:
516 os_profile = {
517 'computer_name': vm_name_aux, # TODO if vm_name cannot be repeated add uuid4() suffix
518 'admin_username': 'osm', # TODO is it mandatory???
519 'admin_password': 'Osm-osm', # TODO is it mandatory???
520 }
521
522 vm_parameters = {
523 'location': self.region,
524 'os_profile': os_profile,
525 'hardware_profile': {
526 'vm_size': flavor_id
527 },
528 'storage_profile': {
529 'image_reference': image_reference
530 },
531 'network_profile': {
532 'network_interfaces': [
533 vm_nics[0]
534 ]
535 }
536 }
537
538 creation_result = self.conn_compute.virtual_machines.create_or_update(
539 self.resource_group,
540 vm_name_aux,
541 vm_parameters
542 )
543
544 #creation_result.wait()
545 result = creation_result.result()
546
547 for index, subnet in enumerate(net_list):
548 net_list[index]['vim_id'] = result.id
549
550 if start == True:
551 #self.logger.debug('Arrancamos VM y esperamos')
552 start_result = self.conn_compute.virtual_machines.start(
553 self.resource_group,
554 vm_name_aux)
555 #start_result.wait()
556
557 return result.id, None
558
559 #run_command_parameters = {
560 # 'command_id': 'RunShellScript', # For linux, don't change it
561 # 'script': [
562 # 'date > /tmp/test.txt'
563 # ]
564 #}
565 except Exception as e:
566 #self.logger.debug('AZURE <=== EX: _new_vminstance', exc_info=True)
567 self.format_vimconn_exception(e)
568
569 # It is necesary extract from image_id data to create the VM with this format
570 # 'image_reference': {
571 # 'publisher': vm_reference['publisher'],
572 # 'offer': vm_reference['offer'],
573 # 'sku': vm_reference['sku'],
574 # 'version': vm_reference['version']
575 # },
576 def get_image_reference(self, imagen):
577
578 # The data input format example:
579 # /Subscriptions/ca3d18ab-d373-4afb-a5d6-7c44f098d16a/Providers/Microsoft.Compute/Locations/westeurope/
580 # Publishers/Canonical/ArtifactTypes/VMImage/
581 # Offers/UbuntuServer/
582 # Skus/18.04-LTS/
583 # Versions/18.04.201809110
584 publiser = str(imagen.split('/')[8])
585 offer = str(imagen.split('/')[12])
586 sku = str(imagen.split('/')[14])
587 version = str(imagen.split('/')[16])
588
589 return {
590 'publisher': publiser,
591 'offer': offer,
592 'sku': sku,
593 'version': version
594 }
595
596 # Azure VM names can not have some special characters
597 def check_vm_name( self, vm_name ):
598
599 #chars_not_allowed_list = ['~','!','@','#','$','%','^','&','*','(',')','=','+','_','[',']','{','}','|',';',':','<','>','/','?','.']
600 chars_not_allowed_list = "~!@#$%^&*()=+_[]{}|;:<>/?."
601
602 # First: the VM name max length is 64 characters
603 vm_name_aux = vm_name[:64]
604
605 # Second: replace not allowed characters
606 for elem in chars_not_allowed_list :
607 # Check if string is in the main string
608 if elem in vm_name_aux :
609 #self.logger.debug('Dentro del IF')
610 # Replace the string
611 vm_name_aux = vm_name_aux.replace(elem, '-')
612
613 return vm_name_aux
614
615
616 def get_flavor_id_from_data(self, flavor_dict):
617 self.logger.debug("Getting flavor id from data")
618
619 try:
620 self._reload_connection()
621 vm_sizes_list = [vm_size.serialize() for vm_size in self.conn_compute.virtual_machine_sizes.list(self.region)]
622
623 cpus = flavor_dict['vcpus']
624 memMB = flavor_dict['ram']
625
626 filteredSizes = [size for size in vm_sizes_list if size['numberOfCores'] >= cpus and size['memoryInMB'] >= memMB]
627 listedFilteredSizes = sorted(filteredSizes, key=lambda k: k['numberOfCores'])
628
629 return listedFilteredSizes[0]['name']
630
631 except Exception as e:
632 self.format_vimconn_exception(e)
633
634 def _get_flavor_id_from_flavor_name(self, flavor_name):
635 self.logger.debug("Getting flavor id from falvor name {}".format(flavor_name))
636
637 try:
638 self._reload_connection()
639 vm_sizes_list = [vm_size.serialize() for vm_size in self.conn_compute.virtual_machine_sizes.list(self.region)]
640
641 output_flavor = None
642 for size in vm_sizes_list:
643 if size['name'] == flavor_name:
644 output_flavor = size
645
646 return output_flavor
647
648 except Exception as e:
649 self.format_vimconn_exception(e)
650
651 def check_vim_connectivity(self):
652 try:
653 self._reload_connection()
654 return True
655 except Exception as e:
656 raise vimconn.vimconnException("Connectivity issue with Azure API: {}".format(e))
657
658 def get_network(self, net_id):
659
660 resName = self._get_resource_name_from_resource_id(net_id)
661
662 self._reload_connection()
663
664 filter_dict = {'name' : net_id}
665 network_list = self.get_network_list(filter_dict)
666
667 if not network_list:
668 raise vimconn.vimconnNotFoundException("network '{}' not found".format(net_id))
669 else:
670 return network_list[0]
671
672 # Added created_items because it is neccesary
673 # self.vim.delete_network(net_vim_id, task["extra"].get("created_items"))
674 # TypeError: delete_network() takes exactly 2 arguments (3 given)
675 def delete_network(self, net_id, created_items=None):
676
677 self.logger.debug('Deletting network {} - {}'.format(self.resource_group, net_id))
678
679 resName = self._get_resource_name_from_resource_id(net_id)
680
681 self._reload_connection()
682
683 filter_dict = {'name' : net_id}
684 network_list = self.get_network_list(filter_dict)
685 if not network_list:
686 raise vimconn.vimconnNotFoundException("network '{}' not found".format(net_id))
687
688 try:
689 # Subnet API fails (CloudError: Azure Error: ResourceNotFound)
690 # Put the initial virtual_network API
691 async_delete=self.conn_vnet.subnets.delete(self.resource_group, self.vnet_name, resName)
692 return net_id
693
694 except CloudError as e:
695 if e.error.error == "ResourceNotFound":
696 raise vimconn.vimconnNotFoundException("network '{}' not found".format(net_id))
697 else:
698 raise
699 except Exception as e:
700 self.format_vimconn_exception(e)
701
702
703
704 # Added third parameter because it is necesary
705 def delete_vminstance(self, vm_id, created_items=None):
706
707 self.logger.debug('Deletting VM instance {} - {}'.format(self.resource_group, vm_id))
708 self._reload_connection()
709
710 try:
711
712 resName = self._get_resource_name_from_resource_id(vm_id)
713 vm = self.conn_compute.virtual_machines.get(self.resource_group, resName)
714
715 # Shuts down the virtual machine and releases the compute resources
716 #vm_stop = self.conn_compute.virtual_machines.power_off(self.resource_group, resName)
717 #vm_stop.wait()
718
719 vm_delete = self.conn_compute.virtual_machines.delete(self.resource_group, resName)
720 vm_delete.wait()
721
722 # Delete OS Disk
723 os_disk_name = vm.storage_profile.os_disk.name
724 self.logger.debug('Delete OS DISK - ' + os_disk_name)
725 self.conn_compute.disks.delete(self.resource_group, os_disk_name)
726
727 # After deletting VM, it is necessary delete NIC, because if is not deleted delete_network
728 # does not work because Azure says that is in use the subnet
729 network_interfaces = vm.network_profile.network_interfaces
730
731 for network_interface in network_interfaces:
732
733 #self.logger.debug('nic - {}'.format(network_interface))
734
735 nic_name = self._get_resource_name_from_resource_id(network_interface.id)
736
737 #self.logger.debug('nic_name - {}'.format(nic_name))
738
739 nic_data = self.conn_vnet.network_interfaces.get(
740 self.resource_group,
741 nic_name)
742
743 exist_public_ip = nic_data.ip_configurations[0].public_ip_address
744 if exist_public_ip:
745 public_ip_id = nic_data.ip_configurations[0].public_ip_address.id
746 self.logger.debug('Public ip id - ' + public_ip_id)
747
748 self.logger.debug('Delete NIC - ' + nic_name)
749 nic_delete = self.conn_vnet.network_interfaces.delete(self.resource_group, nic_name)
750 nic_delete.wait()
751
752 # Delete public_ip
753 public_ip_name = self._get_resource_name_from_resource_id(public_ip_id)
754
755 self.logger.debug('Delete PUBLIC IP - ' + public_ip_name)
756 public_ip = self.conn_vnet.public_ip_addresses.delete(self.resource_group, public_ip_name)
757 except CloudError as e:
758 if e.error.error == "ResourceNotFound":
759 raise vimconn.vimconnNotFoundException("No vminstance found '{}'".format(vm_id))
760 else:
761 raise
762 except Exception as e:
763 self.format_vimconn_exception(e)
764
765 def action_vminstance(self, vm_id, action_dict, created_items={}):
766 """Send and action over a VM instance from VIM
767 Returns the vm_id if the action was successfully sent to the VIM"""
768
769 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
770 try:
771 self._reload_connection()
772 resName = self._get_resource_name_from_resource_id(vm_id)
773 if "start" in action_dict:
774 self.conn_compute.virtual_machines.start(self.resource_group,resName)
775 elif "stop" in action_dict or "shutdown" in action_dict or "shutoff" in action_dict:
776 self.conn_compute.virtual_machines.power_off(self.resource_group,resName)
777 elif "terminate" in action_dict:
778 self.conn_compute.virtual_machines.delete(self.resource_group,resName)
779 elif "reboot" in action_dict:
780 self.conn_compute.virtual_machines.restart(self.resource_group,resName)
781 return None
782 except CloudError as e:
783 if e.error.error == "ResourceNotFound":
784 raise vimconn.vimconnNotFoundException("No vm found '{}'".format(vm_id))
785 else:
786 raise
787 except Exception as e:
788 self.format_vimconn_exception(e)
789
790 def delete_flavor(self, flavor_id):
791
792 raise vimconn.vimconnAuthException("It is not possible to delete a FLAVOR in AZURE")
793
794 def delete_tenant(self,tenant_id,):
795
796 raise vimconn.vimconnAuthException("It is not possible to delete a TENANT in AZURE")
797
798 def delete_image(self, image_id):
799
800 raise vimconn.vimconnAuthException("It is not possible to delete a IMAGE in AZURE")
801
802 def get_vminstance(self, vm_id):
803
804 self._reload_connection()
805 try:
806 resName = self._get_resource_name_from_resource_id(vm_id)
807 vm=self.conn_compute.virtual_machines.get(self.resource_group, resName)
808 except CloudError as e:
809 if e.error.error == "ResourceNotFound":
810 raise vimconn.vimconnNotFoundException("No vminstance found '{}'".format(vm_id))
811 else:
812 raise
813 except Exception as e:
814 self.format_vimconn_exception(e)
815
816 return vm
817
818 def get_flavor(self, flavor_id):
819 self._reload_connection()
820
821 flavor_data = self._get_flavor_id_from_flavor_name(flavor_id)
822 if flavor_data:
823 flavor = {
824 'id': flavor_id,
825 'name': flavor_id,
826 'ram': flavor_data['memoryInMB'],
827 'vcpus': flavor_data['numberOfCores'],
828 'disk': flavor_data['resourceDiskSizeInMB']
829 }
830 return flavor
831 else:
832 raise vimconn.vimconnNotFoundException("flavor '{}' not found".format(flavor_id))
833
834
835 def get_tenant_list(self, filter_dict={}):
836
837 tenants_azure=[{'name': self.tenant, 'id': self.tenant}]
838 tenant_list=[]
839
840 for tenant_azure in tenants_azure:
841 if filter_dict:
842 if filter_dict.get("id") and str(tenant_azure.get("id")) != filter_dict["id"]:
843 continue
844 if filter_dict.get("name") and str(tenant_azure.get("name")) != filter_dict["name"]:
845 continue
846
847 tenant_list.append(tenant_azure)
848
849 return tenant_list
850
851 def refresh_nets_status(self, net_list):
852
853 out_nets = {}
854 self._reload_connection()
855 for net_id in net_list:
856 try:
857 netName = self._get_net_name_from_resource_id(net_id)
858 resName = self._get_resource_name_from_resource_id(net_id)
859
860 net = self.conn_vnet.subnets.get(self.resource_group, netName, resName)
861
862 out_nets[net_id] ={
863 "status": self.provision_state2osm[net.provisioning_state],
864 "vim_info": str(net)
865 }
866 except CloudError as e:
867 if e.error.error == "ResourceNotFound":
868 out_nets[net_id] = {
869 "status": "DELETED",
870 "error_msg": str(e)
871 }
872 else:
873 raise
874 except vimconn.vimconnNotFoundException as e:
875 out_nets[net_id] = {
876 "status": "DELETED",
877 "error_msg": str(e)
878 }
879 except Exception as e:
880 # TODO distinguish when it is deleted
881 out_nets[net_id] = {
882 "status": "VIM_ERROR",
883 "error_msg": str(e)
884 }
885 return out_nets
886
887 def refresh_vms_status(self, vm_list):
888
889 out_vms = {}
890 out_vms_dict = {}
891 self._reload_connection()
892
893 for vm_id in vm_list:
894 try:
895
896 resName = self._get_resource_name_from_resource_id(vm_id)
897
898 vm = self.conn_compute.virtual_machines.get(self.resource_group, resName)
899 out_vms_dict['status'] = self.provision_state2osm[vm.provisioning_state]
900 out_vms_dict['interfaces'] = []
901 interface_dict = {}
902
903 network_interfaces = vm.network_profile.network_interfaces
904
905 for network_interface in network_interfaces:
906
907 nic_name = self._get_resource_name_from_resource_id(network_interface.id)
908 interface_dict['vim_interface_id'] = vm_id
909
910 nic_data = self.conn_vnet.network_interfaces.get(
911 self.resource_group,
912 nic_name)
913
914 private_ip = nic_data.ip_configurations[0].private_ip_address
915
916 interface_dict['mac_address'] = nic_data.mac_address
917 interface_dict['ip_address'] = private_ip
918 out_vms_dict['interfaces'].append(interface_dict)
919
920 except Exception as e:
921 out_vms_dict['status'] = "DELETED"
922 out_vms_dict['error_msg'] = str(e)
923 vm = None
924 finally:
925 if vm:
926 out_vms_dict['vim_info'] = str(vm)
927
928 out_vms[vm_id] = out_vms_dict
929
930 return out_vms
931
932
933 if __name__ == "__main__":
934
935 # Making some basic test
936 vim_id='azure'
937 vim_name='azure'
938 needed_test_params = {
939 "client_id": "AZURE_CLIENT_ID",
940 "secret": "AZURE_SECRET",
941 "tenant": "AZURE_TENANT",
942 "resource_group": "AZURE_RESOURCE_GROUP",
943 "subscription_id": "AZURE_SUBSCRIPTION_ID",
944 "vnet_name": "AZURE_VNET_NAME",
945 }
946 test_params = {}
947
948 for param, env_var in needed_test_params.items():
949 value = getenv(env_var)
950 if not value:
951 raise Exception("Provide a valid value for env '{}'".format(env_var))
952 test_params[param] = value
953
954 config = {
955 'region_name': getenv("AZURE_REGION_NAME", 'westeurope'),
956 'resource_group': getenv("AZURE_RESOURCE_GROUP"),
957 'subscription_id': getenv("AZURE_SUBSCRIPTION_ID"),
958 'pub_key': getenv("AZURE_PUB_KEY", None),
959 'vnet_name': getenv("AZURE_VNET_NAME", 'myNetwork'),
960 }
961
962 virtualMachine = {
963 'name': 'sergio',
964 'description': 'new VM',
965 'status': 'running',
966 'image': {
967 'publisher': 'Canonical',
968 'offer': 'UbuntuServer',
969 'sku': '16.04.0-LTS',
970 'version': 'latest'
971 },
972 'hardware_profile': {
973 'vm_size': 'Standard_DS1_v2'
974 },
975 'networks': [
976 'sergio'
977 ]
978 }
979
980 vnet_config = {
981 'subnet_address': '10.1.2.0/24',
982 #'subnet_name': 'subnet-oam'
983 }
984 ###########################
985
986 azure = vimconnector(vim_id, vim_name, tenant_id=test_params["tenant"], tenant_name=None, url=None, url_admin=None,
987 user=test_params["client_id"], passwd=test_params["secret"], log_level=None, config=config)
988
989 # azure.get_flavor_id_from_data("here")
990 # subnets=azure.get_network_list()
991 # azure.new_vminstance(virtualMachine['name'], virtualMachine['description'], virtualMachine['status'],
992 # virtualMachine['image'], virtualMachine['hardware_profile']['vm_size'], subnets)
993
994 azure.new_network("mynet", None)
995 net_id = "/subscriptions/82f80cc1-876b-4591-9911-1fb5788384fd/resourceGroups/osmRG/providers/Microsoft."\
996 "Network/virtualNetworks/test"
997 net_id_not_found = "/subscriptions/82f80cc1-876b-4591-9911-1fb5788384fd/resourceGroups/osmRG/providers/"\
998 "Microsoft.Network/virtualNetworks/testALF"
999 azure.refresh_nets_status([net_id, net_id_not_found])