Merge "Add support for module ordering on apply"

This commit is contained in:
Jenkins 2017-01-06 11:54:22 +00:00 committed by Gerrit Code Review
commit 12ba0647fb
18 changed files with 878 additions and 242 deletions

View File

@ -0,0 +1,9 @@
---
features:
- Modules can now be applied in a consistent order,
based on the new 'priority_apply' and 'apply_order'
attributes when creating them.
Blueprint module-management-ordering
upgrade:
- For module ordering to work, db_upgrade must be run
on the Trove database.

View File

@ -717,6 +717,18 @@
"No value for argument 'dml' in method call",
"upgrade"
],
[
"trove/db/sqlalchemy/migrate_repo/versions/040_module_priority.py",
"E1101",
"Instance of 'Table' has no 'create_column' member",
"upgrade"
],
[
"trove/db/sqlalchemy/migrate_repo/versions/040_module_priority.py",
"no-member",
"Instance of 'Table' has no 'create_column' member",
"upgrade"
],
[
"trove/db/sqlalchemy/migration.py",
"E0611",
@ -1487,4 +1499,4 @@
"--rcfile=./pylintrc",
"-E"
]
}
}

View File

@ -567,10 +567,16 @@ guest_log = {
module_contents = {
"type": "string",
"minLength": 1,
"maxLength": 16777215,
"maxLength": 4294967295,
"pattern": "^.*.+.*$"
}
module_apply_order = {
"type": "integer",
"minimum": 0,
"maximum": 9,
}
module = {
"create": {
"name": "module:create",
@ -597,6 +603,9 @@ module = {
"all_tenants": boolean_string,
"visible": boolean_string,
"live_update": boolean_string,
"priority_apply": boolean_string,
"apply_order": module_apply_order,
"full_access": boolean_string,
}
}
}
@ -629,6 +638,9 @@ module = {
"all_datastore_versions": boolean_string,
"visible": boolean_string,
"live_update": boolean_string,
"priority_apply": boolean_string,
"apply_order": module_apply_order,
"full_access": boolean_string,
}
}
}

View File

@ -0,0 +1,48 @@
# Copyright 2016 Tesora, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
from sqlalchemy.schema import Column
from sqlalchemy.schema import MetaData
from sqlalchemy.sql.expression import update
from trove.db.sqlalchemy.migrate_repo.schema import Boolean
from trove.db.sqlalchemy.migrate_repo.schema import Integer
from trove.db.sqlalchemy.migrate_repo.schema import Table
from trove.db.sqlalchemy.migrate_repo.schema import Text
COLUMN_NAME_1 = 'priority_apply'
COLUMN_NAME_2 = 'apply_order'
COLUMN_NAME_3 = 'is_admin'
def upgrade(migrate_engine):
meta = MetaData()
meta.bind = migrate_engine
modules = Table('modules', meta, autoload=True)
is_nullable = True if migrate_engine.name == "sqlite" else False
column = Column(COLUMN_NAME_1, Boolean(), nullable=is_nullable, default=0)
modules.create_column(column)
column = Column(COLUMN_NAME_2, Integer(), nullable=is_nullable, default=5)
modules.create_column(column)
column = Column(COLUMN_NAME_3, Boolean(), nullable=is_nullable, default=0)
modules.create_column(column)
modules.c.contents.alter(Text(length=4294967295))
# mark all non-visible, auto-apply and all-tenant modules as is_admin
update(table=modules,
values=dict(is_admin=1),
whereclause="visible=0 or auto_apply=1 or tenant_id is null"
).execute()

View File

@ -15,6 +15,7 @@
#
import abc
import operator
from oslo_config import cfg as oslo_cfg
from oslo_log import log as logging
@ -62,6 +63,8 @@ class Manager(periodic_task.PeriodicTasks):
GUEST_LOG_DEFS_ERROR_LABEL = 'error'
GUEST_LOG_DEFS_SLOW_QUERY_LABEL = 'slow_query'
MODULE_APPLY_TO_ALL = module_manager.ModuleManager.MODULE_APPLY_TO_ALL
def __init__(self, manager_name):
super(Manager, self).__init__(CONF)
@ -644,18 +647,36 @@ class Manager(periodic_task.PeriodicTasks):
def module_apply(self, context, modules=None):
LOG.info(_("Applying modules."))
results = []
for module_data in modules:
module = module_data['module']
modules = [data['module'] for data in modules]
try:
# make sure the modules are applied in the correct order
modules.sort(key=operator.itemgetter('apply_order'))
modules.sort(key=operator.itemgetter('priority_apply'),
reverse=True)
except KeyError:
# If we don't have ordering info then maybe we're running
# a version of the module feature before ordering was
# introduced. In that case, since we don't have any
# way to order the modules we should just continue.
pass
for module in modules:
id = module.get('id', None)
module_type = module.get('type', None)
name = module.get('name', None)
tenant = module.get('tenant', None)
datastore = module.get('datastore', None)
ds_version = module.get('datastore_version', None)
tenant = module.get('tenant', self.MODULE_APPLY_TO_ALL)
datastore = module.get('datastore', self.MODULE_APPLY_TO_ALL)
ds_version = module.get('datastore_version',
self.MODULE_APPLY_TO_ALL)
contents = module.get('contents', None)
md5 = module.get('md5', None)
auto_apply = module.get('auto_apply', True)
visible = module.get('visible', True)
is_admin = module.get('is_admin', None)
if is_admin is None:
# fall back to the old method of checking for an admin option
is_admin = (tenant == self.MODULE_APPLY_TO_ALL or
not visible or
auto_apply)
if not name:
raise AttributeError(_("Module name not specified"))
if not contents:
@ -665,9 +686,14 @@ class Manager(periodic_task.PeriodicTasks):
raise exception.ModuleTypeNotFound(
_("No driver implemented for module type '%s'") %
module_type)
if (datastore and datastore != self.MODULE_APPLY_TO_ALL and
datastore != CONF.datastore_manager):
reason = (_("Module not valid for datastore %s") %
CONF.datastore_manager)
raise exception.ModuleInvalid(reason=reason)
result = module_manager.ModuleManager.apply_module(
driver, module_type, name, tenant, datastore, ds_version,
contents, id, md5, auto_apply, visible)
contents, id, md5, auto_apply, visible, is_admin)
results.append(result)
LOG.info(_("Returning list of modules: %s") % results)
return results

View File

@ -15,6 +15,7 @@
#
import datetime
import operator
import os
from oslo_log import log as logging
@ -41,12 +42,12 @@ class ModuleManager(object):
@classmethod
def get_current_timestamp(cls):
return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[0:22]
@classmethod
def apply_module(cls, driver, module_type, name, tenant,
datastore, ds_version, contents, module_id, md5,
auto_apply, visible):
auto_apply, visible, admin_module):
tenant = tenant or cls.MODULE_APPLY_TO_ALL
datastore = datastore or cls.MODULE_APPLY_TO_ALL
ds_version = ds_version or cls.MODULE_APPLY_TO_ALL
@ -57,9 +58,9 @@ class ModuleManager(object):
now = cls.get_current_timestamp()
default_result = cls.build_default_result(
module_type, name, tenant, datastore,
ds_version, module_id, md5, auto_apply, visible, now)
ds_version, module_id, md5,
auto_apply, visible, now, admin_module)
result = cls.read_module_result(module_dir, default_result)
admin_module = cls.is_admin_module(tenant, auto_apply, visible)
try:
driver.configure(name, datastore, ds_version, data_file)
applied, message = driver.apply(
@ -83,7 +84,7 @@ class ModuleManager(object):
result['tenant'] = tenant
result['auto_apply'] = auto_apply
result['visible'] = visible
result['admin_only'] = admin_module
result['is_admin'] = admin_module
cls.write_module_result(module_dir, result)
return result
@ -113,8 +114,7 @@ class ModuleManager(object):
@classmethod
def build_default_result(cls, module_type, name, tenant,
datastore, ds_version, module_id, md5,
auto_apply, visible, now):
admin_module = cls.is_admin_module(tenant, auto_apply, visible)
auto_apply, visible, now, admin_module):
result = {
'type': module_type,
'name': name,
@ -130,7 +130,7 @@ class ModuleManager(object):
'removed': None,
'auto_apply': auto_apply,
'visible': visible,
'admin_only': admin_module,
'is_admin': admin_module,
'contents': None,
}
return result
@ -183,7 +183,9 @@ class ModuleManager(object):
(is_admin or result.get('visible'))):
if include_contents:
codec = stream_codecs.Base64Codec()
if not is_admin and result.get('admin_only'):
# keep admin_only for backwards compatibility
if not is_admin and (result.get('is_admin') or
result.get('admin_only')):
contents = (
"Must be admin to retrieve contents for module %s"
% result.get('name', 'Unknown'))
@ -195,6 +197,7 @@ class ModuleManager(object):
result['contents'] = operating_system.read_file(
contents_file, codec=codec, decode=False)
results.append(result)
results.sort(key=operator.itemgetter('updated'), reverse=True)
return results
@classmethod

