New Heat IMS testcase 97/59997/6
authorValentin Boucher <valentin.boucher@kontron.com>
Thu, 19 Jul 2018 15:35:50 +0000 (11:35 -0400)
committerCédric Ollivier <cedric.ollivier@orange.com>
Mon, 23 Jul 2018 20:17:34 +0000 (22:17 +0200)
In order to validate OpenStack Master deployment
we create this new testcase using OpenStack Heat
as an Orchestrator for  Clearwater VNF

JIRA: FUNCTEST-995

Change-Id: I8b7b74a3753c2d4d4614e9a2798283bd3f99d5d2
Signed-off-by: Valentin Boucher <valentin.boucher@kontron.com>
docker/vnf/Dockerfile
docker/vnf/testcases.yaml
docs/testing/user/userguide/test_details.rst
functest/ci/config_functest.yaml
functest/ci/testcases.yaml
functest/opnfv_tests/vnf/ims/clearwater.py [moved from functest/opnfv_tests/vnf/ims/clearwater_ims_base.py with 62% similarity]
functest/opnfv_tests/vnf/ims/cloudify_ims.py
functest/opnfv_tests/vnf/ims/cloudify_ims.yaml
functest/opnfv_tests/vnf/ims/heat_ims.py [new file with mode: 0644]
functest/opnfv_tests/vnf/ims/heat_ims.yaml [new file with mode: 0644]
functest/tests/unit/vnf/ims/test_clearwater.py [moved from functest/tests/unit/vnf/ims/test_ims_base.py with 84% similarity]

index 09bb6fb..192c528 100644 (file)
@@ -4,7 +4,8 @@ ARG BRANCH=master
 ARG OPENSTACK_TAG=stable/queens
 ARG VIMS_TEST_TAG=release-129
 ARG QUAFF_TAG=59213d6d8ee29433552bb75f505cdc96b0b18909
-ARG VIMS_TAG=fraser
+ARG CLOUDIFY_VIMS_TAG=fraser
+ARG HEAT_VIMS_TAG=release-129
 ARG VROUTER_TAG=fraser
 ARG JUJU_TAG=tags/juju-2.2.5
 
@@ -27,7 +28,8 @@ RUN apk --no-cache add --update \
     git clone https://github.com/Metaswitch/quaff /src/vims-test/quaff && \
     (cd /src/vims-test/quaff && git checkout $QUAFF_TAG) && \
     git clone --depth 1 -b $VIMS_TEST_TAG https://github.com/Metaswitch/clearwater-build-infra /src/vims-test/build-infra && \
-    git clone --depth 1 -b $VIMS_TAG https://github.com/Orange-OpenSource/opnfv-cloudify-clearwater.git /src/vims && \
+    git clone --depth 1 -b $CLOUDIFY_VIMS_TAG https://github.com/Orange-OpenSource/opnfv-cloudify-clearwater.git /src/cloudify_vims && \
+    git clone --depth 1 -b $HEAT_VIMS_TAG https://github.com/Metaswitch/clearwater-heat.git /src/heat_vims && \
     git clone --depth 1 -b $VROUTER_TAG https://github.com/oolorg/opnfv-vnf-vyos-blueprint.git /src/opnfv-vnf-vyos-blueprint && \
     git clone https://github.com/RebacaInc/abot_charm.git /src/epc-requirements/abot_charm && \
     python3 -m pip install --no-cache-dir --src /src -cupper-constraints.txt -cupper-constraints.opnfv.txt \
@@ -38,7 +40,7 @@ RUN apk --no-cache add --update \
     go install -v github.com/juju/juju/... && \
     rm -r $GOPATH/src/ $GOPATH/pkg && \
     (cd /src/vims-test && bundle config build.nokogiri --use-system-libraries && bundle install --system) && \
-    rm -r upper-constraints.txt upper-constraints.opnfv.txt /src/vims-test/.git /src/vims/.git /src/vims-test/quaff/.git \
+    rm -r upper-constraints.txt upper-constraints.opnfv.txt /src/vims-test/.git /src/cloudify_vims/.git /src/heat_vims/.git /src/vims-test/quaff/.git \
         /src/vims-test/build-infra/.git /src/opnfv-vnf-vyos-blueprint/.git \
         /src/epc-requirements/abot_charm/.git && \
     apk del .build-deps
