# IM - Infrastructure Manager # Copyright (C) 2011 - GRyCAP - Universitat Politecnica de Valencia # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. import base64 import requests import time import os import uuid import tempfile from IM.xmlobject import XMLObject try: from urlparse import urlparse except ImportError: from urllib.parse import urlparse from IM.VirtualMachine import VirtualMachine from .CloudConnector import CloudConnector from radl.radl import UserPassCredential, Feature from IM.config import Config # Set of classes to parse the output of the REST API class Endpoint(XMLObject): values = ['Name', 'Vip', 'PublicPort', 'LocalPort', 'Protocol'] class InstanceEndpoints(XMLObject): tuples_lists = {'InstanceEndpoint': Endpoint} class DataVirtualHardDisk(XMLObject): values = ['DiskName', 'Lun'] class DataVirtualHardDisks(XMLObject): tuples_lists = {'DataVirtualHardDisk': DataVirtualHardDisk} class Role(XMLObject): values = ['RoleName', 'RoleType'] tuples = {'DataVirtualHardDisks': DataVirtualHardDisks} class RoleList(XMLObject): tuples_lists = {'Role': Role} class RoleInstance(XMLObject): values = ['RoleName', 'InstanceStatus', 'InstanceSize', 'InstanceName', 'IpAddress', 'PowerState'] tuples = {'InstanceEndpoints': InstanceEndpoints} class RoleInstanceList(XMLObject): tuples_lists = {'RoleInstance': RoleInstance} class Deployment(XMLObject): tuples = {'RoleInstanceList': RoleInstanceList, 'RoleList': RoleList} values = ['Name', 'Status', 'Url'] class StorageServiceProperties(XMLObject): values = ['Description', 'Location', 'Label', 'Status', 'GeoReplicationEnabled', 'CreationTime', 'GeoPrimaryRegion', 'GeoSecondaryRegion'] class StorageService(XMLObject): values = ['Url', 'ServiceName'] tuples = {'StorageServiceProperties': StorageServiceProperties} class Error(XMLObject): values = ['Code', 'Message'] class Operation(XMLObject): values = ['ID', 'Status', 'HttpStatusCode'] tuples = {'Error': Error} class RoleSize(XMLObject): values = ['Name', 'Label', 'Cores', 'MemoryInMb', 'SupportedByVirtualMachines', 'MaxDataDiskCount', 'VirtualMachineResourceDiskSizeInMb'] numeric = ['Cores', 'MemoryInMb', 'MaxDataDiskCount', 'VirtualMachineResourceDiskSizeInMb'] class RoleSizes(XMLObject): tuples_lists = {'RoleSize': RoleSize} class AzureClassicCloudConnector(CloudConnector): """ Cloud Launcher to the Azure platform Using the Service Management REST API Reference: https://msdn.microsoft.com/en-us/library/azure/ee460799.aspx """ type = "AzureClassic" """str with the name of the provider.""" INSTANCE_TYPE = 'ExtraSmall' """Default instance type.""" AZURE_SERVER = "management.core.windows.net" """Address of the server with the Service Management REST API.""" AZURE_PORT = 443 """Port of the server with the Service Management REST API.""" DEFAULT_LOCATION = "North Europe" """Default location to use""" ROLE_NAME = "IMVMRole" """Name of the Role""" DEFAULT_USER = 'azureuser' """ default user to SSH access the VM """ DEPLOY_STATE_MAP = { 'Running': VirtualMachine.RUNNING, 'Suspended': VirtualMachine.STOPPED, 'SuspendedTransitioning': VirtualMachine.STOPPED, 'RunningTransitioning': VirtualMachine.RUNNING, 'Starting': VirtualMachine.PENDING, 'Suspending': VirtualMachine.STOPPED, 'Deploying': VirtualMachine.PENDING, 'Deleting': VirtualMachine.OFF, } ROLE_STATE_MAP = { 'Starting': VirtualMachine.PENDING, 'Started': VirtualMachine.RUNNING, 'Stopping': VirtualMachine.STOPPED, 'Stopped': VirtualMachine.STOPPED, 'Unknown': VirtualMachine.UNKNOWN } def __init__(self, cloud_info, inf): self.instance_type_list = None CloudConnector.__init__(self, cloud_info, inf) def create_request(self, method, url, auth_data, headers=None, body=None): auths = auth_data.getAuthInfo(AzureClassicCloudConnector.type, self.cloud.server) if not auths: self.log_error("No correct auth data has been specified to Azure.") return None else: auth = auths[0] subscription_id = self.get_subscription_id(auth_data) url = "https://%s:%d/%s%s" % (self.AZURE_SERVER, self.AZURE_PORT, subscription_id, url) cert = self.get_user_cert_data(auth) resp = requests.request(method, url, verify=self.verify_ssl, cert=cert, headers=headers, data=body) return resp def concrete_system(self, radl_system, str_url, auth_data): url = urlparse(str_url) protocol = url[0] if protocol == "azr": instance_type = self.get_instance_type(radl_system, auth_data) if not instance_type: self.log_error("Error generating the RADL of the VM, no instance type available for the requirements.") self.log_debug(radl_system) return None res_system = radl_system.clone() self.update_system_info_from_instance(res_system, instance_type) username = res_system.getValue('disk.0.os.credentials.username') if not username: res_system.setValue('disk.0.os.credentials.username', self.DEFAULT_USER) res_system.updateNewCredentialValues() return res_system else: return None @staticmethod def gen_input_endpoints(radl): """ Gen the InputEndpoints part of the XML of the VM creation using the outports field of the RADL network """ # SSH port must be allways available res = """ <InputEndpoints> <InputEndpoint> <LocalPort>3389</LocalPort> <Name>RDP</Name> <Port>3389</Port> <Protocol>TCP</Protocol> </InputEndpoint> <InputEndpoint> <LocalPort>5986</LocalPort> <Name>WinRM</Name> <Port>5986</Port> <Protocol>TCP</Protocol> </InputEndpoint> <InputEndpoint> <LocalPort>22</LocalPort> <Name>SSH</Name> <Port>22</Port> <Protocol>TCP</Protocol> </InputEndpoint>""" public_net = None for net in radl.networks: if net.isPublic(): public_net = net if public_net: outports = public_net.getOutPorts() if outports: for outport in outports: protocol = outport.get_protocol().upper() if outport.is_range(): for port in range(outport.get_port_init(), outport.get_port_end() + 1): res += """ <InputEndpoint> <LocalPort>%d</LocalPort> <Name>Port %d</Name> <Port>%d</Port> <Protocol>%s</Protocol> </InputEndpoint>""" % (port, port, port, protocol) else: local_port = outport.get_local_port() remote_port = outport.get_remote_port() if local_port != 22 and local_port != 5986 and local_port != 3389: res += """ <InputEndpoint> <LocalPort>%d</LocalPort> <Name>Port %d</Name> <Port>%d</Port> <Protocol>%s</Protocol> </InputEndpoint>""" % (local_port, local_port, remote_port, protocol) res += "\n </InputEndpoints>" return res @staticmethod def gen_configuration_set(hostname, system): """ Gen the ConfigurationSet part of the XML of the VM creation """ # Allways use the new credentials system.updateNewCredentialValues() credentials = system.getCredentials() if system.getValue("disk.0.os.name") == "windows": ConfigurationSet = ''' <ConfigurationSet i:type="WindowsProvisioningConfigurationSet"> <ConfigurationSetType>WindowsProvisioningConfiguration</ConfigurationSetType> <ComputerName>%s</ComputerName> <AdminPassword>%s</AdminPassword> <AdminUsername>%s</AdminUsername> <EnableAutomaticUpdates>true</EnableAutomaticUpdates> <ResetPasswordOnFirstLogon>false</ResetPasswordOnFirstLogon> </ConfigurationSet>''' % (hostname, credentials.password, credentials.username) else: if isinstance(credentials, UserPassCredential): ConfigurationSet = ''' <ConfigurationSet i:type="LinuxProvisioningConfigurationSet"> <ConfigurationSetType>LinuxProvisioningConfiguration</ConfigurationSetType> <HostName>%s</HostName> <UserName>%s</UserName> <UserPassword>%s</UserPassword> <DisableSshPasswordAuthentication>false</DisableSshPasswordAuthentication> </ConfigurationSet>''' % (hostname, credentials.username, credentials.password) else: ConfigurationSet = ''' <ConfigurationSet i:type="LinuxProvisioningConfigurationSet"> <ConfigurationSetType>LinuxProvisioningConfiguration</ConfigurationSetType> <HostName>%s</HostName> <UserName>%s</UserName> <UserPassword>%s</UserPassword> <DisableSshPasswordAuthentication>true</DisableSshPasswordAuthentication> <SSH> <PublicKeys> <PublicKey> <FingerPrint>%s</FingerPrint> <Path>/home/%s/.ssh/authorized_keys</Path> </PublicKey> </PublicKeys> <KeyPairs> <KeyPair> <FingerPrint>%s</FinguerPrint> <Path>/home/%s/.ssh/id_rsa</Path> </KeyPair> </KeyPairs> </SSH> </ConfigurationSet>''' % (hostname, credentials.username, "Pass+Not-Used1", credentials.public_key, credentials.username, credentials.public_key, credentials.username) return ConfigurationSet @staticmethod def gen_data_disks(system, storage_account): """ Gen the DataVirtualHardDisks part of the XML of the VM creation """ disks = "" cont = 1 while system.getValue("disk." + str(cont) + ".size") and system.getValue("disk." + str(cont) + ".device"): disk_size = system.getFeature( "disk." + str(cont) + ".size").getValue('G') disk_name = "datadisk-1-" + str(uuid.uuid1()) disks += ''' <DataVirtualHardDisks> <DataVirtualHardDisk> <HostCaching>ReadWrite</HostCaching> <Lun>%d</Lun> <LogicalDiskSizeInGB>%d</LogicalDiskSizeInGB> <MediaLink>https://%s.blob.core.windows.net/vhds/%s.vhd</MediaLink> </DataVirtualHardDisk> </DataVirtualHardDisks> ''' % (cont, int(disk_size), storage_account, disk_name) cont += 1 return disks def get_azure_vm_create_xml(self, vm, storage_account, radl, num, auth_data): """ Generate the XML to create the VM """ system = radl.systems[0] name = system.getValue("instance_name") if not name: name = system.getValue("disk.0.image.name") if not name: name = "userimage" + str(num) url = urlparse(system.getValue("disk.0.image.url")) label = name + " IM created VM" (hostname, _) = vm.getRequestedName( default_hostname=Config.DEFAULT_VM_NAME, default_domain=Config.DEFAULT_DOMAIN) if not hostname: hostname = "AzureNode" + str(num) SourceImageName = url[1] MediaLink = "https://%s.blob.core.windows.net/vhds/%s.vhd" % (storage_account, vm.id) instance_type = self.get_instance_type(system, auth_data) DataVirtualHardDisks = self.gen_data_disks(system, storage_account) ConfigurationSet = self.gen_configuration_set(hostname, system) InputEndpoints = self.gen_input_endpoints(radl) res = ''' <Deployment xmlns="http://schemas.microsoft.com/windowsazure" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <Name>%s</Name> <DeploymentSlot>Production</DeploymentSlot> <Label>%s</Label> <RoleList> <Role i:type="PersistentVMRole"> <RoleName>%s</RoleName> <OsVersion i:nil="true"/> <RoleType>PersistentVMRole</RoleType> <ConfigurationSets> %s <ConfigurationSet i:type="NetworkConfigurationSet"> <ConfigurationSetType>NetworkConfiguration</ConfigurationSetType> %s </ConfigurationSet> </ConfigurationSets> %s <OSVirtualHardDisk> <MediaLink>%s</MediaLink> <SourceImageName>%s</SourceImageName> </OSVirtualHardDisk> <RoleSize>%s</RoleSize> </Role> </RoleList> </Deployment> ''' % (vm.id, label, self.ROLE_NAME, ConfigurationSet, InputEndpoints, DataVirtualHardDisks, MediaLink, SourceImageName, instance_type.Name) self.log_debug("Azure VM Create XML: " + res) return res def get_subscription_id(self, auth_data): auths = auth_data.getAuthInfo(self.type) if not auths: raise Exception("No auth data has been specified to Azure.") else: auth = auths[0] if 'subscription_id' in auth: subscription_id = auth['subscription_id'] else: raise Exception( "No correct auth data has been specified to Azure: subscription_id, public_key and private_key.") return subscription_id def get_user_cert_data(self, auth): """ Get the Azure private_key and public_key files from the auth data """ if 'public_key' in auth and 'private_key' in auth: certificate = auth['public_key'] fd, cert_file = tempfile.mkstemp() os.write(fd, certificate.encode()) os.close(fd) os.chmod(cert_file, 0o644) private_key = auth['private_key'] fd, key_file = tempfile.mkstemp() os.write(fd, private_key.encode()) os.close(fd) os.chmod(key_file, 0o600) return (cert_file, key_file) else: self.log_error( "No correct auth data has been specified to Azure: subscription_id, public_key and private_key.") raise Exception( "No correct auth data has been specified to Azure: subscription_id, public_key and private_key.") def create_service(self, auth_data, region): """ Create a Azure Cloud Service and return the name """ service_name = "IM-" + str(uuid.uuid1()) self.log_info("Create the service " + service_name + " in region: " + region) try: uri = "/services/hostedservices" service_create_xml = ''' <CreateHostedService xmlns="http://schemas.microsoft.com/windowsazure"> <ServiceName>%s</ServiceName> <Label>%s</Label> <Description>Service %s created by the IM</Description> <Location>%s</Location> </CreateHostedService> ''' % (service_name, base64.b64encode(service_name.encode()), service_name, region) headers = {'x-ms-version': '2013-03-01', 'Content-Type': 'application/xml'} resp = self.create_request('POST', uri, auth_data, headers, service_create_xml) except Exception as ex: self.log_exception("Error creating the service") return None, "Error creating the service" + str(ex) if resp.status_code != 201: self.log_error( "Error creating the service: Error code: " + str(resp.status_code) + ". Msg: " + resp.text) return None, "Error creating the service: Error code: " + str(resp.status_code) + ". Msg: " + resp.text return service_name, None def delete_service(self, service_name, auth_data): """ Delete the Azure Cloud Service with name "service_name" """ try: uri = "/services/hostedservices/%s?comp=media" % service_name headers = {'x-ms-version': '2013-08-01'} resp = self.create_request('DELETE', uri, auth_data, headers) except Exception as ex: self.log_exception("Error deleting the service") return (False, "Error deleting the service: " + str(ex)) if resp.status_code != 202: self.log_error( "Error deleting the service: Error Code " + str(resp.status_code) + ". Msg: " + resp.text) return (False, "Error deleting the service: Error Code " + str(resp.status_code) + ". Msg: " + resp.text) request_id = resp.headers['x-ms-request-id'] # Call to GET OPERATION STATUS until "Succeeded" success = self.wait_operation_status(request_id, auth_data) if success: return (True, "") else: return (False, "Error waiting the VM termination") def wait_operation_status(self, request_id, auth_data, delay=2, timeout=90): """ Wait for the operation "request_id" to finish in the specified state """ self.log_info("Wait the operation: " + request_id + " to finish.") wait = 0 status_str = "InProgress" while status_str == "InProgress" and wait < timeout: time.sleep(delay) wait += delay try: uri = "/operations/%s" % request_id headers = {'x-ms-version': '2013-03-01'} resp = self.create_request('GET', uri, auth_data, headers) if resp.status_code == 200: output = Operation(resp.text) status_str = output.Status # InProgress|Succeeded|Failed self.log_info("Operation string state: " + status_str) else: self.log_error( "Error waiting operation to finish: Code %d. Msg: %s." % (resp.status_code, resp.text)) return False except Exception: self.log_exception( "Error getting the operation state: " + request_id) if status_str == "Succeeded": return True else: self.log_error("Error waiting the operation: %s, %s, %s" % (output.HttpStatusCode, output.Error.Code, output.Error.Message)) return False def get_storage_name(self, subscription_id, region=None): if not region: region = self.DEFAULT_LOCATION res = region.replace(" ", "").lower() + subscription_id.replace("-", "") return res[:24] def create_storage_account(self, storage_account, auth_data, region, timeout=120): """ Create an storage account with the name specified in "storage_account" """ self.log_info("Creating the storage account " + storage_account) try: uri = "/services/storageservices" storage_create_xml = ''' <CreateStorageServiceInput xmlns="http://schemas.microsoft.com/windowsazure"> <ServiceName>%s</ServiceName> <Description>Storage %s created by the IM</Description> <Label>%s</Label> <Location>%s</Location> <GeoReplicationEnabled>false</GeoReplicationEnabled> <ExtendedProperties> <ExtendedProperty> <Name>AccountCreatedBy</Name> <Value>RestAPI</Value> </ExtendedProperty> </ExtendedProperties> </CreateStorageServiceInput> ''' % (storage_account, storage_account, base64.b64encode(storage_account), region) headers = {'x-ms-version': '2013-03-01', 'Content-Type': 'application/xml'} resp = self.create_request('POST', uri, auth_data, headers, storage_create_xml) except Exception as ex: self.log_exception("Error creating the storage account") return None, "Error creating the storage account" + str(ex) if resp.status_code != 202: self.log_error( "Error creating the storage account: Error code " + str(resp.status_code) + ". Msg: " + resp.text) return None, "Error code " + str(resp.status_code) + ". Msg: " + resp.text request_id = resp.headers['x-ms-request-id'] # Call to GET OPERATION STATUS until 200 (OK) success = self.wait_operation_status(request_id, auth_data) # Wait the storage to be "Created" status = None delay = 2 wait = 0 while status != "Created" and wait < timeout: storage = self.get_storage_account(storage_account, auth_data) if storage: status = storage.Status if status != "Created": time.sleep(delay) wait += delay if success: return storage_account, None else: self.log_error( "Error waiting the creation of the storage account") self.delete_storage_account(storage_account, auth_data) return None, "Error waiting the creation of the storage account" def delete_storage_account(self, storage_account, auth_data): """ Delete an storage account with the name specified in "storage_account" """ try: uri = "/services/storageservices/%s" % storage_account headers = {'x-ms-version': '2013-03-01'} resp = self.create_request('DELETE', uri, auth_data, headers) except Exception: self.log_exception("Error deleting the storage account") return False if resp.status_code != 200: self.log_error( "Error deleting the storage account: Error Code " + str(resp.status_code) + ". Msg: " + resp.text) return False return True def get_storage_account(self, storage_account, auth_data): """ Get the information about the Storage Account named "storage_account" or None if it does not exist """ try: uri = "/services/storageservices/%s" % storage_account headers = {'x-ms-version': '2013-03-01'} resp = self.create_request('GET', uri, auth_data, headers) if resp.status_code == 200: storage_info = StorageService(resp.text) return storage_info.StorageServiceProperties elif resp.status_code == 404: self.log_info("Storage " + storage_account + " does not exist") return None else: self.log_warn( "Error checking the storage account " + storage_account + ". Msg: " + resp.text) return None except Exception: self.log_exception("Error checking the storage account") return None def launch(self, inf, radl, requested_radl, num_vm, auth_data): service_name = None region = self.DEFAULT_LOCATION if radl.systems[0].getValue('availability_zone'): region = radl.systems[0].getValue('availability_zone') else: radl.systems[0].setValue('availability_zone', region) res = [] i = 0 while i < num_vm: try: subscription_id = self.get_subscription_id(auth_data) # Create storage account storage_account_name = self.get_storage_name(subscription_id, region) storage_account = self.get_storage_account(storage_account_name, auth_data) if not storage_account: storage_account_name, error_msg = self.create_storage_account(storage_account_name, auth_data, region) if not storage_account_name: res.append((False, error_msg)) break else: # if the user has specified the region if radl.systems[0].getValue('availability_zone'): # Check that the region of the storage account is the # same of the service if region != storage_account.GeoPrimaryRegion: res.append((False, ("Error creating the service. The specified " "region is not the same of the service."))) break else: # Otherwise use the storage account region region = storage_account.GeoPrimaryRegion # and the service service_name, error_msg = self.create_service(auth_data, region) if service_name is None: res.append((False, error_msg)) break self.log_info("Creating the VM with id: " + service_name) # Create the VM to get the nodename vm = VirtualMachine(inf, service_name, self.cloud, radl, requested_radl, self) vm.destroy = True inf.add_vm(vm) vm.info.systems[0].setValue('instance_id', str(vm.id)) # Generate the XML to create the VM vm_create_xml = self.get_azure_vm_create_xml( vm, storage_account_name, radl, i, auth_data) if vm_create_xml is None: self.delete_service(service_name, auth_data) res.append((False, "Incorrect image or auth data")) break uri = "/services/hostedservices/%s/deployments" % service_name headers = {'x-ms-version': '2013-03-01', 'Content-Type': 'application/xml'} resp = self.create_request('POST', uri, auth_data, headers, vm_create_xml) if resp.status_code != 202: self.delete_service(service_name, auth_data) self.log_error( "Error creating the VM: Error Code " + str(resp.status_code) + ". Msg: " + resp.text) res.append((False, "Error creating the VM: Error Code " + str(resp.status_code) + ". Msg: " + resp.text)) else: vm.destroy = False # Call the GET OPERATION STATUS until sea 200 (OK) request_id = resp.headers['x-ms-request-id'] success = self.wait_operation_status(request_id, auth_data) if success: res.append((True, vm)) else: self.log_exception("Error waiting the VM creation") self.delete_service(service_name, auth_data) res.append((False, "Error waiting the VM creation")) except Exception as ex: self.log_exception("Error creating the VM") if service_name: self.delete_service(service_name, auth_data) res.append((False, "Error creating the VM: " + str(ex))) i += 1 return res def get_instance_type(self, system, auth_data): """ Get the name of the instance type to launch to EC2 Arguments: - radl(str): RADL document with the requirements of the VM to get the instance type Returns: a str with the name of the instance type to launch to EC2 """ instance_type_name = system.getValue('instance_type') (cpu, cpu_op, memory, memory_op, disk_free, disk_free_op) = self.get_instance_selectors(system) instace_types = self.get_all_instance_types(auth_data) res = None for instace_type in instace_types: # get the instance type with the lowest Memory if res is None or (instace_type.MemoryInMb <= res.MemoryInMb): comparison = cpu_op(instace_type.Cores, cpu) comparison = comparison and memory_op(instace_type.MemoryInMb, memory) comparison = comparison and disk_free_op(instace_type.VirtualMachineResourceDiskSizeInMb, disk_free) if comparison: if not instance_type_name or instace_type.Name == instance_type_name: res = instace_type if res is None: self.get_instance_type_by_name(self.INSTANCE_TYPE, auth_data) else: return res def updateVMInfo(self, vm, auth_data): self.log_info("Get the VM info with the id: " + vm.id) service_name = vm.id try: uri = "/services/hostedservices/%s/deployments/%s" % (service_name, service_name) headers = {'x-ms-version': '2014-02-01'} resp = self.create_request('GET', uri, auth_data, headers) except Exception as ex: self.log_exception("Error getting the VM info: " + vm.id) return (False, "Error getting the VM info: " + vm.id + ". " + str(ex)) if resp.status_code != 200: self.log_error("Error getting the VM info: " + vm.id + ". Error Code: " + str(resp.status_code) + ". Msg: " + resp.text) return (False, "Error getting the VM info: " + vm.id + ". Error Code: " + str(resp.status_code) + ". Msg: " + resp.text) else: self.log_info("VM info: " + vm.id + " obtained.") self.log_info(resp.text) vm_info = Deployment(resp.text) vm.state = self.get_vm_state(vm_info) self.log_info("The VM state is: " + vm.state) instance_type = self.get_instance_type_by_name( vm_info.RoleInstanceList.RoleInstance[0].InstanceSize, auth_data) self.update_system_info_from_instance( vm.info.systems[0], instance_type) # Update IP info self.setIPs(vm, vm_info) return (True, vm) def get_vm_state(self, vm_info): """ Return the state of the VM using the vm info in format "Deployment" """ try: # If the deploy is running check the state of the RoleInstance if vm_info.Status == "Running": return self.ROLE_STATE_MAP.get(vm_info.RoleInstanceList.RoleInstance[0].PowerState, VirtualMachine.UNKNOWN) else: return self.DEPLOY_STATE_MAP.get(vm_info.Status, VirtualMachine.UNKNOWN) except Exception: return self.DEPLOY_STATE_MAP.get(vm_info.Status, VirtualMachine.UNKNOWN) def setIPs(self, vm, vm_info): """ Set the information about the IPs of the VM """ private_ips = [] public_ips = [] try: role_instance = vm_info.RoleInstanceList.RoleInstance[0] except Exception as ex: self.log_debug("%s" % str(ex)) return try: if role_instance.IpAddress: private_ips.append(role_instance.IpAddress) except Exception as ex: self.log_debug("%s" % str(ex)) try: public_ips.append( role_instance.InstanceEndpoints.InstanceEndpoint[0].Vip) except Exception as ex: self.log_debug("%s" % str(ex)) vm.setIps(public_ips, private_ips) def finalize(self, vm, last, auth_data): if not vm.id: self.log_warn("No VM ID. Ignoring") return True, "No VM ID. Ignoring" self.log_info("Terminate VM: %s" % vm.id) # Delete the service res = self.delete_service(vm.id, auth_data) return res def call_role_operation(self, op, vm, auth_data): """ Call to the specified operation "op" to a Role """ service_name = vm.id try: uri = "/services/hostedservices/%s/deployments/%s/roleinstances/%s/Operations" % ( service_name, service_name, self.ROLE_NAME) headers = {'x-ms-version': '2013-06-01', 'Content-Type': 'application/xml'} resp = self.create_request('POST', uri, auth_data, headers, op) except Exception as ex: self.log_exception("Error calling role operation") return (False, "Error calling role operation: " + str(ex)) if resp.status_code != 202: self.log_error( "Error calling role operation: Error Code " + str(resp.status_code) + ". Msg: " + resp.text) return (False, "Error calling role operation: Error Code " + str(resp.status_code) + ". Msg: " + resp.text) request_id = resp.headers['x-ms-request-id'] # Call to GET OPERATION STATUS until "Succeded" success = self.wait_operation_status( request_id, auth_data, delay=4, timeout=240) if success: return (True, "") else: return (False, "Error waiting the VM role operation") return (True, "") def stop(self, vm, auth_data): self.log_info("Stop VM: " + vm.id) op = """<ShutdownRoleOperation xmlns="http://schemas.microsoft.com/windowsazure" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <OperationType>ShutdownRoleOperation</OperationType> <PostShutdownAction>StoppedDeallocated</PostShutdownAction> </ShutdownRoleOperation>""" return self.call_role_operation(op, vm, auth_data) def start(self, vm, auth_data): self.log_info("Start VM: " + vm.id) op = """<StartRoleOperation xmlns="http://schemas.microsoft.com/windowsazure" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <OperationType>StartRoleOperation</OperationType> </StartRoleOperation>""" return self.call_role_operation(op, vm, auth_data) def get_all_instance_types(self, auth_data): if self.instance_type_list: return self.instance_type_list else: try: uri = "/rolesizes" headers = {'x-ms-version': '2013-08-01'} resp = self.create_request('GET', uri, auth_data, headers) except Exception: self.log_exception("Error getting Role Sizes") return [] if resp.status_code != 200: self.log_error( "Error getting Role Sizes. Error Code: " + str(resp.status_code) + ". Msg: " + resp.text) return [] else: self.log_info("Role List obtained.") role_sizes = RoleSizes(resp.text) res = [] for role_size in role_sizes.RoleSize: if role_size.SupportedByVirtualMachines == "true": res.append(role_size) self.instance_type_list = res return res def get_instance_type_by_name(self, name, auth_data): """ Get the Azure instance type with the specified name Returns: an :py:class:`InstanceTypeInfo` or None if the type is not found """ for inst_type in self.get_all_instance_types(auth_data): if inst_type.Name == name: return inst_type return None def alterVM(self, vm, radl, auth_data): # https://msdn.microsoft.com/en-us/library/azure/jj157187.aspx service_name = vm.id system = radl.systems[0] instance_type = self.get_instance_type(system, auth_data) if not instance_type: return (False, "Error calling update operation: No instance type found for radl: " + str(radl)) try: uri = "/services/hostedservices/%s/deployments/%s/roles/%s" % ( service_name, service_name, self.ROLE_NAME) body = ''' <PersistentVMRole xmlns="http://schemas.microsoft.com/windowsazure" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <RoleSize>%s</RoleSize> </PersistentVMRole> ''' % (instance_type.Name) headers = {'x-ms-version': '2013-11-01', 'Content-Type': 'application/xml'} resp = self.create_request('PUT', uri, auth_data, headers, body) except Exception as ex: self.log_exception("Error calling update operation") return (False, "Error calling update operation: " + str(ex)) if resp.status_code != 202: self.log_error( "Error update role operation: Error Code " + str(resp.status_code) + ". Msg: " + resp.text) return (False, "Error update role operation: Error Code " + str(resp.status_code) + ". Msg: " + resp.text) request_id = resp.headers['x-ms-request-id'] # Call to GET OPERATION STATUS until 200 (OK) success = self.wait_operation_status(request_id, auth_data) if success: self.update_system_info_from_instance( vm.info.systems[0], instance_type) return (True, "") else: return (False, "Error waiting the VM update operation") return (True, "") def update_system_info_from_instance(self, system, instance_type): """ Update the features of the system with the information of the instance_type """ if not instance_type: self.log_warn("No instance type provided. Not updating VM info.") return system.addFeature(Feature("cpu.count", "=", instance_type.Cores), conflict="other", missing="other") system.addFeature(Feature("memory.size", "=", instance_type.MemoryInMb, 'M'), conflict="other", missing="other") system.addFeature(Feature("disks.free_size", "=", instance_type.VirtualMachineResourceDiskSizeInMb, 'M'), conflict="other", missing="other") system.addFeature(Feature("instance_type", "=", instance_type.Name), conflict="other", missing="other")