tempest-2013.2.a1291.g23a1b4f/0000775000175000017500000000000012161375700015446 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/.gitignore0000664000175000017500000000034012161375672017443 0ustar chuckchuck00000000000000AUTHORS ChangeLog *.pyc etc/tempest.conf etc/logging.conf include/swift_objects/swift_small include/swift_objects/swift_medium include/swift_objects/swift_large *.log *.swp *.swo *.egg* .tox .venv dist build .testrepository tempest-2013.2.a1291.g23a1b4f/bin/0000775000175000017500000000000012161375700016216 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/bin/tempest0000775000175000017500000000215212161375672017635 0ustar chuckchuck00000000000000#!/usr/bin/env bash function usage { echo "Usage: $0 [OPTION]..." echo "Run Tempest test suite" echo "" echo " -s, --smoke Only run smoke tests" echo " -w, --whitebox Only run whitebox tests" echo " -h, --help Print this usage message" echo " -d. --debug Debug this script -- set -o xtrace" exit } function process_option { case "$1" in -h|--help) usage;; -d|--debug) set -o xtrace;; -s|--smoke) noseargs="$noseargs --attr=type=smoke";; -w|--whitebox) noseargs="$noseargs --attr=type=whitebox";; *) noseargs="$noseargs $1" esac } noseargs="" export NOSE_WITH_OPENSTACK=1 export NOSE_OPENSTACK_COLOR=1 export NOSE_OPENSTACK_RED=15.00 export NOSE_OPENSTACK_YELLOW=3.00 export NOSE_OPENSTACK_SHOW_ELAPSED=1 export NOSE_OPENSTACK_STDOUT=1 for arg in "$@"; do process_option $arg done # only add tempest default if we don't specify a test if [[ "x$noseargs" =~ "tempest" ]]; then noseargs="$noseargs" else noseargs="$noseargs tempest" fi function run_tests { $NOSETESTS } NOSETESTS="nosetests $noseargs" run_tests || exit tempest-2013.2.a1291.g23a1b4f/include/0000775000175000017500000000000012161375700017071 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/include/sample_vm/0000775000175000017500000000000012161375700021054 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/include/sample_vm/README.txt0000664000175000017500000000056012161375672022563 0ustar chuckchuck00000000000000You will need to download an image into this directory.. Will also need to update the tests to reference this new image. You could use e.g. the Ubuntu Natty cloud images (this matches the sample configuration): $ wget http://cloud-images.ubuntu.com/releases/natty/release/ubuntu-11.04-server-cloudimg-amd64.tar.gz $ tar xvzf ubuntu-11.04-server-cloudimg-amd64.tar.gz tempest-2013.2.a1291.g23a1b4f/include/swift_objects/0000775000175000017500000000000012161375700021736 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/include/swift_objects/README.txt0000664000175000017500000000044712161375672023451 0ustar chuckchuck00000000000000## For the swift tests you will need three objects to upload for the test ## examples below are a 512K object, a 500M object, and 1G object dd if=/dev/zero of=swift_small bs=512 count=1024 dd if=/dev/zero of=swift_medium bs=512 count=1024000 dd if=/dev/zero of=swift_large bs=1024 count=1024000 tempest-2013.2.a1291.g23a1b4f/tools/0000775000175000017500000000000012161375700016606 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tools/find_stack_traces.py0000775000175000017500000001007712161375672022646 0ustar chuckchuck00000000000000#!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 IBM Corp. # 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 gzip import re import StringIO import sys import urllib2 import pprint pp = pprint.PrettyPrinter() NOVA_TIMESTAMP = r"\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d" NOVA_REGEX = r"(?P%s) (?P\d+ )?(?P(ERROR|TRACE)) " \ "(?P[\w\.]+) (?P.*)" % (NOVA_TIMESTAMP) class StackTrace(object): timestamp = None pid = None level = "" module = "" msg = "" def __init__(self, timestamp=None, pid=None, level="", module="", msg=""): self.timestamp = timestamp self.pid = pid self.level = level self.module = module self.msg = msg def append(self, msg): self.msg = self.msg + msg def is_same(self, data): return (data['timestamp'] == self.timestamp and data['level'] == self.level) def not_none(self): return self.timestamp is not None def __str__(self): buff = "<%s %s %s>\n" % (self.timestamp, self.level, self.module) for line in self.msg.splitlines(): buff = buff + line + "\n" return buff def hunt_for_stacktrace(url): """Return TRACE or ERROR lines out of logs.""" page = urllib2.urlopen(url) buf = StringIO.StringIO(page.read()) f = gzip.GzipFile(fileobj=buf) content = f.read() traces = [] trace = StackTrace() for line in content.splitlines(): m = re.match(NOVA_REGEX, line) if m: data = m.groupdict() if trace.not_none() and trace.is_same(data): trace.append(data['msg'] + "\n") else: trace = StackTrace( timestamp=data.get('timestamp'), pid=data.get('pid'), level=data.get('level'), module=data.get('module'), msg=data.get('msg')) else: if trace.not_none(): traces.append(trace) trace = StackTrace() # once more at the end to pick up any stragglers if trace.not_none(): traces.append(trace) return traces def log_url(url, log): return "%s/%s" % (url, log) def collect_logs(url): page = urllib2.urlopen(url) content = page.read() logs = re.findall('(screen-[\w-]+\.txt\.gz)', content) return logs def usage(): print """ Usage: find_stack_traces.py Hunts for stack traces in a devstack run. Must provide it a base log url from a tempest devstack run. Should start with http and end with /logs/. Returns a report listing stack traces out of the various files where they are found. """ sys.exit(0) def print_stats(items, fname, verbose=False): errors = len(filter(lambda x: x.level == "ERROR", items)) traces = len(filter(lambda x: x.level == "TRACE", items)) print "%d ERRORS found in %s" % (errors, fname) print "%d TRACES found in %s" % (traces, fname) if verbose: for item in items: print item print "\n\n" def main(): if len(sys.argv) == 2: url = sys.argv[1] loglist = collect_logs(url) # probably wrong base url if not loglist: usage() for log in loglist: logurl = log_url(url, log) traces = hunt_for_stacktrace(logurl) if traces: print_stats(traces, log, verbose=True) else: usage() if __name__ == '__main__': main() tempest-2013.2.a1291.g23a1b4f/tools/install_venv_common.py0000664000175000017500000001710212161375672023245 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 OpenStack, LLC # Copyright 2013 IBM Corp. # # 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. """Provides methods needed by installation script for OpenStack development virtual environments. Synced in from openstack-common """ import argparse import os import subprocess import sys class InstallVenv(object): def __init__(self, root, venv, pip_requires, test_requires, py_version, project): self.root = root self.venv = venv self.pip_requires = pip_requires self.test_requires = test_requires self.py_version = py_version self.project = project def die(self, message, *args): print >> sys.stderr, message % args sys.exit(1) def check_python_version(self): if sys.version_info < (2, 6): self.die("Need Python Version >= 2.6") def run_command_with_code(self, cmd, redirect_output=True, check_exit_code=True): """Runs a command in an out-of-process shell. Returns the output of that command. Working directory is self.root. """ if redirect_output: stdout = subprocess.PIPE else: stdout = None proc = subprocess.Popen(cmd, cwd=self.root, stdout=stdout) output = proc.communicate()[0] if check_exit_code and proc.returncode != 0: self.die('Command "%s" failed.\n%s', ' '.join(cmd), output) return (output, proc.returncode) def run_command(self, cmd, redirect_output=True, check_exit_code=True): return self.run_command_with_code(cmd, redirect_output, check_exit_code)[0] def get_distro(self): if (os.path.exists('/etc/fedora-release') or os.path.exists('/etc/redhat-release')): return Fedora(self.root, self.venv, self.pip_requires, self.test_requires, self.py_version, self.project) else: return Distro(self.root, self.venv, self.pip_requires, self.test_requires, self.py_version, self.project) def check_dependencies(self): self.get_distro().install_virtualenv() def create_virtualenv(self, no_site_packages=True): """Creates the virtual environment and installs PIP. Creates the virtual environment and installs PIP only into the virtual environment. """ if not os.path.isdir(self.venv): print 'Creating venv...', if no_site_packages: self.run_command(['virtualenv', '-q', '--no-site-packages', self.venv]) else: self.run_command(['virtualenv', '-q', self.venv]) print 'done.' print 'Installing pip in venv...', if not self.run_command(['tools/with_venv.sh', 'easy_install', 'pip>1.0']).strip(): self.die("Failed to install pip.") print 'done.' else: print "venv already exists..." pass def pip_install(self, *args): self.run_command(['tools/with_venv.sh', 'pip', 'install', '--upgrade'] + list(args), redirect_output=False) def install_dependencies(self): print 'Installing dependencies with pip (this can take a while)...' # First things first, make sure our venv has the latest pip and # distribute. # NOTE: we keep pip at version 1.1 since the most recent version causes # the .venv creation to fail. See: # https://bugs.launchpad.net/nova/+bug/1047120 self.pip_install('pip==1.1') self.pip_install('distribute') # Install greenlet by hand - just listing it in the requires file does # not # get it installed in the right order self.pip_install('greenlet') self.pip_install('-r', self.pip_requires) self.pip_install('-r', self.test_requires) def post_process(self): self.get_distro().post_process() def parse_args(self, argv): """Parses command-line arguments.""" parser = argparse.ArgumentParser() parser.add_argument('-n', '--no-site-packages', action='store_true', help="Do not inherit packages from global Python " "install") return parser.parse_args(argv[1:]) class Distro(InstallVenv): def check_cmd(self, cmd): return bool(self.run_command(['which', cmd], check_exit_code=False).strip()) def install_virtualenv(self): if self.check_cmd('virtualenv'): return if self.check_cmd('easy_install'): print 'Installing virtualenv via easy_install...', if self.run_command(['easy_install', 'virtualenv']): print 'Succeeded' return else: print 'Failed' self.die('ERROR: virtualenv not found.\n\n%s development' ' requires virtualenv, please install it using your' ' favorite package management tool' % self.project) def post_process(self): """Any distribution-specific post-processing gets done here. In particular, this is useful for applying patches to code inside the venv. """ pass class Fedora(Distro): """This covers all Fedora-based distributions. Includes: Fedora, RHEL, CentOS, Scientific Linux """ def check_pkg(self, pkg): return self.run_command_with_code(['rpm', '-q', pkg], check_exit_code=False)[1] == 0 def yum_install(self, pkg, **kwargs): print "Attempting to install '%s' via yum" % pkg self.run_command(['sudo', 'yum', 'install', '-y', pkg], **kwargs) def apply_patch(self, originalfile, patchfile): self.run_command(['patch', originalfile, patchfile]) def install_virtualenv(self): if self.check_cmd('virtualenv'): return if not self.check_pkg('python-virtualenv'): self.yum_install('python-virtualenv', check_exit_code=False) super(Fedora, self).install_virtualenv() def post_process(self): """Workaround for a bug in eventlet. This currently affects RHEL6.1, but the fix can safely be applied to all RHEL and Fedora distributions. This can be removed when the fix is applied upstream. Nova: https://bugs.launchpad.net/nova/+bug/884915 Upstream: https://bitbucket.org/which_linden/eventlet/issue/89 """ # Install "patch" program if it's not there if not self.check_pkg('patch'): self.yum_install('patch') # Apply the eventlet patch self.apply_patch(os.path.join(self.venv, 'lib', self.py_version, 'site-packages', 'eventlet/green/subprocess.py'), 'contrib/redhat-eventlet.patch') tempest-2013.2.a1291.g23a1b4f/tools/tempest_coverage.py0000775000175000017500000001443612161375672022537 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 IBM Corp. # # 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 json import os import shutil import sys from oslo.config import cfg from tempest.common.rest_client import RestClient from tempest import config CONF = config.TempestConfig() class CoverageClientJSON(RestClient): def __init__(self, config, username, password, auth_url, tenant_name=None): super(CoverageClientJSON, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type def start_coverage(self): post_body = { 'start': {}, } post_body = json.dumps(post_body) return self.post('os-coverage/action', post_body, self.headers) def start_coverage_combine(self): post_body = { 'start': { 'combine': True, }, } post_body = json.dumps(post_body) return self.post('os-coverage/action', post_body, self.headers) def stop_coverage(self): post_body = { 'stop': {}, } post_body = json.dumps(post_body) resp, body = self.post('os-coverage/action', post_body, self.headers) body = json.loads(body) return resp, body def report_coverage_xml(self, file=None): post_body = { 'report': { 'file': 'coverage.report', 'xml': True, }, } if file: post_body['report']['file'] = file post_body = json.dumps(post_body) resp, body = self.post('os-coverage/action', post_body, self.headers) body = json.loads(body) return resp, body def report_coverage(self, file=None): post_body = { 'report': { 'file': 'coverage.report', }, } if file: post_body['report']['file'] = file post_body = json.dumps(post_body) resp, body = self.post('os-coverage/action', post_body, self.headers) body = json.loads(body) return resp, body def report_coverage_html(self, file=None): post_body = { 'report': { 'file': 'coverage.report', 'html': True, }, } if file: post_body['report']['file'] = file post_body = json.dumps(post_body) resp, body = self.post('os-coverage/action', post_body, self.headers) body = json.loads(body) return resp, body def parse_opts(argv): cli_opts = [ cfg.StrOpt('command', short='c', default='', help="This required argument is used to specify the " "coverage command to run. Only 'start', " "'stop', or 'report' are valid fields."), cfg.StrOpt('filename', default='tempest-coverage', help="Specify a filename to be used for generated report " "files"), cfg.BoolOpt('xml', default=False, help='Generate XML reports instead of text'), cfg.BoolOpt('html', default=False, help='Generate HTML reports instead of text'), cfg.BoolOpt('combine', default=False, help='Generate a single report for all services'), cfg.StrOpt('output', short='o', default=None, help='Optional directory to copy generated coverage data or' ' reports into. This directory must not already exist ' 'it will be created') ] CLI = cfg.ConfigOpts() CLI.register_cli_opts(cli_opts) CLI(argv[1:]) return CLI def main(argv): CLI = parse_opts(argv) client_args = (CONF, CONF.identity.admin_username, CONF.identity.admin_password, CONF.identity.uri, CONF.identity.admin_tenant_name) coverage_client = CoverageClientJSON(*client_args) if CLI.command == 'start': if CLI.combine: coverage_client.start_coverage_combine() else: coverage_client.start_coverage() elif CLI.command == 'stop': resp, body = coverage_client.stop_coverage() if not resp['status'] == '200': print 'coverage stop failed with: %s:' % (resp['status'] + ': ' + body) exit(int(resp['status'])) path = body['path'] if CLI.output: shutil.copytree(path, CLI.output) else: print "Data files located at: %s" % path elif CLI.command == 'report': if CLI.xml: resp, body = coverage_client.report_coverage_xml(file=CLI.filename) elif CLI.html: resp, body = coverage_client.report_coverage_html( file=CLI.filename) else: resp, body = coverage_client.report_coverage(file=CLI.filename) if not resp['status'] == '200': print 'coverage report failed with: %s:' % (resp['status'] + ': ' + body) exit(int(resp['status'])) path = body['path'] if CLI.output: if CLI.html: shutil.copytree(path, CLI.output) else: path = os.path.dirname(path) shutil.copytree(path, CLI.output) else: if not CLI.html: path = os.path.dirname(path) print 'Report files located at: %s' % path else: print 'Invalid command' exit(1) if __name__ == "__main__": main(sys.argv) tempest-2013.2.a1291.g23a1b4f/tools/install_venv.py0000664000175000017500000000534012161375672021676 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. # flake8: noqa # Copyright 2010 OpenStack, LLC # Copyright 2013 IBM Corp. # # 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. """Installation script for Tempest's development virtualenv.""" import os import sys import install_venv_common as install_venv class CentOS(install_venv.Fedora): """This covers CentOS.""" def post_process(self): if not self.check_pkg('openssl-devel'): self.yum.install('openssl-devel', check_exit_code=False) def print_help(): """This prints Help.""" help = """ Tempest development environment setup is complete. Tempest development uses virtualenv to track and manage Python dependencies while in development and testing. To activate the Tempest virtualenv for the extent of your current shell session you can run: $ source .venv/bin/activate Or, if you prefer, you can run commands in the virtualenv on a case by case basis by running: $ tools/with_venv.sh Also, make test will automatically use the virtualenv. """ print help def main(argv): root = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) venv = os.path.join(root, '.venv') pip_requires = os.path.join(root, 'requirements.txt') test_requires = os.path.join(root, 'test-requirements.txt') py_version = "python%s.%s" % (sys.version_info[0], sys.version_info[1]) project = 'Tempest' install = install_venv.InstallVenv(root, venv, pip_requires, test_requires, py_version, project) if os.path.exists('/etc/redhat-release'): with open('/etc/redhat-release') as rh_release: if 'CentOS' in rh_release.read(): install_venv.Fedora = CentOS options = install.parse_args(argv) install.check_python_version() install.check_dependencies() install.create_virtualenv(no_site_packages=options.no_site_packages) install.install_dependencies() install.post_process() print_help() if __name__ == '__main__': main(sys.argv) tempest-2013.2.a1291.g23a1b4f/tools/with_venv.sh0000775000175000017500000000012612161375672021165 0ustar chuckchuck00000000000000#!/bin/bash TOOLS=`dirname $0` VENV=$TOOLS/../.venv source $VENV/bin/activate && "$@" tempest-2013.2.a1291.g23a1b4f/tools/skip_tracker.py0000775000175000017500000001031512161375672021654 0ustar chuckchuck00000000000000#!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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. """ Track test skips via launchpadlib API and raise alerts if a bug is fixed but a skip is still in the Tempest test code """ import logging import os import re from launchpadlib import launchpad BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) TESTDIR = os.path.join(BASEDIR, 'tempest') LPCACHEDIR = os.path.expanduser('~/.launchpadlib/cache') def info(msg, *args, **kwargs): logging.info(msg, *args, **kwargs) def debug(msg, *args, **kwargs): logging.debug(msg, *args, **kwargs) def find_skips(start=TESTDIR): """ Returns a list of tuples (method, bug) that represent test methods that have been decorated to skip because of a particular bug. """ results = [] debug("Searching in %s", start) for root, _dirs, files in os.walk(start): for name in files: if name.startswith('test_') and name.endswith('py'): path = os.path.join(root, name) debug("Searching in %s", path) results += find_skips_in_file(path) return results def find_skips_in_file(path): """ Return the skip tuples in a test file """ BUG_RE = re.compile(r'.*skip\(.*bug:*\s*\#*(\d+)', re.IGNORECASE) DEF_RE = re.compile(r'.*def (\w+)\(') bug_found = False results = [] lines = open(path, 'rb').readlines() for x, line in enumerate(lines): if not bug_found: res = BUG_RE.match(line) if res: bug_no = int(res.group(1)) debug("Found bug skip %s on line %d", bug_no, x + 1) bug_found = True else: res = DEF_RE.match(line) if res: method = res.group(1) debug("Found test method %s skips for bug %d", method, bug_no) results.append((method, bug_no)) bug_found = False return results if __name__ == '__main__': logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.INFO) results = find_skips() unique_bugs = sorted(set([bug for (method, bug) in results])) unskips = [] duplicates = [] info("Total bug skips found: %d", len(results)) info("Total unique bugs causing skips: %d", len(unique_bugs)) lp = launchpad.Launchpad.login_anonymously('grabbing bugs', 'production', LPCACHEDIR) for bug_no in unique_bugs: bug = lp.bugs[bug_no] duplicate = bug.duplicate_of_link if duplicate is not None: dup_id = duplicate.split('/')[-1] duplicates.append((bug_no, dup_id)) for task in bug.bug_tasks: info("Bug #%7s (%12s - %12s)", bug_no, task.importance, task.status) if task.status in ('Fix Released', 'Fix Committed'): unskips.append(bug_no) for bug_id, dup_id in duplicates: if bug_id not in unskips: dup_bug = lp.bugs[dup_id] for task in dup_bug.bug_tasks: info("Bug #%7s is a duplicate of Bug#%7s (%12s - %12s)", bug_id, dup_id, task.importance, task.status) if task.status in ('Fix Released', 'Fix Committed'): unskips.append(bug_id) unskips = sorted(set(unskips)) if unskips: print "The following bugs have been fixed and the corresponding skips" print "should be removed from the test cases:" print for bug in unskips: print " %7s" % bug tempest-2013.2.a1291.g23a1b4f/doc/0000775000175000017500000000000012161375700016213 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/doc/source/0000775000175000017500000000000012161375700017513 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/doc/source/field_guide/0000775000175000017500000000000012161375700021753 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/doc/source/field_guide/thirdparty.rst0000777000175000017500000000000012161375672033105 2../../../tempest/thirdparty/README.rstustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/doc/source/field_guide/whitebox.rst0000777000175000017500000000000012161375672032203 2../../../tempest/whitebox/README.rstustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/doc/source/field_guide/api.rst0000777000175000017500000000000012161375672030043 2../../../tempest/api/README.rstustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/doc/source/field_guide/stress.rst0000777000175000017500000000000012161375672031367 2../../../tempest/stress/README.rstustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/doc/source/field_guide/scenario.rst0000777000175000017500000000000012161375672032127 2../../../tempest/scenario/README.rstustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/doc/source/field_guide/cli.rst0000777000175000017500000000000012161375672030037 2../../../tempest/cli/README.rstustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/doc/source/field_guide/index.rst0000777000175000017500000000000012161375672027630 2../../../tempest/README.rstustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/doc/source/HACKING.rst0000777000175000017500000000000012161375672023540 2../../HACKING.rstustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/doc/source/overview.rst0000777000175000017500000000000012161375672024233 2../../README.rstustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/doc/source/conf.py0000664000175000017500000002147012161375672021026 0ustar chuckchuck00000000000000# -*- coding: utf-8 -*- # # Tempest documentation build configuration file, created by # sphinx-quickstart on Tue May 21 17:43:32 2013. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.viewcode'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Tempest' copyright = u'2013, Sean Dague' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = 'havana' # The full version, including alpha/beta/rc tags. release = 'havana' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'nature' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'Tempestdoc' # -- Options for LaTeX output -------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'Tempest.tex', u'Tempest Documentation', u'Sean Dague', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'tempest', u'Tempest Documentation', [u'Sean Dague'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'Tempest', u'Tempest Documentation', u'Sean Dague', 'Tempest', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # -- Options for Epub output --------------------------------------------------- # Bibliographic Dublin Core info. epub_title = u'Tempest' epub_author = u'Sean Dague' epub_publisher = u'Sean Dague' epub_copyright = u'2013, Sean Dague' # The language of the text. It defaults to the language option # or en if the language is not set. #epub_language = '' # The scheme of the identifier. Typical schemes are ISBN or URL. #epub_scheme = '' # The unique identifier of the text. This can be a ISBN number # or the project homepage. #epub_identifier = '' # A unique identification for the text. #epub_uid = '' # A tuple containing the cover image and cover page html template filenames. #epub_cover = () # HTML files that should be inserted before the pages created by sphinx. # The format is a list of tuples containing the path and title. #epub_pre_files = [] # HTML files shat should be inserted after the pages created by sphinx. # The format is a list of tuples containing the path and title. #epub_post_files = [] # A list of files that should not be packed into the epub file. #epub_exclude_files = [] # The depth of the table of contents in toc.ncx. #epub_tocdepth = 3 # Allow duplicate toc entries. #epub_tocdup = True tempest-2013.2.a1291.g23a1b4f/doc/source/index.rst0000664000175000017500000000154212161375672021366 0ustar chuckchuck00000000000000.. Tempest documentation master file, created by sphinx-quickstart on Tue May 21 17:43:32 2013. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. ======================= Tempest Testing Project ======================= Contents: .. toctree:: :maxdepth: 2 overview HACKING ------------ Field Guides ------------ Tempest contains tests of many different types, the field guides attempt to explain these in a way that makes it easy to understand where your test contributions should go. .. toctree:: :maxdepth: 1 field_guide/index field_guide/api field_guide/cli field_guide/scenario field_guide/stress field_guide/thirdparty field_guide/whitebox ================== Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` tempest-2013.2.a1291.g23a1b4f/run_tests.sh0000775000175000017500000001200312161375672020037 0ustar chuckchuck00000000000000#!/usr/bin/env bash function usage { echo "Usage: $0 [OPTION]..." echo "Run Tempest test suite" echo "" echo " -V, --virtual-env Always use virtualenv. Install automatically if not present" echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment" echo " -n, --no-site-packages Isolate the virtualenv from the global Python environment" echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added." echo " -u, --update Update the virtual environment with any newer package versions" echo " -s, --smoke Only run smoke tests" echo " -w, --whitebox Only run whitebox tests" echo " -c, --nova-coverage Enable Nova coverage collection" echo " -C, --config Config file location" echo " -p, --pep8 Just run pep8" echo " -h, --help Print this usage message" echo " -d, --debug Debug this script -- set -o xtrace" echo " -S, --stdout Don't capture stdout" echo " -l, --logging Enable logging" echo " -L, --logging-config Logging config file location. Default is etc/logging.conf" echo " -- [NOSEOPTIONS] After the first '--' you can pass arbitrary arguments to nosetests " } noseargs="" just_pep8=0 venv=.venv with_venv=tools/with_venv.sh always_venv=0 never_venv=0 no_site_packages=0 force=0 wrapper="" nova_coverage=0 config_file="" update=0 logging=0 logging_config=etc/logging.conf if ! options=$(getopt -o VNnfuswcphdSC:lL: -l virtual-env,no-virtual-env,no-site-packages,force,update,smoke,whitebox,nova-coverage,pep8,help,debug,stdout,config:,logging,logging-config: -- "$@") then # parse error usage exit 1 fi eval set -- $options first_uu=yes while [ $# -gt 0 ]; do case "$1" in -h|--help) usage; exit;; -V|--virtual-env) always_venv=1; never_venv=0;; -N|--no-virtual-env) always_venv=0; never_venv=1;; -n|--no-site-packages) no_site_packages=1;; -f|--force) force=1;; -u|--update) update=1;; -d|--debug) set -o xtrace;; -c|--nova-coverage) let nova_coverage=1;; -C|--config) config_file=$2; shift;; -p|--pep8) let just_pep8=1;; -s|--smoke) noseargs="$noseargs --attr=type=smoke";; -w|--whitebox) noseargs="$noseargs --attr=type=whitebox";; -S|--stdout) noseargs="$noseargs -s";; -l|--logging) logging=1;; -L|--logging-config) logging_config=$2; shift;; --) [ "yes" == "$first_uu" ] || noseargs="$noseargs $1"; first_uu=no ;; *) noseargs="$noseargs $1" esac shift done if [ -n "$config_file" ]; then config_file=`readlink -f "$config_file"` export TEMPEST_CONFIG_DIR=`dirname "$config_file"` export TEMPEST_CONFIG=`basename "$config_file"` fi if [ $logging -eq 1 ]; then if [ ! -f "$logging_config" ]; then echo "No such logging config file: $logging_config" exit 1 fi logging_config=`readlink -f "$logging_config"` export TEMPEST_LOG_CONFIG_DIR=`dirname "$logging_config"` export TEMPEST_LOG_CONFIG=`basename "$logging_config"` fi cd `dirname "$0"` export NOSE_WITH_OPENSTACK=1 export NOSE_OPENSTACK_COLOR=1 export NOSE_OPENSTACK_RED=15.00 export NOSE_OPENSTACK_YELLOW=3.00 export NOSE_OPENSTACK_SHOW_ELAPSED=1 export NOSE_OPENSTACK_STDOUT=1 if [ $no_site_packages -eq 1 ]; then installvenvopts="--no-site-packages" fi # only add tempest default if we don't specify a test if [[ "x$noseargs" =~ "tempest" ]]; then noseargs="$noseargs" else noseargs="$noseargs tempest" fi function run_tests { ${wrapper} $NOSETESTS } function run_pep8 { echo "Running pep8 ..." ${wrapper} flake8 } function run_coverage_start { echo "Starting nova-coverage" ${wrapper} python tools/tempest_coverage.py -c start } function run_coverage_report { echo "Generating nova-coverage report" ${wrapper} python tools/tempest_coverage.py -c report } NOSETESTS="nosetests $noseargs" if [ $never_venv -eq 0 ] then # Remove the virtual environment if --force used if [ $force -eq 1 ]; then echo "Cleaning virtualenv..." rm -rf ${venv} fi if [ $update -eq 1 ]; then echo "Updating virtualenv..." python tools/install_venv.py $installvenvopts fi if [ -e ${venv} ]; then wrapper="${with_venv}" else if [ $always_venv -eq 1 ]; then # Automatically install the virtualenv python tools/install_venv.py $installvenvopts wrapper="${with_venv}" else echo -e "No virtual environment found...create one? (Y/n) \c" read use_ve if [ "x$use_ve" = "xY" -o "x$use_ve" = "x" -o "x$use_ve" = "xy" ]; then # Install the virtualenv and run the test suite in it python tools/install_venv.py $installvenvopts wrapper=${with_venv} fi fi fi fi if [ $just_pep8 -eq 1 ]; then run_pep8 exit fi if [ $nova_coverage -eq 1 ]; then run_coverage_start fi run_tests retval=$? if [ $nova_coverage -eq 1 ]; then run_coverage_report fi if [ -z "$noseargs" ]; then run_pep8 fi exit $retval tempest-2013.2.a1291.g23a1b4f/PKG-INFO0000664000175000017500000000742012161375700016546 0ustar chuckchuck00000000000000Metadata-Version: 1.1 Name: tempest Version: 2013.2.a1291.g23a1b4f Summary: OpenStack Integration Testing Home-page: http://www.openstack.org/ Author: OpenStack QA Author-email: openstack-qa@lists.openstack.org License: UNKNOWN Description: :: Tempest - The OpenStack Integration Test Suite ============================================== This is a set of integration tests to be run against a live OpenStack cluster. Tempest has batteries of tests for OpenStack API validation, Scenarios, and other specific tests useful in validating an OpenStack deployment. Quickstart ---------- To run Tempest, you first need to create a configuration file that will tell Tempest where to find the various OpenStack services and other testing behavior switches. The easiest way to create a configuration file is to copy the sample one in the ``etc/`` directory :: $> cd $TEMPEST_ROOT_DIR $> cp etc/tempest.conf.sample etc/tempest.conf After that, open up the ``etc/tempest.conf`` file and edit the configuration variables to match valid data in your environment. This includes your Keystone endpoint, a valid user and credentials, and reference data to be used in testing. .. note:: If you have a running devstack environment, tempest will be automatically configured and placed in ``/opt/stack/tempest``. It will have a configuration file already set up to work with your devstack installation. Tempest is not tied to any single test runner, but Nose been the most commonly used tool. After setting up your configuration file, you can execute the set of Tempest tests by using ``nosetests`` :: $> nosetests tempest To run one single test :: $> nosetests -sv tempest.tests.compute.servers.test_server_actions.py: ServerActionsTestJSON.test_rebuild_nonexistent_server Configuration ------------- Detailed configuration of tempest is beyond the scope of this document. The etc/tempest.conf.sample attempts to be a self documenting version of the configuration. The most important pieces that are needed are the user ids, openstack endpoints, and basic flavors and images needed to run tests. Common Issues ------------- Tempest was originally designed to primarily run against a full OpenStack deployment. Due to that focus, some issues may occur when running Tempest against devstack. Running Tempest, especially in parallel, against a devstack instance may cause requests to be rate limited, which will cause unexpected failures. Given the number of requests Tempest can make against a cluster, rate limiting should be disabled for all test accounts. Additionally, devstack only provides a single image which Nova can use. For the moment, the best solution is to provide the same image uuid for both image_ref and image_ref_alt. Tempest will skip tests as needed if it detects that both images are the same. Platform: UNKNOWN Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 tempest-2013.2.a1291.g23a1b4f/.mailmap0000664000175000017500000000110712161375672017076 0ustar chuckchuck00000000000000Ravikumar Venkatesan ravikumar-venkatesan Ravikumar Venkatesan ravikumar venkatesan Rohit Karajgi Rohit Karajgi Jay Pipes Jay Pipes Daryl Walleck dwalleck tempest-2013.2.a1291.g23a1b4f/HACKING.rst0000664000175000017500000001525612161375672017265 0ustar chuckchuck00000000000000Tempest Coding Guide ==================== Test Data/Configuration ----------------------- - Assume nothing about existing test data - Tests should be self contained (provide their own data) - Clean up test data at the completion of each test - Use configuration files for values that will vary by environment General ------- - Put two newlines between top-level code (funcs, classes, etc) - Put one newline between methods in classes and anywhere else - Long lines should be wrapped in parentheses in preference to using a backslash for line continuation. - Do not write "except:", use "except Exception:" at the very least - Include your name with TODOs as in "#TODO(termie)" - Do not name anything the same name as a built-in or reserved word Example:: def list(): return [1, 2, 3] mylist = list() # BAD, shadows `list` built-in class Foo(object): def list(self): return [1, 2, 3] mylist = Foo().list() # OKAY, does not shadow built-in Imports ------- - Do not import objects, only modules (*) - Do not import more than one module per line (*) - Do not make relative imports - Order your imports by the full module path - Organize your imports according to the following template Example:: # vim: tabstop=4 shiftwidth=4 softtabstop=4 {{stdlib imports in human alphabetical order}} \n {{third-party lib imports in human alphabetical order}} \n {{tempest imports in human alphabetical order}} \n \n {{begin your code}} Human Alphabetical Order Examples --------------------------------- Example:: import httplib import logging import random import StringIO import testtools import time import eventlet import webob.exc import tempest.config from tempest.services.compute.json.limits_client import LimitsClientJSON from tempest.services.compute.xml.limits_client import LimitsClientXML from tempest.services.volume.volumes_client import VolumesClientJSON import tempest.test Docstrings ---------- Example:: """A one line docstring looks like this and ends in a period.""" """A multi line docstring has a one-line summary, less than 80 characters. Then a new paragraph after a newline that explains in more detail any general information about the function, class or method. Example usages are also great to have here if it is a complex class for function. When writing the docstring for a class, an extra line should be placed after the closing quotations. For more in-depth explanations for these decisions see http://www.python.org/dev/peps/pep-0257/ If you are going to describe parameters and return values, use Sphinx, the appropriate syntax is as follows. :param foo: the foo parameter :param bar: the bar parameter :returns: return_type -- description of the return value :returns: description of the return value :raises: AttributeError, KeyError """ Dictionaries/Lists ------------------ If a dictionary (dict) or list object is longer than 80 characters, its items should be split with newlines. Embedded iterables should have their items indented. Additionally, the last item in the dictionary should have a trailing comma. This increases readability and simplifies future diffs. Example:: my_dictionary = { "image": { "name": "Just a Snapshot", "size": 2749573, "properties": { "user_id": 12, "arch": "x86_64", }, "things": [ "thing_one", "thing_two", ], "status": "ACTIVE", }, } Calling Methods --------------- Calls to methods 80 characters or longer should format each argument with newlines. This is not a requirement, but a guideline:: unnecessarily_long_function_name('string one', 'string two', kwarg1=constants.ACTIVE, kwarg2=['a', 'b', 'c']) Rather than constructing parameters inline, it is better to break things up:: list_of_strings = [ 'what_a_long_string', 'not as long', ] dict_of_numbers = { 'one': 1, 'two': 2, 'twenty four': 24, } object_one.call_a_method('string three', 'string four', kwarg1=list_of_strings, kwarg2=dict_of_numbers) Test Skips ---------- If a test is broken because of a bug it is appropriate to skip the test until bug has been fixed. However, the skip message should be formatted so that Tempest's skip tracking tool can watch the bug status. The skip message should contain the string 'Bug' immediately followed by a space. Then the bug number should be included in the message '#' in front of the number. Example:: @testtools.skip("Skipped until the Bug #980688 is resolved") openstack-common ---------------- A number of modules from openstack-common are imported into the project. These modules are "incubating" in openstack-common and are kept in sync with the help of openstack-common's update.py script. See: http://wiki.openstack.org/CommonLibrary#Incubation The copy of the code should never be directly modified here. Please always update openstack-common first and then run the script to copy the changes across. OpenStack Trademark ------------------- OpenStack is a registered trademark of the OpenStack Foundation, and uses the following capitalization: OpenStack Commit Messages --------------- Using a common format for commit messages will help keep our git history readable. Follow these guidelines: First, provide a brief summary (it is recommended to keep the commit title under 50 chars). The first line of the commit message should provide an accurate description of the change, not just a reference to a bug or blueprint. It must be followed by a single blank line. If the change relates to a specific driver (libvirt, xenapi, qpid, etc...), begin the first line of the commit message with the driver name, lowercased, followed by a colon. Following your brief summary, provide a more detailed description of the patch, manually wrapping the text at 72 characters. This description should provide enough detail that one does not have to refer to external resources to determine its high-level functionality. Once you use 'git review', two lines will be appended to the commit message: a blank line followed by a 'Change-Id'. This is important to correlate this commit with a specific review in Gerrit, and it should not be modified. For further information on constructing high quality commit messages, and how to split up commits into a series of changes, consult the project wiki: http://wiki.openstack.org/GitCommitMessages tempest-2013.2.a1291.g23a1b4f/tempest/0000775000175000017500000000000012161375700017127 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/scenario/0000775000175000017500000000000012161375700020732 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/scenario/test_minimum_basic.py0000664000175000017500000001702112161375672025170 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 NEC Corporation # 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 logging from tempest.common.utils.data_utils import rand_name from tempest.common.utils.linux.remote_client import RemoteClient from tempest.scenario import manager LOG = logging.getLogger(__name__) class TestMinimumBasicScenario(manager.OfficialClientTest): """ This is a basic minimum scenario test. This test below: * across the multiple components * as a regular user * with and without optional parameters * check command outputs """ def _wait_for_server_status(self, status): server_id = self.server.id self.status_timeout( self.compute_client.servers, server_id, status) def _wait_for_volume_status(self, status): volume_id = self.volume.id self.status_timeout( self.volume_client.volumes, volume_id, status) def _image_create(self, name, fmt, path, properties={}): name = rand_name('%s-' % name) image_file = open(path, 'rb') self.addCleanup(image_file.close) params = { 'name': name, 'container_format': fmt, 'disk_format': fmt, 'is_public': 'True', } params.update(properties) image = self.image_client.images.create(**params) self.addCleanup(self.image_client.images.delete, image) self.assertEqual("queued", image.status) image.update(data=image_file) return image.id def glance_image_create(self): aki_img_path = self.config.scenario.img_dir + "/" + \ self.config.scenario.aki_img_file ari_img_path = self.config.scenario.img_dir + "/" + \ self.config.scenario.ari_img_file ami_img_path = self.config.scenario.img_dir + "/" + \ self.config.scenario.ami_img_file LOG.debug("paths: ami: %s, ari: %s, aki: %s" % (ami_img_path, ari_img_path, aki_img_path)) kernel_id = self._image_create('scenario-aki', 'aki', aki_img_path) ramdisk_id = self._image_create('scenario-ari', 'ari', ari_img_path) properties = { 'properties': {'kernel_id': kernel_id, 'ramdisk_id': ramdisk_id} } self.image = self._image_create('scenario-ami', 'ami', path=ami_img_path, properties=properties) def nova_keypair_add(self): name = rand_name('scenario-keypair-') self.keypair = self.compute_client.keypairs.create(name=name) self.addCleanup(self.compute_client.keypairs.delete, self.keypair) self.assertEqual(name, self.keypair.name) def nova_boot(self): name = rand_name('scenario-server-') client = self.compute_client flavor_id = self.config.compute.flavor_ref self.server = client.servers.create(name=name, image=self.image, flavor=flavor_id, key_name=self.keypair.name) self.addCleanup(self.compute_client.servers.delete, self.server) self.assertEqual(name, self.server.name) self._wait_for_server_status('ACTIVE') def nova_list(self): servers = self.compute_client.servers.list() LOG.debug("server_list:%s" % servers) self.assertTrue(self.server in servers) def nova_show(self): got_server = self.compute_client.servers.get(self.server) LOG.debug("got server:%s" % got_server) self.assertEqual(self.server, got_server) def cinder_create(self): name = rand_name('scenario-volume-') LOG.debug("volume display-name:%s" % name) self.volume = self.volume_client.volumes.create(size=1, display_name=name) LOG.debug("volume created:%s" % self.volume.display_name) self._wait_for_volume_status('available') self.addCleanup(self.volume_client.volumes.delete, self.volume) self.assertEqual(name, self.volume.display_name) def cinder_list(self): volumes = self.volume_client.volumes.list() self.assertTrue(self.volume in volumes) def cinder_show(self): volume = self.volume_client.volumes.get(self.volume.id) self.assertEqual(self.volume, volume) def nova_volume_attach(self): attach_volume_client = self.compute_client.volumes.create_server_volume volume = attach_volume_client(self.server.id, self.volume.id, '/dev/vdb') self.assertEqual(self.volume.id, volume.id) self._wait_for_volume_status('in-use') def nova_reboot(self): self.server.reboot() self._wait_for_server_status('ACTIVE') def nova_floating_ip_create(self): self.floating_ip = self.compute_client.floating_ips.create() self.addCleanup(self.floating_ip.delete) def nova_floating_ip_add(self): self.server.add_floating_ip(self.floating_ip) def nova_security_group_rule_create(self): sgs = self.compute_client.security_groups.list() for sg in sgs: if sg.name == 'default': secgroup = sg ruleset = { # ssh 'ip_protocol': 'tcp', 'from_port': 22, 'to_port': 22, 'cidr': '0.0.0.0/0', 'group_id': None } sg_rule = self.compute_client.security_group_rules.create(secgroup.id, **ruleset) self.addCleanup(self.compute_client.security_group_rules.delete, sg_rule.id) def ssh_to_server(self): username = self.config.scenario.ssh_user self.linux_client = RemoteClient(self.floating_ip.ip, username, pkey=self.keypair.private_key) def check_partitions(self): partitions = self.linux_client.get_partitions() self.assertEqual(1, partitions.count('vdb')) def nova_volume_detach(self): detach_volume_client = self.compute_client.volumes.delete_server_volume detach_volume_client(self.server.id, self.volume.id) self._wait_for_volume_status('available') volume = self.volume_client.volumes.get(self.volume.id) self.assertEqual('available', volume.status) def test_minimum_basic_scenario(self): self.glance_image_create() self.nova_keypair_add() self.nova_boot() self.nova_list() self.nova_show() self.cinder_create() self.cinder_list() self.cinder_show() self.nova_volume_attach() self.cinder_show() self.nova_reboot() self.nova_floating_ip_create() self.nova_floating_ip_add() self.nova_security_group_rule_create() self.ssh_to_server() self.check_partitions() self.nova_volume_detach() tempest-2013.2.a1291.g23a1b4f/tempest/scenario/manager.py0000664000175000017500000004412112161375672022730 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # Copyright 2013 IBM Corp. # 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 logging import subprocess # Default client libs import cinderclient.client import glanceclient import keystoneclient.v2_0.client import netaddr import novaclient.client from quantumclient.common import exceptions as exc import quantumclient.v2_0.client from tempest.api.network import common as net_common from tempest.common import ssh from tempest.common.utils.data_utils import rand_name from tempest import exceptions import tempest.manager import tempest.test LOG = logging.getLogger(__name__) class OfficialClientManager(tempest.manager.Manager): """ Manager that provides access to the official python clients for calling various OpenStack APIs. """ NOVACLIENT_VERSION = '2' CINDERCLIENT_VERSION = '1' def __init__(self): super(OfficialClientManager, self).__init__() self.compute_client = self._get_compute_client() self.image_client = self._get_image_client() self.identity_client = self._get_identity_client() self.network_client = self._get_network_client() self.volume_client = self._get_volume_client() self.client_attr_names = [ 'compute_client', 'image_client', 'identity_client', 'network_client', 'volume_client' ] def _get_compute_client(self, username=None, password=None, tenant_name=None): # Novaclient will not execute operations for anyone but the # identified user, so a new client needs to be created for # each user that operations need to be performed for. if not username: username = self.config.identity.username if not password: password = self.config.identity.password if not tenant_name: tenant_name = self.config.identity.tenant_name if None in (username, password, tenant_name): msg = ("Missing required credentials for compute client. " "username: %(username)s, password: %(password)s, " "tenant_name: %(tenant_name)s") % locals() raise exceptions.InvalidConfiguration(msg) auth_url = self.config.identity.uri dscv = self.config.identity.disable_ssl_certificate_validation client_args = (username, password, tenant_name, auth_url) # Create our default Nova client to use in testing service_type = self.config.compute.catalog_type return novaclient.client.Client(self.NOVACLIENT_VERSION, *client_args, service_type=service_type, no_cache=True, insecure=dscv) def _get_image_client(self): keystone = self._get_identity_client() token = keystone.auth_token endpoint = keystone.service_catalog.url_for(service_type='image', endpoint_type='publicURL') dscv = self.config.identity.disable_ssl_certificate_validation return glanceclient.Client('1', endpoint=endpoint, token=token, insecure=dscv) def _get_volume_client(self, username=None, password=None, tenant_name=None): if not username: username = self.config.identity.username if not password: password = self.config.identity.password if not tenant_name: tenant_name = self.config.identity.tenant_name auth_url = self.config.identity.uri return cinderclient.client.Client(self.CINDERCLIENT_VERSION, username, password, tenant_name, auth_url) def _get_identity_client(self, username=None, password=None, tenant_name=None): # This identity client is not intended to check the security # of the identity service, so use admin credentials by default. if not username: username = self.config.identity.admin_username if not password: password = self.config.identity.admin_password if not tenant_name: tenant_name = self.config.identity.admin_tenant_name if None in (username, password, tenant_name): msg = ("Missing required credentials for identity client. " "username: %(username)s, password: %(password)s, " "tenant_name: %(tenant_name)s") % locals() raise exceptions.InvalidConfiguration(msg) auth_url = self.config.identity.uri dscv = self.config.identity.disable_ssl_certificate_validation return keystoneclient.v2_0.client.Client(username=username, password=password, tenant_name=tenant_name, auth_url=auth_url, insecure=dscv) def _get_network_client(self): # The intended configuration is for the network client to have # admin privileges and indicate for whom resources are being # created via a 'tenant_id' parameter. This will often be # preferable to authenticating as a specific user because # working with certain resources (public routers and networks) # often requires admin privileges anyway. username = self.config.identity.admin_username password = self.config.identity.admin_password tenant_name = self.config.identity.admin_tenant_name if None in (username, password, tenant_name): msg = ("Missing required credentials for network client. " "username: %(username)s, password: %(password)s, " "tenant_name: %(tenant_name)s") % locals() raise exceptions.InvalidConfiguration(msg) auth_url = self.config.identity.uri dscv = self.config.identity.disable_ssl_certificate_validation return quantumclient.v2_0.client.Client(username=username, password=password, tenant_name=tenant_name, auth_url=auth_url, insecure=dscv) class OfficialClientTest(tempest.test.TestCase): """ Official Client test base class for scenario testing. Official Client tests are tests that have the following characteristics: * Test basic operations of an API, typically in an order that a regular user would perform those operations * Test only the correct inputs and action paths -- no fuzz or random input data is sent, only valid inputs. * Use only the default client tool for calling an API """ manager_class = OfficialClientManager @classmethod def tearDownClass(cls): # NOTE(jaypipes): Because scenario tests are typically run in a # specific order, and because test methods in scenario tests # generally create resources in a particular order, we destroy # resources in the reverse order in which resources are added to # the scenario test class object while cls.os_resources: thing = cls.os_resources.pop() LOG.debug("Deleting %r from shared resources of %s" % (thing, cls.__name__)) try: # OpenStack resources are assumed to have a delete() # method which destroys the resource... thing.delete() except Exception as e: # If the resource is already missing, mission accomplished. if e.__class__.__name__ == 'NotFound': continue raise def is_deletion_complete(): # Deletion testing is only required for objects whose # existence cannot be checked via retrieval. if isinstance(thing, dict): return True try: thing.get() except Exception as e: # Clients are expected to return an exception # called 'NotFound' if retrieval fails. if e.__class__.__name__ == 'NotFound': return True raise return False # Block until resource deletion has completed or timed-out tempest.test.call_until_true(is_deletion_complete, 10, 1) class NetworkScenarioTest(OfficialClientTest): """ Base class for network scenario tests """ @classmethod def check_preconditions(cls): if (cls.config.network.quantum_available): cls.enabled = True #verify that quantum_available is telling the truth try: cls.network_client.list_networks() except exc.EndpointNotFound: cls.enabled = False raise else: cls.enabled = False msg = 'Quantum not available' raise cls.skipException(msg) @classmethod def setUpClass(cls): super(NetworkScenarioTest, cls).setUpClass() cls.tenant_id = cls.manager._get_identity_client( cls.config.identity.username, cls.config.identity.password, cls.config.identity.tenant_name).tenant_id def _create_keypair(self, client, namestart='keypair-smoke-'): kp_name = rand_name(namestart) keypair = client.keypairs.create(kp_name) try: self.assertEqual(keypair.id, kp_name) self.set_resource(kp_name, keypair) except AttributeError: self.fail("Keypair object not successfully created.") return keypair def _create_security_group(self, client, namestart='secgroup-smoke-'): # Create security group sg_name = rand_name(namestart) sg_desc = sg_name + " description" secgroup = client.security_groups.create(sg_name, sg_desc) try: self.assertEqual(secgroup.name, sg_name) self.assertEqual(secgroup.description, sg_desc) self.set_resource(sg_name, secgroup) except AttributeError: self.fail("SecurityGroup object not successfully created.") # Add rules to the security group # These rules are intended to permit inbound ssh and icmp # traffic from all sources, so no group_id is provided. # Setting a group_id would only permit traffic from ports # belonging to the same security group. rulesets = [ { # ssh 'ip_protocol': 'tcp', 'from_port': 22, 'to_port': 22, 'cidr': '0.0.0.0/0', }, { # ping 'ip_protocol': 'icmp', 'from_port': -1, 'to_port': -1, 'cidr': '0.0.0.0/0', } ] for ruleset in rulesets: try: client.security_group_rules.create(secgroup.id, **ruleset) except Exception: self.fail("Failed to create rule in security group.") return secgroup def _create_network(self, tenant_id, namestart='network-smoke-'): name = rand_name(namestart) body = dict( network=dict( name=name, tenant_id=tenant_id, ), ) result = self.network_client.create_network(body=body) network = net_common.DeletableNetwork(client=self.network_client, **result['network']) self.assertEqual(network.name, name) self.set_resource(name, network) return network def _list_networks(self): nets = self.network_client.list_networks() return nets['networks'] def _list_subnets(self): subnets = self.network_client.list_subnets() return subnets['subnets'] def _list_routers(self): routers = self.network_client.list_routers() return routers['routers'] def _create_subnet(self, network, namestart='subnet-smoke-'): """ Create a subnet for the given network within the cidr block configured for tenant networks. """ cfg = self.config.network tenant_cidr = netaddr.IPNetwork(cfg.tenant_network_cidr) result = None # Repeatedly attempt subnet creation with sequential cidr # blocks until an unallocated block is found. for subnet_cidr in tenant_cidr.subnet(cfg.tenant_network_mask_bits): body = dict( subnet=dict( ip_version=4, network_id=network.id, tenant_id=network.tenant_id, cidr=str(subnet_cidr), ), ) try: result = self.network_client.create_subnet(body=body) break except exc.QuantumClientException as e: is_overlapping_cidr = 'overlaps with another subnet' in str(e) if not is_overlapping_cidr: raise self.assertIsNotNone(result, 'Unable to allocate tenant network') subnet = net_common.DeletableSubnet(client=self.network_client, **result['subnet']) self.assertEqual(subnet.cidr, str(subnet_cidr)) self.set_resource(rand_name(namestart), subnet) return subnet def _create_port(self, network, namestart='port-quotatest-'): name = rand_name(namestart) body = dict( port=dict(name=name, network_id=network.id, tenant_id=network.tenant_id)) result = self.network_client.create_port(body=body) self.assertIsNotNone(result, 'Unable to allocate port') port = net_common.DeletablePort(client=self.network_client, **result['port']) self.set_resource(name, port) return port def _create_server(self, client, network, name, key_name, security_groups): flavor_id = self.config.compute.flavor_ref base_image_id = self.config.compute.image_ref create_kwargs = { 'nics': [ {'net-id': network.id}, ], 'key_name': key_name, 'security_groups': security_groups, } server = client.servers.create(name, base_image_id, flavor_id, **create_kwargs) try: self.assertEqual(server.name, name) self.set_resource(name, server) except AttributeError: self.fail("Server not successfully created.") self.status_timeout(client.servers, server.id, 'ACTIVE') # The instance retrieved on creation is missing network # details, necessitating retrieval after it becomes active to # ensure correct details. server = client.servers.get(server.id) self.set_resource(name, server) return server def _create_floating_ip(self, server, external_network_id): result = self.network_client.list_ports(device_id=server.id) ports = result.get('ports', []) self.assertEqual(len(ports), 1, "Unable to determine which port to target.") port_id = ports[0]['id'] body = dict( floatingip=dict( floating_network_id=external_network_id, port_id=port_id, tenant_id=server.tenant_id, ) ) result = self.network_client.create_floatingip(body=body) floating_ip = net_common.DeletableFloatingIp( client=self.network_client, **result['floatingip']) self.set_resource(rand_name('floatingip-'), floating_ip) return floating_ip def _ping_ip_address(self, ip_address): cmd = ['ping', '-c1', '-w1', ip_address] def ping(): proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) proc.wait() if proc.returncode == 0: return True # TODO(mnewby) Allow configuration of execution and sleep duration. return tempest.test.call_until_true(ping, 20, 1) def _is_reachable_via_ssh(self, ip_address, username, private_key, timeout=120): ssh_client = ssh.Client(ip_address, username, pkey=private_key, timeout=timeout) return ssh_client.test_connection_auth() def _check_vm_connectivity(self, ip_address, username, private_key, timeout=120): self.assertTrue(self._ping_ip_address(ip_address), "Timed out waiting for %s to become " "reachable" % ip_address) self.assertTrue(self._is_reachable_via_ssh(ip_address, username, private_key, timeout=timeout), 'Auth failure in connecting to %s@%s via ssh' % (username, ip_address)) tempest-2013.2.a1291.g23a1b4f/tempest/scenario/test_network_basic_ops.py0000664000175000017500000002563312161375672026077 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # Copyright 2013 Hewlett-Packard Development Company, L.P. # 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 tempest.api.network import common as net_common from tempest.common.utils.data_utils import rand_name from tempest.scenario import manager from tempest.test import attr class TestNetworkBasicOps(manager.NetworkScenarioTest): """ This smoke test suite assumes that Nova has been configured to boot VM's with Quantum-managed networking, and attempts to verify network connectivity as follows: * For a freshly-booted VM with an IP address ("port") on a given network: - the Tempest host can ping the IP address. This implies, but does not guarantee (see the ssh check that follows), that the VM has been assigned the correct IP address and has connectivity to the Tempest host. - the Tempest host can perform key-based authentication to an ssh server hosted at the IP address. This check guarantees that the IP address is associated with the target VM. #TODO(mnewby) - Need to implement the following: - the Tempest host can ssh into the VM via the IP address and successfully execute the following: - ping an external IP address, implying external connectivity. - ping an external hostname, implying that dns is correctly configured. - ping an internal IP address, implying connectivity to another VM on the same network. There are presumed to be two types of networks: tenant and public. A tenant network may or may not be reachable from the Tempest host. A public network is assumed to be reachable from the Tempest host, and it should be possible to associate a public ('floating') IP address with a tenant ('fixed') IP address to faciliate external connectivity to a potentially unroutable tenant IP address. This test suite can be configured to test network connectivity to a VM via a tenant network, a public network, or both. If both networking types are to be evaluated, tests that need to be executed remotely on the VM (via ssh) will only be run against one of the networks (to minimize test execution time). Determine which types of networks to test as follows: * Configure tenant network checks (via the 'tenant_networks_reachable' key) if the Tempest host should have direct connectivity to tenant networks. This is likely to be the case if Tempest is running on the same host as a single-node devstack installation with IP namespaces disabled. * Configure checks for a public network if a public network has been configured prior to the test suite being run and if the Tempest host should have connectivity to that public network. Checking connectivity for a public network requires that a value be provided for 'public_network_id'. A value can optionally be provided for 'public_router_id' if tenants will use a shared router to access a public network (as is likely to be the case when IP namespaces are not enabled). If a value is not provided for 'public_router_id', a router will be created for each tenant and use the network identified by 'public_network_id' as its gateway. """ @classmethod def check_preconditions(cls): super(TestNetworkBasicOps, cls).check_preconditions() cfg = cls.config.network if not (cfg.tenant_networks_reachable or cfg.public_network_id): msg = ('Either tenant_networks_reachable must be "true", or ' 'public_network_id must be defined.') cls.enabled = False raise cls.skipException(msg) @classmethod def setUpClass(cls): super(TestNetworkBasicOps, cls).setUpClass() cls.check_preconditions() cls.tenant_id = cls.manager._get_identity_client( cls.config.identity.username, cls.config.identity.password, cls.config.identity.tenant_name).tenant_id # TODO(mnewby) Consider looking up entities as needed instead # of storing them as collections on the class. cls.keypairs = {} cls.security_groups = {} cls.networks = [] cls.subnets = [] cls.routers = [] cls.servers = [] cls.floating_ips = {} def _get_router(self, tenant_id): """Retrieve a router for the given tenant id. If a public router has been configured, it will be returned. If a public router has not been configured, but a public network has, a tenant router will be created and returned that routes traffic to the public network. """ router_id = self.config.network.public_router_id network_id = self.config.network.public_network_id if router_id: result = self.network_client.show_router(router_id) return net_common.AttributeDict(**result['router']) elif network_id: router = self._create_router(tenant_id) router.add_gateway(network_id) return router else: raise Exception("Neither of 'public_router_id' or " "'public_network_id' has been defined.") def _create_router(self, tenant_id, namestart='router-smoke-'): name = rand_name(namestart) body = dict( router=dict( name=name, admin_state_up=True, tenant_id=tenant_id, ), ) result = self.network_client.create_router(body=body) router = net_common.DeletableRouter(client=self.network_client, **result['router']) self.assertEqual(router.name, name) self.set_resource(name, router) return router @attr(type='smoke') def test_001_create_keypairs(self): self.keypairs[self.tenant_id] = self._create_keypair( self.compute_client) @attr(type='smoke') def test_002_create_security_groups(self): self.security_groups[self.tenant_id] = self._create_security_group( self.compute_client) @attr(type='smoke') def test_003_create_networks(self): network = self._create_network(self.tenant_id) router = self._get_router(self.tenant_id) subnet = self._create_subnet(network) subnet.add_to_router(router.id) self.networks.append(network) self.subnets.append(subnet) self.routers.append(router) @attr(type='smoke') def test_004_check_networks(self): #Checks that we see the newly created network/subnet/router via #checking the result of list_[networks,routers,subnets] seen_nets = self._list_networks() seen_names = [n['name'] for n in seen_nets] seen_ids = [n['id'] for n in seen_nets] for mynet in self.networks: self.assertIn(mynet.name, seen_names) self.assertIn(mynet.id, seen_ids) seen_subnets = self._list_subnets() seen_net_ids = [n['network_id'] for n in seen_subnets] seen_subnet_ids = [n['id'] for n in seen_subnets] for mynet in self.networks: self.assertIn(mynet.id, seen_net_ids) for mysubnet in self.subnets: self.assertIn(mysubnet.id, seen_subnet_ids) seen_routers = self._list_routers() seen_router_ids = [n['id'] for n in seen_routers] seen_router_names = [n['name'] for n in seen_routers] for myrouter in self.routers: self.assertIn(myrouter.name, seen_router_names) self.assertIn(myrouter.id, seen_router_ids) @attr(type='smoke') def test_005_create_servers(self): if not (self.keypairs or self.security_groups or self.networks): raise self.skipTest('Necessary resources have not been defined') for i, network in enumerate(self.networks): tenant_id = network.tenant_id name = rand_name('server-smoke-%d-' % i) keypair_name = self.keypairs[tenant_id].name security_groups = [self.security_groups[tenant_id].name] server = self._create_server(self.compute_client, network, name, keypair_name, security_groups) self.servers.append(server) @attr(type='smoke') def test_006_check_tenant_network_connectivity(self): if not self.config.network.tenant_networks_reachable: msg = 'Tenant networks not configured to be reachable.' raise self.skipTest(msg) if not self.servers: raise self.skipTest("No VM's have been created") # The target login is assumed to have been configured for # key-based authentication by cloud-init. ssh_login = self.config.compute.image_ssh_user private_key = self.keypairs[self.tenant_id].private_key for server in self.servers: for net_name, ip_addresses in server.networks.iteritems(): for ip_address in ip_addresses: self._check_vm_connectivity(ip_address, ssh_login, private_key) @attr(type='smoke') def test_007_assign_floating_ips(self): public_network_id = self.config.network.public_network_id if not public_network_id: raise self.skipTest('Public network not configured') if not self.servers: raise self.skipTest("No VM's have been created") for server in self.servers: floating_ip = self._create_floating_ip(server, public_network_id) self.floating_ips.setdefault(server, []) self.floating_ips[server].append(floating_ip) @attr(type='smoke') def test_008_check_public_network_connectivity(self): if not self.floating_ips: raise self.skipTest('No floating ips have been allocated.') # The target login is assumed to have been configured for # key-based authentication by cloud-init. ssh_login = self.config.compute.image_ssh_user private_key = self.keypairs[self.tenant_id].private_key for server, floating_ips in self.floating_ips.iteritems(): for floating_ip in floating_ips: ip_address = floating_ip.floating_ip_address self._check_vm_connectivity(ip_address, ssh_login, private_key) tempest-2013.2.a1291.g23a1b4f/tempest/scenario/test_server_advanced_ops.py0000664000175000017500000001073012161375672026370 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.common import log as logging from tempest.common.utils.data_utils import rand_name from tempest.scenario import manager LOG = logging.getLogger(__name__) class TestServerAdvancedOps(manager.OfficialClientTest): """ This test case stresses some advanced server instance operations: * Resizing an instance * Sequence suspend resume """ @classmethod def setUpClass(cls): super(TestServerAdvancedOps, cls).setUpClass() if not cls.config.compute.resize_available: msg = "Skipping test - resize not available on this host" raise cls.skipException(msg) resize_flavor = cls.config.compute.flavor_ref_alt if resize_flavor == cls.config.compute.flavor_ref: msg = "Skipping test - flavor_ref and flavor_ref_alt are identical" raise cls.skipException(msg) def test_resize_server_confirm(self): # We create an instance for use in this test i_name = rand_name('instance') flavor_id = self.config.compute.flavor_ref base_image_id = self.config.compute.image_ref self.instance = self.compute_client.servers.create( i_name, base_image_id, flavor_id) self.assertEqual(self.instance.name, i_name) self.set_resource('instance', self.instance) self.assertEqual(self.instance.status, 'BUILD') instance_id = self.get_resource('instance').id self.status_timeout( self.compute_client.servers, instance_id, 'ACTIVE') instance = self.get_resource('instance') instance_id = instance.id resize_flavor = self.config.compute.flavor_ref_alt LOG.debug("Resizing instance %s from flavor %s to flavor %s", instance.id, instance.flavor, resize_flavor) instance.resize(resize_flavor) self.status_timeout(self.compute_client.servers, instance_id, 'VERIFY_RESIZE') LOG.debug("Confirming resize of instance %s", instance_id) instance.confirm_resize() self.status_timeout( self.compute_client.servers, instance_id, 'ACTIVE') def test_server_sequence_suspend_resume(self): # We create an instance for use in this test i_name = rand_name('instance') flavor_id = self.config.compute.flavor_ref base_image_id = self.config.compute.image_ref self.instance = self.compute_client.servers.create( i_name, base_image_id, flavor_id) self.assertEqual(self.instance.name, i_name) self.set_resource('instance', self.instance) self.assertEqual(self.instance.status, 'BUILD') instance_id = self.get_resource('instance').id self.status_timeout( self.compute_client.servers, instance_id, 'ACTIVE') instance = self.get_resource('instance') instance_id = instance.id LOG.debug("Suspending instance %s. Current status: %s", instance_id, instance.status) instance.suspend() self.status_timeout(self.compute_client.servers, instance_id, 'SUSPENDED') LOG.debug("Resuming instance %s. Current status: %s", instance_id, instance.status) instance.resume() self.status_timeout(self.compute_client.servers, instance_id, 'ACTIVE') LOG.debug("Suspending instance %s. Current status: %s", instance_id, instance.status) instance.suspend() self.status_timeout(self.compute_client.servers, instance_id, 'SUSPENDED') LOG.debug("Resuming instance %s. Current status: %s", instance_id, instance.status) instance.resume() self.status_timeout(self.compute_client.servers, instance_id, 'ACTIVE') tempest-2013.2.a1291.g23a1b4f/tempest/scenario/README.rst0000664000175000017500000000301712161375672022432 0ustar chuckchuck00000000000000Tempest Guide to Scenario tests =============================== What are these tests? --------------------- Scenario tests are "through path" tests of OpenStack function. Complicated setups where one part might depend on completion of a previous part. They ideally involve the integration between multiple OpenStack services to exercise the touch points between them. An example would be: start with a blank environment, upload a glance image, deploy a vm from it, ssh to the guest, make changes, capture that vm's image back into glance as a snapshot, and launch a second vm from that snapshot. Why are these tests in tempest? ------------------------------- This is one of tempests core purposes, testing the integration between projects. Scope of these tests -------------------- Scenario tests should always test at least 2 services in interaction. They should use the official python client libraries for OpenStack, as they provide a more realistic approach in how people will interact with the services. TODO: once we have service tags, tests should be tagged with which services they exercise. Example of a good test ---------------------- While we are looking for interaction of 2 or more services, be specific in your interactions. A giant "this is my data center" smoke test is hard to debug when it goes wrong. A flow of interactions between glance and nova, like in the introduction, is a good example. Especially if it involves a repeated interaction when a resource is setup, modified, detached, and then reused later again. tempest-2013.2.a1291.g23a1b4f/tempest/scenario/__init__.py0000664000175000017500000000000012161375672023041 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/scenario/test_network_quotas.py0000664000175000017500000000676212161375672025453 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 Hewlett-Packard Development Company, L.P. # 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 quantumclient.common import exceptions as exc from tempest.scenario.manager import NetworkScenarioTest MAX_REASONABLE_ITERATIONS = 51 # more than enough. Default for port is 50. class TestNetworkQuotaBasic(NetworkScenarioTest): """ This test suite contains tests that each loop trying to grab a particular resource until a quota limit is hit. For sanity, there is a maximum number of iterations - if this is hit the test fails. Covers network, subnet, port. """ @classmethod def check_preconditions(cls): super(TestNetworkQuotaBasic, cls).check_preconditions() @classmethod def setUpClass(cls): super(TestNetworkQuotaBasic, cls).setUpClass() cls.check_preconditions() cls.networks = [] cls.subnets = [] cls.ports = [] def test_create_network_until_quota_hit(self): hit_limit = False for n in xrange(MAX_REASONABLE_ITERATIONS): try: self.networks.append( self._create_network(self.tenant_id, namestart='network-quotatest-')) except exc.QuantumClientException as e: if (e.status_code != 409): raise hit_limit = True break self.assertTrue(hit_limit, "Failed: Did not hit quota limit !") def test_create_subnet_until_quota_hit(self): if not self.networks: self.networks.append( self._create_network(self.tenant_id, namestart='network-quotatest-')) hit_limit = False for n in xrange(MAX_REASONABLE_ITERATIONS): try: self.subnets.append( self._create_subnet(self.networks[0], namestart='subnet-quotatest-')) except exc.QuantumClientException as e: if (e.status_code != 409): raise hit_limit = True break self.assertTrue(hit_limit, "Failed: Did not hit quota limit !") def test_create_ports_until_quota_hit(self): if not self.networks: self.networks.append( self._create_network(self.tenant_id, namestart='network-quotatest-')) hit_limit = False for n in xrange(MAX_REASONABLE_ITERATIONS): try: self.ports.append( self._create_port(self.networks[0], namestart='port-quotatest-')) except exc.QuantumClientException as e: if (e.status_code != 409): raise hit_limit = True break self.assertTrue(hit_limit, "Failed: Did not hit quota limit !") tempest-2013.2.a1291.g23a1b4f/tempest/scenario/test_server_basic_ops.py0000664000175000017500000001307312161375672025707 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.common import log as logging from tempest.common.utils.data_utils import rand_name from tempest.scenario import manager LOG = logging.getLogger(__name__) class TestServerBasicOps(manager.OfficialClientTest): """ This smoke test case follows this basic set of operations: * Create a keypair for use in launching an instance * Create a security group to control network access in instance * Add simple permissive rules to the security group * Launch an instance * Pause/unpause the instance * Suspend/resume the instance * Terminate the instance """ def create_keypair(self): kp_name = rand_name('keypair-smoke') self.keypair = self.compute_client.keypairs.create(kp_name) try: self.assertEqual(self.keypair.id, kp_name) self.set_resource('keypair', self.keypair) except AttributeError: self.fail("Keypair object not successfully created.") def create_security_group(self): sg_name = rand_name('secgroup-smoke') sg_desc = sg_name + " description" self.secgroup = self.compute_client.security_groups.create(sg_name, sg_desc) try: self.assertEqual(self.secgroup.name, sg_name) self.assertEqual(self.secgroup.description, sg_desc) self.set_resource('secgroup', self.secgroup) except AttributeError: self.fail("SecurityGroup object not successfully created.") # Add rules to the security group rulesets = [ { 'ip_protocol': 'tcp', 'from_port': 1, 'to_port': 65535, 'cidr': '0.0.0.0/0', 'group_id': self.secgroup.id }, { 'ip_protocol': 'icmp', 'from_port': -1, 'to_port': -1, 'cidr': '0.0.0.0/0', 'group_id': self.secgroup.id } ] for ruleset in rulesets: try: self.compute_client.security_group_rules.create( self.secgroup.id, **ruleset) except Exception: self.fail("Failed to create rule in security group.") def boot_instance(self): i_name = rand_name('instance') flavor_id = self.config.compute.flavor_ref base_image_id = self.config.compute.image_ref create_kwargs = { 'key_name': self.get_resource('keypair').id } self.instance = self.compute_client.servers.create( i_name, base_image_id, flavor_id, **create_kwargs) try: self.assertEqual(self.instance.name, i_name) self.set_resource('instance', self.instance) except AttributeError: self.fail("Instance not successfully created.") self.assertEqual(self.instance.status, 'BUILD') def wait_on_active(self): instance_id = self.get_resource('instance').id self.status_timeout( self.compute_client.servers, instance_id, 'ACTIVE') def pause_server(self): instance = self.get_resource('instance') instance_id = instance.id LOG.debug("Pausing instance %s. Current status: %s", instance_id, instance.status) instance.pause() self.status_timeout( self.compute_client.servers, instance_id, 'PAUSED') def unpause_server(self): instance = self.get_resource('instance') instance_id = instance.id LOG.debug("Unpausing instance %s. Current status: %s", instance_id, instance.status) instance.unpause() self.status_timeout( self.compute_client.servers, instance_id, 'ACTIVE') def suspend_server(self): instance = self.get_resource('instance') instance_id = instance.id LOG.debug("Suspending instance %s. Current status: %s", instance_id, instance.status) instance.suspend() self.status_timeout(self.compute_client.servers, instance_id, 'SUSPENDED') def resume_server(self): instance = self.get_resource('instance') instance_id = instance.id LOG.debug("Resuming instance %s. Current status: %s", instance_id, instance.status) instance.resume() self.status_timeout( self.compute_client.servers, instance_id, 'ACTIVE') def terminate_instance(self): instance = self.get_resource('instance') instance.delete() self.remove_resource('instance') def test_server_basicops(self): self.create_keypair() self.create_security_group() self.boot_instance() self.wait_on_active() self.pause_server() self.unpause_server() self.suspend_server() self.resume_server() self.terminate_instance() tempest-2013.2.a1291.g23a1b4f/tempest/common/0000775000175000017500000000000012161375700020417 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/common/utils/0000775000175000017500000000000012161375700021557 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/common/utils/misc.py0000664000175000017500000000165212161375672023100 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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. def singleton(cls): """Simple wrapper for classes that should only have a single instance.""" instances = {} def getinstance(): if cls not in instances: instances[cls] = cls() return instances[cls] return getinstance tempest-2013.2.a1291.g23a1b4f/tempest/common/utils/data_utils.py0000664000175000017500000000401012161375672024265 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 itertools import random import re import urllib from tempest import exceptions def rand_name(name='test'): return name + str(random.randint(1, 0x7fffffff)) def rand_int_id(start=0, end=0x7fffffff): return random.randint(start, end) def build_url(host, port, api_version=None, path=None, params=None, use_ssl=False): """Build the request URL from given host, port, path and parameters.""" pattern = 'v\d\.\d' if re.match(pattern, path): message = 'Version should not be included in path.' raise exceptions.InvalidConfiguration(message=message) if use_ssl: url = "https://" + host else: url = "http://" + host if port is not None: url += ":" + port url += "/" if api_version is not None: url += api_version + "/" if path is not None: url += path if params is not None: url += "?" url += urllib.urlencode(params) return url def parse_image_id(image_ref): """Return the image id from a given image ref.""" return image_ref.rsplit('/')[-1] def arbitrary_string(size=4, base_text=None): """ Return size characters from base_text, repeating the base_text infinitely if needed. """ if not base_text: base_text = 'test' return ''.join(itertools.islice(itertools.cycle(base_text), size)) tempest-2013.2.a1291.g23a1b4f/tempest/common/utils/linux/0000775000175000017500000000000012161375700022716 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/common/utils/linux/remote_client.py0000664000175000017500000000561412161375672026137 0ustar chuckchuck00000000000000import re import time from tempest.common.ssh import Client from tempest.common import utils from tempest.config import TempestConfig from tempest.exceptions import ServerUnreachable from tempest.exceptions import SSHTimeout class RemoteClient(): #Note(afazekas): It should always get an address instead of server def __init__(self, server, username, password=None, pkey=None): ssh_timeout = TempestConfig().compute.ssh_timeout network = TempestConfig().compute.network_for_ssh ip_version = TempestConfig().compute.ip_version_for_ssh ssh_channel_timeout = TempestConfig().compute.ssh_channel_timeout if isinstance(server, basestring): ip_address = server else: addresses = server['addresses'][network] for address in addresses: if address['version'] == ip_version: ip_address = address['addr'] break else: raise ServerUnreachable() self.ssh_client = Client(ip_address, username, password, ssh_timeout, pkey=pkey, channel_timeout=ssh_channel_timeout) if not self.ssh_client.test_connection_auth(): raise SSHTimeout() def can_authenticate(self): # Re-authenticate return self.ssh_client.test_connection_auth() def hostname_equals_servername(self, expected_hostname): # Get hostname using command "hostname" actual_hostname = self.ssh_client.exec_command("hostname").rstrip() return expected_hostname == actual_hostname def get_files(self, path): # Return a list of comma seperated files command = "ls -m " + path return self.ssh_client.exec_command(command).rstrip('\n').split(', ') def get_ram_size_in_mb(self): output = self.ssh_client.exec_command('free -m | grep Mem') if output: return output.split()[1] def get_number_of_vcpus(self): command = 'cat /proc/cpuinfo | grep processor | wc -l' output = self.ssh_client.exec_command(command) return int(output) def get_partitions(self): # Return the contents of /proc/partitions command = 'cat /proc/partitions' output = self.ssh_client.exec_command(command) return output def get_boot_time(self): cmd = 'date -d "`cut -f1 -d. /proc/uptime` seconds ago" \ "+%Y-%m-%d %H:%M:%S"' boot_time_string = self.ssh_client.exec_command(cmd) boot_time_string = boot_time_string.replace('\n', '') return time.strptime(boot_time_string, utils.LAST_REBOOT_TIME_FORMAT) def write_to_console(self, message): message = re.sub("([$\\`])", "\\\\\\\\\\1", message) # usually to /dev/ttyS0 cmd = 'sudo sh -c "echo \\"%s\\" >/dev/console"' % message return self.ssh_client.exec_command(cmd) tempest-2013.2.a1291.g23a1b4f/tempest/common/utils/linux/__init__.py0000664000175000017500000000000012161375672025025 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/common/utils/file_utils.py0000664000175000017500000000147112161375672024303 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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. def have_effective_read_access(path): try: fh = open(path, "rb") except IOError: return False fh.close() return True tempest-2013.2.a1291.g23a1b4f/tempest/common/utils/__init__.py0000664000175000017500000000025212161375672023677 0ustar chuckchuck00000000000000LAST_REBOOT_TIME_FORMAT = '%Y-%m-%d %H:%M:%S' PING_IPV4_COMMAND = 'ping -c 3 ' PING_IPV6_COMMAND = 'ping6 -c 3 ' PING_PACKET_LOSS_REGEX = '(\d{1,3})\.?\d*\% packet loss' tempest-2013.2.a1291.g23a1b4f/tempest/common/rest_client.py0000664000175000017500000004605512161375672023326 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # Copyright 2013 IBM Corp. # 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 collections import hashlib import httplib2 import json from lxml import etree import re import time from tempest.common import log as logging from tempest import exceptions from tempest.services.compute.xml.common import xml_to_json # redrive rate limited calls at most twice MAX_RECURSION_DEPTH = 2 TOKEN_CHARS_RE = re.compile('^[-A-Za-z0-9+/=]*$') class RestClient(object): TYPE = "json" LOG = logging.getLogger(__name__) def __init__(self, config, user, password, auth_url, tenant_name=None, auth_version='v2'): self.config = config self.user = user self.password = password self.auth_url = auth_url self.tenant_name = tenant_name self.auth_version = auth_version self.service = None self.token = None self.base_url = None self.region = {'compute': self.config.identity.region} self.endpoint_url = 'publicURL' self.headers = {'Content-Type': 'application/%s' % self.TYPE, 'Accept': 'application/%s' % self.TYPE} self.build_interval = config.compute.build_interval self.build_timeout = config.compute.build_timeout self.general_header_lc = set(('cache-control', 'connection', 'date', 'pragma', 'trailer', 'transfer-encoding', 'via', 'warning')) self.response_header_lc = set(('accept-ranges', 'age', 'etag', 'location', 'proxy-authenticate', 'retry-after', 'server', 'vary', 'www-authenticate')) dscv = self.config.identity.disable_ssl_certificate_validation self.http_obj = httplib2.Http(disable_ssl_certificate_validation=dscv) def _set_auth(self): """ Sets the token and base_url used in requests based on the strategy type """ if self.auth_version == 'v3': auth_func = self.identity_auth_v3 else: auth_func = self.keystone_auth self.token, self.base_url = ( auth_func(self.user, self.password, self.auth_url, self.service, self.tenant_name)) def clear_auth(self): """ Can be called to clear the token and base_url so that the next request will fetch a new token and base_url. """ self.token = None self.base_url = None def get_auth(self): """Returns the token of the current request or sets the token if none. """ if not self.token: self._set_auth() return self.token def basic_auth(self, user, password, auth_url): """ Provides authentication for the target API. """ params = {} params['headers'] = {'User-Agent': 'Test-Client', 'X-Auth-User': user, 'X-Auth-Key': password} resp, body = self.http_obj.request(auth_url, 'GET', **params) try: return resp['x-auth-token'], resp['x-server-management-url'] except Exception: raise def keystone_auth(self, user, password, auth_url, service, tenant_name): """ Provides authentication via Keystone using v2 identity API. """ # Normalize URI to ensure /tokens is in it. if 'tokens' not in auth_url: auth_url = auth_url.rstrip('/') + '/tokens' creds = { 'auth': { 'passwordCredentials': { 'username': user, 'password': password, }, 'tenantName': tenant_name, } } headers = {'Content-Type': 'application/json'} body = json.dumps(creds) self._log_request('POST', auth_url, headers, body) resp, resp_body = self.http_obj.request(auth_url, 'POST', headers=headers, body=body) self._log_response(resp, resp_body) if resp.status == 200: try: auth_data = json.loads(resp_body)['access'] token = auth_data['token']['id'] except Exception, e: print "Failed to obtain token for user: %s" % e raise mgmt_url = None for ep in auth_data['serviceCatalog']: if ep["type"] == service: for _ep in ep['endpoints']: if service in self.region and \ _ep['region'] == self.region[service]: mgmt_url = _ep[self.endpoint_url] if not mgmt_url: mgmt_url = ep['endpoints'][0][self.endpoint_url] break if mgmt_url is None: raise exceptions.EndpointNotFound(service) return token, mgmt_url elif resp.status == 401: raise exceptions.AuthenticationFailure(user=user, password=password) raise exceptions.IdentityError('Unexpected status code {0}'.format( resp.status)) def identity_auth_v3(self, user, password, auth_url, service, project_name, domain_id='default'): """Provides authentication using Identity API v3.""" req_url = auth_url.rstrip('/') + '/auth/tokens' creds = { "auth": { "identity": { "methods": ["password"], "password": { "user": { "name": user, "password": password, "domain": {"id": domain_id} } } }, "scope": { "project": { "domain": {"id": domain_id}, "name": project_name } } } } headers = {'Content-Type': 'application/json'} body = json.dumps(creds) resp, body = self.http_obj.request(req_url, 'POST', headers=headers, body=body) if resp.status == 201: try: token = resp['x-subject-token'] except Exception: self.LOG.exception("Failed to obtain token using V3" " authentication (auth URL is '%s')" % req_url) raise catalog = json.loads(body)['token']['catalog'] mgmt_url = None for service_info in catalog: if service_info['type'] != service: continue # this isn't the entry for us. endpoints = service_info['endpoints'] # Look for an endpoint in the region if configured. if service in self.region: region = self.region[service] for ep in endpoints: if ep['region'] != region: continue mgmt_url = ep['url'] # FIXME(blk-u): this isn't handling endpoint type # (public, internal, admin). break if not mgmt_url: # Didn't find endpoint for region, use the first. ep = endpoints[0] mgmt_url = ep['url'] # FIXME(blk-u): this isn't handling endpoint type # (public, internal, admin). break return token, mgmt_url elif resp.status == 401: raise exceptions.AuthenticationFailure(user=user, password=password) else: self.LOG.error("Failed to obtain token using V3 authentication" " (auth URL is '%s'), the response status is %s" % (req_url, resp.status)) raise exceptions.AuthenticationFailure(user=user, password=password) def post(self, url, body, headers): return self.request('POST', url, headers, body) def get(self, url, headers=None): return self.request('GET', url, headers) def delete(self, url, headers=None): return self.request('DELETE', url, headers) def patch(self, url, body, headers): return self.request('PATCH', url, headers, body) def put(self, url, body, headers): return self.request('PUT', url, headers, body) def head(self, url, headers=None): return self.request('HEAD', url, headers) def copy(self, url, headers=None): return self.request('COPY', url, headers) def get_versions(self): resp, body = self.get('') body = self._parse_resp(body) body = body['versions'] versions = map(lambda x: x['id'], body) return resp, versions def _log_request(self, method, req_url, headers, body): self.LOG.info('Request: ' + method + ' ' + req_url) if headers: print_headers = headers if 'X-Auth-Token' in headers and headers['X-Auth-Token']: token = headers['X-Auth-Token'] if len(token) > 64 and TOKEN_CHARS_RE.match(token): print_headers = headers.copy() print_headers['X-Auth-Token'] = "" self.LOG.debug('Request Headers: ' + str(print_headers)) if body: str_body = str(body) length = len(str_body) self.LOG.debug('Request Body: ' + str_body[:2048]) if length >= 2048: self.LOG.debug("Large body (%d) md5 summary: %s", length, hashlib.md5(str_body).hexdigest()) def _log_response(self, resp, resp_body): status = resp['status'] self.LOG.info("Response Status: " + status) headers = resp.copy() del headers['status'] if len(headers): self.LOG.debug('Response Headers: ' + str(headers)) if resp_body: str_body = str(resp_body) length = len(str_body) self.LOG.debug('Response Body: ' + str_body[:2048]) if length >= 2048: self.LOG.debug("Large body (%d) md5 summary: %s", length, hashlib.md5(str_body).hexdigest()) def _parse_resp(self, body): return json.loads(body) def response_checker(self, method, url, headers, body, resp, resp_body): if (resp.status in set((204, 205, 304)) or resp.status < 200 or method.upper() == 'HEAD') and resp_body: raise exceptions.ResponseWithNonEmptyBody(status=resp.status) #NOTE(afazekas): # If the HTTP Status Code is 205 # 'The response MUST NOT include an entity.' # A HTTP entity has an entity-body and an 'entity-header'. # In the HTTP response specification (Section 6) the 'entity-header' # 'generic-header' and 'response-header' are in OR relation. # All headers not in the above two group are considered as entity # header in every interpretation. if (resp.status == 205 and 0 != len(set(resp.keys()) - set(('status',)) - self.response_header_lc - self.general_header_lc)): raise exceptions.ResponseWithEntity() #NOTE(afazekas) # Now the swift sometimes (delete not empty container) # returns with non json error response, we can create new rest class # for swift. # Usually RFC2616 says error responses SHOULD contain an explanation. # The warning is normal for SHOULD/SHOULD NOT case # Likely it will cause an error if not resp_body and resp.status >= 400: self.LOG.warning("status >= 400 response with empty body") def _request(self, method, url, headers=None, body=None): """A simple HTTP request interface.""" req_url = "%s/%s" % (self.base_url, url) self._log_request(method, req_url, headers, body) resp, resp_body = self.http_obj.request(req_url, method, headers=headers, body=body) self._log_response(resp, resp_body) self.response_checker(method, url, headers, body, resp, resp_body) return resp, resp_body def request(self, method, url, headers=None, body=None): retry = 0 if (self.token is None) or (self.base_url is None): self._set_auth() if headers is None: headers = {} headers['X-Auth-Token'] = self.token resp, resp_body = self._request(method, url, headers=headers, body=body) while (resp.status == 413 and 'retry-after' in resp and not self.is_absolute_limit( resp, self._parse_resp(resp_body)) and retry < MAX_RECURSION_DEPTH): retry += 1 delay = int(resp['retry-after']) time.sleep(delay) resp, resp_body = self._request(method, url, headers=headers, body=body) self._error_checker(method, url, headers, body, resp, resp_body) return resp, resp_body def _error_checker(self, method, url, headers, body, resp, resp_body): # NOTE(mtreinish): Check for httplib response from glance_http. The # object can't be used here because importing httplib breaks httplib2. # If another object from a class not imported were passed here as # resp this could possibly fail if str(type(resp)) == "": ctype = resp.getheader('content-type') else: try: ctype = resp['content-type'] # NOTE(mtreinish): Keystone delete user responses doesn't have a # content-type header. (They don't have a body) So just pretend it # is set. except KeyError: ctype = 'application/json' # It is not an error response if resp.status < 400: return JSON_ENC = ['application/json; charset=UTF-8', 'application/json', 'application/json; charset=utf-8'] # NOTE(mtreinish): This is for compatibility with Glance and swift # APIs. These are the return content types that Glance api v1 # (and occasionally swift) are using. TXT_ENC = ['text/plain; charset=UTF-8', 'text/html; charset=UTF-8', 'text/plain; charset=utf-8'] XML_ENC = ['application/xml', 'application/xml; charset=UTF-8'] if ctype in JSON_ENC or ctype in XML_ENC: parse_resp = True elif ctype in TXT_ENC: parse_resp = False else: raise exceptions.RestClientException(str(resp.status)) if resp.status == 401 or resp.status == 403: raise exceptions.Unauthorized() if resp.status == 404: raise exceptions.NotFound(resp_body) if resp.status == 400: if parse_resp: resp_body = self._parse_resp(resp_body) raise exceptions.BadRequest(resp_body) if resp.status == 409: if parse_resp: resp_body = self._parse_resp(resp_body) raise exceptions.Duplicate(resp_body) if resp.status == 413: if parse_resp: resp_body = self._parse_resp(resp_body) if self.is_absolute_limit(resp, resp_body): raise exceptions.OverLimit(resp_body) else: raise exceptions.RateLimitExceeded(resp_body) if resp.status == 422: if parse_resp: resp_body = self._parse_resp(resp_body) raise exceptions.UnprocessableEntity(resp_body) if resp.status in (500, 501): message = resp_body if parse_resp: resp_body = self._parse_resp(resp_body) #I'm seeing both computeFault and cloudServersFault come back. #Will file a bug to fix, but leave as is for now. if 'cloudServersFault' in resp_body: message = resp_body['cloudServersFault']['message'] elif 'computeFault' in resp_body: message = resp_body['computeFault']['message'] elif 'error' in resp_body: # Keystone errors message = resp_body['error']['message'] raise exceptions.IdentityError(message) elif 'message' in resp_body: message = resp_body['message'] raise exceptions.ComputeFault(message) if resp.status >= 400: if parse_resp: resp_body = self._parse_resp(resp_body) raise exceptions.RestClientException(str(resp.status)) def is_absolute_limit(self, resp, resp_body): if (not isinstance(resp_body, collections.Mapping) or 'retry-after' not in resp): return True over_limit = resp_body.get('overLimit', None) if not over_limit: return True return 'exceed' in over_limit.get('message', 'blabla') def wait_for_resource_deletion(self, id): """Waits for a resource to be deleted.""" start_time = int(time.time()) while True: if self.is_resource_deleted(id): return if int(time.time()) - start_time >= self.build_timeout: raise exceptions.TimeoutException time.sleep(self.build_interval) def is_resource_deleted(self, id): """ Subclasses override with specific deletion detection. """ message = ('"%s" does not implement is_resource_deleted' % self.__class__.__name__) raise NotImplementedError(message) class RestClientXML(RestClient): TYPE = "xml" def _parse_resp(self, body): return xml_to_json(etree.fromstring(body)) def is_absolute_limit(self, resp, resp_body): if (not isinstance(resp_body, collections.Mapping) or 'retry-after' not in resp): return True return 'exceed' in resp_body.get('message', 'blabla') tempest-2013.2.a1291.g23a1b4f/tempest/common/ssh.py0000664000175000017500000001255312161375672021604 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 cStringIO import select import socket import time import warnings from tempest import exceptions with warnings.catch_warnings(): warnings.simplefilter("ignore") import paramiko class Client(object): def __init__(self, host, username, password=None, timeout=300, pkey=None, channel_timeout=10, look_for_keys=False, key_filename=None): self.host = host self.username = username self.password = password if isinstance(pkey, basestring): pkey = paramiko.RSAKey.from_private_key( cStringIO.StringIO(str(pkey))) self.pkey = pkey self.look_for_keys = look_for_keys self.key_filename = key_filename self.timeout = int(timeout) self.channel_timeout = float(channel_timeout) self.buf_size = 1024 def _get_ssh_connection(self, sleep=1.5, backoff=1.01): """Returns an ssh connection to the specified host.""" _timeout = True bsleep = sleep ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy( paramiko.AutoAddPolicy()) _start_time = time.time() while not self._is_timed_out(self.timeout, _start_time): try: ssh.connect(self.host, username=self.username, password=self.password, look_for_keys=self.look_for_keys, key_filename=self.key_filename, timeout=self.timeout, pkey=self.pkey) _timeout = False break except (socket.error, paramiko.AuthenticationException): time.sleep(bsleep) bsleep *= backoff continue if _timeout: raise exceptions.SSHTimeout(host=self.host, user=self.username, password=self.password) return ssh def _is_timed_out(self, timeout, start_time): return (time.time() - timeout) > start_time def connect_until_closed(self): """Connect to the server and wait until connection is lost.""" try: ssh = self._get_ssh_connection() _transport = ssh.get_transport() _start_time = time.time() _timed_out = self._is_timed_out(self.timeout, _start_time) while _transport.is_active() and not _timed_out: time.sleep(5) _timed_out = self._is_timed_out(self.timeout, _start_time) ssh.close() except (EOFError, paramiko.AuthenticationException, socket.error): return def exec_command(self, cmd): """ Execute the specified command on the server. Note that this method is reading whole command outputs to memory, thus shouldn't be used for large outputs. :returns: data read from standard output of the command. :raises: SSHExecCommandFailed if command returns nonzero status. The exception contains command status stderr content. """ ssh = self._get_ssh_connection() transport = ssh.get_transport() channel = transport.open_session() channel.fileno() # Register event pipe channel.exec_command(cmd) channel.shutdown_write() out_data = [] err_data = [] select_params = [channel], [], [], self.channel_timeout while True: ready = select.select(*select_params) if not any(ready): raise exceptions.TimeoutException( "Command: '{0}' executed on host '{1}'.".format( cmd, self.host)) if not ready[0]: # If there is nothing to read. continue out_chunk = err_chunk = None if channel.recv_ready(): out_chunk = channel.recv(self.buf_size) out_data += out_chunk, if channel.recv_stderr_ready(): err_chunk = channel.recv_stderr(self.buf_size) err_data += err_chunk, if channel.closed and not err_chunk and not out_chunk: break exit_status = channel.recv_exit_status() if 0 != exit_status: raise exceptions.SSHExecCommandFailed( command=cmd, exit_status=exit_status, strerror=''.join(err_data)) return ''.join(out_data) def test_connection_auth(self): """Returns true if ssh can connect to server.""" try: connection = self._get_ssh_connection() connection.close() except paramiko.AuthenticationException: return False return True tempest-2013.2.a1291.g23a1b4f/tempest/common/log.py0000664000175000017500000000724112161375672021566 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 NEC Corporation. # 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 ConfigParser import inspect import logging import logging.config import os import re from oslo.config import cfg _DEFAULT_LOG_FORMAT = "%(asctime)s %(levelname)8s [%(name)s] %(message)s" _DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S" _loggers = {} def getLogger(name='unknown'): if len(_loggers) == 0: loaded = _load_log_config() getLogger.adapter = TestsAdapter if loaded else None if name not in _loggers: logger = logging.getLogger(name) if getLogger.adapter: _loggers[name] = getLogger.adapter(logger, name) else: _loggers[name] = logger return _loggers[name] def _load_log_config(): conf_dir = os.environ.get('TEMPEST_LOG_CONFIG_DIR', None) conf_file = os.environ.get('TEMPEST_LOG_CONFIG', None) if not conf_dir or not conf_file: return False log_config = os.path.join(conf_dir, conf_file) try: logging.config.fileConfig(log_config) except ConfigParser.Error, exc: raise cfg.ConfigFileParseError(log_config, str(exc)) return True class TestsAdapter(logging.LoggerAdapter): def __init__(self, logger, project_name): self.logger = logger self.project = project_name self.regexp = re.compile(r"test_\w+\.py") def __getattr__(self, key): return getattr(self.logger, key) def _get_test_name(self): frames = inspect.stack() for frame in frames: binary_name = frame[1] if self.regexp.search(binary_name) and 'self' in frame[0].f_locals: return frame[0].f_locals.get('self').id() elif frame[3] == '_run_cleanups': #NOTE(myamazaki): method calling addCleanup return frame[0].f_locals.get('self').case.id() elif frame[3] in ['setUpClass', 'tearDownClass']: #NOTE(myamazaki): setUpClass or tearDownClass return "%s.%s.%s" % (frame[0].f_locals['cls'].__module__, frame[0].f_locals['cls'].__name__, frame[3]) return None def process(self, msg, kwargs): if 'extra' not in kwargs: kwargs['extra'] = {} extra = kwargs['extra'] test_name = self._get_test_name() if test_name: extra.update({'testname': test_name}) extra['extra'] = extra.copy() return msg, kwargs class TestsFormatter(logging.Formatter): def __init__(self, fmt=None, datefmt=None): super(TestsFormatter, self).__init__() self.default_format = _DEFAULT_LOG_FORMAT self.testname_format =\ "%(asctime)s %(levelname)8s [%(testname)s] %(message)s" self.datefmt = _DEFAULT_LOG_DATE_FORMAT def format(self, record): extra = record.__dict__.get('extra', None) if extra and 'testname' in extra: self._fmt = self.testname_format else: self._fmt = self.default_format return logging.Formatter.format(self, record) tempest-2013.2.a1291.g23a1b4f/tempest/common/glance_http.py0000664000175000017500000003220112161375672023267 0ustar chuckchuck00000000000000# Copyright 2012 OpenStack LLC. # 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. # Originally copied from python-glanceclient import copy import hashlib import httplib import json import posixpath import re import socket import StringIO import struct import urlparse # Python 2.5 compat fix if not hasattr(urlparse, 'parse_qsl'): import cgi urlparse.parse_qsl = cgi.parse_qsl import OpenSSL from tempest.common import log as logging from tempest import exceptions as exc LOG = logging.getLogger(__name__) USER_AGENT = 'tempest' CHUNKSIZE = 1024 * 64 # 64kB TOKEN_CHARS_RE = re.compile('^[-A-Za-z0-9+/=]*$') class HTTPClient(object): def __init__(self, endpoint, **kwargs): self.endpoint = endpoint endpoint_parts = self.parse_endpoint(self.endpoint) self.endpoint_scheme = endpoint_parts.scheme self.endpoint_hostname = endpoint_parts.hostname self.endpoint_port = endpoint_parts.port self.endpoint_path = endpoint_parts.path self.connection_class = self.get_connection_class(self.endpoint_scheme) self.connection_kwargs = self.get_connection_kwargs( self.endpoint_scheme, **kwargs) self.auth_token = kwargs.get('token') @staticmethod def parse_endpoint(endpoint): return urlparse.urlparse(endpoint) @staticmethod def get_connection_class(scheme): if scheme == 'https': return VerifiedHTTPSConnection else: return httplib.HTTPConnection @staticmethod def get_connection_kwargs(scheme, **kwargs): _kwargs = {'timeout': float(kwargs.get('timeout', 600))} if scheme == 'https': _kwargs['cacert'] = kwargs.get('cacert', None) _kwargs['cert_file'] = kwargs.get('cert_file', None) _kwargs['key_file'] = kwargs.get('key_file', None) _kwargs['insecure'] = kwargs.get('insecure', False) _kwargs['ssl_compression'] = kwargs.get('ssl_compression', True) return _kwargs def get_connection(self): _class = self.connection_class try: return _class(self.endpoint_hostname, self.endpoint_port, **self.connection_kwargs) except httplib.InvalidURL: raise exc.EndpointNotFound def _http_request(self, url, method, **kwargs): """Send an http request with the specified characteristics. Wrapper around httplib.HTTP(S)Connection.request to handle tasks such as setting headers and error handling. """ # Copy the kwargs so we can reuse the original in case of redirects kwargs['headers'] = copy.deepcopy(kwargs.get('headers', {})) kwargs['headers'].setdefault('User-Agent', USER_AGENT) if self.auth_token: kwargs['headers'].setdefault('X-Auth-Token', self.auth_token) self._log_request(method, url, kwargs['headers']) conn = self.get_connection() try: conn_url = posixpath.normpath('%s/%s' % (self.endpoint_path, url)) if kwargs['headers'].get('Transfer-Encoding') == 'chunked': conn.putrequest(method, conn_url) for header, value in kwargs['headers'].items(): conn.putheader(header, value) conn.endheaders() chunk = kwargs['body'].read(CHUNKSIZE) # Chunk it, baby... while chunk: conn.send('%x\r\n%s\r\n' % (len(chunk), chunk)) chunk = kwargs['body'].read(CHUNKSIZE) conn.send('0\r\n\r\n') else: conn.request(method, conn_url, **kwargs) resp = conn.getresponse() except socket.gaierror as e: message = "Error finding address for %(url)s: %(e)s" % locals() raise exc.EndpointNotFound(message) except (socket.error, socket.timeout) as e: endpoint = self.endpoint message = "Error communicating with %(endpoint)s %(e)s" % locals() raise exc.TimeoutException(message) body_iter = ResponseBodyIterator(resp) # Read body into string if it isn't obviously image data if resp.getheader('content-type', None) != 'application/octet-stream': body_str = ''.join([body_chunk for body_chunk in body_iter]) body_iter = StringIO.StringIO(body_str) self._log_response(resp, None) else: self._log_response(resp, body_iter) return resp, body_iter def _log_request(self, method, url, headers): LOG.info('Request: ' + method + ' ' + url) if headers: headers_out = headers if 'X-Auth-Token' in headers and headers['X-Auth-Token']: token = headers['X-Auth-Token'] if len(token) > 64 and TOKEN_CHARS_RE.match(token): headers_out = headers.copy() headers_out['X-Auth-Token'] = "" LOG.info('Request Headers: ' + str(headers_out)) def _log_response(self, resp, body): status = str(resp.status) LOG.info("Response Status: " + status) if resp.getheaders(): LOG.info('Response Headers: ' + str(resp.getheaders())) if body: str_body = str(body) length = len(body) LOG.info('Response Body: ' + str_body[:2048]) if length >= 2048: self.LOG.debug("Large body (%d) md5 summary: %s", length, hashlib.md5(str_body).hexdigest()) def json_request(self, method, url, **kwargs): kwargs.setdefault('headers', {}) kwargs['headers'].setdefault('Content-Type', 'application/json') if 'body' in kwargs: kwargs['body'] = json.dumps(kwargs['body']) resp, body_iter = self._http_request(url, method, **kwargs) if 'application/json' in resp.getheader('content-type', None): body = ''.join([chunk for chunk in body_iter]) try: body = json.loads(body) except ValueError: LOG.error('Could not decode response body as JSON') else: body = None return resp, body def raw_request(self, method, url, **kwargs): kwargs.setdefault('headers', {}) kwargs['headers'].setdefault('Content-Type', 'application/octet-stream') if 'body' in kwargs: if (hasattr(kwargs['body'], 'read') and method.lower() in ('post', 'put')): # We use 'Transfer-Encoding: chunked' because # body size may not always be known in advance. kwargs['headers']['Transfer-Encoding'] = 'chunked' return self._http_request(url, method, **kwargs) class OpenSSLConnectionDelegator(object): """ An OpenSSL.SSL.Connection delegator. Supplies an additional 'makefile' method which httplib requires and is not present in OpenSSL.SSL.Connection. Note: Since it is not possible to inherit from OpenSSL.SSL.Connection a delegator must be used. """ def __init__(self, *args, **kwargs): self.connection = OpenSSL.SSL.Connection(*args, **kwargs) def __getattr__(self, name): return getattr(self.connection, name) def makefile(self, *args, **kwargs): return socket._fileobject(self.connection, *args, **kwargs) class VerifiedHTTPSConnection(httplib.HTTPSConnection): """ Extended HTTPSConnection which uses the OpenSSL library for enhanced SSL support. Note: Much of this functionality can eventually be replaced with native Python 3.3 code. """ def __init__(self, host, port=None, key_file=None, cert_file=None, cacert=None, timeout=None, insecure=False, ssl_compression=True): httplib.HTTPSConnection.__init__(self, host, port, key_file=key_file, cert_file=cert_file) self.key_file = key_file self.cert_file = cert_file self.timeout = timeout self.insecure = insecure self.ssl_compression = ssl_compression self.cacert = cacert self.setcontext() @staticmethod def host_matches_cert(host, x509): """ Verify that the the x509 certificate we have received from 'host' correctly identifies the server we are connecting to, ie that the certificate's Common Name or a Subject Alternative Name matches 'host'. """ # First see if we can match the CN if x509.get_subject().commonName == host: return True # Also try Subject Alternative Names for a match san_list = None for i in xrange(x509.get_extension_count()): ext = x509.get_extension(i) if ext.get_short_name() == 'subjectAltName': san_list = str(ext) for san in ''.join(san_list.split()).split(','): if san == "DNS:%s" % host: return True # Server certificate does not match host msg = ('Host "%s" does not match x509 certificate contents: ' 'CommonName "%s"' % (host, x509.get_subject().commonName)) if san_list is not None: msg = msg + ', subjectAltName "%s"' % san_list raise exc.SSLCertificateError(msg) def verify_callback(self, connection, x509, errnum, depth, preverify_ok): if x509.has_expired(): msg = "SSL Certificate expired on '%s'" % x509.get_notAfter() raise exc.SSLCertificateError(msg) if depth == 0 and preverify_ok is True: # We verify that the host matches against the last # certificate in the chain return self.host_matches_cert(self.host, x509) else: # Pass through OpenSSL's default result return preverify_ok def setcontext(self): """ Set up the OpenSSL context. """ self.context = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD) if self.ssl_compression is False: self.context.set_options(0x20000) # SSL_OP_NO_COMPRESSION if self.insecure is not True: self.context.set_verify(OpenSSL.SSL.VERIFY_PEER, self.verify_callback) else: self.context.set_verify(OpenSSL.SSL.VERIFY_NONE, self.verify_callback) if self.cert_file: try: self.context.use_certificate_file(self.cert_file) except Exception, e: msg = 'Unable to load cert from "%s" %s' % (self.cert_file, e) raise exc.SSLConfigurationError(msg) if self.key_file is None: # We support having key and cert in same file try: self.context.use_privatekey_file(self.cert_file) except Exception, e: msg = ('No key file specified and unable to load key ' 'from "%s" %s' % (self.cert_file, e)) raise exc.SSLConfigurationError(msg) if self.key_file: try: self.context.use_privatekey_file(self.key_file) except Exception, e: msg = 'Unable to load key from "%s" %s' % (self.key_file, e) raise exc.SSLConfigurationError(msg) if self.cacert: try: self.context.load_verify_locations(self.cacert) except Exception, e: msg = 'Unable to load CA from "%s"' % (self.cacert, e) raise exc.SSLConfigurationError(msg) else: self.context.set_default_verify_paths() def connect(self): """ Connect to an SSL port using the OpenSSL library and apply per-connection parameters. """ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if self.timeout is not None: # '0' microseconds sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, struct.pack('LL', self.timeout, 0)) self.sock = OpenSSLConnectionDelegator(self.context, sock) self.sock.connect((self.host, self.port)) class ResponseBodyIterator(object): """A class that acts as an iterator over an HTTP response.""" def __init__(self, resp): self.resp = resp def __iter__(self): while True: yield self.next() def next(self): chunk = self.resp.read(CHUNKSIZE) if chunk: return chunk else: raise StopIteration() tempest-2013.2.a1291.g23a1b4f/tempest/common/__init__.py0000664000175000017500000000000012161375672022526 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/manager.py0000664000175000017500000001437612161375672021136 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.common import log as logging import tempest.config from tempest import exceptions # Tempest REST Fuzz testing client libs from tempest.services.compute.json import extensions_client from tempest.services.compute.json import flavors_client from tempest.services.compute.json import floating_ips_client from tempest.services.compute.json import hypervisor_client from tempest.services.compute.json import images_client from tempest.services.compute.json import keypairs_client from tempest.services.compute.json import limits_client from tempest.services.compute.json import quotas_client from tempest.services.compute.json import security_groups_client from tempest.services.compute.json import servers_client from tempest.services.compute.json import volumes_extensions_client from tempest.services.network.json import network_client from tempest.services.volume.json import snapshots_client from tempest.services.volume.json import volumes_client NetworkClient = network_client.NetworkClient ImagesClient = images_client.ImagesClientJSON FlavorsClient = flavors_client.FlavorsClientJSON ServersClient = servers_client.ServersClientJSON LimitsClient = limits_client.LimitsClientJSON ExtensionsClient = extensions_client.ExtensionsClientJSON FloatingIPsClient = floating_ips_client.FloatingIPsClientJSON SecurityGroupsClient = security_groups_client.SecurityGroupsClientJSON KeyPairsClient = keypairs_client.KeyPairsClientJSON VolumesExtensionsClient = volumes_extensions_client.VolumesExtensionsClientJSON VolumesClient = volumes_client.VolumesClientJSON SnapshotsClient = snapshots_client.SnapshotsClientJSON QuotasClient = quotas_client.QuotasClientJSON HypervisorClient = hypervisor_client.HypervisorClientJSON LOG = logging.getLogger(__name__) class Manager(object): """ Base manager class Manager objects are responsible for providing a configuration object and a client object for a test case to use in performing actions. """ def __init__(self): self.config = tempest.config.TempestConfig() self.client_attr_names = [] class FuzzClientManager(Manager): """ Manager class that indicates the client provided by the manager is a fuzz-testing client that Tempest contains. These fuzz-testing clients are used to be able to throw random or invalid data at an endpoint and check for appropriate error messages returned from the endpoint. """ pass class ComputeFuzzClientManager(FuzzClientManager): """ Manager that uses the Tempest REST client that can send random or invalid data at the OpenStack Compute API """ def __init__(self, username=None, password=None, tenant_name=None): """ We allow overriding of the credentials used within the various client classes managed by the Manager object. Left as None, the standard username/password/tenant_name is used. :param username: Override of the username :param password: Override of the password :param tenant_name: Override of the tenant name """ super(ComputeFuzzClientManager, self).__init__() # If no creds are provided, we fall back on the defaults # in the config file for the Compute API. username = username or self.config.identity.username password = password or self.config.identity.password tenant_name = tenant_name or self.config.identity.tenant_name if None in (username, password, tenant_name): msg = ("Missing required credentials. " "username: %(username)s, password: %(password)s, " "tenant_name: %(tenant_name)s") % locals() raise exceptions.InvalidConfiguration(msg) auth_url = self.config.identity.uri # Ensure /tokens is in the URL for Keystone... if 'tokens' not in auth_url: auth_url = auth_url.rstrip('/') + '/tokens' client_args = (self.config, username, password, auth_url, tenant_name) self.servers_client = ServersClient(*client_args) self.flavors_client = FlavorsClient(*client_args) self.images_client = ImagesClient(*client_args) self.limits_client = LimitsClient(*client_args) self.extensions_client = ExtensionsClient(*client_args) self.keypairs_client = KeyPairsClient(*client_args) self.security_groups_client = SecurityGroupsClient(*client_args) self.floating_ips_client = FloatingIPsClient(*client_args) self.volumes_extensions_client = VolumesExtensionsClient(*client_args) self.volumes_client = VolumesClient(*client_args) self.snapshots_client = SnapshotsClient(*client_args) self.quotas_client = QuotasClient(*client_args) self.network_client = NetworkClient(*client_args) self.hypervisor_client = HypervisorClient(*client_args) class ComputeFuzzClientAltManager(Manager): """ Manager object that uses the alt_XXX credentials for its managed client objects """ def __init__(self): conf = tempest.config.TempestConfig() super(ComputeFuzzClientAltManager, self).__init__( conf.identity.alt_username, conf.identity.alt_password, conf.identity.alt_tenant_name) class ComputeFuzzClientAdminManager(Manager): """ Manager object that uses the alt_XXX credentials for its managed client objects """ def __init__(self): conf = tempest.config.TempestConfig() super(ComputeFuzzClientAdminManager, self).__init__( conf.compute_admin.username, conf.compute_admin.password, conf.compute_admin.tenant_name) tempest-2013.2.a1291.g23a1b4f/tempest/stress/0000775000175000017500000000000012161375700020452 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/stress/actions/0000775000175000017500000000000012161375700022112 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/stress/actions/create_destroy_server.py0000664000175000017500000000264512161375672027105 0ustar chuckchuck00000000000000# Copyright 2013 Quanta Research Cambridge, Inc. # # 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 tempest.common.utils.data_utils import rand_name def create_destroy(manager, logger): image = manager.config.compute.image_ref flavor = manager.config.compute.flavor_ref while True: name = rand_name("instance") logger.info("creating %s" % name) resp, server = manager.servers_client.create_server( name, image, flavor) server_id = server['id'] assert(resp.status == 202) manager.servers_client.wait_for_server_status(server_id, 'ACTIVE') logger.info("created %s" % server_id) logger.info("deleting %s" % name) resp, _ = manager.servers_client.delete_server(server_id) assert(resp.status == 204) manager.servers_client.wait_for_server_termination(server_id) logger.info("deleted %s" % server_id) tempest-2013.2.a1291.g23a1b4f/tempest/stress/actions/volume_create_delete.py0000664000175000017500000000255612161375672026660 0ustar chuckchuck00000000000000# 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 tempest.common.utils.data_utils import rand_name def create_delete(manager, logger): while True: name = rand_name("volume") logger.info("creating %s" % name) resp, volume = manager.volumes_client.create_volume(size=1, display_name=name) assert(resp.status == 200) manager.volumes_client.wait_for_volume_status(volume['id'], 'available') logger.info("created %s" % volume['id']) logger.info("deleting %s" % name) resp, _ = manager.volumes_client.delete_volume(volume['id']) assert(resp.status == 202) manager.volumes_client.wait_for_resource_deletion(volume['id']) logger.info("deleted %s" % volume['id']) tempest-2013.2.a1291.g23a1b4f/tempest/stress/actions/__init__.py0000664000175000017500000000115612161375672024236 0ustar chuckchuck00000000000000# Copyright 2013 Quanta Research Cambridge, Inc. # # 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. tempest-2013.2.a1291.g23a1b4f/tempest/stress/tools/0000775000175000017500000000000012161375700021612 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/stress/tools/cleanup.py0000775000175000017500000000127512161375672023633 0ustar chuckchuck00000000000000#!/usr/bin/env python # Copyright 2013 Quanta Research Cambridge, Inc. # # 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 tempest.stress import cleanup cleanup.cleanup() tempest-2013.2.a1291.g23a1b4f/tempest/stress/cleanup.py0000664000175000017500000000454512161375672022473 0ustar chuckchuck00000000000000#!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 Quanta Research Cambridge, Inc. # # 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 tempest import clients def cleanup(): admin_manager = clients.AdminManager() _, body = admin_manager.servers_client.list_servers({"all_tenants": True}) for s in body['servers']: try: admin_manager.servers_client.delete_server(s['id']) except Exception: pass for s in body['servers']: try: admin_manager.servers_client.wait_for_server_termination(s['id']) except Exception: pass _, keypairs = admin_manager.keypairs_client.list_keypairs() for k in keypairs: try: admin_manager.keypairs_client.delete_keypair(k['name']) except Exception: pass _, floating_ips = admin_manager.floating_ips_client.list_floating_ips() for f in floating_ips: try: admin_manager.floating_ips_client.delete_floating_ip(f['id']) except Exception: pass _, users = admin_manager.identity_client.get_users() for user in users: if user['name'].startswith("stress_user"): admin_manager.identity_client.delete_user(user['id']) _, tenants = admin_manager.identity_client.list_tenants() for tenant in tenants: if tenant['name'].startswith("stress_tenant"): admin_manager.identity_client.delete_tenant(tenant['id']) _, vols = admin_manager.volumes_client.list_volumes({"all_tenants": True}) for v in vols: try: admin_manager.volumes_client.delete_volume(v['id']) except Exception: pass for v in vols: try: admin_manager.volumes_client.wait_for_resource_deletion(v['id']) except Exception: pass tempest-2013.2.a1291.g23a1b4f/tempest/stress/run_stress.py0000775000175000017500000000216612161375672023253 0ustar chuckchuck00000000000000#!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 Quanta Research Cambridge, Inc. # # 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 argparse import json from tempest.stress import driver def main(ns): tests = json.load(open(ns.tests, 'r')) driver.stress_openstack(tests, ns.duration) parser = argparse.ArgumentParser(description='Run stress tests. ') parser.add_argument('-d', '--duration', default=300, type=int, help="Duration of test.") parser.add_argument('tests', help="Name of the file with test description.") main(parser.parse_args()) tempest-2013.2.a1291.g23a1b4f/tempest/stress/driver.py0000664000175000017500000001260212161375672022330 0ustar chuckchuck00000000000000# Copyright 2013 Quanta Research Cambridge, Inc. # # 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 importlib import logging import multiprocessing import time from tempest import clients from tempest.common import ssh from tempest.common.utils.data_utils import rand_name from tempest import exceptions from tempest.stress import cleanup admin_manager = clients.AdminManager() # setup logging to file logging.basicConfig( format='%(asctime)s %(process)d %(name)-20s %(levelname)-8s %(message)s', datefmt='%m-%d %H:%M:%S', filename="stress.debug.log", filemode="w", level=logging.DEBUG, ) # define a Handler which writes INFO messages or higher to the sys.stdout _console = logging.StreamHandler() _console.setLevel(logging.INFO) # set a format which is simpler for console use format_str = '%(asctime)s %(process)d %(name)-20s: %(levelname)-8s %(message)s' _formatter = logging.Formatter(format_str) # tell the handler to use this format _console.setFormatter(_formatter) # add the handler to the root logger logger = logging.getLogger('tempest.stress') logger.addHandler(_console) def do_ssh(command, host): username = admin_manager.config.stress.target_ssh_user key_filename = admin_manager.config.stress.target_private_key_path if not (username and key_filename): return None ssh_client = ssh.Client(host, username, key_filename=key_filename) try: return ssh_client.exec_command(command) except exceptions.SSHExecCommandFailed: return None def _get_compute_nodes(controller): """ Returns a list of active compute nodes. List is generated by running nova-manage on the controller. """ nodes = [] cmd = "nova-manage service list | grep ^nova-compute" output = do_ssh(cmd, controller) if not output: return nodes # For example: nova-compute xg11eth0 nova enabled :-) 2011-10-31 18:57:46 # This is fragile but there is, at present, no other way to get this info. for line in output.split('\n'): words = line.split() if len(words) > 0 and words[4] == ":-)": nodes.append(words[1]) return nodes def _error_in_logs(logfiles, nodes): """ Detect errors in the nova log files on the controller and compute nodes. """ grep = 'egrep "ERROR|TRACE" %s' % logfiles for node in nodes: errors = do_ssh(grep, node) if not errors: return None if len(errors) > 0: logger.error('%s: %s' % (node, errors)) return errors return None def get_action_function(path): (module_part, _, function) = path.rpartition('.') return getattr(importlib.import_module(module_part), function) def stress_openstack(tests, duration): """ Workload driver. Executes an action function against a nova-cluster. """ logfiles = admin_manager.config.stress.target_logfiles log_check_interval = int(admin_manager.config.stress.log_check_interval) if logfiles: controller = admin_manager.config.stress.target_controller computes = _get_compute_nodes(controller) for node in computes: do_ssh("rm -f %s" % logfiles, node) processes = [] for test in tests: if test.get('use_admin', False): manager = admin_manager else: manager = clients.Manager() for _ in xrange(test.get('threads', 1)): if test.get('use_isolated_tenants', False): username = rand_name("stress_user") tenant_name = rand_name("stress_tenant") password = "pass" identity_client = admin_manager.identity_client _, tenant = identity_client.create_tenant(name=tenant_name) identity_client.create_user(username, password, tenant['id'], "email") manager = clients.Manager(username=username, password="pass", tenant_name=tenant_name) target = get_action_function(test['action']) p = multiprocessing.Process(target=target, args=(manager, logger), kwargs=test.get('kwargs', {})) processes.append(p) p.start() end_time = time.time() + duration had_errors = False while True: remaining = end_time - time.time() if remaining <= 0: break time.sleep(min(remaining, log_check_interval)) if not logfiles: continue errors = _error_in_logs(logfiles, computes) if errors: had_errors = True break for p in processes: p.terminate() if not had_errors: logger.info("cleaning up") cleanup.cleanup() tempest-2013.2.a1291.g23a1b4f/tempest/stress/etc/0000775000175000017500000000000012161375700021225 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/stress/etc/volume-create-delete-test.json0000664000175000017500000000024512161375672027116 0ustar chuckchuck00000000000000[{"action": "tempest.stress.actions.volume_create_delete.create_delete", "threads": 4, "use_admin": false, "use_isolated_tenants": false, "kwargs": {} } ] tempest-2013.2.a1291.g23a1b4f/tempest/stress/etc/sample-test.json0000664000175000017500000000024712161375672024371 0ustar chuckchuck00000000000000[{"action": "tempest.stress.actions.create_destroy_server.create_destroy", "threads": 8, "use_admin": false, "use_isolated_tenants": false, "kwargs": {} } ] tempest-2013.2.a1291.g23a1b4f/tempest/stress/README.rst0000664000175000017500000000327212161375672022155 0ustar chuckchuck00000000000000Tempest Field Guide to Stress Tests =================================== Nova is a distributed, asynchronous system that is prone to race condition bugs. These bugs will not be easily found during functional testing but will be encountered by users in large deployments in a way that is hard to debug. The stress test tries to cause these bugs to happen in a more controlled environment. Environment ----------- This particular framework assumes your working Nova cluster understands Nova API 2.0. The stress tests can read the logs from the cluster. To enable this you have to provide the hostname to call 'nova-manage' and the private key and user name for ssh to the cluster in the [stress] section of tempest.conf. You also need to provide the location of the log files: target_logfiles = "regexp to all log files to be checked for errors" target_private_key_path = "private ssh key for controller and log file nodes" target_ssh_user = "username for controller and log file nodes" target_controller = "hostname or ip of controller node (for nova-manage) log_check_interval = "time between checking logs for errors (default 60s)" Running the sample test ----------------------- To test installation, do the following (from the tempest/stress directory): ./run_stress.py etc/sample-test.json -d 30 This sample test tries to create a few VMs and kill a few VMs. Additional Tools ---------------- Sometimes the tests don't finish, or there are failures. In these cases, you may want to clean out the nova cluster. We have provided some scripts to do this in the ``tools`` subdirectory. You can use the following script to destroy any keypairs, floating ips, and servers: tempest/stress/tools/cleanup.py tempest-2013.2.a1291.g23a1b4f/tempest/stress/__init__.py0000664000175000017500000000115612161375672022576 0ustar chuckchuck00000000000000# Copyright 2013 Quanta Research Cambridge, Inc. # # 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. tempest-2013.2.a1291.g23a1b4f/tempest/exceptions.py0000664000175000017500000001172112161375672021674 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 testtools class TempestException(Exception): """ Base Tempest Exception To correctly use this class, inherit from it and define a 'message' property. That message will get printf'd with the keyword arguments provided to the constructor. """ message = "An unknown exception occurred" def __init__(self, *args, **kwargs): super(TempestException, self).__init__() try: self._error_string = self.message % kwargs except Exception: # at least get the core message out if something happened self._error_string = self.message if len(args) > 0: # If there is a non-kwarg parameter, assume it's the error # message or reason description and tack it on to the end # of the exception message # Convert all arguments into their string representations... args = ["%s" % arg for arg in args] self._error_string = (self._error_string + "\nDetails: %s" % '\n'.join(args)) def __str__(self): return self._error_string class InvalidConfiguration(TempestException): message = "Invalid Configuration" class RestClientException(TempestException, testtools.TestCase.failureException): pass class NotFound(RestClientException): message = "Object not found" class Unauthorized(RestClientException): message = 'Unauthorized' class TimeoutException(TempestException): message = "Request timed out" class BuildErrorException(TempestException): message = "Server %(server_id)s failed to build and is in ERROR status" class AddImageException(TempestException): message = "Image %(image_id)s failed to become ACTIVE in the allotted time" class EC2RegisterImageException(TempestException): message = ("Image %(image_id)s failed to become 'available' " "in the allotted time") class VolumeBuildErrorException(TempestException): message = "Volume %(volume_id)s failed to build and is in ERROR status" class SnapshotBuildErrorException(TempestException): message = "Snapshot %(snapshot_id)s failed to build and is in ERROR status" class StackBuildErrorException(TempestException): message = ("Stack %(stack_identifier)s is in %(stack_status)s status " "due to '%(stack_status_reason)s'") class BadRequest(RestClientException): message = "Bad request" class UnprocessableEntity(RestClientException): message = "Unprocessable entity" class AuthenticationFailure(RestClientException): message = ("Authentication with user %(user)s and password " "%(password)s failed") class EndpointNotFound(TempestException): message = "Endpoint not found" class RateLimitExceeded(TempestException): message = ("Rate limit exceeded.\nMessage: %(message)s\n" "Details: %(details)s") class OverLimit(TempestException): message = "Quota exceeded" class ComputeFault(TempestException): message = "Got compute fault" class ImageFault(TempestException): message = "Got image fault" class IdentityError(TempestException): message = "Got identity error" class Duplicate(RestClientException): message = "An object with that identifier already exists" class SSHTimeout(TempestException): message = ("Connection to the %(host)s via SSH timed out.\n" "User: %(user)s, Password: %(password)s") class SSHExecCommandFailed(TempestException): """Raised when remotely executed command returns nonzero status.""" message = ("Command '%(command)s', exit status: %(exit_status)d, " "Error:\n%(strerror)s") class ServerUnreachable(TempestException): message = "The server is not reachable via the configured network" class SQLException(TempestException): message = "SQL error: %(message)s" class TearDownException(TempestException): message = "%(num)d cleanUp operation failed" class RFCViolation(RestClientException): message = "RFC Violation" class ResponseWithNonEmptyBody(RFCViolation): message = ("RFC Violation! Response with %(status)d HTTP Status Code " "MUST NOT have a body") class ResponseWithEntity(RFCViolation): message = ("RFC Violation! Response with 205 HTTP Status Code " "MUST NOT have an entity") tempest-2013.2.a1291.g23a1b4f/tempest/cli/0000775000175000017500000000000012161375700017676 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/cli/simple_read_only/0000775000175000017500000000000012161375700023223 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/cli/simple_read_only/test_compute_manage.py0000664000175000017500000000535712161375672027642 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 OpenStack Foundation # 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 logging import subprocess import tempest.cli LOG = logging.getLogger(__name__) class SimpleReadOnlyNovaManageTest(tempest.cli.ClientTestBase): """ This is a first pass at a simple read only nova-manage test. This only exercises client commands that are read only. This should test commands: * with and without optional parameters * initially just check return codes, and later test command outputs """ def test_admin_fake_action(self): self.assertRaises(subprocess.CalledProcessError, self.nova_manage, 'this-does-nova-exist') #NOTE(jogo): Commands in order listed in 'nova-manage -h' # test flags def test_help_flag(self): self.nova_manage('', '-h') def test_version_flag(self): # Bug 1159957: nova-manage --version writes to stderr self.assertNotEqual("", self.nova_manage('', '--version', merge_stderr=True)) self.assertEqual(self.nova_manage('version'), self.nova_manage('', '--version', merge_stderr=True)) def test_debug_flag(self): self.assertNotEqual("", self.nova_manage('instance_type list', '--debug')) def test_verbose_flag(self): self.assertNotEqual("", self.nova_manage('instance_type list', '--verbose')) # test actions def test_version(self): self.assertNotEqual("", self.nova_manage('version')) def test_flavor_list(self): self.assertNotEqual("", self.nova_manage('flavor list')) self.assertEqual(self.nova_manage('instance_type list'), self.nova_manage('flavor list')) def test_db_archive_deleted_rows(self): # make sure command doesn't error out self.nova_manage('db archive_deleted_rows 50') def test_db_sync(self): # make sure command doesn't error out self.nova_manage('db sync') def test_db_version(self): self.assertNotEqual("", self.nova_manage('db version')) tempest-2013.2.a1291.g23a1b4f/tempest/cli/simple_read_only/test_keystone.py0000664000175000017500000001051312161375672026505 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 OpenStack Foundation # 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 logging import re import subprocess import tempest.cli LOG = logging.getLogger(__name__) class SimpleReadOnlyKeystoneClientTest(tempest.cli.ClientTestBase): """Basic, read-only tests for Keystone CLI client. Checks return values and output of read-only commands. These tests do not presume any content, nor do they create their own. They only verify the structure of output if present. """ def test_admin_fake_action(self): self.assertRaises(subprocess.CalledProcessError, self.keystone, 'this-does-not-exist') def test_admin_catalog_list(self): out = self.keystone('catalog') catalog = self.parser.details_multiple(out, with_label=True) for svc in catalog: self.assertTrue(svc['__label'].startswith('Service:')) def test_admin_endpoint_list(self): out = self.keystone('endpoint-list') endpoints = self.parser.listing(out) self.assertTableStruct(endpoints, [ 'id', 'region', 'publicurl', 'internalurl', 'adminurl', 'service_id']) def test_admin_endpoint_service_match(self): endpoints = self.parser.listing(self.keystone('endpoint-list')) services = self.parser.listing(self.keystone('service-list')) svc_by_id = {} for svc in services: svc_by_id[svc['id']] = svc for endpoint in endpoints: self.assertIn(endpoint['service_id'], svc_by_id) def test_admin_role_list(self): roles = self.parser.listing(self.keystone('role-list')) self.assertTableStruct(roles, ['id', 'name']) def test_admin_service_list(self): services = self.parser.listing(self.keystone('service-list')) self.assertTableStruct(services, ['id', 'name', 'type', 'description']) def test_admin_tenant_list(self): tenants = self.parser.listing(self.keystone('tenant-list')) self.assertTableStruct(tenants, ['id', 'name', 'enabled']) def test_admin_user_list(self): users = self.parser.listing(self.keystone('user-list')) self.assertTableStruct(users, [ 'id', 'name', 'enabled', 'email']) def test_admin_user_role_list(self): user_roles = self.parser.listing(self.keystone('user-role-list')) self.assertTableStruct(user_roles, [ 'id', 'name', 'user_id', 'tenant_id']) def test_admin_discover(self): discovered = self.keystone('discover') self.assertIn('Keystone found at http', discovered) self.assertIn('supports version', discovered) def test_admin_help(self): help_text = self.keystone('help') lines = help_text.split('\n') self.assertTrue(lines[0].startswith('usage: keystone')) commands = [] cmds_start = lines.index('Positional arguments:') cmds_end = lines.index('Optional arguments:') command_pattern = re.compile('^ {4}([a-z0-9\-\_]+)') for line in lines[cmds_start:cmds_end]: match = command_pattern.match(line) if match: commands.append(match.group(1)) commands = set(commands) wanted_commands = set(('catalog', 'endpoint-list', 'help', 'token-get', 'discover', 'bootstrap')) self.assertFalse(wanted_commands - commands) def test_admin_bashcompletion(self): self.keystone('bash-completion') # Optional arguments: def test_admin_version(self): self.keystone('', flags='--version') def test_admin_debug_list(self): self.keystone('catalog', flags='--debug') def test_admin_timeout(self): self.keystone('catalog', flags='--timeout 15') tempest-2013.2.a1291.g23a1b4f/tempest/cli/simple_read_only/test_compute.py0000664000175000017500000001213112161375672026316 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 OpenStack Foundation # 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 logging import subprocess from oslo.config import cfg import testtools import tempest.cli CONF = cfg.CONF LOG = logging.getLogger(__name__) class SimpleReadOnlyNovaClientTest(tempest.cli.ClientTestBase): """ This is a first pass at a simple read only python-novaclient test. This only exercises client commands that are read only. This should test commands: * as a regular user * as a admin user * with and without optional parameters * initially just check return codes, and later test command outputs """ def test_admin_fake_action(self): self.assertRaises(subprocess.CalledProcessError, self.nova, 'this-does-nova-exist') #NOTE(jogo): Commands in order listed in 'nova help' # Positional arguments: def test_admin_absolute_limites(self): self.nova('absolute-limits') self.nova('absolute-limits', params='--reserved') def test_admin_aggregate_list(self): self.nova('aggregate-list') def test_admin_availability_zone_list(self): self.assertIn("internal", self.nova('availability-zone-list')) def test_admin_cloudpipe_list(self): self.nova('cloudpipe-list') def test_admin_credentials(self): self.nova('credentials') def test_admin_dns_domains(self): self.nova('dns-domains') @testtools.skip("Test needs parameters, Bug #1157349") def test_admin_dns_list(self): self.nova('dns-list') def test_admin_endpoints(self): self.nova('endpoints') def test_admin_flavor_acces_list(self): self.assertRaises(subprocess.CalledProcessError, self.nova, 'flavor-access-list') # Failed to get access list for public flavor type self.assertRaises(subprocess.CalledProcessError, self.nova, 'flavor-access-list', params='--flavor m1.tiny') def test_admin_flavor_list(self): self.assertIn("Memory_MB", self.nova('flavor-list')) def test_admin_floating_ip_bulk_list(self): self.nova('floating-ip-bulk-list') def test_admin_floating_ip_list(self): self.nova('floating-ip-list') def test_admin_floating_ip_pool_list(self): self.nova('floating-ip-pool-list') def test_admin_host_list(self): self.nova('host-list') def test_admin_hypervisor_list(self): self.nova('hypervisor-list') def test_admin_image_list(self): self.nova('image-list') @testtools.skip("Test needs parameters, Bug #1157349") def test_admin_interface_list(self): self.nova('interface-list') def test_admin_keypair_list(self): self.nova('keypair-list') def test_admin_list(self): self.nova('list') self.nova('list', params='--all-tenants 1') self.nova('list', params='--all-tenants 0') self.assertRaises(subprocess.CalledProcessError, self.nova, 'list', params='--all-tenants bad') def test_admin_network_list(self): self.nova('network-list') def test_admin_rate_limits(self): self.nova('rate-limits') def test_admin_secgroup_list(self): self.nova('secgroup-list') @testtools.skip("Test needs parameters, Bug #1157349") def test_admin_secgroup_list_rules(self): self.nova('secgroup-list-rules') def test_admin_servce_list(self): self.nova('service-list') def test_admin_usage(self): self.nova('usage') def test_admin_usage_list(self): self.nova('usage-list') def test_admin_volume_list(self): self.nova('volume-list') def test_admin_volume_snapshot_list(self): self.nova('volume-snapshot-list') def test_admin_volume_type_list(self): self.nova('volume-type-list') def test_admin_help(self): self.nova('help') def test_admin_list_extensions(self): self.nova('list-extensions') def test_admin_net_list(self): self.nova('net-list') # Optional arguments: def test_admin_version(self): self.nova('', flags='--version') def test_admin_debug_list(self): self.nova('list', flags='--debug') def test_admin_timeout(self): self.nova('list', flags='--timeout 2') def test_admin_timing(self): self.nova('list', flags='--timing') tempest-2013.2.a1291.g23a1b4f/tempest/cli/simple_read_only/README.txt0000664000175000017500000000010112161375672024721 0ustar chuckchuck00000000000000This directory consists of simple read only python client tests. tempest-2013.2.a1291.g23a1b4f/tempest/cli/simple_read_only/test_glance.py0000664000175000017500000000461212161375672026100 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 OpenStack Foundation # 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 logging import re import subprocess import tempest.cli LOG = logging.getLogger(__name__) class SimpleReadOnlyGlanceClientTest(tempest.cli.ClientTestBase): """Basic, read-only tests for Glance CLI client. Checks return values and output of read-only commands. These tests do not presume any content, nor do they create their own. They only verify the structure of output if present. """ def test_glance_fake_action(self): self.assertRaises(subprocess.CalledProcessError, self.glance, 'this-does-not-exist') def test_glance_image_list(self): out = self.glance('image-list') endpoints = self.parser.listing(out) self.assertTableStruct(endpoints, [ 'ID', 'Name', 'Disk Format', 'Container Format', 'Size', 'Status']) def test_glance_help(self): help_text = self.glance('help') lines = help_text.split('\n') self.assertTrue(lines[0].startswith('usage: glance')) commands = [] cmds_start = lines.index('Positional arguments:') cmds_end = lines.index('Optional arguments:') command_pattern = re.compile('^ {4}([a-z0-9\-\_]+)') for line in lines[cmds_start:cmds_end]: match = command_pattern.match(line) if match: commands.append(match.group(1)) commands = set(commands) wanted_commands = set(('image-create', 'image-delete', 'help', 'image-download', 'image-show', 'image-update', 'member-add', 'member-create', 'member-delete', 'member-list')) self.assertFalse(wanted_commands - commands) tempest-2013.2.a1291.g23a1b4f/tempest/cli/simple_read_only/__init__.py0000664000175000017500000000000012161375672025332 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/cli/README.rst0000664000175000017500000000303612161375672021377 0ustar chuckchuck00000000000000Tempest Guide to CLI tests ========================== What are these tests? --------------------- The cli tests test the various OpenStack command line interface tools to ensure that they minimally function. The current scope is read only operations on a cloud that are hard to test via unit tests. Why are these tests in tempest? ------------------------------- These tests exist here because it is extremely difficult to build a functional enough environment in the python-*client unit tests to provide this kind of testing. Because we already put up a cloud in the gate with devstack + tempest it was decided it was better to have these as a side tree in tempest instead of another QA effort which would split review time. Scope of these tests -------------------- This should stay limited to the scope of testing the cli. Functional testing of the cloud should be elsewhere, this is about exercising the cli code. Example of a good test ---------------------- Tests should be isolated to a single command in one of the python clients. Tests should not modify the cloud. If a test is validating the cli for bad data, it should do it with assertRaises. A reasonable example of an existing test is as follows: def test_admin_list(self): self.nova('list') self.nova('list', params='--all-tenants 1') self.nova('list', params='--all-tenants 0') self.assertRaises(subprocess.CalledProcessError, self.nova, 'list', params='--all-tenants bad') tempest-2013.2.a1291.g23a1b4f/tempest/cli/output_parser.py0000664000175000017500000001113612161375672023176 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 OpenStack Foundation # 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. """Collection of utilities for parsing CLI clients output.""" import logging import re LOG = logging.getLogger(__name__) delimiter_line = re.compile('^\+\-[\+\-]+\-\+$') def details_multiple(output_lines, with_label=False): """Return list of dicts with item details from cli output tables. If with_label is True, key '__label' is added to each items dict. For more about 'label' see OutputParser.tables(). """ items = [] tables_ = tables(output_lines) for table_ in tables_: if 'Property' not in table_['headers'] \ or 'Value' not in table_['headers']: raise Exception('Invalid structure of table with details') item = {} for value in table_['values']: item[value[0]] = value[1] if with_label: item['__label'] = table_['label'] items.append(item) return items def details(output_lines, with_label=False): """Return dict with details of first item (table) found in output.""" items = details_multiple(output_lines, with_label) return items[0] def listing(output_lines): """Return list of dicts with basic item info parsed from cli output. """ items = [] table_ = table(output_lines) for row in table_['values']: item = {} for col_idx, col_key in enumerate(table_['headers']): item[col_key] = row[col_idx] items.append(item) return items def tables(output_lines): """Find all ascii-tables in output and parse them. Return list of tables parsed from cli output as dicts. (see OutputParser.table()) And, if found, label key (separated line preceding the table) is added to each tables dict. """ tables_ = [] table_ = [] label = None start = False header = False if not isinstance(output_lines, list): output_lines = output_lines.split('\n') for line in output_lines: if delimiter_line.match(line): if not start: start = True elif not header: # we are after head area header = True else: # table ends here start = header = None table_.append(line) parsed = table(table_) parsed['label'] = label tables_.append(parsed) table_ = [] label = None continue if start: table_.append(line) else: if label is None: label = line else: LOG.warn('Invalid line between tables: %s' % line) if len(table_) > 0: LOG.warn('Missing end of table') return tables_ def table(output_lines): """Parse single table from cli output. Return dict with list of column names in 'headers' key and rows in 'values' key. """ table_ = {'headers': [], 'values': []} columns = None if not isinstance(output_lines, list): output_lines = output_lines.split('\n') for line in output_lines: if delimiter_line.match(line): columns = _table_columns(line) continue if '|' not in line: LOG.warn('skipping invalid table line: %s' % line) continue row = [] for col in columns: row.append(line[col[0]:col[1]].strip()) if table_['headers']: table_['values'].append(row) else: table_['headers'] = row return table_ def _table_columns(first_table_row): """Find column ranges in output line. Return list of touples (start,end) for each column detected by plus (+) characters in delimiter line. """ positions = [] start = 1 # there is '+' at 0 while start < len(first_table_row): end = first_table_row.find('+', start) if end == -1: break positions.append((start, end)) start = end + 1 return positions tempest-2013.2.a1291.g23a1b4f/tempest/cli/__init__.py0000664000175000017500000001135312161375672022022 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 OpenStack Foundation # 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 logging import shlex import subprocess from oslo.config import cfg import tempest.cli.output_parser import tempest.test LOG = logging.getLogger(__name__) cli_opts = [ cfg.BoolOpt('enabled', default=True, help="enable cli tests"), cfg.StrOpt('cli_dir', default='/usr/local/bin/', help="directory where python client binaries are located"), ] CONF = cfg.CONF cli_group = cfg.OptGroup(name='cli', title="cli Configuration Options") CONF.register_group(cli_group) CONF.register_opts(cli_opts, group=cli_group) class ClientTestBase(tempest.test.BaseTestCase): @classmethod def setUpClass(cls): if not CONF.cli.enabled: msg = "cli testing disabled" raise cls.skipException(msg) cls.identity = cls.config.identity super(ClientTestBase, cls).setUpClass() def __init__(self, *args, **kwargs): self.parser = tempest.cli.output_parser super(ClientTestBase, self).__init__(*args, **kwargs) def nova(self, action, flags='', params='', admin=True, fail_ok=False): """Executes nova command for the given action.""" return self.cmd_with_auth( 'nova', action, flags, params, admin, fail_ok) def nova_manage(self, action, flags='', params='', fail_ok=False, merge_stderr=False): """Executes nova-manage command for the given action.""" return self.cmd( 'nova-manage', action, flags, params, fail_ok, merge_stderr) def keystone(self, action, flags='', params='', admin=True, fail_ok=False): """Executes keystone command for the given action.""" return self.cmd_with_auth( 'keystone', action, flags, params, admin, fail_ok) def glance(self, action, flags='', params='', admin=True, fail_ok=False): """Executes glance command for the given action.""" return self.cmd_with_auth( 'glance', action, flags, params, admin, fail_ok) def cmd_with_auth(self, cmd, action, flags='', params='', admin=True, fail_ok=False): """Executes given command with auth attributes appended.""" #TODO(jogo) make admin=False work creds = ('--os-username %s --os-tenant-name %s --os-password %s ' '--os-auth-url %s ' % (self.identity.admin_username, self.identity.admin_tenant_name, self.identity.admin_password, self.identity.uri)) flags = creds + ' ' + flags return self.cmd(cmd, action, flags, params, fail_ok) def check_output(self, cmd, **kwargs): # substitutes subprocess.check_output which is not in python2.6 kwargs['stdout'] = subprocess.PIPE proc = subprocess.Popen(cmd, **kwargs) output = proc.communicate()[0] if proc.returncode != 0: raise CommandFailed(proc.returncode, cmd, output) return output def cmd(self, cmd, action, flags='', params='', fail_ok=False, merge_stderr=False): """Executes specified command for the given action.""" cmd = ' '.join([CONF.cli.cli_dir + cmd, flags, action, params]) LOG.info("running: '%s'" % cmd) cmd = shlex.split(cmd) try: if merge_stderr: result = self.check_output(cmd, stderr=subprocess.STDOUT) else: with open('/dev/null', 'w') as devnull: result = self.check_output(cmd, stderr=devnull) except subprocess.CalledProcessError, e: LOG.error("command output:\n%s" % e.output) raise return result def assertTableStruct(self, items, field_names): """Verify that all items has keys listed in field_names.""" for item in items: for field in field_names: self.assertIn(field, item) class CommandFailed(subprocess.CalledProcessError): # adds output attribute for python2.6 def __init__(self, returncode, cmd, output): super(CommandFailed, self).__init__(returncode, cmd) self.output = output tempest-2013.2.a1291.g23a1b4f/tempest/openstack/0000775000175000017500000000000012161375700021116 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/openstack/__init__.py0000664000175000017500000000000012161375672023225 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/hacking/0000775000175000017500000000000012161375700020533 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/hacking/checks.py0000664000175000017500000000414712161375672022363 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 IBM Corp. # # 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 re PYTHON_CLIENTS = ['cinder', 'glance', 'keystone', 'nova', 'swift', 'quantum'] SKIP_DECORATOR_RE = re.compile(r'\s*@testtools.skip\((.*)\)') SKIP_STR_RE = re.compile(r'.*Bug #\d+.*') PYTHON_CLIENT_RE = re.compile('import (%s)client' % '|'.join(PYTHON_CLIENTS)) def skip_bugs(physical_line): """Check skip lines for proper bug entries T101: skips must contain "Bug #" """ res = SKIP_DECORATOR_RE.match(physical_line) if res: content = res.group(1) res = SKIP_STR_RE.match(content) if not res: return (physical_line.find(content), 'T101: skips must contain "Bug #"') def import_no_clients_in_api(physical_line, filename): """Check for client imports from tempest/api tests T102: Cannot import OpenStack python clients """ if "tempest/api" in filename: res = PYTHON_CLIENT_RE.match(physical_line) if res: return (physical_line.find(res.group(1)), ("T102: python clients import not allowed" " in tempest/api/* tests")) def import_no_files_in_tests(physical_line, filename): """Check for merges that try to land into tempest/tests T103: tempest/tests directory is deprecated """ if "tempest/tests" in filename: return (0, ("T103: tempest/tests is deprecated")) def factory(register): register(skip_bugs) register(import_no_clients_in_api) register(import_no_files_in_tests) tempest-2013.2.a1291.g23a1b4f/tempest/hacking/__init__.py0000664000175000017500000000000012161375672022642 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/0000775000175000017500000000000012161375700017700 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/utils.py0000664000175000017500000000262712161375672021431 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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. """Common utilities used in testing.""" from testtools import TestCase class skip_unless_attr(object): """Decorator that skips a test if a specified attr exists and is True.""" def __init__(self, attr, msg=None): self.attr = attr self.message = msg or ("Test case attribute %s not found " "or False") % attr def __call__(self, func): def _skipper(*args, **kw): """Wrapped skipper function.""" testobj = args[0] if not getattr(testobj, self.attr, False): raise TestCase.skipException(self.message) func(*args, **kw) _skipper.__name__ = func.__name__ _skipper.__doc__ = func.__doc__ return _skipper tempest-2013.2.a1291.g23a1b4f/tempest/api/object_storage/0000775000175000017500000000000012161375700022672 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/object_storage/test_container_sync.py0000664000175000017500000001176212161375672027340 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 time import testtools from tempest.api.object_storage import base from tempest.common.utils.data_utils import rand_name from tempest.test import attr class ContainerSyncTest(base.BaseObjectTest): @classmethod def setUpClass(cls): super(ContainerSyncTest, cls).setUpClass() cls.containers = [] cls.objects = [] container_sync_timeout = \ int(cls.config.object_storage.container_sync_timeout) cls.container_sync_interval = \ int(cls.config.object_storage.container_sync_interval) cls.attempts = \ int(container_sync_timeout / cls.container_sync_interval) # define container and object clients cls.clients = {} cls.clients[rand_name(name='TestContainerSync')] = \ (cls.container_client, cls.object_client) cls.clients[rand_name(name='TestContainerSync')] = \ (cls.container_client_alt, cls.object_client_alt) for cont_name, client in cls.clients.items(): client[0].create_container(cont_name) cls.containers.append(cont_name) @classmethod def tearDownClass(cls): for client in cls.clients.values(): cls.delete_containers(cls.containers, client[0], client[1]) @testtools.skip('Until Bug #1093743 is resolved.') @attr(type='gate') def test_container_synchronization(self): # container to container synchronization # to allow/accept sync requests to/from other accounts # turn container synchronization on and create object in container for cont in (self.containers, self.containers[::-1]): cont_client = [self.clients[c][0] for c in cont] obj_client = [self.clients[c][1] for c in cont] # tell first container to synchronize to a second headers = {'X-Container-Sync-Key': 'sync_key', 'X-Container-Sync-To': "%s/%s" % (cont_client[1].base_url, str(cont[1]))} resp, body = \ cont_client[0].put(str(cont[0]), body=None, headers=headers) self.assertTrue(resp['status'] in ('202', '201'), 'Error installing X-Container-Sync-To ' 'for the container "%s"' % (cont[0])) # create object in container object_name = rand_name(name='TestSyncObject') data = object_name[::-1] # arbitrary_string() resp, _ = obj_client[0].create_object(cont[0], object_name, data) self.assertEqual(resp['status'], '201', 'Error creating the object "%s" in' 'the container "%s"' % (object_name, cont[0])) self.objects.append(object_name) # wait until container contents list is not empty cont_client = [self.clients[c][0] for c in self.containers] params = {'format': 'json'} while self.attempts > 0: # get first container content resp, object_list_0 = \ cont_client[0].\ list_container_contents(self.containers[0], params=params) self.assertEqual(resp['status'], '200', 'Error listing the destination container`s' ' "%s" contents' % (self.containers[0])) object_list_0 = dict((obj['name'], obj) for obj in object_list_0) # get second container content resp, object_list_1 = \ cont_client[1].\ list_container_contents(self.containers[1], params=params) self.assertEqual(resp['status'], '200', 'Error listing the destination container`s' ' "%s" contents' % (self.containers[1])) object_list_1 = dict((obj['name'], obj) for obj in object_list_1) # check that containers are not empty and have equal keys() # or wait for next attempt if not object_list_0 or not object_list_1 or \ set(object_list_0.keys()) != set(object_list_1.keys()): time.sleep(self.container_sync_interval) self.attempts -= 1 else: break self.assertEqual(object_list_0, object_list_1, 'Different object lists in containers.') tempest-2013.2.a1291.g23a1b4f/tempest/api/object_storage/test_account_services.py0000664000175000017500000000742212161375672027657 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.object_storage import base from tempest.common.utils.data_utils import rand_name from tempest import exceptions from tempest.test import attr class AccountTest(base.BaseObjectTest): @classmethod def setUpClass(cls): super(AccountTest, cls).setUpClass() cls.container_name = rand_name(name='TestContainer') cls.container_client.create_container(cls.container_name) @classmethod def tearDownClass(cls): cls.container_client.delete_container(cls.container_name) @attr(type='smoke') def test_list_containers(self): # list of all containers should not be empty params = {'format': 'json'} resp, container_list = \ self.account_client.list_account_containers(params=params) self.assertIsNotNone(container_list) container_names = [c['name'] for c in container_list] self.assertTrue(self.container_name in container_names) @attr(type='smoke') def test_list_account_metadata(self): # list all account metadata resp, metadata = self.account_client.list_account_metadata() self.assertEqual(resp['status'], '204') self.assertIn('x-account-object-count', resp) self.assertIn('x-account-container-count', resp) self.assertIn('x-account-bytes-used', resp) @attr(type='smoke') def test_create_and_delete_account_metadata(self): header = 'test-account-meta' data = 'Meta!' # add metadata to account resp, _ = self.account_client.create_account_metadata( metadata={header: data}) self.assertEqual(resp['status'], '204') resp, _ = self.account_client.list_account_metadata() self.assertIn('x-account-meta-' + header, resp) self.assertEqual(resp['x-account-meta-' + header], data) # delete metadata from account resp, _ = \ self.account_client.delete_account_metadata(metadata=[header]) self.assertEqual(resp['status'], '204') resp, _ = self.account_client.list_account_metadata() self.assertNotIn('x-account-meta-' + header, resp) @attr(type=['negative', 'gate']) def test_list_containers_with_non_authorized_user(self): # list containers using non-authorized user # create user self.data.setup_test_user() resp, body = \ self.token_client.auth(self.data.test_user, self.data.test_password, self.data.test_tenant) new_token = \ self.token_client.get_token(self.data.test_user, self.data.test_password, self.data.test_tenant) custom_headers = {'X-Auth-Token': new_token} params = {'format': 'json'} # list containers with non-authorized user token self.assertRaises(exceptions.Unauthorized, self.custom_account_client.list_account_containers, params=params, metadata=custom_headers) # delete the user which was created self.data.teardown_all() tempest-2013.2.a1291.g23a1b4f/tempest/api/object_storage/test_object_services.py0000664000175000017500000005254612161375672027500 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 hashlib import time from tempest.api.object_storage import base from tempest.common.utils.data_utils import arbitrary_string from tempest.common.utils.data_utils import rand_name from tempest import exceptions from tempest.test import attr class ObjectTest(base.BaseObjectTest): @classmethod def setUpClass(cls): super(ObjectTest, cls).setUpClass() cls.container_name = rand_name(name='TestContainer') cls.container_client.create_container(cls.container_name) cls.containers = [cls.container_name] cls.data.setup_test_user() resp, body = cls.token_client.auth(cls.data.test_user, cls.data.test_password, cls.data.test_tenant) cls.new_token = cls.token_client.get_token(cls.data.test_user, cls.data.test_password, cls.data.test_tenant) cls.custom_headers = {'X-Auth-Token': cls.new_token} @classmethod def tearDownClass(cls): cls.delete_containers(cls.containers) # delete the user setup created cls.data.teardown_all() @attr(type='smoke') def test_create_object(self): # create object object_name = rand_name(name='TestObject') data = arbitrary_string() resp, _ = self.object_client.create_object(self.container_name, object_name, data) # create another object object_name = rand_name(name='TestObject') data = arbitrary_string() resp, _ = self.object_client.create_object(self.container_name, object_name, data) self.assertEqual(resp['status'], '201') @attr(type='smoke') def test_delete_object(self): # create object object_name = rand_name(name='TestObject') data = arbitrary_string() resp, _ = self.object_client.create_object(self.container_name, object_name, data) # delete object resp, _ = self.object_client.delete_object(self.container_name, object_name) self.assertEqual(resp['status'], '204') @attr(type='smoke') def test_object_metadata(self): # add metadata to storage object, test if metadata is retrievable # create Object object_name = rand_name(name='TestObject') data = arbitrary_string() resp, _ = self.object_client.create_object(self.container_name, object_name, data) # set object metadata meta_key = rand_name(name='test-') meta_value = rand_name(name='MetaValue-') orig_metadata = {meta_key: meta_value} resp, _ = self.object_client.update_object_metadata( self.container_name, object_name, orig_metadata) self.assertEqual(resp['status'], '202') # get object metadata resp, resp_metadata = self.object_client.list_object_metadata( self.container_name, object_name) self.assertEqual(resp['status'], '200') actual_meta_key = 'x-object-meta-' + meta_key self.assertTrue(actual_meta_key in resp) self.assertEqual(resp[actual_meta_key], meta_value) @attr(type='smoke') def test_get_object(self): # retrieve object's data (in response body) # create object object_name = rand_name(name='TestObject') data = arbitrary_string() resp, _ = self.object_client.create_object(self.container_name, object_name, data) # get object resp, body = self.object_client.get_object(self.container_name, object_name) self.assertEqual(resp['status'], '200') self.assertEqual(body, data) @attr(type='smoke') def test_copy_object_in_same_container(self): # create source object src_object_name = rand_name(name='SrcObject') src_data = arbitrary_string(size=len(src_object_name) * 2, base_text=src_object_name) resp, _ = self.object_client.create_object(self.container_name, src_object_name, src_data) # create destination object dst_object_name = rand_name(name='DstObject') dst_data = arbitrary_string(size=len(dst_object_name) * 3, base_text=dst_object_name) resp, _ = self.object_client.create_object(self.container_name, dst_object_name, dst_data) # copy source object to destination resp, _ = self.object_client.copy_object_in_same_container( self.container_name, src_object_name, dst_object_name) self.assertEqual(resp['status'], '201') # check data resp, body = self.object_client.get_object(self.container_name, dst_object_name) self.assertEqual(body, src_data) @attr(type='smoke') def test_copy_object_to_itself(self): # change the content type of an existing object # create object object_name = rand_name(name='TestObject') data = arbitrary_string() self.object_client.create_object(self.container_name, object_name, data) # get the old content type resp_tmp, _ = self.object_client.list_object_metadata( self.container_name, object_name) # change the content type of the object metadata = {'content-type': 'text/plain; charset=UTF-8'} self.assertNotEqual(resp_tmp['content-type'], metadata['content-type']) resp, _ = self.object_client.copy_object_in_same_container( self.container_name, object_name, object_name, metadata) self.assertEqual(resp['status'], '201') # check the content type resp, _ = self.object_client.list_object_metadata(self.container_name, object_name) self.assertEqual(resp['content-type'], metadata['content-type']) @attr(type='smoke') def test_copy_object_2d_way(self): # create source object src_object_name = rand_name(name='SrcObject') src_data = arbitrary_string(size=len(src_object_name) * 2, base_text=src_object_name) resp, _ = self.object_client.create_object(self.container_name, src_object_name, src_data) # create destination object dst_object_name = rand_name(name='DstObject') dst_data = arbitrary_string(size=len(dst_object_name) * 3, base_text=dst_object_name) resp, _ = self.object_client.create_object(self.container_name, dst_object_name, dst_data) # copy source object to destination resp, _ = self.object_client.copy_object_2d_way(self.container_name, src_object_name, dst_object_name) self.assertEqual(resp['status'], '201') # check data resp, body = self.object_client.get_object(self.container_name, dst_object_name) self.assertEqual(body, src_data) @attr(type='smoke') def test_copy_object_across_containers(self): # create a container to use as asource container src_container_name = rand_name(name='TestSourceContainer') self.container_client.create_container(src_container_name) self.containers.append(src_container_name) # create a container to use as a destination container dst_container_name = rand_name(name='TestDestinationContainer') self.container_client.create_container(dst_container_name) self.containers.append(dst_container_name) # create object in source container object_name = rand_name(name='Object') data = arbitrary_string(size=len(object_name) * 2, base_text=object_name) resp, _ = self.object_client.create_object(src_container_name, object_name, data) # set object metadata meta_key = rand_name(name='test-') meta_value = rand_name(name='MetaValue-') orig_metadata = {meta_key: meta_value} resp, _ = self.object_client.update_object_metadata(src_container_name, object_name, orig_metadata) self.assertEqual(resp['status'], '202') try: # copy object from source container to destination container resp, _ = self.object_client.copy_object_across_containers( src_container_name, object_name, dst_container_name, object_name) self.assertEqual(resp['status'], '201') # check if object is present in destination container resp, body = self.object_client.get_object(dst_container_name, object_name) self.assertEqual(body, data) actual_meta_key = 'x-object-meta-' + meta_key self.assertTrue(actual_meta_key in resp) self.assertEqual(resp[actual_meta_key], meta_value) except Exception as e: self.fail("Got exception :%s ; while copying" " object across containers" % e) @attr(type=['negative', 'gate']) def test_write_object_without_using_creds(self): # trying to create object with empty headers object_name = rand_name(name='Object') data = arbitrary_string(size=len(object_name), base_text=object_name) obj_headers = {'Content-Type': 'application/json', 'Accept': 'application/json'} self.assertRaises(exceptions.Unauthorized, self.custom_object_client.create_object, self.container_name, object_name, data, metadata=obj_headers) @attr(type=['negative', 'gate']) def test_delete_object_without_using_creds(self): # create object object_name = rand_name(name='Object') data = arbitrary_string(size=len(object_name), base_text=object_name) resp, _ = self.object_client.create_object(self.container_name, object_name, data) # trying to delete object with empty headers self.assertRaises(exceptions.Unauthorized, self.custom_object_client.delete_object, self.container_name, object_name) @attr(type=['negative', 'gate']) def test_write_object_with_non_authorized_user(self): # attempt to upload another file using non-authorized user object_name = rand_name(name='Object') data = arbitrary_string(size=len(object_name) * 5, base_text=object_name) # trying to create object with non-authorized user self.assertRaises(exceptions.Unauthorized, self.custom_object_client.create_object, self.container_name, object_name, data, metadata=self.custom_headers) @attr(type=['negative', 'gate']) def test_read_object_with_non_authorized_user(self): object_name = rand_name(name='Object') data = arbitrary_string(size=len(object_name) * 5, base_text=object_name) resp, body = self.object_client.create_object( self.container_name, object_name, data) self.assertEqual(resp['status'], '201') # trying to get object with non authorized user token self.assertRaises(exceptions.Unauthorized, self.custom_object_client.get_object, self.container_name, object_name, metadata=self.custom_headers) @attr(type=['negative', 'gate']) def test_delete_object_with_non_authorized_user(self): object_name = rand_name(name='Object') data = arbitrary_string(size=len(object_name) * 5, base_text=object_name) resp, body = self.object_client.create_object( self.container_name, object_name, data) self.assertEqual(resp['status'], '201') # trying to delete object with non-authorized user token self.assertRaises(exceptions.Unauthorized, self.custom_object_client.delete_object, self.container_name, object_name, metadata=self.custom_headers) @attr(type='gate') def test_get_object_using_temp_url(self): # access object using temporary URL within expiration time try: # update account metadata # flag to check if account metadata got updated flag = False key = 'Meta' metadata = {'Temp-URL-Key': key} resp, _ = self.account_client.create_account_metadata( metadata=metadata) self.assertEqual(resp['status'], '204') flag = True resp, _ = self.account_client.list_account_metadata() self.assertIn('x-account-meta-temp-url-key', resp) self.assertEqual(resp['x-account-meta-temp-url-key'], key) # create object object_name = rand_name(name='ObjectTemp') data = arbitrary_string(size=len(object_name), base_text=object_name) self.object_client.create_object(self.container_name, object_name, data) expires = int(time.time() + 10) # trying to get object using temp url with in expiry time _, body = self.object_client.get_object_using_temp_url( self.container_name, object_name, expires, key) self.assertEqual(body, data) finally: if flag: resp, _ = self.account_client.delete_account_metadata( metadata=metadata) resp, _ = self.account_client.list_account_metadata() self.assertNotIn('x-account-meta-temp-url-key', resp) @attr(type='gate') def test_object_upload_in_segments(self): # create object object_name = rand_name(name='LObject') data = arbitrary_string(size=len(object_name), base_text=object_name) segments = 10 self.object_client.create_object(self.container_name, object_name, data) # uploading 10 segments for i in range(segments): resp, _ = self.object_client.create_object_segments( self.container_name, object_name, i, data) # creating a manifest file (metadata update) metadata = {'X-Object-Manifest': '%s/%s/' % (self.container_name, object_name)} resp, _ = self.object_client.update_object_metadata( self.container_name, object_name, metadata, metadata_prefix='') resp, _ = self.object_client.list_object_metadata( self.container_name, object_name) self.assertIn('x-object-manifest', resp) self.assertEqual(resp['x-object-manifest'], '%s/%s/' % (self.container_name, object_name)) # downloading the object resp, body = self.object_client.get_object( self.container_name, object_name) self.assertEqual(data * segments, body) @attr(type='gate') def test_get_object_if_different(self): # http://en.wikipedia.org/wiki/HTTP_ETag # Make a conditional request for an object using the If-None-Match # header, it should get downloaded only if the local file is different, # otherwise the response code should be 304 Not Modified object_name = rand_name(name='TestObject') data = arbitrary_string() self.object_client.create_object(self.container_name, object_name, data) # local copy is identical, no download md5 = hashlib.md5(data).hexdigest() headers = {'If-None-Match': md5} url = "%s/%s" % (self.container_name, object_name) resp, _ = self.object_client.get(url, headers=headers) self.assertEqual(resp['status'], '304') # local copy is different, download local_data = "something different" md5 = hashlib.md5(local_data).hexdigest() headers = {'If-None-Match': md5} resp, body = self.object_client.get(url, headers=headers) self.assertEqual(resp['status'], '200') class PublicObjectTest(base.BaseObjectTest): def setUp(self): super(PublicObjectTest, self).setUp() self.container_name = rand_name(name='TestContainer') self.container_client.create_container(self.container_name) def tearDown(self): self.delete_containers([self.container_name]) super(PublicObjectTest, self).tearDown() @attr(type='smoke') def test_access_public_container_object_without_using_creds(self): # make container public-readable and access an object in it object # anonymously, without using credentials # update container metadata to make it publicly readable cont_headers = {'X-Container-Read': '.r:*,.rlistings'} resp_meta, body = self.container_client.update_container_metadata( self.container_name, metadata=cont_headers, metadata_prefix='') self.assertEqual(resp_meta['status'], '204') # create object object_name = rand_name(name='Object') data = arbitrary_string(size=len(object_name), base_text=object_name) resp, _ = self.object_client.create_object(self.container_name, object_name, data) self.assertEqual(resp['status'], '201') # list container metadata resp_meta, _ = self.container_client.list_container_metadata( self.container_name) self.assertEqual(resp_meta['status'], '204') self.assertIn('x-container-read', resp_meta) self.assertEqual(resp_meta['x-container-read'], '.r:*,.rlistings') # trying to get object with empty headers as it is public readable resp, body = self.custom_object_client.get_object( self.container_name, object_name, metadata={}) self.assertEqual(body, data) @attr(type='smoke') def test_access_public_object_with_another_user_creds(self): # make container public-readable and access an object in it using # another user's credentials try: cont_headers = {'X-Container-Read': '.r:*,.rlistings'} resp_meta, body = self.container_client.update_container_metadata( self.container_name, metadata=cont_headers, metadata_prefix='') self.assertEqual(resp_meta['status'], '204') # create object object_name = rand_name(name='Object') data = arbitrary_string(size=len(object_name) * 1, base_text=object_name) resp, _ = self.object_client.create_object(self.container_name, object_name, data) self.assertEqual(resp['status'], '201') # list container metadata resp, _ = self.container_client.list_container_metadata( self.container_name) self.assertEqual(resp['status'], '204') self.assertIn('x-container-read', resp) self.assertEqual(resp['x-container-read'], '.r:*,.rlistings') # get auth token of alternative user token = self.identity_client_alt.get_auth() headers = {'X-Auth-Token': token} # access object using alternate user creds resp, body = self.custom_object_client.get_object( self.container_name, object_name, metadata=headers) self.assertEqual(body, data) except Exception as e: self.fail("Failed to get public readable object with another" " user creds raised exception is %s" % e) tempest-2013.2.a1291.g23a1b4f/tempest/api/object_storage/test_object_expiry.py0000664000175000017500000000613712161375672027170 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 time import testtools from tempest.api.object_storage import base from tempest.common.utils.data_utils import arbitrary_string from tempest.common.utils.data_utils import rand_name from tempest import exceptions from tempest.test import attr class ObjectExpiryTest(base.BaseObjectTest): @classmethod def setUpClass(cls): super(ObjectExpiryTest, cls).setUpClass() cls.container_name = rand_name(name='TestContainer') cls.container_client.create_container(cls.container_name) @classmethod def tearDownClass(cls): """The test script fails in tear down class as the container contains expired objects (LP bug 1069849). But delete action for the expired object is raising NotFound exception and also non empty container cannot be deleted. """ cls.delete_containers([cls.container_name]) @testtools.skip('Until Bug #1069849 is resolved.') @attr(type='gate') def test_get_object_after_expiry_time(self): #TODO(harika-vakadi): similar test case has to be created for # "X-Delete-At", after this test case works. # create object object_name = rand_name(name='TestObject') data = arbitrary_string() resp, _ = self.object_client.create_object(self.container_name, object_name, data) # update object metadata with expiry time of 3 seconds metadata = {'X-Delete-After': '3'} resp, _ = \ self.object_client.update_object_metadata(self.container_name, object_name, metadata, metadata_prefix='') resp, _ = \ self.object_client.list_object_metadata(self.container_name, object_name) self.assertEqual(resp['status'], '200') self.assertIn('x-delete-at', resp) resp, body = self.object_client.get_object(self.container_name, object_name) self.assertEqual(resp['status'], '200') # check data self.assertEqual(body, data) # sleep for over 5 seconds, so that object expires time.sleep(5) # object should not be there anymore self.assertRaises(exceptions.NotFound, self.object_client.get_object, self.container_name, object_name) tempest-2013.2.a1291.g23a1b4f/tempest/api/object_storage/test_object_version.py0000664000175000017500000000764112161375672027336 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.object_storage import base from tempest.common.utils.data_utils import rand_name from tempest.test import attr class ContainerTest(base.BaseObjectTest): @classmethod def setUpClass(cls): super(ContainerTest, cls).setUpClass() cls.containers = [] @classmethod def tearDownClass(cls): cls.delete_containers(cls.containers) def assertContainer(self, container, count, byte, versioned): resp, _ = self.container_client.list_container_metadata(container) self.assertEqual(resp['status'], ('204')) header_value = resp.get('x-container-object-count', 'Missing Header') self.assertEqual(header_value, count) header_value = resp.get('x-container-bytes-used', 'Missing Header') self.assertEqual(header_value, byte) header_value = resp.get('x-versions-location', 'Missing Header') self.assertEqual(header_value, versioned) @attr(type='smoke') def test_versioned_container(self): # create container vers_container_name = rand_name(name='TestVersionContainer') resp, body = self.container_client.create_container( vers_container_name) self.containers.append(vers_container_name) self.assertIn(resp['status'], ('202', '201')) self.assertContainer(vers_container_name, '0', '0', 'Missing Header') base_container_name = rand_name(name='TestBaseContainer') headers = {'X-versions-Location': vers_container_name} resp, body = self.container_client.create_container( base_container_name, metadata=headers, metadata_prefix='') self.containers.append(base_container_name) self.assertIn(resp['status'], ('202', '201')) self.assertContainer(base_container_name, '0', '0', vers_container_name) object_name = rand_name(name='TestObject') # create object resp, _ = self.object_client.create_object(base_container_name, object_name, '1') # create 2nd version of object resp, _ = self.object_client.create_object(base_container_name, object_name, '2') resp, body = self.object_client.get_object(base_container_name, object_name) self.assertEqual(body, '2') # delete object version 2 resp, _ = self.object_client.delete_object(base_container_name, object_name) self.assertContainer(base_container_name, '1', '1', vers_container_name) resp, body = self.object_client.get_object(base_container_name, object_name) self.assertEqual(body, '1') # delete object version 1 resp, _ = self.object_client.delete_object(base_container_name, object_name) # containers should be empty self.assertContainer(base_container_name, '0', '0', vers_container_name) self.assertContainer(vers_container_name, '0', '0', 'Missing Header') tempest-2013.2.a1291.g23a1b4f/tempest/api/object_storage/__init__.py0000664000175000017500000000000012161375672025001 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/object_storage/base.py0000664000175000017500000000605112161375672024170 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.identity.base import DataGenerator from tempest import clients from tempest import exceptions import tempest.test class BaseObjectTest(tempest.test.BaseTestCase): @classmethod def setUpClass(cls): cls.os = clients.Manager() cls.object_client = cls.os.object_client cls.container_client = cls.os.container_client cls.account_client = cls.os.account_client cls.custom_object_client = cls.os.custom_object_client cls.os_admin = clients.AdminManager() cls.token_client = cls.os_admin.token_client cls.identity_admin_client = cls.os_admin.identity_client cls.custom_account_client = cls.os.custom_account_client cls.os_alt = clients.AltManager() cls.object_client_alt = cls.os_alt.object_client cls.container_client_alt = cls.os_alt.container_client cls.identity_client_alt = cls.os_alt.identity_client cls.data = DataGenerator(cls.identity_admin_client) try: cls.account_client.list_account_containers() except exceptions.EndpointNotFound: skip_msg = "No OpenStack Object Storage API endpoint" raise cls.skipException(skip_msg) @classmethod def delete_containers(cls, containers, container_client=None, object_client=None): """Remove given containers and all objects in them. The containers should be visible from the container_client given. Will not throw any error if the containers don't exist. :param containers: list of container names to remove :param container_client: if None, use cls.container_client, this means that the default testing user will be used (see 'username' in 'etc/tempest.conf') :param object_client: if None, use cls.object_client """ if container_client is None: container_client = cls.container_client if object_client is None: object_client = cls.object_client for cont in containers: try: objlist = container_client.list_all_container_objects(cont) # delete every object in the container for obj in objlist: object_client.delete_object(cont, obj['name']) container_client.delete_container(cont) except exceptions.NotFound: pass tempest-2013.2.a1291.g23a1b4f/tempest/api/object_storage/test_container_services.py0000664000175000017500000001142512161375672030203 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.object_storage import base from tempest.common.utils.data_utils import arbitrary_string from tempest.common.utils.data_utils import rand_name from tempest.test import attr class ContainerTest(base.BaseObjectTest): @classmethod def setUpClass(cls): super(ContainerTest, cls).setUpClass() cls.containers = [] @classmethod def tearDownClass(cls): cls.delete_containers(cls.containers) @attr(type='smoke') def test_create_container(self): container_name = rand_name(name='TestContainer') resp, body = self.container_client.create_container(container_name) self.containers.append(container_name) self.assertTrue(resp['status'] in ('202', '201')) @attr(type='smoke') def test_delete_container(self): # create a container container_name = rand_name(name='TestContainer') resp, _ = self.container_client.create_container(container_name) self.containers.append(container_name) # delete container resp, _ = self.container_client.delete_container(container_name) self.assertEqual(resp['status'], '204') self.containers.remove(container_name) @attr(type='smoke') def test_list_container_contents_json(self): # add metadata to an object # create a container container_name = rand_name(name='TestContainer') resp, _ = self.container_client.create_container(container_name) self.containers.append(container_name) # create object object_name = rand_name(name='TestObject') data = arbitrary_string() resp, _ = self.object_client.create_object(container_name, object_name, data) # set object metadata meta_key = rand_name(name='Meta-Test-') meta_value = rand_name(name='MetaValue-') orig_metadata = {meta_key: meta_value} resp, _ = self.object_client.update_object_metadata(container_name, object_name, orig_metadata) # get container contents list params = {'format': 'json'} resp, object_list = \ self.container_client.\ list_container_contents(container_name, params=params) self.assertEqual(resp['status'], '200') self.assertIsNotNone(object_list) object_names = [obj['name'] for obj in object_list] self.assertIn(object_name, object_names) @attr(type='smoke') def test_container_metadata(self): # update/retrieve/delete container metadata # create a container container_name = rand_name(name='TestContainer') resp, _ = self.container_client.create_container(container_name) self.containers.append(container_name) # update container metadata metadata = {'name': 'Pictures', 'description': 'Travel' } resp, _ = \ self.container_client.update_container_metadata(container_name, metadata=metadata) self.assertEqual(resp['status'], '204') # list container metadata resp, _ = self.container_client.list_container_metadata( container_name) self.assertEqual(resp['status'], '204') self.assertIn('x-container-meta-name', resp) self.assertIn('x-container-meta-description', resp) self.assertEqual(resp['x-container-meta-name'], 'Pictures') self.assertEqual(resp['x-container-meta-description'], 'Travel') # delete container metadata resp, _ = self.container_client.delete_container_metadata( container_name, metadata=metadata.keys()) self.assertEqual(resp['status'], '204') # check if the metadata are no longer there resp, _ = self.container_client.list_container_metadata(container_name) self.assertEqual(resp['status'], '204') self.assertNotIn('x-container-meta-name', resp) self.assertNotIn('x-container-meta-description', resp) tempest-2013.2.a1291.g23a1b4f/tempest/api/identity/0000775000175000017500000000000012161375700021531 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/identity/admin/0000775000175000017500000000000012161375700022621 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/identity/admin/v3/0000775000175000017500000000000012161375700023151 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/identity/admin/v3/test_endpoints.py0000664000175000017500000001427612161375672026607 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 OpenStack Foundation # 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 tempest.api.identity import base from tempest.common.utils.data_utils import rand_name from tempest.test import attr class EndPointsTestJSON(base.BaseIdentityAdminTest): _interface = 'json' @classmethod def setUpClass(cls): super(EndPointsTestJSON, cls).setUpClass() cls.identity_client = cls.client cls.client = cls.endpoints_client cls.service_ids = list() s_name = rand_name('service-') s_type = rand_name('type--') s_description = rand_name('description-') resp, cls.service_data =\ cls.identity_client.create_service(s_name, s_type, description=s_description) cls.service_id = cls.service_data['id'] cls.service_ids.append(cls.service_id) #Create endpoints so as to use for LIST and GET test cases cls.setup_endpoints = list() for i in range(2): region = rand_name('region') url = rand_name('url') interface = 'public' resp, endpoint = cls.client.create_endpoint( cls.service_id, interface, url, region=region, enabled=True) cls.setup_endpoints.append(endpoint) @classmethod def tearDownClass(cls): for e in cls.setup_endpoints: cls.client.delete_endpoint(e['id']) for s in cls.service_ids: cls.identity_client.delete_service(s) @attr(type='gate') def test_list_endpoints(self): # Get a list of endpoints resp, fetched_endpoints = self.client.list_endpoints() #Asserting LIST Endpoint self.assertEqual(resp['status'], '200') missing_endpoints =\ [e for e in self.setup_endpoints if e not in fetched_endpoints] self.assertEqual(0, len(missing_endpoints), "Failed to find endpoint %s in fetched list" % ', '.join(str(e) for e in missing_endpoints)) @attr(type='gate') def test_create_delete_endpoint(self): region = rand_name('region') url = rand_name('url') interface = 'public' create_flag = False matched = False try: resp, endpoint =\ self.client.create_endpoint(self.service_id, interface, url, region=region, enabled=True) create_flag = True #Asserting Create Endpoint response body self.assertEqual(resp['status'], '201') self.assertEqual(region, endpoint['region']) self.assertEqual(url, endpoint['url']) #Checking if created endpoint is present in the list of endpoints resp, fetched_endpoints = self.client.list_endpoints() for e in fetched_endpoints: if endpoint['id'] == e['id']: matched = True if not matched: self.fail("Created endpoint does not appear in the list" " of endpoints") finally: if create_flag: matched = False #Deleting the endpoint created in this method resp_header, resp_body =\ self.client.delete_endpoint(endpoint['id']) self.assertEqual(resp_header['status'], '204') self.assertEqual(resp_body, '') #Checking whether endpoint is deleted successfully resp, fetched_endpoints = self.client.list_endpoints() for e in fetched_endpoints: if endpoint['id'] == e['id']: matched = True if matched: self.fail("Delete endpoint is not successful") @attr(type='smoke') def test_update_endpoint(self): #Creating an endpoint so as to check update endpoint #with new values region1 = rand_name('region') url1 = rand_name('url') interface1 = 'public' resp, endpoint_for_update =\ self.client.create_endpoint(self.service_id, interface1, url1, region=region1, enabled=True) #Creating service so as update endpoint with new service ID s_name = rand_name('service-') s_type = rand_name('type--') s_description = rand_name('description-') resp, self.service2 =\ self.identity_client.create_service(s_name, s_type, description=s_description) self.service_ids.append(self.service2['id']) #Updating endpoint with new values region2 = rand_name('region') url2 = rand_name('url') interface2 = 'internal' resp, endpoint = \ self.client.update_endpoint(endpoint_for_update['id'], service_id=self.service2['id'], interface=interface2, url=url2, region=region2, enabled=False) self.assertEqual(resp['status'], '200') #Asserting if the attributes of endpoint are updated self.assertEqual(self.service2['id'], endpoint['service_id']) self.assertEqual(interface2, endpoint['interface']) self.assertEqual(url2, endpoint['url']) self.assertEqual(region2, endpoint['region']) self.assertEqual('False', str(endpoint['enabled'])) self.addCleanup(self.client.delete_endpoint, endpoint_for_update['id']) class EndPointsTestXML(EndPointsTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/identity/admin/v3/test_users.py0000664000175000017500000001307012161375672025734 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 OpenStack Foundation # 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 tempest.api.identity import base from tempest.common.utils.data_utils import rand_name from tempest.test import attr class UsersV3TestJSON(base.BaseIdentityAdminTest): _interface = 'json' @attr(type='gate') def test_user_update(self): # Test case to check if updating of user attributes is successful. #Creating first user u_name = rand_name('user-') u_desc = u_name + 'description' u_email = u_name + '@testmail.tm' u_password = rand_name('pass-') resp, user = self.v3_client.create_user( u_name, description=u_desc, password=u_password, email=u_email, enabled=False) # Delete the User at the end of this method self.addCleanup(self.v3_client.delete_user, user['id']) #Creating second project for updation resp, project = self.v3_client.create_project( rand_name('project-'), description=rand_name('project-desc-')) # Delete the Project at the end of this method self.addCleanup(self.v3_client.delete_project, project['id']) #Updating user details with new values u_name2 = rand_name('user2-') u_email2 = u_name2 + '@testmail.tm' u_description2 = u_name2 + ' description' resp, update_user = self.v3_client.update_user( user['id'], name=u_name2, description=u_description2, project_id=project['id'], email=u_email2, enabled=False) #Assert response body of update user. self.assertEqual(200, resp.status) self.assertEqual(u_name2, update_user['name']) self.assertEqual(u_description2, update_user['description']) self.assertEqual(project['id'], update_user['project_id']) self.assertEqual(u_email2, update_user['email']) self.assertEqual('false', str(update_user['enabled']).lower()) #GET by id after updation resp, new_user_get = self.v3_client.get_user(user['id']) #Assert response body of GET after updation self.assertEqual(u_name2, new_user_get['name']) self.assertEqual(u_description2, new_user_get['description']) self.assertEqual(project['id'], new_user_get['project_id']) self.assertEqual(u_email2, new_user_get['email']) self.assertEqual('false', str(new_user_get['enabled']).lower()) @attr(type='gate') def test_list_user_projects(self): #List the projects that a user has access upon assigned_project_ids = list() fetched_project_ids = list() _, u_project = self.v3_client.create_project( rand_name('project-'), description=rand_name('project-desc-')) # Delete the Project at the end of this method self.addCleanup(self.v3_client.delete_project, u_project['id']) #Create a user. u_name = rand_name('user-') u_desc = u_name + 'description' u_email = u_name + '@testmail.tm' u_password = rand_name('pass-') _, user_body = self.v3_client.create_user( u_name, description=u_desc, password=u_password, email=u_email, enabled=False, project_id=u_project['id']) # Delete the User at the end of this method self.addCleanup(self.v3_client.delete_user, user_body['id']) # Creating Role _, role_body = self.v3_client.create_role(rand_name('role-')) # Delete the Role at the end of this method self.addCleanup(self.v3_client.delete_role, role_body['id']) _, user = self.v3_client.get_user(user_body['id']) _, role = self.v3_client.get_role(role_body['id']) for i in range(2): # Creating project so as to assign role _, project_body = self.v3_client.create_project( rand_name('project-'), description=rand_name('project-desc-')) _, project = self.v3_client.get_project(project_body['id']) # Delete the Project at the end of this method self.addCleanup(self.v3_client.delete_project, project_body['id']) #Assigning roles to user on project self.v3_client.assign_user_role(project['id'], user['id'], role['id']) assigned_project_ids.append(project['id']) resp, body = self.v3_client.list_user_projects(user['id']) self.assertEqual(200, resp.status) for i in body: fetched_project_ids.append(i['id']) #verifying the project ids in list missing_projects =\ [p for p in assigned_project_ids if p not in fetched_project_ids] self.assertEqual(0, len(missing_projects), "Failed to find project %s in fetched list" % ', '.join(m_project for m_project in missing_projects)) class UsersV3TestXML(UsersV3TestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/identity/admin/v3/test_domains.py0000664000175000017500000000401712161375672026226 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 OpenStack Foundation # 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 tempest.api.identity import base from tempest.common.utils.data_utils import rand_name from tempest.test import attr class DomainsTestJSON(base.BaseIdentityAdminTest): _interface = 'json' def _delete_domain(self, domain_id): # It is necessary to disable the domian before deleting, # or else it would result in unauthorized error _, body = self.v3_client.update_domain(domain_id, enabled=False) resp, _ = self.v3_client.delete_domain(domain_id) self.assertEqual(204, resp.status) @attr(type='smoke') def test_list_domains(self): #Test to list domains domain_ids = list() fetched_ids = list() for _ in range(3): _, domain = self.v3_client.create_domain( rand_name('domain-'), description=rand_name('domain-desc-')) # Delete the domian at the end of this method self.addCleanup(self._delete_domain, domain['id']) domain_ids.append(domain['id']) # List and Verify Domains resp, body = self.v3_client.list_domains() self.assertEqual(resp['status'], '200') for d in body: fetched_ids.append(d['id']) missing_doms = [d for d in domain_ids if d not in fetched_ids] self.assertEqual(0, len(missing_doms)) class DomainsTestXML(DomainsTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/identity/admin/v3/test_services.py0000664000175000017500000000371712161375672026425 0ustar chuckchuck00000000000000#vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 OpenStack Foundation # 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 tempest.api.identity import base from tempest.common.utils.data_utils import rand_name from tempest.test import attr class ServicesTestJSON(base.BaseIdentityAdminTest): _interface = 'json' @attr(type='gate') def test_update_service(self): # Update description attribute of service name = rand_name('service-') type = rand_name('type--') description = rand_name('description-') resp, body = self.client.create_service( name, type, description=description) self.assertEqual('200', resp['status']) #Deleting the service created in this method self.addCleanup(self.client.delete_service, body['id']) s_id = body['id'] resp1_desc = body['description'] s_desc2 = rand_name('desc2-') resp, body = self.service_client.update_service( s_id, description=s_desc2) resp2_desc = body['description'] self.assertEqual('200', resp['status']) self.assertNotEqual(resp1_desc, resp2_desc) #Get service resp, body = self.client.get_service(s_id) resp3_desc = body['description'] self.assertNotEqual(resp1_desc, resp3_desc) self.assertEqual(resp2_desc, resp3_desc) class ServicesTestXML(ServicesTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/identity/admin/v3/test_policies.py0000664000175000017500000000643612161375672026412 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 OpenStack Foundation # 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 tempest.api.identity import base from tempest.common.utils.data_utils import rand_name from tempest.test import attr class PoliciesTestJSON(base.BaseIdentityAdminTest): _interface = 'json' def _delete_policy(self, policy_id): resp, _ = self.policy_client.delete_policy(policy_id) self.assertEqual(204, resp.status) @attr(type='smoke') def test_list_policies(self): #Test to list policies policy_ids = list() fetched_ids = list() for _ in range(3): blob = rand_name('BlobName-') policy_type = rand_name('PolicyType-') resp, policy = self.policy_client.create_policy(blob, policy_type) # Delete the Policy at the end of this method self.addCleanup(self._delete_policy, policy['id']) policy_ids.append(policy['id']) # List and Verify Policies resp, body = self.policy_client.list_policies() self.assertEqual(resp['status'], '200') for p in body: fetched_ids.append(p['id']) missing_pols = [p for p in policy_ids if p not in fetched_ids] self.assertEqual(0, len(missing_pols)) @attr(type='smoke') def test_create_update_delete_policy(self): #Test to update policy blob = rand_name('BlobName-') policy_type = rand_name('PolicyType-') resp, policy = self.policy_client.create_policy(blob, policy_type) self.addCleanup(self._delete_policy, policy['id']) self.assertIn('id', policy) self.assertIn('type', policy) self.assertIn('blob', policy) self.assertIsNotNone(policy['id']) self.assertEqual(blob, policy['blob']) self.assertEqual(policy_type, policy['type']) resp, fetched_policy = self.policy_client.get_policy(policy['id']) self.assertEqual(resp['status'], '200') #Update policy update_type = rand_name('UpdatedPolicyType-') resp, data = self.policy_client.update_policy( policy['id'], type=update_type) self.assertTrue('type' in data) #Assertion for updated value with fetched value resp, fetched_policy = self.policy_client.get_policy(policy['id']) self.assertIn('id', fetched_policy) self.assertIn('blob', fetched_policy) self.assertIn('type', fetched_policy) self.assertEqual(fetched_policy['id'], policy['id']) self.assertEqual(fetched_policy['blob'], policy['blob']) self.assertEqual(update_type, fetched_policy['type']) class PoliciesTestXML(PoliciesTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/identity/admin/v3/__init__.py0000664000175000017500000000000012161375672025260 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/identity/admin/test_roles.py0000664000175000017500000002742512161375672025400 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.identity import base from tempest.common.utils.data_utils import rand_name from tempest import exceptions from tempest.test import attr class RolesTestJSON(base.BaseIdentityAdminTest): _interface = 'json' @classmethod def setUpClass(cls): super(RolesTestJSON, cls).setUpClass() for _ in xrange(5): resp, role = cls.client.create_role(rand_name('role-')) cls.data.roles.append(role) def _get_role_params(self): self.data.setup_test_user() self.data.setup_test_role() user = self.get_user_by_name(self.data.test_user) tenant = self.get_tenant_by_name(self.data.test_tenant) role = self.get_role_by_name(self.data.test_role) return (user, tenant, role) def assert_role_in_role_list(self, role, roles): found = False for user_role in roles: if user_role['id'] == role['id']: found = True self.assertTrue(found, "assigned role was not in list") @attr(type='gate') def test_list_roles(self): # Return a list of all roles resp, body = self.client.list_roles() found = [role for role in body if role in self.data.roles] self.assertTrue(any(found)) self.assertEqual(len(found), len(self.data.roles)) @attr(type='gate') def test_list_roles_by_unauthorized_user(self): # Non admin user should not be able to list roles self.assertRaises(exceptions.Unauthorized, self.non_admin_client.list_roles) @attr(type='gate') def test_list_roles_request_without_token(self): # Request to list roles without a valid token should fail token = self.client.get_auth() self.client.delete_token(token) self.assertRaises(exceptions.Unauthorized, self.client.list_roles) self.client.clear_auth() @attr(type='gate') def test_role_create_delete(self): # Role should be created, verified, and deleted role_name = rand_name('role-test-') resp, body = self.client.create_role(role_name) self.assertTrue('status' in resp) self.assertTrue(resp['status'].startswith('2')) self.assertEqual(role_name, body['name']) resp, body = self.client.list_roles() found = [role for role in body if role['name'] == role_name] self.assertTrue(any(found)) resp, body = self.client.delete_role(found[0]['id']) self.assertTrue('status' in resp) self.assertTrue(resp['status'].startswith('2')) resp, body = self.client.list_roles() found = [role for role in body if role['name'] == role_name] self.assertFalse(any(found)) @attr(type='gate') def test_role_create_blank_name(self): # Should not be able to create a role with a blank name self.assertRaises(exceptions.BadRequest, self.client.create_role, '') @attr(type='gate') def test_role_create_duplicate(self): # Role names should be unique role_name = rand_name('role-dup-') resp, body = self.client.create_role(role_name) role1_id = body.get('id') self.assertTrue('status' in resp) self.assertTrue(resp['status'].startswith('2')) self.addCleanup(self.client.delete_role, role1_id) self.assertRaises(exceptions.Duplicate, self.client.create_role, role_name) @attr(type='gate') def test_assign_user_role(self): # Assign a role to a user on a tenant (user, tenant, role) = self._get_role_params() self.client.assign_user_role(tenant['id'], user['id'], role['id']) resp, roles = self.client.list_user_roles(tenant['id'], user['id']) self.assert_role_in_role_list(role, roles) @attr(type='gate') def test_assign_user_role_by_unauthorized_user(self): # Non admin user should not be authorized to assign a role to user (user, tenant, role) = self._get_role_params() self.assertRaises(exceptions.Unauthorized, self.non_admin_client.assign_user_role, tenant['id'], user['id'], role['id']) @attr(type='gate') def test_assign_user_role_request_without_token(self): # Request to assign a role to a user without a valid token (user, tenant, role) = self._get_role_params() token = self.client.get_auth() self.client.delete_token(token) self.assertRaises(exceptions.Unauthorized, self.client.assign_user_role, tenant['id'], user['id'], role['id']) self.client.clear_auth() @attr(type='gate') def test_assign_user_role_for_non_existent_user(self): # Attempt to assign a role to a non existent user should fail (user, tenant, role) = self._get_role_params() self.assertRaises(exceptions.NotFound, self.client.assign_user_role, tenant['id'], 'junk-user-id-999', role['id']) @attr(type='gate') def test_assign_user_role_for_non_existent_role(self): # Attempt to assign a non existent role to user should fail (user, tenant, role) = self._get_role_params() self.assertRaises(exceptions.NotFound, self.client.assign_user_role, tenant['id'], user['id'], 'junk-role-id-12345') @attr(type='gate') def test_assign_user_role_for_non_existent_tenant(self): # Attempt to assign a role on a non existent tenant should fail (user, tenant, role) = self._get_role_params() self.assertRaises(exceptions.NotFound, self.client.assign_user_role, 'junk-tenant-1234', user['id'], role['id']) @attr(type='gate') def test_assign_duplicate_user_role(self): # Duplicate user role should not get assigned (user, tenant, role) = self._get_role_params() self.client.assign_user_role(tenant['id'], user['id'], role['id']) self.assertRaises(exceptions.Duplicate, self.client.assign_user_role, tenant['id'], user['id'], role['id']) @attr(type='gate') def test_remove_user_role(self): # Remove a role assigned to a user on a tenant (user, tenant, role) = self._get_role_params() resp, user_role = self.client.assign_user_role(tenant['id'], user['id'], role['id']) resp, body = self.client.remove_user_role(tenant['id'], user['id'], user_role['id']) self.assertEquals(resp['status'], '204') @attr(type='gate') def test_remove_user_role_by_unauthorized_user(self): # Non admin user should not be authorized to remove a user's role (user, tenant, role) = self._get_role_params() resp, user_role = self.client.assign_user_role(tenant['id'], user['id'], role['id']) self.assertRaises(exceptions.Unauthorized, self.non_admin_client.remove_user_role, tenant['id'], user['id'], role['id']) @attr(type='gate') def test_remove_user_role_request_without_token(self): # Request to remove a user's role without a valid token (user, tenant, role) = self._get_role_params() resp, user_role = self.client.assign_user_role(tenant['id'], user['id'], role['id']) token = self.client.get_auth() self.client.delete_token(token) self.assertRaises(exceptions.Unauthorized, self.client.remove_user_role, tenant['id'], user['id'], role['id']) self.client.clear_auth() @attr(type='gate') def test_remove_user_role_non_existant_user(self): # Attempt to remove a role from a non existent user should fail (user, tenant, role) = self._get_role_params() resp, user_role = self.client.assign_user_role(tenant['id'], user['id'], role['id']) self.assertRaises(exceptions.NotFound, self.client.remove_user_role, tenant['id'], 'junk-user-id-123', role['id']) @attr(type='gate') def test_remove_user_role_non_existant_role(self): # Attempt to delete a non existent role from a user should fail (user, tenant, role) = self._get_role_params() resp, user_role = self.client.assign_user_role(tenant['id'], user['id'], role['id']) self.assertRaises(exceptions.NotFound, self.client.remove_user_role, tenant['id'], user['id'], 'junk-user-role-123') @attr(type='gate') def test_remove_user_role_non_existant_tenant(self): # Attempt to remove a role from a non existent tenant should fail (user, tenant, role) = self._get_role_params() resp, user_role = self.client.assign_user_role(tenant['id'], user['id'], role['id']) self.assertRaises(exceptions.NotFound, self.client.remove_user_role, 'junk-tenant-id-123', user['id'], role['id']) @attr(type='gate') def test_list_user_roles(self): # List roles assigned to a user on tenant (user, tenant, role) = self._get_role_params() self.client.assign_user_role(tenant['id'], user['id'], role['id']) resp, roles = self.client.list_user_roles(tenant['id'], user['id']) self.assert_role_in_role_list(role, roles) @attr(type='gate') def test_list_user_roles_by_unauthorized_user(self): # Non admin user should not be authorized to list a user's roles (user, tenant, role) = self._get_role_params() self.client.assign_user_role(tenant['id'], user['id'], role['id']) self.assertRaises(exceptions.Unauthorized, self.non_admin_client.list_user_roles, tenant['id'], user['id']) @attr(type='gate') def test_list_user_roles_request_without_token(self): # Request to list user's roles without a valid token should fail (user, tenant, role) = self._get_role_params() token = self.client.get_auth() self.client.delete_token(token) try: self.assertRaises(exceptions.Unauthorized, self.client.list_user_roles, tenant['id'], user['id']) finally: self.client.clear_auth() @attr(type='gate') def test_list_user_roles_for_non_existent_user(self): # Attempt to list roles of a non existent user should fail (user, tenant, role) = self._get_role_params() self.assertRaises(exceptions.NotFound, self.client.list_user_roles, tenant['id'], 'junk-role-aabbcc11') class RolesTestXML(RolesTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/identity/admin/test_users.py0000664000175000017500000003555712161375672025422 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 testtools from testtools.matchers._basic import Contains from tempest.api.identity import base from tempest.common.utils.data_utils import rand_name from tempest import exceptions from tempest.test import attr class UsersTestJSON(base.BaseIdentityAdminTest): _interface = 'json' alt_user = rand_name('test_user_') alt_password = rand_name('pass_') alt_email = alt_user + '@testmail.tm' alt_tenant = rand_name('test_tenant_') alt_description = rand_name('desc_') @attr(type='smoke') def test_create_user(self): # Create a user self.data.setup_test_tenant() resp, user = self.client.create_user(self.alt_user, self.alt_password, self.data.tenant['id'], self.alt_email) self.data.users.append(user) self.assertEqual('200', resp['status']) self.assertEqual(self.alt_user, user['name']) @attr(type=['negative', 'gate']) def test_create_user_by_unauthorized_user(self): # Non-admin should not be authorized to create a user self.data.setup_test_tenant() self.assertRaises(exceptions.Unauthorized, self.non_admin_client.create_user, self.alt_user, self.alt_password, self.data.tenant['id'], self.alt_email) @attr(type=['negative', 'gate']) def test_create_user_with_empty_name(self): # User with an empty name should not be created self.data.setup_test_tenant() self.assertRaises(exceptions.BadRequest, self.client.create_user, '', self.alt_password, self.data.tenant['id'], self.alt_email) @attr(type=['negative', 'gate']) def test_create_user_with_name_length_over_255(self): # Length of user name filed should be restricted to 255 characters self.data.setup_test_tenant() self.assertRaises(exceptions.BadRequest, self.client.create_user, 'a' * 256, self.alt_password, self.data.tenant['id'], self.alt_email) @attr(type=['negative', 'gate']) def test_create_user_with_duplicate_name(self): # Duplicate user should not be created self.data.setup_test_user() self.assertRaises(exceptions.Duplicate, self.client.create_user, self.data.test_user, self.data.test_password, self.data.tenant['id'], self.data.test_email) @testtools.skip("Until Bug #999084 is fixed") @attr(type=['negative', 'gate']) def test_create_user_with_empty_password(self): # User with an empty password should not be created self.data.setup_test_tenant() self.assertRaises(exceptions.BadRequest, self.client.create_user, self.alt_user, '', self.data.tenant['id'], self.alt_email) @testtools.skip("Until Bug #999084 is fixed") @attr(type=['negative', 'gate']) def test_create_user_with_long_password(self): # User having password exceeding max length should not be created self.data.setup_test_tenant() self.assertRaises(exceptions.BadRequest, self.client.create_user, self.alt_user, 'a' * 65, self.data.tenant['id'], self.alt_email) @testtools.skip("Until Bug #999084 is fixed") @attr(type=['negative', 'gate']) def test_create_user_with_invalid_email_format(self): # Email format should be validated while creating a user self.data.setup_test_tenant() self.assertRaises(exceptions.BadRequest, self.client.create_user, self.alt_user, '', self.data.tenant['id'], '12345') @attr(type=['negative', 'gate']) def test_create_user_for_non_existant_tenant(self): # Attempt to create a user in a non-existent tenant should fail self.assertRaises(exceptions.NotFound, self.client.create_user, self.alt_user, self.alt_password, '49ffgg99999', self.alt_email) @attr(type=['negative', 'gate']) def test_create_user_request_without_a_token(self): # Request to create a user without a valid token should fail self.data.setup_test_tenant() # Get the token of the current client token = self.client.get_auth() # Delete the token from database self.client.delete_token(token) self.assertRaises(exceptions.Unauthorized, self.client.create_user, self.alt_user, self.alt_password, self.data.tenant['id'], self.alt_email) # Unset the token to allow further tests to generate a new token self.client.clear_auth() @attr(type='smoke') def test_delete_user(self): # Delete a user self.data.setup_test_tenant() resp, user = self.client.create_user('user_1234', self.alt_password, self.data.tenant['id'], self.alt_email) self.assertEquals('200', resp['status']) resp, body = self.client.delete_user(user['id']) self.assertEquals('204', resp['status']) @attr(type=['negative', 'gate']) def test_delete_users_by_unauthorized_user(self): # Non admin user should not be authorized to delete a user self.data.setup_test_user() self.assertRaises(exceptions.Unauthorized, self.non_admin_client.delete_user, self.data.user['id']) @attr(type=['negative', 'gate']) def test_delete_non_existant_user(self): # Attempt to delete a non-existent user should fail self.assertRaises(exceptions.NotFound, self.client.delete_user, 'junk12345123') @attr(type='smoke') def test_user_authentication(self): # Valid user's token is authenticated self.data.setup_test_user() # Get a token self.token_client.auth(self.data.test_user, self.data.test_password, self.data.test_tenant) # Re-auth resp, body = self.token_client.auth(self.data.test_user, self.data.test_password, self.data.test_tenant) self.assertEqual('200', resp['status']) @attr(type=['negative', 'gate']) def test_authentication_for_disabled_user(self): # Disabled user's token should not get authenticated self.data.setup_test_user() self.disable_user(self.data.test_user) self.assertRaises(exceptions.Unauthorized, self.token_client.auth, self.data.test_user, self.data.test_password, self.data.test_tenant) @attr(type=['negative', 'gate']) def test_authentication_when_tenant_is_disabled(self): # User's token for a disabled tenant should not be authenticated self.data.setup_test_user() self.disable_tenant(self.data.test_tenant) self.assertRaises(exceptions.Unauthorized, self.token_client.auth, self.data.test_user, self.data.test_password, self.data.test_tenant) @attr(type=['negative', 'gate']) def test_authentication_with_invalid_tenant(self): # User's token for an invalid tenant should not be authenticated self.data.setup_test_user() self.assertRaises(exceptions.Unauthorized, self.token_client.auth, self.data.test_user, self.data.test_password, 'junktenant1234') @attr(type=['negative', 'gate']) def test_authentication_with_invalid_username(self): # Non-existent user's token should not get authenticated self.data.setup_test_user() self.assertRaises(exceptions.Unauthorized, self.token_client.auth, 'junkuser123', self.data.test_password, self.data.test_tenant) @attr(type=['negative', 'gate']) def test_authentication_with_invalid_password(self): # User's token with invalid password should not be authenticated self.data.setup_test_user() self.assertRaises(exceptions.Unauthorized, self.token_client.auth, self.data.test_user, 'junkpass1234', self.data.test_tenant) @attr(type='gate') def test_authentication_request_without_token(self): # Request for token authentication with a valid token in header self.data.setup_test_user() self.token_client.auth(self.data.test_user, self.data.test_password, self.data.test_tenant) # Get the token of the current client token = self.client.get_auth() # Delete the token from database self.client.delete_token(token) # Re-auth resp, body = self.token_client.auth(self.data.test_user, self.data.test_password, self.data.test_tenant) self.assertEqual('200', resp['status']) self.client.clear_auth() @attr(type='smoke') def test_get_users(self): # Get a list of users and find the test user self.data.setup_test_user() resp, users = self.client.get_users() self.assertThat([u['name'] for u in users], Contains(self.data.test_user), "Could not find %s" % self.data.test_user) @attr(type=['negative', 'gate']) def test_get_users_by_unauthorized_user(self): # Non admin user should not be authorized to get user list self.data.setup_test_user() self.assertRaises(exceptions.Unauthorized, self.non_admin_client.get_users) @attr(type=['negative', 'gate']) def test_get_users_request_without_token(self): # Request to get list of users without a valid token should fail token = self.client.get_auth() self.client.delete_token(token) self.assertRaises(exceptions.Unauthorized, self.client.get_users) self.client.clear_auth() @attr(type='gate') def test_list_users_for_tenant(self): # Return a list of all users for a tenant self.data.setup_test_tenant() user_ids = list() fetched_user_ids = list() resp, user1 = self.client.create_user('tenant_user1', 'password1', self.data.tenant['id'], 'user1@123') self.assertEquals('200', resp['status']) user_ids.append(user1['id']) self.data.users.append(user1) resp, user2 = self.client.create_user('tenant_user2', 'password2', self.data.tenant['id'], 'user2@123') self.assertEquals('200', resp['status']) user_ids.append(user2['id']) self.data.users.append(user2) #List of users for the respective tenant ID resp, body = self.client.list_users_for_tenant(self.data.tenant['id']) self.assertTrue(resp['status'] in ('200', '203')) for i in body: fetched_user_ids.append(i['id']) #verifying the user Id in the list missing_users =\ [user for user in user_ids if user not in fetched_user_ids] self.assertEqual(0, len(missing_users), "Failed to find user %s in fetched list" % ', '.join(m_user for m_user in missing_users)) @attr(type='gate') def test_list_users_with_roles_for_tenant(self): # Return list of users on tenant when roles are assigned to users self.data.setup_test_user() self.data.setup_test_role() user = self.get_user_by_name(self.data.test_user) tenant = self.get_tenant_by_name(self.data.test_tenant) role = self.get_role_by_name(self.data.test_role) #Assigning roles to two users user_ids = list() fetched_user_ids = list() user_ids.append(user['id']) resp, role = self.client.assign_user_role(tenant['id'], user['id'], role['id']) self.assertEquals('200', resp['status']) resp, second_user = self.client.create_user('second_user', 'password1', self.data.tenant['id'], 'user1@123') self.assertEquals('200', resp['status']) user_ids.append(second_user['id']) self.data.users.append(second_user) resp, role = self.client.assign_user_role(tenant['id'], second_user['id'], role['id']) self.assertEquals('200', resp['status']) #List of users with roles for the respective tenant ID resp, body = self.client.list_users_for_tenant(self.data.tenant['id']) self.assertEquals('200', resp['status']) for i in body: fetched_user_ids.append(i['id']) #verifying the user Id in the list missing_users = [missing_user for missing_user in user_ids if missing_user not in fetched_user_ids] self.assertEqual(0, len(missing_users), "Failed to find user %s in fetched list" % ', '.join(m_user for m_user in missing_users)) @attr(type=['negative', 'gate']) def test_list_users_with_invalid_tenant(self): # Should not be able to return a list of all # users for a nonexistant tenant #Assign invalid tenant ids invalid_id = list() invalid_id.append(rand_name('999')) invalid_id.append('alpha') invalid_id.append(rand_name("dddd@#%%^$")) invalid_id.append('!@#()$%^&*?<>{}[]') #List the users with invalid tenant id for invalid in invalid_id: self.assertRaises(exceptions.NotFound, self.client.list_users_for_tenant, invalid) class UsersTestXML(UsersTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/identity/admin/test_services.py0000664000175000017500000001003612161375672026065 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.identity import base from tempest.common.utils.data_utils import rand_name from tempest import exceptions from tempest.test import attr class ServicesTestJSON(base.BaseIdentityAdminTest): _interface = 'json' @attr(type='smoke') def test_create_get_delete_service(self): # GET Service try: #Creating a Service name = rand_name('service-') type = rand_name('type--') description = rand_name('description-') resp, service_data = self.client.create_service( name, type, description=description) self.assertTrue(resp['status'].startswith('2')) #Verifying response body of create service self.assertTrue('id' in service_data) self.assertFalse(service_data['id'] is None) self.assertTrue('name' in service_data) self.assertEqual(name, service_data['name']) self.assertTrue('type' in service_data) self.assertEqual(type, service_data['type']) self.assertTrue('description' in service_data) self.assertEqual(description, service_data['description']) #Get service resp, fetched_service = self.client.get_service(service_data['id']) self.assertTrue(resp['status'].startswith('2')) #verifying the existence of service created self.assertTrue('id' in fetched_service) self.assertEquals(fetched_service['id'], service_data['id']) self.assertTrue('name' in fetched_service) self.assertEqual(fetched_service['name'], service_data['name']) self.assertTrue('type' in fetched_service) self.assertEqual(fetched_service['type'], service_data['type']) self.assertTrue('description' in fetched_service) self.assertEqual(fetched_service['description'], service_data['description']) finally: if 'service_data' in locals(): # Deleting the service created in this method resp, _ = self.client.delete_service(service_data['id']) self.assertEqual(resp['status'], '204') # Checking whether service is deleted successfully self.assertRaises(exceptions.NotFound, self.client.get_service, service_data['id']) @attr(type='smoke') def test_list_services(self): # Create, List, Verify and Delete Services services = [] for _ in xrange(3): name = rand_name('service-') type = rand_name('type--') description = rand_name('description-') resp, service = self.client.create_service( name, type, description=description) services.append(service) service_ids = map(lambda x: x['id'], services) def delete_services(): for service_id in service_ids: self.client.delete_service(service_id) self.addCleanup(delete_services) # List and Verify Services resp, body = self.client.list_services() self.assertTrue(resp['status'].startswith('2')) found = [service for service in body if service['id'] in service_ids] self.assertEqual(len(found), len(services), 'Services not found') class ServicesTestXML(ServicesTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/identity/admin/__init__.py0000664000175000017500000000000012161375672024730 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/identity/admin/test_tenants.py0000664000175000017500000002527412161375672025730 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.identity import base from tempest.common.utils.data_utils import rand_name from tempest import exceptions from tempest.test import attr class TenantsTestJSON(base.BaseIdentityAdminTest): _interface = 'json' @attr(type='gate') def test_list_tenants_by_unauthorized_user(self): # Non-admin user should not be able to list tenants self.assertRaises(exceptions.Unauthorized, self.non_admin_client.list_tenants) @attr(type='gate') def test_list_tenant_request_without_token(self): # Request to list tenants without a valid token should fail token = self.client.get_auth() self.client.delete_token(token) self.assertRaises(exceptions.Unauthorized, self.client.list_tenants) self.client.clear_auth() @attr(type='gate') def test_tenant_list_delete(self): # Create several tenants and delete them tenants = [] for _ in xrange(3): resp, tenant = self.client.create_tenant(rand_name('tenant-new')) self.data.tenants.append(tenant) tenants.append(tenant) tenant_ids = map(lambda x: x['id'], tenants) resp, body = self.client.list_tenants() self.assertTrue(resp['status'].startswith('2')) found = [tenant for tenant in body if tenant['id'] in tenant_ids] self.assertEqual(len(found), len(tenants), 'Tenants not created') for tenant in tenants: resp, body = self.client.delete_tenant(tenant['id']) self.assertTrue(resp['status'].startswith('2')) self.data.tenants.remove(tenant) resp, body = self.client.list_tenants() found = [tenant for tenant in body if tenant['id'] in tenant_ids] self.assertFalse(any(found), 'Tenants failed to delete') @attr(type='gate') def test_tenant_delete_by_unauthorized_user(self): # Non-admin user should not be able to delete a tenant tenant_name = rand_name('tenant-') resp, tenant = self.client.create_tenant(tenant_name) self.data.tenants.append(tenant) self.assertRaises(exceptions.Unauthorized, self.non_admin_client.delete_tenant, tenant['id']) @attr(type='gate') def test_tenant_delete_request_without_token(self): # Request to delete a tenant without a valid token should fail tenant_name = rand_name('tenant-') resp, tenant = self.client.create_tenant(tenant_name) self.data.tenants.append(tenant) token = self.client.get_auth() self.client.delete_token(token) self.assertRaises(exceptions.Unauthorized, self.client.delete_tenant, tenant['id']) self.client.clear_auth() @attr(type='gate') def test_delete_non_existent_tenant(self): # Attempt to delete a non existent tenant should fail self.assertRaises(exceptions.NotFound, self.client.delete_tenant, 'junk_tenant_123456abc') @attr(type='gate') def test_tenant_create_with_description(self): # Create tenant with a description tenant_name = rand_name('tenant-') tenant_desc = rand_name('desc-') resp, body = self.client.create_tenant(tenant_name, description=tenant_desc) tenant = body self.data.tenants.append(tenant) st1 = resp['status'] tenant_id = body['id'] desc1 = body['description'] self.assertTrue(st1.startswith('2')) self.assertEqual(desc1, tenant_desc, 'Description should have ' 'been sent in response for create') resp, body = self.client.get_tenant(tenant_id) desc2 = body['description'] self.assertEqual(desc2, tenant_desc, 'Description does not appear' 'to be set') self.client.delete_tenant(tenant_id) self.data.tenants.remove(tenant) @attr(type='gate') def test_tenant_create_enabled(self): # Create a tenant that is enabled tenant_name = rand_name('tenant-') resp, body = self.client.create_tenant(tenant_name, enabled=True) tenant = body self.data.tenants.append(tenant) tenant_id = body['id'] st1 = resp['status'] en1 = body['enabled'] self.assertTrue(st1.startswith('2')) self.assertTrue(en1, 'Enable should be True in response') resp, body = self.client.get_tenant(tenant_id) en2 = body['enabled'] self.assertTrue(en2, 'Enable should be True in lookup') self.client.delete_tenant(tenant_id) self.data.tenants.remove(tenant) @attr(type='gate') def test_tenant_create_not_enabled(self): # Create a tenant that is not enabled tenant_name = rand_name('tenant-') resp, body = self.client.create_tenant(tenant_name, enabled=False) tenant = body self.data.tenants.append(tenant) tenant_id = body['id'] st1 = resp['status'] en1 = body['enabled'] self.assertTrue(st1.startswith('2')) self.assertEqual('false', str(en1).lower(), 'Enable should be False in response') resp, body = self.client.get_tenant(tenant_id) en2 = body['enabled'] self.assertEqual('false', str(en2).lower(), 'Enable should be False in lookup') self.client.delete_tenant(tenant_id) self.data.tenants.remove(tenant) @attr(type='gate') def test_tenant_create_duplicate(self): # Tenant names should be unique tenant_name = rand_name('tenant-dup-') resp, body = self.client.create_tenant(tenant_name) tenant = body self.data.tenants.append(tenant) tenant1_id = body.get('id') self.addCleanup(self.client.delete_tenant, tenant1_id) self.addCleanup(self.data.tenants.remove, tenant) self.assertRaises(exceptions.Duplicate, self.client.create_tenant, tenant_name) @attr(type='gate') def test_create_tenant_by_unauthorized_user(self): # Non-admin user should not be authorized to create a tenant tenant_name = rand_name('tenant-') self.assertRaises(exceptions.Unauthorized, self.non_admin_client.create_tenant, tenant_name) @attr(type='gate') def test_create_tenant_request_without_token(self): # Create tenant request without a token should not be authorized tenant_name = rand_name('tenant-') token = self.client.get_auth() self.client.delete_token(token) self.assertRaises(exceptions.Unauthorized, self.client.create_tenant, tenant_name) self.client.clear_auth() @attr(type='gate') def test_create_tenant_with_empty_name(self): # Tenant name should not be empty self.assertRaises(exceptions.BadRequest, self.client.create_tenant, name='') @attr(type='gate') def test_create_tenants_name_length_over_64(self): # Tenant name length should not be greater than 64 characters tenant_name = 'a' * 65 self.assertRaises(exceptions.BadRequest, self.client.create_tenant, tenant_name) @attr(type='gate') def test_tenant_update_name(self): # Update name attribute of a tenant t_name1 = rand_name('tenant-') resp, body = self.client.create_tenant(t_name1) tenant = body self.data.tenants.append(tenant) t_id = body['id'] resp1_name = body['name'] t_name2 = rand_name('tenant2-') resp, body = self.client.update_tenant(t_id, name=t_name2) st2 = resp['status'] resp2_name = body['name'] self.assertTrue(st2.startswith('2')) self.assertNotEqual(resp1_name, resp2_name) resp, body = self.client.get_tenant(t_id) resp3_name = body['name'] self.assertNotEqual(resp1_name, resp3_name) self.assertEqual(t_name1, resp1_name) self.assertEqual(resp2_name, resp3_name) self.client.delete_tenant(t_id) self.data.tenants.remove(tenant) @attr(type='gate') def test_tenant_update_desc(self): # Update description attribute of a tenant t_name = rand_name('tenant-') t_desc = rand_name('desc-') resp, body = self.client.create_tenant(t_name, description=t_desc) tenant = body self.data.tenants.append(tenant) t_id = body['id'] resp1_desc = body['description'] t_desc2 = rand_name('desc2-') resp, body = self.client.update_tenant(t_id, description=t_desc2) st2 = resp['status'] resp2_desc = body['description'] self.assertTrue(st2.startswith('2')) self.assertNotEqual(resp1_desc, resp2_desc) resp, body = self.client.get_tenant(t_id) resp3_desc = body['description'] self.assertNotEqual(resp1_desc, resp3_desc) self.assertEqual(t_desc, resp1_desc) self.assertEqual(resp2_desc, resp3_desc) self.client.delete_tenant(t_id) self.data.tenants.remove(tenant) @attr(type='gate') def test_tenant_update_enable(self): # Update the enabled attribute of a tenant t_name = rand_name('tenant-') t_en = False resp, body = self.client.create_tenant(t_name, enabled=t_en) tenant = body self.data.tenants.append(tenant) t_id = body['id'] resp1_en = body['enabled'] t_en2 = True resp, body = self.client.update_tenant(t_id, enabled=t_en2) st2 = resp['status'] resp2_en = body['enabled'] self.assertTrue(st2.startswith('2')) self.assertNotEqual(resp1_en, resp2_en) resp, body = self.client.get_tenant(t_id) resp3_en = body['enabled'] self.assertNotEqual(resp1_en, resp3_en) self.assertEqual('false', str(resp1_en).lower()) self.assertEqual(resp2_en, resp3_en) self.client.delete_tenant(t_id) self.data.tenants.remove(tenant) class TenantsTestXML(TenantsTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/identity/__init__.py0000664000175000017500000000156212161375672023656 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.common import log as logging LOG = logging.getLogger(__name__) # All identity tests -- single setup function def setup_package(): LOG.debug("Entering tempest.api.identity.setup_package") tempest-2013.2.a1291.g23a1b4f/tempest/api/identity/base.py0000664000175000017500000001015012161375672023022 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest import clients from tempest.common.utils.data_utils import rand_name import tempest.test class BaseIdentityAdminTest(tempest.test.BaseTestCase): @classmethod def setUpClass(cls): os = clients.AdminManager(interface=cls._interface) cls.client = os.identity_client cls.token_client = os.token_client cls.endpoints_client = os.endpoints_client cls.v3_client = os.identity_v3_client cls.service_client = os.service_client cls.policy_client = os.policy_client if not cls.client.has_admin_extensions(): raise cls.skipException("Admin extensions disabled") cls.data = DataGenerator(cls.client) os = clients.Manager(interface=cls._interface) cls.non_admin_client = os.identity_client @classmethod def tearDownClass(cls): cls.data.teardown_all() def disable_user(self, user_name): user = self.get_user_by_name(user_name) self.client.enable_disable_user(user['id'], False) def disable_tenant(self, tenant_name): tenant = self.get_tenant_by_name(tenant_name) self.client.update_tenant(tenant['id'], enabled=False) def get_user_by_name(self, name): _, users = self.client.get_users() user = [u for u in users if u['name'] == name] if len(user) > 0: return user[0] def get_tenant_by_name(self, name): _, tenants = self.client.list_tenants() tenant = [t for t in tenants if t['name'] == name] if len(tenant) > 0: return tenant[0] def get_role_by_name(self, name): _, roles = self.client.list_roles() role = [r for r in roles if r['name'] == name] if len(role) > 0: return role[0] class DataGenerator(object): def __init__(self, client): self.client = client self.users = [] self.tenants = [] self.roles = [] self.role_name = None def setup_test_user(self): """Set up a test user.""" self.setup_test_tenant() self.test_user = rand_name('test_user_') self.test_password = rand_name('pass_') self.test_email = self.test_user + '@testmail.tm' resp, self.user = self.client.create_user(self.test_user, self.test_password, self.tenant['id'], self.test_email) self.users.append(self.user) def setup_test_tenant(self): """Set up a test tenant.""" self.test_tenant = rand_name('test_tenant_') self.test_description = rand_name('desc_') resp, self.tenant = self.client.create_tenant( name=self.test_tenant, description=self.test_description) self.tenants.append(self.tenant) def setup_test_role(self): """Set up a test role.""" self.test_role = rand_name('role') resp, self.role = self.client.create_role(self.test_role) self.roles.append(self.role) def teardown_all(self): for user in self.users: self.client.delete_user(user['id']) for tenant in self.tenants: self.client.delete_tenant(tenant['id']) for role in self.roles: self.client.delete_role(role['id']) tempest-2013.2.a1291.g23a1b4f/tempest/api/volume/0000775000175000017500000000000012161375700021207 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/volume/test_volumes_actions.py0000664000175000017500000001017212161375672026043 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.volume.base import BaseVolumeTest from tempest.common.utils.data_utils import rand_name from tempest.test import attr class VolumesActionsTest(BaseVolumeTest): _interface = "json" @classmethod def setUpClass(cls): super(VolumesActionsTest, cls).setUpClass() cls.client = cls.volumes_client cls.servers_client = cls.servers_client # Create a test shared instance and volume for attach/detach tests srv_name = rand_name('Instance-') vol_name = rand_name('Volume-') resp, cls.server = cls.servers_client.create_server(srv_name, cls.image_ref, cls.flavor_ref) cls.servers_client.wait_for_server_status(cls.server['id'], 'ACTIVE') resp, cls.volume = cls.client.create_volume(size=1, display_name=vol_name) cls.client.wait_for_volume_status(cls.volume['id'], 'available') @classmethod def tearDownClass(cls): # Delete the test instance and volume cls.client.delete_volume(cls.volume['id']) cls.client.wait_for_resource_deletion(cls.volume['id']) cls.servers_client.delete_server(cls.server['id']) cls.client.wait_for_resource_deletion(cls.server['id']) super(VolumesActionsTest, cls).tearDownClass() @attr(type='smoke') def test_attach_detach_volume_to_instance(self): # Volume is attached and detached successfully from an instance try: mountpoint = '/dev/vdc' resp, body = self.client.attach_volume(self.volume['id'], self.server['id'], mountpoint) self.assertEqual(202, resp.status) self.client.wait_for_volume_status(self.volume['id'], 'in-use') except Exception: self.fail("Could not attach volume to instance") finally: # Detach the volume from the instance resp, body = self.client.detach_volume(self.volume['id']) self.assertEqual(202, resp.status) self.client.wait_for_volume_status(self.volume['id'], 'available') @attr(type='gate') def test_get_volume_attachment(self): # Verify that a volume's attachment information is retrieved mountpoint = '/dev/vdc' resp, body = self.client.attach_volume(self.volume['id'], self.server['id'], mountpoint) self.client.wait_for_volume_status(self.volume['id'], 'in-use') self.assertEqual(202, resp.status) try: resp, volume = self.client.get_volume(self.volume['id']) self.assertEqual(200, resp.status) self.assertTrue('attachments' in volume) attachment = volume['attachments'][0] self.assertEqual(mountpoint, attachment['device']) self.assertEqual(self.server['id'], attachment['server_id']) self.assertEqual(self.volume['id'], attachment['id']) self.assertEqual(self.volume['id'], attachment['volume_id']) except Exception: self.fail("Could not get attachment details from volume") finally: self.client.detach_volume(self.volume['id']) self.client.wait_for_volume_status(self.volume['id'], 'available') tempest-2013.2.a1291.g23a1b4f/tempest/api/volume/admin/0000775000175000017500000000000012161375700022277 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/volume/admin/test_multi_backend.py0000664000175000017500000001361712161375672026531 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # 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 tempest.api.volume import base from tempest.common import log as logging from tempest.common.utils.data_utils import rand_name from tempest.services.volume.json.admin import volume_types_client from tempest.services.volume.json import volumes_client from tempest.test import attr LOG = logging.getLogger(__name__) class VolumeMultiBackendTest(base.BaseVolumeAdminTest): _interface = "json" @classmethod def setUpClass(cls): super(VolumeMultiBackendTest, cls).setUpClass() if not cls.config.volume.multi_backend_enabled: raise cls.skipException("Cinder multi-backend feature disabled") cls.backend1_name = cls.config.volume.backend1_name cls.backend2_name = cls.config.volume.backend2_name adm_user = cls.config.identity.admin_username adm_pass = cls.config.identity.admin_password adm_tenant = cls.config.identity.admin_tenant_name auth_url = cls.config.identity.uri cls.volume_client = volumes_client.VolumesClientJSON(cls.config, adm_user, adm_pass, auth_url, adm_tenant) cls.type_client = volume_types_client.VolumeTypesClientJSON(cls.config, adm_user, adm_pass, auth_url, adm_tenant) cls.volume_type_id_list = [] cls.volume_id_list = [] try: # Volume/Type creation (uses backend1_name) type1_name = rand_name('Type-') vol1_name = rand_name('Volume-') extra_specs1 = {"volume_backend_name": cls.backend1_name} resp, cls.type1 = cls.type_client.create_volume_type( type1_name, extra_specs=extra_specs1) cls.volume_type_id_list.append(cls.type1['id']) resp, cls.volume1 = cls.volume_client.create_volume( size=1, display_name=vol1_name, volume_type=type1_name) cls.volume_id_list.append(cls.volume1['id']) cls.volume_client.wait_for_volume_status(cls.volume1['id'], 'available') if cls.backend1_name != cls.backend2_name: # Volume/Type creation (uses backend2_name) type2_name = rand_name('Type-') vol2_name = rand_name('Volume-') extra_specs2 = {"volume_backend_name": cls.backend2_name} resp, cls.type2 = cls.type_client.create_volume_type( type2_name, extra_specs=extra_specs2) cls.volume_type_id_list.append(cls.type2['id']) resp, cls.volume2 = cls.volume_client.create_volume( size=1, display_name=vol2_name, volume_type=type2_name) cls.volume_id_list.append(cls.volume2['id']) cls.volume_client.wait_for_volume_status(cls.volume2['id'], 'available') except Exception: LOG.exception("setup failed") cls.tearDownClass() raise @classmethod def tearDownClass(cls): ## volumes deletion for volume_id in cls.volume_id_list: cls.volume_client.delete_volume(volume_id) cls.volume_client.wait_for_resource_deletion(volume_id) ## volume types deletion for volume_type_id in cls.volume_type_id_list: cls.type_client.delete_volume_type(volume_type_id) super(VolumeMultiBackendTest, cls).tearDownClass() @attr(type='smoke') def test_backend_name_reporting(self): # this test checks if os-vol-attr:host is populated correctly after # the multi backend feature has been enabled # if multi-backend is enabled: os-vol-attr:host should be like: # host@backend_name resp, volume = self.volume_client.get_volume(self.volume1['id']) self.assertEqual(200, resp.status) volume1_host = volume['os-vol-host-attr:host'] msg = ("multi-backend reporting incorrect values for volume %s" % self.volume1['id']) self.assertTrue(len(volume1_host.split("@")) > 1, msg) @attr(type='gate') def test_backend_name_distinction(self): # this test checks that the two volumes created at setUp don't # belong to the same backend (if they are, than the # volume backend distinction is not working properly) if self.backend1_name == self.backend2_name: raise self.skipException("backends configured with same name") resp, volume = self.volume_client.get_volume(self.volume1['id']) volume1_host = volume['os-vol-host-attr:host'] resp, volume = self.volume_client.get_volume(self.volume2['id']) volume2_host = volume['os-vol-host-attr:host'] msg = ("volumes %s and %s were created in the same backend" % (self.volume1['id'], self.volume2['id'])) self.assertNotEqual(volume1_host, volume2_host, msg) tempest-2013.2.a1291.g23a1b4f/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py0000664000175000017500000001275712161375672032751 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 uuid from tempest.api.volume import base from tempest.common.utils.data_utils import rand_name from tempest import exceptions from tempest.test import attr class ExtraSpecsNegativeTest(base.BaseVolumeAdminTest): _interface = 'json' @classmethod def setUpClass(cls): super(ExtraSpecsNegativeTest, cls).setUpClass() vol_type_name = rand_name('Volume-type-') cls.extra_specs = {"spec1": "val1"} resp, cls.volume_type = cls.client.create_volume_type(vol_type_name, extra_specs= cls.extra_specs) @classmethod def tearDownClass(cls): cls.client.delete_volume_type(cls.volume_type['id']) super(ExtraSpecsNegativeTest, cls).tearDownClass() @attr(type='gate') def test_update_no_body(self): # Should not update volume type extra specs with no body extra_spec = {"spec1": "val2"} self.assertRaises(exceptions.BadRequest, self.client.update_volume_type_extra_specs, self.volume_type['id'], extra_spec.keys()[0], None) @attr(type='gate') def test_update_nonexistent_extra_spec_id(self): # Should not update volume type extra specs with nonexistent id. extra_spec = {"spec1": "val2"} self.assertRaises(exceptions.BadRequest, self.client.update_volume_type_extra_specs, self.volume_type['id'], str(uuid.uuid4()), extra_spec) @attr(type='gate') def test_update_none_extra_spec_id(self): # Should not update volume type extra specs with none id. extra_spec = {"spec1": "val2"} self.assertRaises(exceptions.BadRequest, self.client.update_volume_type_extra_specs, self.volume_type['id'], None, extra_spec) @attr(type='gate') def test_update_multiple_extra_spec(self): # Should not update volume type extra specs with multiple specs as # body. extra_spec = {"spec1": "val2", 'spec2': 'val1'} self.assertRaises(exceptions.BadRequest, self.client.update_volume_type_extra_specs, self.volume_type['id'], extra_spec.keys()[0], extra_spec) @attr(type='gate') def test_create_nonexistent_type_id(self): # Should not create volume type extra spec for nonexistent volume # type id. extra_specs = {"spec2": "val1"} self.assertRaises(exceptions.NotFound, self.client.create_volume_type_extra_specs, str(uuid.uuid4()), extra_specs) @attr(type='gate') def test_create_none_body(self): # Should not create volume type extra spec for none POST body. self.assertRaises(exceptions.BadRequest, self.client.create_volume_type_extra_specs, self.volume_type['id'], None) @attr(type='gate') def test_create_invalid_body(self): # Should not create volume type extra spec for invalid POST body. self.assertRaises(exceptions.BadRequest, self.client.create_volume_type_extra_specs, self.volume_type['id'], ['invalid']) @attr(type='gate') def test_delete_nonexistent_volume_type_id(self): # Should not delete volume type extra spec for nonexistent # type id. extra_specs = {"spec1": "val1"} self.assertRaises(exceptions.NotFound, self.client.delete_volume_type_extra_specs, str(uuid.uuid4()), extra_specs.keys()[0]) @attr(type='gate') def test_list_nonexistent_volume_type_id(self): # Should not list volume type extra spec for nonexistent type id. self.assertRaises(exceptions.NotFound, self.client.list_volume_types_extra_specs, str(uuid.uuid4())) @attr(type='gate') def test_get_nonexistent_volume_type_id(self): # Should not get volume type extra spec for nonexistent type id. extra_specs = {"spec1": "val1"} self.assertRaises(exceptions.NotFound, self.client.get_volume_type_extra_specs, str(uuid.uuid4()), extra_specs.keys()[0]) @attr(type='gate') def test_get_nonexistent_extra_spec_id(self): # Should not get volume type extra spec for nonexistent extra spec # id. self.assertRaises(exceptions.NotFound, self.client.get_volume_type_extra_specs, self.volume_type['id'], str(uuid.uuid4())) class ExtraSpecsNegativeTestXML(ExtraSpecsNegativeTest): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/volume/admin/test_volume_types.py0000664000175000017500000001635712161375672026467 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.volume.base import BaseVolumeTest from tempest.common.utils.data_utils import rand_name from tempest.services.volume.json.admin import volume_types_client from tempest.test import attr class VolumeTypesTest(BaseVolumeTest): _interface = "json" @classmethod def setUpClass(cls): super(VolumeTypesTest, cls).setUpClass() adm_user = cls.config.identity.admin_username adm_pass = cls.config.identity.admin_password adm_tenant = cls.config.identity.admin_tenant_name auth_url = cls.config.identity.uri cls.client = volume_types_client.VolumeTypesClientJSON(cls.config, adm_user, adm_pass, auth_url, adm_tenant) @attr(type='smoke') def test_volume_type_list(self): # List Volume types. try: resp, body = self.client.list_volume_types() self.assertEqual(200, resp.status) self.assertTrue(type(body), list) except Exception: self.fail("Could not list volume types") @attr(type='smoke') def test_create_get_delete_volume_with_volume_type_and_extra_specs(self): # Create/get/delete volume with volume_type and extra spec. try: volume = {} vol_name = rand_name("volume-") vol_type_name = rand_name("volume-type-") extra_specs = {"storage_protocol": "iSCSI", "vendor_name": "Open Source"} body = {} resp, body = self.client.create_volume_type( vol_type_name, extra_specs=extra_specs) self.assertEqual(200, resp.status) self.assertTrue('id' in body) self.assertTrue('name' in body) resp, volume = self.volumes_client.create_volume( size=1, display_name=vol_name, volume_type=vol_type_name) self.assertEqual(200, resp.status) self.assertTrue('id' in volume) self.assertTrue('display_name' in volume) self.assertEqual(volume['display_name'], vol_name, "The created volume name is not equal " "to the requested name") self.assertTrue(volume['id'] is not None, "Field volume id is empty or not found.") self.volumes_client.wait_for_volume_status(volume['id'], 'available') resp, fetched_volume = self.volumes_client.get_volume(volume['id']) self.assertEqual(200, resp.status) self.assertEqual(vol_name, fetched_volume['display_name'], 'The fetched Volume is different ' 'from the created Volume') self.assertEqual(volume['id'], fetched_volume['id'], 'The fetched Volume is different ' 'from the created Volume') self.assertEqual(vol_type_name, fetched_volume['volume_type'], 'The fetched Volume is different ' 'from the created Volume') except Exception: self.fail("Could not create correct volume with volume_type") finally: if volume: # Delete the Volume if it was created resp, _ = self.volumes_client.delete_volume(volume['id']) self.assertEqual(202, resp.status) if body: resp, _ = self.client.delete_volume_type(body['id']) self.assertEqual(202, resp.status) @attr(type='smoke') def test_volume_type_create_delete(self): # Create/Delete volume type. try: name = rand_name("volume-type-") extra_specs = {"storage_protocol": "iSCSI", "vendor_name": "Open Source"} resp, body = self.client.create_volume_type( name, extra_specs=extra_specs) self.assertEqual(200, resp.status) self.assertTrue('id' in body) self.assertTrue('name' in body) self.assertEqual(body['name'], name, "The created volume_type name is not equal " "to the requested name") self.assertTrue(body['id'] is not None, "Field volume_type id is empty or not found.") resp, _ = self.client.delete_volume_type(body['id']) self.assertEqual(202, resp.status) except Exception: self.fail("Could not create a volume_type") @attr(type='smoke') def test_volume_type_create_get(self): # Create/get volume type. try: body = {} name = rand_name("volume-type-") extra_specs = {"storage_protocol": "iSCSI", "vendor_name": "Open Source"} resp, body = self.client.create_volume_type( name, extra_specs=extra_specs) self.assertEqual(200, resp.status) self.assertTrue('id' in body) self.assertTrue('name' in body) self.assertEqual(body['name'], name, "The created volume_type name is not equal " "to the requested name") self.assertTrue(body['id'] is not None, "Field volume_type id is empty or not found.") resp, fetched_volume_type = self.client.get_volume_type(body['id']) self.assertEqual(200, resp.status) self.assertEqual(name, fetched_volume_type['name'], 'The fetched Volume_type is different ' 'from the created Volume_type') self.assertEqual(str(body['id']), fetched_volume_type['id'], 'The fetched Volume_type is different ' 'from the created Volume_type') self.assertEqual(extra_specs, fetched_volume_type['extra_specs'], 'The fetched Volume_type is different ' 'from the created Volume_type') except Exception: self.fail("Could not create a volume_type") finally: if body: resp, _ = self.client.delete_volume_type(body['id']) self.assertEqual(202, resp.status) tempest-2013.2.a1291.g23a1b4f/tempest/api/volume/admin/__init__.py0000664000175000017500000000000012161375672024406 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/volume/admin/test_volume_types_negative.py0000664000175000017500000000411412161375672030335 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 uuid from tempest.api.volume import base from tempest import exceptions from tempest.test import attr class VolumeTypesNegativeTest(base.BaseVolumeAdminTest): _interface = 'json' @attr(type='gate') def test_create_with_nonexistent_volume_type(self): # Should not be able to create volume with nonexistent volume_type. self.assertRaises(exceptions.NotFound, self.volumes_client.create_volume, size=1, display_name=str(uuid.uuid4()), volume_type=str(uuid.uuid4())) @attr(type='gate') def test_create_with_empty_name(self): # Should not be able to create volume type with an empty name. self.assertRaises(exceptions.BadRequest, self.client.create_volume_type, '') @attr(type='gate') def test_get_nonexistent_type_id(self): # Should not be able to get volume type with nonexistent type id. self.assertRaises(exceptions.NotFound, self.client.get_volume_type, str(uuid.uuid4())) @attr(type='gate') def test_delete_nonexistent_type_id(self): # Should not be able to delete volume type with nonexistent type id. self.assertRaises(exceptions.NotFound, self.client.delete_volume_type, str(uuid.uuid4())) class VolumesTypesNegativeTestXML(VolumeTypesNegativeTest): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/volume/admin/test_volume_types_extra_specs.py0000664000175000017500000001036012161375672031053 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.volume import base from tempest.common.utils.data_utils import rand_name from tempest.test import attr class VolumeTypesExtraSpecsTest(base.BaseVolumeAdminTest): _interface = "json" @classmethod def setUpClass(cls): super(VolumeTypesExtraSpecsTest, cls).setUpClass() vol_type_name = rand_name('Volume-type-') resp, cls.volume_type = cls.client.create_volume_type(vol_type_name) @classmethod def tearDownClass(cls): cls.client.delete_volume_type(cls.volume_type['id']) super(VolumeTypesExtraSpecsTest, cls).tearDownClass() @attr(type='smoke') def test_volume_type_extra_specs_list(self): # List Volume types extra specs. try: extra_specs = {"spec1": "val1"} resp, body = self.client.create_volume_type_extra_specs( self.volume_type['id'], extra_specs) self.assertEqual(200, resp.status) self.assertEqual(extra_specs, body, "Volume type extra spec incorrectly created") resp, body = self.client.list_volume_types_extra_specs( self.volume_type['id']) self.assertEqual(200, resp.status) self.assertTrue(type(body), dict) self.assertTrue('spec1' in body, "Incorrect volume type extra" " spec returned") except Exception: self.fail("Could not list volume types extra specs") @attr(type='gate') def test_volume_type_extra_specs_update(self): # Update volume type extra specs try: extra_specs = {"spec2": "val1"} resp, body = self.client.create_volume_type_extra_specs( self.volume_type['id'], extra_specs) self.assertEqual(200, resp.status) self.assertEqual(extra_specs, body, "Volume type extra spec incorrectly created") extra_spec = {"spec2": "val2"} resp, body = self.client.update_volume_type_extra_specs( self.volume_type['id'], extra_spec.keys()[0], extra_spec) self.assertEqual(200, resp.status) self.assertTrue('spec2' in body, "Volume type extra spec incorrectly updated") self.assertEqual(extra_spec['spec2'], body['spec2'], "Volume type extra spec incorrectly updated") except Exception: self.fail("Couldnt update volume type extra spec") @attr(type='smoke') def test_volume_type_extra_spec_create_get_delete(self): # Create/Get/Delete volume type extra spec. try: extra_specs = {"spec3": "val1"} resp, body = self.client.create_volume_type_extra_specs( self.volume_type['id'], extra_specs) self.assertEqual(200, resp.status) self.assertEqual(extra_specs, body, "Volume type extra spec incorrectly created") resp, _ = self.client.get_volume_type_extra_specs( self.volume_type['id'], extra_specs.keys()[0]) self.assertEqual(200, resp.status) self.assertEqual(extra_specs, body, "Volume type extra spec incorrectly fetched") resp, _ = self.client.delete_volume_type_extra_specs( self.volume_type['id'], extra_specs.keys()[0]) self.assertEqual(202, resp.status) except Exception: self.fail("Could not create a volume_type extra spec") tempest-2013.2.a1291.g23a1b4f/tempest/api/volume/test_volumes_snapshots.py0000664000175000017500000000633612161375672026434 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # 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 tempest.api.volume import base from tempest.common import log as logging from tempest.common.utils.data_utils import rand_name from tempest.test import attr LOG = logging.getLogger(__name__) class VolumesSnapshotTest(base.BaseVolumeTest): _interface = "json" @classmethod def setUpClass(cls): super(VolumesSnapshotTest, cls).setUpClass() try: cls.volume_origin = cls.create_volume() except Exception: LOG.exception("setup failed") cls.tearDownClass() raise @classmethod def tearDownClass(cls): super(VolumesSnapshotTest, cls).tearDownClass() @attr(type='gate') def test_snapshot_create_get_list_delete(self): # Create a snapshot s_name = rand_name('snap') snapshot = self.create_snapshot(self.volume_origin['id'], display_name=s_name) # Get the snap and check for some of its details resp, snap_get = self.snapshots_client.get_snapshot(snapshot['id']) self.assertEqual(200, resp.status) self.assertEqual(self.volume_origin['id'], snap_get['volume_id'], "Referred volume origin mismatch") # Compare also with the output from the list action tracking_data = (snapshot['id'], snapshot['display_name']) resp, snaps_list = self.snapshots_client.list_snapshots() self.assertEqual(200, resp.status) snaps_data = [(f['id'], f['display_name']) for f in snaps_list] self.assertIn(tracking_data, snaps_data) # Delete the snapshot self.snapshots_client.delete_snapshot(snapshot['id']) self.assertEqual(200, resp.status) self.snapshots_client.wait_for_resource_deletion(snapshot['id']) self.snapshots.remove(snapshot) @attr(type='gate') def test_volume_from_snapshot(self): # Create a temporary snap using wrapper method from base, then # create a snap based volume, check resp code and deletes it snapshot = self.create_snapshot(self.volume_origin['id']) # NOTE(gfidente): size is required also when passing snapshot_id resp, volume = self.volumes_client.create_volume( size=1, snapshot_id=snapshot['id']) self.assertEqual(200, resp.status) self.volumes_client.wait_for_volume_status(volume['id'], 'available') self.volumes_client.delete_volume(volume['id']) self.volumes_client.wait_for_resource_deletion(volume['id']) self.clear_snapshots() class VolumesSnapshotTestXML(VolumesSnapshotTest): _interface = "xml" tempest-2013.2.a1291.g23a1b4f/tempest/api/volume/test_volumes_get.py0000664000175000017500000001171112161375672025162 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.volume import base from tempest.common.utils.data_utils import rand_name from tempest.test import attr class VolumesGetTest(base.BaseVolumeTest): _interface = "json" @classmethod def setUpClass(cls): super(VolumesGetTest, cls).setUpClass() cls.client = cls.volumes_client def _volume_create_get_delete(self, **kwargs): # Create a volume, Get it's details and Delete the volume try: volume = {} v_name = rand_name('Volume') metadata = {'Type': 'Test'} #Create a volume resp, volume = self.client.create_volume(size=1, display_name=v_name, metadata=metadata, **kwargs) self.assertEqual(200, resp.status) self.assertTrue('id' in volume) self.assertTrue('display_name' in volume) self.assertEqual(volume['display_name'], v_name, "The created volume name is not equal " "to the requested name") self.assertTrue(volume['id'] is not None, "Field volume id is empty or not found.") self.client.wait_for_volume_status(volume['id'], 'available') # Get Volume information resp, fetched_volume = self.client.get_volume(volume['id']) self.assertEqual(200, resp.status) self.assertEqual(v_name, fetched_volume['display_name'], 'The fetched Volume is different ' 'from the created Volume') self.assertEqual(volume['id'], fetched_volume['id'], 'The fetched Volume is different ' 'from the created Volume') self.assertEqual(metadata, fetched_volume['metadata'], 'The fetched Volume is different ' 'from the created Volume') except Exception: self.fail("Could not create a volume") finally: if volume: # Delete the Volume if it was created resp, _ = self.client.delete_volume(volume['id']) self.assertEqual(202, resp.status) self.client.wait_for_resource_deletion(volume['id']) @attr(type='gate') def test_volume_get_metadata_none(self): # Create a volume without passing metadata, get details, and delete try: volume = {} v_name = rand_name('Volume-') # Create a volume without metadata resp, volume = self.client.create_volume(size=1, display_name=v_name, metadata={}) self.assertEqual(200, resp.status) self.assertTrue('id' in volume) self.assertTrue('display_name' in volume) self.client.wait_for_volume_status(volume['id'], 'available') #GET Volume resp, fetched_volume = self.client.get_volume(volume['id']) self.assertEqual(200, resp.status) self.assertEqual(fetched_volume['metadata'], {}) except Exception: self.fail("Could not get volume metadata") finally: if volume: # Delete the Volume if it was created resp, _ = self.client.delete_volume(volume['id']) self.assertEqual(202, resp.status) self.client.wait_for_resource_deletion(volume['id']) @attr(type='smoke') def test_volume_create_get_delete(self): self._volume_create_get_delete() @attr(type='smoke') def test_volume_create_get_delete_from_image(self): self._volume_create_get_delete(imageRef=self.config.compute.image_ref) @attr(type='gate') def test_volume_create_get_delete_as_clone(self): origin = self.create_volume(size=1, display_name="Volume Origin") self._volume_create_get_delete(source_volid=origin['id']) class VolumesGetTestXML(VolumesGetTest): _interface = "xml" tempest-2013.2.a1291.g23a1b4f/tempest/api/volume/test_volumes_list.py0000664000175000017500000001067512161375672025366 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.volume import base from tempest.common.utils.data_utils import rand_name from tempest.test import attr class VolumesListTest(base.BaseVolumeTest): """ This test creates a number of 1G volumes. To run successfully, ensure that the backing file for the volume group that Nova uses has space for at least 3 1G volumes! If you are running a Devstack environment, ensure that the VOLUME_BACKING_FILE_SIZE is atleast 4G in your localrc """ _interface = 'json' @classmethod def setUpClass(cls): super(VolumesListTest, cls).setUpClass() cls.client = cls.volumes_client # Create 3 test volumes cls.volume_list = [] cls.volume_id_list = [] for i in range(3): v_name = rand_name('volume') metadata = {'Type': 'work'} try: resp, volume = cls.client.create_volume(size=1, display_name=v_name, metadata=metadata) cls.client.wait_for_volume_status(volume['id'], 'available') resp, volume = cls.client.get_volume(volume['id']) cls.volume_list.append(volume) cls.volume_id_list.append(volume['id']) except Exception: if cls.volume_list: # We could not create all the volumes, though we were able # to create *some* of the volumes. This is typically # because the backing file size of the volume group is # too small. So, here, we clean up whatever we did manage # to create and raise a SkipTest for volid in cls.volume_id_list: cls.client.delete_volume(volid) cls.client.wait_for_resource_deletion(volid) msg = ("Failed to create ALL necessary volumes to run " "test. This typically means that the backing file " "size of the nova-volumes group is too small to " "create the 3 volumes needed by this test case") raise cls.skipException(msg) raise @classmethod def tearDownClass(cls): # Delete the created volumes for volid in cls.volume_id_list: resp, _ = cls.client.delete_volume(volid) cls.client.wait_for_resource_deletion(volid) super(VolumesListTest, cls).tearDownClass() @attr(type='smoke') def test_volume_list(self): # Get a list of Volumes # Fetch all volumes resp, fetched_list = self.client.list_volumes() self.assertEqual(200, resp.status) # Now check if all the volumes created in setup are in fetched list missing_vols = [v for v in self.volume_list if v not in fetched_list] self.assertFalse(missing_vols, "Failed to find volume %s in fetched list" % ', '.join(m_vol['display_name'] for m_vol in missing_vols)) @attr(type='gate') def test_volume_list_with_details(self): # Get a list of Volumes with details # Fetch all Volumes resp, fetched_list = self.client.list_volumes_with_detail() self.assertEqual(200, resp.status) # Verify that all the volumes are returned missing_vols = [v for v in self.volume_list if v not in fetched_list] self.assertFalse(missing_vols, "Failed to find volume %s in fetched list" % ', '.join(m_vol['display_name'] for m_vol in missing_vols)) class VolumeListTestXML(VolumesListTest): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/volume/test_volumes_negative.py0000664000175000017500000001063112161375672026205 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.volume import base from tempest.common.utils.data_utils import rand_name from tempest import exceptions from tempest.test import attr class VolumesNegativeTest(base.BaseVolumeTest): _interface = 'json' @classmethod def setUpClass(cls): super(VolumesNegativeTest, cls).setUpClass() cls.client = cls.volumes_client @attr(type='gate') def test_volume_get_nonexistant_volume_id(self): # Should not be able to get a nonexistant volume #Creating a nonexistant volume id volume_id_list = [] resp, volumes = self.client.list_volumes() for i in range(len(volumes)): volume_id_list.append(volumes[i]['id']) while True: non_exist_id = rand_name('999') if non_exist_id not in volume_id_list: break #Trying to Get a non existant volume self.assertRaises(exceptions.NotFound, self.client.get_volume, non_exist_id) @attr(type='gate') def test_volume_delete_nonexistant_volume_id(self): # Should not be able to delete a nonexistant Volume # Creating nonexistant volume id volume_id_list = [] resp, volumes = self.client.list_volumes() for i in range(len(volumes)): volume_id_list.append(volumes[i]['id']) while True: non_exist_id = '12345678-abcd-4321-abcd-123456789098' if non_exist_id not in volume_id_list: break # Try to Delete a non existant volume self.assertRaises(exceptions.NotFound, self.client.delete_volume, non_exist_id) @attr(type='gate') def test_create_volume_with_invalid_size(self): # Should not be able to create volume with invalid size # in request v_name = rand_name('Volume-') metadata = {'Type': 'work'} self.assertRaises(exceptions.BadRequest, self.client.create_volume, size='#$%', display_name=v_name, metadata=metadata) @attr(type='gate') def test_create_volume_with_out_passing_size(self): # Should not be able to create volume without passing size # in request v_name = rand_name('Volume-') metadata = {'Type': 'work'} self.assertRaises(exceptions.BadRequest, self.client.create_volume, size='', display_name=v_name, metadata=metadata) @attr(type='gate') def test_create_volume_with_size_zero(self): # Should not be able to create volume with size zero v_name = rand_name('Volume-') metadata = {'Type': 'work'} self.assertRaises(exceptions.BadRequest, self.client.create_volume, size='0', display_name=v_name, metadata=metadata) @attr(type='gate') def test_get_invalid_volume_id(self): # Should not be able to get volume with invalid id self.assertRaises(exceptions.NotFound, self.client.get_volume, '#$%%&^&^') @attr(type='gate') def test_get_volume_without_passing_volume_id(self): # Should not be able to get volume when empty ID is passed self.assertRaises(exceptions.NotFound, self.client.get_volume, '') @attr(type='gate') def test_delete_invalid_volume_id(self): # Should not be able to delete volume when invalid ID is passed self.assertRaises(exceptions.NotFound, self.client.delete_volume, '!@#$%^&*()') @attr(type='gate') def test_delete_volume_without_passing_volume_id(self): # Should not be able to delete volume when empty ID is passed self.assertRaises(exceptions.NotFound, self.client.delete_volume, '') class VolumesNegativeTestXML(VolumesNegativeTest): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/volume/__init__.py0000664000175000017500000000000012161375672023316 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/volume/base.py0000664000175000017500000001660612161375672022514 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 time from tempest import clients from tempest.common import log as logging from tempest.common.utils.data_utils import rand_name from tempest import exceptions import tempest.test LOG = logging.getLogger(__name__) class BaseVolumeTest(tempest.test.BaseTestCase): """Base test case class for all Cinder API tests.""" @classmethod def setUpClass(cls): cls.isolated_creds = [] if cls.config.compute.allow_tenant_isolation: creds = cls._get_isolated_creds() username, tenant_name, password = creds os = clients.Manager(username=username, password=password, tenant_name=tenant_name, interface=cls._interface) else: os = clients.Manager(interface=cls._interface) cls.os = os cls.volumes_client = os.volumes_client cls.snapshots_client = os.snapshots_client cls.servers_client = os.servers_client cls.image_ref = cls.config.compute.image_ref cls.flavor_ref = cls.config.compute.flavor_ref cls.build_interval = cls.config.volume.build_interval cls.build_timeout = cls.config.volume.build_timeout cls.snapshots = [] cls.volumes = [] skip_msg = ("%s skipped as Cinder endpoint is not available" % cls.__name__) try: cls.volumes_client.keystone_auth(cls.os.username, cls.os.password, cls.os.auth_url, cls.volumes_client.service, cls.os.tenant_name) except exceptions.EndpointNotFound: cls.clear_isolated_creds() raise cls.skipException(skip_msg) @classmethod def _get_identity_admin_client(cls): """ Returns an instance of the Identity Admin API client """ os = clients.ComputeAdminManager() return os.identity_client @classmethod def _get_isolated_creds(cls): """ Creates a new set of user/tenant/password credentials for a **regular** user of the Volume API so that a test case can operate in an isolated tenant container. """ admin_client = cls._get_identity_admin_client() rand_name_root = rand_name(cls.__name__) if cls.isolated_creds: # Main user already created. Create the alt one... rand_name_root += '-alt' username = rand_name_root + "-user" email = rand_name_root + "@example.com" tenant_name = rand_name_root + "-tenant" tenant_desc = tenant_name + "-desc" password = "pass" resp, tenant = admin_client.create_tenant(name=tenant_name, description=tenant_desc) resp, user = admin_client.create_user(username, password, tenant['id'], email) # Store the complete creds (including UUID ids...) for later # but return just the username, tenant_name, password tuple # that the various clients will use. cls.isolated_creds.append((user, tenant)) return username, tenant_name, password @classmethod def clear_isolated_creds(cls): if not cls.isolated_creds: return admin_client = cls._get_identity_admin_client() for user, tenant in cls.isolated_creds: admin_client.delete_user(user['id']) admin_client.delete_tenant(tenant['id']) @classmethod def tearDownClass(cls): cls.clear_snapshots() cls.clear_volumes() cls.clear_isolated_creds() @classmethod def create_snapshot(cls, volume_id=1, **kwargs): """Wrapper utility that returns a test snapshot.""" resp, snapshot = cls.snapshots_client.create_snapshot(volume_id, **kwargs) assert 200 == resp.status cls.snapshots.append(snapshot) cls.snapshots_client.wait_for_snapshot_status(snapshot['id'], 'available') return snapshot #NOTE(afazekas): these create_* and clean_* could be defined # only in a single location in the source, and could be more general. @classmethod def create_volume(cls, size=1, **kwargs): """Wrapper utility that returns a test volume.""" resp, volume = cls.volumes_client.create_volume(size, **kwargs) assert 200 == resp.status cls.volumes.append(volume) cls.volumes_client.wait_for_volume_status(volume['id'], 'available') return volume @classmethod def clear_volumes(cls): for volume in cls.volumes: try: cls.volumes_client.delete_volume(volume['id']) except Exception: pass for volume in cls.volumes: try: cls.volumes_client.wait_for_resource_deletion(volume['id']) except Exception: pass @classmethod def clear_snapshots(cls): for snapshot in cls.snapshots: try: cls.snapshots_client.delete_snapshot(snapshot['id']) except Exception: pass for snapshot in cls.snapshots: try: cls.snapshots_client.wait_for_resource_deletion(snapshot['id']) except Exception: pass def wait_for(self, condition): """Repeatedly calls condition() until a timeout.""" start_time = int(time.time()) while True: try: condition() except Exception: pass else: return if int(time.time()) - start_time >= self.build_timeout: condition() return time.sleep(self.build_interval) class BaseVolumeAdminTest(BaseVolumeTest): """Base test case class for all Volume Admin API tests.""" @classmethod def setUpClass(cls): super(BaseVolumeAdminTest, cls).setUpClass() cls.adm_user = cls.config.identity.admin_username cls.adm_pass = cls.config.identity.admin_password cls.adm_tenant = cls.config.identity.admin_tenant_name if not all((cls.adm_user, cls.adm_pass, cls.adm_tenant)): msg = ("Missing Volume Admin API credentials " "in configuration.") raise cls.skipException(msg) cls.os_adm = clients.AdminManager(interface=cls._interface) cls.client = cls.os_adm.volume_types_client tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/0000775000175000017500000000000012161375700021354 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/volumes/0000775000175000017500000000000012161375700023046 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/volumes/test_volumes_get.py0000664000175000017500000001004212161375672027015 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.compute import base from tempest.common.utils.data_utils import rand_name from tempest.test import attr class VolumesGetTestJSON(base.BaseComputeTest): _interface = 'json' @classmethod def setUpClass(cls): super(VolumesGetTestJSON, cls).setUpClass() cls.client = cls.volumes_extensions_client @attr(type='smoke') def test_volume_create_get_delete(self): # CREATE, GET, DELETE Volume volume = None v_name = rand_name('Volume-%s-') % self._interface metadata = {'Type': 'work'} #Create volume resp, volume = self.client.create_volume(size=1, display_name=v_name, metadata=metadata) self.addCleanup(self._delete_volume, volume) self.assertEqual(200, resp.status) self.assertTrue('id' in volume) self.assertTrue('displayName' in volume) self.assertEqual(volume['displayName'], v_name, "The created volume name is not equal " "to the requested name") self.assertTrue(volume['id'] is not None, "Field volume id is empty or not found.") #Wait for Volume status to become ACTIVE self.client.wait_for_volume_status(volume['id'], 'available') #GET Volume resp, fetched_volume = self.client.get_volume(volume['id']) self.assertEqual(200, resp.status) #Verfication of details of fetched Volume self.assertEqual(v_name, fetched_volume['displayName'], 'The fetched Volume is different ' 'from the created Volume') self.assertEqual(volume['id'], fetched_volume['id'], 'The fetched Volume is different ' 'from the created Volume') self.assertEqual(metadata, fetched_volume['metadata'], 'The fetched Volume is different ' 'from the created Volume') @attr(type='gate') def test_volume_get_metadata_none(self): # CREATE, GET empty metadata dict v_name = rand_name('Volume-') #Create volume resp, volume = self.client.create_volume(size=1, display_name=v_name, metadata={}) self.addCleanup(self._delete_volume, volume) self.assertEqual(200, resp.status) self.assertTrue('id' in volume) self.assertTrue('displayName' in volume) #Wait for Volume status to become ACTIVE self.client.wait_for_volume_status(volume['id'], 'available') #GET Volume resp, fetched_volume = self.client.get_volume(volume['id']) self.assertEqual(200, resp.status) self.assertEqual(fetched_volume['metadata'], {}) def _delete_volume(self, volume): #Delete the Volume created in this method try: resp, _ = self.client.delete_volume(volume['id']) self.assertEqual(202, resp.status) #Checking if the deleted Volume still exists self.client.wait_for_resource_deletion(volume['id']) except KeyError: return class VolumesGetTestXML(VolumesGetTestJSON): _interface = "xml" tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/volumes/test_volumes_list.py0000664000175000017500000001103312161375672027212 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.compute import base from tempest.common.utils.data_utils import rand_name from tempest.test import attr class VolumesTestJSON(base.BaseComputeTest): """ This test creates a number of 1G volumes. To run successfully, ensure that the backing file for the volume group that Nova uses has space for at least 3 1G volumes! If you are running a Devstack environment, ensure that the VOLUME_BACKING_FILE_SIZE is atleast 4G in your localrc """ _interface = 'json' @classmethod def setUpClass(cls): super(VolumesTestJSON, cls).setUpClass() cls.client = cls.volumes_extensions_client # Create 3 Volumes cls.volume_list = [] cls.volume_id_list = [] for i in range(3): v_name = rand_name('volume-%s') metadata = {'Type': 'work'} v_name += cls._interface try: resp, volume = cls.client.create_volume(size=1, display_name=v_name, metadata=metadata) cls.client.wait_for_volume_status(volume['id'], 'available') resp, volume = cls.client.get_volume(volume['id']) cls.volume_list.append(volume) cls.volume_id_list.append(volume['id']) except Exception: if cls.volume_list: # We could not create all the volumes, though we were able # to create *some* of the volumes. This is typically # because the backing file size of the volume group is # too small. So, here, we clean up whatever we did manage # to create and raise a SkipTest for volume in cls.volume_list: cls.client.delete_volume(volume) msg = ("Failed to create ALL necessary volumes to run " "test. This typically means that the backing file " "size of the nova-volumes group is too small to " "create the 3 volumes needed by this test case") raise cls.skipException(msg) raise @classmethod def tearDownClass(cls): # Delete the created Volumes for volume in cls.volume_list: resp, _ = cls.client.delete_volume(volume['id']) cls.client.wait_for_resource_deletion(volume['id']) super(VolumesTestJSON, cls).tearDownClass() @attr(type='gate') def test_volume_list(self): # Should return the list of Volumes # Fetch all Volumes resp, fetched_list = self.client.list_volumes() self.assertEqual(200, resp.status) # Now check if all the Volumes created in setup are in fetched list missing_volumes = [ v for v in self.volume_list if v not in fetched_list ] self.assertFalse(missing_volumes, "Failed to find volume %s in fetched list" % ', '.join(m_vol['displayName'] for m_vol in missing_volumes)) @attr(type='gate') def test_volume_list_with_details(self): # Should return the list of Volumes with details #Fetch all Volumes resp, fetched_list = self.client.list_volumes_with_detail() self.assertEqual(200, resp.status) #Now check if all the Volumes created in setup are in fetched list missing_volumes = [ v for v in self.volume_list if v not in fetched_list ] self.assertFalse(missing_volumes, "Failed to find volume %s in fetched list" % ', '.join(m_vol['displayName'] for m_vol in missing_volumes)) class VolumesTestXML(VolumesTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/volumes/test_attach_volume.py0000664000175000017500000001060412161375672027323 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 IBM Corp. # 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 testtools from tempest.api.compute import base from tempest.common.utils.linux.remote_client import RemoteClient import tempest.config from tempest.test import attr class AttachVolumeTestJSON(base.BaseComputeTest): _interface = 'json' run_ssh = tempest.config.TempestConfig().compute.run_ssh def __init__(self, *args, **kwargs): super(AttachVolumeTestJSON, self).__init__(*args, **kwargs) self.server = None self.volume = None self.attached = False @classmethod def setUpClass(cls): super(AttachVolumeTestJSON, cls).setUpClass() cls.device = 'vdb' def _detach(self, server_id, volume_id): self.servers_client.detach_volume(server_id, volume_id) self.volumes_client.wait_for_volume_status(volume_id, 'available') def _delete(self, volume): if self.volume: self.volumes_client.delete_volume(self.volume['id']) self.volume = None def _create_and_attach(self): # Start a server and wait for it to become ready resp, server = self.create_server(wait_until='ACTIVE', adminPass='password') # Record addresses so that we can ssh later resp, server['addresses'] = \ self.servers_client.list_addresses(server['id']) # Create a volume and wait for it to become ready resp, volume = self.volumes_client.create_volume(1, display_name='test') self.volume = volume self.volumes_client.wait_for_volume_status(volume['id'], 'available') # Attach the volume to the server self.servers_client.attach_volume(server['id'], volume['id'], device='/dev/%s' % self.device) self.volumes_client.wait_for_volume_status(volume['id'], 'in-use') self.attached = True @testtools.skipIf(not run_ssh, 'SSH required for this test') @attr(type='gate') def test_attach_detach_volume(self): # Stop and Start a server with an attached volume, ensuring that # the volume remains attached. try: self._create_and_attach() server = self.server volume = self.volume self.servers_client.stop(server['id']) self.servers_client.wait_for_server_status(server['id'], 'SHUTOFF') self.servers_client.start(server['id']) self.servers_client.wait_for_server_status(server['id'], 'ACTIVE') linux_client = RemoteClient(server, self.ssh_user, server['adminPass']) partitions = linux_client.get_partitions() self.assertTrue(self.device in partitions) self._detach(server['id'], volume['id']) self.attached = False self.servers_client.stop(server['id']) self.servers_client.wait_for_server_status(server['id'], 'SHUTOFF') self.servers_client.start(server['id']) self.servers_client.wait_for_server_status(server['id'], 'ACTIVE') linux_client = RemoteClient(server, self.ssh_user, server['adminPass']) partitions = linux_client.get_partitions() self.assertFalse(self.device in partitions) except Exception: self.fail("The test_attach_detach_volume is faild!") finally: if self.attached: self._detach(server['id'], volume['id']) # NOTE(maurosr): here we do the cleanup for volume, servers are # dealt on BaseComputeTest.tearDownClass self._delete(self.volume) class AttachVolumeTestXML(AttachVolumeTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/volumes/test_volumes_negative.py0000664000175000017500000001076512161375672030054 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.compute import base from tempest.common.utils.data_utils import rand_name from tempest import exceptions from tempest.test import attr class VolumesNegativeTest(base.BaseComputeTest): _interface = 'json' @classmethod def setUpClass(cls): super(VolumesNegativeTest, cls).setUpClass() cls.client = cls.volumes_extensions_client @attr(type='gate') def test_volume_get_nonexistant_volume_id(self): # Negative: Should not be able to get details of nonexistant volume #Creating a nonexistant volume id volume_id_list = list() resp, body = self.client.list_volumes() for i in range(len(body)): volume_id_list.append(body[i]['id']) while True: non_exist_id = rand_name('999') if non_exist_id not in volume_id_list: break # Trying to GET a non existant volume self.assertRaises(exceptions.NotFound, self.client.get_volume, non_exist_id) @attr(type='gate') def test_volume_delete_nonexistant_volume_id(self): # Negative: Should not be able to delete nonexistant Volume # Creating nonexistant volume id volume_id_list = list() resp, body = self.client.list_volumes() for i in range(len(body)): volume_id_list.append(body[i]['id']) while True: non_exist_id = rand_name('999') if non_exist_id not in volume_id_list: break # Trying to DELETE a non existant volume self.assertRaises(exceptions.NotFound, self.client.delete_volume, non_exist_id) @attr(type='gate') def test_create_volume_with_invalid_size(self): # Negative: Should not be able to create volume with invalid size # in request v_name = rand_name('Volume-') metadata = {'Type': 'work'} self.assertRaises(exceptions.BadRequest, self.client.create_volume, size='#$%', display_name=v_name, metadata=metadata) @attr(type='gate') def test_create_volume_with_out_passing_size(self): # Negative: Should not be able to create volume without passing size # in request v_name = rand_name('Volume-') metadata = {'Type': 'work'} self.assertRaises(exceptions.BadRequest, self.client.create_volume, size='', display_name=v_name, metadata=metadata) @attr(type='gate') def test_create_volume_with_size_zero(self): # Negative: Should not be able to create volume with size zero v_name = rand_name('Volume-') metadata = {'Type': 'work'} self.assertRaises(exceptions.BadRequest, self.client.create_volume, size='0', display_name=v_name, metadata=metadata) @attr(type='gate') def test_get_invalid_volume_id(self): # Negative: Should not be able to get volume with invalid id self.assertRaises(exceptions.NotFound, self.client.get_volume, '#$%%&^&^') @attr(type='gate') def test_get_volume_without_passing_volume_id(self): # Negative: Should not be able to get volume when empty ID is passed self.assertRaises(exceptions.NotFound, self.client.get_volume, '') @attr(type='gate') def test_delete_invalid_volume_id(self): # Negative: Should not be able to delete volume when invalid ID is # passed self.assertRaises(exceptions.NotFound, self.client.delete_volume, '!@#$%^&*()') @attr(type='gate') def test_delete_volume_without_passing_volume_id(self): # Negative: Should not be able to delete volume when empty ID is passed self.assertRaises(exceptions.NotFound, self.client.delete_volume, '') class VolumesNegativeTestXML(VolumesNegativeTest): _interface = "xml" tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/volumes/__init__.py0000664000175000017500000000000012161375672025155 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/flavors/0000775000175000017500000000000012161375700023030 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/flavors/test_flavors.py0000664000175000017500000001466012161375672026134 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.compute import base from tempest import exceptions from tempest.test import attr class FlavorsTestJSON(base.BaseComputeTest): _interface = 'json' @classmethod def setUpClass(cls): super(FlavorsTestJSON, cls).setUpClass() cls.client = cls.flavors_client @attr(type='smoke') def test_list_flavors(self): # List of all flavors should contain the expected flavor resp, flavors = self.client.list_flavors() resp, flavor = self.client.get_flavor_details(self.flavor_ref) flavor_min_detail = {'id': flavor['id'], 'links': flavor['links'], 'name': flavor['name']} self.assertTrue(flavor_min_detail in flavors) @attr(type='smoke') def test_list_flavors_with_detail(self): # Detailed list of all flavors should contain the expected flavor resp, flavors = self.client.list_flavors_with_detail() resp, flavor = self.client.get_flavor_details(self.flavor_ref) self.assertTrue(flavor in flavors) @attr(type='smoke') def test_get_flavor(self): # The expected flavor details should be returned resp, flavor = self.client.get_flavor_details(self.flavor_ref) self.assertEqual(self.flavor_ref, int(flavor['id'])) @attr(type=['negative', 'gate']) def test_get_non_existant_flavor(self): # flavor details are not returned for non existant flavors self.assertRaises(exceptions.NotFound, self.client.get_flavor_details, 999) @attr(type='gate') def test_list_flavors_limit_results(self): # Only the expected number of flavors should be returned params = {'limit': 1} resp, flavors = self.client.list_flavors(params) self.assertEqual(1, len(flavors)) @attr(type='gate') def test_list_flavors_detailed_limit_results(self): # Only the expected number of flavors (detailed) should be returned params = {'limit': 1} resp, flavors = self.client.list_flavors_with_detail(params) self.assertEqual(1, len(flavors)) @attr(type='gate') def test_list_flavors_using_marker(self): # The list of flavors should start from the provided marker resp, flavors = self.client.list_flavors() flavor_id = flavors[0]['id'] params = {'marker': flavor_id} resp, flavors = self.client.list_flavors(params) self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]), 'The list of flavors did not start after the marker.') @attr(type='gate') def test_list_flavors_detailed_using_marker(self): # The list of flavors should start from the provided marker resp, flavors = self.client.list_flavors_with_detail() flavor_id = flavors[0]['id'] params = {'marker': flavor_id} resp, flavors = self.client.list_flavors_with_detail(params) self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]), 'The list of flavors did not start after the marker.') @attr(type='gate') def test_list_flavors_detailed_filter_by_min_disk(self): # The detailed list of flavors should be filtered by disk space resp, flavors = self.client.list_flavors_with_detail() flavors = sorted(flavors, key=lambda k: k['disk']) flavor_id = flavors[0]['id'] params = {'minDisk': flavors[0]['disk'] + 1} resp, flavors = self.client.list_flavors_with_detail(params) self.assertFalse(any([i for i in flavors if i['id'] == flavor_id])) @attr(type='gate') def test_list_flavors_detailed_filter_by_min_ram(self): # The detailed list of flavors should be filtered by RAM resp, flavors = self.client.list_flavors_with_detail() flavors = sorted(flavors, key=lambda k: k['ram']) flavor_id = flavors[0]['id'] params = {'minRam': flavors[0]['ram'] + 1} resp, flavors = self.client.list_flavors_with_detail(params) self.assertFalse(any([i for i in flavors if i['id'] == flavor_id])) @attr(type='gate') def test_list_flavors_filter_by_min_disk(self): # The list of flavors should be filtered by disk space resp, flavors = self.client.list_flavors_with_detail() flavors = sorted(flavors, key=lambda k: k['disk']) flavor_id = flavors[0]['id'] params = {'minDisk': flavors[0]['disk'] + 1} resp, flavors = self.client.list_flavors(params) self.assertFalse(any([i for i in flavors if i['id'] == flavor_id])) @attr(type='gate') def test_list_flavors_filter_by_min_ram(self): # The list of flavors should be filtered by RAM resp, flavors = self.client.list_flavors_with_detail() flavors = sorted(flavors, key=lambda k: k['ram']) flavor_id = flavors[0]['id'] params = {'minRam': flavors[0]['ram'] + 1} resp, flavors = self.client.list_flavors(params) self.assertFalse(any([i for i in flavors if i['id'] == flavor_id])) @attr(type=['negative', 'gate']) def test_invalid_minRam_filter(self): self.assertRaises(exceptions.BadRequest, self.client.list_flavors_with_detail, {'minRam': 'invalid'}) @attr(type=['negative', 'gate']) def test_invalid_minDisk_filter(self): self.assertRaises(exceptions.BadRequest, self.client.list_flavors_with_detail, {'minDisk': 'invalid'}) @attr(type=['negative', 'gate']) def test_get_flavor_details_for_invalid_flavor_id(self): # Ensure 404 returned for non-existant flavor ID self.assertRaises(exceptions.NotFound, self.client.get_flavor_details, 9999) class FlavorsTestXML(FlavorsTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/flavors/__init__.py0000664000175000017500000000000012161375672025137 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/test_auth_token.py0000664000175000017500000000330412161375672025136 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp # # 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 testtools from tempest.api.compute import base import tempest.config as config class AuthTokenTestJSON(base.BaseComputeTest): _interface = 'json' @classmethod def setUpClass(cls): super(AuthTokenTestJSON, cls).setUpClass() cls.servers_v2 = cls.os.servers_client cls.servers_v3 = cls.os.servers_client_v3_auth def test_v2_token(self): # Can get a token using v2 of the identity API and use that to perform # an operation on the compute service. # Doesn't matter which compute API is used, # picking list_servers because it's easy. self.servers_v2.list_servers() @testtools.skipIf(not config.TempestConfig().identity.uri_v3, 'v3 auth client not configured') def test_v3_token(self): # Can get a token using v3 of the identity API and use that to perform # an operation on the compute service. # Doesn't matter which compute API is used, # picking list_servers because it's easy. self.servers_v3.list_servers() class AuthTokenTestXML(AuthTokenTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/limits/0000775000175000017500000000000012161375700022655 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/limits/test_absolute_limits.py0000664000175000017500000000566712161375672027513 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.compute import base from tempest import exceptions from tempest.test import attr class AbsoluteLimitsTestJSON(base.BaseComputeTest): _interface = 'json' @classmethod def setUpClass(cls): super(AbsoluteLimitsTestJSON, cls).setUpClass() cls.client = cls.limits_client cls.server_client = cls.servers_client @attr(type='gate') def test_absLimits_get(self): # To check if all limits are present in the response resp, absolute_limits = self.client.get_absolute_limits() expected_elements = ['maxImageMeta', 'maxPersonality', 'maxPersonalitySize', 'maxServerMeta', 'maxTotalCores', 'maxTotalFloatingIps', 'maxSecurityGroups', 'maxSecurityGroupRules', 'maxTotalInstances', 'maxTotalKeypairs', 'maxTotalRAMSize', 'totalCoresUsed', 'totalFloatingIpsUsed', 'totalSecurityGroupsUsed', 'totalInstancesUsed', 'totalRAMUsed'] # check whether all expected elements exist missing_elements =\ [ele for ele in expected_elements if ele not in absolute_limits] self.assertEqual(0, len(missing_elements), "Failed to find element %s in absolute limits list" % ', '.join(ele for ele in missing_elements)) @attr(type=['negative', 'gate']) def test_max_image_meta_exceed_limit(self): #We should not create vm with image meta over maxImageMeta limit # Get max limit value max_meta = self.client.get_specific_absolute_limit('maxImageMeta') #Create server should fail, since we are passing > metadata Limit! max_meta_data = int(max_meta) + 1 meta_data = {} for xx in range(max_meta_data): meta_data[str(xx)] = str(xx) self.assertRaises(exceptions.OverLimit, self.server_client.create_server, name='test', meta=meta_data, flavor_ref=self.flavor_ref, image_ref=self.image_ref) class AbsoluteLimitsTestXML(AbsoluteLimitsTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/limits/__init__.py0000664000175000017500000000000012161375672024764 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/servers/0000775000175000017500000000000012161375700023045 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/servers/test_disk_config.py0000664000175000017500000001175512161375672026756 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 testtools from tempest.api import compute from tempest.api.compute import base from tempest.test import attr class ServerDiskConfigTestJSON(base.BaseComputeTest): _interface = 'json' @classmethod def setUpClass(cls): if not compute.DISK_CONFIG_ENABLED: msg = "DiskConfig extension not enabled." raise cls.skipException(msg) super(ServerDiskConfigTestJSON, cls).setUpClass() cls.client = cls.os.servers_client @attr(type='gate') def test_rebuild_server_with_manual_disk_config(self): # A server should be rebuilt using the manual disk config option resp, server = self.create_server(disk_config='AUTO', wait_until='ACTIVE') #Verify the specified attributes are set correctly resp, server = self.client.get_server(server['id']) self.assertEqual('AUTO', server['OS-DCF:diskConfig']) resp, server = self.client.rebuild(server['id'], self.image_ref_alt, disk_config='MANUAL') #Wait for the server to become active self.client.wait_for_server_status(server['id'], 'ACTIVE') #Verify the specified attributes are set correctly resp, server = self.client.get_server(server['id']) self.assertEqual('MANUAL', server['OS-DCF:diskConfig']) #Delete the server resp, body = self.client.delete_server(server['id']) @attr(type='gate') def test_rebuild_server_with_auto_disk_config(self): # A server should be rebuilt using the auto disk config option resp, server = self.create_server(disk_config='MANUAL', wait_until='ACTIVE') #Verify the specified attributes are set correctly resp, server = self.client.get_server(server['id']) self.assertEqual('MANUAL', server['OS-DCF:diskConfig']) resp, server = self.client.rebuild(server['id'], self.image_ref_alt, disk_config='AUTO') #Wait for the server to become active self.client.wait_for_server_status(server['id'], 'ACTIVE') #Verify the specified attributes are set correctly resp, server = self.client.get_server(server['id']) self.assertEqual('AUTO', server['OS-DCF:diskConfig']) #Delete the server resp, body = self.client.delete_server(server['id']) @testtools.skipUnless(compute.RESIZE_AVAILABLE, 'Resize not available.') @attr(type='gate') def test_resize_server_from_manual_to_auto(self): # A server should be resized from manual to auto disk config resp, server = self.create_server(disk_config='MANUAL', wait_until='ACTIVE') #Resize with auto option self.client.resize(server['id'], self.flavor_ref_alt, disk_config='AUTO') self.client.wait_for_server_status(server['id'], 'VERIFY_RESIZE') self.client.confirm_resize(server['id']) self.client.wait_for_server_status(server['id'], 'ACTIVE') resp, server = self.client.get_server(server['id']) self.assertEqual('AUTO', server['OS-DCF:diskConfig']) #Delete the server resp, body = self.client.delete_server(server['id']) @testtools.skipUnless(compute.RESIZE_AVAILABLE, 'Resize not available.') @attr(type='gate') def test_resize_server_from_auto_to_manual(self): # A server should be resized from auto to manual disk config resp, server = self.create_server(disk_config='AUTO', wait_until='ACTIVE') #Resize with manual option self.client.resize(server['id'], self.flavor_ref_alt, disk_config='MANUAL') self.client.wait_for_server_status(server['id'], 'VERIFY_RESIZE') self.client.confirm_resize(server['id']) self.client.wait_for_server_status(server['id'], 'ACTIVE') resp, server = self.client.get_server(server['id']) self.assertEqual('MANUAL', server['OS-DCF:diskConfig']) #Delete the server resp, body = self.client.delete_server(server['id']) class ServerDiskConfigTestXML(ServerDiskConfigTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/servers/test_servers.py0000664000175000017500000001121212161375672026154 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.compute import base from tempest.common.utils.data_utils import rand_name from tempest.test import attr class ServersTestJSON(base.BaseComputeTest): _interface = 'json' @classmethod def setUpClass(cls): super(ServersTestJSON, cls).setUpClass() cls.client = cls.servers_client def tearDown(self): self.clear_servers() super(ServersTestJSON, self).tearDown() @attr(type='gate') def test_create_server_with_admin_password(self): # If an admin password is provided on server creation, the server's # root password should be set to that password. resp, server = self.create_server(adminPass='testpassword') # Verify the password is set correctly in the response self.assertEqual('testpassword', server['adminPass']) @attr(type='gate') def test_create_with_existing_server_name(self): # Creating a server with a name that already exists is allowed # TODO(sdague): clear out try, we do cleanup one layer up server_name = rand_name('server') resp, server = self.create_server(name=server_name, wait_until='ACTIVE') id1 = server['id'] resp, server = self.create_server(name=server_name, wait_until='ACTIVE') id2 = server['id'] self.assertNotEqual(id1, id2, "Did not create a new server") resp, server = self.client.get_server(id1) name1 = server['name'] resp, server = self.client.get_server(id2) name2 = server['name'] self.assertEqual(name1, name2) @attr(type='gate') def test_create_specify_keypair(self): # Specify a keypair while creating a server key_name = rand_name('key') resp, keypair = self.keypairs_client.create_keypair(key_name) resp, body = self.keypairs_client.list_keypairs() resp, server = self.create_server(key_name=key_name) self.assertEqual('202', resp['status']) self.client.wait_for_server_status(server['id'], 'ACTIVE') resp, server = self.client.get_server(server['id']) self.assertEqual(key_name, server['key_name']) @attr(type='gate') def test_update_server_name(self): # The server name should be changed to the the provided value resp, server = self.create_server(wait_until='ACTIVE') # Update the server with a new name resp, server = self.client.update_server(server['id'], name='newname') self.assertEquals(200, resp.status) self.client.wait_for_server_status(server['id'], 'ACTIVE') # Verify the name of the server has changed resp, server = self.client.get_server(server['id']) self.assertEqual('newname', server['name']) @attr(type='gate') def test_update_access_server_address(self): # The server's access addresses should reflect the provided values resp, server = self.create_server(wait_until='ACTIVE') # Update the IPv4 and IPv6 access addresses resp, body = self.client.update_server(server['id'], accessIPv4='1.1.1.1', accessIPv6='::babe:202:202') self.assertEqual(200, resp.status) self.client.wait_for_server_status(server['id'], 'ACTIVE') # Verify the access addresses have been updated resp, server = self.client.get_server(server['id']) self.assertEqual('1.1.1.1', server['accessIPv4']) self.assertEqual('::babe:202:202', server['accessIPv6']) @attr(type='gate') def test_delete_server_while_in_building_state(self): # Delete a server while it's VM state is Building resp, server = self.create_server(wait_until='BUILD') resp, _ = self.client.delete_server(server['id']) self.assertEqual('204', resp['status']) class ServersTestXML(ServersTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/servers/test_server_personality.py0000664000175000017500000000515512161375672030433 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 base64 from tempest.api.compute import base from tempest import exceptions from tempest.test import attr class ServerPersonalityTestJSON(base.BaseComputeTest): _interface = 'json' @classmethod def setUpClass(cls): super(ServerPersonalityTestJSON, cls).setUpClass() cls.client = cls.servers_client cls.user_client = cls.limits_client @attr(type='gate') def test_personality_files_exceed_limit(self): # Server creation should fail if greater than the maximum allowed # number of files are injected into the server. file_contents = 'This is a test file.' personality = [] max_file_limit = \ self.user_client.get_specific_absolute_limit("maxPersonality") for i in range(0, int(max_file_limit) + 1): path = 'etc/test' + str(i) + '.txt' personality.append({'path': path, 'contents': base64.b64encode(file_contents)}) self.assertRaises(exceptions.OverLimit, self.create_server, personality=personality) @attr(type='gate') def test_can_create_server_with_max_number_personality_files(self): # Server should be created successfully if maximum allowed number of # files is injected into the server during creation. file_contents = 'This is a test file.' max_file_limit = \ self.user_client.get_specific_absolute_limit("maxPersonality") person = [] for i in range(0, int(max_file_limit)): path = 'etc/test' + str(i) + '.txt' person.append({ 'path': path, 'contents': base64.b64encode(file_contents), }) resp, server = self.create_server(personality=person) self.addCleanup(self.client.delete_server, server['id']) self.assertEqual('202', resp['status']) class ServerPersonalityTestXML(ServerPersonalityTestJSON): _interface = "xml" tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/servers/test_list_servers_negative.py0000664000175000017500000002161512161375672031101 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 datetime from tempest.api import compute from tempest.api.compute import base from tempest import clients from tempest import exceptions from tempest.test import attr class ListServersNegativeTestJSON(base.BaseComputeTest): _interface = 'json' @classmethod def setUpClass(cls): super(ListServersNegativeTestJSON, cls).setUpClass() cls.client = cls.servers_client cls.servers = [] if compute.MULTI_USER: if cls.config.compute.allow_tenant_isolation: creds = cls._get_isolated_creds() username, tenant_name, password = creds cls.alt_manager = clients.Manager(username=username, password=password, tenant_name=tenant_name) else: # Use the alt_XXX credentials in the config file cls.alt_manager = clients.AltManager() cls.alt_client = cls.alt_manager.servers_client # Under circumstances when there is not a tenant/user # created for the test case, the test case checks # to see if there are existing servers for the # either the normal user/tenant or the alt user/tenant # and if so, the whole test is skipped. We do this # because we assume a baseline of no servers at the # start of the test instead of destroying any existing # servers. resp, body = cls.client.list_servers() servers = body['servers'] num_servers = len(servers) if num_servers > 0: username = cls.os.username tenant_name = cls.os.tenant_name msg = ("User/tenant %(username)s/%(tenant_name)s already have " "existing server instances. Skipping test.") % locals() raise cls.skipException(msg) resp, body = cls.alt_client.list_servers() servers = body['servers'] num_servers = len(servers) if num_servers > 0: username = cls.alt_manager.username tenant_name = cls.alt_manager.tenant_name msg = ("Alt User/tenant %(username)s/%(tenant_name)s already have " "existing server instances. Skipping test.") % locals() raise cls.skipException(msg) # The following servers are created for use # by the test methods in this class. These # servers are cleaned up automatically in the # tearDownClass method of the super-class. cls.existing_fixtures = [] cls.deleted_fixtures = [] for x in xrange(2): resp, srv = cls.create_server() cls.existing_fixtures.append(srv) resp, srv = cls.create_server() cls.client.delete_server(srv['id']) # We ignore errors on termination because the server may # be put into ERROR status on a quick spawn, then delete, # as the compute node expects the instance local status # to be spawning, not deleted. See LP Bug#1061167 cls.client.wait_for_server_termination(srv['id'], ignore_error=True) cls.deleted_fixtures.append(srv) @attr(type='gate') def test_list_servers_with_a_deleted_server(self): # Verify deleted servers do not show by default in list servers # List servers and verify server not returned resp, body = self.client.list_servers() servers = body['servers'] deleted_ids = [s['id'] for s in self.deleted_fixtures] actual = [srv for srv in servers if srv['id'] in deleted_ids] self.assertEqual('200', resp['status']) self.assertEqual([], actual) @attr(type='gate') def test_list_servers_by_non_existing_image(self): # Listing servers for a non existing image returns empty list non_existing_image = '1234abcd-zzz0-aaa9-ppp3-0987654abcde' resp, body = self.client.list_servers(dict(image=non_existing_image)) servers = body['servers'] self.assertEqual('200', resp['status']) self.assertEqual([], servers) @attr(type='gate') def test_list_servers_by_non_existing_flavor(self): # Listing servers by non existing flavor returns empty list non_existing_flavor = 1234 resp, body = self.client.list_servers(dict(flavor=non_existing_flavor)) servers = body['servers'] self.assertEqual('200', resp['status']) self.assertEqual([], servers) @attr(type='gate') def test_list_servers_by_non_existing_server_name(self): # Listing servers for a non existent server name returns empty list non_existing_name = 'junk_server_1234' resp, body = self.client.list_servers(dict(name=non_existing_name)) servers = body['servers'] self.assertEqual('200', resp['status']) self.assertEqual([], servers) @attr(type='gate') def test_list_servers_status_non_existing(self): # Return an empty list when invalid status is specified non_existing_status = 'BALONEY' resp, body = self.client.list_servers(dict(status=non_existing_status)) servers = body['servers'] self.assertEqual('200', resp['status']) self.assertEqual([], servers) @attr(type='gate') def test_list_servers_by_limits(self): # List servers by specifying limits resp, body = self.client.list_servers({'limit': 1}) self.assertEqual('200', resp['status']) #when _interface='xml', one element for servers_links in servers self.assertEqual(1, len([x for x in body['servers'] if 'id' in x])) @attr(type='gate') def test_list_servers_by_limits_greater_than_actual_count(self): # List servers by specifying a greater value for limit resp, body = self.client.list_servers({'limit': 100}) self.assertEqual('200', resp['status']) self.assertEqual(len(self.existing_fixtures), len(body['servers'])) @attr(type='gate') def test_list_servers_by_limits_pass_string(self): # Return an error if a string value is passed for limit self.assertRaises(exceptions.BadRequest, self.client.list_servers, {'limit': 'testing'}) @attr(type='gate') def test_list_servers_by_limits_pass_negative_value(self): # Return an error if a negative value for limit is passed self.assertRaises(exceptions.BadRequest, self.client.list_servers, {'limit': -1}) @attr(type='gate') def test_list_servers_by_changes_since(self): # Servers are listed by specifying changes-since date since = datetime.datetime.utcnow() - datetime.timedelta(minutes=2) changes_since = {'changes-since': since.isoformat()} resp, body = self.client.list_servers(changes_since) self.assertEqual('200', resp['status']) # changes-since returns all instances, including deleted. num_expected = (len(self.existing_fixtures) + len(self.deleted_fixtures)) self.assertEqual(num_expected, len(body['servers'])) @attr(type='gate') def test_list_servers_by_changes_since_invalid_date(self): # Return an error when invalid date format is passed self.assertRaises(exceptions.BadRequest, self.client.list_servers, {'changes-since': '2011/01/01'}) @attr(type='gate') def test_list_servers_by_changes_since_future_date(self): # Return an empty list when a date in the future is passed changes_since = {'changes-since': '2051-01-01T12:34:00Z'} resp, body = self.client.list_servers(changes_since) self.assertEqual('200', resp['status']) self.assertEqual(0, len(body['servers'])) @attr(type='gate') def test_list_servers_detail_server_is_deleted(self): # Server details are not listed for a deleted server deleted_ids = [s['id'] for s in self.deleted_fixtures] resp, body = self.client.list_servers_with_detail() servers = body['servers'] actual = [srv for srv in servers if srv['id'] in deleted_ids] self.assertEqual('200', resp['status']) self.assertEqual([], actual) class ListServersNegativeTestXML(ListServersNegativeTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/servers/test_create_server.py0000664000175000017500000001263012161375672027321 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 base64 import netaddr import testtools from tempest.api import compute from tempest.api.compute import base from tempest.common.utils.data_utils import rand_name from tempest.common.utils.linux.remote_client import RemoteClient import tempest.config from tempest.test import attr class ServersTestJSON(base.BaseComputeTest): _interface = 'json' run_ssh = tempest.config.TempestConfig().compute.run_ssh disk_config = 'AUTO' @classmethod def setUpClass(cls): super(ServersTestJSON, cls).setUpClass() cls.meta = {'hello': 'world'} cls.accessIPv4 = '1.1.1.1' cls.accessIPv6 = '0000:0000:0000:0000:0000:babe:220.12.22.2' cls.name = rand_name('server') file_contents = 'This is a test file.' personality = [{'path': '/test.txt', 'contents': base64.b64encode(file_contents)}] cls.client = cls.servers_client cli_resp = cls.create_server(name=cls.name, meta=cls.meta, accessIPv4=cls.accessIPv4, accessIPv6=cls.accessIPv6, personality=personality, disk_config=cls.disk_config) cls.resp, cls.server_initial = cli_resp cls.password = cls.server_initial['adminPass'] cls.client.wait_for_server_status(cls.server_initial['id'], 'ACTIVE') resp, cls.server = cls.client.get_server(cls.server_initial['id']) @attr(type='smoke') def test_create_server_response(self): # Check that the required fields are returned with values self.assertEqual(202, self.resp.status) self.assertTrue(self.server_initial['id'] is not None) self.assertTrue(self.server_initial['adminPass'] is not None) @attr(type='smoke') def test_verify_server_details(self): # Verify the specified server attributes are set correctly self.assertEqual(self.accessIPv4, self.server['accessIPv4']) # NOTE(maurosr): See http://tools.ietf.org/html/rfc5952 (section 4) # Here we compare directly with the canonicalized format. self.assertEqual(self.server['accessIPv6'], str(netaddr.IPAddress(self.accessIPv6))) self.assertEqual(self.name, self.server['name']) self.assertEqual(self.image_ref, self.server['image']['id']) self.assertEqual(str(self.flavor_ref), self.server['flavor']['id']) self.assertEqual(self.meta, self.server['metadata']) @attr(type='smoke') def test_list_servers(self): # The created server should be in the list of all servers resp, body = self.client.list_servers() servers = body['servers'] found = any([i for i in servers if i['id'] == self.server['id']]) self.assertTrue(found) @attr(type='smoke') def test_list_servers_with_detail(self): # The created server should be in the detailed list of all servers resp, body = self.client.list_servers_with_detail() servers = body['servers'] found = any([i for i in servers if i['id'] == self.server['id']]) self.assertTrue(found) @testtools.skipIf(not run_ssh, 'Instance validation tests are disabled.') @attr(type='gate') def test_can_log_into_created_server(self): # Check that the user can authenticate with the generated password linux_client = RemoteClient(self.server, self.ssh_user, self.password) self.assertTrue(linux_client.can_authenticate()) @testtools.skipIf(not run_ssh, 'Instance validation tests are disabled.') @attr(type='gate') def test_verify_created_server_vcpus(self): # Verify that the number of vcpus reported by the instance matches # the amount stated by the flavor resp, flavor = self.flavors_client.get_flavor_details(self.flavor_ref) linux_client = RemoteClient(self.server, self.ssh_user, self.password) self.assertEqual(flavor['vcpus'], linux_client.get_number_of_vcpus()) @testtools.skipIf(not run_ssh, 'Instance validation tests are disabled.') @attr(type='gate') def test_host_name_is_same_as_server_name(self): # Verify the instance host name is the same as the server name linux_client = RemoteClient(self.server, self.ssh_user, self.password) self.assertTrue(linux_client.hostname_equals_servername(self.name)) class ServersTestManualDisk(ServersTestJSON): disk_config = 'MANUAL' @classmethod def setUpClass(cls): if not compute.DISK_CONFIG_ENABLED: msg = "DiskConfig extension not enabled." raise cls.skipException(msg) super(ServersTestManualDisk, cls).setUpClass() class ServersTestXML(ServersTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/servers/test_multiple_create.py0000664000175000017500000000730712161375672027653 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 IBM Corp # 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 tempest.api.compute import base from tempest.common.utils.data_utils import rand_name from tempest import exceptions from tempest.test import attr class MultipleCreateTestJSON(base.BaseComputeTest): _interface = 'json' _name = 'multiple-create-test' def _generate_name(self): return rand_name(self._name) def _create_multiple_servers(self, name=None, wait_until=None, **kwargs): """ This is the right way to create_multiple servers and manage to get the created servers into the servers list to be cleaned up after all. """ kwargs['name'] = kwargs.get('name', self._generate_name()) resp, body = self.create_server(**kwargs) return resp, body @attr(type='gate') def test_multiple_create(self): resp, body = self._create_multiple_servers(wait_until='ACTIVE', min_count=1, max_count=2) # NOTE(maurosr): do status response check and also make sure that # reservation_id is not in the response body when the request send # contains return_reservation_id=False self.assertEqual('202', resp['status']) self.assertFalse('reservation_id' in body) @attr(type=['negative', 'gate']) def test_min_count_less_than_one(self): invalid_min_count = 0 self.assertRaises(exceptions.BadRequest, self._create_multiple_servers, min_count=invalid_min_count) @attr(type=['negative', 'gate']) def test_min_count_non_integer(self): invalid_min_count = 2.5 self.assertRaises(exceptions.BadRequest, self._create_multiple_servers, min_count=invalid_min_count) @attr(type=['negative', 'gate']) def test_max_count_less_than_one(self): invalid_max_count = 0 self.assertRaises(exceptions.BadRequest, self._create_multiple_servers, max_count=invalid_max_count) @attr(type=['negative', 'gate']) def test_max_count_non_integer(self): invalid_max_count = 2.5 self.assertRaises(exceptions.BadRequest, self._create_multiple_servers, max_count=invalid_max_count) @attr(type=['negative', 'gate']) def test_max_count_less_than_min_count(self): min_count = 3 max_count = 2 self.assertRaises(exceptions.BadRequest, self._create_multiple_servers, min_count=min_count, max_count=max_count) @attr(type='gate') def test_multiple_create_with_reservation_return(self): resp, body = self._create_multiple_servers(wait_until='ACTIVE', min_count=1, max_count=2, return_reservation_id=True) self.assertTrue(resp['status'], 202) self.assertIn('reservation_id', body) class MultipleCreateTestXML(MultipleCreateTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/servers/test_servers_negative.py0000664000175000017500000002216412161375672030046 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 sys from tempest.api.compute import base from tempest import clients from tempest.common.utils.data_utils import rand_name from tempest import exceptions from tempest.test import attr class ServersNegativeTest(base.BaseComputeTest): _interface = 'json' @classmethod def setUpClass(cls): super(ServersNegativeTest, cls).setUpClass() cls.client = cls.servers_client cls.img_client = cls.images_client cls.alt_os = clients.AltManager() cls.alt_client = cls.alt_os.servers_client @attr(type=['negative', 'gate']) def test_server_name_blank(self): # Create a server with name parameter empty self.assertRaises(exceptions.BadRequest, self.create_server, name='') @attr(type=['negative', 'gate']) def test_personality_file_contents_not_encoded(self): # Use an unencoded file when creating a server with personality file_contents = 'This is a test file.' person = [{'path': '/etc/testfile.txt', 'contents': file_contents}] self.assertRaises(exceptions.BadRequest, self.create_server, personality=person) @attr(type=['negative', 'gate']) def test_create_with_invalid_image(self): # Create a server with an unknown image self.assertRaises(exceptions.BadRequest, self.create_server, image_id=-1) @attr(type=['negative', 'gate']) def test_create_with_invalid_flavor(self): # Create a server with an unknown flavor self.assertRaises(exceptions.BadRequest, self.create_server, flavor=-1,) @attr(type=['negative', 'gate']) def test_invalid_access_ip_v4_address(self): # An access IPv4 address must match a valid address pattern IPv4 = '1.1.1.1.1.1' self.assertRaises(exceptions.BadRequest, self.create_server, accessIPv4=IPv4) @attr(type=['negative', 'gate']) def test_invalid_ip_v6_address(self): # An access IPv6 address must match a valid address pattern IPv6 = 'notvalid' self.assertRaises(exceptions.BadRequest, self.create_server, accessIPv6=IPv6) @attr(type=['negative', 'gate']) def test_reboot_deleted_server(self): # Reboot a deleted server resp, server = self.create_server() self.server_id = server['id'] self.client.delete_server(self.server_id) self.client.wait_for_server_termination(self.server_id) self.assertRaises(exceptions.NotFound, self.client.reboot, self.server_id, 'SOFT') @attr(type=['negative', 'gate']) def test_rebuild_deleted_server(self): # Rebuild a deleted server resp, server = self.create_server() self.server_id = server['id'] self.client.delete_server(self.server_id) self.client.wait_for_server_termination(self.server_id) self.assertRaises(exceptions.NotFound, self.client.rebuild, self.server_id, self.image_ref_alt) @attr(type=['negative', 'gate']) def test_create_numeric_server_name(self): # Create a server with a numeric name if self.__class__._interface == "xml": raise self.skipException("Not testable in XML") server_name = 12345 self.assertRaises(exceptions.BadRequest, self.create_server, name=server_name) @attr(type=['negative', 'gate']) def test_create_server_name_length_exceeds_256(self): # Create a server with name length exceeding 256 characters server_name = 'a' * 256 self.assertRaises(exceptions.BadRequest, self.create_server, name=server_name) @attr(type=['negative', 'gate']) def test_create_with_invalid_network_uuid(self): # Pass invalid network uuid while creating a server networks = [{'fixed_ip': '10.0.1.1', 'uuid': 'a-b-c-d-e-f-g-h-i-j'}] self.assertRaises(exceptions.BadRequest, self.create_server, networks=networks) @attr(type=['negative', 'gate']) def test_create_with_non_existant_keypair(self): # Pass a non existant keypair while creating a server key_name = rand_name('key') self.assertRaises(exceptions.BadRequest, self.create_server, key_name=key_name) @attr(type=['negative', 'gate']) def test_create_server_metadata_exceeds_length_limit(self): # Pass really long metadata while creating a server metadata = {'a': 'b' * 260} self.assertRaises(exceptions.OverLimit, self.create_server, meta=metadata) @attr(type=['negative', 'gate']) def test_update_name_of_non_existent_server(self): # Update name of a non-existent server server_name = rand_name('server') new_name = rand_name('server') + '_updated' self.assertRaises(exceptions.NotFound, self.client.update_server, server_name, name=new_name) @attr(type=['negative', 'gate']) def test_update_server_set_empty_name(self): # Update name of the server to an empty string server_name = rand_name('server') new_name = '' self.assertRaises(exceptions.BadRequest, self.client.update_server, server_name, name=new_name) @attr(type=['negative', 'gate']) def test_update_server_of_another_tenant(self): # Update name of a server that belongs to another tenant resp, server = self.create_server(wait_until='ACTIVE') new_name = server['id'] + '_new' self.assertRaises(exceptions.NotFound, self.alt_client.update_server, server['id'], name=new_name) @attr(type=['negative', 'gate']) def test_update_server_name_length_exceeds_256(self): # Update name of server exceed the name length limit resp, server = self.create_server(wait_until='ACTIVE') new_name = 'a' * 256 self.assertRaises(exceptions.BadRequest, self.client.update_server, server['id'], name=new_name) @attr(type=['negative', 'gate']) def test_delete_non_existent_server(self): # Delete a non existent server self.assertRaises(exceptions.NotFound, self.client.delete_server, '999erra43') @attr(type=['negative', 'gate']) def test_delete_a_server_of_another_tenant(self): # Delete a server that belongs to another tenant try: resp, server = self.create_server(wait_until='ACTIVE') self.assertRaises(exceptions.NotFound, self.alt_client.delete_server, server['id']) finally: self.client.delete_server(server['id']) @attr(type=['negative', 'gate']) def test_delete_server_pass_negative_id(self): # Pass an invalid string parameter to delete server self.assertRaises(exceptions.NotFound, self.client.delete_server, -1) @attr(type=['negative', 'gate']) def test_delete_server_pass_id_exceeding_length_limit(self): # Pass a server ID that exceeds length limit to delete server self.assertRaises(exceptions.NotFound, self.client.delete_server, sys.maxint + 1) @attr(type=['negative', 'gate']) def test_create_with_nonexistent_security_group(self): # Create a server with a nonexistent security group security_groups = [{'name': 'does_not_exist'}] if self.config.network.quantum_available: expected_exception = exceptions.NotFound else: expected_exception = exceptions.BadRequest self.assertRaises(expected_exception, self.create_server, security_groups=security_groups) @attr(type=['negative', 'gate']) def test_get_non_existent_server(self): # Get a non existent server details self.assertRaises(exceptions.NotFound, self.client.get_server, '999erra43') class ServersNegativeTestXML(ServersNegativeTest): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/servers/test_instance_actions.py0000664000175000017500000000530612161375672030016 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 NEC Corporation # 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 tempest.api.compute import base from tempest import exceptions from tempest.test import attr class InstanceActionsTestJSON(base.BaseComputeTest): _interface = 'json' @classmethod def setUpClass(cls): super(InstanceActionsTestJSON, cls).setUpClass() cls.client = cls.servers_client resp, server = cls.create_server(wait_until='ACTIVE') cls.request_id = resp['x-compute-request-id'] cls.server_id = server['id'] @attr(type='gate') def test_list_instance_actions(self): # List actions of the provided server resp, body = self.client.reboot(self.server_id, 'HARD') self.client.wait_for_server_status(self.server_id, 'ACTIVE') resp, body = self.client.list_instance_actions(self.server_id) self.assertEqual(200, resp.status) self.assertTrue(len(body) == 2) self.assertTrue(any([i for i in body if i['action'] == 'create'])) self.assertTrue(any([i for i in body if i['action'] == 'reboot'])) @attr(type='gate') def test_get_instance_action(self): # Get the action details of the provided server resp, body = self.client.get_instance_action(self.server_id, self.request_id) self.assertEqual(200, resp.status) self.assertEqual(self.server_id, body['instance_uuid']) self.assertEqual('create', body['action']) @attr(type=['negative', 'gate']) def test_list_instance_actions_invalid_server(self): # List actions of the invalid server id self.assertRaises(exceptions.NotFound, self.client.list_instance_actions, 'server-999') @attr(type=['negative', 'gate']) def test_get_instance_action_invalid_request(self): # Get the action details of the provided server with invalid request self.assertRaises(exceptions.NotFound, self.client.get_instance_action, self.server_id, '999') class InstanceActionsTestXML(InstanceActionsTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/servers/test_server_rescue.py0000664000175000017500000002203712161375672027346 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 Hewlett-Packard Development Company, L.P. # 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 tempest.api.compute import base from tempest.common.utils.data_utils import rand_name import tempest.config from tempest import exceptions from tempest.test import attr class ServerRescueTestJSON(base.BaseComputeTest): _interface = 'json' run_ssh = tempest.config.TempestConfig().compute.run_ssh @classmethod def setUpClass(cls): super(ServerRescueTestJSON, cls).setUpClass() cls.device = 'vdf' #Floating IP creation resp, body = cls.floating_ips_client.create_floating_ip() cls.floating_ip_id = str(body['id']).strip() cls.floating_ip = str(body['ip']).strip() #Security group creation cls.sg_name = rand_name('sg') cls.sg_desc = rand_name('sg-desc') resp, cls.sg = \ cls.security_groups_client.create_security_group(cls.sg_name, cls.sg_desc) cls.sg_id = cls.sg['id'] # Create a volume and wait for it to become ready for attach resp, cls.volume_to_attach = \ cls.volumes_extensions_client.create_volume(1, display_name= 'test_attach') cls.volumes_extensions_client.wait_for_volume_status( cls.volume_to_attach['id'], 'available') # Create a volume and wait for it to become ready for attach resp, cls.volume_to_detach = \ cls.volumes_extensions_client.create_volume(1, display_name= 'test_detach') cls.volumes_extensions_client.wait_for_volume_status( cls.volume_to_detach['id'], 'available') # Server for positive tests resp, server = cls.create_server(image_id=cls.image_ref, flavor=cls.flavor_ref, wait_until='BUILD') resp, resc_server = cls.create_server(image_id=cls.image_ref, flavor=cls.flavor_ref, wait_until='ACTIVE') cls.server_id = server['id'] cls.password = server['adminPass'] cls.servers_client.wait_for_server_status(cls.server_id, 'ACTIVE') # Server for negative tests cls.rescue_id = resc_server['id'] cls.rescue_password = resc_server['adminPass'] cls.servers_client.rescue_server( cls.rescue_id, cls.rescue_password) cls.servers_client.wait_for_server_status(cls.rescue_id, 'RESCUE') def setUp(self): super(ServerRescueTestJSON, self).setUp() @classmethod def tearDownClass(cls): #Deleting the floating IP which is created in this method cls.floating_ips_client.delete_floating_ip(cls.floating_ip_id) client = cls.volumes_extensions_client client.delete_volume(str(cls.volume_to_attach['id']).strip()) client.delete_volume(str(cls.volume_to_detach['id']).strip()) resp, cls.sg = cls.security_groups_client.delete_security_group( cls.sg_id) super(ServerRescueTestJSON, cls).tearDownClass() def tearDown(self): super(ServerRescueTestJSON, self).tearDown() def _detach(self, server_id, volume_id): self.servers_client.detach_volume(server_id, volume_id) self.volumes_extensions_client.wait_for_volume_status(volume_id, 'available') def _delete(self, volume_id): self.volumes_extensions_client.delete_volume(volume_id) def _unrescue(self, server_id): resp, body = self.servers_client.unrescue_server(server_id) self.assertEqual(202, resp.status) self.servers_client.wait_for_server_status(server_id, 'ACTIVE') @attr(type='smoke') def test_rescue_unrescue_instance(self): resp, body = self.servers_client.rescue_server( self.server_id, self.password) self.assertEqual(200, resp.status) self.servers_client.wait_for_server_status(self.server_id, 'RESCUE') resp, body = self.servers_client.unrescue_server(self.server_id) self.assertEqual(202, resp.status) self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE') @attr(type=['negative', 'gate']) def test_rescued_vm_reboot(self): self.assertRaises(exceptions.Duplicate, self.servers_client.reboot, self.rescue_id, 'HARD') @attr(type=['negative', 'gate']) def test_rescued_vm_rebuild(self): self.assertRaises(exceptions.Duplicate, self.servers_client.rebuild, self.rescue_id, self.image_ref_alt) @attr(type=['negative', 'gate']) def test_rescued_vm_attach_volume(self): # Rescue the server self.servers_client.rescue_server(self.server_id, self.password) self.servers_client.wait_for_server_status(self.server_id, 'RESCUE') self.addCleanup(self._unrescue, self.server_id) # Attach the volume to the server self.assertRaises(exceptions.Duplicate, self.servers_client.attach_volume, self.server_id, self.volume_to_attach['id'], device='/dev/%s' % self.device) @attr(type=['negative', 'gate']) def test_rescued_vm_detach_volume(self): # Attach the volume to the server self.servers_client.attach_volume(self.server_id, self.volume_to_detach['id'], device='/dev/%s' % self.device) self.volumes_extensions_client.wait_for_volume_status( self.volume_to_detach['id'], 'in-use') # Rescue the server self.servers_client.rescue_server(self.server_id, self.password) self.servers_client.wait_for_server_status(self.server_id, 'RESCUE') #addCleanup is a LIFO queue self.addCleanup(self._detach, self.server_id, self.volume_to_detach['id']) self.addCleanup(self._unrescue, self.server_id) # Detach the volume from the server expecting failure self.assertRaises(exceptions.Duplicate, self.servers_client.detach_volume, self.server_id, self.volume_to_detach['id']) @attr(type='gate') def test_rescued_vm_associate_dissociate_floating_ip(self): # Rescue the server self.servers_client.rescue_server( self.server_id, self.password) self.servers_client.wait_for_server_status(self.server_id, 'RESCUE') self.addCleanup(self._unrescue, self.server_id) #Association of floating IP to a rescued vm client = self.floating_ips_client resp, body = client.associate_floating_ip_to_server(self.floating_ip, self.server_id) self.assertEqual(202, resp.status) #Disassociation of floating IP that was associated in this method resp, body = \ client.disassociate_floating_ip_from_server(self.floating_ip, self.server_id) self.assertEqual(202, resp.status) @attr(type='gate') def test_rescued_vm_add_remove_security_group(self): # Rescue the server self.servers_client.rescue_server( self.server_id, self.password) self.servers_client.wait_for_server_status(self.server_id, 'RESCUE') #Add Security group resp, body = self.servers_client.add_security_group(self.server_id, self.sg_name) self.assertEqual(202, resp.status) #Delete Security group resp, body = self.servers_client.remove_security_group(self.server_id, self.sg_name) self.assertEqual(202, resp.status) # Unrescue the server resp, body = self.servers_client.unrescue_server(self.server_id) self.assertEqual(202, resp.status) self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE') class ServerRescueTestXML(ServerRescueTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/servers/test_attach_interfaces.py0000664000175000017500000001063412161375672030141 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # 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 tempest.api.compute import base from tempest.test import attr import time class AttachInterfacesTestJSON(base.BaseComputeTest): _interface = 'json' @classmethod def setUpClass(cls): if not cls.config.network.quantum_available: raise cls.skipException("Quantum is required") super(AttachInterfacesTestJSON, cls).setUpClass() cls.client = cls.os.interfaces_client def _check_interface(self, iface, port_id=None, network_id=None, fixed_ip=None): self.assertIn('port_state', iface) if port_id: self.assertEqual(iface['port_id'], port_id) if network_id: self.assertEqual(iface['net_id'], network_id) if fixed_ip: self.assertEqual(iface['fixed_ips'][0]['ip_address'], fixed_ip) def _create_server_get_interfaces(self): resp, server = self.create_server() self.os.servers_client.wait_for_server_status(server['id'], 'ACTIVE') resp, ifs = self.client.list_interfaces(server['id']) resp, body = self.client.wait_for_interface_status( server['id'], ifs[0]['port_id'], 'ACTIVE') ifs[0]['port_state'] = body['port_state'] return server, ifs def _test_create_interface(self, server): resp, iface = self.client.create_interface(server['id']) resp, iface = self.client.wait_for_interface_status( server['id'], iface['port_id'], 'ACTIVE') self._check_interface(iface) return iface def _test_create_interface_by_network_id(self, server, ifs): network_id = ifs[0]['net_id'] resp, iface = self.client.create_interface(server['id'], network_id=network_id) resp, iface = self.client.wait_for_interface_status( server['id'], iface['port_id'], 'ACTIVE') self._check_interface(iface, network_id=network_id) return iface def _test_show_interface(self, server, ifs): iface = ifs[0] resp, _iface = self.client.show_interface(server['id'], iface['port_id']) self.assertEqual(iface, _iface) def _test_delete_interface(self, server, ifs): # NOTE(danms): delete not the first or last, but one in the middle iface = ifs[1] self.client.delete_interface(server['id'], iface['port_id']) for i in range(0, 5): _r, _ifs = self.client.list_interfaces(server['id']) if len(ifs) != len(_ifs): break time.sleep(1) self.assertEqual(len(_ifs), len(ifs) - 1) for _iface in _ifs: self.assertNotEqual(iface['port_id'], _iface['port_id']) return _ifs def _compare_iface_list(self, list1, list2): # NOTE(danms): port_state will likely have changed, so just # confirm the port_ids are the same at least list1 = [x['port_id'] for x in list1] list2 = [x['port_id'] for x in list2] self.assertEqual(sorted(list1), sorted(list2)) @attr(type='gate') def test_create_list_show_delete_interfaces(self): server, ifs = self._create_server_get_interfaces() interface_count = len(ifs) self.assertTrue(interface_count > 0) self._check_interface(ifs[0]) iface = self._test_create_interface(server) ifs.append(iface) iface = self._test_create_interface_by_network_id(server, ifs) ifs.append(iface) resp, _ifs = self.client.list_interfaces(server['id']) self._compare_iface_list(ifs, _ifs) self._test_show_interface(server, ifs) _ifs = self._test_delete_interface(server, ifs) self.assertEqual(len(ifs) - 1, len(_ifs)) class AttachInterfacesTestXML(AttachInterfacesTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/servers/test_virtual_interfaces.py0000664000175000017500000000455512161375672030370 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 OpenStack Foundation # 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 netaddr from tempest.api.compute import base from tempest.common.utils.data_utils import rand_name from tempest import exceptions from tempest.test import attr class VirtualInterfacesTestJSON(base.BaseComputeTest): _interface = 'json' @classmethod def setUpClass(cls): super(VirtualInterfacesTestJSON, cls).setUpClass() cls.client = cls.servers_client resp, server = cls.create_server(wait_until='ACTIVE') cls.server_id = server['id'] @attr(type='gate') def test_list_virtual_interfaces(self): # Positive test:Should be able to GET the virtual interfaces list # for a given server_id resp, output = self.client.list_virtual_interfaces(self.server_id) self.assertEqual(200, resp.status) self.assertNotEqual(output, None) virt_ifaces = output self.assertNotEqual(0, len(virt_ifaces['virtual_interfaces']), 'Expected virtual interfaces, got 0 interfaces.') for virt_iface in virt_ifaces['virtual_interfaces']: mac_address = virt_iface['mac_address'] self.assertTrue(netaddr.valid_mac(mac_address), "Invalid mac address detected.") @attr(type=['negative', 'gate']) def test_list_virtual_interfaces_invalid_server_id(self): # Negative test: Should not be able to GET virtual interfaces # for an invalid server_id invalid_server_id = rand_name('!@#$%^&*()') self.assertRaises(exceptions.NotFound, self.client.list_virtual_interfaces, invalid_server_id) class VirtualInterfacesTestXML(VirtualInterfacesTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/servers/test_server_actions.py0000664000175000017500000002656212161375672027527 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 base64 import time import testtools from tempest.api import compute from tempest.api.compute import base from tempest.common.utils.data_utils import rand_name from tempest.common.utils.linux.remote_client import RemoteClient import tempest.config from tempest import exceptions from tempest.test import attr class ServerActionsTestJSON(base.BaseComputeTest): _interface = 'json' resize_available = tempest.config.TempestConfig().compute.resize_available run_ssh = tempest.config.TempestConfig().compute.run_ssh def setUp(self): #NOTE(afazekas): Normally we use the same server with all test cases, # but if it has an issue, we build a new one super(ServerActionsTestJSON, self).setUp() # Check if the server is in a clean state after test try: self.client.wait_for_server_status(self.server_id, 'ACTIVE') except Exception: # Rebuild server if something happened to it during a test self.rebuild_servers() @classmethod def setUpClass(cls): super(ServerActionsTestJSON, cls).setUpClass() cls.client = cls.servers_client cls.rebuild_servers() @testtools.skipUnless(compute.CHANGE_PASSWORD_AVAILABLE, 'Change password not available.') @attr(type='gate') def test_change_server_password(self): # The server's password should be set to the provided password new_password = 'Newpass1234' resp, body = self.client.change_password(self.server_id, new_password) self.assertEqual(202, resp.status) self.client.wait_for_server_status(self.server_id, 'ACTIVE') if self.run_ssh: # Verify that the user can authenticate with the new password resp, server = self.client.get_server(self.server_id) linux_client = RemoteClient(server, self.ssh_user, new_password) self.assertTrue(linux_client.can_authenticate()) @attr(type='smoke') def test_reboot_server_hard(self): # The server should be power cycled if self.run_ssh: # Get the time the server was last rebooted, resp, server = self.client.get_server(self.server_id) linux_client = RemoteClient(server, self.ssh_user, self.password) boot_time = linux_client.get_boot_time() resp, body = self.client.reboot(self.server_id, 'HARD') self.assertEqual(202, resp.status) self.client.wait_for_server_status(self.server_id, 'ACTIVE') if self.run_ssh: # Log in and verify the boot time has changed linux_client = RemoteClient(server, self.ssh_user, self.password) new_boot_time = linux_client.get_boot_time() self.assertGreater(new_boot_time, boot_time) @testtools.skip('Until Bug #1014647 is dealt with.') @attr(type='smoke') def test_reboot_server_soft(self): # The server should be signaled to reboot gracefully if self.run_ssh: # Get the time the server was last rebooted, resp, server = self.client.get_server(self.server_id) linux_client = RemoteClient(server, self.ssh_user, self.password) boot_time = linux_client.get_boot_time() resp, body = self.client.reboot(self.server_id, 'SOFT') self.assertEqual(202, resp.status) self.client.wait_for_server_status(self.server_id, 'ACTIVE') if self.run_ssh: # Log in and verify the boot time has changed linux_client = RemoteClient(server, self.ssh_user, self.password) new_boot_time = linux_client.get_boot_time() self.assertGreater(new_boot_time, boot_time) @attr(type='smoke') def test_rebuild_server(self): # The server should be rebuilt using the provided image and data meta = {'rebuild': 'server'} new_name = rand_name('server') file_contents = 'Test server rebuild.' personality = [{'path': '/etc/rebuild.txt', 'contents': base64.b64encode(file_contents)}] password = 'rebuildPassw0rd' resp, rebuilt_server = self.client.rebuild(self.server_id, self.image_ref_alt, name=new_name, meta=meta, personality=personality, adminPass=password) #Verify the properties in the initial response are correct self.assertEqual(self.server_id, rebuilt_server['id']) rebuilt_image_id = rebuilt_server['image']['id'] self.assertTrue(self.image_ref_alt.endswith(rebuilt_image_id)) self.assertEqual(self.flavor_ref, int(rebuilt_server['flavor']['id'])) #Verify the server properties after the rebuild completes self.client.wait_for_server_status(rebuilt_server['id'], 'ACTIVE') resp, server = self.client.get_server(rebuilt_server['id']) rebuilt_image_id = rebuilt_server['image']['id'] self.assertTrue(self.image_ref_alt.endswith(rebuilt_image_id)) self.assertEqual(new_name, rebuilt_server['name']) if self.run_ssh: # Verify that the user can authenticate with the provided password linux_client = RemoteClient(server, self.ssh_user, password) self.assertTrue(linux_client.can_authenticate()) def _detect_server_image_flavor(self, server_id): # Detects the current server image flavor ref. resp, server = self.client.get_server(self.server_id) current_flavor = server['flavor']['id'] new_flavor_ref = self.flavor_ref_alt \ if int(current_flavor) == self.flavor_ref else self.flavor_ref return int(current_flavor), int(new_flavor_ref) @testtools.skipIf(not resize_available, 'Resize not available.') @attr(type='smoke') def test_resize_server_confirm(self): # The server's RAM and disk space should be modified to that of # the provided flavor previous_flavor_ref, new_flavor_ref = \ self._detect_server_image_flavor(self.server_id) resp, server = self.client.resize(self.server_id, new_flavor_ref) self.assertEqual(202, resp.status) self.client.wait_for_server_status(self.server_id, 'VERIFY_RESIZE') self.client.confirm_resize(self.server_id) self.client.wait_for_server_status(self.server_id, 'ACTIVE') resp, server = self.client.get_server(self.server_id) self.assertEqual(new_flavor_ref, int(server['flavor']['id'])) @testtools.skipIf(not resize_available, 'Resize not available.') @attr(type='gate') def test_resize_server_revert(self): # The server's RAM and disk space should return to its original # values after a resize is reverted previous_flavor_ref, new_flavor_ref = \ self._detect_server_image_flavor(self.server_id) resp, server = self.client.resize(self.server_id, new_flavor_ref) self.assertEqual(202, resp.status) self.client.wait_for_server_status(self.server_id, 'VERIFY_RESIZE') self.client.revert_resize(self.server_id) self.client.wait_for_server_status(self.server_id, 'ACTIVE') # Need to poll for the id change until lp#924371 is fixed resp, server = self.client.get_server(self.server_id) start = int(time.time()) while int(server['flavor']['id']) != previous_flavor_ref: time.sleep(self.build_interval) resp, server = self.client.get_server(self.server_id) if int(time.time()) - start >= self.build_timeout: message = 'Server %s failed to revert resize within the \ required time (%s s).' % (self.server_id, self.build_timeout) raise exceptions.TimeoutException(message) @attr(type=['negative', 'gate']) def test_reboot_nonexistent_server_soft(self): # Negative Test: The server reboot on non existent server should return # an error self.assertRaises(exceptions.NotFound, self.client.reboot, 999, 'SOFT') @attr(type=['negative', 'gate']) def test_rebuild_nonexistent_server(self): # Negative test: The server rebuild for a non existing server # should not be allowed meta = {'rebuild': 'server'} new_name = rand_name('server') file_contents = 'Test server rebuild.' personality = [{'path': '/etc/rebuild.txt', 'contents': base64.b64encode(file_contents)}] self.assertRaises(exceptions.NotFound, self.client.rebuild, 999, self.image_ref_alt, name=new_name, meta=meta, personality=personality, adminPass='rebuild') @attr(type='gate') def test_get_console_output(self): # Positive test:Should be able to GET the console output # for a given server_id and number of lines def get_output(): resp, output = self.servers_client.get_console_output( self.server_id, 10) self.assertEqual(200, resp.status) self.assertNotEqual(output, None) lines = len(output.split('\n')) self.assertEqual(lines, 10) self.wait_for(get_output) @attr(type=['negative', 'gate']) def test_get_console_output_invalid_server_id(self): # Negative test: Should not be able to get the console output # for an invalid server_id self.assertRaises(exceptions.NotFound, self.servers_client.get_console_output, '!@#$%^&*()', 10) @testtools.skip('Until tempest Bug #1014683 is fixed.') @attr(type='gate') def test_get_console_output_server_id_in_reboot_status(self): # Positive test:Should be able to GET the console output # for a given server_id in reboot status resp, output = self.servers_client.reboot(self.server_id, 'SOFT') self.servers_client.wait_for_server_status(self.server_id, 'REBOOT') resp, output = self.servers_client.get_console_output(self.server_id, 10) self.assertEqual(200, resp.status) self.assertNotEqual(output, None) lines = len(output.split('\n')) self.assertEqual(lines, 10) @classmethod def rebuild_servers(cls): # Destroy any existing server and creates a new one cls.clear_servers() resp, server = cls.create_server(wait_until='ACTIVE') cls.server_id = server['id'] cls.password = server['adminPass'] class ServerActionsTestXML(ServerActionsTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/servers/__init__.py0000664000175000017500000000000012161375672025154 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/servers/test_server_metadata.py0000664000175000017500000002370112161375672027637 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.compute import base from tempest import exceptions from tempest.test import attr class ServerMetadataTestJSON(base.BaseComputeTest): _interface = 'json' @classmethod def setUpClass(cls): super(ServerMetadataTestJSON, cls).setUpClass() cls.client = cls.servers_client cls.quotas = cls.quotas_client cls.admin_client = cls._get_identity_admin_client() resp, tenants = cls.admin_client.list_tenants() cls.tenant_id = [tnt['id'] for tnt in tenants if tnt['name'] == cls.client.tenant_name][0] resp, server = cls.create_server(meta={}, wait_until='ACTIVE') cls.server_id = server['id'] def setUp(self): super(ServerMetadataTestJSON, self).setUp() meta = {'key1': 'value1', 'key2': 'value2'} resp, _ = self.client.set_server_metadata(self.server_id, meta) self.assertEqual(resp.status, 200) @attr(type='gate') def test_list_server_metadata(self): # All metadata key/value pairs for a server should be returned resp, resp_metadata = self.client.list_server_metadata(self.server_id) #Verify the expected metadata items are in the list self.assertEqual(200, resp.status) expected = {'key1': 'value1', 'key2': 'value2'} self.assertEqual(expected, resp_metadata) @attr(type='gate') def test_set_server_metadata(self): # The server's metadata should be replaced with the provided values #Create a new set of metadata for the server req_metadata = {'meta2': 'data2', 'meta3': 'data3'} resp, metadata = self.client.set_server_metadata(self.server_id, req_metadata) self.assertEqual(200, resp.status) #Verify the expected values are correct, and that the #previous values have been removed resp, resp_metadata = self.client.list_server_metadata(self.server_id) self.assertEqual(resp_metadata, req_metadata) @attr(type='gate') def test_server_create_metadata_key_too_long(self): # Attempt to start a server with a meta-data key that is > 255 # characters # Try a few values for sz in [256, 257, 511, 1023]: key = "k" * sz meta = {key: 'data1'} self.assertRaises(exceptions.OverLimit, self.create_server, meta=meta) # no teardown - all creates should fail @attr(type=['negative', 'gate']) def test_create_metadata_key_error(self): # Blank key should trigger an error. meta = {'': 'data1'} self.assertRaises(exceptions.BadRequest, self.create_server, meta=meta) @attr(type='gate') def test_update_server_metadata(self): # The server's metadata values should be updated to the # provided values meta = {'key1': 'alt1', 'key3': 'value3'} resp, metadata = self.client.update_server_metadata(self.server_id, meta) self.assertEqual(200, resp.status) #Verify the values have been updated to the proper values resp, resp_metadata = self.client.list_server_metadata(self.server_id) expected = {'key1': 'alt1', 'key2': 'value2', 'key3': 'value3'} self.assertEqual(expected, resp_metadata) @attr(type='gate') def test_update_metadata_empty_body(self): # The original metadata should not be lost if empty metadata body is # passed meta = {} _, metadata = self.client.update_server_metadata(self.server_id, meta) resp, resp_metadata = self.client.list_server_metadata(self.server_id) expected = {'key1': 'value1', 'key2': 'value2'} self.assertEqual(expected, resp_metadata) @attr(type='gate') def test_get_server_metadata_item(self): # The value for a specic metadata key should be returned resp, meta = self.client.get_server_metadata_item(self.server_id, 'key2') self.assertTrue('value2', meta['key2']) @attr(type='gate') def test_set_server_metadata_item(self): # The item's value should be updated to the provided value #Update the metadata value meta = {'nova': 'alt'} resp, body = self.client.set_server_metadata_item(self.server_id, 'nova', meta) self.assertEqual(200, resp.status) #Verify the meta item's value has been updated resp, resp_metadata = self.client.list_server_metadata(self.server_id) expected = {'key1': 'value1', 'key2': 'value2', 'nova': 'alt'} self.assertEqual(expected, resp_metadata) @attr(type='gate') def test_delete_server_metadata_item(self): # The metadata value/key pair should be deleted from the server resp, meta = self.client.delete_server_metadata_item(self.server_id, 'key1') self.assertEqual(204, resp.status) #Verify the metadata item has been removed resp, resp_metadata = self.client.list_server_metadata(self.server_id) expected = {'key2': 'value2'} self.assertEqual(expected, resp_metadata) @attr(type=['negative', 'gate']) def test_get_nonexistant_server_metadata_item(self): # Negative test: GET on nonexistant server should not succeed self.assertRaises(exceptions.NotFound, self.client.get_server_metadata_item, 999, 'test2') @attr(type=['negative', 'gate']) def test_list_nonexistant_server_metadata(self): # Negative test:List metadata on a non existant server should # not succeed self.assertRaises(exceptions.NotFound, self.client.list_server_metadata, 999) @attr(type=['negative', 'gate']) def test_set_server_metadata_item_incorrect_uri_key(self): # Raise BadRequest if key in uri does not match # the key passed in body. meta = {'testkey': 'testvalue'} self.assertRaises(exceptions.BadRequest, self.client.set_server_metadata_item, self.server_id, 'key', meta) @attr(type=['negative', 'gate']) def test_set_nonexistant_server_metadata(self): # Negative test: Set metadata on a non existant server should not # succeed meta = {'meta1': 'data1'} self.assertRaises(exceptions.NotFound, self.client.set_server_metadata, 999, meta) @attr(type=['negative', 'gate']) def test_update_nonexistant_server_metadata(self): # Negative test: An update should not happen for a nonexistant image meta = {'key1': 'value1', 'key2': 'value2'} self.assertRaises(exceptions.NotFound, self.client.update_server_metadata, 999, meta) @attr(type=['negative', 'gate']) def test_update_metadata_key_error(self): # Blank key should trigger an error. meta = {'': 'data1'} self.assertRaises(exceptions.BadRequest, self.client.update_server_metadata, self.server_id, meta=meta) @attr(type=['negative', 'gate']) def test_delete_nonexistant_server_metadata_item(self): # Negative test: Should not be able to delete metadata item from a # nonexistant server #Delete the metadata item self.assertRaises(exceptions.NotFound, self.client.delete_server_metadata_item, 999, 'd') @attr(type=['negative', 'gate']) def test_set_server_metadata_too_long(self): # Raise a 413 OverLimit exception while exceeding metadata items limit # for tenant. _, quota_set = self.quotas.get_quota_set(self.tenant_id) quota_metadata = quota_set['metadata_items'] req_metadata = {} for num in range(1, quota_metadata + 2): req_metadata['key' + str(num)] = 'val' + str(num) self.assertRaises(exceptions.OverLimit, self.client.set_server_metadata, self.server_id, req_metadata) @attr(type=['negative', 'gate']) def test_update_server_metadata_too_long(self): # Raise a 413 OverLimit exception while exceeding metadata items limit # for tenant. _, quota_set = self.quotas.get_quota_set(self.tenant_id) quota_metadata = quota_set['metadata_items'] req_metadata = {} for num in range(1, quota_metadata + 2): req_metadata['key' + str(num)] = 'val' + str(num) self.assertRaises(exceptions.OverLimit, self.client.update_server_metadata, self.server_id, req_metadata) @attr(type=['negative', 'gate']) def test_update_all_metadata_field_error(self): # Raise a bad request error for blank key. # set_server_metadata will replace all metadata with new value meta = {'': 'data1'} self.assertRaises(exceptions.BadRequest, self.client.set_server_metadata, self.server_id, meta=meta) class ServerMetadataTestXML(ServerMetadataTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/servers/test_list_server_filters.py0000664000175000017500000002422212161375672030561 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 testtools from tempest.api.compute import base from tempest.api import utils from tempest.common.utils.data_utils import rand_name from tempest import exceptions from tempest.test import attr class ListServerFiltersTestJSON(base.BaseComputeTest): _interface = 'json' @classmethod def setUpClass(cls): super(ListServerFiltersTestJSON, cls).setUpClass() cls.client = cls.servers_client # Check to see if the alternate image ref actually exists... images_client = cls.images_client resp, images = images_client.list_images() if cls.image_ref != cls.image_ref_alt and \ any([image for image in images if image['id'] == cls.image_ref_alt]): cls.multiple_images = True else: cls.image_ref_alt = cls.image_ref # Do some sanity checks here. If one of the images does # not exist, fail early since the tests won't work... try: cls.images_client.get_image(cls.image_ref) except exceptions.NotFound: raise RuntimeError("Image %s (image_ref) was not found!" % cls.image_ref) try: cls.images_client.get_image(cls.image_ref_alt) except exceptions.NotFound: raise RuntimeError("Image %s (image_ref_alt) was not found!" % cls.image_ref_alt) cls.s1_name = rand_name('server') resp, cls.s1 = cls.client.create_server(cls.s1_name, cls.image_ref, cls.flavor_ref) cls.s2_name = rand_name('server') resp, cls.s2 = cls.client.create_server(cls.s2_name, cls.image_ref_alt, cls.flavor_ref) cls.s3_name = rand_name('server') resp, cls.s3 = cls.client.create_server(cls.s3_name, cls.image_ref, cls.flavor_ref_alt) cls.client.wait_for_server_status(cls.s1['id'], 'ACTIVE') resp, cls.s1 = cls.client.get_server(cls.s1['id']) cls.client.wait_for_server_status(cls.s2['id'], 'ACTIVE') resp, cls.s2 = cls.client.get_server(cls.s2['id']) cls.client.wait_for_server_status(cls.s3['id'], 'ACTIVE') resp, cls.s3 = cls.client.get_server(cls.s3['id']) cls.fixed_network_name = cls.config.compute.fixed_network_name @classmethod def tearDownClass(cls): cls.client.delete_server(cls.s1['id']) cls.client.delete_server(cls.s2['id']) cls.client.delete_server(cls.s3['id']) super(ListServerFiltersTestJSON, cls).tearDownClass() @utils.skip_unless_attr('multiple_images', 'Only one image found') @attr(type='gate') def test_list_servers_filter_by_image(self): # Filter the list of servers by image params = {'image': self.image_ref} resp, body = self.client.list_servers(params) servers = body['servers'] self.assertIn(self.s1['id'], map(lambda x: x['id'], servers)) self.assertNotIn(self.s2['id'], map(lambda x: x['id'], servers)) self.assertIn(self.s3['id'], map(lambda x: x['id'], servers)) @attr(type='gate') def test_list_servers_filter_by_flavor(self): # Filter the list of servers by flavor params = {'flavor': self.flavor_ref_alt} resp, body = self.client.list_servers(params) servers = body['servers'] self.assertNotIn(self.s1['id'], map(lambda x: x['id'], servers)) self.assertNotIn(self.s2['id'], map(lambda x: x['id'], servers)) self.assertIn(self.s3['id'], map(lambda x: x['id'], servers)) @attr(type='gate') def test_list_servers_filter_by_server_name(self): # Filter the list of servers by server name params = {'name': self.s1_name} resp, body = self.client.list_servers(params) servers = body['servers'] self.assertIn(self.s1_name, map(lambda x: x['name'], servers)) self.assertNotIn(self.s2_name, map(lambda x: x['name'], servers)) self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers)) @attr(type='gate') def test_list_servers_filter_by_server_status(self): # Filter the list of servers by server status params = {'status': 'active'} resp, body = self.client.list_servers(params) servers = body['servers'] self.assertIn(self.s1['id'], map(lambda x: x['id'], servers)) self.assertIn(self.s2['id'], map(lambda x: x['id'], servers)) self.assertIn(self.s3['id'], map(lambda x: x['id'], servers)) @attr(type='gate') def test_list_servers_filter_by_limit(self): # Verify only the expected number of servers are returned params = {'limit': 1} resp, servers = self.client.list_servers(params) #when _interface='xml', one element for servers_links in servers self.assertEqual(1, len([x for x in servers['servers'] if 'id' in x])) @utils.skip_unless_attr('multiple_images', 'Only one image found') @attr(type='gate') def test_list_servers_detailed_filter_by_image(self): # Filter the detailed list of servers by image params = {'image': self.image_ref} resp, body = self.client.list_servers_with_detail(params) servers = body['servers'] self.assertIn(self.s1['id'], map(lambda x: x['id'], servers)) self.assertNotIn(self.s2['id'], map(lambda x: x['id'], servers)) self.assertIn(self.s3['id'], map(lambda x: x['id'], servers)) @attr(type='gate') def test_list_servers_detailed_filter_by_flavor(self): # Filter the detailed list of servers by flavor params = {'flavor': self.flavor_ref_alt} resp, body = self.client.list_servers_with_detail(params) servers = body['servers'] self.assertNotIn(self.s1['id'], map(lambda x: x['id'], servers)) self.assertNotIn(self.s2['id'], map(lambda x: x['id'], servers)) self.assertIn(self.s3['id'], map(lambda x: x['id'], servers)) @attr(type='gate') def test_list_servers_detailed_filter_by_server_name(self): # Filter the detailed list of servers by server name params = {'name': self.s1_name} resp, body = self.client.list_servers_with_detail(params) servers = body['servers'] self.assertIn(self.s1_name, map(lambda x: x['name'], servers)) self.assertNotIn(self.s2_name, map(lambda x: x['name'], servers)) self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers)) @attr(type='gate') def test_list_servers_detailed_filter_by_server_status(self): # Filter the detailed list of servers by server status params = {'status': 'active'} resp, body = self.client.list_servers_with_detail(params) servers = body['servers'] self.assertIn(self.s1['id'], map(lambda x: x['id'], servers)) self.assertIn(self.s2['id'], map(lambda x: x['id'], servers)) self.assertIn(self.s3['id'], map(lambda x: x['id'], servers)) self.assertEqual(['ACTIVE'] * 3, [x['status'] for x in servers]) @attr(type='gate') def test_list_servers_filtered_by_name_wildcard(self): # List all servers that contains 'server' in name params = {'name': 'server'} resp, body = self.client.list_servers(params) servers = body['servers'] self.assertIn(self.s1_name, map(lambda x: x['name'], servers)) self.assertIn(self.s2_name, map(lambda x: x['name'], servers)) self.assertIn(self.s3_name, map(lambda x: x['name'], servers)) # Let's take random part of name and try to search it part_name = self.s1_name[6:-1] params = {'name': part_name} resp, body = self.client.list_servers(params) servers = body['servers'] self.assertIn(self.s1_name, map(lambda x: x['name'], servers)) self.assertNotIn(self.s2_name, map(lambda x: x['name'], servers)) self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers)) @testtools.skip('Until Bug #1170718 is resolved.') @attr(type='gate') def test_list_servers_filtered_by_ip(self): # Filter servers by ip # Here should be listed 1 server ip = self.s1['addresses'][self.fixed_network_name][0]['addr'] params = {'ip': ip} resp, body = self.client.list_servers(params) servers = body['servers'] self.assertIn(self.s1_name, map(lambda x: x['name'], servers)) self.assertNotIn(self.s2_name, map(lambda x: x['name'], servers)) self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers)) @attr(type='gate') def test_list_servers_filtered_by_ip_regex(self): # Filter servers by regex ip # List all servers filtered by part of ip address. # Here should be listed all servers ip = self.s1['addresses'][self.fixed_network_name][0]['addr'][0:-3] params = {'ip': ip} resp, body = self.client.list_servers(params) servers = body['servers'] self.assertIn(self.s1_name, map(lambda x: x['name'], servers)) self.assertIn(self.s2_name, map(lambda x: x['name'], servers)) self.assertIn(self.s3_name, map(lambda x: x['name'], servers)) @attr(type='gate') def test_list_servers_detailed_limit_results(self): # Verify only the expected number of detailed results are returned params = {'limit': 1} resp, servers = self.client.list_servers_with_detail(params) self.assertEqual(1, len(servers['servers'])) class ListServerFiltersTestXML(ListServerFiltersTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/servers/test_server_addresses.py0000664000175000017500000000632012161375672030032 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.compute import base from tempest import exceptions from tempest.test import attr class ServerAddressesTest(base.BaseComputeTest): _interface = 'json' @classmethod def setUpClass(cls): super(ServerAddressesTest, cls).setUpClass() cls.client = cls.servers_client resp, cls.server = cls.create_server(wait_until='ACTIVE') @attr(type=['negative', 'gate']) def test_list_server_addresses_invalid_server_id(self): # List addresses request should fail if server id not in system self.assertRaises(exceptions.NotFound, self.client.list_addresses, '999') @attr(type=['negative', 'gate']) def test_list_server_addresses_by_network_neg(self): # List addresses by network should fail if network name not valid self.assertRaises(exceptions.NotFound, self.client.list_addresses_by_network, self.server['id'], 'invalid') @attr(type='smoke') def test_list_server_addresses(self): # All public and private addresses for # a server should be returned resp, addresses = self.client.list_addresses(self.server['id']) self.assertEqual('200', resp['status']) # We do not know the exact network configuration, but an instance # should at least have a single public or private address self.assertTrue(len(addresses) >= 1) for network_name, network_addresses in addresses.iteritems(): self.assertTrue(len(network_addresses) >= 1) for address in network_addresses: self.assertTrue(address['addr']) self.assertTrue(address['version']) @attr(type='smoke') def test_list_server_addresses_by_network(self): # Providing a network type should filter # the addresses return by that type resp, addresses = self.client.list_addresses(self.server['id']) # Once again we don't know the environment's exact network config, # but the response for each individual network should be the same # as the partial result of the full address list id = self.server['id'] for addr_type in addresses: resp, addr = self.client.list_addresses_by_network(id, addr_type) self.assertEqual('200', resp['status']) addr = addr[addr_type] for address in addresses[addr_type]: self.assertTrue(any([a for a in addr if a == address])) class ServerAddressesTestXML(ServerAddressesTest): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/test_live_block_migration.py0000664000175000017500000001503312161375672027161 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 random import string import testtools from tempest.api.compute import base from tempest import config from tempest import exceptions from tempest.test import attr class LiveBlockMigrationTestJSON(base.BaseComputeAdminTest): _host_key = 'OS-EXT-SRV-ATTR:host' _interface = 'json' CONF = config.TempestConfig() @classmethod def setUpClass(cls): super(LiveBlockMigrationTestJSON, cls).setUpClass() cls.admin_hosts_client = cls.os_adm.hosts_client cls.admin_servers_client = cls.os_adm.servers_client cls.created_server_ids = [] def _get_compute_hostnames(self): _resp, body = self.admin_hosts_client.list_hosts() return [ host_record['host_name'] for host_record in body if host_record['service'] == 'compute' ] def _get_server_details(self, server_id): _resp, body = self.admin_servers_client.get_server(server_id) return body def _get_host_for_server(self, server_id): return self._get_server_details(server_id)[self._host_key] def _migrate_server_to(self, server_id, dest_host): _resp, body = self.admin_servers_client.live_migrate_server( server_id, dest_host, self.config.compute.use_block_migration_for_live_migration) return body def _get_host_other_than(self, host): for target_host in self._get_compute_hostnames(): if host != target_host: return target_host def _get_non_existing_host_name(self): random_name = ''.join( random.choice(string.ascii_uppercase) for x in range(20)) self.assertNotIn(random_name, self._get_compute_hostnames()) return random_name def _get_server_status(self, server_id): return self._get_server_details(server_id)['status'] def _get_an_active_server(self): for server_id in self.created_server_ids: if 'ACTIVE' == self._get_server_status(server_id): return server_id else: _, server = self.create_server(wait_until="ACTIVE") server_id = server['id'] self.password = server['adminPass'] self.password = 'password' self.created_server_ids.append(server_id) return server_id def _volume_clean_up(self, server_id, volume_id): resp, body = self.volumes_client.get_volume(volume_id) if body['status'] == 'in-use': self.servers_client.detach_volume(server_id, volume_id) self.volumes_client.wait_for_volume_status(volume_id, 'available') self.volumes_client.delete_volume(volume_id) @testtools.skipIf(not CONF.compute.live_migration_available, 'Live migration not available') @attr(type='gate') def test_live_block_migration(self): # Live block migrate an instance to another host if len(self._get_compute_hostnames()) < 2: raise self.skipTest( "Less than 2 compute nodes, skipping migration test.") server_id = self._get_an_active_server() actual_host = self._get_host_for_server(server_id) target_host = self._get_host_other_than(actual_host) self._migrate_server_to(server_id, target_host) self.servers_client.wait_for_server_status(server_id, 'ACTIVE') self.assertEquals(target_host, self._get_host_for_server(server_id)) @testtools.skipIf(not CONF.compute.live_migration_available, 'Live migration not available') @attr(type='gate') def test_invalid_host_for_migration(self): # Migrating to an invalid host should not change the status server_id = self._get_an_active_server() target_host = self._get_non_existing_host_name() self.assertRaises(exceptions.BadRequest, self._migrate_server_to, server_id, target_host) self.assertEquals('ACTIVE', self._get_server_status(server_id)) @testtools.skipIf(not CONF.compute.live_migration_available or not CONF.compute.use_block_migration_for_live_migration, 'Block Live migration not available') @testtools.skipIf(not CONF.compute.block_migrate_supports_cinder_iscsi, 'Block Live migration not configured for iSCSI') @attr(type='gate') def test_iscsi_volume(self): # Live block migrate an instance to another host if len(self._get_compute_hostnames()) < 2: raise self.skipTest( "Less than 2 compute nodes, skipping migration test.") server_id = self._get_an_active_server() actual_host = self._get_host_for_server(server_id) target_host = self._get_host_other_than(actual_host) resp, volume = self.volumes_client.create_volume(1, display_name='test') self.volumes_client.wait_for_volume_status(volume['id'], 'available') self.addCleanup(self._volume_clean_up, server_id, volume['id']) # Attach the volume to the server self.servers_client.attach_volume(server_id, volume['id'], device='/dev/xvdb') self.volumes_client.wait_for_volume_status(volume['id'], 'in-use') self._migrate_server_to(server_id, target_host) self.servers_client.wait_for_server_status(server_id, 'ACTIVE') self.assertEquals(target_host, self._get_host_for_server(server_id)) @classmethod def tearDownClass(cls): for server_id in cls.created_server_ids: cls.servers_client.delete_server(server_id) super(LiveBlockMigrationTestJSON, cls).tearDownClass() class LiveBlockMigrationTestXML(LiveBlockMigrationTestJSON): _host_key = ( '{http://docs.openstack.org/compute/ext/extended_status/api/v1.1}host') _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/test_authorization.py0000664000175000017500000004163312161375672025704 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api import compute from tempest.api.compute import base from tempest import clients from tempest.common.utils.data_utils import parse_image_id from tempest.common.utils.data_utils import rand_name from tempest import exceptions from tempest.test import attr class AuthorizationTestJSON(base.BaseComputeTest): _interface = 'json' @classmethod def setUpClass(cls): if not compute.MULTI_USER: msg = "Need >1 user" raise cls.skipException(msg) super(AuthorizationTestJSON, cls).setUpClass() cls.client = cls.os.servers_client cls.images_client = cls.os.images_client cls.keypairs_client = cls.os.keypairs_client cls.security_client = cls.os.security_groups_client if cls.config.compute.allow_tenant_isolation: creds = cls._get_isolated_creds() username, tenant_name, password = creds cls.alt_manager = clients.Manager(username=username, password=password, tenant_name=tenant_name) else: # Use the alt_XXX credentials in the config file cls.alt_manager = clients.AltManager() cls.alt_client = cls.alt_manager.servers_client cls.alt_images_client = cls.alt_manager.images_client cls.alt_keypairs_client = cls.alt_manager.keypairs_client cls.alt_security_client = cls.alt_manager.security_groups_client cls.alt_security_client._set_auth() resp, server = cls.create_server(wait_until='ACTIVE') resp, cls.server = cls.client.get_server(server['id']) name = rand_name('image') resp, body = cls.client.create_image(server['id'], name) image_id = parse_image_id(resp['location']) cls.images_client.wait_for_image_resp_code(image_id, 200) cls.images_client.wait_for_image_status(image_id, 'ACTIVE') resp, cls.image = cls.images_client.get_image(image_id) cls.keypairname = rand_name('keypair') resp, keypair = \ cls.keypairs_client.create_keypair(cls.keypairname) name = rand_name('security') description = rand_name('description') resp, cls.security_group = cls.security_client.create_security_group( name, description) parent_group_id = cls.security_group['id'] ip_protocol = 'tcp' from_port = 22 to_port = 22 resp, cls.rule = cls.security_client.create_security_group_rule( parent_group_id, ip_protocol, from_port, to_port) @classmethod def tearDownClass(cls): if compute.MULTI_USER: cls.images_client.delete_image(cls.image['id']) cls.keypairs_client.delete_keypair(cls.keypairname) cls.security_client.delete_security_group(cls.security_group['id']) super(AuthorizationTestJSON, cls).tearDownClass() @attr(type='gate') def test_get_server_for_alt_account_fails(self): # A GET request for a server on another user's account should fail self.assertRaises(exceptions.NotFound, self.alt_client.get_server, self.server['id']) @attr(type='gate') def test_delete_server_for_alt_account_fails(self): # A DELETE request for another user's server should fail self.assertRaises(exceptions.NotFound, self.alt_client.delete_server, self.server['id']) @attr(type='gate') def test_update_server_for_alt_account_fails(self): # An update server request for another user's server should fail self.assertRaises(exceptions.NotFound, self.alt_client.update_server, self.server['id'], name='test') @attr(type='gate') def test_list_server_addresses_for_alt_account_fails(self): # A list addresses request for another user's server should fail self.assertRaises(exceptions.NotFound, self.alt_client.list_addresses, self.server['id']) @attr(type='gate') def test_list_server_addresses_by_network_for_alt_account_fails(self): # A list address/network request for another user's server should fail server_id = self.server['id'] self.assertRaises(exceptions.NotFound, self.alt_client.list_addresses_by_network, server_id, 'public') @attr(type='gate') def test_list_servers_with_alternate_tenant(self): # A list on servers from one tenant should not # show on alternate tenant #Listing servers from alternate tenant alt_server_ids = [] resp, body = self.alt_client.list_servers() alt_server_ids = [s['id'] for s in body['servers']] self.assertNotIn(self.server['id'], alt_server_ids) @attr(type='gate') def test_change_password_for_alt_account_fails(self): # A change password request for another user's server should fail self.assertRaises(exceptions.NotFound, self.alt_client.change_password, self.server['id'], 'newpass') @attr(type='gate') def test_reboot_server_for_alt_account_fails(self): # A reboot request for another user's server should fail self.assertRaises(exceptions.NotFound, self.alt_client.reboot, self.server['id'], 'HARD') @attr(type='gate') def test_rebuild_server_for_alt_account_fails(self): # A rebuild request for another user's server should fail self.assertRaises(exceptions.NotFound, self.alt_client.rebuild, self.server['id'], self.image_ref_alt) @attr(type='gate') def test_resize_server_for_alt_account_fails(self): # A resize request for another user's server should fail self.assertRaises(exceptions.NotFound, self.alt_client.resize, self.server['id'], self.flavor_ref_alt) @attr(type='gate') def test_create_image_for_alt_account_fails(self): # A create image request for another user's server should fail self.assertRaises(exceptions.NotFound, self.alt_images_client.create_image, self.server['id'], 'testImage') @attr(type='gate') def test_create_server_with_unauthorized_image(self): # Server creation with another user's image should fail self.assertRaises(exceptions.BadRequest, self.alt_client.create_server, 'test', self.image['id'], self.flavor_ref) @attr(type='gate') def test_create_server_fails_when_tenant_incorrect(self): # A create server request should fail if the tenant id does not match # the current user saved_base_url = self.alt_client.base_url try: # Change the base URL to impersonate another user self.alt_client.base_url = self.client.base_url self.assertRaises(exceptions.BadRequest, self.alt_client.create_server, 'test', self.image['id'], self.flavor_ref) finally: # Reset the base_url... self.alt_client.base_url = saved_base_url @attr(type='gate') def test_create_keypair_in_analt_user_tenant(self): # A create keypair request should fail if the tenant id does not match # the current user #POST keypair with other user tenant k_name = rand_name('keypair-') self.alt_keypairs_client._set_auth() self.saved_base_url = self.alt_keypairs_client.base_url try: # Change the base URL to impersonate another user self.alt_keypairs_client.base_url = self.keypairs_client.base_url resp = {} resp['status'] = None self.assertRaises(exceptions.BadRequest, self.alt_keypairs_client.create_keypair, k_name) finally: # Reset the base_url... self.alt_keypairs_client.base_url = self.saved_base_url if (resp['status'] is not None): resp, _ = self.alt_keypairs_client.delete_keypair(k_name) self.fail("Create keypair request should not happen " "if the tenant id does not match the current user") @attr(type='gate') def test_get_keypair_of_alt_account_fails(self): # A GET request for another user's keypair should fail self.assertRaises(exceptions.NotFound, self.alt_keypairs_client.get_keypair, self.keypairname) @attr(type='gate') def test_delete_keypair_of_alt_account_fails(self): # A DELETE request for another user's keypair should fail self.assertRaises(exceptions.NotFound, self.alt_keypairs_client.delete_keypair, self.keypairname) @attr(type='gate') def test_get_image_for_alt_account_fails(self): # A GET request for an image on another user's account should fail self.assertRaises(exceptions.NotFound, self.alt_images_client.get_image, self.image['id']) @attr(type='gate') def test_delete_image_for_alt_account_fails(self): # A DELETE request for another user's image should fail self.assertRaises(exceptions.NotFound, self.alt_images_client.delete_image, self.image['id']) @attr(type='gate') def test_create_security_group_in_analt_user_tenant(self): # A create security group request should fail if the tenant id does not # match the current user #POST security group with other user tenant s_name = rand_name('security-') s_description = rand_name('security') self.saved_base_url = self.alt_security_client.base_url try: # Change the base URL to impersonate another user self.alt_security_client.base_url = self.security_client.base_url resp = {} resp['status'] = None self.assertRaises(exceptions.BadRequest, self.alt_security_client.create_security_group, s_name, s_description) finally: # Reset the base_url... self.alt_security_client.base_url = self.saved_base_url if resp['status'] is not None: self.alt_security_client.delete_security_group(resp['id']) self.fail("Create Security Group request should not happen if" "the tenant id does not match the current user") @attr(type='gate') def test_get_security_group_of_alt_account_fails(self): # A GET request for another user's security group should fail self.assertRaises(exceptions.NotFound, self.alt_security_client.get_security_group, self.security_group['id']) @attr(type='gate') def test_delete_security_group_of_alt_account_fails(self): # A DELETE request for another user's security group should fail self.assertRaises(exceptions.NotFound, self.alt_security_client.delete_security_group, self.security_group['id']) @attr(type='gate') def test_create_security_group_rule_in_analt_user_tenant(self): # A create security group rule request should fail if the tenant id # does not match the current user #POST security group rule with other user tenant parent_group_id = self.security_group['id'] ip_protocol = 'icmp' from_port = -1 to_port = -1 self.saved_base_url = self.alt_security_client.base_url try: # Change the base URL to impersonate another user self.alt_security_client.base_url = self.security_client.base_url resp = {} resp['status'] = None self.assertRaises(exceptions.BadRequest, self.alt_security_client. create_security_group_rule, parent_group_id, ip_protocol, from_port, to_port) finally: # Reset the base_url... self.alt_security_client.base_url = self.saved_base_url if resp['status'] is not None: self.alt_security_client.delete_security_group_rule(resp['id']) self.fail("Create security group rule request should not " "happen if the tenant id does not match the" " current user") @attr(type='gate') def test_delete_security_group_rule_of_alt_account_fails(self): # A DELETE request for another user's security group rule # should fail self.assertRaises(exceptions.NotFound, self.alt_security_client.delete_security_group_rule, self.rule['id']) @attr(type='gate') def test_set_metadata_of_alt_account_server_fails(self): # A set metadata for another user's server should fail req_metadata = {'meta1': 'data1', 'meta2': 'data2'} self.assertRaises(exceptions.NotFound, self.alt_client.set_server_metadata, self.server['id'], req_metadata) @attr(type='gate') def test_set_metadata_of_alt_account_image_fails(self): # A set metadata for another user's image should fail req_metadata = {'meta1': 'value1', 'meta2': 'value2'} self.assertRaises(exceptions.NotFound, self.alt_images_client.set_image_metadata, self.image['id'], req_metadata) @attr(type='gate') def test_get_metadata_of_alt_account_server_fails(self): # A get metadata for another user's server should fail req_metadata = {'meta1': 'data1'} self.client.set_server_metadata(self.server['id'], req_metadata) self.addCleanup(self.client.delete_server_metadata_item, self.server['id'], 'meta1') self.assertRaises(exceptions.NotFound, self.alt_client.get_server_metadata_item, self.server['id'], 'meta1') @attr(type='gate') def test_get_metadata_of_alt_account_image_fails(self): # A get metadata for another user's image should fail req_metadata = {'meta1': 'value1'} self.addCleanup(self.images_client.delete_image_metadata_item, self.image['id'], 'meta1') self.images_client.set_image_metadata(self.image['id'], req_metadata) self.assertRaises(exceptions.NotFound, self.alt_images_client.get_image_metadata_item, self.image['id'], 'meta1') @attr(type='gate') def test_delete_metadata_of_alt_account_server_fails(self): # A delete metadata for another user's server should fail req_metadata = {'meta1': 'data1'} self.addCleanup(self.client.delete_server_metadata_item, self.server['id'], 'meta1') self.client.set_server_metadata(self.server['id'], req_metadata) self.assertRaises(exceptions.NotFound, self.alt_client.delete_server_metadata_item, self.server['id'], 'meta1') @attr(type='gate') def test_delete_metadata_of_alt_account_image_fails(self): # A delete metadata for another user's image should fail req_metadata = {'meta1': 'data1'} self.addCleanup(self.images_client.delete_image_metadata_item, self.image['id'], 'meta1') self.images_client.set_image_metadata(self.image['id'], req_metadata) self.assertRaises(exceptions.NotFound, self.alt_images_client.delete_image_metadata_item, self.image['id'], 'meta1') @attr(type='gate') def test_get_console_output_of_alt_account_server_fails(self): # A Get Console Output for another user's server should fail self.assertRaises(exceptions.NotFound, self.alt_client.get_console_output, self.server['id'], 10) class AuthorizationTestXML(AuthorizationTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/admin/0000775000175000017500000000000012161375700022444 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/admin/test_hypervisor.py0000664000175000017500000001000412161375672026272 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 IBM Corporation # 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 tempest.api.compute import base from tempest import exceptions from tempest.test import attr class HypervisorAdminTestJSON(base.BaseComputeAdminTest): """ Tests Hypervisors API that require admin privileges """ _interface = 'json' @classmethod def setUpClass(cls): super(HypervisorAdminTestJSON, cls).setUpClass() cls.client = cls.os_adm.hypervisor_client cls.non_adm_client = cls.hypervisor_client def _list_hypervisors(self): # List of hypervisors resp, hypers = self.client.get_hypervisor_list() self.assertEqual(200, resp.status) return hypers @attr(type=['positive', 'gate']) def test_get_hypervisor_list(self): # List of hypervisor and available hypervisors hostname hypers = self._list_hypervisors() self.assertTrue(len(hypers) > 0) @attr(type=['positive', 'gate']) def test_get_hypervisor_list_details(self): # Display the details of the all hypervisor resp, hypers = self.client.get_hypervisor_list_details() self.assertEqual(200, resp.status) self.assertTrue(len(hypers) > 0) @attr(type=['positive', 'gate']) def test_get_hypervisor_show_details(self): # Display the details of the specified hypervisor hypers = self._list_hypervisors() self.assertTrue(len(hypers) > 0) resp, details = (self.client. get_hypervisor_show_details(hypers[0]['id'])) self.assertEqual(200, resp.status) self.assertTrue(len(details) > 0) self.assertEqual(details['hypervisor_hostname'], hypers[0]['hypervisor_hostname']) @attr(type=['positive', 'gate']) def test_get_hypervisor_show_servers(self): # Show instances about the specific hypervisors hypers = self._list_hypervisors() self.assertTrue(len(hypers) > 0) hostname = hypers[0]['hypervisor_hostname'] resp, hypervisors = self.client.get_hypervisor_servers(hostname) self.assertEqual(200, resp.status) self.assertTrue(len(hypervisors) > 0) @attr(type=['positive', 'gate']) def test_get_hypervisor_stats(self): # Verify the stats of the all hypervisor resp, stats = self.client.get_hypervisor_stats() self.assertEqual(200, resp.status) self.assertTrue(len(stats) > 0) @attr(type=['positive', 'gate']) def test_get_hypervisor_uptime(self): # Verify that GET shows the specified hypervisor uptime hypers = self._list_hypervisors() resp, uptime = self.client.get_hypervisor_uptime(hypers[0]['id']) self.assertEqual(200, resp.status) self.assertTrue(len(uptime) > 0) @attr(type=['negative', 'gate']) def test_get_hypervisor_list_with_non_admin_user(self): # List of hypervisor and available services with non admin user self.assertRaises( exceptions.Unauthorized, self.non_adm_client.get_hypervisor_list) @attr(type=['negative', 'gate']) def test_get_hypervisor_list_details_with_non_admin_user(self): # List of hypervisor details and available services with non admin user self.assertRaises( exceptions.Unauthorized, self.non_adm_client.get_hypervisor_list_details) class HypervisorAdminTestXML(HypervisorAdminTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/admin/test_flavors.py0000664000175000017500000003243512161375672025550 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api import compute from tempest.api.compute import base from tempest.common.utils.data_utils import rand_int_id from tempest.common.utils.data_utils import rand_name from tempest import exceptions from tempest.test import attr class FlavorsAdminTestJSON(base.BaseComputeAdminTest): """ Tests Flavors API Create and Delete that require admin privileges """ _interface = 'json' @classmethod def setUpClass(cls): super(FlavorsAdminTestJSON, cls).setUpClass() if not compute.FLAVOR_EXTRA_DATA_ENABLED: msg = "FlavorExtraData extension not enabled." raise cls.skipException(msg) cls.client = cls.os_adm.flavors_client cls.user_client = cls.os.flavors_client cls.flavor_name_prefix = 'test_flavor_' cls.ram = 512 cls.vcpus = 1 cls.disk = 10 cls.ephemeral = 10 cls.swap = 1024 cls.rxtx = 2 def flavor_clean_up(self, flavor_id): resp, body = self.client.delete_flavor(flavor_id) self.assertEqual(resp.status, 202) self.client.wait_for_resource_deletion(flavor_id) @attr(type='gate') def test_create_flavor(self): # Create a flavor and ensure it is listed # This operation requires the user to have 'admin' role flavor_name = rand_name(self.flavor_name_prefix) new_flavor_id = rand_int_id(start=1000) #Create the flavor resp, flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, ephemeral=self.ephemeral, swap=self.swap, rxtx=self.rxtx) self.addCleanup(self.flavor_clean_up, flavor['id']) self.assertEqual(200, resp.status) self.assertEqual(flavor['name'], flavor_name) self.assertEqual(flavor['vcpus'], self.vcpus) self.assertEqual(flavor['disk'], self.disk) self.assertEqual(flavor['ram'], self.ram) self.assertEqual(int(flavor['id']), new_flavor_id) self.assertEqual(flavor['swap'], self.swap) self.assertEqual(flavor['rxtx_factor'], self.rxtx) self.assertEqual(flavor['OS-FLV-EXT-DATA:ephemeral'], self.ephemeral) if self._interface == "xml": XMLNS_OS_FLV_ACCESS = "http://docs.openstack.org/compute/ext/"\ "flavor_access/api/v2" key = "{" + XMLNS_OS_FLV_ACCESS + "}is_public" self.assertEqual(flavor[key], "True") if self._interface == "json": self.assertEqual(flavor['os-flavor-access:is_public'], True) #Verify flavor is retrieved resp, flavor = self.client.get_flavor_details(new_flavor_id) self.assertEqual(resp.status, 200) self.assertEqual(flavor['name'], flavor_name) @attr(type='gate') def test_create_flavor_verify_entry_in_list_details(self): # Create a flavor and ensure it's details are listed # This operation requires the user to have 'admin' role flavor_name = rand_name(self.flavor_name_prefix) new_flavor_id = rand_int_id(start=1000) #Create the flavor resp, flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, ephemeral=self.ephemeral, swap=self.swap, rxtx=self.rxtx) self.addCleanup(self.flavor_clean_up, flavor['id']) flag = False #Verify flavor is retrieved resp, flavors = self.client.list_flavors_with_detail() self.assertEqual(resp.status, 200) for flavor in flavors: if flavor['name'] == flavor_name: flag = True self.assertTrue(flag) @attr(type=['negative', 'gate']) def test_get_flavor_details_for_deleted_flavor(self): # Delete a flavor and ensure it is not listed # Create a test flavor flavor_name = rand_name(self.flavor_name_prefix) new_flavor_id = rand_int_id(start=1000) resp, flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, ephemeral=self.ephemeral, swap=self.swap, rxtx=self.rxtx) # Delete the flavor new_flavor_id = flavor['id'] resp_delete, body = self.client.delete_flavor(new_flavor_id) self.assertEquals(200, resp.status) self.assertEquals(202, resp_delete.status) # Deleted flavors can be seen via detailed GET resp, flavor = self.client.get_flavor_details(new_flavor_id) self.assertEqual(resp.status, 200) self.assertEqual(flavor['name'], flavor_name) # Deleted flavors should not show up in a list however resp, flavors = self.client.list_flavors_with_detail() self.assertEqual(resp.status, 200) flag = True for flavor in flavors: if flavor['name'] == flavor_name: flag = False self.assertTrue(flag) @attr(type='gate') def test_create_list_flavor_without_extra_data(self): #Create a flavor and ensure it is listed #This operation requires the user to have 'admin' role flavor_name = rand_name(self.flavor_name_prefix) new_flavor_id = rand_int_id(start=1000) #Create the flavor resp, flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id) self.addCleanup(self.flavor_clean_up, flavor['id']) self.assertEqual(200, resp.status) self.assertEqual(flavor['name'], flavor_name) self.assertEqual(flavor['ram'], self.ram) self.assertEqual(flavor['vcpus'], self.vcpus) self.assertEqual(flavor['disk'], self.disk) self.assertEqual(int(flavor['id']), new_flavor_id) self.assertEqual(flavor['swap'], '') self.assertEqual(int(flavor['rxtx_factor']), 1) self.assertEqual(int(flavor['OS-FLV-EXT-DATA:ephemeral']), 0) if self._interface == "xml": XMLNS_OS_FLV_ACCESS = "http://docs.openstack.org/compute/ext/"\ "flavor_access/api/v2" key = "{" + XMLNS_OS_FLV_ACCESS + "}is_public" self.assertEqual(flavor[key], "True") if self._interface == "json": self.assertEqual(flavor['os-flavor-access:is_public'], True) #Verify flavor is retrieved resp, flavor = self.client.get_flavor_details(new_flavor_id) self.assertEqual(resp.status, 200) self.assertEqual(flavor['name'], flavor_name) #Check if flavor is present in list resp, flavors = self.client.list_flavors_with_detail() self.assertEqual(resp.status, 200) for flavor in flavors: if flavor['name'] == flavor_name: flag = True self.assertTrue(flag) @attr(type='gate') def test_flavor_not_public_verify_entry_not_in_list_details(self): #Create a flavor with os-flavor-access:is_public false should not #be present in list_details. #This operation requires the user to have 'admin' role flavor_name = rand_name(self.flavor_name_prefix) new_flavor_id = rand_int_id(start=1000) #Create the flavor resp, flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, is_public="False") self.addCleanup(self.flavor_clean_up, flavor['id']) flag = False #Verify flavor is retrieved resp, flavors = self.client.list_flavors_with_detail() self.assertEqual(resp.status, 200) for flavor in flavors: if flavor['name'] == flavor_name: flag = True self.assertFalse(flag) @attr(type='gate') def test_list_public_flavor_with_other_user(self): #Create a Flavor with public access. #Try to List/Get flavor with another user flavor_name = rand_name(self.flavor_name_prefix) new_flavor_id = rand_int_id(start=1000) #Create the flavor resp, flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, is_public="True") self.addCleanup(self.flavor_clean_up, flavor['id']) flag = False self.new_client = self.flavors_client #Verify flavor is retrieved with new user resp, flavors = self.new_client.list_flavors_with_detail() self.assertEqual(resp.status, 200) for flavor in flavors: if flavor['name'] == flavor_name: flag = True self.assertTrue(flag) @attr(type='gate') def test_is_public_string_variations(self): flavor_id_not_public = rand_int_id(start=1000) flavor_name_not_public = rand_name(self.flavor_name_prefix) flavor_id_public = rand_int_id(start=1000) flavor_name_public = rand_name(self.flavor_name_prefix) # Create a non public flavor resp, flavor = self.client.create_flavor(flavor_name_not_public, self.ram, self.vcpus, self.disk, flavor_id_not_public, is_public="False") self.addCleanup(self.flavor_clean_up, flavor['id']) # Create a public flavor resp, flavor = self.client.create_flavor(flavor_name_public, self.ram, self.vcpus, self.disk, flavor_id_public, is_public="True") self.addCleanup(self.flavor_clean_up, flavor['id']) def _flavor_lookup(flavors, flavor_name): for flavor in flavors: if flavor['name'] == flavor_name: return flavor return None def _test_string_variations(variations, flavor_name): for string in variations: params = {'is_public': string} r, flavors = self.client.list_flavors_with_detail(params) self.assertEqual(r.status, 200) flavor = _flavor_lookup(flavors, flavor_name) self.assertNotEqual(flavor, None) _test_string_variations(['f', 'false', 'no', '0'], flavor_name_not_public) _test_string_variations(['t', 'true', 'yes', '1'], flavor_name_public) @attr(type=['negative', 'gate']) def test_invalid_is_public_string(self): self.assertRaises(exceptions.BadRequest, self.client.list_flavors_with_detail, {'is_public': 'invalid'}) @attr(type=['negative', 'gate']) def test_create_flavor_as_user(self): flavor_name = rand_name(self.flavor_name_prefix) new_flavor_id = rand_int_id(start=1000) self.assertRaises(exceptions.Unauthorized, self.user_client.create_flavor, flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, ephemeral=self.ephemeral, swap=self.swap, rxtx=self.rxtx) @attr(type=['negative', 'gate']) def test_delete_flavor_as_user(self): self.assertRaises(exceptions.Unauthorized, self.user_client.delete_flavor, self.flavor_ref_alt) class FlavorsAdminTestXML(FlavorsAdminTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/admin/test_aggregates.py0000664000175000017500000002711512161375672026204 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 NEC Corporation. # 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 tempest.api.compute import base from tempest.common.utils.data_utils import rand_name from tempest import exceptions from tempest.test import attr class AggregatesAdminTestJSON(base.BaseComputeAdminTest): """ Tests Aggregates API that require admin privileges """ _host_key = 'OS-EXT-SRV-ATTR:host' _interface = 'json' @classmethod def setUpClass(cls): super(AggregatesAdminTestJSON, cls).setUpClass() cls.client = cls.os_adm.aggregates_client cls.user_client = cls.aggregates_client cls.aggregate_name_prefix = 'test_aggregate_' cls.az_name_prefix = 'test_az_' resp, hosts_all = cls.os_adm.hosts_client.list_hosts() hosts = map(lambda x: x['host_name'], filter(lambda y: y['service'] == 'compute', hosts_all)) cls.host = hosts[0] @attr(type='gate') def test_aggregate_create_delete(self): # Create and delete an aggregate. aggregate_name = rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(aggregate_name) self.assertEquals(200, resp.status) self.assertEquals(aggregate_name, aggregate['name']) self.assertEquals(None, aggregate['availability_zone']) resp, _ = self.client.delete_aggregate(aggregate['id']) self.assertEquals(200, resp.status) self.client.wait_for_resource_deletion(aggregate['id']) @attr(type='gate') def test_aggregate_create_delete_with_az(self): # Create and delete an aggregate. aggregate_name = rand_name(self.aggregate_name_prefix) az_name = rand_name(self.az_name_prefix) resp, aggregate = self.client.create_aggregate(aggregate_name, az_name) self.assertEquals(200, resp.status) self.assertEquals(aggregate_name, aggregate['name']) self.assertEquals(az_name, aggregate['availability_zone']) resp, _ = self.client.delete_aggregate(aggregate['id']) self.assertEquals(200, resp.status) self.client.wait_for_resource_deletion(aggregate['id']) @attr(type='gate') def test_aggregate_create_verify_entry_in_list(self): # Create an aggregate and ensure it is listed. aggregate_name = rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(aggregate_name) self.addCleanup(self.client.delete_aggregate, aggregate['id']) resp, aggregates = self.client.list_aggregates() self.assertEquals(200, resp.status) self.assertIn((aggregate['id'], aggregate['availability_zone']), map(lambda x: (x['id'], x['availability_zone']), aggregates)) @attr(type='gate') def test_aggregate_create_get_details(self): # Create an aggregate and ensure its details are returned. aggregate_name = rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(aggregate_name) self.addCleanup(self.client.delete_aggregate, aggregate['id']) resp, body = self.client.get_aggregate(aggregate['id']) self.assertEquals(200, resp.status) self.assertEquals(aggregate['name'], body['name']) self.assertEquals(aggregate['availability_zone'], body['availability_zone']) @attr(type=['negative', 'gate']) def test_aggregate_create_as_user(self): # Regular user is not allowed to create an aggregate. aggregate_name = rand_name(self.aggregate_name_prefix) self.assertRaises(exceptions.Unauthorized, self.user_client.create_aggregate, aggregate_name) @attr(type=['negative', 'gate']) def test_aggregate_delete_as_user(self): # Regular user is not allowed to delete an aggregate. aggregate_name = rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(aggregate_name) self.addCleanup(self.client.delete_aggregate, aggregate['id']) self.assertRaises(exceptions.Unauthorized, self.user_client.delete_aggregate, aggregate['id']) @attr(type=['negative', 'gate']) def test_aggregate_list_as_user(self): # Regular user is not allowed to list aggregates. self.assertRaises(exceptions.Unauthorized, self.user_client.list_aggregates) @attr(type=['negative', 'gate']) def test_aggregate_get_details_as_user(self): # Regular user is not allowed to get aggregate details. aggregate_name = rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(aggregate_name) self.addCleanup(self.client.delete_aggregate, aggregate['id']) self.assertRaises(exceptions.Unauthorized, self.user_client.get_aggregate, aggregate['id']) @attr(type=['negative', 'gate']) def test_aggregate_delete_with_invalid_id(self): # Delete an aggregate with invalid id should raise exceptions. self.assertRaises(exceptions.NotFound, self.client.delete_aggregate, -1) @attr(type=['negative', 'gate']) def test_aggregate_get_details_with_invalid_id(self): # Get aggregate details with invalid id should raise exceptions. self.assertRaises(exceptions.NotFound, self.client.get_aggregate, -1) @attr(type='gate') def test_aggregate_add_remove_host(self): # Add an host to the given aggregate and remove. aggregate_name = rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(aggregate_name) self.addCleanup(self.client.delete_aggregate, aggregate['id']) resp, body = self.client.add_host(aggregate['id'], self.host) self.assertEquals(200, resp.status) self.assertEquals(aggregate_name, body['name']) self.assertEquals(aggregate['availability_zone'], body['availability_zone']) self.assertIn(self.host, body['hosts']) resp, body = self.client.remove_host(aggregate['id'], self.host) self.assertEquals(200, resp.status) self.assertEquals(aggregate_name, body['name']) self.assertEquals(aggregate['availability_zone'], body['availability_zone']) self.assertNotIn(self.host, body['hosts']) @attr(type='gate') def test_aggregate_add_host_list(self): # Add an host to the given aggregate and list. aggregate_name = rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(aggregate_name) self.addCleanup(self.client.delete_aggregate, aggregate['id']) self.client.add_host(aggregate['id'], self.host) self.addCleanup(self.client.remove_host, aggregate['id'], self.host) resp, aggregates = self.client.list_aggregates() aggs = filter(lambda x: x['id'] == aggregate['id'], aggregates) self.assertEquals(1, len(aggs)) agg = aggs[0] self.assertEquals(aggregate_name, agg['name']) self.assertEquals(None, agg['availability_zone']) self.assertIn(self.host, agg['hosts']) @attr(type='gate') def test_aggregate_add_host_get_details(self): # Add an host to the given aggregate and get details. aggregate_name = rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(aggregate_name) self.addCleanup(self.client.delete_aggregate, aggregate['id']) self.client.add_host(aggregate['id'], self.host) self.addCleanup(self.client.remove_host, aggregate['id'], self.host) resp, body = self.client.get_aggregate(aggregate['id']) self.assertEquals(aggregate_name, body['name']) self.assertEquals(None, body['availability_zone']) self.assertIn(self.host, body['hosts']) @attr(type='gate') def test_aggregate_add_host_create_server_with_az(self): # Add an host to the given aggregate and create a server. aggregate_name = rand_name(self.aggregate_name_prefix) az_name = rand_name(self.az_name_prefix) resp, aggregate = self.client.create_aggregate(aggregate_name, az_name) self.addCleanup(self.client.delete_aggregate, aggregate['id']) self.client.add_host(aggregate['id'], self.host) self.addCleanup(self.client.remove_host, aggregate['id'], self.host) server_name = rand_name('test_server_') servers_client = self.servers_client admin_servers_client = self.os_adm.servers_client resp, server = self.create_server(name=server_name, availability_zone=az_name) servers_client.wait_for_server_status(server['id'], 'ACTIVE') resp, body = admin_servers_client.get_server(server['id']) self.assertEqual(self.host, body[self._host_key]) @attr(type=['negative', 'gate']) def test_aggregate_add_non_exist_host(self): # Adding a non-exist host to an aggregate should raise exceptions. resp, hosts_all = self.os_adm.hosts_client.list_hosts() hosts = map(lambda x: x['host_name'], hosts_all) while True: non_exist_host = rand_name('nonexist_host_') if non_exist_host not in hosts: break aggregate_name = rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(aggregate_name) self.addCleanup(self.client.delete_aggregate, aggregate['id']) self.assertRaises(exceptions.NotFound, self.client.add_host, aggregate['id'], non_exist_host) @attr(type=['negative', 'gate']) def test_aggregate_add_host_as_user(self): # Regular user is not allowed to add a host to an aggregate. aggregate_name = rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(aggregate_name) self.addCleanup(self.client.delete_aggregate, aggregate['id']) self.assertRaises(exceptions.Unauthorized, self.user_client.add_host, aggregate['id'], self.host) @attr(type=['negative', 'gate']) def test_aggregate_remove_host_as_user(self): # Regular user is not allowed to remove a host from an aggregate. aggregate_name = rand_name(self.aggregate_name_prefix) resp, aggregate = self.client.create_aggregate(aggregate_name) self.addCleanup(self.client.delete_aggregate, aggregate['id']) self.client.add_host(aggregate['id'], self.host) self.addCleanup(self.client.remove_host, aggregate['id'], self.host) self.assertRaises(exceptions.Unauthorized, self.user_client.remove_host, aggregate['id'], self.host) class AggregatesAdminTestXML(AggregatesAdminTestJSON): _host_key = ( '{http://docs.openstack.org/compute/ext/extended_status/api/v1.1}host') _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/admin/test_availability_zone.py0000664000175000017500000000474212161375672027601 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 NEC Corporation # 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 tempest.api.compute import base from tempest import exceptions from tempest.test import attr class AvailabilityZoneAdminTestJSON(base.BaseComputeAdminTest): """ Tests Availability Zone API List that require admin privileges """ _interface = 'json' @classmethod def setUpClass(cls): super(AvailabilityZoneAdminTestJSON, cls).setUpClass() cls.client = cls.os_adm.availability_zone_client cls.non_adm_client = cls.availability_zone_client @attr(type='gate') def test_get_availability_zone_list(self): # List of availability zone resp, availability_zone = self.client.get_availability_zone_list() self.assertEqual(200, resp.status) self.assertTrue(len(availability_zone) > 0) @attr(type='gate') def test_get_availability_zone_list_detail(self): # List of availability zones and available services resp, availability_zone = \ self.client.get_availability_zone_list_detail() self.assertEqual(200, resp.status) self.assertTrue(len(availability_zone) > 0) @attr(type='gate') def test_get_availability_zone_list_with_non_admin_user(self): # List of availability zone with non admin user resp, availability_zone = \ self.non_adm_client.get_availability_zone_list() self.assertEqual(200, resp.status) self.assertTrue(len(availability_zone) > 0) @attr(type=['negative', 'gate']) def test_get_availability_zone_list_detail_with_non_admin_user(self): # List of availability zones and available services with non admin user self.assertRaises( exceptions.Unauthorized, self.non_adm_client.get_availability_zone_list_detail) class AvailabilityZoneAdminTestXML(AvailabilityZoneAdminTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/admin/test_simple_tenant_usage.py0000664000175000017500000001006612161375672030116 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 NEC Corporation # 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 datetime from tempest.api.compute import base from tempest import exceptions from tempest.test import attr import time class TenantUsagesTestJSON(base.BaseComputeAdminTest): _interface = 'json' @classmethod def setUpClass(cls): super(TenantUsagesTestJSON, cls).setUpClass() cls.adm_client = cls.os_adm.tenant_usages_client cls.client = cls.os.tenant_usages_client cls.identity_client = cls._get_identity_admin_client() resp, tenants = cls.identity_client.list_tenants() cls.tenant_id = [tnt['id'] for tnt in tenants if tnt['name'] == cls.client.tenant_name][0] # Create a server in the demo tenant resp, server = cls.create_server(wait_until='ACTIVE') time.sleep(2) now = datetime.datetime.now() cls.start = cls._parse_strtime(now - datetime.timedelta(days=1)) cls.end = cls._parse_strtime(now + datetime.timedelta(days=1)) @classmethod def _parse_strtime(cls, at): # Returns formatted datetime return at.strftime('%Y-%m-%dT%H:%M:%S.%f') @attr(type='gate') def test_list_usage_all_tenants(self): # Get usage for all tenants params = {'start': self.start, 'end': self.end, 'detailed': int(bool(True))} resp, tenant_usage = self.adm_client.list_tenant_usages(params) self.assertEqual(200, resp.status) self.assertEqual(len(tenant_usage), 8) @attr(type='gate') def test_get_usage_tenant(self): # Get usage for a specific tenant params = {'start': self.start, 'end': self.end} resp, tenant_usage = self.adm_client.get_tenant_usage( self.tenant_id, params) self.assertEqual(200, resp.status) self.assertEqual(len(tenant_usage), 8) @attr(type='gate') def test_get_usage_tenant_with_non_admin_user(self): # Get usage for a specific tenant with non admin user params = {'start': self.start, 'end': self.end} resp, tenant_usage = self.client.get_tenant_usage( self.tenant_id, params) self.assertEqual(200, resp.status) self.assertEqual(len(tenant_usage), 8) @attr(type=['negative', 'gate']) def test_get_usage_tenant_with_empty_tenant_id(self): # Get usage for a specific tenant empty params = {'start': self.start, 'end': self.end} self.assertRaises(exceptions.NotFound, self.adm_client.get_tenant_usage, '', params) @attr(type=['negative', 'gate']) def test_get_usage_tenant_with_invalid_date(self): # Get usage for tenant with invalid date params = {'start': self.end, 'end': self.start} self.assertRaises(exceptions.BadRequest, self.adm_client.get_tenant_usage, self.tenant_id, params) @attr(type=['negative', 'gate']) def test_list_usage_all_tenants_with_non_admin_user(self): # Get usage for all tenants with non admin user params = {'start': self.start, 'end': self.end, 'detailed': int(bool(True))} self.assertRaises(exceptions.Unauthorized, self.client.list_tenant_usages, params) class TenantUsagesTestXML(TenantUsagesTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/admin/test_quotas.py0000664000175000017500000002450512161375672025407 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.compute import base from tempest.common.utils.data_utils import rand_name from tempest import exceptions from tempest.test import attr import testtools class QuotasAdminTestJSON(base.BaseComputeAdminTest): _interface = 'json' @classmethod def setUpClass(cls): super(QuotasAdminTestJSON, cls).setUpClass() cls.auth_url = cls.config.identity.uri cls.client = cls.os.quotas_client cls.adm_client = cls.os_adm.quotas_client cls.identity_admin_client = cls._get_identity_admin_client() cls.sg_client = cls.security_groups_client resp, tenants = cls.identity_admin_client.list_tenants() #NOTE(afazekas): these test cases should always create and use a new # tenant most of them should be skipped if we can't do that if cls.config.compute.allow_tenant_isolation: cls.demo_tenant_id = cls.isolated_creds[0][0]['tenantId'] else: cls.demo_tenant_id = [tnt['id'] for tnt in tenants if tnt['name'] == cls.config.identity.tenant_name][0] cls.default_quota_set = {'injected_file_content_bytes': 10240, 'metadata_items': 128, 'injected_files': 5, 'ram': 51200, 'floating_ips': 10, 'fixed_ips': -1, 'key_pairs': 100, 'injected_file_path_bytes': 255, 'instances': 10, 'security_group_rules': 20, 'cores': 20, 'security_groups': 10} @classmethod def tearDownClass(cls): for server in cls.servers: try: cls.servers_client.delete_server(server['id']) except exceptions.NotFound: continue super(QuotasAdminTestJSON, cls).tearDownClass() @attr(type='smoke') def test_get_default_quotas(self): # Admin can get the default resource quota set for a tenant expected_quota_set = self.default_quota_set.copy() expected_quota_set['id'] = self.demo_tenant_id resp, quota_set = self.client.get_default_quota_set( self.demo_tenant_id) self.assertEqual(200, resp.status) self.assertEqual(expected_quota_set, quota_set) @testtools.skip("Skipped until the Bug #1160749 is resolved") @attr(type='gate') def test_update_all_quota_resources_for_tenant(self): # Admin can update all the resource quota limits for a tenant new_quota_set = {'force': True, 'injected_file_content_bytes': 20480, 'metadata_items': 256, 'injected_files': 10, 'ram': 10240, 'floating_ips': 20, 'fixed_ips': 10, 'key_pairs': 200, 'injected_file_path_bytes': 512, 'instances': 20, 'security_group_rules': 20, 'cores': 2, 'security_groups': 20} try: # Update limits for all quota resources resp, quota_set = self.adm_client.update_quota_set( self.demo_tenant_id, **new_quota_set) self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id, **self.default_quota_set) self.assertEqual(200, resp.status) self.assertEqual(new_quota_set, quota_set) except Exception: self.fail("Admin could not update quota set for the tenant") finally: # Reset quota resource limits to default values resp, quota_set = self.adm_client.update_quota_set( self.demo_tenant_id, **self.default_quota_set) self.assertEqual(200, resp.status, "Failed to reset quota " "defaults") #TODO(afazekas): merge these test cases @attr(type='gate') def test_get_updated_quotas(self): # Verify that GET shows the updated quota set self.adm_client.update_quota_set(self.demo_tenant_id, ram='5120') self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id, **self.default_quota_set) try: resp, quota_set = self.client.get_quota_set(self.demo_tenant_id) self.assertEqual(200, resp.status) self.assertEqual(quota_set['ram'], 5120) except Exception: self.fail("Could not get the update quota limit for resource") finally: # Reset quota resource limits to default values resp, quota_set = self.adm_client.update_quota_set( self.demo_tenant_id, **self.default_quota_set) self.assertEqual(200, resp.status, "Failed to reset quota " "defaults") @testtools.skip("Skipped until the Bug #1160749 is resolved") @attr(type='gate') def test_create_server_when_cpu_quota_is_full(self): # Disallow server creation when tenant's vcpu quota is full resp, quota_set = self.client.get_quota_set(self.demo_tenant_id) default_vcpu_quota = quota_set['cores'] vcpu_quota = 0 # Set the quota to zero to conserve resources resp, quota_set = self.adm_client.update_quota_set(self.demo_tenant_id, force=True, cores=vcpu_quota) self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id, cores=default_vcpu_quota) self.assertRaises(exceptions.OverLimit, self.create_server) @testtools.skip("Skipped until the Bug #1160749 is resolved") @attr(type='gate') def test_create_server_when_memory_quota_is_full(self): # Disallow server creation when tenant's memory quota is full resp, quota_set = self.client.get_quota_set(self.demo_tenant_id) default_mem_quota = quota_set['ram'] mem_quota = 0 # Set the quota to zero to conserve resources self.adm_client.update_quota_set(self.demo_tenant_id, force=True, ram=mem_quota) self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id, ram=default_mem_quota) self.assertRaises(exceptions.OverLimit, self.create_server) #TODO(afazekas): Add test that tried to update the quota_set as a regular user @testtools.skip("Skipped until the Bug #1160749 is resolved") @attr(type=['negative', 'gate']) def test_create_server_when_instances_quota_is_full(self): #Once instances quota limit is reached, disallow server creation resp, quota_set = self.client.get_quota_set(self.demo_tenant_id) default_instances_quota = quota_set['instances'] instances_quota = 0 # Set quota to zero to disallow server creation self.adm_client.update_quota_set(self.demo_tenant_id, force=True, instances=instances_quota) self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id, instances=default_instances_quota) self.assertRaises(exceptions.OverLimit, self.create_server) @testtools.skip("Skipped until the Bug #1160749 is resolved") @attr(type=['negative', 'gate']) def test_security_groups_exceed_limit(self): # Negative test: Creation Security Groups over limit should FAIL resp, quota_set = self.client.get_quota_set(self.demo_tenant_id) default_sg_quota = quota_set['security_groups'] sg_quota = 0 # Set the quota to zero to conserve resources resp, quota_set =\ self.adm_client.update_quota_set(self.demo_tenant_id, security_groups=sg_quota) self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id, security_groups=default_sg_quota) # Check we cannot create anymore self.assertRaises(exceptions.OverLimit, self.sg_client.create_security_group, "sg-overlimit", "sg-desc") @testtools.skip("Skipped until the Bug #1160749 is resolved") @attr(type=['negative', 'gate']) def test_security_groups_rules_exceed_limit(self): # Negative test: Creation of Security Group Rules should FAIL # when we reach limit maxSecurityGroupRules resp, quota_set = self.client.get_quota_set(self.demo_tenant_id) default_sg_rules_quota = quota_set['security_group_rules'] sg_rules_quota = 0 # Set the quota to zero to conserve resources resp, quota_set =\ self.adm_client.update_quota_set( self.demo_tenant_id, security_group_rules=sg_rules_quota) self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id, security_group_rules=default_sg_rules_quota) s_name = rand_name('securitygroup-') s_description = rand_name('description-') resp, securitygroup =\ self.sg_client.create_security_group(s_name, s_description) self.addCleanup(self.sg_client.delete_security_group, securitygroup['id']) secgroup_id = securitygroup['id'] ip_protocol = 'tcp' # Check we cannot create SG rule anymore self.assertRaises(exceptions.OverLimit, self.sg_client.create_security_group_rule, secgroup_id, ip_protocol, 1025, 1025) class QuotasAdminTestXML(QuotasAdminTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/admin/test_services.py0000664000175000017500000000325412161375672025714 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 NEC Corporation # 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 tempest.api.compute import base from tempest import exceptions from tempest.test import attr class ServicesAdminTestJSON(base.BaseComputeAdminTest): """ Tests Services API. List and Enable/Disable require admin privileges. """ _interface = 'json' @classmethod def setUpClass(cls): super(ServicesAdminTestJSON, cls).setUpClass() cls.client = cls.os_adm.services_client cls.non_admin_client = cls.services_client @attr(type='gate') def test_list_services(self): # List Compute services resp, services = self.client.list_services() self.assertEqual(200, resp.status) self.assertTrue(len(services) >= 2) @attr(type=['negative', 'gate']) def test_list_services_with_non_admin_user(self): # List Compute service with non admin user self.assertRaises(exceptions.Unauthorized, self.non_admin_client.list_services) class ServicesAdminTestXML(ServicesAdminTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/admin/test_flavors_extra_specs.py0000664000175000017500000001067012161375672030145 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api import compute from tempest.api.compute import base from tempest import exceptions from tempest.test import attr class FlavorsExtraSpecsTestJSON(base.BaseComputeAdminTest): """ Tests Flavor Extra Spec API extension. SET, UNSET Flavor Extra specs require admin privileges. GET Flavor Extra specs can be performed even by without admin privileges. """ _interface = 'json' @classmethod def setUpClass(cls): super(FlavorsExtraSpecsTestJSON, cls).setUpClass() if not compute.FLAVOR_EXTRA_DATA_ENABLED: msg = "FlavorExtraData extension not enabled." raise cls.skipException(msg) cls.client = cls.os_adm.flavors_client flavor_name = 'test_flavor2' ram = 512 vcpus = 1 disk = 10 ephemeral = 10 cls.new_flavor_id = 12345 swap = 1024 rxtx = 1 #Create a flavor so as to set/get/unset extra specs resp, cls.flavor = cls.client.create_flavor(flavor_name, ram, vcpus, disk, cls.new_flavor_id, ephemeral=ephemeral, swap=swap, rxtx=rxtx) @classmethod def tearDownClass(cls): resp, body = cls.client.delete_flavor(cls.flavor['id']) super(FlavorsExtraSpecsTestJSON, cls).tearDownClass() @attr(type='gate') def test_flavor_set_get_unset_keys(self): #Test to SET, GET UNSET flavor extra spec as a user #with admin privileges. #Assigning extra specs values that are to be set specs = {"key1": "value1", "key2": "value2"} #SET extra specs to the flavor created in setUp set_resp, set_body = \ self.client.set_flavor_extra_spec(self.flavor['id'], specs) self.assertEqual(set_resp.status, 200) self.assertEqual(set_body, specs) #GET extra specs and verify get_resp, get_body = \ self.client.get_flavor_extra_spec(self.flavor['id']) self.assertEqual(get_resp.status, 200) self.assertEqual(get_body, specs) #UNSET extra specs that were set in this test unset_resp, _ = \ self.client.unset_flavor_extra_spec(self.flavor['id'], "key1") self.assertEqual(unset_resp.status, 200) @attr(type=['negative', 'gate']) def test_flavor_non_admin_set_keys(self): #Test to SET flavor extra spec as a user without admin privileges. specs = {"key1": "value1", "key2": "value2"} self.assertRaises(exceptions.Unauthorized, self.flavors_client.set_flavor_extra_spec, self.flavor['id'], specs) @attr(type='gate') def test_flavor_non_admin_get_keys(self): specs = {"key1": "value1", "key2": "value2"} set_resp, set_body = self.client.set_flavor_extra_spec( self.flavor['id'], specs) resp, body = self.flavors_client.get_flavor_extra_spec( self.flavor['id']) self.assertEqual(resp.status, 200) for key in specs: self.assertEquals(body[key], specs[key]) @attr(type=['negative', 'gate']) def test_flavor_non_admin_unset_keys(self): specs = {"key1": "value1", "key2": "value2"} set_resp, set_body = self.client.set_flavor_extra_spec( self.flavor['id'], specs) self.assertRaises(exceptions.Unauthorized, self.flavors_client.unset_flavor_extra_spec, self.flavor['id'], 'key1') class FlavorsExtraSpecsTestXML(FlavorsExtraSpecsTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/admin/test_fixed_ips.py0000664000175000017500000001016412161375672026041 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 IBM Corp # 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 tempest.api.compute import base from tempest import exceptions from tempest.test import attr class FixedIPsBase(base.BaseComputeAdminTest): _interface = 'json' ip = None @classmethod def setUpClass(cls): super(FixedIPsBase, cls).setUpClass() # NOTE(maurosr): The idea here is: the server creation is just an # auxiliary element to the ip details or reservation, there was no way # (at least none in my mind) to get an valid and existing ip except # by creating a server and using its ip. So the intention is to create # fewer server possible (one) and use it to both: json and xml tests. # This decreased time to run both tests, in my test machine, from 53 # secs to 29 (agains 23 secs when running only json tests) if cls.ip is None: cls.client = cls.os_adm.fixed_ips_client cls.non_admin_client = cls.fixed_ips_client resp, server = cls.create_server(wait_until='ACTIVE') resp, server = cls.servers_client.get_server(server['id']) for ip_set in server['addresses']: for ip in server['addresses'][ip_set]: if ip['OS-EXT-IPS:type'] == 'fixed': cls.ip = ip['addr'] break if cls.ip: break class FixedIPsTestJson(FixedIPsBase): _interface = 'json' @attr(type='gate') def test_list_fixed_ip_details(self): resp, fixed_ip = self.client.get_fixed_ip_details(self.ip) self.assertEqual(fixed_ip['address'], self.ip) @attr(type=['negative', 'gate']) def test_list_fixed_ip_details_with_non_admin_user(self): self.assertRaises(exceptions.Unauthorized, self.non_admin_client.get_fixed_ip_details, self.ip) @attr(type='gate') def test_set_reserve(self): body = {"reserve": "None"} resp, body = self.client.reserve_fixed_ip(self.ip, body) self.assertEqual(resp.status, 202) @attr(type='gate') def test_set_unreserve(self): body = {"unreserve": "None"} resp, body = self.client.reserve_fixed_ip(self.ip, body) self.assertEqual(resp.status, 202) @attr(type=['negative', 'gate']) def test_set_reserve_with_non_admin_user(self): body = {"reserve": "None"} self.assertRaises(exceptions.Unauthorized, self.non_admin_client.reserve_fixed_ip, self.ip, body) @attr(type=['negative', 'gate']) def test_set_unreserve_with_non_admin_user(self): body = {"unreserve": "None"} self.assertRaises(exceptions.Unauthorized, self.non_admin_client.reserve_fixed_ip, self.ip, body) @attr(type=['negative', 'gate']) def test_set_reserve_with_invalid_ip(self): # NOTE(maurosr): since this exercises the same code snippet, we do it # only for reserve action body = {"reserve": "None"} self.assertRaises(exceptions.NotFound, self.client.reserve_fixed_ip, "my.invalid.ip", body) @attr(type=['negative', 'gate']) def test_fixed_ip_with_invalid_action(self): body = {"invalid_action": "None"} self.assertRaises(exceptions.BadRequest, self.client.reserve_fixed_ip, self.ip, body) class FixedIPsTestXml(FixedIPsTestJson): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/admin/test_flavors_access.py0000664000175000017500000001267712161375672027077 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 NEC Corporation. # 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 tempest.api import compute from tempest.api.compute import base from tempest.common.utils.data_utils import rand_int_id from tempest.common.utils.data_utils import rand_name from tempest import exceptions from tempest.test import attr class FlavorsAccessTestJSON(base.BaseComputeAdminTest): """ Tests Flavor Access API extension. Add and remove Flavor Access require admin privileges. """ _interface = 'json' @classmethod def setUpClass(cls): super(FlavorsAccessTestJSON, cls).setUpClass() if not compute.FLAVOR_EXTRA_DATA_ENABLED: msg = "FlavorExtraData extension not enabled." raise cls.skipException(msg) cls.client = cls.os_adm.flavors_client admin_client = cls._get_identity_admin_client() resp, tenants = admin_client.list_tenants() cls.tenant_id = [tnt['id'] for tnt in tenants if tnt['name'] == cls.flavors_client.tenant_name][0] cls.flavor_name_prefix = 'test_flavor_access_' cls.ram = 512 cls.vcpus = 1 cls.disk = 10 @attr(type='gate') def test_flavor_access_add_remove(self): #Test to add and remove flavor access to a given tenant. flavor_name = rand_name(self.flavor_name_prefix) new_flavor_id = rand_int_id(start=1000) resp, new_flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, is_public='False') self.addCleanup(self.client.delete_flavor, new_flavor['id']) #Add flavor access to a tenant. resp_body = { "tenant_id": str(self.tenant_id), "flavor_id": str(new_flavor['id']), } add_resp, add_body = \ self.client.add_flavor_access(new_flavor['id'], self.tenant_id) self.assertEqual(add_resp.status, 200) self.assertIn(resp_body, add_body) #The flavor is present in list. resp, flavors = self.flavors_client.list_flavors_with_detail() self.assertEqual(resp.status, 200) self.assertIn(new_flavor['id'], map(lambda x: x['id'], flavors)) #Remove flavor access from a tenant. remove_resp, remove_body = \ self.client.remove_flavor_access(new_flavor['id'], self.tenant_id) self.assertEqual(remove_resp.status, 200) self.assertNotIn(resp_body, remove_body) #The flavor is not present in list. resp, flavors = self.flavors_client.list_flavors_with_detail() self.assertEqual(resp.status, 200) self.assertNotIn(new_flavor['id'], map(lambda x: x['id'], flavors)) @attr(type=['negative', 'gate']) def test_flavor_non_admin_add(self): #Test to add flavor access as a user without admin privileges. flavor_name = rand_name(self.flavor_name_prefix) new_flavor_id = rand_int_id(start=1000) resp, new_flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, is_public='False') self.addCleanup(self.client.delete_flavor, new_flavor['id']) self.assertRaises(exceptions.Unauthorized, self.flavors_client.add_flavor_access, new_flavor['id'], self.tenant_id) @attr(type=['negative', 'gate']) def test_flavor_non_admin_remove(self): #Test to remove flavor access as a user without admin privileges. flavor_name = rand_name(self.flavor_name_prefix) new_flavor_id = rand_int_id(start=1000) resp, new_flavor = self.client.create_flavor(flavor_name, self.ram, self.vcpus, self.disk, new_flavor_id, is_public='False') self.addCleanup(self.client.delete_flavor, new_flavor['id']) #Add flavor access to a tenant. self.client.add_flavor_access(new_flavor['id'], self.tenant_id) self.addCleanup(self.client.remove_flavor_access, new_flavor['id'], self.tenant_id) self.assertRaises(exceptions.Unauthorized, self.flavors_client.remove_flavor_access, new_flavor['id'], self.tenant_id) class FlavorsAdminTestXML(FlavorsAccessTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/admin/__init__.py0000664000175000017500000000000012161375672024553 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/security_groups/0000775000175000017500000000000012161375700024622 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/security_groups/test_security_group_rules.py0000664000175000017500000002475012161375672032550 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.compute import base from tempest.common.utils.data_utils import rand_name from tempest import exceptions from tempest.test import attr class SecurityGroupRulesTestJSON(base.BaseComputeTest): _interface = 'json' @classmethod def setUpClass(cls): super(SecurityGroupRulesTestJSON, cls).setUpClass() cls.client = cls.security_groups_client @attr(type='gate') def test_security_group_rules_create(self): # Positive test: Creation of Security Group rule # should be successfull #Creating a Security Group to add rules to it s_name = rand_name('securitygroup-') s_description = rand_name('description-') resp, securitygroup = \ self.client.create_security_group(s_name, s_description) securitygroup_id = securitygroup['id'] self.addCleanup(self.client.delete_security_group, securitygroup_id) #Adding rules to the created Security Group ip_protocol = 'tcp' from_port = 22 to_port = 22 resp, rule = \ self.client.create_security_group_rule(securitygroup_id, ip_protocol, from_port, to_port) self.addCleanup(self.client.delete_security_group_rule, rule['id']) self.assertEqual(200, resp.status) @attr(type='gate') def test_security_group_rules_create_with_optional_arguments(self): # Positive test: Creation of Security Group rule # with optional arguments # should be successfull secgroup1 = None secgroup2 = None #Creating a Security Group to add rules to it s_name = rand_name('securitygroup-') s_description = rand_name('description-') resp, securitygroup = \ self.client.create_security_group(s_name, s_description) secgroup1 = securitygroup['id'] self.addCleanup(self.client.delete_security_group, secgroup1) #Creating a Security Group so as to assign group_id to the rule s_name2 = rand_name('securitygroup-') s_description2 = rand_name('description-') resp, securitygroup = \ self.client.create_security_group(s_name2, s_description2) secgroup2 = securitygroup['id'] self.addCleanup(self.client.delete_security_group, secgroup2) #Adding rules to the created Security Group with optional arguments parent_group_id = secgroup1 ip_protocol = 'tcp' from_port = 22 to_port = 22 cidr = '10.2.3.124/24' group_id = secgroup2 resp, rule = \ self.client.create_security_group_rule(parent_group_id, ip_protocol, from_port, to_port, cidr=cidr, group_id=group_id) self.addCleanup(self.client.delete_security_group_rule, rule['id']) self.assertEqual(200, resp.status) @attr(type=['negative', 'gate']) def test_security_group_rules_create_with_invalid_id(self): # Negative test: Creation of Security Group rule should FAIL # with invalid Parent group id # Adding rules to the invalid Security Group id parent_group_id = rand_name('999') ip_protocol = 'tcp' from_port = 22 to_port = 22 self.assertRaises(exceptions.NotFound, self.client.create_security_group_rule, parent_group_id, ip_protocol, from_port, to_port) @attr(type=['negative', 'gate']) def test_security_group_rules_create_with_invalid_ip_protocol(self): # Negative test: Creation of Security Group rule should FAIL # with invalid ip_protocol #Creating a Security Group to add rule to it s_name = rand_name('securitygroup-') s_description = rand_name('description-') resp, securitygroup = self.client.create_security_group(s_name, s_description) #Adding rules to the created Security Group parent_group_id = securitygroup['id'] ip_protocol = rand_name('999') from_port = 22 to_port = 22 self.addCleanup(self.client.delete_security_group, securitygroup['id']) self.assertRaises(exceptions.BadRequest, self.client.create_security_group_rule, parent_group_id, ip_protocol, from_port, to_port) @attr(type=['negative', 'gate']) def test_security_group_rules_create_with_invalid_from_port(self): # Negative test: Creation of Security Group rule should FAIL # with invalid from_port #Creating a Security Group to add rule to it s_name = rand_name('securitygroup-') s_description = rand_name('description-') resp, securitygroup = self.client.create_security_group(s_name, s_description) #Adding rules to the created Security Group parent_group_id = securitygroup['id'] ip_protocol = 'tcp' from_port = rand_name('999') to_port = 22 self.addCleanup(self.client.delete_security_group, securitygroup['id']) self.assertRaises(exceptions.BadRequest, self.client.create_security_group_rule, parent_group_id, ip_protocol, from_port, to_port) @attr(type=['negative', 'gate']) def test_security_group_rules_create_with_invalid_to_port(self): # Negative test: Creation of Security Group rule should FAIL # with invalid from_port #Creating a Security Group to add rule to it s_name = rand_name('securitygroup-') s_description = rand_name('description-') resp, securitygroup = self.client.create_security_group(s_name, s_description) #Adding rules to the created Security Group parent_group_id = securitygroup['id'] ip_protocol = 'tcp' from_port = 22 to_port = rand_name('999') self.addCleanup(self.client.delete_security_group, securitygroup['id']) self.assertRaises(exceptions.BadRequest, self.client.create_security_group_rule, parent_group_id, ip_protocol, from_port, to_port) @attr(type=['negative', 'gate']) def test_security_group_rules_create_with_invalid_port_range(self): # Negative test: Creation of Security Group rule should FAIL # with invalid port range. # Creating a Security Group to add rule to it. s_name = rand_name('securitygroup-') s_description = rand_name('description-') resp, securitygroup = self.client.create_security_group(s_name, s_description) # Adding a rule to the created Security Group secgroup_id = securitygroup['id'] ip_protocol = 'tcp' from_port = 22 to_port = 21 self.addCleanup(self.client.delete_security_group, securitygroup['id']) self.assertRaises(exceptions.BadRequest, self.client.create_security_group_rule, secgroup_id, ip_protocol, from_port, to_port) @attr(type=['negative', 'gate']) def test_security_group_rules_delete_with_invalid_id(self): # Negative test: Deletion of Security Group rule should be FAIL # with invalid rule id self.assertRaises(exceptions.NotFound, self.client.delete_security_group_rule, rand_name('999')) @attr(type='gate') def test_security_group_rules_list(self): # Positive test: Created Security Group rules should be # in the list of all rules # Creating a Security Group to add rules to it s_name = rand_name('securitygroup-') s_description = rand_name('description-') resp, securitygroup = \ self.client.create_security_group(s_name, s_description) securitygroup_id = securitygroup['id'] # Delete the Security Group at the end of this method self.addCleanup(self.client.delete_security_group, securitygroup_id) # Add a first rule to the created Security Group ip_protocol1 = 'tcp' from_port1 = 22 to_port1 = 22 resp, rule = \ self.client.create_security_group_rule(securitygroup_id, ip_protocol1, from_port1, to_port1) rule1_id = rule['id'] # Delete the Security Group rule1 at the end of this method self.addCleanup(self.client.delete_security_group_rule, rule1_id) # Add a second rule to the created Security Group ip_protocol2 = 'icmp' from_port2 = -1 to_port2 = -1 resp, rule = \ self.client.create_security_group_rule(securitygroup_id, ip_protocol2, from_port2, to_port2) rule2_id = rule['id'] # Delete the Security Group rule2 at the end of this method self.addCleanup(self.client.delete_security_group_rule, rule2_id) # Get rules of the created Security Group resp, rules = \ self.client.list_security_group_rules(securitygroup_id) self.assertTrue(any([i for i in rules if i['id'] == rule1_id])) self.assertTrue(any([i for i in rules if i['id'] == rule2_id])) class SecurityGroupRulesTestXML(SecurityGroupRulesTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/security_groups/test_security_groups.py0000664000175000017500000002771512161375672031525 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.compute import base from tempest.common.utils.data_utils import rand_name from tempest import exceptions from tempest.test import attr class SecurityGroupsTestJSON(base.BaseComputeTest): _interface = 'json' @classmethod def setUpClass(cls): super(SecurityGroupsTestJSON, cls).setUpClass() cls.client = cls.security_groups_client def _delete_security_group(self, securitygroup_id): resp, _ = self.client.delete_security_group(securitygroup_id) self.assertEqual(202, resp.status) @attr(type='gate') def test_security_groups_create_list_delete(self): # Positive test:Should return the list of Security Groups #Create 3 Security Groups security_group_list = list() for i in range(3): s_name = rand_name('securitygroup-') s_description = rand_name('description-') resp, securitygroup = \ self.client.create_security_group(s_name, s_description) self.assertEqual(200, resp.status) self.addCleanup(self._delete_security_group, securitygroup['id']) security_group_list.append(securitygroup) #Fetch all Security Groups and verify the list #has all created Security Groups resp, fetched_list = self.client.list_security_groups() self.assertEqual(200, resp.status) #Now check if all the created Security Groups are in fetched list missing_sgs = \ [sg for sg in security_group_list if sg not in fetched_list] self.assertFalse(missing_sgs, "Failed to find Security Group %s in fetched " "list" % ', '.join(m_group['name'] for m_group in missing_sgs)) #TODO(afazekas): scheduled for delete, #test_security_group_create_get_delete covers it @attr(type='gate') def test_security_group_create_delete(self): # Security Group should be created, verified and deleted s_name = rand_name('securitygroup-') s_description = rand_name('description-') resp, securitygroup = \ self.client.create_security_group(s_name, s_description) self.assertTrue('id' in securitygroup) securitygroup_id = securitygroup['id'] self.addCleanup(self._delete_security_group, securitygroup_id) self.assertEqual(200, resp.status) self.assertFalse(securitygroup_id is None) self.assertTrue('name' in securitygroup) securitygroup_name = securitygroup['name'] self.assertEqual(securitygroup_name, s_name, "The created Security Group name is " "not equal to the requested name") @attr(type='gate') def test_security_group_create_get_delete(self): # Security Group should be created, fetched and deleted s_name = rand_name('securitygroup-') s_description = rand_name('description-') resp, securitygroup = \ self.client.create_security_group(s_name, s_description) self.addCleanup(self._delete_security_group, securitygroup['id']) self.assertEqual(200, resp.status) self.assertTrue('name' in securitygroup) securitygroup_name = securitygroup['name'] self.assertEqual(securitygroup_name, s_name, "The created Security Group name is " "not equal to the requested name") #Now fetch the created Security Group by its 'id' resp, fetched_group = \ self.client.get_security_group(securitygroup['id']) self.assertEqual(200, resp.status) self.assertEqual(securitygroup, fetched_group, "The fetched Security Group is different " "from the created Group") @attr(type=['negative', 'gate']) def test_security_group_get_nonexistant_group(self): # Negative test:Should not be able to GET the details # of nonexistant Security Group security_group_id = [] resp, body = self.client.list_security_groups() for i in range(len(body)): security_group_id.append(body[i]['id']) #Creating a nonexistant Security Group id while True: non_exist_id = rand_name('999') if non_exist_id not in security_group_id: break self.assertRaises(exceptions.NotFound, self.client.get_security_group, non_exist_id) @attr(type=['negative', 'gate']) def test_security_group_create_with_invalid_group_name(self): # Negative test: Security Group should not be created with group name # as an empty string/with white spaces/chars more than 255 s_description = rand_name('description-') # Create Security Group with empty string as group name self.assertRaises(exceptions.BadRequest, self.client.create_security_group, "", s_description) # Create Security Group with white space in group name self.assertRaises(exceptions.BadRequest, self.client.create_security_group, " ", s_description) # Create Security Group with group name longer than 255 chars s_name = 'securitygroup-'.ljust(260, '0') self.assertRaises(exceptions.BadRequest, self.client.create_security_group, s_name, s_description) @attr(type=['negative', 'gate']) def test_security_group_create_with_invalid_group_description(self): # Negative test:Security Group should not be created with description # as an empty string/with white spaces/chars more than 255 s_name = rand_name('securitygroup-') # Create Security Group with empty string as description self.assertRaises(exceptions.BadRequest, self.client.create_security_group, s_name, "") # Create Security Group with white space in description self.assertRaises(exceptions.BadRequest, self.client.create_security_group, s_name, " ") # Create Security Group with group description longer than 255 chars s_description = 'description-'.ljust(260, '0') self.assertRaises(exceptions.BadRequest, self.client.create_security_group, s_name, s_description) @attr(type=['negative', 'gate']) def test_security_group_create_with_duplicate_name(self): # Negative test:Security Group with duplicate name should not # be created s_name = rand_name('securitygroup-') s_description = rand_name('description-') resp, security_group =\ self.client.create_security_group(s_name, s_description) self.assertEqual(200, resp.status) self.addCleanup(self.client.delete_security_group, security_group['id']) # Now try the Security Group with the same 'Name' self.assertRaises(exceptions.BadRequest, self.client.create_security_group, s_name, s_description) @attr(type=['negative', 'gate']) def test_delete_the_default_security_group(self): # Negative test:Deletion of the "default" Security Group should Fail default_security_group_id = None resp, body = self.client.list_security_groups() for i in range(len(body)): if body[i]['name'] == 'default': default_security_group_id = body[i]['id'] break #Deleting the "default" Security Group self.assertRaises(exceptions.BadRequest, self.client.delete_security_group, default_security_group_id) @attr(type=['negative', 'gate']) def test_delete_nonexistant_security_group(self): # Negative test:Deletion of a nonexistant Security Group should Fail security_group_id = [] resp, body = self.client.list_security_groups() for i in range(len(body)): security_group_id.append(body[i]['id']) #Creating Non Existant Security Group while True: non_exist_id = rand_name('999') if non_exist_id not in security_group_id: break self.assertRaises(exceptions.NotFound, self.client.delete_security_group, non_exist_id) @attr(type=['negative', 'gate']) def test_delete_security_group_without_passing_id(self): # Negative test:Deletion of a Security Group with out passing ID # should Fail self.assertRaises(exceptions.NotFound, self.client.delete_security_group, '') @attr(type='gate') def test_server_security_groups(self): # Checks that security groups may be added and linked to a server # and not deleted if the server is active. # Create a couple security groups that we will use # for the server resource this test creates sg_name = rand_name('sg') sg_desc = rand_name('sg-desc') resp, sg = self.client.create_security_group(sg_name, sg_desc) sg_id = sg['id'] sg2_name = rand_name('sg') sg2_desc = rand_name('sg-desc') resp, sg2 = self.client.create_security_group(sg2_name, sg2_desc) sg2_id = sg2['id'] # Create server and add the security group created # above to the server we just created server_name = rand_name('server') resp, server = self.servers_client.create_server(server_name, self.image_ref, self.flavor_ref) server_id = server['id'] self.servers_client.wait_for_server_status(server_id, 'ACTIVE') resp, body = self.servers_client.add_security_group(server_id, sg_name) # Check that we are not able to delete the security # group since it is in use by an active server self.assertRaises(exceptions.BadRequest, self.client.delete_security_group, sg_id) # Reboot and add the other security group resp, body = self.servers_client.reboot(server_id, 'HARD') self.servers_client.wait_for_server_status(server_id, 'ACTIVE') resp, body = self.servers_client.add_security_group(server_id, sg2_name) # Check that we are not able to delete the other security # group since it is in use by an active server self.assertRaises(exceptions.BadRequest, self.client.delete_security_group, sg2_id) # Shutdown the server and then verify we can destroy the # security groups, since no active server instance is using them self.servers_client.delete_server(server_id) self.servers_client.wait_for_server_termination(server_id) self.client.delete_security_group(sg_id) self.assertEqual(202, resp.status) self.client.delete_security_group(sg2_id) self.assertEqual(202, resp.status) class SecurityGroupsTestXML(SecurityGroupsTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/security_groups/__init__.py0000664000175000017500000000000012161375672026731 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/test_extensions.py0000664000175000017500000000216712161375672025202 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.compute import base from tempest.test import attr class ExtensionsTestJSON(base.BaseComputeTest): _interface = 'json' @attr(type='gate') def test_list_extensions(self): # List of all extensions resp, extensions = self.extensions_client.list_extensions() self.assertTrue("extensions" in extensions) self.assertEqual(200, resp.status) class ExtensionsTestXML(ExtensionsTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/test_quotas.py0000664000175000017500000000474612161375672024324 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.compute import base from tempest.test import attr class QuotasTestJSON(base.BaseComputeTest): _interface = 'json' @classmethod def setUpClass(cls): super(QuotasTestJSON, cls).setUpClass() cls.client = cls.quotas_client cls.admin_client = cls._get_identity_admin_client() resp, tenants = cls.admin_client.list_tenants() cls.tenant_id = [tnt['id'] for tnt in tenants if tnt['name'] == cls.client.tenant_name][0] cls.default_quota_set = {'injected_file_content_bytes': 10240, 'metadata_items': 128, 'injected_files': 5, 'ram': 51200, 'floating_ips': 10, 'fixed_ips': -1, 'key_pairs': 100, 'injected_file_path_bytes': 255, 'instances': 10, 'security_group_rules': 20, 'cores': 20, 'security_groups': 10} @attr(type='smoke') def test_get_quotas(self): # User can get the quota set for it's tenant expected_quota_set = self.default_quota_set.copy() expected_quota_set['id'] = self.tenant_id resp, quota_set = self.client.get_quota_set(self.tenant_id) self.assertEqual(200, resp.status) self.assertEqual(expected_quota_set, quota_set) @attr(type='smoke') def test_get_default_quotas(self): # User can get the default quota set for it's tenant expected_quota_set = self.default_quota_set.copy() expected_quota_set['id'] = self.tenant_id resp, quota_set = self.client.get_default_quota_set(self.tenant_id) self.assertEqual(200, resp.status) self.assertEqual(expected_quota_set, quota_set) class QuotasTestXML(QuotasTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/floating_ips/0000775000175000017500000000000012161375700024032 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/floating_ips/test_floating_ips_actions.py0000664000175000017500000001735512161375672031664 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.compute import base from tempest.common.utils.data_utils import rand_name from tempest import exceptions from tempest.test import attr class FloatingIPsTestJSON(base.BaseComputeTest): _interface = 'json' server_id = None floating_ip = None @classmethod def setUpClass(cls): super(FloatingIPsTestJSON, cls).setUpClass() cls.client = cls.floating_ips_client cls.servers_client = cls.servers_client #Server creation resp, server = cls.create_server(wait_until='ACTIVE') cls.server_id = server['id'] resp, body = cls.servers_client.get_server(server['id']) #Floating IP creation resp, body = cls.client.create_floating_ip() cls.floating_ip_id = body['id'] cls.floating_ip = body['ip'] #Generating a nonexistent floatingIP id cls.floating_ip_ids = [] resp, body = cls.client.list_floating_ips() for i in range(len(body)): cls.floating_ip_ids.append(body[i]['id']) while True: cls.non_exist_id = rand_name('999') if cls.non_exist_id not in cls.floating_ip_ids: break @classmethod def tearDownClass(cls): #Deleting the floating IP which is created in this method resp, body = cls.client.delete_floating_ip(cls.floating_ip_id) super(FloatingIPsTestJSON, cls).tearDownClass() @attr(type='gate') def test_allocate_floating_ip(self): # Positive test:Allocation of a new floating IP to a project # should be successful try: resp, body = self.client.create_floating_ip() self.assertEqual(200, resp.status) floating_ip_id_allocated = body['id'] resp, floating_ip_details = \ self.client.get_floating_ip_details(floating_ip_id_allocated) #Checking if the details of allocated IP is in list of floating IP resp, body = self.client.list_floating_ips() self.assertTrue(floating_ip_details in body) finally: #Deleting the floating IP which is created in this method self.client.delete_floating_ip(floating_ip_id_allocated) @attr(type=['negative', 'gate']) def test_allocate_floating_ip_from_nonexistent_pool(self): # Positive test:Allocation of a new floating IP from a nonexistent_pool #to a project should fail self.assertRaises(exceptions.NotFound, self.client.create_floating_ip, "non_exist_pool") @attr(type='gate') def test_delete_floating_ip(self): # Positive test:Deletion of valid floating IP from project # should be successful #Creating the floating IP that is to be deleted in this method resp, floating_ip_body = self.client.create_floating_ip() #Storing the details of floating IP before deleting it cli_resp = self.client.get_floating_ip_details(floating_ip_body['id']) resp, floating_ip_details = cli_resp #Deleting the floating IP from the project resp, body = self.client.delete_floating_ip(floating_ip_body['id']) self.assertEqual(202, resp.status) # Check it was really deleted. self.client.wait_for_resource_deletion(floating_ip_body['id']) @attr(type='gate') def test_associate_disassociate_floating_ip(self): # Positive test:Associate and disassociate the provided floating IP # to a specific server should be successful #Association of floating IP to fixed IP address resp, body = self.client.associate_floating_ip_to_server( self.floating_ip, self.server_id) self.assertEqual(202, resp.status) #Disassociation of floating IP that was associated in this method resp, body = self.client.disassociate_floating_ip_from_server( self.floating_ip, self.server_id) self.assertEqual(202, resp.status) @attr(type=['negative', 'gate']) def test_delete_nonexistant_floating_ip(self): # Negative test:Deletion of a nonexistent floating IP # from project should fail # Deleting the non existent floating IP self.assertRaises(exceptions.NotFound, self.client.delete_floating_ip, self.non_exist_id) @attr(type=['negative', 'gate']) def test_associate_nonexistant_floating_ip(self): # Negative test:Association of a non existent floating IP # to specific server should fail # Associating non existent floating IP self.assertRaises(exceptions.NotFound, self.client.associate_floating_ip_to_server, "0.0.0.0", self.server_id) @attr(type=['negative', 'gate']) def test_dissociate_nonexistant_floating_ip(self): # Negative test:Dissociation of a non existent floating IP should fail # Dissociating non existent floating IP self.assertRaises(exceptions.NotFound, self.client.disassociate_floating_ip_from_server, "0.0.0.0", self.server_id) @attr(type='gate') def test_associate_already_associated_floating_ip(self): # positive test:Association of an already associated floating IP # to specific server should change the association of the Floating IP #Create server so as to use for Multiple association resp, body = self.servers_client.create_server('floating-server2', self.image_ref, self.flavor_ref) self.servers_client.wait_for_server_status(body['id'], 'ACTIVE') self.new_server_id = body['id'] #Associating floating IP for the first time resp, _ = self.client.associate_floating_ip_to_server( self.floating_ip, self.server_id) #Associating floating IP for the second time resp, body = self.client.associate_floating_ip_to_server( self.floating_ip, self.new_server_id) self.addCleanup(self.servers_client.delete_server, self.new_server_id) if (resp['status'] is not None): self.addCleanup(self.client.disassociate_floating_ip_from_server, self.floating_ip, self.new_server_id) # Make sure no longer associated with old server self.assertRaises((exceptions.NotFound, exceptions.UnprocessableEntity), self.client.disassociate_floating_ip_from_server, self.floating_ip, self.server_id) @attr(type=['negative', 'gate']) def test_associate_ip_to_server_without_passing_floating_ip(self): # Negative test:Association of empty floating IP to specific server # should raise NotFound exception self.assertRaises(exceptions.NotFound, self.client.associate_floating_ip_to_server, '', self.server_id) class FloatingIPsTestXML(FloatingIPsTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/floating_ips/test_list_floating_ips.py0000664000175000017500000000737312161375672031176 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.compute import base from tempest.common.utils.data_utils import rand_name from tempest import exceptions from tempest.test import attr class FloatingIPDetailsTestJSON(base.BaseComputeTest): _interface = 'json' @classmethod def setUpClass(cls): super(FloatingIPDetailsTestJSON, cls).setUpClass() cls.client = cls.floating_ips_client cls.floating_ip = [] cls.floating_ip_id = [] cls.random_number = 0 for i in range(3): resp, body = cls.client.create_floating_ip() cls.floating_ip.append(body) cls.floating_ip_id.append(body['id']) @classmethod def tearDownClass(cls): for i in range(3): cls.client.delete_floating_ip(cls.floating_ip_id[i]) super(FloatingIPDetailsTestJSON, cls).tearDownClass() @attr(type='gate') def test_list_floating_ips(self): # Positive test:Should return the list of floating IPs resp, body = self.client.list_floating_ips() self.assertEqual(200, resp.status) floating_ips = body self.assertNotEqual(0, len(floating_ips), "Expected floating IPs. Got zero.") for i in range(3): self.assertTrue(self.floating_ip[i] in floating_ips) @attr(type='gate') def test_get_floating_ip_details(self): # Positive test:Should be able to GET the details of floatingIP #Creating a floating IP for which details are to be checked try: resp, body = self.client.create_floating_ip() floating_ip_instance_id = body['instance_id'] floating_ip_ip = body['ip'] floating_ip_fixed_ip = body['fixed_ip'] floating_ip_id = body['id'] resp, body = \ self.client.get_floating_ip_details(floating_ip_id) self.assertEqual(200, resp.status) #Comparing the details of floating IP self.assertEqual(floating_ip_instance_id, body['instance_id']) self.assertEqual(floating_ip_ip, body['ip']) self.assertEqual(floating_ip_fixed_ip, body['fixed_ip']) self.assertEqual(floating_ip_id, body['id']) #Deleting the floating IP created in this method finally: self.client.delete_floating_ip(floating_ip_id) @attr(type=['negative', 'gate']) def test_get_nonexistant_floating_ip_details(self): # Negative test:Should not be able to GET the details # of nonexistant floating IP floating_ip_id = [] resp, body = self.client.list_floating_ips() for i in range(len(body)): floating_ip_id.append(body[i]['id']) #Creating a nonexistant floatingIP id while True: non_exist_id = rand_name('999') if non_exist_id not in floating_ip_id: break self.assertRaises(exceptions.NotFound, self.client.get_floating_ip_details, non_exist_id) class FloatingIPDetailsTestXML(FloatingIPDetailsTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/floating_ips/__init__.py0000664000175000017500000000000012161375672026141 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/keypairs/0000775000175000017500000000000012161375700023203 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/keypairs/test_keypairs.py0000664000175000017500000002070712161375672026461 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.compute import base from tempest.common.utils.data_utils import rand_name from tempest import exceptions from tempest.test import attr class KeyPairsTestJSON(base.BaseComputeTest): _interface = 'json' @classmethod def setUpClass(cls): super(KeyPairsTestJSON, cls).setUpClass() cls.client = cls.keypairs_client @attr(type='gate') def test_keypairs_create_list_delete(self): # Keypairs created should be available in the response list #Create 3 keypairs key_list = list() for i in range(3): k_name = rand_name('keypair-') resp, keypair = self.client.create_keypair(k_name) #Need to pop these keys so that our compare doesn't fail later, #as the keypair dicts from list API doesn't have them. keypair.pop('private_key') keypair.pop('user_id') self.assertEqual(200, resp.status) key_list.append(keypair) #Fetch all keypairs and verify the list #has all created keypairs resp, fetched_list = self.client.list_keypairs() self.assertEqual(200, resp.status) #We need to remove the extra 'keypair' element in the #returned dict. See comment in keypairs_client.list_keypairs() new_list = list() for keypair in fetched_list: new_list.append(keypair['keypair']) fetched_list = new_list #Now check if all the created keypairs are in the fetched list missing_kps = [kp for kp in key_list if kp not in fetched_list] self.assertFalse(missing_kps, "Failed to find keypairs %s in fetched list" % ', '.join(m_key['name'] for m_key in missing_kps)) #Delete all the keypairs created for keypair in key_list: resp, _ = self.client.delete_keypair(keypair['name']) self.assertEqual(202, resp.status) @attr(type='gate') def test_keypair_create_delete(self): # Keypair should be created, verified and deleted k_name = rand_name('keypair-') resp, keypair = self.client.create_keypair(k_name) self.assertEqual(200, resp.status) private_key = keypair['private_key'] key_name = keypair['name'] self.assertEqual(key_name, k_name, "The created keypair name is not equal " "to the requested name") self.assertTrue(private_key is not None, "Field private_key is empty or not found.") resp, _ = self.client.delete_keypair(k_name) self.assertEqual(202, resp.status) @attr(type='gate') def test_get_keypair_detail(self): # Keypair should be created, Got details by name and deleted k_name = rand_name('keypair-') resp, keypair = self.client.create_keypair(k_name) try: resp, keypair_detail = self.client.get_keypair(k_name) self.assertEqual(200, resp.status) self.assertTrue('name' in keypair_detail) self.assertTrue('public_key' in keypair_detail) self.assertEqual(keypair_detail['name'], k_name, "The created keypair name is not equal " "to requested name") public_key = keypair_detail['public_key'] self.assertTrue(public_key is not None, "Field public_key is empty or not found.") except Exception: self.fail("GET keypair details requested by keypair name " "has failed") finally: resp, _ = self.client.delete_keypair(k_name) self.assertEqual(202, resp.status) @attr(type='gate') def test_keypair_create_with_pub_key(self): # Keypair should be created with a given public key k_name = rand_name('keypair-') pub_key = ("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCs" "Ne3/1ILNCqFyfYWDeTKLD6jEXC2OQHLmietMWW+/vd" "aZq7KZEwO0jhglaFjU1mpqq4Gz5RX156sCTNM9vRbw" "KAxfsdF9laBYVsex3m3Wmui3uYrKyumsoJn2g9GNnG1P" "I1mrVjZ61i0GY3khna+wzlTpCCmy5HNlrmbj3XLqBUpip" "TOXmsnr4sChzC53KCd8LXuwc1i/CZPvF+3XipvAgFSE53pCt" "LOeB1kYMOBaiUPLQTWXR3JpckqFIQwhIH0zoHlJvZE8hh90" "XcPojYN56tI0OlrGqojbediJYD0rUsJu4weZpbn8vilb3JuDY+jws" "snSA8wzBx3A/8y9Pp1B nova@ubuntu") resp, keypair = self.client.create_keypair(k_name, pub_key) self.assertEqual(200, resp.status) self.assertFalse('private_key' in keypair, "Field private_key is not empty!") key_name = keypair['name'] self.assertEqual(key_name, k_name, "The created keypair name is not equal " "to the requested name!") resp, _ = self.client.delete_keypair(k_name) self.assertEqual(202, resp.status) @attr(type=['negative', 'gate']) def test_keypair_create_with_invalid_pub_key(self): # Keypair should not be created with a non RSA public key k_name = rand_name('keypair-') pub_key = "ssh-rsa JUNK nova@ubuntu" self.assertRaises(exceptions.BadRequest, self.client.create_keypair, k_name, pub_key) @attr(type=['negative', 'gate']) def test_keypair_delete_nonexistant_key(self): # Non-existant key deletion should throw a proper error k_name = rand_name("keypair-non-existant-") self.assertRaises(exceptions.NotFound, self.client.delete_keypair, k_name) @attr(type=['negative', 'gate']) def test_create_keypair_with_empty_public_key(self): # Keypair should not be created with an empty public key k_name = rand_name("keypair-") pub_key = ' ' self.assertRaises(exceptions.BadRequest, self.client.create_keypair, k_name, pub_key) @attr(type=['negative', 'gate']) def test_create_keypair_when_public_key_bits_exceeds_maximum(self): # Keypair should not be created when public key bits are too long k_name = rand_name("keypair-") pub_key = 'ssh-rsa ' + 'A' * 2048 + ' openstack@ubuntu' self.assertRaises(exceptions.BadRequest, self.client.create_keypair, k_name, pub_key) @attr(type=['negative', 'gate']) def test_create_keypair_with_duplicate_name(self): # Keypairs with duplicate names should not be created k_name = rand_name('keypair-') resp, _ = self.client.create_keypair(k_name) self.assertEqual(200, resp.status) #Now try the same keyname to ceate another key self.assertRaises(exceptions.Duplicate, self.client.create_keypair, k_name) resp, _ = self.client.delete_keypair(k_name) self.assertEqual(202, resp.status) @attr(type=['negative', 'gate']) def test_create_keypair_with_empty_name_string(self): # Keypairs with name being an empty string should not be created self.assertRaises(exceptions.BadRequest, self.client.create_keypair, '') @attr(type=['negative', 'gate']) def test_create_keypair_with_long_keynames(self): # Keypairs with name longer than 255 chars should not be created k_name = 'keypair-'.ljust(260, '0') self.assertRaises(exceptions.BadRequest, self.client.create_keypair, k_name) @attr(type=['negative', 'gate']) def test_create_keypair_invalid_name(self): # Keypairs with name being an invalid name should not be created k_name = 'key_/.\@:' self.assertRaises(exceptions.BadRequest, self.client.create_keypair, k_name) class KeyPairsTestXML(KeyPairsTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/keypairs/__init__.py0000664000175000017500000000000012161375672025312 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/images/0000775000175000017500000000000012161375700022621 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/images/test_list_images.py0000664000175000017500000000342712161375672026550 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.compute import base from tempest.test import attr class ListImagesTestJSON(base.BaseComputeTest): _interface = 'json' @classmethod def setUpClass(cls): super(ListImagesTestJSON, cls).setUpClass() cls.client = cls.images_client @attr(type='smoke') def test_get_image(self): # Returns the correct details for a single image resp, image = self.client.get_image(self.image_ref) self.assertEqual(self.image_ref, image['id']) @attr(type='smoke') def test_list_images(self): # The list of all images should contain the image resp, images = self.client.list_images() found = any([i for i in images if i['id'] == self.image_ref]) self.assertTrue(found) @attr(type='smoke') def test_list_images_with_detail(self): # Detailed list of all images should contain the expected images resp, images = self.client.list_images_with_detail() found = any([i for i in images if i['id'] == self.image_ref]) self.assertTrue(found) class ListImagesTestXML(ListImagesTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/images/test_list_image_filters.py0000664000175000017500000002417512161375672030120 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.compute import base from tempest.common import log as logging from tempest.common.utils.data_utils import parse_image_id from tempest import exceptions from tempest.test import attr LOG = logging.getLogger(__name__) class ListImageFiltersTestJSON(base.BaseComputeTest): _interface = 'json' @classmethod def setUpClass(cls): super(ListImageFiltersTestJSON, cls).setUpClass() cls.client = cls.images_client cls.image_ids = [] try: resp, cls.server1 = cls.create_server() resp, cls.server2 = cls.create_server(wait_until='ACTIVE') # NOTE(sdague) this is faster than doing the sync wait_util on both cls.servers_client.wait_for_server_status(cls.server1['id'], 'ACTIVE') # Create images to be used in the filter tests resp, body = cls.create_image_from_server(cls.server1['id']) cls.image1_id = parse_image_id(resp['location']) cls.client.wait_for_image_resp_code(cls.image1_id, 200) cls.client.wait_for_image_status(cls.image1_id, 'ACTIVE') resp, cls.image1 = cls.client.get_image(cls.image1_id) # Servers have a hidden property for when they are being imaged # Performing back-to-back create image calls on a single # server will sometimes cause failures resp, body = cls.create_image_from_server(cls.server2['id']) cls.image3_id = parse_image_id(resp['location']) cls.client.wait_for_image_resp_code(cls.image3_id, 200) cls.client.wait_for_image_status(cls.image3_id, 'ACTIVE') resp, cls.image3 = cls.client.get_image(cls.image3_id) resp, body = cls.create_image_from_server(cls.server1['id']) cls.image2_id = parse_image_id(resp['location']) cls.client.wait_for_image_resp_code(cls.image2_id, 200) cls.client.wait_for_image_status(cls.image2_id, 'ACTIVE') resp, cls.image2 = cls.client.get_image(cls.image2_id) except Exception as exc: LOG.exception(exc) cls.tearDownClass() raise @attr(type=['negative', 'gate']) def test_get_image_not_existing(self): # Check raises a NotFound self.assertRaises(exceptions.NotFound, self.client.get_image, "nonexistingimageid") @attr(type='gate') def test_list_images_filter_by_status(self): # The list of images should contain only images with the # provided status params = {'status': 'ACTIVE'} resp, images = self.client.list_images(params) self.assertTrue(any([i for i in images if i['id'] == self.image1_id])) self.assertTrue(any([i for i in images if i['id'] == self.image2_id])) self.assertTrue(any([i for i in images if i['id'] == self.image3_id])) @attr(type='gate') def test_list_images_filter_by_name(self): # List of all images should contain the expected images filtered # by name params = {'name': self.image1['name']} resp, images = self.client.list_images(params) self.assertTrue(any([i for i in images if i['id'] == self.image1_id])) self.assertFalse(any([i for i in images if i['id'] == self.image2_id])) self.assertFalse(any([i for i in images if i['id'] == self.image3_id])) @attr(type='gate') def test_list_images_filter_by_server_id(self): # The images should contain images filtered by server id params = {'server': self.server1['id']} resp, images = self.client.list_images(params) self.assertTrue(any([i for i in images if i['id'] == self.image1_id]), "Failed to find image %s in images. Got images %s" % (self.image1_id, images)) self.assertTrue(any([i for i in images if i['id'] == self.image2_id])) self.assertFalse(any([i for i in images if i['id'] == self.image3_id])) @attr(type='gate') def test_list_images_filter_by_server_ref(self): # The list of servers should be filtered by server ref server_links = self.server2['links'] # Try all server link types for link in server_links: params = {'server': link['href']} resp, images = self.client.list_images(params) self.assertFalse(any([i for i in images if i['id'] == self.image1_id])) self.assertFalse(any([i for i in images if i['id'] == self.image2_id])) self.assertTrue(any([i for i in images if i['id'] == self.image3_id])) @attr(type='gate') def test_list_images_filter_by_type(self): # The list of servers should be filtered by image type params = {'type': 'snapshot'} resp, images = self.client.list_images(params) self.assertTrue(any([i for i in images if i['id'] == self.image1_id])) self.assertTrue(any([i for i in images if i['id'] == self.image2_id])) self.assertTrue(any([i for i in images if i['id'] == self.image3_id])) self.assertFalse(any([i for i in images if i['id'] == self.image_ref])) @attr(type='gate') def test_list_images_limit_results(self): # Verify only the expected number of results are returned params = {'limit': '1'} resp, images = self.client.list_images(params) #when _interface='xml', one element for images_links in images #ref: Question #224349 self.assertEqual(1, len([x for x in images if 'id' in x])) @attr(type='gate') def test_list_images_filter_by_changes_since(self): # Verify only updated images are returned in the detailed list #Becoming ACTIVE will modify the updated time #Filter by the image's created time params = {'changes-since': self.image3['created']} resp, images = self.client.list_images(params) found = any([i for i in images if i['id'] == self.image3_id]) self.assertTrue(found) @attr(type='gate') def test_list_images_with_detail_filter_by_status(self): # Detailed list of all images should only contain images # with the provided status params = {'status': 'ACTIVE'} resp, images = self.client.list_images_with_detail(params) self.assertTrue(any([i for i in images if i['id'] == self.image1_id])) self.assertTrue(any([i for i in images if i['id'] == self.image2_id])) self.assertTrue(any([i for i in images if i['id'] == self.image3_id])) @attr(type='gate') def test_list_images_with_detail_filter_by_name(self): # Detailed list of all images should contain the expected # images filtered by name params = {'name': self.image1['name']} resp, images = self.client.list_images_with_detail(params) self.assertTrue(any([i for i in images if i['id'] == self.image1_id])) self.assertFalse(any([i for i in images if i['id'] == self.image2_id])) self.assertFalse(any([i for i in images if i['id'] == self.image3_id])) @attr(type='gate') def test_list_images_with_detail_limit_results(self): # Verify only the expected number of results (with full details) # are returned params = {'limit': '1'} resp, images = self.client.list_images_with_detail(params) self.assertEqual(1, len(images)) @attr(type='gate') def test_list_images_with_detail_filter_by_server_ref(self): # Detailed list of servers should be filtered by server ref server_links = self.server2['links'] # Try all server link types for link in server_links: params = {'server': link['href']} resp, images = self.client.list_images_with_detail(params) self.assertFalse(any([i for i in images if i['id'] == self.image1_id])) self.assertFalse(any([i for i in images if i['id'] == self.image2_id])) self.assertTrue(any([i for i in images if i['id'] == self.image3_id])) @attr(type='gate') def test_list_images_with_detail_filter_by_type(self): # The detailed list of servers should be filtered by image type params = {'type': 'snapshot'} resp, images = self.client.list_images_with_detail(params) resp, image4 = self.client.get_image(self.image_ref) self.assertTrue(any([i for i in images if i['id'] == self.image1_id])) self.assertTrue(any([i for i in images if i['id'] == self.image2_id])) self.assertTrue(any([i for i in images if i['id'] == self.image3_id])) self.assertFalse(any([i for i in images if i['id'] == self.image_ref])) @attr(type='gate') def test_list_images_with_detail_filter_by_changes_since(self): # Verify an update image is returned #Becoming ACTIVE will modify the updated time #Filter by the image's created time params = {'changes-since': self.image1['created']} resp, images = self.client.list_images_with_detail(params) self.assertTrue(any([i for i in images if i['id'] == self.image1_id])) @attr(type=['negative', 'gate']) def test_get_nonexistant_image(self): # Negative test: GET on non existant image should fail self.assertRaises(exceptions.NotFound, self.client.get_image, 999) class ListImageFiltersTestXML(ListImageFiltersTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/images/test_image_metadata.py0000664000175000017500000001457112161375672027174 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.compute import base from tempest.common.utils.data_utils import rand_name from tempest import exceptions from tempest.test import attr class ImagesMetadataTestJSON(base.BaseComputeTest): _interface = 'json' @classmethod def setUpClass(cls): super(ImagesMetadataTestJSON, cls).setUpClass() cls.servers_client = cls.servers_client cls.client = cls.images_client resp, server = cls.create_server(wait_until='ACTIVE') cls.server_id = server['id'] # Snapshot the server once to save time name = rand_name('image') resp, _ = cls.client.create_image(cls.server_id, name, {}) cls.image_id = resp['location'].rsplit('/', 1)[1] cls.client.wait_for_image_resp_code(cls.image_id, 200) cls.client.wait_for_image_status(cls.image_id, 'ACTIVE') @classmethod def tearDownClass(cls): cls.client.delete_image(cls.image_id) super(ImagesMetadataTestJSON, cls).tearDownClass() def setUp(self): super(ImagesMetadataTestJSON, self).setUp() meta = {'key1': 'value1', 'key2': 'value2'} resp, _ = self.client.set_image_metadata(self.image_id, meta) self.assertEqual(resp.status, 200) @attr(type='gate') def test_list_image_metadata(self): # All metadata key/value pairs for an image should be returned resp, resp_metadata = self.client.list_image_metadata(self.image_id) expected = {'key1': 'value1', 'key2': 'value2'} self.assertEqual(expected, resp_metadata) @attr(type='gate') def test_set_image_metadata(self): # The metadata for the image should match the new values req_metadata = {'meta2': 'value2', 'meta3': 'value3'} resp, body = self.client.set_image_metadata(self.image_id, req_metadata) resp, resp_metadata = self.client.list_image_metadata(self.image_id) self.assertEqual(req_metadata, resp_metadata) @attr(type='gate') def test_update_image_metadata(self): # The metadata for the image should match the updated values req_metadata = {'key1': 'alt1', 'key3': 'value3'} resp, metadata = self.client.update_image_metadata(self.image_id, req_metadata) resp, resp_metadata = self.client.list_image_metadata(self.image_id) expected = {'key1': 'alt1', 'key2': 'value2', 'key3': 'value3'} self.assertEqual(expected, resp_metadata) @attr(type='gate') def test_get_image_metadata_item(self): # The value for a specific metadata key should be returned resp, meta = self.client.get_image_metadata_item(self.image_id, 'key2') self.assertTrue('value2', meta['key2']) @attr(type='gate') def test_set_image_metadata_item(self): # The value provided for the given meta item should be set for # the image meta = {'key1': 'alt'} resp, body = self.client.set_image_metadata_item(self.image_id, 'key1', meta) resp, resp_metadata = self.client.list_image_metadata(self.image_id) expected = {'key1': 'alt', 'key2': 'value2'} self.assertEqual(expected, resp_metadata) @attr(type='gate') def test_delete_image_metadata_item(self): # The metadata value/key pair should be deleted from the image resp, body = self.client.delete_image_metadata_item(self.image_id, 'key1') resp, resp_metadata = self.client.list_image_metadata(self.image_id) expected = {'key2': 'value2'} self.assertEqual(expected, resp_metadata) @attr(type=['negative', 'gate']) def test_list_nonexistant_image_metadata(self): # Negative test: List on nonexistant image # metadata should not happen self.assertRaises(exceptions.NotFound, self.client.list_image_metadata, 999) @attr(type=['negative', 'gate']) def test_update_nonexistant_image_metadata(self): # Negative test:An update should not happen for a nonexistant image meta = {'key1': 'alt1', 'key2': 'alt2'} self.assertRaises(exceptions.NotFound, self.client.update_image_metadata, 999, meta) @attr(type=['negative', 'gate']) def test_get_nonexistant_image_metadata_item(self): # Negative test: Get on nonexistant image should not happen self.assertRaises(exceptions.NotFound, self.client.get_image_metadata_item, 999, 'key2') @attr(type=['negative', 'gate']) def test_set_nonexistant_image_metadata(self): # Negative test: Metadata should not be set to a nonexistant image meta = {'key1': 'alt1', 'key2': 'alt2'} self.assertRaises(exceptions.NotFound, self.client.set_image_metadata, 999, meta) @attr(type=['negative', 'gate']) def test_set_nonexistant_image_metadata_item(self): # Negative test: Metadata item should not be set to a # nonexistant image meta = {'key1': 'alt'} self.assertRaises(exceptions.NotFound, self.client.set_image_metadata_item, 999, 'key1', meta) @attr(type=['negative', 'gate']) def test_delete_nonexistant_image_metadata_item(self): # Negative test: Shouldnt be able to delete metadata # item from nonexistant image self.assertRaises(exceptions.NotFound, self.client.delete_image_metadata_item, 999, 'key1') class ImagesMetadataTestXML(ImagesMetadataTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/images/__init__.py0000664000175000017500000000000012161375672024730 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/images/test_images.py0000664000175000017500000001567412161375672025524 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api import compute from tempest.api.compute import base from tempest import clients from tempest.common.utils.data_utils import parse_image_id from tempest.common.utils.data_utils import rand_name from tempest import exceptions from tempest.test import attr class ImagesTestJSON(base.BaseComputeTest): _interface = 'json' @classmethod def setUpClass(cls): super(ImagesTestJSON, cls).setUpClass() cls.client = cls.images_client cls.servers_client = cls.servers_client cls.image_ids = [] if compute.MULTI_USER: if cls.config.compute.allow_tenant_isolation: creds = cls._get_isolated_creds() username, tenant_name, password = creds cls.alt_manager = clients.Manager(username=username, password=password, tenant_name=tenant_name) else: # Use the alt_XXX credentials in the config file cls.alt_manager = clients.AltManager() cls.alt_client = cls.alt_manager.images_client def tearDown(self): """Terminate test instances created after a test is executed.""" for image_id in self.image_ids: self.client.delete_image(image_id) self.image_ids.remove(image_id) super(ImagesTestJSON, self).tearDown() def __create_image__(self, server_id, name, meta=None): resp, body = self.client.create_image(server_id, name, meta) image_id = parse_image_id(resp['location']) self.client.wait_for_image_resp_code(image_id, 200) self.client.wait_for_image_status(image_id, 'ACTIVE') self.image_ids.append(image_id) return resp, body @attr(type=['negative', 'gate']) def test_create_image_from_deleted_server(self): # An image should not be created if the server instance is removed resp, server = self.create_server(wait_until='ACTIVE') # Delete server before trying to create server self.servers_client.delete_server(server['id']) self.servers_client.wait_for_server_termination(server['id']) # Create a new image after server is deleted name = rand_name('image') meta = {'image_type': 'test'} self.assertRaises(exceptions.NotFound, self.__create_image__, server['id'], name, meta) @attr(type=['negative', 'gate']) def test_create_image_from_invalid_server(self): # An image should not be created with invalid server id # Create a new image with invalid server id name = rand_name('image') meta = {'image_type': 'test'} resp = {} resp['status'] = None self.assertRaises(exceptions.NotFound, self.__create_image__, '!@#$%^&*()', name, meta) @attr(type=['negative', 'gate']) def test_create_image_when_server_is_terminating(self): # Return an error when creating image of server that is terminating resp, server = self.create_server(wait_until='ACTIVE') self.servers_client.delete_server(server['id']) snapshot_name = rand_name('test-snap-') self.assertRaises(exceptions.Duplicate, self.client.create_image, server['id'], snapshot_name) @attr(type=['negative', 'gate']) def test_create_image_when_server_is_rebooting(self): # Return error when creating an image of server that is rebooting resp, server = self.create_server(wait_until='ACTIVE') self.servers_client.reboot(server['id'], 'HARD') snapshot_name = rand_name('test-snap-') self.assertRaises(exceptions.Duplicate, self.client.create_image, server['id'], snapshot_name) @attr(type=['negative', 'gate']) def test_create_image_specify_uuid_35_characters_or_less(self): # Return an error if Image ID passed is 35 characters or less snapshot_name = rand_name('test-snap-') test_uuid = ('a' * 35) self.assertRaises(exceptions.NotFound, self.client.create_image, test_uuid, snapshot_name) @attr(type=['negative', 'gate']) def test_create_image_specify_uuid_37_characters_or_more(self): # Return an error if Image ID passed is 37 characters or more snapshot_name = rand_name('test-snap-') test_uuid = ('a' * 37) self.assertRaises(exceptions.NotFound, self.client.create_image, test_uuid, snapshot_name) @attr(type=['negative', 'gate']) def test_delete_image_with_invalid_image_id(self): # An image should not be deleted with invalid image id self.assertRaises(exceptions.NotFound, self.client.delete_image, '!@$%^&*()') @attr(type=['negative', 'gate']) def test_delete_non_existent_image(self): # Return an error while trying to delete a non-existent image non_existent_image_id = '11a22b9-12a9-5555-cc11-00ab112223fa' self.assertRaises(exceptions.NotFound, self.client.delete_image, non_existent_image_id) @attr(type=['negative', 'gate']) def test_delete_image_blank_id(self): # Return an error while trying to delete an image with blank Id self.assertRaises(exceptions.NotFound, self.client.delete_image, '') @attr(type=['negative', 'gate']) def test_delete_image_non_hex_string_id(self): # Return an error while trying to delete an image with non hex id image_id = '11a22b9-120q-5555-cc11-00ab112223gj' self.assertRaises(exceptions.NotFound, self.client.delete_image, image_id) @attr(type=['negative', 'gate']) def test_delete_image_negative_image_id(self): # Return an error while trying to delete an image with negative id self.assertRaises(exceptions.NotFound, self.client.delete_image, -1) @attr(type=['negative', 'gate']) def test_delete_image_id_is_over_35_character_limit(self): # Return an error while trying to delete image with id over limit self.assertRaises(exceptions.NotFound, self.client.delete_image, '11a22b9-120q-5555-cc11-00ab112223gj-3fac') class ImagesTestXML(ImagesTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/images/test_images_oneserver.py0000664000175000017500000001765312161375672027613 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 testtools from tempest.api import compute from tempest.api.compute import base from tempest import clients from tempest.common.utils.data_utils import parse_image_id from tempest.common.utils.data_utils import rand_name from tempest import exceptions from tempest.test import attr class ImagesOneServerTestJSON(base.BaseComputeTest): _interface = 'json' def tearDown(self): """Terminate test instances created after a test is executed.""" for image_id in self.image_ids: self.client.delete_image(image_id) self.image_ids.remove(image_id) super(ImagesOneServerTestJSON, self).tearDown() @classmethod def setUpClass(cls): super(ImagesOneServerTestJSON, cls).setUpClass() cls.client = cls.images_client cls.servers_client = cls.servers_client try: resp, cls.server = cls.create_server(wait_until='ACTIVE') except Exception: cls.tearDownClass() raise cls.image_ids = [] if compute.MULTI_USER: if cls.config.compute.allow_tenant_isolation: creds = cls._get_isolated_creds() username, tenant_name, password = creds cls.alt_manager = clients.Manager(username=username, password=password, tenant_name=tenant_name) else: # Use the alt_XXX credentials in the config file cls.alt_manager = clients.AltManager() cls.alt_client = cls.alt_manager.images_client @testtools.skip("Until Bug #1006725 is fixed") @attr(type=['negative', 'gate']) def test_create_image_specify_multibyte_character_image_name(self): # Return an error if the image name has multi-byte characters snapshot_name = rand_name('\xef\xbb\xbf') self.assertRaises(exceptions.BadRequest, self.client.create_image, self.server['id'], snapshot_name) @attr(type=['negative', 'gate']) def test_create_image_specify_invalid_metadata(self): # Return an error when creating image with invalid metadata snapshot_name = rand_name('test-snap-') meta = {'': ''} self.assertRaises(exceptions.BadRequest, self.client.create_image, self.server['id'], snapshot_name, meta) @attr(type=['negative', 'gate']) def test_create_image_specify_metadata_over_limits(self): # Return an error when creating image with meta data over 256 chars snapshot_name = rand_name('test-snap-') meta = {'a' * 260: 'b' * 260} self.assertRaises(exceptions.BadRequest, self.client.create_image, self.server['id'], snapshot_name, meta) @testtools.skipUnless(compute.MULTI_USER, 'Need multiple users for this test.') @attr(type=['negative', 'gate']) def test_delete_image_of_another_tenant(self): # Return an error while trying to delete another tenant's image self.servers_client.wait_for_server_status(self.server['id'], 'ACTIVE') snapshot_name = rand_name('test-snap-') resp, body = self.client.create_image(self.server['id'], snapshot_name) image_id = parse_image_id(resp['location']) self.image_ids.append(image_id) self.client.wait_for_image_resp_code(image_id, 200) self.client.wait_for_image_status(image_id, 'ACTIVE') # Delete image self.assertRaises(exceptions.NotFound, self.alt_client.delete_image, image_id) @testtools.skipUnless(compute.CREATE_IMAGE_ENABLED, 'Environment unable to create images.') @attr(type='smoke') def test_create_delete_image(self): # Create a new image name = rand_name('image') meta = {'image_type': 'test'} resp, body = self.client.create_image(self.server['id'], name, meta) self.assertEqual(202, resp.status) image_id = parse_image_id(resp['location']) self.client.wait_for_image_resp_code(image_id, 200) self.client.wait_for_image_status(image_id, 'ACTIVE') # Verify the image was created correctly resp, image = self.client.get_image(image_id) self.assertEqual(name, image['name']) self.assertEqual('test', image['metadata']['image_type']) # Verify minRAM and minDisk values are the same as the original image resp, original_image = self.client.get_image(self.image_ref) self.assertEqual(original_image['minRam'], image['minRam']) self.assertEqual(original_image['minDisk'], image['minDisk']) # Verify the image was deleted correctly resp, body = self.client.delete_image(image_id) self.assertEqual('204', resp['status']) self.client.wait_for_resource_deletion(image_id) @testtools.skipUnless(compute.MULTI_USER, 'Need multiple users for this test.') @attr(type=['negative', 'gate']) def test_create_image_for_server_in_another_tenant(self): # Creating image of another tenant's server should be return error snapshot_name = rand_name('test-snap-') self.assertRaises(exceptions.NotFound, self.alt_client.create_image, self.server['id'], snapshot_name) @attr(type=['negative', 'gate']) def test_create_second_image_when_first_image_is_being_saved(self): # Disallow creating another image when first image is being saved # Create first snapshot snapshot_name = rand_name('test-snap-') resp, body = self.client.create_image(self.server['id'], snapshot_name) self.assertEqual(202, resp.status) image_id = parse_image_id(resp['location']) self.image_ids.append(image_id) # Create second snapshot alt_snapshot_name = rand_name('test-snap-') self.assertRaises(exceptions.Duplicate, self.client.create_image, self.server['id'], alt_snapshot_name) self.client.wait_for_image_status(image_id, 'ACTIVE') @attr(type=['negative', 'gate']) def test_create_image_specify_name_over_256_chars(self): # Return an error if snapshot name over 256 characters is passed snapshot_name = rand_name('a' * 260) self.assertRaises(exceptions.BadRequest, self.client.create_image, self.server['id'], snapshot_name) @attr(type=['negative', 'gate']) def test_delete_image_that_is_not_yet_active(self): # Return an error while trying to delete an image what is creating snapshot_name = rand_name('test-snap-') resp, body = self.client.create_image(self.server['id'], snapshot_name) self.assertEqual(202, resp.status) image_id = parse_image_id(resp['location']) self.image_ids.append(image_id) # Do not wait, attempt to delete the image, ensure it's successful resp, body = self.client.delete_image(image_id) self.assertEqual('204', resp['status']) self.image_ids.remove(image_id) self.assertRaises(exceptions.NotFound, self.client.get_image, image_id) class ImagesOneServerTestXML(ImagesOneServerTestJSON): _interface = 'xml' tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/__init__.py0000664000175000017500000000425212161375672023500 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.common import log as logging from tempest import config from tempest.exceptions import InvalidConfiguration LOG = logging.getLogger(__name__) CONFIG = config.TempestConfig() CREATE_IMAGE_ENABLED = CONFIG.compute.create_image_enabled RESIZE_AVAILABLE = CONFIG.compute.resize_available CHANGE_PASSWORD_AVAILABLE = CONFIG.compute.change_password_available DISK_CONFIG_ENABLED = CONFIG.compute.disk_config_enabled FLAVOR_EXTRA_DATA_ENABLED = CONFIG.compute.flavor_extra_enabled MULTI_USER = True # All compute tests -- single setup function def generic_setup_package(): LOG.debug("Entering tempest.api.compute.setup_package") global MULTI_USER # Determine if there are two regular users that can be # used in testing. If the test cases are allowed to create # users (config.compute.allow_tenant_isolation is true, # then we allow multi-user. if not CONFIG.compute.allow_tenant_isolation: user1 = CONFIG.identity.username user2 = CONFIG.identity.alt_username if not user2 or user1 == user2: MULTI_USER = False else: user2_password = CONFIG.identity.alt_password user2_tenant_name = CONFIG.identity.alt_tenant_name if not user2_password or not user2_tenant_name: msg = ("Alternate user specified but not alternate " "tenant or password: alt_tenant_name=%s alt_password=%s" % (user2_tenant_name, user2_password)) raise InvalidConfiguration(msg) tempest-2013.2.a1291.g23a1b4f/tempest/api/compute/base.py0000664000175000017500000002337212161375672022657 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 time from tempest.api import compute from tempest import clients from tempest.common import log as logging from tempest.common.utils.data_utils import parse_image_id from tempest.common.utils.data_utils import rand_name from tempest import exceptions import tempest.test LOG = logging.getLogger(__name__) class BaseComputeTest(tempest.test.BaseTestCase): """Base test case class for all Compute API tests.""" conclusion = compute.generic_setup_package() @classmethod def setUpClass(cls): cls.isolated_creds = [] if cls.config.compute.allow_tenant_isolation: creds = cls._get_isolated_creds() username, tenant_name, password = creds os = clients.Manager(username=username, password=password, tenant_name=tenant_name, interface=cls._interface) else: os = clients.Manager(interface=cls._interface) cls.os = os cls.servers_client = os.servers_client cls.flavors_client = os.flavors_client cls.images_client = os.images_client cls.extensions_client = os.extensions_client cls.floating_ips_client = os.floating_ips_client cls.keypairs_client = os.keypairs_client cls.security_groups_client = os.security_groups_client cls.quotas_client = os.quotas_client cls.limits_client = os.limits_client cls.volumes_extensions_client = os.volumes_extensions_client cls.volumes_client = os.volumes_client cls.interfaces_client = os.interfaces_client cls.fixed_ips_client = os.fixed_ips_client cls.availability_zone_client = os.availability_zone_client cls.aggregates_client = os.aggregates_client cls.services_client = os.services_client cls.hypervisor_client = os.hypervisor_client cls.build_interval = cls.config.compute.build_interval cls.build_timeout = cls.config.compute.build_timeout cls.ssh_user = cls.config.compute.ssh_user cls.image_ref = cls.config.compute.image_ref cls.image_ref_alt = cls.config.compute.image_ref_alt cls.flavor_ref = cls.config.compute.flavor_ref cls.flavor_ref_alt = cls.config.compute.flavor_ref_alt cls.servers = [] cls.images = [] cls.servers_client_v3_auth = os.servers_client_v3_auth @classmethod def _get_identity_admin_client(cls): """ Returns an instance of the Identity Admin API client """ os = clients.AdminManager(interface=cls._interface) admin_client = os.identity_client return admin_client @classmethod def _get_client_args(cls): return ( cls.config, cls.config.identity.admin_username, cls.config.identity.admin_password, cls.config.identity.uri ) @classmethod def _get_isolated_creds(cls): """ Creates a new set of user/tenant/password credentials for a **regular** user of the Compute API so that a test case can operate in an isolated tenant container. """ admin_client = cls._get_identity_admin_client() password = "pass" while True: try: rand_name_root = rand_name(cls.__name__) if cls.isolated_creds: # Main user already created. Create the alt one... rand_name_root += '-alt' tenant_name = rand_name_root + "-tenant" tenant_desc = tenant_name + "-desc" resp, tenant = admin_client.create_tenant( name=tenant_name, description=tenant_desc) break except exceptions.Duplicate: if cls.config.compute.allow_tenant_reuse: tenant = admin_client.get_tenant_by_name(tenant_name) LOG.info('Re-using existing tenant %s', tenant) break while True: try: rand_name_root = rand_name(cls.__name__) if cls.isolated_creds: # Main user already created. Create the alt one... rand_name_root += '-alt' username = rand_name_root + "-user" email = rand_name_root + "@example.com" resp, user = admin_client.create_user(username, password, tenant['id'], email) break except exceptions.Duplicate: if cls.config.compute.allow_tenant_reuse: user = admin_client.get_user_by_username(tenant['id'], username) LOG.info('Re-using existing user %s', user) break # Store the complete creds (including UUID ids...) for later # but return just the username, tenant_name, password tuple # that the various clients will use. cls.isolated_creds.append((user, tenant)) return username, tenant_name, password @classmethod def clear_isolated_creds(cls): if not cls.isolated_creds: return admin_client = cls._get_identity_admin_client() for user, tenant in cls.isolated_creds: admin_client.delete_user(user['id']) admin_client.delete_tenant(tenant['id']) @classmethod def clear_servers(cls): for server in cls.servers: try: cls.servers_client.delete_server(server['id']) except Exception: pass for server in cls.servers: try: cls.servers_client.wait_for_server_termination(server['id']) except Exception: pass @classmethod def clear_images(cls): for image_id in cls.images: try: cls.images_client.delete_image(image_id) except Exception as exc: LOG.info('Exception raised deleting image %s', image_id) LOG.exception(exc) pass @classmethod def tearDownClass(cls): cls.clear_images() cls.clear_servers() cls.clear_isolated_creds() @classmethod def create_server(cls, **kwargs): """Wrapper utility that returns a test server.""" name = rand_name(cls.__name__ + "-instance") if 'name' in kwargs: name = kwargs.pop('name') flavor = kwargs.get('flavor', cls.flavor_ref) image_id = kwargs.get('image_id', cls.image_ref) resp, body = cls.servers_client.create_server( name, image_id, flavor, **kwargs) # handle the case of multiple servers servers = [body] if 'min_count' in kwargs or 'max_count' in kwargs: # Get servers created which name match with name param. r, b = cls.servers_client.list_servers() servers = [s for s in b['servers'] if s['name'].startswith(name)] cls.servers.extend(servers) if 'wait_until' in kwargs: for server in servers: cls.servers_client.wait_for_server_status( server['id'], kwargs['wait_until']) return resp, body @classmethod def create_image_from_server(cls, server_id, **kwargs): """Wrapper utility that returns a test server.""" name = rand_name(cls.__name__ + "-image") if 'name' in kwargs: name = kwargs.pop('name') resp, image = cls.images_client.create_image( server_id, name) image_id = parse_image_id(resp['location']) cls.images.append(image_id) if 'wait_until' in kwargs: cls.images_client.wait_for_image_status(image_id, kwargs['wait_until']) resp, image = cls.images_client.get_image(image_id) return resp, image def wait_for(self, condition): """Repeatedly calls condition() until a timeout.""" start_time = int(time.time()) while True: try: condition() except Exception: pass else: return if int(time.time()) - start_time >= self.build_timeout: condition() return time.sleep(self.build_interval) class BaseComputeAdminTest(BaseComputeTest): """Base test case class for all Compute Admin API tests.""" @classmethod def setUpClass(cls): super(BaseComputeAdminTest, cls).setUpClass() admin_username = cls.config.compute_admin.username admin_password = cls.config.compute_admin.password admin_tenant = cls.config.compute_admin.tenant_name if not (admin_username and admin_password and admin_tenant): msg = ("Missing Compute Admin API credentials " "in configuration.") raise cls.skipException(msg) cls.os_adm = clients.ComputeAdminManager(interface=cls._interface) tempest-2013.2.a1291.g23a1b4f/tempest/api/README.rst0000664000175000017500000000333512161375672021403 0ustar chuckchuck00000000000000Tempest Guide to API tests ========================== What are these tests? --------------------- One of Tempest's prime function is to ensure that your OpenStack cloud works with the OpenStack API as documented. The current largest portion of Tempest code is devoted to test cases that do exactly this. It's also important to test not only the expected possitive path on APIs, but also to provide them with invalid data to ensure they fail in expected and documented ways. Over the course of the OpenStack project Tempest has discovered many fundamental bugs by doing just this. In order for some APIs to return meaniful results, there must be enough data in the system. This means these tests might start by spinning up a server, image, etc, then opperating on it. Why are these tests in tempest? ------------------------------- This is one of the core missions for the Tempest project, and where it started. Many people use this bit of function in Tempest to ensure their clouds haven't broken the OpenStack API. It could be argued that some of the negative testing could be done back in the projects themselves, and we might evolve there over time, but currently in the OpenStack gate this is a fundamentally important place to keep things. Scope of these tests -------------------- API tests should always use the Tempest implementation of the OpenStack API, as we want to ensure that bugs aren't hidden by the official clients. They should test specific API calls, and can build up complex state if it's needed for the API call to be meaningful. They should send not only good data, but bad data at the API and look for error codes. They should all be able to be run on their own, not depending on the state created by a previous test. tempest-2013.2.a1291.g23a1b4f/tempest/api/__init__.py0000664000175000017500000000124212161375672022020 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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. tempest-2013.2.a1291.g23a1b4f/tempest/api/orchestration/0000775000175000017500000000000012161375700022564 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/orchestration/__init__.py0000664000175000017500000000000012161375672024673 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/orchestration/base.py0000664000175000017500000000665212161375672024071 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # 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 logging import time from tempest import clients from tempest.common.utils.data_utils import rand_name import tempest.test LOG = logging.getLogger(__name__) class BaseOrchestrationTest(tempest.test.BaseTestCase): """Base test case class for all Orchestration API tests.""" @classmethod def setUpClass(cls): os = clients.OrchestrationManager() cls.orchestration_cfg = os.config.orchestration if not cls.orchestration_cfg.heat_available: raise cls.skipException("Heat support is required") cls.os = os cls.orchestration_client = os.orchestration_client cls.keypairs_client = os.keypairs_client cls.stacks = [] @classmethod def _get_identity_admin_client(cls): """ Returns an instance of the Identity Admin API client """ os = clients.AdminManager(interface=cls._interface) admin_client = os.identity_client return admin_client @classmethod def _get_client_args(cls): return ( cls.config, cls.config.identity.admin_username, cls.config.identity.admin_password, cls.config.identity.uri ) def create_stack(self, stack_name, template_data, parameters={}): resp, body = self.client.create_stack( stack_name, template=template_data, parameters=parameters) self.assertEqual('201', resp['status']) stack_id = resp['location'].split('/')[-1] stack_identifier = '%s/%s' % (stack_name, stack_id) self.stacks.append(stack_identifier) return stack_identifier @classmethod def clear_stacks(cls): for stack_identifier in cls.stacks: try: cls.orchestration_client.delete_stack(stack_identifier) except Exception: pass for stack_identifier in cls.stacks: try: cls.orchestration_client.wait_for_stack_status( stack_identifier, 'DELETE_COMPLETE') except Exception: pass def _create_keypair(self, namestart='keypair-heat-'): kp_name = rand_name(namestart) resp, body = self.keypairs_client.create_keypair(kp_name) self.assertEqual(body['name'], kp_name) return body @classmethod def tearDownClass(cls): cls.clear_stacks() def wait_for(self, condition): """Repeatedly calls condition() until a timeout.""" start_time = int(time.time()) while True: try: condition() except Exception: pass else: return if int(time.time()) - start_time >= self.build_timeout: condition() return time.sleep(self.build_interval) tempest-2013.2.a1291.g23a1b4f/tempest/api/orchestration/stacks/0000775000175000017500000000000012161375700024054 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/orchestration/stacks/test_instance_cfn_init.py0000664000175000017500000001111512161375672031151 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # 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 json import logging from tempest.api.orchestration import base from tempest.common.utils.data_utils import rand_name from tempest.test import attr LOG = logging.getLogger(__name__) class InstanceCfnInitTestJSON(base.BaseOrchestrationTest): _interface = 'json' template = """ HeatTemplateFormatVersion: '2012-12-12' Description: | Template which uses a wait condition to confirm that a minimal cfn-init and cfn-signal has worked Parameters: KeyName: Type: String InstanceType: Type: String ImageId: Type: String Resources: CfnUser: Type: AWS::IAM::User SmokeKeys: Type: AWS::IAM::AccessKey Properties: UserName: {Ref: CfnUser} SmokeServer: Type: AWS::EC2::Instance Metadata: AWS::CloudFormation::Init: config: files: /tmp/smoke-status: content: smoke test complete /etc/cfn/cfn-credentials: content: Fn::Join: - '' - - AWSAccessKeyId= - {Ref: SmokeKeys} - ' ' - AWSSecretKey= - Fn::GetAtt: [SmokeKeys, SecretAccessKey] - ' ' mode: '000400' owner: root group: root Properties: ImageId: {Ref: ImageId} InstanceType: {Ref: InstanceType} KeyName: {Ref: KeyName} UserData: Fn::Base64: Fn::Join: - '' - - |- #!/bin/bash -v /opt/aws/bin/cfn-init - |- || error_exit ''Failed to run cfn-init'' /opt/aws/bin/cfn-signal -e 0 --data "`cat /tmp/smoke-status`" ' - {Ref: WaitHandle} - ''' ' WaitHandle: Type: AWS::CloudFormation::WaitConditionHandle WaitCondition: Type: AWS::CloudFormation::WaitCondition DependsOn: SmokeServer Properties: Handle: {Ref: WaitHandle} Timeout: '600' Outputs: WaitConditionStatus: Description: Contents of /tmp/smoke-status on SmokeServer Value: Fn::GetAtt: [WaitCondition, Data] """ @classmethod def setUpClass(cls): super(InstanceCfnInitTestJSON, cls).setUpClass() if not cls.orchestration_cfg.image_ref: raise cls.skipException("No image available to test") cls.client = cls.orchestration_client def setUp(self): super(InstanceCfnInitTestJSON, self).setUp() stack_name = rand_name('heat') keypair_name = (self.orchestration_cfg.keypair_name or self._create_keypair()['name']) # create the stack self.stack_identifier = self.create_stack( stack_name, self.template, parameters={ 'KeyName': keypair_name, 'InstanceType': self.orchestration_cfg.instance_type, 'ImageId': self.orchestration_cfg.image_ref }) @attr(type='gate') def test_stack_wait_condition_data(self): sid = self.stack_identifier # wait for create to complete. self.client.wait_for_stack_status(sid, 'CREATE_COMPLETE') # fetch the stack resp, body = self.client.get_stack(sid) self.assertEqual('CREATE_COMPLETE', body['stack_status']) # fetch the stack resp, body = self.client.get_stack(sid) self.assertEqual('CREATE_COMPLETE', body['stack_status']) # This is an assert of great significance, as it means the following # has happened: # - cfn-init read the provided metadata and wrote out a file # - a user was created and credentials written to the instance # - a cfn-signal was built which was signed with provided credentials # - the wait condition was fulfilled and the stack has changed state wait_status = json.loads(body['outputs'][0]['output_value']) self.assertEqual('smoke test complete', wait_status['00000']) tempest-2013.2.a1291.g23a1b4f/tempest/api/orchestration/stacks/__init__.py0000664000175000017500000000000012161375672026163 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/orchestration/stacks/test_stacks.py0000664000175000017500000000510512161375672026766 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # 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 logging from tempest.api.orchestration import base from tempest.common.utils.data_utils import rand_name from tempest.test import attr LOG = logging.getLogger(__name__) class StacksTestJSON(base.BaseOrchestrationTest): _interface = 'json' empty_template = "HeatTemplateFormatVersion: '2012-12-12'\n" @classmethod def setUpClass(cls): super(StacksTestJSON, cls).setUpClass() cls.client = cls.orchestration_client @attr(type='smoke') def test_stack_list_responds(self): resp, body = self.client.list_stacks() stacks = body['stacks'] self.assertEqual('200', resp['status']) self.assertIsInstance(stacks, list) @attr(type='smoke') def test_stack_crud_no_resources(self): stack_name = rand_name('heat') # count how many stacks to start with resp, body = self.client.list_stacks() # create the stack stack_identifier = self.create_stack( stack_name, self.empty_template) stack_id = stack_identifier.split('/')[1] # wait for create complete (with no resources it should be instant) self.client.wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE') # check for stack in list resp, body = self.client.list_stacks() list_ids = list([stack['id'] for stack in body['stacks']]) self.assertIn(stack_id, list_ids) # fetch the stack resp, body = self.client.get_stack(stack_identifier) self.assertEqual('CREATE_COMPLETE', body['stack_status']) # fetch the stack by name resp, body = self.client.get_stack(stack_name) self.assertEqual('CREATE_COMPLETE', body['stack_status']) # fetch the stack by id resp, body = self.client.get_stack(stack_id) self.assertEqual('CREATE_COMPLETE', body['stack_status']) # delete the stack resp = self.client.delete_stack(stack_identifier) self.assertEqual('204', resp[0]['status']) tempest-2013.2.a1291.g23a1b4f/tempest/api/network/0000775000175000017500000000000012161375700021371 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/network/test_networks.py0000664000175000017500000001070112161375672024665 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 netaddr from tempest.api.network import base from tempest.common.utils.data_utils import rand_name from tempest import exceptions from tempest.test import attr class NetworksTest(base.BaseNetworkTest): """ Tests the following operations in the Quantum API using the REST client for Quantum: create a network for a tenant list tenant's networks show a tenant network details create a subnet for a tenant list tenant's subnets show a tenant subnet details v2.0 of the Quantum API is assumed. It is also assumed that the following options are defined in the [network] section of etc/tempest.conf: tenant_network_cidr with a block of cidr's from which smaller blocks can be allocated for tenant networks tenant_network_mask_bits with the mask bits to be used to partition the block defined by tenant-network_cidr """ @classmethod def setUpClass(cls): super(NetworksTest, cls).setUpClass() cls.network = cls.create_network() cls.name = cls.network['name'] cls.subnet = cls.create_subnet(cls.network) cls.cidr = cls.subnet['cidr'] @attr(type='gate') def test_create_delete_network_subnet(self): # Creates a network name = rand_name('network-') resp, body = self.client.create_network(name) self.assertEqual('201', resp['status']) network = body['network'] self.assertTrue(network['id'] is not None) # Find a cidr that is not in use yet and create a subnet with it cidr = netaddr.IPNetwork(self.network_cfg.tenant_network_cidr) mask_bits = self.network_cfg.tenant_network_mask_bits for subnet_cidr in cidr.subnet(mask_bits): try: resp, body = self.client.create_subnet(network['id'], str(subnet_cidr)) break except exceptions.BadRequest as e: is_overlapping_cidr = 'overlaps with another subnet' in str(e) if not is_overlapping_cidr: raise self.assertEqual('201', resp['status']) subnet = body['subnet'] self.assertTrue(subnet['id'] is not None) #Deletes subnet and network resp, body = self.client.delete_subnet(subnet['id']) self.assertEqual('204', resp['status']) resp, body = self.client.delete_network(network['id']) self.assertEqual('204', resp['status']) @attr(type='gate') def test_show_network(self): # Verifies the details of a network resp, body = self.client.show_network(self.network['id']) self.assertEqual('200', resp['status']) network = body['network'] self.assertEqual(self.network['id'], network['id']) self.assertEqual(self.name, network['name']) @attr(type='gate') def test_list_networks(self): # Verify the network exists in the list of all networks resp, body = self.client.list_networks() networks = body['networks'] found = any(n for n in networks if n['id'] == self.network['id']) self.assertTrue(found) @attr(type='gate') def test_show_subnet(self): # Verifies the details of a subnet resp, body = self.client.show_subnet(self.subnet['id']) self.assertEqual('200', resp['status']) subnet = body['subnet'] self.assertEqual(self.subnet['id'], subnet['id']) self.assertEqual(self.cidr, subnet['cidr']) @attr(type='gate') def test_list_subnets(self): # Verify the subnet exists in the list of all subnets resp, body = self.client.list_subnets() subnets = body['subnets'] found = any(n for n in subnets if n['id'] == self.subnet['id']) self.assertTrue(found) tempest-2013.2.a1291.g23a1b4f/tempest/api/network/common.py0000664000175000017500000000530212161375672023243 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 Hewlett-Packard Development Company, L.P. # 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. class AttributeDict(dict): """ Provide attribute access (dict.key) to dictionary values. """ def __getattr__(self, name): """Allow attribute access for all keys in the dict.""" if name in self: return self[name] return super(AttributeDict, self).__getattribute__(name) class DeletableResource(AttributeDict): """ Support deletion of quantum resources (networks, subnets) via a delete() method, as is supported by keystone and nova resources. """ def __init__(self, *args, **kwargs): self.client = kwargs.pop('client', None) super(DeletableResource, self).__init__(*args, **kwargs) def __str__(self): return '<%s id="%s" name="%s">' % (self.__class__.__name__, self.id, self.name) def delete(self): raise NotImplemented() class DeletableNetwork(DeletableResource): def delete(self): self.client.delete_network(self.id) class DeletableSubnet(DeletableResource): _router_ids = set() def add_to_router(self, router_id): self._router_ids.add(router_id) body = dict(subnet_id=self.id) self.client.add_interface_router(router_id, body=body) def delete(self): for router_id in self._router_ids.copy(): body = dict(subnet_id=self.id) self.client.remove_interface_router(router_id, body=body) self._router_ids.remove(router_id) self.client.delete_subnet(self.id) class DeletableRouter(DeletableResource): def add_gateway(self, network_id): body = dict(network_id=network_id) self.client.add_gateway_router(self.id, body=body) def delete(self): self.client.remove_gateway_router(self.id) self.client.delete_router(self.id) class DeletableFloatingIp(DeletableResource): def delete(self): self.client.delete_floatingip(self.id) class DeletablePort(DeletableResource): def delete(self): self.client.delete_port(self.id) tempest-2013.2.a1291.g23a1b4f/tempest/api/network/__init__.py0000664000175000017500000000000012161375672023500 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/network/base.py0000664000175000017500000000662012161375672022671 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 netaddr from tempest import clients from tempest.common.utils.data_utils import rand_name from tempest import exceptions import tempest.test class BaseNetworkTest(tempest.test.BaseTestCase): """ Base class for the Quantum tests that use the Tempest Quantum REST client Per the Quantum API Guide, API v1.x was removed from the source code tree (docs.openstack.org/api/openstack-network/2.0/content/Overview-d1e71.html) Therefore, v2.x of the Quantum API is assumed. It is also assumed that the following options are defined in the [network] section of etc/tempest.conf: tenant_network_cidr with a block of cidr's from which smaller blocks can be allocated for tenant networks tenant_network_mask_bits with the mask bits to be used to partition the block defined by tenant-network_cidr """ @classmethod def setUpClass(cls): os = clients.Manager() cls.network_cfg = os.config.network if not cls.network_cfg.quantum_available: raise cls.skipException("Quantum support is required") cls.client = os.network_client cls.networks = [] cls.subnets = [] @classmethod def tearDownClass(cls): for subnet in cls.subnets: cls.client.delete_subnet(subnet['id']) for network in cls.networks: cls.client.delete_network(network['id']) @classmethod def create_network(cls, network_name=None): """Wrapper utility that returns a test network.""" network_name = network_name or rand_name('test-network-') resp, body = cls.client.create_network(network_name) network = body['network'] cls.networks.append(network) return network @classmethod def create_subnet(cls, network): """Wrapper utility that returns a test subnet.""" cidr = netaddr.IPNetwork(cls.network_cfg.tenant_network_cidr) mask_bits = cls.network_cfg.tenant_network_mask_bits # Find a cidr that is not in use yet and create a subnet with it body = None failure = None for subnet_cidr in cidr.subnet(mask_bits): try: resp, body = cls.client.create_subnet(network['id'], str(subnet_cidr)) break except exceptions.BadRequest as e: is_overlapping_cidr = 'overlaps with another subnet' in str(e) if not is_overlapping_cidr: raise # save the failure in case all of the CIDRs are overlapping failure = e if not body and failure: raise failure subnet = body['subnet'] cls.subnets.append(subnet) return subnet tempest-2013.2.a1291.g23a1b4f/tempest/api/image/0000775000175000017500000000000012161375700020762 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/image/v2/0000775000175000017500000000000012161375700021311 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/image/v2/__init__.py0000664000175000017500000000000012161375672023420 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/image/v2/test_images.py0000664000175000017500000000757512161375672024215 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 OpenStack, LLC # All Rights Reserved. # Copyright 2013 IBM Corp. # # 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 cStringIO as StringIO import random from tempest.api.image import base from tempest import exceptions from tempest.test import attr class CreateRegisterImagesTest(base.BaseV2ImageTest): """ Here we test the registration and creation of images """ @attr(type='gate') def test_register_with_invalid_container_format(self): # Negative tests for invalid data supplied to POST /images self.assertRaises(exceptions.BadRequest, self.client.create_image, 'test', 'wrong', 'vhd') @attr(type='gate') def test_register_with_invalid_disk_format(self): self.assertRaises(exceptions.BadRequest, self.client.create_image, 'test', 'bare', 'wrong') @attr(type='gate') def test_register_then_upload(self): # Register, then upload an image resp, body = self.create_image(name='New Name', container_format='bare', disk_format='raw', visibility='public') self.assertTrue('id' in body) image_id = body.get('id') self.assertTrue('name' in body) self.assertEqual('New Name', body.get('name')) self.assertTrue('visibility' in body) self.assertTrue(body.get('visibility') == 'public') self.assertTrue('status' in body) self.assertEqual('queued', body.get('status')) # Now try uploading an image file image_file = StringIO.StringIO(('*' * 1024)) resp, body = self.client.store_image(image_id, image_file) self.assertEqual(resp.status, 204) resp, body = self.client.get_image_metadata(image_id) self.assertTrue('size' in body) self.assertEqual(1024, body.get('size')) class ListImagesTest(base.BaseV2ImageTest): """ Here we test the listing of image information """ @classmethod def setUpClass(cls): super(ListImagesTest, cls).setUpClass() # We add a few images here to test the listing functionality of # the images API for x in xrange(0, 10): cls._create_standard_image(x) @classmethod def _create_standard_image(cls, number): """ Create a new standard image and return the ID of the newly-registered image. Note that the size of the new image is a random number between 1024 and 4096 """ image_file = StringIO.StringIO('*' * random.randint(1024, 4096)) name = 'New Standard Image %s' % number resp, body = cls.create_image(name=name, container_format='bare', disk_format='raw', visibility='public') image_id = body['id'] resp, body = cls.client.store_image(image_id, data=image_file) return image_id @attr(type='gate') def test_index_no_params(self): # Simple test to see all fixture images returned resp, images_list = self.client.image_list() self.assertEqual(resp['status'], '200') image_list = map(lambda x: x['id'], images_list) for image in self.created_images: self.assertTrue(image in image_list) tempest-2013.2.a1291.g23a1b4f/tempest/api/image/v1/0000775000175000017500000000000012161375700021310 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/image/v1/__init__.py0000664000175000017500000000000012161375672023417 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/image/v1/test_image_members.py0000664000175000017500000000625612161375672025536 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 IBM Corp. # # 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 cStringIO as StringIO from tempest.api.image import base from tempest import clients from tempest.test import attr class ImageMembersTests(base.BaseV1ImageTest): @classmethod def setUpClass(cls): super(ImageMembersTests, cls).setUpClass() admin = clients.AdminManager(interface='json') cls.admin_client = admin.identity_client cls.tenants = cls._get_tenants() @classmethod def _get_tenants(cls): resp, tenants = cls.admin_client.list_tenants() tenants = map(lambda x: x['id'], tenants) return tenants def _create_image(self): image_file = StringIO.StringIO('*' * 1024) resp, image = self.create_image(container_format='bare', disk_format='raw', is_public=True, data=image_file) self.assertEquals(201, resp.status) image_id = image['id'] return image_id @attr(type='gate') def test_add_image_member(self): image = self._create_image() resp = self.client.add_member(self.tenants[0], image) self.assertEquals(204, resp.status) resp, body = self.client.get_image_membership(image) self.assertEquals(200, resp.status) members = body['members'] members = map(lambda x: x['member_id'], members) self.assertIn(self.tenants[0], members) @attr(type='gate') def test_get_shared_images(self): image = self._create_image() resp = self.client.add_member(self.tenants[0], image) self.assertEquals(204, resp.status) share_image = self._create_image() resp = self.client.add_member(self.tenants[0], share_image) self.assertEquals(204, resp.status) resp, body = self.client.get_shared_images(self.tenants[0]) self.assertEquals(200, resp.status) images = body['shared_images'] images = map(lambda x: x['image_id'], images) self.assertIn(share_image, images) self.assertIn(image, images) @attr(type='gate') def test_remove_member(self): image_id = self._create_image() resp = self.client.add_member(self.tenants[0], image_id) self.assertEquals(204, resp.status) resp = self.client.delete_member(self.tenants[0], image_id) self.assertEquals(204, resp.status) resp, body = self.client.get_image_membership(image_id) self.assertEquals(200, resp.status) members = body['members'] self.assertEquals(0, len(members)) tempest-2013.2.a1291.g23a1b4f/tempest/api/image/v1/test_images.py0000664000175000017500000002534012161375672024202 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 cStringIO as StringIO from tempest.api.image import base from tempest import exceptions from tempest.test import attr class CreateRegisterImagesTest(base.BaseV1ImageTest): """Here we test the registration and creation of images.""" @attr(type='gate') def test_register_with_invalid_container_format(self): # Negative tests for invalid data supplied to POST /images self.assertRaises(exceptions.BadRequest, self.client.create_image, 'test', 'wrong', 'vhd') @attr(type='gate') def test_register_with_invalid_disk_format(self): self.assertRaises(exceptions.BadRequest, self.client.create_image, 'test', 'bare', 'wrong') @attr(type='gate') def test_register_then_upload(self): # Register, then upload an image properties = {'prop1': 'val1'} resp, body = self.create_image(name='New Name', container_format='bare', disk_format='raw', is_public=True, properties=properties) self.assertTrue('id' in body) image_id = body.get('id') self.assertEqual('New Name', body.get('name')) self.assertTrue(body.get('is_public')) self.assertEqual('queued', body.get('status')) for key, val in properties.items(): self.assertEqual(val, body.get('properties')[key]) # Now try uploading an image file image_file = StringIO.StringIO(('*' * 1024)) resp, body = self.client.update_image(image_id, data=image_file) self.assertTrue('size' in body) self.assertEqual(1024, body.get('size')) @attr(type='gate') def test_register_remote_image(self): # Register a new remote image resp, body = self.create_image(name='New Remote Image', container_format='bare', disk_format='raw', is_public=True, location='http://example.com' '/someimage.iso', properties={'key1': 'value1', 'key2': 'value2'}) self.assertTrue('id' in body) self.assertEqual('New Remote Image', body.get('name')) self.assertTrue(body.get('is_public')) self.assertEqual('active', body.get('status')) properties = body.get('properties') self.assertEqual(properties['key1'], 'value1') self.assertEqual(properties['key2'], 'value2') @attr(type='gate') def test_register_http_image(self): resp, body = self.create_image(name='New Http Image', container_format='bare', disk_format='raw', is_public=True, copy_from=self.config.images.http_image) self.assertTrue('id' in body) image_id = body.get('id') self.assertEqual('New Http Image', body.get('name')) self.assertTrue(body.get('is_public')) self.client.wait_for_image_status(image_id, 'active') resp, body = self.client.get_image(image_id) self.assertEqual(resp['status'], '200') @attr(type='gate') def test_register_image_with_min_ram(self): # Register an image with min ram properties = {'prop1': 'val1'} resp, body = self.create_image(name='New_image_with_min_ram', container_format='bare', disk_format='raw', is_public=True, min_ram=40, properties=properties) self.assertTrue('id' in body) self.assertEqual('New_image_with_min_ram', body.get('name')) self.assertTrue(body.get('is_public')) self.assertEqual('queued', body.get('status')) self.assertEqual(40, body.get('min_ram')) for key, val in properties.items(): self.assertEqual(val, body.get('properties')[key]) class ListImagesTest(base.BaseV1ImageTest): """ Here we test the listing of image information """ @classmethod def setUpClass(cls): super(ListImagesTest, cls).setUpClass() # We add a few images here to test the listing functionality of # the images API img1 = cls._create_remote_image('one', 'bare', 'raw') img2 = cls._create_remote_image('two', 'ami', 'ami') img3 = cls._create_remote_image('dup', 'bare', 'raw') img4 = cls._create_remote_image('dup', 'bare', 'raw') img5 = cls._create_standard_image('1', 'ami', 'ami', 42) img6 = cls._create_standard_image('2', 'ami', 'ami', 142) img7 = cls._create_standard_image('33', 'bare', 'raw', 142) img8 = cls._create_standard_image('33', 'bare', 'raw', 142) cls.created_set = set(cls.created_images) # 4x-4x remote image cls.remote_set = set((img1, img2, img3, img4)) cls.standard_set = set((img5, img6, img7, img8)) # 5x bare, 3x ami cls.bare_set = set((img1, img3, img4, img7, img8)) cls.ami_set = set((img2, img5, img6)) # 1x with size 42 cls.size42_set = set((img5,)) # 3x with size 142 cls.size142_set = set((img6, img7, img8)) # dup named cls.dup_set = set((img3, img4)) @classmethod def _create_remote_image(cls, name, container_format, disk_format): """ Create a new remote image and return the ID of the newly-registered image """ name = 'New Remote Image %s' % name location = 'http://example.com/someimage_%s.iso' % name resp, image = cls.create_image(name=name, container_format=container_format, disk_format=disk_format, is_public=True, location=location) image_id = image['id'] return image_id @classmethod def _create_standard_image(cls, name, container_format, disk_format, size): """ Create a new standard image and return the ID of the newly-registered image. Note that the size of the new image is a random number between 1024 and 4096 """ image_file = StringIO.StringIO('*' * size) name = 'New Standard Image %s' % name resp, image = cls.create_image(name=name, container_format=container_format, disk_format=disk_format, is_public=True, data=image_file) image_id = image['id'] return image_id @attr(type='gate') def test_index_no_params(self): # Simple test to see all fixture images returned resp, images_list = self.client.image_list() self.assertEqual(resp['status'], '200') image_list = map(lambda x: x['id'], images_list) for image_id in self.created_images: self.assertTrue(image_id in image_list) @attr(type='gate') def test_index_disk_format(self): resp, images_list = self.client.image_list(disk_format='ami') self.assertEqual(resp['status'], '200') for image in images_list: self.assertEqual(image['disk_format'], 'ami') result_set = set(map(lambda x: x['id'], images_list)) self.assertTrue(self.ami_set <= result_set) self.assertFalse(self.created_set - self.ami_set <= result_set) @attr(type='gate') def test_index_container_format(self): resp, images_list = self.client.image_list(container_format='bare') self.assertEqual(resp['status'], '200') for image in images_list: self.assertEqual(image['container_format'], 'bare') result_set = set(map(lambda x: x['id'], images_list)) self.assertTrue(self.bare_set <= result_set) self.assertFalse(self.created_set - self.bare_set <= result_set) @attr(type='gate') def test_index_max_size(self): resp, images_list = self.client.image_list(size_max=42) self.assertEqual(resp['status'], '200') for image in images_list: self.assertTrue(image['size'] <= 42) result_set = set(map(lambda x: x['id'], images_list)) self.assertTrue(self.size42_set <= result_set) self.assertFalse(self.created_set - self.size42_set <= result_set) @attr(type='gate') def test_index_min_size(self): resp, images_list = self.client.image_list(size_min=142) self.assertEqual(resp['status'], '200') for image in images_list: self.assertTrue(image['size'] >= 142) result_set = set(map(lambda x: x['id'], images_list)) self.assertTrue(self.size142_set <= result_set) self.assertFalse(self.size42_set <= result_set) @attr(type='gate') def test_index_status_active_detail(self): resp, images_list = self.client.image_list_detail(status='active', sort_key='size', sort_dir='desc') self.assertEqual(resp['status'], '200') top_size = images_list[0]['size'] # We have non-zero sized images for image in images_list: size = image['size'] self.assertTrue(size <= top_size) top_size = size self.assertEqual(image['status'], 'active') @attr(type='gate') def test_index_name(self): resp, images_list = self.client.image_list_detail( name='New Remote Image dup') self.assertEqual(resp['status'], '200') result_set = set(map(lambda x: x['id'], images_list)) for image in images_list: self.assertEqual(image['name'], 'New Remote Image dup') self.assertTrue(self.dup_set <= result_set) self.assertFalse(self.created_set - self.dup_set <= result_set) tempest-2013.2.a1291.g23a1b4f/tempest/api/image/__init__.py0000664000175000017500000000000012161375672023071 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/api/image/base.py0000664000175000017500000000550612161375672022264 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 IBM Corp. # # 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 tempest import clients from tempest.common import log as logging from tempest.common.utils.data_utils import rand_name from tempest import exceptions import tempest.test LOG = logging.getLogger(__name__) class BaseImageTest(tempest.test.BaseTestCase): """Base test class for Image API tests.""" @classmethod def setUpClass(cls): cls.os = clients.Manager() cls.created_images = [] @classmethod def tearDownClass(cls): for image_id in cls.created_images: try: cls.client.delete_image(image_id) except exceptions.NotFound: pass for image_id in cls.created_images: cls.client.wait_for_resource_deletion(image_id) @classmethod def create_image(cls, **kwargs): """Wrapper that returns a test image.""" name = rand_name(cls.__name__ + "-instance") if 'name' in kwargs: name = kwargs.pop('name') container_format = kwargs.pop('container_format') disk_format = kwargs.pop('disk_format') resp, image = cls.client.create_image(name, container_format, disk_format, **kwargs) cls.created_images.append(image['id']) return resp, image @classmethod def _check_version(cls, version): __, versions = cls.client.get_versions() if version == 'v2.0': if 'v2.0' in versions: return True elif version == 'v1.0': if 'v1.1' in versions or 'v1.0' in versions: return True return False class BaseV1ImageTest(BaseImageTest): @classmethod def setUpClass(cls): super(BaseV1ImageTest, cls).setUpClass() cls.client = cls.os.image_client if not cls._check_version('v1.0'): msg = "Glance API v1 not supported" raise cls.skipException(msg) class BaseV2ImageTest(BaseImageTest): @classmethod def setUpClass(cls): super(BaseV2ImageTest, cls).setUpClass() cls.client = cls.os.image_client_v2 if not cls._check_version('v2.0'): msg = "Glance API v2 not supported" raise cls.skipException(msg) tempest-2013.2.a1291.g23a1b4f/tempest/services/0000775000175000017500000000000012161375700020752 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/object_storage/0000775000175000017500000000000012161375700023744 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/object_storage/container_client.py0000664000175000017500000001405012161375672027646 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 json import urllib from tempest.common.rest_client import RestClient class ContainerClient(RestClient): def __init__(self, config, username, password, auth_url, tenant_name=None): super(ContainerClient, self).__init__(config, username, password, auth_url, tenant_name) #Overwrites json-specific header encoding in RestClient self.headers = {} self.service = self.config.object_storage.catalog_type self.format = 'json' def create_container(self, container_name, metadata=None, metadata_prefix='X-Container-Meta-'): """ Creates a container, with optional metadata passed in as a dictonary """ url = str(container_name) headers = {} if metadata is not None: for key in metadata: headers[metadata_prefix + key] = metadata[key] resp, body = self.put(url, body=None, headers=headers) return resp, body def delete_container(self, container_name): """Deletes the container (if it's empty).""" url = str(container_name) resp, body = self.delete(url) return resp, body def update_container_metadata(self, container_name, metadata, metadata_prefix='X-Container-Meta-'): """Updates arbitrary metadata on container.""" url = str(container_name) headers = {} if metadata is not None: for key in metadata: headers[metadata_prefix + key] = metadata[key] resp, body = self.post(url, body=None, headers=headers) return resp, body def delete_container_metadata(self, container_name, metadata, metadata_prefix='X-Remove-Container-Meta-'): """Deletes arbitrary metadata on container.""" url = str(container_name) headers = {} if metadata is not None: for item in metadata: headers[metadata_prefix + item] = 'x' resp, body = self.post(url, body=None, headers=headers) return resp, body def list_container_metadata(self, container_name): """ Retrieves container metadata headers """ url = str(container_name) headers = {"X-Storage-Token": self.token} resp, body = self.head(url, headers=headers) return resp, body def list_all_container_objects(self, container, params=None): """ Returns complete list of all objects in the container, even if item count is beyond 10,000 item listing limit. Does not require any paramaters aside from container name. """ #TODO(dwalleck): Rewite using json format to avoid newlines at end of #obj names. Set limit to API limit - 1 (max returned items = 9999) limit = 9999 if params is not None: if 'limit' in params: limit = params['limit'] if 'marker' in params: limit = params['marker'] resp, objlist = self.list_container_contents(container, params={'limit': limit}) return objlist """tmp = [] for obj in objlist: tmp.append(obj['name']) objlist = tmp if len(objlist) >= limit: #Increment marker marker = objlist[len(objlist) - 1] #Get the next chunk of the list objlist.extend(_list_all_container_objects(container, params={'marker': marker, 'limit': limit})) return objlist else: #Return final, complete list return objlist""" def list_container_contents(self, container, params=None): """ List the objects in a container, given the container name Returns the container object listing as a plain text list, or as xml or json if that option is specified via the 'format' argument. Optional Arguments: limit = integer For an integer value n, limits the number of results to at most n values. marker = 'string' Given a string value x, return object names greater in value than the specified marker. prefix = 'string' For a string value x, causes the results to be limited to names beginning with the substring x. format = 'json' or 'xml' Specify either json or xml to return the respective serialized response. If json, returns a list of json objects if xml, returns a string of xml path = 'string' For a string value x, return the object names nested in the pseudo path (assuming preconditions are met - see below). delimiter = 'character' For a character c, return all the object names nested in the container (without the need for the directory marker objects). """ url = str(container) url += '?format=%s' % self.format if params: url += '&%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body tempest-2013.2.a1291.g23a1b4f/tempest/services/object_storage/account_client.py0000664000175000017500000001254112161375672027323 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 httplib2 import json import urllib from tempest.common.rest_client import RestClient from tempest import exceptions class AccountClient(RestClient): def __init__(self, config, username, password, auth_url, tenant_name=None): super(AccountClient, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.object_storage.catalog_type self.format = 'json' def list_account_metadata(self): """ HEAD on the storage URL Returns all account metadata headers """ headers = {"X-Storage-Token": self.token} resp, body = self.head('', headers=headers) return resp, body def create_account_metadata(self, metadata, metadata_prefix='X-Account-Meta-'): """Creates an account metadata entry.""" headers = {} for key in metadata: headers[metadata_prefix + key] = metadata[key] resp, body = self.post('', headers=headers, body=None) return resp, body def delete_account_metadata(self, metadata, metadata_prefix='X-Remove-Account-Meta-'): """ Deletes an account metadata entry. """ headers = {"X-Storage-Token": self.token} for item in metadata: headers[metadata_prefix + item] = 'x' resp, body = self.post('', headers=headers, body=None) return resp, body def list_account_containers(self, params=None): """ GET on the (base) storage URL Given the X-Storage-URL and a valid X-Auth-Token, returns a list of all containers for the account. Optional Arguments: limit=[integer value N] Limits the number of results to at most N values DEFAULT: 10,000 marker=[string value X] Given string value X, return object names greater in value than the specified marker. DEFAULT: No Marker format=[string value, either 'json' or 'xml'] Specify either json or xml to return the respective serialized response. DEFAULT: Python-List returned in response body """ url = '?format=%s' % self.format if params: url += '&%s' + urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body class AccountClientCustomizedHeader(RestClient): def __init__(self, config, username, password, auth_url, tenant_name=None): super(AccountClientCustomizedHeader, self).__init__(config, username, password, auth_url, tenant_name) #Overwrites json-specific header encoding in RestClient self.service = self.config.object_storage.catalog_type self.format = 'json' def request(self, method, url, headers=None, body=None): """A simple HTTP request interface.""" self.http_obj = httplib2.Http() if headers is None: headers = {} if self.base_url is None: self._set_auth() req_url = "%s/%s" % (self.base_url, url) self._log_request(method, req_url, headers, body) resp, resp_body = self.http_obj.request(req_url, method, headers=headers, body=body) self._log_response(resp, resp_body) if resp.status == 401 or resp.status == 403: raise exceptions.Unauthorized() return resp, resp_body def list_account_containers(self, params=None, metadata=None): """ GET on the (base) storage URL Given the X-Storage-URL and a valid X-Auth-Token, returns a list of all containers for the account. Optional Arguments: limit=[integer value N] Limits the number of results to at most N values DEFAULT: 10,000 marker=[string value X] Given string value X, return object names greater in value than the specified marker. DEFAULT: No Marker format=[string value, either 'json' or 'xml'] Specify either json or xml to return the respective serialized response. DEFAULT: Python-List returned in response body """ url = '?format=%s' % self.format if params: url += '&%s' + urllib.urlencode(params) headers = {} if metadata: for key in metadata: headers[str(key)] = metadata[key] resp, body = self.get(url, headers=headers) return resp, body tempest-2013.2.a1291.g23a1b4f/tempest/services/object_storage/__init__.py0000664000175000017500000000000012161375672026053 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/object_storage/object_client.py0000664000175000017500000002005312161375672027132 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 hashlib import hmac import httplib2 import urlparse from tempest.common.rest_client import RestClient from tempest import exceptions class ObjectClient(RestClient): def __init__(self, config, username, password, auth_url, tenant_name=None): super(ObjectClient, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.object_storage.catalog_type def create_object(self, container, object_name, data): """Create storage object.""" headers = dict(self.headers) if not data: headers['content-length'] = '0' url = "%s/%s" % (str(container), str(object_name)) resp, body = self.put(url, data, headers) return resp, body def update_object(self, container, object_name, data): """Upload data to replace current storage object.""" return self.create_object(container, object_name, data) def delete_object(self, container, object_name): """Delete storage object.""" url = "%s/%s" % (str(container), str(object_name)) resp, body = self.delete(url) return resp, body def update_object_metadata(self, container, object_name, metadata, metadata_prefix='X-Object-Meta-'): """Add, remove, or change X-Object-Meta metadata for storage object.""" headers = {} for key in metadata: headers["%s%s" % (str(metadata_prefix), str(key))] = metadata[key] url = "%s/%s" % (str(container), str(object_name)) resp, body = self.post(url, None, headers=headers) return resp, body def list_object_metadata(self, container, object_name): """List all storage object X-Object-Meta- metadata.""" url = "%s/%s" % (str(container), str(object_name)) resp, body = self.head(url) return resp, body def get_object(self, container, object_name): """Retrieve object's data.""" url = "{0}/{1}".format(container, object_name) resp, body = self.get(url) return resp, body def copy_object_in_same_container(self, container, src_object_name, dest_object_name, metadata=None): """Copy storage object's data to the new object using PUT.""" url = "{0}/{1}".format(container, dest_object_name) headers = {} headers['X-Copy-From'] = "%s/%s" % (str(container), str(src_object_name)) headers['content-length'] = '0' if metadata: for key in metadata: headers[str(key)] = metadata[key] resp, body = self.put(url, None, headers=headers) return resp, body def copy_object_across_containers(self, src_container, src_object_name, dst_container, dst_object_name, metadata=None): """Copy storage object's data to the new object using PUT.""" url = "{0}/{1}".format(dst_container, dst_object_name) headers = {} headers['X-Copy-From'] = "%s/%s" % (str(src_container), str(src_object_name)) headers['content-length'] = '0' if metadata: for key in metadata: headers[str(key)] = metadata[key] resp, body = self.put(url, None, headers=headers) return resp, body def copy_object_2d_way(self, container, src_object_name, dest_object_name, metadata=None): """Copy storage object's data to the new object using COPY.""" url = "{0}/{1}".format(container, src_object_name) headers = {} headers['Destination'] = "%s/%s" % (str(container), str(dest_object_name)) if metadata: for key in metadata: headers[str(key)] = metadata[key] resp, body = self.copy(url, headers=headers) return resp, body def get_object_using_temp_url(self, container, object_name, expires, key): """Retrieve object's data using temp URL.""" self._set_auth() method = 'GET' path = "%s/%s/%s" % (urlparse.urlparse(self.base_url).path, container, object_name) hmac_body = '%s\n%s\n%s' % (method, expires, path) sig = hmac.new(key, hmac_body, hashlib.sha1).hexdigest() url = "%s/%s?temp_url_sig=%s&temp_url_expires=%s" % (container, object_name, sig, expires) resp, body = self.get(url) return resp, body def create_object_segments(self, container, object_name, segment, data): """Creates object segments.""" url = "{0}/{1}/{2}".format(container, object_name, segment) resp, body = self.put(url, data, self.headers) return resp, body class ObjectClientCustomizedHeader(RestClient): def __init__(self, config, username, password, auth_url, tenant_name=None): super(ObjectClientCustomizedHeader, self).__init__(config, username, password, auth_url, tenant_name) #Overwrites json-specific header encoding in RestClient self.service = self.config.object_storage.catalog_type self.format = 'json' def request(self, method, url, headers=None, body=None): """A simple HTTP request interface.""" dscv = self.config.identity.disable_ssl_certificate_validation self.http_obj = httplib2.Http(disable_ssl_certificate_validation=dscv) if headers is None: headers = {} if self.base_url is None: self._set_auth() req_url = "%s/%s" % (self.base_url, url) self._log_request(method, req_url, headers, body) resp, resp_body = self.http_obj.request(req_url, method, headers=headers, body=body) self._log_response(resp, resp_body) if resp.status == 401 or resp.status == 403: raise exceptions.Unauthorized() return resp, resp_body def get_object(self, container, object_name, metadata=None): """Retrieve object's data.""" headers = {} if metadata: for key in metadata: headers[str(key)] = metadata[key] url = "{0}/{1}".format(container, object_name) resp, body = self.get(url, headers=headers) return resp, body def create_object(self, container, object_name, data, metadata=None): """Create storage object.""" headers = {} if metadata: for key in metadata: headers[str(key)] = metadata[key] if not data: headers['content-length'] = '0' url = "%s/%s" % (str(container), str(object_name)) resp, body = self.put(url, data, headers=headers) return resp, body def delete_object(self, container, object_name, metadata=None): """Delete storage object.""" headers = {} if metadata: for key in metadata: headers[str(key)] = metadata[key] url = "%s/%s" % (str(container), str(object_name)) resp, body = self.delete(url, headers=headers) return resp, body tempest-2013.2.a1291.g23a1b4f/tempest/services/identity/0000775000175000017500000000000012161375700022603 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/identity/v3/0000775000175000017500000000000012161375700023133 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/identity/v3/xml/0000775000175000017500000000000012161375700023733 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/identity/v3/xml/service_client.py0000664000175000017500000000621412161375672027316 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # # Copyright 2013 OpenStack Foundation # 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 urlparse import urlparse from lxml import etree from tempest.common.rest_client import RestClientXML from tempest.services.compute.xml.common import Document from tempest.services.compute.xml.common import Element from tempest.services.compute.xml.common import xml_to_json XMLNS = "http://docs.openstack.org/identity/api/v3" class ServiceClientXML(RestClientXML): def __init__(self, config, username, password, auth_url, tenant_name=None): super(ServiceClientXML, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.identity.catalog_type self.endpoint_url = 'adminURL' def _parse_array(self, node): array = [] for child in node.getchildren(): array.append(xml_to_json(child)) return array def _parse_body(self, body): data = xml_to_json(body) return data def request(self, method, url, headers=None, body=None, wait=None): """Overriding the existing HTTP request in super class rest_client.""" self._set_auth() self.base_url = self.base_url.replace(urlparse(self.base_url).path, "/v3") return super(ServiceClientXML, self).request(method, url, headers=headers, body=body) def update_service(self, service_id, **kwargs): """Updates a service_id.""" resp, body = self.get_service(service_id) name = kwargs.get('name', body['name']) description = kwargs.get('description', body['description']) type = kwargs.get('type', body['type']) update_service = Element("service", xmlns=XMLNS, id=service_id, name=name, description=description, type=type) resp, body = self.patch('services/%s' % service_id, str(Document(update_service)), self.headers) body = self._parse_body(etree.fromstring(body)) return resp, body def get_service(self, service_id): """Get Service.""" url = 'services/%s' % service_id resp, body = self.get(url, self.headers) body = self._parse_body(etree.fromstring(body)) return resp, body tempest-2013.2.a1291.g23a1b4f/tempest/services/identity/v3/xml/endpoints_client.py0000664000175000017500000001034412161375672027660 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # # Copyright 2013 OpenStack Foundation # 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 urlparse import httplib2 from lxml import etree from tempest.common.rest_client import RestClientXML from tempest.services.compute.xml.common import Document from tempest.services.compute.xml.common import Element from tempest.services.compute.xml.common import xml_to_json XMLNS = "http://docs.openstack.org/identity/api/v3" class EndPointClientXML(RestClientXML): def __init__(self, config, username, password, auth_url, tenant_name=None): super(EndPointClientXML, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.identity.catalog_type self.endpoint_url = 'adminURL' def _parse_array(self, node): array = [] for child in node.getchildren(): tag_list = child.tag.split('}', 1) if tag_list[1] == "endpoint": array.append(xml_to_json(child)) return array def _parse_body(self, body): json = xml_to_json(body) return json def request(self, method, url, headers=None, body=None, wait=None): """Overriding the existing HTTP request in super class RestClient.""" dscv = self.config.identity.disable_ssl_certificate_validation self.http_obj = httplib2.Http(disable_ssl_certificate_validation=dscv) self._set_auth() self.base_url = self.base_url.replace( urlparse.urlparse(self.base_url).path, "/v3") return super(EndPointClientXML, self).request(method, url, headers=headers, body=body) def list_endpoints(self): """Get the list of endpoints.""" resp, body = self.get("endpoints", self.headers) body = self._parse_array(etree.fromstring(body)) return resp, body def create_endpoint(self, service_id, interface, url, **kwargs): """Create endpoint.""" region = kwargs.get('region', None) enabled = kwargs.get('enabled', None) create_endpoint = Element("endpoint", xmlns=XMLNS, service_id=service_id, interface=interface, url=url, region=region, enabled=enabled) resp, body = self.post('endpoints', str(Document(create_endpoint)), self.headers) body = self._parse_body(etree.fromstring(body)) return resp, body def update_endpoint(self, endpoint_id, service_id=None, interface=None, url=None, region=None, enabled=None): """Updates an endpoint with given parameters.""" doc = Document() endpoint = Element("endpoint") doc.append(endpoint) if service_id: endpoint.add_attr("service_id", service_id) if interface: endpoint.add_attr("interface", interface) if url: endpoint.add_attr("url", url) if region: endpoint.add_attr("region", region) if enabled is not None: endpoint.add_attr("enabled", enabled) resp, body = self.patch('endpoints/%s' % str(endpoint_id), str(doc), self.headers) body = self._parse_body(etree.fromstring(body)) return resp, body def delete_endpoint(self, endpoint_id): """Delete endpoint.""" resp_header, resp_body = self.delete('endpoints/%s' % endpoint_id) return resp_header, resp_body tempest-2013.2.a1291.g23a1b4f/tempest/services/identity/v3/xml/policy_client.py0000664000175000017500000000733012161375672027155 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 OpenStack Foundation # 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 urlparse import urlparse import httplib2 from lxml import etree from tempest.common.rest_client import RestClientXML from tempest.services.compute.xml.common import Document from tempest.services.compute.xml.common import Element from tempest.services.compute.xml.common import xml_to_json XMLNS = "http://docs.openstack.org/identity/api/v3" class PolicyClientXML(RestClientXML): def __init__(self, config, username, password, auth_url, tenant_name=None): super(PolicyClientXML, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.identity.catalog_type self.endpoint_url = 'adminURL' def _parse_array(self, node): array = [] for child in node.getchildren(): tag_list = child.tag.split('}', 1) if tag_list[1] == "policy": array.append(xml_to_json(child)) return array def _parse_body(self, body): json = xml_to_json(body) return json def request(self, method, url, headers=None, body=None, wait=None): """Overriding the existing HTTP request in super class RestClient.""" dscv = self.config.identity.disable_ssl_certificate_validation self.http_obj = httplib2.Http(disable_ssl_certificate_validation=dscv) self._set_auth() self.base_url = self.base_url.replace(urlparse(self.base_url).path, "/v3") return super(PolicyClientXML, self).request(method, url, headers=headers, body=body) def create_policy(self, blob, type): """Creates a Policy.""" create_policy = Element("policy", xmlns=XMLNS, blob=blob, type=type) resp, body = self.post('policies', str(Document(create_policy)), self.headers) body = self._parse_body(etree.fromstring(body)) return resp, body def list_policies(self): """Lists the policies.""" resp, body = self.get('policies', self.headers) body = self._parse_array(etree.fromstring(body)) return resp, body def get_policy(self, policy_id): """Lists out the given policy.""" url = 'policies/%s' % policy_id resp, body = self.get(url, self.headers) body = self._parse_body(etree.fromstring(body)) return resp, body def update_policy(self, policy_id, **kwargs): """Updates a policy.""" resp, body = self.get_policy(policy_id) type = kwargs.get('type') update_policy = Element("policy", xmlns=XMLNS, type=type) url = 'policies/%s' % policy_id resp, body = self.patch(url, str(Document(update_policy)), self.headers) body = self._parse_body(etree.fromstring(body)) return resp, body def delete_policy(self, policy_id): """Deletes the policy.""" url = "policies/%s" % policy_id return self.delete(url) tempest-2013.2.a1291.g23a1b4f/tempest/services/identity/v3/xml/identity_client.py0000664000175000017500000002261612161375672027513 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # # Copyright 2013 OpenStack Foundation # 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 urlparse import urlparse from lxml import etree from tempest.common.rest_client import RestClientXML from tempest.services.compute.xml.common import Document from tempest.services.compute.xml.common import Element from tempest.services.compute.xml.common import xml_to_json XMLNS = "http://docs.openstack.org/identity/api/v3" class IdentityV3ClientXML(RestClientXML): def __init__(self, config, username, password, auth_url, tenant_name=None): super(IdentityV3ClientXML, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.identity.catalog_type self.endpoint_url = 'adminURL' def _parse_projects(self, node): array = [] for child in node.getchildren(): tag_list = child.tag.split('}', 1) if tag_list[1] == "project": array.append(xml_to_json(child)) return array def _parse_domains(self, node): array = [] for child in node.getchildren(): tag_list = child.tag.split('}', 1) if tag_list[1] == "domain": array.append(xml_to_json(child)) return array def _parse_array(self, node): array = [] for child in node.getchildren(): array.append(xml_to_json(child)) return array def _parse_body(self, body): json = xml_to_json(body) return json def request(self, method, url, headers=None, body=None, wait=None): """Overriding the existing HTTP request in super class RestClient.""" self._set_auth() self.base_url = self.base_url.replace(urlparse(self.base_url).path, "/v3") return super(IdentityV3ClientXML, self).request(method, url, headers=headers, body=body) def create_user(self, user_name, **kwargs): """Creates a user.""" password = kwargs.get('password', None) email = kwargs.get('email', None) en = kwargs.get('enabled', 'true') project_id = kwargs.get('project_id', None) description = kwargs.get('description', None) domain_id = kwargs.get('domain_id', 'default') post_body = Element("user", xmlns=XMLNS, name=user_name, password=password, description=description, email=email, enabled=str(en).lower(), project_id=project_id, domain_id=domain_id) resp, body = self.post('users', str(Document(post_body)), self.headers) body = self._parse_body(etree.fromstring(body)) return resp, body def update_user(self, user_id, name, **kwargs): """Updates a user.""" email = kwargs.get('email', None) en = kwargs.get('enabled', True) project_id = kwargs.get('project_id', None) domain_id = kwargs.get('domain_id', 'default') description = kwargs.get('description', None) update_user = Element("user", xmlns=XMLNS, name=name, email=email, project_id=project_id, domain_id=domain_id, description=description, enabled=str(en).lower()) resp, body = self.patch('users/%s' % user_id, str(Document(update_user)), self.headers) body = self._parse_body(etree.fromstring(body)) return resp, body def list_user_projects(self, user_id): """Lists the projects on which a user has roles assigned.""" resp, body = self.get('users/%s/projects' % user_id, self.headers) body = self._parse_projects(etree.fromstring(body)) return resp, body def get_users(self): """Get the list of users.""" resp, body = self.get("users", self.headers) body = self._parse_array(etree.fromstring(body)) return resp, body def get_user(self, user_id): """GET a user.""" resp, body = self.get("users/%s" % user_id, self.headers) body = self._parse_body(etree.fromstring(body)) return resp, body def delete_user(self, user_id): """Deletes a User.""" resp, body = self.delete("users/%s" % user_id, self.headers) return resp, body def create_project(self, name, **kwargs): """Creates a project.""" description = kwargs.get('description', None) en = kwargs.get('enabled', 'true') domain_id = kwargs.get('domain_id', 'default') post_body = Element("project", xmlns=XMLNS, description=description, domain_id=domain_id, enabled=str(en).lower(), name=name) resp, body = self.post('projects', str(Document(post_body)), self.headers) body = self._parse_body(etree.fromstring(body)) return resp, body def get_project(self, project_id): """GET a Project.""" resp, body = self.get("projects/%s" % project_id, self.headers) body = self._parse_body(etree.fromstring(body)) return resp, body def delete_project(self, project_id): """Delete a project.""" resp, body = self.delete('projects/%s' % str(project_id)) return resp, body def create_role(self, name): """Create a Role.""" post_body = Element("role", xmlns=XMLNS, name=name) resp, body = self.post('roles', str(Document(post_body)), self.headers) body = self._parse_body(etree.fromstring(body)) return resp, body def get_role(self, role_id): """GET a Role.""" resp, body = self.get('roles/%s' % str(role_id), self.headers) body = self._parse_body(etree.fromstring(body)) return resp, body def delete_role(self, role_id): """Delete a role.""" resp, body = self.delete('roles/%s' % str(role_id), self.headers) return resp, body def assign_user_role(self, project_id, user_id, role_id): """Add roles to a user on a tenant.""" resp, body = self.put('projects/%s/users/%s/roles/%s' % (project_id, user_id, role_id), '', self.headers) return resp, body def create_domain(self, name, **kwargs): """Creates a domain.""" description = kwargs.get('description', None) en = kwargs.get('enabled', True) post_body = Element("domain", xmlns=XMLNS, name=name, description=description, enabled=str(en).lower()) resp, body = self.post('domains', str(Document(post_body)), self.headers) body = self._parse_body(etree.fromstring(body)) return resp, body def list_domains(self): """Get the list of domains.""" resp, body = self.get("domains", self.headers) body = self._parse_domains(etree.fromstring(body)) return resp, body def delete_domain(self, domain_id): """Delete a domain.""" resp, body = self.delete('domains/%s' % domain_id, self.headers) return resp, body def update_domain(self, domain_id, **kwargs): """Updates a domain.""" resp, body = self.get_domain(domain_id) description = kwargs.get('description', body['description']) en = kwargs.get('enabled', body['enabled']) name = kwargs.get('name', body['name']) post_body = Element("domain", xmlns=XMLNS, name=name, description=description, enabled=str(en).lower()) resp, body = self.patch('domains/%s' % domain_id, str(Document(post_body)), self.headers) body = self._parse_body(etree.fromstring(body)) return resp, body def get_domain(self, domain_id): """Get Domain details.""" resp, body = self.get('domains/%s' % domain_id, self.headers) body = self._parse_body(etree.fromstring(body)) return resp, body tempest-2013.2.a1291.g23a1b4f/tempest/services/identity/v3/xml/__init__.py0000664000175000017500000000000012161375672026042 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/identity/v3/__init__.py0000664000175000017500000000000012161375672025242 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/identity/v3/json/0000775000175000017500000000000012161375700024104 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/identity/v3/json/service_client.py0000664000175000017500000000472112161375672027470 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 OpenStack Foundation # 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 json from urlparse import urlparse from tempest.common.rest_client import RestClient class ServiceClientJSON(RestClient): def __init__(self, config, username, password, auth_url, tenant_name=None): super(ServiceClientJSON, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.identity.catalog_type self.endpoint_url = 'adminURL' def request(self, method, url, headers=None, body=None, wait=None): """Overriding the existing HTTP request in super class rest_client.""" self._set_auth() self.base_url = self.base_url.replace(urlparse(self.base_url).path, "/v3") return super(ServiceClientJSON, self).request(method, url, headers=headers, body=body) def update_service(self, service_id, **kwargs): """Updates a service.""" resp, body = self.get_service(service_id) name = kwargs.get('name', body['name']) type = kwargs.get('type', body['type']) desc = kwargs.get('description', body['description']) patch_body = { 'description': desc, 'type': type, 'name': name } patch_body = json.dumps({'service': patch_body}) resp, body = self.patch('services/%s' % service_id, patch_body, self.headers) body = json.loads(body) return resp, body['service'] def get_service(self, service_id): """Get Service.""" url = 'services/%s' % service_id resp, body = self.get(url) body = json.loads(body) return resp, body['service'] tempest-2013.2.a1291.g23a1b4f/tempest/services/identity/v3/json/endpoints_client.py0000664000175000017500000000656312161375672030041 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # # Copyright 2013 OpenStack Foundation # 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 json import urlparse from tempest.common.rest_client import RestClient class EndPointClientJSON(RestClient): def __init__(self, config, username, password, auth_url, tenant_name=None): super(EndPointClientJSON, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.identity.catalog_type self.endpoint_url = 'adminURL' def request(self, method, url, headers=None, body=None, wait=None): """Overriding the existing HTTP request in super class rest_client.""" self._set_auth() self.base_url = self.base_url.replace( urlparse.urlparse(self.base_url).path, "/v3") return super(EndPointClientJSON, self).request(method, url, headers=headers, body=body) def list_endpoints(self): """GET endpoints.""" resp, body = self.get('endpoints') body = json.loads(body) return resp, body['endpoints'] def create_endpoint(self, service_id, interface, url, **kwargs): """Create endpoint.""" region = kwargs.get('region', None) enabled = kwargs.get('enabled', None) post_body = { 'service_id': service_id, 'interface': interface, 'url': url, 'region': region, 'enabled': enabled } post_body = json.dumps({'endpoint': post_body}) resp, body = self.post('endpoints', post_body, self.headers) body = json.loads(body) return resp, body['endpoint'] def update_endpoint(self, endpoint_id, service_id=None, interface=None, url=None, region=None, enabled=None): """Updates an endpoint with given parameters.""" post_body = {} if service_id is not None: post_body['service_id'] = service_id if interface is not None: post_body['interface'] = interface if url is not None: post_body['url'] = url if region is not None: post_body['region'] = region if enabled is not None: post_body['enabled'] = enabled post_body = json.dumps({'endpoint': post_body}) resp, body = self.patch('endpoints/%s' % endpoint_id, post_body, self.headers) body = json.loads(body) return resp, body['endpoint'] def delete_endpoint(self, endpoint_id): """Delete endpoint.""" resp_header, resp_body = self.delete('endpoints/%s' % endpoint_id) return resp_header, resp_body tempest-2013.2.a1291.g23a1b4f/tempest/services/identity/v3/json/policy_client.py0000664000175000017500000000570312161375672027330 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 OpenStack Foundation # 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 json from urlparse import urlparse from tempest.common.rest_client import RestClient class PolicyClientJSON(RestClient): def __init__(self, config, username, password, auth_url, tenant_name=None): super(PolicyClientJSON, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.identity.catalog_type self.endpoint_url = 'adminURL' def request(self, method, url, headers=None, body=None, wait=None): """Overriding the existing HTTP request in super class rest_client.""" self._set_auth() self.base_url = self.base_url.replace(urlparse(self.base_url).path, "/v3") return super(PolicyClientJSON, self).request(method, url, headers=headers, body=body) def create_policy(self, blob, type): """Creates a Policy.""" post_body = { "blob": blob, "type": type } post_body = json.dumps({'policy': post_body}) resp, body = self.post('policies', post_body, self.headers) body = json.loads(body) return resp, body['policy'] def list_policies(self): """Lists the policies.""" resp, body = self.get('policies') body = json.loads(body) return resp, body['policies'] def get_policy(self, policy_id): """Lists out the given policy.""" url = 'policies/%s' % policy_id resp, body = self.get(url) body = json.loads(body) return resp, body['policy'] def update_policy(self, policy_id, **kwargs): """Updates a policy.""" resp, body = self.get_policy(policy_id) type = kwargs.get('type') post_body = { 'type': type } post_body = json.dumps({'policy': post_body}) url = 'policies/%s' % policy_id resp, body = self.patch(url, post_body, self.headers) body = json.loads(body) return resp, body['policy'] def delete_policy(self, policy_id): """Deletes the policy.""" url = "policies/%s" % policy_id return self.delete(url) tempest-2013.2.a1291.g23a1b4f/tempest/services/identity/v3/json/identity_client.py0000664000175000017500000001673412161375672027670 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 OpenStack Foundation # 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 json from urlparse import urlparse from tempest.common.rest_client import RestClient class IdentityV3ClientJSON(RestClient): def __init__(self, config, username, password, auth_url, tenant_name=None): super(IdentityV3ClientJSON, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.identity.catalog_type self.endpoint_url = 'adminURL' def request(self, method, url, headers=None, body=None, wait=None): """Overriding the existing HTTP request in super class rest_client.""" self._set_auth() self.base_url = self.base_url.replace(urlparse(self.base_url).path, "/v3") return super(IdentityV3ClientJSON, self).request(method, url, headers=headers, body=body) def create_user(self, user_name, **kwargs): """Creates a user.""" password = kwargs.get('password', None) email = kwargs.get('email', None) en = kwargs.get('enabled', True) project_id = kwargs.get('project_id', None) description = kwargs.get('description', None) domain_id = kwargs.get('domain_id', 'default') post_body = { 'project_id': project_id, 'description': description, 'domain_id': domain_id, 'email': email, 'enabled': en, 'name': user_name, 'password': password } post_body = json.dumps({'user': post_body}) resp, body = self.post('users', post_body, self.headers) body = json.loads(body) return resp, body['user'] def update_user(self, user_id, name, **kwargs): """Updates a user.""" email = kwargs.get('email', None) en = kwargs.get('enabled', True) project_id = kwargs.get('project_id', None) description = kwargs.get('description', None) domain_id = kwargs.get('domain_id', 'default') post_body = { 'name': name, 'email': email, 'enabled': en, 'project_id': project_id, 'id': user_id, 'domain_id': domain_id, 'description': description } post_body = json.dumps({'user': post_body}) resp, body = self.patch('users/%s' % user_id, post_body, self.headers) body = json.loads(body) return resp, body['user'] def list_user_projects(self, user_id): """Lists the projects on which a user has roles assigned.""" resp, body = self.get('users/%s/projects' % user_id, self.headers) body = json.loads(body) return resp, body['projects'] def get_users(self): """Get the list of users.""" resp, body = self.get("users") body = json.loads(body) return resp, body['users'] def get_user(self, user_id): """GET a user.""" resp, body = self.get("users/%s" % user_id) body = json.loads(body) return resp, body['user'] def delete_user(self, user_id): """Deletes a User.""" resp, body = self.delete("users/%s" % user_id) return resp, body def create_project(self, name, **kwargs): """Creates a project.""" description = kwargs.get('description', None) en = kwargs.get('enabled', True) domain_id = kwargs.get('domain_id', 'default') post_body = { 'description': description, 'domain_id': domain_id, 'enabled': en, 'name': name } post_body = json.dumps({'project': post_body}) resp, body = self.post('projects', post_body, self.headers) body = json.loads(body) return resp, body['project'] def get_project(self, project_id): """GET a Project.""" resp, body = self.get("projects/%s" % project_id) body = json.loads(body) return resp, body['project'] def delete_project(self, project_id): """Delete a project.""" resp, body = self.delete('projects/%s' % str(project_id)) return resp, body def create_role(self, name): """Create a Role.""" post_body = { 'name': name } post_body = json.dumps({'role': post_body}) resp, body = self.post('roles', post_body, self.headers) body = json.loads(body) return resp, body['role'] def get_role(self, role_id): """GET a Role.""" resp, body = self.get('roles/%s' % str(role_id)) body = json.loads(body) return resp, body['role'] def delete_role(self, role_id): """Delete a role.""" resp, body = self.delete('roles/%s' % str(role_id)) return resp, body def assign_user_role(self, project_id, user_id, role_id): """Add roles to a user on a project.""" resp, body = self.put('projects/%s/users/%s/roles/%s' % (project_id, user_id, role_id), None, self.headers) return resp, body def create_domain(self, name, **kwargs): """Creates a domain.""" description = kwargs.get('description', None) en = kwargs.get('enabled', True) post_body = { 'description': description, 'enabled': en, 'name': name } post_body = json.dumps({'domain': post_body}) resp, body = self.post('domains', post_body, self.headers) body = json.loads(body) return resp, body['domain'] def delete_domain(self, domain_id): """Delete a domain.""" resp, body = self.delete('domains/%s' % str(domain_id)) return resp, body def list_domains(self): """List Domains.""" resp, body = self.get('domains') body = json.loads(body) return resp, body['domains'] def update_domain(self, domain_id, **kwargs): """Updates a domain.""" resp, body = self.get_domain(domain_id) description = kwargs.get('description', body['description']) en = kwargs.get('enabled', body['enabled']) name = kwargs.get('name', body['name']) post_body = { 'description': description, 'enabled': en, 'name': name } post_body = json.dumps({'domain': post_body}) resp, body = self.patch('domains/%s' % domain_id, post_body, self.headers) body = json.loads(body) return resp, body['domain'] def get_domain(self, domain_id): """Get Domain details.""" resp, body = self.get('domains/%s' % domain_id) body = json.loads(body) return resp, body['domain'] tempest-2013.2.a1291.g23a1b4f/tempest/services/identity/v3/json/__init__.py0000664000175000017500000000000012161375672026213 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/identity/xml/0000775000175000017500000000000012161375700023403 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/identity/xml/identity_client.py0000664000175000017500000002640312161375672027161 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # # Copyright 2012 IBM Corp. # 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 httplib2 import json from lxml import etree from tempest.common.rest_client import RestClientXML from tempest import exceptions from tempest.services.compute.xml.common import Document from tempest.services.compute.xml.common import Element from tempest.services.compute.xml.common import xml_to_json XMLNS = "http://docs.openstack.org/identity/api/v2.0" class IdentityClientXML(RestClientXML): def __init__(self, config, username, password, auth_url, tenant_name=None): super(IdentityClientXML, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.identity.catalog_type self.endpoint_url = 'adminURL' def _parse_array(self, node): array = [] for child in node.getchildren(): array.append(xml_to_json(child)) return array def _parse_body(self, body): data = xml_to_json(body) return data def has_admin_extensions(self): """ Returns True if the KSADM Admin Extensions are supported False otherwise """ if hasattr(self, '_has_admin_extensions'): return self._has_admin_extensions resp, body = self.list_roles() self._has_admin_extensions = ('status' in resp and resp.status != 503) return self._has_admin_extensions def create_role(self, name): """Create a role.""" create_role = Element("role", xmlns=XMLNS, name=name) resp, body = self.post('OS-KSADM/roles', str(Document(create_role)), self.headers) body = self._parse_body(etree.fromstring(body)) return resp, body def create_tenant(self, name, **kwargs): """ Create a tenant name (required): New tenant name description: Description of new tenant (default is none) enabled : Initial tenant status (default is true) """ en = kwargs.get('enabled', 'true') create_tenant = Element("tenant", xmlns=XMLNS, name=name, description=kwargs.get('description', ''), enabled=str(en).lower()) resp, body = self.post('tenants', str(Document(create_tenant)), self.headers) body = self._parse_body(etree.fromstring(body)) return resp, body def delete_role(self, role_id): """Delete a role.""" resp, body = self.delete('OS-KSADM/roles/%s' % str(role_id), self.headers) return resp, body def list_user_roles(self, tenant_id, user_id): """Returns a list of roles assigned to a user for a tenant.""" url = '/tenants/%s/users/%s/roles' % (tenant_id, user_id) resp, body = self.get(url, self.headers) body = self._parse_array(etree.fromstring(body)) return resp, body def assign_user_role(self, tenant_id, user_id, role_id): """Add roles to a user on a tenant.""" resp, body = self.put('/tenants/%s/users/%s/roles/OS-KSADM/%s' % (tenant_id, user_id, role_id), '', self.headers) body = self._parse_body(etree.fromstring(body)) return resp, body def remove_user_role(self, tenant_id, user_id, role_id): """Removes a role assignment for a user on a tenant.""" return self.delete('/tenants/%s/users/%s/roles/OS-KSADM/%s' % (tenant_id, user_id, role_id), self.headers) def delete_tenant(self, tenant_id): """Delete a tenant.""" resp, body = self.delete('tenants/%s' % str(tenant_id), self.headers) return resp, body def get_tenant(self, tenant_id): """Get tenant details.""" resp, body = self.get('tenants/%s' % str(tenant_id), self.headers) body = self._parse_body(etree.fromstring(body)) return resp, body def list_roles(self): """Returns roles.""" resp, body = self.get('OS-KSADM/roles', self.headers) body = self._parse_array(etree.fromstring(body)) return resp, body def list_tenants(self): """Returns tenants.""" resp, body = self.get('tenants', self.headers) body = self._parse_array(etree.fromstring(body)) return resp, body def get_tenant_by_name(self, tenant_name): resp, tenants = self.list_tenants() for tenant in tenants: if tenant['name'] == tenant_name: return tenant raise exceptions.NotFound('No such tenant') def update_tenant(self, tenant_id, **kwargs): """Updates a tenant.""" resp, body = self.get_tenant(tenant_id) name = kwargs.get('name', body['name']) desc = kwargs.get('description', body['description']) en = kwargs.get('enabled', body['enabled']) update_tenant = Element("tenant", xmlns=XMLNS, id=tenant_id, name=name, description=desc, enabled=str(en).lower()) resp, body = self.post('tenants/%s' % tenant_id, str(Document(update_tenant)), self.headers) body = self._parse_body(etree.fromstring(body)) return resp, body def create_user(self, name, password, tenant_id, email): """Create a user.""" create_user = Element("user", xmlns=XMLNS, name=name, password=password, tenantId=tenant_id, email=email) resp, body = self.post('users', str(Document(create_user)), self.headers) body = self._parse_body(etree.fromstring(body)) return resp, body def get_user(self, user_id): """GET a user.""" resp, body = self.get("users/%s" % user_id, self.headers) body = self._parse_body(etree.fromstring(body)) return resp, body def delete_user(self, user_id): """Delete a user.""" resp, body = self.delete("users/%s" % user_id, self.headers) return resp, body def get_users(self): """Get the list of users.""" resp, body = self.get("users", self.headers) body = self._parse_array(etree.fromstring(body)) return resp, body def enable_disable_user(self, user_id, enabled): """Enables or disables a user.""" enable_user = Element("user", enabled=str(enabled).lower()) resp, body = self.put('users/%s/enabled' % user_id, str(Document(enable_user)), self.headers) body = self._parse_array(etree.fromstring(body)) return resp, body def delete_token(self, token_id): """Delete a token.""" resp, body = self.delete("tokens/%s" % token_id, self.headers) return resp, body def list_users_for_tenant(self, tenant_id): """List users for a Tenant.""" resp, body = self.get('/tenants/%s/users' % tenant_id, self.headers) body = self._parse_array(etree.fromstring(body)) return resp, body def get_user_by_username(self, tenant_id, username): resp, users = self.list_users_for_tenant(tenant_id) for user in users: if user['name'] == username: return user raise exceptions.NotFound('No such user') def create_service(self, name, type, **kwargs): """Create a service.""" OS_KSADM = "http://docs.openstack.org/identity/api/ext/OS-KSADM/v1.0" create_service = Element("service", xmlns=OS_KSADM, name=name, type=type, description=kwargs.get('description')) resp, body = self.post('OS-KSADM/services', str(Document(create_service)), self.headers) body = self._parse_body(etree.fromstring(body)) return resp, body def list_services(self): """Returns services.""" resp, body = self.get('OS-KSADM/services', self.headers) body = self._parse_array(etree.fromstring(body)) return resp, body def get_service(self, service_id): """Get Service.""" url = '/OS-KSADM/services/%s' % service_id resp, body = self.get(url, self.headers) body = self._parse_body(etree.fromstring(body)) return resp, body def delete_service(self, service_id): """Delete Service.""" url = '/OS-KSADM/services/%s' % service_id return self.delete(url, self.headers) class TokenClientXML(RestClientXML): def __init__(self, config): auth_url = config.identity.uri # TODO(jaypipes) Why is this all repeated code in here? # Normalize URI to ensure /tokens is in it. if 'tokens' not in auth_url: auth_url = auth_url.rstrip('/') + '/tokens' self.auth_url = auth_url self.config = config def auth(self, user, password, tenant): passwordCreds = Element("passwordCredentials", username=user, password=password) auth = Element("auth", tenantName=tenant) auth.append(passwordCreds) headers = {'Content-Type': 'application/xml'} resp, body = self.post(self.auth_url, headers=headers, body=str(Document(auth))) return resp, body def request(self, method, url, headers=None, body=None): """A simple HTTP request interface.""" dscv = self.config.identity.disable_ssl_certificate_validation self.http_obj = httplib2.Http(disable_ssl_certificate_validation=dscv) if headers is None: headers = {} self._log_request(method, url, headers, body) resp, resp_body = self.http_obj.request(url, method, headers=headers, body=body) self._log_response(resp, resp_body) if resp.status in (401, 403): resp_body = json.loads(resp_body) raise exceptions.Unauthorized(resp_body['error']['message']) return resp, resp_body def get_token(self, user, password, tenant): resp, body = self.auth(user, password, tenant) if resp['status'] != '202': body = json.loads(body) access = body['access'] token = access['token'] return token['id'] tempest-2013.2.a1291.g23a1b4f/tempest/services/identity/xml/__init__.py0000664000175000017500000000000012161375672025512 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/identity/__init__.py0000664000175000017500000000000012161375672024712 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/identity/json/0000775000175000017500000000000012161375700023554 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/identity/json/identity_client.py0000664000175000017500000002227712161375672027337 0ustar chuckchuck00000000000000import httplib2 import json from tempest.common.rest_client import RestClient from tempest import exceptions class IdentityClientJSON(RestClient): def __init__(self, config, username, password, auth_url, tenant_name=None): super(IdentityClientJSON, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.identity.catalog_type self.endpoint_url = 'adminURL' def has_admin_extensions(self): """ Returns True if the KSADM Admin Extensions are supported False otherwise """ if hasattr(self, '_has_admin_extensions'): return self._has_admin_extensions resp, body = self.list_roles() self._has_admin_extensions = ('status' in resp and resp.status != 503) return self._has_admin_extensions def create_role(self, name): """Create a role.""" post_body = { 'name': name, } post_body = json.dumps({'role': post_body}) resp, body = self.post('OS-KSADM/roles', post_body, self.headers) body = json.loads(body) return resp, body['role'] def create_tenant(self, name, **kwargs): """ Create a tenant name (required): New tenant name description: Description of new tenant (default is none) enabled : Initial tenant status (default is true) """ post_body = { 'name': name, 'description': kwargs.get('description', ''), 'enabled': kwargs.get('enabled', True), } post_body = json.dumps({'tenant': post_body}) resp, body = self.post('tenants', post_body, self.headers) body = json.loads(body) return resp, body['tenant'] def delete_role(self, role_id): """Delete a role.""" resp, body = self.delete('OS-KSADM/roles/%s' % str(role_id)) return resp, body def list_user_roles(self, tenant_id, user_id): """Returns a list of roles assigned to a user for a tenant.""" url = '/tenants/%s/users/%s/roles' % (tenant_id, user_id) resp, body = self.get(url) body = json.loads(body) return resp, body['roles'] def assign_user_role(self, tenant_id, user_id, role_id): """Add roles to a user on a tenant.""" post_body = json.dumps({}) resp, body = self.put('/tenants/%s/users/%s/roles/OS-KSADM/%s' % (tenant_id, user_id, role_id), post_body, self.headers) body = json.loads(body) return resp, body['role'] def remove_user_role(self, tenant_id, user_id, role_id): """Removes a role assignment for a user on a tenant.""" return self.delete('/tenants/%s/users/%s/roles/OS-KSADM/%s' % (tenant_id, user_id, role_id)) def delete_tenant(self, tenant_id): """Delete a tenant.""" resp, body = self.delete('tenants/%s' % str(tenant_id)) return resp, body def get_tenant(self, tenant_id): """Get tenant details.""" resp, body = self.get('tenants/%s' % str(tenant_id)) body = json.loads(body) return resp, body['tenant'] def list_roles(self): """Returns roles.""" resp, body = self.get('OS-KSADM/roles') body = json.loads(body) return resp, body['roles'] def list_tenants(self): """Returns tenants.""" resp, body = self.get('tenants') body = json.loads(body) return resp, body['tenants'] def get_tenant_by_name(self, tenant_name): resp, tenants = self.list_tenants() for tenant in tenants: if tenant['name'] == tenant_name: return tenant raise exceptions.NotFound('No such tenant') def update_tenant(self, tenant_id, **kwargs): """Updates a tenant.""" resp, body = self.get_tenant(tenant_id) name = kwargs.get('name', body['name']) desc = kwargs.get('description', body['description']) en = kwargs.get('enabled', body['enabled']) post_body = { 'id': tenant_id, 'name': name, 'description': desc, 'enabled': en, } post_body = json.dumps({'tenant': post_body}) resp, body = self.post('tenants/%s' % tenant_id, post_body, self.headers) body = json.loads(body) return resp, body['tenant'] def create_user(self, name, password, tenant_id, email): """Create a user.""" post_body = { 'name': name, 'password': password, 'tenantId': tenant_id, 'email': email } post_body = json.dumps({'user': post_body}) resp, body = self.post('users', post_body, self.headers) body = json.loads(body) return resp, body['user'] def get_user(self, user_id): """GET a user.""" resp, body = self.get("users/%s" % user_id) body = json.loads(body) return resp, body['user'] def delete_user(self, user_id): """Delete a user.""" resp, body = self.delete("users/%s" % user_id) return resp, body def get_users(self): """Get the list of users.""" resp, body = self.get("users") body = json.loads(body) return resp, body['users'] def enable_disable_user(self, user_id, enabled): """Enables or disables a user.""" put_body = { 'enabled': enabled } put_body = json.dumps({'user': put_body}) resp, body = self.put('users/%s/enabled' % user_id, put_body, self.headers) body = json.loads(body) return resp, body def delete_token(self, token_id): """Delete a token.""" resp, body = self.delete("tokens/%s" % token_id) return resp, body def list_users_for_tenant(self, tenant_id): """List users for a Tenant.""" resp, body = self.get('/tenants/%s/users' % tenant_id) body = json.loads(body) return resp, body['users'] def get_user_by_username(self, tenant_id, username): resp, users = self.list_users_for_tenant(tenant_id) for user in users: if user['name'] == username: return user raise exceptions.NotFound('No such user') def create_service(self, name, type, **kwargs): """Create a service.""" post_body = { 'name': name, 'type': type, 'description': kwargs.get('description') } post_body = json.dumps({'OS-KSADM:service': post_body}) resp, body = self.post('/OS-KSADM/services', post_body, self.headers) body = json.loads(body) return resp, body['OS-KSADM:service'] def get_service(self, service_id): """Get Service.""" url = '/OS-KSADM/services/%s' % service_id resp, body = self.get(url) body = json.loads(body) return resp, body['OS-KSADM:service'] def list_services(self): """List Service - Returns Services.""" resp, body = self.get('/OS-KSADM/services/') body = json.loads(body) return resp, body['OS-KSADM:services'] def delete_service(self, service_id): """Delete Service.""" url = '/OS-KSADM/services/%s' % service_id return self.delete(url) class TokenClientJSON(RestClient): def __init__(self, config): auth_url = config.identity.uri # TODO(jaypipes) Why is this all repeated code in here? # Normalize URI to ensure /tokens is in it. if 'tokens' not in auth_url: auth_url = auth_url.rstrip('/') + '/tokens' self.auth_url = auth_url self.config = config def auth(self, user, password, tenant): creds = { 'auth': { 'passwordCredentials': { 'username': user, 'password': password, }, 'tenantName': tenant, } } headers = {'Content-Type': 'application/json'} body = json.dumps(creds) resp, body = self.post(self.auth_url, headers=headers, body=body) return resp, body def request(self, method, url, headers=None, body=None): """A simple HTTP request interface.""" dscv = self.config.identity.disable_ssl_certificate_validation self.http_obj = httplib2.Http(disable_ssl_certificate_validation=dscv) if headers is None: headers = {} self._log_request(method, url, headers, body) resp, resp_body = self.http_obj.request(url, method, headers=headers, body=body) self._log_response(resp, resp_body) if resp.status in (401, 403): resp_body = json.loads(resp_body) raise exceptions.Unauthorized(resp_body['error']['message']) return resp, resp_body def get_token(self, user, password, tenant): resp, body = self.auth(user, password, tenant) if resp['status'] != '202': body = json.loads(body) access = body['access'] token = access['token'] return token['id'] tempest-2013.2.a1291.g23a1b4f/tempest/services/identity/json/__init__.py0000664000175000017500000000000012161375672025663 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/volume/0000775000175000017500000000000012161375700022261 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/volume/xml/0000775000175000017500000000000012161375700023061 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/volume/xml/snapshots_client.py0000664000175000017500000001221112161375672027020 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # 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 time import urllib from lxml import etree from tempest.common import log as logging from tempest.common.rest_client import RestClientXML from tempest import exceptions from tempest.services.compute.xml.common import Document from tempest.services.compute.xml.common import Element from tempest.services.compute.xml.common import xml_to_json from tempest.services.compute.xml.common import XMLNS_11 LOG = logging.getLogger(__name__) class SnapshotsClientXML(RestClientXML): """Client class to send CRUD Volume API requests.""" def __init__(self, config, username, password, auth_url, tenant_name=None): super(SnapshotsClientXML, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.volume.catalog_type self.build_interval = self.config.volume.build_interval self.build_timeout = self.config.volume.build_timeout def list_snapshots(self, params=None): """List all snapshot.""" url = 'snapshots' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url, self.headers) body = etree.fromstring(body) snapshots = [] for snap in body: snapshots.append(xml_to_json(snap)) return resp, snapshots def list_snapshots_with_detail(self, params=None): """List all the details of snapshot.""" url = 'snapshots/detail' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url, self.headers) body = etree.fromstring(body) snapshots = [] for snap in body: snapshots.append(xml_to_json(snap)) return resp, snapshots def get_snapshot(self, snapshot_id): """Returns the details of a single snapshot.""" url = "snapshots/%s" % str(snapshot_id) resp, body = self.get(url, self.headers) body = etree.fromstring(body) return resp, xml_to_json(body) def create_snapshot(self, volume_id, **kwargs): """Creates a new snapshot. volume_id(Required): id of the volume. force: Create a snapshot even if the volume attached (Default=False) display_name: Optional snapshot Name. display_description: User friendly snapshot description. """ #NOTE(afazekas): it should use the volume namaspace snapshot = Element("snapshot", xmlns=XMLNS_11, volume_id=volume_id) for key, value in kwargs.items(): snapshot.add_attr(key, value) resp, body = self.post('snapshots', str(Document(snapshot)), self.headers) body = xml_to_json(etree.fromstring(body)) return resp, body #NOTE(afazekas): just for the wait function def _get_snapshot_status(self, snapshot_id): resp, body = self.get_snapshot(snapshot_id) status = body['status'] #NOTE(afazekas): snapshot can reach an "error" # state in a "normal" lifecycle if (status == 'error'): raise exceptions.SnapshotBuildErrorException( snapshot_id=snapshot_id) return status #NOTE(afazkas): Wait reinvented again. It is not in the correct layer def wait_for_snapshot_status(self, snapshot_id, status): """Waits for a Snapshot to reach a given status.""" start_time = time.time() old_value = value = self._get_snapshot_status(snapshot_id) while True: dtime = time.time() - start_time time.sleep(self.build_interval) if value != old_value: LOG.info('Value transition from "%s" to "%s"' 'in %d second(s).', old_value, value, dtime) if (value == status): return value if dtime > self.build_timeout: message = ('Time Limit Exceeded! (%ds)' 'while waiting for %s, ' 'but we got %s.' % (self.build_timeout, status, value)) raise exceptions.TimeoutException(message) time.sleep(self.build_interval) old_value = value value = self._get_snapshot_status(snapshot_id) def delete_snapshot(self, snapshot_id): """Delete Snapshot.""" return self.delete("snapshots/%s" % str(snapshot_id)) def is_resource_deleted(self, id): try: self.get_snapshot(id) except exceptions.NotFound: return True return False tempest-2013.2.a1291.g23a1b4f/tempest/services/volume/xml/admin/0000775000175000017500000000000012161375700024151 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/volume/xml/admin/volume_types_client.py0000664000175000017500000001735512161375672030637 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # # Copyright 2012 IBM Corp. # 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 urllib from lxml import etree from tempest.common.rest_client import RestClientXML from tempest import exceptions from tempest.services.compute.xml.common import Document from tempest.services.compute.xml.common import Element from tempest.services.compute.xml.common import Text from tempest.services.compute.xml.common import xml_to_json from tempest.services.compute.xml.common import XMLNS_11 class VolumeTypesClientXML(RestClientXML): """ Client class to send CRUD Volume Types API requests to a Cinder endpoint """ def __init__(self, config, username, password, auth_url, tenant_name=None): super(VolumeTypesClientXML, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.volume.catalog_type self.build_interval = self.config.compute.build_interval self.build_timeout = self.config.compute.build_timeout def _parse_volume_type(self, body): vol_type = dict((attr, body.get(attr)) for attr in body.keys()) for child in body.getchildren(): tag = child.tag if tag.startswith("{"): ns, tag = tag.split("}", 1) if tag == 'extra_specs': vol_type['extra_specs'] = dict((meta.get('key'), meta.text) for meta in list(child)) else: vol_type[tag] = xml_to_json(child) return vol_type def _parse_volume_type_extra_specs(self, body): extra_spec = dict((attr, body.get(attr)) for attr in body.keys()) for child in body.getchildren(): tag = child.tag if tag.startswith("{"): ns, tag = tag.split("}", 1) else: extra_spec[tag] = xml_to_json(child) return extra_spec def list_volume_types(self, params=None): """List all the volume_types created.""" url = 'types' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url, self.headers) body = etree.fromstring(body) volume_types = [] if body is not None: volume_types += [self._parse_volume_type(vol) for vol in list(body)] return resp, volume_types def get_volume_type(self, type_id): """Returns the details of a single volume_type.""" url = "types/%s" % str(type_id) resp, body = self.get(url, self.headers) body = etree.fromstring(body) return resp, self._parse_volume_type(body) def create_volume_type(self, name, **kwargs): """ Creates a new Volume_type. name(Required): Name of volume_type. Following optional keyword arguments are accepted: extra_specs: A dictionary of values to be used as extra_specs. """ vol_type = Element("volume_type", xmlns=XMLNS_11) if name: vol_type.add_attr('name', name) extra_specs = kwargs.get('extra_specs') if extra_specs: _extra_specs = Element('extra_specs') vol_type.append(_extra_specs) for key, value in extra_specs.items(): spec = Element('extra_spec') spec.add_attr('key', key) spec.append(Text(value)) _extra_specs.append(spec) resp, body = self.post('types', str(Document(vol_type)), self.headers) body = xml_to_json(etree.fromstring(body)) return resp, body def delete_volume_type(self, type_id): """Deletes the Specified Volume_type.""" return self.delete("types/%s" % str(type_id)) def list_volume_types_extra_specs(self, vol_type_id, params=None): """List all the volume_types extra specs created.""" url = 'types/%s/extra_specs' % str(vol_type_id) if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url, self.headers) body = etree.fromstring(body) extra_specs = [] if body is not None: extra_specs += [self._parse_volume_type_extra_specs(spec) for spec in list(body)] return resp, extra_specs def get_volume_type_extra_specs(self, vol_type_id, extra_spec_name): """Returns the details of a single volume_type extra spec.""" url = "types/%s/extra_specs/%s" % (str(vol_type_id), str(extra_spec_name)) resp, body = self.get(url, self.headers) body = etree.fromstring(body) return resp, self._parse_volume_type_extra_specs(body) def create_volume_type_extra_specs(self, vol_type_id, extra_spec): """ Creates a new Volume_type extra spec. vol_type_id: Id of volume_type. extra_specs: A dictionary of values to be used as extra_specs. """ url = "types/%s/extra_specs" % str(vol_type_id) extra_specs = Element("extra_specs", xmlns=XMLNS_11) if extra_spec: if isinstance(extra_spec, list): extra_specs.append(extra_spec) else: for key, value in extra_spec.items(): spec = Element('extra_spec') spec.add_attr('key', key) spec.append(Text(value)) extra_specs.append(spec) else: extra_specs = None resp, body = self.post(url, str(Document(extra_specs)), self.headers) body = xml_to_json(etree.fromstring(body)) return resp, body def delete_volume_type_extra_specs(self, vol_id, extra_spec_name): """Deletes the Specified Volume_type extra spec.""" return self.delete("types/%s/extra_specs/%s" % ((str(vol_id)), str(extra_spec_name))) def update_volume_type_extra_specs(self, vol_type_id, extra_spec_name, extra_spec): """ Update a volume_type extra spec. vol_type_id: Id of volume_type. extra_spec_name: Name of the extra spec to be updated. extra_spec: A dictionary of with key as extra_spec_name and the updated value. """ url = "types/%s/extra_specs/%s" % (str(vol_type_id), str(extra_spec_name)) extra_specs = Element("extra_specs", xmlns=XMLNS_11) if extra_spec is not None: for key, value in extra_spec.items(): spec = Element('extra_spec') spec.add_attr('key', key) spec.append(Text(value)) extra_specs.append(spec) resp, body = self.put(url, str(Document(extra_specs)), self.headers) body = xml_to_json(etree.fromstring(body)) return resp, body def is_resource_deleted(self, id): try: self.get_volume_type(id) except exceptions.NotFound: return True return False tempest-2013.2.a1291.g23a1b4f/tempest/services/volume/xml/admin/__init__.py0000664000175000017500000000000012161375672026260 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/volume/xml/volumes_client.py0000664000175000017500000001352212161375672026476 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # # Copyright 2012 IBM Corp. # 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 time import urllib from lxml import etree from tempest.common.rest_client import RestClientXML from tempest import exceptions from tempest.services.compute.xml.common import Document from tempest.services.compute.xml.common import Element from tempest.services.compute.xml.common import Text from tempest.services.compute.xml.common import xml_to_json from tempest.services.compute.xml.common import XMLNS_11 class VolumesClientXML(RestClientXML): """ Client class to send CRUD Volume API requests to a Cinder endpoint """ def __init__(self, config, username, password, auth_url, tenant_name=None): super(VolumesClientXML, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.volume.catalog_type self.build_interval = self.config.compute.build_interval self.build_timeout = self.config.compute.build_timeout def _parse_volume(self, body): vol = dict((attr, body.get(attr)) for attr in body.keys()) for child in body.getchildren(): tag = child.tag if tag.startswith("{"): ns, tag = tag.split("}", 1) if tag == 'metadata': vol['metadata'] = dict((meta.get('key'), meta.text) for meta in child.getchildren()) else: vol[tag] = xml_to_json(child) return vol def list_volumes(self, params=None): """List all the volumes created.""" url = 'volumes' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url, self.headers) body = etree.fromstring(body) volumes = [] if body is not None: volumes += [self._parse_volume(vol) for vol in list(body)] return resp, volumes def list_volumes_with_detail(self, params=None): """List all the details of volumes.""" url = 'volumes/detail' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url, self.headers) body = etree.fromstring(body) volumes = [] if body is not None: volumes += [self._parse_volume(vol) for vol in list(body)] return resp, volumes def get_volume(self, volume_id): """Returns the details of a single volume.""" url = "volumes/%s" % str(volume_id) resp, body = self.get(url, self.headers) body = etree.fromstring(body) return resp, self._parse_volume(body) def create_volume(self, size, **kwargs): """Creates a new Volume. :param size: Size of volume in GB. (Required) :param display_name: Optional Volume Name. :param metadata: An optional dictionary of values for metadata. :param volume_type: Optional Name of volume_type for the volume :param snapshot_id: When specified the volume is created from this snapshot :param imageRef: When specified the volume is created from this image """ #NOTE(afazekas): it should use a volume namespace volume = Element("volume", xmlns=XMLNS_11, size=size) if 'metadata' in kwargs: _metadata = Element('metadata') volume.append(_metadata) for key, value in kwargs['metadata'].items(): meta = Element('meta') meta.add_attr('key', key) meta.append(Text(value)) _metadata.append(meta) attr_to_add = kwargs.copy() del attr_to_add['metadata'] else: attr_to_add = kwargs for key, value in attr_to_add.items(): volume.add_attr(key, value) resp, body = self.post('volumes', str(Document(volume)), self.headers) body = xml_to_json(etree.fromstring(body)) return resp, body def delete_volume(self, volume_id): """Deletes the Specified Volume.""" return self.delete("volumes/%s" % str(volume_id)) def wait_for_volume_status(self, volume_id, status): """Waits for a Volume to reach a given status.""" resp, body = self.get_volume(volume_id) volume_status = body['status'] start = int(time.time()) while volume_status != status: time.sleep(self.build_interval) resp, body = self.get_volume(volume_id) volume_status = body['status'] if volume_status == 'error': raise exceptions.VolumeBuildErrorException(volume_id=volume_id) if int(time.time()) - start >= self.build_timeout: message = 'Volume %s failed to reach %s status within '\ 'the required time (%s s).' % (volume_id, status, self.build_timeout) raise exceptions.TimeoutException(message) def is_resource_deleted(self, id): try: self.get_volume(id) except exceptions.NotFound: return True return False tempest-2013.2.a1291.g23a1b4f/tempest/services/volume/xml/__init__.py0000664000175000017500000000000012161375672025170 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/volume/__init__.py0000664000175000017500000000000012161375672024370 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/volume/json/0000775000175000017500000000000012161375700023232 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/volume/json/snapshots_client.py0000664000175000017500000001105612161375672027177 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # 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 json import time import urllib from tempest.common import log as logging from tempest.common.rest_client import RestClient from tempest import exceptions LOG = logging.getLogger(__name__) class SnapshotsClientJSON(RestClient): """Client class to send CRUD Volume API requests.""" def __init__(self, config, username, password, auth_url, tenant_name=None): super(SnapshotsClientJSON, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.volume.catalog_type self.build_interval = self.config.volume.build_interval self.build_timeout = self.config.volume.build_timeout def list_snapshots(self, params=None): """List all the snapshot.""" url = 'snapshots' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body['snapshots'] def list_snapshot_with_detail(self, params=None): """List the details of all snapshots.""" url = 'snapshots/detail' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body['snapshots'] def get_snapshot(self, snapshot_id): """Returns the details of a single snapshot.""" url = "snapshots/%s" % str(snapshot_id) resp, body = self.get(url) body = json.loads(body) return resp, body['snapshot'] def create_snapshot(self, volume_id, **kwargs): """ Creates a new snapshot. volume_id(Required): id of the volume. force: Create a snapshot even if the volume attached (Default=False) display_name: Optional snapshot Name. display_description: User friendly snapshot description. """ post_body = {'volume_id': volume_id} post_body.update(kwargs) post_body = json.dumps({'snapshot': post_body}) resp, body = self.post('snapshots', post_body, self.headers) body = json.loads(body) return resp, body['snapshot'] #NOTE(afazekas): just for the wait function def _get_snapshot_status(self, snapshot_id): resp, body = self.get_snapshot(snapshot_id) status = body['status'] #NOTE(afazekas): snapshot can reach an "error" # state in a "normal" lifecycle if (status == 'error'): raise exceptions.SnapshotBuildErrorException( snapshot_id=snapshot_id) return status #NOTE(afazkas): Wait reinvented again. It is not in the correct layer def wait_for_snapshot_status(self, snapshot_id, status): """Waits for a Snapshot to reach a given status.""" start_time = time.time() old_value = value = self._get_snapshot_status(snapshot_id) while True: dtime = time.time() - start_time time.sleep(self.build_interval) if value != old_value: LOG.info('Value transition from "%s" to "%s"' 'in %d second(s).', old_value, value, dtime) if (value == status): return value if dtime > self.build_timeout: message = ('Time Limit Exceeded! (%ds)' 'while waiting for %s, ' 'but we got %s.' % (self.build_timeout, status, value)) raise exceptions.TimeoutException(message) time.sleep(self.build_interval) old_value = value value = self._get_snapshot_status(snapshot_id) def delete_snapshot(self, snapshot_id): """Delete Snapshot.""" return self.delete("snapshots/%s" % str(snapshot_id)) def is_resource_deleted(self, id): try: self.get_snapshot(id) except exceptions.NotFound: return True return False tempest-2013.2.a1291.g23a1b4f/tempest/services/volume/json/admin/0000775000175000017500000000000012161375700024322 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/volume/json/admin/volume_types_client.py0000664000175000017500000001134112161375672030775 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 json import urllib from tempest.common.rest_client import RestClient class VolumeTypesClientJSON(RestClient): """ Client class to send CRUD Volume Types API requests to a Cinder endpoint """ def __init__(self, config, username, password, auth_url, tenant_name=None): super(VolumeTypesClientJSON, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.volume.catalog_type self.build_interval = self.config.volume.build_interval self.build_timeout = self.config.volume.build_timeout def list_volume_types(self, params=None): """List all the volume_types created.""" url = 'types' if params is not None: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body['volume_types'] def get_volume_type(self, volume_id): """Returns the details of a single volume_type.""" url = "types/%s" % str(volume_id) resp, body = self.get(url) body = json.loads(body) return resp, body['volume_type'] def create_volume_type(self, name, **kwargs): """ Creates a new Volume_type. name(Required): Name of volume_type. Following optional keyword arguments are accepted: extra_specs: A dictionary of values to be used as extra_specs. """ post_body = { 'name': name, 'extra_specs': kwargs.get('extra_specs'), } post_body = json.dumps({'volume_type': post_body}) resp, body = self.post('types', post_body, self.headers) body = json.loads(body) return resp, body['volume_type'] def delete_volume_type(self, volume_id): """Deletes the Specified Volume_type.""" return self.delete("types/%s" % str(volume_id)) def list_volume_types_extra_specs(self, vol_type_id, params=None): """List all the volume_types extra specs created.""" url = 'types/%s/extra_specs' % str(vol_type_id) if params is not None: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body['extra_specs'] def get_volume_type_extra_specs(self, vol_type_id, extra_spec_name): """Returns the details of a single volume_type extra spec.""" url = "types/%s/extra_specs/%s" % (str(vol_type_id), str(extra_spec_name)) resp, body = self.get(url) body = json.loads(body) return resp, body def create_volume_type_extra_specs(self, vol_type_id, extra_spec): """ Creates a new Volume_type extra spec. vol_type_id: Id of volume_type. extra_specs: A dictionary of values to be used as extra_specs. """ url = "types/%s/extra_specs" % str(vol_type_id) post_body = json.dumps({'extra_specs': extra_spec}) resp, body = self.post(url, post_body, self.headers) body = json.loads(body) return resp, body['extra_specs'] def delete_volume_type_extra_specs(self, vol_id, extra_spec_name): """Deletes the Specified Volume_type extra spec.""" return self.delete("types/%s/extra_specs/%s" % ((str(vol_id)), str(extra_spec_name))) def update_volume_type_extra_specs(self, vol_type_id, extra_spec_name, extra_spec): """ Update a volume_type extra spec. vol_type_id: Id of volume_type. extra_spec_name: Name of the extra spec to be updated. extra_spec: A dictionary of with key as extra_spec_name and the updated value. """ url = "types/%s/extra_specs/%s" % (str(vol_type_id), str(extra_spec_name)) put_body = json.dumps(extra_spec) resp, body = self.put(url, put_body, self.headers) body = json.loads(body) return resp, body tempest-2013.2.a1291.g23a1b4f/tempest/services/volume/json/admin/__init__.py0000664000175000017500000000000012161375672026431 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/volume/json/volumes_client.py0000664000175000017500000001147212161375672026651 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 json import time import urllib from tempest.common.rest_client import RestClient from tempest import exceptions class VolumesClientJSON(RestClient): """ Client class to send CRUD Volume API requests to a Cinder endpoint """ def __init__(self, config, username, password, auth_url, tenant_name=None): super(VolumesClientJSON, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.volume.catalog_type self.build_interval = self.config.volume.build_interval self.build_timeout = self.config.volume.build_timeout def list_volumes(self, params=None): """List all the volumes created.""" url = 'volumes' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body['volumes'] def list_volumes_with_detail(self, params=None): """List the details of all volumes.""" url = 'volumes/detail' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body['volumes'] def get_volume(self, volume_id): """Returns the details of a single volume.""" url = "volumes/%s" % str(volume_id) resp, body = self.get(url) body = json.loads(body) return resp, body['volume'] def create_volume(self, size, **kwargs): """ Creates a new Volume. size(Required): Size of volume in GB. Following optional keyword arguments are accepted: display_name: Optional Volume Name. metadata: A dictionary of values to be used as metadata. volume_type: Optional Name of volume_type for the volume snapshot_id: When specified the volume is created from this snapshot imageRef: When specified the volume is created from this image """ post_body = {'size': size} post_body.update(kwargs) post_body = json.dumps({'volume': post_body}) resp, body = self.post('volumes', post_body, self.headers) body = json.loads(body) return resp, body['volume'] def delete_volume(self, volume_id): """Deletes the Specified Volume.""" return self.delete("volumes/%s" % str(volume_id)) def attach_volume(self, volume_id, instance_uuid, mountpoint): """Attaches a volume to a given instance on a given mountpoint.""" post_body = { 'instance_uuid': instance_uuid, 'mountpoint': mountpoint, } post_body = json.dumps({'os-attach': post_body}) url = 'volumes/%s/action' % (volume_id) resp, body = self.post(url, post_body, self.headers) return resp, body def detach_volume(self, volume_id): """Detaches a volume from an instance.""" post_body = {} post_body = json.dumps({'os-detach': post_body}) url = 'volumes/%s/action' % (volume_id) resp, body = self.post(url, post_body, self.headers) return resp, body def wait_for_volume_status(self, volume_id, status): """Waits for a Volume to reach a given status.""" resp, body = self.get_volume(volume_id) volume_name = body['display_name'] volume_status = body['status'] start = int(time.time()) while volume_status != status: time.sleep(self.build_interval) resp, body = self.get_volume(volume_id) volume_status = body['status'] if volume_status == 'error': raise exceptions.VolumeBuildErrorException(volume_id=volume_id) if int(time.time()) - start >= self.build_timeout: message = ('Volume %s failed to reach %s status within ' 'the required time (%s s).' % (volume_name, status, self.build_timeout)) raise exceptions.TimeoutException(message) def is_resource_deleted(self, id): try: self.get_volume(id) except exceptions.NotFound: return True return False tempest-2013.2.a1291.g23a1b4f/tempest/services/volume/json/__init__.py0000664000175000017500000000000012161375672025341 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/botoclients.py0000664000175000017500000002232012161375672023660 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 ConfigParser import contextlib import types import urlparse from tempest import exceptions import boto import boto.ec2 import boto.s3.connection class BotoClientBase(object): ALLOWED_METHODS = set() def __init__(self, config, username=None, password=None, auth_url=None, tenant_name=None, *args, **kwargs): self.connection_timeout = str(config.boto.http_socket_timeout) self.num_retries = str(config.boto.num_retries) self.build_timeout = config.boto.build_timeout self.ks_cred = {"username": username, "password": password, "auth_url": auth_url, "tenant_name": tenant_name} def _keystone_aws_get(self): import keystoneclient.v2_0.client keystone = keystoneclient.v2_0.client.Client(**self.ks_cred) ec2_cred_list = keystone.ec2.list(keystone.auth_user_id) ec2_cred = None for cred in ec2_cred_list: if cred.tenant_id == keystone.auth_tenant_id: ec2_cred = cred break else: ec2_cred = keystone.ec2.create(keystone.auth_user_id, keystone.auth_tenant_id) if not all((ec2_cred, ec2_cred.access, ec2_cred.secret)): raise exceptions.NotFound("Unable to get access and secret keys") return ec2_cred def _config_boto_timeout(self, timeout, retries): try: boto.config.add_section("Boto") except ConfigParser.DuplicateSectionError: pass boto.config.set("Boto", "http_socket_timeout", timeout) boto.config.set("Boto", "num_retries", retries) def __getattr__(self, name): """Automatically creates methods for the allowed methods set.""" if name in self.ALLOWED_METHODS: def func(self, *args, **kwargs): with contextlib.closing(self.get_connection()) as conn: return getattr(conn, name)(*args, **kwargs) func.__name__ = name setattr(self, name, types.MethodType(func, self, self.__class__)) setattr(self.__class__, name, types.MethodType(func, None, self.__class__)) return getattr(self, name) else: raise AttributeError(name) def get_connection(self): self._config_boto_timeout(self.connection_timeout, self.num_retries) if not all((self.connection_data["aws_access_key_id"], self.connection_data["aws_secret_access_key"])): if all(self.ks_cred.itervalues()): ec2_cred = self._keystone_aws_get() self.connection_data["aws_access_key_id"] = \ ec2_cred.access self.connection_data["aws_secret_access_key"] = \ ec2_cred.secret else: raise exceptions.InvalidConfiguration( "Unable to get access and secret keys") return self.connect_method(**self.connection_data) class APIClientEC2(BotoClientBase): def connect_method(self, *args, **kwargs): return boto.connect_ec2(*args, **kwargs) def __init__(self, config, *args, **kwargs): super(APIClientEC2, self).__init__(config, *args, **kwargs) aws_access = config.boto.aws_access aws_secret = config.boto.aws_secret purl = urlparse.urlparse(config.boto.ec2_url) region = boto.ec2.regioninfo.RegionInfo(name=config.identity.region, endpoint=purl.hostname) port = purl.port if port is None: if purl.scheme is not "https": port = 80 else: port = 443 else: port = int(port) self.connection_data = {"aws_access_key_id": aws_access, "aws_secret_access_key": aws_secret, "is_secure": purl.scheme == "https", "region": region, "host": purl.hostname, "port": port, "path": purl.path} ALLOWED_METHODS = set(('create_key_pair', 'get_key_pair', 'delete_key_pair', 'import_key_pair', 'get_all_key_pairs', 'create_image', 'get_image', 'register_image', 'deregister_image', 'get_all_images', 'get_image_attribute', 'modify_image_attribute', 'reset_image_attribute', 'get_all_kernels', 'create_volume', 'delete_volume', 'get_all_volume_status', 'get_all_volumes', 'get_volume_attribute', 'modify_volume_attribute' 'bundle_instance', 'cancel_spot_instance_requests', 'confirm_product_instanc', 'get_all_instance_status', 'get_all_instances', 'get_all_reserved_instances', 'get_all_spot_instance_requests', 'get_instance_attribute', 'monitor_instance', 'monitor_instances', 'unmonitor_instance', 'unmonitor_instances', 'purchase_reserved_instance_offering', 'reboot_instances', 'request_spot_instances', 'reset_instance_attribute', 'run_instances', 'start_instances', 'stop_instances', 'terminate_instances', 'attach_network_interface', 'attach_volume', 'detach_network_interface', 'detach_volume', 'get_console_output', 'delete_network_interface', 'create_subnet', 'create_network_interface', 'delete_subnet', 'get_all_network_interfaces', 'allocate_address', 'associate_address', 'disassociate_address', 'get_all_addresses', 'release_address', 'create_snapshot', 'delete_snapshot', 'get_all_snapshots', 'get_snapshot_attribute', 'modify_snapshot_attribute', 'reset_snapshot_attribute', 'trim_snapshots', 'get_all_regions', 'get_all_zones', 'get_all_security_groups', 'create_security_group', 'delete_security_group', 'authorize_security_group', 'authorize_security_group_egress', 'revoke_security_group', 'revoke_security_group_egress')) def get_good_zone(self): """ :rtype: BaseString :return: Returns with the first available zone name """ for zone in self.get_all_zones(): #NOTE(afazekas): zone.region_name was None if (zone.state == "available" and zone.region.name == self.connection_data["region"].name): return zone.name else: raise IndexError("Don't have a good zone") class ObjectClientS3(BotoClientBase): def connect_method(self, *args, **kwargs): return boto.connect_s3(*args, **kwargs) def __init__(self, config, *args, **kwargs): super(ObjectClientS3, self).__init__(config, *args, **kwargs) aws_access = config.boto.aws_access aws_secret = config.boto.aws_secret purl = urlparse.urlparse(config.boto.s3_url) port = purl.port if port is None: if purl.scheme is not "https": port = 80 else: port = 443 else: port = int(port) self.connection_data = {"aws_access_key_id": aws_access, "aws_secret_access_key": aws_secret, "is_secure": purl.scheme == "https", "host": purl.hostname, "port": port, "calling_format": boto.s3.connection. OrdinaryCallingFormat()} ALLOWED_METHODS = set(('create_bucket', 'delete_bucket', 'generate_url', 'get_all_buckets', 'get_bucket', 'delete_key', 'lookup')) tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/0000775000175000017500000000000012161375700022426 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/xml/0000775000175000017500000000000012161375700023226 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/xml/interfaces_client.py0000664000175000017500000001043112161375672027270 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # 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 time from lxml import etree from tempest.common.rest_client import RestClientXML from tempest import exceptions from tempest.services.compute.xml.common import Document from tempest.services.compute.xml.common import Element from tempest.services.compute.xml.common import Text from tempest.services.compute.xml.common import xml_to_json class InterfacesClientXML(RestClientXML): def __init__(self, config, username, password, auth_url, tenant_name=None): super(InterfacesClientXML, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type def _process_xml_interface(self, node): iface = xml_to_json(node) # NOTE(danms): if multiple addresses per interface is ever required, # xml_to_json will need to be fixed or replaced in this case iface['fixed_ips'] = [dict(iface['fixed_ips']['fixed_ip'].items())] return iface def list_interfaces(self, server): resp, body = self.get('servers/%s/os-interface' % server, self.headers) node = etree.fromstring(body) interfaces = [self._process_xml_interface(x) for x in node.getchildren()] return resp, interfaces def create_interface(self, server, port_id=None, network_id=None, fixed_ip=None): doc = Document() iface = Element('interfaceAttachment') if port_id: _port_id = Element('port_id') _port_id.append(Text(port_id)) iface.append(_port_id) if network_id: _network_id = Element('net_id') _network_id.append(Text(network_id)) iface.append(_network_id) if fixed_ip: _fixed_ips = Element('fixed_ips') _fixed_ip = Element('fixed_ip') _ip_address = Element('ip_address') _ip_address.append(Text(fixed_ip)) _fixed_ip.append(_ip_address) _fixed_ips.append(_fixed_ip) iface.append(_fixed_ips) doc.append(iface) resp, body = self.post('servers/%s/os-interface' % server, headers=self.headers, body=str(doc)) body = self._process_xml_interface(etree.fromstring(body)) return resp, body def show_interface(self, server, port_id): resp, body = self.get('servers/%s/os-interface/%s' % (server, port_id), self.headers) body = self._process_xml_interface(etree.fromstring(body)) return resp, body def delete_interface(self, server, port_id): resp, body = self.delete('servers/%s/os-interface/%s' % (server, port_id)) return resp, body def wait_for_interface_status(self, server, port_id, status): """Waits for a interface to reach a given status.""" resp, body = self.show_interface(server, port_id) interface_status = body['port_state'] start = int(time.time()) while(interface_status != status): time.sleep(self.build_interval) resp, body = self.show_interface(server, port_id) interface_status = body['port_state'] timed_out = int(time.time()) - start >= self.build_timeout if interface_status != status and timed_out: message = ('Interface %s failed to reach %s status within ' 'the required time (%s s).' % (port_id, status, self.build_timeout)) raise exceptions.TimeoutException(message) return resp, body tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/xml/aggregates_client.py0000664000175000017500000001001312161375672027252 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 NEC Corporation. # 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 lxml import etree from tempest.common.rest_client import RestClientXML from tempest import exceptions from tempest.services.compute.xml.common import Document from tempest.services.compute.xml.common import Element from tempest.services.compute.xml.common import xml_to_json class AggregatesClientXML(RestClientXML): def __init__(self, config, username, password, auth_url, tenant_name=None): super(AggregatesClientXML, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type def _format_aggregate(self, g): agg = xml_to_json(g) aggregate = {} for key, value in agg.items(): if key == 'hosts': aggregate['hosts'] = [] for k, v in value.items(): aggregate['hosts'].append(v) elif key == 'availability_zone': aggregate[key] = None if value == 'None' else value else: aggregate[key] = value return aggregate def _parse_array(self, node): return [self._format_aggregate(x) for x in node] def list_aggregates(self): """Get aggregate list.""" resp, body = self.get("os-aggregates", self.headers) aggregates = self._parse_array(etree.fromstring(body)) return resp, aggregates def get_aggregate(self, aggregate_id): """Get details of the given aggregate.""" resp, body = self.get("os-aggregates/%s" % str(aggregate_id), self.headers) aggregate = self._format_aggregate(etree.fromstring(body)) return resp, aggregate def create_aggregate(self, name, availability_zone=None): """Creates a new aggregate.""" post_body = Element("aggregate", name=name, availability_zone=availability_zone) resp, body = self.post('os-aggregates', str(Document(post_body)), self.headers) aggregate = self._format_aggregate(etree.fromstring(body)) return resp, aggregate def delete_aggregate(self, aggregate_id): """Deletes the given aggregate.""" return self.delete("os-aggregates/%s" % str(aggregate_id), self.headers) def is_resource_deleted(self, id): try: self.get_aggregate(id) except exceptions.NotFound: return True return False def add_host(self, aggregate_id, host): """Adds a host to the given aggregate.""" post_body = Element("add_host", host=host) resp, body = self.post('os-aggregates/%s/action' % aggregate_id, str(Document(post_body)), self.headers) aggregate = self._format_aggregate(etree.fromstring(body)) return resp, aggregate def remove_host(self, aggregate_id, host): """Removes a host from the given aggregate.""" post_body = Element("remove_host", host=host) resp, body = self.post('os-aggregates/%s/action' % aggregate_id, str(Document(post_body)), self.headers) aggregate = self._format_aggregate(etree.fromstring(body)) return resp, aggregate tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/xml/extensions_client.py0000664000175000017500000000322612161375672027350 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 lxml import etree from tempest.common.rest_client import RestClientXML from tempest.services.compute.xml.common import xml_to_json class ExtensionsClientXML(RestClientXML): def __init__(self, config, username, password, auth_url, tenant_name=None): super(ExtensionsClientXML, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type def _parse_array(self, node): array = [] for child in node: array.append(xml_to_json(child)) return array def list_extensions(self): url = 'extensions' resp, body = self.get(url, self.headers) body = self._parse_array(etree.fromstring(body)) return resp, {'extensions': body} def is_enabled(self, extension): _, extensions = self.list_extensions() exts = extensions['extensions'] return any([e for e in exts if e['name'] == extension]) tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/xml/common.py0000664000175000017500000000657612161375672025116 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # # Copyright 2012 IBM Corp. # 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. XMLNS_11 = "http://docs.openstack.org/compute/api/v1.1" # NOTE(danms): This is just a silly implementation to help make generating # XML faster for prototyping. Could be replaced with proper etree gorp # if desired class Element(object): def __init__(self, element_name, *args, **kwargs): self.element_name = element_name self._attrs = kwargs self._elements = list(args) def add_attr(self, name, value): self._attrs[name] = value def append(self, element): self._elements.append(element) def __str__(self): args = " ".join(['%s="%s"' % (k, v) for k, v in self._attrs.items()]) string = '<%s %s' % (self.element_name, args) if not self._elements: string += '/>' return string string += '>' for element in self._elements: string += str(element) string += '' % self.element_name return string def __getitem__(self, name): for element in self._elements: if element.element_name == name: return element raise KeyError("No such element `%s'" % name) def __getattr__(self, name): if name in self._attrs: return self._attrs[name] return object.__getattr__(self, name) def attributes(self): return self._attrs.items() def children(self): return self._elements class Document(Element): def __init__(self, *args, **kwargs): if 'version' not in kwargs: kwargs['version'] = '1.0' if 'encoding' not in kwargs: kwargs['encoding'] = 'UTF-8' Element.__init__(self, '?xml', *args, **kwargs) def __str__(self): args = " ".join(['%s="%s"' % (k, v) for k, v in self._attrs.items()]) string = '\n' % args for element in self._elements: string += str(element) return string class Text(Element): def __init__(self, content=""): Element.__init__(self, None) self.__content = content def __str__(self): return self.__content def xml_to_json(node): """This does a really braindead conversion of an XML tree to something that looks like a json dump. In cases where the XML and json structures are the same, then this "just works". In others, it requires a little hand-editing of the result. """ json = {} for attr in node.keys(): if not attr.startswith("xmlns"): json[attr] = node.get(attr) if not node.getchildren(): return node.text or json for child in node.getchildren(): tag = child.tag if tag.startswith("{"): ns, tag = tag.split("}", 1) json[tag] = xml_to_json(child) return json tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/xml/services_client.py0000664000175000017500000000250712161375672026775 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 NEC Corporation # 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 lxml import etree from tempest.common.rest_client import RestClientXML from tempest.services.compute.xml.common import xml_to_json class ServicesClientXML(RestClientXML): def __init__(self, config, username, password, auth_url, tenant_name=None): super(ServicesClientXML, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type def list_services(self): resp, body = self.get("os-services", self.headers) node = etree.fromstring(body) body = [xml_to_json(x) for x in node.getchildren()] return resp, body tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/xml/floating_ips_client.py0000664000175000017500000001006412161375672027625 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # # Copyright 2012 IBM Corp. # 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 lxml import etree import urllib from tempest.common.rest_client import RestClientXML from tempest import exceptions from tempest.services.compute.xml.common import Document from tempest.services.compute.xml.common import Element from tempest.services.compute.xml.common import Text from tempest.services.compute.xml.common import xml_to_json class FloatingIPsClientXML(RestClientXML): def __init__(self, config, username, password, auth_url, tenant_name=None): super(FloatingIPsClientXML, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type def _parse_array(self, node): array = [] for child in node.getchildren(): array.append(xml_to_json(child)) return array def _parse_floating_ip(self, body): json = xml_to_json(body) return json def list_floating_ips(self, params=None): """Returns a list of all floating IPs filtered by any parameters.""" url = 'os-floating-ips' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url, self.headers) body = self._parse_array(etree.fromstring(body)) return resp, body def get_floating_ip_details(self, floating_ip_id): """Get the details of a floating IP.""" url = "os-floating-ips/%s" % str(floating_ip_id) resp, body = self.get(url, self.headers) body = self._parse_floating_ip(etree.fromstring(body)) if resp.status == 404: raise exceptions.NotFound(body) return resp, body def create_floating_ip(self, pool_name=None): """Allocate a floating IP to the project.""" url = 'os-floating-ips' if pool_name: doc = Document() pool = Element("pool") pool.append(Text(pool_name)) doc.append(pool) resp, body = self.post(url, str(doc), self.headers) else: resp, body = self.post(url, None, self.headers) body = self._parse_floating_ip(etree.fromstring(body)) return resp, body def delete_floating_ip(self, floating_ip_id): """Deletes the provided floating IP from the project.""" url = "os-floating-ips/%s" % str(floating_ip_id) resp, body = self.delete(url, self.headers) return resp, body def associate_floating_ip_to_server(self, floating_ip, server_id): """Associate the provided floating IP to a specific server.""" url = "servers/%s/action" % str(server_id) doc = Document() server = Element("addFloatingIp") doc.append(server) server.add_attr("address", floating_ip) resp, body = self.post(url, str(doc), self.headers) return resp, body def disassociate_floating_ip_from_server(self, floating_ip, server_id): """Disassociate the provided floating IP from a specific server.""" url = "servers/%s/action" % str(server_id) doc = Document() server = Element("removeFloatingIp") doc.append(server) server.add_attr("address", floating_ip) resp, body = self.post(url, str(doc), self.headers) return resp, body def is_resource_deleted(self, id): try: self.get_floating_ip_details(id) except exceptions.NotFound: return True return False tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/xml/servers_client.py0000664000175000017500000005262612161375672026652 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # # Copyright 2012 IBM Corp. # Copyright 2013 Hewlett-Packard Development Company, L.P. # 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 time import urllib from lxml import etree from tempest.common import log as logging from tempest.common.rest_client import RestClientXML from tempest import exceptions from tempest.services.compute.xml.common import Document from tempest.services.compute.xml.common import Element from tempest.services.compute.xml.common import Text from tempest.services.compute.xml.common import xml_to_json from tempest.services.compute.xml.common import XMLNS_11 LOG = logging.getLogger(__name__) def _translate_ip_xml_json(ip): """ Convert the address version to int. """ ip = dict(ip) version = ip.get('version') if version: ip['version'] = int(version) # NOTE(maurosr): just a fast way to avoid the xml version with the # expanded xml namespace. type_ns_prefix = ('{http://docs.openstack.org/compute/ext/extended_ips/' 'api/v1.1}type') if type_ns_prefix in ip: ip['OS-EXT-IPS:type'] = ip[type_ns_prefix] ip.pop(type_ns_prefix) return ip def _translate_network_xml_to_json(network): return [_translate_ip_xml_json(ip.attrib) for ip in network.findall('{%s}ip' % XMLNS_11)] def _translate_addresses_xml_to_json(xml_addresses): return dict((network.attrib['id'], _translate_network_xml_to_json(network)) for network in xml_addresses.findall('{%s}network' % XMLNS_11)) def _translate_server_xml_to_json(xml_dom): """Convert server XML to server JSON. The addresses collection does not convert well by the dumb xml_to_json. This method does some pre and post-processing to deal with that. Translate XML addresses subtree to JSON. Having xml_doc similar to the _translate_server_xml_to_json(etree.fromstring(xml_doc)) should produce something like {'addresses': {'bar_novanetwork': [{'addr': '10.1.0.4', 'version': 4}, {'addr': '2001:0:0:1:2:3:4:5', 'version': 6}], 'foo_novanetwork': [{'addr': '192.168.0.4', 'version': 4}]}} """ nsmap = {'api': XMLNS_11} addresses = xml_dom.xpath('/api:server/api:addresses', namespaces=nsmap) if addresses: if len(addresses) > 1: raise ValueError('Expected only single `addresses` element.') json_addresses = _translate_addresses_xml_to_json(addresses[0]) json = xml_to_json(xml_dom) json['addresses'] = json_addresses else: json = xml_to_json(xml_dom) diskConfig = '{http://docs.openstack.org/compute/ext/disk_config/api/v1.1'\ '}diskConfig' if diskConfig in json: json['OS-DCF:diskConfig'] = json[diskConfig] del json[diskConfig] return json class ServersClientXML(RestClientXML): def __init__(self, config, username, password, auth_url, tenant_name=None, auth_version='v2'): super(ServersClientXML, self).__init__(config, username, password, auth_url, tenant_name, auth_version=auth_version) self.service = self.config.compute.catalog_type def _parse_key_value(self, node): """Parse value data into {'key': 'value'}.""" data = {} for node in node.getchildren(): data[node.get('key')] = node.text return data def _parse_links(self, node, json): del json['link'] json['links'] = [] for linknode in node.findall('{http://www.w3.org/2005/Atom}link'): json['links'].append(xml_to_json(linknode)) def _parse_server(self, body): json = _translate_server_xml_to_json(body) if 'metadata' in json and json['metadata']: # NOTE(danms): if there was metadata, we need to re-parse # that as a special type metadata_tag = body.find('{%s}metadata' % XMLNS_11) json["metadata"] = self._parse_key_value(metadata_tag) if 'link' in json: self._parse_links(body, json) for sub in ['image', 'flavor']: if sub in json and 'link' in json[sub]: self._parse_links(body, json[sub]) return json def _parse_xml_virtual_interfaces(self, xml_dom): """ Return server's virtual interfaces XML as JSON. """ data = {"virtual_interfaces": []} for iface in xml_dom.getchildren(): data["virtual_interfaces"].append( {"id": iface.get("id"), "mac_address": iface.get("mac_address")}) return data def get_server(self, server_id): """Returns the details of an existing server.""" resp, body = self.get("servers/%s" % str(server_id), self.headers) server = self._parse_server(etree.fromstring(body)) return resp, server def delete_server(self, server_id): """Deletes the given server.""" return self.delete("servers/%s" % str(server_id)) def _parse_array(self, node): array = [] for child in node.getchildren(): array.append(xml_to_json(child)) return array def list_servers(self, params=None): url = 'servers' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url, self.headers) servers = self._parse_array(etree.fromstring(body)) return resp, {"servers": servers} def list_servers_with_detail(self, params=None): url = 'servers/detail' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url, self.headers) servers = self._parse_array(etree.fromstring(body)) return resp, {"servers": servers} def update_server(self, server_id, name=None, meta=None, accessIPv4=None, accessIPv6=None): doc = Document() server = Element("server") doc.append(server) if name is not None: server.add_attr("name", name) if accessIPv4 is not None: server.add_attr("accessIPv4", accessIPv4) if accessIPv6 is not None: server.add_attr("accessIPv6", accessIPv6) if meta is not None: metadata = Element("metadata") server.append(metadata) for k, v in meta: meta = Element("meta", key=k) meta.append(Text(v)) metadata.append(meta) resp, body = self.put('servers/%s' % str(server_id), str(doc), self.headers) return resp, xml_to_json(etree.fromstring(body)) def create_server(self, name, image_ref, flavor_ref, **kwargs): """ Creates an instance of a server. name (Required): The name of the server. image_ref (Required): Reference to the image used to build the server. flavor_ref (Required): The flavor used to build the server. Following optional keyword arguments are accepted: adminPass: Sets the initial root password. key_name: Key name of keypair that was created earlier. meta: A dictionary of values to be used as metadata. personality: A list of dictionaries for files to be injected into the server. security_groups: A list of security group dicts. networks: A list of network dicts with UUID and fixed_ip. user_data: User data for instance. availability_zone: Availability zone in which to launch instance. accessIPv4: The IPv4 access address for the server. accessIPv6: The IPv6 access address for the server. min_count: Count of minimum number of instances to launch. max_count: Count of maximum number of instances to launch. disk_config: Determines if user or admin controls disk configuration. """ server = Element("server", xmlns=XMLNS_11, imageRef=image_ref, flavorRef=flavor_ref, name=name) for attr in ["adminPass", "accessIPv4", "accessIPv6", "key_name", "user_data", "availability_zone", "min_count", "max_count", "return_reservation_id"]: if attr in kwargs: server.add_attr(attr, kwargs[attr]) if 'disk_config' in kwargs: server.add_attr('xmlns:OS-DCF', "http://docs.openstack.org/" "compute/ext/disk_config/api/v1.1") server.add_attr('OS-DCF:diskConfig', kwargs['disk_config']) if 'security_groups' in kwargs: secgroups = Element("security_groups") server.append(secgroups) for secgroup in kwargs['security_groups']: s = Element("security_group", name=secgroup['name']) secgroups.append(s) if 'networks' in kwargs: networks = Element("networks") server.append(networks) for network in kwargs['networks']: s = Element("network", uuid=network['uuid'], fixed_ip=network['fixed_ip']) networks.append(s) if 'meta' in kwargs: metadata = Element("metadata") server.append(metadata) for k, v in kwargs['meta'].items(): meta = Element("meta", key=k) meta.append(Text(v)) metadata.append(meta) if 'personality' in kwargs: personality = Element('personality') server.append(personality) for k in kwargs['personality']: temp = Element('file', path=k['path']) temp.append(Text(k['contents'])) personality.append(temp) resp, body = self.post('servers', str(Document(server)), self.headers) server = self._parse_server(etree.fromstring(body)) return resp, server def wait_for_server_status(self, server_id, status): """Waits for a server to reach a given status.""" resp, body = self.get_server(server_id) server_status = body['status'] start = int(time.time()) while(server_status != status): time.sleep(self.build_interval) resp, body = self.get_server(server_id) server_status = body['status'] if server_status == 'ERROR': raise exceptions.BuildErrorException(server_id=server_id) timed_out = int(time.time()) - start >= self.build_timeout if server_status != status and timed_out: message = ('Server %s failed to reach %s status within the ' 'required time (%s s).' % (server_id, status, self.build_timeout)) message += ' Current status: %s.' % server_status raise exceptions.TimeoutException(message) def wait_for_server_termination(self, server_id, ignore_error=False): """Waits for server to reach termination.""" start_time = int(time.time()) while True: try: resp, body = self.get_server(server_id) except exceptions.NotFound: return server_status = body['status'] if server_status == 'ERROR' and not ignore_error: raise exceptions.BuildErrorException if int(time.time()) - start_time >= self.build_timeout: raise exceptions.TimeoutException time.sleep(self.build_interval) def _parse_network(self, node): addrs = [] for child in node.getchildren(): addrs.append({'version': int(child.get('version')), 'addr': child.get('version')}) return {node.get('id'): addrs} def list_addresses(self, server_id): """Lists all addresses for a server.""" resp, body = self.get("servers/%s/ips" % str(server_id), self.headers) networks = {} xml_list = etree.fromstring(body) for child in xml_list.getchildren(): network = self._parse_network(child) networks.update(**network) return resp, networks def list_addresses_by_network(self, server_id, network_id): """Lists all addresses of a specific network type for a server.""" resp, body = self.get("servers/%s/ips/%s" % (str(server_id), network_id), self.headers) network = self._parse_network(etree.fromstring(body)) return resp, network def action(self, server_id, action_name, response_key, **kwargs): if 'xmlns' not in kwargs: kwargs['xmlns'] = XMLNS_11 doc = Document((Element(action_name, **kwargs))) resp, body = self.post("servers/%s/action" % server_id, str(doc), self.headers) if response_key is not None: body = xml_to_json(etree.fromstring(body)) return resp, body def change_password(self, server_id, password): return self.action(server_id, "changePassword", None, adminPass=password) def reboot(self, server_id, reboot_type): return self.action(server_id, "reboot", None, type=reboot_type) def rebuild(self, server_id, image_ref, **kwargs): kwargs['imageRef'] = image_ref if 'disk_config' in kwargs: kwargs['OS-DCF:diskConfig'] = kwargs['disk_config'] del kwargs['disk_config'] kwargs['xmlns:OS-DCF'] = "http://docs.openstack.org/"\ "compute/ext/disk_config/api/v1.1" kwargs['xmlns:atom'] = "http://www.w3.org/2005/Atom" if 'xmlns' not in kwargs: kwargs['xmlns'] = XMLNS_11 attrs = kwargs.copy() if 'metadata' in attrs: del attrs['metadata'] rebuild = Element("rebuild", **attrs) if 'metadata' in kwargs: metadata = Element("metadata") rebuild.append(metadata) for k, v in kwargs['metadata'].items(): meta = Element("meta", key=k) meta.append(Text(v)) metadata.append(meta) resp, body = self.post('servers/%s/action' % server_id, str(Document(rebuild)), self.headers) server = self._parse_server(etree.fromstring(body)) return resp, server def resize(self, server_id, flavor_ref, **kwargs): if 'disk_config' in kwargs: kwargs['OS-DCF:diskConfig'] = kwargs['disk_config'] del kwargs['disk_config'] kwargs['xmlns:OS-DCF'] = "http://docs.openstack.org/"\ "compute/ext/disk_config/api/v1.1" kwargs['xmlns:atom'] = "http://www.w3.org/2005/Atom" kwargs['flavorRef'] = flavor_ref return self.action(server_id, 'resize', None, **kwargs) def confirm_resize(self, server_id, **kwargs): return self.action(server_id, 'confirmResize', None, **kwargs) def revert_resize(self, server_id, **kwargs): return self.action(server_id, 'revertResize', None, **kwargs) def create_image(self, server_id, name): return self.action(server_id, 'createImage', None, name=name) def add_security_group(self, server_id, name): return self.action(server_id, 'addSecurityGroup', None, name=name) def remove_security_group(self, server_id, name): return self.action(server_id, 'removeSecurityGroup', None, name=name) def live_migrate_server(self, server_id, dest_host, use_block_migration): """This should be called with administrator privileges .""" req_body = Element("os-migrateLive", xmlns=XMLNS_11, disk_over_commit=False, block_migration=use_block_migration, host=dest_host) resp, body = self.post("servers/%s/action" % str(server_id), str(Document(req_body)), self.headers) return resp, body def list_server_metadata(self, server_id): resp, body = self.get("servers/%s/metadata" % str(server_id), self.headers) body = self._parse_key_value(etree.fromstring(body)) return resp, body def set_server_metadata(self, server_id, meta): doc = Document() metadata = Element("metadata") doc.append(metadata) for k, v in meta.items(): meta_element = Element("meta", key=k) meta_element.append(Text(v)) metadata.append(meta_element) resp, body = self.put('servers/%s/metadata' % str(server_id), str(doc), self.headers) return resp, xml_to_json(etree.fromstring(body)) def update_server_metadata(self, server_id, meta): doc = Document() metadata = Element("metadata") doc.append(metadata) for k, v in meta.items(): meta_element = Element("meta", key=k) meta_element.append(Text(v)) metadata.append(meta_element) resp, body = self.post("/servers/%s/metadata" % str(server_id), str(doc), headers=self.headers) body = xml_to_json(etree.fromstring(body)) return resp, body def get_server_metadata_item(self, server_id, key): resp, body = self.get("servers/%s/metadata/%s" % (str(server_id), key), headers=self.headers) return resp, dict([(etree.fromstring(body).attrib['key'], xml_to_json(etree.fromstring(body)))]) def set_server_metadata_item(self, server_id, key, meta): doc = Document() for k, v in meta.items(): meta_element = Element("meta", key=k) meta_element.append(Text(v)) doc.append(meta_element) resp, body = self.put('servers/%s/metadata/%s' % (str(server_id), key), str(doc), self.headers) return resp, xml_to_json(etree.fromstring(body)) def delete_server_metadata_item(self, server_id, key): resp, body = self.delete("servers/%s/metadata/%s" % (str(server_id), key)) return resp, body def get_console_output(self, server_id, length): return self.action(server_id, 'os-getConsoleOutput', 'output', length=length) def list_virtual_interfaces(self, server_id): """ List the virtual interfaces used in an instance. """ resp, body = self.get('/'.join(['servers', server_id, 'os-virtual-interfaces']), self.headers) virt_int = self._parse_xml_virtual_interfaces(etree.fromstring(body)) return resp, virt_int def rescue_server(self, server_id, adminPass=None): """Rescue the provided server.""" return self.action(server_id, 'rescue', None, adminPass=adminPass) def unrescue_server(self, server_id): """Unrescue the provided server.""" return self.action(server_id, 'unrescue', None) def attach_volume(self, server_id, volume_id, device='/dev/vdz'): post_body = Element("volumeAttachment", volumeId=volume_id, device=device) resp, body = self.post('servers/%s/os-volume_attachments' % server_id, str(Document(post_body)), self.headers) return resp, body def detach_volume(self, server_id, volume_id): headers = {'Content-Type': 'application/xml', 'Accept': 'application/xml'} resp, body = self.delete('servers/%s/os-volume_attachments/%s' % (server_id, volume_id), headers) return resp, body def list_instance_actions(self, server_id): """List the provided server action.""" resp, body = self.get("servers/%s/os-instance-actions" % server_id, self.headers) body = self._parse_array(etree.fromstring(body)) return resp, body def get_instance_action(self, server_id, request_id): """Returns the action details of the provided server.""" resp, body = self.get("servers/%s/os-instance-actions/%s" % (server_id, request_id), self.headers) body = xml_to_json(etree.fromstring(body)) return resp, body tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/xml/tenant_usages_client.py0000664000175000017500000000365012161375672030012 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 NEC Corporation # 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 urllib from lxml import etree from tempest.common.rest_client import RestClientXML from tempest.services.compute.xml.common import xml_to_json class TenantUsagesClientXML(RestClientXML): def __init__(self, config, username, password, auth_url, tenant_name=None): super(TenantUsagesClientXML, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type def _parse_array(self, node): json = xml_to_json(node) return json def list_tenant_usages(self, params=None): url = 'os-simple-tenant-usage' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url, self.headers) tenant_usage = self._parse_array(etree.fromstring(body)) return resp, tenant_usage['tenant_usage'] def get_tenant_usage(self, tenant_id, params=None): url = 'os-simple-tenant-usage/%s' % tenant_id if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url, self.headers) tenant_usage = self._parse_array(etree.fromstring(body)) return resp, tenant_usage tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/xml/volumes_extensions_client.py0000664000175000017500000001257612161375672031132 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # # Copyright 2012 IBM Corp. # 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 time import urllib from lxml import etree from tempest.common.rest_client import RestClientXML from tempest import exceptions from tempest.services.compute.xml.common import Document from tempest.services.compute.xml.common import Element from tempest.services.compute.xml.common import Text from tempest.services.compute.xml.common import xml_to_json from tempest.services.compute.xml.common import XMLNS_11 class VolumesExtensionsClientXML(RestClientXML): def __init__(self, config, username, password, auth_url, tenant_name=None): super(VolumesExtensionsClientXML, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type self.build_interval = self.config.compute.build_interval self.build_timeout = self.config.compute.build_timeout def _parse_volume(self, body): vol = dict((attr, body.get(attr)) for attr in body.keys()) for child in body.getchildren(): tag = child.tag if tag.startswith("{"): ns, tag = tag.split("}", 1) if tag == 'metadata': vol['metadata'] = dict((meta.get('key'), meta.text) for meta in list(child)) else: vol[tag] = xml_to_json(child) return vol def list_volumes(self, params=None): """List all the volumes created.""" url = 'os-volumes' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url, self.headers) body = etree.fromstring(body) volumes = [] if body is not None: volumes += [self._parse_volume(vol) for vol in list(body)] return resp, volumes def list_volumes_with_detail(self, params=None): """List all the details of volumes.""" url = 'os-volumes/detail' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url, self.headers) body = etree.fromstring(body) volumes = [] if body is not None: volumes += [self._parse_volume(vol) for vol in list(body)] return resp, volumes def get_volume(self, volume_id): """Returns the details of a single volume.""" url = "os-volumes/%s" % str(volume_id) resp, body = self.get(url, self.headers) body = etree.fromstring(body) return resp, self._parse_volume(body) def create_volume(self, size, display_name=None, metadata=None): """Creates a new Volume. :param size: Size of volume in GB. (Required) :param display_name: Optional Volume Name. :param metadata: An optional dictionary of values for metadata. """ volume = Element("volume", xmlns=XMLNS_11, size=size) if display_name: volume.add_attr('display_name', display_name) if metadata: _metadata = Element('metadata') volume.append(_metadata) for key, value in metadata.items(): meta = Element('meta') meta.add_attr('key', key) meta.append(Text(value)) _metadata.append(meta) resp, body = self.post('os-volumes', str(Document(volume)), self.headers) body = xml_to_json(etree.fromstring(body)) return resp, body def delete_volume(self, volume_id): """Deletes the Specified Volume.""" return self.delete("os-volumes/%s" % str(volume_id)) def wait_for_volume_status(self, volume_id, status): """Waits for a Volume to reach a given status.""" resp, body = self.get_volume(volume_id) volume_name = body['displayName'] volume_status = body['status'] start = int(time.time()) while volume_status != status: time.sleep(self.build_interval) resp, body = self.get_volume(volume_id) volume_status = body['status'] if volume_status == 'error': raise exceptions.VolumeBuildErrorException(volume_id=volume_id) if int(time.time()) - start >= self.build_timeout: message = 'Volume %s failed to reach %s status within '\ 'the required time (%s s).' % (volume_name, status, self.build_timeout) raise exceptions.TimeoutException(message) def is_resource_deleted(self, id): try: self.get_volume(id) except exceptions.NotFound: return True return False tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/xml/flavors_client.py0000664000175000017500000001505212161375672026625 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 urllib from lxml import etree from tempest.common.rest_client import RestClientXML from tempest.services.compute.xml.common import Document from tempest.services.compute.xml.common import Element from tempest.services.compute.xml.common import xml_to_json from tempest.services.compute.xml.common import XMLNS_11 XMLNS_OS_FLV_EXT_DATA = \ "http://docs.openstack.org/compute/ext/flavor_extra_data/api/v1.1" XMLNS_OS_FLV_ACCESS = \ "http://docs.openstack.org/compute/ext/flavor_access/api/v1.1" class FlavorsClientXML(RestClientXML): def __init__(self, config, username, password, auth_url, tenant_name=None): super(FlavorsClientXML, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type def _format_flavor(self, f): flavor = {'links': []} for k, v in f.items(): if k == 'link': flavor['links'].append(v) continue if k == '{%s}ephemeral' % XMLNS_OS_FLV_EXT_DATA: k = 'OS-FLV-EXT-DATA:ephemeral' try: v = int(v) except ValueError: try: v = float(v) except ValueError: pass flavor[k] = v return flavor def _parse_array(self, node): return [self._format_flavor(xml_to_json(x)) for x in node] def _list_flavors(self, url, params): if params: url += "?%s" % urllib.urlencode(params) resp, body = self.get(url, self.headers) flavors = self._parse_array(etree.fromstring(body)) return resp, flavors def list_flavors(self, params=None): url = 'flavors' return self._list_flavors(url, params) def list_flavors_with_detail(self, params=None): url = 'flavors/detail' return self._list_flavors(url, params) def get_flavor_details(self, flavor_id): resp, body = self.get("flavors/%s" % str(flavor_id), self.headers) body = xml_to_json(etree.fromstring(body)) flavor = self._format_flavor(body) return resp, flavor def create_flavor(self, name, ram, vcpus, disk, flavor_id, **kwargs): """Creates a new flavor or instance type.""" flavor = Element("flavor", xmlns=XMLNS_11, ram=ram, vcpus=vcpus, disk=disk, id=flavor_id, name=name) if kwargs.get('rxtx'): flavor.add_attr('rxtx_factor', kwargs.get('rxtx')) if kwargs.get('swap'): flavor.add_attr('swap', kwargs.get('swap')) if kwargs.get('ephemeral'): flavor.add_attr('OS-FLV-EXT-DATA:ephemeral', kwargs.get('ephemeral')) if kwargs.get('is_public'): flavor.add_attr('os-flavor-access:is_public', kwargs.get('is_public')) flavor.add_attr('xmlns:OS-FLV-EXT-DATA', XMLNS_OS_FLV_EXT_DATA) flavor.add_attr('xmlns:os-flavor-access', XMLNS_OS_FLV_ACCESS) resp, body = self.post('flavors', str(Document(flavor)), self.headers) body = xml_to_json(etree.fromstring(body)) flavor = self._format_flavor(body) return resp, flavor def delete_flavor(self, flavor_id): """Deletes the given flavor.""" return self.delete("flavors/%s" % str(flavor_id), self.headers) def is_resource_deleted(self, id): #Did not use get_flavor_details(id) for verification as it gives #200 ok even for deleted id. LP #981263 #we can remove the loop here and use get by ID when bug gets sortedout resp, flavors = self.list_flavors_with_detail() for flavor in flavors: if flavor['id'] == id: return False return True def set_flavor_extra_spec(self, flavor_id, specs): """Sets extra Specs to the mentioned flavor.""" extra_specs = Element("extra_specs") for key in specs.keys(): extra_specs.add_attr(key, specs[key]) resp, body = self.post('flavors/%s/os-extra_specs' % flavor_id, str(Document(extra_specs)), self.headers) body = xml_to_json(etree.fromstring(body)) return resp, body def get_flavor_extra_spec(self, flavor_id): """Gets extra Specs of the mentioned flavor.""" resp, body = self.get('flavors/%s/os-extra_specs' % flavor_id, self.headers) body = xml_to_json(etree.fromstring(body)) return resp, body def unset_flavor_extra_spec(self, flavor_id, key): """Unsets an extra spec based on the mentioned flavor and key.""" return self.delete('flavors/%s/os-extra_specs/%s' % (str(flavor_id), key)) def _parse_array_access(self, node): return [xml_to_json(x) for x in node] def add_flavor_access(self, flavor_id, tenant_id): """Add flavor access for the specified tenant.""" doc = Document() server = Element("addTenantAccess") doc.append(server) server.add_attr("tenant", tenant_id) resp, body = self.post('flavors/%s/action' % str(flavor_id), str(doc), self.headers) body = self._parse_array_access(etree.fromstring(body)) return resp, body def remove_flavor_access(self, flavor_id, tenant_id): """Remove flavor access from the specified tenant.""" doc = Document() server = Element("removeTenantAccess") doc.append(server) server.add_attr("tenant", tenant_id) resp, body = self.post('flavors/%s/action' % str(flavor_id), str(doc), self.headers) body = self._parse_array_access(etree.fromstring(body)) return resp, body tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/xml/keypairs_client.py0000664000175000017500000000477512161375672027012 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # # Copyright 2012 IBM Corp. # 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 lxml import etree from tempest.common.rest_client import RestClientXML from tempest.services.compute.xml.common import Document from tempest.services.compute.xml.common import Element from tempest.services.compute.xml.common import Text from tempest.services.compute.xml.common import xml_to_json class KeyPairsClientXML(RestClientXML): def __init__(self, config, username, password, auth_url, tenant_name=None): super(KeyPairsClientXML, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type def list_keypairs(self): resp, body = self.get("os-keypairs", self.headers) node = etree.fromstring(body) body = [{'keypair': xml_to_json(x)} for x in node.getchildren()] return resp, body def get_keypair(self, key_name): resp, body = self.get("os-keypairs/%s" % str(key_name), self.headers) body = xml_to_json(etree.fromstring(body)) return resp, body def create_keypair(self, name, pub_key=None): doc = Document() keypair_element = Element("keypair") if pub_key: public_key_element = Element("public_key") public_key_text = Text(pub_key) public_key_element.append(public_key_text) keypair_element.append(public_key_element) name_element = Element("name") name_text = Text(name) name_element.append(name_text) keypair_element.append(name_element) doc.append(keypair_element) resp, body = self.post("os-keypairs", headers=self.headers, body=str(doc)) body = xml_to_json(etree.fromstring(body)) return resp, body def delete_keypair(self, key_name): return self.delete("os-keypairs/%s" % str(key_name)) tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/xml/limits_client.py0000664000175000017500000000360212161375672026450 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # # Copyright 2012 IBM Corp. # 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 lxml import objectify from tempest.common.rest_client import RestClientXML NS = "{http://docs.openstack.org/common/api/v1.0}" class LimitsClientXML(RestClientXML): def __init__(self, config, username, password, auth_url, tenant_name=None): super(LimitsClientXML, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type def get_absolute_limits(self): resp, body = self.get("limits", self.headers) body = objectify.fromstring(body) lim = NS + 'absolute' ret = {} for el in body[lim].iterchildren(): attributes = el.attrib ret[attributes['name']] = attributes['value'] return resp, ret def get_specific_absolute_limit(self, absolute_limit): resp, body = self.get("limits", self.headers) body = objectify.fromstring(body) lim = NS + 'absolute' ret = {} for el in body[lim].iterchildren(): attributes = el.attrib ret[attributes['name']] = attributes['value'] if absolute_limit not in ret: return None else: return ret[absolute_limit] tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/xml/quotas_client.py0000664000175000017500000001071012161375672026461 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # # Copyright 2012 NTT Data # 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 lxml import etree from tempest.common.rest_client import RestClientXML from tempest.services.compute.xml.common import Document from tempest.services.compute.xml.common import Element from tempest.services.compute.xml.common import xml_to_json from tempest.services.compute.xml.common import XMLNS_11 class QuotasClientXML(RestClientXML): def __init__(self, config, username, password, auth_url, tenant_name=None): super(QuotasClientXML, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type def _format_quota(self, q): quota = {} for k, v in q.items(): try: v = int(v) except ValueError: pass quota[k] = v return quota def _parse_array(self, node): return [self._format_quota(xml_to_json(x)) for x in node] def get_quota_set(self, tenant_id): """List the quota set for a tenant.""" url = 'os-quota-sets/%s' % str(tenant_id) resp, body = self.get(url, self.headers) body = xml_to_json(etree.fromstring(body)) body = self._format_quota(body) return resp, body def get_default_quota_set(self, tenant_id): """List the default quota set for a tenant.""" url = 'os-quota-sets/%s/defaults' % str(tenant_id) resp, body = self.get(url, self.headers) body = xml_to_json(etree.fromstring(body)) body = self._format_quota(body) return resp, body def update_quota_set(self, tenant_id, force=None, injected_file_content_bytes=None, metadata_items=None, ram=None, floating_ips=None, fixed_ips=None, key_pairs=None, instances=None, security_group_rules=None, injected_files=None, cores=None, injected_file_path_bytes=None, security_groups=None): """ Updates the tenant's quota limits for one or more resources """ post_body = Element("quota_set", xmlns=XMLNS_11) if force is not None: post_body.add_attr('force', force) if injected_file_content_bytes is not None: post_body.add_attr('injected_file_content_bytes', injected_file_content_bytes) if metadata_items is not None: post_body.add_attr('metadata_items', metadata_items) if ram is not None: post_body.add_attr('ram', ram) if floating_ips is not None: post_body.add_attr('floating_ips', floating_ips) if fixed_ips is not None: post_body.add_attr('fixed_ips', fixed_ips) if key_pairs is not None: post_body.add_attr('key_pairs', key_pairs) if instances is not None: post_body.add_attr('instances', instances) if security_group_rules is not None: post_body.add_attr('security_group_rules', security_group_rules) if injected_files is not None: post_body.add_attr('injected_files', injected_files) if cores is not None: post_body.add_attr('cores', cores) if injected_file_path_bytes is not None: post_body.add_attr('injected_file_path_bytes', injected_file_path_bytes) if security_groups is not None: post_body.add_attr('security_groups', security_groups) resp, body = self.put('os-quota-sets/%s' % str(tenant_id), str(Document(post_body)), self.headers) body = xml_to_json(etree.fromstring(body)) body = self._format_quota(body) return resp, body tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/xml/hypervisor_client.py0000664000175000017500000000564312161375672027370 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 IBM Corporation # 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 lxml import etree from tempest.common.rest_client import RestClientXML from tempest.services.compute.xml.common import xml_to_json class HypervisorClientXML(RestClientXML): def __init__(self, config, username, password, auth_url, tenant_name=None): super(HypervisorClientXML, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type def _parse_array(self, node): return [xml_to_json(x) for x in node] def get_hypervisor_list(self): """List hypervisors information.""" resp, body = self.get('os-hypervisors', self.headers) hypervisors = self._parse_array(etree.fromstring(body)) return resp, hypervisors def get_hypervisor_list_details(self): """Show detailed hypervisors information.""" resp, body = self.get('os-hypervisors/detail', self.headers) hypervisors = self._parse_array(etree.fromstring(body)) return resp, hypervisors def get_hypervisor_show_details(self, hyper_id): """Display the details of the specified hypervisor.""" resp, body = self.get('os-hypervisors/%s' % hyper_id, self.headers) hypervisor = xml_to_json(etree.fromstring(body)) return resp, hypervisor def get_hypervisor_servers(self, hyper_name): """List instances belonging to the specified hypervisor.""" resp, body = self.get('os-hypervisors/%s/servers' % hyper_name, self.headers) hypervisors = self._parse_array(etree.fromstring(body)) return resp, hypervisors def get_hypervisor_stats(self): """Get hypervisor statistics over all compute nodes.""" resp, body = self.get('os-hypervisors/statistics', self.headers) stats = xml_to_json(etree.fromstring(body)) return resp, stats def get_hypervisor_uptime(self, hyper_id): """Display the uptime of the specified hypervisor.""" resp, body = self.get('os-hypervisors/%s/uptime' % hyper_id, self.headers) uptime = xml_to_json(etree.fromstring(body)) return resp, uptime tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/xml/__init__.py0000664000175000017500000000000012161375672025335 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/xml/fixed_ips_client.py0000664000175000017500000000417212161375672027124 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 IBM Corp # 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 lxml import etree from tempest.common.rest_client import RestClientXML from tempest.services.compute.xml.common import Document from tempest.services.compute.xml.common import Element from tempest.services.compute.xml.common import Text from tempest.services.compute.xml.common import xml_to_json class FixedIPsClientXML(RestClientXML): def __init__(self, config, username, password, auth_url, tenant_name=None): super(FixedIPsClientXML, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type def _parse_fixed_ip_details(self, body): body = xml_to_json(etree.fromstring(body)) return body def get_fixed_ip_details(self, fixed_ip): url = "os-fixed-ips/%s" % (fixed_ip) resp, body = self.get(url, self.headers) body = self._parse_resp(body) return resp, body def reserve_fixed_ip(self, ip, body): """This reserves and unreserves fixed ips.""" url = "os-fixed-ips/%s/action" % (ip) # NOTE(maurosr): First converts the dict body to a json string then # accept any action key value here to permit tests to cover cases with # invalid actions raising badrequest. key, value = body.popitem() xml_body = Element(key) xml_body.append(Text(value)) resp, body = self.post(url, str(Document(xml_body)), self.headers) return resp, body tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/xml/images_client.py0000664000175000017500000002145512161375672026422 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # # Copyright 2012 IBM Corp. # 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 time import urllib from lxml import etree from tempest.common.rest_client import RestClientXML from tempest import exceptions from tempest.services.compute.xml.common import Document from tempest.services.compute.xml.common import Element from tempest.services.compute.xml.common import Text from tempest.services.compute.xml.common import xml_to_json from tempest.services.compute.xml.common import XMLNS_11 class ImagesClientXML(RestClientXML): def __init__(self, config, username, password, auth_url, tenant_name=None): super(ImagesClientXML, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type self.build_interval = self.config.compute.build_interval self.build_timeout = self.config.compute.build_timeout def _parse_server(self, node): data = xml_to_json(node) return self._parse_links(node, data) def _parse_image(self, node): """Parses detailed XML image information into dictionary.""" data = xml_to_json(node) self._parse_links(node, data) # parse all metadata if 'metadata' in data: tag = node.find('{%s}metadata' % XMLNS_11) data['metadata'] = dict((x.get('key'), x.text) for x in tag.getchildren()) # parse server information if 'server' in data: tag = node.find('{%s}server' % XMLNS_11) data['server'] = self._parse_server(tag) return data def _parse_links(self, node, data): """Append multiple links under a list.""" # look for links if 'link' in data: # remove single link element del data['link'] data['links'] = [xml_to_json(x) for x in node.findall('{http://www.w3.org/2005/Atom}link')] return data def _parse_images(self, xml): data = {'images': []} images = xml.getchildren() for image in images: data['images'].append(self._parse_image(image)) return data def _parse_key_value(self, node): """Parse value data into {'key': 'value'}.""" data = {} for node in node.getchildren(): data[node.get('key')] = node.text return data def _parse_metadata(self, node): """Parse the response body without children.""" data = {} data[node.get('key')] = node.text return data def create_image(self, server_id, name, meta=None): """Creates an image of the original server.""" post_body = Element('createImage', name=name) if meta: metadata = Element('metadata') post_body.append(metadata) for k, v in meta.items(): data = Element('meta', key=k) data.append(Text(v)) metadata.append(data) resp, body = self.post('servers/%s/action' % str(server_id), str(Document(post_body)), self.headers) return resp, body def list_images(self, params=None): """Returns a list of all images filtered by any parameters.""" url = 'images' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url, self.headers) body = self._parse_images(etree.fromstring(body)) return resp, body['images'] def list_images_with_detail(self, params=None): """Returns a detailed list of images filtered by any parameters.""" url = 'images/detail' if params: param_list = urllib.urlencode(params) url = "images/detail?" + param_list resp, body = self.get(url, self.headers) body = self._parse_images(etree.fromstring(body)) return resp, body['images'] def get_image(self, image_id): """Returns the details of a single image.""" resp, body = self.get("images/%s" % str(image_id), self.headers) body = self._parse_image(etree.fromstring(body)) return resp, body def delete_image(self, image_id): """Deletes the provided image.""" return self.delete("images/%s" % str(image_id), self.headers) def wait_for_image_resp_code(self, image_id, code): """ Waits until the HTTP response code for the request matches the expected value """ resp, body = self.get("images/%s" % str(image_id), self.headers) start = int(time.time()) while resp.status != code: time.sleep(self.build_interval) resp, body = self.get("images/%s" % str(image_id), self.headers) if int(time.time()) - start >= self.build_timeout: raise exceptions.TimeoutException def wait_for_image_status(self, image_id, status): """Waits for an image to reach a given status.""" resp, image = self.get_image(image_id) start = int(time.time()) while image['status'] != status: time.sleep(self.build_interval) resp, image = self.get_image(image_id) if image['status'] == 'ERROR': raise exceptions.AddImageException(image_id=image_id) if int(time.time()) - start >= self.build_timeout: raise exceptions.TimeoutException def _metadata_body(self, meta): post_body = Element('metadata') for k, v in meta.items(): data = Element('meta', key=k) data.append(Text(v)) post_body.append(data) return post_body def list_image_metadata(self, image_id): """Lists all metadata items for an image.""" resp, body = self.get("images/%s/metadata" % str(image_id), self.headers) body = self._parse_key_value(etree.fromstring(body)) return resp, body def set_image_metadata(self, image_id, meta): """Sets the metadata for an image.""" post_body = self._metadata_body(meta) resp, body = self.put('images/%s/metadata' % image_id, str(Document(post_body)), self.headers) body = self._parse_key_value(etree.fromstring(body)) return resp, body def update_image_metadata(self, image_id, meta): """Updates the metadata for an image.""" post_body = self._metadata_body(meta) resp, body = self.post('images/%s/metadata' % str(image_id), str(Document(post_body)), self.headers) body = self._parse_key_value(etree.fromstring(body)) return resp, body def get_image_metadata_item(self, image_id, key): """Returns the value for a specific image metadata key.""" resp, body = self.get("images/%s/metadata/%s.xml" % (str(image_id), key), self.headers) body = self._parse_metadata(etree.fromstring(body)) return resp, body def set_image_metadata_item(self, image_id, key, meta): """Sets the value for a specific image metadata key.""" for k, v in meta.items(): post_body = Element('meta', key=key) post_body.append(Text(v)) resp, body = self.put('images/%s/metadata/%s' % (str(image_id), key), str(Document(post_body)), self.headers) body = xml_to_json(etree.fromstring(body)) return resp, body def update_image_metadata_item(self, image_id, key, meta): """Sets the value for a specific image metadata key.""" post_body = Document('meta', Text(meta), key=key) resp, body = self.put('images/%s/metadata/%s' % (str(image_id), key), post_body, self.headers) body = xml_to_json(etree.fromstring(body)) return resp, body['meta'] def delete_image_metadata_item(self, image_id, key): """Deletes a single image metadata key/value pair.""" return self.delete("images/%s/metadata/%s" % (str(image_id), key), self.headers) def is_resource_deleted(self, id): try: self.get_image(id) except exceptions.NotFound: return True return False tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/xml/security_groups_client.py0000664000175000017500000001222112161375672030412 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # # Copyright 2012 IBM Corp. # 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 lxml import etree import urllib from tempest.common.rest_client import RestClientXML from tempest import exceptions from tempest.services.compute.xml.common import Document from tempest.services.compute.xml.common import Element from tempest.services.compute.xml.common import Text from tempest.services.compute.xml.common import xml_to_json from tempest.services.compute.xml.common import XMLNS_11 class SecurityGroupsClientXML(RestClientXML): def __init__(self, config, username, password, auth_url, tenant_name=None): super(SecurityGroupsClientXML, self).__init__( config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type def _parse_array(self, node): array = [] for child in node.getchildren(): array.append(xml_to_json(child)) return array def _parse_body(self, body): json = xml_to_json(body) return json def list_security_groups(self, params=None): """List all security groups for a user.""" url = 'os-security-groups' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url, self.headers) body = self._parse_array(etree.fromstring(body)) return resp, body def get_security_group(self, security_group_id): """Get the details of a Security Group.""" url = "os-security-groups/%s" % str(security_group_id) resp, body = self.get(url, self.headers) body = self._parse_body(etree.fromstring(body)) return resp, body def create_security_group(self, name, description): """ Creates a new security group. name (Required): Name of security group. description (Required): Description of security group. """ security_group = Element("security_group", name=name) des = Element("description") des.append(Text(content=description)) security_group.append(des) resp, body = self.post('os-security-groups', str(Document(security_group)), self.headers) body = self._parse_body(etree.fromstring(body)) return resp, body def delete_security_group(self, security_group_id): """Deletes the provided Security Group.""" return self.delete('os-security-groups/%s' % str(security_group_id), self.headers) def create_security_group_rule(self, parent_group_id, ip_proto, from_port, to_port, **kwargs): """ Creating a new security group rules. parent_group_id :ID of Security group ip_protocol : ip_proto (icmp, tcp, udp). from_port: Port at start of range. to_port : Port at end of range. Following optional keyword arguments are accepted: cidr : CIDR for address range. group_id : ID of the Source group """ group_rule = Element("security_group_rule") elements = dict() elements['cidr'] = kwargs.get('cidr') elements['group_id'] = kwargs.get('group_id') elements['parent_group_id'] = parent_group_id elements['ip_protocol'] = ip_proto elements['from_port'] = from_port elements['to_port'] = to_port for k, v in elements.items(): if v is not None: element = Element(k) element.append(Text(content=str(v))) group_rule.append(element) url = 'os-security-group-rules' resp, body = self.post(url, str(Document(group_rule)), self.headers) body = self._parse_body(etree.fromstring(body)) return resp, body def delete_security_group_rule(self, group_rule_id): """Deletes the provided Security Group rule.""" return self.delete('os-security-group-rules/%s' % str(group_rule_id), self.headers) def list_security_group_rules(self, security_group_id): """List all rules for a security group.""" url = "os-security-groups" resp, body = self.get(url, self.headers) body = etree.fromstring(body) secgroups = body.getchildren() for secgroup in secgroups: if secgroup.get('id') == security_group_id: node = secgroup.find('{%s}rules' % XMLNS_11) rules = [xml_to_json(x) for x in node.getchildren()] return resp, rules raise exceptions.NotFound('No such Security Group') tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/xml/availability_zone_client.py0000664000175000017500000000333212161375672030654 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 NEC Corporation # 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 lxml import etree from tempest.common.rest_client import RestClientXML from tempest.services.compute.xml.common import xml_to_json class AvailabilityZoneClientXML(RestClientXML): def __init__(self, config, username, password, auth_url, tenant_name=None): super(AvailabilityZoneClientXML, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type def _parse_array(self, node): return [xml_to_json(x) for x in node] def get_availability_zone_list(self): resp, body = self.get('os-availability-zone', self.headers) availability_zone = self._parse_array(etree.fromstring(body)) return resp, availability_zone def get_availability_zone_list_detail(self): resp, body = self.get('os-availability-zone/detail', self.headers) availability_zone = self._parse_array(etree.fromstring(body)) return resp, availability_zone tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/__init__.py0000664000175000017500000000000012161375672024535 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/json/0000775000175000017500000000000012161375700023377 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/json/interfaces_client.py0000664000175000017500000000624412161375672027450 0ustar chuckchuck00000000000000# Copyright 2013 IBM Corp. # 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 json import time from tempest.common.rest_client import RestClient from tempest import exceptions class InterfacesClientJSON(RestClient): def __init__(self, config, username, password, auth_url, tenant_name=None): super(InterfacesClientJSON, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type def list_interfaces(self, server): resp, body = self.get('servers/%s/os-interface' % server) body = json.loads(body) return resp, body['interfaceAttachments'] def create_interface(self, server, port_id=None, network_id=None, fixed_ip=None): post_body = dict(interfaceAttachment=dict()) if port_id: post_body['port_id'] = port_id if network_id: post_body['net_id'] = network_id if fixed_ip: post_body['fixed_ips'] = [dict(ip_address=fixed_ip)] post_body = json.dumps(post_body) resp, body = self.post('servers/%s/os-interface' % server, headers=self.headers, body=post_body) body = json.loads(body) return resp, body['interfaceAttachment'] def show_interface(self, server, port_id): resp, body = self.get('servers/%s/os-interface/%s' % (server, port_id)) body = json.loads(body) return resp, body['interfaceAttachment'] def delete_interface(self, server, port_id): resp, body = self.delete('servers/%s/os-interface/%s' % (server, port_id)) return resp, body def wait_for_interface_status(self, server, port_id, status): """Waits for a interface to reach a given status.""" resp, body = self.show_interface(server, port_id) interface_status = body['port_state'] start = int(time.time()) while(interface_status != status): time.sleep(self.build_interval) resp, body = self.show_interface(server, port_id) interface_status = body['port_state'] timed_out = int(time.time()) - start >= self.build_timeout if interface_status != status and timed_out: message = ('Interface %s failed to reach %s status within ' 'the required time (%s s).' % (port_id, status, self.build_timeout)) raise exceptions.TimeoutException(message) return resp, body tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/json/aggregates_client.py0000664000175000017500000000602412161375672027432 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 NEC Corporation. # 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 json from tempest.common.rest_client import RestClient from tempest import exceptions class AggregatesClientJSON(RestClient): def __init__(self, config, username, password, auth_url, tenant_name=None): super(AggregatesClientJSON, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type def list_aggregates(self): """Get aggregate list.""" resp, body = self.get("os-aggregates") body = json.loads(body) return resp, body['aggregates'] def get_aggregate(self, aggregate_id): """Get details of the given aggregate.""" resp, body = self.get("os-aggregates/%s" % str(aggregate_id)) body = json.loads(body) return resp, body['aggregate'] def create_aggregate(self, name, availability_zone=None): """Creates a new aggregate.""" post_body = { 'name': name, 'availability_zone': availability_zone, } post_body = json.dumps({'aggregate': post_body}) resp, body = self.post('os-aggregates', post_body, self.headers) body = json.loads(body) return resp, body['aggregate'] def delete_aggregate(self, aggregate_id): """Deletes the given aggregate.""" return self.delete("os-aggregates/%s" % str(aggregate_id)) def is_resource_deleted(self, id): try: self.get_aggregate(id) except exceptions.NotFound: return True return False def add_host(self, aggregate_id, host): """Adds a host to the given aggregate.""" post_body = { 'host': host, } post_body = json.dumps({'add_host': post_body}) resp, body = self.post('os-aggregates/%s/action' % aggregate_id, post_body, self.headers) body = json.loads(body) return resp, body['aggregate'] def remove_host(self, aggregate_id, host): """Removes a host from the given aggregate.""" post_body = { 'host': host, } post_body = json.dumps({'remove_host': post_body}) resp, body = self.post('os-aggregates/%s/action' % aggregate_id, post_body, self.headers) body = json.loads(body) return resp, body['aggregate'] tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/json/hosts_client.py0000664000175000017500000000106612161375672026462 0ustar chuckchuck00000000000000import json from tempest.common.rest_client import RestClient class HostsClientJSON(RestClient): def __init__(self, config, username, password, auth_url, tenant_name=None): super(HostsClientJSON, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type def list_hosts(self): """Lists all hosts.""" url = 'os-hosts' resp, body = self.get(url) body = json.loads(body) return resp, body['hosts'] tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/json/extensions_client.py0000664000175000017500000000260312161375672027517 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 json from tempest.common.rest_client import RestClient class ExtensionsClientJSON(RestClient): def __init__(self, config, username, password, auth_url, tenant_name=None): super(ExtensionsClientJSON, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type def list_extensions(self): url = 'extensions' resp, body = self.get(url) body = json.loads(body) return resp, body def is_enabled(self, extension): _, extensions = self.list_extensions() exts = extensions['extensions'] return any([e for e in exts if e['name'] == extension]) tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/json/services_client.py0000664000175000017500000000227212161375672027145 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 NEC Corporation # 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 json from tempest.common.rest_client import RestClient class ServicesClientJSON(RestClient): def __init__(self, config, username, password, auth_url, tenant_name=None): super(ServicesClientJSON, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type def list_services(self): resp, body = self.get("os-services") body = json.loads(body) return resp, body['services'] tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/json/floating_ips_client.py0000664000175000017500000000660512161375672030004 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 json import urllib from tempest.common.rest_client import RestClient from tempest import exceptions class FloatingIPsClientJSON(RestClient): def __init__(self, config, username, password, auth_url, tenant_name=None): super(FloatingIPsClientJSON, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type def list_floating_ips(self, params=None): """Returns a list of all floating IPs filtered by any parameters.""" url = 'os-floating-ips' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body['floating_ips'] def get_floating_ip_details(self, floating_ip_id): """Get the details of a floating IP.""" url = "os-floating-ips/%s" % str(floating_ip_id) resp, body = self.get(url) body = json.loads(body) if resp.status == 404: raise exceptions.NotFound(body) return resp, body['floating_ip'] def create_floating_ip(self, pool_name=None): """Allocate a floating IP to the project.""" url = 'os-floating-ips' post_body = {'pool': pool_name} post_body = json.dumps(post_body) resp, body = self.post(url, post_body, self.headers) body = json.loads(body) return resp, body['floating_ip'] def delete_floating_ip(self, floating_ip_id): """Deletes the provided floating IP from the project.""" url = "os-floating-ips/%s" % str(floating_ip_id) resp, body = self.delete(url) return resp, body def associate_floating_ip_to_server(self, floating_ip, server_id): """Associate the provided floating IP to a specific server.""" url = "servers/%s/action" % str(server_id) post_body = { 'addFloatingIp': { 'address': floating_ip, } } post_body = json.dumps(post_body) resp, body = self.post(url, post_body, self.headers) return resp, body def disassociate_floating_ip_from_server(self, floating_ip, server_id): """Disassociate the provided floating IP from a specific server.""" url = "servers/%s/action" % str(server_id) post_body = { 'removeFloatingIp': { 'address': floating_ip, } } post_body = json.dumps(post_body) resp, body = self.post(url, post_body, self.headers) return resp, body def is_resource_deleted(self, id): try: self.get_floating_ip_details(id) except exceptions.NotFound: return True return False tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/json/servers_client.py0000664000175000017500000003751312161375672027021 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # Copyright 2013 Hewlett-Packard Development Company, L.P. # 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 json import time import urllib from tempest.common.rest_client import RestClient from tempest import exceptions class ServersClientJSON(RestClient): def __init__(self, config, username, password, auth_url, tenant_name=None, auth_version='v2'): super(ServersClientJSON, self).__init__(config, username, password, auth_url, tenant_name, auth_version=auth_version) self.service = self.config.compute.catalog_type def create_server(self, name, image_ref, flavor_ref, **kwargs): """ Creates an instance of a server. name (Required): The name of the server. image_ref (Required): Reference to the image used to build the server. flavor_ref (Required): The flavor used to build the server. Following optional keyword arguments are accepted: adminPass: Sets the initial root password. key_name: Key name of keypair that was created earlier. meta: A dictionary of values to be used as metadata. personality: A list of dictionaries for files to be injected into the server. security_groups: A list of security group dicts. networks: A list of network dicts with UUID and fixed_ip. user_data: User data for instance. availability_zone: Availability zone in which to launch instance. accessIPv4: The IPv4 access address for the server. accessIPv6: The IPv6 access address for the server. min_count: Count of minimum number of instances to launch. max_count: Count of maximum number of instances to launch. disk_config: Determines if user or admin controls disk configuration. return_reservation_id: Enable/Disable the return of reservation id """ post_body = { 'name': name, 'imageRef': image_ref, 'flavorRef': flavor_ref } for option in ['personality', 'adminPass', 'key_name', 'security_groups', 'networks', 'user_data', 'availability_zone', 'accessIPv4', 'accessIPv6', 'min_count', 'max_count', ('metadata', 'meta'), ('OS-DCF:diskConfig', 'disk_config'), 'return_reservation_id']: if isinstance(option, tuple): post_param = option[0] key = option[1] else: post_param = option key = option value = kwargs.get(key) if value is not None: post_body[post_param] = value post_body = json.dumps({'server': post_body}) resp, body = self.post('servers', post_body, self.headers) body = json.loads(body) # NOTE(maurosr): this deals with the case of multiple server create # with return reservation id set True if 'reservation_id' in body: return resp, body return resp, body['server'] def update_server(self, server_id, name=None, meta=None, accessIPv4=None, accessIPv6=None): """ Updates the properties of an existing server. server_id: The id of an existing server. name: The name of the server. personality: A list of files to be injected into the server. accessIPv4: The IPv4 access address for the server. accessIPv6: The IPv6 access address for the server. """ post_body = {} if meta is not None: post_body['metadata'] = meta if name is not None: post_body['name'] = name if accessIPv4 is not None: post_body['accessIPv4'] = accessIPv4 if accessIPv6 is not None: post_body['accessIPv6'] = accessIPv6 post_body = json.dumps({'server': post_body}) resp, body = self.put("servers/%s" % str(server_id), post_body, self.headers) body = json.loads(body) return resp, body['server'] def get_server(self, server_id): """Returns the details of an existing server.""" resp, body = self.get("servers/%s" % str(server_id)) body = json.loads(body) return resp, body['server'] def delete_server(self, server_id): """Deletes the given server.""" return self.delete("servers/%s" % str(server_id)) def list_servers(self, params=None): """Lists all servers for a user.""" url = 'servers' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body def list_servers_with_detail(self, params=None): """Lists all servers in detail for a user.""" url = 'servers/detail' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body def wait_for_server_status(self, server_id, status): """Waits for a server to reach a given status.""" resp, body = self.get_server(server_id) server_status = body['status'] start = int(time.time()) while(server_status != status): time.sleep(self.build_interval) resp, body = self.get_server(server_id) server_status = body['status'] if server_status == 'ERROR': raise exceptions.BuildErrorException(server_id=server_id) timed_out = int(time.time()) - start >= self.build_timeout if server_status != status and timed_out: message = ('Server %s failed to reach %s status within the ' 'required time (%s s).' % (server_id, status, self.build_timeout)) message += ' Current status: %s.' % server_status raise exceptions.TimeoutException(message) def wait_for_server_termination(self, server_id, ignore_error=False): """Waits for server to reach termination.""" start_time = int(time.time()) while True: try: resp, body = self.get_server(server_id) except exceptions.NotFound: return server_status = body['status'] if server_status == 'ERROR' and not ignore_error: raise exceptions.BuildErrorException(server_id=server_id) if int(time.time()) - start_time >= self.build_timeout: raise exceptions.TimeoutException time.sleep(self.build_interval) def list_addresses(self, server_id): """Lists all addresses for a server.""" resp, body = self.get("servers/%s/ips" % str(server_id)) body = json.loads(body) return resp, body['addresses'] def list_addresses_by_network(self, server_id, network_id): """Lists all addresses of a specific network type for a server.""" resp, body = self.get("servers/%s/ips/%s" % (str(server_id), network_id)) body = json.loads(body) return resp, body def action(self, server_id, action_name, response_key, **kwargs): post_body = json.dumps({action_name: kwargs}) resp, body = self.post('servers/%s/action' % str(server_id), post_body, self.headers) if response_key is not None: body = json.loads(body)[response_key] return resp, body def change_password(self, server_id, adminPass): """Changes the root password for the server.""" return self.action(server_id, 'changePassword', None, adminPass=adminPass) def reboot(self, server_id, reboot_type): """Reboots a server.""" return self.action(server_id, 'reboot', None, type=reboot_type) def rebuild(self, server_id, image_ref, **kwargs): """Rebuilds a server with a new image.""" kwargs['imageRef'] = image_ref if 'disk_config' in kwargs: kwargs['OS-DCF:diskConfig'] = kwargs['disk_config'] del kwargs['disk_config'] return self.action(server_id, 'rebuild', 'server', **kwargs) def resize(self, server_id, flavor_ref, **kwargs): """Changes the flavor of a server.""" kwargs['flavorRef'] = flavor_ref if 'disk_config' in kwargs: kwargs['OS-DCF:diskConfig'] = kwargs['disk_config'] del kwargs['disk_config'] return self.action(server_id, 'resize', None, **kwargs) def confirm_resize(self, server_id, **kwargs): """Confirms the flavor change for a server.""" return self.action(server_id, 'confirmResize', None, **kwargs) def revert_resize(self, server_id, **kwargs): """Reverts a server back to its original flavor.""" return self.action(server_id, 'revertResize', None, **kwargs) def create_image(self, server_id, name): """Creates an image of the given server.""" return self.action(server_id, 'createImage', None, name=name) def list_server_metadata(self, server_id): resp, body = self.get("servers/%s/metadata" % str(server_id)) body = json.loads(body) return resp, body['metadata'] def set_server_metadata(self, server_id, meta): post_body = json.dumps({'metadata': meta}) resp, body = self.put('servers/%s/metadata' % str(server_id), post_body, self.headers) body = json.loads(body) return resp, body['metadata'] def update_server_metadata(self, server_id, meta): post_body = json.dumps({'metadata': meta}) resp, body = self.post('servers/%s/metadata' % str(server_id), post_body, self.headers) body = json.loads(body) return resp, body['metadata'] def get_server_metadata_item(self, server_id, key): resp, body = self.get("servers/%s/metadata/%s" % (str(server_id), key)) body = json.loads(body) return resp, body['meta'] def set_server_metadata_item(self, server_id, key, meta): post_body = json.dumps({'meta': meta}) resp, body = self.put('servers/%s/metadata/%s' % (str(server_id), key), post_body, self.headers) body = json.loads(body) return resp, body['meta'] def delete_server_metadata_item(self, server_id, key): resp, body = self.delete("servers/%s/metadata/%s" % (str(server_id), key)) return resp, body def stop(self, server_id, **kwargs): return self.action(server_id, 'os-stop', None, **kwargs) def start(self, server_id, **kwargs): return self.action(server_id, 'os-start', None, **kwargs) def attach_volume(self, server_id, volume_id, device='/dev/vdz'): """Attaches a volume to a server instance.""" post_body = json.dumps({ 'volumeAttachment': { 'volumeId': volume_id, 'device': device, } }) resp, body = self.post('servers/%s/os-volume_attachments' % server_id, post_body, self.headers) return resp, body def detach_volume(self, server_id, volume_id): """Detaches a volume from a server instance.""" resp, body = self.delete('servers/%s/os-volume_attachments/%s' % (server_id, volume_id)) return resp, body def add_security_group(self, server_id, name): """Adds a security group to the server.""" return self.action(server_id, 'addSecurityGroup', None, name=name) def remove_security_group(self, server_id, name): """Removes a security group from the server.""" return self.action(server_id, 'removeSecurityGroup', None, name=name) def live_migrate_server(self, server_id, dest_host, use_block_migration): """This should be called with administrator privileges .""" migrate_params = { "disk_over_commit": False, "block_migration": use_block_migration, "host": dest_host } req_body = json.dumps({'os-migrateLive': migrate_params}) resp, body = self.post("servers/%s/action" % str(server_id), req_body, self.headers) return resp, body def list_servers_for_all_tenants(self): url = self.base_url + '/servers?all_tenants=1' resp = self.requests.get(url) resp, body = self.get('servers', self.headers) body = json.loads(body) return resp, body['servers'] def migrate_server(self, server_id, **kwargs): """Migrates a server to a new host.""" return self.action(server_id, 'migrate', None, **kwargs) def lock_server(self, server_id, **kwargs): """Locks the given server.""" return self.action(server_id, 'lock', None, **kwargs) def unlock_server(self, server_id, **kwargs): """UNlocks the given server.""" return self.action(server_id, 'unlock', None, **kwargs) def suspend_server(self, server_id, **kwargs): """Suspends the provded server.""" return self.action(server_id, 'suspend', None, **kwargs) def resume_server(self, server_id, **kwargs): """Un-suspends the provded server.""" return self.action(server_id, 'resume', None, **kwargs) def pause_server(self, server_id, **kwargs): """Pauses the provded server.""" return self.action(server_id, 'pause', None, **kwargs) def unpause_server(self, server_id, **kwargs): """Un-pauses the provded server.""" return self.action(server_id, 'unpause', None, **kwargs) def reset_state(self, server_id, state='error'): """Resets the state of a server to active/error.""" return self.action(server_id, 'os-resetState', None, state=state) def get_console_output(self, server_id, length): return self.action(server_id, 'os-getConsoleOutput', 'output', length=length) def list_virtual_interfaces(self, server_id): """ List the virtual interfaces used in an instance. """ resp, body = self.get('/'.join(['servers', server_id, 'os-virtual-interfaces'])) return resp, json.loads(body) def rescue_server(self, server_id, adminPass=None): """Rescue the provided server.""" return self.action(server_id, 'rescue', None, adminPass=adminPass) def unrescue_server(self, server_id): """Unrescue the provided server.""" return self.action(server_id, 'unrescue', None) def list_instance_actions(self, server_id): """List the provided server action.""" resp, body = self.get("servers/%s/os-instance-actions" % str(server_id)) body = json.loads(body) return resp, body['instanceActions'] def get_instance_action(self, server_id, request_id): """Returns the action details of the provided server.""" resp, body = self.get("servers/%s/os-instance-actions/%s" % (str(server_id), str(request_id))) body = json.loads(body) return resp, body['instanceAction'] tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/json/tenant_usages_client.py0000664000175000017500000000311712161375672030161 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 NEC Corporation # 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 json import urllib from tempest.common.rest_client import RestClient class TenantUsagesClientJSON(RestClient): def __init__(self, config, username, password, auth_url, tenant_name=None): super(TenantUsagesClientJSON, self).__init__( config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type def list_tenant_usages(self, params=None): url = 'os-simple-tenant-usage' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body['tenant_usages'][0] def get_tenant_usage(self, tenant_id, params=None): url = 'os-simple-tenant-usage/%s' % tenant_id if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body['tenant_usage'] tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/json/volumes_extensions_client.py0000664000175000017500000000773012161375672031277 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 json import time import urllib from tempest.common.rest_client import RestClient from tempest import exceptions class VolumesExtensionsClientJSON(RestClient): def __init__(self, config, username, password, auth_url, tenant_name=None): super(VolumesExtensionsClientJSON, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type self.build_interval = self.config.volume.build_interval self.build_timeout = self.config.volume.build_timeout def list_volumes(self, params=None): """List all the volumes created.""" url = 'os-volumes' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body['volumes'] def list_volumes_with_detail(self, params=None): """List all the details of volumes.""" url = 'os-volumes/detail' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body['volumes'] def get_volume(self, volume_id): """Returns the details of a single volume.""" url = "os-volumes/%s" % str(volume_id) resp, body = self.get(url) body = json.loads(body) return resp, body['volume'] def create_volume(self, size, **kwargs): """ Creates a new Volume. size(Required): Size of volume in GB. Following optional keyword arguments are accepted: display_name: Optional Volume Name. metadata: A dictionary of values to be used as metadata. """ post_body = { 'size': size, 'display_name': kwargs.get('display_name'), 'metadata': kwargs.get('metadata'), } post_body = json.dumps({'volume': post_body}) resp, body = self.post('os-volumes', post_body, self.headers) body = json.loads(body) return resp, body['volume'] def delete_volume(self, volume_id): """Deletes the Specified Volume.""" return self.delete("os-volumes/%s" % str(volume_id)) def wait_for_volume_status(self, volume_id, status): """Waits for a Volume to reach a given status.""" resp, body = self.get_volume(volume_id) volume_name = body['displayName'] volume_status = body['status'] start = int(time.time()) while volume_status != status: time.sleep(self.build_interval) resp, body = self.get_volume(volume_id) volume_status = body['status'] if volume_status == 'error': raise exceptions.VolumeBuildErrorException(volume_id=volume_id) if int(time.time()) - start >= self.build_timeout: message = ('Volume %s failed to reach %s status within ' 'the required time (%s s).' % (volume_name, status, self.build_timeout)) raise exceptions.TimeoutException(message) def is_resource_deleted(self, id): try: self.get_volume(id) except exceptions.NotFound: return True return False tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/json/flavors_client.py0000664000175000017500000001156112161375672026777 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 json import urllib from tempest.common.rest_client import RestClient class FlavorsClientJSON(RestClient): def __init__(self, config, username, password, auth_url, tenant_name=None): super(FlavorsClientJSON, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type def list_flavors(self, params=None): url = 'flavors' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body['flavors'] def list_flavors_with_detail(self, params=None): url = 'flavors/detail' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body['flavors'] def get_flavor_details(self, flavor_id): resp, body = self.get("flavors/%s" % str(flavor_id)) body = json.loads(body) return resp, body['flavor'] def create_flavor(self, name, ram, vcpus, disk, flavor_id, **kwargs): """Creates a new flavor or instance type.""" post_body = { 'name': name, 'ram': ram, 'vcpus': vcpus, 'disk': disk, 'id': flavor_id, } if kwargs.get('ephemeral'): post_body['OS-FLV-EXT-DATA:ephemeral'] = kwargs.get('ephemeral') if kwargs.get('swap'): post_body['swap'] = kwargs.get('swap') if kwargs.get('rxtx'): post_body['rxtx_factor'] = kwargs.get('rxtx') if kwargs.get('is_public'): post_body['os-flavor-access:is_public'] = kwargs.get('is_public') post_body = json.dumps({'flavor': post_body}) resp, body = self.post('flavors', post_body, self.headers) body = json.loads(body) return resp, body['flavor'] def delete_flavor(self, flavor_id): """Deletes the given flavor.""" return self.delete("flavors/%s" % str(flavor_id)) def is_resource_deleted(self, id): #Did not use get_flavor_details(id) for verification as it gives #200 ok even for deleted id. LP #981263 #we can remove the loop here and use get by ID when bug gets sortedout resp, flavors = self.list_flavors_with_detail() for flavor in flavors: if flavor['id'] == id: return False return True def set_flavor_extra_spec(self, flavor_id, specs): """Sets extra Specs to the mentioned flavor.""" post_body = json.dumps({'extra_specs': specs}) resp, body = self.post('flavors/%s/os-extra_specs' % flavor_id, post_body, self.headers) body = json.loads(body) return resp, body['extra_specs'] def get_flavor_extra_spec(self, flavor_id): """Gets extra Specs details of the mentioned flavor.""" resp, body = self.get('flavors/%s/os-extra_specs' % flavor_id) body = json.loads(body) return resp, body['extra_specs'] def unset_flavor_extra_spec(self, flavor_id, key): """Unsets extra Specs from the mentioned flavor.""" return self.delete('flavors/%s/os-extra_specs/%s' % (str(flavor_id), key)) def add_flavor_access(self, flavor_id, tenant_id): """Add flavor access for the specified tenant.""" post_body = { 'addTenantAccess': { 'tenant': tenant_id } } post_body = json.dumps(post_body) resp, body = self.post('flavors/%s/action' % flavor_id, post_body, self.headers) body = json.loads(body) return resp, body['flavor_access'] def remove_flavor_access(self, flavor_id, tenant_id): """Remove flavor access from the specified tenant.""" post_body = { 'removeTenantAccess': { 'tenant': tenant_id } } post_body = json.dumps(post_body) resp, body = self.post('flavors/%s/action' % flavor_id, post_body, self.headers) body = json.loads(body) return resp, body['flavor_access'] tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/json/keypairs_client.py0000664000175000017500000000416712161375672027156 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 json from tempest.common.rest_client import RestClient class KeyPairsClientJSON(RestClient): def __init__(self, config, username, password, auth_url, tenant_name=None): super(KeyPairsClientJSON, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type def list_keypairs(self): resp, body = self.get("os-keypairs") body = json.loads(body) #Each returned keypair is embedded within an unnecessary 'keypair' #element which is a deviation from other resources like floating-ips, #servers, etc. A bug? #For now we shall adhere to the spec, but the spec for keypairs #is yet to be found return resp, body['keypairs'] def get_keypair(self, key_name): resp, body = self.get("os-keypairs/%s" % str(key_name)) body = json.loads(body) return resp, body['keypair'] def create_keypair(self, name, pub_key=None): post_body = {'keypair': {'name': name}} if pub_key: post_body['keypair']['public_key'] = pub_key post_body = json.dumps(post_body) resp, body = self.post("os-keypairs", headers=self.headers, body=post_body) body = json.loads(body) return resp, body['keypair'] def delete_keypair(self, key_name): return self.delete("os-keypairs/%s" % str(key_name)) tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/json/limits_client.py0000664000175000017500000000274212161375672026625 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 json from tempest.common.rest_client import RestClient class LimitsClientJSON(RestClient): def __init__(self, config, username, password, auth_url, tenant_name=None): super(LimitsClientJSON, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type def get_absolute_limits(self): resp, body = self.get("limits") body = json.loads(body) return resp, body['limits']['absolute'] def get_specific_absolute_limit(self, absolute_limit): resp, body = self.get("limits") body = json.loads(body) if absolute_limit not in body['limits']['absolute']: return None else: return body['limits']['absolute'][absolute_limit] tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/json/quotas_client.py0000664000175000017500000000704112161375672026635 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # # Copyright 2012 NTT Data # 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 json from tempest.common.rest_client import RestClient class QuotasClientJSON(RestClient): def __init__(self, config, username, password, auth_url, tenant_name=None): super(QuotasClientJSON, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type def get_quota_set(self, tenant_id): """List the quota set for a tenant.""" url = 'os-quota-sets/%s' % str(tenant_id) resp, body = self.get(url) body = json.loads(body) return resp, body['quota_set'] def get_default_quota_set(self, tenant_id): """List the default quota set for a tenant.""" url = 'os-quota-sets/%s/defaults' % str(tenant_id) resp, body = self.get(url) body = json.loads(body) return resp, body['quota_set'] def update_quota_set(self, tenant_id, force=None, injected_file_content_bytes=None, metadata_items=None, ram=None, floating_ips=None, fixed_ips=None, key_pairs=None, instances=None, security_group_rules=None, injected_files=None, cores=None, injected_file_path_bytes=None, security_groups=None): """ Updates the tenant's quota limits for one or more resources """ post_body = {} if force is not None: post_body['force'] = force if injected_file_content_bytes is not None: post_body['injected_file_content_bytes'] = \ injected_file_content_bytes if metadata_items is not None: post_body['metadata_items'] = metadata_items if ram is not None: post_body['ram'] = ram if floating_ips is not None: post_body['floating_ips'] = floating_ips if fixed_ips is not None: post_body['fixed_ips'] = fixed_ips if key_pairs is not None: post_body['key_pairs'] = key_pairs if instances is not None: post_body['instances'] = instances if security_group_rules is not None: post_body['security_group_rules'] = security_group_rules if injected_files is not None: post_body['injected_files'] = injected_files if cores is not None: post_body['cores'] = cores if injected_file_path_bytes is not None: post_body['injected_file_path_bytes'] = injected_file_path_bytes if security_groups is not None: post_body['security_groups'] = security_groups post_body = json.dumps({'quota_set': post_body}) resp, body = self.put('os-quota-sets/%s' % str(tenant_id), post_body, self.headers) body = json.loads(body) return resp, body['quota_set'] tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/json/hypervisor_client.py0000664000175000017500000000477612161375672027547 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 IBM Corporation. # 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 json from tempest.common.rest_client import RestClient class HypervisorClientJSON(RestClient): def __init__(self, config, username, password, auth_url, tenant_name=None): super(HypervisorClientJSON, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type def get_hypervisor_list(self): """List hypervisors information.""" resp, body = self.get('os-hypervisors') body = json.loads(body) return resp, body['hypervisors'] def get_hypervisor_list_details(self): """Show detailed hypervisors information.""" resp, body = self.get('os-hypervisors/detail') body = json.loads(body) return resp, body['hypervisors'] def get_hypervisor_show_details(self, hyper_id): """Display the details of the specified hypervisor.""" resp, body = self.get('os-hypervisors/%s' % hyper_id) body = json.loads(body) return resp, body['hypervisor'] def get_hypervisor_servers(self, hyper_name): """List instances belonging to the specified hypervisor.""" resp, body = self.get('os-hypervisors/%s/servers' % hyper_name) body = json.loads(body) return resp, body['hypervisors'] def get_hypervisor_stats(self): """Get hypervisor statistics over all compute nodes.""" resp, body = self.get('os-hypervisors/statistics') body = json.loads(body) return resp, body['hypervisor_statistics'] def get_hypervisor_uptime(self, hyper_id): """Display the uptime of the specified hypervisor.""" resp, body = self.get('os-hypervisors/%s/uptime' % hyper_id) body = json.loads(body) return resp, body['hypervisor'] tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/json/__init__.py0000664000175000017500000000000012161375672025506 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/json/fixed_ips_client.py0000664000175000017500000000272412161375672027276 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 IBM Corp # 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 json from tempest.common.rest_client import RestClient class FixedIPsClientJSON(RestClient): def __init__(self, config, username, password, auth_url, tenant_name=None): super(FixedIPsClientJSON, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type def get_fixed_ip_details(self, fixed_ip): url = "os-fixed-ips/%s" % (fixed_ip) resp, body = self.get(url) body = json.loads(body) return resp, body['fixed_ip'] def reserve_fixed_ip(self, ip, body): """This reserves and unreserves fixed ips.""" url = "os-fixed-ips/%s/action" % (ip) resp, body = self.post(url, json.dumps(body), self.headers) return resp, body tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/json/images_client.py0000664000175000017500000001326112161375672026567 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 json import time import urllib from tempest.common.rest_client import RestClient from tempest import exceptions class ImagesClientJSON(RestClient): def __init__(self, config, username, password, auth_url, tenant_name=None): super(ImagesClientJSON, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type self.build_interval = self.config.compute.build_interval self.build_timeout = self.config.compute.build_timeout def create_image(self, server_id, name, meta=None): """Creates an image of the original server.""" post_body = { 'createImage': { 'name': name, } } if meta is not None: post_body['createImage']['metadata'] = meta post_body = json.dumps(post_body) resp, body = self.post('servers/%s/action' % str(server_id), post_body, self.headers) return resp, body def list_images(self, params=None): """Returns a list of all images filtered by any parameters.""" url = 'images' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body['images'] def list_images_with_detail(self, params=None): """Returns a detailed list of images filtered by any parameters.""" url = 'images/detail' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body['images'] def get_image(self, image_id): """Returns the details of a single image.""" resp, body = self.get("images/%s" % str(image_id)) body = json.loads(body) return resp, body['image'] def delete_image(self, image_id): """Deletes the provided image.""" return self.delete("images/%s" % str(image_id)) def wait_for_image_resp_code(self, image_id, code): """ Waits until the HTTP response code for the request matches the expected value """ resp, body = self.get("images/%s" % str(image_id)) start = int(time.time()) while resp.status != code: time.sleep(self.build_interval) resp, body = self.get("images/%s" % str(image_id)) if int(time.time()) - start >= self.build_timeout: raise exceptions.TimeoutException def wait_for_image_status(self, image_id, status): """Waits for an image to reach a given status.""" resp, image = self.get_image(image_id) start = int(time.time()) while image['status'] != status: time.sleep(self.build_interval) resp, image = self.get_image(image_id) if image['status'] == 'ERROR': raise exceptions.AddImageException(image_id=image_id) if int(time.time()) - start >= self.build_timeout: raise exceptions.TimeoutException def list_image_metadata(self, image_id): """Lists all metadata items for an image.""" resp, body = self.get("images/%s/metadata" % str(image_id)) body = json.loads(body) return resp, body['metadata'] def set_image_metadata(self, image_id, meta): """Sets the metadata for an image.""" post_body = json.dumps({'metadata': meta}) resp, body = self.put('images/%s/metadata' % str(image_id), post_body, self.headers) body = json.loads(body) return resp, body['metadata'] def update_image_metadata(self, image_id, meta): """Updates the metadata for an image.""" post_body = json.dumps({'metadata': meta}) resp, body = self.post('images/%s/metadata' % str(image_id), post_body, self.headers) body = json.loads(body) return resp, body['metadata'] def get_image_metadata_item(self, image_id, key): """Returns the value for a specific image metadata key.""" resp, body = self.get("images/%s/metadata/%s" % (str(image_id), key)) body = json.loads(body) return resp, body['meta'] def set_image_metadata_item(self, image_id, key, meta): """Sets the value for a specific image metadata key.""" post_body = json.dumps({'meta': meta}) resp, body = self.put('images/%s/metadata/%s' % (str(image_id), key), post_body, self.headers) body = json.loads(body) return resp, body['meta'] def delete_image_metadata_item(self, image_id, key): """Deletes a single image metadata key/value pair.""" resp, body = self.delete("images/%s/metadata/%s" % (str(image_id), key)) return resp, body def is_resource_deleted(self, id): try: self.get_image(id) except exceptions.NotFound: return True return False tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/json/security_groups_client.py0000664000175000017500000001005312161375672030564 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 json import urllib from tempest.common.rest_client import RestClient from tempest import exceptions class SecurityGroupsClientJSON(RestClient): def __init__(self, config, username, password, auth_url, tenant_name=None): super(SecurityGroupsClientJSON, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type def list_security_groups(self, params=None): """List all security groups for a user.""" url = 'os-security-groups' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) return resp, body['security_groups'] def get_security_group(self, security_group_id): """Get the details of a Security Group.""" url = "os-security-groups/%s" % str(security_group_id) resp, body = self.get(url) body = json.loads(body) return resp, body['security_group'] def create_security_group(self, name, description): """ Creates a new security group. name (Required): Name of security group. description (Required): Description of security group. """ post_body = { 'name': name, 'description': description, } post_body = json.dumps({'security_group': post_body}) resp, body = self.post('os-security-groups', post_body, self.headers) body = json.loads(body) return resp, body['security_group'] def delete_security_group(self, security_group_id): """Deletes the provided Security Group.""" return self.delete('os-security-groups/%s' % str(security_group_id)) def create_security_group_rule(self, parent_group_id, ip_proto, from_port, to_port, **kwargs): """ Creating a new security group rules. parent_group_id :ID of Security group ip_protocol : ip_proto (icmp, tcp, udp). from_port: Port at start of range. to_port : Port at end of range. Following optional keyword arguments are accepted: cidr : CIDR for address range. group_id : ID of the Source group """ post_body = { 'parent_group_id': parent_group_id, 'ip_protocol': ip_proto, 'from_port': from_port, 'to_port': to_port, 'cidr': kwargs.get('cidr'), 'group_id': kwargs.get('group_id'), } post_body = json.dumps({'security_group_rule': post_body}) url = 'os-security-group-rules' resp, body = self.post(url, post_body, self.headers) body = json.loads(body) return resp, body['security_group_rule'] def delete_security_group_rule(self, group_rule_id): """Deletes the provided Security Group rule.""" return self.delete('os-security-group-rules/%s' % str(group_rule_id)) def list_security_group_rules(self, security_group_id): """List all rules for a security group.""" resp, body = self.get('os-security-groups') body = json.loads(body) for sg in body['security_groups']: if sg['id'] == security_group_id: return resp, sg['rules'] raise exceptions.NotFound('No such Security Group') tempest-2013.2.a1291.g23a1b4f/tempest/services/compute/json/availability_zone_client.py0000664000175000017500000000275712161375672031037 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2013 NEC Corporation. # 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 json from tempest.common.rest_client import RestClient class AvailabilityZoneClientJSON(RestClient): def __init__(self, config, username, password, auth_url, tenant_name=None): super(AvailabilityZoneClientJSON, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.compute.catalog_type def get_availability_zone_list(self): resp, body = self.get('os-availability-zone') body = json.loads(body) return resp, body['availabilityZoneInfo'] def get_availability_zone_list_detail(self): resp, body = self.get('os-availability-zone/detail') body = json.loads(body) return resp, body['availabilityZoneInfo'] tempest-2013.2.a1291.g23a1b4f/tempest/services/__init__.py0000664000175000017500000000217212161375672023075 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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. """ Base Service class, which acts as a descriptor for an OpenStack service in the test environment """ class Service(object): def __init__(self, config): """ Initializes the service. :param config: `tempest.config.Config` object """ self.config = config def get_client(self): """ Returns a client object that may be used to query the service API. """ raise NotImplementedError tempest-2013.2.a1291.g23a1b4f/tempest/services/orchestration/0000775000175000017500000000000012161375700023636 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/orchestration/__init__.py0000664000175000017500000000000012161375672025745 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/orchestration/json/0000775000175000017500000000000012161375700024607 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/orchestration/json/orchestration_client.py0000664000175000017500000000732212161375672031417 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # # Copyright 2013 IBM Corp. # 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 json import time import urllib from tempest.common import rest_client from tempest import exceptions class OrchestrationClient(rest_client.RestClient): def __init__(self, config, username, password, auth_url, tenant_name=None): super(OrchestrationClient, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.orchestration.catalog_type self.build_interval = self.config.orchestration.build_interval self.build_timeout = self.config.orchestration.build_timeout def list_stacks(self, params=None): """Lists all stacks for a user.""" uri = 'stacks' if params: uri += '?%s' % urllib.urlencode(params) resp, body = self.get(uri) body = json.loads(body) return resp, body def create_stack(self, name, disable_rollback=True, parameters={}, timeout_mins=60, template=None, template_url=None): post_body = { "stack_name": name, "disable_rollback": disable_rollback, "parameters": parameters, "timeout_mins": timeout_mins, "template": "HeatTemplateFormatVersion: '2012-12-12'\n" } if template: post_body['template'] = template if template_url: post_body['template_url'] = template_url body = json.dumps(post_body) uri = 'stacks' resp, body = self.post(uri, headers=self.headers, body=body) return resp, body def get_stack(self, stack_identifier): """Returns the details of a single stack.""" url = "stacks/%s" % stack_identifier resp, body = self.get(url) body = json.loads(body) return resp, body['stack'] def delete_stack(self, stack_identifier): """Deletes the specified Stack.""" return self.delete("stacks/%s" % str(stack_identifier)) def wait_for_stack_status(self, stack_identifier, status, failure_status=( 'CREATE_FAILED', 'DELETE_FAILED', 'UPDATE_FAILED', 'ROLLBACK_FAILED')): """Waits for a Volume to reach a given status.""" stack_status = None start = int(time.time()) while stack_status != status: resp, body = self.get_stack(stack_identifier) stack_name = body['stack_name'] stack_status = body['stack_status'] if stack_status in failure_status: raise exceptions.StackBuildErrorException( stack_identifier=stack_identifier, stack_status=stack_status, stack_status_reason=body['stack_status_reason']) if int(time.time()) - start >= self.build_timeout: message = ('Stack %s failed to reach %s status within ' 'the required time (%s s).' % (stack_name, status, self.build_timeout)) raise exceptions.TimeoutException(message) time.sleep(self.build_interval) tempest-2013.2.a1291.g23a1b4f/tempest/services/orchestration/json/__init__.py0000664000175000017500000000000012161375672026716 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/network/0000775000175000017500000000000012161375700022443 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/network/__init__.py0000664000175000017500000000000012161375672024552 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/network/json/0000775000175000017500000000000012161375700023414 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/network/json/network_client.py0000664000175000017500000000705512161375672027034 0ustar chuckchuck00000000000000import json from tempest.common.rest_client import RestClient class NetworkClient(RestClient): """ Tempest REST client for Quantum. Uses v2 of the Quantum API, since the V1 API has been removed from the code base. Implements the following operations for each one of the basic Quantum abstractions (networks, sub-networks and ports): create delete list show """ def __init__(self, config, username, password, auth_url, tenant_name=None): super(NetworkClient, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.network.catalog_type self.version = '2.0' self.uri_prefix = "v%s" % (self.version) def list_networks(self): uri = '%s/networks' % (self.uri_prefix) resp, body = self.get(uri, self.headers) body = json.loads(body) return resp, body def create_network(self, name): post_body = { 'network': { 'name': name, } } body = json.dumps(post_body) uri = '%s/networks' % (self.uri_prefix) resp, body = self.post(uri, headers=self.headers, body=body) body = json.loads(body) return resp, body def show_network(self, uuid): uri = '%s/networks/%s' % (self.uri_prefix, uuid) resp, body = self.get(uri, self.headers) body = json.loads(body) return resp, body def delete_network(self, uuid): uri = '%s/networks/%s' % (self.uri_prefix, uuid) resp, body = self.delete(uri, self.headers) return resp, body def create_subnet(self, net_uuid, cidr): post_body = dict( subnet=dict( ip_version=4, network_id=net_uuid, cidr=cidr),) body = json.dumps(post_body) uri = '%s/subnets' % (self.uri_prefix) resp, body = self.post(uri, headers=self.headers, body=body) body = json.loads(body) return resp, body def delete_subnet(self, uuid): uri = '%s/subnets/%s' % (self.uri_prefix, uuid) resp, body = self.delete(uri, self.headers) return resp, body def list_subnets(self): uri = '%s/subnets' % (self.uri_prefix) resp, body = self.get(uri, self.headers) body = json.loads(body) return resp, body def show_subnet(self, uuid): uri = '%s/subnets/%s' % (self.uri_prefix, uuid) resp, body = self.get(uri, self.headers) body = json.loads(body) return resp, body def create_port(self, network_id, state=None): if not state: state = True post_body = { 'port': { 'network_id': network_id, 'admin_state_up': state, } } body = json.dumps(post_body) uri = '%s/ports' % (self.uri_prefix) resp, body = self.post(uri, headers=self.headers, body=body) body = json.loads(body) return resp, body def delete_port(self, port_id): uri = '%s/ports/%s' % (self.uri_prefix, port_id) resp, body = self.delete(uri, self.headers) return resp, body def list_ports(self): uri = '%s/ports' % (self.uri_prefix) resp, body = self.get(uri, self.headers) body = json.loads(body) return resp, body def show_port(self, port_id): uri = '%s/ports/%s' % (self.uri_prefix, port_id) resp, body = self.get(uri, self.headers) body = json.loads(body) return resp, body tempest-2013.2.a1291.g23a1b4f/tempest/services/network/json/__init__.py0000664000175000017500000000000012161375672025523 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/image/0000775000175000017500000000000012161375700022034 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/image/v2/0000775000175000017500000000000012161375700022363 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/image/v2/__init__.py0000664000175000017500000000000012161375672024472 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/image/v2/json/0000775000175000017500000000000012161375700023334 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/image/v2/json/__init__.py0000664000175000017500000000000012161375672025443 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/image/v2/json/image_client.py0000664000175000017500000001014212161375672026334 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # # Copyright 2013 IBM Corp. # 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 json import urllib import jsonschema from tempest.common import glance_http from tempest.common import rest_client from tempest import exceptions class ImageClientV2JSON(rest_client.RestClient): def __init__(self, config, username, password, auth_url, tenant_name=None): super(ImageClientV2JSON, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.images.catalog_type self.http = self._get_http() def _get_http(self): token, endpoint = self.keystone_auth(self.user, self.password, self.auth_url, self.service, self.tenant_name) dscv = self.config.identity.disable_ssl_certificate_validation return glance_http.HTTPClient(endpoint=endpoint, token=token, insecure=dscv) def get_images_schema(self): url = 'v2/schemas/images' resp, body = self.get(url) body = json.loads(body) return resp, body def get_image_schema(self): url = 'v2/schemas/image' resp, body = self.get(url) body = json.loads(body) return resp, body def _validate_schema(self, body, type='image'): if type == 'image': resp, schema = self.get_image_schema() elif type == 'images': resp, schema = self.get_images_schema() else: raise ValueError("%s is not a valid schema type" % type) jsonschema.validate(body, schema) def create_image(self, name, container_format, disk_format, **kwargs): params = { "name": name, "container_format": container_format, "disk_format": disk_format, } for option in ['visibility']: if option in kwargs: value = kwargs.get(option) if isinstance(value, dict) or isinstance(value, tuple): params.update(value) else: params[option] = value data = json.dumps(params) self._validate_schema(data) resp, body = self.post('v2/images', data, self.headers) body = json.loads(body) return resp, body def delete_image(self, image_id): url = 'v2/images/%s' % image_id self.delete(url) def image_list(self, params=None): url = 'v2/images' if params: url += '?%s' % urllib.urlencode(params) resp, body = self.get(url) body = json.loads(body) self._validate_schema(body, type='images') return resp, body['images'] def get_image_metadata(self, image_id): url = 'v2/images/%s' % image_id resp, body = self.get(url) body = json.loads(body) return resp, body def is_resource_deleted(self, id): try: self.get_image_metadata(id) except exceptions.NotFound: return True return False def store_image(self, image_id, data): url = 'v2/images/%s/file' % image_id headers = {'Content-Type': 'application/octet-stream'} resp, body = self.http.raw_request('PUT', url, headers=headers, body=data) return resp, body def get_image_file(self, image_id): url = 'v2/images/%s/file' % image_id resp, body = self.get(url) return resp, body tempest-2013.2.a1291.g23a1b4f/tempest/services/image/v1/0000775000175000017500000000000012161375700022362 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/image/v1/__init__.py0000664000175000017500000000000012161375672024471 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/image/v1/json/0000775000175000017500000000000012161375700023333 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/image/v1/json/__init__.py0000664000175000017500000000000012161375672025442 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/services/image/v1/json/image_client.py0000664000175000017500000002375712161375672026353 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # # Copyright 2013 IBM Corp. # 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 copy import errno import json import os import time import urllib from tempest.common import glance_http from tempest.common import log as logging from tempest.common.rest_client import RestClient from tempest import exceptions LOG = logging.getLogger(__name__) class ImageClientJSON(RestClient): def __init__(self, config, username, password, auth_url, tenant_name=None): super(ImageClientJSON, self).__init__(config, username, password, auth_url, tenant_name) self.service = self.config.images.catalog_type self.http = self._get_http() def _image_meta_from_headers(self, headers): meta = {'properties': {}} for key, value in headers.iteritems(): if key.startswith('x-image-meta-property-'): _key = key[22:] meta['properties'][_key] = value elif key.startswith('x-image-meta-'): _key = key[13:] meta[_key] = value for key in ['is_public', 'protected', 'deleted']: if key in meta: meta[key] = meta[key].strip().lower() in ('t', 'true', 'yes', '1') for key in ['size', 'min_ram', 'min_disk']: if key in meta: try: meta[key] = int(meta[key]) except ValueError: pass return meta def _image_meta_to_headers(self, fields): headers = {} fields_copy = copy.deepcopy(fields) copy_from = fields_copy.pop('copy_from', None) if copy_from is not None: headers['x-glance-api-copy-from'] = copy_from for key, value in fields_copy.pop('properties', {}).iteritems(): headers['x-image-meta-property-%s' % key] = str(value) for key, value in fields_copy.pop('api', {}).iteritems(): headers['x-glance-api-property-%s' % key] = str(value) for key, value in fields_copy.iteritems(): headers['x-image-meta-%s' % key] = str(value) return headers def _get_file_size(self, obj): """Analyze file-like object and attempt to determine its size. :param obj: file-like object, typically redirected from stdin. :retval The file's size or None if it cannot be determined. """ # For large images, we need to supply the size of the # image file. See LP Bugs #827660 and #845788. if hasattr(obj, 'seek') and hasattr(obj, 'tell'): try: obj.seek(0, os.SEEK_END) obj_size = obj.tell() obj.seek(0) return obj_size except IOError, e: if e.errno == errno.ESPIPE: # Illegal seek. This means the user is trying # to pipe image data to the client, e.g. # echo testdata | bin/glance add blah..., or # that stdin is empty, or that a file-like # object which doesn't support 'seek/tell' has # been supplied. return None else: raise else: # Cannot determine size of input image return None def _get_http(self): token, endpoint = self.keystone_auth(self.user, self.password, self.auth_url, self.service, self.tenant_name) dscv = self.config.identity.disable_ssl_certificate_validation return glance_http.HTTPClient(endpoint=endpoint, token=token, insecure=dscv) def _create_with_data(self, headers, data): resp, body_iter = self.http.raw_request('POST', '/v1/images', headers=headers, body=data) self._error_checker('POST', '/v1/images', headers, data, resp, body_iter) body = json.loads(''.join([c for c in body_iter])) return resp, body['image'] def _update_with_data(self, image_id, headers, data): url = '/v1/images/%s' % image_id resp, body_iter = self.http.raw_request('PUT', url, headers=headers, body=data) self._error_checker('PUT', url, headers, data, resp, body_iter) body = json.loads(''.join([c for c in body_iter])) return resp, body['image'] def create_image(self, name, container_format, disk_format, **kwargs): params = { "name": name, "container_format": container_format, "disk_format": disk_format, } headers = {} for option in ['is_public', 'location', 'properties', 'copy_from', 'min_ram']: if option in kwargs: params[option] = kwargs.get(option) headers.update(self._image_meta_to_headers(params)) if 'data' in kwargs: return self._create_with_data(headers, kwargs.get('data')) resp, body = self.post('v1/images', None, headers) body = json.loads(body) return resp, body['image'] def update_image(self, image_id, name=None, container_format=None, data=None): params = {} headers = {} if name is not None: params['name'] = name if container_format is not None: params['container_format'] = container_format headers.update(self._image_meta_to_headers(params)) if data is not None: return self._update_with_data(image_id, headers, data) url = 'v1/images/%s' % image_id resp, body = self.put(url, data, headers) body = json.loads(body) return resp, body['image'] def delete_image(self, image_id): url = 'v1/images/%s' % image_id self.delete(url) def image_list(self, **kwargs): url = 'v1/images' if len(kwargs) > 0: url += '?%s' % urllib.urlencode(kwargs) resp, body = self.get(url) body = json.loads(body) return resp, body['images'] def image_list_detail(self, **kwargs): url = 'v1/images/detail' if len(kwargs) > 0: url += '?%s' % urllib.urlencode(kwargs) resp, body = self.get(url) body = json.loads(body) return resp, body['images'] def get_image_meta(self, image_id): url = 'v1/images/%s' % image_id resp, __ = self.head(url) body = self._image_meta_from_headers(resp) return resp, body def get_image(self, image_id): url = 'v1/images/%s' % image_id resp, body = self.get(url) return resp, body def is_resource_deleted(self, id): try: self.get_image(id) except exceptions.NotFound: return True return False def get_image_membership(self, image_id): url = 'v1/images/%s/members' % image_id resp, body = self.get(url) body = json.loads(body) return resp, body def get_shared_images(self, member_id): url = 'v1/shared-images/%s' % member_id resp, body = self.get(url) body = json.loads(body) return resp, body def add_member(self, member_id, image_id, can_share=False): url = 'v1/images/%s/members/%s' % (image_id, member_id) body = None if can_share: body = json.dumps({'member': {'can_share': True}}) resp, __ = self.put(url, body, self.headers) return resp def delete_member(self, member_id, image_id): url = 'v1/images/%s/members/%s' % (image_id, member_id) resp, __ = self.delete(url) return resp def replace_membership_list(self, image_id, member_list): url = 'v1/images/%s/members' % image_id body = json.dumps({'membership': member_list}) resp, data = self.put(url, body, self.headers) data = json.loads(data) return resp, data #NOTE(afazekas): just for the wait function def _get_image_status(self, image_id): resp, meta = self.get_image_meta(image_id) status = meta['status'] return status #NOTE(afazkas): Wait reinvented again. It is not in the correct layer def wait_for_image_status(self, image_id, status): """Waits for a Image to reach a given status.""" start_time = time.time() old_value = value = self._get_image_status(image_id) while True: dtime = time.time() - start_time time.sleep(self.build_interval) if value != old_value: LOG.info('Value transition from "%s" to "%s"' 'in %d second(s).', old_value, value, dtime) if (value == status): return value if dtime > self.build_timeout: message = ('Time Limit Exceeded! (%ds)' 'while waiting for %s, ' 'but we got %s.' % (self.build_timeout, status, value)) raise exceptions.TimeoutException(message) time.sleep(self.build_interval) old_value = value value = self._get_image_status(image_id) tempest-2013.2.a1291.g23a1b4f/tempest/services/image/__init__.py0000664000175000017500000000000012161375672024143 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/whitebox/0000775000175000017500000000000012161375700020760 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/whitebox/test_servers_whitebox.py0000664000175000017500000002001512161375672026001 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.identity.base import BaseIdentityAdminTest from tempest import exceptions from tempest.whitebox import manager class ServersWhiteboxTest(manager.ComputeWhiteboxTest): _interface = 'json' @classmethod def setUpClass(cls): raise cls.skipException("Until Bug 1034129 is fixed") super(ServersWhiteboxTest, cls).setUpClass() #NOTE(afazekas): Strange relationship BaseIdentityAdminTest.setUpClass() cls.client = cls.servers_client cls.img_client = cls.images_client cls.admin_client = BaseIdentityAdminTest.client cls.connection, cls.meta = cls.get_db_handle_and_meta() resp, tenants = cls.admin_client.list_tenants() cls.tenant_id = [ tnt['id'] for tnt in tenants if tnt['name'] == cls.config.compute.tenant_name ][0] cls.shared_server = cls.create_server() def tearDown(cls): for server in cls.servers: try: cls.client.delete_server(server['id']) except exceptions.NotFound: continue def update_state(self, server_id, vm_state, task_state, deleted=0): """Update states of an instance in database for validation.""" if not task_state: task_state = 'NULL' instances = self.meta.tables['instances'] stmt = instances.update().where(instances.c.uuid == server_id).values( deleted=deleted, vm_state=vm_state, task_state=task_state) self.connection.execute(stmt, autocommit=True) def _test_delete_server_base(self, vm_state, task_state): """ Base method for delete server tests based on vm and task states. Validates for successful server termination. """ try: server = self.create_server() self.update_state(server['id'], vm_state, task_state) resp, body = self.client.delete_server(server['id']) self.assertEqual('204', resp['status']) self.client.wait_for_server_termination(server['id'], ignore_error=True) instances = self.meta.tables['instances'] stmt = instances.select().where(instances.c.uuid == server['id']) result = self.connection.execute(stmt).first() self.assertEqual(1, result.deleted) self.assertEqual('deleted', result.vm_state) self.assertEqual(None, result.task_state) except Exception: self.fail("Should be able to delete a server when vm_state=%s and " "task_state=%s" % (vm_state, task_state)) def _test_delete_server_403_base(self, vm_state, task_state): """ Base method for delete server tests based on vm and task states. Validates for 403 error code. """ try: self.update_state(self.shared_server['id'], vm_state, task_state) self.assertRaises(exceptions.Unauthorized, self.client.delete_server, self.shared_server['id']) except Exception: self.fail("Should not allow delete server when vm_state=%s and " "task_state=%s" % (vm_state, task_state)) finally: self.update_state(self.shared_server['id'], 'active', None) def test_delete_server_when_vm_eq_building_task_eq_networking(self): # Delete server when instance states are building,networking self._test_delete_server_base('building', 'networking') def test_delete_server_when_vm_eq_building_task_eq_bdm(self): # Delete server when instance states are building,block device mapping self._test_delete_server_base('building', 'block_device_mapping') def test_delete_server_when_vm_eq_building_task_eq_spawning(self): # Delete server when instance states are building,spawning self._test_delete_server_base('building', 'spawning') def test_delete_server_when_vm_eq_active_task_eq_image_backup(self): # Delete server when instance states are active,image_backup self._test_delete_server_base('active', 'image_backup') def test_delete_server_when_vm_eq_active_task_eq_rebuilding(self): # Delete server when instance states are active,rebuilding self._test_delete_server_base('active', 'rebuilding') def test_delete_server_when_vm_eq_error_task_eq_spawning(self): # Delete server when instance states are error,spawning self._test_delete_server_base('error', 'spawning') def test_delete_server_when_vm_eq_resized_task_eq_resize_prep(self): # Delete server when instance states are resized,resize_prep self._test_delete_server_403_base('resized', 'resize_prep') def test_delete_server_when_vm_eq_resized_task_eq_resize_migrating(self): # Delete server when instance states are resized,resize_migrating self._test_delete_server_403_base('resized', 'resize_migrating') def test_delete_server_when_vm_eq_resized_task_eq_resize_migrated(self): # Delete server when instance states are resized,resize_migrated self._test_delete_server_403_base('resized', 'resize_migrated') def test_delete_server_when_vm_eq_resized_task_eq_resize_finish(self): # Delete server when instance states are resized,resize_finish self._test_delete_server_403_base('resized', 'resize_finish') def test_delete_server_when_vm_eq_resized_task_eq_resize_reverting(self): # Delete server when instance states are resized,resize_reverting self._test_delete_server_403_base('resized', 'resize_reverting') def test_delete_server_when_vm_eq_resized_task_eq_resize_confirming(self): # Delete server when instance states are resized,resize_confirming self._test_delete_server_403_base('resized', 'resize_confirming') def test_delete_server_when_vm_eq_active_task_eq_resize_verify(self): # Delete server when instance states are active,resize_verify self._test_delete_server_base('active', 'resize_verify') def test_delete_server_when_vm_eq_active_task_eq_rebooting(self): # Delete server when instance states are active,rebooting self._test_delete_server_base('active', 'rebooting') def test_delete_server_when_vm_eq_building_task_eq_deleting(self): # Delete server when instance states are building,deleting self._test_delete_server_base('building', 'deleting') def test_delete_server_when_vm_eq_active_task_eq_deleting(self): # Delete server when instance states are active,deleting self._test_delete_server_base('active', 'deleting') def test_delete_server_when_vm_eq_error_task_eq_none(self): # Delete server when instance states are error,None self._test_delete_server_base('error', None) def test_delete_server_when_vm_eq_resized_task_eq_none(self): # Delete server when instance states are resized,None self._test_delete_server_403_base('resized', None) def test_delete_server_when_vm_eq_error_task_eq_resize_prep(self): # Delete server when instance states are error,resize_prep self._test_delete_server_base('error', 'resize_prep') def test_delete_server_when_vm_eq_error_task_eq_error(self): # Delete server when instance states are error,error self._test_delete_server_base('error', 'error') tempest-2013.2.a1291.g23a1b4f/tempest/whitebox/manager.py0000664000175000017500000001417212161375672022761 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 os import shlex import subprocess import sys from sqlalchemy import create_engine, MetaData from tempest.common import log as logging from tempest.common.ssh import Client from tempest.common.utils.data_utils import rand_name from tempest import exceptions from tempest import test LOG = logging.getLogger(__name__) class WhiteboxTest(object): """ Base test case class mixin for "whitebox tests" Whitebox tests are tests that have the following characteristics: * Test common and advanced operations against a set of servers * Use a client that it is possible to send random or bad data with * SSH into either a host or a guest in order to validate server state * May execute SQL queries directly against internal databases to verify the state of data records """ pass class ComputeWhiteboxTest(test.ComputeFuzzClientTest, WhiteboxTest): """ Base smoke test case class for OpenStack Compute API (Nova) """ @classmethod def setUpClass(cls): super(ComputeWhiteboxTest, cls).setUpClass() if not cls.config.whitebox.whitebox_enabled: msg = "Whitebox testing disabled" raise cls.skipException(msg) # Add some convenience attributes that tests use... cls.nova_dir = cls.config.whitebox.source_dir cls.compute_bin_dir = cls.config.whitebox.bin_dir cls.compute_config_path = cls.config.whitebox.config_path cls.servers_client = cls.manager.servers_client cls.images_client = cls.manager.images_client cls.flavors_client = cls.manager.flavors_client cls.extensions_client = cls.manager.extensions_client cls.floating_ips_client = cls.manager.floating_ips_client cls.keypairs_client = cls.manager.keypairs_client cls.security_groups_client = cls.manager.security_groups_client cls.limits_client = cls.manager.limits_client cls.volumes_client = cls.manager.volumes_client cls.build_interval = cls.config.compute.build_interval cls.build_timeout = cls.config.compute.build_timeout cls.ssh_user = cls.config.compute.ssh_user cls.image_ref = cls.config.compute.image_ref cls.image_ref_alt = cls.config.compute.image_ref_alt cls.flavor_ref = cls.config.compute.flavor_ref cls.flavor_ref_alt = cls.config.compute.flavor_ref_alt cls.servers = [] @classmethod def tearDownClass(cls): # NOTE(jaypipes): Tests often add things in a particular order # so we destroy resources in the reverse order in which resources # are added to the test class object if not cls.os_resources: return thing = cls.os_resources.pop() while True: LOG.debug("Deleting %r from shared resources of %s" % (thing, cls.__name__)) # Resources in novaclient all have a delete() method # which destroys the resource... thing.delete() if not cls.os_resources: return thing = cls.os_resources.pop() @classmethod def create_server(cls, image_id=None): """Wrapper utility that returns a test server.""" server_name = rand_name(cls.__name__ + "-instance") flavor = cls.flavor_ref if not image_id: image_id = cls.image_ref resp, server = cls.servers_client.create_server( server_name, image_id, flavor) cls.servers_client.wait_for_server_status(server['id'], 'ACTIVE') cls.servers.append(server) return server @classmethod def get_db_handle_and_meta(cls, database='nova'): """Return a connection handle and metadata of an OpenStack database.""" engine_args = {"echo": False, "convert_unicode": True, "pool_recycle": 3600 } try: engine = create_engine(cls.config.whitebox.db_uri, **engine_args) connection = engine.connect() meta = MetaData() meta.reflect(bind=engine) except Exception, e: raise exceptions.SQLException(message=e) return connection, meta def nova_manage(self, category, action, params): """Executes nova-manage command for the given action.""" nova_manage_path = os.path.join(self.compute_bin_dir, 'nova-manage') cmd = ' '.join([nova_manage_path, category, action, params]) if self.deploy_mode == 'devstack-local': if not os.path.isdir(self.nova_dir): sys.exit("Cannot find Nova source directory: %s" % self.nova_dir) cmd = shlex.split(cmd) result = subprocess.Popen(cmd, stdout=subprocess.PIPE) #Todo(rohitk): Need to define host connection parameters in config else: client = self.get_ssh_connection(self.config.whitebox.api_host, self.config.whitebox.api_user, self.config.whitebox.api_passwd) result = client.exec_command(cmd) return result def get_ssh_connection(self, host, username, password): """Create an SSH connection object to a host.""" ssh_timeout = self.config.compute.ssh_timeout ssh_client = Client(host, username, password, ssh_timeout) if not ssh_client.test_connection_auth(): raise exceptions.SSHTimeout() else: return ssh_client tempest-2013.2.a1291.g23a1b4f/tempest/whitebox/test_images_whitebox.py0000664000175000017500000001650212161375672025563 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.api.compute import base from tempest.common.utils.data_utils import rand_name from tempest import exceptions from tempest.whitebox import manager #TODO(afazekas): The whitebox tests are using complex testclass/manager # hierarchy, without a real need. It is difficult to maintain. # They could share more code with scenario tests. class ImagesWhiteboxTest(manager.ComputeWhiteboxTest, base.BaseComputeTest): _interface = 'json' @classmethod def setUpClass(cls): super(ImagesWhiteboxTest, cls).setUpClass() cls.client = cls.images_client cls.connection, cls.meta = cls.get_db_handle_and_meta() cls.shared_server = cls.create_server() cls.image_ids = [] @classmethod def tearDownClass(cls): """Delete images and server after a test is executed.""" cls.servers_client.delete_server(cls.shared_server['id']) for image_id in cls.image_ids: cls.client.delete_image(image_id) cls.image_ids.remove(image_id) super(ImagesWhiteboxTest, cls).tearDownClass() @classmethod def update_state(self, server_id, vm_state, task_state, deleted=0): """Update states of an instance in database for validation.""" if not task_state: task_state = "NULL" instances = self.meta.tables['instances'] stmt = instances.update().where(instances.c.uuid == server_id).values( deleted=deleted, vm_state=vm_state, task_state=task_state) self.connection.execute(stmt, autocommit=True) def _test_create_image_409_base(self, vm_state, task_state, deleted=0): """Base method for create image tests based on vm and task states.""" try: self.update_state(self.shared_server['id'], vm_state, task_state, deleted) image_name = rand_name('snap-') self.assertRaises(exceptions.Duplicate, self.client.create_image, self.shared_server['id'], image_name) except Exception: self.fail("Should not allow create image when vm_state=%s and " "task_state=%s" % (vm_state, task_state)) finally: self.update_state(self.shared_server['id'], 'active', None) def test_create_image_when_vm_eq_building_task_eq_scheduling(self): # 409 error when instance states are building,scheduling self._test_create_image_409_base("building", "scheduling") def test_create_image_when_vm_eq_building_task_eq_networking(self): # 409 error when instance states are building,networking self._test_create_image_409_base("building", "networking") def test_create_image_when_vm_eq_building_task_eq_bdm(self): # 409 error when instance states are building,block_device_mapping self._test_create_image_409_base("building", "block_device_mapping") def test_create_image_when_vm_eq_building_task_eq_spawning(self): # 409 error when instance states are building,spawning self._test_create_image_409_base("building", "spawning") def test_create_image_when_vm_eq_active_task_eq_image_backup(self): # 409 error when instance states are active,image_backup self._test_create_image_409_base("active", "image_backup") def test_create_image_when_vm_eq_resized_task_eq_resize_prep(self): # 409 error when instance states are resized,resize_prep self._test_create_image_409_base("resized", "resize_prep") def test_create_image_when_vm_eq_resized_task_eq_resize_migrating(self): # 409 error when instance states are resized,resize_migrating self._test_create_image_409_base("resized", "resize_migrating") def test_create_image_when_vm_eq_resized_task_eq_resize_migrated(self): # 409 error when instance states are resized,resize_migrated self._test_create_image_409_base("resized", "resize_migrated") def test_create_image_when_vm_eq_resized_task_eq_resize_finish(self): # 409 error when instance states are resized,resize_finish self._test_create_image_409_base("resized", "resize_finish") def test_create_image_when_vm_eq_resized_task_eq_resize_reverting(self): # 409 error when instance states are resized,resize_reverting self._test_create_image_409_base("resized", "resize_reverting") def test_create_image_when_vm_eq_resized_task_eq_resize_confirming(self): # 409 error when instance states are resized,resize_confirming self._test_create_image_409_base("resized", "resize_confirming") def test_create_image_when_vm_eq_active_task_eq_resize_verify(self): # 409 error when instance states are active,resize_verify self._test_create_image_409_base("active", "resize_verify") def test_create_image_when_vm_eq_active_task_eq_updating_password(self): # 409 error when instance states are active,updating_password self._test_create_image_409_base("active", "updating_password") def test_create_image_when_vm_eq_active_task_eq_rebuilding(self): # 409 error when instance states are active,rebuilding self._test_create_image_409_base("active", "rebuilding") def test_create_image_when_vm_eq_active_task_eq_rebooting(self): # 409 error when instance states are active,rebooting self._test_create_image_409_base("active", "rebooting") def test_create_image_when_vm_eq_building_task_eq_deleting(self): # 409 error when instance states are building,deleting self._test_create_image_409_base("building", "deleting") def test_create_image_when_vm_eq_active_task_eq_deleting(self): # 409 error when instance states are active,deleting self._test_create_image_409_base("active", "deleting") def test_create_image_when_vm_eq_error_task_eq_building(self): # 409 error when instance states are error,building self._test_create_image_409_base("error", "building") def test_create_image_when_vm_eq_error_task_eq_none(self): # 409 error when instance states are error,None self._test_create_image_409_base("error", None) def test_create_image_when_vm_eq_deleted_task_eq_none(self): # 409 error when instance states are deleted,None self._test_create_image_409_base("deleted", None) def test_create_image_when_vm_eq_resized_task_eq_none(self): # 409 error when instance states are resized,None self._test_create_image_409_base("resized", None) def test_create_image_when_vm_eq_error_task_eq_resize_prep(self): # 409 error when instance states are error,resize_prep self._test_create_image_409_base("error", "resize_prep") tempest-2013.2.a1291.g23a1b4f/tempest/whitebox/README.rst0000664000175000017500000000301612161375672022457 0ustar chuckchuck00000000000000Tempest Guide to Whitebox tests =============================== What are these tests? --------------------- When you hit the OpenStack API, this causes internal state changes in the system. This might be database transitions, vm modifications, other deep state changes which aren't really accessible from the OpenStack API. These side effects are sometimes important to validate. White box testing is an approach there. In white box testing you are given database access to the environment, and can verify internal record changes after an API call. This is an optional part of testing, and requires extra setup, but can be useful for validating Tempest internals. Why are these tests in tempest? ------------------------------- Especially when it comes to something like VM state changing, which is a coordination of numerous running daemons, and a functioning VM, it's very difficult to get a realistic test like this in unit tests. Scope of these tests -------------------- White box tests should be limitted to tests where black box testing (using the OpenStack API to verify results) isn't sufficient. As these poke at internals of OpenStack, it should also be realized that these tests are very tightly coupled to current implementation of OpenStack. They will need to be maintained agressively to keep up with internals changes in OpenStack projects. Example of a good test ---------------------- Pushing VMs through a series of state transitions, and ensuring along the way the database state transitions match what's expected. tempest-2013.2.a1291.g23a1b4f/tempest/whitebox/__init__.py0000664000175000017500000000000012161375672023067 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/config.py0000664000175000017500000005326512161375672020771 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 os import sys from oslo.config import cfg from tempest.common import log as logging from tempest.common.utils.misc import singleton LOG = logging.getLogger(__name__) identity_group = cfg.OptGroup(name='identity', title="Keystone Configuration Options") IdentityGroup = [ cfg.StrOpt('catalog_type', default='identity', help="Catalog type of the Identity service."), cfg.BoolOpt('disable_ssl_certificate_validation', default=False, help="Set to True if using self-signed SSL certificates."), cfg.StrOpt('uri', default=None, help="Full URI of the OpenStack Identity API (Keystone), v2"), cfg.StrOpt('uri_v3', help='Full URI of the OpenStack Identity API (Keystone), v3'), cfg.StrOpt('region', default='RegionOne', help="The identity region name to use."), cfg.StrOpt('username', default='demo', help="Username to use for Nova API requests."), cfg.StrOpt('tenant_name', default='demo', help="Tenant name to use for Nova API requests."), cfg.StrOpt('password', default='pass', help="API key to use when authenticating.", secret=True), cfg.StrOpt('alt_username', default=None, help="Username of alternate user to use for Nova API " "requests."), cfg.StrOpt('alt_tenant_name', default=None, help="Alternate user's Tenant name to use for Nova API " "requests."), cfg.StrOpt('alt_password', default=None, help="API key to use when authenticating as alternate user.", secret=True), cfg.StrOpt('admin_username', default='admin', help="Administrative Username to use for" "Keystone API requests."), cfg.StrOpt('admin_tenant_name', default='admin', help="Administrative Tenant name to use for Keystone API " "requests."), cfg.StrOpt('admin_password', default='pass', help="API key to use when authenticating as admin.", secret=True), ] def register_identity_opts(conf): conf.register_group(identity_group) for opt in IdentityGroup: conf.register_opt(opt, group='identity') compute_group = cfg.OptGroup(name='compute', title='Compute Service Options') ComputeGroup = [ cfg.BoolOpt('allow_tenant_isolation', default=False, help="Allows test cases to create/destroy tenants and " "users. This option enables isolated test cases and " "better parallel execution, but also requires that " "OpenStack Identity API admin credentials are known."), cfg.BoolOpt('allow_tenant_reuse', default=True, help="If allow_tenant_isolation is True and a tenant that " "would be created for a given test already exists (such " "as from a previously-failed run), re-use that tenant " "instead of failing because of the conflict. Note that " "this would result in the tenant being deleted at the " "end of a subsequent successful run."), cfg.StrOpt('image_ref', default="{$IMAGE_ID}", help="Valid secondary image reference to be used in tests."), cfg.StrOpt('image_ref_alt', default="{$IMAGE_ID_ALT}", help="Valid secondary image reference to be used in tests."), cfg.IntOpt('flavor_ref', default=1, help="Valid primary flavor to use in tests."), cfg.IntOpt('flavor_ref_alt', default=2, help='Valid secondary flavor to be used in tests.'), cfg.StrOpt('image_ssh_user', default="root", help="User name used to authenticate to an instance."), cfg.StrOpt('image_alt_ssh_user', default="root", help="User name used to authenticate to an instance using " "the alternate image."), cfg.BoolOpt('resize_available', default=False, help="Does the test environment support resizing?"), cfg.BoolOpt('live_migration_available', default=False, help="Does the test environment support live migration " "available?"), cfg.BoolOpt('use_block_migration_for_live_migration', default=False, help="Does the test environment use block devices for live " "migration"), cfg.BoolOpt('block_migrate_supports_cinder_iscsi', default=False, help="Does the test environment block migration support " "cinder iSCSI volumes"), cfg.BoolOpt('change_password_available', default=False, help="Does the test environment support changing the admin " "password?"), cfg.BoolOpt('create_image_enabled', default=False, help="Does the test environment support snapshots?"), cfg.IntOpt('build_interval', default=10, help="Time in seconds between build status checks."), cfg.IntOpt('build_timeout', default=300, help="Timeout in seconds to wait for an instance to build."), cfg.BoolOpt('run_ssh', default=False, help="Does the test environment support snapshots?"), cfg.StrOpt('ssh_user', default='root', help="User name used to authenticate to an instance."), cfg.IntOpt('ssh_timeout', default=300, help="Timeout in seconds to wait for authentication to " "succeed."), cfg.IntOpt('ssh_channel_timeout', default=60, help="Timeout in seconds to wait for output from ssh " "channel."), cfg.StrOpt('fixed_network_name', default='private', help="Visible fixed network name "), cfg.StrOpt('network_for_ssh', default='public', help="Network used for SSH connections."), cfg.IntOpt('ip_version_for_ssh', default=4, help="IP version used for SSH connections."), cfg.StrOpt('catalog_type', default='compute', help="Catalog type of the Compute service."), cfg.StrOpt('path_to_private_key', default=None, help="Path to a private key file for SSH access to remote " "hosts"), cfg.BoolOpt('disk_config_enabled', default=True, help="If false, skip disk config tests"), cfg.BoolOpt('flavor_extra_enabled', default=True, help="If false, skip flavor extra data test"), ] def register_compute_opts(conf): conf.register_group(compute_group) for opt in ComputeGroup: conf.register_opt(opt, group='compute') compute_admin_group = cfg.OptGroup(name='compute-admin', title="Compute Admin Options") ComputeAdminGroup = [ cfg.StrOpt('username', default='admin', help="Administrative Username to use for Nova API requests."), cfg.StrOpt('tenant_name', default='admin', help="Administrative Tenant name to use for Nova API " "requests."), cfg.StrOpt('password', default='pass', help="API key to use when authenticating as admin.", secret=True), ] def register_compute_admin_opts(conf): conf.register_group(compute_admin_group) for opt in ComputeAdminGroup: conf.register_opt(opt, group='compute-admin') whitebox_group = cfg.OptGroup(name='whitebox', title="Whitebox Options") WhiteboxGroup = [ cfg.BoolOpt('whitebox_enabled', default=False, help="Does the test environment support whitebox tests for " "Compute?"), cfg.StrOpt('db_uri', default=None, help="Connection string to the database of Compute service"), cfg.StrOpt('source_dir', default="/opt/stack/nova", help="Path of nova source directory"), cfg.StrOpt('config_path', default='/etc/nova/nova.conf', help="Path of nova configuration file"), cfg.StrOpt('bin_dir', default="/usr/local/bin/", help="Directory containing nova binaries such as nova-manage"), ] def register_whitebox_opts(conf): conf.register_group(whitebox_group) for opt in WhiteboxGroup: conf.register_opt(opt, group='whitebox') image_group = cfg.OptGroup(name='image', title="Image Service Options") ImageGroup = [ cfg.StrOpt('api_version', default='1', help="Version of the API"), cfg.StrOpt('catalog_type', default='image', help='Catalog type of the Image service.'), cfg.StrOpt('http_image', default='http://download.cirros-cloud.net/0.3.1/' 'cirros-0.3.1-x86_64-uec.tar.gz', help='http accessable image') ] def register_image_opts(conf): conf.register_group(image_group) for opt in ImageGroup: conf.register_opt(opt, group='image') network_group = cfg.OptGroup(name='network', title='Network Service Options') NetworkGroup = [ cfg.StrOpt('catalog_type', default='network', help='Catalog type of the Quantum service.'), cfg.StrOpt('tenant_network_cidr', default="10.100.0.0/16", help="The cidr block to allocate tenant networks from"), cfg.IntOpt('tenant_network_mask_bits', default=29, help="The mask bits for tenant networks"), cfg.BoolOpt('tenant_networks_reachable', default=False, help="Whether tenant network connectivity should be " "evaluated directly"), cfg.StrOpt('public_network_id', default="", help="Id of the public network that provides external " "connectivity"), cfg.StrOpt('public_router_id', default="", help="Id of the public router that provides external " "connectivity"), cfg.BoolOpt('quantum_available', default=False, help="Whether or not quantum is expected to be available"), ] def register_network_opts(conf): conf.register_group(network_group) for opt in NetworkGroup: conf.register_opt(opt, group='network') volume_group = cfg.OptGroup(name='volume', title='Block Storage Options') VolumeGroup = [ cfg.IntOpt('build_interval', default=10, help='Time in seconds between volume availability checks.'), cfg.IntOpt('build_timeout', default=300, help='Timeout in seconds to wait for a volume to become' 'available.'), cfg.StrOpt('catalog_type', default='Volume', help="Catalog type of the Volume Service"), cfg.BoolOpt('multi_backend_enabled', default=False, help="Runs Cinder multi-backend test (requires 2 backends)"), cfg.StrOpt('backend1_name', default='BACKEND_1', help="Name of the backend1 (must be declared in cinder.conf)"), cfg.StrOpt('backend2_name', default='BACKEND_2', help="Name of the backend2 (must be declared in cinder.conf)"), ] def register_volume_opts(conf): conf.register_group(volume_group) for opt in VolumeGroup: conf.register_opt(opt, group='volume') object_storage_group = cfg.OptGroup(name='object-storage', title='Object Storage Service Options') ObjectStoreConfig = [ cfg.StrOpt('catalog_type', default='object-store', help="Catalog type of the Object-Storage service."), cfg.StrOpt('container_sync_timeout', default=120, help="Number of seconds to time on waiting for a container" "to container synchronization complete."), cfg.StrOpt('container_sync_interval', default=5, help="Number of seconds to wait while looping to check the" "status of a container to container synchronization"), ] def register_object_storage_opts(conf): conf.register_group(object_storage_group) for opt in ObjectStoreConfig: conf.register_opt(opt, group='object-storage') orchestration_group = cfg.OptGroup(name='orchestration', title='Orchestration Service Options') OrchestrationGroup = [ cfg.StrOpt('catalog_type', default='orchestration', help="Catalog type of the Orchestration service."), cfg.BoolOpt('allow_tenant_isolation', default=False, help="Allows test cases to create/destroy tenants and " "users. This option enables isolated test cases and " "better parallel execution, but also requires that " "OpenStack Identity API admin credentials are known."), cfg.IntOpt('build_interval', default=1, help="Time in seconds between build status checks."), cfg.IntOpt('build_timeout', default=300, help="Timeout in seconds to wait for a stack to build."), cfg.BoolOpt('heat_available', default=False, help="Whether or not Heat is expected to be available"), cfg.StrOpt('instance_type', default='m1.micro', help="Instance type for tests. Needs to be big enough for a " "full OS plus the test workload"), cfg.StrOpt('image_ref', default=None, help="Name of heat-cfntools enabled image to use when " "launching test instances."), cfg.StrOpt('keypair_name', default=None, help="Name of existing keypair to launch servers with."), ] def register_orchestration_opts(conf): conf.register_group(orchestration_group) for opt in OrchestrationGroup: conf.register_opt(opt, group='orchestration') boto_group = cfg.OptGroup(name='boto', title='EC2/S3 options') BotoConfig = [ cfg.StrOpt('ec2_url', default="http://localhost:8773/services/Cloud", help="EC2 URL"), cfg.StrOpt('s3_url', default="http://localhost:8080", help="S3 URL"), cfg.StrOpt('aws_secret', default=None, help="AWS Secret Key", secret=True), cfg.StrOpt('aws_access', default=None, help="AWS Access Key"), cfg.StrOpt('s3_materials_path', default="/opt/stack/devstack/files/images/" "s3-materials/cirros-0.3.0", help="S3 Materials Path"), cfg.StrOpt('ari_manifest', default="cirros-0.3.0-x86_64-initrd.manifest.xml", help="ARI Ramdisk Image manifest"), cfg.StrOpt('ami_manifest', default="cirros-0.3.0-x86_64-blank.img.manifest.xml", help="AMI Machine Image manifest"), cfg.StrOpt('aki_manifest', default="cirros-0.3.0-x86_64-vmlinuz.manifest.xml", help="AKI Kernel Image manifest"), cfg.StrOpt('instance_type', default="m1.tiny", help="Instance type"), cfg.IntOpt('http_socket_timeout', default=3, help="boto Http socket timeout"), cfg.IntOpt('num_retries', default=1, help="boto num_retries on error"), cfg.IntOpt('build_timeout', default=60, help="Status Change Timeout"), cfg.IntOpt('build_interval', default=1, help="Status Change Test Interval"), ] def register_boto_opts(conf): conf.register_group(boto_group) for opt in BotoConfig: conf.register_opt(opt, group='boto') stress_group = cfg.OptGroup(name='stress', title='Stress Test Options') StressGroup = [ cfg.StrOpt('nova_logdir', default=None, help='Directory containing log files on the compute nodes'), cfg.IntOpt('max_instances', default=16, help='Maximum number of instances to create during test.'), cfg.StrOpt('controller', default=None, help='Controller host.'), # new stress options cfg.StrOpt('target_controller', default=None, help='Controller host.'), cfg.StrOpt('target_ssh_user', default=None, help='ssh user.'), cfg.StrOpt('target_private_key_path', default=None, help='Path to private key.'), cfg.StrOpt('target_logfiles', default=None, help='regexp for list of log files.'), cfg.StrOpt('log_check_interval', default=60, help='time between log file error checks.') ] def register_stress_opts(conf): conf.register_group(stress_group) for opt in StressGroup: conf.register_opt(opt, group='stress') scenario_group = cfg.OptGroup(name='scenario', title='Scenario Test Options') ScenarioGroup = [ cfg.StrOpt('img_dir', default='/opt/stack/new/devstack/files/images/' 'cirros-0.3.1-x86_64-uec', help='Directory containing image files'), cfg.StrOpt('ami_img_file', default='cirros-0.3.1-x86_64-blank.img', help='AMI image file name'), cfg.StrOpt('ari_img_file', default='cirros-0.3.1-x86_64-initrd', help='ARI image file name'), cfg.StrOpt('aki_img_file', default='cirros-0.3.1-x86_64-vmlinuz', help='AKI image file name'), cfg.StrOpt('ssh_user', default='cirros', help='ssh username for the image file') ] def register_scenario_opts(conf): conf.register_group(scenario_group) for opt in ScenarioGroup: conf.register_opt(opt, group='scenario') @singleton class TempestConfig: """Provides OpenStack configuration information.""" DEFAULT_CONFIG_DIR = os.path.join( os.path.abspath(os.path.dirname(os.path.dirname(__file__))), "etc") DEFAULT_CONFIG_FILE = "tempest.conf" def __init__(self): """Initialize a configuration from a conf directory and conf file.""" config_files = [] failsafe_path = "/etc/tempest/" + self.DEFAULT_CONFIG_FILE # Environment variables override defaults... conf_dir = os.environ.get('TEMPEST_CONFIG_DIR', self.DEFAULT_CONFIG_DIR) conf_file = os.environ.get('TEMPEST_CONFIG', self.DEFAULT_CONFIG_FILE) path = os.path.join(conf_dir, conf_file) if not (os.path.isfile(path) or 'TEMPEST_CONFIG_DIR' in os.environ or 'TEMPEST_CONFIG' in os.environ): path = failsafe_path LOG.info("Using tempest config file %s" % path) if not os.path.exists(path): msg = "Config file %(path)s not found" % locals() print >> sys.stderr, RuntimeError(msg) else: config_files.append(path) cfg.CONF([], project='tempest', default_config_files=config_files) register_compute_opts(cfg.CONF) register_identity_opts(cfg.CONF) register_whitebox_opts(cfg.CONF) register_image_opts(cfg.CONF) register_network_opts(cfg.CONF) register_volume_opts(cfg.CONF) register_object_storage_opts(cfg.CONF) register_orchestration_opts(cfg.CONF) register_boto_opts(cfg.CONF) register_compute_admin_opts(cfg.CONF) register_stress_opts(cfg.CONF) register_scenario_opts(cfg.CONF) self.compute = cfg.CONF.compute self.whitebox = cfg.CONF.whitebox self.identity = cfg.CONF.identity self.images = cfg.CONF.image self.network = cfg.CONF.network self.volume = cfg.CONF.volume self.object_storage = cfg.CONF['object-storage'] self.orchestration = cfg.CONF.orchestration self.boto = cfg.CONF.boto self.compute_admin = cfg.CONF['compute-admin'] self.stress = cfg.CONF.stress self.scenario = cfg.CONF.scenario if not self.compute_admin.username: self.compute_admin.username = self.identity.admin_username self.compute_admin.password = self.identity.admin_password self.compute_admin.tenant_name = self.identity.admin_tenant_name tempest-2013.2.a1291.g23a1b4f/tempest/clients.py0000664000175000017500000003564512161375672021167 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest.common import log as logging from tempest import config from tempest import exceptions from tempest.services import botoclients from tempest.services.compute.json.aggregates_client import \ AggregatesClientJSON from tempest.services.compute.json.availability_zone_client import \ AvailabilityZoneClientJSON from tempest.services.compute.json.extensions_client import \ ExtensionsClientJSON from tempest.services.compute.json.fixed_ips_client import FixedIPsClientJSON from tempest.services.compute.json.flavors_client import FlavorsClientJSON from tempest.services.compute.json.floating_ips_client import \ FloatingIPsClientJSON from tempest.services.compute.json.hosts_client import HostsClientJSON from tempest.services.compute.json.hypervisor_client import \ HypervisorClientJSON from tempest.services.compute.json.images_client import ImagesClientJSON from tempest.services.compute.json.interfaces_client import \ InterfacesClientJSON from tempest.services.compute.json.keypairs_client import KeyPairsClientJSON from tempest.services.compute.json.limits_client import LimitsClientJSON from tempest.services.compute.json.quotas_client import QuotasClientJSON from tempest.services.compute.json.security_groups_client import \ SecurityGroupsClientJSON from tempest.services.compute.json.servers_client import ServersClientJSON from tempest.services.compute.json.services_client import ServicesClientJSON from tempest.services.compute.json.tenant_usages_client import \ TenantUsagesClientJSON from tempest.services.compute.json.volumes_extensions_client import \ VolumesExtensionsClientJSON from tempest.services.compute.xml.aggregates_client import AggregatesClientXML from tempest.services.compute.xml.availability_zone_client import \ AvailabilityZoneClientXML from tempest.services.compute.xml.extensions_client import ExtensionsClientXML from tempest.services.compute.xml.fixed_ips_client import FixedIPsClientXML from tempest.services.compute.xml.flavors_client import FlavorsClientXML from tempest.services.compute.xml.floating_ips_client import \ FloatingIPsClientXML from tempest.services.compute.xml.hypervisor_client import HypervisorClientXML from tempest.services.compute.xml.images_client import ImagesClientXML from tempest.services.compute.xml.interfaces_client import \ InterfacesClientXML from tempest.services.compute.xml.keypairs_client import KeyPairsClientXML from tempest.services.compute.xml.limits_client import LimitsClientXML from tempest.services.compute.xml.quotas_client import QuotasClientXML from tempest.services.compute.xml.security_groups_client \ import SecurityGroupsClientXML from tempest.services.compute.xml.servers_client import ServersClientXML from tempest.services.compute.xml.services_client import ServicesClientXML from tempest.services.compute.xml.tenant_usages_client import \ TenantUsagesClientXML from tempest.services.compute.xml.volumes_extensions_client import \ VolumesExtensionsClientXML from tempest.services.identity.json.identity_client import IdentityClientJSON from tempest.services.identity.json.identity_client import TokenClientJSON from tempest.services.identity.v3.json.endpoints_client import \ EndPointClientJSON from tempest.services.identity.v3.json.identity_client import \ IdentityV3ClientJSON from tempest.services.identity.v3.json.policy_client import PolicyClientJSON from tempest.services.identity.v3.json.service_client import \ ServiceClientJSON from tempest.services.identity.v3.xml.endpoints_client import EndPointClientXML from tempest.services.identity.v3.xml.identity_client import \ IdentityV3ClientXML from tempest.services.identity.v3.xml.policy_client import PolicyClientXML from tempest.services.identity.v3.xml.service_client import \ ServiceClientXML from tempest.services.identity.xml.identity_client import IdentityClientXML from tempest.services.identity.xml.identity_client import TokenClientXML from tempest.services.image.v1.json.image_client import ImageClientJSON from tempest.services.image.v2.json.image_client import ImageClientV2JSON from tempest.services.network.json.network_client import NetworkClient from tempest.services.object_storage.account_client import AccountClient from tempest.services.object_storage.account_client import \ AccountClientCustomizedHeader from tempest.services.object_storage.container_client import ContainerClient from tempest.services.object_storage.object_client import ObjectClient from tempest.services.object_storage.object_client import \ ObjectClientCustomizedHeader from tempest.services.orchestration.json.orchestration_client import \ OrchestrationClient from tempest.services.volume.json.admin.volume_types_client import \ VolumeTypesClientJSON from tempest.services.volume.json.snapshots_client import SnapshotsClientJSON from tempest.services.volume.json.volumes_client import VolumesClientJSON from tempest.services.volume.xml.admin.volume_types_client import \ VolumeTypesClientXML from tempest.services.volume.xml.snapshots_client import SnapshotsClientXML from tempest.services.volume.xml.volumes_client import VolumesClientXML LOG = logging.getLogger(__name__) IMAGES_CLIENTS = { "json": ImagesClientJSON, "xml": ImagesClientXML, } KEYPAIRS_CLIENTS = { "json": KeyPairsClientJSON, "xml": KeyPairsClientXML, } QUOTAS_CLIENTS = { "json": QuotasClientJSON, "xml": QuotasClientXML, } SERVERS_CLIENTS = { "json": ServersClientJSON, "xml": ServersClientXML, } LIMITS_CLIENTS = { "json": LimitsClientJSON, "xml": LimitsClientXML, } FLAVORS_CLIENTS = { "json": FlavorsClientJSON, "xml": FlavorsClientXML } EXTENSIONS_CLIENTS = { "json": ExtensionsClientJSON, "xml": ExtensionsClientXML } VOLUMES_EXTENSIONS_CLIENTS = { "json": VolumesExtensionsClientJSON, "xml": VolumesExtensionsClientXML, } FLOAT_CLIENTS = { "json": FloatingIPsClientJSON, "xml": FloatingIPsClientXML, } SNAPSHOTS_CLIENTS = { "json": SnapshotsClientJSON, "xml": SnapshotsClientXML, } VOLUMES_CLIENTS = { "json": VolumesClientJSON, "xml": VolumesClientXML, } VOLUME_TYPES_CLIENTS = { "json": VolumeTypesClientJSON, "xml": VolumeTypesClientXML, } IDENTITY_CLIENT = { "json": IdentityClientJSON, "xml": IdentityClientXML, } IDENTITY_V3_CLIENT = { "json": IdentityV3ClientJSON, "xml": IdentityV3ClientXML, } TOKEN_CLIENT = { "json": TokenClientJSON, "xml": TokenClientXML, } SECURITY_GROUPS_CLIENT = { "json": SecurityGroupsClientJSON, "xml": SecurityGroupsClientXML, } INTERFACES_CLIENT = { "json": InterfacesClientJSON, "xml": InterfacesClientXML, } ENDPOINT_CLIENT = { "json": EndPointClientJSON, "xml": EndPointClientXML, } FIXED_IPS_CLIENT = { "json": FixedIPsClientJSON, "xml": FixedIPsClientXML } AVAILABILITY_ZONE_CLIENT = { "json": AvailabilityZoneClientJSON, "xml": AvailabilityZoneClientXML, } SERVICE_CLIENT = { "json": ServiceClientJSON, "xml": ServiceClientXML, } AGGREGATES_CLIENT = { "json": AggregatesClientJSON, "xml": AggregatesClientXML, } SERVICES_CLIENT = { "json": ServicesClientJSON, "xml": ServicesClientXML, } TENANT_USAGES_CLIENT = { "json": TenantUsagesClientJSON, "xml": TenantUsagesClientXML, } POLICY_CLIENT = { "json": PolicyClientJSON, "xml": PolicyClientXML, } HYPERVISOR_CLIENT = { "json": HypervisorClientJSON, "xml": HypervisorClientXML, } class Manager(object): """ Top level manager for OpenStack Compute clients """ def __init__(self, username=None, password=None, tenant_name=None, interface='json'): """ We allow overriding of the credentials used within the various client classes managed by the Manager object. Left as None, the standard username/password/tenant_name is used. :param username: Override of the username :param password: Override of the password :param tenant_name: Override of the tenant name """ self.config = config.TempestConfig() # If no creds are provided, we fall back on the defaults # in the config file for the Compute API. self.username = username or self.config.identity.username self.password = password or self.config.identity.password self.tenant_name = tenant_name or self.config.identity.tenant_name if None in (self.username, self.password, self.tenant_name): msg = ("Missing required credentials. " "username: %(username)s, password: %(password)s, " "tenant_name: %(tenant_name)s") % locals() raise exceptions.InvalidConfiguration(msg) self.auth_url = self.config.identity.uri self.auth_url_v3 = self.config.identity.uri_v3 client_args = (self.config, self.username, self.password, self.auth_url, self.tenant_name) if self.auth_url_v3: auth_version = 'v3' client_args_v3_auth = (self.config, self.username, self.password, self.auth_url_v3, self.tenant_name, auth_version) else: client_args_v3_auth = None try: self.servers_client = SERVERS_CLIENTS[interface](*client_args) self.limits_client = LIMITS_CLIENTS[interface](*client_args) self.images_client = IMAGES_CLIENTS[interface](*client_args) self.keypairs_client = KEYPAIRS_CLIENTS[interface](*client_args) self.quotas_client = QUOTAS_CLIENTS[interface](*client_args) self.flavors_client = FLAVORS_CLIENTS[interface](*client_args) ext_cli = EXTENSIONS_CLIENTS[interface](*client_args) self.extensions_client = ext_cli vol_ext_cli = VOLUMES_EXTENSIONS_CLIENTS[interface](*client_args) self.volumes_extensions_client = vol_ext_cli self.floating_ips_client = FLOAT_CLIENTS[interface](*client_args) self.snapshots_client = SNAPSHOTS_CLIENTS[interface](*client_args) self.volumes_client = VOLUMES_CLIENTS[interface](*client_args) self.volume_types_client = \ VOLUME_TYPES_CLIENTS[interface](*client_args) self.identity_client = IDENTITY_CLIENT[interface](*client_args) self.identity_v3_client = \ IDENTITY_V3_CLIENT[interface](*client_args) self.token_client = TOKEN_CLIENT[interface](self.config) self.security_groups_client = \ SECURITY_GROUPS_CLIENT[interface](*client_args) self.interfaces_client = INTERFACES_CLIENT[interface](*client_args) self.endpoints_client = ENDPOINT_CLIENT[interface](*client_args) self.fixed_ips_client = FIXED_IPS_CLIENT[interface](*client_args) self.availability_zone_client = \ AVAILABILITY_ZONE_CLIENT[interface](*client_args) self.service_client = SERVICE_CLIENT[interface](*client_args) self.aggregates_client = AGGREGATES_CLIENT[interface](*client_args) self.services_client = SERVICES_CLIENT[interface](*client_args) self.tenant_usages_client = \ TENANT_USAGES_CLIENT[interface](*client_args) self.policy_client = POLICY_CLIENT[interface](*client_args) self.hypervisor_client = HYPERVISOR_CLIENT[interface](*client_args) if client_args_v3_auth: self.servers_client_v3_auth = SERVERS_CLIENTS[interface]( *client_args_v3_auth) else: self.servers_client_v3_auth = None except KeyError: msg = "Unsupported interface type `%s'" % interface raise exceptions.InvalidConfiguration(msg) self.network_client = NetworkClient(*client_args) self.hosts_client = HostsClientJSON(*client_args) self.account_client = AccountClient(*client_args) self.image_client = ImageClientJSON(*client_args) self.image_client_v2 = ImageClientV2JSON(*client_args) self.container_client = ContainerClient(*client_args) self.object_client = ObjectClient(*client_args) self.orchestration_client = OrchestrationClient(*client_args) self.ec2api_client = botoclients.APIClientEC2(*client_args) self.s3_client = botoclients.ObjectClientS3(*client_args) self.custom_object_client = ObjectClientCustomizedHeader(*client_args) self.custom_account_client = \ AccountClientCustomizedHeader(*client_args) class AltManager(Manager): """ Manager object that uses the alt_XXX credentials for its managed client objects """ def __init__(self): conf = config.TempestConfig() super(AltManager, self).__init__(conf.identity.alt_username, conf.identity.alt_password, conf.identity.alt_tenant_name) class AdminManager(Manager): """ Manager object that uses the admin credentials for its managed client objects """ def __init__(self, interface='json'): conf = config.TempestConfig() super(AdminManager, self).__init__(conf.identity.admin_username, conf.identity.admin_password, conf.identity.admin_tenant_name, interface=interface) class ComputeAdminManager(Manager): """ Manager object that uses the compute_admin credentials for its managed client objects """ def __init__(self, interface='json'): conf = config.TempestConfig() base = super(ComputeAdminManager, self) base.__init__(conf.compute_admin.username, conf.compute_admin.password, conf.compute_admin.tenant_name, interface=interface) class OrchestrationManager(Manager): """ Manager object that uses the admin credentials for its so that heat templates can create users """ def __init__(self, interface='json'): conf = config.TempestConfig() base = super(OrchestrationManager, self) base.__init__(conf.identity.admin_username, conf.identity.admin_password, conf.identity.admin_tenant_name, interface=interface) tempest-2013.2.a1291.g23a1b4f/tempest/README.rst0000664000175000017500000000532412161375672020632 0ustar chuckchuck00000000000000============ Tempest Field Guide Overview ============ Tempest is designed to be useful for a large number of different environments. This includes being useful for gating commits to OpenStack core projects, being used to validate OpenStack cloud implementations for both correctness, as well as a burn in tool for OpenStack clouds. As such Tempest tests come in many flavors, each with their own rules and guidelines. Below is the proposed Havana restructuring for Tempest to make this clear. | tempest/ | api/ - API tests | cli/ - CLI tests | scenario/ - complex scenario tests | stress/ - stress tests | thirdparty/ - 3rd party api tests | whitebox/ - white box testing Each of these directories contains different types of tests. What belongs in each directory, the rules and examples for good tests, are documented in a README.rst file in the directory. api ------------ API tests are validation tests for the OpenStack API. They should not use the existing python clients for OpenStack, but should instead use the tempest implementations of clients. This allows us to test both XML and JSON. Having raw clients also lets us pass invalid JSON and XML to the APIs and see the results, something we could not get with the native clients. When it makes sense, API testing should be moved closer to the projects themselves, possibly as functional tests in their unit test frameworks. cli ------------ CLI tests use the openstack CLI to interact with the OpenStack cloud. CLI testing in unit tests is somewhat difficult because unlike server testing, there is no access to server code to instantiate. Tempest seems like a logical place for this, as it prereqs having a running OpenStack cloud. scenario ------------ Scenario tests are complex "through path" tests for OpenStack functionality. They are typically a series of steps where complicated state requiring multiple services is set up exercised, and torn down. Scenario tests can and should use the OpenStack python clients. stress ----------- Stress tests are designed to stress an OpenStack environment by running a high workload against it and seeing what breaks. Tools may be provided to help detect breaks (stack traces in the logs). TODO: old stress tests deleted, new_stress that david is working on moves into here. thirdparty ------------ Many openstack components include 3rdparty API support. It is completely legitimate for Tempest to include tests of 3rdparty APIs, but those should be kept separate from the normal OpenStack validation. whitebox ---------- Whitebox tests are tests which require access to the database of the target OpenStack machine to verify internal state after operations are made. White box tests are allowed to use the python clients. tempest-2013.2.a1291.g23a1b4f/tempest/__init__.py0000664000175000017500000000000012161375672021236 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/thirdparty/0000775000175000017500000000000012161375700021321 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/thirdparty/boto/0000775000175000017500000000000012161375700022264 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/thirdparty/boto/test_ec2_network.py0000664000175000017500000000362712161375672026137 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 testtools from tempest import clients from tempest.test import attr from tempest.thirdparty.boto.test import BotoTestCase class EC2NetworkTest(BotoTestCase): @classmethod def setUpClass(cls): super(EC2NetworkTest, cls).setUpClass() cls.os = clients.Manager() cls.client = cls.os.ec2api_client #Note(afazekas): these tests for things duable without an instance @testtools.skip("Skipped until the Bug #1080406 is resolved") @attr(type='smoke') def test_disassociate_not_associated_floating_ip(self): # EC2 disassociate not associated floating ip ec2_codes = self.ec2_error_code address = self.client.allocate_address() public_ip = address.public_ip rcuk = self.addResourceCleanUp(self.client.release_address, public_ip) addresses_get = self.client.get_all_addresses(addresses=(public_ip,)) self.assertEqual(len(addresses_get), 1) self.assertEqual(addresses_get[0].public_ip, public_ip) self.assertBotoError(ec2_codes.client.InvalidAssociationID.NotFound, address.disassociate) self.client.release_address(public_ip) self.cancelResourceCleanUp(rcuk) self.assertAddressReleasedWait(address) tempest-2013.2.a1291.g23a1b4f/tempest/thirdparty/boto/test_ec2_volumes.py0000664000175000017500000000474212161375672026137 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest import clients from tempest.common import log as logging from tempest.test import attr from tempest.thirdparty.boto.test import BotoTestCase LOG = logging.getLogger(__name__) def compare_volumes(a, b): return (a.id == b.id and a.size == b.size) class EC2VolumesTest(BotoTestCase): @classmethod def setUpClass(cls): super(EC2VolumesTest, cls).setUpClass() cls.os = clients.Manager() cls.client = cls.os.ec2api_client cls.zone = cls.client.get_good_zone() @attr(type='smoke') def test_create_get_delete(self): # EC2 Create, get, delete Volume volume = self.client.create_volume(1, self.zone) cuk = self.addResourceCleanUp(self.client.delete_volume, volume.id) self.assertIn(volume.status, self.valid_volume_status) retrieved = self.client.get_all_volumes((volume.id,)) self.assertEqual(1, len(retrieved)) self.assertTrue(compare_volumes(volume, retrieved[0])) self.assertVolumeStatusWait(volume, "available") self.client.delete_volume(volume.id) self.cancelResourceCleanUp(cuk) @attr(type='smoke') def test_create_volume_from_snapshot(self): # EC2 Create volume from snapshot volume = self.client.create_volume(1, self.zone) self.addResourceCleanUp(self.client.delete_volume, volume.id) self.assertVolumeStatusWait(volume, "available") snap = self.client.create_snapshot(volume.id) self.addResourceCleanUp(self.destroy_snapshot_wait, snap) self.assertSnapshotStatusWait(snap, "completed") svol = self.client.create_volume(1, self.zone, snapshot=snap) cuk = self.addResourceCleanUp(svol.delete) self.assertVolumeStatusWait(svol, "available") svol.delete() self.cancelResourceCleanUp(cuk) tempest-2013.2.a1291.g23a1b4f/tempest/thirdparty/boto/utils/0000775000175000017500000000000012161375700023424 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/thirdparty/boto/utils/wait.py0000664000175000017500000001120012161375672024744 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 re import time import boto.exception from testtools import TestCase from tempest.common import log as logging import tempest.config LOG = logging.getLogger(__name__) _boto_config = tempest.config.TempestConfig().boto default_timeout = _boto_config.build_timeout default_check_interval = _boto_config.build_interval def state_wait(lfunction, final_set=set(), valid_set=None): #TODO(afazekas): evaluate using ABC here if not isinstance(final_set, set): final_set = set((final_set,)) if not isinstance(valid_set, set) and valid_set is not None: valid_set = set((valid_set,)) start_time = time.time() old_status = status = lfunction() while True: if status != old_status: LOG.info('State transition "%s" ==> "%s" %d second', old_status, status, time.time() - start_time) if status in final_set: return status if valid_set is not None and status not in valid_set: return status dtime = time.time() - start_time if dtime > default_timeout: raise TestCase.failureException("State change timeout exceeded!" '(%ds) While waiting' 'for %s at "%s"' % (dtime, final_set, status)) time.sleep(default_check_interval) old_status = status status = lfunction() def re_search_wait(lfunction, regexp): """Stops waiting on success.""" start_time = time.time() while True: text = lfunction() result = re.search(regexp, text) if result is not None: LOG.info('Pattern "%s" found in %d second in "%s"', regexp, time.time() - start_time, text) return result dtime = time.time() - start_time if dtime > default_timeout: raise TestCase.failureException('Pattern find timeout exceeded!' '(%ds) While waiting for' '"%s" pattern in "%s"' % (dtime, regexp, text)) time.sleep(default_check_interval) def wait_no_exception(lfunction, exc_class=None, exc_matcher=None): """Stops waiting on success.""" start_time = time.time() if exc_matcher is not None: exc_class = boto.exception.BotoServerError if exc_class is None: exc_class = BaseException while True: result = None try: result = lfunction() LOG.info('No Exception in %d second', time.time() - start_time) return result except exc_class as exc: if exc_matcher is not None: res = exc_matcher.match(exc) if res is not None: LOG.info(res) raise exc # Let the other exceptions propagate dtime = time.time() - start_time if dtime > default_timeout: raise TestCase.failureException("Wait timeout exceeded! (%ds)" % dtime) time.sleep(default_check_interval) #NOTE(afazekas): EC2/boto normally raise exception instead of empty list def wait_exception(lfunction): """Returns with the exception or raises one.""" start_time = time.time() while True: try: lfunction() except BaseException as exc: LOG.info('Exception in %d second', time.time() - start_time) return exc dtime = time.time() - start_time if dtime > default_timeout: raise TestCase.failureException("Wait timeout exceeded! (%ds)" % dtime) time.sleep(default_check_interval) #TODO(afazekas): consider strategy design pattern.. tempest-2013.2.a1291.g23a1b4f/tempest/thirdparty/boto/utils/s3.py0000664000175000017500000000305312161375672024334 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 os import re import boto import boto.s3.key from tempest.common import log as logging LOG = logging.getLogger(__name__) def s3_upload_dir(bucket, path, prefix="", connection_data=None): if isinstance(bucket, basestring): with contextlib.closing(boto.connect_s3(**connection_data)) as conn: bucket = conn.lookup(bucket) for root, dirs, files in os.walk(path): for fil in files: with contextlib.closing(boto.s3.key.Key(bucket)) as key: source = root + os.sep + fil target = re.sub("^" + re.escape(path) + "?/", prefix, source) if os.sep != '/': target = re.sub(re.escape(os.sep), '/', target) key.key = target LOG.info("Uploading %s to %s/%s", source, bucket.name, target) key.set_contents_from_filename(source) tempest-2013.2.a1291.g23a1b4f/tempest/thirdparty/boto/utils/__init__.py0000664000175000017500000000000012161375672025533 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/thirdparty/boto/test_ec2_security_groups.py0000664000175000017500000000666712161375672027723 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 tempest import clients from tempest.common.utils.data_utils import rand_name from tempest.test import attr from tempest.thirdparty.boto.test import BotoTestCase class EC2SecurityGroupTest(BotoTestCase): @classmethod def setUpClass(cls): super(EC2SecurityGroupTest, cls).setUpClass() cls.os = clients.Manager() cls.client = cls.os.ec2api_client @attr(type='smoke') def test_create_authorize_security_group(self): # EC2 Create, authorize/revoke security group group_name = rand_name("securty_group-") group_description = group_name + " security group description " group = self.client.create_security_group(group_name, group_description) self.addResourceCleanUp(self.client.delete_security_group, group_name) groups_get = self.client.get_all_security_groups( groupnames=(group_name,)) self.assertEqual(len(groups_get), 1) group_get = groups_get[0] self.assertEqual(group.name, group_get.name) self.assertEqual(group.name, group_get.name) #ping (icmp_echo) and other icmp allowed from everywhere # from_port and to_port act as icmp type success = self.client.authorize_security_group(group_name, ip_protocol="icmp", cidr_ip="0.0.0.0/0", from_port=-1, to_port=-1) self.assertTrue(success) #allow standard ssh port from anywhere success = self.client.authorize_security_group(group_name, ip_protocol="tcp", cidr_ip="0.0.0.0/0", from_port=22, to_port=22) self.assertTrue(success) #TODO(afazekas): Duplicate tests group_get = self.client.get_all_security_groups( groupnames=(group_name,))[0] #remove listed rules for ip_permission in group_get.rules: for cidr in ip_permission.grants: self.assertTrue(self.client.revoke_security_group(group_name, ip_protocol=ip_permission.ip_protocol, cidr_ip=cidr, from_port=ip_permission.from_port, to_port=ip_permission.to_port)) group_get = self.client.get_all_security_groups( groupnames=(group_name,))[0] #all rules shuld be removed now self.assertEqual(0, len(group_get.rules)) tempest-2013.2.a1291.g23a1b4f/tempest/thirdparty/boto/test_ec2_keys.py0000664000175000017500000000552612161375672025421 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 testtools from tempest import clients from tempest.common.utils.data_utils import rand_name from tempest.test import attr from tempest.thirdparty.boto.test import BotoTestCase def compare_key_pairs(a, b): return (a.name == b.name and a.fingerprint == b.fingerprint) class EC2KeysTest(BotoTestCase): @classmethod def setUpClass(cls): super(EC2KeysTest, cls).setUpClass() cls.os = clients.Manager() cls.client = cls.os.ec2api_client cls.ec = cls.ec2_error_code #TODO(afazekas): merge create, delete, get test cases @attr(type='smoke') def test_create_ec2_keypair(self): # EC2 create KeyPair key_name = rand_name("keypair-") self.addResourceCleanUp(self.client.delete_key_pair, key_name) keypair = self.client.create_key_pair(key_name) self.assertTrue(compare_key_pairs(keypair, self.client.get_key_pair(key_name))) @attr(type='smoke') @testtools.skip("Skipped until the Bug #1072318 is resolved") def test_delete_ec2_keypair(self): # EC2 delete KeyPair key_name = rand_name("keypair-") self.client.create_key_pair(key_name) self.client.delete_key_pair(key_name) self.assertEqual(None, self.client.get_key_pair(key_name)) @attr(type='smoke') def test_get_ec2_keypair(self): # EC2 get KeyPair key_name = rand_name("keypair-") self.addResourceCleanUp(self.client.delete_key_pair, key_name) keypair = self.client.create_key_pair(key_name) self.assertTrue(compare_key_pairs(keypair, self.client.get_key_pair(key_name))) @attr(type='smoke') def test_duplicate_ec2_keypair(self): # EC2 duplicate KeyPair key_name = rand_name("keypair-") self.addResourceCleanUp(self.client.delete_key_pair, key_name) keypair = self.client.create_key_pair(key_name) self.assertBotoError(self.ec.client.InvalidKeyPair.Duplicate, self.client.create_key_pair, key_name) self.assertTrue(compare_key_pairs(keypair, self.client.get_key_pair(key_name))) tempest-2013.2.a1291.g23a1b4f/tempest/thirdparty/boto/test_s3_buckets.py0000664000175000017500000000350612161375672025756 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 testtools from tempest import clients from tempest.common.utils.data_utils import rand_name from tempest.test import attr from tempest.thirdparty.boto.test import BotoTestCase class S3BucketsTest(BotoTestCase): @classmethod def setUpClass(cls): super(S3BucketsTest, cls).setUpClass() cls.os = clients.Manager() cls.client = cls.os.s3_client @testtools.skip("Skipped until the Bug #1076965 is resolved") @attr(type='smoke') def test_create_and_get_delete_bucket(self): # S3 Create, get and delete bucket bucket_name = rand_name("s3bucket-") cleanup_key = self.addResourceCleanUp(self.client.delete_bucket, bucket_name) bucket = self.client.create_bucket(bucket_name) self.assertTrue(bucket.name == bucket_name) bucket = self.client.get_bucket(bucket_name) self.assertTrue(bucket.name == bucket_name) self.client.delete_bucket(bucket_name) self.assertBotoError(self.s3_error_code.client.NoSuchBucket, self.client.get_bucket, bucket_name) self.cancelResourceCleanUp(cleanup_key) tempest-2013.2.a1291.g23a1b4f/tempest/thirdparty/boto/test_s3_objects.py0000664000175000017500000000371212161375672025746 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 boto.s3.key from tempest import clients from tempest.common.utils.data_utils import rand_name from tempest.test import attr from tempest.thirdparty.boto.test import BotoTestCase class S3BucketsTest(BotoTestCase): @classmethod def setUpClass(cls): super(S3BucketsTest, cls).setUpClass() cls.os = clients.Manager() cls.client = cls.os.s3_client @attr(type='smoke') def test_create_get_delete_object(self): # S3 Create, get and delete object bucket_name = rand_name("s3bucket-") object_name = rand_name("s3object-") content = 'x' * 42 bucket = self.client.create_bucket(bucket_name) self.addResourceCleanUp(self.destroy_bucket, self.client.connection_data, bucket_name) self.assertTrue(bucket.name == bucket_name) with contextlib.closing(boto.s3.key.Key(bucket)) as key: key.key = object_name key.set_contents_from_string(content) readback = key.get_contents_as_string() self.assertTrue(readback == content) bucket.delete_key(key) self.assertBotoError(self.s3_error_code.client.NoSuchKey, key.get_contents_as_string) tempest-2013.2.a1291.g23a1b4f/tempest/thirdparty/boto/test_s3_ec2_images.py0000664000175000017500000001404612161375672026315 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 os import testtools from tempest import clients from tempest.common.utils.data_utils import rand_name from tempest.test import attr from tempest.thirdparty.boto.test import BotoTestCase from tempest.thirdparty.boto.utils.s3 import s3_upload_dir from tempest.thirdparty.boto.utils.wait import state_wait class S3ImagesTest(BotoTestCase): @classmethod def setUpClass(cls): super(S3ImagesTest, cls).setUpClass() if not cls.conclusion['A_I_IMAGES_READY']: raise cls.skipException("".join(("EC2 ", cls.__name__, ": requires ami/aki/ari manifest"))) cls.os = clients.Manager() cls.s3_client = cls.os.s3_client cls.images_client = cls.os.ec2api_client config = cls.config cls.materials_path = config.boto.s3_materials_path cls.ami_manifest = config.boto.ami_manifest cls.aki_manifest = config.boto.aki_manifest cls.ari_manifest = config.boto.ari_manifest cls.ami_path = cls.materials_path + os.sep + cls.ami_manifest cls.aki_path = cls.materials_path + os.sep + cls.aki_manifest cls.ari_path = cls.materials_path + os.sep + cls.ari_manifest cls.bucket_name = rand_name("bucket-") bucket = cls.s3_client.create_bucket(cls.bucket_name) cls.addResourceCleanUp(cls.destroy_bucket, cls.s3_client.connection_data, cls.bucket_name) s3_upload_dir(bucket, cls.materials_path) #Note(afazekas): Without the normal status change test! # otherwise I would skip it too @attr(type='smoke') def test_register_get_deregister_ami_image(self): # Register and deregister ami image image = {"name": rand_name("ami-name-"), "location": self.bucket_name + "/" + self.ami_manifest, "type": "ami"} image["image_id"] = self.images_client.register_image( name=image["name"], image_location=image["location"]) #Note(afazekas): delete_snapshot=True might trigger boto lib? bug image["cleanUp"] = self.addResourceCleanUp( self.images_client.deregister_image, image["image_id"]) self.assertEqual(image["image_id"][0:3], image["type"]) retrieved_image = self.images_client.get_image(image["image_id"]) self.assertTrue(retrieved_image.name == image["name"]) self.assertTrue(retrieved_image.id == image["image_id"]) state = retrieved_image.state if state != "available": def _state(): retr = self.images_client.get_image(image["image_id"]) return retr.state state = state_wait(_state, "available") self.assertEqual("available", state) self.images_client.deregister_image(image["image_id"]) self.assertNotIn(image["image_id"], str( self.images_client.get_all_images())) self.cancelResourceCleanUp(image["cleanUp"]) def test_register_get_deregister_aki_image(self): # Register and deregister aki image image = {"name": rand_name("aki-name-"), "location": self.bucket_name + "/" + self.aki_manifest, "type": "aki"} image["image_id"] = self.images_client.register_image( name=image["name"], image_location=image["location"]) image["cleanUp"] = self.addResourceCleanUp( self.images_client.deregister_image, image["image_id"]) self.assertEqual(image["image_id"][0:3], image["type"]) retrieved_image = self.images_client.get_image(image["image_id"]) self.assertTrue(retrieved_image.name == image["name"]) self.assertTrue(retrieved_image.id == image["image_id"]) self.assertIn(retrieved_image.state, self.valid_image_state) if retrieved_image.state != "available": self.assertImageStateWait(retrieved_image, "available") self.images_client.deregister_image(image["image_id"]) self.assertNotIn(image["image_id"], str( self.images_client.get_all_images())) self.cancelResourceCleanUp(image["cleanUp"]) @testtools.skip("Skipped until the Bug #1074908 and #1074904 is resolved") def test_register_get_deregister_ari_image(self): # Register and deregister ari image image = {"name": rand_name("ari-name-"), "location": "/" + self.bucket_name + "/" + self.ari_manifest, "type": "ari"} image["image_id"] = self.images_client.register_image( name=image["name"], image_location=image["location"]) image["cleanUp"] = self.addResourceCleanUp( self.images_client.deregister_image, image["image_id"]) self.assertEqual(image["image_id"][0:3], image["type"]) retrieved_image = self.images_client.get_image(image["image_id"]) self.assertIn(retrieved_image.state, self.valid_image_state) if retrieved_image.state != "available": self.assertImageStateWait(retrieved_image, "available") self.assertIn(retrieved_image.state, self.valid_image_state) self.assertTrue(retrieved_image.name == image["name"]) self.assertTrue(retrieved_image.id == image["image_id"]) self.images_client.deregister_image(image["image_id"]) self.cancelResourceCleanUp(image["cleanUp"]) #TODO(afazekas): less copy-paste style tempest-2013.2.a1291.g23a1b4f/tempest/thirdparty/boto/__init__.py0000664000175000017500000000000012161375672024373 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/thirdparty/boto/test_ec2_instance_run.py0000664000175000017500000002446312161375672027137 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 boto import exception import testtools from tempest import clients from tempest.common import log as logging from tempest.common.utils.data_utils import rand_name from tempest.common.utils.linux.remote_client import RemoteClient from tempest import exceptions from tempest.test import attr from tempest.thirdparty.boto.test import BotoTestCase from tempest.thirdparty.boto.utils.s3 import s3_upload_dir from tempest.thirdparty.boto.utils.wait import re_search_wait from tempest.thirdparty.boto.utils.wait import state_wait LOG = logging.getLogger(__name__) class InstanceRunTest(BotoTestCase): @classmethod def setUpClass(cls): super(InstanceRunTest, cls).setUpClass() if not cls.conclusion['A_I_IMAGES_READY']: raise cls.skipException("".join(("EC2 ", cls.__name__, ": requires ami/aki/ari manifest"))) cls.os = clients.Manager() cls.s3_client = cls.os.s3_client cls.ec2_client = cls.os.ec2api_client cls.zone = cls.ec2_client.get_good_zone() config = cls.config cls.materials_path = config.boto.s3_materials_path ami_manifest = config.boto.ami_manifest aki_manifest = config.boto.aki_manifest ari_manifest = config.boto.ari_manifest cls.instance_type = config.boto.instance_type cls.bucket_name = rand_name("s3bucket-") cls.keypair_name = rand_name("keypair-") cls.keypair = cls.ec2_client.create_key_pair(cls.keypair_name) cls.addResourceCleanUp(cls.ec2_client.delete_key_pair, cls.keypair_name) bucket = cls.s3_client.create_bucket(cls.bucket_name) cls.addResourceCleanUp(cls.destroy_bucket, cls.s3_client.connection_data, cls.bucket_name) s3_upload_dir(bucket, cls.materials_path) cls.images = {"ami": {"name": rand_name("ami-name-"), "location": cls.bucket_name + "/" + ami_manifest}, "aki": {"name": rand_name("aki-name-"), "location": cls.bucket_name + "/" + aki_manifest}, "ari": {"name": rand_name("ari-name-"), "location": cls.bucket_name + "/" + ari_manifest}} for image in cls.images.itervalues(): image["image_id"] = cls.ec2_client.register_image( name=image["name"], image_location=image["location"]) cls.addResourceCleanUp(cls.ec2_client.deregister_image, image["image_id"]) for image in cls.images.itervalues(): def _state(): retr = cls.ec2_client.get_image(image["image_id"]) return retr.state state = state_wait(_state, "available") if state != "available": for _image in cls.images.itervalues(): cls.ec2_client.deregister_image(_image["image_id"]) raise exceptions.EC2RegisterImageException(image_id= image["image_id"]) @attr(type='smoke') def test_run_stop_terminate_instance(self): # EC2 run, stop and terminate instance image_ami = self.ec2_client.get_image(self.images["ami"] ["image_id"]) reservation = image_ami.run(kernel_id=self.images["aki"]["image_id"], ramdisk_id=self.images["ari"]["image_id"], instance_type=self.instance_type) rcuk = self.addResourceCleanUp(self.destroy_reservation, reservation) for instance in reservation.instances: LOG.info("state: %s", instance.state) if instance.state != "running": self.assertInstanceStateWait(instance, "running") for instance in reservation.instances: instance.stop() LOG.info("state: %s", instance.state) if instance.state != "stopped": self.assertInstanceStateWait(instance, "stopped") for instance in reservation.instances: instance.terminate() self.cancelResourceCleanUp(rcuk) @attr(type='smoke') @testtools.skip("Skipped until the Bug #1098891 is resolved") def test_run_terminate_instance(self): # EC2 run, terminate immediately image_ami = self.ec2_client.get_image(self.images["ami"] ["image_id"]) reservation = image_ami.run(kernel_id=self.images["aki"]["image_id"], ramdisk_id=self.images["ari"]["image_id"], instance_type=self.instance_type) for instance in reservation.instances: instance.terminate() try: instance.update(validate=True) except ValueError: pass except exception.EC2ResponseError as exc: if self.ec2_error_code.\ client.InvalidInstanceID.NotFound.match(exc): pass else: raise else: self.assertNotEqual(instance.state, "running") #NOTE(afazekas): doctored test case, # with normal validation it would fail @testtools.skip("Until Bug #1182679 is fixed") @attr(type='smoke') def test_integration_1(self): # EC2 1. integration test (not strict) image_ami = self.ec2_client.get_image(self.images["ami"]["image_id"]) sec_group_name = rand_name("securitygroup-") group_desc = sec_group_name + " security group description " security_group = self.ec2_client.create_security_group(sec_group_name, group_desc) self.addResourceCleanUp(self.destroy_security_group_wait, security_group) self.assertTrue( self.ec2_client.authorize_security_group( sec_group_name, ip_protocol="icmp", cidr_ip="0.0.0.0/0", from_port=-1, to_port=-1)) self.assertTrue( self.ec2_client.authorize_security_group( sec_group_name, ip_protocol="tcp", cidr_ip="0.0.0.0/0", from_port=22, to_port=22)) reservation = image_ami.run(kernel_id=self.images["aki"]["image_id"], ramdisk_id=self.images["ari"]["image_id"], instance_type=self.instance_type, key_name=self.keypair_name, security_groups=(sec_group_name,)) self.addResourceCleanUp(self.destroy_reservation, reservation) volume = self.ec2_client.create_volume(1, self.zone) self.addResourceCleanUp(self.destroy_volume_wait, volume) instance = reservation.instances[0] LOG.info("state: %s", instance.state) if instance.state != "running": self.assertInstanceStateWait(instance, "running") address = self.ec2_client.allocate_address() rcuk_a = self.addResourceCleanUp(address.delete) self.assertTrue(address.associate(instance.id)) rcuk_da = self.addResourceCleanUp(address.disassociate) #TODO(afazekas): ping test. dependecy/permission ? self.assertVolumeStatusWait(volume, "available") #NOTE(afazekas): it may be reports availble before it is available ssh = RemoteClient(address.public_ip, self.os.config.compute.ssh_user, pkey=self.keypair.material) text = rand_name("Pattern text for console output -") resp = ssh.write_to_console(text) self.assertFalse(resp) def _output(): output = instance.get_console_output() return output.output re_search_wait(_output, text) part_lines = ssh.get_partitions().split('\n') volume.attach(instance.id, "/dev/vdh") def _volume_state(): volume.update(validate=True) return volume.status self.assertVolumeStatusWait(_volume_state, "in-use") re_search_wait(_volume_state, "in-use") #NOTE(afazekas): Different Hypervisor backends names # differently the devices, # now we just test is the partition number increased/decrised def _part_state(): current = ssh.get_partitions().split('\n') if current > part_lines: return 'INCREASE' if current < part_lines: return 'DECREASE' return 'EQUAL' state_wait(_part_state, 'INCREASE') part_lines = ssh.get_partitions().split('\n') #TODO(afazekas): Resource compare to the flavor settings volume.detach() self.assertVolumeStatusWait(_volume_state, "available") re_search_wait(_volume_state, "available") LOG.info("Volume %s state: %s", volume.id, volume.status) state_wait(_part_state, 'DECREASE') instance.stop() address.disassociate() self.assertAddressDissasociatedWait(address) self.cancelResourceCleanUp(rcuk_da) address.release() self.assertAddressReleasedWait(address) self.cancelResourceCleanUp(rcuk_a) LOG.info("state: %s", instance.state) if instance.state != "stopped": self.assertInstanceStateWait(instance, "stopped") #TODO(afazekas): move steps from teardown to the test case #TODO(afazekas): Snapshot/volume read/write test case tempest-2013.2.a1291.g23a1b4f/tempest/thirdparty/boto/test.py0000664000175000017500000006424312161375672023636 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 as orig_logging import os import re import urlparse import boto from boto import ec2 from boto import exception from boto import s3 import keystoneclient.exceptions import tempest.clients from tempest.common import log as logging from tempest.common.utils.file_utils import have_effective_read_access import tempest.config from tempest import exceptions import tempest.test from tempest.thirdparty.boto.utils.wait import re_search_wait from tempest.thirdparty.boto.utils.wait import state_wait from tempest.thirdparty.boto.utils.wait import wait_exception LOG = logging.getLogger(__name__) def decision_maker(): A_I_IMAGES_READY = True # ari,ami,aki S3_CAN_CONNECT_ERROR = None EC2_CAN_CONNECT_ERROR = None secret_matcher = re.compile("[A-Za-z0-9+/]{32,}") # 40 in other system id_matcher = re.compile("[A-Za-z0-9]{20,}") def all_read(*args): return all(map(have_effective_read_access, args)) config = tempest.config.TempestConfig() materials_path = config.boto.s3_materials_path ami_path = materials_path + os.sep + config.boto.ami_manifest aki_path = materials_path + os.sep + config.boto.aki_manifest ari_path = materials_path + os.sep + config.boto.ari_manifest A_I_IMAGES_READY = all_read(ami_path, aki_path, ari_path) boto_logger = logging.getLogger('boto') level = boto_logger.level boto_logger.setLevel(orig_logging.CRITICAL) # suppress logging for these def _cred_sub_check(connection_data): if not id_matcher.match(connection_data["aws_access_key_id"]): raise Exception("Invalid AWS access Key") if not secret_matcher.match(connection_data["aws_secret_access_key"]): raise Exception("Invalid AWS secret Key") raise Exception("Unknown (Authentication?) Error") openstack = tempest.clients.Manager() try: if urlparse.urlparse(config.boto.ec2_url).hostname is None: raise Exception("Failed to get hostname from the ec2_url") ec2client = openstack.ec2api_client try: ec2client.get_all_regions() except exception.BotoServerError as exc: if exc.error_code is None: raise Exception("EC2 target does not looks EC2 service") _cred_sub_check(ec2client.connection_data) except keystoneclient.exceptions.Unauthorized: EC2_CAN_CONNECT_ERROR = "AWS credentials not set," +\ " faild to get them even by keystoneclient" except Exception as exc: EC2_CAN_CONNECT_ERROR = str(exc) try: if urlparse.urlparse(config.boto.s3_url).hostname is None: raise Exception("Failed to get hostname from the s3_url") s3client = openstack.s3_client try: s3client.get_bucket("^INVALID*#()@INVALID.") except exception.BotoServerError as exc: if exc.status == 403: _cred_sub_check(s3client.connection_data) except Exception as exc: S3_CAN_CONNECT_ERROR = str(exc) except keystoneclient.exceptions.Unauthorized: S3_CAN_CONNECT_ERROR = "AWS credentials not set," +\ " faild to get them even by keystoneclient" boto_logger.setLevel(level) return {'A_I_IMAGES_READY': A_I_IMAGES_READY, 'S3_CAN_CONNECT_ERROR': S3_CAN_CONNECT_ERROR, 'EC2_CAN_CONNECT_ERROR': EC2_CAN_CONNECT_ERROR} class BotoExceptionMatcher(object): STATUS_RE = r'[45]\d\d' CODE_RE = '.*' # regexp makes sense in group match def match(self, exc): if not isinstance(exc, exception.BotoServerError): return "%r not an BotoServerError instance" % exc LOG.info("Status: %s , error_code: %s", exc.status, exc.error_code) if re.match(self.STATUS_RE, str(exc.status)) is None: return ("Status code (%s) does not match" "the expected re pattern \"%s\"" % (exc.status, self.STATUS_RE)) if re.match(self.CODE_RE, str(exc.error_code)) is None: return ("Error code (%s) does not match" + "the expected re pattern \"%s\"") %\ (exc.error_code, self.CODE_RE) class ClientError(BotoExceptionMatcher): STATUS_RE = r'4\d\d' class ServerError(BotoExceptionMatcher): STATUS_RE = r'5\d\d' def _add_matcher_class(error_cls, error_data, base=BotoExceptionMatcher): """ Usable for adding an ExceptionMatcher(s) into the exception tree. The not leaf elements does wildcard match """ # in error_code just literal and '.' characters expected if not isinstance(error_data, basestring): (error_code, status_code) = map(str, error_data) else: status_code = None error_code = error_data parts = error_code.split('.') basematch = "" num_parts = len(parts) max_index = num_parts - 1 add_cls = error_cls for i_part in xrange(num_parts): part = parts[i_part] leaf = i_part == max_index if not leaf: match = basematch + part + "[.].*" else: match = basematch + part basematch += part + "[.]" if not hasattr(add_cls, part): cls_dict = {"CODE_RE": match} if leaf and status_code is not None: cls_dict["STATUS_RE"] = status_code cls = type(part, (base, ), cls_dict) setattr(add_cls, part, cls()) add_cls = cls elif leaf: raise LookupError("Tries to redefine an error code \"%s\"" % part) else: add_cls = getattr(add_cls, part) #TODO(afazekas): classmethod handling def friendly_function_name_simple(call_able): name = "" if hasattr(call_able, "im_class"): name += call_able.im_class.__name__ + "." name += call_able.__name__ return name def friendly_function_call_str(call_able, *args, **kwargs): string = friendly_function_name_simple(call_able) string += "(" + ", ".join(map(str, args)) if len(kwargs): if len(args): string += ", " string += ", ".join("=".join(map(str, (key, value))) for (key, value) in kwargs.items()) return string + ")" class BotoTestCase(tempest.test.BaseTestCase): """Recommended to use as base class for boto related test.""" conclusion = decision_maker() @classmethod def setUpClass(cls): # The trash contains cleanup functions and paramaters in tuples # (function, *args, **kwargs) cls._resource_trash_bin = {} cls._sequence = -1 if (hasattr(cls, "EC2") and cls.conclusion['EC2_CAN_CONNECT_ERROR'] is not None): raise cls.skipException("EC2 " + cls.__name__ + ": " + cls.conclusion['EC2_CAN_CONNECT_ERROR']) if (hasattr(cls, "S3") and cls.conclusion['S3_CAN_CONNECT_ERROR'] is not None): raise cls.skipException("S3 " + cls.__name__ + ": " + cls.conclusion['S3_CAN_CONNECT_ERROR']) @classmethod def addResourceCleanUp(cls, function, *args, **kwargs): """Adds CleanUp callable, used by tearDownClass. Recommended to a use (deep)copy on the mutable args. """ cls._sequence = cls._sequence + 1 cls._resource_trash_bin[cls._sequence] = (function, args, kwargs) return cls._sequence @classmethod def cancelResourceCleanUp(cls, key): """Cancel Clean up request.""" del cls._resource_trash_bin[key] #TODO(afazekas): Add "with" context handling def assertBotoError(self, excMatcher, callableObj, *args, **kwargs): """Example usage: self.assertBotoError(self.ec2_error_code.client. InvalidKeyPair.Duplicate, self.client.create_keypair, key_name) """ try: callableObj(*args, **kwargs) except exception.BotoServerError as exc: error_msg = excMatcher.match(exc) if error_msg is not None: raise self.failureException, error_msg else: raise self.failureException, "BotoServerError not raised" @classmethod def tearDownClass(cls): """Calls the callables added by addResourceCleanUp, when you overwire this function dont't forget to call this too. """ fail_count = 0 trash_keys = sorted(cls._resource_trash_bin, reverse=True) for key in trash_keys: (function, pos_args, kw_args) = cls._resource_trash_bin[key] try: LOG.debug("Cleaning up: %s" % friendly_function_call_str(function, *pos_args, **kw_args)) function(*pos_args, **kw_args) except BaseException as exc: fail_count += 1 LOG.exception(exc) finally: del cls._resource_trash_bin[key] if fail_count: raise exceptions.TearDownException(num=fail_count) ec2_error_code = BotoExceptionMatcher() # InsufficientInstanceCapacity can be both server and client error ec2_error_code.server = ServerError() ec2_error_code.client = ClientError() s3_error_code = BotoExceptionMatcher() s3_error_code.server = ServerError() s3_error_code.client = ClientError() valid_image_state = set(('available', 'pending', 'failed')) #NOTE(afazekas): 'paused' is not valid status in EC2, but it does not have # a good mapping, because it uses memory, but not really a running machine valid_instance_state = set(('pending', 'running', 'shutting-down', 'terminated', 'stopping', 'stopped', 'paused')) valid_volume_status = set(('creating', 'available', 'in-use', 'deleting', 'deleted', 'error')) valid_snapshot_status = set(('pending', 'completed', 'error')) gone_set = set(('_GONE',)) @classmethod def get_lfunction_gone(cls, obj): """If the object is instance of a well know type returns back with with the correspoding function otherwise it assumes the obj itself is the function. """ ec = cls.ec2_error_code if isinstance(obj, ec2.instance.Instance): colusure_matcher = ec.client.InvalidInstanceID.NotFound status_attr = "state" elif isinstance(obj, ec2.image.Image): colusure_matcher = ec.client.InvalidAMIID.NotFound status_attr = "state" elif isinstance(obj, ec2.snapshot.Snapshot): colusure_matcher = ec.client.InvalidSnapshot.NotFound status_attr = "status" elif isinstance(obj, ec2.volume.Volume): colusure_matcher = ec.client.InvalidVolume.NotFound status_attr = "status" else: return obj def _status(): try: obj.update(validate=True) except ValueError: return "_GONE" except exception.EC2ResponseError as exc: if colusure_matcher.match(exc): return "_GONE" else: raise return getattr(obj, status_attr) return _status def state_wait_gone(self, lfunction, final_set, valid_set): if not isinstance(final_set, set): final_set = set((final_set,)) final_set |= self.gone_set lfunction = self.get_lfunction_gone(lfunction) state = state_wait(lfunction, final_set, valid_set) self.assertIn(state, valid_set | self.gone_set) return state def waitImageState(self, lfunction, wait_for): return self.state_wait_gone(lfunction, wait_for, self.valid_image_state) def waitInstanceState(self, lfunction, wait_for): return self.state_wait_gone(lfunction, wait_for, self.valid_instance_state) def waitSnapshotStatus(self, lfunction, wait_for): return self.state_wait_gone(lfunction, wait_for, self.valid_snapshot_status) def waitVolumeStatus(self, lfunction, wait_for): return self.state_wait_gone(lfunction, wait_for, self.valid_volume_status) def assertImageStateWait(self, lfunction, wait_for): state = self.waitImageState(lfunction, wait_for) self.assertIn(state, wait_for) def assertInstanceStateWait(self, lfunction, wait_for): state = self.waitInstanceState(lfunction, wait_for) self.assertIn(state, wait_for) def assertVolumeStatusWait(self, lfunction, wait_for): state = self.waitVolumeStatus(lfunction, wait_for) self.assertIn(state, wait_for) def assertSnapshotStatusWait(self, lfunction, wait_for): state = self.waitSnapshotStatus(lfunction, wait_for) self.assertIn(state, wait_for) def assertAddressDissasociatedWait(self, address): def _disassociate(): cli = self.ec2_client addresses = cli.get_all_addresses(addresses=(address.public_ip,)) if len(addresses) != 1: return "INVALID" if addresses[0].instance_id: LOG.info("%s associated to %s", address.public_ip, addresses[0].instance_id) return "ASSOCIATED" return "DISASSOCIATED" state = state_wait(_disassociate, "DISASSOCIATED", set(("ASSOCIATED", "DISASSOCIATED"))) self.assertEqual(state, "DISASSOCIATED") def assertAddressReleasedWait(self, address): def _address_delete(): #NOTE(afazekas): the filter gives back IP # even if it is not associated to my tenant if (address.public_ip not in map(lambda a: a.public_ip, self.ec2_client.get_all_addresses())): return "DELETED" return "NOTDELETED" state = state_wait(_address_delete, "DELETED") self.assertEqual(state, "DELETED") def assertReSearch(self, regexp, string): if re.search(regexp, string) is None: raise self.failureException("regexp: '%s' not found in '%s'" % (regexp, string)) def assertNotReSearch(self, regexp, string): if re.search(regexp, string) is not None: raise self.failureException("regexp: '%s' found in '%s'" % (regexp, string)) def assertReMatch(self, regexp, string): if re.match(regexp, string) is None: raise self.failureException("regexp: '%s' not matches on '%s'" % (regexp, string)) def assertNotReMatch(self, regexp, string): if re.match(regexp, string) is not None: raise self.failureException("regexp: '%s' matches on '%s'" % (regexp, string)) @classmethod def destroy_bucket(cls, connection_data, bucket): """Destroys the bucket and its content, just for teardown.""" exc_num = 0 try: with contextlib.closing( boto.connect_s3(**connection_data)) as conn: if isinstance(bucket, basestring): bucket = conn.lookup(bucket) assert isinstance(bucket, s3.bucket.Bucket) for obj in bucket.list(): try: bucket.delete_key(obj.key) obj.close() except BaseException as exc: LOG.exception(exc) exc_num += 1 conn.delete_bucket(bucket) except BaseException as exc: LOG.exception(exc) exc_num += 1 if exc_num: raise exceptions.TearDownException(num=exc_num) @classmethod def destroy_reservation(cls, reservation): """Terminate instances in a reservation, just for teardown.""" exc_num = 0 def _instance_state(): try: instance.update(validate=True) except ValueError: return "_GONE" except exception.EC2ResponseError as exc: if cls.ec2_error_code.\ client.InvalidInstanceID.NotFound.match(exc): return "_GONE" #NOTE(afazekas): incorrect code, # but the resource must be destoreyd if exc.error_code == "InstanceNotFound": return "_GONE" return instance.state for instance in reservation.instances: try: instance.terminate() re_search_wait(_instance_state, "_GONE") except BaseException as exc: LOG.exception(exc) exc_num += 1 if exc_num: raise exceptions.TearDownException(num=exc_num) #NOTE(afazekas): The incorrect ErrorCodes makes very, very difficult # to write better teardown @classmethod def destroy_security_group_wait(cls, group): """Delete group. Use just for teardown! """ #NOTE(afazekas): should wait/try until all related instance terminates group.delete() @classmethod def destroy_volume_wait(cls, volume): """Delete volume, tryies to detach first. Use just for teardown! """ exc_num = 0 snaps = volume.snapshots() if len(snaps): LOG.critical("%s Volume has %s snapshot(s)", volume.id, map(snaps.id, snaps)) #Note(afazekas): detaching/attching not valid EC2 status def _volume_state(): volume.update(validate=True) try: if volume.status != "available": volume.detach(force=True) except BaseException as exc: LOG.exception(exc) #exc_num += 1 "nonlocal" not in python2 return volume.status try: re_search_wait(_volume_state, "available") # not validates status LOG.info(_volume_state()) volume.delete() except BaseException as exc: LOG.exception(exc) exc_num += 1 if exc_num: raise exceptions.TearDownException(num=exc_num) @classmethod def destroy_snapshot_wait(cls, snapshot): """delete snaphot, wait until not exists.""" snapshot.delete() def _update(): snapshot.update(validate=True) wait_exception(_update) # you can specify tuples if you want to specify the status pattern for code in ('AddressLimitExceeded', 'AttachmentLimitExceeded', 'AuthFailure', 'Blocked', 'CustomerGatewayLimitExceeded', 'DependencyViolation', 'DiskImageSizeTooLarge', 'FilterLimitExceeded', 'Gateway.NotAttached', 'IdempotentParameterMismatch', 'IncorrectInstanceState', 'IncorrectState', 'InstanceLimitExceeded', 'InsufficientInstanceCapacity', 'InsufficientReservedInstancesCapacity', 'InternetGatewayLimitExceeded', 'InvalidAMIAttributeItemValue', 'InvalidAMIID.Malformed', 'InvalidAMIID.NotFound', 'InvalidAMIID.Unavailable', 'InvalidAssociationID.NotFound', 'InvalidAttachment.NotFound', 'InvalidConversionTaskId', 'InvalidCustomerGateway.DuplicateIpAddress', 'InvalidCustomerGatewayID.NotFound', 'InvalidDevice.InUse', 'InvalidDhcpOptionsID.NotFound', 'InvalidFormat', 'InvalidFilter', 'InvalidGatewayID.NotFound', 'InvalidGroup.Duplicate', 'InvalidGroupId.Malformed', 'InvalidGroup.InUse', 'InvalidGroup.NotFound', 'InvalidGroup.Reserved', 'InvalidInstanceID.Malformed', 'InvalidInstanceID.NotFound', 'InvalidInternetGatewayID.NotFound', 'InvalidIPAddress.InUse', 'InvalidKeyPair.Duplicate', 'InvalidKeyPair.Format', 'InvalidKeyPair.NotFound', 'InvalidManifest', 'InvalidNetworkAclEntry.NotFound', 'InvalidNetworkAclID.NotFound', 'InvalidParameterCombination', 'InvalidParameterValue', 'InvalidPermission.Duplicate', 'InvalidPermission.Malformed', 'InvalidReservationID.Malformed', 'InvalidReservationID.NotFound', 'InvalidRoute.NotFound', 'InvalidRouteTableID.NotFound', 'InvalidSecurity.RequestHasExpired', 'InvalidSnapshotID.Malformed', 'InvalidSnapshot.NotFound', 'InvalidUserID.Malformed', 'InvalidReservedInstancesId', 'InvalidReservedInstancesOfferingId', 'InvalidSubnetID.NotFound', 'InvalidVolumeID.Duplicate', 'InvalidVolumeID.Malformed', 'InvalidVolumeID.ZoneMismatch', 'InvalidVolume.NotFound', 'InvalidVpcID.NotFound', 'InvalidVpnConnectionID.NotFound', 'InvalidVpnGatewayID.NotFound', 'InvalidZone.NotFound', 'LegacySecurityGroup', 'MissingParameter', 'NetworkAclEntryAlreadyExists', 'NetworkAclEntryLimitExceeded', 'NetworkAclLimitExceeded', 'NonEBSInstance', 'PendingSnapshotLimitExceeded', 'PendingVerification', 'OptInRequired', 'RequestLimitExceeded', 'ReservedInstancesLimitExceeded', 'Resource.AlreadyAssociated', 'ResourceLimitExceeded', 'RouteAlreadyExists', 'RouteLimitExceeded', 'RouteTableLimitExceeded', 'RulesPerSecurityGroupLimitExceeded', 'SecurityGroupLimitExceeded', 'SecurityGroupsPerInstanceLimitExceeded', 'SnapshotLimitExceeded', 'SubnetLimitExceeded', 'UnknownParameter', 'UnsupportedOperation', 'VolumeLimitExceeded', 'VpcLimitExceeded', 'VpnConnectionLimitExceeded', 'VpnGatewayAttachmentLimitExceeded', 'VpnGatewayLimitExceeded'): _add_matcher_class(BotoTestCase.ec2_error_code.client, code, base=ClientError) for code in ('InsufficientAddressCapacity', 'InsufficientInstanceCapacity', 'InsufficientReservedInstanceCapacity', 'InternalError', 'Unavailable'): _add_matcher_class(BotoTestCase.ec2_error_code.server, code, base=ServerError) for code in (('AccessDenied', 403), ('AccountProblem', 403), ('AmbiguousGrantByEmailAddress', 400), ('BadDigest', 400), ('BucketAlreadyExists', 409), ('BucketAlreadyOwnedByYou', 409), ('BucketNotEmpty', 409), ('CredentialsNotSupported', 400), ('CrossLocationLoggingProhibited', 403), ('EntityTooSmall', 400), ('EntityTooLarge', 400), ('ExpiredToken', 400), ('IllegalVersioningConfigurationException', 400), ('IncompleteBody', 400), ('IncorrectNumberOfFilesInPostRequest', 400), ('InlineDataTooLarge', 400), ('InvalidAccessKeyId', 403), 'InvalidAddressingHeader', ('InvalidArgument', 400), ('InvalidBucketName', 400), ('InvalidBucketState', 409), ('InvalidDigest', 400), ('InvalidLocationConstraint', 400), ('InvalidPart', 400), ('InvalidPartOrder', 400), ('InvalidPayer', 403), ('InvalidPolicyDocument', 400), ('InvalidRange', 416), ('InvalidRequest', 400), ('InvalidSecurity', 403), ('InvalidSOAPRequest', 400), ('InvalidStorageClass', 400), ('InvalidTargetBucketForLogging', 400), ('InvalidToken', 400), ('InvalidURI', 400), ('KeyTooLong', 400), ('MalformedACLError', 400), ('MalformedPOSTRequest', 400), ('MalformedXML', 400), ('MaxMessageLengthExceeded', 400), ('MaxPostPreDataLengthExceededError', 400), ('MetadataTooLarge', 400), ('MethodNotAllowed', 405), ('MissingAttachment'), ('MissingContentLength', 411), ('MissingRequestBodyError', 400), ('MissingSecurityElement', 400), ('MissingSecurityHeader', 400), ('NoLoggingStatusForKey', 400), ('NoSuchBucket', 404), ('NoSuchKey', 404), ('NoSuchLifecycleConfiguration', 404), ('NoSuchUpload', 404), ('NoSuchVersion', 404), ('NotSignedUp', 403), ('NotSuchBucketPolicy', 404), ('OperationAborted', 409), ('PermanentRedirect', 301), ('PreconditionFailed', 412), ('Redirect', 307), ('RequestIsNotMultiPartContent', 400), ('RequestTimeout', 400), ('RequestTimeTooSkewed', 403), ('RequestTorrentOfBucketError', 400), ('SignatureDoesNotMatch', 403), ('TemporaryRedirect', 307), ('TokenRefreshRequired', 400), ('TooManyBuckets', 400), ('UnexpectedContent', 400), ('UnresolvableGrantByEmailAddress', 400), ('UserKeyMustBeSpecified', 400)): _add_matcher_class(BotoTestCase.s3_error_code.client, code, base=ClientError) for code in (('InternalError', 500), ('NotImplemented', 501), ('ServiceUnavailable', 503), ('SlowDown', 503)): _add_matcher_class(BotoTestCase.s3_error_code.server, code, base=ServerError) tempest-2013.2.a1291.g23a1b4f/tempest/thirdparty/README.rst0000664000175000017500000000167212161375672023026 0ustar chuckchuck00000000000000Tempest Guide to Third Party API tests ======== What are these tests? -------- Third party tests are tests for non native OpenStack APIs that are part of OpenStack projects. If we ship an API, we're really required to ensure that it's working. An example is that Nova Compute currently has EC2 API support in tree, which should be tested as part of normal process. Why are these tests in tempest? -------- If we ship an API in an OpenStack component, there should be tests in tempest to exercise it in some way. Scope of these tests -------- Third party API testing should be limited to the functional testing of third party API compliance. Complex scenarios should be avoided, and instead exercised with the OpenStack API, unless the third party API can't be tested without those scenarios. Whenever possible third party API testing should use a client as close to the third party API as possible. The point of these tests is API validation. tempest-2013.2.a1291.g23a1b4f/tempest/thirdparty/__init__.py0000664000175000017500000000000012161375672023430 0ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest/test.py0000664000175000017500000001265512161375672020501 0ustar chuckchuck00000000000000# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack, LLC # 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 time import nose.plugins.attrib import testresources import testtools from tempest.common import log as logging from tempest import config from tempest import manager LOG = logging.getLogger(__name__) def attr(*args, **kwargs): """A decorator which applies the nose and testtools attr decorator This decorator applies the nose attr decorator as well as the the testtools.testcase.attr if it is in the list of attributes to testtools we want to apply. """ def decorator(f): if 'type' in kwargs and isinstance(kwargs['type'], str): f = testtools.testcase.attr(kwargs['type'])(f) if kwargs['type'] == 'smoke': f = testtools.testcase.attr('gate')(f) elif 'type' in kwargs and isinstance(kwargs['type'], list): for attr in kwargs['type']: f = testtools.testcase.attr(attr)(f) if attr == 'smoke': f = testtools.testcase.attr('gate')(f) return nose.plugins.attrib.attr(*args, **kwargs)(f) return decorator class BaseTestCase(testtools.TestCase, testtools.testcase.WithAttributes, testresources.ResourcedTestCase): config = config.TempestConfig() @classmethod def setUpClass(cls): if hasattr(super(BaseTestCase, cls), 'setUpClass'): super(BaseTestCase, cls).setUpClass() def call_until_true(func, duration, sleep_for): """ Call the given function until it returns True (and return True) or until the specified duration (in seconds) elapses (and return False). :param func: A zero argument callable that returns True on success. :param duration: The number of seconds for which to attempt a successful call of the function. :param sleep_for: The number of seconds to sleep after an unsuccessful invocation of the function. """ now = time.time() timeout = now + duration while now < timeout: if func(): return True LOG.debug("Sleeping for %d seconds", sleep_for) time.sleep(sleep_for) now = time.time() return False class TestCase(BaseTestCase): """Base test case class for all Tempest tests Contains basic setup and convenience methods """ manager_class = None @classmethod def setUpClass(cls): cls.manager = cls.manager_class() for attr_name in cls.manager.client_attr_names: # Ensure that pre-existing class attributes won't be # accidentally overriden. assert not hasattr(cls, attr_name) client = getattr(cls.manager, attr_name) setattr(cls, attr_name, client) cls.resource_keys = {} cls.os_resources = [] def set_resource(self, key, thing): LOG.debug("Adding %r to shared resources of %s" % (thing, self.__class__.__name__)) self.resource_keys[key] = thing self.os_resources.append(thing) def get_resource(self, key): return self.resource_keys[key] def remove_resource(self, key): thing = self.resource_keys[key] self.os_resources.remove(thing) del self.resource_keys[key] def status_timeout(self, things, thing_id, expected_status): """ Given a thing and an expected status, do a loop, sleeping for a configurable amount of time, checking for the expected status to show. At any time, if the returned status of the thing is ERROR, fail out. """ def check_status(): # python-novaclient has resources available to its client # that all implement a get() method taking an identifier # for the singular resource to retrieve. thing = things.get(thing_id) new_status = thing.status if new_status == 'ERROR': self.fail("%s failed to get to expected status." "In ERROR state." % thing) elif new_status == expected_status: return True # All good. LOG.debug("Waiting for %s to get to %s status. " "Currently in %s status", thing, expected_status, new_status) conf = config.TempestConfig() if not call_until_true(check_status, conf.compute.build_timeout, conf.compute.build_interval): self.fail("Timed out waiting for thing %s to become %s" % (thing_id, expected_status)) class ComputeFuzzClientTest(TestCase): """ Base test case class for OpenStack Compute API (Nova) that uses the Tempest REST fuzz client libs for calling the API. """ manager_class = manager.ComputeFuzzClientManager tempest-2013.2.a1291.g23a1b4f/setup.py0000775000175000017500000000132312161375672017172 0ustar chuckchuck00000000000000#!/usr/bin/env python # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # 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 setuptools setuptools.setup( setup_requires=['d2to1', 'pbr'], d2to1=True) tempest-2013.2.a1291.g23a1b4f/test-requirements.txt0000664000175000017500000000025212161375672021716 0ustar chuckchuck00000000000000# Install bounded pep8/pyflakes first, then let flake8 install pep8==1.4.5 pyflakes==0.7.2 flake8==2.0 hacking>=0.5.3,<0.6 psycopg2 # needed for doc build sphinx>=1.1.2 tempest-2013.2.a1291.g23a1b4f/etc/0000775000175000017500000000000012161375700016221 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/etc/TEMPEST_README.txt0000664000175000017500000000056612161375672021137 0ustar chuckchuck00000000000000To run: -rename the /etc/tempest.conf.sample file to tempest.conf -Set the fields in the file to values relevant to your system -Set the "authentication" value (basic or keystone_v2 currently supported) -From the root directory of the project, run "./run_tests.sh" this will create the venv to install the project dependencies and run nosetests tempest to run all the tests tempest-2013.2.a1291.g23a1b4f/etc/README.txt0000664000175000017500000000012112161375672017721 0ustar chuckchuck00000000000000Copy config.ini.sample to config.ini, and update it to reflect your environment. tempest-2013.2.a1291.g23a1b4f/etc/logging.conf.sample0000664000175000017500000000117112161375672022006 0ustar chuckchuck00000000000000[loggers] keys=root,tempest [handlers] keys=file,syslog,devel [formatters] keys=default,tests [logger_root] level=NOTSET handlers=syslog [logger_tempest] level=DEBUG handlers=file qualname=tempest [handler_file] class=FileHandler level=DEBUG formatter=tests args=('tempest.log', 'w') [handler_syslog] class=handlers.SysLogHandler level=ERROR formatter = default args = ('/dev/log', handlers.SysLogHandler.LOG_USER) [handler_devel] class=StreamHandler level=DEBUG formatter=default args=(sys.stdout,) [formatter_default] format=%(name)s: %(levelname)s: %(message)s [formatter_tests] class = tempest.common.log.TestsFormatter tempest-2013.2.a1291.g23a1b4f/etc/tempest.conf.sample0000664000175000017500000002531612161375672022050 0ustar chuckchuck00000000000000[identity] # This section contains configuration options that a variety of Tempest # test clients use when authenticating with different user/tenant # combinations # The type of endpoint for a Identity service. Unless you have a # custom Keystone service catalog implementation, you probably want to leave # this value as "identity" catalog_type = identity # Ignore SSL certificate validation failures? Use when in testing # environments that have self-signed SSL certs. disable_ssl_certificate_validation = False # URL for where to find the OpenStack Identity API endpoint (Keystone) uri = http://127.0.0.1:5000/v2.0/ # URL for where to find the OpenStack V3 Identity API endpoint (Keystone) uri_v3 = http://127.0.0.1:5000/v3/ # The identity region region = RegionOne # This should be the username of a user WITHOUT administrative privileges username = demo # The above non-administrative user's password password = secret # The above non-administrative user's tenant name tenant_name = demo # This should be the username of an alternate user WITHOUT # administrative privileges alt_username = alt_demo # The above non-administrative user's password alt_password = secret # The above non-administrative user's tenant name alt_tenant_name = alt_demo # This should be the username of a user WITH administrative privileges admin_username = admin # The above administrative user's password admin_password = secret # The above administrative user's tenant name admin_tenant_name = admin [compute] # This section contains configuration options used when executing tests # against the OpenStack Compute API. # Allows test cases to create/destroy tenants and users. This option # enables isolated test cases and better parallel execution, # but also requires that OpenStack Identity API admin credentials # are known. allow_tenant_isolation = true # Allows test cases to create/destroy tenants and users. This option # enables isolated test cases and better parallel execution, # but also requires that OpenStack Identity API admin credentials # are known. allow_tenant_reuse = true # Reference data for tests. The ref and ref_alt should be # distinct images/flavors. image_ref = {$IMAGE_ID} image_ref_alt = {$IMAGE_ID_ALT} flavor_ref = 1 flavor_ref_alt = 2 # User names used to authenticate to an instance for a given image. image_ssh_user = root image_alt_ssh_user = root # Number of seconds to wait while looping to check the status of an # instance that is building. build_interval = 10 # Number of seconds to time out on waiting for an instance # to build or reach an expected status build_timeout = 600 # Run additional tests that use SSH for instance validation? # This requires the instances be routable from the host # executing the tests run_ssh = false # Name of a user used to authenticated to an instance ssh_user = cirros # Visible fixed network name fixed_network_name = private # Network id used for SSH (public, private, etc) network_for_ssh = private # IP version of the address used for SSH ip_version_for_ssh = 4 # Number of seconds to wait to authenticate to an instance ssh_timeout = 300 # Number of seconds to wait for output from ssh channel ssh_channel_timeout = 60 # The type of endpoint for a Compute API service. Unless you have a # custom Keystone service catalog implementation, you probably want to leave # this value as "compute" catalog_type = compute # Does the Compute API support creation of images? create_image_enabled = true # For resize to work with libvirt/kvm, one of the following must be true: # Single node: allow_resize_to_same_host=True must be set in nova.conf # Cluster: the 'nova' user must have scp access between cluster nodes resize_available = true # Does the compute API support changing the admin password? change_password_available=true # Run live migration tests (requires 2 hosts) live_migration_available = false # Use block live migration (Otherwise, non-block migration will be # performed, which requires XenServer pools in case of using XS) use_block_migration_for_live_migration = false # Supports iSCSI block migration - depends on a XAPI supporting # relax-xsm-sr-check block_migrate_supports_cinder_iscsi = false # When set to false, disk config tests are forced to skip disk_config_enabled = true # When set to false, flavor extra data tests are forced to skip flavor_extra_enabled = true [whitebox] # Whitebox options for compute. Whitebox options enable the # whitebox test cases, which look at internal Nova database state, # SSH into VMs to check instance state, etc. # Should we run whitebox tests for Compute? whitebox_enabled = true # Path of nova source directory source_dir = /opt/stack/nova # Path of nova configuration file config_path = /etc/nova/nova.conf # Directory containing nova binaries such as nova-manage bin_dir = /usr/local/bin # Connection string to the database of Compute service db_uri = mysql://nova:secret@localhost/nova # Path to a private key file for SSH access to remote hosts path_to_private_key = /home/user/.ssh/id_rsa [compute-admin] # This should be the username of a user WITH administrative privileges # If not defined the admin user from the identity section will be used username = # The above administrative user's password password = # The above administrative user's tenant name tenant_name = [image] # This section contains configuration options used when executing tests # against the OpenStack Images API # The type of endpoint for an Image API service. Unless you have a # custom Keystone service catalog implementation, you probably want to leave # this value as "image" catalog_type = image # The version of the OpenStack Images API to use api_version = 1 # HTTP image to use for glance http image testing http_image = http://download.cirros-cloud.net/0.3.1/cirros-0.3.1-x86_64-uec.tar.gz [network] # This section contains configuration options used when executing tests # against the OpenStack Network API. # Version of the Quantum API api_version = v1.1 # Catalog type of the Quantum Service catalog_type = network # A large private cidr block from which to allocate smaller blocks for # tenant networks. tenant_network_cidr = 10.100.0.0/16 # The mask bits used to partition the tenant block. tenant_network_mask_bits = 28 # If tenant networks are reachable, connectivity checks will be # performed directly against addresses on those networks. tenant_networks_reachable = false # Id of the public network that provides external connectivity. public_network_id = {$PUBLIC_NETWORK_ID} # Id of a shared public router that provides external connectivity. # A shared public router would commonly be used where IP namespaces # were disabled. If namespaces are enabled, it would be preferable # for each tenant to have their own router. public_router_id = {$PUBLIC_ROUTER_ID} # Whether or not quantum is expected to be available quantum_available = false [volume] # This section contains the configuration options used when executing tests # against the OpenStack Block Storage API service # The type of endpoint for a Cinder or Block Storage API service. # Unless you have a custom Keystone service catalog implementation, you # probably want to leave this value as "volume" catalog_type = volume # Number of seconds to wait while looping to check the status of a # volume that is being made available build_interval = 10 # Number of seconds to time out on waiting for a volume # to be available or reach an expected status build_timeout = 300 # Runs Cinder multi-backend tests (requires 2 backends declared in cinder.conf) # They must have different volume_backend_name (backend1_name and backend2_name # have to be different) multi_backend_enabled = false backend1_name = BACKEND_1 backend2_name = BACKEND_2 [object-storage] # This section contains configuration options used when executing tests # against the OpenStack Object Storage API. # You can configure the credentials in the compute section # The type of endpoint for an Object Storage API service. Unless you have a # custom Keystone service catalog implementation, you probably want to leave # this value as "object-store" catalog_type = object-store # Number of seconds to time on waiting for a container to container # synchronization complete container_sync_timeout = 120 # Number of seconds to wait while looping to check the status of a # container to container synchronization container_sync_interval = 5 [boto] # This section contains configuration options used when executing tests # with boto. # EC2 URL ec2_url = http://localhost:8773/services/Cloud # S3 URL s3_url = http://localhost:3333 # Use keystone ec2-* command to get those values for your test user and tenant aws_access = aws_secret = #Image materials for S3 upload # ALL content of the specified directory will be uploaded to S3 s3_materials_path = /opt/stack/devstack/files/images/s3-materials/cirros-0.3.1 # The manifest.xml files, must be in the s3_materials_path directory # Subdirectories not allowed! # The filenames will be used as a Keys in the S3 Buckets #ARI Ramdisk manifest. Must be in the above s3_materials_path ari_manifest = cirros-0.3.1-x86_64-initrd.manifest.xml #AMI Machine Image manifest. Must be in the above s3_materials_path ami_manifest = cirros-0.3.1-x86_64-blank.img.manifest.xml #AKI Kernel Image manifest, Must be in the above s3_materials_path aki_manifest = cirros-0.3.1-x86_64-vmlinuz.manifest.xml #Instance type instance_type = m1.tiny #TCP/IP connection timeout http_socket_timeout = 5 #Number of retries actions on connection or 5xx error num_retries = 1 # Status change wait timout build_timeout = 120 # Status change wait interval build_interval = 1 [orchestration] # Status change wait interval build_interval = 1 # Status change wait timout. This may vary across environments as some some # tests spawn full VMs, which could be slow if the test is already in a VM. build_timeout = 300 # Whether or not Heat is expected to be available heat_available = false # Instance type for tests. Needs to be big enough for a # full OS plus the test workload instance_type = m1.micro # Name of heat-cfntools enabled image to use when launching test instances # If not specified, tests that spawn instances will not run #image_ref = ubuntu-vm-heat-cfntools # Name of existing keypair to launch servers with. The default is not to specify # any key, which will generate a keypair for each test class #keypair_name = heat_key [scenario] # Directory containing image files img_dir = /opt/stack/new/devstack/files/images/cirros-0.3.1-x86_64-uec # AMI image file name ami_img_file = cirros-0.3.1-x86_64-blank.img # ARI image file name ari_img_file = cirros-0.3.1-x86_64-initrd # AKI image file name aki_img_file = cirros-0.3.1-x86_64-vmlinuz # ssh username for the image file ssh_user = cirros [CLI] # Enable cli tests enabled = True # directory where python client binaries are located cli_dir = /usr/local/bin/ tempest-2013.2.a1291.g23a1b4f/openstack-common.conf0000664000175000017500000000024012161375672021576 0ustar chuckchuck00000000000000[DEFAULT] # The list of modules to copy from openstack-common modules=install_venv_common # The base module to hold the copy of openstack.common base=tempest tempest-2013.2.a1291.g23a1b4f/tempest.egg-info/0000775000175000017500000000000012161375700020621 5ustar chuckchuck00000000000000tempest-2013.2.a1291.g23a1b4f/tempest.egg-info/dependency_links.txt0000664000175000017500000000000112161375700024667 0ustar chuckchuck00000000000000 tempest-2013.2.a1291.g23a1b4f/tempest.egg-info/requires.txt0000664000175000017500000000051312161375700023220 0ustar chuckchuck00000000000000d2to1>=0.2.10,<0.3 pbr>=0.5,<0.6 anyjson nose httplib2>=0.7.0 testtools>=0.9.32 lxml boto>=2.2.1 paramiko netaddr python-glanceclient>=0.5.0 python-keystoneclient>=0.2.0 python-novaclient>=2.10.0 python-quantumclient>=2.1 python-cinderclient>=1.0.4,<2 testresources keyring testrepository oslo.config>=1.1.0 sqlalchemy MySQL-pythontempest-2013.2.a1291.g23a1b4f/tempest.egg-info/PKG-INFO0000664000175000017500000000742012161375700021721 0ustar chuckchuck00000000000000Metadata-Version: 1.1 Name: tempest Version: 2013.2.a1291.g23a1b4f Summary: OpenStack Integration Testing Home-page: http://www.openstack.org/ Author: OpenStack QA Author-email: openstack-qa@lists.openstack.org License: UNKNOWN Description: :: Tempest - The OpenStack Integration Test Suite ============================================== This is a set of integration tests to be run against a live OpenStack cluster. Tempest has batteries of tests for OpenStack API validation, Scenarios, and other specific tests useful in validating an OpenStack deployment. Quickstart ---------- To run Tempest, you first need to create a configuration file that will tell Tempest where to find the various OpenStack services and other testing behavior switches. The easiest way to create a configuration file is to copy the sample one in the ``etc/`` directory :: $> cd $TEMPEST_ROOT_DIR $> cp etc/tempest.conf.sample etc/tempest.conf After that, open up the ``etc/tempest.conf`` file and edit the configuration variables to match valid data in your environment. This includes your Keystone endpoint, a valid user and credentials, and reference data to be used in testing. .. note:: If you have a running devstack environment, tempest will be automatically configured and placed in ``/opt/stack/tempest``. It will have a configuration file already set up to work with your devstack installation. Tempest is not tied to any single test runner, but Nose been the most commonly used tool. After setting up your configuration file, you can execute the set of Tempest tests by using ``nosetests`` :: $> nosetests tempest To run one single test :: $> nosetests -sv tempest.tests.compute.servers.test_server_actions.py: ServerActionsTestJSON.test_rebuild_nonexistent_server Configuration ------------- Detailed configuration of tempest is beyond the scope of this document. The etc/tempest.conf.sample attempts to be a self documenting version of the configuration. The most important pieces that are needed are the user ids, openstack endpoints, and basic flavors and images needed to run tests. Common Issues ------------- Tempest was originally designed to primarily run against a full OpenStack deployment. Due to that focus, some issues may occur when running Tempest against devstack. Running Tempest, especially in parallel, against a devstack instance may cause requests to be rate limited, which will cause unexpected failures. Given the number of requests Tempest can make against a cluster, rate limiting should be disabled for all test accounts. Additionally, devstack only provides a single image which Nova can use. For the moment, the best solution is to provide the same image uuid for both image_ref and image_ref_alt. Tempest will skip tests as needed if it detects that both images are the same. Platform: UNKNOWN Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 tempest-2013.2.a1291.g23a1b4f/tempest.egg-info/top_level.txt0000664000175000017500000000001012161375700023342 0ustar chuckchuck00000000000000tempest tempest-2013.2.a1291.g23a1b4f/tempest.egg-info/SOURCES.txt0000664000175000017500000002770612161375700022521 0ustar chuckchuck00000000000000.gitignore .gitreview .mailmap .testr.conf HACKING.rst LICENSE README.rst openstack-common.conf requirements.txt run_tests.sh setup.cfg setup.py test-requirements.txt tox.ini bin/tempest doc/source/HACKING.rst doc/source/conf.py doc/source/index.rst doc/source/overview.rst doc/source/field_guide/api.rst doc/source/field_guide/cli.rst doc/source/field_guide/index.rst doc/source/field_guide/scenario.rst doc/source/field_guide/stress.rst doc/source/field_guide/thirdparty.rst doc/source/field_guide/whitebox.rst etc/README.txt etc/TEMPEST_README.txt etc/logging.conf.sample etc/tempest.conf.sample include/sample_vm/README.txt include/swift_objects/README.txt tempest/README.rst tempest/__init__.py tempest/clients.py tempest/config.py tempest/exceptions.py tempest/manager.py tempest/test.py tempest.egg-info/PKG-INFO tempest.egg-info/SOURCES.txt tempest.egg-info/dependency_links.txt tempest.egg-info/requires.txt tempest.egg-info/top_level.txt tempest/api/README.rst tempest/api/__init__.py tempest/api/utils.py tempest/api/compute/__init__.py tempest/api/compute/base.py tempest/api/compute/test_auth_token.py tempest/api/compute/test_authorization.py tempest/api/compute/test_extensions.py tempest/api/compute/test_live_block_migration.py tempest/api/compute/test_quotas.py tempest/api/compute/admin/__init__.py tempest/api/compute/admin/test_aggregates.py tempest/api/compute/admin/test_availability_zone.py tempest/api/compute/admin/test_fixed_ips.py tempest/api/compute/admin/test_flavors.py tempest/api/compute/admin/test_flavors_access.py tempest/api/compute/admin/test_flavors_extra_specs.py tempest/api/compute/admin/test_hypervisor.py tempest/api/compute/admin/test_quotas.py tempest/api/compute/admin/test_services.py tempest/api/compute/admin/test_simple_tenant_usage.py tempest/api/compute/flavors/__init__.py tempest/api/compute/flavors/test_flavors.py tempest/api/compute/floating_ips/__init__.py tempest/api/compute/floating_ips/test_floating_ips_actions.py tempest/api/compute/floating_ips/test_list_floating_ips.py tempest/api/compute/images/__init__.py tempest/api/compute/images/test_image_metadata.py tempest/api/compute/images/test_images.py tempest/api/compute/images/test_images_oneserver.py tempest/api/compute/images/test_list_image_filters.py tempest/api/compute/images/test_list_images.py tempest/api/compute/keypairs/__init__.py tempest/api/compute/keypairs/test_keypairs.py tempest/api/compute/limits/__init__.py tempest/api/compute/limits/test_absolute_limits.py tempest/api/compute/security_groups/__init__.py tempest/api/compute/security_groups/test_security_group_rules.py tempest/api/compute/security_groups/test_security_groups.py tempest/api/compute/servers/__init__.py tempest/api/compute/servers/test_attach_interfaces.py tempest/api/compute/servers/test_create_server.py tempest/api/compute/servers/test_disk_config.py tempest/api/compute/servers/test_instance_actions.py tempest/api/compute/servers/test_list_server_filters.py tempest/api/compute/servers/test_list_servers_negative.py tempest/api/compute/servers/test_multiple_create.py tempest/api/compute/servers/test_server_actions.py tempest/api/compute/servers/test_server_addresses.py tempest/api/compute/servers/test_server_metadata.py tempest/api/compute/servers/test_server_personality.py tempest/api/compute/servers/test_server_rescue.py tempest/api/compute/servers/test_servers.py tempest/api/compute/servers/test_servers_negative.py tempest/api/compute/servers/test_virtual_interfaces.py tempest/api/compute/volumes/__init__.py tempest/api/compute/volumes/test_attach_volume.py tempest/api/compute/volumes/test_volumes_get.py tempest/api/compute/volumes/test_volumes_list.py tempest/api/compute/volumes/test_volumes_negative.py tempest/api/identity/__init__.py tempest/api/identity/base.py tempest/api/identity/admin/__init__.py tempest/api/identity/admin/test_roles.py tempest/api/identity/admin/test_services.py tempest/api/identity/admin/test_tenants.py tempest/api/identity/admin/test_users.py tempest/api/identity/admin/v3/__init__.py tempest/api/identity/admin/v3/test_domains.py tempest/api/identity/admin/v3/test_endpoints.py tempest/api/identity/admin/v3/test_policies.py tempest/api/identity/admin/v3/test_services.py tempest/api/identity/admin/v3/test_users.py tempest/api/image/__init__.py tempest/api/image/base.py tempest/api/image/v1/__init__.py tempest/api/image/v1/test_image_members.py tempest/api/image/v1/test_images.py tempest/api/image/v2/__init__.py tempest/api/image/v2/test_images.py tempest/api/network/__init__.py tempest/api/network/base.py tempest/api/network/common.py tempest/api/network/test_networks.py tempest/api/object_storage/__init__.py tempest/api/object_storage/base.py tempest/api/object_storage/test_account_services.py tempest/api/object_storage/test_container_services.py tempest/api/object_storage/test_container_sync.py tempest/api/object_storage/test_object_expiry.py tempest/api/object_storage/test_object_services.py tempest/api/object_storage/test_object_version.py tempest/api/orchestration/__init__.py tempest/api/orchestration/base.py tempest/api/orchestration/stacks/__init__.py tempest/api/orchestration/stacks/test_instance_cfn_init.py tempest/api/orchestration/stacks/test_stacks.py tempest/api/volume/__init__.py tempest/api/volume/base.py tempest/api/volume/test_volumes_actions.py tempest/api/volume/test_volumes_get.py tempest/api/volume/test_volumes_list.py tempest/api/volume/test_volumes_negative.py tempest/api/volume/test_volumes_snapshots.py tempest/api/volume/admin/__init__.py tempest/api/volume/admin/test_multi_backend.py tempest/api/volume/admin/test_volume_types.py tempest/api/volume/admin/test_volume_types_extra_specs.py tempest/api/volume/admin/test_volume_types_extra_specs_negative.py tempest/api/volume/admin/test_volume_types_negative.py tempest/cli/README.rst tempest/cli/__init__.py tempest/cli/output_parser.py tempest/cli/simple_read_only/README.txt tempest/cli/simple_read_only/__init__.py tempest/cli/simple_read_only/test_compute.py tempest/cli/simple_read_only/test_compute_manage.py tempest/cli/simple_read_only/test_glance.py tempest/cli/simple_read_only/test_keystone.py tempest/common/__init__.py tempest/common/glance_http.py tempest/common/log.py tempest/common/rest_client.py tempest/common/ssh.py tempest/common/utils/__init__.py tempest/common/utils/data_utils.py tempest/common/utils/file_utils.py tempest/common/utils/misc.py tempest/common/utils/linux/__init__.py tempest/common/utils/linux/remote_client.py tempest/hacking/__init__.py tempest/hacking/checks.py tempest/openstack/__init__.py tempest/scenario/README.rst tempest/scenario/__init__.py tempest/scenario/manager.py tempest/scenario/test_minimum_basic.py tempest/scenario/test_network_basic_ops.py tempest/scenario/test_network_quotas.py tempest/scenario/test_server_advanced_ops.py tempest/scenario/test_server_basic_ops.py tempest/services/__init__.py tempest/services/botoclients.py tempest/services/compute/__init__.py tempest/services/compute/json/__init__.py tempest/services/compute/json/aggregates_client.py tempest/services/compute/json/availability_zone_client.py tempest/services/compute/json/extensions_client.py tempest/services/compute/json/fixed_ips_client.py tempest/services/compute/json/flavors_client.py tempest/services/compute/json/floating_ips_client.py tempest/services/compute/json/hosts_client.py tempest/services/compute/json/hypervisor_client.py tempest/services/compute/json/images_client.py tempest/services/compute/json/interfaces_client.py tempest/services/compute/json/keypairs_client.py tempest/services/compute/json/limits_client.py tempest/services/compute/json/quotas_client.py tempest/services/compute/json/security_groups_client.py tempest/services/compute/json/servers_client.py tempest/services/compute/json/services_client.py tempest/services/compute/json/tenant_usages_client.py tempest/services/compute/json/volumes_extensions_client.py tempest/services/compute/xml/__init__.py tempest/services/compute/xml/aggregates_client.py tempest/services/compute/xml/availability_zone_client.py tempest/services/compute/xml/common.py tempest/services/compute/xml/extensions_client.py tempest/services/compute/xml/fixed_ips_client.py tempest/services/compute/xml/flavors_client.py tempest/services/compute/xml/floating_ips_client.py tempest/services/compute/xml/hypervisor_client.py tempest/services/compute/xml/images_client.py tempest/services/compute/xml/interfaces_client.py tempest/services/compute/xml/keypairs_client.py tempest/services/compute/xml/limits_client.py tempest/services/compute/xml/quotas_client.py tempest/services/compute/xml/security_groups_client.py tempest/services/compute/xml/servers_client.py tempest/services/compute/xml/services_client.py tempest/services/compute/xml/tenant_usages_client.py tempest/services/compute/xml/volumes_extensions_client.py tempest/services/identity/__init__.py tempest/services/identity/json/__init__.py tempest/services/identity/json/identity_client.py tempest/services/identity/v3/__init__.py tempest/services/identity/v3/json/__init__.py tempest/services/identity/v3/json/endpoints_client.py tempest/services/identity/v3/json/identity_client.py tempest/services/identity/v3/json/policy_client.py tempest/services/identity/v3/json/service_client.py tempest/services/identity/v3/xml/__init__.py tempest/services/identity/v3/xml/endpoints_client.py tempest/services/identity/v3/xml/identity_client.py tempest/services/identity/v3/xml/policy_client.py tempest/services/identity/v3/xml/service_client.py tempest/services/identity/xml/__init__.py tempest/services/identity/xml/identity_client.py tempest/services/image/__init__.py tempest/services/image/v1/__init__.py tempest/services/image/v1/json/__init__.py tempest/services/image/v1/json/image_client.py tempest/services/image/v2/__init__.py tempest/services/image/v2/json/__init__.py tempest/services/image/v2/json/image_client.py tempest/services/network/__init__.py tempest/services/network/json/__init__.py tempest/services/network/json/network_client.py tempest/services/object_storage/__init__.py tempest/services/object_storage/account_client.py tempest/services/object_storage/container_client.py tempest/services/object_storage/object_client.py tempest/services/orchestration/__init__.py tempest/services/orchestration/json/__init__.py tempest/services/orchestration/json/orchestration_client.py tempest/services/volume/__init__.py tempest/services/volume/json/__init__.py tempest/services/volume/json/snapshots_client.py tempest/services/volume/json/volumes_client.py tempest/services/volume/json/admin/__init__.py tempest/services/volume/json/admin/volume_types_client.py tempest/services/volume/xml/__init__.py tempest/services/volume/xml/snapshots_client.py tempest/services/volume/xml/volumes_client.py tempest/services/volume/xml/admin/__init__.py tempest/services/volume/xml/admin/volume_types_client.py tempest/stress/README.rst tempest/stress/__init__.py tempest/stress/cleanup.py tempest/stress/driver.py tempest/stress/run_stress.py tempest/stress/actions/__init__.py tempest/stress/actions/create_destroy_server.py tempest/stress/actions/volume_create_delete.py tempest/stress/etc/sample-test.json tempest/stress/etc/volume-create-delete-test.json tempest/stress/tools/cleanup.py tempest/thirdparty/README.rst tempest/thirdparty/__init__.py tempest/thirdparty/boto/__init__.py tempest/thirdparty/boto/test.py tempest/thirdparty/boto/test_ec2_instance_run.py tempest/thirdparty/boto/test_ec2_keys.py tempest/thirdparty/boto/test_ec2_network.py tempest/thirdparty/boto/test_ec2_security_groups.py tempest/thirdparty/boto/test_ec2_volumes.py tempest/thirdparty/boto/test_s3_buckets.py tempest/thirdparty/boto/test_s3_ec2_images.py tempest/thirdparty/boto/test_s3_objects.py tempest/thirdparty/boto/utils/__init__.py tempest/thirdparty/boto/utils/s3.py tempest/thirdparty/boto/utils/wait.py tempest/whitebox/README.rst tempest/whitebox/__init__.py tempest/whitebox/manager.py tempest/whitebox/test_images_whitebox.py tempest/whitebox/test_servers_whitebox.py tools/find_stack_traces.py tools/install_venv.py tools/install_venv_common.py tools/skip_tracker.py tools/tempest_coverage.py tools/with_venv.shtempest-2013.2.a1291.g23a1b4f/README.rst0000664000175000017500000000506512161375672017153 0ustar chuckchuck00000000000000:: Tempest - The OpenStack Integration Test Suite ============================================== This is a set of integration tests to be run against a live OpenStack cluster. Tempest has batteries of tests for OpenStack API validation, Scenarios, and other specific tests useful in validating an OpenStack deployment. Quickstart ---------- To run Tempest, you first need to create a configuration file that will tell Tempest where to find the various OpenStack services and other testing behavior switches. The easiest way to create a configuration file is to copy the sample one in the ``etc/`` directory :: $> cd $TEMPEST_ROOT_DIR $> cp etc/tempest.conf.sample etc/tempest.conf After that, open up the ``etc/tempest.conf`` file and edit the configuration variables to match valid data in your environment. This includes your Keystone endpoint, a valid user and credentials, and reference data to be used in testing. .. note:: If you have a running devstack environment, tempest will be automatically configured and placed in ``/opt/stack/tempest``. It will have a configuration file already set up to work with your devstack installation. Tempest is not tied to any single test runner, but Nose been the most commonly used tool. After setting up your configuration file, you can execute the set of Tempest tests by using ``nosetests`` :: $> nosetests tempest To run one single test :: $> nosetests -sv tempest.tests.compute.servers.test_server_actions.py: ServerActionsTestJSON.test_rebuild_nonexistent_server Configuration ------------- Detailed configuration of tempest is beyond the scope of this document. The etc/tempest.conf.sample attempts to be a self documenting version of the configuration. The most important pieces that are needed are the user ids, openstack endpoints, and basic flavors and images needed to run tests. Common Issues ------------- Tempest was originally designed to primarily run against a full OpenStack deployment. Due to that focus, some issues may occur when running Tempest against devstack. Running Tempest, especially in parallel, against a devstack instance may cause requests to be rate limited, which will cause unexpected failures. Given the number of requests Tempest can make against a cluster, rate limiting should be disabled for all test accounts. Additionally, devstack only provides a single image which Nova can use. For the moment, the best solution is to provide the same image uuid for both image_ref and image_ref_alt. Tempest will skip tests as needed if it detects that both images are the same. tempest-2013.2.a1291.g23a1b4f/.gitreview0000664000175000017500000000011412161375672017460 0ustar chuckchuck00000000000000[gerrit] host=review.openstack.org port=29418 project=openstack/tempest.git tempest-2013.2.a1291.g23a1b4f/setup.cfg0000664000175000017500000000143612161375700017273 0ustar chuckchuck00000000000000[metadata] name = tempest version = 2013.2 summary = OpenStack Integration Testing description-file = README.rst author = OpenStack QA author-email = openstack-qa@lists.openstack.org home-page = http://www.openstack.org/ classifier = Intended Audience :: Information Technology Intended Audience :: System Administrators Intended Audience :: Developers License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 [global] setup-hooks = pbr.hooks.setup_hook [files] scripts = bin/tempest [build_sphinx] all_files = 1 build-dir = doc/build source-dir = doc/source [nosetests] verbosity = 2 [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 tempest-2013.2.a1291.g23a1b4f/requirements.txt0000664000175000017500000000055212161375672020744 0ustar chuckchuck00000000000000d2to1>=0.2.10,<0.3 pbr>=0.5,<0.6 anyjson nose httplib2>=0.7.0 testtools>=0.9.32 lxml boto>=2.2.1 paramiko netaddr python-glanceclient>=0.5.0 python-keystoneclient>=0.2.0 python-novaclient>=2.10.0 python-quantumclient>=2.1 python-cinderclient>=1.0.4,<2 testresources keyring testrepository oslo.config>=1.1.0 # Needed for whitebox testing sqlalchemy MySQL-python tempest-2013.2.a1291.g23a1b4f/LICENSE0000664000175000017500000002363712161375672016476 0ustar chuckchuck00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. tempest-2013.2.a1291.g23a1b4f/.testr.conf0000664000175000017500000000023712161375672017546 0ustar chuckchuck00000000000000[DEFAULT] test_command=${PYTHON:-python} -m subunit.run discover -t ./ ./tempest $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list tempest-2013.2.a1291.g23a1b4f/tox.ini0000664000175000017500000000465012161375672016776 0ustar chuckchuck00000000000000[tox] envlist = pep8 [testenv] setenv = VIRTUAL_ENV={envdir} NOSE_WITH_OPENSTACK=1 NOSE_OPENSTACK_COLOR=1 NOSE_OPENSTACK_RED=15 NOSE_OPENSTACK_YELLOW=3 NOSE_OPENSTACK_SHOW_ELAPSED=1 NOSE_OPENSTACK_STDOUT=1 [testenv:all] sitepackages = True setenv = VIRTUAL_ENV={envdir} NOSE_WITH_OPENSTACK=1 NOSE_OPENSTACK_COLOR=1 NOSE_OPENSTACK_RED=15 NOSE_OPENSTACK_YELLOW=3 NOSE_OPENSTACK_SHOW_ELAPSED=1 NOSE_OPENSTACK_STDOUT=1 commands = nosetests --logging-format '%(asctime)-15s %(message)s' --with-xunit --xunit-file=nosetests-all.xml -sv tempest [testenv:full] sitepackages = True setenv = VIRTUAL_ENV={envdir} NOSE_WITH_OPENSTACK=1 NOSE_OPENSTACK_COLOR=1 NOSE_OPENSTACK_RED=15 NOSE_OPENSTACK_YELLOW=3 NOSE_OPENSTACK_SHOW_ELAPSED=1 NOSE_OPENSTACK_STDOUT=1 commands = nosetests --logging-format '%(asctime)-15s %(message)s' --with-xunit --xunit-file=nosetests-full.xml -sv tempest/api tempest/scenario tempest/thirdparty tempest/cli [testenv:smoke] sitepackages = True setenv = VIRTUAL_ENV={envdir} NOSE_WITH_OPENSTACK=1 NOSE_OPENSTACK_COLOR=1 NOSE_OPENSTACK_RED=15 NOSE_OPENSTACK_YELLOW=3 NOSE_OPENSTACK_SHOW_ELAPSED=1 NOSE_OPENSTACK_STDOUT=1 commands = nosetests --logging-format '%(asctime)-15s %(message)s' --with-xunit -sv --attr=type=smoke --xunit-file=nosetests-smoke.xml tempest [testenv:coverage] sitepackages = True setenv = VIRTUAL_ENV={envdir} NOSE_WITH_OPENSTACK=1 NOSE_OPENSTACK_COLOR=1 NOSE_OPENSTACK_RED=15 NOSE_OPENSTACK_YELLOW=3 NOSE_OPENSTACK_SHOW_ELAPSED=1 NOSE_OPENSTACK_STDOUT=1 commands = python -m tools/tempest_coverage -c start --combine nosetests --logging-format '%(asctime)-15s %(message)s' --with-xunit --xunit-file=nosetests-full.xml -sv tempest/api tempest/scenario tempest/thirdparty tempest/cli python -m tools/tempest_coverage -c report --html {posargs} [testenv:venv] commands = {posargs} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt [testenv:pep8] commands = flake8 deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt [hacking] local-check-factory = tempest.hacking.checks.factory [flake8] ignore = E125,H302,H404 show-source = True exclude = .git,.venv,.tox,dist,doc,openstack,*egg