-##############################################################################
-# Copyright (c) 2015 Ericsson AB and others.
+#############################################################################
+# Copyright (c) 2015-2017 Ericsson AB and others.
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Apache License, Version 2.0
from __future__ import absolute_import
from __future__ import print_function
+from six.moves import range
import collections
import datetime
import socket
import time
-import heatclient
+import heatclient.client
import pkg_resources
from oslo_utils import encodeutils
self._heat_client = None
self.uuid = None
- def _get_heat_client(self):
+ @property
+ def heat_client(self):
"""returns a heat client instance"""
if self._heat_client is None:
def status(self):
"""returns stack state as a string"""
- heat = self._get_heat_client()
- stack = heat.stacks.get(self.uuid)
- return getattr(stack, 'stack_status')
+ heat_client = self.heat_client
+ stack = heat_client.stacks.get(self.uuid)
+ return stack.stack_status
class HeatStack(HeatObject):
return
log.info("Deleting stack '%s', uuid:%s", self.name, self.uuid)
- heat = self._get_heat_client()
+ heat = self.heat_client
template = heat.stacks.get(self.uuid)
start_time = time.time()
template.delete()
- status = self.status()
- while status != u'DELETE_COMPLETE':
+ for status in iter(self.status, u'DELETE_COMPLETE'):
log.debug("stack state %s", status)
if status == u'DELETE_FAILED':
raise RuntimeError(
heat.stacks.get(self.uuid).stack_status_reason)
time.sleep(2)
- status = self.status()
end_time = time.time()
log.info("Deleted stack '%s' in %d secs", self.name,
self._delete()
return
- i = 0
- while i < retries:
+ for _ in range(retries):
try:
self._delete()
break
except RuntimeError as err:
log.warning(err.args)
time.sleep(2)
- i += 1
# if still not deleted try once more and let it fail everything
if self.uuid is not None:
self.name = name
self.state = "NOT_CREATED"
self.keystone_client = None
- self.heat_client = None
self.heat_parameters = {}
# heat_parameters is passed to heat in stack create, empty dict when
'value': {'get_resource': name}
}
- def add_network(self, name, physical_network='physnet1', provider=None):
+ def add_volume(self, name, size=10):
+ """add to the template a volume description"""
+ log.debug("adding Cinder::Volume '%s' size '%d' ", name, size)
+
+ self.resources[name] = {
+ 'type': 'OS::Cinder::Volume',
+ 'properties': {'name': name,
+ 'size': size}
+ }
+
+ self._template['outputs'][name] = {
+ 'description': 'Volume %s ID' % name,
+ 'value': {'get_resource': name}
+ }
+
+ def add_volume_attachment(self, server_name, volume_name, mountpoint=None):
+ """add to the template an association of volume to instance"""
+ log.debug("adding Cinder::VolumeAttachment server '%s' volume '%s' ", server_name,
+ volume_name)
+
+ name = "%s-%s" % (server_name, volume_name)
+
+ volume_id = op_utils.get_volume_id(volume_name)
+ if not volume_id:
+ volume_id = {'get_resource': volume_name}
+ self.resources[name] = {
+ 'type': 'OS::Cinder::VolumeAttachment',
+ 'properties': {'instance_uuid': {'get_resource': server_name},
+ 'volume_id': volume_id}
+ }
+
+ if mountpoint:
+ self.resources[name]['properties']['mountpoint'] = mountpoint
+
+ def add_network(self, name, physical_network='physnet1', provider=None,
+ segmentation_id=None, port_security_enabled=None, network_type=None):
"""add to the template a Neutron Net"""
log.debug("adding Neutron::Net '%s'", name)
if provider is None:
self.resources[name] = {
'type': 'OS::Neutron::Net',
- 'properties': {'name': name}
+ 'properties': {
+ 'name': name,
+ }
}
else:
self.resources[name] = {
'type': 'OS::Neutron::ProviderNet',
'properties': {
'name': name,
- 'network_type': 'vlan',
- 'physical_network': physical_network
- }
+ 'network_type': 'flat' if network_type is None else network_type,
+ 'physical_network': physical_network,
+ },
}
+ if segmentation_id:
+ self.resources[name]['properties']['segmentation_id'] = segmentation_id
+ if network_type is None:
+ self.resources[name]['properties']['network_type'] = 'vlan'
+ # if port security is not defined then don't add to template:
+ # some deployments don't have port security plugin installed
+ if port_security_enabled is not None:
+ self.resources[name]['properties']['port_security_enabled'] = port_security_enabled
def add_server_group(self, name, policies): # pragma: no cover
"""add to the template a ServerGroup"""
'policies': policies}
}
- def add_subnet(self, name, network, cidr):
- """add to the template a Neutron Subnet"""
+ def add_subnet(self, name, network, cidr, enable_dhcp='true', gateway_ip=None):
+ """add to the template a Neutron Subnet
+ """
log.debug("adding Neutron::Subnet '%s' in network '%s', cidr '%s'",
name, network, cidr)
self.resources[name] = {
'properties': {
'name': name,
'cidr': cidr,
- 'network_id': {'get_resource': network}
+ 'network_id': {'get_resource': network},
+ 'enable_dhcp': enable_dhcp,
}
}
+ if gateway_ip == 'null':
+ self.resources[name]['properties']['gateway_ip'] = None
+ elif gateway_ip is not None:
+ self.resources[name]['properties']['gateway_ip'] = gateway_ip
self._template['outputs'][name] = {
'description': 'subnet %s ID' % name,
'value': {'get_resource': name}
}
+ self._template['outputs'][name + "-cidr"] = {
+ 'description': 'subnet %s cidr' % name,
+ 'value': {'get_attr': [name, 'cidr']}
+ }
+ self._template['outputs'][name + "-gateway_ip"] = {
+ 'description': 'subnet %s gateway_ip' % name,
+ 'value': {'get_attr': [name, 'gateway_ip']}
+ }
def add_router(self, name, ext_gw_net, subnet_name):
"""add to the template a Neutron Router and interface"""
}
}
- def add_port(self, name, network_name, subnet_name, sec_group_id=None,
- provider=None):
- """add to the template a named Neutron Port"""
- log.debug("adding Neutron::Port '%s', network:'%s', subnet:'%s', "
- "secgroup:%s", name, network_name, subnet_name, sec_group_id)
+ def add_port(self, name, network_name, subnet_name, vnic_type, sec_group_id=None,
+ provider=None, allowed_address_pairs=None):
+ """add to the template a named Neutron Port
+ """
+ log.debug("adding Neutron::Port '%s', network:'%s', subnet:'%s', vnic_type:'%s', "
+ "secgroup:%s", name, network_name, subnet_name, vnic_type, sec_group_id)
self.resources[name] = {
'type': 'OS::Neutron::Port',
'depends_on': [subnet_name],
'properties': {
'name': name,
+ 'binding:vnic_type': vnic_type,
'fixed_ips': [{'subnet': {'get_resource': subnet_name}}],
'network_id': {'get_resource': network_name},
'replacement_policy': 'AUTO',
self.resources[name]['properties']['security_groups'] = \
[sec_group_id]
+ if allowed_address_pairs:
+ self.resources[name]['properties'][
+ 'allowed_address_pairs'] = allowed_address_pairs
+
self._template['outputs'][name] = {
'description': 'Address for interface %s' % name,
'value': {'get_attr': [name, 'fixed_ips', 0, 'ip_address']}
}
+ self._template['outputs'][name + "-subnet_id"] = {
+ 'description': 'Address for interface %s' % name,
+ 'value': {'get_attr': [name, 'fixed_ips', 0, 'subnet_id']}
+ }
+ self._template['outputs'][name + "-mac_address"] = {
+ 'description': 'MAC Address for interface %s' % name,
+ 'value': {'get_attr': [name, 'mac_address']}
+ }
+ self._template['outputs'][name + "-device_id"] = {
+ 'description': 'Device ID for interface %s' % name,
+ 'value': {'get_attr': [name, 'device_id']}
+ }
+ self._template['outputs'][name + "-network_id"] = {
+ 'description': 'Network ID for interface %s' % name,
+ 'value': {'get_attr': [name, 'network_id']}
+ }
def add_floating_ip(self, name, network_name, port_name, router_if_name,
secgroup_name=None):
'value': {'get_resource': name}
}
- def create(self, block=True):
- """creates a template in the target cloud using heat
+ HEAT_WAIT_LOOP_INTERVAL = 2
+ HEAT_CREATE_COMPLETE_STATUS = u'CREATE_COMPLETE'
+
+ def create(self, block=True, timeout=3600):
+ """
+ creates a template in the target cloud using heat
returns a dict with the requested output values from the template
+
+ :param block: Wait for Heat create to finish
+ :type block: bool
+ :param: timeout: timeout in seconds for Heat create, default 3600s
+ :type timeout: int
"""
log.info("Creating stack '%s'", self.name)
# create stack early to support cleanup, e.g. ctrl-c while waiting
stack = HeatStack(self.name)
- heat = self._get_heat_client()
+ heat_client = self.heat_client
start_time = time.time()
- stack.uuid = self.uuid = heat.stacks.create(
+ stack.uuid = self.uuid = heat_client.stacks.create(
stack_name=self.name, template=self._template,
parameters=self.heat_parameters)['stack']['id']
- status = self.status()
- outputs = []
+ if not block:
+ self.outputs = stack.outputs = {}
+ end_time = time.time()
+ log.info("Created stack '%s' in %.3e secs",
+ self.name, end_time - start_time)
+ return stack
- if block:
- while status != u'CREATE_COMPLETE':
- log.debug("stack state %s", status)
- if status == u'CREATE_FAILED':
- raise RuntimeError(getattr(heat.stacks.get(self.uuid),
- 'stack_status_reason'))
+ time_limit = start_time + timeout
+ for status in iter(self.status, self.HEAT_CREATE_COMPLETE_STATUS):
+ log.debug("stack state %s", status)
+ if status == u'CREATE_FAILED':
+ stack_status_reason = heat_client.stacks.get(self.uuid).stack_status_reason
+ heat_client.stacks.delete(self.uuid)
+ raise RuntimeError(stack_status_reason)
+ if time.time() > time_limit:
+ raise RuntimeError("Heat stack create timeout")
- time.sleep(2)
- status = self.status()
+ time.sleep(self.HEAT_WAIT_LOOP_INTERVAL)
- end_time = time.time()
- outputs = getattr(heat.stacks.get(self.uuid), 'outputs')
- log.info("Created stack '%s' in %d secs",
- self.name, end_time - start_time)
+ end_time = time.time()
+ outputs = heat_client.stacks.get(self.uuid).outputs
+ log.info("Created stack '%s' in %.3e secs",
+ self.name, end_time - start_time)
# keep outputs as unicode
self.outputs = {output["output_key"]: output["output_value"] for output