View File

@ -564,6 +564,26 @@ def load_server_group_info(instance, context, compute_id):
instance.locality = srv_grp.ServerGroup.get_locality(server_group)
def validate_modules_for_apply(modules, datastore_id, datastore_version_id):
for module in modules:
if (module.datastore_id and
module.datastore_id != datastore_id):
reason = (_("Module '%(mod)s' cannot be applied "
" (Wrong datastore '%(ds)s' - expected '%(ds2)s')")
% {'mod': module.name, 'ds': module.datastore_id,
'ds2': datastore_id})
raise exception.ModuleInvalid(reason=reason)
if (module.datastore_version_id and
module.datastore_version_id != datastore_version_id):
reason = (_("Module '%(mod)s' cannot be applied "
" (Wrong datastore version '%(ver)s' "
"- expected '%(ver2)s')")
% {'mod': module.name,
'ver': module.datastore_version_id,
'ver2': datastore_version_id})
raise exception.ModuleInvalid(reason=reason)
class BaseInstance(SimpleInstance):
"""Represents an instance.
-----------
@ -980,13 +1000,8 @@ class Instance(BuiltInstance):
for aa_module in auto_apply_modules:
if aa_module.id not in module_ids:
modules.append(aa_module)
module_list = []
for module in modules:
module.contents = module_models.Module.deprocess_contents(
module.contents)
module_info = module_views.DetailedModuleView(module).data(
include_contents=True)
module_list.append(module_info)
validate_modules_for_apply(modules, datastore.id, datastore_version.id)
module_list = module_views.get_module_list(modules)
def _create_resources():

View File

@ -536,13 +536,9 @@ class InstanceController(wsgi.Controller):
self.authorize_instance_action(context, 'module_apply', instance)
module_ids = [mod['id'] for mod in body.get('modules', [])]
modules = module_models.Modules.load_by_ids(context, module_ids)
module_list = []
for module in modules:
module.contents = module_models.Module.deprocess_contents(
module.contents)
module_info = module_views.DetailedModuleView(module).data(
include_contents=True)
module_list.append(module_info)
models.validate_modules_for_apply(
modules, instance.datastore.id, instance.datastore_version.id)
module_list = module_views.get_module_list(modules)
client = create_guest_client(context, id)
result_list = client.module_apply(module_list)
models.Instance.add_instance_modules(context, id, modules)

View File

@ -137,12 +137,14 @@ class Module(object):
@staticmethod
def create(context, name, module_type, contents,
description, tenant_id, datastore,
datastore_version, auto_apply, visible, live_update):
datastore_version, auto_apply, visible, live_update,
priority_apply, apply_order, full_access):
if module_type.lower() not in Modules.VALID_MODULE_TYPES:
LOG.error(_("Valid module types: %s") % Modules.VALID_MODULE_TYPES)
raise exception.ModuleTypeNotFound(module_type=module_type)
Module.validate_action(
context, 'create', tenant_id, auto_apply, visible)
context, 'create', tenant_id, auto_apply, visible, priority_apply,
full_access)
datastore_id, datastore_version_id = Module.validate_datastore(
datastore, datastore_version)
if Module.key_exists(
@ -153,6 +155,9 @@ class Module(object):
raise exception.ModuleAlreadyExists(
name=name, datastore=datastore_str, ds_version=ds_version_str)
md5, processed_contents = Module.process_contents(contents)
is_admin = context.is_admin
if full_access:
is_admin = 0
module = DBModule.create(
name=name,
type=module_type.lower(),
@ -164,37 +169,53 @@ class Module(object):
auto_apply=auto_apply,
visible=visible,
live_update=live_update,
priority_apply=priority_apply,
apply_order=apply_order,
is_admin=is_admin,
md5=md5)
return module
# Certain fields require admin access to create/change/delete
@staticmethod
def validate_action(context, action_str, tenant_id, auto_apply, visible):
error_str = None
if not context.is_admin:
option_strs = []
if tenant_id is None:
option_strs.append(_("Tenant: %s") % Modules.MATCH_ALL_NAME)
if auto_apply:
option_strs.append(_("Auto: %s") % auto_apply)
if not visible:
option_strs.append(_("Visible: %s") % visible)
if option_strs:
error_str = "(" + " ".join(option_strs) + ")"
if error_str:
def validate_action(context, action_str, tenant_id, auto_apply, visible,
priority_apply, full_access):
admin_options_str = None
option_strs = []
if tenant_id is None:
option_strs.append(_("Tenant: %s") % Modules.MATCH_ALL_NAME)
if auto_apply:
option_strs.append(_("Auto: %s") % auto_apply)
if not visible:
option_strs.append(_("Visible: %s") % visible)
if priority_apply:
option_strs.append(_("Priority: %s") % priority_apply)
if full_access is not None:
if full_access and option_strs:
admin_options_str = "(" + ", ".join(option_strs) + ")"
raise exception.InvalidModelError(
errors=_('Cannot make module full access: %s') %
admin_options_str)
option_strs.append(_("Full Access: %s") % full_access)
if option_strs:
admin_options_str = "(" + ", ".join(option_strs) + ")"
if not context.is_admin and admin_options_str:
raise exception.ModuleAccessForbidden(
action=action_str, options=error_str)
action=action_str, options=admin_options_str)
return admin_options_str
@staticmethod
def validate_datastore(datastore, datastore_version):
datastore_id = None
datastore_version_id = None
if datastore:
ds, ds_ver = datastore_models.get_datastore_version(
type=datastore, version=datastore_version)
datastore_id = ds.id
if datastore_version:
ds, ds_ver = datastore_models.get_datastore_version(
type=datastore, version=datastore_version)
datastore_id = ds.id
datastore_version_id = ds_ver.id
else:
ds = datastore_models.Datastore.load(datastore)
datastore_id = ds.id
elif datastore_version:
msg = _("Cannot specify version without datastore")
raise exception.BadRequest(message=msg)
@ -237,7 +258,8 @@ class Module(object):
def delete(context, module):
Module.validate_action(
context, 'delete',
module.tenant_id, module.auto_apply, module.visible)
module.tenant_id, module.auto_apply, module.visible,
module.priority_apply, None)
Module.enforce_live_update(module.id, module.live_update, module.md5)
module.deleted = True
module.deleted_at = datetime.utcnow()
@ -282,28 +304,33 @@ class Module(object):
return module
@staticmethod
def update(context, module, original_module):
def update(context, module, original_module, full_access):
Module.enforce_live_update(
original_module.id, original_module.live_update,
original_module.md5)
# we don't allow any changes to 'admin'-type modules, even if
# the values changed aren't the admin ones.
access_tenant_id = (None if (original_module.tenant_id is None or
module.tenant_id is None)
else module.tenant_id)
access_auto_apply = original_module.auto_apply or module.auto_apply
access_visible = original_module.visible and module.visible
Module.validate_action(
context, 'update',
access_tenant_id, access_auto_apply, access_visible)
# we don't allow any changes to 'is_admin' modules by non-admin
if original_module.is_admin and not context.is_admin:
raise exception.ModuleAccessForbidden(
action='update', options='(Module is an admin module)')
# we don't allow any changes to admin-only attributes by non-admin
admin_options = Module.validate_action(
context, 'update', module.tenant_id, module.auto_apply,
module.visible, module.priority_apply, full_access)
# make sure we set the is_admin flag, but only if it was
# originally is_admin or we changed an admin option
module.is_admin = original_module.is_admin or (
1 if admin_options else 0)
# but we turn it on/off if full_access is specified
if full_access is not None:
module.is_admin = 0 if full_access else 1
ds_id, ds_ver_id = Module.validate_datastore(
module.datastore_id, module.datastore_version_id)
if module.contents != original_module.contents:
md5, processed_contents = Module.process_contents(module.contents)
module.md5 = md5
module.contents = processed_contents
else:
# on load the contents were decrypted, so
elif hasattr(original_module, 'encrypted_contents'):
# on load the contents may have been decrypted, so
# we need to put the encrypted contents back before we update
module.contents = original_module.encrypted_contents
if module.datastore_id:
@ -415,6 +442,7 @@ class DBModule(models.DatabaseModelBase):
'id', 'name', 'type', 'contents', 'description',
'tenant_id', 'datastore_id', 'datastore_version_id',
'auto_apply', 'visible', 'live_update',
'priority_apply', 'apply_order', 'is_admin',
'md5', 'created', 'updated', 'deleted', 'deleted_at']

View File

@ -91,11 +91,15 @@ class ModuleController(wsgi.Controller):
auto_apply = body['module'].get('auto_apply', 0)
visible = body['module'].get('visible', 1)
live_update = body['module'].get('live_update', 0)
priority_apply = body['module'].get('priority_apply', 0)
apply_order = body['module'].get('apply_order', 5)
full_access = body['module'].get('full_access', None)
module = models.Module.create(
context, name, module_type, contents,
description, module_tenant_id, datastore, ds_version,
auto_apply, visible, live_update)
auto_apply, visible, live_update, priority_apply,
apply_order, full_access)
view_data = views.DetailedModuleView(module)
return wsgi.Result(view_data.data(), 200)
@ -154,8 +158,15 @@ class ModuleController(wsgi.Controller):
module.visible = body['module']['visible']
if 'live_update' in body['module']:
module.live_update = body['module']['live_update']
if 'priority_apply' in body['module']:
module.priority_apply = body['module']['priority_apply']
if 'apply_order' in body['module']:
module.apply_order = body['module']['apply_order']
full_access = None
if 'full_access' in body['module']:
full_access = body['module']['full_access']
models.Module.update(context, module, original_module)
models.Module.update(context, module, original_module, full_access)
view_data = views.DetailedModuleView(module)
return wsgi.Result(view_data.data(), 200)

View File

@ -33,6 +33,9 @@ class ModuleView(object):
datastore_id=self.module.datastore_id,
datastore_version_id=self.module.datastore_version_id,
auto_apply=self.module.auto_apply,
priority_apply=self.module.priority_apply,
apply_order=self.module.apply_order,
is_admin=self.module.is_admin,
md5=self.module.md5,
visible=self.module.visible,
created=self.module.created,
@ -48,13 +51,15 @@ class ModuleView(object):
datastore = self.module.datastore_id
datastore_version = self.module.datastore_version_id
if datastore:
ds, ds_ver = (
datastore_models.get_datastore_version(
type=datastore, version=datastore_version))
datastore = ds.name
if datastore_version:
ds, ds_ver = (
datastore_models.get_datastore_version(
type=datastore, version=datastore_version))
datastore = ds.name
datastore_version = ds_ver.name
else:
ds = datastore_models.Datastore.load(datastore)
datastore = ds.name
datastore_version = models.Modules.MATCH_ALL_NAME
else:
datastore = models.Modules.MATCH_ALL_NAME
@ -95,5 +100,18 @@ class DetailedModuleView(ModuleView):
if hasattr(self.module, 'instance_count'):
module_dict["instance_count"] = self.module.instance_count
if include_contents:
if not hasattr(self.module, 'encrypted_contents'):
self.module.encrypted_contents = self.module.contents
self.module.contents = models.Module.deprocess_contents(
self.module.contents)
module_dict['contents'] = self.module.contents
return {"module": module_dict}
def get_module_list(modules):
module_list = []
for module in modules:
module_info = DetailedModuleView(module).data(
include_contents=True)
module_list.append(module_info)
return module_list

View File

@ -63,6 +63,21 @@ class ModuleCreateGroup(TestGroup):
"""Ensure create hidden module for non-admin fails."""
self.test_runner.run_module_create_non_admin_hidden()
@test
def module_create_non_admin_priority(self):
"""Ensure create priority module for non-admin fails."""
self.test_runner.run_module_create_non_admin_priority()
@test
def module_create_non_admin_no_full_access(self):
"""Ensure create no full access module for non-admin fails."""
self.test_runner.run_module_create_non_admin_no_full_access()
@test
def module_create_full_access_with_admin_opt(self):
"""Ensure create full access module with admin opts fails."""
self.test_runner.run_module_create_full_access_with_admin_opt()
@test
def module_create_bad_datastore(self):
"""Ensure create module with invalid datastore fails."""
@ -154,12 +169,24 @@ class ModuleCreateGroup(TestGroup):
@test(depends_on=[module_create, module_create_bin, module_create_bin2],
runs_after=[module_create_admin_live_update])
def module_create_admin_priority_apply(self):
"""Check that create module works with priority-apply option."""
self.test_runner.run_module_create_admin_priority_apply()
@test(depends_on=[module_create, module_create_bin, module_create_bin2],
runs_after=[module_create_admin_priority_apply])
def module_create_datastore(self):
"""Check that create module with datastore works."""
self.test_runner.run_module_create_datastore()
@test(depends_on=[module_create, module_create_bin, module_create_bin2],
runs_after=[module_create_datastore])
def module_create_different_datastore(self):
"""Check that create module with different datastore works."""
self.test_runner.run_module_create_different_datastore()
@test(depends_on=[module_create, module_create_bin, module_create_bin2],
runs_after=[module_create_different_datastore])
def module_create_ds_version(self):
"""Check that create module with ds version works."""
self.test_runner.run_module_create_ds_version()
@ -176,8 +203,20 @@ class ModuleCreateGroup(TestGroup):
"""Check that create with same name on different tenant works."""
self.test_runner.run_module_create_different_tenant()
@test(depends_on=[module_create_all_tenant],
@test(depends_on=[module_create, module_create_bin, module_create_bin2],
runs_after=[module_create_different_tenant])
def module_create_full_access(self):
"""Check that create by admin with full access works."""
self.test_runner.run_module_create_full_access()
@test(depends_on=[module_create_all_tenant],
runs_after=[module_create_full_access])
def module_full_access_toggle(self):
"""Check that toggling full access works."""
self.test_runner.run_module_full_access_toggle()
@test(depends_on=[module_create_all_tenant],
runs_after=[module_full_access_toggle])
def module_list_again(self):
"""Check that list modules skips invisible modules."""
self.test_runner.run_module_list_again()
@ -236,60 +275,66 @@ class ModuleCreateGroup(TestGroup):
@test(depends_on=[module_update],
runs_after=[module_update_invisible_toggle])
def module_update_priority_toggle(self):
"""Check that update module works for priority toggle."""
self.test_runner.run_module_update_priority_toggle()
@test(depends_on=[module_update],
runs_after=[module_update_priority_toggle])
def module_update_unauth(self):
"""Ensure update module for unauth user fails."""
self.test_runner.run_module_update_unauth()
@test(depends_on=[module_update],
runs_after=[module_update_invisible_toggle])
runs_after=[module_update_priority_toggle])
def module_update_non_admin_auto(self):
"""Ensure update module to auto_apply for non-admin fails."""
self.test_runner.run_module_update_non_admin_auto()
@test(depends_on=[module_update],
runs_after=[module_update_invisible_toggle])
runs_after=[module_update_priority_toggle])
def module_update_non_admin_auto_off(self):
"""Ensure update module to auto_apply off for non-admin fails."""
self.test_runner.run_module_update_non_admin_auto_off()
@test(depends_on=[module_update],
runs_after=[module_update_invisible_toggle])
runs_after=[module_update_priority_toggle])
def module_update_non_admin_auto_any(self):
"""Ensure any update module to auto_apply for non-admin fails."""
self.test_runner.run_module_update_non_admin_auto_any()
@test(depends_on=[module_update],
runs_after=[module_update_invisible_toggle])
runs_after=[module_update_priority_toggle])
def module_update_non_admin_all_tenant(self):
"""Ensure update module to all tenant for non-admin fails."""
self.test_runner.run_module_update_non_admin_all_tenant()
@test(depends_on=[module_update],
runs_after=[module_update_invisible_toggle])
runs_after=[module_update_priority_toggle])
def module_update_non_admin_all_tenant_off(self):
"""Ensure update module to all tenant off for non-admin fails."""
self.test_runner.run_module_update_non_admin_all_tenant_off()
@test(depends_on=[module_update],
runs_after=[module_update_invisible_toggle])
runs_after=[module_update_priority_toggle])
def module_update_non_admin_all_tenant_any(self):
"""Ensure any update module to all tenant for non-admin fails."""
self.test_runner.run_module_update_non_admin_all_tenant_any()
@test(depends_on=[module_update],
runs_after=[module_update_invisible_toggle])
runs_after=[module_update_priority_toggle])
def module_update_non_admin_invisible(self):
"""Ensure update module to invisible for non-admin fails."""
self.test_runner.run_module_update_non_admin_invisible()
@test(depends_on=[module_update],
runs_after=[module_update_invisible_toggle])
runs_after=[module_update_priority_toggle])
def module_update_non_admin_invisible_off(self):
"""Ensure update module to invisible off for non-admin fails."""
self.test_runner.run_module_update_non_admin_invisible_off()
@test(depends_on=[module_update],
runs_after=[module_update_invisible_toggle])
runs_after=[module_update_priority_toggle])
def module_update_non_admin_invisible_any(self):
"""Ensure any update module to invisible for non-admin fails."""
self.test_runner.run_module_update_non_admin_invisible_any()
@ -325,6 +370,11 @@ class ModuleInstCreateGroup(TestGroup):
"""Check that module-apply works."""
self.test_runner.run_module_apply()
@test(runs_after=[module_query_empty])
def module_apply_wrong_module(self):
"""Ensure that module-apply for wrong module fails."""
self.test_runner.run_module_apply_wrong_module()
@test(depends_on=[module_apply])
def module_list_instance_after_apply(self):
"""Check that the instance has one module associated."""
@ -356,6 +406,11 @@ class ModuleInstCreateGroup(TestGroup):
"""Check that creating an instance with modules works."""
self.test_runner.run_create_inst_with_mods()
@test(runs_after=[module_query_empty])
def create_inst_with_wrong_module(self):
"""Ensure that creating an inst with wrong ds mod fails."""
self.test_runner.run_create_inst_with_wrong_module()
@test(depends_on=[module_apply])
def module_delete_applied(self):
"""Ensure that deleting an applied module fails."""

View File

@ -42,6 +42,28 @@ class ModuleRunner(TestRunner):
self.MODULE_BINARY_CONTENTS = Crypto.Random.new().read(20)
self.MODULE_BINARY_CONTENTS2 = '\x00\xFF\xea\x9c\x11\xfeok\xb1\x8ax'
self.module_name_order = [
{'suffix': self.MODULE_BINARY_SUFFIX,
'priority': True, 'order': 1},
{'suffix': self.MODULE_BINARY_SUFFIX2,
'priority': True, 'order': 2},
{'suffix': '_hidden_all_tenant_auto_priority',
'priority': True, 'order': 3},
{'suffix': '_hidden', 'priority': True, 'order': 4},
{'suffix': '_auto', 'priority': True, 'order': 5},
{'suffix': '_live', 'priority': True, 'order': 6},
{'suffix': '_priority', 'priority': True, 'order': 7},
{'suffix': '_ds', 'priority': False, 'order': 1},
{'suffix': '_ds_ver', 'priority': False, 'order': 2},
{'suffix': '_all_tenant_ds_ver', 'priority': False, 'order': 3},
{'suffix': '', 'priority': False, 'order': 4},
{'suffix': '_ds_diff', 'priority': False, 'order': 5},
{'suffix': '_diff_tenant', 'priority': False, 'order': 6},
{'suffix': '_full_access', 'priority': False, 'order': 7},
{'suffix': '_for_update', 'priority': False, 'order': 8},
{'suffix': '_updated', 'priority': False, 'order': 8},
]
self.mod_inst_id = None
self.temp_module = None
self._module_type = None
@ -82,12 +104,19 @@ class ModuleRunner(TestRunner):
def update_test_module(self):
return self._get_test_module(1)
def build_module_args(self, extra=None):
extra = extra or ''
name = self.MODULE_NAME + extra
desc = self.MODULE_DESC + extra.replace('_', ' ')
cont = self.get_module_contents(name)
return name, desc, cont
def build_module_args(self, name_order=None):
suffix = "_unknown"
priority = False
order = 5
if name_order is not None:
name_rec = self.module_name_order[name_order]
suffix = name_rec['suffix']
priority = name_rec['priority']
order = name_rec['order']
name = self.MODULE_NAME + suffix
description = self.MODULE_DESC + suffix.replace('_', ' ')
contents = self.get_module_contents(name)
return name, description, contents, priority, order
def get_module_contents(self, name=None):
message = self.get_module_message(name=name)
@ -102,7 +131,8 @@ class ModuleRunner(TestRunner):
return not mod.visible and mod.tenant_id and not mod.auto_apply
return self._find_module(_match, "Could not find invisible module")
def _find_module(self, match_fn, not_found_message, find_all=False):
def _find_module(self, match_fn, not_found_message, find_all=False,
fail_on_not_found=True):
found = [] if find_all else None
for test_module in self.test_modules:
if match_fn(test_module):
@ -112,7 +142,10 @@ class ModuleRunner(TestRunner):
found = test_module
break
if not found:
self.fail(not_found_message)
if fail_on_not_found:
self.fail(not_found_message)
else:
SkipTest(not_found_message)
return found
def _find_auto_apply_module(self):
@ -125,6 +158,21 @@ class ModuleRunner(TestRunner):
return mod.tenant_id is None and mod.visible
return self._find_module(_match, "Could not find all tenant module")
def _find_priority_apply_module(self):
def _match(mod):
return mod.priority_apply and mod.tenant_id and mod.visible
return self._find_module(_match,
"Could not find priority-apply module")
def _find_diff_datastore_module(self):
def _match(mod):
return (mod.datastore and
mod.datastore != models.Modules.MATCH_ALL_NAME and
mod.datastore != self.instance_info.dbaas_datastore)
return self._find_module(_match,
"Could not find different datastore module",
fail_on_not_found=False)
def _find_all_auto_apply_modules(self, visible=None):
def _match(mod):
return mod.auto_apply and (
@ -132,6 +180,12 @@ class ModuleRunner(TestRunner):
return self._find_module(
_match, "Could not find all auto apply modules", find_all=True)
def _find_module_by_id(self, module_id):
def _match(mod):
return mod.id == module_id
return self._find_module(_match, "Could not find module with id %s" %
module_id)
# Tests start here
def run_module_delete_existing(self):
modules = self.admin_client.modules.list()
@ -178,6 +232,36 @@ class ModuleRunner(TestRunner):
self.MODULE_NAME, self.module_type, self.MODULE_NEG_CONTENTS,
visible=False)
def run_module_create_non_admin_priority(
self, expected_exception=exceptions.Forbidden,
expected_http_code=403):
client = self.auth_client
self.assert_raises(
expected_exception, expected_http_code,
client, client.modules.create,
self.MODULE_NAME, self.module_type, self.MODULE_NEG_CONTENTS,
priority_apply=True)
def run_module_create_non_admin_no_full_access(
self, expected_exception=exceptions.Forbidden,
expected_http_code=403):
client = self.auth_client
self.assert_raises(
expected_exception, expected_http_code,
client, client.modules.create,
self.MODULE_NAME, self.module_type, self.MODULE_NEG_CONTENTS,
full_access=False)
def run_module_create_full_access_with_admin_opt(
self, expected_exception=exceptions.BadRequest,
expected_http_code=400):
client = self.admin_client
self.assert_raises(
expected_exception, expected_http_code,
client, client.modules.create,
self.MODULE_NAME, self.module_type, self.MODULE_NEG_CONTENTS,
full_access=True, auto_apply=True)
def run_module_create_bad_datastore(
self, expected_exception=exceptions.NotFound,
expected_http_code=404):
@ -228,33 +312,45 @@ class ModuleRunner(TestRunner):
self.admin_client.modules.list())
self.module_other_count_prior_to_create = len(
self.unauth_client.modules.list())
name, description, contents = self.build_module_args()
self.assert_module_create(
self.auth_client,
name=name,
module_type=self.module_type,
contents=contents,
description=description)
self.assert_module_create(self.auth_client, 10)
def assert_module_create(self, client, name=None, module_type=None,
def assert_module_create(self, client, name_order,
name=None, module_type=None,
contents=None, description=None,
all_tenants=False,
datastore=None, datastore_version=None,
auto_apply=False,
live_update=False, visible=True):
live_update=False, visible=True,
priority_apply=None,
apply_order=None,
full_access=None):
(temp_name, temp_description, temp_contents,
temp_priority, temp_order) = self.build_module_args(name_order)
name = name if name is not None else temp_name
description = (
description if description is not None else temp_description)
contents = contents if contents is not None else temp_contents
priority_apply = (
priority_apply if priority_apply is not None else temp_priority)
apply_order = apply_order if apply_order is not None else temp_order
module_type = module_type or self.module_type
result = client.modules.create(
name, module_type, contents,
description=description,
all_tenants=all_tenants,
datastore=datastore, datastore_version=datastore_version,
auto_apply=auto_apply,
live_update=live_update, visible=visible)
live_update=live_update, visible=visible,
priority_apply=priority_apply,
apply_order=apply_order,
full_access=full_access)
username = client.real_client.client.username
if (('alt' in username and 'admin' not in username) or
('admin' in username and visible)):
self.module_create_count += 1
if datastore:
self.module_ds_create_count += 1
if datastore == self.instance_info.dbaas_datastore:
self.module_ds_create_count += 1
else:
self.module_ds_all_create_count += 1
elif not visible:
@ -286,7 +382,8 @@ class ModuleRunner(TestRunner):
expected_datastore=datastore,
expected_datastore_version=datastore_version,
expected_auto_apply=auto_apply,
expected_contents=contents)
expected_contents=contents,
expected_is_admin=('admin' in username and not full_access))
def validate_module(self, module, validate_all=False,
expected_name=None,
@ -304,7 +401,11 @@ class ModuleRunner(TestRunner):
expected_auto_apply=None,
expected_live_update=None,
expected_visible=None,
expected_contents=None):
expected_contents=None,
expected_priority_apply=None,
expected_apply_order=None,
expected_is_admin=None,
expected_full_access=None):
if expected_all_tenants:
expected_tenant = expected_tenant or models.Modules.MATCH_ALL_NAME
@ -339,6 +440,18 @@ class ModuleRunner(TestRunner):
if expected_auto_apply is not None:
self.assert_equal(expected_auto_apply, module.auto_apply,
'Unexpected auto_apply')
if expected_priority_apply is not None:
self.assert_equal(expected_priority_apply, module.priority_apply,
'Unexpected priority_apply')
if expected_apply_order is not None:
self.assert_equal(expected_apply_order, module.apply_order,
'Unexpected apply_order')
if expected_is_admin is not None:
self.assert_equal(expected_is_admin, module.is_admin,
'Unexpected is_admin')
if expected_full_access is not None:
self.assert_equal(expected_full_access, not module.is_admin,
'Unexpected full_access')
if validate_all:
if expected_datastore_id:
self.assert_equal(expected_datastore_id, module.datastore_id,
@ -355,13 +468,7 @@ class ModuleRunner(TestRunner):
'Unexpected visible')
def run_module_create_for_update(self):
name, description, contents = self.build_module_args('_for_update')
self.assert_module_create(
self.auth_client,
name=name,
module_type=self.module_type,
contents=contents,
description=description)
self.assert_module_create(self.auth_client, 14)
def run_module_create_dupe(
self, expected_exception=exceptions.BadRequest,
@ -383,28 +490,16 @@ class ModuleRunner(TestRunner):
datastore_version=self.instance_info.dbaas_datastore_version)
def run_module_create_bin(self):
name, description, contents = self.build_module_args(
self.MODULE_BINARY_SUFFIX)
self.assert_module_create(
self.admin_client,
name=name,
module_type=self.module_type,
self.admin_client, 0,
contents=self.MODULE_BINARY_CONTENTS,
description=description,
auto_apply=True,
visible=False)
auto_apply=True, visible=False)
def run_module_create_bin2(self):
name, description, contents = self.build_module_args(
self.MODULE_BINARY_SUFFIX2)
self.assert_module_create(
self.admin_client,
name=name,
module_type=self.module_type,
self.admin_client, 1,
contents=self.MODULE_BINARY_CONTENTS2,
description=description,
auto_apply=True,
visible=False)
auto_apply=True, visible=False)
def run_module_show(self):
test_module = self.main_test_module
@ -419,7 +514,10 @@ class ModuleRunner(TestRunner):
expected_datastore_version=test_module.datastore_version,
expected_auto_apply=test_module.auto_apply,
expected_live_update=False,
expected_visible=True)
expected_visible=True,
expected_priority_apply=test_module.priority_apply,
expected_apply_order=test_module.apply_order,
expected_is_admin=test_module.is_admin)
def run_module_show_unauth_user(
self, expected_exception=exceptions.NotFound,
@ -434,28 +532,29 @@ class ModuleRunner(TestRunner):
self.auth_client,
self.module_count_prior_to_create + self.module_create_count)
def assert_module_list(self, client, expected_count, datastore=None,
skip_validation=False):
def assert_module_list(self, client, expected_count, datastore=None):
if datastore:
module_list = client.modules.list(datastore=datastore)
else:
module_list = client.modules.list()
self.assert_equal(expected_count, len(module_list),
"Wrong number of modules for list")
if not skip_validation:
for module in module_list:
if module.name != self.MODULE_NAME:
continue
test_module = self.main_test_module
for module in module_list:
# only validate the test modules
if module.name.startswith(self.MODULE_NAME):
test_module = self._find_module_by_id(module.id)
self.validate_module(
module, validate_all=False,
module, validate_all=True,
expected_name=test_module.name,
expected_module_type=test_module.type,
expected_description=test_module.description,
expected_tenant=test_module.tenant,
expected_datastore=test_module.datastore,
expected_datastore_version=test_module.datastore_version,
expected_auto_apply=test_module.auto_apply)
expected_auto_apply=test_module.auto_apply,
expected_priority_apply=test_module.priority_apply,
expected_apply_order=test_module.apply_order,
expected_is_admin=test_module.is_admin)
def run_module_list_unauth_user(self):
self.assert_module_list(
@ -465,95 +564,103 @@ class ModuleRunner(TestRunner):
self.module_other_create_count))
def run_module_create_admin_all(self):
name, description, contents = self.build_module_args(
'_hidden_all_tenant_auto')
self.assert_module_create(
self.admin_client,
name=name, module_type=self.module_type, contents=contents,
description=description,
self.admin_client, 2,
all_tenants=True,
visible=False,
auto_apply=True)
def run_module_create_admin_hidden(self):
name, description, contents = self.build_module_args('_hidden')
self.assert_module_create(
self.admin_client,
name=name, module_type=self.module_type, contents=contents,
description=description,
self.admin_client, 3,
visible=False)
def run_module_create_admin_auto(self):
name, description, contents = self.build_module_args('_auto')
self.assert_module_create(
self.admin_client,
name=name, module_type=self.module_type, contents=contents,
description=description,
self.admin_client, 4,
auto_apply=True)
def run_module_create_admin_live_update(self):
name, description, contents = self.build_module_args('_live')
self.assert_module_create(
self.admin_client,
name=name, module_type=self.module_type, contents=contents,
description=description,
self.admin_client, 5,
live_update=True)
def run_module_create_datastore(self):
name, description, contents = self.build_module_args('_ds')
def run_module_create_admin_priority_apply(self):
self.assert_module_create(
self.admin_client,
name=name, module_type=self.module_type, contents=contents,
description=description,
self.admin_client, 6)
def run_module_create_datastore(self):
self.assert_module_create(
self.admin_client, 7,
datastore=self.instance_info.dbaas_datastore)
def run_module_create_ds_version(self):
name, description, contents = self.build_module_args('_ds_ver')
def run_module_create_different_datastore(self):
diff_datastore = self._get_different_datastore()
if not diff_datastore:
raise SkipTest("Could not find a different datastore")
self.assert_module_create(
self.admin_client,
name=name, module_type=self.module_type, contents=contents,
description=description,
self.auth_client, 11,
datastore=diff_datastore)
def _get_different_datastore(self):
different_datastore = None
datastores = self.admin_client.datastores.list()
for datastore in datastores:
self.report.log("Found datastore: %s" % datastore.name)
if datastore.name != self.instance_info.dbaas_datastore:
different_datastore = datastore.name
break
return different_datastore
def run_module_create_ds_version(self):
self.assert_module_create(
self.admin_client, 8,
datastore=self.instance_info.dbaas_datastore,
datastore_version=self.instance_info.dbaas_datastore_version)
def run_module_create_all_tenant(self):
name, description, contents = self.build_module_args(
'_all_tenant_ds_ver')
self.assert_module_create(
self.admin_client,
name=name, module_type=self.module_type, contents=contents,
description=description,
self.admin_client, 9,
all_tenants=True,
datastore=self.instance_info.dbaas_datastore,
datastore_version=self.instance_info.dbaas_datastore_version)
def run_module_create_different_tenant(self):
name, description, contents = self.build_module_args()
self.assert_module_create(
self.unauth_client,
name=name, module_type=self.module_type, contents=contents,
description=description)
self.unauth_client, 12)
def run_module_create_full_access(self):
self.assert_module_create(
self.admin_client, 13,
full_access=True)
def run_module_full_access_toggle(self):
self.assert_module_update(
self.admin_client,
self.main_test_module.id,
full_access=False)
self.assert_module_update(
self.admin_client,
self.main_test_module.id,
full_access=True)
def run_module_list_again(self):
self.assert_module_list(
self.auth_client,
self.module_count_prior_to_create + self.module_create_count,
skip_validation=True)
self.module_count_prior_to_create + self.module_create_count)
def run_module_list_ds(self):
self.assert_module_list(
self.auth_client,
self.module_ds_count_prior_to_create + self.module_ds_create_count,
datastore=self.instance_info.dbaas_datastore,
skip_validation=True)
datastore=self.instance_info.dbaas_datastore)
def run_module_list_ds_all(self):
self.assert_module_list(
self.auth_client,
(self.module_ds_all_count_prior_to_create +
self.module_ds_all_create_count),
datastore=models.Modules.MATCH_ALL_NAME,
skip_validation=True)
datastore=models.Modules.MATCH_ALL_NAME)
def run_module_show_invisible(
self, expected_exception=exceptions.NotFound,
@ -570,8 +677,7 @@ class ModuleRunner(TestRunner):
(self.module_admin_count_prior_to_create +
self.module_create_count +
self.module_admin_create_count +
self.module_other_create_count),
skip_validation=True)
self.module_other_create_count))
def run_module_update(self):
self.assert_module_update(
@ -579,46 +685,6 @@ class ModuleRunner(TestRunner):
self.main_test_module.id,
description=self.MODULE_DESC + " modified")
def run_module_update_same_contents(self):
old_md5 = self.main_test_module.md5
self.assert_module_update(
self.auth_client,
self.main_test_module.id,
contents=self.get_module_contents(self.main_test_module.name))
self.assert_equal(old_md5, self.main_test_module.md5,
"MD5 changed with same contents")
def run_module_update_auto_toggle(self):
module = self._find_auto_apply_module()
toggle_off_args = {'auto_apply': False}
toggle_on_args = {'auto_apply': True}
self.assert_module_toggle(module, toggle_off_args, toggle_on_args)
def assert_module_toggle(self, module, toggle_off_args, toggle_on_args):
# First try to update the module based on the change
# (this should toggle the state and allow non-admin access)
self.assert_module_update(
self.admin_client, module.id, **toggle_off_args)
# Now we can update using the non-admin client
self.assert_module_update(
self.auth_client, module.id, description='Updated by auth')
# Now set it back
self.assert_module_update(
self.admin_client, module.id, description=module.description,
**toggle_on_args)
def run_module_update_all_tenant_toggle(self):
module = self._find_all_tenant_module()
toggle_off_args = {'all_tenants': False}
toggle_on_args = {'all_tenants': True}
self.assert_module_toggle(module, toggle_off_args, toggle_on_args)
def run_module_update_invisible_toggle(self):
module = self._find_invisible_module()
toggle_off_args = {'visible': True}
toggle_on_args = {'visible': False}
self.assert_module_toggle(module, toggle_off_args, toggle_on_args)
def assert_module_update(self, client, module_id, **kwargs):
result = client.modules.update(module_id, **kwargs)
found = False
@ -638,6 +704,75 @@ class ModuleRunner(TestRunner):
expected_args[new_key] = value
self.validate_module(result, **expected_args)
def run_module_update_same_contents(self):
old_md5 = self.main_test_module.md5
self.assert_module_update(
self.auth_client,
self.main_test_module.id,
contents=self.get_module_contents(self.main_test_module.name))
self.assert_equal(old_md5, self.main_test_module.md5,
"MD5 changed with same contents")
def run_module_update_auto_toggle(self,
expected_exception=exceptions.Forbidden,
expected_http_code=403):
module = self._find_auto_apply_module()
toggle_off_args = {'auto_apply': False}
toggle_on_args = {'auto_apply': True}
self.assert_module_toggle(module, toggle_off_args, toggle_on_args,
expected_exception=expected_exception,
expected_http_code=expected_http_code)
def assert_module_toggle(self, module, toggle_off_args, toggle_on_args,
expected_exception, expected_http_code):
# First try to update the module based on the change
# (this should toggle the state but still not allow non-admin access)
client = self.admin_client
self.assert_module_update(client, module.id, **toggle_off_args)
# The non-admin client should fail to update
non_admin_client = self.auth_client
self.assert_raises(
expected_exception, expected_http_code,
non_admin_client, non_admin_client.modules.update, module.id,
description='Updated by non-admin')
# Make sure we can still update with the admin client
self.assert_module_update(
client, module.id, description='Updated by admin')
# Now set it back
self.assert_module_update(
client, module.id, description=module.description,
**toggle_on_args)
def run_module_update_all_tenant_toggle(
self, expected_exception=exceptions.Forbidden,
expected_http_code=403):
module = self._find_all_tenant_module()
toggle_off_args = {'all_tenants': False}
toggle_on_args = {'all_tenants': True}
self.assert_module_toggle(module, toggle_off_args, toggle_on_args,
expected_exception=expected_exception,
expected_http_code=expected_http_code)
def run_module_update_invisible_toggle(
self, expected_exception=exceptions.Forbidden,
expected_http_code=403):
module = self._find_invisible_module()
toggle_off_args = {'visible': True}
toggle_on_args = {'visible': False}
self.assert_module_toggle(module, toggle_off_args, toggle_on_args,
expected_exception=expected_exception,
expected_http_code=expected_http_code)
def run_module_update_priority_toggle(
self, expected_exception=exceptions.Forbidden,
expected_http_code=403):
module = self._find_priority_apply_module()
toggle_off_args = {'priority_apply': False}
toggle_on_args = {'priority_apply': True}
self.assert_module_toggle(module, toggle_off_args, toggle_on_args,
expected_exception=expected_exception,
expected_http_code=expected_http_code)
def run_module_update_unauth(
self, expected_exception=exceptions.NotFound,
expected_http_code=404):
@ -775,32 +910,47 @@ class ModuleRunner(TestRunner):
self.assert_equal(expected_count, count,
"Wrong number of modules from query")
expected_results = expected_results or {}
name_index = len(self.module_name_order)
for modquery in modquery_list:
if modquery.name in expected_results:
self.report.log("Validating module '%s'" % modquery.name)
expected = expected_results[modquery.name]
self.validate_module_info(
self.validate_module_apply_info(
modquery,
expected_status=expected['status'],
expected_message=expected['message'])
# make sure we're in the correct order
found = False
while name_index > 0:
name_index -= 1
name_order_rec = self.module_name_order[name_index]
order_name = self.MODULE_NAME + name_order_rec['suffix']
self.report.log("Next module order '%s'" % order_name)
if order_name == modquery.name:
self.report.log("Match found")
found = True
break
if name_index == 0 and not found:
self.fail("Module '%s' was not found in the correct order"
% modquery.name)
def run_module_apply(self):
self.assert_module_apply(self.auth_client, self.instance_info.id,
self.main_test_module)
def assert_module_apply(self, client, instance_id, module,
expected_is_admin=False,
expected_status=None, expected_message=None,
expected_contents=None,
expected_http_code=200):
module_apply_list = client.instances.module_apply(
instance_id, [module.id])
self.assert_client_code(client, expected_http_code)
admin_only = (not module.visible or module.auto_apply or
not module.tenant_id)
expected_status = expected_status or 'OK'
expected_message = (expected_message or
self.get_module_message(module.name))
for module_apply in module_apply_list:
self.validate_module_info(
self.validate_module_apply_info(
module_apply,
expected_name=module.name,
expected_module_type=module.type,
@ -808,22 +958,22 @@ class ModuleRunner(TestRunner):
expected_datastore_version=module.datastore_version,
expected_auto_apply=module.auto_apply,
expected_visible=module.visible,
expected_admin_only=admin_only,
expected_contents=expected_contents,
expected_status=expected_status,
expected_message=expected_message)
expected_message=expected_message,
expected_is_admin=expected_is_admin)
def validate_module_info(self, module_apply,
expected_name=None,
expected_module_type=None,
expected_datastore=None,
expected_datastore_version=None,
expected_auto_apply=None,
expected_visible=None,
expected_admin_only=None,
expected_contents=None,
expected_message=None,
expected_status=None):
def validate_module_apply_info(self, module_apply,
expected_name=None,
expected_module_type=None,
expected_datastore=None,
expected_datastore_version=None,
expected_auto_apply=None,
expected_visible=None,
expected_contents=None,
expected_message=None,
expected_status=None,
expected_is_admin=None):
prefix = "Module: %s -" % expected_name
if expected_name:
@ -845,9 +995,6 @@ class ModuleRunner(TestRunner):
if expected_visible is not None:
self.assert_equal(expected_visible, module_apply.visible,
'%s Unexpected visible' % prefix)
if expected_admin_only is not None:
self.assert_equal(expected_admin_only, module_apply.admin_only,
'%s Unexpected admin_only' % prefix)
if expected_contents is not None:
self.assert_equal(expected_contents, module_apply.contents,
'%s Unexpected contents' % prefix)
@ -859,6 +1006,20 @@ class ModuleRunner(TestRunner):
if expected_status is not None:
self.assert_equal(expected_status, module_apply.status,
'%s Unexpected status' % prefix)
if expected_is_admin is not None:
self.assert_equal(expected_is_admin, module_apply.is_admin,
'%s Unexpected is_admin' % prefix)
def run_module_apply_wrong_module(
self, expected_exception=exceptions.BadRequest,
expected_http_code=400):
module = self._find_diff_datastore_module()
self.report.log("Found 'wrong' module: %s" % module.name)
client = self.auth_client
self.assert_raises(
expected_exception, expected_http_code,
client, client.instances.module_apply,
self.instance_info.id, [module.id])
def run_module_list_instance_after_apply(self):
self.assert_module_list_instance(
@ -873,7 +1034,8 @@ class ModuleRunner(TestRunner):
self.auth_client, self.instance_info.id, 2)
def run_module_update_after_remove(self):
name, description, contents = self.build_module_args('_updated')
name, description, contents, priority, order = (
self.build_module_args(15))
self.assert_module_update(
self.auth_client,
self.update_test_module.id,
@ -952,6 +1114,24 @@ class ModuleRunner(TestRunner):
self.register_debug_inst_ids(inst.id)
return inst.id
def run_create_inst_with_wrong_module(
self, expected_exception=exceptions.BadRequest,
expected_http_code=400):
module = self._find_diff_datastore_module()
self.report.log("Found 'wrong' module: %s" % module.name)
client = self.auth_client
self.assert_raises(
expected_exception, expected_http_code,
client, client.instances.create,
self.instance_info.name + '_wrong_ds',
self.instance_info.dbaas_flavor_href,
self.instance_info.volume,
datastore=self.instance_info.dbaas_datastore,
datastore_version=self.instance_info.dbaas_datastore_version,
nics=self.instance_info.nics,
modules=[module.id])
def run_module_delete_applied(
self, expected_exception=exceptions.Forbidden,
expected_http_code=403):

View File

@ -24,6 +24,7 @@ from mock import Mock
from mock import patch
from oslo_utils import encodeutils
from proboscis.asserts import assert_equal
from proboscis.asserts import assert_is_none
from proboscis.asserts import assert_true
from trove.common.context import TroveContext
@ -31,6 +32,7 @@ from trove.common import exception
from trove.guestagent.common import operating_system
from trove.guestagent.datastore import manager
from trove.guestagent import guest_log
from trove.guestagent.module import module_manager
from trove import rpc
from trove.tests.unittests import trove_testtools
@ -110,6 +112,12 @@ class ManagerTest(trove_testtools.TestCase):
self.expected_details_sys['type'] = 'SYS'
self.expected_details_sys['status'] = 'Enabled'
self.expected_details_sys['name'] = self.log_name_sys
self.expected_module_details = {
'name': 'mymod',
'type': 'ping',
'contents': 'e262cfe36134'
}
self.manager.module_manager = Mock()
def tearDown(self):
super(ManagerTest, self).tearDown()
@ -475,3 +483,36 @@ class ManagerTest(trove_testtools.TestCase):
self.manager.status.end_install(
error_occurred=True,
post_processing=ANY)
def test_module_list(self):
with patch.object(module_manager.ModuleManager, 'read_module_results',
return_value=[
self.expected_module_details]) as mock_rmr:
module_list = self.manager.module_list(self.context)
expected = [self.expected_module_details]
assert_equal(self._flatten_list_of_dicts(expected),
self._flatten_list_of_dicts(module_list),
"Wrong list: %s (Expected: %s)" % (
self._flatten_list_of_dicts(module_list),
self._flatten_list_of_dicts(expected)))
assert_equal(1, mock_rmr.call_count)
def test_module_apply(self):
with patch.object(
module_manager.ModuleManager, 'apply_module',
return_value=[self.expected_module_details]) as mock_am:
module_details = self.manager.module_apply(
self.context,
[{'module': self.expected_module_details}])
assert_equal([[self.expected_module_details]], module_details)
assert_equal(1, mock_am.call_count)
def test_module_remove(self):
with patch.object(
module_manager.ModuleManager, 'remove_module',
return_value=[self.expected_module_details]) as mock_rm:
module_details = self.manager.module_remove(
self.context,
{'module': self.expected_module_details})
assert_is_none(module_details)
assert_equal(1, mock_rm.call_count)

View File

@ -17,6 +17,7 @@ from mock import Mock, patch
from trove.backup import models as backup_models
from trove.common import cfg
from trove.common import crypto_utils
from trove.common import exception
from trove.common.instance import ServiceStatuses
from trove.datastore import models as datastore_models
@ -403,3 +404,68 @@ class TestReplication(trove_testtools.TestCase):
None, 'name', 2, "UUID", [], [], None,
self.datastore_version, 1,
None, slave_of_id=self.replica_info.id)
class TestModules(trove_testtools.TestCase):
def setUp(self):
super(TestModules, self).setUp()
def tearDown(self):
super(TestModules, self).tearDown()
def _build_module(self, ds_id, ds_ver_id):
module = Mock()
module.datastore_id = ds_id
module.datastore_version_id = ds_ver_id
module.contents = crypto_utils.encode_data(
crypto_utils.encrypt_data(
'VGhpc2lzbXlkYXRhc3RyaW5n',
'thisismylongkeytouse'))
return module
def test_validate_modules_for_apply(self):
data = [
[[self._build_module('ds', 'ds_ver')], 'ds', 'ds_ver', True],
[[self._build_module('ds', None)], 'ds', 'ds_ver', True],
[[self._build_module(None, None)], 'ds', 'ds_ver', True],
[[self._build_module('ds', 'ds_ver')], 'ds', 'ds2_ver', False,
exception.TroveError],
[[self._build_module('ds', 'ds_ver')], 'ds2', 'ds_ver', False,
exception.TroveError],
[[self._build_module('ds', 'ds_ver')], 'ds2', 'ds2_ver', False,
exception.TroveError],
[[self._build_module('ds', None)], 'ds2', 'ds2_ver', False,
exception.TroveError],
[[self._build_module(None, None)], 'ds2', 'ds2_ver', True],
[[self._build_module(None, 'ds_ver')], 'ds2', 'ds_ver', True],
]
for datum in data:
modules = datum[0]
ds_id = datum[1]
ds_ver_id = datum[2]
match = datum[3]
expected_exception = None
if not match:
expected_exception = datum[4]
ds = Mock()
ds.id = ds_id
ds.name = ds_id
ds_ver = Mock()
ds_ver.id = ds_ver_id
ds_ver.name = ds_ver_id
ds_ver.datastore_id = ds_id
with patch.object(datastore_models.Datastore, 'load',
return_value=ds):
with patch.object(datastore_models.DatastoreVersion, 'load',
return_value=ds_ver):
if match:
models.validate_modules_for_apply(
modules, ds_id, ds_ver_id)
else:
self.assertRaises(
expected_exception,
models.validate_modules_for_apply,
modules, ds_id, ds_ver_id)

View File

@ -30,6 +30,8 @@ class TestModuleController(trove_testtools.TestCase):
"name": 'test_module',
"module_type": 'test',
"contents": 'my_contents\n',
"priority_apply": 0,
"apply_order": 5
}
}
@ -44,7 +46,7 @@ class TestModuleController(trove_testtools.TestCase):
validator = jsonschema.Draft4Validator(schema)
self.assertTrue(validator.is_valid(body))
def test_validate_create_blankname(self):
def test_validate_create_blank_name(self):
body = self.module
body['module']['name'] = " "
schema = self.controller.get_schema('create', body)
@ -65,3 +67,14 @@ class TestModuleController(trove_testtools.TestCase):
self.assertEqual(1, len(errors))
self.assertIn("'$#$%^^' does not match '^.*[0-9a-zA-Z]+.*$'",
errors[0].message)
def test_validate_create_invalid_apply_order(self):
body = self.module
body['module']['apply_order'] = 12
schema = self.controller.get_schema('create', body)
validator = jsonschema.Draft4Validator(schema)
self.assertFalse(validator.is_valid(body))
errors = sorted(validator.iter_errors(body), key=lambda e: e.path)
self.assertEqual(1, len(errors))
self.assertIn("12 is greater than the maximum of 9",
errors[0].message)

View File

@ -14,8 +14,11 @@
# under the License.
#
import copy
from mock import Mock, patch
from trove.common import exception
from trove.datastore import models as datastore_models
from trove.module import models
from trove.taskmanager import api as task_api
from trove.tests.unittests import trove_testtools
@ -38,10 +41,101 @@ class CreateModuleTest(trove_testtools.TestCase):
def tearDown(self):
super(CreateModuleTest, self).tearDown()
def test_can_create_module(self):
def test_can_create_update_module(self):
module = models.Module.create(
self.context,
self.name, self.module_type, self.contents,
'my desc', 'my_tenant', None, None, False, True, False)
'my desc', 'my_tenant', None, None, False, True, False,
False, 5, True)
self.assertIsNotNone(module)
new_module = copy.copy(module)
models.Module.update(self.context, new_module, module, False)
module.delete()
def test_validate_action(self):
# tenant_id, auto_apply, visible, priority_apply, full_access,
# valid, exception, works_for_admin
data = [
['tenant', False, True, False, None,
True],
['tenant', True, True, False, None,
False, exception.ModuleAccessForbidden],
['tenant', False, False, False, None,
False, exception.ModuleAccessForbidden],
['tenant', False, True, True, None,
False, exception.ModuleAccessForbidden],
['tenant', False, True, False, True,
False, exception.ModuleAccessForbidden, False],
['tenant', False, True, False, False,
False, exception.ModuleAccessForbidden],
['tenant', True, False, True, False,
False, exception.ModuleAccessForbidden],
['tenant', True, False, True, True,
False, exception.InvalidModelError, False],
]
for datum in data:
tenant = datum[0]
auto_apply = datum[1]
visible = datum[2]
priority_apply = datum[3]
full_access = datum[4]
valid = datum[5]
expected_exception = None
if not valid:
expected_exception = datum[6]
context = Mock()
context.is_admin = False
works_for_admin = True
if len(datum) > 7:
works_for_admin = datum[7]
if valid:
models.Module.validate_action(
context, 'action', tenant, auto_apply, visible,
priority_apply, full_access)
else:
self.assertRaises(
expected_exception,
models.Module.validate_action, context, 'action', tenant,
auto_apply, visible, priority_apply, full_access)
# also make sure that it works for admin
if works_for_admin:
context.is_admin = True
models.Module.validate_action(
context, 'action', tenant, auto_apply, visible,
priority_apply, full_access)
def test_validate_datastore(self):
# datastore, datastore_version, valid, exception
data = [
[None, None, True],
['ds', None, True],
['ds', 'ds_ver', True],
[None, 'ds_ver', False,
exception.BadRequest],
]
for datum in data:
ds_id = datum[0]
ds_ver_id = datum[1]
valid = datum[2]
expected_exception = None
if not valid:
expected_exception = datum[3]
ds = Mock()
ds.id = ds_id
ds.name = ds_id
ds_ver = Mock()
ds_ver.id = ds_ver_id
ds_ver.name = ds_ver_id
ds_ver.datastore_id = ds_id
with patch.object(datastore_models.Datastore, 'load',
return_value=ds):
with patch.object(datastore_models.DatastoreVersion, 'load',
return_value=ds_ver):
if valid:
models.Module.validate_datastore(ds_id, ds_ver_id)
else:
self.assertRaises(
expected_exception,
models.Module.validate_datastore, ds_id, ds_ver_id)

View File

@ -43,6 +43,9 @@ class DetailedModuleViewTest(trove_testtools.TestCase):
self.module.datastore_version = '5.6'
self.module.auto_apply = False
self.module.tenant_id = 'my_tenant'
self.module.is_admin = False
self.module.priority_apply = False
self.module.apply_order = 5
def tearDown(self):
super(DetailedModuleViewTest, self).tearDown()
@ -69,3 +72,9 @@ class DetailedModuleViewTest(trove_testtools.TestCase):
result['module']['auto_apply'])
self.assertEqual(self.module.tenant_id,
result['module']['tenant_id'])
self.assertEqual(self.module.is_admin,
result['module']['is_admin'])
self.assertEqual(self.module.priority_apply,
result['module']['priority_apply'])
self.assertEqual(self.module.apply_order,
result['module']['apply_order'])