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:
parent
a3f595fe18
commit
8c2bd5fa14
@ -30,7 +30,6 @@ import pecan
|
||||
from pecan import expose
|
||||
from pecan import request
|
||||
|
||||
from controllerconfig.common import crypt
|
||||
from controllerconfig.common.exceptions import ValidateFail
|
||||
from controllerconfig.utils import validate_address_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.rpc import client as rpc_client
|
||||
|
||||
from Crypto.Hash import MD5
|
||||
import json
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -82,6 +79,9 @@ class SubcloudsController(object):
|
||||
management_start_ip_str,
|
||||
management_end_ip_str,
|
||||
management_gateway_ip_str,
|
||||
external_oam_subnet_str,
|
||||
external_oam_gateway_address_str,
|
||||
external_oam_floating_address_str,
|
||||
systemcontroller_gateway_ip_str):
|
||||
"""Check whether subcloud config is valid."""
|
||||
|
||||
@ -113,7 +113,7 @@ class SubcloudsController(object):
|
||||
existing_networks=subcloud_subnets)
|
||||
except ValidateFail as 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
|
||||
management_start_ip = None
|
||||
@ -122,7 +122,7 @@ class SubcloudsController(object):
|
||||
management_start_ip_str, management_subnet)
|
||||
except ValidateFail as 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
|
||||
try:
|
||||
@ -130,12 +130,13 @@ class SubcloudsController(object):
|
||||
management_end_ip_str, management_subnet)
|
||||
except ValidateFail as 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:
|
||||
pecan.abort(
|
||||
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)) >= \
|
||||
MIN_MANAGEMENT_ADDRESSES:
|
||||
@ -150,7 +151,7 @@ class SubcloudsController(object):
|
||||
management_gateway_ip_str, management_subnet)
|
||||
except ValidateFail as 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
|
||||
# management subnet address pool for consistency with the
|
||||
@ -161,7 +162,7 @@ class SubcloudsController(object):
|
||||
subcloud_mgmt_gw_ip = IPAddress(management_gateway_ip_str)
|
||||
if ((subcloud_mgmt_gw_ip >= subcloud_mgmt_address_start) and
|
||||
(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 - "
|
||||
"%(end)s") %
|
||||
{'start': subcloud_mgmt_address_start,
|
||||
@ -179,7 +180,8 @@ class SubcloudsController(object):
|
||||
systemcontroller_gateway_ip_str, systemcontroller_subnet)
|
||||
except ValidateFail as 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
|
||||
# management subnet address pool to prevent address collision.
|
||||
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)
|
||||
if ((systemcontroller_gw_ip >= mgmt_address_start) and
|
||||
(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 - "
|
||||
"%(end)s") %
|
||||
{'start': mgmt_address_start, 'end': mgmt_address_end})
|
||||
|
||||
def _create_subcloud_config_file(self, context, subcloud, payload):
|
||||
"""Creates the subcloud config file for a subcloud."""
|
||||
DEFAULT_STR = '<EDIT>'
|
||||
# Parse/validate the oam subnet
|
||||
MIN_OAM_SUBNET_SIZE = 3
|
||||
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(
|
||||
'pxe-subnet', DEFAULT_STR)
|
||||
management_vlan = payload.get(
|
||||
'management-vlan', DEFAULT_STR)
|
||||
management_interface_mtu = payload.get(
|
||||
'management-interface-mtu', DEFAULT_STR)
|
||||
management_interface_ports = payload.get(
|
||||
'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)
|
||||
# Parse/validate the addresses
|
||||
try:
|
||||
validate_address_str(
|
||||
external_oam_gateway_address_str, oam_subnet)
|
||||
except ValidateFail as e:
|
||||
LOG.exception(e)
|
||||
pecan.abort(400, _("oam_gateway_address invalid: %s") % e)
|
||||
|
||||
management_address_pool = self._get_management_address_pool(context)
|
||||
systemcontroller_subnet = "%s/%d" % (
|
||||
management_address_pool.network,
|
||||
management_address_pool.prefix)
|
||||
sc_mgmt_floating_ip = management_address_pool.floating_address
|
||||
|
||||
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
|
||||
try:
|
||||
validate_address_str(
|
||||
external_oam_floating_address_str, oam_subnet)
|
||||
except ValidateFail as e:
|
||||
LOG.exception(e)
|
||||
pecan.abort(400, _("oam_floating_address invalid: %s") % e)
|
||||
|
||||
def _get_subcloud_users(self):
|
||||
"""Get the subcloud users and passwords from keyring"""
|
||||
@ -410,7 +261,7 @@ class SubcloudsController(object):
|
||||
return sysinv_client.get_management_address_pool()
|
||||
|
||||
@index.when(method='GET', template='json')
|
||||
def get(self, subcloud_ref=None, qualifier=None):
|
||||
def get(self, subcloud_ref=None):
|
||||
"""Get details about subcloud.
|
||||
|
||||
:param subcloud_ref: ID or name of subcloud
|
||||
@ -497,81 +348,86 @@ class SubcloudsController(object):
|
||||
|
||||
subcloud_id = subcloud.id
|
||||
|
||||
if qualifier:
|
||||
# Configuration for this subcloud requested.
|
||||
# Encrypt before sending.
|
||||
if qualifier == 'config':
|
||||
result = dict()
|
||||
user_list = self._get_subcloud_users()
|
||||
# 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)
|
||||
|
||||
# Use a hash of the subcloud name + management subnet
|
||||
# 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
|
||||
return subcloud_dict
|
||||
|
||||
@index.when(method='POST', template='json')
|
||||
def post(self, subcloud_ref=None, qualifier=None):
|
||||
"""Create a new subcloud.
|
||||
def post(self, subcloud_ref=None):
|
||||
"""Create and deploy a new subcloud.
|
||||
|
||||
:param subcloud_ref: ID of or name subcloud (only used when generating
|
||||
config)
|
||||
:param qualifier: if 'config', returns the config INI file for the
|
||||
subcloud
|
||||
"""
|
||||
|
||||
context = restcomm.extract_context_from_environ()
|
||||
|
||||
if subcloud_ref is None:
|
||||
payload = eval(request.body)
|
||||
|
||||
if not payload:
|
||||
pecan.abort(400, _('Body required'))
|
||||
name = payload.get('name')
|
||||
if not name:
|
||||
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:
|
||||
pecan.abort(400, _('management-subnet required'))
|
||||
management_start_ip = payload.get('management-start-ip')
|
||||
pecan.abort(400, _('management_subnet required'))
|
||||
|
||||
management_start_ip = payload.get('management_start_address')
|
||||
if not management_start_ip:
|
||||
pecan.abort(400, _('management-start-ip required'))
|
||||
management_end_ip = payload.get('management-end-ip')
|
||||
pecan.abort(400, _('management_start_address required'))
|
||||
|
||||
management_end_ip = payload.get('management_end_address')
|
||||
if not management_end_ip:
|
||||
pecan.abort(400, _('management-end-ip required'))
|
||||
management_gateway_ip = payload.get('management-gateway-ip')
|
||||
pecan.abort(400, _('management_end_address required'))
|
||||
|
||||
management_gateway_ip = payload.get('management_gateway_address')
|
||||
if not management_gateway_ip:
|
||||
pecan.abort(400, _('management-gateway-ip required'))
|
||||
pecan.abort(400, _('management_gateway_address required'))
|
||||
|
||||
systemcontroller_gateway_ip = \
|
||||
payload.get('systemcontroller-gateway-ip')
|
||||
payload.get('systemcontroller_gateway_address')
|
||||
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,
|
||||
name,
|
||||
@ -579,6 +435,9 @@ class SubcloudsController(object):
|
||||
management_start_ip,
|
||||
management_end_ip,
|
||||
management_gateway_ip,
|
||||
external_oam_subnet,
|
||||
external_oam_gateway_ip,
|
||||
external_oam_floating_ip,
|
||||
systemcontroller_gateway_ip)
|
||||
|
||||
try:
|
||||
@ -590,34 +449,6 @@ class SubcloudsController(object):
|
||||
except Exception as e:
|
||||
LOG.exception(e)
|
||||
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:
|
||||
pecan.abort(400, _('Invalid request'))
|
||||
|
||||
|
@ -12,7 +12,7 @@
|
||||
# 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
|
||||
@ -88,3 +88,9 @@ STRATEGY_STATE_ABORTED = "aborted"
|
||||
STRATEGY_STATE_FAILED = "failed"
|
||||
|
||||
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'
|
||||
|
@ -13,7 +13,7 @@
|
||||
# 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
|
||||
@ -58,6 +58,7 @@ def subcloud_db_model_to_dict(subcloud):
|
||||
"software-version": subcloud.software_version,
|
||||
"management-state": subcloud.management_state,
|
||||
"availability-status": subcloud.availability_status,
|
||||
"deploy-status": subcloud.deploy_status,
|
||||
"management-subnet": subcloud.management_subnet,
|
||||
"management-start-ip": subcloud.management_start_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,
|
||||
management_subnet, management_gateway_ip,
|
||||
management_start_ip, management_end_ip,
|
||||
systemcontroller_gateway_ip):
|
||||
systemcontroller_gateway_ip, deploy_status):
|
||||
"""Create a subcloud."""
|
||||
return IMPL.subcloud_create(context, name, description, location,
|
||||
software_version,
|
||||
management_subnet, management_gateway_ip,
|
||||
management_start_ip, management_end_ip,
|
||||
systemcontroller_gateway_ip)
|
||||
systemcontroller_gateway_ip, deploy_status)
|
||||
|
||||
|
||||
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,
|
||||
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."""
|
||||
return IMPL.subcloud_update(context, subcloud_id, management_state,
|
||||
availability_status, software_version,
|
||||
description, location, audit_fail_count)
|
||||
description, location, audit_fail_count,
|
||||
deploy_status)
|
||||
|
||||
|
||||
def subcloud_destroy(context, subcloud_id):
|
||||
|
@ -13,7 +13,7 @@
|
||||
# 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
|
||||
@ -205,7 +205,7 @@ def subcloud_get_all_with_status(context):
|
||||
def subcloud_create(context, name, description, location, software_version,
|
||||
management_subnet, management_gateway_ip,
|
||||
management_start_ip, management_end_ip,
|
||||
systemcontroller_gateway_ip):
|
||||
systemcontroller_gateway_ip, deploy_status):
|
||||
with write_session() as session:
|
||||
subcloud_ref = models.Subcloud()
|
||||
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_end_ip = management_end_ip
|
||||
subcloud_ref.systemcontroller_gateway_ip = systemcontroller_gateway_ip
|
||||
subcloud_ref.deploy_status = deploy_status
|
||||
subcloud_ref.audit_fail_count = 0
|
||||
session.add(subcloud_ref)
|
||||
return subcloud_ref
|
||||
@ -227,7 +228,8 @@ def subcloud_create(context, name, description, location, software_version,
|
||||
@require_admin_context
|
||||
def subcloud_update(context, subcloud_id, management_state=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:
|
||||
subcloud_ref = subcloud_get(context, subcloud_id)
|
||||
if management_state is not None:
|
||||
@ -242,6 +244,8 @@ def subcloud_update(context, subcloud_id, management_state=None,
|
||||
subcloud_ref.location = location
|
||||
if audit_fail_count is not None:
|
||||
subcloud_ref.audit_fail_count = audit_fail_count
|
||||
if deploy_status is not None:
|
||||
subcloud_ref.deploy_status = deploy_status
|
||||
subcloud_ref.save(session)
|
||||
return subcloud_ref
|
||||
|
||||
|
@ -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.')
|
@ -87,6 +87,7 @@ class Subcloud(BASE, DCManagerBase):
|
||||
software_version = Column(String(255))
|
||||
management_state = Column(String(255))
|
||||
availability_status = Column(String(255))
|
||||
deploy_status = Column(String(255))
|
||||
management_subnet = Column(String(255))
|
||||
management_gateway_ip = Column(String(255))
|
||||
management_start_ip = Column(String(255), unique=True)
|
||||
|
@ -13,16 +13,20 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# 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
|
||||
# of this software may be licensed only pursuant to the terms
|
||||
# of an applicable Wind River license agreement.
|
||||
#
|
||||
|
||||
import datetime
|
||||
import filecmp
|
||||
import keyring
|
||||
import netaddr
|
||||
import os
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_messaging import RemoteError
|
||||
@ -51,6 +55,24 @@ LOG = logging.getLogger(__name__)
|
||||
# to read. This file is referenced in dnsmasq.conf
|
||||
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):
|
||||
"""Manages tasks related to subclouds."""
|
||||
@ -92,11 +114,12 @@ class SubcloudManager(manager.Manager):
|
||||
payload.get('description'),
|
||||
payload.get('location'),
|
||||
software_version,
|
||||
payload['management-subnet'],
|
||||
payload['management-gateway-ip'],
|
||||
payload['management-start-ip'],
|
||||
payload['management-end-ip'],
|
||||
payload['systemcontroller-gateway-ip'])
|
||||
payload['management_subnet'],
|
||||
payload['management_gateway_address'],
|
||||
payload['management_start_address'],
|
||||
payload['management_end_address'],
|
||||
payload['systemcontroller_gateway_address'],
|
||||
consts.DEPLOY_STATE_NONE)
|
||||
except Exception as e:
|
||||
LOG.exception(e)
|
||||
raise e
|
||||
@ -111,7 +134,7 @@ class SubcloudManager(manager.Manager):
|
||||
# Create a new route to this subcloud on the management interface
|
||||
# on both controllers.
|
||||
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(
|
||||
context.auth_token, context.project)
|
||||
sysinv_client = SysinvClient(consts.DEFAULT_REGION_NAME, session)
|
||||
@ -124,7 +147,7 @@ class SubcloudManager(manager.Manager):
|
||||
management_interface.uuid,
|
||||
str(subcloud_subnet.ip),
|
||||
subcloud_subnet.prefixlen,
|
||||
payload['systemcontroller-gateway-ip'],
|
||||
payload['systemcontroller_gateway_address'],
|
||||
1)
|
||||
|
||||
# Create identity endpoints to this subcloud on the
|
||||
@ -145,7 +168,7 @@ class SubcloudManager(manager.Manager):
|
||||
resource='subcloud',
|
||||
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:
|
||||
identity_endpoint_url = \
|
||||
@ -168,6 +191,51 @@ class SubcloudManager(manager.Manager):
|
||||
# Regenerate the addn_hosts_dc file
|
||||
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)
|
||||
|
||||
except Exception as e:
|
||||
@ -178,6 +246,34 @@ class SubcloudManager(manager.Manager):
|
||||
db_api.subcloud_destroy(context, subcloud.id)
|
||||
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):
|
||||
"""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.
|
||||
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):
|
||||
"""Delete the routes to this subcloud"""
|
||||
|
||||
|
@ -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_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_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_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, "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, "10.10.10.0/24", "10.10.10.1", "10.10.10.12", "testpass"]
|
||||
}
|
||||
|
@ -41,12 +41,17 @@ FAKE_HEADERS = {'X-Tenant-Id': FAKE_TENANT, 'X_ROLE': 'admin',
|
||||
FAKE_SUBCLOUD_DATA = {"name": "subcloud1",
|
||||
"description": "subcloud1 description",
|
||||
"location": "subcloud1 location",
|
||||
"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",
|
||||
"availability-status": "disabled"}
|
||||
"system_mode": "duplex",
|
||||
"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",
|
||||
"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):
|
||||
@ -92,7 +97,7 @@ class TestSubclouds(testroot.DCManagerApiTest):
|
||||
def test_post_subcloud_bad_gateway(self, mock_db_api, mock_rpc_client,
|
||||
mock_get_management_address_pool):
|
||||
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,
|
||||
'192.168.204.2',
|
||||
'192.168.204.100')
|
||||
@ -120,7 +125,7 @@ class TestSubclouds(testroot.DCManagerApiTest):
|
||||
@mock.patch.object(subclouds, 'db_api')
|
||||
def test_post_subcloud_bad_subnet(self, mock_db_api, mock_rpc_client):
|
||||
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 *",
|
||||
self.app.post_json, FAKE_URL,
|
||||
headers=FAKE_HEADERS, params=data)
|
||||
@ -129,9 +134,9 @@ class TestSubclouds(testroot.DCManagerApiTest):
|
||||
@mock.patch.object(subclouds, 'db_api')
|
||||
def test_post_subcloud_bad_start_ip(self, mock_db_api, mock_rpc_client):
|
||||
data = copy.copy(FAKE_SUBCLOUD_DATA)
|
||||
data["management-subnet"] = "192.168.101.0/24"
|
||||
data["management-start-ip"] = "192.168.100.2"
|
||||
data["management-end-ip"] = "192.168.100.50"
|
||||
data["management_subnet"] = "192.168.101.0/24"
|
||||
data["management_start_address"] = "192.168.100.2"
|
||||
data["management_end_address"] = "192.168.100.50"
|
||||
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
|
||||
self.app.post_json, FAKE_URL,
|
||||
headers=FAKE_HEADERS, params=data)
|
||||
@ -140,8 +145,8 @@ class TestSubclouds(testroot.DCManagerApiTest):
|
||||
@mock.patch.object(subclouds, 'db_api')
|
||||
def test_post_subcloud_bad_end_ip(self, mock_db_api, mock_rpc_client):
|
||||
data = copy.copy(FAKE_SUBCLOUD_DATA)
|
||||
data["management-start-ip"] = "192.168.101.2"
|
||||
data["management-end-ip"] = "192.168.100.100"
|
||||
data["management_start_address"] = "192.168.101.2"
|
||||
data["management_end_address"] = "192.168.100.100"
|
||||
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
|
||||
self.app.post_json, FAKE_URL,
|
||||
headers=FAKE_HEADERS, params=data)
|
||||
@ -150,8 +155,8 @@ class TestSubclouds(testroot.DCManagerApiTest):
|
||||
@mock.patch.object(subclouds, 'db_api')
|
||||
def test_post_subcloud_short_ip_range(self, mock_db_api, mock_rpc_client):
|
||||
data = copy.copy(FAKE_SUBCLOUD_DATA)
|
||||
data["management-start-ip"] = "192.168.101.2"
|
||||
data["management-end-ip"] = "192.168.101.4"
|
||||
data["management_start_address"] = "192.168.101.2"
|
||||
data["management_end_address"] = "192.168.101.4"
|
||||
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
|
||||
self.app.post_json, FAKE_URL,
|
||||
headers=FAKE_HEADERS, params=data)
|
||||
@ -160,8 +165,8 @@ class TestSubclouds(testroot.DCManagerApiTest):
|
||||
@mock.patch.object(subclouds, 'db_api')
|
||||
def test_post_subcloud_invert_ip_range(self, mock_db_api, mock_rpc_client):
|
||||
data = copy.copy(FAKE_SUBCLOUD_DATA)
|
||||
data["management-start-ip"] = "192.168.101.20"
|
||||
data["management-end-ip"] = "192.168.101.4"
|
||||
data["management_start_address"] = "192.168.101.20"
|
||||
data["management_end_address"] = "192.168.101.4"
|
||||
six.assertRaisesRegex(self, webtest.app.AppError, "400 *",
|
||||
self.app.post_json, FAKE_URL,
|
||||
headers=FAKE_HEADERS, params=data)
|
||||
@ -188,18 +193,6 @@ class TestSubclouds(testroot.DCManagerApiTest):
|
||||
self.app.post_json, FAKE_URL,
|
||||
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(subclouds, 'db_api')
|
||||
def test_delete_subcloud(self, mock_db_api, mock_rpc_client):
|
||||
|
@ -85,6 +85,7 @@ class DBAPISubcloudTest(base.DCManagerTestCase):
|
||||
'management_start_ip': "192.168.101.2",
|
||||
'management_end_ip': "192.168.101.50",
|
||||
'systemcontroller_gateway_ip': "192.168.204.101",
|
||||
'deploy_status': "not-deployed",
|
||||
}
|
||||
values.update(kwargs)
|
||||
return db_api.subcloud_create(ctxt, **values)
|
||||
@ -96,11 +97,13 @@ class DBAPISubcloudTest(base.DCManagerTestCase):
|
||||
'description': data['description'],
|
||||
'location': data['location'],
|
||||
'software_version': data['software-version'],
|
||||
'management_subnet': data['management-subnet'],
|
||||
'management_gateway_ip': data['management-gateway-ip'],
|
||||
'management_start_ip': data['management-start-ip'],
|
||||
'management_end_ip': data['management-end-ip'],
|
||||
'systemcontroller_gateway_ip': data['systemcontroller-gateway-ip'],
|
||||
'management_subnet': data['management_subnet'],
|
||||
'management_gateway_ip': data['management_gateway_address'],
|
||||
'management_start_ip': data['management_start_address'],
|
||||
'management_end_ip': data['management_end_address'],
|
||||
'systemcontroller_gateway_ip': data[
|
||||
'systemcontroller_gateway_address'],
|
||||
'deploy_status': "not-deployed",
|
||||
}
|
||||
return db_api.subcloud_create(ctxt, **values)
|
||||
|
||||
|
@ -41,11 +41,15 @@ FAKE_ID = '1'
|
||||
FAKE_SUBCLOUD_DATA = {"name": "subcloud1",
|
||||
"description": "subcloud1 description",
|
||||
"location": "subcloud1 location",
|
||||
"management-subnet": "192.168.101.0/24",
|
||||
"management-start-ip": "192.168.101.3",
|
||||
"management-end-ip": "192.168.101.4",
|
||||
"management-gateway-ip": "192.168.101.1",
|
||||
"systemcontroller-gateway-ip": "192.168.204.101"}
|
||||
"system_mode": "duplex",
|
||||
"management_subnet": "192.168.101.0/24",
|
||||
"management_start_address": "192.168.101.3",
|
||||
"management_end_address": "192.168.101.4",
|
||||
"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):
|
||||
@ -72,11 +76,17 @@ class Subcloud(object):
|
||||
else:
|
||||
self.availability_status = consts.AVAILABILITY_OFFLINE
|
||||
|
||||
self.management_subnet = data['management-subnet']
|
||||
self.management_gateway_ip = data['management-gateway-ip']
|
||||
self.management_start_ip = data['management-start-ip']
|
||||
self.management_end_ip = data['management-end-ip']
|
||||
self.systemcontroller_gateway_ip = data['systemcontroller-gateway-ip']
|
||||
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()
|
||||
|
||||
@ -106,7 +116,15 @@ class TestSubcloudManager(base.DCManagerTestCase):
|
||||
@mock.patch.object(subcloud_manager, 'SysinvClient')
|
||||
@mock.patch.object(subcloud_manager.SubcloudManager,
|
||||
'_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_db_api, mock_keystone_client, mock_context,
|
||||
mock_dcorch_rpc_client):
|
||||
@ -119,6 +137,7 @@ class TestSubcloudManager(base.DCManagerTestCase):
|
||||
|
||||
mock_sysinv_client().get_controller_hosts.return_value = controllers
|
||||
mock_keystone_client().services_list = services
|
||||
mock_keyring.get_password.return_value = "testpassword"
|
||||
|
||||
sm = subcloud_manager.SubcloudManager()
|
||||
sm.add_subcloud(self.ctxt, payload=value)
|
||||
@ -127,6 +146,10 @@ class TestSubcloudManager(base.DCManagerTestCase):
|
||||
mock_sysinv_client().create_route.assert_called()
|
||||
mock_dcorch_rpc_client().add_subcloud.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'))
|
||||
@mock.patch.object(dcorch_rpc_client, 'EngineClient')
|
||||
|
@ -116,18 +116,22 @@ def create_subcloud_dict(data_list):
|
||||
'software-version': data_list[4],
|
||||
'management-state': data_list[5],
|
||||
'availability-status': data_list[6],
|
||||
'management-subnet': data_list[7],
|
||||
'management-gateway-ip': data_list[8],
|
||||
'management-start-ip': data_list[9],
|
||||
'management-end-ip': data_list[10],
|
||||
'systemcontroller-gateway-ip': data_list[11],
|
||||
'management_subnet': data_list[7],
|
||||
'management_gateway_address': data_list[8],
|
||||
'management_start_address': data_list[9],
|
||||
'management_end_address': data_list[10],
|
||||
'systemcontroller_gateway_address': data_list[11],
|
||||
'audit-fail-count': data_list[12],
|
||||
'reserved-1': data_list[13],
|
||||
'reserved-2': data_list[14],
|
||||
'created-at': data_list[15],
|
||||
'updated-at': data_list[16],
|
||||
'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):
|
||||
|
Loading…
Reference in New Issue
Block a user