# Copyright 2013 Mirantis, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import contextlib import logging import os import time import zipfile try: from oslo.serialization import jsonutils except ImportError: from oslo_serialization import jsonutils import muranoclient.common.exceptions as exceptions import requests from fuel_health.common.utils.data_utils import rand_name import fuel_health.nmanager LOG = logging.getLogger(__name__) class MuranoTest(fuel_health.nmanager.PlatformServicesBaseClass): """Manager that provides access to the Murano python client for calling Murano API. """ @classmethod def setUpClass(cls): super(MuranoTest, cls).setUpClass() cls.packages = [] cls.environments = [] def setUp(self): super(MuranoTest, self).setUp() self.check_clients_state() self.env_name = rand_name("ostf_test-Murano_env") if not self.config.compute.compute_nodes: self.skipTest('There are no compute nodes to run tests') self.min_required_ram_mb = 4096 self.murano_available = True self.endpoint = '{0}/v1/'.format( self.identity_client.service_catalog.url_for( service_type='application-catalog', endpoint_type='publicURL')) self.headers = { 'X-Auth-Token': self.murano_client.http_client.auth_token, 'content-type': 'application/json' } try: self.list_environments() except exceptions.CommunicationError: self.murano_available = False self.skipTest("Murano service is not available") def tearDown(self): """This method allows to clean up the OpenStack environment after the Murano OSTF tests. """ if self.murano_available: if self.environments: for environment_id in self.environments: try: self.delete_environment(environment_id) except Exception: LOG.exception('Failure to delete environment \ {0}'.format(environment_id)) if self.packages: for package in self.packages: try: self.delete_package(package.id) except Exception: LOG.exception('Failure to delete package \ {0}'.format(package.id)) super(MuranoTest, self).tearDown() def zip_dir(self, parent_dir, app_dir): """This method allows to zip directory with application :param parent_dir: Directory, where application lives :param app_dir: Directory with application :return: """ abs_path = os.path.join(parent_dir, app_dir) path_len = len(abs_path) + 1 zip_file = abs_path + ".zip" with contextlib.closing(zipfile.ZipFile(zip_file, "w")) as zf: for dir_name, _, files in os.walk(abs_path): for filename in files: fn = os.path.join(dir_name, filename) zf.write(fn, fn[path_len:]) return zip_file def find_murano_image(self, image_type): """This method allows to find Windows images with Murano tag. Returns the image object or None image_type should be in [linux, windows.2012, cirros.demo] """ tag = 'murano_image_info' for image in self.compute_client.images.list(): if tag in image.metadata and image.status.lower() == 'active': metadata = jsonutils.loads(image.metadata[tag]) if image_type == metadata['type']: return image def list_environments(self): """This method allows to get the list of environments. Returns the list of environments. """ resp = requests.get(self.endpoint + 'environments', headers=self.headers, verify=False) return resp.json() def create_environment(self, name): """This method allows to create environment. Input parameters: name - Name of new environment Returns new environment. """ environment = self.murano_client.environments.create({'name': name}) self.environments.append(environment.id) return environment def get_environment(self, environment_id): """This method allows to get specific environment by ID. Input parameters: environment_id - ID of environment session_id - ID of session for this environment (optional) Returns specific environment. """ return self.murano_client.environments.get(environment_id) def update_environment(self, environment_id, new_name): """This method allows to update specific environment by ID. Input parameters: environment_id - ID of environment new_name - New name for environment Returns new environment. """ return self.murano_client.environments.update(environment_id, new_name) def delete_environment(self, environment_id): """This method allows to delete specific environment by ID. Input parameters: environment_id - ID of environment Returns None. """ self.murano_client.environments.delete(environment_id) return self.environments.remove(environment_id) def environment_delete_check(self, environment_id, timeout=120): resp = requests.get('{0}environments/{1}'.format(self.endpoint, environment_id), headers=self.headers, verify=False) self.delete_environment(environment_id) point = time.time() while resp.status_code == 200: if time.time() - point > timeout: self.fail("Can't delete environment more than {0} seconds". format(timeout)) resp = requests.get('{0}environments/{1}'.format(self.endpoint, environment_id), headers=self.headers, verify=False) try: env = resp.json() if env["status"] == "delete failure": self.fail("Environment status: {0}".format(env["status"])) except Exception: LOG.debug("Failed to get environment status " "or environment no more exists") time.sleep(1) def create_session(self, environment_id): """This method allows to create session for environment. Input parameters: environment_id - ID of environment Returns new session. """ return self.murano_client.sessions.configure(environment_id) def get_session(self, environment_id, session_id): """This method allows to get specific session. Input parameters: environment_id - ID of environment session_id - ID of session for this environment Returns specific session. """ return self.murano_client.sessions.get(environment_id, session_id) def delete_session(self, environment_id, session_id): """This method allows to delete session for environment. Input parameters: environment_id - ID of environment session_id - ID of session for this environment Returns None. """ return self.murano_client.sessions.delete(environment_id, session_id) def deploy_session(self, environment_id, session_id): """This method allows to deploy session for environment. Input parameters: environment_id - ID of environment session_id - ID of session for this environment Returns specific session. """ endpoint = '{0}environments/{1}/sessions/{2}/deploy'.format( self.endpoint, environment_id, session_id) return requests.post(endpoint, data=None, headers=self.headers, verify=False) def create_service(self, environment_id, session_id, json_data): """This method allows to create service. Input parameters: environment_id - ID of environment session_id - ID of session for this environment json_data - JSON with service description Returns specific service. """ headers = self.headers.copy() headers.update({'x-configuration-session': session_id}) endpoint = '{0}environments/{1}/services'.format(self.endpoint, environment_id) return requests.post(endpoint, data=jsonutils.dumps(json_data), headers=headers, verify=False).json() def list_services(self, environment_id, session_id=None): """This method allows to get list of services. Input parameters: environment_id - ID of environment session_id - ID of session for this environment (optional) Returns list of services. """ return self.murano_client.services.get(environment_id, '/', session_id) def get_service(self, environment_id, session_id, service_id): """This method allows to get service by ID. Input parameters: environment_id - ID of environment session_id - ID of session for this environment service_id - ID of service in this environment Returns specific service. """ return self.murano_client.services.get(environment_id, '/{0}'.format(service_id), session_id) def delete_service(self, environment_id, session_id, service_id): """This method allows to delete specific service. Input parameters: environment_id - ID of environment session_id - ID of session for this environment service_id - ID of service in this environment Returns None. """ return self.murano_client.services.delete(environment_id, '/{0}'.format(service_id), session_id) def deploy_check(self, environment): """This method allows to wait for deployment of Murano evironments. Input parameters: environment - Murano environment Returns environment. """ environment = self.get_environment(environment.id) while environment.status != 'ready': time.sleep(5) environment = self.get_environment(environment.id) if environment.status == 'deploy failure': LOG.error( 'Environment has incorrect status' ' %s' % environment.status) self.fail( 'Environment has incorrect status' ' %s .' % environment.status) return environment def deployments_status_check(self, environment_id): """This method allows to check that deployment status is 'success'. Input parameters: environment_id - ID of environment Returns 'OK'. """ endpoint = '{0}environments/{1}/deployments'.format(self.endpoint, environment_id) deployments = requests.get(endpoint, headers=self.headers, verify=False).json()['deployments'] for deployment in deployments: # Save the information about all deployments LOG.debug("Environment state: {0}".format(deployment['state'])) r = requests.get('{0}/{1}'.format(endpoint, deployment['id']), headers=self.headers, verify=False).json() LOG.debug("Reports: {0}".format(r)) self.assertEqual('success', deployment['state']) return 'OK' def check_port_access(self, ip, port): output = '' start_time = time.time() while time.time() - start_time < 600: # Check VM port availability from controller node: output, err = self._run_ssh_cmd("nc -z {0} {1}; echo $?" .format(ip, port)) if '0' in output: break time.sleep(5) self.assertIn('0', output, '%s port is closed on instance' % port) def port_status_check(self, environment, configurations): """Function which gives opportunity to check multiple instances :param environment: Murano environment :param configurations: Array of configurations. Example: [[instance_name, *ports], [instance_name, *ports]] ... """ for configuration in configurations: inst_name = configuration[0] ports = configuration[1:] ip = self.get_ip_by_instance_name(environment, inst_name) if ip and ports: for port in ports: self.check_port_access(ip, port) else: self.fail('Instance does not have floating IP') def get_ip_by_instance_name(self, environment, inst_name): """Returns ip of instance using instance name :param environment: Murano environment :param name: String, which is substring of name of instance or name of instance :return: """ for service in environment.services: if inst_name in service['instance']['name']: return service['instance']['floatingIpAddress'] def get_list_packages(self, artifacts=False): try: if artifacts: packages_list = self.murano_art_client.packages.list() packages = [] for package in packages_list: packages.append(package) else: packages_list = self.murano_client.packages.list() packages = list(packages_list) except exceptions.ClientException: self.fail("Can not get list of packages") LOG.debug('Packages List: {0}'.format(packages)) self.assertIsInstance(packages, list) return packages def generate_fqn_list(self, artifacts=False): fqn_list = [] packages = self.get_list_packages(artifacts) for package in packages: fqn_list.append(package.to_dict()['fully_qualified_name']) LOG.debug('FQN List: {0}'.format(fqn_list)) return fqn_list def upload_package(self, package_name, body, app, artifacts=False): files = {'%s' % package_name: open(app, 'rb')} if artifacts: package = self.murano_art_client.packages.create(body, files) else: package = self.murano_client.packages.create(body, files) self.packages.append(package) return package def package_exists(self, artifacts=False, *packages): fqn_list = self.generate_fqn_list(artifacts) LOG.debug("Response for packages is {0}".format(fqn_list)) for package in packages: if package not in fqn_list: return False return True def get_package(self, package_id, artifacts=False): if artifacts: package = self.murano_art_client.packages.get(package_id) else: package = self.murano_client.packages.get(package_id) return package def get_package_by_fqdn(self, package_name, artifacts=False): package_list = self.get_list_packages(artifacts) for package in package_list: if package.to_dict()["fully_qualified_name"] == package_name: return package def delete_package(self, package_id, artifacts=False): if artifacts: self.murano_art_client.packages.delete(package_id) else: self.murano_client.packages.delete(package_id) def get_list_categories(self): resp = requests.get(self.endpoint + 'catalog/packages/categories', headers=self.headers, verify=False) self.assertEqual(200, resp.status_code) self.assertIsInstance(resp.json()['categories'], list) def check_path(self, env, path, inst_name=None): environment = env.manager.get(env.id) if inst_name: ip = self.get_ip_by_instance_name(environment, inst_name) else: ip = environment.services[0]['instance']['floatingIpAddress'] uri = 'http://{0}/{1}'.format(ip, path) cmd = "curl --connect-timeout 1 --head {0}".format(uri) stdout, stderr = self._run_ssh_cmd(cmd) if '404' in stdout: self.fail("Service path unavailable")