Zero Touch Provisioning changes for subcloud configuration

- Adding logic to automatically deploy subclouds via ansible when
  they are added, as well as a 'deploy' field to subcloud entity to
  report status
- Converting subcloud fields to take underscored
  parameters instead of dashed to match ansible variable style
- Adding checks to OAM network parameters
- Removing generate subcloud config logic

Depends-On: https://review.opendev.org/#/c/670321/
Depends-On: https://review.opendev.org/#/c/670325/
Change-Id: Ib7fe2f4a42fffb7bd5082e6e851cb9136edf5a00
Story: 2004766
Task: 35756
Signed-off-by: Tyler Smith <tyler.smith@windriver.com>
This commit is contained in:
Tyler Smith 2019-07-11 10:56:45 -04:00
parent a3f595fe18
commit 8c2bd5fa14
12 changed files with 391 additions and 339 deletions

View File

@ -30,7 +30,6 @@ import pecan
from pecan import expose from pecan import expose
from pecan import request from pecan import request
from controllerconfig.common import crypt
from controllerconfig.common.exceptions import ValidateFail from controllerconfig.common.exceptions import ValidateFail
from controllerconfig.utils import validate_address_str from controllerconfig.utils import validate_address_str
from controllerconfig.utils import validate_network_str from controllerconfig.utils import validate_network_str
@ -45,8 +44,6 @@ from dcmanager.db import api as db_api
from dcmanager.drivers.openstack.sysinv_v1 import SysinvClient from dcmanager.drivers.openstack.sysinv_v1 import SysinvClient
from dcmanager.rpc import client as rpc_client from dcmanager.rpc import client as rpc_client
from Crypto.Hash import MD5
import json
CONF = cfg.CONF CONF = cfg.CONF
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -82,6 +79,9 @@ class SubcloudsController(object):
management_start_ip_str, management_start_ip_str,
management_end_ip_str, management_end_ip_str,
management_gateway_ip_str, management_gateway_ip_str,
external_oam_subnet_str,
external_oam_gateway_address_str,
external_oam_floating_address_str,
systemcontroller_gateway_ip_str): systemcontroller_gateway_ip_str):
"""Check whether subcloud config is valid.""" """Check whether subcloud config is valid."""
@ -113,7 +113,7 @@ class SubcloudsController(object):
existing_networks=subcloud_subnets) existing_networks=subcloud_subnets)
except ValidateFail as e: except ValidateFail as e:
LOG.exception(e) LOG.exception(e)
pecan.abort(400, _("management-subnet invalid: %s") % e) pecan.abort(400, _("management_subnet invalid: %s") % e)
# Parse/validate the start/end addresses # Parse/validate the start/end addresses
management_start_ip = None management_start_ip = None
@ -122,7 +122,7 @@ class SubcloudsController(object):
management_start_ip_str, management_subnet) management_start_ip_str, management_subnet)
except ValidateFail as e: except ValidateFail as e:
LOG.exception(e) LOG.exception(e)
pecan.abort(400, _("management-start-ip invalid: %s") % e) pecan.abort(400, _("management_start_address invalid: %s") % e)
management_end_ip = None management_end_ip = None
try: try:
@ -130,12 +130,13 @@ class SubcloudsController(object):
management_end_ip_str, management_subnet) management_end_ip_str, management_subnet)
except ValidateFail as e: except ValidateFail as e:
LOG.exception(e) LOG.exception(e)
pecan.abort(400, _("management-end-ip invalid: %s") % e) pecan.abort(400, _("management_end_address invalid: %s") % e)
if not management_start_ip < management_end_ip: if not management_start_ip < management_end_ip:
pecan.abort( pecan.abort(
400, 400,
_("management-start-ip not less than management-end-ip")) _("management_start_address not less than "
"management_end_address"))
if not len(IPRange(management_start_ip, management_end_ip)) >= \ if not len(IPRange(management_start_ip, management_end_ip)) >= \
MIN_MANAGEMENT_ADDRESSES: MIN_MANAGEMENT_ADDRESSES:
@ -150,7 +151,7 @@ class SubcloudsController(object):
management_gateway_ip_str, management_subnet) management_gateway_ip_str, management_subnet)
except ValidateFail as e: except ValidateFail as e:
LOG.exception(e) LOG.exception(e)
pecan.abort(400, _("management-gateway-ip invalid: %s") % e) pecan.abort(400, _("management_gateway_address invalid: %s") % e)
# Ensure subcloud management gateway is not within the actual subcloud # Ensure subcloud management gateway is not within the actual subcloud
# management subnet address pool for consistency with the # management subnet address pool for consistency with the
@ -161,7 +162,7 @@ class SubcloudsController(object):
subcloud_mgmt_gw_ip = IPAddress(management_gateway_ip_str) subcloud_mgmt_gw_ip = IPAddress(management_gateway_ip_str)
if ((subcloud_mgmt_gw_ip >= subcloud_mgmt_address_start) and if ((subcloud_mgmt_gw_ip >= subcloud_mgmt_address_start) and
(subcloud_mgmt_gw_ip <= subcloud_mgmt_address_end)): (subcloud_mgmt_gw_ip <= subcloud_mgmt_address_end)):
pecan.abort(400, _("management-gateway-ip invalid, " pecan.abort(400, _("management_gateway_address invalid, "
"is within management pool: %(start)s - " "is within management pool: %(start)s - "
"%(end)s") % "%(end)s") %
{'start': subcloud_mgmt_address_start, {'start': subcloud_mgmt_address_start,
@ -179,7 +180,8 @@ class SubcloudsController(object):
systemcontroller_gateway_ip_str, systemcontroller_subnet) systemcontroller_gateway_ip_str, systemcontroller_subnet)
except ValidateFail as e: except ValidateFail as e:
LOG.exception(e) LOG.exception(e)
pecan.abort(400, _("systemcontroller-gateway-ip invalid: %s") % e) pecan.abort(400,
_("systemcontroller_gateway_address invalid: %s") % e)
# Ensure systemcontroller gateway is not within the actual # Ensure systemcontroller gateway is not within the actual
# management subnet address pool to prevent address collision. # management subnet address pool to prevent address collision.
mgmt_address_start = IPAddress(management_address_pool.ranges[0][0]) mgmt_address_start = IPAddress(management_address_pool.ranges[0][0])
@ -187,188 +189,37 @@ class SubcloudsController(object):
systemcontroller_gw_ip = IPAddress(systemcontroller_gateway_ip_str) systemcontroller_gw_ip = IPAddress(systemcontroller_gateway_ip_str)
if ((systemcontroller_gw_ip >= mgmt_address_start) and if ((systemcontroller_gw_ip >= mgmt_address_start) and
(systemcontroller_gw_ip <= mgmt_address_end)): (systemcontroller_gw_ip <= mgmt_address_end)):
pecan.abort(400, _("systemcontroller-gateway-ip invalid, " pecan.abort(400, _("systemcontroller_gateway_address invalid, "
"is within management pool: %(start)s - " "is within management pool: %(start)s - "
"%(end)s") % "%(end)s") %
{'start': mgmt_address_start, 'end': mgmt_address_end}) {'start': mgmt_address_start, 'end': mgmt_address_end})
def _create_subcloud_config_file(self, context, subcloud, payload): # Parse/validate the oam subnet
"""Creates the subcloud config file for a subcloud.""" MIN_OAM_SUBNET_SIZE = 3
DEFAULT_STR = '<EDIT>' oam_subnet = None
try:
oam_subnet = validate_network_str(
external_oam_subnet_str,
minimum_size=MIN_OAM_SUBNET_SIZE,
existing_networks=subcloud_subnets)
except ValidateFail as e:
LOG.exception(e)
pecan.abort(400, _("external_oam_subnet invalid: %s") % e)
pxe_cidr = payload.get( # Parse/validate the addresses
'pxe-subnet', DEFAULT_STR) try:
management_vlan = payload.get( validate_address_str(
'management-vlan', DEFAULT_STR) external_oam_gateway_address_str, oam_subnet)
management_interface_mtu = payload.get( except ValidateFail as e:
'management-interface-mtu', DEFAULT_STR) LOG.exception(e)
management_interface_ports = payload.get( pecan.abort(400, _("oam_gateway_address invalid: %s") % e)
'management-interface-port', DEFAULT_STR)
cluster_vlan = payload.get(
'cluster-vlan', DEFAULT_STR)
cluster_interface_mtu = payload.get(
'cluster-interface-mtu', DEFAULT_STR)
cluster_interface_ports = payload.get(
'cluster-interface-port', management_interface_ports)
cluster_cidr = payload.get(
'cluster-subnet', IPNetwork("192.168.206.0/24"))
oam_cidr = payload.get(
'oam-subnet', DEFAULT_STR)
oam_gateway = payload.get(
'oam-gateway-ip', DEFAULT_STR)
oam_ip_floating_address = payload.get(
'oam-floating-ip', DEFAULT_STR)
oam_ip_unit_0_address = payload.get(
'oam-unit-0-ip', DEFAULT_STR)
oam_ip_unit_1_address = payload.get(
'oam-unit-1-ip', DEFAULT_STR)
oam_interface_mtu = payload.get(
'oam-interface-mtu', DEFAULT_STR)
oam_interface_ports = payload.get(
'oam-interface-port', DEFAULT_STR)
system_mode = payload.get(
'system-mode', DEFAULT_STR)
management_address_pool = self._get_management_address_pool(context) try:
systemcontroller_subnet = "%s/%d" % ( validate_address_str(
management_address_pool.network, external_oam_floating_address_str, oam_subnet)
management_address_pool.prefix) except ValidateFail as e:
sc_mgmt_floating_ip = management_address_pool.floating_address LOG.exception(e)
pecan.abort(400, _("oam_floating_address invalid: %s") % e)
subcloud_config = ""
if system_mode in [SYSTEM_MODE_SIMPLEX, SYSTEM_MODE_DUPLEX,
SYSTEM_MODE_DUPLEX_DIRECT]:
subcloud_config += (
"[SYSTEM]\n"
"SYSTEM_MODE={}\n".format(system_mode))
if system_mode == SYSTEM_MODE_SIMPLEX:
subcloud_oamip_config = (
"IP_ADDRESS = {oam_ip_floating_address}\n"
).format(
oam_ip_floating_address=oam_ip_floating_address,
)
else:
subcloud_oamip_config = (
"IP_FLOATING_ADDRESS = {oam_ip_floating_address}\n"
"IP_UNIT_0_ADDRESS = {oam_ip_unit_0_address}\n"
"IP_UNIT_1_ADDRESS = {oam_ip_unit_1_address}\n"
).format(
oam_ip_floating_address=oam_ip_floating_address,
oam_ip_unit_0_address=oam_ip_unit_0_address,
oam_ip_unit_1_address=oam_ip_unit_1_address,
)
subcloud_config += (
"[CLUSTER_NETWORK]\n"
"CIDR = {cluster_cidr}\n"
"DYNAMIC_ALLOCATION = Y\n"
).format(
cluster_cidr=cluster_cidr
)
if cluster_vlan != DEFAULT_STR:
subcloud_config += (
"VLAN={cluster_vlan}\n"
).format(
cluster_vlan=cluster_vlan
)
if management_interface_ports == cluster_interface_ports:
subcloud_config += (
"LOGICAL_INTERFACE = LOGICAL_INTERFACE_1\n")
else:
subcloud_config += (
"LOGICAL_INTERFACE = LOGICAL_INTERFACE_3\n"
"[LOGICAL_INTERFACE_3]\n"
"LAG_INTERFACE = N\n"
"INTERFACE_MTU = {cluster_interface_mtu}\n"
"INTERFACE_PORTS = {cluster_interface_ports}\n"
).format(
cluster_interface_mtu=cluster_interface_mtu,
cluster_interface_ports=cluster_interface_ports,
)
MIN_MANAGEMENT_SUBNET_SIZE = 8
tmp_management_subnet = validate_network_str(
subcloud.management_subnet,
minimum_size=MIN_MANAGEMENT_SUBNET_SIZE)
is_ipv6_mgmt = (tmp_management_subnet.version == 6)
# If ipv6 then we need pxe subnet and management_vlan.
# If user specified pxe boot subnet, then management vlan is required
# and vice versa
if is_ipv6_mgmt or (pxe_cidr != DEFAULT_STR) or \
(management_vlan != DEFAULT_STR):
subcloud_config += (
"[REGION2_PXEBOOT_NETWORK]\n"
"PXEBOOT_CIDR = {pxe_cidr}\n"
"[MGMT_NETWORK]\n"
"VLAN = {management_vlan}\n"
).format(
pxe_cidr=pxe_cidr,
management_vlan=management_vlan,
)
else:
subcloud_config += "[MGMT_NETWORK]\n"
subcloud_config += (
"CIDR = {management_cidr}\n"
"GATEWAY = {management_gateway}\n"
"IP_START_ADDRESS = {management_ip_start_address}\n"
"IP_END_ADDRESS = {management_ip_end_address}\n"
"DYNAMIC_ALLOCATION = Y\n"
"LOGICAL_INTERFACE = LOGICAL_INTERFACE_1\n"
"[LOGICAL_INTERFACE_1]\n"
"LAG_INTERFACE = N\n"
"INTERFACE_MTU = {management_interface_mtu}\n"
"INTERFACE_PORTS = {management_interface_ports}\n"
"[OAM_NETWORK]\n"
"CIDR = {oam_cidr}\n"
"GATEWAY = {oam_gateway}\n" +
subcloud_oamip_config +
"LOGICAL_INTERFACE = LOGICAL_INTERFACE_2\n"
"[LOGICAL_INTERFACE_2]\n"
"LAG_INTERFACE = N\n"
"INTERFACE_MTU = {oam_interface_mtu}\n"
"INTERFACE_PORTS = {oam_interface_ports}\n"
"[SHARED_SERVICES]\n"
"SYSTEM_CONTROLLER_SUBNET = {systemcontroller_subnet}\n"
"SYSTEM_CONTROLLER_FLOATING_ADDRESS = {sc_mgmt_floating_ip}\n"
"REGION_NAME = SystemController\n"
"ADMIN_PROJECT_NAME = admin\n"
"ADMIN_USER_NAME = admin\n"
"ADMIN_PASSWORD = {admin_password}\n"
"KEYSTONE_ADMINURL = {keystone_adminurl}\n"
"KEYSTONE_SERVICE_NAME = keystone\n"
"KEYSTONE_SERVICE_TYPE = identity\n"
"GLANCE_SERVICE_NAME = glance\n"
"GLANCE_SERVICE_TYPE = image\n"
"GLANCE_CACHED = True\n"
"[REGION_2_SERVICES]\n"
"REGION_NAME = {region_2_name}\n"
"[VERSION]\n"
"RELEASE = {release}\n"
).format(
management_cidr=subcloud.management_subnet,
management_gateway=subcloud.management_gateway_ip,
management_ip_start_address=subcloud.management_start_ip,
management_ip_end_address=subcloud.management_end_ip,
management_interface_mtu=management_interface_mtu,
management_interface_ports=management_interface_ports,
oam_cidr=oam_cidr,
oam_gateway=oam_gateway,
oam_interface_mtu=oam_interface_mtu,
oam_interface_ports=oam_interface_ports,
systemcontroller_subnet=systemcontroller_subnet,
sc_mgmt_floating_ip=sc_mgmt_floating_ip,
admin_password=cfg.CONF.cache.admin_password,
keystone_adminurl=cfg.CONF.cache.auth_uri,
region_2_name=subcloud.name,
release=subcloud.software_version,
)
return subcloud_config
def _get_subcloud_users(self): def _get_subcloud_users(self):
"""Get the subcloud users and passwords from keyring""" """Get the subcloud users and passwords from keyring"""
@ -410,7 +261,7 @@ class SubcloudsController(object):
return sysinv_client.get_management_address_pool() return sysinv_client.get_management_address_pool()
@index.when(method='GET', template='json') @index.when(method='GET', template='json')
def get(self, subcloud_ref=None, qualifier=None): def get(self, subcloud_ref=None):
"""Get details about subcloud. """Get details about subcloud.
:param subcloud_ref: ID or name of subcloud :param subcloud_ref: ID or name of subcloud
@ -497,81 +348,86 @@ class SubcloudsController(object):
subcloud_id = subcloud.id subcloud_id = subcloud.id
if qualifier: # Data for this subcloud requested
# Configuration for this subcloud requested. # Build up and append a dictionary of the endpoints
# Encrypt before sending. # sync status to the result.
if qualifier == 'config': for subcloud, subcloud_status in db_api. \
result = dict() subcloud_get_with_status(context, subcloud_id):
user_list = self._get_subcloud_users() subcloud_dict = db_api.subcloud_db_model_to_dict(
subcloud)
# may be empty subcloud_status entry, account for this
if subcloud_status:
subcloud_status_list.append(
db_api.subcloud_endpoint_status_db_model_to_dict(
subcloud_status))
endpoint_sync_dict = {consts.ENDPOINT_SYNC_STATUS:
subcloud_status_list}
subcloud_dict.update(endpoint_sync_dict)
# Use a hash of the subcloud name + management subnet return subcloud_dict
# as the encryption key
hashstring = subcloud.name + subcloud.management_subnet
h = MD5.new()
h.update(hashstring)
encryption_key = h.hexdigest()
user_list_string = json.dumps(user_list)
user_list_encrypted = crypt.urlsafe_encrypt(
encryption_key,
user_list_string)
result['users'] = user_list_encrypted
return result
else:
pecan.abort(400, _('Invalid request'))
else:
# Data for this subcloud requested
# Build up and append a dictionary of the endpoints
# sync status to the result.
for subcloud, subcloud_status in db_api. \
subcloud_get_with_status(context, subcloud_id):
subcloud_dict = db_api.subcloud_db_model_to_dict(
subcloud)
# may be empty subcloud_status entry, account for this
if subcloud_status:
subcloud_status_list.append(
db_api.subcloud_endpoint_status_db_model_to_dict(
subcloud_status))
endpoint_sync_dict = {consts.ENDPOINT_SYNC_STATUS:
subcloud_status_list}
subcloud_dict.update(endpoint_sync_dict)
return subcloud_dict
@index.when(method='POST', template='json') @index.when(method='POST', template='json')
def post(self, subcloud_ref=None, qualifier=None): def post(self, subcloud_ref=None):
"""Create a new subcloud. """Create and deploy a new subcloud.
:param subcloud_ref: ID of or name subcloud (only used when generating :param subcloud_ref: ID of or name subcloud (only used when generating
config) config)
:param qualifier: if 'config', returns the config INI file for the
subcloud
""" """
context = restcomm.extract_context_from_environ() context = restcomm.extract_context_from_environ()
if subcloud_ref is None: if subcloud_ref is None:
payload = eval(request.body) payload = eval(request.body)
if not payload: if not payload:
pecan.abort(400, _('Body required')) pecan.abort(400, _('Body required'))
name = payload.get('name') name = payload.get('name')
if not name: if not name:
pecan.abort(400, _('name required')) pecan.abort(400, _('name required'))
management_subnet = payload.get('management-subnet') system_mode = payload.get('system_mode')
if not system_mode:
pecan.abort(400, _('system_mode required'))
management_subnet = payload.get('management_subnet')
if not management_subnet: if not management_subnet:
pecan.abort(400, _('management-subnet required')) pecan.abort(400, _('management_subnet required'))
management_start_ip = payload.get('management-start-ip')
management_start_ip = payload.get('management_start_address')
if not management_start_ip: if not management_start_ip:
pecan.abort(400, _('management-start-ip required')) pecan.abort(400, _('management_start_address required'))
management_end_ip = payload.get('management-end-ip')
management_end_ip = payload.get('management_end_address')
if not management_end_ip: if not management_end_ip:
pecan.abort(400, _('management-end-ip required')) pecan.abort(400, _('management_end_address required'))
management_gateway_ip = payload.get('management-gateway-ip')
management_gateway_ip = payload.get('management_gateway_address')
if not management_gateway_ip: if not management_gateway_ip:
pecan.abort(400, _('management-gateway-ip required')) pecan.abort(400, _('management_gateway_address required'))
systemcontroller_gateway_ip = \ systemcontroller_gateway_ip = \
payload.get('systemcontroller-gateway-ip') payload.get('systemcontroller_gateway_address')
if not systemcontroller_gateway_ip: if not systemcontroller_gateway_ip:
pecan.abort(400, _('systemcontroller-gateway-ip required')) pecan.abort(400,
_('systemcontroller_gateway_address required'))
external_oam_subnet = payload.get('external_oam_subnet')
if not external_oam_subnet:
pecan.abort(400, _('external_oam_subnet required'))
external_oam_gateway_ip = \
payload.get('external_oam_gateway_address')
if not external_oam_gateway_ip:
pecan.abort(400, _('external_oam_gateway_address required'))
external_oam_floating_ip = \
payload.get('external_oam_floating_address')
if not external_oam_floating_ip:
pecan.abort(400, _('external_oam_floating_address required'))
subcloud_password = \
payload.get('subcloud_password')
if not subcloud_password:
pecan.abort(400, _('subcloud_password required'))
self._validate_subcloud_config(context, self._validate_subcloud_config(context,
name, name,
@ -579,6 +435,9 @@ class SubcloudsController(object):
management_start_ip, management_start_ip,
management_end_ip, management_end_ip,
management_gateway_ip, management_gateway_ip,
external_oam_subnet,
external_oam_gateway_ip,
external_oam_floating_ip,
systemcontroller_gateway_ip) systemcontroller_gateway_ip)
try: try:
@ -590,34 +449,6 @@ class SubcloudsController(object):
except Exception as e: except Exception as e:
LOG.exception(e) LOG.exception(e)
pecan.abort(500, _('Unable to create subcloud')) pecan.abort(500, _('Unable to create subcloud'))
elif qualifier:
if qualifier == 'config':
subcloud = None
if subcloud_ref.isdigit():
# Look up subcloud as an ID
try:
subcloud = db_api.subcloud_get(context, subcloud_ref)
except exceptions.SubcloudNotFound:
pecan.abort(404, _('Subcloud not found'))
else:
# Look up subcloud by name
try:
subcloud = db_api.subcloud_get_by_name(context,
subcloud_ref)
except exceptions.SubcloudNameNotFound:
pecan.abort(404, _('Subcloud not found'))
payload = dict()
if request.body:
payload = eval(request.body)
config_file = self._create_subcloud_config_file(
context, subcloud, payload)
result = dict()
result['config'] = config_file
return result
else:
pecan.abort(400, _('Invalid request'))
else: else:
pecan.abort(400, _('Invalid request')) pecan.abort(400, _('Invalid request'))

View File

@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # 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 # The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms # of this software may be licensed only pursuant to the terms
@ -88,3 +88,9 @@ STRATEGY_STATE_ABORTED = "aborted"
STRATEGY_STATE_FAILED = "failed" STRATEGY_STATE_FAILED = "failed"
SW_UPDATE_DEFAULT_TITLE = "all clouds default" SW_UPDATE_DEFAULT_TITLE = "all clouds default"
# Subcloud deploy status states
DEPLOY_STATE_NONE = 'not-deployed'
DEPLOY_STATE_DEPLOYING = 'deploying'
DEPLOY_STATE_DONE = 'complete'
DEPLOY_STATE_FAILED = 'failed'

View File

@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # 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 # The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms # of this software may be licensed only pursuant to the terms
@ -58,6 +58,7 @@ def subcloud_db_model_to_dict(subcloud):
"software-version": subcloud.software_version, "software-version": subcloud.software_version,
"management-state": subcloud.management_state, "management-state": subcloud.management_state,
"availability-status": subcloud.availability_status, "availability-status": subcloud.availability_status,
"deploy-status": subcloud.deploy_status,
"management-subnet": subcloud.management_subnet, "management-subnet": subcloud.management_subnet,
"management-start-ip": subcloud.management_start_ip, "management-start-ip": subcloud.management_start_ip,
"management-end-ip": subcloud.management_end_ip, "management-end-ip": subcloud.management_end_ip,
@ -72,13 +73,13 @@ def subcloud_db_model_to_dict(subcloud):
def subcloud_create(context, name, description, location, software_version, def subcloud_create(context, name, description, location, software_version,
management_subnet, management_gateway_ip, management_subnet, management_gateway_ip,
management_start_ip, management_end_ip, management_start_ip, management_end_ip,
systemcontroller_gateway_ip): systemcontroller_gateway_ip, deploy_status):
"""Create a subcloud.""" """Create a subcloud."""
return IMPL.subcloud_create(context, name, description, location, return IMPL.subcloud_create(context, name, description, location,
software_version, software_version,
management_subnet, management_gateway_ip, management_subnet, management_gateway_ip,
management_start_ip, management_end_ip, management_start_ip, management_end_ip,
systemcontroller_gateway_ip) systemcontroller_gateway_ip, deploy_status)
def subcloud_get(context, subcloud_id): def subcloud_get(context, subcloud_id):
@ -108,11 +109,13 @@ def subcloud_get_all_with_status(context):
def subcloud_update(context, subcloud_id, management_state=None, def subcloud_update(context, subcloud_id, management_state=None,
availability_status=None, software_version=None, availability_status=None, software_version=None,
description=None, location=None, audit_fail_count=None): description=None, location=None, audit_fail_count=None,
deploy_status=None):
"""Update a subcloud or raise if it does not exist.""" """Update a subcloud or raise if it does not exist."""
return IMPL.subcloud_update(context, subcloud_id, management_state, return IMPL.subcloud_update(context, subcloud_id, management_state,
availability_status, software_version, availability_status, software_version,
description, location, audit_fail_count) description, location, audit_fail_count,
deploy_status)
def subcloud_destroy(context, subcloud_id): def subcloud_destroy(context, subcloud_id):

View File

@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # 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 # The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms # of this software may be licensed only pursuant to the terms
@ -205,7 +205,7 @@ def subcloud_get_all_with_status(context):
def subcloud_create(context, name, description, location, software_version, def subcloud_create(context, name, description, location, software_version,
management_subnet, management_gateway_ip, management_subnet, management_gateway_ip,
management_start_ip, management_end_ip, management_start_ip, management_end_ip,
systemcontroller_gateway_ip): systemcontroller_gateway_ip, deploy_status):
with write_session() as session: with write_session() as session:
subcloud_ref = models.Subcloud() subcloud_ref = models.Subcloud()
subcloud_ref.name = name subcloud_ref.name = name
@ -219,6 +219,7 @@ def subcloud_create(context, name, description, location, software_version,
subcloud_ref.management_start_ip = management_start_ip subcloud_ref.management_start_ip = management_start_ip
subcloud_ref.management_end_ip = management_end_ip subcloud_ref.management_end_ip = management_end_ip
subcloud_ref.systemcontroller_gateway_ip = systemcontroller_gateway_ip subcloud_ref.systemcontroller_gateway_ip = systemcontroller_gateway_ip
subcloud_ref.deploy_status = deploy_status
subcloud_ref.audit_fail_count = 0 subcloud_ref.audit_fail_count = 0
session.add(subcloud_ref) session.add(subcloud_ref)
return subcloud_ref return subcloud_ref
@ -227,7 +228,8 @@ def subcloud_create(context, name, description, location, software_version,
@require_admin_context @require_admin_context
def subcloud_update(context, subcloud_id, management_state=None, def subcloud_update(context, subcloud_id, management_state=None,
availability_status=None, software_version=None, availability_status=None, software_version=None,
description=None, location=None, audit_fail_count=None): description=None, location=None, audit_fail_count=None,
deploy_status=None):
with write_session() as session: with write_session() as session:
subcloud_ref = subcloud_get(context, subcloud_id) subcloud_ref = subcloud_get(context, subcloud_id)
if management_state is not None: if management_state is not None:
@ -242,6 +244,8 @@ def subcloud_update(context, subcloud_id, management_state=None,
subcloud_ref.location = location subcloud_ref.location = location
if audit_fail_count is not None: if audit_fail_count is not None:
subcloud_ref.audit_fail_count = audit_fail_count subcloud_ref.audit_fail_count = audit_fail_count
if deploy_status is not None:
subcloud_ref.deploy_status = deploy_status
subcloud_ref.save(session) subcloud_ref.save(session)
return subcloud_ref return subcloud_ref

View File

@ -0,0 +1,36 @@
# 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.
#
# Copyright (c) 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.
#
from sqlalchemy import Column, MetaData, String, Table
def upgrade(migrate_engine):
meta = MetaData()
meta.bind = migrate_engine
subclouds = Table('subclouds', meta, autoload=True)
# Add the 'deploy_status' column to the subclouds table.
subclouds.create_column(Column('deploy_status', String(255)))
return True
def downgrade(migrate_engine):
raise NotImplementedError('Database downgrade is unsupported.')

View File

@ -87,6 +87,7 @@ class Subcloud(BASE, DCManagerBase):
software_version = Column(String(255)) software_version = Column(String(255))
management_state = Column(String(255)) management_state = Column(String(255))
availability_status = Column(String(255)) availability_status = Column(String(255))
deploy_status = Column(String(255))
management_subnet = Column(String(255)) management_subnet = Column(String(255))
management_gateway_ip = Column(String(255)) management_gateway_ip = Column(String(255))
management_start_ip = Column(String(255), unique=True) management_start_ip = Column(String(255), unique=True)

View File

@ -13,16 +13,20 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# #
# Copyright (c) 2017-2018 Wind River Systems, Inc. # Copyright (c) 2017-2019 Wind River Systems, Inc.
# #
# The right to copy, distribute, modify, or otherwise make use # The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms # of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement. # of an applicable Wind River license agreement.
# #
import datetime
import filecmp import filecmp
import keyring
import netaddr import netaddr
import os import os
import subprocess
import threading
from oslo_log import log as logging from oslo_log import log as logging
from oslo_messaging import RemoteError from oslo_messaging import RemoteError
@ -51,6 +55,24 @@ LOG = logging.getLogger(__name__)
# to read. This file is referenced in dnsmasq.conf # to read. This file is referenced in dnsmasq.conf
ADDN_HOSTS_DC = 'dnsmasq.addn_hosts_dc' ADDN_HOSTS_DC = 'dnsmasq.addn_hosts_dc'
# Subcloud configuration paths
ANSIBLE_OVERRIDES_PATH = '/opt/dc/ansible'
ANSIBLE_SUBCLOUD_INVENTORY_FILE = 'subclouds.yml'
ANSIBLE_SUBCLOUD_PLAYBOOK = \
'/usr/share/ansible/stx-ansible/playbooks/bootstrap/bootstrap.yml'
DC_LOG_DIR = '/var/log/dcmanager/'
USERS_TO_REPLICATE = [
'sysinv',
'patching',
'vim',
'mtce',
'fm',
'barbican']
SERVICES_USER = 'services'
class SubcloudManager(manager.Manager): class SubcloudManager(manager.Manager):
"""Manages tasks related to subclouds.""" """Manages tasks related to subclouds."""
@ -92,11 +114,12 @@ class SubcloudManager(manager.Manager):
payload.get('description'), payload.get('description'),
payload.get('location'), payload.get('location'),
software_version, software_version,
payload['management-subnet'], payload['management_subnet'],
payload['management-gateway-ip'], payload['management_gateway_address'],
payload['management-start-ip'], payload['management_start_address'],
payload['management-end-ip'], payload['management_end_address'],
payload['systemcontroller-gateway-ip']) payload['systemcontroller_gateway_address'],
consts.DEPLOY_STATE_NONE)
except Exception as e: except Exception as e:
LOG.exception(e) LOG.exception(e)
raise e raise e
@ -111,7 +134,7 @@ class SubcloudManager(manager.Manager):
# Create a new route to this subcloud on the management interface # Create a new route to this subcloud on the management interface
# on both controllers. # on both controllers.
m_ks_client = KeystoneClient() m_ks_client = KeystoneClient()
subcloud_subnet = netaddr.IPNetwork(payload['management-subnet']) subcloud_subnet = netaddr.IPNetwork(payload['management_subnet'])
session = m_ks_client.endpoint_cache.get_session_from_token( session = m_ks_client.endpoint_cache.get_session_from_token(
context.auth_token, context.project) context.auth_token, context.project)
sysinv_client = SysinvClient(consts.DEFAULT_REGION_NAME, session) sysinv_client = SysinvClient(consts.DEFAULT_REGION_NAME, session)
@ -124,7 +147,7 @@ class SubcloudManager(manager.Manager):
management_interface.uuid, management_interface.uuid,
str(subcloud_subnet.ip), str(subcloud_subnet.ip),
subcloud_subnet.prefixlen, subcloud_subnet.prefixlen,
payload['systemcontroller-gateway-ip'], payload['systemcontroller_gateway_address'],
1) 1)
# Create identity endpoints to this subcloud on the # Create identity endpoints to this subcloud on the
@ -145,7 +168,7 @@ class SubcloudManager(manager.Manager):
resource='subcloud', resource='subcloud',
msg='No Identity service found on SystemController') msg='No Identity service found on SystemController')
identity_endpoint_ip = payload['management-start-ip'] identity_endpoint_ip = payload['management_start_address']
if netaddr.IPAddress(identity_endpoint_ip).version == 6: if netaddr.IPAddress(identity_endpoint_ip).version == 6:
identity_endpoint_url = \ identity_endpoint_url = \
@ -168,6 +191,51 @@ class SubcloudManager(manager.Manager):
# Regenerate the addn_hosts_dc file # Regenerate the addn_hosts_dc file
self._create_addn_hosts_dc(context) self._create_addn_hosts_dc(context)
# Add the admin and service user passwords to the payload so they
# get copied to the override file
payload['ansible_become_pass'] = payload['subcloud_password']
payload['ansible_ssh_pass'] = payload['subcloud_password']
payload['admin_password'] = keyring.get_password('CGCS', 'admin')
del payload['subcloud_password']
payload['users'] = dict()
for user in USERS_TO_REPLICATE:
payload['users'][user] = \
keyring.get_password(user, SERVICES_USER)
# Update the ansible inventory with the new subcloud
self._update_subcloud_inventory(payload)
# Write this subclouds overrides to file
self._write_subcloud_ansible_config(context, payload)
# Update the subcloud to deploying
try:
db_api.subcloud_update(
context, subcloud.id,
deploy_status=consts.DEPLOY_STATE_DEPLOYING)
except Exception as e:
LOG.exception(e)
raise e
apply_command = [
"ansible-playbook", ANSIBLE_SUBCLOUD_PLAYBOOK, "-i",
ANSIBLE_OVERRIDES_PATH + '/' +
ANSIBLE_SUBCLOUD_INVENTORY_FILE,
"--limit", subcloud.name
]
# Add the overrides dir and region_name so the playbook knows
# which overrides to load
apply_command += [
"-e", str("override_files_dir='%s' region_name=%s") % (
ANSIBLE_OVERRIDES_PATH, subcloud.name)]
apply_thread = threading.Thread(
target=self.run_bootstrap,
args=(apply_command, subcloud, context))
apply_thread.start()
return db_api.subcloud_db_model_to_dict(subcloud) return db_api.subcloud_db_model_to_dict(subcloud)
except Exception as e: except Exception as e:
@ -178,6 +246,34 @@ class SubcloudManager(manager.Manager):
db_api.subcloud_destroy(context, subcloud.id) db_api.subcloud_destroy(context, subcloud.id)
raise e raise e
@staticmethod
def run_bootstrap(apply_command, subcloud, context):
# Run the ansible boostrap-subcloud playbook
with open(DC_LOG_DIR + subcloud.name + '_' +
str(datetime.datetime.now().strftime('%Y-%m-%d-%H-%M-%S')) +
'.log', "w") as f_out_log:
try:
subprocess.check_call(apply_command,
stdout=f_out_log,
stderr=f_out_log)
except subprocess.CalledProcessError as ex:
msg = "Failed to run the subcloud bootstrap playbook" \
" for subcloud %s, check individual log at " \
"%s for detailed output." % (
subcloud.name,
DC_LOG_DIR + subcloud.name)
ex.cmd = 'ansible-playbook'
LOG.error(msg)
db_api.subcloud_update(
context, subcloud.id,
deploy_status=consts.DEPLOY_STATE_FAILED)
return
LOG.info("Successfully deployed subcloud %s" %
subcloud.name)
db_api.subcloud_update(
context, subcloud.id,
deploy_status=consts.DEPLOY_STATE_DONE)
def _create_addn_hosts_dc(self, context): def _create_addn_hosts_dc(self, context):
"""Generate the addn_hosts_dc file for hostname/ip translation""" """Generate the addn_hosts_dc file for hostname/ip translation"""
@ -201,6 +297,58 @@ class SubcloudManager(manager.Manager):
# restart dnsmasq so it can re-read our addn_hosts file. # restart dnsmasq so it can re-read our addn_hosts file.
os.system("pkill -HUP dnsmasq") os.system("pkill -HUP dnsmasq")
def _update_subcloud_inventory(self, new_subcloud):
"""Update the inventory file for usage with the specified subcloud"""
inventory_file = os.path.join(ANSIBLE_OVERRIDES_PATH,
ANSIBLE_SUBCLOUD_INVENTORY_FILE)
exists = False
if os.path.isfile(inventory_file):
exists = True
with open(inventory_file, 'a') as f_out_inventory:
if not exists:
f_out_inventory.write(
'---\n'
'all:\n'
' vars:\n'
' ansible_ssh_user: sysadmin\n'
' hosts:\n',
)
f_out_inventory.write(
' ' + new_subcloud['name'] + ':\n'
' ansible_host: ' +
new_subcloud['bootstrap-address'] + '\n'
)
def _write_subcloud_ansible_config(self, context, payload):
"""Create the override file for usage with the specified subcloud"""
overrides_file = os.path.join(ANSIBLE_OVERRIDES_PATH,
payload['name'] + '.yml')
m_ks_client = KeystoneClient()
session = m_ks_client.endpoint_cache.get_session_from_token(
context.auth_token, context.project)
sysinv_client = SysinvClient(consts.DEFAULT_REGION_NAME, session)
pool = sysinv_client.get_management_address_pool()
floating_ip = pool.floating_address
subnet = "%s/%d" % (pool.network, pool.prefix)
with open(overrides_file, 'w') as f_out_overrides_file:
f_out_overrides_file.write(
'---'
'\nregion_config: yes'
'\ndistributed_cloud_role: subcloud'
'\nsystem_controller_subnet: ' + subnet +
'\nsystem_controller_floating_address: ' + floating_ip + '\n'
)
for k, v in payload.items():
f_out_overrides_file.write("%s: %s\n" % (k, v))
def _delete_subcloud_routes(self, context, subcloud): def _delete_subcloud_routes(self, context, subcloud):
"""Delete the routes to this subcloud""" """Delete the routes to this subcloud"""

