VNF workflow implementation

- New VNF workflow
- ETSI FEAT03 changes
- Drop py27 support

story: 2006838
Task: #37413

Change-Id: Icfe89d4f2b04791f65674a4fd5d8fb63e0a54f70
Signed-off-by: Tomi Juvonen <tomi.juvonen@nokia.com>
This commit is contained in:
Tomi Juvonen 2019-12-19 10:56:19 +02:00
parent e815519d6a
commit cbfd985549
17 changed files with 1753 additions and 18 deletions

View File

@ -34,7 +34,7 @@ api_opts = [
help="API host IP"),
cfg.IntOpt('port',
default=5000,
help="API port to use."),
help="API port to use.")
]
CONF = cfg.CONF
@ -49,9 +49,8 @@ def setup_app(pecan_config=None, debug=False, argv=None):
hooks.DBHook(),
hooks.ContextHook(),
hooks.RPCHook()]
app = pecan.make_app(pecan_config.app.root,
debug=CONF.debug,
debug=True,
hooks=app_hooks,
wrap_app=middleware.ParsableErrorMiddleware,
guess_content_type_from_ext=False)

View File

@ -36,6 +36,12 @@ class V1Controller(rest.RestController):
session = controller.SessionController()
# maps to v1/maintenance/{session_id}/{project_id}
project = controller.ProjectController()
# maps to v1/maintenance/{session_id}/{project_id}/{instance_id}
project_instance = controller.ProjectInstanceController()
# maps to v1/instance/{instance_id}
instance = controller.InstanceController()
# maps to v1/instance_group/{group_id}
instance_group = controller.InstanceGroupController()
@pecan.expose()
def _route(self, args):
@ -44,7 +50,6 @@ class V1Controller(rest.RestController):
It allows to map controller URL with correct controller instance.
By default, it maps with the same name.
"""
try:
route = self._routes.get(args[0], args[0])
depth = len(args)
@ -53,10 +58,17 @@ class V1Controller(rest.RestController):
args[0] = 'http404-nonexistingcontroller'
elif depth == 1:
args[0] = route
elif depth == 2 and route == "maintenance":
args[0] = "session"
elif depth == 2:
if route == "maintenance":
args[0] = "session"
elif route in ["instance", "instance_group"]:
args[0] = route
else:
args[0] = 'http404-nonexistingcontroller'
elif depth == 3 and route == "maintenance":
args[0] = "project"
elif depth == 4 and route == "maintenance":
args[0] = "project_instance"
else:
args[0] = 'http404-nonexistingcontroller'
except IndexError:

View File

@ -45,7 +45,10 @@ class ProjectController(rest.RestController):
abort(400)
engine_data = self.engine_rpcapi.project_get_session(session_id,
project_id)
response.text = jsonutils.dumps(engine_data)
try:
response.text = jsonutils.dumps(engine_data)
except TypeError:
response.body = jsonutils.dumps(engine_data)
# PUT /v1/maintenance/<session_id>/<project_id>
@policy.authorize('maintenance:session:project', 'put')
@ -55,7 +58,33 @@ class ProjectController(rest.RestController):
engine_data = self.engine_rpcapi.project_update_session(session_id,
project_id,
data)
response.text = jsonutils.dumps(engine_data)
try:
response.text = jsonutils.dumps(engine_data)
except TypeError:
response.body = jsonutils.dumps(engine_data)
class ProjectInstanceController(rest.RestController):
name = 'project_instance'
def __init__(self):
self.engine_rpcapi = maintenance.EngineRPCAPI()
# PUT /v1/maintenance/<session_id>/<project_id>/<instance_id>
@policy.authorize('maintenance:session:project:instance', 'put')
@expose(content_type='application/json')
def put(self, session_id, project_id, instance_id):
data = json.loads(request.body.decode('utf8'))
engine_data = (
self.engine_rpcapi.project_update_session_instance(session_id,
project_id,
instance_id,
data))
try:
response.text = jsonutils.dumps(engine_data)
except TypeError:
response.body = jsonutils.dumps(engine_data)
class SessionController(rest.RestController):
@ -76,7 +105,10 @@ class SessionController(rest.RestController):
if session is None:
response.status = 404
return {"error": "Invalid session"}
response.text = jsonutils.dumps(session)
try:
response.text = jsonutils.dumps(session)
except TypeError:
response.body = jsonutils.dumps(session)
# PUT /v1/maintenance/<session_id>
@policy.authorize('maintenance:session', 'put')
@ -84,7 +116,10 @@ class SessionController(rest.RestController):
def put(self, session_id):
data = json.loads(request.body.decode('utf8'))
engine_data = self.engine_rpcapi.admin_update_session(session_id, data)
response.text = jsonutils.dumps(engine_data)
try:
response.text = jsonutils.dumps(engine_data)
except TypeError:
response.body = jsonutils.dumps(engine_data)
# DELETE /v1/maintenance/<session_id>
@policy.authorize('maintenance:session', 'delete')
@ -94,7 +129,10 @@ class SessionController(rest.RestController):
LOG.error("Unexpected data")
abort(400)
engine_data = self.engine_rpcapi.admin_delete_session(session_id)
response.text = jsonutils.dumps(engine_data)
try:
response.text = jsonutils.dumps(engine_data)
except TypeError:
response.body = jsonutils.dumps(engine_data)
class MaintenanceController(rest.RestController):
@ -112,16 +150,120 @@ class MaintenanceController(rest.RestController):
LOG.error("Unexpected data")
abort(400)
sessions = self.engine_rpcapi.admin_get()
response.text = jsonutils.dumps(sessions)
try:
response.text = jsonutils.dumps(sessions)
except TypeError:
response.body = jsonutils.dumps(sessions)
# POST /v1/maintenance
@policy.authorize('maintenance', 'post')
@expose(content_type='application/json')
def post(self):
LOG.debug("POST /v1/maintenance")
data = json.loads(request.body.decode('utf8'))
session = self.engine_rpcapi.admin_create_session(data)
if session is None:
response.status = 509
return {"error": "Too many sessions"}
response.text = jsonutils.dumps(session)
try:
response.text = jsonutils.dumps(session)
except TypeError:
response.body = jsonutils.dumps(session)
class InstanceController(rest.RestController):
name = 'instance'
def __init__(self):
self.engine_rpcapi = maintenance.EngineRPCAPI()
# GET /v1/instance/<instance_id>
@policy.authorize('instance', 'get')
@expose(content_type='application/json')
def get(self, instance_id):
if request.body:
LOG.error("Unexpected data")
abort(400)
session = self.engine_rpcapi.get_instance(instance_id)
if session is None:
response.status = 404
return {"error": "Invalid session"}
try:
response.text = jsonutils.dumps(session)
except TypeError:
response.body = jsonutils.dumps(session)
# PUT /v1/instance/<instance_id>
@policy.authorize('instance', 'put')
@expose(content_type='application/json')
def put(self, instance_id):
data = json.loads(request.body.decode('utf8'))
engine_data = self.engine_rpcapi.update_instance(instance_id,
data)
try:
response.text = jsonutils.dumps(engine_data)
except TypeError:
response.body = jsonutils.dumps(engine_data)
# DELETE /v1/instance/<instance_id>
@policy.authorize('instance', 'delete')
@expose(content_type='application/json')
def delete(self, instance_id):
if request.body:
LOG.error("Unexpected data")
abort(400)
engine_data = self.engine_rpcapi.delete_instance(instance_id)
try:
response.text = jsonutils.dumps(engine_data)
except TypeError:
response.body = jsonutils.dumps(engine_data)
class InstanceGroupController(rest.RestController):
name = 'instance_group'
def __init__(self):
self.engine_rpcapi = maintenance.EngineRPCAPI()
# GET /v1/instance_group/<group_id>
@policy.authorize('instance_group', 'get')
@expose(content_type='application/json')
def get(self, group_id):
if request.body:
LOG.error("Unexpected data")
abort(400)
session = self.engine_rpcapi.get_instance_group(group_id)
if session is None:
response.status = 404
return {"error": "Invalid session"}
try:
response.text = jsonutils.dumps(session)
except TypeError:
response.body = jsonutils.dumps(session)
# PUT /v1/instance_group/<group_id>
@policy.authorize('instance_group', 'put')
@expose(content_type='application/json')
def put(self, group_id):
data = json.loads(request.body.decode('utf8'))
engine_data = (
self.engine_rpcapi.update_instance_group(group_id, data))
try:
response.text = jsonutils.dumps(engine_data)
except TypeError:
response.body = jsonutils.dumps(engine_data)
# DELETE /v1/instance_group/<group_id>
@policy.authorize('instance_group', 'delete')
@expose(content_type='application/json')
def delete(self, group_id):
if request.body:
LOG.error("Unexpected data")
abort(400)
engine_data = (
self.engine_rpcapi.delete_instance_group(group_id))
try:
response.text = jsonutils.dumps(engine_data)
except TypeError:
response.body = jsonutils.dumps(engine_data)

View File

@ -58,3 +58,40 @@ class EngineRPCAPI(service.RPCClient):
"""Update maintenance workflow session project state"""
return self.call('project_update_session', session_id=session_id,
project_id=project_id, data=data)
def project_update_session_instance(self, session_id, project_id,
instance_id, data):
"""Update maintenance workflow session project instance project_state
"""
return self.call('project_update_session_instance',
session_id=session_id,
project_id=project_id,
instance_id=instance_id,
data=data)
def get_instance(self, instance_id):
"""Get internal instance"""
return self.call('get_instance', instance_id=instance_id)
def update_instance(self, instance_id, data):
"""Update internal instance"""
return self.call('update_instance', instance_id=instance_id,
data=data)
def delete_instance(self, instance_id):
"""Delete internal instance"""
return self.call('delete_instance', instance_id=instance_id)
def get_instance_group(self, group_id):
"""Get internal instance group"""
return self.call('get_instance_group', group_id=group_id)
def update_instance_group(self, group_id, data):
"""Update internal instance group"""
return self.call('update_instance_group', group_id=group_id,
data=data)
def delete_instance_group(self, group_id):
"""Delete internal instance group"""
return self.call('delete_instance_group', group_id=group_id)

View File

@ -197,3 +197,38 @@ def create_instances(instances):
def remove_instance(session_id, instance_id):
return IMPL.remove_instance(session_id, instance_id)
def update_project_instance(values):
return IMPL.update_project_instance(values)
def remove_project_instance(instance_id):
return IMPL.remove_project_instance(instance_id)
def project_instance_get(instance_id):
return IMPL.project_instance_get(instance_id)
def group_instances_get(group_id):
return IMPL.group_instances_get(group_id)
def instance_group_get(group_id):
return IMPL.instance_group_get(group_id)
def instance_group_get_detailed(group_id):
instances = group_instances_get(group_id)
group = instance_group_get(group_id)
group['instance_ids'] = instances
return group
def update_instance_group(values):
return IMPL.update_instance_group(values)
def remove_instance_group(instance_id):
return IMPL.remove_instance_group(instance_id)

View File

@ -141,6 +141,37 @@ def upgrade():
name='_session_local_file_uc'),
sa.PrimaryKeyConstraint('id'))
op.create_table(
'project_instances',
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.Column('instance_id', sa.String(36), primary_key=True,
default=_generate_unicode_uuid),
sa.Column('project_id', sa.String(36), nullable=True),
sa.Column('group_id', sa.String(36), nullable=False),
sa.Column('instance_name', sa.String(length=255),
nullable=False),
sa.Column('max_interruption_time', sa.Integer, nullable=False),
sa.Column('migration_type', sa.String(36), nullable=False),
sa.Column('resource_mitigation', sa.Boolean, default=False),
sa.Column('lead_time', sa.Integer, nullable=False),
sa.PrimaryKeyConstraint('instance_id'))
op.create_table(
'instance_groups',
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.Column('group_id', sa.String(36), primary_key=True),
sa.Column('project_id', sa.String(36), nullable=True),
sa.Column('group_name', sa.String(length=255), nullable=False),
sa.Column('anti_affinity_group', sa.Boolean, default=False),
sa.Column('max_instances_per_host', sa.Integer,
nullable=False),
sa.Column('max_impacted_members', sa.Integer, nullable=False),
sa.Column('recovery_time', sa.Integer, nullable=False),
sa.Column('resource_mitigation', sa.Boolean, default=False),
sa.PrimaryKeyConstraint('group_id'))
def downgrade():
op.drop_table('sessions')
@ -149,3 +180,5 @@ def downgrade():
op.drop_table('instances')
op.drop_table('action_plugins')
op.drop_table('downloads')
op.drop_table('project_instances')
op.drop_table('instance_groups')

View File

@ -60,6 +60,9 @@ def setup_db():
models.MaintenanceHost.metadata.create_all(engine)
models.MaintenanceProject.metadata.create_all(engine)
models.MaintenanceInstance.metadata.create_all(engine)
models.ProjectInstance.metadata.create_all(engine)
models.MaintenanceProject.metadata.create_all(engine)
models.InstanceGroup.metadata.create_all(engine)
except sa.exc.OperationalError as e:
LOG.error("Database registration exception: %s", e)
return False
@ -499,3 +502,90 @@ def remove_instance(session_id, instance_id):
model='instances')
session.delete(minstance)
# Project instances
def _project_instance_get(session, instance_id):
query = model_query(models.ProjectInstance, session)
return query.filter_by(instance_id=instance_id).first()
def project_instance_get(instance_id):
return _project_instance_get(get_session(), instance_id)
def update_project_instance(values):
values = values.copy()
minstance = models.ProjectInstance()
minstance.update(values)
session = get_session()
with session.begin():
try:
minstance.save(session=session)
except common_db_exc.DBDuplicateEntry as e:
# raise exception about duplicated columns (e.columns)
raise db_exc.FenixDBDuplicateEntry(
model=minstance.__class__.__name__, columns=e.columns)
return project_instance_get(minstance.instance_id)
def remove_project_instance(instance_id):
session = get_session()
with session.begin():
minstance = _project_instance_get(session, instance_id)
if not minstance:
# raise not found error
raise db_exc.FenixDBNotFound(session, instance_id=instance_id,
model='project_instances')
session.delete(minstance)
# Instances groups
def _instance_group_get(session, group_id):
query = model_query(models.InstanceGroup, session)
return query.filter_by(group_id=group_id).first()
def instance_group_get(group_id):
return _instance_group_get(get_session(), group_id)
def _group_instances_get(session, group_id):
query = model_query(models.ProjectInstance, session)
return query.filter_by(group_id=group_id).all()
def group_instances_get(group_id):
return _group_instances_get(get_session(), group_id)
def update_instance_group(values):
values = values.copy()
minstance = models.InstanceGroup()
minstance.update(values)
session = get_session()
with session.begin():
try:
minstance.save(session=session)
except common_db_exc.DBDuplicateEntry as e:
# raise exception about duplicated columns (e.columns)
raise db_exc.FenixDBDuplicateEntry(
model=minstance.__class__.__name__, columns=e.columns)
return instance_group_get(minstance.group_id)
def remove_instance_group(group_id):
session = get_session()
with session.begin():
minstance = _instance_group_get(session, group_id)
if not minstance:
# raise not found error
raise db_exc.FenixDBNotFound(session, group_id=group_id,
model='instance_groups')
session.delete(minstance)

View File

@ -154,3 +154,39 @@ class MaintenanceDownload(mb.FenixBase):
def to_dict(self):
return super(MaintenanceDownload, self).to_dict()
class ProjectInstance(mb.FenixBase):
"""Project instances"""
__tablename__ = 'project_instances'
instance_id = sa.Column(sa.String(36), primary_key=True)
project_id = sa.Column(sa.String(36), nullable=True)
group_id = sa.Column(sa.String(36), nullable=False)
instance_name = sa.Column(sa.String(length=255), nullable=False)
max_interruption_time = sa.Column(sa.Integer, nullable=False)
migration_type = sa.Column(sa.String(36), nullable=False)
resource_mitigation = sa.Column(sa.Boolean, default=False)
lead_time = sa.Column(sa.Integer, nullable=False)
def to_dict(self):
return super(ProjectInstance, self).to_dict()
class InstanceGroup(mb.FenixBase):
"""Instances groups"""
__tablename__ = 'instance_groups'
group_id = sa.Column(sa.String(36), primary_key=True)
project_id = sa.Column(sa.String(36), nullable=True)
group_name = sa.Column(sa.String(length=255), nullable=False)
anti_affinity_group = sa.Column(sa.Boolean, default=False)
max_instances_per_host = sa.Column(sa.Integer, nullable=True)
max_impacted_members = sa.Column(sa.Integer, nullable=False)
recovery_time = sa.Column(sa.Integer, nullable=False)
resource_mitigation = sa.Column(sa.Boolean, default=False)
def to_dict(self):
return super(InstanceGroup, self).to_dict()

View File

@ -13,15 +13,21 @@
import itertools
from fenix.policies import base
from fenix.policies import instance
from fenix.policies import instance_group
from fenix.policies import maintenance
from fenix.policies import project
from fenix.policies import project_instance
from fenix.policies import session
def list_rules():
return itertools.chain(
base.list_rules(),
instance.list_rules(),
instance_group.list_rules(),
maintenance.list_rules(),
session.list_rules(),
project.list_rules(),
project_instance.list_rules()
)

View File

@ -0,0 +1,57 @@
# 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_policy import policy
from fenix.policies import base
POLICY_ROOT = 'fenix:instance:%s'
instance_policies = [
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'get',
check_str=base.RULE_ADMIN_OR_OWNER,
description='Policy rule for showing instance API.',
operations=[
{
'path': '/{api_version}/instance/{instance_id}/',
'method': 'GET'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'put',
check_str=base.RULE_ADMIN_OR_OWNER,
description='Policy rule for updating instance API.',
operations=[
{
'path': '/{api_version}/instance/{instance_id}/',
'method': 'PUT'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'delete',
check_str=base.RULE_ADMIN_OR_OWNER,
description='Policy rule for deleting instance API',
operations=[
{
'path': '/{api_version}/instance/{instance_id}',
'method': 'DELETE'
}
]
)
]
def list_rules():
return instance_policies

View File

@ -0,0 +1,57 @@
# 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_policy import policy
from fenix.policies import base
POLICY_ROOT = 'fenix:instance_group:%s'
instance_group_policies = [
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'get',
check_str=base.RULE_ADMIN_OR_OWNER,
description='Policy rule for showing instance_group API.',
operations=[
{
'path': '/{api_version}/instance_group/{group_id}/',
'method': 'GET'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'put',
check_str=base.RULE_ADMIN_OR_OWNER,
description='Policy rule for updating instance_group API.',
operations=[
{
'path': '/{api_version}/instance_group/{group_id}/',
'method': 'PUT'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'delete',
check_str=base.RULE_ADMIN_OR_OWNER,
description='Policy rule for deleting instance_group API',
operations=[
{
'path': '/{api_version}/instance_group/{group_id}',
'method': 'DELETE'
}
]
)
]
def list_rules():
return instance_group_policies

View File

@ -0,0 +1,37 @@
# 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_policy import policy
from fenix.policies import base
POLICY_ROOT = 'fenix:maintenance:session:project:instance:%s'
project_policies = [
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'put',
check_str=base.RULE_ADMIN_OR_OWNER,
description='Policy rule for setting project session affected '
'instance action and state API.',
operations=[
{
'path': '/{api_version}/maintenance/{seission_id}/'
'{project_id}/{instance_id}',
'method': 'PUT'
}
]
)
]
def list_rules():
return project_policies

View File

@ -36,6 +36,7 @@ from shutil import rmtree
from uuid import uuid1 as generate_uuid
from fenix import context
from fenix.db import api as db_api
from fenix.utils.download import download_url
import fenix.utils.identity_auth
@ -191,6 +192,47 @@ class EngineEndpoint(object):
data["instance_actions"].copy())
return data
def project_update_session_instance(self, ctx, session_id, project_id,
instance_id, data):
"""Update maintenance workflow session project instance state"""
LOG.info("EngineEndpoint: project_update_session_instance")
session_obj = self.workflow_sessions[session_id]
instance = session_obj.instance_by_id(instance_id)
instance.project_state = data["state"]
if "instance_action" in data:
instance.action = data["instance_action"]
return data
def get_instance(self, ctx, instance_id):
LOG.info("EngineEndpoint: get_instance")
instance = db_api.project_instance_get(instance_id)
return instance
def update_instance(self, ctx, instance_id, data):
LOG.info("EngineEndpoint: update_instance")
instance = db_api.update_project_instance(data)
return instance
def delete_instance(self, ctx, instance_id):
LOG.info("EngineEndpoint: delete_instance")
db_api.remove_project_instance(instance_id)
return {}
def get_instance_group(self, ctx, group_id):
LOG.info("EngineEndpoint: get_instance_group")
instance_group = db_api.instance_group_get_detailed(group_id)
return instance_group
def update_instance_group(self, ctx, group_id, data):
LOG.info("EngineEndpoint: update_instance_group")
instance_group = db_api.update_instance_group(data)
return instance_group
def delete_instance_group(self, ctx, group_id):
LOG.info("EngineEndpoint: delete_instance_group")
db_api.remove_instance_group(group_id)
return {}
class RPCServer(service.Service):

27
fenix/utils/thread.py Normal file
View File

@ -0,0 +1,27 @@
# 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.
def run_async(func):
from functools import wraps
from threading import Thread
@wraps(func)
def async_func(*args, **kwargs):
thread = Thread(target=func, args=args, kwargs=kwargs)
thread.start()
return thread
return async_func

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import aodhclient.client as aodhclient
from aodhclient.exceptions import BadRequest
from ast import literal_eval
import collections
from oslo_log import log as logging
@ -293,6 +294,10 @@ class BaseWorkflow(Thread):
if instance.host == host and
instance.project_id == project]
def instances_by_host(self, host):
return [instance for instance in self.instances if
instance.host == host]
def instance_action_by_project_reply(self, project, instance_id):
return self.proj_instance_actions[project][instance_id]
@ -416,9 +421,13 @@ class BaseWorkflow(Thread):
LOG.info("%s: done" % self.session_id)
def projects_listen_alarm(self, match_event):
match_projects = ([str(alarm['project_id']) for alarm in
self.aodh.alarm.list() if
str(alarm['event_rule']['event_type']) ==
try:
alarms = self.aodh.alarm.list({'all_projects': True})
except BadRequest:
#Train or earlier
alarms = self.aodh.alarm.list()
match_projects = ([str(alarm['project_id']) for alarm in alarms
if str(alarm['event_rule']['event_type']) ==
match_event])
all_projects_match = True
for project in self.project_names():
@ -435,6 +444,10 @@ class BaseWorkflow(Thread):
self.session_id,
project_id)
if "/maintenance/" not in instance_ids:
# Single instance
reply_url += "/%s" % instance_ids[0]
payload = dict(project_id=project_id,
instance_ids=instance_ids,
allowed_actions=allowed_actions,
@ -513,3 +526,28 @@ class BaseWorkflow(Thread):
pnames = self._project_names_in_state(projects, state)
LOG.error('%s: projects not answered: %s' % (self.session_id, pnames))
return False
def wait_instance_reply_state(self, state, instance, timer_name):
state_ack = 'ACK_%s' % state
state_nack = 'NACK_%s' % state
LOG.info('wait_instance_reply_state: %s' % instance.instance_id)
while not self.is_timer_expired(timer_name):
answer = instance.project_state
if answer == state:
pass
else:
self.stop_timer(timer_name)
if answer == state_ack:
LOG.info('%s in: %s' % (instance.instance_id, answer))
return True
elif answer == state_nack:
LOG.info('%s in: %s' % (instance.instance_id, answer))
return False
else:
LOG.error('%s in invalid state: %s' %
(instance.instance_id, answer))
return False
time.sleep(1)
LOG.error('%s: timer %s expired' %
(self.session_id, timer_name))
return False

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[tox]
minversion = 2.0
envlist = py36,py35,py27,pep8,docs
envlist = py36,py35,pep8,docs
skipsdist = True
[testenv]