Hugo Brito 9aa90f102e 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>
2024-07-03 15:52:22 -03:00

581 lines
21 KiB
Python

# Copyright (c) 2023-2024 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import http.client as httpclient
import json
import uuid
import ipaddress
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 system_peers as system_peer_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__)
# validation constants for System Peer
MAX_SYSTEM_PEER_MANAGER_ENDPOINT_LEN = 255
MAX_SYSTEM_PEER_MANAGER_USERNAME_LEN = 255
MAX_SYSTEM_PEER_MANAGER_PASSWORD_LEN = 255
MAX_SYSTEM_PEER_STRING_DEFAULT_LEN = 255
# validation constants for System Peer Administrative State
# Set to disabled this function will be disabled
#
# We will not support this function in the first release
SYSTEM_PEER_ADMINISTRATIVE_STATE_LIST = ["enabled", "disabled"]
MIN_SYSTEM_PEER_HEARTBEAT_INTERVAL = 10
MAX_SYSTEM_PEER_HEARTBEAT_INTERVAL = 600
MIN_SYSTEM_PEER_HEARTBEAT_FAILURE_THRESHOLD = 1
MAX_SYSTEM_PEER_HEARTBEAT_FAILURE_THRESHOLD = 30
# validation constants for System Peer Heartbeat Failure Policy
# Set to alarm this function will be triggered alarm when the
# heartbeat failure threshold is reached
# Set to rehome this function will be automatically rehome the
# subcloud when the heartbeat failure threshold is reached
# Set to delegate this function will be delegate the system when
# the heartbeat failure threshold is reached
#
# We will only support alarm in the first release
SYSTEM_PEER_HEARTBEAT_FAILURE_POLICY_LIST = ["alarm", "rehome", "delegate"]
MIN_SYSTEM_PEER_HEARTBEAT_MAINTENACE_TIMEOUT = 300
MAX_SYSTEM_PEER_HEARTBEAT_MAINTENACE_TIMEOUT = 36000
class SystemPeersController(restcomm.GenericPathController):
def __init__(self):
super(SystemPeersController, self).__init__()
self.dcmanager_rpc_client = rpc_client.ManagerClient()
@expose(generic=True, template="json")
def index(self):
# Route the request to specific methods with parameters
pass
@staticmethod
def _get_payload(request):
try:
payload = json.loads(request.body)
except Exception:
error_msg = "Request body is malformed."
LOG.exception(error_msg)
pecan.abort(400, _(error_msg))
if not isinstance(payload, dict):
pecan.abort(400, _("Invalid request body format"))
return payload
def _get_peer_group_list_for_system_peer(self, context, peer_id):
peer_groups = db_api.peer_group_get_for_system_peer(context, peer_id)
return utils.subcloud_peer_group_db_list_to_dict(peer_groups)
def _get_system_peer_list(self, context):
peers = db_api.system_peer_get_all(context)
system_peer_list = list()
for peer in peers:
peer_dict = db_api.system_peer_db_model_to_dict(peer)
system_peer_list.append(peer_dict)
result = dict()
result["system_peers"] = system_peer_list
return result
@index.when(method="GET", template="json")
def get(self, peer_ref=None, subcloud_peer_groups=False):
"""Retrieve information about a system peer.
This function allows you to retrieve details about a specific
system peer or obtain a list of subcloud peer groups associated with
a specific system peer.
:param peer_ref: ID or UUID or Name of system peer
:param subcloud_peer_groups: If this request should return subcloud
peer groups
"""
policy.authorize(
system_peer_policy.POLICY_ROOT % "get",
{},
restcomm.extract_credentials_for_policy(),
)
context = restcomm.extract_context_from_environ()
if peer_ref is None:
# List of system peers requested
return self._get_system_peer_list(context)
peer = utils.system_peer_get_by_ref(context, peer_ref)
if peer is None:
pecan.abort(httpclient.NOT_FOUND, _("System Peer not found"))
if subcloud_peer_groups:
return self._get_peer_group_list_for_system_peer(context, peer.id)
system_peer_dict = db_api.system_peer_db_model_to_dict(peer)
return system_peer_dict
def _validate_uuid(self, _uuid):
try:
uuid.UUID(str(_uuid))
return True
except ValueError:
LOG.exception("Invalid UUID: %s" % _uuid)
return False
def _validate_manager_endpoint(self, endpoint):
if (
not endpoint
or len(endpoint) >= MAX_SYSTEM_PEER_MANAGER_ENDPOINT_LEN
or not endpoint.startswith(("http", "https"))
):
LOG.debug("Invalid manager_endpoint: %s" % endpoint)
return False
return True
def _validate_manager_username(self, username):
if not username or len(username) >= MAX_SYSTEM_PEER_MANAGER_USERNAME_LEN:
LOG.debug("Invalid manager_username: %s" % username)
return False
return True
def _validate_manager_password(self, password):
if not password or len(password) >= MAX_SYSTEM_PEER_MANAGER_PASSWORD_LEN:
LOG.debug("Invalid manager_password: %s" % password)
return False
return True
def _validate_peer_controller_gateway_ip(self, ip):
if not ip or len(ip) >= MAX_SYSTEM_PEER_STRING_DEFAULT_LEN:
LOG.debug("Invalid peer_manager_gateway_address: %s" % ip)
return False
try:
ipaddress.ip_address(ip)
return True
except Exception:
LOG.warning("Invalid IP address: %s" % ip)
return False
def _validate_administrative_state(self, administrative_state):
if administrative_state not in SYSTEM_PEER_ADMINISTRATIVE_STATE_LIST:
LOG.debug("Invalid administrative_state: %s" % administrative_state)
return False
return True
def _validate_heartbeat_interval(self, heartbeat_interval):
try:
# Check the value is an integer
val = int(heartbeat_interval)
except ValueError:
LOG.warning("Invalid heartbeat_interval: %s" % heartbeat_interval)
return False
# We do not support less than min or greater than max
if (
val < MIN_SYSTEM_PEER_HEARTBEAT_INTERVAL
or val > MAX_SYSTEM_PEER_HEARTBEAT_INTERVAL
):
LOG.debug("Invalid heartbeat_interval: %s" % heartbeat_interval)
return False
return True
def _validate_heartbeat_failure_threshold(self, heartbeat_failure_threshold):
try:
# Check the value is an integer
val = int(heartbeat_failure_threshold)
except ValueError:
LOG.warning(
"Invalid heartbeat_failure_threshold: %s" % heartbeat_failure_threshold
)
return False
# We do not support less than min or greater than max
if (
val < MIN_SYSTEM_PEER_HEARTBEAT_FAILURE_THRESHOLD
or val > MAX_SYSTEM_PEER_HEARTBEAT_FAILURE_THRESHOLD
):
LOG.debug(
"Invalid heartbeat_failure_threshold: %s" % heartbeat_failure_threshold
)
return False
return True
def _validate_heartbeat_failure_policy(self, heartbeat_failure_policy):
if heartbeat_failure_policy not in SYSTEM_PEER_HEARTBEAT_FAILURE_POLICY_LIST:
LOG.debug("Invalid heartbeat_failure_policy: %s" % heartbeat_failure_policy)
return False
return True
def _validate_heartbeat_maintenance_timeout(self, heartbeat_maintenance_timeout):
try:
# Check the value is an integer
val = int(heartbeat_maintenance_timeout)
except ValueError:
LOG.warning(
"Invalid heartbeat_maintenance_timeout: %s"
% heartbeat_maintenance_timeout
)
return False
# We do not support less than min or greater than max
if (
val < MIN_SYSTEM_PEER_HEARTBEAT_MAINTENACE_TIMEOUT
or val > MAX_SYSTEM_PEER_HEARTBEAT_MAINTENACE_TIMEOUT
):
LOG.debug(
"Invalid heartbeat_maintenance_timeout: %s"
% heartbeat_maintenance_timeout
)
return False
return True
@index.when(method="POST", template="json")
def post(self):
"""Create a new system peer."""
policy.authorize(
system_peer_policy.POLICY_ROOT % "create",
{},
restcomm.extract_credentials_for_policy(),
)
context = restcomm.extract_context_from_environ()
LOG.info("Creating a new system peer: %s" % context)
payload = self._get_payload(request)
if not payload:
pecan.abort(httpclient.BAD_REQUEST, _("Body required"))
# Validate payload
peer_uuid = payload.get("peer_uuid")
if not self._validate_uuid(peer_uuid):
pecan.abort(httpclient.BAD_REQUEST, _("Invalid peer uuid"))
peer_name = payload.get("peer_name")
if not utils.validate_name(peer_name):
pecan.abort(httpclient.BAD_REQUEST, _("Invalid peer name"))
endpoint = payload.get("manager_endpoint")
if not self._validate_manager_endpoint(endpoint):
pecan.abort(httpclient.BAD_REQUEST, _("Invalid peer manager_endpoint"))
username = payload.get("manager_username")
if not self._validate_manager_username(username):
pecan.abort(httpclient.BAD_REQUEST, _("Invalid peer manager_username"))
password = payload.get("manager_password")
if not self._validate_manager_password(password):
pecan.abort(httpclient.BAD_REQUEST, _("Invalid peer manager_password"))
gateway_ip = payload.get("peer_controller_gateway_address")
if not self._validate_peer_controller_gateway_ip(gateway_ip):
pecan.abort(
httpclient.BAD_REQUEST,
_("Invalid peer peer_controller_gateway_address"),
)
# Optional request parameters
kwargs = {}
administrative_state = payload.get("administrative_state")
if administrative_state:
if not self._validate_administrative_state(administrative_state):
pecan.abort(
httpclient.BAD_REQUEST, _("Invalid peer administrative_state")
)
kwargs["administrative_state"] = administrative_state
heartbeat_interval = payload.get("heartbeat_interval")
if heartbeat_interval is not None:
if not self._validate_heartbeat_interval(heartbeat_interval):
pecan.abort(
httpclient.BAD_REQUEST, _("Invalid peer heartbeat_interval")
)
kwargs["heartbeat_interval"] = heartbeat_interval
heartbeat_failure_threshold = payload.get("heartbeat_failure_threshold")
if heartbeat_failure_threshold is not None:
if not self._validate_heartbeat_failure_threshold(
heartbeat_failure_threshold
):
pecan.abort(
httpclient.BAD_REQUEST,
_("Invalid peer heartbeat_failure_threshold"),
)
kwargs["heartbeat_failure_threshold"] = heartbeat_failure_threshold
heartbeat_failure_policy = payload.get("heartbeat_failure_policy")
if heartbeat_failure_policy:
if not self._validate_heartbeat_failure_policy(heartbeat_failure_policy):
pecan.abort(
httpclient.BAD_REQUEST, _("Invalid peer heartbeat_failure_policy")
)
kwargs["heartbeat_failure_policy"] = heartbeat_failure_policy
heartbeat_maintenance_timeout = payload.get("heartbeat_maintenance_timeout")
if heartbeat_maintenance_timeout is not None:
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
try:
peer_ref = db_api.system_peer_create(
context,
peer_uuid,
peer_name,
endpoint,
username,
password,
gateway_ip,
**kwargs
)
return db_api.system_peer_db_model_to_dict(peer_ref)
except db_exc.DBDuplicateEntry:
LOG.info("Peer create failed. Peer UUID %s already exists" % peer_uuid)
pecan.abort(
httpclient.CONFLICT, _("A system peer with this UUID 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 system peer")
)
@index.when(method="PATCH", template="json")
def patch(self, peer_ref):
"""Update a system peer.
:param peer_ref: ID or UUID of system peer to update
"""
policy.authorize(
system_peer_policy.POLICY_ROOT % "modify",
{},
restcomm.extract_credentials_for_policy(),
)
context = restcomm.extract_context_from_environ()
LOG.info("Updating system peer: %s" % context)
if peer_ref is None:
pecan.abort(httpclient.BAD_REQUEST, _("System Peer UUID or ID required"))
payload = self._get_payload(request)
if not payload:
pecan.abort(httpclient.BAD_REQUEST, _("Body required"))
peer = utils.system_peer_get_by_ref(context, peer_ref)
if peer is None:
pecan.abort(httpclient.NOT_FOUND, _("System Peer not found"))
(
peer_uuid,
peer_name,
endpoint,
username,
password,
gateway_ip,
administrative_state,
heartbeat_interval,
heartbeat_failure_threshold,
heartbeat_failure_policy,
heartbeat_maintenance_timeout,
) = (
payload.get("peer_uuid"),
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
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
if peer_uuid:
if not self._validate_uuid(peer_uuid):
pecan.abort(httpclient.BAD_REQUEST, _("Invalid peer uuid"))
if peer_name:
if not utils.validate_name(peer_name):
pecan.abort(httpclient.BAD_REQUEST, _("Invalid peer name"))
if endpoint:
if not self._validate_manager_endpoint(endpoint):
pecan.abort(httpclient.BAD_REQUEST, _("Invalid peer manager_endpoint"))
if username:
if not self._validate_manager_username(username):
pecan.abort(httpclient.BAD_REQUEST, _("Invalid peer manager_username"))
if password:
if not self._validate_manager_password(password):
pecan.abort(httpclient.BAD_REQUEST, _("Invalid peer manager_password"))
if gateway_ip:
if not self._validate_peer_controller_gateway_ip(gateway_ip):
pecan.abort(
httpclient.BAD_REQUEST,
_("Invalid peer peer_controller_gateway_address"),
)
if administrative_state:
if not self._validate_administrative_state(administrative_state):
pecan.abort(
httpclient.BAD_REQUEST, _("Invalid peer administrative_state")
)
if heartbeat_interval:
if not self._validate_heartbeat_interval(heartbeat_interval):
pecan.abort(
httpclient.BAD_REQUEST, _("Invalid peer heartbeat_interval")
)
if heartbeat_failure_threshold:
if not self._validate_heartbeat_failure_threshold(
heartbeat_failure_threshold
):
pecan.abort(
httpclient.BAD_REQUEST,
_("Invalid peer heartbeat_failure_threshold"),
)
if heartbeat_failure_policy:
if not self._validate_heartbeat_failure_policy(heartbeat_failure_policy):
pecan.abort(
httpclient.BAD_REQUEST, _("Invalid peer heartbeat_failure_policy")
)
if heartbeat_maintenance_timeout:
if not self._validate_heartbeat_maintenance_timeout(
heartbeat_maintenance_timeout
):
pecan.abort(
httpclient.BAD_REQUEST,
_("Invalid peer heartbeat_maintenance_timeout"),
)
try:
updated_peer = db_api.system_peer_update(
context,
peer.id,
peer_uuid,
peer_name,
endpoint,
username,
password,
gateway_ip,
administrative_state,
heartbeat_interval,
heartbeat_failure_threshold,
heartbeat_failure_policy,
heartbeat_maintenance_timeout,
)
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
# was updated
if gateway_ip is not None and gateway_ip != peer.peer_controller_gateway_ip:
associations = db_api.peer_group_association_get_by_system_peer_id(
context, peer.id
)
association_ids = set()
for association in associations:
# Update the sync status on both sites
association_ids.update(
self.dcmanager_rpc_client.update_association_sync_status(
context,
association.peer_group_id,
consts.ASSOCIATION_SYNC_STATUS_OUT_OF_SYNC,
)
)
# Generate an informative message to remind the operator
# that the sync command(s) should be executed.
info_message = utils.generate_sync_info_message(association_ids)
if info_message:
updated_peer_dict["info_message"] = info_message
return updated_peer_dict
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 system peer")
)
@index.when(method="delete", template="json")
def delete(self, peer_ref):
"""Delete the system peer."""
policy.authorize(
system_peer_policy.POLICY_ROOT % "delete",
{},
restcomm.extract_credentials_for_policy(),
)
context = restcomm.extract_context_from_environ()
LOG.info("Deleting system peer: %s" % context)
if peer_ref is None:
pecan.abort(httpclient.BAD_REQUEST, _("System Peer UUID or ID required"))
peer = utils.system_peer_get_by_ref(context, peer_ref)
if peer is None:
pecan.abort(httpclient.NOT_FOUND, _("System Peer not found"))
# A system peer cannot be deleted if it is used by any associations
association = db_api.peer_group_association_get_by_system_peer_id(
context, str(peer.id)
)
if len(association) > 0:
pecan.abort(
httpclient.BAD_REQUEST,
_("Cannot delete a system peer which is associated with peer group."),
)
try:
db_api.system_peer_destroy(context, peer.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 system peer")
)