Merge "Support for action plug-in"

This commit is contained in:
Zuul 2019-02-11 07:45:20 +00:00 committed by Gerrit Code Review
commit fde7ad411d
8 changed files with 263 additions and 59 deletions

View File

@ -120,9 +120,21 @@ def remove_session(session_id):
return IMPL.remove_session(session_id)
def create_action(values):
def create_action_plugin(values):
"""Create a action from the values."""
return IMPL.create_action(values)
return IMPL.create_action_plugin(values)
def create_action_plugins(session_id, action_dict_list):
return IMPL.create_action_plugins(action_dict_list)
def create_action_plugin_instance(values):
return IMPL.create_action_plugin_instance(values)
def remove_action_plugin_instance(ap_instance):
return IMPL.remove_action_plugin_instance(ap_instance)
def create_host(values):

View File

@ -13,9 +13,6 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
revision = '001'
down_revision = None
import uuid
from alembic import op
@ -23,6 +20,9 @@ import six
import sqlalchemy as sa
from sqlalchemy.dialects.mysql import MEDIUMTEXT
revision = '001'
down_revision = None
def _generate_unicode_uuid():
return six.text_type(str(uuid.uuid4()))
@ -57,6 +57,8 @@ def upgrade():
sa.Column('maintained', sa.Boolean, default=False),
sa.Column('disabled', sa.Boolean, default=False),
sa.Column('details', sa.String(length=255), nullable=True),
sa.Column('plugin', sa.String(length=255), nullable=True),
sa.Column('plugin_state', sa.String(length=32), nullable=True),
sa.UniqueConstraint('session_id', 'hostname', name='_session_host_uc'),
sa.PrimaryKeyConstraint('id'))
@ -106,12 +108,26 @@ def upgrade():
sa.Column('session_id', sa.String(36),
sa.ForeignKey('sessions.session_id')),
sa.Column('plugin', sa.String(length=255), nullable=False),
sa.Column('state', sa.String(length=32), nullable=True),
sa.Column('type', sa.String(length=32), nullable=True),
sa.Column('meta', MediumText(), nullable=False),
sa.UniqueConstraint('session_id', 'plugin', name='_session_plugin_uc'),
sa.PrimaryKeyConstraint('id'))
op.create_table(
'action_plugin_instances',
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.Column('id', sa.String(36), primary_key=True,
default=_generate_unicode_uuid),
sa.Column('session_id', sa.String(36),
sa.ForeignKey('sessions.session_id')),
sa.Column('plugin', sa.String(length=255), nullable=False),
sa.Column('hostname', sa.String(length=255), nullable=False),
sa.Column('state', MediumText(), nullable=True),
sa.UniqueConstraint('session_id', 'plugin', 'hostname',
name='_session_plugin_instance_uc'),
sa.PrimaryKeyConstraint('id'))
def downgrade():
op.drop_table('sessions')

View File