View File

@ -1,5 +1,5 @@
{ {
"subclouds_0": [6, "subcloud-4", "wcp85 subcloud", "Ottawa-PheonixLab-Aisle_3-Rack_C", "18.03", "managed", "online", "fd01:3::0/64", "fd01:3::1", "fd01:3::2", "fd01:3::f", "fd01:1::1", 0, "NULL", "NULL", "2018-05-15 14:45:12.508708", "2018-05-24 10:48:18.090931", "NULL", 0], "subclouds_0": [6, "subcloud-4", "wcp85 subcloud", "Ottawa-PheonixLab-Aisle_3-Rack_C", "18.03", "managed", "online", "fd01:3::0/64", "fd01:3::1", "fd01:3::2", "fd01:3::f", "fd01:1::1", 0, "NULL", "NULL", "2018-05-15 14:45:12.508708", "2018-05-24 10:48:18.090931", "NULL", 0, "10.10.10.0/24", "10.10.10.1", "10.10.10.12", "testpass"],
"subclouds_1": [1, "subcloud-1", "wcp80 subcloud", "Ottawa-PheonixLab-Aisle_3-Rack_B", "18.03", "managed", "online", "fd01:2::0/64", "fd01:2::1", "fd01:2::2", "fd01:2::f", "fd01:1::1", 0, "NULL", "NULL", "2018-04-11 17:01:48.54467", "2018-05-24 00:17:34.74161", "NULL", 0], "subclouds_1": [1, "subcloud-1", "wcp80 subcloud", "Ottawa-PheonixLab-Aisle_3-Rack_B", "18.03", "managed", "online", "fd01:2::0/64", "fd01:2::1", "fd01:2::2", "fd01:2::f", "fd01:1::1", 0, "NULL", "NULL", "2018-04-11 17:01:48.54467", "2018-05-24 00:17:34.74161", "NULL", 0, "10.10.10.0/24", "10.10.10.1", "10.10.10.12", "testpass"],
"subclouds_2": [7, "subcloud-5", "wcp87 subcloud", "Ottawa-PheonixLab-Aisle_4-Rack_B", "18.03", "managed", "online", "fd01:4::0/64", "fd01:4::1", "fd01:4::2", "fd01:4::f", "fd01:1::1", 0, "NULL", "NULL", "2018-05-15 14:45:48.95625", "2018-05-24 10:48:17.907767", "NULL", 0] "subclouds_2": [7, "subcloud-5", "wcp87 subcloud", "Ottawa-PheonixLab-Aisle_4-Rack_B", "18.03", "managed", "online", "fd01:4::0/64", "fd01:4::1", "fd01:4::2", "fd01:4::f", "fd01:1::1", 0, "NULL", "NULL", "2018-05-15 14:45:48.95625", "2018-05-24 10:48:17.907767", "NULL", 0, "10.10.10.0/24", "10.10.10.1", "10.10.10.12", "testpass"]
} }

View File

@ -41,12 +41,17 @@ FAKE_HEADERS = {'X-Tenant-Id': FAKE_TENANT, 'X_ROLE': 'admin',
FAKE_SUBCLOUD_DATA = {"name": "subcloud1", FAKE_SUBCLOUD_DATA = {"name": "subcloud1",
"description": "subcloud1 description", "description": "subcloud1 description",
"location": "subcloud1 location", "location": "subcloud1 location",
"management-subnet": "192.168.101.0/24", "system_mode": "duplex",
"management-start-ip": "192.168.101.2", "management_subnet": "192.168.101.0/24",
"management-end-ip": "192.168.101.50", "management_start_address": "192.168.101.2",
"management-gateway-ip": "192.168.101.1", "management_end_address": "192.168.101.50",
"systemcontroller-gateway-ip": "192.168.204.101", "management_gateway_address": "192.168.101.1",
"availability-status": "disabled"} "systemcontroller_gateway_address": "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",
"availability-status": "disabled",
"subcloud_password": "testpass"}
class FakeAddressPool(object): class FakeAddressPool(object):
@ -92,7 +97,7 @@ class TestSubclouds(testroot.DCManagerApiTest):
def test_post_subcloud_bad_gateway(self, mock_db_api, mock_rpc_client, def test_post_subcloud_bad_gateway(self, mock_db_api, mock_rpc_client,
mock_get_management_address_pool): mock_get_management_address_pool):
data = copy.copy(FAKE_SUBCLOUD_DATA) data = copy.copy(FAKE_SUBCLOUD_DATA)
data["systemcontroller-gateway-ip"] = "192.168.205.101" data["systemcontroller_gateway_address"] = "192.168.205.101"
management_address_pool = FakeAddressPool('192.168.204.0', 24, management_address_pool = FakeAddressPool('192.168.204.0', 24,
'192.168.204.2', '192.168.204.2',
'192.168.204.100') '192.168.204.100')
@ -120,7 +125,7 @@ class TestSubclouds(testroot.DCManagerApiTest):
@mock.patch.object(subclouds, 'db_api') @mock.patch.object(subclouds, 'db_api')
def test_post_subcloud_bad_subnet(self, mock_db_api, mock_rpc_client): def test_post_subcloud_bad_subnet(self, mock_db_api, mock_rpc_client):
data = copy.copy(FAKE_SUBCLOUD_DATA) data = copy.copy(FAKE_SUBCLOUD_DATA)
data["management-subnet"] = "192.168.101.0/32" data["management_subnet"] = "192.168.101.0/32"
six.assertRaisesRegex(self, webtest.app.AppError, "400 *", six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.post_json, FAKE_URL, self.app.post_json, FAKE_URL,
headers=FAKE_HEADERS, params=data) headers=FAKE_HEADERS, params=data)
@ -129,9 +134,9 @@ class TestSubclouds(testroot.DCManagerApiTest):
@mock.patch.object(subclouds, 'db_api') @mock.patch.object(subclouds, 'db_api')
def test_post_subcloud_bad_start_ip(self, mock_db_api, mock_rpc_client): def test_post_subcloud_bad_start_ip(self, mock_db_api, mock_rpc_client):
data = copy.copy(FAKE_SUBCLOUD_DATA) data = copy.copy(FAKE_SUBCLOUD_DATA)
data["management-subnet"] = "192.168.101.0/24" data["management_subnet"] = "192.168.101.0/24"
data["management-start-ip"] = "192.168.100.2" data["management_start_address"] = "192.168.100.2"
data["management-end-ip"] = "192.168.100.50" data["management_end_address"] = "192.168.100.50"
six.assertRaisesRegex(self, webtest.app.AppError, "400 *", six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.post_json, FAKE_URL, self.app.post_json, FAKE_URL,
headers=FAKE_HEADERS, params=data) headers=FAKE_HEADERS, params=data)
@ -140,8 +145,8 @@ class TestSubclouds(testroot.DCManagerApiTest):
@mock.patch.object(subclouds, 'db_api') @mock.patch.object(subclouds, 'db_api')
def test_post_subcloud_bad_end_ip(self, mock_db_api, mock_rpc_client): def test_post_subcloud_bad_end_ip(self, mock_db_api, mock_rpc_client):
data = copy.copy(FAKE_SUBCLOUD_DATA) data = copy.copy(FAKE_SUBCLOUD_DATA)
data["management-start-ip"] = "192.168.101.2" data["management_start_address"] = "192.168.101.2"
data["management-end-ip"] = "192.168.100.100" data["management_end_address"] = "192.168.100.100"
six.assertRaisesRegex(self, webtest.app.AppError, "400 *", six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.post_json, FAKE_URL, self.app.post_json, FAKE_URL,
headers=FAKE_HEADERS, params=data) headers=FAKE_HEADERS, params=data)
@ -150,8 +155,8 @@ class TestSubclouds(testroot.DCManagerApiTest):
@mock.patch.object(subclouds, 'db_api') @mock.patch.object(subclouds, 'db_api')
def test_post_subcloud_short_ip_range(self, mock_db_api, mock_rpc_client): def test_post_subcloud_short_ip_range(self, mock_db_api, mock_rpc_client):
data = copy.copy(FAKE_SUBCLOUD_DATA) data = copy.copy(FAKE_SUBCLOUD_DATA)
data["management-start-ip"] = "192.168.101.2" data["management_start_address"] = "192.168.101.2"
data["management-end-ip"] = "192.168.101.4" data["management_end_address"] = "192.168.101.4"
six.assertRaisesRegex(self, webtest.app.AppError, "400 *", six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.post_json, FAKE_URL, self.app.post_json, FAKE_URL,
headers=FAKE_HEADERS, params=data) headers=FAKE_HEADERS, params=data)
@ -160,8 +165,8 @@ class TestSubclouds(testroot.DCManagerApiTest):
@mock.patch.object(subclouds, 'db_api') @mock.patch.object(subclouds, 'db_api')
def test_post_subcloud_invert_ip_range(self, mock_db_api, mock_rpc_client): def test_post_subcloud_invert_ip_range(self, mock_db_api, mock_rpc_client):
data = copy.copy(FAKE_SUBCLOUD_DATA) data = copy.copy(FAKE_SUBCLOUD_DATA)
data["management-start-ip"] = "192.168.101.20" data["management_start_address"] = "192.168.101.20"
data["management-end-ip"] = "192.168.101.4" data["management_end_address"] = "192.168.101.4"
six.assertRaisesRegex(self, webtest.app.AppError, "400 *", six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
self.app.post_json, FAKE_URL, self.app.post_json, FAKE_URL,
headers=FAKE_HEADERS, params=data) headers=FAKE_HEADERS, params=data)
@ -188,18 +193,6 @@ class TestSubclouds(testroot.DCManagerApiTest):
self.app.post_json, FAKE_URL, self.app.post_json, FAKE_URL,
headers=FAKE_HEADERS, params=data) headers=FAKE_HEADERS, params=data)
@mock.patch.object(subclouds.SubcloudsController,
'_create_subcloud_config_file')
@mock.patch.object(rpc_client, 'ManagerClient')
@mock.patch.object(subclouds, 'db_api')
def test_post_subcloud_config(self, mock_db_api, mock_rpc_client,
mock_create_config):
mock_create_config.return_value = "Some\n long multiline config data"
post_url = FAKE_URL + '/' + FAKE_ID + '/config'
self.app.post(post_url, headers=FAKE_HEADERS)
self.assertEqual(1, mock_db_api.subcloud_get.call_count)
self.assertEqual(1, mock_create_config.call_count)
@mock.patch.object(rpc_client, 'ManagerClient') @mock.patch.object(rpc_client, 'ManagerClient')
@mock.patch.object(subclouds, 'db_api') @mock.patch.object(subclouds, 'db_api')
def test_delete_subcloud(self, mock_db_api, mock_rpc_client): def test_delete_subcloud(self, mock_db_api, mock_rpc_client):

