fix some issues at azure plugin
[osm/RO.git] / osm_ro / vimconn_azure.py
1 # -*- coding: utf-8 -*-
2
3 __author__='Sergio Gonzalez'
4 __date__ ='$18-apr-2019 23:59:59$'
5
6 import vimconn
7 import logging
8
9 from os import getenv
10 from uuid import uuid4
11
12 from azure.common.credentials import ServicePrincipalCredentials
13 from azure.mgmt.resource import ResourceManagementClient
14 from azure.mgmt.network import NetworkManagementClient
15 from azure.mgmt.compute import ComputeManagementClient
16
17 class vimconnector(vimconn.vimconnector):
18
19 def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None, log_level=None,
20 config={}, persistent_info={}):
21
22 vimconn.vimconnector.__init__(self, uuid, name, tenant_id, tenant_name, url, url_admin, user, passwd, log_level,
23 config, persistent_info)
24
25 # LOGGER
26 self.logger = logging.getLogger('openmano.vim.azure')
27 if log_level:
28 logging.basicConfig()
29 self.logger.setLevel(getattr(logging, log_level))
30
31 # CREDENTIALS
32 self.credentials = ServicePrincipalCredentials(
33 client_id=user,
34 secret=passwd,
35 tenant=(tenant_id or tenant_name)
36 )
37
38 # SUBSCRIPTION
39 if 'subscription_id' in config:
40 self.subscription_id = config.get('subscription_id')
41 self.logger.debug('Setting subscription '+str(self.subscription_id))
42 else:
43 raise vimconn.vimconnException('Subscription not specified')
44 # REGION
45 if 'region_name' in config:
46 self.region = config.get('region_name')
47 else:
48 raise vimconn.vimconnException('Azure region_name is not specified at config')
49 # RESOURCE_GROUP
50 if 'resource_group' in config:
51 self.resource_group = config.get('resource_group')
52 else:
53 raise vimconn.vimconnException('Azure resource_group is not specified at config')
54 # VNET_NAME
55 if 'vnet_name' in config:
56 self.vnet_name = config["vnet_name"]
57
58 # public ssh key
59 self.pub_key = config.get('pub_key')
60
61 def _reload_connection(self):
62 '''Sets connections to work with Azure service APIs
63 '''
64 self.logger.debug('Reloading API Connection')
65 try:
66 self.conn = ResourceManagementClient(self.credentials, self.subscription_id)
67 self.conn_compute = ComputeManagementClient(self.credentials, self.subscription_id)
68 self.conn_vnet = NetworkManagementClient(self.credentials, self.subscription_id)
69 except Exception as e:
70 self.format_vimconn_exception(e)
71
72 def _get_resource_name_from_resource_id(self, resource_id):
73 return str(resource_id.split('/')[-1])
74
75 def _get_location_from_resource_group(self, resource_group_name):
76 return self.conn.resource_groups.get(resource_group_name).location
77
78 def _get_resource_group_name_from_resource_id(self, resource_id):
79 return str(resource_id.split('/')[4])
80
81 def _check_subnets_for_vm(self, net_list):
82 # All subnets must belong to the same resource group and vnet
83 if len(set(self._get_resource_group_name_from_resource_id(net['id']) +
84 self._get_resource_name_from_resource_id(net['id']) for net in net_list)) != 1:
85 raise self.format_vimconn_exception('Azure VMs can only attach to subnets in same VNET')
86
87 def format_vimconn_exception(self, e):
88 '''Params: an Exception object
89 Returns: Raises the exception 'e' passed in mehtod parameters
90 '''
91 self.conn = None
92 self.conn_vnet = None
93 raise vimconn.vimconnConnectionException(type(e).__name__ + ': ' + str(e))
94
95 def _check_or_create_resource_group(self):
96 '''Creates a resource group in indicated region
97 '''
98 self.logger.debug('Creating RG {} in location {}'.format(self.resource_group, self.region))
99 self.conn.resource_groups.create_or_update(self.resource_group, {'location':self.region})
100
101 def new_network(self, net_name, net_type, ip_profile=None, shared=False, vlan=None):
102 '''Adds a tenant network to VIM
103 Params:
104 'net_name': name of the network
105 'ip_profile': is a dict containing the IP parameters of the network (Currently only IPv4 is implemented)
106 'ip-version': can be one of ['IPv4','IPv6']
107 'subnet-address': ip_prefix_schema, that is X.X.X.X/Y
108 'gateway-address': (Optional) ip_schema, that is X.X.X.X
109 'dns-address': (Optional) ip_schema,
110 'dhcp': (Optional) dict containing
111 'enabled': {'type': 'boolean'},
112 'start-address': ip_schema, first IP to grant
113 'count': number of IPs to grant.
114 Returns a tuple with the network identifier and created_items, or raises an exception on error
115 created_items can be None or a dictionary where this method can include key-values that will be passed to
116 the method delete_network. Can be used to store created segments, created l2gw connections, etc.
117 Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same
118 as not present.
119 '''
120 return self._new_subnet(net_name, ip_profile)
121
122 def _new_subnet(self, net_name, ip_profile):
123 '''Adds a tenant network to VIM
124 It creates a new VNET with a single subnet
125 '''
126 self.logger.debug('Adding a subnet to VNET '+self.vnet_name)
127 self._reload_connection()
128 self._check_or_create_resource_group()
129
130 if ip_profile is None:
131 # TODO get a non used vnet ip range /24 and allocate automatically
132 raise vimconn.vimconnException('Azure cannot create VNET with no CIDR')
133
134 try:
135 vnet_params= {
136 'location': self.region,
137 'address_space': {
138 'address_prefixes': [ip_profile['subnet_address']]
139 },
140 'subnets': [
141 {
142 'name': "{}-{}".format(net_name[:24], uuid4()),
143 'address_prefix': ip_profile['subnet_address']
144 }
145 ]
146 }
147 self.conn_vnet.virtual_networks.create_or_update(self.resource_group, self.vnet_name, vnet_params)
148 # TODO return a tuple (subnet-ID, None)
149 except Exception as e:
150 self.format_vimconn_exception(e)
151
152 def _create_nic(self, subnet_id, nic_name, static_ip=None):
153 self._reload_connection()
154
155 resource_group_name=self._get_resource_group_name_from_resource_id(subnet_id)
156 location = self._get_location_from_resource_group(resource_group_name)
157
158 if static_ip:
159 async_nic_creation = self.conn_vnet.network_interfaces.create_or_update(
160 resource_group_name,
161 nic_name,
162 {
163 'location': location,
164 'ip_configurations': [{
165 'name': nic_name + 'ipconfiguration',
166 'privateIPAddress': static_ip,
167 'privateIPAllocationMethod': 'Static',
168 'subnet': {
169 'id': subnet_id
170 }
171 }]
172 }
173 )
174 else:
175 async_nic_creation = self.conn_vnet.network_interfaces.create_or_update(
176 resource_group_name,
177 nic_name,
178 {
179 'location': location,
180 'ip_configurations': [{
181 'name': nic_name + 'ipconfiguration',
182 'subnet': {
183 'id': subnet_id
184 }
185 }]
186 }
187 )
188
189 return async_nic_creation.result()
190
191 def get_network_list(self, filter_dict={}):
192 '''Obtain tenant networks of VIM
193 Filter_dict can be:
194 name: network name
195 id: network uuid
196 shared: boolean
197 tenant_id: tenant
198 admin_state_up: boolean
199 status: 'ACTIVE'
200 Returns the network list of dictionaries
201 '''
202 self.logger.debug('Getting all subnets from VIM')
203 try:
204 self._reload_connection()
205 vnet = self.conn_vnet.virtual_networks.get(self.config["resource_group"], self.vnet_name)
206 subnet_list = []
207
208 for subnet in vnet.subnets:
209 # TODO implement filter_dict
210 if filter_dict:
211 if filter_dict.get("id") and str(subnet.id) != filter_dict["id"]:
212 continue
213 if filter_dict.get("name") and \
214 self._get_resource_name_from_resource_id(subnet.id) != filter_dict["name"]:
215 continue
216
217 subnet_list.append({
218 'id': str(subnet.id),
219 'name': self._get_resource_name_from_resource_id(subnet.id),
220 'status': str(vnet.provisioning_state), # TODO Does subnet contains status???
221 'cidr_block': str(subnet.address_prefix)
222 }
223 )
224 return subnet_list
225 except Exception as e:
226 self.format_vimconn_exception(e)
227
228 def new_vminstance(self, vm_name, description, start, image_id, flavor_id, net_list, cloud_config=None,
229 disk_list=None, availability_zone_index=None, availability_zone_list=None):
230
231 return self._new_vminstance(vm_name, image_id, flavor_id, net_list)
232
233 def _new_vminstance(self, vm_name, image_id, flavor_id, net_list, cloud_config=None, disk_list=None,
234 availability_zone_index=None, availability_zone_list=None):
235 #Create NICs
236 self._check_subnets_for_vm(net_list)
237 vm_nics = []
238 for idx, net in enumerate(net_list):
239 subnet_id=net['subnet_id']
240 nic_name = vm_name + '-nic-'+str(idx)
241 vm_nic = self._create_nic(subnet_id, nic_name)
242 vm_nics.append({ 'id': str(vm_nic.id)})
243
244 try:
245 vm_parameters = {
246 'location': self.region,
247 'os_profile': {
248 'computer_name': vm_name, # TODO if vm_name cannot be repeated add uuid4() suffix
249 'admin_username': 'sergio', # TODO is it mandatory???
250 'linuxConfiguration': {
251 'disablePasswordAuthentication': 'true',
252 'ssh': {
253 'publicKeys': [
254 {
255 'path': '/home/sergio/.ssh/authorized_keys',
256 'keyData': self.pub_key
257 }
258 ]
259 }
260 }
261
262 },
263 'hardware_profile': {
264 'vm_size':flavor_id
265 },
266 'storage_profile': {
267 'image_reference': image_id
268 },
269 'network_profile': {
270 'network_interfaces': [
271 vm_nics[0]
272 ]
273 }
274 }
275 creation_result = self.conn_compute.virtual_machines.create_or_update(
276 self.resource_group,
277 vm_name,
278 vm_parameters
279 )
280
281 run_command_parameters = {
282 'command_id': 'RunShellScript', # For linux, don't change it
283 'script': [
284 'date > /home/sergio/test.txt'
285 ]
286 }
287 poller = self.conn_compute.virtual_machines.run_command(
288 self.resource_group,
289 vm_name,
290 run_command_parameters
291 )
292 # TODO return a tuple (vm-ID, None)
293 except Exception as e:
294 self.format_vimconn_exception(e)
295
296 def get_flavor_id_from_data(self, flavor_dict):
297 self.logger.debug("Getting flavor id from data")
298 self._reload_connection()
299 vm_sizes_list = [vm_size.serialize() for vm_size in self.conn_compute.virtual_machine_sizes.list(self.region)]
300
301 cpus = flavor_dict['vcpus']
302 memMB = flavor_dict['ram']
303
304 filteredSizes = [size for size in vm_sizes_list if size['numberOfCores'] > cpus and size['memoryInMB'] > memMB]
305 listedFilteredSizes = sorted(filteredSizes, key=lambda k: k['numberOfCores'])
306
307 return listedFilteredSizes[0]['name']
308
309 def check_vim_connectivity(self):
310 try:
311 self._reload_connection()
312 return True
313 except Exception as e:
314 raise vimconn.vimconnException("Connectivity issue with Azure API: {}".format(e))
315
316 def get_network(self, net_id):
317 resGroup = self._get_resource_group_name_from_resource_id(net_id)
318 resName = self._get_resource_name_from_resource_id(net_id)
319
320 self._reload_connection()
321 vnet = self.conn_vnet.virtual_networks.get(resGroup, resName)
322
323 return vnet
324
325 def delete_network(self, net_id):
326 resGroup = self._get_resource_group_name_from_resource_id(net_id)
327 resName = self._get_resource_name_from_resource_id(net_id)
328
329 self._reload_connection()
330 self.conn_vnet.virtual_networks.delete(resGroup, resName)
331
332 def delete_vminstance(self, vm_id):
333 resGroup = self._get_resource_group_name_from_resource_id(net_id)
334 resName = self._get_resource_name_from_resource_id(net_id)
335
336 self._reload_connection()
337 self.conn_compute.virtual_machines.delete(resGroup, resName)
338
339 def get_vminstance(self, vm_id):
340 resGroup = self._get_resource_group_name_from_resource_id(net_id)
341 resName = self._get_resource_name_from_resource_id(net_id)
342
343 self._reload_connection()
344 vm=self.conn_compute.virtual_machines.get(resGroup, resName)
345
346 return vm
347
348 def get_flavor(self, flavor_id):
349 self._reload_connection()
350 for vm_size in self.conn_compute.virtual_machine_sizes.list(self.region):
351 if vm_size.name == flavor_id :
352 return vm_size
353
354
355 # TODO refresh_nets_status ver estado activo
356 # TODO PRIORITARY get_image_list
357 # TODO refresh_vms_status ver estado activo
358 # TODO get_vminstance_console for getting console
359
360 if __name__ == "__main__":
361
362 # Making some basic test
363 vim_id='azure'
364 vim_name='azure'
365 needed_test_params = {
366 "client_id": "AZURE_CLIENT_ID", # TODO delete private information
367 "secret": "AZURE_SECRET", # TODO delete private information
368 "tenant": "AZURE_TENANT", # TODO delete private information
369 "resource_group": "AZURE_RESOURCE_GROUP", # TODO delete private information # 'testOSMlive2',
370 # TODO maybe it should be created a resouce_group per VNFD
371 "subscription_id": "AZURE_SUBSCRIPTION_ID", # TODO delete private information 'ca3d18ab-d373-4afb-a5d6-7c44f098d16a'
372 "vnet_name": "AZURE_VNET_NAME",
373 }
374 test_params = {}
375
376 for param, env_var in needed_test_params.items():
377 value = getenv(env_var)
378 if not value:
379 raise Exception("Provide a valid value for env '{}'".format(env_var))
380 test_params[param] = value
381
382 config = {
383 'region_name': getenv("AZURE_REGION_NAME", 'westeurope'),
384 'resource_group': getenv("AZURE_RESOURCE_GROUP"),
385 'subscription_id': getenv("AZURE_SUBSCRIPTION_ID"),
386 'pub_key': getenv("AZURE_PUB_KEY", None),
387 'vnet_name': getenv("AZURE_VNET_NAME", 'myNetwork'),
388 }
389
390 virtualMachine = {
391 'name': 'sergio',
392 'description': 'new VM',
393 'status': 'running',
394 'image': {
395 'publisher': 'Canonical',
396 'offer': 'UbuntuServer',
397 'sku': '16.04.0-LTS',
398 'version': 'latest'
399 },
400 'hardware_profile': {
401 'vm_size': 'Standard_DS1_v2'
402 },
403 'networks': [
404 'sergio'
405 ]
406 }
407
408 vnet_config = {
409 'subnet_address': '10.1.2.0/24',
410 #'subnet_name': 'subnet-oam'
411 }
412 ###########################
413
414 azure = vimconnector(vim_id, vim_name, tenant_id=test_params["tenant"], tenant_name=None, url=None, url_admin=None,
415 user=test_params["client_id"], passwd=test_params["secret"], log_level=None, config=config)
416
417 #azure.get_flavor_id_from_data("here")
418 #subnets=azure.get_network_list()
419 #azure.new_vminstance(virtualMachine['name'], virtualMachine['description'], virtualMachine['status'],
420 # virtualMachine['image'], virtualMachine['hardware_profile']['vm_size'], subnets)
421 #
422 azure.get_flavor("Standard_A11")