Apply black formatter to dcmanager/api

This commit applies the Black format to the `dcmanager/api`
files to ensure that it adheres to the Black code style guidelines.

Test Plan:
PASS: Success in stx-distcloud-tox-black

Story: 2011149
Task: 50444

Change-Id: Ib1af98da7b1fdd6a478b8c093dc7dd474d3bc5e6
Signed-off-by: Hugo Brito <hugo.brito@windriver.com>
This commit is contained in:
Hugo Brito
2024-06-28 17:03:01 -03:00
parent 5558d9a4ee
commit 9aa90f102e
36 changed files with 2465 additions and 2101 deletions

View File

@@ -18,5 +18,18 @@
# - Because you must use a hash, you need to append to this list in a follow-up # - Because you must use a hash, you need to append to this list in a follow-up
# commit to the actual reformatting commit that you are trying to ignore. # commit to the actual reformatting commit that you are trying to ignore.
# Enable pylint/pep8 violations for all Python files
3685223b28194754fef89229724d795c6862014f
a2b9bb7ab28a0da08dd4ba20dc97671eb10b6b50
766f0522956d9e3a5272cff4dec06008d791c129
4438b8fd5579f20ece09e670b712e3afd691bd79
# Format all Python files with Black formatter # Format all Python files with Black formatter
69970aac60f91f1bc7cbc1323364a5a782756cfe 69970aac60f91f1bc7cbc1323364a5a782756cfe
83dbf64acabff56f9dfc9d496a5eed66fcd41481
686abd1cbc75b5a27798f522c1e686f3c8bd7376
56e359bf8adfa871838194145b04c25bd43f8321
fb82532bbf0c91fbd6ba385b0cbab880ec1159d6
51bee5a605ebb016b7905b243ac9d3da86551df6
51b6e19a2cf572399dc394ff3336c3c384de010a
70fd84b263fef57d9589683ce22496b4cb55d65c

View File

@@ -341,7 +341,7 @@ class SubcloudInstall(object):
) )
update_iso_cmd += [ update_iso_cmd += [
consts.GEN_ISO_OPTIONS[key], consts.GEN_ISO_OPTIONS[key],
("vlan" + "=" + vlan_inteface), ("vlan=" + vlan_inteface),
] ]
elif key == "bootstrap_interface" and "bootstrap_vlan" in values: elif key == "bootstrap_interface" and "bootstrap_vlan" in values:
boot_interface = "%s.%s" % ( boot_interface = "%s.%s" % (

View File

@@ -1,5 +1,5 @@
# Copyright 2015 Huawei Technologies Co., Ltd. # Copyright 2015 Huawei Technologies Co., Ltd.
# Copyright (c) 2017-2022 Wind River Systems, Inc. # Copyright (c) 2017-2022, 2024 Wind River Systems, Inc.
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -32,29 +32,35 @@ from dcmanager.common import version
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
common_opts = [ common_opts = [
cfg.StrOpt('bind_host', default='0.0.0.0', cfg.StrOpt("bind_host", default="0.0.0.0", help=_("The host IP to bind to")),
help=_("The host IP to bind to")), cfg.IntOpt("bind_port", default=8119, help=_("The port to bind to")),
cfg.IntOpt('bind_port', default=8119, cfg.IntOpt("api_workers", default=2, help=_("number of api workers")),
help=_("The port to bind to")), cfg.StrOpt(
cfg.IntOpt('api_workers', default=2, "state_path",
help=_("number of api workers")), default=os.path.join(os.path.dirname(__file__), "../"),
cfg.StrOpt('state_path', help="Top-level directory for maintaining dcmanager state",
default=os.path.join(os.path.dirname(__file__), '../'), ),
help='Top-level directory for maintaining dcmanager state'), cfg.StrOpt(
cfg.StrOpt('api_extensions_path', default="", "api_extensions_path", default="", help=_("The path for API extensions")
help=_("The path for API extensions")), ),
cfg.StrOpt('auth_strategy', default='keystone', cfg.StrOpt(
help=_("The type of authentication to use")), "auth_strategy", default="keystone", help=_("The type of authentication to use")
cfg.BoolOpt('allow_bulk', default=True, ),
help=_("Allow the usage of the bulk API")), cfg.BoolOpt("allow_bulk", default=True, help=_("Allow the usage of the bulk API")),
cfg.BoolOpt('allow_pagination', default=False, cfg.BoolOpt(
help=_("Allow the usage of the pagination")), "allow_pagination", default=False, help=_("Allow the usage of the pagination")
cfg.BoolOpt('allow_sorting', default=False, ),
help=_("Allow the usage of the sorting")), cfg.BoolOpt(
cfg.StrOpt('pagination_max_limit', default="-1", "allow_sorting", default=False, help=_("Allow the usage of the sorting")
help=_("The maximum number of items returned in a single " ),
"response, value was 'infinite' or negative integer " cfg.StrOpt(
"means no limit")), "pagination_max_limit",
default="-1",
help=_(
"The maximum number of items returned in a single response, "
"value was 'infinite' or negative integer means no limit"
),
),
] ]
@@ -66,9 +72,12 @@ def init(args, **kwargs):
# auth.register_conf_options(cfg.CONF) # auth.register_conf_options(cfg.CONF)
logging.register_options(cfg.CONF) logging.register_options(cfg.CONF)
cfg.CONF(args=args, project='dcmanager', cfg.CONF(
version='%%(prog)s %s' % version.version_info.release_string(), args=args,
**kwargs) project="dcmanager",
version="%%(prog)s %s" % version.version_info.release_string(),
**kwargs
)
def setup_logging(): def setup_logging():
@@ -76,9 +85,10 @@ def setup_logging():
product_name = "dcmanager" product_name = "dcmanager"
logging.setup(cfg.CONF, product_name) logging.setup(cfg.CONF, product_name)
LOG.info("Logging enabled!") LOG.info("Logging enabled!")
LOG.info("%(prog)s version %(version)s", LOG.info(
{'prog': sys.argv[0], "%(prog)s version %(version)s",
'version': version.version_info.release_string()}) {"prog": sys.argv[0], "version": version.version_info.release_string()},
)
LOG.debug("command line: %s", " ".join(sys.argv)) LOG.debug("command line: %s", " ".join(sys.argv))

View File

@@ -30,20 +30,14 @@ def setup_app(*args, **kwargs):
opts = cfg.CONF.pecan opts = cfg.CONF.pecan
config = { config = {
'server': { "server": {"port": cfg.CONF.bind_port, "host": cfg.CONF.bind_host},
'port': cfg.CONF.bind_port, "app": {
'host': cfg.CONF.bind_host "root": "dcmanager.api.controllers.root.RootController",
}, "modules": ["dcmanager.api"],
'app': {
'root': 'dcmanager.api.controllers.root.RootController',
'modules': ['dcmanager.api'],
"debug": opts.debug, "debug": opts.debug,
"auth_enable": opts.auth_enable, "auth_enable": opts.auth_enable,
'errors': { "errors": {400: "/error", "__force_dict__": True},
400: '/error', },
'__force_dict__': True
}
}
} }
pecan_config = pecan.configuration.conf_from_dict(config) pecan_config = pecan.configuration.conf_from_dict(config)
@@ -55,11 +49,8 @@ def setup_app(*args, **kwargs):
debug=False, debug=False,
wrap_app=_wrap_app, wrap_app=_wrap_app,
force_canonical=False, force_canonical=False,
hooks=lambda: [ hooks=lambda: [ctx.AuthHook(), ctx.AuditLoggingHook()],
ctx.AuthHook(), guess_content_type_from_ext=True,
ctx.AuditLoggingHook()
],
guess_content_type_from_ext=True
) )
return app return app
@@ -67,10 +58,10 @@ def setup_app(*args, **kwargs):
def _wrap_app(app): def _wrap_app(app):
app = request_id.RequestId(app) app = request_id.RequestId(app)
if cfg.CONF.pecan.auth_enable and cfg.CONF.auth_strategy == 'keystone': if cfg.CONF.pecan.auth_enable and cfg.CONF.auth_strategy == "keystone":
conf = dict(cfg.CONF.keystone_authtoken) conf = dict(cfg.CONF.keystone_authtoken)
# Change auth decisions of requests to the app itself. # Change auth decisions of requests to the app itself.
conf.update({'delay_auth_decision': True}) conf.update({"delay_auth_decision": True})
# NOTE: Policy enforcement works only if Keystone # NOTE: Policy enforcement works only if Keystone
# authentication is enabled. No support for other authentication # authentication is enabled. No support for other authentication
@@ -86,7 +77,7 @@ _launcher = None
def serve(api_service, conf, workers=1): def serve(api_service, conf, workers=1):
global _launcher global _launcher
if _launcher: if _launcher:
raise RuntimeError(_('serve() can only be called once')) raise RuntimeError(_("serve() can only be called once"))
_launcher = service.launch(conf, api_service, workers=workers) _launcher = service.launch(conf, api_service, workers=workers)

View File

@@ -24,34 +24,35 @@ import dcmanager.common.context as k_context
def extract_context_from_environ(): def extract_context_from_environ():
context_paras = {'auth_token': 'HTTP_X_AUTH_TOKEN', context_paras = {
'user': 'HTTP_X_USER_ID', "auth_token": "HTTP_X_AUTH_TOKEN",
'project': 'HTTP_X_TENANT_ID', "user": "HTTP_X_USER_ID",
'user_name': 'HTTP_X_USER_NAME', "project": "HTTP_X_TENANT_ID",
'tenant_name': 'HTTP_X_PROJECT_NAME', "user_name": "HTTP_X_USER_NAME",
'domain': 'HTTP_X_DOMAIN_ID', "tenant_name": "HTTP_X_PROJECT_NAME",
'roles': 'HTTP_X_ROLE', "domain": "HTTP_X_DOMAIN_ID",
'user_domain': 'HTTP_X_USER_DOMAIN_ID', "roles": "HTTP_X_ROLE",
'project_domain': 'HTTP_X_PROJECT_DOMAIN_ID', "user_domain": "HTTP_X_USER_DOMAIN_ID",
'request_id': 'openstack.request_id'} "project_domain": "HTTP_X_PROJECT_DOMAIN_ID",
"request_id": "openstack.request_id",
}
environ = request.environ environ = request.environ
for key, val in context_paras.items(): for key, val in context_paras.items():
context_paras[key] = environ.get(val) context_paras[key] = environ.get(val)
role = environ.get('HTTP_X_ROLE') role = environ.get("HTTP_X_ROLE")
context_paras['is_admin'] = 'admin' in role.split(',') context_paras["is_admin"] = "admin" in role.split(",")
return k_context.RequestContext(**context_paras) return k_context.RequestContext(**context_paras)
def extract_credentials_for_policy(): def extract_credentials_for_policy():
context_paras = {'project_name': 'HTTP_X_PROJECT_NAME', context_paras = {"project_name": "HTTP_X_PROJECT_NAME", "roles": "HTTP_X_ROLE"}
'roles': 'HTTP_X_ROLE'}
environ = request.environ environ = request.environ
for key, val in context_paras.items(): for key, val in context_paras.items():
context_paras[key] = environ.get(val) context_paras[key] = environ.get(val)
context_paras['roles'] = context_paras['roles'].split(',') context_paras["roles"] = context_paras["roles"].split(",")
return context_paras return context_paras

View File

@@ -23,16 +23,16 @@ from dcmanager.api.controllers.v1 import root as v1_root
class RootController(object): class RootController(object):
@pecan.expose('json') @pecan.expose("json")
def _lookup(self, version, *remainder): def _lookup(self, version, *remainder):
version = str(version) version = str(version)
minor_version = version[-1] minor_version = version[-1]
major_version = version[1] major_version = version[1]
remainder = remainder + (minor_version,) remainder = remainder + (minor_version,)
if major_version == '1': if major_version == "1":
return v1_root.Controller(), remainder return v1_root.Controller(), remainder
@pecan.expose(generic=True, template='json') @pecan.expose(generic=True, template="json")
def index(self): def index(self):
return { return {
"versions": [ "versions": [
@@ -41,19 +41,19 @@ class RootController(object):
"links": [ "links": [
{ {
"rel": "self", "rel": "self",
"href": pecan.request.application_url + "/v1.0/" "href": pecan.request.application_url + "/v1.0/",
} }
], ],
"id": "v1.0", "id": "v1.0",
"updated": "2017-10-2" "updated": "2017-10-2",
} }
] ]
} }
@index.when(method='POST') @index.when(method="POST")
@index.when(method='PUT') @index.when(method="PUT")
@index.when(method='DELETE') @index.when(method="DELETE")
@index.when(method='HEAD') @index.when(method="HEAD")
@index.when(method='PATCH') @index.when(method="PATCH")
def not_supported(self): def not_supported(self):
pecan.abort(405) pecan.abort(405)

View File

@@ -28,13 +28,13 @@ LOG = logging.getLogger(__name__)
class SubcloudAlarmController(object): class SubcloudAlarmController(object):
VERSION_ALIASES = { VERSION_ALIASES = {
'Newton': '1.0', "Newton": "1.0",
} }
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(SubcloudAlarmController, self).__init__(*args, **kwargs) super(SubcloudAlarmController, self).__init__(*args, **kwargs)
@expose(generic=True, template='json') @expose(generic=True, template="json")
def index(self): def index(self):
# Route the request to specific methods with parameters # Route the request to specific methods with parameters
pass pass
@@ -44,21 +44,24 @@ class SubcloudAlarmController(object):
context = restcomm.extract_context_from_environ() context = restcomm.extract_context_from_environ()
alarms = db_api.subcloud_alarms_get_all(context) alarms = db_api.subcloud_alarms_get_all(context)
for alarm in alarms: for alarm in alarms:
alarm_dict = {'region_name': alarm['name'], alarm_dict = {
'uuid': alarm['uuid'], "region_name": alarm["name"],
'critical_alarms': alarm['critical_alarms'], "uuid": alarm["uuid"],
'major_alarms': alarm['major_alarms'], "critical_alarms": alarm["critical_alarms"],
'minor_alarms': alarm['minor_alarms'], "major_alarms": alarm["major_alarms"],
'warnings': alarm['warnings'], "minor_alarms": alarm["minor_alarms"],
'cloud_status': alarm['cloud_status']} "warnings": alarm["warnings"],
"cloud_status": alarm["cloud_status"],
}
summary.append(alarm_dict) summary.append(alarm_dict)
return {'alarm_summary': summary} return {"alarm_summary": summary}
@index.when(method='GET', template='json') @index.when(method="GET", template="json")
def get(self): def get(self):
"""Get List of alarm summarys """Get List of alarm summarys"""
policy.authorize(
""" alarm_manager_policy.POLICY_ROOT % "get",
policy.authorize(alarm_manager_policy.POLICY_ROOT % "get", {}, {},
restcomm.extract_credentials_for_policy()) restcomm.extract_credentials_for_policy(),
)
return self._get_alarm_aggregates() return self._get_alarm_aggregates()

View File

@@ -34,31 +34,30 @@ class NotificationsController(object):
super(NotificationsController, self).__init__() super(NotificationsController, self).__init__()
self.audit_rpc_client = audit_rpc_client.ManagerAuditClient() self.audit_rpc_client = audit_rpc_client.ManagerAuditClient()
@expose(generic=True, template='json') @expose(generic=True, template="json")
def index(self): def index(self):
# Route the request to specific methods with parameters # Route the request to specific methods with parameters
pass pass
@index.when(method='POST', template='json') @index.when(method="POST", template="json")
def post(self): def post(self):
if 'events' not in request.json_body: if "events" not in request.json_body:
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(httpclient.BAD_REQUEST, "Missing required notification events")
"Missing required notification events")
events = request.json_body['events'] events = request.json_body["events"]
if 'platform-upgrade-completed' in events: if "platform-upgrade-completed" in events:
# We're being notified that a platform upgrade has completed, # We're being notified that a platform upgrade has completed,
# so we want to trigger a load audit of all subclouds on the # so we want to trigger a load audit of all subclouds on the
# next audit cycle. # next audit cycle.
context = restcomm.extract_context_from_environ() context = restcomm.extract_context_from_environ()
self.audit_rpc_client.trigger_load_audit(context) self.audit_rpc_client.trigger_load_audit(context)
if 'k8s-upgrade-completed' in events: if "k8s-upgrade-completed" in events:
# We're being notified that a kubernetes upgrade has completed, # We're being notified that a kubernetes upgrade has completed,
# so we want to trigger a kubernetes audit of all subclouds on # so we want to trigger a kubernetes audit of all subclouds on
# the next audit cycle. # the next audit cycle.
context = restcomm.extract_context_from_environ() context = restcomm.extract_context_from_environ()
self.audit_rpc_client.trigger_kubernetes_audit(context) self.audit_rpc_client.trigger_kubernetes_audit(context)
if 'kube-rootca-update-completed' in events: if "kube-rootca-update-completed" in events:
# We're being notified that a kube rootca update has completed, so # We're being notified that a kube rootca update has completed, so
# we want to trigger a kube rootca update audit of all subclouds on # we want to trigger a kube rootca update audit of all subclouds on
# the next audit cycle. # the next audit cycle.

View File

@@ -17,8 +17,9 @@ from pecan import request
from dccommon import consts as dccommon_consts from dccommon import consts as dccommon_consts
from dccommon.drivers.openstack.sysinv_v1 import SysinvClient from dccommon.drivers.openstack.sysinv_v1 import SysinvClient
from dcmanager.api.controllers import restcomm from dcmanager.api.controllers import restcomm
from dcmanager.api.policies import peer_group_association as \ from dcmanager.api.policies import (
peer_group_association_policy peer_group_association as peer_group_association_policy,
)
from dcmanager.api import policy from dcmanager.api import policy
from dcmanager.common import consts from dcmanager.common import consts
from dcmanager.common import exceptions as exception from dcmanager.common import exceptions as exception
@@ -33,12 +34,13 @@ LOG = logging.getLogger(__name__)
MIN_PEER_GROUP_ASSOCIATION_PRIORITY = 1 MIN_PEER_GROUP_ASSOCIATION_PRIORITY = 1
MAX_PEER_GROUP_ASSOCIATION_PRIORITY = 65536 MAX_PEER_GROUP_ASSOCIATION_PRIORITY = 65536
ASSOCIATION_SYNC_STATUS_LIST = \ ASSOCIATION_SYNC_STATUS_LIST = [
[consts.ASSOCIATION_SYNC_STATUS_SYNCING, consts.ASSOCIATION_SYNC_STATUS_SYNCING,
consts.ASSOCIATION_SYNC_STATUS_IN_SYNC, consts.ASSOCIATION_SYNC_STATUS_IN_SYNC,
consts.ASSOCIATION_SYNC_STATUS_OUT_OF_SYNC, consts.ASSOCIATION_SYNC_STATUS_OUT_OF_SYNC,
consts.ASSOCIATION_SYNC_STATUS_FAILED, consts.ASSOCIATION_SYNC_STATUS_FAILED,
consts.ASSOCIATION_SYNC_STATUS_UNKNOWN] consts.ASSOCIATION_SYNC_STATUS_UNKNOWN,
]
class PeerGroupAssociationsController(restcomm.GenericPathController): class PeerGroupAssociationsController(restcomm.GenericPathController):
@@ -47,7 +49,7 @@ class PeerGroupAssociationsController(restcomm.GenericPathController):
super(PeerGroupAssociationsController, self).__init__() super(PeerGroupAssociationsController, self).__init__()
self.rpc_client = rpc_client.ManagerClient() self.rpc_client = rpc_client.ManagerClient()
@expose(generic=True, template='json') @expose(generic=True, template="json")
def index(self): def index(self):
# Route the request to specific methods with parameters # Route the request to specific methods with parameters
pass pass
@@ -58,12 +60,13 @@ class PeerGroupAssociationsController(restcomm.GenericPathController):
for association in associations: for association in associations:
association_dict = db_api.peer_group_association_db_model_to_dict( association_dict = db_api.peer_group_association_db_model_to_dict(
association) association
)
# Remove the sync_message from the list response # Remove the sync_message from the list response
association_dict.pop('sync-message', None) association_dict.pop("sync-message", None)
association_list.append(association_dict) association_list.append(association_dict)
result = {'peer_group_associations': association_list} result = {"peer_group_associations": association_list}
return result return result
@staticmethod @staticmethod
@@ -71,12 +74,12 @@ class PeerGroupAssociationsController(restcomm.GenericPathController):
try: try:
payload = json.loads(request.body) payload = json.loads(request.body)
except Exception: except Exception:
error_msg = 'Request body is malformed.' error_msg = "Request body is malformed."
LOG.exception(error_msg) LOG.exception(error_msg)
pecan.abort(400, _(error_msg)) pecan.abort(400, _(error_msg))
if not isinstance(payload, dict): if not isinstance(payload, dict):
pecan.abort(400, _('Invalid request body format')) pecan.abort(400, _("Invalid request body format"))
return payload return payload
def _validate_peer_group_leader_id(self, system_leader_id): def _validate_peer_group_leader_id(self, system_leader_id):
@@ -84,33 +87,37 @@ class PeerGroupAssociationsController(restcomm.GenericPathController):
sysinv_client = SysinvClient( sysinv_client = SysinvClient(
dccommon_consts.DEFAULT_REGION_NAME, dccommon_consts.DEFAULT_REGION_NAME,
ks_client.session, ks_client.session,
endpoint=ks_client.endpoint_cache.get_endpoint('sysinv')) endpoint=ks_client.endpoint_cache.get_endpoint("sysinv"),
)
system = sysinv_client.get_system() system = sysinv_client.get_system()
return True if system.uuid == system_leader_id else False return True if system.uuid == system_leader_id else False
@index.when(method='GET', template='json') @index.when(method="GET", template="json")
def get(self, association_id=None): def get(self, association_id=None):
"""Get details about peer group association. """Get details about peer group association.
:param association_id: ID of peer group association :param association_id: ID of peer group association
""" """
policy.authorize(peer_group_association_policy.POLICY_ROOT % "get", {}, policy.authorize(
restcomm.extract_credentials_for_policy()) peer_group_association_policy.POLICY_ROOT % "get",
{},
restcomm.extract_credentials_for_policy(),
)
context = restcomm.extract_context_from_environ() context = restcomm.extract_context_from_environ()
if association_id is None: if association_id is None:
# List of peer group association requested # List of peer group association requested
return self._get_peer_group_association_list(context) return self._get_peer_group_association_list(context)
elif not association_id.isdigit(): elif not association_id.isdigit():
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(
_('Peer Group Association ID must be an integer')) httpclient.BAD_REQUEST,
_("Peer Group Association ID must be an integer"),
)
try: try:
association = db_api.peer_group_association_get(context, association = db_api.peer_group_association_get(context, association_id)
association_id)
except exception.PeerGroupAssociationNotFound: except exception.PeerGroupAssociationNotFound:
pecan.abort(httpclient.NOT_FOUND, pecan.abort(httpclient.NOT_FOUND, _("Peer Group Association not found"))
_('Peer Group Association not found'))
return db_api.peer_group_association_db_model_to_dict(association) return db_api.peer_group_association_db_model_to_dict(association)
@@ -118,12 +125,15 @@ class PeerGroupAssociationsController(restcomm.GenericPathController):
try: try:
db_api.subcloud_peer_group_get(context, peer_group_id) db_api.subcloud_peer_group_get(context, peer_group_id)
except exception.SubcloudPeerGroupNotFound: except exception.SubcloudPeerGroupNotFound:
LOG.debug("Subcloud Peer Group Not Found, peer group id: %s" LOG.debug(
% peer_group_id) "Subcloud Peer Group Not Found, peer group id: %s" % peer_group_id
)
return False return False
except Exception as e: except Exception as e:
LOG.warning("Get Subcloud Peer Group failed: %s; peer_group_id: %s" LOG.warning(
% (e, peer_group_id)) "Get Subcloud Peer Group failed: %s; peer_group_id: %s"
% (e, peer_group_id)
)
return False return False
return True return True
@@ -131,12 +141,12 @@ class PeerGroupAssociationsController(restcomm.GenericPathController):
try: try:
db_api.system_peer_get(context, system_peer_id) db_api.system_peer_get(context, system_peer_id)
except exception.SystemPeerNotFound: except exception.SystemPeerNotFound:
LOG.debug("System Peer Not Found, system peer id: %s" LOG.debug("System Peer Not Found, system peer id: %s" % system_peer_id)
% system_peer_id)
return False return False
except Exception as e: except Exception as e:
LOG.warning("Get System Peer failed: %s; system_peer_id: %s" LOG.warning(
% (e, system_peer_id)) "Get System Peer failed: %s; system_peer_id: %s" % (e, system_peer_id)
)
return False return False
return True return True
@@ -145,14 +155,17 @@ class PeerGroupAssociationsController(restcomm.GenericPathController):
# Check the value is an integer # Check the value is an integer
val = int(peer_group_priority) val = int(peer_group_priority)
except ValueError: except ValueError:
LOG.debug("Peer Group Priority is not Integer: %s" LOG.debug("Peer Group Priority is not Integer: %s" % peer_group_priority)
% peer_group_priority)
return False return False
# Less than min or greater than max priority is not supported. # Less than min or greater than max priority is not supported.
if val < MIN_PEER_GROUP_ASSOCIATION_PRIORITY or \ if (
val > MAX_PEER_GROUP_ASSOCIATION_PRIORITY: val < MIN_PEER_GROUP_ASSOCIATION_PRIORITY
LOG.debug("Invalid Peer Group Priority out of support range: %s" or val > MAX_PEER_GROUP_ASSOCIATION_PRIORITY
% peer_group_priority) ):
LOG.debug(
"Invalid Peer Group Priority out of support range: %s"
% peer_group_priority
)
return False return False
return True return True
@@ -162,80 +175,108 @@ class PeerGroupAssociationsController(restcomm.GenericPathController):
return False return False
return True return True
@index.when(method='POST', template='json') @index.when(method="POST", template="json")
def post(self): def post(self):
"""Create a new peer group association.""" """Create a new peer group association."""
policy.authorize(peer_group_association_policy.POLICY_ROOT % policy.authorize(
"create", {}, peer_group_association_policy.POLICY_ROOT % "create",
restcomm.extract_credentials_for_policy()) {},
restcomm.extract_credentials_for_policy(),
)
context = restcomm.extract_context_from_environ() context = restcomm.extract_context_from_environ()
payload = self._get_payload(request) payload = self._get_payload(request)
if not payload: if not payload:
pecan.abort(httpclient.BAD_REQUEST, _('Body required')) pecan.abort(httpclient.BAD_REQUEST, _("Body required"))
# Validate payload # Validate payload
peer_group_id = payload.get('peer_group_id') peer_group_id = payload.get("peer_group_id")
if not self._validate_peer_group_id(context, peer_group_id): if not self._validate_peer_group_id(context, peer_group_id):
pecan.abort(httpclient.BAD_REQUEST, _('Invalid peer_group_id')) pecan.abort(httpclient.BAD_REQUEST, _("Invalid peer_group_id"))
system_peer_id = payload.get('system_peer_id') system_peer_id = payload.get("system_peer_id")
if not self._validate_system_peer_id(context, system_peer_id): if not self._validate_system_peer_id(context, system_peer_id):
pecan.abort(httpclient.BAD_REQUEST, _('Invalid system_peer_id')) pecan.abort(httpclient.BAD_REQUEST, _("Invalid system_peer_id"))
peer_group_priority = payload.get('peer_group_priority') peer_group_priority = payload.get("peer_group_priority")
peer_group = db_api.subcloud_peer_group_get(context, peer_group_id) peer_group = db_api.subcloud_peer_group_get(context, peer_group_id)
if peer_group_priority is not None and not \ if peer_group_priority is not None and not self._validate_peer_group_priority(
self._validate_peer_group_priority(peer_group_priority): peer_group_priority
pecan.abort(httpclient.BAD_REQUEST, ):
_('Invalid peer_group_priority')) pecan.abort(httpclient.BAD_REQUEST, _("Invalid peer_group_priority"))
if (peer_group.group_priority == consts.PEER_GROUP_PRIMARY_PRIORITY and if (
peer_group_priority is None) or ( peer_group.group_priority == consts.PEER_GROUP_PRIMARY_PRIORITY
peer_group.group_priority > consts.PEER_GROUP_PRIMARY_PRIORITY and and peer_group_priority is None
peer_group_priority is not None): ) or (
pecan.abort(httpclient.BAD_REQUEST, peer_group.group_priority > consts.PEER_GROUP_PRIMARY_PRIORITY
_('Peer Group Association create is not allowed when ' and peer_group_priority is not None
'the subcloud peer group priority is greater than 0 ' ):
'and it is required when the subcloud peer group ' pecan.abort(
'priority is 0.')) httpclient.BAD_REQUEST,
_(
"Peer Group Association create is not allowed when the subcloud "
"peer group priority is greater than 0 and it is required when "
"the subcloud peer group priority is 0."
),
)
is_primary = peer_group.group_priority == consts.PEER_GROUP_PRIMARY_PRIORITY is_primary = peer_group.group_priority == consts.PEER_GROUP_PRIMARY_PRIORITY
# only one combination of peer_group_id + system_peer_id can exists # only one combination of peer_group_id + system_peer_id can exists
association = None association = None
try: try:
association = db_api.\ association = (
peer_group_association_get_by_peer_group_and_system_peer_id( db_api.peer_group_association_get_by_peer_group_and_system_peer_id(
context, context, peer_group_id, system_peer_id
peer_group_id, )
system_peer_id) )
except exception.PeerGroupAssociationCombinationNotFound: except exception.PeerGroupAssociationCombinationNotFound:
# This is a normal scenario, no need to log or raise an error # This is a normal scenario, no need to log or raise an error
pass pass
except Exception as e: except Exception as e:
LOG.warning("Peer Group Association get failed: %s;" LOG.warning(
"peer_group_id: %s, system_peer_id: %s" "Peer Group Association get failed: %s;"
% (e, peer_group_id, system_peer_id)) "peer_group_id: %s, system_peer_id: %s"
pecan.abort(httpclient.INTERNAL_SERVER_ERROR, % (e, peer_group_id, system_peer_id)
_('peer_group_association_get_by_peer_group_and_' )
'system_peer_id failed: %s' % e)) pecan.abort(
httpclient.INTERNAL_SERVER_ERROR,
_(
"peer_group_association_get_by_peer_group_and_"
"system_peer_id failed: %s" % e
),
)
if association: if association:
LOG.warning("Failed to create Peer group association, association " LOG.warning(
"with peer_group_id:[%s],system_peer_id:[%s] " "Failed to create Peer group association, association with "
"already exists" % (peer_group_id, system_peer_id)) "peer_group_id:[%s],system_peer_id:[%s] already exists"
pecan.abort(httpclient.BAD_REQUEST, % (peer_group_id, system_peer_id)
_('A Peer group association with same peer_group_id, ' )
'system_peer_id already exists')) pecan.abort(
httpclient.BAD_REQUEST,
_(
"A Peer group association with same peer_group_id, "
"system_peer_id already exists"
),
)
# Create the peer group association # Create the peer group association
try: try:
association_type = consts.ASSOCIATION_TYPE_PRIMARY if is_primary \ association_type = (
consts.ASSOCIATION_TYPE_PRIMARY
if is_primary
else consts.ASSOCIATION_TYPE_NON_PRIMARY else consts.ASSOCIATION_TYPE_NON_PRIMARY
)
association = db_api.peer_group_association_create( association = db_api.peer_group_association_create(
context, peer_group_id, system_peer_id, peer_group_priority, context,
association_type, consts.ASSOCIATION_SYNC_STATUS_SYNCING) peer_group_id,
system_peer_id,
peer_group_priority,
association_type,
consts.ASSOCIATION_SYNC_STATUS_SYNCING,
)
if is_primary: if is_primary:
# Sync the subcloud peer group to peer site # Sync the subcloud peer group to peer site
@@ -247,107 +288,133 @@ class PeerGroupAssociationsController(restcomm.GenericPathController):
pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value) pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value)
except Exception as e: except Exception as e:
LOG.exception(e) LOG.exception(e)
pecan.abort(httpclient.INTERNAL_SERVER_ERROR, pecan.abort(
_('Unable to create peer group association')) httpclient.INTERNAL_SERVER_ERROR,
_("Unable to create peer group association"),
)
def _sync_association(self, context, association, is_non_primary): def _sync_association(self, context, association, is_non_primary):
if is_non_primary: if is_non_primary:
self.rpc_client.peer_monitor_notify(context) self.rpc_client.peer_monitor_notify(context)
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(
_('Peer Group Association sync is not allowed ' httpclient.BAD_REQUEST,
'when the association type is non-primary. But the ' _(
'peer monitor notify was triggered.')) "Peer Group Association sync is not allowed when the association "
"type is non-primary. But the peer monitor notify was triggered."
),
)
else: else:
peer_group = db_api.subcloud_peer_group_get( peer_group = db_api.subcloud_peer_group_get(
context, association.peer_group_id) context, association.peer_group_id
if not self._validate_peer_group_leader_id(peer_group. )
system_leader_id): if not self._validate_peer_group_leader_id(peer_group.system_leader_id):
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(
_('Peer Group Association sync is not allowed when ' httpclient.BAD_REQUEST,
'the subcloud peer group system_leader_id is not ' _(
'the current system controller UUID.')) "Peer Group Association sync is not allowed when "
"the subcloud peer group system_leader_id is not "
"the current system controller UUID."
),
)
try: try:
# Sync the subcloud peer group to peer site # Sync the subcloud peer group to peer site
self.rpc_client.sync_subcloud_peer_group(context, self.rpc_client.sync_subcloud_peer_group(context, association.id)
association.id)
association = db_api.peer_group_association_update( association = db_api.peer_group_association_update(
context, id=association.id, context,
id=association.id,
sync_status=consts.ASSOCIATION_SYNC_STATUS_SYNCING, sync_status=consts.ASSOCIATION_SYNC_STATUS_SYNCING,
sync_message='None') sync_message="None",
return db_api.peer_group_association_db_model_to_dict( )
association) return db_api.peer_group_association_db_model_to_dict(association)
except RemoteError as e: except RemoteError as e:
pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value) pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value)
except Exception as e: except Exception as e:
# additional exceptions. # additional exceptions.
LOG.exception(e) LOG.exception(e)
pecan.abort(httpclient.INTERNAL_SERVER_ERROR, pecan.abort(
_('Unable to sync peer group association')) httpclient.INTERNAL_SERVER_ERROR,
_("Unable to sync peer group association"),
)
def _update_association(self, context, association, is_non_primary): def _update_association(self, context, association, is_non_primary):
payload = self._get_payload(request) payload = self._get_payload(request)
if not payload: if not payload:
pecan.abort(httpclient.BAD_REQUEST, _('Body required')) pecan.abort(httpclient.BAD_REQUEST, _("Body required"))
peer_group_priority = payload.get('peer_group_priority') peer_group_priority = payload.get("peer_group_priority")
sync_status = payload.get('sync_status') sync_status = payload.get("sync_status")
# Check value is not None or empty before calling validate # Check value is not None or empty before calling validate
if not (peer_group_priority is not None or sync_status): if not (peer_group_priority is not None or sync_status):
pecan.abort(httpclient.BAD_REQUEST, _('nothing to update')) pecan.abort(httpclient.BAD_REQUEST, _("nothing to update"))
elif peer_group_priority is not None and sync_status: elif peer_group_priority is not None and sync_status:
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(
_('peer_group_priority and sync_status cannot be ' httpclient.BAD_REQUEST,
'updated at the same time.')) _(
"peer_group_priority and sync_status cannot be "
"updated at the same time."
),
)
if peer_group_priority is not None: if peer_group_priority is not None:
if not self._validate_peer_group_priority(peer_group_priority): if not self._validate_peer_group_priority(peer_group_priority):
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(httpclient.BAD_REQUEST, _("Invalid peer_group_priority"))
_('Invalid peer_group_priority'))
if is_non_primary: if is_non_primary:
self.rpc_client.peer_monitor_notify(context) self.rpc_client.peer_monitor_notify(context)
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(
_('Peer Group Association peer_group_priority is ' httpclient.BAD_REQUEST,
'not allowed to update when the association type ' _(
'is non-primary.')) "Peer Group Association peer_group_priority is not allowed to "
"update when the association type is non-primary."
),
)
else: else:
db_api.peer_group_association_update( db_api.peer_group_association_update(
context, id=association.id, context, id=association.id, peer_group_priority=peer_group_priority
peer_group_priority=peer_group_priority) )
if sync_status: if sync_status:
if not self._validate_sync_status(sync_status): if not self._validate_sync_status(sync_status):
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(httpclient.BAD_REQUEST, _("Invalid sync_status"))
_('Invalid sync_status'))
if not is_non_primary: if not is_non_primary:
self.rpc_client.peer_monitor_notify(context) self.rpc_client.peer_monitor_notify(context)
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(
_('Peer Group Association sync_status is not ' httpclient.BAD_REQUEST,
'allowed to update when the association type is ' _(
'primary.')) "Peer Group Association sync_status is not allowed to update "
"when the association type is primary."
),
)
else: else:
sync_message = 'Primary association sync to current site ' + \ sync_message = (
'failed.' if sync_status == \ "Primary association sync to current site failed."
consts.ASSOCIATION_SYNC_STATUS_FAILED else 'None' if sync_status == consts.ASSOCIATION_SYNC_STATUS_FAILED
else "None"
)
association = db_api.peer_group_association_update( association = db_api.peer_group_association_update(
context, id=association.id, sync_status=sync_status, context,
sync_message=sync_message) id=association.id,
sync_status=sync_status,
sync_message=sync_message,
)
self.rpc_client.peer_monitor_notify(context) self.rpc_client.peer_monitor_notify(context)
return db_api.peer_group_association_db_model_to_dict( return db_api.peer_group_association_db_model_to_dict(association)
association)
try: try:
# Ask dcmanager-manager to update the subcloud peer group priority # Ask dcmanager-manager to update the subcloud peer group priority
# to peer site. It will do the real work... # to peer site. It will do the real work...
return self.rpc_client.sync_subcloud_peer_group_only( return self.rpc_client.sync_subcloud_peer_group_only(
context, association.id) context, association.id
)
except RemoteError as e: except RemoteError as e:
pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value) pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value)
except Exception as e: except Exception as e:
# additional exceptions. # additional exceptions.
LOG.exception(e) LOG.exception(e)
pecan.abort(httpclient.INTERNAL_SERVER_ERROR, pecan.abort(
_('Unable to update peer group association')) httpclient.INTERNAL_SERVER_ERROR,
_("Unable to update peer group association"),
)
@index.when(method='PATCH', template='json') @index.when(method="PATCH", template="json")
def patch(self, association_id, sync=False): def patch(self, association_id, sync=False):
"""Update a peer group association. """Update a peer group association.
@@ -355,70 +422,78 @@ class PeerGroupAssociationsController(restcomm.GenericPathController):
:param sync: sync action that sync the peer group :param sync: sync action that sync the peer group
""" """
policy.authorize(peer_group_association_policy.POLICY_ROOT % "modify", policy.authorize(
{}, restcomm.extract_credentials_for_policy()) peer_group_association_policy.POLICY_ROOT % "modify",
{},
restcomm.extract_credentials_for_policy(),
)
context = restcomm.extract_context_from_environ() context = restcomm.extract_context_from_environ()
if association_id is None: if association_id is None:
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(httpclient.BAD_REQUEST, _("Peer Group Association ID required"))
_('Peer Group Association ID required'))
elif not association_id.isdigit(): elif not association_id.isdigit():
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(
_('Peer Group Association ID must be an integer')) httpclient.BAD_REQUEST,
_("Peer Group Association ID must be an integer"),
)
try: try:
association = db_api.peer_group_association_get(context, association = db_api.peer_group_association_get(context, association_id)
association_id)
except exception.PeerGroupAssociationNotFound: except exception.PeerGroupAssociationNotFound:
pecan.abort(httpclient.NOT_FOUND, pecan.abort(httpclient.NOT_FOUND, _("Peer Group Association not found"))
_('Peer Group Association not found'))
is_non_primary = association.association_type == consts.\ is_non_primary = (
ASSOCIATION_TYPE_NON_PRIMARY association.association_type == consts.ASSOCIATION_TYPE_NON_PRIMARY
)
if sync: if sync:
return self._sync_association(context, association, is_non_primary) return self._sync_association(context, association, is_non_primary)
else: else:
return self._update_association(context, association, is_non_primary) return self._update_association(context, association, is_non_primary)
@index.when(method='delete', template='json') @index.when(method="delete", template="json")
def delete(self, association_id): def delete(self, association_id):
"""Delete the peer group association. """Delete the peer group association.
:param association_id: ID of peer group association to delete :param association_id: ID of peer group association to delete
""" """
policy.authorize(peer_group_association_policy.POLICY_ROOT % "delete", policy.authorize(
{}, restcomm.extract_credentials_for_policy()) peer_group_association_policy.POLICY_ROOT % "delete",
{},
restcomm.extract_credentials_for_policy(),
)
context = restcomm.extract_context_from_environ() context = restcomm.extract_context_from_environ()
if association_id is None: if association_id is None:
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(httpclient.BAD_REQUEST, _("Peer Group Association ID required"))
_('Peer Group Association ID required'))
# Validate the ID # Validate the ID
if not association_id.isdigit(): if not association_id.isdigit():
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(
_('Peer Group Association ID must be an integer')) httpclient.BAD_REQUEST,
_("Peer Group Association ID must be an integer"),
)
try: try:
association = db_api.peer_group_association_get(context, association = db_api.peer_group_association_get(context, association_id)
association_id) is_non_primary = (
is_non_primary = association.association_type == consts.\ association.association_type == consts.ASSOCIATION_TYPE_NON_PRIMARY
ASSOCIATION_TYPE_NON_PRIMARY )
if is_non_primary: if is_non_primary:
result = db_api.peer_group_association_destroy(context, result = db_api.peer_group_association_destroy(context, association_id)
association_id)
self.rpc_client.peer_monitor_notify(context) self.rpc_client.peer_monitor_notify(context)
return result return result
else: else:
# Ask system-peer-manager to delete the association. # Ask system-peer-manager to delete the association.
# It will do all the real work... # It will do all the real work...
return self.rpc_client.delete_peer_group_association( return self.rpc_client.delete_peer_group_association(
context, association_id) context, association_id
)
except exception.PeerGroupAssociationNotFound: except exception.PeerGroupAssociationNotFound:
pecan.abort(httpclient.NOT_FOUND, pecan.abort(httpclient.NOT_FOUND, _("Peer Group Association not found"))
_('Peer Group Association not found'))
except RemoteError as e: except RemoteError as e:
pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value) pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value)
except Exception as e: except Exception as e:
LOG.exception(e) LOG.exception(e)
pecan.abort(httpclient.INTERNAL_SERVER_ERROR, pecan.abort(
_('Unable to delete peer group association')) httpclient.INTERNAL_SERVER_ERROR,
_("Unable to delete peer group association"),
)