View File

@ -85,6 +85,7 @@ class DBAPISubcloudTest(base.DCManagerTestCase):
'management_start_ip': "192.168.101.2", 'management_start_ip': "192.168.101.2",
'management_end_ip': "192.168.101.50", 'management_end_ip': "192.168.101.50",
'systemcontroller_gateway_ip': "192.168.204.101", 'systemcontroller_gateway_ip': "192.168.204.101",
'deploy_status': "not-deployed",
} }
values.update(kwargs) values.update(kwargs)
return db_api.subcloud_create(ctxt, **values) return db_api.subcloud_create(ctxt, **values)
@ -96,11 +97,13 @@ class DBAPISubcloudTest(base.DCManagerTestCase):
'description': data['description'], 'description': data['description'],
'location': data['location'], 'location': data['location'],
'software_version': data['software-version'], 'software_version': data['software-version'],
'management_subnet': data['management-subnet'], 'management_subnet': data['management_subnet'],
'management_gateway_ip': data['management-gateway-ip'], 'management_gateway_ip': data['management_gateway_address'],
'management_start_ip': data['management-start-ip'], 'management_start_ip': data['management_start_address'],
'management_end_ip': data['management-end-ip'], 'management_end_ip': data['management_end_address'],
'systemcontroller_gateway_ip': data['systemcontroller-gateway-ip'], 'systemcontroller_gateway_ip': data[
'systemcontroller_gateway_address'],
'deploy_status': "not-deployed",
} }
return db_api.subcloud_create(ctxt, **values) return db_api.subcloud_create(ctxt, **values)

View File

@ -41,11 +41,15 @@ FAKE_ID = '1'
FAKE_SUBCLOUD_DATA = {"name": "subcloud1", FAKE_SUBCLOUD_DATA = {"name": "subcloud1",
"description": "subcloud1 description", "description": "subcloud1 description",
"location": "subcloud1 location", "location": "subcloud1 location",
"management-subnet": "192.168.101.0/24", "system_mode": "duplex",
"management-start-ip": "192.168.101.3", "management_subnet": "192.168.101.0/24",
"management-end-ip": "192.168.101.4", "management_start_address": "192.168.101.3",
"management-gateway-ip": "192.168.101.1", "management_end_address": "192.168.101.4",
"systemcontroller-gateway-ip": "192.168.204.101"} "management_gateway_address": "192.168.101.1",
"systemcontroller_gateway_address": "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"}
class Controller(object): class Controller(object):
@ -72,11 +76,17 @@ class Subcloud(object):
else: else:
self.availability_status = consts.AVAILABILITY_OFFLINE self.availability_status = consts.AVAILABILITY_OFFLINE
self.management_subnet = data['management-subnet'] self.management_subnet = data['management_subnet']
self.management_gateway_ip = data['management-gateway-ip'] self.management_gateway_ip = data['management_gateway_address']
self.management_start_ip = data['management-start-ip'] self.management_start_ip = data['management_start_address']
self.management_end_ip = data['management-end-ip'] self.management_end_ip = data['management_end_address']
self.systemcontroller_gateway_ip = data['systemcontroller-gateway-ip'] 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.created_at = timeutils.utcnow()
self.updated_at = timeutils.utcnow() self.updated_at = timeutils.utcnow()
@ -106,7 +116,15 @@ class TestSubcloudManager(base.DCManagerTestCase):
@mock.patch.object(subcloud_manager, 'SysinvClient') @mock.patch.object(subcloud_manager, 'SysinvClient')
@mock.patch.object(subcloud_manager.SubcloudManager, @mock.patch.object(subcloud_manager.SubcloudManager,
'_create_addn_hosts_dc') '_create_addn_hosts_dc')
def test_add_subcloud(self, value, @mock.patch.object(subcloud_manager.SubcloudManager,
'_update_subcloud_inventory')
@mock.patch.object(subcloud_manager.SubcloudManager,
'_write_subcloud_ansible_config')
@mock.patch.object(subcloud_manager,
'keyring')
def test_add_subcloud(self, value, mock_keyring,
mock_write_subcloud_ansible_config,
mock_update_subcloud_inventory,
mock_create_addn_hosts, mock_sysinv_client, mock_create_addn_hosts, mock_sysinv_client,
mock_db_api, mock_keystone_client, mock_context, mock_db_api, mock_keystone_client, mock_context,
mock_dcorch_rpc_client): mock_dcorch_rpc_client):
@ -119,6 +137,7 @@ class TestSubcloudManager(base.DCManagerTestCase):
mock_sysinv_client().get_controller_hosts.return_value = controllers mock_sysinv_client().get_controller_hosts.return_value = controllers
mock_keystone_client().services_list = services mock_keystone_client().services_list = services
mock_keyring.get_password.return_value = "testpassword"
sm = subcloud_manager.SubcloudManager() sm = subcloud_manager.SubcloudManager()
sm.add_subcloud(self.ctxt, payload=value) sm.add_subcloud(self.ctxt, payload=value)
@ -127,6 +146,10 @@ class TestSubcloudManager(base.DCManagerTestCase):
mock_sysinv_client().create_route.assert_called() mock_sysinv_client().create_route.assert_called()
mock_dcorch_rpc_client().add_subcloud.assert_called_once() mock_dcorch_rpc_client().add_subcloud.assert_called_once()
mock_create_addn_hosts.assert_called_once() mock_create_addn_hosts.assert_called_once()
mock_update_subcloud_inventory.assert_called_once()
mock_write_subcloud_ansible_config.assert_called_once()
mock_db_api.subcloud_update.assert_called()
mock_keyring.get_password.assert_called()
@file_data(utils.get_data_filepath('dcmanager', 'subclouds')) @file_data(utils.get_data_filepath('dcmanager', 'subclouds'))
@mock.patch.object(dcorch_rpc_client, 'EngineClient') @mock.patch.object(dcorch_rpc_client, 'EngineClient')

