Rework glare hooks
This patch adds pre and post hooks for all artifact modifying operations: create, update, activate, deactivate, reactivate, publish, upload, add_location, download, delete. Example: 'pre_create_hook' and 'post_create_hook'. Previous hook calls with 'validate_' prefix are removed except 'validate_upload' which is marked as depricated. Change-Id: I746ceaacd357e332790c84ffe50e712cfa6cecda
This commit is contained in:
parent
408e77dc5c
commit
b93aea2e3a
|
@ -14,7 +14,6 @@
|
|||
# under the License.
|
||||
|
||||
from copy import deepcopy
|
||||
import os
|
||||
|
||||
import jsonpatch
|
||||
from oslo_config import cfg
|
||||
|
@ -145,8 +144,10 @@ class Engine(object):
|
|||
def _apply_patch(self, context, af, patch):
|
||||
# This function is a collection of hacks and workarounds to make
|
||||
# json patch apply changes to oslo_vo object.
|
||||
action_names = {'artifact:update'}
|
||||
action_names = ['update']
|
||||
af_dict = af.to_dict()
|
||||
policy.authorize('artifact:update', af_dict, context)
|
||||
af.pre_update_hook(context, af)
|
||||
try:
|
||||
for operation in patch._ops:
|
||||
# apply the change to make sure that it's correct
|
||||
|
@ -174,19 +175,29 @@ class Engine(object):
|
|||
to_visibility=af_dict['visibility']
|
||||
)
|
||||
if af_dict['visibility'] == 'public':
|
||||
af.validate_publish(context, af)
|
||||
action_names.add('artifact:publish')
|
||||
policy.authorize(
|
||||
'artifact:publish', af_dict, context)
|
||||
af.pre_publish_hook(context, af)
|
||||
action_names.append('publish')
|
||||
elif field_name == 'status':
|
||||
utils.validate_status_transition(
|
||||
af, from_status=af.status, to_status=af_dict['status'])
|
||||
if af_dict['status'] == 'deactivated':
|
||||
action_names.add('artifact:deactivate')
|
||||
policy.authorize(
|
||||
'artifact:deactivate', af_dict, context)
|
||||
af.pre_deactivate_hook(context, af)
|
||||
action_names.append('deactivate')
|
||||
elif af_dict['status'] == 'active':
|
||||
if af.status == 'deactivated':
|
||||
action_names.add('artifact:reactivate')
|
||||
policy.authorize(
|
||||
'artifact:reactivate', af_dict, context)
|
||||
af.pre_reactivate_hook(context, af)
|
||||
action_names.append('reactivate')
|
||||
else:
|
||||
af.validate_activate(context, af)
|
||||
action_names.add('artifact:activate')
|
||||
policy.authorize(
|
||||
'artifact:activate', af_dict, context)
|
||||
af.pre_activate_hook(context, af)
|
||||
action_names.append('activate')
|
||||
else:
|
||||
utils.validate_change_allowed(af, field_name)
|
||||
|
||||
|
@ -235,7 +246,9 @@ class Engine(object):
|
|||
raise exception.BadRequest(msg)
|
||||
utils.validate_change_allowed(af, field_name)
|
||||
setattr(af, field_name, value)
|
||||
artifact_type.pre_create_hook(context, af)
|
||||
af = af.create(context)
|
||||
artifact_type.post_create_hook(context, af)
|
||||
# notify about new artifact
|
||||
Notifier.notify(context, action_name, af)
|
||||
# return artifact to the user
|
||||
|
@ -266,9 +279,6 @@ class Engine(object):
|
|||
if not updates:
|
||||
return af.to_dict()
|
||||
|
||||
for action_name in action_names:
|
||||
policy.authorize(action_name, af.to_dict(), context)
|
||||
|
||||
if any(i in updates for i in ('name', 'version', 'visibility')):
|
||||
# to change an artifact scope it's required to set a lock first
|
||||
with self._create_scoped_lock(
|
||||
|
@ -279,8 +289,14 @@ class Engine(object):
|
|||
else:
|
||||
modified_af = af.save(context)
|
||||
|
||||
# call post hooks for all operations when data is written in db and
|
||||
# send broadcast notifications
|
||||
for action_name in action_names:
|
||||
Notifier.notify(context, action_name, modified_af)
|
||||
getattr(modified_af, 'post_%s_hook' % action_name)(
|
||||
context, modified_af)
|
||||
Notifier.notify(
|
||||
context, 'artifact:' + action_name, modified_af)
|
||||
|
||||
return modified_af.to_dict()
|
||||
|
||||
def show(self, context, type_name, artifact_id):
|
||||
|
@ -353,7 +369,7 @@ class Engine(object):
|
|||
af = self._show_artifact(context, type_name, artifact_id)
|
||||
action_name = 'artifact:delete'
|
||||
policy.authorize(action_name, af.to_dict(), context)
|
||||
af.validate_delete(context, af)
|
||||
af.pre_delete_hook(context, af)
|
||||
blobs = af.delete(context, af)
|
||||
|
||||
delayed_delete = getattr(
|
||||
|
@ -369,6 +385,7 @@ class Engine(object):
|
|||
LOG.info("Blobs successfully deleted for artifact %s", af.id)
|
||||
# delete artifact itself
|
||||
af.db_api.delete(context, af.id)
|
||||
af.post_delete_hook(context, af)
|
||||
Notifier.notify(context, action_name, af)
|
||||
|
||||
@staticmethod
|
||||
|
@ -450,6 +467,8 @@ class Engine(object):
|
|||
"%(af)s") % {'blob': field_name, 'af': af.id}
|
||||
raise exception.Conflict(message=msg)
|
||||
utils.validate_change_allowed(af, field_name)
|
||||
af.pre_add_location_hook(
|
||||
context, af, field_name, location, blob_key)
|
||||
modified_af = self._save_blob_info(
|
||||
context, af, field_name, blob_key, blob)
|
||||
|
||||
|
@ -458,6 +477,8 @@ class Engine(object):
|
|||
{'location': location, 'artifact': af.id,
|
||||
'blob': blob_name})
|
||||
|
||||
modified_af.post_add_location_hook(
|
||||
context, modified_af, field_name, blob_key)
|
||||
Notifier.notify(context, action_name, modified_af)
|
||||
return modified_af.to_dict()
|
||||
|
||||
|
@ -548,11 +569,16 @@ class Engine(object):
|
|||
{'artifact': af.id, 'blob_name': blob_name})
|
||||
|
||||
# try to perform blob uploading to storage
|
||||
path = None
|
||||
try:
|
||||
try:
|
||||
# call upload hook first
|
||||
fd, path = af.validate_upload(context, af, field_name, fd)
|
||||
if hasattr(af, 'validate_upload'):
|
||||
LOG.warning("Method 'validate_upload' was deprecated. "
|
||||
"Please use 'pre_upload_hook' instead.")
|
||||
fd, path = af.validate_upload(context, af, field_name, fd)
|
||||
else:
|
||||
fd = af.pre_upload_hook(
|
||||
context, af, field_name, blob_key, fd)
|
||||
except exception.GlareException:
|
||||
raise
|
||||
except Exception as e:
|
||||
|
@ -576,9 +602,6 @@ class Engine(object):
|
|||
blob_dict_attr = getattr(modified_af, field_name)
|
||||
del blob_dict_attr[blob_key]
|
||||
af.update_blob(context, af.id, field_name, blob_dict_attr)
|
||||
finally:
|
||||
if path:
|
||||
os.remove(path)
|
||||
|
||||
LOG.info("Successfully finished blob uploading for artifact "
|
||||
"%(artifact)s blob field %(blob)s.",
|
||||
|
@ -595,6 +618,9 @@ class Engine(object):
|
|||
modified_af = self._save_blob_info(
|
||||
context, af, field_name, blob_key, blob)
|
||||
|
||||
modified_af.post_upload_hook(
|
||||
context, modified_af, field_name, blob_key)
|
||||
|
||||
Notifier.notify(context, action_name, modified_af)
|
||||
return modified_af.to_dict()
|
||||
|
||||
|
@ -628,6 +654,8 @@ class Engine(object):
|
|||
msg = _("%s is not ready for download") % blob_name
|
||||
raise exception.Conflict(message=msg)
|
||||
|
||||
af.pre_download_hook(context, af, field_name, blob_key)
|
||||
|
||||
meta = {'md5': blob.get('md5'),
|
||||
'sha1': blob.get('sha1'),
|
||||
'sha256': blob.get('sha256'),
|
||||
|
@ -639,18 +667,14 @@ class Engine(object):
|
|||
meta['size'] = blob.get('size')
|
||||
meta['content_type'] = blob.get('content_type')
|
||||
|
||||
path = None
|
||||
try:
|
||||
# call download hook in the end
|
||||
data, path = af.validate_download(
|
||||
context, af, field_name, data)
|
||||
data = af.post_download_hook(
|
||||
context, af, field_name, blob_key, data)
|
||||
except exception.GlareException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise exception.BadRequest(message=str(e))
|
||||
finally:
|
||||
if path:
|
||||
os.remove(path)
|
||||
|
||||
return data, meta
|
||||
|
||||
|
|
|
@ -478,29 +478,87 @@ Possible values:
|
|||
af_upd = cls.db_api.update_blob(context, af_id, {field_name: values})
|
||||
return cls.init_artifact(context, af_upd)
|
||||
|
||||
# Next comes a collection of hooks for various operations
|
||||
|
||||
@classmethod
|
||||
def validate_activate(cls, context, af):
|
||||
"""Validation hook for activation."""
|
||||
def pre_create_hook(cls, context, af):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def validate_upload(cls, context, af, field_name, fd):
|
||||
"""Validation hook for uploading."""
|
||||
return fd, None
|
||||
|
||||
@classmethod
|
||||
def validate_download(cls, context, af, field_name, fd):
|
||||
"""Validation hook for downloading."""
|
||||
return fd, None
|
||||
|
||||
@classmethod
|
||||
def validate_publish(cls, context, af):
|
||||
"""Validation hook for publishing."""
|
||||
def post_create_hook(cls, context, af):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def validate_delete(cls, context, af):
|
||||
"""Validation hook for deletion."""
|
||||
def pre_update_hook(cls, context, af):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def post_update_hook(cls, context, af):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def pre_activate_hook(cls, context, af):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def post_activate_hook(cls, context, af):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def pre_publish_hook(cls, context, af):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def post_publish_hook(cls, context, af):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def pre_deactivate_hook(cls, context, af):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def post_deactivate_hook(cls, context, af):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def pre_reactivate_hook(cls, context, af):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def post_reactivate_hook(cls, context, af):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def pre_upload_hook(cls, context, af, field_name, blob_key, fd):
|
||||
return fd
|
||||
|
||||
@classmethod
|
||||
def post_upload_hook(cls, context, af, field_name, blob_key):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def pre_add_location_hook(
|
||||
cls, context, af, field_name, blob_key, location):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def post_add_location_hook(cls, context, af, field_name, blob_key):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def pre_download_hook(cls, context, af, field_name, blob_key):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def post_download_hook(cls, context, af, field_name, blob_key, fd):
|
||||
return fd
|
||||
|
||||
@classmethod
|
||||
def pre_delete_hook(cls, context, af):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def post_delete_hook(cls, context, af):
|
||||
pass
|
||||
|
||||
def to_notification(self):
|
||||
|
|
|
@ -12,16 +12,13 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_versionedobjects import fields
|
||||
|
||||
from glare.common import exception
|
||||
from glare.objects import base
|
||||
from glare.objects.meta import file_utils
|
||||
from glare.objects.meta import wrappers
|
||||
|
||||
Field = wrappers.Field.init
|
||||
|
@ -36,21 +33,41 @@ LOG = logging.getLogger(__name__)
|
|||
|
||||
class HookChecker(base.BaseArtifact):
|
||||
fields = {
|
||||
'zip': Blob(description="Original zipped data.",
|
||||
required_on_activate=False),
|
||||
'content': Folder(system=True, required_on_activate=False),
|
||||
'forbid_activate': Field(fields.FlexibleBooleanField,
|
||||
default=False),
|
||||
'forbid_publish': Field(fields.FlexibleBooleanField,
|
||||
default=False, mutable=True),
|
||||
'forbid_download_zip': Field(fields.FlexibleBooleanField,
|
||||
default=False),
|
||||
'forbid_delete': Field(fields.FlexibleBooleanField,
|
||||
default=False, mutable=True),
|
||||
'temp_dir': Field(
|
||||
fields.StringField,
|
||||
required_on_activate=False,
|
||||
mutable=True),
|
||||
'temp_file_path_create': Field(
|
||||
fields.StringField,
|
||||
required_on_activate=False,
|
||||
mutable=True),
|
||||
'temp_file_path_update': Field(
|
||||
fields.StringField,
|
||||
required_on_activate=False,
|
||||
mutable=True),
|
||||
'temp_file_path_activate': Field(
|
||||
fields.StringField,
|
||||
required_on_activate=False,
|
||||
mutable=True),
|
||||
'temp_file_path_reactivate': Field(
|
||||
fields.StringField,
|
||||
required_on_activate=False,
|
||||
mutable=True),
|
||||
'temp_file_path_deactivate': Field(
|
||||
fields.StringField,
|
||||
required_on_activate=False,
|
||||
mutable=True),
|
||||
'temp_file_path_publish': Field(
|
||||
fields.StringField,
|
||||
required_on_activate=False,
|
||||
mutable=True),
|
||||
'blob': Blob(
|
||||
required_on_activate=False,
|
||||
mutable=True)
|
||||
}
|
||||
|
||||
artifact_type_opts = [
|
||||
cfg.BoolOpt('in_memory_processing')
|
||||
cfg.StrOpt('temp_file_path')
|
||||
]
|
||||
|
||||
@classmethod
|
||||
|
@ -58,61 +75,142 @@ class HookChecker(base.BaseArtifact):
|
|||
return "hooks_artifact"
|
||||
|
||||
@classmethod
|
||||
def _validate_upload_harddrive(cls, context, af, field_name, fd):
|
||||
path = None
|
||||
tdir = None
|
||||
try:
|
||||
tfile, path = file_utils.create_temporary_file(fd, '.zip')
|
||||
tdir = file_utils.extract_zip_to_temporary_folder(tfile)
|
||||
|
||||
# upload all files to 'content' folder
|
||||
for subdir, dirs, files in os.walk(tdir):
|
||||
for file_name in files:
|
||||
path_to_file = os.path.join(subdir, file_name)
|
||||
with open(path_to_file, "rb") as f:
|
||||
file_utils.upload_content_file(
|
||||
context, af, f, 'content',
|
||||
path_to_file[len(tdir) + 1:])
|
||||
except Exception as e:
|
||||
if path is not None and os.path.exists(path):
|
||||
# remove temporary file if something went wrong
|
||||
os.remove(path)
|
||||
raise e
|
||||
finally:
|
||||
# remove temporary folder
|
||||
if tdir is not None:
|
||||
shutil.rmtree(tdir)
|
||||
|
||||
tfile.flush()
|
||||
tfile.seek(0)
|
||||
return tfile, path
|
||||
def pre_create_hook(cls, context, af):
|
||||
# create a temporary file and set the path to artifact field
|
||||
__, af.temp_file_path_create = tempfile.mkstemp(dir=af.temp_dir)
|
||||
with open(af.temp_file_path_create, 'w') as f:
|
||||
f.write('pre_create_hook was called\n')
|
||||
|
||||
@classmethod
|
||||
def validate_upload(cls, context, af, field_name, fd):
|
||||
if getattr(CONF, 'artifact_type:hooks_artifact').in_memory_processing:
|
||||
return file_utils.unpack_zip_archive_in_memory(
|
||||
context, af, 'content', fd), None
|
||||
else:
|
||||
return cls._validate_upload_harddrive(
|
||||
context, af, field_name, fd)
|
||||
def post_create_hook(cls, context, af):
|
||||
with open(af.temp_file_path_create, 'a') as f:
|
||||
f.write('post_create_hook was called\n')
|
||||
|
||||
@classmethod
|
||||
def validate_download(cls, context, af, field_name, fd):
|
||||
if af.forbid_download_zip and field_name == 'zip':
|
||||
raise exception.BadRequest
|
||||
return fd, None
|
||||
def pre_update_hook(cls, context, af):
|
||||
# create a temporary file and set the path to artifact field
|
||||
__, af.temp_file_path_update = tempfile.mkstemp(dir=af.temp_dir)
|
||||
with open(af.temp_file_path_update, 'w') as f:
|
||||
f.write('pre_update_hook was called\n')
|
||||
|
||||
@classmethod
|
||||
def validate_activate(cls, context, af):
|
||||
if af.forbid_activate:
|
||||
raise exception.BadRequest
|
||||
def post_update_hook(cls, context, af):
|
||||
with open(af.temp_file_path_update, 'a') as f:
|
||||
f.write('post_update_hook was called\n')
|
||||
|
||||
@classmethod
|
||||
def validate_publish(cls, context, af):
|
||||
if af.forbid_publish:
|
||||
raise exception.BadRequest
|
||||
def pre_activate_hook(cls, context, af):
|
||||
# create a temporary file and set the path to artifact field
|
||||
__, af.temp_file_path_activate = tempfile.mkstemp(dir=af.temp_dir)
|
||||
with open(af.temp_file_path_activate, 'w') as f:
|
||||
f.write('pre_activate_hook was called\n')
|
||||
|
||||
@classmethod
|
||||
def validate_delete(cls, context, af):
|
||||
if af.forbid_delete:
|
||||
raise exception.BadRequest
|
||||
def post_activate_hook(cls, context, af):
|
||||
with open(af.temp_file_path_activate, 'a') as f:
|
||||
f.write('post_activate_hook was called\n')
|
||||
|
||||
@classmethod
|
||||
def pre_publish_hook(cls, context, af):
|
||||
# create a temporary file and set the path to artifact field
|
||||
__, af.temp_file_path_publish = tempfile.mkstemp(dir=af.temp_dir)
|
||||
with open(af.temp_file_path_publish, 'w') as f:
|
||||
f.write('pre_publish_hook was called\n')
|
||||
|
||||
@classmethod
|
||||
def post_publish_hook(cls, context, af):
|
||||
with open(af.temp_file_path_publish, 'a') as f:
|
||||
f.write('post_publish_hook was called\n')
|
||||
|
||||
@classmethod
|
||||
def pre_deactivate_hook(cls, context, af):
|
||||
# create a temporary file and set the path to artifact field
|
||||
__, af.temp_file_path_deactivate = tempfile.mkstemp(dir=af.temp_dir)
|
||||
with open(af.temp_file_path_deactivate, 'w') as f:
|
||||
f.write('pre_deactivate_hook was called\n')
|
||||
|
||||
@classmethod
|
||||
def post_deactivate_hook(cls, context, af):
|
||||
with open(af.temp_file_path_deactivate, 'a') as f:
|
||||
f.write('post_deactivate_hook was called\n')
|
||||
|
||||
@classmethod
|
||||
def pre_reactivate_hook(cls, context, af):
|
||||
# create a temporary file and set the path to artifact field
|
||||
__, af.temp_file_path_reactivate = tempfile.mkstemp(dir=af.temp_dir)
|
||||
with open(af.temp_file_path_reactivate, 'w') as f:
|
||||
f.write('pre_reactivate_hook was called\n')
|
||||
|
||||
@classmethod
|
||||
def post_reactivate_hook(cls, context, af):
|
||||
with open(af.temp_file_path_reactivate, 'a') as f:
|
||||
f.write('post_reactivate_hook was called\n')
|
||||
|
||||
@classmethod
|
||||
def pre_upload_hook(cls, context, af, field_name, blob_key, fd):
|
||||
# create a temporary file and set the path to artifact field
|
||||
file_path = getattr(
|
||||
CONF, 'artifact_type:hooks_artifact').temp_file_path
|
||||
if file_path:
|
||||
with open(file_path, 'w') as f:
|
||||
f.write('pre_upload_hook was called\n')
|
||||
return fd
|
||||
|
||||
@classmethod
|
||||
def post_upload_hook(cls, context, af, field_name, blob_key):
|
||||
file_path = getattr(
|
||||
CONF, 'artifact_type:hooks_artifact').temp_file_path
|
||||
if file_path:
|
||||
with open(file_path, 'a') as f:
|
||||
f.write('post_upload_hook was called\n')
|
||||
|
||||
@classmethod
|
||||
def pre_add_location_hook(
|
||||
cls, context, af, field_name, blob_key, location):
|
||||
# create a temporary file and set the path to artifact field
|
||||
file_path = getattr(
|
||||
CONF, 'artifact_type:hooks_artifact').temp_file_path
|
||||
if file_path:
|
||||
with open(file_path, 'w') as f:
|
||||
f.write('pre_add_location_hook was called\n')
|
||||
|
||||
@classmethod
|
||||
def post_add_location_hook(cls, context, af, field_name, blob_key):
|
||||
file_path = getattr(
|
||||
CONF, 'artifact_type:hooks_artifact').temp_file_path
|
||||
if file_path:
|
||||
with open(file_path, 'a') as f:
|
||||
f.write('post_add_location_hook was called\n')
|
||||
|
||||
@classmethod
|
||||
def pre_download_hook(cls, context, af, field_name, blob_key):
|
||||
file_path = getattr(
|
||||
CONF, 'artifact_type:hooks_artifact').temp_file_path
|
||||
if file_path:
|
||||
with open(file_path, 'a') as f:
|
||||
f.write('pre_download_hook was called\n')
|
||||
|
||||
@classmethod
|
||||
def post_download_hook(cls, context, af, field_name, blob_key, fd):
|
||||
file_path = getattr(
|
||||
CONF, 'artifact_type:hooks_artifact').temp_file_path
|
||||
if file_path:
|
||||
with open(file_path, 'a') as f:
|
||||
f.write('post_download_hook was called\n')
|
||||
return fd
|
||||
|
||||
@classmethod
|
||||
def pre_delete_hook(cls, context, af):
|
||||
file_path = getattr(
|
||||
CONF, 'artifact_type:hooks_artifact').temp_file_path
|
||||
if file_path:
|
||||
with open(file_path, 'w') as f:
|
||||
f.write('pre_delete_hook was called\n')
|
||||
|
||||
@classmethod
|
||||
def post_delete_hook(cls, context, af):
|
||||
file_path = getattr(
|
||||
CONF, 'artifact_type:hooks_artifact').temp_file_path
|
||||
if file_path:
|
||||
with open(file_path, 'a') as f:
|
||||
f.write('post_delete_hook was called\n')
|
||||
|
|
|
@ -252,25 +252,21 @@ class TestArtifactUpload(base.BaseTestArtifactAPI):
|
|||
self.sample_artifact['id'])
|
||||
self.assertNotIn('blb', artifact['dict_of_blobs'])
|
||||
|
||||
@mock.patch('os.remove')
|
||||
def test_upload_with_hook(self, mocked_os_remove):
|
||||
def test_upload_with_hook(self):
|
||||
with mock.patch.object(
|
||||
sample_artifact.SampleArtifact, 'validate_upload',
|
||||
return_value=(BytesIO(b'aaa'), 'temporary_path')):
|
||||
sample_artifact.SampleArtifact, 'pre_upload_hook',
|
||||
return_value=BytesIO(b'ffff')):
|
||||
self.controller.upload_blob(
|
||||
self.req, 'sample_artifact', self.sample_artifact['id'],
|
||||
'blob', BytesIO(b'aaa'), 'application/octet-stream')
|
||||
artifact = self.controller.show(self.req, 'sample_artifact',
|
||||
self.sample_artifact['id'])
|
||||
self.assertEqual(3, artifact['blob']['size'])
|
||||
self.assertEqual(4, artifact['blob']['size'])
|
||||
self.assertEqual('active', artifact['blob']['status'])
|
||||
# If temporary folder has been created it must be removed
|
||||
mocked_os_remove.assert_called_once_with('temporary_path')
|
||||
|
||||
@mock.patch('os.remove')
|
||||
def test_upload_with_hook_error(self, mocked_os_remove):
|
||||
def test_upload_with_hook_error(self):
|
||||
with mock.patch.object(
|
||||
sample_artifact.SampleArtifact, 'validate_upload',
|
||||
sample_artifact.SampleArtifact, 'pre_upload_hook',
|
||||
side_effect=Exception):
|
||||
self.assertRaises(
|
||||
exc.BadRequest, self.controller.upload_blob,
|
||||
|
@ -280,7 +276,6 @@ class TestArtifactUpload(base.BaseTestArtifactAPI):
|
|||
art = self.controller.show(self.req, 'sample_artifact',
|
||||
self.sample_artifact['id'])
|
||||
self.assertEqual({}, art['dict_of_blobs'])
|
||||
self.assertEqual(0, mocked_os_remove.call_count)
|
||||
|
||||
def test_upload_nonexistent_field(self):
|
||||
self.assertRaises(
|
||||
|
|
|
@ -12,187 +12,115 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import mock
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from six import BytesIO
|
||||
|
||||
from glare.common import exception as exc
|
||||
from glare.tests.unit import base
|
||||
|
||||
|
||||
class TestArtifactHooks(base.BaseTestArtifactAPI):
|
||||
|
||||
def setUp(self):
|
||||
super(TestArtifactHooks, self).setUp()
|
||||
values = {'name': 'ttt', 'version': '1.0'}
|
||||
self.hooks_artifact = self.controller.create(
|
||||
self.req, 'hooks_artifact', values)
|
||||
def test_create_hook(self):
|
||||
values = {'name': 'ttt', 'version': '1.0', 'temp_dir': self.test_dir}
|
||||
art = self.controller.create(self.req, 'hooks_artifact', values)
|
||||
self.assertEqual(self.test_dir, art['temp_dir'])
|
||||
self.assertIsNotNone(art['temp_file_path_create'])
|
||||
with open(art['temp_file_path_create']) as f:
|
||||
self.assertEqual('pre_create_hook was called\n', f.readline())
|
||||
self.assertEqual('post_create_hook was called\n', f.readline())
|
||||
|
||||
def test_upload_hook(self):
|
||||
var_dir = os.path.abspath(os.path.join(os.path.dirname(__file__),
|
||||
'../', 'var'))
|
||||
data_path = os.path.join(var_dir, 'hooks.zip')
|
||||
with open(data_path, "rb") as data:
|
||||
self.controller.upload_blob(
|
||||
self.req, 'hooks_artifact', self.hooks_artifact['id'], 'zip',
|
||||
data, 'application/octet-stream')
|
||||
artifact = self.controller.show(self.req, 'hooks_artifact',
|
||||
self.hooks_artifact['id'])
|
||||
self.assertEqual(818, artifact['zip']['size'])
|
||||
self.assertEqual('active', artifact['zip']['status'])
|
||||
|
||||
self.assertEqual(11, artifact['content']['aaa.txt']['size'])
|
||||
self.assertEqual(11, artifact['content']['folder1/bbb.txt']['size'])
|
||||
self.assertEqual(
|
||||
11, artifact['content']['folder1/folder2/ccc.txt']['size'])
|
||||
|
||||
def test_upload_hook_inmemory(self):
|
||||
# enable in-memory processing
|
||||
self.config(in_memory_processing=True,
|
||||
group='artifact_type:hooks_artifact')
|
||||
|
||||
# First check uploading with smaller limit fails
|
||||
with mock.patch('glare.objects.meta.file_utils.'
|
||||
'INMEMORY_OBJECT_SIZE_LIMIT', 817):
|
||||
self.assertRaises(exc.RequestEntityTooLarge, self.test_upload_hook)
|
||||
|
||||
# Now try with standard limit
|
||||
self.test_upload_hook()
|
||||
|
||||
def test_download_hook(self):
|
||||
# upload data
|
||||
var_dir = os.path.abspath(os.path.join(os.path.dirname(__file__),
|
||||
'../', 'var'))
|
||||
data_path = os.path.join(var_dir, 'hooks.zip')
|
||||
with open(data_path, "rb") as data:
|
||||
self.controller.upload_blob(
|
||||
self.req, 'hooks_artifact', self.hooks_artifact['id'], 'zip',
|
||||
data, 'application/octet-stream')
|
||||
|
||||
# download main 'zip'
|
||||
data = self.controller.download_blob(
|
||||
self.req, 'hooks_artifact', self.hooks_artifact['id'],
|
||||
'zip')['data']
|
||||
bytes_read = 0
|
||||
for chunk in data:
|
||||
bytes_read += len(chunk)
|
||||
self.assertEqual(818, bytes_read)
|
||||
|
||||
# download a file from 'content'
|
||||
data = self.controller.download_blob(
|
||||
self.req, 'hooks_artifact', self.hooks_artifact['id'],
|
||||
'content/folder1/bbb.txt')['data']
|
||||
bytes_read = 0
|
||||
for chunk in data:
|
||||
bytes_read += len(chunk)
|
||||
self.assertEqual(11, bytes_read)
|
||||
|
||||
# now forbid to download zip
|
||||
changes = [{'op': 'replace', 'path': '/forbid_download_zip',
|
||||
'value': 'yes'}]
|
||||
self.update_with_values(changes, art_type='hooks_artifact',
|
||||
art_id=self.hooks_artifact['id'])
|
||||
|
||||
artifact = self.controller.show(self.req, 'hooks_artifact',
|
||||
self.hooks_artifact['id'])
|
||||
self.assertEqual(True, artifact['forbid_download_zip'])
|
||||
|
||||
# download from 'zip' fails now
|
||||
self.assertRaises(
|
||||
exc.BadRequest, self.controller.download_blob,
|
||||
self.req, 'hooks_artifact', self.hooks_artifact['id'], 'zip')
|
||||
|
||||
# download a 'content' file still works
|
||||
data = self.controller.download_blob(
|
||||
self.req, 'hooks_artifact', self.hooks_artifact['id'],
|
||||
'content/folder1/folder2/ccc.txt')['data']
|
||||
bytes_read = 0
|
||||
for chunk in data:
|
||||
bytes_read += len(chunk)
|
||||
self.assertEqual(11, bytes_read)
|
||||
|
||||
def test_activation_hook(self):
|
||||
# forbid to activate artifact
|
||||
changes = [{'op': 'replace', 'path': '/forbid_activate',
|
||||
'value': 'yes'}]
|
||||
self.update_with_values(changes, art_type='hooks_artifact',
|
||||
art_id=self.hooks_artifact['id'])
|
||||
|
||||
# activation fails now
|
||||
changes = [{'op': 'replace', 'path': '/status',
|
||||
'value': 'active'}]
|
||||
self.assertRaises(
|
||||
exc.BadRequest, self.update_with_values, changes,
|
||||
art_type='hooks_artifact', art_id=self.hooks_artifact['id'])
|
||||
|
||||
# unblock artifact activation
|
||||
changes = [{'op': 'replace', 'path': '/forbid_activate',
|
||||
'value': 'no'}]
|
||||
self.update_with_values(changes, art_type='hooks_artifact',
|
||||
art_id=self.hooks_artifact['id'])
|
||||
|
||||
# now activation works
|
||||
changes = [{'op': 'replace', 'path': '/status',
|
||||
'value': 'active'}]
|
||||
art = self.update_with_values(changes, art_type='hooks_artifact',
|
||||
art_id=self.hooks_artifact['id'])
|
||||
self.assertEqual('active', art['status'])
|
||||
|
||||
def test_publishing_hook(self):
|
||||
def test_update_ops_hook(self):
|
||||
self.req = self.get_fake_request(user=self.users['admin'])
|
||||
values = {'name': 'ttt', 'version': '1.0', 'temp_dir': self.test_dir}
|
||||
art = self.controller.create(self.req, 'hooks_artifact', values)
|
||||
self.assertEqual(self.test_dir, art['temp_dir'])
|
||||
|
||||
# activate artifact to begin
|
||||
changes = [{'op': 'replace', 'path': '/status',
|
||||
'value': 'active'}]
|
||||
changes = [{'op': 'replace', 'path': '/description',
|
||||
'value': 'some_string'},
|
||||
{'op': 'replace', 'path': '/status',
|
||||
'value': 'active'},
|
||||
{'op': 'replace', 'path': '/status',
|
||||
'value': 'deactivated'},
|
||||
{'op': 'replace', 'path': '/status',
|
||||
'value': 'active'},
|
||||
{'op': 'replace', 'path': '/visibility',
|
||||
'value': 'public'}]
|
||||
art = self.update_with_values(changes, art_type='hooks_artifact',
|
||||
art_id=self.hooks_artifact['id'])
|
||||
art_id=art['id'])
|
||||
self.assertEqual('active', art['status'])
|
||||
|
||||
# forbid to publish artifact
|
||||
changes = [{'op': 'replace', 'path': '/forbid_publish',
|
||||
'value': 'yes'}]
|
||||
self.update_with_values(changes, art_type='hooks_artifact',
|
||||
art_id=self.hooks_artifact['id'])
|
||||
|
||||
# publication fails now
|
||||
changes = [{'op': 'replace', 'path': '/visibility',
|
||||
'value': 'public'}]
|
||||
self.assertRaises(
|
||||
exc.BadRequest, self.update_with_values, changes,
|
||||
art_type='hooks_artifact', art_id=self.hooks_artifact['id'])
|
||||
|
||||
# unblock artifact publication
|
||||
changes = [{'op': 'replace', 'path': '/forbid_publish',
|
||||
'value': 'no'}]
|
||||
self.update_with_values(changes, art_type='hooks_artifact',
|
||||
art_id=self.hooks_artifact['id'])
|
||||
|
||||
# now publication works
|
||||
changes = [{'op': 'replace', 'path': '/visibility',
|
||||
'value': 'public'}]
|
||||
art = self.update_with_values(changes, art_type='hooks_artifact',
|
||||
art_id=self.hooks_artifact['id'])
|
||||
self.assertEqual('some_string', art['description'])
|
||||
self.assertEqual('public', art['visibility'])
|
||||
|
||||
def test_deletion_hook(self):
|
||||
# forbid to activate artifact
|
||||
changes = [{'op': 'replace', 'path': '/forbid_delete',
|
||||
'value': 'yes'}]
|
||||
self.update_with_values(changes, art_type='hooks_artifact',
|
||||
art_id=self.hooks_artifact['id'])
|
||||
actions = ['update', 'activate', 'deactivate', 'reactivate', 'publish']
|
||||
for action in actions:
|
||||
with open(art['temp_file_path_%s' % action]) as f:
|
||||
self.assertEqual('pre_%s_hook was called\n' % action,
|
||||
f.readline())
|
||||
self.assertEqual('post_%s_hook was called\n' % action,
|
||||
f.readline())
|
||||
|
||||
# deletion fails now
|
||||
self.assertRaises(
|
||||
exc.BadRequest, self.controller.delete, self.req,
|
||||
'hooks_artifact', self.hooks_artifact['id'])
|
||||
def test_upload_download_hooks(self):
|
||||
temp_file_path = tempfile.mktemp(dir=self.test_dir)
|
||||
self.config(temp_file_path=temp_file_path,
|
||||
group='artifact_type:hooks_artifact')
|
||||
|
||||
# unblock artifact deletion
|
||||
changes = [{'op': 'replace', 'path': '/forbid_delete',
|
||||
'value': 'no'}]
|
||||
self.update_with_values(changes, art_type='hooks_artifact',
|
||||
art_id=self.hooks_artifact['id'])
|
||||
values = {'name': 'ttt', 'version': '1.0', 'temp_dir': self.test_dir}
|
||||
art = self.controller.create(self.req, 'hooks_artifact', values)
|
||||
|
||||
# now deletion works
|
||||
self.controller.delete(self.req, 'hooks_artifact',
|
||||
self.hooks_artifact['id'])
|
||||
self.assertRaises(exc.NotFound, self.controller.show, self.req,
|
||||
'hooks_artifact', self.hooks_artifact['id'])
|
||||
art = self.controller.upload_blob(
|
||||
self.req, 'hooks_artifact', art['id'], 'blob',
|
||||
BytesIO(b'aaa'), 'application/octet-stream')
|
||||
self.assertEqual(3, art['blob']['size'])
|
||||
self.assertEqual('active', art['blob']['status'])
|
||||
|
||||
self.controller.download_blob(
|
||||
self.req, 'hooks_artifact', art['id'], 'blob')
|
||||
|
||||
with open(temp_file_path) as f:
|
||||
self.assertEqual('pre_upload_hook was called\n', f.readline())
|
||||
self.assertEqual('post_upload_hook was called\n', f.readline())
|
||||
self.assertEqual('pre_download_hook was called\n', f.readline())
|
||||
self.assertEqual('post_download_hook was called\n', f.readline())
|
||||
|
||||
def test_add_location_hook(self):
|
||||
|
||||
temp_file_path = tempfile.mktemp(dir=self.test_dir)
|
||||
self.config(temp_file_path=temp_file_path,
|
||||
group='artifact_type:hooks_artifact')
|
||||
|
||||
values = {'name': 'ttt', 'version': '1.0', 'temp_dir': self.test_dir}
|
||||
art = self.controller.create(self.req, 'hooks_artifact', values)
|
||||
|
||||
ct = 'application/vnd+openstack.glare-custom-location+json'
|
||||
|
||||
body = {'url': 'https://FAKE_LOCATION.com',
|
||||
'md5': "fake", 'sha1': "fake_sha", "sha256": "fake_sha256"}
|
||||
art = self.controller.upload_blob(
|
||||
self.req, 'hooks_artifact', art['id'], 'blob', body, ct)
|
||||
self.assertIsNone(art['blob']['size'])
|
||||
self.assertEqual('active', art['blob']['status'])
|
||||
|
||||
# hook isn't called if we download external location
|
||||
self.controller.download_blob(
|
||||
self.req, 'hooks_artifact', art['id'], 'blob')
|
||||
|
||||
with open(temp_file_path) as f:
|
||||
self.assertEqual(
|
||||
'pre_add_location_hook was called\n', f.readline())
|
||||
self.assertEqual(
|
||||
'post_add_location_hook was called\n', f.readline())
|
||||
|
||||
def test_delete_hook(self):
|
||||
temp_file_path = tempfile.mktemp(dir=self.test_dir)
|
||||
self.config(temp_file_path=temp_file_path,
|
||||
group='artifact_type:hooks_artifact')
|
||||
|
||||
values = {'name': 'ttt', 'version': '1.0', 'temp_dir': self.test_dir}
|
||||
art = self.controller.create(self.req, 'hooks_artifact', values)
|
||||
|
||||
self.controller.delete(self.req, 'hooks_artifact', art['id'])
|
||||
|
||||
with open(temp_file_path) as f:
|
||||
self.assertEqual('pre_delete_hook was called\n', f.readline())
|
||||
self.assertEqual('post_delete_hook was called\n', f.readline())
|
||||
|
|
Loading…
Reference in New Issue