index 1b2bd98..1f08177 100644 (file)
@@ -21,7 +21,7 @@ tiers:
             -
                 case_name: cloudify_ims
                 project_name: functest
-                criteria: 80
+                criteria: 100
                 blocking: false
                 description: >-
                     This test case deploys an OpenSource vIMS solution from
@@ -33,6 +33,21 @@ tiers:
                     module: 'functest.opnfv_tests.vnf.ims.cloudify_ims'
                     class: 'CloudifyIms'
 
+            -
+                case_name: heat_ims
+                project_name: functest
+                criteria: 100
+                blocking: false
+                description: >-
+                    This test case deploys an OpenSource vIMS solution from
+                    Clearwater using the OpenStack Heat orchestrator.
+                    It also runs some signaling traffic.
+                dependencies:
+                    - DEPLOY_SCENARIO: 'os-.*-nofeature-.*ha'
+                run:
+                    module: 'functest.opnfv_tests.vnf.ims.heat_ims'
+                    class: 'HeatIms'
+
             -
                 case_name: vyos_vrouter
                 project_name: functest
index 992b546..03020f7 100644 (file)
@@ -376,6 +376,27 @@ The Clearwater architecture is described as follows:
    :align: center
    :alt: vIMS architecture
 
+heat_ims
+^^^^^^^^
+The IP Multimedia Subsystem or IP Multimedia Core Network Subsystem (IMS) is an
+architectural framework for delivering IP multimedia services.
+
+vIMS has been integrated in Functest to demonstrate the capability to deploy a
+relatively complex NFV scenario on the OPNFV platform. The deployment of a
+complete functional VNF allows the test of most of the essential functions
+needed for a NFV platform.
+
+The goal of this test suite consists of:
+
+* deploy a Clearwater vIMS (IP Multimedia Subsystem) VNF using
+  OpenStack Heat orchestrator based on a HOT template defined in `[17]`_
+* run suite of signaling tests on top of this VNF
+
+The Clearwater architecture is described as follows:
+
+.. figure:: ../../../images/clearwater-architecture-v2.png
+  :align: center
+  :alt: vIMS architecture
 
 vyos-vrouter
 ^^^^^^^^^^^^
@@ -466,3 +487,4 @@ The kubernetes testcases are distributed across various Tiers:
 .. _`[14]`: https://github.com/oolorg/opnfv-functest-vrouter
 .. _`[15]`: https://www.rebaca.com/abot-test-orchestration-tool/
 .. _`[16]`: https://github.com/kubernetes/community/blob/master/contributors/devel/e2e-tests.md
+.. _`[17]`: https://github.com/Metaswitch/clearwater-heat/blob/release-129/clearwater.yaml
index 5aa02be..7e51ee7 100644 (file)
@@ -165,10 +165,8 @@ vnf:
         tenant_name: cloudify_ims
         tenant_description: vIMS
         config: cloudify_ims.yaml
-    cloudify_ims_perf:
-        tenant_name: cloudify_ims_perf
-        tenant_description: vIMS
-        config: cloudify_ims_perf.yaml
+    heat_ims:
+        config: heat_ims.yaml
     orchestra_openims:
         tenant_name: orchestra_openims
         tenant_description: OpenIMS deployed with Open Baton
index ecd1d80..511935e 100644 (file)
@@ -479,7 +479,7 @@ tiers:
             -
                 case_name: cloudify_ims
                 project_name: functest
-                criteria: 80
+                criteria: 100
                 blocking: false
                 description: >-
                     This test case deploys an OpenSource vIMS solution from
@@ -491,6 +491,21 @@ tiers:
                     module: 'functest.opnfv_tests.vnf.ims.cloudify_ims'
                     class: 'CloudifyIms'
 
+            -
+                case_name: heat_ims
+                project_name: functest
+                criteria: 100
+                blocking: false
+                description: >-
+                    This test case deploys an OpenSource vIMS solution from
+                    Clearwater using the OpenStack Heat orchestrator.
+                    It also runs some signaling traffic.
+                dependencies:
+                    - DEPLOY_SCENARIO: 'os-.*-nofeature-.*ha'
+                run:
+                    module: 'functest.opnfv_tests.vnf.ims.heat_ims'
+                    class: 'HeatIms'
+
             -
                 case_name: vyos_vrouter
                 project_name: functest
@@ -27,10 +27,10 @@ __author__ = ("Valentin Boucher <valentin.boucher@orange.com>, "
               "Helen Yao <helanyao@gmail.com>")
 
 