View File

@ -116,18 +116,22 @@ def create_subcloud_dict(data_list):
'software-version': data_list[4], 'software-version': data_list[4],
'management-state': data_list[5], 'management-state': data_list[5],
'availability-status': data_list[6], 'availability-status': data_list[6],
'management-subnet': data_list[7], 'management_subnet': data_list[7],
'management-gateway-ip': data_list[8], 'management_gateway_address': data_list[8],
'management-start-ip': data_list[9], 'management_start_address': data_list[9],
'management-end-ip': data_list[10], 'management_end_address': data_list[10],
'systemcontroller-gateway-ip': data_list[11], 'systemcontroller_gateway_address': data_list[11],
'audit-fail-count': data_list[12], 'audit-fail-count': data_list[12],
'reserved-1': data_list[13], 'reserved-1': data_list[13],
'reserved-2': data_list[14], 'reserved-2': data_list[14],
'created-at': data_list[15], 'created-at': data_list[15],
'updated-at': data_list[16], 'updated-at': data_list[16],
'deleted-at': data_list[17], 'deleted-at': data_list[17],
'deleted': data_list[18]} 'deleted': data_list[18],
'external_oam_subnet': data_list[19],
'external_oam_gateway_address': data_list[20],
'external_oam_floating_address': data_list[21],
'subcloud_password': data_list[22]}
def create_route_dict(data_list): def create_route_dict(data_list):