Add system peer management API support

Add dcmanager system-peer management api.

Test Plan:
1. PASS - Verify that cloud manage system-peer
          through api successfully.
2. PASS - Add system peer with invalid UUID, manager_endpoint,
	  systemcontroller_gateway_address, administrative_state,
	  heartbeat_interval
3. PASS - Update system peer with invalid administrative_state,
	  heartbeat_interval
4. PASS - Get system peer with UUID, name
5. PASS - Delete system peer with UUID, name

CLI example:
dcmanager system-peer add --peer_uuid $(uuidgen) --peer_name dc-0
--manager_endpoint http://128.128.128.1:5000/v3
(The peer_uuid get from the peer site with command `system show`)

dcmanager system-peer list

dcmanager system-peer update --administrative_state enabled 1

dcmanager system-peer show 1

dcmanager system-peer delete 1

Story: 2010852
Task: 48482

Change-Id: I349cd24bccc732eb8ed56df9346185cfce7b2570
Signed-off-by: Zhang Rong(Jon) <rong.zhang@windriver.com>
This commit is contained in:
Zhang Rong(Jon) 2023-08-01 20:29:12 +08:00 committed by Jon Zhang
parent ee49b840a9
commit 9d1c9ccd23
19 changed files with 1638 additions and 3 deletions

View File

@ -2262,4 +2262,284 @@ Response Example
----------------
.. literalinclude:: samples/phased-subcloud-deploy/phased-subcloud-deploy-patch-resume-response.json
:language: json
:language: json
------------
System Peers
------------
System Peers are logical entities which are managed by a central System Controller.
Each System Peer maintains the information which is used for health check
and data synchronization in the protection group in Geo-Redundancy deployment.
**********************
Lists all system peers
**********************
.. rest_method:: GET /v1.0/system-peers
This operation does not accept a request body.
**Normal response codes**
200
**Error response codes**
badRequest (400), unauthorized (401), forbidden (403),
itemNotFound (404), badMethod (405), HTTPUnprocessableEntity (422),
internalServerError (500), serviceUnavailable (503)
Response
--------
.. rest_parameters:: parameters.yaml
- system_peers: system_peers
- id: system_peer_id
- peer-uuid: peer_uuid
- peer-name: peer_name
- manager-endpoint: manager_endpoint
- manager-username: manager_username
- peer-controller-gateway-address: peer_controller_gateway_address
- 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
- created-at: created_at
- updated-at: updated_at
Response Example
----------------
.. literalinclude:: samples/system-peers/system-peers-get-response.json
:language: json
*********************
Creates a system peer
*********************
.. rest_method:: POST /v1.0/system-peers
Accepts Content-Type multipart/form-data.
**Normal response codes**
200
**Error response codes**
badRequest (400), unauthorized (401), forbidden (403), badMethod (405),
HTTPUnprocessableEntity (422), internalServerError (500),
serviceUnavailable (503)
**Request parameters**
.. rest_parameters:: parameters.yaml
- peer_uuid: peer_uuid
- peer_name: peer_name
- manager_endpoint: manager_endpoint
- manager_username: manager_username
- manager_password: manager_password
- peer_controller_gateway_address: peer_controller_gateway_address
- 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
Request Example
----------------
.. literalinclude:: samples/system-peers/system-peers-post-request.json
:language: json
**Response parameters**
.. rest_parameters:: parameters.yaml
- id: system_peer_id
- peer-uuid: peer_uuid
- peer-name: peer_name
- manager-endpoint: manager_endpoint
- manager-username: manager_username
- peer-controller-gateway-address: peer_controller_gateway_address
- 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
- created-at: created_at
- updated-at: updated_at
Response Example
----------------
.. literalinclude:: samples/system-peers/system-peers-post-response.json
:language: json
**********************************************
Shows information about a specific system peer
**********************************************
.. rest_method:: GET /v1.0/system-peers/{system-peer}
**Normal response codes**
200
**Error response codes**
badRequest (400), unauthorized (401), forbidden (403),
itemNotFound (404), badMethod (405), HTTPUnprocessableEntity (422),
internalServerError (500), serviceUnavailable (503)
**Request parameters**
.. rest_parameters:: parameters.yaml
- system-peer: system_peer_uri
This operation does not accept a request body.
**Response parameters**
.. rest_parameters:: parameters.yaml
- id: system_peer_id
- peer-uuid: peer_uuid
- peer-name: peer_name
- manager-endpoint: manager_endpoint
- manager-username: manager_username
- peer-controller-gateway-address: peer_controller_gateway_address
- 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
- created-at: created_at
- updated-at: updated_at
Response Example
----------------
.. literalinclude:: samples/system-peers/system-peer-get-response.json
:language: json
*******************************
Modifies a specific system peer
*******************************
.. rest_method:: PATCH /v1.0/system-peers/{system-peer}
The attributes of a subcloud group which are modifiable:
- peer-uuid
- peer-name
- manager-endpoint
- manager-username
- manager-password
- peer-controller-gateway-address
- administrative-state
- heartbeat-interval
- heartbeat-failure-threshold
- heartbeat-failure-policy
- heartbeat-maintenance-timeout
**Normal response codes**
200
**Error response codes**
badRequest (400), unauthorized (401), forbidden (403), badMethod (405),
HTTPUnprocessableEntity (422), internalServerError (500),
serviceUnavailable (503)
**Request parameters**
.. rest_parameters:: parameters.yaml
- system-peer: system_peer_uri
- peer_uuid: peer_uuid
- peer_name: peer_name
- manager_endpoint: manager_endpoint
- manager_username: manager_username
- manager_password: manager_password
- peer_controller_gateway_address: peer_controller_gateway_address
- 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
Request Example
----------------
.. literalinclude:: samples/system-peers/system-peer-patch-request.json
:language: json
**Response parameters**
.. rest_parameters:: parameters.yaml
- id: system_peer_id
- peer-uuid: peer_uuid
- peer-name: peer_name
- manager-endpoint: manager_endpoint
- manager-username: manager_username
- peer-controller-gateway-address: peer_controller_gateway_address
- 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
- created-at: created_at
- updated-at: updated_at
Response Example
----------------
.. literalinclude:: samples/system-peers/system-peer-patch-response.json
:language: json
******************************
Deletes a specific system peer
******************************
.. rest_method:: DELETE /v1.0/system-peers/{system-peer}
**Normal response codes**
200
**Error response codes**
badRequest (400), unauthorized (401), forbidden (403),
itemNotFound (404), badMethod (405), HTTPUnprocessableEntity (422),
internalServerError (500), serviceUnavailable (503)
**Request parameters**
.. rest_parameters:: parameters.yaml
- system-peer: system_peer_uri
This operation does not accept a request body.