View File

@@ -13,8 +13,9 @@ import pecan
from dcmanager.api.controllers import restcomm from dcmanager.api.controllers import restcomm
from dcmanager.api.controllers.v1.subclouds import SubcloudsController from dcmanager.api.controllers.v1.subclouds import SubcloudsController
from dcmanager.api.policies import phased_subcloud_deploy as \ from dcmanager.api.policies import (
phased_subcloud_deploy_policy phased_subcloud_deploy as phased_subcloud_deploy_policy,
)
from dcmanager.api import policy from dcmanager.api import policy
from dcmanager.common import consts from dcmanager.common import consts
from dcmanager.common.context import RequestContext from dcmanager.common.context import RequestContext
@@ -27,7 +28,7 @@ from dcmanager.db.sqlalchemy import models
from dcmanager.rpc import client as rpc_client from dcmanager.rpc import client as rpc_client
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
LOCK_NAME = 'PhasedSubcloudDeployController' LOCK_NAME = "PhasedSubcloudDeployController"
INSTALL = consts.DEPLOY_PHASE_INSTALL INSTALL = consts.DEPLOY_PHASE_INSTALL
BOOTSTRAP = consts.DEPLOY_PHASE_BOOTSTRAP BOOTSTRAP = consts.DEPLOY_PHASE_BOOTSTRAP
@@ -39,41 +40,26 @@ ENROLL = consts.DEPLOY_PHASE_ENROLL
SUBCLOUD_CREATE_REQUIRED_PARAMETERS = ( SUBCLOUD_CREATE_REQUIRED_PARAMETERS = (
consts.BOOTSTRAP_VALUES, consts.BOOTSTRAP_VALUES,
consts.BOOTSTRAP_ADDRESS consts.BOOTSTRAP_ADDRESS,
) )
# The consts.DEPLOY_CONFIG is missing here because it's handled differently # The consts.DEPLOY_CONFIG is missing here because it's handled differently
# by the upload_deploy_config_file() function # by the upload_deploy_config_file() function
SUBCLOUD_CREATE_GET_FILE_CONTENTS = ( SUBCLOUD_CREATE_GET_FILE_CONTENTS = (
consts.BOOTSTRAP_VALUES, consts.BOOTSTRAP_VALUES,
consts.INSTALL_VALUES, consts.INSTALL_VALUES,
) )
SUBCLOUD_INSTALL_GET_FILE_CONTENTS = (consts.INSTALL_VALUES,)
SUBCLOUD_INSTALL_GET_FILE_CONTENTS = ( SUBCLOUD_BOOTSTRAP_GET_FILE_CONTENTS = (consts.BOOTSTRAP_VALUES,)
consts.INSTALL_VALUES, SUBCLOUD_ENROLL_GET_FILE_CONTENTS = (consts.BOOTSTRAP_VALUES, consts.INSTALL_VALUES)
) SUBCLOUD_CONFIG_GET_FILE_CONTENTS = (consts.DEPLOY_CONFIG,)
SUBCLOUD_BOOTSTRAP_GET_FILE_CONTENTS = (
consts.BOOTSTRAP_VALUES,
)
SUBCLOUD_ENROLL_GET_FILE_CONTENTS = (
consts.BOOTSTRAP_VALUES,
consts.INSTALL_VALUES
)
SUBCLOUD_CONFIG_GET_FILE_CONTENTS = (
consts.DEPLOY_CONFIG,
)
VALID_STATES_FOR_DEPLOY_INSTALL = ( VALID_STATES_FOR_DEPLOY_INSTALL = (
consts.DEPLOY_STATE_CREATED, consts.DEPLOY_STATE_CREATED,
consts.DEPLOY_STATE_PRE_INSTALL_FAILED, consts.DEPLOY_STATE_PRE_INSTALL_FAILED,
consts.DEPLOY_STATE_INSTALL_FAILED, consts.DEPLOY_STATE_INSTALL_FAILED,
consts.DEPLOY_STATE_INSTALLED, consts.DEPLOY_STATE_INSTALLED,
consts.DEPLOY_STATE_INSTALL_ABORTED consts.DEPLOY_STATE_INSTALL_ABORTED,
) )
VALID_STATES_FOR_DEPLOY_BOOTSTRAP = [ VALID_STATES_FOR_DEPLOY_BOOTSTRAP = [
consts.DEPLOY_STATE_INSTALLED, consts.DEPLOY_STATE_INSTALLED,
consts.DEPLOY_STATE_PRE_BOOTSTRAP_FAILED, consts.DEPLOY_STATE_PRE_BOOTSTRAP_FAILED,
@@ -82,44 +68,37 @@ VALID_STATES_FOR_DEPLOY_BOOTSTRAP = [
consts.DEPLOY_STATE_BOOTSTRAPPED, consts.DEPLOY_STATE_BOOTSTRAPPED,
# The subcloud can be installed manually (without remote install) so we need # The subcloud can be installed manually (without remote install) so we need
# to allow the bootstrap operation when the state == DEPLOY_STATE_CREATED # to allow the bootstrap operation when the state == DEPLOY_STATE_CREATED
consts.DEPLOY_STATE_CREATED consts.DEPLOY_STATE_CREATED,
] ]
VALID_STATES_FOR_DEPLOY_CONFIG = ( VALID_STATES_FOR_DEPLOY_CONFIG = (
consts.DEPLOY_STATE_DONE, consts.DEPLOY_STATE_DONE,
consts.DEPLOY_STATE_PRE_CONFIG_FAILED, consts.DEPLOY_STATE_PRE_CONFIG_FAILED,
consts.DEPLOY_STATE_CONFIG_FAILED, consts.DEPLOY_STATE_CONFIG_FAILED,
consts.DEPLOY_STATE_BOOTSTRAPPED, consts.DEPLOY_STATE_BOOTSTRAPPED,
consts.DEPLOY_STATE_CONFIG_ABORTED consts.DEPLOY_STATE_CONFIG_ABORTED,
) )
VALID_STATES_FOR_DEPLOY_ABORT = ( VALID_STATES_FOR_DEPLOY_ABORT = (
consts.DEPLOY_STATE_INSTALLING, consts.DEPLOY_STATE_INSTALLING,
consts.DEPLOY_STATE_BOOTSTRAPPING, consts.DEPLOY_STATE_BOOTSTRAPPING,
consts.DEPLOY_STATE_CONFIGURING consts.DEPLOY_STATE_CONFIGURING,
) )
VALID_STATES_FOR_DEPLOY_ENROLL = ( VALID_STATES_FOR_DEPLOY_ENROLL = (
consts.DEPLOY_STATE_CREATED, consts.DEPLOY_STATE_CREATED,
consts.DEPLOY_STATE_ENROLL_FAILED, consts.DEPLOY_STATE_ENROLL_FAILED,
consts.DEPLOY_STATE_ENROLLED, consts.DEPLOY_STATE_ENROLLED,
consts.DEPLOY_STATE_PRE_ENROLL, consts.DEPLOY_STATE_PRE_ENROLL,
consts.DEPLOY_STATE_INIT_ENROLL_FAILED consts.DEPLOY_STATE_INIT_ENROLL_FAILED,
) )
FILES_FOR_RESUME_INSTALL = \ FILES_FOR_RESUME_INSTALL = (
SUBCLOUD_INSTALL_GET_FILE_CONTENTS + \ SUBCLOUD_INSTALL_GET_FILE_CONTENTS
SUBCLOUD_BOOTSTRAP_GET_FILE_CONTENTS + \ + SUBCLOUD_BOOTSTRAP_GET_FILE_CONTENTS
SUBCLOUD_CONFIG_GET_FILE_CONTENTS + SUBCLOUD_CONFIG_GET_FILE_CONTENTS
)
FILES_FOR_RESUME_BOOTSTRAP = (
FILES_FOR_RESUME_BOOTSTRAP = \ SUBCLOUD_BOOTSTRAP_GET_FILE_CONTENTS + SUBCLOUD_CONFIG_GET_FILE_CONTENTS
SUBCLOUD_BOOTSTRAP_GET_FILE_CONTENTS + \ )
SUBCLOUD_CONFIG_GET_FILE_CONTENTS
FILES_FOR_RESUME_CONFIG = SUBCLOUD_CONFIG_GET_FILE_CONTENTS FILES_FOR_RESUME_CONFIG = SUBCLOUD_CONFIG_GET_FILE_CONTENTS
RESUMABLE_STATES = { RESUMABLE_STATES = {
consts.DEPLOY_STATE_CREATED: [INSTALL, BOOTSTRAP, CONFIG], consts.DEPLOY_STATE_CREATED: [INSTALL, BOOTSTRAP, CONFIG],
consts.DEPLOY_STATE_INSTALLED: [BOOTSTRAP, CONFIG], consts.DEPLOY_STATE_INSTALLED: [BOOTSTRAP, CONFIG],
@@ -132,21 +111,18 @@ RESUMABLE_STATES = {
consts.DEPLOY_STATE_BOOTSTRAP_ABORTED: [BOOTSTRAP, CONFIG], consts.DEPLOY_STATE_BOOTSTRAP_ABORTED: [BOOTSTRAP, CONFIG],
consts.DEPLOY_STATE_PRE_CONFIG_FAILED: [CONFIG], consts.DEPLOY_STATE_PRE_CONFIG_FAILED: [CONFIG],
consts.DEPLOY_STATE_CONFIG_FAILED: [CONFIG], consts.DEPLOY_STATE_CONFIG_FAILED: [CONFIG],
consts.DEPLOY_STATE_CONFIG_ABORTED: [CONFIG] consts.DEPLOY_STATE_CONFIG_ABORTED: [CONFIG],
} }
DEPLOY_PHASES = [INSTALL, BOOTSTRAP, CONFIG] DEPLOY_PHASES = [INSTALL, BOOTSTRAP, CONFIG]
FILES_MAPPING = { FILES_MAPPING = {
INSTALL: SUBCLOUD_INSTALL_GET_FILE_CONTENTS, INSTALL: SUBCLOUD_INSTALL_GET_FILE_CONTENTS,
BOOTSTRAP: SUBCLOUD_BOOTSTRAP_GET_FILE_CONTENTS, BOOTSTRAP: SUBCLOUD_BOOTSTRAP_GET_FILE_CONTENTS,
CONFIG: SUBCLOUD_CONFIG_GET_FILE_CONTENTS CONFIG: SUBCLOUD_CONFIG_GET_FILE_CONTENTS,
} }
RESUME_PREP_UPDATE_STATUS = { RESUME_PREP_UPDATE_STATUS = {
INSTALL: consts.DEPLOY_STATE_PRE_INSTALL, INSTALL: consts.DEPLOY_STATE_PRE_INSTALL,
BOOTSTRAP: consts.DEPLOY_STATE_PRE_BOOTSTRAP, BOOTSTRAP: consts.DEPLOY_STATE_PRE_BOOTSTRAP,
CONFIG: consts.DEPLOY_STATE_PRE_CONFIG CONFIG: consts.DEPLOY_STATE_PRE_CONFIG,
} }
@@ -157,7 +133,7 @@ def get_create_payload(request: pecan.Request) -> dict:
if f in request.POST: if f in request.POST:
file_item = request.POST[f] file_item = request.POST[f]
file_item.file.seek(0, os.SEEK_SET) file_item.file.seek(0, os.SEEK_SET)
data = utils.yaml_safe_load(file_item.file.read().decode('utf8'), f) data = utils.yaml_safe_load(file_item.file.read().decode("utf8"), f)
if f == consts.BOOTSTRAP_VALUES: if f == consts.BOOTSTRAP_VALUES:
payload.update(data) payload.update(data)
else: else:
@@ -175,10 +151,14 @@ class PhasedSubcloudDeployController(object):
self.dcmanager_rpc_client = rpc_client.ManagerClient() self.dcmanager_rpc_client = rpc_client.ManagerClient()
def _deploy_create(self, context: RequestContext, request: pecan.Request): def _deploy_create(self, context: RequestContext, request: pecan.Request):
policy.authorize(phased_subcloud_deploy_policy.POLICY_ROOT % "create", policy.authorize(
{}, restcomm.extract_credentials_for_policy()) phased_subcloud_deploy_policy.POLICY_ROOT % "create",
{},
restcomm.extract_credentials_for_policy(),
)
psd_common.check_required_parameters( psd_common.check_required_parameters(
request, SUBCLOUD_CREATE_REQUIRED_PARAMETERS) request, SUBCLOUD_CREATE_REQUIRED_PARAMETERS
)
payload = get_create_payload(request) payload = get_create_payload(request)
@@ -193,43 +173,54 @@ class PhasedSubcloudDeployController(object):
# Ask dcmanager-manager to create the subcloud. # Ask dcmanager-manager to create the subcloud.
# It will do all the real work... # It will do all the real work...
subcloud_dict = self.dcmanager_rpc_client.subcloud_deploy_create( subcloud_dict = self.dcmanager_rpc_client.subcloud_deploy_create(
context, subcloud.id, payload) context, subcloud.id, payload
)
return subcloud_dict return subcloud_dict
except RemoteError as e: except RemoteError as e:
pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value) pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value)
except Exception: except Exception:
LOG.exception("Unable to create subcloud %s" % payload.get('name')) LOG.exception("Unable to create subcloud %s" % payload.get("name"))
pecan.abort(httpclient.INTERNAL_SERVER_ERROR, pecan.abort(
_('Unable to create subcloud')) httpclient.INTERNAL_SERVER_ERROR, _("Unable to create subcloud")
)
def _deploy_install(self, context: RequestContext, def _deploy_install(
request: pecan.Request, subcloud): self, context: RequestContext, request: pecan.Request, subcloud
):
payload = psd_common.get_request_data( payload = psd_common.get_request_data(
request, subcloud, SUBCLOUD_INSTALL_GET_FILE_CONTENTS) request, subcloud, SUBCLOUD_INSTALL_GET_FILE_CONTENTS
)
if not payload: if not payload:
pecan.abort(400, _('Body required')) pecan.abort(400, _("Body required"))
SubcloudsController.validate_software_deploy_state() SubcloudsController.validate_software_deploy_state()
if subcloud.deploy_status not in VALID_STATES_FOR_DEPLOY_INSTALL: if subcloud.deploy_status not in VALID_STATES_FOR_DEPLOY_INSTALL:
allowed_states_str = ', '.join(VALID_STATES_FOR_DEPLOY_INSTALL) allowed_states_str = ", ".join(VALID_STATES_FOR_DEPLOY_INSTALL)
pecan.abort(400, _('Subcloud deploy status must be either: %s') pecan.abort(
% allowed_states_str) 400, _("Subcloud deploy status must be either: %s") % allowed_states_str
)
initial_deployment = psd_common.is_initial_deployment(subcloud.name) initial_deployment = psd_common.is_initial_deployment(subcloud.name)
if not initial_deployment: if not initial_deployment:
pecan.abort(400, _('The deploy install command can only be used ' pecan.abort(
'during initial deployment.')) 400,
_(
"The deploy install command can only be used "
"during initial deployment."
),
)
unvalidated_sw_version = payload.get('release', subcloud.software_version) unvalidated_sw_version = payload.get("release", subcloud.software_version)
# get_sw_version will simply return back # get_sw_version will simply return back
# the passed unvalidated_sw_version after validating it. # the passed unvalidated_sw_version after validating it.
payload['software_version'] = utils.get_sw_version(unvalidated_sw_version) payload["software_version"] = utils.get_sw_version(unvalidated_sw_version)
psd_common.populate_payload_with_pre_existing_data( psd_common.populate_payload_with_pre_existing_data(
payload, subcloud, SUBCLOUD_INSTALL_GET_FILE_CONTENTS) payload, subcloud, SUBCLOUD_INSTALL_GET_FILE_CONTENTS
)
psd_common.pre_deploy_install(payload, subcloud) psd_common.pre_deploy_install(payload, subcloud)
@@ -238,123 +229,140 @@ class PhasedSubcloudDeployController(object):
# version. Update the deploy status as pre-install. # version. Update the deploy status as pre-install.
self.dcmanager_rpc_client.subcloud_deploy_install( self.dcmanager_rpc_client.subcloud_deploy_install(
context, subcloud.id, payload, initial_deployment=True) context, subcloud.id, payload, initial_deployment=True
)
subcloud_dict = db_api.subcloud_db_model_to_dict(subcloud) subcloud_dict = db_api.subcloud_db_model_to_dict(subcloud)
subcloud_dict['deploy-status'] = consts.DEPLOY_STATE_PRE_INSTALL subcloud_dict["deploy-status"] = consts.DEPLOY_STATE_PRE_INSTALL
subcloud_dict['software-version'] = payload['software_version'] subcloud_dict["software-version"] = payload["software_version"]
return subcloud_dict return subcloud_dict
except RemoteError as e: except RemoteError as e:
pecan.abort(422, e.value) pecan.abort(422, e.value)
except Exception: except Exception:
LOG.exception("Unable to install subcloud %s" % subcloud.name) LOG.exception("Unable to install subcloud %s" % subcloud.name)
pecan.abort(500, _('Unable to install subcloud')) pecan.abort(500, _("Unable to install subcloud"))
def _deploy_bootstrap(self, context: RequestContext, def _deploy_bootstrap(
request: pecan.Request, self, context: RequestContext, request: pecan.Request, subcloud: models.Subcloud
subcloud: models.Subcloud): ):
if subcloud.deploy_status not in VALID_STATES_FOR_DEPLOY_BOOTSTRAP: if subcloud.deploy_status not in VALID_STATES_FOR_DEPLOY_BOOTSTRAP:
valid_states_str = ', '.join(VALID_STATES_FOR_DEPLOY_BOOTSTRAP) valid_states_str = ", ".join(VALID_STATES_FOR_DEPLOY_BOOTSTRAP)
pecan.abort(400, _('Subcloud deploy status must be either: %s') pecan.abort(
% valid_states_str) 400, _("Subcloud deploy status must be either: %s") % valid_states_str
)
has_bootstrap_values = consts.BOOTSTRAP_VALUES in request.POST has_bootstrap_values = consts.BOOTSTRAP_VALUES in request.POST
payload = psd_common.get_request_data( payload = psd_common.get_request_data(
request, subcloud, SUBCLOUD_BOOTSTRAP_GET_FILE_CONTENTS) request, subcloud, SUBCLOUD_BOOTSTRAP_GET_FILE_CONTENTS
)
# Try to load the existing override values # Try to load the existing override values
override_file = psd_common.get_config_file_path(subcloud.name) override_file = psd_common.get_config_file_path(subcloud.name)
if os.path.exists(override_file): if os.path.exists(override_file):
if not has_bootstrap_values: if not has_bootstrap_values:
psd_common.populate_payload_with_pre_existing_data( psd_common.populate_payload_with_pre_existing_data(
payload, subcloud, SUBCLOUD_BOOTSTRAP_GET_FILE_CONTENTS) payload, subcloud, SUBCLOUD_BOOTSTRAP_GET_FILE_CONTENTS
)
elif not has_bootstrap_values: elif not has_bootstrap_values:
msg = _("Required bootstrap-values file was not provided and it was" msg = _(
" not previously available at %s") % (override_file) "Required bootstrap-values file was not provided and it was "
"not previously available at %s"
) % (override_file)
pecan.abort(400, msg) pecan.abort(400, msg)
payload['software_version'] = subcloud.software_version payload["software_version"] = subcloud.software_version
psd_common.pre_deploy_bootstrap(context, payload, subcloud, psd_common.pre_deploy_bootstrap(
has_bootstrap_values) context, payload, subcloud, has_bootstrap_values
)
try: try:
# Ask dcmanager-manager to bootstrap the subcloud. # Ask dcmanager-manager to bootstrap the subcloud.
self.dcmanager_rpc_client.subcloud_deploy_bootstrap( self.dcmanager_rpc_client.subcloud_deploy_bootstrap(
context, subcloud.id, payload, initial_deployment=True) context, subcloud.id, payload, initial_deployment=True
)
# Change the response to correctly display the values # Change the response to correctly display the values
# that will be updated on the manager. # that will be updated on the manager.
subcloud_dict = db_api.subcloud_db_model_to_dict(subcloud) subcloud_dict = db_api.subcloud_db_model_to_dict(subcloud)
subcloud_dict['deploy-status'] = consts.DEPLOY_STATE_PRE_BOOTSTRAP subcloud_dict["deploy-status"] = consts.DEPLOY_STATE_PRE_BOOTSTRAP
subcloud_dict['description'] = payload.get("description", subcloud_dict["description"] = payload.get(
subcloud.description) "description", subcloud.description
subcloud_dict['location'] = payload.get("location", subcloud.location) )
subcloud_dict['management-subnet'] = utils.get_management_subnet(payload) subcloud_dict["location"] = payload.get("location", subcloud.location)
subcloud_dict['management-gateway-ip'] = \ subcloud_dict["management-subnet"] = utils.get_management_subnet(payload)
subcloud_dict["management-gateway-ip"] = (
utils.get_management_gateway_address(payload) utils.get_management_gateway_address(payload)
subcloud_dict['management-start-ip'] = \ )
utils.get_management_start_address(payload) subcloud_dict["management-start-ip"] = utils.get_management_start_address(
subcloud_dict['management-end-ip'] = \ payload
utils.get_management_end_address(payload) )
subcloud_dict['systemcontroller-gateway-ip'] = payload.get( subcloud_dict["management-end-ip"] = utils.get_management_end_address(
"systemcontroller_gateway_address", payload
subcloud.systemcontroller_gateway_ip )
subcloud_dict["systemcontroller-gateway-ip"] = payload.get(
"systemcontroller_gateway_address", subcloud.systemcontroller_gateway_ip
) )
return subcloud_dict return subcloud_dict
except RemoteError as e: except RemoteError as e:
pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value) pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value)
except Exception: except Exception:
LOG.exception("Unable to bootstrap subcloud %s" % LOG.exception("Unable to bootstrap subcloud %s" % payload.get("name"))
payload.get('name')) pecan.abort(
pecan.abort(httpclient.INTERNAL_SERVER_ERROR, httpclient.INTERNAL_SERVER_ERROR, _("Unable to bootstrap subcloud")
_('Unable to bootstrap subcloud')) )
def _deploy_config(self, context: RequestContext, def _deploy_config(self, context: RequestContext, request: pecan.Request, subcloud):
request: pecan.Request, subcloud):
payload = psd_common.get_request_data( payload = psd_common.get_request_data(
request, subcloud, SUBCLOUD_CONFIG_GET_FILE_CONTENTS) request, subcloud, SUBCLOUD_CONFIG_GET_FILE_CONTENTS
)
if not payload: if not payload:
pecan.abort(400, _('Body required')) pecan.abort(400, _("Body required"))
if subcloud.deploy_status not in VALID_STATES_FOR_DEPLOY_CONFIG: if subcloud.deploy_status not in VALID_STATES_FOR_DEPLOY_CONFIG:
allowed_states_str = ', '.join(VALID_STATES_FOR_DEPLOY_CONFIG) allowed_states_str = ", ".join(VALID_STATES_FOR_DEPLOY_CONFIG)
pecan.abort(400, _('Subcloud deploy status must be %s') % pecan.abort(
allowed_states_str) 400, _("Subcloud deploy status must be %s") % allowed_states_str
)
if subcloud.prestage_status in consts.STATES_FOR_ONGOING_PRESTAGE: if subcloud.prestage_status in consts.STATES_FOR_ONGOING_PRESTAGE:
pecan.abort(400, _('Subcloud prestage is ongoing %s') % pecan.abort(
subcloud.prestage_status) 400, _("Subcloud prestage is ongoing %s") % subcloud.prestage_status
)
# If the subcloud belongs to a peer group, ensure that # If the subcloud belongs to a peer group, ensure that
# it's not being configured in a secondary site. # it's not being configured in a secondary site.
if subcloud.peer_group_id is not None: if subcloud.peer_group_id is not None:
peer_group = utils.subcloud_peer_group_get_by_ref( peer_group = utils.subcloud_peer_group_get_by_ref(
context, str(subcloud.peer_group_id)) context, str(subcloud.peer_group_id)
)
if peer_group is not None: if peer_group is not None:
if peer_group.group_priority != consts.PEER_GROUP_PRIMARY_PRIORITY: if peer_group.group_priority != consts.PEER_GROUP_PRIMARY_PRIORITY:
pecan.abort(400, pecan.abort(
_('Subcloud can only be configured in' 400,
' its primary site.')) _("Subcloud can only be configured in its primary site."),
)
psd_common.populate_payload_with_pre_existing_data( psd_common.populate_payload_with_pre_existing_data(
payload, subcloud, SUBCLOUD_CONFIG_GET_FILE_CONTENTS) payload, subcloud, SUBCLOUD_CONFIG_GET_FILE_CONTENTS
)
psd_common.pre_deploy_config(payload, subcloud) psd_common.pre_deploy_config(payload, subcloud)
try: try:
self.dcmanager_rpc_client.subcloud_deploy_config( self.dcmanager_rpc_client.subcloud_deploy_config(
context, subcloud.id, payload, initial_deployment=True) context, subcloud.id, payload, initial_deployment=True
)
subcloud_dict = db_api.subcloud_db_model_to_dict(subcloud) subcloud_dict = db_api.subcloud_db_model_to_dict(subcloud)
subcloud_dict['deploy-status'] = consts.DEPLOY_STATE_PRE_CONFIG subcloud_dict["deploy-status"] = consts.DEPLOY_STATE_PRE_CONFIG
return subcloud_dict return subcloud_dict
except RemoteError as e: except RemoteError as e:
pecan.abort(422, e.value) pecan.abort(422, e.value)
except Exception: except Exception:
LOG.exception("Unable to configure subcloud %s" % subcloud.name) LOG.exception("Unable to configure subcloud %s" % subcloud.name)
pecan.abort(500, _('Unable to configure subcloud')) pecan.abort(500, _("Unable to configure subcloud"))
def _deploy_complete(self, context: RequestContext, subcloud): def _deploy_complete(self, context: RequestContext, subcloud):
@@ -362,67 +370,81 @@ class PhasedSubcloudDeployController(object):
# is consts.DEPLOY_STATE_BOOTSTRAPPED because the user could have # is consts.DEPLOY_STATE_BOOTSTRAPPED because the user could have
# configured the subcloud manually # configured the subcloud manually
if subcloud.deploy_status != consts.DEPLOY_STATE_BOOTSTRAPPED: if subcloud.deploy_status != consts.DEPLOY_STATE_BOOTSTRAPPED:
pecan.abort(400, _('Subcloud deploy can only be completed when' pecan.abort(
' its deploy status is: %s') 400,
% consts.DEPLOY_STATE_BOOTSTRAPPED) _(
"Subcloud deploy can only be completed when "
"its deploy status is: %s"
)
% consts.DEPLOY_STATE_BOOTSTRAPPED,
)
try: try:
# Ask dcmanager-manager to complete the subcloud deployment # Ask dcmanager-manager to complete the subcloud deployment
subcloud = self.dcmanager_rpc_client.subcloud_deploy_complete( subcloud = self.dcmanager_rpc_client.subcloud_deploy_complete(
context, subcloud.id) context, subcloud.id
)
return subcloud return subcloud
except RemoteError as e: except RemoteError as e:
pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value) pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value)
except Exception: except Exception:
LOG.exception("Unable to complete subcloud %s deployment" % LOG.exception("Unable to complete subcloud %s deployment" % subcloud.name)
subcloud.name) pecan.abort(
pecan.abort(httpclient.INTERNAL_SERVER_ERROR, httpclient.INTERNAL_SERVER_ERROR,
_('Unable to complete subcloud deployment')) _("Unable to complete subcloud deployment"),
)
def _deploy_abort(self, context, subcloud): def _deploy_abort(self, context, subcloud):
if subcloud.deploy_status not in VALID_STATES_FOR_DEPLOY_ABORT: if subcloud.deploy_status not in VALID_STATES_FOR_DEPLOY_ABORT:
allowed_states_str = ', '.join(VALID_STATES_FOR_DEPLOY_ABORT) allowed_states_str = ", ".join(VALID_STATES_FOR_DEPLOY_ABORT)
pecan.abort(400, _('Subcloud deploy status must be in one ' pecan.abort(
'of the following states: %s') 400,
% allowed_states_str) _("Subcloud deploy status must be in one of the following states: %s")
% allowed_states_str,
)
initial_deployment = psd_common.is_initial_deployment(subcloud.name) initial_deployment = psd_common.is_initial_deployment(subcloud.name)
if not initial_deployment: if not initial_deployment:
pecan.abort(400, _('The subcloud can only be aborted during ' pecan.abort(
'initial deployment.')) 400, _("The subcloud can only be aborted during initial deployment.")
)
try: try:
self.dcmanager_rpc_client.subcloud_deploy_abort( self.dcmanager_rpc_client.subcloud_deploy_abort(
context, subcloud.id, subcloud.deploy_status) context, subcloud.id, subcloud.deploy_status
)
subcloud_dict = db_api.subcloud_db_model_to_dict(subcloud) subcloud_dict = db_api.subcloud_db_model_to_dict(subcloud)
subcloud_dict['deploy-status'] = \ subcloud_dict["deploy-status"] = utils.ABORT_UPDATE_STATUS[
utils.ABORT_UPDATE_STATUS[subcloud.deploy_status] subcloud.deploy_status
]
return subcloud_dict return subcloud_dict
except RemoteError as e: except RemoteError as e:
pecan.abort(422, e.value) pecan.abort(422, e.value)
except Exception: except Exception:
LOG.exception("Unable to abort subcloud %s deployment" % subcloud.name) LOG.exception("Unable to abort subcloud %s deployment" % subcloud.name)
pecan.abort(500, _('Unable to abort subcloud deployment')) pecan.abort(500, _("Unable to abort subcloud deployment"))
def _deploy_resume(self, context: RequestContext, def _deploy_resume(self, context: RequestContext, request: pecan.Request, subcloud):
request: pecan.Request, subcloud):
if subcloud.deploy_status not in RESUMABLE_STATES: if subcloud.deploy_status not in RESUMABLE_STATES:
allowed_states_str = ', '.join(RESUMABLE_STATES) allowed_states_str = ", ".join(RESUMABLE_STATES)
pecan.abort(400, _('Subcloud deploy status must be either: %s') pecan.abort(
% allowed_states_str) 400, _("Subcloud deploy status must be either: %s") % allowed_states_str
)
initial_deployment = psd_common.is_initial_deployment(subcloud.name) initial_deployment = psd_common.is_initial_deployment(subcloud.name)
if not initial_deployment: if not initial_deployment:
pecan.abort(400, _('The subcloud can only be resumed during ' pecan.abort(
'initial deployment.')) 400, _("The subcloud can only be resumed during initial deployment.")
)
# Since both install and config are optional phases, # Since both install and config are optional phases,
# it's necessary to check if they should be executed # it's necessary to check if they should be executed
config_file = psd_common.get_config_file_path(subcloud.name, config_file = psd_common.get_config_file_path(
consts.DEPLOY_CONFIG) subcloud.name, consts.DEPLOY_CONFIG
)
has_original_install_values = subcloud.data_install has_original_install_values = subcloud.data_install
has_original_config_values = os.path.exists(config_file) has_original_config_values = os.path.exists(config_file)
has_new_install_values = consts.INSTALL_VALUES in request.POST has_new_install_values = consts.INSTALL_VALUES in request.POST
@@ -433,11 +455,15 @@ class PhasedSubcloudDeployController(object):
base_deploy_states = RESUMABLE_STATES[subcloud.deploy_status] base_deploy_states = RESUMABLE_STATES[subcloud.deploy_status]
if base_deploy_states == [CONFIG] and not has_config_values: if base_deploy_states == [CONFIG] and not has_config_values:
msg = _("Only deploy phase left is deploy config. " msg = (
_(
"Only deploy phase left is deploy config. "
"Required %s file was not provided and it was not " "Required %s file was not provided and it was not "
"previously available. If manually configuring the " "previously available. If manually configuring the "
"subcloud, please run 'dcmanager subcloud deploy " "subcloud, please run 'dcmanager subcloud deploy complete'"
"complete'") % consts.DEPLOY_CONFIG )
% consts.DEPLOY_CONFIG
)
pecan.abort(400, msg) pecan.abort(400, msg)
# Since the subcloud can be installed manually and the config is optional, # Since the subcloud can be installed manually and the config is optional,
@@ -475,126 +501,138 @@ class PhasedSubcloudDeployController(object):
# of the pending deploy states # of the pending deploy states
if INSTALL in deploy_states_to_run: if INSTALL in deploy_states_to_run:
SubcloudsController.validate_software_deploy_state() SubcloudsController.validate_software_deploy_state()
unvalidated_sw_version = \ unvalidated_sw_version = payload.get("release", subcloud.software_version)
payload.get('release', subcloud.software_version)
else: else:
LOG.debug( LOG.debug(
'Disregarding release parameter for %s as installation is complete.' "Disregarding release parameter for %s as installation is complete."
% subcloud.name % subcloud.name
) )
unvalidated_sw_version = subcloud.software_version unvalidated_sw_version = subcloud.software_version
# get_sw_version will simply return back the passed # get_sw_version will simply return back the passed
# unvalidated_sw_version after validating it. # unvalidated_sw_version after validating it.
payload['software_version'] = utils.get_sw_version(unvalidated_sw_version) payload["software_version"] = utils.get_sw_version(unvalidated_sw_version)
# Need to remove bootstrap_values from the list of files to populate # Need to remove bootstrap_values from the list of files to populate
# pre existing data so it does not overwrite newly loaded values # pre existing data so it does not overwrite newly loaded values
if has_bootstrap_values: if has_bootstrap_values:
files_for_resume = [f for f in files_for_resume if f files_for_resume = [
not in FILES_MAPPING[BOOTSTRAP]] f for f in files_for_resume if f not in FILES_MAPPING[BOOTSTRAP]
]
psd_common.populate_payload_with_pre_existing_data( psd_common.populate_payload_with_pre_existing_data(
payload, subcloud, files_for_resume) payload, subcloud, files_for_resume
)
psd_common.validate_sysadmin_password(payload) psd_common.validate_sysadmin_password(payload)
for state in deploy_states_to_run: for state in deploy_states_to_run:
if state == INSTALL: if state == INSTALL:
psd_common.pre_deploy_install(payload, validate_password=False) psd_common.pre_deploy_install(payload, validate_password=False)
elif state == BOOTSTRAP: elif state == BOOTSTRAP:
psd_common.pre_deploy_bootstrap(context, payload, subcloud, psd_common.pre_deploy_bootstrap(
has_bootstrap_values, context,
validate_password=False) payload,
subcloud,
has_bootstrap_values,
validate_password=False,
)
elif state == CONFIG: elif state == CONFIG:
psd_common.pre_deploy_config(payload, subcloud, psd_common.pre_deploy_config(payload, subcloud, validate_password=False)
validate_password=False)
try: try:
self.dcmanager_rpc_client.subcloud_deploy_resume( self.dcmanager_rpc_client.subcloud_deploy_resume(
context, subcloud.id, subcloud.name, payload, deploy_states_to_run) context, subcloud.id, subcloud.name, payload, deploy_states_to_run
)
# Change the response to correctly display the values # Change the response to correctly display the values
# that will be updated on the manager. # that will be updated on the manager.
subcloud_dict = db_api.subcloud_db_model_to_dict(subcloud) subcloud_dict = db_api.subcloud_db_model_to_dict(subcloud)
next_deploy_phase = RESUMABLE_STATES[subcloud.deploy_status][0] next_deploy_phase = RESUMABLE_STATES[subcloud.deploy_status][0]
next_deploy_state = RESUME_PREP_UPDATE_STATUS[next_deploy_phase] next_deploy_state = RESUME_PREP_UPDATE_STATUS[next_deploy_phase]
subcloud_dict['deploy-status'] = next_deploy_state subcloud_dict["deploy-status"] = next_deploy_state
subcloud_dict['software-version'] = payload['software_version'] subcloud_dict["software-version"] = payload["software_version"]
subcloud_dict['description'] = payload.get("description", subcloud_dict["description"] = payload.get(
subcloud.description) "description", subcloud.description
subcloud_dict['location'] = payload.get("location", subcloud.location) )
subcloud_dict['management-subnet'] = utils.get_management_subnet(payload) subcloud_dict["location"] = payload.get("location", subcloud.location)
subcloud_dict['management-gateway-ip'] = \ subcloud_dict["management-subnet"] = utils.get_management_subnet(payload)
subcloud_dict["management-gateway-ip"] = (
utils.get_management_gateway_address(payload) utils.get_management_gateway_address(payload)
subcloud_dict['management-start-ip'] = \ )
utils.get_management_start_address(payload) subcloud_dict["management-start-ip"] = utils.get_management_start_address(
subcloud_dict['management-end-ip'] = \ payload
utils.get_management_end_address(payload) )
subcloud_dict['systemcontroller-gateway-ip'] = payload.get( subcloud_dict["management-end-ip"] = utils.get_management_end_address(
"systemcontroller_gateway_address", payload
subcloud.systemcontroller_gateway_ip )
subcloud_dict["systemcontroller-gateway-ip"] = payload.get(
"systemcontroller_gateway_address", subcloud.systemcontroller_gateway_ip
) )
return subcloud_dict return subcloud_dict
except RemoteError as e: except RemoteError as e:
pecan.abort(422, e.value) pecan.abort(422, e.value)
except Exception: except Exception:
LOG.exception("Unable to resume subcloud %s deployment" % subcloud.name) LOG.exception("Unable to resume subcloud %s deployment" % subcloud.name)
pecan.abort(500, _('Unable to resume subcloud deployment')) pecan.abort(500, _("Unable to resume subcloud deployment"))
def _deploy_enroll(self, context: RequestContext, def _deploy_enroll(
request: pecan.Request, subcloud: models.Subcloud): self, context: RequestContext, request: pecan.Request, subcloud: models.Subcloud
):
if subcloud.deploy_status not in VALID_STATES_FOR_DEPLOY_ENROLL: if subcloud.deploy_status not in VALID_STATES_FOR_DEPLOY_ENROLL:
valid_states_str = ', '.join(VALID_STATES_FOR_DEPLOY_ENROLL) valid_states_str = ", ".join(VALID_STATES_FOR_DEPLOY_ENROLL)
msg = f'Subcloud deploy status must be either: {valid_states_str}' msg = f"Subcloud deploy status must be either: {valid_states_str}"
pecan.abort(400, _(msg)) pecan.abort(400, _(msg))
has_bootstrap_values = consts.BOOTSTRAP_VALUES in request.POST has_bootstrap_values = consts.BOOTSTRAP_VALUES in request.POST
payload = psd_common.get_request_data( payload = psd_common.get_request_data(
request, subcloud, SUBCLOUD_ENROLL_GET_FILE_CONTENTS) request, subcloud, SUBCLOUD_ENROLL_GET_FILE_CONTENTS
)
# Try to load the existing override values # Try to load the existing override values
override_file = psd_common.get_config_file_path(subcloud.name) override_file = psd_common.get_config_file_path(subcloud.name)
if os.path.exists(override_file): if os.path.exists(override_file):
if not has_bootstrap_values: if not has_bootstrap_values:
psd_common.populate_payload_with_pre_existing_data( psd_common.populate_payload_with_pre_existing_data(
payload, subcloud, SUBCLOUD_ENROLL_GET_FILE_CONTENTS) payload, subcloud, SUBCLOUD_ENROLL_GET_FILE_CONTENTS
)
elif not has_bootstrap_values: elif not has_bootstrap_values:
msg = ("Required bootstrap-values file was not provided and it was " msg = (
f"not previously available at {override_file}") "Required bootstrap-values file was not provided and it was "
f"not previously available at {override_file}"
)
pecan.abort(400, _(msg)) pecan.abort(400, _(msg))
psd_common.validate_enroll_parameter(payload) psd_common.validate_enroll_parameter(payload)
payload['software_version'] = subcloud.software_version payload["software_version"] = subcloud.software_version
# Use bootstrap file verification # Use bootstrap file verification
psd_common.pre_deploy_bootstrap(context, payload, subcloud, psd_common.pre_deploy_bootstrap(
has_bootstrap_values) context, payload, subcloud, has_bootstrap_values
)
self.dcmanager_rpc_client.subcloud_deploy_enroll( self.dcmanager_rpc_client.subcloud_deploy_enroll(context, subcloud.id, payload)
context, subcloud.id, payload)
pecan.abort(400, "subcloud deploy enrollment is not " pecan.abort(400, "subcloud deploy enrollment is not available yet")
"available yet")
return "" return ""
# TODO(glyraper): The return is necessary to avoid # TODO(glyraper): The return is necessary to avoid
# the E1111 while the implementation is not complete # the E1111 while the implementation is not complete
# TODO(glyraper): Enroll function in development # TODO(glyraper): Enroll function in development
@pecan.expose(generic=True, template='json') @pecan.expose(generic=True, template="json")
def index(self): def index(self):
# Route the request to specific methods with parameters # Route the request to specific methods with parameters
pass pass
@utils.synchronized(LOCK_NAME) @utils.synchronized(LOCK_NAME)
@index.when(method='POST', template='json') @index.when(method="POST", template="json")
def post(self): def post(self):
context = restcomm.extract_context_from_environ() context = restcomm.extract_context_from_environ()
return self._deploy_create(context, pecan.request) return self._deploy_create(context, pecan.request)
@utils.synchronized(LOCK_NAME) @utils.synchronized(LOCK_NAME)
@index.when(method='PATCH', template='json') @index.when(method="PATCH", template="json")
def patch(self, subcloud_ref=None, verb=None): def patch(self, subcloud_ref=None, verb=None):
"""Modify the subcloud deployment. """Modify the subcloud deployment.
@@ -604,12 +642,15 @@ class PhasedSubcloudDeployController(object):
or subcloud operation or subcloud operation
""" """
policy.authorize(phased_subcloud_deploy_policy.POLICY_ROOT % "modify", {}, policy.authorize(
restcomm.extract_credentials_for_policy()) phased_subcloud_deploy_policy.POLICY_ROOT % "modify",
{},
restcomm.extract_credentials_for_policy(),
)
context = restcomm.extract_context_from_environ() context = restcomm.extract_context_from_environ()
if not subcloud_ref: if not subcloud_ref:
pecan.abort(400, _('Subcloud ID required')) pecan.abort(400, _("Subcloud ID required"))
try: try:
if subcloud_ref.isdigit(): if subcloud_ref.isdigit():
@@ -617,7 +658,7 @@ class PhasedSubcloudDeployController(object):
else: else:
subcloud = db_api.subcloud_get_by_name(context, subcloud_ref) subcloud = db_api.subcloud_get_by_name(context, subcloud_ref)
except (exceptions.SubcloudNotFound, exceptions.SubcloudNameNotFound): except (exceptions.SubcloudNotFound, exceptions.SubcloudNameNotFound):
pecan.abort(404, _('Subcloud not found')) pecan.abort(404, _("Subcloud not found"))
if verb == ABORT: if verb == ABORT:
subcloud = self._deploy_abort(context, subcloud) subcloud = self._deploy_abort(context, subcloud)
@@ -634,6 +675,6 @@ class PhasedSubcloudDeployController(object):
elif verb == ENROLL: elif verb == ENROLL:
subcloud = self._deploy_enroll(context, pecan.request, subcloud) subcloud = self._deploy_enroll(context, pecan.request, subcloud)
else: else:
pecan.abort(400, _('Invalid request')) pecan.abort(400, _("Invalid request"))
return subcloud return subcloud

