Combine rules and resources to plugins

Change-Id: Iee26ccbd47918c198edbcea5f9368d0609783e2b
This commit is contained in:
lvdongbing 2016-06-06 03:48:39 -04:00
parent 3746f1cb34
commit 1dbdb83115
20 changed files with 361 additions and 375 deletions

View File

@ -96,6 +96,10 @@ class InvalidParameter(BileanException):
msg_fmt = _("Invalid value '%(value)s' specified for '%(name)s'")
class PluginTypeNotFound(BileanException):
msg_fmt = _("Plugin type (%(plugin_type)s) is not found.")
class RuleTypeNotFound(BileanException):
msg_fmt = _("Rule type (%(rule_type)s) is not found.")

View File

@ -21,7 +21,7 @@ from bilean.engine import event as EVENT
from bilean.engine.flows import flow as bilean_flow
from bilean.engine import lock as bilean_lock
from bilean.engine import user as user_mod
from bilean.resources import base as resource_base
from bilean.plugins import base as plugin_base
from oslo_log import log as logging
@ -57,7 +57,7 @@ class UserAction(base.Action):
self.user = None
def do_create_resource(self):
resource = resource_base.Resource.from_dict(self.inputs)
resource = plugin_base.Resource.from_dict(self.inputs)
try:
flow_engine = bilean_flow.get_create_resource_flow(
self.context, self.target, resource)
@ -75,7 +75,7 @@ class UserAction(base.Action):
try:
values = self.inputs
resource_id = values.pop('id', None)
resource = resource_base.Resource.load(
resource = plugin_base.Resource.load(
self.context, resource_id=resource_id)
except exception.ResourceNotFound:
LOG.error(_LE('The resource(%s) trying to update not found.'),
@ -99,7 +99,7 @@ class UserAction(base.Action):
def do_delete_resource(self):
try:
resource_id = self.inputs.get('resource_id')
resource = resource_base.Resource.load(
resource = plugin_base.Resource.load(
self.context, resource_id=resource_id)
except exception.ResourceNotFound:
LOG.error(_LE('The resource(%s) trying to delete not found.'),

View File

@ -39,12 +39,12 @@ def global_env():
class Environment(object):
'''An object that contains all rules, resources and customizations.'''
'''An object that contains all plugins, drivers and customizations.'''
SECTIONS = (
PARAMETERS, CUSTOM_RULES,
PARAMETERS, CUSTOM_PLUGINS,
) = (
'parameters', 'custom_rules'
'parameters', 'custom_plugins'
)
def __init__(self, env=None, is_global=False):
@ -55,22 +55,19 @@ class Environment(object):
'''
self.params = {}
if is_global:
self.rule_registry = registry.Registry('rules')
self.plugin_registry = registry.Registry('plugins')
self.driver_registry = registry.Registry('drivers')
self.resource_registry = registry.Registry('resources')
else:
self.rule_registry = registry.Registry(
'rules', global_env().rule_registry)
self.resource_registry = registry.Registry(
'resources', global_env().resource_registry)
self.plugin_registry = registry.Registry(
'plugins', global_env().plugin_registry)
self.driver_registry = registry.Registry(
'drivers', global_env().driver_registry)
if env is not None:
# Merge user specified keys with current environment
self.params = env.get(self.PARAMETERS, {})
custom_rules = env.get(self.CUSTOM_RULES, {})
self.rule_registry.load(custom_rules)
custom_plugins = env.get(self.CUSTOM_PLUGINS, {})
self.plugin_registry.load(custom_plugins)
def parse(self, env_str):
'''Parse a string format environment file into a dictionary.'''
@ -97,7 +94,7 @@ class Environment(object):
'''Load environment from the given dictionary.'''
self.params.update(env_dict.get(self.PARAMETERS, {}))
self.rule_registry.load(env_dict.get(self.CUSTOM_RULES, {}))
self.plugin_registry.load(env_dict.get(self.CUSTOM_PLUGINS, {}))
def _check_plugin_name(self, plugin_type, name):
if name is None or name == "":
@ -107,33 +104,22 @@ class Environment(object):
msg = _('%s type name is not a string') % plugin_type
raise exception.InvalidPlugin(message=msg)
def register_rule(self, name, plugin):
self._check_plugin_name('Rule', name)
self.rule_registry.register_plugin(name, plugin)
def register_plugin(self, name, plugin):
self._check_plugin_name('Plugin', name)
self.plugin_registry.register_plugin(name, plugin)
def get_rule(self, name):
self._check_plugin_name('Rule', name)
plugin = self.rule_registry.get_plugin(name)
def get_plugin(self, name):
self._check_plugin_name('Plugin', name)
plugin = self.plugin_registry.get_plugin(name)
if plugin is None:
raise exception.RuleTypeNotFound(rule_type=name)
raise exception.PluginTypeNotFound(plugin_type=name)
return plugin
def get_rule_types(self):
return self.rule_registry.get_types()
def get_plugins(self):
return self.plugin_registry.get_plugins()
def register_resource(self, name, plugin):
self._check_plugin_name('Resource', name)
self.resource_registry.register_plugin(name, plugin)
def get_resource(self, name):
self._check_plugin_name('Resource', name)
plugin = self.resource_registry.get_plugin(name)
if plugin is None:
raise exception.ResourceTypeNotFound(resource_type=name)
return plugin
def get_resource_types(self):
return self.resource_registry.get_types()
def get_plugin_types(self):
return self.plugin_registry.get_types()
def register_driver(self, name, plugin):
self._check_plugin_name('Driver', name)
@ -193,13 +179,9 @@ def initialize():
env = Environment(is_global=True)
# Register global plugins when initialized
entries = _get_mapping('bilean.rules')
entries = _get_mapping('bilean.plugins')
for name, plugin in entries:
env.register_rule(name, plugin)
entries = _get_mapping('bilean.resources')
for name, plugin in entries:
env.register_resource(name, plugin)
env.register_plugin(name, plugin)
entries = _get_mapping('bilean.drivers')
for name, plugin in entries:

View File

@ -24,8 +24,7 @@ from bilean.common import exception
from bilean.common.i18n import _LE
from bilean.engine import policy as policy_mod
from bilean.engine import user as user_mod
from bilean.resources import base as resource_base
from bilean.rules import base as rule_base
from bilean.plugin import base as plugin_base
from bilean import scheduler as bilean_scheduler
LOG = logging.getLogger(__name__)
@ -96,7 +95,7 @@ class UpdateResourceTask(task.Task):
def execute(self, context, resource, values, resource_bak, **kwargs):
old_rate = resource.rate
resource.properties = values.get('properties')
rule = rule_base.Rule.load(context, rule_id=resource.rule_id)
rule = plugin_base.Rule.load(context, rule_id=resource.rule_id)
resource.rate = rule.get_price(resource)
resource.delta_rate = resource.rate - old_rate
resource.store(context)
@ -107,7 +106,7 @@ class UpdateResourceTask(task.Task):
return
# restore resource
res = resource_base.Resource.from_dict(resource_bak)
res = plugin_base.Resource.from_dict(resource_bak)
res.store(context)

View File

@ -14,7 +14,7 @@
from bilean.common import exception
from bilean.common import utils
from bilean.db import api as db_api
from bilean.rules import base as rule_base
from bilean.plugins import base as plugin_base
class Policy(object):
@ -132,6 +132,6 @@ class Policy(object):
for rule in self.rules:
if rtype == rule['type'].split('-')[0]:
return rule_base.Rule.load(context, rule_id=rule['id'])
return plugin_base.Rule.load(context, rule_id=rule['id'])
raise exception.RuleNotFound(rule_type=rtype)

View File

@ -128,6 +128,9 @@ class Registry(object):
infoes = sorted(matches)
return infoes[0].plugin if infoes else None
def get_plugins(self):
return [p.plugin for p in six.itervalues(self._registry)]
def as_dict(self):
return dict((k, v.plugin) for k, v in self._registry.items())

View File

@ -39,8 +39,7 @@ from bilean.engine import environment
from bilean.engine import event as event_mod
from bilean.engine import policy as policy_mod
from bilean.engine import user as user_mod
from bilean.resources import base as resource_base
from bilean.rules import base as rule_base
from bilean.plugins import base as plugin_base
from bilean import scheduler as bilean_scheduler
LOG = logging.getLogger(__name__)
@ -338,14 +337,14 @@ class EngineService(service.Service):
@request_context
def rule_create(self, cnxt, name, spec, metadata=None):
if len(rule_base.Rule.load_all(cnxt, filters={'name': name})) > 0:
if len(plugin_base.Rule.load_all(cnxt, filters={'name': name})) > 0:
msg = _("The rule (%(name)s) already exists."
) % {"name": name}
raise exception.BileanBadRequest(msg=msg)
type_name, version = schema.get_spec_version(spec)
try:
plugin = environment.global_env().get_rule(type_name)
plugin = environment.global_env().get_plugin(type_name)
except exception.RuleTypeNotFound:
msg = _("The specified rule type (%(type)s) is not supported."
) % {"type": type_name}
@ -353,7 +352,7 @@ class EngineService(service.Service):
LOG.info(_LI("Creating rule type: %(type)s, name: %(name)s."),
{'type': type_name, 'name': name})
rule = plugin(name, spec, metadata=metadata)
rule = plugin.RuleClass(name, spec, metadata=metadata)
try:
rule.validate()
except exception.InvalidSpec as ex:
@ -374,18 +373,18 @@ class EngineService(service.Service):
if show_deleted is not None:
show_deleted = utils.parse_bool_param('show_deleted',
show_deleted)
rules = rule_base.Rule.load_all(cnxt, limit=limit,
marker=marker,
sort_keys=sort_keys,
sort_dir=sort_dir,
filters=filters,
show_deleted=show_deleted)
rules = plugin_base.Rule.load_all(cnxt, limit=limit,
marker=marker,
sort_keys=sort_keys,
sort_dir=sort_dir,
filters=filters,
show_deleted=show_deleted)
return [rule.to_dict() for rule in rules]
@request_context
def rule_get(self, cnxt, rule_id):
rule = rule_base.Rule.load(cnxt, rule_id=rule_id)
rule = plugin_base.Rule.load(cnxt, rule_id=rule_id)
return rule.to_dict()
@request_context
@ -395,7 +394,7 @@ class EngineService(service.Service):
@request_context
def rule_delete(self, cnxt, rule_id):
LOG.info(_LI("Deleting rule: '%s'."), rule_id)
rule_base.Rule.delete(cnxt, rule_id)
plugin_base.Rule.delete(cnxt, rule_id)
@request_context
def validate_creation(self, cnxt, resources):
@ -410,9 +409,9 @@ class EngineService(service.Service):
total_rate = 0
for resource in resources['resources']:
rule = policy.find_rule(cnxt, resource['resource_type'])
res = resource_base.Resource('FAKE_ID', user.id,
resource['resource_type'],
resource['properties'])
res = plugin_base.Resource('FAKE_ID', user.id,
resource['resource_type'],
resource['properties'])
total_rate += rule.get_price(res)
if count > 1:
total_rate = total_rate * count
@ -426,8 +425,8 @@ class EngineService(service.Service):
properties):
"""Create resource by given data."""
resource = resource_base.Resource(resource_id, user_id, resource_type,
properties)
resource = plugin_base.Resource(resource_id, user_id, resource_type,
properties)
params = {
'name': 'create_resource_%s' % resource_id,
@ -451,18 +450,18 @@ class EngineService(service.Service):
if show_deleted is not None:
show_deleted = utils.parse_bool_param('show_deleted',
show_deleted)
resources = resource_base.Resource.load_all(cnxt, user_id=user_id,
limit=limit, marker=marker,
sort_keys=sort_keys,
sort_dir=sort_dir,
filters=filters,
project_safe=project_safe,
show_deleted=show_deleted)
resources = plugin_base.Resource.load_all(cnxt, user_id=user_id,
limit=limit, marker=marker,
sort_keys=sort_keys,
sort_dir=sort_dir,
filters=filters,
project_safe=project_safe,
show_deleted=show_deleted)
return [r.to_dict() for r in resources]
@request_context
def resource_get(self, cnxt, resource_id):
resource = resource_base.Resource.load(cnxt, resource_id=resource_id)
resource = plugin_base.Resource.load(cnxt, resource_id=resource_id)
return resource.to_dict()
def resource_update(self, cnxt, user_id, resource):
@ -485,7 +484,7 @@ class EngineService(service.Service):
"""Delete a specific resource"""
try:
resource_base.Resource.load(cnxt, resource_id=resource_id)
plugin_base.Resource.load(cnxt, resource_id=resource_id)
except exception.ResourceNotFound:
LOG.error(_LE('The resource(%s) trying to delete not found.'),
resource_id)
@ -539,7 +538,7 @@ class EngineService(service.Service):
type_cache = []
for rule_id in rule_ids:
try:
rule = rule_base.Rule.load(cnxt, rule_id=rule_id)
rule = plugin_base.Rule.load(cnxt, rule_id=rule_id)
if rule.type not in type_cache:
rules.append({'id': rule_id, 'type': rule.type})
type_cache.append(rule.type)
@ -631,7 +630,7 @@ class EngineService(service.Service):
not_found = []
for rule in rules:
try:
db_rule = rule_base.Rule.load(cnxt, rule_id=rule)
db_rule = plugin_base.Rule.load(cnxt, rule_id=rule)
append_data = {'id': db_rule.id, 'type': db_rule.type}
if db_rule.type in exist_types:
error_rules.append(append_data)

View File

@ -20,7 +20,7 @@ from bilean.common import utils
from bilean.db import api as db_api
from bilean.drivers import base as driver_base
from bilean import notifier as bilean_notifier
from bilean.resources import base as resource_base
from bilean.plugins import base as plugin_base
from oslo_config import cfg
from oslo_log import log as logging
@ -335,7 +335,7 @@ class User(object):
reason = _("Balance overdraft")
LOG.info(_LI("Freeze user %(user_id)s, reason: %(reason)s"),
{'user_id': self.id, 'reason': reason})
resources = resource_base.Resource.load_all(
resources = plugin_base.Resource.load_all(
context, user_id=self.id, project_safe=False)
for resource in resources:
resource.do_delete(context)

View File

@ -1,23 +1,228 @@
#
# 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
# 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.
# 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 oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import timeutils
from bilean.common import exception
from bilean.common.i18n import _
from bilean.common import schema
from bilean.common import utils
from bilean.db import api as db_api
from bilean.engine import consumption as consumption_mod
from bilean.engine import environment
from oslo_utils import timeutils
LOG = logging.getLogger(__name__)
resource_opts = [
cfg.StrOpt('notifications_topic', default="notifications",
help="The default messaging notifications topic"),
]
CONF = cfg.CONF
CONF.register_opts(resource_opts, group='resource_plugin')
class Plugin(object):
'''Base class for plugins.'''
RuleClass = None
ResourceClass = None
notification_exchanges = []
@classmethod
def get_notification_topics_exchanges(cls):
"""Returns a list of (topic,exchange), (topic,exchange)..)."""
return [(CONF.resource_plugin.notifications_topic, exchange)
for exchange in cls.notification_exchanges]
class Rule(object):
'''Base class for rules.'''
KEYS = (
TYPE, VERSION, PROPERTIES,
) = (
'type', 'version', 'properties',
)
spec_schema = {
TYPE: schema.String(
_('Name of the rule type.'),
required=True,
),
VERSION: schema.String(
_('Version number of the rule type.'),
required=True,
),
PROPERTIES: schema.Map(
_('Properties for the rule.'),
required=True,
)
}
properties_schema = {}
def __new__(cls, name, spec, **kwargs):
"""Create a new rule of the appropriate class.
:param name: The name for the rule.
:param spec: A dictionary containing the spec for the rule.
:param kwargs: Keyword arguments for rule creation.
:returns: An instance of a specific sub-class of Rule.
"""
type_name, version = schema.get_spec_version(spec)
if cls != Rule:
RuleClass = cls
else:
PluginClass = environment.global_env().get_plugin(type_name)
RuleClass = PluginClass.RuleClass
return super(Rule, cls).__new__(RuleClass)
def __init__(self, name, spec, **kwargs):
"""Initialize a rule instance.
:param name: A string that specifies the name for the rule.
:param spec: A dictionary containing the detailed rule spec.
:param kwargs: Keyword arguments for initializing the rule.
:returns: An instance of a specific sub-class of Rule.
"""
type_name, version = schema.get_spec_version(spec)
self.name = name
self.spec = spec
self.id = kwargs.get('id')
self.type = kwargs.get('type', '%s-%s' % (type_name, version))
self.metadata = kwargs.get('metadata', {})
self.created_at = kwargs.get('created_at')
self.updated_at = kwargs.get('updated_at')
self.deleted_at = kwargs.get('deleted_at')
self.spec_data = schema.Spec(self.spec_schema, self.spec)
self.properties = schema.Spec(self.properties_schema,
self.spec.get(self.PROPERTIES, {}))
@classmethod
def from_db_record(cls, record):
'''Construct a rule object from database record.
:param record: a DB Profle object that contains all required fields.
'''
kwargs = {
'id': record.id,
'type': record.type,
'metadata': record.meta_data,
'created_at': record.created_at,
'updated_at': record.updated_at,
'deleted_at': record.deleted_at,
}
return cls(record.name, record.spec, **kwargs)
@classmethod
def load(cls, context, rule_id=None, rule=None, show_deleted=False):
'''Retrieve a rule object from database.'''
if rule is None:
rule = db_api.rule_get(context, rule_id,
show_deleted=show_deleted)
if rule is None:
raise exception.RuleNotFound(rule=rule_id)
return cls.from_db_record(rule)
@classmethod
def load_all(cls, context, limit=None, marker=None, sort_keys=None,
sort_dir=None, filters=None, show_deleted=False):
'''Retrieve all rules from database.'''
records = db_api.rule_get_all(context, limit=limit,
marker=marker,
sort_keys=sort_keys,
sort_dir=sort_dir,
filters=filters,
show_deleted=show_deleted)
return [cls.from_db_record(record) for record in records]
@classmethod
def delete(cls, context, rule_id):
db_api.rule_delete(context, rule_id)
def store(self, context):
'''Store the rule into database and return its ID.'''
timestamp = timeutils.utcnow()
values = {
'name': self.name,
'type': self.type,
'spec': self.spec,
'meta_data': self.metadata,
}
if self.id:
self.updated_at = timestamp
values['updated_at'] = timestamp
db_api.rule_update(context, self.id, values)
else:
self.created_at = timestamp
values['created_at'] = timestamp
rule = db_api.rule_create(context, values)
self.id = rule.id
return self.id
def validate(self):
'''Validate the schema and the data provided.'''
# general validation
self.spec_data.validate()
self.properties.validate()
@classmethod
def get_schema(cls):
return dict((name, dict(schema))
for name, schema in cls.properties_schema.items())
def get_price(self, resource):
'''For subclass to override.'''
return NotImplemented
def to_dict(self):
rule_dict = {
'id': self.id,
'name': self.name,
'type': self.type,
'spec': self.spec,
'metadata': self.metadata,
'created_at': utils.format_time(self.created_at),
'updated_at': utils.format_time(self.updated_at),
'deleted_at': utils.format_time(self.deleted_at),
}
return rule_dict
@classmethod
def from_dict(cls, **kwargs):
type_name = kwargs.pop('type')
name = kwargs.pop('name')
return cls(type_name, name, kwargs)
class Resource(object):
@ -41,7 +246,8 @@ class Resource(object):
if cls != Resource:
ResourceClass = cls
else:
ResourceClass = environment.global_env().get_resource(res_type)
PluginClass = environment.global_env().get_plugin(res_type)
ResourceClass = PluginClass.ResourceClass
return super(Resource, cls).__new__(ResourceClass)

View File

@ -10,12 +10,17 @@
# License for the specific language governing permissions and limitations
# under the License.
import six
from oslo_log import log as logging
from bilean.common import exception
from bilean.common.i18n import _
from bilean.common.i18n import _LE
from bilean.common import schema
from bilean.rules import base
from bilean.db import api as db_api
from bilean.drivers import base as driver_base
from bilean.plugins import base
LOG = logging.getLogger(__name__)
@ -85,3 +90,42 @@ class ServerRule(base.Rule):
if self.PER_HOUR == self.properties.get(self.UNIT) and price > 0:
price = price * 1.0 / 3600
return price
class ServerResource(base.Resource):
'''Resource for an OpenStack Nova server.'''
@classmethod
def do_check(context, user):
'''Communicate with other services and check user's resources.
This would be a period job of user to check if there are any missing
actions, and then make correction.
'''
# TODO(ldb)
return NotImplemented
def do_delete(self, context, ignore_missing=True, timeout=None):
'''Delete resource from other services.'''
# Delete resource from db
db_api.resource_delete(context, self.id)
# Delete resource from nova
novaclient = driver_base.BileanDriver().compute()
try:
novaclient.server_delete(self.id, ignore_missing=ignore_missing)
novaclient.wait_for_server_delete(self.id, timeout=timeout)
except Exception as ex:
LOG.error(_LE('Error: %s'), six.text_type(ex))
return False
return True
class ServerPlugin(base.Plugin):
'''Plugin for Openstack Nova server.'''
RuleClass = ServerRule
ResourceClass = ServerResource
notification_exchanges = ['nova', 'neutron']

View File

@ -1,54 +0,0 @@
#
# 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.
import six
from bilean.common.i18n import _LE
from bilean.db import api as db_api
from bilean.drivers import base as driver_base
from bilean.resources import base
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
class ServerResource(base.Resource):
'''Resource for an OpenStack Nova server.'''
@classmethod
def do_check(context, user):
'''Communicate with other services and check user's resources.
This would be a period job of user to check if there are any missing
actions, and then make correction.
'''
# TODO(ldb)
return NotImplemented
def do_delete(self, context, ignore_missing=True, timeout=None):
'''Delete resource from other services.'''
# Delete resource from db
db_api.resource_delete(context, self.id)
# Delete resource from nova
novaclient = driver_base.BileanDriver().compute()
try:
novaclient.server_delete(self.id, ignore_missing=ignore_missing)
novaclient.wait_for_server_delete(self.id, timeout=timeout)
except Exception as ex:
LOG.error(_LE('Error: %s'), six.text_type(ex))
return False
return True

View File

@ -1,198 +0,0 @@
# 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 oslo_log import log as logging
from oslo_utils import timeutils
from bilean.common import exception
from bilean.common.i18n import _
from bilean.common import schema
from bilean.common import utils
from bilean.db import api as db_api
from bilean.engine import environment
LOG = logging.getLogger(__name__)
class Rule(object):
'''Base class for rules.'''
KEYS = (
TYPE, VERSION, PROPERTIES,
) = (
'type', 'version', 'properties',
)
spec_schema = {
TYPE: schema.String(
_('Name of the rule type.'),
required=True,
),
VERSION: schema.String(
_('Version number of the rule type.'),
required=True,
),
PROPERTIES: schema.Map(
_('Properties for the rule.'),
required=True,
)
}
properties_schema = {}
def __new__(cls, name, spec, **kwargs):
"""Create a new rule of the appropriate class.
:param name: The name for the rule.
:param spec: A dictionary containing the spec for the rule.
:param kwargs: Keyword arguments for rule creation.
:returns: An instance of a specific sub-class of Rule.
"""
type_name, version = schema.get_spec_version(spec)
if cls != Rule:
RuleClass = cls
else:
RuleClass = environment.global_env().get_rule(type_name)
return super(Rule, cls).__new__(RuleClass)
def __init__(self, name, spec, **kwargs):
"""Initialize a rule instance.
:param name: A string that specifies the name for the rule.
:param spec: A dictionary containing the detailed rule spec.
:param kwargs: Keyword arguments for initializing the rule.
:returns: An instance of a specific sub-class of Rule.
"""
type_name, version = schema.get_spec_version(spec)
self.name = name
self.spec = spec
self.id = kwargs.get('id')
self.type = kwargs.get('type', '%s-%s' % (type_name, version))
self.metadata = kwargs.get('metadata', {})
self.created_at = kwargs.get('created_at')
self.updated_at = kwargs.get('updated_at')
self.deleted_at = kwargs.get('deleted_at')
self.spec_data = schema.Spec(self.spec_schema, self.spec)
self.properties = schema.Spec(self.properties_schema,
self.spec.get(self.PROPERTIES, {}))
@classmethod
def from_db_record(cls, record):
'''Construct a rule object from database record.
:param record: a DB Profle object that contains all required fields.
'''
kwargs = {
'id': record.id,
'type': record.type,
'metadata': record.meta_data,
'created_at': record.created_at,
'updated_at': record.updated_at,
'deleted_at': record.deleted_at,
}
return cls(record.name, record.spec, **kwargs)
@classmethod
def load(cls, context, rule_id=None, rule=None, show_deleted=False):
'''Retrieve a rule object from database.'''
if rule is None:
rule = db_api.rule_get(context, rule_id,
show_deleted=show_deleted)
if rule is None:
raise exception.RuleNotFound(rule=rule_id)
return cls.from_db_record(rule)
@classmethod
def load_all(cls, context, limit=None, marker=None, sort_keys=None,
sort_dir=None, filters=None, show_deleted=False):
'''Retrieve all rules from database.'''
records = db_api.rule_get_all(context, limit=limit,
marker=marker,
sort_keys=sort_keys,
sort_dir=sort_dir,
filters=filters,
show_deleted=show_deleted)
return [cls.from_db_record(record) for record in records]
@classmethod
def delete(cls, context, rule_id):
db_api.rule_delete(context, rule_id)
def store(self, context):
'''Store the rule into database and return its ID.'''
timestamp = timeutils.utcnow()
values = {
'name': self.name,
'type': self.type,
'spec': self.spec,
'meta_data': self.metadata,
}
if self.id:
self.updated_at = timestamp
values['updated_at'] = timestamp
db_api.rule_update(context, self.id, values)
else:
self.created_at = timestamp
values['created_at'] = timestamp
rule = db_api.rule_create(context, values)
self.id = rule.id
return self.id
def validate(self):
'''Validate the schema and the data provided.'''
# general validation
self.spec_data.validate()
self.properties.validate()
@classmethod
def get_schema(cls):
return dict((name, dict(schema))
for name, schema in cls.properties_schema.items())
def get_price(self, resource):
'''For subclass to override.'''
return NotImplemented
def to_dict(self):
rule_dict = {
'id': self.id,
'name': self.name,
'type': self.type,
'spec': self.spec,
'metadata': self.metadata,
'created_at': utils.format_time(self.created_at),
'updated_at': utils.format_time(self.updated_at),
'deleted_at': utils.format_time(self.deleted_at),
}
return rule_dict
@classmethod
def from_dict(cls, **kwargs):
type_name = kwargs.pop('type')
name = kwargs.pop('name')
return cls(type_name, name, kwargs)

View File

@ -18,12 +18,12 @@ from bilean.common import schema
from bilean.common import utils as common_utils
from bilean.db import api as db_api
from bilean.engine import environment
from bilean.rules import base as rule_base
from bilean.plugins import base as plugin_base
from bilean.tests.common import base
from bilean.tests.common import utils
class DummyRule(rule_base.Rule):
class DummyRule(plugin_base.Rule):
VERSION = '1.0'
properties_schema = {
@ -41,15 +41,19 @@ class DummyRule(rule_base.Rule):
super(DummyRule, self).__init__(name, spec, **kwargs)
class DummyPlugin(plugin_base.Plugin):
RuleClass = DummyRule
class TestRuleBase(base.BileanTestCase):
def setUp(self):
super(TestRuleBase, self).setUp()
self.context = utils.dummy_context()
environment.global_env().register_rule('bilean.rule.dummy', DummyRule)
environment.global_env().register_plugin('bilean.dummy', DummyPlugin)
self.spec = {
'type': 'bilean.rule.dummy',
'type': 'bilean.dummy',
'version': '1.0',
'properties': {
'key1': 'value1',
@ -58,7 +62,7 @@ class TestRuleBase(base.BileanTestCase):
}
def _create_rule(self, rule_name, rule_id=None):
rule = rule_base.Rule(rule_name, self.spec)
rule = plugin_base.Rule(rule_name, self.spec)
if rule_id:
rule.id = rule_id
@ -67,7 +71,7 @@ class TestRuleBase(base.BileanTestCase):
def _create_db_rule(self, **kwargs):
values = {
'name': 'test-rule',
'type': 'bilean.rule.dummy-1.0',
'type': 'bilean.dummy-1.0',
'spec': self.spec,
'metadata': {}
}
@ -81,7 +85,7 @@ class TestRuleBase(base.BileanTestCase):
self.assertIsNone(rule.id)
self.assertEqual(name, rule.name)
self.assertEqual('bilean.rule.dummy-1.0', rule.type)
self.assertEqual('bilean.dummy-1.0', rule.type)
self.assertEqual(self.spec, rule.spec)
self.assertEqual({}, rule.metadata)
self.assertIsNone(rule.created_at)
@ -89,7 +93,7 @@ class TestRuleBase(base.BileanTestCase):
self.assertIsNone(rule.deleted_at)
spec_data = rule.spec_data
self.assertEqual('bilean.rule.dummy', spec_data['type'])
self.assertEqual('bilean.dummy', spec_data['type'])
self.assertEqual('1.0', spec_data['version'])
self.assertEqual({'key1': 'value1', 'key2': 2},
spec_data['properties'])
@ -102,13 +106,13 @@ class TestRuleBase(base.BileanTestCase):
'properties': '',
}
self.assertRaises(exception.RuleTypeNotFound,
rule_base.Rule,
self.assertRaises(exception.PluginTypeNotFound,
plugin_base.Rule,
'test-rule', bad_spec)
def test_load(self):
rule = self._create_db_rule()
result = rule_base.Rule.load(self.context, rule.id)
result = plugin_base.Rule.load(self.context, rule.id)
self.assertEqual(rule.id, result.id)
self.assertEqual(rule.name, result.name)
@ -122,25 +126,25 @@ class TestRuleBase(base.BileanTestCase):
def test_load_not_found(self):
ex = self.assertRaises(exception.RuleNotFound,
rule_base.Rule.load,
plugin_base.Rule.load,
self.context, 'fake-rule', None)
self.assertEqual('The rule (fake-rule) could not be found.',
six.text_type(ex))
ex = self.assertRaises(exception.RuleNotFound,
rule_base.Rule.load,
plugin_base.Rule.load,
self.context, None, None)
self.assertEqual('The rule (None) could not be found.',
six.text_type(ex))
def test_load_all(self):
result = rule_base.Rule.load_all(self.context)
result = plugin_base.Rule.load_all(self.context)
self.assertEqual([], list(result))
rule1 = self._create_db_rule(name='rule-1', id='ID1')
rule2 = self._create_db_rule(name='rule-2', id='ID2')
result = rule_base.Rule.load_all(self.context)
result = plugin_base.Rule.load_all(self.context)
rules = list(result)
self.assertEqual(2, len(rules))
self.assertEqual(rule1.id, rules[0].id)
@ -150,7 +154,7 @@ class TestRuleBase(base.BileanTestCase):
def test_load_all_with_params(self, mock_get_all):
mock_get_all.return_value = []
res = list(rule_base.Rule.load_all(self.context))
res = list(plugin_base.Rule.load_all(self.context))
self.assertEqual([], res)
mock_get_all.assert_called_once_with(self.context, limit=None,
marker=None, sort_keys=None,
@ -158,11 +162,11 @@ class TestRuleBase(base.BileanTestCase):
show_deleted=False)
mock_get_all.reset_mock()
res = list(rule_base.Rule.load_all(self.context, limit=1,
marker='MARKER',
sort_keys=['K1'],
sort_dir='asc',
filters={'name': 'fake-name'}))
res = list(plugin_base.Rule.load_all(self.context, limit=1,
marker='MARKER',
sort_keys=['K1'],
sort_dir='asc',
filters={'name': 'fake-name'}))
self.assertEqual([], res)
mock_get_all.assert_called_once_with(self.context, limit=1,
marker='MARKER',
@ -175,14 +179,14 @@ class TestRuleBase(base.BileanTestCase):
rule = self._create_db_rule()
rule_id = rule.id
res = rule_base.Rule.delete(self.context, rule_id)
res = plugin_base.Rule.delete(self.context, rule_id)
self.assertIsNone(res)
self.assertRaises(exception.RuleNotFound,
rule_base.Rule.load,
plugin_base.Rule.load,
self.context, rule_id, None)
def test_delete_not_found(self):
result = rule_base.Rule.delete(self.context, 'fake-rule')
result = plugin_base.Rule.delete(self.context, 'fake-rule')
self.assertIsNone(result)
def test_store_for_create(self):
@ -240,7 +244,7 @@ class TestRuleBase(base.BileanTestCase):
'deleted_at': None,
}
result = rule_base.Rule.load(self.context, rule_id=rule.id)
result = plugin_base.Rule.load(self.context, rule_id=rule.id)
self.assertEqual(expected, result.to_dict())
def test_get_schema(self):

View File

@ -40,11 +40,8 @@ oslo.config.opts =
bilean.drivers =
openstack = bilean.drivers.openstack
bilean.rules =
os.nova.server = bilean.rules.os.nova.server:ServerRule
bilean.resources =
os.nova.server = bilean.resources.os.nova.server:ServerResource
bilean.plugins =
os.nova.server = bilean.plugins.os.nova.server:ServerPlugin
[global]
setup-hooks =