View File

@ -39,7 +39,19 @@ sw_update_strategy_type:
in: path
required: false
type: string
system_peer_uri:
description: |
The system peer reference, name or id or UUID.
in: path
required: true
type: string
# variables in body
administrative_state:
description: |
The administrative state of the system peer site. (enabled, disabled)
in: body
required: true
type: string
alarm_restriction_type:
description: |
Whether to allow update if subcloud alarms are present or not.
@ -245,6 +257,30 @@ group_id:
in: body
required: true
type: integer
heartbeat_failure_policy:
description: |
The failure policy of the peer site heartbeats. (alarm, rehome, delegate)
in: body
required: true
type: string
heartbeat_failure_threshold:
description: |
The failure threshold of the peer site heartbeats.
in: body
required: true
type: integer
heartbeat_interval:
description: |
The interval of the message between the peer site heartbeats. (in seconds)
in: body
required: true
type: integer
heartbeat_maintenance_timeout:
description: |
The maintenance timeout of the peer site heartbeats. (in seconds)
in: body
required: true
type: integer
install_values:
description: |
The content of a file containing install variables such as subcloud
@ -288,6 +324,24 @@ management_subnet:
in: body
required: true
type: string
manager_endpoint:
description: |
The endpoint of the system peer site manager.
in: body
required: true
type: string
manager_password:
description: |
The password of the system peer site manager.
in: body
required: true
type: string
manager_username:
description: |
The username of the system peer site manager.
in: body
required: true
type: string
max_parallel_subclouds:
description: |
The maximum number of subclouds to update in parallel.
@ -331,6 +385,24 @@ patch_strategy_upload_only:
in: body
required: false
type: boolean
peer_controller_gateway_address:
description: |
The gateway IP address of the system peer site system controller.
in: body
required: true
type: string
peer_name:
description: |
The name of a peer as a string.
in: body
required: true
type: string
peer_uuid:
description: |
The UUID of a peer as a string.
in: body
required: true
type: string
prestage_software_version:
description: |
The prestage software version for the subcloud.
@ -678,6 +750,18 @@ system_mode:
in: body
required: true
type: string
system_peer_id:
description: |
The ID of a system peer as an integer.
in: body
required: true
type: integer
system_peers:
description: |
The list of ``system-peer`` objects.
in: body
required: true
type: array
systemcontroller_gateway_ip:
description: |
The gateway IP address of the system controller of the subcloud.

View File

@ -0,0 +1,15 @@
{
"id": 1,
"peer-uuid": "b00d0863-c54e-4340-af4d-3e2093764276",
"peer-name": "PeerDistributedCloud1",
"manager-endpoint": "http://128.128.128.1:5000/v3",
"manager-username": "admin",
"peer-controller-gateway-address": "192.168.204.1",
"administrative-state": "enabled",
"heartbeat-interval": 60,
"heartbeat-failure-threshold": 3,
"heartbeat-failure-policy": "alarm",
"heartbeat-maintenance-timeout": 600,
"created-at": "2023-08-14 05:47:35.587528",
"updated-at": "2023-08-14 05:47:35.587528"
}

View File

