Zero Touch Provisioning changes for subcloud configuration

- Adding deploy field to subcloud entity
- removing generate config option
- modifying subcloud add to take a yaml file
  instead of individual parameters

Depends-On: https://review.opendev.org/#/c/670328/
Change-Id: I3160fb65dde7c4d514246fcf413e8b341b9c75a8
Story: 2004766
Task: 35756
Signed-off-by: Tyler Smith <tyler.smith@windriver.com>
This commit is contained in:
Tyler Smith 2019-07-11 10:57:48 -04:00
parent 45db426cb0
commit e96710bfc9
4 changed files with 103 additions and 299 deletions

View File

@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Copyright (c) 2017 Wind River Systems, Inc.
# Copyright (c) 2017-2019 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
@ -31,6 +31,7 @@ class Subcloud(base.Resource):
def __init__(self, manager, subcloud_id, name, description, location,
software_version, management_state, availability_status,
deploy_status,
management_subnet, management_start_ip, management_end_ip,
management_gateway_ip, systemcontroller_gateway_ip,
created_at, updated_at, sync_status="unknown",
@ -44,6 +45,7 @@ class Subcloud(base.Resource):
self.management_subnet = management_subnet
self.management_state = management_state
self.availability_status = availability_status
self.deploy_status = deploy_status
self.management_start_ip = management_start_ip
self.management_end_ip = management_end_ip
self.management_gateway_ip = management_gateway_ip
@ -74,6 +76,7 @@ class subcloud_manager(base.ResourceManager):
software_version=json_object['software-version'],
management_state=json_object['management-state'],
availability_status=json_object['availability-status'],
deploy_status=json_object['deploy-status'],
management_subnet=json_object['management-subnet'],
management_start_ip=json_object['management-start-ip'],
management_end_ip=json_object['management-end-ip'],
@ -101,6 +104,7 @@ class subcloud_manager(base.ResourceManager):
software_version=json_object['software-version'],
management_state=json_object['management-state'],
availability_status=json_object['availability-status'],
deploy_status=json_object['deploy-status'],
management_subnet=json_object['management-subnet'],
management_start_ip=json_object['management-start-ip'],
management_end_ip=json_object['management-end-ip'],
@ -129,6 +133,7 @@ class subcloud_manager(base.ResourceManager):
software_version=json_object['software-version'],
management_state=json_object['management-state'],
availability_status=json_object['availability-status'],
deploy_status=json_object['deploy-status'],
management_subnet=json_object['management-subnet'],
management_start_ip=json_object['management-start-ip'],
management_end_ip=json_object['management-end-ip'],
@ -157,6 +162,7 @@ class subcloud_manager(base.ResourceManager):
software_version=json_object['software-version'],
management_state=json_object['management-state'],
availability_status=json_object['availability-status'],
deploy_status=json_object['deploy-status'],
management_subnet=json_object['management-subnet'],
management_start_ip=json_object['management-start-ip'],
management_end_ip=json_object['management-end-ip'],
@ -168,14 +174,6 @@ class subcloud_manager(base.ResourceManager):
endpoint_sync_status=json_object['endpoint_sync_status']))
return resource
def subcloud_generate_config(self, url, data):
data = json.dumps(data)
resp = self.http_client.post(url, data)
if resp.status_code != 200:
self._raise_api_exception(resp)
json_object = get_json(resp)
return json_object['config']
def add_subcloud(self, **kwargs):
data = kwargs
url = '/subclouds/'
@ -197,8 +195,3 @@ class subcloud_manager(base.ResourceManager):
data = kwargs
url = '/subclouds/%s' % subcloud_ref
return self.subcloud_update(url, data)
def generate_config_subcloud(self, subcloud_ref, **kwargs):
data = kwargs
url = '/subclouds/%s/config' % subcloud_ref
return self.subcloud_generate_config(url, data)

View File