-class ClearwaterOnBoardingBase(object):
+class ClearwaterTesting(object):
     """vIMS clearwater base usable by several orchestrators"""
 
-    def __init__(self, case_name):
+    def __init__(self, case_name, ellis_ip):
         self.logger = logging.getLogger(__name__)
         self.case_dir = pkg_resources.resource_filename(
             'functest', 'opnfv_tests/vnf/ims')
@@ -44,54 +44,38 @@ class ClearwaterOnBoardingBase(object):
         if not os.path.exists(self.result_dir):
             os.makedirs(self.result_dir)
 
-    def config_ellis(self, ellis_ip, signup_code='secret', two_numbers=False):
+        self.ellis_ip = ellis_ip
+
+    def availability_check_by_creating_numbers(self,
+                                               signup_code='secret',
+                                               two_numbers=False):
         """Create one or two numbers"""
+        assert self.ellis_ip
         output_dict = {}
-        self.logger.debug('Configure Ellis: %s', ellis_ip)
-        output_dict['ellis_ip'] = ellis_ip
-        account_url = 'http://{0}/accounts'.format(ellis_ip)
+        self.logger.debug('Ellis IP: %s', self.ellis_ip)
+        output_dict['ellis_ip'] = self.ellis_ip
+        account_url = 'http://{0}/accounts'.format(self.ellis_ip)
         params = {"password": "functest",
                   "full_name": "opnfv functest user",
                   "email": "functest@opnfv.org",
                   "signup_code": signup_code}
-        req = requests.post(account_url, data=params)
         output_dict['login'] = params
-        if req.status_code != 201 and req.status_code != 409:
-            raise Exception(
-                "Unable to create an account {}\n{}".format(
-                    params, req.text))
-        self.logger.debug(
-            'Account %s is created on Ellis\n%s', params, req.json())
-
-        session_url = 'http://{0}/session'.format(ellis_ip)
+
+        number_res = self._create_ellis_account(account_url, params)
+        output_dict['number'] = number_res
+
+        session_url = 'http://{0}/session'.format(self.ellis_ip)
         session_data = {
             'username': params['email'],
             'password': params['password'],
             'email': params['email']
         }
-        req = requests.post(session_url, data=session_data)
-        if req.status_code != 201:
-            raise Exception('Failed to get cookie for Ellis\n{}'.format(
-                req.text))
-        cookies = req.cookies
-        self.logger.debug('Cookies: %s', cookies)
+        cookies = self._get_ellis_session_cookies(session_url, session_data)
 
         number_url = 'http://{0}/accounts/{1}/numbers'.format(
-            ellis_ip, params['email'])
+            self.ellis_ip, params['email'])
         self.logger.debug('Create 1st calling number on Ellis')
-        i = 30
-        while req.status_code != 200 and i > 0:
-            try:
-                number_res = self._create_ellis_number(number_url, cookies)
-                break
-            except Exception:  # pylint: disable=broad-except
-                if i == 1:
-                    self.logger.exception("Unable to create a number")
-                    raise Exception("Unable to create a number")
-                self.logger.info("Unable to create a number. Retry ..")
-                time.sleep(25)
-            i = i - 1
-        output_dict['number'] = number_res
+        number_res = self._create_ellis_number(number_url, cookies)
 
         if two_numbers:
             self.logger.debug('Create 2nd calling number on Ellis')
@@ -100,18 +84,66 @@ class ClearwaterOnBoardingBase(object):
 
         return output_dict
 
+    def _create_ellis_account(self, account_url, params):
+        i = 50
+        for iloop in range(i):
+            try:
+                req = requests.post(account_url, data=params)
+                if req.status_code == 201:
+                    account_res = req.json()
+                    self.logger.info(
+                        'Account %s is created on Ellis\n%s',
+                        params.get('full_name'), account_res)
+                    return account_res
+                else:
+                    raise Exception("Cannot create ellis account")
+            except Exception:  # pylint: disable=broad-except
+                self.logger.info(
+                    "try %s: cannot create ellis account", iloop + 1)
+                time.sleep(25)
+        raise Exception(
+            "Unable to create an account {}".format(
+                params.get('full_name')))
+
+    def _get_ellis_session_cookies(self, session_url, params):
+        i = 15
+        for iloop in range(i):
+            try:
+                req = requests.post(session_url, data=params)
+                if req.status_code == 201:
+                    cookies = req.cookies
+                    self.logger.debug('cookies: %s', cookies)
+                    return cookies
+                else:
+                    raise Exception('Failed to get cookies for Ellis')
+            except Exception:  # pylint: disable=broad-except
+                self.logger.info(
+                    "try %s: cannot get cookies for Ellis", iloop + 1)
+                time.sleep(10)
+        raise Exception('Failed to get cookies for Ellis')
+
     def _create_ellis_number(self, number_url, cookies):
