4438b8fd55
This commit enables the check of new pylint/pep8 violations. PYLINT - All convention related checks, except: - missing-class-docstring - missing-function-docstring - missing-module-docstring - consider-using-f-string - invalid-name - import-outside-toplevel - too-many-lines - consider-iterating-dictionary - unnecessary-lambda-assignment PEP8: - E117: over-indented - E123: closing bracket does not match indentation of opening bracket's line - E125: continuation line with the same indent as the next logical line - E305: expected 2 blank lines after class or function definition - E402: module level import not at top of file - E501: line too long - H216: flag use of third party mock Test Plan: 1. Perform `tox` command - Pass in py39, pylint, pep8 Closes-bug: 2033294 Change-Id: I635df8e809905cff582bd9d5eb57b91133560cf9 Signed-off-by: Hugo Brito <hugo.brito@windriver.com>
281 lines
11 KiB
Python
281 lines
11 KiB
Python
# Copyright (c) 2017 Ericsson AB.
|
|
# Copyright (c) 2020-2022, 2024 Wind River Systems, Inc.
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
#
|
|
|
|
import http.client as httpclient
|
|
from oslo_config import cfg
|
|
from oslo_db import exception as db_exc
|
|
from oslo_log import log as logging
|
|
from oslo_messaging import RemoteError
|
|
import pecan
|
|
from pecan import expose
|
|
from pecan import request
|
|
|
|
from dcmanager.api.controllers import restcomm
|
|
from dcmanager.api.policies import subcloud_group as subcloud_group_policy
|
|
from dcmanager.api import policy
|
|
from dcmanager.common import consts
|
|
from dcmanager.common.i18n import _
|
|
from dcmanager.common import utils
|
|
from dcmanager.db import api as db_api
|
|
from dcmanager.rpc import client as rpc_client
|
|
|
|
CONF = cfg.CONF
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
SUPPORTED_GROUP_APPLY_TYPES = [
|
|
consts.SUBCLOUD_APPLY_TYPE_PARALLEL,
|
|
consts.SUBCLOUD_APPLY_TYPE_SERIAL
|
|
]
|
|
|
|
# validation constants for Subcloud Group
|
|
MAX_SUBCLOUD_GROUP_NAME_LEN = 255
|
|
MAX_SUBCLOUD_GROUP_DESCRIPTION_LEN = 255
|
|
MIN_SUBCLOUD_GROUP_MAX_PARALLEL_SUBCLOUDS = 1
|
|
MAX_SUBCLOUD_GROUP_MAX_PARALLEL_SUBCLOUDS = 500
|
|
|
|
|
|
class SubcloudGroupsController(restcomm.GenericPathController):
|
|
|
|
def __init__(self):
|
|
super(SubcloudGroupsController, self).__init__()
|
|
self.rpc_client = rpc_client.ManagerClient()
|
|
|
|
@expose(generic=True, template='json')
|
|
def index(self):
|
|
# Route the request to specific methods with parameters
|
|
pass
|
|
|
|
def _get_subcloud_list_for_group(self, context, group_id):
|
|
subclouds = db_api.subcloud_get_for_group(context, group_id)
|
|
return utils.subcloud_db_list_to_dict(subclouds)
|
|
|
|
def _get_subcloud_group_list(self, context):
|
|
groups = db_api.subcloud_group_get_all(context)
|
|
subcloud_group_list = []
|
|
|
|
for group in groups:
|
|
group_dict = db_api.subcloud_group_db_model_to_dict(group)
|
|
subcloud_group_list.append(group_dict)
|
|
|
|
result = dict()
|
|
result['subcloud_groups'] = subcloud_group_list
|
|
return result
|
|
|
|
@index.when(method='GET', template='json')
|
|
def get(self, group_ref=None, subclouds=False):
|
|
"""Get details about subcloud group.
|
|
|
|
:param group_ref: ID or name of subcloud group
|
|
"""
|
|
policy.authorize(subcloud_group_policy.POLICY_ROOT % "get", {},
|
|
restcomm.extract_credentials_for_policy())
|
|
context = restcomm.extract_context_from_environ()
|
|
|
|
if group_ref is None:
|
|
# List of subcloud groups requested
|
|
return self._get_subcloud_group_list(context)
|
|
|
|
group = utils.subcloud_group_get_by_ref(context, group_ref)
|
|
if group is None:
|
|
pecan.abort(httpclient.NOT_FOUND, _('Subcloud Group not found'))
|
|
if subclouds:
|
|
# Return only the subclouds for this subcloud group
|
|
return self._get_subcloud_list_for_group(context, group.id)
|
|
subcloud_group_dict = db_api.subcloud_group_db_model_to_dict(group)
|
|
return subcloud_group_dict
|
|
|
|
def _validate_description(self, description):
|
|
if not description:
|
|
return False
|
|
if len(description) >= MAX_SUBCLOUD_GROUP_DESCRIPTION_LEN:
|
|
return False
|
|
return True
|
|
|
|
def _validate_update_apply_type(self, update_apply_type):
|
|
if not update_apply_type:
|
|
return False
|
|
if update_apply_type not in SUPPORTED_GROUP_APPLY_TYPES:
|
|
return False
|
|
return True
|
|
|
|
def _validate_max_parallel_subclouds(self, max_parallel_str):
|
|
if not max_parallel_str:
|
|
return False
|
|
try:
|
|
# Check the value is an integer
|
|
val = int(max_parallel_str)
|
|
except ValueError:
|
|
return False
|
|
|
|
# We do not support less than min or greater than max
|
|
if val < MIN_SUBCLOUD_GROUP_MAX_PARALLEL_SUBCLOUDS:
|
|
return False
|
|
if val > MAX_SUBCLOUD_GROUP_MAX_PARALLEL_SUBCLOUDS:
|
|
return False
|
|
return True
|
|
|
|
@index.when(method='POST', template='json')
|
|
def post(self):
|
|
"""Create a new subcloud group."""
|
|
policy.authorize(subcloud_group_policy.POLICY_ROOT % "create", {},
|
|
restcomm.extract_credentials_for_policy())
|
|
context = restcomm.extract_context_from_environ()
|
|
|
|
payload = eval(request.body)
|
|
if not payload:
|
|
pecan.abort(httpclient.BAD_REQUEST, _('Body required'))
|
|
|
|
name = payload.get('name')
|
|
description = payload.get('description')
|
|
update_apply_type = payload.get('update_apply_type')
|
|
max_parallel_subclouds = payload.get('max_parallel_subclouds')
|
|
|
|
# Validate payload
|
|
if not utils.validate_name(name,
|
|
prohibited_name_list=[
|
|
consts.DEFAULT_SUBCLOUD_GROUP_NAME]):
|
|
pecan.abort(httpclient.BAD_REQUEST, _('Invalid group name'))
|
|
if not self._validate_description(description):
|
|
pecan.abort(httpclient.BAD_REQUEST, _('Invalid group description'))
|
|
if not self._validate_update_apply_type(update_apply_type):
|
|
pecan.abort(httpclient.BAD_REQUEST,
|
|
_('Invalid group update_apply_type'))
|
|
if not self._validate_max_parallel_subclouds(max_parallel_subclouds):
|
|
pecan.abort(httpclient.BAD_REQUEST,
|
|
_('Invalid group max_parallel_subclouds'))
|
|
try:
|
|
group_ref = db_api.subcloud_group_create(context,
|
|
name,
|
|
description,
|
|
update_apply_type,
|
|
max_parallel_subclouds)
|
|
return db_api.subcloud_group_db_model_to_dict(group_ref)
|
|
except db_exc.DBDuplicateEntry:
|
|
LOG.info("Group create failed. Group %s already exists" % name)
|
|
pecan.abort(httpclient.BAD_REQUEST,
|
|
_('A subcloud group with this name already exists'))
|
|
except RemoteError as e:
|
|
pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value)
|
|
except Exception as e:
|
|
LOG.exception(e)
|
|
pecan.abort(httpclient.INTERNAL_SERVER_ERROR,
|
|
_('Unable to create subcloud group'))
|
|
|
|
@index.when(method='PATCH', template='json')
|
|
def patch(self, group_ref):
|
|
"""Update a subcloud group.
|
|
|
|
:param group_ref: ID or name of subcloud group to update
|
|
"""
|
|
|
|
policy.authorize(subcloud_group_policy.POLICY_ROOT % "modify", {},
|
|
restcomm.extract_credentials_for_policy())
|
|
context = restcomm.extract_context_from_environ()
|
|
if group_ref is None:
|
|
pecan.abort(httpclient.BAD_REQUEST,
|
|
_('Subcloud Group Name or ID required'))
|
|
|
|
payload = eval(request.body)
|
|
if not payload:
|
|
pecan.abort(httpclient.BAD_REQUEST, _('Body required'))
|
|
|
|
group = utils.subcloud_group_get_by_ref(context, group_ref)
|
|
if group is None:
|
|
pecan.abort(httpclient.NOT_FOUND, _('Subcloud Group not found'))
|
|
|
|
name = payload.get('name')
|
|
description = payload.get('description')
|
|
update_apply_type = payload.get('update_apply_type')
|
|
max_parallel_str = payload.get('max_parallel_subclouds')
|
|
|
|
if not (name or description or update_apply_type or max_parallel_str):
|
|
pecan.abort(httpclient.BAD_REQUEST, _('nothing to update'))
|
|
|
|
# Check value is not None or empty before calling validate
|
|
if name:
|
|
if not utils.validate_name(name,
|
|
prohibited_name_list=[
|
|
consts.DEFAULT_SUBCLOUD_GROUP_NAME]):
|
|
pecan.abort(httpclient.BAD_REQUEST,
|
|
_('Invalid group name'))
|
|
# Special case. Default group name cannot be changed
|
|
if group.id == consts.DEFAULT_SUBCLOUD_GROUP_ID:
|
|
pecan.abort(httpclient.BAD_REQUEST,
|
|
_('Default group name cannot be changed'))
|
|
|
|
if description:
|
|
if not self._validate_description(description):
|
|
pecan.abort(httpclient.BAD_REQUEST,
|
|
_('Invalid group description'))
|
|
if update_apply_type:
|
|
if not self._validate_update_apply_type(update_apply_type):
|
|
pecan.abort(httpclient.BAD_REQUEST,
|
|
_('Invalid group update_apply_type'))
|
|
if max_parallel_str:
|
|
if not self._validate_max_parallel_subclouds(max_parallel_str):
|
|
pecan.abort(httpclient.BAD_REQUEST,
|
|
_('Invalid group max_parallel_subclouds'))
|
|
|
|
try:
|
|
updated_group = db_api.subcloud_group_update(
|
|
context,
|
|
group.id,
|
|
name=name,
|
|
description=description,
|
|
update_apply_type=update_apply_type,
|
|
max_parallel_subclouds=max_parallel_str)
|
|
return db_api.subcloud_group_db_model_to_dict(updated_group)
|
|
except RemoteError as e:
|
|
pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value)
|
|
except Exception as e:
|
|
# additional exceptions.
|
|
LOG.exception(e)
|
|
pecan.abort(httpclient.INTERNAL_SERVER_ERROR,
|
|
_('Unable to update subcloud group'))
|
|
|
|
@index.when(method='delete', template='json')
|
|
def delete(self, group_ref):
|
|
"""Delete the subcloud group."""
|
|
policy.authorize(subcloud_group_policy.POLICY_ROOT % "delete", {},
|
|
restcomm.extract_credentials_for_policy())
|
|
context = restcomm.extract_context_from_environ()
|
|
|
|
if group_ref is None:
|
|
pecan.abort(httpclient.BAD_REQUEST,
|
|
_('Subcloud Group Name or ID required'))
|
|
group = utils.subcloud_group_get_by_ref(context, group_ref)
|
|
if group is None:
|
|
pecan.abort(httpclient.NOT_FOUND, _('Subcloud Group not found'))
|
|
if group.name == consts.DEFAULT_SUBCLOUD_GROUP_NAME:
|
|
pecan.abort(httpclient.BAD_REQUEST,
|
|
_('Default Subcloud Group may not be deleted'))
|
|
try:
|
|
# a subcloud group may not be deleted if it is use by any subclouds
|
|
subclouds = db_api.subcloud_get_for_group(context, group.id)
|
|
if len(subclouds) > 0:
|
|
pecan.abort(httpclient.BAD_REQUEST,
|
|
_('Subcloud Group not empty'))
|
|
db_api.subcloud_group_destroy(context, group.id)
|
|
except RemoteError as e:
|
|
pecan.abort(httpclient.UNPROCESSABLE_ENTITY, e.value)
|
|
except Exception as e:
|
|
LOG.exception(e)
|
|
pecan.abort(httpclient.INTERNAL_SERVER_ERROR,
|
|
_('Unable to delete subcloud group'))
|
|
# This should return nothing
|
|
return None
|