Combine rules and resources to plugins
Change-Id: Iee26ccbd47918c198edbcea5f9368d0609783e2b
This commit is contained in:
parent
3746f1cb34
commit
1dbdb83115
@ -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.")
|
||||
|
||||
|
@ -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.'),
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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())
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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']
|
@ -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
|
@ -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)
|
@ -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):
|
||||
|
@ -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 =
|
||||
|
Loading…
Reference in New Issue
Block a user