Merge "VNF workflow implementation"
This commit is contained in:
commit
ce134562ef
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
)
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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):
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
Loading…
Reference in New Issue