View File

@@ -1,5 +1,5 @@
# Copyright (c) 2017 Ericsson AB. # Copyright (c) 2017 Ericsson AB.
# Copyright (c) 2017-2023 Wind River Systems, Inc. # Copyright (c) 2017-2024 Wind River Systems, Inc.
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -40,29 +40,33 @@ class Controller(object):
minor_version = remainder[-1] minor_version = remainder[-1]
remainder = remainder[:-1] remainder = remainder[:-1]
sub_controllers = dict() sub_controllers = dict()
if minor_version == '0': if minor_version == "0":
sub_controllers["subclouds"] = subclouds.SubcloudsController sub_controllers["subclouds"] = subclouds.SubcloudsController
sub_controllers["subcloud-deploy"] = subcloud_deploy.\ sub_controllers["subcloud-deploy"] = (
SubcloudDeployController subcloud_deploy.SubcloudDeployController
)
sub_controllers["alarms"] = alarm_manager.SubcloudAlarmController sub_controllers["alarms"] = alarm_manager.SubcloudAlarmController
sub_controllers["sw-update-strategy"] = \ sub_controllers["sw-update-strategy"] = (
sw_update_strategy.SwUpdateStrategyController sw_update_strategy.SwUpdateStrategyController
sub_controllers["sw-update-options"] = \ )
sub_controllers["sw-update-options"] = (
sw_update_options.SwUpdateOptionsController sw_update_options.SwUpdateOptionsController
sub_controllers["subcloud-groups"] = \ )
subcloud_group.SubcloudGroupsController sub_controllers["subcloud-groups"] = subcloud_group.SubcloudGroupsController
sub_controllers["notifications"] = \ sub_controllers["notifications"] = notifications.NotificationsController
notifications.NotificationsController sub_controllers["subcloud-backup"] = (
sub_controllers["subcloud-backup"] = subcloud_backup.\ subcloud_backup.SubcloudBackupController
SubcloudBackupController )
sub_controllers["phased-subcloud-deploy"] = phased_subcloud_deploy.\ sub_controllers["phased-subcloud-deploy"] = (
PhasedSubcloudDeployController phased_subcloud_deploy.PhasedSubcloudDeployController
sub_controllers["subcloud-peer-groups"] = \ )
sub_controllers["subcloud-peer-groups"] = (
subcloud_peer_group.SubcloudPeerGroupsController subcloud_peer_group.SubcloudPeerGroupsController
sub_controllers["peer-group-associations"] = \ )
sub_controllers["peer-group-associations"] = (
peer_group_association.PeerGroupAssociationsController peer_group_association.PeerGroupAssociationsController
sub_controllers["system-peers"] = system_peers.\ )
SystemPeersController sub_controllers["system-peers"] = system_peers.SystemPeersController
for name, ctrl in sub_controllers.items(): for name, ctrl in sub_controllers.items():
setattr(self, name, ctrl) setattr(self, name, ctrl)

View File

