f50a50b3fe
Implement RPC service to work with plugins and DB. Base plugin class added. Implements: blueprint lease-manager Change-Id: Icbed7fabef6c0673c62f67017e5e9cd8d257b5ee
214 lines
7.5 KiB
Python
214 lines
7.5 KiB
Python
# Copyright (c) 2013 Mirantis Inc.
|
|
#
|
|
# 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 datetime
|
|
import eventlet
|
|
import six
|
|
|
|
from oslo.config import cfg
|
|
from stevedore import enabled
|
|
|
|
from climate.db import api as db_api
|
|
from climate import exceptions
|
|
from climate.openstack.common import log as logging
|
|
from climate.openstack.common.rpc import service as rpc_service
|
|
from climate.utils import service as service_utils
|
|
|
|
manager_opts = [
|
|
cfg.StrOpt('rpc_topic',
|
|
default='climate.manager',
|
|
help='The topic Climate uses for climate-manager messages.'),
|
|
cfg.ListOpt('plugins',
|
|
default=['dummy.vm.plugin'],
|
|
help='All plugins to use (one for every resource type to '
|
|
'support.)'),
|
|
]
|
|
|
|
CONF = cfg.CONF
|
|
CONF.register_opts(manager_opts, 'manager')
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class ManagerService(rpc_service.Service):
|
|
"""Service class for the climate-manager service.
|
|
|
|
Responsible for working with Climate DB, scheduling logic, running events,
|
|
working with plugins, etc.
|
|
"""
|
|
|
|
RPC_API_VERSION = '1.0'
|
|
|
|
def __init__(self, host):
|
|
super(ManagerService, self).__init__(host, CONF.manager.rpc_topic)
|
|
self.plugins = self._get_plugins()
|
|
self.resource_actions = self._setup_actions()
|
|
|
|
def start(self):
|
|
super(ManagerService, self).start()
|
|
self.tg.add_timer(10, self._event)
|
|
|
|
def _get_plugins(self):
|
|
"""Return dict of resource-plugin class pairs."""
|
|
config_plugins = CONF.manager.plugins
|
|
plugins = {}
|
|
|
|
extension_manager = enabled.EnabledExtensionManager(
|
|
check_func=lambda ext: ext.name in config_plugins,
|
|
namespace='climate.resource.plugins',
|
|
invoke_on_load=True
|
|
)
|
|
|
|
for ext in extension_manager.extensions:
|
|
if ext.obj.resource_type in plugins:
|
|
raise exceptions.ClimateException(
|
|
'You have provided several plugins for one resource type '
|
|
'in configuration file. '
|
|
'Please set one plugin per resource type.'
|
|
)
|
|
|
|
plugins[ext.obj.resource_type] = ext.obj
|
|
|
|
if len(plugins) < len(config_plugins):
|
|
raise exceptions.ClimateException('Not all requested plugins are '
|
|
'loaded.')
|
|
|
|
return plugins
|
|
|
|
def _setup_actions(self):
|
|
"""Setup actions for each resource type supported.
|
|
|
|
BasePlugin interface provides only on_start and on_end behaviour now.
|
|
If there are some configs needed by plugin, they should be returned
|
|
from get_plugin_opts method. These flags are registered in
|
|
[resource_type] group of configuration file.
|
|
"""
|
|
actions = {}
|
|
|
|
for resource_type, plugin in six.iteritems(self.plugins):
|
|
plugin = self.plugins[resource_type]
|
|
CONF.register_opts(plugin.get_plugin_opts(), group=resource_type)
|
|
|
|
actions[resource_type] = {}
|
|
actions[resource_type]['on_start'] = plugin.on_start
|
|
actions[resource_type]['on_end'] = plugin.on_end
|
|
|
|
return actions
|
|
|
|
@service_utils.with_empty_context
|
|
def _event(self):
|
|
"""Tries to commit event.
|
|
|
|
If there is an event in Climate DB to be done, do it and change its
|
|
status to 'DONE'.
|
|
"""
|
|
LOG.debug('Trying to get event from DB.')
|
|
events = db_api.event_get_all_sorted_by_filters(
|
|
sort_key='time',
|
|
sort_dir='asc',
|
|
filters={'status': 'UNDONE'}
|
|
)
|
|
|
|
if not events:
|
|
return
|
|
|
|
event = events[0]
|
|
|
|
if event['time'] < datetime.datetime.utcnow():
|
|
db_api.event_update(event['id'], {'status': 'IN_PROGRESS'})
|
|
event_type = event['event_type']
|
|
event_fn = getattr(self, event_type, None)
|
|
if event_fn is None:
|
|
raise exceptions.ClimateException('Event type %s is not '
|
|
'supported' % event_type)
|
|
try:
|
|
eventlet.spawn_n(service_utils.with_empty_context(event_fn),
|
|
event['lease_id'], event['id'])
|
|
except Exception:
|
|
db_api.event_update(event['id'], {'status': 'ERROR'})
|
|
LOG.exception('Error occurred while event handling.')
|
|
|
|
@service_utils.export_context
|
|
def get_lease(self, lease_id):
|
|
return db_api.lease_get(lease_id)
|
|
|
|
@service_utils.export_context
|
|
def list_leases(self):
|
|
return db_api.lease_list()
|
|
|
|
@service_utils.export_context
|
|
def create_lease(self, lease_values):
|
|
start_date = lease_values['start_date']
|
|
end_date = lease_values['end_date']
|
|
|
|
if start_date == 'now':
|
|
start_date = datetime.datetime.utcnow()
|
|
else:
|
|
start_date = datetime.datetime.strptime(start_date,
|
|
"%Y-%m-%d %H:%M")
|
|
end_date = datetime.datetime.strptime(end_date, "%Y-%m-%d %H:%M")
|
|
|
|
lease_values['start_date'] = start_date
|
|
lease_values['end_date'] = end_date
|
|
if not lease_values.get('events'):
|
|
lease_values['events'] = []
|
|
|
|
lease_values['events'].append({'event_type': 'start_lease',
|
|
'time': start_date,
|
|
'status': 'UNDONE'})
|
|
lease_values['events'].append({'event_type': 'end_lease',
|
|
'time': end_date,
|
|
'status': 'UNDONE'})
|
|
|
|
lease = db_api.lease_create(lease_values)
|
|
|
|
return db_api.lease_get(lease['id'])
|
|
|
|
@service_utils.export_context
|
|
def update_lease(self, lease_id, values):
|
|
if values:
|
|
db_api.lease_update(lease_id, values)
|
|
return db_api.lease_get(lease_id)
|
|
|
|
@service_utils.export_context
|
|
def delete_lease(self, lease_id):
|
|
lease = self.get_lease(lease_id)
|
|
for reservation in lease['reservations']:
|
|
self.plugins[reservation['resource_type']]\
|
|
.on_end(reservation['resource_id'])
|
|
db_api.lease_destroy(lease_id)
|
|
|
|
def start_lease(self, lease_id, event_id):
|
|
self._basic_action(lease_id, event_id, 'on_start', 'active')
|
|
|
|
def end_lease(self, lease_id, event_id):
|
|
self._basic_action(lease_id, event_id, 'on_end', 'deleted')
|
|
|
|
def _basic_action(self, lease_id, event_id, action_time,
|
|
reservation_status=None):
|
|
"""Commits basic lease actions such as starting and ending."""
|
|
lease = self.get_lease(lease_id)
|
|
|
|
for reservation in lease['reservations']:
|
|
resource_type = reservation['resource_type']
|
|
self.resource_actions[resource_type][action_time](
|
|
reservation['resource_id']
|
|
)
|
|
|
|
if reservation_status is not None:
|
|
db_api.reservation_update(reservation['id'],
|
|
{'status': reservation_status})
|
|
|
|
db_api.event_update(event_id, {'status': 'DONE'})
|