Release instance when freeze user

Change-Id: I54694f0573615399dad55fdbc4b82bfa51b4e0ca
This commit is contained in:
lvdongbing 2016-03-16 02:08:45 -04:00
parent f980eeb988
commit df6044d409
12 changed files with 191 additions and 94 deletions

View File

@ -47,6 +47,15 @@ service_opts = [
help=_('The directory to search for environment files.')),
]
engine_opts = [
cfg.StrOpt('environment_dir',
default='/etc/bilean/environments',
help=_('The directory to search for environment files.')),
cfg.IntOpt('default_action_timeout',
default=3600,
help=_('Timeout in seconds for actions.')),
]
rpc_opts = [
cfg.StrOpt('host',
default=socket.gethostname(),
@ -91,6 +100,7 @@ revision_opts = [
def list_opts():
yield None, rpc_opts
yield None, engine_opts
yield None, service_opts
yield None, cloud_backend_opts
yield paste_deploy_group.name, paste_deploy_opts

View File

@ -39,7 +39,7 @@ def global_env():
class Environment(object):
'''An object that contains all rules, policies and customizations.'''
'''An object that contains all rules, resources and customizations.'''
SECTIONS = (
PARAMETERS, CUSTOM_RULES,
@ -57,9 +57,12 @@ class Environment(object):
if is_global:
self.rule_registry = registry.Registry('rules')
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.driver_registry = registry.Registry(
'drivers', global_env().driver_registry)
@ -118,6 +121,20 @@ class Environment(object):
def get_rule_types(self):
return self.rule_registry.get_types()
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 register_driver(self, name, plugin):
self._check_plugin_name('Driver', name)
self.driver_registry.register_plugin(name, plugin)
@ -180,12 +197,13 @@ def initialize():
for name, plugin in entries:
env.register_rule(name, plugin)
try:
entries = _get_mapping('bilean.drivers')
for name, plugin in entries:
env.register_driver(name, plugin)
except Exception:
pass
entries = _get_mapping('bilean.resources')
for name, plugin in entries:
env.register_resource(name, plugin)
entries = _get_mapping('bilean.drivers')
for name, plugin in entries:
env.register_driver(name, plugin)
env.read_global_environment()
_environment = env

View File

@ -17,7 +17,7 @@ from bilean.common import exception
from bilean.common.i18n import _
from bilean.common import utils
from bilean.db import api as db_api
from bilean.engine import resource as resource_mod
from bilean.resources import base as resource_base
from oslo_log import log as logging
from oslo_utils import timeutils
@ -123,7 +123,7 @@ def record(context, user_id, action=None, seconds=0, value=0):
"""
try:
if action == 'charge':
resources = resource_mod.Resource.load_all(
resources = resource_base.Resource.load_all(
context, user_id=user_id, project_safe=False)
for resource in resources:
usage = resource.rate * seconds

View File

@ -74,16 +74,15 @@ class BileanScheduler(object):
self._scheduler = BackgroundScheduler()
self.notifier = notifier.Notifier()
self.engine_id = kwargs.get('engine_id', None)
self.context = kwargs.get('context', None)
if not self.context:
self.context = bilean_context.get_admin_context()
if cfg.CONF.scheduler.store_ap_job:
self._scheduler.add_jobstore(cfg.CONF.scheduler.backend,
url=cfg.CONF.scheduler.connection)
def init_scheduler(self):
"""Init all jobs related to the engine from db."""
jobs = db_api.job_get_all(self.context, engine_id=self.engine_id) or []
admin_context = bilean_context.get_admin_context()
jobs = [] or db_api.job_get_all(admin_context,
engine_id=self.engine_id)
for job in jobs:
if self.is_exist(job.id):
continue
@ -96,7 +95,7 @@ class BileanScheduler(object):
params=job.parameters)
# Init daily job for all users
users = user_mod.User.load_all(self.context)
users = user_mod.User.load_all(admin_context)
for user in users:
job_id = self._generate_job_id(user.id, self.DAILY)
if self.is_exist(job_id):
@ -176,38 +175,41 @@ class BileanScheduler(object):
return job is not None
def _notify_task(self, user_id):
user = user_mod.User.load(self.context, user_id=user_id)
admin_context = bilean_context.get_admin_context()
user = user_mod.User.load(admin_context, user_id=user_id)
reason = "The balance is almost use up"
msg = {'user': user.id, 'notification': reason}
self.notifier.info('billing.notify', msg)
if user.status != user.FREEZE and user.rate > 0:
user.do_bill(self.context)
user.do_bill(admin_context)
try:
db_api.job_delete(
self.context, self._generate_job_id(user.id, 'notify'))
admin_context, self._generate_job_id(user.id, 'notify'))
except exception.NotFound as e:
LOG.warn(_("Failed in deleting job: %s") % six.text_type(e))
user.set_status(self.context, user.WARNING, reason)
user.set_status(admin_context, user.WARNING, reason)
self.update_user_job(user)
def _daily_task(self, user_id):
user = user_mod.User.load(self.context, user_id=user_id)
admin_context = bilean_context.get_admin_context()
user = user_mod.User.load(admin_context, user_id=user_id)
if user.status != user.FREEZE and user.rate > 0:
user.do_bill(self.context)
user.do_bill(admin_context)
try:
db_api.job_delete(
self.context, self._generate_job_id(user.id, 'daily'))
admin_context, self._generate_job_id(user.id, 'daily'))
except exception.NotFound as e:
LOG.warn(_("Failed in deleting job: %s") % six.text_type(e))
self.update_user_job(user)
def _freeze_task(self, user_id):
user = user_mod.User.load(self.context, user_id=user_id)
admin_context = bilean_context.get_admin_context()
user = user_mod.User.load(admin_context, user_id=user_id)
if user.status != user.FREEZE and user.rate > 0:
user.do_bill(self.context)
user.do_bill(admin_context)
try:
db_api.job_delete(
self.context, self._generate_job_id(user.id, 'freeze'))
admin_context, self._generate_job_id(user.id, 'freeze'))
except exception.NotFound as e:
LOG.warn(_("Failed in deleting job: %s") % six.text_type(e))
self.update_user_job(user)
@ -228,7 +230,8 @@ class BileanScheduler(object):
'job_type': self.NOTIFY,
'engine_id': self.engine_id,
'parameters': {'run_date': utils.format_time(run_date)}}
db_api.job_create(self.context, job)
admin_context = bilean_context.get_admin_context()
db_api.job_create(admin_context, job)
def _add_freeze_job(self, user):
if not user.rate:
@ -243,7 +246,8 @@ class BileanScheduler(object):
'job_type': self.FREEZE,
'engine_id': self.engine_id,
'parameters': {'run_date': utils.format_time(run_date)}}
db_api.job_create(self.context, job)
admin_context = bilean_context.get_admin_context()
db_api.job_create(admin_context, job)
return True
def _add_daily_job(self, user):
@ -257,18 +261,20 @@ class BileanScheduler(object):
'job_type': self.DAILY,
'engine_id': self.engine_id,
'parameters': job_params}
db_api.job_create(self.context, job)
admin_context = bilean_context.get_admin_context()
db_api.job_create(admin_context, job)
return True
def update_user_job(self, user):
"""Update user's billing job"""
# Delete all jobs except daily job
admin_context = bilean_context.get_admin_context()
for job_type in self.NOTIFY, self.FREEZE:
job_id = self._generate_job_id(user.id, job_type)
try:
if self.is_exist(job_id):
self.remove_job(job_id)
db_api.job_delete(self.context, job_id)
db_api.job_delete(admin_context, job_id)
except Exception as e:
LOG.warn(_("Failed in deleting job: %s") % six.text_type(e))
@ -279,12 +285,13 @@ class BileanScheduler(object):
def delete_user_jobs(self, user):
"""Delete all jobs related the specific user."""
admin_context = bilean_context.get_admin_context()
for job_type in self.job_types:
job_id = self._generate_job_id(user.id, job_type)
try:
if self.is_exist(job_id):
self.remove_job(job_id)
db_api.job_delete(self.context, job_id)
db_api.job_delete(admin_context, job_id)
except Exception as e:
LOG.warn(_("Failed in deleting job: %s") % six.text_type(e))

View File

@ -30,9 +30,9 @@ from bilean.common import utils
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 resource as resource_mod
from bilean.engine import scheduler
from bilean.engine import user as user_mod
from bilean.resources import base as resource_base
from bilean.rules import base as rule_base
LOG = logging.getLogger(__name__)
@ -74,17 +74,14 @@ class EngineService(service.Service):
self.target = None
self._rpc_server = None
if context is None:
self.context = bilean_context.get_admin_context()
def start(self):
self.engine_id = socket.gethostname()
LOG.info(_LI("Initialise bilean users from keystone."))
user_mod.User.init_users(self.context)
admin_context = bilean_context.get_admin_context()
user_mod.User.init_users(admin_context)
self.scheduler = scheduler.BileanScheduler(engine_id=self.engine_id,
context=self.context)
self.scheduler = scheduler.BileanScheduler(engine_id=self.engine_id)
LOG.info(_LI("Starting billing scheduler for engine: %s"),
self.engine_id)
self.scheduler.init_scheduler()
@ -263,9 +260,9 @@ class EngineService(service.Service):
total_rate = 0
for resource in resources['resources']:
rule = policy.find_rule(cnxt, resource['resource_type'])
res = resource_mod.Resource('FAKE_ID', user.id,
resource['resource_type'],
resource['properties'])
res = resource_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
@ -283,21 +280,22 @@ class EngineService(service.Service):
would be done.
"""
resource = resource_mod.Resource(resource_id, user_id, resource_type,
properties)
resource = resource_base.Resource(resource_id, user_id, resource_type,
properties)
# Find the exact rule of resource
user = user_mod.User.load(self.context, user_id=user_id)
admin_context = bilean_context.get_admin_context()
user = user_mod.User.load(admin_context, user_id=user_id)
user_policy = policy_mod.Policy.load(
self.context, policy_id=user.policy_id)
rule = user_policy.find_rule(self.context, resource_type)
admin_context, policy_id=user.policy_id)
rule = user_policy.find_rule(admin_context, resource_type)
# Update resource with rule_id and rate
resource.rule_id = rule.id
resource.rate = rule.get_price(resource)
# Update user with resource
user.update_with_resource(self.context, resource)
resource.store(self.context)
user.update_with_resource(admin_context, resource)
resource.store(admin_context)
# As the rate of user has changed, the billing job for the user
# should change too.
@ -314,48 +312,49 @@ class EngineService(service.Service):
if show_deleted is not None:
show_deleted = utils.parse_bool_param('show_deleted',
show_deleted)
resources = resource_mod.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 = 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)
return [r.to_dict() for r in resources]
@request_context
def resource_get(self, cnxt, resource_id):
resource = resource_mod.Resource.load(cnxt, resource_id=resource_id)
resource = resource_base.Resource.load(cnxt, resource_id=resource_id)
return resource.to_dict()
def resource_update(self, cnxt, resource):
"""Do resource update."""
res = resource_mod.Resource.load(
self.context, resource_id=resource['id'])
admin_context = bilean_context.get_admin_context()
res = resource_base.Resource.load(
admin_context, resource_id=resource['id'])
old_rate = res.rate
res.properties = resource['properties']
rule = rule_base.Rule.load(self.context, rule_id=res.rule_id)
rule = rule_base.Rule.load(admin_context, rule_id=res.rule_id)
res.rate = rule.get_price(res)
res.store(self.context)
res.store(admin_context)
res.d_rate = res.rate - old_rate
user = user_mod.User.load(self.context, res.user_id)
user.update_with_resource(self.context, res, action='update')
user = user_mod.User.load(admin_context, res.user_id)
user.update_with_resource(admin_context, res, action='update')
self.scheduler.update_user_job(user)
def resource_delete(self, cnxt, resource_id):
"""Do resource delete"""
res = resource_mod.Resource.load(
self.context, resource_id=resource_id, project_safe=False)
user = user_mod.User.load(self.context, user_id=res.user_id)
user.update_with_resource(self.context, res, action='delete')
admin_context = bilean_context.get_admin_context()
res = resource_base.Resource.load(
admin_context, resource_id=resource_id, project_safe=False)
user = user_mod.User.load(admin_context, user_id=res.user_id)
user.update_with_resource(admin_context, res, action='delete')
self.scheduler.update_user_job(user)
try:
res.do_delete(self.context)
except Exception as ex:
LOG.warn(_("Delete resource error %s"), ex)
return
res.delete(admin_context)
@request_context
def event_list(self, cnxt, user_id=None, limit=None, marker=None,
@ -409,7 +408,7 @@ class EngineService(service.Service):
}
policy = policy_mod.Policy(name, **kwargs)
policy.store(cnxt)
LOG.info(_LI("Policy is created: %(id)s."), policy.id)
LOG.info(_LI("Successfully create policy (%s)."), policy.id)
return policy.to_dict()
@request_context

View File

@ -19,7 +19,7 @@ from bilean.common import utils
from bilean.db import api as db_api
from bilean.drivers import base as driver_base
from bilean.engine import event as event_mod
from bilean.engine import resource as resource_mod
from bilean.resources import base as resource_base
from oslo_config import cfg
from oslo_log import log as logging
@ -242,19 +242,13 @@ class User(object):
def _freeze(self, context, reason=None):
'''Freeze user when balance overdraft.'''
LOG.info(_("Freeze user because of: %s") % reason)
self._release_resource(context)
LOG.info(_("Balance of user %s overdraft, change user's "
"status to 'freeze'") % self.id)
self.status = self.FREEZE
self.status_reason = reason
def _release_resource(self, context):
'''Do freeze user, delete all resources ralated to user.'''
filters = {'user_id': self.id}
resources = resource_mod.Resource.load_all(context, filters=filters)
LOG.info(_("Freeze user %(user_id), reason: %(reason)s"),
{'user_id': self.id, 'reason': reason})
resources = resource_base.Resource.load_all(
context, user_id=self.id, project_safe=False)
for resource in resources:
resource.do_delete(context)
resource.do_delete()
self.set_status(context, self.FREEZE, reason)
def do_delete(self, context):
db_api.user_delete(context, self.id)
@ -266,7 +260,7 @@ class User(object):
total_seconds = (now - self.last_bill).total_seconds()
self.balance = self.balance - self.rate * total_seconds
self.last_bill = now
if self.balance < 0:
if self.balance <= 0:
self._freeze(context, reason="Balance overdraft")
self.store(context)
event_mod.record(context, self.id,

View File

View File

@ -14,6 +14,7 @@
from bilean.common import exception
from bilean.common import utils
from bilean.db import api as db_api
from bilean.engine import environment
class Resource(object):
@ -24,6 +25,15 @@ class Resource(object):
something else.
"""
def __new__(cls, id, user_id, res_type, properties, **kwargs):
"""Create a new resource of the appropriate class."""
if cls != Resource:
ResourceClass = cls
else:
ResourceClass = environment.global_env().get_resource(res_type)
return super(Resource, cls).__new__(ResourceClass)
def __init__(self, id, user_id, resource_type, properties, **kwargs):
self.id = id
self.user_id = user_id
@ -61,6 +71,10 @@ class Resource(object):
return self.id
def delete(self, context):
'''Delete resource from db.'''
db_api.resource_delete(context, self.id)
@classmethod
def _from_db_record(cls, record):
'''Construct a resource object from database record.
@ -95,7 +109,7 @@ class Resource(object):
def load_all(cls, context, user_id=None, show_deleted=False,
limit=None, marker=None, sort_keys=None, sort_dir=None,
filters=None, project_safe=True):
'''Retrieve all users of from database.'''
'''Retrieve all users from database.'''
records = db_api.resource_get_all(context, user_id=user_id,
show_deleted=show_deleted,
@ -121,14 +135,17 @@ class Resource(object):
}
return resource_dict
def do_delete(self, context, resource_id):
db_api.resource_delete(context, resource_id)
@classmethod
def do_check(cls, context, user):
'''Communicate with other services and check user's resources.
def resource_delete_by_physical_resource_id(self, context,
physical_resource_id,
resource_type):
db_api.resource_delete_by_physical_resource_id(
context, physical_resource_id, resource_type)
This would be a period job of user to check if there are any missing
actions, and then make correction.
'''
def resource_delete_by_user_id(self, context, user_id):
db_api.resource_delete(context, user_id)
return NotImplemented
def do_delete(self, ignore_missing=True, timeout=None):
'''Delete resource from other services.'''
return NotImplemented

View File

View File

View File

@ -0,0 +1,49 @@
#
# 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.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, ignore_missing=True, timeout=None):
'''Delete resource from other services.'''
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

@ -41,6 +41,9 @@ bilean.drivers =
bilean.rules =
os.nova.server = bilean.rules.os.nova.server:ServerRule
bilean.resources =
os.nova.server = bilean.resources.os.nova.server:ServerResource
[global]
setup-hooks =
pbr.hooks.setup_hook