@@ -30,19 +30,20 @@ from dcmanager.rpc import client as rpc_client
CONF = cfg.CONF CONF = cfg.CONF
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
LOCK_NAME = 'SubcloudBackupController' LOCK_NAME = "SubcloudBackupController"
# Subcloud/group information to be retrieved from request params # Subcloud/group information to be retrieved from request params
RequestEntity = namedtuple('RequestEntity', ['type', 'id', 'name', 'subclouds']) RequestEntity = namedtuple("RequestEntity", ["type", "id", "name", "subclouds"])
class SubcloudBackupController(object): class SubcloudBackupController(object):
def __init__(self): def __init__(self):
super(SubcloudBackupController, self).__init__() super(SubcloudBackupController, self).__init__()
self.dcmanager_rpc_client = rpc_client.ManagerClient( self.dcmanager_rpc_client = rpc_client.ManagerClient(
timeout=consts.RPC_SUBCLOUD_BACKUP_TIMEOUT) timeout=consts.RPC_SUBCLOUD_BACKUP_TIMEOUT
)
@expose(generic=True, template='json') @expose(generic=True, template="json")
def index(self): def index(self):
# Route the request to specific methods with parameters # Route the request to specific methods with parameters
pass pass
@@ -50,24 +51,24 @@ class SubcloudBackupController(object):
@staticmethod @staticmethod
def _get_payload(request, verb): def _get_payload(request, verb):
expected_params = dict() expected_params = dict()
if verb == 'create': if verb == "create":
expected_params = { expected_params = {
"subcloud": "text", "subcloud": "text",
"group": "text", "group": "text",
"local_only": "text", "local_only": "text",
"registry_images": "text", "registry_images": "text",
"backup_values": "yaml", "backup_values": "yaml",
"sysadmin_password": "text" "sysadmin_password": "text",
} }
elif verb == 'delete': elif verb == "delete":
expected_params = { expected_params = {
"release": "text", "release": "text",
"subcloud": "text", "subcloud": "text",
"group": "text", "group": "text",
"local_only": "text", "local_only": "text",
"sysadmin_password": "text" "sysadmin_password": "text",
} }
elif verb == 'restore': elif verb == "restore":
expected_params = { expected_params = {
"with_install": "text", "with_install": "text",
"release": "text", "release": "text",
@@ -76,30 +77,29 @@ class SubcloudBackupController(object):
"sysadmin_password": "text", "sysadmin_password": "text",
"restore_values": "text", "restore_values": "text",
"subcloud": "text", "subcloud": "text",
"group": "text" "group": "text",
} }
else: else:
pecan.abort(400, _("Unexpected verb received")) pecan.abort(400, _("Unexpected verb received"))
content_type = request.headers.get('content-type') content_type = request.headers.get("content-type")
LOG.info('Request content-type: %s' % content_type) LOG.info("Request content-type: %s" % content_type)
if 'multipart/form-data' in content_type.lower(): if "multipart/form-data" in content_type.lower():
return SubcloudBackupController._get_multipart_payload(request, return SubcloudBackupController._get_multipart_payload(
expected_params) request, expected_params
)
else: else:
return SubcloudBackupController._get_json_payload(request, return SubcloudBackupController._get_json_payload(request, expected_params)
expected_params)
@staticmethod @staticmethod
def _get_multipart_payload(request, expected_params): def _get_multipart_payload(request, expected_params):
payload = dict() payload = dict()
file_params = ['backup_values', 'restore_values'] file_params = ["backup_values", "restore_values"]
for param in file_params: for param in file_params:
if param in request.POST: if param in request.POST:
file_item = request.POST[param] file_item = request.POST[param]
file_item.file.seek(0, os.SEEK_SET) file_item.file.seek(0, os.SEEK_SET)
data = \ data = utils.yaml_safe_load(file_item.file.read().decode("utf8"), param)
utils.yaml_safe_load(file_item.file.read().decode('utf8'), param)
payload.update({param: data}) payload.update({param: data})
del request.POST[param] del request.POST[param]
@@ -116,12 +116,12 @@ class SubcloudBackupController(object):
try: try:
payload = json.loads(request.body) payload = json.loads(request.body)
except Exception: except Exception:
error_msg = 'Request body is malformed.' error_msg = "Request body is malformed."
LOG.exception(error_msg) LOG.exception(error_msg)
pecan.abort(400, _(error_msg)) pecan.abort(400, _(error_msg))
return return
if not isinstance(payload, dict): if not isinstance(payload, dict):
pecan.abort(400, _('Invalid request body format')) pecan.abort(400, _("Invalid request body format"))
if not set(payload.keys()).issubset(expected_params.keys()): if not set(payload.keys()).issubset(expected_params.keys()):
LOG.info("Got an unexpected parameter in: %s" % payload) LOG.info("Got an unexpected parameter in: %s" % payload)
pecan.abort(400, _("Unexpected parameter received")) pecan.abort(400, _("Unexpected parameter received"))
@@ -133,13 +133,16 @@ class SubcloudBackupController(object):
sysadmin_password = payload.get(param_name) sysadmin_password = payload.get(param_name)
if not sysadmin_password: if not sysadmin_password:
pecan.abort(400, _('subcloud sysadmin_password required')) pecan.abort(400, _("subcloud sysadmin_password required"))
try: try:
payload['sysadmin_password'] = \ payload["sysadmin_password"] = utils.decode_and_normalize_passwd(
utils.decode_and_normalize_passwd(sysadmin_password) sysadmin_password
)
except Exception: except Exception:
msg = _('Failed to decode subcloud sysadmin_password, ' msg = _(
'verify the password is base64 encoded') "Failed to decode subcloud sysadmin_password, "
"verify the password is base64 encoded"
)
LOG.exception(msg) LOG.exception(msg)
pecan.abort(400, msg) pecan.abort(400, msg)
@@ -148,13 +151,14 @@ class SubcloudBackupController(object):
for param_name in param_names: for param_name in param_names:
param = payload.get(param_name) param = payload.get(param_name)
if param: if param:
if param.lower() == 'true': if param.lower() == "true":
payload[param_name] = True payload[param_name] = True
elif param.lower() == 'false': elif param.lower() == "false":
payload[param_name] = False payload[param_name] = False
else: else:
pecan.abort(400, _('Invalid %s value, should be boolean' pecan.abort(
% param_name)) 400, _("Invalid %s value, should be boolean" % param_name)
)
else: else:
payload[param_name] = default payload[param_name] = default
@@ -177,23 +181,25 @@ class SubcloudBackupController(object):
operation (string): Subcloud backup operation operation (string): Subcloud backup operation
""" """
subclouds = request_entity.subclouds subclouds = request_entity.subclouds
error_msg = _( error_msg = _("Subcloud(s) must be in a valid state for backup %s." % operation)
'Subcloud(s) must be in a valid state for backup %s.' % operation)
has_valid_subclouds = False has_valid_subclouds = False
valid_subclouds = list() valid_subclouds = list()
for subcloud in subclouds: for subcloud in subclouds:
try: try:
is_valid = utils.is_valid_for_backup_operation( is_valid = utils.is_valid_for_backup_operation(
operation, subcloud, bootstrap_address_dict) operation, subcloud, bootstrap_address_dict
)
if operation == 'create': if operation == "create":
backup_in_progress = subcloud.backup_status in \ backup_in_progress = (
consts.STATES_FOR_ONGOING_BACKUP subcloud.backup_status in consts.STATES_FOR_ONGOING_BACKUP
)
if is_valid and not backup_in_progress: if is_valid and not backup_in_progress:
has_valid_subclouds = True has_valid_subclouds = True
else: else:
error_msg = _('Subcloud(s) already have a backup ' error_msg = _(
'operation in progress.') "Subcloud(s) already have a backup operation in progress."
)
else: else:
if is_valid: if is_valid:
valid_subclouds.append(subcloud) valid_subclouds.append(subcloud)
@@ -202,21 +208,27 @@ class SubcloudBackupController(object):
except exceptions.ValidateFail as e: except exceptions.ValidateFail as e:
error_msg = e.message error_msg = e.message
if (operation == 'create' and has_valid_subclouds if (
and request_entity.type == 'subcloud'): operation == "create"
and has_valid_subclouds
and request_entity.type == "subcloud"
):
# Check the system health only if the command was issued # Check the system health only if the command was issued
# to a single subcloud to avoid huge delays. # to a single subcloud to avoid huge delays.
if not utils.is_subcloud_healthy(subcloud.region_name): if not utils.is_subcloud_healthy(subcloud.region_name):
msg = _('Subcloud %s must be in good health for ' msg = _(
'subcloud-backup create.' % subcloud.name) "Subcloud %s must be in good health for subcloud-backup create."
% subcloud.name
)
pecan.abort(400, msg) pecan.abort(400, msg)
if not has_valid_subclouds: if not has_valid_subclouds:
if request_entity.type == 'group': if request_entity.type == "group":
msg = _('None of the subclouds in group %s are in a valid ' msg = _(
'state for subcloud-backup %s') % (request_entity.name, "None of the subclouds in group %s are in a valid "
operation) "state for subcloud-backup %s"
elif request_entity.type == 'subcloud': ) % (request_entity.name, operation)
elif request_entity.type == "subcloud":
msg = error_msg msg = error_msg
pecan.abort(400, msg) pecan.abort(400, msg)
@@ -225,52 +237,62 @@ class SubcloudBackupController(object):
@staticmethod @staticmethod
def _get_subclouds_from_group(group, context): def _get_subclouds_from_group(group, context):
if not group: if not group:
pecan.abort(404, _('Group not found')) pecan.abort(404, _("Group not found"))
return db_api.subcloud_get_for_group(context, group.id) return db_api.subcloud_get_for_group(context, group.id)
def _read_entity_from_request_params(self, context, payload): def _read_entity_from_request_params(self, context, payload):
subcloud_ref = payload.get('subcloud') subcloud_ref = payload.get("subcloud")
group_ref = payload.get('group') group_ref = payload.get("group")
if subcloud_ref: if subcloud_ref:
if group_ref: if group_ref:
pecan.abort(400, _("'subcloud' and 'group' parameters " pecan.abort(
"should not be given at the same time")) 400,
_(
"'subcloud' and 'group' parameters should not be given at "
"the same time"
),
)
subcloud = utils.subcloud_get_by_ref(context, subcloud_ref) subcloud = utils.subcloud_get_by_ref(context, subcloud_ref)
if not subcloud: if not subcloud:
pecan.abort(400, _('Subcloud not found')) pecan.abort(400, _("Subcloud not found"))
return RequestEntity('subcloud', subcloud.id, subcloud_ref, [subcloud]) return RequestEntity("subcloud", subcloud.id, subcloud_ref, [subcloud])
elif group_ref: elif group_ref:
group = utils.subcloud_group_get_by_ref(context, group_ref) group = utils.subcloud_group_get_by_ref(context, group_ref)
group_subclouds = self._get_subclouds_from_group(group, context) group_subclouds = self._get_subclouds_from_group(group, context)
if not group_subclouds: if not group_subclouds:
pecan.abort(400, _('No subclouds present in group')) pecan.abort(400, _("No subclouds present in group"))
return RequestEntity('group', group.id, group_ref, group_subclouds) return RequestEntity("group", group.id, group_ref, group_subclouds)
else: else:
pecan.abort(400, _("'subcloud' or 'group' parameter is required")) pecan.abort(400, _("'subcloud' or 'group' parameter is required"))
@utils.synchronized(LOCK_NAME) @utils.synchronized(LOCK_NAME)
@index.when(method='POST', template='json') @index.when(method="POST", template="json")
def post(self): def post(self):
"""Create a new subcloud backup.""" """Create a new subcloud backup."""
context = restcomm.extract_context_from_environ() context = restcomm.extract_context_from_environ()
payload = self._get_payload(pecan_request, 'create') payload = self._get_payload(pecan_request, "create")
policy.authorize(subcloud_backup_policy.POLICY_ROOT % "create", {}, policy.authorize(
restcomm.extract_credentials_for_policy()) subcloud_backup_policy.POLICY_ROOT % "create",
self._validate_and_decode_sysadmin_password(payload, 'sysadmin_password') {},
restcomm.extract_credentials_for_policy(),
)
self._validate_and_decode_sysadmin_password(payload, "sysadmin_password")
if not payload.get('local_only') and payload.get('registry_images'): if not payload.get("local_only") and payload.get("registry_images"):
pecan.abort(400, _('Option registry_images can not be used without ' pecan.abort(
'local_only option.')) 400,
_("Option registry_images can not be used without local_only option."),
)
request_entity = self._read_entity_from_request_params(context, payload) request_entity = self._read_entity_from_request_params(context, payload)
self._validate_subclouds(request_entity, 'create') self._validate_subclouds(request_entity, "create")
# Set subcloud/group ID as reference instead of name to ease processing # Set subcloud/group ID as reference instead of name to ease processing
payload[request_entity.type] = request_entity.id payload[request_entity.type] = request_entity.id
self._convert_param_to_bool(payload, ['local_only', 'registry_images']) self._convert_param_to_bool(payload, ["local_only", "registry_images"])
try: try:
self.dcmanager_rpc_client.backup_subclouds(context, payload) self.dcmanager_rpc_client.backup_subclouds(context, payload)
@@ -279,10 +301,10 @@ class SubcloudBackupController(object):
pecan.abort(422, e.value) pecan.abort(422, e.value)
except Exception: except Exception:
LOG.exception("Unable to backup subclouds") LOG.exception("Unable to backup subclouds")
pecan.abort(500, _('Unable to backup subcloud')) pecan.abort(500, _("Unable to backup subcloud"))
@utils.synchronized(LOCK_NAME) @utils.synchronized(LOCK_NAME)
@index.when(method='PATCH', template='json') @index.when(method="PATCH", template="json")
def patch(self, verb, release_version=None): def patch(self, verb, release_version=None):
"""Delete or restore a subcloud backup. """Delete or restore a subcloud backup.
@@ -294,25 +316,29 @@ class SubcloudBackupController(object):
context = restcomm.extract_context_from_environ() context = restcomm.extract_context_from_environ()
payload = self._get_payload(pecan_request, verb) payload = self._get_payload(pecan_request, verb)
if verb == 'delete': if verb == "delete":
policy.authorize(subcloud_backup_policy.POLICY_ROOT % "delete", {}, policy.authorize(
restcomm.extract_credentials_for_policy()) subcloud_backup_policy.POLICY_ROOT % "delete",
{},
restcomm.extract_credentials_for_policy(),
)
if not release_version: if not release_version:
pecan.abort(400, _('Release version required')) pecan.abort(400, _("Release version required"))
self._convert_param_to_bool(payload, ['local_only']) self._convert_param_to_bool(payload, ["local_only"])
# Backup delete in systemcontroller doesn't need sysadmin_password # Backup delete in systemcontroller doesn't need sysadmin_password
if payload.get('local_only'): if payload.get("local_only"):
self._validate_and_decode_sysadmin_password( self._validate_and_decode_sysadmin_password(
payload, 'sysadmin_password') payload, "sysadmin_password"
)
request_entity = self._read_entity_from_request_params(context, payload) request_entity = self._read_entity_from_request_params(context, payload)
# Validate subcloud state when deleting locally # Validate subcloud state when deleting locally
# Not needed for centralized storage, since connection is not required # Not needed for centralized storage, since connection is not required
local_only = payload.get('local_only') local_only = payload.get("local_only")
if local_only: if local_only:
self._validate_subclouds(request_entity, verb) self._validate_subclouds(request_entity, verb)
@@ -321,7 +347,8 @@ class SubcloudBackupController(object):
try: try:
message = self.dcmanager_rpc_client.delete_subcloud_backups( message = self.dcmanager_rpc_client.delete_subcloud_backups(
context, release_version, payload) context, release_version, payload
)
if message: if message:
response.status_int = 207 response.status_int = 207
@@ -332,83 +359,109 @@ class SubcloudBackupController(object):
pecan.abort(422, e.value) pecan.abort(422, e.value)
except Exception: except Exception:
LOG.exception("Unable to delete subcloud backups") LOG.exception("Unable to delete subcloud backups")
pecan.abort(500, _('Unable to delete subcloud backups')) pecan.abort(500, _("Unable to delete subcloud backups"))
elif verb == 'restore': elif verb == "restore":
policy.authorize(subcloud_backup_policy.POLICY_ROOT % "restore", {}, policy.authorize(
restcomm.extract_credentials_for_policy()) subcloud_backup_policy.POLICY_ROOT % "restore",
{},
restcomm.extract_credentials_for_policy(),
)
if not payload: if not payload:
pecan.abort(400, _('Body required')) pecan.abort(400, _("Body required"))
self._validate_and_decode_sysadmin_password(payload, 'sysadmin_password') self._validate_and_decode_sysadmin_password(payload, "sysadmin_password")
self._convert_param_to_bool(payload, ['local_only', 'with_install', self._convert_param_to_bool(
'registry_images']) payload, ["local_only", "with_install", "registry_images"]
)
if not payload['local_only'] and payload['registry_images']: if not payload["local_only"] and payload["registry_images"]:
pecan.abort(400, _('Option registry_images cannot be used ' pecan.abort(
'without local_only option.')) 400,
_(
"Option registry_images cannot be used "
"without local_only option."
),
)
if not payload['with_install'] and payload.get('release'): if not payload["with_install"] and payload.get("release"):
pecan.abort(400, _('Option release cannot be used ' pecan.abort(
'without with_install option.')) 400,
_("Option release cannot be used without with_install option."),
)
request_entity = self._read_entity_from_request_params(context, payload) request_entity = self._read_entity_from_request_params(context, payload)
if len(request_entity.subclouds) == 0: if len(request_entity.subclouds) == 0:
msg = "No subclouds exist under %s %s" % (request_entity.type, msg = "No subclouds exist under %s %s" % (
request_entity.id) request_entity.type,
request_entity.id,
)
pecan.abort(400, _(msg)) pecan.abort(400, _(msg))
bootstrap_address_dict = \ bootstrap_address_dict = payload.get("restore_values", {}).get(
payload.get('restore_values', {}).get('bootstrap_address', {}) "bootstrap_address", {}
)
if not isinstance(bootstrap_address_dict, dict): if not isinstance(bootstrap_address_dict, dict):
pecan.abort( pecan.abort(
400, _('The bootstrap_address provided in restore_values ' 400,
'is in invalid format.') _(
"The bootstrap_address provided in restore_values "
"is in invalid format."
),
) )
restore_subclouds = self._validate_subclouds( restore_subclouds = self._validate_subclouds(
request_entity, verb, bootstrap_address_dict) request_entity, verb, bootstrap_address_dict
)
payload[request_entity.type] = request_entity.id payload[request_entity.type] = request_entity.id
if payload.get('with_install'): if payload.get("with_install"):
subclouds_without_install_values = [ subclouds_without_install_values = [
subcloud.name for subcloud in request_entity.subclouds if subcloud.name
not subcloud.data_install for subcloud in request_entity.subclouds
if not subcloud.data_install
] ]
if subclouds_without_install_values: if subclouds_without_install_values:
subclouds_str = ', '.join(subclouds_without_install_values) subclouds_str = ", ".join(subclouds_without_install_values)
pecan.abort( pecan.abort(
400, _('The restore operation was requested with_install, ' 400,
'but the following subcloud(s) does not contain ' _(
'install values: %s' % subclouds_str) "The restore operation was requested with_install, "
"but the following subcloud(s) does not contain "
"install values: %s" % subclouds_str
),
) )
# Confirm the requested or active load is still in dc-vault # Confirm the requested or active load is still in dc-vault
payload['software_version'] = utils.get_sw_version( payload["software_version"] = utils.get_sw_version(
payload.get('release')) payload.get("release")
matching_iso, err_msg = \ )
utils.get_matching_iso(payload['software_version']) matching_iso, err_msg = utils.get_matching_iso(
payload["software_version"]
)
if err_msg: if err_msg:
LOG.exception(err_msg) LOG.exception(err_msg)
pecan.abort(400, _(err_msg)) pecan.abort(400, _(err_msg))
LOG.info("Restore operation will use image %s in subcloud " LOG.info(
"installation" % matching_iso) "Restore operation will use image %s in subcloud installation"
% matching_iso
)
try: try:
# local update to deploy_status - this is just for CLI response # local update to deploy_status - this is just for CLI response
# pylint: disable-next=consider-using-enumerate # pylint: disable-next=consider-using-enumerate
for i in range(len(restore_subclouds)): for i in range(len(restore_subclouds)):
restore_subclouds[i].deploy_status = ( restore_subclouds[i].deploy_status = consts.DEPLOY_STATE_PRE_RESTORE
consts.DEPLOY_STATE_PRE_RESTORE)
message = self.dcmanager_rpc_client.restore_subcloud_backups( message = self.dcmanager_rpc_client.restore_subcloud_backups(
context, payload) context, payload
)
return utils.subcloud_db_list_to_dict(restore_subclouds) return utils.subcloud_db_list_to_dict(restore_subclouds)
except RemoteError as e: except RemoteError as e:
pecan.abort(422, e.value) pecan.abort(422, e.value)
except Exception: except Exception:
LOG.exception("Unable to restore subcloud") LOG.exception("Unable to restore subcloud")
pecan.abort(500, _('Unable to restore subcloud')) pecan.abort(500, _("Unable to restore subcloud"))
else: else:
pecan.abort(400, _('Invalid request')) pecan.abort(400, _("Invalid request"))

View File

@@ -37,7 +37,7 @@ from dcmanager.common import utils
CONF = cfg.CONF CONF = cfg.CONF
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
LOCK_NAME = 'SubcloudDeployController' LOCK_NAME = "SubcloudDeployController"
class SubcloudDeployController(object): class SubcloudDeployController(object):
@@ -48,7 +48,7 @@ class SubcloudDeployController(object):
@staticmethod @staticmethod
def _upload_files(dir_path, file_option, file_item, binary): def _upload_files(dir_path, file_option, file_item, binary):
prefix = file_option + '_' prefix = file_option + "_"
# create the version directory if it does not exist # create the version directory if it does not exist
if not os.path.isdir(dir_path): if not os.path.isdir(dir_path):
os.mkdir(dir_path, 0o755) os.mkdir(dir_path, 0o755)
@@ -56,30 +56,32 @@ class SubcloudDeployController(object):
# check if the file exists, if so remove it # check if the file exists, if so remove it
filename = utils.get_filename_by_prefix(dir_path, prefix) filename = utils.get_filename_by_prefix(dir_path, prefix)
if filename is not None: if filename is not None:
os.remove(dir_path + '/' + filename) os.remove(dir_path + "/" + filename)
# upload the new file # upload the new file
file_item.file.seek(0, os.SEEK_SET) file_item.file.seek(0, os.SEEK_SET)
contents = file_item.file.read() contents = file_item.file.read()
fn = os.path.join(dir_path, prefix + os.path.basename( fn = os.path.join(dir_path, prefix + os.path.basename(file_item.filename))
file_item.filename))
if binary: if binary:
dst = open(fn, 'wb') dst = open(fn, "wb")
dst.write(contents) dst.write(contents)
else: else:
dst = os.open(fn, os.O_WRONLY | os.O_CREAT | os.O_TRUNC) dst = os.open(fn, os.O_WRONLY | os.O_CREAT | os.O_TRUNC)
os.write(dst, contents) os.write(dst, contents)
@expose(generic=True, template='json') @expose(generic=True, template="json")
def index(self): def index(self):
# Route the request to specific methods with parameters # Route the request to specific methods with parameters
pass pass
@utils.synchronized(LOCK_NAME) @utils.synchronized(LOCK_NAME)
@index.when(method='POST', template='json') @index.when(method="POST", template="json")
def post(self): def post(self):
policy.authorize(subcloud_deploy_policy.POLICY_ROOT % "upload", {}, policy.authorize(
restcomm.extract_credentials_for_policy()) subcloud_deploy_policy.POLICY_ROOT % "upload",
{},
restcomm.extract_credentials_for_policy(),
)
deploy_dicts = dict() deploy_dicts = dict()
missing_options = set() missing_options = set()
for f in consts.DEPLOY_COMMON_FILE_OPTIONS: for f in consts.DEPLOY_COMMON_FILE_OPTIONS:
@@ -92,30 +94,31 @@ class SubcloudDeployController(object):
# 3. DEPLOY_PRESTAGE # 3. DEPLOY_PRESTAGE
size = len(missing_options) size = len(missing_options)
if len(missing_options) > 0: if len(missing_options) > 0:
if ((consts.DEPLOY_PRESTAGE in missing_options and size != 1) or if (consts.DEPLOY_PRESTAGE in missing_options and size != 1) or (
(consts.DEPLOY_PRESTAGE not in missing_options and size != 3)): consts.DEPLOY_PRESTAGE not in missing_options and size != 3
):
missing_str = str() missing_str = str()
for missing in missing_options: for missing in missing_options:
if missing is not consts.DEPLOY_PRESTAGE: if missing is not consts.DEPLOY_PRESTAGE:
missing_str += '--%s ' % missing missing_str += "--%s " % missing
error_msg = "error: argument %s is required" % missing_str.rstrip() error_msg = "error: argument %s is required" % missing_str.rstrip()
pecan.abort(httpclient.BAD_REQUEST, error_msg) pecan.abort(httpclient.BAD_REQUEST, error_msg)
deploy_dicts['software_version'] = \ deploy_dicts["software_version"] = utils.get_sw_version(
utils.get_sw_version(request.POST.get('release')) request.POST.get("release")
)
dir_path = os.path.join( dir_path = os.path.join(
dccommon_consts.DEPLOY_DIR, deploy_dicts['software_version'] dccommon_consts.DEPLOY_DIR, deploy_dicts["software_version"]
) )
for f in consts.DEPLOY_COMMON_FILE_OPTIONS: for f in consts.DEPLOY_COMMON_FILE_OPTIONS:
if f not in request.POST: if f not in request.POST:
continue continue
file_item = request.POST[f] file_item = request.POST[f]
filename = getattr(file_item, 'filename', '') filename = getattr(file_item, "filename", "")
if not filename: if not filename:
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(httpclient.BAD_REQUEST, _("No %s file uploaded" % f))
_("No %s file uploaded" % f))
binary = False binary = False
if f == consts.DEPLOY_CHART: if f == consts.DEPLOY_CHART:
@@ -123,76 +126,90 @@ class SubcloudDeployController(object):
try: try:
self._upload_files(dir_path, f, file_item, binary) self._upload_files(dir_path, f, file_item, binary)
except Exception as e: except Exception as e:
pecan.abort(httpclient.INTERNAL_SERVER_ERROR, pecan.abort(
_("Failed to upload %s file: %s" % (f, e))) httpclient.INTERNAL_SERVER_ERROR,
_("Failed to upload %s file: %s" % (f, e)),
)
deploy_dicts.update({f: filename}) deploy_dicts.update({f: filename})
return deploy_dicts return deploy_dicts
@index.when(method='GET', template='json') @index.when(method="GET", template="json")
def get(self, release=None): def get(self, release=None):
"""Get the subcloud deploy files that has been uploaded and stored. """Get the subcloud deploy files that has been uploaded and stored.
:param release: release version :param release: release version
""" """
policy.authorize(subcloud_deploy_policy.POLICY_ROOT % "get", {}, policy.authorize(
restcomm.extract_credentials_for_policy()) subcloud_deploy_policy.POLICY_ROOT % "get",
{},
restcomm.extract_credentials_for_policy(),
)
deploy_dicts = dict() deploy_dicts = dict()
deploy_dicts['software_version'] = utils.get_sw_version(release) deploy_dicts["software_version"] = utils.get_sw_version(release)
dir_path = os.path.join( dir_path = os.path.join(
dccommon_consts.DEPLOY_DIR, deploy_dicts['software_version'] dccommon_consts.DEPLOY_DIR, deploy_dicts["software_version"]
) )
for f in consts.DEPLOY_COMMON_FILE_OPTIONS: for f in consts.DEPLOY_COMMON_FILE_OPTIONS:
filename = None filename = None
if os.path.isdir(dir_path): if os.path.isdir(dir_path):
prefix = f + '_' prefix = f + "_"
filename = utils.get_filename_by_prefix(dir_path, prefix) filename = utils.get_filename_by_prefix(dir_path, prefix)
if filename is not None: if filename is not None:
filename = filename.replace(prefix, '', 1) filename = filename.replace(prefix, "", 1)
deploy_dicts.update({f: filename}) deploy_dicts.update({f: filename})
return dict(subcloud_deploy=deploy_dicts) return dict(subcloud_deploy=deploy_dicts)
@index.when(method='DELETE', template='json') @index.when(method="DELETE", template="json")
def delete(self, release=None): def delete(self, release=None):
"""Delete the subcloud deploy files. """Delete the subcloud deploy files.
:param release: release version :param release: release version
""" """
policy.authorize(subcloud_deploy_policy.POLICY_ROOT % "delete", {}, policy.authorize(
restcomm.extract_credentials_for_policy()) subcloud_deploy_policy.POLICY_ROOT % "delete",
{},
restcomm.extract_credentials_for_policy(),
)
is_prestage_images = \ is_prestage_images = request.params.get("prestage_images", "").lower() == "true"
request.params.get('prestage_images', '').lower() == 'true' is_deployment_files = (
is_deployment_files = \ request.params.get("deployment_files", "").lower() == "true"
request.params.get('deployment_files', '').lower() == 'true' )
dir_path = \ dir_path = os.path.join(
os.path.join(dccommon_consts.DEPLOY_DIR, utils.get_sw_version(release)) dccommon_consts.DEPLOY_DIR, utils.get_sw_version(release)
)
if not os.path.isdir(dir_path): if not os.path.isdir(dir_path):
pecan.abort(httpclient.NOT_FOUND, pecan.abort(httpclient.NOT_FOUND, _("Directory not found: %s" % dir_path))
_("Directory not found: %s" % dir_path))
try: try:
file_options = [] file_options = []
if is_prestage_images: if is_prestage_images:
file_options.append(consts.DEPLOY_PRESTAGE) file_options.append(consts.DEPLOY_PRESTAGE)
if is_deployment_files: if is_deployment_files:
file_options.extend([consts.DEPLOY_OVERRIDES, consts.DEPLOY_CHART, file_options.extend(
consts.DEPLOY_PLAYBOOK]) [
consts.DEPLOY_OVERRIDES,
consts.DEPLOY_CHART,
consts.DEPLOY_PLAYBOOK,
]
)
if not (is_deployment_files or is_prestage_images): if not (is_deployment_files or is_prestage_images):
file_options.extend(consts.DEPLOY_COMMON_FILE_OPTIONS) file_options.extend(consts.DEPLOY_COMMON_FILE_OPTIONS)
for file_option in file_options: for file_option in file_options:
prefix = file_option + '_' prefix = file_option + "_"
file_name = utils.get_filename_by_prefix(dir_path, prefix) file_name = utils.get_filename_by_prefix(dir_path, prefix)
if file_name: if file_name:
os.remove(os.path.join(dir_path, file_name)) os.remove(os.path.join(dir_path, file_name))
else: else:
LOG.warning('%s file not present' % file_option) LOG.warning("%s file not present" % file_option)
except Exception as e: except Exception as e:
pecan.abort(httpclient.INTERNAL_SERVER_ERROR, pecan.abort(
_("Failed to delete file: %s" % e)) httpclient.INTERNAL_SERVER_ERROR, _("Failed to delete file: %s" % e)
)
return None return None

View File

