diff --git a/libcloud/common/azure.py b/libcloud/common/azure.py index b7f9ffc549..218f97cb99 100644 --- a/libcloud/common/azure.py +++ b/libcloud/common/azure.py @@ -20,6 +20,7 @@ import hmac from hashlib import sha256 + from libcloud.utils.py3 import httplib from libcloud.utils.py3 import b from libcloud.utils.xml import fixxpath @@ -31,7 +32,8 @@ from libcloud.common.types import InvalidCredsError from libcloud.common.types import LibcloudError, MalformedResponseError -from libcloud.common.base import ConnectionUserAndKey, RawResponse +from libcloud.common.base import ConnectionUserAndKey, RawResponse, \ + Connection, JsonResponse from libcloud.common.base import CertificateConnection from libcloud.common.base import XmlResponse @@ -42,6 +44,258 @@ AZURE_TIME_FORMAT = '%a, %d %b %Y %H:%M:%S GMT' +""" +Sizes must be hardcoded because Microsoft doesn't provide an API to fetch them +From http://msdn.microsoft.com/en-us/library/windowsazure/dn197896.aspx + +Prices are for Linux instances in East US data center. To see what pricing will +actually be, visit: +http://azure.microsoft.com/en-gb/pricing/details/virtual-machines/ +""" +AZURE_COMPUTE_INSTANCE_TYPES = { + 'A0': { + 'id': 'ExtraSmall', + 'name': 'Extra Small Instance', + 'ram': 768, + 'disk': 127, + 'bandwidth': None, + 'price': '0.0211', + 'max_data_disks': 1, + 'cores': 'Shared' + }, + 'A1': { + 'id': 'Small', + 'name': 'Small Instance', + 'ram': 1792, + 'disk': 127, + 'bandwidth': None, + 'price': '0.0633', + 'max_data_disks': 2, + 'cores': 1 + }, + 'A2': { + 'id': 'Medium', + 'name': 'Medium Instance', + 'ram': 3584, + 'disk': 127, + 'bandwidth': None, + 'price': '0.1266', + 'max_data_disks': 4, + 'cores': 2 + }, + 'A3': { + 'id': 'Large', + 'name': 'Large Instance', + 'ram': 7168, + 'disk': 127, + 'bandwidth': None, + 'price': '0.2531', + 'max_data_disks': 8, + 'cores': 4 + }, + 'A4': { + 'id': 'ExtraLarge', + 'name': 'Extra Large Instance', + 'ram': 14336, + 'disk': 127, + 'bandwidth': None, + 'price': '0.5062', + 'max_data_disks': 16, + 'cores': 8 + }, + 'A5': { + 'id': 'A5', + 'name': 'Memory Intensive Instance', + 'ram': 14336, + 'disk': 127, + 'bandwidth': None, + 'price': '0.2637', + 'max_data_disks': 4, + 'cores': 2 + }, + 'A6': { + 'id': 'A6', + 'name': 'A6 Instance', + 'ram': 28672, + 'disk': 127, + 'bandwidth': None, + 'price': '0.5273', + 'max_data_disks': 8, + 'cores': 4 + }, + 'A7': { + 'id': 'A7', + 'name': 'A7 Instance', + 'ram': 57344, + 'disk': 127, + 'bandwidth': None, + 'price': '1.0545', + 'max_data_disks': 16, + 'cores': 8 + }, + 'A8': { + 'id': 'A8', + 'name': 'A8 Instance', + 'ram': 57344, + 'disk': 127, + 'bandwidth': None, + 'price': '2.0774', + 'max_data_disks': 16, + 'cores': 8 + }, + 'A9': { + 'id': 'A9', + 'name': 'A9 Instance', + 'ram': 114688, + 'disk': 127, + 'bandwidth': None, + 'price': '4.7137', + 'max_data_disks': 16, + 'cores': 16 + }, + 'A10': { + 'id': 'A10', + 'name': 'A10 Instance', + 'ram': 57344, + 'disk': 127, + 'bandwidth': None, + 'price': '1.2233', + 'max_data_disks': 16, + 'cores': 8 + }, + 'A11': { + 'id': 'A11', + 'name': 'A11 Instance', + 'ram': 114688, + 'disk': 127, + 'bandwidth': None, + 'price': '2.1934', + 'max_data_disks': 16, + 'cores': 16 + }, + 'D1': { + 'id': 'Standard_D1', + 'name': 'D1 Faster Compute Instance', + 'ram': 3584, + 'disk': 127, + 'bandwidth': None, + 'price': '0.0992', + 'max_data_disks': 2, + 'cores': 1 + }, + 'D2': { + 'id': 'Standard_D2', + 'name': 'D2 Faster Compute Instance', + 'ram': 7168, + 'disk': 127, + 'bandwidth': None, + 'price': '0.1983', + 'max_data_disks': 4, + 'cores': 2 + }, + 'D3': { + 'id': 'Standard_D3', + 'name': 'D3 Faster Compute Instance', + 'ram': 14336, + 'disk': 127, + 'bandwidth': None, + 'price': '0.3965', + 'max_data_disks': 8, + 'cores': 4 + }, + 'D4': { + 'id': 'Standard_D4', + 'name': 'D4 Faster Compute Instance', + 'ram': 28672, + 'disk': 127, + 'bandwidth': None, + 'price': '0.793', + 'max_data_disks': 16, + 'cores': 8 + }, + 'D11': { + 'id': 'Standard_D11', + 'name': 'D11 Faster Compute Instance', + 'ram': 14336, + 'disk': 127, + 'bandwidth': None, + 'price': '0.251', + 'max_data_disks': 4, + 'cores': 2 + }, + 'D12': { + 'id': 'Standard_D12', + 'name': 'D12 Faster Compute Instance', + 'ram': 28672, + 'disk': 127, + 'bandwidth': None, + 'price': '0.502', + 'max_data_disks': 8, + 'cores': 4 + }, + 'D13': { + 'id': 'Standard_D13', + 'name': 'D13 Faster Compute Instance', + 'ram': 57344, + 'disk': 127, + 'bandwidth': None, + 'price': '0.9038', + 'max_data_disks': 16, + 'cores': 8 + }, + 'D14': { + 'id': 'Standard_D14', + 'name': 'D14 Faster Compute Instance', + 'ram': 114688, + 'disk': 127, + 'bandwidth': None, + 'price': '1.6261', + 'max_data_disks': 32, + 'cores': 16 + }, + 'Standard_D2_v2': { + 'id': 'Standard_D2_v2', + 'name': 'D2 v2 Faster Compute Instance', + 'ram': 7168, + 'disk': 100, + 'bandwidth': None, + 'price': '0.16', + 'max_data_disks': 4, + 'cores': 2 + }, + 'Standard_D3_v2': { + 'id': 'Standard_D3_v2', + 'name': 'D3 v2 Faster Compute Instance', + 'ram': 14336, + 'disk': 200, + 'bandwidth': None, + 'price': '0.319', + 'max_data_disks': 8, + 'cores': 4 + }, + 'Standard_D4_v2': { + 'id': 'Standard_D4_v2', + 'name': 'D4 v2 Faster Compute Instance', + 'ram': 28672, + 'disk': 400, + 'bandwidth': None, + 'price': '0.672', + 'max_data_disks': 16, + 'cores': 8 + }, + 'Standard_D5_v2': { + 'id': 'Standard_D5_v2', + 'name': 'D5 v2 Faster Compute Instance', + 'ram': 57344, + 'disk': 800, + 'bandwidth': None, + 'price': '1.277', + 'max_data_disks': 32, + 'cores': 16 + } +} + + class AzureRedirectException(Exception): def __init__(self, response): @@ -293,3 +547,53 @@ def add_default_headers(self, headers): headers['x-ms-date'] = time.strftime(AZURE_TIME_FORMAT, time.gmtime()) # headers['host'] = self.host return headers + + +class AzureARMResponse(JsonResponse): + + valid_response_codes = [ + httplib.NOT_FOUND, + httplib.CONFLICT, + httplib.BAD_REQUEST, + httplib.TEMPORARY_REDIRECT + # added TEMPORARY_REDIRECT as this can sometimes be + # sent by azure instead of a success or fail response + ] + + def success(self): + i = int(self.status) + return 200 <= i <= 299 or i in self.valid_response_codes + + +class AzureResourceManagerConnection(Connection): + driver = AzureBaseDriver + responseCls = AzureARMResponse + rawResponseCls = AzureRawResponse + name = 'Azure Resource Manager API Connection' + host = 'management.azure.com' + token = '' + + def __init__(self, subscription_id, token, *args, **kwargs): + """ + :param subscription_id: Azure subscription ID. + :type subscription_id: ``str`` + + :param token: JSON web token to authenticate with + Azure Active Directory + :type token: ``str`` + """ + + super(AzureResourceManagerConnection, self).__init__( + *args, + **kwargs + ) + self.subscription_id = subscription_id + self.token = token + + def add_default_headers(self, headers): + """ + @inherits: :class:`Connection.add_default_headers` + """ + headers['Content-Type'] = 'application/json' + headers['Authorization'] = 'Bearer %s' % self.token + return headers diff --git a/libcloud/common/exceptions.py b/libcloud/common/exceptions.py index 14dcea80d2..e8ec4a9c09 100644 --- a/libcloud/common/exceptions.py +++ b/libcloud/common/exceptions.py @@ -71,5 +71,8 @@ def exception_from_message(code, message, headers=None): if headers and 'retry_after' in headers: kwargs['retry_after'] = headers['retry_after'] + if headers and 'retry-after' in headers: + kwargs['retry_after'] = headers['retry-after'] + cls = _code_map.get(code, BaseHTTPError) return cls(**kwargs) diff --git a/libcloud/compute/drivers/azure.py b/libcloud/compute/drivers/azure.py index 41e0166e8b..d2a4b79009 100644 --- a/libcloud/compute/drivers/azure.py +++ b/libcloud/compute/drivers/azure.py @@ -36,7 +36,8 @@ except ImportError: from xml.etree import ElementTree as ET -from libcloud.common.azure import AzureServiceManagementConnection +from libcloud.common.azure import AzureServiceManagementConnection, \ + AZURE_COMPUTE_INSTANCE_TYPES from libcloud.common.azure import AzureRedirectException from libcloud.compute.providers import Provider from libcloud.compute.base import Node, NodeDriver, NodeLocation, NodeSize @@ -72,257 +73,6 @@ def _str(value): r'Win|SQL|SharePoint|Visual|Dynamics|DynGP|BizTalk' ) -""" -Sizes must be hardcoded because Microsoft doesn't provide an API to fetch them -From http://msdn.microsoft.com/en-us/library/windowsazure/dn197896.aspx - -Prices are for Linux instances in East US data center. To see what pricing will -actually be, visit: -http://azure.microsoft.com/en-gb/pricing/details/virtual-machines/ -""" -AZURE_COMPUTE_INSTANCE_TYPES = { - 'A0': { - 'id': 'ExtraSmall', - 'name': 'Extra Small Instance', - 'ram': 768, - 'disk': 127, - 'bandwidth': None, - 'price': '0.0211', - 'max_data_disks': 1, - 'cores': 'Shared' - }, - 'A1': { - 'id': 'Small', - 'name': 'Small Instance', - 'ram': 1792, - 'disk': 127, - 'bandwidth': None, - 'price': '0.0633', - 'max_data_disks': 2, - 'cores': 1 - }, - 'A2': { - 'id': 'Medium', - 'name': 'Medium Instance', - 'ram': 3584, - 'disk': 127, - 'bandwidth': None, - 'price': '0.1266', - 'max_data_disks': 4, - 'cores': 2 - }, - 'A3': { - 'id': 'Large', - 'name': 'Large Instance', - 'ram': 7168, - 'disk': 127, - 'bandwidth': None, - 'price': '0.2531', - 'max_data_disks': 8, - 'cores': 4 - }, - 'A4': { - 'id': 'ExtraLarge', - 'name': 'Extra Large Instance', - 'ram': 14336, - 'disk': 127, - 'bandwidth': None, - 'price': '0.5062', - 'max_data_disks': 16, - 'cores': 8 - }, - 'A5': { - 'id': 'A5', - 'name': 'Memory Intensive Instance', - 'ram': 14336, - 'disk': 127, - 'bandwidth': None, - 'price': '0.2637', - 'max_data_disks': 4, - 'cores': 2 - }, - 'A6': { - 'id': 'A6', - 'name': 'A6 Instance', - 'ram': 28672, - 'disk': 127, - 'bandwidth': None, - 'price': '0.5273', - 'max_data_disks': 8, - 'cores': 4 - }, - 'A7': { - 'id': 'A7', - 'name': 'A7 Instance', - 'ram': 57344, - 'disk': 127, - 'bandwidth': None, - 'price': '1.0545', - 'max_data_disks': 16, - 'cores': 8 - }, - 'A8': { - 'id': 'A8', - 'name': 'A8 Instance', - 'ram': 57344, - 'disk': 127, - 'bandwidth': None, - 'price': '2.0774', - 'max_data_disks': 16, - 'cores': 8 - }, - 'A9': { - 'id': 'A9', - 'name': 'A9 Instance', - 'ram': 114688, - 'disk': 127, - 'bandwidth': None, - 'price': '4.7137', - 'max_data_disks': 16, - 'cores': 16 - }, - 'A10': { - 'id': 'A10', - 'name': 'A10 Instance', - 'ram': 57344, - 'disk': 127, - 'bandwidth': None, - 'price': '1.2233', - 'max_data_disks': 16, - 'cores': 8 - }, - 'A11': { - 'id': 'A11', - 'name': 'A11 Instance', - 'ram': 114688, - 'disk': 127, - 'bandwidth': None, - 'price': '2.1934', - 'max_data_disks': 16, - 'cores': 16 - }, - 'D1': { - 'id': 'Standard_D1', - 'name': 'D1 Faster Compute Instance', - 'ram': 3584, - 'disk': 127, - 'bandwidth': None, - 'price': '0.0992', - 'max_data_disks': 2, - 'cores': 1 - }, - 'D2': { - 'id': 'Standard_D2', - 'name': 'D2 Faster Compute Instance', - 'ram': 7168, - 'disk': 127, - 'bandwidth': None, - 'price': '0.1983', - 'max_data_disks': 4, - 'cores': 2 - }, - 'D3': { - 'id': 'Standard_D3', - 'name': 'D3 Faster Compute Instance', - 'ram': 14336, - 'disk': 127, - 'bandwidth': None, - 'price': '0.3965', - 'max_data_disks': 8, - 'cores': 4 - }, - 'D4': { - 'id': 'Standard_D4', - 'name': 'D4 Faster Compute Instance', - 'ram': 28672, - 'disk': 127, - 'bandwidth': None, - 'price': '0.793', - 'max_data_disks': 16, - 'cores': 8 - }, - 'D11': { - 'id': 'Standard_D11', - 'name': 'D11 Faster Compute Instance', - 'ram': 14336, - 'disk': 127, - 'bandwidth': None, - 'price': '0.251', - 'max_data_disks': 4, - 'cores': 2 - }, - 'D12': { - 'id': 'Standard_D12', - 'name': 'D12 Faster Compute Instance', - 'ram': 28672, - 'disk': 127, - 'bandwidth': None, - 'price': '0.502', - 'max_data_disks': 8, - 'cores': 4 - }, - 'D13': { - 'id': 'Standard_D13', - 'name': 'D13 Faster Compute Instance', - 'ram': 57344, - 'disk': 127, - 'bandwidth': None, - 'price': '0.9038', - 'max_data_disks': 16, - 'cores': 8 - }, - 'D14': { - 'id': 'Standard_D14', - 'name': 'D14 Faster Compute Instance', - 'ram': 114688, - 'disk': 127, - 'bandwidth': None, - 'price': '1.6261', - 'max_data_disks': 32, - 'cores': 16 - }, - 'Standard_D2_v2': { - 'id': 'Standard_D2_v2', - 'name': 'D2 v2 Faster Compute Instance', - 'ram': 7168, - 'disk': 100, - 'bandwidth': None, - 'price': '0.16', - 'max_data_disks': 4, - 'cores': 2 - }, - 'Standard_D3_v2': { - 'id': 'Standard_D3_v2', - 'name': 'D3 v2 Faster Compute Instance', - 'ram': 14336, - 'disk': 200, - 'bandwidth': None, - 'price': '0.319', - 'max_data_disks': 8, - 'cores': 4 - }, - 'Standard_D4_v2': { - 'id': 'Standard_D4_v2', - 'name': 'D4 v2 Faster Compute Instance', - 'ram': 28672, - 'disk': 400, - 'bandwidth': None, - 'price': '0.672', - 'max_data_disks': 16, - 'cores': 8 - }, - 'Standard_D5_v2': { - 'id': 'Standard_D5_v2', - 'name': 'D5 v2 Faster Compute Instance', - 'ram': 57344, - 'disk': 800, - 'bandwidth': None, - 'price': '1.277', - 'max_data_disks': 32, - 'cores': 16 - } -} - _KNOWN_SERIALIZATION_XFORMS = { 'include_apis': 'IncludeAPIs', 'message_id': 'MessageId', @@ -1607,7 +1357,8 @@ def _parse_response_body_from_xml_text(self, response, return_type): respbody = response.body # WARNING: HACK - # Special case for PublicIPs, because somehow it is named completely differently from the rest + # Special case for PublicIPs, because somehow it is named + # completely differently from the rest respbody = respbody.replace('PublicIPs', 'PublicIps') doc = minidom.parseString(respbody) diff --git a/libcloud/compute/drivers/azure_arm.py b/libcloud/compute/drivers/azure_arm.py new file mode 100644 index 0000000000..851715933f --- /dev/null +++ b/libcloud/compute/drivers/azure_arm.py @@ -0,0 +1,827 @@ +import json +import sys + +import time + +from libcloud.common.azure import AzureResourceManagerConnection, \ + AzureRedirectException +from libcloud.common.exceptions import RateLimitReachedError +from libcloud.compute.base import NodeDriver, NodeLocation, NodeSize, Node, \ + NodeImage +from libcloud.compute.drivers.azure import AzureHTTPRequest +from libcloud.compute.drivers.vcloud import urlparse +from libcloud.compute.types import Provider, NodeState + +from libcloud.utils.py3 import urlquote as url_quote, ensure_string + +AZURE_RESOURCE_MANAGEMENT_HOST = 'management.azure.com' +DEFAULT_API_VERSION = '2016-07-01' +MAX_RETRIES = 5 +if sys.version_info < (3,): + _unicode_type = unicode + + def _str(value): + if isinstance(value, unicode): + return value.encode('utf-8') + + return str(value) +else: + _str = str + _unicode_type = str + + +class AzureImage(NodeImage): + def __init__(self, publisher, offer, sku, os, version, location, driver): + self.publisher = publisher + self.offer = offer + self.sku = sku + self.os = os + self.version = version + self.location = location + self.driver = driver + urn = "%s:%s:%s:%s:%s" % (self.publisher, self.offer, + self.sku, self.os, self.version) + name = "%s %s %s %s %s" % (self.publisher, self.offer, + self.sku, self.os, self.version) + super(AzureImage, self).__init__(urn, name, driver) + + def __repr__(self): + return ('' + % (self.id, self.name, self.location)) + + def _get_image_reference(self): + return { + 'publisher': self.publisher, + 'offer': self.offer, + 'sku': self.sku, + 'version': self.version + } + + +class AzureVirtualNetwork(object): + def __init__(self, id, name, location, driver, snets=None): + self.id = id + self.name = name + self.location = location + self.driver = driver + if snets: + self.snets = snets + else: + self.snets = self.driver.ex_list_subnets(self.id) + + def __repr__(self): + return ('' + % (self.id, self.name, self.location)) + + +class AzureSubNet(object): + def __init__(self, id, name, driver): + self.id = id + self.name = name + self.driver = driver + + def __repr__(self): + return ('` + - Command to fetch the Tenant ID: `azure account show` + """ + self.subscription_id = subscription_id + self.token = token + self.follow_redirects = kwargs.get('follow_redirects', True) + super(AzureARMNodeDriver, self).__init__( + self.subscription_id, + self.token, + secure=True, + **kwargs + ) + + def list_nodes(self, ex_resource_group=None): + """ + List all nodes in a resource group + """ + if ex_resource_group: + path = '%sresourceGroups/%s/providers' \ + '/Microsoft.Compute/virtualMachines' % \ + (self._default_path_prefix, ex_resource_group) + else: + path = '%sproviders/Microsoft.Compute/virtualMachines' \ + % self._default_path_prefix + + json_response = self._perform_get(path, api_version='2016-03-30') + raw_data = json_response.parse_body() + if (int(json_response.status)) != 200: + raise AssertionError('%s' % raw_data['error']['message'] ) + return [self._to_node(x) for x in raw_data['value']] + + def list_sizes(self, location): + """ + List all image sizes available for location + """ + path = '%sproviders/Microsoft.Compute/locations/%s/vmSizes' % ( + self._default_path_prefix, location) + json_response = self._perform_get(path, api_version='2016-03-30') + raw_data = json_response.parse_body() + return [self._to_size(x) for x in raw_data['value']] + + def list_locations(self): + """ + Lists all locations + + :rtype: ``list`` of :class:`NodeLocation` + """ + path = '%slocations' % self._default_path_prefix + json_response = self._perform_get(path) + raw_data = json_response.parse_body() + return [self._to_location(x) for x in raw_data['value']] + + def create_node(self, name, location, node_size, + ex_resource_group_name, + ex_network_config, + ex_admin_username, + ex_marketplace_image, + ex_os_disk, + ex_data_disks=None, + ex_availability_set=None, + ex_public_ip_address=None, + ex_public_key=None): + + """ + Create Azure Virtual Machine using Resource Management model. + + For now this only creates a Linux machine, always default to public IP + address, and only support 1 data disk. + + It also assumes you have created these things: + - A resource group + - A storage account + - A virtual network + - A valid subnet inside the virtual network + + @inherits: :class:`NodeDriver.create_node` + + :keyword name: Required. The name given to this node + :type name: `str` + + :keyword location: Required. The location of the node to create + :type location: `NodeLocation` + + :keyword node_size: Required. The size of the node to create + :type node_size: `NodeSize` + + :keyword ex_resource_group_name: Required. + The name of the resource group the node belongs to + :type ex_resource_group_name: `str` + + :keyword ex_network_config: Required + Class holds the network information needed to create a + a node e.g. the virtual network and subnet the node is + connected to + :type ex_network_config: `AzureNetworkConfig` + + :keyword ex_admin_username: Required. + The name of the default admin user on the node + :type ex_admin_username: `str` + + :keyword ex_marketplace_image: Required. + The image from market place to be used for setting up the + OS disk. + :type ex_marketplace_image: `AzureImage` + + :keyword ex_os_disk: a dictionary containing the desired OS Disk + config including the storage account (`account`) and the + size in GB (`size`) + :type ex_os_disk: `dict` + + :keyword ex_data_disks: Optional. + A list of dictionaries containing the desired data disk + config including the storage profile (`profile`) and the + (`size`) + :type ex_data_disks: `list` + + :keyword ex_availability_set: Optional. + The availability set that the node lives in + :type ex_availability_set: `int` + + :keyword ex_public_ip_address: Optional. + User can provide one will be created for them + :type ex_public_ip_address: 'string' + + :keyword ex_public_key: Optional. + The content of the SSH public key to be deployed on the + box + :type ex_public_key: `str` + """ + if not ex_network_config: + raise AssertionError(' Network configuration needed. Can be' + 'like this, AzureNetworkConfig(vm, snet, ' + 'public_ip_allocation, public_ip_address') + + if ex_resource_group_name not in ex_network_config.virtual_network.id: + raise AssertionError(' Resource group and virtual network do not ' + 'match. Enter networks within the resource' + 'group') + + # Create the network interface card with that public IP address + nic = self._create_network_interface(name, ex_resource_group_name, + location, ex_network_config) + # Create the machine + node_payload = { + 'name': name, + 'location': location.id, + } + + os_disk_name = '%s-os-disk' % name + + node_payload['properties'] = { + 'hardwareProfile': { + 'vmSize': node_size.id + }, + 'storageProfile': { + 'imageReference': ex_marketplace_image._get_image_reference(), + 'osDisk': { + 'name': os_disk_name, + 'vhd': { + 'uri': 'http://%s.blob.core.windows.net/vhds/%s.vhd' % + (ex_os_disk['account'], os_disk_name) + }, + 'caching': 'ReadWrite', + 'createOption': 'fromImage', + 'diskSizeGB': ex_os_disk['size'] + } + }, + 'osProfile': { + 'computerName': name, + 'adminUsername': ex_admin_username, + 'linuxConfiguration': { + 'disablePasswordAuthentication': True, + 'ssh': { + 'publicKeys': [ + { + 'path': '/home/%s/.ssh/authorized_keys' % + ex_admin_username, + 'keyData': ex_public_key + } + ] + } + } + }, + 'networkProfile': { + 'networkInterfaces': [ + { + 'id': nic['id'], + 'properties': { + 'primary': True + } + } + ] + } + } + + if ex_data_disks: + data_disks = [] + for i, disk in enumerate(ex_data_disks): + data_disk_name = '%s-data-disk-%d' % (name, i) + # Attach an empty data disk if value this is given + data_disks.append({ + 'name': data_disk_name, + 'diskSizeGB': disk['size'], + 'lun': i, + 'vhd': { + 'uri': 'http://%s.blob.core.windows.net/vhds/%s.vhd' % + (disk['account'], data_disk_name) + }, + 'caching': 'ReadWrite', + 'createOption': 'empty' + }) + + node_payload['properties']['storageProfile']['dataDisks'] = data_disks + + if ex_availability_set: + availability_set_id = \ + '/subscriptions/%s/resourceGroups/%s/providers/' \ + 'Microsoft.Compute/availabilitySets/%s' % \ + (self.subscription_id, ex_resource_group_name, + ex_availability_set) + node_payload['properties']['availabilitySet'] = { + 'id': availability_set_id + } + + path = '%sresourceGroups/%s/providers/' \ + 'Microsoft.Compute/virtualMachines/%s' % \ + (self._default_path_prefix, ex_resource_group_name, name) + + output = self._perform_put(path, node_payload, + api_version='2016-03-30') + output = output.parse_body() + + if 'error' in output: + raise Exception('Error encountered: %s' % output['error']) + + return Node( + id=output['id'], + name=name, + state=NodeState.PENDING, + public_ips=[], + private_ips=[], + driver=self.connection.driver, + ) + + def reboot_node(self, node): + """ + Reboot a node. + + :param node: The node to be rebooted + :type node: :class:`.Node` + + :return: True if the reboot was successful, otherwise False + :rtype: ``bool`` + """ + state = self.ex_get_state_of_node(node) + if state == NodeState.RUNNING: + raise AssertionError("Node is already running") + if state == NodeState.STOPPED: + raise AssertionError("Node has been deallocated, " + "cannot be rebooted") + + path = '%s/restart' % node.id + json_response = self._perform_post(path, api_version="2015-06-15") + + return int(json_response.status) == 200 + + def destroy_node(self, node): + """ + Destroy a node. + + Depending upon the provider, this may destroy all data associated with + the node, including backups. + + :param node: The node to be destroyed + :type node: :class:`.Node` + + :return: True if the destroy was successful, False otherwise. + :rtype: ``bool`` + """ + + json_response = self._perform_delete(node.id, api_version='2016-03-30') + return json_response.success() + + def list_images(self, location=None, publisher=None): + images = [] + if location: + locations = [location] + else: + locations = [loc.name for loc in self.list_locations()] + + for loc in locations: + path = '%sproviders/Microsoft.Compute/locations/%s/publishers'\ + % (self._default_path_prefix, loc) + publishers = self.ex_list_publishers(path) + + if publisher: + publishers = [x for x in publishers + if x['id'].lower() == publisher.lower() or + x['name'].lower() == publisher.lower()] + + for pub in publishers: + offers = self.ex_list_offers(pub['id']) + + for offer in offers: + skus = self.ex_list_skus(offer['id']) + + for sku in skus: + versions = self.ex_list_versions(sku['id']) + + for version in versions: + os = self._get_os_from_version(version['id']) + azure_image = AzureImage(pub['name'], + offer['name'], + sku['name'], os, + version['name'], loc, + self.connection.driver) + images.append(azure_image) + + # Finally, return the images + return images + + def ex_list_publishers(self, path): + json_response = self._perform_get(path, api_version='2016-03-30') + raw_data = json_response.parse_body() + return [{'name': pub['name'], 'id': pub['id']} for pub in raw_data] + + def ex_list_offers(self, path): + json_response = self._perform_get( + '%s/artifacttypes/vmimage/offers' % path, api_version='2016-03-30') + raw_data = json_response.parse_body() + return [{'name': offer['name'], 'id': offer['id']} for offer in + raw_data] + + def ex_list_skus(self, path): + json_response = self._perform_get('%s/skus' % path, + api_version='2016-03-30') + raw_data = json_response.parse_body() + return [{'name': sku['name'], 'id': sku['id']} for sku in raw_data] + + def ex_list_versions(self, path): + json_response = self._perform_get('%s/versions' % path, + api_version='2016-03-30') + raw_data = json_response.parse_body() + return [{'name': sku['name'], 'id': sku['id']} for sku in raw_data] + + def ex_list_virtual_networks(self): + json_response = self._perform_get( + '%sproviders/Microsoft.Network/virtualnetworks' % + self._default_path_prefix, + api_version='2016-03-30') + raw_data = json_response.parse_body() + return [self._to_virtual_network(x) for x in raw_data['value']] + + def ex_list_subnets(self, path): + json_response = self._perform_get('%s/subnets' % path, + api_version='2016-03-30') + raw_data = json_response.parse_body() + return [self._to_subnet(x) for x in raw_data['value']] + + def _get_os_from_version(self, path): + json_response = self._perform_get(path, api_version='2016-03-30') + raw_data = json_response.parse_body() + return raw_data['properties']['osDiskImage']['operatingSystem'] + + def _create_network_interface(self, node_name, resource_group_name, + location, network_config): + nic_name = '%s-nic' % node_name + + payload = { + 'location': location.id, + 'properties': { + 'ipConfigurations': [{ + 'name': '%s-ip' % node_name, + 'properties': { + 'subnet': { + 'id': network_config.subnet.id + }, + 'privateIPAllocationMethod': 'Dynamic' + } + }] + } + } + + if network_config.public_ip_alllocation: + if network_config.public_ip_adress: + public_ip_address = network_config.public_ip_adress + else: + pip = self._create_public_ip_address( + node_name, resource_group_name, location) + public_ip_address = pip['id'] + + payload['properties']['ipConfigurations'][0]['properties'][ + 'publicIPAddress'] = { + 'id': public_ip_address + } + + path = '%sresourceGroups/%s/providers/Microsoft.Network/' \ + 'networkInterfaces/%s' % \ + (self._default_path_prefix, resource_group_name, nic_name) + output = self._perform_put(path, payload) + + return output.parse_body() + + def _create_public_ip_address(self, node_name, resource_group_name, + location): + public_ip_address_name = '%s-public-ip' % node_name + payload = { + 'location': location.id, + 'properties': { + 'publicIPAllocationMethod': 'Dynamic', + 'publicIPAddressVersion': "IPv4", + 'idleTimeoutInMinutes': 5, + "dnsSettings": { + "domainNameLabel": node_name + } + } + } + path = '%sresourceGroups/%s/providers/Microsoft.Network/' \ + 'publicIPAddresses/%s' % \ + (self._default_path_prefix, resource_group_name, + public_ip_address_name) + + output = self._perform_put(path, payload) + return output.parse_body() + + def _to_node(self, node_data): + """ + Take the azure raw data and turn into a Node class + """ + network_interfaces = node_data.get('properties', {}).get( + 'networkProfile', {}).get( + 'networkInterfaces', []) + network_interface_urls = ['%s' % x.get('id') for x in + network_interfaces if x.get('id')] + public_ips = [] + private_ips = [] + for network_interface_url in network_interface_urls: + _public_ips, _private_ips = self._get_public_and_private_ips( + network_interface_url) + public_ips.extend(_public_ips) + private_ips.extend(_private_ips) + + provisioning_state = node_data.get('properties', {}).get( + 'provisioningState') + node_state = NodeState.RUNNING if provisioning_state == 'Succeeded' \ + else NodeState.PENDING + + return Node( + id=node_data.get('id'), + name=node_data.get('name'), + state=node_state, + public_ips=public_ips, + private_ips=private_ips, + driver=self.connection.driver, + extra={ + 'provisioningState': node_data.get('properties', {}).get( + 'provisioningState') + } + ) + + def _get_public_and_private_ips(self, network_interace_url): + """ + Get public and and private ips of the virtual machine by following the + urls provided. + :param network_interace_url: + :return: + """ + json_response = self._perform_get(network_interace_url) + raw_data = json_response.parse_body() + ip_configurations = raw_data.get('properties', {}).get( + 'ipConfigurations', []) + public_ips = [] + private_ips = [] + for ip_configuration in ip_configurations: + private_ips.append( + ip_configuration['properties']['privateIPAddress']) + public_ips.append( + self._get_public_ip( + ip_configuration['properties']['publicIPAddress']['id'])) + return public_ips, private_ips + + def _get_public_ip(self, public_ip_url): + """ + Using the public ip url we can query the azure api and get the public + ip adrewss + """ + json_response = self._perform_get(public_ip_url) + raw_data = json_response.parse_body() + return raw_data.get('properties', {}).get('ipAddress', None) + + def _to_location(self, location_data): + """ + Convert the data from a Azure response object into a location. + Commented out code is from the classic Azure driver, not sure if we + need those fields. + """ + return NodeLocation( + id=location_data.get('name'), + name=location_data.get('name'), + country=location_data.get('displayName'), + driver=self.connection.driver, + # available_services=data.available_services, + # virtual_machine_role_sizes=vm_role_sizes + ) + + def _to_size(self, size_data): + """ + Convert the data from a Azure response object into a size + + Sample raw data: + { + 'maxDataDiskCount': 32, + 'memoryInMB': 114688, + 'name': 'Standard_D14', + 'numberOfCores': 16, + 'osDiskSizeInMB': 1047552, + 'resourceDiskSizeInMB': 819200 + } + """ + return NodeSize( + id=size_data.get('name'), + name=size_data.get('name'), + ram=size_data.get('memoryInMB'), + disk=size_data.get('osDiskSizeInMB'), + driver=self, + price=-1, + bandwidth=-1, + extra=size_data + ) + + def _to_virtual_network(self, network_data): + snets = [self._to_subnet(x) for x in network_data['properties'][ + 'subnets']] + return AzureVirtualNetwork( + id=network_data.get('id'), + name=network_data.get('name'), + location=network_data.get('location'), + snets=snets, + driver=self.connection.driver + ) + + def _to_subnet(self, snet_data): + return AzureSubNet( + id=snet_data.get('id'), + name=snet_data.get('name'), + driver=self.connection.driver + ) + + def ex_get_state_of_node(self, Node): + """ + Returns the state of a virtual machine + """ + path = '%s/InstanceView' % Node.id + json_response = self._perform_get(path, api_version='2016-03-30') + raw_date = json_response.parse_body() + raw_state = raw_date.get('statuses')[1].get('code') + if raw_state == 'PowerState/stopped': + return NodeState.SUSPENDED + if raw_state == 'PowerState/running': + return NodeState.RUNNING + if raw_state == 'PowerState/deallocated': + return NodeState.STOPPED + + @property + def _default_path_prefix(self): + """Everything starts with the subscription prefix""" + return '/subscriptions/%s/' % self.subscription_id + + def _perform_put(self, path, body, api_version=None): + request = AzureHTTPRequest() + request.method = 'PUT' + request.host = AZURE_RESOURCE_MANAGEMENT_HOST + request.path = path + request.body = ensure_string(self._get_request_body(body)) + request.path, request.query = self._update_request_uri_query( + request, api_version) + return self._perform_request(request) + + def _get_request_body(self, request_body): + if request_body is None: + return b'' + + if isinstance(request_body, dict): + return json.dumps(request_body) + + if isinstance(request_body, bytes): + return request_body + + if isinstance(request_body, _unicode_type): + return request_body.encode('utf-8') + + request_body = str(request_body) + if isinstance(request_body, _unicode_type): + return request_body.encode('utf-8') + + return request_body + + def _perform_get(self, path, api_version=None): + request = AzureHTTPRequest() + request.method = 'GET' + request.host = AZURE_RESOURCE_MANAGEMENT_HOST + request.path = path + request.path, request.query = self._update_request_uri_query( + request, api_version) + return self._perform_request(request) + + def _perform_post(self, path, api_version=None, body=None): + request = AzureHTTPRequest() + request.method = 'POST' + request.body = body + request.host = AZURE_RESOURCE_MANAGEMENT_HOST + request.path = path + request.path, request.query = self._update_request_uri_query( + request, api_version) + return self._perform_request(request) + + def _perform_delete(self, path, api_version=None, body=None): + request = AzureHTTPRequest() + request.method = 'DELETE' + request.body = body + request.host = AZURE_RESOURCE_MANAGEMENT_HOST + request.path = path + request.path, request.query = self._update_request_uri_query( + request, api_version) + return self._perform_request(request) + + def _update_request_uri_query(self, request, api_version=None): + """ + pulls the query string out of the URI and moves it into + the query portion of the request object. If there are already + query parameters on the request the parameters in the URI will + appear after the existing parameters + """ + if '?' in request.path: + request.path, _, query_string = request.path.partition('?') + if query_string: + query_params = query_string.split('&') + for query in query_params: + if '=' in query: + name, _, value = query.partition('=') + request.query.append((name, value)) + + request.path = url_quote(request.path, '/()$=\',') + + # Add the API version + if not api_version: + api_version_query = ('api-version', DEFAULT_API_VERSION) + else: + api_version_query = ('api-version', api_version) + + if request.query: + request.query.append(api_version_query) + else: + request.query = [api_version_query] + + # add encoded queries to request.path. + request.path += '?' + for name, value in request.query: + if value is not None: + request.path += '%s=%s%s' % ( + name, + url_quote(value, '/()$=\','), + '&' + ) + request.path = request.path[:-1] + + return request.path, request.query + + def _perform_request(self, request, retries=0): + if retries > MAX_RETRIES: + # We have retried more than enough, let's quit + raise Exception( + 'Maximum retries (%d) reached. Please try again later' % + MAX_RETRIES) + try: + return self.connection.request( + action=request.path, + data=request.body, + headers=request.headers, + method=request.method + ) + except AzureRedirectException: + e = sys.exc_info()[1] + parsed_url = urlparse.urlparse(e.location) + request.host = parsed_url.netloc + return self._perform_request(request) + except RateLimitReachedError as e: + if e.retry_after is not None: + time.sleep(e.retry_after) + # Redo the request but with retries value incremented + return self._perform_request(request, retries=retries + 1) + else: + raise e + except Exception as e: + raise e diff --git a/libcloud/compute/providers.py b/libcloud/compute/providers.py index 568c886338..e635d35710 100644 --- a/libcloud/compute/providers.py +++ b/libcloud/compute/providers.py @@ -30,6 +30,8 @@ DRIVERS = { Provider.AZURE: ('libcloud.compute.drivers.azure', 'AzureNodeDriver'), + Provider.AZURE_ARM: + ('libcloud.compute.drivers.azure_arm', 'AzureARMNodeDriver'), Provider.DUMMY: ('libcloud.compute.drivers.dummy', 'DummyNodeDriver'), Provider.EC2: diff --git a/libcloud/compute/types.py b/libcloud/compute/types.py index 740b688cb7..9e788e2cee 100644 --- a/libcloud/compute/types.py +++ b/libcloud/compute/types.py @@ -106,6 +106,7 @@ class Provider(Type): ALIYUN_ECS = 'aliyun_ecs' AURORACOMPUTE = 'aurora_compute' AZURE = 'azure' + AZURE_ARM = 'azure_arm' BLUEBOX = 'bluebox' BRIGHTBOX = 'brightbox' BSNL = 'bsnl' diff --git a/libcloud/test/compute/fixtures/azure_arm/4psa_offers b/libcloud/test/compute/fixtures/azure_arm/4psa_offers new file mode 100644 index 0000000000..84074a9b26 --- /dev/null +++ b/libcloud/test/compute/fixtures/azure_arm/4psa_offers @@ -0,0 +1,7 @@ +[ + { + "location": "brazilsouth", + "name": "voipnow", + "id": "/Subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/Providers/Microsoft.Compute/Locations/brazilsouth/Publishers/4psa/ArtifactTypes/VMImage/Offers/voipnow" + } +] \ No newline at end of file diff --git a/libcloud/test/compute/fixtures/azure_arm/_subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_locations.json b/libcloud/test/compute/fixtures/azure_arm/_subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_locations.json new file mode 100644 index 0000000000..383033227c --- /dev/null +++ b/libcloud/test/compute/fixtures/azure_arm/_subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_locations.json @@ -0,0 +1,172 @@ +{ + "value": [ + { + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/locations/eastasia", + "name": "eastasia", + "displayName": "East Asia", + "longitude": "114.188", + "latitude": "22.267" + }, + { + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/locations/southeastasia", + "name": "southeastasia", + "displayName": "Southeast Asia", + "longitude": "103.833", + "latitude": "1.283" + }, + { + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/locations/centralus", + "name": "centralus", + "displayName": "Central US", + "longitude": "-93.6208", + "latitude": "41.5908" + }, + { + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/locations/eastus", + "name": "eastus", + "displayName": "East US", + "longitude": "-79.8164", + "latitude": "37.3719" + }, + { + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/locations/eastus2", + "name": "eastus2", + "displayName": "East US 2", + "longitude": "-78.3889", + "latitude": "36.6681" + }, + { + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/locations/westus", + "name": "westus", + "displayName": "West US", + "longitude": "-122.417", + "latitude": "37.783" + }, + { + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/locations/northcentralus", + "name": "northcentralus", + "displayName": "North Central US", + "longitude": "-87.6278", + "latitude": "41.8819" + }, + { + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/locations/southcentralus", + "name": "southcentralus", + "displayName": "South Central US", + "longitude": "-98.5", + "latitude": "29.4167" + }, + { + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/locations/northeurope", + "name": "northeurope", + "displayName": "North Europe", + "longitude": "-6.2597", + "latitude": "53.3478" + }, + { + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/locations/westeurope", + "name": "westeurope", + "displayName": "West Europe", + "longitude": "4.9", + "latitude": "52.3667" + }, + { + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/locations/japanwest", + "name": "japanwest", + "displayName": "Japan West", + "longitude": "135.5022", + "latitude": "34.6939" + }, + { + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/locations/japaneast", + "name": "japaneast", + "displayName": "Japan East", + "longitude": "139.77", + "latitude": "35.68" + }, + { + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/locations/brazilsouth", + "name": "brazilsouth", + "displayName": "Brazil South", + "longitude": "-46.633", + "latitude": "-23.55" + }, + { + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/locations/australiaeast", + "name": "australiaeast", + "displayName": "Australia East", + "longitude": "151.2094", + "latitude": "-33.86" + }, + { + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/locations/australiasoutheast", + "name": "australiasoutheast", + "displayName": "Australia Southeast", + "longitude": "144.9631", + "latitude": "-37.8136" + }, + { + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/locations/southindia", + "name": "southindia", + "displayName": "South India", + "longitude": "80.1636", + "latitude": "12.9822" + }, + { + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/locations/centralindia", + "name": "centralindia", + "displayName": "Central India", + "longitude": "73.9197", + "latitude": "18.5822" + }, + { + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/locations/westindia", + "name": "westindia", + "displayName": "West India", + "longitude": "72.868", + "latitude": "19.088" + }, + { + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/locations/canadacentral", + "name": "canadacentral", + "displayName": "Canada Central", + "longitude": "-79.383", + "latitude": "43.653" + }, + { + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/locations/canadaeast", + "name": "canadaeast", + "displayName": "Canada East", + "longitude": "-71.217", + "latitude": "46.817" + }, + { + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/locations/uksouth", + "name": "uksouth", + "displayName": "UK South", + "longitude": "-0.799", + "latitude": "50.941" + }, + { + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/locations/ukwest", + "name": "ukwest", + "displayName": "UK West", + "longitude": "-3.084", + "latitude": "53.427" + }, + { + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/locations/westcentralus", + "name": "westcentralus", + "displayName": "West Central US", + "longitude": "-110.234", + "latitude": "40.890" + }, + { + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/locations/westus2", + "name": "westus2", + "displayName": "West US 2", + "longitude": "-119.852", + "latitude": "47.233" + } + ] +} \ No newline at end of file diff --git a/libcloud/test/compute/fixtures/azure_arm/_subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_providers_Microsoft_Compute_virtualmachines.json b/libcloud/test/compute/fixtures/azure_arm/_subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_providers_Microsoft_Compute_virtualmachines.json new file mode 100644 index 0000000000..d7107fd26a --- /dev/null +++ b/libcloud/test/compute/fixtures/azure_arm/_subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_providers_Microsoft_Compute_virtualmachines.json @@ -0,0 +1,57 @@ +{ + "value": [ + { + "properties": { + "vmId": "g4bb0538-gg35-9119-f34r-9v392b43184b", + "hardwareProfile": { + "vmSize": "Standard_D1_v2" + }, + "storageProfile": { + "imageReference": { + "publisher": "canonical", + "offer": "ubuntuserver", + "sku": "16.10-DAILY", + "version": "16.10.201609180" + }, + "osDisk": { + "osType": "Linux", + "name": "cli123fa4asf1a23bef-os-1243501352184", + "createOption": "FromImage", + "vhd": { + "uri": "https://ssfwwefwg7.blob.core.windows.net/vhds/cli123fa4asf1a23bef-os-1243501352184.vhd" + }, + "caching": "ReadWrite" + }, + "dataDisks": [] + }, + "osProfile": { + "computerName": "uservm", + "adminUsername": "User", + "linuxConfiguration": { + "disablePasswordAuthentication": false + }, + "secrets": [] + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/resourceGroups/myapp/providers/Microsoft.Network/networkInterfaces/user-brazi-40wudhabjn7g-nic" + } + ] + }, + "diagnosticsProfile": { + "bootDiagnostics": { + "enabled": true, + "storageUri": "https://ssfwwefwg7wv.blob.core.windows.net/" + } + }, + "provisioningState": "Succeeded" + }, + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/resourceGroups/myapp/providers/Microsoft.Compute/virtualMachines/myvm", + "name": "myvm", + "type": "Microsoft.Compute/virtualMachines", + "location": "brazilsouth", + "tags": {} + } + ] +} \ No newline at end of file diff --git a/libcloud/test/compute/fixtures/azure_arm/_subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_resourceGroups_fakegroup_providers_Microsoft_Compute_virtualmachines.json b/libcloud/test/compute/fixtures/azure_arm/_subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_resourceGroups_fakegroup_providers_Microsoft_Compute_virtualmachines.json new file mode 100644 index 0000000000..231d0e93a9 --- /dev/null +++ b/libcloud/test/compute/fixtures/azure_arm/_subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_resourceGroups_fakegroup_providers_Microsoft_Compute_virtualmachines.json @@ -0,0 +1,6 @@ +{ + "error": { + "code": "ResourceGroupNotFound", + "message": "Resource group 'fakegroup' could not be found." + } +} \ No newline at end of file diff --git a/libcloud/test/compute/fixtures/azure_arm/_subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_resourceGroups_myapp_providers_Microsoft_Compute_virtualmachines.json b/libcloud/test/compute/fixtures/azure_arm/_subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_resourceGroups_myapp_providers_Microsoft_Compute_virtualmachines.json new file mode 100644 index 0000000000..d7107fd26a --- /dev/null +++ b/libcloud/test/compute/fixtures/azure_arm/_subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_resourceGroups_myapp_providers_Microsoft_Compute_virtualmachines.json @@ -0,0 +1,57 @@ +{ + "value": [ + { + "properties": { + "vmId": "g4bb0538-gg35-9119-f34r-9v392b43184b", + "hardwareProfile": { + "vmSize": "Standard_D1_v2" + }, + "storageProfile": { + "imageReference": { + "publisher": "canonical", + "offer": "ubuntuserver", + "sku": "16.10-DAILY", + "version": "16.10.201609180" + }, + "osDisk": { + "osType": "Linux", + "name": "cli123fa4asf1a23bef-os-1243501352184", + "createOption": "FromImage", + "vhd": { + "uri": "https://ssfwwefwg7.blob.core.windows.net/vhds/cli123fa4asf1a23bef-os-1243501352184.vhd" + }, + "caching": "ReadWrite" + }, + "dataDisks": [] + }, + "osProfile": { + "computerName": "uservm", + "adminUsername": "User", + "linuxConfiguration": { + "disablePasswordAuthentication": false + }, + "secrets": [] + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/resourceGroups/myapp/providers/Microsoft.Network/networkInterfaces/user-brazi-40wudhabjn7g-nic" + } + ] + }, + "diagnosticsProfile": { + "bootDiagnostics": { + "enabled": true, + "storageUri": "https://ssfwwefwg7wv.blob.core.windows.net/" + } + }, + "provisioningState": "Succeeded" + }, + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/resourceGroups/myapp/providers/Microsoft.Compute/virtualMachines/myvm", + "name": "myvm", + "type": "Microsoft.Compute/virtualMachines", + "location": "brazilsouth", + "tags": {} + } + ] +} \ No newline at end of file diff --git a/libcloud/test/compute/fixtures/azure_arm/_subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_resourceGroups_myapp_providers_Microsoft_Network_networkInterfaces_user_brazi_40wudhabjn7g_nic.json b/libcloud/test/compute/fixtures/azure_arm/_subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_resourceGroups_myapp_providers_Microsoft_Network_networkInterfaces_user_brazi_40wudhabjn7g_nic.json new file mode 100644 index 0000000000..8781d8c206 --- /dev/null +++ b/libcloud/test/compute/fixtures/azure_arm/_subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_resourceGroups_myapp_providers_Microsoft_Network_networkInterfaces_user_brazi_40wudhabjn7g_nic.json @@ -0,0 +1,38 @@ +{ + "name": "user-brazi-40wudhabjn7g-nic", + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/resourceGroups/myapp/providers/Microsoft.Network/networkInterfaces/user-brazi-40wudhabjn7g-nic", + "location": "brazilsouth", + "properties": { + "provisioningState": "Succeeded", + "resourceGuid": "2349067g-c235-4eg8-83b5-sdfehe23df90", + "ipConfigurations": [ + { + "name": "ipconfig1472384756473", + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/resourceGroups/myapp/providers/Microsoft.Network/networkInterfaces/user-brazi-40wudhabjn7g-nic/ipConfigurations/ipconfig1472384756473", + "properties": { + "provisioningState": "Succeeded", + "privateIPAddress": "10.1.1.1", + "privateIPAllocationMethod": "Dynamic", + "publicIPAddress": { + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/resourceGroups/myapp/providers/Microsoft.Network/publicIPAddresses/user-brazi-40wudhabjn7g-pip" + }, + "subnet": { + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/resourceGroups/myapp/providers/Microsoft.Network/virtualNetworks/user-brazi-40wudhabjn7g-vnet/subnets/user-brazi-40wudhabjn7g-snet" + }, + "primary": true, + "privateIPAddressVersion": "IPv4" + } + } + ], + "dnsSettings": { + "dnsServers": [], + "appliedDnsServers": [] + }, + "enableIPForwarding": false, + "primary": true, + "virtualMachine": { + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/resourceGroups/myapp/providers/Microsoft.Compute/virtualMachines/myvm" + } + }, + "type": "Microsoft.Network/networkInterfaces" +} diff --git a/libcloud/test/compute/fixtures/azure_arm/_subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_resourceGroups_myapp_providers_Microsoft_Network_publicIPAddresses_user_brazi_40wudhabjn7g_pip.json b/libcloud/test/compute/fixtures/azure_arm/_subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_resourceGroups_myapp_providers_Microsoft_Network_publicIPAddresses_user_brazi_40wudhabjn7g_pip.json new file mode 100644 index 0000000000..703d8e52b4 --- /dev/null +++ b/libcloud/test/compute/fixtures/azure_arm/_subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_resourceGroups_myapp_providers_Microsoft_Network_publicIPAddresses_user_brazi_40wudhabjn7g_pip.json @@ -0,0 +1,21 @@ +{ + "name": "user-brazi-40wudhabjn7g-pip", + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/resourceGroups/myapp/providers/Microsoft.Network/publicIPAddresses/user-brazi-40wudhabjn7g-pip", + "type": "Microsoft.Network/publicIPAddresses", + "location": "brazilsouth", + "properties": { + "provisioningState": "Succeeded", + "ipAddress": "1.1.1.1", + "resourceGuid": "2349067g-c235-4eg8-83b5-sdfehe23df90", + "publicIPAddressVersion": "IPv4", + "publicIPAllocationMethod": "Dynamic", + "idleTimeoutInMinutes": 4, + "dnsSettings": { + "domainNameLabel": "user-brazi-40wudhabjn7g-pip", + "fqdn": "user-brazi-40wudhabjn7g-pip.brazilsouth.cloudapp.azure.com" + }, + "ipConfiguration": { + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/resourceGroups/denvarapptest/providers/Microsoft.Network/networkInterfaces/user-brazi-40wudhabjn7g-nic/ipConfigurations/ipconfig1472384756473" + } + } +} diff --git a/libcloud/test/compute/fixtures/azure_arm/brazil_south_publishers b/libcloud/test/compute/fixtures/azure_arm/brazil_south_publishers new file mode 100644 index 0000000000..a39953aec8 --- /dev/null +++ b/libcloud/test/compute/fixtures/azure_arm/brazil_south_publishers @@ -0,0 +1,7 @@ +[ + { + "location": "brazilsouth", + "name": "4psa", + "id": "/Subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/Providers/Microsoft.Compute/Locations/brazilsouth/Publishers/4psa" + } + ] \ No newline at end of file diff --git a/libcloud/test/compute/fixtures/azure_arm/brazil_south_virtual_networks b/libcloud/test/compute/fixtures/azure_arm/brazil_south_virtual_networks new file mode 100644 index 0000000000..21ea23e725 --- /dev/null +++ b/libcloud/test/compute/fixtures/azure_arm/brazil_south_virtual_networks @@ -0,0 +1,39 @@ +{ + "value": [ + { + "name": "user-brazi-40wudhabjn7g-vnet", + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/resourceGroups/myapp/providers/Microsoft.Network/virtualNetworks/user-brazi-40wudhabjn7g-vnet", + "etag": "W/sfsfdfd-6a60-4406-b0cd-sfsfs", + "type": "Microsoft.Network/virtualNetworks", + "location": "brazilsouth", + "properties": { + "provisioningState": "Succeeded", + "resourceGuid": "2349067g-c235-4eg8-83b5-sdfehe23df90", + "addressSpace": { + "addressPrefixes": [ + "10.0.0.0/16" + ] + }, + "dhcpOptions": { + "dnsServers": [] + }, + "subnets": [ + { + "name": "denva-brazi-40gpoqeyjn7g-snet", + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/resourceGroups/myapp/providers/Microsoft.Network/virtualNetworks/user-brazi-40wudhabjn7g-vnet/subnets/user-brazi-40wudhabjn7g-snet", + "etag": "W/dsdsdsd-6a60-adaa-b0cd-5a199e2cffa9", + "properties": { + "provisioningState": "Succeeded", + "addressPrefix": "10.0.1.0/24", + "ipConfigurations": [ + { + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/resourceGroups/myapp/providers/Microsoft.Network/networkInterfaces/user-brazi-40wudhabjn7g-nic/ipConfigurations/ipconfig1472384756473" + } + ] + } + } + ] + } + } + ] +} \ No newline at end of file diff --git a/libcloud/test/compute/fixtures/azure_arm/brazil_south_vm_sizes b/libcloud/test/compute/fixtures/azure_arm/brazil_south_vm_sizes new file mode 100644 index 0000000000..378f85537e --- /dev/null +++ b/libcloud/test/compute/fixtures/azure_arm/brazil_south_vm_sizes @@ -0,0 +1,468 @@ +{ + "value": [ + { + "name": "Standard_A0", + "numberOfCores": 1, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 20480, + "memoryInMB": 768, + "maxDataDiskCount": 1 + }, + { + "name": "Standard_A1", + "numberOfCores": 1, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 71680, + "memoryInMB": 1792, + "maxDataDiskCount": 2 + }, + { + "name": "Standard_A2", + "numberOfCores": 2, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 138240, + "memoryInMB": 3584, + "maxDataDiskCount": 4 + }, + { + "name": "Standard_A3", + "numberOfCores": 4, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 291840, + "memoryInMB": 7168, + "maxDataDiskCount": 8 + }, + { + "name": "Standard_A5", + "numberOfCores": 2, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 138240, + "memoryInMB": 14336, + "maxDataDiskCount": 4 + }, + { + "name": "Standard_A4", + "numberOfCores": 8, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 619520, + "memoryInMB": 14336, + "maxDataDiskCount": 16 + }, + { + "name": "Standard_A6", + "numberOfCores": 4, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 291840, + "memoryInMB": 28672, + "maxDataDiskCount": 8 + }, + { + "name": "Standard_A7", + "numberOfCores": 8, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 619520, + "memoryInMB": 57344, + "maxDataDiskCount": 16 + }, + { + "name": "Basic_A0", + "numberOfCores": 1, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 20480, + "memoryInMB": 768, + "maxDataDiskCount": 1 + }, + { + "name": "Basic_A1", + "numberOfCores": 1, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 40960, + "memoryInMB": 1792, + "maxDataDiskCount": 2 + }, + { + "name": "Basic_A2", + "numberOfCores": 2, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 61440, + "memoryInMB": 3584, + "maxDataDiskCount": 4 + }, + { + "name": "Basic_A3", + "numberOfCores": 4, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 122880, + "memoryInMB": 7168, + "maxDataDiskCount": 8 + }, + { + "name": "Basic_A4", + "numberOfCores": 8, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 245760, + "memoryInMB": 14336, + "maxDataDiskCount": 16 + }, + { + "name": "Standard_D1_v2", + "numberOfCores": 1, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 51200, + "memoryInMB": 3584, + "maxDataDiskCount": 2 + }, + { + "name": "Standard_D2_v2", + "numberOfCores": 2, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 102400, + "memoryInMB": 7168, + "maxDataDiskCount": 4 + }, + { + "name": "Standard_D3_v2", + "numberOfCores": 4, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 204800, + "memoryInMB": 14336, + "maxDataDiskCount": 8 + }, + { + "name": "Standard_D4_v2", + "numberOfCores": 8, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 409600, + "memoryInMB": 28672, + "maxDataDiskCount": 16 + }, + { + "name": "Standard_D5_v2", + "numberOfCores": 16, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 819200, + "memoryInMB": 57344, + "maxDataDiskCount": 32 + }, + { + "name": "Standard_D11_v2", + "numberOfCores": 2, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 102400, + "memoryInMB": 14336, + "maxDataDiskCount": 4 + }, + { + "name": "Standard_D12_v2", + "numberOfCores": 4, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 204800, + "memoryInMB": 28672, + "maxDataDiskCount": 8 + }, + { + "name": "Standard_D13_v2", + "numberOfCores": 8, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 409600, + "memoryInMB": 57344, + "maxDataDiskCount": 16 + }, + { + "name": "Standard_D14_v2", + "numberOfCores": 16, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 819200, + "memoryInMB": 114688, + "maxDataDiskCount": 32 + }, + { + "name": "Standard_D15_v2", + "numberOfCores": 20, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 286720, + "memoryInMB": 143360, + "maxDataDiskCount": 40 + }, + { + "name": "Standard_F1", + "numberOfCores": 1, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 16384, + "memoryInMB": 2048, + "maxDataDiskCount": 2 + }, + { + "name": "Standard_F2", + "numberOfCores": 2, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 32768, + "memoryInMB": 4096, + "maxDataDiskCount": 4 + }, + { + "name": "Standard_F4", + "numberOfCores": 4, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 65536, + "memoryInMB": 8192, + "maxDataDiskCount": 8 + }, + { + "name": "Standard_F8", + "numberOfCores": 8, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 131072, + "memoryInMB": 16384, + "maxDataDiskCount": 16 + }, + { + "name": "Standard_F16", + "numberOfCores": 16, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 262144, + "memoryInMB": 32768, + "maxDataDiskCount": 32 + }, + { + "name": "Standard_A1_v2", + "numberOfCores": 1, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 10240, + "memoryInMB": 2048, + "maxDataDiskCount": 2 + }, + { + "name": "Standard_A2m_v2", + "numberOfCores": 2, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 20480, + "memoryInMB": 16384, + "maxDataDiskCount": 4 + }, + { + "name": "Standard_A2_v2", + "numberOfCores": 2, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 20480, + "memoryInMB": 4096, + "maxDataDiskCount": 4 + }, + { + "name": "Standard_A4m_v2", + "numberOfCores": 4, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 40960, + "memoryInMB": 32768, + "maxDataDiskCount": 8 + }, + { + "name": "Standard_A4_v2", + "numberOfCores": 4, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 40960, + "memoryInMB": 8192, + "maxDataDiskCount": 8 + }, + { + "name": "Standard_A8m_v2", + "numberOfCores": 8, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 81920, + "memoryInMB": 65536, + "maxDataDiskCount": 16 + }, + { + "name": "Standard_A8_v2", + "numberOfCores": 8, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 81920, + "memoryInMB": 16384, + "maxDataDiskCount": 16 + }, + { + "name": "Standard_D1", + "numberOfCores": 1, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 51200, + "memoryInMB": 3584, + "maxDataDiskCount": 2 + }, + { + "name": "Standard_D2", + "numberOfCores": 2, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 102400, + "memoryInMB": 7168, + "maxDataDiskCount": 4 + }, + { + "name": "Standard_D3", + "numberOfCores": 4, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 204800, + "memoryInMB": 14336, + "maxDataDiskCount": 8 + }, + { + "name": "Standard_D4", + "numberOfCores": 8, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 409600, + "memoryInMB": 28672, + "maxDataDiskCount": 16 + }, + { + "name": "Standard_D11", + "numberOfCores": 2, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 102400, + "memoryInMB": 14336, + "maxDataDiskCount": 4 + }, + { + "name": "Standard_D12", + "numberOfCores": 4, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 204800, + "memoryInMB": 28672, + "maxDataDiskCount": 8 + }, + { + "name": "Standard_D13", + "numberOfCores": 8, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 409600, + "memoryInMB": 57344, + "maxDataDiskCount": 16 + }, + { + "name": "Standard_D14", + "numberOfCores": 16, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 819200, + "memoryInMB": 114688, + "maxDataDiskCount": 32 + }, + { + "name": "Standard_DS1_v2", + "numberOfCores": 1, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 7168, + "memoryInMB": 3584, + "maxDataDiskCount": 2 + }, + { + "name": "Standard_DS2_v2", + "numberOfCores": 2, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 14336, + "memoryInMB": 7168, + "maxDataDiskCount": 4 + }, + { + "name": "Standard_DS3_v2", + "numberOfCores": 4, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 28672, + "memoryInMB": 14336, + "maxDataDiskCount": 8 + }, + { + "name": "Standard_DS4_v2", + "numberOfCores": 8, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 57344, + "memoryInMB": 28672, + "maxDataDiskCount": 16 + }, + { + "name": "Standard_DS5_v2", + "numberOfCores": 16, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 114688, + "memoryInMB": 57344, + "maxDataDiskCount": 32 + }, + { + "name": "Standard_DS11_v2", + "numberOfCores": 2, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 28672, + "memoryInMB": 14336, + "maxDataDiskCount": 4 + }, + { + "name": "Standard_DS12_v2", + "numberOfCores": 4, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 57344, + "memoryInMB": 28672, + "maxDataDiskCount": 8 + }, + { + "name": "Standard_DS13_v2", + "numberOfCores": 8, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 114688, + "memoryInMB": 57344, + "maxDataDiskCount": 16 + }, + { + "name": "Standard_DS14_v2", + "numberOfCores": 16, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 229376, + "memoryInMB": 114688, + "maxDataDiskCount": 32 + }, + { + "name": "Standard_DS15_v2", + "numberOfCores": 20, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 286720, + "memoryInMB": 143360, + "maxDataDiskCount": 40 + }, + { + "name": "Standard_F1s", + "numberOfCores": 1, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 4096, + "memoryInMB": 2048, + "maxDataDiskCount": 2 + }, + { + "name": "Standard_F2s", + "numberOfCores": 2, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 8192, + "memoryInMB": 4096, + "maxDataDiskCount": 4 + }, + { + "name": "Standard_F4s", + "numberOfCores": 4, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 16384, + "memoryInMB": 8192, + "maxDataDiskCount": 8 + }, + { + "name": "Standard_F8s", + "numberOfCores": 8, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 32768, + "memoryInMB": 16384, + "maxDataDiskCount": 16 + }, + { + "name": "Standard_F16s", + "numberOfCores": 16, + "osDiskSizeInMB": 1047552, + "resourceDiskSizeInMB": 65536, + "memoryInMB": 32768, + "maxDataDiskCount": 32 + } + ] +} \ No newline at end of file diff --git a/libcloud/test/compute/fixtures/azure_arm/create_myvm b/libcloud/test/compute/fixtures/azure_arm/create_myvm new file mode 100644 index 0000000000..05703849cd --- /dev/null +++ b/libcloud/test/compute/fixtures/azure_arm/create_myvm @@ -0,0 +1,59 @@ + +{ + "id":"/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/resourceGroups/myapp/providers/Microsoft.Compute/virtualMachines/user-brazi-40wudhabjn7g", + "type":"Microsoft.Compute/virtualMachines", + "properties":{ + "osProfile":{ + "adminUsername":"default_user_name", + "computerName":"user-brazi-40wudhabjn7g", + "linuxConfiguration":{ + "ssh":{ + "publicKeys":[ + { + "path":"/home/ubuntu/.ssh/authorized_keys", + "keyData":"" + } + ] + } + } + }, + "networkProfile":{ + "networkInterfaces":[ + { + "id": "/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/resourceGroups/myapp/providers/Microsoft.Network/networkInterfaces/user-brazi-40wudhabjn7g-nic", + "properties":{ + "primary":"True" + } + } + ] + }, + "storageProfile":{ + "imageReference":{ + "sku":"vnp360-single", + "publisher":"4psa", + "version":"3.6.0", + "offer":"voipnow" + }, + "osDisk":{ + "name":"myvm-os-disk", + "diskSizeGB":50, + "caching":"ReadWrite", + "createOption":"FromImage", + "osType":"Linux", + "vhd":{ + "uri":"test.uri" + } + }, + "dataDisks":[ + + ] + }, + "vmId":"test-vm-id", + "hardwareProfile":{ + "vmSize":"Standard_A0" + }, + "provisioningState":"Creating" + }, + "name":"user-brazi-40wudhabjn7g", + "location":"southbrazil" +} diff --git a/libcloud/test/compute/fixtures/azure_arm/vnp30_version_3 b/libcloud/test/compute/fixtures/azure_arm/vnp30_version_3 new file mode 100644 index 0000000000..70b577fc08 --- /dev/null +++ b/libcloud/test/compute/fixtures/azure_arm/vnp30_version_3 @@ -0,0 +1,16 @@ +{ + "properties": { + "plan": { + "publisher": "4psa", + "name": "vnp360-single", + "product": "voipnow" + }, + "osDiskImage": { + "operatingSystem": "Linux" + }, + "dataDiskImages": [] + }, + "location": "brazilsouth", + "name": "3.6.0", + "id": "/Subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/Providers/Microsoft.Compute/Locations/brazilsouth/Publishers/4psa/ArtifactTypes/VMImage/Offers/voipnow/Skus/vnp360-single/Versions/3.6.0" +} \ No newline at end of file diff --git a/libcloud/test/compute/fixtures/azure_arm/vnp30_versions.json b/libcloud/test/compute/fixtures/azure_arm/vnp30_versions.json new file mode 100644 index 0000000000..5f88479f4d --- /dev/null +++ b/libcloud/test/compute/fixtures/azure_arm/vnp30_versions.json @@ -0,0 +1,7 @@ +[ + { + "location": "brazilsouth", + "name": "3.6.0", + "id": "/Subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/Providers/Microsoft.Compute/Locations/brazilsouth/Publishers/4psa/ArtifactTypes/VMImage/Offers/voipnow/Skus/vnp360-single/Versions/3.6.0" + } +] \ No newline at end of file diff --git a/libcloud/test/compute/fixtures/azure_arm/voipnow_skus.json b/libcloud/test/compute/fixtures/azure_arm/voipnow_skus.json new file mode 100644 index 0000000000..4a58f44a66 --- /dev/null +++ b/libcloud/test/compute/fixtures/azure_arm/voipnow_skus.json @@ -0,0 +1,7 @@ +[ + { + "location": "brazilsouth", + "name": "vnp360-single", + "id": "/Subscriptions/Providers/Microsoft.Compute/Locations/brazilsouth/Publishers/4psa/ArtifactTypes/VMImage/Offers/voipnow/Skus/vnp360-single" + } +] diff --git a/libcloud/test/compute/test_azure_arm.py b/libcloud/test/compute/test_azure_arm.py new file mode 100644 index 0000000000..64fb74ae11 --- /dev/null +++ b/libcloud/test/compute/test_azure_arm.py @@ -0,0 +1,252 @@ +import os + +import libcloud.security +from libcloud.compute.drivers.azure_arm import AzureNetworkConfig +from libcloud.compute.providers import get_driver +from libcloud.compute.types import Provider, NodeState +from libcloud.test import LibcloudTestCase, MockHttp +from libcloud.test.file_fixtures import ComputeFileFixtures +from libcloud.utils.py3 import httplib + + +class AzureArmNodeDriverTests(LibcloudTestCase): + # required otherwise we get client side SSL verification + libcloud.security.VERIFY_SSL_CERT = False + + SUBSCRIPTION_ID = '3s42h548_4f8h_948h_3847_663h35u3905h' + KEY_FILE = os.path.join(os.path.dirname(__file__), 'fixtures/azure/libcloud.pem') + + def setUp(self): + Azure_Arm = get_driver(Provider.AZURE_ARM) + Azure_Arm.connectionCls.conn_classes = (None, AzureArmMockHttp) + self.driver = Azure_Arm(self.SUBSCRIPTION_ID, self.KEY_FILE) + + def test_locations_returned_successfully(self): + locations = self.driver.list_locations() + self.assertEqual(len(locations), 24) + location_names_result = list(a.country for a in locations) + location_names_expected = [ + 'East Asia', + 'Southeast Asia', + 'Central US', + 'East US', + 'East US 2', + 'West US', + 'North Central US', + 'South Central US', + 'North Europe', + 'West Europe', + 'Japan West', + 'Japan East', + 'Brazil South', + 'Australia East', + 'Australia Southeast', + 'South India', + 'Central India', + 'West India', + 'Canada Central', + 'Canada East', + 'UK South', + 'UK West', + 'West Central US', + 'West US 2' + ] + print(location_names_result) + + self.assertListEqual(location_names_result, location_names_expected) + + def test_list_nodes_with_ressouce_group_returned_successfully(self): + vmimages = self.driver.list_nodes('myapp') + self.assertEqual(len(vmimages), 1) + vmimage = vmimages[0] + self.assertEqual("myvm", vmimage.name) + self.assertEqual('/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/' + 'resourceGroups/myapp/providers/Microsoft.Compute/' + 'virtualMachines/myvm', vmimage.id) + self.assertEqual(NodeState.RUNNING, vmimage.state) + self.assertEqual(["1.1.1.1"], vmimage.public_ips) + self.assertEqual(["10.1.1.1"], vmimage.private_ips) + + def test_list_nodes_without_resource_group(self): + vmimages = self.driver.list_nodes() + self.assertEqual(len(vmimages), 1) + vmimage = vmimages[0] + self.assertEqual("myvm", vmimage.name) + self.assertEqual('/subscriptions/3s42h548-4f8h-948h-3847-663h35u3905h/' + 'resourceGroups/myapp/providers/Microsoft.Compute/' + 'virtualMachines/myvm', vmimage.id) + self.assertEqual(NodeState.RUNNING, vmimage.state) + self.assertEqual(["1.1.1.1"], vmimage.public_ips) + self.assertEqual(["10.1.1.1"], vmimage.private_ips) + + def test_list_nodes_with_wrong_resource_group(self): + self.assertRaises(AssertionError, self.driver.list_nodes, 'fakegroup') + + def test_list_images(self): + vmimages = self.driver.list_images(location='brazilsouth') + vmimage = vmimages[0] + self.assertEqual(vmimage.id, '4psa:voipnow:vnp360-single:Linux:3.6.0') + self.assertEqual(vmimage.name, '4psa voipnow vnp360-single Linux 3.6.0') + self.assertEqual(vmimage.location, 'brazilsouth') + + def test_list_sizes(self): + vmsizes = self.driver.list_sizes(location='brazilsouth') + vmsize = vmsizes[0] + self.assertEqual('Standard_A0', vmsize.name) + self.assertEqual(58, len(vmsizes)) + + + def test_create_node_and_deployment_one_node(self): + # Create a network config + virtual_networks = self.driver.ex_list_virtual_networks() + virtual_network = virtual_networks[0] + + locations = self.driver.list_locations() + loc = locations[12] + + vmimages = self.driver.list_images(location='brazilsouth') + vmimage = vmimages[0] + + subnets = virtual_network.snets + subnet = subnets[0] + + vmsizes = self.driver.list_sizes(location='brazilsouth') + vmsize = vmsizes[0] + + ex_network_config = AzureNetworkConfig(virtual_network, subnet, False) + + # Create the node with the above ex_network_config + node = self.driver.create_node('user-brazi-40wudhabjn7g', loc, vmsize, + ex_resource_group_name='myapp', + ex_storage_account_name="default_storage_acc_name", + ex_network_config=ex_network_config, + ex_admin_username="default_user_name", + ex_marketplace_image=vmimage) + + self.assertEqual('user-brazi-40wudhabjn7g', node.name) + +class AzureArmMockHttp(MockHttp): + + fixtures = ComputeFileFixtures('azure_arm') + + def _subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_locations(self, method, url, body, headers): + """ Requests the list of locations from microsoft azure""" + if method == "GET": + body = self.fixtures.load( + '_subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_locations.json') + + return httplib.OK, body, headers, httplib.responses[httplib.OK] + + def _subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_providers_Microsoft_Compute_virtualMachines(self, method, url, body, headers): + """ Request for the list of nodes of the subscriber """ + if method == "GET": + body = self.fixtures.load( + '_subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_providers_Microsoft_Compute_virtualmachines.json') + + return httplib.OK, body, headers, httplib.responses[httplib.OK] + + def _subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_resourceGroups_myapp_providers_Microsoft_Compute_virtualMachines(self, method, url, body, headers): + """ Requests list of nodes for the resource group """ + if method == "GET": + body = self.fixtures.load( + '_subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_resourceGroups_myapp_providers_Microsoft_Compute_virtualmachines.json') + + return httplib.OK, body, headers, httplib.responses[httplib.OK] + + def _subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_resourceGroups_fakegroup_providers_Microsoft_Compute_virtualMachines(self, method, url, body, headers): + """ Bad request for nodes in a resource group that not exist """ + if method == "GET": + body = self.fixtures.load( + '_subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_resourceGroups_fakegroup_providers_Microsoft_Compute_virtualmachines.json') + + return httplib.NOT_FOUND, body, headers, httplib.responses[ + httplib.NOT_FOUND] + + def _subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_resourceGroups_myapp_providers_Microsoft_Network_networkInterfaces_user_brazi_40wudhabjn7g_nic(self, method, url, body, headers): + """ A request for the network interface card information about vm1""" + if method == "GET" or method == "PUT": + body = self.fixtures.load( + '_subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_resourceGroups_myapp_providers_Microsoft_Network_networkInterfaces_user_brazi_40wudhabjn7g_nic.json' + ) + + return httplib.OK, body, headers, httplib.responses[httplib.OK] + + def _subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_resourceGroups_myapp_providers_Microsoft_Network_publicIPAddresses_user_brazi_40wudhabjn7g_pip(self, method, url, body, headers): + """ A request for the public ip information included in the nic above""" + if method == "GET": + body = self.fixtures.load( + '_subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_resourceGroups_myapp_providers_Microsoft_Network_publicIPAddresses_user_brazi_40wudhabjn7g_pip.json' + ) + + return httplib.OK, body, headers, httplib.responses[httplib.OK] + + def _subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_providers_Microsoft_Compute_locations_brazilsouth_publishers(self, method, url, body, headers): + """ A request for the publishers in south brazil (only one is returned out of many possible due to tests """ + if method == "GET": + body = self.fixtures.load( + 'brazil_south_publishers' + ) + + return httplib.OK, body, headers, httplib.responses[httplib.OK] + + def _Subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_Providers_Microsoft_Compute_Locations_brazilsouth_Publishers_4psa_artifacttypes_vmimage_offers(self, method, url, body, headers): + """" Request for offers from the publisher 4psa""" + if method == "GET": + body = self.fixtures.load( + '4psa_offers' + ) + + return httplib.OK, body, headers, httplib.responses[httplib.OK] + + def _Subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_Providers_Microsoft_Compute_Locations_brazilsouth_Publishers_4psa_ArtifactTypes_VMImage_Offers_voipnow_skus(self, method, url, body, headers): + """ Request for skus for voipnow""" + if method == "GET": + body = self.fixtures.load( + 'voipnow_skus.json' + ) + + return httplib.OK, body, headers, httplib.responses[httplib.OK] + + def _Subscriptions_Providers_Microsoft_Compute_Locations_brazilsouth_Publishers_4psa_ArtifactTypes_VMImage_Offers_voipnow_Skus_vnp360_single_versions(self, method, url, body, headers): + """" Request for versions of vnp30 sku""" + if method == "GET": + body = self.fixtures.load( + 'vnp30_versions.json' + ) + + return httplib.OK, body, headers, httplib.responses[httplib.OK] + + def _Subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_Providers_Microsoft_Compute_Locations_brazilsouth_Publishers_4psa_ArtifactTypes_VMImage_Offers_voipnow_Skus_vnp360_single_Versions_3_6_0(self, method, url, body, headers): + """" Request for version 3.6.0""" + if method == "GET": + body = self.fixtures.load( + 'vnp30_version_3' + ) + + return httplib.OK, body, headers, httplib.responses[httplib.OK] + + def _subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_providers_Microsoft_Compute_locations_brazilsouth_vmSizes(self, method, url, body, headers): + """ Request for all the vmSizes in brazil south""" + if method == "GET": + body = self.fixtures.load( + 'brazil_south_vm_sizes' + ) + + return httplib.OK, body, headers, httplib.responses[httplib.OK] + + def _subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_providers_Microsoft_Network_virtualnetworks(self, method, url, body, headers): + """ Request for virtual networks""" + if method == "GET": + body = self.fixtures.load( + 'brazil_south_virtual_networks' + ) + + return httplib.OK, body, headers, httplib.responses[httplib.OK] + + def _subscriptions_3s42h548_4f8h_948h_3847_663h35u3905h_resourceGroups_myapp_providers_Microsoft_Compute_virtualMachines_user_brazi_40wudhabjn7g(self, method, url, body, headers): + if method == "PUT": + body = self.fixtures.load( + 'create_myvm' + ) + + return httplib.OK, body, headers, httplib.responses[httplib.OK] \ No newline at end of file