1 # -*- coding: utf-8 -*-
3 __author__
='Sergio Gonzalez'
4 __date__
='$18-apr-2019 23:59:59$'
10 from uuid
import uuid4
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
17 class vimconnector(vimconn
.vimconnector
):
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
={}):
22 vimconn
.vimconnector
.__init
__(self
, uuid
, name
, tenant_id
, tenant_name
, url
, url_admin
, user
, passwd
, log_level
,
23 config
, persistent_info
)
26 self
.logger
= logging
.getLogger('openmano.vim.azure')
29 self
.logger
.setLevel(getattr(logging
, log_level
))
32 self
.credentials
= ServicePrincipalCredentials(
35 tenant
=(tenant_id
or tenant_name
)
39 if 'subscription_id' in config
:
40 self
.subscription_id
= config
.get('subscription_id')
41 self
.logger
.debug('Setting subscription '+str(self
.subscription_id
))
43 raise vimconn
.vimconnException('Subscription not specified')
45 if 'region_name' in config
:
46 self
.region
= config
.get('region_name')
48 raise vimconn
.vimconnException('Azure region_name is not specified at config')
50 if 'resource_group' in config
:
51 self
.resource_group
= config
.get('resource_group')
53 raise vimconn
.vimconnException('Azure resource_group is not specified at config')
55 if 'vnet_name' in config
:
56 self
.vnet_name
= config
["vnet_name"]
59 self
.pub_key
= config
.get('pub_key')
61 def _reload_connection(self
):
62 '''Sets connections to work with Azure service APIs
64 self
.logger
.debug('Reloading API Connection')
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
)
72 def _get_resource_name_from_resource_id(self
, resource_id
):
73 return str(resource_id
.split('/')[-1])
75 def _get_location_from_resource_group(self
, resource_group_name
):
76 return self
.conn
.resource_groups
.get(resource_group_name
).location
78 def _get_resource_group_name_from_resource_id(self
, resource_id
):
79 return str(resource_id
.split('/')[4])
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')
87 def format_vimconn_exception(self
, e
):
88 '''Params: an Exception object
89 Returns: Raises the exception 'e' passed in mehtod parameters
93 raise vimconn
.vimconnConnectionException(type(e
).__name
__ + ': ' + str(e
))
95 def _check_or_create_resource_group(self
):
96 '''Creates a resource group in indicated region
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
})
101 def new_network(self
, net_name
, net_type
, ip_profile
=None, shared
=False, vlan
=None):
102 '''Adds a tenant network to VIM
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
120 return self
._new
_subnet
(net_name
, ip_profile
)
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
126 self
.logger
.debug('Adding a subnet to VNET '+self
.vnet_name
)
127 self
._reload
_connection
()
128 self
._check
_or
_create
_resource
_group
()
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')
136 'location': self
.region
,
138 'address_prefixes': [ip_profile
['subnet_address']]
142 'name': "{}-{}".format(net_name
[:24], uuid4()),
143 'address_prefix': ip_profile
['subnet_address']
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
)
152 def _create_nic(self
, subnet_id
, nic_name
, static_ip
=None):
153 self
._reload
_connection
()
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
)
159 async_nic_creation
= self
.conn_vnet
.network_interfaces
.create_or_update(
163 'location': location
,
164 'ip_configurations': [{
165 'name': nic_name
+ 'ipconfiguration',
166 'privateIPAddress': static_ip
,
167 'privateIPAllocationMethod': 'Static',
175 async_nic_creation
= self
.conn_vnet
.network_interfaces
.create_or_update(
179 'location': location
,
180 'ip_configurations': [{
181 'name': nic_name
+ 'ipconfiguration',
189 return async_nic_creation
.result()
191 def get_network_list(self
, filter_dict
={}):
192 '''Obtain tenant networks of VIM
198 admin_state_up: boolean
200 Returns the network list of dictionaries
202 self
.logger
.debug('Getting all subnets from VIM')
204 self
._reload
_connection
()
205 vnet
= self
.conn_vnet
.virtual_networks
.get(self
.config
["resource_group"], self
.vnet_name
)
208 for subnet
in vnet
.subnets
:
209 # TODO implement filter_dict
211 if filter_dict
.get("id") and str(subnet
.id) != filter_dict
["id"]:
213 if filter_dict
.get("name") and \
214 self
._get
_resource
_name
_from
_resource
_id
(subnet
.id) != filter_dict
["name"]:
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
)
225 except Exception as e
:
226 self
.format_vimconn_exception(e
)
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):
231 return self
._new
_vminstance
(vm_name
, image_id
, flavor_id
, net_list
)
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):
236 self
._check
_subnets
_for
_vm
(net_list
)
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)})
246 'location': self
.region
,
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',
255 'path': '/home/sergio/.ssh/authorized_keys',
256 'keyData': self
.pub_key
263 'hardware_profile': {
267 'image_reference': image_id
270 'network_interfaces': [
275 creation_result
= self
.conn_compute
.virtual_machines
.create_or_update(
281 run_command_parameters
= {
282 'command_id': 'RunShellScript', # For linux, don't change it
284 'date > /home/sergio/test.txt'
287 poller
= self
.conn_compute
.virtual_machines
.run_command(
290 run_command_parameters
292 # TODO return a tuple (vm-ID, None)
293 except Exception as e
:
294 self
.format_vimconn_exception(e
)
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
)]
301 cpus
= flavor_dict
['vcpus']
302 memMB
= flavor_dict
['ram']
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'])
307 return listedFilteredSizes
[0]['name']
309 def check_vim_connectivity(self
):
311 self
._reload
_connection
()
313 except Exception as e
:
314 raise vimconn
.vimconnException("Connectivity issue with Azure API: {}".format(e
))
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
)
320 self
._reload
_connection
()
321 vnet
= self
.conn_vnet
.virtual_networks
.get(resGroup
, resName
)
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
)
329 self
._reload
_connection
()
330 self
.conn_vnet
.virtual_networks
.delete(resGroup
, resName
)
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
)
336 self
._reload
_connection
()
337 self
.conn_compute
.virtual_machines
.delete(resGroup
, resName
)
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
)
343 self
._reload
_connection
()
344 vm
=self
.conn_compute
.virtual_machines
.get(resGroup
, resName
)
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
:
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
360 if __name__
== "__main__":
362 # Making some basic test
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",
376 for param
, env_var
in needed_test_params
.items():
377 value
= getenv(env_var
)
379 raise Exception("Provide a valid value for env '{}'".format(env_var
))
380 test_params
[param
] = value
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'),
392 'description': 'new VM',
395 'publisher': 'Canonical',
396 'offer': 'UbuntuServer',
397 'sku': '16.04.0-LTS',
400 'hardware_profile': {
401 'vm_size': 'Standard_DS1_v2'
409 'subnet_address': '10.1.2.0/24',
410 #'subnet_name': 'subnet-oam'
412 ###########################
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
)
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)
422 azure
.get_flavor("Standard_A11")