@ -54,7 +54,8 @@ def setup_db():
engine = db_session.EngineFacade(cfg.CONF.database.connection,
sqlite_fk=True).get_engine()
models.MaintenanceSession.metadata.create_all(engine)
models.MaintenanceAction.metadata.create_all(engine)
models.MaintenanceActionPlugin.metadata.create_all(engine)
models.MaintenanceActionPluginInstance.metadata.create_all(engine)
models.MaintenanceHost.metadata.create_all(engine)
models.MaintenanceProject.metadata.create_all(engine)
models.MaintenanceInstance.metadata.create_all(engine)
@ -148,72 +149,138 @@ def remove_session(session_id):
session = get_session()
with session.begin():
action_plugin_instances = _action_plugin_instances_get_all(session,
session_id)
if action_plugin_instances:
for action in action_plugin_instances:
session.delete(action)
action_plugins = _action_plugins_get_all(session, session_id)
if action_plugins:
for action in action_plugins:
session.delete(action)
hosts = _hosts_get(session, session_id)
if not hosts:
# raise not found error
raise db_exc.FenixDBNotFound(session, session_id=session_id,
model='hosts')
for host in hosts:
session.delete(host)
if hosts:
for host in hosts:
session.delete(host)
projects = _projects_get(session, session_id)
if not projects:
# raise not found error
raise db_exc.FenixDBNotFound(session, session_id=session_id,
model='projects')
for project in projects:
session.delete(project)
if projects:
for project in projects:
session.delete(project)
instances = _instances_get(session, session_id)
if not instances:
# raise not found error
raise db_exc.FenixDBNotFound(session, session_id=session_id,
model='instances')
for instance in instances:
session.delete(instance)
if instances:
for instance in instances:
session.delete(instance)
msession = _maintenance_session_get(session, session_id)
if not msession:
# raise not found error
raise db_exc.FenixDBNotFound(session, session_id=session_id,
model='sessions')
session.delete(msession)
# TBD Other tables content when implemented
# Action
def _action_get(session, session_id, plugin):
query = model_query(models.MaintenanceActions, session)
# Action Plugin
def _action_plugin_get(session, session_id, plugin):
query = model_query(models.MaintenanceActionPlugin, session)
return query.filter_by(session_id=session_id, plugin=plugin).first()
def action_get(session_id, plugin):
return _action_get(get_session(), session_id, plugin)
def action_plugin_get(session_id, plugin):
return _action_plugin_get(get_session(), session_id, plugin)
def create_action(values):
def _action_plugins_get_all(session, session_id):
query = model_query(models.MaintenanceActionPlugin, session)
return query.filter_by(session_id=session_id).all()
def action_plugins_get_all(session_id):
return _action_plugins_get_all(get_session(), session_id)
def create_action_plugin(values):
values = values.copy()
maction = models.MaintenanceActions()
maction.update(values)
ap = models.MaintenanceActionPlugin()
ap.update(values)
session = get_session()
with session.begin():
try:
maction.save(session=session)
ap.save(session=session)
except common_db_exc.DBDuplicateEntry as e:
# raise exception about duplicated columns (e.columns)
raise db_exc.FenixDBDuplicateEntry(
model=maction.__class__.__name__, columns=e.columns)
model=ap.__class__.__name__, columns=e.columns)
return action_get(maction.session_id, maction.plugin)
return action_plugin_get(ap.session_id, ap.plugin)
def create_action_plugins(values_list):
for values in values_list:
vals = values.copy()
session = get_session()
with session.begin():
ap = models.MaintenanceActionPlugin()
ap.update(vals)
try:
ap.save(session=session)
except common_db_exc.DBDuplicateEntry as e:
# raise exception about duplicated columns (e.columns)
raise db_exc.FenixDBDuplicateEntry(
model=ap.__class__.__name__, columns=e.columns)
return action_plugins_get_all(ap.session_id)
# Action Plugin Instance
def _action_plugin_instance_get(session, session_id, plugin, hostname):
query = model_query(models.MaintenanceActionPluginInstance, session)
return query.filter_by(session_id=session_id, plugin=plugin,
hostname=hostname).first()
def action_plugin_instance_get(session_id, plugin, hostname):
return _action_plugin_instance_get(get_session(), session_id, plugin,
hostname)
def _action_plugin_instances_get_all(session, session_id):
query = model_query(models.MaintenanceActionPluginInstance, session)
return query.filter_by(session_id=session_id).all()
def action_plugin_instances_get_all(session_id):
return _action_plugin_instances_get_all(get_session(), session_id)
def create_action_plugin_instance(values):
values = values.copy()
ap_instance = models.MaintenanceActionPluginInstance()
ap_instance.update(values)
session = get_session()
with session.begin():
try:
ap_instance.save(session=session)
except common_db_exc.DBDuplicateEntry as e:
# raise exception about duplicated columns (e.columns)
raise db_exc.FenixDBDuplicateEntry(
model=ap_instance.__class__.__name__, columns=e.columns)
return action_plugin_instance_get(ap_instance.session_id,
ap_instance.plugin,
ap_instance.hostname)
def remove_action_plugin_instance(ap_instance):
session = get_session()
with session.begin():
session.delete(ap_instance)
# Host
@ -386,6 +453,6 @@ def remove_instance(session_id, instance_id):
if not minstance:
# raise not found error
raise db_exc.FenixDBNotFound(session, session_id=session_id,
model='sessions')
model='instances')
session.delete(minstance)

View File

@ -53,21 +53,36 @@ class MaintenanceSession(mb.FenixBase):
return super(MaintenanceSession, self).to_dict()
class MaintenanceAction(mb.FenixBase):
"""Maintenance action"""
class MaintenanceActionPlugin(mb.FenixBase):
"""Maintenance action plugin"""
__tablename__ = 'actions'
__tablename__ = 'action_plugins'
id = _id_column()
session_id = sa.Column(sa.String(36), sa.ForeignKey('sessions.session_id'),
nullable=False)
plugin = sa.Column(sa.String(length=255), nullable=False)
state = sa.Column(sa.String(length=32), nullable=True)
type = sa.Column(sa.String(length=32), nullable=True)
meta = sa.Column(MediumText(), nullable=False)
def to_dict(self):
return super(MaintenanceAction, self).to_dict()
return super(MaintenanceActionPlugin, self).to_dict()
class MaintenanceActionPluginInstance(mb.FenixBase):
"""Maintenance action instance"""
__tablename__ = 'action_plugin_instances'
id = _id_column()
session_id = sa.Column(sa.String(36), sa.ForeignKey('sessions.session_id'),
nullable=False)
hostname = sa.Column(sa.String(255), nullable=False)
plugin = sa.Column(sa.String(255), nullable=False)
state = sa.Column(sa.String(length=32), nullable=True)
def to_dict(self):
return super(MaintenanceActionPluginInstance, self).to_dict()
class MaintenanceHost(mb.FenixBase):
@ -83,6 +98,8 @@ class MaintenanceHost(mb.FenixBase):
maintained = sa.Column(sa.Boolean, default=False)
disabled = sa.Column(sa.Boolean, default=False)
details = sa.Column(sa.String(length=255), nullable=True)
plugin = sa.Column(sa.String(length=255), nullable=True)
plugin_state = sa.Column(sa.String(length=32), nullable=True)
def to_dict(self):
return super(MaintenanceHost, self).to_dict()

