Merge "Create a sample fio test case base on kubernetes"
[yardstick.git] / yardstick / orchestrator / heat.py
index 403c7e8..9da4948 100644 (file)
@@ -15,17 +15,20 @@ import datetime
 import getpass
 import logging
 import pkg_resources
+import pprint
 import socket
 import tempfile
 import time
 
 from oslo_serialization import jsonutils
 from oslo_utils import encodeutils
-import shade
+from shade._heat import event_utils
 
-import yardstick.common.openstack_utils as op_utils
+from yardstick.common import constants as consts
 from yardstick.common import exceptions
 from yardstick.common import template_format
+from yardstick.common import openstack_utils as op_utils
+
 
 log = logging.getLogger(__name__)
 
@@ -38,12 +41,20 @@ _DEPLOYED_STACKS = {}
 class HeatStack(object):
     """Represents a Heat stack (deployed template) """
 
-    def __init__(self, name):
+    def __init__(self, name, os_cloud_config=None):
         self.name = name
         self.outputs = {}
-        self._cloud = shade.openstack_cloud()
+        os_cloud_config = {} if not os_cloud_config else os_cloud_config
+        self._cloud = op_utils.get_shade_client(**os_cloud_config)
         self._stack = None
 
+    def _update_stack_tracking(self):
+        outputs = self._stack.outputs
+        self.outputs = {output['output_key']: output['output_value'] for output
+                        in outputs}
+        if self.uuid:
+            _DEPLOYED_STACKS[self.uuid] = self._stack
+
     def create(self, template, heat_parameters, wait, timeout):
         """Creates an OpenStack stack from a template"""
         with tempfile.NamedTemporaryFile('wb', delete=False) as template_file:
@@ -52,11 +63,25 @@ class HeatStack(object):
             self._stack = self._cloud.create_stack(
                 self.name, template_file=template_file.name, wait=wait,
                 timeout=timeout, **heat_parameters)
-        outputs = self._stack.outputs
-        self.outputs = {output['output_key']: output['output_value'] for output
-                        in outputs}
-        if self.uuid:
-            _DEPLOYED_STACKS[self.uuid] = self._stack
+
+        self._update_stack_tracking()
+
+    def get_failures(self):
+        return event_utils.get_events(self._cloud, self._stack.id,
+                                      event_args={'resource_status': 'FAILED'})
+
+    def get(self):
+        """Retrieves an existing stack from the target cloud
+
+        Returns a bool indicating whether the stack exists in the target cloud
+        If the stack exists, it will be stored as self._stack
+        """
+        self._stack = self._cloud.get_stack(self.name)
+        if not self._stack:
+            return False
+
+        self._update_stack_tracking()
+        return True
 
     @staticmethod
     def stacks_exist():
@@ -128,10 +153,12 @@ name (i.e. %s).
         # short hand for resources part of template
         self.resources = self._template['resources']
 
-    def __init__(self, name, template_file=None, heat_parameters=None):
+    def __init__(self, name, template_file=None, heat_parameters=None,
+                 os_cloud_config=None):
         self.name = name
         self.keystone_client = None
         self.heat_parameters = {}
+        self._os_cloud_config = {} if not os_cloud_config else os_cloud_config
 
         # heat_parameters is passed to heat in stack create, empty dict when
         # yardstick creates the template (no get_param in resources part)
@@ -200,14 +227,10 @@ name (i.e. %s).
 
     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)
-
+        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}
+        volume_id = {'get_resource': volume_name}
         self.resources[name] = {
             'type': 'OS::Cinder::VolumeAttachment',
             'properties': {'instance_uuid': {'get_resource': server_name},
@@ -316,21 +339,24 @@ name (i.e. %s).
             }
         }
 