@@ -38,7 +38,7 @@ LOG = logging.getLogger(__name__)
SUPPORTED_GROUP_APPLY_TYPES = [ SUPPORTED_GROUP_APPLY_TYPES = [
consts.SUBCLOUD_APPLY_TYPE_PARALLEL, consts.SUBCLOUD_APPLY_TYPE_PARALLEL,
consts.SUBCLOUD_APPLY_TYPE_SERIAL consts.SUBCLOUD_APPLY_TYPE_SERIAL,
] ]
# validation constants for Subcloud Group # validation constants for Subcloud Group
@@ -54,7 +54,7 @@ class SubcloudGroupsController(restcomm.GenericPathController):
super(SubcloudGroupsController, self).__init__() super(SubcloudGroupsController, self).__init__()
self.rpc_client = rpc_client.ManagerClient() self.rpc_client = rpc_client.ManagerClient()
@expose(generic=True, template='json') @expose(generic=True, template="json")
def index(self): def index(self):
# Route the request to specific methods with parameters # Route the request to specific methods with parameters
pass pass
@@ -72,17 +72,20 @@ class SubcloudGroupsController(restcomm.GenericPathController):
subcloud_group_list.append(group_dict) subcloud_group_list.append(group_dict)
result = dict() result = dict()
result['subcloud_groups'] = subcloud_group_list result["subcloud_groups"] = subcloud_group_list
return result return result
@index.when(method='GET', template='json') @index.when(method="GET", template="json")
def get(self, group_ref=None, subclouds=False): def get(self, group_ref=None, subclouds=False):
"""Get details about subcloud group. """Get details about subcloud group.
:param group_ref: ID or name of subcloud group :param group_ref: ID or name of subcloud group
""" """
policy.authorize(subcloud_group_policy.POLICY_ROOT % "get", {}, policy.authorize(
restcomm.extract_credentials_for_policy()) subcloud_group_policy.POLICY_ROOT % "get",
{},
restcomm.extract_credentials_for_policy(),
)
context = restcomm.extract_context_from_environ() context = restcomm.extract_context_from_environ()
if group_ref is None: if group_ref is None:
@@ -91,7 +94,7 @@ class SubcloudGroupsController(restcomm.GenericPathController):
group = utils.subcloud_group_get_by_ref(context, group_ref) group = utils.subcloud_group_get_by_ref(context, group_ref)
if group is None: if group is None:
pecan.abort(httpclient.NOT_FOUND, _('Subcloud Group not found')) pecan.abort(httpclient.NOT_FOUND, _("Subcloud Group not found"))
if subclouds: if subclouds:
# Return only the subclouds for this subcloud group # Return only the subclouds for this subcloud group
return self._get_subcloud_list_for_group(context, group.id) return self._get_subcloud_list_for_group(context, group.id)
@@ -128,107 +131,114 @@ class SubcloudGroupsController(restcomm.GenericPathController):
return False return False
return True return True
@index.when(method='POST', template='json') @index.when(method="POST", template="json")
def post(self): def post(self):
"""Create a new subcloud group.""" """Create a new subcloud group."""
policy.authorize(subcloud_group_policy.POLICY_ROOT % "create", {}, policy.authorize(
restcomm.extract_credentials_for_policy()) subcloud_group_policy.POLICY_ROOT % "create",
{},
restcomm.extract_credentials_for_policy(),
)
context = restcomm.extract_context_from_environ() context = restcomm.extract_context_from_environ()
payload = eval(request.body) payload = eval(request.body)
if not payload: if not payload:
pecan.abort(httpclient.BAD_REQUEST, _('Body required')) pecan.abort(httpclient.BAD_REQUEST, _("Body required"))
name = payload.get('name') name = payload.get("name")
description = payload.get('description') description = payload.get("description")
update_apply_type = payload.get('update_apply_type') update_apply_type = payload.get("update_apply_type")
max_parallel_subclouds = payload.get('max_parallel_subclouds') max_parallel_subclouds = payload.get("max_parallel_subclouds")
# Validate payload # Validate payload
if not utils.validate_name(name, if not utils.validate_name(
prohibited_name_list=[ name, prohibited_name_list=[consts.DEFAULT_SUBCLOUD_GROUP_NAME]
consts.DEFAULT_SUBCLOUD_GROUP_NAME]): ):
pecan.abort(httpclient.BAD_REQUEST, _('Invalid group name')) pecan.abort(httpclient.BAD_REQUEST, _("Invalid group name"))
if not self._validate_description(description): if not self._validate_description(description):
pecan.abort(httpclient.BAD_REQUEST, _('Invalid group description')) pecan.abort(httpclient.BAD_REQUEST, _("Invalid group description"))
if not self._validate_update_apply_type(update_apply_type): if not self._validate_update_apply_type(update_apply_type):
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(httpclient.BAD_REQUEST, _("Invalid group update_apply_type"))
_('Invalid group update_apply_type'))
if not self._validate_max_parallel_subclouds(max_parallel_subclouds): if not self._validate_max_parallel_subclouds(max_parallel_subclouds):
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(
_('Invalid group max_parallel_subclouds')) httpclient.BAD_REQUEST, _("Invalid group max_parallel_subclouds")
)
try: try:
group_ref = db_api.subcloud_group_create(context, group_ref = db_api.subcloud_group_create(
name, context, name, description, update_apply_type, max_parallel_subclouds
description, )
update_apply_type,
max_parallel_subclouds)
return db_api.subcloud_group_db_model_to_dict(group_ref) return db_api.subcloud_group_db_model_to_dict(group_ref)
except db_exc.DBDuplicateEntry: except db_exc.DBDuplicateEntry:
LOG.info("Group create failed. Group %s already exists" % name) LOG.info("Group create failed. Group %s already exists" % name)
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(
_('A subcloud group with this name already exists')) httpclient.BAD_REQUEST,
_("A subcloud group with this name already exists"),
)
except RemoteError as e: except RemoteError as e:
pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value) pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value)
except Exception as e: except Exception as e:
LOG.exception(e) LOG.exception(e)
pecan.abort(httpclient.INTERNAL_SERVER_ERROR, pecan.abort(
_('Unable to create subcloud group')) httpclient.INTERNAL_SERVER_ERROR, _("Unable to create subcloud group")
)
@index.when(method='PATCH', template='json') @index.when(method="PATCH", template="json")
def patch(self, group_ref): def patch(self, group_ref):
"""Update a subcloud group. """Update a subcloud group.
:param group_ref: ID or name of subcloud group to update :param group_ref: ID or name of subcloud group to update
""" """
policy.authorize(subcloud_group_policy.POLICY_ROOT % "modify", {}, policy.authorize(
restcomm.extract_credentials_for_policy()) subcloud_group_policy.POLICY_ROOT % "modify",
{},
restcomm.extract_credentials_for_policy(),
)
context = restcomm.extract_context_from_environ() context = restcomm.extract_context_from_environ()
if group_ref is None: if group_ref is None:
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(httpclient.BAD_REQUEST, _("Subcloud Group Name or ID required"))
_('Subcloud Group Name or ID required'))
payload = eval(request.body) payload = eval(request.body)
if not payload: if not payload:
pecan.abort(httpclient.BAD_REQUEST, _('Body required')) pecan.abort(httpclient.BAD_REQUEST, _("Body required"))
group = utils.subcloud_group_get_by_ref(context, group_ref) group = utils.subcloud_group_get_by_ref(context, group_ref)
if group is None: if group is None:
pecan.abort(httpclient.NOT_FOUND, _('Subcloud Group not found')) pecan.abort(httpclient.NOT_FOUND, _("Subcloud Group not found"))
name = payload.get('name') name = payload.get("name")
description = payload.get('description') description = payload.get("description")
update_apply_type = payload.get('update_apply_type') update_apply_type = payload.get("update_apply_type")
max_parallel_str = payload.get('max_parallel_subclouds') max_parallel_str = payload.get("max_parallel_subclouds")
if not (name or description or update_apply_type or max_parallel_str): if not (name or description or update_apply_type or max_parallel_str):
pecan.abort(httpclient.BAD_REQUEST, _('nothing to update')) pecan.abort(httpclient.BAD_REQUEST, _("nothing to update"))
# Check value is not None or empty before calling validate # Check value is not None or empty before calling validate
if name: if name:
if not utils.validate_name(name, if not utils.validate_name(
prohibited_name_list=[ name, prohibited_name_list=[consts.DEFAULT_SUBCLOUD_GROUP_NAME]
consts.DEFAULT_SUBCLOUD_GROUP_NAME]): ):
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(httpclient.BAD_REQUEST, _("Invalid group name"))
_('Invalid group name'))
# Special case. Default group name cannot be changed # Special case. Default group name cannot be changed
if group.id == consts.DEFAULT_SUBCLOUD_GROUP_ID: if group.id == consts.DEFAULT_SUBCLOUD_GROUP_ID:
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(
_('Default group name cannot be changed')) httpclient.BAD_REQUEST, _("Default group name cannot be changed")
)
if description: if description:
if not self._validate_description(description): if not self._validate_description(description):
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(httpclient.BAD_REQUEST, _("Invalid group description"))
_('Invalid group description'))
if update_apply_type: if update_apply_type:
if not self._validate_update_apply_type(update_apply_type): if not self._validate_update_apply_type(update_apply_type):
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(
_('Invalid group update_apply_type')) httpclient.BAD_REQUEST, _("Invalid group update_apply_type")
)
if max_parallel_str: if max_parallel_str:
if not self._validate_max_parallel_subclouds(max_parallel_str): if not self._validate_max_parallel_subclouds(max_parallel_str):
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(
_('Invalid group max_parallel_subclouds')) httpclient.BAD_REQUEST, _("Invalid group max_parallel_subclouds")
)
try: try:
updated_group = db_api.subcloud_group_update( updated_group = db_api.subcloud_group_update(
@@ -237,44 +247,49 @@ class SubcloudGroupsController(restcomm.GenericPathController):
name=name, name=name,
description=description, description=description,
update_apply_type=update_apply_type, update_apply_type=update_apply_type,
max_parallel_subclouds=max_parallel_str) max_parallel_subclouds=max_parallel_str,
)
return db_api.subcloud_group_db_model_to_dict(updated_group) return db_api.subcloud_group_db_model_to_dict(updated_group)
except RemoteError as e: except RemoteError as e:
pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value) pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value)
except Exception as e: except Exception as e:
# additional exceptions. # additional exceptions.
LOG.exception(e) LOG.exception(e)
pecan.abort(httpclient.INTERNAL_SERVER_ERROR, pecan.abort(
_('Unable to update subcloud group')) httpclient.INTERNAL_SERVER_ERROR, _("Unable to update subcloud group")
)
@index.when(method='delete', template='json') @index.when(method="delete", template="json")
def delete(self, group_ref): def delete(self, group_ref):
"""Delete the subcloud group.""" """Delete the subcloud group."""
policy.authorize(subcloud_group_policy.POLICY_ROOT % "delete", {}, policy.authorize(
restcomm.extract_credentials_for_policy()) subcloud_group_policy.POLICY_ROOT % "delete",
{},
restcomm.extract_credentials_for_policy(),
)
context = restcomm.extract_context_from_environ() context = restcomm.extract_context_from_environ()
if group_ref is None: if group_ref is None:
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(httpclient.BAD_REQUEST, _("Subcloud Group Name or ID required"))
_('Subcloud Group Name or ID required'))
group = utils.subcloud_group_get_by_ref(context, group_ref) group = utils.subcloud_group_get_by_ref(context, group_ref)
if group is None: if group is None:
pecan.abort(httpclient.NOT_FOUND, _('Subcloud Group not found')) pecan.abort(httpclient.NOT_FOUND, _("Subcloud Group not found"))
if group.name == consts.DEFAULT_SUBCLOUD_GROUP_NAME: if group.name == consts.DEFAULT_SUBCLOUD_GROUP_NAME:
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(
_('Default Subcloud Group may not be deleted')) httpclient.BAD_REQUEST, _("Default Subcloud Group may not be deleted")
)
try: try:
# a subcloud group may not be deleted if it is use by any subclouds # a subcloud group may not be deleted if it is use by any subclouds
subclouds = db_api.subcloud_get_for_group(context, group.id) subclouds = db_api.subcloud_get_for_group(context, group.id)
if len(subclouds) > 0: if len(subclouds) > 0:
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(httpclient.BAD_REQUEST, _("Subcloud Group not empty"))
_('Subcloud Group not empty'))
db_api.subcloud_group_destroy(context, group.id) db_api.subcloud_group_destroy(context, group.id)
except RemoteError as e: except RemoteError as e:
pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value) pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value)
except Exception as e: except Exception as e:
LOG.exception(e) LOG.exception(e)
pecan.abort(httpclient.INTERNAL_SERVER_ERROR, pecan.abort(
_('Unable to delete subcloud group')) httpclient.INTERNAL_SERVER_ERROR, _("Unable to delete subcloud group")
)
# This should return nothing # This should return nothing
return None return None

View File

@@ -42,10 +42,7 @@ MAX_SUBCLOUD_PEER_GROUP_PRIORITY = 65536
MIN_SUBCLOUD_PEER_GROUP_PRIORITY = 0 MIN_SUBCLOUD_PEER_GROUP_PRIORITY = 0
DEFAULT_SUBCLOUD_PEER_GROUP_PRIORITY = 0 DEFAULT_SUBCLOUD_PEER_GROUP_PRIORITY = 0
DEFAULT_SUBCLOUD_PEER_GROUP_MAX_REHOMING = 10 DEFAULT_SUBCLOUD_PEER_GROUP_MAX_REHOMING = 10
SUPPORTED_GROUP_STATES = [ SUPPORTED_GROUP_STATES = [consts.OPERATIONAL_ENABLED, consts.OPERATIONAL_DISABLED]
consts.OPERATIONAL_ENABLED,
consts.OPERATIONAL_DISABLED
]
class SubcloudPeerGroupsController(restcomm.GenericPathController): class SubcloudPeerGroupsController(restcomm.GenericPathController):
@@ -54,7 +51,7 @@ class SubcloudPeerGroupsController(restcomm.GenericPathController):
super(SubcloudPeerGroupsController, self).__init__() super(SubcloudPeerGroupsController, self).__init__()
self.rpc_client = rpc_client.ManagerClient() self.rpc_client = rpc_client.ManagerClient()
@expose(generic=True, template='json') @expose(generic=True, template="json")
def index(self): def index(self):
# Route the request to specific methods with parameters # Route the request to specific methods with parameters
pass pass
@@ -71,7 +68,7 @@ class SubcloudPeerGroupsController(restcomm.GenericPathController):
group_dict = db_api.subcloud_peer_group_db_model_to_dict(group) group_dict = db_api.subcloud_peer_group_db_model_to_dict(group)
subcloud_peer_group_list.append(group_dict) subcloud_peer_group_list.append(group_dict)
result = {'subcloud_peer_groups': subcloud_peer_group_list} result = {"subcloud_peer_groups": subcloud_peer_group_list}
return result return result
def _get_local_system(self): def _get_local_system(self):
@@ -84,46 +81,46 @@ class SubcloudPeerGroupsController(restcomm.GenericPathController):
sysinv_client = SysinvClient( sysinv_client = SysinvClient(
dccommon_consts.DEFAULT_REGION_NAME, dccommon_consts.DEFAULT_REGION_NAME,
ks_client.keystone_client.session, ks_client.keystone_client.session,
endpoint=ks_client.keystone_client.endpoint_cache.get_endpoint endpoint=ks_client.keystone_client.endpoint_cache.get_endpoint(
("sysinv"), "sysinv"
),
) )
system = sysinv_client.get_system() system = sysinv_client.get_system()
return system return system
except Exception: except Exception:
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(httpclient.BAD_REQUEST, _("Failed to get local system info"))
_("Failed to get local system info"))
def _get_subcloud_status_for_peer_group(self, context, group): def _get_subcloud_status_for_peer_group(self, context, group):
subclouds = db_api.subcloud_get_for_peer_group(context, group.id) subclouds = db_api.subcloud_get_for_peer_group(context, group.id)
pg_status = dict() pg_status = dict()
pg_status['peer_group_id'] = group.id pg_status["peer_group_id"] = group.id
pg_status['peer_group_name'] = group.peer_group_name pg_status["peer_group_name"] = group.peer_group_name
pg_status['total_subclouds'] = len(subclouds) pg_status["total_subclouds"] = len(subclouds)
pg_status['complete'] = 0 pg_status["complete"] = 0
pg_status['waiting_for_migrate'] = 0 pg_status["waiting_for_migrate"] = 0
pg_status['rehoming'] = 0 pg_status["rehoming"] = 0
pg_status['rehome_failed'] = 0 pg_status["rehome_failed"] = 0
pg_status['managed'] = 0 pg_status["managed"] = 0
pg_status['unmanaged'] = 0 pg_status["unmanaged"] = 0
for subcloud in subclouds: for subcloud in subclouds:
if subcloud.management_state == 'managed': if subcloud.management_state == "managed":
pg_status['managed'] += 1 pg_status["managed"] += 1
else: else:
pg_status['unmanaged'] += 1 pg_status["unmanaged"] += 1
if subcloud.deploy_status == 'secondary': if subcloud.deploy_status == "secondary":
pg_status['waiting_for_migrate'] += 1 pg_status["waiting_for_migrate"] += 1
elif subcloud.deploy_status == 'rehome-failed': elif subcloud.deploy_status == "rehome-failed":
pg_status['rehome_failed'] += 1 pg_status["rehome_failed"] += 1
elif subcloud.deploy_status == 'rehome-prep-failed': elif subcloud.deploy_status == "rehome-prep-failed":
pg_status['rehome_failed'] += 1 pg_status["rehome_failed"] += 1
elif subcloud.deploy_status == 'complete': elif subcloud.deploy_status == "complete":
pg_status['complete'] += 1 pg_status["complete"] += 1
elif subcloud.deploy_status == 'rehoming': elif subcloud.deploy_status == "rehoming":
pg_status['rehoming'] += 1 pg_status["rehoming"] += 1
return pg_status return pg_status
@index.when(method='GET', template='json') @index.when(method="GET", template="json")
def get(self, group_ref=None, verb=None): def get(self, group_ref=None, verb=None):
"""Get details about subcloud peer group. """Get details about subcloud peer group.
@@ -131,8 +128,11 @@ class SubcloudPeerGroupsController(restcomm.GenericPathController):
to the subcloud-peer-group get operation to the subcloud-peer-group get operation
:param group_ref: ID or name of subcloud peer group :param group_ref: ID or name of subcloud peer group
""" """
policy.authorize(subcloud_peer_group_policy.POLICY_ROOT % "get", {}, policy.authorize(
restcomm.extract_credentials_for_policy()) subcloud_peer_group_policy.POLICY_ROOT % "get",
{},
restcomm.extract_credentials_for_policy(),
)
context = restcomm.extract_context_from_environ() context = restcomm.extract_context_from_environ()
if group_ref is None: if group_ref is None:
@@ -143,42 +143,45 @@ class SubcloudPeerGroupsController(restcomm.GenericPathController):
if group is None: if group is None:
pecan.abort(httpclient.NOT_FOUND, _("Subcloud Peer Group not found")) pecan.abort(httpclient.NOT_FOUND, _("Subcloud Peer Group not found"))
if verb is None: if verb is None:
subcloud_peer_group_dict = \ subcloud_peer_group_dict = db_api.subcloud_peer_group_db_model_to_dict(
db_api.subcloud_peer_group_db_model_to_dict(group) group
)
return subcloud_peer_group_dict return subcloud_peer_group_dict
elif verb == 'subclouds': elif verb == "subclouds":
# Return only the subclouds for this subcloud peer group # Return only the subclouds for this subcloud peer group
return self._get_subcloud_list_for_peer_group(context, group.id) return self._get_subcloud_list_for_peer_group(context, group.id)
elif verb == 'status': elif verb == "status":
return self._get_subcloud_status_for_peer_group(context, group) return self._get_subcloud_status_for_peer_group(context, group)
else: else:
pecan.abort(400, _('Invalid request')) pecan.abort(400, _("Invalid request"))
@index.when(method='POST', template='json') @index.when(method="POST", template="json")
def post(self): def post(self):
"""Create a new subcloud peer group.""" """Create a new subcloud peer group."""
policy.authorize(subcloud_peer_group_policy.POLICY_ROOT % "create", {}, policy.authorize(
restcomm.extract_credentials_for_policy()) subcloud_peer_group_policy.POLICY_ROOT % "create",
{},
restcomm.extract_credentials_for_policy(),
)
context = restcomm.extract_context_from_environ() context = restcomm.extract_context_from_environ()
payload = json.loads(request.body) payload = json.loads(request.body)
if not payload: if not payload:
pecan.abort(httpclient.BAD_REQUEST, _('Body required')) pecan.abort(httpclient.BAD_REQUEST, _("Body required"))
LOG.info("Handling create subcloud peer group request for: %s" % payload) LOG.info("Handling create subcloud peer group request for: %s" % payload)
peer_group_name = payload.get('peer-group-name') peer_group_name = payload.get("peer-group-name")
group_priority = payload.get('group-priority') group_priority = payload.get("group-priority")
group_state = payload.get('group-state') group_state = payload.get("group-state")
system_leader_id = payload.get('system-leader-id') system_leader_id = payload.get("system-leader-id")
system_leader_name = payload.get('system-leader-name') system_leader_name = payload.get("system-leader-name")
max_subcloud_rehoming = payload.get('max-subcloud-rehoming') max_subcloud_rehoming = payload.get("max-subcloud-rehoming")
local_system = None local_system = None
# Validate payload # Validate payload
# peer_group_name is mandatory # peer_group_name is mandatory
if not utils.validate_name(peer_group_name, if not utils.validate_name(peer_group_name, prohibited_name_list=["none"]):
prohibited_name_list=['none']): pecan.abort(httpclient.BAD_REQUEST, _("Invalid peer-group-name"))
pecan.abort(httpclient.BAD_REQUEST, _('Invalid peer-group-name'))
if not system_leader_id: if not system_leader_id:
# 1.Operator does not need to (and should not) specify # 1.Operator does not need to (and should not) specify
# system_leader_id for a local subcloud peer group which # system_leader_id for a local subcloud peer group which
@@ -191,8 +194,10 @@ class SubcloudPeerGroupsController(restcomm.GenericPathController):
local_system = self._get_local_system() local_system = self._get_local_system()
system_leader_id = local_system.uuid system_leader_id = local_system.uuid
elif not self._validate_system_leader_id(system_leader_id): elif not self._validate_system_leader_id(system_leader_id):
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(
_('Invalid system-leader-id [%s]' % (system_leader_id))) httpclient.BAD_REQUEST,
_("Invalid system-leader-id [%s]" % (system_leader_id)),
)
if not system_leader_name: if not system_leader_name:
# Get system_leader_name from local DC # Get system_leader_name from local DC
# if no system_leader_name provided # if no system_leader_name provided
@@ -200,43 +205,46 @@ class SubcloudPeerGroupsController(restcomm.GenericPathController):
local_system = self._get_local_system() local_system = self._get_local_system()
system_leader_name = local_system.name system_leader_name = local_system.name
elif not utils.validate_name(system_leader_name): elif not utils.validate_name(system_leader_name):
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(httpclient.BAD_REQUEST, _("Invalid system-leader-name"))
_('Invalid system-leader-name'))
if group_priority is None: if group_priority is None:
group_priority = DEFAULT_SUBCLOUD_PEER_GROUP_PRIORITY group_priority = DEFAULT_SUBCLOUD_PEER_GROUP_PRIORITY
elif not self._validate_group_priority(group_priority): elif not self._validate_group_priority(group_priority):
pecan.abort(httpclient.BAD_REQUEST, _('Invalid group-priority')) pecan.abort(httpclient.BAD_REQUEST, _("Invalid group-priority"))
if not group_state: if not group_state:
group_state = consts.OPERATIONAL_ENABLED group_state = consts.OPERATIONAL_ENABLED
elif not self._validate_group_state(group_state): elif not self._validate_group_state(group_state):
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(httpclient.BAD_REQUEST, _("Invalid group-state"))
_('Invalid group-state'))
if max_subcloud_rehoming is None: if max_subcloud_rehoming is None:
max_subcloud_rehoming = DEFAULT_SUBCLOUD_PEER_GROUP_MAX_REHOMING max_subcloud_rehoming = DEFAULT_SUBCLOUD_PEER_GROUP_MAX_REHOMING
elif not self._validate_max_subcloud_rehoming(max_subcloud_rehoming): elif not self._validate_max_subcloud_rehoming(max_subcloud_rehoming):
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(httpclient.BAD_REQUEST, _("Invalid max-subcloud-rehoming"))
_('Invalid max-subcloud-rehoming'))
try: try:
group_ref = db_api.subcloud_peer_group_create(context, group_ref = db_api.subcloud_peer_group_create(
peer_group_name, context,
group_priority, peer_group_name,
group_state, group_priority,
max_subcloud_rehoming, group_state,
system_leader_id, max_subcloud_rehoming,
system_leader_name) system_leader_id,
system_leader_name,
)
return db_api.subcloud_peer_group_db_model_to_dict(group_ref) return db_api.subcloud_peer_group_db_model_to_dict(group_ref)
except db_exc.DBDuplicateEntry: except db_exc.DBDuplicateEntry:
pecan.abort(httpclient.CONFLICT, pecan.abort(
_('A subcloud peer group with this name already exists')) httpclient.CONFLICT,
_("A subcloud peer group with this name already exists"),
)
except RemoteError as e: except RemoteError as e:
pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value) pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value)
except Exception as e: except Exception as e:
LOG.exception(e) LOG.exception(e)
pecan.abort(httpclient.INTERNAL_SERVER_ERROR, pecan.abort(
_('Unable to create subcloud peer group')) httpclient.INTERNAL_SERVER_ERROR,
_("Unable to create subcloud peer group"),
)
@index.when(method='PATCH', template='json') @index.when(method="PATCH", template="json")
def patch(self, group_ref, verb=None): def patch(self, group_ref, verb=None):
"""Update a subcloud peer group. """Update a subcloud peer group.
@@ -245,36 +253,40 @@ class SubcloudPeerGroupsController(restcomm.GenericPathController):
:param group_ref: ID or name of subcloud group to update :param group_ref: ID or name of subcloud group to update
""" """
policy.authorize(subcloud_peer_group_policy.POLICY_ROOT % "modify", {}, policy.authorize(
restcomm.extract_credentials_for_policy()) subcloud_peer_group_policy.POLICY_ROOT % "modify",
{},
restcomm.extract_credentials_for_policy(),
)
context = restcomm.extract_context_from_environ() context = restcomm.extract_context_from_environ()
if group_ref is None: if group_ref is None:
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(
_('Subcloud Peer Group Name or ID required')) httpclient.BAD_REQUEST, _("Subcloud Peer Group Name or ID required")
)
group = utils.subcloud_peer_group_get_by_ref(context, group_ref) group = utils.subcloud_peer_group_get_by_ref(context, group_ref)
if group is None: if group is None:
pecan.abort(httpclient.NOT_FOUND, _('Subcloud Peer Group not found')) pecan.abort(httpclient.NOT_FOUND, _("Subcloud Peer Group not found"))
if verb is None: if verb is None:
payload = json.loads(request.body) payload = json.loads(request.body)
if not payload: if not payload:
pecan.abort(httpclient.BAD_REQUEST, _('Body required')) pecan.abort(httpclient.BAD_REQUEST, _("Body required"))
if group.group_priority > 0 and \ if group.group_priority > 0 and not utils.is_req_from_another_dc(request):
not utils.is_req_from_another_dc(request): pecan.abort(
pecan.abort(httpclient.BAD_REQUEST, httpclient.BAD_REQUEST,
_("Cannot update a peer group from a non-primary " _("Cannot update a peer group from a non-primary site."),
"site.")) )
LOG.info("Handling update subcloud peer group request for: %s" % payload) LOG.info("Handling update subcloud peer group request for: %s" % payload)
peer_group_name = payload.get('peer-group-name') peer_group_name = payload.get("peer-group-name")
group_priority = payload.get('group-priority') group_priority = payload.get("group-priority")
group_state = payload.get('group-state') group_state = payload.get("group-state")
system_leader_id = payload.get('system-leader-id') system_leader_id = payload.get("system-leader-id")
system_leader_name = payload.get('system-leader-name') system_leader_name = payload.get("system-leader-name")
max_subcloud_rehoming = payload.get('max-subcloud-rehoming') max_subcloud_rehoming = payload.get("max-subcloud-rehoming")
if 'migration_status' in payload: if "migration_status" in payload:
migration_status = payload.get('migration_status') migration_status = payload.get("migration_status")
if migration_status is None: if migration_status is None:
migration_status = consts.PEER_GROUP_MIGRATION_NONE migration_status = consts.PEER_GROUP_MIGRATION_NONE
else: else:
@@ -289,84 +301,91 @@ class SubcloudPeerGroupsController(restcomm.GenericPathController):
or max_subcloud_rehoming is not None or max_subcloud_rehoming is not None
or migration_status or migration_status
): ):
pecan.abort(httpclient.BAD_REQUEST, _('nothing to update')) pecan.abort(httpclient.BAD_REQUEST, _("nothing to update"))
# The flag to indicate if the update needs to be synced to # The flag to indicate if the update needs to be synced to
# the peer site(s). # the peer site(s).
sync_needed = ((peer_group_name and sync_needed = (
peer_group_name != group.peer_group_name) or (peer_group_name and peer_group_name != group.peer_group_name)
(group_state and or (group_state and group_state != group.group_state)
group_state != group.group_state) or or (
(max_subcloud_rehoming is not None and max_subcloud_rehoming is not None
max_subcloud_rehoming != group.max_subcloud_rehoming)) and max_subcloud_rehoming != group.max_subcloud_rehoming
)
)
any_update = (sync_needed or any_update = sync_needed or (
((group_priority is not None and (group_priority is not None and group_priority != group.group_priority)
group_priority != group.group_priority) or or (system_leader_id and system_leader_id != group.system_leader_id)
(system_leader_id and or (
system_leader_id != group.system_leader_id) or system_leader_name
(system_leader_name and and system_leader_name != group.system_leader_name
system_leader_name != group.system_leader_name) or )
(migration_status and or (migration_status and migration_status != group.migration_status)
migration_status != group.migration_status))) )
if not any_update: if not any_update:
return db_api.subcloud_peer_group_db_model_to_dict(group) return db_api.subcloud_peer_group_db_model_to_dict(group)
# Check value is not None or empty before calling validation function # Check value is not None or empty before calling validation function
if (peer_group_name is not None and if peer_group_name is not None and not utils.validate_name(
not utils.validate_name(peer_group_name, peer_group_name, prohibited_name_list=["none"]
prohibited_name_list=['none'])): ):
pecan.abort(httpclient.BAD_REQUEST, _('Invalid peer-group-name')) pecan.abort(httpclient.BAD_REQUEST, _("Invalid peer-group-name"))
if (group_priority is not None and if group_priority is not None and not self._validate_group_priority(
not self._validate_group_priority(group_priority)): group_priority
pecan.abort(httpclient.BAD_REQUEST, _('Invalid group-priority')) ):
pecan.abort(httpclient.BAD_REQUEST, _("Invalid group-priority"))
if group_state and not self._validate_group_state(group_state): if group_state and not self._validate_group_state(group_state):
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(httpclient.BAD_REQUEST, _("Invalid group-state"))
_('Invalid group-state')) if (
if (max_subcloud_rehoming is not None and max_subcloud_rehoming is not None
not self._validate_max_subcloud_rehoming(max_subcloud_rehoming)): and not self._validate_max_subcloud_rehoming(max_subcloud_rehoming)
pecan.abort(httpclient.BAD_REQUEST, ):
_('Invalid max-subcloud-rehoming')) pecan.abort(httpclient.BAD_REQUEST, _("Invalid max-subcloud-rehoming"))
if (system_leader_id and if system_leader_id and not self._validate_system_leader_id(
not self._validate_system_leader_id(system_leader_id)): system_leader_id
pecan.abort(httpclient.BAD_REQUEST, ):
_('Invalid system-leader-id')) pecan.abort(httpclient.BAD_REQUEST, _("Invalid system-leader-id"))
if (system_leader_name is not None and if system_leader_name is not None and not utils.validate_name(
not utils.validate_name(system_leader_name)): system_leader_name
pecan.abort(httpclient.BAD_REQUEST, ):
_('Invalid system-leader-name')) pecan.abort(httpclient.BAD_REQUEST, _("Invalid system-leader-name"))
if (migration_status and if migration_status and migration_status.lower() not in [
migration_status.lower() not in [ consts.PEER_GROUP_MIGRATING,
consts.PEER_GROUP_MIGRATING, consts.PEER_GROUP_MIGRATION_COMPLETE,
consts.PEER_GROUP_MIGRATION_COMPLETE, consts.PEER_GROUP_MIGRATION_NONE,
consts.PEER_GROUP_MIGRATION_NONE ]:
]): pecan.abort(httpclient.BAD_REQUEST, _("Invalid migration_status"))
pecan.abort(httpclient.BAD_REQUEST,
_('Invalid migration_status'))
# Update on peer site(s) # Update on peer site(s)
if (not utils.is_req_from_another_dc(request)) and sync_needed: if (not utils.is_req_from_another_dc(request)) and sync_needed:
success_peer_ids, failed_peer_ids = \ success_peer_ids, failed_peer_ids = (
self.rpc_client.update_subcloud_peer_group( self.rpc_client.update_subcloud_peer_group(
context, group.id, group_state, max_subcloud_rehoming, context,
group.peer_group_name, peer_group_name) group.id,
group_state,
max_subcloud_rehoming,
group.peer_group_name,
peer_group_name,
)
)
if failed_peer_ids: if failed_peer_ids:
if not success_peer_ids: if not success_peer_ids:
# Reject if all failed # Reject if all failed
pecan.abort( pecan.abort(
httpclient.FORBIDDEN, httpclient.FORBIDDEN,
_('Unable to sync the update to the peer site(s)')) _("Unable to sync the update to the peer site(s)"),
)
# Local update will continue if it's only partial # Local update will continue if it's only partial
# failure. # failure.
# TODO(gherzmann): update the association sync status to # TODO(gherzmann): update the association sync status to
# out-of-date on partial failures when support for multiple # out-of-date on partial failures when support for multiple
# associations is added (geo-redundancy phase 2) # associations is added (geo-redundancy phase 2)
LOG.error(f"Failed to sync the subcloud peer group " LOG.error(
f"{group.id} update on the peer site(s) " f"Failed to sync the subcloud peer group {group.id} update on "
f"{failed_peer_ids} with the values: " f"the peer site(s) {failed_peer_ids} with the values: "
f"{group_state=}, " f"{group_state=}, {max_subcloud_rehoming=}, {peer_group_name=}"
f"{max_subcloud_rehoming=}, " )
f"{peer_group_name=}")
try: try:
updated_peer_group = db_api.subcloud_peer_group_update( updated_peer_group = db_api.subcloud_peer_group_update(
@@ -378,29 +397,31 @@ class SubcloudPeerGroupsController(restcomm.GenericPathController):
max_subcloud_rehoming=max_subcloud_rehoming, max_subcloud_rehoming=max_subcloud_rehoming,
system_leader_id=system_leader_id, system_leader_id=system_leader_id,
system_leader_name=system_leader_name, system_leader_name=system_leader_name,
migration_status=migration_status) migration_status=migration_status,
return db_api.subcloud_peer_group_db_model_to_dict(
updated_peer_group
) )
return db_api.subcloud_peer_group_db_model_to_dict(updated_peer_group)
except RemoteError as e: except RemoteError as e:
pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value) pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value)
except Exception as e: except Exception as e:
# additional exceptions. # additional exceptions.
LOG.exception(e) LOG.exception(e)
pecan.abort(httpclient.INTERNAL_SERVER_ERROR, pecan.abort(
_('Unable to update subcloud peer group')) httpclient.INTERNAL_SERVER_ERROR,
elif verb == 'migrate': _("Unable to update subcloud peer group"),
)
elif verb == "migrate":
payload = json.loads(request.body) payload = json.loads(request.body)
LOG.info("Handling migrate subcloud peer group request for: %s" % LOG.info("Handling migrate subcloud peer group request for: %s" % group_ref)
group_ref)
if not payload: if not payload:
pecan.abort(httpclient.BAD_REQUEST, _('Body required')) pecan.abort(httpclient.BAD_REQUEST, _("Body required"))
if 'sysadmin_password' not in payload: if "sysadmin_password" not in payload:
msg = ("Unable to migrate subcloud peer group: %s " msg = (
"need sysadmin_password" % group_ref) "Unable to migrate subcloud peer group: %s need sysadmin_password"
% group_ref
)
LOG.error(msg) LOG.error(msg)
pecan.abort(400, _(msg)) pecan.abort(400, _(msg))
payload['peer_group'] = group_ref payload["peer_group"] = group_ref
# Validate subclouds # Validate subclouds
subclouds = db_api.subcloud_get_for_peer_group(context, group.id) subclouds = db_api.subcloud_get_for_peer_group(context, group.id)
rehome_ready_subclouds = [] rehome_ready_subclouds = []
@@ -409,67 +430,81 @@ class SubcloudPeerGroupsController(restcomm.GenericPathController):
# Verify rehome data # Verify rehome data
rehome_data_json_str = tmp_subcloud.rehome_data rehome_data_json_str = tmp_subcloud.rehome_data
if not rehome_data_json_str: if not rehome_data_json_str:
msg = ("Unable to migrate subcloud: %s " msg = (
"required rehoming data is missing" % "Unable to migrate subcloud: %s "
tmp_subcloud.name) "required rehoming data is missing" % tmp_subcloud.name
)
err_msg_list.append(msg) err_msg_list.append(msg)
continue continue
tmp_rehome_data = json.loads(rehome_data_json_str) tmp_rehome_data = json.loads(rehome_data_json_str)
if 'saved_payload' not in tmp_rehome_data: if "saved_payload" not in tmp_rehome_data:
msg = ("Unable to migrate subcloud: %s " msg = (
"saved_payload is missing in " "Unable to migrate subcloud: %s saved_payload is missing in "
"rehoming data" % tmp_subcloud.name) "rehoming data" % tmp_subcloud.name
)
err_msg_list.append(msg) err_msg_list.append(msg)
continue continue
saved_payload = tmp_rehome_data['saved_payload'] saved_payload = tmp_rehome_data["saved_payload"]
# Validate saved_payload # Validate saved_payload
if not saved_payload: if not saved_payload:
msg = ("Unable to migrate subcloud: %s saved_payload " msg = (
"is empty" % tmp_subcloud.name) "Unable to migrate subcloud: %s saved_payload is empty"
% tmp_subcloud.name
)
err_msg_list.append(msg) err_msg_list.append(msg)
continue continue
if 'bootstrap-address' not in saved_payload: if "bootstrap-address" not in saved_payload:
msg = ("Unable to migrate subcloud: %s, " msg = (
"bootstrap-address is missing in rehoming " "Unable to migrate subcloud: %s, bootstrap-address is missing "
"data" % tmp_subcloud.name) "in rehoming data" % tmp_subcloud.name
)
err_msg_list.append(msg) err_msg_list.append(msg)
continue continue
# If any subcloud in the peer group is in 'rehoming' # If any subcloud in the peer group is in 'rehoming'
# or 'pre-rehome'state, we consider the peer group # or 'pre-rehome'state, we consider the peer group
# is already in batch rehoming, then abort. # is already in batch rehoming, then abort.
rehome_states = [consts.DEPLOY_STATE_PRE_REHOME, rehome_states = [
consts.DEPLOY_STATE_REHOMING] consts.DEPLOY_STATE_PRE_REHOME,
consts.DEPLOY_STATE_REHOMING,
]
if tmp_subcloud.deploy_status in rehome_states: if tmp_subcloud.deploy_status in rehome_states:
msg = ("Unable to migrate subcloud peer group %s, " msg = (
"subcloud %s already in rehoming process" % "Unable to migrate subcloud peer group %s, "
(group.peer_group_name, tmp_subcloud.name)) "subcloud %s already in rehoming process"
% (group.peer_group_name, tmp_subcloud.name)
)
err_msg_list.append(msg) err_msg_list.append(msg)
continue continue
# Filter for secondary/rehome-failed/rehome-prep-failed # Filter for secondary/rehome-failed/rehome-prep-failed
# subclouds, which is the correct state for rehoming # subclouds, which is the correct state for rehoming
if (tmp_subcloud.deploy_status in if tmp_subcloud.deploy_status in [
[consts.DEPLOY_STATE_SECONDARY, consts.DEPLOY_STATE_SECONDARY,
consts.DEPLOY_STATE_REHOME_FAILED, consts.DEPLOY_STATE_REHOME_FAILED,
consts.DEPLOY_STATE_REHOME_PREP_FAILED]): consts.DEPLOY_STATE_REHOME_PREP_FAILED,
]:
rehome_ready_subclouds.append(tmp_subcloud) rehome_ready_subclouds.append(tmp_subcloud)
else: else:
LOG.info("Excluding subcloud: %s from batch migration: " LOG.info(
"subcloud deploy_status is not secondary, " "Excluding subcloud: %s from batch migration: "
"rehome-failed or rehome-prep-failed" % "subcloud deploy_status is not secondary, "
tmp_subcloud.name) "rehome-failed or rehome-prep-failed" % tmp_subcloud.name
)
if err_msg_list: if err_msg_list:
for m in err_msg_list: for m in err_msg_list:
LOG.error(m) LOG.error(m)
pecan.abort(500, _("Batch migrate subclouds error: %s" % pecan.abort(500, _("Batch migrate subclouds error: %s" % err_msg_list))
err_msg_list))
if not rehome_ready_subclouds: if not rehome_ready_subclouds:
pecan.abort(400, _("Nothing to migrate, no " pecan.abort(
"secondary, rehome-failed or " 400,
"rehome-prep-failed subcloud in peer " _(
"group %s" % group.peer_group_name)) "Nothing to migrate, no secondary, rehome-failed or "
"rehome-prep-failed subcloud in peer group %s"
% group.peer_group_name
),
)
# Call batch migrate # Call batch migrate
try: try:
@@ -479,52 +514,82 @@ class SubcloudPeerGroupsController(restcomm.GenericPathController):
pecan.abort(422, e.value) pecan.abort(422, e.value)
except Exception: except Exception:
LOG.exception( LOG.exception(
"Unable to batch migrate peer group %s" % group.peer_group_name) "Unable to batch migrate peer group %s" % group.peer_group_name
pecan.abort(500, _('Unable to batch migrate ' )
'peer group %s' % group.peer_group_name)) pecan.abort(
elif verb == 'audit': 500,
_("Unable to batch migrate peer group %s" % group.peer_group_name),
)
elif verb == "audit":
payload = json.loads(request.body) payload = json.loads(request.body)
if 'peer_uuid' not in payload: if "peer_uuid" not in payload:
pecan.abort(400, _('Unable to audit peer group ' pecan.abort(
'%s, missing peer_uuid' % 400,
group.peer_group_name)) _(
if 'peer_group_name' not in payload: "Unable to audit peer group %s, missing peer_uuid"
pecan.abort(400, _('Unable to audit peer group ' % group.peer_group_name
'%s, missing peer_group_name' % ),
group.peer_group_name)) )
if 'group_priority' not in payload: if "peer_group_name" not in payload:
pecan.abort(400, _('Unable to audit peer group ' pecan.abort(
'%s, missing group_priority' % 400,
group.peer_group_name)) _(
if 'group_state' not in payload: "Unable to audit peer group %s, missing peer_group_name"
pecan.abort(400, _('Unable to audit peer group ' % group.peer_group_name
'%s, missing group_state' % ),
group.peer_group_name)) )
if 'system_leader_id' not in payload: if "group_priority" not in payload:
pecan.abort(400, _('Unable to audit peer group ' pecan.abort(
'%s, missing system_leader_id' % 400,
group.peer_group_name)) _(
if 'system_leader_name' not in payload: "Unable to audit peer group %s, missing group_priority"
pecan.abort(400, _('Unable to audit peer group ' % group.peer_group_name
'%s, missing system_leader_name' % ),
group.peer_group_name)) )
if 'migration_status' not in payload: if "group_state" not in payload:
pecan.abort(400, _('Unable to audit peer group ' pecan.abort(
'%s, missing migration_status' % 400,
group.peer_group_name)) _(
"Unable to audit peer group %s, missing group_state"
% group.peer_group_name
),
)
if "system_leader_id" not in payload:
pecan.abort(
400,
_(
"Unable to audit peer group %s, missing system_leader_id"
% group.peer_group_name
),
)
if "system_leader_name" not in payload:
pecan.abort(
400,
_(
"Unable to audit peer group %s, missing system_leader_name"
% group.peer_group_name
),
)
if "migration_status" not in payload:
pecan.abort(
400,
_(
"Unable to audit peer group %s, missing migration_status"
% group.peer_group_name
),
)
try: try:
msg = self.rpc_client.peer_group_audit_notify( msg = self.rpc_client.peer_group_audit_notify(
context, context, group.peer_group_name, payload
group.peer_group_name, )
payload)
return {"message": msg} return {"message": msg}
except Exception: except Exception:
LOG.exception('Unable to audit peer group %s' % LOG.exception("Unable to audit peer group %s" % group.peer_group_name)
group.peer_group_name) pecan.abort(
pecan.abort(500, _('Unable to audit peer group %s' % 500, _("Unable to audit peer group %s" % group.peer_group_name)
group.peer_group_name)) )
else: else:
pecan.abort(400, _('Invalid request')) pecan.abort(400, _("Invalid request"))
def _validate_group_priority(self, priority): def _validate_group_priority(self, priority):
try: try:
@@ -565,40 +630,46 @@ class SubcloudPeerGroupsController(restcomm.GenericPathController):
except Exception: except Exception:
return False return False
@index.when(method='delete', template='json') @index.when(method="delete", template="json")
def delete(self, group_ref): def delete(self, group_ref):
"""Delete the subcloud peer group.""" """Delete the subcloud peer group."""
policy.authorize(subcloud_peer_group_policy.POLICY_ROOT % "delete", {}, policy.authorize(
restcomm.extract_credentials_for_policy()) subcloud_peer_group_policy.POLICY_ROOT % "delete",
{},
restcomm.extract_credentials_for_policy(),
)
context = restcomm.extract_context_from_environ() context = restcomm.extract_context_from_environ()
if group_ref is None: if group_ref is None:
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(
_('Subcloud Peer Group Name or ID required')) httpclient.BAD_REQUEST, _("Subcloud Peer Group Name or ID required")
)
group = utils.subcloud_peer_group_get_by_ref(context, group_ref) group = utils.subcloud_peer_group_get_by_ref(context, group_ref)
if group is None: if group is None:
LOG.info("Subcloud Peer Group [%s] not found" % group_ref) LOG.info("Subcloud Peer Group [%s] not found" % group_ref)
pecan.abort(httpclient.NOT_FOUND, _('Subcloud Peer Group not found')) pecan.abort(httpclient.NOT_FOUND, _("Subcloud Peer Group not found"))
LOG.info("Handling delete subcloud peer group request for: %s" % LOG.info("Handling delete subcloud peer group request for: %s" % group)
group)
# A peer group cannot be deleted if it is used by any associations # A peer group cannot be deleted if it is used by any associations
association = db_api.peer_group_association_get_by_peer_group_id( association = db_api.peer_group_association_get_by_peer_group_id(
context, group.id) context, group.id
)
if len(association) > 0: if len(association) > 0:
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(
_("Cannot delete a peer group " httpclient.BAD_REQUEST,
"which is associated with a system peer.")) _("Cannot delete a peer group which is associated with a system peer."),
)
try: try:
db_api.subcloud_peer_group_destroy(context, group.id) db_api.subcloud_peer_group_destroy(context, group.id)
# Disassociate the subcloud. # Disassociate the subcloud.
subclouds = db_api.subcloud_get_for_peer_group(context, group.id) subclouds = db_api.subcloud_get_for_peer_group(context, group.id)
for subcloud in subclouds: for subcloud in subclouds:
db_api.subcloud_update(context, subcloud.id, db_api.subcloud_update(context, subcloud.id, peer_group_id="none")
peer_group_id='none')
except RemoteError as e: except RemoteError as e:
pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value) pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value)
except Exception as e: except Exception as e:
LOG.exception(e) LOG.exception(e)
pecan.abort(httpclient.INTERNAL_SERVER_ERROR, pecan.abort(
_('Unable to delete subcloud peer group')) httpclient.INTERNAL_SERVER_ERROR,
_("Unable to delete subcloud peer group"),
)

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
# Copyright (c) 2017 Ericsson AB. # Copyright (c) 2017 Ericsson AB.
# Copyright (c) 2017-2022 Wind River Systems, Inc. # Copyright (c) 2017-2022, 2024 Wind River Systems, Inc.
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -42,19 +42,22 @@ class SwUpdateOptionsController(object):
super(SwUpdateOptionsController, self).__init__() super(SwUpdateOptionsController, self).__init__()
self.rpc_client = rpc_client.ManagerClient() self.rpc_client = rpc_client.ManagerClient()
@expose(generic=True, template='json') @expose(generic=True, template="json")
def index(self): def index(self):
# Route the request to specific methods with parameters # Route the request to specific methods with parameters
pass pass
@index.when(method='GET', template='json') @index.when(method="GET", template="json")
def get(self, subcloud_ref=None): def get(self, subcloud_ref=None):
"""Get details about software update options. """Get details about software update options.
:param subcloud: name or id of subcloud (optional) :param subcloud: name or id of subcloud (optional)
""" """
policy.authorize(sw_update_options_policy.POLICY_ROOT % "get", {}, policy.authorize(
restcomm.extract_credentials_for_policy()) sw_update_options_policy.POLICY_ROOT % "get",
{},
restcomm.extract_credentials_for_policy(),
)
context = restcomm.extract_context_from_environ() context = restcomm.extract_context_from_environ()
if subcloud_ref is None: if subcloud_ref is None:
@@ -62,21 +65,21 @@ class SwUpdateOptionsController(object):
# Prepend the all clouds default options to the result. # Prepend the all clouds default options to the result.
result = dict() result = dict()
result['sw-update-options'] = list() result["sw-update-options"] = list()
default_sw_update_opts_dict = utils.get_sw_update_opts( default_sw_update_opts_dict = utils.get_sw_update_opts(context)
context)
result['sw-update-options'].append(default_sw_update_opts_dict) result["sw-update-options"].append(default_sw_update_opts_dict)
subclouds = db_api.sw_update_opts_get_all_plus_subcloud_info( subclouds = db_api.sw_update_opts_get_all_plus_subcloud_info(context)
context)
for subcloud, sw_update_opts in subclouds: for subcloud, sw_update_opts in subclouds:
if sw_update_opts: if sw_update_opts:
result['sw-update-options'].append( result["sw-update-options"].append(
db_api.sw_update_opts_w_name_db_model_to_dict( db_api.sw_update_opts_w_name_db_model_to_dict(
sw_update_opts, subcloud.name)) sw_update_opts, subcloud.name
)
)
return result return result
@@ -93,22 +96,20 @@ class SwUpdateOptionsController(object):
try: try:
subcloud = db_api.subcloud_get(context, subcloud_ref) subcloud = db_api.subcloud_get(context, subcloud_ref)
except exceptions.SubcloudNotFound: except exceptions.SubcloudNotFound:
pecan.abort(404, _('Subcloud not found')) pecan.abort(404, _("Subcloud not found"))
else: else:
# Look up subcloud by name # Look up subcloud by name
try: try:
subcloud = db_api.subcloud_get_by_name(context, subcloud = db_api.subcloud_get_by_name(context, subcloud_ref)
subcloud_ref)
except exceptions.SubcloudNameNotFound: except exceptions.SubcloudNameNotFound:
pecan.abort(404, _('Subcloud not found')) pecan.abort(404, _("Subcloud not found"))
try: try:
return utils.get_sw_update_opts( return utils.get_sw_update_opts(context, subcloud_id=subcloud.id)
context, subcloud_id=subcloud.id)
except Exception as e: except Exception as e:
pecan.abort(404, _('%s') % e) pecan.abort(404, _("%s") % e)
@index.when(method='POST', template='json') @index.when(method="POST", template="json")
def post(self, subcloud_ref=None): def post(self, subcloud_ref=None):
"""Update or create sw update options. """Update or create sw update options.
@@ -118,13 +119,16 @@ class SwUpdateOptionsController(object):
# Note creating or updating subcloud specific options require # Note creating or updating subcloud specific options require
# setting all options. # setting all options.
policy.authorize(sw_update_options_policy.POLICY_ROOT % "update", {}, policy.authorize(
restcomm.extract_credentials_for_policy()) sw_update_options_policy.POLICY_ROOT % "update",
{},
restcomm.extract_credentials_for_policy(),
)
context = restcomm.extract_context_from_environ() context = restcomm.extract_context_from_environ()
payload = eval(request.body) payload = eval(request.body)
if not payload: if not payload:
pecan.abort(400, _('Body required')) pecan.abort(400, _("Body required"))
if subcloud_ref == dccommon_consts.DEFAULT_REGION_NAME: if subcloud_ref == dccommon_consts.DEFAULT_REGION_NAME:
@@ -136,11 +140,12 @@ class SwUpdateOptionsController(object):
try: try:
sw_update_opts_ref = db_api.sw_update_opts_default_update( sw_update_opts_ref = db_api.sw_update_opts_default_update(
context, context,
payload['storage-apply-type'], payload["storage-apply-type"],
payload['worker-apply-type'], payload["worker-apply-type"],
payload['max-parallel-workers'], payload["max-parallel-workers"],
payload['alarm-restriction-type'], payload["alarm-restriction-type"],
payload['default-instance-action']) payload["default-instance-action"],
)
except Exception as e: except Exception as e:
LOG.exception(e) LOG.exception(e)
raise e raise e
@@ -149,11 +154,12 @@ class SwUpdateOptionsController(object):
try: try:
sw_update_opts_ref = db_api.sw_update_opts_default_create( sw_update_opts_ref = db_api.sw_update_opts_default_create(
context, context,
payload['storage-apply-type'], payload["storage-apply-type"],
payload['worker-apply-type'], payload["worker-apply-type"],
payload['max-parallel-workers'], payload["max-parallel-workers"],
payload['alarm-restriction-type'], payload["alarm-restriction-type"],
payload['default-instance-action']) payload["default-instance-action"],
)
except Exception as e: except Exception as e:
LOG.exception(e) LOG.exception(e)
raise e raise e
@@ -165,53 +171,57 @@ class SwUpdateOptionsController(object):
try: try:
subcloud = db_api.subcloud_get(context, subcloud_ref) subcloud = db_api.subcloud_get(context, subcloud_ref)
except exceptions.SubcloudNotFound: except exceptions.SubcloudNotFound:
pecan.abort(404, _('Subcloud not found')) pecan.abort(404, _("Subcloud not found"))
subcloud_name = subcloud.name subcloud_name = subcloud.name
else: else:
# Look up subcloud by name # Look up subcloud by name
try: try:
subcloud = db_api.subcloud_get_by_name(context, subcloud = db_api.subcloud_get_by_name(context, subcloud_ref)
subcloud_ref)
except exceptions.SubcloudNameNotFound: except exceptions.SubcloudNameNotFound:
pecan.abort(404, _('Subcloud not found')) pecan.abort(404, _("Subcloud not found"))
subcloud_name = subcloud_ref subcloud_name = subcloud_ref
sw_update_opts = db_api.sw_update_opts_get(context, sw_update_opts = db_api.sw_update_opts_get(context, subcloud.id)
subcloud.id)
if sw_update_opts is None: if sw_update_opts is None:
sw_update_opts_ref = db_api.sw_update_opts_create( sw_update_opts_ref = db_api.sw_update_opts_create(
context, context,
subcloud.id, subcloud.id,
payload['storage-apply-type'], payload["storage-apply-type"],
payload['worker-apply-type'], payload["worker-apply-type"],
payload['max-parallel-workers'], payload["max-parallel-workers"],
payload['alarm-restriction-type'], payload["alarm-restriction-type"],
payload['default-instance-action']) payload["default-instance-action"],
)
else: else:
# a row is present in table, update # a row is present in table, update
sw_update_opts_ref = db_api.sw_update_opts_update( sw_update_opts_ref = db_api.sw_update_opts_update(
context, context,
subcloud.id, subcloud.id,
payload['storage-apply-type'], payload["storage-apply-type"],
payload['worker-apply-type'], payload["worker-apply-type"],
payload['max-parallel-workers'], payload["max-parallel-workers"],
payload['alarm-restriction-type'], payload["alarm-restriction-type"],
payload['default-instance-action']) payload["default-instance-action"],
)
return db_api.sw_update_opts_w_name_db_model_to_dict( return db_api.sw_update_opts_w_name_db_model_to_dict(
sw_update_opts_ref, subcloud_name) sw_update_opts_ref, subcloud_name
)
@index.when(method='delete', template='json') @index.when(method="delete", template="json")
def delete(self, subcloud_ref): def delete(self, subcloud_ref):
"""Delete the software update options.""" """Delete the software update options."""
policy.authorize(sw_update_options_policy.POLICY_ROOT % "delete", {}, policy.authorize(
restcomm.extract_credentials_for_policy()) sw_update_options_policy.POLICY_ROOT % "delete",
{},
restcomm.extract_credentials_for_policy(),
)
context = restcomm.extract_context_from_environ() context = restcomm.extract_context_from_environ()
if subcloud_ref == dccommon_consts.DEFAULT_REGION_NAME: if subcloud_ref == dccommon_consts.DEFAULT_REGION_NAME:
@@ -230,18 +240,17 @@ class SwUpdateOptionsController(object):
try: try:
subcloud = db_api.subcloud_get(context, subcloud_ref) subcloud = db_api.subcloud_get(context, subcloud_ref)
except exceptions.SubcloudNotFound: except exceptions.SubcloudNotFound:
pecan.abort(404, _('Subcloud not found')) pecan.abort(404, _("Subcloud not found"))
else: else:
# Look up subcloud by name # Look up subcloud by name
try: try:
subcloud = db_api.subcloud_get_by_name(context, subcloud = db_api.subcloud_get_by_name(context, subcloud_ref)
subcloud_ref)
except exceptions.SubcloudNameNotFound: except exceptions.SubcloudNameNotFound:
pecan.abort(404, _('Subcloud not found')) pecan.abort(404, _("Subcloud not found"))
# Delete the subcloud specific options # Delete the subcloud specific options
if db_api.sw_update_opts_get(context, subcloud.id): if db_api.sw_update_opts_get(context, subcloud.id):
db_api.sw_update_opts_destroy(context, subcloud.id) db_api.sw_update_opts_destroy(context, subcloud.id)
else: else:
pecan.abort(404, _('Subcloud patch options not found')) pecan.abort(404, _("Subcloud patch options not found"))

