heat-6.0.0/0000775000567000056710000000000012701407211013572 5ustar jenkinsjenkins00000000000000heat-6.0.0/uninstall.sh0000775000567000056710000000075112701407050016146 0ustar jenkinsjenkins00000000000000#!/bin/bash if [ $EUID -ne 0 ]; then echo "This script must be run as root." >&2 exit fi type -P pip-python &> /dev/null && have_pip_python=1 || have_pip_python=0 if [ $have_pip_python -eq 1 ]; then pip-python uninstall -y heat exit fi type -P pip &> /dev/null && have_pip=1 || have_pip=0 if [ $have_pip -eq 1 ]; then pip uninstall -y heat exit fi echo "pip-python not found. install package (probably python-pip) or run 'easy_install pip', then rerun $0" >&2; heat-6.0.0/devstack/0000775000567000056710000000000012701407211015376 5ustar jenkinsjenkins00000000000000heat-6.0.0/devstack/upgrade/0000775000567000056710000000000012701407211017025 5ustar jenkinsjenkins00000000000000heat-6.0.0/devstack/upgrade/shutdown.sh0000775000567000056710000000200312701407050021233 0ustar jenkinsjenkins00000000000000#!/bin/bash # # 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. set -o errexit source $GRENADE_DIR/grenaderc source $GRENADE_DIR/functions # We need base DevStack functions for this source $BASE_DEVSTACK_DIR/functions source $BASE_DEVSTACK_DIR/stackrc # needed for status directory set -o xtrace for serv in h-eng h-api h-api-cfn h-api-cw; do stop_process $serv done SERVICES_DOWN="heat-api heat-engine heat-api-cfn heat-api-cloudwatch" # sanity check that services are actually down ensure_services_stopped $SERVICES_DOWN heat-6.0.0/devstack/upgrade/templates/0000775000567000056710000000000012701407211021023 5ustar jenkinsjenkins00000000000000heat-6.0.0/devstack/upgrade/templates/random_string.yaml0000664000567000056710000000013712701407050024557 0ustar jenkinsjenkins00000000000000heat_template_version: 2014-10-16 resources: random_string: type: OS::Heat::RandomString heat-6.0.0/devstack/upgrade/settings0000664000567000056710000000041312701407050020607 0ustar jenkinsjenkins00000000000000register_project_for_upgrade heat register_db_to_save heat devstack_localrc base enable_service h-api h-api-cfn h-api-cw h-eng heat tempest devstack_localrc target enable_service h-api h-api-cfn h-api-cw h-eng heat tempest BASE_RUN_SMOKE=False TARGET_RUN_SMOKE=False heat-6.0.0/devstack/upgrade/resources.sh0000775000567000056710000000516312701407053021407 0ustar jenkinsjenkins00000000000000#!/bin/bash # # 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. set -o errexit source $GRENADE_DIR/grenaderc source $GRENADE_DIR/functions source $TOP_DIR/openrc admin admin set -o xtrace HEAT_USER=heat_grenade HEAT_PROJECT=heat_grenade HEAT_PASS=pass function _heat_set_user { OS_TENANT_NAME=$HEAT_PROJECT OS_USERNAME=$HEAT_USER OS_PASSWORD=$HEAT_PASS } function create { # run heat_integrationtests instead of tempest smoke before create pushd $BASE_DEVSTACK_DIR/../heat tox -eintegration heat_integrationtests.functional.test_create_update popd # creates a tenant for the server eval $(openstack project create -f shell -c id $HEAT_PROJECT) if [[ -z "$id" ]]; then die $LINENO "Didn't create $HEAT_PROJECT project" fi resource_save heat project_id $id # creates the user, and sets $id locally eval $(openstack user create $HEAT_USER \ --project $id \ --password $HEAT_PASS \ -f shell -c id) if [[ -z "$id" ]]; then die $LINENO "Didn't create $HEAT_USER user" fi resource_save heat user_id $id _heat_set_user local stack_name='grenadine' resource_save heat stack_name $stack_name local loc=`dirname $BASH_SOURCE` heat stack-create -f $loc/templates/random_string.yaml $stack_name } function verify { _heat_set_user stack_name=$(resource_get heat stack_name) heat stack-show $stack_name # TODO(sirushtim): Create more granular checks for Heat. } function verify_noapi { # TODO(sirushtim): Write tests to validate liveness of the resources # it creates during possible API downtime. : } function destroy { _heat_set_user heat stack-delete $(resource_get heat stack_name) source $TOP_DIR/openrc admin admin local user_id=$(resource_get heat user_id) local project_id=$(resource_get heat project_id) openstack user delete $user_id openstack project delete $project_id } # Dispatcher case $1 in "create") create ;; "verify_noapi") verify_noapi ;; "verify") verify ;; "destroy") destroy ;; esac heat-6.0.0/devstack/upgrade/upgrade.sh0000775000567000056710000000563712701407050021027 0ustar jenkinsjenkins00000000000000#!/bin/bash # # 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. # # ``upgrade-heat`` echo "*********************************************************************" echo "Begin $0" echo "*********************************************************************" # Clean up any resources that may be in use cleanup() { set +o errexit echo "*********************************************************************" echo "ERROR: Abort $0" >&2 echo "*********************************************************************" # Kill ourselves to signal any calling process trap 2; kill -2 $$ } trap cleanup SIGHUP SIGINT SIGTERM # Keep track of the grenade directory RUN_DIR=$(cd $(dirname "$0") && pwd) # Source params source $GRENADE_DIR/grenaderc # Import common functions source $GRENADE_DIR/functions # This script exits on an error so that errors don't compound and you see # only the first error that occurred. set -o errexit # Upgrade Heat # ============ # Duplicate some setup bits from target DevStack source $TARGET_DEVSTACK_DIR/functions source $TARGET_DEVSTACK_DIR/stackrc source $TARGET_DEVSTACK_DIR/lib/tls source $TARGET_DEVSTACK_DIR/lib/stack source $TARGET_DEVSTACK_DIR/lib/apache source $TARGET_DEVSTACK_DIR/lib/heat # Print the commands being run so that we can see the command that triggers # an error. It is also useful for following allowing as the install occurs. set -o xtrace # Save current config files for posterity [[ -d $SAVE_DIR/etc.heat ]] || cp -pr $HEAT_CONF_DIR $SAVE_DIR/etc.heat # install_heat() stack_install_service heat install_heatclient install_heat_other # calls upgrade-heat for specific release upgrade_project heat $RUN_DIR $BASE_DEVSTACK_BRANCH $TARGET_DEVSTACK_BRANCH # Simulate init_heat() create_heat_cache_dir HEAT_BIN_DIR=$(dirname $(which heat-manage)) $HEAT_BIN_DIR/heat-manage --config-file $HEAT_CONF db_sync || die $LINENO "DB sync error" # Start Heat start_heat # Don't succeed unless the services come up ensure_services_started heat-api heat-engine heat-api-cloudwatch heat-api-cfn # run heat_integrationtests instead of tempest smoke after upgrade pushd $TARGET_DEVSTACK_DIR/../heat source $TARGET_DEVSTACK_DIR/openrc demo demo tox -eintegration heat_integrationtests.functional.test_create_update popd set +o xtrace echo "*********************************************************************" echo "SUCCESS: End $0" echo "*********************************************************************" heat-6.0.0/heat_integrationtests/0000775000567000056710000000000012701407211020201 5ustar jenkinsjenkins00000000000000heat-6.0.0/heat_integrationtests/__init__.py0000664000567000056710000000000012701407050022301 0ustar jenkinsjenkins00000000000000heat-6.0.0/heat_integrationtests/pre_test_hook.sh0000775000567000056710000000411412701407053023411 0ustar jenkinsjenkins00000000000000#!/bin/bash # # 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. # This script is executed inside pre_test_hook function in devstack gate. set -x localrc_path=$BASE/new/devstack/localrc localconf=$BASE/new/devstack/local.conf echo "CEILOMETER_PIPELINE_INTERVAL=60" >> $localrc_path echo "HEAT_ENABLE_ADOPT_ABANDON=True" >> $localrc_path echo -e '[[post-config|$HEAT_CONF]]\n[DEFAULT]\n' >> $localconf if [ "$ENABLE_CONVERGENCE" == "true" ] ; then echo -e 'convergence_engine=true\n' >> $localconf fi echo -e 'notification_driver=messagingv2\n' >> $localconf echo -e 'hidden_stack_tags=hidden\n' >> $localconf echo -e 'encrypt_parameters_and_properties=True\n' >> $localconf echo -e '[heat_api]\nworkers=2\n' >> $localconf echo -e '[heat_api_cfn]\nworkers=2\n' >> $localconf echo -e '[heat_api_cloudwatch]\nworkers=2\n' >> $localconf echo -e '[cache]\nenabled=True\n' >> $localconf echo -e '[[post-config|/etc/neutron/neutron_vpnaas.conf]]\n' >> $localconf echo -e '[service_providers]\nservice_provider=VPN:openswan:neutron_vpnaas.services.vpn.service_drivers.ipsec.IPsecVPNDriver:default\n' >> $localconf # TODO (MRV) Temp hack to use just the no-op octavia drivers for functional tests if [[ $OVERRIDE_ENABLED_SERVICES =~ "q-lbaasv2" ]] then echo "DISABLE_AMP_IMAGE_BUILD=True" >> $localrc_path echo -e '[[post-config|/etc/octavia/octavia.conf]]\n' >> $localconf echo -e '[controller_worker]\n' >> $localconf echo -e 'amphora_driver = amphora_noop_driver\n' >> $localconf echo -e 'compute_driver = compute_noop_driver\n' >> $localconf echo -e 'network_driver = network_noop_driver\n' >> $localconf fi heat-6.0.0/heat_integrationtests/heat_integrationtests.conf.sample0000664000567000056710000000721112701407053026744 0ustar jenkinsjenkins00000000000000[DEFAULT] # # From heat_integrationtests.common.config # # Username to use for non admin API requests. (string value) #username = # Non admin API key to use when authenticating. (string value) #password = # Username to use for admin API requests. (string value) #admin_username = # Admin API key to use when authentication. (string value) #admin_password = # Tenant name to use for API requests. (string value) #tenant_name = # Full URI of the OpenStack Identity API (Keystone) (string value) #auth_url = # User/project domain name, if keystone v3 auth_urlis used (string value) #domain_name = default # The region name to use (string value) #region = # Instance type for tests. Needs to be big enough for a full OS plus the test # workload (string value) #instance_type = # Instance type enough for simplest cases. (string value) #minimal_instance_type = # Name of image to use for tests which boot servers. (string value) #image_ref = # Name of existing keypair to launch servers with. (string value) #keypair_name = # Name of minimal (e.g cirros) image to use when launching test instances. # (string value) #minimal_image_ref = # Set to True if using self-signed SSL certificates. (boolean value) #disable_ssl_certificate_validation = false # Time in seconds between build status checks. (integer value) #build_interval = 4 # Timeout in seconds to wait for a stack to build. (integer value) #build_timeout = 1200 # Network used for SSH connections. (string value) #network_for_ssh = heat-net # Visible fixed network name (string value) #fixed_network_name = heat-net # Visible floating network name (string value) #floating_network_name = public # Path to environment file which defines the resource type # Heat::InstallConfigAgent. Needs to be appropriate for the image_ref. (string # value) #boot_config_env = heat_integrationtests/scenario/templates/boot_config_none_env.yaml # Visible fixed sub-network name (string value) #fixed_subnet_name = heat-subnet # Timeout in seconds to wait for authentication to succeed. (integer value) #ssh_timeout = 300 # IP version used for SSH connections. (integer value) #ip_version_for_ssh = 4 # Timeout in seconds to wait for output from ssh channel. (integer value) #ssh_channel_timeout = 60 # The mask bits for tenant ipv4 subnets (integer value) #tenant_network_mask_bits = 28 # Skip all scenario tests (boolean value) #skip_scenario_tests = false # Skip all functional tests (boolean value) #skip_functional_tests = false # List of functional test class or class.method names to skip ex. # AutoscalingGroupTest,InstanceGroupBasicTest.test_size_updates_work (list # value) #skip_functional_test_list = # List of scenario test class or class.method names to skip ex. # NeutronLoadBalancerTest, CeilometerAlarmTest.test_alarm (list value) #skip_scenario_test_list = # List of stack actions in tests to skip ex. ABANDON, ADOPT, SUSPEND, RESUME # (list value) #skip_test_stack_action_list = # Default size in GB for volumes created by volumes tests (integer value) #volume_size = 1 # Timeout in seconds to wait for connectivity to server. (integer value) #connectivity_timeout = 120 # Timeout in seconds to wait for adding or removing childprocess after # receiving of sighup signal (integer value) #sighup_timeout = 30 # Count of retries to edit config file during sighup. If another worker already # edit config file, file can be busy, so need to wait and try edit file # again. (integer value) #sighup_config_edit_retries = 10 # Path to the script heat-config-notify (string value) #heat_config_notify_script = heat-config-notify heat-6.0.0/heat_integrationtests/requirements.txt0000664000567000056710000000127512701407053023476 0ustar jenkinsjenkins00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr>=1.6 kombu>=3.0.25 os-collect-config oslo.log>=1.14.0 oslo.messaging>=4.0.0 oslo.concurrency>=3.5.0 oslo.config>=3.7.0 oslo.utils>=3.5.0 paramiko>=1.16.0 python-ceilometerclient>=2.2.1 python-cinderclient>=1.3.1 python-keystoneclient!=1.8.0,!=2.1.0,>=1.6.0 python-heatclient>=0.6.0 python-neutronclient!=4.1.0,>=2.6.0 python-novaclient!=2.33.0,>=2.29.0 python-swiftclient>=2.2.0 PyYAML>=3.1.0 requests!=2.9.0,>=2.8.1 six>=1.9.0 testrepository>=0.0.18 testscenarios>=0.4 testtools>=1.4.0 heat-6.0.0/heat_integrationtests/prepare_test_env.sh0000775000567000056710000000466312701407050024117 0ustar jenkinsjenkins00000000000000#!/bin/bash # # 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. # This script creates required cloud resources and sets test options # in heat_integrationtests.conf. # Credentials are required for creating nova flavors and glance images. set -x DEST=${DEST:-/opt/stack/new} source $DEST/devstack/inc/ini-config cd $DEST/heat/heat_integrationtests # Register the flavors for booting test servers iniset heat_integrationtests.conf DEFAULT instance_type m1.heat_int iniset heat_integrationtests.conf DEFAULT minimal_instance_type m1.heat_micro nova flavor-create m1.heat_int 452 512 0 1 nova flavor-create m1.heat_micro 453 128 0 1 # Register the glance image for testing curl http://tarballs.openstack.org/heat-test-image/fedora-heat-test-image.qcow2 | glance image-create --name fedora-heat-test-image --disk-format qcow2 --container-format bare --visibility public iniset heat_integrationtests.conf DEFAULT image_ref fedora-heat-test-image iniset heat_integrationtests.conf DEFAULT boot_config_env $DEST/heat-templates/hot/software-config/boot-config/test_image_env.yaml iniset heat_integrationtests.conf DEFAULT heat_config_notify_script $DEST/heat-templates/hot/software-config/elements/heat-config/bin/heat-config-notify iniset heat_integrationtests.conf DEFAULT minimal_image_ref cirros-0.3.4-x86_64-uec # admin creds already sourced, store in conf iniset heat_integrationtests.conf DEFAULT admin_username $OS_USERNAME iniset heat_integrationtests.conf DEFAULT admin_password $OS_PASSWORD # Add scenario tests to skip # VolumeBackupRestoreIntegrationTest skipped until failure rate can be reduced ref bug #1382300 iniset heat_integrationtests.conf DEFAULT skip_scenario_test_list 'SoftwareConfigIntegrationTest, VolumeBackupRestoreIntegrationTest' # Skip some tests for convergence until it is fixed if [ "$ENABLE_CONVERGENCE" == "true" ] ; then iniset heat_integrationtests.conf DEFAULT skip_functional_test_list 'StackValidationTest' fi cat heat_integrationtests.conf heat-6.0.0/heat_integrationtests/post_test_hook.sh0000775000567000056710000000162012701407050023604 0ustar jenkinsjenkins00000000000000#!/bin/bash # # 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. # This script is executed inside post_test_hook function in devstack gate. set -x export DEST=${DEST:-/opt/stack/new} source $DEST/devstack/openrc admin admin sudo -E $DEST/heat/heat_integrationtests/prepare_test_env.sh sudo -E $DEST/heat/heat_integrationtests/prepare_test_network.sh source $DEST/devstack/openrc demo demo sudo -E tox -eintegration heat-6.0.0/heat_integrationtests/common/0000775000567000056710000000000012701407211021471 5ustar jenkinsjenkins00000000000000heat-6.0.0/heat_integrationtests/common/test.py0000664000567000056710000006076412701407050023040 0ustar jenkinsjenkins00000000000000# 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 os import random import re import subprocess import time import fixtures from heatclient import exc as heat_exceptions from neutronclient.common import exceptions as network_exceptions from oslo_log import log as logging from oslo_utils import timeutils import six from six.moves import urllib import testscenarios import testtools from heat_integrationtests.common import clients from heat_integrationtests.common import config from heat_integrationtests.common import exceptions from heat_integrationtests.common import remote_client LOG = logging.getLogger(__name__) _LOG_FORMAT = "%(levelname)8s [%(name)s] %(message)s" def call_until_true(duration, sleep_for, func, *args, **kwargs): """Call the function until it returns True or the duration elapsed. Call the given function until it returns True (and return True) or until the specified duration (in seconds) elapses (and return False). :param func: A zero argument callable that returns True on success. :param duration: The number of seconds for which to attempt a successful call of the function. :param sleep_for: The number of seconds to sleep after an unsuccessful invocation of the function. """ now = time.time() timeout = now + duration while now < timeout: if func(*args, **kwargs): return True LOG.debug("Sleeping for %d seconds", sleep_for) time.sleep(sleep_for) now = time.time() return False def rand_name(name=''): randbits = str(random.randint(1, 0x7fffffff)) if name: return name + '-' + randbits else: return randbits class HeatIntegrationTest(testscenarios.WithScenarios, testtools.TestCase): def setUp(self): super(HeatIntegrationTest, self).setUp() self.conf = config.init_conf() self.assertIsNotNone(self.conf.auth_url, 'No auth_url configured') self.assertIsNotNone(self.conf.username, 'No username configured') self.assertIsNotNone(self.conf.password, 'No password configured') self.manager = clients.ClientManager(self.conf) self.identity_client = self.manager.identity_client self.orchestration_client = self.manager.orchestration_client self.compute_client = self.manager.compute_client self.network_client = self.manager.network_client self.volume_client = self.manager.volume_client self.object_client = self.manager.object_client self.metering_client = self.manager.metering_client self.useFixture(fixtures.FakeLogger(format=_LOG_FORMAT)) self.updated_time = {} if self.conf.disable_ssl_certificate_validation: self.verify_cert = False else: self.verify_cert = self.conf.ca_file or True def get_remote_client(self, server_or_ip, username, private_key=None): if isinstance(server_or_ip, six.string_types): ip = server_or_ip else: network_name_for_ssh = self.conf.network_for_ssh ip = server_or_ip.networks[network_name_for_ssh][0] if private_key is None: private_key = self.keypair.private_key linux_client = remote_client.RemoteClient(ip, username, pkey=private_key, conf=self.conf) try: linux_client.validate_authentication() except exceptions.SSHTimeout: LOG.exception('ssh connection to %s failed' % ip) raise return linux_client def check_connectivity(self, check_ip): def try_connect(ip): try: urllib.request.urlopen('http://%s/' % ip) return True except IOError: return False timeout = self.conf.connectivity_timeout elapsed_time = 0 while not try_connect(check_ip): time.sleep(10) elapsed_time += 10 if elapsed_time > timeout: raise exceptions.TimeoutException() def _log_console_output(self, servers=None): if not servers: servers = self.compute_client.servers.list() for server in servers: LOG.info('Console output for %s', server.id) LOG.info(server.get_console_output()) def _load_template(self, base_file, file_name, sub_dir=None): sub_dir = sub_dir or '' filepath = os.path.join(os.path.dirname(os.path.realpath(base_file)), sub_dir, file_name) with open(filepath) as f: return f.read() def create_keypair(self, client=None, name=None): if client is None: client = self.compute_client if name is None: name = rand_name('heat-keypair') keypair = client.keypairs.create(name) self.assertEqual(keypair.name, name) def delete_keypair(): keypair.delete() self.addCleanup(delete_keypair) return keypair def assign_keypair(self): if self.conf.keypair_name: self.keypair = None self.keypair_name = self.conf.keypair_name else: self.keypair = self.create_keypair() self.keypair_name = self.keypair.id @classmethod def _stack_rand_name(cls): return rand_name(cls.__name__) def _get_network(self, net_name=None): if net_name is None: net_name = self.conf.fixed_network_name networks = self.network_client.list_networks() for net in networks['networks']: if net['name'] == net_name: return net def is_network_extension_supported(self, extension_alias): try: self.network_client.show_extension(extension_alias) except network_exceptions.NeutronClientException: return False return True @staticmethod def _stack_output(stack, output_key, validate_errors=True): """Return a stack output value for a given key.""" value = None for o in stack.outputs: if validate_errors and 'output_error' in o: # scan for errors in the stack output. raise ValueError( 'Unexpected output errors in %s : %s' % ( output_key, o['output_error'])) if o['output_key'] == output_key: value = o['output_value'] return value def _ping_ip_address(self, ip_address, should_succeed=True): cmd = ['ping', '-c1', '-w1', ip_address] def ping(): proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) proc.wait() return (proc.returncode == 0) == should_succeed return call_until_true( self.conf.build_timeout, 1, ping) def _wait_for_all_resource_status(self, stack_identifier, status, failure_pattern='^.*_FAILED$', success_on_not_found=False): for res in self.client.resources.list(stack_identifier): self._wait_for_resource_status( stack_identifier, res.resource_name, status, failure_pattern=failure_pattern, success_on_not_found=success_on_not_found) def _wait_for_resource_status(self, stack_identifier, resource_name, status, failure_pattern='^.*_FAILED$', success_on_not_found=False): """Waits for a Resource to reach a given status.""" fail_regexp = re.compile(failure_pattern) build_timeout = self.conf.build_timeout build_interval = self.conf.build_interval start = timeutils.utcnow() while timeutils.delta_seconds(start, timeutils.utcnow()) < build_timeout: try: res = self.client.resources.get( stack_identifier, resource_name) except heat_exceptions.HTTPNotFound: if success_on_not_found: return # ignore this, as the resource may not have # been created yet else: if res.resource_status == status: return wait_for_action = status.split('_')[0] resource_action = res.resource_status.split('_')[0] if (resource_action == wait_for_action and fail_regexp.search(res.resource_status)): raise exceptions.StackResourceBuildErrorException( resource_name=res.resource_name, stack_identifier=stack_identifier, resource_status=res.resource_status, resource_status_reason=res.resource_status_reason) time.sleep(build_interval) message = ('Resource %s failed to reach %s status within ' 'the required time (%s s).' % (resource_name, status, build_timeout)) raise exceptions.TimeoutException(message) def verify_resource_status(self, stack_identifier, resource_name, status='CREATE_COMPLETE'): try: res = self.client.resources.get(stack_identifier, resource_name) except heat_exceptions.HTTPNotFound: return False return res.resource_status == status def _verify_status(self, stack, stack_identifier, status, fail_regexp): if stack.stack_status == status: # Handle UPDATE_COMPLETE/FAILED case: Make sure we don't # wait for a stale UPDATE_COMPLETE/FAILED status. if status in ('UPDATE_FAILED', 'UPDATE_COMPLETE'): if self.updated_time.get( stack_identifier) != stack.updated_time: self.updated_time[stack_identifier] = stack.updated_time return True else: return True wait_for_action = status.split('_')[0] if (stack.action == wait_for_action and fail_regexp.search(stack.stack_status)): # Handle UPDATE_COMPLETE/UPDATE_FAILED case. if status in ('UPDATE_FAILED', 'UPDATE_COMPLETE'): if self.updated_time.get( stack_identifier) != stack.updated_time: self.updated_time[stack_identifier] = stack.updated_time raise exceptions.StackBuildErrorException( stack_identifier=stack_identifier, stack_status=stack.stack_status, stack_status_reason=stack.stack_status_reason) else: raise exceptions.StackBuildErrorException( stack_identifier=stack_identifier, stack_status=stack.stack_status, stack_status_reason=stack.stack_status_reason) def _wait_for_stack_status(self, stack_identifier, status, failure_pattern=None, success_on_not_found=False): """Waits for a Stack to reach a given status. Note this compares the full $action_$status, e.g CREATE_COMPLETE, not just COMPLETE which is exposed via the status property of Stack in heatclient """ if failure_pattern: fail_regexp = re.compile(failure_pattern) elif 'FAILED' in status: # If we're looking for e.g CREATE_FAILED, COMPLETE is unexpected. fail_regexp = re.compile('^.*_COMPLETE$') else: fail_regexp = re.compile('^.*_FAILED$') build_timeout = self.conf.build_timeout build_interval = self.conf.build_interval start = timeutils.utcnow() while timeutils.delta_seconds(start, timeutils.utcnow()) < build_timeout: try: stack = self.client.stacks.get(stack_identifier, resolve_outputs=False) except heat_exceptions.HTTPNotFound: if success_on_not_found: return # ignore this, as the resource may not have # been created yet else: if self._verify_status(stack, stack_identifier, status, fail_regexp): return time.sleep(build_interval) message = ('Stack %s failed to reach %s status within ' 'the required time (%s s).' % (stack_identifier, status, build_timeout)) raise exceptions.TimeoutException(message) def _stack_delete(self, stack_identifier): try: self._handle_in_progress(self.client.stacks.delete, stack_identifier) except heat_exceptions.HTTPNotFound: pass self._wait_for_stack_status( stack_identifier, 'DELETE_COMPLETE', success_on_not_found=True) def _handle_in_progress(self, fn, *args, **kwargs): build_timeout = self.conf.build_timeout build_interval = self.conf.build_interval start = timeutils.utcnow() while timeutils.delta_seconds(start, timeutils.utcnow()) < build_timeout: try: fn(*args, **kwargs) except heat_exceptions.HTTPConflict as ex: # FIXME(sirushtim): Wait a little for the stack lock to be # released and hopefully, the stack should be usable again. if ex.error['error']['type'] != 'ActionInProgress': raise ex time.sleep(build_interval) else: break def update_stack(self, stack_identifier, template=None, environment=None, files=None, parameters=None, tags=None, expected_status='UPDATE_COMPLETE', disable_rollback=True, existing=False): env = environment or {} env_files = files or {} parameters = parameters or {} stack_name = stack_identifier.split('/')[0] self.updated_time[stack_identifier] = self.client.stacks.get( stack_identifier, resolve_outputs=False).updated_time self._handle_in_progress( self.client.stacks.update, stack_id=stack_identifier, stack_name=stack_name, template=template, files=env_files, disable_rollback=disable_rollback, parameters=parameters, environment=env, tags=tags, existing=existing) kwargs = {'stack_identifier': stack_identifier, 'status': expected_status} if expected_status in ['ROLLBACK_COMPLETE']: # To trigger rollback you would intentionally fail the stack # Hence check for rollback failures kwargs['failure_pattern'] = '^ROLLBACK_FAILED$' self._wait_for_stack_status(**kwargs) def preview_update_stack(self, stack_identifier, template, environment=None, files=None, parameters=None, tags=None, disable_rollback=True, show_nested=False): env = environment or {} env_files = files or {} parameters = parameters or {} stack_name = stack_identifier.split('/')[0] return self.client.stacks.preview_update( stack_id=stack_identifier, stack_name=stack_name, template=template, files=env_files, disable_rollback=disable_rollback, parameters=parameters, environment=env, tags=tags, show_nested=show_nested ) def assert_resource_is_a_stack(self, stack_identifier, res_name, wait=False): build_timeout = self.conf.build_timeout build_interval = self.conf.build_interval start = timeutils.utcnow() while timeutils.delta_seconds(start, timeutils.utcnow()) < build_timeout: time.sleep(build_interval) try: nested_identifier = self._get_nested_identifier( stack_identifier, res_name) except Exception: # We may have to wait, if the create is in-progress if wait: time.sleep(build_interval) else: raise else: return nested_identifier def _get_nested_identifier(self, stack_identifier, res_name): rsrc = self.client.resources.get(stack_identifier, res_name) nested_link = [l for l in rsrc.links if l['rel'] == 'nested'] nested_href = nested_link[0]['href'] nested_id = nested_href.split('/')[-1] nested_identifier = '/'.join(nested_href.split('/')[-2:]) self.assertEqual(rsrc.physical_resource_id, nested_id) nested_stack = self.client.stacks.get(nested_id, resolve_outputs=False) nested_identifier2 = '%s/%s' % (nested_stack.stack_name, nested_stack.id) self.assertEqual(nested_identifier, nested_identifier2) parent_id = stack_identifier.split("/")[-1] self.assertEqual(parent_id, nested_stack.parent) return nested_identifier def group_nested_identifier(self, stack_identifier, group_name): # Get the nested stack identifier from a group resource rsrc = self.client.resources.get(stack_identifier, group_name) physical_resource_id = rsrc.physical_resource_id nested_stack = self.client.stacks.get(physical_resource_id, resolve_outputs=False) nested_identifier = '%s/%s' % (nested_stack.stack_name, nested_stack.id) parent_id = stack_identifier.split("/")[-1] self.assertEqual(parent_id, nested_stack.parent) return nested_identifier def list_group_resources(self, stack_identifier, group_name, minimal=True): nested_identifier = self.group_nested_identifier(stack_identifier, group_name) if minimal: return self.list_resources(nested_identifier) return self.client.resources.list(nested_identifier) def list_resources(self, stack_identifier): resources = self.client.resources.list(stack_identifier) return dict((r.resource_name, r.resource_type) for r in resources) def stack_create(self, stack_name=None, template=None, files=None, parameters=None, environment=None, tags=None, expected_status='CREATE_COMPLETE', disable_rollback=True, enable_cleanup=True, environment_files=None): name = stack_name or self._stack_rand_name() templ = template or self.template templ_files = files or {} params = parameters or {} env = environment or {} self.client.stacks.create( stack_name=name, template=templ, files=templ_files, disable_rollback=disable_rollback, parameters=params, environment=env, tags=tags, environment_files=environment_files ) if expected_status not in ['ROLLBACK_COMPLETE'] and enable_cleanup: self.addCleanup(self._stack_delete, name) stack = self.client.stacks.get(name, resolve_outputs=False) stack_identifier = '%s/%s' % (name, stack.id) kwargs = {'stack_identifier': stack_identifier, 'status': expected_status} if expected_status: if expected_status in ['ROLLBACK_COMPLETE']: # To trigger rollback you would intentionally fail the stack # Hence check for rollback failures kwargs['failure_pattern'] = '^ROLLBACK_FAILED$' self._wait_for_stack_status(**kwargs) return stack_identifier def stack_adopt(self, stack_name=None, files=None, parameters=None, environment=None, adopt_data=None, wait_for_status='ADOPT_COMPLETE'): if (self.conf.skip_test_stack_action_list and 'ADOPT' in self.conf.skip_test_stack_action_list): self.skipTest('Testing Stack adopt disabled in conf, skipping') name = stack_name or self._stack_rand_name() templ_files = files or {} params = parameters or {} env = environment or {} self.client.stacks.create( stack_name=name, files=templ_files, disable_rollback=True, parameters=params, environment=env, adopt_stack_data=adopt_data, ) self.addCleanup(self._stack_delete, name) stack = self.client.stacks.get(name, resolve_outputs=False) stack_identifier = '%s/%s' % (name, stack.id) self._wait_for_stack_status(stack_identifier, wait_for_status) return stack_identifier def stack_abandon(self, stack_id): if (self.conf.skip_test_stack_action_list and 'ABANDON' in self.conf.skip_test_stack_action_list): self.addCleanup(self._stack_delete, stack_id) self.skipTest('Testing Stack abandon disabled in conf, skipping') info = self.client.stacks.abandon(stack_id=stack_id) return info def stack_suspend(self, stack_identifier): if (self.conf.skip_test_stack_action_list and 'SUSPEND' in self.conf.skip_test_stack_action_list): self.addCleanup(self._stack_delete, stack_identifier) self.skipTest('Testing Stack suspend disabled in conf, skipping') stack_name = stack_identifier.split('/')[0] self._handle_in_progress(self.client.actions.suspend, stack_name) # improve debugging by first checking the resource's state. self._wait_for_all_resource_status(stack_identifier, 'SUSPEND_COMPLETE') self._wait_for_stack_status(stack_identifier, 'SUSPEND_COMPLETE') def stack_resume(self, stack_identifier): if (self.conf.skip_test_stack_action_list and 'RESUME' in self.conf.skip_test_stack_action_list): self.addCleanup(self._stack_delete, stack_identifier) self.skipTest('Testing Stack resume disabled in conf, skipping') stack_name = stack_identifier.split('/')[0] self._handle_in_progress(self.client.actions.resume, stack_name) # improve debugging by first checking the resource's state. self._wait_for_all_resource_status(stack_identifier, 'RESUME_COMPLETE') self._wait_for_stack_status(stack_identifier, 'RESUME_COMPLETE') def wait_for_event_with_reason(self, stack_identifier, reason, rsrc_name=None, num_expected=1): build_timeout = self.conf.build_timeout build_interval = self.conf.build_interval start = timeutils.utcnow() while timeutils.delta_seconds(start, timeutils.utcnow()) < build_timeout: try: rsrc_events = self.client.events.list(stack_identifier, resource_name=rsrc_name) except heat_exceptions.HTTPNotFound: LOG.debug("No events yet found for %s" % rsrc_name) else: matched = [e for e in rsrc_events if e.resource_status_reason == reason] if len(matched) == num_expected: return matched time.sleep(build_interval) heat-6.0.0/heat_integrationtests/common/clients.py0000664000567000056710000001747512701407053023526 0ustar jenkinsjenkins00000000000000# 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 os from ceilometerclient import client as ceilometer_client from cinderclient import client as cinder_client from heat.common.i18n import _ from heatclient import client as heat_client from keystoneclient.auth.identity.generic import password from keystoneclient import exceptions as kc_exceptions from keystoneclient import session from neutronclient.v2_0 import client as neutron_client from novaclient import client as nova_client from swiftclient import client as swift_client class KeystoneWrapperClient(object): """Wrapper object for keystone client This wraps keystone client, so we can encpasulate certain added properties like auth_token, project_id etc. """ def __init__(self, auth_plugin, verify=True): self.auth_plugin = auth_plugin self.session = session.Session( auth=auth_plugin, verify=verify) @property def auth_token(self): return self.auth_plugin.get_token(self.session) @property def auth_ref(self): return self.auth_plugin.get_access(self.session) @property def project_id(self): return self.auth_plugin.get_project_id(self.session) def get_endpoint_url(self, service_type, region=None): kwargs = { 'service_type': service_type, 'endpoint_type': 'publicURL'} if region: kwargs.update({'attr': 'region', 'filter_value': region}) return self.auth_ref.service_catalog.url_for(**kwargs) class ClientManager(object): """Provides access to the official python clients for calling various APIs. Manager that provides access to the official python clients for calling various OpenStack APIs. """ CINDERCLIENT_VERSION = '2' HEATCLIENT_VERSION = '1' NOVACLIENT_VERSION = '2' CEILOMETER_VERSION = '2' def __init__(self, conf): self.conf = conf if self.conf.auth_url.find('/v'): self.v2_auth_url = self.conf.auth_url.replace('/v3', '/v2.0') self.auth_version = self.conf.auth_url.split('/v')[1] else: raise ValueError(_('Incorrectly specified auth_url config: no ' 'version found.')) self.insecure = self.conf.disable_ssl_certificate_validation self.ca_file = self.conf.ca_file self.identity_client = self._get_identity_client() self.orchestration_client = self._get_orchestration_client() self.compute_client = self._get_compute_client() self.network_client = self._get_network_client() self.volume_client = self._get_volume_client() self.object_client = self._get_object_client() self.metering_client = self._get_metering_client() def _get_orchestration_client(self): endpoint = os.environ.get('HEAT_URL') if os.environ.get('OS_NO_CLIENT_AUTH') == 'True': token = None else: token = self.identity_client.auth_token try: if endpoint is None: endpoint = self.identity_client.get_endpoint_url( 'orchestration', self.conf.region) except kc_exceptions.EndpointNotFound: return None else: return heat_client.Client( self.HEATCLIENT_VERSION, endpoint, token=token, username=self.conf.username, password=self.conf.password) def _get_identity_client(self): domain = self.conf.domain_name kwargs = { 'username': self.conf.username, 'password': self.conf.password, 'tenant_name': self.conf.tenant_name, 'auth_url': self.conf.auth_url } # keystone v2 can't ignore domain details if self.auth_version == '3': kwargs.update({ 'project_domain_name': domain, 'user_domain_name': domain}) auth = password.Password(**kwargs) if self.insecure: verify_cert = False else: verify_cert = self.ca_file or True return KeystoneWrapperClient(auth, verify_cert) def _get_compute_client(self): region = self.conf.region client_args = ( self.conf.username, self.conf.password, self.conf.tenant_name, # novaclient can not use v3 url self.v2_auth_url ) # Create our default Nova client to use in testing return nova_client.Client( self.NOVACLIENT_VERSION, *client_args, service_type='compute', endpoint_type='publicURL', region_name=region, no_cache=True, insecure=self.insecure, cacert=self.ca_file, http_log_debug=True) def _get_network_client(self): return neutron_client.Client( username=self.conf.username, password=self.conf.password, tenant_name=self.conf.tenant_name, endpoint_type='publicURL', # neutronclient can not use v3 url auth_url=self.v2_auth_url, insecure=self.insecure, ca_cert=self.ca_file) def _get_volume_client(self): region = self.conf.region endpoint_type = 'publicURL' return cinder_client.Client( self.CINDERCLIENT_VERSION, self.conf.username, self.conf.password, self.conf.tenant_name, # cinderclient can not use v3 url self.v2_auth_url, region_name=region, endpoint_type=endpoint_type, insecure=self.insecure, cacert=self.ca_file, http_log_debug=True) def _get_object_client(self): args = { 'auth_version': self.auth_version, 'tenant_name': self.conf.tenant_name, 'user': self.conf.username, 'key': self.conf.password, 'authurl': self.conf.auth_url, 'os_options': {'endpoint_type': 'publicURL'}, 'insecure': self.insecure, 'cacert': self.ca_file, } return swift_client.Connection(**args) def _get_metering_client(self): domain = self.conf.domain_name try: endpoint = self.identity_client.get_endpoint_url('metering', self.conf.region) except kc_exceptions.EndpointNotFound: return None else: args = { 'username': self.conf.username, 'password': self.conf.password, 'tenant_name': self.conf.tenant_name, 'auth_url': self.conf.auth_url, 'insecure': self.insecure, 'cacert': self.ca_file, 'region_name': self.conf.region, 'endpoint_type': 'publicURL', 'service_type': 'metering', } # ceilometerclient can't ignore domain details for # v2 auth_url if self.auth_version == '3': args.update( {'user_domain_name': domain, 'project_domain_name': domain}) return ceilometer_client.Client(self.CEILOMETER_VERSION, endpoint, **args) heat-6.0.0/heat_integrationtests/common/__init__.py0000664000567000056710000000000012701407050023571 0ustar jenkinsjenkins00000000000000heat-6.0.0/heat_integrationtests/common/exceptions.py0000664000567000056710000000556112701407050024234 0ustar jenkinsjenkins00000000000000# 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. class IntegrationException(Exception): """Base Tempest Exception. To correctly use this class, inherit from it and define a 'message' property. That message will get printf'd with the keyword arguments provided to the constructor. """ message = "An unknown exception occurred" def __init__(self, *args, **kwargs): super(IntegrationException, self).__init__() try: self._error_string = self.message % kwargs except Exception: # at least get the core message out if something happened self._error_string = self.message if len(args) > 0: # If there is a non-kwarg parameter, assume it's the error # message or reason description and tack it on to the end # of the exception message # Convert all arguments into their string representations... args = ["%s" % arg for arg in args] self._error_string = (self._error_string + "\nDetails: %s" % '\n'.join(args)) def __str__(self): return self._error_string class InvalidCredentials(IntegrationException): message = "Invalid Credentials" class TimeoutException(IntegrationException): message = "Request timed out" class BuildErrorException(IntegrationException): message = "Server %(server_id)s failed to build and is in ERROR status" class StackBuildErrorException(IntegrationException): message = ("Stack %(stack_identifier)s is in %(stack_status)s status " "due to '%(stack_status_reason)s'") class StackResourceBuildErrorException(IntegrationException): message = ("Resource %(resource_name)s in stack %(stack_identifier)s is " "in %(resource_status)s status due to " "'%(resource_status_reason)s'") class SSHTimeout(IntegrationException): message = ("Connection to the %(host)s via SSH timed out.\n" "User: %(user)s, Password: %(password)s") class SSHExecCommandFailed(IntegrationException): """Raised when remotely executed command returns nonzero status.""" message = ("Command '%(command)s', exit status: %(exit_status)d, " "Error:\n%(strerror)s") class ServerUnreachable(IntegrationException): message = "The server is not reachable via the configured network" heat-6.0.0/heat_integrationtests/common/config.py0000664000567000056710000001553212701407053023322 0ustar jenkinsjenkins00000000000000# 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 os from oslo_config import cfg import heat_integrationtests IntegrationTestGroup = [ cfg.StrOpt('username', default=os.environ.get('OS_USERNAME'), help="Username to use for non admin API requests."), cfg.StrOpt('password', default=os.environ.get('OS_PASSWORD'), help="Non admin API key to use when authenticating.", secret=True), cfg.StrOpt('admin_username', help="Username to use for admin API requests."), cfg.StrOpt('admin_password', help="Admin API key to use when authentication.", secret=True), cfg.StrOpt('tenant_name', default=(os.environ.get('OS_PROJECT_NAME') or os.environ.get('OS_TENANT_NAME')), help="Tenant name to use for API requests."), cfg.StrOpt('auth_url', default=os.environ.get('OS_AUTH_URL'), help="Full URI of the OpenStack Identity API (Keystone)"), cfg.StrOpt('domain_name', default='default', help="User/project domain name, if keystone v3 auth_url" "is used"), cfg.StrOpt('region', default=os.environ.get('OS_REGION_NAME'), help="The region name to use"), cfg.StrOpt('instance_type', help="Instance type for tests. Needs to be big enough for a " "full OS plus the test workload"), cfg.StrOpt('minimal_instance_type', help="Instance type enough for simplest cases."), cfg.StrOpt('image_ref', help="Name of image to use for tests which boot servers."), cfg.StrOpt('keypair_name', help="Name of existing keypair to launch servers with."), cfg.StrOpt('minimal_image_ref', help="Name of minimal (e.g cirros) image to use when " "launching test instances."), cfg.BoolOpt('disable_ssl_certificate_validation', default=False, help="Set to True if using self-signed SSL certificates."), cfg.StrOpt('ca_file', default=None, help="CA certificate to pass for servers that have " "https endpoint."), cfg.IntOpt('build_interval', default=4, help="Time in seconds between build status checks."), cfg.IntOpt('build_timeout', default=1200, help="Timeout in seconds to wait for a stack to build."), cfg.StrOpt('network_for_ssh', default='heat-net', help="Network used for SSH connections."), cfg.StrOpt('fixed_network_name', default='heat-net', help="Visible fixed network name "), cfg.StrOpt('floating_network_name', default='public', help="Visible floating network name "), cfg.StrOpt('boot_config_env', default=('heat_integrationtests/scenario/templates' '/boot_config_none_env.yaml'), help="Path to environment file which defines the " "resource type Heat::InstallConfigAgent. Needs to " "be appropriate for the image_ref."), cfg.StrOpt('fixed_subnet_name', default='heat-subnet', help="Visible fixed sub-network name "), cfg.IntOpt('ssh_timeout', default=300, help="Timeout in seconds to wait for authentication to " "succeed."), cfg.IntOpt('ip_version_for_ssh', default=4, help="IP version used for SSH connections."), cfg.IntOpt('ssh_channel_timeout', default=60, help="Timeout in seconds to wait for output from ssh " "channel."), cfg.IntOpt('tenant_network_mask_bits', default=28, help="The mask bits for tenant ipv4 subnets"), cfg.BoolOpt('skip_scenario_tests', default=False, help="Skip all scenario tests"), cfg.BoolOpt('skip_functional_tests', default=False, help="Skip all functional tests"), cfg.ListOpt('skip_functional_test_list', help="List of functional test class or class.method " "names to skip ex. AutoscalingGroupTest," "InstanceGroupBasicTest.test_size_updates_work"), cfg.ListOpt('skip_scenario_test_list', help="List of scenario test class or class.method " "names to skip ex. NeutronLoadBalancerTest, " "CeilometerAlarmTest.test_alarm"), cfg.ListOpt('skip_test_stack_action_list', help="List of stack actions in tests to skip " "ex. ABANDON, ADOPT, SUSPEND, RESUME"), cfg.IntOpt('volume_size', default=1, help='Default size in GB for volumes created by volumes tests'), cfg.IntOpt('connectivity_timeout', default=120, help="Timeout in seconds to wait for connectivity to " "server."), cfg.IntOpt('sighup_timeout', default=30, help="Timeout in seconds to wait for adding or removing child" "process after receiving of sighup signal"), cfg.IntOpt('sighup_config_edit_retries', default=10, help='Count of retries to edit config file during sighup. If ' 'another worker already edit config file, file can be ' 'busy, so need to wait and try edit file again.'), cfg.StrOpt('heat-config-notify-script', default=('heat-config-notify'), help="Path to the script heat-config-notify"), ] def init_conf(read_conf=True): default_config_files = None if read_conf: confpath = os.path.join( os.path.dirname(os.path.realpath(heat_integrationtests.__file__)), 'heat_integrationtests.conf') if os.path.isfile(confpath): default_config_files = [confpath] conf = cfg.ConfigOpts() conf(args=[], project='heat_integrationtests', default_config_files=default_config_files) for opt in IntegrationTestGroup: conf.register_opt(opt) return conf def list_opts(): yield None, IntegrationTestGroup heat-6.0.0/heat_integrationtests/common/remote_client.py0000664000567000056710000001735512701407050024710 0ustar jenkinsjenkins00000000000000# 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 re import select import socket import time from oslo_log import log as logging import paramiko import six from heat_integrationtests.common import exceptions LOG = logging.getLogger(__name__) class Client(object): def __init__(self, host, username, password=None, timeout=300, pkey=None, channel_timeout=10, look_for_keys=False, key_filename=None): self.host = host self.username = username self.password = password if isinstance(pkey, six.string_types): pkey = paramiko.RSAKey.from_private_key( six.moves.cStringIO(str(pkey))) self.pkey = pkey self.look_for_keys = look_for_keys self.key_filename = key_filename self.timeout = int(timeout) self.channel_timeout = float(channel_timeout) self.buf_size = 1024 def _get_ssh_connection(self, sleep=1.5, backoff=1): """Returns an ssh connection to the specified host.""" bsleep = sleep ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy( paramiko.AutoAddPolicy()) _start_time = time.time() if self.pkey is not None: LOG.info("Creating ssh connection to '%s' as '%s'" " with public key authentication", self.host, self.username) else: LOG.info("Creating ssh connection to '%s' as '%s'" " with password %s", self.host, self.username, str(self.password)) attempts = 0 while True: try: ssh.connect(self.host, username=self.username, password=self.password, look_for_keys=self.look_for_keys, key_filename=self.key_filename, timeout=self.channel_timeout, pkey=self.pkey) LOG.info("ssh connection to %s@%s successfuly created", self.username, self.host) return ssh except (socket.error, paramiko.SSHException) as e: if self._is_timed_out(_start_time): LOG.exception("Failed to establish authenticated ssh" " connection to %s@%s after %d attempts", self.username, self.host, attempts) raise exceptions.SSHTimeout(host=self.host, user=self.username, password=self.password) bsleep += backoff attempts += 1 LOG.warning("Failed to establish authenticated ssh" " connection to %s@%s (%s). Number attempts: %s." " Retry after %d seconds.", self.username, self.host, e, attempts, bsleep) time.sleep(bsleep) def _is_timed_out(self, start_time): return (time.time() - self.timeout) > start_time def exec_command(self, cmd): """Execute the specified command on the server. Note that this method is reading whole command outputs to memory, thus shouldn't be used for large outputs. :returns: data read from standard output of the command. :raises: SSHExecCommandFailed if command returns nonzero status. The exception contains command status stderr content. """ ssh = self._get_ssh_connection() transport = ssh.get_transport() channel = transport.open_session() channel.fileno() # Register event pipe channel.exec_command(cmd) channel.shutdown_write() out_data = [] err_data = [] poll = select.poll() poll.register(channel, select.POLLIN) start_time = time.time() while True: ready = poll.poll(self.channel_timeout) if not any(ready): if not self._is_timed_out(start_time): continue raise exceptions.TimeoutException( "Command: '{0}' executed on host '{1}'.".format( cmd, self.host)) if not ready[0]: # If there is nothing to read. continue out_chunk = err_chunk = None if channel.recv_ready(): out_chunk = channel.recv(self.buf_size) out_data += out_chunk, if channel.recv_stderr_ready(): err_chunk = channel.recv_stderr(self.buf_size) err_data += err_chunk, if channel.closed and not err_chunk and not out_chunk: break exit_status = channel.recv_exit_status() if 0 != exit_status: raise exceptions.SSHExecCommandFailed( command=cmd, exit_status=exit_status, strerror=''.join(err_data)) return ''.join(out_data) def test_connection_auth(self): """Raises an exception when we can not connect to server via ssh.""" connection = self._get_ssh_connection() connection.close() class RemoteClient(object): # NOTE(afazekas): It should always get an address instead of server def __init__(self, server, username, password=None, pkey=None, conf=None): self.conf = conf ssh_timeout = self.conf.ssh_timeout network = self.conf.network_for_ssh ip_version = self.conf.ip_version_for_ssh ssh_channel_timeout = self.conf.ssh_channel_timeout if isinstance(server, six.string_types): ip_address = server else: addresses = server['addresses'][network] for address in addresses: if address['version'] == ip_version: ip_address = address['addr'] break else: raise exceptions.ServerUnreachable() self.ssh_client = Client(ip_address, username, password, ssh_timeout, pkey=pkey, channel_timeout=ssh_channel_timeout) def exec_command(self, cmd): return self.ssh_client.exec_command(cmd) def validate_authentication(self): """Validate ssh connection and authentication. This method raises an Exception when the validation fails. """ self.ssh_client.test_connection_auth() def get_partitions(self): # Return the contents of /proc/partitions command = 'cat /proc/partitions' output = self.exec_command(command) return output def get_boot_time(self): cmd = 'cut -f1 -d. /proc/uptime' boot_secs = self.exec_command(cmd) boot_time = time.time() - int(boot_secs) return time.localtime(boot_time) def write_to_console(self, message): message = re.sub("([$\\`])", "\\\\\\\\\\1", message) # usually to /dev/ttyS0 cmd = 'sudo sh -c "echo \\"%s\\" >/dev/console"' % message return self.exec_command(cmd) def ping_host(self, host): cmd = 'ping -c1 -w1 %s' % host return self.exec_command(cmd) def get_ip_list(self): cmd = "/bin/ip address" return self.exec_command(cmd) heat-6.0.0/heat_integrationtests/README.rst0000664000567000056710000000127012701407050021671 0ustar jenkinsjenkins00000000000000====================== Heat integration tests ====================== These tests can be run against any heat-enabled OpenStack cloud, however defaults match running against a recent DevStack. To run the tests against DevStack, do the following: # source DevStack credentials source /opt/stack/devstack/openrc # run the heat integration tests with those credentials cd /opt/stack/heat tox -eintegration If custom configuration is required, copy the following file: heat_integrationtests/heat_integrationtests.conf.sample to: heat_integrationtests/heat_integrationtests.conf and make any required configuration changes before running: tox -eintegration heat-6.0.0/heat_integrationtests/config-generator.conf0000664000567000056710000000022012701407050024274 0ustar jenkinsjenkins00000000000000[DEFAULT] output_file = heat_integrationtests/heat_integrationtests.conf.sample wrap_width = 79 namespace = heat_integrationtests.common.config heat-6.0.0/heat_integrationtests/.gitignore0000664000567000056710000000003212701407050022165 0ustar jenkinsjenkins00000000000000heat_integrationtests.confheat-6.0.0/heat_integrationtests/functional/0000775000567000056710000000000012701407211022343 5ustar jenkinsjenkins00000000000000heat-6.0.0/heat_integrationtests/functional/test_create_update.py0000664000567000056710000006451612701407050026576 0ustar jenkinsjenkins00000000000000# 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 copy import json from heat_integrationtests.functional import functional_base test_template_one_resource = { 'heat_template_version': '2013-05-23', 'description': 'Test template to create one instance.', 'resources': { 'test1': { 'type': 'OS::Heat::TestResource', 'properties': { 'value': 'Test1', 'fail': False, 'update_replace': False, 'wait_secs': 0, 'action_wait_secs': {'create': 1}, 'client_name': 'nova', 'entity_name': 'servers', } } } } test_template_two_resource = { 'heat_template_version': '2013-05-23', 'description': 'Test template to create two instance.', 'resources': { 'test1': { 'type': 'OS::Heat::TestResource', 'properties': { 'value': 'Test1', 'fail': False, 'update_replace': False, 'wait_secs': 0, 'action_wait_secs': {'update': 1} } }, 'test2': { 'type': 'OS::Heat::TestResource', 'properties': { 'value': 'Test1', 'fail': False, 'update_replace': False, 'wait_secs': 0 } } } } def _change_rsrc_properties(template, rsrcs, values): modified_template = copy.deepcopy(template) for rsrc_name in rsrcs: rsrc_prop = modified_template['resources'][ rsrc_name]['properties'] for prop in rsrc_prop: if prop in values: rsrc_prop[prop] = values[prop] return modified_template class CreateStackTest(functional_base.FunctionalTestsBase): def setUp(self): super(CreateStackTest, self).setUp() def test_create_rollback(self): values = {'fail': True, 'value': 'test_create_rollback'} template = _change_rsrc_properties(test_template_one_resource, ['test1'], values) self.stack_create( template=template, expected_status='ROLLBACK_COMPLETE', disable_rollback=False) class UpdateStackTest(functional_base.FunctionalTestsBase): provider_template = { 'heat_template_version': '2013-05-23', 'description': 'foo', 'resources': { 'test1': { 'type': 'My::TestResource' } } } provider_group_template = ''' heat_template_version: 2013-05-23 parameters: count: type: number default: 2 resources: test_group: type: OS::Heat::ResourceGroup properties: count: {get_param: count} resource_def: type: My::TestResource ''' update_userdata_template = ''' heat_template_version: 2014-10-16 parameters: flavor: type: string user_data: type: string image: type: string network: type: string resources: server: type: OS::Nova::Server properties: image: {get_param: image} flavor: {get_param: flavor} networks: [{network: {get_param: network} }] user_data_format: SOFTWARE_CONFIG user_data: {get_param: user_data} ''' fail_param_template = ''' heat_template_version: 2014-10-16 parameters: do_fail: type: boolean default: False resources: aresource: type: OS::Heat::TestResource properties: value: Test fail: {get_param: do_fail} ''' def setUp(self): super(UpdateStackTest, self).setUp() def test_stack_update_nochange(self): template = _change_rsrc_properties(test_template_one_resource, ['test1'], {'value': 'test_no_change'}) stack_identifier = self.stack_create( template=template) expected_resources = {'test1': 'OS::Heat::TestResource'} self.assertEqual(expected_resources, self.list_resources(stack_identifier)) # Update with no changes, resources should be unchanged self.update_stack(stack_identifier, template) self.assertEqual(expected_resources, self.list_resources(stack_identifier)) def test_stack_in_place_update(self): template = _change_rsrc_properties(test_template_one_resource, ['test1'], {'value': 'test_in_place'}) stack_identifier = self.stack_create( template=template) expected_resources = {'test1': 'OS::Heat::TestResource'} self.assertEqual(expected_resources, self.list_resources(stack_identifier)) resource = self.client.resources.list(stack_identifier) initial_phy_id = resource[0].physical_resource_id tmpl_update = _change_rsrc_properties( test_template_one_resource, ['test1'], {'value': 'test_in_place_update'}) # Update the Value self.update_stack(stack_identifier, tmpl_update) resource = self.client.resources.list(stack_identifier) # By default update_in_place self.assertEqual(initial_phy_id, resource[0].physical_resource_id) def test_stack_update_replace(self): template = _change_rsrc_properties(test_template_one_resource, ['test1'], {'value': 'test_replace'}) stack_identifier = self.stack_create( template=template) expected_resources = {'test1': 'OS::Heat::TestResource'} self.assertEqual(expected_resources, self.list_resources(stack_identifier)) resource = self.client.resources.list(stack_identifier) initial_phy_id = resource[0].physical_resource_id # Update the value and also set update_replace prop tmpl_update = _change_rsrc_properties( test_template_one_resource, ['test1'], {'value': 'test_in_place_update', 'update_replace': True}) self.update_stack(stack_identifier, tmpl_update) resource = self.client.resources.list(stack_identifier) # update Replace self.assertNotEqual(initial_phy_id, resource[0].physical_resource_id) def test_stack_update_add_remove(self): template = _change_rsrc_properties(test_template_one_resource, ['test1'], {'value': 'test_add_remove'}) stack_identifier = self.stack_create( template=template) initial_resources = {'test1': 'OS::Heat::TestResource'} self.assertEqual(initial_resources, self.list_resources(stack_identifier)) tmpl_update = _change_rsrc_properties( test_template_two_resource, ['test1', 'test2'], {'value': 'test_add_remove_update'}) # Add one resource via a stack update self.update_stack(stack_identifier, tmpl_update) updated_resources = {'test1': 'OS::Heat::TestResource', 'test2': 'OS::Heat::TestResource'} self.assertEqual(updated_resources, self.list_resources(stack_identifier)) # Then remove it by updating with the original template self.update_stack(stack_identifier, template) self.assertEqual(initial_resources, self.list_resources(stack_identifier)) def test_stack_update_rollback(self): template = _change_rsrc_properties(test_template_one_resource, ['test1'], {'value': 'test_update_rollback'}) stack_identifier = self.stack_create( template=template) initial_resources = {'test1': 'OS::Heat::TestResource'} self.assertEqual(initial_resources, self.list_resources(stack_identifier)) tmpl_update = _change_rsrc_properties( test_template_two_resource, ['test1', 'test2'], {'value': 'test_update_rollback', 'fail': True}) # stack update, also set failure self.update_stack(stack_identifier, tmpl_update, expected_status='ROLLBACK_COMPLETE', disable_rollback=False) # since stack update failed only the original resource is present updated_resources = {'test1': 'OS::Heat::TestResource'} self.assertEqual(updated_resources, self.list_resources(stack_identifier)) def test_stack_update_from_failed(self): # Prove it's possible to update from an UPDATE_FAILED state template = _change_rsrc_properties(test_template_one_resource, ['test1'], {'value': 'test_update_failed'}) stack_identifier = self.stack_create( template=template) initial_resources = {'test1': 'OS::Heat::TestResource'} self.assertEqual(initial_resources, self.list_resources(stack_identifier)) tmpl_update = _change_rsrc_properties( test_template_one_resource, ['test1'], {'fail': True}) # Update with bad template, we should fail self.update_stack(stack_identifier, tmpl_update, expected_status='UPDATE_FAILED') # but then passing a good template should succeed self.update_stack(stack_identifier, test_template_two_resource) updated_resources = {'test1': 'OS::Heat::TestResource', 'test2': 'OS::Heat::TestResource'} self.assertEqual(updated_resources, self.list_resources(stack_identifier)) def test_stack_update_provider(self): template = _change_rsrc_properties( test_template_one_resource, ['test1'], {'value': 'test_provider_template'}) files = {'provider.template': json.dumps(template)} env = {'resource_registry': {'My::TestResource': 'provider.template'}} stack_identifier = self.stack_create( template=self.provider_template, files=files, environment=env ) initial_resources = {'test1': 'My::TestResource'} self.assertEqual(initial_resources, self.list_resources(stack_identifier)) # Prove the resource is backed by a nested stack, save the ID nested_identifier = self.assert_resource_is_a_stack(stack_identifier, 'test1') nested_id = nested_identifier.split('/')[-1] # Then check the expected resources are in the nested stack nested_resources = {'test1': 'OS::Heat::TestResource'} self.assertEqual(nested_resources, self.list_resources(nested_identifier)) tmpl_update = _change_rsrc_properties( test_template_two_resource, ['test1', 'test2'], {'value': 'test_provider_template'}) # Add one resource via a stack update by changing the nested stack files['provider.template'] = json.dumps(tmpl_update) self.update_stack(stack_identifier, self.provider_template, environment=env, files=files) # Parent resources should be unchanged and the nested stack # should have been updated in-place without replacement self.assertEqual(initial_resources, self.list_resources(stack_identifier)) rsrc = self.client.resources.get(stack_identifier, 'test1') self.assertEqual(rsrc.physical_resource_id, nested_id) # Then check the expected resources are in the nested stack nested_resources = {'test1': 'OS::Heat::TestResource', 'test2': 'OS::Heat::TestResource'} self.assertEqual(nested_resources, self.list_resources(nested_identifier)) def test_stack_update_alias_type(self): env = {'resource_registry': {'My::TestResource': 'OS::Heat::RandomString', 'My::TestResource2': 'OS::Heat::RandomString'}} stack_identifier = self.stack_create( template=self.provider_template, environment=env ) p_res = self.client.resources.get(stack_identifier, 'test1') self.assertEqual('My::TestResource', p_res.resource_type) initial_resources = {'test1': 'My::TestResource'} self.assertEqual(initial_resources, self.list_resources(stack_identifier)) res = self.client.resources.get(stack_identifier, 'test1') # Modify the type of the resource alias to My::TestResource2 tmpl_update = copy.deepcopy(self.provider_template) tmpl_update['resources']['test1']['type'] = 'My::TestResource2' self.update_stack(stack_identifier, tmpl_update, environment=env) res_a = self.client.resources.get(stack_identifier, 'test1') self.assertEqual(res.physical_resource_id, res_a.physical_resource_id) self.assertEqual(res.attributes['value'], res_a.attributes['value']) def test_stack_update_alias_changes(self): env = {'resource_registry': {'My::TestResource': 'OS::Heat::RandomString'}} stack_identifier = self.stack_create( template=self.provider_template, environment=env ) p_res = self.client.resources.get(stack_identifier, 'test1') self.assertEqual('My::TestResource', p_res.resource_type) initial_resources = {'test1': 'My::TestResource'} self.assertEqual(initial_resources, self.list_resources(stack_identifier)) res = self.client.resources.get(stack_identifier, 'test1') # Modify the resource alias to point to a different type env = {'resource_registry': {'My::TestResource': 'OS::Heat::TestResource'}} self.update_stack(stack_identifier, template=self.provider_template, environment=env) res_a = self.client.resources.get(stack_identifier, 'test1') self.assertNotEqual(res.physical_resource_id, res_a.physical_resource_id) def test_stack_update_provider_type(self): template = _change_rsrc_properties( test_template_one_resource, ['test1'], {'value': 'test_provider_template'}) files = {'provider.template': json.dumps(template)} env = {'resource_registry': {'My::TestResource': 'provider.template', 'My::TestResource2': 'provider.template'}} stack_identifier = self.stack_create( template=self.provider_template, files=files, environment=env ) p_res = self.client.resources.get(stack_identifier, 'test1') self.assertEqual('My::TestResource', p_res.resource_type) initial_resources = {'test1': 'My::TestResource'} self.assertEqual(initial_resources, self.list_resources(stack_identifier)) # Prove the resource is backed by a nested stack, save the ID nested_identifier = self.assert_resource_is_a_stack(stack_identifier, 'test1') nested_id = nested_identifier.split('/')[-1] # Then check the expected resources are in the nested stack nested_resources = {'test1': 'OS::Heat::TestResource'} self.assertEqual(nested_resources, self.list_resources(nested_identifier)) n_res = self.client.resources.get(nested_identifier, 'test1') # Modify the type of the provider resource to My::TestResource2 tmpl_update = copy.deepcopy(self.provider_template) tmpl_update['resources']['test1']['type'] = 'My::TestResource2' self.update_stack(stack_identifier, tmpl_update, environment=env, files=files) p_res = self.client.resources.get(stack_identifier, 'test1') self.assertEqual('My::TestResource2', p_res.resource_type) # Parent resources should be unchanged and the nested stack # should have been updated in-place without replacement self.assertEqual({u'test1': u'My::TestResource2'}, self.list_resources(stack_identifier)) rsrc = self.client.resources.get(stack_identifier, 'test1') self.assertEqual(rsrc.physical_resource_id, nested_id) # Then check the expected resources are in the nested stack self.assertEqual(nested_resources, self.list_resources(nested_identifier)) n_res2 = self.client.resources.get(nested_identifier, 'test1') self.assertEqual(n_res.physical_resource_id, n_res2.physical_resource_id) def test_stack_update_provider_group(self): """Test two-level nested update.""" # Create a ResourceGroup (which creates a nested stack), # containing provider resources (which create a nested # stack), thus exercising an update which traverses # two levels of nesting. template = _change_rsrc_properties( test_template_one_resource, ['test1'], {'value': 'test_provider_group_template'}) files = {'provider.template': json.dumps(template)} env = {'resource_registry': {'My::TestResource': 'provider.template'}} stack_identifier = self.stack_create( template=self.provider_group_template, files=files, environment=env ) initial_resources = {'test_group': 'OS::Heat::ResourceGroup'} self.assertEqual(initial_resources, self.list_resources(stack_identifier)) # Prove the resource is backed by a nested stack, save the ID nested_identifier = self.assert_resource_is_a_stack(stack_identifier, 'test_group') # Then check the expected resources are in the nested stack nested_resources = {'0': 'My::TestResource', '1': 'My::TestResource'} self.assertEqual(nested_resources, self.list_resources(nested_identifier)) for n_rsrc in nested_resources: rsrc = self.client.resources.get(nested_identifier, n_rsrc) provider_stack = self.client.stacks.get(rsrc.physical_resource_id) provider_identifier = '%s/%s' % (provider_stack.stack_name, provider_stack.id) provider_resources = {u'test1': u'OS::Heat::TestResource'} self.assertEqual(provider_resources, self.list_resources(provider_identifier)) tmpl_update = _change_rsrc_properties( test_template_two_resource, ['test1', 'test2'], {'value': 'test_provider_group_template'}) # Add one resource via a stack update by changing the nested stack files['provider.template'] = json.dumps(tmpl_update) self.update_stack(stack_identifier, self.provider_group_template, environment=env, files=files) # Parent resources should be unchanged and the nested stack # should have been updated in-place without replacement self.assertEqual(initial_resources, self.list_resources(stack_identifier)) # Resource group stack should also be unchanged (but updated) nested_stack = self.client.stacks.get(nested_identifier) self.assertEqual('UPDATE_COMPLETE', nested_stack.stack_status) self.assertEqual(nested_resources, self.list_resources(nested_identifier)) for n_rsrc in nested_resources: rsrc = self.client.resources.get(nested_identifier, n_rsrc) provider_stack = self.client.stacks.get(rsrc.physical_resource_id) provider_identifier = '%s/%s' % (provider_stack.stack_name, provider_stack.id) provider_resources = {'test1': 'OS::Heat::TestResource', 'test2': 'OS::Heat::TestResource'} self.assertEqual(provider_resources, self.list_resources(provider_identifier)) def test_stack_update_with_replacing_userdata(self): """Test case for updating userdata of instance. Confirm that we can update userdata of instance during updating stack by the user of member role. Make sure that a resource that inherits from StackUser can be deleted during updating stack. """ if not self.conf.minimal_image_ref: raise self.skipException("No minimal image configured to test") if not self.conf.minimal_instance_type: raise self.skipException("No flavor configured to test") parms = {'flavor': self.conf.minimal_instance_type, 'image': self.conf.minimal_image_ref, 'network': self.conf.fixed_network_name, 'user_data': ''} stack_identifier = self.stack_create( template=self.update_userdata_template, parameters=parms ) parms_updated = parms parms_updated['user_data'] = 'two' self.update_stack( stack_identifier, template=self.update_userdata_template, parameters=parms_updated) def test_stack_update_provider_group_patch(self): '''Test two-level nested update with PATCH''' template = _change_rsrc_properties( test_template_one_resource, ['test1'], {'value': 'test_provider_group_template'}) files = {'provider.template': json.dumps(template)} env = {'resource_registry': {'My::TestResource': 'provider.template'}} stack_identifier = self.stack_create( template=self.provider_group_template, files=files, environment=env ) initial_resources = {'test_group': 'OS::Heat::ResourceGroup'} self.assertEqual(initial_resources, self.list_resources(stack_identifier)) # Prove the resource is backed by a nested stack, save the ID nested_identifier = self.assert_resource_is_a_stack(stack_identifier, 'test_group') # Then check the expected resources are in the nested stack nested_resources = {'0': 'My::TestResource', '1': 'My::TestResource'} self.assertEqual(nested_resources, self.list_resources(nested_identifier)) # increase the count, pass only the paramter, no env or template params = {'count': 3} self.update_stack(stack_identifier, parameters=params, existing=True) # Parent resources should be unchanged and the nested stack # should have been updated in-place without replacement self.assertEqual(initial_resources, self.list_resources(stack_identifier)) # Resource group stack should also be unchanged (but updated) nested_stack = self.client.stacks.get(nested_identifier) self.assertEqual('UPDATE_COMPLETE', nested_stack.stack_status) # Add a resource, as we should have added one nested_resources['2'] = 'My::TestResource' self.assertEqual(nested_resources, self.list_resources(nested_identifier)) def test_stack_update_from_failed_patch(self): '''Test PATCH update from a failed state.''' # Start with empty template stack_identifier = self.stack_create( template='heat_template_version: 2014-10-16') # Update with a good template, but bad parameter self.update_stack(stack_identifier, template=self.fail_param_template, parameters={'do_fail': True}, expected_status='UPDATE_FAILED') # PATCH update, only providing the parameter self.update_stack(stack_identifier, parameters={'do_fail': False}, existing=True) self.assertEqual({u'aresource': u'OS::Heat::TestResource'}, self.list_resources(stack_identifier)) def test_stack_update_with_new_env(self): """Update handles new resource types in the environment. If a resource type appears during an update and the update fails, retrying the update is able to find the type properly in the environment. """ stack_identifier = self.stack_create( template=test_template_one_resource) # Update with a new resource and make the update fails template = _change_rsrc_properties(test_template_one_resource, ['test1'], {'fail': True}) template['resources']['test2'] = {'type': 'My::TestResource'} template['resources']['test1']['depends_on'] = 'test2' env = {'resource_registry': {'My::TestResource': 'OS::Heat::TestResource'}} self.update_stack(stack_identifier, template=template, environment=env, expected_status='UPDATE_FAILED') # Fixing the template should fix the stack template = _change_rsrc_properties(template, ['test1'], {'fail': False}) self.update_stack(stack_identifier, template=template, environment=env) self.assertEqual({'test1': 'OS::Heat::TestResource', 'test2': 'My::TestResource'}, self.list_resources(stack_identifier)) heat-6.0.0/heat_integrationtests/functional/test_update_restricted.py0000664000567000056710000001473612701407050027502 0ustar jenkinsjenkins00000000000000# 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 heat_integrationtests.functional import functional_base test_template = { 'heat_template_version': '2013-05-23', 'description': 'Test template to create one instance.', 'resources': { 'bar': { 'type': 'OS::Heat::TestResource', 'properties': { 'value': '1234', 'update_replace': False, } } } } env_both_restrict = {u'resource_registry': { u'resources': { 'bar': {'restricted_actions': ['update', 'replace']} } } } env_replace_restrict = {u'resource_registry': { u'resources': { '*ar': {'restricted_actions': 'replace'} } } } reason_update_restrict = 'update is restricted for resource.' reason_replace_restrict = 'replace is restricted for resource.' class UpdateRestrictedStackTest(functional_base.FunctionalTestsBase): def _check_for_restriction_reason(self, events, reason, num_expected=1): matched = [e for e in events if e.resource_status_reason == reason] return len(matched) == num_expected def test_update(self): stack_identifier = self.stack_create(template=test_template) update_template = test_template.copy() props = update_template['resources']['bar']['properties'] props['value'] = '4567' # check update fails - with 'both' restricted self.update_stack(stack_identifier, update_template, env_both_restrict, expected_status='UPDATE_FAILED') self.assertTrue(self.verify_resource_status(stack_identifier, 'bar', 'CREATE_COMPLETE')) resource_events = self.client.events.list(stack_identifier, 'bar') self.assertTrue( self._check_for_restriction_reason(resource_events, reason_update_restrict)) # check update succeeds - with only 'replace' restricted self.update_stack(stack_identifier, update_template, env_replace_restrict, expected_status='UPDATE_COMPLETE') self.assertTrue(self.verify_resource_status(stack_identifier, 'bar', 'UPDATE_COMPLETE')) resource_events = self.client.events.list(stack_identifier, 'bar') self.assertFalse( self._check_for_restriction_reason(resource_events, reason_update_restrict, 2)) self.assertTrue( self._check_for_restriction_reason(resource_events, reason_replace_restrict, 0)) def test_replace(self): stack_identifier = self.stack_create(template=test_template) update_template = test_template.copy() props = update_template['resources']['bar']['properties'] props['update_replace'] = True # check replace fails - with 'both' restricted self.update_stack(stack_identifier, update_template, env_replace_restrict, expected_status='UPDATE_FAILED') self.assertTrue(self.verify_resource_status(stack_identifier, 'bar', 'CREATE_COMPLETE')) resource_events = self.client.events.list(stack_identifier, 'bar') self.assertTrue( self._check_for_restriction_reason(resource_events, reason_replace_restrict)) # check replace fails - with only 'replace' restricted self.update_stack(stack_identifier, update_template, env_replace_restrict, expected_status='UPDATE_FAILED') self.assertTrue(self.verify_resource_status(stack_identifier, 'bar', 'CREATE_COMPLETE')) resource_events = self.client.events.list(stack_identifier, 'bar') self.assertTrue( self._check_for_restriction_reason(resource_events, reason_replace_restrict, 2)) self.assertTrue( self._check_for_restriction_reason(resource_events, reason_update_restrict, 0)) def test_update_type_changed(self): stack_identifier = self.stack_create(template=test_template) update_template = test_template.copy() rsrc = update_template['resources']['bar'] rsrc['type'] = 'OS::Heat::None' # check replace fails - with 'both' restricted self.update_stack(stack_identifier, update_template, env_both_restrict, expected_status='UPDATE_FAILED') self.assertTrue(self.verify_resource_status(stack_identifier, 'bar', 'CREATE_COMPLETE')) resource_events = self.client.events.list(stack_identifier, 'bar') self.assertTrue( self._check_for_restriction_reason(resource_events, reason_replace_restrict)) # check replace fails - with only 'replace' restricted self.update_stack(stack_identifier, update_template, env_replace_restrict, expected_status='UPDATE_FAILED') self.assertTrue(self.verify_resource_status(stack_identifier, 'bar', 'CREATE_COMPLETE')) resource_events = self.client.events.list(stack_identifier, 'bar') self.assertTrue( self._check_for_restriction_reason(resource_events, reason_replace_restrict, 2)) self.assertTrue( self._check_for_restriction_reason(resource_events, reason_update_restrict, 0)) heat-6.0.0/heat_integrationtests/functional/test_default_parameters.py0000664000567000056710000000574512701407050027637 0ustar jenkinsjenkins00000000000000# 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 heat_integrationtests.functional import functional_base class DefaultParametersTest(functional_base.FunctionalTestsBase): template = ''' heat_template_version: 2013-05-23 parameters: length: type: string default: 40 resources: random1: type: nested_random.yaml random2: type: OS::Heat::RandomString properties: length: {get_param: length} outputs: random1: value: {get_attr: [random1, random1_value]} random2: value: {get_resource: random2} ''' nested_template = ''' heat_template_version: 2013-05-23 parameters: length: type: string default: 50 resources: random1: type: OS::Heat::RandomString properties: length: {get_param: length} outputs: random1_value: value: {get_resource: random1} ''' scenarios = [ ('none', dict(param=None, default=None, temp_def=True, expect1=50, expect2=40)), ('default', dict(param=None, default=12, temp_def=True, expect1=12, expect2=12)), ('both', dict(param=15, default=12, temp_def=True, expect1=12, expect2=15)), ('no_temp_default', dict(param=None, default=12, temp_def=False, expect1=12, expect2=12)), ] def setUp(self): super(DefaultParametersTest, self).setUp() def test_defaults(self): env = {'parameters': {}, 'parameter_defaults': {}} if self.param: env['parameters'] = {'length': self.param} if self.default: env['parameter_defaults'] = {'length': self.default} if not self.temp_def: # remove the default from the parameter in the nested template. ntempl = yaml.safe_load(self.nested_template) del ntempl['parameters']['length']['default'] nested_template = yaml.safe_dump(ntempl) else: nested_template = self.nested_template stack_identifier = self.stack_create( template=self.template, files={'nested_random.yaml': nested_template}, environment=env ) stack = self.client.stacks.get(stack_identifier) for out in stack.outputs: if out['output_key'] == 'random1': self.assertEqual(self.expect1, len(out['output_value'])) if out['output_key'] == 'random2': self.assertEqual(self.expect2, len(out['output_value'])) heat-6.0.0/heat_integrationtests/functional/test_resource_group.py0000664000567000056710000006467512701407050027042 0ustar jenkinsjenkins00000000000000# 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 copy import json from heatclient import exc import six import yaml from heat_integrationtests.functional import functional_base class ResourceGroupTest(functional_base.FunctionalTestsBase): template = ''' heat_template_version: 2013-05-23 resources: random_group: type: OS::Heat::ResourceGroup properties: count: 0 resource_def: type: My::RandomString properties: length: 30 salt: initial outputs: random1: value: {get_attr: [random_group, resource.0.value]} random2: value: {get_attr: [random_group, resource.1.value]} all_values: value: {get_attr: [random_group, value]} ''' def setUp(self): super(ResourceGroupTest, self).setUp() def test_resource_group_zero_novalidate(self): # Nested resources should be validated only when size > 0 # This allows features to be disabled via size=0 without # triggering validation of nested resource custom contraints # e.g images etc in the nested schema. nested_template_fail = ''' heat_template_version: 2013-05-23 parameters: length: type: string default: 50 salt: type: string default: initial resources: random: type: OS::Heat::RandomString properties: length: BAD ''' files = {'provider.yaml': nested_template_fail} env = {'resource_registry': {'My::RandomString': 'provider.yaml'}} stack_identifier = self.stack_create( template=self.template, files=files, environment=env ) self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'}, self.list_resources(stack_identifier)) # Check we created an empty nested stack nested_identifier = self.group_nested_identifier(stack_identifier, 'random_group') self.assertEqual({}, self.list_resources(nested_identifier)) # Prove validation works for non-zero create/update template_two_nested = self.template.replace("count: 0", "count: 2") expected_err = "Value 'BAD' is not an integer" ex = self.assertRaises(exc.HTTPBadRequest, self.update_stack, stack_identifier, template_two_nested, environment=env, files=files) self.assertIn(expected_err, six.text_type(ex)) ex = self.assertRaises(exc.HTTPBadRequest, self.stack_create, template=template_two_nested, environment=env, files=files) self.assertIn(expected_err, six.text_type(ex)) def _validate_resources(self, stack_identifier, expected_count): resources = self.list_group_resources(stack_identifier, 'random_group') self.assertEqual(expected_count, len(resources)) expected_resources = dict( (str(idx), 'My::RandomString') for idx in range(expected_count)) self.assertEqual(expected_resources, resources) def test_create(self): def validate_output(stack, output_key, length): output_value = self._stack_output(stack, output_key) self.assertEqual(length, len(output_value)) return output_value # verify that the resources in resource group are identically # configured, resource names and outputs are appropriate. env = {'resource_registry': {'My::RandomString': 'OS::Heat::RandomString'}} create_template = self.template.replace("count: 0", "count: 2") stack_identifier = self.stack_create(template=create_template, environment=env) self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'}, self.list_resources(stack_identifier)) # validate count, type and name of resources in a resource group. self._validate_resources(stack_identifier, 2) # validate outputs stack = self.client.stacks.get(stack_identifier) outputs = [] outputs.append(validate_output(stack, 'random1', 30)) outputs.append(validate_output(stack, 'random2', 30)) self.assertEqual(outputs, self._stack_output(stack, 'all_values')) def test_update_increase_decrease_count(self): # create stack with resource group count 2 env = {'resource_registry': {'My::RandomString': 'OS::Heat::RandomString'}} create_template = self.template.replace("count: 0", "count: 2") stack_identifier = self.stack_create(template=create_template, environment=env) self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'}, self.list_resources(stack_identifier)) # verify that the resource group has 2 resources self._validate_resources(stack_identifier, 2) # increase the resource group count to 5 update_template = self.template.replace("count: 0", "count: 5") self.update_stack(stack_identifier, update_template, environment=env) # verify that the resource group has 5 resources self._validate_resources(stack_identifier, 5) # decrease the resource group count to 3 update_template = self.template.replace("count: 0", "count: 3") self.update_stack(stack_identifier, update_template, environment=env) # verify that the resource group has 3 resources self._validate_resources(stack_identifier, 3) def test_update_removal_policies(self): rp_template = ''' heat_template_version: 2014-10-16 resources: random_group: type: OS::Heat::ResourceGroup properties: count: 5 removal_policies: [] resource_def: type: OS::Heat::RandomString ''' # create stack with resource group, initial count 5 stack_identifier = self.stack_create(template=rp_template) self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'}, self.list_resources(stack_identifier)) group_resources = self.list_group_resources(stack_identifier, 'random_group') expected_resources = {u'0': u'OS::Heat::RandomString', u'1': u'OS::Heat::RandomString', u'2': u'OS::Heat::RandomString', u'3': u'OS::Heat::RandomString', u'4': u'OS::Heat::RandomString'} self.assertEqual(expected_resources, group_resources) # Remove three, specifying the middle resources to be removed update_template = rp_template.replace( 'removal_policies: []', 'removal_policies: [{resource_list: [\'1\', \'2\', \'3\']}]') self.update_stack(stack_identifier, update_template) group_resources = self.list_group_resources(stack_identifier, 'random_group') expected_resources = {u'0': u'OS::Heat::RandomString', u'4': u'OS::Heat::RandomString', u'5': u'OS::Heat::RandomString', u'6': u'OS::Heat::RandomString', u'7': u'OS::Heat::RandomString'} self.assertEqual(expected_resources, group_resources) def test_props_update(self): """Test update of resource_def properties behaves as expected.""" env = {'resource_registry': {'My::RandomString': 'OS::Heat::RandomString'}} template_one = self.template.replace("count: 0", "count: 1") stack_identifier = self.stack_create(template=template_one, environment=env) self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'}, self.list_resources(stack_identifier)) initial_nested_ident = self.group_nested_identifier(stack_identifier, 'random_group') self.assertEqual({'0': 'My::RandomString'}, self.list_resources(initial_nested_ident)) # get the resource id res = self.client.resources.get(initial_nested_ident, '0') initial_res_id = res.physical_resource_id # change the salt (this should replace the RandomString but # not the nested stack or resource group. template_salt = template_one.replace("salt: initial", "salt: more") self.update_stack(stack_identifier, template_salt, environment=env) updated_nested_ident = self.group_nested_identifier(stack_identifier, 'random_group') self.assertEqual(initial_nested_ident, updated_nested_ident) # compare the resource id, we expect a change. res = self.client.resources.get(updated_nested_ident, '0') updated_res_id = res.physical_resource_id self.assertNotEqual(initial_res_id, updated_res_id) def test_update_nochange(self): """Test update with no properties change.""" env = {'resource_registry': {'My::RandomString': 'OS::Heat::RandomString'}} template_one = self.template.replace("count: 0", "count: 2") stack_identifier = self.stack_create(template=template_one, environment=env) self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'}, self.list_resources(stack_identifier)) initial_nested_ident = self.group_nested_identifier(stack_identifier, 'random_group') self.assertEqual({'0': 'My::RandomString', '1': 'My::RandomString'}, self.list_resources(initial_nested_ident)) # get the output stack0 = self.client.stacks.get(stack_identifier) initial_rand = self._stack_output(stack0, 'random1') template_copy = copy.deepcopy(template_one) self.update_stack(stack_identifier, template_copy, environment=env) updated_nested_ident = self.group_nested_identifier(stack_identifier, 'random_group') self.assertEqual(initial_nested_ident, updated_nested_ident) # compare the random number, we expect no change. stack1 = self.client.stacks.get(stack_identifier) updated_rand = self._stack_output(stack1, 'random1') self.assertEqual(initial_rand, updated_rand) def test_update_nochange_resource_needs_update(self): """Test update when the resource definition has changed. Test the scenario when the ResourceGroup update happens without any changed properties, this can happen if the definition of a contained provider resource changes (files map changes), then the group and underlying nested stack should end up updated. """ random_templ1 = ''' heat_template_version: 2013-05-23 parameters: length: type: string default: not-used salt: type: string default: not-used resources: random1: type: OS::Heat::RandomString properties: salt: initial outputs: value: value: {get_attr: [random1, value]} ''' files1 = {'my_random.yaml': random_templ1} random_templ2 = random_templ1.replace('salt: initial', 'salt: more') files2 = {'my_random.yaml': random_templ2} env = {'resource_registry': {'My::RandomString': 'my_random.yaml'}} template_one = self.template.replace("count: 0", "count: 2") stack_identifier = self.stack_create(template=template_one, environment=env, files=files1) self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'}, self.list_resources(stack_identifier)) initial_nested_ident = self.group_nested_identifier(stack_identifier, 'random_group') self.assertEqual({'0': 'My::RandomString', '1': 'My::RandomString'}, self.list_resources(initial_nested_ident)) # get the output stack0 = self.client.stacks.get(stack_identifier) initial_rand = self._stack_output(stack0, 'random1') # change the environment so we use a different TemplateResource. # note "files2". self.update_stack(stack_identifier, template_one, environment=env, files=files2) updated_nested_ident = self.group_nested_identifier(stack_identifier, 'random_group') self.assertEqual(initial_nested_ident, updated_nested_ident) # compare the output, we expect a change. stack1 = self.client.stacks.get(stack_identifier) updated_rand = self._stack_output(stack1, 'random1') self.assertNotEqual(initial_rand, updated_rand) class ResourceGroupTestNullParams(functional_base.FunctionalTestsBase): template = ''' heat_template_version: 2013-05-23 parameters: param: type: empty resources: random_group: type: OS::Heat::ResourceGroup properties: count: 1 resource_def: type: My::RandomString properties: param: {get_param: param} outputs: val: value: {get_attr: [random_group, val]} ''' nested_template_file = ''' heat_template_version: 2013-05-23 parameters: param: type: empty outputs: val: value: {get_param: param} ''' scenarios = [ ('string_empty', dict( param='', p_type='string', )), ('boolean_false', dict( param=False, p_type='boolean', )), ('number_zero', dict( param=0, p_type='number', )), ('comma_delimited_list', dict( param=[], p_type='comma_delimited_list', )), ('json_empty', dict( param={}, p_type='json', )), ] def setUp(self): super(ResourceGroupTestNullParams, self).setUp() def test_create_pass_zero_parameter(self): templ = self.template.replace('type: empty', 'type: %s' % self.p_type) n_t_f = self.nested_template_file.replace('type: empty', 'type: %s' % self.p_type) files = {'provider.yaml': n_t_f} env = {'resource_registry': {'My::RandomString': 'provider.yaml'}} stack_identifier = self.stack_create( template=templ, files=files, environment=env, parameters={'param': self.param} ) stack = self.client.stacks.get(stack_identifier) self.assertEqual(self.param, self._stack_output(stack, 'val')[0]) class ResourceGroupAdoptTest(functional_base.FunctionalTestsBase): """Prove that we can do resource group adopt.""" main_template = ''' heat_template_version: "2013-05-23" resources: group1: type: OS::Heat::ResourceGroup properties: count: 2 resource_def: type: OS::Heat::RandomString outputs: test0: value: {get_attr: [group1, resource.0.value]} test1: value: {get_attr: [group1, resource.1.value]} ''' def setUp(self): super(ResourceGroupAdoptTest, self).setUp() def _yaml_to_json(self, yaml_templ): return yaml.safe_load(yaml_templ) def test_adopt(self): data = { "resources": { "group1": { "status": "COMPLETE", "name": "group1", "resource_data": {}, "metadata": {}, "resource_id": "test-group1-id", "action": "CREATE", "type": "OS::Heat::ResourceGroup", "resources": { "0": { "status": "COMPLETE", "name": "0", "resource_data": {"value": "goopie"}, "resource_id": "ID-0", "action": "CREATE", "type": "OS::Heat::RandomString", "metadata": {} }, "1": { "status": "COMPLETE", "name": "1", "resource_data": {"value": "different"}, "resource_id": "ID-1", "action": "CREATE", "type": "OS::Heat::RandomString", "metadata": {} } } } }, "environment": {"parameters": {}}, "template": yaml.safe_load(self.main_template) } stack_identifier = self.stack_adopt( adopt_data=json.dumps(data)) self.assert_resource_is_a_stack(stack_identifier, 'group1') stack = self.client.stacks.get(stack_identifier) self.assertEqual('goopie', self._stack_output(stack, 'test0')) self.assertEqual('different', self._stack_output(stack, 'test1')) class ResourceGroupErrorResourceTest(functional_base.FunctionalTestsBase): template = ''' heat_template_version: "2013-05-23" resources: group1: type: OS::Heat::ResourceGroup properties: count: 2 resource_def: type: fail.yaml ''' nested_templ = ''' heat_template_version: "2013-05-23" resources: oops: type: OS::Heat::TestResource properties: fail: true wait_secs: 2 ''' def setUp(self): super(ResourceGroupErrorResourceTest, self).setUp() def test_fail(self): stack_identifier = self.stack_create( template=self.template, files={'fail.yaml': self.nested_templ}, expected_status='CREATE_FAILED', enable_cleanup=False) stack = self.client.stacks.get(stack_identifier) self.assertEqual('CREATE_FAILED', stack.stack_status) self.client.stacks.delete(stack_identifier) self._wait_for_stack_status( stack_identifier, 'DELETE_COMPLETE', success_on_not_found=True) class ResourceGroupUpdatePolicyTest(functional_base.FunctionalTestsBase): template = ''' heat_template_version: '2015-04-30' resources: random_group: type: OS::Heat::ResourceGroup update_policy: rolling_update: min_in_service: 1 max_batch_size: 2 pause_time: 1 properties: count: 10 resource_def: type: OS::Heat::TestResource properties: value: initial update_replace: False ''' def update_resource_group(self, update_template, updated, created, deleted): stack_identifier = self.stack_create(template=self.template) group_resources = self.list_group_resources(stack_identifier, 'random_group', minimal=False) init_names = [res.physical_resource_id for res in group_resources] self.update_stack(stack_identifier, update_template) group_resources = self.list_group_resources(stack_identifier, 'random_group', minimal=False) updt_names = [res.physical_resource_id for res in group_resources] matched_names = set(updt_names) & set(init_names) self.assertEqual(updated, len(matched_names)) self.assertEqual(created, len(set(updt_names) - set(init_names))) self.assertEqual(deleted, len(set(init_names) - set(updt_names))) def test_resource_group_update(self): """Test rolling update with no conflict. Simple rolling update with no conflict in batch size and minimum instances in service. """ updt_template = yaml.safe_load(copy.deepcopy(self.template)) grp = updt_template['resources']['random_group'] policy = grp['update_policy']['rolling_update'] policy['min_in_service'] = '1' policy['max_batch_size'] = '3' res_def = grp['properties']['resource_def'] res_def['properties']['value'] = 'updated' self.update_resource_group(updt_template, updated=10, created=0, deleted=0) def test_resource_group_update_replace(self): """Test rolling update(replace)with no conflict. Simple rolling update replace with no conflict in batch size and minimum instances in service. """ updt_template = yaml.safe_load(copy.deepcopy(self.template)) grp = updt_template['resources']['random_group'] policy = grp['update_policy']['rolling_update'] policy['min_in_service'] = '1' policy['max_batch_size'] = '3' res_def = grp['properties']['resource_def'] res_def['properties']['value'] = 'updated' res_def['properties']['update_replace'] = True self.update_resource_group(updt_template, updated=0, created=10, deleted=10) def test_resource_group_update_scaledown(self): """Test rolling update with scaledown. Simple rolling update with reduced size. """ updt_template = yaml.safe_load(copy.deepcopy(self.template)) grp = updt_template['resources']['random_group'] policy = grp['update_policy']['rolling_update'] policy['min_in_service'] = '1' policy['max_batch_size'] = '3' grp['properties']['count'] = 6 res_def = grp['properties']['resource_def'] res_def['properties']['value'] = 'updated' self.update_resource_group(updt_template, updated=6, created=0, deleted=4) def test_resource_group_update_scaleup(self): """Test rolling update with scaleup. Simple rolling update with increased size. """ updt_template = yaml.safe_load(copy.deepcopy(self.template)) grp = updt_template['resources']['random_group'] policy = grp['update_policy']['rolling_update'] policy['min_in_service'] = '1' policy['max_batch_size'] = '3' grp['properties']['count'] = 12 res_def = grp['properties']['resource_def'] res_def['properties']['value'] = 'updated' self.update_resource_group(updt_template, updated=10, created=2, deleted=0) def test_resource_group_update_adjusted(self): """Test rolling update with enough available resources Update with capacity adjustment with enough resources. """ updt_template = yaml.safe_load(copy.deepcopy(self.template)) grp = updt_template['resources']['random_group'] policy = grp['update_policy']['rolling_update'] policy['min_in_service'] = '8' policy['max_batch_size'] = '4' grp['properties']['count'] = 6 res_def = grp['properties']['resource_def'] res_def['properties']['value'] = 'updated' self.update_resource_group(updt_template, updated=6, created=0, deleted=4) def test_resource_group_update_with_adjusted_capacity(self): """Test rolling update with capacity adjustment. Rolling update with capacity adjustment due to conflict in batch size and minimum instances in service. """ updt_template = yaml.safe_load(copy.deepcopy(self.template)) grp = updt_template['resources']['random_group'] policy = grp['update_policy']['rolling_update'] policy['min_in_service'] = '8' policy['max_batch_size'] = '4' res_def = grp['properties']['resource_def'] res_def['properties']['value'] = 'updated' self.update_resource_group(updt_template, updated=10, created=0, deleted=0) def test_resource_group_update_huge_batch_size(self): """Test rolling update with huge batch size. Rolling Update with a huge batch size(more than current size). """ updt_template = yaml.safe_load(copy.deepcopy(self.template)) grp = updt_template['resources']['random_group'] policy = grp['update_policy']['rolling_update'] policy['min_in_service'] = '0' policy['max_batch_size'] = '20' res_def = grp['properties']['resource_def'] res_def['properties']['value'] = 'updated' self.update_resource_group(updt_template, updated=10, created=0, deleted=0) def test_resource_group_update_huge_min_in_service(self): """Test rolling update with huge minimum capacity. Rolling Update with a huge number of minimum instances in service. """ updt_template = yaml.safe_load(copy.deepcopy(self.template)) grp = updt_template['resources']['random_group'] policy = grp['update_policy']['rolling_update'] policy['min_in_service'] = '20' policy['max_batch_size'] = '1' res_def = grp['properties']['resource_def'] res_def['properties']['value'] = 'updated' self.update_resource_group(updt_template, updated=10, created=0, deleted=0) heat-6.0.0/heat_integrationtests/functional/test_template_resource.py0000664000567000056710000006441612701407050027512 0ustar jenkinsjenkins00000000000000# 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 json from heatclient import exc as heat_exceptions import six import yaml from heat_integrationtests.common import test from heat_integrationtests.functional import functional_base class TemplateResourceTest(functional_base.FunctionalTestsBase): """Prove that we can use the registry in a nested provider.""" template = ''' heat_template_version: 2013-05-23 resources: secret1: type: OS::Heat::RandomString outputs: secret-out: value: { get_attr: [secret1, value] } ''' nested_templ = ''' heat_template_version: 2013-05-23 resources: secret2: type: OS::Heat::RandomString outputs: value: value: { get_attr: [secret2, value] } ''' env_templ = ''' resource_registry: "OS::Heat::RandomString": nested.yaml ''' def setUp(self): super(TemplateResourceTest, self).setUp() def test_nested_env(self): main_templ = ''' heat_template_version: 2013-05-23 resources: secret1: type: My::NestedSecret outputs: secret-out: value: { get_attr: [secret1, value] } ''' nested_templ = ''' heat_template_version: 2013-05-23 resources: secret2: type: My::Secret outputs: value: value: { get_attr: [secret2, value] } ''' env_templ = ''' resource_registry: "My::Secret": "OS::Heat::RandomString" "My::NestedSecret": nested.yaml ''' stack_identifier = self.stack_create( template=main_templ, files={'nested.yaml': nested_templ}, environment=env_templ) nested_ident = self.assert_resource_is_a_stack(stack_identifier, 'secret1') # prove that resource.parent_resource is populated. sec2 = self.client.resources.get(nested_ident, 'secret2') self.assertEqual('secret1', sec2.parent_resource) def test_no_infinite_recursion(self): """Prove that we can override a python resource. And use that resource within the template resource. """ stack_identifier = self.stack_create( template=self.template, files={'nested.yaml': self.nested_templ}, environment=self.env_templ) self.assert_resource_is_a_stack(stack_identifier, 'secret1') def test_nested_stack_delete_then_delete_parent_stack(self): """Check the robustness of stack deletion. This tests that if you manually delete a nested stack, the parent stack is still deletable. """ # disable cleanup so we can call _stack_delete() directly. stack_identifier = self.stack_create( template=self.template, files={'nested.yaml': self.nested_templ}, environment=self.env_templ, enable_cleanup=False) nested_ident = self.assert_resource_is_a_stack(stack_identifier, 'secret1') self._stack_delete(nested_ident) self._stack_delete(stack_identifier) def test_change_in_file_path(self): stack_identifier = self.stack_create( template=self.template, files={'nested.yaml': self.nested_templ}, environment=self.env_templ) stack = self.client.stacks.get(stack_identifier) secret_out1 = self._stack_output(stack, 'secret-out') nested_templ_2 = ''' heat_template_version: 2013-05-23 resources: secret2: type: OS::Heat::RandomString outputs: value: value: freddy ''' env_templ_2 = ''' resource_registry: "OS::Heat::RandomString": new/nested.yaml ''' self.update_stack(stack_identifier, template=self.template, files={'new/nested.yaml': nested_templ_2}, environment=env_templ_2) stack = self.client.stacks.get(stack_identifier) secret_out2 = self._stack_output(stack, 'secret-out') self.assertNotEqual(secret_out1, secret_out2) self.assertEqual('freddy', secret_out2) class NestedAttributesTest(functional_base.FunctionalTestsBase): """Prove that we can use the template resource references.""" main_templ = ''' heat_template_version: 2014-10-16 resources: secret2: type: My::NestedSecret outputs: old_way: value: { get_attr: [secret2, nested_str]} test_attr1: value: { get_attr: [secret2, resource.secret1, value]} test_attr2: value: { get_attr: [secret2, resource.secret1.value]} test_ref: value: { get_resource: secret2 } ''' env_templ = ''' resource_registry: "My::NestedSecret": nested.yaml ''' def setUp(self): super(NestedAttributesTest, self).setUp() def test_stack_ref(self): nested_templ = ''' heat_template_version: 2014-10-16 resources: secret1: type: OS::Heat::RandomString outputs: nested_str: value: {get_attr: [secret1, value]} ''' stack_identifier = self.stack_create( template=self.main_templ, files={'nested.yaml': nested_templ}, environment=self.env_templ) self.assert_resource_is_a_stack(stack_identifier, 'secret2') stack = self.client.stacks.get(stack_identifier) test_ref = self._stack_output(stack, 'test_ref') self.assertIn('arn:openstack:heat:', test_ref) def test_transparent_ref(self): """Test using nested resource more transparently. With the addition of OS::stack_id we can now use the nested resource more transparently. """ nested_templ = ''' heat_template_version: 2014-10-16 resources: secret1: type: OS::Heat::RandomString outputs: OS::stack_id: value: {get_resource: secret1} nested_str: value: {get_attr: [secret1, value]} ''' stack_identifier = self.stack_create( template=self.main_templ, files={'nested.yaml': nested_templ}, environment=self.env_templ) self.assert_resource_is_a_stack(stack_identifier, 'secret2') stack = self.client.stacks.get(stack_identifier) test_ref = self._stack_output(stack, 'test_ref') test_attr = self._stack_output(stack, 'old_way') self.assertNotIn('arn:openstack:heat', test_ref) self.assertEqual(test_attr, test_ref) def test_nested_attributes(self): nested_templ = ''' heat_template_version: 2014-10-16 resources: secret1: type: OS::Heat::RandomString outputs: nested_str: value: {get_attr: [secret1, value]} ''' stack_identifier = self.stack_create( template=self.main_templ, files={'nested.yaml': nested_templ}, environment=self.env_templ) self.assert_resource_is_a_stack(stack_identifier, 'secret2') stack = self.client.stacks.get(stack_identifier) old_way = self._stack_output(stack, 'old_way') test_attr1 = self._stack_output(stack, 'test_attr1') test_attr2 = self._stack_output(stack, 'test_attr2') self.assertEqual(old_way, test_attr1) self.assertEqual(old_way, test_attr2) class TemplateResourceFacadeTest(functional_base.FunctionalTestsBase): """Prove that we can use ResourceFacade in a HOT template.""" main_template = ''' heat_template_version: 2013-05-23 resources: the_nested: type: the.yaml metadata: foo: bar outputs: value: value: {get_attr: [the_nested, output]} ''' nested_templ = ''' heat_template_version: 2013-05-23 resources: test: type: OS::Heat::TestResource properties: value: {"Fn::Select": [foo, {resource_facade: metadata}]} outputs: output: value: {get_attr: [test, output]} ''' def test_metadata(self): stack_identifier = self.stack_create( template=self.main_template, files={'the.yaml': self.nested_templ}) stack = self.client.stacks.get(stack_identifier) value = self._stack_output(stack, 'value') self.assertEqual('bar', value) class TemplateResourceUpdateTest(functional_base.FunctionalTestsBase): """Prove that we can do template resource updates.""" main_template = ''' HeatTemplateFormatVersion: '2012-12-12' Resources: the_nested: Type: the.yaml Properties: one: my_name two: your_name Outputs: identifier: Value: {Ref: the_nested} value: Value: {'Fn::GetAtt': [the_nested, the_str]} ''' main_template_change_prop = ''' HeatTemplateFormatVersion: '2012-12-12' Resources: the_nested: Type: the.yaml Properties: one: updated_name two: your_name Outputs: identifier: Value: {Ref: the_nested} value: Value: {'Fn::GetAtt': [the_nested, the_str]} ''' main_template_add_prop = ''' HeatTemplateFormatVersion: '2012-12-12' Resources: the_nested: Type: the.yaml Properties: one: my_name two: your_name three: third_name Outputs: identifier: Value: {Ref: the_nested} value: Value: {'Fn::GetAtt': [the_nested, the_str]} ''' main_template_remove_prop = ''' HeatTemplateFormatVersion: '2012-12-12' Resources: the_nested: Type: the.yaml Properties: one: my_name Outputs: identifier: Value: {Ref: the_nested} value: Value: {'Fn::GetAtt': [the_nested, the_str]} ''' initial_tmpl = ''' HeatTemplateFormatVersion: '2012-12-12' Parameters: one: Default: foo Type: String two: Default: bar Type: String Resources: NestedResource: Type: OS::Heat::RandomString Properties: salt: {Ref: one} Outputs: the_str: Value: {'Fn::GetAtt': [NestedResource, value]} ''' prop_change_tmpl = ''' HeatTemplateFormatVersion: '2012-12-12' Parameters: one: Default: yikes Type: String two: Default: foo Type: String Resources: NestedResource: Type: OS::Heat::RandomString Properties: salt: {Ref: two} Outputs: the_str: Value: {'Fn::GetAtt': [NestedResource, value]} ''' prop_add_tmpl = ''' HeatTemplateFormatVersion: '2012-12-12' Parameters: one: Default: yikes Type: String two: Default: foo Type: String three: Default: bar Type: String Resources: NestedResource: Type: OS::Heat::RandomString Properties: salt: {Ref: three} Outputs: the_str: Value: {'Fn::GetAtt': [NestedResource, value]} ''' prop_remove_tmpl = ''' HeatTemplateFormatVersion: '2012-12-12' Parameters: one: Default: yikes Type: String Resources: NestedResource: Type: OS::Heat::RandomString Properties: salt: {Ref: one} Outputs: the_str: Value: {'Fn::GetAtt': [NestedResource, value]} ''' attr_change_tmpl = ''' HeatTemplateFormatVersion: '2012-12-12' Parameters: one: Default: foo Type: String two: Default: bar Type: String Resources: NestedResource: Type: OS::Heat::RandomString Properties: salt: {Ref: one} Outputs: the_str: Value: {'Fn::GetAtt': [NestedResource, value]} something_else: Value: just_a_string ''' content_change_tmpl = ''' HeatTemplateFormatVersion: '2012-12-12' Parameters: one: Default: foo Type: String two: Default: bar Type: String Resources: NestedResource: Type: OS::Heat::RandomString Properties: salt: yum Outputs: the_str: Value: {'Fn::GetAtt': [NestedResource, value]} ''' EXPECTED = (UPDATE, NOCHANGE) = ('update', 'nochange') scenarios = [ ('no_changes', dict(template=main_template, provider=initial_tmpl, expect=NOCHANGE)), ('main_tmpl_change', dict(template=main_template_change_prop, provider=initial_tmpl, expect=UPDATE)), ('provider_change', dict(template=main_template, provider=content_change_tmpl, expect=UPDATE)), ('provider_props_change', dict(template=main_template, provider=prop_change_tmpl, expect=UPDATE)), ('provider_props_add', dict(template=main_template_add_prop, provider=prop_add_tmpl, expect=UPDATE)), ('provider_props_remove', dict(template=main_template_remove_prop, provider=prop_remove_tmpl, expect=NOCHANGE)), ('provider_attr_change', dict(template=main_template, provider=attr_change_tmpl, expect=NOCHANGE)), ] def setUp(self): super(TemplateResourceUpdateTest, self).setUp() def test_template_resource_update_template_schema(self): stack_identifier = self.stack_create( template=self.main_template, files={'the.yaml': self.initial_tmpl}) stack = self.client.stacks.get(stack_identifier) initial_id = self._stack_output(stack, 'identifier') initial_val = self._stack_output(stack, 'value') self.update_stack(stack_identifier, self.template, files={'the.yaml': self.provider}) stack = self.client.stacks.get(stack_identifier) self.assertEqual(initial_id, self._stack_output(stack, 'identifier')) if self.expect == self.NOCHANGE: self.assertEqual(initial_val, self._stack_output(stack, 'value')) else: self.assertNotEqual(initial_val, self._stack_output(stack, 'value')) class TemplateResourceUpdateFailedTest(functional_base.FunctionalTestsBase): """Prove that we can do updates on a nested stack to fix a stack.""" main_template = ''' HeatTemplateFormatVersion: '2012-12-12' Resources: keypair: Type: OS::Nova::KeyPair Properties: name: replace-this save_private_key: false server: Type: server_fail.yaml DependsOn: keypair ''' nested_templ = ''' HeatTemplateFormatVersion: '2012-12-12' Resources: RealRandom: Type: OS::Heat::RandomString ''' def setUp(self): super(TemplateResourceUpdateFailedTest, self).setUp() self.assign_keypair() def test_update_on_failed_create(self): # create a stack with "server" dependent on "keypair", but # keypair fails, so "server" is not created properly. # We then fix the template and it should succeed. broken_templ = self.main_template.replace('replace-this', self.keypair_name) stack_identifier = self.stack_create( template=broken_templ, files={'server_fail.yaml': self.nested_templ}, expected_status='CREATE_FAILED') fixed_templ = self.main_template.replace('replace-this', test.rand_name()) self.update_stack(stack_identifier, fixed_templ, files={'server_fail.yaml': self.nested_templ}) class TemplateResourceAdoptTest(functional_base.FunctionalTestsBase): """Prove that we can do template resource adopt/abandon.""" main_template = ''' HeatTemplateFormatVersion: '2012-12-12' Resources: the_nested: Type: the.yaml Properties: one: my_name Outputs: identifier: Value: {Ref: the_nested} value: Value: {'Fn::GetAtt': [the_nested, the_str]} ''' nested_templ = ''' HeatTemplateFormatVersion: '2012-12-12' Parameters: one: Default: foo Type: String Resources: RealRandom: Type: OS::Heat::RandomString Properties: salt: {Ref: one} Outputs: the_str: Value: {'Fn::GetAtt': [RealRandom, value]} ''' def setUp(self): super(TemplateResourceAdoptTest, self).setUp() def _yaml_to_json(self, yaml_templ): return yaml.safe_load(yaml_templ) def test_abandon(self): stack_identifier = self.stack_create( template=self.main_template, files={'the.yaml': self.nested_templ}, enable_cleanup=False ) info = self.stack_abandon(stack_id=stack_identifier) self.assertEqual(self._yaml_to_json(self.main_template), info['template']) self.assertEqual(self._yaml_to_json(self.nested_templ), info['resources']['the_nested']['template']) # TODO(james combs): Implement separate test cases for export # once export REST API is available. Also test reverse order # of invocation: export -> abandon AND abandon -> export def test_adopt(self): data = { 'resources': { 'the_nested': { "type": "the.yaml", "resources": { "RealRandom": { "type": "OS::Heat::RandomString", 'resource_data': {'value': 'goopie'}, 'resource_id': 'froggy' } } } }, "environment": {"parameters": {}}, "template": yaml.safe_load(self.main_template) } stack_identifier = self.stack_adopt( adopt_data=json.dumps(data), files={'the.yaml': self.nested_templ}) self.assert_resource_is_a_stack(stack_identifier, 'the_nested') stack = self.client.stacks.get(stack_identifier) self.assertEqual('goopie', self._stack_output(stack, 'value')) class TemplateResourceCheckTest(functional_base.FunctionalTestsBase): """Prove that we can do template resource check.""" main_template = ''' HeatTemplateFormatVersion: '2012-12-12' Resources: the_nested: Type: the.yaml Properties: one: my_name Outputs: identifier: Value: {Ref: the_nested} value: Value: {'Fn::GetAtt': [the_nested, the_str]} ''' nested_templ = ''' HeatTemplateFormatVersion: '2012-12-12' Parameters: one: Default: foo Type: String Resources: RealRandom: Type: OS::Heat::RandomString Properties: salt: {Ref: one} Outputs: the_str: Value: {'Fn::GetAtt': [RealRandom, value]} ''' def setUp(self): super(TemplateResourceCheckTest, self).setUp() def test_check(self): stack_identifier = self.stack_create( template=self.main_template, files={'the.yaml': self.nested_templ} ) self.client.actions.check(stack_id=stack_identifier) self._wait_for_stack_status(stack_identifier, 'CHECK_COMPLETE') class TemplateResourceErrorMessageTest(functional_base.FunctionalTestsBase): """Prove that nested stack errors don't suck.""" template = ''' HeatTemplateFormatVersion: '2012-12-12' Resources: victim: Type: fail.yaml ''' nested_templ = ''' HeatTemplateFormatVersion: '2012-12-12' Resources: oops: Type: OS::Heat::TestResource Properties: fail: true wait_secs: 2 ''' def setUp(self): super(TemplateResourceErrorMessageTest, self).setUp() def test_fail(self): stack_identifier = self.stack_create( template=self.template, files={'fail.yaml': self.nested_templ}, expected_status='CREATE_FAILED') stack = self.client.stacks.get(stack_identifier) exp_path = 'resources.victim.resources.oops' exp_msg = 'Test Resource failed oops' exp = 'Resource CREATE failed: ValueError: %s: %s' % (exp_path, exp_msg) self.assertEqual(exp, stack.stack_status_reason) class TemplateResourceSuspendResumeTest(functional_base.FunctionalTestsBase): """Prove that we can do template resource suspend/resume.""" main_template = ''' heat_template_version: 2014-10-16 parameters: resources: the_nested: type: the.yaml ''' nested_templ = ''' heat_template_version: 2014-10-16 resources: test_random_string: type: OS::Heat::RandomString ''' def setUp(self): super(TemplateResourceSuspendResumeTest, self).setUp() def test_suspend_resume(self): """Basic test for template resource suspend resume.""" stack_identifier = self.stack_create( template=self.main_template, files={'the.yaml': self.nested_templ} ) self.stack_suspend(stack_identifier=stack_identifier) self.stack_resume(stack_identifier=stack_identifier) class ValidateFacadeTest(test.HeatIntegrationTest): """Prove that nested stack errors don't suck.""" template = ''' heat_template_version: 2015-10-15 resources: thisone: type: OS::Thingy properties: one: pre two: post outputs: one: value: {get_attr: [thisone, here-it-is]} ''' templ_facade = ''' heat_template_version: 2015-04-30 parameters: one: type: string two: type: string outputs: here-it-is: value: noop ''' env = ''' resource_registry: OS::Thingy: facade.yaml resources: thisone: OS::Thingy: concrete.yaml ''' def setUp(self): super(ValidateFacadeTest, self).setUp() self.client = self.orchestration_client def test_missing_param(self): templ_missing_parameter = ''' heat_template_version: 2015-04-30 parameters: one: type: string resources: str: type: OS::Heat::RandomString outputs: here-it-is: value: not-important ''' try: self.stack_create( template=self.template, environment=self.env, files={'facade.yaml': self.templ_facade, 'concrete.yaml': templ_missing_parameter}, expected_status='CREATE_FAILED') except heat_exceptions.HTTPBadRequest as exc: exp = ('ERROR: Required property two for facade ' 'OS::Thingy missing in provider') self.assertEqual(exp, six.text_type(exc)) def test_missing_output(self): templ_missing_output = ''' heat_template_version: 2015-04-30 parameters: one: type: string two: type: string resources: str: type: OS::Heat::RandomString ''' try: self.stack_create( template=self.template, environment=self.env, files={'facade.yaml': self.templ_facade, 'concrete.yaml': templ_missing_output}, expected_status='CREATE_FAILED') except heat_exceptions.HTTPBadRequest as exc: exp = ('ERROR: Attribute here-it-is for facade ' 'OS::Thingy missing in provider') self.assertEqual(exp, six.text_type(exc)) class TemplateResourceNewParamTest(functional_base.FunctionalTestsBase): main_template = ''' heat_template_version: 2013-05-23 resources: my_resource: type: resource.yaml properties: value1: foo ''' nested_templ = ''' heat_template_version: 2013-05-23 parameters: value1: type: string resources: test: type: OS::Heat::TestResource properties: value: {get_param: value1} ''' main_template_update = ''' heat_template_version: 2013-05-23 resources: my_resource: type: resource.yaml properties: value1: foo value2: foo ''' nested_templ_update_fail = ''' heat_template_version: 2013-05-23 parameters: value1: type: string value2: type: string resources: test: type: OS::Heat::TestResource properties: fail: True value: str_replace: template: VAL1-VAL2 params: VAL1: {get_param: value1} VAL2: {get_param: value2} ''' nested_templ_update = ''' heat_template_version: 2013-05-23 parameters: value1: type: string value2: type: string resources: test: type: OS::Heat::TestResource properties: value: str_replace: template: VAL1-VAL2 params: VAL1: {get_param: value1} VAL2: {get_param: value2} ''' def test_update(self): stack_identifier = self.stack_create( template=self.main_template, files={'resource.yaml': self.nested_templ}) # Make the update fails with the new parameter inserted. self.update_stack( stack_identifier, self.main_template_update, files={'resource.yaml': self.nested_templ_update_fail}, expected_status='UPDATE_FAILED') # Fix the update, it should succeed now. self.update_stack( stack_identifier, self.main_template_update, files={'resource.yaml': self.nested_templ_update}) class TemplateResourceRemovedParamTest(functional_base.FunctionalTestsBase): main_template = ''' heat_template_version: 2013-05-23 parameters: value1: type: string default: foo resources: my_resource: type: resource.yaml properties: value1: {get_param: value1} ''' nested_templ = ''' heat_template_version: 2013-05-23 parameters: value1: type: string default: foo resources: test: type: OS::Heat::TestResource properties: value: {get_param: value1} ''' main_template_update = ''' heat_template_version: 2013-05-23 resources: my_resource: type: resource.yaml ''' nested_templ_update = ''' heat_template_version: 2013-05-23 parameters: value1: type: string default: foo value2: type: string default: bar resources: test: type: OS::Heat::TestResource properties: value: str_replace: template: VAL1-VAL2 params: VAL1: {get_param: value1} VAL2: {get_param: value2} ''' def test_update(self): stack_identifier = self.stack_create( template=self.main_template, environment={'parameters': {'value1': 'spam'}}, files={'resource.yaml': self.nested_templ}) self.update_stack( stack_identifier, self.main_template_update, environment={'parameter_defaults': {'value2': 'egg'}}, files={'resource.yaml': self.nested_templ_update}, existing=True) heat-6.0.0/heat_integrationtests/functional/test_stack_outputs.py0000664000567000056710000000450312701407050026667 0ustar jenkinsjenkins00000000000000# 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 heat_integrationtests.functional import functional_base class StackOutputsTest(functional_base.FunctionalTestsBase): template = ''' heat_template_version: 2015-10-15 resources: test_resource_a: type: OS::Heat::TestResource properties: value: 'a' test_resource_b: type: OS::Heat::TestResource properties: value: 'b' outputs: resource_output_a: description: 'Output of resource a' value: { get_attr: [test_resource_a, output] } resource_output_b: description: 'Output of resource b' value: { get_attr: [test_resource_b, output] } ''' def test_outputs(self): stack_identifier = self.stack_create( template=self.template ) expected_list = [{u'output_key': u'resource_output_a', u'description': u'Output of resource a'}, {u'output_key': u'resource_output_b', u'description': u'Output of resource b'}] actual_list = self.client.stacks.output_list( stack_identifier)['outputs'] self.assertEqual(expected_list, actual_list) expected_output_a = { u'output_value': u'a', u'output_key': u'resource_output_a', u'description': u'Output of resource a'} expected_output_b = { u'output_value': u'b', u'output_key': u'resource_output_b', u'description': u'Output of resource b'} actual_output_a = self.client.stacks.output_show( stack_identifier, 'resource_output_a')['output'] actual_output_b = self.client.stacks.output_show( stack_identifier, 'resource_output_b')['output'] self.assertEqual(expected_output_a, actual_output_a) self.assertEqual(expected_output_b, actual_output_b) heat-6.0.0/heat_integrationtests/functional/test_preview.py0000664000567000056710000001743212701407050025445 0ustar jenkinsjenkins00000000000000# 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 heat_integrationtests.common import test from heatclient import exc import six class StackPreviewTest(test.HeatIntegrationTest): template = ''' heat_template_version: 2015-04-30 parameters: incomming: type: string resources: one: type: OS::Heat::TestResource properties: value: fred two: type: OS::Heat::TestResource properties: value: {get_param: incomming} depends_on: one outputs: main_out: value: {get_attr: [two, output]} ''' env = ''' parameters: incomming: abc ''' def setUp(self): super(StackPreviewTest, self).setUp() self.client = self.orchestration_client self.project_id = self.identity_client.project_id def _assert_resource(self, res, stack_name): self.assertEqual(stack_name, res['stack_name']) self.assertEqual('INIT', res['resource_action']) self.assertEqual('COMPLETE', res['resource_status']) for field in ('resource_status_reason', 'physical_resource_id', 'description'): self.assertIn(field, res) self.assertEqual('', res[field]) # 'creation_time' and 'updated_time' are None when preview for field in ('creation_time', 'updated_time'): self.assertIn(field, res) self.assertIsNone(res[field]) self.assertIn('output', res['attributes']) # resource_identity self.assertEqual(stack_name, res['resource_identity']['stack_name']) self.assertEqual('None', res['resource_identity']['stack_id']) self.assertEqual(self.project_id, res['resource_identity']['tenant']) self.assertEqual('/resources/%s' % res['resource_name'], res['resource_identity']['path']) # stack_identity self.assertEqual(stack_name, res['stack_identity']['stack_name']) self.assertEqual('None', res['stack_identity']['stack_id']) self.assertEqual(self.project_id, res['stack_identity']['tenant']) self.assertEqual('', res['stack_identity']['path']) def _assert_results(self, result, stack_name): # global stuff. self.assertEqual(stack_name, result['stack_name']) self.assertTrue(result['disable_rollback']) self.assertEqual('None', result['id']) self.assertIsNone(result['parent']) self.assertEqual('No description', result['template_description']) # parameters self.assertEqual('None', result['parameters']['OS::stack_id']) self.assertEqual(stack_name, result['parameters']['OS::stack_name']) self.assertEqual('abc', result['parameters']['incomming']) def test_basic_pass(self): stack_name = self._stack_rand_name() result = self.client.stacks.preview( template=self.template, stack_name=stack_name, disable_rollback=True, environment=self.env).to_dict() self._assert_results(result, stack_name) for res in result['resources']: self._assert_resource(res, stack_name) self.assertEqual('OS::Heat::TestResource', res['resource_type']) # common properties self.assertFalse(res['properties']['fail']) self.assertEqual(0, res['properties']['wait_secs']) self.assertFalse(res['properties']['update_replace']) if res['resource_name'] == 'one': self.assertEqual('fred', res['properties']['value']) self.assertEqual(['two'], res['required_by']) if res['resource_name'] == 'two': self.assertEqual('abc', res['properties']['value']) self.assertEqual([], res['required_by']) def test_basic_fail(self): stack_name = self._stack_rand_name() # break the template so it fails validation. wont_work = self.template.replace('get_param: incomming', 'get_param: missing') excp = self.assertRaises(exc.HTTPBadRequest, self.client.stacks.preview, template=wont_work, stack_name=stack_name, disable_rollback=True, environment=self.env) self.assertIn('Property error: : resources.two.properties.value: ' ': The Parameter (missing) was not provided.', six.text_type(excp)) def test_nested_pass(self): """Nested stacks need to recurse down the stacks.""" main_template = ''' heat_template_version: 2015-04-30 parameters: incomming: type: string resources: main: type: nested.yaml properties: value: {get_param: incomming} outputs: main_out: value: {get_attr: [main, output]} ''' nested_template = ''' heat_template_version: 2015-04-30 parameters: value: type: string resources: nested: type: OS::Heat::TestResource properties: value: {get_param: value} outputs: output: value: {get_attr: [nested, output]} ''' stack_name = self._stack_rand_name() result = self.client.stacks.preview( disable_rollback=True, stack_name=stack_name, template=main_template, files={'nested.yaml': nested_template}, environment=self.env).to_dict() self._assert_results(result, stack_name) # nested resources return a list of their resources. res = result['resources'][0][0] nested_stack_name = '%s-%s' % (stack_name, res['parent_resource']) self._assert_resource(res, nested_stack_name) self.assertEqual('OS::Heat::TestResource', res['resource_type']) self.assertFalse(res['properties']['fail']) self.assertEqual(0, res['properties']['wait_secs']) self.assertFalse(res['properties']['update_replace']) self.assertEqual('abc', res['properties']['value']) self.assertEqual([], res['required_by']) def test_res_group_with_nested_template(self): main_template = ''' heat_template_version: 2015-04-30 resources: fixed_network: type: "OS::Neutron::Net" rg: type: "OS::Heat::ResourceGroup" properties: count: 1 resource_def: type: nested.yaml properties: fixed_network_id: {get_resource: fixed_network} ''' nested_template = ''' heat_template_version: 2015-04-30 parameters: fixed_network_id: type: string resources: port: type: "OS::Neutron::Port" properties: network_id: get_param: fixed_network_id ''' stack_name = self._stack_rand_name() result = self.client.stacks.preview( disable_rollback=True, stack_name=stack_name, template=main_template, files={'nested.yaml': nested_template}).to_dict() # ensure that fixed network and port here self.assertEqual('fixed_network', result['resources'][0]['resource_name']) self.assertEqual('port', result['resources'][1][0][0]['resource_name']) heat-6.0.0/heat_integrationtests/functional/functional_base.py0000664000567000056710000000252112701407050026052 0ustar jenkinsjenkins00000000000000# 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 oslo_utils import reflection from heat_integrationtests.common import test class FunctionalTestsBase(test.HeatIntegrationTest): def setUp(self): super(FunctionalTestsBase, self).setUp() self.check_skip() self.client = self.orchestration_client def check_skip(self): test_cls_name = reflection.get_class_name(self, fully_qualified=False) test_method_name = '.'.join([test_cls_name, self._testMethodName]) test_skipped = (self.conf.skip_functional_test_list and ( test_cls_name in self.conf.skip_functional_test_list or test_method_name in self.conf.skip_functional_test_list)) if self.conf.skip_functional_tests or test_skipped: self.skipTest('Test disabled in conf, skipping') heat-6.0.0/heat_integrationtests/functional/__init__.py0000664000567000056710000000000012701407050024443 0ustar jenkinsjenkins00000000000000heat-6.0.0/heat_integrationtests/functional/test_encryption_vol_type.py0000664000567000056710000000773512701407050030104 0ustar jenkinsjenkins00000000000000# 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 heat_integrationtests.common import clients from heat_integrationtests.common import config from heat_integrationtests.functional import functional_base test_encryption_vol_type = { 'heat_template_version': '2015-04-30', 'description': 'Test template to create encryption volume type.', 'resources': { 'my_volume_type': { 'type': 'OS::Cinder::VolumeType', 'properties': { 'name': 'LUKS' } }, 'my_encrypted_vol_type': { 'type': 'OS::Cinder::EncryptedVolumeType', 'properties': { 'provider': 'nova.volume.encryptors.luks.LuksEncryptor', 'control_location': 'front-end', 'cipher': 'aes-xts-plain64', 'key_size': 512, 'volume_type': {'get_resource': 'my_volume_type'} } } } } class EncryptionVolTypeTest(functional_base.FunctionalTestsBase): def setUp(self): super(EncryptionVolTypeTest, self).setUp() if not self.conf.admin_username or not self.conf.admin_password: self.skipTest('No admin creds found, skipping') self.conf = config.init_conf() # cinder security policy usage of volume type is limited # to being used by administrators only. # Temporarily switch to admin self.conf.username = self.conf.admin_username self.conf.password = self.conf.admin_password self.manager = clients.ClientManager(self.conf) self.client = self.manager.orchestration_client self.volume_client = self.manager.volume_client def check_stack(self, sid): vt = 'my_volume_type' e_vt = 'my_encrypted_vol_type' # check if only two resources are present. expected_resources = {vt: 'OS::Cinder::VolumeType', e_vt: 'OS::Cinder::EncryptedVolumeType'} self.assertEqual(expected_resources, self.list_resources(sid)) e_vt_obj = self.client.resources.get(sid, e_vt) my_encrypted_vol_type_tmpl_prop = test_encryption_vol_type[ 'resources']['my_encrypted_vol_type']['properties'] # check if the phy rsrc specs was created in accordance with template. phy_rsrc_specs = self.volume_client.volume_encryption_types.get( e_vt_obj.physical_resource_id) self.assertEqual(my_encrypted_vol_type_tmpl_prop['key_size'], phy_rsrc_specs.key_size) self.assertEqual(my_encrypted_vol_type_tmpl_prop['provider'], phy_rsrc_specs.provider) self.assertEqual(my_encrypted_vol_type_tmpl_prop['cipher'], phy_rsrc_specs.cipher) self.assertEqual(my_encrypted_vol_type_tmpl_prop['control_location'], phy_rsrc_specs.control_location) def test_create_update(self): stack_identifier = self.stack_create( template=test_encryption_vol_type) self.check_stack(stack_identifier) # Change some properties and trigger update. my_encrypted_vol_type_tmpl_prop = test_encryption_vol_type[ 'resources']['my_encrypted_vol_type']['properties'] my_encrypted_vol_type_tmpl_prop['key_size'] = 256 my_encrypted_vol_type_tmpl_prop['cipher'] = 'aes-cbc-essiv' self.update_stack(stack_identifier, test_encryption_vol_type) self.check_stack(stack_identifier) heat-6.0.0/heat_integrationtests/functional/test_nova_server_networks.py0000664000567000056710000000620012701407050030240 0ustar jenkinsjenkins00000000000000# 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 heat_integrationtests.functional import functional_base server_with_sub_fixed_ip_template = ''' heat_template_version: 2016-04-08 description: Test template to test nova server with subnet and fixed_ip. parameters: flavor: type: string image: type: string resources: net: type: OS::Neutron::Net properties: name: my_net subnet: type: OS::Neutron::Subnet properties: network: {get_resource: net} cidr: 11.11.11.0/24 server: type: OS::Nova::Server properties: image: {get_param: image} flavor: {get_param: flavor} networks: - subnet: {get_resource: subnet} fixed_ip: 11.11.11.11 outputs: networks: value: {get_attr: [server, networks]} ''' class CreateServerTest(functional_base.FunctionalTestsBase): def setUp(self): super(CreateServerTest, self).setUp() def get_outputs(self, stack_identifier, output_key): stack = self.client.stacks.get(stack_identifier) output = self._stack_output(stack, output_key) return output def test_create_server_with_subnet_fixed_ip(self): parms = {'flavor': self.conf.minimal_instance_type, 'image': self.conf.minimal_image_ref} stack_identifier = self.stack_create( template=server_with_sub_fixed_ip_template, stack_name='server_with_sub_ip', parameters=parms) networks = self.get_outputs(stack_identifier, 'networks') self.assertEqual(['11.11.11.11'], networks['my_net']) def test_create_update_server_with_subnet(self): parms = {'flavor': self.conf.minimal_instance_type, 'image': self.conf.minimal_image_ref} template = server_with_sub_fixed_ip_template.replace( 'fixed_ip: 11.11.11.11', 'fixed_ip: 11.11.11.22') stack_identifier = self.stack_create( template=template, stack_name='create_server_with_sub_ip', parameters=parms) networks = self.get_outputs(stack_identifier, 'networks') self.assertEqual(['11.11.11.22'], networks['my_net']) # update the server only with subnet, we won't pass # both port_id and net_id to attach interface, then update success template_only_subnet = template.replace( 'fixed_ip: 11.11.11.22', '') self.update_stack(stack_identifier, template_only_subnet, parameters=parms) new_networks = self.get_outputs(stack_identifier, 'networks') self.assertNotEqual(['11.11.11.22'], new_networks['my_net']) heat-6.0.0/heat_integrationtests/functional/test_stack_tags.py0000664000567000056710000000464612701407050026112 0ustar jenkinsjenkins00000000000000# 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 heat_integrationtests.functional import functional_base class StackTagTest(functional_base.FunctionalTestsBase): template = ''' heat_template_version: 2014-10-16 description: foo parameters: input: type: string default: test resources: not-used: type: OS::Heat::TestResource properties: wait_secs: 1 value: {get_param: input} ''' def test_stack_tag(self): # Stack create with stack tags tags = 'foo,bar' stack_identifier = self.stack_create( template=self.template, tags=tags ) # Ensure property tag is populated and matches given tags stack = self.client.stacks.get(stack_identifier) self.assertEqual(['foo', 'bar'], stack.tags) # Update tags updated_tags = 'tag1,tag2' self.update_stack( stack_identifier, template=self.template, tags=updated_tags, parameters={'input': 'next'}) # Ensure property tag is populated and matches updated tags updated_stack = self.client.stacks.get(stack_identifier) self.assertEqual(['tag1', 'tag2'], updated_stack.tags) # Delete tags self.update_stack( stack_identifier, template=self.template, parameters={'input': 'none'} ) # Ensure property tag is not populated empty_tags_stack = self.client.stacks.get(stack_identifier) self.assertIsNone(empty_tags_stack.tags) def test_hidden_stack(self): # Stack create with hidden stack tag tags = 'foo,hidden' self.stack_create( template=self.template, tags=tags) # Ensure stack does not exist when we do a stack list for stack in self.client.stacks.list(): self.assertNotIn('hidden', stack.tags, "Hidden stack can be seen") heat-6.0.0/heat_integrationtests/functional/test_heat_autoscaling.py0000664000567000056710000000776612701407050027307 0ustar jenkinsjenkins00000000000000# 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 heat_integrationtests.functional import functional_base class HeatAutoscalingTest(functional_base.FunctionalTestsBase): template = ''' heat_template_version: 2014-10-16 resources: random_group: type: OS::Heat::AutoScalingGroup properties: max_size: 10 min_size: 10 resource: type: OS::Heat::RandomString outputs: all_values: value: {get_attr: [random_group, outputs_list, value]} value_0: value: {get_attr: [random_group, resource.0.value]} value_5: value: {get_attr: [random_group, resource.5.value]} value_9: value: {get_attr: [random_group, resource.9.value]} ''' template_nested = ''' heat_template_version: 2014-10-16 resources: random_group: type: OS::Heat::AutoScalingGroup properties: max_size: 10 min_size: 10 resource: type: randomstr.yaml outputs: all_values: value: {get_attr: [random_group, outputs_list, random_str]} value_0: value: {get_attr: [random_group, resource.0.random_str]} value_5: value: {get_attr: [random_group, resource.5.random_str]} value_9: value: {get_attr: [random_group, resource.9.random_str]} ''' template_randomstr = ''' heat_template_version: 2013-05-23 resources: random_str: type: OS::Heat::RandomString outputs: random_str: value: {get_attr: [random_str, value]} ''' def setUp(self): super(HeatAutoscalingTest, self).setUp() def _assert_output_values(self, stack_id): stack = self.client.stacks.get(stack_id) all_values = self._stack_output(stack, 'all_values') self.assertEqual(10, len(all_values)) self.assertEqual(all_values[0], self._stack_output(stack, 'value_0')) self.assertEqual(all_values[5], self._stack_output(stack, 'value_5')) self.assertEqual(all_values[9], self._stack_output(stack, 'value_9')) def test_path_attrs(self): stack_id = self.stack_create(template=self.template) expected_resources = {'random_group': 'OS::Heat::AutoScalingGroup'} self.assertEqual(expected_resources, self.list_resources(stack_id)) self._assert_output_values(stack_id) def test_path_attrs_nested(self): files = {'randomstr.yaml': self.template_randomstr} stack_id = self.stack_create(template=self.template_nested, files=files) expected_resources = {'random_group': 'OS::Heat::AutoScalingGroup'} self.assertEqual(expected_resources, self.list_resources(stack_id)) self._assert_output_values(stack_id) class AutoScalingGroupUpdateWithNoChanges(functional_base.FunctionalTestsBase): template = ''' heat_template_version: 2013-05-23 resources: test_group: type: OS::Heat::AutoScalingGroup properties: desired_capacity: 0 max_size: 0 min_size: 0 resource: type: OS::Heat::RandomString test_policy: type: OS::Heat::ScalingPolicy properties: adjustment_type: change_in_capacity auto_scaling_group_id: { get_resource: test_group } scaling_adjustment: 1 ''' def setUp(self): super(AutoScalingGroupUpdateWithNoChanges, self).setUp() def test_as_group_update_without_resource_changes(self): stack_identifier = self.stack_create(template=self.template) new_template = self.template.replace( 'scaling_adjustment: 1', 'scaling_adjustment: 2') self.update_stack(stack_identifier, template=new_template) heat-6.0.0/heat_integrationtests/functional/test_instance_group.py0000664000567000056710000005210312701407050026776 0ustar jenkinsjenkins00000000000000# 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 copy import json from testtools import matchers from heat_integrationtests.functional import functional_base class InstanceGroupTest(functional_base.FunctionalTestsBase): template = ''' { "AWSTemplateFormatVersion" : "2010-09-09", "Description" : "Template to create multiple instances.", "Parameters" : {"size": {"Type": "String", "Default": "1"}, "AZ": {"Type": "String", "Default": "nova"}, "image": {"Type": "String"}, "flavor": {"Type": "String"}}, "Resources": { "JobServerGroup": { "Type": "OS::Heat::InstanceGroup", "Properties": { "LaunchConfigurationName" : {"Ref": "JobServerConfig"}, "Size" : {"Ref": "size"}, "AvailabilityZones" : [{"Ref": "AZ"}] } }, "JobServerConfig" : { "Type" : "AWS::AutoScaling::LaunchConfiguration", "Metadata": {"foo": "bar"}, "Properties": { "ImageId" : {"Ref": "image"}, "InstanceType" : {"Ref": "flavor"}, "SecurityGroups" : [ "sg-1" ], "UserData" : "jsconfig data" } } }, "Outputs": { "InstanceList": {"Value": { "Fn::GetAtt": ["JobServerGroup", "InstanceList"]}}, "JobServerConfigRef": {"Value": { "Ref": "JobServerConfig"}} } } ''' instance_template = ''' heat_template_version: 2013-05-23 parameters: ImageId: {type: string} InstanceType: {type: string} SecurityGroups: {type: comma_delimited_list} UserData: {type: string} Tags: {type: comma_delimited_list} resources: random1: type: OS::Heat::RandomString properties: salt: {get_param: ImageId} outputs: PublicIp: value: {get_attr: [random1, value]} ''' # This is designed to fail. bad_instance_template = ''' heat_template_version: 2013-05-23 parameters: ImageId: {type: string} InstanceType: {type: string} SecurityGroups: {type: comma_delimited_list} UserData: {type: string} Tags: {type: comma_delimited_list} resources: random1: type: OS::Heat::RandomString depends_on: waiter ready_poster: type: AWS::CloudFormation::WaitConditionHandle waiter: type: AWS::CloudFormation::WaitCondition properties: Handle: {Ref: ready_poster} Timeout: 1 outputs: PublicIp: value: {get_attr: [random1, value]} ''' def setUp(self): super(InstanceGroupTest, self).setUp() if not self.conf.image_ref: raise self.skipException("No image configured to test") if not self.conf.minimal_image_ref: raise self.skipException("No minimal image configured to test") if not self.conf.instance_type: raise self.skipException("No flavor configured to test") def assert_instance_count(self, stack, expected_count): inst_list = self._stack_output(stack, 'InstanceList') self.assertEqual(expected_count, len(inst_list.split(','))) def _assert_instance_state(self, nested_identifier, num_complete, num_failed): for res in self.client.resources.list(nested_identifier): if 'COMPLETE' in res.resource_status: num_complete = num_complete - 1 elif 'FAILED' in res.resource_status: num_failed = num_failed - 1 self.assertEqual(0, num_failed) self.assertEqual(0, num_complete) class InstanceGroupBasicTest(InstanceGroupTest): def test_basic_create_works(self): """Make sure the working case is good. Note this combines test_override_aws_ec2_instance into this test as well, which is: If AWS::EC2::Instance is overridden, InstanceGroup will automatically use that overridden resource type. """ files = {'provider.yaml': self.instance_template} env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'}, 'parameters': {'size': 4, 'image': self.conf.image_ref, 'flavor': self.conf.instance_type}} stack_identifier = self.stack_create(template=self.template, files=files, environment=env) initial_resources = { 'JobServerConfig': 'AWS::AutoScaling::LaunchConfiguration', 'JobServerGroup': 'OS::Heat::InstanceGroup'} self.assertEqual(initial_resources, self.list_resources(stack_identifier)) stack = self.client.stacks.get(stack_identifier) self.assert_instance_count(stack, 4) def test_size_updates_work(self): files = {'provider.yaml': self.instance_template} env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'}, 'parameters': {'size': 2, 'image': self.conf.image_ref, 'flavor': self.conf.instance_type}} stack_identifier = self.stack_create(template=self.template, files=files, environment=env) stack = self.client.stacks.get(stack_identifier) self.assert_instance_count(stack, 2) # Increase min size to 5 env2 = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'}, 'parameters': {'size': 5, 'image': self.conf.image_ref, 'flavor': self.conf.instance_type}} self.update_stack(stack_identifier, self.template, environment=env2, files=files) stack = self.client.stacks.get(stack_identifier) self.assert_instance_count(stack, 5) def test_update_group_replace(self): """Test case for ensuring non-updatable props case a replacement. Make sure that during a group update the non-updatable properties cause a replacement. """ files = {'provider.yaml': self.instance_template} env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'}, 'parameters': {'size': 1, 'image': self.conf.image_ref, 'flavor': self.conf.instance_type}} stack_identifier = self.stack_create(template=self.template, files=files, environment=env) rsrc = self.client.resources.get(stack_identifier, 'JobServerGroup') orig_asg_id = rsrc.physical_resource_id env2 = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'}, 'parameters': {'size': '2', 'AZ': 'wibble', 'image': self.conf.image_ref, 'flavor': self.conf.instance_type}} self.update_stack(stack_identifier, self.template, environment=env2, files=files) # replacement will cause the resource physical_resource_id to change. rsrc = self.client.resources.get(stack_identifier, 'JobServerGroup') self.assertNotEqual(orig_asg_id, rsrc.physical_resource_id) def test_create_instance_error_causes_group_error(self): """Test create failing a resource in the instance group. If a resource in an instance group fails to be created, the instance group itself will fail and the broken inner resource will remain. """ stack_name = self._stack_rand_name() files = {'provider.yaml': self.bad_instance_template} env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'}, 'parameters': {'size': 2, 'image': self.conf.image_ref, 'flavor': self.conf.instance_type}} self.client.stacks.create( stack_name=stack_name, template=self.template, files=files, disable_rollback=True, parameters={}, environment=env ) self.addCleanup(self._stack_delete, stack_name) stack = self.client.stacks.get(stack_name) stack_identifier = '%s/%s' % (stack_name, stack.id) self._wait_for_stack_status(stack_identifier, 'CREATE_FAILED') initial_resources = { 'JobServerConfig': 'AWS::AutoScaling::LaunchConfiguration', 'JobServerGroup': 'OS::Heat::InstanceGroup'} self.assertEqual(initial_resources, self.list_resources(stack_identifier)) nested_ident = self.assert_resource_is_a_stack(stack_identifier, 'JobServerGroup') self._assert_instance_state(nested_ident, 0, 2) def test_update_instance_error_causes_group_error(self): """Test update failing a resource in the instance group. If a resource in an instance group fails to be created during an update, the instance group itself will fail and the broken inner resource will remain. """ files = {'provider.yaml': self.instance_template} env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'}, 'parameters': {'size': 2, 'image': self.conf.image_ref, 'flavor': self.conf.instance_type}} stack_identifier = self.stack_create(template=self.template, files=files, environment=env) initial_resources = { 'JobServerConfig': 'AWS::AutoScaling::LaunchConfiguration', 'JobServerGroup': 'OS::Heat::InstanceGroup'} self.assertEqual(initial_resources, self.list_resources(stack_identifier)) stack = self.client.stacks.get(stack_identifier) self.assert_instance_count(stack, 2) nested_ident = self.assert_resource_is_a_stack(stack_identifier, 'JobServerGroup') self._assert_instance_state(nested_ident, 2, 0) initial_list = [res.resource_name for res in self.client.resources.list(nested_ident)] env['parameters']['size'] = 3 files2 = {'provider.yaml': self.bad_instance_template} self.client.stacks.update( stack_id=stack_identifier, template=self.template, files=files2, disable_rollback=True, parameters={}, environment=env ) self._wait_for_stack_status(stack_identifier, 'UPDATE_FAILED') nested_ident = self.assert_resource_is_a_stack(stack_identifier, 'JobServerGroup') # assert that there are 3 bad instances # 2 resources should be in update failed, and one create failed. for res in self.client.resources.list(nested_ident): if res.resource_name in initial_list: self._wait_for_resource_status(nested_ident, res.resource_name, 'UPDATE_FAILED') else: self._wait_for_resource_status(nested_ident, res.resource_name, 'CREATE_FAILED') class InstanceGroupUpdatePolicyTest(InstanceGroupTest): def ig_tmpl_with_updt_policy(self): templ = json.loads(copy.deepcopy(self.template)) up = {"RollingUpdate": { "MinInstancesInService": "1", "MaxBatchSize": "2", "PauseTime": "PT1S"}} templ['Resources']['JobServerGroup']['UpdatePolicy'] = up return templ def update_instance_group(self, updt_template, num_updates_expected_on_updt, num_creates_expected_on_updt, num_deletes_expected_on_updt, update_replace): # setup stack from the initial template files = {'provider.yaml': self.instance_template} size = 5 env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'}, 'parameters': {'size': size, 'image': self.conf.image_ref, 'flavor': self.conf.instance_type}} stack_name = self._stack_rand_name() stack_identifier = self.stack_create( stack_name=stack_name, template=self.ig_tmpl_with_updt_policy(), files=files, environment=env) stack = self.client.stacks.get(stack_identifier) nested_ident = self.assert_resource_is_a_stack(stack_identifier, 'JobServerGroup') # test that physical resource name of launch configuration is used conf_name = self._stack_output(stack, 'JobServerConfigRef') conf_name_pattern = '%s-JobServerConfig-[a-zA-Z0-9]+$' % stack_name self.assertThat(conf_name, matchers.MatchesRegex(conf_name_pattern)) # test the number of instances created self.assert_instance_count(stack, size) # saves info from initial list of instances for comparison later init_instances = self.client.resources.list(nested_ident) init_names = [inst.resource_name for inst in init_instances] # test stack update self.update_stack(stack_identifier, updt_template, environment=env, files=files) updt_stack = self.client.stacks.get(stack_identifier) # test that the launch configuration is replaced updt_conf_name = self._stack_output(updt_stack, 'JobServerConfigRef') self.assertThat(updt_conf_name, matchers.MatchesRegex(conf_name_pattern)) self.assertNotEqual(conf_name, updt_conf_name) # test that the group size are the same updt_instances = self.client.resources.list(nested_ident) updt_names = [inst.resource_name for inst in updt_instances] self.assertEqual(len(init_names), len(updt_names)) for res in updt_instances: self.assertEqual('UPDATE_COMPLETE', res.resource_status) # test that the appropriate number of instance names are the same matched_names = set(updt_names) & set(init_names) self.assertEqual(num_updates_expected_on_updt, len(matched_names)) # test that the appropriate number of new instances are created self.assertEqual(num_creates_expected_on_updt, len(set(updt_names) - set(init_names))) # test that the appropriate number of instances are deleted self.assertEqual(num_deletes_expected_on_updt, len(set(init_names) - set(updt_names))) # test that the older instances are the ones being deleted if num_deletes_expected_on_updt > 0: deletes_expected = init_names[:num_deletes_expected_on_updt] self.assertNotIn(deletes_expected, updt_names) def test_instance_group_update_replace(self): """Test simple update replace with no conflict. Test simple update replace with no conflict in batch size and minimum instances in service. """ updt_template = self.ig_tmpl_with_updt_policy() grp = updt_template['Resources']['JobServerGroup'] policy = grp['UpdatePolicy']['RollingUpdate'] policy['MinInstancesInService'] = '1' policy['MaxBatchSize'] = '3' config = updt_template['Resources']['JobServerConfig'] config['Properties']['ImageId'] = self.conf.minimal_image_ref self.update_instance_group(updt_template, num_updates_expected_on_updt=5, num_creates_expected_on_updt=0, num_deletes_expected_on_updt=0, update_replace=True) def test_instance_group_update_replace_with_adjusted_capacity(self): """Test update replace with capacity adjustment. Test update replace with capacity adjustment due to conflict in batch size and minimum instances in service. """ updt_template = self.ig_tmpl_with_updt_policy() grp = updt_template['Resources']['JobServerGroup'] policy = grp['UpdatePolicy']['RollingUpdate'] policy['MinInstancesInService'] = '4' policy['MaxBatchSize'] = '4' config = updt_template['Resources']['JobServerConfig'] config['Properties']['ImageId'] = self.conf.minimal_image_ref self.update_instance_group(updt_template, num_updates_expected_on_updt=2, num_creates_expected_on_updt=3, num_deletes_expected_on_updt=3, update_replace=True) def test_instance_group_update_replace_huge_batch_size(self): """Test update replace with a huge batch size.""" updt_template = self.ig_tmpl_with_updt_policy() group = updt_template['Resources']['JobServerGroup'] policy = group['UpdatePolicy']['RollingUpdate'] policy['MinInstancesInService'] = '0' policy['MaxBatchSize'] = '20' config = updt_template['Resources']['JobServerConfig'] config['Properties']['ImageId'] = self.conf.minimal_image_ref self.update_instance_group(updt_template, num_updates_expected_on_updt=5, num_creates_expected_on_updt=0, num_deletes_expected_on_updt=0, update_replace=True) def test_instance_group_update_replace_huge_min_in_service(self): """Update replace with huge number of minimum instances in service.""" updt_template = self.ig_tmpl_with_updt_policy() group = updt_template['Resources']['JobServerGroup'] policy = group['UpdatePolicy']['RollingUpdate'] policy['MinInstancesInService'] = '20' policy['MaxBatchSize'] = '2' policy['PauseTime'] = 'PT0S' config = updt_template['Resources']['JobServerConfig'] config['Properties']['ImageId'] = self.conf.minimal_image_ref self.update_instance_group(updt_template, num_updates_expected_on_updt=3, num_creates_expected_on_updt=2, num_deletes_expected_on_updt=2, update_replace=True) def test_instance_group_update_no_replace(self): """Test simple update only and no replace with no conflict. Test simple update only and no replace (i.e. updated instance flavor in Launch Configuration) with no conflict in batch size and minimum instances in service. """ updt_template = self.ig_tmpl_with_updt_policy() group = updt_template['Resources']['JobServerGroup'] policy = group['UpdatePolicy']['RollingUpdate'] policy['MinInstancesInService'] = '1' policy['MaxBatchSize'] = '3' policy['PauseTime'] = 'PT0S' config = updt_template['Resources']['JobServerConfig'] config['Properties']['InstanceType'] = 'm1.tiny' self.update_instance_group(updt_template, num_updates_expected_on_updt=5, num_creates_expected_on_updt=0, num_deletes_expected_on_updt=0, update_replace=False) def test_instance_group_update_no_replace_with_adjusted_capacity(self): """Test update only and no replace with capacity adjustment. Test update only and no replace (i.e. updated instance flavor in Launch Configuration) with capacity adjustment due to conflict in batch size and minimum instances in service. """ updt_template = self.ig_tmpl_with_updt_policy() group = updt_template['Resources']['JobServerGroup'] policy = group['UpdatePolicy']['RollingUpdate'] policy['MinInstancesInService'] = '4' policy['MaxBatchSize'] = '4' policy['PauseTime'] = 'PT0S' config = updt_template['Resources']['JobServerConfig'] config['Properties']['InstanceType'] = 'm1.tiny' self.update_instance_group(updt_template, num_updates_expected_on_updt=2, num_creates_expected_on_updt=3, num_deletes_expected_on_updt=3, update_replace=False) heat-6.0.0/heat_integrationtests/functional/test_unicode_template.py0000664000567000056710000001036612701407050027304 0ustar jenkinsjenkins00000000000000# 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 heat_integrationtests.functional import functional_base class StackUnicodeTemplateTest(functional_base.FunctionalTestsBase): random_template = u''' heat_template_version: 2014-10-16 description: \u8fd9\u662f\u4e00\u4e2a\u63cf\u8ff0 parameters: \u53c2\u6570: type: number default: 10 label: \u6807\u7b7e description: \u8fd9\u662f\u4e00\u4e2a\u63cf\u8ff0 resources: \u8d44\u6e90: type: OS::Heat::RandomString properties: length: {get_param: \u53c2\u6570} outputs: \u8f93\u51fa: description: \u8fd9\u662f\u4e00\u4e2a\u63cf\u8ff0 value: {get_attr: [\u8d44\u6e90, value]} ''' def setUp(self): super(StackUnicodeTemplateTest, self).setUp() def _assert_results(self, result): self.assertTrue(result['disable_rollback']) self.assertIsNone(result['parent']) self.assertEqual(u'\u8fd9\u662f\u4e00\u4e2a\u63cf\u8ff0', result['template_description']) self.assertEqual(u'10', result['parameters'][u'\u53c2\u6570']) def _assert_preview_results(self, result): self._assert_results(result) res = result['resources'][0] self.assertEqual('/resources/%s' % res['resource_name'], res['resource_identity']['path']) def _assert_create_results(self, result): self._assert_results(result) output = result['outputs'][0] self.assertEqual(u'\u8fd9\u662f\u4e00\u4e2a\u63cf\u8ff0', output['description']) self.assertEqual(u'\u8f93\u51fa', output['output_key']) self.assertIsNotNone(output['output_value']) def _assert_resource_results(self, result): self.assertEqual(u'\u8d44\u6e90', result['resource_name']) self.assertEqual('OS::Heat::RandomString', result['resource_type']) def test_template_validate_basic(self): ret = self.client.stacks.validate(template=self.random_template) expected = { 'Description': u'\u8fd9\u662f\u4e00\u4e2a\u63cf\u8ff0', 'Parameters': { u'\u53c2\u6570': { 'Default': 10, 'Description': u'\u8fd9\u662f\u4e00\u4e2a\u63cf\u8ff0', 'Label': u'\u6807\u7b7e', 'NoEcho': 'false', 'Type': 'Number'} } } self.assertEqual(expected, ret) def test_template_validate_override_default(self): env = {'parameters': {u'\u53c2\u6570': 5}} ret = self.client.stacks.validate(template=self.random_template, environment=env) expected = { 'Description': u'\u8fd9\u662f\u4e00\u4e2a\u63cf\u8ff0', 'Parameters': { u'\u53c2\u6570': { 'Default': 10, 'Value': 5, 'Description': u'\u8fd9\u662f\u4e00\u4e2a\u63cf\u8ff0', 'Label': u'\u6807\u7b7e', 'NoEcho': 'false', 'Type': 'Number'} } } self.assertEqual(expected, ret) def test_stack_preview(self): result = self.client.stacks.preview( template=self.random_template, stack_name=self._stack_rand_name(), disable_rollback=True).to_dict() self._assert_preview_results(result) def test_create_stack(self): stack_identifier = self.stack_create(template=self.random_template) stack = self.client.stacks.get(stack_identifier) self._assert_create_results(stack.to_dict()) rl = self.client.resources.list(stack_identifier) self.assertEqual(1, len(rl)) self._assert_resource_results(rl[0].to_dict()) heat-6.0.0/heat_integrationtests/functional/test_env_merge.py0000664000567000056710000000534612701407050025734 0ustar jenkinsjenkins00000000000000# # 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 heat_integrationtests.functional import functional_base TEMPLATE = ''' heat_template_version: 2015-04-30 parameters: p0: type: string default: CORRECT p1: type: string default: INCORRECT p2: type: string default: INCORRECT resources: r1: type: test::R1 r2: type: test::R2 r3a: type: test::R3 r3b: type: test::R3 ''' ENV_1 = ''' parameters: p1: CORRECT p2: INCORRECT-E1 resource_registry: test::R1: OS::Heat::RandomString test::R2: BROKEN test::R3: OS::Heat::None ''' ENV_2 = ''' parameters: p2: CORRECT resource_registry: test::R2: OS::Heat::RandomString resources: r3b: test::R3: OS::Heat::RandomString ''' class EnvironmentMergingTests(functional_base.FunctionalTestsBase): def test_server_environment_merging(self): # Setup files = {'env1.yaml': ENV_1, 'env2.yaml': ENV_2} environment_files = ['env1.yaml', 'env2.yaml'] # Test stack_id = self.stack_create(stack_name='env_merge', template=TEMPLATE, files=files, environment_files=environment_files) # Verify # Since there is no environment show, the registry overriding # is partially verified by there being no error. If it wasn't # working, test::R2 would remain mapped to BROKEN in env1. # Sanity check resources = self.list_resources(stack_id) self.assertEqual(4, len(resources)) # Verify the parameters are correctly set stack = self.client.stacks.get(stack_id) self.assertEqual('CORRECT', stack.parameters['p0']) self.assertEqual('CORRECT', stack.parameters['p1']) self.assertEqual('CORRECT', stack.parameters['p2']) # Verify that r3b has been overridden into a RandomString # by checking to see that it has a value r3b = self.client.resources.get(stack_id, 'r3b') r3b_attrs = r3b.attributes self.assertTrue('value' in r3b_attrs) heat-6.0.0/heat_integrationtests/functional/test_stack_events.py0000664000567000056710000001077012701407050026453 0ustar jenkinsjenkins00000000000000# 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 heat_integrationtests.functional import functional_base class StackEventsTest(functional_base.FunctionalTestsBase): template = ''' heat_template_version: 2014-10-16 parameters: resources: test_resource: type: OS::Heat::TestResource properties: value: 'test1' fail: False update_replace: False wait_secs: 0 outputs: resource_id: description: 'ID of resource' value: { get_resource: test_resource } ''' def setUp(self): super(StackEventsTest, self).setUp() def _verify_event_fields(self, event, event_characteristics): self.assertIsNotNone(event_characteristics) self.assertIsNotNone(event.event_time) self.assertIsNotNone(event.links) self.assertIsNotNone(event.logical_resource_id) self.assertIsNotNone(event.resource_status) self.assertIn(event.resource_status, event_characteristics[1]) self.assertIsNotNone(event.resource_status_reason) self.assertIsNotNone(event.id) def test_event(self): parameters = {} test_stack_name = self._stack_rand_name() stack_identifier = self.stack_create( stack_name=test_stack_name, template=self.template, parameters=parameters ) expected_status = ['CREATE_IN_PROGRESS', 'CREATE_COMPLETE'] event_characteristics = { test_stack_name: ('OS::Heat::Stack', expected_status), 'test_resource': ('OS::Heat::TestResource', expected_status)} # List stack events # API: GET /v1/{tenant_id}/stacks/{stack_name}/{stack_id}/events stack_events = self.client.events.list(stack_identifier) for stack_event in stack_events: # Key on an expected/valid resource name self._verify_event_fields( stack_event, event_characteristics[stack_event.resource_name]) # Test the event filtering API based on this resource_name # /v1/{tenant_id}/stacks/{stack_name}/{stack_id}/resources/{resource_name}/events resource_events = self.client.events.list( stack_identifier, stack_event.resource_name) # Resource events are a subset of the original stack event list self.assertTrue(len(resource_events) < len(stack_events)) # Get the event details for each resource event for resource_event in resource_events: # A resource_event should be in the original stack event list self.assertIn(resource_event, stack_events) # Given a filtered list, the resource names should be identical self.assertEqual( resource_event.resource_name, stack_event.resource_name) # Verify all fields, keying off the resource_name self._verify_event_fields( resource_event, event_characteristics[resource_event.resource_name]) # Exercise the event details API # /v1/{tenant_id}/stacks/{stack_name}/{stack_id}/resources/{resource_name}/events/{event_id} event_details = self.client.events.get( stack_identifier, resource_event.resource_name, resource_event.id) self._verify_event_fields( event_details, event_characteristics[event_details.resource_name]) # The names should be identical to the non-detailed event self.assertEqual( resource_event.resource_name, event_details.resource_name) # Verify the extra field in the detail results self.assertIsNotNone(event_details.resource_type) self.assertEqual( event_characteristics[event_details.resource_name][0], event_details.resource_type) heat-6.0.0/heat_integrationtests/functional/test_templates.py0000664000567000056710000000525212701407053025762 0ustar jenkinsjenkins00000000000000# 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 heat_integrationtests.functional import functional_base class TemplateAPITest(functional_base.FunctionalTestsBase): """This will test the following template calls: 1. Get the template content for the specific stack 2. List template versions 3. List resource types 4. Show resource details for OS::Heat::TestResource """ template = { 'heat_template_version': '2014-10-16', 'description': 'Test Template APIs', 'resources': { 'test1': { 'type': 'OS::Heat::TestResource', 'properties': { 'update_replace': False, 'wait_secs': 0, 'value': 'Test1', 'fail': False, } } } } def setUp(self): super(TemplateAPITest, self).setUp() def test_get_stack_template(self): stack_identifier = self.stack_create( template=self.template ) template_from_client = self.client.stacks.template(stack_identifier) self.assertDictEqual(self.template, template_from_client) def test_template_version(self): template_versions = self.client.template_versions.list() supported_template_versions = ["2013-05-23", "2014-10-16", "2015-04-30", "2015-10-15", "2012-12-12", "2010-09-09", "2016-04-08"] for template in template_versions: self.assertIn(template.version.split(".")[1], supported_template_versions) def test_resource_types(self): resource_types = self.client.resource_types.list() self.assertTrue(any(resource.resource_type == "OS::Heat::TestResource" for resource in resource_types)) def test_show_resource_template(self): resource_details = self.client.resource_types.get( resource_type="OS::Heat::TestResource" ) self.assertEqual("OS::Heat::TestResource", resource_details['resource_type']) heat-6.0.0/heat_integrationtests/functional/test_reload_on_sighup.py0000664000567000056710000001157712701407050027311 0ustar jenkinsjenkins00000000000000# 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 time import eventlet from oslo_concurrency import processutils from six.moves import configparser from heat_integrationtests.functional import functional_base class ReloadOnSighupTest(functional_base.FunctionalTestsBase): def setUp(self): self.config_file = "/etc/heat/heat.conf" super(ReloadOnSighupTest, self).setUp() def _set_config_value(self, service, key, value): config = configparser.ConfigParser() # NOTE(prazumovsky): If there are several workers, there can be # situation, when one thread opens self.config_file for writing # (so config_file erases with opening), in that moment other thread # intercepts to this file and try to set config option value, i.e. # write to file, which is already erased by first thread, so, # NoSectionError raised. So, should wait until first thread writes to # config_file. retries_count = self.conf.sighup_config_edit_retries while True: config.read(self.config_file) try: config.set(service, key, value) except configparser.NoSectionError: if retries_count <= 0: raise retries_count -= 1 eventlet.sleep(1) else: break with open(self.config_file, 'wb') as f: config.write(f) def _get_config_value(self, service, key): config = configparser.ConfigParser() config.read(self.config_file) val = config.get(service, key) return val def _get_heat_api_pids(self, service): # get the pids of all heat-api processes if service == "heat_api": process = "heat-api|grep -Ev 'grep|cloudwatch|cfn'" else: process = "%s|grep -Ev 'grep'" % service.replace('_', '-') cmd = "ps -ef|grep %s|awk '{print $2}'" % process out, err = processutils.execute(cmd, shell=True) self.assertIsNotNone(out, "heat-api service not running. %s" % err) pids = filter(None, out.split('\n')) # get the parent pids of all heat-api processes cmd = "ps -ef|grep %s|awk '{print $3}'" % process out, _ = processutils.execute(cmd, shell=True) parent_pids = filter(None, out.split('\n')) heat_api_parent = list(set(pids) & set(parent_pids))[0] heat_api_children = list(set(pids) - set(parent_pids)) return heat_api_parent, heat_api_children def _change_config(self, service, old_workers, new_workers): pre_reload_parent, pre_reload_children = self._get_heat_api_pids( service) self.assertEqual(old_workers, len(pre_reload_children)) # change the config values self._set_config_value(service, 'workers', new_workers) cmd = "kill -HUP %s" % pre_reload_parent processutils.execute(cmd, shell=True) # wait till heat-api reloads start_time = time.time() while time.time() - start_time < self.conf.sighup_timeout: post_reload_parent, post_reload_children = self._get_heat_api_pids( service) intersect = set(post_reload_children) & set(pre_reload_children) if (new_workers == len(post_reload_children) and pre_reload_parent == post_reload_parent and intersect == set()): break eventlet.sleep(1) self.assertEqual(pre_reload_parent, post_reload_parent) self.assertEqual(new_workers, len(post_reload_children)) # test if all child processes are newly created self.assertEqual(set(post_reload_children) & set(pre_reload_children), set()) def _reload(self, service): old_workers = int(self._get_config_value(service, 'workers')) new_workers = old_workers + 1 self.addCleanup(self._set_config_value, service, 'workers', old_workers) self._change_config(service, old_workers, new_workers) # revert all the changes made self._change_config(service, new_workers, old_workers) def test_api_reload_on_sighup(self): self._reload('heat_api') def test_api_cfn_reload_on_sighup(self): self._reload('heat_api_cfn') def test_api_cloudwatch_on_sighup(self): self._reload('heat_api_cloudwatch') heat-6.0.0/heat_integrationtests/functional/test_immutable_parameters.py0000664000567000056710000001156112701407050030163 0ustar jenkinsjenkins00000000000000# 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 heat_integrationtests.functional import functional_base from heatclient import exc as heat_exceptions class ImmutableParametersTest(functional_base.FunctionalTestsBase): template_param_has_no_immutable_field = ''' heat_template_version: 2014-10-16 parameters: param1: type: string default: default_value outputs: param1_output: description: 'parameter 1 details' value: { get_param: param1 } ''' template_param_has_immutable_field = ''' heat_template_version: 2014-10-16 parameters: param1: type: string default: default_value immutable: false outputs: param1_output: description: 'parameter 1 details' value: { get_param: param1 } ''' def test_no_immutable_param_field(self): param1_create_value = 'value1' create_parameters = {"param1": param1_create_value} stack_identifier = self.stack_create( template=self.template_param_has_no_immutable_field, parameters=create_parameters ) stack = self.client.stacks.get(stack_identifier) # Verify the value of the parameter self.assertEqual(param1_create_value, self._stack_output(stack, 'param1_output')) param1_update_value = 'value2' update_parameters = {"param1": param1_update_value} self.update_stack( stack_identifier, template=self.template_param_has_no_immutable_field, parameters=update_parameters) stack = self.client.stacks.get(stack_identifier) # Verify the value of the updated parameter self.assertEqual(param1_update_value, self._stack_output(stack, 'param1_output')) def test_immutable_param_field_allowed(self): param1_create_value = 'value1' create_parameters = {"param1": param1_create_value} stack_identifier = self.stack_create( template=self.template_param_has_immutable_field, parameters=create_parameters ) stack = self.client.stacks.get(stack_identifier) # Verify the value of the parameter self.assertEqual(param1_create_value, self._stack_output(stack, 'param1_output')) param1_update_value = 'value2' update_parameters = {"param1": param1_update_value} self.update_stack( stack_identifier, template=self.template_param_has_immutable_field, parameters=update_parameters) stack = self.client.stacks.get(stack_identifier) # Verify the value of the updated parameter self.assertEqual(param1_update_value, self._stack_output(stack, 'param1_output')) # Ensure stack is not in a failed state self.assertEqual('UPDATE_COMPLETE', stack.stack_status) def test_immutable_param_field_error(self): param1_create_value = 'value1' create_parameters = {"param1": param1_create_value} # Toggle the immutable field to preclude updating immutable_true = self.template_param_has_immutable_field.replace( 'immutable: false', 'immutable: true') stack_identifier = self.stack_create( template=immutable_true, parameters=create_parameters ) stack = self.client.stacks.get(stack_identifier) param1_update_value = 'value2' update_parameters = {"param1": param1_update_value} # Verify the value of the parameter self.assertEqual(param1_create_value, self._stack_output(stack, 'param1_output')) # Attempt to update the stack with a new parameter value try: self.update_stack( stack_identifier, template=immutable_true, parameters=update_parameters) except heat_exceptions.HTTPBadRequest as exc: exp = ('The following parameters are immutable and may not be ' 'updated: param1') self.assertIn(exp, str(exc)) stack = self.client.stacks.get(stack_identifier) # Ensure stack is not in a failed state self.assertEqual('CREATE_COMPLETE', stack.stack_status) # Ensure immutable parameter has not changed self.assertEqual(param1_create_value, self._stack_output(stack, 'param1_output')) heat-6.0.0/heat_integrationtests/functional/test_encrypted_parameter.py0000664000567000056710000000413312701407050030013 0ustar jenkinsjenkins00000000000000# 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 heat_integrationtests.functional import functional_base class EncryptedParametersTest(functional_base.FunctionalTestsBase): template = ''' heat_template_version: 2014-10-16 parameters: image: type: string flavor: type: string network: type: string foo: type: string description: 'parameter with encryption turned on' hidden: true default: secret resources: server_with_encrypted_property: type: OS::Nova::Server properties: name: { get_param: foo } image: { get_param: image } flavor: { get_param: flavor } networks: [{network: {get_param: network} }] outputs: encrypted_foo_param: description: 'encrypted param' value: { get_param: foo } ''' def setUp(self): super(EncryptedParametersTest, self).setUp() def test_db_encryption(self): # Create a stack with the value of 'foo' to be encrypted foo_param = 'my_encrypted_foo' parameters = { "image": self.conf.minimal_image_ref, "flavor": self.conf.minimal_instance_type, 'network': self.conf.fixed_network_name, "foo": foo_param } stack_identifier = self.stack_create( template=self.template, parameters=parameters ) stack = self.client.stacks.get(stack_identifier) # Verify the output value for 'foo' parameter for out in stack.outputs: if out['output_key'] == 'encrypted_foo_param': self.assertEqual(foo_param, out['output_value']) heat-6.0.0/heat_integrationtests/functional/test_aws_stack.py0000664000567000056710000001704312701407050025741 0ustar jenkinsjenkins00000000000000# 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 hashlib import json import random from oslo_log import log as logging from six.moves.urllib import parse from swiftclient import utils as swiftclient_utils import yaml from heat_integrationtests.common import test from heat_integrationtests.functional import functional_base LOG = logging.getLogger(__name__) class AwsStackTest(functional_base.FunctionalTestsBase): test_template = ''' HeatTemplateFormatVersion: '2012-12-12' Resources: the_nested: Type: AWS::CloudFormation::Stack Properties: TemplateURL: the.yaml Parameters: KeyName: foo Outputs: output_foo: Value: {"Fn::GetAtt": [the_nested, Outputs.Foo]} ''' nested_template = ''' HeatTemplateFormatVersion: '2012-12-12' Parameters: KeyName: Type: String Outputs: Foo: Value: bar ''' update_template = ''' HeatTemplateFormatVersion: '2012-12-12' Parameters: KeyName: Type: String Outputs: Foo: Value: foo ''' nested_with_res_template = ''' HeatTemplateFormatVersion: '2012-12-12' Parameters: KeyName: Type: String Resources: NestedResource: Type: OS::Heat::RandomString Outputs: Foo: Value: {"Fn::GetAtt": [NestedResource, value]} ''' def setUp(self): super(AwsStackTest, self).setUp() self.object_container_name = test.rand_name() self.project_id = self.identity_client.project_id self.swift_key = hashlib.sha224( str(random.getrandbits(256))).hexdigest()[:32] key_header = 'x-container-meta-temp-url-key' self.object_client.put_container(self.object_container_name, {key_header: self.swift_key}) self.addCleanup(self.object_client.delete_container, self.object_container_name) def publish_template(self, contents, cleanup=True): oc = self.object_client # post the object oc.put_object(self.object_container_name, 'template.yaml', contents) if cleanup: self.addCleanup(oc.delete_object, self.object_container_name, 'template.yaml') path = '/v1/AUTH_%s/%s/%s' % (self.project_id, self.object_container_name, 'template.yaml') timeout = self.conf.build_timeout * 10 tempurl = swiftclient_utils.generate_temp_url(path, timeout, self.swift_key, 'GET') sw_url = parse.urlparse(oc.url) return '%s://%s%s' % (sw_url.scheme, sw_url.netloc, tempurl) def test_nested_stack_create(self): url = self.publish_template(self.nested_template) self.template = self.test_template.replace('the.yaml', url) stack_identifier = self.stack_create(template=self.template) stack = self.client.stacks.get(stack_identifier) self.assert_resource_is_a_stack(stack_identifier, 'the_nested') self.assertEqual('bar', self._stack_output(stack, 'output_foo')) def test_nested_stack_create_with_timeout(self): url = self.publish_template(self.nested_template) self.template = self.test_template.replace('the.yaml', url) timeout_template = yaml.safe_load(self.template) props = timeout_template['Resources']['the_nested']['Properties'] props['TimeoutInMinutes'] = '50' stack_identifier = self.stack_create( template=timeout_template) stack = self.client.stacks.get(stack_identifier) self.assert_resource_is_a_stack(stack_identifier, 'the_nested') self.assertEqual('bar', self._stack_output(stack, 'output_foo')) def test_nested_stack_adopt_ok(self): url = self.publish_template(self.nested_with_res_template) self.template = self.test_template.replace('the.yaml', url) adopt_data = { "resources": { "the_nested": { "resource_id": "test-res-id", "resources": { "NestedResource": { "type": "OS::Heat::RandomString", "resource_id": "test-nested-res-id", "resource_data": {"value": "goopie"} } } } }, "environment": {"parameters": {}}, "template": yaml.safe_load(self.template) } stack_identifier = self.stack_adopt(adopt_data=json.dumps(adopt_data)) self.assert_resource_is_a_stack(stack_identifier, 'the_nested') stack = self.client.stacks.get(stack_identifier) self.assertEqual('goopie', self._stack_output(stack, 'output_foo')) def test_nested_stack_adopt_fail(self): url = self.publish_template(self.nested_with_res_template) self.template = self.test_template.replace('the.yaml', url) adopt_data = { "resources": { "the_nested": { "resource_id": "test-res-id", "resources": { } } }, "environment": {"parameters": {}}, "template": yaml.safe_load(self.template) } stack_identifier = self.stack_adopt(adopt_data=json.dumps(adopt_data), wait_for_status='ADOPT_FAILED') rsrc = self.client.resources.get(stack_identifier, 'the_nested') self.assertEqual('ADOPT_FAILED', rsrc.resource_status) def test_nested_stack_update(self): url = self.publish_template(self.nested_template) self.template = self.test_template.replace('the.yaml', url) stack_identifier = self.stack_create(template=self.template) original_nested_id = self.assert_resource_is_a_stack( stack_identifier, 'the_nested') stack = self.client.stacks.get(stack_identifier) self.assertEqual('bar', self._stack_output(stack, 'output_foo')) new_template = yaml.safe_load(self.template) props = new_template['Resources']['the_nested']['Properties'] props['TemplateURL'] = self.publish_template(self.update_template, cleanup=False) self.update_stack(stack_identifier, new_template) # Expect the physical resource name staying the same after update, # so that the nested was actually updated instead of replaced. new_nested_id = self.assert_resource_is_a_stack( stack_identifier, 'the_nested') self.assertEqual(original_nested_id, new_nested_id) updt_stack = self.client.stacks.get(stack_identifier) self.assertEqual('foo', self._stack_output(updt_stack, 'output_foo')) def test_nested_stack_suspend_resume(self): url = self.publish_template(self.nested_template) self.template = self.test_template.replace('the.yaml', url) stack_identifier = self.stack_create(template=self.template) self.stack_suspend(stack_identifier) self.stack_resume(stack_identifier) heat-6.0.0/heat_integrationtests/functional/test_software_config.py0000664000567000056710000002050012701407050027131 0ustar jenkinsjenkins00000000000000# 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 json import os import requests import subprocess import tempfile import time import yaml from oslo_utils import timeutils from heat_integrationtests.common import exceptions from heat_integrationtests.functional import functional_base class ParallelDeploymentsTest(functional_base.FunctionalTestsBase): server_template = ''' heat_template_version: "2013-05-23" parameters: flavor: type: string image: type: string network: type: string resources: server: type: OS::Nova::Server properties: image: {get_param: image} flavor: {get_param: flavor} user_data_format: SOFTWARE_CONFIG networks: [{network: {get_param: network}}] outputs: server: value: {get_resource: server} ''' config_template = ''' heat_template_version: "2013-05-23" parameters: server: type: string resources: config: type: OS::Heat::SoftwareConfig properties: ''' deployment_snippet = ''' type: OS::Heat::SoftwareDeployments properties: config: {get_resource: config} servers: {'0': {get_param: server}} ''' enable_cleanup = True def test_deployments_metadata(self): parms = {'flavor': self.conf.minimal_instance_type, 'network': self.conf.fixed_network_name, 'image': self.conf.minimal_image_ref} stack_identifier = self.stack_create( parameters=parms, template=self.server_template, enable_cleanup=self.enable_cleanup) server_stack = self.client.stacks.get(stack_identifier) server = server_stack.outputs[0]['output_value'] config_stacks = [] # add up to 3 stacks each with up to 3 deployments deploy_count = 0 deploy_count = self.deploy_many_configs( stack_identifier, server, config_stacks, 2, 5, deploy_count) self.deploy_many_configs( stack_identifier, server, config_stacks, 3, 3, deploy_count) self.signal_deployments(stack_identifier) for config_stack in config_stacks: self._wait_for_stack_status(config_stack, 'CREATE_COMPLETE') def deploy_many_configs(self, stack, server, config_stacks, stack_count, deploys_per_stack, deploy_count_start): for a in range(stack_count): config_stacks.append( self.deploy_config(server, deploys_per_stack)) new_count = deploy_count_start + stack_count * deploys_per_stack self.wait_for_deploy_metadata_set(stack, new_count) return new_count def deploy_config(self, server, deploy_count): parms = {'server': server} template = yaml.safe_load(self.config_template) resources = template['resources'] resources['config']['properties'] = {'config': 'x' * 10000} for a in range(deploy_count): resources['dep_%s' % a] = yaml.safe_load(self.deployment_snippet) return self.stack_create( parameters=parms, template=template, enable_cleanup=self.enable_cleanup, expected_status=None) def wait_for_deploy_metadata_set(self, stack, deploy_count): build_timeout = self.conf.build_timeout build_interval = self.conf.build_interval start = timeutils.utcnow() while timeutils.delta_seconds(start, timeutils.utcnow()) < build_timeout: server_metadata = self.client.resources.metadata( stack, 'server') if len(server_metadata['deployments']) == deploy_count: return time.sleep(build_interval) message = ('Deployment resources failed to be created within ' 'the required time (%s s).' % (build_timeout)) raise exceptions.TimeoutException(message) def signal_deployments(self, stack_identifier): server_metadata = self.client.resources.metadata( stack_identifier, 'server') for dep in server_metadata['deployments']: iv = dict((i['name'], i['value']) for i in dep['inputs']) sigurl = iv.get('deploy_signal_id') requests.post(sigurl, data='{}', headers={'content-type': None}, verify=self.verify_cert) class ZaqarSignalTransportTest(functional_base.FunctionalTestsBase): server_template = ''' heat_template_version: "2013-05-23" parameters: flavor: type: string image: type: string network: type: string resources: server: type: OS::Nova::Server properties: image: {get_param: image} flavor: {get_param: flavor} user_data_format: SOFTWARE_CONFIG software_config_transport: ZAQAR_MESSAGE networks: [{network: {get_param: network}}] config: type: OS::Heat::SoftwareConfig properties: config: echo 'foo' deployment: type: OS::Heat::SoftwareDeployment properties: config: {get_resource: config} server: {get_resource: server} signal_transport: ZAQAR_SIGNAL outputs: data: value: {get_attr: [deployment, deploy_stdout]} ''' conf_template = ''' [zaqar] user_id = %(user_id)s password = %(password)s project_id = %(project_id)s auth_url = %(auth_url)s queue_id = %(queue_id)s ''' def test_signal_queues(self): parms = {'flavor': self.conf.minimal_instance_type, 'network': self.conf.fixed_network_name, 'image': self.conf.minimal_image_ref} stack_identifier = self.stack_create( parameters=parms, template=self.server_template, expected_status=None) metadata = self.wait_for_deploy_metadata_set(stack_identifier) config = metadata['os-collect-config']['zaqar'] conf_content = self.conf_template % config fd, temp_path = tempfile.mkstemp() os.write(fd, conf_content) os.close(fd) cmd = ['os-collect-config', '--one-time', '--config-file=%s' % temp_path, 'zaqar'] proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) stdout_value = proc.communicate()[0] data = json.loads(stdout_value) self.assertEqual(config, data['zaqar']['os-collect-config']['zaqar']) proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) stdout_value = proc.communicate()[0] data = json.loads(stdout_value) fd, temp_path = tempfile.mkstemp() os.write(fd, json.dumps(data['zaqar']['deployments'][0])) os.close(fd) cmd = ['python', self.conf.heat_config_notify_script, temp_path] proc = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdin=subprocess.PIPE) proc.communicate(json.dumps({'deploy_stdout': 'here!'})) self._wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE') stack = self.client.stacks.get(stack_identifier) self.assertEqual('here!', stack.outputs[0]['output_value']) def wait_for_deploy_metadata_set(self, stack): build_timeout = self.conf.build_timeout build_interval = self.conf.build_interval start = timeutils.utcnow() while timeutils.delta_seconds(start, timeutils.utcnow()) < build_timeout: server_metadata = self.client.resources.metadata( stack, 'server') if server_metadata.get('deployments'): return server_metadata time.sleep(build_interval) message = ('Deployment resources failed to be created within ' 'the required time (%s s).' % (build_timeout)) raise exceptions.TimeoutException(message) heat-6.0.0/heat_integrationtests/functional/test_remote_stack.py0000664000567000056710000001273612701407053026451 0ustar jenkinsjenkins00000000000000# 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 heatclient import exc import six from heat_integrationtests.functional import functional_base class RemoteStackTest(functional_base.FunctionalTestsBase): template = ''' heat_template_version: 2013-05-23 resources: my_stack: type: OS::Heat::Stack properties: context: region_name: RegionOne template: get_file: remote_stack.yaml outputs: key: value: {get_attr: [my_stack, outputs]} ''' remote_template = ''' heat_template_version: 2013-05-23 resources: random1: type: OS::Heat::RandomString outputs: remote_key: value: {get_attr: [random1, value]} ''' def setUp(self): super(RemoteStackTest, self).setUp() def test_remote_stack_alone(self): stack_id = self.stack_create(template=self.remote_template) expected_resources = {'random1': 'OS::Heat::RandomString'} self.assertEqual(expected_resources, self.list_resources(stack_id)) stack = self.client.stacks.get(stack_id) output_value = self._stack_output(stack, 'remote_key') self.assertEqual(32, len(output_value)) def test_stack_create(self): files = {'remote_stack.yaml': self.remote_template} stack_id = self.stack_create(files=files) expected_resources = {'my_stack': 'OS::Heat::Stack'} self.assertEqual(expected_resources, self.list_resources(stack_id)) stack = self.client.stacks.get(stack_id) output = self._stack_output(stack, 'key') parent_output_value = output['remote_key'] self.assertEqual(32, len(parent_output_value)) rsrc = self.client.resources.get(stack_id, 'my_stack') remote_id = rsrc.physical_resource_id rstack = self.client.stacks.get(remote_id) self.assertEqual(remote_id, rstack.id) remote_output_value = self._stack_output(rstack, 'remote_key') self.assertEqual(32, len(remote_output_value)) self.assertEqual(parent_output_value, remote_output_value) remote_resources = {'random1': 'OS::Heat::RandomString'} self.assertEqual(remote_resources, self.list_resources(remote_id)) def test_stack_create_bad_region(self): tmpl_bad_region = self.template.replace('RegionOne', 'DARKHOLE') files = {'remote_stack.yaml': self.remote_template} kwargs = { 'template': tmpl_bad_region, 'files': files } ex = self.assertRaises(exc.HTTPBadRequest, self.stack_create, **kwargs) error_msg = ('ERROR: Cannot establish connection to Heat endpoint ' 'at region "DARKHOLE" due to "publicURL endpoint for ' 'orchestration service in DARKHOLE region not found"') self.assertEqual(error_msg, six.text_type(ex)) def test_stack_resource_validation_fail(self): tmpl_bad_format = self.remote_template.replace('resources', 'resource') files = {'remote_stack.yaml': tmpl_bad_format} kwargs = {'files': files} ex = self.assertRaises(exc.HTTPBadRequest, self.stack_create, **kwargs) error_msg = ('ERROR: Failed validating stack template using Heat ' 'endpoint at region "RegionOne" due to ' '"ERROR: The template section is invalid: resource"') self.assertEqual(error_msg, six.text_type(ex)) def test_stack_update(self): files = {'remote_stack.yaml': self.remote_template} stack_id = self.stack_create(files=files) expected_resources = {'my_stack': 'OS::Heat::Stack'} self.assertEqual(expected_resources, self.list_resources(stack_id)) rsrc = self.client.resources.get(stack_id, 'my_stack') physical_resource_id = rsrc.physical_resource_id rstack = self.client.stacks.get(physical_resource_id) self.assertEqual(physical_resource_id, rstack.id) remote_resources = {'random1': 'OS::Heat::RandomString'} self.assertEqual(remote_resources, self.list_resources(rstack.id)) # do an update update_template = self.remote_template.replace('random1', 'random2') files = {'remote_stack.yaml': update_template} self.update_stack(stack_id, self.template, files=files) # check if the remote stack is still there with the same ID self.assertEqual(expected_resources, self.list_resources(stack_id)) rsrc = self.client.resources.get(stack_id, 'my_stack') physical_resource_id = rsrc.physical_resource_id rstack = self.client.stacks.get(physical_resource_id) self.assertEqual(physical_resource_id, rstack.id) remote_resources = {'random2': 'OS::Heat::RandomString'} self.assertEqual(remote_resources, self.list_resources(rstack.id)) def test_stack_suspend_resume(self): files = {'remote_stack.yaml': self.remote_template} stack_id = self.stack_create(files=files) self.stack_suspend(stack_id) self.stack_resume(stack_id) heat-6.0.0/heat_integrationtests/functional/test_preview_update.py0000664000567000056710000002603612701407050027007 0ustar jenkinsjenkins00000000000000# 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 heat_integrationtests.functional import functional_base test_template_one_resource = { 'heat_template_version': '2013-05-23', 'description': 'Test template to create one instance.', 'resources': { 'test1': { 'type': 'OS::Heat::TestResource', 'properties': { 'value': 'Test1', 'fail': False, 'update_replace': False, 'wait_secs': 0 } } } } test_template_two_resource = { 'heat_template_version': '2013-05-23', 'description': 'Test template to create two instance.', 'resources': { 'test1': { 'type': 'OS::Heat::TestResource', 'properties': { 'value': 'Test1', 'fail': False, 'update_replace': False, 'wait_secs': 0 } }, 'test2': { 'type': 'OS::Heat::TestResource', 'properties': { 'value': 'Test1', 'fail': False, 'update_replace': False, 'wait_secs': 0 } } } } class UpdatePreviewBase(functional_base.FunctionalTestsBase): def assert_empty_sections(self, changes, empty_sections): for section in empty_sections: self.assertEqual([], changes[section]) class UpdatePreviewStackTest(UpdatePreviewBase): def test_add_resource(self): self.stack_identifier = self.stack_create( template=test_template_one_resource) result = self.preview_update_stack(self.stack_identifier, test_template_two_resource) changes = result['resource_changes'] unchanged = changes['unchanged'][0]['resource_name'] self.assertEqual('test1', unchanged) added = changes['added'][0]['resource_name'] self.assertEqual('test2', added) self.assert_empty_sections(changes, ['updated', 'replaced', 'deleted']) def test_no_change(self): self.stack_identifier = self.stack_create( template=test_template_one_resource) result = self.preview_update_stack(self.stack_identifier, test_template_one_resource) changes = result['resource_changes'] unchanged = changes['unchanged'][0]['resource_name'] self.assertEqual('test1', unchanged) self.assert_empty_sections( changes, ['updated', 'replaced', 'deleted', 'added']) def test_update_resource(self): self.stack_identifier = self.stack_create( template=test_template_one_resource) test_template_updated_resource = { 'heat_template_version': '2013-05-23', 'description': 'Test template to create one instance.', 'resources': { 'test1': { 'type': 'OS::Heat::TestResource', 'properties': { 'value': 'Test1 foo', 'fail': False, 'update_replace': False, 'wait_secs': 0 } } } } result = self.preview_update_stack(self.stack_identifier, test_template_updated_resource) changes = result['resource_changes'] updated = changes['updated'][0]['resource_name'] self.assertEqual('test1', updated) self.assert_empty_sections( changes, ['added', 'unchanged', 'replaced', 'deleted']) def test_replaced_resource(self): self.stack_identifier = self.stack_create( template=test_template_one_resource) new_template = { 'heat_template_version': '2013-05-23', 'description': 'Test template to create one instance.', 'resources': { 'test1': { 'type': 'OS::Heat::TestResource', 'properties': { 'update_replace': True, } } } } result = self.preview_update_stack(self.stack_identifier, new_template) changes = result['resource_changes'] replaced = changes['replaced'][0]['resource_name'] self.assertEqual('test1', replaced) self.assert_empty_sections( changes, ['added', 'unchanged', 'updated', 'deleted']) def test_delete_resource(self): self.stack_identifier = self.stack_create( template=test_template_two_resource) result = self.preview_update_stack(self.stack_identifier, test_template_one_resource) changes = result['resource_changes'] unchanged = changes['unchanged'][0]['resource_name'] self.assertEqual('test1', unchanged) deleted = changes['deleted'][0]['resource_name'] self.assertEqual('test2', deleted) self.assert_empty_sections(changes, ['updated', 'replaced', 'added']) class UpdatePreviewStackTestNested(UpdatePreviewBase): template_nested_parent = ''' heat_template_version: 2016-04-08 resources: nested1: type: nested1.yaml ''' template_nested1 = ''' heat_template_version: 2016-04-08 resources: nested2: type: nested2.yaml ''' template_nested2 = ''' heat_template_version: 2016-04-08 resources: random: type: OS::Heat::RandomString ''' template_nested2_2 = ''' heat_template_version: 2016-04-08 resources: random: type: OS::Heat::RandomString random2: type: OS::Heat::RandomString ''' def _get_by_resource_name(self, changes, name, action): filtered_l = [x for x in changes[action] if x['resource_name'] == name] self.assertEqual(1, len(filtered_l)) return filtered_l[0] def test_nested_resources_nochange(self): files = {'nested1.yaml': self.template_nested1, 'nested2.yaml': self.template_nested2} self.stack_identifier = self.stack_create( template=self.template_nested_parent, files=files) result = self.preview_update_stack( self.stack_identifier, template=self.template_nested_parent, files=files, show_nested=True) changes = result['resource_changes'] # The nested random resource should be unchanged, but we always # update nested stacks even when there are no changes self.assertEqual(1, len(changes['unchanged'])) self.assertEqual('random', changes['unchanged'][0]['resource_name']) self.assertEqual('nested2', changes['unchanged'][0]['parent_resource']) self.assertEqual(2, len(changes['updated'])) u_nested1 = self._get_by_resource_name(changes, 'nested1', 'updated') self.assertNotIn('parent_resource', u_nested1) u_nested2 = self._get_by_resource_name(changes, 'nested2', 'updated') self.assertEqual('nested1', u_nested2['parent_resource']) self.assert_empty_sections(changes, ['replaced', 'deleted', 'added']) def test_nested_resources_add(self): files = {'nested1.yaml': self.template_nested1, 'nested2.yaml': self.template_nested2} self.stack_identifier = self.stack_create( template=self.template_nested_parent, files=files) files['nested2.yaml'] = self.template_nested2_2 result = self.preview_update_stack( self.stack_identifier, template=self.template_nested_parent, files=files, show_nested=True) changes = result['resource_changes'] # The nested random resource should be unchanged, but we always # update nested stacks even when there are no changes self.assertEqual(1, len(changes['unchanged'])) self.assertEqual('random', changes['unchanged'][0]['resource_name']) self.assertEqual('nested2', changes['unchanged'][0]['parent_resource']) self.assertEqual(1, len(changes['added'])) self.assertEqual('random2', changes['added'][0]['resource_name']) self.assertEqual('nested2', changes['added'][0]['parent_resource']) self.assert_empty_sections(changes, ['replaced', 'deleted']) def test_nested_resources_delete(self): files = {'nested1.yaml': self.template_nested1, 'nested2.yaml': self.template_nested2_2} self.stack_identifier = self.stack_create( template=self.template_nested_parent, files=files) files['nested2.yaml'] = self.template_nested2 result = self.preview_update_stack( self.stack_identifier, template=self.template_nested_parent, files=files, show_nested=True) changes = result['resource_changes'] # The nested random resource should be unchanged, but we always # update nested stacks even when there are no changes self.assertEqual(1, len(changes['unchanged'])) self.assertEqual('random', changes['unchanged'][0]['resource_name']) self.assertEqual('nested2', changes['unchanged'][0]['parent_resource']) self.assertEqual(1, len(changes['deleted'])) self.assertEqual('random2', changes['deleted'][0]['resource_name']) self.assertEqual('nested2', changes['deleted'][0]['parent_resource']) self.assert_empty_sections(changes, ['replaced', 'added']) def test_nested_resources_replace(self): files = {'nested1.yaml': self.template_nested1, 'nested2.yaml': self.template_nested2} self.stack_identifier = self.stack_create( template=self.template_nested_parent, files=files) parent_none = self.template_nested_parent.replace( 'nested1.yaml', 'OS::Heat::None') result = self.preview_update_stack( self.stack_identifier, template=parent_none, show_nested=True) changes = result['resource_changes'] # The nested random resource should be unchanged, but we always # update nested stacks even when there are no changes self.assertEqual(1, len(changes['replaced'])) self.assertEqual('nested1', changes['replaced'][0]['resource_name']) self.assertEqual(2, len(changes['deleted'])) d_random = self._get_by_resource_name(changes, 'random', 'deleted') self.assertEqual('nested2', d_random['parent_resource']) d_nested2 = self._get_by_resource_name(changes, 'nested2', 'deleted') self.assertEqual('nested1', d_nested2['parent_resource']) self.assert_empty_sections(changes, ['updated', 'unchanged', 'added']) heat-6.0.0/heat_integrationtests/functional/test_waitcondition.py0000664000567000056710000000507712701407050026641 0ustar jenkinsjenkins00000000000000# 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 json from keystoneclient.v3 import client as keystoneclient from zaqarclient.queues.v1 import client as zaqarclient from heat_integrationtests.functional import functional_base class ZaqarWaitConditionTest(functional_base.FunctionalTestsBase): template = ''' heat_template_version: "2013-05-23" resources: wait_condition: type: OS::Heat::WaitCondition properties: handle: {get_resource: wait_handle} timeout: 120 wait_handle: type: OS::Heat::WaitConditionHandle properties: signal_transport: ZAQAR_SIGNAL outputs: wait_data: value: {'Fn::Select': ['data_id', {get_attr: [wait_condition, data]}]} ''' def test_signal_queues(self): stack_identifier = self.stack_create( template=self.template, expected_status=None) self._wait_for_resource_status(stack_identifier, 'wait_handle', 'CREATE_COMPLETE') resource = self.client.resources.get(stack_identifier, 'wait_handle') signal = json.loads(resource.attributes['signal']) ks = keystoneclient.Client( auth_url=signal['auth_url'], user_id=signal['user_id'], password=signal['password'], project_id=signal['project_id']) endpoint = ks.service_catalog.url_for( service_type='messaging', endpoint_type='publicURL') conf = { 'auth_opts': { 'backend': 'keystone', 'options': { 'os_auth_token': ks.auth_token, 'os_project_id': signal['project_id'] } } } zaqar = zaqarclient.Client(endpoint, conf=conf, version=1.1) queue = zaqar.queue(signal['queue_id']) queue.post({'body': {'data': 'here!', 'id': 'data_id'}, 'ttl': 600}) self._wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE') stack = self.client.stacks.get(stack_identifier) self.assertEqual('here!', stack.outputs[0]['output_value']) heat-6.0.0/heat_integrationtests/functional/test_create_update_neutron_subnet.py0000664000567000056710000001266112701407050031722 0ustar jenkinsjenkins00000000000000# 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 heat_integrationtests.functional import functional_base test_template = ''' heat_template_version: 2015-04-30 description: Test template to create/update subnet with allocation_pools. resources: net: type: OS::Neutron::Net subnet: type: OS::Neutron::Subnet properties: network: { get_resource: net } cidr: 11.11.11.0/24 gateway_ip: 11.11.11.5 allocation_pools: [{start: 11.11.11.10, end: 11.11.11.250}] outputs: alloc_pools: value: {get_attr: [subnet, allocation_pools]} gateway_ip: value: {get_attr: [subnet, gateway_ip]} ''' class UpdateSubnetTest(functional_base.FunctionalTestsBase): def setUp(self): super(UpdateSubnetTest, self).setUp() def get_outputs(self, stack_identifier, output_key): stack = self.client.stacks.get(stack_identifier) output = self._stack_output(stack, output_key) return output def test_update_allocation_pools(self): stack_identifier = self.stack_create(template=test_template) alloc_pools = self.get_outputs(stack_identifier, 'alloc_pools') self.assertEqual([{'start': '11.11.11.10', 'end': '11.11.11.250'}], alloc_pools) # Update allocation_pools with a new range templ_other_pool = test_template.replace( 'allocation_pools: [{start: 11.11.11.10, end: 11.11.11.250}]', 'allocation_pools: [{start: 11.11.11.10, end: 11.11.11.100}]') self.update_stack(stack_identifier, templ_other_pool) new_alloc_pools = self.get_outputs(stack_identifier, 'alloc_pools') # the new pools should be the new range self.assertEqual([{'start': '11.11.11.10', 'end': '11.11.11.100'}], new_alloc_pools) def test_update_allocation_pools_to_empty(self): stack_identifier = self.stack_create(template=test_template) alloc_pools = self.get_outputs(stack_identifier, 'alloc_pools') self.assertEqual([{'start': '11.11.11.10', 'end': '11.11.11.250'}], alloc_pools) # Update allocation_pools with [] templ_empty_pools = test_template.replace( 'allocation_pools: [{start: 11.11.11.10, end: 11.11.11.250}]', 'allocation_pools: []') self.update_stack(stack_identifier, templ_empty_pools) new_alloc_pools = self.get_outputs(stack_identifier, 'alloc_pools') # new_alloc_pools should be [] self.assertEqual([], new_alloc_pools) def test_update_to_no_allocation_pools(self): stack_identifier = self.stack_create(template=test_template) alloc_pools = self.get_outputs(stack_identifier, 'alloc_pools') self.assertEqual([{'start': '11.11.11.10', 'end': '11.11.11.250'}], alloc_pools) # Remove the allocation_pools from template templ_no_pools = test_template.replace( 'allocation_pools: [{start: 11.11.11.10, end: 11.11.11.250}]', '') self.update_stack(stack_identifier, templ_no_pools) last_alloc_pools = self.get_outputs(stack_identifier, 'alloc_pools') # last_alloc_pools should be [] self.assertEqual([], last_alloc_pools) def test_update_gateway_ip(self): stack_identifier = self.stack_create(template=test_template) gw_ip = self.get_outputs(stack_identifier, 'gateway_ip') self.assertEqual('11.11.11.5', gw_ip) # Update gateway_ip templ_other_gw_ip = test_template.replace( 'gateway_ip: 11.11.11.5', 'gateway_ip: 11.11.11.9') self.update_stack(stack_identifier, templ_other_gw_ip) new_gw_ip = self.get_outputs(stack_identifier, 'gateway_ip') # the gateway_ip should be the new one self.assertEqual('11.11.11.9', new_gw_ip) def test_update_gateway_ip_to_empty(self): stack_identifier = self.stack_create(template=test_template) gw_ip = self.get_outputs(stack_identifier, 'gateway_ip') self.assertEqual('11.11.11.5', gw_ip) # Update gateway_ip to null(resolve to '') templ_empty_gw_ip = test_template.replace( 'gateway_ip: 11.11.11.5', 'gateway_ip: null') self.update_stack(stack_identifier, templ_empty_gw_ip) new_gw_ip = self.get_outputs(stack_identifier, 'gateway_ip') # new gateway_ip should be None self.assertIsNone(new_gw_ip) def test_update_to_no_gateway_ip(self): stack_identifier = self.stack_create(template=test_template) gw_ip = self.get_outputs(stack_identifier, 'gateway_ip') self.assertEqual('11.11.11.5', gw_ip) # Remove the gateway from template templ_no_gw_ip = test_template.replace( 'gateway_ip: 11.11.11.5', '') self.update_stack(stack_identifier, templ_no_gw_ip) new_gw_ip = self.get_outputs(stack_identifier, 'gateway_ip') # new gateway_ip should be None self.assertIsNone(new_gw_ip) heat-6.0.0/heat_integrationtests/functional/test_create_update_neutron_port.py0000664000567000056710000001424212701407050031403 0ustar jenkinsjenkins00000000000000# 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 heat_integrationtests.functional import functional_base test_template = ''' heat_template_version: 2015-04-30 description: Test template to create port wit ip_address. parameters: mac: type: string default: 00-00-00-00-BB-BB resources: net: type: OS::Neutron::Net subnet: type: OS::Neutron::Subnet properties: network: { get_resource: net } cidr: 11.11.11.0/24 port: type: OS::Neutron::Port properties: network: {get_resource: net} mac_address: {get_param: mac} fixed_ips: - subnet: {get_resource: subnet} ip_address: 11.11.11.11 test: depends_on: port type: OS::Heat::TestResource properties: value: Test1 fail: False outputs: port_ip: value: {get_attr: [port, fixed_ips, 0, ip_address]} ''' class UpdatePortTest(functional_base.FunctionalTestsBase): def setUp(self): super(UpdatePortTest, self).setUp() def get_port_id_and_ip(self, stack_identifier): resources = self.client.resources.list(stack_identifier) port_id = [res.physical_resource_id for res in resources if res.resource_name == 'port'] stack = self.client.stacks.get(stack_identifier) port_ip = self._stack_output(stack, 'port_ip') return port_id[0], port_ip def test_stack_update_replace_no_ip(self): templ_no_ip = test_template.replace('ip_address: 11.11.11.11', '') # create with default 'mac' parameter stack_identifier = self.stack_create(template=templ_no_ip) _id, _ip = self.get_port_id_and_ip(stack_identifier) # Update with another 'mac' parameter parameters = {'mac': '00-00-00-00-AA-AA'} self.update_stack(stack_identifier, templ_no_ip, parameters=parameters) new_id, new_ip = self.get_port_id_and_ip(stack_identifier) # port id and ip should be different self.assertNotEqual(_ip, new_ip) self.assertNotEqual(_id, new_id) def test_stack_update_replace_with_ip(self): # create with default 'mac' parameter stack_identifier = self.stack_create(template=test_template) _id, _ip = self.get_port_id_and_ip(stack_identifier) # Update with another 'mac' parameter parameters = {'mac': '00-00-00-00-AA-AA'} # port should be replaced with same ip self.update_stack(stack_identifier, test_template, parameters=parameters) new_id, new_ip = self.get_port_id_and_ip(stack_identifier) # port id should be different, ip should be the same self.assertEqual(_ip, new_ip) self.assertNotEqual(_id, new_id) def test_stack_update_replace_with_ip_rollback(self): # create with default 'mac' parameter stack_identifier = self.stack_create(template=test_template) _id, _ip = self.get_port_id_and_ip(stack_identifier) # Update with another 'mac' parameter parameters = {'mac': '00-00-00-00-AA-AA'} # make test resource failing during update fail_template = test_template.replace('fail: False', 'fail: True') fail_template = fail_template.replace('value: Test1', 'value: Rollback') # port should be replaced with same ip self.update_stack(stack_identifier, fail_template, parameters=parameters, expected_status='ROLLBACK_COMPLETE', disable_rollback=False) new_id, new_ip = self.get_port_id_and_ip(stack_identifier) # port id and ip should be the same after rollback self.assertEqual(_ip, new_ip) self.assertEqual(_id, new_id) def test_stack_update_replace_with_ip_after_failed_update(self): # create with default 'mac' parameter stack_identifier = self.stack_create(template=test_template) _id, _ip = self.get_port_id_and_ip(stack_identifier) # Update with another 'mac' parameter parameters = {'mac': '00-00-00-00-AA-AA'} # make test resource failing during update fail_template = test_template.replace('fail: False', 'fail: True') fail_template = fail_template.replace('value: Test1', 'value: Rollback') # port should be replaced with same ip self.update_stack(stack_identifier, fail_template, parameters=parameters, expected_status='UPDATE_FAILED') # port should be replaced with same ip self.update_stack(stack_identifier, test_template, parameters=parameters) new_id, new_ip = self.get_port_id_and_ip(stack_identifier) # ip should be the same, but port id should be different, because it's # restore replace self.assertEqual(_ip, new_ip) self.assertNotEqual(_id, new_id) def test_stack_update_in_place_remove_ip(self): # create with default 'mac' parameter and defined ip_address stack_identifier = self.stack_create(template=test_template) _id, _ip = self.get_port_id_and_ip(stack_identifier) # remove ip_address property and update stack templ_no_ip = test_template.replace('ip_address: 11.11.11.11', '') self.update_stack(stack_identifier, templ_no_ip) new_id, new_ip = self.get_port_id_and_ip(stack_identifier) # port should be updated with the same id, but different ip self.assertNotEqual(_ip, new_ip) self.assertEqual(_id, new_id) heat-6.0.0/heat_integrationtests/functional/test_notifications.py0000664000567000056710000001436712701407050026641 0ustar jenkinsjenkins00000000000000# 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 kombu from oslo_config import cfg from oslo_messaging._drivers import common from oslo_messaging import transport import requests from heat_integrationtests.common import test from heat_integrationtests.functional import functional_base BASIC_NOTIFICATIONS = [ 'orchestration.stack.create.start', 'orchestration.stack.create.end', 'orchestration.stack.update.start', 'orchestration.stack.update.end', 'orchestration.stack.suspend.start', 'orchestration.stack.suspend.end', 'orchestration.stack.resume.start', 'orchestration.stack.resume.end', 'orchestration.stack.delete.start', 'orchestration.stack.delete.end' ] ASG_NOTIFICATIONS = [ 'orchestration.autoscaling.start', 'orchestration.autoscaling.end' ] def get_url(conf): conf = conf.oslo_messaging_rabbit return 'amqp://%s:%s@%s:%s/' % (conf.rabbit_userid, conf.rabbit_password, conf.rabbit_host, conf.rabbit_port) class NotificationHandler(object): def __init__(self, stack_id, events=None): self._notifications = [] self.stack_id = stack_id self.events = events def process_message(self, body, message): notification = common.deserialize_msg(body) if notification['payload']['stack_name'] == self.stack_id: if self.events is not None: if notification['event_type'] in self.events: self.notifications.append(notification['event_type']) else: self.notifications.append(notification['event_type']) message.ack() def clear(self): self._notifications = [] @property def notifications(self): return self._notifications class NotificationTest(functional_base.FunctionalTestsBase): basic_template = ''' heat_template_version: 2013-05-23 resources: random1: type: OS::Heat::RandomString ''' update_basic_template = ''' heat_template_version: 2013-05-23 resources: random1: type: OS::Heat::RandomString random2: type: OS::Heat::RandomString ''' asg_template = ''' heat_template_version: 2013-05-23 resources: asg: type: OS::Heat::AutoScalingGroup properties: resource: type: OS::Heat::RandomString min_size: 1 desired_capacity: 2 max_size: 3 scale_up_policy: type: OS::Heat::ScalingPolicy properties: adjustment_type: change_in_capacity auto_scaling_group_id: {get_resource: asg} cooldown: 0 scaling_adjustment: 1 scale_down_policy: type: OS::Heat::ScalingPolicy properties: adjustment_type: change_in_capacity auto_scaling_group_id: {get_resource: asg} cooldown: 0 scaling_adjustment: '-1' outputs: scale_up_url: value: {get_attr: [scale_up_policy, alarm_url]} scale_dn_url: value: {get_attr: [scale_down_policy, alarm_url]} ''' def setUp(self): super(NotificationTest, self).setUp() self.exchange = kombu.Exchange('heat', 'topic', durable=False) queue = kombu.Queue(exchange=self.exchange, routing_key='notifications.info', exclusive=True) self.conn = kombu.Connection(get_url( transport.get_transport(cfg.CONF).conf)) self.ch = self.conn.channel() self.queue = queue(self.ch) self.queue.declare() def consume_events(self, handler, count): self.conn.drain_events() return len(handler.notifications) == count def test_basic_notifications(self): # disable cleanup so we can call _stack_delete() directly. stack_identifier = self.stack_create(template=self.basic_template, enable_cleanup=False) self.update_stack(stack_identifier, template=self.update_basic_template) self.stack_suspend(stack_identifier) self.stack_resume(stack_identifier) self._stack_delete(stack_identifier) handler = NotificationHandler(stack_identifier.split('/')[0]) with self.conn.Consumer(self.queue, callbacks=[handler.process_message], auto_declare=False): try: while True: self.conn.drain_events(timeout=1) except Exception: pass for n in BASIC_NOTIFICATIONS: self.assertIn(n, handler.notifications) def test_asg_notifications(self): stack_identifier = self.stack_create(template=self.asg_template) for output in self.client.stacks.get(stack_identifier).outputs: if output['output_key'] == 'scale_dn_url': scale_down_url = output['output_value'] else: scale_up_url = output['output_value'] notifications = [] handler = NotificationHandler(stack_identifier.split('/')[0], ASG_NOTIFICATIONS) with self.conn.Consumer(self.queue, callbacks=[handler.process_message], auto_declare=False): requests.post(scale_up_url, verify=self.verify_cert) test.call_until_true(20, 0, self.consume_events, handler, 2) notifications += handler.notifications handler.clear() requests.post(scale_down_url, verify=self.verify_cert) test.call_until_true(20, 0, self.consume_events, handler, 2) notifications += handler.notifications self.assertEqual(2, notifications.count(ASG_NOTIFICATIONS[0])) self.assertEqual(2, notifications.count(ASG_NOTIFICATIONS[1])) heat-6.0.0/heat_integrationtests/functional/test_purge.py0000664000567000056710000000361612701407050025105 0ustar jenkinsjenkins00000000000000# 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 oslo_concurrency import processutils from heat_integrationtests.functional import functional_base class PurgeTest(functional_base.FunctionalTestsBase): template = ''' heat_template_version: 2014-10-16 parameters: resources: test_resource: type: OS::Heat::TestResource ''' def test_purge(self): stack_identifier = self.stack_create(template=self.template) self._stack_delete(stack_identifier) stacks = dict((stack.id, stack) for stack in self.client.stacks.list(show_deleted=True)) self.assertIn(stack_identifier.split('/')[1], stacks) cmd = "heat-manage purge_deleted 0" processutils.execute(cmd, shell=True) stacks = dict((stack.id, stack) for stack in self.client.stacks.list(show_deleted=True)) self.assertNotIn(stack_identifier.split('/')[1], stacks) # Test with tags stack_identifier = self.stack_create(template=self.template, tags="foo,bar") self._stack_delete(stack_identifier) cmd = "heat-manage purge_deleted 0" processutils.execute(cmd, shell=True) stacks = dict((stack.id, stack) for stack in self.client.stacks.list(show_deleted=True)) self.assertNotIn(stack_identifier.split('/')[1], stacks) heat-6.0.0/heat_integrationtests/functional/test_swiftsignal_update.py0000664000567000056710000000326212701407050027654 0ustar jenkinsjenkins00000000000000# 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 heat_integrationtests.functional import functional_base test_template = ''' heat_template_version: 2014-10-16 resources: signal_handle: type: "OS::Heat::SwiftSignalHandle" outputs: signal_curl: value: { get_attr: ['signal_handle', 'curl_cli'] } description: Swift signal cURL signal_url: value: { get_attr: ['signal_handle', 'endpoint'] } description: Swift signal URL ''' class SwiftSignalHandleUpdateTest(functional_base.FunctionalTestsBase): def setUp(self): super(SwiftSignalHandleUpdateTest, self).setUp() def test_stack_update_same_template_replace_no_url(self): stack_identifier = self.stack_create(template=test_template) stack = self.client.stacks.get(stack_identifier) orig_url = self._stack_output(stack, 'signal_url') orig_curl = self._stack_output(stack, 'signal_curl') self.update_stack(stack_identifier, test_template) stack = self.client.stacks.get(stack_identifier) self.assertEqual(orig_url, self._stack_output(stack, 'signal_url')) self.assertEqual(orig_curl, self._stack_output(stack, 'signal_curl')) heat-6.0.0/heat_integrationtests/functional/test_template_validate.py0000664000567000056710000002406312701407050027446 0ustar jenkinsjenkins00000000000000# 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 six from heatclient import exc from heat_integrationtests.functional import functional_base class StackTemplateValidateTest(functional_base.FunctionalTestsBase): random_template = ''' heat_template_version: 2014-10-16 description: the stack description parameters: aparam: type: number default: 10 description: the param description resources: myres: type: OS::Heat::RandomString properties: length: {get_param: aparam} ''' parent_template = ''' heat_template_version: 2014-10-16 description: the parent template parameters: pparam: type: number default: 5 description: the param description resources: nres: type: mynested.yaml properties: aparam: {get_param: pparam} ''' parent_template_noprop = ''' heat_template_version: 2014-10-16 description: the parent template resources: nres: type: mynested.yaml ''' random_template_groups = ''' heat_template_version: 2014-10-16 description: the stack description parameters: aparam: type: number default: 10 description: the param description bparam: type: string default: foo cparam: type: string default: secret hidden: true parameter_groups: - label: str_params description: The string params parameters: - bparam - cparam resources: myres: type: OS::Heat::RandomString properties: length: {get_param: aparam} ''' def test_template_validate_basic(self): ret = self.client.stacks.validate(template=self.random_template) expected = {'Description': 'the stack description', 'Parameters': { 'aparam': {'Default': 10, 'Description': 'the param description', 'Label': 'aparam', 'NoEcho': 'false', 'Type': 'Number'}}} self.assertEqual(expected, ret) def test_template_validate_override_default(self): env = {'parameters': {'aparam': 5}} ret = self.client.stacks.validate(template=self.random_template, environment=env) expected = {'Description': 'the stack description', 'Parameters': { 'aparam': {'Default': 10, 'Value': 5, 'Description': 'the param description', 'Label': 'aparam', 'NoEcho': 'false', 'Type': 'Number'}}} self.assertEqual(expected, ret) def test_template_validate_override_none(self): env = {'resource_registry': { 'OS::Heat::RandomString': 'OS::Heat::None'}} ret = self.client.stacks.validate(template=self.random_template, environment=env) expected = {'Description': 'the stack description', 'Parameters': { 'aparam': {'Default': 10, 'Description': 'the param description', 'Label': 'aparam', 'NoEcho': 'false', 'Type': 'Number'}}} self.assertEqual(expected, ret) def test_template_validate_basic_required_param(self): tmpl = self.random_template.replace('default: 10', '') ret = self.client.stacks.validate(template=tmpl) expected = {'Description': 'the stack description', 'Parameters': { 'aparam': {'Description': 'the param description', 'Label': 'aparam', 'NoEcho': 'false', 'Type': 'Number'}}} self.assertEqual(expected, ret) def test_template_validate_fail_version(self): fail_template = self.random_template.replace('2014-10-16', 'invalid') ex = self.assertRaises(exc.HTTPBadRequest, self.client.stacks.validate, template=fail_template) self.assertIn('The template version is invalid', six.text_type(ex)) def test_template_validate_parameter_groups(self): ret = self.client.stacks.validate(template=self.random_template_groups) expected = {'Description': 'the stack description', 'ParameterGroups': [{'description': 'The string params', 'label': 'str_params', 'parameters': ['bparam', 'cparam']}], 'Parameters': {'aparam': {'Default': 10, 'Description': 'the param description', 'Label': 'aparam', 'NoEcho': 'false', 'Type': 'Number'}, 'bparam': {'Default': 'foo', 'Description': '', 'Label': 'bparam', 'NoEcho': 'false', 'Type': 'String'}, 'cparam': {'Default': 'secret', 'Description': '', 'Label': 'cparam', 'NoEcho': 'true', 'Type': 'String'}}} self.assertEqual(expected, ret) def test_template_validate_nested_off(self): files = {'mynested.yaml': self.random_template} ret = self.client.stacks.validate(template=self.parent_template, files=files) expected = {'Description': 'the parent template', 'Parameters': { 'pparam': {'Default': 5, 'Description': 'the param description', 'Label': 'pparam', 'NoEcho': 'false', 'Type': 'Number'}}} self.assertEqual(expected, ret) def test_template_validate_nested_on(self): files = {'mynested.yaml': self.random_template} ret = self.client.stacks.validate(template=self.parent_template_noprop, files=files, show_nested=True) expected = {'Description': 'the parent template', 'Parameters': {}, 'NestedParameters': { 'nres': {'Description': 'the stack description', 'Parameters': {'aparam': {'Default': 10, 'Description': 'the param ' 'description', 'Label': 'aparam', 'NoEcho': 'false', 'Type': 'Number'}}, 'Type': 'mynested.yaml'}}} self.assertEqual(expected, ret) def test_template_validate_nested_on_multiple(self): # parent_template -> nested_template -> random_template nested_template = self.random_template.replace( 'OS::Heat::RandomString', 'mynested2.yaml') files = {'mynested.yaml': nested_template, 'mynested2.yaml': self.random_template} ret = self.client.stacks.validate(template=self.parent_template, files=files, show_nested=True) n_param2 = {'myres': {'Description': 'the stack description', 'Parameters': {'aparam': {'Default': 10, 'Description': 'the param ' 'description', 'Label': 'aparam', 'NoEcho': 'false', 'Type': 'Number'}}, 'Type': 'mynested2.yaml'}} expected = {'Description': 'the parent template', 'Parameters': { 'pparam': {'Default': 5, 'Description': 'the param description', 'Label': 'pparam', 'NoEcho': 'false', 'Type': 'Number'}}, 'NestedParameters': { 'nres': {'Description': 'the stack description', 'Parameters': {'aparam': {'Default': 10, 'Description': 'the param ' 'description', 'Label': 'aparam', 'Value': 5, 'NoEcho': 'false', 'Type': 'Number'}}, 'NestedParameters': n_param2, 'Type': 'mynested.yaml'}}} self.assertEqual(expected, ret) heat-6.0.0/heat_integrationtests/functional/test_resource_chain.py0000664000567000056710000001155712701407050026757 0ustar jenkinsjenkins00000000000000# # 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 heat_integrationtests.functional import functional_base TEMPLATE_SIMPLE = ''' heat_template_version: 2016-04-08 parameters: string-length: type: number resources: my-chain: type: OS::Heat::ResourceChain properties: resources: ['OS::Heat::RandomString', 'OS::Heat::RandomString'] resource_properties: length: { get_param: string-length } outputs: resource-ids: value: { get_attr: [my-chain, refs] } resource-0-value: value: { get_attr: [my-chain, resource.0, value] } all-resource-attrs: value: { get_attr: [my-chain, attributes, value] } ''' TEMPLATE_PARAM_DRIVEN = ''' heat_template_version: 2016-04-08 parameters: chain-types: type: comma_delimited_list resources: my-chain: type: OS::Heat::ResourceChain properties: resources: { get_param: chain-types } ''' class ResourceChainTests(functional_base.FunctionalTestsBase): def test_create(self): # Test params = {'string-length': 8} stack_id = self.stack_create(template=TEMPLATE_SIMPLE, parameters=params) # Verify stack = self.client.stacks.get(stack_id) self.assertTrue(stack is not None) # Top-level resource for chain expected = {'my-chain': 'OS::Heat::ResourceChain'} found = self.list_resources(stack_id) self.assertEqual(expected, found) # Nested stack exists and has two resources nested_id = self.group_nested_identifier(stack_id, 'my-chain') expected = {'0': 'OS::Heat::RandomString', '1': 'OS::Heat::RandomString'} found = self.list_resources(nested_id) self.assertEqual(expected, found) # Outputs resource_ids = self._stack_output(stack, 'resource-ids') self.assertTrue(resource_ids is not None) self.assertEqual(2, len(resource_ids)) resource_value = self._stack_output(stack, 'resource-0-value') self.assertTrue(resource_value is not None) self.assertEqual(8, len(resource_value)) # from parameter resource_attrs = self._stack_output(stack, 'all-resource-attrs') self.assertTrue(resource_attrs is not None) self.assertIsInstance(resource_attrs, dict) self.assertEqual(2, len(resource_attrs)) self.assertEqual(8, len(resource_attrs['0'])) self.assertEqual(8, len(resource_attrs['1'])) def test_update(self): # Setup params = {'string-length': 8} stack_id = self.stack_create(template=TEMPLATE_SIMPLE, parameters=params) update_tmpl = ''' heat_template_version: 2016-04-08 parameters: string-length: type: number resources: my-chain: type: OS::Heat::ResourceChain properties: resources: ['OS::Heat::None'] ''' # Test self.update_stack(stack_id, template=update_tmpl, parameters=params) # Verify # Nested stack only has the None resource nested_id = self.group_nested_identifier(stack_id, 'my-chain') expected = {'0': 'OS::Heat::None'} found = self.list_resources(nested_id) self.assertEqual(expected, found) def test_resources_param_driven(self): # Setup params = {'chain-types': 'OS::Heat::None,OS::Heat::RandomString,OS::Heat::None'} # Test stack_id = self.stack_create(template=TEMPLATE_PARAM_DRIVEN, parameters=params) # Verify nested_id = self.group_nested_identifier(stack_id, 'my-chain') expected = {'0': 'OS::Heat::None', '1': 'OS::Heat::RandomString', '2': 'OS::Heat::None'} found = self.list_resources(nested_id) self.assertEqual(expected, found) def test_resources_env_defined(self): # Setup env = {'parameters': {'chain-types': 'OS::Heat::None'}} # Test stack_id = self.stack_create(template=TEMPLATE_PARAM_DRIVEN, environment=env) # Verify nested_id = self.group_nested_identifier(stack_id, 'my-chain') expected = {'0': 'OS::Heat::None'} found = self.list_resources(nested_id) self.assertEqual(expected, found) heat-6.0.0/heat_integrationtests/functional/test_autoscaling.py0000664000567000056710000007551312701407050026301 0ustar jenkinsjenkins00000000000000# 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 copy import json from heatclient import exc from oslo_log import log as logging import six from testtools import matchers from heat_integrationtests.common import test from heat_integrationtests.functional import functional_base LOG = logging.getLogger(__name__) class AutoscalingGroupTest(functional_base.FunctionalTestsBase): template = ''' { "AWSTemplateFormatVersion" : "2010-09-09", "Description" : "Template to create multiple instances.", "Parameters" : {"size": {"Type": "String", "Default": "1"}, "AZ": {"Type": "String", "Default": "nova"}, "image": {"Type": "String"}, "flavor": {"Type": "String"}}, "Resources": { "JobServerGroup": { "Type" : "AWS::AutoScaling::AutoScalingGroup", "Properties" : { "AvailabilityZones" : [{"Ref": "AZ"}], "LaunchConfigurationName" : { "Ref" : "JobServerConfig" }, "MinSize" : {"Ref": "size"}, "MaxSize" : "20" } }, "JobServerConfig" : { "Type" : "AWS::AutoScaling::LaunchConfiguration", "Metadata": {"foo": "bar"}, "Properties": { "ImageId" : {"Ref": "image"}, "InstanceType" : {"Ref": "flavor"}, "SecurityGroups" : [ "sg-1" ], "UserData" : "jsconfig data" } } }, "Outputs": { "InstanceList": {"Value": { "Fn::GetAtt": ["JobServerGroup", "InstanceList"]}}, "JobServerConfigRef": {"Value": { "Ref": "JobServerConfig"}} } } ''' instance_template = ''' heat_template_version: 2013-05-23 parameters: ImageId: {type: string} InstanceType: {type: string} SecurityGroups: {type: comma_delimited_list} UserData: {type: string} Tags: {type: comma_delimited_list, default: "x,y"} resources: random1: type: OS::Heat::RandomString properties: salt: {get_param: ImageId} outputs: PublicIp: {value: {get_attr: [random1, value]}} AvailabilityZone: {value: 'not-used11'} PrivateDnsName: {value: 'not-used12'} PublicDnsName: {value: 'not-used13'} PrivateIp: {value: 'not-used14'} ''' # This is designed to fail. bad_instance_template = ''' heat_template_version: 2013-05-23 parameters: ImageId: {type: string} InstanceType: {type: string} SecurityGroups: {type: comma_delimited_list} UserData: {type: string} Tags: {type: comma_delimited_list, default: "x,y"} resources: random1: type: OS::Heat::RandomString depends_on: waiter ready_poster: type: AWS::CloudFormation::WaitConditionHandle waiter: type: AWS::CloudFormation::WaitCondition properties: Handle: {get_resource: ready_poster} Timeout: 1 outputs: PublicIp: value: {get_attr: [random1, value]} ''' def setUp(self): super(AutoscalingGroupTest, self).setUp() if not self.conf.image_ref: raise self.skipException("No image configured to test") if not self.conf.minimal_image_ref: raise self.skipException("No minimal image configured to test") if not self.conf.instance_type: raise self.skipException("No flavor configured to test") def assert_instance_count(self, stack, expected_count): inst_list = self._stack_output(stack, 'InstanceList') self.assertEqual(expected_count, len(inst_list.split(','))) def _assert_instance_state(self, nested_identifier, num_complete, num_failed): for res in self.client.resources.list(nested_identifier): if 'COMPLETE' in res.resource_status: num_complete = num_complete - 1 elif 'FAILED' in res.resource_status: num_failed = num_failed - 1 self.assertEqual(0, num_failed) self.assertEqual(0, num_complete) class AutoscalingGroupBasicTest(AutoscalingGroupTest): def test_basic_create_works(self): """Make sure the working case is good. Note this combines test_override_aws_ec2_instance into this test as well, which is: If AWS::EC2::Instance is overridden, AutoScalingGroup will automatically use that overridden resource type. """ files = {'provider.yaml': self.instance_template} env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'}, 'parameters': {'size': 4, 'image': self.conf.image_ref, 'flavor': self.conf.instance_type}} stack_identifier = self.stack_create(template=self.template, files=files, environment=env) initial_resources = { 'JobServerConfig': 'AWS::AutoScaling::LaunchConfiguration', 'JobServerGroup': 'AWS::AutoScaling::AutoScalingGroup'} self.assertEqual(initial_resources, self.list_resources(stack_identifier)) stack = self.client.stacks.get(stack_identifier) self.assert_instance_count(stack, 4) def test_size_updates_work(self): files = {'provider.yaml': self.instance_template} env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'}, 'parameters': {'size': 2, 'image': self.conf.image_ref, 'flavor': self.conf.instance_type}} stack_identifier = self.stack_create(template=self.template, files=files, environment=env) stack = self.client.stacks.get(stack_identifier) self.assert_instance_count(stack, 2) # Increase min size to 5 env2 = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'}, 'parameters': {'size': 5, 'image': self.conf.image_ref, 'flavor': self.conf.instance_type}} self.update_stack(stack_identifier, self.template, environment=env2, files=files) stack = self.client.stacks.get(stack_identifier) self.assert_instance_count(stack, 5) def test_update_group_replace(self): """Test case for ensuring non-updatable props case a replacement. Make sure that during a group update the non-updatable properties cause a replacement. """ files = {'provider.yaml': self.instance_template} env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'}, 'parameters': {'size': '1', 'image': self.conf.image_ref, 'flavor': self.conf.instance_type}} stack_identifier = self.stack_create(template=self.template, files=files, environment=env) rsrc = self.client.resources.get(stack_identifier, 'JobServerGroup') orig_asg_id = rsrc.physical_resource_id env2 = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'}, 'parameters': {'size': '1', 'AZ': 'wibble', 'image': self.conf.image_ref, 'flavor': self.conf.instance_type}} self.update_stack(stack_identifier, self.template, environment=env2, files=files) # replacement will cause the resource physical_resource_id to change. rsrc = self.client.resources.get(stack_identifier, 'JobServerGroup') self.assertNotEqual(orig_asg_id, rsrc.physical_resource_id) def test_create_instance_error_causes_group_error(self): """Test create failing a resource in the instance group. If a resource in an instance group fails to be created, the instance group itself will fail and the broken inner resource will remain. """ stack_name = self._stack_rand_name() files = {'provider.yaml': self.bad_instance_template} env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'}, 'parameters': {'size': 2, 'image': self.conf.image_ref, 'flavor': self.conf.instance_type}} self.client.stacks.create( stack_name=stack_name, template=self.template, files=files, disable_rollback=True, parameters={}, environment=env ) self.addCleanup(self._stack_delete, stack_name) stack = self.client.stacks.get(stack_name) stack_identifier = '%s/%s' % (stack_name, stack.id) self._wait_for_stack_status(stack_identifier, 'CREATE_FAILED') initial_resources = { 'JobServerConfig': 'AWS::AutoScaling::LaunchConfiguration', 'JobServerGroup': 'AWS::AutoScaling::AutoScalingGroup'} self.assertEqual(initial_resources, self.list_resources(stack_identifier)) nested_ident = self.assert_resource_is_a_stack(stack_identifier, 'JobServerGroup') self._assert_instance_state(nested_ident, 0, 2) def test_update_instance_error_causes_group_error(self): """Test update failing a resource in the instance group. If a resource in an instance group fails to be created during an update, the instance group itself will fail and the broken inner resource will remain. """ files = {'provider.yaml': self.instance_template} env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'}, 'parameters': {'size': 2, 'image': self.conf.image_ref, 'flavor': self.conf.instance_type}} stack_identifier = self.stack_create(template=self.template, files=files, environment=env) initial_resources = { 'JobServerConfig': 'AWS::AutoScaling::LaunchConfiguration', 'JobServerGroup': 'AWS::AutoScaling::AutoScalingGroup'} self.assertEqual(initial_resources, self.list_resources(stack_identifier)) stack = self.client.stacks.get(stack_identifier) self.assert_instance_count(stack, 2) nested_ident = self.assert_resource_is_a_stack(stack_identifier, 'JobServerGroup') self._assert_instance_state(nested_ident, 2, 0) initial_list = [res.resource_name for res in self.client.resources.list(nested_ident)] env['parameters']['size'] = 3 files2 = {'provider.yaml': self.bad_instance_template} self.client.stacks.update( stack_id=stack_identifier, template=self.template, files=files2, disable_rollback=True, parameters={}, environment=env ) self._wait_for_stack_status(stack_identifier, 'UPDATE_FAILED') # assert that there are 3 bad instances nested_ident = self.assert_resource_is_a_stack(stack_identifier, 'JobServerGroup') # 2 resources should be in update failed, and one create failed. for res in self.client.resources.list(nested_ident): if res.resource_name in initial_list: self._wait_for_resource_status(nested_ident, res.resource_name, 'UPDATE_FAILED') else: self._wait_for_resource_status(nested_ident, res.resource_name, 'CREATE_FAILED') def test_group_suspend_resume(self): files = {'provider.yaml': self.instance_template} env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'}, 'parameters': {'size': 4, 'image': self.conf.image_ref, 'flavor': self.conf.instance_type}} stack_identifier = self.stack_create(template=self.template, files=files, environment=env) nested_ident = self.assert_resource_is_a_stack(stack_identifier, 'JobServerGroup') self.stack_suspend(stack_identifier) self._wait_for_all_resource_status(nested_ident, 'SUSPEND_COMPLETE') self.stack_resume(stack_identifier) self._wait_for_all_resource_status(nested_ident, 'RESUME_COMPLETE') class AutoscalingGroupUpdatePolicyTest(AutoscalingGroupTest): def ig_tmpl_with_updt_policy(self): templ = json.loads(copy.deepcopy(self.template)) up = {"AutoScalingRollingUpdate": { "MinInstancesInService": "1", "MaxBatchSize": "2", "PauseTime": "PT1S"}} templ['Resources']['JobServerGroup']['UpdatePolicy'] = up return templ def update_instance_group(self, updt_template, num_updates_expected_on_updt, num_creates_expected_on_updt, num_deletes_expected_on_updt): # setup stack from the initial template files = {'provider.yaml': self.instance_template} size = 10 env = {'resource_registry': {'AWS::EC2::Instance': 'provider.yaml'}, 'parameters': {'size': size, 'image': self.conf.image_ref, 'flavor': self.conf.instance_type}} stack_name = self._stack_rand_name() stack_identifier = self.stack_create( stack_name=stack_name, template=self.ig_tmpl_with_updt_policy(), files=files, environment=env) stack = self.client.stacks.get(stack_identifier) nested_ident = self.assert_resource_is_a_stack(stack_identifier, 'JobServerGroup') # test that physical resource name of launch configuration is used conf_name = self._stack_output(stack, 'JobServerConfigRef') conf_name_pattern = '%s-JobServerConfig-[a-zA-Z0-9]+$' % stack_name self.assertThat(conf_name, matchers.MatchesRegex(conf_name_pattern)) # test the number of instances created self.assert_instance_count(stack, size) # saves info from initial list of instances for comparison later init_instances = self.client.resources.list(nested_ident) init_names = [inst.resource_name for inst in init_instances] # test stack update self.update_stack(stack_identifier, updt_template, environment=env, files=files) updt_stack = self.client.stacks.get(stack_identifier) # test that the launch configuration is replaced updt_conf_name = self._stack_output(updt_stack, 'JobServerConfigRef') self.assertThat(updt_conf_name, matchers.MatchesRegex(conf_name_pattern)) self.assertNotEqual(conf_name, updt_conf_name) # test that the group size are the same updt_instances = self.client.resources.list(nested_ident) updt_names = [inst.resource_name for inst in updt_instances] self.assertEqual(len(init_names), len(updt_names)) for res in updt_instances: self.assertEqual('UPDATE_COMPLETE', res.resource_status) # test that the appropriate number of instance names are the same matched_names = set(updt_names) & set(init_names) self.assertEqual(num_updates_expected_on_updt, len(matched_names)) # test that the appropriate number of new instances are created self.assertEqual(num_creates_expected_on_updt, len(set(updt_names) - set(init_names))) # test that the appropriate number of instances are deleted self.assertEqual(num_deletes_expected_on_updt, len(set(init_names) - set(updt_names))) # test that the older instances are the ones being deleted if num_deletes_expected_on_updt > 0: deletes_expected = init_names[:num_deletes_expected_on_updt] self.assertNotIn(deletes_expected, updt_names) def test_instance_group_update_replace(self): """Test simple update replace. Test update replace with no conflict in batch size and minimum instances in service. """ updt_template = self.ig_tmpl_with_updt_policy() grp = updt_template['Resources']['JobServerGroup'] policy = grp['UpdatePolicy']['AutoScalingRollingUpdate'] policy['MinInstancesInService'] = '1' policy['MaxBatchSize'] = '3' config = updt_template['Resources']['JobServerConfig'] config['Properties']['ImageId'] = self.conf.minimal_image_ref self.update_instance_group(updt_template, num_updates_expected_on_updt=10, num_creates_expected_on_updt=0, num_deletes_expected_on_updt=0) def test_instance_group_update_replace_with_adjusted_capacity(self): """Test update replace with capacity adjustment. Test update replace with capacity adjustment due to conflict in batch size and minimum instances in service. """ updt_template = self.ig_tmpl_with_updt_policy() grp = updt_template['Resources']['JobServerGroup'] policy = grp['UpdatePolicy']['AutoScalingRollingUpdate'] policy['MinInstancesInService'] = '8' policy['MaxBatchSize'] = '4' config = updt_template['Resources']['JobServerConfig'] config['Properties']['ImageId'] = self.conf.minimal_image_ref self.update_instance_group(updt_template, num_updates_expected_on_updt=8, num_creates_expected_on_updt=2, num_deletes_expected_on_updt=2) def test_instance_group_update_replace_huge_batch_size(self): """Test update replace with a huge batch size.""" updt_template = self.ig_tmpl_with_updt_policy() group = updt_template['Resources']['JobServerGroup'] policy = group['UpdatePolicy']['AutoScalingRollingUpdate'] policy['MinInstancesInService'] = '0' policy['MaxBatchSize'] = '20' config = updt_template['Resources']['JobServerConfig'] config['Properties']['ImageId'] = self.conf.minimal_image_ref self.update_instance_group(updt_template, num_updates_expected_on_updt=10, num_creates_expected_on_updt=0, num_deletes_expected_on_updt=0) def test_instance_group_update_replace_huge_min_in_service(self): """Update replace with huge number of minimum instances in service.""" updt_template = self.ig_tmpl_with_updt_policy() group = updt_template['Resources']['JobServerGroup'] policy = group['UpdatePolicy']['AutoScalingRollingUpdate'] policy['MinInstancesInService'] = '20' policy['MaxBatchSize'] = '1' policy['PauseTime'] = 'PT0S' config = updt_template['Resources']['JobServerConfig'] config['Properties']['ImageId'] = self.conf.minimal_image_ref self.update_instance_group(updt_template, num_updates_expected_on_updt=9, num_creates_expected_on_updt=1, num_deletes_expected_on_updt=1) def test_instance_group_update_no_replace(self): """Test simple update only and no replace. Test simple update only and no replace (i.e. updated instance flavor in Launch Configuration) with no conflict in batch size and minimum instances in service. """ updt_template = self.ig_tmpl_with_updt_policy() group = updt_template['Resources']['JobServerGroup'] policy = group['UpdatePolicy']['AutoScalingRollingUpdate'] policy['MinInstancesInService'] = '1' policy['MaxBatchSize'] = '3' policy['PauseTime'] = 'PT0S' config = updt_template['Resources']['JobServerConfig'] config['Properties']['InstanceType'] = 'm1.tiny' self.update_instance_group(updt_template, num_updates_expected_on_updt=10, num_creates_expected_on_updt=0, num_deletes_expected_on_updt=0) def test_instance_group_update_no_replace_with_adjusted_capacity(self): """Test update only and no replace with capacity adjustment. Test update only and no replace (i.e. updated instance flavor in Launch Configuration) with capacity adjustment due to conflict in batch size and minimum instances in service. """ updt_template = self.ig_tmpl_with_updt_policy() group = updt_template['Resources']['JobServerGroup'] policy = group['UpdatePolicy']['AutoScalingRollingUpdate'] policy['MinInstancesInService'] = '8' policy['MaxBatchSize'] = '4' policy['PauseTime'] = 'PT0S' config = updt_template['Resources']['JobServerConfig'] config['Properties']['InstanceType'] = 'm1.tiny' self.update_instance_group(updt_template, num_updates_expected_on_updt=8, num_creates_expected_on_updt=2, num_deletes_expected_on_updt=2) class AutoScalingSignalTest(AutoscalingGroupTest): template = ''' { "AWSTemplateFormatVersion" : "2010-09-09", "Description" : "Template to create multiple instances.", "Parameters" : {"size": {"Type": "String", "Default": "1"}, "AZ": {"Type": "String", "Default": "nova"}, "image": {"Type": "String"}, "flavor": {"Type": "String"}}, "Resources": { "custom_lb": { "Type": "AWS::EC2::Instance", "Properties": { "ImageId": {"Ref": "image"}, "InstanceType": {"Ref": "flavor"}, "UserData": "foo", "SecurityGroups": [ "sg-1" ], "Tags": [] }, "Metadata": { "IPs": {"Fn::GetAtt": ["JobServerGroup", "InstanceList"]} } }, "JobServerGroup": { "Type" : "AWS::AutoScaling::AutoScalingGroup", "Properties" : { "AvailabilityZones" : [{"Ref": "AZ"}], "LaunchConfigurationName" : { "Ref" : "JobServerConfig" }, "DesiredCapacity" : {"Ref": "size"}, "MinSize" : "0", "MaxSize" : "20" } }, "JobServerConfig" : { "Type" : "AWS::AutoScaling::LaunchConfiguration", "Metadata": {"foo": "bar"}, "Properties": { "ImageId" : {"Ref": "image"}, "InstanceType" : {"Ref": "flavor"}, "SecurityGroups" : [ "sg-1" ], "UserData" : "jsconfig data" } }, "ScaleUpPolicy" : { "Type" : "AWS::AutoScaling::ScalingPolicy", "Properties" : { "AdjustmentType" : "ChangeInCapacity", "AutoScalingGroupName" : { "Ref" : "JobServerGroup" }, "Cooldown" : "0", "ScalingAdjustment": "1" } }, "ScaleDownPolicy" : { "Type" : "AWS::AutoScaling::ScalingPolicy", "Properties" : { "AdjustmentType" : "ChangeInCapacity", "AutoScalingGroupName" : { "Ref" : "JobServerGroup" }, "Cooldown" : "0", "ScalingAdjustment" : "-2" } } }, "Outputs": { "InstanceList": {"Value": { "Fn::GetAtt": ["JobServerGroup", "InstanceList"]}} } } ''' lb_template = ''' heat_template_version: 2013-05-23 parameters: ImageId: {type: string} InstanceType: {type: string} SecurityGroups: {type: comma_delimited_list} UserData: {type: string} Tags: {type: comma_delimited_list, default: "x,y"} resources: outputs: PublicIp: {value: "not-used"} AvailabilityZone: {value: 'not-used1'} PrivateDnsName: {value: 'not-used2'} PublicDnsName: {value: 'not-used3'} PrivateIp: {value: 'not-used4'} ''' def setUp(self): super(AutoScalingSignalTest, self).setUp() self.build_timeout = self.conf.build_timeout self.build_interval = self.conf.build_interval self.files = {'provider.yaml': self.instance_template, 'lb.yaml': self.lb_template} self.env = {'resource_registry': {'resources': {'custom_lb': {'AWS::EC2::Instance': 'lb.yaml'}}, 'AWS::EC2::Instance': 'provider.yaml'}, 'parameters': {'size': 2, 'image': self.conf.image_ref, 'flavor': self.conf.instance_type}} def check_instance_count(self, stack_identifier, expected): md = self.client.resources.metadata(stack_identifier, 'custom_lb') actual_md = len(md['IPs'].split(',')) if actual_md != expected: LOG.warning('check_instance_count exp:%d, meta:%s' % (expected, md['IPs'])) return False stack = self.client.stacks.get(stack_identifier) inst_list = self._stack_output(stack, 'InstanceList') actual = len(inst_list.split(',')) if actual != expected: LOG.warning('check_instance_count exp:%d, act:%s' % (expected, inst_list)) return actual == expected def test_scaling_meta_update(self): """Use heatclient to signal the up and down policy. Then confirm that the metadata in the custom_lb is updated each time. """ stack_identifier = self.stack_create(template=self.template, files=self.files, environment=self.env) self.assertTrue(test.call_until_true( self.build_timeout, self.build_interval, self.check_instance_count, stack_identifier, 2)) nested_ident = self.assert_resource_is_a_stack(stack_identifier, 'JobServerGroup') # Scale up one, Trigger alarm self.client.resources.signal(stack_identifier, 'ScaleUpPolicy') self._wait_for_stack_status(nested_ident, 'UPDATE_COMPLETE') self.assertTrue(test.call_until_true( self.build_timeout, self.build_interval, self.check_instance_count, stack_identifier, 3)) # Scale down two, Trigger alarm self.client.resources.signal(stack_identifier, 'ScaleDownPolicy') self._wait_for_stack_status(nested_ident, 'UPDATE_COMPLETE') self.assertTrue(test.call_until_true( self.build_timeout, self.build_interval, self.check_instance_count, stack_identifier, 1)) def test_signal_with_policy_update(self): """Prove that an updated policy is used in the next signal.""" stack_identifier = self.stack_create(template=self.template, files=self.files, environment=self.env) self.assertTrue(test.call_until_true( self.build_timeout, self.build_interval, self.check_instance_count, stack_identifier, 2)) nested_ident = self.assert_resource_is_a_stack(stack_identifier, 'JobServerGroup') # Scale up one, Trigger alarm self.client.resources.signal(stack_identifier, 'ScaleUpPolicy') self._wait_for_stack_status(nested_ident, 'UPDATE_COMPLETE') self.assertTrue(test.call_until_true( self.build_timeout, self.build_interval, self.check_instance_count, stack_identifier, 3)) # increase the adjustment to "+2" and remove the DesiredCapacity # so we don't go from 3 to 2. new_template = self.template.replace( '"ScalingAdjustment": "1"', '"ScalingAdjustment": "2"').replace( '"DesiredCapacity" : {"Ref": "size"},', '') self.update_stack(stack_identifier, template=new_template, environment=self.env, files=self.files) # Scale up two, Trigger alarm self.client.resources.signal(stack_identifier, 'ScaleUpPolicy') self._wait_for_stack_status(nested_ident, 'UPDATE_COMPLETE') self.assertTrue(test.call_until_true( self.build_timeout, self.build_interval, self.check_instance_count, stack_identifier, 5)) def test_signal_during_suspend(self): """Prove that a signal will fail when the stack is in suspend.""" stack_identifier = self.stack_create(template=self.template, files=self.files, environment=self.env) self.assertTrue(test.call_until_true( self.build_timeout, self.build_interval, self.check_instance_count, stack_identifier, 2)) nested_ident = self.assert_resource_is_a_stack(stack_identifier, 'JobServerGroup') # suspend the top level stack. self.client.actions.suspend(stack_id=stack_identifier) self._wait_for_resource_status( stack_identifier, 'JobServerGroup', 'SUSPEND_COMPLETE') # Send a signal and an exception will raise ex = self.assertRaises(exc.BadRequest, self.client.resources.signal, stack_identifier, 'ScaleUpPolicy') error_msg = 'Signal resource during SUSPEND is not supported' self.assertIn(error_msg, six.text_type(ex)) ev = self.wait_for_event_with_reason( stack_identifier, reason='Cannot signal resource during SUSPEND', rsrc_name='ScaleUpPolicy') self.assertEqual('SUSPEND_COMPLETE', ev[0].resource_status) # still SUSPEND_COMPLETE (not gone to UPDATE_COMPLETE) self._wait_for_stack_status(nested_ident, 'SUSPEND_COMPLETE') self._wait_for_stack_status(stack_identifier, 'SUSPEND_COMPLETE') # still 2 instances. self.assertTrue(test.call_until_true( self.build_timeout, self.build_interval, self.check_instance_count, stack_identifier, 2)) heat-6.0.0/heat_integrationtests/functional/test_hooks.py0000664000567000056710000003153212701407050025104 0ustar jenkinsjenkins00000000000000# 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 heat_integrationtests.functional import functional_base class HooksTest(functional_base.FunctionalTestsBase): def setUp(self): super(HooksTest, self).setUp() self.template = {'heat_template_version': '2014-10-16', 'resources': { 'foo_step1': {'type': 'OS::Heat::RandomString'}, 'foo_step2': {'type': 'OS::Heat::RandomString', 'depends_on': 'foo_step1'}, 'foo_step3': {'type': 'OS::Heat::RandomString', 'depends_on': 'foo_step2'}}} def test_hook_pre_create(self): env = {'resource_registry': {'resources': {'foo_step2': {'hooks': 'pre-create'}}}} # Note we don't wait for CREATE_COMPLETE, because we need to # signal to clear the hook before create will complete stack_identifier = self.stack_create( template=self.template, environment=env, expected_status='CREATE_IN_PROGRESS') self._wait_for_resource_status( stack_identifier, 'foo_step1', 'CREATE_COMPLETE') self._wait_for_resource_status( stack_identifier, 'foo_step2', 'INIT_COMPLETE') ev = self.wait_for_event_with_reason( stack_identifier, reason='CREATE paused until Hook pre-create is cleared', rsrc_name='foo_step2') self.assertEqual('INIT_COMPLETE', ev[0].resource_status) self.client.resources.signal(stack_identifier, 'foo_step2', data={'unset_hook': 'pre-create'}) ev = self.wait_for_event_with_reason( stack_identifier, reason='Hook pre-create is cleared', rsrc_name='foo_step2') self.assertEqual('INIT_COMPLETE', ev[0].resource_status) self._wait_for_resource_status( stack_identifier, 'foo_step2', 'CREATE_COMPLETE') self._wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE') def test_hook_pre_update_nochange(self): env = {'resource_registry': {'resources': {'foo_step2': {'hooks': 'pre-update'}}}} stack_identifier = self.stack_create( template=self.template, environment=env) res_before = self.client.resources.get(stack_identifier, 'foo_step2') # Note we don't wait for UPDATE_COMPLETE, because we need to # signal to clear the hook before update will complete self.update_stack( stack_identifier, template=self.template, environment=env, expected_status='UPDATE_IN_PROGRESS') # Note when a hook is specified, the resource status doesn't change # when we hit the hook, so we look for the event, then assert the # state is unchanged. self._wait_for_resource_status( stack_identifier, 'foo_step2', 'CREATE_COMPLETE') ev = self.wait_for_event_with_reason( stack_identifier, reason='UPDATE paused until Hook pre-update is cleared', rsrc_name='foo_step2') self.assertEqual('CREATE_COMPLETE', ev[0].resource_status) self.client.resources.signal(stack_identifier, 'foo_step2', data={'unset_hook': 'pre-update'}) ev = self.wait_for_event_with_reason( stack_identifier, reason='Hook pre-update is cleared', rsrc_name='foo_step2') self.assertEqual('CREATE_COMPLETE', ev[0].resource_status) self._wait_for_resource_status( stack_identifier, 'foo_step2', 'CREATE_COMPLETE') self._wait_for_stack_status(stack_identifier, 'UPDATE_COMPLETE') res_after = self.client.resources.get(stack_identifier, 'foo_step2') self.assertEqual(res_before.physical_resource_id, res_after.physical_resource_id) def test_hook_pre_update_replace(self): env = {'resource_registry': {'resources': {'foo_step2': {'hooks': 'pre-update'}}}} stack_identifier = self.stack_create( template=self.template, environment=env) res_before = self.client.resources.get(stack_identifier, 'foo_step2') # Note we don't wait for UPDATE_COMPLETE, because we need to # signal to clear the hook before update will complete self.template['resources']['foo_step2']['properties'] = {'length': 10} self.update_stack( stack_identifier, template=self.template, environment=env, expected_status='UPDATE_IN_PROGRESS') # Note when a hook is specified, the resource status doesn't change # when we hit the hook, so we look for the event, then assert the # state is unchanged. self._wait_for_resource_status( stack_identifier, 'foo_step2', 'CREATE_COMPLETE') ev = self.wait_for_event_with_reason( stack_identifier, reason='UPDATE paused until Hook pre-update is cleared', rsrc_name='foo_step2') self.assertEqual('CREATE_COMPLETE', ev[0].resource_status) self.client.resources.signal(stack_identifier, 'foo_step2', data={'unset_hook': 'pre-update'}) ev = self.wait_for_event_with_reason( stack_identifier, reason='Hook pre-update is cleared', rsrc_name='foo_step2') self.assertEqual('CREATE_COMPLETE', ev[0].resource_status) self._wait_for_resource_status( stack_identifier, 'foo_step2', 'CREATE_COMPLETE') self._wait_for_stack_status(stack_identifier, 'UPDATE_COMPLETE') res_after = self.client.resources.get(stack_identifier, 'foo_step2') self.assertNotEqual(res_before.physical_resource_id, res_after.physical_resource_id) def test_hook_pre_update_in_place(self): env = {'resource_registry': {'resources': {'rg': {'hooks': 'pre-update'}}}} template = {'heat_template_version': '2014-10-16', 'resources': { 'rg': { 'type': 'OS::Heat::ResourceGroup', 'properties': { 'count': 1, 'resource_def': { 'type': 'OS::Heat::RandomString'}}}}} # Note we don't wait for CREATE_COMPLETE, because we need to # signal to clear the hook before create will complete stack_identifier = self.stack_create( template=template, environment=env) res_before = self.client.resources.get(stack_identifier, 'rg') template['resources']['rg']['properties']['count'] = 2 self.update_stack( stack_identifier, template=template, environment=env, expected_status='UPDATE_IN_PROGRESS') # Note when a hook is specified, the resource status doesn't change # when we hit the hook, so we look for the event, then assert the # state is unchanged. self._wait_for_resource_status( stack_identifier, 'rg', 'CREATE_COMPLETE') ev = self.wait_for_event_with_reason( stack_identifier, reason='UPDATE paused until Hook pre-update is cleared', rsrc_name='rg') self.assertEqual('CREATE_COMPLETE', ev[0].resource_status) self.client.resources.signal(stack_identifier, 'rg', data={'unset_hook': 'pre-update'}) ev = self.wait_for_event_with_reason( stack_identifier, reason='Hook pre-update is cleared', rsrc_name='rg') self.assertEqual('CREATE_COMPLETE', ev[0].resource_status) self._wait_for_stack_status(stack_identifier, 'UPDATE_COMPLETE') res_after = self.client.resources.get(stack_identifier, 'rg') self.assertEqual(res_before.physical_resource_id, res_after.physical_resource_id) def test_hook_pre_create_nested(self): files = {'nested.yaml': yaml.safe_dump(self.template)} env = {'resource_registry': {'resources': {'nested': {'foo_step2': {'hooks': 'pre-create'}}}}} template = {'heat_template_version': '2014-10-16', 'resources': { 'nested': {'type': 'nested.yaml'}}} # Note we don't wait for CREATE_COMPLETE, because we need to # signal to clear the hook before create will complete stack_identifier = self.stack_create( template=template, environment=env, files=files, expected_status='CREATE_IN_PROGRESS') self._wait_for_resource_status(stack_identifier, 'nested', 'CREATE_IN_PROGRESS') nested_identifier = self.assert_resource_is_a_stack( stack_identifier, 'nested', wait=True) self._wait_for_resource_status( nested_identifier, 'foo_step1', 'CREATE_COMPLETE') self._wait_for_resource_status( nested_identifier, 'foo_step2', 'INIT_COMPLETE') ev = self.wait_for_event_with_reason( nested_identifier, reason='CREATE paused until Hook pre-create is cleared', rsrc_name='foo_step2') self.assertEqual('INIT_COMPLETE', ev[0].resource_status) self.client.resources.signal(nested_identifier, 'foo_step2', data={'unset_hook': 'pre-create'}) ev = self.wait_for_event_with_reason( nested_identifier, reason='Hook pre-create is cleared', rsrc_name='foo_step2') self.assertEqual('INIT_COMPLETE', ev[0].resource_status) self._wait_for_resource_status( nested_identifier, 'foo_step2', 'CREATE_COMPLETE') self._wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE') def test_hook_pre_create_wildcard(self): env = {'resource_registry': {'resources': {'foo_*': {'hooks': 'pre-create'}}}} # Note we don't wait for CREATE_COMPLETE, because we need to # signal to clear the hook before create will complete stack_identifier = self.stack_create( template=self.template, environment=env, expected_status='CREATE_IN_PROGRESS') self._wait_for_resource_status( stack_identifier, 'foo_step1', 'INIT_COMPLETE') self.wait_for_event_with_reason( stack_identifier, reason='CREATE paused until Hook pre-create is cleared', rsrc_name='foo_step1') self.client.resources.signal(stack_identifier, 'foo_step1', data={'unset_hook': 'pre-create'}) self.wait_for_event_with_reason( stack_identifier, reason='Hook pre-create is cleared', rsrc_name='foo_step1') self._wait_for_resource_status( stack_identifier, 'foo_step2', 'INIT_COMPLETE') self.wait_for_event_with_reason( stack_identifier, reason='CREATE paused until Hook pre-create is cleared', rsrc_name='foo_step2') self.client.resources.signal(stack_identifier, 'foo_step2', data={'unset_hook': 'pre-create'}) self.wait_for_event_with_reason( stack_identifier, reason='Hook pre-create is cleared', rsrc_name='foo_step2') self._wait_for_resource_status( stack_identifier, 'foo_step3', 'INIT_COMPLETE') self.wait_for_event_with_reason( stack_identifier, reason='CREATE paused until Hook pre-create is cleared', rsrc_name='foo_step3') self.client.resources.signal(stack_identifier, 'foo_step3', data={'unset_hook': 'pre-create'}) self.wait_for_event_with_reason( stack_identifier, reason='Hook pre-create is cleared', rsrc_name='foo_step3') self._wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE') heat-6.0.0/heat_integrationtests/functional/test_validation.py0000664000567000056710000000566012701407050026116 0ustar jenkinsjenkins00000000000000# 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 heat_integrationtests.functional import functional_base class StackValidationTest(functional_base.FunctionalTestsBase): def setUp(self): super(StackValidationTest, self).setUp() if not self.conf.minimal_image_ref: raise self.skipException("No image configured to test") if not self.conf.minimal_instance_type: raise self.skipException( "No minimal_instance_type configured to test") self.assign_keypair() def test_stack_validate_provider_references_parent_resource(self): template = ''' heat_template_version: 2014-10-16 parameters: keyname: type: string flavor: type: string image: type: string network: type: string resources: config: type: My::Config properties: server: {get_resource: server} server: type: OS::Nova::Server properties: image: {get_param: image} flavor: {get_param: flavor} key_name: {get_param: keyname} networks: [{network: {get_param: network} }] user_data_format: SOFTWARE_CONFIG ''' config_template = ''' heat_template_version: 2014-10-16 parameters: server: type: string resources: config: type: OS::Heat::SoftwareConfig deployment: type: OS::Heat::SoftwareDeployment properties: config: get_resource: config server: get_param: server ''' files = {'provider.yaml': config_template} env = {'resource_registry': {'My::Config': 'provider.yaml'}} parameters = {'keyname': self.keypair_name, 'flavor': self.conf.minimal_instance_type, 'image': self.conf.minimal_image_ref, 'network': self.conf.fixed_network_name} # Note we don't wait for CREATE_COMPLETE, because we're using a # minimal image without the tools to apply the config. # The point of the test is just to prove that validation won't # falsely prevent stack creation starting, ref bug #1407100 # Note that we can be sure My::Config will stay IN_PROGRESS as # there's no signal sent to the deployment self.stack_create(template=template, files=files, environment=env, parameters=parameters, expected_status='CREATE_IN_PROGRESS') heat-6.0.0/heat_integrationtests/functional/test_lbaasv2.py0000664000567000056710000001252412701407050025313 0ustar jenkinsjenkins00000000000000# 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 oslo_log import log as logging from heat_integrationtests.functional import functional_base LOG = logging.getLogger(__name__) class LoadBalancerv2Test(functional_base.FunctionalTestsBase): create_template = ''' heat_template_version: 2016-04-08 resources: loadbalancer: type: OS::Neutron::LBaaS::LoadBalancer properties: description: aLoadBalancer vip_subnet: private-subnet listener: type: OS::Neutron::LBaaS::Listener properties: description: aListener loadbalancer: { get_resource: loadbalancer } protocol: HTTP protocol_port: 80 connection_limit: 5555 pool: type: OS::Neutron::LBaaS::Pool properties: description: aPool lb_algorithm: ROUND_ROBIN protocol: HTTP listener: { get_resource: listener } poolmember: type: OS::Neutron::LBaaS::PoolMember properties: address: 1.1.1.1 pool: { get_resource: pool } protocol_port: 1111 subnet: private-subnet weight: 255 # pm2 healthmonitor: type: OS::Neutron::LBaaS::HealthMonitor properties: delay: 3 type: HTTP timeout: 3 max_retries: 3 pool: { get_resource: pool } outputs: loadbalancer: value: { get_attr: [ loadbalancer, show ] } pool: value: { get_attr: [ pool, show ] } poolmember: value: { get_attr: [ poolmember, show ] } listener: value: { get_attr: [ listener, show ] } healthmonitor: value: { get_attr: [ healthmonitor, show ] } ''' add_member = ''' poolmember2: type: OS::Neutron::LBaaS::PoolMember properties: address: 2.2.2.2 pool: { get_resource: pool } protocol_port: 2222 subnet: private-subnet weight: 222 ''' def setUp(self): super(LoadBalancerv2Test, self).setUp() if not self.is_network_extension_supported('lbaasv2'): self.skipTest('LBaasv2 extension not available, skipping') def test_create_update_loadbalancer(self): stack_identifier = self.stack_create(template=self.create_template) stack = self.client.stacks.get(stack_identifier) output = self._stack_output(stack, 'loadbalancer') self.assertEqual('ONLINE', output['operating_status']) template = self.create_template.replace('ROUND_ROBIN', 'SOURCE_IP') template = template.replace('3', '6') template = template.replace('255', '256') template = template.replace('5555', '7777') template = template.replace('aLoadBalancer', 'updatedLoadBalancer') template = template.replace('aPool', 'updatedPool') template = template.replace('aListener', 'updatedListener') self.update_stack(stack_identifier, template=template) stack = self.client.stacks.get(stack_identifier) output = self._stack_output(stack, 'loadbalancer') self.assertEqual('ONLINE', output['operating_status']) self.assertEqual('updatedLoadBalancer', output['description']) output = self._stack_output(stack, 'pool') self.assertEqual('SOURCE_IP', output['lb_algorithm']) self.assertEqual('updatedPool', output['description']) output = self._stack_output(stack, 'poolmember') self.assertEqual(256, output['weight']) output = self._stack_output(stack, 'healthmonitor') self.assertEqual(6, output['delay']) self.assertEqual(6, output['timeout']) self.assertEqual(6, output['max_retries']) output = self._stack_output(stack, 'listener') self.assertEqual(7777, output['connection_limit']) self.assertEqual('updatedListener', output['description']) def test_add_delete_poolmember(self): stack_identifier = self.stack_create(template=self.create_template) stack = self.client.stacks.get(stack_identifier) output = self._stack_output(stack, 'loadbalancer') self.assertEqual('ONLINE', output['operating_status']) output = self._stack_output(stack, 'pool') self.assertEqual(1, len(output['members'])) # add pool member template = self.create_template.replace('# pm2', self.add_member) self.update_stack(stack_identifier, template=template) stack = self.client.stacks.get(stack_identifier) output = self._stack_output(stack, 'loadbalancer') self.assertEqual('ONLINE', output['operating_status']) output = self._stack_output(stack, 'pool') self.assertEqual(2, len(output['members'])) # delete pool member self.update_stack(stack_identifier, template=self.create_template) stack = self.client.stacks.get(stack_identifier) output = self._stack_output(stack, 'loadbalancer') self.assertEqual('ONLINE', output['operating_status']) output = self._stack_output(stack, 'pool') self.assertEqual(1, len(output['members'])) heat-6.0.0/heat_integrationtests/functional/test_os_wait_condition.py0000664000567000056710000000611512701407050027473 0ustar jenkinsjenkins00000000000000# 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 heat_integrationtests.functional import functional_base class OSWaitCondition(functional_base.FunctionalTestsBase): template = ''' heat_template_version: 2013-05-23 parameters: flavor: type: string image: type: string network: type: string timeout: type: number default: 60 resources: instance1: type: OS::Nova::Server properties: flavor: {get_param: flavor} image: {get_param: image} networks: - network: {get_param: network} user_data_format: RAW user_data: str_replace: template: '#!/bin/sh wc_notify --data-binary ''{"status": "SUCCESS"}'' # signals with reason wc_notify --data-binary ''{"status": "SUCCESS", "reason": "signal2"}'' # signals with data wc_notify --data-binary ''{"status": "SUCCESS", "reason": "signal3", "data": "data3"}'' wc_notify --data-binary ''{"status": "SUCCESS", "reason": "signal4", "data": "data4"}'' # check signals with the same number wc_notify --data-binary ''{"status": "SUCCESS", "id": "5"}'' wc_notify --data-binary ''{"status": "SUCCESS", "id": "5"}'' # loop for 20 signals without reasons and data for i in `seq 1 20`; do wc_notify --data-binary ''{"status": "SUCCESS"}'' & done wait ' params: wc_notify: get_attr: [wait_handle, curl_cli] wait_condition: type: OS::Heat::WaitCondition depends_on: instance1 properties: count: 25 handle: {get_resource: wait_handle} timeout: {get_param: timeout} wait_handle: type: OS::Heat::WaitConditionHandle outputs: curl_cli: value: get_attr: [wait_handle, curl_cli] wc_data: value: get_attr: [wait_condition, data] ''' def setUp(self): super(OSWaitCondition, self).setUp() if not self.conf.minimal_image_ref: raise self.skipException("No minimal image configured to test") if not self.conf.minimal_instance_type: raise self.skipException("No minimal flavor configured to test") def test_create_stack_with_multi_signal_waitcondition(self): params = {'flavor': self.conf.minimal_instance_type, 'image': self.conf.minimal_image_ref, 'network': self.conf.fixed_network_name, 'timeout': 120} self.stack_create(template=self.template, parameters=params) heat-6.0.0/heat_integrationtests/functional/test_event_sinks.py0000664000567000056710000000506512701407050026313 0ustar jenkinsjenkins00000000000000# 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 uuid from zaqarclient.queues.v1 import client as zaqarclient from heat_integrationtests.functional import functional_base class ZaqarEventSinkTest(functional_base.FunctionalTestsBase): template = ''' heat_template_version: "2013-05-23" resources: test_resource: type: OS::Heat::TestResource properties: value: ok ''' def test_events(self): queue_id = str(uuid.uuid4()) environment = {'event_sinks': [{'type': 'zaqar-queue', 'target': queue_id, 'ttl': 120}]} stack_identifier = self.stack_create( template=self.template, environment=environment) stack_name, stack_id = stack_identifier.split('/') conf = { 'auth_opts': { 'backend': 'keystone', 'options': { 'os_username': self.conf.username, 'os_password': self.conf.password, 'os_project_name': self.conf.tenant_name, 'os_auth_url': self.conf.auth_url } } } zaqar = zaqarclient.Client(conf=conf, version=1.1) queue = zaqar.queue(queue_id) messages = list(queue.messages()) self.assertEqual(4, len(messages)) types = [m.body['type'] for m in messages] self.assertEqual(['os.heat.event'] * 4, types) resources = set([m.body['payload']['resource_name'] for m in messages]) self.assertEqual(set([stack_name, 'test_resource']), resources) stack_ids = [m.body['payload']['stack_id'] for m in messages] self.assertEqual([stack_id] * 4, stack_ids) statuses = [m.body['payload']['resource_status'] for m in messages] statuses.sort() self.assertEqual( ['COMPLETE', 'COMPLETE', 'IN_PROGRESS', 'IN_PROGRESS'], statuses) actions = [m.body['payload']['resource_action'] for m in messages] self.assertEqual(['CREATE'] * 4, actions) heat-6.0.0/heat_integrationtests/functional/test_conditional_exposure.py0000664000567000056710000000707612701407050030224 0ustar jenkinsjenkins00000000000000# 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 heatclient import exc import keystoneclient from heat_integrationtests.functional import functional_base class ServiceBasedExposureTest(functional_base.FunctionalTestsBase): # NOTE(pas-ha) if we ever decide to install Sahara on Heat # functional gate, this must be changed to other not-installed # but in principle supported service unavailable_service = 'Sahara' unavailable_template = """ heat_template_version: 2015-10-15 resources: not_available: type: OS::Sahara::NodeGroupTemplate properties: plugin_name: fake hadoop_version: 0.1 flavor: m1.large node_processes: [] """ def setUp(self): super(ServiceBasedExposureTest, self).setUp() # check that Sahara endpoint is available if self._is_sahara_deployed(): self.skipTest("Sahara is actually deployed, " "can not run negative tests on " "Sahara resources availability.") def _is_sahara_deployed(self): try: self.identity_client.get_endpoint_url('data-processing', self.conf.region) except keystoneclient.exceptions.EndpointNotFound: return False return True def test_unavailable_resources_not_listed(self): resources = self.client.resource_types.list() self.assertFalse(any(self.unavailable_service in r.resource_type for r in resources)) def test_unavailable_resources_not_created(self): stack_name = self._stack_rand_name() ex = self.assertRaises(exc.HTTPBadRequest, self.client.stacks.create, stack_name=stack_name, template=self.unavailable_template) self.assertIn('ResourceTypeUnavailable', ex.message) self.assertIn('OS::Sahara::NodeGroupTemplate', ex.message) class RoleBasedExposureTest(functional_base.FunctionalTestsBase): forbidden_resource_type = "OS::Nova::Flavor" fl_tmpl = """ heat_template_version: 2015-10-15 resources: not4everyone: type: OS::Nova::Flavor properties: ram: 20000 vcpus: 10 """ def test_non_admin_forbidden_create_flavors(self): """Fail to create Flavor resource w/o admin role. Integration tests job runs as normal OpenStack user, and OS::Nova:Flavor is configured to require admin role in default policy file of Heat. """ stack_name = self._stack_rand_name() ex = self.assertRaises(exc.Forbidden, self.client.stacks.create, stack_name=stack_name, template=self.fl_tmpl) self.assertIn(self.forbidden_resource_type, ex.message) def test_forbidden_resource_not_listed(self): resources = self.client.resource_types.list() self.assertNotIn(self.forbidden_resource_type, (r.resource_type for r in resources)) heat-6.0.0/heat_integrationtests/prepare_test_network.sh0000775000567000056710000000245212701407050025012 0ustar jenkinsjenkins00000000000000#!/bin/bash # # 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. # This script creates default tenant networks for the tests set -x source $DEST/devstack/openrc admin admin PUB_SUBNET_ID=`neutron subnet-list | grep ' public-subnet ' | awk '{split($0,a,"|"); print a[2]}'` ROUTER_GW_IP=`neutron port-list -c fixed_ips -c device_owner | grep router_gateway | awk -F '"' -v subnet_id="${PUB_SUBNET_ID//[[:space:]]/}" '$4 == subnet_id { print $8; }'` # create a heat specific private network (default 'private' network has ipv6 subnet) source $DEST/devstack/openrc demo demo HEAT_PRIVATE_SUBNET_CIDR=10.0.5.0/24 neutron net-create heat-net neutron subnet-create --name heat-subnet heat-net $HEAT_PRIVATE_SUBNET_CIDR neutron router-interface-add router1 heat-subnet sudo route add -net $HEAT_PRIVATE_SUBNET_CIDR gw $ROUTER_GW_IP heat-6.0.0/heat_integrationtests/scenario/0000775000567000056710000000000012701407211022004 5ustar jenkinsjenkins00000000000000heat-6.0.0/heat_integrationtests/scenario/test_ceilometer_alarm.py0000664000567000056710000000460212701407050026724 0ustar jenkinsjenkins00000000000000# 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 oslo_log import log as logging from heat_integrationtests.common import test from heat_integrationtests.scenario import scenario_base LOG = logging.getLogger(__name__) class CeilometerAlarmTest(scenario_base.ScenarioTestsBase): """Class is responsible for testing of ceilometer usage.""" def setUp(self): super(CeilometerAlarmTest, self).setUp() self.template = self._load_template(__file__, 'test_ceilometer_alarm.yaml', 'templates') def check_instance_count(self, stack_identifier, expected): stack = self.client.stacks.get(stack_identifier) actual = self._stack_output(stack, 'asg_size') if actual != expected: LOG.warning('check_instance_count exp:%d, act:%s' % (expected, actual)) return actual == expected def test_alarm(self): """Confirm we can create an alarm and trigger it.""" # 1. create the stack stack_identifier = self.stack_create(template=self.template) # 2. send ceilometer a metric (should cause the alarm to fire) sample = {} sample['counter_type'] = 'gauge' sample['counter_name'] = 'test_meter' sample['counter_volume'] = 1 sample['counter_unit'] = 'count' sample['resource_metadata'] = {'metering.stack_id': stack_identifier.split('/')[-1]} sample['resource_id'] = 'shouldnt_matter' self.metering_client.samples.create(**sample) # 3. confirm we get a scaleup. # Note: there is little point waiting more than 60s+time to scale up. self.assertTrue(test.call_until_true( 120, 2, self.check_instance_count, stack_identifier, 2)) heat-6.0.0/heat_integrationtests/scenario/__init__.py0000664000567000056710000000000012701407050024104 0ustar jenkinsjenkins00000000000000heat-6.0.0/heat_integrationtests/scenario/test_server_cfn_init.py0000664000567000056710000001162512701407050026602 0ustar jenkinsjenkins00000000000000# 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 json from heat_integrationtests.common import exceptions from heat_integrationtests.scenario import scenario_base class CfnInitIntegrationTest(scenario_base.ScenarioTestsBase): """Testing cfn-init and cfn-signal workability.""" def setUp(self): super(CfnInitIntegrationTest, self).setUp() def check_stack(self, sid): # Check status of all resources for res in ('WaitHandle', 'SmokeSecurityGroup', 'SmokeKeys', 'CfnUser', 'SmokeServer', 'SmokeServerElasticIp'): self._wait_for_resource_status( sid, res, 'CREATE_COMPLETE') server_resource = self.client.resources.get(sid, 'SmokeServer') server_id = server_resource.physical_resource_id server = self.compute_client.servers.get(server_id) try: self._wait_for_resource_status( sid, 'WaitCondition', 'CREATE_COMPLETE') except (exceptions.StackResourceBuildErrorException, exceptions.TimeoutException) as e: raise e finally: # attempt to log the server console regardless of WaitCondition # going to complete. This allows successful and failed cloud-init # logs to be compared self._log_console_output(servers=[server]) stack = self.client.stacks.get(sid) # This is an assert of great significance, as it means the following # has happened: # - cfn-init read the provided metadata and wrote out a file # - a user was created and credentials written to the server # - a cfn-signal was built which was signed with provided credentials # - the wait condition was fulfilled and the stack has changed state wait_status = json.loads( self._stack_output(stack, 'WaitConditionStatus')) self.assertEqual('smoke test complete', wait_status['smoke_status']) # Check EIP attributes. server_floatingip_id = self._stack_output(stack, 'ElasticIp_Id') self.assertIsNotNone(server_floatingip_id) # Fetch EIP details. net_show = self.network_client.show_floatingip( floatingip=server_floatingip_id) floating_ip = net_show['floatingip']['floating_ip_address'] port_id = net_show['floatingip']['port_id'] # Ensure that EIP was assigned to server. port_show = self.network_client.show_port(port=port_id) self.assertEqual(server.id, port_show['port']['device_id']) server_ip = self._stack_output(stack, 'SmokeServerElasticIp') self.assertEqual(server_ip, floating_ip) # Check that created server is reachable if not self._ping_ip_address(server_ip): self._log_console_output(servers=[server]) self.fail( "Timed out waiting for %s to become reachable" % server_ip) # Check that the user can authenticate with the generated keypair if self.keypair: try: linux_client = self.get_remote_client( server_ip, username='ec2-user') linux_client.validate_authentication() except (exceptions.ServerUnreachable, exceptions.SSHTimeout) as e: self._log_console_output(servers=[server]) raise e def test_server_cfn_init(self): """Check cfn-init and cfn-signal availability on the created server. The alternative scenario is the following: 1. Create a stack with a server and configured security group. 2. Check that all stack resources were created. 3. Check that created server is reachable. 4. Check that stack was created successfully. 5. Check that is it possible to connect to server via generated keypair. """ parameters = { 'key_name': self.keypair_name, 'flavor': self.conf.instance_type, 'image': self.conf.image_ref, 'timeout': self.conf.build_timeout, 'subnet': self.net['subnets'][0], } # Launch stack stack_id = self.launch_stack( template_name="test_server_cfn_init.yaml", parameters=parameters, expected_status=None ) # Check stack self.check_stack(stack_id) heat-6.0.0/heat_integrationtests/scenario/test_server_software_config.py0000664000567000056710000001425312701407050030170 0ustar jenkinsjenkins00000000000000# 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 heatclient.common import template_utils import six from heat_integrationtests.common import exceptions from heat_integrationtests.scenario import scenario_base CFG1_SH = '''#!/bin/sh echo "Writing to /tmp/$bar" echo $foo > /tmp/$bar echo -n "The file /tmp/$bar contains `cat /tmp/$bar` for server \ $deploy_server_id during $deploy_action" > $heat_outputs_path.result echo "Written to /tmp/$bar" echo "Output to stderr" 1>&2 ''' CFG3_PP = '''file {'barfile': ensure => file, mode => 0644, path => "/tmp/$::bar", content => "$::foo", } file {'output_result': ensure => file, path => "$::heat_outputs_path.result", mode => 0644, content => "The file /tmp/$::bar contains $::foo for server \ $::deploy_server_id during $::deploy_action", } ''' class SoftwareConfigIntegrationTest(scenario_base.ScenarioTestsBase): def check_stack(self): sid = self.stack_identifier # Check that all stack resources were created for res in ('cfg2a', 'cfg2b', 'cfg1', 'cfg3', 'server'): self._wait_for_resource_status( sid, res, 'CREATE_COMPLETE') server_resource = self.client.resources.get(sid, 'server') server_id = server_resource.physical_resource_id server = self.compute_client.servers.get(server_id) # Waiting for each deployment to contribute their # config to resource try: for res in ('dep2b', 'dep1', 'dep3'): self._wait_for_resource_status( sid, res, 'CREATE_IN_PROGRESS') server_metadata = self.client.resources.metadata( sid, 'server') deployments = dict((d['name'], d) for d in server_metadata['deployments']) for res in ('dep2a', 'dep2b', 'dep1', 'dep3'): self._wait_for_resource_status( sid, res, 'CREATE_COMPLETE') except (exceptions.StackResourceBuildErrorException, exceptions.TimeoutException) as e: raise e finally: # attempt to log the server console regardless of deployments # going to complete. This allows successful and failed boot # logs to be compared self._log_console_output(servers=[server]) complete_server_metadata = self.client.resources.metadata( sid, 'server') # Ensure any previously available deployments haven't changed so # config isn't re-triggered complete_deployments = dict((d['name'], d) for d in complete_server_metadata['deployments']) for k, v in six.iteritems(deployments): self.assertEqual(v, complete_deployments[k]) stack = self.client.stacks.get(sid) res1 = self._stack_output(stack, 'res1') self.assertEqual( 'The file %s contains %s for server %s during %s' % ( '/tmp/baaaaa', 'fooooo', server_id, 'CREATE'), res1['result']) self.assertEqual(0, res1['status_code']) self.assertEqual('Output to stderr\n', res1['stderr']) self.assertTrue(len(res1['stdout']) > 0) res2 = self._stack_output(stack, 'res2') self.assertEqual( 'The file %s contains %s for server %s during %s' % ( '/tmp/cfn-init-foo', 'barrr', server_id, 'CREATE'), res2['result']) self.assertEqual(0, res2['status_code']) self.assertEqual('', res2['stderr']) self.assertEqual('', res2['stdout']) res3 = self._stack_output(stack, 'res3') self.assertEqual( 'The file %s contains %s for server %s during %s' % ( '/tmp/ba', 'fo', server_id, 'CREATE'), res3['result']) self.assertEqual(0, res3['status_code']) self.assertEqual('', res3['stderr']) self.assertTrue(len(res1['stdout']) > 0) dep1_resource = self.client.resources.get(sid, 'dep1') dep1_id = dep1_resource.physical_resource_id dep1_dep = self.client.software_deployments.get(dep1_id) if hasattr(dep1_dep, 'updated_time'): # Only check updated_time if the attribute exists. # This allows latest heat agent code to be tested with # Juno heat (which doesn't expose updated_time) self.assertIsNotNone(dep1_dep.updated_time) self.assertNotEqual( dep1_dep.updated_time, dep1_dep.creation_time) def test_server_software_config(self): """Check that passed files with scripts are executed on created server. The alternative scenario is the following: 1. Create a stack and pass files with scripts. 2. Check that all stack resources are created successfully. 3. Wait for all deployments. 4. Check that stack was created. 5. Check stack outputs. """ parameters = { 'key_name': self.keypair_name, 'flavor': self.conf.instance_type, 'image': self.conf.image_ref, 'network': self.net['id'] } files = { 'cfg1.sh': CFG1_SH, 'cfg3.pp': CFG3_PP } env_files, env = template_utils.process_environment_and_files( self.conf.boot_config_env) # Launch stack self.stack_identifier = self.launch_stack( template_name='test_server_software_config.yaml', parameters=parameters, files=dict(list(files.items()) + list(env_files.items())), expected_status=None, environment=env ) # Check stack self.check_stack() heat-6.0.0/heat_integrationtests/scenario/templates/0000775000567000056710000000000012701407211024002 5ustar jenkinsjenkins00000000000000heat-6.0.0/heat_integrationtests/scenario/templates/test_autoscaling_lb_neutron.yaml0000664000567000056710000000520012701407050032463 0ustar jenkinsjenkins00000000000000heat_template_version: 2015-04-30 description: | Template which tests Neutron load balancing requests to members of Heat AutoScalingGroup. Instances must be running some webserver on a given app_port producing HTTP response that is different between servers but stable over time for given server. parameters: flavor: type: string image: type: string net: type: string subnet: type: string public_net: type: string app_port: type: number default: 8080 lb_port: type: number default: 80 timeout: type: number default: 600 resources: sec_group: type: OS::Neutron::SecurityGroup properties: rules: - remote_ip_prefix: 0.0.0.0/0 protocol: tcp port_range_min: { get_param: app_port } port_range_max: { get_param: app_port } asg: type: OS::Heat::AutoScalingGroup properties: desired_capacity: 1 max_size: 2 min_size: 1 resource: type: OS::Test::NeutronAppServer properties: image: { get_param: image } flavor: { get_param: flavor } net: { get_param: net} sec_group: { get_resource: sec_group } app_port: { get_param: app_port } pool_id: { get_resource: pool } timeout: { get_param: timeout } scale_up: type: OS::Heat::ScalingPolicy properties: adjustment_type: change_in_capacity auto_scaling_group_id: { get_resource: asg } scaling_adjustment: 1 scale_down: type: OS::Heat::ScalingPolicy properties: adjustment_type: change_in_capacity auto_scaling_group_id: { get_resource: asg } scaling_adjustment: -1 health_monitor: type: OS::Neutron::HealthMonitor properties: delay: 3 type: HTTP timeout: 3 max_retries: 3 pool: type: OS::Neutron::Pool properties: lb_method: ROUND_ROBIN protocol: HTTP subnet: { get_param: subnet } monitors: - { get_resource: health_monitor } vip: protocol_port: { get_param: lb_port } floating_ip: type: OS::Neutron::FloatingIP properties: floating_network: { get_param: public_net } port_id: { get_attr: [pool, vip, 'port_id'] } loadbalancer: type: OS::Neutron::LoadBalancer properties: pool_id: { get_resource: pool } protocol_port: { get_param: app_port } outputs: lburl: description: URL of the loadbalanced app value: str_replace: template: http://IP_ADDRESS:PORT params: IP_ADDRESS: { get_attr: [ floating_ip, floating_ip_address ] } PORT: { get_param: lb_port } heat-6.0.0/heat_integrationtests/scenario/templates/test_volumes_create_from_backup.yaml0000664000567000056710000000640712701407050033322 0ustar jenkinsjenkins00000000000000heat_template_version: 2013-05-23 parameters: key_name: type: string description: keypair to enable SSH access to the instance. instance_type: type: string description: Type of the instance to be created. default: m1.small image_id: type: string description: ID of the image to use for the instance to be created. timeout: type: number description: Stack creation timeout dev_name: type: string description: Expected device name for volume default: vdb rescan_timeout: type: number description: Max number of seconds to wait for volume after rescan default: 120 backup_id: type: string description: backup_id to create volume from network: type: string volume_description: type: string description: Description of volume default: A volume description resources: volume: type: OS::Cinder::Volume properties: backup_id: { get_param: backup_id } description: { get_param: volume_description } volume_attachment: type: OS::Cinder::VolumeAttachment properties: volume_id: { get_resource: volume } instance_uuid: { get_resource: instance } instance: type: OS::Nova::Server properties: image: { get_param: image_id } flavor: { get_param: instance_type } key_name: { get_param: key_name } networks: - uuid: {get_param: network} user_data_format: RAW user_data: str_replace: template: | #!/bin/sh # Trigger rescan to ensure we see the attached volume for i in /sys/class/scsi_host/*; do echo "- - -" > $i/scan; done # Wait for the rescan as the volume doesn't appear immediately for i in $(seq 1 rescan_timeout) do grep -q dev_name /proc/partitions && break sleep 1 done if grep -q dev_name /proc/partitions then mount /dev/dev_name /mnt TESTDATA=$(cat /mnt/testfile) curl -X PUT -H 'Content-Type:' --data-binary '{"Status": "SUCCESS", "Reason": "Test Complete", "Data": "Volume Data:'$TESTDATA'", "UniqueId": "instance1"}' "wc_url" else curl -X PUT -H 'Content-Type:' --data-binary '{"Status": "FAILURE", "Reason": "Test Failed", "Data": "Expected device dev_name not found.", "UniqueId": "instance1"}' "wc_url" fi params: wc_url: { get_resource: wait_handle } dev_name: { get_param: dev_name } rescan_timeout: { get_param: rescan_timeout } wait_handle: type: OS::Heat::UpdateWaitConditionHandle wait_condition: type: AWS::CloudFormation::WaitCondition properties: Count: 1 Handle: { get_resource: wait_handle } Timeout: { get_param: timeout } outputs: status: description: status value: { get_attr: ['volume', 'status'] } size: description: size value: { get_attr: ['volume', 'size'] } display_description: description: display_description value: { get_attr: ['volume', 'display_description'] } volume_id: value: { get_resource: volume } testfile_data: description: Contents of /mnt/testfile from the mounted volume value: { get_attr: ['wait_condition', 'Data'] } heat-6.0.0/heat_integrationtests/scenario/templates/test_volumes_delete_snapshot.yaml0000664000567000056710000000661212701407050032666 0ustar jenkinsjenkins00000000000000heat_template_version: 2013-05-23 parameters: key_name: type: string description: keypair to enable SSH access to the instance. instance_type: type: string description: Type of the instance to be created. default: m1.small image_id: type: string description: ID of the image to use for the instance to be created. timeout: type: number description: Stack creation timeout dev_name: type: string description: Expected device name for volume default: vdb test_string: type: string description: Test string which is written to volume default: ateststring rescan_timeout: type: number description: Max number of seconds to wait for volume after rescan default: 120 network: type: string volume_description: type: string description: Description of volume default: A volume description volume_size: type: number description: Size of volume default: 1 resources: volume: deletion_policy: 'Snapshot' type: OS::Cinder::Volume properties: size: {get_param: volume_size} description: {get_param: volume_description} volume_attachment: type: OS::Cinder::VolumeAttachment properties: volume_id: { get_resource: volume } instance_uuid: { get_resource: instance } instance: type: OS::Nova::Server properties: image: { get_param: image_id } flavor: { get_param: instance_type } key_name: { get_param: key_name } networks: - uuid: {get_param: network} user_data_format: RAW user_data: str_replace: template: | #!/bin/sh # Trigger rescan to ensure we see the attached volume for i in /sys/class/scsi_host/*; do echo "- - -" > $i/scan; done # Wait for the rescan as the volume doesn't appear immediately for i in $(seq 1 rescan_timeout) do grep -q dev_name /proc/partitions && break sleep 1 done if grep -q dev_name /proc/partitions then mkfs.ext4 /dev/dev_name mount /dev/dev_name /mnt echo "test_string" > /mnt/testfile umount /mnt curl -X PUT -H 'Content-Type:' --data-binary '{"Status": "SUCCESS", "Reason": "Test Complete", "Data": "Completed volume configuration.", "UniqueId": "instance1"}' "wc_url" else curl -X PUT -H 'Content-Type:' --data-binary '{"Status": "FAILURE", "Reason": "Test Failed", "Data": "Expected device dev_name not found.", "UniqueId": "instance1"}' "wc_url" fi params: wc_url: { get_resource: wait_handle } dev_name: { get_param: dev_name } rescan_timeout: { get_param: rescan_timeout } test_string: { get_param: test_string } wait_handle: type: OS::Heat::UpdateWaitConditionHandle wait_condition: type: AWS::CloudFormation::WaitCondition properties: Count: 1 Handle: { get_resource: wait_handle } Timeout: { get_param: timeout } outputs: status: description: status value: { get_attr: ['volume', 'status'] } size: description: size value: { get_attr: ['volume', 'size'] } display_description: description: display_description value: { get_attr: ['volume', 'display_description'] } volume_id: value: { get_resource: volume } heat-6.0.0/heat_integrationtests/scenario/templates/test_server_cfn_init.yaml0000664000567000056710000000512112701407050031104 0ustar jenkinsjenkins00000000000000HeatTemplateFormatVersion: '2012-12-12' Description: | Template which uses a wait condition to confirm that a minimal cfn-init and cfn-signal has worked Parameters: key_name: Type: String flavor: Type: String image: Type: String subnet: Type: String timeout: Type: Number Resources: CfnUser: Type: AWS::IAM::User SmokeSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Enable only ping and SSH access SecurityGroupIngress: - {CidrIp: 0.0.0.0/0, FromPort: '-1', IpProtocol: icmp, ToPort: '-1'} - {CidrIp: 0.0.0.0/0, FromPort: '22', IpProtocol: tcp, ToPort: '22'} SmokeKeys: Type: AWS::IAM::AccessKey Properties: UserName: {Ref: CfnUser} ElasticIp: Type: AWS::EC2::EIP Properties: Domain: vpc SmokeServerElasticIp: Type: AWS::EC2::EIPAssociation Properties: EIP: {Ref: ElasticIp} InstanceId: {Ref: SmokeServer} SmokeServer: Type: AWS::EC2::Instance Metadata: AWS::CloudFormation::Init: config: files: /tmp/smoke-status: content: smoke test complete /etc/cfn/cfn-credentials: content: Fn::Replace: - SmokeKeys: {Ref: SmokeKeys} SecretAccessKey: 'Fn::GetAtt': [SmokeKeys, SecretAccessKey] - | AWSAccessKeyId=SmokeKeys AWSSecretKey=SecretAccessKey mode: '000400' owner: root group: root Properties: ImageId: {Ref: image} InstanceType: {Ref: flavor} KeyName: {Ref: key_name} SubnetId: {Ref: subnet} SecurityGroups: - {Ref: SmokeSecurityGroup} UserData: Fn::Replace: - WaitHandle: {Ref: WaitHandle} - | #!/bin/bash -v /opt/aws/bin/cfn-init /opt/aws/bin/cfn-signal -e 0 --data "`cat /tmp/smoke-status`" \ --id smoke_status "WaitHandle" WaitHandle: Type: AWS::CloudFormation::WaitConditionHandle WaitCondition: Type: AWS::CloudFormation::WaitCondition DependsOn: SmokeServer Properties: Handle: {Ref: WaitHandle} Timeout: {Ref: timeout} Outputs: WaitConditionStatus: Description: Contents of /tmp/smoke-status on SmokeServer Value: Fn::GetAtt: [WaitCondition, Data] ElasticIp_Id: Description: Elastic ip allocation id Value: Fn::GetAtt: [ElasticIp, AllocationId] SmokeServerElasticIp: Description: Elastic ip address of server Value: Ref: ElasticIp heat-6.0.0/heat_integrationtests/scenario/templates/netcat-webapp.yaml0000664000567000056710000000147312701407050027426 0ustar jenkinsjenkins00000000000000heat_template_version: 2015-10-15 description: | Simplest web-app using netcat reporting only hostname. Specifically tailored for minimal Cirros image. parameters: app_port: type: number wc_curl_cli: type: string resources: webapp_nc: type: OS::Heat::SoftwareConfig properties: group: ungrouped config: str_replace: template: | #! /bin/sh -v Body=$(hostname) Response="HTTP/1.1 200 OK\r\nContent-Length: ${#Body}\r\n\r\n$Body" wc_notify --data-binary '{"status": "SUCCESS"}' while true ; do echo -e $Response | nc -llp PORT; done params: PORT: { get_param: app_port } wc_notify: { get_param: wc_curl_cli } outputs: OS::stack_id: value: { get_resource: webapp_nc } heat-6.0.0/heat_integrationtests/scenario/templates/test_ceilometer_alarm.yaml0000664000567000056710000000157412701407050031241 0ustar jenkinsjenkins00000000000000heat_template_version: 2013-05-23 resources: asg: type: OS::Heat::AutoScalingGroup properties: max_size: 5 min_size: 1 resource: type: OS::Heat::RandomString scaleup_policy: type: OS::Heat::ScalingPolicy properties: adjustment_type: change_in_capacity auto_scaling_group_id: {get_resource: asg} cooldown: 0 scaling_adjustment: 1 alarm: type: OS::Ceilometer::Alarm properties: description: Scale-up if the average CPU > 50% for 1 minute meter_name: test_meter statistic: count comparison_operator: ge threshold: 1 period: 60 evaluation_periods: 1 alarm_actions: - {get_attr: [scaleup_policy, alarm_url]} matching_metadata: metadata.metering.stack_id: {get_param: "OS::stack_id"} outputs: asg_size: value: {get_attr: [asg, current_size]} heat-6.0.0/heat_integrationtests/scenario/templates/app_server_neutron.yaml0000664000567000056710000000232612701407050030612 0ustar jenkinsjenkins00000000000000heat_template_version: 2015-10-15 description: | App server that is a member of Neutron Pool. parameters: image: type: string flavor: type: string net: type: string sec_group: type: string pool_id: type: string app_port: type: number timeout: type: number resources: config: type: OS::Test::WebAppConfig properties: app_port: { get_param: app_port } wc_curl_cli: { get_attr: [ handle, curl_cli ] } server: type: OS::Nova::Server properties: image: { get_param: image } flavor: { get_param: flavor } networks: - network: { get_param: net } security_groups: - { get_param: sec_group } user_data_format: RAW user_data: { get_resource: config } handle: type: OS::Heat::WaitConditionHandle waiter: type: OS::Heat::WaitCondition depends_on: server properties: timeout: { get_param: timeout } handle: { get_resource: handle } pool_member: type: OS::Neutron::PoolMember depends_on: waiter properties: address: { get_attr: [ server, networks, { get_param: net }, 0 ] } pool_id: { get_param: pool_id } protocol_port: { get_param: app_port } heat-6.0.0/heat_integrationtests/scenario/templates/boot_config_none_env.yaml0000664000567000056710000000040412701407050031044 0ustar jenkinsjenkins00000000000000# Defines a Heat::InstallConfigAgent config resource which performs no config. # This environment can be used when the image already has the required agents # installed and configured. resource_registry: "Heat::InstallConfigAgent": "OS::Heat::SoftwareConfig"heat-6.0.0/heat_integrationtests/scenario/templates/test_server_software_config.yaml0000664000567000056710000000757612701407050032512 0ustar jenkinsjenkins00000000000000heat_template_version: 2014-10-16 parameters: key_name: type: string flavor: type: string image: type: string network: type: string signal_transport: type: string default: CFN_SIGNAL software_config_transport: type: string default: POLL_SERVER_CFN dep1_foo: default: fooooo type: string dep1_bar: default: baaaaa type: string dep2a_bar: type: string default: barrr dep3_foo: default: fo type: string dep3_bar: default: ba type: string resources: the_sg: type: OS::Neutron::SecurityGroup properties: name: the_sg description: Ping and SSH rules: - protocol: icmp - protocol: tcp port_range_min: 22 port_range_max: 22 cfg1: type: OS::Heat::SoftwareConfig properties: group: script inputs: - name: foo - name: bar outputs: - name: result config: {get_file: cfg1.sh} cfg2a: type: OS::Heat::StructuredConfig properties: group: cfn-init inputs: - name: bar config: config: files: /tmp/cfn-init-foo: content: get_input: bar mode: '000644' cfg2b: type: OS::Heat::SoftwareConfig properties: group: script outputs: - name: result config: | #!/bin/sh echo -n "The file /tmp/cfn-init-foo contains `cat /tmp/cfn-init-foo` for server $deploy_server_id during $deploy_action" > $heat_outputs_path.result cfg3: type: OS::Heat::SoftwareConfig properties: group: puppet inputs: - name: foo - name: bar outputs: - name: result config: {get_file: cfg3.pp} dep1: type: OS::Heat::SoftwareDeployment properties: config: get_resource: cfg1 server: get_resource: server input_values: foo: {get_param: dep1_foo} bar: {get_param: dep1_bar} signal_transport: {get_param: signal_transport} dep2a: type: OS::Heat::StructuredDeployment properties: name: 10_dep2a signal_transport: NO_SIGNAL config: get_resource: cfg2a server: get_resource: server input_values: bar: {get_param: dep2a_bar} dep2b: type: OS::Heat::SoftwareDeployment properties: name: 20_dep2b config: get_resource: cfg2b server: get_resource: server signal_transport: {get_param: signal_transport} dep3: type: OS::Heat::SoftwareDeployment properties: config: get_resource: cfg3 server: get_resource: server input_values: foo: {get_param: dep3_foo} bar: {get_param: dep3_bar} signal_transport: {get_param: signal_transport} cfg_user_data: type: Heat::InstallConfigAgent server: type: OS::Nova::Server properties: image: {get_param: image} flavor: {get_param: flavor} key_name: {get_param: key_name} security_groups: - {get_resource: the_sg} networks: - network: {get_param: network} user_data_format: SOFTWARE_CONFIG software_config_transport: {get_param: software_config_transport} user_data: {get_attr: [cfg_user_data, config]} outputs: res1: value: result: {get_attr: [dep1, result]} stdout: {get_attr: [dep1, deploy_stdout]} stderr: {get_attr: [dep1, deploy_stderr]} status_code: {get_attr: [dep1, deploy_status_code]} res2: value: result: {get_attr: [dep2b, result]} stdout: {get_attr: [dep2b, deploy_stdout]} stderr: {get_attr: [dep2b, deploy_stderr]} status_code: {get_attr: [dep2b, deploy_status_code]} res3: value: result: {get_attr: [dep3, result]} stdout: {get_attr: [dep3, deploy_stdout]} stderr: {get_attr: [dep3, deploy_stderr]} status_code: {get_attr: [dep3, deploy_status_code]} heat-6.0.0/heat_integrationtests/scenario/test_autoscaling_lbv2.py0000664000567000056710000000374312701407053026666 0ustar jenkinsjenkins00000000000000# # 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 heat_integrationtests.scenario import scenario_base class AutoscalingLoadBalancerv2Test(scenario_base.ScenarioTestsBase): """The class is responsible for testing ASG + LBv2 scenario. The very common use case tested is an autoscaling group of some web application servers behind a loadbalancer. """ def setUp(self): super(AutoscalingLoadBalancerv2Test, self).setUp() self.template_name = 'test_autoscaling_lbv2_neutron.yaml' self.app_server_template_name = 'app_server_lbv2_neutron.yaml' self.webapp_template_name = 'netcat-webapp.yaml' if not self.is_network_extension_supported('lbaasv2'): self.skipTest('LBaasv2 extension not available, skipping') def test_autoscaling_loadbalancer_neutron(self): """Check work of AutoScaing and Neutron LBaaS v2 resource in Heat. The scenario is the following: 1. Launch a stack with a load balancer and autoscaling group of one server, wait until stack create is complete. 2. Check that there is only one distinctive response from loadbalanced IP. 3. Signal the scale_up policy, wait until all resources in autoscaling group are complete. 4. Check that now there are two distinctive responses from loadbalanced IP. """ # TODO(MRV): Place holder for AutoScaing and Neutron LBaaS v2 test pass heat-6.0.0/heat_integrationtests/scenario/scenario_base.py0000664000567000056710000000532112701407050025155 0ustar jenkinsjenkins00000000000000# 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 oslo_utils import reflection from heat_integrationtests.common import test class ScenarioTestsBase(test.HeatIntegrationTest): """This class defines common parameters for scenario tests.""" def setUp(self): super(ScenarioTestsBase, self).setUp() self.check_skip() self.client = self.orchestration_client self.sub_dir = 'templates' self.assign_keypair() if not self.conf.fixed_network_name: raise self.skipException("No default network configured to test") self.net = self._get_network() if not self.conf.image_ref: raise self.skipException("No image configured to test") if not self.conf.instance_type: raise self.skipException("No flavor configured to test") if not self.conf.minimal_image_ref: raise self.skipException("No minimal image configured to test") if not self.conf.minimal_instance_type: raise self.skipException("No minimal flavor configured to test") def launch_stack(self, template_name, expected_status='CREATE_COMPLETE', parameters=None, **kwargs): template = self._load_template(__file__, template_name, self.sub_dir) parameters = parameters or {} if kwargs.get('add_parameters'): parameters.update(kwargs['add_parameters']) stack_id = self.stack_create( stack_name=kwargs.get('stack_name'), template=template, files=kwargs.get('files'), parameters=parameters, environment=kwargs.get('environment'), expected_status=expected_status ) return stack_id def check_skip(self): test_cls_name = reflection.get_class_name(self, fully_qualified=False) test_method_name = '.'.join([test_cls_name, self._testMethodName]) test_skipped = (self.conf.skip_scenario_test_list and ( test_cls_name in self.conf.skip_scenario_test_list or test_method_name in self.conf.skip_scenario_test_list)) if self.conf.skip_scenario_tests or test_skipped: self.skipTest('Test disabled in conf, skipping') heat-6.0.0/heat_integrationtests/scenario/test_volumes.py0000664000567000056710000001265612701407050025122 0ustar jenkinsjenkins00000000000000# 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 cinderclient import exceptions as cinder_exceptions from oslo_log import log as logging import six from heat_integrationtests.common import exceptions from heat_integrationtests.scenario import scenario_base LOG = logging.getLogger(__name__) class VolumeBackupRestoreIntegrationTest(scenario_base.ScenarioTestsBase): """Class is responsible for testing of volume backup.""" def setUp(self): super(VolumeBackupRestoreIntegrationTest, self).setUp() self.volume_description = 'A test volume description 123' self.volume_size = self.conf.volume_size def _cinder_verify(self, volume_id, expected_status='available'): self.assertIsNotNone(volume_id) volume = self.volume_client.volumes.get(volume_id) self.assertIsNotNone(volume) self.assertEqual(expected_status, volume.status) self.assertEqual(self.volume_size, volume.size) self.assertEqual(self.volume_description, volume.display_description) def _outputs_verify(self, stack, expected_status='available'): self.assertEqual(expected_status, self._stack_output(stack, 'status')) self.assertEqual(six.text_type(self.volume_size), self._stack_output(stack, 'size')) self.assertEqual(self.volume_description, self._stack_output(stack, 'display_description')) def check_stack(self, stack_id): stack = self.client.stacks.get(stack_id) # Verify with cinder that the volume exists, with matching details volume_id = self._stack_output(stack, 'volume_id') self._cinder_verify(volume_id, expected_status='in-use') # Verify the stack outputs are as expected self._outputs_verify(stack, expected_status='in-use') # Delete the stack and ensure a backup is created for volume_id # but the volume itself is gone self._stack_delete(stack_id) self.assertRaises(cinder_exceptions.NotFound, self.volume_client.volumes.get, volume_id) backups = self.volume_client.backups.list() self.assertIsNotNone(backups) backups_filtered = [b for b in backups if b.volume_id == volume_id] self.assertEqual(1, len(backups_filtered)) backup = backups_filtered[0] self.addCleanup(self.volume_client.backups.delete, backup.id) # Now, we create another stack where the volume is created from the # backup created by the previous stack try: stack_identifier2 = self.launch_stack( template_name='test_volumes_create_from_backup.yaml', add_parameters={'backup_id': backup.id}) stack2 = self.client.stacks.get(stack_identifier2) except exceptions.StackBuildErrorException as e: LOG.error("Halting test due to bug: #1382300") LOG.exception(e) return # Verify with cinder that the volume exists, with matching details volume_id2 = self._stack_output(stack2, 'volume_id') self._cinder_verify(volume_id2, expected_status='in-use') # Verify the stack outputs are as expected self._outputs_verify(stack2, expected_status='in-use') testfile_data = self._stack_output(stack2, 'testfile_data') self.assertEqual('{"instance1": "Volume Data:ateststring"}', testfile_data) # Delete the stack and ensure the volume is gone self._stack_delete(stack_identifier2) self.assertRaises(cinder_exceptions.NotFound, self.volume_client.volumes.get, volume_id2) def test_cinder_volume_create_backup_restore(self): """Ensure the 'Snapshot' deletion policy works. This requires a more complex test, but it tests several aspects of the heat cinder resources: 1. Create a volume, attach it to an instance, write some data to it 2. Delete the stack, with 'Snapshot' specified, creates a backup 3. Check the snapshot has created a volume backup 4. Create a new stack, where the volume is created from the backup 5. Verify the test data written in (1) is present in the new volume """ parameters = { 'key_name': self.keypair_name, 'instance_type': self.conf.minimal_instance_type, 'image_id': self.conf.minimal_image_ref, 'volume_description': self.volume_description, 'timeout': self.conf.build_timeout, 'network': self.net['id'] } # Launch stack stack_id = self.launch_stack( template_name='test_volumes_delete_snapshot.yaml', parameters=parameters, add_parameters={'volume_size': self.volume_size} ) # Check stack self.check_stack(stack_id) heat-6.0.0/heat_integrationtests/scenario/test_autoscaling_lb.py0000664000567000056710000001121112701407050026400 0ustar jenkinsjenkins00000000000000# # 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 time import requests from heat_integrationtests.common import test from heat_integrationtests.scenario import scenario_base class AutoscalingLoadBalancerTest(scenario_base.ScenarioTestsBase): """The class is responsible for testing ASG + LBv1 scenario. The very common use case tested is an autoscaling group of some web application servers behind a loadbalancer. """ def setUp(self): super(AutoscalingLoadBalancerTest, self).setUp() self.template_name = 'test_autoscaling_lb_neutron.yaml' self.app_server_template_name = 'app_server_neutron.yaml' self.webapp_template_name = 'netcat-webapp.yaml' if not self.is_network_extension_supported('lbaas'): self.skipTest('LBaas v1 extension not available, skipping') def check_num_responses(self, url, expected_num, retries=10): resp = set() for count in range(retries): time.sleep(1) try: r = requests.get(url, verify=self.verify_cert) except requests.exceptions.ConnectionError: # The LB may not be up yet, let's retry continue # skip unsuccessful requests if r.status_code == 200: resp.add(r.text) self.assertEqual(expected_num, len(resp)) def autoscale_complete(self, stack_id, expected_num): res_list = self.client.resources.list(stack_id) all_res_complete = all(res.resource_status in ('UPDATE_COMPLETE', 'CREATE_COMPLETE') for res in res_list) all_res = len(res_list) == expected_num return all_res and all_res_complete def test_autoscaling_loadbalancer_neutron(self): """Check work of AutoScaing and Neutron LBaaS v1 resource in Heat. The scenario is the following: 1. Launch a stack with a load balancer and autoscaling group of one server, wait until stack create is complete. 2. Check that there is only one distinctive response from loadbalanced IP. 3. Signal the scale_up policy, wait until all resources in autoscaling group are complete. 4. Check that now there are two distinctive responses from loadbalanced IP. """ parameters = { 'flavor': self.conf.minimal_instance_type, 'image': self.conf.minimal_image_ref, 'net': self.conf.fixed_network_name, 'subnet': self.conf.fixed_subnet_name, 'public_net': self.conf.floating_network_name, 'app_port': 8080, 'lb_port': 80, 'timeout': 600 } app_server_template = self._load_template( __file__, self.app_server_template_name, self.sub_dir ) webapp_template = self._load_template( __file__, self.webapp_template_name, self.sub_dir ) files = {'appserver.yaml': app_server_template, 'webapp.yaml': webapp_template} env = {'resource_registry': {'OS::Test::NeutronAppServer': 'appserver.yaml', 'OS::Test::WebAppConfig': 'webapp.yaml'}} # Launch stack sid = self.launch_stack( template_name=self.template_name, parameters=parameters, files=files, environment=env ) stack = self.client.stacks.get(sid) lb_url = self._stack_output(stack, 'lburl') # Check number of distinctive responces, must be 1 self.check_num_responses(lb_url, 1) # Signal the scaling hook self.client.resources.signal(sid, 'scale_up') # Wait for AutoScalingGroup update to finish asg = self.client.resources.get(sid, 'asg') test.call_until_true(self.conf.build_timeout, self.conf.build_interval, self.autoscale_complete, asg.physical_resource_id, 2) # Check number of distinctive responses, must now be 2 self.check_num_responses(lb_url, 2) heat-6.0.0/etc/0000775000567000056710000000000012701407211014345 5ustar jenkinsjenkins00000000000000heat-6.0.0/etc/heat/0000775000567000056710000000000012701407211015266 5ustar jenkinsjenkins00000000000000heat-6.0.0/etc/heat/environment.d/0000775000567000056710000000000012701407211020054 5ustar jenkinsjenkins00000000000000heat-6.0.0/etc/heat/environment.d/default.yaml0000664000567000056710000000066412701407050022373 0ustar jenkinsjenkins00000000000000 resource_registry: # allow older templates with Quantum in them. "OS::Quantum*": "OS::Neutron*" # Choose your implementation of AWS::CloudWatch::Alarm "AWS::CloudWatch::Alarm": "file:///etc/heat/templates/AWS_CloudWatch_Alarm.yaml" #"AWS::CloudWatch::Alarm": "OS::Heat::CWLiteAlarm" "OS::Metering::Alarm": "OS::Ceilometer::Alarm" "AWS::RDS::DBInstance": "file:///etc/heat/templates/AWS_RDS_DBInstance.yaml" heat-6.0.0/etc/heat/README-heat.conf.txt0000664000567000056710000000017412701407050020632 0ustar jenkinsjenkins00000000000000To generate the sample heat.conf file, run the following command from the top level of the heat directory: tox -egenconfig heat-6.0.0/etc/heat/templates/0000775000567000056710000000000012701407211017264 5ustar jenkinsjenkins00000000000000heat-6.0.0/etc/heat/templates/AWS_RDS_DBInstance.yaml0000664000567000056710000000730612701407050023353 0ustar jenkinsjenkins00000000000000HeatTemplateFormatVersion: '2012-12-12' Description: 'Builtin AWS::RDS::DBInstance' Parameters: AllocatedStorage: Type: String DBInstanceClass: Type: String DBName: Type: String DBSecurityGroups: Type: CommaDelimitedList Default: '' Engine: Type: String AllowedValues: ['MySQL'] MasterUsername: Type: String MasterUserPassword: Type: String Port: Type: String Default: '3306' KeyName: Type: String Default: '' Mappings: DBInstanceToInstance: db.m1.small: {Instance: m1.small} db.m1.large: {Instance: m1.large} db.m1.xlarge: {Instance: m1.xlarge} db.m2.xlarge: {Instance: m2.xlarge} db.m2.2xlarge: {Instance: m2.2xlarge} db.m2.4xlarge: {Instance: m2.4xlarge} Resources: ServerSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: 'Enable SSH access' SecurityGroupIngress: - IpProtocol: icmp FromPort: '-1' ToPort: '-1' CidrIp: '0.0.0.0/0' - IpProtocol: tcp FromPort: '22' ToPort : '22' CidrIp : '0.0.0.0/0' - IpProtocol: tcp FromPort: {Ref: Port} ToPort : {Ref: Port} CidrIp : '0.0.0.0/0' DatabaseInstance: Type: AWS::EC2::Instance Metadata: AWS::CloudFormation::Init: config: files: /tmp/db_setup.sql: content: 'Fn::Replace': - DBName: {Ref: DBName} MasterUserPassword: {Ref: MasterUserPassword} MasterUsername: {Ref: MasterUsername} - | CREATE DATABASE DBName; GRANT ALL PRIVILEGES ON DBName.* TO "MasterUsername"@"%" IDENTIFIED BY "MasterUserPassword"; FLUSH PRIVILEGES; EXIT mode: '000644' owner: root group: root packages: yum: mariadb: [] mariadb-server: [] services: systemd: mysqld: enabled: true ensureRunning: true Properties: ImageId: F19-x86_64-cfntools InstanceType: {'Fn::FindInMap': [DBInstanceToInstance, {Ref: DBInstanceClass}, Instance]} KeyName: {Ref: KeyName} SecurityGroups: [{"Ref" : "ServerSecurityGroup"}] UserData: Fn::Base64: Fn::Replace: - 'AWS::StackName': {Ref: 'AWS::StackName'} 'AWS::Region': {Ref: 'AWS::Region'} MasterUserPassword: {Ref: MasterUserPassword} WaitHandle: {Ref: WaitHandle} - | #!/bin/bash -v # iptables -F # Helper function function error_exit { /opt/aws/bin/cfn-signal -e 1 -r \"$1\" 'WaitHandle' exit 1 } /opt/aws/bin/cfn-init -s AWS::StackName -r DatabaseInstance --region AWS::Region || error_exit 'Failed to run cfn-init' # Setup MySQL root password and create a user mysqladmin -u root password 'MasterUserPassword' mysql -u root --password='MasterUserPassword' < /tmp/db_setup.sql || error_exit 'Failed to setup mysql' # Database setup completed, signal success /opt/aws/bin/cfn-signal -e 0 -r "MySQL server setup complete" 'WaitHandle' WaitHandle: Type: AWS::CloudFormation::WaitConditionHandle WaitCondition: Type: AWS::CloudFormation::WaitCondition DependsOn: DatabaseInstance Properties: Handle: {Ref: WaitHandle} Timeout: "600" Outputs: Endpoint.Address: {'Fn::GetAtt': [DatabaseInstance, PublicIp]} Endpoint.Port: {Ref: Port} heat-6.0.0/etc/heat/templates/AWS_CloudWatch_Alarm.yaml0000664000567000056710000000476412701407050024047 0ustar jenkinsjenkins00000000000000HeatTemplateFormatVersion: '2012-12-12' Description: AWS::CloudWatch::Alarm using Ceilometer. Parameters: AlarmDescription: Type: String Default: An alarm EvaluationPeriods: Type: String MetricName: Type: String Namespace: Type: String Default: system/linux Period: Type: String ComparisonOperator: Type: String AllowedValues: [GreaterThanOrEqualToThreshold, GreaterThanThreshold, LessThanThreshold, LessThanOrEqualToThreshold] Statistic: Type: String AllowedValues: [SampleCount, Average, Sum, Minimum, Maximum] Threshold: Type: String Units: Type: String AllowedValues: [Seconds, Microseconds, Milliseconds, Bytes, Kilobytes, Megabytes, Gigabytes, Terabytes, Bits, Kilobits, Megabits, Gigabits, Terabits, Percent, Count, Bytes/Second, Kilobytes/Second, Megabytes/Second, Gigabytes/Second, Terabytes/Second, Bits/Second, Kilobits/Second, Megabits/Second, Gigabits/Second, Terabits/Second, Count/Second, None] Default: None AlarmActions: Type: CommaDelimitedList Default: '' OKActions: Type: CommaDelimitedList Default: '' InsufficientDataActions: Type: CommaDelimitedList Default: '' Dimensions: Type: CommaDelimitedList Default: '' Mappings: ComparisonOperatorMap: LessThanOrEqualToThreshold: {Ceilometer: le} LessThanThreshold: {Ceilometer: lt} GreaterThanThreshold: {Ceilometer: gt} GreaterThanOrEqualToThreshold: {Ceilometer: ge} StatisticMap: SampleCount: {Ceilometer: count} Average: {Ceilometer: avg} Sum: {Ceilometer: sum} Minimum: {Ceilometer: min} Maximum: {Ceilometer: max} Resources: __alarm__: Type: OS::Ceilometer::Alarm Properties: description: Ref: AlarmDescription meter_name: Ref: MetricName period: Ref: Period evaluation_periods: Ref: EvaluationPeriods repeat_actions: true threshold: Ref: Threshold alarm_actions: Ref: AlarmActions ok_actions: Ref: OKActions insufficient_data_actions: Ref: InsufficientDataActions statistic: "Fn::FindInMap": [StatisticMap, {Ref: Statistic}, Ceilometer] comparison_operator: "Fn::FindInMap": [ComparisonOperatorMap, {Ref: ComparisonOperator}, Ceilometer] matching_metadata: "Fn::MemberListToMap": [Name, Value, {Ref: Dimensions}] heat-6.0.0/etc/heat/policy.json0000664000567000056710000000772512701407050017474 0ustar jenkinsjenkins00000000000000{ "context_is_admin": "role:admin", "deny_stack_user": "not role:heat_stack_user", "deny_everybody": "!", "cloudformation:ListStacks": "rule:deny_stack_user", "cloudformation:CreateStack": "rule:deny_stack_user", "cloudformation:DescribeStacks": "rule:deny_stack_user", "cloudformation:DeleteStack": "rule:deny_stack_user", "cloudformation:UpdateStack": "rule:deny_stack_user", "cloudformation:CancelUpdateStack": "rule:deny_stack_user", "cloudformation:DescribeStackEvents": "rule:deny_stack_user", "cloudformation:ValidateTemplate": "rule:deny_stack_user", "cloudformation:GetTemplate": "rule:deny_stack_user", "cloudformation:EstimateTemplateCost": "rule:deny_stack_user", "cloudformation:DescribeStackResource": "", "cloudformation:DescribeStackResources": "rule:deny_stack_user", "cloudformation:ListStackResources": "rule:deny_stack_user", "cloudwatch:DeleteAlarms": "rule:deny_stack_user", "cloudwatch:DescribeAlarmHistory": "rule:deny_stack_user", "cloudwatch:DescribeAlarms": "rule:deny_stack_user", "cloudwatch:DescribeAlarmsForMetric": "rule:deny_stack_user", "cloudwatch:DisableAlarmActions": "rule:deny_stack_user", "cloudwatch:EnableAlarmActions": "rule:deny_stack_user", "cloudwatch:GetMetricStatistics": "rule:deny_stack_user", "cloudwatch:ListMetrics": "rule:deny_stack_user", "cloudwatch:PutMetricAlarm": "rule:deny_stack_user", "cloudwatch:PutMetricData": "", "cloudwatch:SetAlarmState": "rule:deny_stack_user", "actions:action": "rule:deny_stack_user", "build_info:build_info": "rule:deny_stack_user", "events:index": "rule:deny_stack_user", "events:show": "rule:deny_stack_user", "resource:index": "rule:deny_stack_user", "resource:metadata": "", "resource:signal": "", "resource:mark_unhealthy": "rule:deny_stack_user", "resource:show": "rule:deny_stack_user", "stacks:abandon": "rule:deny_stack_user", "stacks:create": "rule:deny_stack_user", "stacks:delete": "rule:deny_stack_user", "stacks:detail": "rule:deny_stack_user", "stacks:export": "rule:deny_stack_user", "stacks:generate_template": "rule:deny_stack_user", "stacks:global_index": "rule:deny_everybody", "stacks:index": "rule:deny_stack_user", "stacks:list_resource_types": "rule:deny_stack_user", "stacks:list_template_versions": "rule:deny_stack_user", "stacks:list_template_functions": "rule:deny_stack_user", "stacks:lookup": "", "stacks:preview": "rule:deny_stack_user", "stacks:resource_schema": "rule:deny_stack_user", "stacks:show": "rule:deny_stack_user", "stacks:template": "rule:deny_stack_user", "stacks:update": "rule:deny_stack_user", "stacks:update_patch": "rule:deny_stack_user", "stacks:preview_update": "rule:deny_stack_user", "stacks:preview_update_patch": "rule:deny_stack_user", "stacks:validate_template": "rule:deny_stack_user", "stacks:snapshot": "rule:deny_stack_user", "stacks:show_snapshot": "rule:deny_stack_user", "stacks:delete_snapshot": "rule:deny_stack_user", "stacks:list_snapshots": "rule:deny_stack_user", "stacks:restore_snapshot": "rule:deny_stack_user", "stacks:list_outputs": "rule:deny_stack_user", "stacks:show_output": "rule:deny_stack_user", "software_configs:global_index": "rule:deny_everybody", "software_configs:index": "rule:deny_stack_user", "software_configs:create": "rule:deny_stack_user", "software_configs:show": "rule:deny_stack_user", "software_configs:delete": "rule:deny_stack_user", "software_deployments:index": "rule:deny_stack_user", "software_deployments:create": "rule:deny_stack_user", "software_deployments:show": "rule:deny_stack_user", "software_deployments:update": "rule:deny_stack_user", "software_deployments:delete": "rule:deny_stack_user", "software_deployments:metadata": "", "service:index": "rule:context_is_admin", "resource_types:OS::Nova::Flavor": "rule:context_is_admin" } heat-6.0.0/etc/heat/api-paste.ini0000664000567000056710000000713612701407050017662 0ustar jenkinsjenkins00000000000000 # heat-api pipeline [pipeline:heat-api] pipeline = cors request_id faultwrap http_proxy_to_wsgi versionnegotiation osprofiler authurl authtoken context apiv1app # heat-api pipeline for standalone heat # ie. uses alternative auth backend that authenticates users against keystone # using username and password instead of validating token (which requires # an admin/service token). # To enable, in heat.conf: # [paste_deploy] # flavor = standalone # [pipeline:heat-api-standalone] pipeline = cors request_id faultwrap http_proxy_to_wsgi versionnegotiation authurl authpassword context apiv1app # heat-api pipeline for custom cloud backends # i.e. in heat.conf: # [paste_deploy] # flavor = custombackend # [pipeline:heat-api-custombackend] pipeline = cors request_id faultwrap versionnegotiation context custombackendauth apiv1app # heat-api-cfn pipeline [pipeline:heat-api-cfn] pipeline = cors cfnversionnegotiation osprofiler ec2authtoken authtoken context apicfnv1app # heat-api-cfn pipeline for standalone heat # relies exclusively on authenticating with ec2 signed requests [pipeline:heat-api-cfn-standalone] pipeline = cors cfnversionnegotiation ec2authtoken context apicfnv1app # heat-api-cloudwatch pipeline [pipeline:heat-api-cloudwatch] pipeline = cors versionnegotiation osprofiler ec2authtoken authtoken context apicwapp # heat-api-cloudwatch pipeline for standalone heat # relies exclusively on authenticating with ec2 signed requests [pipeline:heat-api-cloudwatch-standalone] pipeline = cors versionnegotiation ec2authtoken context apicwapp [app:apiv1app] paste.app_factory = heat.common.wsgi:app_factory heat.app_factory = heat.api.openstack.v1:API [app:apicfnv1app] paste.app_factory = heat.common.wsgi:app_factory heat.app_factory = heat.api.cfn.v1:API [app:apicwapp] paste.app_factory = heat.common.wsgi:app_factory heat.app_factory = heat.api.cloudwatch:API [filter:versionnegotiation] paste.filter_factory = heat.common.wsgi:filter_factory heat.filter_factory = heat.api.openstack:version_negotiation_filter [filter:cors] paste.filter_factory = oslo_middleware.cors:filter_factory oslo_config_project = heat [filter:faultwrap] paste.filter_factory = heat.common.wsgi:filter_factory heat.filter_factory = heat.api.openstack:faultwrap_filter [filter:cfnversionnegotiation] paste.filter_factory = heat.common.wsgi:filter_factory heat.filter_factory = heat.api.cfn:version_negotiation_filter [filter:cwversionnegotiation] paste.filter_factory = heat.common.wsgi:filter_factory heat.filter_factory = heat.api.cloudwatch:version_negotiation_filter [filter:context] paste.filter_factory = heat.common.context:ContextMiddleware_filter_factory [filter:ec2authtoken] paste.filter_factory = heat.api.aws.ec2token:EC2Token_filter_factory [filter:http_proxy_to_wsgi] paste.filter_factory = oslo_middleware:HTTPProxyToWSGI.factory # Middleware to set auth_url header appropriately [filter:authurl] paste.filter_factory = heat.common.auth_url:filter_factory # Auth middleware that validates token against keystone [filter:authtoken] paste.filter_factory = keystonemiddleware.auth_token:filter_factory # Auth middleware that validates username/password against keystone [filter:authpassword] paste.filter_factory = heat.common.auth_password:filter_factory # Auth middleware that validates against custom backend [filter:custombackendauth] paste.filter_factory = heat.common.custom_backend_auth:filter_factory # Middleware to set x-openstack-request-id in http response header [filter:request_id] paste.filter_factory = oslo_middleware.request_id:RequestId.factory [filter:osprofiler] paste.filter_factory = osprofiler.web:WsgiMiddleware.factory heat-6.0.0/babel.cfg0000664000567000056710000000002012701407050015311 0ustar jenkinsjenkins00000000000000[python: **.py] heat-6.0.0/openstack-common.conf0000664000567000056710000000021512701407050017715 0ustar jenkinsjenkins00000000000000[DEFAULT] # The list of modules to copy from oslo-incubator module=crypto # The base module to hold the copy of openstack.common base=heat heat-6.0.0/.coveragerc0000664000567000056710000000015312701407050015713 0ustar jenkinsjenkins00000000000000[run] branch = True source = heat,contrib omit = */tests/*,heat/openstack/* [report] ignore_errors = True heat-6.0.0/setup.cfg0000664000567000056710000002120612701407211015414 0ustar jenkinsjenkins00000000000000[metadata] name = heat summary = OpenStack Orchestration description-file = README.rst author = OpenStack author-email = openstack-dev@lists.openstack.org home-page = http://www.openstack.org/ classifier = Environment :: OpenStack Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.4 [files] packages = heat scripts = bin/heat-db-setup bin/heat-keystone-setup bin/heat-keystone-setup-domain [entry_points] console_scripts = heat-api = heat.cmd.api:main heat-api-cfn = heat.cmd.api_cfn:main heat-api-cloudwatch = heat.cmd.api_cloudwatch:main heat-engine = heat.cmd.engine:main heat-manage = heat.cmd.manage:main wsgi_scripts = heat-wsgi-api = heat.httpd.heat_api:init_application heat-wsgi-api-cfn = heat.httpd.heat_api_cfn:init_application heat-wsgi-api-cloudwatch = heat.httpd.heat_api_cloudwatch:init_application oslo.config.opts = heat.common.config = heat.common.config:list_opts heat.common.context = heat.common.context:list_opts heat.common.crypt = heat.common.crypt:list_opts heat.common.heat_keystoneclient = heat.common.heat_keystoneclient:list_opts heat.common.wsgi = heat.common.wsgi:list_opts heat.engine.clients = heat.engine.clients:list_opts heat.engine.notification = heat.engine.notification:list_opts heat.engine.resources = heat.engine.resources:list_opts heat.openstack.common.policy = heat.openstack.common.policy:list_opts heat.api.middleware.ssl = heat.api.middleware.ssl:list_opts heat.api.aws.ec2token = heat.api.aws.ec2token:list_opts heat_integrationtests.common.config = heat_integrationtests.common.config:list_opts oslo.config.opts.defaults = heat.common.config = heat.common.config:set_config_defaults heat.clients = barbican = heat.engine.clients.os.barbican:BarbicanClientPlugin ceilometer = heat.engine.clients.os.ceilometer:CeilometerClientPlugin cinder = heat.engine.clients.os.cinder:CinderClientPlugin designate = heat.engine.clients.os.designate:DesignateClientPlugin glance = heat.engine.clients.os.glance:GlanceClientPlugin heat = heat.engine.clients.os.heat_plugin:HeatClientPlugin keystone = heat.engine.clients.os.keystone:KeystoneClientPlugin magnum = heat.engine.clients.os.magnum:MagnumClientPlugin manila = heat.engine.clients.os.manila:ManilaClientPlugin mistral = heat.engine.clients.os.mistral:MistralClientPlugin monasca = heat.engine.clients.os.monasca:MonascaClientPlugin nova = heat.engine.clients.os.nova:NovaClientPlugin neutron = heat.engine.clients.os.neutron:NeutronClientPlugin sahara = heat.engine.clients.os.sahara:SaharaClientPlugin senlin = heat.engine.clients.os.senlin:SenlinClientPlugin swift = heat.engine.clients.os.swift:SwiftClientPlugin trove = heat.engine.clients.os.trove:TroveClientPlugin zaqar = heat.engine.clients.os.zaqar:ZaqarClientPlugin heat.constraints = # common constraints cron_expression = heat.engine.constraint.common_constraints:CRONExpressionConstraint ip_addr = heat.engine.constraint.common_constraints:IPConstraint iso_8601 = heat.engine.constraint.common_constraints:ISO8601Constraint mac_addr = heat.engine.constraint.common_constraints:MACConstraint net_cidr = heat.engine.constraint.common_constraints:CIDRConstraint test_constr = heat.engine.constraint.common_constraints:TestConstraintDelay timezone = heat.engine.constraint.common_constraints:TimezoneConstraint # service constraints barbican.secret = heat.engine.clients.os.barbican:SecretConstraint cinder.backup = heat.engine.clients.os.cinder:VolumeBackupConstraint cinder.snapshot = heat.engine.clients.os.cinder:VolumeSnapshotConstraint cinder.volume = heat.engine.clients.os.cinder:VolumeConstraint cinder.vtype = heat.engine.clients.os.cinder:VolumeTypeConstraint designate.domain = heat.engine.clients.os.designate:DesignateDomainConstraint glance.image = heat.engine.clients.os.glance:ImageConstraint keystone.domain = heat.engine.clients.os.keystone:KeystoneDomainConstraint keystone.group = heat.engine.clients.os.keystone:KeystoneGroupConstraint keystone.project = heat.engine.clients.os.keystone:KeystoneProjectConstraint keystone.region = heat.engine.clients.os.keystone:KeystoneRegionConstraint keystone.role = heat.engine.clients.os.keystone:KeystoneRoleConstraint keystone.service = heat.engine.clients.os.keystone:KeystoneServiceConstraint keystone.user = heat.engine.clients.os.keystone:KeystoneUserConstraint magnum.baymodel = heat.engine.clients.os.magnum:BaymodelConstraint manila.share_network = heat.engine.clients.os.manila:ManilaShareNetworkConstraint manila.share_snapshot = heat.engine.clients.os.manila:ManilaShareSnapshotConstraint manila.share_type = heat.engine.clients.os.manila:ManilaShareTypeConstraint monasca.notification = heat.engine.clients.os.monasca:MonascaNotificationConstraint neutron.address_scope = heat.engine.clients.os.neutron.neutron_constraints:AddressScopeConstraint neutron.lbaas.listener = heat.engine.clients.os.neutron.lbaas_constraints:ListenerConstraint neutron.lbaas.loadbalancer = heat.engine.clients.os.neutron.lbaas_constraints:LoadbalancerConstraint neutron.lbaas.pool = heat.engine.clients.os.neutron.lbaas_constraints:PoolConstraint neutron.lbaas.provider = heat.engine.clients.os.neutron.lbaas_constraints:LBaasV2ProviderConstraint neutron.lb.provider = heat.engine.clients.os.neutron.neutron_constraints:LBaasV1ProviderConstraint neutron.network = heat.engine.clients.os.neutron.neutron_constraints:NetworkConstraint neutron.port = heat.engine.clients.os.neutron.neutron_constraints:PortConstraint neutron.qos_policy = heat.engine.clients.os.neutron.neutron_constraints:QoSPolicyConstraint neutron.router = heat.engine.clients.os.neutron.neutron_constraints:RouterConstraint neutron.subnet = heat.engine.clients.os.neutron.neutron_constraints:SubnetConstraint neutron.subnetpool = heat.engine.clients.os.neutron.neutron_constraints:SubnetPoolConstraint nova.flavor = heat.engine.clients.os.nova:FlavorConstraint nova.host = heat.engine.clients.os.nova:HostConstraint nova.keypair = heat.engine.clients.os.nova:KeypairConstraint nova.network = heat.engine.clients.os.nova:NetworkConstraint nova.server = heat.engine.clients.os.nova:ServerConstraint sahara.image = heat.engine.clients.os.sahara:ImageConstraint sahara.plugin = heat.engine.clients.os.sahara:PluginConstraint senlin.cluster = heat.engine.clients.os.senlin:ClusterConstraint senlin.policy_type = heat.engine.clients.os.senlin:PolicyTypeConstraint senlin.profile = heat.engine.clients.os.senlin:ProfileConstraint senlin.profile_type = heat.engine.clients.os.senlin:ProfileTypeConstraint trove.flavor = heat.engine.clients.os.trove:FlavorConstraint heat.stack_lifecycle_plugins = heat.event_sinks = zaqar-queue = heat.engine.clients.os.zaqar:ZaqarEventSink heat.templates = heat_template_version.2013-05-23 = heat.engine.hot.template:HOTemplate20130523 heat_template_version.2014-10-16 = heat.engine.hot.template:HOTemplate20141016 heat_template_version.2015-04-30 = heat.engine.hot.template:HOTemplate20150430 heat_template_version.2015-10-15 = heat.engine.hot.template:HOTemplate20151015 heat_template_version.2016-04-08 = heat.engine.hot.template:HOTemplate20160408 HeatTemplateFormatVersion.2012-12-12 = heat.engine.cfn.template:HeatTemplate AWSTemplateFormatVersion.2010-09-09 = heat.engine.cfn.template:CfnTemplate oslo.messaging.notify.drivers = heat.openstack.common.notifier.log_notifier = oslo_messaging.notify._impl_log:LogDriver heat.openstack.common.notifier.no_op_notifier = oslo_messaging.notify._impl_noop:NoOpDriver heat.openstack.common.notifier.rpc_notifier2 = oslo_messaging.notify.messaging:MessagingV2Driver heat.openstack.common.notifier.rpc_notifier = oslo_messaging.notify.messaging:MessagingDriver heat.openstack.common.notifier.test_notifier = oslo_messaging.notify._impl_test:TestDriver [global] setup-hooks = pbr.hooks.setup_hook [compile_catalog] directory = heat/locale domain = heat [update_catalog] domain = heat output_dir = heat/locale input_file = heat/locale/heat.pot [pbr] autodoc_index_modules = true autodoc_exclude_modules = heat.testing.* heat.cmd.* heat.common.* heat.cloudinit.* heat.cfn_client.* heat.doc.* heat.db.* heat.engine.resources.* heat.locale.* heat.openstack.* *.tests.* *.resources.* [extract_messages] keywords = _ gettext ngettext l_ lazy_gettext mapping_file = babel.cfg output_file = heat/locale/heat.pot [build_sphinx] all_files = 1 build-dir = doc/build source-dir = doc/source [egg_info] tag_build = tag_svn_revision = 0 tag_date = 0 heat-6.0.0/install.sh0000775000567000056710000000636112701407050015606 0ustar jenkinsjenkins00000000000000#!/bin/bash if [[ $EUID -ne 0 ]]; then echo "This script must be run as root" >&2 exit 1 fi # Install prefix for config files (e.g. "/usr/local"). # Leave empty to install into /etc CONF_PREFIX="" LOG_DIR=/var/log/heat install -d $LOG_DIR detect_rabbit() { PKG_CMD="rpm -q" RABBIT_PKG="rabbitmq-server" QPID_PKG="qpid-cpp-server" # Detect OS type # Ubuntu has an lsb_release command which allows us to detect if it is Ubuntu if lsb_release -i 2>/dev/null | grep -iq ubuntu then PKG_CMD="dpkg -s" QPID_PKG="qpidd" fi if $PKG_CMD $RABBIT_PKG > /dev/null 2>&1 then if ! $PKG_CMD $QPID_PKG > /dev/null 2>&1 then return 0 fi fi return 1 } # Determinate is the given option present in the INI file # ini_has_option config-file section option function ini_has_option() { local file=$1 local section=$2 local option=$3 local line line=$(sed -ne "/^\[$section\]/,/^\[.*\]/ { /^$option[ \t]*=/ p; }" "$file") [ -n "$line" ] } # Set an option in an INI file # iniset config-file section option value function iniset() { local file=$1 local section=$2 local option=$3 local value=$4 if ! grep -q "^\[$section\]" "$file"; then # Add section at the end echo -e "\n[$section]" >>"$file" fi if ! ini_has_option "$file" "$section" "$option"; then # Add it sed -i -e "/^\[$section\]/ a\\ $option = $value " "$file" else # Replace it sed -i -e "/^\[$section\]/,/^\[.*\]/ s|^\($option[ \t]*=[ \t]*\).*$|\1$value|" "$file" fi } basic_configuration() { conf_path=$1 if echo $conf_path | grep ".conf$" >/dev/null 2>&1 then iniset $target DEFAULT auth_encryption_key `hexdump -n 16 -v -e '/1 "%02x"' /dev/random` iniset $target database connection "mysql+pymysql://heat:heat@localhost/heat" BRIDGE_IP=127.0.0.1 iniset $target DEFAULT heat_metadata_server_url "http://${BRIDGE_IP}:8000/" iniset $target DEFAULT heat_watch_server_url "http://${BRIDGE_IP}:8003/" if detect_rabbit then echo "rabbitmq detected, configuring $conf_path for rabbit" >&2 iniset $conf_path DEFAULT rpc_backend kombu iniset $conf_path oslo_messaging_rabbit rabbit_password guest else echo "qpid detected, configuring $conf_path for qpid" >&2 iniset $conf_path DEFAULT rpc_backend qpid fi fi } install_dir() { local dir=$1 local prefix=$2 for fn in $(ls $dir); do f=$dir/$fn target=$prefix/$f if [ $fn = 'heat.conf.sample' ]; then target=$prefix/$dir/heat.conf fi if [ -d $f ]; then [ -d $target ] || install -d $target install_dir $f $prefix elif [ -f $target ]; then echo "NOT replacing existing config file $target" >&2 diff -u $target $f else echo "Installing $fn in $prefix/$dir" >&2 install -m 664 $f $target if [ $fn = 'heat.conf.sample' ]; then basic_configuration $target fi fi done } install_dir etc $CONF_PREFIX python setup.py install >/dev/null rm -rf build heat.egg-info heat-6.0.0/heat.egg-info/0000775000567000056710000000000012701407211016205 5ustar jenkinsjenkins00000000000000heat-6.0.0/heat.egg-info/requires.txt0000664000567000056710000000251312701407210020605 0ustar jenkinsjenkins00000000000000pbr>=1.6 Babel>=1.3 croniter>=0.3.4 cryptography>=1.0 debtcollector>=1.2.0 eventlet!=0.18.3,>=0.18.2 greenlet>=0.3.2 keystonemiddleware!=4.1.0,>=4.0.0 lxml>=2.3 netaddr!=0.7.16,>=0.7.12 oslo.cache>=1.5.0 oslo.config>=3.7.0 oslo.concurrency>=3.5.0 oslo.context>=0.2.0 oslo.db>=4.1.0 oslo.i18n>=2.1.0 oslo.log>=1.14.0 oslo.messaging>=4.0.0 oslo.middleware>=3.0.0 oslo.policy>=0.5.0 oslo.reports>=0.6.0 oslo.serialization>=1.10.0 oslo.service>=1.0.0 oslo.utils>=3.5.0 osprofiler>=1.1.0 oslo.versionedobjects>=1.5.0 PasteDeploy>=1.5.0 pycrypto>=2.6 python-barbicanclient>=3.3.0 python-ceilometerclient>=2.2.1 python-cinderclient>=1.3.1 python-designateclient>=1.5.0 python-glanceclient>=2.0.0 python-heatclient>=0.6.0 python-keystoneclient!=1.8.0,!=2.1.0,>=1.6.0 python-magnumclient>=0.2.1 python-manilaclient>=1.3.0 python-mistralclient>=1.0.0 python-neutronclient!=4.1.0,>=2.6.0 python-novaclient!=2.33.0,>=2.29.0 python-openstackclient>=2.1.0 python-saharaclient>=0.13.0 python-senlinclient>=0.3.0 python-swiftclient>=2.2.0 python-troveclient!=2.1.0,>=1.2.0 python-zaqarclient>=0.3.0 pytz>=2013.6 PyYAML>=3.1.0 requests!=2.9.0,>=2.8.1 retrying!=1.3.0,>=1.2.3 six>=1.9.0 SQLAlchemy<1.1.0,>=1.0.10 sqlalchemy-migrate>=0.9.6 stevedore>=1.5.0 WebOb>=1.2.3 [:(python_version!='2.7')] Routes!=2.0,>=1.12.3 [:(python_version=='2.7')] Routes!=2.0,!=2.1,>=1.12.3 heat-6.0.0/heat.egg-info/dependency_links.txt0000664000567000056710000000000112701407210022252 0ustar jenkinsjenkins00000000000000 heat-6.0.0/heat.egg-info/not-zip-safe0000664000567000056710000000000112701407204020435 0ustar jenkinsjenkins00000000000000 heat-6.0.0/heat.egg-info/SOURCES.txt0000664000567000056710000012151312701407211020074 0ustar jenkinsjenkins00000000000000.coveragerc .testr.conf AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst LICENSE README.rst babel.cfg bandit.yaml config-generator.conf install.sh openstack-common.conf pylintrc requirements.txt setup.cfg setup.py test-requirements.txt tox.ini uninstall.sh bin/heat-api bin/heat-api-cfn bin/heat-api-cloudwatch bin/heat-db-setup bin/heat-engine bin/heat-keystone-setup bin/heat-keystone-setup-domain bin/heat-manage contrib/heat_docker/README.md contrib/heat_docker/requirements.txt contrib/heat_docker/setup.cfg contrib/heat_docker/setup.py contrib/heat_docker/heat_docker/__init__.py contrib/heat_docker/heat_docker/resources/__init__.py contrib/heat_docker/heat_docker/resources/docker_container.py contrib/heat_docker/heat_docker/tests/__init__.py contrib/heat_docker/heat_docker/tests/fake_docker_client.py contrib/heat_docker/heat_docker/tests/test_docker_container.py contrib/rackspace/README.md contrib/rackspace/requirements.txt contrib/rackspace/setup.cfg contrib/rackspace/setup.py contrib/rackspace/heat_keystoneclient_v2/__init__.py contrib/rackspace/heat_keystoneclient_v2/client.py contrib/rackspace/heat_keystoneclient_v2/tests/__init__.py contrib/rackspace/heat_keystoneclient_v2/tests/test_client.py contrib/rackspace/rackspace/__init__.py contrib/rackspace/rackspace/clients.py contrib/rackspace/rackspace/resources/__init__.py contrib/rackspace/rackspace/resources/auto_scale.py contrib/rackspace/rackspace/resources/cloud_dns.py contrib/rackspace/rackspace/resources/cloud_loadbalancer.py contrib/rackspace/rackspace/resources/cloud_server.py contrib/rackspace/rackspace/resources/cloudnetworks.py contrib/rackspace/rackspace/resources/lb_node.py contrib/rackspace/rackspace/tests/__init__.py contrib/rackspace/rackspace/tests/test_auto_scale.py contrib/rackspace/rackspace/tests/test_cloud_loadbalancer.py contrib/rackspace/rackspace/tests/test_cloudnetworks.py contrib/rackspace/rackspace/tests/test_lb_node.py contrib/rackspace/rackspace/tests/test_rackspace_cloud_server.py contrib/rackspace/rackspace/tests/test_rackspace_dns.py devstack/upgrade/resources.sh devstack/upgrade/settings devstack/upgrade/shutdown.sh devstack/upgrade/upgrade.sh devstack/upgrade/templates/random_string.yaml doc/.gitignore doc/Makefile doc/README.rst doc/docbkx/README.rst doc/docbkx/api-ref/pom.xml doc/docbkx/api-ref/src/docbkx/api-ref.xml doc/docbkx/api-ref/src/wadls/heat-api/src/README.rst doc/docbkx/heat-admin/app_core.xml doc/docbkx/heat-admin/bk-heat-admin-guide.xml doc/docbkx/heat-admin/ch_install.xml doc/docbkx/heat-admin/ch_limitations.xml doc/docbkx/heat-admin/ch_overview.xml doc/docbkx/heat-admin/ch_preface.xml doc/docbkx/heat-admin/ch_using.xml doc/docbkx/heat-admin/pom.xml doc/source/conf.py doc/source/glossary.rst doc/source/index.rst doc/source/_templates/.placeholder doc/source/api/index.rst doc/source/contributing/blueprints.rst doc/source/contributing/index.rst doc/source/developing_guides/architecture.rst doc/source/developing_guides/gmr.rst doc/source/developing_guides/index.rst doc/source/developing_guides/pluginguide.rst doc/source/developing_guides/rally_on_gates.rst doc/source/developing_guides/schedulerhints.rst doc/source/developing_guides/supportstatus.rst doc/source/ext/__init__.py doc/source/ext/resources.py doc/source/ext/tablefromtext.py doc/source/getting_started/create_a_stack.rst doc/source/getting_started/index.rst doc/source/getting_started/jeos_building.rst doc/source/getting_started/on_devstack.rst doc/source/getting_started/on_fedora.rst doc/source/getting_started/on_other.rst doc/source/getting_started/on_ubuntu.rst doc/source/getting_started/standalone.rst doc/source/man/heat-api-cfn.rst doc/source/man/heat-api-cloudwatch.rst doc/source/man/heat-api.rst doc/source/man/heat-db-setup.rst doc/source/man/heat-engine.rst doc/source/man/heat-keystone-setup-domain.rst doc/source/man/heat-keystone-setup.rst doc/source/man/heat-manage.rst doc/source/man/index.rst doc/source/operating_guides/scale_deployment.rst doc/source/sourcecode/.gitignore doc/source/template_guide/advanced_topics.rst doc/source/template_guide/basic_resources.rst doc/source/template_guide/cfn.rst doc/source/template_guide/composition.rst doc/source/template_guide/contrib.rst doc/source/template_guide/environment.rst doc/source/template_guide/existing_templates.rst doc/source/template_guide/functions.rst doc/source/template_guide/hello_world.rst doc/source/template_guide/hot_guide.rst doc/source/template_guide/hot_spec.rst doc/source/template_guide/index.rst doc/source/template_guide/openstack.rst doc/source/template_guide/software_deployment.rst doc/source/template_guide/unsupported.rst doc/source/templates/index.rst doc/source/templates/cfn/WordPress_Single_Instance.rst doc/source/templates/hot/hello_world.rst etc/heat/README-heat.conf.txt etc/heat/api-paste.ini etc/heat/policy.json etc/heat/environment.d/default.yaml etc/heat/templates/AWS_CloudWatch_Alarm.yaml etc/heat/templates/AWS_RDS_DBInstance.yaml heat/__init__.py heat/version.py heat.egg-info/PKG-INFO heat.egg-info/SOURCES.txt heat.egg-info/dependency_links.txt heat.egg-info/entry_points.txt heat.egg-info/not-zip-safe heat.egg-info/pbr.json heat.egg-info/requires.txt heat.egg-info/top_level.txt heat/api/__init__.py heat/api/versions.py heat/api/aws/__init__.py heat/api/aws/ec2token.py heat/api/aws/exception.py heat/api/aws/utils.py heat/api/cfn/__init__.py heat/api/cfn/versions.py heat/api/cfn/v1/__init__.py heat/api/cfn/v1/signal.py heat/api/cfn/v1/stacks.py heat/api/cloudwatch/__init__.py heat/api/cloudwatch/watch.py heat/api/middleware/__init__.py heat/api/middleware/fault.py heat/api/middleware/ssl.py heat/api/middleware/version_negotiation.py heat/api/openstack/__init__.py heat/api/openstack/versions.py heat/api/openstack/v1/__init__.py heat/api/openstack/v1/actions.py heat/api/openstack/v1/build_info.py heat/api/openstack/v1/events.py heat/api/openstack/v1/resources.py heat/api/openstack/v1/services.py heat/api/openstack/v1/software_configs.py heat/api/openstack/v1/software_deployments.py heat/api/openstack/v1/stacks.py heat/api/openstack/v1/util.py heat/api/openstack/v1/views/__init__.py heat/api/openstack/v1/views/stacks_view.py heat/api/openstack/v1/views/views_common.py heat/cloudinit/__init__.py heat/cloudinit/boothook.sh heat/cloudinit/config heat/cloudinit/loguserdata.py heat/cloudinit/part_handler.py heat/cmd/__init__.py heat/cmd/api.py heat/cmd/api_cfn.py heat/cmd/api_cloudwatch.py heat/cmd/engine.py heat/cmd/manage.py heat/common/__init__.py heat/common/auth_password.py heat/common/auth_url.py heat/common/cache.py heat/common/config.py heat/common/context.py heat/common/crypt.py heat/common/custom_backend_auth.py heat/common/endpoint_utils.py heat/common/environment_format.py heat/common/exception.py heat/common/grouputils.py heat/common/heat_keystoneclient.py heat/common/i18n.py heat/common/identifier.py heat/common/lifecycle_plugin_utils.py heat/common/messaging.py heat/common/netutils.py heat/common/param_utils.py heat/common/plugin_loader.py heat/common/policy.py heat/common/profiler.py heat/common/serializers.py heat/common/service_utils.py heat/common/short_id.py heat/common/template_format.py heat/common/timeutils.py heat/common/urlfetch.py heat/common/wsgi.py heat/db/__init__.py heat/db/api.py heat/db/utils.py heat/db/sqlalchemy/__init__.py heat/db/sqlalchemy/api.py heat/db/sqlalchemy/filters.py heat/db/sqlalchemy/migration.py heat/db/sqlalchemy/models.py heat/db/sqlalchemy/types.py heat/db/sqlalchemy/utils.py heat/db/sqlalchemy/migrate_repo/README heat/db/sqlalchemy/migrate_repo/__init__.py heat/db/sqlalchemy/migrate_repo/manage.py heat/db/sqlalchemy/migrate_repo/migrate.cfg heat/db/sqlalchemy/migrate_repo/versions/028_havana.py heat/db/sqlalchemy/migrate_repo/versions/029_event_id_to_uuid.py heat/db/sqlalchemy/migrate_repo/versions/030_remove_uuidutils.py heat/db/sqlalchemy/migrate_repo/versions/031_stack_lock.py heat/db/sqlalchemy/migrate_repo/versions/032_decrypt_method.py heat/db/sqlalchemy/migrate_repo/versions/033_software_config.py heat/db/sqlalchemy/migrate_repo/versions/034_raw_template_files.py heat/db/sqlalchemy/migrate_repo/versions/035_event_uuid_to_id.py heat/db/sqlalchemy/migrate_repo/versions/036_stack_domain_project.py heat/db/sqlalchemy/migrate_repo/versions/037_migrate_hot_template.py heat/db/sqlalchemy/migrate_repo/versions/038_software_config_json_config.py heat/db/sqlalchemy/migrate_repo/versions/039_user_creds_nullable.py heat/db/sqlalchemy/migrate_repo/versions/040_software_deployment_no_signal_id.py heat/db/sqlalchemy/migrate_repo/versions/041_migrate_hot_template_resources.py heat/db/sqlalchemy/migrate_repo/versions/042_software_deployment_domain_project.py heat/db/sqlalchemy/migrate_repo/versions/043_migrate_template_versions.py heat/db/sqlalchemy/migrate_repo/versions/044_snapshots.py heat/db/sqlalchemy/migrate_repo/versions/045_stack_backup.py heat/db/sqlalchemy/migrate_repo/versions/046_properties_data.py heat/db/sqlalchemy/migrate_repo/versions/047_stack_nested_depth.py heat/db/sqlalchemy/migrate_repo/versions/048_resource_id_server_default_none_psql.py heat/db/sqlalchemy/migrate_repo/versions/049_user_creds_region_name.py heat/db/sqlalchemy/migrate_repo/versions/050_stack_tags.py heat/db/sqlalchemy/migrate_repo/versions/051_service.py heat/db/sqlalchemy/migrate_repo/versions/052_convergence_flag.py heat/db/sqlalchemy/migrate_repo/versions/053_stack_name_tenant_indexes.py heat/db/sqlalchemy/migrate_repo/versions/054_stack_tags_table.py heat/db/sqlalchemy/migrate_repo/versions/055_stack_convg_data.py heat/db/sqlalchemy/migrate_repo/versions/056_convergence_parameter_storage.py heat/db/sqlalchemy/migrate_repo/versions/057_resource_uuid_to_id.py heat/db/sqlalchemy/migrate_repo/versions/058_resource_engine_id.py heat/db/sqlalchemy/migrate_repo/versions/059_sync_point.py heat/db/sqlalchemy/migrate_repo/versions/060_resource_convg_data.py heat/db/sqlalchemy/migrate_repo/versions/061_status_reason_longtext.py heat/db/sqlalchemy/migrate_repo/versions/062_parent_resource.py heat/db/sqlalchemy/migrate_repo/versions/063_properties_data_encrypted.py heat/db/sqlalchemy/migrate_repo/versions/064_raw_template_predecessor.py heat/db/sqlalchemy/migrate_repo/versions/065_root_resource.py heat/db/sqlalchemy/migrate_repo/versions/066_placeholder.py heat/db/sqlalchemy/migrate_repo/versions/067_placeholder.py heat/db/sqlalchemy/migrate_repo/versions/068_placeholder.py heat/db/sqlalchemy/migrate_repo/versions/069_placeholder.py heat/db/sqlalchemy/migrate_repo/versions/070_placeholder.py heat/db/sqlalchemy/migrate_repo/versions/071_stack_owner_id_index.py heat/db/sqlalchemy/migrate_repo/versions/__init__.py heat/engine/__init__.py heat/engine/api.py heat/engine/attributes.py heat/engine/constraints.py heat/engine/dependencies.py heat/engine/environment.py heat/engine/event.py heat/engine/function.py heat/engine/lifecycle_plugin.py heat/engine/parameter_groups.py heat/engine/parameters.py heat/engine/plugin_manager.py heat/engine/properties.py heat/engine/resource.py heat/engine/rsrc_defn.py heat/engine/scheduler.py heat/engine/service.py heat/engine/service_software_config.py heat/engine/service_stack_watch.py heat/engine/stack.py heat/engine/stack_lock.py heat/engine/support.py heat/engine/sync_point.py heat/engine/template.py heat/engine/timestamp.py heat/engine/translation.py heat/engine/update.py heat/engine/watchrule.py heat/engine/worker.py heat/engine/cfn/__init__.py heat/engine/cfn/functions.py heat/engine/cfn/template.py heat/engine/clients/__init__.py heat/engine/clients/client_plugin.py heat/engine/clients/progress.py heat/engine/clients/os/__init__.py heat/engine/clients/os/barbican.py heat/engine/clients/os/ceilometer.py heat/engine/clients/os/cinder.py heat/engine/clients/os/designate.py heat/engine/clients/os/glance.py heat/engine/clients/os/heat_plugin.py heat/engine/clients/os/keystone.py heat/engine/clients/os/magnum.py heat/engine/clients/os/manila.py heat/engine/clients/os/mistral.py heat/engine/clients/os/monasca.py heat/engine/clients/os/nova.py heat/engine/clients/os/sahara.py heat/engine/clients/os/senlin.py heat/engine/clients/os/swift.py heat/engine/clients/os/trove.py heat/engine/clients/os/zaqar.py heat/engine/clients/os/neutron/__init__.py heat/engine/clients/os/neutron/lbaas_constraints.py heat/engine/clients/os/neutron/neutron_constraints.py heat/engine/constraint/__init__.py heat/engine/constraint/common_constraints.py heat/engine/hot/__init__.py heat/engine/hot/functions.py heat/engine/hot/parameters.py heat/engine/hot/template.py heat/engine/notification/__init__.py heat/engine/notification/autoscaling.py heat/engine/notification/stack.py heat/engine/resources/__init__.py heat/engine/resources/scheduler_hints.py heat/engine/resources/signal_responder.py heat/engine/resources/stack_resource.py heat/engine/resources/stack_user.py heat/engine/resources/template_resource.py heat/engine/resources/volume_base.py heat/engine/resources/wait_condition.py heat/engine/resources/aws/__init__.py heat/engine/resources/aws/autoscaling/__init__.py heat/engine/resources/aws/autoscaling/autoscaling_group.py heat/engine/resources/aws/autoscaling/launch_config.py heat/engine/resources/aws/autoscaling/scaling_policy.py heat/engine/resources/aws/cfn/__init__.py heat/engine/resources/aws/cfn/stack.py heat/engine/resources/aws/cfn/wait_condition.py heat/engine/resources/aws/cfn/wait_condition_handle.py heat/engine/resources/aws/ec2/__init__.py heat/engine/resources/aws/ec2/eip.py heat/engine/resources/aws/ec2/instance.py heat/engine/resources/aws/ec2/internet_gateway.py heat/engine/resources/aws/ec2/network_interface.py heat/engine/resources/aws/ec2/route_table.py heat/engine/resources/aws/ec2/security_group.py heat/engine/resources/aws/ec2/subnet.py heat/engine/resources/aws/ec2/volume.py heat/engine/resources/aws/ec2/vpc.py heat/engine/resources/aws/iam/__init__.py heat/engine/resources/aws/iam/user.py heat/engine/resources/aws/lb/__init__.py heat/engine/resources/aws/lb/loadbalancer.py heat/engine/resources/aws/s3/__init__.py heat/engine/resources/aws/s3/s3.py heat/engine/resources/openstack/__init__.py heat/engine/resources/openstack/barbican/__init__.py heat/engine/resources/openstack/barbican/container.py heat/engine/resources/openstack/barbican/order.py heat/engine/resources/openstack/barbican/secret.py heat/engine/resources/openstack/ceilometer/__init__.py heat/engine/resources/openstack/ceilometer/alarm.py heat/engine/resources/openstack/ceilometer/gnocchi/__init__.py heat/engine/resources/openstack/ceilometer/gnocchi/alarm.py heat/engine/resources/openstack/cinder/__init__.py heat/engine/resources/openstack/cinder/encrypted_volume_type.py heat/engine/resources/openstack/cinder/volume.py heat/engine/resources/openstack/cinder/volume_type.py heat/engine/resources/openstack/designate/__init__.py heat/engine/resources/openstack/designate/domain.py heat/engine/resources/openstack/designate/record.py heat/engine/resources/openstack/glance/__init__.py heat/engine/resources/openstack/glance/image.py heat/engine/resources/openstack/heat/__init__.py heat/engine/resources/openstack/heat/access_policy.py heat/engine/resources/openstack/heat/autoscaling_group.py heat/engine/resources/openstack/heat/cloud_config.py heat/engine/resources/openstack/heat/cloud_watch.py heat/engine/resources/openstack/heat/ha_restarter.py heat/engine/resources/openstack/heat/instance_group.py heat/engine/resources/openstack/heat/multi_part.py heat/engine/resources/openstack/heat/none_resource.py heat/engine/resources/openstack/heat/random_string.py heat/engine/resources/openstack/heat/remote_stack.py heat/engine/resources/openstack/heat/resource_chain.py heat/engine/resources/openstack/heat/resource_group.py heat/engine/resources/openstack/heat/scaling_policy.py heat/engine/resources/openstack/heat/software_component.py heat/engine/resources/openstack/heat/software_config.py heat/engine/resources/openstack/heat/software_deployment.py heat/engine/resources/openstack/heat/structured_config.py heat/engine/resources/openstack/heat/swiftsignal.py heat/engine/resources/openstack/heat/test_resource.py heat/engine/resources/openstack/heat/wait_condition.py heat/engine/resources/openstack/heat/wait_condition_handle.py heat/engine/resources/openstack/keystone/__init__.py heat/engine/resources/openstack/keystone/endpoint.py heat/engine/resources/openstack/keystone/group.py heat/engine/resources/openstack/keystone/project.py heat/engine/resources/openstack/keystone/region.py heat/engine/resources/openstack/keystone/role.py heat/engine/resources/openstack/keystone/role_assignments.py heat/engine/resources/openstack/keystone/service.py heat/engine/resources/openstack/keystone/user.py heat/engine/resources/openstack/magnum/__init__.py heat/engine/resources/openstack/magnum/bay.py heat/engine/resources/openstack/magnum/baymodel.py heat/engine/resources/openstack/manila/__init__.py heat/engine/resources/openstack/manila/security_service.py heat/engine/resources/openstack/manila/share.py heat/engine/resources/openstack/manila/share_network.py heat/engine/resources/openstack/manila/share_type.py heat/engine/resources/openstack/mistral/__init__.py heat/engine/resources/openstack/mistral/cron_trigger.py heat/engine/resources/openstack/mistral/workflow.py heat/engine/resources/openstack/monasca/__init__.py heat/engine/resources/openstack/monasca/alarm_definition.py heat/engine/resources/openstack/monasca/notification.py heat/engine/resources/openstack/neutron/__init__.py heat/engine/resources/openstack/neutron/address_scope.py heat/engine/resources/openstack/neutron/extraroute.py heat/engine/resources/openstack/neutron/firewall.py heat/engine/resources/openstack/neutron/floatingip.py heat/engine/resources/openstack/neutron/loadbalancer.py heat/engine/resources/openstack/neutron/metering.py heat/engine/resources/openstack/neutron/net.py heat/engine/resources/openstack/neutron/network_gateway.py heat/engine/resources/openstack/neutron/neutron.py heat/engine/resources/openstack/neutron/port.py heat/engine/resources/openstack/neutron/provider_net.py heat/engine/resources/openstack/neutron/qos.py heat/engine/resources/openstack/neutron/rbac_policy.py heat/engine/resources/openstack/neutron/router.py heat/engine/resources/openstack/neutron/security_group.py heat/engine/resources/openstack/neutron/subnet.py heat/engine/resources/openstack/neutron/subnetpool.py heat/engine/resources/openstack/neutron/vpnservice.py heat/engine/resources/openstack/neutron/lbaas/__init__.py heat/engine/resources/openstack/neutron/lbaas/health_monitor.py heat/engine/resources/openstack/neutron/lbaas/listener.py heat/engine/resources/openstack/neutron/lbaas/loadbalancer.py heat/engine/resources/openstack/neutron/lbaas/pool.py heat/engine/resources/openstack/neutron/lbaas/pool_member.py heat/engine/resources/openstack/nova/__init__.py heat/engine/resources/openstack/nova/flavor.py heat/engine/resources/openstack/nova/floatingip.py heat/engine/resources/openstack/nova/host_aggregate.py heat/engine/resources/openstack/nova/keypair.py heat/engine/resources/openstack/nova/server.py heat/engine/resources/openstack/nova/server_group.py heat/engine/resources/openstack/nova/server_network_mixin.py heat/engine/resources/openstack/sahara/__init__.py heat/engine/resources/openstack/sahara/cluster.py heat/engine/resources/openstack/sahara/data_source.py heat/engine/resources/openstack/sahara/image.py heat/engine/resources/openstack/sahara/job_binary.py heat/engine/resources/openstack/sahara/templates.py heat/engine/resources/openstack/senlin/__init__.py heat/engine/resources/openstack/senlin/cluster.py heat/engine/resources/openstack/senlin/node.py heat/engine/resources/openstack/senlin/policy.py heat/engine/resources/openstack/senlin/profile.py heat/engine/resources/openstack/senlin/receiver.py heat/engine/resources/openstack/swift/__init__.py heat/engine/resources/openstack/swift/swift.py heat/engine/resources/openstack/trove/__init__.py heat/engine/resources/openstack/trove/cluster.py heat/engine/resources/openstack/trove/os_database.py heat/engine/resources/openstack/zaqar/__init__.py heat/engine/resources/openstack/zaqar/queue.py heat/httpd/__init__.py heat/httpd/heat_api.py heat/httpd/heat_api_cfn.py heat/httpd/heat_api_cloudwatch.py heat/locale/heat-log-critical.pot heat/locale/heat-log-error.pot heat/locale/heat-log-info.pot heat/locale/heat-log-warning.pot heat/locale/heat.pot heat/locale/cs/LC_MESSAGES/heat-log-critical.po heat/locale/de/LC_MESSAGES/heat-log-critical.po heat/locale/de/LC_MESSAGES/heat.po heat/locale/es/LC_MESSAGES/heat-log-critical.po heat/locale/es/LC_MESSAGES/heat.po heat/locale/fr/LC_MESSAGES/heat-log-critical.po heat/locale/fr/LC_MESSAGES/heat-log-error.po heat/locale/fr/LC_MESSAGES/heat.po heat/locale/it/LC_MESSAGES/heat-log-critical.po heat/locale/it/LC_MESSAGES/heat.po heat/locale/ja/LC_MESSAGES/heat-log-critical.po heat/locale/ja/LC_MESSAGES/heat.po heat/locale/ko_KR/LC_MESSAGES/heat-log-critical.po heat/locale/ko_KR/LC_MESSAGES/heat-log-error.po heat/locale/ko_KR/LC_MESSAGES/heat-log-warning.po heat/locale/pt/LC_MESSAGES/heat-log-critical.po heat/locale/pt_BR/LC_MESSAGES/heat-log-critical.po heat/locale/pt_BR/LC_MESSAGES/heat.po heat/locale/ru/LC_MESSAGES/heat-log-critical.po heat/locale/ru/LC_MESSAGES/heat.po heat/locale/tr_TR/LC_MESSAGES/heat-log-critical.po heat/locale/zh_CN/LC_MESSAGES/heat-log-critical.po heat/locale/zh_CN/LC_MESSAGES/heat.po heat/locale/zh_TW/LC_MESSAGES/heat-log-critical.po heat/locale/zh_TW/LC_MESSAGES/heat.po heat/objects/__init__.py heat/objects/base.py heat/objects/event.py heat/objects/fields.py heat/objects/raw_template.py heat/objects/resource.py heat/objects/resource_data.py heat/objects/service.py heat/objects/snapshot.py heat/objects/software_config.py heat/objects/software_deployment.py heat/objects/stack.py heat/objects/stack_lock.py heat/objects/stack_tag.py heat/objects/sync_point.py heat/objects/user_creds.py heat/objects/watch_data.py heat/objects/watch_rule.py heat/openstack/__init__.py heat/openstack/common/README heat/openstack/common/__init__.py heat/openstack/common/_i18n.py heat/openstack/common/crypto/__init__.py heat/openstack/common/crypto/utils.py heat/rpc/__init__.py heat/rpc/api.py heat/rpc/client.py heat/rpc/listener_client.py heat/rpc/worker_api.py heat/rpc/worker_client.py heat/scaling/__init__.py heat/scaling/cooldown.py heat/scaling/lbutils.py heat/scaling/rolling_update.py heat/scaling/scalingutil.py heat/scaling/template.py heat/tests/__init__.py heat/tests/common.py heat/tests/fakes.py heat/tests/generic_resource.py heat/tests/test_attributes.py heat/tests/test_auth_password.py heat/tests/test_auth_url.py heat/tests/test_common_context.py heat/tests/test_common_exception.py heat/tests/test_common_param_utils.py heat/tests/test_common_policy.py heat/tests/test_common_serializers.py heat/tests/test_common_service_utils.py heat/tests/test_constraints.py heat/tests/test_convg_stack.py heat/tests/test_crypt.py heat/tests/test_dbinstance.py heat/tests/test_empty_stack.py heat/tests/test_engine_api_utils.py heat/tests/test_engine_service.py heat/tests/test_engine_service_stack_watch.py heat/tests/test_environment.py heat/tests/test_environment_format.py heat/tests/test_event.py heat/tests/test_exception.py heat/tests/test_fault_middleware.py heat/tests/test_function.py heat/tests/test_grouputils.py heat/tests/test_hot.py heat/tests/test_identifier.py heat/tests/test_lifecycle_plugin_utils.py heat/tests/test_loguserdata.py heat/tests/test_metadata_refresh.py heat/tests/test_nested_stack.py heat/tests/test_nokey.py heat/tests/test_notifications.py heat/tests/test_parameters.py heat/tests/test_plugin_loader.py heat/tests/test_properties.py heat/tests/test_provider_template.py heat/tests/test_resource.py heat/tests/test_rpc_client.py heat/tests/test_rpc_listener_client.py heat/tests/test_rpc_worker_client.py heat/tests/test_rsrc_defn.py heat/tests/test_server_tags.py heat/tests/test_short_id.py heat/tests/test_signal.py heat/tests/test_stack.py heat/tests/test_stack_collect_attributes.py heat/tests/test_stack_delete.py heat/tests/test_stack_lock.py heat/tests/test_stack_resource.py heat/tests/test_stack_update.py heat/tests/test_stack_user.py heat/tests/test_support.py heat/tests/test_template.py heat/tests/test_template_format.py heat/tests/test_timeutils.py heat/tests/test_translation_rule.py heat/tests/test_urlfetch.py heat/tests/test_validate.py heat/tests/test_version.py heat/tests/test_vpc.py heat/tests/test_watch.py heat/tests/testing-overview.txt heat/tests/utils.py heat/tests/api/__init__.py heat/tests/api/test_wsgi.py heat/tests/api/aws/__init__.py heat/tests/api/aws/test_api_aws.py heat/tests/api/aws/test_api_ec2token.py heat/tests/api/cfn/__init__.py heat/tests/api/cfn/test_api_cfn_v1.py heat/tests/api/cloudwatch/__init__.py heat/tests/api/cloudwatch/test_api_cloudwatch.py heat/tests/api/middleware/__init__.py heat/tests/api/middleware/test_version_negotiation_middleware.py heat/tests/api/openstack_v1/__init__.py heat/tests/api/openstack_v1/test_actions.py heat/tests/api/openstack_v1/test_build_info.py heat/tests/api/openstack_v1/test_events.py heat/tests/api/openstack_v1/test_resources.py heat/tests/api/openstack_v1/test_routes.py heat/tests/api/openstack_v1/test_services.py heat/tests/api/openstack_v1/test_software_configs.py heat/tests/api/openstack_v1/test_software_deployments.py heat/tests/api/openstack_v1/test_stacks.py heat/tests/api/openstack_v1/test_util.py heat/tests/api/openstack_v1/test_views_common.py heat/tests/api/openstack_v1/test_views_stacks_view.py heat/tests/api/openstack_v1/tools.py heat/tests/autoscaling/__init__.py heat/tests/autoscaling/inline_templates.py heat/tests/autoscaling/test_heat_scaling_group.py heat/tests/autoscaling/test_heat_scaling_policy.py heat/tests/autoscaling/test_launch_config.py heat/tests/autoscaling/test_lbutils.py heat/tests/autoscaling/test_new_capacity.py heat/tests/autoscaling/test_rolling_update.py heat/tests/autoscaling/test_scaling_group.py heat/tests/autoscaling/test_scaling_policy.py heat/tests/autoscaling/test_scaling_template.py heat/tests/aws/__init__.py heat/tests/aws/test_eip.py heat/tests/aws/test_instance.py heat/tests/aws/test_instance_network.py heat/tests/aws/test_loadbalancer.py heat/tests/aws/test_network_interface.py heat/tests/aws/test_s3.py heat/tests/aws/test_security_group.py heat/tests/aws/test_user.py heat/tests/aws/test_volume.py heat/tests/aws/test_waitcondition.py heat/tests/clients/__init__.py heat/tests/clients/test_barbican_client.py heat/tests/clients/test_ceilometer_client.py heat/tests/clients/test_cinder_client.py heat/tests/clients/test_clients.py heat/tests/clients/test_designate_client.py heat/tests/clients/test_glance_client.py heat/tests/clients/test_heat_client.py heat/tests/clients/test_keystone_client.py heat/tests/clients/test_magnum_client.py heat/tests/clients/test_manila_client.py heat/tests/clients/test_mistral_client.py heat/tests/clients/test_monasca_client.py heat/tests/clients/test_neutron_client.py heat/tests/clients/test_nova_client.py heat/tests/clients/test_progress.py heat/tests/clients/test_sahara_client.py heat/tests/clients/test_senlin_client.py heat/tests/clients/test_swift_client.py heat/tests/clients/test_zaqar_client.py heat/tests/constraints/__init__.py heat/tests/constraints/test_common_constraints.py heat/tests/convergence/__init__.py heat/tests/convergence/test_converge.py heat/tests/convergence/framework/__init__.py heat/tests/convergence/framework/engine_wrapper.py heat/tests/convergence/framework/event_loop.py heat/tests/convergence/framework/fake_resource.py heat/tests/convergence/framework/message_processor.py heat/tests/convergence/framework/message_queue.py heat/tests/convergence/framework/processes.py heat/tests/convergence/framework/reality.py heat/tests/convergence/framework/scenario.py heat/tests/convergence/framework/scenario_template.py heat/tests/convergence/framework/testutils.py heat/tests/convergence/framework/worker_wrapper.py heat/tests/convergence/scenarios/basic_create.py heat/tests/convergence/scenarios/basic_create_rollback.py heat/tests/convergence/scenarios/basic_update_delete.py heat/tests/convergence/scenarios/create_early_delete.py heat/tests/convergence/scenarios/disjoint_create.py heat/tests/convergence/scenarios/multiple_update.py heat/tests/convergence/scenarios/update_add.py heat/tests/convergence/scenarios/update_add_concurrent.py heat/tests/convergence/scenarios/update_add_rollback.py heat/tests/convergence/scenarios/update_add_rollback_early.py heat/tests/convergence/scenarios/update_remove.py heat/tests/convergence/scenarios/update_remove_rollback.py heat/tests/convergence/scenarios/update_replace.py heat/tests/convergence/scenarios/update_replace_invert_deps.py heat/tests/convergence/scenarios/update_replace_missed_cleanup.py heat/tests/convergence/scenarios/update_replace_missed_cleanup_delete.py heat/tests/convergence/scenarios/update_replace_rollback.py heat/tests/convergence/scenarios/update_user_replace.py heat/tests/convergence/scenarios/update_user_replace_rollback.py heat/tests/db/__init__.py heat/tests/db/test_migrations.py heat/tests/db/test_sqlalchemy_api.py heat/tests/db/test_sqlalchemy_filters.py heat/tests/db/test_sqlalchemy_types.py heat/tests/db/test_utils.py heat/tests/engine/__init__.py heat/tests/engine/test_dependencies.py heat/tests/engine/test_engine_worker.py heat/tests/engine/test_plugin_manager.py heat/tests/engine/test_resource_type.py heat/tests/engine/test_scheduler.py heat/tests/engine/test_sync_point.py heat/tests/engine/tools.py heat/tests/engine/service/__init__.py heat/tests/engine/service/test_service_engine.py heat/tests/engine/service/test_software_config.py heat/tests/engine/service/test_stack_action.py heat/tests/engine/service/test_stack_adopt.py heat/tests/engine/service/test_stack_create.py heat/tests/engine/service/test_stack_delete.py heat/tests/engine/service/test_stack_events.py heat/tests/engine/service/test_stack_resources.py heat/tests/engine/service/test_stack_snapshot.py heat/tests/engine/service/test_stack_update.py heat/tests/engine/service/test_stack_watch.py heat/tests/engine/service/test_threadgroup_mgr.py heat/tests/openstack/__init__.py heat/tests/openstack/barbican/__init__.py heat/tests/openstack/barbican/test_container.py heat/tests/openstack/barbican/test_order.py heat/tests/openstack/barbican/test_secret.py heat/tests/openstack/ceilometer/__init__.py heat/tests/openstack/ceilometer/test_ceilometer_alarm.py heat/tests/openstack/ceilometer/test_gnocchi_alarm.py heat/tests/openstack/cinder/__init__.py heat/tests/openstack/cinder/test_volume.py heat/tests/openstack/cinder/test_volume_type.py heat/tests/openstack/cinder/test_volume_type_encryption.py heat/tests/openstack/cinder/test_volume_utils.py heat/tests/openstack/designate/__init__.py heat/tests/openstack/designate/test_domain.py heat/tests/openstack/designate/test_record.py heat/tests/openstack/glance/__init__.py heat/tests/openstack/glance/test_image.py heat/tests/openstack/heat/__init__.py heat/tests/openstack/heat/test_cloud_config.py heat/tests/openstack/heat/test_cloudwatch.py heat/tests/openstack/heat/test_cw_alarm.py heat/tests/openstack/heat/test_instance_group.py heat/tests/openstack/heat/test_instance_group_update_policy.py heat/tests/openstack/heat/test_multi_part.py heat/tests/openstack/heat/test_none_resource.py heat/tests/openstack/heat/test_random_string.py heat/tests/openstack/heat/test_remote_stack.py heat/tests/openstack/heat/test_resource_chain.py heat/tests/openstack/heat/test_resource_group.py heat/tests/openstack/heat/test_restarter.py heat/tests/openstack/heat/test_software_component.py heat/tests/openstack/heat/test_software_config.py heat/tests/openstack/heat/test_software_deployment.py heat/tests/openstack/heat/test_structured_config.py heat/tests/openstack/heat/test_swiftsignal.py heat/tests/openstack/heat/test_waitcondition.py heat/tests/openstack/keystone/__init__.py heat/tests/openstack/keystone/test_endpoint.py heat/tests/openstack/keystone/test_group.py heat/tests/openstack/keystone/test_project.py heat/tests/openstack/keystone/test_region.py heat/tests/openstack/keystone/test_role.py heat/tests/openstack/keystone/test_role_assignments.py heat/tests/openstack/keystone/test_service.py heat/tests/openstack/keystone/test_user.py heat/tests/openstack/magnum/__init__.py heat/tests/openstack/magnum/test_bay.py heat/tests/openstack/magnum/test_baymodel.py heat/tests/openstack/manila/__init__.py heat/tests/openstack/manila/test_security_service.py heat/tests/openstack/manila/test_share.py heat/tests/openstack/manila/test_share_network.py heat/tests/openstack/manila/test_share_type.py heat/tests/openstack/mistral/__init__.py heat/tests/openstack/mistral/test_cron_trigger.py heat/tests/openstack/mistral/test_workflow.py heat/tests/openstack/monasca/__init__.py heat/tests/openstack/monasca/test_alarm_definition.py heat/tests/openstack/monasca/test_notification.py heat/tests/openstack/neutron/__init__.py heat/tests/openstack/neutron/inline_templates.py heat/tests/openstack/neutron/test_address_scope.py heat/tests/openstack/neutron/test_extraroute.py heat/tests/openstack/neutron/test_neutron.py heat/tests/openstack/neutron/test_neutron_firewall.py heat/tests/openstack/neutron/test_neutron_floating_ip.py heat/tests/openstack/neutron/test_neutron_loadbalancer.py heat/tests/openstack/neutron/test_neutron_metering.py heat/tests/openstack/neutron/test_neutron_net.py heat/tests/openstack/neutron/test_neutron_network_gateway.py heat/tests/openstack/neutron/test_neutron_port.py heat/tests/openstack/neutron/test_neutron_provider_net.py heat/tests/openstack/neutron/test_neutron_rbac_policy.py heat/tests/openstack/neutron/test_neutron_router.py heat/tests/openstack/neutron/test_neutron_security_group.py heat/tests/openstack/neutron/test_neutron_subnet.py heat/tests/openstack/neutron/test_neutron_subnetpool.py heat/tests/openstack/neutron/test_neutron_vpnservice.py heat/tests/openstack/neutron/test_qos.py heat/tests/openstack/neutron/lbaas/__init__.py heat/tests/openstack/neutron/lbaas/test_health_monitor.py heat/tests/openstack/neutron/lbaas/test_listener.py heat/tests/openstack/neutron/lbaas/test_loadbalancer.py heat/tests/openstack/neutron/lbaas/test_pool.py heat/tests/openstack/neutron/lbaas/test_pool_member.py heat/tests/openstack/nova/__init__.py heat/tests/openstack/nova/fakes.py heat/tests/openstack/nova/test_flavor.py heat/tests/openstack/nova/test_floatingip.py heat/tests/openstack/nova/test_host_aggregate.py heat/tests/openstack/nova/test_keypair.py heat/tests/openstack/nova/test_server.py heat/tests/openstack/nova/test_server_group.py heat/tests/openstack/sahara/__init__.py heat/tests/openstack/sahara/test_cluster.py heat/tests/openstack/sahara/test_data_source.py heat/tests/openstack/sahara/test_image.py heat/tests/openstack/sahara/test_job_binary.py heat/tests/openstack/sahara/test_templates.py heat/tests/openstack/senlin/__init__.py heat/tests/openstack/senlin/test_cluster.py heat/tests/openstack/senlin/test_node.py heat/tests/openstack/senlin/test_policy.py heat/tests/openstack/senlin/test_profile.py heat/tests/openstack/senlin/test_receiver.py heat/tests/openstack/swift/__init__.py heat/tests/openstack/swift/test_swift.py heat/tests/openstack/trove/__init__.py heat/tests/openstack/trove/test_cluster.py heat/tests/openstack/trove/test_os_database.py heat/tests/openstack/zaqar/__init__.py heat/tests/openstack/zaqar/test_queue.py heat/tests/policy/check_admin.json heat/tests/policy/deny_stack_user.json heat/tests/policy/notallowed.json heat/tests/policy/resources.json heat/tests/templates/Neutron.template heat/tests/templates/Neutron.yaml heat/tests/templates/README heat/tests/templates/WordPress_Single_Instance.template heat/tests/templates/WordPress_Single_Instance.yaml heat_integrationtests/.gitignore heat_integrationtests/README.rst heat_integrationtests/__init__.py heat_integrationtests/config-generator.conf heat_integrationtests/heat_integrationtests.conf.sample heat_integrationtests/post_test_hook.sh heat_integrationtests/pre_test_hook.sh heat_integrationtests/prepare_test_env.sh heat_integrationtests/prepare_test_network.sh heat_integrationtests/requirements.txt heat_integrationtests/common/__init__.py heat_integrationtests/common/clients.py heat_integrationtests/common/config.py heat_integrationtests/common/exceptions.py heat_integrationtests/common/remote_client.py heat_integrationtests/common/test.py heat_integrationtests/functional/__init__.py heat_integrationtests/functional/functional_base.py heat_integrationtests/functional/test_autoscaling.py heat_integrationtests/functional/test_aws_stack.py heat_integrationtests/functional/test_conditional_exposure.py heat_integrationtests/functional/test_create_update.py heat_integrationtests/functional/test_create_update_neutron_port.py heat_integrationtests/functional/test_create_update_neutron_subnet.py heat_integrationtests/functional/test_default_parameters.py heat_integrationtests/functional/test_encrypted_parameter.py heat_integrationtests/functional/test_encryption_vol_type.py heat_integrationtests/functional/test_env_merge.py heat_integrationtests/functional/test_event_sinks.py heat_integrationtests/functional/test_heat_autoscaling.py heat_integrationtests/functional/test_hooks.py heat_integrationtests/functional/test_immutable_parameters.py heat_integrationtests/functional/test_instance_group.py heat_integrationtests/functional/test_lbaasv2.py heat_integrationtests/functional/test_notifications.py heat_integrationtests/functional/test_nova_server_networks.py heat_integrationtests/functional/test_os_wait_condition.py heat_integrationtests/functional/test_preview.py heat_integrationtests/functional/test_preview_update.py heat_integrationtests/functional/test_purge.py heat_integrationtests/functional/test_reload_on_sighup.py heat_integrationtests/functional/test_remote_stack.py heat_integrationtests/functional/test_resource_chain.py heat_integrationtests/functional/test_resource_group.py heat_integrationtests/functional/test_software_config.py heat_integrationtests/functional/test_stack_events.py heat_integrationtests/functional/test_stack_outputs.py heat_integrationtests/functional/test_stack_tags.py heat_integrationtests/functional/test_swiftsignal_update.py heat_integrationtests/functional/test_template_resource.py heat_integrationtests/functional/test_template_validate.py heat_integrationtests/functional/test_templates.py heat_integrationtests/functional/test_unicode_template.py heat_integrationtests/functional/test_update_restricted.py heat_integrationtests/functional/test_validation.py heat_integrationtests/functional/test_waitcondition.py heat_integrationtests/scenario/__init__.py heat_integrationtests/scenario/scenario_base.py heat_integrationtests/scenario/test_autoscaling_lb.py heat_integrationtests/scenario/test_autoscaling_lbv2.py heat_integrationtests/scenario/test_ceilometer_alarm.py heat_integrationtests/scenario/test_server_cfn_init.py heat_integrationtests/scenario/test_server_software_config.py heat_integrationtests/scenario/test_volumes.py heat_integrationtests/scenario/templates/app_server_neutron.yaml heat_integrationtests/scenario/templates/boot_config_none_env.yaml heat_integrationtests/scenario/templates/netcat-webapp.yaml heat_integrationtests/scenario/templates/test_autoscaling_lb_neutron.yaml heat_integrationtests/scenario/templates/test_ceilometer_alarm.yaml heat_integrationtests/scenario/templates/test_server_cfn_init.yaml heat_integrationtests/scenario/templates/test_server_software_config.yaml heat_integrationtests/scenario/templates/test_volumes_create_from_backup.yaml heat_integrationtests/scenario/templates/test_volumes_delete_snapshot.yaml heat_upgradetests/post_test_hook.sh heat_upgradetests/pre_test_hook.sh rally-scenarios/README.rst rally-scenarios/heat-fakevirt.yaml rally-scenarios/extra/README.rst rally-scenarios/extra/default.yaml rally-scenarios/extra/rg_template_with_constraint.yaml rally-scenarios/extra/rg_template_with_outputs.yaml rally-scenarios/plugins/sample_plugin.py rally-scenarios/plugins/stack_output.py releasenotes/notes/.placeholder releasenotes/notes/api-outputs-6d09ebf5044f51c3.yaml releasenotes/notes/barbican-container-77967add0832d51b.yaml releasenotes/notes/bp-support-host-aggregate-fbc4097f4e6332b8.yaml releasenotes/notes/bp-support-neutron-qos-3feb38eb2abdcc87.yaml releasenotes/notes/bp-support-rbac-policy-fd71f8f6cc97bfb6.yaml releasenotes/notes/event-transport-302d1db6c5a5daa9.yaml releasenotes/notes/immutable-parameters-a13dc9bec7d6fa0f.yaml releasenotes/notes/keystone-region-ce3b435c73c81ce4.yaml releasenotes/notes/legacy-stack-user-id-cebbad8b0f2ed490.yaml releasenotes/notes/neutron-address-scope-ce234763e22c7449.yaml releasenotes/notes/neutron-lbaas-v2-resources-c0ebbeb9bc9f7a42.yaml releasenotes/notes/resource-search-3234afe601ea4e9d.yaml releasenotes/notes/restrict_update_replace-68abece58cf3f6a0.yaml releasenotes/notes/senlin-resources-71c856dc62d0b407.yaml releasenotes/notes/server-add-user-data-update-policy-c34646acfaada4d4.yaml releasenotes/notes/server-side-multi-env-7862a75e596ae8f5.yaml releasenotes/notes/subnet-pool-resource-c32ff97d4f956b73.yaml releasenotes/notes/template-validate-improvements-52ecf5125c9efeda.yaml releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/liberty.rst releasenotes/source/unreleased.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder tools/README.rst tools/cfn-json2yaml tools/custom_guidelines.py tools/heat-db-drop tools/pretty_tox.sh tools/subunit-trace.py tools/test-requires-deb tools/test-requires-rpmheat-6.0.0/heat.egg-info/PKG-INFO0000664000567000056710000000672412701407210017312 0ustar jenkinsjenkins00000000000000Metadata-Version: 1.1 Name: heat Version: 6.0.0 Summary: OpenStack Orchestration Home-page: http://www.openstack.org/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description: ==== Heat ==== Heat is a service to orchestrate multiple composite cloud applications using templates, through both an OpenStack-native REST API and a CloudFormation-compatible Query API. Why heat? It makes the clouds rise and keeps them there. Getting Started --------------- If you'd like to run from the master branch, you can clone the git repo: git clone https://git.openstack.org/openstack/heat * Wiki: http://wiki.openstack.org/Heat * Developer docs: http://docs.openstack.org/developer/heat * Template samples: https://git.openstack.org/cgit/openstack/heat-templates Python client ------------- https://git.openstack.org/cgit/openstack/python-heatclient References ---------- * http://docs.amazonwebservices.com/AWSCloudFormation/latest/APIReference/API_CreateStack.html * http://docs.amazonwebservices.com/AWSCloudFormation/latest/UserGuide/create-stack.html * http://docs.amazonwebservices.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html * http://www.oasis-open.org/committees/tc_home.php?wg_abbrev=tosca We have integration with ------------------------ * https://git.openstack.org/cgit/openstack/python-novaclient (instance) * https://git.openstack.org/cgit/openstack/python-keystoneclient (auth) * https://git.openstack.org/cgit/openstack/python-swiftclient (s3) * https://git.openstack.org/cgit/openstack/python-neutronclient (networking) * https://git.openstack.org/cgit/openstack/python-ceilometerclient (metering) * https://git.openstack.org/cgit/openstack/python-cinderclient (storage service) * https://git.openstack.org/cgit/openstack/python-glanceclient (image service) * https://git.openstack.org/cgit/openstack/python-troveclient (database as a Service) * https://git.openstack.org/cgit/openstack/python-saharaclient (hadoop cluster) * https://git.openstack.org/cgit/openstack/python-barbicanclient (key management service) * https://git.openstack.org/cgit/openstack/python-designateclient (DNS service) * https://git.openstack.org/cgit/openstack/python-magnumclient (container service) * https://git.openstack.org/cgit/openstack/python-manilaclient (shared file system service) * https://git.openstack.org/cgit/openstack/python-mistralclient (workflow service) * https://git.openstack.org/cgit/openstack/python-zaqarclient (messaging service) * https://git.openstack.org/cgit/openstack/python-monascaclient (monitoring service) Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 heat-6.0.0/heat.egg-info/top_level.txt0000664000567000056710000000000512701407210020731 0ustar jenkinsjenkins00000000000000heat heat-6.0.0/heat.egg-info/pbr.json0000664000567000056710000000005612701407210017663 0ustar jenkinsjenkins00000000000000{"is_release": true, "git_version": "05dde21"}heat-6.0.0/heat.egg-info/entry_points.txt0000664000567000056710000001613312701407210021506 0ustar jenkinsjenkins00000000000000[console_scripts] heat-api = heat.cmd.api:main heat-api-cfn = heat.cmd.api_cfn:main heat-api-cloudwatch = heat.cmd.api_cloudwatch:main heat-engine = heat.cmd.engine:main heat-manage = heat.cmd.manage:main [heat.clients] barbican = heat.engine.clients.os.barbican:BarbicanClientPlugin ceilometer = heat.engine.clients.os.ceilometer:CeilometerClientPlugin cinder = heat.engine.clients.os.cinder:CinderClientPlugin designate = heat.engine.clients.os.designate:DesignateClientPlugin glance = heat.engine.clients.os.glance:GlanceClientPlugin heat = heat.engine.clients.os.heat_plugin:HeatClientPlugin keystone = heat.engine.clients.os.keystone:KeystoneClientPlugin magnum = heat.engine.clients.os.magnum:MagnumClientPlugin manila = heat.engine.clients.os.manila:ManilaClientPlugin mistral = heat.engine.clients.os.mistral:MistralClientPlugin monasca = heat.engine.clients.os.monasca:MonascaClientPlugin neutron = heat.engine.clients.os.neutron:NeutronClientPlugin nova = heat.engine.clients.os.nova:NovaClientPlugin sahara = heat.engine.clients.os.sahara:SaharaClientPlugin senlin = heat.engine.clients.os.senlin:SenlinClientPlugin swift = heat.engine.clients.os.swift:SwiftClientPlugin trove = heat.engine.clients.os.trove:TroveClientPlugin zaqar = heat.engine.clients.os.zaqar:ZaqarClientPlugin [heat.constraints] barbican.secret = heat.engine.clients.os.barbican:SecretConstraint cinder.backup = heat.engine.clients.os.cinder:VolumeBackupConstraint cinder.snapshot = heat.engine.clients.os.cinder:VolumeSnapshotConstraint cinder.volume = heat.engine.clients.os.cinder:VolumeConstraint cinder.vtype = heat.engine.clients.os.cinder:VolumeTypeConstraint cron_expression = heat.engine.constraint.common_constraints:CRONExpressionConstraint designate.domain = heat.engine.clients.os.designate:DesignateDomainConstraint glance.image = heat.engine.clients.os.glance:ImageConstraint ip_addr = heat.engine.constraint.common_constraints:IPConstraint iso_8601 = heat.engine.constraint.common_constraints:ISO8601Constraint keystone.domain = heat.engine.clients.os.keystone:KeystoneDomainConstraint keystone.group = heat.engine.clients.os.keystone:KeystoneGroupConstraint keystone.project = heat.engine.clients.os.keystone:KeystoneProjectConstraint keystone.region = heat.engine.clients.os.keystone:KeystoneRegionConstraint keystone.role = heat.engine.clients.os.keystone:KeystoneRoleConstraint keystone.service = heat.engine.clients.os.keystone:KeystoneServiceConstraint keystone.user = heat.engine.clients.os.keystone:KeystoneUserConstraint mac_addr = heat.engine.constraint.common_constraints:MACConstraint magnum.baymodel = heat.engine.clients.os.magnum:BaymodelConstraint manila.share_network = heat.engine.clients.os.manila:ManilaShareNetworkConstraint manila.share_snapshot = heat.engine.clients.os.manila:ManilaShareSnapshotConstraint manila.share_type = heat.engine.clients.os.manila:ManilaShareTypeConstraint monasca.notification = heat.engine.clients.os.monasca:MonascaNotificationConstraint net_cidr = heat.engine.constraint.common_constraints:CIDRConstraint neutron.address_scope = heat.engine.clients.os.neutron.neutron_constraints:AddressScopeConstraint neutron.lb.provider = heat.engine.clients.os.neutron.neutron_constraints:LBaasV1ProviderConstraint neutron.lbaas.listener = heat.engine.clients.os.neutron.lbaas_constraints:ListenerConstraint neutron.lbaas.loadbalancer = heat.engine.clients.os.neutron.lbaas_constraints:LoadbalancerConstraint neutron.lbaas.pool = heat.engine.clients.os.neutron.lbaas_constraints:PoolConstraint neutron.lbaas.provider = heat.engine.clients.os.neutron.lbaas_constraints:LBaasV2ProviderConstraint neutron.network = heat.engine.clients.os.neutron.neutron_constraints:NetworkConstraint neutron.port = heat.engine.clients.os.neutron.neutron_constraints:PortConstraint neutron.qos_policy = heat.engine.clients.os.neutron.neutron_constraints:QoSPolicyConstraint neutron.router = heat.engine.clients.os.neutron.neutron_constraints:RouterConstraint neutron.subnet = heat.engine.clients.os.neutron.neutron_constraints:SubnetConstraint neutron.subnetpool = heat.engine.clients.os.neutron.neutron_constraints:SubnetPoolConstraint nova.flavor = heat.engine.clients.os.nova:FlavorConstraint nova.host = heat.engine.clients.os.nova:HostConstraint nova.keypair = heat.engine.clients.os.nova:KeypairConstraint nova.network = heat.engine.clients.os.nova:NetworkConstraint nova.server = heat.engine.clients.os.nova:ServerConstraint sahara.image = heat.engine.clients.os.sahara:ImageConstraint sahara.plugin = heat.engine.clients.os.sahara:PluginConstraint senlin.cluster = heat.engine.clients.os.senlin:ClusterConstraint senlin.policy_type = heat.engine.clients.os.senlin:PolicyTypeConstraint senlin.profile = heat.engine.clients.os.senlin:ProfileConstraint senlin.profile_type = heat.engine.clients.os.senlin:ProfileTypeConstraint test_constr = heat.engine.constraint.common_constraints:TestConstraintDelay timezone = heat.engine.constraint.common_constraints:TimezoneConstraint trove.flavor = heat.engine.clients.os.trove:FlavorConstraint [heat.event_sinks] zaqar-queue = heat.engine.clients.os.zaqar:ZaqarEventSink [heat.stack_lifecycle_plugins] [heat.templates] AWSTemplateFormatVersion.2010-09-09 = heat.engine.cfn.template:CfnTemplate HeatTemplateFormatVersion.2012-12-12 = heat.engine.cfn.template:HeatTemplate heat_template_version.2013-05-23 = heat.engine.hot.template:HOTemplate20130523 heat_template_version.2014-10-16 = heat.engine.hot.template:HOTemplate20141016 heat_template_version.2015-04-30 = heat.engine.hot.template:HOTemplate20150430 heat_template_version.2015-10-15 = heat.engine.hot.template:HOTemplate20151015 heat_template_version.2016-04-08 = heat.engine.hot.template:HOTemplate20160408 [oslo.config.opts] heat.api.aws.ec2token = heat.api.aws.ec2token:list_opts heat.api.middleware.ssl = heat.api.middleware.ssl:list_opts heat.common.config = heat.common.config:list_opts heat.common.context = heat.common.context:list_opts heat.common.crypt = heat.common.crypt:list_opts heat.common.heat_keystoneclient = heat.common.heat_keystoneclient:list_opts heat.common.wsgi = heat.common.wsgi:list_opts heat.engine.clients = heat.engine.clients:list_opts heat.engine.notification = heat.engine.notification:list_opts heat.engine.resources = heat.engine.resources:list_opts heat.openstack.common.policy = heat.openstack.common.policy:list_opts heat_integrationtests.common.config = heat_integrationtests.common.config:list_opts [oslo.config.opts.defaults] heat.common.config = heat.common.config:set_config_defaults [oslo.messaging.notify.drivers] heat.openstack.common.notifier.log_notifier = oslo_messaging.notify._impl_log:LogDriver heat.openstack.common.notifier.no_op_notifier = oslo_messaging.notify._impl_noop:NoOpDriver heat.openstack.common.notifier.rpc_notifier = oslo_messaging.notify.messaging:MessagingDriver heat.openstack.common.notifier.rpc_notifier2 = oslo_messaging.notify.messaging:MessagingV2Driver heat.openstack.common.notifier.test_notifier = oslo_messaging.notify._impl_test:TestDriver [wsgi_scripts] heat-wsgi-api = heat.httpd.heat_api:init_application heat-wsgi-api-cfn = heat.httpd.heat_api_cfn:init_application heat-wsgi-api-cloudwatch = heat.httpd.heat_api_cloudwatch:init_application heat-6.0.0/tools/0000775000567000056710000000000012701407211014732 5ustar jenkinsjenkins00000000000000heat-6.0.0/tools/pretty_tox.sh0000664000567000056710000000023312701407050017506 0ustar jenkinsjenkins00000000000000#!/usr/bin/env bash set -o pipefail TESTRARGS=$1 python setup.py testr --slowest --testr-args="--subunit $TESTRARGS" | $(dirname $0)/subunit-trace.py -f heat-6.0.0/tools/heat-db-drop0000775000567000056710000000122412701407050017126 0ustar jenkinsjenkins00000000000000#!/bin/bash DATABASE=heat while [ $# -gt 0 ]; do case $1 in heat|nova|keystone|glance) DATABASE=$1 ;; -r|--rootpw) shift MYSQL_ROOT_PW_ARG="--password=$1" ;; *) ;; esac shift done if [ ! "${MYSQL_ROOT_PW_ARG+defined}" ] ; then printf "Please enter the password for the 'root' MySQL user: " read -s MYSQL_ROOT_PW MYSQL_ROOT_PW_ARG="--password=${MYSQL_ROOT_PW}" echo fi cat << EOF | mysql -u root ${MYSQL_ROOT_PW_ARG} DROP USER '${DATABASE}'@'localhost'; DROP USER '${DATABASE}'@'%'; DROP DATABASE ${DATABASE}; flush privileges; EOF heat-6.0.0/tools/subunit-trace.py0000775000567000056710000002454412701407050020106 0ustar jenkinsjenkins00000000000000#!/usr/bin/env python # Copyright 2014 Hewlett-Packard Development Company, L.P. # Copyright 2014 Samsung Electronics # 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. """Trace a subunit stream in reasonable detail and high accuracy.""" import argparse import functools import os import re import sys import mimeparse import subunit import testtools DAY_SECONDS = 60 * 60 * 24 FAILS = [] RESULTS = {} class Starts(testtools.StreamResult): def __init__(self, output): super(Starts, self).__init__() self._output = output def startTestRun(self): self._neednewline = False self._emitted = set() def status(self, test_id=None, test_status=None, test_tags=None, runnable=True, file_name=None, file_bytes=None, eof=False, mime_type=None, route_code=None, timestamp=None): super(Starts, self).status( test_id, test_status, test_tags=test_tags, runnable=runnable, file_name=file_name, file_bytes=file_bytes, eof=eof, mime_type=mime_type, route_code=route_code, timestamp=timestamp) if not test_id: if not file_bytes: return if not mime_type or mime_type == 'test/plain;charset=utf8': mime_type = 'text/plain; charset=utf-8' primary, sub, parameters = mimeparse.parse_mime_type(mime_type) content_type = testtools.content_type.ContentType( primary, sub, parameters) content = testtools.content.Content( content_type, lambda: [file_bytes]) text = content.as_text() if text and text[-1] not in '\r\n': self._neednewline = True self._output.write(text) elif test_status == 'inprogress' and test_id not in self._emitted: if self._neednewline: self._neednewline = False self._output.write('\n') worker = '' for tag in test_tags or (): if tag.startswith('worker-'): worker = '(' + tag[7:] + ') ' if timestamp: timestr = timestamp.isoformat() else: timestr = '' self._output.write('%s: %s%s [start]\n' % (timestr, worker, test_id)) self._emitted.add(test_id) def cleanup_test_name(name, strip_tags=True, strip_scenarios=False): """Clean up the test name for display. By default we strip out the tags in the test because they don't help us in identifying the test that is run to it's result. Make it possible to strip out the testscenarios information (not to be confused with tempest scenarios) however that's often needed to indentify generated negative tests. """ if strip_tags: tags_start = name.find('[') tags_end = name.find(']') if tags_start > 0 and tags_end > tags_start: newname = name[:tags_start] newname += name[tags_end + 1:] name = newname if strip_scenarios: tags_start = name.find('(') tags_end = name.find(')') if tags_start > 0 and tags_end > tags_start: newname = name[:tags_start] newname += name[tags_end + 1:] name = newname return name def get_duration(timestamps): start, end = timestamps if not start or not end: duration = '' else: delta = end - start duration = '%d.%06ds' % ( delta.days * DAY_SECONDS + delta.seconds, delta.microseconds) return duration def find_worker(test): for tag in test['tags']: if tag.startswith('worker-'): return int(tag[7:]) return 'NaN' # Print out stdout/stderr if it exists, always def print_attachments(stream, test, all_channels=False): """Print out subunit attachments. Print out subunit attachments that contain content. This runs in 2 modes, one for successes where we print out just stdout and stderr, and an override that dumps all the attachments. """ channels = ('stdout', 'stderr') for name, detail in test['details'].items(): # NOTE(sdague): the subunit names are a little crazy, and actually # are in the form pythonlogging:'' (with the colon and quotes) name = name.split(':')[0] if detail.content_type.type == 'test': detail.content_type.type = 'text' if (all_channels or name in channels) and detail.as_text(): title = "Captured %s:" % name stream.write("\n%s\n%s\n" % (title, ('~' * len(title)))) # indent attachment lines 4 spaces to make them visually # offset for line in detail.as_text().split('\n'): stream.write(" %s\n" % line) def show_outcome(stream, test, print_failures=False, failonly=False): global RESULTS status = test['status'] # TODO(sdague): ask lifeless why on this? if status == 'exists': return worker = find_worker(test) name = cleanup_test_name(test['id']) duration = get_duration(test['timestamps']) if worker not in RESULTS: RESULTS[worker] = [] RESULTS[worker].append(test) # don't count the end of the return code as a fail if name == 'process-returncode': return if status == 'fail': FAILS.append(test) stream.write('{%s} %s [%s] ... FAILED\n' % ( worker, name, duration)) if not print_failures: print_attachments(stream, test, all_channels=True) elif not failonly: if status == 'success': stream.write('{%s} %s [%s] ... ok\n' % ( worker, name, duration)) print_attachments(stream, test) elif status == 'skip': stream.write('{%s} %s ... SKIPPED: %s\n' % ( worker, name, test['details']['reason'].as_text())) else: stream.write('{%s} %s [%s] ... %s\n' % ( worker, name, duration, test['status'])) if not print_failures: print_attachments(stream, test, all_channels=True) stream.flush() def print_fails(stream): """Print summary failure report. Currently unused, however there remains debate on inline vs. at end reporting, so leave the utility function for later use. """ if not FAILS: return stream.write("\n==============================\n") stream.write("Failed %s tests - output below:" % len(FAILS)) stream.write("\n==============================\n") for f in FAILS: stream.write("\n%s\n" % f['id']) stream.write("%s\n" % ('-' * len(f['id']))) print_attachments(stream, f, all_channels=True) stream.write('\n') def count_tests(key, value): count = 0 for k, v in RESULTS.items(): for item in v: if key in item: if re.search(value, item[key]): count += 1 return count def run_time(): runtime = 0.0 for k, v in RESULTS.items(): for test in v: runtime += float(get_duration(test['timestamps']).strip('s')) return runtime def worker_stats(worker): tests = RESULTS[worker] num_tests = len(tests) delta = tests[-1]['timestamps'][1] - tests[0]['timestamps'][0] return num_tests, delta def print_summary(stream): stream.write("\n======\nTotals\n======\n") stream.write("Run: %s in %s sec.\n" % (count_tests('status', '.*'), run_time())) stream.write(" - Passed: %s\n" % count_tests('status', 'success')) stream.write(" - Skipped: %s\n" % count_tests('status', 'skip')) stream.write(" - Failed: %s\n" % count_tests('status', 'fail')) # we could have no results, especially as we filter out the process-codes if RESULTS: stream.write("\n==============\nWorker Balance\n==============\n") for w in range(max(RESULTS.keys()) + 1): if w not in RESULTS: stream.write( " - WARNING: missing Worker %s! " "Race in testr accounting.\n" % w) else: num, time = worker_stats(w) stream.write(" - Worker %s (%s tests) => %ss\n" % (w, num, time)) def parse_args(): parser = argparse.ArgumentParser() parser.add_argument('--no-failure-debug', '-n', action='store_true', dest='print_failures', help='Disable printing failure ' 'debug information in realtime') parser.add_argument('--fails', '-f', action='store_true', dest='post_fails', help='Print failure debug ' 'information after the stream is proccesed') parser.add_argument('--failonly', action='store_true', dest='failonly', help="Don't print success items", default=( os.environ.get('TRACE_FAILONLY', False) is not False)) return parser.parse_args() def main(): args = parse_args() stream = subunit.ByteStreamToStreamResult( sys.stdin, non_subunit_name='stdout') starts = Starts(sys.stdout) outcomes = testtools.StreamToDict( functools.partial(show_outcome, sys.stdout, print_failures=args.print_failures, failonly=args.failonly )) summary = testtools.StreamSummary() result = testtools.CopyStreamResult([starts, outcomes, summary]) result.startTestRun() try: stream.run(result) finally: result.stopTestRun() if count_tests('status', '.*') == 0: print("The test run didn't actually run any tests") return 1 if args.post_fails: print_fails(sys.stdout) print_summary(sys.stdout) return (0 if summary.wasSuccessful() else 1) if __name__ == '__main__': sys.exit(main()) heat-6.0.0/tools/test-requires-rpm0000664000567000056710000000016512701407050020270 0ustar jenkinsjenkins00000000000000gcc python-devel libxml2-devel libxslt-devel libyaml-devel openssl-devel libffi-devel mariadb-devel postgresql-devel heat-6.0.0/tools/cfn-json2yaml0000775000567000056710000000276712701407050017357 0ustar jenkinsjenkins00000000000000#!/usr/bin/env python # # 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 sys import os import yaml import json import re from heat.common import template_format def main(): path = sys.argv[1] if os.path.isdir(path): convert_directory(path) elif os.path.isfile(path): convert_file(path) else: print('File or directory not valid: %s' % path) def convert_file(path): f = open(path, 'r') print(template_format.convert_json_to_yaml(f.read())) def convert_directory(dirpath): for path in os.listdir(dirpath): if not path.endswith('.template') and not path.endswith('.json'): continue yamlpath = re.sub('\..*$', '.yaml', path) print('Writing to %s' % yamlpath) f = open(os.path.join(dirpath, path), 'r') out = open(os.path.join(dirpath, yamlpath), 'w') yml = template_format.convert_json_to_yaml(f.read()) out.write(yml) out.close() if __name__ == '__main__': main() heat-6.0.0/tools/custom_guidelines.py0000664000567000056710000003004312701407050021027 0ustar jenkinsjenkins00000000000000# # 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 argparse import re import six import sys from heat.common.i18n import _ from heat.engine import constraints from heat.engine import resources from heat.engine import support class HeatCustomGuidelines(object): _RULES = ['resource_descriptions', 'trailing_spaces'] def __init__(self, exclude): self.error_count = 0 self.resources_classes = [] global_env = resources.global_env() for resource_type in global_env.get_types(): cls = global_env.get_class(resource_type) # Skip resources, which defined as template resource in environment if cls.__module__ == 'heat.engine.resources.template_resource': continue if (lambda module: True if [path for path in exclude if path in module] else False)(cls.__module__.replace('.', '/')): continue self.resources_classes.append(cls) def run_check(self): print(_('Heat custom guidelines check started.')) for rule in self._RULES: getattr(self, 'check_%s' % rule)() if self.error_count > 0: print(_('Heat custom guidelines check failed - ' 'found %s errors.') % self.error_count) sys.exit(1) else: print(_('Heat custom guidelines check succeeded.')) def check_resource_descriptions(self): for cls in self.resources_classes: # check resource's description self._check_resource_description(cls) # check properties' descriptions self._check_resource_schemas(cls, cls.properties_schema, 'property') # check attributes' descriptions self._check_resource_schemas(cls, cls.attributes_schema, 'attribute') # check methods descriptions self._check_resource_methods(cls) def _check_resource_description(self, resource): description = resource.__doc__ if resource.support_status.status not in (support.SUPPORTED, support.UNSUPPORTED): return kwargs = {'path': resource.__module__, 'details': resource.__name__} if not description: kwargs.update({'message': _("Resource description missing, " "should add resource description " "about resource's purpose")}) self.print_guideline_error(**kwargs) return doclines = [key.strip() for key in description.split('\n')] if len(doclines) == 1 or (len(doclines) == 2 and doclines[-1] == ''): kwargs.update({'message': _("Resource description missing, " "should add resource description " "about resource's purpose")}) self.print_guideline_error(**kwargs) return self._check_description_summary(doclines[0], kwargs, 'resource') self._check_description_details(doclines, kwargs, 'resource') def _check_resource_schemas(self, resource, schema, schema_name, error_path=None): for key, value in six.iteritems(schema): if error_path is None: error_path = [resource.__name__, key] else: error_path.append(key) # need to check sub-schema of current schema, if exists if (hasattr(value, 'schema') and getattr(value, 'schema') is not None): self._check_resource_schemas(resource, value.schema, schema_name, error_path) description = value.description kwargs = {'path': resource.__module__, 'details': error_path} if description is None: if (value.support_status.status == support.SUPPORTED and not isinstance(value.schema, constraints.AnyIndexDict) and not isinstance(schema, constraints.AnyIndexDict)): kwargs.update({'message': _("%s description " "missing, need to add " "description about property's " "purpose") % schema_name}) self.print_guideline_error(**kwargs) error_path.pop() continue self._check_description_summary(description, kwargs, schema_name) error_path.pop() def _check_resource_methods(self, resource): for method in six.itervalues(resource.__dict__): # need to skip non-functions attributes if not callable(method): continue description = method.__doc__ if not description: continue if method.__name__.startswith('__'): continue doclines = [key.strip() for key in description.split('\n')] kwargs = {'path': resource.__module__, 'details': [resource.__name__, method.__name__]} self._check_description_summary(doclines[0], kwargs, 'method') if len(doclines) == 2: kwargs.update({'message': _('Method description summary ' 'should be in one line')}) self.print_guideline_error(**kwargs) continue if len(doclines) > 1: self._check_description_details(doclines, kwargs, 'method') def check_trailing_spaces(self): for cls in self.resources_classes: cls_file = open(cls.__module__.replace('.', '/') + '.py') lines = [line.strip() for line in cls_file.readlines()] idx = 0 kwargs = {'path': cls.__module__} while idx < len(lines): if ('properties_schema' in lines[idx] or 'attributes_schema' in lines[idx]): level = len(re.findall('(\{|\()', lines[idx])) level -= len(re.findall('(\}|\))', lines[idx])) idx += 1 while level != 0: level += len(re.findall('(\{|\()', lines[idx])) level -= len(re.findall('(\}|\))', lines[idx])) if re.search("^((\'|\") )", lines[idx]): kwargs.update( {'details': 'line %s' % idx, 'message': _('Trailing whitespace should ' 'be on previous line'), 'snippet': lines[idx]}) self.print_guideline_error(**kwargs) elif (re.search("(\S(\'|\"))$", lines[idx - 1]) and re.search("^((\'|\")\S)", lines[idx])): kwargs.update( {'details': 'line %s' % (idx - 1), 'message': _('Omitted whitespace at the ' 'end of the line'), 'snippet': lines[idx - 1]}) self.print_guideline_error(**kwargs) idx += 1 idx += 1 def _check_description_summary(self, description, error_kwargs, error_key): if re.search("^[a-z]", description): error_kwargs.update( {'message': _('%s description summary should start ' 'with uppercase letter') % error_key.title(), 'snippet': description}) self.print_guideline_error(**error_kwargs) if not description.endswith('.'): error_kwargs.update( {'message': _('%s description summary omitted ' 'terminator at the end') % error_key.title(), 'snippet': description}) self.print_guideline_error(**error_kwargs) if re.search("\s{2,}", description): error_kwargs.update( {'message': _('%s description contains double or more ' 'whitespaces') % error_key.title(), 'snippet': description}) self.print_guideline_error(**error_kwargs) def _check_description_details(self, doclines, error_kwargs, error_key): if re.search("\S", doclines[1]): error_kwargs.update( {'message': _('%s description summary and ' 'main resource description should be ' 'separated by blank line') % error_key.title(), 'snippet': doclines[0]}) self.print_guideline_error(**error_kwargs) if re.search("^[a-z]", doclines[2]): error_kwargs.update( {'message': _('%s description should start ' 'with with uppercase ' 'letter') % error_key.title(), 'snippet': doclines[2]}) self.print_guideline_error(**error_kwargs) if doclines[-1] != '': error_kwargs.update( {'message': _('%s description multistring ' 'should have singly closing quotes at ' 'the next line') % error_key.title(), 'snippet': doclines[-1]}) self.print_guideline_error(**error_kwargs) params = False for line in doclines[1:]: if re.search("\s{2,}", line): error_kwargs.update( {'message': _('%s description ' 'contains double or more ' 'whitespaces') % error_key.title(), 'snippet': line}) self.print_guideline_error(**error_kwargs) if re.search("^(:param|:type|:returns|:rtype|:raises)", line): params = True if not params and not doclines[-2].endswith('.'): error_kwargs.update( {'message': _('%s description omitted ' 'terminator at the end') % error_key.title(), 'snippet': doclines[-2]}) self.print_guideline_error(**error_kwargs) def print_guideline_error(self, path, details, message, snippet=None): if isinstance(details, list): details = '.'.join(details) msg = _('ERROR (in %(path)s: %(details)s): %(message)s') % { 'message': message, 'path': path.replace('.', '/'), 'details': details } if snippet is not None: msg = _('%(msg)s\n (Error snippet): %(snippet)s') % { 'msg': msg, 'snippet': '%s...' % snippet[:79] } print(msg) self.error_count += 1 def parse_args(): parser = argparse.ArgumentParser() parser.add_argument('--exclude', '-e', metavar='', nargs='+', help=_('Exclude specified paths from checking.')) return parser.parse_args() if __name__ == '__main__': args = parse_args() guidelines = HeatCustomGuidelines(args.exclude or []) guidelines.run_check() heat-6.0.0/tools/test-requires-deb0000664000567000056710000000016312701407050020222 0ustar jenkinsjenkins00000000000000build-essential python-dev libxml2-dev libxslt1-dev libyaml-dev libssl-dev libffi-dev libmysqlclient-dev libpq-dev heat-6.0.0/tools/README.rst0000664000567000056710000000136712701407050016431 0ustar jenkinsjenkins00000000000000Files in this directory are general developer tools or examples of how to do certain activities. If you're running on Fedora, see the instructions at http://docs.openstack.org/developer/heat/getting_started/on_fedora.html Tools ===== heat-db-drop This script drops the heat database from mysql in the case of developer data corruption or erasing heat. cfn-json2yaml (bulk) convert AWS CloudFormation templates written in JSON to HeatTemplateFormatVersion YAML templates Package lists ============= Lists of Linux packages to install in order to successfully run heat's unit test suit on a clean new Linux distribution. test-requires-deb list of DEB packages as of Ubuntu 14.04 Trusty test-requires-rpm list of RPM packages as of Fedora 20 heat-6.0.0/doc/0000775000567000056710000000000012701407211014337 5ustar jenkinsjenkins00000000000000heat-6.0.0/doc/docbkx/0000775000567000056710000000000012701407211015611 5ustar jenkinsjenkins00000000000000heat-6.0.0/doc/docbkx/heat-admin/0000775000567000056710000000000012701407211017620 5ustar jenkinsjenkins00000000000000heat-6.0.0/doc/docbkx/heat-admin/ch_using.xml0000664000567000056710000000267112701407050022150 0ustar jenkinsjenkins00000000000000 GET'> PUT'> POST'> DELETE'> '> '> ]> Using Heat TODO heat-6.0.0/doc/docbkx/heat-admin/pom.xml0000664000567000056710000001627612701407050021152 0ustar jenkinsjenkins00000000000000 4.0.0 org.openstack.docs openstack-guide 1.0.0-SNAPSHOT jar OpenStack Guides local 1 Rackspace Research Repositories true rackspace-research Rackspace Research Repository http://maven.research.rackspacecloud.com/content/groups/public/ rackspace-research Rackspace Research Repository http://maven.research.rackspacecloud.com/content/groups/public/ target/docbkx/pdf **/*.fo com.rackspace.cloud.api clouddocs-maven-plugin 1.12.2 goal1 generate-pdf generate-sources false 0 0 goal2 generate-webhelp generate-sources ${comments.enabled} 1 os-heat-guides 1 UA-17511903-1 appendix toc,title article/appendix nop article toc,title book title,figure,table,example,equation chapter toc,title part toc,title preface toc,title qandadiv toc qandaset toc reference toc,title set toc,title 0 0 true . bk-heat-admin-guide.xml http://docs.openstack.org/${release.path.name}/heat-admin/content/ reviewer openstack heat-6.0.0/doc/docbkx/heat-admin/bk-heat-admin-guide.xml0000664000567000056710000000440612701407050024043 0ustar jenkinsjenkins00000000000000 ]> Heat Administration Guide 2012 OpenStack Grizzly (2013.1) Heat 2012-12-14 Copyright details are filled in by the template. This document is intended for administrators interested in running the Heat Service. 2012-12-14 First edition of this document. heat-6.0.0/doc/docbkx/heat-admin/ch_limitations.xml0000664000567000056710000000270012701407050023350 0ustar jenkinsjenkins00000000000000 GET'> PUT'> POST'> DELETE'> '> '> ]> Limitations TODO heat-6.0.0/doc/docbkx/heat-admin/ch_install.xml0000664000567000056710000000106612701407050022466 0ustar jenkinsjenkins00000000000000 ]> Heat Installation This chapter describes how to install the Heat Service and get it up and running. heat-6.0.0/doc/docbkx/heat-admin/ch_preface.xml0000664000567000056710000000475012701407050022430 0ustar jenkinsjenkins00000000000000 GET'> PUT'> POST'> DELETE'> '> '> powered by OpenStack'> powered by OpenStack'> ]> Preface
Intended Audience TODO
Document Change History The most recent changes are described in the table below:
Resources TODO
heat-6.0.0/doc/docbkx/heat-admin/app_core.xml0000664000567000056710000000074712701407050022143 0ustar jenkinsjenkins00000000000000 ]> Core Configuration File Options TODO heat-6.0.0/doc/docbkx/heat-admin/ch_overview.xml0000664000567000056710000000422412701407050022665 0ustar jenkinsjenkins00000000000000 GET'> PUT'> POST'> DELETE'> '> '> powered by OpenStack'> powered by OpenStack'> ]> Overview This chapter describes the high-level concepts and components of a Heat deployment. heat-6.0.0/doc/docbkx/api-ref/0000775000567000056710000000000012701407211017134 5ustar jenkinsjenkins00000000000000heat-6.0.0/doc/docbkx/api-ref/pom.xml0000664000567000056710000001011312701407050020446 0ustar jenkinsjenkins00000000000000 4.0.0 org.openstack.identity docs 1.0 jar OpenStack API Page Project Rackspace Research Repositories true rackspace-research Rackspace Research Repository http://maven.research.rackspacecloud.com/content/groups/public/ rackspace-research Rackspace Research Repository http://maven.research.rackspacecloud.com/content/groups/public/ always UTF-8 1.12.2 com.rackspace.cloud.api clouddocs-maven-plugin ${doctools.version} g1 generate-webhelp generate-sources false 1 UA-17511903-1 true src/docbkx api-ref.xml reviewer openstack 2 true heat-6.0.0/doc/docbkx/api-ref/src/0000775000567000056710000000000012701407211017723 5ustar jenkinsjenkins00000000000000heat-6.0.0/doc/docbkx/api-ref/src/docbkx/0000775000567000056710000000000012701407211021175 5ustar jenkinsjenkins00000000000000heat-6.0.0/doc/docbkx/api-ref/src/docbkx/api-ref.xml0000664000567000056710000005771712701407050023264 0ustar jenkinsjenkins00000000000000 GET'> PUT'> POST'> DELETE'> ]> OpenStack Orchestration API v1 Reference OpenStack LLC 2012 2013 OpenStack LLC API v1 OpenStack Orchestration 2013-10-03 Copyright details are filled in by the template. This document is intended for software developers interested in developing applications using the OpenStack Orchestration Application Programming Interface (API). 2013-10-03 Initial review. Overview OpenStack &Deployment; is a service to deploy and manage multiple composite cloud applications on OpenStack clouds. Interactions with OpenStack &Deployment; occur programmatically via the OpenStack &Deployment; API as described in this document. Reviewer: Can you provide me with a high-level Orchestration architecture diagram that I can add to this section? We welcome feedback, comments, and bug reports at https://bugs.launchpad.net/heat.
Intended Audience This Reference is intended to assist support teams, DevOps Engineers, and software developers who want to manage cloud applications with non-trivial architectures that leverage multiple OpenStack services. To use the information provided here, you should first have a general understanding of the &Deployment; service. You should also be familiar with: Other OpenStack services applicable to your cloud application architecture (Nova, Cinder, Neutron, and so forth) RESTful web services JSON and/or YAML data serialization formats
Document Change History This version of the Reference replaces and obsoletes all previous versions. The most recent changes are described in the table below:
Additional Resources You can find information for developing templates in the Heat Orchestration Template (HOT) Specification at http://docs.openstack.org/developer/heat/template_guide/hot_spec.html#hot-spec. You can find example templates at https://github.com/heat-ci/heat-prod-templates/tree/master/example. For information about OpenStack services, refer to http://docs.openstack.org.
Concepts To use &Deployment; effectively, you should understand several key concepts:
Stack A stack is a group of resources (servers, load balancers, databases, and so forth) combined to fulfill a useful purpose. A stack is a set of resources that can be deployed. A stack contains a template and generates resources to run the application framework or component specified.
Resource A resource is a template artifact that represents some component of your desired architecture (a Nova server, a group of scaled servers, a Cinder volume, some configuration management system, and so forth).
Template An &Deployment; template is a portable file, written in a user-readable language, that describes how a set of resources should be assembled and what software should be installed in order to produce a working deployment. The template specifies what resources should be used, what attributes can be set, and other parameters that are critical to the successful, repeatable automation of a specific application deployment.
General API Information The &Deployment; API is implemented using a RESTful web service interface. Like other OpenStack services, the &Deployment; Service shares a common token-based authentication system that allows seamless access between products and services.
Authentication The &Deployment; Service supports standard Keystone authentication. For more information, refer to the OpenStack Identity Service API v2.0 Reference. Please contact your provider for additional details on how to authenticate against this API.
Contract Version The contract version denotes the data model and behavior that the API supports. The current contract version is v1. The requested contract version is included in all request URLs. Different contract versions of the API may be available at any given time and are not guaranteed to be compatible with one another. Example Request URL (contract version in <emphasis role="strong" >bold</emphasis>) https://openstack.example.com/v1/1234 This document pertains to contract version 1.
Request/Response Types The &Deployment; API supports both the JSON and YAML data serialization formats. The request format is specified using the Content-Type header and is required for calls that have a request body. The response format can be specified in requests either by using the Accept header or by adding a .yaml or .json extension to the request URI. Note that it is possible for a response to be serialized using a format different from the request. If no response format is specified, JSON is the default. If conflicting formats are specified using both an Accept header and a query extension, the query extension takes precedence.
Response Formats
Format Accept Header Query Extension Default
JSON application/json .json Yes
YAML application/yaml .yaml No
Reviewer: need new examples below: one for "Request with Headers: JSON" and one for "Request with Headers: YAML". In the request example below, notice that Content-Type is set to application/json, but application/yaml is requested via the Accept header: Therefore a YAML response format is returned:
Date/Time Format The &Deployment; Service uses an ISO-8601 compliant date format for the display and consumption of date/time values. The system timezone is in UTC. MySQL converts TIMESTAMP values from the current time zone to UTC for storage, and back from UTC to the current time zone for retrieval. This does not occur for other types, such as DATETIME. &Deployment; Service Date/Time Format yyyy-MM-dd'T'HH:mm:ssZ See the table below for a description of the date/time format codes. May 19th, 2011 at 8:07:08 AM, GMT-5 would have the following format: 2011-05-19T08:07:08-05:00
Explanation of Date/Time Format Codes
Code Description
yyyy Four digit year
MM Two digit month
dd Two digit day of month
T Separator for date/time
HH Two digit hour of day (00-23)
mm Two digit minutes of hour
ss Two digit seconds of the minute
Z RFC-822 timezone
Faults The &Deployment; Service returns the following error codes: Error Code Description 400 Bad Request Invalid parameter values, un-parsable data, or missing required values. 404 Not Found The stack or resource cannot be found. 409 Conflict Invalid action is requested for the current stack status; more than one object exists for the specified non-unique identifier. 500 Internal Server Error Reverting the previously failed action encountered an error, an operation failed on one or more resources, an unexpected error occurred. Reviewer: Need new orchestration examples to replace the fault response examples below. The following two instanceFault examples show errors when the server has erred or cannot perform the requested operation: The error code (code) is returned in the body of the response for convenience. The message element returns a human-readable message that is appropriate for display to the end user. The details element is optional and may contain information that is useful for tracking down an error, such as a stack trace. The details element may or may not be appropriate for display to an end user, depending on the role and experience of the end user. The fault's root element (for example, instanceFault) may change depending on the type of error. The following two badRequest examples show errors when the volume size is invalid: The next two examples show itemNotFound errors:
Stack Status Stacks and resources have a state and a status as described in the lists that follow: State: INIT – (Resources only) The resource has not been provisioned. CREATE – The stack/resource is new. UPDATE – The stack/resource is changed. DELETE – The stack/resource is deleted. ROLLBACK – A previously failed change is being reverted. SUSPEND – (Stacks only) The stack is suspended. RESUME – (Stacks only) The stack is resumed. Status: IN-PROGRESS – The operation is in progress. COMPLETE – The operation is compete. FAILED – The operation failed. So, if you create a new stack and something goes wrong, your stack would be CREATE FAILED. One or more resources of that stack may be in the following states: INIT COMPLETE CREATE FAILED CREATE COMPLETE
API Operations This section describes the Orchestration API operations. Reviewer: please give me a list of section titles to use for dividing up the API calls into related sections. Reviewer: please give me the Error Response Codes for each of the API calls. Glossary resource A resource is a template artifact that represents some component of your desired architecture (a Nova server, a group of scaled servers, a Cinder volume, some configuration management system, and so forth). stack A stack is a group of resources (servers, load balancers, databases, and so forth) combined to fulfill a useful purpose. A stack is a set of resources that can be deployed. A stack contains a template and generates resources to run the application framework or component specified. template An &Deployment; template is a portable file, written in a user-readable language, that describes how a set of resources should be assembled and what software should be installed in order to produce a working deployment. The template specifies what resources should be used, what attributes can be set, and other parameters that are critical to the successful, repeatable automation of a specific application deployment.
heat-6.0.0/doc/docbkx/api-ref/src/wadls/0000775000567000056710000000000012701407211021035 5ustar jenkinsjenkins00000000000000heat-6.0.0/doc/docbkx/api-ref/src/wadls/heat-api/0000775000567000056710000000000012701407211022525 5ustar jenkinsjenkins00000000000000heat-6.0.0/doc/docbkx/api-ref/src/wadls/heat-api/src/0000775000567000056710000000000012701407211023314 5ustar jenkinsjenkins00000000000000heat-6.0.0/doc/docbkx/api-ref/src/wadls/heat-api/src/README.rst0000664000567000056710000000053612701407050025010 0ustar jenkinsjenkins00000000000000========= HEAT wadl ========= The original heat wadl (heat-api-1.0.wadl) and the samples have now been deleted from this repository. The wadl has been renamed and migrated to the following repo location along with the samples: https://git.openstack.org/cgit/openstack/api-site/tree/api-ref/src/wadls/orchestration-api/src/v1/orchestration-api.wadl heat-6.0.0/doc/docbkx/README.rst0000664000567000056710000000100212701407050017272 0ustar jenkinsjenkins00000000000000================================ Building the user and admin docs ================================ This documentation should eventually end up in the OpenStack documentation repositories `api-site` and `openstack-manuals`. Dependencies ============ on Ubuntu: sudo apt-get install maven on Fedora Core: sudo yum install maven Use `mvn` ========= Build the REST API reference manual: cd api-ref mvn clean generate-sources Build the heat admin guide: cd heat-admin mvn clean generate-sources heat-6.0.0/doc/source/0000775000567000056710000000000012701407211015637 5ustar jenkinsjenkins00000000000000heat-6.0.0/doc/source/index.rst0000664000567000056710000000623012701407050017502 0ustar jenkinsjenkins00000000000000.. 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. ================================== Welcome to the Heat documentation! ================================== Heat is a service to orchestrate composite cloud applications using a declarative template format through an OpenStack-native REST API. Heat's purpose and vision ========================= * Heat provides a template based orchestration for describing a cloud application by executing appropriate :term:`OpenStack` API calls to generate running cloud applications. * A Heat template describes the infrastructure for a cloud application in text files which are readable and writable by humans, and can be managed by version control tools. * Templates specify the relationships between resources (e.g. this volume is connected to this server). This enables Heat to call out to the OpenStack APIs to create all of your infrastructure in the correct order to completely launch your application. * The software integrates other components of OpenStack. The templates allow creation of most OpenStack resource types (such as instances, floating ips, volumes, security groups, users, etc), as well as some more advanced functionality such as instance high availability, instance autoscaling, and nested stacks. * Heat primarily manages infrastructure, but the templates integrate well with software configuration management tools such as Puppet and Ansible. * Operators can customise the capabilities of Heat by installing plugins. This documentation offers information aimed at end-users, operators and developers of Heat. Using Heat ========== .. toctree:: :maxdepth: 1 getting_started/create_a_stack template_guide/index templates/index glossary Operating Heat ============== .. toctree:: :maxdepth: 1 getting_started/on_fedora getting_started/on_ubuntu operating_guides/scale_deployment man/index Developing Heat =============== .. toctree:: :maxdepth: 1 contributing/index getting_started/on_devstack developing_guides/architecture developing_guides/pluginguide developing_guides/schedulerhints developing_guides/gmr developing_guides/supportstatus developing_guides/rally_on_gates API Documentation ======================== - `Heat REST API Reference (OpenStack API Complete Reference - Orchestration)`_ .. _`Heat REST API Reference (OpenStack API Complete Reference - Orchestration)`: http://api.openstack.org/api-ref-orchestration-v1.html Code Documentation ================== .. toctree:: :maxdepth: 1 api/autoindex Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` heat-6.0.0/doc/source/developing_guides/0000775000567000056710000000000012701407211021333 5ustar jenkinsjenkins00000000000000heat-6.0.0/doc/source/developing_guides/index.rst0000664000567000056710000000133512701407050023177 0ustar jenkinsjenkins00000000000000.. 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. Developing Guides ================= .. toctree:: :maxdepth: 1 on_devstack on_fedora on_ubuntu on_other jeos_building standalone heat-6.0.0/doc/source/developing_guides/schedulerhints.rst0000664000567000056710000000352712701407050025121 0ustar jenkinsjenkins00000000000000.. 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. ==================================== Heat Stack Lifecycle Scheduler Hints ==================================== This is a mechanism whereby when heat processes a stack Server or Volume resource, the stack id, root stack id, stack resource uuid, stack resource name and the path in the stack can be passed to nova and cinder by heat as scheduler hints, to the configured schedulers for nova and cinder. Enabling the scheduler hints ---------------------------- By default, passing the lifecycle scheduler hints is disabled. To enable it, set stack_scheduler_hints to True in heat.conf. The hints --------- When heat processes a stack, and the feature is enabled, the stack id, root stack id, stack resource uuid, stack resource name, and the path in the stack (as a list of tuple, (stackresourcename, stackname)) will be passed to nova and cinder by heat as scheduler hints, to the configured schedulers for nova and cinder. Purpose ------- A heat provider may have a need for custom code to examine stack requests prior to performing the operations to create or update a stack. After the custom code completes, the provider may want to provide hints to the nova or cinder schedulers with stack related identifiers, for processing by any custom scheduler plug-ins configured for nova or cinder. heat-6.0.0/doc/source/developing_guides/supportstatus.rst0000664000567000056710000002444512701407050025057 0ustar jenkinsjenkins00000000000000.. 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. .. _supportstatus: =============================== Heat Support Status usage Guide =============================== Heat allows to use for each resource, property, attribute special option named *support_status*, which describes current state of object: current status, since what time this status is actual, any additional information about object's state. This guide describes a detailed state life cycle of resources, properties and attributes. Support Status option and its parameters ---------------------------------------- Support status of object may be specified by using class ``SupportStatus``, which has follow options: *status*: Current status of object. Allowed values: - SUPPORTED. Default value of status parameter. All objects with this status are available and can be used. - DEPRECATED. Object with this status is available, but using it in code or templates is undesirable. As usual, can be reference in message to new object, which can be used instead of deprecated resource. - HIDDEN. Object with this status is unavailable and can't be used anywhere else. Old stacks with such object continue running. All new stacks cannot be created with such object. HIDDEN status notifies, that object is unavailable for using in templates, because can be deleted later. Object with HIDDEN status is not displaying in resource-type-show and in documentation. See below more details about removing and deprecating process. - UNSUPPORTED. Resources with UNSUPPORTED status are not supported by Heat team, i.e. user can use it, but it may be broken. *version*: Release name, since which current status is active. Parameter is optional, but should be defined or changed any time SupportStatus is specified or status changed. It used for better understanding from which release object in current status. .. note:: Since Liberty release mark looks like 5.0.0 instead of 2015.2. *message*: Any additional information about object's state, e.g. ``'Use property new_property instead.'``. *previous_status*: Option, which allows to display object's previous status, if any. This is helpful for displaying full life cycle of object. Type of *previous_status* is SupportStatus. Life cycle of resource, property, attribute ------------------------------------------- This section describes life cycle of such objects as resource, property and attribute. All these objects have same life cycle:: UNSUPPORTED -> SUPPORTED -> DEPRECATED -> HIDDEN \ -> UNSUPPORTED where UNSUPPORTED is optional. Creating process of object ++++++++++++++++++++++++++ During creating object there is a reason to add support status. So new object should contains *support_status* parameter equals to ``SupportStatus`` class with defined version of object and, maybe, some message. This parameter allows user to understand, from which this object OpenStack release this object is available and can be used. Deprecating process of object +++++++++++++++++++++++++++++ When some object becomes obsolete, user should know about that, so there is need to add information about deprecation in *support_status* of object. Status of ``SupportStatus`` must equals to DEPRECATED. If there is no *version* parameter, need to add one with current release otherwise move current status to *previous_status* and add to *version* current release as value. If some new object replaces old object, it will be good decision to add some information about new object to *support_status* message of old object, e.g. 'Use property new_property instead.'. Removing process of object ++++++++++++++++++++++++++ After at least one full release cycle deprecated object should be hidden and *support_status* status should equals to HIDDEN. HIDDEN status means hiding object from documentation and from result of :code:`resource-type-list` CLI command, if object is resource. Also, :code:`resource-type-show` command with such resource will raise `NotSupported` exception. Using Support Status during code writing ---------------------------------------- When adding new objects or adding objects instead of some old (e.g. property subnet instead of subnet_id in OS::Neutron::RouterInterface), there is some information about time of adding objects (since which release it will be available or unavailable). This section described ``SupportStatus`` during creating/deprecating/removing resources and properties and attributes. Note, that ``SupportStatus`` locates in support.py, so you need to import *support*. For specifying status, use *support* constant names, e.g. support.SUPPORTED. All constant names described in section above. Using Support Status during creation ++++++++++++++++++++++++++++++++++++ Option *support_status* may be used for whole resource: .. code-block:: python class ResourceWithType(resource.Resource): support_status=support.SupportStatus( version='5.0.0', message=_('Optional message') ) To define *support_status* for property or attribute, follow next steps: .. code-block:: python PROPERTY: properties.Schema( ... support_status=support.SupportStatus( version='5.0.0', message=_('Optional message') ) ) Same support_status definition for attribute schema. Note, that in this situation status parameter of ``SupportStatus`` uses default value, equals to SUPPORTED. Using Support Status during deprecation and hiding ++++++++++++++++++++++++++++++++++++++++++++++++++ When time of deprecation or hiding resource/property/attribute comes, follow next steps: 1. If there is some support_status in object, add `previous_status` parameter with current ``SupportStatus`` value and change all other parameters for current `status`, `version` and, maybe, `message`. 2. If there is no support_status option, add new one with parameters status equals to current status, `version` equals to current release note and, optionally, some message. Using Support Status during resource deprecating looks like: .. code-block:: python class ResourceWithType(resource.Resource): support_status=support.SupportStatus( status=support.DEPRECATED, version='5.0.0', message=_('Optional message'), previous_status=support.SupportStatus(version='2014.2') ) Using Support Status during attribute (or property) deprecating looks like: .. code-block:: python ATTRIBUTE: attributes.Schema( ... support_status=support.SupportStatus( status=support.DEPRECATED, version='5.0.0', message=_('Optional message like: Use attribute new_attr'), previous_status=support.SupportStatus( version='2014.2', message=_('Feature available since 2014.2')) ) ) Same *support_status* defining for property schema. Note, that during hiding object status should be equal support.HIDDEN instead of support.DEPRECATED. Besides that, SupportStatus with DEPRECATED status should be moved to *previous_status*, e.g.: .. code-block:: python support.SupportStatus( status=support.HIDDEN, version='5.0.0', message=_('Some message'), previous_status=support.SupportStatus( status=support.DEPRECATED, version='2015.1', previous_status=support.SupportStatus(version='2014.2') ) ) During hiding properties, if some hidden property has alternative, use translation mechanism for translating properties from old to new one. See below, how to use this mechanism. Translating mechanism for hidden properties ------------------------------------------- Sometimes properties become deprecated and replaced by another. There is translation mechanism for that. Mechanism used for such cases: 1. If there are two properties in properties_schema, which have STRING, INTEGER, NUMBER or BOOLEAN type. 2. If there are two properties: one in LIST or MAP property sub-schema and another on the top schema. 3. If there are two properties in LIST property. 4. If there was non-LIST property and LIST property, which was designed to replace non-LIST property. Mechanism has rules and executes them. To define rule, ``TranslationRule`` class called and specifies *source_path* - list with path in properties_schema for property which will be affected; *value* - value, which will be added to property, specified by previous parameter; *value_name* - name of old property, used for case 4; *value_path* - list with path in properties_schema for property which will be used for getting value. ``TranslationRule`` supports next rules: - *ADD*. This rule allows to add some value to LIST-type properties. Only LIST-type values can be added to such properties. Using for other cases is prohibited and will be returned with error. - *REPLACE*. This rule allows to replace some property value to another. Used for all types of properties. Note, that if property has list type, then value will be replaced for all elements of list, where it needed. If element in such property must be replaced by value of another element of this property, *value_name* must be defined. - *DELETE*. This rule allows to delete some property. If property has list type, then deleting affects value in all list elements. Each resource, which has some hidden properties, which can be replaced by new, must overload `translation_rules` method, which should return a list of ``TranslationRules``, for example: .. code-block:: python def translation_rules(self): return [properties.TranslationRule( self.properties, properties.TranslationRule.REPLACE, source_path=[self.NETWORKS, self.NETWORK_ID], value_name=self.NETWORK_UUID)] heat-6.0.0/doc/source/developing_guides/gmr.rst0000664000567000056710000000577412701407050022670 0ustar jenkinsjenkins00000000000000.. Copyright (c) 2014 OpenStack Foundation 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. ======================= Guru Meditation Reports ======================= Heat contains a mechanism whereby developers and system administrators can generate a report about the state of a running Heat executable. This report is called a *Guru Meditation Report* (*GMR* for short). Generating a GMR ~~~~~~~~~~~~~~~~ A *GMR* can be generated by sending the *USR2* signal to any Heat process with support (see below). The *GMR* will then be outputted standard error for that particular process. For example, suppose that ``heat-api`` has process id ``10172``, and was run with ``2>/var/log/heat/heat-api-err.log``. Then, ``kill -USR2 10172`` will trigger the Guru Meditation report to be printed to ``/var/log/heat/heat-api-err.log``. Structure of a GMR ~~~~~~~~~~~~~~~~~~ The *GMR* is designed to be extensible; any particular executable may add its own sections. However, the base *GMR* consists of several sections: Package Shows information about the package to which this process belongs, including version information Threads Shows stack traces and thread ids for each of the threads within this process Green Threads Shows stack traces for each of the green threads within this process (green threads don't have thread ids) Configuration Lists all the configuration options currently accessible via the CONF object for the current process Adding support for GMRs to new executable ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Adding support for a *GMR* to a given executable is fairly easy. First import the module (currently residing in oslo-incubator), as well as the Heat version module: .. code-block:: python from oslo_reports import guru_meditation_report as gmr from heat import version Then, register any additional sections (optional): .. code-block:: python TextGuruMeditation.register_section('Some Special Section', some_section_generator) Finally (under main), before running the "main loop" of the executable (usually ``server.start()`` or something similar), register the *GMR* hook: .. code-block:: python TextGuruMeditation.setup_autorun(version) Extending the GMR ~~~~~~~~~~~~~~~~~ As mentioned above, additional sections can be added to the GMR for a particular executable. For more information, see the documentation about ``oslo.reports``: `oslo.reports `_ heat-6.0.0/doc/source/developing_guides/rally_on_gates.rst0000664000567000056710000003061312701407050025073 0ustar jenkinsjenkins00000000000000.. 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. .. _rally_gates: ========================= Using Rally on Heat gates ========================= Heat gate allows to use Rally for performance testing for each particular patch. This functionality can be used for checking patch on performance regressions and also for detecting any floating bugs for common scenarios. How to run Rally for particular patch ------------------------------------- As was mentioned above Heat allows to execute Rally scenarios as a gate job for particular patch. It can be done by posting comment with text ``check experimental`` for patch on review. It will run bunch of jobs, one of which has name ``gate-rally-dsvm-fakevirt-heat``. List of scenarios, which will be executed, is presented in file ``heat-fakevirt.yaml``. Default version of this file is available here: https://github.com/openstack/heat/blob/master/rally-scenarios/heat-fakevirt.yaml Obviously performance analysis make sense, when it can be compared with some another performance data. So two different approaches can be used for it: - Comparison of one part of code with some custom changes (see :ref:`check_performance_or_detect_regression`) - Comparison of two different code parts (see :ref:`compare_output_API_performance`) Examples of using Rally ----------------------- Previously two main approaches of using Rally job for Heat were highlighted. Corresponding examples will be described in this part of documentation. However need to note, that there are a lot of other ways how to use Rally job for Heat performance. For example, this job can be launched periodically (twice in week) for random patches and these results will be compared between each other. It allows to see, that Heat has not any performance regressions. .. _check_performance_or_detect_regression: Check performance or how to detect regression +++++++++++++++++++++++++++++++++++++++++++++ The easiest way of using Rally is to execute already existing scenarios. One of the examples is presented in patch https://review.openstack.org/#/c/279450/ . In this patch was executed scenario already existing in Rally ``HeatStacks.create_and_delete_stack``. During executing this scenario Rally creates and then, when stack is created, delete Heat stack. All existing scenarios can be found here: https://github.com/openstack/rally/blob/master/rally/plugins/openstack/scenarios/heat/stacks.py Mentioned scenario uses Heat template as a parameter for task. The template path should be mentioned for argument ``template_path``. It can be one of Heat templates presented in Rally repository (https://github.com/openstack/rally/tree/master/samples/tasks/scenarios/heat/templates) or new one, like it was done for mentioned patch. New added template should be placed in ``rally-scenarios/extra/`` directory. Also it's possible to specify other fields for each Rally task, like ``sla`` or ``context``. More information about other configuration setting is available by link https://rally.readthedocs.org/en/latest/tutorial.html Mentioned patch was proposed for confirmation caching mechanism of Heat template validation process (see https://specs.openstack.org/openstack/heat-specs/specs/liberty/constraint-validation-cache.html). So it contains some changes in OS::Heat::TestResource resource, which allows to demonstrate mentioned caching feature improvements. Initially test was run against current devstack installation, where caching is disabled (e.g. Patch Set 7). The follow results were gotten: +------------------+----------+----------+----------+--------+------+ |Action | Min (sec)| Max (sec)| Avg (sec)| Success| Count| +------------------+----------+----------+----------+--------+------+ |heat.create_stack | 38.223 | 48.085 | 42.971 | 100.0% | 10 | +------------------+----------+----------+----------+--------+------+ |heat.delete_stack | 11.755 | 18.155 | 14.085 | 100.0% | 10 | +------------------+----------+----------+----------+--------+------+ |total | 50.188 | 65.361 | 57.057 | 100.0% | 10 | +------------------+----------+----------+----------+--------+------+ In the next patch set (Patch Set 8) was updated by adding Depends-On reference to commit message. It let to execute the same test with patch for devstack, which turns on caching (https://review.openstack.org/#/c/279400/). The results for this case were: +------------------+----------+----------+----------+--------+------+ |Action | Min (sec)| Max (sec)| Avg (sec)| Success| Count| +------------------+----------+----------+----------+--------+------+ |heat.create_stack | 11.863 | 16.074 | 14.174 | 100.0% | 10 | +------------------+----------+----------+----------+--------+------+ |heat.delete_stack | 9.144 | 11.663 | 10.595 | 100.0% | 10 | +------------------+----------+----------+----------+--------+------+ |total | 21.557 | 27.18 | 24.77 | 100.0% | 10 | +------------------+----------+----------+----------+--------+------+ Comparison average values for create_stack action in the first and the second executions shows, that with enabled caching create_stack works faster in 3 times. It is a tangible improvement for create_stack operation. Need to note, that in described test delay for each constraint validation request takes 0.3 sec. as specified in ``constraint_prop_secs`` property of TestResource. It may be more, than real time delay, but it allows to confirm, that caching works correct. Also this approach may be used for detecting regressions. In this case workflow may be presented as follow list of steps: - add to task list (``heat-fakevirt.yaml``) existing or new tasks. - wait a result of this execution. - upload patchset with changes (new feature) and launch the same test again. - compare performance results. .. _compare_output_API_performance: Compare output API performance ++++++++++++++++++++++++++++++ Another example of using Rally job is writing custom Rally scenarios in Heat repository. There is an example of this is presented on review: https://review.openstack.org/#/c/270225/ It's similar on the first examnple, but requires more Rally specific coding. New tasks in ``heat-fakevirt.yaml`` use undefined in Rally repository scenarios: - CustomHeatBenchmark.create_stack_and_show_output_new - CustomHeatBenchmark.create_stack_and_show_output_old - CustomHeatBenchmark.create_stack_and_list_output_new - CustomHeatBenchmark.create_stack_and_list_output_old All these scenarios are defined in the same patch and placed in ``rally-scenarios/plugins/`` directory. The aim of these scenarios and tasks is to demonstrate differences between new and old API calls. Heat client has a two commands for operating stack outputs: ``heat output-list`` and ``heat output-show ``. Previously there are no special API calls for getting this information from server and this data was obtained from whole Heat Stack object. This was changed after implementation new API for outputs: https://specs.openstack.org/openstack/heat-specs/specs/mitaka/api-calls-for-output.html As described in the mentioned specification outputs can be obtained via special requests to Heat API. According to this changes code in Heat client was updated to use new API, if it's available. The initial problem for this change was performance issue, which can be formulated as: execution command ``heat output-show `` with old approach required resolving all outputs in Heat Stack, before getting only one output specified by user. The same issue was and with ``heat output-list``, which required to resolve all outputs only for providing list of output keys without resolved values. Two scenarios with suffix ``*_new`` use new output API. These scenarios are not presented in Rally yet, because it's new API. Another two scenarios with suffix ``*_old`` are based on the old approach of getting outputs. This code was partially replaced by new API, so it's not possible to use it on fresh devstack. As result this custom code was written as two custom scenarios. All these scenarios were added to task list and executed in the same time. Results of execution are shown below: create_stack_and_show_output_old -------------------------------- +---------------------+----------+----------+----------+--------+------+ |Action | Min (sec)| Max (sec)| Avg (sec)| Success| Count| +---------------------+----------+----------+----------+--------+------+ |heat.create_stack | 13.559 | 14.298 | 13.899 | 100.0% | 5 | +---------------------+----------+----------+----------+--------+------+ |heat.show_output_old | 5.214 | 5.297 | 5.252 | 100.0% | 5 | +---------------------+----------+----------+----------+--------+------+ |heat.delete_stack | 5.445 | 6.962 | 6.008 | 100.0% | 5 | +---------------------+----------+----------+----------+--------+------+ |total | 24.243 | 26.146 | 25.159 | 100.0% | 5 | +---------------------+----------+----------+----------+--------+------+ create_stack_and_show_output_new -------------------------------- +---------------------+----------+----------+----------+--------+------+ |Action | Min (sec)| Max (sec)| Avg (sec)| Success| Count| +---------------------+----------+----------+----------+--------+------+ |heat.create_stack | 13.719 | 14.286 | 13.935 | 100.0% | 5 | +---------------------+----------+----------+----------+--------+------+ |heat.show_output_new | 0.699 | 0.835 | 0.762 | 100.0% | 5 | +---------------------+----------+----------+----------+--------+------+ |heat.delete_stack | 5.398 | 6.457 | 5.636 | 100.0% | 5 | +---------------------+----------+----------+----------+--------+------+ |total | 19.873 | 21.21 | 20.334 | 100.0% | 5 | +---------------------+----------+----------+----------+--------+------+ Average value for execution ``output-show`` for old approach obviously more, then for new API. It happens, because new API resolve only one specified output. Same results are for ``output-list``: create_stack_and_list_output_old -------------------------------- +---------------------+----------+----------+----------+--------+------+ |Action | Min (sec)| Max (sec)| Avg (sec)| Success| Count| +---------------------+----------+----------+----------+--------+------+ |heat.create_stack | 13.861 | 14.573 | 14.141 | 100.0% | 5 | +---------------------+----------+----------+----------+--------+------+ |heat.list_output_old | 5.247 | 5.339 | 5.281 | 100.0% | 5 | +---------------------+----------+----------+----------+--------+------+ |heat.delete_stack | 6.727 | 6.845 | 6.776 | 100.0% | 5 | +---------------------+----------+----------+----------+--------+------+ |total | 25.886 | 26.696 | 26.199 | 100.0% | 5 | +---------------------+----------+----------+----------+--------+------+ create_stack_and_list_output_new -------------------------------- +---------------------+----------+----------+----------+--------+------+ |Action | Min (sec)| Max (sec)| Avg (sec)| Success| Count| +---------------------+----------+----------+----------+--------+------+ |heat.create_stack | 13.902 | 21.117 | 16.729 | 100.0% | 5 | +---------------------+----------+----------+----------+--------+------+ |heat.list_output_new | 0.147 | 0.363 | 0.213 | 100.0% | 5 | +---------------------+----------+----------+----------+--------+------+ |heat.delete_stack | 6.616 | 8.202 | 7.022 | 100.0% | 5 | +---------------------+----------+----------+----------+--------+------+ |total | 20.838 | 27.908 | 23.964 | 100.0% | 5 | +---------------------+----------+----------+----------+--------+------+ It's also expected, because for getting list of output names is not necessary resolved values, how it is done in new API. All mentioned results clearly show performance changes and allow to confirm, that new approach works correctly. heat-6.0.0/doc/source/developing_guides/pluginguide.rst0000664000567000056710000005746412701407053024425 0ustar jenkinsjenkins00000000000000.. 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. ======================================= Heat Resource Plug-in Development Guide ======================================= Heat allows service providers to extend the capabilities of the orchestration service by writing their own resource plug-ins. These plug-ins are written in Python and included in a directory configured by the service provider. This guide describes a resource plug-in structure and life cycle in order to assist developers in writing their own resource plug-ins. Resource Plug-in Life Cycle --------------------------- A resource plug-in is relatively simple in that it needs to extend a base ``Resource`` class and implement some relevant life cycle handler methods. The basic life cycle methods of a resource are: create The plug-in should create a new physical resource. update The plug-in should update an existing resource with new configuration or tell the engine that the resource must be destroyed and re-created. This method is optional; the default behavior is to create a replacement resource and then delete the old resource. suspend The plug-in should suspend operation of the physical resource; this is an optional operation. resume The plug-in should resume operation of the physical resource; this is an optional operation. delete The plug-in should delete the physical resource. The base class ``Resource`` implements each of these life cycle methods and defines one or more handler methods that plug-ins can implement in order to manifest and manage the actual physical resource abstracted by the plug-in. These handler methods will be described in detail in the following sections. Heat Resource Base Class ++++++++++++++++++++++++ Plug-ins must extend the class ``heat.engine.resource.Resource``. This class is responsible for managing the overall life cycle of the plug-in. It defines methods corresponding to the life cycle as well as the basic hooks for plug-ins to handle the work of communicating with specific down-stream services. For example, when the engine determines it is time to create a resource, it calls the ``create`` method of the applicable plug-in. This method is implemented in the ``Resource`` base class and handles most of the bookkeeping and interaction with the engine. This method then calls a ``handle_create`` method defined in the plug-in class (if implemented) which is responsible for using specific service calls or other methods needed to instantiate the desired physical resource (server, network, volume, etc). Resource Status and Action ************************** The base class handles reporting state of the resource back to the engine. A resource's state is the combination of the life cycle action and the status of that action. For example, if a resource is created successfully, the status of that resource will be ``CREATE COMPLETE``. Alternatively, if the plug-in encounters an error when attempting to create the physical resource, the status would be ``CREATE FAILED``. The base class handles the reporting and persisting of resource state, so a plug-in's handler methods only need to return data or raise exceptions as appropriate. Resource Support Status *********************** New resource should be marked from which OpenStack release it will be available with *support_status* option. For more details, see :ref:`supportstatus`. Properties and Attributes +++++++++++++++++++++++++ A resource's *properties* define the settings the template author can manipulate when including that resource in a template. Some examples would be: * Which flavor and image to use for a Nova server * The port to listen to on Neutron LBaaS nodes * The size of a Cinder volume *Attributes* describe runtime state data of the physical resource that the plug-in can expose to other resources in a Stack. Generally, these aren't available until the physical resource has been created and is in a usable state. Some examples would be: * The host id of a Nova server * The status of a Neutron network * The creation time of a Cinder volume Defining Resource Properties **************************** Each property that a resource supports must be defined in a schema that informs the engine and validation logic what the properties are, what type each is, and validation constraints. The schema is a dictionary whose keys define property names and whose values describe the constraints on that property. This dictionary must be assigned to the ``properties_schema`` attribute of the plug-in. .. code-block:: python from heat.common.i18n import _ from heat.engine import constraints from heat.engine import properties nested_schema = { "foo": properties.Schema( properties.Schema.STRING, _('description of foo field'), constraints=[ constraints.AllowedPattern('(Ba[rc]?)+'), constraints.Length(max=10, description="don't go crazy") ] ) } properties_schema = { "property_name": properties.Schema( properties.Schema.MAP, _('Internationalized description of property'), required=True, default={"Foo": "Bar"}, schema=nested_schema ) } As shown above, some properties may themselves be complex and reference nested schema definitions. Following are the parameters to the ``Schema`` constructor; all but the first have defaults. *data_type*: Defines the type of the property's value. The valid types are the members of the list ``properties.Schema.TYPES``, currently ``INTEGER``, ``STRING``, ``NUMBER``, ``BOOLEAN``, ``MAP``, and ``LIST``; please use those symbolic names rather than the literals to which they are equated. For ``LIST`` and ``MAP`` type properties, the ``schema`` referenced constrains the format of complex items in the list or map. *description*: A description of the property and its function; also used in documentation generation. Default is ``None`` --- but you should always provide a description. *default*: The default value to assign to this property if none was supplied in the template. Default is ``None``. *schema*: This property's value is complex and its members must conform to this referenced schema in order to be valid. The referenced schema dictionary has the same format as the ``properties_schema``. Default is ``None``. *required*: ``True`` if the property must have a value for the template to be valid; ``False`` otherwise. The default is ``False`` *constraints*: A list of constraints that apply to the property's value. See `Property Constraints`_. *update_allowed*: ``True`` if an existing resource can be updated, ``False`` means update is accomplished by delete and re-create. Default is ``False``. *immutable*: ``True`` means updates are not supported, resource update will fail on every change of this property. ``False`` otherwise. Default is ``False``. Accessing property values of the plug-in at runtime is then a simple call to: .. code-block:: python self.properties['PropertyName'] Based on the property type, properties without a set value will return the default "empty" value for that type: ======= ============ Type Empty Value ======= ============ String '' Number 0 Integer 0 List [] Map {} Boolean False ======= ============ Property Constraints ******************** Following are the available kinds of constraints. The description is optional and, if given, states the constraint in plain language for the end user. *AllowedPattern(regex, description)*: Constrains the value to match the given regular expression; applicable to STRING. *AllowedValues(allowed, description)*: Lists the allowed values. ``allowed`` must be a ``collections.Sequence`` or ``basestring``. Applicable to all types of value except MAP. *Length(min, max, description)*: Constrains the length of the value. Applicable to STRING, LIST, MAP. Both ``min`` and ``max`` default to ``None``. *Range(min, max, description)*: Constrains a numerical value. Applicable to INTEGER and NUMBER. Both ``min`` and ``max`` default to ``None``. *CustomConstraint(name, description, environment)*: This constructor brings in a named constraint class from an environment. If the given environment is ``None`` (its default) then the environment used is the global one. Defining Resource Attributes **************************** Attributes communicate runtime state of the physical resource. Note that some plug-ins do not define any attributes and doing so is optional. If the plug-in needs to expose attributes, it will define an ``attributes_schema`` similar to the properties schema described above. This schema, however, is much simpler to define as each item in the dictionary only defines the attribute name and a description of the attribute. .. code-block:: python attributes_schema = { "foo": _("The foo attribute"), "bar": _("The bar attribute"), "baz": _("The baz attribute") } If attributes are defined, their values must also be resolved by the plug-in. The simplest way to do this is to override the ``_resolve_attribute`` method from the ``Resource`` class:: def _resolve_attribute(self, name): # _example_get_physical_resource is just an example and is not defined # in the Resource class phys_resource = self._example_get_physical_resource() if phys_resource: if not hasattr(phys_resource, name): # this is usually not needed, but this is a simple example raise exception.InvalidTemplateAttribute(name) return getattr(phys_resource, name) return None If the plug-in needs to be more sophisticated in its attribute resolution, the plug-in may instead choose to override ``FnGetAtt``. However, if this method is chosen, validation and accessibility of the attribute would be the plug-in's responsibility. Property and Attribute Example ****************************** Assume the following simple property and attribute definition: .. code-block:: python properties_schema = { 'foo': properties.Schema( properties.Schema.STRING, _('foo prop description'), default='foo', required=True ), 'bar': properties.Schema( properties.Schema.INTEGER, _('bar prop description'), required=True, constraints=[ constraints.Range(5, 10) ] ) } attributes_schema = { 'Attr_1': 'The first attribute', 'Attr_2': 'The second attribute' } Also assume the plug-in defining the above has been registered under the template reference name 'Resource::Foo' (see `Registering Resource Plug-ins`_). A template author could then use this plug-in in a stack by simply making following declarations in a template: .. code-block:: yaml # ... other sections omitted for brevity ... resources: resource-1: type: Resource::Foo properties: foo: Value of the foo property bar: 7 outputs: foo-attrib-1: value: { get_attr: [resource-1, Attr_1] } description: The first attribute of the foo resource foo-attrib-2: value: { get_attr: [resource-1, Attr_2] } description: The second attribute of the foo resource Life Cycle Handler Methods ++++++++++++++++++++++++++ To do the work of managing the physical resource the plug-in supports, the following life cycle handler methods should be implemented. Note that the plug-in need not implement *all* of these methods; optional handlers will be documented as such. Generally, the handler methods follow a basic pattern. The basic handler method for any life cycle step follows the format ``handle_``. So for the create step, the handler method would be ``handle_create``. Once a handler is called, an optional ``check__complete`` may also be implemented so that the plug-in may return immediately from the basic handler and then take advantage of cooperative multi-threading built in to the base class and periodically poll a down-stream service for completion; the check method is polled until it returns ``True``. Again, for the create step, this method would be ``check_create_complete``. Create ****** .. py:function:: handle_create(self) Create a new physical resource. This function should make the required calls to create the physical resource and return as soon as there is enough information to identify the resource. The function should return this identifying information and implement ``check_create_complete`` which will take this information in as a parameter and then periodically be polled. This allows for cooperative multi-threading between multiple resources that have had their dependencies satisfied. *Note* once the native identifier of the physical resource is known, this function should call ``self.resource_id_set`` passing the native identifier of the physical resource. This will persist the identifier and make it available to the plug-in by accessing ``self.resource_id``. :returns: A representation of the created physical resource :raise: any ``Exception`` if the create failed .. py:function:: check_create_complete(self, token) If defined, will be called with the return value of ``handle_create`` :param token: the return value of ``handle_create``; used to poll the physical resource's status. :returns: ``True`` if the physical resource is active and ready for use; ``False`` otherwise. :raise: any ``Exception`` if the create failed. Update (Optional) ***************** Note that there is a default implementation of ``handle_update`` in ``heat.engine.resource.Resource`` that simply raises an exception indicating that updates require the engine to delete and re-create the resource (this is the default behavior) so implementing this is optional. .. py:function:: handle_update(self, json_snippet, templ_diff, prop_diff) Update the physical resources using updated information. :param json_snippet: the resource definition from the updated template :type json_snippet: collections.Mapping :param templ_diff: changed values from the original template definition :type templ_diff: collections.Mapping :param prop_diff: property values that are different between the original definition and the updated definition; keys are property names and values are the new values. Deleted or properties that were originally present but now absent have values of ``None`` :type prop_diff: collections.Mapping .. py:function:: check_update_complete(self, token) If defined, will be called with the return value of ``handle_update`` :param token: the return value of ``handle_update``; used to poll the physical resource's status. :returns: ``True`` if the update has finished; ``False`` otherwise. :raise: any ``Exception`` if the update failed. Suspend (Optional) ****************** *These handler functions are optional and only need to be implemented if the physical resource supports suspending* .. py:function:: handle_suspend(self) If the physical resource supports it, this function should call the native API and suspend the resource's operation. This function should return information sufficient for ``check_suspend_complete`` to poll the native API to verify the operation's status. :return: a token containing enough information for ``check_suspend_complete`` to verify operation status. :raise: any ``Exception`` if the suspend operation fails. .. py:function:: check_suspend_complete(self, token) Verify the suspend operation completed successfully. :param token: the return value of ``handle_suspend`` :return: ``True`` if the suspend operation completed and the physical resource is now suspended; ``False`` otherwise. :raise: any ``Exception`` if the suspend operation failed. Resume (Optional) ****************** *These handler functions are optional and only need to be implemented if the physical resource supports resuming from a suspended state* .. py:function:: handle_resume(self) If the physical resource supports it, this function should call the native API and resume a suspended resource's operation. This function should return information sufficient for ``check_resume_complete`` to poll the native API to verify the operation's status. :return: a token containing enough information for ``check_resume_complete`` to verify operation status. :raise: any ``Exception`` if the resume operation fails. .. py:function:: check_resume_complete(self, token) Verify the resume operation completed successfully. :param token: the return value of ``handle_resume`` :return: ``True`` if the resume operation completed and the physical resource is now active; ``False`` otherwise. :raise: any Exception if the resume operation failed. Delete ****** .. py:function:: handle_delete(self) Delete the physical resource. :return: a token containing sufficient data to verify the operations status :raise: any ``Exception`` if the delete operation failed .. note:: As of the Liberty release, implementing handle_delete is optional. The parent resource class can handle the most common pattern for deleting resources: .. code-block:: python def handle_delete(self): if self.resource_id is not None: try: self.client()..delete(self.resource_id) except Exception as ex: self.client_plugin().ignore_not_found(ex) return None return self.resource_id For this to work for a particular resource, the `entity` and `default_client_name` attributes must be overridden in the resource implementation. For example, `entity` of Ceilometer Alarm should equals to "alarms" and `default_client_name` to "ceilometer". .. py:function:: handle_delete_snapshot(self, snapshot) Delete resource snapshot. :param snapshot: dictionary describing current snapshot. :return: a token containing sufficient data to verify the operations status :raise: any ``Exception`` if the delete operation failed .. py:function:: handle_snapshot_delete(self, state) Called instead of ``handle_delete`` when the deletion policy is SNAPSHOT. Create backup of resource and then delete resource. :param state: the (action, status) tuple of the resource to make sure that backup may be created for the current resource :return: a token containing sufficient data to verify the operations status :raise: any ``Exception`` if the delete operation failed .. py:function:: check_delete_complete(self, token) Verify the delete operation completed successfully. :param token: the return value of ``handle_delete`` or ``handle_snapshot_delete`` (for deletion policy - Snapshot) used to verify the status of the operation :return: ``True`` if the delete operation completed and the physical resource is deleted; ``False`` otherwise. :raise: any ``Exception`` if the delete operation failed. .. py:function:: check_delete_snapshot_complete(self, token) Verify the delete snapshot operation completed successfully. :param token: the return value of ``handle_delete_snapshot`` used to verify the status of the operation :return: ``True`` if the delete operation completed and the snapshot is deleted; ``False`` otherwise. :raise: any ``Exception`` if the delete operation failed. Registering Resource Plug-ins +++++++++++++++++++++++++++++ To make your plug-in available for use in stack templates, the plug-in must register a reference name with the engine. This is done by defining a ``resource_mapping`` function in your plug-in module that returns a map of template resource type names and their corresponding implementation classes:: def resource_mapping(): return { 'My::Custom::Plugin': MyResourceClass } This would allow a template author to define a resource as: .. code-block:: yaml resources: my_resource: type: My::Custom::Plugin properties: # ... your plug-in's properties ... Note that you can define multiple plug-ins per module by simply returning a map containing a unique template type name for each. You may also use this to register a single resource plug-in under multiple template type names (which you would only want to do when constrained by backwards compatibility). Configuring the Engine ---------------------- In order to use your plug-in, Heat must be configured to read your resources from a particular directory. The ``plugin_dirs`` configuration option lists the directories on the local file system where the engine will search for plug-ins. Simply place the file containing your resource in one of these directories and the engine will make them available next time the service starts. See one of the Installation Guides at http://docs.OpenStack.org/ for more information on configuring the orchestration service. Testing ------- Tests can live inside the plug-in under the ``tests`` namespace/directory. The Heat plug-in loader will implicitly not load anything under that directory. This is useful when your plug-in tests have dependencies you don't want installed in production. Putting It All Together ----------------------- You can find the plugin classes in ``heat/engine/resources``. An exceptionally simple one to start with is ``random_string.py``; it is unusual in that it does not manipulate anything in the cloud! Resource Contributions ---------------------- The Heat team is interested in adding new resources that give Heat access to additional OpenStack or StackForge projects. The following checklist defines the requirements for a candidate resource to be considered for inclusion: - Must wrap an OpenStack or StackForge project, or a third party project that is relevant to OpenStack users. - Must have its dependencies listed in OpenStack's ``global-requirements.txt`` file, or else it should be able to conditionally disable itself when there are missing dependencies, without crashing or otherwise affecting the normal operation of the heat-engine service. - The resource's support status flag must be set to ``UNSUPPORTED``, to indicate that the Heat team is not responsible for supporting this resource. - The code must be of comparable quality to official resources. The Heat team can help with this during the review phase. If you have a resource that is a good fit, you are welcome to contact the Heat team. If for any reason your resource does not meet the above requirements, but you still think it can be useful to other users, you are encouraged to host it on your own repository and share it as a regular Python installable package. You can find example resource plug-ins that have all the required packaging files in the ``contrib`` directory of the official Heat git repository. heat-6.0.0/doc/source/developing_guides/architecture.rst0000664000567000056710000000703712701407050024557 0ustar jenkinsjenkins00000000000000.. Copyright 2011-2012 OpenStack Foundation 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. ================= Heat architecture ================= Heat is a service to orchestrate multiple composite cloud applications using the `AWS CloudFormation`_ template format, through both an OpenStack-native REST API and a CloudFormation-compatible Query API. Detailed description ~~~~~~~~~~~~~~~~~~~~ What is the purpose of the project and vision for it? *Heat provides an AWS CloudFormation implementation for OpenStack that orchestrates an AWS CloudFormation template describing a cloud application by executing appropriate OpenStack API calls to generate running cloud applications.* Describe the relevance of the project to other OpenStack projects and the OpenStack mission to provide a ubiquitous cloud computing platform: *The software integrates other core components of OpenStack into a one-file template system. The templates allow creation of most OpenStack resource types (such as instances, floating IPs, volumes, security groups and users), as well as some more advanced functionality such as instance high availability, instance autoscaling, and nested stacks. By providing very tight integration with other OpenStack core projects, all OpenStack core projects could receive a larger user base.* *Currently no other CloudFormation implementation exists for OpenStack. The developers believe cloud developers have a strong desire to move workloads from AWS to OpenStack deployments. Given the missing gap of a well-implemented and integrated CloudFormation API in OpenStack, we provide a high quality implementation of this gap improving the ubiquity of OpenStack.* Heat services ~~~~~~~~~~~~~ The developers are focused on creating an OpenStack style project using OpenStack design tenets, implemented in Python. We have started with full integration with keystone. We have a number of components. As the developers have only started development in March 2012, the architecture is evolving rapidly. heat ---- The heat tool is a CLI which communicates with the heat-api to execute AWS CloudFormation APIs. End developers could also use the heat REST API directly. heat-api -------- The heat-api component provides an OpenStack-native REST API that processes API requests by sending them to the heat-engine over RPC. heat-api-cfn ------------ The heat-api-cfn component provides an AWS Query API that is compatible with AWS CloudFormation and processes API requests by sending them to the heat-engine over RPC. heat-engine ----------- The heat-engine's main responsibility is to orchestrate the launching of templates and provide events back to the API consumer. The templates integrate well with Puppet_ and Chef_. .. _Puppet: https://s3.amazonaws.com/cloudformation-examples/IntegratingAWSCloudFormationWithPuppet.pdf .. _Chef: http://www.full360.com/2011/02/27/integrating-aws-cloudformation-and-chef.html .. _`AWS CloudFormation`: http://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/Welcome.html?r=7078 heat-6.0.0/doc/source/sourcecode/0000775000567000056710000000000012701407211017772 5ustar jenkinsjenkins00000000000000heat-6.0.0/doc/source/sourcecode/.gitignore0000664000567000056710000000000612701407050021757 0ustar jenkinsjenkins00000000000000*.rst heat-6.0.0/doc/source/glossary.rst0000664000567000056710000001511512701407050020240 0ustar jenkinsjenkins00000000000000.. 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. ========== Glossary ========== .. glossary:: :sorted: API server HTTP REST API service for heat. CFN An abbreviated form of "AWS CloudFormation". Constraint Defines valid input :term:`parameters` for a :term:`template`. Dependency When a :term:`resource` must wait for another resource to finish creation before being created itself. Heat adds an implicit dependency when a resource references another resource or one of its :term:`attributes `. An explicit dependency can also be created by the user in the template definition. Environment Used to affect the run-time behavior of the template. Provides a way to override the default resource implementation and parameters passed to Heat. See :ref:`Environments`. Heat Orchestration Template A particular :term:`template` format that is native to Heat. Heat Orchestration Templates are expressed in YAML and are not backwards-compatible with CloudFormation templates. HOT An acronym for ":term:`Heat Orchestration Template`". Input parameters See :term:`Parameters`. Metadata May refer to :term:`Resource Metadata`, :term:`Nova Instance metadata`, or the :term:`Metadata service`. Metadata service A Compute service that enables virtual machine instances to retrieve instance-specific data. See `Metadata service (OpenStack Cloud Admin Guide)`_. .. _Metadata service (OpenStack Cloud Admin Guide): http://docs.openstack.org/admin-guide-cloud/compute-networking-nova.html#metadata-service Multi-region A feature of Heat that supports deployment to multiple regions. Nested resource A :term:`resource` instantiated as part of a :term:`nested stack`. Nested stack A :term:`template` referenced by URL inside of another template. Used to reduce redundant resource definitions and group complex architectures into logical groups. Nova Instance metadata User-provided *key:value* pairs associated with a Compute Instance. See `Instance specific data (OpenStack Operations Guide)`_. .. _Instance specific data (OpenStack Operations Guide): http://docs.openstack.org/openstack-ops/content/instances.html#instance_specific_data OpenStack Open source software for building private and public clouds. Orchestrate Arrange or direct the elements of a situation to produce a desired effect. Outputs A top-level block in a :term:`template` that defines what data will be returned by a stack after instantiation. Parameters A top-level block in a :term:`template` that defines what data can be passed to customise a template when it is used to create or update a :term:`stack`. Provider resource A :term:`resource` implemented by a :term:`provider template`. The parent resource's properties become the :term:`nested stack's ` parameters. See `What are "Providers"? (OpenStack Wiki)`_. .. _`What are "Providers"? (OpenStack Wiki)`: https://wiki.openstack.org/wiki/Heat/Providers#What_are_.22Providers.22.3F Provider template Allows user-definable :term:`resource providers ` to be specified via :term:`nested stacks `. The nested stack's :term:`outputs` become the parent stack's :term:`attributes `. Resource An element of OpenStack infrastructure instantiated from a particular :term:`resource provider`. See also :term:`Nested resource`. Resource attribute Data that can be obtained from a :term:`resource`, e.g. a server's public IP or name. Usually passed to another resource's :term:`properties ` or added to the stack's :term:`outputs`. Resource group A :term:`resource provider` that creates one or more identically configured :term:`resources ` or :term:`nested resources `. Resource Metadata A :term:`resource property` that contains CFN-style template metadata. See `AWS::CloudFormation::Init (AWS CloudFormation User Guide)`_ .. _AWS::CloudFormation::Init (AWS CloudFormation User Guide): http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-init.html Resource plugin Python code that understands how to instantiate and manage a :term:`resource`. See `Heat Resource Plugins (OpenStack wiki)`_. .. _Heat Resource Plugins (OpenStack wiki): https://wiki.openstack.org/wiki/Heat/Plugins#Heat_Resource_Plugins Resource property Data utilized for the instantiation of a :term:`resource`. Can be defined statically in a :term:`template` or passed in as :term:`input parameters `. Resource provider The implementation of a particular resource type. May be a :term:`Resource plugin` or a :term:`Provider template`. Stack A collection of instantiated :term:`resources ` that are defined in a single :term:`template`. Stack resource A :term:`resource provider` that allows the management of a :term:`nested stack` as a :term:`resource` in a parent stack. Template An orchestration document that details everything needed to carry out an :term:`orchestration `. Template resource See :term:`Provider resource`. User data A :term:`resource property` that contains a user-provided data blob. User data gets passed to `cloud-init`_ to automatically configure instances at boot time. See also `User data (OpenStack End User Guide)`_. .. _User data (OpenStack End User Guide): http://docs.openstack.org/user-guide/cli_provide_user_data_to_instances.html .. _cloud-init: https://help.ubuntu.com/community/CloudInit Wait condition A :term:`resource provider` that provides a way to communicate data or events from servers back to the orchestration engine. Most commonly used to pause the creation of the :term:`stack` while the server is being configured. heat-6.0.0/doc/source/contributing/0000775000567000056710000000000012701407211020346 5ustar jenkinsjenkins00000000000000heat-6.0.0/doc/source/contributing/index.rst0000664000567000056710000000067712701407050022222 0ustar jenkinsjenkins00000000000000Heat Contribution Guidelines ============================ In the Contributions Guide, you will find documented policies for developing with heat. This includes the processes we use for blueprints and specs, bugs, contributor onboarding, core reviewer memberships, and other procedural items. Policies -------- .. toctree:: :maxdepth: 3 blueprints .. bugs contributor-onboarding core-reviewers gate-failure-triage code-reviews heat-6.0.0/doc/source/contributing/blueprints.rst0000664000567000056710000001030412701407050023266 0ustar jenkinsjenkins00000000000000Blueprints and Specs ==================== The Heat team uses the `heat-specs `_ repository for its specification reviews. Detailed information can be found `here `_. Please note that we use a `template `_ for spec submissions. It is not required to fill out all sections in the template. Spec Notes ---------- There are occasions when a spec is approved and the code does not land in the cycle it was targeted for. For these cases, the workflow to get the spec into the next release is as below: * Anyone can propose a patch to heat-specs which moves a spec from the previous release backlog into the new release directory. The specs which are moved in this way can be fast-tracked into the next release. Please note that it is required to re-propose the spec for the new release and it'll be evaluated based on the resources available and cycle priorities. Heat Spec Lite -------------- Lite specs are small feature requests tracked as Launchpad bugs, with status 'Wishlist' and tagged with 'spec-lite' tag. These allow for submission and review of these feature requests before code is submitted. These can be used for small features that don’t warrant a detailed spec to be proposed, evaluated, and worked on. The team evaluates these requests as it evaluates specs. Once a `spec-lite` bug has been approved/triaged as a Request for Enhancement(RFE), it’ll be targeted for a release. The workflow for the life of a spec-lite in Launchpad is as follows: * File a bug with a small summary of what the requested change is and tag it as `spec-lite`. * The bug is triaged and importance changed to `Wishlist`. * The bug is evaluated and marked as `Triaged` to announce approval or to `Won't fix` to announce rejection or `Invalid` to request a full spec. * The bug is moved to `In Progress` once the code is up and ready to review. * The bug is moved to `Fix Committed` once the patch lands. In summary: +--------------+-----------------------------------------------------------------------------+ |State | Meaning | +==============+=============================================================================+ |New | This is where spec-lite starts, as filed by the community. | +--------------+-----------------------------------------------------------------------------+ |Triaged | Drivers - Move to this state to mean, "you can start working on it" | +--------------+-----------------------------------------------------------------------------+ |Won't Fix | Drivers - Move to this state to reject a lite-spec. | +--------------+-----------------------------------------------------------------------------+ |Invalid | Drivers - Move to this state to request a full spec for this request | +--------------+-----------------------------------------------------------------------------+ The drivers team will discuss the following bug reports in IRC meetings: * `heat RFE's `_ * `python-heatclient RFE's `_ Lite spec Submission Guidelines ------------------------------- When a bug is submitted, there are two fields that must be filled: ‘summary’ and ‘further information’. The ‘summary’ must be brief enough to fit in one line. The ‘further information’ section must be a description of what you would like to see implemented in heat. The description should provide enough details for a knowledgeable developer to understand what is the existing problem and what’s the proposed solution. Add `spec-lite` tag to the bug. Lite spec from existing bugs ---------------------------- If there's an already existing bug that describes a small feature suitable for a spec-lite, add a `spec-lite' tag to the bug. There is no need to create a new bug. The comments and history of the existing bug are important for it's review. heat-6.0.0/doc/source/_templates/0000775000567000056710000000000012701407211017774 5ustar jenkinsjenkins00000000000000heat-6.0.0/doc/source/_templates/.placeholder0000664000567000056710000000000012701407050022246 0ustar jenkinsjenkins00000000000000heat-6.0.0/doc/source/getting_started/0000775000567000056710000000000012701407211021026 5ustar jenkinsjenkins00000000000000heat-6.0.0/doc/source/getting_started/index.rst0000664000567000056710000000134712701407050022675 0ustar jenkinsjenkins00000000000000.. 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. Getting Started Guides ====================== .. toctree:: :maxdepth: 2 on_devstack on_fedora on_ubuntu on_other jeos_building standalone heat-6.0.0/doc/source/getting_started/create_a_stack.rst0000664000567000056710000000720612701407050024516 0ustar jenkinsjenkins00000000000000.. 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. .. _create-a-stack: Creating your first stack ========================= Confirming you can access a Heat endpoint ----------------------------------------- Before any Heat commands can be run, your cloud credentials need to be sourced:: $ source openrc You can confirm that Heat is available with this command:: $ heat stack-list This should return an empty line Preparing to create a stack --------------------------- Your cloud will have different flavors and images available for launching instances, you can discover what is available by running:: $ openstack flavor list $ openstack image list To allow you to SSH into instances launched by Heat, a keypair will be generated:: $ openstack keypair create heat_key > heat_key.priv $ chmod 600 heat_key.priv Launching a stack ----------------- Now lets launch a stack, using an example template from the heat-templates repository:: $ heat stack-create -u http://git.openstack.org/cgit/openstack/heat-templates/plain/hot/F20/WordPress_Native.yaml -P key_name=heat_key -P image_id=my-fedora-image -P instance_type=m1.small teststack Which will respond:: +--------------------------------------+-----------+--------------------+----------------------+ | ID | Name | Status | Created | +--------------------------------------+-----------+--------------------+----------------------+ | (uuid) | teststack | CREATE_IN_PROGRESS | (timestamp) | +--------------------------------------+-----------+--------------------+----------------------+ .. note:: Link on Heat template presented in command above should reference on RAW template. In case if it be a "html" page with template, Heat will return an error. List stacks ~~~~~~~~~~~ List the stacks in your tenant:: $ heat stack-list List stack events ~~~~~~~~~~~~~~~~~ List the events related to a particular stack:: $ heat event-list teststack Describe the wordpress stack ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Show detailed state of a stack:: $ heat stack-show teststack Note: After a few seconds, the stack_status should change from ``IN_PROGRESS`` to ``CREATE_COMPLETE``. Verify instance creation ~~~~~~~~~~~~~~~~~~~~~~~~ Because the software takes some time to install from the repository, it may be a few minutes before the Wordpress instance is in a running state. Point a web browser at the location given by the ``WebsiteURL`` output as shown by ``heat output-show``:: $ WebsiteURL=$(heat output-show --format raw teststack WebsiteURL) $ curl $WebsiteURL Delete the instance when done ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Note: The list operation will show no running stack.:: $ heat stack-delete teststack $ heat stack-list You can explore other heat commands by referring to the `Heat chapter `_ of the `OpenStack Command-Line Interface Reference `_ then read the :ref:`template-guide` and start authoring your own templates. heat-6.0.0/doc/source/getting_started/on_ubuntu.rst0000664000567000056710000000170312701407050023600 0ustar jenkinsjenkins00000000000000.. 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. Installing Heat on Ubuntu ------------------------- Heat is packaged for Debian, and Ubuntu (from 13.10) Go to the `OpenStack Documentation `_ for the latest version of the Installation Guide for Ubuntu which includes a chapter on installing the Orchestration module (Heat). There is a `Juju Charm for Heat ` available. heat-6.0.0/doc/source/getting_started/on_devstack.rst0000664000567000056710000000637512701407053024077 0ustar jenkinsjenkins00000000000000.. 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. Heat and DevStack ================= Heat is fully integrated into DevStack. This is a convenient way to try out or develop heat alongside the current development state of all the other OpenStack projects. Heat on DevStack works on both Ubuntu and Fedora. These instructions assume you already have a working DevStack installation which can launch basic instances. Configure DevStack to enable Heat --------------------------------- Heat is configured by default on devstack for Icehouse and Juno releases. Newer versions of OpenStack require enabling heat services in devstack `local.conf`. Add the following to `[[local|localrc]]` section of `local.conf`:: [[local|localrc]] #Enable heat services enable_service h-eng h-api h-api-cfn h-api-cw It would also be useful to automatically download and register a VM image that Heat can launch. To do that add the following to your devstack `localrc`:: IMAGE_URL_SITE="http://download.fedoraproject.org" IMAGE_URL_PATH="/pub/fedora/linux/releases/21/Cloud/Images/x86_64/" IMAGE_URL_FILE="Fedora-Cloud-Base-20141203-21.x86_64.qcow2" IMAGE_URLS+=","$IMAGE_URL_SITE$IMAGE_URL_PATH$IMAGE_URL_FILE URLs for any cloud image may be specified, but fedora images from F20 contain the heat-cfntools package which is required for some heat functionality. That is all the configuration that is required. When you run `./stack.sh` the Heat processes will be launched in `screen` with the labels prefixed with `h-`. Configure DevStack to enable Ceilometer (if using Alarms) --------------------------------------------------------- To use Ceilometer Alarms you need to enable Ceilometer in devstack. Adding the following lines to your `localrc` file will enable the ceilometer services:: CEILOMETER_BACKEND=mongodb enable_plugin ceilometer https://git.openstack.org/openstack/ceilometer Configure DevStack to enable OSprofiler --------------------------------------- Add the profiler notifier to your Ceilometer to your config:: CEILOMETER_NOTIFICATION_TOPICS=notifications,profiler Enable the profiler in /etc/heat/heat.conf:: $ echo -e "[profiler]\nprofiler_enabled = True\n"\ "trace_sqlalchemy = True\n"\ >> /etc/heat/heat.conf Change the default hmac_key in /etc/heat/api-paste.ini:: $ sed -i "s/hmac_keys =.*/hmac_keys = SECRET_KEY/" /etc/heat/api-paste.ini Run any command with --profile SECRET_KEY:: $ heat --profile SECRET_KEY stack-list # it will print Get pretty HTML with traces:: $ osprofiler trace show --html Note that osprofiler should be run with the admin user name & tenant. Create a stack -------------- Now that you have a working Heat environment you can go to :ref:`create-a-stack`. heat-6.0.0/doc/source/getting_started/on_other.rst0000664000567000056710000000206612701407050023402 0ustar jenkinsjenkins00000000000000.. 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. Installing OpenStack on other distributions =========================================== - There is a `Debian packaging team for OpenStack`_. - There are instructions for `installing OpenStack on Ubuntu`_. - Various other distributions may have packaging teams or getting started guides available. .. _Debian packaging team for OpenStack: http://wiki.openstack.org/Packaging/Debian .. _installing OpenStack on Ubuntu: http://docs.openstack.org/bexar/openstack-compute/admin/content/ch03s02.html heat-6.0.0/doc/source/getting_started/on_fedora.rst0000664000567000056710000000250312701407050023515 0ustar jenkinsjenkins00000000000000.. 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. Installing OpenStack and Heat on RHEL/Fedora/CentOS --------------------------------------------------- Go to the `OpenStack Documentation `_ for the latest version of the Installation Guide for Red Hat Enterprise Linux, CentOS and Fedora which includes a chapter on installing the Orchestration module (Heat). There are instructions for `installing the RDO OpenStack distribution `_ on Fedora and CentOS. If installing with packstack, you can install heat by specifying ``--os-heat-install=y`` in your packstack invocation, or setting ``CONFIG_HEAT_INSTALL=y`` in your answers file. If installing with `RDO-Manager `_ Heat will be installed by default. heat-6.0.0/doc/source/getting_started/jeos_building.rst0000664000567000056710000000745212701407050024406 0ustar jenkinsjenkins00000000000000.. 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. Building JEOS images for use with Heat ====================================== Heat's full functionality can only be used when launching cloud images that have the heat-cfntools_ package installed. This document describes some options for creating a heat-cfntools enabled image for yourself. .. _heat-cfntools: https://git.openstack.org/cgit/openstack/heat-cfntools Building an image with diskimage-builder ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diskimage-builder_ is a tool for customizing cloud images. tripleo-image-elements_ is a collection of diskimage-builder elements related to the TripleO_ project. It includes an element for heat-cfntools which can be used to create heat-enabled images. .. _diskimage-builder: https://git.openstack.org/cgit/openstack/diskimage-builder .. _tripleo-image-elements: https://git.openstack.org/cgit/openstack/tripleo-image-elements .. _TripleO: https://wiki.openstack.org/wiki/TripleO Install the tool (preferably in a virtualenv) and fetch the elements:: pip install git+https://git.openstack.org/openstack/diskimage-builder git clone https://git.openstack.org/openstack/tripleo-image-elements To create a heat-cfntools enabled image with the current release of Fedora x86_64:: export ELEMENTS_PATH=tripleo-image-elements/elements disk-image-create vm fedora heat-cfntools -a amd64 -o fedora-heat-cfntools The image may then be pushed to glance, e.g:: source ~/.openstack/keystonerc glance image-create --name fedora-heat-cfntools --is-public true --disk-format qcow2 --container-format bare < fedora-heat-cfntools.qcow2 To create a heat-cfntools enabled image with the current release of Ubuntu i386:: export ELEMENTS_PATH=tripleo-image-elements/elements disk-image-create vm ubuntu heat-cfntools -a i386 -o ubuntu-heat-cfntools If you are creating your own images you should consider creating golden images which contain all the packages required for the stacks that you launch. You can do this by writing your own diskimage-builder elements and invoking those elements in the call to disk-image-create. This means that the resulting heat templates only need to modify configuration files. This will speed stack launch time and reduce the risk of a transient package download failure causing the stack launch to fail. To create an image that contains hooks needed for SoftwareConfig and SoftwareDeployment, you can follow the steps bellow to build a fedora based image:: pip install git+https://git.openstack.org/openstack/diskimage-builder git clone https://git.openstack.org/openstack/tripleo-image-elements git clone https://git.openstack.org/openstack/heat-templates export ELEMENTS_PATH=tripleo-image-elements/elements:heat-templates/hot/software-config/elements disk-image-create vm \ fedora selinux-permissive \ heat-config \ os-collect-config \ os-refresh-config \ os-apply-config \ heat-config-cfn-init \ heat-config-puppet \ heat-config-script \ -o fedora-software-config.qcow2 The image may then be pushed to glance, e.g:: source ~/.openstack/keystonerc glance image-create --name=fedora-software-config --is-public=true --disk-format=qcow2 --container-format=bare < fedora-software-config.qcow2 heat-6.0.0/doc/source/getting_started/standalone.rst0000664000567000056710000000607112701407050023715 0ustar jenkinsjenkins00000000000000.. 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. How to get heat to work with a remote OpenStack. ================================================ Say you have a remote/public install of OpenStack and you want to use a local install of heat to talk to it. This can be handy when developing, as the remote OpenStack can be kept stable and is not effected by changes made to the development machine. So lets say you have 2 machines: * “rock” ip == 192.168.1.88 (used for base OpenStack services) * “hack” ip == 192.168.1.77 (used for heat development) Install your OpenStack as normal on “rock”. In this example "hack" is used as the devstack to install heat on. The localrc looked like this:: HEAT_STANDALONE=True KEYSTONE_AUTH_HOST=192.168.1.88 KEYSTONE_AUTH_PORT=35357 KEYSTONE_AUTH_PROTOCOL=http KEYSTONE_SERVICE_HOST=$KEYSTONE_AUTH_HOST KEYSTONE_SERVICE_PORT=$KEYSTONE_AUTH_PORT KEYSTONE_SERVICE_PROTOCOL=$KEYSTONE_AUTH_PROTOCOL MY_PASSWORD=abetterpasswordthanthis DATABASE_PASSWORD=$MY_PASSWORD RABBIT_PASSWORD=$MY_PASSWORD disable_all_services # Alternative RPC backends are zeromq and rabbit ENABLED_SERVICES=qpid enable_service mysql heat h-api h-api-cfn h-api-cw h-eng Then run your ./stack.sh as normal. You then need a special environment (not devstack/openrc) to make this work. go to your “rock” machine and get the tenant_id that you want to work with:: keystone tenant-list +----------------------------------+--------------------+---------+ | id | name | enabled | +----------------------------------+--------------------+---------+ | 6943e3ebad0d465387d05d73f8e0b3fc | admin | True | | b12482712e354dd3b9f64ce608ba20f3 | alt_demo | True | | bf03bf32e3884d489004ac995ff7a61c | demo | True | | c23ceb3bf5dd4f9692488855de99137b | invisible_to_admin | True | | c328c1f3b945487d859ed2f53dcf0fe4 | service | True | +----------------------------------+--------------------+---------+ Let's say you want “demo”. Now make a file to store your new environment (heat.env). :: export HEAT_URL=http://192.168.1.77:8004/v1/bf03bf32e3884d489004ac995ff7a61c export OS_NO_CLIENT_AUTH=True export OS_USERNAME=admin export OS_TENANT_NAME=demo export OS_PASSWORD=abetterpasswordthanthis export OS_AUTH_URL=http://192.168.1.88:35357/v2.0/ Now you use this like:: . heat.env heat stack-list Note: remember to open up firewall ports on “rock” so that you can access the OpenStack services. heat-6.0.0/doc/source/man/0000775000567000056710000000000012701407211016412 5ustar jenkinsjenkins00000000000000heat-6.0.0/doc/source/man/heat-manage.rst0000664000567000056710000000263412701407050021321 0ustar jenkinsjenkins00000000000000=========== heat-manage =========== .. program:: heat-manage SYNOPSIS ======== ``heat-manage [options]`` DESCRIPTION =========== heat-manage helps manage heat specific database operations. OPTIONS ======= The standard pattern for executing a heat-manage command is: ``heat-manage []`` Run with -h to see a list of available commands: ``heat-manage -h`` Commands are ``db_version``, ``db_sync``, ``purge_deleted`` and ``service``. Detailed descriptions are below. Heat Db version ~~~~~~~~~~~~~~~ ``heat-manage db_version`` Print out the db schema version. ``heat-manage db_sync`` Sync the database up to the most recent version. ``heat-manage purge_deleted [-g {days,hours,minutes,seconds}] [age]`` Purge db entries marked as deleted and older than [age]. ``heat-manage service list`` Shows details for all currently running heat-engines. ``heat-manage service clean`` Clean dead engine records. ``heat-manage --version`` Shows program's version number and exit. The output could be empty if the distribution didn't specify any version information. FILES ===== The /etc/heat/heat.conf file contains global options which can be used to configure some aspects of heat-manage, for example the DB connection and logging. BUGS ==== * Heat issues are tracked in Launchpad so you can view or report bugs here `OpenStack Heat Bugs `__ heat-6.0.0/doc/source/man/index.rst0000664000567000056710000000063512701407050020260 0ustar jenkinsjenkins00000000000000==================================== Man pages for services and utilities ==================================== ------------- Heat services ------------- .. toctree:: :maxdepth: 2 heat-engine heat-api heat-api-cfn heat-api-cloudwatch -------------- Heat utilities -------------- .. toctree:: :maxdepth: 2 heat-manage heat-db-setup heat-keystone-setup heat-keystone-setup-domain heat-6.0.0/doc/source/man/heat-db-setup.rst0000664000567000056710000000313312701407050021607 0ustar jenkinsjenkins00000000000000============= heat-db-setup ============= .. program:: heat-db-setup SYNOPSIS ======== ``heat-db-setup [COMMANDS] [OPTIONS]`` DESCRIPTION =========== heat-db-setup is a tool which configures the local MySQL database for heat. Typically distro-specific tools would provide this functionality so please read the distro-specific documentation for configuring heat. COMMANDS ======== ``rpm`` Indicate the distribution is a RPM packaging based distribution. ``deb`` Indicate the distribution is a DEB packaging based distribution. OPTIONS ======= .. cmdoption:: -h, --help Print usage information. .. cmdoption:: -p, --password Specify the password for the 'heat' MySQL user that the script will use to connect to the 'heat' MySQL database. By default, the password 'heat' will be used. .. cmdoption:: -r, --rootpw Specify the root MySQL password. If the script installs the MySQL server, it will set the root password to this value instead of prompting for a password. If the MySQL server is already installed, this password will be used to connect to the database instead of having to prompt for it. .. cmdoption:: -y, --yes In cases where the script would normally ask for confirmation before doing something, such as installing mysql-server, just assume yes. This is useful if you want to run the script non-interactively. EXAMPLES ======== heat-db-setup rpm -p heat_password -r mysql_pwd -y heat-db-setup deb -p heat_password -r mysql_pwd -y heat-db-setup rpm BUGS ==== Heat bugs are managed through Launchpad `OpenStack Heat Bugs `__ heat-6.0.0/doc/source/man/heat-api-cloudwatch.rst0000664000567000056710000000165712701407050023001 0ustar jenkinsjenkins00000000000000=================== heat-api-cloudwatch =================== .. program:: heat-api-cloudwatch SYNOPSIS ======== ``heat-api-cloudwatch [options]`` DESCRIPTION =========== heat-api-cloudwatch is a CloudWatch-like API service to the heat project. OPTIONS ======= .. cmdoption:: --config-file Path to a config file to use. Multiple config files can be specified, with values in later files taking precedence. .. cmdoption:: --config-dir Path to a config directory to pull .conf files from. This file set is sorted, so as to provide a predictable parse order if individual options are over-ridden. The set is parsed after the file(s), if any, specified via --config-file, hence over-ridden options in the directory take precedence. .. cmdoption:: --version Show program's version number and exit. The output could be empty if the distribution didn't specify any version information. FILES ======== * /etc/heat/heat.conf heat-6.0.0/doc/source/man/heat-engine.rst0000664000567000056710000000204312701407050021330 0ustar jenkinsjenkins00000000000000=========== heat-engine =========== .. program:: heat-engine SYNOPSIS ======== ``heat-engine [options]`` DESCRIPTION =========== heat-engine is the heat project server with an internal RPC api called by the heat-api server. INVENTORY ========= The heat-engine does all the orchestration work and is the layer in which the resource integration is implemented. OPTIONS ======= .. cmdoption:: --config-file Path to a config file to use. Multiple config files can be specified, with values in later files taking precedence. .. cmdoption:: --config-dir Path to a config directory to pull .conf files from. This file set is sorted, so as to provide a predictable parse order if individual options are over-ridden. The set is parsed after the file(s), if any, specified via --config-file, hence over-ridden options in the directory take precedence. .. cmdoption:: --version Show program's version number and exit. The output could be empty if the distribution didn't specify any version information. FILES ======== * /etc/heat/heat.conf heat-6.0.0/doc/source/man/heat-api.rst0000664000567000056710000000205612701407050020640 0ustar jenkinsjenkins00000000000000======== heat-api ======== .. program:: heat-api SYNOPSIS ======== ``heat-api [options]`` DESCRIPTION =========== heat-api provides an external REST API to the heat project. INVENTORY ========= heat-api is a service that exposes an external REST based api to the heat-engine service. The communication between the heat-api and heat-engine uses message queue based RPC. OPTIONS ======= .. cmdoption:: --config-file Path to a config file to use. Multiple config files can be specified, with values in later files taking precedence. .. cmdoption:: --config-dir Path to a config directory to pull .conf files from. This file set is sorted, so as to provide a predictable parse order if individual options are over-ridden. The set is parsed after the file(s), if any, specified via --config-file, hence over-ridden options in the directory take precedence. .. cmdoption:: --version Show program's version number and exit. The output could be empty if the distribution didn't specify any version information. FILES ======== * /etc/heat/heat.conf heat-6.0.0/doc/source/man/heat-keystone-setup-domain.rst0000664000567000056710000000663312701407050024340 0ustar jenkinsjenkins00000000000000========================== heat-keystone-setup-domain ========================== .. program:: heat-keystone-setup-domain SYNOPSIS ======== ``heat-keystone-setup-domain [OPTIONS]`` DESCRIPTION =========== The `heat-keystone-setup-domain` tool configures keystone by creating a 'stack user domain' and the user credential used to manage this domain. A 'stack user domain' can be treated as a namespace for projects, groups and users created by heat. The domain will have an admin user that manages other users, groups and projects in the domain. This script requires admin keystone credentials to be available in the shell environment by setting `OS_USERNAME` and `OS_PASSWORD`. After running this script, a user needs to take actions to check or modify the heat configuration file (e.g. /etc/heat/heat.conf). The tool is NOT performing these updates on behalf of the user. Distributions may provide other tools to setup 'stack user domain' for use with heat, so check the distro documentation first. Other tools are available to set up the 'stack user domain', for example `python-openstackclient`, which is preferred to this tool where it is available. OPTIONS ======= .. cmdoption:: -h, --help Print usage information. .. cmdoption:: --config-dir Path to a config directory from which to read the ``heat.conf`` file(s). This file set is sorted, so as to provide a predictable parse order if individual options are over-ridden. The set is parsed after the file(s) specified via previous --config-file, arguments hence over-ridden options in the directory take precedence. .. cmdoption:: --config-file Path to a config file to use. Multiple config files can be specified, with values in later files taking precedence. The default files used is `/etc/heat/heat.conf`. .. cmdoption:: --stack-domain-admin Name of a user for Keystone to create, which has roles sufficient to manage users (i.e. stack domain users) and projects (i.e. stack domain projects) in the 'stack user domain'. Another way to specify the admin user name is by setting an environment variable `STACK_DOMAIN_ADMIN` before running this tool. If both command line arguments and environment variable are specified, the command line arguments take precedence. .. cmdoption:: --stack-domain-admin-password Password for the 'stack-domain-admin' user. The password can be instead specified using an environment variable `STACK_DOMAIN_ADMIN_PASSWORD` before invoking this tool. If both command line arguments and environment variable are specified, the command line arguments take precedence. .. cmdoption:: --stack-user-domain-name Name of domain to create for stack users. The domain name can be instead specified using an environment variable `STACK_USER_DOMAIN_NAME` before invoking this tool. If both command line arguments and environment variable are specified, the command line argument take precedence. .. cmdoption:: --version Show program's version number and exit. The output could be empty if the distribution didn't specify any version information. EXAMPLES ======== heat-keystone-setup-domain heat-keystone-setup-domain --stack-user-domain-name heat_user_domain \ --stack-domain-admin heat_domain_admin \ --stack-domain-admin-password verysecrete BUGS ==== Heat bugs are managed through Launchpad `OpenStack Heat Bugs `__ heat-6.0.0/doc/source/man/heat-api-cfn.rst0000664000567000056710000000213312701407050021400 0ustar jenkinsjenkins00000000000000============ heat-api-cfn ============ .. program:: heat-api-cfn SYNOPSIS ======== ``heat-api-cfn [options]`` DESCRIPTION =========== heat-api-cfn is a CloudFormation compatible API service to the heat project. INVENTORY ========= heat-api-cfn is a service that exposes an external REST based api to the heat-engine service. The communication between the heat-api-cfn and heat-engine uses message queue based RPC. OPTIONS ======= .. cmdoption:: --config-file Path to a config file to use. Multiple config files can be specified, with values in later files taking precedence. .. cmdoption:: --config-dir Path to a config directory to pull .conf files from. This file set is sorted, so as to provide a predictable parse order if individual options are over-ridden. The set is parsed after the file(s), if any, specified via --config-file, hence over-ridden options in the directory take precedence. .. cmdoption:: --version Show program's version number and exit. The output could be empty if the distribution didn't specify any version information. FILES ======== * /etc/heat/heat.conf heat-6.0.0/doc/source/man/heat-keystone-setup.rst0000664000567000056710000000132012701407050023057 0ustar jenkinsjenkins00000000000000=================== heat-keystone-setup =================== .. program:: heat-keystone-setup SYNOPSIS ======== ``heat-keystone-setup`` DESCRIPTION =========== Warning: This script is deprecated, please use other tool to setup keystone for heat. The ``heat-keystone-setup`` tool configures keystone for use with heat. This script requires admin keystone credentials to be available in the shell environment and write access to ``/etc/keystone``. Distributions may provide other tools to setup keystone for use with Heat, so check the distro documentation first. EXAMPLES ======== heat-keystone-setup BUGS ==== Heat bugs are managed through Launchpad `OpenStack Heat Bugs `__ heat-6.0.0/doc/source/templates/0000775000567000056710000000000012701407211017635 5ustar jenkinsjenkins00000000000000heat-6.0.0/doc/source/templates/index.rst0000664000567000056710000000155412701407050021504 0ustar jenkinsjenkins00000000000000.. 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. This page documents the templates at https://git.openstack.org/cgit/openstack/heat-templates/ Example HOT Templates ===================== .. toctree:: :maxdepth: 1 hot/hello_world Example CFN Templates ===================== .. toctree:: :maxdepth: 1 cfn/WordPress_Single_Instance heat-6.0.0/doc/source/templates/cfn/0000775000567000056710000000000012701407211020403 5ustar jenkinsjenkins00000000000000heat-6.0.0/doc/source/templates/cfn/WordPress_Single_Instance.rst0000664000567000056710000000341512701407050026216 0ustar jenkinsjenkins00000000000000.. 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. AWS Wordpress Single Instance Template -------------------------------------- https://git.openstack.org/cgit/openstack/heat-templates/tree/cfn/F18/WordPress_Single_Instance.template Description ----------- AWS CloudFormation Sample Template WordPress_Single_Instance: WordPress is web software you can use to create a beautiful website or blog. This template installs a single-instance WordPress deployment using a local MySQL database to store the data. Parameters ---------- *KeyName* :mod:`(required)` *type* *string* *description* Name of an existing EC2 KeyPair to enable SSH access to the instance *InstanceType* :mod:`(optional)` *type* *string* *description* The EC2 instance type *DBName* :mod:`(optional)` *type* *string* *description* The WordPress database name *DBUsernameName* :mod:`(optional)` *type* *string* *description* The WordPress database admin account username *DBPassword* :mod:`(optional)` *type* *string* *description* The WordPress database admin account password *DBRootPassword* :mod:`(optional)` *type* *string* *description* Root password for MySQL *LinuxDistribution* :mod:`(optional)` *type* *string* *description* Linux distribution of choice heat-6.0.0/doc/source/templates/hot/0000775000567000056710000000000012701407211020427 5ustar jenkinsjenkins00000000000000heat-6.0.0/doc/source/templates/hot/hello_world.rst0000664000567000056710000000260312701407050023475 0ustar jenkinsjenkins00000000000000.. 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. Hello World HOT Template ------------------------ https://git.openstack.org/cgit/openstack/heat-templates/tree/hot/hello_world.yaml Description ----------- Hello world HOT template that just defines a single compute instance. Contains just base features to verify base HOT support. Parameters ---------- *key_name* :mod:`(required)` *type* *string* *description* Name of an existing key pair to use for the instance *flavor* :mod:`(optional)` *type* *string* *description* Flavor for the instance to be created *image* :mod:`(required)` *type* *string* *description* Image *ID* or image name to use for the instance *admin_pass* :mod:`(required)` *type* *string* *description* The admin password for the instance *db_port* :mod:`(optional)` *type* *number* *description* The database port number heat-6.0.0/doc/source/template_guide/0000775000567000056710000000000012701407211020627 5ustar jenkinsjenkins00000000000000heat-6.0.0/doc/source/template_guide/index.rst0000664000567000056710000000155512701407050022477 0ustar jenkinsjenkins00000000000000.. 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. .. _template-guide: Template Guide ============== .. toctree:: :maxdepth: 2 hot_guide hello_world hot_spec basic_resources software_deployment environment composition openstack cfn unsupported contrib functions .. existing_templates .. advanced_topics heat-6.0.0/doc/source/template_guide/composition.rst0000664000567000056710000001163212701407050023730 0ustar jenkinsjenkins00000000000000.. highlight: yaml :linenothreshold: 5 .. 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. .. _composition: ==================== Template composition ==================== When writing complex templates you are encouraged to break up your template into separate smaller templates. These can then be brought together using template resources. This is a mechanism to define a resource using a template, thus composing one logical stack with multiple templates. Template resources provide a feature similar to the :ref:`AWS::CloudFormation::Stack` resource, but also provide a way to: * Define new resource types and build your own resource library. * Override the default behavior of existing resource types. To achieve this: * The Orchestration client gets the associated template files and passes them along in the ``files`` section of the ``POST stacks/`` API request. * The environment in the Orchestration engine manages the mapping of resource type to template creation. * The Orchestration engine translates template parameters into resource properties. The following examples illustrate how you can use a custom template to define new types of resources. These examples use a custom template stored in a :file:`my_nova.yaml` file .. code-block:: yaml heat_template_version: 2015-04-30 parameters: key_name: type: string description: Name of a KeyPair resources: server: type: OS::Nova::Server properties: key_name: {get_param: key_name} flavor: m1.small image: ubuntu-trusty-x86_64 Use the template filename as type ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The following template defines the :file:`my_nova.yaml` file as value for the ``type`` property of a resource .. code-block:: yaml heat_template_version: 2015-04-30 resources: my_server: type: my_nova.yaml properties: key_name: my_key The ``key_name`` argument of the ``my_nova.yaml`` template gets its value from the ``key_name`` property of the new template. .. note:: The above reference to :file:`my_nova.yaml` assumes it is in the same directory. You can use any of the following forms: * Relative path (:file:`my_nova.yaml`) * Absolute path (:file:`file:///home/user/templates/my_nova.yaml`) * Http URL (``http://example.com/templates/my_nova.yaml``) * Https URL (``https://example.com/templates/my_nova.yaml``) To create the stack run:: $ heat stack-create -f main.yaml stack1 Define a new resource type ~~~~~~~~~~~~~~~~~~~~~~~~~~ You can associate a name to the :file:`my_nova.yaml` template in an environment file. If the name is already known by the Orchestration module then your new resource will override the default one. In the following example a new ``OS::Nova::Server`` resource overrides the default resource of the same name. An :file:`env.yaml` environment file holds the definition of the new resource .. code-block:: yaml resource_registry: "OS::Nova::Server": my_nova.yaml .. note:: See :ref:`environments` for more detail about environment files. You can now use the new ``OS::Nova::Server`` in your new template .. code-block:: yaml heat_template_version: 2015-04-30 resources: my_server: type: OS::Nova::Server properties: key_name: my_key To create the stack run:: $ heat stack-create -f main.yaml -e env.yaml example-two Get access to nested attributes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ There are implicit attributes of a template resource. Accessing nested attributes requires ``heat_template_version`` 2014-10-16 or higher. These are accessible as follows .. code-block:: yaml heat_template_version: 2015-04-30 resources: my_server: type: my_nova.yaml outputs: test_out: value: {get_attr: my_server, resource.server, first_address} Making your template resource more "transparent" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. note:: Available since 2015.1 (Kilo). If you wish to be able to return the ID of one of the inner resources instead of the nested stack's identifier, you can add the special reserved output ``OS::stack_id`` to your template resource .. code-block:: yaml heat_template_version: 2015-04-30 resources: server: type: OS::Nova::Server outputs: OS::stack_id: value: {get_resource: server} Now when you use ``get_resource`` from the outer template heat will use the nova server id and not the template resource identifier. heat-6.0.0/doc/source/template_guide/unsupported.rst0000664000567000056710000000135512701407050023756 0ustar jenkinsjenkins00000000000000.. 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. Unsupported Heat Resource Types =============================== .. rubric:: These resources are enabled, but are not officially supported. .. unsupportedrespages:: heat-6.0.0/doc/source/template_guide/advanced_topics.rst0000664000567000056710000000044512701407050024513 0ustar jenkinsjenkins00000000000000:orphan: .. _advanced_topics: =============== Advanced topics =============== Networking ~~~~~~~~~~ Load balancer ------------- TODO Firewall -------- TODO VPN --- TODO Auto scaling ~~~~~~~~~~~~ Alarming -------- TODO Up scaling and down scaling --------------------------- TODO heat-6.0.0/doc/source/template_guide/basic_resources.rst0000664000567000056710000003262312701407050024543 0ustar jenkinsjenkins00000000000000.. highlight: yaml :linenothreshold: 5 .. _basic_resources: ========= Instances ========= .. For consistency let's define a few values to use in the samples: * image name: ubuntu-trusty-x86_64 * shared/provider network name: "public" * tenant network and subnet names: "private" and "private-subnet" Manage instances ~~~~~~~~~~~~~~~~ Create an instance ------------------ Use the :ref:`OS::Nova::Server` resource to create a Compute instance. The ``flavor`` property is the only mandatory one, but you need to define a boot source using one of the ``image`` or ``block_device_mapping`` properties. You also need to define the ``networks`` property to indicate to which networks your instance must connect if multiple networks are available in your tenant. The following example creates a simple instance, booted from an image, and connecting to the ``private`` network: .. code-block:: yaml :linenos: resources: instance: type: OS::Nova::Server properties: flavor: m1.small image: ubuntu-trusty-x86_64 networks: - network: private Connect an instance to a network -------------------------------- Use the ``networks`` property of an :ref:`OS::Nova::Server` resource to define which networks an instance should connect to. Define each network as a YAML map, containing one of the following keys: ``port`` The ID of an existing Networking port. You usually create this port in the same template using an :ref:`OS::Neutron::Port` resource. You will be able to associate a floating IP to this port, and the port to your Compute instance. ``network`` The name or ID of an existing network. You don't need to create an :ref:`OS::Neutron::Port` resource if you use this property. But you will not be able to use neutron floating IP association for this instance because there will be no specified port for server. The following example demonstrates the use of the ``port`` and ``network`` properties: .. code-block:: yaml :linenos: resources: instance_port: type: OS::Neutron::Port properties: network: private fixed_ips: - subnet_id: "private-subnet" instance1: type: OS::Nova::Server properties: flavor: m1.small image: ubuntu-trusty-x86_64 networks: - port: { get_resource: instance_port } instance2: type: OS::Nova::Server properties: flavor: m1.small image: ubuntu-trusty-x86_64 networks: - network: private Create and associate security groups to an instance --------------------------------------------------- Use the :ref:`OS::Neutron::SecurityGroup` resource to create security groups. Define the ``security_groups`` property of the :ref:`OS::Neutron::Port` resource to associate security groups to a port, then associate the port to an instance. The following example creates a security group allowing inbound connections on ports 80 and 443 (web server) and associates this security group to an instance port: .. code-block:: yaml :linenos: resources: web_secgroup: type: OS::Neutron::SecurityGroup properties: rules: - protocol: tcp remote_ip_prefix: 0.0.0.0/0 port_range_min: 80 port_range_max: 80 - protocol: tcp remote_ip_prefix: 0.0.0.0/0 port_range_min: 443 port_range_max: 443 instance_port: type: OS::Neutron::Port properties: network: private security_groups: - default - { get_resource: web_secgroup } fixed_ips: - subnet_id: private-subnet instance: type: OS::Nova::Server properties: flavor: m1.small image: ubuntu-trusty-x86_64 networks: - port: { get_resource: instance_port } Create and associate a floating IP to an instance ------------------------------------------------- You can use two sets of resources to create and associate floating IPs to instances. OS::Nova resources ++++++++++++++++++ Use the :ref:`OS::Nova::FloatingIP` resource to create a floating IP, and the :ref:`OS::Nova::FloatingIPAssociation` resource to associate the floating IP to an instance. The following example creates an instance and a floating IP, and associate the floating IP to the instance: .. code-block:: yaml :linenos: resources: floating_ip: type: OS::Nova::FloatingIP properties: pool: public inst1: type: OS::Nova::Server properties: flavor: m1.small image: ubuntu-trusty-x86_64 networks: - network: private association: type: OS::Nova::FloatingIPAssociation properties: floating_ip: { get_resource: floating_ip } server_id: { get_resource: inst1 } OS::Neutron resources +++++++++++++++++++++ .. note:: The Networking service (neutron) must be enabled on your OpenStack deployment to use these resources. Use the :ref:`OS::Neutron::FloatingIP` resource to create a floating IP, and the :ref:`OS::Neutron::FloatingIPAssociation` resource to associate the floating IP to a port: .. code-block:: yaml :linenos: parameters: net: description: name of network used to launch instance. type: string default: private resources: inst1: type: OS::Nova::Server properties: flavor: m1.small image: ubuntu-trusty-x86_64 networks: - network: {get_param: net} floating_ip: type: OS::Neutron::FloatingIP properties: floating_network: public association: type: OS::Neutron::FloatingIPAssociation properties: floatingip_id: { get_resource: floating_ip } port_id: {get_attr: [inst1, addresses, {get_param: net}, 0, port]} You can also create an OS::Neutron::Port and associate that with the server and the floating IP. However the approach mentioned above will work better with stack updates. .. code-block:: yaml :linenos: resources: instance_port: type: OS::Neutron::Port properties: network: private fixed_ips: - subnet_id: "private-subnet" floating_ip: type: OS::Neutron::FloatingIP properties: floating_network: public association: type: OS::Neutron::FloatingIPAssociation properties: floatingip_id: { get_resource: floating_ip } port_id: { get_resource: instance_port } Enable remote access to an instance ----------------------------------- The ``key_name`` attribute of the :ref:`OS::Nova::Server` resource defines the key pair to use to enable SSH remote access: .. code-block:: yaml :linenos: resources: my_instance: type: OS::Nova::Server properties: flavor: m1.small image: ubuntu-trusty-x86_64 key_name: my_key .. note:: For more information about key pairs, see `Configure access and security for instances `_. Create a key pair ----------------- You can create new key pairs with the :ref:`OS::Nova::KeyPair` resource. Key pairs can be imported or created during the stack creation. If the ``public_key`` property is not specified, the Orchestration module creates a new key pair. If the ``save_private_key`` property is set to ``true``, the ``private_key`` attribute of the resource holds the private key. The following example creates a new key pair and uses it as authentication key for an instance: .. code-block:: yaml :linenos: resources: my_key: type: OS::Nova::KeyPair properties: save_private_key: true name: my_key my_instance: type: OS::Nova::Server properties: flavor: m1.small image: ubuntu-trusty-x86_64 key_name: { get_resource: my_key } outputs: private_key: description: Private key value: { get_attr: [ my_key, private_key ] } Manage networks ~~~~~~~~~~~~~~~ Create a network and a subnet ----------------------------- .. note:: The Networking service (neutron) must be enabled on your OpenStack deployment to create and manage networks and subnets. Networks and subnets cannot be created if your deployment uses legacy networking (nova-network). Use the :ref:`OS::Neutron::Net` resource to create a network, and the :ref:`OS::Neutron::Subnet` resource to provide a subnet for this network: .. code-block:: yaml :linenos: resources: new_net: type: OS::Neutron::Net new_subnet: type: OS::Neutron::Subnet properties: network_id: { get_resource: new_net } cidr: "10.8.1.0/24" dns_nameservers: [ "8.8.8.8", "8.8.4.4" ] ip_version: 4 Create and manage a router -------------------------- Use the :ref:`OS::Neutron::Router` resource to create a router. You can define its gateway with the ``external_gateway_info`` property: .. code-block:: yaml :linenos: resources: router1: type: OS::Neutron::Router properties: external_gateway_info: { network: public } You can connect subnets to routers with the :ref:`OS::Neutron::RouterInterface` resource: .. code-block:: yaml :linenos: resources: subnet1_interface: type: OS::Neutron::RouterInterface properties: router_id: { get_resource: router1 } subnet: private-subnet Complete network example ------------------------ The following example creates a network stack: * A network and an associated subnet. * A router with an external gateway. * An interface to the new subnet for the new router. In this example, the ``public`` network is an existing shared network: .. code-block:: yaml :linenos: resources: internal_net: type: OS::Neutron::Net internal_subnet: type: OS::Neutron::Subnet properties: network_id: { get_resource: internal_net } cidr: "10.8.1.0/24" dns_nameservers: [ "8.8.8.8", "8.8.4.4" ] ip_version: 4 internal_router: type: OS::Neutron::Router properties: external_gateway_info: { network: public } internal_interface: type: OS::Neutron::RouterInterface properties: router_id: { get_resource: internal_router } subnet: { get_resource: internal_subnet } Manage volumes ~~~~~~~~~~~~~~ Create a volume --------------- Use the :ref:`OS::Cinder::Volume` resource to create a new Block Storage volume. For example: .. code-block:: yaml :linenos: resources: my_new_volume: type: OS::Cinder::Volume properties: size: 10 The volumes that you create are empty by default. Use the ``image`` property to create a bootable volume from an existing image: .. code-block:: yaml :linenos: resources: my_new_bootable_volume: type: OS::Cinder::Volume properties: size: 10 image: ubuntu-trusty-x86_64 You can also create new volumes from another volume, a volume snapshot, or a volume backup. Use the ``source_volid``, ``snapshot_id`` or ``backup_id`` properties to create a new volume from an existing source. For example, to create a new volume from a backup: .. code-block:: yaml :linenos: resources: another_volume: type: OS::Cinder::Volume properties: backup_id: 2fff50ab-1a9c-4d45-ae60-1d054d6bc868 In this example the ``size`` property is not defined because the Block Storage service uses the size of the backup to define the size of the new volume. Attach a volume to an instance ------------------------------ Use the :ref:`OS::Cinder::VolumeAttachment` resource to attach a volume to an instance. The following example creates a volume and an instance, and attaches the volume to the instance: .. code-block:: yaml :linenos: resources: new_volume: type: OS::Cinder::Volume properties: size: 1 new_instance: type: OS::Nova::Server properties: flavor: m1.small image: ubuntu-trusty-x86_64 volume_attachment: type: OS::Cinder::VolumeAttachment properties: volume_id: { get_resource: new_volume } instance_uuid: { get_resource: new_instance } Boot an instance from a volume ------------------------------ Use the ``block_device_mapping`` property of the :ref:`OS::Nova::Server` resource to define a volume used to boot the instance. This property is a list of volumes to attach to the instance before its boot. The following example creates a bootable volume from an image, and uses it to boot an instance: .. code-block:: yaml :linenos: resources: bootable_volume: type: OS::Cinder::Volume properties: size: 10 image: ubuntu-trusty-x86_64 instance: type: OS::Nova::Server properties: flavor: m1.small networks: - network: private block_device_mapping: - device_name: vda volume_id: { get_resource: bootable_volume } delete_on_termination: false .. TODO A few elements that probably belong here: - OS::Swift::Container - OS::Trove::Instance heat-6.0.0/doc/source/template_guide/hot_spec.rst0000664000567000056710000011240212701407053023171 0ustar jenkinsjenkins00000000000000.. highlight: yaml :linenothreshold: 5 .. 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. .. _hot_spec: =============================================== Heat Orchestration Template (HOT) specification =============================================== HOT is a new template format meant to replace the Heat CloudFormation-compatible format (CFN) as the native format supported by the Heat over time. This specification explains in detail all elements of the HOT template format. An example driven guide to writing HOT templates can be found at :ref:`hot_guide`. Status ~~~~~~ HOT is considered reliable, supported, and standardized as of our Icehouse (April 2014) release. The Heat core team may make improvements to the standard, which very likely would be backward compatible. The template format is also versioned. Since Juno release, Heat supports multiple different versions of the HOT specification. Template structure ~~~~~~~~~~~~~~~~~~ HOT templates are defined in YAML and follow the structure outlined below. .. code-block:: yaml heat_template_version: 2015-04-30 description: # a description of the template parameter_groups: # a declaration of input parameter groups and order parameters: # declaration of input parameters resources: # declaration of template resources outputs: # declaration of output parameters heat_template_version This key with value ``2013-05-23`` (or a later date) indicates that the YAML document is a HOT template of the specified version. description This optional key allows for giving a description of the template, or the workload that can be deployed using the template. parameter_groups This section allows for specifying how the input parameters should be grouped and the order to provide the parameters in. This section is optional and can be omitted when necessary. parameters This section allows for specifying input parameters that have to be provided when instantiating the template. The section is optional and can be omitted when no input is required. resources This section contains the declaration of the single resources of the template. This section with at least one resource should be defined in any HOT template, or the template would not really do anything when being instantiated. outputs This section allows for specifying output parameters available to users once the template has been instantiated. This section is optional and can be omitted when no output values are required. .. _hot_spec_template_version: Heat template version ~~~~~~~~~~~~~~~~~~~~~ The value of ``heat_template_version`` tells Heat not only the format of the template but also features that will be validated and supported. For example, Heat currently supports the following values for the ``heat_template_version`` key: 2013-05-23 ---------- The key with value ``2013-05-23`` indicates that the YAML document is a HOT template and it may contain features implemented until the Icehouse release. This version supports the following functions (some are back ported to this version):: get_attr get_file get_param get_resource list_join resource_facade str_replace Fn::Base64 Fn::GetAZs Fn::Join Fn::MemberListToMap Fn::Replace Fn::ResourceFacade Fn::Select Fn::Split Ref 2014-10-16 ---------- The key with value ``2014-10-16`` indicates that the YAML document is a HOT template and it may contain features added and/or removed up until the Juno release. This version removes most CFN functions that were supported in the Icehouse release, i.e. the ``2013-05-23`` version. So the supported functions now are:: get_attr get_file get_param get_resource list_join resource_facade str_replace Fn::Select 2015-04-30 ---------- The key with value ``2015-04-30`` indicates that the YAML document is a HOT template and it may contain features added and/or removed up until the Kilo release. This version adds the ``repeat`` function. So the complete list of supported functions is:: get_attr get_file get_param get_resource list_join repeat digest resource_facade str_replace Fn::Select 2015-10-15 ---------- The key with value ``2015-10-15`` indicates that the YAML document is a HOT template and it may contain features added and/or removed up until the Liberty release. This version removes the *Fn::Select* function, path based ``get_attr``/``get_param`` references should be used instead. Moreover ``get_attr`` since this version returns dict of all attributes for the given resource excluding *show* attribute, if there's no specified, e.g. :code:`{ get_attr: []}`. This version also adds the str_split function and support for passing multiple lists to the existing list_join function. The complete list of supported functions is:: get_attr get_file get_param get_resource list_join repeat digest resource_facade str_replace str_split 2016-04-08 ---------- The key with value ``2016-04-08`` indicates that the YAML document is a HOT template and it may contain features added and/or removed up until the Mitaka release. This version also adds the map_merge function which can be used to merge the contents of maps. The complete list of supported functions is:: digest get_attr get_file get_param get_resource list_join map_merge repeat resource_facade str_replace str_split .. _hot_spec_parameter_groups: Parameter groups section ~~~~~~~~~~~~~~~~~~~~~~~~ The ``parameter_groups`` section allows for specifying how the input parameters should be grouped and the order to provide the parameters in. These groups are typically used to describe expected behavior for downstream user interfaces. These groups are specified in a list with each group containing a list of associated parameters. The lists are used to denote the expected order of the parameters. Each parameter should be associated to a specific group only once using the parameter name to bind it to a defined parameter in the ``parameters`` section. .. code-block:: yaml parameter_groups: - label: description: parameters: - - label A human-readable label that defines the associated group of parameters. description This attribute allows for giving a human-readable description of the parameter group. parameters A list of parameters associated with this parameter group. param name The name of the parameter that is defined in the associated ``parameters`` section. .. _hot_spec_parameters: Parameters section ~~~~~~~~~~~~~~~~~~ The ``parameters`` section allows for specifying input parameters that have to be provided when instantiating the template. Such parameters are typically used to customize each deployment (e.g. by setting custom user names or passwords) or for binding to environment-specifics like certain images. Each parameter is specified in a separated nested block with the name of the parameters defined in the first line and additional attributes such as type or default value defined as nested elements. .. code-block:: yaml parameters: : type: label: description: default: hidden: constraints: param name The name of the parameter. type The type of the parameter. Supported types are ``string``, ``number``, ``comma_delimited_list``, ``json`` and ``boolean``. This attribute is required. label A human readable name for the parameter. This attribute is optional. description A human readable description for the parameter. This attribute is optional. default A default value for the parameter. This value is used if the user doesn't specify his own value during deployment. This attribute is optional. hidden Defines whether the parameters should be hidden when a user requests information about a stack created from the template. This attribute can be used to hide passwords specified as parameters. This attribute is optional and defaults to ``false``. constraints A list of constraints to apply. The constraints are validated by the Orchestration engine when a user deploys a stack. The stack creation fails if the parameter value doesn't comply to the constraints. This attribute is optional. The table below describes all currently supported types with examples: +----------------------+-------------------------------+------------------+ | Type | Description | Examples | +======================+===============================+==================+ | string | A literal string. | "String param" | +----------------------+-------------------------------+------------------+ | number | An integer or float. | "2"; "0.2" | +----------------------+-------------------------------+------------------+ | comma_delimited_list | An array of literal strings | ["one", "two"]; | | | that are separated by commas. | "one, two"; | | | The total number of strings | Note: "one, two" | | | should be one more than the | returns | | | total number of commas. | ["one", " two"] | +----------------------+-------------------------------+------------------+ | json | A JSON-formatted map or list. | {"key": "value"} | +----------------------+-------------------------------+------------------+ | boolean | Boolean type value, which can | "on"; "n" | | | be equal "t", "true", "on", | | | | "y", "yes", or "1" for true | | | | value and "f", "false", | | | | "off", "n", "no", or "0" for | | | | false value. | | +----------------------+-------------------------------+------------------+ The following example shows a minimalistic definition of two parameters .. code-block:: yaml parameters: user_name: type: string label: User Name description: User name to be configured for the application port_number: type: number label: Port Number description: Port number to be configured for the web server .. note:: The description and the label are optional, but defining these attributes is good practice to provide useful information about the role of the parameter to the user. .. _hot_spec_parameters_constraints: Parameter Constraints --------------------- The ``constraints`` block of a parameter definition defines additional validation constraints that apply to the value of the parameter. The parameter values provided by a user are validated against the constraints at instantiation time. The constraints are defined as a list with the following syntax .. code-block:: yaml constraints: - : description: constraint type Type of constraint to apply. The set of currently supported constraints is given below. constraint definition The actual constraint, depending on the constraint type. The concrete syntax for each constraint type is given below. description A description of the constraint. The text is presented to the user when the value he defines violates the constraint. If omitted, a default validation message is presented to the user. This attribute is optional. The following example shows the definition of a string parameter with two constraints. Note that while the descriptions for each constraint are optional, it is good practice to provide concrete descriptions to present useful messages to the user at deployment time. .. code-block:: yaml parameters: user_name: type: string label: User Name description: User name to be configured for the application constraints: - length: { min: 6, max: 8 } description: User name must be between 6 and 8 characters - allowed_pattern: "[A-Z]+[a-zA-Z0-9]*" description: User name must start with an uppercase character .. note:: While the descriptions for each constraint are optional, it is good practice to provide concrete descriptions so useful messages can be presented to the user at deployment time. The following sections list the supported types of parameter constraints, along with the concrete syntax for each type. length ++++++ The ``length`` constraint applies to parameters of type ``string``. It defines a lower and upper limit for the length of the string value. The syntax of the ``length`` constraint is .. code-block:: yaml length: { min: , max: } It is possible to define a length constraint with only a lower limit or an upper limit. However, at least one of ``min`` or ``max`` must be specified. range +++++ The ``range`` constraint applies to parameters of type ``number``. It defines a lower and upper limit for the numeric value of the parameter. The syntax of the ``range`` constraint is .. code-block:: yaml range: { min: , max: } It is possible to define a range constraint with only a lower limit or an upper limit. However, at least one of ``min`` or ``max`` must be specified. The minimum and maximum boundaries are included in the range. For example, the following range constraint would allow for all numeric values between 0 and 10 .. code-block:: yaml range: { min: 0, max: 10 } allowed_values ++++++++++++++ The ``allowed_values`` constraint applies to parameters of type ``string`` or ``number``. It specifies a set of possible values for a parameter. At deployment time, the user-provided value for the respective parameter must match one of the elements of the list. The syntax of the ``allowed_values`` constraint is .. code-block:: yaml allowed_values: [ , , ... ] Alternatively, the following YAML list notation can be used .. code-block:: yaml allowed_values: - - - ... For example .. code-block:: yaml parameters: instance_type: type: string label: Instance Type description: Instance type for compute instances constraints: - allowed_values: - m1.small - m1.medium - m1.large allowed_pattern +++++++++++++++ The ``allowed_pattern`` constraint applies to parameters of type ``string``. It specifies a regular expression against which a user-provided parameter value must evaluate at deployment. The syntax of the ``allowed_pattern`` constraint is .. code-block:: yaml allowed_pattern: For example .. code-block:: yaml parameters: user_name: type: string label: User Name description: User name to be configured for the application constraints: - allowed_pattern: "[A-Z]+[a-zA-Z0-9]*" description: User name must start with an uppercase character custom_constraint +++++++++++++++++ The ``custom_constraint`` constraint adds an extra step of validation, generally to check that the specified resource exists in the backend. Custom constraints get implemented by plug-ins and can provide any kind of advanced constraint validation logic. The syntax of the ``custom_constraint`` constraint is .. code-block:: yaml custom_constraint: The ``name`` attribute specifies the concrete type of custom constraint. It corresponds to the name under which the respective validation plugin has been registered in the Orchestration engine. For example .. code-block:: yaml parameters: key_name type: string description: SSH key pair constraints: - custom_constraint: nova.keypair The following section lists the custom constraints and the plug-ins that support them. .. table_from_text:: ../../setup.cfg :header: Name,Plug-in :regex: (.*)=(.*) :start-after: heat.constraints = :end-before: heat.stack_lifecycle_plugins = :sort: .. _hot_spec_pseudo_parameters: Pseudo parameters ----------------- In addition to parameters defined by a template author, Heat also creates three parameters for every stack that allow referential access to the stack's name, stack's identifier and project's identifier. These parameters are named ``OS::stack_name`` for the stack name, ``OS::stack_id`` for the stack identifier and ``OS::project_id`` for the project identifier. These values are accessible via the `get_param`_ intrinsic function, just like user-defined parameters. .. note:: ``OS::project_id`` is available since 2015.1 (Kilo). .. _hot_spec_resources: Resources section ~~~~~~~~~~~~~~~~~ The ``resources`` section defines actual resources that make up a stack deployed from the HOT template (for instance compute instances, networks, storage volumes). Each resource is defined as a separate block in the ``resources`` section with the following syntax .. code-block:: yaml resources: : type: properties: : metadata: depends_on: update_policy: deletion_policy: resource ID A resource ID which must be unique within the ``resources`` section of the template. type The resource type, such as ``OS::Nova::Server`` or ``OS::Neutron::Port``. This attribute is required. properties A list of resource-specific properties. The property value can be provided in place, or via a function (see :ref:`hot_spec_intrinsic_functions`). This section is optional. metadata Resource-specific metadata. This section is optional. depends_on Dependencies of the resource on one or more resources of the template. See :ref:`hot_spec_resources_dependencies` for details. This attribute is optional. update_policy Update policy for the resource, in the form of a nested dictionary. Whether update policies are supported and what the exact semantics are depends on the type of the current resource. This attribute is optional. deletion_policy Deletion policy for the resource. Which type of deletion policy is supported depends on the type of the current resource. This attribute is optional. Depending on the type of resource, the resource block might include more resource specific data. All resource types that can be used in CFN templates can also be used in HOT templates, adapted to the YAML structure as outlined above. The following example demonstrates the definition of a simple compute resource with some fixed property values .. code-block:: yaml resources: my_instance: type: OS::Nova::Server properties: flavor: m1.small image: F18-x86_64-cfntools .. _hot_spec_resources_dependencies: Resource dependencies --------------------- The ``depends_on`` attribute of a resource defines a dependency between this resource and one or more other resources. If a resource depends on just one other resource, the ID of the other resource is specified as string of the ``depends_on`` attribute, as shown in the following example .. code-block:: yaml resources: server1: type: OS::Nova::Server depends_on: server2 server2: type: OS::Nova::Server If a resource depends on more than one other resources, the value of the ``depends_on`` attribute is specified as a list of resource IDs, as shown in the following example .. code-block:: yaml resources: server1: type: OS::Nova::Server depends_on: [ server2, server3 ] server2: type: OS::Nova::Server server3: type: OS::Nova::Server .. _hot_spec_outputs: Outputs section ~~~~~~~~~~~~~~~ The ``outputs`` section defines output parameters that should be available to the user after a stack has been created. This would be, for example, parameters such as IP addresses of deployed instances, or URLs of web applications deployed as part of a stack. Each output parameter is defined as a separate block within the outputs section according to the following syntax .. code-block:: yaml outputs: : description: value: parameter name The output parameter name, which must be unique within the ``outputs`` section of a template. description A short description of the output parameter. This attribute is optional. parameter value The value of the output parameter. This value is usually resolved by means of a function. See :ref:`hot_spec_intrinsic_functions` for details about the functions. This attribute is required. The example below shows how the IP address of a compute resource can be defined as an output parameter .. code-block:: yaml outputs: instance_ip: description: IP address of the deployed compute instance value: { get_attr: [my_instance, first_address] } .. _hot_spec_intrinsic_functions: Intrinsic functions ~~~~~~~~~~~~~~~~~~~ HOT provides a set of intrinsic functions that can be used inside templates to perform specific tasks, such as getting the value of a resource attribute at runtime. The following section describes the role and syntax of the intrinsic functions. Note: these functions can only be used within the "properties" section of each resource or in the outputs section. get_attr -------- The ``get_attr`` function references an attribute of a resource. The attribute value is resolved at runtime using the resource instance created from the respective resource definition. Path based attribute referencing using keys or indexes requires ``heat_template_version`` ``2014-10-16`` or higher. The syntax of the ``get_attr`` function is .. code-block:: yaml get_attr: - - - (optional) - (optional) - ... resource name The resource name for which the attribute needs to be resolved. The resource name must exist in the ``resources`` section of the template. attribute name The attribute name to be resolved. If the attribute returns a complex data structure such as a list or a map, then subsequent keys or indexes can be specified. These additional parameters are used to navigate the data structure to return the desired value. The following example demonstrates how to use the :code:`get_attr` function: .. code-block:: yaml resources: my_instance: type: OS::Nova::Server # ... outputs: instance_ip: description: IP address of the deployed compute instance value: { get_attr: [my_instance, first_address] } instance_private_ip: description: Private IP address of the deployed compute instance value: { get_attr: [my_instance, networks, private, 0] } In this example, if the ``networks`` attribute contained the following data:: {"public": ["2001:0db8:0000:0000:0000:ff00:0042:8329", "1.2.3.4"], "private": ["10.0.0.1"]} then the value of ``get_attr`` function would resolve to ``10.0.0.1`` (first item of the ``private`` entry in the ``networks`` map). From ``heat_template_version``: '2015-10-15' is optional and if is not specified, ``get_attr`` returns dict of all attributes for the given resource excluding *show* attribute. In this case syntax would be next: .. code-block:: yaml get_attr: - get_file -------- The ``get_file`` function returns the content of a file into the template. It is generally used as a file inclusion mechanism for files containing scripts or configuration files. The syntax of ``get_file`` function is .. code-block:: yaml get_file: The ``content key`` is used to look up the ``files`` dictionary that is provided in the REST API call. The Orchestration client command (``heat``) is ``get_file`` aware and populates the ``files`` dictionary with the actual content of fetched paths and URLs. The Orchestration client command supports relative paths and transforms these to the absolute URLs required by the Orchestration API. .. note:: The ``get_file`` argument must be a static path or URL and not rely on intrinsic functions like ``get_param``. the Orchestration client does not process intrinsic functions (they are only processed by the Orchestration engine). The example below demonstrates the ``get_file`` function usage with both relative and absolute URLs .. code-block:: yaml resources: my_instance: type: OS::Nova::Server properties: # general properties ... user_data: get_file: my_instance_user_data.sh my_other_instance: type: OS::Nova::Server properties: # general properties ... user_data: get_file: http://example.com/my_other_instance_user_data.sh The ``files`` dictionary generated by the Orchestration client during instantiation of the stack would contain the following keys: * :file:`file:///path/to/my_instance_user_data.sh` * :file:`http://example.com/my_other_instance_user_data.sh` get_param --------- The ``get_param`` function references an input parameter of a template. It resolves to the value provided for this input parameter at runtime. The syntax of the ``get_param`` function is .. code-block:: yaml get_param: - - (optional) - (optional) - ... parameter name The parameter name to be resolved. If the parameters returns a complex data structure such as a list or a map, then subsequent keys or indexes can be specified. These additional parameters are used to navigate the data structure to return the desired value. The following example demonstrates the use of the ``get_param`` function .. code-block:: yaml parameters: instance_type: type: string label: Instance Type description: Instance type to be used. server_data: type: json resources: my_instance: type: OS::Nova::Server properties: flavor: { get_param: instance_type} metadata: { get_param: [ server_data, metadata ] } key_name: { get_param: [ server_data, keys, 0 ] } In this example, if the ``instance_type`` and ``server_data`` parameters contained the following data:: {"instance_type": "m1.tiny", {"server_data": {"metadata": {"foo": "bar"}, "keys": ["a_key","other_key"]}}} then the value of the property ``flavor`` would resolve to ``m1.tiny``, ``metadata`` would resolve to ``{"foo": "bar"}`` and ``key_name`` would resolve to ``a_key``. get_resource ------------ The ``get_resource`` function references another resource within the same template. At runtime, it is resolved to reference the ID of the referenced resource, which is resource type specific. For example, a reference to a floating IP resource returns the respective IP address at runtime. The syntax of the ``get_resource`` function is .. code-block:: yaml get_resource: The resource ID of the referenced resource is given as single parameter to the ``get_resource`` function. For example .. code-block:: yaml resources: instance_port: type: OS::Neutron::Port properties: ... instance: type: OS::Nova::Server properties: ... networks: port: { get_resource: instance_port } list_join --------- The ``list_join`` function joins a list of strings with the given delimiter. The syntax of the ``list_join`` function is .. code-block:: yaml list_join: - - For example .. code-block:: yaml list_join: [', ', ['one', 'two', 'and three']] This resolve to the string ``one, two, and three``. From HOT version ``2015-10-15`` you may optionally pass additional lists, which will be appended to the previous lists to join. For example:: list_join: [', ', ['one', 'two'], ['three', 'four']]] This resolve to the string ``one, two, three, four``. From HOT version ``2015-10-15`` you may optionally also pass non-string list items (e.g json/map/list parameters or attributes) and they will be serialized as json before joining. digest ------ The ``digest`` function allows for performing digest operations on a given value. This function has been introduced in the Kilo release and is usable with HOT versions later than ``2015-04-30``. The syntax of the ``digest`` function is .. code-block:: yaml digest: - - algorithm The digest algorithm. Valid algorithms are the ones provided natively by hashlib (md5, sha1, sha224, sha256, sha384, and sha512) or any one provided by OpenSSL. value The value to digest. This function will resolve to the corresponding hash of the value. For example .. code-block:: yaml # from a user supplied parameter pwd_hash: { digest: ['sha512', { get_param: raw_password }] } The value of the digest function would resolve to the corresponding hash of the value of ``raw_password``. repeat ------ The ``repeat`` function allows for dynamically transforming lists by iterating over the contents of one or more source lists and replacing the list elements into a template. The result of this function is a new list, where the elements are set to the template, rendered for each list item. The syntax of the ``repeat`` function is .. code-block:: yaml repeat: template: