fuel-ostf/fuel_health/muranomanager.py

482 lines
17 KiB
Python

# 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=60):
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(5)
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")