-    def add_port(self, name, network_name, subnet_name, vnic_type, sec_group_id=None,
+    def add_port(self, name, network, 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)
+        net_is_existing = network.net_flags.get(consts.IS_EXISTING)
+        depends_on = [] if net_is_existing else [network.subnet_stack_name]
+        fixed_ips = [{'subnet': network.subnet}] if net_is_existing else [
+            {'subnet': {'get_resource': network.subnet_stack_name}}]
+        network_ = network.name if net_is_existing else {
+            'get_resource': network.stack_name}
         self.resources[name] = {
             'type': 'OS::Neutron::Port',
-            'depends_on': [subnet_name],
+            'depends_on': depends_on,
             'properties': {
                 'name': name,
-                'binding:vnic_type': vnic_type,
-                'fixed_ips': [{'subnet': {'get_resource': subnet_name}}],
-                'network_id': {'get_resource': network_name},
-                'replacement_policy': 'AUTO',
+                'binding:vnic_type': network.vnic_type,
+                'fixed_ips': fixed_ips,
+                'network': network_,
             }
         }
 
@@ -347,6 +373,8 @@ name (i.e. %s).
             self.resources[name]['properties'][
                 'allowed_address_pairs'] = allowed_address_pairs
 
+        log.debug("adding Neutron::Port %s", self.resources[name])
+
         self._template['outputs'][name] = {
             'description': 'Address for interface %s' % name,
             'value': {'get_attr': [name, 'fixed_ips', 0, 'ip_address']}
@@ -443,39 +471,77 @@ name (i.e. %s).
             'value': {'get_resource': name}
         }
 
-    def add_security_group(self, name):
+    def add_security_group(self, name, security_group=None):
         """add to the template a Neutron SecurityGroup"""
         log.debug("adding Neutron::SecurityGroup '%s'", name)
+        description = ("Group allowing IPv4 and IPv6 for icmp and upd/tcp on"
+                       "all ports")
+        rules = [
+            {'remote_ip_prefix': '0.0.0.0/0',
+             'protocol': 'tcp',
+             'port_range_min': '1',
+             'port_range_max': '65535'},
+            {'remote_ip_prefix': '0.0.0.0/0',
+             'protocol': 'udp',
+             'port_range_min': '1',
+             'port_range_max': '65535'},
+            {'remote_ip_prefix': '0.0.0.0/0',
+             'protocol': 'icmp'},
+            {'remote_ip_prefix': '::/0',
+             'ethertype': 'IPv6',
+             'protocol': 'tcp',
+             'port_range_min': '1',
+             'port_range_max': '65535'},
+            {'remote_ip_prefix': '::/0',
+             'ethertype': 'IPv6',
+             'protocol': 'udp',
+             'port_range_min': '1',
+             'port_range_max': '65535'},
+            {'remote_ip_prefix': '::/0',
+             'ethertype': 'IPv6',
+             'protocol': 'ipv6-icmp'},
+            {'remote_ip_prefix': '0.0.0.0/0',
+             'direction': 'egress',
+             'protocol': 'tcp',
+             'port_range_min': '1',
+             'port_range_max': '65535'},
+            {'remote_ip_prefix': '0.0.0.0/0',
+             'direction': 'egress',
+             'protocol': 'udp',
+             'port_range_min': '1',
+             'port_range_max': '65535'},
+            {'remote_ip_prefix': '0.0.0.0/0',
+             'direction': 'egress',
+             'protocol': 'icmp'},
+            {'remote_ip_prefix': '::/0',
+             'direction': 'egress',
+             'ethertype': 'IPv6',
+             'protocol': 'tcp',
+             'port_range_min': '1',
+             'port_range_max': '65535'},
+            {'remote_ip_prefix': '::/0',
+             'direction': 'egress',
+             'ethertype': 'IPv6',
+             'protocol': 'udp',
+             'port_range_min': '1',
+             'port_range_max': '65535'},
+            {'remote_ip_prefix': '::/0',
+             'direction': 'egress',
+             'ethertype': 'IPv6',
+             'protocol': 'ipv6-icmp'},
+        ]
+        if security_group:
+            description = "Custom security group rules defined by the user"
+            rules = security_group.get('rules')
+
+        log.debug("The security group rules is %s", rules)
+
         self.resources[name] = {
             'type': 'OS::Neutron::SecurityGroup',
             'properties': {
                 'name': name,
-                'description': "Group allowing IPv4 and IPv6 for icmp and upd/tcp on all ports",
-                'rules': [
-                    {'remote_ip_prefix': '0.0.0.0/0',
-                     'protocol': 'tcp',
-                     'port_range_min': '1',
-                     'port_range_max': '65535'},
-                    {'remote_ip_prefix': '0.0.0.0/0',
-                     'protocol': 'udp',
-                     'port_range_min': '1',
-                     'port_range_max': '65535'},
-                    {'remote_ip_prefix': '0.0.0.0/0',
-                     'protocol': 'icmp'},
-                    {'remote_ip_prefix': '::/0',
-                     'ethertype': 'IPv6',
-                     'protocol': 'tcp',
-                     'port_range_min': '1',
-                     'port_range_max': '65535'},
-                    {'remote_ip_prefix': '::/0',
-                     'ethertype': 'IPv6',
-                     'protocol': 'udp',
-                     'port_range_min': '1',
-                     'port_range_max': '65535'},
-                    {'remote_ip_prefix': '::/0',
-                     'ethertype': 'IPv6',
-                     'protocol': 'ipv6-icmp'}
-                ]
+                'description': description,
+                'rules': rules
             }
         }
 
@@ -564,7 +630,7 @@ name (i.e. %s).
         log.info("Creating stack '%s' START", self.name)
 
         start_time = time.time()
-        stack = HeatStack(self.name)
+        stack = HeatStack(self.name, os_cloud_config=self._os_cloud_config)
         stack.create(self._template, self.heat_parameters, block, timeout)
 
         if not block:
@@ -573,6 +639,9 @@ name (i.e. %s).
             return stack
 
         if stack.status != self.HEAT_STATUS_COMPLETE:
+            for event in stack.get_failures():
+                log.error("%s", event.resource_status_reason)
+            log.error(pprint.pformat(self._template))
             raise exceptions.HeatTemplateError(stack_name=self.name)
 
         log.info("Creating stack '%s' DONE in %d secs",