def validate_json(json_string, schema): """ invokes the validate function of jsonschema """ schema_dict = json.loads(schema) schema_title = schema_dict['title'] try: validate(json_string, schema_dict) except ValidationError as err: title = 'JSON validation failed: {}'.format(err.message) description = 'Failed validator: {} : {}'.format( err.validator, err.validator_value) LOG.error(title) LOG.error(description) raise InvalidFormatError( title=title, description=description, ) except SchemaError as err: title = 'SchemaError: Unable to validate JSON: {}'.format(err) description = 'Invalid Schema: {}'.format(schema_title) LOG.error(title) LOG.error(description) raise AppError(title=title, description=description) except FormatError as err: title = 'FormatError: Unable to validate JSON: {}'.format(err) description = 'Invalid Format: {}'.format(schema_title) LOG.error(title) LOG.error(description) raise AppError(title=title, description=description)
def get_configdocs_status(self): """ Returns a list of the configdocs, committed or in buffer, and their current committed and buffer statuses """ configdocs_status = [] # If there is no committed revision, then it's 0. # new revision is ok because we just checked for buffer emptiness old_revision_id = self._get_committed_rev_id() or 0 new_revision_id = self._get_buffer_rev_id() or old_revision_id try: diff = self.deckhand.get_diff(old_revision_id=old_revision_id, new_revision_id=new_revision_id) except DeckhandResponseError as drex: raise AppError( title='Unable to retrieve revisions', description=( 'Deckhand has responded unexpectedly: {}:{}'.format( drex.status_code, drex.response_message)), status=falcon.HTTP_500, retry=False, ) for collection_id in diff: collection = {"collection_name": collection_id} if diff[collection_id] in [ "unmodified", "modified", "created", "deleted" ]: collection['buffer_status'] = diff[collection_id] if diff[collection_id] == "created": collection['committed_status'] = 'not present' else: collection['committed_status'] = 'present' else: raise AppError( title='Invalid collection status', description=( 'Collection_id, {} has an invalid collection status. ' 'unmodified, modified, created, and deleted are the' ' only valid collection statuses.', collection_id), status=falcon.HTTP_500, retry=False, ) configdocs_status.append(collection) return configdocs_status
def _get_versions_name_id(self, ordered_versions): # Get version id old_version_id = self.get_revision_id(ordered_versions[0]) new_version_id = self.get_revision_id(ordered_versions[1]) # Get revision name old_version_name = ordered_versions[0] new_version_name = ordered_versions[1] # Check that revision id of LAST_SITE_ACTION and SUCCESSFUL_SITE_ACTION # is not None for name, rev_id in [(old_version_name, old_version_id), (new_version_name, new_version_id)]: if (name in [LAST_SITE_ACTION, SUCCESSFUL_SITE_ACTION] and rev_id is None): raise AppError( title='Version does not exist', description='{} version does not exist'.format(name), status=falcon.HTTP_404, retry=False) # Set to 0 if there is no committed version if old_version_name == COMMITTED and old_version_id is None: old_version_id = 0 new_version_id = self.get_revision_id(BUFFER) or 0 # Set new_version_id if None if new_version_id is None: new_version_id = (self.get_revision_id(BUFFER) or old_version_id or 0) return (old_version_name, new_version_name, old_version_id, new_version_id)
def is_collection_in_buffer(self, collection_id): """ Returns if the collection is represented in the buffer """ if self.is_buffer_empty(): return False # If there is no committed revision, then it's 0. # new revision is ok because we just checked for buffer emptiness old_revision_id = self._get_committed_rev_id() or 0 try: diff = self.deckhand.get_diff( old_revision_id=old_revision_id, new_revision_id=self._get_buffer_rev_id()) # the collection is in the buffer if it's not unmodified return diff.get(collection_id, 'unmodified') != 'unmodified' except DeckhandResponseError as drex: raise AppError( title='Unable to retrieve revisions', description=( 'Deckhand has responded unexpectedly: {}:{}'.format( drex.status_code, drex.response_message)), status=falcon.HTTP_500, retry=False, )
def _get_service_type(endpoint): """ Because these values should not be used until after initialization, they cannot be directly associated with the enum. Thie method takes the enum value and retrieves the values when accessed the first time. :param Endpoints endpoint: The endpoint to look up :returns: The service type value for the named endpoint :rtype: str :raises AppError: if not provided a valid Endpoints enumeration value """ if isinstance(endpoint, Endpoints): endpoint_values = { Endpoints.SHIPYARD: CONF.shipyard.service_type, Endpoints.DRYDOCK: CONF.drydock.service_type, Endpoints.ARMADA: CONF.armada.service_type, Endpoints.DECKHAND: CONF.deckhand.service_type, Endpoints.PROMENADE: CONF.promenade.service_type } return endpoint_values.get(endpoint) raise AppError( title='Endpoint is not known', description=( 'Shipyard is trying to reach an unknown endpoint: {}'.format( endpoint.name)), status=falcon.HTTP_500, retry=False)
def _get_ordered_versions(self, versions=None): """returns a list of ordered versions""" # Default ordering def_order = [SUCCESSFUL_SITE_ACTION, LAST_SITE_ACTION, COMMITTED, BUFFER] # Defaults to COMMITTED and BUFFER if versions is None: versions = [COMMITTED, BUFFER] elif not len(versions) == 2: raise AppError( title='Incorrect number of versions for comparison', description=( 'User must pass in 2 valid versions for comparison'), status=falcon.HTTP_400, retry=False) elif versions[0] == versions[1]: raise AppError( title='Versions must be different for comparison', description=( 'Versions must be unique in order to perform comparison'), status=falcon.HTTP_400, retry=False) for version in versions: if version not in def_order: raise AppError( title='Invalid version detected', description=( '{} is not a valid version, which include: ' '{}'.format(version, ', '.join(def_order))), status=falcon.HTTP_400, retry=False) # Higher index in the def_order list will mean that it is a newer # version. We will swap the order and sort the version if need be. if def_order.index(versions[0]) > def_order.index(versions[1]): ordered_versions = list(reversed(versions)) else: ordered_versions = versions return ordered_versions
def secure_handler(slf, req, resp, *args, **kwargs): ctx = req.context policy_eng = ctx.policy_engine LOG.info("Policy Engine: %s", policy_eng.__class__.__name__) # perform auth LOG.info("Enforcing policy %s on request %s", self.action, ctx.request_id) # policy engine must be configured if policy_eng is None: LOG.error( "Error-Policy engine required-action: %s", self.action) raise AppError( title="Auth is not being handled by any policy engine", status=falcon.HTTP_500, retry=False ) authorized = False try: if policy_eng.authorize(self.action, ctx): # authorized LOG.info("Request is authorized") authorized = True except: # couldn't service the auth request LOG.error( "Error - Expectation Failed - action: %s", self.action) raise ApiError( title="Expectation Failed", status=falcon.HTTP_417, retry=False ) if authorized: return f(slf, req, resp, *args, **kwargs) else: LOG.error("Auth check failed. Authenticated:%s", ctx.authenticated) # raise the appropriate response exeception if ctx.authenticated: LOG.error("Error: Forbidden access - action: %s", self.action) raise ApiError( title="Forbidden", status=falcon.HTTP_403, description="Credentials do not permit access", retry=False ) else: LOG.error("Error - Unauthenticated access") raise ApiError( title="Unauthenticated", status=falcon.HTTP_401, description="Credentials are not established", retry=False )
def tag_buffer(self, tag): """Convenience method to tag the buffer version.""" buffer_rev_id = self.get_revision_id(BUFFER) if buffer_rev_id is None: raise AppError( title='Unable to tag buffer as {}'.format(tag), description=('Buffer revision id could not be determined from' 'Deckhand'), status=falcon.HTTP_500, retry=False) self.tag_revision(buffer_rev_id, tag)
def _get_revision_dict(self): """ Returns a dictionary with values representing the revisions in Deckhand that Shipyard cares about - committed, buffer, and latest, as well as a count of revisions Committed and buffer are revisions associated with the shipyard tags. If either of those are not present in deckhand, returns None for the value. Latest holds the revision information for the newest revision. """ # return the cached instance version of the revision dict. if self.revision_dict is not None: return self.revision_dict # or generate one for the cache committed_revision = None buffer_revision = None latest_revision = None revision_count = 0 try: revisions = self.deckhand.get_revision_list() revision_count = len(revisions) if revisions: latest_revision = revisions[-1] for revision in reversed(revisions): tags = revision.get('tags', []) if COMMITTED in tags or ROLLBACK_COMMIT in tags: committed_revision = revision break else: # there are buffer revisions, only grab it on # the first pass through # if the first revision is committed, or if there # are no revsisions, buffer revsision stays None if buffer_revision is None: buffer_revision = revision except NoRevisionsExistError: # the values of None/None/None/0 are fine pass except DeckhandResponseError as drex: raise AppError( title='Unable to retrieve revisions', description=( 'Deckhand has responded unexpectedly: {}:{}'.format( drex.status_code, drex.response_message)), status=falcon.HTTP_500, retry=False) self.revision_dict = { COMMITTED: committed_revision, BUFFER: buffer_revision, LATEST: latest_revision, REVISION_COUNT: revision_count } return self.revision_dict
def check_auth(ctx, rule): """Checks the authorization to the requested rule :param ctx: the request context for the action being performed :param rule: the name of the policy rule to validate the user in the context against Returns if authorized, otherwise raises an ApiError. """ try: policy_eng = ctx.policy_engine LOG.info("Policy Engine: %s", policy_eng.__class__.__name__) # perform auth LOG.info("Enforcing policy %s on request %s", rule, ctx.request_id) # policy engine must be configured if policy_eng is None: LOG.error( "Error-Policy engine required-action: %s", rule) raise AppError( title="Auth is not being handled by any policy engine", status=falcon.HTTP_500, retry=False ) if policy_eng.authorize(rule, ctx): # authorized - log and return LOG.info("Request to %s is authorized", rule) return except Exception as ex: # couldn't service the auth request LOG.exception("Error - Expectation Failed - action: %s", rule) raise ApiError( title="Expectation Failed", status=falcon.HTTP_417, retry=False ) # raise the appropriate response exeception if ctx.authenticated: # authenticated but not authorized LOG.error("Error: Forbidden access - action: %s", rule) raise ApiError( title="Forbidden", status=falcon.HTTP_403, description="Credentials do not permit access", retry=False ) else: LOG.error("Error - Unauthenticated access") raise ApiError( title="Unauthenticated", status=falcon.HTTP_401, description="Credentials are not established", retry=False )
def _get_ks_session(): # Establishes a keystone session try: auth = loading.load_auth_from_conf_options(CONF, "keystone_authtoken") return session.Session(auth=auth) except exc.AuthorizationFailure as aferr: LOG.error('Could not authorize against keystone: %s', str(aferr)) raise AppError( title='Could not authorize Shipyard against Keystone', description=( 'Keystone has rejected the authorization request by Shipyard'), status=falcon.HTTP_500, retry=False)
def get_validations_for_buffer(self): """ Convenience method to do validations for buffer version. """ buffer_rev_id = self._get_buffer_rev_id() if buffer_rev_id: return self.get_validations_for_revision(buffer_rev_id) raise AppError( title='Unable to start validation of buffer', description=('Buffer revision id could not be determined from' 'Deckhand'), status=falcon.HTTP_500, retry=False)
def _get_ks_session(): # Establishes a keystone session keystone_auth = {} for attr in ('auth_url', 'password', 'project_domain_name', 'project_name', 'username', 'user_domain_name'): keystone_auth[attr] = CONF.get('keystone_authtoken').get(attr) try: auth = v3.Password(**keystone_auth) return session.Session(auth=auth) except AuthorizationFailure as aferr: LOG.error('Could not authorize against keystone: %s', str(aferr)) raise AppError( title='Could not authorize Shipyard against Keystone', description=( 'Keystone has reqjected the authorization request by Shipyard' ), status=falcon.HTTP_500, retry=False)
def check_intermediate_commit(self): # Initialize variable list_of_committed_rev = [] try: # Get the list of all revisions present in Deckhand all_revisions = self.deckhand.get_revision_list() except NoRevisionsExistError: # the values of None/None/None/0 are fine pass except DeckhandResponseError as drex: raise AppError( title='Unable to retrieve revisions', description=( 'Deckhand has responded unexpectedly: {}:{}'.format( drex.status_code, drex.response_message)), status=falcon.HTTP_500, retry=False) if all_revisions: # Get the list of 'committed' revisions for revision in all_revisions: if 'committed' in revision['tags']: list_of_committed_rev.append(revision) # This is really applicable for scenarios where multiple # configdocs commits and site actions were performed. Hence # we should expect at least 2 'committed' revisions to be # present in deckhand. # # We will check the second last most recent committed revision # to see if a site-action has been executed on it if len(list_of_committed_rev) > 1: revision_tags = list_of_committed_rev[-2]['tags'] if ('site-action-success' not in revision_tags and 'site-action-failure' not in revision_tags): return True return False
def get_endpoint(endpoint): """ Wraps calls to keystone for lookup of an endpoint by service type :param Endpoints endpoint: The endpoint to look up :returns: The url string of the endpoint :rtype: str :raises AppError: if the endpoint cannot be resolved """ service_type = _get_service_type(endpoint) try: return _get_ks_session().get_endpoint(interface='internal', service_type=service_type) except exc.EndpointNotFound: LOG.error('Could not find an internal interface for %s', endpoint.name) raise AppError( title='Can not access service endpoint', description=( 'Keystone catalog has no internal endpoint for service type: ' '{}'.format(service_type)), status=falcon.HTTP_500, retry=False)
def get_nodes_provision_status(drydock): # Calls Drydock client to fetch node provision status try: nodes = drydock.get_nodes() nodes_status = [] for node in nodes: nodes_status.append({ 'hostname': node.get('hostname'), 'status': node.get('status_name') }) except dderrors.ClientError as ddex: raise AppError( title='Unable to retrieve nodes status', description=('Drydock has responded unexpectedly: ' '{}'.format(ddex.response_message)), status=falcon.HTTP_500, retry=False, ) machine_status = {'nodes_provision_status': nodes_status} return machine_status
def get_machines_powerstate(drydock): # Calls Drydock client to fetch nodes power state try: machines = drydock.get_nodes() machines_ps = [] for machine in machines: machines_ps.append({ 'hostname': machine.get('hostname'), 'power_state': machine.get('power_state') }) except dderrors.ClientError as ddex: raise AppError( title='Unable to retrieve nodes power-state', description=('Drydock has responded unexpectedly: {}'.format( ddex.response_message)), status=falcon.HTTP_500, retry=False, ) machines_powerstate = {'machines_powerstate': machines_ps} return machines_powerstate
def get_configdocs_status(self, versions=None): """ :param versions: A list of 2 versions. Defaults to buffer and commmitted if None. Returns a list of the configdocs based on their versions and statuses """ configdocs_status = [] # Get ordered versions ordered_versions = self._get_ordered_versions(versions) # Get version name and id old_version_name, new_version_name, old_version_id, new_version_id = ( self._get_versions_name_id(ordered_versions)) try: diff = self.deckhand.get_diff(old_revision_id=old_version_id, new_revision_id=new_version_id) except DeckhandResponseError as drex: raise AppError( title='Unable to retrieve revisions', description=( 'Deckhand has responded unexpectedly: {}:{}'.format( drex.status_code, drex.response_message)), status=falcon.HTTP_500, retry=False, ) for collection_id in diff: collection = {"collection_name": collection_id} if diff[collection_id] in [ "unmodified", "modified", "created", "deleted" ]: collection['base_version'] = old_version_name collection['base_revision'] = old_version_id collection['new_version'] = new_version_name collection['new_revision'] = new_version_id collection['new_status'] = diff[collection_id] if diff[collection_id] == "created": collection['base_status'] = 'not present' else: collection['base_status'] = 'present' else: raise AppError( title='Invalid collection status', description=( 'Collection_id, {} has an invalid collection status. ' 'unmodified, modified, created, and deleted are the' ' only valid collection statuses.', collection_id), status=falcon.HTTP_500, retry=False) configdocs_status.append(collection) return configdocs_status
def _get_revision_dict(self): """ Returns a dictionary with values representing the revisions in Deckhand that Shipyard cares about - committed, buffer, latest, last_site_action and successful_site_action, as well as a count of revisions. Committed and buffer are revisions associated with the shipyard tags. If either of those are not present in deckhand, returns None for the value. Latest holds the revision information for the newest revision. Last site action holds the revision information for the most recent site action Successful site action holds the revision information for the most recent successfully executed site action. """ # return the cached instance version of the revision dict. if self.revision_dict is not None: return self.revision_dict # or generate one for the cache committed_revision = None buffer_revision = None last_site_action = None latest_revision = None revision_count = 0 successful_site_action = None try: revisions = self.deckhand.get_revision_list() revision_count = len(revisions) if revisions: # Retrieve latest revision latest_revision = revisions[-1] # Get required revision for revision in reversed(revisions): tags = revision.get('tags', []) if (committed_revision is None and (COMMITTED in tags or ROLLBACK_COMMIT in tags)): committed_revision = revision else: # there are buffer revisions, only grab it on # the first pass through # if the first revision is committed, or if there # are no revsisions, buffer revsision stays None if (committed_revision is None and buffer_revision is None): buffer_revision = revision # Get the revision of the last successful site action if (successful_site_action is None and SITE_ACTION_SUCCESS in tags): successful_site_action = revision # Get the revision of the last site action if (last_site_action is None and (SITE_ACTION_SUCCESS in tags or SITE_ACTION_FAILURE in tags)): last_site_action = revision except NoRevisionsExistError: # the values of None/None/None/0 are fine pass except DeckhandResponseError as drex: raise AppError( title='Unable to retrieve revisions', description=( 'Deckhand has responded unexpectedly: {}:{}'.format( drex.status_code, drex.response_message)), status=falcon.HTTP_500, retry=False) self.revision_dict = { BUFFER: buffer_revision, COMMITTED: committed_revision, LAST_SITE_ACTION: last_site_action, LATEST: latest_revision, REVISION_COUNT: revision_count, SUCCESSFUL_SITE_ACTION: successful_site_action } return self.revision_dict