View File

View File

@ -0,0 +1,31 @@
# Copyright (c) 2019 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
class ActionPlugin(object):
def __init__(self, wf, ap_dbi):
self.hostname = ap_dbi.hostname
self.wf = wf
self.ap_dbi = ap_dbi
LOG.info("%s: Dummy action plugin initialized" % self.wf.session_id)
def run(self):
LOG.info("%s: Dummy action plugin run %s" % (self.wf.session_id,
self.hostname))
self.ap_dbi.state = "DONE"

View File

@ -40,15 +40,16 @@ class BaseWorkflow(Thread):
self.thg = threadgroup.ThreadGroup()
self.timer = {}
self.session = self._init_session(data)
LOG.info('%s: session %s' % (self.session_id, self.session))
self.hosts = []
if "hosts" in data and data['hosts']:
# Hosts given as input, not to be discovered in workflow
self.hosts = self.init_hosts(self.convert(data['hosts']))
else:
LOG.info('%s: No hosts as input' % self.session_id)
# TBD API to support action plugins
# self.actions =
if "actions" in data:
self.actions = self._init_action_plugins(data["actions"])
else:
self.actions = []
self.projects = []
self.instances = []
self.proj_instance_actions = {}
@ -104,9 +105,35 @@ class BaseWorkflow(Thread):
'maintenance_at': str(data['maintenance_at']),
'meta': str(self.convert(data['metadata'])),
'workflow': self.convert((data['workflow']))}
LOG.info('%s: _init_session: %s' % (self.session_id, session))
LOG.info('Initializing maintenance session: %s' % session)
return db_api.create_session(session)
def _init_action_plugins(self, ap_list):
actions = []
for action in ap_list:
adict = {
'session_id': self.session_id,
'plugin': str(action['plugin']),
'type': str(action['type'])}
if 'metadata' in action:
adict['meta'] = str(self.convert(action['metadata']))
actions.append(adict)
return db_api.create_action_plugins(self.session_id, actions)
def _create_action_plugin_instance(self, plugin, hostname, state=None):
ap_instance = {
'session_id': self.session_id,
'plugin': plugin,
'hostname': hostname,
'state': state}
return db_api.create_action_plugin_instance(ap_instance)
def get_action_plugins_by_type(self, ap_type):
aps = [ap for ap in self.actions if ap.type == ap_type]
if aps:
aps = sorted(aps, key=lambda k: k['plugin'])
return aps
def get_compute_hosts(self):
return [host.hostname for host in self.hosts
if host.type == 'compute']

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import datetime
from importlib import import_module
from novaclient import API_MAX_VERSION as nova_max_version
import novaclient.client as novaclient
@ -554,11 +555,44 @@ class Workflow(BaseWorkflow):
(server_id, instance.state))
return False
def host_maintenance(self, host):
LOG.info('maintaining host %s' % host)
# TBD Here we should call maintenance plugin given in maintenance
# session creation
time.sleep(5)
def host_maintenance_by_plugin_type(self, hostname, plugin_type):
aps = self.get_action_plugins_by_type(plugin_type)
if aps:
LOG.info("%s: Calling action plug-ins with type %s" %
(self.session_id, plugin_type))
for ap in aps:
ap_name = "fenix.workflow.actions.%s" % ap.plugin
LOG.info("%s: Calling action plug-in module: %s" %
(self.session_id, ap_name))
action_plugin = getattr(import_module(ap_name), 'ActionPlugin')
ap_db_instance = self._create_action_plugin_instance(ap.plugin,
hostname)
ap_instance = action_plugin(self, ap_db_instance)
ap_instance.run()
if ap_db_instance.state:
LOG.info('%s: %s finished with %s host %s' %
(self.session_id, ap.plugin,
ap_db_instance.state, hostname))
if 'FAILED' in ap_db_instance.state:
raise Exception('%s: %s finished with %s host %s' %
(self.session_id, ap.plugin,
ap_db_instance.state, hostname))
else:
raise Exception('%s: %s reported no state for host %s' %
(self.session_id, ap.plugin, hostname))
# If ap_db_instance failed, we keep it for state
db_api.remove_action_plugin_instance(ap_db_instance)
else:
LOG.info("%s: No action plug-ins with type %s" %
(self.session_id, plugin_type))
def host_maintenance(self, hostname):
host = self.get_host_by_name(hostname)
LOG.info('%s: Maintaining host %s' % (self.session_id, hostname))
for plugin_type in ["host", host.type]:
self.host_maintenance_by_plugin_type(hostname, plugin_type)
LOG.info('%s: Maintaining host %s complete' % (self.session_id,
hostname))
def maintenance(self):
LOG.info("%s: maintenance called" % self.session_id)