def delete(self, name): ''' Deletes an existing resource :param string name: Name of the existing resource :raises: :exc:`cloudify.exceptions.RecoverableError`, :exc:`cloudify.exceptions.NonRecoverableError`, :exc:`requests.RequestException` ''' self.log.info('Deleting {0} "{1}"'.format(self.name, name)) if self.ctx.instance._modifiable: self.ctx.instance.runtime_properties['async_op'] = None # Make the request res = self.client.request( method='delete', url='{0}/{1}'.format(self.endpoint, name)) # Convert headers from CaseInsensitiveDict to Dict headers = dict(res.headers) self.log.debug('headers: {0}'.format( utils.secure_logging_content(headers))) # HTTP 200 (OK) - The operation is successful and complete if res.status_code == httplib.OK: return # HTTP 204 (NO_CONTENT) - The resource doesn't exist elif res.status_code == httplib.NO_CONTENT: return # HTTP 202 (ACCEPTED) - The operation has started but is asynchronous elif res.status_code == httplib.ACCEPTED: if not headers.get('location'): raise RecoverableError( 'HTTP 202 ACCEPTED but no Location header present') if not self.ctx.instance._modifiable: self.log.warn( 'Not retrying async operation, ' 'because NodeInstanceContext is not modifiable. ' 'headers: {0}'.format(headers) ) return self.ctx.instance.runtime_properties['async_op'] = headers return ctx.operation.retry( 'Operation: "{0}" started' .format(self.get_operation_id(headers)), retry_after=self.get_retry_after(headers)) # HTTP 400 (BAD_REQUEST) - We're sending bad data elif res.status_code == httplib.BAD_REQUEST: self.log.info('BAD REQUEST: response: {}'.format( utils.secure_logging_content(res.content))) raise UnexpectedResponse( 'Recieved HTTP 400 BAD REQUEST', res.json()) # HTTP 409 (CONFLICT) - Operation failed elif res.status_code == httplib.CONFLICT: raise NonRecoverableError( 'Operation failed. (code={0}, data={1})' .format(res.status_code, res.text)) # All other errors will be treated as recoverable elif res.status_code != httplib.CREATED: raise RecoverableError( 'Expected HTTP status code {0}, recieved {1}' .format(httplib.CREATED, res.status_code))
def get(self, group_name, vm_name): self.logger.info("Get virtual_machine...{0}".format(vm_name)) virtual_machine = self.client.virtual_machines.get( resource_group_name=group_name, vm_name=vm_name).as_dict() self.logger.info('Get virtual_machine result: {0}'.format( utils.secure_logging_content(virtual_machine))) return virtual_machine
def get(self, group_name, plan_name): self.logger.info("Get plan...{0}".format(plan_name)) plan = self.client.app_service_plans.get( resource_group_name=group_name, name=plan_name).as_dict() self.logger.info('Get plan result: {0}'.format( utils.secure_logging_content(plan))) return plan
def get(self, group_name, resource_name): self.logger.info("Get managed_cluster...{0}".format(resource_name)) managed_cluster = self.client.managed_clusters.get( resource_group_name=group_name, resource_name=resource_name).as_dict() self.logger.info('Get managed_cluster result: {0}'.format( utils.secure_logging_content(managed_cluster))) return managed_cluster
def operation_complete(self, op_info): ''' Checks the status of an asynchronous operation :param dict op_info: Long-running operation headers :raises: :exc:`cloudify.exceptions.RecoverableError`, :exc:`cloudify.exceptions.NonRecoverableError`, :exc:`requests.RequestException` ''' # Get the operation ID op_id = op_info.get('x-ms-request-id', 'not-reported') # Make the request self.log.info('Checking status of operation "{0}"' .format(op_id)) op_url = op_info.get('location') or \ op_info.get('azure-asyncoperation') res = self.client.request(method='get', url=op_url) # Convert headers from CaseInsensitiveDict to Dict headers = dict(res.headers) self.log.debug('headers: {0}'.format( utils.secure_logging_content(headers))) # HTTP 200 (OK) - Operation is successful and complete if res.status_code == httplib.OK: if self.validate_res_json(res) == 'InProgress': return ctx.operation.retry( 'Operation: "{0}" still pending' .format(self.get_operation_id(headers)), retry_after=self.get_retry_after(headers)) self.ctx.instance.runtime_properties['async_op'] = None return # Clear the async state self.ctx.instance.runtime_properties['async_op'] = None # HTTP 202 (ACCEPTED) - Operation is still pending if res.status_code == httplib.ACCEPTED: if not headers.get('location'): raise RecoverableError( 'HTTP 202 ACCEPTED but no Location header present') self.ctx.instance.runtime_properties['async_op'] = headers return ctx.operation.retry( 'Operation: "{0}" still pending' .format(self.get_operation_id(headers)), retry_after=self.get_retry_after(headers)) # HTTP 409 (CONFLICT) - Operation failed elif res.status_code == httplib.CONFLICT: raise NonRecoverableError( 'Operation "{0}" failed. (code={1}, data={2})' .format(op_id, res.status_code, res.text)) # HTTP 500 (INTERNAL_SERVER_ERROR) - Operation time out elif res.status_code == httplib.INTERNAL_SERVER_ERROR: # Preserve op_info in case it's really just a time-out self.ctx.instance.runtime_properties['async_op'] = op_info return ctx.operation.retry( 'Operation: "{0}" still pending' .format(self.get_operation_id(headers)), retry_after=self.get_retry_after(headers)) res.raise_for_status()
def get(self, group_name, network_interface_name): self.logger.info( "Get network_interface...{0}".format(network_interface_name)) network_interface = self.client.network_interfaces.get( resource_group_name=group_name, network_interface_name=network_interface_name).as_dict() self.logger.info('Get network_interface result: {0}'.format( utils.secure_logging_content(network_interface))) return network_interface
def get(self, group_name, virtual_network_name): self.logger.info( "Get virtual_network...{0}".format(virtual_network_name)) virtual_network = self.client.virtual_networks.get( resource_group_name=group_name, virtual_network_name=virtual_network_name).as_dict() self.logger.info('Get virtual_network result: {0}'.format( utils.secure_logging_content(virtual_network))) return virtual_network
def get(self, group_name, container_service_name): self.logger.info( "Get container_service...{0}".format(container_service_name)) container_service = self.client.container_services.get( resource_group_name=group_name, container_service_name=container_service_name).as_dict() self.logger.info('Get container_service result: {0}'.format( utils.secure_logging_content(container_service))) return container_service
def get(self, name=None): ''' Gets details about an existing resource :param string name: Name of the existing resource :returns: Response data from the Azure API call :rtype: dict :raises: :exc:`cloudify.exceptions.RecoverableError`, :exc:`cloudify.exceptions.NonRecoverableError`, :exc:`requests.RequestException` ''' self.log.info('Retrieving {0} "{1}"'.format(self.name, name)) # Make the request if name: url = '{0}/{1}'.format(self.endpoint, name) else: url = self.endpoint res = self.client.request( method='get', url=url) # Convert headers from CaseInsensitiveDict to Dict headers = dict(res.headers) self.log.debug('headers: {0}'.format( utils.secure_logging_content(headers))) # Check the response # HTTP 202 (ACCEPTED) - The operation has started but is asynchronous if res.status_code == httplib.ACCEPTED: if not headers.get('location'): raise RecoverableError( 'HTTP 202 ACCEPTED but no Location header present') self.ctx.instance.runtime_properties['async_op'] = headers return ctx.operation.retry( 'Operation: "{0}" started' .format(self.get_operation_id(headers)), retry_after=self.get_retry_after(headers)) # HTTP 200 (OK) - The resource already exists elif res.status_code == httplib.OK: return res.json() # If Azure sent a 400, we're sending bad data if res.status_code == httplib.BAD_REQUEST: self.log.info('BAD REQUEST: response: {}'.format(res.content)) raise UnexpectedResponse( 'Recieved HTTP 400 BAD REQUEST', res.json()) # If Azure sent a 404, the resource doesn't exist (yet?) if res.status_code == httplib.NOT_FOUND: raise RecoverableError( '{0} "{1}" doesn\'t exist (yet?)' .format(self.name, name)) # All other errors will be treated as recoverable if res.status_code != httplib.CREATED: raise RecoverableError( 'Expected HTTP status code {0}, recieved {1}' .format(httplib.CREATED, res.status_code)) return res.json()
def create_or_update(self, group_name, plan_name, params): self.logger.info("Create/Updating plan...{0}".format(plan_name)) create_async_operation = \ self.client.app_service_plans.create_or_update( resource_group_name=group_name, name=plan_name, app_service_plan=params, ) create_async_operation.wait() plan = create_async_operation.result().as_dict() self.logger.info('Create plan result: {0}'.format( utils.secure_logging_content(plan))) return plan
def create_or_update(self, group_name, virtual_network_name, params): self.logger.info("Create/Updating virtual_network...{0}".format( virtual_network_name)) async_vnet_creation = self.client.virtual_networks.create_or_update( resource_group_name=group_name, virtual_network_name=virtual_network_name, parameters=params, ) async_vnet_creation.wait() virtual_network = async_vnet_creation.result().as_dict() self.logger.info('create virtual_network result: {0}'.format( utils.secure_logging_content(virtual_network))) return virtual_network
def create_or_update(self, group_name, vm_name, params): self.logger.info( "Create/Updating virtual_machine...{0}".format(vm_name)) create_async_operation = self.client.virtual_machines.create_or_update( resource_group_name=group_name, vm_name=vm_name, parameters=params, ) create_async_operation.wait() virtual_machine = create_async_operation.result().as_dict() self.logger.info('Create virtual_machine result: {0}'.format( utils.secure_logging_content(virtual_machine))) return virtual_machine
def exists(self, name=None): ''' Determines if a resource exists or not :param string name: Name of the existing resource :returns: True if resource exists, False if it doesn't :rtype: boolean :raises: :exc:`cloudify_azure.exceptions.UnexpectedResponse`, :exc:`requests.RequestException` ''' self.log.info('Checking {0} "{1}"'.format(self.name, name)) # Make the request if name: url = '{0}/{1}'.format(self.endpoint, name) else: url = self.endpoint res = self.client.request( method='get', url=url) # Convert headers from CaseInsensitiveDict to Dict headers = dict(res.headers) self.log.debug('headers: {0}'.format( utils.secure_logging_content(headers))) # Check the response # HTTP 202 (ACCEPTED) - An asynchronous operation has started if res.status_code == httplib.ACCEPTED: return True # HTTP 200 (OK) - The resource already exists elif res.status_code == httplib.OK: return True # HTTP 404 (NOT_FOUND) - The resource can't be found elif res.status_code == httplib.NOT_FOUND: return False # HTTP 400 (BAD_REQUEST) - The resource name is invalid elif res.status_code == httplib.BAD_REQUEST: raise ValueError('Invalid resource name') raise UnexpectedResponse( 'Recieved unexpected HTTP ({0}) response' .format(res.status_code), res.json())
def update(self, name, params, force=False): ''' Updates an existing resource :param string name: Name of the resource :param dict params: Parameters to update the resource with :param boolean force: Forces the params to be sent without merging with the resources' existing data :raises: :exc:`cloudify.exceptions.RecoverableError`, :exc:`cloudify.exceptions.NonRecoverableError`, :exc:`requests.RequestException` ''' if not force: # Get the existing data (since partial updates seem to # be in a questionable state on Azure's side of things) data = self.get(name) # Updating the data with our new data params = utils.dict_update(data, params) self.log.info('Updating {0} "{1}"'.format(self.name, name)) if self.ctx.instance._modifiable: self.ctx.instance.runtime_properties['async_op'] = None # Sanitize input data params = self.sanitize_json_input(params) # Make the request res = self.client.request( method='put', url='{0}/{1}'.format(self.endpoint, name), json=params) # Convert headers from CaseInsensitiveDict to Dict headers = dict(res.headers) self.log.debug('headers: {0}'.format( utils.secure_logging_content(headers))) # Check the response # HTTP 202 (ACCEPTED) - The operation has started but is asynchronous if res.status_code == httplib.ACCEPTED: if not headers.get('location'): raise RecoverableError( 'HTTP 202 ACCEPTED but no Location header present') if not self.ctx.instance._modifiable: self.log.warn( 'Not retrying async operation, ' 'because NodeInstanceContext is not modifiable. ' 'headers: {0}'.format(headers) ) return self.ctx.instance.runtime_properties['async_op'] = headers return ctx.operation.retry( 'Operation: "{0}" started' .format(self.get_operation_id(headers)), retry_after=self.get_retry_after(headers)) # HTTP 200 (OK) - The resource already exists elif res.status_code == httplib.OK: if headers.get('azure-asyncoperation'): if not self.ctx.instance._modifiable: self.log.warn( 'Not retrying async operation, ' 'because NodeInstanceContext is not modifiable. ' 'headers: {0}'.format(headers) ) return self.ctx.instance.runtime_properties['async_op'] = headers return ctx.operation.retry( 'Operation: "{0}" started' .format(self.get_operation_id(headers)), retry_after=self.get_retry_after(headers)) self.log.warn('{0} already exists. Using resource.' .format(self.name)) return # HTTP 400 (BAD_REQUEST) - We're sending bad data elif res.status_code == httplib.BAD_REQUEST: self.log.info('BAD REQUEST: response: {}'.format( utils.secure_logging_content(res.content))) raise UnexpectedResponse( 'Recieved HTTP 400 BAD REQUEST', res.json()) # HTTP 409 (CONFLICT) - Operation failed elif res.status_code == httplib.CONFLICT: raise NonRecoverableError( 'Operation failed. (code={0}, data={1})' .format(res.status_code, res.text)) # All other errors will be treated as recoverable elif res.status_code != httplib.CREATED: raise RecoverableError( 'Expected HTTP status code {0}, recieved {1}' .format(httplib.CREATED, res.status_code))
def request(self, **kwargs): ''' Builds, and executes, a request to the Microsoft Azure API service. The parameters are passed as-is (with one exception, see notes) to the underlying requests.Session.request() function. .. note:: The *url* parameter should be passed in as a root path starting AFTER the base endpoint https://management.azure.com/subscriptions/{subscription-id}. For example, to send a request to provision a Resource Group, the URL parameter should be */resourcegroups/{rg-name}*. Also, *api-version* request parameter will be added automatically if not provided. If a full URL is passed in (any string not starting with a forward slash), it will be passed as-is to the underlying request() function. :returns: A configured requests.Session instance :rtype: :class:`requests.Response` ''' # Get current credentials creds = utils.get_credentials(_ctx=self.ctx) # Rework the URL url = kwargs.pop('url', '') # Check if this is a relative operation if url.startswith('/'): # Add the endpoint and subscription ID url = '{0}/subscriptions/{1}{2}'.format( creds.endpoints_resource_manager, creds.subscription_id, url) kwargs['url'] = url # SSL Verification kwargs['verify'] = creds.endpoint_verify # Update the params list with the api version url_params = urlparse.parse_qs(urlparse.urlparse(url).query) if not url_params.get('api-version'): params = kwargs.pop('params', dict()) params['api-version'] = params.get('api-version', self.api_version) kwargs['params'] = params # Log the request details self.log.info('request({0})'.format(utils.secure_logging_content( kwargs))) res = self.session.request(**kwargs) # Only get data if there's data to be gotten data = None if res.text: data = res.json() self.log.debug('response: ' '(status={0}, data={1})'.format( res.status_code, json.dumps(data, indent=2))) # Check for 401 Unauthorized if res.status_code == 401: # We received an explicit credentials rejection if 'error' in data and data['error'] == 'invalid_client': raise exceptions.InvalidCredentials( 'Azure rejected the provided credentials', data) # All other rejection reason will be caught here raise exceptions.UnauthorizedRequest(data) return res