blob: 384d7533fac8068ba4b8e36774df0d95f9767040 [file] [log] [blame]
seryio34478552019-05-23 14:50:49 +02001# -*- coding: utf-8 -*-
2
3__author__='Sergio Gonzalez'
4__date__ ='$18-apr-2019 23:59:59$'
5
jamartinezv14a823d2019-08-01 11:45:15 +02006import base64
7
seryio34478552019-05-23 14:50:49 +02008import vimconn
9import logging
tierno24620412019-06-03 14:05:08 +000010import netaddr
seryio34478552019-05-23 14:50:49 +020011
12from os import getenv
13from uuid import uuid4
14
15from azure.common.credentials import ServicePrincipalCredentials
16from azure.mgmt.resource import ResourceManagementClient
17from azure.mgmt.network import NetworkManagementClient
18from azure.mgmt.compute import ComputeManagementClient
19
jamartinezv14a823d2019-08-01 11:45:15 +020020
21
tierno84efdc12019-05-29 09:29:01 +000022from msrestazure.azure_exceptions import CloudError
tiernodeb74b22019-05-27 10:24:50 +000023
jamartinezv14a823d2019-08-01 11:45:15 +020024if getenv('OSMRO_PDB_DEBUG'):
25 import sys
26 print(sys.path)
27 import pdb
28 pdb.set_trace()
29
30
seryio34478552019-05-23 14:50:49 +020031class vimconnector(vimconn.vimconnector):
32
tierno84efdc12019-05-29 09:29:01 +000033 provision_state2osm = {
34 "Deleting": "INACTIVE",
35 "Failed": "ERROR",
36 "Succeeded": "ACTIVE",
37 "Updating": "BUILD",
38 }
39
seryio34478552019-05-23 14:50:49 +020040 def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None, log_level=None,
41 config={}, persistent_info={}):
42
43 vimconn.vimconnector.__init__(self, uuid, name, tenant_id, tenant_name, url, url_admin, user, passwd, log_level,
44 config, persistent_info)
45
tierno84efdc12019-05-29 09:29:01 +000046 self.vnet_address_space = None
seryio34478552019-05-23 14:50:49 +020047 # LOGGER
48 self.logger = logging.getLogger('openmano.vim.azure')
49 if log_level:
50 logging.basicConfig()
51 self.logger.setLevel(getattr(logging, log_level))
52
53 # CREDENTIALS
54 self.credentials = ServicePrincipalCredentials(
tierno30d0d6d2019-05-27 08:14:01 +000055 client_id=user,
56 secret=passwd,
57 tenant=(tenant_id or tenant_name)
seryio34478552019-05-23 14:50:49 +020058 )
tierno30d0d6d2019-05-27 08:14:01 +000059
jamartinezv14a823d2019-08-01 11:45:15 +020060 self.tenant=(tenant_id or tenant_name)
61
tierno30d0d6d2019-05-27 08:14:01 +000062 # SUBSCRIPTION
seryio34478552019-05-23 14:50:49 +020063 if 'subscription_id' in config:
64 self.subscription_id = config.get('subscription_id')
65 self.logger.debug('Setting subscription '+str(self.subscription_id))
66 else:
67 raise vimconn.vimconnException('Subscription not specified')
tierno30d0d6d2019-05-27 08:14:01 +000068 # REGION
seryio34478552019-05-23 14:50:49 +020069 if 'region_name' in config:
70 self.region = config.get('region_name')
71 else:
72 raise vimconn.vimconnException('Azure region_name is not specified at config')
tierno30d0d6d2019-05-27 08:14:01 +000073 # RESOURCE_GROUP
seryio34478552019-05-23 14:50:49 +020074 if 'resource_group' in config:
75 self.resource_group = config.get('resource_group')
76 else:
77 raise vimconn.vimconnException('Azure resource_group is not specified at config')
78 # VNET_NAME
79 if 'vnet_name' in config:
80 self.vnet_name = config["vnet_name"]
81
82 # public ssh key
83 self.pub_key = config.get('pub_key')
84
85 def _reload_connection(self):
tiernodeb74b22019-05-27 10:24:50 +000086 """
87 Sets connections to work with Azure service APIs
88 :return:
89 """
seryio34478552019-05-23 14:50:49 +020090 self.logger.debug('Reloading API Connection')
91 try:
92 self.conn = ResourceManagementClient(self.credentials, self.subscription_id)
93 self.conn_compute = ComputeManagementClient(self.credentials, self.subscription_id)
94 self.conn_vnet = NetworkManagementClient(self.credentials, self.subscription_id)
tiernodeb74b22019-05-27 10:24:50 +000095 self._check_or_create_resource_group()
96 self._check_or_create_vnet()
seryio34478552019-05-23 14:50:49 +020097 except Exception as e:
98 self.format_vimconn_exception(e)
99
100 def _get_resource_name_from_resource_id(self, resource_id):
jamartinezv14a823d2019-08-01 11:45:15 +0200101
102 try:
103 resource=str(resource_id.split('/')[-1])
104 return resource
105 except Exception as e:
106 raise vimconn.vimconnNotFoundException("Resource name '{}' not found".format(resource_id))
seryio34478552019-05-23 14:50:49 +0200107
108 def _get_location_from_resource_group(self, resource_group_name):
jamartinezv14a823d2019-08-01 11:45:15 +0200109
110 try:
111 location=self.conn.resource_groups.get(resource_group_name).location
112 return location
113 except Exception as e:
114 raise vimconn.vimconnNotFoundException("Location '{}' not found".format(resource_group_name))
115
116
seryio34478552019-05-23 14:50:49 +0200117 def _get_resource_group_name_from_resource_id(self, resource_id):
jamartinezv14a823d2019-08-01 11:45:15 +0200118
119 try:
120 rg=str(resource_id.split('/')[4])
121 return rg
122 except Exception as e:
123 raise vimconn.vimconnNotFoundException("Resource group '{}' not found".format(resource_id))
124
125
126 def _get_net_name_from_resource_id(self, resource_id):
127
128 try:
129 net_name=str(resource_id.split('/')[8])
130 return net_name
131 except Exception as e:
132 raise vimconn.vimconnNotFoundException("Net name '{}' not found".format(resource_id))
133
seryio34478552019-05-23 14:50:49 +0200134
135 def _check_subnets_for_vm(self, net_list):
tierno30d0d6d2019-05-27 08:14:01 +0000136 # All subnets must belong to the same resource group and vnet
jamartinezv14a823d2019-08-01 11:45:15 +0200137 # ERROR
138 # File "/root/RO/build/osm_ro/vimconn_azure.py", line 110, in <genexpr>
139 # self._get_resource_name_from_resource_id(net['id']) for net in net_list)) != 1:
140 #if len(set(self._get_resource_group_name_from_resource_id(net['net_id']) +
141 # self._get_resource_name_from_resource_id(net['net_id']) for net in net_list)) != 2:
142 # raise self.format_vimconn_exception('Azure VMs can only attach to subnets in same VNET')
143 self.logger.debug('Checking subnets for VM')
144 num_elem_set = len(set(self._get_resource_group_name_from_resource_id(net['net_id']) +
145 self._get_resource_name_from_resource_id(net['net_id']) for net in net_list))
146
147 if ( num_elem_set != 1 ):
148 raise self.format_vimconn_exception('Azure VMs can only attach to subnets in same VNET')
seryio34478552019-05-23 14:50:49 +0200149
150 def format_vimconn_exception(self, e):
tiernodeb74b22019-05-27 10:24:50 +0000151 """
152 Params: an Exception object
153 :param e:
154 :return: Raises the proper vimconnException
155 """
seryio34478552019-05-23 14:50:49 +0200156 self.conn = None
157 self.conn_vnet = None
jamartinezv14a823d2019-08-01 11:45:15 +0200158 raise vimconn.vimconnException(type(e).__name__ + ': ' + str(e))
seryio34478552019-05-23 14:50:49 +0200159
160 def _check_or_create_resource_group(self):
tiernodeb74b22019-05-27 10:24:50 +0000161 """
162 Creates a resource group in indicated region
163 :return: None
164 """
seryio34478552019-05-23 14:50:49 +0200165 self.logger.debug('Creating RG {} in location {}'.format(self.resource_group, self.region))
tiernodeb74b22019-05-27 10:24:50 +0000166 self.conn.resource_groups.create_or_update(self.resource_group, {'location': self.region})
167
168 def _check_or_create_vnet(self):
jamartinezv14a823d2019-08-01 11:45:15 +0200169
tiernodeb74b22019-05-27 10:24:50 +0000170 try:
tierno84efdc12019-05-29 09:29:01 +0000171 vnet = self.conn_vnet.virtual_networks.get(self.resource_group, self.vnet_name)
172 self.vnet_address_space = vnet.address_space.address_prefixes[0]
tierno24620412019-06-03 14:05:08 +0000173 self.vnet_id = vnet.id
jamartinezv14a823d2019-08-01 11:45:15 +0200174
tierno84efdc12019-05-29 09:29:01 +0000175 return
176 except CloudError as e:
177 if e.error.error == "ResourceNotFound":
178 pass
179 else:
180 raise
181 # if not exist, creates it
182 try:
tiernodeb74b22019-05-27 10:24:50 +0000183 vnet_params = {
184 'location': self.region,
185 'address_space': {
tierno84efdc12019-05-29 09:29:01 +0000186 'address_prefixes': ["10.0.0.0/8"]
tiernodeb74b22019-05-27 10:24:50 +0000187 },
188 }
tierno84efdc12019-05-29 09:29:01 +0000189 self.vnet_address_space = "10.0.0.0/8"
jamartinezv14a823d2019-08-01 11:45:15 +0200190
tiernodeb74b22019-05-27 10:24:50 +0000191 self.conn_vnet.virtual_networks.create_or_update(self.resource_group, self.vnet_name, vnet_params)
tierno24620412019-06-03 14:05:08 +0000192 vnet = self.conn_vnet.virtual_networks.get(self.resource_group, self.vnet_name)
193 self.vnet_id = vnet.id
tiernodeb74b22019-05-27 10:24:50 +0000194 except Exception as e:
195 self.format_vimconn_exception(e)
seryio34478552019-05-23 14:50:49 +0200196
197 def new_network(self, net_name, net_type, ip_profile=None, shared=False, vlan=None):
tiernodeb74b22019-05-27 10:24:50 +0000198 """
199 Adds a tenant network to VIM
200 :param net_name: name of the network
201 :param net_type:
202 :param ip_profile: is a dict containing the IP parameters of the network (Currently only IPv4 is implemented)
seryio34478552019-05-23 14:50:49 +0200203 'ip-version': can be one of ['IPv4','IPv6']
204 'subnet-address': ip_prefix_schema, that is X.X.X.X/Y
205 'gateway-address': (Optional) ip_schema, that is X.X.X.X
206 'dns-address': (Optional) ip_schema,
207 'dhcp': (Optional) dict containing
208 'enabled': {'type': 'boolean'},
209 'start-address': ip_schema, first IP to grant
210 'count': number of IPs to grant.
tiernodeb74b22019-05-27 10:24:50 +0000211 :param shared:
212 :param vlan:
213 :return: a tuple with the network identifier and created_items, or raises an exception on error
seryio34478552019-05-23 14:50:49 +0200214 created_items can be None or a dictionary where this method can include key-values that will be passed to
215 the method delete_network. Can be used to store created segments, created l2gw connections, etc.
216 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
217 as not present.
tiernodeb74b22019-05-27 10:24:50 +0000218 """
seryio34478552019-05-23 14:50:49 +0200219 return self._new_subnet(net_name, ip_profile)
220
221 def _new_subnet(self, net_name, ip_profile):
tiernodeb74b22019-05-27 10:24:50 +0000222 """
223 Adds a tenant network to VIM. It creates a new VNET with a single subnet
224 :param net_name:
225 :param ip_profile:
226 :return:
227 """
seryio34478552019-05-23 14:50:49 +0200228 self.logger.debug('Adding a subnet to VNET '+self.vnet_name)
229 self._reload_connection()
seryio34478552019-05-23 14:50:49 +0200230
231 if ip_profile is None:
tierno24620412019-06-03 14:05:08 +0000232 # get a non used vnet ip range /24 and allocate automatically inside the range self.vnet_address_space
233 used_subnets = self.get_network_list()
234 for ip_range in netaddr.IPNetwork(self.vnet_address_space).subnet(24):
235 for used_subnet in used_subnets:
236 subnet_range = netaddr.IPNetwork(used_subnet["cidr_block"])
237 if subnet_range in ip_range or ip_range in subnet_range:
238 # this range overlaps with an existing subnet ip range. Breaks and look for another
239 break
240 else:
241 ip_profile = {"subnet_address": str(ip_range)}
jamartinezv14a823d2019-08-01 11:45:15 +0200242 self.logger.debug('ip_profile: ' + str(ip_range))
tierno24620412019-06-03 14:05:08 +0000243 break
244 else:
245 vimconn.vimconnException("Cannot find a non-used subnet range in {}".format(self.vnet_address_space))
jamartinezv14a823d2019-08-01 11:45:15 +0200246 else:
247 ip_profile = {"subnet_address": ip_profile['subnet_address']}
248
seryio34478552019-05-23 14:50:49 +0200249 try:
jamartinezv14a823d2019-08-01 11:45:15 +0200250 #subnet_name = "{}-{}".format(net_name[:24], uuid4())
251 subnet_name = net_name[:24]
tierno24620412019-06-03 14:05:08 +0000252 subnet_params= {
253 'address_prefix': ip_profile['subnet_address']
seryio34478552019-05-23 14:50:49 +0200254 }
jamartinezv14a823d2019-08-01 11:45:15 +0200255 self.logger.debug('subnet_name : {}'.format(subnet_name))
256 async_creation=self.conn_vnet.subnets.create_or_update(self.resource_group, self.vnet_name, subnet_name, subnet_params)
257 async_creation.wait()
258
259 #return "{}/subnet/{}".format(self.vnet_id, subnet_name), None
260 return "{}/subnets/{}".format(self.vnet_id, subnet_name), None
seryio34478552019-05-23 14:50:49 +0200261 except Exception as e:
262 self.format_vimconn_exception(e)
263
jamartinezv14a823d2019-08-01 11:45:15 +0200264 def _create_nic(self, net, nic_name, static_ip=None):
seryio34478552019-05-23 14:50:49 +0200265
jamartinezv14a823d2019-08-01 11:45:15 +0200266 self._reload_connection()
267
268 subnet_id = net['net_id']
269 location = self._get_location_from_resource_group(self.resource_group)
270
271 try:
272 if static_ip:
273 async_nic_creation = self.conn_vnet.network_interfaces.create_or_update(
274 self.resource_group,
275 nic_name,
276 {
277 'location': location,
278 'ip_configurations': [{
279 'name': nic_name + '-ipconfiguration',
280 'privateIPAddress': static_ip,
281 'privateIPAllocationMethod': 'Static',
282 'subnet': {
283 'id': subnet_id
284 }
285 }]
286 }
287 )
288 async_nic_creation.wait()
289 else:
290 ip_configuration_name = nic_name + '-ipconfiguration'
291 self.logger.debug('Create NIC')
292 async_nic_creation = self.conn_vnet.network_interfaces.create_or_update(
293 self.resource_group,
294 nic_name,
295 {
296 'location': location,
297 'ip_configurations': [{
298 'name': ip_configuration_name,
299 'subnet': {
300 'id': subnet_id
301 }
302 }]
303 }
304 )
305 async_nic_creation.wait()
306
307 public_ip = net.get('floating_ip')
308 if public_ip and public_ip == True:
309 self.logger.debug('Creating PUBLIC IP')
310 public_ip_addess_params = {
311 'location': location,
312 'public_ip_allocation_method': 'Dynamic'
313 }
314 public_ip_name = nic_name + '-public-ip'
315 public_ip = self.conn_vnet.public_ip_addresses.create_or_update(
316 self.resource_group,
317 public_ip_name,
318 public_ip_addess_params
319 )
320 self.logger.debug('Create PUBLIC IP: {}'.format(public_ip.result()))
321
322 # Asociate NIC to Public IP
323 self.logger.debug('Getting NIC DATA')
324 nic_data = self.conn_vnet.network_interfaces.get(
325 self.resource_group,
326 nic_name)
327
328 nic_data.ip_configurations[0].public_ip_address = public_ip.result()
329
330 self.logger.debug('Updating NIC with public IP')
331 self.conn_vnet.network_interfaces.create_or_update(
332 self.resource_group,
333 nic_name,
334 nic_data)
335
336 except Exception as e:
337 self.format_vimconn_exception(e)
338
339 result = async_nic_creation.result()
seryio34478552019-05-23 14:50:49 +0200340 return async_nic_creation.result()
341
jamartinezv14a823d2019-08-01 11:45:15 +0200342 def new_flavor(self, flavor_data):
tiernodeb74b22019-05-27 10:24:50 +0000343
jamartinezv14a823d2019-08-01 11:45:15 +0200344 if flavor_data:
345 flavor_id = self.get_flavor_id_from_data(flavor_data)
346
347 if flavor_id != []:
348 return flavor_id
349 else:
350 raise vimconn.vimconnNotFoundException("flavor '{}' not found".format(flavor_data))
351 else:
352 vimconn.vimconnException("There is no data in the flavor_data input parameter")
353
354 def new_tenant(self,tenant_name,tenant_description):
355
356 raise vimconn.vimconnAuthException("It is not possible to create a TENANT in AZURE")
357
358 def new_image(self, image_dict):
tiernodeb74b22019-05-27 10:24:50 +0000359
360 self._reload_connection()
jamartinezv14a823d2019-08-01 11:45:15 +0200361
362 try:
363 self.logger.debug('new_image - image_dict - {}'.format(image_dict))
364
365 if image_dict.get("name"):
366 image_name = image_dict.get("name")
367 else:
368 raise vimconn.vimconnException("There is no name in the image input data")
369
370 if image_dict.get("location"):
371 params = image_dict["location"].split(":")
372 if len(params) >= 4:
373 publisher = params[0]
374 offer = params[1]
375 sku = params[2]
376 version = params[3]
377 #image_params = {'location': self.region, 'publisher': publisher, 'offer': offer, 'sku': sku, 'version': version }
378 image_params = {'location': self.region}
379
380 self.conn_compute.images.create_or_update()
381 async_creation=self.conn_compute.images.create_or_update(self.resource_group, image_name, image_params)
382 image_id = async_creation.result().id
383 else:
384 raise vimconn.vimconnException("The image location is not correct: {}".format(image_dict["location"]))
385 return image_id
386
387 except Exception as e:
388 self.format_vimconn_exception(e)
389
390 def get_image_id_from_path(self, path):
391 """Get the image id from image path in the VIM database.
392 Returns the image_id or raises a vimconnNotFoundException
393 """
394
395 def get_image_list(self, filter_dict={}):
396 """Obtain tenant images from VIM
397 Filter_dict can be:
398 name: image name
399 id: image uuid
400 checksum: image checksum
401 location: image path
402 Returns the image list of dictionaries:
403 [{<the fields at Filter_dict plus some VIM specific>}, ...]
404 List can be empty
405 """
406 self._reload_connection()
407
408 image_list = []
tiernodeb74b22019-05-27 10:24:50 +0000409 if filter_dict.get("name"):
410 params = filter_dict["name"].split(":")
411 if len(params) >= 3:
412 publisher = params[0]
413 offer = params[1]
414 sku = params[2]
415 version = None
416 if len(params) == 4:
417 version = params[3]
418 images = self.conn_compute.virtual_machine_images.list(self.region, publisher, offer, sku)
419 for image in images:
420 if version:
421 image_version = str(image.id).split("/")[-1]
422 if image_version != version:
423 continue
424 image_list.append({
425 'id': str(image.id),
426 'name': self._get_resource_name_from_resource_id(image.id)
427 })
tiernodeb74b22019-05-27 10:24:50 +0000428
tiernodeb74b22019-05-27 10:24:50 +0000429 return image_list
430
seryio34478552019-05-23 14:50:49 +0200431 def get_network_list(self, filter_dict={}):
tiernodeb74b22019-05-27 10:24:50 +0000432 """Obtain tenant networks of VIM
seryio34478552019-05-23 14:50:49 +0200433 Filter_dict can be:
434 name: network name
435 id: network uuid
436 shared: boolean
437 tenant_id: tenant
438 admin_state_up: boolean
439 status: 'ACTIVE'
440 Returns the network list of dictionaries
tiernodeb74b22019-05-27 10:24:50 +0000441 """
seryio34478552019-05-23 14:50:49 +0200442 self.logger.debug('Getting all subnets from VIM')
443 try:
444 self._reload_connection()
jamartinezv14a823d2019-08-01 11:45:15 +0200445
446 vnet = self.conn_vnet.virtual_networks.get(self.resource_group, self.vnet_name)
seryio34478552019-05-23 14:50:49 +0200447 subnet_list = []
jamartinezv14a823d2019-08-01 11:45:15 +0200448
seryio34478552019-05-23 14:50:49 +0200449 for subnet in vnet.subnets:
jamartinezv14a823d2019-08-01 11:45:15 +0200450
seryio34478552019-05-23 14:50:49 +0200451 if filter_dict:
452 if filter_dict.get("id") and str(subnet.id) != filter_dict["id"]:
453 continue
454 if filter_dict.get("name") and \
jamartinezv14a823d2019-08-01 11:45:15 +0200455 str(subnet.id) != filter_dict["name"]:
seryio34478552019-05-23 14:50:49 +0200456 continue
457
jamartinezv14a823d2019-08-01 11:45:15 +0200458 name = self._get_resource_name_from_resource_id(subnet.id)
459
seryio34478552019-05-23 14:50:49 +0200460 subnet_list.append({
461 'id': str(subnet.id),
jamartinezv14a823d2019-08-01 11:45:15 +0200462 'name': self._get_resource_name_from_resource_id(subnet.id),
463 'status' : self.provision_state2osm[subnet.provisioning_state],
464 'cidr_block': str(subnet.address_prefix),
465 'type': 'bridge',
466 'shared': False
seryio34478552019-05-23 14:50:49 +0200467 }
468 )
jamartinezv14a823d2019-08-01 11:45:15 +0200469
seryio34478552019-05-23 14:50:49 +0200470 return subnet_list
471 except Exception as e:
472 self.format_vimconn_exception(e)
473
474 def new_vminstance(self, vm_name, description, start, image_id, flavor_id, net_list, cloud_config=None,
475 disk_list=None, availability_zone_index=None, availability_zone_list=None):
476
477 return self._new_vminstance(vm_name, image_id, flavor_id, net_list)
478
jamartinezv14a823d2019-08-01 11:45:15 +0200479 #def _new_vminstance(self, vm_name, image_id, flavor_id, net_list, cloud_config=None, disk_list=None,
480 # availability_zone_index=None, availability_zone_list=None):
481 def new_vminstance(self, name, description, start, image_id, flavor_id, net_list, cloud_config=None,
482 disk_list=None,
483 availability_zone_index=None, availability_zone_list=None):
484
seryio34478552019-05-23 14:50:49 +0200485 self._check_subnets_for_vm(net_list)
486 vm_nics = []
487 for idx, net in enumerate(net_list):
jamartinezv14a823d2019-08-01 11:45:15 +0200488 # Fault with subnet_id
489 # subnet_id=net['subnet_id']
490 # subnet_id=net['net_id']
491
492 nic_name = name + '-nic-'+str(idx)
493 vm_nic = self._create_nic(net, nic_name)
seryio34478552019-05-23 14:50:49 +0200494 vm_nics.append({ 'id': str(vm_nic.id)})
495
496 try:
jamartinezv14a823d2019-08-01 11:45:15 +0200497 # image_id are several fields of the image_id
498 image_reference = self.get_image_reference(image_id)
499
500 # The virtual machine name must have less or 64 characters and it can not have the following
501 # characters: (~ ! @ # $ % ^ & * ( ) = + _ [ ] { } \ | ; : ' " , < > / ?.)
502 vm_name_aux = self.check_vm_name(name)
503
504 # cloud-init configuration
505 # cloud config
506 if cloud_config:
507 config_drive, userdata = self._create_user_data(cloud_config)
508 custom_data = base64.b64encode(userdata.encode('utf-8')).decode('latin-1')
509 os_profile = {
510 'computer_name': vm_name_aux, # TODO if vm_name cannot be repeated add uuid4() suffix
511 'admin_username': 'osm', # TODO is it mandatory???
512 'admin_password': 'Osm-osm', # TODO is it mandatory???
513 'custom_data': custom_data
514 }
515 else:
516 os_profile = {
517 'computer_name': vm_name_aux, # TODO if vm_name cannot be repeated add uuid4() suffix
518 'admin_username': 'osm', # TODO is it mandatory???
519 'admin_password': 'Osm-osm', # TODO is it mandatory???
520 }
521
seryio34478552019-05-23 14:50:49 +0200522 vm_parameters = {
523 'location': self.region,
jamartinezv14a823d2019-08-01 11:45:15 +0200524 'os_profile': os_profile,
seryio34478552019-05-23 14:50:49 +0200525 'hardware_profile': {
jamartinezv14a823d2019-08-01 11:45:15 +0200526 'vm_size': flavor_id
seryio34478552019-05-23 14:50:49 +0200527 },
528 'storage_profile': {
jamartinezv14a823d2019-08-01 11:45:15 +0200529 'image_reference': image_reference
seryio34478552019-05-23 14:50:49 +0200530 },
531 'network_profile': {
532 'network_interfaces': [
533 vm_nics[0]
534 ]
535 }
536 }
jamartinezv14a823d2019-08-01 11:45:15 +0200537
seryio34478552019-05-23 14:50:49 +0200538 creation_result = self.conn_compute.virtual_machines.create_or_update(
539 self.resource_group,
jamartinezv14a823d2019-08-01 11:45:15 +0200540 vm_name_aux,
seryio34478552019-05-23 14:50:49 +0200541 vm_parameters
542 )
543
jamartinezv14a823d2019-08-01 11:45:15 +0200544 #creation_result.wait()
545 result = creation_result.result()
546
547 for index, subnet in enumerate(net_list):
548 net_list[index]['vim_id'] = result.id
549
550 if start == True:
551 #self.logger.debug('Arrancamos VM y esperamos')
552 start_result = self.conn_compute.virtual_machines.start(
553 self.resource_group,
554 vm_name_aux)
555 #start_result.wait()
556
557 return result.id, None
558
559 #run_command_parameters = {
560 # 'command_id': 'RunShellScript', # For linux, don't change it
561 # 'script': [
562 # 'date > /tmp/test.txt'
563 # ]
564 #}
seryio34478552019-05-23 14:50:49 +0200565 except Exception as e:
jamartinezv14a823d2019-08-01 11:45:15 +0200566 #self.logger.debug('AZURE <=== EX: _new_vminstance', exc_info=True)
seryio34478552019-05-23 14:50:49 +0200567 self.format_vimconn_exception(e)
568
jamartinezv14a823d2019-08-01 11:45:15 +0200569 # It is necesary extract from image_id data to create the VM with this format
570 # 'image_reference': {
571 # 'publisher': vm_reference['publisher'],
572 # 'offer': vm_reference['offer'],
573 # 'sku': vm_reference['sku'],
574 # 'version': vm_reference['version']
575 # },
576 def get_image_reference(self, imagen):
577
578 # The data input format example:
579 # /Subscriptions/ca3d18ab-d373-4afb-a5d6-7c44f098d16a/Providers/Microsoft.Compute/Locations/westeurope/
580 # Publishers/Canonical/ArtifactTypes/VMImage/
581 # Offers/UbuntuServer/
582 # Skus/18.04-LTS/
583 # Versions/18.04.201809110
584 publiser = str(imagen.split('/')[8])
585 offer = str(imagen.split('/')[12])
586 sku = str(imagen.split('/')[14])
587 version = str(imagen.split('/')[16])
588
589 return {
590 'publisher': publiser,
591 'offer': offer,
592 'sku': sku,
593 'version': version
594 }
595
596 # Azure VM names can not have some special characters
597 def check_vm_name( self, vm_name ):
598
599 #chars_not_allowed_list = ['~','!','@','#','$','%','^','&','*','(',')','=','+','_','[',']','{','}','|',';',':','<','>','/','?','.']
600 chars_not_allowed_list = "~!@#$%^&*()=+_[]{}|;:<>/?."
601
602 # First: the VM name max length is 64 characters
603 vm_name_aux = vm_name[:64]
604
605 # Second: replace not allowed characters
606 for elem in chars_not_allowed_list :
607 # Check if string is in the main string
608 if elem in vm_name_aux :
609 #self.logger.debug('Dentro del IF')
610 # Replace the string
611 vm_name_aux = vm_name_aux.replace(elem, '-')
612
613 return vm_name_aux
614
615
seryio34478552019-05-23 14:50:49 +0200616 def get_flavor_id_from_data(self, flavor_dict):
tierno30d0d6d2019-05-27 08:14:01 +0000617 self.logger.debug("Getting flavor id from data")
seryio34478552019-05-23 14:50:49 +0200618
jamartinezv14a823d2019-08-01 11:45:15 +0200619 try:
620 self._reload_connection()
621 vm_sizes_list = [vm_size.serialize() for vm_size in self.conn_compute.virtual_machine_sizes.list(self.region)]
tierno30d0d6d2019-05-27 08:14:01 +0000622
jamartinezv14a823d2019-08-01 11:45:15 +0200623 cpus = flavor_dict['vcpus']
624 memMB = flavor_dict['ram']
tierno30d0d6d2019-05-27 08:14:01 +0000625
jamartinezv14a823d2019-08-01 11:45:15 +0200626 filteredSizes = [size for size in vm_sizes_list if size['numberOfCores'] >= cpus and size['memoryInMB'] >= memMB]
627 listedFilteredSizes = sorted(filteredSizes, key=lambda k: k['numberOfCores'])
628
629 return listedFilteredSizes[0]['name']
630
631 except Exception as e:
632 self.format_vimconn_exception(e)
633
634 def _get_flavor_id_from_flavor_name(self, flavor_name):
635 self.logger.debug("Getting flavor id from falvor name {}".format(flavor_name))
636
637 try:
638 self._reload_connection()
639 vm_sizes_list = [vm_size.serialize() for vm_size in self.conn_compute.virtual_machine_sizes.list(self.region)]
640
641 output_flavor = None
642 for size in vm_sizes_list:
643 if size['name'] == flavor_name:
644 output_flavor = size
645
646 return output_flavor
647
648 except Exception as e:
649 self.format_vimconn_exception(e)
tierno30d0d6d2019-05-27 08:14:01 +0000650
651 def check_vim_connectivity(self):
seryio34478552019-05-23 14:50:49 +0200652 try:
653 self._reload_connection()
tierno30d0d6d2019-05-27 08:14:01 +0000654 return True
655 except Exception as e:
656 raise vimconn.vimconnException("Connectivity issue with Azure API: {}".format(e))
seryio34478552019-05-23 14:50:49 +0200657
658 def get_network(self, net_id):
seryio34478552019-05-23 14:50:49 +0200659
seryio34478552019-05-23 14:50:49 +0200660 resName = self._get_resource_name_from_resource_id(net_id)
seryio34478552019-05-23 14:50:49 +0200661
seryio34478552019-05-23 14:50:49 +0200662 self._reload_connection()
jamartinezv14a823d2019-08-01 11:45:15 +0200663
664 filter_dict = {'name' : net_id}
665 network_list = self.get_network_list(filter_dict)
666
667 if not network_list:
668 raise vimconn.vimconnNotFoundException("network '{}' not found".format(net_id))
669 else:
670 return network_list[0]
671
672 # Added created_items because it is neccesary
673 # self.vim.delete_network(net_vim_id, task["extra"].get("created_items"))
674 # TypeError: delete_network() takes exactly 2 arguments (3 given)
675 def delete_network(self, net_id, created_items=None):
676
677 self.logger.debug('Deletting network {} - {}'.format(self.resource_group, net_id))
678
679 resName = self._get_resource_name_from_resource_id(net_id)
680
681 self._reload_connection()
682
683 filter_dict = {'name' : net_id}
684 network_list = self.get_network_list(filter_dict)
685 if not network_list:
686 raise vimconn.vimconnNotFoundException("network '{}' not found".format(net_id))
687
688 try:
689 # Subnet API fails (CloudError: Azure Error: ResourceNotFound)
690 # Put the initial virtual_network API
691 async_delete=self.conn_vnet.subnets.delete(self.resource_group, self.vnet_name, resName)
692 return net_id
693
694 except CloudError as e:
695 if e.error.error == "ResourceNotFound":
696 raise vimconn.vimconnNotFoundException("network '{}' not found".format(net_id))
697 else:
698 raise
699 except Exception as e:
700 self.format_vimconn_exception(e)
701
702
703
704 # Added third parameter because it is necesary
705 def delete_vminstance(self, vm_id, created_items=None):
706
707 self.logger.debug('Deletting VM instance {} - {}'.format(self.resource_group, vm_id))
708 self._reload_connection()
709
710 try:
711
712 resName = self._get_resource_name_from_resource_id(vm_id)
713 vm = self.conn_compute.virtual_machines.get(self.resource_group, resName)
714
715 # Shuts down the virtual machine and releases the compute resources
716 #vm_stop = self.conn_compute.virtual_machines.power_off(self.resource_group, resName)
717 #vm_stop.wait()
718
719 vm_delete = self.conn_compute.virtual_machines.delete(self.resource_group, resName)
720 vm_delete.wait()
721
722 # Delete OS Disk
723 os_disk_name = vm.storage_profile.os_disk.name
724 self.logger.debug('Delete OS DISK - ' + os_disk_name)
725 self.conn_compute.disks.delete(self.resource_group, os_disk_name)
726
727 # After deletting VM, it is necessary delete NIC, because if is not deleted delete_network
728 # does not work because Azure says that is in use the subnet
729 network_interfaces = vm.network_profile.network_interfaces
730
731 for network_interface in network_interfaces:
732
733 #self.logger.debug('nic - {}'.format(network_interface))
734
735 nic_name = self._get_resource_name_from_resource_id(network_interface.id)
736
737 #self.logger.debug('nic_name - {}'.format(nic_name))
738
739 nic_data = self.conn_vnet.network_interfaces.get(
740 self.resource_group,
741 nic_name)
742
743 exist_public_ip = nic_data.ip_configurations[0].public_ip_address
744 if exist_public_ip:
745 public_ip_id = nic_data.ip_configurations[0].public_ip_address.id
746 self.logger.debug('Public ip id - ' + public_ip_id)
747
748 self.logger.debug('Delete NIC - ' + nic_name)
749 nic_delete = self.conn_vnet.network_interfaces.delete(self.resource_group, nic_name)
750 nic_delete.wait()
751
752 # Delete public_ip
753 public_ip_name = self._get_resource_name_from_resource_id(public_ip_id)
754
755 self.logger.debug('Delete PUBLIC IP - ' + public_ip_name)
756 public_ip = self.conn_vnet.public_ip_addresses.delete(self.resource_group, public_ip_name)
757 except CloudError as e:
758 if e.error.error == "ResourceNotFound":
759 raise vimconn.vimconnNotFoundException("No vminstance found '{}'".format(vm_id))
760 else:
761 raise
762 except Exception as e:
763 self.format_vimconn_exception(e)
764
765 def action_vminstance(self, vm_id, action_dict, created_items={}):
766 """Send and action over a VM instance from VIM
767 Returns the vm_id if the action was successfully sent to the VIM"""
768
769 self.logger.debug("Action over VM '%s': %s", vm_id, str(action_dict))
770 try:
771 self._reload_connection()
772 resName = self._get_resource_name_from_resource_id(vm_id)
773 if "start" in action_dict:
774 self.conn_compute.virtual_machines.start(self.resource_group,resName)
775 elif "stop" in action_dict or "shutdown" in action_dict or "shutoff" in action_dict:
776 self.conn_compute.virtual_machines.power_off(self.resource_group,resName)
777 elif "terminate" in action_dict:
778 self.conn_compute.virtual_machines.delete(self.resource_group,resName)
779 elif "reboot" in action_dict:
780 self.conn_compute.virtual_machines.restart(self.resource_group,resName)
781 return None
782 except CloudError as e:
783 if e.error.error == "ResourceNotFound":
784 raise vimconn.vimconnNotFoundException("No vm found '{}'".format(vm_id))
785 else:
786 raise
787 except Exception as e:
788 self.format_vimconn_exception(e)
789
790 def delete_flavor(self, flavor_id):
791
792 raise vimconn.vimconnAuthException("It is not possible to delete a FLAVOR in AZURE")
793
794 def delete_tenant(self,tenant_id,):
795
796 raise vimconn.vimconnAuthException("It is not possible to delete a TENANT in AZURE")
797
798 def delete_image(self, image_id):
799
800 raise vimconn.vimconnAuthException("It is not possible to delete a IMAGE in AZURE")
seryio34478552019-05-23 14:50:49 +0200801
802 def get_vminstance(self, vm_id):
jamartinezv14a823d2019-08-01 11:45:15 +0200803
seryio34478552019-05-23 14:50:49 +0200804 self._reload_connection()
jamartinezv14a823d2019-08-01 11:45:15 +0200805 try:
806 resName = self._get_resource_name_from_resource_id(vm_id)
807 vm=self.conn_compute.virtual_machines.get(self.resource_group, resName)
808 except CloudError as e:
809 if e.error.error == "ResourceNotFound":
810 raise vimconn.vimconnNotFoundException("No vminstance found '{}'".format(vm_id))
811 else:
812 raise
813 except Exception as e:
814 self.format_vimconn_exception(e)
seryio34478552019-05-23 14:50:49 +0200815
816 return vm
817
818 def get_flavor(self, flavor_id):
819 self._reload_connection()
jamartinezv14a823d2019-08-01 11:45:15 +0200820
821 flavor_data = self._get_flavor_id_from_flavor_name(flavor_id)
822 if flavor_data:
823 flavor = {
824 'id': flavor_id,
825 'name': flavor_id,
826 'ram': flavor_data['memoryInMB'],
827 'vcpus': flavor_data['numberOfCores'],
828 'disk': flavor_data['resourceDiskSizeInMB']
829 }
830 return flavor
831 else:
832 raise vimconn.vimconnNotFoundException("flavor '{}' not found".format(flavor_id))
833
834
835 def get_tenant_list(self, filter_dict={}):
836
837 tenants_azure=[{'name': self.tenant, 'id': self.tenant}]
838 tenant_list=[]
839
840 for tenant_azure in tenants_azure:
841 if filter_dict:
842 if filter_dict.get("id") and str(tenant_azure.get("id")) != filter_dict["id"]:
843 continue
844 if filter_dict.get("name") and str(tenant_azure.get("name")) != filter_dict["name"]:
845 continue
846
847 tenant_list.append(tenant_azure)
848
849 return tenant_list
seryio34478552019-05-23 14:50:49 +0200850
tierno84efdc12019-05-29 09:29:01 +0000851 def refresh_nets_status(self, net_list):
jamartinezv14a823d2019-08-01 11:45:15 +0200852
tierno84efdc12019-05-29 09:29:01 +0000853 out_nets = {}
854 self._reload_connection()
855 for net_id in net_list:
856 try:
jamartinezv14a823d2019-08-01 11:45:15 +0200857 netName = self._get_net_name_from_resource_id(net_id)
tierno84efdc12019-05-29 09:29:01 +0000858 resName = self._get_resource_name_from_resource_id(net_id)
seryio34478552019-05-23 14:50:49 +0200859
jamartinezv14a823d2019-08-01 11:45:15 +0200860 net = self.conn_vnet.subnets.get(self.resource_group, netName, resName)
861
tierno84efdc12019-05-29 09:29:01 +0000862 out_nets[net_id] ={
jamartinezv14a823d2019-08-01 11:45:15 +0200863 "status": self.provision_state2osm[net.provisioning_state],
864 "vim_info": str(net)
tierno84efdc12019-05-29 09:29:01 +0000865 }
866 except CloudError as e:
867 if e.error.error == "ResourceNotFound":
868 out_nets[net_id] = {
869 "status": "DELETED",
jamartinezv14a823d2019-08-01 11:45:15 +0200870 "error_msg": str(e)
tierno84efdc12019-05-29 09:29:01 +0000871 }
872 else:
873 raise
jamartinezv14a823d2019-08-01 11:45:15 +0200874 except vimconn.vimconnNotFoundException as e:
875 out_nets[net_id] = {
876 "status": "DELETED",
877 "error_msg": str(e)
878 }
tierno84efdc12019-05-29 09:29:01 +0000879 except Exception as e:
880 # TODO distinguish when it is deleted
881 out_nets[net_id] = {
882 "status": "VIM_ERROR",
tierno84efdc12019-05-29 09:29:01 +0000883 "error_msg": str(e)
884 }
tierno84efdc12019-05-29 09:29:01 +0000885 return out_nets
886
887 def refresh_vms_status(self, vm_list):
jamartinezv14a823d2019-08-01 11:45:15 +0200888
tierno84efdc12019-05-29 09:29:01 +0000889 out_vms = {}
jamartinezv14a823d2019-08-01 11:45:15 +0200890 out_vms_dict = {}
tierno84efdc12019-05-29 09:29:01 +0000891 self._reload_connection()
jamartinezv14a823d2019-08-01 11:45:15 +0200892
tierno84efdc12019-05-29 09:29:01 +0000893 for vm_id in vm_list:
894 try:
jamartinezv14a823d2019-08-01 11:45:15 +0200895
tierno84efdc12019-05-29 09:29:01 +0000896 resName = self._get_resource_name_from_resource_id(vm_id)
897
jamartinezv14a823d2019-08-01 11:45:15 +0200898 vm = self.conn_compute.virtual_machines.get(self.resource_group, resName)
899 out_vms_dict['status'] = self.provision_state2osm[vm.provisioning_state]
900 out_vms_dict['interfaces'] = []
901 interface_dict = {}
902
903 network_interfaces = vm.network_profile.network_interfaces
904
905 for network_interface in network_interfaces:
906
907 nic_name = self._get_resource_name_from_resource_id(network_interface.id)
908 interface_dict['vim_interface_id'] = vm_id
909
910 nic_data = self.conn_vnet.network_interfaces.get(
911 self.resource_group,
912 nic_name)
913
914 private_ip = nic_data.ip_configurations[0].private_ip_address
915
916 interface_dict['mac_address'] = nic_data.mac_address
917 interface_dict['ip_address'] = private_ip
918 out_vms_dict['interfaces'].append(interface_dict)
919
tierno84efdc12019-05-29 09:29:01 +0000920 except Exception as e:
jamartinezv14a823d2019-08-01 11:45:15 +0200921 out_vms_dict['status'] = "DELETED"
922 out_vms_dict['error_msg'] = str(e)
923 vm = None
924 finally:
925 if vm:
926 out_vms_dict['vim_info'] = str(vm)
927
928 out_vms[vm_id] = out_vms_dict
tierno84efdc12019-05-29 09:29:01 +0000929
930 return out_vms
seryio07ad1362019-05-29 09:16:24 +0200931
seryio34478552019-05-23 14:50:49 +0200932
933if __name__ == "__main__":
934
935 # Making some basic test
936 vim_id='azure'
937 vim_name='azure'
938 needed_test_params = {
tiernodeb74b22019-05-27 10:24:50 +0000939 "client_id": "AZURE_CLIENT_ID",
940 "secret": "AZURE_SECRET",
941 "tenant": "AZURE_TENANT",
942 "resource_group": "AZURE_RESOURCE_GROUP",
943 "subscription_id": "AZURE_SUBSCRIPTION_ID",
tierno30d0d6d2019-05-27 08:14:01 +0000944 "vnet_name": "AZURE_VNET_NAME",
seryio34478552019-05-23 14:50:49 +0200945 }
946 test_params = {}
947
948 for param, env_var in needed_test_params.items():
949 value = getenv(env_var)
950 if not value:
951 raise Exception("Provide a valid value for env '{}'".format(env_var))
952 test_params[param] = value
953
954 config = {
955 'region_name': getenv("AZURE_REGION_NAME", 'westeurope'),
956 'resource_group': getenv("AZURE_RESOURCE_GROUP"),
957 'subscription_id': getenv("AZURE_SUBSCRIPTION_ID"),
tierno30d0d6d2019-05-27 08:14:01 +0000958 'pub_key': getenv("AZURE_PUB_KEY", None),
seryio34478552019-05-23 14:50:49 +0200959 'vnet_name': getenv("AZURE_VNET_NAME", 'myNetwork'),
960 }
tierno30d0d6d2019-05-27 08:14:01 +0000961
seryio34478552019-05-23 14:50:49 +0200962 virtualMachine = {
tierno30d0d6d2019-05-27 08:14:01 +0000963 'name': 'sergio',
964 'description': 'new VM',
seryio34478552019-05-23 14:50:49 +0200965 'status': 'running',
966 'image': {
967 'publisher': 'Canonical',
968 'offer': 'UbuntuServer',
969 'sku': '16.04.0-LTS',
970 'version': 'latest'
971 },
972 'hardware_profile': {
973 'vm_size': 'Standard_DS1_v2'
974 },
975 'networks': [
976 'sergio'
977 ]
978 }
979
980 vnet_config = {
981 'subnet_address': '10.1.2.0/24',
982 #'subnet_name': 'subnet-oam'
983 }
984 ###########################
985
tierno30d0d6d2019-05-27 08:14:01 +0000986 azure = vimconnector(vim_id, vim_name, tenant_id=test_params["tenant"], tenant_name=None, url=None, url_admin=None,
987 user=test_params["client_id"], passwd=test_params["secret"], log_level=None, config=config)
seryio34478552019-05-23 14:50:49 +0200988
tiernodeb74b22019-05-27 10:24:50 +0000989 # azure.get_flavor_id_from_data("here")
990 # subnets=azure.get_network_list()
991 # azure.new_vminstance(virtualMachine['name'], virtualMachine['description'], virtualMachine['status'],
992 # virtualMachine['image'], virtualMachine['hardware_profile']['vm_size'], subnets)
993
tierno24620412019-06-03 14:05:08 +0000994 azure.new_network("mynet", None)
tierno84efdc12019-05-29 09:29:01 +0000995 net_id = "/subscriptions/82f80cc1-876b-4591-9911-1fb5788384fd/resourceGroups/osmRG/providers/Microsoft."\
996 "Network/virtualNetworks/test"
997 net_id_not_found = "/subscriptions/82f80cc1-876b-4591-9911-1fb5788384fd/resourceGroups/osmRG/providers/"\
998 "Microsoft.Network/virtualNetworks/testALF"
999 azure.refresh_nets_status([net_id, net_id_not_found])