Cristian Mondo a6a6b84258 Subcloud Name Reconfiguration
This change adds the capability to rename the subcloud after
bootstrap or during subcloud rehome operation.

Added a field in the database to separate the region name
from the subcloud name.
The region name determines the subcloud reference in the
Openstack core, through which it is possible to access
the endpoints of a given subcloud. Since the region name
cannot be changed, this commit adds the ability to maintain
a unique region name based on the UUID format, and allows
subcloud renaming when necessary without any endpoint
impact.
The region is randomly generated to configure the subcloud
when it is created and only applies to future subclouds.
For those systems that have existing subclouds, the region
will be the same as on day 0, that is, region will keep the
same name as the subcloud, but subclouds can be renamed.

This topic involves changes to dcmanager, dcmanager-client
and GUI. To ensure the region name reference needed by the
cert-monitor, a mechanism to determine if the request is
coming from the cert-monitor has been created.

Usage for subcloud rename:
dcmanager subcloud update <subcloud-name> --name <new-name>

Usage for subcloud rehoming:
dcmanager subcloud add --name <subcloud-name> --migrate ...

Note: Upgrade test from StarlingX 8 -> 9 for this commit
is deferred until upgrade functionality in master is
restored. Any issue found during upgrade test will be
addressed in a separate commit

Test Plan:
PASS: Run dcmanager subcloud passing subcommands:
      - add/delete/migrate/list/show/show --detail
      - errors/manage/unmanage/reinstall/reconfig
      - update/deploy
PASS: Run dcmanager subcloud add supplying --name
      parameter and validate the operation is not allowed
PASS: Run dcmanager supplying subcommands:
      - kube/patch/prestage strategies
PASS: Run dcmanager to apply patch and remove it
PASS: Run dcmanager subcloud-backup:
      - create/delete/restore/show/upload
PASS: Run subcloud-group:
      - add/delete/list/list-subclouds/show/update
PASS: Run dcmanager subcloud strategy for:
      - patch/kubernetes/firmware
PASS: Run dcmanager subcloud update command passing --name
      parameter supplying the following values:
      - current subcloud name (not changed)
      - different existing subcloud name
PASS: Run dcmanager to migrate a subcloud passing --name
      parameter supplying a new subcloud name
PASS: Run dcmanager to migrate a subcloud without --name
      parameter
PASS: Run dcmanager to migrate a subcloud passing --name
      parameter supplying a new subcloud name and
      different subcloud name in bootstrap file
PASS: Test dcmanager API response using cURL command line
      to validate new region name field
PASS: Run full DC sanity and regression

Story: 2010788
Task: 48217

Signed-off-by: Cristian Mondo <cristian.mondo@windriver.com>
Change-Id: Id04f42504b8e325d9ec3880c240fe4a06e3a20b7
2023-09-07 10:30:06 -03:00

2339 lines
108 KiB
Python