-        req = requests.post(number_url, cookies=cookies)
-
-        if req.status_code != 200:
-            if req and req.json():
-                reason = req.json()['reason']
-            else:
-                reason = req
-            raise Exception("Unable to create a number: %s" % reason)
-        number_res = req.json()
-        self.logger.info('Calling number is created: %s', number_res)
-        return number_res
+        i = 30
+        for iloop in range(i):
+            try:
+                req = requests.post(number_url, cookies=cookies)
+                if req.status_code == 200:
+                    number_res = req.json()
+                    self.logger.info(
+                        'Calling number is created: %s', number_res)
+                    return number_res
+                else:
+                    if req and req.json():
+                        reason = req.json()['reason']
+                    else:
+                        reason = req
+                    self.logger.info("cannot create a number: %s", reason)
+                    raise Exception('Failed to create a number')
+            except Exception:  # pylint: disable=broad-except
+                self.logger.info(
+                    "try %s: cannot create a number", iloop + 1)
+                time.sleep(25)
+        raise Exception('Failed to create a number')
 
     def run_clearwater_live_test(self, dns_ip, public_domain,
                                  bono_ip=None, ellis_ip=None,
index 36862bd..7ec647c 100644 (file)
@@ -22,7 +22,7 @@ import scp
 import six
 
 from functest.core import cloudify
-from functest.opnfv_tests.vnf.ims import clearwater_ims_base
+from functest.opnfv_tests.vnf.ims import clearwater
 from functest.utils import config
 from functest.utils import env
 
@@ -61,20 +61,17 @@ class CloudifyIms(cloudify.Cloudify):
         self.case_dir = pkg_resources.resource_filename(
             'functest', 'opnfv_tests/vnf/ims')
         config_file = os.path.join(self.case_dir, self.config)
-        self.orchestrator = dict(
-            requirements=get_config("orchestrator.requirements", config_file),
-        )
+
         self.details['orchestrator'] = dict(
             name=get_config("orchestrator.name", config_file),
             version=get_config("orchestrator.version", config_file),
             status='ERROR',
             result=''
         )
-        self.__logger.debug("Orchestrator configuration %s", self.orchestrator)
+
         self.vnf = dict(
             descriptor=get_config("vnf.descriptor", config_file),
-            inputs=get_config("vnf.inputs", config_file),
-            requirements=get_config("vnf.requirements", config_file)
+            inputs=get_config("vnf.inputs", config_file)
         )
         self.details['vnf'] = dict(
             descriptor_version=self.vnf['descriptor']['version'],
@@ -90,6 +87,7 @@ class CloudifyIms(cloudify.Cloudify):
 
         self.image_alt = None
         self.flavor_alt = None
+        self.clearwater = None
 
     def check_requirements(self):
         if env.get('NEW_USER_ROLE').lower() == "admin":
@@ -194,33 +192,34 @@ class CloudifyIms(cloudify.Cloudify):
         execution = wait_for_execution(
             self.cfy_client, execution, self.__logger, timeout=3600)
 
-        duration = time.time() - start_time
-
         self.__logger.info(execution)
-        if execution.status == 'terminated':
-            self.details['vnf'].update(status='PASS', duration=duration)
-            self.result += 1/3 * 100
-            result = True
-        else:
-            self.details['vnf'].update(status='FAIL', duration=duration)
-            result = False
-        return result
+        if execution.status != 'terminated':
+            self.details['vnf'].update(status='FAIL',
+                                       duration=time.time() - start_time)
+            return False
+
+        ellis_ip = self.cfy_client.deployments.outputs.get(
+                self.vnf['descriptor'].get('name'))['outputs']['ellis_ip']
+        self.clearwater = clearwater.ClearwaterTesting(self.case_name,
+                                                       ellis_ip)
+        self.clearwater.availability_check_by_creating_numbers()
+
+        self.details['vnf'].update(status='PASS',
+                                   duration=time.time() - start_time)
+        self.result += 1/3 * 100
+        return True
 
     def test_vnf(self):
         """Run test on clearwater ims instance."""
         start_time = time.time()
 
-        testing = clearwater_ims_base.ClearwaterOnBoardingBase(self.case_name)
-        outputs = self.cfy_client.deployments.outputs.get(
-            self.vnf['descriptor'].get('name'))['outputs']
-        dns_ip = outputs['dns_ip']
-        ellis_ip = outputs['ellis_ip']
-        testing.config_ellis(ellis_ip)
+        dns_ip = self.cfy_client.deployments.outputs.get(
+            self.vnf['descriptor'].get('name'))['outputs']['dns_ip']
 
         if not dns_ip:
             return False
 
-        short_result = testing.run_clearwater_live_test(
+        short_result = self.clearwater.run_clearwater_live_test(
             dns_ip=dns_ip,
             public_domain=self.vnf['inputs']["public_domain"])
         duration = time.time() - start_time
index 6808cf3..869281a 100644 (file)
@@ -1,35 +1,14 @@
 ---
-tenant_images:
-    ubuntu_14.04:
-        /home/opnfv/functest/images/ubuntu-14.04-server-cloudimg-amd64-disk1.img
-    cloudify_manager_4.0:
-        /home/opnfv/functest/images/cloudify-manager-premium-4.0.1.qcow2
 orchestrator:
     name: cloudify
     version: '4.0'
-    requirements:
-        flavor:
-            name: cloudify.medium
-            ram_min: 4096
-        os_image: 'cloudify_manager_4.0'
 vnf:
     name: clearwater
-    version: '107'
+    version: '129'
     descriptor:
-        file_name: /src/vims/openstack-blueprint.yaml
+        file_name: /src/cloudify_vims/openstack-blueprint.yaml
         name: clearwater-opnfv
-        version: '122'
-    requirements:
-        flavor:
-            name: cloudify.small
-            ram_min: 2048
-        compute_quotas:
-            cores: 50
-            instances: 15
-        network_quotas:
-            security_group: 20
-            security_group_rule: 100
-            port: 50
+        version: '129'
     inputs:
         image_id: 'ubuntu_14.04'
         flavor_id: 'cloudify.small'
@@ -46,4 +25,4 @@ vnf:
         homer_cluster_size: 1
 vnf_test_suite:
     name: clearwater-live-test
-    version: "1.0"
+    version: '1.0'
diff --git a/functest/opnfv_tests/vnf/ims/heat_ims.py b/functest/opnfv_tests/vnf/ims/heat_ims.py
new file mode 100644 (file)
index 0000000..32783da
--- /dev/null
@@ -0,0 +1,256 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2018 Kontron, Orange and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+
+"""HeatIms testcase implementation."""
+
+from __future__ import division
+
+import logging
+import os
+import re
+import time
+import yaml
+
+import pkg_resources
+from xtesting.core import testcase
+
+from functest.core import singlevm
+from functest.opnfv_tests.vnf.ims import clearwater
+from functest.utils import config
+from functest.utils import env
+
+__author__ = "Valentin Boucher <valentin.boucher@kontron.com>"
+
+
+class HeatIms(singlevm.VmReady2):
+    # pylint: disable=too-many-instance-attributes
+    """Clearwater vIMS deployed with Heat Orchestrator Case."""
+
+    __logger = logging.getLogger(__name__)
+
+    filename_alt = ('/home/opnfv/functest/images/'
+                    'ubuntu-14.04-server-cloudimg-amd64-disk1.img')
+
+    flavor_alt_ram = 2048
+    flavor_alt_vcpus = 2
+    flavor_alt_disk = 25
+
+    quota_security_group = 20
+    quota_security_group_rule = 100
+    quota_port = 50
+
+    def __init__(self, **kwargs):
+        """Initialize HeatIms testcase object."""
+        if "case_name" not in kwargs:
+            kwargs["case_name"] = "heat_ims"
+        super(HeatIms, self).__init__(**kwargs)
+
+        # Retrieve the configuration
+        try:
+            self.config = getattr(
+                config.CONF, 'vnf_{}_config'.format(self.case_name))
+        except Exception:
+            raise Exception("VNF config file not found")
+
+        self.case_dir = pkg_resources.resource_filename(
+            'functest', 'opnfv_tests/vnf/ims')
+        config_file = os.path.join(self.case_dir, self.config)
+
+        self.vnf = dict(
+            descriptor=get_config("vnf.descriptor", config_file),
+            parameters=get_config("vnf.inputs", config_file)
+        )
+        self.details['vnf'] = dict(
+            descriptor_version=self.vnf['descriptor']['version'],
+            name=get_config("vnf.name", config_file),
+            version=get_config("vnf.version", config_file),
+        )
+        self.__logger.debug("VNF configuration: %s", self.vnf)
+
+        self.image_alt = None
+        self.flavor_alt = None
+        self.keypair = None
+        self.stack = None
+        self.clearwater = None
+        self.role = None
+
+    def execute(self):
+        # pylint: disable=too-many-locals,too-many-statements
+        """
+        Prepare Tenant/User
+
+        network, security group, fip, VM creation
+        """
+        self.orig_cloud.set_network_quotas(
+            self.project.project.name,
+            security_group=self.quota_security_group,
+            security_group_rule=self.quota_security_group_rule,
+            port=self.quota_port)
+        if not self.orig_cloud.get_role("heat_stack_owner"):
+            self.role = self.orig_cloud.create_role("heat_stack_owner")
+        self.orig_cloud.grant_role(
+            "heat_stack_owner", user=self.project.user.id,
+            project=self.project.project.id,
+            domain=self.project.domain.id)
+        self.keypair = self.cloud.create_keypair(
+            '{}-kp_{}'.format(self.case_name, self.guid))
+        self.__logger.debug("keypair: %s", self.keypair)
+
+        if (self.deploy_vnf() and self.test_vnf()):
+            self.result = 100
+            return 0
+        self.result = 1/3 * 100
+        return 1
+
+    def run(self, **kwargs):
+        """Deploy and test clearwater
+
+        Here are the main actions:
+        - deploy clearwater stack via heat
+        - test the vnf instance
+
+        Returns:
+        - TestCase.EX_OK
+        - TestCase.EX_RUN_ERROR on error
+        """
+        status = testcase.TestCase.EX_RUN_ERROR
+        try:
+            assert self.cloud
+            self.start_time = time.time()
+            self.result = 0
+            if not self.execute():
+                self.result = 100
+                status = testcase.TestCase.EX_OK
+        except Exception:  # pylint: disable=broad-except
+            self.__logger.exception('Cannot run %s', self.case_name)
+        finally:
+            self.stop_time = time.time()
+        return status
+
+    def deploy_vnf(self):
+        """Deploy Clearwater IMS."""
+        start_time = time.time()
+
+        self.image_alt = self.publish_image_alt()
+        self.flavor_alt = self.create_flavor_alt()
+        # KeyPair + Image + Flavor OK
+
+        descriptor = self.vnf['descriptor']
+        parameters = self.vnf['parameters']
+
+        parameters['public_mgmt_net_id'] = self.ext_net.id
+        parameters['public_sig_net_id'] = self.ext_net.id
+        parameters['flavor'] = self.flavor_alt.name
+        parameters['image'] = self.image_alt.name
+        parameters['key_name'] = self.keypair.name
+        parameters['external_mgmt_dns_ip'] = env.get('NAMESERVER')
+        parameters['external_sig_dns_ip'] = env.get('NAMESERVER')
+
+        self.__logger.info("Create Heat Stack")
+        self.stack = self.cloud.create_stack(
+            name=descriptor.get('name'),
+            template_file=descriptor.get('file_name'),
+            wait=True, **parameters)
+        self.__logger.debug("stack: %s", self.stack)
+
+        servers = self.cloud.list_servers(detailed=True)
+        self.__logger.debug("servers: %s", servers)
+        for server in servers:
+            if not self.check_regex_in_console(
+                    server.name, regex='Cloud-init .* finished at ', loop=60):
+                return False
+            if 'ellis' in server.name:
+                self.__logger.debug("server: %s", server)
+                ellis_ip = server.public_v4
+
+        assert ellis_ip
+        self.clearwater = clearwater.ClearwaterTesting(self.case_name,
+                                                       ellis_ip)
+        # This call can take time and many retry because Heat is
+        # an infrastructure orchestrator so when Heat say "stack created"
+        # it means that all OpenStack ressources are created but not that
+        # Clearwater are up and ready (Cloud-Init script still running)
+        self.clearwater.availability_check_by_creating_numbers()
+
+        duration = time.time() - start_time
+
+        self.details['vnf'].update(status='PASS', duration=duration)
+        self.result += 1/3 * 100
+
+        return True
+
+    def test_vnf(self):
+        """Run test on clearwater ims instance."""
+        start_time = time.time()
+
+        outputs = self.cloud.get_stack(self.stack.id).outputs
+        self.__logger.debug("stack outputs: %s", outputs)
+        dns_ip = re.findall(r'[0-9]+(?:\.[0-9]+){3}', str(outputs))[0]
+
+        if not dns_ip:
+            return False
+
+        short_result = self.clearwater.run_clearwater_live_test(
+            dns_ip=dns_ip,
+            public_domain=self.vnf['parameters']["zone"])
+        duration = time.time() - start_time
+        self.__logger.info(short_result)
+        self.details['test_vnf'] = dict(result=short_result,
+                                        duration=duration)
+        try:
+            vnf_test_rate = short_result['passed'] / (
+                short_result['total'] - short_result['skipped'])
+            # orchestrator + vnf + test_vnf
+            self.result += vnf_test_rate / 3 * 100
+        except ZeroDivisionError:
+            self.__logger.error("No test has been executed")
+            self.details['test_vnf'].update(status='FAIL')
+            return False
+        except Exception:  # pylint: disable=broad-except
+            self.__logger.exception("Cannot calculate results")
+            self.details['test_vnf'].update(status='FAIL')
+            return False
+        return True if vnf_test_rate > 0 else False
+
+    def clean(self):
+        """Clean created objects/functions."""
+        assert self.cloud
+        try:
+            if self.stack:
+                self.cloud.delete_stack(self.stack.id, wait=True)
+        except Exception:  # pylint: disable=broad-except
+            self.__logger.exception("Cannot clean stack ressources")
+        super(HeatIms, self).clean()
+        if self.role:
+            self.orig_cloud.delete_role(self.role.id)
+
+
+# ----------------------------------------------------------
+#
+#               YAML UTILS
+#
+# -----------------------------------------------------------
+def get_config(parameter, file_path):
+    """
+    Get config parameter.
+
+    Returns the value of a given parameter in file.yaml
+    parameter must be given in string format with dots
+    Example: general.openstack.image_name
+    """
+    with open(file_path) as config_file:
+        file_yaml = yaml.safe_load(config_file)
+    config_file.close()
+    value = file_yaml
+    for element in parameter.split("."):
+        value = value.get(element)
+        if value is None:
+            raise ValueError("The parameter %s is not defined in"
+                             " reporting.yaml" % parameter)
+    return value
diff --git a/functest/opnfv_tests/vnf/ims/heat_ims.yaml b/functest/opnfv_tests/vnf/ims/heat_ims.yaml
new file mode 100644 (file)
index 0000000..883c4df
--- /dev/null
@@ -0,0 +1,22 @@
+---
+orchestrator:
+    name: heat
+    version: '4.0'
+vnf:
+    name: clearwater
+    version: '129'
+    descriptor:
+        file_name: /src/heat_vims/clearwater.yaml
+        name: clearwater-opnfv
+        version: '129'
+    inputs:
+        zone: clearwater.opnfv
+        dn_range_start: "6505550000"
+        dn_range_length: "1000"
+        bono_cluster_size: 1
+        sprout_cluster_size: 1
+        vellum_cluster_size: 1
+        dime_cluster_size: 1
+        homer_cluster_size: 1
+        dnssec_key:
+            GkBraPnditvP2Em4oXV5wUTawmZaGGuO+Jt3ZnFkznGV3zFoQ+Ak13nuuOnO0JV5FqAr/KitdW6siqjXSjROXg==
@@ -12,15 +12,15 @@ import unittest
 
 import mock
 
-from functest.opnfv_tests.vnf.ims import clearwater_ims_base as ims_base
+from functest.opnfv_tests.vnf.ims import clearwater
 
 
-class ClearwaterOnBoardingBaseTesting(unittest.TestCase):
+class ClearwaterTesting(unittest.TestCase):
 
     def setUp(self):
         with mock.patch('functest.opnfv_tests.vnf.ims.cloudify_ims.'
                         'os.makedirs'):
-            self.ims_vnf = ims_base.ClearwaterOnBoardingBase("foo")
+            self.ims_vnf = clearwater.ClearwaterTesting("foo", "0.0.0.0")
 
         self.mock_post = mock.Mock()
         attrs = {'status_code': 201,