d96a64e4a3ac353b68927aa388e44393be0a07dc
[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_plugin 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:
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:
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:
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,
378 net_ifz)
379 nic_data = async_nic_creation.result()
380 created_items[nic_data.id] = True
381 self.logger.debug('created nic name %s', nic_name)
382
383 public_ip = net.get('floating_ip')
384 if public_ip:
385 public_ip_address_params = {
386 'location': location,
387 'public_ip_allocation_method': 'Dynamic'
388 }
389 public_ip_name = nic_name + '-public-ip'
390 async_public_ip = self.conn_vnet.public_ip_addresses.create_or_update(
391 self.resource_group,
392 public_ip_name,
393 public_ip_address_params
394 )
395 public_ip = async_public_ip.result()
396 self.logger.debug('created public IP: {}'.format(public_ip))
397
398 # Associate NIC to Public IP
399 nic_data = self.conn_vnet.network_interfaces.get(
400 self.resource_group,
401 nic_name)
402
403 nic_data.ip_configurations[0].public_ip_address = public_ip
404 created_items[public_ip.id] = True
405
406 self.conn_vnet.network_interfaces.create_or_update(
407 self.resource_group,
408 nic_name,
409 nic_data)
410
411 except Exception as e:
412 self._format_vimconn_exception(e)
413
414 return nic_data, created_items
415
416 def new_flavor(self, flavor_data):
417 """
418 It is not allowed to create new flavors in Azure, must always use an existing one
419 """
420 raise vimconn.VimConnAuthException("It is not possible to create new flavors in AZURE")
421
422 def new_tenant(self, tenant_name, tenant_description):
423 """
424 It is not allowed to create new tenants in azure
425 """
426 raise vimconn.VimConnAuthException("It is not possible to create a TENANT in AZURE")
427
428 def new_image(self, image_dict):
429 """
430 It is not allowed to create new images in Azure, must always use an existing one
431 """
432 raise vimconn.VimConnAuthException("It is not possible to create new images in AZURE")
433
434 def get_image_id_from_path(self, path):
435 """Get the image id from image path in the VIM database.
436 Returns the image_id or raises a vimconnNotFoundException
437 """
438 raise vimconn.VimConnAuthException("It is not possible to obtain image from path in AZURE")
439
440 def get_image_list(self, filter_dict={}):
441 """Obtain tenant images from VIM
442 Filter_dict can be:
443 name: image name with the format: publisher:offer:sku:version
444 If some part of the name is provide ex: publisher:offer it will search all availables skus and version
445 for the provided publisher and offer
446 id: image uuid, currently not supported for azure
447 Returns the image list of dictionaries:
448 [{<the fields at Filter_dict plus some VIM specific>}, ...]
449 List can be empty
450 """
451
452 self.logger.debug("get_image_list filter {}".format(filter_dict))
453
454 self._reload_connection()
455 try:
456 image_list = []
457 if filter_dict.get("name"):
458 # name will have the format 'publisher:offer:sku:version'
459 # publisher is required, offer sku and version will be searched if not provided
460 params = filter_dict["name"].split(":")
461 publisher = params[0]
462 if publisher:
463 # obtain offer list
464 offer_list = self._get_offer_list(params, publisher)
465 for offer in offer_list:
466 # obtain skus
467 sku_list = self._get_sku_list(params, publisher, offer)
468 for sku in sku_list:
469 # if version is defined get directly version, else list images
470 if len(params) == 4 and params[3]:
471 version = params[3]
472 image_list = self._get_version_image_list(publisher, offer, sku, version)
473 else:
474 image_list = self._get_sku_image_list(publisher, offer, sku)
475 else:
476 raise vimconn.VimConnAuthException(
477 "List images in Azure must include name param with at least publisher")
478 else:
479 raise vimconn.VimConnAuthException("List images in Azure must include name param with at"
480 " least publisher")
481
482 return image_list
483 except Exception as e:
484 self._format_vimconn_exception(e)
485
486 def _get_offer_list(self, params, publisher):
487 """
488 Helper method to obtain offer list for defined publisher
489 """
490 if len(params) >= 2 and params[1]:
491 return [params[1]]
492 else:
493 try:
494 # get list of offers from azure
495 result_offers = self.conn_compute.virtual_machine_images.list_offers(self.region, publisher)
496 return [offer.name for offer in result_offers]
497 except CloudError as e:
498 # azure raises CloudError when not found
499 self.logger.info("error listing offers for publisher {}, Error: {}".format(publisher, e))
500 return []
501
502 def _get_sku_list(self, params, publisher, offer):
503 """
504 Helper method to obtain sku list for defined publisher and offer
505 """
506 if len(params) >= 3 and params[2]:
507 return [params[2]]
508 else:
509 try:
510 # get list of skus from azure
511 result_skus = self.conn_compute.virtual_machine_images.list_skus(self.region, publisher, offer)
512 return [sku.name for sku in result_skus]
513 except CloudError as e:
514 # azure raises CloudError when not found
515 self.logger.info("error listing skus for publisher {}, offer {}, Error: {}".format(publisher, offer, e))
516 return []
517
518 def _get_sku_image_list(self, publisher, offer, sku):
519 """
520 Helper method to obtain image list for publisher, offer and sku
521 """
522 image_list = []
523 try:
524 result_images = self.conn_compute.virtual_machine_images.list(self.region, publisher, offer, sku)
525 for result_image in result_images:
526 image_list.append({
527 'id': str(result_image.id),
528 'name': ":".join([publisher, offer, sku, result_image.name])
529 })
530 except CloudError as e:
531 self.logger.info(
532 "error listing skus for publisher {}, offer {}, Error: {}".format(publisher, offer, e))
533 image_list = []
534 return image_list
535
536 def _get_version_image_list(self, publisher, offer, sku, version):
537 image_list = []
538 try:
539 result_image = self.conn_compute.virtual_machine_images.get(self.region, publisher, offer, sku, version)
540 if result_image:
541 image_list.append({
542 'id': str(result_image.id),
543 'name': ":".join([publisher, offer, sku, version])
544 })
545 except CloudError as e:
546 # azure gives CloudError when not found
547 self.logger.info("error listing images for publisher {}, offer {}, sku {}, version {} Error: {}".
548 format(publisher, offer, sku, version, e))
549 image_list = []
550 return image_list
551
552 def get_network_list(self, filter_dict={}):
553 """Obtain tenant networks of VIM
554 Filter_dict can be:
555 name: network name
556 id: network id
557 shared: boolean, not implemented in Azure
558 tenant_id: tenant, not used in Azure, all networks same tenants
559 admin_state_up: boolean, not implemented in Azure
560 status: 'ACTIVE', not implemented in Azure #
561 Returns the network list of dictionaries
562 """
563 # self.logger.debug('getting network list for vim, filter %s', filter_dict)
564 try:
565 self._reload_connection()
566
567 vnet = self.conn_vnet.virtual_networks.get(self.resource_group, self.vnet_name)
568 subnet_list = []
569
570 for subnet in vnet.subnets:
571 if filter_dict:
572 if filter_dict.get("id") and str(subnet.id) != filter_dict["id"]:
573 continue
574 if filter_dict.get("name") and \
575 str(subnet.name) != filter_dict["name"]:
576 continue
577
578 name = self._get_resource_name_from_resource_id(subnet.id)
579
580 subnet_list.append({
581 'id': str(subnet.id),
582 'name': name,
583 'status': self.provision_state2osm[subnet.provisioning_state],
584 'cidr_block': str(subnet.address_prefix),
585 'type': 'bridge',
586 'shared': False
587 })
588
589 return subnet_list
590 except Exception as e:
591 self._format_vimconn_exception(e)
592
593 def new_vminstance(self, name, description, start, image_id, flavor_id, net_list, cloud_config=None,
594 disk_list=None, availability_zone_index=None, availability_zone_list=None):
595
596 self.logger.debug("new vm instance name: %s, image_id: %s, flavor_id: %s, net_list: %s, cloud_config: %s, "
597 "disk_list: %s, availability_zone_index: %s, availability_zone_list: %s",
598 name, image_id, flavor_id, net_list, cloud_config, disk_list,
599 availability_zone_index, availability_zone_list)
600
601 self._reload_connection()
602
603 # Validate input data is valid
604 # The virtual machine name must have less or 64 characters and it can not have the following
605 # characters: (~ ! @ # $ % ^ & * ( ) = + _ [ ] { } \ | ; : ' " , < > / ?.)
606 vm_name = self._check_vm_name(name)
607 # Obtain vm unused name
608 vm_name = self._get_unused_vm_name(vm_name)
609
610 # At least one network must be provided
611 if not net_list:
612 raise vimconn.VimConnException("At least one net must be provided to create a new VM")
613
614 # image_id are several fields of the image_id
615 image_reference = self._get_image_reference(image_id)
616
617 try:
618 virtual_machine = None
619 created_items = {}
620
621 # Create nics for each subnet
622 self._check_subnets_for_vm(net_list)
623 vm_nics = []
624 for idx, net in enumerate(net_list):
625 # Fault with subnet_id
626 # subnet_id=net['subnet_id']
627 # subnet_id=net['net_id']
628 nic_name = vm_name + '-nic-' + str(idx)
629 vm_nic, nic_items = self._create_nic(net, nic_name, net.get('ip_address'), created_items)
630 vm_nics.append({'id': str(vm_nic.id)})
631 # net['vim_id'] = vm_nic.id
632
633 # cloud-init configuration
634 # cloud config
635 if cloud_config:
636 config_drive, userdata = self._create_user_data(cloud_config)
637 custom_data = base64.b64encode(userdata.encode('utf-8')).decode('latin-1')
638 key_data = None
639 key_pairs = cloud_config.get("key-pairs")
640 if key_pairs:
641 key_data = key_pairs[0]
642
643 if cloud_config.get("users"):
644 user_name = cloud_config.get("users")[0].get("name", "osm")
645 else:
646 user_name = "osm" # DEFAULT USER IS OSM
647
648 os_profile = {
649 'computer_name': vm_name,
650 'admin_username': user_name,
651 'linux_configuration': {
652 "disable_password_authentication": True,
653 "ssh": {
654 "public_keys": [{
655 "path": "/home/{}/.ssh/authorized_keys".format(user_name),
656 "key_data": key_data
657 }]
658 }
659 },
660 'custom_data': custom_data
661 }
662 else:
663 os_profile = {
664 'computer_name': vm_name,
665 'admin_username': 'osm',
666 'admin_password': 'Osm4u!',
667 }
668
669 vm_parameters = {
670 'location': self.region,
671 'os_profile': os_profile,
672 'hardware_profile': {
673 'vm_size': flavor_id
674 },
675 'storage_profile': {
676 'image_reference': image_reference
677 }
678 }
679
680 # If the machine has several networks one must be marked as primary
681 # As it is not indicated in the interface the first interface will be marked as primary
682 if len(vm_nics) > 1:
683 for idx, vm_nic in enumerate(vm_nics):
684 if idx == 0:
685 vm_nics[0]['Primary'] = True
686 else:
687 vm_nics[idx]['Primary'] = False
688
689 vm_parameters['network_profile'] = {'network_interfaces': vm_nics}
690
691 # Obtain zone information
692 vm_zone = self._get_vm_zone(availability_zone_index, availability_zone_list)
693 if vm_zone:
694 vm_parameters['zones'] = [vm_zone]
695
696 self.logger.debug("create vm name: %s", vm_name)
697 creation_result = self.conn_compute.virtual_machines.create_or_update(
698 self.resource_group,
699 vm_name,
700 vm_parameters
701 )
702 virtual_machine = creation_result.result()
703 self.logger.debug("created vm name: %s", vm_name)
704
705 # Add disks if they are provided
706 if disk_list:
707 for disk_index, disk in enumerate(disk_list):
708 self.logger.debug("add disk size: %s, image: %s", disk.get("size"), disk.get("image"))
709 self._add_newvm_disk(virtual_machine, vm_name, disk_index, disk, created_items)
710
711 if start:
712 self.conn_compute.virtual_machines.start(
713 self.resource_group,
714 vm_name)
715 # start_result.wait()
716
717 return virtual_machine.id, created_items
718
719 # run_command_parameters = {
720 # 'command_id': 'RunShellScript', # For linux, don't change it
721 # 'script': [
722 # 'date > /tmp/test.txt'
723 # ]
724 # }
725 except Exception as e:
726 # Rollback vm creacion
727 vm_id = None
728 if virtual_machine:
729 vm_id = virtual_machine.id
730 try:
731 self.logger.debug("exception creating vm try to rollback")
732 self.delete_vminstance(vm_id, created_items)
733 except Exception as e2:
734 self.logger.error("new_vminstance rollback fail {}".format(e2))
735
736 self.logger.debug('Exception creating new vminstance: %s', e, exc_info=True)
737 self._format_vimconn_exception(e)
738
739 def _get_unused_vm_name(self, vm_name):
740 """
741 Checks the vm name and in case it is used adds a suffix to the name to allow creation
742 :return:
743 """
744 all_vms = self.conn_compute.virtual_machines.list(self.resource_group)
745 # Filter to vms starting with the indicated name
746 vms = list(filter(lambda vm: (vm.name.startswith(vm_name)), all_vms))
747 vm_names = [str(vm.name) for vm in vms]
748
749 # get the name with the first not used suffix
750 name_suffix = 0
751 # name = subnet_name + "-" + str(name_suffix)
752 name = vm_name # first subnet created will have no prefix
753 while name in vm_names:
754 name_suffix += 1
755 name = vm_name + "-" + str(name_suffix)
756 return name
757
758 def _get_vm_zone(self, availability_zone_index, availability_zone_list):
759
760 if availability_zone_index is None:
761 return None
762
763 vim_availability_zones = self._get_azure_availability_zones()
764 # check if VIM offer enough availability zones describe in the VNFD
765 if vim_availability_zones and len(availability_zone_list) <= len(vim_availability_zones):
766 # check if all the names of NFV AV match VIM AV names
767 match_by_index = False
768 if not availability_zone_list:
769 match_by_index = True
770 else:
771 for av in availability_zone_list:
772 if av not in vim_availability_zones:
773 match_by_index = True
774 break
775 if match_by_index:
776 return vim_availability_zones[availability_zone_index]
777 else:
778 return availability_zone_list[availability_zone_index]
779 else:
780 raise vimconn.VimConnConflictException("No enough availability zones at VIM for this deployment")
781
782 def _get_azure_availability_zones(self):
783 return self.AZURE_ZONES
784
785 def _add_newvm_disk(self, virtual_machine, vm_name, disk_index, disk, created_items={}):
786
787 disk_name = None
788 data_disk = None
789
790 # Check if must create empty disk or from image
791 if disk.get('vim_id'):
792 # disk already exists, just get
793 parsed_id = azure_tools.parse_resource_id(disk.get('vim_id'))
794 disk_name = parsed_id.get("name")
795 data_disk = self.conn_compute.disks.get(self.resource_group, disk_name)
796 else:
797 disk_name = vm_name + "_DataDisk_" + str(disk_index)
798 if not disk.get("image_id"):
799 self.logger.debug("create new data disk name: %s", disk_name)
800 async_disk_creation = self.conn_compute.disks.create_or_update(
801 self.resource_group,
802 disk_name,
803 {
804 'location': self.region,
805 'disk_size_gb': disk.get("size"),
806 'creation_data': {
807 'create_option': DiskCreateOption.empty
808 }
809 }
810 )
811 data_disk = async_disk_creation.result()
812 created_items[data_disk.id] = True
813 else:
814 image_id = disk.get("image_id")
815 if azure_tools.is_valid_resource_id(image_id):
816 parsed_id = azure_tools.parse_resource_id(image_id)
817
818 # Check if image is snapshot or disk
819 image_name = parsed_id.get("name")
820 type = parsed_id.get("resource_type")
821 if type == 'snapshots' or type == 'disks':
822
823 self.logger.debug("create disk from copy name: %s", image_name)
824 # ¿Should check that snapshot exists?
825 async_disk_creation = self.conn_compute.disks.create_or_update(
826 self.resource_group,
827 disk_name,
828 {
829 'location': self.region,
830 'creation_data': {
831 'create_option': 'Copy',
832 'source_uri': image_id
833 }
834 }
835 )
836 data_disk = async_disk_creation.result()
837 created_items[data_disk.id] = True
838
839 else:
840 raise vimconn.VimConnNotFoundException("Invalid image_id: %s ", image_id)
841 else:
842 raise vimconn.VimConnNotFoundException("Invalid image_id: %s ", image_id)
843
844 # Attach the disk created
845 virtual_machine.storage_profile.data_disks.append({
846 'lun': disk_index,
847 'name': disk_name,
848 'create_option': DiskCreateOption.attach,
849 'managed_disk': {
850 'id': data_disk.id
851 },
852 'disk_size_gb': disk.get('size')
853 })
854 self.logger.debug("attach disk name: %s", disk_name)
855 self.conn_compute.virtual_machines.create_or_update(
856 self.resource_group,
857 virtual_machine.name,
858 virtual_machine
859 )
860
861 # It is necesary extract from image_id data to create the VM with this format
862 # 'image_reference': {
863 # 'publisher': vm_reference['publisher'],
864 # 'offer': vm_reference['offer'],
865 # 'sku': vm_reference['sku'],
866 # 'version': vm_reference['version']
867 # },
868 def _get_image_reference(self, image_id):
869
870 try:
871 # The data input format example:
872 # /Subscriptions/ca3d18ab-d373-4afb-a5d6-7c44f098d16a/Providers/Microsoft.Compute/Locations/westeurope/
873 # Publishers/Canonical/ArtifactTypes/VMImage/
874 # Offers/UbuntuServer/
875 # Skus/18.04-LTS/
876 # Versions/18.04.201809110
877 publisher = str(image_id.split('/')[8])
878 offer = str(image_id.split('/')[12])
879 sku = str(image_id.split('/')[14])
880 version = str(image_id.split('/')[16])
881
882 return {
883 'publisher': publisher,
884 'offer': offer,
885 'sku': sku,
886 'version': version
887 }
888 except Exception:
889 raise vimconn.VimConnException(
890 "Unable to get image_reference from invalid image_id format: '{}'".format(image_id))
891
892 # Azure VM names can not have some special characters
893 def _check_vm_name(self, vm_name):
894 """
895 Checks vm name, in case the vm has not allowed characters they are removed, not error raised
896 """
897
898 chars_not_allowed_list = "~!@#$%^&*()=+_[]{}|;:<>/?."
899
900 # First: the VM name max length is 64 characters
901 vm_name_aux = vm_name[:64]
902
903 # Second: replace not allowed characters
904 for elem in chars_not_allowed_list:
905 # Check if string is in the main string
906 if elem in vm_name_aux:
907 # self.logger.debug('Dentro del IF')
908 # Replace the string
909 vm_name_aux = vm_name_aux.replace(elem, '-')
910
911 return vm_name_aux
912
913 def get_flavor_id_from_data(self, flavor_dict):
914
915 self.logger.debug("getting flavor id from data, flavor_dict: %s", flavor_dict)
916 filter_dict = flavor_dict or {}
917 try:
918 self._reload_connection()
919 vm_sizes_list = [vm_size.serialize() for vm_size in
920 self.conn_compute.virtual_machine_sizes.list(self.region)]
921
922 cpus = filter_dict.get('vcpus') or 0
923 memMB = filter_dict.get('ram') or 0
924
925 # Filter
926 if self._config.get("flavors_pattern"):
927 filtered_sizes = [size for size in vm_sizes_list if size['numberOfCores'] >= cpus and
928 size['memoryInMB'] >= memMB and
929 re.search(self._config.get("flavors_pattern"), size["name"])]
930 else:
931 filtered_sizes = [size for size in vm_sizes_list if size['numberOfCores'] >= cpus and
932 size['memoryInMB'] >= memMB]
933
934 # Sort
935 listedFilteredSizes = sorted(filtered_sizes, key=lambda k: (k['numberOfCores'], k['memoryInMB'],
936 k['resourceDiskSizeInMB']))
937
938 if listedFilteredSizes:
939 return listedFilteredSizes[0]['name']
940 raise vimconn.VimConnNotFoundException("Cannot find any flavor matching '{}'".format(str(flavor_dict)))
941
942 except Exception as e:
943 self._format_vimconn_exception(e)
944
945 def _get_flavor_id_from_flavor_name(self, flavor_name):
946
947 # self.logger.debug("getting flavor id from flavor name {}".format(flavor_name))
948 try:
949 self._reload_connection()
950 vm_sizes_list = [vm_size.serialize() for vm_size in
951 self.conn_compute.virtual_machine_sizes.list(self.region)]
952
953 output_flavor = None
954 for size in vm_sizes_list:
955 if size['name'] == flavor_name:
956 output_flavor = size
957
958 # None is returned if not found anything
959 return output_flavor
960
961 except Exception as e:
962 self._format_vimconn_exception(e)
963
964 def check_vim_connectivity(self):
965 try:
966 self._reload_connection()
967 return True
968 except Exception as e:
969 raise vimconn.VimConnException("Connectivity issue with Azure API: {}".format(e))
970
971 def get_network(self, net_id):
972
973 # self.logger.debug('get network id: {}'.format(net_id))
974 # res_name = self._get_resource_name_from_resource_id(net_id)
975 self._reload_connection()
976
977 filter_dict = {'name': net_id}
978 network_list = self.get_network_list(filter_dict)
979
980 if not network_list:
981 raise vimconn.VimConnNotFoundException("network '{}' not found".format(net_id))
982 else:
983 return network_list[0]
984
985 def delete_network(self, net_id, created_items=None):
986
987 self.logger.debug('deleting network {} - {}'.format(self.resource_group, net_id))
988
989 self._reload_connection()
990 res_name = self._get_resource_name_from_resource_id(net_id)
991 filter_dict = {'name': res_name}
992 network_list = self.get_network_list(filter_dict)
993 if not network_list:
994 raise vimconn.VimConnNotFoundException("network '{}' not found".format(net_id))
995
996 try:
997 # Subnet API fails (CloudError: Azure Error: ResourceNotFound)
998 # Put the initial virtual_network API
999 async_delete = self.conn_vnet.subnets.delete(self.resource_group, self.vnet_name, res_name)
1000 async_delete.wait()
1001 return net_id
1002
1003 except CloudError as e:
1004 if e.error.error and "notfound" in e.error.error.lower():
1005 raise vimconn.VimConnNotFoundException("network '{}' not found".format(net_id))
1006 else:
1007 self._format_vimconn_exception(e)
1008 except Exception as e:
1009 self._format_vimconn_exception(e)
1010
1011 def delete_vminstance(self, vm_id, created_items=None):
1012 """ Deletes a vm instance from the vim.
1013 """
1014 self.logger.debug('deleting VM instance {} - {}'.format(self.resource_group, vm_id))
1015 self._reload_connection()
1016
1017 created_items = created_items or {}
1018 try:
1019 # Check vm exists, we can call delete_vm to clean created_items
1020 if vm_id:
1021 res_name = self._get_resource_name_from_resource_id(vm_id)
1022 vm = self.conn_compute.virtual_machines.get(self.resource_group, res_name)
1023
1024 # Shuts down the virtual machine and releases the compute resources
1025 # vm_stop = self.conn_compute.virtual_machines.power_off(self.resource_group, resName)
1026 # vm_stop.wait()
1027
1028 vm_delete = self.conn_compute.virtual_machines.delete(self.resource_group, res_name)
1029 vm_delete.wait()
1030 self.logger.debug('deleted VM name: %s', res_name)
1031
1032 # Delete OS Disk
1033 os_disk_name = vm.storage_profile.os_disk.name
1034 self.logger.debug('delete OS DISK: %s', os_disk_name)
1035 async_disk_delete = self.conn_compute.disks.delete(self.resource_group, os_disk_name)
1036 async_disk_delete.wait()
1037 # os disks are created always with the machine
1038 self.logger.debug('deleted OS DISK name: %s', os_disk_name)
1039
1040 for data_disk in vm.storage_profile.data_disks:
1041 self.logger.debug('delete data_disk: %s', data_disk.name)
1042 async_disk_delete = self.conn_compute.disks.delete(self.resource_group, data_disk.name)
1043 async_disk_delete.wait()
1044 self._markdel_created_item(data_disk.managed_disk.id, created_items)
1045 self.logger.debug('deleted OS DISK name: %s', data_disk.name)
1046
1047 # After deleting VM, it is necessary to delete NIC, because if is not deleted delete_network
1048 # does not work because Azure says that is in use the subnet
1049 network_interfaces = vm.network_profile.network_interfaces
1050
1051 for network_interface in network_interfaces:
1052
1053 nic_name = self._get_resource_name_from_resource_id(network_interface.id)
1054 nic_data = self.conn_vnet.network_interfaces.get(
1055 self.resource_group,
1056 nic_name)
1057
1058 public_ip_name = None
1059 exist_public_ip = nic_data.ip_configurations[0].public_ip_address
1060 if exist_public_ip:
1061 public_ip_id = nic_data.ip_configurations[0].public_ip_address.id
1062
1063 # Delete public_ip
1064 public_ip_name = self._get_resource_name_from_resource_id(public_ip_id)
1065
1066 # Public ip must be deleted afterwards of nic that is attached
1067
1068 self.logger.debug('delete NIC name: %s', nic_name)
1069 nic_delete = self.conn_vnet.network_interfaces.delete(self.resource_group, nic_name)
1070 nic_delete.wait()
1071 self._markdel_created_item(network_interface.id, created_items)
1072 self.logger.debug('deleted NIC name: %s', nic_name)
1073
1074 # Delete list of public ips
1075 if public_ip_name:
1076 self.logger.debug('delete PUBLIC IP - ' + public_ip_name)
1077 ip_delete = self.conn_vnet.public_ip_addresses.delete(self.resource_group, public_ip_name)
1078 ip_delete.wait()
1079 self._markdel_created_item(public_ip_id, created_items)
1080
1081 # Delete created items
1082 self._delete_created_items(created_items)
1083
1084 except CloudError as e:
1085 if e.error.error and "notfound" in e.error.error.lower():
1086 raise vimconn.VimConnNotFoundException("No vm instance found '{}'".format(vm_id))
1087 else:
1088 self._format_vimconn_exception(e)
1089 except Exception as e:
1090 self._format_vimconn_exception(e)
1091
1092 def _markdel_created_item(self, item_id, created_items):
1093 if item_id in created_items:
1094 created_items[item_id] = False
1095
1096 def _delete_created_items(self, created_items):
1097 """ Delete created_items elements that have not been deleted with the virtual machine
1098 Created_items may not be deleted correctly with the created machine if the
1099 virtual machine fails creating or in other cases of error
1100 """
1101 self.logger.debug("Created items: %s", created_items)
1102 # Must delete in order first nics, then public_ips
1103 # As dictionaries don't preserve order, first get items to be deleted then delete them
1104 nics_to_delete = []
1105 publics_ip_to_delete = []
1106 disks_to_delete = []
1107 for item_id, v in created_items.items():
1108 if not v: # skip already deleted
1109 continue
1110
1111 # self.logger.debug("Must delete item id: %s", item_id)
1112
1113 # Obtain type, supported nic, disk or public ip
1114 parsed_id = azure_tools.parse_resource_id(item_id)
1115 resource_type = parsed_id.get("resource_type")
1116 name = parsed_id.get("name")
1117
1118 if resource_type == "networkInterfaces":
1119 nics_to_delete.append(name)
1120 elif resource_type == "publicIPAddresses":
1121 publics_ip_to_delete.append(name)
1122 elif resource_type == "disks":
1123 disks_to_delete.append(name)
1124
1125 # Now delete
1126 for item_name in nics_to_delete:
1127 try:
1128 self.logger.debug("deleting nic name %s:", item_name)
1129 nic_delete = self.conn_vnet.network_interfaces.delete(self.resource_group, item_name)
1130 nic_delete.wait()
1131 self.logger.debug("deleted nic name %s:", item_name)
1132 except Exception as e:
1133 self.logger.error("Error deleting item: {}: {}".format(type(e).__name__, e))
1134
1135 for item_name in publics_ip_to_delete:
1136 try:
1137 self.logger.debug("deleting public ip name %s:", item_name)
1138 ip_delete = self.conn_vnet.public_ip_addresses.delete(self.resource_group, name)
1139 ip_delete.wait()
1140 self.logger.debug("deleted public ip name %s:", item_name)
1141 except Exception as e:
1142 self.logger.error("Error deleting item: {}: {}".format(type(e).__name__, e))
1143
1144 for item_name in disks_to_delete:
1145 try:
1146 self.logger.debug("deleting data disk name %s:", name)
1147 async_disk_delete = self.conn_compute.disks.delete(self.resource_group, item_name)
1148 async_disk_delete.wait()
1149 self.logger.debug("deleted data disk name %s:", name)
1150 except Exception as e:
1151 self.logger.error("Error deleting item: {}: {}".format(type(e).__name__, e))
1152
1153 def action_vminstance(self, vm_id, action_dict, created_items={}):
1154 """Send and action over a VM instance from VIM
1155 Returns the vm_id if the action was successfully sent to the VIM
1156 """
1157
1158 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
1159 try:
1160 self._reload_connection()
1161 resName = self._get_resource_name_from_resource_id(vm_id)
1162 if "start" in action_dict:
1163 self.conn_compute.virtual_machines.start(self.resource_group, resName)
1164 elif "stop" in action_dict or "shutdown" in action_dict or "shutoff" in action_dict:
1165 self.conn_compute.virtual_machines.power_off(self.resource_group, resName)
1166 elif "terminate" in action_dict:
1167 self.conn_compute.virtual_machines.delete(self.resource_group, resName)
1168 elif "reboot" in action_dict:
1169 self.conn_compute.virtual_machines.restart(self.resource_group, resName)
1170 return None
1171 except CloudError as e:
1172 if e.error.error and "notfound" in e.error.error.lower():
1173 raise vimconn.VimConnNotFoundException("No vm found '{}'".format(vm_id))
1174 else:
1175 self._format_vimconn_exception(e)
1176 except Exception as e:
1177 self._format_vimconn_exception(e)
1178
1179 def delete_flavor(self, flavor_id):
1180 raise vimconn.VimConnAuthException("It is not possible to delete a FLAVOR in AZURE")
1181
1182 def delete_tenant(self, tenant_id,):
1183 raise vimconn.VimConnAuthException("It is not possible to delete a TENANT in AZURE")
1184
1185 def delete_image(self, image_id):
1186 raise vimconn.VimConnAuthException("It is not possible to delete a IMAGE in AZURE")
1187
1188 def get_vminstance(self, vm_id):
1189 """
1190 Obtaing the vm instance data from v_id
1191 """
1192 self.logger.debug("get vm instance: %s", vm_id)
1193 self._reload_connection()
1194 try:
1195 resName = self._get_resource_name_from_resource_id(vm_id)
1196 vm = self.conn_compute.virtual_machines.get(self.resource_group, resName)
1197 except CloudError as e:
1198 if e.error.error and "notfound" in e.error.error.lower():
1199 raise vimconn.VimConnNotFoundException("No vminstance found '{}'".format(vm_id))
1200 else:
1201 self._format_vimconn_exception(e)
1202 except Exception as e:
1203 self._format_vimconn_exception(e)
1204
1205 return vm
1206
1207 def get_flavor(self, flavor_id):
1208 """
1209 Obtains the flavor_data from the flavor_id
1210 """
1211 self._reload_connection()
1212 self.logger.debug("get flavor from id: %s", flavor_id)
1213 flavor_data = self._get_flavor_id_from_flavor_name(flavor_id)
1214 if flavor_data:
1215 flavor = {
1216 'id': flavor_id,
1217 'name': flavor_id,
1218 'ram': flavor_data['memoryInMB'],
1219 'vcpus': flavor_data['numberOfCores'],
1220 'disk': flavor_data['resourceDiskSizeInMB']/1024
1221 }
1222 return flavor
1223 else:
1224 raise vimconn.VimConnNotFoundException("flavor '{}' not found".format(flavor_id))
1225
1226 def get_tenant_list(self, filter_dict={}):
1227 """ Obtains the list of tenants
1228 For the azure connector only the azure tenant will be returned if it is compatible
1229 with filter_dict
1230 """
1231 tenants_azure = [{'name': self.tenant, 'id': self.tenant}]
1232 tenant_list = []
1233
1234 self.logger.debug("get tenant list: %s", filter_dict)
1235 for tenant_azure in tenants_azure:
1236 if filter_dict:
1237 if filter_dict.get("id") and str(tenant_azure.get("id")) != filter_dict["id"]:
1238 continue
1239 if filter_dict.get("name") and str(tenant_azure.get("name")) != filter_dict["name"]:
1240 continue
1241
1242 tenant_list.append(tenant_azure)
1243
1244 return tenant_list
1245
1246 def refresh_nets_status(self, net_list):
1247 """Get the status of the networks
1248 Params: the list of network identifiers
1249 Returns a dictionary with:
1250 net_id: #VIM id of this network
1251 status: #Mandatory. Text with one of:
1252 # DELETED (not found at vim)
1253 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1254 # OTHER (Vim reported other status not understood)
1255 # ERROR (VIM indicates an ERROR status)
1256 # ACTIVE, INACTIVE, DOWN (admin down),
1257 # BUILD (on building process)
1258 #
1259 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1260 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1261
1262 """
1263
1264 out_nets = {}
1265 self._reload_connection()
1266
1267 self.logger.debug("reload nets status net_list: %s", net_list)
1268 for net_id in net_list:
1269 try:
1270 netName = self._get_net_name_from_resource_id(net_id)
1271 resName = self._get_resource_name_from_resource_id(net_id)
1272
1273 net = self.conn_vnet.subnets.get(self.resource_group, netName, resName)
1274
1275 out_nets[net_id] = {
1276 "status": self.provision_state2osm[net.provisioning_state],
1277 "vim_info": str(net)
1278 }
1279 except CloudError as e:
1280 if e.error.error and "notfound" in e.error.error.lower():
1281 self.logger.info("Not found subnet net_name: %s, subnet_name: %s", netName, resName)
1282 out_nets[net_id] = {
1283 "status": "DELETED",
1284 "error_msg": str(e)
1285 }
1286 else:
1287 self.logger.error("CloudError Exception %s when searching subnet", e)
1288 out_nets[net_id] = {
1289 "status": "VIM_ERROR",
1290 "error_msg": str(e)
1291 }
1292 except vimconn.VimConnNotFoundException as e:
1293 self.logger.error("VimConnNotFoundException %s when searching subnet", e)
1294 out_nets[net_id] = {
1295 "status": "DELETED",
1296 "error_msg": str(e)
1297 }
1298 except Exception as e:
1299 self.logger.error("Exception %s when searching subnet", e, exc_info=True)
1300 out_nets[net_id] = {
1301 "status": "VIM_ERROR",
1302 "error_msg": str(e)
1303 }
1304 return out_nets
1305
1306 def refresh_vms_status(self, vm_list):
1307 """ Get the status of the virtual machines and their interfaces/ports
1308 Params: the list of VM identifiers
1309 Returns a dictionary with:
1310 vm_id: # VIM id of this Virtual Machine
1311 status: # Mandatory. Text with one of:
1312 # DELETED (not found at vim)
1313 # VIM_ERROR (Cannot connect to VIM, VIM response error, ...)
1314 # OTHER (Vim reported other status not understood)
1315 # ERROR (VIM indicates an ERROR status)
1316 # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running),
1317 # BUILD (on building process), ERROR
1318 # ACTIVE:NoMgmtIP (Active but none of its interfaces has an IP address
1319 # (ACTIVE:NoMgmtIP is not returned for Azure)
1320 #
1321 error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR
1322 vim_info: #Text with plain information obtained from vim (yaml.safe_dump)
1323 interfaces: list with interface info. Each item a dictionary with:
1324 vim_interface_id - The ID of the interface
1325 mac_address - The MAC address of the interface.
1326 ip_address - The IP address of the interface within the subnet.
1327 """
1328
1329 out_vms = {}
1330 self._reload_connection()
1331
1332 self.logger.debug("refresh vm status vm_list: %s", vm_list)
1333 search_vm_list = vm_list or {}
1334
1335 for vm_id in search_vm_list:
1336 out_vm = {}
1337 try:
1338 res_name = self._get_resource_name_from_resource_id(vm_id)
1339
1340 vm = self.conn_compute.virtual_machines.get(self.resource_group, res_name)
1341 out_vm['vim_info'] = str(vm)
1342 out_vm['status'] = self.provision_state2osm.get(vm.provisioning_state, 'OTHER')
1343 if vm.provisioning_state == 'Succeeded':
1344 # check if machine is running or stopped
1345 instance_view = self.conn_compute.virtual_machines.instance_view(self.resource_group,
1346 res_name)
1347 for status in instance_view.statuses:
1348 splitted_status = status.code.split("/")
1349 if len(splitted_status) == 2 and splitted_status[0] == 'PowerState':
1350 out_vm['status'] = self.power_state2osm.get(splitted_status[1], 'OTHER')
1351
1352 network_interfaces = vm.network_profile.network_interfaces
1353 out_vm['interfaces'] = self._get_vm_interfaces_status(vm_id, network_interfaces)
1354
1355 except CloudError as e:
1356 if e.error.error and "notfound" in e.error.error.lower():
1357 self.logger.debug("Not found vm id: %s", vm_id)
1358 out_vm['status'] = "DELETED"
1359 out_vm['error_msg'] = str(e)
1360 out_vm['vim_info'] = None
1361 else:
1362 # maybe connection error or another type of error, return vim error
1363 self.logger.error("Exception %s refreshing vm_status", e)
1364 out_vm['status'] = "VIM_ERROR"
1365 out_vm['error_msg'] = str(e)
1366 out_vm['vim_info'] = None
1367 except Exception as e:
1368 self.logger.error("Exception %s refreshing vm_status", e, exc_info=True)
1369 out_vm['status'] = "VIM_ERROR"
1370 out_vm['error_msg'] = str(e)
1371 out_vm['vim_info'] = None
1372
1373 out_vms[vm_id] = out_vm
1374
1375 return out_vms
1376
1377 def _get_vm_interfaces_status(self, vm_id, interfaces):
1378 """
1379 Gets the interfaces detail for a vm
1380 :param interfaces: List of interfaces.
1381 :return: Dictionary with list of interfaces including, vim_interface_id, mac_address and ip_address
1382 """
1383 try:
1384 interface_list = []
1385 for network_interface in interfaces:
1386 interface_dict = {}
1387 nic_name = self._get_resource_name_from_resource_id(network_interface.id)
1388 interface_dict['vim_interface_id'] = network_interface.id
1389
1390 nic_data = self.conn_vnet.network_interfaces.get(
1391 self.resource_group,
1392 nic_name)
1393
1394 ips = []
1395 if nic_data.ip_configurations[0].public_ip_address:
1396 self.logger.debug("Obtain public ip address")
1397 public_ip_name = self._get_resource_name_from_resource_id(
1398 nic_data.ip_configurations[0].public_ip_address.id)
1399 public_ip = self.conn_vnet.public_ip_addresses.get(self.resource_group, public_ip_name)
1400 self.logger.debug("Public ip address is: %s", public_ip.ip_address)
1401 ips.append(public_ip.ip_address)
1402
1403 private_ip = nic_data.ip_configurations[0].private_ip_address
1404 ips.append(private_ip)
1405
1406 interface_dict['mac_address'] = nic_data.mac_address
1407 interface_dict['ip_address'] = ";".join(ips)
1408 interface_list.append(interface_dict)
1409
1410 return interface_list
1411 except Exception as e:
1412 self.logger.error("Exception %s obtaining interface data for vm: %s, error: %s", vm_id, e, exc_info=True)
1413 self._format_vimconn_exception(e)
1414
1415
1416 if __name__ == "__main__":
1417
1418 # Making some basic test
1419 vim_id = 'azure'
1420 vim_name = 'azure'
1421 needed_test_params = {
1422 "client_id": "AZURE_CLIENT_ID",
1423 "secret": "AZURE_SECRET",
1424 "tenant": "AZURE_TENANT",
1425 "resource_group": "AZURE_RESOURCE_GROUP",
1426 "subscription_id": "AZURE_SUBSCRIPTION_ID",
1427 "vnet_name": "AZURE_VNET_NAME",
1428 }
1429 test_params = {}
1430
1431 for param, env_var in needed_test_params.items():
1432 value = getenv(env_var)
1433 if not value:
1434 raise Exception("Provide a valid value for env '{}'".format(env_var))
1435 test_params[param] = value
1436
1437 config = {
1438 'region_name': getenv("AZURE_REGION_NAME", 'westeurope'),
1439 'resource_group': getenv("AZURE_RESOURCE_GROUP"),
1440 'subscription_id': getenv("AZURE_SUBSCRIPTION_ID"),
1441 'pub_key': getenv("AZURE_PUB_KEY", None),
1442 'vnet_name': getenv("AZURE_VNET_NAME", 'myNetwork'),
1443 }
1444
1445 virtualMachine = {
1446 'name': 'sergio',
1447 'description': 'new VM',
1448 'status': 'running',
1449 'image': {
1450 'publisher': 'Canonical',
1451 'offer': 'UbuntuServer',
1452 'sku': '16.04.0-LTS',
1453 'version': 'latest'
1454 },
1455 'hardware_profile': {
1456 'vm_size': 'Standard_DS1_v2'
1457 },
1458 'networks': [
1459 'sergio'
1460 ]
1461 }
1462
1463 vnet_config = {
1464 'subnet_address': '10.1.2.0/24',
1465 # 'subnet_name': 'subnet-oam'
1466 }
1467 ###########################
1468
1469 azure = vimconnector(vim_id, vim_name, tenant_id=test_params["tenant"], tenant_name=None, url=None, url_admin=None,
1470 user=test_params["client_id"], passwd=test_params["secret"], log_level=None, config=config)
1471
1472 # azure.get_flavor_id_from_data("here")
1473 # subnets=azure.get_network_list()
1474 # azure.new_vminstance(virtualMachine['name'], virtualMachine['description'], virtualMachine['status'],
1475 # virtualMachine['image'], virtualMachine['hardware_profile']['vm_size'], subnets)
1476
1477 azure.new_network("mynet", None)
1478 net_id = "/subscriptions/82f80cc1-876b-4591-9911-1fb5788384fd/resourceGroups/osmRG/providers/Microsoft."\
1479 "Network/virtualNetworks/test"
1480 net_id_not_found = "/subscriptions/82f80cc1-876b-4591-9911-1fb5788384fd/resourceGroups/osmRG/providers/"\
1481 "Microsoft.Network/virtualNetworks/testALF"
1482 azure.refresh_nets_status([net_id, net_id_not_found])