# Copyright (c) 2017 Ericsson AB
# Copyright (c) 2017-2023 Wind River Systems, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
import base64
import copy
import json
import os
import keyring
import mock
from oslo_utils import timeutils
import six
from six.moves import http_client
from tsconfig.tsconfig import SW_VERSION
import webtest
from dccommon import consts as dccommon_consts
from dcmanager.api.controllers.v1 import phased_subcloud_deploy as psd
from dcmanager.api.controllers.v1 import subclouds
from dcmanager.common import consts
from dcmanager.common import exceptions
from dcmanager.common import phased_subcloud_deploy as psd_common
from dcmanager.common import prestage
from dcmanager.common import utils as cutils
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 PostMixin
from dcmanager.tests.unit.common import fake_subcloud
from dcmanager.tests import utils
SAMPLE_SUBCLOUD_NAME = 'SubcloudX'
SAMPLE_SUBCLOUD_DESCRIPTION = 'A Subcloud of mystery'
FAKE_ID = fake_subcloud.FAKE_ID
FAKE_URL = fake_subcloud.FAKE_URL
WRONG_URL = fake_subcloud.WRONG_URL
FAKE_HEADERS = fake_subcloud.FAKE_HEADERS
FAKE_SUBCLOUD_DATA = fake_subcloud.FAKE_SUBCLOUD_DATA
FAKE_BOOTSTRAP_VALUE = fake_subcloud.FAKE_BOOTSTRAP_VALUE
FAKE_SUBCLOUD_INSTALL_VALUES = fake_subcloud.FAKE_SUBCLOUD_INSTALL_VALUES
FAKE_SUBCLOUD_INSTALL_VALUES_WITH_PERSISTENT_SIZE = \
fake_subcloud.FAKE_SUBCLOUD_INSTALL_VALUES_WITH_PERSISTENT_SIZE
FAKE_SUBCLOUD_BOOTSTRAP_PAYLOAD = fake_subcloud.FAKE_SUBCLOUD_BOOTSTRAP_PAYLOAD
OAM_FLOATING_IP = '10.10.10.12'
FAKE_PATCH = {
"value": {
"patchstate": "Partial-Apply"
}
}
health_report_no_alarm = \
"System Health:\n \
All hosts are provisioned: [Fail]\n \
1 Unprovisioned hosts\n \
All hosts are unlocked/enabled: [OK]\n \
All hosts have current configurations: [OK]\n \
All hosts are patch current: [OK]\n \
No alarms: [OK]\n \
All kubernetes nodes are ready: [OK]\n \
All kubernetes control plane pods are ready: [OK]"
health_report_no_mgmt_alarm = \
"System Health:\n" \
"All hosts are provisioned: [OK]\n" \
"All hosts are unlocked/enabled: [OK]\n" \
"All hosts have current configurations: [OK]\n" \
"All hosts are patch current: [OK]\n" \
"Ceph Storage Healthy: [OK]\n" \
"No alarms: [Fail]\n" \
"[1] alarms found, [0] of which are management affecting\n" \
"All kubernetes nodes are ready: [OK]\n" \
"All kubernetes control plane pods are ready: [OK]"
health_report_mgmt_alarm = \
"System Health:\n" \
"All hosts are provisioned: [OK]\n" \
"All hosts are unlocked/enabled: [OK]\n" \
"All hosts have current configurations: [OK]\n" \
"All hosts are patch current: [OK]\n" \
"Ceph Storage Healthy: [OK]\n" \
"No alarms: [Fail]\n" \
"[1] alarms found, [1] of which are management affecting\n" \
"All kubernetes nodes are ready: [OK]\n" \
"All kubernetes control plane pods are ready: [OK]"
class Subcloud(object):
def __init__(self, data, is_online):
self.id = data['id']
self.name = data['name']
self.description = data['description']
self.location = data['location']
self.management_state = dccommon_consts.MANAGEMENT_UNMANAGED
if is_online:
self.availability_status = dccommon_consts.AVAILABILITY_ONLINE
else:
self.availability_status = dccommon_consts.AVAILABILITY_OFFLINE
self.deploy_status = data['deploy_status']
self.management_subnet = data['management_subnet']
self.management_gateway_ip = data['management_gateway_address']
self.management_start_ip = data['management_start_address']
self.management_end_ip = data['management_end_address']
self.external_oam_subnet = data['external_oam_subnet']
self.external_oam_gateway_address = \
data['external_oam_gateway_address']
self.external_oam_floating_address = \
data['external_oam_floating_address']
self.systemcontroller_gateway_ip = \
data['systemcontroller_gateway_address']
self.created_at = timeutils.utcnow()
self.updated_at = timeutils.utcnow()
self.data_install = ''
self.data_upgrade = ''
class FakeAddressPool(object):
def __init__(self, pool_network, pool_prefix, pool_start, pool_end):
self.network = pool_network
self.prefix = pool_prefix
range = list()
range.append(pool_start)
range.append(pool_end)
self.ranges = list()
self.ranges.append(range)
class FakeOAMAddressPool(object):
def __init__(self, oam_subnet, oam_start_ip,
oam_end_ip, oam_c1_ip,
oam_c0_ip, oam_gateway_ip,
oam_floating_ip):
self.oam_start_ip = oam_start_ip
self.oam_end_ip = oam_end_ip
self.oam_c1_ip = oam_c1_ip
self.oam_c0_ip = oam_c0_ip
self.oam_subnet = oam_subnet
self.oam_gateway_ip = oam_gateway_ip
self.oam_floating_ip = oam_floating_ip
class SubcloudAPIMixin(APIMixin):
API_PREFIX = '/v1.0/subclouds'
RESULT_KEY = 'subclouds'
# todo: populate the entire expected fields
EXPECTED_FIELDS = ['id',
'name',
'description',
'location',
'management-state',
'created-at',
'updated-at']
FAKE_BOOTSTRAP_DATA = {
"system_mode": "simplex",
"name": "fake subcloud1",
"management_subnet": "192.168.101.0/24",
"management_start_address": "192.168.101.2",
"management_end_address": "192.168.101.50",
"management_gateway_address": "192.168.101.1",
"external_oam_subnet": "10.10.10.0/24",
"external_oam_gateway_address": "10.10.10.1",
"external_oam_floating_address": "10.10.10.12",
"systemcontroller_gateway_address": "192.168.204.101",
}
OPTIONAL_BOOTSTRAP_DATA = {
"location": "fake location",
"description": "fake description",
}
# based off MANDATORY_INSTALL_VALUES
# bmc_password must be passed as a param
FAKE_INSTALL_DATA = {
"bootstrap_interface": "fake interface",
"bootstrap_address": "10.10.10.12",
"bootstrap_address_prefix": "10.10.10.12",
"bmc_address": "128.224.64.1",
"bmc_username": "fake bmc user",
"install_type": 2,
}
list_of_post_files = psd.SUBCLOUD_BOOTSTRAP_GET_FILE_CONTENTS
bootstrap_data = copy.copy(FAKE_BOOTSTRAP_DATA)
install_data = copy.copy(FAKE_INSTALL_DATA)
def setUp(self):
super(SubcloudAPIMixin, self).setUp()
def _get_test_subcloud_dict(self, **kw):
# id should not be part of the structure
subcloud = {
'name': kw.get('name', SAMPLE_SUBCLOUD_NAME),
'description': kw.get('description',
SAMPLE_SUBCLOUD_DESCRIPTION),
}
return subcloud
def _post_get_test_subcloud(self, **kw):
post_body = self._get_test_subcloud_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_subcloud_dict(**kw)
return db_api.subcloud_create(context, **creation_fields)
def get_post_params(self):
return copy.copy(FAKE_BOOTSTRAP_VALUE)
def set_list_of_post_files(self, value):
self.list_of_post_files = value
def get_post_upload_files(self):
fields = list()
for f in self.list_of_post_files:
fake_name = f + "_fake"
# The data in the bootstrap file needs to be dictionary syntax
if f == consts.BOOTSTRAP_VALUES:
fake_content = json.dumps(self.bootstrap_data).encode("utf-8")
elif f == consts.INSTALL_VALUES:
fake_content = json.dumps(self.install_data).encode("utf-8")
else:
fake_content = "fake content".encode("utf-8")
fields.append((f, fake_name, fake_content))
return fields
def get_post_object(self):
return self._post_get_test_subcloud()
def get_update_object(self):
update_object = {
'description': 'Updated description'
}
return update_object
# Combine Subcloud Group API with mixins to test post, get, update and delete
class TestSubcloudPost(testroot.DCManagerApiTest,
SubcloudAPIMixin,
PostMixin):
def setUp(self):
super(TestSubcloudPost, self).setUp()
self.list_of_post_files = psd.SUBCLOUD_BOOTSTRAP_GET_FILE_CONTENTS
self.bootstrap_data = copy.copy(self.FAKE_BOOTSTRAP_DATA)
self.install_data = copy.copy(self.FAKE_INSTALL_DATA)
self.management_address_pool = FakeAddressPool('192.168.204.0', 24,
'192.168.204.2',
'192.168.204.100')
p = mock.patch.object(psd_common, 'get_network_address_pool')
self.mock_get_network_address_pool = p.start()
self.mock_get_network_address_pool.return_value = \
self.management_address_pool
self.addCleanup(p.stop)
p = mock.patch.object(rpc_client, 'ManagerClient')
self.mock_rpc_client = p.start()
self.addCleanup(p.stop)
p = mock.patch.object(psd_common, 'get_ks_client')
self.mock_get_ks_client = p.start()
self.addCleanup(p.stop)
p = mock.patch.object(psd_common.PatchingClient, 'query')
self.mock_query = p.start()
self.addCleanup(p.stop)
p = mock.patch.object(rpc_client, 'SubcloudStateClient')
self.mock_rpc_state_client = p.start()
self.addCleanup(p.stop)
def _verify_post_failure(self, response, param, value):
self.assertEqual(http_client.BAD_REQUEST,
response.status_code,
message=("%s=%s returned %s instead of %s"
% (param,
value,
response.status_code,
http_client.BAD_REQUEST)))
# Note: response failures return 'text' rather than json
self.assertEqual('text/plain', response.content_type)
def _verify_post_success(self, response):
self.assertEqual(http_client.OK, response.status_code)
self.assertEqual('application/json', response.content_type)
self.assert_fields(response.json)
def test_post_subcloud_wrong_url(self):
"""Test POST operation rejected when going to the wrong URL."""
params = self.get_post_params()
upload_files = self.get_post_upload_files()
six.assertRaisesRegex(self,
webtest.app.AppError,
"404 *",
self.app.post,
WRONG_URL,
params=params,
upload_files=upload_files,
headers=self.get_api_headers())
def test_post_no_body(self):
"""Test POST operation with nearly everything wrong with it."""
six.assertRaisesRegex(self,
webtest.app.AppError,
"400 *",
self.app.post,
self.get_api_prefix(),
params={},
headers=self.get_api_headers())
def test_post_subcloud_boostrap_entries_missing(self):
"""Test POST operation with some mandatory boostrap fields missing.
Example: name is a required field
"""
self.list_of_post_files = psd.SUBCLOUD_BOOTSTRAP_GET_FILE_CONTENTS
params = self.get_post_params()
for key in self.FAKE_BOOTSTRAP_DATA:
self.bootstrap_data = copy.copy(self.FAKE_BOOTSTRAP_DATA)
del self.bootstrap_data[key]
upload_files = self.get_post_upload_files()
response = self.app.post(self.get_api_prefix(),
params=params,
upload_files=upload_files,
headers=self.get_api_headers(),
expect_errors=True)
self._verify_post_failure(response, key, None)
# try with nothing removed and verify it works
self.bootstrap_data = copy.copy(self.FAKE_BOOTSTRAP_DATA)
upload_files = self.get_post_upload_files()
response = self.app.post(self.get_api_prefix(),
params=params,
upload_files=upload_files,
headers=self.get_api_headers())
self._verify_post_success(response)
def _test_post_param_inputs(self, param_key, bad_values, good_value):
upload_files = self.get_post_upload_files()
params = self.get_post_params()
# Test all the bad param values
for bad_value in bad_values:
params[param_key] = bad_value
response = self.app.post(self.get_api_prefix(),
params=params,
upload_files=upload_files,
headers=self.get_api_headers(),
expect_errors=True)
self._verify_post_failure(response, param_key, bad_value)
# Test that a good value will work
params[param_key] = good_value
response = self.app.post(self.get_api_prefix(),
params=params,
upload_files=upload_files,
headers=self.get_api_headers())
self._verify_post_success(response)
def test_post_subcloud_bad_bootstrap_address(self):
"""Test POST operation with a bad bootstrap-address"""
param_key = "bootstrap-address"
# bootstrap-address must be valid IP address
bad_values = ["10.10.10.wut", # including letters in the IP
"10.10.10.276" # 276 is invalid
]
good_values = "10.10.10.3"
self._test_post_param_inputs(param_key,
bad_values,
good_values)
def test_post_subcloud_bad_IPv6_bootstrap_address(self):
"""Test POST operation with a bad bootstrap-address"""
param_key = "bootstrap-address"
# bootstrap-address must be valid IP address
bad_values = ["2620::10a:a103::1135", # more than one double colons
"2620:10a:a001:a103::wut", # invalid letter
"2620:10a:a001:a103:1135" # Incomplete IP
]
good_values = "2620:10a:a001:a103::1135"
self._test_post_param_inputs(param_key,
bad_values,
good_values)
def test_post_subcloud_bad_gateway(self):
"""Test POST with an invalid gateway."""
param_key = "systemcontroller_gateway_address"
# systemcontroller_gateway_address must be appropriate address within
# the management address pool which is
# 192.168.204.0/24 greater than 100
bad_values = ["192.168.205.101", # 205.xx not in the pool
"192.168.204.99", # 99 is reserved in the pool
"192.168.276.276", # 276 is not a valid IP address
"192.168.206.wut", # including letters in the IP
"192.168.204", # incomplete IP
]
good_value = "192.168.204.101"
self._test_post_param_inputs(param_key,
bad_values,
good_value)
def test_post_subcloud_bad_subnet(self):
"""Test POST with an invalid subnet."""
param_key = "management_subnet"
bad_values = ["192.168.101.0/32", # /32 would be just one IP
"192.168.101.0/33", # /33 is an invalid CIDR
"192.168.276.0/24", # 276 makes no sense as an IP
"192.168.206.wut/24", # including letters in the IP
"192.168.204/24", # incomplete CIDR
]
good_value = "192.168.101.0/24"
self._test_post_param_inputs(param_key,
bad_values,
good_value)
def test_post_subcloud_bad_start_ip(self):
"""Test POST with an invalid management_start_address.
The management_start_address cannot be after the end or too close
since there must be enough range to allocate the IPs.
"""
param_key = "management_start_address"
# subnet is 192.168.101.0/24
# end address is 192.168.101.50
bad_values = ["192.168.100.2", # xx.xx.100.xx is not in the subnet
"192.168.101.51", # start is higher than end
"192.168.101.48", # start is too close to end
"192.168.276.0", # 276 makes no sense as an IP
"192.168.206.wut", # including letters in the IP
"192.168.204", # incomplete IP
]
good_value = "192.168.101.2"
self._test_post_param_inputs(param_key,
bad_values,
good_value)
def test_post_subcloud_bad_end_ip(self):
"""Test POST with an invalid management_end_address.
The management_end_address cannot be less than the start or too close
since there must be enough range to allocate the IPs.
"""
param_key = "management_end_address"
# subnet is 192.168.101.0/24
# start address is 192.168.101.2
bad_values = ["192.168.100.50", # xx.xx.100.xx is not in the subnet
"192.168.101.1", # end is less than start
"192.168.101.4", # end is too close to start
"192.168.276.50", # 276 makes no sense as an IP
"192.168.206.wut", # including letters in the IP
"192.168.204", # incomplete IP
]
good_value = "192.168.101.50"
self._test_post_param_inputs(param_key,
bad_values,
good_value)
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_install_values(self, mock_vault_files):
"""Test POST operation with install values is supported by the API."""
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
# pass a different "install" list of files for this POST
self.set_list_of_post_files(subclouds.SUBCLOUD_ADD_GET_FILE_CONTENTS)
upload_files = self.get_post_upload_files()
params = self.get_post_params()
# add bmc_password to params
params.update(
{'bmc_password':
base64.b64encode('fake pass'.encode("utf-8")).decode("utf-8")})
response = self.app.post(self.get_api_prefix(),
params=params,
upload_files=upload_files,
headers=self.get_api_headers())
self._verify_post_success(response)
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_without_release_parameter(self, mock_vault_files):
"""Test POST operation without release parameter."""
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
self.set_list_of_post_files(subclouds.SUBCLOUD_ADD_GET_FILE_CONTENTS)
upload_files = self.get_post_upload_files()
params = self.get_post_params()
# add bmc_password to params
params.update(
{'bmc_password':
base64.b64encode('fake pass'.encode("utf-8")).decode("utf-8")})
response = self.app.post(self.get_api_prefix(),
params=params,
upload_files=upload_files,
headers=self.get_api_headers())
self._verify_post_success(response)
# Verify that the subcloud installed with the active release
# when no release parameter provided.
self.assertEqual(SW_VERSION, response.json['software-version'])
def test_post_subcloud_release_not_match_install_values_sw(self):
"""Release parameter not match software_version in the install_values."""
self.set_list_of_post_files(subclouds.SUBCLOUD_ADD_GET_FILE_CONTENTS)
upload_files = self.get_post_upload_files()
params = self.get_post_params()
# add bmc_password and release to params
params.update(
{'bmc_password':
base64.b64encode('fake pass'.encode("utf-8")).decode("utf-8"),
'release': '21.12'})
response = self.app.post(self.get_api_prefix(),
params=params,
upload_files=upload_files,
headers=self.get_api_headers(),
expect_errors=True)
# Verify the request was rejected
self.assertEqual(response.status_code, http_client.BAD_REQUEST)
@mock.patch.object(psd_common, 'validate_k8s_version')
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_with_release_parameter(self, mock_vault_files,
mock_validate_k8s_version):
"""Test POST operation with release parameter."""
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
software_version = '21.12'
# Update the software_version value to match the release parameter value,
# otherwise, the request will be rejected
self.install_data['software_version'] = software_version
self.set_list_of_post_files(subclouds.SUBCLOUD_ADD_GET_FILE_CONTENTS)
upload_files = self.get_post_upload_files()
params = self.get_post_params()
# add bmc_password and release to params
params.update(
{'bmc_password':
base64.b64encode('fake pass'.encode("utf-8")).decode("utf-8"),
'release': software_version})
response = self.app.post(self.get_api_prefix(),
params=params,
upload_files=upload_files,
headers=self.get_api_headers(),
expect_errors=True)
self.assertEqual(response.status_code, http_client.OK)
self.assertEqual(software_version, response.json['software-version'])
# Revert the software_version value
self.install_data['software_version'] = SW_VERSION
@mock.patch.object(psd_common.PatchingClient, 'query')
def test_post_subcloud_when_partial_applied_patch(self, mock_query):
"""Test POST operation when there is a partial-applied patch."""
upload_files = self.get_post_upload_files()
params = self.get_post_params()
mock_query.return_value = FAKE_PATCH
response = self.app.post(self.get_api_prefix(),
params=params,
upload_files=upload_files,
headers=self.get_api_headers(),
expect_errors=True)
self.assertEqual(http_client.UNPROCESSABLE_ENTITY, response.status_code)
self.assertEqual('text/plain', response.content_type)
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_install_values_no_bmc_password(self, mock_vault_files):
"""Test POST operation with install values is supported by the API."""
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
# pass a different "install" list of files for this POST
self.set_list_of_post_files(subclouds.SUBCLOUD_ADD_GET_FILE_CONTENTS)
upload_files = self.get_post_upload_files()
params = self.get_post_params()
# for this unit test, omit adding bmc_password to params
response = self.app.post(self.get_api_prefix(),
params=params,
upload_files=upload_files,
headers=self.get_api_headers(),
expect_errors=True)
self._verify_post_failure(response, "bmc_password", None)
# add the bmc_password and verify that now it works
params.update(
{'bmc_password':
base64.b64encode('fake pass'.encode("utf-8")).decode("utf-8")})
response = self.app.post(self.get_api_prefix(),
params=params,
upload_files=upload_files,
headers=self.get_api_headers())
self._verify_post_success(response)
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_missing_image(self, mock_vault_files):
"""Test POST operation without image in install values and vault files."""
mock_vault_files.return_value = (None, None)
params = self.get_post_params()
# add bmc_password to params
params.update(
{'bmc_password':
base64.b64encode('fake pass'.encode("utf-8")).decode("utf-8")})
self.set_list_of_post_files(subclouds.SUBCLOUD_ADD_GET_FILE_CONTENTS)
self.install_data = copy.copy(self.FAKE_INSTALL_DATA)
upload_files = self.get_post_upload_files()
response = self.app.post(self.get_api_prefix(),
params=params,
upload_files=upload_files,
headers=self.get_api_headers(),
expect_errors=True)
self.assertEqual(response.status_code, http_client.BAD_REQUEST)
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_install_values_missing(self, mock_vault_files):
"""Test POST operation with install values fails if data missing."""
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
params = self.get_post_params()
# add bmc_password to params
params.update(
{'bmc_password':
base64.b64encode('fake pass'.encode("utf-8")).decode("utf-8")})
self.set_list_of_post_files(subclouds.SUBCLOUD_ADD_GET_FILE_CONTENTS)
# for each entry in install content, try with one key missing
for key in self.FAKE_INSTALL_DATA:
self.install_data = copy.copy(self.FAKE_INSTALL_DATA)
del self.install_data[key]
upload_files = self.get_post_upload_files()
response = self.app.post(self.get_api_prefix(),
params=params,
upload_files=upload_files,
headers=self.get_api_headers(),
expect_errors=True)
self._verify_post_failure(response, key, None)
@mock.patch('dcmanager.common.utils.get_vault_load_files')
@mock.patch.object(cutils, 'get_playbook_for_software_version')
@mock.patch.object(cutils, 'get_value_from_yaml_file')
def test_post_subcloud_bad_kubernetes_version(self,
mock_get_value_from_yaml_file,
mock_get_playbook_for_software_version,
mock_vault_files):
"""Test POST operation with bad kubernetes_version."""
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
software_version = '21.12'
# Update the software_version value to match the release parameter value,
# otherwise, the request will be rejected
self.install_data['software_version'] = software_version
params = self.get_post_params()
# add bmc_password to params
params.update(
{'bmc_password':
base64.b64encode('fake pass'.encode("utf-8")).decode("utf-8"),
'release': software_version})
# Add kubernetes version to bootstrap_data
self.bootstrap_data['kubernetes_version'] = '1.21.8'
mock_get_value_from_yaml_file.return_value = '1.23.1'
self.set_list_of_post_files(subclouds.SUBCLOUD_ADD_GET_FILE_CONTENTS)
self.install_data = copy.copy(self.FAKE_INSTALL_DATA)
upload_files = self.get_post_upload_files()
response = self.app.post(self.get_api_prefix(),
params=params,
upload_files=upload_files,
headers=self.get_api_headers(),
expect_errors=True)
self.assertEqual(response.status_code, http_client.BAD_REQUEST)
# Revert the change of bootstrap_data
del self.bootstrap_data['kubernetes_version']
def _test_post_input_value_inputs(self,
setup_overrides,
required_overrides,
param_key, bad_values, good_value):
"""This utility checks for test permutions.
The setup_overrides are the initial modifications to the install data
The required_overrides are all tested to see that if any of them are
missing, the 'good' value will not work.
The param_key is tested with the list of bad_values to ensure they fail
The param_key is tested with the good value to ensure it passes.
"""
params = self.get_post_params()
params.update(
{'bmc_password':
base64.b64encode('fake pass'.encode("utf-8")).decode("utf-8")})
self.set_list_of_post_files(subclouds.SUBCLOUD_ADD_GET_FILE_CONTENTS)
# Setup starting install data
# Note: upload_files are populated based on the install values data.
starting_data = copy.copy(self.FAKE_INSTALL_DATA)
for key, val in setup_overrides.items():
starting_data[key] = val
starting_data['image'] = 'fake image'
# Test all the bad param values
for bad_value in bad_values:
self.install_data = copy.copy(starting_data)
# Apply all required_overrides
for key, val in required_overrides.items():
self.install_data[key] = val
# Apply the bad value
self.install_data[param_key] = bad_value
upload_files = self.get_post_upload_files()
response = self.app.post(self.get_api_prefix(),
params=params,
upload_files=upload_files,
headers=self.get_api_headers(),
expect_errors=True)
self._verify_post_failure(response, param_key, bad_value)
# Test that any missing override required to use with the good value
# will cause a failure
for missing_override in required_overrides:
self.install_data = copy.copy(starting_data)
# We cannot simply delete the missing override, but we can skip it
for key, val in required_overrides.items():
if key != missing_override:
self.install_data[key] = val
# The 'good' value should still fail if a required override missing
self.install_data[param_key] = good_value
upload_files = self.get_post_upload_files()
response = self.app.post(self.get_api_prefix(),
params=params,
upload_files=upload_files,
headers=self.get_api_headers(),
expect_errors=True)
self._verify_post_failure(response, param_key, bad_value)
# Test that a good value and all required overrides works
self.install_data = copy.copy(starting_data)
for key, val in required_overrides.items():
self.install_data[key] = val
self.install_data[param_key] = good_value
upload_files = self.get_post_upload_files()
response = self.app.post(self.get_api_prefix(),
params=params,
upload_files=upload_files,
headers=self.get_api_headers())
self._verify_post_success(response)
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_install_values_invalid_type(self, mock_vault_files):
"""Test POST with an invalid type specified in install values."""
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
setup_overrides = {}
required_overrides = {}
# the install_type must a number 0 <= X <=5
install_key = "install_type"
bad_values = [-1, # negative
6, # too big
"3", # alphbetical
"w", # really alphbetical
"", # empty
None, # None
]
good_value = 3
self._test_post_input_value_inputs(setup_overrides, required_overrides,
install_key, bad_values, good_value)
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_install_bad_bootstrap_ip(self, mock_vault_files):
"""Test POST with invalid boostrap ip specified in install values."""
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
setup_overrides = {}
required_overrides = {}
install_key = "bootstrap_address"
bad_values = ["192.168.1.256", # 256 is not valid
"192.168.206.wut", # including letters in the IP
None, # None
]
# Note: an incomplete IP address is 10.10.10 is considered valid
good_value = "10.10.10.12"
self._test_post_input_value_inputs(setup_overrides, required_overrides,
install_key, bad_values, good_value)
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_install_bad_bmc_ip(self, mock_vault_files):
"""Test POST with invalid bmc ip specified in install values."""
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
setup_overrides = {}
required_overrides = {}
install_key = "bmc_address"
bad_values = ["128.224.64.256", # 256 is not valid
"128.224.64.wut", # including letters in the IP
None, # None
]
good_value = "128.224.64.1"
self._test_post_input_value_inputs(setup_overrides, required_overrides,
install_key, bad_values, good_value)
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_install_bad_persistent_size(self, mock_vault_files):
"""Test POST with invalid persistent_size specified in install values."""
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
setup_overrides = {}
required_overrides = {}
install_key = "persistent_size"
bad_values = ["4000o", # not an integer
"20000", # less than 30000
40000.1, # fraction
None, # None
]
good_value = 40000
self._test_post_input_value_inputs(setup_overrides, required_overrides,
install_key, bad_values, good_value)
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_install_bad_nexthop_gateway(self, mock_vault_files):
"""Test POST with invalid nexthop_gateway in install values."""
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
setup_overrides = {}
required_overrides = {}
# nexthop_gateway is not required. but if provided, it must be valid
install_key = "nexthop_gateway"
bad_values = ["128.224.64.256", # 256 is not valid
"128.224.64.wut", # including letters in the IP
None, # None
]
good_value = "192.168.1.2"
self._test_post_input_value_inputs(setup_overrides, required_overrides,
install_key, bad_values, good_value)
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_install_bad_network_address(self, mock_vault_files):
"""Test POST with invalid network_address in install values."""
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
setup_overrides = {}
# The nexthop_gateway is required when network_address is present
# The network mask is required when network address is present
required_overrides = {
"nexthop_gateway": "192.168.1.2",
"network_mask": 32, # Note: this netmask is validated when used
}
# network_address is not required. but if provided, it must be valid
install_key = "network_address"
# todo(abailey): None will cause the API to fail
bad_values = ["fd01:6::0", # mis-match ipv6 vs ipv4
]
good_value = "192.168.101.10" # ipv4
self._test_post_input_value_inputs(setup_overrides, required_overrides,
install_key, bad_values, good_value)
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_install_bad_network_mask(self, mock_vault_files):
"""Test POST with invalid network_mask in install values."""
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
# network_address is not required. but if provided a valid network_mask
# is needed
setup_overrides = {
"nexthop_gateway": "192.168.1.2",
"network_address": "192.168.101.10"
}
required_overrides = {}
install_key = "network_mask"
bad_values = [None, # None
64, # network_mask cannot really be greater than 32
-1, # network_mask cannot really be negative
"junk", # network_mask cannot be a junk string
]
good_value = 32
self._test_post_input_value_inputs(setup_overrides, required_overrides,
install_key, bad_values, good_value)
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_install_diff_bmc_ip_version(self, mock_vault_files):
"""Test POST install values with mismatched(ipv4/ipv6) bmc ip."""
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
setup_overrides = {
"bootstrap_address": "192.168.1.2"
}
required_overrides = {}
# bootstrap address ip version must match bmc_address. default ipv4
install_key = "bmc_address"
bad_values = ["fd01:6::7", # ipv6
None, # None
"192.168.-1.1", # bad ipv4
]
good_value = "192.168.1.7" # ipv4
self._test_post_input_value_inputs(setup_overrides, required_overrides,
install_key, bad_values, good_value)
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_install_diff_bmc_ip_version_ipv6(self, mock_vault_files):
"""Test POST install values with mismatched(ipv6/ipv4) bmc ip."""
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
# version of bootstrap address must be same as bmc_address
setup_overrides = {
"bootstrap_address": "fd01:6::7"
}
required_overrides = {}
install_key = "bmc_address"
bad_values = ["192.168.1.7", # ipv4
None, # None
"fd01:6:-1", # bad ipv6
]
good_value = "fd01:6::7" # ipv6
self._test_post_input_value_inputs(setup_overrides, required_overrides,
install_key, bad_values, good_value)
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_install_diff_nexthop_ip_version(self, mock_vault_files):
"""Test POST install values mismatched(ipv4/ipv6) nexthop_gateway."""
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
# ip version of bootstrap address must be same as nexthop_gateway
# All required addresses (like bmc address) much match bootstrap
# default bmc address is ipv4
setup_overrides = {
"bootstrap_address": "192.168.1.5"
}
required_overrides = {}
install_key = "nexthop_gateway"
bad_values = ["fd01:6::7", ] # ipv6
good_value = "192.168.1.7" # ipv4
self._test_post_input_value_inputs(setup_overrides, required_overrides,
install_key, bad_values, good_value)
@mock.patch('dcmanager.common.utils.get_vault_load_files')
def test_post_subcloud_install_diff_nexthop_ip_version_ipv6(self,
mock_vault_files):
"""Test POST install values with mismatched(ipv6/ipv4) bmc ip."""
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
# version of bootstrap address must be same as nexthop_gateway
# All required addresses must also be setup ipv6 such as bmc_address
# default bmc address is ipv4
setup_overrides = {
"bootstrap_address": "fd01:6::6"
}
required_overrides = {
"bmc_address": "fd01:6::7"
}
install_key = "nexthop_gateway"
bad_values = ["192.168.1.7", ] # ipv4
good_value = "fd01:6::8" # ipv6
self._test_post_input_value_inputs(setup_overrides, required_overrides,
install_key, bad_values, good_value)
class TestSubcloudAPIOther(testroot.DCManagerApiTest):
"""Test GET, delete and patch API calls"""
def setUp(self):
super(TestSubcloudAPIOther, self).setUp()
self.ctx = utils.dummy_context()
p = mock.patch.object(rpc_client, 'SubcloudStateClient')
self.mock_rpc_state_client = p.start()
self.addCleanup(p.stop)
p = mock.patch.object(rpc_client, 'ManagerClient')
self.mock_rpc_client = p.start()
self.addCleanup(p.stop)
p = mock.patch.object(psd_common, 'get_ks_client')
self.mock_get_ks_client = p.start()
self.addCleanup(p.stop)
def test_delete_subcloud(self):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
delete_url = FAKE_URL + '/' + str(subcloud.id)
self.mock_rpc_client().delete_subcloud.return_value = True
response = self.app.delete_json(delete_url, headers=FAKE_HEADERS)
self.mock_rpc_client().delete_subcloud.assert_called_once_with(
mock.ANY, mock.ANY)
self.assertEqual(response.status_int, 200)
def test_delete_wrong_request(self):
delete_url = WRONG_URL + '/' + FAKE_ID
six.assertRaisesRegex(self, webtest.app.AppError, "404 *",
self.app.delete_json, delete_url,
headers=FAKE_HEADERS)
@mock.patch.object(subclouds.SubcloudsController, '_get_oam_addresses')
def test_get_subcloud(self,
mock_get_oam_addresses):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
get_url = FAKE_URL + '/' + str(subcloud.id)
response = self.app.get(get_url, headers=FAKE_HEADERS)
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.status_code, http_client.OK)
self.assertEqual(response.json.get('oam_floating_ip', None), None)
self.assertEqual(response.json['name'], subcloud.name)
@mock.patch.object(subclouds.SubcloudsController,
'_get_deploy_config_sync_status')
@mock.patch.object(subclouds.SubcloudsController, '_get_oam_addresses')
def test_get_online_subcloud_with_additional_detail(self,
mock_get_oam_addresses,
mock_get_deploy_config_sync_status):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
updated_subcloud = db_api.subcloud_update(
self.ctx, subcloud.id, availability_status=dccommon_consts.AVAILABILITY_ONLINE)
get_url = FAKE_URL + '/' + str(updated_subcloud.id) + '/detail'
oam_addresses = FakeOAMAddressPool('10.10.10.254',
'10.10.10.1',
'10.10.10.254',
'10.10.10.4',
'10.10.10.3',
'10.10.10.1',
'10.10.10.2')
mock_get_oam_addresses.return_value = oam_addresses
mock_get_deploy_config_sync_status.return_value = dccommon_consts.DEPLOY_CONFIG_UP_TO_DATE
response = self.app.get(get_url, headers=FAKE_HEADERS)
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.status_code, http_client.OK)
self.assertEqual('10.10.10.2', response.json['oam_floating_ip'])
self.assertEqual(
'Deployment: configurations up-to-date', response.json['deploy_config_sync_status'])
def test_get_offline_subcloud_with_additional_detail(self):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
get_url = FAKE_URL + '/' + str(subcloud.id) + '/detail'
response = self.app.get(get_url, headers=FAKE_HEADERS)
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.status_code, http_client.OK)
self.assertEqual('unavailable', response.json['oam_floating_ip'])
self.assertEqual('unknown', response.json['deploy_config_sync_status'])
@mock.patch.object(subclouds.SubcloudsController,
'_get_deploy_config_sync_status')
@mock.patch.object(subclouds.SubcloudsController, '_get_oam_addresses')
def test_get_subcloud_deploy_config_status_unknown(self,
mock_get_oam_addresses,
mock_get_deploy_config_sync_status):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
updated_subcloud = db_api.subcloud_update(
self.ctx, subcloud.id, availability_status=dccommon_consts.AVAILABILITY_ONLINE)
get_url = FAKE_URL + '/' + str(updated_subcloud.id) + '/detail'
mock_get_oam_addresses.return_value = None
mock_get_deploy_config_sync_status.return_value = None
response = self.app.get(get_url, headers=FAKE_HEADERS)
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.status_code, http_client.OK)
self.assertEqual('unknown', response.json['deploy_config_sync_status'])
@mock.patch.object(subclouds.SubcloudsController, '_get_oam_addresses')
def test_get_subcloud_oam_ip_unavailable(self, mock_get_oam_addresses):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
updated_subcloud = db_api.subcloud_update(
self.ctx, subcloud.id, availability_status=dccommon_consts.AVAILABILITY_ONLINE)
get_url = FAKE_URL + '/' + str(updated_subcloud.id) + '/detail'
self.mock_get_ks_client.return_value = 'ks_client'
mock_get_oam_addresses.return_value = None
response = self.app.get(get_url, headers=FAKE_HEADERS)
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.status_code, http_client.OK)
self.assertEqual('unavailable', response.json['oam_floating_ip'])
def test_get_wrong_request(self):
get_url = WRONG_URL + '/' + FAKE_ID
six.assertRaisesRegex(self, webtest.app.AppError, "404 *",
self.app.get, get_url,
headers=FAKE_HEADERS)
def test_get_subcloud_all(self):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
get_url = FAKE_URL
response = self.app.get(get_url, headers=FAKE_HEADERS)
self.assertEqual(response.json['subclouds'][0]['name'], subcloud.name)
@mock.patch.object(subclouds.SubcloudsController, '_get_patch_data')
def test_patch_subcloud(self, mock_get_patch_data):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
data = {'management-state': dccommon_consts.MANAGEMENT_UNMANAGED}
self.mock_rpc_client().update_subcloud.return_value = True
mock_get_patch_data.return_value = data
response = self.app.patch_json(FAKE_URL + '/' + str(subcloud.id),
headers=FAKE_HEADERS,
params=data)
self.assertEqual(response.status_int, 200)
# Verify subcloud was updated with correct values
updated_subcloud = db_api.subcloud_get_by_name(self.ctx, subcloud.name)
self.assertEqual(dccommon_consts.MANAGEMENT_UNMANAGED,
updated_subcloud.management_state)
@mock.patch.object(subclouds.SubcloudsController, '_get_patch_data')
def test_update_subcloud_group_value(self, mock_get_patch_data):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
good_values = [1, "1"]
expected_group_id = 1
for x in good_values:
data = {'group_id': x}
self.mock_rpc_client().update_subcloud.return_value = True
mock_get_patch_data.return_value = data
response = self.app.patch_json(FAKE_URL + '/' + str(subcloud.id),
headers=FAKE_HEADERS,
params=data)
self.assertEqual(response.status_int, 200)
# Verify subcloud was updated with correct values
updated_subcloud = db_api.subcloud_get_by_name(self.ctx,
subcloud.name)
self.assertEqual(expected_group_id,
updated_subcloud.group_id)
@mock.patch.object(subclouds.SubcloudsController, '_get_patch_data')
def test_update_subcloud_group_value_by_name(self, mock_get_patch_data):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
expected_group_id = 1
data = {'group_id': 'Default'}
self.mock_rpc_client().update_subcloud.return_value = True
mock_get_patch_data.return_value = data
response = self.app.patch_json(FAKE_URL + '/' + str(subcloud.id),
headers=FAKE_HEADERS,
params=data)
self.assertEqual(response.status_int, 200)
# Verify subcloud was updated with correct values
updated_subcloud = db_api.subcloud_get_by_name(self.ctx, subcloud.name)
self.assertEqual(expected_group_id,
updated_subcloud.group_id)
@mock.patch.object(subclouds.SubcloudsController, '_get_patch_data')
def test_update_subcloud_group_bad_value(self, mock_get_patch_data):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
# There is only 1 subcloud group 'Default' which has id '1'
# This should test that boolean, zero, negative, float and bad values
# all get rejected
bad_values = [0, -1, 2, "0", "-1", 0.5, "BadName", "False", "True"]
for x in bad_values:
data = {'group_id': x}
self.mock_rpc_client().update_subcloud.return_value = True
mock_get_patch_data.return_value = data
response = self.app.patch_json(FAKE_URL + '/' + str(subcloud.id),
headers=FAKE_HEADERS,
params=data,
expect_errors=True)
self.assertEqual(response.status_int, 400)
@mock.patch.object(subclouds.SubcloudsController, '_get_patch_data')
@mock.patch.object(cutils, 'get_vault_load_files')
def test_update_subcloud_install_values_persistent_size(self, mock_vault_files,
mock_get_patch_data):
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
subcloud = fake_subcloud.create_fake_subcloud(self.ctx, data_install=None)
payload = {}
install_data = copy.copy(FAKE_SUBCLOUD_INSTALL_VALUES_WITH_PERSISTENT_SIZE)
encoded_password = base64.b64encode(
'bmc_password'.encode("utf-8")).decode('utf-8')
data = {'bmc_password': encoded_password}
payload.update({'install_values': install_data})
payload.update(data)
self.mock_rpc_client().update_subcloud.return_value = True
mock_get_patch_data.return_value = payload
fake_content = "fake content".encode("utf-8")
response = self.app.patch(FAKE_URL + '/' + str(subcloud.id),
headers=FAKE_HEADERS,
params=data,
upload_files=[("install_values",
"fake_name",
fake_content)])
install_data.update({'bmc_password': encoded_password})
self.mock_rpc_client().update_subcloud.assert_called_once_with(
mock.ANY,
subcloud.id,
management_state=None,
description=None,
location=None,
group_id=None,
data_install=json.dumps(install_data),
force=None,
bootstrap_values=None,
bootstrap_address=None)
self.assertEqual(response.status_int, 200)
@mock.patch.object(psd_common, 'get_network_address_pool')
@mock.patch.object(subclouds.SubcloudsController,
'_validate_network_reconfiguration')
@mock.patch.object(subclouds.SubcloudsController, '_get_patch_data')
def test_patch_subcloud_network_values(
self, mock_get_patch_data, mock_validate_network_reconfiguration,
mock_mgmt_address_pool):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
db_api.subcloud_update(
self.ctx, subcloud.id,
availability_status=dccommon_consts.AVAILABILITY_ONLINE)
fake_password = (
base64.b64encode('testpass'.encode("utf-8"))).decode('ascii')
payload = {'sysadmin_password': fake_password,
'bootstrap_address': "192.168.102.2",
'management_subnet': "192.168.102.0/24",
'management_start_ip': "192.168.102.5",
'management_end_ip': "192.168.102.49",
'management_gateway_ip': "192.168.102.1"}
fake_management_address_pool = FakeAddressPool('192.168.204.0', 24,
'192.168.204.2',
'192.168.204.100')
mock_mgmt_address_pool.return_value = fake_management_address_pool
self.mock_rpc_client().update_subcloud_with_network_reconfig.return_value = True
mock_get_patch_data.return_value = payload
response = self.app.patch_json(FAKE_URL + '/' + str(subcloud.id),
headers=FAKE_HEADERS,
params=payload)
self.assertEqual(response.status_int, 200)
mock_validate_network_reconfiguration.assert_called_once()
self.mock_rpc_client().update_subcloud_with_network_reconfig.\
assert_called_once_with(mock.ANY, subcloud.id, payload)
self.assertEqual(response.status_int, 200)
@mock.patch.object(subclouds.SubcloudsController, '_get_patch_data')
@mock.patch.object(cutils, 'get_vault_load_files')
def test_patch_subcloud_install_values(self, mock_vault_files,
mock_get_patch_data):
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
subcloud = fake_subcloud.create_fake_subcloud(self.ctx, data_install=None)
payload = {}
install_data = copy.copy(FAKE_SUBCLOUD_INSTALL_VALUES)
encoded_password = base64.b64encode(
'bmc_password'.encode("utf-8")).decode('utf-8')
data = {'bmc_password': encoded_password}
payload.update({'install_values': install_data})
payload.update(data)
self.mock_rpc_client().update_subcloud.return_value = True
mock_get_patch_data.return_value = payload
fake_content = "fake content".encode("utf-8")
response = self.app.patch(FAKE_URL + '/' + str(subcloud.id),
headers=FAKE_HEADERS,
params=data,
upload_files=[("install_values",
"fake_name",
fake_content)])
install_data.update({'bmc_password': encoded_password})
self.mock_rpc_client().update_subcloud.assert_called_once_with(
mock.ANY,
subcloud.id,
management_state=None,
description=None,
location=None,
group_id=None,
data_install=json.dumps(install_data),
force=None,
bootstrap_values=None,
bootstrap_address=None)
self.assertEqual(response.status_int, 200)
@mock.patch.object(subclouds.SubcloudsController, '_get_patch_data')
@mock.patch.object(cutils, 'get_vault_load_files')
def test_patch_subcloud_install_values_with_existing_data_install(
self, mock_vault_files, mock_get_patch_data):
mock_vault_files.return_value = ('fake_iso', 'fake_sig')
install_data = copy.copy(FAKE_SUBCLOUD_INSTALL_VALUES)
subcloud = fake_subcloud.create_fake_subcloud(
self.ctx, data_install=json.dumps(install_data))
install_data.update({"install_type": 2})
payload = {}
encoded_password = base64.b64encode(
'bmc_password'.encode("utf-8")).decode('utf-8')
data = {'bmc_password': encoded_password}
payload.update({'install_values': install_data})
payload.update(data)
self.mock_rpc_client().update_subcloud.return_value = True
mock_get_patch_data.return_value = payload
fake_content = "fake content".encode("utf-8")
response = self.app.patch(FAKE_URL + '/' + str(subcloud.id),
headers=FAKE_HEADERS,
params=data,
upload_files=[("install_values",
"fake_name",
fake_content)])
install_data.update({'bmc_password': encoded_password})
self.mock_rpc_client().update_subcloud.assert_called_once_with(
mock.ANY,
subcloud.id,
management_state=None,
description=None,
location=None,
group_id=None,
data_install=json.dumps(install_data),
force=None,
bootstrap_values=None,
bootstrap_address=None)
self.assertEqual(response.status_int, 200)
@mock.patch.object(subclouds.SubcloudsController, '_get_patch_data')
def test_patch_subcloud_no_body(self, mock_get_patch_data):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
data = {}
mock_get_patch_data.return_value = data
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.patch_json,
FAKE_URL + '/' + str(subcloud.id),
headers=FAKE_HEADERS, params=data)
@mock.patch.object(subclouds.SubcloudsController, '_get_patch_data')
def test_patch_subcloud_bad_status(self, mock_get_patch_data):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
data = {'management-state': 'bad-status'}
mock_get_patch_data.return_value = data
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.patch_json,
FAKE_URL + '/' + str(subcloud.id),
headers=FAKE_HEADERS, params=data)
@mock.patch.object(subclouds.SubcloudsController, '_get_patch_data')
def test_patch_subcloud_bad_force_value(self, mock_get_patch_data):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
data = {'management-state': dccommon_consts.MANAGEMENT_MANAGED,
'force': 'bad-value'}
mock_get_patch_data.return_value = data
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.patch_json,
FAKE_URL + '/' + str(subcloud.id),
headers=FAKE_HEADERS, params=data)
@mock.patch.object(subclouds.SubcloudsController, '_get_patch_data')
def test_patch_subcloud_forced_unmanaged(self, mock_get_patch_data):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
data = {'management-state': dccommon_consts.MANAGEMENT_UNMANAGED,
'force': True}
mock_get_patch_data.return_value = data
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.patch_json,
FAKE_URL + '/' + str(subcloud.id),
headers=FAKE_HEADERS, params=data)
@mock.patch.object(subclouds.SubcloudsController, '_get_patch_data')
def test_patch_subcloud_forced_manage(self, mock_get_patch_data):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
payload = {'management-state': dccommon_consts.MANAGEMENT_MANAGED,
'force': True}
self.mock_rpc_client().update_subcloud.return_value = True
mock_get_patch_data.return_value = payload
response = self.app.patch_json(FAKE_URL + '/' + str(subcloud.id),
headers=FAKE_HEADERS,
params=payload)
self.mock_rpc_client().update_subcloud.assert_called_once_with(
mock.ANY,
mock.ANY,
management_state=dccommon_consts.MANAGEMENT_MANAGED,
description=None,
location=None,
group_id=None,
data_install=None,
force=True,
bootstrap_values=None,
bootstrap_address=None)
self.assertEqual(response.status_int, 200)
@mock.patch.object(subclouds.SubcloudsController, '_get_updatestatus_payload')
def test_subcloud_updatestatus(self, mock_get_updatestatus_payload):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
data = {'endpoint': 'dc-cert', 'status': 'in-sync'}
mock_get_updatestatus_payload.return_value = data
self.mock_rpc_state_client().update_subcloud_endpoint_status.return_value = True
response = self.app.patch_json(
FAKE_URL + '/' + str(subcloud.id) + '/update_status',
data, headers=FAKE_HEADERS)
self.mock_rpc_state_client().update_subcloud_endpoint_status.\
assert_called_once_with(mock.ANY, subcloud.name, subcloud.region_name,
'dc-cert', 'in-sync')
self.assertEqual(response.status_int, 200)
@mock.patch.object(subclouds.SubcloudsController, '_get_updatestatus_payload')
def test_subcloud_updatestatus_invalid_endpoint(
self, mock_get_updatestatus_payload):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
data = {'endpoint': 'any-other-endpoint', 'status': 'in-sync'}
mock_get_updatestatus_payload.return_value = data
self.mock_rpc_client().update_subcloud_endpoint_status.return_value = True
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.patch_json, FAKE_URL + '/' +
str(subcloud.id) + '/update_status',
headers=FAKE_HEADERS, params=data)
self.mock_rpc_client().update_subcloud_endpoint_status.assert_not_called()
@mock.patch.object(subclouds.SubcloudsController, '_get_updatestatus_payload')
def test_subcloud_updatestatus_invalid_status(
self, mock_get_updatestatus_payload):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
data = {'endpoint': 'dc-cert', 'status': 'not-sure'}
mock_get_updatestatus_payload.return_value = data
self.mock_rpc_client().update_subcloud_endpoint_status.return_value = True
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.patch_json, FAKE_URL + '/' +
str(subcloud.id) + '/update_status',
headers=FAKE_HEADERS, params=data)
self.mock_rpc_client().update_subcloud_endpoint_status.assert_not_called()
def test_get_config_file_path(self):
bootstrap_file = psd_common.get_config_file_path("subcloud1")
install_values = psd_common.get_config_file_path("subcloud1",
"install_values")
deploy_config = psd_common.get_config_file_path("subcloud1",
consts.DEPLOY_CONFIG)
self.assertEqual(bootstrap_file,
f'{dccommon_consts.ANSIBLE_OVERRIDES_PATH}/subcloud1.yml')
self.assertEqual(install_values,
f'{dccommon_consts.ANSIBLE_OVERRIDES_PATH}/subcloud1/install_values.yml')
self.assertEqual(deploy_config,
f'{dccommon_consts.ANSIBLE_OVERRIDES_PATH}/subcloud1_deploy_config.yml')
def test_format_ip_address(self):
fake_payload = {}
good_values = {
'10.10.10.3': '10.10.10.3',
'2620:10a:a001:a103::1135': '2620:10a:a001:a103::1135',
'2620:10A:A001:A103::1135': '2620:10a:a001:a103::1135', # with upper case letters
'2620:010a:a001:a103::1135': '2620:10a:a001:a103::1135', # with leading zeros
'2620:10a:a001:a103:0000::1135': '2620:10a:a001:a103::1135' # with a string of zeros
}
for k, v in good_values.items():
fake_payload['bootstrap-address'] = k
psd_common.format_ip_address(fake_payload)
self.assertEqual(fake_payload['bootstrap-address'], v)
fake_payload[consts.INSTALL_VALUES] = {}
for k, v in good_values.items():
fake_payload[consts.INSTALL_VALUES]['bmc_address'] = k
psd_common.format_ip_address(fake_payload)
self.assertEqual(fake_payload[consts.INSTALL_VALUES]['bmc_address'], v)
fake_payload['othervalues1'] = 'othervalues1'
fake_payload[consts.INSTALL_VALUES]['othervalues2'] = 'othervalues2'
psd_common.format_ip_address(fake_payload)
self.assertEqual(fake_payload['othervalues1'], 'othervalues1')
self.assertEqual(fake_payload[consts.INSTALL_VALUES]['othervalues2'], 'othervalues2')
def test_get_subcloud_db_install_values(self):
install_data = copy.copy(FAKE_SUBCLOUD_INSTALL_VALUES)
encoded_password = base64.b64encode(
'bmc_password'.encode("utf-8")).decode('utf-8')
install_data['bmc_password'] = encoded_password
test_subcloud = copy.copy(FAKE_SUBCLOUD_DATA)
subcloud_info = Subcloud(test_subcloud, False)
subcloud_info.data_install = json.dumps(install_data)
actual_result = psd_common.get_subcloud_db_install_values(subcloud_info)
self.assertEqual(
json.loads(json.dumps(install_data)),
json.loads(json.dumps(actual_result)))
@mock.patch.object(keyring, 'get_password')
def test_get_subcloud_db_install_values_without_bmc_password(
self, mock_keyring):
install_data = copy.copy(FAKE_SUBCLOUD_INSTALL_VALUES)
subcloud = fake_subcloud.create_fake_subcloud(
self.ctx, data_install=json.dumps(install_data))
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.patch_json, FAKE_URL + '/' +
str(subcloud.id) + '/redeploy',
headers=FAKE_HEADERS)
@mock.patch.object(psd_common, 'upload_config_file')
@mock.patch.object(psd_common.PatchingClient, 'query')
@mock.patch.object(os.path, 'isdir')
@mock.patch.object(os, 'listdir')
@mock.patch.object(cutils, 'get_vault_load_files')
@mock.patch.object(psd_common, 'validate_k8s_version')
@mock.patch.object(psd_common, 'validate_subcloud_config')
@mock.patch.object(psd_common, 'validate_bootstrap_values')
def test_redeploy_subcloud(
self, mock_validate_bootstrap_values, mock_validate_subcloud_config,
mock_validate_k8s_version, mock_get_vault_load_files,
mock_os_listdir, mock_os_isdir, mock_query, mock_upload_config_file):
fake_bmc_password = base64.b64encode(
'bmc_password'.encode("utf-8")).decode('utf-8')
fake_sysadmin_password = base64.b64encode(
'sysadmin_password'.encode("utf-8")).decode('utf-8')
install_data = copy.copy(FAKE_SUBCLOUD_INSTALL_VALUES)
install_data.pop('software_version')
bootstrap_data = copy.copy(fake_subcloud.FAKE_BOOTSTRAP_FILE_DATA)
config_data = {'deploy_config': 'deploy config values'}
redeploy_data = {**install_data, **bootstrap_data, **config_data,
'sysadmin_password': fake_sysadmin_password,
'bmc_password': fake_bmc_password}
subcloud = fake_subcloud.create_fake_subcloud(
self.ctx, name=bootstrap_data["name"])
mock_query.return_value = {}
mock_get_vault_load_files.return_value = ('iso_file_path', 'sig_file_path')
mock_os_isdir.return_value = True
mock_upload_config_file.return_value = True
mock_os_listdir.return_value = ['deploy_chart_fake.tgz',
'deploy_overrides_fake.yaml',
'deploy_playbook_fake.yaml']
upload_files = [("install_values", "install_fake_filename",
json.dumps(install_data).encode("utf-8")),
("bootstrap_values", "bootstrap_fake_filename",
json.dumps(bootstrap_data).encode("utf-8")),
("deploy_config", "config_fake_filename",
json.dumps(config_data).encode("utf-8"))]
response = self.app.patch(
FAKE_URL + '/' + str(subcloud.id) + '/redeploy',
headers=FAKE_HEADERS, params=redeploy_data,
upload_files=upload_files)
mock_validate_bootstrap_values.assert_called_once()
mock_validate_subcloud_config.assert_called_once()
mock_validate_k8s_version.assert_called_once()
self.mock_rpc_client().redeploy_subcloud.assert_called_once_with(
mock.ANY,
subcloud.id,
mock.ANY)
self.assertEqual(response.status_int, 200)
self.assertEqual(SW_VERSION, response.json['software-version'])
@mock.patch.object(cutils, 'load_yaml_file')
@mock.patch.object(psd_common.PatchingClient, 'query')
@mock.patch.object(os.path, 'exists')
@mock.patch.object(os.path, 'isdir')
@mock.patch.object(os, 'listdir')
@mock.patch.object(cutils, 'get_vault_load_files')
@mock.patch.object(psd_common, 'validate_k8s_version')
def test_redeploy_subcloud_no_request_data(
self, mock_validate_k8s_version, mock_get_vault_load_files,
mock_os_listdir, mock_os_isdir, mock_path_exists, mock_query,
mock_load_yaml):
fake_bmc_password = base64.b64encode(
'bmc_password'.encode("utf-8")).decode('utf-8')
fake_sysadmin_password = base64.b64encode(
'sysadmin_password'.encode("utf-8")).decode('utf-8')
install_data = copy.copy(FAKE_SUBCLOUD_INSTALL_VALUES)
install_data.pop('software_version')
install_data['bmc_password'] = fake_bmc_password
redeploy_data = {'sysadmin_password': fake_sysadmin_password}
subcloud = fake_subcloud.create_fake_subcloud(
self.ctx, name=fake_subcloud.FAKE_BOOTSTRAP_FILE_DATA["name"],
data_install=json.dumps(install_data))
config_file = psd_common.get_config_file_path(subcloud.name,
consts.DEPLOY_CONFIG)
mock_query.return_value = {}
mock_get_vault_load_files.return_value = ('iso_file_path', 'sig_file_path')
mock_os_isdir.return_value = True
mock_os_listdir.return_value = ['deploy_chart_fake.tgz',
'deploy_overrides_fake.yaml',
'deploy_playbook_fake.yaml']
mock_path_exists.side_effect = lambda x: True if x == config_file else False
mock_load_yaml.return_value = {"software_version": SW_VERSION}
response = self.app.patch(
FAKE_URL + '/' + str(subcloud.id) + '/redeploy',
headers=FAKE_HEADERS, params=redeploy_data)
mock_validate_k8s_version.assert_called_once()
self.mock_rpc_client().redeploy_subcloud.assert_called_once_with(
mock.ANY,
subcloud.id,
mock.ANY)
self.assertEqual(response.status_int, 200)
self.assertEqual(SW_VERSION, response.json['software-version'])
@mock.patch.object(psd_common, 'upload_config_file')
@mock.patch.object(psd_common.PatchingClient, 'query')
@mock.patch.object(os.path, 'isdir')
@mock.patch.object(os, 'listdir')
@mock.patch.object(cutils, 'get_vault_load_files')
@mock.patch.object(psd_common, 'validate_k8s_version')
@mock.patch.object(psd_common, 'validate_subcloud_config')
@mock.patch.object(psd_common, 'validate_bootstrap_values')
def test_redeploy_subcloud_with_release_version(
self, mock_validate_bootstrap_values, mock_validate_subcloud_config,
mock_validate_k8s_version, mock_get_vault_load_files,
mock_os_listdir, mock_os_isdir, mock_query, mock_upload_config_file):
fake_bmc_password = base64.b64encode(
'bmc_password'.encode("utf-8")).decode('utf-8')
fake_sysadmin_password = base64.b64encode(
'sysadmin_password'.encode("utf-8")).decode('utf-8')
install_data = copy.copy(FAKE_SUBCLOUD_INSTALL_VALUES)
install_data.pop('software_version')
bootstrap_data = copy.copy(fake_subcloud.FAKE_BOOTSTRAP_FILE_DATA)
config_data = {'deploy_config': 'deploy config values'}
redeploy_data = {**install_data, **bootstrap_data, **config_data,
'sysadmin_password': fake_sysadmin_password,
'bmc_password': fake_bmc_password,
'release': fake_subcloud.FAKE_SOFTWARE_VERSION}
subcloud = fake_subcloud.create_fake_subcloud(
self.ctx, name=bootstrap_data["name"],
software_version=SW_VERSION)
mock_query.return_value = {}
mock_get_vault_load_files.return_value = ('iso_file_path', 'sig_file_path')
mock_os_isdir.return_value = True
mock_upload_config_file.return_value = True
mock_os_listdir.return_value = ['deploy_chart_fake.tgz',
'deploy_overrides_fake.yaml',
'deploy_playbook_fake.yaml']
upload_files = [("install_values", "install_fake_filename",
json.dumps(install_data).encode("utf-8")),
("bootstrap_values", "bootstrap_fake_filename",
json.dumps(bootstrap_data).encode("utf-8")),
("deploy_config", "config_fake_filename",
json.dumps(config_data).encode("utf-8"))]
response = self.app.patch(
FAKE_URL + '/' + str(subcloud.id) + '/redeploy',
headers=FAKE_HEADERS, params=redeploy_data,
upload_files=upload_files)
mock_validate_bootstrap_values.assert_called_once()
mock_validate_subcloud_config.assert_called_once()
mock_validate_k8s_version.assert_called_once()
self.mock_rpc_client().redeploy_subcloud.assert_called_once_with(
mock.ANY,
subcloud.id,
mock.ANY)
self.assertEqual(response.status_int, 200)
self.assertEqual(fake_subcloud.FAKE_SOFTWARE_VERSION,
response.json['software-version'])
@mock.patch.object(cutils, 'load_yaml_file')
@mock.patch.object(psd_common.PatchingClient, 'query')
@mock.patch.object(os.path, 'exists')
@mock.patch.object(os.path, 'isdir')
@mock.patch.object(os, 'listdir')
@mock.patch.object(cutils, 'get_vault_load_files')
def test_redeploy_subcloud_no_request_body(
self, mock_get_vault_load_files, mock_os_listdir,
mock_os_isdir, mock_path_exists, mock_query, mock_load_yaml):
fake_bmc_password = base64.b64encode(
'bmc_password'.encode("utf-8")).decode('utf-8')
install_data = copy.copy(FAKE_SUBCLOUD_INSTALL_VALUES)
install_data.pop('software_version')
install_data['bmc_password'] = fake_bmc_password
subcloud = fake_subcloud.create_fake_subcloud(
self.ctx, name=fake_subcloud.FAKE_BOOTSTRAP_FILE_DATA["name"],
data_install=json.dumps(install_data))
config_file = psd_common.get_config_file_path(subcloud.name,
consts.DEPLOY_CONFIG)
mock_query.return_value = {}
mock_get_vault_load_files.return_value = ('iso_file_path', 'sig_file_path')
mock_os_isdir.return_value = True
mock_os_listdir.return_value = ['deploy_chart_fake.tgz',
'deploy_overrides_fake.yaml',
'deploy_playbook_fake.yaml']
mock_path_exists.side_effect = lambda x: True if x == config_file else False
mock_load_yaml.return_value = {"software_version": SW_VERSION}
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.patch_json, FAKE_URL + '/' +
str(subcloud.id) + '/redeploy',
headers=FAKE_HEADERS, params={})
def test_redeploy_online_subcloud(self):
subcloud = fake_subcloud.create_fake_subcloud(
self.ctx, name=fake_subcloud.FAKE_BOOTSTRAP_FILE_DATA["name"])
db_api.subcloud_update(self.ctx, subcloud.id,
availability_status=dccommon_consts.AVAILABILITY_ONLINE)
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.patch_json, FAKE_URL + '/' +
str(subcloud.id) + '/redeploy',
headers=FAKE_HEADERS, params={})
self.mock_rpc_client().redeploy_subcloud.assert_not_called()
def test_redeploy_managed_subcloud(self):
subcloud = fake_subcloud.create_fake_subcloud(
self.ctx, name=fake_subcloud.FAKE_BOOTSTRAP_FILE_DATA["name"])
db_api.subcloud_update(self.ctx, subcloud.id,
management_state=dccommon_consts.MANAGEMENT_MANAGED)
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.patch_json, FAKE_URL + '/' +
str(subcloud.id) + '/redeploy',
headers=FAKE_HEADERS, params={})
self.mock_rpc_client().redeploy_subcloud.assert_not_called()
@mock.patch.object(cutils, 'load_yaml_file')
@mock.patch.object(psd_common.PatchingClient, 'query')
@mock.patch.object(os.path, 'exists')
@mock.patch.object(os.path, 'isdir')
@mock.patch.object(os, 'listdir')
@mock.patch.object(cutils, 'get_vault_load_files')
@mock.patch.object(psd_common, 'validate_k8s_version')
def test_redeploy_subcloud_missing_required_value(
self, mock_validate_k8s_version, mock_get_vault_load_files,
mock_os_listdir, mock_os_isdir, mock_path_exists, mock_query,
mock_load_yaml):
fake_bmc_password = base64.b64encode(
'bmc_password'.encode("utf-8")).decode('utf-8')
fake_sysadmin_password = base64.b64encode(
'sysadmin_password'.encode("utf-8")).decode('utf-8')
install_data = copy.copy(FAKE_SUBCLOUD_INSTALL_VALUES)
install_data.pop('software_version')
install_data['bmc_password'] = fake_bmc_password
subcloud = fake_subcloud.create_fake_subcloud(
self.ctx, name=fake_subcloud.FAKE_BOOTSTRAP_FILE_DATA["name"],
data_install=json.dumps(install_data))
config_file = psd_common.get_config_file_path(subcloud.name,
consts.DEPLOY_CONFIG)
mock_query.return_value = {}
mock_get_vault_load_files.return_value = ('iso_file_path', 'sig_file_path')
mock_os_isdir.return_value = True
mock_os_listdir.return_value = ['deploy_chart_fake.tgz',
'deploy_overrides_fake.yaml',
'deploy_playbook_fake.yaml']
mock_path_exists.side_effect = lambda x: True if x == config_file else False
mock_load_yaml.return_value = {"software_version": SW_VERSION}
for k in ['name', 'system_mode', 'external_oam_subnet',
'external_oam_gateway_address', 'external_oam_floating_address',
'sysadmin_password']:
bootstrap_values = copy.copy(fake_subcloud.FAKE_BOOTSTRAP_FILE_DATA)
redeploy_data = {**bootstrap_values,
'sysadmin_password': fake_sysadmin_password}
del redeploy_data[k]
upload_files = [("bootstrap_values", "bootstrap_fake_filename",
json.dumps(redeploy_data).encode("utf-8"))]
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.patch_json, FAKE_URL + '/' +
str(subcloud.id) + '/redeploy',
headers=FAKE_HEADERS, params=redeploy_data,
upload_files=upload_files)
@mock.patch.object(psd_common, 'upload_config_file')
@mock.patch.object(psd_common.PatchingClient, 'query')
@mock.patch.object(os.path, 'isdir')
@mock.patch.object(os, 'listdir')
@mock.patch.object(cutils, 'get_vault_load_files')
@mock.patch.object(psd_common, 'validate_k8s_version')
@mock.patch.object(psd_common, 'validate_subcloud_config')
@mock.patch.object(psd_common, 'validate_bootstrap_values')
def test_redeploy_subcloud_missing_stored_values(
self, mock_validate_bootstrap_values, mock_validate_subcloud_config,
mock_validate_k8s_version, mock_get_vault_load_files,
mock_os_listdir, mock_os_isdir, mock_query, mock_upload_config_values):
fake_bmc_password = base64.b64encode(
'bmc_password'.encode("utf-8")).decode('utf-8')
fake_sysadmin_password = base64.b64encode(
'sysadmin_password'.encode("utf-8")).decode('utf-8')
install_data = copy.copy(FAKE_SUBCLOUD_INSTALL_VALUES)
install_data.pop('software_version')
bootstrap_data = copy.copy(fake_subcloud.FAKE_BOOTSTRAP_FILE_DATA)
config_data = {'deploy_config': 'deploy config values'}
for k in ['management_subnet', 'management_start_address',
'management_end_address', 'management_gateway_address',
'systemcontroller_gateway_address']:
del bootstrap_data[k]
redeploy_data = {**install_data, **bootstrap_data, **config_data,
'sysadmin_password': fake_sysadmin_password,
'bmc_password': fake_bmc_password}
subcloud = fake_subcloud.create_fake_subcloud(
self.ctx, name=bootstrap_data["name"])
mock_query.return_value = {}
mock_get_vault_load_files.return_value = ('iso_file_path', 'sig_file_path')
mock_os_isdir.return_value = True
mock_upload_config_values.return_value = True
mock_os_listdir.return_value = ['deploy_chart_fake.tgz',
'deploy_overrides_fake.yaml',
'deploy_playbook_fake.yaml']
upload_files = [("install_values", "install_fake_filename",
json.dumps(install_data).encode("utf-8")),
("bootstrap_values", "bootstrap_fake_filename",
json.dumps(bootstrap_data).encode("utf-8")),
("deploy_config", "config_fake_filename",
json.dumps(config_data).encode("utf-8"))]
response = self.app.patch(
FAKE_URL + '/' + str(subcloud.id) + '/redeploy',
headers=FAKE_HEADERS, params=redeploy_data,
upload_files=upload_files)
mock_validate_bootstrap_values.assert_called_once()
mock_validate_subcloud_config.assert_called_once()
mock_validate_k8s_version.assert_called_once()
self.mock_rpc_client().redeploy_subcloud.assert_called_once_with(
mock.ANY,
subcloud.id,
mock.ANY)
self.assertEqual(response.status_int, 200)
self.assertEqual(SW_VERSION, response.json['software-version'])
@mock.patch.object(prestage, '_get_system_controller_upgrades')
@mock.patch.object(prestage, '_get_prestage_subcloud_info')
@mock.patch.object(subclouds.SubcloudsController, '_get_prestage_payload')
def test_prestage_subcloud_validate_detailed(self, mock_get_prestage_payload,
mock_prestage_subcloud_info,
mock_controller_upgrade):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
subcloud = db_api.subcloud_update(
self.ctx, subcloud.id, availability_status=dccommon_consts.AVAILABILITY_ONLINE,
management_state=dccommon_consts.MANAGEMENT_MANAGED)
fake_password = (base64.b64encode('testpass'.encode("utf-8"))).decode('ascii')
data = {'sysadmin_password': fake_password,
'force': False}
mock_controller_upgrade.return_value = list()
mock_prestage_subcloud_info.return_value = consts.SYSTEM_MODE_SIMPLEX, \
health_report_no_alarm, \
OAM_FLOATING_IP
self.mock_rpc_client().prestage_subcloud.return_value = True
mock_get_prestage_payload.return_value = data
response = self.app.patch_json(FAKE_URL + '/' + str(subcloud.id) +
'/prestage',
headers=FAKE_HEADERS,
params=data)
self.mock_rpc_client().prestage_subcloud.assert_called_once_with(
mock.ANY,
mock.ANY)
self.assertEqual(response.status_int, 200)
@mock.patch.object(cutils, 'get_systemcontroller_installed_loads')
@mock.patch.object(prestage, '_get_system_controller_upgrades')
@mock.patch.object(subclouds.SubcloudsController, '_get_prestage_payload')
def test_prestage_subcloud_invalid_release(self, mock_get_prestage_payload,
mock_controller_upgrade,
mock_installed_loads):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
subcloud = db_api.subcloud_update(
self.ctx, subcloud.id,
availability_status=dccommon_consts.AVAILABILITY_ONLINE,
management_state=dccommon_consts.MANAGEMENT_MANAGED)
fake_release = '21.12'
mock_installed_loads.return_value = ['22.12']
fake_password = (base64.b64encode('testpass'.encode("utf-8"))). \
decode('ascii')
data = {'sysadmin_password': fake_password,
'force': False,
'release': fake_release}
mock_controller_upgrade.return_value = list()
self.mock_rpc_client().prestage_subcloud.return_value = True
mock_get_prestage_payload.return_value = data
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.patch_json, FAKE_URL + '/' +
str(subcloud.id) + '/prestage',
headers=FAKE_HEADERS, params=data)
@mock.patch.object(subclouds.SubcloudsController, '_get_prestage_payload')
@mock.patch.object(prestage, '_get_system_controller_upgrades')
def test_prestage_subcloud_unmanaged(self, mock_controller_upgrade,
mock_get_prestage_payload):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
subcloud = db_api.subcloud_update(
self.ctx, subcloud.id, availability_status=dccommon_consts.AVAILABILITY_ONLINE,
management_state=dccommon_consts.MANAGEMENT_UNMANAGED)
fake_password = (base64.b64encode('testpass'.encode("utf-8"))).decode('ascii')
data = {'sysadmin_password': fake_password}
mock_controller_upgrade.return_value = list()
self.mock_rpc_client().prestage_subcloud.return_value = True
mock_get_prestage_payload.return_value = data
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.patch_json, FAKE_URL + '/' +
str(subcloud.id) + '/prestage',
headers=FAKE_HEADERS, params=data)
@mock.patch.object(subclouds.SubcloudsController, '_get_prestage_payload')
@mock.patch.object(prestage, '_get_system_controller_upgrades')
def test_prestage_subcloud_offline(self, mock_controller_upgrade,
mock_get_prestage_payload):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
subcloud = db_api.subcloud_update(
self.ctx, subcloud.id, availability_status=dccommon_consts.AVAILABILITY_OFFLINE,
management_state=dccommon_consts.MANAGEMENT_MANAGED)
fake_password = (base64.b64encode('testpass'.encode("utf-8"))).decode('ascii')
data = {'sysadmin_password': fake_password}
mock_controller_upgrade.return_value = list()
self.mock_rpc_client().prestage_subcloud.return_value = True
mock_get_prestage_payload.return_value = data
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.patch_json, FAKE_URL + '/' +
str(subcloud.id) + '/prestage',
headers=FAKE_HEADERS, params=data)
def test_prestage_subcloud_backup_in_progress(self):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
subcloud = db_api.subcloud_update(
self.ctx,
subcloud.id,
availability_status=dccommon_consts.AVAILABILITY_ONLINE,
deploy_status=consts.DEPLOY_STATE_DONE,
management_state=dccommon_consts.MANAGEMENT_MANAGED,
backup_status=consts.BACKUP_STATE_IN_PROGRESS)
self.assertRaises(exceptions.PrestagePreCheckFailedException,
prestage.initial_subcloud_validate,
subcloud,
[fake_subcloud.FAKE_SOFTWARE_VERSION],
fake_subcloud.FAKE_SOFTWARE_VERSION)
@mock.patch.object(cutils, 'get_systemcontroller_installed_loads')
@mock.patch.object(prestage, '_get_system_controller_upgrades')
@mock.patch.object(prestage, '_get_prestage_subcloud_info')
@mock.patch.object(subclouds.SubcloudsController, '_get_prestage_payload')
def test_prestage_subcloud_duplex(self, mock_get_prestage_payload,
mock_prestage_subcloud_info,
mock_controller_upgrade,
mock_installed_loads):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
subcloud = db_api.subcloud_update(
self.ctx, subcloud.id, availability_status=dccommon_consts.AVAILABILITY_ONLINE,
management_state=dccommon_consts.MANAGEMENT_MANAGED)
fake_release = '21.12'
mock_installed_loads.return_value = [fake_release]
fake_password = (base64.b64encode('testpass'.encode("utf-8"))).\
decode('ascii')
data = {'sysadmin_password': fake_password,
'force': False}
mock_controller_upgrade.return_value = list()
mock_prestage_subcloud_info.return_value = consts.SYSTEM_MODE_DUPLEX, \
health_report_no_alarm, \
OAM_FLOATING_IP
self.mock_rpc_client().prestage_subcloud.return_value = True
mock_get_prestage_payload.return_value = data
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.patch_json, FAKE_URL + '/' +
str(subcloud.id) + '/prestage',
headers=FAKE_HEADERS, params=data)
@mock.patch.object(prestage, '_get_system_controller_upgrades')
@mock.patch.object(prestage, '_get_prestage_subcloud_info')
@mock.patch.object(subclouds.SubcloudsController, '_get_prestage_payload')
def test_prestage_subcloud_non_mgmt_alarm(self, mock_get_prestage_payload,
mock_prestage_subcloud_info,
mock_controller_upgrade):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
subcloud = db_api.subcloud_update(
self.ctx, subcloud.id, availability_status=dccommon_consts.AVAILABILITY_ONLINE,
management_state=dccommon_consts.MANAGEMENT_MANAGED)
fake_password = (base64.b64encode('testpass'.encode("utf-8"))).decode('ascii')
data = {'sysadmin_password': fake_password,
'force': False}
mock_controller_upgrade.return_value = list()
mock_prestage_subcloud_info.return_value = consts.SYSTEM_MODE_SIMPLEX, \
health_report_no_mgmt_alarm, \
OAM_FLOATING_IP
self.mock_rpc_client().prestage_subcloud.return_value = True
mock_get_prestage_payload.return_value = data
response = self.app.patch_json(FAKE_URL + '/' + str(subcloud.id) +
'/prestage',
headers=FAKE_HEADERS,
params=data)
self.mock_rpc_client().prestage_subcloud.assert_called_once_with(
mock.ANY,
mock.ANY)
self.assertEqual(response.status_int, 200)
@mock.patch.object(prestage, '_get_system_controller_upgrades')
@mock.patch.object(prestage, '_get_prestage_subcloud_info')
@mock.patch.object(subclouds.SubcloudsController, '_get_prestage_payload')
def test_prestage_subcloud_mgmt_alarm(self, mock_get_prestage_payload,
mock_prestage_subcloud_info,
mock_controller_upgrade):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
subcloud = db_api.subcloud_update(
self.ctx, subcloud.id, availability_status=dccommon_consts.AVAILABILITY_ONLINE,
management_state=dccommon_consts.MANAGEMENT_MANAGED)
fake_password = (base64.b64encode('testpass'.encode("utf-8"))).decode('ascii')
data = {'sysadmin_password': fake_password,
'force': False}
mock_controller_upgrade.return_value = list()
mock_prestage_subcloud_info.return_value = consts.SYSTEM_MODE_SIMPLEX, \
health_report_mgmt_alarm, \
OAM_FLOATING_IP
self.mock_rpc_client().prestage_subcloud.return_value = True
mock_get_prestage_payload.return_value = data
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.patch_json, FAKE_URL + '/' +
str(subcloud.id) + '/prestage',
headers=FAKE_HEADERS, params=data)
@mock.patch.object(prestage, '_get_system_controller_upgrades')
@mock.patch.object(prestage, '_get_prestage_subcloud_info')
@mock.patch.object(subclouds.SubcloudsController, '_get_prestage_payload')
def test_prestage_subcloud_mgmt_alarm_force(self, mock_get_prestage_payload,
mock_prestage_subcloud_info,
mock_controller_upgrade):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
subcloud = db_api.subcloud_update(
self.ctx, subcloud.id, availability_status=dccommon_consts.AVAILABILITY_ONLINE,
management_state=dccommon_consts.MANAGEMENT_MANAGED)
fake_password = (base64.b64encode('testpass'.encode("utf-8"))).decode('ascii')
data = {'sysadmin_password': fake_password,
'force': True}
mock_controller_upgrade.return_value = list()
mock_prestage_subcloud_info.return_value = consts.SYSTEM_MODE_SIMPLEX, \
health_report_mgmt_alarm, \
OAM_FLOATING_IP
self.mock_rpc_client().prestage_subcloud.return_value = True
mock_get_prestage_payload.return_value = data
response = self.app.patch_json(FAKE_URL + '/' + str(subcloud.id) +
'/prestage',
headers=FAKE_HEADERS,
params=data)
self.mock_rpc_client().prestage_subcloud.assert_called_once_with(
mock.ANY,
mock.ANY)
self.assertEqual(response.status_int, 200)
@mock.patch.object(prestage, '_get_system_controller_upgrades')
@mock.patch.object(prestage, '_get_prestage_subcloud_info')
@mock.patch.object(subclouds.SubcloudsController, '_get_prestage_payload')
def test_prestage_subcloud_not_allowed_state(self, mock_get_prestage_payload,
mock_prestage_subcloud_info,
mock_controller_upgrade):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
subcloud = db_api.subcloud_update(self.ctx, subcloud.id,
availability_status=dccommon_consts.AVAILABILITY_ONLINE,
management_state=dccommon_consts.MANAGEMENT_MANAGED,
deploy_status='NotAllowedState')
fake_password = (base64.b64encode('testpass'.encode("utf-8"))).decode('ascii')
data = {'sysadmin_password': fake_password,
'force': False}
mock_controller_upgrade.return_value = list()
mock_prestage_subcloud_info.return_value = consts.SYSTEM_MODE_SIMPLEX, \
health_report_no_alarm, \
OAM_FLOATING_IP
self.mock_rpc_client().prestage_subcloud.return_value = True
mock_get_prestage_payload.return_value = data
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.patch_json, FAKE_URL + '/' +
str(subcloud.id) + '/prestage',
headers=FAKE_HEADERS, params=data)
@mock.patch.object(prestage, '_get_system_controller_upgrades')
@mock.patch.object(subclouds.SubcloudsController, '_get_prestage_payload')
def test_prestage_subcloud_controller_upgrading(self, mock_get_prestage_payload,
mock_controller_upgrade):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
fake_password = (base64.b64encode('testpass'.encode("utf-8"))).decode('ascii')
data = {'sysadmin_password': fake_password}
mock_controller_upgrade.return_value = list('upgrade')
self.mock_rpc_client().prestage_subcloud.return_value = True
mock_get_prestage_payload.return_value = data
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.patch_json, FAKE_URL + '/' +
str(subcloud.id) + '/prestage',
headers=FAKE_HEADERS, params=data)
@mock.patch.object(prestage, '_get_system_controller_upgrades')
@mock.patch.object(subclouds.SubcloudsController, '_get_prestage_payload')
def test_prestage_subcloud_no_password(self, mock_get_prestage_payload,
mock_controller_upgrade):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
data = {}
mock_controller_upgrade.return_value = list()
self.mock_rpc_client().prestage_subcloud.return_value = True
mock_get_prestage_payload.return_value = data
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.patch_json, FAKE_URL + '/' +
str(subcloud.id) + '/prestage',
headers=FAKE_HEADERS, params=data)
@mock.patch.object(prestage, '_get_system_controller_upgrades')
@mock.patch.object(subclouds.SubcloudsController, '_get_prestage_payload')
def test_prestage_subcloud_password_not_encoded(self, mock_get_prestage_payload,
mock_controller_upgrade):
subcloud = fake_subcloud.create_fake_subcloud(self.ctx)
data = {'sysadmin_password': 'notencoded'}
mock_controller_upgrade.return_value = list()
self.mock_rpc_client().prestage_subcloud.return_value = True
mock_get_prestage_payload.return_value = data
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.patch_json, FAKE_URL + '/' +
str(subcloud.id) + '/prestage',
headers=FAKE_HEADERS, params=data)
def test_get_management_subnet(self):
payload = {
'management_subnet': "192.168.204.0/24"
}
self.assertEqual(cutils.get_management_subnet(payload),
payload['management_subnet'])
def test_get_management_subnet_return_admin(self):
payload = {
'admin_subnet': "192.168.205.0/24",
'management_subnet': "192.168.204.0/24"
}
self.assertEqual(cutils.get_management_subnet(payload),
payload['admin_subnet'])
def test_get_management_start_address(self):
payload = {
'management_start_address': "192.168.204.2"
}
self.assertEqual(cutils.get_management_start_address(payload),
payload['management_start_address'])
def test_get_management_start_address_return_admin(self):
payload = {
'admin_start_address': "192.168.205.2",
'management_start_address': "192.168.204.2"
}
self.assertEqual(cutils.get_management_start_address(payload),
payload['admin_start_address'])
def test_get_management_end_address(self):
payload = {
'management_end_address': "192.168.204.50"
}
self.assertEqual(cutils.get_management_end_address(payload),
payload['management_end_address'])
def test_get_management_end_address_return_admin(self):
payload = {
'admin_end_address': "192.168.205.50",
'management_end_address': "192.168.204.50"
}
self.assertEqual(cutils.get_management_end_address(payload),
payload['admin_end_address'])
def test_get_management_gateway_address(self):
payload = {
'management_gateway_address': "192.168.204.1"
}
self.assertEqual(cutils.get_management_gateway_address(payload),
payload['management_gateway_address'])
def test_get_management_gateway_address_return_admin(self):
payload = {
'admin_gateway_address': "192.168.205.1",
'management_gateway_address': "192.168.204.1"
}
self.assertEqual(cutils.get_management_gateway_address(payload),
payload['admin_gateway_address'])
def test_validate_admin_config_subnet_small(self):
admin_subnet = "192.168.205.0/32"
admin_start_address = "192.168.205.2"
admin_end_address = "192.168.205.50"
admin_gateway_address = "192.168.205.1"
six.assertRaisesRegex(self,
Exception,
"Subnet too small*",
psd_common.validate_admin_network_config,
admin_subnet,
admin_start_address,
admin_end_address,
admin_gateway_address,
existing_networks=None,
operation=None)
def test_validate_admin_config_start_address_outOfSubnet(self):
admin_subnet = "192.168.205.0/28"
admin_start_address = "192.168.205.200"
admin_end_address = "192.168.205.50"
admin_gateway_address = "192.168.205.1"
six.assertRaisesRegex(self,
Exception,
"Address must be in subnet*",
psd_common.validate_admin_network_config,
admin_subnet,
admin_start_address,
admin_end_address,
admin_gateway_address,
existing_networks=None,
operation=None)
def test_validate_admin_config_end_address_outOfSubnet(self):
admin_subnet = "192.168.205.0/28"
admin_start_address = "192.168.205.1"
admin_end_address = "192.168.205.50"
admin_gateway_address = "192.168.205.1"
six.assertRaisesRegex(self,
Exception,
"Address must be in subnet*",
psd_common.validate_admin_network_config,
admin_subnet,
admin_start_address,
admin_end_address,
admin_gateway_address,
existing_networks=None,
operation=None)
def test_validate_admin_config_gateway_address_outOfSubnet(self):
admin_subnet = "192.168.205.0/28"
admin_start_address = "192.168.205.1"
admin_end_address = "192.168.205.12"
admin_gateway_address = "192.168.205.50"
six.assertRaisesRegex(self,
Exception,
"Address must be in subnet*",
psd_common.validate_admin_network_config,
admin_subnet,
admin_start_address,
admin_end_address,
admin_gateway_address,
existing_networks=None,
operation=None)