Add Subcloud Peer group management
Group of the current managed subclouds which are supposed to be duplicated in a peer site as secondary subclouds. This commit add subcloud-peer-group APIs of create/delete/update/show/list/ list-subclouds of a subcloud-peer-group Update setting peer-group for subcloud, Using DB of subclouds' 'peer_group_id' Column. Update subcloud update API, add peer_group parameter Usage: Add a subcloud to peer-group: dcmanager subcloud update SUBCLOUD --peer-group PEER_GROUP Remove a subcloud from peer-group: dcmanager subcloud update SUBCLOUD --peer-group none Test Plan: 1. PASS - Create a subcloud-peer-group 2. PASS - Update an existing subcloud's peer-group to a existing subcloud-peer-group successfully; 3. PASS - Verify subcloud-peer-group list-subclouds can get the expected Subcloud above successfully; 4. PASS - Update group_priority/group_state/max_subcloud_rehoming/ system_leader_id/system_leader_name of a subcloud-peer-group successfully; 5. PASS - Check can get subcloud status of a subcloud-peer-group successfully; 6. PASS - Delete a subcloud-peer-group completes successfully. 7. PASS - Delete a subcloud-peer-group while it still has subclouds associated to it. the subclouds' peer-group-id is auto set to None successfully; 8. PASS - Add a subcloud, update the peer-group-id as a non-existing subcloud-peer-group, get error message successfully; 9. PASS - Update subcloud peer group with invalid group_priority/group_state/max_subcloud_rehoming/ system_leader_id/system_leader_name Story: 2010852 Task: 48485 Change-Id: I93d0808b8cf02eba0e6f687007df42e2d2ea1848 Signed-off-by: Wang Tao <tao.wang@windriver.com>
This commit is contained in:
parent
a0dfc1adc3
commit
8196e7f946
|
@ -229,6 +229,7 @@ This operation does not accept a request body.
|
|||
- management-end-ip: management_end_ip
|
||||
- management-subnet: management_subnet
|
||||
- management-gateway-ip: management_gateway_ip
|
||||
- peer_group_id: subcloud_peer_group_id
|
||||
- rehome_data: rehome_data
|
||||
- created-at: created_at
|
||||
- updated-at: updated_at
|
||||
|
@ -294,6 +295,7 @@ This operation does not accept a request body.
|
|||
- management-subnet: management_subnet
|
||||
- management-gateway-ip: management_gateway_ip
|
||||
- oam_floating_ip: oam_floating_ip
|
||||
- peer_group_id: subcloud_peer_group_id
|
||||
- rehome_data: rehome_data
|
||||
- created-at: created_at
|
||||
- updated-at: updated_at
|
||||
|
@ -335,6 +337,8 @@ The attributes of a subcloud which are modifiable:
|
|||
|
||||
- management-end-ip
|
||||
|
||||
- peer_group_id
|
||||
|
||||
- bootstrap_values
|
||||
|
||||
- bootstrap_address
|
||||
|
@ -363,6 +367,7 @@ serviceUnavailable (503)
|
|||
- management-gateway-ip: subcloud_management_gateway_ip
|
||||
- management-start-ip: subcloud_management_start_ip
|
||||
- management-end-ip: subcloud_management_end_ip
|
||||
- peer_group_id: subcloud_peer_group_id
|
||||
- bootstrap-address: bootstrap_address
|
||||
- sysadmin-password: sysadmin_password
|
||||
- bootstrap-values: bootstrap_values_for_rehome
|
||||
|
@ -379,6 +384,7 @@ Request Example
|
|||
|
||||
- id: subcloud_id
|
||||
- group_id: group_id
|
||||
- peer_group_id: subcloud_peer_group_id
|
||||
- name: subcloud_name
|
||||
- description: subcloud_description
|
||||
- location: subcloud_location
|
||||
|
@ -2561,3 +2567,332 @@ internalServerError (500), serviceUnavailable (503)
|
|||
- system-peer: system_peer_uri
|
||||
|
||||
This operation does not accept a request body.
|
||||
|
||||
--------------------
|
||||
Subcloud Peer Groups
|
||||
--------------------
|
||||
|
||||
Subcloud Peer Groups are logical groupings managed by a central System Controller.
|
||||
It's a group of the current managed subclouds which are supposed to be duplicated
|
||||
in a peer site as secondary subclouds
|
||||
|
||||
******************************
|
||||
Lists all subcloud peer groups
|
||||
******************************
|
||||
|
||||
.. rest_method:: GET /v1.0/subcloud-peer-groups
|
||||
|
||||
This operation does not accept a request body.
|
||||
|
||||
**Normal response codes**
|
||||
|
||||
200
|
||||
|
||||
**Error response codes**
|
||||
|
||||
badRequest (400), unauthorized (401), forbidden (403),
|
||||
badMethod (405), HTTPUnprocessableEntity (422),
|
||||
internalServerError (500), serviceUnavailable (503)
|
||||
|
||||
|
||||
**Response parameters**
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- subcloud_peer_groups: subcloud_peer_groups
|
||||
- id: subcloud_peer_group_id
|
||||
- peer_group_name: subcloud_peer_group_name
|
||||
- group_priority: subcloud_peer_group_priority
|
||||
- group_state: subcloud_peer_group_administrative_state
|
||||
- max_subcloud_rehoming: subcloud_peer_group_max_subcloud_rehoming
|
||||
- system_leader_id: subcloud_peer_group_system_leader_id
|
||||
- system_leader_name: subcloud_peer_group_system_leader_name
|
||||
- created_at: created_at
|
||||
- updated_at: updated_at
|
||||
|
||||
Response Example
|
||||
----------------
|
||||
|
||||
.. literalinclude:: samples/subcloud-peer-groups/subcloud-peer-groups-get-response.json
|
||||
:language: json
|
||||
|
||||
|
||||
*****************************
|
||||
Creates a subcloud peer group
|
||||
*****************************
|
||||
|
||||
.. rest_method:: POST /v1.0/subcloud-peer-groups
|
||||
|
||||
**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_group_name: subcloud_peer_group_name
|
||||
- group_priority: subcloud_peer_group_priority
|
||||
- group_state: subcloud_peer_group_administrative_state
|
||||
- max_subcloud_rehoming: subcloud_peer_group_max_subcloud_rehoming
|
||||
- system_leader_id: subcloud_peer_group_system_leader_id
|
||||
- system_leader_name: subcloud_peer_group_system_leader_name
|
||||
|
||||
Request Example
|
||||
----------------
|
||||
|
||||
.. literalinclude:: samples/subcloud-peer-groups/subcloud-peer-groups-post-request.json
|
||||
:language: json
|
||||
|
||||
**Response parameters**
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- id: subcloud_peer_group_id
|
||||
- peer_group_name: subcloud_peer_group_name
|
||||
- group_priority: subcloud_peer_group_priority
|
||||
- group_state: subcloud_peer_group_administrative_state
|
||||
- max_subcloud_rehoming: subcloud_peer_group_max_subcloud_rehoming
|
||||
- system_leader_id: subcloud_peer_group_system_leader_id
|
||||
- system_leader_name: subcloud_peer_group_system_leader_name
|
||||
- created_at: created_at
|
||||
- updated_at: updated_at
|
||||
|
||||
Response Example
|
||||
----------------
|
||||
|
||||
.. literalinclude:: samples/subcloud-peer-groups/subcloud-peer-groups-post-response.json
|
||||
:language: json
|
||||
|
||||
|
||||
***************************************************
|
||||
Shows information about a specific subcloud group
|
||||
***************************************************
|
||||
|
||||
.. rest_method:: GET /v1.0/subcloud-peer-groups/{subcloud-peer-group}
|
||||
|
||||
**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
|
||||
|
||||
- subcloud-peer-group: subcloud_peer_group_uri
|
||||
|
||||
This operation does not accept a request body.
|
||||
|
||||
**Response parameters**
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- id: subcloud_peer_group_id
|
||||
- peer_group_name: subcloud_peer_group_name
|
||||
- group_priority: subcloud_peer_group_priority
|
||||
- group_state: subcloud_peer_group_administrative_state
|
||||
- max_subcloud_rehoming: subcloud_peer_group_max_subcloud_rehoming
|
||||
- system_leader_id: subcloud_peer_group_system_leader_id
|
||||
- system_leader_name: subcloud_peer_group_system_leader_name
|
||||
- created_at: created_at
|
||||
- updated_at: updated_at
|
||||
|
||||
Response Example
|
||||
----------------
|
||||
|
||||
.. literalinclude:: samples/subcloud-peer-groups/subcloud-peer-groups-post-response.json
|
||||
:language: json
|
||||
|
||||
|
||||
******************************************************
|
||||
Shows subclouds that are part of a subcloud peer group
|
||||
******************************************************
|
||||
|
||||
.. rest_method:: GET /v1.0/subcloud-peer-groups/{subcloud-peer-group}/subclouds
|
||||
|
||||
**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
|
||||
|
||||
- subcloud-peer-group: subcloud_peer_group_uri
|
||||
|
||||
This operation does not accept a request body.
|
||||
|
||||
**Response parameters**
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- subclouds: subclouds
|
||||
- id: subcloud_id
|
||||
- group_id: group_id
|
||||
- name: subcloud_name
|
||||
- description: subcloud_description
|
||||
- location: subcloud_location
|
||||
- software-version: software_version
|
||||
- availability-status: availability_status
|
||||
- error-description: error_description
|
||||
- deploy-status: deploy_status
|
||||
- backup-status: backup_status
|
||||
- backup-datetime: backup_datetime
|
||||
- openstack-installed: openstack_installed
|
||||
- management-state: management_state
|
||||
- systemcontroller-gateway-ip: systemcontroller_gateway_ip
|
||||
- management-start-ip: management_start_ip
|
||||
- management-end-ip: management_end_ip
|
||||
- management-subnet: management_subnet
|
||||
- management-gateway-ip: management_gateway_ip
|
||||
- created-at: created_at
|
||||
- updated-at: updated_at
|
||||
- data_install: data_install
|
||||
- data_upgrade: data_upgrade
|
||||
|
||||
Response Example
|
||||
----------------
|
||||
|
||||
.. literalinclude:: samples/subcloud-peer-groups/subcloud-peer-groups-get-subclouds-response.json
|
||||
:language: json
|
||||
|
||||
|
||||
***************************************
|
||||
Modifies a specific subcloud peer group
|
||||
***************************************
|
||||
|
||||
.. rest_method:: PATCH /v1.0/subcloud-peer-groups/{subcloud-peer-group}
|
||||
|
||||
The attributes of a subcloud peer group which are modifiable:
|
||||
|
||||
- peer_group_name
|
||||
|
||||
- group_priority
|
||||
|
||||
- group_state
|
||||
|
||||
- max_subcloud_rehoming
|
||||
|
||||
- system_leader_id
|
||||
|
||||
- system_leader_name
|
||||
|
||||
|
||||
**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
|
||||
|
||||
- subcloud-peer-group: subcloud_peer_group_uri
|
||||
- peer_group_name: subcloud_peer_group_name
|
||||
- group_priority: subcloud_peer_group_priority
|
||||
- group_state: subcloud_peer_group_administrative_state
|
||||
- max_subcloud_rehoming: subcloud_peer_group_max_subcloud_rehoming
|
||||
- system_leader_id: subcloud_peer_group_system_leader_id
|
||||
- system_leader_name: subcloud_peer_group_system_leader_name
|
||||
|
||||
Request Example
|
||||
----------------
|
||||
.. literalinclude:: samples/subcloud-peer-groups/subcloud-peer-group-patch-request.json
|
||||
:language: json
|
||||
|
||||
**Response parameters**
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- id: subcloud_peer_group_id
|
||||
- peer_group_name: subcloud_peer_group_name
|
||||
- group_priority: subcloud_peer_group_priority
|
||||
- group_state: subcloud_peer_group_administrative_state
|
||||
- max_subcloud_rehoming: subcloud_peer_group_max_subcloud_rehoming
|
||||
- system_leader_id: subcloud_peer_group_system_leader_id
|
||||
- system_leader_name: subcloud_peer_group_system_leader_name
|
||||
- created_at: created_at
|
||||
- updated_at: updated_at
|
||||
|
||||
Response Example
|
||||
----------------
|
||||
|
||||
.. literalinclude:: samples/subcloud-peer-groups/subcloud-peer-group-patch-response.json
|
||||
:language: json
|
||||
|
||||
|
||||
**************************************
|
||||
Migrate a specific subcloud peer group
|
||||
**************************************
|
||||
|
||||
.. rest_method:: PATCH /v1.0/subcloud-peer-groups/{subcloud-peer-group}/migrate
|
||||
|
||||
**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
|
||||
|
||||
- subcloud-peer-group: subcloud_peer_group_uri
|
||||
- sysadmin-password: sysadmin_password
|
||||
|
||||
Request Example
|
||||
----------------
|
||||
.. literalinclude:: samples/subcloud-peer-groups/subcloud-peer-groups-patch-migrate-request.json
|
||||
:language: json
|
||||
|
||||
|
||||
**************************************
|
||||
Deletes a specific subcloud peer group
|
||||
**************************************
|
||||
|
||||
.. rest_method:: DELETE /v1.0/subcloud-peer-groups/{subcloud-peer-group}
|
||||
|
||||
**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
|
||||
|
||||
- subcloud-peer-group: subcloud_peer_group_uri
|
||||
|
||||
This operation does not accept a request body.
|
|
@ -25,6 +25,12 @@ subcloud_options_uri:
|
|||
in: path
|
||||
required: true
|
||||
type: string
|
||||
subcloud_peer_group_uri:
|
||||
description: |
|
||||
The subcloud peer group reference, name or id.
|
||||
in: path
|
||||
required: true
|
||||
type: string
|
||||
subcloud_uri:
|
||||
description: |
|
||||
The subcloud reference, name or id.
|
||||
|
@ -671,6 +677,56 @@ subcloud_name:
|
|||
in: body
|
||||
required: true
|
||||
type: string
|
||||
subcloud_peer_group_administrative_state:
|
||||
description: |
|
||||
The administrative state of the subcloud peer group.
|
||||
Valid value is enabled/disabled.
|
||||
in: body
|
||||
required: false
|
||||
type: string
|
||||
subcloud_peer_group_id:
|
||||
description: |
|
||||
The ID of the subcloud peer group associated with this object.
|
||||
in: body
|
||||
required: false
|
||||
type: string
|
||||
subcloud_peer_group_max_subcloud_rehoming:
|
||||
description: |
|
||||
The maximum number of subclouds to rehome in parallel.
|
||||
in: body
|
||||
required: false
|
||||
type: integer
|
||||
subcloud_peer_group_name:
|
||||
description: |
|
||||
The NAME of the subcloud peer group.
|
||||
in: body
|
||||
required: true
|
||||
type: string
|
||||
subcloud_peer_group_priority:
|
||||
description: |
|
||||
The priority of the subcloud peer group.
|
||||
Number lower priority is higher.
|
||||
in: body
|
||||
required: false
|
||||
type: integer
|
||||
subcloud_peer_group_system_leader_id:
|
||||
description: |
|
||||
UUID of the peer system.
|
||||
in: body
|
||||
required: false
|
||||
type: string
|
||||
subcloud_peer_group_system_leader_name:
|
||||
description: |
|
||||
NAME of the peer system.
|
||||
in: body
|
||||
required: false
|
||||
type: string
|
||||
subcloud_peer_groups:
|
||||
description: |
|
||||
The list of ``subcloud-peer-group`` objects.
|
||||
in: body
|
||||
required: true
|
||||
type: array
|
||||
subcloud_uuid:
|
||||
description: |
|
||||
The ID of a subcloud as a uuid.
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"id": 1,
|
||||
"peer_group_name": "dc1-pg",
|
||||
"group_priority": 0,
|
||||
"group_state": "enabled",
|
||||
"max_subcloud_rehoming": 10,
|
||||
"system_leader_id": "ac62f555-9386-42f1-b3a1-51ecb709409d",
|
||||
"system_leader_name": "dc1-name",
|
||||
"created-at": "2023-07-26 00:51:01.396694",
|
||||
"updated-at": "2023-07-26 00:57:35.941816"
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"id": 1,
|
||||
"peer_group_name": "dc1-pg",
|
||||
"group_priority": 0,
|
||||
"group_state": "enabled",
|
||||
"max_subcloud_rehoming": 10,
|
||||
"system_leader_id": "ac62f555-9386-42f1-b3a1-51ecb709409d",
|
||||
"system_leader_name": "dc1-name",
|
||||
"created-at": "2023-07-26 00:51:01.396694",
|
||||
"updated-at": "2023-08-07 06:09:04.086417"
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"id": 1,
|
||||
"peer_group_name": "dc1-pg",
|
||||
"group_priority": 0,
|
||||
"group_state": "enabled",
|
||||
"max_subcloud_rehoming": 10,
|
||||
"system_leader_id": "ac62f555-9386-42f1-b3a1-51ecb709409d",
|
||||
"system_leader_name": "dc1-name",
|
||||
"created-at": "2023-07-26 00:51:01.396694",
|
||||
"updated-at": "2023-08-07 06:09:04.086417"
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"subcloud_peer_groups": [{
|
||||
"id": 1,
|
||||
"peer_group_name": "dc1-pg",
|
||||
"group_priority": 0,
|
||||
"group_state": "enabled",
|
||||
"max_subcloud_rehoming": 10,
|
||||
"system_leader_id": "ac62f555-9386-42f1-b3a1-51ecb709409d",
|
||||
"system_leader_name": "dc1-name",
|
||||
"created-at": "2023-07-26 00:51:01.396694",
|
||||
"updated-at": "2023-08-07 06:09:04.086417"
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"subclouds": [{
|
||||
"id": 23,
|
||||
"name": "fakesub1",
|
||||
"description": "desc",
|
||||
"location": "PEK SE Lab",
|
||||
"software-version": "23.09",
|
||||
"management-state": "unmanaged",
|
||||
"availability-status": "offline",
|
||||
"deploy-status": "secondary",
|
||||
"backup-status": null,
|
||||
"backup-datetime": null,
|
||||
"error-description": "No errors present",
|
||||
"management-subnet": "192.168.38.0/24",
|
||||
"management-start-ip": "192.168.38.2",
|
||||
"management-end-ip": "192.168.38.200",
|
||||
"management-gateway-ip": "192.168.38.1",
|
||||
"openstack-installed": false,
|
||||
"systemcontroller-gateway-ip": "192.168.10.1",
|
||||
"data_install": null,
|
||||
"data_upgrade": null,
|
||||
"created-at": "2023-08-04 05:45:04.416188",
|
||||
"updated-at": "2023-08-04 08:55:13.034874",
|
||||
"group_id": 1,
|
||||
"peer_group_id": "6",
|
||||
"rehome_data": "{\"saved_payload\": {\"system_mode\": \"simplex\", \"name\": \"fakesub2\", \"description\": \"bbb\", \"location\": \"PEK SE Lab\", \"external_oam_subnet\": \"128.224.115.0/24\", \"external_oam_gateway_address\": \"128.224.115.1\", \"external_oam_floating_address\": \"128.224.115.15\", \"management_subnet\": \"192.168.38.0/24\", \"management_start_address\": \"192.168.38.2\", \"management_end_address\": \"192.168.38.200\", \"management_gateway_address\": \"192.168.38.1\", \"systemcontroller_gateway_address\": \"192.168.10.1\", \"docker_http_proxy\": \"http://147.11.252.42:9090\", \"docker_https_proxy\": \"http://147.11.252.42:9090\", \"docker_no_proxy\": [], \"sysadmin_password\": \"Wind123$\", \"bootstrap-address\": \"192.168.58.2\"}}"
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"sysadmin_password": "XXXXXXX"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"peer_group_name": "pg-name",
|
||||
"group_priority": 0,
|
||||
"group_state": "enabled",
|
||||
"max_subcloud_rehoming": 10,
|
||||
"system_leader_id": "ac62f555-9386-42f1-b3a1-51ecb709409d",
|
||||
"system_leader_name": "dc1-name"
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"id": 9,
|
||||
"peer_group_name": "pg-name",
|
||||
"group_priority": 0,
|
||||
"group_state": "enabled",
|
||||
"max_subcloud_rehoming": 10,
|
||||
"system_leader_id": "ac62f555-9386-42f1-b3a1-51ecb709409d",
|
||||
"system_leader_name": "dc1-name",
|
||||
"created-at": "2023-08-07 06:13:52.664047",
|
||||
"updated-at": null
|
||||
}
|
|
@ -22,6 +22,7 @@ from dcmanager.api.controllers.v1 import phased_subcloud_deploy
|
|||
from dcmanager.api.controllers.v1 import subcloud_backup
|
||||
from dcmanager.api.controllers.v1 import subcloud_deploy
|
||||
from dcmanager.api.controllers.v1 import subcloud_group
|
||||
from dcmanager.api.controllers.v1 import subcloud_peer_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
|
||||
|
@ -55,6 +56,8 @@ class Controller(object):
|
|||
SubcloudBackupController
|
||||
sub_controllers["phased-subcloud-deploy"] = phased_subcloud_deploy.\
|
||||
PhasedSubcloudDeployController
|
||||
sub_controllers["subcloud-peer-groups"] = \
|
||||
subcloud_peer_group.SubcloudPeerGroupsController
|
||||
sub_controllers["system-peers"] = system_peers.\
|
||||
SystemPeersController
|
||||
|
||||
|
|
|
@ -0,0 +1,424 @@
|
|||
# Copyright (c) 2023 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
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 http.client as httpclient
|
||||
import json
|
||||
import pecan
|
||||
from pecan import expose
|
||||
from pecan import request
|
||||
import uuid
|
||||
|
||||
from dccommon import consts as dccommon_consts
|
||||
from dccommon.drivers.openstack.sdk_platform import OpenStackDriver
|
||||
from dccommon.drivers.openstack.sysinv_v1 import SysinvClient
|
||||
from dcmanager.api.controllers import restcomm
|
||||
from dcmanager.api.policies import subcloud_peer_group as subcloud_peer_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__)
|
||||
|
||||
|
||||
# validation constants for Subcloud Peer Group
|
||||
MAX_SUBCLOUD_PEER_GROUP_NAME_LEN = 255
|
||||
MIN_SUBCLOUD_PEER_GROUP_SUBCLOUD_REHOMING = 1
|
||||
MAX_SUBCLOUD_PEER_GROUP_SUBCLOUD_REHOMING = 250
|
||||
MAX_SYSTEM_LEADER_NAME_LEN = 255
|
||||
MAX_SUBCLOUD_PEER_GROUP_PRIORITY = 65536
|
||||
MIN_SUBCLOUD_PEER_GROUP_PRIORITY = 0
|
||||
DEFAULT_SUBCLOUD_PEER_GROUP_PRIORITY = 0
|
||||
DEFAULT_SUBCLOUD_PEER_GROUP_MAX_REHOMING = 10
|
||||
SUPPORTED_GROUP_STATES = [
|
||||
consts.OPERATIONAL_ENABLED,
|
||||
consts.OPERATIONAL_DISABLED
|
||||
]
|
||||
|
||||
|
||||
class SubcloudPeerGroupsController(restcomm.GenericPathController):
|
||||
|
||||
def __init__(self):
|
||||
super(SubcloudPeerGroupsController, 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_peer_group(self, context, group_id):
|
||||
subclouds = db_api.subcloud_get_for_peer_group(context, group_id)
|
||||
return utils.subcloud_db_list_to_dict(subclouds)
|
||||
|
||||
def _get_subcloud_peer_group_list(self, context):
|
||||
groups = db_api.subcloud_peer_group_get_all(context)
|
||||
subcloud_peer_group_list = []
|
||||
|
||||
for group in groups:
|
||||
group_dict = db_api.subcloud_peer_group_db_model_to_dict(group)
|
||||
subcloud_peer_group_list.append(group_dict)
|
||||
|
||||
result = {'subcloud_peer_groups': subcloud_peer_group_list}
|
||||
return result
|
||||
|
||||
def _get_local_system(self):
|
||||
try:
|
||||
ks_client = OpenStackDriver(
|
||||
region_name=dccommon_consts.DEFAULT_REGION_NAME,
|
||||
region_clients=None
|
||||
)
|
||||
sysinv_client = SysinvClient(
|
||||
dccommon_consts.DEFAULT_REGION_NAME,
|
||||
ks_client.keystone_client.session,
|
||||
endpoint=ks_client.keystone_client.endpoint_cache.get_endpoint
|
||||
("sysinv"),
|
||||
)
|
||||
system = sysinv_client.get_system()
|
||||
return system
|
||||
except Exception:
|
||||
pecan.abort(httpclient.BAD_REQUEST,
|
||||
_("Failed to get local system info"))
|
||||
|
||||
def _get_subcloud_status_for_peer_group(self, context, group):
|
||||
subclouds = db_api.subcloud_get_for_peer_group(context, group.id)
|
||||
pg_status = dict()
|
||||
pg_status['peer_group_id'] = group.id
|
||||
pg_status['peer_group_name'] = group.peer_group_name
|
||||
pg_status['total_subclouds'] = len(subclouds)
|
||||
pg_status['complete'] = 0
|
||||
pg_status['waiting_for_migrate'] = 0
|
||||
pg_status['rehoming'] = 0
|
||||
pg_status['rehome_failed'] = 0
|
||||
pg_status['managed'] = 0
|
||||
pg_status['unmanaged'] = 0
|
||||
for subcloud in subclouds:
|
||||
if subcloud.management_state == 'managed':
|
||||
pg_status['managed'] += 1
|
||||
else:
|
||||
pg_status['unmanaged'] += 1
|
||||
|
||||
if subcloud.deploy_status == 'secondary':
|
||||
pg_status['waiting_for_migrate'] += 1
|
||||
elif subcloud.deploy_status == 'rehome-failed':
|
||||
pg_status['rehome_failed'] += 1
|
||||
elif subcloud.deploy_status == 'rehome-prep-failed':
|
||||
pg_status['rehome_failed'] += 1
|
||||
elif subcloud.deploy_status == 'complete':
|
||||
pg_status['complete'] += 1
|
||||
elif subcloud.deploy_status == 'rehoming':
|
||||
pg_status['rehoming'] += 1
|
||||
return pg_status
|
||||
|
||||
@index.when(method='GET', template='json')
|
||||
def get(self, group_ref=None, verb=None):
|
||||
"""Get details about subcloud peer group.
|
||||
|
||||
:param verb: Specifies the get action to be taken
|
||||
to the subcloud-peer-group get operation
|
||||
:param group_ref: ID or name of subcloud peer group
|
||||
"""
|
||||
policy.authorize(subcloud_peer_group_policy.POLICY_ROOT % "get", {},
|
||||
restcomm.extract_credentials_for_policy())
|
||||
context = restcomm.extract_context_from_environ()
|
||||
|
||||
if group_ref is None:
|
||||
# List of subcloud peer groups requested
|
||||
return self._get_subcloud_peer_group_list(context)
|
||||
|
||||
group = utils.subcloud_peer_group_get_by_ref(context, group_ref)
|
||||
if group is None:
|
||||
pecan.abort(httpclient.NOT_FOUND, _("Subcloud Peer Group not found"))
|
||||
if verb is None:
|
||||
subcloud_peer_group_dict = db_api.subcloud_peer_group_db_model_to_dict(group)
|
||||
return subcloud_peer_group_dict
|
||||
elif verb == 'subclouds':
|
||||
# Return only the subclouds for this subcloud peer group
|
||||
return self._get_subcloud_list_for_peer_group(context, group.id)
|
||||
elif verb == 'status':
|
||||
return self._get_subcloud_status_for_peer_group(context, group)
|
||||
else:
|
||||
pecan.abort(400, _('Invalid request'))
|
||||
|
||||
@index.when(method='POST', template='json')
|
||||
def post(self):
|
||||
"""Create a new subcloud peer group."""
|
||||
policy.authorize(subcloud_peer_group_policy.POLICY_ROOT % "create", {},
|
||||
restcomm.extract_credentials_for_policy())
|
||||
context = restcomm.extract_context_from_environ()
|
||||
|
||||
payload = json.loads(request.body)
|
||||
if not payload:
|
||||
pecan.abort(httpclient.BAD_REQUEST, _('Body required'))
|
||||
|
||||
LOG.info("Handling create subcloud peer group request for: %s" % payload)
|
||||
peer_group_name = payload.get('peer-group-name')
|
||||
group_priority = payload.get('group-priority')
|
||||
group_state = payload.get('group-state')
|
||||
system_leader_id = payload.get('system-leader-id')
|
||||
system_leader_name = payload.get('system-leader-name')
|
||||
max_subcloud_rehoming = payload.get('max-subcloud-rehoming')
|
||||
|
||||
local_system = None
|
||||
# Validate payload
|
||||
# peer_group_name is mandatory
|
||||
if not self._validate_name(peer_group_name):
|
||||
pecan.abort(httpclient.BAD_REQUEST, _('Invalid peer-group-name'))
|
||||
if not system_leader_id:
|
||||
# 1.Operator does not need to (and should not) specify
|
||||
# system_leader_id for a local subcloud peer group which
|
||||
# is supposed to group local subclouds being managed by
|
||||
# local system, since the leader should be the local system
|
||||
# 2.system_leader_id should be specified via API when the
|
||||
# subcloud peer group is duplicated into peer system which
|
||||
# is not the leader of this subcloud peer group
|
||||
if not local_system:
|
||||
local_system = self._get_local_system()
|
||||
system_leader_id = local_system.uuid
|
||||
elif not self._validate_system_leader_id(system_leader_id):
|
||||
pecan.abort(httpclient.BAD_REQUEST,
|
||||
_('Invalid system-leader-id [%s]' % (system_leader_id)))
|
||||
if not system_leader_name:
|
||||
# Get system_leader_name from local DC
|
||||
# if no system_leader_name provided
|
||||
if not local_system:
|
||||
local_system = self._get_local_system()
|
||||
system_leader_name = local_system.name
|
||||
elif not self._validate_system_leader_name(system_leader_name):
|
||||
pecan.abort(httpclient.BAD_REQUEST,
|
||||
_('Invalid system-leader-name'))
|
||||
if not group_priority:
|
||||
group_priority = DEFAULT_SUBCLOUD_PEER_GROUP_PRIORITY
|
||||
elif not self._validate_group_priority(group_priority):
|
||||
pecan.abort(httpclient.BAD_REQUEST, _('Invalid group-priority'))
|
||||
if not group_state:
|
||||
group_state = consts.OPERATIONAL_ENABLED
|
||||
elif not self._validate_group_state(group_state):
|
||||
pecan.abort(httpclient.BAD_REQUEST,
|
||||
_('Invalid group-state'))
|
||||
if not max_subcloud_rehoming:
|
||||
max_subcloud_rehoming = DEFAULT_SUBCLOUD_PEER_GROUP_MAX_REHOMING
|
||||
elif not self._validate_max_subcloud_rehoming(max_subcloud_rehoming):
|
||||
pecan.abort(httpclient.BAD_REQUEST,
|
||||
_('Invalid max-subcloud-rehoming'))
|
||||
|
||||
try:
|
||||
group_ref = db_api.subcloud_peer_group_create(context,
|
||||
peer_group_name,
|
||||
group_priority,
|
||||
group_state,
|
||||
max_subcloud_rehoming,
|
||||
system_leader_id,
|
||||
system_leader_name)
|
||||
return db_api.subcloud_peer_group_db_model_to_dict(group_ref)
|
||||
except db_exc.DBDuplicateEntry:
|
||||
pecan.abort(httpclient.CONFLICT,
|
||||
_('A subcloud peer 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 peer group'))
|
||||
|
||||
@index.when(method='PATCH', template='json')
|
||||
def patch(self, group_ref, verb=None):
|
||||
"""Update a subcloud peer group.
|
||||
|
||||
:param verb: Specifies the get action to be taken
|
||||
to the subcloud-peer-group patch operation
|
||||
:param group_ref: ID or name of subcloud group to update
|
||||
"""
|
||||
|
||||
policy.authorize(subcloud_peer_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 Peer Group Name or ID required'))
|
||||
|
||||
group = utils.subcloud_peer_group_get_by_ref(context, group_ref)
|
||||
if group is None:
|
||||
pecan.abort(httpclient.NOT_FOUND, _('Subcloud Peer Group not found'))
|
||||
if verb is None:
|
||||
payload = json.loads(request.body)
|
||||
if not payload:
|
||||
pecan.abort(httpclient.BAD_REQUEST, _('Body required'))
|
||||
|
||||
LOG.info("Handling update subcloud peer group request for: %s" % payload)
|
||||
peer_group_name = payload.get('peer-group-name')
|
||||
group_priority = payload.get('group-priority')
|
||||
group_state = payload.get('group-state')
|
||||
system_leader_id = payload.get('system-leader-id')
|
||||
system_leader_name = payload.get('system-leader-name')
|
||||
max_subcloud_rehoming = payload.get('max-subcloud-rehoming')
|
||||
|
||||
if not (
|
||||
peer_group_name
|
||||
or group_priority
|
||||
or group_state
|
||||
or system_leader_id
|
||||
or system_leader_name
|
||||
or max_subcloud_rehoming
|
||||
):
|
||||
pecan.abort(httpclient.BAD_REQUEST, _('nothing to update'))
|
||||
|
||||
# Check value is not None or empty before calling validation function
|
||||
if peer_group_name and not self._validate_name(peer_group_name):
|
||||
pecan.abort(httpclient.BAD_REQUEST, _('Invalid peer-group-name'))
|
||||
if group_priority and not self._validate_group_priority(group_priority):
|
||||
pecan.abort(httpclient.BAD_REQUEST, _('Invalid group-priority'))
|
||||
if group_state and not self._validate_group_state(group_state):
|
||||
pecan.abort(httpclient.BAD_REQUEST,
|
||||
_('Invalid group-state'))
|
||||
if (max_subcloud_rehoming and
|
||||
not self._validate_max_subcloud_rehoming(max_subcloud_rehoming)):
|
||||
pecan.abort(httpclient.BAD_REQUEST,
|
||||
_('Invalid max-subcloud-rehoming'))
|
||||
if (system_leader_id and
|
||||
not self._validate_system_leader_id(system_leader_id)):
|
||||
pecan.abort(httpclient.BAD_REQUEST,
|
||||
_('Invalid system-leader-id'))
|
||||
if (system_leader_name and
|
||||
not self._validate_system_leader_name(system_leader_name)):
|
||||
pecan.abort(httpclient.BAD_REQUEST,
|
||||
_('Invalid system-leader-name'))
|
||||
|
||||
try:
|
||||
updated_peer_group = db_api.subcloud_peer_group_update(
|
||||
context,
|
||||
group.id,
|
||||
peer_group_name=peer_group_name,
|
||||
group_priority=group_priority,
|
||||
group_state=group_state,
|
||||
max_subcloud_rehoming=max_subcloud_rehoming,
|
||||
system_leader_id=system_leader_id,
|
||||
system_leader_name=system_leader_name)
|
||||
return db_api.subcloud_peer_group_db_model_to_dict(updated_peer_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 peer group'))
|
||||
elif verb == 'migrate':
|
||||
# TODO(tao): Subcloud Peer Group migrate implementation will
|
||||
# be submitted in the follow-up review.
|
||||
pass
|
||||
else:
|
||||
pecan.abort(400, _('Invalid request'))
|
||||
|
||||
def _validate_name(self, name):
|
||||
# Reject post and update operations for name that:
|
||||
# - attempt to set to None
|
||||
# - attempt to set to a number
|
||||
# - exceed the max length
|
||||
if not name:
|
||||
return False
|
||||
if name.isdigit():
|
||||
LOG.warning("Invalid name [%s], can not be digit" % name)
|
||||
return False
|
||||
if len(name) > MAX_SUBCLOUD_PEER_GROUP_NAME_LEN:
|
||||
LOG.warning("Invalid name length")
|
||||
return False
|
||||
# none is not a valid name
|
||||
if name.lower() == 'none':
|
||||
LOG.warning("Invalid name, cannot use 'none' as name")
|
||||
return False
|
||||
return True
|
||||
|
||||
def _validate_group_priority(self, priority):
|
||||
try:
|
||||
# Check the value is an integer
|
||||
val = int(priority)
|
||||
except ValueError:
|
||||
return False
|
||||
# We do not support less than min or greater than max
|
||||
if val < MIN_SUBCLOUD_PEER_GROUP_PRIORITY:
|
||||
return False
|
||||
if val > MAX_SUBCLOUD_PEER_GROUP_PRIORITY:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _validate_group_state(self, state):
|
||||
if state not in SUPPORTED_GROUP_STATES:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _validate_max_subcloud_rehoming(self, max_parallel_str):
|
||||
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_PEER_GROUP_SUBCLOUD_REHOMING:
|
||||
return False
|
||||
if val > MAX_SUBCLOUD_PEER_GROUP_SUBCLOUD_REHOMING:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _validate_system_leader_name(self, name):
|
||||
if len(name) > MAX_SYSTEM_LEADER_NAME_LEN:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _validate_system_leader_id(self, uuid_str):
|
||||
try:
|
||||
uuid.UUID(str(uuid_str))
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@index.when(method='delete', template='json')
|
||||
def delete(self, group_ref):
|
||||
"""Delete the subcloud peer group."""
|
||||
policy.authorize(subcloud_peer_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 Peer Group Name or ID required'))
|
||||
group = utils.subcloud_peer_group_get_by_ref(context, group_ref)
|
||||
if group is None:
|
||||
LOG.info("Subcloud Peer Group [%s] not found" % group_ref)
|
||||
pecan.abort(httpclient.NOT_FOUND, _('Subcloud Peer Group not found'))
|
||||
|
||||
LOG.info("Handling delete subcloud peer group request for: %s" % group)
|
||||
# TODO(Jon): uncomment in Association of System and Peer Group management commit
|
||||
'''
|
||||
# a peer group may not be deleted if it is used by any associations
|
||||
association = db_api.peer_group_association_get_by_peer_group_id(context,
|
||||
group.id)
|
||||
if len(association) > 0:
|
||||
pecan.abort(httpclient.BAD_REQUEST,
|
||||
_("Cannot delete a peer group "
|
||||
"which is associated with a system peer."))
|
||||
'''
|
||||
try:
|
||||
db_api.subcloud_peer_group_destroy(context, group.id)
|
||||
# Disassociate the subcloud.
|
||||
subclouds = db_api.subcloud_get_for_peer_group(context, group.id)
|
||||
for subcloud in subclouds:
|
||||
db_api.subcloud_update(context, subcloud.id,
|
||||
peer_group_id='none')
|
||||
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 peer group'))
|
|
@ -652,6 +652,7 @@ class SubcloudsController(object):
|
|||
description = payload.get('description')
|
||||
location = payload.get('location')
|
||||
bootstrap_values = payload.get('bootstrap_values')
|
||||
peer_group = payload.get('peer_group')
|
||||
bootstrap_address = payload.get('bootstrap_address')
|
||||
|
||||
# Syntax checking
|
||||
|
@ -683,6 +684,26 @@ class SubcloudsController(object):
|
|||
exceptions.SubcloudGroupNotFound):
|
||||
pecan.abort(400, _('Invalid group'))
|
||||
|
||||
# Verify the peer_group is valid
|
||||
peer_group_id = None
|
||||
if peer_group is not None:
|
||||
# peer_group may be passed in the payload as an int or str
|
||||
peer_group = str(peer_group)
|
||||
# Check if user wants to remove a subcloud
|
||||
# from a subcloud-peer-group by
|
||||
# setting peer_group_id as 'none',
|
||||
# then we will pass 'none' string as
|
||||
# the peer_group_id,
|
||||
# update_subcloud() will handle it and
|
||||
# Set the peer_group_id DB into None.
|
||||
if peer_group.lower() == 'none':
|
||||
peer_group_id = 'none'
|
||||
else:
|
||||
pgrp = utils.subcloud_peer_group_get_by_ref(context, peer_group)
|
||||
if not pgrp:
|
||||
pecan.abort(400, _('Invalid peer group'))
|
||||
peer_group_id = pgrp.id
|
||||
|
||||
if consts.INSTALL_VALUES in payload:
|
||||
psd_common.validate_install_values(payload, subcloud)
|
||||
payload['data_install'] = json.dumps(payload[consts.INSTALL_VALUES])
|
||||
|
@ -697,6 +718,7 @@ class SubcloudsController(object):
|
|||
description=description, location=location,
|
||||
group_id=group_id, data_install=payload.get('data_install'),
|
||||
force=force_flag,
|
||||
peer_group_id=peer_group_id,
|
||||
bootstrap_values=bootstrap_values,
|
||||
bootstrap_address=bootstrap_address)
|
||||
return subcloud
|
||||
|
|
|
@ -12,6 +12,7 @@ from dcmanager.api.policies import phased_subcloud_deploy
|
|||
from dcmanager.api.policies import subcloud_backup
|
||||
from dcmanager.api.policies import subcloud_deploy
|
||||
from dcmanager.api.policies import subcloud_group
|
||||
from dcmanager.api.policies import subcloud_peer_group
|
||||
from dcmanager.api.policies import subclouds
|
||||
from dcmanager.api.policies import sw_update_options
|
||||
from dcmanager.api.policies import sw_update_strategy
|
||||
|
@ -29,5 +30,6 @@ def list_rules():
|
|||
subcloud_group.list_rules(),
|
||||
subcloud_backup.list_rules(),
|
||||
phased_subcloud_deploy.list_rules(),
|
||||
subcloud_peer_group.list_rules(),
|
||||
system_peers.list_rules()
|
||||
)
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
#
|
||||
# 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:subcloud_peer_groups:%s'
|
||||
|
||||
|
||||
_subcloud_peer_groups_rules = [
|
||||
|
||||
# CRUD of subcloud-peer-groups entity
|
||||
policy.DocumentedRuleDefault(
|
||||
name=POLICY_ROOT % 'create',
|
||||
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS,
|
||||
description="Create subcloud peer group.",
|
||||
operations=[
|
||||
{
|
||||
'method': 'POST',
|
||||
'path': '/v1.0/subcloud-peer-groups'
|
||||
}
|
||||
]
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name=POLICY_ROOT % 'delete',
|
||||
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS,
|
||||
description="Delete subcloud peer group.",
|
||||
operations=[
|
||||
{
|
||||
'method': 'DELETE',
|
||||
'path': '/v1.0/subcloud-peer-groups/{subcloud_peer_group}'
|
||||
}
|
||||
]
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name=POLICY_ROOT % 'get',
|
||||
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS,
|
||||
description="Get Subcloud Peer Group data",
|
||||
operations=[
|
||||
{
|
||||
'method': 'GET',
|
||||
'path': '/v1.0/subcloud-peer-groups/'
|
||||
},
|
||||
# Show details of a specified Subcloud Peer Group
|
||||
{
|
||||
'method': 'GET',
|
||||
'path': '/v1.0/subcloud-peer-groups/{subcloud_peer_group}'
|
||||
},
|
||||
# Show subclouds status of the subcloud-peer-group
|
||||
{
|
||||
'method': 'GET',
|
||||
'path': '/v1.0/subcloud-peer-groups/{subcloud_peer_group}/status'
|
||||
},
|
||||
# List Subclouds assigned to the given Subcloud Peer Group
|
||||
{
|
||||
'method': 'GET',
|
||||
'path': '/v1.0/subcloud-peer-groups/{subcloud_peer_group}/subclouds'
|
||||
}
|
||||
]
|
||||
),
|
||||
# Update a Subcloud Peer Group with specified configuration
|
||||
policy.DocumentedRuleDefault(
|
||||
name=POLICY_ROOT % 'modify',
|
||||
check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS,
|
||||
description="Update a Subcloud Peer Group with specified configuration",
|
||||
operations=[
|
||||
{
|
||||
'method': 'PATCH',
|
||||
'path': '/v1.0/subcloud-peer-groups/{subcloud_peer_group}'
|
||||
},
|
||||
# Migrate subclouds entity of the subcloud-peer-group
|
||||
{
|
||||
'method': 'PATCH',
|
||||
'path': '/v1.0/subcloud-peer-groups/{subcloud_peer_group}/migrate'
|
||||
}
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def list_rules():
|
||||
return _subcloud_peer_groups_rules
|
|
@ -169,6 +169,14 @@ class SubcloudGroupNameNotFound(NotFound):
|
|||
message = _("Subcloud Group with name %(name)s doesn't exist.")
|
||||
|
||||
|
||||
class SubcloudPeerGroupNameNotFound(NotFound):
|
||||
message = _("Subcloud Peer Group with name %(name)s doesn't exist.")
|
||||
|
||||
|
||||
class SubcloudPeerGroupNotFound(NotFound):
|
||||
message = _("Subcloud Peer Group with id %(group_id)s doesn't exist.")
|
||||
|
||||
|
||||
class SubcloudGroupNameViolation(DCManagerException):
|
||||
message = _("Default Subcloud Group name cannot be changed or reused.")
|
||||
|
||||
|
|
|
@ -517,6 +517,11 @@ def system_peer_get_by_ref(context, peer_ref):
|
|||
return None
|
||||
|
||||
|
||||
def subcloud_peer_group_db_list_to_dict(peer_groups):
|
||||
return {'subcloud_peer_groups': [db_api.subcloud_peer_group_db_model_to_dict(
|
||||
peer_group) for peer_group in peer_groups]}
|
||||
|
||||
|
||||
def subcloud_get_by_ref(context, subcloud_ref):
|
||||
"""Handle getting a subcloud by either name, or ID
|
||||
|
||||
|
@ -548,6 +553,21 @@ def subcloud_group_get_by_ref(context, group_ref):
|
|||
return group
|
||||
|
||||
|
||||
def subcloud_peer_group_get_by_ref(context, group_ref):
|
||||
"""Handle getting a peer group by either name, or ID"""
|
||||
try:
|
||||
if group_ref.isdigit():
|
||||
# Lookup subcloud group as an ID
|
||||
group = db_api.subcloud_peer_group_get(context, group_ref)
|
||||
else:
|
||||
# Lookup subcloud group as a name
|
||||
group = db_api.subcloud_peer_group_get_by_name(context, group_ref)
|
||||
except (exceptions.SubcloudPeerGroupNotFound,
|
||||
exceptions.SubcloudPeerGroupNameNotFound):
|
||||
return None
|
||||
return group
|
||||
|
||||
|
||||
def subcloud_db_list_to_dict(subclouds):
|
||||
return {'subclouds': [db_api.subcloud_db_model_to_dict(subcloud)
|
||||
for subcloud in subclouds]}
|
||||
|
|
|
@ -125,6 +125,7 @@ def subcloud_db_model_to_dict(subcloud):
|
|||
"created-at": subcloud.created_at,
|
||||
"updated-at": subcloud.updated_at,
|
||||
"group_id": subcloud.group_id,
|
||||
"peer_group_id": subcloud.peer_group_id,
|
||||
"rehome_data": subcloud.rehome_data}
|
||||
return result
|
||||
|
||||
|
@ -195,7 +196,7 @@ def subcloud_update(context, subcloud_id, management_state=None,
|
|||
data_install=None, data_upgrade=None,
|
||||
first_identity_sync_complete=None,
|
||||
systemcontroller_gateway_ip=None,
|
||||
rehome_data=None):
|
||||
peer_group_id=None, rehome_data=None):
|
||||
"""Update a subcloud or raise if it does not exist."""
|
||||
return IMPL.subcloud_update(context, subcloud_id, management_state,
|
||||
availability_status, software_version, name,
|
||||
|
@ -205,7 +206,7 @@ def subcloud_update(context, subcloud_id, management_state=None,
|
|||
backup_datetime, error_description, openstack_installed,
|
||||
group_id, data_install, data_upgrade,
|
||||
first_identity_sync_complete,
|
||||
systemcontroller_gateway_ip, rehome_data)
|
||||
systemcontroller_gateway_ip, peer_group_id, rehome_data)
|
||||
|
||||
|
||||
def subcloud_bulk_update_by_ids(context, subcloud_ids, update_form):
|
||||
|
@ -450,9 +451,84 @@ def system_peer_update(context, peer_id,
|
|||
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)
|
||||
###################
|
||||
|
||||
|
||||
###################
|
||||
# subcloud_peer_group
|
||||
def subcloud_peer_group_db_model_to_dict(subcloud_peer_group):
|
||||
"""Convert subcloud_peer_group db model to dictionary."""
|
||||
result = {"id": subcloud_peer_group.id,
|
||||
"peer_group_name": subcloud_peer_group.peer_group_name,
|
||||
"group_priority": subcloud_peer_group.group_priority,
|
||||
"group_state": subcloud_peer_group.group_state,
|
||||
"max_subcloud_rehoming": subcloud_peer_group.max_subcloud_rehoming,
|
||||
"system_leader_id": subcloud_peer_group.system_leader_id,
|
||||
"system_leader_name": subcloud_peer_group.system_leader_name,
|
||||
"created-at": subcloud_peer_group.created_at,
|
||||
"updated-at": subcloud_peer_group.updated_at}
|
||||
return result
|
||||
|
||||
|
||||
def subcloud_peer_group_create(context, peer_group_name, group_priority, group_state,
|
||||
max_subcloud_rehoming, system_leader_id, system_leader_name):
|
||||
"""Create a subcloud_peer_group."""
|
||||
return IMPL.subcloud_peer_group_create(context,
|
||||
peer_group_name,
|
||||
group_priority,
|
||||
group_state,
|
||||
max_subcloud_rehoming,
|
||||
system_leader_id,
|
||||
system_leader_name)
|
||||
|
||||
|
||||
def subcloud_peer_group_destroy(context, group_id):
|
||||
"""Destroy the subcloud peer group or raise if it does not exist."""
|
||||
return IMPL.subcloud_peer_group_destroy(context, group_id)
|
||||
|
||||
|
||||
def subcloud_peer_group_get(context, group_id):
|
||||
"""Retrieve a subcloud_peer_group or raise if it does not exist."""
|
||||
return IMPL.subcloud_peer_group_get(context, group_id)
|
||||
|
||||
|
||||
def subcloud_peer_group_get_by_name(context, name):
|
||||
"""Retrieve a subcloud_peer_group by name or raise if it does not exist."""
|
||||
return IMPL.subcloud_peer_group_get_by_name(context, name)
|
||||
|
||||
|
||||
def subcloud_peer_group_get_by_leader_id(context, system_leader_id):
|
||||
"""Retrieve subcloud peer groups by system_leader_id."""
|
||||
return IMPL.subcloud_peer_group_get_by_leader_id(context, system_leader_id)
|
||||
|
||||
|
||||
def subcloud_get_for_peer_group(context, group_id):
|
||||
"""Retrieve all subclouds belonging to a subcloud_peer_group
|
||||
|
||||
or raise if it does not exist.
|
||||
"""
|
||||
return IMPL.subcloud_get_for_peer_group(context, group_id)
|
||||
|
||||
|
||||
def subcloud_peer_group_get_all(context):
|
||||
"""Retrieve all subcloud peer groups."""
|
||||
return IMPL.subcloud_peer_group_get_all(context)
|
||||
|
||||
|
||||
def subcloud_peer_group_update(context, group_id, peer_group_name, group_priority,
|
||||
group_state, max_subcloud_rehoming, system_leader_id,
|
||||
system_leader_name):
|
||||
"""Update the subcloud peer group or raise if it does not exist."""
|
||||
return IMPL.subcloud_peer_group_update(context,
|
||||
group_id,
|
||||
peer_group_name,
|
||||
group_priority,
|
||||
group_state,
|
||||
max_subcloud_rehoming,
|
||||
system_leader_id,
|
||||
system_leader_name)
|
||||
###################
|
||||
|
||||
|
||||
def sw_update_strategy_db_model_to_dict(sw_update_strategy):
|
||||
"""Convert sw update db model to dictionary."""
|
||||
|
|
|
@ -420,6 +420,7 @@ def subcloud_update(context, subcloud_id, management_state=None,
|
|||
data_upgrade=None,
|
||||
first_identity_sync_complete=None,
|
||||
systemcontroller_gateway_ip=None,
|
||||
peer_group_id=None,
|
||||
rehome_data=None):
|
||||
with write_session() as session:
|
||||
subcloud_ref = subcloud_get(context, subcloud_id)
|
||||
|
@ -466,6 +467,11 @@ def subcloud_update(context, subcloud_id, management_state=None,
|
|||
if systemcontroller_gateway_ip is not None:
|
||||
subcloud_ref.systemcontroller_gateway_ip = \
|
||||
systemcontroller_gateway_ip
|
||||
if peer_group_id is not None:
|
||||
if str(peer_group_id).lower() == 'none':
|
||||
subcloud_ref.peer_group_id = None
|
||||
else:
|
||||
subcloud_ref.peer_group_id = peer_group_id
|
||||
if rehome_data is not None:
|
||||
subcloud_ref.rehome_data = rehome_data
|
||||
subcloud_ref.save(session)
|
||||
|
@ -1069,6 +1075,132 @@ def initialize_subcloud_group_default(engine):
|
|||
##########################
|
||||
|
||||
|
||||
##########################
|
||||
# subcloud peer group
|
||||
##########################
|
||||
@require_context
|
||||
def subcloud_peer_group_get(context, group_id):
|
||||
try:
|
||||
result = model_query(context, models.SubcloudPeerGroup). \
|
||||
filter_by(deleted=0). \
|
||||
filter_by(id=group_id). \
|
||||
one()
|
||||
except NoResultFound:
|
||||
raise exception.SubcloudPeerGroupNotFound(group_id=group_id)
|
||||
except MultipleResultsFound:
|
||||
raise exception.InvalidParameterValue(
|
||||
err="Multiple entries found for subcloud peer group %s" % group_id)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@require_context
|
||||
def subcloud_get_for_peer_group(context, peer_group_id):
|
||||
"""Get all subclouds for a subcloud peer group.
|
||||
|
||||
:param context: request context object
|
||||
:param peer_group_id: ID of the subcloud peer group
|
||||
"""
|
||||
return model_query(context, models.Subcloud). \
|
||||
filter_by(deleted=0). \
|
||||
filter_by(peer_group_id=peer_group_id). \
|
||||
order_by(models.Subcloud.id). \
|
||||
all()
|
||||
|
||||
|
||||
@require_context
|
||||
def subcloud_peer_group_get_all(context):
|
||||
result = model_query(context, models.SubcloudPeerGroup). \
|
||||
filter_by(deleted=0). \
|
||||
order_by(models.SubcloudPeerGroup.id). \
|
||||
all()
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@require_context
|
||||
def subcloud_peer_group_get_by_name(context, name):
|
||||
try:
|
||||
result = model_query(context, models.SubcloudPeerGroup). \
|
||||
filter_by(deleted=0). \
|
||||
filter_by(peer_group_name=name). \
|
||||
one()
|
||||
except NoResultFound:
|
||||
raise exception.SubcloudPeerGroupNameNotFound(name=name)
|
||||
except MultipleResultsFound:
|
||||
# This exception should never happen due to the UNIQUE setting for name
|
||||
raise exception.InvalidParameterValue(
|
||||
err="Multiple entries found for subcloud peer group %s" % name)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@require_context
|
||||
def subcloud_peer_group_get_by_leader_id(context, system_leader_id):
|
||||
result = model_query(context, models.SubcloudPeerGroup). \
|
||||
filter_by(deleted=0). \
|
||||
filter_by(system_leader_id=system_leader_id). \
|
||||
order_by(models.SubcloudPeerGroup.id). \
|
||||
all()
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@require_admin_context
|
||||
def subcloud_peer_group_create(context,
|
||||
peer_group_name,
|
||||
group_priority,
|
||||
group_state,
|
||||
max_subcloud_rehoming,
|
||||
system_leader_id,
|
||||
system_leader_name):
|
||||
with write_session() as session:
|
||||
subcloud_peer_group_ref = models.SubcloudPeerGroup()
|
||||
subcloud_peer_group_ref.peer_group_name = peer_group_name
|
||||
subcloud_peer_group_ref.group_priority = group_priority
|
||||
subcloud_peer_group_ref.group_state = group_state
|
||||
subcloud_peer_group_ref.max_subcloud_rehoming = max_subcloud_rehoming
|
||||
subcloud_peer_group_ref.system_leader_id = system_leader_id
|
||||
subcloud_peer_group_ref.system_leader_name = system_leader_name
|
||||
session.add(subcloud_peer_group_ref)
|
||||
return subcloud_peer_group_ref
|
||||
|
||||
|
||||
@require_admin_context
|
||||
def subcloud_peer_group_destroy(context, group_id):
|
||||
with write_session() as session:
|
||||
subcloud_peer_group_ref = subcloud_peer_group_get(context, group_id)
|
||||
session.delete(subcloud_peer_group_ref)
|
||||
|
||||
|
||||
@require_admin_context
|
||||
def subcloud_peer_group_update(context,
|
||||
group_id,
|
||||
peer_group_name=None,
|
||||
group_priority=None,
|
||||
group_state=None,
|
||||
max_subcloud_rehoming=None,
|
||||
system_leader_id=None,
|
||||
system_leader_name=None):
|
||||
with write_session() as session:
|
||||
subcloud_peer_group_ref = subcloud_peer_group_get(context, group_id)
|
||||
if peer_group_name is not None:
|
||||
subcloud_peer_group_ref.peer_group_name = peer_group_name
|
||||
if group_priority is not None:
|
||||
subcloud_peer_group_ref.group_priority = group_priority
|
||||
if group_state is not None:
|
||||
subcloud_peer_group_ref.group_state = group_state
|
||||
if max_subcloud_rehoming is not None:
|
||||
subcloud_peer_group_ref.max_subcloud_rehoming = max_subcloud_rehoming
|
||||
if system_leader_id is not None:
|
||||
subcloud_peer_group_ref.system_leader_id = system_leader_id
|
||||
if system_leader_name is not None:
|
||||
subcloud_peer_group_ref.system_leader_name = system_leader_name
|
||||
subcloud_peer_group_ref.save(session)
|
||||
return subcloud_peer_group_ref
|
||||
##########################
|
||||
|
||||
|
||||
@require_context
|
||||
def strategy_step_get(context, subcloud_id):
|
||||
result = model_query(context, models.StrategyStep). \
|
||||
|
|
|
@ -16,6 +16,32 @@ 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 subcloud_peer_group table
|
||||
subcloud_peer_group = sqlalchemy.Table(
|
||||
'subcloud_peer_group', meta,
|
||||
sqlalchemy.Column('id', sqlalchemy.Integer,
|
||||
primary_key=True,
|
||||
autoincrement=True,
|
||||
nullable=False),
|
||||
sqlalchemy.Column('peer_group_name', sqlalchemy.String(255), unique=True),
|
||||
sqlalchemy.Column('group_priority', sqlalchemy.Integer),
|
||||
sqlalchemy.Column('group_state', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('system_leader_id', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('system_leader_name', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('max_subcloud_rehoming', sqlalchemy.Integer),
|
||||
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
|
||||
)
|
||||
subcloud_peer_group.create()
|
||||
# Add the 'peer_greoup_id' column to the subclouds table.
|
||||
subclouds.create_column(sqlalchemy.Column('peer_group_id', sqlalchemy.Integer))
|
||||
|
||||
# Declare the new system_peer table
|
||||
system_peer = sqlalchemy.Table(
|
||||
'system_peer', meta,
|
||||
|
|
|
@ -131,6 +131,20 @@ class SubcloudGroup(BASE, DCManagerBase):
|
|||
max_parallel_subclouds = Column(Integer)
|
||||
|
||||
|
||||
class SubcloudPeerGroup(BASE, DCManagerBase):
|
||||
"""Represents a subcloud group"""
|
||||
|
||||
__tablename__ = 'subcloud_peer_group'
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
|
||||
peer_group_name = Column(String(255), unique=True)
|
||||
group_priority = Column(Integer)
|
||||
group_state = Column(String(255))
|
||||
max_subcloud_rehoming = Column(Integer)
|
||||
system_leader_id = Column(String(255))
|
||||
system_leader_name = Column(String(255))
|
||||
|
||||
|
||||
class Subcloud(BASE, DCManagerBase):
|
||||
"""Represents a subcloud"""
|
||||
|
||||
|
@ -158,6 +172,8 @@ class Subcloud(BASE, DCManagerBase):
|
|||
systemcontroller_gateway_ip = Column(String(255))
|
||||
audit_fail_count = Column(Integer)
|
||||
first_identity_sync_complete = Column(Boolean, default=False)
|
||||
peer_group_id = Column(Integer,
|
||||
ForeignKey('subcloud_peer_group.id'))
|
||||
rehome_data = Column(Text())
|
||||
|
||||
# multiple subclouds can be in a particular group
|
||||
|
|
|
@ -136,7 +136,7 @@ class DCManagerService(service.Service):
|
|||
description=None, location=None,
|
||||
group_id=None, data_install=None, force=None,
|
||||
deploy_status=None,
|
||||
bootstrap_values=None, bootstrap_address=None):
|
||||
peer_group_id=None, bootstrap_values=None, bootstrap_address=None):
|
||||
# Updates a subcloud
|
||||
LOG.info("Handling update_subcloud request for: %s" % subcloud_id)
|
||||
subcloud = self.subcloud_manager.update_subcloud(context, subcloud_id,
|
||||
|
@ -147,6 +147,7 @@ class DCManagerService(service.Service):
|
|||
data_install,
|
||||
force,
|
||||
deploy_status,
|
||||
peer_group_id,
|
||||
bootstrap_values,
|
||||
bootstrap_address)
|
||||
return subcloud
|
||||
|
|
|
@ -2313,6 +2313,7 @@ class SubcloudManager(manager.Manager):
|
|||
data_install=None,
|
||||
force=None,
|
||||
deploy_status=None,
|
||||
peer_group_id=None,
|
||||
bootstrap_values=None,
|
||||
bootstrap_address=None):
|
||||
"""Update subcloud and notify orchestrators.
|
||||
|
@ -2326,6 +2327,7 @@ class SubcloudManager(manager.Manager):
|
|||
:param data_install: subcloud install values
|
||||
:param force: force flag
|
||||
:param deploy_status: update to expected deploy status
|
||||
:param peer_group_id: id of peer group
|
||||
:param bootstrap_values: bootstrap_values yaml content
|
||||
:param bootstrap_address: oam IP for rehome
|
||||
"""
|
||||
|
@ -2449,6 +2451,7 @@ class SubcloudManager(manager.Manager):
|
|||
group_id=group_id,
|
||||
data_install=data_install,
|
||||
deploy_status=new_deploy_status,
|
||||
peer_group_id=peer_group_id,
|
||||
rehome_data=rehome_data
|
||||
)
|
||||
|
||||
|
|
|
@ -148,7 +148,7 @@ class ManagerClient(RPCClient):
|
|||
def update_subcloud(self, ctxt, subcloud_id, management_state=None,
|
||||
description=None, location=None, group_id=None,
|
||||
data_install=None, force=None,
|
||||
deploy_status=None, bootstrap_values=None, bootstrap_address=None):
|
||||
deploy_status=None, peer_group_id=None, bootstrap_values=None, bootstrap_address=None):
|
||||
return self.call(ctxt, self.make_msg('update_subcloud',
|
||||
subcloud_id=subcloud_id,
|
||||
management_state=management_state,
|
||||
|
@ -158,6 +158,7 @@ class ManagerClient(RPCClient):
|
|||
data_install=data_install,
|
||||
force=force,
|
||||
deploy_status=deploy_status,
|
||||
peer_group_id=peer_group_id,
|
||||
bootstrap_values=bootstrap_values,
|
||||
bootstrap_address=bootstrap_address))
|
||||
|
||||
|
|
|
@ -0,0 +1,332 @@
|
|||
# Copyright (c) 2023 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
import mock
|
||||
from six.moves import http_client
|
||||
|
||||
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 PostJSONMixin
|
||||
from dcmanager.tests.unit.api.v1.controllers.test_subclouds \
|
||||
import FAKE_SUBCLOUD_DATA
|
||||
from dcmanager.tests import utils
|
||||
|
||||
SAMPLE_SUBCLOUD_PEER_GROUP_NAME = 'GroupX'
|
||||
SAMPLE_SUBCLOUD_PEER_GROUP_MAX_SUBCLOUDS_REHOMING = 50
|
||||
SAMPLE_SUBCLOUD_PEER_GROUP_PIRORITY = 0
|
||||
|
||||
API_PREFIX = '/v1.0/subcloud-peer-groups'
|
||||
RESULT_KEY = 'subcloud_peer_groups'
|
||||
EXPECTED_FIELDS = ["id",
|
||||
"peer_group_name",
|
||||
"group_priority",
|
||||
"group_state",
|
||||
"max_subcloud_rehoming",
|
||||
"system_leader_id",
|
||||
"system_leader_name",
|
||||
"created-at",
|
||||
"updated-at"]
|
||||
|
||||
|
||||
class SubcloudPeerGroupAPIMixin(APIMixin):
|
||||
|
||||
def validate_entry(self, result_item):
|
||||
self.assert_fields(result_item)
|
||||
|
||||
def setUp(self):
|
||||
super(SubcloudPeerGroupAPIMixin, self).setUp()
|
||||
self.fake_rpc_client.some_method = mock.MagicMock()
|
||||
|
||||
def _get_test_subcloud_peer_group_request(self, **kw):
|
||||
# id should not be part of the structure
|
||||
group = {
|
||||
'peer-group-name': kw.get('peer_group_name', SAMPLE_SUBCLOUD_PEER_GROUP_NAME),
|
||||
'system-leader-id': kw.get(
|
||||
'system_leader_id',
|
||||
'62c9592d-f799-4db9-8d40-6786a74d6021'),
|
||||
'system-leader-name': kw.get(
|
||||
'system_leader_name',
|
||||
'dc-test'),
|
||||
'group-priority': kw.get(
|
||||
'group_priority',
|
||||
'0'),
|
||||
'group-state': kw.get(
|
||||
'group_state',
|
||||
'enabled'),
|
||||
'max-subcloud-rehoming': kw.get(
|
||||
'max_subcloud_rehoming',
|
||||
SAMPLE_SUBCLOUD_PEER_GROUP_MAX_SUBCLOUDS_REHOMING)
|
||||
}
|
||||
return group
|
||||
|
||||
def _get_test_subcloud_peer_group_dict(self, **kw):
|
||||
# id should not be part of the structure
|
||||
group = {
|
||||
'peer_group_name': kw.get('peer_group_name', SAMPLE_SUBCLOUD_PEER_GROUP_NAME),
|
||||
'system_leader_id': kw.get(
|
||||
'system_leader_id',
|
||||
'62c9592d-f799-4db9-8d40-6786a74d6021'),
|
||||
'system_leader_name': kw.get(
|
||||
'system_leader_name',
|
||||
'dc-test'),
|
||||
'group_priority': kw.get(
|
||||
'group_priority',
|
||||
'0'),
|
||||
'group_state': kw.get(
|
||||
'group_state',
|
||||
'enabled'),
|
||||
'max_subcloud_rehoming': kw.get(
|
||||
'max_subcloud_rehoming',
|
||||
SAMPLE_SUBCLOUD_PEER_GROUP_MAX_SUBCLOUDS_REHOMING)
|
||||
}
|
||||
return group
|
||||
|
||||
def _post_get_test_subcloud_peer_group(self, **kw):
|
||||
post_body = self._get_test_subcloud_peer_group_request(**kw)
|
||||
return post_body
|
||||
|
||||
# The following methods are required for subclasses of APIMixin
|
||||
def get_api_prefix(self):
|
||||
return API_PREFIX
|
||||
|
||||
def get_result_key(self):
|
||||
return RESULT_KEY
|
||||
|
||||
def get_expected_api_fields(self):
|
||||
return EXPECTED_FIELDS
|
||||
|
||||
def get_omitted_api_fields(self):
|
||||
return []
|
||||
|
||||
def _create_db_object(self, context, **kw):
|
||||
creation_fields = self._get_test_subcloud_peer_group_dict(**kw)
|
||||
return db_api.subcloud_peer_group_create(context, **creation_fields)
|
||||
|
||||
def get_post_object(self):
|
||||
return self._post_get_test_subcloud_peer_group()
|
||||
|
||||
def get_update_object(self):
|
||||
update_object = {
|
||||
'system_leader_name': 'Updated system_leader_name'
|
||||
}
|
||||
return update_object
|
||||
|
||||
|
||||
# Combine Subcloud Group API with mixins to test post, get, update and delete
|
||||
class TestSubcloudPeerGroupPost(testroot.DCManagerApiTest,
|
||||
SubcloudPeerGroupAPIMixin,
|
||||
PostJSONMixin):
|
||||
def setUp(self):
|
||||
super(TestSubcloudPeerGroupPost, 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_name_fails(self, mock_client):
|
||||
# A numerical name is not permitted. otherwise the 'get' operations
|
||||
# which support getting by either name or ID could become confused
|
||||
# if a name for one group was the same as an ID for another.
|
||||
ndict = self.get_post_object()
|
||||
ndict['peer-group-name'] = '123'
|
||||
response = self.app.post_json(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_none_string_name_fails(self, mock_client):
|
||||
# A name as 'none' not permitted.
|
||||
# None is a special word for clean a peer-group-id from subcloud.
|
||||
ndict = self.get_post_object()
|
||||
ndict['peer-group-name'] = 'none'
|
||||
response = self.app.post_json(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_name_fails(self, mock_client):
|
||||
# An empty name is not permitted
|
||||
ndict = self.get_post_object()
|
||||
ndict['peer-group-name'] = ''
|
||||
response = self.app.post_json(API_PREFIX,
|
||||
ndict,
|
||||
headers=self.get_api_headers(),
|
||||
expect_errors=True)
|
||||
self.verify_post_failure(response)
|
||||
|
||||
|
||||
class TestSubcloudPeerGroupGet(testroot.DCManagerApiTest,
|
||||
SubcloudPeerGroupAPIMixin):
|
||||
def setUp(self):
|
||||
super(TestSubcloudPeerGroupGet, self).setUp()
|
||||
# Override initial_list_size. Default group is setup during db sync
|
||||
self.initial_list_size = 1
|
||||
|
||||
@mock.patch.object(rpc_client, 'ManagerClient')
|
||||
def test_get_single_by_name(self, mock_client):
|
||||
# create a group
|
||||
context = utils.dummy_context()
|
||||
group_name = 'TestGroup'
|
||||
system_id = '0907033e-b7ec-4832-92ad-4b0913580b3b'
|
||||
self._create_db_object(
|
||||
context, peer_group_name=group_name, system_leader_id=system_id)
|
||||
|
||||
# Test that a GET operation for a valid ID works
|
||||
response = self.app.get(self.get_single_url(group_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)
|
||||
|
||||
@mock.patch.object(rpc_client, 'ManagerClient')
|
||||
def test_list_subclouds_empty(self, mock_client):
|
||||
# API GET on: subcloud-peer-groups/<uuid>/subclouds
|
||||
# create a subcloud peer group
|
||||
context = utils.dummy_context()
|
||||
group_name = 'TestGroup'
|
||||
system_id = '0907033e-b7ec-4832-92ad-4b0913580b3b'
|
||||
self._create_db_object(
|
||||
context, peer_group_name=group_name, system_leader_id=system_id)
|
||||
url = '%s/%s/subclouds' % (API_PREFIX, group_name)
|
||||
response = self.app.get(url,
|
||||
headers=self.get_api_headers())
|
||||
# This API returns 'subclouds' rather than 'subcloud-peer-groups'
|
||||
self.assertIn('subclouds', response.json)
|
||||
# no subclouds exist yet, so this length should be zero
|
||||
result_list = response.json.get('subclouds')
|
||||
self.assertEqual(0, len(result_list))
|
||||
|
||||
def _create_subcloud_db_object(self, context):
|
||||
creation_fields = {
|
||||
'name': FAKE_SUBCLOUD_DATA.get('name'),
|
||||
'description': FAKE_SUBCLOUD_DATA.get('description'),
|
||||
'location': FAKE_SUBCLOUD_DATA.get('location'),
|
||||
'software_version': FAKE_SUBCLOUD_DATA.get('software_version'),
|
||||
'management_subnet': FAKE_SUBCLOUD_DATA.get('management_subnet'),
|
||||
'management_gateway_ip':
|
||||
FAKE_SUBCLOUD_DATA.get('management_gateway_ip'),
|
||||
'management_start_ip':
|
||||
FAKE_SUBCLOUD_DATA.get('management_start_ip'),
|
||||
'management_end_ip': FAKE_SUBCLOUD_DATA.get('management_end_ip'),
|
||||
'systemcontroller_gateway_ip':
|
||||
FAKE_SUBCLOUD_DATA.get('systemcontroller_gateway_ip'),
|
||||
'deploy_status': FAKE_SUBCLOUD_DATA.get('deploy_status'),
|
||||
'error_description': FAKE_SUBCLOUD_DATA.get('error_description'),
|
||||
'openstack_installed':
|
||||
FAKE_SUBCLOUD_DATA.get('openstack_installed'),
|
||||
'group_id': FAKE_SUBCLOUD_DATA.get('group_id', 1),
|
||||
'region_name': FAKE_SUBCLOUD_DATA.get('region_name', "RegionOne")
|
||||
}
|
||||
return db_api.subcloud_create(context, **creation_fields)
|
||||
|
||||
def _update_subcloud_peer_group_id(self, ctx, subcloud, pg_id):
|
||||
return db_api.subcloud_update(ctx, subcloud.id, peer_group_id=pg_id)
|
||||
|
||||
@mock.patch.object(rpc_client, 'ManagerClient')
|
||||
def test_list_subclouds_populated(self, mock_client):
|
||||
context = utils.dummy_context()
|
||||
|
||||
# Create subcloud peer group
|
||||
group_name = 'TestGroup'
|
||||
system_id = '0907033e-b7ec-4832-92ad-4b0913580b3b'
|
||||
pg = self._create_db_object(
|
||||
context, peer_group_name=group_name, system_leader_id=system_id)
|
||||
|
||||
# Create subcloud set peer-group-id as above subcloud-peer-group
|
||||
subcloud = self._create_subcloud_db_object(context)
|
||||
self._update_subcloud_peer_group_id(context, subcloud, pg.id)
|
||||
|
||||
# API GET on: subcloud-peer-groups/<uuid>/subclouds
|
||||
url = '%s/%s/subclouds' % (API_PREFIX, pg.id)
|
||||
response = self.app.get(url,
|
||||
headers=self.get_api_headers())
|
||||
# This API returns 'subclouds' rather than 'subcloud-groups'
|
||||
self.assertIn('subclouds', response.json)
|
||||
# the subcloud created earlier will have been queried
|
||||
result_list = response.json.get('subclouds')
|
||||
self.assertEqual(1, len(result_list))
|
||||
|
||||
@mock.patch.object(rpc_client, 'ManagerClient')
|
||||
def test_get_status(self, mock_client):
|
||||
context = utils.dummy_context()
|
||||
|
||||
# Create subcloud peer group
|
||||
group_name = 'TestGroup'
|
||||
system_id = '0907033e-b7ec-4832-92ad-4b0913580b3b'
|
||||
pg = self._create_db_object(
|
||||
context, peer_group_name=group_name, system_leader_id=system_id)
|
||||
|
||||
# Create subcloud set peer-group-id as above subcloud-peer-group
|
||||
subcloud = self._create_subcloud_db_object(context)
|
||||
self._update_subcloud_peer_group_id(context, subcloud, pg.id)
|
||||
|
||||
# API GET on: subcloud-peer-groups/<uuid>/status
|
||||
url = '%s/%s/status' % (API_PREFIX, pg.id)
|
||||
response = self.app.get(url,
|
||||
headers=self.get_api_headers())
|
||||
|
||||
self.assertIn('total_subclouds', response.json)
|
||||
self.assertIn('peer_group_id', response.json)
|
||||
|
||||
|
||||
class TestSubcloudPeerGroupUpdate(testroot.DCManagerApiTest,
|
||||
SubcloudPeerGroupAPIMixin):
|
||||
def setUp(self):
|
||||
super(TestSubcloudPeerGroupUpdate, self).setUp()
|
||||
|
||||
@mock.patch.object(rpc_client, 'ManagerClient')
|
||||
def test_update_invalid_system_leader_id(self, mock_client):
|
||||
context = utils.dummy_context()
|
||||
single_obj = self._create_db_object(context)
|
||||
update_data = {
|
||||
'system_leader_id': 'not-valid-uuid'
|
||||
}
|
||||
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_max_subcloud_rehoming(self, mock_client):
|
||||
context = utils.dummy_context()
|
||||
single_obj = self._create_db_object(context)
|
||||
update_data = {
|
||||
'max_subcloud_rehoming': -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 TestSubcloudPeerGroupDelete(testroot.DCManagerApiTest,
|
||||
SubcloudPeerGroupAPIMixin):
|
||||
def setUp(self):
|
||||
super(TestSubcloudPeerGroupDelete, self).setUp()
|
||||
|
||||
@mock.patch.object(rpc_client, 'ManagerClient')
|
||||
def test_delete_success(self, mock_client):
|
||||
context = utils.dummy_context()
|
||||
single_obj = self._create_db_object(context)
|
||||
response = self.app.delete(self.get_single_url(single_obj.id),
|
||||
headers=self.get_api_headers())
|
||||
# Failures will return text rather than json
|
||||
self.assertEqual(response.content_type, 'application/json')
|
||||
self.assertEqual(response.status_code, http_client.OK)
|
|
@ -1236,6 +1236,7 @@ class TestSubcloudAPIOther(testroot.DCManagerApiTest):
|
|||
group_id=None,
|
||||
data_install=json.dumps(install_data),
|
||||
force=None,
|
||||
peer_group_id=None,
|
||||
bootstrap_values=None,
|
||||
bootstrap_address=None)
|
||||
self.assertEqual(response.status_int, 200)
|
||||
|
@ -1309,6 +1310,7 @@ class TestSubcloudAPIOther(testroot.DCManagerApiTest):
|
|||
group_id=None,
|
||||
data_install=json.dumps(install_data),
|
||||
force=None,
|
||||
peer_group_id=None,
|
||||
bootstrap_values=None,
|
||||
bootstrap_address=None)
|
||||
self.assertEqual(response.status_int, 200)
|
||||
|
@ -1348,6 +1350,7 @@ class TestSubcloudAPIOther(testroot.DCManagerApiTest):
|
|||
group_id=None,
|
||||
data_install=json.dumps(install_data),
|
||||
force=None,
|
||||
peer_group_id=None,
|
||||
bootstrap_values=None,
|
||||
bootstrap_address=None)
|
||||
self.assertEqual(response.status_int, 200)
|
||||
|
@ -1413,6 +1416,7 @@ class TestSubcloudAPIOther(testroot.DCManagerApiTest):
|
|||
group_id=None,
|
||||
data_install=None,
|
||||
force=True,
|
||||
peer_group_id=None,
|
||||
bootstrap_values=None,
|
||||
bootstrap_address=None)
|
||||
self.assertEqual(response.status_int, 200)
|
||||
|
|
|
@ -100,7 +100,7 @@ class TestDCManagerService(base.DCManagerTestCase):
|
|||
self.context, subcloud_id=1,
|
||||
management_state='testmgmtstatus')
|
||||
mock_subcloud_manager().update_subcloud.assert_called_once_with(
|
||||
self.context, 1, 'testmgmtstatus', None, None, None, None, None, None, None, None)
|
||||
self.context, 1, 'testmgmtstatus', None, None, None, None, None, None, None, None, None)
|
||||
|
||||
@mock.patch.object(service, 'SubcloudManager')
|
||||
@mock.patch.object(service, 'rpc_messaging')
|
||||
|
|
|
@ -431,6 +431,19 @@ class TestSubcloudManager(base.DCManagerTestCase):
|
|||
values.update(kwargs)
|
||||
return db_api.subcloud_create(ctxt, **values)
|
||||
|
||||
@staticmethod
|
||||
def create_subcloud_peer_group_static(ctxt, **kwargs):
|
||||
values = {
|
||||
"peer_group_name": "pgname",
|
||||
"system_leader_id": "12e0cb13-2c5c-480e-b0ea-9161fc03f3ef",
|
||||
"system_leader_name": "DC0",
|
||||
"group_priority": 0,
|
||||
"group_state": "enabled",
|
||||
"max_subcloud_rehoming": 50
|
||||
}
|
||||
values.update(kwargs)
|
||||
return db_api.subcloud_peer_group_create(ctxt, **values)
|
||||
|
||||
def test_init(self):
|
||||
sm = subcloud_manager.SubcloudManager()
|
||||
self.assertIsNotNone(sm)
|
||||
|
@ -2935,3 +2948,57 @@ class TestSubcloudManager(base.DCManagerTestCase):
|
|||
|
||||
mock_remove.assert_has_calls(calls, any_order=True)
|
||||
mock_rmtree.assert_called_with(install_dir)
|
||||
|
||||
def test_update_subcloud_peer_group_id(self):
|
||||
|
||||
subcloud = self.create_subcloud_static(
|
||||
self.ctx,
|
||||
name='subcloud1',
|
||||
deploy_status=consts.DEPLOY_STATE_DONE)
|
||||
fake_peer_group_id = 123
|
||||
|
||||
fake_dcmanager_cermon_api = FakeDCManagerNotifications()
|
||||
|
||||
p = mock.patch('dcmanager.rpc.client.DCManagerNotifications')
|
||||
mock_dcmanager_api = p.start()
|
||||
mock_dcmanager_api.return_value = fake_dcmanager_cermon_api
|
||||
|
||||
sm = subcloud_manager.SubcloudManager()
|
||||
sm.update_subcloud(self.ctx,
|
||||
subcloud.id,
|
||||
peer_group_id=fake_peer_group_id)
|
||||
|
||||
# Verify subcloud was updated with correct values
|
||||
updated_subcloud = db_api.subcloud_get_by_name(self.ctx, subcloud.name)
|
||||
self.assertEqual(fake_peer_group_id,
|
||||
updated_subcloud.peer_group_id)
|
||||
|
||||
def test_update_subcloud_peer_group_id_to_none(self):
|
||||
|
||||
subcloud = self.create_subcloud_static(
|
||||
self.ctx,
|
||||
name='subcloud1',
|
||||
deploy_status=consts.DEPLOY_STATE_DONE)
|
||||
fake_peer_group_id = 123
|
||||
|
||||
fake_dcmanager_cermon_api = FakeDCManagerNotifications()
|
||||
|
||||
p = mock.patch('dcmanager.rpc.client.DCManagerNotifications')
|
||||
mock_dcmanager_api = p.start()
|
||||
mock_dcmanager_api.return_value = fake_dcmanager_cermon_api
|
||||
|
||||
sm = subcloud_manager.SubcloudManager()
|
||||
sm.update_subcloud(self.ctx,
|
||||
subcloud.id,
|
||||
peer_group_id=fake_peer_group_id)
|
||||
# Verify subcloud was updated with correct values
|
||||
updated_subcloud = db_api.subcloud_get_by_name(self.ctx, subcloud.name)
|
||||
self.assertEqual(fake_peer_group_id,
|
||||
updated_subcloud.peer_group_id)
|
||||
sm.update_subcloud(self.ctx,
|
||||
subcloud.id,
|
||||
peer_group_id='NoNe')
|
||||
# Verify subcloud was updated to None
|
||||
updated_subcloud = db_api.subcloud_get_by_name(self.ctx, subcloud.name)
|
||||
self.assertEqual(None,
|
||||
updated_subcloud.peer_group_id)
|
||||
|
|
Loading…
Reference in New Issue