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) self.log.debug('headers: {0}'.format( utils.secure_logging_content(dict(res.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 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, 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) self.log.debug('headers: {0}'.format( utils.secure_logging_content(dict(res.headers)))) headers = self.lowercase_headers(res.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 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) self.log.debug('headers: {0}'.format( utils.secure_logging_content(dict(res.headers)))) headers = self.lowercase_headers(res.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: if "AnotherOperationInProgress" in res.text: self.log.warn( "Another Operation In Progress, let's wait: {0}" .format(headers) ) raise RecoverableError('Another Operation In Progress') 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 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 # Before making the delete request, check if the resource exists or if not self.exists(name): self.log.info('Resource {0} does not exist anymore'.format(name)) return # Make the request res = self.client.request(method='delete', url='{0}/{1}'.format(self.endpoint, name)) self.log.debug('headers: {0}'.format( utils.secure_logging_content(dict(res.headers)))) headers = self.lowercase_headers(res.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: # Sometime it takes longer time to fully delete a parent node, so # lets wait for sometime to give it a chance to be fully deleted # This check will cover: [NicInUse, InUseSubnetCannotBeDeleted, # InUseNetworkSecurityGroupCannotBeDeleted] if "InUse" in res.json()['error']['code']: self.log.warn( "The resource currently in use, maybe a deletion for a " "parent node using it still in progress, let's wait: {0}". format(headers)) raise RecoverableError('Resource in use') 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: # Sometime it takes longer time to fully delete a parent node, so # lets wait for sometime to give it a chance to be fully deleted # Checking against message content as codes are not informative # [OperationNotAllowed, AccountIsLocked] if "ensure that it does not contain any VM" in res.text or \ "its artifacts being in use" in res.text: self.log.warn( "The resource is still in use, maybe a deletion for a " "parent node using it still in progress, let's wait: {0}". format(headers)) raise RecoverableError('Resource in use') if "AnotherOperationInProgress" in res.text: self.log.warn( "Another Operation In Progress, let's wait: {0}".format( headers)) raise RecoverableError('Another Operation In Progress') 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 create(self, name, params=None): ''' Creates a new resource :param string name: Name of the new resource :param dict params: Parameters to be passed as-is to the Azure API :raises: :exc:`cloudify.exceptions.RecoverableError`, :exc:`cloudify.exceptions.NonRecoverableError`, :exc:`requests.RequestException` ''' self.log.info('Creating {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 201 (CREATED) - The operation succeeded if res.status_code == httplib.CREATED: 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)) 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 200 (OK) - The resource already exists elif res.status_code == httplib.OK: if not self.ctx.instance._modifiable: self.log.warn('Not retrying async operation, ' 'because NodeInstanceContext is not modifiable. ' 'headers: {0}'.format(headers)) return if headers.get('azure-asyncoperation'): 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 raise RecoverableError( 'Expected HTTP status code {0}, recieved {1}'.format( httplib.CREATED, res.status_code))