1919import string
2020import base64
2121import re
22+ import requests
2223try :
2324 from urlparse import urlparse
2425except ImportError :
@@ -70,10 +71,12 @@ class AzureInstanceTypeInfo:
7071 - gpu(int, optional): the number of gpus of this instance
7172 - gpu_model(str, optional): the model of the gpus of this instance
7273 - gpu_vendor(str, optional): the model of the gpus of this instance
74+ - sgx(bool, optional): True if the instance has SGX support
75+ - family(str, optional): family of the instance
7376 """
7477
7578 def __init__ (self , name = "" , cpu = 1 , mem = 0 , os_disk_space = 0 , res_disk_space = 0 ,
76- gpu = 0 , gpu_model = None , gpu_vendor = None , sgx = False ):
79+ gpu = 0 , gpu_model = None , gpu_vendor = None , sgx = False , family = None ):
7780 self .name = name
7881 self .cpu = cpu
7982 self .mem = mem
@@ -83,6 +86,8 @@ def __init__(self, name="", cpu=1, mem=0, os_disk_space=0, res_disk_space=0,
8386 self .gpu_model = gpu_model
8487 self .gpu_vendor = gpu_vendor
8588 self .sgx = sgx
89+ self .family = family
90+ self .price = None
8691
8792 def set_sgx (self ):
8893 """Guess SGX from instance name"""
@@ -126,7 +131,7 @@ def set_gpu_models(self):
126131 self .gpu_model = "Tesla M60"
127132
128133 @staticmethod
129- def fromSKU (sku ):
134+ def fromSKU (sku , prices = None ):
130135 """Get an instance type object from SKU Json data"""
131136 gpu = os_disk_space = res_disk_space = mem = cpu = 0
132137 for elem in sku .capabilities :
@@ -140,11 +145,41 @@ def fromSKU(sku):
140145 os_disk_space = int (elem .value )
141146 elif elem .name == "GPUs" :
142147 gpu = int (elem .value )
143- instance_type = AzureInstanceTypeInfo (sku .name , cpu , mem , os_disk_space , res_disk_space , gpu )
148+ instance_type = AzureInstanceTypeInfo (sku .name , cpu , mem , os_disk_space , res_disk_space , gpu , family = sku . family )
144149 instance_type .set_gpu_models ()
145150 instance_type .set_sgx ()
151+ if prices and sku .name in prices :
152+ instance_type .price = prices [sku .name ]
146153 return instance_type
147154
155+ @staticmethod
156+ def get_price_list (region = 'westeurope' ):
157+ """Get the price list from Azure"""
158+ url = "https://prices.azure.com/api/retail/prices"
159+ url += "?$filter=serviceName eq 'Virtual Machines'"
160+ url += " and armRegionName eq '%s'" % region
161+ vm_prices = []
162+
163+ try :
164+ # Iterate over paginated results
165+ while url :
166+ response = requests .get (url , timeout = 5 )
167+ data = response .json ()
168+ items = data .get ("Items" , [])
169+ vm_prices .extend (items )
170+ url = data .get ("NextPageLink" )
171+ except Exception :
172+ pass
173+
174+ # Process and print the results
175+ res = {}
176+ for item in vm_prices :
177+ sku = item .get ("armSkuName" )
178+ price = item .get ("unitPrice" )
179+ if sku and region and price :
180+ res [sku ] = price
181+ return res
182+
148183
149184class AzureCloudConnector (CloudConnector ):
150185 """
@@ -156,12 +191,14 @@ class AzureCloudConnector(CloudConnector):
156191
157192 type = "Azure"
158193 """str with the name of the provider."""
159- INSTANCE_TYPE = 'ExtraSmall '
194+ INSTANCE_TYPE = 'Standard_A0 '
160195 """Default instance type."""
161196 DEFAULT_LOCATION = "westeurope"
162197 """Default location to use"""
163198 DEFAULT_USER = 'azureuser'
164199 """ default user to SSH access the VM """
200+ instance_type_list = {}
201+ """ Information about the instance types """
165202
166203 PROVISION_STATE_MAP = {
167204 'Accepted' : VirtualMachine .PENDING ,
@@ -244,10 +281,16 @@ def get_instance_type_by_name(self, instance_name, location, credentials, subscr
244281 def get_instance_type_list (self , credentials , subscription_id , location ):
245282 compute_client = ComputeManagementClient (credentials , subscription_id )
246283
284+ if AzureCloudConnector .instance_type_list and location in AzureCloudConnector .instance_type_list :
285+ return AzureCloudConnector .instance_type_list [location ]
286+
247287 try :
248288 skus = list (compute_client .resource_skus .list (filter = "location eq '%s'" % location ))
249- inst_types = [AzureInstanceTypeInfo .fromSKU (sku ) for sku in skus if sku .resource_type == "virtualMachines" ]
250- inst_types .sort (key = lambda x : (x .cpu , x .mem , x .gpu , x .res_disk_space ))
289+ prices = AzureInstanceTypeInfo .get_price_list (location )
290+ inst_types = [AzureInstanceTypeInfo .fromSKU (sku , prices )
291+ for sku in skus if sku .resource_type == "virtualMachines" ]
292+ inst_types .sort (key = lambda x : (x .price if x .price else 9999999 , x .cpu , x .mem , x .gpu , x .res_disk_space ))
293+ AzureCloudConnector .instance_type_list [location ] = inst_types
251294 return inst_types
252295 except Exception :
253296 self .log_exception ("Error getting instance type list." )
@@ -317,6 +360,9 @@ def update_system_info_from_instance(system, instance_type):
317360 conflict = "other" , missing = "other" )
318361 system .addFeature (Feature ("instance_type" , "=" , instance_type .name ),
319362 conflict = "other" , missing = "other" )
363+ if instance_type .price :
364+ system .addFeature (Feature ("price" , "=" , instance_type .price ),
365+ conflict = "me" , missing = "other" )
320366 if instance_type .gpu :
321367 system .addFeature (Feature ("gpu.count" , "=" , instance_type .gpu ),
322368 conflict = "other" , missing = "other" )
@@ -1131,6 +1177,8 @@ def alterVM(self, vm, radl, auth_data):
11311177 return (True , "" )
11321178
11331179 instance_type = self .get_instance_type (new_system , credentials , subscription_id )
1180+ if not instance_type :
1181+ raise Exception ("Instance type not found for the new flavor." )
11341182 vm_parameters = " { 'hardware_profile': { 'vm_size': %s } } " % instance_type .name
11351183
11361184 async_vm_update = compute_client .virtual_machines .begin_create_or_update (group_name ,
@@ -1285,7 +1333,6 @@ def list_images(self, auth_data, filters=None):
12851333
12861334 def get_quotas (self , auth_data , region = None ):
12871335 credentials , subscription_id = self .get_credentials (auth_data )
1288- compute_client = ComputeManagementClient (credentials , subscription_id )
12891336 location = self .DEFAULT_LOCATION
12901337 try :
12911338 # Get the region from the auth data
@@ -1294,8 +1341,10 @@ def get_quotas(self, auth_data, region=None):
12941341 pass
12951342 if region :
12961343 location = region
1344+ return self ._get_quotas (credentials , subscription_id , location )
12971345
1298- # Initialize default values
1346+ def _get_quotas (self , credentials , subscription_id , location ):
1347+ compute_client = ComputeManagementClient (credentials , subscription_id )
12991348 quotas = {}
13001349
13011350 try :
@@ -1315,7 +1364,7 @@ def get_quotas(self, auth_data, region=None):
13151364 quotas ["volumes" ]["used" ] = usage .current_value
13161365 quotas ["volumes" ]["limit" ] = usage .limit
13171366 elif "family vcpus" in name :
1318- fam = usage .name .localized_value [: - 13 ]. strip (). replace ( " " , "_" )
1367+ fam = usage .name .value
13191368 quotas [fam ] = {"cores" : {}}
13201369 quotas [fam ]["cores" ]["used" ] = usage .current_value
13211370 quotas [fam ]["cores" ]["limit" ] = usage .limit
0 commit comments