Skip to content

Commit 38fb230

Browse files
authored
Merge pull request #1734 from grycap/devel
Devel
2 parents dc259f8 + 9f2a006 commit 38fb230

File tree

7 files changed

+136
-19
lines changed

7 files changed

+136
-19
lines changed

IM/connectors/Azure.py

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -558,11 +558,15 @@ def get_azure_vm_create_json(self, group_name, vm_name, nics, radl,
558558
# azr://Canonical/UbuntuServer/16.04.0-LTS/latest
559559
# azr://MicrosoftWindowsServerEssentials/WindowsServerEssentials/WindowsServerEssentials/latest
560560
image_values = (url[1] + url[2]).split("/")
561-
if len(image_values) not in [3, 4]:
561+
if len(image_values) not in [3, 4, 5]:
562562
raise Exception("The Azure image has to have the format: azr://publisher/offer/sku/version"
563+
" or azr://region/publisher/offer/sku/version"
563564
" or azr://[snapshots|disk]/rgname/diskname")
564565

565566
location = self.DEFAULT_LOCATION
567+
if len(image_values) == 5:
568+
location = image_values[0]
569+
image_values = image_values[1:]
566570
if system.getValue('availability_zone'):
567571
location = system.getValue('availability_zone')
568572

@@ -837,12 +841,6 @@ def create_vms(self, rg_name, inf, radl, requested_radl, num_vm, location,
837841
return vms
838842

839843
def launch(self, inf, radl, requested_radl, num_vm, auth_data):
840-
location = self.DEFAULT_LOCATION
841-
if radl.systems[0].getValue('availability_zone'):
842-
location = radl.systems[0].getValue('availability_zone')
843-
else:
844-
radl.systems[0].setValue('availability_zone', location)
845-
846844
credentials, subscription_id = self.get_credentials(auth_data)
847845
compute_client = ComputeManagementClient(credentials, subscription_id)
848846

@@ -852,12 +850,22 @@ def launch(self, inf, radl, requested_radl, num_vm, auth_data):
852850
# azr://Canonical/UbuntuServer/16.04.0-LTS/latest
853851
# azr://MicrosoftWindowsServerEssentials/WindowsServerEssentials/WindowsServerEssentials/latest
854852
image_values = (url[1] + url[2]).split("/")
855-
if len(image_values) not in [3, 4]:
853+
if len(image_values) not in [3, 4, 5]:
856854
raise Exception("The Azure image has to have the format: azr://publisher/offer/sku/version"
855+
" or azr://region/publisher/offer/sku/version"
857856
" or azr://[snapshots|disk|image]/rgname/diskname")
858857
if len(image_values) == 3 and image_values[0] not in ["snapshot", "disk"]:
859858
raise Exception("Incorrect image url: it must be snapshot or disk.")
860859

860+
location = self.DEFAULT_LOCATION
861+
if len(image_values) == 5:
862+
location = image_values[0]
863+
image_values = image_values[1:]
864+
if radl.systems[0].getValue('availability_zone'):
865+
location = radl.systems[0].getValue('availability_zone')
866+
else:
867+
radl.systems[0].setValue('availability_zone', location)
868+
861869
if len(image_values) == 4:
862870
offers = compute_client.virtual_machine_images.list(location,
863871
image_values[0],
@@ -1274,3 +1282,44 @@ def list_images(self, auth_data, filters=None):
12741282
res.append({"uri": "azr://%s/%s/%s/latest" % (pub, offer, sku),
12751283
"name": name})
12761284
return self._filter_images(res, filters)
1285+
1286+
def get_quotas(self, auth_data, region=None):
1287+
credentials, subscription_id = self.get_credentials(auth_data)
1288+
compute_client = ComputeManagementClient(credentials, subscription_id)
1289+
location = self.DEFAULT_LOCATION
1290+
try:
1291+
# Get the region from the auth data
1292+
location = auth_data.getAuthInfo(self.type)[0].get('region', location)
1293+
except Exception:
1294+
pass
1295+
if region:
1296+
location = region
1297+
1298+
# Initialize default values
1299+
quotas = {}
1300+
1301+
try:
1302+
usage_list = compute_client.usage.list(location)
1303+
for usage in usage_list:
1304+
name = usage.name.localized_value.lower()
1305+
if name == "total regional vcpus":
1306+
quotas["cores"] = {}
1307+
quotas["cores"]["used"] = usage.current_value
1308+
quotas["cores"]["limit"] = usage.limit
1309+
elif name == "virtual machines":
1310+
quotas["instances"] = {}
1311+
quotas["instances"]["used"] = usage.current_value
1312+
quotas["instances"]["limit"] = usage.limit
1313+
elif "storage" in name and "disks" in name:
1314+
quotas["volumes"] = {}
1315+
quotas["volumes"]["used"] = usage.current_value
1316+
quotas["volumes"]["limit"] = usage.limit
1317+
elif "family vcpus" in name:
1318+
fam = usage.name.localized_value[:-13].strip().replace(" ", "_")
1319+
quotas[fam] = {"cores": {}}
1320+
quotas[fam]["cores"]["used"] = usage.current_value
1321+
quotas[fam]["cores"]["limit"] = usage.limit
1322+
return quotas
1323+
except Exception:
1324+
self.log_exception("Error retrieving Azure quotas")
1325+
return {}

IM/connectors/CloudConnector.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,12 +361,13 @@ def _filter_images(self, image_list, filters=None):
361361
res.append(image)
362362
return res
363363

364-
def get_quotas(self, auth_data):
364+
def get_quotas(self, auth_data, region=None):
365365
"""
366366
Get the number of used and available resources in the cloud provider
367367
368368
Arguments:
369369
- auth_data(:py:class:`dict` of str objects): Authentication data to access cloud provider.
370+
- region(str): Region to get the quotas. If None, the default region is used.
370371
371372
Returns: dict with the following structure (if there are no limit in some metric, value is set to 1):
372373
{

IM/connectors/Dummy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def list_images(self, auth_data, filters=None):
101101
return [{"uri": "mock0://linux.for.ev.er/image1", "name": "Image Name1"},
102102
{"uri": "mock0://linux.for.ev.er/image2", "name": "Image Name2"}]
103103

104-
def get_quotas(self, auth_data):
104+
def get_quotas(self, auth_data, region=None):
105105
return {"cores": {"used": 1, "limit": 10},
106106
"ram": {"used": 1, "limit": 10},
107107
"instances": {"used": 1, "limit": 10},

IM/connectors/OpenNebula.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1308,7 +1308,7 @@ def _get_public_ip_quota(net_quotas, net_info):
13081308

13091309
return res
13101310

1311-
def get_quotas(self, auth_data):
1311+
def get_quotas(self, auth_data, region=None):
13121312
server = ServerProxy(self.server_url, allow_none=True)
13131313
session_id = self.getSessionID(auth_data)
13141314

IM/connectors/OpenStack.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2187,9 +2187,12 @@ def _get_tenant_id(auth):
21872187

21882188
return None
21892189

2190-
def get_quotas(self, auth_data):
2190+
def get_quotas(self, auth_data, region=None):
21912191
driver = self.get_driver(auth_data)
21922192
tenant_id = self._get_tenant_id(auth_data.getAuthInfo(self.type, self.cloud.server)[0])
2193+
if region:
2194+
# In this case the region parameter refers to the tenant_id
2195+
tenant_id = region
21932196
quotas = driver.ex_get_quota_set(tenant_id)
21942197
try:
21952198
net_quotas = driver.ex_get_network_quotas(tenant_id)

doc/source/tosca.rst

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -286,14 +286,14 @@ and, optionally, an availability zone.
286286
287287
policies:
288288
- deploy_group_on_cloudid:
289-
type: tosca.policies.indigo.Placement
290-
properties: { cloud_id: cloudid1 }
291-
targets: [ my_placement_group ]
289+
type: tosca.policies.indigo.Placement
290+
properties: { cloud_id: cloudid1 }
291+
targets: [ my_placement_group ]
292292
293293
- deploy_on_cloudid:
294-
type: tosca.policies.indigo.Placement
295-
properties: { cloud_id: cloudid2, availability_zone: some_zone }
296-
targets: [ compute_three ]
294+
type: tosca.policies.indigo.Placement
295+
properties: { cloud_id: cloudid2, availability_zone: some_zone }
296+
targets: [ compute_three ]
297297
298298
...
299299
@@ -419,4 +419,30 @@ value of a internally defined value.
419419
420420
outputs:
421421
node_ip:
422-
value: { get_attribute: [ front, ansible_output, lrms_front_end_front_conf_front, tasks, 'grycap.nomad : nomad_secret_id', output ] }
422+
value:
423+
get_attribute:
424+
- front
425+
- ansible_output
426+
- lrms_front_end_front_conf_front
427+
- tasks
428+
- 'grycap.nomad : nomad_secret_id'
429+
- output
430+
431+
432+
Random Input values
433+
^^^^^^^^^^^^^^^^^^^^
434+
435+
The IM TOSCA parser supports the generation of random values for string inputs.
436+
The special string ``random(N)`` generates a random string of length N with
437+
alphanumeric characters. It can be used to define passwords or any other random
438+
string input value (from version 1.19.2).
439+
440+
.. code-block:: yaml
441+
442+
...
443+
444+
inputs:
445+
app_password:
446+
type: string
447+
description: Password for the App
448+
default: 'random(12)'

test/unit/connectors/Azure.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,44 @@ def test_invalid_rg_name_launch(self, save_data, credentials, compute_client, re
699699
res = azure_cloud.launch_with_retry(inf, radl, radl, 1, auth, 1, 0)
700700
self.assertEqual(res, [(False, 'Attempt 1: Error: Invalid rg_name. It must be unique per infrastructure.\n')])
701701

702+
@patch('IM.connectors.Azure.ComputeManagementClient')
703+
@patch('IM.connectors.Azure.ClientSecretCredential')
704+
def test_get_quotas(self, credentials, compute_client):
705+
auth = Authentication([{'id': 'azure', 'type': 'Azure', 'subscription_id': 'subscription_id',
706+
'client_id': 'client', 'secret': 'password', 'tenant': 'tenant'}])
707+
azure_cloud = self.get_azure_cloud()
708+
709+
cclient = MagicMock()
710+
compute_client.return_value = cclient
711+
name1 = MagicMock(localized_value='Total Regional vCPUs')
712+
name2 = MagicMock(localized_value='Standard DSv3 Family vCPUs')
713+
name3 = MagicMock(localized_value='Standard Dv3 Family vCPUs')
714+
name4 = MagicMock(localized_value='Standard Storage Managed Disks')
715+
name5 = MagicMock(localized_value='Virtual Machines')
716+
elem1 = MagicMock(limit=10, current_value=5)
717+
elem2 = MagicMock(limit=8, current_value=4)
718+
elem3 = MagicMock(limit=6, current_value=2)
719+
elem4 = MagicMock(limit=20, current_value=10)
720+
elem5 = MagicMock(limit=20, current_value=1)
721+
elem1.name = name1
722+
elem2.name = name2
723+
elem3.name = name3
724+
elem4.name = name4
725+
elem5.name = name5
726+
cclient.usage.list.return_value = [
727+
elem1, elem2, elem3, elem4, elem5
728+
]
729+
730+
expected_quotas = {
731+
'cores': {'used': 5, 'limit': 10},
732+
'Standard_DSv3': {'cores': {'used': 4, 'limit': 8}},
733+
'Standard_Dv3': {'cores': {'used': 2, 'limit': 6}},
734+
'volumes': {'used': 10, 'limit': 20},
735+
'instances': {'used': 1, 'limit': 20}
736+
}
737+
quotas = azure_cloud.get_quotas(auth, "northeurope")
738+
self.assertEqual(quotas, expected_quotas)
739+
702740

703741
if __name__ == '__main__':
704742
unittest.main()

0 commit comments

Comments
 (0)