@ -12,13 +12,17 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Copyright (c) 2017 Wind River Systems, Inc.
# Copyright (c) 2017-2019 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
import getpass
import os
import yaml
from osc_lib.command import command
from dcmanagerclient.commands.v1 import base
@ -31,6 +35,7 @@ def format(subcloud=None):
'name',
'management',
'availability',
'deploy status',
'sync'
)
@ -40,6 +45,7 @@ def format(subcloud=None):
subcloud.name,
subcloud.management_state,
subcloud.availability_status,
subcloud.deploy_status,
subcloud.sync_status
)
@ -58,6 +64,7 @@ def detail_format(subcloud=None):
'software_version',
'management',
'availability',
'deploy_status',
'management_subnet',
'management_start_ip',
'management_end_ip',
@ -76,6 +83,7 @@ def detail_format(subcloud=None):
subcloud.software_version,
subcloud.management_state,
subcloud.availability_status,
subcloud.deploy_status,
subcloud.management_subnet,
subcloud.management_start_ip,
subcloud.management_end_ip,
@ -108,51 +116,21 @@ class AddSubcloud(base.DCManagerShowOne):
parser = super(AddSubcloud, self).get_parser(parsed_args)
parser.add_argument(
'--name',
'--bootstrap-address',
required=True,
help='Name of subcloud.'
help='IP address for initial subcloud controller.'
)
parser.add_argument(
'--description',
'--bootstrap-values',
required=True,
help='YAML file containing subcloud configuration settings.'
)
parser.add_argument(
'--subcloud-password',
required=False,
help='Description of subcloud.'
)
parser.add_argument(
'--location',
required=False,
help='Location of subcloud.'
)
parser.add_argument(
'--management-subnet',
required=True,
help='Management subnet for subcloud in CIDR format.'
)
parser.add_argument(
'--management-start-ip',
required=True,
help='Start of management IP address range for subcloud'
)
parser.add_argument(
'--management-end-ip',
required=True,
help='End of management IP address range for subcloud',
)
parser.add_argument(
'--management-gateway-ip',
required=True,
help='Management gateway IP for subcloud',
)
parser.add_argument(
'--systemcontroller-gateway-ip',
required=True,
help='Central gateway IP',
help='sysadmin password of the subcloud to be configured.'
)
return parser
@ -160,17 +138,39 @@ class AddSubcloud(base.DCManagerShowOne):
def _get_resources(self, parsed_args):
dcmanager_client = self.app.client_manager.subcloud_manager
kwargs = dict()
kwargs['name'] = parsed_args.name
if parsed_args.description:
kwargs['description'] = parsed_args.description
if parsed_args.location:
kwargs['location'] = parsed_args.location
kwargs['management-subnet'] = parsed_args.management_subnet
kwargs['management-start-ip'] = parsed_args.management_start_ip
kwargs['management-end-ip'] = parsed_args.management_end_ip
kwargs['management-gateway-ip'] = parsed_args.management_gateway_ip
kwargs['systemcontroller-gateway-ip'] = \
parsed_args.systemcontroller_gateway_ip
kwargs['bootstrap-address'] = parsed_args.bootstrap_address
# Load the configuration from the yaml file
filename = parsed_args.bootstrap_values
if os.path.isdir(filename):
error_msg = "Error: %s is a directory." % filename
raise exceptions.DCManagerClientException(error_msg)
try:
with open(filename, 'rb') as stream:
kwargs.update(yaml.safe_load(stream))
except Exception:
error_msg = "Error: Could not open file %s." % filename
raise exceptions.DCManagerClientException(error_msg)
# Prompt the user for the subcloud's password if it isn't provided
if parsed_args.subcloud_password is not None:
kwargs['subcloud_password'] = parsed_args.subcloud_password
else:
while True:
password = getpass.getpass(
"Enter the sysadmin password for the subcloud: ")
if len(password) < 1:
print("Password cannot be empty")
continue
confirm = getpass.getpass(
"Re-enter sysadmin password to confirm: ")
if password != confirm:
print("Passwords did not match")
continue
kwargs["subcloud_password"] = password
break
return dcmanager_client.subcloud_manager.add_subcloud(**kwargs)
@ -339,170 +339,3 @@ class UpdateSubcloud(base.DCManagerShowOne):
print(e)
error_msg = "Unable to update subcloud %s" % (subcloud_ref)
raise exceptions.DCManagerClientException(error_msg)
class GenerateConfigSubcloud(command.Command):
"""Generate configuration for a subcloud."""
def get_parser(self, prog_name):
parser = super(GenerateConfigSubcloud, self).get_parser(prog_name)
parser.add_argument(
'subcloud',
help='Name or ID of the subcloud to generate config.'
)
parser.add_argument(
'--pxe-subnet',
required=False,
help='PXE boot subnet for subcloud in CIDR format.'
)
parser.add_argument(
'--management-vlan',
required=False,
help='VLAN for subcloud management network.'
)
parser.add_argument(
'--management-interface-port',
required=False,
help='Subcloud management interface port.'
)
parser.add_argument(
'--management-interface-mtu',
required=False,
help='Subcloud management interface mtu.'
)
parser.add_argument(
'--cluster-vlan',
required=False,
help='VLAN for subcloud cluster network.'
)
parser.add_argument(
'--cluster-interface-port',
required=False,
help='Subcloud cluster interface port.'
)
parser.add_argument(
'--cluster-interface-mtu',
required=False,
help='Subcloud cluster interface mtu.'
)
parser.add_argument(
'--cluster-subnet',
required=False,
help='Cluster subnet for subcloud in CIDR format.'
)
parser.add_argument(
'--oam-subnet',
required=False,
help='OAM subnet for subcloud in CIDR format.'
)
parser.add_argument(
'--oam-gateway-ip',
required=False,
help='OAM gateway IP for subcloud.'
)
parser.add_argument(
'--oam-floating-ip',
required=False,
help='OAM floating IP address for subcloud.'
)
parser.add_argument(
'--oam-unit-0-ip',
required=False,
help='OAM unit 0 IP address for subcloud.'
)
parser.add_argument(
'--oam-unit-1-ip',
required=False,
help='OAM unit 1 IP address for subcloud.'
)
parser.add_argument(
'--oam-interface-port',
required=False,
help='Subcloud OAM interface port.'
)
parser.add_argument(
'--oam-interface-mtu',
required=False,
help='Subcloud OAM interface mtu.'
)
parser.add_argument(
'--system-mode',
required=False,
help='System mode',
choices=['simplex', 'duplex', 'duplex-direct']
)
return parser
def take_action(self, parsed_args):
subcloud_ref = parsed_args.subcloud
dcmanager_client = self.app.client_manager.subcloud_manager
kwargs = dict()
if parsed_args.pxe_subnet:
kwargs['pxe-subnet'] = \
parsed_args.pxe_subnet
if parsed_args.management_vlan:
kwargs['management-vlan'] = \
parsed_args.management_vlan
if parsed_args.management_interface_port:
kwargs['management-interface-port'] = \
parsed_args.management_interface_port
if parsed_args.management_interface_mtu:
kwargs['management-interface-mtu'] = \
parsed_args.management_interface_mtu
if parsed_args.cluster_vlan:
kwargs['cluster-vlan'] = \
parsed_args.cluster_vlan
if parsed_args.cluster_interface_port:
kwargs['cluster-interface-port'] = \
parsed_args.cluster_interface_port
if parsed_args.cluster_interface_mtu:
kwargs['cluster-interface-mtu'] = \
parsed_args.cluster_interface_mtu
if parsed_args.cluster_subnet:
kwargs['cluster-subnet'] = parsed_args.cluster_subnet
if parsed_args.oam_subnet:
kwargs['oam-subnet'] = parsed_args.oam_subnet
if parsed_args.oam_gateway_ip:
kwargs['oam-gateway-ip'] = parsed_args.oam_gateway_ip
if parsed_args.oam_floating_ip:
kwargs['oam-floating-ip'] = parsed_args.oam_floating_ip
if parsed_args.oam_unit_0_ip:
kwargs['oam-unit-0-ip'] = parsed_args.oam_unit_0_ip
if parsed_args.oam_unit_1_ip:
kwargs['oam-unit-1-ip'] = parsed_args.oam_unit_1_ip
if parsed_args.oam_interface_port:
kwargs['oam-interface-port'] = parsed_args.oam_interface_port
if parsed_args.oam_interface_mtu:
kwargs['oam-interface-mtu'] = parsed_args.oam_interface_mtu
if parsed_args.system_mode:
kwargs['system-mode'] = parsed_args.system_mode
try:
subcloud_config = dcmanager_client.subcloud_manager.\
generate_config_subcloud(subcloud_ref, **kwargs)
return subcloud_config
except Exception as e:
print(e)
error_msg = "Unable to generate config for subcloud %s" % \
(subcloud_ref)
raise exceptions.DCManagerClientException(error_msg)