View File

@@ -61,42 +61,44 @@ class SwUpdateStrategyController(object):
super(SwUpdateStrategyController, self).__init__() super(SwUpdateStrategyController, self).__init__()
self.orch_rpc_client = orch_rpc_client.ManagerOrchestratorClient() self.orch_rpc_client = orch_rpc_client.ManagerOrchestratorClient()
@expose(generic=True, template='json') @expose(generic=True, template="json")
def index(self): def index(self):
# Route the request to specific methods with parameters # Route the request to specific methods with parameters
pass pass
@index.when(method='GET', template='json') @index.when(method="GET", template="json")
def get(self, steps=None, cloud_name=None): def get(self, steps=None, cloud_name=None):
"""Get details about software update strategy. """Get details about software update strategy.
:param steps: get the steps for this strategy (optional) :param steps: get the steps for this strategy (optional)
:param cloud_name: name of cloud (optional) :param cloud_name: name of cloud (optional)
""" """
policy.authorize(sw_update_strat_policy.POLICY_ROOT % "get", {}, policy.authorize(
restcomm.extract_credentials_for_policy()) sw_update_strat_policy.POLICY_ROOT % "get",
{},
restcomm.extract_credentials_for_policy(),
)
context = restcomm.extract_context_from_environ() context = restcomm.extract_context_from_environ()
# If 'type' is in the request params, filter the update_type # If 'type' is in the request params, filter the update_type
update_type_filter = request.params.get('type', None) update_type_filter = request.params.get("type", None)
if steps is None: if steps is None:
# Strategy requested # Strategy requested
strategy = None strategy = None
try: try:
strategy = db_api.sw_update_strategy_get( strategy = db_api.sw_update_strategy_get(
context, context, update_type=update_type_filter
update_type=update_type_filter) )
except exceptions.NotFound: except exceptions.NotFound:
if update_type_filter is None: if update_type_filter is None:
pecan.abort(404, _('Strategy not found')) pecan.abort(404, _("Strategy not found"))
else: else:
pecan.abort(404, pecan.abort(
_("Strategy of type '%s' not found" 404, _("Strategy of type '%s' not found" % update_type_filter)
% update_type_filter)) )
strategy_dict = db_api.sw_update_strategy_db_model_to_dict( strategy_dict = db_api.sw_update_strategy_db_model_to_dict(strategy)
strategy)
return strategy_dict return strategy_dict
elif steps == "steps": elif steps == "steps":
@@ -104,11 +106,12 @@ class SwUpdateStrategyController(object):
if cloud_name is None: if cloud_name is None:
# List of steps requested # List of steps requested
result = dict() result = dict()
result['strategy-steps'] = list() result["strategy-steps"] = list()
strategy_steps = db_api.strategy_step_get_all(context) strategy_steps = db_api.strategy_step_get_all(context)
for strategy_step in strategy_steps: for strategy_step in strategy_steps:
result['strategy-steps'].append( result["strategy-steps"].append(
db_api.strategy_step_db_model_to_dict(strategy_step)) db_api.strategy_step_db_model_to_dict(strategy_step)
)
return result return result
else: else:
@@ -120,184 +123,213 @@ class SwUpdateStrategyController(object):
try: try:
strategy_step = db_api.strategy_step_get(context, None) strategy_step = db_api.strategy_step_get(context, None)
except exceptions.StrategyStepNotFound: except exceptions.StrategyStepNotFound:
pecan.abort(404, _('Strategy step not found')) pecan.abort(404, _("Strategy step not found"))
else: else:
try: try:
strategy_step = db_api.strategy_step_get_by_name( strategy_step = db_api.strategy_step_get_by_name(
context, cloud_name) context, cloud_name
)
except exceptions.StrategyStepNameNotFound: except exceptions.StrategyStepNameNotFound:
pecan.abort(404, _('Strategy step not found')) pecan.abort(404, _("Strategy step not found"))
strategy_step_dict = db_api.strategy_step_db_model_to_dict( strategy_step_dict = db_api.strategy_step_db_model_to_dict(
strategy_step) strategy_step
)
return strategy_step_dict return strategy_step_dict
@index.when(method='POST', template='json') @index.when(method="POST", template="json")
def post(self, actions=None): def post(self, actions=None):
"""Create a new software update strategy.""" """Create a new software update strategy."""
context = restcomm.extract_context_from_environ() context = restcomm.extract_context_from_environ()
payload = eval(request.body) payload = eval(request.body)
if not payload: if not payload:
pecan.abort(400, _('Body required')) pecan.abort(400, _("Body required"))
if actions is None: if actions is None:
policy.authorize(sw_update_strat_policy.POLICY_ROOT % "create", {}, policy.authorize(
restcomm.extract_credentials_for_policy()) sw_update_strat_policy.POLICY_ROOT % "create",
{},
restcomm.extract_credentials_for_policy(),
)
# Validate any options that were supplied # Validate any options that were supplied
strategy_type = payload.get('type') strategy_type = payload.get("type")
if not strategy_type: if not strategy_type:
pecan.abort(400, _('type required')) pecan.abort(400, _("type required"))
if strategy_type not in SUPPORTED_STRATEGY_TYPES: if strategy_type not in SUPPORTED_STRATEGY_TYPES:
pecan.abort(400, _('type invalid')) pecan.abort(400, _("type invalid"))
subcloud_apply_type = payload.get('subcloud-apply-type') subcloud_apply_type = payload.get("subcloud-apply-type")
if subcloud_apply_type is not None: if subcloud_apply_type is not None:
if subcloud_apply_type not in [ if subcloud_apply_type not in [
consts.SUBCLOUD_APPLY_TYPE_PARALLEL, consts.SUBCLOUD_APPLY_TYPE_PARALLEL,
consts.SUBCLOUD_APPLY_TYPE_SERIAL]: consts.SUBCLOUD_APPLY_TYPE_SERIAL,
pecan.abort(400, _('subcloud-apply-type invalid')) ]:
pecan.abort(400, _("subcloud-apply-type invalid"))
patch_file = payload.get('patch') patch_file = payload.get("patch")
if patch_file and not os.path.isfile(patch_file): if patch_file and not os.path.isfile(patch_file):
message = f"Patch file {patch_file} is missing." message = f"Patch file {patch_file} is missing."
pecan.abort(400, _(message)) pecan.abort(400, _(message))
if strategy_type == consts.SW_UPDATE_TYPE_SOFTWARE and not payload.get( if strategy_type == consts.SW_UPDATE_TYPE_SOFTWARE and not payload.get(
'release'): "release"
message = ("Release parameter is required for strategy " ):
f"type {strategy_type}.") message = (
"Release parameter is required for strategy type {strategy_type}."
)
pecan.abort(400, _(message)) pecan.abort(400, _(message))
max_parallel_subclouds_str = payload.get('max-parallel-subclouds') max_parallel_subclouds_str = payload.get("max-parallel-subclouds")
if max_parallel_subclouds_str is not None: if max_parallel_subclouds_str is not None:
max_parallel_subclouds = None max_parallel_subclouds = None
try: try:
max_parallel_subclouds = int(max_parallel_subclouds_str) max_parallel_subclouds = int(max_parallel_subclouds_str)
except ValueError: except ValueError:
pecan.abort(400, _('max-parallel-subclouds invalid')) pecan.abort(400, _("max-parallel-subclouds invalid"))
if max_parallel_subclouds < 1 or max_parallel_subclouds > 500: if max_parallel_subclouds < 1 or max_parallel_subclouds > 500:
pecan.abort(400, _('max-parallel-subclouds invalid')) pecan.abort(400, _("max-parallel-subclouds invalid"))
stop_on_failure = payload.get('stop-on-failure') stop_on_failure = payload.get("stop-on-failure")
if stop_on_failure is not None: if stop_on_failure is not None:
if stop_on_failure not in ["true", "false"]: if stop_on_failure not in ["true", "false"]:
pecan.abort(400, _('stop-on-failure invalid')) pecan.abort(400, _("stop-on-failure invalid"))
force_flag = payload.get('force') force_flag = payload.get("force")
if force_flag is not None: if force_flag is not None:
if force_flag not in ["true", "false"]: if force_flag not in ["true", "false"]:
pecan.abort(400, _('force invalid')) pecan.abort(400, _("force invalid"))
elif strategy_type not in FORCE_ALL_TYPES: elif strategy_type not in FORCE_ALL_TYPES:
if payload.get('cloud_name') is None: if payload.get("cloud_name") is None:
pecan.abort(400, pecan.abort(
_('The --force option can only be applied ' 400,
'for a single subcloud. Please specify ' _(
'the subcloud name.')) "The --force option can only be applied for a single "
"subcloud. Please specify the subcloud name."
),
)
subcloud_group = payload.get('subcloud_group') subcloud_group = payload.get("subcloud_group")
# prevents passing both cloud_name and subcloud_group options # prevents passing both cloud_name and subcloud_group options
# from REST APIs and checks if the group exists # from REST APIs and checks if the group exists
if subcloud_group is not None: if subcloud_group is not None:
if payload.get('cloud_name') is not None: if payload.get("cloud_name") is not None:
pecan.abort(400, _('cloud_name and subcloud_group are ' pecan.abort(
'mutually exclusive')) 400,
_("cloud_name and subcloud_group are mutually exclusive"),
)
if (subcloud_apply_type is not None or if (
max_parallel_subclouds_str is not None): subcloud_apply_type is not None
pecan.abort(400, _('subcloud-apply-type and ' or max_parallel_subclouds_str is not None
'max-parallel-subclouds are not ' ):
'supported when subcloud_group is ' pecan.abort(
'applied')) 400,
_(
"subcloud-apply-type and max-parallel-subclouds are not "
"supported when subcloud_group is applied"
),
)
group = utils.subcloud_group_get_by_ref(context, group = utils.subcloud_group_get_by_ref(context, subcloud_group)
subcloud_group)
if group is None: if group is None:
pecan.abort(400, _('Invalid group_id')) pecan.abort(400, _("Invalid group_id"))
# get_sw_version is used here to validate the # get_sw_version is used here to validate the
# release parameter if specified. # release parameter if specified.
utils.get_sw_version(payload.get('release')) utils.get_sw_version(payload.get("release"))
# Not adding validation for extra args. Passing them through. # Not adding validation for extra args. Passing them through.
try: try:
# Ask dcmanager-manager to create the strategy. # Ask dcmanager-manager to create the strategy.
# It will do all the real work... # It will do all the real work...
return self.orch_rpc_client.create_sw_update_strategy(context, return self.orch_rpc_client.create_sw_update_strategy(context, payload)
payload)
except RemoteError as e: except RemoteError as e:
pecan.abort( pecan.abort(
422, _("Unable to create strategy of type '%s': %s") 422,
% (strategy_type, e.value) _("Unable to create strategy of type '%s': %s")
% (strategy_type, e.value),
) )
except Exception as e: except Exception as e:
LOG.exception(e) LOG.exception(e)
pecan.abort(500, _('Unable to create strategy')) pecan.abort(500, _("Unable to create strategy"))
elif actions == 'actions': elif actions == "actions":
# If 'type' is in the request params, filter the update_type # If 'type' is in the request params, filter the update_type
update_type_filter = request.params.get('type', None) update_type_filter = request.params.get("type", None)
# Apply or abort a strategy # Apply or abort a strategy
action = payload.get('action') action = payload.get("action")
if not action: if not action:
pecan.abort(400, _('action required')) pecan.abort(400, _("action required"))
if action == consts.SW_UPDATE_ACTION_APPLY: if action == consts.SW_UPDATE_ACTION_APPLY:
policy.authorize(sw_update_strat_policy.POLICY_ROOT % "apply", policy.authorize(
{}, restcomm.extract_credentials_for_policy()) sw_update_strat_policy.POLICY_ROOT % "apply",
{},
restcomm.extract_credentials_for_policy(),
)
try: try:
# Ask dcmanager-manager to apply the strategy. # Ask dcmanager-manager to apply the strategy.
# It will do all the real work... # It will do all the real work...
return self.orch_rpc_client.apply_sw_update_strategy( return self.orch_rpc_client.apply_sw_update_strategy(
context, context, update_type=update_type_filter
update_type=update_type_filter) )
except RemoteError as e: except RemoteError as e:
pecan.abort( pecan.abort(
422, _("Unable to apply strategy of type '%s': %s") 422,
% (update_type_filter, e.value) _("Unable to apply strategy of type '%s': %s")
% (update_type_filter, e.value),
) )
except Exception as e: except Exception as e:
LOG.exception(e) LOG.exception(e)
pecan.abort(500, _('Unable to apply strategy')) pecan.abort(500, _("Unable to apply strategy"))
elif action == consts.SW_UPDATE_ACTION_ABORT: elif action == consts.SW_UPDATE_ACTION_ABORT:
policy.authorize(sw_update_strat_policy.POLICY_ROOT % "abort", policy.authorize(
{}, restcomm.extract_credentials_for_policy()) sw_update_strat_policy.POLICY_ROOT % "abort",
{},
restcomm.extract_credentials_for_policy(),
)
try: try:
# Ask dcmanager-manager to abort the strategy. # Ask dcmanager-manager to abort the strategy.
# It will do all the real work... # It will do all the real work...
return self.orch_rpc_client.abort_sw_update_strategy( return self.orch_rpc_client.abort_sw_update_strategy(
context, context, update_type=update_type_filter
update_type=update_type_filter) )
except RemoteError as e: except RemoteError as e:
pecan.abort( pecan.abort(
422, _("Unable to abort strategy of type '%s': %s") 422,
% (update_type_filter, e.value) _("Unable to abort strategy of type '%s': %s")
% (update_type_filter, e.value),
) )
except Exception as e: except Exception as e:
LOG.exception(e) LOG.exception(e)
pecan.abort(500, _('Unable to abort strategy')) pecan.abort(500, _("Unable to abort strategy"))
@index.when(method='delete', template='json') @index.when(method="delete", template="json")
def delete(self): def delete(self):
"""Delete the software update strategy.""" """Delete the software update strategy."""
policy.authorize(sw_update_strat_policy.POLICY_ROOT % "delete", {}, policy.authorize(
restcomm.extract_credentials_for_policy()) sw_update_strat_policy.POLICY_ROOT % "delete",
{},
restcomm.extract_credentials_for_policy(),
)
context = restcomm.extract_context_from_environ() context = restcomm.extract_context_from_environ()
# If 'type' is in the request params, filter the update_type # If 'type' is in the request params, filter the update_type
update_type_filter = request.params.get('type', None) update_type_filter = request.params.get("type", None)
try: try:
# Ask dcmanager-manager to delete the strategy. # Ask dcmanager-manager to delete the strategy.
# It will do all the real work... # It will do all the real work...
return self.orch_rpc_client.delete_sw_update_strategy( return self.orch_rpc_client.delete_sw_update_strategy(
context, context, update_type=update_type_filter
update_type=update_type_filter) )
except RemoteError as e: except RemoteError as e:
pecan.abort( pecan.abort(
422, _("Unable to delete strategy of type '%s': %s") 422,
% (update_type_filter, e.value) _("Unable to delete strategy of type '%s': %s")
% (update_type_filter, e.value),
) )
except Exception as e: except Exception as e:
LOG.exception(e) LOG.exception(e)
pecan.abort(500, _('Unable to delete strategy')) pecan.abort(500, _("Unable to delete strategy"))

