def delete_machine(self, instance_ids, deployment_name=None, cleanup_service=None, **kwargs): sms = ServiceManagementService(self.profile.username, self.cert_path) for inst in instance_ids: try: sms.delete_deployment(service_name=inst, deployment_name=deployment_name) if cleanup_service: sms.delete_hosted_service(service_name=inst) except Exception, ex: self.log.exception(ex) return self.FAIL
class AffinityGroupManagementServiceTest(AzureTestCase): def setUp(self): self.sms = ServiceManagementService(credentials.getSubscriptionId(), credentials.getManagementCertFile()) self.sms.set_proxy(credentials.getProxyHost(), credentials.getProxyPort(), credentials.getProxyUser(), credentials.getProxyPassword()) self.affinity_group_name = getUniqueNameBasedOnCurrentTime('utaffgrp') self.hosted_service_name = None self.storage_account_name = None def tearDown(self): try: if self.hosted_service_name is not None: self.sms.delete_hosted_service(self.hosted_service_name) except: pass try: if self.storage_account_name is not None: self.sms.delete_storage_account(self.storage_account_name) except: pass try: self.sms.delete_affinity_group(self.affinity_group_name) except: pass #--Helpers----------------------------------------------------------------- def _create_affinity_group(self, name): result = self.sms.create_affinity_group(name, 'tstmgmtaffgrp', 'West US', 'tstmgmt affinity group') self.assertIsNone(result) def _affinity_group_exists(self, name): try: props = self.sms.get_affinity_group_properties(name) return props is not None except: return False #--Test cases for affinity groups ------------------------------------ def test_list_affinity_groups(self): # Arrange self._create_affinity_group(self.affinity_group_name) # Act result = self.sms.list_affinity_groups() # Assert self.assertIsNotNone(result) self.assertTrue(len(result) > 0) group = None for temp in result: if temp.name == self.affinity_group_name: group = temp break self.assertIsNotNone(group) self.assertIsNotNone(group.name) self.assertIsNotNone(group.label) self.assertIsNotNone(group.description) self.assertIsNotNone(group.location) self.assertIsNotNone(group.capabilities) self.assertTrue(len(group.capabilities) > 0) def test_get_affinity_group_properties(self): # Arrange self.hosted_service_name = getUniqueNameBasedOnCurrentTime('utsvc') self.storage_account_name = getUniqueNameBasedOnCurrentTime('utstorage') self._create_affinity_group(self.affinity_group_name) self.sms.create_hosted_service(self.hosted_service_name, 'affgrptestlabel', 'affgrptestdesc', None, self.affinity_group_name) self.sms.create_storage_account(self.storage_account_name, self.storage_account_name + 'desc', self.storage_account_name + 'label', self.affinity_group_name) # Act result = self.sms.get_affinity_group_properties(self.affinity_group_name) # Assert self.assertIsNotNone(result) self.assertEqual(result.name, self.affinity_group_name) self.assertIsNotNone(result.label) self.assertIsNotNone(result.description) self.assertIsNotNone(result.location) self.assertIsNotNone(result.hosted_services[0]) self.assertEqual(result.hosted_services[0].service_name, self.hosted_service_name) self.assertEqual(result.hosted_services[0].hosted_service_properties.affinity_group, self.affinity_group_name) # not sure why azure does not return any storage service self.assertTrue(len(result.capabilities) > 0) def test_create_affinity_group(self): # Arrange label = 'tstmgmtaffgrp' description = 'tstmgmt affinity group' # Act result = self.sms.create_affinity_group(self.affinity_group_name, label, 'West US', description) # Assert self.assertIsNone(result) self.assertTrue(self._affinity_group_exists(self.affinity_group_name)) def test_update_affinity_group(self): # Arrange self._create_affinity_group(self.affinity_group_name) label = 'tstlabelupdate' description = 'testmgmt affinity group update' # Act result = self.sms.update_affinity_group(self.affinity_group_name, label, description) # Assert self.assertIsNone(result) props = self.sms.get_affinity_group_properties(self.affinity_group_name) self.assertEqual(props.label, label) self.assertEqual(props.description, description) def test_delete_affinity_group(self): # Arrange self._create_affinity_group(self.affinity_group_name) # Act result = self.sms.delete_affinity_group(self.affinity_group_name) # Assert self.assertIsNone(result) self.assertFalse(self._affinity_group_exists(self.affinity_group_name)) #--Test cases for locations ------------------------------------------ def test_list_locations(self): # Arrange # Act result = self.sms.list_locations() # Assert self.assertIsNotNone(result) self.assertTrue(len(result) > 0) self.assertIsNotNone(result[0].name) self.assertIsNotNone(result[0].display_name) self.assertIsNotNone(result[0].available_services) self.assertTrue(len(result[0].available_services) > 0)
class AzureServicesManager: # Storage container = 'vhds' windows_blob_url = 'blob.core.windows.net' # Linux linux_user = '******' linux_pass = '******' location = 'West US' # SSH Keys def __init__(self, subscription_id, cert_file): self.subscription_id = subscription_id self.cert_file = cert_file self.sms = ServiceManagementService(self.subscription_id, self.cert_file) @property def sms(self): return self.sms def list_locations(self): locations = self.sms.list_locations() for location in locations: print location def list_images(self): return self.sms.list_os_images() @utils.resource_not_found_handler def get_hosted_service(self, service_name): resp = self.sms.get_hosted_service_properties(service_name) properties = resp.hosted_service_properties return properties.__dict__ def delete_hosted_service(self, service_name): res = self.sms.check_hosted_service_name_availability(service_name) if not res.result: return self.sms.delete_hosted_service(service_name) def create_hosted_service(self, os_user, service_name=None, random=False): if not service_name: service_name = self.generate_cloud_service_name(os_user, random) available = False while not available: res = self.sms.check_hosted_service_name_availability(service_name) if not res.result: service_name = self.generate_cloud_service_name(os_user, random) else: available = True self.sms.create_hosted_service(service_name=service_name, label=service_name, location='West US') return service_name def create_virtual_machine(self, service_name, vm_name, image_name, role_size): media_link = self._get_media_link(vm_name) # Linux VM configuration hostname = '-'.join((vm_name, 'host')) linux_config = LinuxConfigurationSet(hostname, self.linux_user, self.linux_pass, True) # Hard disk for the OS os_hd = OSVirtualHardDisk(image_name, media_link) # Create vm result = self.sms.create_virtual_machine_deployment( service_name=service_name, deployment_name=vm_name, deployment_slot='production', label=vm_name, role_name=vm_name, system_config=linux_config, os_virtual_hard_disk=os_hd, role_size=role_size ) request_id = result.request_id return { 'request_id': request_id, 'media_link': media_link } def delete_virtual_machine(self, service_name, vm_name): resp = self.sms.delete_deployment(service_name, vm_name, True) self.sms.wait_for_operation_status(resp.request_id) result = self.sms.delete_hosted_service(service_name) return result def generate_cloud_service_name(self, os_user=None, random=False): if random: return utils.generate_random_name(10) return '-'.join((os_user, utils.generate_random_name(6))) @utils.resource_not_found_handler def get_virtual_machine_info(self, service_name, vm_name): vm_info = {} deploy_info = self.sms.get_deployment_by_name(service_name, vm_name) if deploy_info and deploy_info.role_instance_list: vm_info = deploy_info.role_instance_list[0].__dict__ return vm_info def list_virtual_machines(self): vm_list = [] services = self.sms.list_hosted_services() for service in services: deploys = service.deployments if deploys and deploys.role_instance_list: vm_name = deploys.role_instance_list[0].instance_name vm_list.append(vm_name) return vm_list def power_on(self, service_name, vm_name): resp = self.sms.start_role(service_name, vm_name, vm_name) return resp.request_id def power_off(self, service_name, vm_name): resp = self.sms.shutdown_role(service_name, vm_name, vm_name) return resp.request_id def soft_reboot(self, service_name, vm_name): resp = self.sms.restart_role(service_name, vm_name, vm_name) return resp.request_id def hard_reboot(self, service_name, vm_name): resp = self.sms.reboot_role_instance(service_name, vm_name, vm_name) return resp.request_id def attach_volume(self, service_name, vm_name, size, lun): disk_name = utils.generate_random_name(5, vm_name) media_link = self._get_media_link(vm_name, disk_name) self.sms.add_data_disk(service_name, vm_name, vm_name, lun, host_caching='ReadWrite', media_link=media_link, disk_name=disk_name, logical_disk_size_in_gb=size) def detach_volume(self, service_name, vm_name, lun): self.sms.delete_data_disk(service_name, vm_name, vm_name, lun, True) def get_available_lun(self, service_name, vm_name): try: role = self.sms.get_role(service_name, vm_name, vm_name) except Exception: return 0 disks = role.data_virtual_hard_disks luns = [disk.lun for disk in disks].sort() for i in range(1, 16): if i not in luns: return i return None def snapshot(self, service_name, vm_name, image_id, snanshot_name): image_desc = 'Snapshot for image %s' % vm_name image = CaptureRoleAsVMImage('Specialized', snanshot_name, image_id, image_desc, 'english') resp = self.sms.capture_vm_image(service_name, vm_name, vm_name, image) self.sms.wait_for_operation_status(resp.request_id) def _get_media_link(self, vm_name, filename=None, storage_account=None): """ The MediaLink should be constructed as: https://<storageAccount>.<blobLink>/<blobContainer>/<filename>.vhd """ if not storage_account: storage_account = self._get_or_create_storage_account() container = self.container filename = vm_name if filename is None else filename blob = vm_name + '-' + filename + '.vhd' media_link = "http://%s.%s/%s/%s" % (storage_account, self.windows_blob_url, container, blob) return media_link def _get_or_create_storage_account(self): account_list = self.sms.list_storage_accounts() if account_list: return account_list[-1].service_name storage_account = utils.generate_random_name(10) description = "Storage account %s description" % storage_account label = storage_account + 'label' self.sms.create_storage_account(storage_account, description, label, location=self.location) return storage_account def _wait_for_operation(self, request_id, timeout=3000, failure_callback=None, failure_callback_kwargs=None): try: self.sms.wait_for_operation_status(request_id, timeout=timeout) except Exception as ex: if failure_callback and failure_callback_kwargs: failure_callback(**failure_callback_kwargs) raise ex
class Deployment(object): """ Helper class to handle deployment of the web site. """ def __init__(self, config): self.config = config self.sms = ServiceManagementService(config.getAzureSubscriptionId(), config.getAzureCertificatePath()) self.sbms = ServiceBusManagementService( config.getAzureSubscriptionId(), config.getAzureCertificatePath()) @staticmethod def _resource_exists(get_resource): """ Helper to check for the existence of a resource in Azure. get_resource: Parameter-less function to invoke in order to get the resource. The resource is assumed to exist when the call to get_resource() returns a value that is not None. If the call to get_resource() returns None or throws a WindowsAzureMissingResourceError exception, then it is assumed that the resource does not exist. Returns: A boolean value which is True if the resource exists. """ resource = None try: resource = get_resource() except WindowsAzureMissingResourceError: pass return resource is not None def _wait_for_operation_success(self, request_id, timeout=600, wait=5): """ Waits for an asynchronous Azure operation to finish. request_id: The ID of the request to track. timeout: Maximum duration (in seconds) allowed for the operation to complete. wait: Wait time (in seconds) between consecutive calls to fetch the latest operation status. """ result = self.sms.get_operation_status(request_id) start_time = time.time() max_time = start_time + timeout now = start_time while result.status == 'InProgress': if now >= max_time: raise Exception( "Operation did not finish within the expected timeout") logger.info( 'Waiting for operation to finish (last_status=%s wait_so_far=%s)', result.status, round(now - start_time, 1)) time_to_wait = max(0.0, min(max_time - now, wait)) time.sleep(time_to_wait) result = self.sms.get_operation_status(request_id) now = time.time() if result.status != 'Succeeded': raise Exception("Operation terminated but it did not succeed.") def _wait_for_role_instance_status(self, role_instance_name, service_name, expected_status, timeout=600, wait=5): """ Waits for a role instance within the web site's cloud service to reach the status specified. role_instance_name: Name of the role instance. service_name: Name of service in which to find the role instance. expected_status: Expected instance status. timeout: Maximum duration (in seconds) allowed for the operation to complete. wait: Wait time (in seconds) between consecutive calls to fetch the latest role status. """ start_time = time.time() max_time = start_time + timeout now = start_time while True: status = None deployment = self.sms.get_deployment_by_name( service_name, service_name) for role_instance in deployment.role_instance_list: if role_instance.instance_name == role_instance_name: status = role_instance.instance_status if status == expected_status: break if now >= max_time: raise Exception( "Operation did not finish within the expected timeout") logger.info( 'Waiting for deployment status: expecting %s but got %s (wait_so_far=%s)', expected_status, status, round(now - start_time, 1)) time_to_wait = max(0.0, min(max_time - now, wait)) time.sleep(time_to_wait) now = time.time() def _wait_for_disk_deletion(self, disk_name, timeout=600, wait=5): """ Waits for a VM disk to disappear when it is being deleted. disk_name: Name of the VHD. timeout: Maximum duration (in seconds) allowed for the operation to complete. wait: Wait time (in seconds) between consecutive calls to check for the existence of the disk. """ start_time = time.time() max_time = start_time + timeout now = start_time logger.info("Checking that disk %s has been deleted.", disk_name) while self._resource_exists(lambda: self.sms.get_disk(disk_name)): if now >= max_time: raise Exception( "Disk %s was not deleted within the expected timeout.". format(disk_name)) logger.info("Waiting for disk %s to disappear (wait_so_far=%s).", disk_name, round(now - start_time, 1)) time_to_wait = max(0.0, min(max_time - now, wait)) time.sleep(time_to_wait) now = time.time() logger.info("Disk %s has been deleted.", disk_name) def _wait_for_namespace_active(self, name, timeout=600, wait=5): """ Waits for a service bus namespace to become Active. name: Namespace name. timeout: Maximum duration (in seconds) allowed for the operation to complete. wait: Wait time (in seconds) between consecutive calls to check for the existence of the disk. """ start_time = time.time() max_time = start_time + timeout now = start_time while True: status = None props = self.sbms.get_namespace(name) status = props.status if status == 'Active': break if now >= max_time: raise Exception( "Operation did not finish within the expected timeout") logger.info( 'Waiting for namepsace status: expecting Active but got %s (wait_so_far=%s)', status, round(now - start_time, 1)) time_to_wait = max(0.0, min(max_time - now, wait)) time.sleep(time_to_wait) now = time.time() def _getRoleInstances(self, service_name): """ Returns the role instances in the given cloud service deployment. The results are provided as a dictionary where keys are role instance names and values are RoleInstance objects. """ role_instances = {} if self._resource_exists(lambda: self.sms.get_deployment_by_name( service_name, service_name)): deployment = self.sms.get_deployment_by_name( service_name, service_name) for role_instance in deployment.role_instance_list: role_instances[role_instance.instance_name] = role_instance return role_instances def _ensureAffinityGroupExists(self): """ Creates the affinity group if it does not exist. """ name = self.config.getAffinityGroupName() location = self.config.getServiceLocation() logger.info( "Checking for existence of affinity group (name=%s; location=%s).", name, location) if self._resource_exists( lambda: self.sms.get_affinity_group_properties(name)): logger.warn("An affinity group named %s already exists.", name) else: self.sms.create_affinity_group(name, name, location) logger.info("Created affinity group %s.", name) def _ensureStorageAccountExists(self, name): """ Creates the storage account if it does not exist. """ logger.info("Checking for existence of storage account (name=%s).", name) if self._resource_exists( lambda: self.sms.get_storage_account_properties(name)): logger.warn("A storage account named %s already exists.", name) else: result = self.sms.create_storage_account( name, "", name, affinity_group=self.config.getAffinityGroupName()) self._wait_for_operation_success( result.request_id, timeout=self.config.getAzureOperationTimeout()) logger.info("Created storage account %s.", name) def _getStorageAccountKey(self, account_name): """ Gets the storage account key (primary key) for the given storage account. """ storage_props = self.sms.get_storage_account_keys(account_name) return storage_props.storage_service_keys.primary def _ensureStorageContainersExist(self): """ Creates Blob storage containers required by the service. """ logger.info("Checking for existence of Blob containers.") account_name = self.config.getServiceStorageAccountName() account_key = self._getStorageAccountKey(account_name) blob_service = BlobService(account_name, account_key) name_and_access_list = [ (self.config.getServicePublicStorageContainer(), 'blob'), (self.config.getServiceBundleStorageContainer(), None) ] for name, access in name_and_access_list: logger.info("Checking for existence of Blob container %s.", name) blob_service.create_container(name, x_ms_blob_public_access=access, fail_on_exist=False) access_info = 'private' if access is None else 'public {0}'.format( access) logger.info("Blob container %s is ready (access: %s).", name, access_info) def ensureStorageHasCorsConfiguration(self): """ Ensures Blob storage container for bundles is configured to allow cross-origin resource sharing. """ logger.info("Setting CORS rules.") account_name = self.config.getServiceStorageAccountName() account_key = self._getStorageAccountKey(account_name) cors_rule = CorsRule() cors_rule.allowed_origins = self.config.getServiceStorageCorsAllowedOrigins( ) cors_rule.allowed_methods = 'PUT' cors_rule.exposed_headers = '*' cors_rule.allowed_headers = '*' cors_rule.max_age_in_seconds = 1800 cors_rules = Cors() cors_rules.cors_rule.append(cors_rule) set_storage_service_cors_properties(account_name, account_key, cors_rules) def _ensureServiceExists(self, service_name, affinity_group_name): """ Creates the specified cloud service host if it does not exist. service_name: Name of the cloud service. affinity_group_name: Name of the affinity group (which should exists). """ logger.info("Checking for existence of cloud service (name=%s).", service_name) if self._resource_exists( lambda: self.sms.get_hosted_service_properties(service_name)): logger.warn("A cloud service named %s already exists.", service_name) else: self.sms.create_hosted_service(service_name, service_name, affinity_group=affinity_group_name) logger.info("Created cloud service %s.", service_name) def _ensureServiceCertificateExists(self, service_name): """ Adds certificate to the specified cloud service. service_name: Name of the target cloud service (which should exist). """ cert_format = self.config.getServiceCertificateFormat() cert_algorithm = self.config.getServiceCertificateAlgorithm() cert_thumbprint = self.config.getServiceCertificateThumbprint() cert_path = self.config.getServiceCertificateFilename() cert_password = self.config.getServiceCertificatePassword() logger.info( "Checking for existence of cloud service certificate for service %s.", service_name) get_cert = lambda: self.sms.get_service_certificate( service_name, cert_algorithm, cert_thumbprint) if self._resource_exists(get_cert): logger.info("Found expected cloud service certificate.") else: with open(cert_path, 'rb') as f: cert_data = base64.b64encode(f.read()) if len(cert_data) <= 0: raise Exception("Detected invalid certificate data.") result = self.sms.add_service_certificate(service_name, cert_data, cert_format, cert_password) self._wait_for_operation_success( result.request_id, timeout=self.config.getAzureOperationTimeout()) logger.info("Added service certificate.") def _assertOsImageExists(self, os_image_name): """ Asserts that the named OS image exists. """ logger.info("Checking for availability of OS image (name=%s).", os_image_name) if self.sms.get_os_image(os_image_name) is None: raise Exception( "Unable to find OS Image '{0}'.".format(os_image_name)) def _ensureVirtualMachinesExist(self): """ Creates the VMs for the web site. """ service_name = self.config.getServiceName() cert_thumbprint = self.config.getServiceCertificateThumbprint() vm_username = self.config.getVirtualMachineLogonUsername() vm_password = self.config.getVirtualMachineLogonPassword() vm_role_size = self.config.getServiceInstanceRoleSize() vm_numbers = self.config.getServiceInstanceCount() if vm_numbers < 1: raise Exception( "Detected an invalid number of instances: {0}.".format( vm_numbers)) self._assertOsImageExists(self.config.getServiceOSImageName()) role_instances = self._getRoleInstances(service_name) for vm_number in range(1, vm_numbers + 1): vm_hostname = '{0}-{1}'.format(service_name, vm_number) if vm_hostname in role_instances: logger.warn( "Role instance %s already exists: skipping creation.", vm_hostname) continue logger.info("Role instance %s provisioning begins.", vm_hostname) vm_diskname = '{0}.vhd'.format(vm_hostname) vm_disk_media_link = 'http://{0}.blob.core.windows.net/vhds/{1}'.format( self.config.getServiceStorageAccountName(), vm_diskname) ssh_port = str(self.config.getServiceInstanceSshPort() + vm_number) os_hd = OSVirtualHardDisk(self.config.getServiceOSImageName(), vm_disk_media_link, disk_name=vm_diskname, disk_label=vm_diskname) linux_config = LinuxConfigurationSet(vm_hostname, vm_username, vm_password, True) linux_config.ssh.public_keys.public_keys.append( PublicKey( cert_thumbprint, u'/home/{0}/.ssh/authorized_keys'.format(vm_username))) linux_config.ssh.key_pairs.key_pairs.append( KeyPair(cert_thumbprint, u'/home/{0}/.ssh/id_rsa'.format(vm_username))) network_config = ConfigurationSet() network_config.configuration_set_type = 'NetworkConfiguration' ssh_endpoint = ConfigurationSetInputEndpoint(name='SSH', protocol='TCP', port=ssh_port, local_port=u'22') network_config.input_endpoints.input_endpoints.append(ssh_endpoint) http_endpoint = ConfigurationSetInputEndpoint( name='HTTP', protocol='TCP', port=u'80', local_port=u'80', load_balanced_endpoint_set_name=service_name) http_endpoint.load_balancer_probe.port = '80' http_endpoint.load_balancer_probe.protocol = 'TCP' network_config.input_endpoints.input_endpoints.append( http_endpoint) if vm_number == 1: result = self.sms.create_virtual_machine_deployment( service_name=service_name, deployment_name=service_name, deployment_slot='Production', label=vm_hostname, role_name=vm_hostname, system_config=linux_config, os_virtual_hard_disk=os_hd, network_config=network_config, availability_set_name=service_name, data_virtual_hard_disks=None, role_size=vm_role_size) self._wait_for_operation_success( result.request_id, timeout=self.config.getAzureOperationTimeout()) self._wait_for_role_instance_status( vm_hostname, service_name, 'ReadyRole', self.config.getAzureOperationTimeout()) else: result = self.sms.add_role(service_name=service_name, deployment_name=service_name, role_name=vm_hostname, system_config=linux_config, os_virtual_hard_disk=os_hd, network_config=network_config, availability_set_name=service_name, role_size=vm_role_size) self._wait_for_operation_success( result.request_id, timeout=self.config.getAzureOperationTimeout()) self._wait_for_role_instance_status( vm_hostname, service_name, 'ReadyRole', self.config.getAzureOperationTimeout()) logger.info("Role instance %s has been created.", vm_hostname) def _deleteVirtualMachines(self, service_name): """ Deletes the VMs in the given cloud service. """ if self._resource_exists(lambda: self.sms.get_deployment_by_name( service_name, service_name)) == False: logger.warn("Deployment %s not found: no VMs to delete.", service_name) else: logger.info("Attempting to delete deployment %s.", service_name) # Get set of role instances before we remove them role_instances = self._getRoleInstances(service_name) def update_request(request): """ A filter to intercept the HTTP request sent by the ServiceManagementService so we can take advantage of a newer feature ('comp=media') in the delete deployment API (see http://msdn.microsoft.com/en-us/library/windowsazure/ee460812.aspx) """ hdrs = [] for name, value in request.headers: if 'x-ms-version' == name: value = '2013-08-01' hdrs.append((name, value)) request.headers = hdrs request.path = request.path + '?comp=media' #pylint: disable=W0212 response = self.sms._filter(request) return response svc = ServiceManagementService(self.sms.subscription_id, self.sms.cert_file) #pylint: disable=W0212 svc._filter = update_request result = svc.delete_deployment(service_name, service_name) logger.info( "Deployment %s deletion in progress: waiting for delete_deployment operation.", service_name) self._wait_for_operation_success(result.request_id) logger.info( "Deployment %s deletion in progress: waiting for VM disks to be removed.", service_name) # Now wait for the disks to disappear for role_instance_name in role_instances.keys(): disk_name = "{0}.vhd".format(role_instance_name) self._wait_for_disk_deletion(disk_name) logger.info("Deployment %s deleted.", service_name) def _ensureBuildMachineExists(self): """ Creates the VM for the build server. """ service_name = self.config.getBuildServiceName() service_storage_name = self.config.getStorageAccountName() cert_thumbprint = self.config.getServiceCertificateThumbprint() vm_username = self.config.getVirtualMachineLogonUsername() vm_password = self.config.getVirtualMachineLogonPassword() vm_hostname = service_name role_instances = self._getRoleInstances(service_name) if vm_hostname in role_instances: logger.warn("Role instance %s already exists: skipping creation.", vm_hostname) else: logger.info("Role instance %s provisioning begins.", vm_hostname) self._assertOsImageExists(self.config.getBuildOSImageName()) vm_diskname = '{0}.vhd'.format(vm_hostname) vm_disk_media_link = 'http://{0}.blob.core.windows.net/vhds/{1}'.format( service_storage_name, vm_diskname) os_hd = OSVirtualHardDisk(self.config.getBuildOSImageName(), vm_disk_media_link, disk_name=vm_diskname, disk_label=vm_diskname) linux_config = LinuxConfigurationSet(vm_hostname, vm_username, vm_password, True) linux_config.ssh.public_keys.public_keys.append( PublicKey( cert_thumbprint, u'/home/{0}/.ssh/authorized_keys'.format(vm_username))) linux_config.ssh.key_pairs.key_pairs.append( KeyPair(cert_thumbprint, u'/home/{0}/.ssh/id_rsa'.format(vm_username))) network_config = ConfigurationSet() network_config.configuration_set_type = 'NetworkConfiguration' ssh_endpoint = ConfigurationSetInputEndpoint(name='SSH', protocol='TCP', port=u'22', local_port=u'22') network_config.input_endpoints.input_endpoints.append(ssh_endpoint) result = self.sms.create_virtual_machine_deployment( service_name=service_name, deployment_name=service_name, deployment_slot='Production', label=vm_hostname, role_name=vm_hostname, system_config=linux_config, os_virtual_hard_disk=os_hd, network_config=network_config, availability_set_name=None, data_virtual_hard_disks=None, role_size=self.config.getBuildInstanceRoleSize()) self._wait_for_operation_success( result.request_id, timeout=self.config.getAzureOperationTimeout()) self._wait_for_role_instance_status( vm_hostname, service_name, 'ReadyRole', self.config.getAzureOperationTimeout()) logger.info("Role instance %s has been created.", vm_hostname) def _deleteStorageAccount(self, name): """ Deletes the storage account for the web site. """ logger.info("Attempting to delete storage account %s.", name) if self._resource_exists( lambda: self.sms.get_storage_account_properties(name )) == False: logger.warn("Storage account %s not found: nothing to delete.", name) else: self.sms.delete_storage_account(name) logger.info("Storage account %s deleted.", name) def _deleteService(self, name): """ Deletes the specified cloud service. """ logger.info("Attempting to delete cloud service %s.", name) if self._resource_exists( lambda: self.sms.get_hosted_service_properties(name)) == False: logger.warn("Cloud service %s not found: nothing to delete.", name) else: self.sms.delete_hosted_service(name) logger.info("Cloud service %s deleted.", name) def _deleteAffinityGroup(self): """ Deletes the affinity group for the web site. """ name = self.config.getAffinityGroupName() logger.info("Attempting to delete affinity group %s.", name) if self._resource_exists( lambda: self.sms.get_affinity_group_properties(name)) == False: logger.warn("Affinity group %s not found: nothing to delete.", name) else: self.sms.delete_affinity_group(name) logger.info("Affinity group %s deleted.", name) def _ensureServiceBusNamespaceExists(self): """ Creates the Azure Service Bus Namespace if it does not exist. """ name = self.config.getServiceBusNamespace() logger.info( "Checking for existence of service bus namespace (name=%s).", name) if self._resource_exists(lambda: self.sbms.get_namespace(name)): logger.warn("A namespace named %s already exists.", name) else: self.sbms.create_namespace(name, self.config.getServiceLocation()) self._wait_for_namespace_active(name) logger.info("Created namespace %s.", name) def _ensureServiceBusQueuesExist(self): """ Creates Azure service bus queues required by the service. """ logger.info("Checking for existence of Service Bus Queues.") namespace = self.sbms.get_namespace( self.config.getServiceBusNamespace()) sbs = ServiceBusService(namespace.name, namespace.default_key, issuer='owner') queue_names = [ 'jobresponsequeue', 'windowscomputequeue', 'linuxcomputequeue' ] for name in queue_names: logger.info("Checking for existence of Queue %s.", name) sbs.create_queue(name, fail_on_exist=False) logger.info("Queue %s is ready.", name) def _deleteServiceBusNamespace(self): """ Deletes the Azure Service Bus Namespace. """ name = self.config.getServiceBusNamespace() logger.info("Attempting to delete service bus namespace %s.", name) if self._resource_exists( lambda: self.sbms.get_namespace(name)) == False: logger.warn("Namespace %s not found: nothing to delete.", name) else: self.sbms.delete_namespace(name) logger.info("Namespace %s deleted.", name) def Deploy(self, assets): """ Creates a deployment. assets: The set of assets to create. The full set is: {'build', 'web'}. """ if len(assets) == 0: raise ValueError("Set of assets to deploy is not specified.") logger.info("Starting deployment operation.") self._ensureAffinityGroupExists() self._ensureStorageAccountExists(self.config.getStorageAccountName()) ## Build instance if 'build' in assets: self._ensureServiceExists(self.config.getBuildServiceName(), self.config.getAffinityGroupName()) self._ensureServiceCertificateExists( self.config.getBuildServiceName()) self._ensureBuildMachineExists() # Web instances if 'web' in assets: self._ensureStorageAccountExists( self.config.getServiceStorageAccountName()) self._ensureStorageContainersExist() self.ensureStorageHasCorsConfiguration() self._ensureServiceBusNamespaceExists() self._ensureServiceBusQueuesExist() self._ensureServiceExists(self.config.getServiceName(), self.config.getAffinityGroupName()) self._ensureServiceCertificateExists(self.config.getServiceName()) self._ensureVirtualMachinesExist() #queues logger.info("Deployment operation is complete.") def Teardown(self, assets): """ Deletes a deployment. assets: The set of assets to delete. The full set is: {'web', 'build'}. """ if len(assets) == 0: raise ValueError("Set of assets to teardown is not specified.") logger.info("Starting teardown operation.") if 'web' in assets: self._deleteVirtualMachines(self.config.getServiceName()) self._deleteService(self.config.getServiceName()) self._deleteStorageAccount( self.config.getServiceStorageAccountName()) if 'build' in assets: self._deleteVirtualMachines(self.config.getBuildServiceName()) self._deleteService(self.config.getBuildServiceName()) self._deleteStorageAccount(self.config.getStorageAccountName()) if ('web' in assets) and ('build' in assets): self._deleteServiceBusNamespace() self._deleteAffinityGroup() logger.info("Teardown operation is complete.") def getSettingsFileContent(self): """ Generates the content of the local Django settings file. """ allowed_hosts = [ '{0}.cloudapp.net'.format(self.config.getServiceName()) ] allowed_hosts.extend(self.config.getWebHostnames()) allowed_hosts.extend(['www.codalab.org', 'codalab.org']) ssl_allowed_hosts = self.config.getSslRewriteHosts() if len(ssl_allowed_hosts) == 0: ssl_allowed_hosts = allowed_hosts storage_key = self._getStorageAccountKey( self.config.getServiceStorageAccountName()) namespace = self.sbms.get_namespace( self.config.getServiceBusNamespace()) if len(self.config.getSslCertificateInstalledPath()) > 0: bundle_auth_scheme = "https" else: bundle_auth_scheme = "http" if len(ssl_allowed_hosts) == 0: bundle_auth_host = '{0}.cloudapp.net'.format( self.config.getServiceName()) else: bundle_auth_host = ssl_allowed_hosts[0] bundle_auth_url = "{0}://{1}".format(bundle_auth_scheme, bundle_auth_host) lines = [ "from base import Base", "from default import *", "from configurations import Settings", "", "import sys", "from os.path import dirname, abspath, join", "from pkgutil import extend_path", "import codalab", "", "class {0}(Base):".format(self.config.getDjangoConfiguration()), "", " DEBUG=False", "", " ALLOWED_HOSTS = {0}".format(allowed_hosts), "", " SSL_PORT = '443'", " SSL_CERTIFICATE = '{0}'".format( self.config.getSslCertificateInstalledPath()), " SSL_CERTIFICATE_KEY = '{0}'".format( self.config.getSslCertificateKeyInstalledPath()), " SSL_ALLOWED_HOSTS = {0}".format(ssl_allowed_hosts), "", " DEFAULT_FILE_STORAGE = 'codalab.azure_storage.AzureStorage'", " AZURE_ACCOUNT_NAME = '{0}'".format( self.config.getServiceStorageAccountName()), " AZURE_ACCOUNT_KEY = '{0}'".format(storage_key), " AZURE_CONTAINER = '{0}'".format( self.config.getServicePublicStorageContainer()), " BUNDLE_AZURE_ACCOUNT_NAME = AZURE_ACCOUNT_NAME", " BUNDLE_AZURE_ACCOUNT_KEY = AZURE_ACCOUNT_KEY", " BUNDLE_AZURE_CONTAINER = '{0}'".format( self.config.getServiceBundleStorageContainer()), "", " SBS_NAMESPACE = '{0}'".format( self.config.getServiceBusNamespace()), " SBS_ISSUER = 'owner'", " SBS_ACCOUNT_KEY = '{0}'".format(namespace.default_key), " SBS_RESPONSE_QUEUE = 'jobresponsequeue'", " SBS_COMPUTE_QUEUE = 'windowscomputequeue'", "", " EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'", " EMAIL_HOST = '{0}'".format(self.config.getEmailHost()), " EMAIL_HOST_USER = '******'".format(self.config.getEmailUser()), " EMAIL_HOST_PASSWORD = '******'".format( self.config.getEmailPassword()), " EMAIL_PORT = 587", " EMAIL_USE_TLS = True", " DEFAULT_FROM_EMAIL = '*****@*****.**'", " SERVER_EMAIL = '*****@*****.**'", "", " # Django secret", " SECRET_KEY = '{0}'".format(self.config.getDjangoSecretKey()), "", " ADMINS = (('CodaLab', '*****@*****.**'),)", " MANAGERS = ADMINS", "", " DATABASES = {", " 'default': {", " 'ENGINE': '{0}',".format( self.config.getDatabaseEngine()), " 'NAME': '{0}',".format(self.config.getDatabaseName()), " 'USER': '******',".format(self.config.getDatabaseUser()), " 'PASSWORD': '******',".format( self.config.getDatabasePassword()), " 'HOST': '{0}',".format(self.config.getDatabaseHost()), " 'PORT': '{0}', ".format( self.config.getDatabasePort()), " 'OPTIONS' : {", " 'init_command': 'SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED',", " 'read_timeout': 5", " }", " }", " }", "", " BUNDLE_DB_NAME = '{0}'".format( self.config.getBundleServiceDatabaseName()), " BUNDLE_DB_USER = '******'".format( self.config.getBundleServiceDatabaseUser()), " BUNDLE_DB_PASSWORD = '******'".format( self.config.getBundleServiceDatabasePassword()), " BUNDLE_APP_ID = '{0}'".format( self.config.getBundleServiceAppId()), " BUNDLE_APP_KEY = '{0}'".format( self.config.getBundleServiceAppKey()), " BUNDLE_AUTH_URL = '{0}'".format(bundle_auth_url), "", " BUNDLE_SERVICE_URL = '{0}'".format( self.config.getBundleServiceUrl()), " BUNDLE_SERVICE_CODE_PATH = '/home/{0}/deploy/bundles'".format( self.config.getVirtualMachineLogonUsername()), " sys.path.append(BUNDLE_SERVICE_CODE_PATH)", " codalab.__path__ = extend_path(codalab.__path__, codalab.__name__)", "", ] preview = self.config.getShowPreviewFeatures() if preview >= 1: if preview == 1: lines.append(" PREVIEW_WORKSHEETS = True") if preview > 1: lines.append(" SHOW_BETA_FEATURES = True") lines.append("") return '\n'.join(lines)
class Deployment(object): """ Helper class to handle deployment of the web site. """ def __init__(self, config): self.config = config self.sms = ServiceManagementService(config.getAzureSubscriptionId(), config.getAzureCertificatePath()) self.sbms = ServiceBusManagementService(config.getAzureSubscriptionId(), config.getAzureCertificatePath()) @staticmethod def _resource_exists(get_resource): """ Helper to check for the existence of a resource in Azure. get_resource: Parameter-less function to invoke in order to get the resource. The resource is assumed to exist when the call to get_resource() returns a value that is not None. If the call to get_resource() returns None or throws a WindowsAzureMissingResourceError exception, then it is assumed that the resource does not exist. Returns: A boolean value which is True if the resource exists. """ resource = None try: resource = get_resource() except WindowsAzureMissingResourceError: pass return resource is not None def _wait_for_operation_success(self, request_id, timeout=600, wait=5): """ Waits for an asynchronous Azure operation to finish. request_id: The ID of the request to track. timeout: Maximum duration (in seconds) allowed for the operation to complete. wait: Wait time (in seconds) between consecutive calls to fetch the latest operation status. """ result = self.sms.get_operation_status(request_id) start_time = time.time() max_time = start_time + timeout now = start_time while result.status == 'InProgress': if now >= max_time: raise Exception("Operation did not finish within the expected timeout") logger.info('Waiting for operation to finish (last_status=%s wait_so_far=%s)', result.status, round(now - start_time, 1)) time_to_wait = max(0.0, min(max_time - now, wait)) time.sleep(time_to_wait) result = self.sms.get_operation_status(request_id) now = time.time() if result.status != 'Succeeded': raise Exception("Operation terminated but it did not succeed.") def _wait_for_role_instance_status(self, role_instance_name, service_name, expected_status, timeout=600, wait=5): """ Waits for a role instance within the web site's cloud service to reach the status specified. role_instance_name: Name of the role instance. service_name: Name of service in which to find the role instance. expected_status: Expected instance status. timeout: Maximum duration (in seconds) allowed for the operation to complete. wait: Wait time (in seconds) between consecutive calls to fetch the latest role status. """ start_time = time.time() max_time = start_time + timeout now = start_time while True: status = None deployment = self.sms.get_deployment_by_name(service_name, service_name) for role_instance in deployment.role_instance_list: if role_instance.instance_name == role_instance_name: status = role_instance.instance_status if status == expected_status: break if now >= max_time: raise Exception("Operation did not finish within the expected timeout") logger.info('Waiting for deployment status: expecting %s but got %s (wait_so_far=%s)', expected_status, status, round(now - start_time, 1)) time_to_wait = max(0.0, min(max_time - now, wait)) time.sleep(time_to_wait) now = time.time() def _wait_for_disk_deletion(self, disk_name, timeout=600, wait=5): """ Waits for a VM disk to disappear when it is being deleted. disk_name: Name of the VHD. timeout: Maximum duration (in seconds) allowed for the operation to complete. wait: Wait time (in seconds) between consecutive calls to check for the existence of the disk. """ start_time = time.time() max_time = start_time + timeout now = start_time logger.info("Checking that disk %s has been deleted.", disk_name) while self._resource_exists(lambda: self.sms.get_disk(disk_name)): if now >= max_time: raise Exception("Disk %s was not deleted within the expected timeout.".format(disk_name)) logger.info("Waiting for disk %s to disappear (wait_so_far=%s).", disk_name, round(now - start_time, 1)) time_to_wait = max(0.0, min(max_time - now, wait)) time.sleep(time_to_wait) now = time.time() logger.info("Disk %s has been deleted.", disk_name) def _wait_for_namespace_active(self, name, timeout=600, wait=5): """ Waits for a service bus namespace to become Active. name: Namespace name. timeout: Maximum duration (in seconds) allowed for the operation to complete. wait: Wait time (in seconds) between consecutive calls to check for the existence of the disk. """ start_time = time.time() max_time = start_time + timeout now = start_time while True: status = None props = self.sbms.get_namespace(name) status = props.status if status == 'Active': break if now >= max_time: raise Exception("Operation did not finish within the expected timeout") logger.info('Waiting for namespace status: expecting Active but got %s (wait_so_far=%s)', status, round(now - start_time, 1)) time_to_wait = max(0.0, min(max_time - now, wait)) time.sleep(time_to_wait) now = time.time() def _getRoleInstances(self, service_name): """ Returns the role instances in the given cloud service deployment. The results are provided as a dictionary where keys are role instance names and values are RoleInstance objects. """ role_instances = {} if self._resource_exists(lambda: self.sms.get_deployment_by_name(service_name, service_name)): deployment = self.sms.get_deployment_by_name(service_name, service_name) for role_instance in deployment.role_instance_list: role_instances[role_instance.instance_name] = role_instance return role_instances def _ensureAffinityGroupExists(self): """ Creates the affinity group if it does not exist. """ name = self.config.getAffinityGroupName() location = self.config.getServiceLocation() logger.info("Checking for existence of affinity group (name=%s; location=%s).", name, location) if self._resource_exists(lambda: self.sms.get_affinity_group_properties(name)): logger.warn("An affinity group named %s already exists.", name) else: self.sms.create_affinity_group(name, name, location) logger.info("Created affinity group %s.", name) def _ensureStorageAccountExists(self, name): """ Creates the storage account if it does not exist. """ logger.info("Checking for existence of storage account (name=%s).", name) if self._resource_exists(lambda: self.sms.get_storage_account_properties(name)): logger.warn("A storage account named %s already exists.", name) else: result = self.sms.create_storage_account(name, "", name, affinity_group=self.config.getAffinityGroupName()) self._wait_for_operation_success(result.request_id, timeout=self.config.getAzureOperationTimeout()) logger.info("Created storage account %s.", name) def _getStorageAccountKey(self, account_name): """ Gets the storage account key (primary key) for the given storage account. """ storage_props = self.sms.get_storage_account_keys(account_name) return storage_props.storage_service_keys.primary def _ensureStorageContainersExist(self): """ Creates Blob storage containers required by the service. """ logger.info("Checking for existence of Blob containers.") account_name = self.config.getServiceStorageAccountName() account_key = self._getStorageAccountKey(account_name) blob_service = BlobService(account_name, account_key) name_and_access_list = [(self.config.getServicePublicStorageContainer(), 'blob'), (self.config.getServiceBundleStorageContainer(), None)] for name, access in name_and_access_list: logger.info("Checking for existence of Blob container %s.", name) blob_service.create_container(name, x_ms_blob_public_access=access, fail_on_exist=False) access_info = 'private' if access is None else 'public {0}'.format(access) logger.info("Blob container %s is ready (access: %s).", name, access_info) def ensureStorageHasCorsConfiguration(self): """ Ensures Blob storage container for bundles is configured to allow cross-origin resource sharing. """ logger.info("Setting CORS rules.") account_name = self.config.getServiceStorageAccountName() account_key = self._getStorageAccountKey(account_name) cors_rule = CorsRule() cors_rule.allowed_origins = self.config.getServiceStorageCorsAllowedOrigins() cors_rule.allowed_methods = 'PUT' cors_rule.exposed_headers = '*' cors_rule.allowed_headers = '*' cors_rule.max_age_in_seconds = 1800 cors_rules = Cors() cors_rules.cors_rule.append(cors_rule) set_storage_service_cors_properties(account_name, account_key, cors_rules) def _ensureServiceExists(self, service_name, affinity_group_name): """ Creates the specified cloud service host if it does not exist. service_name: Name of the cloud service. affinity_group_name: Name of the affinity group (which should exists). """ logger.info("Checking for existence of cloud service (name=%s).", service_name) if self._resource_exists(lambda: self.sms.get_hosted_service_properties(service_name)): logger.warn("A cloud service named %s already exists.", service_name) else: self.sms.create_hosted_service(service_name, service_name, affinity_group=affinity_group_name) logger.info("Created cloud service %s.", service_name) def _ensureServiceCertificateExists(self, service_name): """ Adds certificate to the specified cloud service. service_name: Name of the target cloud service (which should exist). """ cert_format = self.config.getServiceCertificateFormat() cert_algorithm = self.config.getServiceCertificateAlgorithm() cert_thumbprint = self.config.getServiceCertificateThumbprint() cert_path = self.config.getServiceCertificateFilename() cert_password = self.config.getServiceCertificatePassword() logger.info("Checking for existence of cloud service certificate for service %s.", service_name) get_cert = lambda: self.sms.get_service_certificate(service_name, cert_algorithm, cert_thumbprint) if self._resource_exists(get_cert): logger.info("Found expected cloud service certificate.") else: with open(cert_path, 'rb') as f: cert_data = base64.b64encode(f.read()) if len(cert_data) <= 0: raise Exception("Detected invalid certificate data.") result = self.sms.add_service_certificate(service_name, cert_data, cert_format, cert_password) self._wait_for_operation_success(result.request_id, timeout=self.config.getAzureOperationTimeout()) logger.info("Added service certificate.") def _assertOsImageExists(self, os_image_name): """ Asserts that the named OS image exists. """ logger.info("Checking for availability of OS image (name=%s).", os_image_name) if self.sms.get_os_image(os_image_name) is None: raise Exception("Unable to find OS Image '{0}'.".format(os_image_name)) def _ensureVirtualMachinesExist(self): """ Creates the VMs for the web site. """ service_name = self.config.getServiceName() cert_thumbprint = self.config.getServiceCertificateThumbprint() vm_username = self.config.getVirtualMachineLogonUsername() vm_password = self.config.getVirtualMachineLogonPassword() vm_role_size = self.config.getServiceInstanceRoleSize() vm_numbers = self.config.getServiceInstanceCount() if vm_numbers < 1: raise Exception("Detected an invalid number of instances: {0}.".format(vm_numbers)) self._assertOsImageExists(self.config.getServiceOSImageName()) role_instances = self._getRoleInstances(service_name) for vm_number in range(1, vm_numbers+1): vm_hostname = '{0}-{1}'.format(service_name, vm_number) if vm_hostname in role_instances: logger.warn("Role instance %s already exists: skipping creation.", vm_hostname) continue logger.info("Role instance %s provisioning begins.", vm_hostname) vm_diskname = '{0}.vhd'.format(vm_hostname) vm_disk_media_link = 'http://{0}.blob.core.windows.net/vhds/{1}'.format( self.config.getServiceStorageAccountName(), vm_diskname ) ssh_port = str(self.config.getServiceInstanceSshPort() + vm_number) os_hd = OSVirtualHardDisk(self.config.getServiceOSImageName(), vm_disk_media_link, disk_name=vm_diskname, disk_label=vm_diskname) linux_config = LinuxConfigurationSet(vm_hostname, vm_username, vm_password, True) linux_config.ssh.public_keys.public_keys.append( PublicKey(cert_thumbprint, u'/home/{0}/.ssh/authorized_keys'.format(vm_username)) ) linux_config.ssh.key_pairs.key_pairs.append( KeyPair(cert_thumbprint, u'/home/{0}/.ssh/id_rsa'.format(vm_username)) ) network_config = ConfigurationSet() network_config.configuration_set_type = 'NetworkConfiguration' ssh_endpoint = ConfigurationSetInputEndpoint(name='SSH', protocol='TCP', port=ssh_port, local_port=u'22') network_config.input_endpoints.input_endpoints.append(ssh_endpoint) http_endpoint = ConfigurationSetInputEndpoint(name='HTTP', protocol='TCP', port=u'80', local_port=u'80', load_balanced_endpoint_set_name=service_name) http_endpoint.load_balancer_probe.port = '80' http_endpoint.load_balancer_probe.protocol = 'TCP' network_config.input_endpoints.input_endpoints.append(http_endpoint) if vm_number == 1: result = self.sms.create_virtual_machine_deployment(service_name=service_name, deployment_name=service_name, deployment_slot='Production', label=vm_hostname, role_name=vm_hostname, system_config=linux_config, os_virtual_hard_disk=os_hd, network_config=network_config, availability_set_name=service_name, data_virtual_hard_disks=None, role_size=vm_role_size) self._wait_for_operation_success(result.request_id, timeout=self.config.getAzureOperationTimeout()) self._wait_for_role_instance_status(vm_hostname, service_name, 'ReadyRole', self.config.getAzureOperationTimeout()) else: result = self.sms.add_role(service_name=service_name, deployment_name=service_name, role_name=vm_hostname, system_config=linux_config, os_virtual_hard_disk=os_hd, network_config=network_config, availability_set_name=service_name, role_size=vm_role_size) self._wait_for_operation_success(result.request_id, timeout=self.config.getAzureOperationTimeout()) self._wait_for_role_instance_status(vm_hostname, service_name, 'ReadyRole', self.config.getAzureOperationTimeout()) logger.info("Role instance %s has been created.", vm_hostname) def _deleteVirtualMachines(self, service_name): """ Deletes the VMs in the given cloud service. """ if self._resource_exists(lambda: self.sms.get_deployment_by_name(service_name, service_name)) == False: logger.warn("Deployment %s not found: no VMs to delete.", service_name) else: logger.info("Attempting to delete deployment %s.", service_name) # Get set of role instances before we remove them role_instances = self._getRoleInstances(service_name) def update_request(request): """ A filter to intercept the HTTP request sent by the ServiceManagementService so we can take advantage of a newer feature ('comp=media') in the delete deployment API (see http://msdn.microsoft.com/en-us/library/windowsazure/ee460812.aspx) """ hdrs = [] for name, value in request.headers: if 'x-ms-version' == name: value = '2013-08-01' hdrs.append((name, value)) request.headers = hdrs request.path = request.path + '?comp=media' #pylint: disable=W0212 response = self.sms._filter(request) return response svc = ServiceManagementService(self.sms.subscription_id, self.sms.cert_file) #pylint: disable=W0212 svc._filter = update_request result = svc.delete_deployment(service_name, service_name) logger.info("Deployment %s deletion in progress: waiting for delete_deployment operation.", service_name) self._wait_for_operation_success(result.request_id) logger.info("Deployment %s deletion in progress: waiting for VM disks to be removed.", service_name) # Now wait for the disks to disappear for role_instance_name in role_instances.keys(): disk_name = "{0}.vhd".format(role_instance_name) self._wait_for_disk_deletion(disk_name) logger.info("Deployment %s deleted.", service_name) def _ensureBuildMachineExists(self): """ Creates the VM for the build server. """ service_name = self.config.getBuildServiceName() service_storage_name = self.config.getStorageAccountName() cert_thumbprint = self.config.getServiceCertificateThumbprint() vm_username = self.config.getVirtualMachineLogonUsername() vm_password = self.config.getVirtualMachineLogonPassword() vm_hostname = service_name role_instances = self._getRoleInstances(service_name) if vm_hostname in role_instances: logger.warn("Role instance %s already exists: skipping creation.", vm_hostname) else: logger.info("Role instance %s provisioning begins.", vm_hostname) self._assertOsImageExists(self.config.getBuildOSImageName()) vm_diskname = '{0}.vhd'.format(vm_hostname) vm_disk_media_link = 'http://{0}.blob.core.windows.net/vhds/{1}'.format(service_storage_name, vm_diskname) os_hd = OSVirtualHardDisk(self.config.getBuildOSImageName(), vm_disk_media_link, disk_name=vm_diskname, disk_label=vm_diskname) linux_config = LinuxConfigurationSet(vm_hostname, vm_username, vm_password, True) linux_config.ssh.public_keys.public_keys.append( PublicKey(cert_thumbprint, u'/home/{0}/.ssh/authorized_keys'.format(vm_username)) ) linux_config.ssh.key_pairs.key_pairs.append( KeyPair(cert_thumbprint, u'/home/{0}/.ssh/id_rsa'.format(vm_username)) ) network_config = ConfigurationSet() network_config.configuration_set_type = 'NetworkConfiguration' ssh_endpoint = ConfigurationSetInputEndpoint(name='SSH', protocol='TCP', port=u'22', local_port=u'22') network_config.input_endpoints.input_endpoints.append(ssh_endpoint) result = self.sms.create_virtual_machine_deployment(service_name=service_name, deployment_name=service_name, deployment_slot='Production', label=vm_hostname, role_name=vm_hostname, system_config=linux_config, os_virtual_hard_disk=os_hd, network_config=network_config, availability_set_name=None, data_virtual_hard_disks=None, role_size=self.config.getBuildInstanceRoleSize()) self._wait_for_operation_success(result.request_id, timeout=self.config.getAzureOperationTimeout()) self._wait_for_role_instance_status(vm_hostname, service_name, 'ReadyRole', self.config.getAzureOperationTimeout()) logger.info("Role instance %s has been created.", vm_hostname) def _deleteStorageAccount(self, name): """ Deletes the storage account for the web site. """ logger.info("Attempting to delete storage account %s.", name) if self._resource_exists(lambda: self.sms.get_storage_account_properties(name)) == False: logger.warn("Storage account %s not found: nothing to delete.", name) else: self.sms.delete_storage_account(name) logger.info("Storage account %s deleted.", name) def _deleteService(self, name): """ Deletes the specified cloud service. """ logger.info("Attempting to delete cloud service %s.", name) if self._resource_exists(lambda: self.sms.get_hosted_service_properties(name)) == False: logger.warn("Cloud service %s not found: nothing to delete.", name) else: self.sms.delete_hosted_service(name) logger.info("Cloud service %s deleted.", name) def _deleteAffinityGroup(self): """ Deletes the affinity group for the web site. """ name = self.config.getAffinityGroupName() logger.info("Attempting to delete affinity group %s.", name) if self._resource_exists(lambda: self.sms.get_affinity_group_properties(name)) == False: logger.warn("Affinity group %s not found: nothing to delete.", name) else: self.sms.delete_affinity_group(name) logger.info("Affinity group %s deleted.", name) def _ensureServiceBusNamespaceExists(self): """ Creates the Azure Service Bus Namespace if it does not exist. """ name = self.config.getServiceBusNamespace() logger.info("Checking for existence of service bus namespace (name=%s).", name) if self._resource_exists(lambda: self.sbms.get_namespace(name)): logger.warn("A namespace named %s already exists.", name) else: self.sbms.create_namespace(name, self.config.getServiceLocation()) self._wait_for_namespace_active(name) logger.info("Created namespace %s.", name) def _ensureServiceBusQueuesExist(self): """ Creates Azure service bus queues required by the service. """ logger.info("Checking for existence of Service Bus Queues.") namespace = self.sbms.get_namespace(self.config.getServiceBusNamespace()) sbs = ServiceBusService(namespace.name, namespace.default_key, issuer='owner') queue_names = ['jobresponsequeue', 'windowscomputequeue', 'linuxcomputequeue'] for name in queue_names: logger.info("Checking for existence of Queue %s.", name) sbs.create_queue(name, fail_on_exist=False) logger.info("Queue %s is ready.", name) def _deleteServiceBusNamespace(self): """ Deletes the Azure Service Bus Namespace. """ name = self.config.getServiceBusNamespace() logger.info("Attempting to delete service bus namespace %s.", name) if self._resource_exists(lambda: self.sbms.get_namespace(name)) == False: logger.warn("Namespace %s not found: nothing to delete.", name) else: self.sbms.delete_namespace(name) logger.info("Namespace %s deleted.", name) def Deploy(self, assets): """ Creates a deployment. assets: The set of assets to create. The full set is: {'build', 'web'}. """ if len(assets) == 0: raise ValueError("Set of assets to deploy is not specified.") logger.info("Starting deployment operation.") self._ensureAffinityGroupExists() self._ensureStorageAccountExists(self.config.getStorageAccountName()) ## Build instance if 'build' in assets: self._ensureServiceExists(self.config.getBuildServiceName(), self.config.getAffinityGroupName()) self._ensureServiceCertificateExists(self.config.getBuildServiceName()) self._ensureBuildMachineExists() # Web instances if 'web' in assets: self._ensureStorageAccountExists(self.config.getServiceStorageAccountName()) self._ensureStorageContainersExist() self.ensureStorageHasCorsConfiguration() self._ensureServiceBusNamespaceExists() self._ensureServiceBusQueuesExist() self._ensureServiceExists(self.config.getServiceName(), self.config.getAffinityGroupName()) self._ensureServiceCertificateExists(self.config.getServiceName()) self._ensureVirtualMachinesExist() #queues logger.info("Deployment operation is complete.") def Teardown(self, assets): """ Deletes a deployment. assets: The set of assets to delete. The full set is: {'web', 'build'}. """ if len(assets) == 0: raise ValueError("Set of assets to teardown is not specified.") logger.info("Starting teardown operation.") if 'web' in assets: self._deleteVirtualMachines(self.config.getServiceName()) self._deleteService(self.config.getServiceName()) self._deleteStorageAccount(self.config.getServiceStorageAccountName()) if 'build' in assets: self._deleteVirtualMachines(self.config.getBuildServiceName()) self._deleteService(self.config.getBuildServiceName()) self._deleteStorageAccount(self.config.getStorageAccountName()) if ('web' in assets) and ('build' in assets): self._deleteServiceBusNamespace() self._deleteAffinityGroup() logger.info("Teardown operation is complete.") def getSettingsFileContent(self): """ Generates the content of the local Django settings file. """ allowed_hosts = ['{0}.cloudapp.net'.format(self.config.getServiceName())] allowed_hosts.extend(self.config.getWebHostnames()) allowed_hosts.extend(['www.codalab.org', 'codalab.org']) ssl_allowed_hosts = self.config.getSslRewriteHosts(); if len(ssl_allowed_hosts) == 0: ssl_allowed_hosts = allowed_hosts storage_key = self._getStorageAccountKey(self.config.getServiceStorageAccountName()) namespace = self.sbms.get_namespace(self.config.getServiceBusNamespace()) if len(self.config.getSslCertificateInstalledPath()) > 0: bundle_auth_scheme = "https" else: bundle_auth_scheme = "http" if len(ssl_allowed_hosts) == 0: bundle_auth_host = '{0}.cloudapp.net'.format(self.config.getServiceName()) else: bundle_auth_host = ssl_allowed_hosts[0] bundle_auth_url = "{0}://{1}".format(bundle_auth_scheme, bundle_auth_host) lines = [ "from base import Base", "from default import *", "from configurations import Settings", "", "import sys", "from os.path import dirname, abspath, join", "from pkgutil import extend_path", "import codalab", "", "class {0}(Base):".format(self.config.getDjangoConfiguration()), "", " DEBUG=False", "", " ALLOWED_HOSTS = {0}".format(allowed_hosts), "", " SSL_PORT = '443'", " SSL_CERTIFICATE = '{0}'".format(self.config.getSslCertificateInstalledPath()), " SSL_CERTIFICATE_KEY = '{0}'".format(self.config.getSslCertificateKeyInstalledPath()), " SSL_ALLOWED_HOSTS = {0}".format(ssl_allowed_hosts), "", " DEFAULT_FILE_STORAGE = 'codalab.azure_storage.AzureStorage'", " AZURE_ACCOUNT_NAME = '{0}'".format(self.config.getServiceStorageAccountName()), " AZURE_ACCOUNT_KEY = '{0}'".format(storage_key), " AZURE_CONTAINER = '{0}'".format(self.config.getServicePublicStorageContainer()), " BUNDLE_AZURE_ACCOUNT_NAME = AZURE_ACCOUNT_NAME", " BUNDLE_AZURE_ACCOUNT_KEY = AZURE_ACCOUNT_KEY", " BUNDLE_AZURE_CONTAINER = '{0}'".format(self.config.getServiceBundleStorageContainer()), "", " SBS_NAMESPACE = '{0}'".format(self.config.getServiceBusNamespace()), " SBS_ISSUER = 'owner'", " SBS_ACCOUNT_KEY = '{0}'".format(namespace.default_key), " SBS_RESPONSE_QUEUE = 'jobresponsequeue'", " SBS_COMPUTE_QUEUE = 'windowscomputequeue'", "", " EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'", " EMAIL_HOST = '{0}'".format(self.config.getEmailHost()), " EMAIL_HOST_USER = '******'".format(self.config.getEmailUser()), " EMAIL_HOST_PASSWORD = '******'".format(self.config.getEmailPassword()), " EMAIL_PORT = 587", " EMAIL_USE_TLS = True", " DEFAULT_FROM_EMAIL = 'CodaLab <*****@*****.**>'", " SERVER_EMAIL = '*****@*****.**'", "", " # Django secret", " SECRET_KEY = '{0}'".format(self.config.getDjangoSecretKey()), "", " ADMINS = (('CodaLab', '*****@*****.**'),)", " MANAGERS = ADMINS", "", " DATABASES = {", " 'default': {", " 'ENGINE': '{0}',".format(self.config.getDatabaseEngine()), " 'NAME': '{0}',".format(self.config.getDatabaseName()), " 'USER': '******',".format(self.config.getDatabaseUser()), " 'PASSWORD': '******',".format(self.config.getDatabasePassword()), " 'HOST': '{0}',".format(self.config.getDatabaseHost()), " 'PORT': '{0}', ".format(self.config.getDatabasePort()), " 'OPTIONS' : {", " 'init_command': 'SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED',", " 'read_timeout': 5", " }", " }", " }", "", " BUNDLE_DB_NAME = '{0}'".format(self.config.getBundleServiceDatabaseName()), " BUNDLE_DB_USER = '******'".format(self.config.getBundleServiceDatabaseUser()), " BUNDLE_DB_PASSWORD = '******'".format(self.config.getBundleServiceDatabasePassword()), " BUNDLE_APP_ID = '{0}'".format(self.config.getBundleServiceAppId()), " BUNDLE_APP_KEY = '{0}'".format(self.config.getBundleServiceAppKey()), " BUNDLE_AUTH_URL = '{0}'".format(bundle_auth_url), "", " BUNDLE_SERVICE_URL = '{0}'".format(self.config.getBundleServiceUrl()), " BUNDLE_SERVICE_CODE_PATH = '/home/{0}/deploy/bundles'".format(self.config.getVirtualMachineLogonUsername()), " sys.path.append(BUNDLE_SERVICE_CODE_PATH)", " codalab.__path__ = extend_path(codalab.__path__, codalab.__name__)", "", ] preview = self.config.getShowPreviewFeatures() if preview >= 1: if preview == 1: lines.append(" PREVIEW_WORKSHEETS = True") if preview > 1: lines.append(" SHOW_BETA_FEATURES = True") lines.append("") return '\n'.join(lines)
class AzureServicesManager: # Storage container = 'vhds' windows_blob_url = 'blob.core.windows.net' # Linux linux_user = '******' linux_pass = '******' location = 'West US' # SSH Keys def __init__(self, subscription_id, cert_file): self.subscription_id = subscription_id self.cert_file = cert_file self.sms = ServiceManagementService(self.subscription_id, self.cert_file) @property def sms(self): return self.sms def list_locations(self): locations = self.sms.list_locations() for location in locations: print location def list_images(self): return self.sms.list_os_images() @utils.resource_not_found_handler def get_hosted_service(self, service_name): resp = self.sms.get_hosted_service_properties(service_name) properties = resp.hosted_service_properties return properties.__dict__ def delete_hosted_service(self, service_name): res = self.sms.check_hosted_service_name_availability(service_name) if not res.result: return self.sms.delete_hosted_service(service_name) def create_hosted_service(self, os_user, service_name=None, random=False): if not service_name: service_name = self.generate_cloud_service_name(os_user, random) available = False while not available: res = self.sms.check_hosted_service_name_availability(service_name) if not res.result: service_name = self.generate_cloud_service_name( os_user, random) else: available = True self.sms.create_hosted_service(service_name=service_name, label=service_name, location='West US') return service_name def create_virtual_machine(self, service_name, vm_name, image_name, role_size): media_link = self._get_media_link(vm_name) # Linux VM configuration hostname = '-'.join((vm_name, 'host')) linux_config = LinuxConfigurationSet(hostname, self.linux_user, self.linux_pass, True) # Hard disk for the OS os_hd = OSVirtualHardDisk(image_name, media_link) # Create vm result = self.sms.create_virtual_machine_deployment( service_name=service_name, deployment_name=vm_name, deployment_slot='production', label=vm_name, role_name=vm_name, system_config=linux_config, os_virtual_hard_disk=os_hd, role_size=role_size) request_id = result.request_id return {'request_id': request_id, 'media_link': media_link} def delete_virtual_machine(self, service_name, vm_name): resp = self.sms.delete_deployment(service_name, vm_name, True) self.sms.wait_for_operation_status(resp.request_id) result = self.sms.delete_hosted_service(service_name) return result def generate_cloud_service_name(self, os_user=None, random=False): if random: return utils.generate_random_name(10) return '-'.join((os_user, utils.generate_random_name(6))) @utils.resource_not_found_handler def get_virtual_machine_info(self, service_name, vm_name): vm_info = {} deploy_info = self.sms.get_deployment_by_name(service_name, vm_name) if deploy_info and deploy_info.role_instance_list: vm_info = deploy_info.role_instance_list[0].__dict__ return vm_info def list_virtual_machines(self): vm_list = [] services = self.sms.list_hosted_services() for service in services: deploys = service.deployments if deploys and deploys.role_instance_list: vm_name = deploys.role_instance_list[0].instance_name vm_list.append(vm_name) return vm_list def power_on(self, service_name, vm_name): resp = self.sms.start_role(service_name, vm_name, vm_name) return resp.request_id def power_off(self, service_name, vm_name): resp = self.sms.shutdown_role(service_name, vm_name, vm_name) return resp.request_id def soft_reboot(self, service_name, vm_name): resp = self.sms.restart_role(service_name, vm_name, vm_name) return resp.request_id def hard_reboot(self, service_name, vm_name): resp = self.sms.reboot_role_instance(service_name, vm_name, vm_name) return resp.request_id def attach_volume(self, service_name, vm_name, size, lun): disk_name = utils.generate_random_name(5, vm_name) media_link = self._get_media_link(vm_name, disk_name) self.sms.add_data_disk(service_name, vm_name, vm_name, lun, host_caching='ReadWrite', media_link=media_link, disk_name=disk_name, logical_disk_size_in_gb=size) def detach_volume(self, service_name, vm_name, lun): self.sms.delete_data_disk(service_name, vm_name, vm_name, lun, True) def get_available_lun(self, service_name, vm_name): try: role = self.sms.get_role(service_name, vm_name, vm_name) except Exception: return 0 disks = role.data_virtual_hard_disks luns = [disk.lun for disk in disks].sort() for i in range(1, 16): if i not in luns: return i return None def snapshot(self, service_name, vm_name, image_id, snanshot_name): image_desc = 'Snapshot for image %s' % vm_name image = CaptureRoleAsVMImage('Specialized', snanshot_name, image_id, image_desc, 'english') resp = self.sms.capture_vm_image(service_name, vm_name, vm_name, image) self.sms.wait_for_operation_status(resp.request_id) def _get_media_link(self, vm_name, filename=None, storage_account=None): """ The MediaLink should be constructed as: https://<storageAccount>.<blobLink>/<blobContainer>/<filename>.vhd """ if not storage_account: storage_account = self._get_or_create_storage_account() container = self.container filename = vm_name if filename is None else filename blob = vm_name + '-' + filename + '.vhd' media_link = "http://%s.%s/%s/%s" % ( storage_account, self.windows_blob_url, container, blob) return media_link def _get_or_create_storage_account(self): account_list = self.sms.list_storage_accounts() if account_list: return account_list[-1].service_name storage_account = utils.generate_random_name(10) description = "Storage account %s description" % storage_account label = storage_account + 'label' self.sms.create_storage_account(storage_account, description, label, location=self.location) return storage_account def _wait_for_operation(self, request_id, timeout=3000, failure_callback=None, failure_callback_kwargs=None): try: self.sms.wait_for_operation_status(request_id, timeout=timeout) except Exception as ex: if failure_callback and failure_callback_kwargs: failure_callback(**failure_callback_kwargs) raise ex
class AffinityGroupManagementServiceTest(AzureTestCase): def setUp(self): proxy_host = credentials.getProxyHost() proxy_port = credentials.getProxyPort() self.sms = ServiceManagementService( credentials.getSubscriptionId(), credentials.getManagementCertFile()) if proxy_host: self.sms.set_proxy(proxy_host, proxy_port) self.affinity_group_name = getUniqueNameBasedOnCurrentTime('utaffgrp') self.hosted_service_name = None self.storage_account_name = None def tearDown(self): try: if self.hosted_service_name is not None: self.sms.delete_hosted_service(self.hosted_service_name) except: pass try: if self.storage_account_name is not None: self.sms.delete_storage_account(self.storage_account_name) except: pass try: self.sms.delete_affinity_group(self.affinity_group_name) except: pass #--Helpers----------------------------------------------------------------- def _create_affinity_group(self, name): result = self.sms.create_affinity_group(name, 'tstmgmtaffgrp', 'West US', 'tstmgmt affinity group') self.assertIsNone(result) def _affinity_group_exists(self, name): try: props = self.sms.get_affinity_group_properties(name) return props is not None except: return False #--Test cases for affinity groups ------------------------------------ def test_list_affinity_groups(self): # Arrange self._create_affinity_group(self.affinity_group_name) # Act result = self.sms.list_affinity_groups() # Assert self.assertIsNotNone(result) self.assertTrue(len(result) > 0) group = None for temp in result: if temp.name == self.affinity_group_name: group = temp break self.assertIsNotNone(group) self.assertIsNotNone(group.name) self.assertIsNotNone(group.label) self.assertIsNotNone(group.description) self.assertIsNotNone(group.location) self.assertIsNotNone(group.capabilities) self.assertTrue(len(group.capabilities) > 0) def test_get_affinity_group_properties(self): # Arrange self.hosted_service_name = getUniqueNameBasedOnCurrentTime('utsvc') self.storage_account_name = getUniqueNameBasedOnCurrentTime( 'utstorage') self._create_affinity_group(self.affinity_group_name) self.sms.create_hosted_service(self.hosted_service_name, 'affgrptestlabel', 'affgrptestdesc', None, self.affinity_group_name) self.sms.create_storage_account(self.storage_account_name, self.storage_account_name + 'desc', self.storage_account_name + 'label', self.affinity_group_name) # Act result = self.sms.get_affinity_group_properties( self.affinity_group_name) # Assert self.assertIsNotNone(result) self.assertEqual(result.name, self.affinity_group_name) self.assertIsNotNone(result.label) self.assertIsNotNone(result.description) self.assertIsNotNone(result.location) self.assertIsNotNone(result.hosted_services[0]) self.assertEqual(result.hosted_services[0].service_name, self.hosted_service_name) self.assertEqual( result.hosted_services[0].hosted_service_properties.affinity_group, self.affinity_group_name) # not sure why azure does not return any storage service self.assertTrue(len(result.capabilities) > 0) def test_create_affinity_group(self): # Arrange label = 'tstmgmtaffgrp' description = 'tstmgmt affinity group' # Act result = self.sms.create_affinity_group(self.affinity_group_name, label, 'West US', description) # Assert self.assertIsNone(result) self.assertTrue(self._affinity_group_exists(self.affinity_group_name)) def test_update_affinity_group(self): # Arrange self._create_affinity_group(self.affinity_group_name) label = 'tstlabelupdate' description = 'testmgmt affinity group update' # Act result = self.sms.update_affinity_group(self.affinity_group_name, label, description) # Assert self.assertIsNone(result) props = self.sms.get_affinity_group_properties( self.affinity_group_name) self.assertEqual(props.label, label) self.assertEqual(props.description, description) def test_delete_affinity_group(self): # Arrange self._create_affinity_group(self.affinity_group_name) # Act result = self.sms.delete_affinity_group(self.affinity_group_name) # Assert self.assertIsNone(result) self.assertFalse(self._affinity_group_exists(self.affinity_group_name)) #--Test cases for locations ------------------------------------------ def test_list_locations(self): # Arrange # Act result = self.sms.list_locations() # Assert self.assertIsNotNone(result) self.assertTrue(len(result) > 0) self.assertIsNotNone(result[0].name) self.assertIsNotNone(result[0].display_name) self.assertIsNotNone(result[0].available_services) self.assertTrue(len(result[0].available_services) > 0)