Update docstrings
Change-Id: I2be4437a5afb221f56f4d93a24be0356e7abc033
This commit is contained in:
@@ -67,7 +67,7 @@ class ContextMiddleware(base_middleware.ConfigurableMiddleware):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def process_request(req):
|
def process_request(req):
|
||||||
"""Convert authentication information into a request context
|
"""Convert authentication information into a request context.
|
||||||
|
|
||||||
Generate a RequestContext object from the available
|
Generate a RequestContext object from the available
|
||||||
authentication headers and store on the 'context' attribute
|
authentication headers and store on the 'context' attribute
|
||||||
@@ -88,7 +88,7 @@ class ContextMiddleware(base_middleware.ConfigurableMiddleware):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_anonymous_context():
|
def _get_anonymous_context():
|
||||||
"""Anonymous user has only Read-Only grants"""
|
"""Anonymous user has only Read-Only grants."""
|
||||||
return RequestContext(read_only=True, is_admin=False)
|
return RequestContext(read_only=True, is_admin=False)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@@ -16,7 +16,7 @@
|
|||||||
"""
|
"""
|
||||||
A filter middleware that inspects the requested URI for a version string
|
A filter middleware that inspects the requested URI for a version string
|
||||||
and/or Accept headers and attempts to negotiate an API controller to
|
and/or Accept headers and attempts to negotiate an API controller to
|
||||||
return
|
return.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import microversion_parse
|
import microversion_parse
|
||||||
@@ -32,7 +32,7 @@ LOG = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
def get_version_from_accept(accept_header, vnd_mime_type):
|
def get_version_from_accept(accept_header, vnd_mime_type):
|
||||||
"""Try to parse accept header to extract api version
|
"""Try to parse accept header to extract api version.
|
||||||
|
|
||||||
:param accept_header: accept header
|
:param accept_header: accept header
|
||||||
:return: version string in the request or None if not specified
|
:return: version string in the request or None if not specified
|
||||||
|
@@ -40,7 +40,7 @@ class APIVersionRequest(object):
|
|||||||
"""Create an API version request object.
|
"""Create an API version request object.
|
||||||
|
|
||||||
:param version_string: String representation of APIVersionRequest.
|
:param version_string: String representation of APIVersionRequest.
|
||||||
Correct format is 'X.Y', where 'X' and 'Y' are int values.
|
Correct format is 'X.Y', where 'X' and 'Y' are int values.
|
||||||
"""
|
"""
|
||||||
match = re.match(r"^([1-9]\d*)\.([1-9]\d*|0)$", version_string)
|
match = re.match(r"^([1-9]\d*)\.([1-9]\d*|0)$", version_string)
|
||||||
if match:
|
if match:
|
||||||
@@ -95,9 +95,9 @@ class APIVersionRequest(object):
|
|||||||
greater than or equal to the minimum version and less than
|
greater than or equal to the minimum version and less than
|
||||||
or equal to the maximum version.
|
or equal to the maximum version.
|
||||||
|
|
||||||
@param min_version: Minimum acceptable version.
|
:param min_version: Minimum acceptable version.
|
||||||
@param max_version: Maximum acceptable version.
|
:param max_version: Maximum acceptable version.
|
||||||
@returns: boolean
|
:returns: boolean
|
||||||
"""
|
"""
|
||||||
return min_version <= self <= max_version
|
return min_version <= self <= max_version
|
||||||
|
|
||||||
@@ -109,15 +109,15 @@ class APIVersionRequest(object):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def min_version(cls):
|
def min_version(cls):
|
||||||
"""Minimal allowed api version"""
|
"""Minimal allowed api version."""
|
||||||
return APIVersionRequest(cls._MIN_API_VERSION)
|
return APIVersionRequest(cls._MIN_API_VERSION)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def max_version(cls):
|
def max_version(cls):
|
||||||
"""Maximal allowed api version"""
|
"""Maximal allowed api version."""
|
||||||
return APIVersionRequest(cls._MAX_API_VERSION)
|
return APIVersionRequest(cls._MAX_API_VERSION)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def default_version(cls):
|
def default_version(cls):
|
||||||
"""Default api version if no version in request"""
|
"""Default api version if no version in request."""
|
||||||
return APIVersionRequest(cls._DEFAULT_API_VERSION)
|
return APIVersionRequest(cls._DEFAULT_API_VERSION)
|
||||||
|
@@ -22,13 +22,14 @@ from glare.i18n import _
|
|||||||
class VersionedMethod(object):
|
class VersionedMethod(object):
|
||||||
|
|
||||||
def __init__(self, name, start_version, end_version, func):
|
def __init__(self, name, start_version, end_version, func):
|
||||||
"""Versioning information for a single method
|
"""Versioning information for a single method.
|
||||||
|
|
||||||
:param name: Name of the method
|
:param name: Name of the method
|
||||||
:param start_version: Minimum acceptable version
|
:param start_version: Minimum acceptable version
|
||||||
:param end_version: Maximum acceptable_version
|
:param end_version: Maximum acceptable_version
|
||||||
:param func: Method to call
|
:param func: Method to call
|
||||||
Minimum and maximums are inclusive
|
|
||||||
"""
|
"""
|
||||||
|
# NOTE(kairat): minimums and maximums are inclusive
|
||||||
self.name = name
|
self.name = name
|
||||||
self.start_version = start_version
|
self.start_version = start_version
|
||||||
self.end_version = end_version
|
self.end_version = end_version
|
||||||
@@ -41,7 +42,7 @@ class VersionedMethod(object):
|
|||||||
|
|
||||||
class VersionedResource(object):
|
class VersionedResource(object):
|
||||||
"""Versioned mixin that provides ability to define versioned methods and
|
"""Versioned mixin that provides ability to define versioned methods and
|
||||||
return appropriate methods based on user request
|
return appropriate methods based on user request.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# prefix for all versioned methods in class
|
# prefix for all versioned methods in class
|
||||||
@@ -52,6 +53,7 @@ class VersionedResource(object):
|
|||||||
"""Determines whether function list contains version intervals
|
"""Determines whether function list contains version intervals
|
||||||
intersections or not. General algorithm:
|
intersections or not. General algorithm:
|
||||||
https://en.wikipedia.org/wiki/Intersection_algorithm
|
https://en.wikipedia.org/wiki/Intersection_algorithm
|
||||||
|
|
||||||
:param func_list: list of VersionedMethod objects
|
:param func_list: list of VersionedMethod objects
|
||||||
:return: boolean
|
:return: boolean
|
||||||
"""
|
"""
|
||||||
@@ -128,9 +130,10 @@ class VersionedResource(object):
|
|||||||
def version_select(*args, **kwargs):
|
def version_select(*args, **kwargs):
|
||||||
"""Look for the method which matches the name supplied and version
|
"""Look for the method which matches the name supplied and version
|
||||||
constraints and calls it with the supplied arguments.
|
constraints and calls it with the supplied arguments.
|
||||||
|
|
||||||
:returns: Returns the result of the method called
|
:returns: Returns the result of the method called
|
||||||
:raises: VersionNotFoundForAPIMethod if there is no method which
|
:raises: VersionNotFoundForAPIMethod if there is no method which
|
||||||
matches the name and version constraints
|
matches the name and version constraints
|
||||||
"""
|
"""
|
||||||
# versioning is used in 3 classes: request deserializer and
|
# versioning is used in 3 classes: request deserializer and
|
||||||
# controller have request as first argument
|
# controller have request as first argument
|
||||||
|
@@ -50,11 +50,12 @@ supported_versions = api_versioning.VersionedResource.supported_versions
|
|||||||
|
|
||||||
class RequestDeserializer(api_versioning.VersionedResource,
|
class RequestDeserializer(api_versioning.VersionedResource,
|
||||||
wsgi.JSONRequestDeserializer):
|
wsgi.JSONRequestDeserializer):
|
||||||
"""Glare deserializer for incoming webop Requests.
|
"""Glare deserializer for incoming webob requests.
|
||||||
Deserializer converts incoming request into bunch of python primitives.
|
|
||||||
So other components doesn't work with requests at all. Deserializer also
|
Deserializer checks and converts incoming request into a bunch of Glare
|
||||||
executes primary API validation without any knowledge about Artifact
|
primitives. So other service components don't work with requests at all.
|
||||||
structure.
|
Deserializer also performs primary API validation without any knowledge
|
||||||
|
about concrete artifact type structure.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -74,6 +75,7 @@ class RequestDeserializer(api_versioning.VersionedResource,
|
|||||||
return content_type
|
return content_type
|
||||||
|
|
||||||
def _get_request_body(self, req):
|
def _get_request_body(self, req):
|
||||||
|
"""Get request json body and convert it to python structures."""
|
||||||
return self.from_json(req.body)
|
return self.from_json(req.body)
|
||||||
|
|
||||||
@supported_versions(min_ver='1.0')
|
@supported_versions(min_ver='1.0')
|
||||||
@@ -137,7 +139,7 @@ class RequestDeserializer(api_versioning.VersionedResource,
|
|||||||
patch = jsonpatch.JsonPatch(body)
|
patch = jsonpatch.JsonPatch(body)
|
||||||
try:
|
try:
|
||||||
# Initially patch object doesn't validate input. It's only checked
|
# Initially patch object doesn't validate input. It's only checked
|
||||||
# we call get operation on each method
|
# when we call get operation on each method
|
||||||
tuple(map(patch._get_operation, patch.patch))
|
tuple(map(patch._get_operation, patch.patch))
|
||||||
except (jsonpatch.InvalidJsonPatch, TypeError):
|
except (jsonpatch.InvalidJsonPatch, TypeError):
|
||||||
msg = _("Json Patch body is malformed")
|
msg = _("Json Patch body is malformed")
|
||||||
@@ -152,7 +154,7 @@ class RequestDeserializer(api_versioning.VersionedResource,
|
|||||||
data = self._get_request_body(req)
|
data = self._get_request_body(req)
|
||||||
if 'url' not in data:
|
if 'url' not in data:
|
||||||
msg = _("url is required when specifying external location. "
|
msg = _("url is required when specifying external location. "
|
||||||
"Cannot find url in body: %s") % str(data)
|
"Cannot find 'url' in request body: %s") % str(data)
|
||||||
raise exc.BadRequest(msg)
|
raise exc.BadRequest(msg)
|
||||||
else:
|
else:
|
||||||
data = req.body_file
|
data = req.body_file
|
||||||
@@ -176,10 +178,10 @@ def log_request_progress(f):
|
|||||||
|
|
||||||
class ArtifactsController(api_versioning.VersionedResource):
|
class ArtifactsController(api_versioning.VersionedResource):
|
||||||
"""API controller for Glare Artifacts.
|
"""API controller for Glare Artifacts.
|
||||||
|
|
||||||
Artifact Controller prepares incoming data for Glare Engine and redirects
|
Artifact Controller prepares incoming data for Glare Engine and redirects
|
||||||
data to appropriate engine method (so only controller is working with
|
data to the appropriate engine method. Once the response data is returned
|
||||||
Engine. Once the data returned from Engine Controller returns data
|
from the engine Controller passes it next to Response Serializer.
|
||||||
in appropriate format for Response Serializer.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -188,12 +190,21 @@ class ArtifactsController(api_versioning.VersionedResource):
|
|||||||
@supported_versions(min_ver='1.0')
|
@supported_versions(min_ver='1.0')
|
||||||
@log_request_progress
|
@log_request_progress
|
||||||
def list_type_schemas(self, req):
|
def list_type_schemas(self, req):
|
||||||
|
"""List of detailed descriptions of enabled artifact types.
|
||||||
|
|
||||||
|
:return: list of json-schemas of all enabled artifact types.
|
||||||
|
"""
|
||||||
type_schemas = self.engine.list_type_schemas(req.context)
|
type_schemas = self.engine.list_type_schemas(req.context)
|
||||||
return type_schemas
|
return type_schemas
|
||||||
|
|
||||||
@supported_versions(min_ver='1.0')
|
@supported_versions(min_ver='1.0')
|
||||||
@log_request_progress
|
@log_request_progress
|
||||||
def show_type_schema(self, req, type_name):
|
def show_type_schema(self, req, type_name):
|
||||||
|
"""Get detailed artifact type description.
|
||||||
|
|
||||||
|
:param type_name: artifact type name
|
||||||
|
:return: json-schema representation of artifact type
|
||||||
|
"""
|
||||||
type_schema = self.engine.show_type_schema(req.context, type_name)
|
type_schema = self.engine.show_type_schema(req.context, type_name)
|
||||||
return {type_name: type_schema}
|
return {type_name: type_schema}
|
||||||
|
|
||||||
@@ -202,10 +213,10 @@ class ArtifactsController(api_versioning.VersionedResource):
|
|||||||
def create(self, req, type_name, values):
|
def create(self, req, type_name, values):
|
||||||
"""Create artifact record in Glare.
|
"""Create artifact record in Glare.
|
||||||
|
|
||||||
:param req: User request
|
:param req: user request
|
||||||
:param type_name: Artifact type name
|
:param type_name: artifact type name
|
||||||
:param values: dict with artifact fields {field_name: field_value}
|
:param values: dict with artifact fields
|
||||||
:return definition of created artifact
|
:return: definition of created artifact
|
||||||
"""
|
"""
|
||||||
return self.engine.create(req.context, type_name, values)
|
return self.engine.create(req.context, type_name, values)
|
||||||
|
|
||||||
@@ -218,14 +229,14 @@ class ArtifactsController(api_versioning.VersionedResource):
|
|||||||
:param type_name: Artifact type name
|
:param type_name: Artifact type name
|
||||||
:param artifact_id: id of artifact to update
|
:param artifact_id: id of artifact to update
|
||||||
:param patch: json patch with artifact changes
|
:param patch: json patch with artifact changes
|
||||||
:return definition of updated artifact
|
:return: definition of updated artifact
|
||||||
"""
|
"""
|
||||||
return self.engine.update(req.context, type_name, artifact_id, patch)
|
return self.engine.update(req.context, type_name, artifact_id, patch)
|
||||||
|
|
||||||
@supported_versions(min_ver='1.0')
|
@supported_versions(min_ver='1.0')
|
||||||
@log_request_progress
|
@log_request_progress
|
||||||
def delete(self, req, type_name, artifact_id):
|
def delete(self, req, type_name, artifact_id):
|
||||||
"""Delete artifact from Glare
|
"""Delete artifact from Glare.
|
||||||
|
|
||||||
:param req: User request
|
:param req: User request
|
||||||
:param type_name: Artifact type name
|
:param type_name: Artifact type name
|
||||||
@@ -236,7 +247,7 @@ class ArtifactsController(api_versioning.VersionedResource):
|
|||||||
@supported_versions(min_ver='1.0')
|
@supported_versions(min_ver='1.0')
|
||||||
@log_request_progress
|
@log_request_progress
|
||||||
def show(self, req, type_name, artifact_id):
|
def show(self, req, type_name, artifact_id):
|
||||||
"""Show detailed artifact info
|
"""Show detailed artifact info.
|
||||||
|
|
||||||
:param req: User request
|
:param req: User request
|
||||||
:param type_name: Artifact type name
|
:param type_name: Artifact type name
|
||||||
@@ -249,7 +260,7 @@ class ArtifactsController(api_versioning.VersionedResource):
|
|||||||
@log_request_progress
|
@log_request_progress
|
||||||
def list(self, req, type_name, filters, marker=None, limit=None,
|
def list(self, req, type_name, filters, marker=None, limit=None,
|
||||||
sort=None, latest=False):
|
sort=None, latest=False):
|
||||||
"""List available artifacts
|
"""List available artifacts.
|
||||||
|
|
||||||
:param req: User request
|
:param req: User request
|
||||||
:param type_name: Artifact type name
|
:param type_name: Artifact type name
|
||||||
@@ -261,7 +272,7 @@ class ArtifactsController(api_versioning.VersionedResource):
|
|||||||
:param sort: sorting options
|
:param sort: sorting options
|
||||||
:param latest: flag that indicates, that only artifacts with highest
|
:param latest: flag that indicates, that only artifacts with highest
|
||||||
versions should be returned in output
|
versions should be returned in output
|
||||||
:return: list of artifacts
|
:return: list of requested artifact definitions
|
||||||
"""
|
"""
|
||||||
artifacts = self.engine.list(req.context, type_name, filters, marker,
|
artifacts = self.engine.list(req.context, type_name, filters, marker,
|
||||||
limit, sort, latest)
|
limit, sort, latest)
|
||||||
@@ -273,18 +284,19 @@ class ArtifactsController(api_versioning.VersionedResource):
|
|||||||
|
|
||||||
@supported_versions(min_ver='1.0')
|
@supported_versions(min_ver='1.0')
|
||||||
@log_request_progress
|
@log_request_progress
|
||||||
def upload_blob(self, req, type_name, artifact_id, blob_name, data,
|
def upload_blob(self, req, type_name, artifact_id, blob_path, data,
|
||||||
content_type):
|
content_type):
|
||||||
"""Upload blob into Glare repo
|
"""Upload blob into Glare repo.
|
||||||
|
|
||||||
:param req: User request
|
:param req: User request
|
||||||
:param type_name: Artifact type name
|
:param type_name: Artifact type name
|
||||||
:param artifact_id: id of Artifact to reactivate
|
:param artifact_id: id of artifact where to perform upload
|
||||||
:param blob_name: name of blob field in artifact
|
:param blob_path: path to artifact blob
|
||||||
:param data: Artifact payload
|
:param data: blob payload
|
||||||
:param content_type: data content-type
|
:param content_type: data content-type
|
||||||
|
:return: definition of requested artifact with uploaded blob
|
||||||
"""
|
"""
|
||||||
field_name, _sep, blob_key = blob_name.partition('/')
|
field_name, _sep, blob_key = blob_path.partition('/')
|
||||||
if not blob_key:
|
if not blob_key:
|
||||||
blob_key = None
|
blob_key = None
|
||||||
if content_type == ('application/vnd+openstack.glare-custom-location'
|
if content_type == ('application/vnd+openstack.glare-custom-location'
|
||||||
@@ -300,16 +312,16 @@ class ArtifactsController(api_versioning.VersionedResource):
|
|||||||
|
|
||||||
@supported_versions(min_ver='1.0')
|
@supported_versions(min_ver='1.0')
|
||||||
@log_request_progress
|
@log_request_progress
|
||||||
def download_blob(self, req, type_name, artifact_id, blob_name):
|
def download_blob(self, req, type_name, artifact_id, blob_path):
|
||||||
"""Download blob data from Artifact
|
"""Download blob data from Artifact.
|
||||||
|
|
||||||
:param req: User request
|
:param req: User request
|
||||||
:param type_name: Artifact type name
|
:param type_name: artifact type name
|
||||||
:param artifact_id: id of Artifact to reactivate
|
:param artifact_id: id of artifact from where to perform download
|
||||||
:param blob_name: name of blob field in artifact
|
:param blob_path: path to artifact blob
|
||||||
:return: iterator that returns blob data
|
:return: requested blob data
|
||||||
"""
|
"""
|
||||||
field_name, _sep, blob_key = blob_name.partition('/')
|
field_name, _sep, blob_key = blob_path.partition('/')
|
||||||
if not blob_key:
|
if not blob_key:
|
||||||
blob_key = None
|
blob_key = None
|
||||||
data, meta = self.engine.download_blob(
|
data, meta = self.engine.download_blob(
|
||||||
@@ -320,10 +332,10 @@ class ArtifactsController(api_versioning.VersionedResource):
|
|||||||
|
|
||||||
class ResponseSerializer(api_versioning.VersionedResource,
|
class ResponseSerializer(api_versioning.VersionedResource,
|
||||||
wsgi.JSONResponseSerializer):
|
wsgi.JSONResponseSerializer):
|
||||||
"""Glare Response Serializer converts data received from Glare Engine
|
"""Glare serializer for outgoing responses.
|
||||||
(it consists from plain data types - dict, int, string, file descriptors,
|
|
||||||
etc) to WSGI Requests. It also specifies proper response status and
|
Converts data received from the engine to WSGI responses. It also
|
||||||
content type as specified by API design.
|
specifies proper response status and content type as declared in the API.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -425,7 +437,7 @@ class ResponseSerializer(api_versioning.VersionedResource,
|
|||||||
|
|
||||||
|
|
||||||
def create_resource():
|
def create_resource():
|
||||||
"""Artifact resource factory method"""
|
"""Artifact resource factory method."""
|
||||||
deserializer = RequestDeserializer()
|
deserializer = RequestDeserializer()
|
||||||
serializer = ResponseSerializer()
|
serializer = ResponseSerializer()
|
||||||
controller = ArtifactsController()
|
controller = ArtifactsController()
|
||||||
|
@@ -83,16 +83,16 @@ class API(wsgi.Router):
|
|||||||
allowed_methods='GET, PATCH, DELETE')
|
allowed_methods='GET, PATCH, DELETE')
|
||||||
|
|
||||||
# ---blobs---
|
# ---blobs---
|
||||||
mapper.connect('/artifacts/{type_name}/{artifact_id}/{blob_name:.*?}',
|
mapper.connect('/artifacts/{type_name}/{artifact_id}/{blob_path:.*?}',
|
||||||
controller=glare_resource,
|
controller=glare_resource,
|
||||||
action='download_blob',
|
action='download_blob',
|
||||||
conditions={'method': ['GET']},
|
conditions={'method': ['GET']},
|
||||||
body_reject=True)
|
body_reject=True)
|
||||||
mapper.connect('/artifacts/{type_name}/{artifact_id}/{blob_name:.*?}',
|
mapper.connect('/artifacts/{type_name}/{artifact_id}/{blob_path:.*?}',
|
||||||
controller=glare_resource,
|
controller=glare_resource,
|
||||||
action='upload_blob',
|
action='upload_blob',
|
||||||
conditions={'method': ['PUT']})
|
conditions={'method': ['PUT']})
|
||||||
mapper.connect('/artifacts/{type_name}/{artifact_id}/{blob_name:.*?}',
|
mapper.connect('/artifacts/{type_name}/{artifact_id}/{blob_path:.*?}',
|
||||||
controller=reject_method_resource,
|
controller=reject_method_resource,
|
||||||
action='reject',
|
action='reject',
|
||||||
allowed_methods='GET, PUT')
|
allowed_methods='GET, PUT')
|
||||||
|
@@ -60,6 +60,7 @@ class Controller(object):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def index(req, is_multi):
|
def index(req, is_multi):
|
||||||
"""Respond to a request for all OpenStack API versions.
|
"""Respond to a request for all OpenStack API versions.
|
||||||
|
|
||||||
:param is_multi: defines if multiple choices should be response status
|
:param is_multi: defines if multiple choices should be response status
|
||||||
or not
|
or not
|
||||||
:param req: user request object
|
:param req: user request object
|
||||||
|
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Glare (Glare Artifact Repository) API service
|
Glare (Glare Artifact Repository) API service.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
@@ -14,7 +14,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Routines for configuring Glare
|
Routines for configuring Glare.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging.config
|
import logging.config
|
||||||
|
@@ -24,7 +24,7 @@ LOG = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class GlareException(Exception):
|
class GlareException(Exception):
|
||||||
"""
|
"""
|
||||||
Base Glare Exception
|
Base Glare Exception class.
|
||||||
|
|
||||||
To correctly use this class, inherit from it and define
|
To correctly use this class, inherit from it and define
|
||||||
a 'message' property. That message will get printf'd
|
a 'message' property. That message will get printf'd
|
||||||
|
@@ -97,7 +97,7 @@ def reset():
|
|||||||
|
|
||||||
|
|
||||||
def authorize(policy_name, target, context, do_raise=True):
|
def authorize(policy_name, target, context, do_raise=True):
|
||||||
"""Method checks that user action can be executed according to policies
|
"""Method checks that user action can be executed according to policies.
|
||||||
|
|
||||||
:param policy_name: policy name
|
:param policy_name: policy name
|
||||||
:param target:
|
:param target:
|
||||||
|
@@ -53,7 +53,7 @@ error_map = [{'catch': store_exc.NotFound,
|
|||||||
@utils.error_handler(error_map)
|
@utils.error_handler(error_map)
|
||||||
def save_blob_to_store(blob_id, blob, context, max_size,
|
def save_blob_to_store(blob_id, blob, context, max_size,
|
||||||
store_type=None, verifier=None):
|
store_type=None, verifier=None):
|
||||||
"""Save file to specified store type and return location info to the user
|
"""Save file to specified store type and return location info to the user.
|
||||||
|
|
||||||
:param store_type: type of the store, None means save to default store.
|
:param store_type: type of the store, None means save to default store.
|
||||||
:param blob_id: id of artifact
|
:param blob_id: id of artifact
|
||||||
@@ -84,7 +84,7 @@ def load_from_store(uri, context):
|
|||||||
|
|
||||||
@utils.error_handler(error_map)
|
@utils.error_handler(error_map)
|
||||||
def delete_blob(uri, context):
|
def delete_blob(uri, context):
|
||||||
"""Delete blob from backend store
|
"""Delete blob from backend store.
|
||||||
|
|
||||||
:param uri: blob uri
|
:param uri: blob uri
|
||||||
:param context: user context
|
:param context: user context
|
||||||
|
@@ -68,7 +68,7 @@ def chunkreadable(iter, chunk_size=65536):
|
|||||||
|
|
||||||
def chunkiter(fp, chunk_size=65536):
|
def chunkiter(fp, chunk_size=65536):
|
||||||
"""
|
"""
|
||||||
Return an iterator to a file-like obj which yields fixed size chunks
|
Return an iterator to a file-like obj which yields fixed size chunks.
|
||||||
|
|
||||||
:param fp: a file-like object
|
:param fp: a file-like object
|
||||||
:param chunk_size: maximum size of chunk
|
:param chunk_size: maximum size of chunk
|
||||||
@@ -361,7 +361,7 @@ except re.error:
|
|||||||
def no_4byte_params(f):
|
def no_4byte_params(f):
|
||||||
"""
|
"""
|
||||||
Checks that no 4 byte unicode characters are allowed
|
Checks that no 4 byte unicode characters are allowed
|
||||||
in dicts' keys/values and string's parameters
|
in dicts' keys/values and string's parameters.
|
||||||
"""
|
"""
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
|
|
||||||
@@ -423,8 +423,7 @@ def split_filter_op(expression):
|
|||||||
When no operator is found, default to an equality comparison.
|
When no operator is found, default to an equality comparison.
|
||||||
|
|
||||||
:param expression: the expression to parse
|
:param expression: the expression to parse
|
||||||
|
:return: a tuple (operator, threshold) parsed from expression
|
||||||
:returns: a tuple (operator, threshold) parsed from expression
|
|
||||||
"""
|
"""
|
||||||
left, sep, right = expression.partition(':')
|
left, sep, right = expression.partition(':')
|
||||||
if sep:
|
if sep:
|
||||||
@@ -500,11 +499,8 @@ def evaluate_filter_op(value, operator, threshold):
|
|||||||
:param value: evaluated against the operator, as left side of expression
|
:param value: evaluated against the operator, as left side of expression
|
||||||
:param operator: any supported filter operation
|
:param operator: any supported filter operation
|
||||||
:param threshold: to compare value against, as right side of expression
|
:param threshold: to compare value against, as right side of expression
|
||||||
|
|
||||||
:raises: InvalidFilterOperatorValue if an unknown operator is provided
|
:raises: InvalidFilterOperatorValue if an unknown operator is provided
|
||||||
|
:return: boolean result of applied comparison
|
||||||
:returns: boolean result of applied comparison
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if operator == 'gt':
|
if operator == 'gt':
|
||||||
return value > threshold
|
return value > threshold
|
||||||
@@ -525,21 +521,25 @@ def evaluate_filter_op(value, operator, threshold):
|
|||||||
|
|
||||||
class error_handler(object):
|
class error_handler(object):
|
||||||
def __init__(self, error_map, default_exception=None):
|
def __init__(self, error_map, default_exception=None):
|
||||||
|
"""Init method of the class.
|
||||||
|
|
||||||
|
:param error_map: dict of exception that can be raised
|
||||||
|
in func and exceptions that must be raised for these exceptions.
|
||||||
|
For example, if sqlalchemy NotFound might be raised and we need
|
||||||
|
re-raise it as glare NotFound exception then error_map must
|
||||||
|
contain {"catch": SQLAlchemyNotFound,
|
||||||
|
"raise": exceptions.NotFound}
|
||||||
|
:param default_exception: default exception that must be raised if
|
||||||
|
exception that cannot be found in error map was raised
|
||||||
|
:return: func
|
||||||
|
"""
|
||||||
self.error_map = error_map
|
self.error_map = error_map
|
||||||
self.default_exception = default_exception
|
self.default_exception = default_exception
|
||||||
|
|
||||||
def __call__(self, f):
|
def __call__(self, f):
|
||||||
"""Decorator that catches exception that came from func or method
|
"""Decorator that catches exception that came from func or method.
|
||||||
:param f: targer func
|
|
||||||
:param error_map: dict of exception that can be raised
|
:param f: target func
|
||||||
in func and exceptions that must be raised for these exceptions.
|
|
||||||
For example, if sqlalchemy NotFound might be raised and we need
|
|
||||||
re-raise it as glare NotFound exception then error_map must
|
|
||||||
contain {"catch": SQLAlchemyNotFound,
|
|
||||||
"raise": exceptions.NotFound}
|
|
||||||
:param default_exception: default exception that must be raised if
|
|
||||||
exception that cannot be found in error map was raised
|
|
||||||
:return: func
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def new_function(*args, **kwargs):
|
def new_function(*args, **kwargs):
|
||||||
|
113
glare/engine.py
113
glare/engine.py
@@ -39,15 +39,16 @@ class Engine(object):
|
|||||||
"""Engine is responsible for executing different helper operations when
|
"""Engine is responsible for executing different helper operations when
|
||||||
processing incoming requests from Glare API.
|
processing incoming requests from Glare API.
|
||||||
Engine receives incoming data and does the following:
|
Engine receives incoming data and does the following:
|
||||||
- check basic policy permissions
|
- check basic policy permissions;
|
||||||
- requests artifact definition from registry
|
- requests artifact definition from artifact type registry;
|
||||||
- check access permission(ro, rw)
|
- check access permission(ro, rw);
|
||||||
- lock artifact for update if needed
|
- lock artifact for update if needed;
|
||||||
- pass data to base artifact to execute all business logic operations
|
- pass data to base artifact to execute all business logic operations
|
||||||
|
with database;
|
||||||
- notify other users about finished operation.
|
- notify other users about finished operation.
|
||||||
Engine should not include any business logic and validation related
|
Engine should not include any business logic and validation related
|
||||||
to Artifacts. Engine should not know any internal details of Artifacts
|
to Artifacts. Engine should not know any internal details of artifact
|
||||||
because it controls access to Artifacts in common.
|
type, because this part of the work is done by Base artifact type.
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
# register all artifact types
|
# register all artifact types
|
||||||
@@ -69,28 +70,32 @@ class Engine(object):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def _get_artifact(cls, context, type_name, artifact_id,
|
def _get_artifact(cls, context, type_name, artifact_id,
|
||||||
read_only=False):
|
read_only=False):
|
||||||
"""Return artifact for users
|
"""Return artifact requested by user.
|
||||||
|
Check access permissions and policies.
|
||||||
|
|
||||||
Return artifact for reading/modification by users. Check
|
:param context: user context
|
||||||
access permissions and policies for artifact.
|
:param type_name: artifact type name
|
||||||
|
:param artifact_id: id of the artifact to be updated
|
||||||
|
:param read_only: flag, if set to True only read access is checked,
|
||||||
|
if False then engine checks if artifact can be modified by the user
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _check_read_write_access(ctx, af):
|
def _check_read_write_access(ctx, af):
|
||||||
"""Check if artifact can be modified by user
|
"""Check if artifact can be modified by user.
|
||||||
|
|
||||||
:param ctx: user context
|
:param ctx: user context
|
||||||
:param af: artifact definition
|
:param af: artifact definition
|
||||||
:raise Forbidden if access is not allowed
|
:raise: Forbidden if access is not allowed
|
||||||
"""
|
"""
|
||||||
if not ctx.is_admin and ctx.tenant != af.owner or ctx.read_only:
|
if not ctx.is_admin and ctx.tenant != af.owner or ctx.read_only:
|
||||||
raise exception.Forbidden()
|
raise exception.Forbidden()
|
||||||
|
|
||||||
def _check_read_only_access(ctx, af):
|
def _check_read_only_access(ctx, af):
|
||||||
"""Check if user has read only access to artifact
|
"""Check if user has read only access to artifact.
|
||||||
|
|
||||||
:param ctx: user context
|
:param ctx: user context
|
||||||
:param af: artifact definition
|
:param af: artifact definition
|
||||||
:raise Forbidden if access is not allowed
|
:raise: Forbidden if access is not allowed
|
||||||
"""
|
"""
|
||||||
private = af.visibility != 'public'
|
private = af.visibility != 'public'
|
||||||
if (private and
|
if (private and
|
||||||
@@ -125,13 +130,19 @@ class Engine(object):
|
|||||||
return schemas[type_name]
|
return schemas[type_name]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, context, type_name, field_values):
|
def create(cls, context, type_name, values):
|
||||||
"""Create new artifact in Glare"""
|
"""Create artifact record in Glare.
|
||||||
|
|
||||||
|
:param context: user context
|
||||||
|
:param type_name: artifact type name
|
||||||
|
:param values: dict with artifact fields
|
||||||
|
:return: dict representation of created artifact
|
||||||
|
"""
|
||||||
action_name = "artifact:create"
|
action_name = "artifact:create"
|
||||||
policy.authorize(action_name, field_values, context)
|
policy.authorize(action_name, values, context)
|
||||||
artifact_type = registry.ArtifactRegistry.get_artifact_type(type_name)
|
artifact_type = registry.ArtifactRegistry.get_artifact_type(type_name)
|
||||||
# acquire version lock and execute artifact create
|
# acquire version lock and execute artifact create
|
||||||
af = artifact_type.create(context, field_values)
|
af = artifact_type.create(context, values)
|
||||||
# notify about new artifact
|
# notify about new artifact
|
||||||
Notifier.notify(context, action_name, af)
|
Notifier.notify(context, action_name, af)
|
||||||
# return artifact to the user
|
# return artifact to the user
|
||||||
@@ -142,24 +153,23 @@ class Engine(object):
|
|||||||
"""Update artifact with json patch.
|
"""Update artifact with json patch.
|
||||||
|
|
||||||
Apply patch to artifact and validate artifact before updating it
|
Apply patch to artifact and validate artifact before updating it
|
||||||
in database. If there is request for visibility change or custom
|
in database. If there is request for visibility or status change
|
||||||
location change then call specific method for that.
|
then call specific method for that.
|
||||||
|
|
||||||
:param context: user context
|
:param context: user context
|
||||||
:param type_name: name of artifact type
|
:param type_name: name of artifact type
|
||||||
:param artifact_id: id of the artifact to be updated
|
:param artifact_id: id of the artifact to be updated
|
||||||
:param patch: json patch
|
:param patch: json patch object
|
||||||
:return: updated artifact
|
:return: dict representation of updated artifact
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get_updates(af_dict, patch_with_upd):
|
def _get_updates(af_dict, patch_with_upd):
|
||||||
"""Get updated values for artifact and json patch
|
"""Get updated values for artifact and json patch.
|
||||||
|
|
||||||
:param af_dict: current artifact definition as dict
|
:param af_dict: current artifact definition as dict
|
||||||
:param patch_with_upd: json-patch
|
:param patch_with_upd: json-patch object
|
||||||
:return: dict of updated attributes and their values
|
:return dict of updated attributes and their values
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
af_dict_patched = patch_with_upd.apply(af_dict)
|
af_dict_patched = patch_with_upd.apply(af_dict)
|
||||||
diff = utils.DictDiffer(af_dict_patched, af_dict)
|
diff = utils.DictDiffer(af_dict_patched, af_dict)
|
||||||
@@ -187,7 +197,7 @@ class Engine(object):
|
|||||||
with base.BaseArtifact.lock_engine.acquire(context, lock_key):
|
with base.BaseArtifact.lock_engine.acquire(context, lock_key):
|
||||||
artifact = cls._get_artifact(context, type_name, artifact_id)
|
artifact = cls._get_artifact(context, type_name, artifact_id)
|
||||||
af_dict = artifact.to_dict()
|
af_dict = artifact.to_dict()
|
||||||
updates = get_updates(af_dict, patch)
|
updates = _get_updates(af_dict, patch)
|
||||||
LOG.debug("Update diff successfully calculated for artifact "
|
LOG.debug("Update diff successfully calculated for artifact "
|
||||||
"%(af)s %(diff)s", {'af': artifact_id, 'diff': updates})
|
"%(af)s %(diff)s", {'af': artifact_id, 'diff': updates})
|
||||||
|
|
||||||
@@ -196,6 +206,8 @@ class Engine(object):
|
|||||||
else:
|
else:
|
||||||
action = artifact.get_action_for_updates(
|
action = artifact.get_action_for_updates(
|
||||||
context, artifact, updates, registry.ArtifactRegistry)
|
context, artifact, updates, registry.ArtifactRegistry)
|
||||||
|
LOG.debug("Action %(action)s was defined to values %(val)s.",
|
||||||
|
{'action': action.__name__, 'val': updates})
|
||||||
action_name = "artifact:%s" % action.__name__
|
action_name = "artifact:%s" % action.__name__
|
||||||
policy.authorize(action_name, af_dict, context)
|
policy.authorize(action_name, af_dict, context)
|
||||||
modified_af = action(context, artifact, updates)
|
modified_af = action(context, artifact, updates)
|
||||||
@@ -204,7 +216,13 @@ class Engine(object):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get(cls, context, type_name, artifact_id):
|
def get(cls, context, type_name, artifact_id):
|
||||||
"""Return artifact representation from artifact repo."""
|
"""Show detailed artifact info.
|
||||||
|
|
||||||
|
:param context: user context
|
||||||
|
:param type_name: Artifact type name
|
||||||
|
:param artifact_id: id of artifact to show
|
||||||
|
:return: definition of requested artifact
|
||||||
|
"""
|
||||||
policy.authorize("artifact:get", {}, context)
|
policy.authorize("artifact:get", {}, context)
|
||||||
af = cls._get_artifact(context, type_name, artifact_id,
|
af = cls._get_artifact(context, type_name, artifact_id,
|
||||||
read_only=True)
|
read_only=True)
|
||||||
@@ -225,7 +243,7 @@ class Engine(object):
|
|||||||
:param sort: sorting options
|
:param sort: sorting options
|
||||||
:param latest: flag that indicates, that only artifacts with highest
|
:param latest: flag that indicates, that only artifacts with highest
|
||||||
versions should be returned in output
|
versions should be returned in output
|
||||||
:return: list of artifacts
|
:return: list of artifact definitions
|
||||||
"""
|
"""
|
||||||
policy.authorize("artifact:list", {}, context)
|
policy.authorize("artifact:list", {}, context)
|
||||||
artifact_type = registry.ArtifactRegistry.get_artifact_type(type_name)
|
artifact_type = registry.ArtifactRegistry.get_artifact_type(type_name)
|
||||||
@@ -237,7 +255,12 @@ class Engine(object):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def delete(cls, context, type_name, artifact_id):
|
def delete(cls, context, type_name, artifact_id):
|
||||||
"""Delete artifact from glare"""
|
"""Delete artifact from Glare.
|
||||||
|
|
||||||
|
:param context: User context
|
||||||
|
:param type_name: Artifact type name
|
||||||
|
:param artifact_id: id of artifact to delete
|
||||||
|
"""
|
||||||
af = cls._get_artifact(context, type_name, artifact_id)
|
af = cls._get_artifact(context, type_name, artifact_id)
|
||||||
policy.authorize("artifact:delete", af.to_dict(), context)
|
policy.authorize("artifact:delete", af.to_dict(), context)
|
||||||
af.delete(context, af)
|
af.delete(context, af)
|
||||||
@@ -254,9 +277,9 @@ class Engine(object):
|
|||||||
:param field_name: name of blob or blob dict field
|
:param field_name: name of blob or blob dict field
|
||||||
:param location: external blob url
|
:param location: external blob url
|
||||||
:param blob_meta: dictionary containing blob metadata like md5 checksum
|
:param blob_meta: dictionary containing blob metadata like md5 checksum
|
||||||
:param blob_key: if field_name is blob dict it specifies concrete key
|
:param blob_key: if field_name is blob dict it specifies key
|
||||||
in this dict
|
in this dict
|
||||||
:return updated artifact
|
:return: dict representation of updated artifact
|
||||||
"""
|
"""
|
||||||
af = cls._get_artifact(context, type_name, artifact_id)
|
af = cls._get_artifact(context, type_name, artifact_id)
|
||||||
action_name = 'artifact:set_location'
|
action_name = 'artifact:set_location'
|
||||||
@@ -299,11 +322,10 @@ class Engine(object):
|
|||||||
:param artifact_id: id of the artifact to be updated
|
:param artifact_id: id of the artifact to be updated
|
||||||
:param field_name: name of blob or blob dict field
|
:param field_name: name of blob or blob dict field
|
||||||
:param fd: file descriptor that Glare uses to upload the file
|
:param fd: file descriptor that Glare uses to upload the file
|
||||||
:param field_name: name of blob dict field
|
|
||||||
:param content_type: data content-type
|
:param content_type: data content-type
|
||||||
:param blob_key: if field_name is blob dict it specifies concrete key
|
:param blob_key: if field_name is blob dict it specifies key
|
||||||
in this dict
|
in this dictionary
|
||||||
:return file iterator for requested file
|
:return: dict representation of updated artifact
|
||||||
"""
|
"""
|
||||||
af = cls._get_artifact(context, type_name, artifact_id)
|
af = cls._get_artifact(context, type_name, artifact_id)
|
||||||
action_name = "artifact:upload"
|
action_name = "artifact:upload"
|
||||||
@@ -348,12 +370,12 @@ class Engine(object):
|
|||||||
# if upload failed remove blob from db and storage
|
# if upload failed remove blob from db and storage
|
||||||
with excutils.save_and_reraise_exception(logger=LOG):
|
with excutils.save_and_reraise_exception(logger=LOG):
|
||||||
if blob_key is None:
|
if blob_key is None:
|
||||||
af.update_blob(context, af.id, {field_name: None})
|
af.update_blob(context, af.id, field_name, None)
|
||||||
else:
|
else:
|
||||||
blob_dict_attr = modified_af[field_name]
|
blob_dict_attr = modified_af[field_name]
|
||||||
del blob_dict_attr[blob_key]
|
del blob_dict_attr[blob_key]
|
||||||
af.update_blob(context, af.id,
|
af.update_blob(context, af.id,
|
||||||
{field_name: blob_dict_attr})
|
field_name, blob_dict_attr)
|
||||||
blob_name = "%s[%s]" % (field_name, blob_key) \
|
blob_name = "%s[%s]" % (field_name, blob_key) \
|
||||||
if blob_key else field_name
|
if blob_key else field_name
|
||||||
LOG.info(_LI("Successfully finished blob upload for artifact "
|
LOG.info(_LI("Successfully finished blob upload for artifact "
|
||||||
@@ -378,40 +400,43 @@ class Engine(object):
|
|||||||
def update_blob(cls, context, type_name, artifact_id, blob,
|
def update_blob(cls, context, type_name, artifact_id, blob,
|
||||||
field_name, blob_key=None, validate=False):
|
field_name, blob_key=None, validate=False):
|
||||||
"""Update blob info.
|
"""Update blob info.
|
||||||
|
|
||||||
:param context: user context
|
:param context: user context
|
||||||
:param type_name: name of artifact type
|
:param type_name: name of artifact type
|
||||||
:param artifact_id: id of the artifact to be updated
|
:param artifact_id: id of the artifact to be updated
|
||||||
:param blob: blob representation in dict format
|
:param blob: blob representation in dict format
|
||||||
:param field_name: name of blob or blob dict field
|
:param field_name: name of blob or blob dict field
|
||||||
:param blob_key: if field_name is blob dict it specifies concrete key
|
:param blob_key: if field_name is blob dict it specifies key
|
||||||
in this dict
|
in this dict
|
||||||
:param validate: enable validation of possibility of blob uploading
|
:param validate: enable validation of possibility of blob uploading
|
||||||
:return updated artifact
|
|
||||||
|
:return: dict representation of updated artifact
|
||||||
"""
|
"""
|
||||||
lock_key = "%s:%s" % (type_name, artifact_id)
|
lock_key = "%s:%s" % (type_name, artifact_id)
|
||||||
with base.BaseArtifact.lock_engine.acquire(context, lock_key):
|
with base.BaseArtifact.lock_engine.acquire(context, lock_key):
|
||||||
af = cls._get_artifact(context, type_name, artifact_id)
|
af = cls._get_artifact(context, type_name, artifact_id)
|
||||||
if validate:
|
if validate:
|
||||||
af.validate_upload_allowed(context, af, field_name, blob_key)
|
af.validate_upload_allowed(af, field_name, blob_key)
|
||||||
if blob_key is None:
|
if blob_key is None:
|
||||||
setattr(af, field_name, blob)
|
setattr(af, field_name, blob)
|
||||||
return af.update_blob(
|
return af.update_blob(
|
||||||
context, af.id, {field_name: getattr(af, field_name)})
|
context, af.id, field_name, getattr(af, field_name))
|
||||||
else:
|
else:
|
||||||
blob_dict_attr = getattr(af, field_name)
|
blob_dict_attr = getattr(af, field_name)
|
||||||
blob_dict_attr[blob_key] = blob
|
blob_dict_attr[blob_key] = blob
|
||||||
return af.update_blob(
|
return af.update_blob(
|
||||||
context, af.id, {field_name: blob_dict_attr})
|
context, af.id, field_name, blob_dict_attr)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def download_blob(cls, context, type_name, artifact_id, field_name,
|
def download_blob(cls, context, type_name, artifact_id, field_name,
|
||||||
blob_key=None):
|
blob_key=None):
|
||||||
"""Download binary data from Glare Artifact.
|
"""Download binary data from Glare Artifact.
|
||||||
|
|
||||||
:param context: user context
|
:param context: user context
|
||||||
:param type_name: name of artifact type
|
:param type_name: name of artifact type
|
||||||
:param artifact_id: id of the artifact to be updated
|
:param artifact_id: id of the artifact to be updated
|
||||||
:param field_name: name of blob or blob dict field
|
:param field_name: name of blob or blob dict field
|
||||||
:param blob_key: if field_name is blob dict it specifies concrete key
|
:param blob_key: if field_name is blob dict it specifies key
|
||||||
in this dict
|
in this dict
|
||||||
:return: file iterator for requested file
|
:return: file iterator for requested file
|
||||||
"""
|
"""
|
||||||
|
@@ -20,11 +20,10 @@ LOG = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class LockApiBase(object):
|
class LockApiBase(object):
|
||||||
"""Lock Api Base class that responsible for acquiring/releasing locks
|
"""Lock Api Base class that responsible for acquiring/releasing locks."""
|
||||||
"""
|
|
||||||
|
|
||||||
def create_lock(self, context, lock_key):
|
def create_lock(self, context, lock_key):
|
||||||
"""Acquire lock for current user
|
"""Acquire lock for current user.
|
||||||
|
|
||||||
:param context user context
|
:param context user context
|
||||||
:param lock_key: unique lock identifier that defines lock scope
|
:param lock_key: unique lock identifier that defines lock scope
|
||||||
@@ -33,22 +32,21 @@ class LockApiBase(object):
|
|||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def delete_lock(self, context, lock_id):
|
def delete_lock(self, context, lock_id):
|
||||||
"""Delete acquired user lock
|
"""Delete acquired user lock.
|
||||||
|
|
||||||
:param context: user context
|
:param context: user context
|
||||||
:param lock_id: lock internal identifier
|
:param lock_id: lock internal identifier
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
class Lock(object):
|
class Lock(object):
|
||||||
"""Object that stores lock context for users. This class is internal
|
"""Object that stores lock context for users. This class is internal
|
||||||
and used only for Lock Engine. So users shouldn't use this class directly
|
and used only in lock engine, so users shouldn't use this class directly.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, context, lock_id, lock_key, release_method):
|
def __init__(self, context, lock_id, lock_key, release_method):
|
||||||
"""Initialize lock context"""
|
"""Initialize lock context."""
|
||||||
self.context = context
|
self.context = context
|
||||||
self.lock_id = lock_id
|
self.lock_id = lock_id
|
||||||
self.lock_key = lock_key
|
self.lock_key = lock_key
|
||||||
@@ -64,31 +62,33 @@ class Lock(object):
|
|||||||
|
|
||||||
class LockEngine(object):
|
class LockEngine(object):
|
||||||
"""Glare lock engine.
|
"""Glare lock engine.
|
||||||
|
|
||||||
Defines how artifact updates must be synchronized with each other. When
|
Defines how artifact updates must be synchronized with each other. When
|
||||||
some user obtains lock for the same piece of data then other user cannot
|
some user obtains a lock for the same artifact then other user cannot
|
||||||
request that lock and get Conflict error.
|
request that lock and gets a Conflict error.
|
||||||
This little engine also allows to encapsulate lock logic in one place so
|
|
||||||
we can potentially add tooz functionality in future to Glare. Right now
|
|
||||||
there are troubles with locks in Galera (especially in mysql) and zookeeper
|
|
||||||
requires additional work from IT engineers. So we need support production
|
|
||||||
ready DB locks in our implementation.
|
|
||||||
"""
|
"""
|
||||||
|
# NOTE(kairat): Lock Engine also allows to encapsulate lock logic in one
|
||||||
|
# place so we can potentially add tooz functionality in future to Glare.
|
||||||
|
# Right now there are troubles with locks in Galera (especially in mysql)
|
||||||
|
# and zookeeper requires additional work from IT engineers. So we need
|
||||||
|
# support production ready DB locks in our implementation.
|
||||||
|
|
||||||
MAX_LOCK_LENGTH = 255
|
MAX_LOCK_LENGTH = 255
|
||||||
|
|
||||||
def __init__(self, lock_api):
|
def __init__(self, lock_api):
|
||||||
"""Initialize lock engine with some lock api
|
"""Initialize lock engine with some lock api.
|
||||||
|
|
||||||
:param lock_api: api that allows to create/delete locks. It must be
|
:param lock_api: api that allows to create/delete locks
|
||||||
db_api but it might be replaced with DLM in near future.
|
|
||||||
"""
|
"""
|
||||||
|
# NOTE(kairat): lock_api is db_api now but it might be
|
||||||
|
# replaced with DLM in near future.
|
||||||
self.lock_api = lock_api
|
self.lock_api = lock_api
|
||||||
|
|
||||||
def acquire(self, context, lock_key):
|
def acquire(self, context, lock_key):
|
||||||
"""Acquire lock to update whole artifact
|
"""Acquire lock for artifact.
|
||||||
|
|
||||||
Acquire lock to update artifact. If there is some other
|
If there is some other lock with the same key then
|
||||||
lock for the same artifact then raise Conflict Error.
|
raise Conflict Error.
|
||||||
|
|
||||||
:param context: user context
|
:param context: user context
|
||||||
:param lock_key: lock key
|
:param lock_key: lock key
|
||||||
@@ -106,6 +106,10 @@ class LockEngine(object):
|
|||||||
return Lock(context, lock_id, lock_key, self.release)
|
return Lock(context, lock_id, lock_key, self.release)
|
||||||
|
|
||||||
def release(self, lock):
|
def release(self, lock):
|
||||||
|
"""Release lock for artifact.
|
||||||
|
|
||||||
|
:param lock: Lock object
|
||||||
|
"""
|
||||||
if lock.lock_id is not None:
|
if lock.lock_id is not None:
|
||||||
self.lock_api.delete_lock(lock.context, lock.lock_id)
|
self.lock_api.delete_lock(lock.context, lock.lock_id)
|
||||||
LOG.info(_LI("Lock %(lock_id)s released for lock_key %(key)s"),
|
LOG.info(_LI("Lock %(lock_id)s released for lock_key %(key)s"),
|
||||||
|
@@ -35,9 +35,7 @@ def set_defaults(control_exchange='glare'):
|
|||||||
|
|
||||||
|
|
||||||
class Notifier(object):
|
class Notifier(object):
|
||||||
"""Simple interface to receive Glare notifier
|
"""Simple interface to receive Glare notifier."""
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
SERVICE_NAME = 'artifact'
|
SERVICE_NAME = 'artifact'
|
||||||
GLARE_NOTIFIER = None
|
GLARE_NOTIFIER = None
|
||||||
@@ -52,7 +50,7 @@ class Notifier(object):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def notify(cls, context, event_type, body, level='INFO'):
|
def notify(cls, context, event_type, body, level='INFO'):
|
||||||
"""Notify Glare listeners with some useful info
|
"""Notify Glare listeners with some useful info.
|
||||||
|
|
||||||
:param context: User request context
|
:param context: User request context
|
||||||
:param event_type: type of event
|
:param event_type: type of event
|
||||||
|
@@ -63,7 +63,7 @@ class All(base.BaseArtifact):
|
|||||||
raise exception.Forbidden("This type is read only.")
|
raise exception.Forbidden("This type is read only.")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def update_blob(cls, context, af_id, values):
|
def update_blob(cls, context, af_id, field_name, values):
|
||||||
raise exception.Forbidden("This type is read only.")
|
raise exception.Forbidden("This type is read only.")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@@ -170,19 +170,19 @@ class BaseArtifact(base.VersionedObject):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_blob(cls, field_name):
|
def is_blob(cls, field_name):
|
||||||
"""Helper to check that field is blob
|
"""Helper to check that a field is a blob.
|
||||||
|
|
||||||
:param field_name: name of field
|
:param field_name: name of the field
|
||||||
:return: True if field is a blob, False otherwise
|
:return: True if the field is a blob, False otherwise
|
||||||
"""
|
"""
|
||||||
return isinstance(cls.fields.get(field_name), glare_fields.BlobField)
|
return isinstance(cls.fields.get(field_name), glare_fields.BlobField)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_blob_dict(cls, field_name):
|
def is_blob_dict(cls, field_name):
|
||||||
"""Helper to check that field is blob dict
|
"""Helper to check that field is a blob dict.
|
||||||
|
|
||||||
:param field_name: name of field
|
:param field_name: name of the field
|
||||||
:return: True if field is a blob dict, False otherwise
|
:return: True if the field is a blob dict, False otherwise
|
||||||
"""
|
"""
|
||||||
return (isinstance(cls.fields.get(field_name), glare_fields.Dict) and
|
return (isinstance(cls.fields.get(field_name), glare_fields.Dict) and
|
||||||
cls.fields[field_name].element_type ==
|
cls.fields[field_name].element_type ==
|
||||||
@@ -190,18 +190,18 @@ class BaseArtifact(base.VersionedObject):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_link(cls, field_name):
|
def is_link(cls, field_name):
|
||||||
"""Helper to check that field is link
|
"""Helper to check that a field is a link.
|
||||||
|
|
||||||
:param field_name: name of field
|
:param field_name: name of the field
|
||||||
:return: True if field is a link, False otherwise
|
:return: True if field is a link, False otherwise
|
||||||
"""
|
"""
|
||||||
return isinstance(cls.fields.get(field_name), glare_fields.Link)
|
return isinstance(cls.fields.get(field_name), glare_fields.Link)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_link_dict(cls, field_name):
|
def is_link_dict(cls, field_name):
|
||||||
"""Helper to check that field is link dict
|
"""Helper to check that a field is a link dict.
|
||||||
|
|
||||||
:param field_name: name of field
|
:param field_name: name of the field
|
||||||
:return: True if field is a link dict, False otherwise
|
:return: True if field is a link dict, False otherwise
|
||||||
"""
|
"""
|
||||||
return (isinstance(cls.fields.get(field_name), glare_fields.Dict) and
|
return (isinstance(cls.fields.get(field_name), glare_fields.Dict) and
|
||||||
@@ -210,10 +210,10 @@ class BaseArtifact(base.VersionedObject):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_link_list(cls, field_name):
|
def is_link_list(cls, field_name):
|
||||||
"""Helper to check that field is link list
|
"""Helper to check that a field is a link list.
|
||||||
|
|
||||||
:param field_name: name of field
|
:param field_name: name of the field
|
||||||
:return: True if field is a link list, False otherwise
|
:return: True if the field is a link list, False otherwise
|
||||||
"""
|
"""
|
||||||
return (isinstance(cls.fields.get(field_name), glare_fields.List) and
|
return (isinstance(cls.fields.get(field_name), glare_fields.List) and
|
||||||
cls.fields[field_name].element_type ==
|
cls.fields[field_name].element_type ==
|
||||||
@@ -221,11 +221,11 @@ class BaseArtifact(base.VersionedObject):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _init_artifact(cls, context, values):
|
def _init_artifact(cls, context, values):
|
||||||
"""Initialize an empty versioned object with values
|
"""Initialize an empty versioned object with values.
|
||||||
|
|
||||||
Initialize vo object with default values and values specified by user.
|
Initialize vo object with default values and values specified by user.
|
||||||
Also reset all changes for initialized object so user of the method
|
Also reset all changes of initialized object so user can track own
|
||||||
can track own changes.
|
changes.
|
||||||
|
|
||||||
:param context: user context
|
:param context: user context
|
||||||
:param values: values needs to be set
|
:param values: values needs to be set
|
||||||
@@ -239,31 +239,28 @@ class BaseArtifact(base.VersionedObject):
|
|||||||
default_attrs.append(attr)
|
default_attrs.append(attr)
|
||||||
if default_attrs:
|
if default_attrs:
|
||||||
af.obj_set_defaults(*default_attrs)
|
af.obj_set_defaults(*default_attrs)
|
||||||
|
|
||||||
|
# apply values specified by user
|
||||||
for name, value in six.iteritems(values):
|
for name, value in six.iteritems(values):
|
||||||
setattr(af, name, value)
|
setattr(af, name, value)
|
||||||
return af
|
return af
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_type_name(cls):
|
def get_type_name(cls):
|
||||||
"""Return type name that allows to find Artifact Type in Glare
|
"""Return type name that allows to find artifact type in Glare
|
||||||
|
|
||||||
Type name allows to find Artifact Type definition in Glare registry
|
Type name allows to find artifact type definition in Glare registry.
|
||||||
so Engine can instantiate Artifacts. Artifact also becomes available
|
|
||||||
with artifact type in Glare API.
|
:return: string that identifies current artifact type
|
||||||
For example, when get_type_name returns 'my_artifact' then
|
|
||||||
users can list artifacts by GET <host_name>/v1/artifacts/my_artifact.
|
|
||||||
This type name is also used in glare configuration when turning on/off
|
|
||||||
specific Artifact Types.
|
|
||||||
:return: string that identifies current Artifact Type.
|
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _lock_version(cls, context, values):
|
def _lock_version(cls, context, values):
|
||||||
"""Calculate version scope for new artifact
|
"""Create scope lock for artifact creation.
|
||||||
|
|
||||||
:param values: af values
|
:param values: artifact values
|
||||||
:return: string that identifies af version or None
|
:return: Lock object
|
||||||
"""
|
"""
|
||||||
name = values.get('name')
|
name = values.get('name')
|
||||||
version = values.get('version', cls.DEFAULT_ARTIFACT_VERSION)
|
version = values.get('version', cls.DEFAULT_ARTIFACT_VERSION)
|
||||||
@@ -274,10 +271,15 @@ class BaseArtifact(base.VersionedObject):
|
|||||||
return cls.lock_engine.acquire(context, scope_id)
|
return cls.lock_engine.acquire(context, scope_id)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _lock_updated_version(cls, af, updates):
|
def _lock_updated_version(cls, af, values):
|
||||||
name = updates.get('name', af.name)
|
"""Create scope lock for artifact update.
|
||||||
version = updates.get('version', af.version)
|
|
||||||
visibility = updates.get('visibility', af.visibility)
|
:param values: artifact values
|
||||||
|
:return: Lock object
|
||||||
|
"""
|
||||||
|
name = values.get('name', af.name)
|
||||||
|
version = values.get('version', af.version)
|
||||||
|
visibility = values.get('visibility', af.visibility)
|
||||||
scope_id = None
|
scope_id = None
|
||||||
if (name, version, visibility) != (af.name, af.version, af.visibility):
|
if (name, version, visibility) != (af.name, af.version, af.visibility):
|
||||||
# no version change == no lock for version
|
# no version change == no lock for version
|
||||||
@@ -289,11 +291,11 @@ class BaseArtifact(base.VersionedObject):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, context, values):
|
def create(cls, context, values):
|
||||||
"""Create new Artifact in Glare repo
|
"""Create new artifact in Glare repo.
|
||||||
|
|
||||||
:param context: user context
|
:param context: user context
|
||||||
:param values: Dict with specified artifact properties
|
:param values: dictionary with specified artifact fields
|
||||||
:return: definition of create Artifact
|
:return: created artifact object
|
||||||
"""
|
"""
|
||||||
if context.tenant is None or context.read_only:
|
if context.tenant is None or context.read_only:
|
||||||
msg = _("It's forbidden to anonymous users to create artifacts.")
|
msg = _("It's forbidden to anonymous users to create artifacts.")
|
||||||
@@ -317,11 +319,18 @@ class BaseArtifact(base.VersionedObject):
|
|||||||
LOG.info(_LI("Parameters validation for artifact creation "
|
LOG.info(_LI("Parameters validation for artifact creation "
|
||||||
"passed for request %s."), context.request_id)
|
"passed for request %s."), context.request_id)
|
||||||
af_vals = cls.db_api.create(
|
af_vals = cls.db_api.create(
|
||||||
context, af.obj_changes_to_primitive(), cls.get_type_name())
|
context, af._obj_changes_to_primitive(), cls.get_type_name())
|
||||||
return cls._init_artifact(context, af_vals)
|
return cls._init_artifact(context, af_vals)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _validate_versioning(cls, context, name, version, is_public=False):
|
def _validate_versioning(cls, context, name, version, is_public=False):
|
||||||
|
"""Validate if artifact with given name and version already exists.
|
||||||
|
|
||||||
|
:param context: user context
|
||||||
|
:param name: name of artifact to be checked
|
||||||
|
:param version: version of artifact
|
||||||
|
:param is_public: flag that indicates to search artifact globally
|
||||||
|
"""
|
||||||
if version is not None and name not in (None, ""):
|
if version is not None and name not in (None, ""):
|
||||||
filters = [('name', name), ('version', version),
|
filters = [('name', name), ('version', version),
|
||||||
('status', 'neq:deleted')]
|
('status', 'neq:deleted')]
|
||||||
@@ -341,7 +350,7 @@ class BaseArtifact(base.VersionedObject):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def _validate_change_allowed(cls, field_names, af=None,
|
def _validate_change_allowed(cls, field_names, af=None,
|
||||||
validate_blob_names=True):
|
validate_blob_names=True):
|
||||||
"""Validate if fields can be updated in artifact"""
|
"""Validate if fields can be updated in artifact."""
|
||||||
af_status = cls.STATUS.DRAFTED if af is None else af.status
|
af_status = cls.STATUS.DRAFTED if af is None else af.status
|
||||||
if af_status not in (cls.STATUS.ACTIVE, cls.STATUS.DRAFTED):
|
if af_status not in (cls.STATUS.ACTIVE, cls.STATUS.DRAFTED):
|
||||||
msg = _("Forbidden to change attributes "
|
msg = _("Forbidden to change attributes "
|
||||||
@@ -350,15 +359,15 @@ class BaseArtifact(base.VersionedObject):
|
|||||||
|
|
||||||
for field_name in field_names:
|
for field_name in field_names:
|
||||||
if field_name not in cls.fields:
|
if field_name not in cls.fields:
|
||||||
msg = _("%s property does not exist") % field_name
|
msg = _("%s field does not exist") % field_name
|
||||||
raise exception.BadRequest(msg)
|
raise exception.BadRequest(msg)
|
||||||
field = cls.fields[field_name]
|
field = cls.fields[field_name]
|
||||||
if field.system is True:
|
if field.system is True:
|
||||||
msg = _("Cannot specify system property %s. It is not "
|
msg = _("Cannot specify system field %s. It is not "
|
||||||
"available for modifying by users.") % field_name
|
"available for modifying by users.") % field_name
|
||||||
raise exception.Forbidden(msg)
|
raise exception.Forbidden(msg)
|
||||||
if af_status == cls.STATUS.ACTIVE and not field.mutable:
|
if af_status == cls.STATUS.ACTIVE and not field.mutable:
|
||||||
msg = (_("Forbidden to change property '%s' after activation.")
|
msg = (_("Forbidden to change field '%s' after activation.")
|
||||||
% field_name)
|
% field_name)
|
||||||
raise exception.Forbidden(message=msg)
|
raise exception.Forbidden(message=msg)
|
||||||
if validate_blob_names and \
|
if validate_blob_names and \
|
||||||
@@ -369,12 +378,12 @@ class BaseArtifact(base.VersionedObject):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def update(cls, context, af, values):
|
def update(cls, context, af, values):
|
||||||
"""Update Artifact in Glare repo
|
"""Update artifact in Glare repo.
|
||||||
|
|
||||||
:param context: user Context
|
:param context: user context
|
||||||
:param af: current definition of Artifact in Glare
|
:param af: current definition of artifact
|
||||||
:param values: dictionary with changes for artifact
|
:param values: dictionary with changes for artifact
|
||||||
:return: definition of updated Artifact
|
:return: updated artifact object
|
||||||
"""
|
"""
|
||||||
# reset all changes of artifact to reuse them after update
|
# reset all changes of artifact to reuse them after update
|
||||||
af.obj_reset_changes()
|
af.obj_reset_changes()
|
||||||
@@ -396,35 +405,39 @@ class BaseArtifact(base.VersionedObject):
|
|||||||
"update passed for request %(request)s."),
|
"update passed for request %(request)s."),
|
||||||
{'artifact': af.id, 'request': context.request_id})
|
{'artifact': af.id, 'request': context.request_id})
|
||||||
updated_af = cls.db_api.update(
|
updated_af = cls.db_api.update(
|
||||||
context, af.id, af.obj_changes_to_primitive())
|
context, af.id, af._obj_changes_to_primitive())
|
||||||
return cls._init_artifact(context, updated_af)
|
return cls._init_artifact(context, updated_af)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_action_for_updates(cls, context, artifact, updates, registry):
|
def get_action_for_updates(cls, context, af, values, registry):
|
||||||
"""The method defines how to detect appropriate action based on update
|
"""Define the appropriate method for artifact update.
|
||||||
|
|
||||||
Validate request for update and determine if it is request for action.
|
Based on update params this method defines what action engine should
|
||||||
Also do a validation for request for action if it is an action.
|
call for artifact update: activate, deactivate, reactivate, publish or
|
||||||
|
just a regular update of artifact fields.
|
||||||
|
|
||||||
:return: action reference for updates dict
|
:param context: user context
|
||||||
|
:param af: current definition of artifact
|
||||||
|
:param values: dictionary with changes for artifact
|
||||||
|
:param registry: registry of enabled artifact types
|
||||||
|
:return: method reference for updates dict
|
||||||
"""
|
"""
|
||||||
action = cls.update
|
if 'visibility' in values:
|
||||||
if 'visibility' in updates:
|
|
||||||
# validate publish action format
|
# validate publish action format
|
||||||
action = cls.publish
|
return cls.publish
|
||||||
elif 'status' in updates:
|
elif 'status' in values:
|
||||||
status = updates['status']
|
status = values['status']
|
||||||
if status == cls.STATUS.DEACTIVATED:
|
if status == cls.STATUS.DEACTIVATED:
|
||||||
action = cls.deactivate
|
return cls.deactivate
|
||||||
elif status == cls.STATUS.ACTIVE:
|
elif status == cls.STATUS.ACTIVE:
|
||||||
if artifact.status == artifact.STATUS.DEACTIVATED:
|
if af.status == af.STATUS.DEACTIVATED:
|
||||||
action = cls.reactivate
|
return cls.reactivate
|
||||||
else:
|
else:
|
||||||
action = cls.activate
|
return cls.activate
|
||||||
|
|
||||||
# check updates for links and validate them
|
# check updates for links and validate them
|
||||||
try:
|
try:
|
||||||
for key, value in six.iteritems(updates):
|
for key, value in six.iteritems(values):
|
||||||
if cls.is_link(key) and value is not None:
|
if cls.is_link(key) and value is not None:
|
||||||
cls._validate_link(key, value, context, registry)
|
cls._validate_link(key, value, context, registry)
|
||||||
elif cls.is_link_dict(key) and value:
|
elif cls.is_link_dict(key) and value:
|
||||||
@@ -434,14 +447,11 @@ class BaseArtifact(base.VersionedObject):
|
|||||||
for l in value:
|
for l in value:
|
||||||
cls._validate_link(key, l, context, registry)
|
cls._validate_link(key, l, context, registry)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
msg = (_("Bad link in artifact %(af)s: %(msg)s")
|
msg = (_("Broken link in artifact %(af)s: %(msg)s")
|
||||||
% {"af": artifact.id, "msg": str(e)})
|
% {"af": af.id, "msg": str(e)})
|
||||||
raise exception.BadRequest(msg)
|
raise exception.BadRequest(msg)
|
||||||
|
|
||||||
LOG.debug("Action %(action)s defined to updates %(updates)s.",
|
return cls.update
|
||||||
{'action': action.__name__, 'updates': updates})
|
|
||||||
|
|
||||||
return action
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _validate_link(cls, key, value, context, registry):
|
def _validate_link(cls, key, value, context, registry):
|
||||||
@@ -450,36 +460,29 @@ class BaseArtifact(base.VersionedObject):
|
|||||||
# check containment
|
# check containment
|
||||||
if glare_fields.LinkFieldType.is_external(value):
|
if glare_fields.LinkFieldType.is_external(value):
|
||||||
# validate external link
|
# validate external link
|
||||||
cls._validate_external_link(value)
|
with urlrequest.urlopen(value) as data:
|
||||||
|
data.read(1)
|
||||||
else:
|
else:
|
||||||
type_name = (glare_fields.LinkFieldType.
|
type_name = (glare_fields.LinkFieldType.
|
||||||
get_type_name(value))
|
get_type_name(value))
|
||||||
af_type = registry.get_artifact_type(type_name)
|
af_type = registry.get_artifact_type(type_name)
|
||||||
cls._validate_soft_link(context, value, af_type)
|
af_id = value.split('/')[3]
|
||||||
|
af_type.get(context, af_id)
|
||||||
@classmethod
|
|
||||||
def _validate_external_link(cls, link):
|
|
||||||
with urlrequest.urlopen(link) as data:
|
|
||||||
data.read(1)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _validate_soft_link(cls, context, link, af_type):
|
|
||||||
af_id = link.split('/')[3]
|
|
||||||
af_type.get(context, af_id)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get(cls, context, artifact_id):
|
def get(cls, context, artifact_id):
|
||||||
"""Return Artifact from Glare repo
|
"""Return Artifact from Glare repo
|
||||||
|
|
||||||
:param context: user context
|
:param context: user context
|
||||||
:param artifact_id: id of requested Artifact
|
:param artifact_id: id of requested artifact
|
||||||
:return: Artifact definition
|
:return: requested artifact object
|
||||||
"""
|
"""
|
||||||
af = cls.db_api.get(context, artifact_id)
|
af = cls.db_api.get(context, artifact_id)
|
||||||
return cls._init_artifact(context, af)
|
return cls._init_artifact(context, af)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _get_field_type(cls, obj):
|
def _get_field_type(cls, obj):
|
||||||
|
"""Get string representation of field type for filters."""
|
||||||
if isinstance(obj, fields.IntegerField) or obj is fields.Integer:
|
if isinstance(obj, fields.IntegerField) or obj is fields.Integer:
|
||||||
return 'int'
|
return 'int'
|
||||||
elif isinstance(obj, fields.FloatField) or obj is fields.Float:
|
elif isinstance(obj, fields.FloatField) or obj is fields.Float:
|
||||||
@@ -491,6 +494,7 @@ class BaseArtifact(base.VersionedObject):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _parse_sort_values(cls, sort):
|
def _parse_sort_values(cls, sort):
|
||||||
|
"""Prepare sorting parameters for database."""
|
||||||
new_sort = []
|
new_sort = []
|
||||||
for key, direction in sort:
|
for key, direction in sort:
|
||||||
if key not in cls.fields:
|
if key not in cls.fields:
|
||||||
@@ -504,12 +508,6 @@ class BaseArtifact(base.VersionedObject):
|
|||||||
cls.fields.get(key))))
|
cls.fields.get(key))))
|
||||||
return new_sort
|
return new_sort
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _validate_filter_name(cls, filter_name):
|
|
||||||
if cls.fields.get(filter_name) is None:
|
|
||||||
msg = _("Unable filter '%s'") % filter_name
|
|
||||||
raise exception.BadRequest(msg)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _validate_filter_ops(cls, filter_name, op):
|
def _validate_filter_ops(cls, filter_name, op):
|
||||||
field = cls.fields.get(filter_name)
|
field = cls.fields.get(filter_name)
|
||||||
@@ -550,7 +548,10 @@ class BaseArtifact(base.VersionedObject):
|
|||||||
msg = _("Field %s is not Dict") % filter_name
|
msg = _("Field %s is not Dict") % filter_name
|
||||||
raise exception.BadRequest(msg)
|
raise exception.BadRequest(msg)
|
||||||
|
|
||||||
cls._validate_filter_name(filter_name)
|
if cls.fields.get(filter_name) is None:
|
||||||
|
msg = _("Unable filter '%s'") % filter_name
|
||||||
|
raise exception.BadRequest(msg)
|
||||||
|
|
||||||
field_type = cls.fields.get(filter_name)
|
field_type = cls.fields.get(filter_name)
|
||||||
|
|
||||||
if isinstance(field_type, glare_fields.List) or isinstance(
|
if isinstance(field_type, glare_fields.List) or isinstance(
|
||||||
@@ -581,16 +582,18 @@ class BaseArtifact(base.VersionedObject):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def list(cls, context, filters=None, marker=None, limit=None,
|
def list(cls, context, filters=None, marker=None, limit=None,
|
||||||
sort=None, latest=False):
|
sort=None, latest=False):
|
||||||
"""List all available Artifacts in Glare repo
|
"""Return list of artifacts requested by user.
|
||||||
|
|
||||||
:param context: user context
|
:param context: user context
|
||||||
:param filters: filtering conditions to Artifact list
|
:param filters: filters that need to be applied to artifact
|
||||||
:param marker: id of Artifact that identifies where Glare should
|
:param marker: the artifact that considered as begin of the list
|
||||||
start listing Artifacts. So all Artifacts before that Artifact in
|
so all artifacts before marker (including marker itself) will not be
|
||||||
resulting list must be ignored. It is useful for Artifact pagination.
|
added to artifact list
|
||||||
:param limit: maximum number of Artifact items in list.
|
:param limit: maximum number of items in the list
|
||||||
:param sort: sorting preferences when requesting Artifact list.
|
:param sort: sorting options
|
||||||
:return: list of Artifacts
|
:param latest: flag that indicates, that only artifacts with highest
|
||||||
|
versions should be returned in output
|
||||||
|
:return: list of artifact objects
|
||||||
"""
|
"""
|
||||||
if sort is not None:
|
if sort is not None:
|
||||||
sort = cls._parse_sort_values(sort)
|
sort = cls._parse_sort_values(sort)
|
||||||
@@ -635,14 +638,11 @@ class BaseArtifact(base.VersionedObject):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def delete(cls, context, af):
|
def delete(cls, context, af):
|
||||||
"""Delete Artifact and all blobs from Glare.
|
"""Delete artifact and all its blobs from Glare.
|
||||||
|
|
||||||
:param context: user context
|
:param context: user context
|
||||||
:param af: definition of artifact targeted to delete
|
:param af: artifact object targeted for deletion
|
||||||
"""
|
"""
|
||||||
if af.visibility == 'public' and not context.is_admin:
|
|
||||||
msg = _("Only admins are allowed to delete public artifacts")
|
|
||||||
raise exception.Forbidden(msg)
|
|
||||||
# marking artifact as deleted
|
# marking artifact as deleted
|
||||||
cls.db_api.update(context, af.id, {'status': cls.STATUS.DELETED})
|
cls.db_api.update(context, af.id, {'status': cls.STATUS.DELETED})
|
||||||
|
|
||||||
@@ -669,17 +669,17 @@ class BaseArtifact(base.VersionedObject):
|
|||||||
# delete blobs one by one
|
# delete blobs one by one
|
||||||
cls._delete_blobs(blobs, context, af)
|
cls._delete_blobs(blobs, context, af)
|
||||||
LOG.info(_LI("Blobs successfully deleted for artifact %s"), af.id)
|
LOG.info(_LI("Blobs successfully deleted for artifact %s"), af.id)
|
||||||
|
# delete artifact itself
|
||||||
# delete the artifact itself
|
|
||||||
cls.db_api.delete(context, af.id)
|
cls.db_api.delete(context, af.id)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def activate(cls, context, af, values):
|
def activate(cls, context, af, values):
|
||||||
"""Activate Artifact and make it available for users
|
"""Activate artifact and make it available for usage.
|
||||||
|
|
||||||
:param context: User Context
|
:param context: user context
|
||||||
:param af: current Artifact definition in Glare
|
:param af: current artifact object
|
||||||
:return: definition of activated Artifact
|
:param values: dictionary with changes for artifact
|
||||||
|
:return: artifact object with changed status
|
||||||
"""
|
"""
|
||||||
# validate that came to artifact as updates
|
# validate that came to artifact as updates
|
||||||
if values != {'status': cls.STATUS.ACTIVE}:
|
if values != {'status': cls.STATUS.ACTIVE}:
|
||||||
@@ -700,16 +700,17 @@ class BaseArtifact(base.VersionedObject):
|
|||||||
LOG.info(_LI("Parameters validation for artifact %(artifact)s "
|
LOG.info(_LI("Parameters validation for artifact %(artifact)s "
|
||||||
"activate passed for request %(request)s."),
|
"activate passed for request %(request)s."),
|
||||||
{'artifact': af.id, 'request': context.request_id})
|
{'artifact': af.id, 'request': context.request_id})
|
||||||
active_af = cls.db_api.update(context, af.id, values)
|
af = cls.db_api.update(context, af.id, {'status': cls.STATUS.ACTIVE})
|
||||||
return cls._init_artifact(context, active_af)
|
return cls._init_artifact(context, af)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def reactivate(cls, context, af, values):
|
def reactivate(cls, context, af, values):
|
||||||
"""Make Artifact active after de-activation
|
"""Make Artifact active after deactivation
|
||||||
|
|
||||||
:param context: user context
|
:param context: user context
|
||||||
:param af: definition of de-activated Artifact
|
:param af: current artifact object
|
||||||
:return: definition of active Artifact
|
:param values: dictionary with changes for artifact
|
||||||
|
:return: artifact object with changed status
|
||||||
"""
|
"""
|
||||||
# validate that came to artifact as updates
|
# validate that came to artifact as updates
|
||||||
if values != {'status': cls.STATUS.ACTIVE}:
|
if values != {'status': cls.STATUS.ACTIVE}:
|
||||||
@@ -723,21 +724,23 @@ class BaseArtifact(base.VersionedObject):
|
|||||||
LOG.info(_LI("Parameters validation for artifact %(artifact)s "
|
LOG.info(_LI("Parameters validation for artifact %(artifact)s "
|
||||||
"reactivate passed for request %(request)s."),
|
"reactivate passed for request %(request)s."),
|
||||||
{'artifact': af.id, 'request': context.request_id})
|
{'artifact': af.id, 'request': context.request_id})
|
||||||
af = cls.db_api.update(context, af.id, values)
|
af = cls.db_api.update(context, af.id, {'status': cls.STATUS.ACTIVE})
|
||||||
return cls._init_artifact(context, af)
|
return cls._init_artifact(context, af)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def deactivate(cls, context, af, values):
|
def deactivate(cls, context, af, values):
|
||||||
"""Deny Artifact downloading due to security concerns
|
"""Deny Artifact downloading due to security concerns.
|
||||||
|
|
||||||
If user uploaded suspicious Artifact then Cloud Admins(or other users -
|
If user uploaded suspicious artifact then administrators(or other
|
||||||
it depends on policy configurations) can deny Artifact download by
|
users - it depends on policy configurations) can deny artifact data
|
||||||
users by making Artifact de-activated. After additional investigation
|
to be downloaded by regular users by making artifact deactivated.
|
||||||
Artifact can be re-activated or deleted from Glare.
|
After additional investigation artifact can be reactivated or
|
||||||
|
deleted from Glare.
|
||||||
|
|
||||||
:param context: user context
|
:param context: user context
|
||||||
:param af: Artifact definition in Glare
|
:param af: current artifact object
|
||||||
:return: definition of de-activated Artifact
|
:param values: dictionary with changes for artifact
|
||||||
|
:return: artifact object with changed status
|
||||||
"""
|
"""
|
||||||
if values != {'status': cls.STATUS.DEACTIVATED}:
|
if values != {'status': cls.STATUS.DEACTIVATED}:
|
||||||
msg = _("Only {'status': %s} is allowed in a request "
|
msg = _("Only {'status': %s} is allowed in a request "
|
||||||
@@ -751,16 +754,18 @@ class BaseArtifact(base.VersionedObject):
|
|||||||
LOG.info(_LI("Parameters validation for artifact %(artifact)s "
|
LOG.info(_LI("Parameters validation for artifact %(artifact)s "
|
||||||
"deactivate passed for request %(request)s."),
|
"deactivate passed for request %(request)s."),
|
||||||
{'artifact': af.id, 'request': context.request_id})
|
{'artifact': af.id, 'request': context.request_id})
|
||||||
af = cls.db_api.update(context, af.id, values)
|
af = cls.db_api.update(context, af.id,
|
||||||
|
{'status': cls.STATUS.DEACTIVATED})
|
||||||
return cls._init_artifact(context, af)
|
return cls._init_artifact(context, af)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def publish(cls, context, af, values):
|
def publish(cls, context, af, values):
|
||||||
"""Make Artifact available for everyone
|
"""Make artifact available for all tenants.
|
||||||
|
|
||||||
:param context: user context
|
:param context: user context
|
||||||
:param af: definition of published Artifact
|
:param af: current artifact object
|
||||||
:return: definition of active Artifact
|
:param values: dictionary with changes for artifact
|
||||||
|
:return: artifact object with changed visibility
|
||||||
"""
|
"""
|
||||||
if values != {'visibility': 'public'}:
|
if values != {'visibility': 'public'}:
|
||||||
msg = _("Only {'visibility': 'public'} is allowed in a request "
|
msg = _("Only {'visibility': 'public'} is allowed in a request "
|
||||||
@@ -778,17 +783,27 @@ class BaseArtifact(base.VersionedObject):
|
|||||||
LOG.info(_LI("Parameters validation for artifact %(artifact)s "
|
LOG.info(_LI("Parameters validation for artifact %(artifact)s "
|
||||||
"publish passed for request %(request)s."),
|
"publish passed for request %(request)s."),
|
||||||
{'artifact': af.id, 'request': context.request_id})
|
{'artifact': af.id, 'request': context.request_id})
|
||||||
af = cls.db_api.update(context, af.id, values)
|
af = cls.db_api.update(context, af.id, {'visibility': 'public'})
|
||||||
return cls._init_artifact(context, af)
|
return cls._init_artifact(context, af)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_max_blob_size(cls, field_name):
|
def get_max_blob_size(cls, field_name):
|
||||||
|
"""Get the maximum allowed blob size in bytes.
|
||||||
|
|
||||||
|
:param field_name: blob or blob dict field name
|
||||||
|
:return: maximum blob size in bytes
|
||||||
|
"""
|
||||||
return getattr(cls.fields[field_name], 'max_blob_size',
|
return getattr(cls.fields[field_name], 'max_blob_size',
|
||||||
attribute.BlobAttribute.DEFAULT_MAX_BLOB_SIZE)
|
attribute.BlobAttribute.DEFAULT_MAX_BLOB_SIZE)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate_upload_allowed(cls, context, af, field_name, blob_key=None):
|
def validate_upload_allowed(cls, af, field_name, blob_key=None):
|
||||||
"""Validate if given blob is ready for uploading."""
|
"""Validate if given blob is ready for uploading.
|
||||||
|
|
||||||
|
:param af: current artifact object
|
||||||
|
:param field_name: blob or blob dict field name
|
||||||
|
:param blob_key: indicates key name if field_name is a blob dict
|
||||||
|
"""
|
||||||
|
|
||||||
blob_name = "%s[%s]" % (field_name, blob_key)\
|
blob_name = "%s[%s]" % (field_name, blob_key)\
|
||||||
if blob_key else field_name
|
if blob_key else field_name
|
||||||
@@ -818,35 +833,40 @@ class BaseArtifact(base.VersionedObject):
|
|||||||
{'artifact': af.id, 'blob_name': blob_name})
|
{'artifact': af.id, 'blob_name': blob_name})
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def update_blob(cls, context, af_id, values):
|
def update_blob(cls, context, af_id, field_name, values):
|
||||||
"""Upload binary object as artifact property
|
"""Update blob info in database.
|
||||||
|
|
||||||
:param context: user context
|
:param context: user context
|
||||||
:param af_id: id of modified artifact
|
:param af_id: id of modified artifact
|
||||||
|
:param field_name: blob or blob dict field name
|
||||||
:param values: updated blob values
|
:param values: updated blob values
|
||||||
:return updated Artifact definition in Glare
|
:return updated artifact definition in Glare
|
||||||
"""
|
"""
|
||||||
af_upd = cls.db_api.update_blob(context, af_id, values)
|
af_upd = cls.db_api.update_blob(context, af_id, {field_name: values})
|
||||||
return cls._init_artifact(context, af_upd)
|
return cls._init_artifact(context, af_upd)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate_activate(cls, context, af, values=None):
|
def validate_activate(cls, context, af, values=None):
|
||||||
|
"""Validation hook for activation."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate_upload(cls, context, af, field_name, fd):
|
def validate_upload(cls, context, af, field_name, fd):
|
||||||
|
"""Validation hook for uploading."""
|
||||||
return fd, None
|
return fd, None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate_publish(cls, context, af):
|
def validate_publish(cls, context, af):
|
||||||
|
"""Validation hook for publishing."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_default_store(cls, context, af, field_name, blob_key):
|
def get_default_store(cls, context, af, field_name, blob_key):
|
||||||
|
"""Return a default store type for artifact type."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def to_notification(self):
|
def to_notification(self):
|
||||||
"""Return notification body that can be send to listeners
|
"""Return notification body that can be send to listeners.
|
||||||
|
|
||||||
:return: dict with notification information
|
:return: dict with notification information
|
||||||
"""
|
"""
|
||||||
@@ -865,13 +885,13 @@ class BaseArtifact(base.VersionedObject):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
"""Convert oslo versioned object to dictionary
|
"""Convert oslo versioned object to dictionary.
|
||||||
|
|
||||||
:return: dict with field names and field values
|
:return: dict with field names and field values
|
||||||
"""
|
"""
|
||||||
return self.obj_to_primitive()['versioned_object.data']
|
return self.obj_to_primitive()['versioned_object.data']
|
||||||
|
|
||||||
def obj_changes_to_primitive(self):
|
def _obj_changes_to_primitive(self):
|
||||||
changes = self.obj_get_changes()
|
changes = self.obj_get_changes()
|
||||||
res = {}
|
res = {}
|
||||||
for key, val in six.iteritems(changes):
|
for key, val in six.iteritems(changes):
|
||||||
@@ -882,7 +902,7 @@ class BaseArtifact(base.VersionedObject):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def schema_attr(cls, attr, attr_name=''):
|
def _schema_attr(cls, attr, attr_name=''):
|
||||||
attr_type = utils.get_schema_type(attr)
|
attr_type = utils.get_schema_type(attr)
|
||||||
schema = {}
|
schema = {}
|
||||||
|
|
||||||
@@ -968,10 +988,11 @@ class BaseArtifact(base.VersionedObject):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def gen_schemas(cls):
|
def gen_schemas(cls):
|
||||||
|
"""Return json schema representation of the artifact type."""
|
||||||
schemas_prop = {}
|
schemas_prop = {}
|
||||||
for attr_name, attr in six.iteritems(cls.fields):
|
for attr_name, attr in six.iteritems(cls.fields):
|
||||||
schemas_prop[attr_name] = cls.schema_attr(attr,
|
schemas_prop[attr_name] = cls._schema_attr(
|
||||||
attr_name=attr_name)
|
attr, attr_name=attr_name)
|
||||||
schemas = {'properties': schemas_prop,
|
schemas = {'properties': schemas_prop,
|
||||||
'name': cls.get_type_name(),
|
'name': cls.get_type_name(),
|
||||||
'version': cls.VERSION,
|
'version': cls.VERSION,
|
||||||
|
@@ -29,7 +29,7 @@ class Attribute(object):
|
|||||||
def __init__(self, field_class, mutable=False, required_on_activate=True,
|
def __init__(self, field_class, mutable=False, required_on_activate=True,
|
||||||
system=False, validators=None, nullable=True, default=None,
|
system=False, validators=None, nullable=True, default=None,
|
||||||
sortable=False, filter_ops=None, description=""):
|
sortable=False, filter_ops=None, description=""):
|
||||||
"""Init and validate attribute"""
|
"""Init and validate attribute."""
|
||||||
if not issubclass(field_class, fields.AutoTypedField):
|
if not issubclass(field_class, fields.AutoTypedField):
|
||||||
raise exc.IncorrectArtifactType(
|
raise exc.IncorrectArtifactType(
|
||||||
"Field class %s must be sub-class of AutoTypedField." %
|
"Field class %s must be sub-class of AutoTypedField." %
|
||||||
@@ -115,7 +115,7 @@ class Attribute(object):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def init(cls, *args, **kwargs):
|
def init(cls, *args, **kwargs):
|
||||||
"""Fabric to build attributes"""
|
"""Fabric to build attributes."""
|
||||||
return cls(*args, **kwargs).get_field()
|
return cls(*args, **kwargs).get_field()
|
||||||
|
|
||||||
|
|
||||||
|
@@ -49,7 +49,7 @@ CONF.register_opts(registry_options)
|
|||||||
|
|
||||||
|
|
||||||
def import_submodules(module):
|
def import_submodules(module):
|
||||||
"""Import all submodules of a module
|
"""Import all submodules of a module.
|
||||||
|
|
||||||
:param module: Package name
|
:param module: Package name
|
||||||
:return list of imported modules
|
:return list of imported modules
|
||||||
@@ -92,7 +92,7 @@ class ArtifactRegistry(vo_base.VersionedObjectRegistry):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def register_all_artifacts(cls):
|
def register_all_artifacts(cls):
|
||||||
"""Register all artifacts in glare"""
|
"""Register all artifacts in Glare."""
|
||||||
# get all submodules in glare.objects
|
# get all submodules in glare.objects
|
||||||
# please note that we registering trusted modules first
|
# please note that we registering trusted modules first
|
||||||
# and applying custom modules after that to allow custom modules
|
# and applying custom modules after that to allow custom modules
|
||||||
@@ -114,7 +114,7 @@ class ArtifactRegistry(vo_base.VersionedObjectRegistry):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_artifact_type(cls, type_name):
|
def get_artifact_type(cls, type_name):
|
||||||
"""Return artifact type based on artifact type name
|
"""Return artifact type based on artifact type name.
|
||||||
|
|
||||||
:param type_name: name of artifact type
|
:param type_name: name of artifact type
|
||||||
:return: artifact class
|
:return: artifact class
|
||||||
@@ -126,5 +126,5 @@ class ArtifactRegistry(vo_base.VersionedObjectRegistry):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def reset_registry(cls):
|
def reset_registry(cls):
|
||||||
"""Resets all registered artifact type classes"""
|
"""Resets all registered artifact type classes."""
|
||||||
cls._registry._obj_classes = collections.defaultdict(list)
|
cls._registry._obj_classes = collections.defaultdict(list)
|
||||||
|
@@ -26,7 +26,7 @@ LOG = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class Validator(object):
|
class Validator(object):
|
||||||
"""Common interface for all validators"""
|
"""Common interface for all validators."""
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
@@ -12,7 +12,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
"""Glare WSGI module
|
"""Glare WSGI module.
|
||||||
|
|
||||||
Use this module to deploy glare as WSGI application.
|
Use this module to deploy glare as WSGI application.
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user