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