View File

@@ -51,8 +51,7 @@ MAX_SYSTEM_PEER_HEARTBEAT_FAILURE_THRESHOLD = 30
# the heartbeat failure threshold is reached # the heartbeat failure threshold is reached
# #
# We will only support alarm in the first release # We will only support alarm in the first release
SYSTEM_PEER_HEARTBEAT_FAILURE_POLICY_LIST = \ SYSTEM_PEER_HEARTBEAT_FAILURE_POLICY_LIST = ["alarm", "rehome", "delegate"]
["alarm", "rehome", "delegate"]
MIN_SYSTEM_PEER_HEARTBEAT_MAINTENACE_TIMEOUT = 300 MIN_SYSTEM_PEER_HEARTBEAT_MAINTENACE_TIMEOUT = 300
MAX_SYSTEM_PEER_HEARTBEAT_MAINTENACE_TIMEOUT = 36000 MAX_SYSTEM_PEER_HEARTBEAT_MAINTENACE_TIMEOUT = 36000
@@ -64,7 +63,7 @@ class SystemPeersController(restcomm.GenericPathController):
self.dcmanager_rpc_client = rpc_client.ManagerClient() self.dcmanager_rpc_client = rpc_client.ManagerClient()
@expose(generic=True, template='json') @expose(generic=True, template="json")
def index(self): def index(self):
# Route the request to specific methods with parameters # Route the request to specific methods with parameters
pass pass
@@ -74,12 +73,12 @@ class SystemPeersController(restcomm.GenericPathController):
try: try:
payload = json.loads(request.body) payload = json.loads(request.body)
except Exception: except Exception:
error_msg = 'Request body is malformed.' error_msg = "Request body is malformed."
LOG.exception(error_msg) LOG.exception(error_msg)
pecan.abort(400, _(error_msg)) pecan.abort(400, _(error_msg))
if not isinstance(payload, dict): if not isinstance(payload, dict):
pecan.abort(400, _('Invalid request body format')) pecan.abort(400, _("Invalid request body format"))
return payload return payload
def _get_peer_group_list_for_system_peer(self, context, peer_id): def _get_peer_group_list_for_system_peer(self, context, peer_id):
@@ -95,10 +94,10 @@ class SystemPeersController(restcomm.GenericPathController):
system_peer_list.append(peer_dict) system_peer_list.append(peer_dict)
result = dict() result = dict()
result['system_peers'] = system_peer_list result["system_peers"] = system_peer_list
return result return result
@index.when(method='GET', template='json') @index.when(method="GET", template="json")
def get(self, peer_ref=None, subcloud_peer_groups=False): def get(self, peer_ref=None, subcloud_peer_groups=False):
"""Retrieve information about a system peer. """Retrieve information about a system peer.
@@ -110,8 +109,11 @@ class SystemPeersController(restcomm.GenericPathController):
:param subcloud_peer_groups: If this request should return subcloud :param subcloud_peer_groups: If this request should return subcloud
peer groups peer groups
""" """
policy.authorize(system_peer_policy.POLICY_ROOT % "get", {}, policy.authorize(
restcomm.extract_credentials_for_policy()) system_peer_policy.POLICY_ROOT % "get",
{},
restcomm.extract_credentials_for_policy(),
)
context = restcomm.extract_context_from_environ() context = restcomm.extract_context_from_environ()
if peer_ref is None: if peer_ref is None:
@@ -120,7 +122,7 @@ class SystemPeersController(restcomm.GenericPathController):
peer = utils.system_peer_get_by_ref(context, peer_ref) peer = utils.system_peer_get_by_ref(context, peer_ref)
if peer is None: if peer is None:
pecan.abort(httpclient.NOT_FOUND, _('System Peer not found')) pecan.abort(httpclient.NOT_FOUND, _("System Peer not found"))
if subcloud_peer_groups: if subcloud_peer_groups:
return self._get_peer_group_list_for_system_peer(context, peer.id) return self._get_peer_group_list_for_system_peer(context, peer.id)
system_peer_dict = db_api.system_peer_db_model_to_dict(peer) system_peer_dict = db_api.system_peer_db_model_to_dict(peer)
@@ -135,8 +137,11 @@ class SystemPeersController(restcomm.GenericPathController):
return False return False
def _validate_manager_endpoint(self, endpoint): def _validate_manager_endpoint(self, endpoint):
if not endpoint or len(endpoint) >= MAX_SYSTEM_PEER_MANAGER_ENDPOINT_LEN or \ if (
not endpoint.startswith(("http", "https")): not endpoint
or len(endpoint) >= MAX_SYSTEM_PEER_MANAGER_ENDPOINT_LEN
or not endpoint.startswith(("http", "https"))
):
LOG.debug("Invalid manager_endpoint: %s" % endpoint) LOG.debug("Invalid manager_endpoint: %s" % endpoint)
return False return False
return True return True
@@ -179,286 +184,334 @@ class SystemPeersController(restcomm.GenericPathController):
return False return False
# We do not support less than min or greater than max # We do not support less than min or greater than max
if val < MIN_SYSTEM_PEER_HEARTBEAT_INTERVAL or \ if (
val > MAX_SYSTEM_PEER_HEARTBEAT_INTERVAL: val < MIN_SYSTEM_PEER_HEARTBEAT_INTERVAL
or val > MAX_SYSTEM_PEER_HEARTBEAT_INTERVAL
):
LOG.debug("Invalid heartbeat_interval: %s" % heartbeat_interval) LOG.debug("Invalid heartbeat_interval: %s" % heartbeat_interval)
return False return False
return True return True
def _validate_heartbeat_failure_threshold(self, def _validate_heartbeat_failure_threshold(self, heartbeat_failure_threshold):
heartbeat_failure_threshold):
try: try:
# Check the value is an integer # Check the value is an integer
val = int(heartbeat_failure_threshold) val = int(heartbeat_failure_threshold)
except ValueError: except ValueError:
LOG.warning("Invalid heartbeat_failure_threshold: %s" % LOG.warning(
heartbeat_failure_threshold) "Invalid heartbeat_failure_threshold: %s" % heartbeat_failure_threshold
)
return False return False
# We do not support less than min or greater than max # We do not support less than min or greater than max
if val < MIN_SYSTEM_PEER_HEARTBEAT_FAILURE_THRESHOLD or \ if (
val > MAX_SYSTEM_PEER_HEARTBEAT_FAILURE_THRESHOLD: val < MIN_SYSTEM_PEER_HEARTBEAT_FAILURE_THRESHOLD
LOG.debug("Invalid heartbeat_failure_threshold: %s" % or val > MAX_SYSTEM_PEER_HEARTBEAT_FAILURE_THRESHOLD
heartbeat_failure_threshold) ):
LOG.debug(
"Invalid heartbeat_failure_threshold: %s" % heartbeat_failure_threshold
)
return False return False
return True return True
def _validate_heartbeat_failure_policy(self, heartbeat_failure_policy): def _validate_heartbeat_failure_policy(self, heartbeat_failure_policy):
if heartbeat_failure_policy not in SYSTEM_PEER_HEARTBEAT_FAILURE_POLICY_LIST: if heartbeat_failure_policy not in SYSTEM_PEER_HEARTBEAT_FAILURE_POLICY_LIST:
LOG.debug("Invalid heartbeat_failure_policy: %s" % LOG.debug("Invalid heartbeat_failure_policy: %s" % heartbeat_failure_policy)
heartbeat_failure_policy)
return False return False
return True return True
def _validate_heartbeat_maintenance_timeout(self, def _validate_heartbeat_maintenance_timeout(self, heartbeat_maintenance_timeout):
heartbeat_maintenance_timeout):
try: try:
# Check the value is an integer # Check the value is an integer
val = int(heartbeat_maintenance_timeout) val = int(heartbeat_maintenance_timeout)
except ValueError: except ValueError:
LOG.warning("Invalid heartbeat_maintenance_timeout: %s" % LOG.warning(
heartbeat_maintenance_timeout) "Invalid heartbeat_maintenance_timeout: %s"
% heartbeat_maintenance_timeout
)
return False return False
# We do not support less than min or greater than max # We do not support less than min or greater than max
if val < MIN_SYSTEM_PEER_HEARTBEAT_MAINTENACE_TIMEOUT or \ if (
val > MAX_SYSTEM_PEER_HEARTBEAT_MAINTENACE_TIMEOUT: val < MIN_SYSTEM_PEER_HEARTBEAT_MAINTENACE_TIMEOUT
LOG.debug("Invalid heartbeat_maintenance_timeout: %s" % or val > MAX_SYSTEM_PEER_HEARTBEAT_MAINTENACE_TIMEOUT
heartbeat_maintenance_timeout) ):
LOG.debug(
"Invalid heartbeat_maintenance_timeout: %s"
% heartbeat_maintenance_timeout
)
return False return False
return True return True
@index.when(method='POST', template='json') @index.when(method="POST", template="json")
def post(self): def post(self):
"""Create a new system peer.""" """Create a new system peer."""
policy.authorize(system_peer_policy.POLICY_ROOT % "create", {}, policy.authorize(
restcomm.extract_credentials_for_policy()) system_peer_policy.POLICY_ROOT % "create",
{},
restcomm.extract_credentials_for_policy(),
)
context = restcomm.extract_context_from_environ() context = restcomm.extract_context_from_environ()
LOG.info("Creating a new system peer: %s" % context) LOG.info("Creating a new system peer: %s" % context)
payload = self._get_payload(request) payload = self._get_payload(request)
if not payload: if not payload:
pecan.abort(httpclient.BAD_REQUEST, _('Body required')) pecan.abort(httpclient.BAD_REQUEST, _("Body required"))
# Validate payload # Validate payload
peer_uuid = payload.get('peer_uuid') peer_uuid = payload.get("peer_uuid")
if not self._validate_uuid(peer_uuid): if not self._validate_uuid(peer_uuid):
pecan.abort(httpclient.BAD_REQUEST, _('Invalid peer uuid')) pecan.abort(httpclient.BAD_REQUEST, _("Invalid peer uuid"))
peer_name = payload.get('peer_name') peer_name = payload.get("peer_name")
if not utils.validate_name(peer_name): if not utils.validate_name(peer_name):
pecan.abort(httpclient.BAD_REQUEST, _('Invalid peer name')) pecan.abort(httpclient.BAD_REQUEST, _("Invalid peer name"))
endpoint = payload.get('manager_endpoint') endpoint = payload.get("manager_endpoint")
if not self._validate_manager_endpoint(endpoint): if not self._validate_manager_endpoint(endpoint):
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(httpclient.BAD_REQUEST, _("Invalid peer manager_endpoint"))
_('Invalid peer manager_endpoint'))
username = payload.get('manager_username') username = payload.get("manager_username")
if not self._validate_manager_username(username): if not self._validate_manager_username(username):
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(httpclient.BAD_REQUEST, _("Invalid peer manager_username"))
_('Invalid peer manager_username'))
password = payload.get('manager_password') password = payload.get("manager_password")
if not self._validate_manager_password(password): if not self._validate_manager_password(password):
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(httpclient.BAD_REQUEST, _("Invalid peer manager_password"))
_('Invalid peer manager_password'))
gateway_ip = payload.get('peer_controller_gateway_address') gateway_ip = payload.get("peer_controller_gateway_address")
if not self._validate_peer_controller_gateway_ip(gateway_ip): if not self._validate_peer_controller_gateway_ip(gateway_ip):
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(
_('Invalid peer peer_controller_gateway_address')) httpclient.BAD_REQUEST,
_("Invalid peer peer_controller_gateway_address"),
)
# Optional request parameters # Optional request parameters
kwargs = {} kwargs = {}
administrative_state = payload.get('administrative_state') administrative_state = payload.get("administrative_state")
if administrative_state: if administrative_state:
if not self._validate_administrative_state(administrative_state): if not self._validate_administrative_state(administrative_state):
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(
_('Invalid peer administrative_state')) httpclient.BAD_REQUEST, _("Invalid peer administrative_state")
kwargs['administrative_state'] = administrative_state )
kwargs["administrative_state"] = administrative_state
heartbeat_interval = payload.get('heartbeat_interval') heartbeat_interval = payload.get("heartbeat_interval")
if heartbeat_interval is not None: if heartbeat_interval is not None:
if not self._validate_heartbeat_interval(heartbeat_interval): if not self._validate_heartbeat_interval(heartbeat_interval):
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(
_('Invalid peer heartbeat_interval')) httpclient.BAD_REQUEST, _("Invalid peer heartbeat_interval")
kwargs['heartbeat_interval'] = heartbeat_interval )
kwargs["heartbeat_interval"] = heartbeat_interval
heartbeat_failure_threshold = \ heartbeat_failure_threshold = payload.get("heartbeat_failure_threshold")
payload.get('heartbeat_failure_threshold')
if heartbeat_failure_threshold is not None: if heartbeat_failure_threshold is not None:
if not self._validate_heartbeat_failure_threshold( if not self._validate_heartbeat_failure_threshold(
heartbeat_failure_threshold): heartbeat_failure_threshold
pecan.abort(httpclient.BAD_REQUEST, ):
_('Invalid peer heartbeat_failure_threshold')) pecan.abort(
kwargs['heartbeat_failure_threshold'] = heartbeat_failure_threshold httpclient.BAD_REQUEST,
_("Invalid peer heartbeat_failure_threshold"),
)
kwargs["heartbeat_failure_threshold"] = heartbeat_failure_threshold
heartbeat_failure_policy = payload.get('heartbeat_failure_policy') heartbeat_failure_policy = payload.get("heartbeat_failure_policy")
if heartbeat_failure_policy: if heartbeat_failure_policy:
if not self._validate_heartbeat_failure_policy( if not self._validate_heartbeat_failure_policy(heartbeat_failure_policy):
heartbeat_failure_policy): pecan.abort(
pecan.abort(httpclient.BAD_REQUEST, httpclient.BAD_REQUEST, _("Invalid peer heartbeat_failure_policy")
_('Invalid peer heartbeat_failure_policy')) )
kwargs['heartbeat_failure_policy'] = heartbeat_failure_policy kwargs["heartbeat_failure_policy"] = heartbeat_failure_policy
heartbeat_maintenance_timeout = \ heartbeat_maintenance_timeout = payload.get("heartbeat_maintenance_timeout")
payload.get('heartbeat_maintenance_timeout')
if heartbeat_maintenance_timeout is not None: if heartbeat_maintenance_timeout is not None:
if not self._validate_heartbeat_maintenance_timeout( if not self._validate_heartbeat_maintenance_timeout(
heartbeat_maintenance_timeout):
pecan.abort(httpclient.BAD_REQUEST,
_('Invalid peer heartbeat_maintenance_timeout'))
kwargs['heartbeat_maintenance_timeout'] = \
heartbeat_maintenance_timeout heartbeat_maintenance_timeout
):
pecan.abort(
httpclient.BAD_REQUEST,
_("Invalid peer heartbeat_maintenance_timeout"),
)
kwargs["heartbeat_maintenance_timeout"] = heartbeat_maintenance_timeout
try: try:
peer_ref = db_api.system_peer_create(context, peer_ref = db_api.system_peer_create(
peer_uuid, context,
peer_name, peer_uuid,
endpoint, peer_name,
username, endpoint,
password, username,
gateway_ip, **kwargs) password,
gateway_ip,
**kwargs
)
return db_api.system_peer_db_model_to_dict(peer_ref) return db_api.system_peer_db_model_to_dict(peer_ref)
except db_exc.DBDuplicateEntry: except db_exc.DBDuplicateEntry:
LOG.info("Peer create failed. Peer UUID %s already exists" LOG.info("Peer create failed. Peer UUID %s already exists" % peer_uuid)
% peer_uuid) pecan.abort(
pecan.abort(httpclient.CONFLICT, httpclient.CONFLICT, _("A system peer with this UUID already exists")
_('A system peer with this UUID already exists')) )
except RemoteError as e: except RemoteError as e:
pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value) pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value)
except Exception as e: except Exception as e:
LOG.exception(e) LOG.exception(e)
pecan.abort(httpclient.INTERNAL_SERVER_ERROR, pecan.abort(
_('Unable to create system peer')) httpclient.INTERNAL_SERVER_ERROR, _("Unable to create system peer")
)
@index.when(method='PATCH', template='json') @index.when(method="PATCH", template="json")
def patch(self, peer_ref): def patch(self, peer_ref):
"""Update a system peer. """Update a system peer.
:param peer_ref: ID or UUID of system peer to update :param peer_ref: ID or UUID of system peer to update
""" """
policy.authorize(system_peer_policy.POLICY_ROOT % "modify", {}, policy.authorize(
restcomm.extract_credentials_for_policy()) system_peer_policy.POLICY_ROOT % "modify",
{},
restcomm.extract_credentials_for_policy(),
)
context = restcomm.extract_context_from_environ() context = restcomm.extract_context_from_environ()
LOG.info("Updating system peer: %s" % context) LOG.info("Updating system peer: %s" % context)
if peer_ref is None: if peer_ref is None:
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(httpclient.BAD_REQUEST, _("System Peer UUID or ID required"))
_('System Peer UUID or ID required'))
payload = self._get_payload(request) payload = self._get_payload(request)
if not payload: if not payload:
pecan.abort(httpclient.BAD_REQUEST, _('Body required')) pecan.abort(httpclient.BAD_REQUEST, _("Body required"))
peer = utils.system_peer_get_by_ref(context, peer_ref) peer = utils.system_peer_get_by_ref(context, peer_ref)
if peer is None: if peer is None:
pecan.abort(httpclient.NOT_FOUND, _('System Peer not found')) pecan.abort(httpclient.NOT_FOUND, _("System Peer not found"))
peer_uuid, peer_name, endpoint, username, password, gateway_ip, \ (
administrative_state, heartbeat_interval, \ peer_uuid,
heartbeat_failure_threshold, heartbeat_failure_policy, \ peer_name,
heartbeat_maintenance_timeout = ( endpoint,
payload.get('peer_uuid'), username,
payload.get('peer_name'), password,
payload.get('manager_endpoint'), gateway_ip,
payload.get('manager_username'), administrative_state,
payload.get('manager_password'), heartbeat_interval,
payload.get('peer_controller_gateway_address'), heartbeat_failure_threshold,
payload.get('administrative_state'), heartbeat_failure_policy,
payload.get('heartbeat_interval'), heartbeat_maintenance_timeout,
payload.get('heartbeat_failure_threshold'), ) = (
payload.get('heartbeat_failure_policy'), payload.get("peer_uuid"),
payload.get('heartbeat_maintenance_timeout') payload.get("peer_name"),
) payload.get("manager_endpoint"),
payload.get("manager_username"),
payload.get("manager_password"),
payload.get("peer_controller_gateway_address"),
payload.get("administrative_state"),
payload.get("heartbeat_interval"),
payload.get("heartbeat_failure_threshold"),
payload.get("heartbeat_failure_policy"),
payload.get("heartbeat_maintenance_timeout"),
)
if not (peer_uuid or peer_name or endpoint or username or password if not (
or administrative_state or heartbeat_interval peer_uuid
or heartbeat_failure_threshold or heartbeat_failure_policy or peer_name
or heartbeat_maintenance_timeout or gateway_ip): or endpoint
pecan.abort(httpclient.BAD_REQUEST, _('nothing to update')) or username
or password
or administrative_state
or heartbeat_interval
or heartbeat_failure_threshold
or heartbeat_failure_policy
or heartbeat_maintenance_timeout
or gateway_ip
):
pecan.abort(httpclient.BAD_REQUEST, _("nothing to update"))
# Check value is not None or empty before calling validate # Check value is not None or empty before calling validate
if peer_uuid: if peer_uuid:
if not self._validate_uuid(peer_uuid): if not self._validate_uuid(peer_uuid):
pecan.abort(httpclient.BAD_REQUEST, _('Invalid peer uuid')) pecan.abort(httpclient.BAD_REQUEST, _("Invalid peer uuid"))
if peer_name: if peer_name:
if not utils.validate_name(peer_name): if not utils.validate_name(peer_name):
pecan.abort(httpclient.BAD_REQUEST, _('Invalid peer name')) pecan.abort(httpclient.BAD_REQUEST, _("Invalid peer name"))
if endpoint: if endpoint:
if not self._validate_manager_endpoint(endpoint): if not self._validate_manager_endpoint(endpoint):
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(httpclient.BAD_REQUEST, _("Invalid peer manager_endpoint"))
_('Invalid peer manager_endpoint'))
if username: if username:
if not self._validate_manager_username(username): if not self._validate_manager_username(username):
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(httpclient.BAD_REQUEST, _("Invalid peer manager_username"))
_('Invalid peer manager_username'))
if password: if password:
if not self._validate_manager_password(password): if not self._validate_manager_password(password):
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(httpclient.BAD_REQUEST, _("Invalid peer manager_password"))
_('Invalid peer manager_password'))
if gateway_ip: if gateway_ip:
if not self._validate_peer_controller_gateway_ip(gateway_ip): if not self._validate_peer_controller_gateway_ip(gateway_ip):
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(
_('Invalid peer peer_controller_gateway_address')) httpclient.BAD_REQUEST,
_("Invalid peer peer_controller_gateway_address"),
)
if administrative_state: if administrative_state:
if not self._validate_administrative_state(administrative_state): if not self._validate_administrative_state(administrative_state):
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(
_('Invalid peer administrative_state')) httpclient.BAD_REQUEST, _("Invalid peer administrative_state")
)
if heartbeat_interval: if heartbeat_interval:
if not self._validate_heartbeat_interval(heartbeat_interval): if not self._validate_heartbeat_interval(heartbeat_interval):
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(
_('Invalid peer heartbeat_interval')) httpclient.BAD_REQUEST, _("Invalid peer heartbeat_interval")
)
if heartbeat_failure_threshold: if heartbeat_failure_threshold:
if not self._validate_heartbeat_failure_threshold( if not self._validate_heartbeat_failure_threshold(
heartbeat_failure_threshold): heartbeat_failure_threshold
pecan.abort(httpclient.BAD_REQUEST, ):
_('Invalid peer heartbeat_failure_threshold')) pecan.abort(
httpclient.BAD_REQUEST,
_("Invalid peer heartbeat_failure_threshold"),
)
if heartbeat_failure_policy: if heartbeat_failure_policy:
if not self._validate_heartbeat_failure_policy( if not self._validate_heartbeat_failure_policy(heartbeat_failure_policy):
heartbeat_failure_policy): pecan.abort(
pecan.abort(httpclient.BAD_REQUEST, httpclient.BAD_REQUEST, _("Invalid peer heartbeat_failure_policy")
_('Invalid peer heartbeat_failure_policy')) )
if heartbeat_maintenance_timeout: if heartbeat_maintenance_timeout:
if not self._validate_heartbeat_maintenance_timeout( if not self._validate_heartbeat_maintenance_timeout(
heartbeat_maintenance_timeout): heartbeat_maintenance_timeout
pecan.abort(httpclient.BAD_REQUEST, ):
_('Invalid peer heartbeat_maintenance_timeout')) pecan.abort(
httpclient.BAD_REQUEST,
_("Invalid peer heartbeat_maintenance_timeout"),
)
try: try:
updated_peer = db_api.system_peer_update( updated_peer = db_api.system_peer_update(
context, context,
peer.id, peer.id,
peer_uuid, peer_name, peer_uuid,
endpoint, username, password, peer_name,
endpoint,
username,
password,
gateway_ip, gateway_ip,
administrative_state, administrative_state,
heartbeat_interval, heartbeat_interval,
heartbeat_failure_threshold, heartbeat_failure_threshold,
heartbeat_failure_policy, heartbeat_failure_policy,
heartbeat_maintenance_timeout) heartbeat_maintenance_timeout,
)
updated_peer_dict = db_api.system_peer_db_model_to_dict(updated_peer) updated_peer_dict = db_api.system_peer_db_model_to_dict(updated_peer)
# Set the sync status to out-of-sync if the peer_controller_gateway_ip # Set the sync status to out-of-sync if the peer_controller_gateway_ip
# was updated # was updated
if ( if gateway_ip is not None and gateway_ip != peer.peer_controller_gateway_ip:
gateway_ip is not None and
gateway_ip != peer.peer_controller_gateway_ip
):
associations = db_api.peer_group_association_get_by_system_peer_id( associations = db_api.peer_group_association_get_by_system_peer_id(
context, peer.id context, peer.id
) )
@@ -467,8 +520,9 @@ class SystemPeersController(restcomm.GenericPathController):
# Update the sync status on both sites # Update the sync status on both sites
association_ids.update( association_ids.update(
self.dcmanager_rpc_client.update_association_sync_status( self.dcmanager_rpc_client.update_association_sync_status(
context, association.peer_group_id, context,
consts.ASSOCIATION_SYNC_STATUS_OUT_OF_SYNC association.peer_group_id,
consts.ASSOCIATION_SYNC_STATUS_OUT_OF_SYNC,
) )
) )
# Generate an informative message to remind the operator # Generate an informative message to remind the operator
@@ -483,33 +537,37 @@ class SystemPeersController(restcomm.GenericPathController):
except Exception as e: except Exception as e:
# additional exceptions. # additional exceptions.
LOG.exception(e) LOG.exception(e)
pecan.abort(httpclient.INTERNAL_SERVER_ERROR, pecan.abort(
_('Unable to update system peer')) httpclient.INTERNAL_SERVER_ERROR, _("Unable to update system peer")
)
@index.when(method='delete', template='json') @index.when(method="delete", template="json")
def delete(self, peer_ref): def delete(self, peer_ref):
"""Delete the system peer.""" """Delete the system peer."""
policy.authorize(system_peer_policy.POLICY_ROOT % "delete", {}, policy.authorize(
restcomm.extract_credentials_for_policy()) system_peer_policy.POLICY_ROOT % "delete",
{},
restcomm.extract_credentials_for_policy(),
)
context = restcomm.extract_context_from_environ() context = restcomm.extract_context_from_environ()
LOG.info("Deleting system peer: %s" % context) LOG.info("Deleting system peer: %s" % context)
if peer_ref is None: if peer_ref is None:
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(httpclient.BAD_REQUEST, _("System Peer UUID or ID required"))
_('System Peer UUID or ID required'))
peer = utils.system_peer_get_by_ref(context, peer_ref) peer = utils.system_peer_get_by_ref(context, peer_ref)
if peer is None: if peer is None:
pecan.abort(httpclient.NOT_FOUND, _('System Peer not found')) pecan.abort(httpclient.NOT_FOUND, _("System Peer not found"))
# A system peer cannot be deleted if it is used by any associations # A system peer cannot be deleted if it is used by any associations
association = db_api.\ association = db_api.peer_group_association_get_by_system_peer_id(
peer_group_association_get_by_system_peer_id(context, context, str(peer.id)
str(peer.id)) )
if len(association) > 0: if len(association) > 0:
pecan.abort(httpclient.BAD_REQUEST, pecan.abort(
_('Cannot delete a system peer which is ' httpclient.BAD_REQUEST,
'associated with peer group.')) _("Cannot delete a system peer which is associated with peer group."),
)
try: try:
db_api.system_peer_destroy(context, peer.id) db_api.system_peer_destroy(context, peer.id)
@@ -517,5 +575,6 @@ class SystemPeersController(restcomm.GenericPathController):
pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value) pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value)
except Exception as e: except Exception as e:
LOG.exception(e) LOG.exception(e)
pecan.abort(httpclient.INTERNAL_SERVER_ERROR, pecan.abort(
_('Unable to delete system peer')) httpclient.INTERNAL_SERVER_ERROR, _("Unable to delete system peer")
)

