Removes unused code
This patch removes the code previously committed for the API implementation we abandoned in favor of Mistral workflows. Change-Id: I34ece26dced3b4b07c030b7689f2b408925f86d6
This commit is contained in:
parent
61a164c8dd
commit
65821cc7cf
@ -17,8 +17,8 @@ import logging
|
||||
import yaml
|
||||
|
||||
from tripleo_common.actions import base
|
||||
from tripleo_common.core import constants
|
||||
from tripleo_common.core import exception
|
||||
from tripleo_common import constants
|
||||
from tripleo_common import exception
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
@ -31,3 +31,11 @@ DEFAULT_CONTAINER_NAME = 'overcloud'
|
||||
|
||||
#: The path to the tripleo heat templates installed on the undercloud
|
||||
DEFAULT_TEMPLATES_PATH = '/usr/share/openstack-tripleo-heat-templates/'
|
||||
|
||||
# TRIPLEO_META_USAGE_KEY is inserted into metadata for containers created in
|
||||
# Swift via SwiftPlanStorageBackend to identify them from other containers
|
||||
TRIPLEO_META_USAGE_KEY = 'x-container-meta-usage-tripleo'
|
||||
|
||||
# OBJECT_META_KEY_PREFIX is used to prefix Swift metadata keys per object
|
||||
# in SwiftPlanStorageBackend
|
||||
OBJECT_META_KEY_PREFIX = 'x-object-meta-'
|
||||
|
@ -1,28 +0,0 @@
|
||||
# Copyright 2015 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
# TRIPLEO_META_USAGE_KEY is inserted into metadata for containers created in
|
||||
# Swift via SwiftPlanStorageBackend to identify them from other containers
|
||||
TRIPLEO_META_USAGE_KEY = 'x-container-meta-usage-tripleo'
|
||||
|
||||
# OBJECT_META_KEY_PREFIX is used to prefix Swift metadata keys per object
|
||||
# in SwiftPlanStorageBackend
|
||||
OBJECT_META_KEY_PREFIX = 'x-object-meta-'
|
||||
|
||||
# The following keys are used when identifying metadata from the capabilities
|
||||
# map file
|
||||
ROOT_TEMPLATE_META = {'file-type': 'root-template'}
|
||||
ROOT_ENVIRONMENT_META = {'file-type': 'root-environment', 'enabled': 'True'}
|
||||
ENVIRONMENT_META = {'file-type': 'environment'}
|
@ -1,96 +0,0 @@
|
||||
# Copyright 2015 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import logging
|
||||
import six
|
||||
from six import reraise as raise_
|
||||
import sys
|
||||
|
||||
from tripleo_common.core.i18n import _
|
||||
from tripleo_common.core.i18n import _LE
|
||||
|
||||
_FATAL_EXCEPTION_FORMAT_ERRORS = False
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@six.python_2_unicode_compatible
|
||||
class TripleoCommonException(Exception):
|
||||
"""Base Tripleo-Common Exception.
|
||||
|
||||
To correctly use this class, inherit from it and define a 'msg_fmt'
|
||||
property. That msg_fmt will get printf'd with the keyword arguments
|
||||
provided to the constructor.
|
||||
"""
|
||||
message = _("An unknown exception occurred.")
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.kwargs = kwargs
|
||||
|
||||
try:
|
||||
self.message = self.msg_fmt % kwargs
|
||||
except KeyError:
|
||||
exc_info = sys.exc_info()
|
||||
# kwargs doesn't match a variable in the message
|
||||
# log the issue and the kwargs
|
||||
LOG.exception(_LE('Exception in string format operation'))
|
||||
for name, value in six.iteritems(kwargs):
|
||||
LOG.error(_LE("%(name)s: %(value)s"),
|
||||
{'name': name, 'value': value}) # noqa
|
||||
|
||||
if _FATAL_EXCEPTION_FORMAT_ERRORS:
|
||||
raise_(exc_info[0], exc_info[1], exc_info[2])
|
||||
|
||||
def __str__(self):
|
||||
return self.message
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
return self.__class__(**self.kwargs)
|
||||
|
||||
|
||||
class StackInUseError(TripleoCommonException):
|
||||
msg_fmt = _("Cannot delete a plan that has an associated stack.")
|
||||
|
||||
|
||||
class PlanDoesNotExistError(TripleoCommonException):
|
||||
msg_fmt = _("A plan with the name %(name)s does not exist.")
|
||||
|
||||
|
||||
class FileDoesNotExistError(TripleoCommonException):
|
||||
msg_fmt = _("A file with the name %(name)s does not exist.")
|
||||
|
||||
|
||||
class ContainerAlreadyExistsError(TripleoCommonException):
|
||||
msg_fmt = _("A container with the name %(name)s already exists.")
|
||||
|
||||
|
||||
class TooManyRootTemplatesError(TripleoCommonException):
|
||||
msg_fmt = _("There can only be up to one root template in a given plan.")
|
||||
|
||||
|
||||
class HeatValidationFailedError(TripleoCommonException):
|
||||
msg_fmt = _("The plan failed to validate via the Heat service. %(msg)s")
|
||||
|
||||
|
||||
class MappingFileNotFoundError(TripleoCommonException):
|
||||
msg_fmt = _("The capabilities_map.yaml file was not found in the root"
|
||||
" of the plan.")
|
||||
|
||||
|
||||
class TooManyCapabilitiesMapFilesError(TripleoCommonException):
|
||||
msg_fmt = _("There cannot be more than one root template in a given plan.")
|
||||
|
||||
|
||||
class ServiceEndpointNotFoundError(TripleoCommonException):
|
||||
msg_fmt = _("No endpoints found for [%(service_name)s, %(service_type)s].")
|
@ -1,42 +0,0 @@
|
||||
# Copyright 2015 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import datetime
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
class BaseModel(object):
|
||||
|
||||
def __repr__(self, *args, **kwargs):
|
||||
repr_ = {self.__class__.__name__: self.__dict__}
|
||||
return yaml.safe_dump(repr_, default_flow_style=True)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not (isinstance(other, self.__class__) or
|
||||
isinstance(self, other.__class__)):
|
||||
return False
|
||||
return self.__dict__ == getattr(other, '__dict__')
|
||||
|
||||
|
||||
class Plan(BaseModel):
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.files = {}
|
||||
self.metadata = {}
|
||||
|
||||
def created_date(self):
|
||||
return datetime.datetime.fromtimestamp(
|
||||
float(self.metadata['x-timestamp']))
|
@ -1,272 +0,0 @@
|
||||
# Copyright 2015 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import logging
|
||||
import yaml
|
||||
|
||||
from heatclient import exc as heatexceptions
|
||||
import six
|
||||
from swiftclient import exceptions as swiftexceptions
|
||||
|
||||
from tripleo_common.core import exception
|
||||
from tripleo_common.utils import meta
|
||||
from tripleo_common.utils import templates
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PlanManager(object):
|
||||
|
||||
def __init__(self, plan_storage_backend, heatclient):
|
||||
# TODO(rbrady) add code to create a storage backend based on config
|
||||
# so the API can send a string representing a backend type and any
|
||||
# client objects needed by the backend type
|
||||
self.plan_store = plan_storage_backend
|
||||
self.heatclient = heatclient
|
||||
|
||||
def create_plan(self, plan_name, plan_files):
|
||||
"""Creates a plan to store templates
|
||||
|
||||
Creates a plan by creating a container matching plan_name, and
|
||||
import given plan_files into it. The plan files is a dictionary
|
||||
where the keys are filenames and the values are file contents.
|
||||
|
||||
:param plan_name: The name of the plan to use as the container name
|
||||
:type plan_name: str
|
||||
:param plan_files: The files to import into the container.
|
||||
:type plan_files: dict
|
||||
"""
|
||||
|
||||
# create container with versioning
|
||||
try:
|
||||
self.plan_store.create(plan_name)
|
||||
except Exception:
|
||||
LOG.exception("Error creating plan.")
|
||||
raise
|
||||
|
||||
plan_files = meta.add_file_metadata(plan_files)
|
||||
return self.update_plan(plan_name, plan_files)
|
||||
|
||||
def delete_plan(self, plan_name):
|
||||
"""Deletes a plan and associated files
|
||||
|
||||
Deletes a plan by deleting the container matching plan_name. It
|
||||
will not delete the plan if a stack exists with the same name.
|
||||
|
||||
Raises StackInUseError if a stack with the same name as plan_name
|
||||
exists.
|
||||
|
||||
:param plan_name: The name of the container to delete
|
||||
:type plan_name: str
|
||||
"""
|
||||
# heat throws HTTPNotFound if the stack is not found
|
||||
try:
|
||||
stack = self.heatclient.stacks.get(plan_name)
|
||||
if stack is not None:
|
||||
raise exception.StackInUseError(name=plan_name)
|
||||
except heatexceptions.HTTPNotFound:
|
||||
try:
|
||||
self.plan_store.delete(plan_name)
|
||||
except swiftexceptions.ClientException as ce:
|
||||
LOG.exception("Swift error deleting plan.")
|
||||
if ce.http_status == 404:
|
||||
six.raise_from(exception.PlanDoesNotExistError(
|
||||
name=plan_name), ce)
|
||||
except Exception:
|
||||
LOG.exception("Error deleting plan.")
|
||||
raise
|
||||
|
||||
def delete_file(self, plan_name, filename):
|
||||
"""Deletes file in a plan container
|
||||
|
||||
:param plan_name: The name of the plan to use as the container name
|
||||
:type plan_name: str
|
||||
:param filename: The file to delete from the container.
|
||||
:type filename: str
|
||||
"""
|
||||
try:
|
||||
self.plan_store.delete_file(plan_name, filename)
|
||||
except swiftexceptions.ClientException as ce:
|
||||
LOG.exception("Swift error deleting file.")
|
||||
if ce.http_status == 404:
|
||||
six.raise_from(exception.FileDoesNotExistError(
|
||||
name=filename), ce)
|
||||
except Exception:
|
||||
LOG.exception("Error deleting file from plan.")
|
||||
raise
|
||||
|
||||
def delete_temporary_environment(self, plan_name):
|
||||
"""Deletes the temporary environment files
|
||||
|
||||
The temporary environment is the combination of deployment parameters
|
||||
and selected environment information
|
||||
|
||||
:param plan_name: The name of the plan to use as the container name
|
||||
:type plan_name: str
|
||||
"""
|
||||
plan = self.get_plan(plan_name)
|
||||
for item in {k: v for (k, v) in plan.files.items() if
|
||||
v.get('meta', {}).get('file-type') == 'temp-environment'}:
|
||||
self.plan_store.delete_file(plan_name, item)
|
||||
|
||||
def get_plan(self, plan_name):
|
||||
"""Retrieves the Heat templates and environment file
|
||||
|
||||
Retrieves the files from the container matching plan_name.
|
||||
|
||||
:param plan_name: The name of the plan to retrieve files for.
|
||||
:type plan_name: str
|
||||
:rtype dict
|
||||
"""
|
||||
|
||||
try:
|
||||
return self.plan_store.get(plan_name)
|
||||
except swiftexceptions.ClientException as ce:
|
||||
LOG.exception("Swift error retrieving plan.")
|
||||
if ce.http_status == 404:
|
||||
six.raise_from(exception.PlanDoesNotExistError(
|
||||
name=plan_name), ce)
|
||||
except Exception:
|
||||
LOG.exception("Error retrieving plan.")
|
||||
raise
|
||||
|
||||
def get_plan_list(self):
|
||||
"""Gets a list of containers that store plans
|
||||
|
||||
Gets a list of containers that contain metadata with the key of
|
||||
X-Container-Meta-Usage-Tripleo and value or 'plan'.
|
||||
|
||||
:return: a list of strings containing plan names
|
||||
"""
|
||||
try:
|
||||
return self.plan_store.list()
|
||||
except Exception:
|
||||
LOG.exception("Error retrieving plan list.")
|
||||
raise
|
||||
|
||||
def get_deployment_parameters(self, plan_name):
|
||||
"""Determine available deployment parameters
|
||||
|
||||
:param plan_name: The name of the plan and container name
|
||||
"""
|
||||
|
||||
plan = self.get_plan(plan_name)
|
||||
template, environment, files = templates.process_plan_data(plan.files)
|
||||
try:
|
||||
params = self.heatclient.stacks.validate(
|
||||
template=template,
|
||||
files=files,
|
||||
environment=environment,
|
||||
show_nested=True)
|
||||
except heatexceptions.HTTPBadRequest as exc:
|
||||
six.raise_from(exception.HeatValidationFailedError(msg=exc), exc)
|
||||
|
||||
return params
|
||||
|
||||
def update_deployment_parameters(self, plan_name, deployment_parameters):
|
||||
"""Update the deployment parameters
|
||||
|
||||
:param plan_name: The name of the plan and container name
|
||||
:type plan_name: str
|
||||
:param deployment_parameters: dictionary of deployment parameters
|
||||
:type deployment_parameters: dict
|
||||
"""
|
||||
|
||||
plan = self.get_plan(plan_name)
|
||||
deployment_params_file = 'environments/deployment_parameters.yaml'
|
||||
# Make sure the dict has the expected environment file format.
|
||||
if not deployment_parameters.get('parameter_defaults'):
|
||||
deployment_parameters = {
|
||||
'parameter_defaults': deployment_parameters
|
||||
}
|
||||
# pop the deployment params temporary environment file from the plan
|
||||
# so it's not included in the validation call. If the stack is valid
|
||||
# the deployment params temporary environment file is overwritten
|
||||
if deployment_params_file in plan.files:
|
||||
plan.files.pop(deployment_params_file)
|
||||
# Update deployment params and validate through heat API.
|
||||
template, environment, files = templates.process_plan_data(plan.files)
|
||||
environment = templates.deep_update(environment, deployment_parameters)
|
||||
try:
|
||||
self.heatclient.stacks.validate(
|
||||
template=template,
|
||||
files=files,
|
||||
environment=environment,
|
||||
show_nested=True)
|
||||
except heatexceptions.HTTPBadRequest as exc:
|
||||
six.raise_from(exception.HeatValidationFailedError(msg=exc), exc)
|
||||
|
||||
env = yaml.safe_dump(deployment_parameters, default_flow_style=False)
|
||||
plan.files[deployment_params_file] = {
|
||||
'contents': env,
|
||||
'meta': {
|
||||
'file-type': 'temp-environment',
|
||||
}
|
||||
}
|
||||
self.update_plan(plan_name, plan.files)
|
||||
|
||||
def update_plan(self, plan_name, plan_files):
|
||||
"""Updates files in a plan container
|
||||
|
||||
:param plan_name: The name of the plan to use as the container name
|
||||
:type plan_name: str
|
||||
:param plan_files: The files to import into the container.
|
||||
:type plan_files: dict
|
||||
"""
|
||||
|
||||
try:
|
||||
self.plan_store.update(plan_name, plan_files)
|
||||
except swiftexceptions.ClientException as ce:
|
||||
LOG.exception("Swift error updating plan.")
|
||||
if ce.http_status == 404:
|
||||
six.raise_from(exception.PlanDoesNotExistError(
|
||||
name=plan_name), ce)
|
||||
except Exception:
|
||||
LOG.exception("Error updating plan.")
|
||||
raise
|
||||
|
||||
return self.get_plan(plan_name)
|
||||
|
||||
def validate_plan(self, plan_name):
|
||||
"""Validate Plan
|
||||
|
||||
This private method provides validations to ensure a plan
|
||||
meets the proper criteria before allowed to persist in storage.
|
||||
|
||||
:param plan_files: The files to import into the container.
|
||||
:type plan_files: dict
|
||||
:returns boolean
|
||||
"""
|
||||
|
||||
plan = self.get_plan(plan_name)
|
||||
# there can only be up to one root-template file in metadata
|
||||
rt = {k: v for (k, v) in plan.files.items()
|
||||
if v.get('meta', {}).get('file-type') == 'root-template'}
|
||||
if len(rt) > 1:
|
||||
raise exception.TooManyRootTemplatesError()
|
||||
|
||||
# the plan needs to be validated with heat to ensure it conforms
|
||||
template, environment, files = templates.process_plan_data(plan.files)
|
||||
try:
|
||||
self.heatclient.stacks.validate(
|
||||
template=template,
|
||||
files=files,
|
||||
environment=environment,
|
||||
show_nested=True)
|
||||
except heatexceptions.HTTPBadRequest as exc:
|
||||
LOG.exception("Error validating the plan.")
|
||||
six.raise_from(exception.HeatValidationFailedError(msg=exc), exc)
|
||||
|
||||
# no validation issues found
|
||||
return True
|
@ -1,135 +0,0 @@
|
||||
# Copyright 2015 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from tripleo_common.core import constants
|
||||
from tripleo_common.core import exception
|
||||
from tripleo_common.core.models import Plan
|
||||
from tripleo_common.utils import meta
|
||||
|
||||
default_container_headers = {
|
||||
'X-Versions-Location': 'versions',
|
||||
constants.TRIPLEO_META_USAGE_KEY: 'plan'
|
||||
}
|
||||
|
||||
|
||||
class SwiftPlanStorageBackend(object):
|
||||
def __init__(self, swiftclient):
|
||||
self.swiftclient = swiftclient
|
||||
|
||||
def create(self, plan_name):
|
||||
"""Creates a plan to store files
|
||||
|
||||
Creates a plan by creating a Swift container matching plan_name, and
|
||||
given files into it.
|
||||
|
||||
:param plan_name: The name of the plan
|
||||
:type plan_name: str
|
||||
:param plan_files: names and contents of files to store
|
||||
:type plan_files: dict
|
||||
"""
|
||||
if plan_name not in self.list():
|
||||
self.swiftclient.put_container(
|
||||
plan_name,
|
||||
headers=default_container_headers
|
||||
)
|
||||
else:
|
||||
raise exception.ContainerAlreadyExistsError(name=plan_name)
|
||||
|
||||
def delete(self, plan_name):
|
||||
"""Deletes a plan and associated files
|
||||
|
||||
Deletes a plan by deleting the Swift container matching plan_name.
|
||||
|
||||
:param plan_name: The name of the plan
|
||||
:type plan_name: str
|
||||
"""
|
||||
# delete files from plan
|
||||
for data in self.swiftclient.get_container(plan_name)[1]:
|
||||
self.swiftclient.delete_object(plan_name, data['name'])
|
||||
# delete plan container
|
||||
self.swiftclient.delete_container(plan_name)
|
||||
|
||||
def delete_file(self, plan_name, filepath):
|
||||
"""Deletes a file for a given filepath from a plan container
|
||||
|
||||
:param plan_name: The name of the plan
|
||||
:type plan_name: str
|
||||
:param filepath: The path of the file to be deleted
|
||||
"""
|
||||
self.swiftclient.delete_object(plan_name, filepath)
|
||||
|
||||
def get(self, plan_name):
|
||||
"""Retrieves the files for a given container name
|
||||
|
||||
Retrieves the files from the Swift container matching plan_name.
|
||||
|
||||
:param plan_name: The name of the plan
|
||||
:type plan_name: str
|
||||
:return: a list of files
|
||||
:rtype list
|
||||
"""
|
||||
plan = Plan(plan_name)
|
||||
container = self.swiftclient.get_container(plan_name)
|
||||
plan.metadata = container[0]
|
||||
for data in container[1]:
|
||||
filename = data['name']
|
||||
plan_obj = self.swiftclient.get_object(plan_name, filename)
|
||||
plan.files[filename] = {}
|
||||
plan.files[filename]['contents'] = plan_obj[1]
|
||||
meta_info = {k: v for (k, v) in plan_obj[0].items()
|
||||
if constants.OBJECT_META_KEY_PREFIX in k}
|
||||
if len(meta_info) > 0:
|
||||
plan.files[filename]['meta'] = \
|
||||
meta.remove_key_prefix(meta_info)
|
||||
|
||||
return plan
|
||||
|
||||
def list(self):
|
||||
"""Gets a list of containers that store plans
|
||||
|
||||
Gets a list of containers that contain metadata with the key of
|
||||
X-Container-Meta-Usage-Tripleo and value or 'plan'.
|
||||
|
||||
:return: a list of strings containing plan names
|
||||
"""
|
||||
plan_list = []
|
||||
for item in self.swiftclient.get_account()[1]:
|
||||
container = self.swiftclient.get_container(item['name'])[0]
|
||||
if constants.TRIPLEO_META_USAGE_KEY in container.keys():
|
||||
plan_list.append(item['name'])
|
||||
|
||||
return plan_list
|
||||
|
||||
def update(self, plan_name, plan_files):
|
||||
"""Updates a plan by updating the files in container
|
||||
|
||||
Updates a plan by updating the files in the Swift container
|
||||
matching plan_name.
|
||||
|
||||
:param plan_name: The name of the plan
|
||||
:type plan_name: str
|
||||
:param plan_files: names and contents of files to store
|
||||
:type plan_files: dict
|
||||
"""
|
||||
|
||||
for filename, details in plan_files.items():
|
||||
custom_headers = {}
|
||||
if 'meta' in details:
|
||||
custom_headers = meta.add_key_prefix(details['meta'])
|
||||
self.swiftclient.put_object(
|
||||
plan_name,
|
||||
filename,
|
||||
details['contents'],
|
||||
headers=custom_headers
|
||||
)
|
@ -1,3 +1,6 @@
|
||||
# Copyright 2015 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
@ -11,6 +14,64 @@
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import logging
|
||||
import sys
|
||||
|
||||
import six
|
||||
from six import reraise as raise_
|
||||
|
||||
from tripleo_common.i18n import _
|
||||
from tripleo_common.i18n import _LE
|
||||
|
||||
_FATAL_EXCEPTION_FORMAT_ERRORS = False
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@six.python_2_unicode_compatible
|
||||
class TripleoCommonException(Exception):
|
||||
"""Base Tripleo-Common Exception.
|
||||
|
||||
To correctly use this class, inherit from it and define a 'msg_fmt'
|
||||
property. That msg_fmt will get printf'd with the keyword arguments
|
||||
provided to the constructor.
|
||||
"""
|
||||
message = _("An unknown exception occurred.")
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.kwargs = kwargs
|
||||
|
||||
try:
|
||||
self.message = self.msg_fmt % kwargs
|
||||
except KeyError:
|
||||
exc_info = sys.exc_info()
|
||||
# kwargs doesn't match a variable in the message
|
||||
# log the issue and the kwargs
|
||||
LOG.exception(_LE('Exception in string format operation'))
|
||||
for name, value in six.iteritems(kwargs):
|
||||
LOG.error(_LE("%(name)s: %(value)s"),
|
||||
{'name': name, 'value': value}) # noqa
|
||||
|
||||
if _FATAL_EXCEPTION_FORMAT_ERRORS:
|
||||
raise_(exc_info[0], exc_info[1], exc_info[2])
|
||||
|
||||
def __str__(self):
|
||||
return self.message
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
return self.__class__(**self.kwargs)
|
||||
|
||||
|
||||
class StackInUseError(TripleoCommonException):
|
||||
msg_fmt = _("Cannot delete a plan that has an associated stack.")
|
||||
|
||||
|
||||
class ContainerAlreadyExistsError(TripleoCommonException):
|
||||
msg_fmt = _("A container with the name %(name)s already exists.")
|
||||
|
||||
|
||||
class HeatValidationFailedError(TripleoCommonException):
|
||||
msg_fmt = _("The plan failed to validate via the Heat service. %(msg)s")
|
||||
|
||||
|
||||
class InvalidNode(ValueError):
|
||||
|
@ -15,7 +15,7 @@
|
||||
import mock
|
||||
|
||||
from tripleo_common.actions import plan
|
||||
from tripleo_common.core import exception
|
||||
from tripleo_common import exception
|
||||
from tripleo_common.tests import base
|
||||
|
||||
MAPPING_YAML_CONTENTS = """root_template: /path/to/overcloud.yaml
|
||||
|
@ -1,70 +0,0 @@
|
||||
# Copyright 2015 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
import datetime
|
||||
import time
|
||||
|
||||
import six
|
||||
import yaml
|
||||
|
||||
from tripleo_common.core.models import Plan
|
||||
from tripleo_common.tests import base
|
||||
|
||||
|
||||
class ModelTest(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ModelTest, self).setUp()
|
||||
self.timestamp = time.time()
|
||||
|
||||
def test_plan(self):
|
||||
plan = Plan('overcloud')
|
||||
plan.metadata = {
|
||||
'x-container-meta-usage-tripleo': 'plan',
|
||||
'accept-ranges': 'bytes',
|
||||
'x-storage-policy': 'Policy-0',
|
||||
'connection': 'keep-alive',
|
||||
'x-timestamp': self.timestamp,
|
||||
'x-trans-id': 'tx1f41a9d34a2a437d8f8dd-00565dd486',
|
||||
'content-type': 'application/json; charset=utf-8',
|
||||
'x-versions-location': 'versions'
|
||||
}
|
||||
plan.files = {
|
||||
'some-name.yaml': {
|
||||
'contents': "some fake contents",
|
||||
'meta': {'file-type': 'environment'}
|
||||
},
|
||||
}
|
||||
|
||||
expected_date = datetime.datetime.fromtimestamp(
|
||||
float(self.timestamp))
|
||||
self.assertEqual(expected_date, plan.created_date(), "Date mismatch")
|
||||
|
||||
def test_eq(self):
|
||||
self.assertEqual(Plan('foo'), Plan('foo'))
|
||||
self.assertNotEqual(Plan('bar'), Plan('foo'))
|
||||
self.assertNotEqual(Plan('bar'), None)
|
||||
|
||||
class thing(object):
|
||||
pass
|
||||
|
||||
self.assertNotEqual(Plan('bar'), thing())
|
||||
|
||||
def test_repr(self):
|
||||
plan = Plan('foo')
|
||||
plan_str = six.text_type(plan)
|
||||
self.assertEqual({'Plan': {
|
||||
'files': {}, 'name': 'foo', 'metadata': {}
|
||||
}}, yaml.safe_load(plan_str))
|
@ -1,375 +0,0 @@
|
||||
# Copyright 2015 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
import mock
|
||||
|
||||
from heatclient import exc as heatexceptions
|
||||
from swiftclient import exceptions as swiftexceptions
|
||||
|
||||
from tripleo_common.core import exception
|
||||
from tripleo_common.core import models
|
||||
from tripleo_common.core import plan
|
||||
from tripleo_common.tests import base
|
||||
|
||||
|
||||
class PlanManagerTest(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(PlanManagerTest, self).setUp()
|
||||
self.heatclient = mock.MagicMock()
|
||||
self.plan_store = mock.MagicMock()
|
||||
self.plan_name = "overcloud"
|
||||
self.stack = mock.MagicMock(
|
||||
id='123',
|
||||
status='CREATE_COMPLETE',
|
||||
stack_name=self.plan_name
|
||||
)
|
||||
|
||||
self.expected_plan = models.Plan(self.plan_name)
|
||||
self.expected_plan.metadata = {
|
||||
'x-container-meta-usage-tripleo': 'plan',
|
||||
}
|
||||
self.expected_plan.files = {
|
||||
'some-environment.yaml': {
|
||||
'contents': "some fake contents",
|
||||
'meta': {'file-type': 'environment'}
|
||||
},
|
||||
'some-root-environment.yaml': {
|
||||
'contents': "parameters:\n"
|
||||
" one: uno\n"
|
||||
" obj:\n"
|
||||
" two: due\n"
|
||||
" three: tre\n",
|
||||
'meta': {
|
||||
'file-type': 'root-environment',
|
||||
'enabled': 'True'
|
||||
}
|
||||
},
|
||||
'some-root-template.yaml': {
|
||||
'contents': "some fake contents",
|
||||
'meta': {'file-type': 'root-template'}
|
||||
},
|
||||
'some-template.yaml': {
|
||||
'contents': "some fake contents",
|
||||
},
|
||||
}
|
||||
|
||||
def test_create_plan(self):
|
||||
self.plan_store.create = mock.MagicMock()
|
||||
plan_mgr = plan.PlanManager(self.plan_store, self.heatclient)
|
||||
plan_mgr.create_plan(self.plan_name, self.expected_plan.files)
|
||||
self.plan_store.create.assert_called_with(self.plan_name)
|
||||
# calls the Exception handling in create_plan
|
||||
with mock.patch('tripleo_common.core.plan.LOG') as log_mock:
|
||||
self.plan_store.create = mock.Mock(side_effect=ValueError())
|
||||
self.assertRaises(ValueError,
|
||||
plan_mgr.create_plan,
|
||||
self.plan_name, self.expected_plan.files)
|
||||
log_mock.exception.assert_called_with("Error creating plan.")
|
||||
|
||||
def test_delete_plan(self):
|
||||
# test that stack exists
|
||||
self.heatclient.stacks.get = mock.MagicMock(return_value=self.stack)
|
||||
plan_mgr = plan.PlanManager(self.plan_store, self.heatclient)
|
||||
self.assertRaises(exception.StackInUseError,
|
||||
plan_mgr.delete_plan,
|
||||
self.plan_name)
|
||||
self.heatclient.stacks.get.assert_called_with(self.plan_name)
|
||||
|
||||
# test that stack doesn't exist yet
|
||||
self.plan_store.delete = mock.MagicMock()
|
||||
self.heatclient.stacks.get = mock.Mock(
|
||||
side_effect=heatexceptions.HTTPNotFound)
|
||||
plan_mgr.delete_plan(self.plan_name)
|
||||
self.plan_store.delete.assert_called_with(self.plan_name)
|
||||
|
||||
# set side effect of swiftexceptions.ClientException
|
||||
with mock.patch('tripleo_common.core.plan.LOG') as log_mock:
|
||||
self.plan_store.delete = mock.Mock(
|
||||
side_effect=swiftexceptions.ClientException(
|
||||
"test-error", http_status=404))
|
||||
self.assertRaises(exception.PlanDoesNotExistError,
|
||||
plan_mgr.delete_plan,
|
||||
self.plan_name)
|
||||
log_mock.exception.assert_called_with('Swift error deleting plan.')
|
||||
|
||||
# set side effect of random Exception
|
||||
self.heatclient.stacks.get = mock.Mock(
|
||||
side_effect=ValueError())
|
||||
self.assertRaises(ValueError,
|
||||
plan_mgr.delete_plan,
|
||||
self.plan_name)
|
||||
|
||||
def test_delete_files(self):
|
||||
self.plan_store.delete_file = mock.MagicMock()
|
||||
plan_mgr = plan.PlanManager(self.plan_store, self.heatclient)
|
||||
plan_mgr.delete_file(self.plan_name, 'fake-file.yaml')
|
||||
self.plan_store.delete_file.assert_called_with(
|
||||
self.plan_name, 'fake-file.yaml')
|
||||
|
||||
self.plan_store.delete_file = mock.Mock(
|
||||
side_effect=exception.FileDoesNotExistError(name='fake-file.yaml'))
|
||||
self.assertRaises(exception.FileDoesNotExistError,
|
||||
plan_mgr.delete_file, self.plan_name,
|
||||
'fake-file.yaml')
|
||||
|
||||
with mock.patch('tripleo_common.core.plan.LOG') as log_mock:
|
||||
self.plan_store.delete_file = mock.Mock(side_effect=ValueError())
|
||||
self.assertRaises(ValueError, plan_mgr.delete_file,
|
||||
self.plan_name, 'fake-file.yaml')
|
||||
log_mock.exception.assert_called_with(
|
||||
"Error deleting file from plan.")
|
||||
|
||||
def test_delete_temporary_environment(self):
|
||||
|
||||
self.expected_plan.files = {
|
||||
'some-name.yaml': {
|
||||
'contents': "some fake contents",
|
||||
'meta': {'file-type': 'temp-environment'}
|
||||
},
|
||||
}
|
||||
self.plan_store.get = mock.MagicMock(return_value=self.expected_plan)
|
||||
self.plan_store.delete_file = mock.MagicMock()
|
||||
plan_mgr = plan.PlanManager(self.plan_store, self.heatclient)
|
||||
plan_mgr.delete_temporary_environment(self.plan_name)
|
||||
self.plan_store.delete_file.assert_called_with(
|
||||
self.plan_name, 'some-name.yaml')
|
||||
|
||||
def test_get_plan(self):
|
||||
self.expected_plan.files = {
|
||||
'some-name.yaml': {
|
||||
'contents': "some fake contents",
|
||||
'meta': {'file-type': 'environment'}
|
||||
},
|
||||
}
|
||||
self.plan_store.get = mock.MagicMock(return_value=self.expected_plan)
|
||||
plan_mgr = plan.PlanManager(self.plan_store, self.heatclient)
|
||||
self.assertEqual(self.expected_plan,
|
||||
plan_mgr.get_plan(self.plan_name),
|
||||
"Plan mismatch")
|
||||
self.plan_store.get.assert_called_with(self.plan_name)
|
||||
with mock.patch('tripleo_common.core.plan.LOG') as log_mock:
|
||||
# test swift container doesn't exist
|
||||
self.plan_store.get = mock.Mock(
|
||||
side_effect=swiftexceptions.ClientException(
|
||||
"test-error", http_status=404))
|
||||
self.assertRaises(exception.PlanDoesNotExistError,
|
||||
plan_mgr.get_plan,
|
||||
self.plan_name)
|
||||
log_mock.exception.assert_called_with(
|
||||
'Swift error retrieving plan.')
|
||||
|
||||
# test other exception occurs
|
||||
self.plan_store.get = mock.Mock(side_effect=ValueError())
|
||||
self.assertRaises(ValueError, plan_mgr.get_plan, 'overcloud')
|
||||
log_mock.exception.assert_called_with("Error retrieving plan.")
|
||||
|
||||
def test_get_plan_list(self):
|
||||
plan_mgr = plan.PlanManager(self.plan_store, self.heatclient)
|
||||
self.plan_store.list = mock.MagicMock(return_value=['overcloud'])
|
||||
self.assertEqual(['overcloud'], plan_mgr.get_plan_list(),
|
||||
"get_plan_list failed")
|
||||
with mock.patch('tripleo_common.core.plan.LOG') as log_mock:
|
||||
self.plan_store.list = mock.Mock(side_effect=ValueError())
|
||||
self.assertRaises(ValueError, plan_mgr.get_plan_list)
|
||||
log_mock.exception.assert_called_with(
|
||||
"Error retrieving plan list.")
|
||||
|
||||
def test_get_deployment_parameters(self):
|
||||
self.plan_store.get = mock.MagicMock(return_value=self.expected_plan)
|
||||
plan_mgr = plan.PlanManager(self.plan_store, self.heatclient)
|
||||
with mock.patch('tripleo_common.utils.templates') as templates:
|
||||
templates.process_plan_data.return_value = (
|
||||
"some fake contents", {
|
||||
'parameters': {
|
||||
'obj': {
|
||||
'two': 'due',
|
||||
'three': 'tre'
|
||||
},
|
||||
'one': 'uno'
|
||||
}
|
||||
}, {'some-template.yaml': 'some fake contents'})
|
||||
self.heatclient.stacks.validate = mock.MagicMock(
|
||||
return_value={
|
||||
'parameters': {
|
||||
'obj': {
|
||||
'two': 'due',
|
||||
'three': 'tre'
|
||||
},
|
||||
'one': 'uno'
|
||||
}
|
||||
})
|
||||
self.assertEqual({
|
||||
'parameters': {
|
||||
'obj': {
|
||||
'two': 'due',
|
||||
'three': 'tre'
|
||||
},
|
||||
'one': 'uno'
|
||||
}
|
||||
},
|
||||
plan_mgr.get_deployment_parameters(self.plan_name),
|
||||
"Bad params")
|
||||
self.heatclient.stacks.validate.assert_called_with(
|
||||
template="some fake contents",
|
||||
files={'some-template.yaml': 'some fake contents'},
|
||||
environment={
|
||||
'parameters': {
|
||||
'obj': {
|
||||
'two': 'due',
|
||||
'three': 'tre'
|
||||
},
|
||||
'one': 'uno'
|
||||
}
|
||||
},
|
||||
show_nested=True
|
||||
)
|
||||
|
||||
# set side effect of heatexceptions.HTTPBadRequest on validate
|
||||
self.heatclient.stacks.validate = mock.Mock(
|
||||
side_effect=heatexceptions.HTTPBadRequest
|
||||
)
|
||||
|
||||
self.assertRaises(exception.HeatValidationFailedError,
|
||||
plan_mgr.get_deployment_parameters,
|
||||
self.plan_name)
|
||||
|
||||
def test_update_deployment_parameters(self):
|
||||
self.plan_store.get = mock.MagicMock(return_value=self.expected_plan)
|
||||
# calls templates.process_plan_data(plan.files)
|
||||
plan_mgr = plan.PlanManager(self.plan_store, self.heatclient)
|
||||
self.expected_plan.files['environments/deployment_parameters.yaml'] = {
|
||||
'contents':
|
||||
"parameters:\n"
|
||||
" one: uno\n"
|
||||
" obj:\n"
|
||||
" two: due\n"
|
||||
" three: tre\n",
|
||||
'meta': {'file-type': 'temp-environment'}
|
||||
}
|
||||
with mock.patch('tripleo_common.utils.templates') as templates:
|
||||
templates.process_plan_data.return_value = (
|
||||
"some fake contents", {
|
||||
'parameters': {
|
||||
'obj': {
|
||||
'two': 'due',
|
||||
'three': 'tre'
|
||||
},
|
||||
'one': 'uno'
|
||||
}
|
||||
}, {'some-template.yaml': 'some fake contents'})
|
||||
self.heatclient.stacks.validate = mock.MagicMock()
|
||||
plan_mgr.validate_plan(self.plan_name)
|
||||
self.heatclient.stacks.validate.assert_called_with(
|
||||
template="some fake contents",
|
||||
files={'some-template.yaml': 'some fake contents'},
|
||||
environment={
|
||||
'parameters': {
|
||||
'obj': {
|
||||
'two': 'due',
|
||||
'three': 'tre'
|
||||
},
|
||||
'one': 'uno'
|
||||
}
|
||||
},
|
||||
show_nested=True
|
||||
)
|
||||
# set side effect of heatexceptions.HTTPBadRequest on validate
|
||||
self.heatclient.stacks.validate = mock.Mock(
|
||||
side_effect=heatexceptions.HTTPBadRequest
|
||||
)
|
||||
|
||||
self.assertRaises(exception.HeatValidationFailedError,
|
||||
plan_mgr.get_deployment_parameters,
|
||||
self.plan_name)
|
||||
|
||||
def test_update_plan(self):
|
||||
self.plan_store.update = mock.MagicMock()
|
||||
self.plan_store.get = mock.MagicMock(return_value=self.expected_plan)
|
||||
plan_mgr = plan.PlanManager(self.plan_store, self.heatclient)
|
||||
self.assertEqual(self.expected_plan,
|
||||
plan_mgr.update_plan(
|
||||
self.plan_name, self.expected_plan.files),
|
||||
"Plan mismatch")
|
||||
self.plan_store.get.assert_called_with(self.plan_name)
|
||||
with mock.patch('tripleo_common.core.plan.LOG') as log_mock:
|
||||
# test swift container doesn't exist
|
||||
self.plan_store.update = mock.Mock(
|
||||
side_effect=swiftexceptions.ClientException(
|
||||
"test-error", http_status=404))
|
||||
self.assertRaises(exception.PlanDoesNotExistError,
|
||||
plan_mgr.update_plan,
|
||||
self.plan_name,
|
||||
self.expected_plan.files)
|
||||
log_mock.exception.assert_called_with(
|
||||
'Swift error updating plan.')
|
||||
|
||||
# test other exception occurs
|
||||
self.plan_store.update = mock.Mock(side_effect=ValueError())
|
||||
self.assertRaises(ValueError, plan_mgr.update_plan,
|
||||
self.plan_name, self.expected_plan.files)
|
||||
log_mock.exception.assert_called_with("Error updating plan.")
|
||||
|
||||
def test_validate_plan(self):
|
||||
# calls self.get_plan(plan_name)
|
||||
self.plan_store.get = mock.MagicMock(return_value=self.expected_plan)
|
||||
# test 2 root-templates to get exception.TooManyRootTemplatesError
|
||||
self.expected_plan.files['another-root-template.yaml'] = {
|
||||
'contents': "some fake contents",
|
||||
'meta': {'file-type': 'root-template'}
|
||||
}
|
||||
plan_mgr = plan.PlanManager(self.plan_store, self.heatclient)
|
||||
self.assertRaises(exception.TooManyRootTemplatesError,
|
||||
plan_mgr.validate_plan,
|
||||
self.plan_name)
|
||||
del(self.expected_plan.files['another-root-template.yaml'])
|
||||
# calls templates.process_plan_data(plan.files) (mock and assert_call)
|
||||
with mock.patch('tripleo_common.utils.templates') as templates:
|
||||
templates.process_plan_data = mock.MagicMock(return_value=(
|
||||
"some fake contents", {
|
||||
'parameters': {
|
||||
'obj': {
|
||||
'two': 'due',
|
||||
'three': 'tre'
|
||||
},
|
||||
'one': 'uno'
|
||||
}
|
||||
}, {'some-template.yaml': 'some fake contents'}))
|
||||
self.heatclient.stacks.validate = mock.MagicMock()
|
||||
plan_mgr.validate_plan(self.plan_name)
|
||||
self.heatclient.stacks.validate.assert_called_with(
|
||||
template="some fake contents",
|
||||
files={'some-template.yaml': 'some fake contents'},
|
||||
environment={
|
||||
'parameters': {
|
||||
'obj': {
|
||||
'two': 'due',
|
||||
'three': 'tre'
|
||||
},
|
||||
'one': 'uno'
|
||||
}
|
||||
},
|
||||
show_nested=True
|
||||
)
|
||||
# set side effect of heatexceptions.HTTPBadRequest on validate
|
||||
self.heatclient.stacks.validate = mock.Mock(
|
||||
side_effect=heatexceptions.HTTPBadRequest
|
||||
)
|
||||
with mock.patch('tripleo_common.core.plan.LOG') as log_mock:
|
||||
self.assertRaises(exception.HeatValidationFailedError,
|
||||
plan_mgr.validate_plan,
|
||||
self.plan_name)
|
||||
log_mock.exception.assert_called_with(
|
||||
"Error validating the plan.")
|
@ -1,194 +0,0 @@
|
||||
# Copyright 2015 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
import mock
|
||||
|
||||
from tripleo_common.core import exception
|
||||
from tripleo_common.core.models import Plan
|
||||
from tripleo_common.core.plan_storage import default_container_headers
|
||||
from tripleo_common.core.plan_storage import SwiftPlanStorageBackend
|
||||
from tripleo_common.tests import base
|
||||
|
||||
PLAN_DATA = {
|
||||
'/path/to/overcloud.yaml': {
|
||||
'contents': "heat_template_version: 2015-04-30\n\n"
|
||||
"resources:\n"
|
||||
"\n"
|
||||
" HorizonSecret:\n"
|
||||
" type: OS::Heat::RandomString\n"
|
||||
" properties:\n"
|
||||
" length: 10\n"
|
||||
"\n"
|
||||
" Controller:\n"
|
||||
" type: OS::Heat::ResourceGroup\n"
|
||||
" depends_on: Networks\n"
|
||||
" properties:\n"
|
||||
" count: {get_param: ControllerCount}\n",
|
||||
'meta': {'file-type': 'root-template'},
|
||||
},
|
||||
'/path/to/environment.yaml': {
|
||||
'contents': "parameters:\n"
|
||||
" one: uno\n"
|
||||
" obj:\n"
|
||||
" two: due\n"
|
||||
" three: tre\n",
|
||||
'meta': {'file-type': 'root-environment'},
|
||||
},
|
||||
'/path/to/network-isolation.json': {
|
||||
'contents': '{"parameters": {"one": "one"}}',
|
||||
'meta': {'file-type': 'environment', 'order': 1},
|
||||
},
|
||||
'/path/to/ceph-storage-env.yaml': {
|
||||
'contents': "parameters:\n"
|
||||
" obj:\n"
|
||||
" two: dos,\n"
|
||||
" three: three",
|
||||
'meta': {'file-type': 'environment', 'order': 2},
|
||||
},
|
||||
'/path/to/poc-custom-env.yaml': {
|
||||
'contents': "parameters:\n"
|
||||
" obj:\n"
|
||||
" two: two\n"
|
||||
" some::resource: /path/to/somefile.yaml",
|
||||
'meta': {'file-type': 'environment', 'order': 0}
|
||||
},
|
||||
'/path/to/somefile.yaml': {'contents': "description: lorem ipsum"}
|
||||
}
|
||||
|
||||
|
||||
class PlanStorageTest(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(PlanStorageTest, self).setUp()
|
||||
self.swiftclient = mock.MagicMock()
|
||||
self.plan_store = SwiftPlanStorageBackend(self.swiftclient)
|
||||
self.plan_name = "overcloud"
|
||||
|
||||
def test_create(self):
|
||||
# create a plan
|
||||
self.plan_store.list = mock.MagicMock(return_value=['test1', 'test2'])
|
||||
self.swiftclient.put_container = mock.MagicMock()
|
||||
self.plan_store.create(self.plan_name)
|
||||
self.swiftclient.put_container.assert_called_with(
|
||||
self.plan_name,
|
||||
headers=default_container_headers
|
||||
)
|
||||
|
||||
# attempt to create a 2nd plan should fail
|
||||
self.plan_store.list = mock.MagicMock(return_value=['overcloud'])
|
||||
self.assertRaisesRegexp(exception.ContainerAlreadyExistsError,
|
||||
self.plan_name,
|
||||
self.plan_store.create,
|
||||
self.plan_name)
|
||||
|
||||
def test_delete(self):
|
||||
self.swiftclient.get_container = mock.MagicMock(
|
||||
return_value=({}, [
|
||||
{'name': 'some-name.yaml'},
|
||||
{'name': 'some-other-name.yaml'},
|
||||
{'name': 'yet-some-other-name.yaml'},
|
||||
{'name': 'finally-another-name.yaml'}
|
||||
])
|
||||
)
|
||||
self.swiftclient.delete_object = mock.MagicMock()
|
||||
self.plan_store.delete(self.plan_name)
|
||||
mock_calls = [
|
||||
mock.call('overcloud', 'some-name.yaml'),
|
||||
mock.call('overcloud', 'some-other-name.yaml'),
|
||||
mock.call('overcloud', 'yet-some-other-name.yaml'),
|
||||
mock.call('overcloud', 'finally-another-name.yaml')
|
||||
]
|
||||
self.swiftclient.delete_object.assert_has_calls(
|
||||
mock_calls, any_order=True)
|
||||
|
||||
def test_delete_file(self):
|
||||
self.swiftclient.delete_object = mock.MagicMock()
|
||||
filepath = '/a/random/path/to/file.yaml'
|
||||
self.plan_store.delete_file(self.plan_name, filepath)
|
||||
self.swiftclient.delete_object.assert_called_with(
|
||||
self.plan_name, filepath)
|
||||
|
||||
def test_get(self):
|
||||
metadata = {
|
||||
'x-container-meta-usage-tripleo': 'plan',
|
||||
'accept-ranges': 'bytes',
|
||||
'x-storage-policy': 'Policy-0',
|
||||
'connection': 'keep-alive',
|
||||
'x-timestamp': '1447161410.72641',
|
||||
'x-trans-id': 'tx1f41a9d34a2a437d8f8dd-00565dd486',
|
||||
'content-type': 'application/json; charset=utf-8',
|
||||
'x-versions-location': 'versions'
|
||||
}
|
||||
expected_plan = Plan(self.plan_name)
|
||||
expected_plan.metadata = metadata
|
||||
expected_plan.files = {
|
||||
'some-name.yaml': {
|
||||
'contents': "some fake contents",
|
||||
'meta': {'file-type': 'environment'}
|
||||
},
|
||||
}
|
||||
self.swiftclient.get_container = mock.MagicMock(return_value=(
|
||||
metadata, [
|
||||
{'name': 'some-name.yaml'},
|
||||
])
|
||||
)
|
||||
self.swiftclient.get_object = mock.MagicMock(return_value=(
|
||||
{'x-object-meta-file-type': 'environment'}, "some fake contents"
|
||||
))
|
||||
self.assertEqual(expected_plan.name,
|
||||
self.plan_store.get(self.plan_name).name)
|
||||
self.swiftclient.get_container.assert_called_with(self.plan_name)
|
||||
self.swiftclient.get_object.assert_called_with(
|
||||
'overcloud', 'some-name.yaml')
|
||||
|
||||
def test_list(self):
|
||||
self.swiftclient.get_account = mock.MagicMock(
|
||||
return_value=({}, [
|
||||
{
|
||||
'count': 1,
|
||||
'bytes': 55,
|
||||
'name': 'overcloud'
|
||||
},
|
||||
])
|
||||
)
|
||||
self.swiftclient.get_container = mock.MagicMock(
|
||||
return_value=({
|
||||
'x-container-meta-usage-tripleo': 'plan',
|
||||
}, [])
|
||||
)
|
||||
self.assertEqual(['overcloud'], self.plan_store.list())
|
||||
self.swiftclient.get_container.assert_called_with('overcloud')
|
||||
|
||||
def test_update(self):
|
||||
expected_plan = Plan(self.plan_name)
|
||||
expected_plan.metadata = {
|
||||
'x-container-meta-usage-tripleo': 'plan',
|
||||
'accept-ranges': 'bytes',
|
||||
'x-storage-policy': 'Policy-0',
|
||||
}
|
||||
expected_plan.files = {
|
||||
'some-name.yaml': {
|
||||
'contents': "some fake contents",
|
||||
'meta': {'file-type': 'environment'}
|
||||
},
|
||||
}
|
||||
self.swiftclient.put_object = mock.MagicMock()
|
||||
self.plan_store.update(self.plan_name, expected_plan.files)
|
||||
self.swiftclient.put_object.assert_called_with(
|
||||
self.plan_name,
|
||||
'some-name.yaml',
|
||||
"some fake contents",
|
||||
headers={'x-object-meta-file-type': 'environment'}
|
||||
)
|
@ -1,165 +0,0 @@
|
||||
# Copyright 2015 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
from tripleo_common.core import exception
|
||||
from tripleo_common.tests import base
|
||||
from tripleo_common.utils.meta import add_file_metadata
|
||||
|
||||
|
||||
MAPPING_YAML_CONTENTS = """root_template: /path/to/overcloud.yaml
|
||||
root_environment: /path/to/environment.yaml
|
||||
topics:
|
||||
- title: Fake Single Environment Group Configuration
|
||||
description:
|
||||
environment_groups:
|
||||
- title:
|
||||
description: Random fake string of text
|
||||
environments:
|
||||
- file: /path/to/network-isolation.json
|
||||
title: Default Configuration
|
||||
description:
|
||||
|
||||
- title: Fake Multiple Environment Group Configuration
|
||||
description:
|
||||
environment_groups:
|
||||
- title: Random Fake 1
|
||||
description: Random fake string of text
|
||||
environments:
|
||||
- file: /path/to/ceph-storage-env.yaml
|
||||
title: Fake1
|
||||
description: Random fake string of text
|
||||
|
||||
- title: Random Fake 2
|
||||
description:
|
||||
environments:
|
||||
- file: /path/to/poc-custom-env.yaml
|
||||
title: Fake2
|
||||
description:
|
||||
"""
|
||||
|
||||
PLAN_DATA = {
|
||||
'/path/to/overcloud.yaml': {
|
||||
'contents': 'heat_template_version: 2015-04-30',
|
||||
'meta': {'file-type': 'root-template'},
|
||||
},
|
||||
'/path/to/environment.yaml': {
|
||||
'contents': "parameters:\n"
|
||||
" one: uno\n"
|
||||
" obj:\n"
|
||||
" two: due\n"
|
||||
" three: tre\n",
|
||||
'meta': {'file-type': 'root-environment', 'enabled': 'True'},
|
||||
},
|
||||
'/path/to/network-isolation.json': {
|
||||
'contents': '{"parameters": {"one": "one"}}',
|
||||
'meta': {'file-type': 'environment'},
|
||||
},
|
||||
'/path/to/ceph-storage-env.yaml': {
|
||||
'contents': "parameters:\n"
|
||||
" obj:\n"
|
||||
" two: dos,\n"
|
||||
" three: three",
|
||||
'meta': {'file-type': 'environment'},
|
||||
},
|
||||
'/path/to/poc-custom-env.yaml': {
|
||||
'contents': "parameters:\n"
|
||||
" obj:\n"
|
||||
" two: two\n"
|
||||
" some::resource: /path/to/somefile.yaml",
|
||||
'meta': {'file-type': 'environment'}
|
||||
},
|
||||
'/path/to/somefile.yaml': {'contents': "description: lorem ipsum"},
|
||||
'capabilities-map.yaml': {
|
||||
'contents': MAPPING_YAML_CONTENTS,
|
||||
'meta': {'file-type': 'capabilities-map'},
|
||||
},
|
||||
}
|
||||
|
||||
PLAN_DATA_NO_META = {
|
||||
'/path/to/overcloud.yaml': {
|
||||
'contents': 'heat_template_version: 2015-04-30',
|
||||
},
|
||||
'/path/to/environment.yaml': {
|
||||
'contents': "parameters:\n"
|
||||
" one: uno\n"
|
||||
" obj:\n"
|
||||
" two: due\n"
|
||||
" three: tre\n",
|
||||
},
|
||||
'/path/to/network-isolation.json': {
|
||||
'contents': '{"parameters": {"one": "one"}}',
|
||||
},
|
||||
'/path/to/ceph-storage-env.yaml': {
|
||||
'contents': "parameters:\n"
|
||||
" obj:\n"
|
||||
" two: dos,\n"
|
||||
" three: three",
|
||||
},
|
||||
'/path/to/poc-custom-env.yaml': {
|
||||
'contents': "parameters:\n"
|
||||
" obj:\n"
|
||||
" two: two\n"
|
||||
" some::resource: /path/to/somefile.yaml",
|
||||
},
|
||||
'/path/to/somefile.yaml': {'contents': "description: lorem ipsum"},
|
||||
'capabilities-map.yaml': {
|
||||
'contents': MAPPING_YAML_CONTENTS,
|
||||
'meta': {'file-type': 'capabilities-map'},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class UtilsMetaTest(base.TestCase):
|
||||
|
||||
def test_add_file_metadata(self):
|
||||
# tests case where files have no metadata yet
|
||||
plan_files_with_metadata = add_file_metadata(PLAN_DATA_NO_META)
|
||||
self.assertEqual(
|
||||
PLAN_DATA,
|
||||
plan_files_with_metadata,
|
||||
"Metadata not added properly"
|
||||
)
|
||||
|
||||
# tests case where files already have a metadata dict per file
|
||||
for k, v in PLAN_DATA.items():
|
||||
if 'meta' in v:
|
||||
v.update({'enabled': 'True'})
|
||||
else:
|
||||
v['meta'] = {'enabled': 'True'}
|
||||
|
||||
for k, v in PLAN_DATA_NO_META.items():
|
||||
if 'meta' in v:
|
||||
v.update({'enabled': 'True'})
|
||||
else:
|
||||
v['meta'] = {'enabled': 'True'}
|
||||
|
||||
plan_files_with_metadata = add_file_metadata(PLAN_DATA_NO_META)
|
||||
self.assertEqual(
|
||||
PLAN_DATA,
|
||||
plan_files_with_metadata,
|
||||
"Metadata not added properly"
|
||||
)
|
||||
|
||||
# test to ensure having more than one capabilities-map file
|
||||
# results in an exception
|
||||
PLAN_DATA_NO_META.update({
|
||||
'capabilities-map2.yaml': {
|
||||
'contents': MAPPING_YAML_CONTENTS,
|
||||
'meta': {'file-type': 'capabilities-map'}
|
||||
}
|
||||
})
|
||||
self.assertRaises(exception.TooManyCapabilitiesMapFilesError,
|
||||
add_file_metadata,
|
||||
PLAN_DATA_NO_META)
|
@ -61,34 +61,6 @@ class UtilsTemplatesTest(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(UtilsTemplatesTest, self).setUp()
|
||||
self.tpl, self.env, self.files = templates.process_plan_data(PLAN_DATA)
|
||||
print(self.files)
|
||||
|
||||
def test_find_root_template(self):
|
||||
# delete the root_template from sample data
|
||||
del PLAN_DATA['/path/to/overcloud.yaml']
|
||||
|
||||
# without root, should return {}
|
||||
self.assertEqual({}, templates.find_root_template(PLAN_DATA))
|
||||
|
||||
# add root_template back to sample data
|
||||
root_template = {
|
||||
'/path/to/overcloud.yaml': {
|
||||
'contents': 'heat_template_version: 2015-04-30',
|
||||
'meta': {'file-type': 'root-template'}}
|
||||
}
|
||||
PLAN_DATA.update(root_template)
|
||||
|
||||
self.assertEqual(root_template,
|
||||
templates.find_root_template(PLAN_DATA))
|
||||
|
||||
def test_template_found(self):
|
||||
self.assertEqual(self.tpl, 'heat_template_version: 2015-04-30')
|
||||
|
||||
def test_files_found(self):
|
||||
self.assertEqual(self.files, {
|
||||
'/path/to/somefile.yaml': 'description: lorem ipsum',
|
||||
})
|
||||
|
||||
@mock.patch("requests.request")
|
||||
def test_preprocess_templates(self, mock_request):
|
||||
|
@ -1,85 +0,0 @@
|
||||
# Copyright 2015 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import yaml
|
||||
|
||||
from tripleo_common.core import constants
|
||||
from tripleo_common.core import exception
|
||||
|
||||
|
||||
def add_key_prefix(source):
|
||||
result = dict()
|
||||
for keyname, value in source.items():
|
||||
new_keyname = "%s%s" % (constants.OBJECT_META_KEY_PREFIX, keyname)
|
||||
result[new_keyname] = value
|
||||
return result
|
||||
|
||||
|
||||
def remove_key_prefix(source):
|
||||
result = dict()
|
||||
for keyname, value in source.items():
|
||||
new_keyname = keyname.replace(constants.OBJECT_META_KEY_PREFIX, '')
|
||||
result[new_keyname] = value
|
||||
return result
|
||||
|
||||
|
||||
def add_file_metadata(plan_files):
|
||||
cm = {k: v for (k, v) in plan_files.items()
|
||||
if v.get('meta', {}).get('file-type') == 'capabilities-map'}
|
||||
# if there is more than one capabilities-map file, throw an exception
|
||||
# if there is a capabilities-map file, then process it and set metadata
|
||||
# in files found
|
||||
if len(cm) > 1:
|
||||
raise exception.TooManyCapabilitiesMapFilesError()
|
||||
if len(cm) == 1:
|
||||
mapfile = yaml.load(list(cm.items())[0][1]['contents'])
|
||||
|
||||
# identify the root template
|
||||
if mapfile['root_template']:
|
||||
if plan_files[mapfile['root_template']]:
|
||||
# if the file exists in the plan and has meta, update it
|
||||
# otherwise add meta dict
|
||||
if 'meta' in plan_files[mapfile['root_template']]:
|
||||
plan_files[mapfile['root_template']]['meta'].update(
|
||||
dict(constants.ROOT_TEMPLATE_META)
|
||||
)
|
||||
else:
|
||||
plan_files[mapfile['root_template']]['meta'] =\
|
||||
dict(constants.ROOT_TEMPLATE_META)
|
||||
|
||||
# identify all environments
|
||||
for topic in mapfile['topics']:
|
||||
for eg in topic['environment_groups']:
|
||||
for env in eg['environments']:
|
||||
if 'meta' in plan_files[env['file']]:
|
||||
plan_files[env['file']]['meta'].update(
|
||||
dict(constants.ENVIRONMENT_META)
|
||||
)
|
||||
else:
|
||||
plan_files[env['file']]['meta'] =\
|
||||
dict(constants.ENVIRONMENT_META)
|
||||
|
||||
# identify the root environment
|
||||
if mapfile['root_environment']:
|
||||
if plan_files[mapfile['root_environment']]:
|
||||
# if the file exists in the plan and has meta, update it
|
||||
# otherwise add meta dict
|
||||
if 'meta' in plan_files[mapfile['root_environment']]:
|
||||
plan_files[mapfile['root_environment']]['meta'].update(
|
||||
dict(constants.ROOT_ENVIRONMENT_META)
|
||||
)
|
||||
else:
|
||||
plan_files[mapfile['root_environment']]['meta'] =\
|
||||
dict(constants.ROOT_ENVIRONMENT_META)
|
||||
return plan_files
|
@ -48,54 +48,6 @@ def deep_update(base, new):
|
||||
return base
|
||||
|
||||
|
||||
def process_plan_data(plan_data):
|
||||
"""Preprocesses and organizes plan files for heatclient interaction
|
||||
|
||||
This method separates the root template, environments and other
|
||||
associated files in preparation to send to heatclient for validation
|
||||
or deployment. The environment files are merged with the temporary
|
||||
environment information stored in the deployment parameters.
|
||||
|
||||
:param plan_data: the files stored in a plan
|
||||
:return: template, merged environment and associated files
|
||||
"""
|
||||
template = ''
|
||||
environment = {}
|
||||
env_items = []
|
||||
temp_env_items = []
|
||||
files = {}
|
||||
|
||||
for key, val in plan_data.items():
|
||||
file_type = val.get('meta', {}).get('file-type')
|
||||
enabled = val.get('meta', {}).get('enabled')
|
||||
if not file_type:
|
||||
files[key] = val['contents']
|
||||
elif file_type == 'environment' and enabled:
|
||||
env_items.append({'name': key,
|
||||
'meta': val['meta'],
|
||||
'contents': val['contents']})
|
||||
elif file_type == 'temp-environment':
|
||||
temp_env_items.append({'name': key,
|
||||
'meta': val['meta'],
|
||||
'contents': val['contents']})
|
||||
elif file_type == 'root-template':
|
||||
template = val['contents']
|
||||
elif file_type == 'root-environment' and enabled:
|
||||
environment = _get_dict_from_env_string(key, val['contents'])
|
||||
|
||||
# merge environment files
|
||||
for item in env_items:
|
||||
env_dict = _get_dict_from_env_string(item['name'], item['contents'])
|
||||
environment = deep_update(environment, env_dict)
|
||||
|
||||
# merge the temporary environment files last
|
||||
for item in temp_env_items:
|
||||
env_dict = _get_dict_from_env_string(item['name'], item['contents'])
|
||||
environment = deep_update(environment, env_dict)
|
||||
|
||||
return template, environment, files
|
||||
|
||||
|
||||
def preprocess_templates(swift_base_url, container_name, template,
|
||||
environments, auth_token):
|
||||
"""Pre-processes and organizes plan files
|
||||
@ -168,8 +120,3 @@ def preprocess_templates(swift_base_url, container_name, template,
|
||||
'environment': env,
|
||||
'files': files
|
||||
}
|
||||
|
||||
|
||||
def find_root_template(plan_files):
|
||||
return {k: v for (k, v) in plan_files.items()
|
||||
if v.get('meta', {}).get('file-type') == 'root-template'}
|
||||
|
Loading…
Reference in New Issue
Block a user