fc366384c179d52854509bad2aed9e0a99d7834d
[osm/RO.git] / RO-VIM-azure / osm_rovim_azure / vimconn_azure.py
1 # -*- coding: utf-8 -*-
2 ##
3 # Licensed under the Apache License, Version 2.0 (the "License"); you may
4 # not use this file except in compliance with the License. You may obtain
5 # a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 # License for the specific language governing permissions and limitations
13 # under the License.
14 ##
15
16 import base64
17 from osm_ro import vimconn
18 import logging
19 import netaddr
20 import re
21
22 from os import getenv
23 from azure.common.credentials import ServicePrincipalCredentials
24 from azure.mgmt.resource import ResourceManagementClient
25 from azure.mgmt.network import NetworkManagementClient
26 from azure.mgmt.compute import ComputeManagementClient
27 from azure.mgmt.compute.models import DiskCreateOption
28 from msrestazure.azure_exceptions import CloudError
29 from msrest.exceptions import AuthenticationError
30 import msrestazure.tools as azure_tools
31 from requests.exceptions import ConnectionError
32
33 __author__ = 'Isabel Lloret, Sergio Gonzalez, Alfonso Tierno'
34 __date__ = '$18-apr-2019 23:59:59$'
35
36
37 if getenv('OSMRO_PDB_DEBUG'):
38 import sys
39 print(sys.path)
40 import pdb
41 pdb.set_trace()
42
43
44 class vimconnector(vimconn.vimconnector):
45
46 # Translate azure provisioning state to OSM provision state
47 # The first three ones are the transitional status once a user initiated action has been requested
48 # Once the operation is complete, it will transition into the states Succeeded or Failed
49 # https://docs.microsoft.com/en-us/azure/virtual-machines/windows/states-lifecycle
50 provision_state2osm = {
51 "Creating": "BUILD",
52 "Updating": "BUILD",
53 "Deleting": "INACTIVE",
54 "Succeeded": "ACTIVE",
55 "Failed": "ERROR"
56 }
57
58 # Translate azure power state to OSM provision state
59 power_state2osm = {
60 "starting": "INACTIVE",
61 "running": "ACTIVE",
62 "stopping": "INACTIVE",
63 "stopped": "INACTIVE",
64 "unknown": "OTHER",
65 "deallocated": "BUILD",
66 "deallocating": "BUILD"
67 }
68
69 AZURE_ZONES = ["1", "2", "3"]
70
71 def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None, log_level=None,
72 config={}, persistent_info={}):
73 """
74 Constructor of VIM. Raise an exception is some needed parameter is missing, but it must not do any connectivity
75 checking against the VIM
76 Using common constructor parameters.
77 In this case: config must include the following parameters:
78 subscription_id: assigned azure subscription identifier
79 region_name: current region for azure network
80 resource_group: used for all azure created resources
81 vnet_name: base vnet for azure, created networks will be subnets from this base network
82 config may also include the following parameter:
83 flavors_pattern: pattern that will be used to select a range of vm sizes, for example
84 "^((?!Standard_B).)*$" will filter out Standard_B range that is cheap but is very overused
85 "^Standard_B" will select a serie B maybe for test environment
86 """
87
88 vimconn.vimconnector.__init__(self, uuid, name, tenant_id, tenant_name, url, url_admin, user, passwd, log_level,
89 config, persistent_info)
90
91 # Variable that indicates if client must be reloaded or initialized
92 self.reload_client = True
93
94 self.vnet_address_space = None
95 # LOGGER
96 self.logger = logging.getLogger('openmano.vim.azure')
97 if log_level:
98 logging.basicConfig()
99 self.logger.setLevel(getattr(logging, log_level))
100
101 self.tenant = (tenant_id or tenant_name)
102
103 # Store config to create azure subscription later
104 self._config = {
105 "user": user,
106 "passwd": passwd,
107 "tenant": tenant_id or tenant_name
108 }
109
110 # SUBSCRIPTION
111 if 'subscription_id' in config:
112 self._config["subscription_id"] = config.get('subscription_id')
113 # self.logger.debug('Setting subscription to: %s', self.config["subscription_id"])
114 else:
115 raise vimconn.vimconnException('Subscription not specified')
116
117 # REGION
118 if 'region_name' in config:
119 self.region = config.get('region_name')
120 else:
121 raise vimconn.vimconnException('Azure region_name is not specified at config')
122
123 # RESOURCE_GROUP
124 if 'resource_group' in config:
125 self.resource_group = config.get('resource_group')
126 else:
127 raise vimconn.vimconnException('Azure resource_group is not specified at config')
128
129 # VNET_NAME
130 if 'vnet_name' in config:
131 self.vnet_name = config["vnet_name"]
132
133 # public ssh key
134 self.pub_key = config.get('pub_key')
135
136 # flavor pattern regex
137 if 'flavors_pattern' in config:
138 self._config['flavors_pattern'] = config['flavors_pattern']
139
140 def _reload_connection(self):
141 """
142 Called before any operation, checks python azure clients
143 """
144 if self.reload_client:
145 self.logger.debug('reloading azure client')
146 try:
147 self.credentials = ServicePrincipalCredentials(
148 client_id=self._config["user"],
149 secret=self._config["passwd"],
150 tenant=self._config["tenant"]
151 )
152 self.conn = ResourceManagementClient(self.credentials, self._config["subscription_id"])
153 self.conn_compute = ComputeManagementClient(self.credentials, self._config["subscription_id"])
154 self.conn_vnet = NetworkManagementClient(self.credentials, self._config["subscription_id"])
155 self._check_or_create_resource_group()
156 self._check_or_create_vnet()
157
158 # Set to client created
159 self.reload_client = False
160 except Exception as e:
161 self._format_vimconn_exception(e)
162
163 def _get_resource_name_from_resource_id(self, resource_id):
164 """
165 Obtains resource_name from the azure complete identifier: resource_name will always be last item
166 """
167 try:
168 resource = str(resource_id.split('/')[-1])
169 return resource
170 except Exception as e:
171 raise vimconn.vimconnException("Unable to get resource name from resource_id '{}' Error: '{}'".
172 format(resource_id, e))
173
174 def _get_location_from_resource_group(self, resource_group_name):
175 try:
176 location = self.conn.resource_groups.get(resource_group_name).location
177 return location
178 except Exception as e:
179 raise vimconn.vimconnNotFoundException("Location '{}' not found".format(resource_group_name))
180
181 def _get_resource_group_name_from_resource_id(self, resource_id):
182
183 try:
184 rg = str(resource_id.split('/')[4])
185 return rg
186 except Exception as e:
187 raise vimconn.vimconnException("Unable to get resource group from invalid resource_id format '{}'".
188 format(resource_id))
189
190 def _get_net_name_from_resource_id(self, resource_id):
191
192 try:
193 net_name = str(resource_id.split('/')[8])
194 return net_name
195 except Exception as e:
196 raise vimconn.vimconnException("Unable to get azure net_name from invalid resource_id format '{}'".
197 format(resource_id))
198
199 def _check_subnets_for_vm(self, net_list):
200 # All subnets must belong to the same resource group and vnet
201 rg_vnet = set(self._get_resource_group_name_from_resource_id(net['net_id']) +
202 self._get_net_name_from_resource_id(net['net_id']) for net in net_list)
203
204 if len(rg_vnet) != 1:
205 raise self._format_vimconn_exception('Azure VMs can only attach to subnets in same VNET')
206
207 def _format_vimconn_exception(self, e):
208 """
209 Transforms a generic or azure exception to a vimcommException
210 """
211 if isinstance(e, vimconn.vimconnException):
212 raise
213 elif isinstance(e, AuthenticationError):
214 raise vimconn.vimconnAuthException(type(e).__name__ + ': ' + str(e))
215 elif isinstance(e, ConnectionError):
216 raise vimconn.vimconnConnectionException(type(e).__name__ + ': ' + str(e))
217 else:
218 # In case of generic error recreate client
219 self.reload_client = True
220 raise vimconn.vimconnException(type(e).__name__ + ': ' + str(e))
221
222 def _check_or_create_resource_group(self):
223 """
224 Creates the base resource group if it does not exist
225 """
226 try:
227 rg_exists = self.conn.resource_groups.check_existence(self.resource_group)
228 if not rg_exists:
229 self.logger.debug("create base rgroup: %s", self.resource_group)
230 self.conn.resource_groups.create_or_update(self.resource_group, {'location': self.region})
231 except Exception as e:
232 self._format_vimconn_exception(e)
233
234 def _check_or_create_vnet(self):
235 """
236 Try to get existent base vnet, in case it does not exist it creates it
237 """
238 try:
239 vnet = self.conn_vnet.virtual_networks.get(self.resource_group, self.vnet_name)
240 self.vnet_address_space = vnet.address_space.address_prefixes[0]
241 self.vnet_id = vnet.id
242 return
243 except CloudError as e:
244 if e.error.error and "notfound" in e.error.error.lower():
245 pass
246 # continue and create it
247 else:
248 self._format_vimconn_exception(e)
249
250 # if it does not exist, create it
251 try:
252 vnet_params = {
253 'location': self.region,
254 'address_space': {
255 'address_prefixes': ["10.0.0.0/8"]
256 },
257 }
258 self.vnet_address_space = "10.0.0.0/8"
259
260 self.logger.debug("create base vnet: %s", self.vnet_name)
261 self.conn_vnet.virtual_networks.create_or_update(self.resource_group, self.vnet_name, vnet_params)
262 vnet = self.conn_vnet.virtual_networks.get(self.resource_group, self.vnet_name)
263 self.vnet_id = vnet.id
264 except Exception as e:
265 self._format_vimconn_exception(e)
266
267 def new_network(self, net_name, net_type, ip_profile=None, shared=False, provider_network_profile=None):
268 """
269 Adds a tenant network to VIM
270 :param net_name: name of the network
271 :param net_type: not used for azure networks
272 :param ip_profile: is a dict containing the IP parameters of the network (Currently only IPv4 is implemented)
273 'ip-version': can be one of ['IPv4','IPv6']
274 'subnet-address': ip_prefix_schema, that is X.X.X.X/Y
275 'gateway-address': (Optional) ip_schema, that is X.X.X.X, not implemented for azure connector
276 'dns-address': (Optional) ip_schema, not implemented for azure connector
277 'dhcp': (Optional) dict containing, not implemented for azure connector
278 'enabled': {'type': 'boolean'},
279 'start-address': ip_schema, first IP to grant
280 'count': number of IPs to grant.
281 :param shared: Not allowed for Azure Connector
282 :param provider_network_profile: (optional) contains {segmentation-id: vlan, provider-network: vim_netowrk}
283 :return: a tuple with the network identifier and created_items, or raises an exception on error
284 created_items can be None or a dictionary where this method can include key-values that will be passed to
285 the method delete_network. Can be used to store created segments, created l2gw connections, etc.
286 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
287 as not present.
288 """
289 return self._new_subnet(net_name, ip_profile)
290
291 def _new_subnet(self, net_name, ip_profile):
292 """
293 Adds a tenant network to VIM. It creates a new subnet at existing base vnet
294 :param net_name: subnet name
295 :param ip_profile:
296 subnet-address: if it is not provided a subnet/24 in the default vnet is created,
297 otherwise it creates a subnet in the indicated address
298 :return: a tuple with the network identifier and created_items, or raises an exception on error
299 """
300 self.logger.debug('create subnet name %s, ip_profile %s', net_name, ip_profile)
301 self._reload_connection()
302
303 if ip_profile is None:
304 # get a non used vnet ip range /24 and allocate automatically inside the range self.vnet_address_space
305 used_subnets = self.get_network_list()
306 for ip_range in netaddr.IPNetwork(self.vnet_address_space).subnet(24):
307 for used_subnet in used_subnets:
308 subnet_range = netaddr.IPNetwork(used_subnet["cidr_block"])
309 if subnet_range in ip_range or ip_range in subnet_range:
310 # this range overlaps with an existing subnet ip range. Breaks and look for another
311 break
312 else:
313 ip_profile = {"subnet_address": str(ip_range)}
314 self.logger.debug('dinamically obtained ip_profile: %s', ip_range)
315 break
316 else:
317 raise vimconn.vimconnException("Cannot find a non-used subnet range in {}".
318 format(self.vnet_address_space))
319 else:
320 ip_profile = {"subnet_address": ip_profile['subnet_address']}
321
322 try:
323 # subnet_name = "{}-{}".format(net_name[:24], uuid4())
324 subnet_params = {
325 'address_prefix': ip_profile['subnet_address']
326 }
327 # Assign a not duplicated net name
328 subnet_name = self._get_unused_subnet_name(net_name)
329
330 self.logger.debug('creating subnet_name: {}'.format(subnet_name))
331 async_creation = self.conn_vnet.subnets.create_or_update(self.resource_group, self.vnet_name,
332 subnet_name, subnet_params)
333 async_creation.wait()
334 self.logger.debug('created subnet_name: {}'.format(subnet_name))
335
336 return "{}/subnets/{}".format(self.vnet_id, subnet_name), None
337 except Exception as e:
338 self._format_vimconn_exception(e)
339
340 def _get_unused_subnet_name(self, subnet_name):
341 """
342 Adds a prefix to the subnet_name with a number in case the indicated name is repeated
343 Checks subnets with the indicated name (without suffix) and adds a suffix with a number
344 """
345 all_subnets = self.conn_vnet.subnets.list(self.resource_group, self.vnet_name)
346 # Filter to subnets starting with the indicated name
347 subnets = list(filter(lambda subnet: (subnet.name.startswith(subnet_name)), all_subnets))
348 net_names = [str(subnet.name) for subnet in subnets]
349
350 # get the name with the first not used suffix
351 name_suffix = 0
352 # name = subnet_name + "-" + str(name_suffix)
353 name = subnet_name # first subnet created will have no prefix
354 while name in net_names:
355 name_suffix += 1
356 name = subnet_name + "-" + str(name_suffix)
357 return name
358
359 def _create_nic(self, net, nic_name, static_ip=None, created_items={}):
360
361 self.logger.debug('create nic name %s, net_name %s', nic_name, net)
362 self._reload_connection()
363
364 subnet_id = net['net_id']
365 location = self._get_location_from_resource_group(self.resource_group)
366 try:
367 net_ifz = {'location': location}
368 net_ip_config = {'name': nic_name + '-ipconfiguration', 'subnet': {'id': subnet_id}}
369 if static_ip:
370 net_ip_config['privateIPAddress'] = static_ip
371 net_ip_config['privateIPAllocationMethod'] = 'Static'
372 net_ifz['ip_configurations'] = [net_ip_config]
373 mac_address = net.get('mac_address')
374 if mac_address:
375 net_ifz['mac_address'] = mac_address
376
377 async_nic_creation = self.conn_vnet.network_interfaces.create_or_update(self.resource_group, nic_name, net_ifz)
378 nic_data = async_nic_creation.result()
379 created_items[nic_data.id] = True
380 self.logger.debug('created nic name %s', nic_name)
381
382 public_ip = net.get('floating_ip')
383 if public_ip:
384 public_ip_address_params = {
385 'location': location,
386 'public_ip_allocation_method': 'Dynamic'
387 }
388 public_ip_name = nic_name + '-public-ip'
389 async_public_ip = self.conn_vnet.public_ip_addresses.create_or_update(
390 self.resource_group,
391 public_ip_name,
392 public_ip_address_params
393 )
394 public_ip = async_public_ip.result()
395 self.logger.debug('created public IP: {}'.format(public_ip))
396
397 # Associate NIC to Public IP
398 nic_data = self.conn_vnet.network_interfaces.get(
399 self.resource_group,
400 nic_name)
401
402 nic_data.ip_configurations[0].public_ip_address = public_ip
403 created_items[public_ip.id] = True
404
405 self.conn_vnet.network_interfaces.create_or_update(
406 self.resource_group,
407 nic_name,
408 nic_data)
409
410 except Exception as e:
411 self._format_vimconn_exception(e)
412
413 return nic_data, created_items
414
415 def new_flavor(self, flavor_data):
416 """
417 It is not allowed to create new flavors in Azure, must always use an existing one
418 """
419 raise vimconn.vimconnAuthException("It is not possible to create new flavors in AZURE")
420
421 def new_tenant(self, tenant_name, tenant_description):
422 """
423 It is not allowed to create new tenants in azure
424 """
425 raise vimconn.vimconnAuthException("It is not possible to create a TENANT in AZURE")
426
427 def new_image(self, image_dict):
428 """
429 It is not allowed to create new images in Azure, must always use an existing one
430 """
431 raise vimconn.vimconnAuthException("It is not possible to create new images in AZURE")
432
433 def get_image_id_from_path(self, path):
434 """Get the image id from image path in the VIM database.
435 Returns the image_id or raises a vimconnNotFoundException
436 """
437 raise vimconn.vimconnAuthException("It is not possible to obtain image from path in AZURE")
438
439 def get_image_list(self, filter_dict={}):
440 """Obtain tenant images from VIM
441 Filter_dict can be:
442 name: image name with the format: publisher:offer:sku:version
443 If some part of the name is provide ex: publisher:offer it will search all availables skus and version
444 for the provided publisher and offer
445 id: image uuid, currently not supported for azure
446 Returns the image list of dictionaries:
447 [{<the fields at Filter_dict plus some VIM specific>}, ...]
448 List can be empty
449 """
450
451 self.logger.debug("get_image_list filter {}".format(filter_dict))
452
453 self._reload_connection()
454 try:
455 image_list = []
456 if filter_dict.get("name"):
457 # name will have the format 'publisher:offer:sku:version'
458 # publisher is required, offer sku and version will be searched if not provided
459 params = filter_dict["name"].split(":")
460 publisher = params[0]
461 if publisher:
462 # obtain offer list
463 offer_list = self._get_offer_list(params, publisher)
464 for offer in offer_list:
465 # obtain skus
466 sku_list = self._get_sku_list(params, publisher, offer)
467 for sku in sku_list:
468 # if version is defined get directly version, else list images
469 if len(params) == 4 and params[3]:
470 version = params[3]
471 image_list = self._get_version_image_list(publisher, offer, sku, version)
472 else:
473 image_list = self._get_sku_image_list(publisher, offer, sku)
474 else:
475 raise vimconn.vimconnAuthException(
476 "List images in Azure must include name param with at least publisher")
477 else:
478 raise vimconn.vimconnAuthException("List images in Azure must include name param with at"
479 " least publisher")
480
481 return image_list
482 except Exception as e:
483 self._format_vimconn_exception(e)
484
485 def _get_offer_list(self, params, publisher):
486 """
487 Helper method to obtain offer list for defined publisher
488 """
489 if len(params) >= 2 and params[1]:
490 return [params[1]]
491 else:
492 try:
493 # get list of offers from azure
494 result_offers = self.conn_compute.virtual_machine_images.list_offers(self.region, publisher)
495 return [offer.name for offer in result_offers]
496 except CloudError as e:
497 # azure raises CloudError when not found
498 self.logger.info("error listing offers for publisher {}, Error: {}".format(publisher, e))
499 return []
500
501 def _get_sku_list(self, params, publisher, offer):
502 """
503 Helper method to obtain sku list for defined publisher and offer
504 """
505 if len(params) >= 3 and params[2]:
506 return [params[2]]
507 else:
508 try:
509 # get list of skus from azure
510 result_skus = self.conn_compute.virtual_machine_images.list_skus(self.region, publisher, offer)
511 return [sku.name for sku in result_skus]
512 except CloudError as e:
513 # azure raises CloudError when not found
514 self.logger.info("error listing skus for publisher {}, offer {}, Error: {}".format(publisher, offer, e))
515 return []
516
517 def _get_sku_image_list(self, publisher, offer, sku):
518 """
519 Helper method to obtain image list for publisher, offer and sku
520 """
521 image_list = []
522 try:
523 result_images = self.conn_compute.virtual_machine_images.list(self.region, publisher, offer, sku)
524 for result_image in result_images:
525 image_list.append({
526 'id': str(result_image.id),
527 'name': ":".join([publisher, offer, sku, result_image.name])
528 })
529 except CloudError as e:
530 self.logger.info(
531 "error listing skus for publisher {}, offer {}, Error: {}".format(publisher, offer, e))
532 image_list = []
533 return image_list
534
535 def _get_version_image_list(self, publisher, offer, sku, version):
536 image_list = []
537 try:
538 result_image = self.conn_compute.virtual_machine_images.get(self.region, publisher, offer, sku, version)
539 if result_image:
540 image_list.append({
541 'id': str(result_image.id),
542 'name': ":".join([publisher, offer, sku, version])
543 })
544 except CloudError as e:
545 # azure gives CloudError when not found
546 self.logger.info("error listing images for publisher {}, offer {}, sku {}, version {} Error: {}".
547 format(publisher, offer, sku, version, e))
548 image_list = []
549 return image_list
550
551 def get_network_list(self, filter_dict={}):
552 """Obtain tenant networks of VIM
553 Filter_dict can be:
554 name: network name
555 id: network id
556 shared: boolean, not implemented in Azure
557 tenant_id: tenant, not used in Azure, all networks same tenants
558 admin_state_up: boolean, not implemented in Azure
559 status: 'ACTIVE', not implemented in Azure #
560 Returns the network list of dictionaries
561 """
562 # self.logger.debug('getting network list for vim, filter %s', filter_dict)
563 try:
564 self._reload_connection()
565
566 vnet = self.conn_vnet.virtual_networks.get(self.resource_group, self.vnet_name)
567 subnet_list = []
568
569 for subnet in vnet.subnets:
570 if filter_dict:
571 if filter_dict.get("id") and str(subnet.id) != filter_dict["id"]:
572 continue
573 if filter_dict.get("name") and \
574 str(subnet.name) != filter_dict["name"]:
575 continue
576
577 name = self._get_resource_name_from_resource_id(subnet.id)
578
579 subnet_list.append({
580 'id': str(subnet.id),
581 'name': name,
582 'status': self.provision_state2osm[subnet.provisioning_state],
583 'cidr_block': str(subnet.address_prefix),
584 'type': 'bridge',
585 'shared': False
586 })
587
588 return subnet_list
589 except Exception as e:
590 self._format_vimconn_exception(e)
591
592 def new_vminstance(self, name, description, start, image_id, flavor_id, net_list, cloud_config=None,
593 disk_list=None, availability_zone_index=None, availability_zone_list=None):
594
595 self.logger.debug("new vm instance name: %s, image_id: %s, flavor_id: %s, net_list: %s, cloud_config: %s, "
596 "disk_list: %s, availability_zone_index: %s, availability_zone_list: %s",
597 name, image_id, flavor_id, net_list, cloud_config, disk_list,
598 availability_zone_index, availability_zone_list)
599
600 self._reload_connection()
601
602 # Validate input data is valid
603 # The virtual machine name must have less or 64 characters and it can not have the following
604 # characters: (~ ! @ # $ % ^ & * ( ) = + _ [ ] { } \ | ; : ' " , < > / ?.)
605 vm_name = self._check_vm_name(name)
606 # Obtain vm unused name
607 vm_name = self._get_unused_vm_name(vm_name)
608
609 # At least one network must be provided
610 if not net_list:
611 raise vimconn.vimconnException("At least one net must be provided to create a new VM")
612
613 # image_id are several fields of the image_id
614 image_reference = self._get_image_reference(image_id)
615
616
617
618 try:
619 virtual_machine = None
620 created_items = {}
621
622 # Create nics for each subnet
623 self._check_subnets_for_vm(net_list)
624 vm_nics = []
625 for idx, net in enumerate(net_list):
626 # Fault with subnet_id
627 # subnet_id=net['subnet_id']
628 # subnet_id=net['net_id']
629 nic_name = vm_name + '-nic-' + str(idx)
630 vm_nic, nic_items = self._create_nic(net, nic_name, net.get('ip_address'), created_items)
631 vm_nics.append({'id': str(vm_nic.id)})
632 #net['vim_id'] = vm_nic.id
633
634 # cloud-init configuration
635 # cloud config
636 if cloud_config:
637 config_drive, userdata = self._create_user_data(cloud_config)
638 custom_data = base64.b64encode(userdata.encode('utf-8')).decode('latin-1')
639 key_data = None
640 key_pairs = cloud_config.get("key-pairs")
641 if key_pairs:
642 key_data = key_pairs[0]
643
644 if cloud_config.get("users"):
645 user_name = cloud_config.get("users")[0].get("name", "osm")
646 else:
647 user_name = "osm" # DEFAULT USER IS OSM
648
649 os_profile = {
650 'computer_name': vm_name,
651 'admin_username': user_name,
652 'linux_configuration': {
653 "disable_password_authentication": True,
654 "ssh": {
655 "public_keys": [{
656 "path": "/home/{}/.ssh/authorized_keys".format(user_name),
657 "key_data": key_data
658 }]
659 }
660 },
661 'custom_data': custom_data
662 }
663 else:
664 os_profile = {
665 'computer_name': vm_name,
666 'admin_username': 'osm',
667 'admin_password': 'Osm4u!',
668 }
669
670 vm_parameters = {
671 'location': self.region,
672 'os_profile': os_profile,
673 'hardware_profile': {
674 'vm_size': flavor_id
675 },
676 'storage_profile': {
677 'image_reference': image_reference
678 }
679 }
680
681 # If the machine has several networks one must be marked as primary
682 # As it is not indicated in the interface the first interface will be marked as primary
683 if len(vm_nics) > 1:
684 for idx, vm_nic in enumerate(vm_nics):
685 if idx == 0:
686 vm_nics[0]['Primary'] = True
687 else:
688 vm_nics[idx]['Primary'] = False
689
690 vm_parameters['network_profile'] = {'network_interfaces': vm_nics}
691
692 # Obtain zone information
693 vm_zone = self._get_vm_zone(availability_zone_index, availability_zone_list)
694 if vm_zone:
695 vm_parameters['zones'] = [vm_zone]
696
697 self.logger.debug("create vm name: %s", vm_name)
698 creation_result = self.conn_compute.virtual_machines.create_or_update(
699 self.resource_group,
700 vm_name,
701 vm_parameters
702 )
703 virtual_machine = creation_result.result()
704 self.logger.debug("created vm name: %s", vm_name)
705
706 # Add disks if they are provided
707 if disk_list:
708 for disk_index, disk in enumerate(disk_list):
709 self.logger.debug("add disk size: %s, image: %s", disk.get("size"), disk.get("image"))
710 self._add_newvm_disk(virtual_machine, vm_name, disk_index, disk, created_items)
711
712 if start:
713 self.conn_compute.virtual_machines.start(
714 self.resource_group,
715 vm_name)
716 # start_result.wait()
717
718 return virtual_machine.id, created_items
719
720 # run_command_parameters = {
721 # 'command_id': 'RunShellScript', # For linux, don't change it
722 # 'script': [
723 # 'date > /tmp/test.txt'
724 # ]
725 # }
726 except Exception as e:
727 # Rollback vm creacion
728 vm_id = None
729 if virtual_machine:
730 vm_id = virtual_machine.id
731 try:
732 self.logger.debug("exception creating vm try to rollback")
733 self.delete_vminstance(vm_id, created_items)
734 except Exception as e2:
735 self.logger.error("new_vminstance rollback fail {}".format(e2))
736
737 self.logger.debug('Exception creating new vminstance: %s', e, exc_info=True)
738 self._format_vimconn_exception(e)
739
740 def _get_unused_vm_name(self, vm_name):
741 """
742 Checks the vm name and in case it is used adds a suffix to the name to allow creation
743 :return:
744 """
745 all_vms = self.conn_compute.virtual_machines.list(self.resource_group)
746 # Filter to vms starting with the indicated name
747 vms = list(filter(lambda vm: (vm.name.startswith(vm_name)), all_vms))
748 vm_names = [str(vm.name) for vm in vms]
749
750 # get the name with the first not used suffix
751 name_suffix = 0
752 # name = subnet_name + "-" + str(name_suffix)
753 name = vm_name # first subnet created will have no prefix
754 while name in vm_names:
755 name_suffix += 1
756 name = vm_name + "-" + str(name_suffix)
757 return name
758
759 def _get_vm_zone(self, availability_zone_index, availability_zone_list):
760
761 if availability_zone_index is None:
762 return None
763
764 vim_availability_zones = self._get_azure_availability_zones()
765 # check if VIM offer enough availability zones describe in the VNFD
766 if vim_availability_zones and len(availability_zone_list) <= len(vim_availability_zones):
767 # check if all the names of NFV AV match VIM AV names
768 match_by_index = False
769 if not availability_zone_list:
770 match_by_index = True
771 else:
772 for av in availability_zone_list:
773 if av not in vim_availability_zones:
774 match_by_index = True
775 break
776 if match_by_index:
777 return vim_availability_zones[availability_zone_index]
778 else:
779 return availability_zone_list[availability_zone_index]
780 else:
781 raise vimconn.vimconnConflictException("No enough availability zones at VIM for this deployment")
782
783 def _get_azure_availability_zones(self):
784 return self.AZURE_ZONES
785
786 def _add_newvm_disk(self, virtual_machine, vm_name, disk_index, disk, created_items={}):
787
788 disk_name = None
789 data_disk = None
790
791 # Check if must create empty disk or from image
792 if disk.get('vim_id'):
793 # disk already exists, just get
794 parsed_id = azure_tools.parse_resource_id(disk.get('vim_id'))
795 disk_name = parsed_id.get("name")
796 data_disk = self.conn_compute.disks.get(self.resource_group, disk_name)
797 else:
798 disk_name = vm_name + "_DataDisk_" + str(disk_index)
799 if not disk.get("image_id"):
800 self.logger.debug("create new data disk name: %s", disk_name)
801 async_disk_creation = self.conn_compute.disks.create_or_update(
802 self.resource_group,
803 disk_name,
804 {
805 'location': self.region,
806 'disk_size_gb': disk.get("size"),
807 'creation_data': {
808 'create_option': DiskCreateOption.empty
809 }
810 }
811 )
812 data_disk = async_disk_creation.result()
813 created_items[data_disk.id] = True
814 else:
815 image_id = disk.get("image_id")
816 if azure_tools.is_valid_resource_id(image_id):
817 parsed_id = azure_tools.parse_resource_id(image_id)
818
819 # Check if image is snapshot or disk
820 image_name = parsed_id.get("name")
821 type = parsed_id.get("resource_type")
822 if type == 'snapshots' or type == 'disks':
823
824 self.logger.debug("create disk from copy name: %s", image_name)
825 # ¿Should check that snapshot exists?
826 async_disk_creation = self.conn_compute.disks.create_or_update(
827 self.resource_group,
828 disk_name,
829 {
830 'location': self.region,
831 'creation_data': {
832 'create_option': 'Copy',
833 'source_uri': image_id
834 }
835 }
836 )
837 data_disk = async_disk_creation.result()
838 created_items[data_disk.id] = True
839
840 else:
841 raise vimconn.vimconnNotFoundException("Invalid image_id: %s ", image_id)
842 else:
843 raise vimconn.vimconnNotFoundException("Invalid image_id: %s ", image_id)
844
845 # Attach the disk created
846 virtual_machine.storage_profile.data_disks.append({
847 'lun': disk_index,
848 'name': disk_name,
849 'create_option': DiskCreateOption.attach,
850 'managed_disk': {
851 'id': data_disk.id
852 },
853 'disk_size_gb': disk.get('size')
854 })
855 self.logger.debug("attach disk name: %s", disk_name)
856 async_disk_attach = self.conn_compute.virtual_machines.create_or_update(
857 self.resource_group,
858 virtual_machine.name,
859 virtual_machine
860 )
861
862 # It is necesary extract from image_id data to create the VM with this format
863 # 'image_reference': {
864 # 'publisher': vm_reference['publisher'],
865 # 'offer': vm_reference['offer'],
866 # 'sku': vm_reference['sku'],
867 # 'version': vm_reference['version']
868 # },
869 def _get_image_reference(self, image_id):
870
871 try:
872 # The data input format example:
873 # /Subscriptions/ca3d18ab-d373-4afb-a5d6-7c44f098d16a/Providers/Microsoft.Compute/Locations/westeurope/
874 # Publishers/Canonical/ArtifactTypes/VMImage/
875 # Offers/UbuntuServer/
876 # Skus/18.04-LTS/
877 # Versions/18.04.201809110
878 publisher = str(image_id.split('/')[8])
879 offer = str(image_id.split('/')[12])
880 sku = str(image_id.split('/')[14])
881 version = str(image_id.split('/')[16])
882
883 return {
884 'publisher': publisher,
885 'offer': offer,
886 'sku': sku,
887 'version': version
888 }
889 except Exception as e:
890 raise vimconn.vimconnException(
891 "Unable to get image_reference from invalid image_id format: '{}'".format(image_id))
892
893 # Azure VM names can not have some special characters
894 def _check_vm_name(self, vm_name):
895 """
896 Checks vm name, in case the vm has not allowed characters they are removed, not error raised
897 """
898
899 chars_not_allowed_list = "~!@#$%^&*()=+_[]{}|;:<>/?."
900
901 # First: the VM name max length is 64 characters
902 vm_name_aux = vm_name[:64]
903
904 # Second: replace not allowed characters
905 for elem in chars_not_allowed_list:
906 # Check if string is in the main string
907 if elem in vm_name_aux:
908 # self.logger.debug('Dentro del IF')
909 # Replace the string
910 vm_name_aux = vm_name_aux.replace(elem, '-')
911
912 return vm_name_aux
913
914 def get_flavor_id_from_data(self, flavor_dict):
915
916 self.logger.debug("getting flavor id from data, flavor_dict: %s", flavor_dict)
917 filter_dict = flavor_dict or {}
918 try:
919 self._reload_connection()
920 vm_sizes_list = [vm_size.serialize() for vm_size in
921 self.conn_compute.virtual_machine_sizes.list(self.region)]
922
923 cpus = filter_dict.get('vcpus') or 0
924 memMB = filter_dict.get('ram') or 0
925
926 # Filter
927 if self._config.get("flavors_pattern"):
928 filtered_sizes = [size for size in vm_sizes_list if size['numberOfCores'] >= cpus and
929 size['memoryInMB'] >= memMB and
930 re.search(self._config.get("flavors_pattern"), size["name"])]
931 else:
932 filtered_sizes = [size for size in vm_sizes_list if size['numberOfCores'] >= cpus and
933 size['memoryInMB'] >= memMB]
934
935 # Sort
936 listedFilteredSizes = sorted(filtered_sizes, key=lambda k: (k['numberOfCores'], k['memoryInMB'],
937 k['resourceDiskSizeInMB']))
938
939 if listedFilteredSizes:
940 return listedFilteredSizes[0]['name']
941 raise vimconn.vimconnNotFoundException("Cannot find any flavor matching '{}'".format(str(flavor_dict)))
942
943 except Exception as e:
944 self._format_vimconn_exception(e)
945
946 def _get_flavor_id_from_flavor_name(self, flavor_name):
947
948 # self.logger.debug("getting flavor id from flavor name {}".format(flavor_name))
949 try:
950 self._reload_connection()
951 vm_sizes_list = [vm_size.serialize() for vm_size in
952 self.conn_compute.virtual_machine_sizes.list(self.region)]
953
954 output_flavor = None
955 for size in vm_sizes_list:
956 if size['name'] == flavor_name:
957 output_flavor = size
958
959 # None is returned if not found anything
960 return output_flavor
961
962 except Exception as e:
963 self._format_vimconn_exception(e)
964
965 def check_vim_connectivity(self):
966 try:
967 self._reload_connection()
968 return True
969 except Exception as e:
970 raise vimconn.vimconnException("Connectivity issue with Azure API: {}".format(e))
971
972 def get_network(self, net_id):
973
974 # self.logger.debug('get network id: {}'.format(net_id))
975 # res_name = self._get_resource_name_from_resource_id(net_id)
976 self._reload_connection()
977
978 filter_dict = {'name': net_id}
979 network_list = self.get_network_list(filter_dict)
980
981 if not network_list:
982 raise vimconn.vimconnNotFoundException("network '{}' not found".format(net_id))
983 else:
984 return network_list[0]
985
986 def delete_network(self, net_id, created_items=None):
987
988 self.logger.debug('deleting network {} - {}'.format(self.resource_group, net_id))
989
990 self._reload_connection()
991 res_name = self._get_resource_name_from_resource_id(net_id)
992 filter_dict = {'name': res_name}
993 network_list = self.get_network_list(filter_dict)
994 if not network_list:
995 raise vimconn.vimconnNotFoundException("network '{}' not found".format(net_id))
996
997 try:
998 # Subnet API fails (CloudError: Azure Error: ResourceNotFound)
999 # Put the initial virtual_network API
1000 async_delete = self.conn_vnet.subnets.delete(self.resource_group, self.vnet_name, res_name)
1001 async_delete.wait()
1002 return net_id
1003
1004 except CloudError as e:
1005 if e.error.error and "notfound" in e.error.error.lower():
1006 raise vimconn.vimconnNotFoundException("network '{}' not found".format(net_id))
1007 else:
1008 self._format_vimconn_exception(e)
1009 except Exception as e:
1010 self._format_vimconn_exception(e)
1011
1012 def delete_vminstance(self, vm_id, created_items=None):
1013 """ Deletes a vm instance from the vim.
1014 """
1015 self.logger.debug('deleting VM instance {} - {}'.format(self.resource_group, vm_id))
1016 self._reload_connection()
1017
1018 created_items = created_items or {}
1019 try:
1020 # Check vm exists, we can call delete_vm to clean created_items
1021 if vm_id:
1022 res_name = self._get_resource_name_from_resource_id(vm_id)
1023 vm = self.conn_compute.virtual_machines.get(self.resource_group, res_name)
1024
1025 # Shuts down the virtual machine and releases the compute resources
1026 # vm_stop = self.conn_compute.virtual_machines.power_off(self.resource_group, resName)
1027 # vm_stop.wait()
1028
1029 vm_delete = self.conn_compute.virtual_machines.delete(self.resource_group, res_name)
1030 vm_delete.wait()
1031 self.logger.debug('deleted VM name: %s', res_name)
1032
1033 # Delete OS Disk
1034 os_disk_name = vm.storage_profile.os_disk.name
1035 self.logger.debug('delete OS DISK: %s', os_disk_name)
1036 async_disk_delete = self.conn_compute.disks.delete(self.resource_group, os_disk_name)
1037 async_disk_delete.wait()
1038 # os disks are created always with the machine
1039 self.logger.debug('deleted OS DISK name: %s', os_disk_name)
1040
1041 for data_disk in vm.storage_profile.data_disks:
1042 self.logger.debug('delete data_disk: %s', data_disk.name)
1043 async_disk_delete = self.conn_compute.disks.delete(self.resource_group, data_disk.name)
1044 async_disk_delete.wait()
1045 self._markdel_created_item(data_disk.managed_disk.id, created_items)
1046 self.logger.debug('deleted OS DISK name: %s', data_disk.name)
1047
1048 # After deleting VM, it is necessary to delete NIC, because if is not deleted delete_network
1049 # does not work because Azure says that is in use the subnet
1050 network_interfaces = vm.network_profile.network_interfaces
1051
1052 for network_interface in network_interfaces:
1053
1054 nic_name = self._get_resource_name_from_resource_id(network_interface.id)
1055 nic_data = self.conn_vnet.network_interfaces.get(
1056 self.resource_group,
1057 nic_name)
1058
1059 public_ip_name = None
1060 exist_public_ip = nic_data.ip_configurations[0].public_ip_address
1061 if exist_public_ip:
1062 public_ip_id = nic_data.ip_configurations[0].public_ip_address.id
1063
1064 # Delete public_ip
1065 public_ip_name = self._get_resource_name_from_resource_id(public_ip_id)
1066
1067 # Public ip must be deleted afterwards of nic that is attached
1068
1069 self.logger.debug('delete NIC name: %s', nic_name)
1070 nic_delete = self.conn_vnet.network_interfaces.delete(self.resource_group, nic_name)
1071 nic_delete.wait()
1072 self._markdel_created_item(network_interface.id, created_items)
1073 self.logger.debug('deleted NIC name: %s', nic_name)
1074
1075 # Delete list of public ips
1076 if public_ip_name:
1077 self.logger.debug('delete PUBLIC IP - ' + public_ip_name)
1078 ip_delete = self.conn_vnet.public_ip_addresses.delete(self.resource_group, public_ip_name)
1079 ip_delete.wait()
1080 self._markdel_created_item(public_ip_id, created_items)
1081
1082 # Delete created items
1083 self._delete_created_items(created_items)
1084
1085 except CloudError as e:
1086 if e.error.error and "notfound" in e.error.error.lower():
1087 raise vimconn.vimconnNotFoundException("No vm instance found '{}'".format(vm_id))
1088 else:
1089 self._format_vimconn_exception(e)
1090 except Exception as e:
1091 self._format_vimconn_exception(e)
1092
1093 def _markdel_created_item(self, item_id, created_items):
1094 if item_id in created_items:
1095 created_items[item_id] = False
1096
1097 def _delete_created_items(self, created_items):
1098 """ Delete created_items elements that have not been deleted with the virtual machine
1099 Created_items may not be deleted correctly with the created machine if the
1100 virtual machine fails creating or in other cases of error
1101 """
1102 self.logger.debug("Created items: %s", created_items)
1103 # Must delete in order first nics, then public_ips
1104 # As dictionaries don't preserve order, first get items to be deleted then delete them
1105 nics_to_delete = []
1106 publics_ip_to_delete = []
1107 disks_to_delete = []
1108 for item_id, v in created_items.items():
1109 if not v: # skip already deleted
1110 continue
1111
1112 #self.logger.debug("Must delete item id: %s", item_id)
1113
1114 # Obtain type, supported nic, disk or public ip
1115 parsed_id = azure_tools.parse_resource_id(item_id)
1116 resource_type = parsed_id.get("resource_type")
1117 name = parsed_id.get("name")
1118
1119 if resource_type == "networkInterfaces":
1120 nics_to_delete.append(name)
1121 elif resource_type == "publicIPAddresses":
1122 publics_ip_to_delete.append(name)
1123 elif resource_type == "disks":
1124 disks_to_delete.append(name)
1125
1126 # Now delete
1127 for item_name in nics_to_delete:
1128 try:
1129 self.logger.debug("deleting nic name %s:", item_name)
1130 nic_delete = self.conn_vnet.network_interfaces.delete(self.resource_group, item_name)
1131 nic_delete.wait()
1132 self.logger.debug("deleted nic name %s:", item_name)
1133 except Exception as e:
1134 self.logger.error("Error deleting item: {}: {}".format(type(e).__name__, e))
1135
1136 for item_name in publics_ip_to_delete:
1137 try:
1138 self.logger.debug("deleting public ip name %s:", item_name)
1139 ip_delete = self.conn_vnet.public_ip_addresses.delete(self.resource_group, name)
1140 ip_delete.wait()
1141 self.logger.debug("deleted public ip name %s:", item_name)
1142 except Exception as e:
1143 self.logger.error("Error deleting item: {}: {}".format(type(e).__name__, e))
1144
1145 for item_name in disks_to_delete:
1146 try:
1147 self.logger.debug("deleting data disk name %s:", name)
1148 async_disk_delete = self.conn_compute.disks.delete(self.resource_group, item_name)
1149 async_disk_delete.wait()
1150 self.logger.debug("deleted data disk name %s:", name)
1151 except Exception as e:
1152 self.logger.error("Error deleting item: {}: {}".format(type(e).__name__, e))
1153
1154 def action_vminstance(self, vm_id, action_dict, created_items={}):
1155 """Send and action over a VM instance from VIM
1156 Returns the vm_id if the action was successfully sent to the VIM
1157 """
1158
1159 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
1160 try:
1161 self._reload_connection()
1162 resName = self._get_resource_name_from_resource_id(vm_id)
1163 if "start" in action_dict:
1164 self.conn_compute.virtual_machines.start(self.resource_group, resName)
1165 elif "stop" in action_dict or "shutdown" in action_dict or "shutoff" in action_dict:
1166 self.conn_compute.virtual_machines.power_off(self.resource_group, resName)
1167 elif "terminate" in action_dict:
1168 self.conn_compute.virtual_machines.delete(self.resource_group, resName)
1169 elif "reboot" in action_dict:
1170 self.conn_compute.virtual_machines.restart(self.resource_group, resName)
1171 return None
1172 except CloudError as e:
1173 if e.error.error and "notfound" in e.error.error.lower():
1174 raise vimconn.vimconnNotFoundException("No vm found '{}'".format(vm_id))
1175 else:
1176 self._format_vimconn_exception(e)
1177 except Exception as e:
1178 self._format_vimconn_exception(e)
1179
1180 def delete_flavor(self, flavor_id):
1181 raise vimconn.vimconnAuthException("It is not possible to delete a FLAVOR in AZURE")
1182
1183 def delete_tenant(self, tenant_id,):
1184 raise vimconn.vimconnAuthException("It is not possible to delete a TENANT in AZURE")
1185
1186 def delete_image(self, image_id):
1187 raise vimconn.vimconnAuthException("It is not possible to delete a IMAGE in AZURE")
1188
1189 def get_vminstance(self, vm_id):
1190 """
1191 Obtaing the vm instance data from v_id
1192 """
1193 self.logger.debug("get vm instance: %s", vm_id)
1194 self._reload_connection()
1195 try:
1196 resName = self._get_resource_name_from_resource_id(vm_id)
1197 vm = self.conn_compute.virtual_machines.get(self.resource_group, resName)
1198 except CloudError as e:
1199 if e.error.error and "notfound" in e.error.error.lower():
1200 raise vimconn.vimconnNotFoundException("No vminstance found '{}'".format(vm_id))
1201 else:
1202 self._format_vimconn_exception(e)
1203 except Exception as e:
1204 self._format_vimconn_exception(e)
1205
1206 return vm
1207
1208 def get_flavor(self, flavor_id):
1209 """
1210 Obtains the flavor_data from the flavor_id
1211 """
1212 self._reload_connection()
1213 self.logger.debug("get flavor from id: %s", flavor_id)
1214 flavor_data = self._get_flavor_id_from_flavor_name(flavor_id)
1215 if flavor_data:
1216 flavor = {
1217 'id': flavor_id,
1218 'name': flavor_id,
1219 'ram': flavor_data['memoryInMB'],
1220 'vcpus': flavor_data['numberOfCores'],
1221 'disk': flavor_data['resourceDiskSizeInMB']/1024
1222 }
1223 return flavor
1224 else:
1225 raise vimconn.vimconnNotFoundException("flavor '{}' not found".format(flavor_id))
1226
1227 def get_tenant_list(self, filter_dict={}):
1228 """ Obtains the list of tenants
1229 For the azure connector only the azure tenant will be returned if it is compatible
1230 with filter_dict
1231 """
1232 tenants_azure = [{'name': self.tenant, 'id': self.tenant}]
1233 tenant_list = []
1234
1235 self.logger.debug("get tenant list: %s", filter_dict)
1236 for tenant_azure in tenants_azure:
1237 if filter_dict:
1238 if filter_dict.get("id") and str(tenant_azure.get("id")) != filter_dict["id"]:
1239 continue
1240 if filter_dict.get("name") and str(tenant_azure.get("name")) != filter_dict["name"]:
1241 continue
1242
1243 tenant_list.append(tenant_azure)
1244
1245 return tenant_list
1246
1247 def refresh_nets_status(self, net_list):
1248 """Get the status of the networks
1249 Params: the list of network identifiers
1250 Returns a dictionary with:
1251 net_id: #VIM id of this network
1252 status: #Mandatory. Text with one of:
1253 # DELETED (not found at vim)
1254 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1255 # OTHER (Vim reported other status not understood)
1256 # ERROR (VIM indicates an ERROR status)
1257 # ACTIVE, INACTIVE, DOWN (admin down),
1258 # BUILD (on building process)
1259 #
1260 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1261 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1262
1263 """
1264
1265 out_nets = {}
1266 self._reload_connection()
1267
1268 self.logger.debug("reload nets status net_list: %s", net_list)
1269 for net_id in net_list:
1270 try:
1271 netName = self._get_net_name_from_resource_id(net_id)
1272 resName = self._get_resource_name_from_resource_id(net_id)
1273
1274 net = self.conn_vnet.subnets.get(self.resource_group, netName, resName)
1275
1276 out_nets[net_id] = {
1277 "status": self.provision_state2osm[net.provisioning_state],
1278 "vim_info": str(net)
1279 }
1280 except CloudError as e:
1281 if e.error.error and "notfound" in e.error.error.lower():
1282 self.logger.info("Not found subnet net_name: %s, subnet_name: %s", netName, resName)
1283 out_nets[net_id] = {
1284 "status": "DELETED",
1285 "error_msg": str(e)
1286 }
1287 else:
1288 self.logger.error("CloudError Exception %s when searching subnet", e)
1289 out_nets[net_id] = {
1290 "status": "VIM_ERROR",
1291 "error_msg": str(e)
1292 }
1293 except vimconn.vimconnNotFoundException as e:
1294 self.logger.error("VimConnNotFoundException %s when searching subnet", e)
1295 out_nets[net_id] = {
1296 "status": "DELETED",
1297 "error_msg": str(e)
1298 }
1299 except Exception as e:
1300 self.logger.error("Exception %s when searching subnet", e, exc_info=True)
1301 out_nets[net_id] = {
1302 "status": "VIM_ERROR",
1303 "error_msg": str(e)
1304 }
1305 return out_nets
1306
1307 def refresh_vms_status(self, vm_list):
1308 """ Get the status of the virtual machines and their interfaces/ports
1309 Params: the list of VM identifiers
1310 Returns a dictionary with:
1311 vm_id: # VIM id of this Virtual Machine
1312 status: # Mandatory. Text with one of:
1313 # DELETED (not found at vim)
1314 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1315 # OTHER (Vim reported other status not understood)
1316 # ERROR (VIM indicates an ERROR status)
1317 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
1318 # BUILD (on building process), ERROR
1319 # ACTIVE:NoMgmtIP (Active but none of its interfaces has an IP address
1320 # (ACTIVE:NoMgmtIP is not returned for Azure)
1321 #
1322 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1323 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1324 interfaces: list with interface info. Each item a dictionary with:
1325 vim_interface_id - The ID of the interface
1326 mac_address - The MAC address of the interface.
1327 ip_address - The IP address of the interface within the subnet.
1328 """
1329
1330 out_vms = {}
1331 self._reload_connection()
1332
1333 self.logger.debug("refresh vm status vm_list: %s", vm_list)
1334 search_vm_list = vm_list or {}
1335
1336 for vm_id in search_vm_list:
1337 out_vm = {}
1338 try:
1339 res_name = self._get_resource_name_from_resource_id(vm_id)
1340
1341 vm = self.conn_compute.virtual_machines.get(self.resource_group, res_name)
1342 out_vm['vim_info'] = str(vm)
1343 out_vm['status'] = self.provision_state2osm.get(vm.provisioning_state, 'OTHER')
1344 if vm.provisioning_state == 'Succeeded':
1345 # check if machine is running or stopped
1346 instance_view = self.conn_compute.virtual_machines.instance_view(self.resource_group,
1347 res_name)
1348 for status in instance_view.statuses:
1349 splitted_status = status.code.split("/")
1350 if len(splitted_status) == 2 and splitted_status[0] == 'PowerState':
1351 out_vm['status'] = self.power_state2osm.get(splitted_status[1], 'OTHER')
1352
1353 network_interfaces = vm.network_profile.network_interfaces
1354 out_vm['interfaces'] = self._get_vm_interfaces_status(vm_id, network_interfaces)
1355
1356 except CloudError as e:
1357 if e.error.error and "notfound" in e.error.error.lower():
1358 self.logger.debug("Not found vm id: %s", vm_id)
1359 out_vm['status'] = "DELETED"
1360 out_vm['error_msg'] = str(e)
1361 out_vm['vim_info'] = None
1362 else:
1363 # maybe connection error or another type of error, return vim error
1364 self.logger.error("Exception %s refreshing vm_status", e)
1365 out_vm['status'] = "VIM_ERROR"
1366 out_vm['error_msg'] = str(e)
1367 out_vm['vim_info'] = None
1368 except Exception as e:
1369 self.logger.error("Exception %s refreshing vm_status", e, exc_info=True)
1370 out_vm['status'] = "VIM_ERROR"
1371 out_vm['error_msg'] = str(e)
1372 out_vm['vim_info'] = None
1373
1374 out_vms[vm_id] = out_vm
1375
1376 return out_vms
1377
1378 def _get_vm_interfaces_status(self, vm_id, interfaces):
1379 """
1380 Gets the interfaces detail for a vm
1381 :param interfaces: List of interfaces.
1382 :return: Dictionary with list of interfaces including, vim_interface_id, mac_address and ip_address
1383 """
1384 try:
1385 interface_list = []
1386 for network_interface in interfaces:
1387 interface_dict = {}
1388 nic_name = self._get_resource_name_from_resource_id(network_interface.id)
1389 interface_dict['vim_interface_id'] = network_interface.id
1390
1391 nic_data = self.conn_vnet.network_interfaces.get(
1392 self.resource_group,
1393 nic_name)
1394
1395 ips = []
1396 if nic_data.ip_configurations[0].public_ip_address:
1397 self.logger.debug("Obtain public ip address")
1398 public_ip_name = self._get_resource_name_from_resource_id(
1399 nic_data.ip_configurations[0].public_ip_address.id)
1400 public_ip = self.conn_vnet.public_ip_addresses.get(self.resource_group, public_ip_name)
1401 self.logger.debug("Public ip address is: %s", public_ip.ip_address)
1402 ips.append(public_ip.ip_address)
1403
1404 private_ip = nic_data.ip_configurations[0].private_ip_address
1405 ips.append(private_ip)
1406
1407 interface_dict['mac_address'] = nic_data.mac_address
1408 interface_dict['ip_address'] = ";".join(ips)
1409 interface_list.append(interface_dict)
1410
1411 return interface_list
1412 except Exception as e:
1413 self.logger.error("Exception %s obtaining interface data for vm: %s, error: %s", vm_id, e, exc_info=True)
1414 self._format_vimconn_exception(e)
1415
1416
1417 if __name__ == "__main__":
1418
1419 # Making some basic test
1420 vim_id = 'azure'
1421 vim_name = 'azure'
1422 needed_test_params = {
1423 "client_id": "AZURE_CLIENT_ID",
1424 "secret": "AZURE_SECRET",
1425 "tenant": "AZURE_TENANT",
1426 "resource_group": "AZURE_RESOURCE_GROUP",
1427 "subscription_id": "AZURE_SUBSCRIPTION_ID",
1428 "vnet_name": "AZURE_VNET_NAME",
1429 }
1430 test_params = {}
1431
1432 for param, env_var in needed_test_params.items():
1433 value = getenv(env_var)
1434 if not value:
1435 raise Exception("Provide a valid value for env '{}'".format(env_var))
1436 test_params[param] = value
1437
1438 config = {
1439 'region_name': getenv("AZURE_REGION_NAME", 'westeurope'),
1440 'resource_group': getenv("AZURE_RESOURCE_GROUP"),
1441 'subscription_id': getenv("AZURE_SUBSCRIPTION_ID"),
1442 'pub_key': getenv("AZURE_PUB_KEY", None),
1443 'vnet_name': getenv("AZURE_VNET_NAME", 'myNetwork'),
1444 }
1445
1446 virtualMachine = {
1447 'name': 'sergio',
1448 'description': 'new VM',
1449 'status': 'running',
1450 'image': {
1451 'publisher': 'Canonical',
1452 'offer': 'UbuntuServer',
1453 'sku': '16.04.0-LTS',
1454 'version': 'latest'
1455 },
1456 'hardware_profile': {
1457 'vm_size': 'Standard_DS1_v2'
1458 },
1459 'networks': [
1460 'sergio'
1461 ]
1462 }
1463
1464 vnet_config = {
1465 'subnet_address': '10.1.2.0/24',
1466 # 'subnet_name': 'subnet-oam'
1467 }
1468 ###########################
1469
1470 azure = vimconnector(vim_id, vim_name, tenant_id=test_params["tenant"], tenant_name=None, url=None, url_admin=None,
1471 user=test_params["client_id"], passwd=test_params["secret"], log_level=None, config=config)
1472
1473 # azure.get_flavor_id_from_data("here")
1474 # subnets=azure.get_network_list()
1475 # azure.new_vminstance(virtualMachine['name'], virtualMachine['description'], virtualMachine['status'],
1476 # virtualMachine['image'], virtualMachine['hardware_profile']['vm_size'], subnets)
1477
1478 azure.new_network("mynet", None)
1479 net_id = "/subscriptions/82f80cc1-876b-4591-9911-1fb5788384fd/resourceGroups/osmRG/providers/Microsoft."\
1480 "Network/virtualNetworks/test"
1481 net_id_not_found = "/subscriptions/82f80cc1-876b-4591-9911-1fb5788384fd/resourceGroups/osmRG/providers/"\
1482 "Microsoft.Network/virtualNetworks/testALF"
1483 azure.refresh_nets_status([net_id, net_id_not_found])