Ejemplo n.º 1
0
    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())
Ejemplo n.º 2
0
    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))
Ejemplo n.º 3
0
    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()
Ejemplo n.º 4
0
    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))
Ejemplo n.º 5
0
    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))
Ejemplo n.º 6
0
    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))