@ -0,0 +1,13 @@
{
"peer_uuid": "b00d0863-c54e-4340-af4d-3e2093764276",
"peer_name": "PeerDistributedCloud1",
"manager_endpoint": "http://128.128.128.1:5000/v3",
"manager_username": "admin",
"manager-password": "V2luZDEyMyQ=",
"peer_controller_gateway-address": "192.168.204.1",
"administrative_state": "enabled",
"heartbeat_interval": 60,
"heartbeat_failure_threshold": 3,
"heartbeat_failure_policy": "alarm",
"heartbeat_maintenance_timeout": 600
}

View File

@ -0,0 +1,15 @@
{
"id": 1,
"peer-uuid": "b00d0863-c54e-4340-af4d-3e2093764276",
"peer-name": "PeerDistributedCloud1",
"manager-endpoint": "http://128.128.128.1:5000/v3",
"manager-username": "admin",
"peer-controller-gateway-address": "192.168.204.1",
"administrative-state": "enabled",
"heartbeat-interval": 60,
"heartbeat-failure-threshold": 3,
"heartbeat-failure-policy": "alarm",
"heartbeat-maintenance-timeout": 600,
"created-at": "2023-08-14 05:47:35.587528",
"updated-at": "2023-08-14 06:47:35.587528"
}

View File

@ -0,0 +1,19 @@
{
"system_peers": [
{
"id": 1,
"peer-uuid": "b00d0863-c54e-4340-af4d-3e2093764276",
"peer-name": "PeerDistributedCloud1",
"manager-endpoint": "http://128.128.128.1:5000/v3",
"manager-username": "admin",
"peer-controller-gateway-address": "192.168.204.1",
"administrative-state": "enabled",
"heartbeat-interval": 60,
"heartbeat-failure-threshold": 3,
"heartbeat-failure-policy": "alarm",
"heartbeat-maintenance-timeout": 600,
"created-at": "2023-08-14 05:47:35.587528",
"updated-at": "2023-08-14 05:47:35.587528"
}
]
}

View File

@ -0,0 +1,13 @@
{
"peer_uuid": "b00d0863-c54e-4340-af4d-3e2093764276",
"peer_name": "PeerDistributedCloud1",
"manager_endpoint": "http://128.128.128.1:5000/v3",
"manager_username": "admin",
"manager-password": "V2luZDEyMyQ=",
"peer_controller_gateway-address": "192.168.204.1",
"administrative_state": "enabled",
"heartbeat_interval": 60,
"heartbeat_failure_threshold": 3,
"heartbeat_failure_policy": "alarm",
"heartbeat_maintenance_timeout": 600
}

View File

@ -0,0 +1,15 @@
{
"id": 1,
"peer-uuid": "b00d0863-c54e-4340-af4d-3e2093764276",
"peer-name": "PeerDistributedCloud1",
"manager-endpoint": "http://128.128.128.1:5000/v3",
"manager-username": "admin",
"peer-controller-gateway-address": "192.168.204.1",
"administrative-state": "enabled",
"heartbeat-interval": 60,
"heartbeat-failure-threshold": 3,
"heartbeat-failure-policy": "alarm",
"heartbeat-maintenance-timeout": 600,
"created-at": "2023-08-14 05:47:35.587528",
"updated-at": null
}

View File

@ -25,6 +25,7 @@ from dcmanager.api.controllers.v1 import subcloud_group
from dcmanager.api.controllers.v1 import subclouds
from dcmanager.api.controllers.v1 import sw_update_options
from dcmanager.api.controllers.v1 import sw_update_strategy
from dcmanager.api.controllers.v1 import system_peers
class Controller(object):
@ -54,6 +55,8 @@ class Controller(object):
SubcloudBackupController
sub_controllers["phased-subcloud-deploy"] = phased_subcloud_deploy.\
PhasedSubcloudDeployController
sub_controllers["system-peers"] = system_peers.\
SystemPeersController
for name, ctrl in sub_controllers.items():
setattr(self, name, ctrl)

View File

@ -0,0 +1,485 @@
# Copyright (c) 2023 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.i18n import _
from dcmanager.common import utils
from dcmanager.db import api as db_api
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
# validation constants for System Peer
MAX_SYSTEM_PEER_NAME_LEN = 255
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__()
@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_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):
"""Get details about system peer.
:param peer_ref: ID or UUID or Name of system peer
"""
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'))
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_name(self, name):
if not name or name.isdigit() or len(name) >= MAX_SYSTEM_PEER_NAME_LEN:
LOG.debug("Invalid name: %s" % name)
return False
return True
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 self._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 self._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)
return db_api.system_peer_db_model_to_dict(updated_peer)
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'))
# TODO(jon): Add this back in when we have peer group associations
# a system peer may not be deleted if it is use 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,
# _('System peer 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'))

View File

@ -15,6 +15,7 @@ from dcmanager.api.policies import subcloud_group
from dcmanager.api.policies import subclouds
from dcmanager.api.policies import sw_update_options
from dcmanager.api.policies import sw_update_strategy
from dcmanager.api.policies import system_peers
def list_rules():
@ -27,5 +28,6 @@ def list_rules():
sw_update_options.list_rules(),
subcloud_group.list_rules(),
subcloud_backup.list_rules(),
phased_subcloud_deploy.list_rules()
phased_subcloud_deploy.list_rules(),
system_peers.list_rules()
)

