# 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. from oslo_utils import encodeutils from six.moves import configparser import inspect import json import os import pwd import shutil import subprocess import tempfile TEST_ENV = { "OS_USERNAME": "admin", "OS_PASSWORD": "admin", "OS_TENANT_NAME": "admin", "OS_AUTH_URL": "http://fake/", } DEPLOYMENT_FILE = "/tmp/rally_functests_main_deployment.json" class RallyCmdError(Exception): def __init__(self, code, output): self.code = code self.output = encodeutils.safe_decode(output) def __str__(self): return "Code: %d Output: %s\n" % (self.code, self.output) def __unicode__(self): return "Code: %d Output: %s\n" % (self.code, self.output) class TaskConfig(object): def __init__(self, config): config_file = tempfile.NamedTemporaryFile(delete=False) config_file.write(encodeutils.safe_encode(json.dumps(config))) config_file.close() self.filename = config_file.name def __del__(self): os.unlink(self.filename) class Rally(object): """Create and represent separate rally installation. Usage: rally = Rally() rally("deployment", "create", "--name", "Some Deployment Name") output = rally("deployment list") """ def __init__(self, fake=False): # NOTE(sskripnick): we shoud change home dir to avoid races # and do not touch any user files in ~/.rally os.environ["HOME"] = pwd.getpwuid(os.getuid()).pw_dir if not os.path.exists(DEPLOYMENT_FILE): subprocess.call("rally deployment config > %s" % DEPLOYMENT_FILE, shell=True) self.tmp_dir = tempfile.mkdtemp() os.environ["HOME"] = self.tmp_dir if "RCI_KEEP_DB" not in os.environ: config_filename = os.path.join(self.tmp_dir, "conf") config = configparser.RawConfigParser() config.add_section("database") config.set("database", "connection", "sqlite:///%s/db" % self.tmp_dir) with open(config_filename, "w") as conf: config.write(conf) self.args = ["rally", "--config-file", config_filename] subprocess.call(["rally-manage", "--config-file", config_filename, "db", "recreate"]) else: self.args = ["rally"] subprocess.call(["rally-manage", "db", "recreate"]) self.reports_root = os.environ.get("REPORTS_ROOT", "rally-cli-output-files") self._created_files = [] self("deployment create --file %s --name MAIN" % DEPLOYMENT_FILE, write_report=False) def __del__(self): shutil.rmtree(self.tmp_dir) def gen_report_path(self, suffix=None, extension=None, keep_old=False): """Report file path/name modifier :param suffix: suffix that will be appended to filename. It will be appended before extension :param extension: file extension. :param keep_old: if True, previous reports will not be deleted, but rename to 'nameSuffix.old*.extension' :return: complete report name to write report """ caller_frame = inspect.currentframe().f_back if caller_frame.f_code.co_name == "__call__": caller_frame = caller_frame.f_back method_name = caller_frame.f_code.co_name test_object = caller_frame.f_locals["self"] class_name = test_object.__class__.__name__ if not os.path.exists("%s/%s" % (self.reports_root, class_name)): os.makedirs("%s/%s" % (self.reports_root, class_name)) suff = suffix or "" ext = extension or "txt" path = "%s/%s/%s%s.%s" % (self.reports_root, class_name, method_name, suff, ext) if path not in self._created_files: if os.path.exists(path): if not keep_old: os.remove(path) else: path_list = path.split(".") old_suff = "old" path_list.insert(-1, old_suff) new_path = ".".join(path_list) count = 0 while os.path.exists(new_path): count += 1 path_list[-2] = "old%d" % count new_path = ".".join(path_list) os.rename(path, new_path) self._created_files.append(path) return path def __call__(self, cmd, getjson=False, report_path=None, raw=False, suffix=None, extension=None, keep_old=False, write_report=True): """Call rally in the shell :param cmd: rally command :param getjson: in cases, when rally prints JSON, you can catch output deserialized :param report_path: if present, rally command and its output will be written to file with passed file name :param raw: don't write command itself to report file. Only output will be written """ if not isinstance(cmd, list): cmd = cmd.split(" ") try: output = encodeutils.safe_decode(subprocess.check_output( self.args + cmd, stderr=subprocess.STDOUT)) if write_report: if not report_path: report_path = self.gen_report_path( suffix=suffix, extension=extension, keep_old=keep_old) with open(report_path, "a") as rep: if not raw: rep.write("\n%s:\n" % " ".join(self.args + cmd)) rep.write("%s\n" % output) if getjson: return json.loads(output) return output except subprocess.CalledProcessError as e: raise RallyCmdError(e.returncode, e.output)