View File

@@ -1,5 +1,5 @@
# #
# Copyright (c) 2023 Wind River Systems, Inc. # Copyright (c) 2023, 2024 Wind River Systems, Inc.
# #
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
# #
@@ -33,5 +33,5 @@ def list_rules():
phased_subcloud_deploy.list_rules(), phased_subcloud_deploy.list_rules(),
subcloud_peer_group.list_rules(), subcloud_peer_group.list_rules(),
peer_group_association.list_rules(), peer_group_association.list_rules(),
system_peers.list_rules() system_peers.list_rules(),
) )

View File

@@ -8,20 +8,15 @@ from oslo_policy import policy
from dcmanager.api.policies import base from dcmanager.api.policies import base
POLICY_ROOT = 'dc_api:alarm_manager:%s' POLICY_ROOT = "dc_api:alarm_manager:%s"
alarm_manager_rules = [ alarm_manager_rules = [
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'get', name=POLICY_ROOT % "get",
check_str='rule:' + base.READER_IN_SYSTEM_PROJECTS, check_str="rule:" + base.READER_IN_SYSTEM_PROJECTS,
description="Get alarms from subclouds.", description="Get alarms from subclouds.",
operations=[ operations=[{"method": "GET", "path": "/v1.0/alarms"}],
{
'method': 'GET',
'path': '/v1.0/alarms'
}
]
) )
] ]

View File

@@ -1,28 +1,26 @@
# #
# Copyright (c) 2022 Wind River Systems, Inc. # Copyright (c) 2022, 2024 Wind River Systems, Inc.
# #
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
# #
from oslo_policy import policy from oslo_policy import policy
ADMIN_IN_SYSTEM_PROJECTS = 'admin_in_system_projects' ADMIN_IN_SYSTEM_PROJECTS = "admin_in_system_projects"
READER_IN_SYSTEM_PROJECTS = 'reader_in_system_projects' READER_IN_SYSTEM_PROJECTS = "reader_in_system_projects"
base_rules = [ base_rules = [
policy.RuleDefault( policy.RuleDefault(
name=ADMIN_IN_SYSTEM_PROJECTS, name=ADMIN_IN_SYSTEM_PROJECTS,
check_str='role:admin and (project_name:admin or ' + check_str="role:admin and (project_name:admin or project_name:services)",
'project_name:services)',
description="Base rule.", description="Base rule.",
), ),
policy.RuleDefault( policy.RuleDefault(
name=READER_IN_SYSTEM_PROJECTS, name=READER_IN_SYSTEM_PROJECTS,
check_str='role:reader and (project_name:admin or ' + check_str="role:reader and (project_name:admin or project_name:services)",
'project_name:services)', description="Base rule.",
description="Base rule." ),
)
] ]

View File

@@ -8,64 +8,46 @@ from oslo_policy import policy
from dcmanager.api.policies import base from dcmanager.api.policies import base
POLICY_ROOT = 'dc_api:peer_group_associations:%s' POLICY_ROOT = "dc_api:peer_group_associations:%s"
peer_group_associations_rules = [ peer_group_associations_rules = [
# CRUD of peer_group_associations entity # CRUD of peer_group_associations entity
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'create', name=POLICY_ROOT % "create",
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, check_str="rule:" + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Create peer group association.", description="Create peer group association.",
operations=[ operations=[{"method": "POST", "path": "/v1.0/peer-group-associations"}],
{
'method': 'POST',
'path': '/v1.0/peer-group-associations'
}
]
), ),
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'delete', name=POLICY_ROOT % "delete",
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, check_str="rule:" + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Delete peer group association.", description="Delete peer group association.",
operations=[ operations=[
{ {"method": "DELETE", "path": "/v1.0/peer-group-associations/{associate_id}"}
'method': 'DELETE', ],
'path': '/v1.0/peer-group-associations/{associate_id}'
}
]
), ),
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'get', name=POLICY_ROOT % "get",
check_str='rule:' + base.READER_IN_SYSTEM_PROJECTS, check_str="rule:" + base.READER_IN_SYSTEM_PROJECTS,
description="Get peer group associations.", description="Get peer group associations.",
operations=[ operations=[
{ {"method": "GET", "path": "/v1.0/peer-group-associations"},
'method': 'GET', {"method": "GET", "path": "/v1.0/peer-group-associations/{associate_id}"},
'path': '/v1.0/peer-group-associations' ],
},
{
'method': 'GET',
'path': '/v1.0/peer-group-associations/{associate_id}'
}
]
), ),
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'modify', name=POLICY_ROOT % "modify",
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, check_str="rule:" + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Modify peer group association.", description="Modify peer group association.",
operations=[ operations=[
{"method": "PATCH", "path": "/v1.0/peer-group-associations/{associate_id}"},
{ {
'method': 'PATCH', "method": "PATCH",
'path': '/v1.0/peer-group-associations/{associate_id}' "path": "/v1.0/peer-group-associations/{associate_id}/sync",
}, },
{ ],
'method': 'PATCH', ),
'path': '/v1.0/peer-group-associations/{associate_id}/sync'
}
]
)
] ]

View File

@@ -8,52 +8,47 @@ from oslo_policy import policy
from dcmanager.api.policies import base from dcmanager.api.policies import base
POLICY_ROOT = 'dc_api:phased_subcloud_deploy:%s' POLICY_ROOT = "dc_api:phased_subcloud_deploy:%s"
phased_subcloud_deploy_rules = [ phased_subcloud_deploy_rules = [
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'create', name=POLICY_ROOT % "create",
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, check_str="rule:" + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Create a subcloud", description="Create a subcloud",
operations=[ operations=[{"method": "POST", "path": "/v1.0/phased-subcloud-deploy"}],
{
'method': 'POST',
'path': '/v1.0/phased-subcloud-deploy'
}
]
), ),
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'modify', name=POLICY_ROOT % "modify",
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, check_str="rule:" + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Modify the subcloud deployment.", description="Modify the subcloud deployment.",
operations=[ operations=[
{ {
'method': 'PATCH', "method": "PATCH",
'path': '/v1.0/phased-subcloud-deploy/{subcloud}/abort' "path": "/v1.0/phased-subcloud-deploy/{subcloud}/abort",
}, },
{ {
'method': 'PATCH', "method": "PATCH",
'path': '/v1.0/phased-subcloud-deploy/{subcloud}/resume' "path": "/v1.0/phased-subcloud-deploy/{subcloud}/resume",
}, },
{ {
'method': 'PATCH', "method": "PATCH",
'path': '/v1.0/phased-subcloud-deploy/{subcloud}/install' "path": "/v1.0/phased-subcloud-deploy/{subcloud}/install",
}, },
{ {
'method': 'PATCH', "method": "PATCH",
'path': '/v1.0/phased-subcloud-deploy/{subcloud}/bootstrap' "path": "/v1.0/phased-subcloud-deploy/{subcloud}/bootstrap",
}, },
{ {
'method': 'PATCH', "method": "PATCH",
'path': '/v1.0/phased-subcloud-deploy/{subcloud}/configure' "path": "/v1.0/phased-subcloud-deploy/{subcloud}/configure",
}, },
{ {
'method': 'PATCH', "method": "PATCH",
'path': '/v1.0/phased-subcloud-deploy/{subcloud}/complete' "path": "/v1.0/phased-subcloud-deploy/{subcloud}/complete",
} },
] ],
) ),
] ]

View File

@@ -8,43 +8,33 @@ from oslo_policy import policy
from dcmanager.api.policies import base from dcmanager.api.policies import base
POLICY_ROOT = 'dc_api:subcloud_backup:%s' POLICY_ROOT = "dc_api:subcloud_backup:%s"
subcloud_backup_rules = [ subcloud_backup_rules = [
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'create', name=POLICY_ROOT % "create",
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, check_str="rule:" + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Create new subcloud backup.", description="Create new subcloud backup.",
operations=[ operations=[{"method": "POST", "path": "/v1.0/subcloud-backup"}],
{
'method': 'POST',
'path': '/v1.0/subcloud-backup'
}
]
), ),
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'delete', name=POLICY_ROOT % "delete",
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, check_str="rule:" + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Delete a subcloud backup.", description="Delete a subcloud backup.",
operations=[ operations=[
{ {
'method': 'PATCH', "method": "PATCH",
'path': '/v1.0/subcloud-backup/delete/{release_version}' "path": "/v1.0/subcloud-backup/delete/{release_version}",
} }
] ],
), ),
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'restore', name=POLICY_ROOT % "restore",
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, check_str="rule:" + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Restore a subcloud backup.", description="Restore a subcloud backup.",
operations=[ operations=[{"method": "PATCH", "path": "/v1.0/subcloud-backup/restore"}],
{ ),
'method': 'PATCH',
'path': '/v1.0/subcloud-backup/restore'
}
]
)
] ]

View File

@@ -8,51 +8,34 @@ from oslo_policy import policy
from dcmanager.api.policies import base from dcmanager.api.policies import base
POLICY_ROOT = 'dc_api:subcloud_deploy:%s' POLICY_ROOT = "dc_api:subcloud_deploy:%s"
subcloud_deploy_rules = [ subcloud_deploy_rules = [
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'upload', name=POLICY_ROOT % "upload",
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, check_str="rule:" + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Upload subcloud deploy files.", description="Upload subcloud deploy files.",
operations=[ operations=[{"method": "POST", "path": "/v1.0/subcloud-deploy"}],
{
'method': 'POST',
'path': '/v1.0/subcloud-deploy'
}
]
), ),
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'get', name=POLICY_ROOT % "get",
check_str='rule:' + base.READER_IN_SYSTEM_PROJECTS, check_str="rule:" + base.READER_IN_SYSTEM_PROJECTS,
description="Show subcloud deploy files.", description="Show subcloud deploy files.",
operations=[ operations=[
{ {"method": "GET", "path": "/v1.0/subcloud-deploy"},
'method': 'GET', {"method": "GET", "path": "/v1.0/subcloud-deploy/{release}"},
'path': '/v1.0/subcloud-deploy' ],
},
{
'method': 'GET',
'path': '/v1.0/subcloud-deploy/{release}'
}
]
), ),
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'delete', name=POLICY_ROOT % "delete",
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, check_str="rule:" + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Delete subcloud deploy files.", description="Delete subcloud deploy files.",
operations=[ operations=[
{ {"method": "DELETE", "path": "/v1.0/subcloud-deploy"},
'method': 'DELETE', {"method": "DELETE", "path": "/v1.0/subcloud-deploy/{release}"},
'path': '/v1.0/subcloud-deploy' ],
}, ),
{
'method': 'DELETE',
'path': '/v1.0/subcloud-deploy/{release}'
}
]
)
] ]

View File

@@ -8,62 +8,45 @@ from oslo_policy import policy
from dcmanager.api.policies import base from dcmanager.api.policies import base
POLICY_ROOT = 'dc_api:subcloud_groups:%s' POLICY_ROOT = "dc_api:subcloud_groups:%s"
subcloud_groups_rules = [ subcloud_groups_rules = [
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'create', name=POLICY_ROOT % "create",
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, check_str="rule:" + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Create subcloud group.", description="Create subcloud group.",
operations=[ operations=[{"method": "POST", "path": "/v1.0/subcloud-groups"}],
{
'method': 'POST',
'path': '/v1.0/subcloud-groups'
}
]
), ),
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'delete', name=POLICY_ROOT % "delete",
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, check_str="rule:" + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Delete subcloud group.", description="Delete subcloud group.",
operations=[ operations=[
{ {"method": "DELETE", "path": "/v1.0/subcloud-groups/{subcloud_group}"}
'method': 'DELETE', ],
'path': '/v1.0/subcloud-groups/{subcloud_group}'
}
]
), ),
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'get', name=POLICY_ROOT % "get",
check_str='rule:' + base.READER_IN_SYSTEM_PROJECTS, check_str="rule:" + base.READER_IN_SYSTEM_PROJECTS,
description="Get subcloud groups.", description="Get subcloud groups.",
operations=[ operations=[
{"method": "GET", "path": "/v1.0/subcloud-groups"},
{"method": "GET", "path": "/v1.0/subcloud-groups/{subcloud_group}"},
{ {
'method': 'GET', "method": "GET",
'path': '/v1.0/subcloud-groups' "path": "/v1.0/subcloud-groups/{subcloud_group}/subclouds",
}, },
{ ],
'method': 'GET',
'path': '/v1.0/subcloud-groups/{subcloud_group}'
},
{
'method': 'GET',
'path': '/v1.0/subcloud-groups/{subcloud_group}/subclouds'
}
]
), ),
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'modify', name=POLICY_ROOT % "modify",
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, check_str="rule:" + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Modify subcloud group.", description="Modify subcloud group.",
operations=[ operations=[
{ {"method": "PATCH", "path": "/v1.0/subcloud-groups/{subcloud_group}"}
'method': 'PATCH', ],
'path': '/v1.0/subcloud-groups/{subcloud_group}' ),
}
]
)
] ]

View File

@@ -8,82 +8,73 @@ from oslo_policy import policy
from dcmanager.api.policies import base from dcmanager.api.policies import base
POLICY_ROOT = 'dc_api:subcloud_peer_groups:%s' POLICY_ROOT = "dc_api:subcloud_peer_groups:%s"
_subcloud_peer_groups_rules = [ _subcloud_peer_groups_rules = [
# CRUD of subcloud-peer-groups entity # CRUD of subcloud-peer-groups entity
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'create', name=POLICY_ROOT % "create",
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, check_str="rule:" + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Create subcloud peer group.", description="Create subcloud peer group.",
operations=[ operations=[{"method": "POST", "path": "/v1.0/subcloud-peer-groups"}],
{
'method': 'POST',
'path': '/v1.0/subcloud-peer-groups'
}
]
), ),
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'delete', name=POLICY_ROOT % "delete",
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, check_str="rule:" + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Delete subcloud peer group.", description="Delete subcloud peer group.",
operations=[ operations=[
{ {
'method': 'DELETE', "method": "DELETE",
'path': '/v1.0/subcloud-peer-groups/{subcloud_peer_group}' "path": "/v1.0/subcloud-peer-groups/{subcloud_peer_group}",
} }
] ],
), ),
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'get', name=POLICY_ROOT % "get",
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, check_str="rule:" + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Get Subcloud Peer Group data", description="Get Subcloud Peer Group data",
operations=[ operations=[
{ {"method": "GET", "path": "/v1.0/subcloud-peer-groups/"},
'method': 'GET',
'path': '/v1.0/subcloud-peer-groups/'
},
# Show details of a specified Subcloud Peer Group # Show details of a specified Subcloud Peer Group
{ {
'method': 'GET', "method": "GET",
'path': '/v1.0/subcloud-peer-groups/{subcloud_peer_group}' "path": "/v1.0/subcloud-peer-groups/{subcloud_peer_group}",
}, },
# Show subclouds status of the subcloud-peer-group # Show subclouds status of the subcloud-peer-group
{ {
'method': 'GET', "method": "GET",
'path': '/v1.0/subcloud-peer-groups/{subcloud_peer_group}/status' "path": "/v1.0/subcloud-peer-groups/{subcloud_peer_group}/status",
}, },
# List Subclouds assigned to the given Subcloud Peer Group # List Subclouds assigned to the given Subcloud Peer Group
{ {
'method': 'GET', "method": "GET",
'path': '/v1.0/subcloud-peer-groups/{subcloud_peer_group}/subclouds' "path": "/v1.0/subcloud-peer-groups/{subcloud_peer_group}/subclouds",
} },
] ],
), ),
# Update a Subcloud Peer Group with specified configuration # Update a Subcloud Peer Group with specified configuration
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'modify', name=POLICY_ROOT % "modify",
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, check_str="rule:" + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Update a Subcloud Peer Group with specified configuration", description="Update a Subcloud Peer Group with specified configuration",
operations=[ operations=[
{ {
'method': 'PATCH', "method": "PATCH",
'path': '/v1.0/subcloud-peer-groups/{subcloud_peer_group}' "path": "/v1.0/subcloud-peer-groups/{subcloud_peer_group}",
}, },
# Migrate subclouds entity of the subcloud-peer-group # Migrate subclouds entity of the subcloud-peer-group
{ {
'method': 'PATCH', "method": "PATCH",
'path': '/v1.0/subcloud-peer-groups/{subcloud_peer_group}/migrate' "path": "/v1.0/subcloud-peer-groups/{subcloud_peer_group}/migrate",
}, },
# Trigger a peer group audit # Trigger a peer group audit
{ {
'method': 'PATCH', "method": "PATCH",
'path': '/v1.0/subcloud-peer-groups/{subcloud_peer_group}/audit' "path": "/v1.0/subcloud-peer-groups/{subcloud_peer_group}/audit",
} },
] ],
) ),
] ]

View File

@@ -8,86 +8,46 @@ from oslo_policy import policy
from dcmanager.api.policies import base from dcmanager.api.policies import base
POLICY_ROOT = 'dc_api:subclouds:%s' POLICY_ROOT = "dc_api:subclouds:%s"
subclouds_rules = [ subclouds_rules = [
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'create', name=POLICY_ROOT % "create",
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, check_str="rule:" + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Create a subcloud.", description="Create a subcloud.",
operations=[ operations=[{"method": "POST", "path": "/v1.0/subclouds"}],
{
'method': 'POST',
'path': '/v1.0/subclouds'
}
]
), ),
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'delete', name=POLICY_ROOT % "delete",
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, check_str="rule:" + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Delete a subcloud.", description="Delete a subcloud.",
operations=[ operations=[{"method": "DELETE", "path": "/v1.0/subclouds/{alarm_uuid}"}],
{
'method': 'DELETE',
'path': '/v1.0/subclouds/{alarm_uuid}'
}
]
), ),
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'get', name=POLICY_ROOT % "get",
check_str='rule:' + base.READER_IN_SYSTEM_PROJECTS, check_str="rule:" + base.READER_IN_SYSTEM_PROJECTS,
description="Get subclouds data.", description="Get subclouds data.",
operations=[ operations=[
{ {"method": "GET", "path": "/v1.0/subclouds"},
'method': 'GET', {"method": "GET", "path": "/v1.0/subclouds/{subcloud}"},
'path': '/v1.0/subclouds' {"method": "GET", "path": "/v1.0/subclouds/{subcloud}/detail"},
}, ],
{
'method': 'GET',
'path': '/v1.0/subclouds/{subcloud}'
},
{
'method': 'GET',
'path': '/v1.0/subclouds/{subcloud}/detail'
}
]
), ),
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'modify', name=POLICY_ROOT % "modify",
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, check_str="rule:" + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Modify a subcloud.", description="Modify a subcloud.",
operations=[ operations=[
{ {"method": "PATCH", "path": "/v1.0/subclouds/{subcloud}"},
'method': 'PATCH', {"method": "PATCH", "path": "/v1.0/subclouds/{subcloud}/prestage"},
'path': '/v1.0/subclouds/{subcloud}' {"method": "PATCH", "path": "/v1.0/subclouds/{subcloud}/reconfigure"},
}, {"method": "PATCH", "path": "/v1.0/subclouds/{subcloud}/reinstall"},
{ {"method": "PATCH", "path": "/v1.0/subclouds/{subcloud}/redeploy"},
'method': 'PATCH', {"method": "PATCH", "path": "/v1.0/subclouds/{subcloud}/restore"},
'path': '/v1.0/subclouds/{subcloud}/prestage' {"method": "PATCH", "path": "/v1.0/subclouds/{subcloud}/update_status"},
}, ],
{ ),
'method': 'PATCH',
'path': '/v1.0/subclouds/{subcloud}/reconfigure'
},
{
'method': 'PATCH',
'path': '/v1.0/subclouds/{subcloud}/reinstall'
},
{
'method': 'PATCH',
'path': '/v1.0/subclouds/{subcloud}/redeploy'
},
{
'method': 'PATCH',
'path': '/v1.0/subclouds/{subcloud}/restore'
},
{
'method': 'PATCH',
'path': '/v1.0/subclouds/{subcloud}/update_status'
}
]
)
] ]

View File

@@ -8,47 +8,31 @@ from oslo_policy import policy
from dcmanager.api.policies import base from dcmanager.api.policies import base
POLICY_ROOT = 'dc_api:sw_update_options:%s' POLICY_ROOT = "dc_api:sw_update_options:%s"
sw_update_options_rules = [ sw_update_options_rules = [
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'delete', name=POLICY_ROOT % "delete",
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, check_str="rule:" + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Delete per subcloud sw-update options.", description="Delete per subcloud sw-update options.",
operations=[ operations=[{"method": "DELETE", "path": "/v1.0/sw-update-options/{subcloud}"}],
{
'method': 'DELETE',
'path': '/v1.0/sw-update-options/{subcloud}'
}
]
), ),
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'get', name=POLICY_ROOT % "get",
check_str='rule:' + base.READER_IN_SYSTEM_PROJECTS, check_str="rule:" + base.READER_IN_SYSTEM_PROJECTS,
description="Get sw-update options.", description="Get sw-update options.",
operations=[ operations=[
{ {"method": "GET", "path": "/v1.0/sw-update-options"},
'method': 'GET', {"method": "GET", "path": "/v1.0/sw-update-options/{subcloud}"},
'path': '/v1.0/sw-update-options' ],
},
{
'method': 'GET',
'path': '/v1.0/sw-update-options/{subcloud}'
}
]
), ),
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'update', name=POLICY_ROOT % "update",
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, check_str="rule:" + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Update sw-update options (defaults or per subcloud).", description="Update sw-update options (defaults or per subcloud).",
operations=[ operations=[{"method": "POST", "path": "/v1.0/sw-update-options/{subcloud}"}],
{ ),
'method': 'POST',
'path': '/v1.0/sw-update-options/{subcloud}'
}
]
)
] ]

View File

@@ -8,73 +8,44 @@ from oslo_policy import policy
from dcmanager.api.policies import base from dcmanager.api.policies import base
POLICY_ROOT = 'dc_api:sw_update_strategy:%s' POLICY_ROOT = "dc_api:sw_update_strategy:%s"
sw_update_strategy_rules = [ sw_update_strategy_rules = [
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'abort', name=POLICY_ROOT % "abort",
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, check_str="rule:" + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Abort update strategy execution.", description="Abort update strategy execution.",
operations=[ operations=[{"method": "POST", "path": "/v1.0/sw-update-strategy/actions"}],
{
'method': 'POST',
'path': '/v1.0/sw-update-strategy/actions'
}
]
), ),
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'apply', name=POLICY_ROOT % "apply",
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, check_str="rule:" + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Apply update strategy.", description="Apply update strategy.",
operations=[ operations=[{"method": "POST", "path": "/v1.0/sw-update-strategy/actions"}],
{
'method': 'POST',
'path': '/v1.0/sw-update-strategy/actions'
}
]
), ),
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'create', name=POLICY_ROOT % "create",
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, check_str="rule:" + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Create update strategy.", description="Create update strategy.",
operations=[ operations=[{"method": "POST", "path": "/v1.0/sw-update-strategy"}],
{
'method': 'POST',
'path': '/v1.0/sw-update-strategy'
}
]
), ),
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'delete', name=POLICY_ROOT % "delete",
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, check_str="rule:" + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Delete update strategy.", description="Delete update strategy.",
operations=[ operations=[{"method": "DELETE", "path": "/v1.0/sw-update-strategy"}],
{
'method': 'DELETE',
'path': '/v1.0/sw-update-strategy'
}
]
), ),
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'get', name=POLICY_ROOT % "get",
check_str='rule:' + base.READER_IN_SYSTEM_PROJECTS, check_str="rule:" + base.READER_IN_SYSTEM_PROJECTS,
description="Get update strategy.", description="Get update strategy.",
operations=[ operations=[
{ {"method": "GET", "path": "/v1.0/sw-update-strategy"},
'method': 'GET', {"method": "GET", "path": "/v1.0/sw-update-strategy/steps"},
'path': '/v1.0/sw-update-strategy' {"method": "GET", "path": "/v1.0/sw-update-strategy/steps/{cloud_name}"},
},
{
'method': 'GET',
'path': '/v1.0/sw-update-strategy/steps'
},
{
'method': 'GET',
'path': '/v1.0/sw-update-strategy/steps/{cloud_name}'
}
], ],
) ),
] ]

View File

@@ -7,64 +7,43 @@ from oslo_policy import policy
from dcmanager.api.policies import base from dcmanager.api.policies import base
POLICY_ROOT = 'dc_api:system_peers:%s' POLICY_ROOT = "dc_api:system_peers:%s"
system_peers_rules = [ system_peers_rules = [
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'create', name=POLICY_ROOT % "create",
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, check_str="rule:" + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Create system peer.", description="Create system peer.",
operations=[ operations=[{"method": "POST", "path": "/v1.0/system-peers"}],
{
'method': 'POST',
'path': '/v1.0/system-peers'
}
]
), ),
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'delete', name=POLICY_ROOT % "delete",
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, check_str="rule:" + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Delete system peer.", description="Delete system peer.",
operations=[ operations=[{"method": "DELETE", "path": "/v1.0/system-peers/{system_peer}"}],
{
'method': 'DELETE',
'path': '/v1.0/system-peers/{system_peer}'
}
]
), ),
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'get', name=POLICY_ROOT % "get",
check_str='rule:' + base.READER_IN_SYSTEM_PROJECTS, check_str="rule:" + base.READER_IN_SYSTEM_PROJECTS,
description="Get system peers.", description="Get system peers.",
operations=[ operations=[
{ {"method": "GET", "path": "/v1.0/system-peers"},
'method': 'GET',
'path': '/v1.0/system-peers'
},
# Show details of a specified System Peer # Show details of a specified System Peer
{ {"method": "GET", "path": "/v1.0/system-peers/{system_peer}"},
'method': 'GET',
'path': '/v1.0/system-peers/{system_peer}'
},
# List Subcloud Peer Groups associated with the given System Peer # List Subcloud Peer Groups associated with the given System Peer
{ {
'method': 'GET', "method": "GET",
'path': '/v1.0/system-peers/{system_peer}/subcloud-peer-groups' "path": "/v1.0/system-peers/{system_peer}/subcloud-peer-groups",
} },
] ],
), ),
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'modify', name=POLICY_ROOT % "modify",
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, check_str="rule:" + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Modify system peer.", description="Modify system peer.",
operations=[ operations=[{"method": "PATCH", "path": "/v1.0/system-peers/{system_peer}"}],
{ ),
'method': 'PATCH',
'path': '/v1.0/system-peers/{system_peer}'
}
]
)
] ]

View File

@@ -36,22 +36,24 @@ def reset():
_ENFORCER = None _ENFORCER = None
def init(policy_file='policy.yaml'): def init(policy_file="policy.yaml"):
"""Init an Enforcer class. """Init an Enforcer class.
:param policy_file: Custom policy file to be used. :param policy_file: Custom policy file to be used.
:return: Returns a Enforcer instance. :return: Returns a Enforcer instance.
""" """
global _ENFORCER global _ENFORCER
if not _ENFORCER: if not _ENFORCER:
# https://docs.openstack.org/oslo.policy/latest/user/usage.html # https://docs.openstack.org/oslo.policy/latest/user/usage.html
_ENFORCER = policy.Enforcer(CONF, _ENFORCER = policy.Enforcer(
policy_file=policy_file, CONF,
default_rule='default', policy_file=policy_file,
use_conf=True, default_rule="default",
overwrite=True) use_conf=True,
overwrite=True,
)
_ENFORCER.register_defaults(controller_policies.list_rules()) _ENFORCER.register_defaults(controller_policies.list_rules())
return _ENFORCER return _ENFORCER
@@ -59,5 +61,6 @@ def init(policy_file='policy.yaml'):
def authorize(rule, target, creds, do_raise=True): def authorize(rule, target, creds, do_raise=True):
"""A wrapper around 'authorize' from 'oslo_policy.policy'.""" """A wrapper around 'authorize' from 'oslo_policy.policy'."""
init() init()
return _ENFORCER.authorize(rule, target, creds, do_raise=do_raise, return _ENFORCER.authorize(
exc=exc.HTTPForbidden) rule, target, creds, do_raise=do_raise, exc=exc.HTTPForbidden
)

View File

@@ -2651,7 +2651,7 @@ class TestSubcloudsDelete(BaseTestSubcloudsController):
self._assert_pecan_and_response( self._assert_pecan_and_response(
response, http.client.BAD_REQUEST, response, http.client.BAD_REQUEST,
"Cannot delete a subcloud that is \"managed\" status" "Cannot delete a subcloud that is 'managed' status"
) )
def test_delete_fails_with_invalid_deploy_states(self): def test_delete_fails_with_invalid_deploy_states(self):

View File

@@ -13,12 +13,12 @@ READER_IN_SYSTEM_PROJECTS = "reader_in_system_projects"
base_rules = [ base_rules = [
policy.RuleDefault( policy.RuleDefault(
name=ADMIN_IN_SYSTEM_PROJECTS, name=ADMIN_IN_SYSTEM_PROJECTS,
check_str="role:admin and (project_name:admin or " + "project_name:services)", check_str="role:admin and (project_name:admin or project_name:services)",
description="Base rule.", description="Base rule.",
), ),
policy.RuleDefault( policy.RuleDefault(
name=READER_IN_SYSTEM_PROJECTS, name=READER_IN_SYSTEM_PROJECTS,
check_str="role:reader and (project_name:admin or " + "project_name:services)", check_str="role:reader and (project_name:admin or project_name:services)",
description="Base rule.", description="Base rule.",
), ),
] ]

View File

@@ -22,13 +22,16 @@ formatted_modules = [
"dccommon", "dccommon",
"dcdbsync", "dcdbsync",
"dcorch", "dcorch",
"dcmanager/api",
] ]
# Function to run black check # Function to run black check
def run_black_check(module): def run_black_check(module):
try: try:
subprocess.run(["black", "--check", "--quiet", f"./{module}"], check=True) subprocess.run(
["black", "--check", "--diff", "--quiet", f"./{module}"], check=True
)
print(f"Black check passed for {module}") print(f"Black check passed for {module}")
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
print(f"Black check failed for {module}") print(f"Black check failed for {module}")