View File

@ -480,7 +480,6 @@ class DCManagerShell(app.App):
'subcloud unmanage': sm.UnmanageSubcloud,
'subcloud manage': sm.ManageSubcloud,
'subcloud update': sm.UpdateSubcloud,
'subcloud generate-config': sm.GenerateConfigSubcloud,
'alarm summary': am.ListAlarmSummary,
'patch-strategy create': sum.CreatePatchStrategy,
'patch-strategy delete': sum.DeletePatchStrategy,

View File

@ -19,8 +19,10 @@
# of an applicable Wind River license agreement.
#
import copy
import mock
import os
import tempfile
import yaml
from oslo_utils import timeutils
@ -28,20 +30,26 @@ from dcmanagerclient.api.v1 import subcloud_manager as sm
from dcmanagerclient.commands.v1 import subcloud_manager as subcloud_cmd
from dcmanagerclient.tests import base
BOOTSTRAP_ADDRESS = '10.10.10.12'
TIME_NOW = timeutils.utcnow().isoformat()
ID = '1'
ID_1 = '2'
NAME = 'subcloud1'
SYSTEM_MODE = "duplex"
DESCRIPTION = 'subcloud1 description'
LOCATION = 'subcloud1 location'
SOFTWARE_VERSION = '12.34'
MANAGEMENT_STATE = 'unmanaged'
AVAILABILITY_STATUS = 'offline'
DEPLOY_STATUS = 'not-deployed'
MANAGEMENT_SUBNET = '192.168.101.0/24'
MANAGEMENT_START_IP = '192.168.101.2'
MANAGEMENT_END_IP = '192.168.101.50'
MANAGEMENT_GATEWAY_IP = '192.168.101.1'
SYSTEMCONTROLLER_GATEWAY_IP = '192.168.204.101'
EXTERNAL_OAM_SUBNET = "10.10.10.0/24"
EXTERNAL_OAM_GATEWAY_ADDRESS = "10.10.10.1"
EXTERNAL_OAM_FLOATING_ADDRESS = "10.10.10.12"
SUBCLOUD_DICT = {
'SUBCLOUD_ID': ID,
@ -51,6 +59,7 @@ SUBCLOUD_DICT = {
'SOFTWARE_VERSION': SOFTWARE_VERSION,
'MANAGEMENT_STATE': MANAGEMENT_STATE,
'AVAILABILITY_STATUS': AVAILABILITY_STATUS,
'DEPLOY_STATUS': DEPLOY_STATUS,
'MANAGEMENT_SUBNET': MANAGEMENT_SUBNET,
'MANAGEMENT_START_IP': MANAGEMENT_START_IP,
'MANAGEMENT_END_IP': MANAGEMENT_END_IP,
@ -69,6 +78,7 @@ SUBCLOUD = sm.Subcloud(
software_version=SUBCLOUD_DICT['SOFTWARE_VERSION'],
management_state=SUBCLOUD_DICT['MANAGEMENT_STATE'],
availability_status=SUBCLOUD_DICT['AVAILABILITY_STATUS'],
deploy_status=SUBCLOUD_DICT['DEPLOY_STATUS'],
management_subnet=SUBCLOUD_DICT['MANAGEMENT_SUBNET'],
management_start_ip=SUBCLOUD_DICT['MANAGEMENT_START_IP'],
management_end_ip=SUBCLOUD_DICT['MANAGEMENT_END_IP'],
@ -84,13 +94,13 @@ class TestCLISubcloudManagerV1(base.BaseCommandTest):
self.client.subcloud_manager.list_subclouds.return_value = [SUBCLOUD]
actual_call = self.call(subcloud_cmd.ListSubcloud)
self.assertEqual([(ID, NAME, MANAGEMENT_STATE, AVAILABILITY_STATUS,
"unknown")],
DEPLOY_STATUS, "unknown")],
actual_call[1])
def test_negative_list_subclouds(self):
self.client.subcloud_manager.list_subclouds.return_value = []
actual_call = self.call(subcloud_cmd.ListSubcloud)
self.assertEqual((('<none>', '<none>', '<none>', '<none>',
self.assertEqual((('<none>', '<none>', '<none>', '<none>', '<none>',
'<none>'),),
actual_call[1])
@ -113,6 +123,7 @@ class TestCLISubcloudManagerV1(base.BaseCommandTest):
SOFTWARE_VERSION,
MANAGEMENT_STATE,
AVAILABILITY_STATUS,
DEPLOY_STATUS,
MANAGEMENT_SUBNET,
MANAGEMENT_START_IP,
MANAGEMENT_END_IP,
@ -127,63 +138,43 @@ class TestCLISubcloudManagerV1(base.BaseCommandTest):
self.assertEqual((('<none>', '<none>', '<none>', '<none>',
'<none>', '<none>', '<none>', '<none>',
'<none>', '<none>', '<none>', '<none>',
'<none>', '<none>'),),
'<none>', '<none>', '<none>'),),
actual_call[1])
def test_add_subcloud(self):
@mock.patch('getpass.getpass', return_value='testpassword')
def test_add_subcloud(self, getpass):
self.client.subcloud_manager.add_subcloud.\
return_value = [SUBCLOUD]
actual_call = self.call(
subcloud_cmd.AddSubcloud, app_args=[
'--name', NAME,
'--description', DESCRIPTION,
'--location', LOCATION,
'--management-subnet', MANAGEMENT_SUBNET,
'--management-start-ip', MANAGEMENT_START_IP,
'--management-end-ip', MANAGEMENT_END_IP,
'--management-gateway-ip', MANAGEMENT_GATEWAY_IP,
'--systemcontroller-gateway-ip', SYSTEMCONTROLLER_GATEWAY_IP])
values = {
"system_mode": SYSTEM_MODE,
"name": NAME,
"description": DESCRIPTION,
"location": LOCATION,
"management_subnet": MANAGEMENT_SUBNET,
"management_start_address": MANAGEMENT_START_IP,
"management_end_address": MANAGEMENT_END_IP,
"management_gateway_address": MANAGEMENT_GATEWAY_IP,
"external_oam_subnet": EXTERNAL_OAM_SUBNET,
"external_oam_gateway_address": EXTERNAL_OAM_GATEWAY_ADDRESS,
"external_oam_floating_address": EXTERNAL_OAM_FLOATING_ADDRESS,
}
with tempfile.NamedTemporaryFile() as f:
yaml.dump(values, f)
file_path = os.path.abspath(f.name)
actual_call = self.call(
subcloud_cmd.AddSubcloud, app_args=[
'--bootstrap-address', BOOTSTRAP_ADDRESS,
'--bootstrap-values', file_path,
])
self.assertEqual((ID, NAME, DESCRIPTION, LOCATION, SOFTWARE_VERSION,
MANAGEMENT_STATE, AVAILABILITY_STATUS,
MANAGEMENT_STATE, AVAILABILITY_STATUS, DEPLOY_STATUS,
MANAGEMENT_SUBNET, MANAGEMENT_START_IP,
MANAGEMENT_END_IP, MANAGEMENT_GATEWAY_IP,
SYSTEMCONTROLLER_GATEWAY_IP,
TIME_NOW, TIME_NOW), actual_call[1])
def test_add_subcloud_no_optional_parameters(self):
subcloud = copy.copy(SUBCLOUD)
subcloud.description = ''
subcloud.location = ''
self.client.subcloud_manager.add_subcloud.\
return_value = [subcloud]
actual_call = self.call(
subcloud_cmd.AddSubcloud, app_args=[
'--name', NAME,
'--management-subnet', MANAGEMENT_SUBNET,
'--management-start-ip', MANAGEMENT_START_IP,
'--management-end-ip', MANAGEMENT_END_IP,
'--management-gateway-ip', MANAGEMENT_GATEWAY_IP,
'--systemcontroller-gateway-ip', SYSTEMCONTROLLER_GATEWAY_IP])
self.assertEqual((ID, NAME, '', '', SOFTWARE_VERSION,
MANAGEMENT_STATE, AVAILABILITY_STATUS,
MANAGEMENT_SUBNET, MANAGEMENT_START_IP,
MANAGEMENT_END_IP, MANAGEMENT_GATEWAY_IP,
SYSTEMCONTROLLER_GATEWAY_IP,
TIME_NOW, TIME_NOW), actual_call[1])
def test_add_subcloud_without_name(self):
self.client.subcloud_manager.add_subcloud.\
return_value = [SUBCLOUD]
self.assertRaises(
SystemExit, self.call, subcloud_cmd.AddSubcloud, app_args=[
'--description', DESCRIPTION,
'--location', LOCATION,
'--management-subnet', MANAGEMENT_SUBNET,
'--management-start-ip', MANAGEMENT_START_IP,
'--management-end-ip', MANAGEMENT_END_IP,
'--management-gateway-ip', MANAGEMENT_GATEWAY_IP,
'--systemcontroller-gateway-ip', SYSTEMCONTROLLER_GATEWAY_IP])
def test_unmanage_subcloud(self):
self.client.subcloud_manager.update_subcloud.\
return_value = [SUBCLOUD]
@ -192,7 +183,7 @@ class TestCLISubcloudManagerV1(base.BaseCommandTest):
self.assertEqual((ID, NAME,
DESCRIPTION, LOCATION,
SOFTWARE_VERSION, MANAGEMENT_STATE,
AVAILABILITY_STATUS,
AVAILABILITY_STATUS, DEPLOY_STATUS,
MANAGEMENT_SUBNET, MANAGEMENT_START_IP,
MANAGEMENT_END_IP, MANAGEMENT_GATEWAY_IP,
SYSTEMCONTROLLER_GATEWAY_IP,
@ -210,7 +201,7 @@ class TestCLISubcloudManagerV1(base.BaseCommandTest):
self.assertEqual((ID, NAME,
DESCRIPTION, LOCATION,
SOFTWARE_VERSION, MANAGEMENT_STATE,
AVAILABILITY_STATUS,
AVAILABILITY_STATUS, DEPLOY_STATUS,
MANAGEMENT_SUBNET, MANAGEMENT_START_IP,
MANAGEMENT_END_IP, MANAGEMENT_GATEWAY_IP,
SYSTEMCONTROLLER_GATEWAY_IP,
@ -231,20 +222,8 @@ class TestCLISubcloudManagerV1(base.BaseCommandTest):
self.assertEqual((ID, NAME,
DESCRIPTION, LOCATION,
SOFTWARE_VERSION, MANAGEMENT_STATE,
AVAILABILITY_STATUS,
AVAILABILITY_STATUS, DEPLOY_STATUS,
MANAGEMENT_SUBNET, MANAGEMENT_START_IP,
MANAGEMENT_END_IP, MANAGEMENT_GATEWAY_IP,
SYSTEMCONTROLLER_GATEWAY_IP,
TIME_NOW, TIME_NOW), actual_call[1])
def test_generate_config_subcloud(self):
FAKE_CONFIG = "This is a fake config file."
self.client.subcloud_manager.generate_config_subcloud.\
return_value = FAKE_CONFIG
actual_call = self.call(
subcloud_cmd.GenerateConfigSubcloud, app_args=[ID])
self.assertEqual(FAKE_CONFIG, actual_call)
def test_generate_config_subcloud_without_subcloud_id(self):
self.assertRaises(SystemExit, self.call,
subcloud_cmd.GenerateConfigSubcloud, app_args=[])