View File

@ -0,0 +1,65 @@
# Copyright (c) 2023 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
from dcmanager.api.policies import base
from oslo_policy import policy
POLICY_ROOT = 'dc_api:system_peers:%s'
system_peers_rules = [
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'create',
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Create system peer.",
operations=[
{
'method': 'POST',
'path': '/v1.0/system-peers'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'delete',
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Delete system peer.",
operations=[
{
'method': 'DELETE',
'path': '/v1.0/system-peers/{system_peer}'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'get',
check_str='rule:' + base.READER_IN_SYSTEM_PROJECTS,
description="Get system peers.",
operations=[
{
'method': 'GET',
'path': '/v1.0/system-peers'
},
{
'method': 'GET',
'path': '/v1.0/system-peers/{system_peer}'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'modify',
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Modify system peer.",
operations=[
{
'method': 'PATCH',
'path': '/v1.0/system-peers/{system_peer}'
}
]
)
]
def list_rules():
return system_peers_rules

View File

@ -137,6 +137,18 @@ class SubcloudPatchOptsNotFound(NotFound):
"defaults will be used.")
class SystemPeerNotFound(NotFound):
message = _("System Peer with id %(peer_id)s doesn't exist.")
class SystemPeerNameNotFound(NotFound):
message = _("System Peer with peer_name %(name)s doesn't exist.")
class SystemPeerUUIDNotFound(NotFound):
message = _("System Peer with peer_uuid %(uuid)s doesn't exist.")
class SubcloudGroupNotFound(NotFound):
message = _("Subcloud Group with id %(group_id)s doesn't exist.")

View File

@ -28,6 +28,7 @@ import six.moves
import string
import subprocess
import tsconfig.tsconfig as tsc
import uuid
import xml.etree.ElementTree as ElementTree
import yaml
@ -495,6 +496,26 @@ def get_loads_for_prestage(loads):
return [load.software_version for load in loads if load.state in valid_states]
def system_peer_get_by_ref(context, peer_ref):
"""Handle getting a system peer by either UUID, or ID, or Name
:param context: The request context
:param peer_ref: Reference to the system peer, either an UUID or an ID or
a Name
"""
try:
if peer_ref.isdigit():
return db_api.system_peer_get(context, peer_ref)
try:
uuid.UUID(peer_ref)
return db_api.system_peer_get_by_uuid(context, peer_ref)
except ValueError:
return db_api.system_peer_get_by_name(context, peer_ref)
except (exceptions.SystemPeerNotFound, exceptions.SystemPeerUUIDNotFound,
exceptions.SystemPeerNameNotFound):
return None
def subcloud_get_by_ref(context, subcloud_ref):
"""Handle getting a subcloud by either name, or ID

View File

@ -351,6 +351,96 @@ def subcloud_group_destroy(context, group_id):
return IMPL.subcloud_group_destroy(context, group_id)
###################
# system_peer
def system_peer_db_model_to_dict(system_peer):
"""Convert system_peer db model to dictionary."""
result = {"id": system_peer.id,
"peer-uuid": system_peer.peer_uuid,
"peer-name": system_peer.peer_name,
"manager-endpoint": system_peer.manager_endpoint,
"manager-username": system_peer.manager_username,
"peer-controller-gateway-address": system_peer.
peer_controller_gateway_ip,
"administrative-state": system_peer.administrative_state,
"heartbeat-interval": system_peer.heartbeat_interval,
"heartbeat-failure-threshold": system_peer.
heartbeat_failure_threshold,
"heartbeat-failure-policy": system_peer.heartbeat_failure_policy,
"heartbeat-maintenance-timeout": system_peer.
heartbeat_maintenance_timeout,
"created-at": system_peer.created_at,
"updated-at": system_peer.updated_at}
return result
def system_peer_create(context,
peer_uuid, peer_name,
endpoint, username, password,
gateway_ip,
administrative_state,
heartbeat_interval,
heartbeat_failure_threshold,
heartbeat_failure_policy,
heartbeat_maintenance_timeout):
"""Create a system_peer."""
return IMPL.system_peer_create(context,
peer_uuid, peer_name,
endpoint, username, password,
gateway_ip,
administrative_state,
heartbeat_interval,
heartbeat_failure_threshold,
heartbeat_failure_policy,
heartbeat_maintenance_timeout)
def system_peer_get(context, peer_id):
"""Retrieve a system_peer or raise if it does not exist."""
return IMPL.system_peer_get(context, peer_id)
def system_peer_get_by_uuid(context, uuid):
"""Retrieve a system_peer by uuid or raise if it does not exist."""
return IMPL.system_peer_get_by_uuid(context, uuid)
def system_peer_get_by_name(context, uuid):
"""Retrieve a system_peer by name or raise if it does not exist."""
return IMPL.system_peer_get_by_name(context, uuid)
def system_peer_get_all(context):
"""Retrieve all system peers."""
return IMPL.system_peer_get_all(context)
def 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):
"""Update the system peer or raise if it does not exist."""
return IMPL.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)
def system_peer_destroy(context, peer_id):
"""Destroy the system peer or raise if it does not exist."""
return IMPL.system_peer_destroy(context, peer_id)
###################
def sw_update_strategy_db_model_to_dict(sw_update_strategy):

View File

@ -764,6 +764,145 @@ def sw_update_opts_default_destroy(context):
session.delete(sw_update_opts_default_ref)
##########################
# system peer
##########################
@require_context
def system_peer_get(context, peer_id):
try:
result = model_query(context, models.SystemPeer). \
filter_by(deleted=0). \
filter_by(id=peer_id). \
one()
except NoResultFound:
raise exception.SystemPeerNotFound(peer_id=peer_id)
except MultipleResultsFound:
raise exception.InvalidParameterValue(
err="Multiple entries found for system peer %s" % peer_id)
return result
@require_context
def system_peer_get_by_name(context, name):
try:
result = model_query(context, models.SystemPeer). \
filter_by(deleted=0). \
filter_by(peer_name=name). \
one()
except NoResultFound:
raise exception.SystemPeerNameNotFound(name=name)
except MultipleResultsFound:
# This exception should never happen due to the UNIQUE setting for name
raise exception.InvalidParameterValue(
err="Multiple entries found for system peer %s" % name)
return result
@require_context
def system_peer_get_by_uuid(context, uuid):
try:
result = model_query(context, models.SystemPeer). \
filter_by(deleted=0). \
filter_by(peer_uuid=uuid). \
one()
except NoResultFound:
raise exception.SystemPeerUUIDNotFound(uuid=uuid)
except MultipleResultsFound:
# This exception should never happen due to the UNIQUE setting for uuid
raise exception.InvalidParameterValue(
err="Multiple entries found for system peer %s" % uuid)
return result
@require_context
def system_peer_get_all(context):
result = model_query(context, models.SystemPeer). \
filter_by(deleted=0). \
order_by(models.SystemPeer.id). \
all()
return result
@require_admin_context
def system_peer_create(context,
peer_uuid, peer_name,
endpoint, username, password,
gateway_ip,
administrative_state="enabled",
heartbeat_interval=60,
heartbeat_failure_threshold=3,
heartbeat_failure_policy="alarm",
heartbeat_maintenance_timeout=600):
with write_session() as session:
system_peer_ref = models.SystemPeer()
system_peer_ref.peer_uuid = peer_uuid
system_peer_ref.peer_name = peer_name
system_peer_ref.manager_endpoint = endpoint
system_peer_ref.manager_username = username
system_peer_ref.manager_password = password
system_peer_ref.peer_controller_gateway_ip = gateway_ip
system_peer_ref.administrative_state = administrative_state
system_peer_ref.heartbeat_interval = heartbeat_interval
system_peer_ref.heartbeat_failure_threshold = \
heartbeat_failure_threshold
system_peer_ref.heartbeat_failure_policy = heartbeat_failure_policy
system_peer_ref.heartbeat_maintenance_timeout = \
heartbeat_maintenance_timeout
session.add(system_peer_ref)
return system_peer_ref
@require_admin_context
def system_peer_update(context, peer_id,
peer_uuid=None, peer_name=None,
endpoint=None, username=None, password=None,
gateway_ip=None,
administrative_state=None,
heartbeat_interval=None,
heartbeat_failure_threshold=None,
heartbeat_failure_policy=None,
heartbeat_maintenance_timeout=None):
with write_session() as session:
system_peer_ref = system_peer_get(context, peer_id)
if peer_uuid is not None:
system_peer_ref.peer_uuid = peer_uuid
if peer_name is not None:
system_peer_ref.peer_name = peer_name
if endpoint is not None:
system_peer_ref.manager_endpoint = endpoint
if username is not None:
system_peer_ref.manager_username = username
if password is not None:
system_peer_ref.manager_password = password
if gateway_ip is not None:
system_peer_ref.peer_controller_gateway_ip = gateway_ip
if administrative_state is not None:
system_peer_ref.administrative_state = administrative_state
if heartbeat_interval is not None:
system_peer_ref.heartbeat_interval = heartbeat_interval
if heartbeat_failure_threshold is not None:
system_peer_ref.heartbeat_failure_threshold = \
heartbeat_failure_threshold
if heartbeat_failure_policy is not None:
system_peer_ref.heartbeat_failure_policy = heartbeat_failure_policy
if heartbeat_maintenance_timeout is not None:
system_peer_ref.heartbeat_maintenance_timeout = \
heartbeat_maintenance_timeout
system_peer_ref.save(session)
return system_peer_ref
@require_admin_context
def system_peer_destroy(context, peer_id):
with write_session() as session:
system_peer_ref = system_peer_get(context, peer_id)
session.delete(system_peer_ref)
##########################
# subcloud group
##########################
@ -897,7 +1036,6 @@ def initialize_subcloud_group_default(engine):
pass
except Exception as ex:
LOG.error("Exception occurred setting up default subcloud group", ex)
##########################

View File

@ -16,6 +16,36 @@ def upgrade(migrate_engine):
# Add the 'rehome_data' column to the subclouds table.
subclouds.create_column(sqlalchemy.Column('rehome_data', sqlalchemy.Text))
# Declare the new system_peer table
system_peer = sqlalchemy.Table(
'system_peer', meta,
sqlalchemy.Column('id', sqlalchemy.Integer,
primary_key=True,
autoincrement=True,
nullable=False),
sqlalchemy.Column('peer_uuid', sqlalchemy.String(36), unique=True),
sqlalchemy.Column('peer_name', sqlalchemy.String(255), unique=True),
sqlalchemy.Column('manager_endpoint', sqlalchemy.String(255)),
sqlalchemy.Column('manager_username', sqlalchemy.String(255)),
sqlalchemy.Column('manager_password', sqlalchemy.String(255)),
sqlalchemy.Column('peer_controller_gateway_ip', sqlalchemy.String(255)),
sqlalchemy.Column('administrative_state', sqlalchemy.String(255)),
sqlalchemy.Column('heartbeat_interval', sqlalchemy.Integer),
sqlalchemy.Column('heartbeat_failure_threshold', sqlalchemy.Integer),
sqlalchemy.Column('heartbeat_failure_policy', sqlalchemy.String(255)),
sqlalchemy.Column('heartbeat_maintenance_timeout', sqlalchemy.Integer),
sqlalchemy.Column('heartbeat_status', sqlalchemy.String(255)),
sqlalchemy.Column('reserved_1', sqlalchemy.Text),
sqlalchemy.Column('reserved_2', sqlalchemy.Text),
sqlalchemy.Column('created_at', sqlalchemy.DateTime),
sqlalchemy.Column('updated_at', sqlalchemy.DateTime),
sqlalchemy.Column('deleted_at', sqlalchemy.DateTime),
sqlalchemy.Column('deleted', sqlalchemy.Integer, default=0),
mysql_engine=ENGINE,
mysql_charset=CHARSET
)
system_peer.create()
def downgrade(migrate_engine):
raise NotImplementedError('Database downgrade is unsupported.')

View File

@ -100,6 +100,25 @@ class DCManagerBase(models.ModelBase,
session.commit()
class SystemPeer(BASE, DCManagerBase):
"""Represents a system peer"""
__tablename__ = 'system_peer'
id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
peer_uuid = Column(String(36), unique=True)
peer_name = Column(String(255), unique=True)
manager_endpoint = Column(String(255))
manager_username = Column(String(255))
manager_password = Column(String(255))
peer_controller_gateway_ip = Column(String(255))
administrative_state = Column(String(255))
heartbeat_interval = Column(Integer)
heartbeat_failure_threshold = Column(Integer)
heartbeat_failure_policy = Column(String(255))
heartbeat_maintenance_timeout = Column(Integer)
class SubcloudGroup(BASE, DCManagerBase):
"""Represents a subcloud group"""

View File

@ -0,0 +1,316 @@
# Copyright (c) 2023 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import mock
from six.moves import http_client
import uuid
from dcmanager.db.sqlalchemy import api as db_api
from dcmanager.rpc import client as rpc_client
from dcmanager.tests.unit.api import test_root_controller as testroot
from dcmanager.tests.unit.api.v1.controllers.mixins import APIMixin
from dcmanager.tests.unit.api.v1.controllers.mixins import DeleteMixin
from dcmanager.tests.unit.api.v1.controllers.mixins import GetMixin
from dcmanager.tests.unit.api.v1.controllers.mixins import PostJSONMixin
from dcmanager.tests.unit.api.v1.controllers.mixins import UpdateMixin
from dcmanager.tests import utils
SAMPLE_SYSTEM_PEER_UUID = str(uuid.uuid4())
SAMPLE_SYSTEM_PEER_NAME = 'SystemPeer1'
SAMPLE_MANAGER_ENDPOINT = 'http://127.0.0.1:5000'
SAMPLE_MANAGER_USERNAME = 'admin'
SAMPLE_MANAGER_PASSWORD = 'password'
SAMPLE_ADMINISTRATIVE_STATE = 'enabled'
SAMPLE_HEARTBEAT_INTERVAL = 10
SAMPLE_HEARTBEAT_FAILURE_THRESHOLD = 3
SAMPLE_HEARTBEAT_FAILURES_POLICY = 'alarm'
SAMPLE_HEARTBEAT_MAINTENANCE_TIMEOUT = 600
SAMPLE_PEER_CONTROLLER_GATEWAY_IP = '128.128.128.1'
class SystemPeerAPIMixin(APIMixin):
API_PREFIX = '/v1.0/system-peers'
RESULT_KEY = 'system_peers'
EXPECTED_FIELDS = ['id',
'peer-uuid',
'peer-name',
'manager-endpoint',
'manager-username',
'peer-controller-gateway-address',
'administrative-state',
'heartbeat-interval',
'heartbeat-failure-threshold',
'heartbeat-failure-policy',
'heartbeat-maintenance-timeout',
'created-at',
'updated-at']
def setUp(self):
super(SystemPeerAPIMixin, self).setUp()
self.fake_rpc_client.some_method = mock.MagicMock()
def _get_test_system_peer_dict(self, data_type, **kw):
# id should not be part of the structure
system_peer = {
'peer_uuid': kw.get('peer_uuid', SAMPLE_SYSTEM_PEER_UUID),
'peer_name': kw.get('peer_name', SAMPLE_SYSTEM_PEER_NAME),
'administrative_state': kw.get('administrative_state',
SAMPLE_ADMINISTRATIVE_STATE),
'heartbeat_interval': kw.get('heartbeat_interval',
SAMPLE_HEARTBEAT_INTERVAL),
'heartbeat_failure_threshold': kw.get(
'heartbeat_failure_threshold', SAMPLE_HEARTBEAT_FAILURE_THRESHOLD),
'heartbeat_failure_policy': kw.get(
'heartbeat_failure_policy', SAMPLE_HEARTBEAT_FAILURES_POLICY),
'heartbeat_maintenance_timeout': kw.get(
'heartbeat_maintenance_timeout',
SAMPLE_HEARTBEAT_MAINTENANCE_TIMEOUT)
}
if data_type == 'db':
system_peer['endpoint'] = kw.get('manager_endpoint',
SAMPLE_MANAGER_ENDPOINT)
system_peer['username'] = kw.get('manager_username',
SAMPLE_MANAGER_USERNAME)
system_peer['password'] = kw.get('manager_password',
SAMPLE_MANAGER_PASSWORD)
system_peer['gateway_ip'] = kw.get(
'peer_controller_gateway_ip', SAMPLE_PEER_CONTROLLER_GATEWAY_IP)
else:
system_peer['manager_endpoint'] = kw.get('manager_endpoint',
SAMPLE_MANAGER_ENDPOINT)
system_peer['manager_username'] = kw.get('manager_username',
SAMPLE_MANAGER_USERNAME)
system_peer['manager_password'] = kw.get('manager_password',
SAMPLE_MANAGER_PASSWORD)
system_peer['peer_controller_gateway_address'] = kw.get(
'peer_controller_gateway_ip', SAMPLE_PEER_CONTROLLER_GATEWAY_IP)
return system_peer
def _post_get_test_system_peer(self, **kw):
post_body = self._get_test_system_peer_dict('dict', **kw)
return post_body
# The following methods are required for subclasses of APIMixin
def get_api_prefix(self):
return self.API_PREFIX
def get_result_key(self):
return self.RESULT_KEY
def get_expected_api_fields(self):
return self.EXPECTED_FIELDS
def get_omitted_api_fields(self):
return []
def _create_db_object(self, context, **kw):
creation_fields = self._get_test_system_peer_dict('db', **kw)
return db_api.system_peer_create(context, **creation_fields)
def get_post_object(self):
return self._post_get_test_system_peer()
def get_update_object(self):
update_object = {
'peer_controller_gateway_address': '192.168.205.1'
}
return update_object
# Combine System Peer API with mixins to test post, get, update and delete
class TestSystemPeerPost(testroot.DCManagerApiTest,
SystemPeerAPIMixin, PostJSONMixin):
def setUp(self):
super(TestSystemPeerPost, self).setUp()
def verify_post_failure(self, response):
# Failures will return text rather than JSON
self.assertEqual(response.content_type, 'text/plain')
self.assertEqual(response.status_code, http_client.BAD_REQUEST)
@mock.patch.object(rpc_client, 'ManagerClient')
def test_create_with_numerical_uuid_fails(self, mock_client):
# A numerical uuid is not permitted. otherwise the 'get' operations
# which support getting by either name or ID could become confused
# if a name for one peer was the same as an ID for another.
ndict = self.get_post_object()
ndict['peer_uuid'] = '123'
response = self.app.post_json(self.get_api_prefix(),
ndict,
headers=self.get_api_headers(),
expect_errors=True)
self.verify_post_failure(response)
@mock.patch.object(rpc_client, 'ManagerClient')
def test_create_with_blank_uuid_fails(self, mock_client):
# An empty name is not permitted
ndict = self.get_post_object()
ndict['peer_uuid'] = ''
response = self.app.post_json(self.get_api_prefix(),
ndict,
headers=self.get_api_headers(),
expect_errors=True)
self.verify_post_failure(response)
@mock.patch.object(rpc_client, 'ManagerClient')
def test_create_with_empty_manager_endpoint_fails(self, mock_client):
# An empty description is considered invalid
ndict = self.get_post_object()
ndict['manager_endpoint'] = ''
response = self.app.post_json(self.get_api_prefix(),
ndict,
headers=self.get_api_headers(),
expect_errors=True)
self.verify_post_failure(response)
@mock.patch.object(rpc_client, 'ManagerClient')
def test_create_with_wrong_manager_endpoint_fails(self, mock_client):
# An empty description is considered invalid
ndict = self.get_post_object()
ndict['manager_endpoint'] = 'ftp://somepath'
response = self.app.post_json(self.get_api_prefix(),
ndict,
headers=self.get_api_headers(),
expect_errors=True)
self.verify_post_failure(response)
@mock.patch.object(rpc_client, 'ManagerClient')
def test_create_with_wrong_peergw_ip_fails(self, mock_client):
# An empty description is considered invalid
ndict = self.get_post_object()
ndict['peer_controller_gateway_address'] = '123'
response = self.app.post_json(self.get_api_prefix(),
ndict,
headers=self.get_api_headers(),
expect_errors=True)
self.verify_post_failure(response)
@mock.patch.object(rpc_client, 'ManagerClient')
def test_create_with_bad_administrative_state(self, mock_client):
# update_apply_type must be either 'enabled' or 'disabled'
ndict = self.get_post_object()
ndict['administrative_state'] = 'something_invalid'
response = self.app.post_json(self.get_api_prefix(),
ndict,
headers=self.get_api_headers(),
expect_errors=True)
self.verify_post_failure(response)
@mock.patch.object(rpc_client, 'ManagerClient')
def test_create_with_bad_heartbeat_interval(self, mock_client):
# heartbeat_interval must be an integer between 1 and 600
ndict = self.get_post_object()
# All the entries in bad_values should be considered invalid
bad_values = [0, 601, -1, 'abc']
for bad_value in bad_values:
ndict['heartbeat_interval'] = bad_value
response = self.app.post_json(self.get_api_prefix(),
ndict,
headers=self.get_api_headers(),
expect_errors=True)
self.verify_post_failure(response)
class TestSystemPeerGet(testroot.DCManagerApiTest,
SystemPeerAPIMixin, GetMixin):
def setUp(self):
super(TestSystemPeerGet, self).setUp()
@mock.patch.object(rpc_client, 'ManagerClient')
def test_get_single_by_uuid(self, mock_client):
# create a system peer
context = utils.dummy_context()
peer_uuid = str(uuid.uuid4())
self._create_db_object(context, peer_uuid=peer_uuid)
# Test that a GET operation for a valid ID works
response = self.app.get(self.get_single_url(peer_uuid),
headers=self.get_api_headers())
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.status_code, http_client.OK)
self.validate_entry(response.json)
@mock.patch.object(rpc_client, 'ManagerClient')
def test_get_single_by_name(self, mock_client):
# create a system peer
context = utils.dummy_context()
peer_name = 'TestPeer'
self._create_db_object(context, peer_name=peer_name)
# Test that a GET operation for a valid ID works
response = self.app.get(self.get_single_url(peer_name),
headers=self.get_api_headers())
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.status_code, http_client.OK)
self.validate_entry(response.json)
class TestSystemPeerUpdate(testroot.DCManagerApiTest,
SystemPeerAPIMixin, UpdateMixin):
def setUp(self):
super(TestSystemPeerUpdate, self).setUp()
def validate_updated_fields(self, sub_dict, full_obj):
for key, value in sub_dict.items():
key = key.replace('_', '-')
self.assertEqual(value, full_obj.get(key))
@mock.patch.object(rpc_client, 'ManagerClient')
def test_update_invalid_administrative_state(self, mock_client):
context = utils.dummy_context()
single_obj = self._create_db_object(context)
update_data = {
'administrative_state': 'something_bad'
}
response = self.app.patch_json(self.get_single_url(single_obj.id),
headers=self.get_api_headers(),
params=update_data,
expect_errors=True)
# Failures will return text rather than json
self.assertEqual(response.content_type, 'text/plain')
self.assertEqual(response.status_code, http_client.BAD_REQUEST)
@mock.patch.object(rpc_client, 'ManagerClient')
def test_update_invalid_heartbeat_interval(self, mock_client):
context = utils.dummy_context()
single_obj = self._create_db_object(context)
update_data = {
'heartbeat_interval': -1
}
response = self.app.patch_json(self.get_single_url(single_obj.id),
headers=self.get_api_headers(),
params=update_data,
expect_errors=True)
# Failures will return text rather than json
self.assertEqual(response.content_type, 'text/plain')
self.assertEqual(response.status_code, http_client.BAD_REQUEST)
class TestSystemPeerDelete(testroot.DCManagerApiTest,
SystemPeerAPIMixin, DeleteMixin):
def setUp(self):
super(TestSystemPeerDelete, self).setUp()
@mock.patch.object(rpc_client, 'ManagerClient')
def test_delete_by_uuid(self, mock_client):
context = utils.dummy_context()
peer_uuid = str(uuid.uuid4())
self._create_db_object(context, peer_uuid=peer_uuid)
response = self.app.delete_json(self.get_single_url(peer_uuid),
headers=self.get_api_headers())
self.assertEqual(response.status_int, 200)
@mock.patch.object(rpc_client, 'ManagerClient')
def test_delete_by_name(self, mock_client):
context = utils.dummy_context()
peer_name = 'TestPeer'
self._create_db_object(context, peer_name=peer_name)
response = self.app.delete_json(self.get_single_url(peer_name),
headers=self.get_api_headers())
self.assertEqual(response.status_int, 200)