Adding tests to Reddwarf.

The tests come from the Reddwarf Integration repository. wsgi_intercept
is used to allow the test code to hit the server code directly. It also
properly sets up the SqlLite database before each run.

* Adds an "event simulator" which queues up functions that would
  normally be spawned with eventlet. The various sleep functions are
  then swapped with a method that runs these faux-events.

* Adds many of the Reddwarf Integration tests. The idea is these
  could still run in a richer, real environment, but are running here
  enables us to quickly check feature breaks for each commit and
  lowers the learning curve necessary to test the API. The downside
  is some of these tests still have artifacts of their origins, such
  as (unused) classes to connect to MySQL databases. Some more work
  will be necessary to separate the "real mode" functionality of these
  tests further.

Implements: blueprint tox-tests
Change-Id: I9857f265c1cb46832906ef5e6a0c7bb4a092e637
This commit is contained in:
Tim Simpson 2012-11-16 11:50:34 -06:00
parent a070023752
commit c007356a78
37 changed files with 4818 additions and 509 deletions

26
.coveragerc Normal file
View File

@ -0,0 +1,26 @@
# .coveragerc to control coverage.py
[run]
branch = True
timid=True
include=*reddwarf*
[report]
# Regexes for lines to exclude from consideration
exclude_lines =
# Have to re-enable the standard pragma
pragma: no cover
# Don't complain about missing debug-only code:
def __repr__
if self\.debug
# Don't complain if tests don't hit defensive assertion code:
raise AssertionError
raise NotImplementedError
# Don't complain if non-runnable code isn't run:
if 0:
if __name__ == .__main__.:
ignore_errors = False

1
.gitignore vendored
View File

@ -14,3 +14,4 @@ covhtml/
host-syslog.log
tags
.tox
rdtest.log

View File

@ -59,7 +59,7 @@ class Commands(object):
def __init__(self, conf):
self.conf = conf
def db_sync(self, repo_path=None):
def db_sync(self):
db_api.db_sync(self.conf, repo_path=None)
def db_upgrade(self, version=None, repo_path=None):
@ -89,7 +89,7 @@ class Commands(object):
from reddwarf.instance import models
from reddwarf.db.sqlalchemy import session
db_api.drop_db(self.conf)
self.db_sync(repo_path)
self.db_sync()
# Sets up database engine, so the next line will work...
session.configure_db(self.conf)
models.ServiceImage.create(service_name=service_name,

View File

@ -1,6 +1,10 @@
#!/usr/bin/env bash
# Arguments: Use --pid_file to specify a pid file location.
tox -e py26
if [ ! -d ".tox/py26" ]; then
tox -epy26
fi
function run() {
.tox/py26/bin/python $@

View File

@ -2,6 +2,8 @@
remote_implementation = fake
log_file = rdtest.log
# Show more verbose log output (sets INFO log level output)
verbose = True
@ -34,7 +36,7 @@ sql_connection = sqlite:///reddwarf_test.sqlite
sql_idle_timeout = 3600
#DB Api Implementation
db_api_implementation = "reddwarf.db.sqlalchemy.api"
db_api_implementation = reddwarf.db.sqlalchemy.api
# Path to the extensions
api_extensions_path = reddwarf/extensions

38
etc/tests/core.test.conf Normal file
View File

@ -0,0 +1,38 @@
{
"report_directory":"rdli-test-report",
"start_services": false,
"test_mgmt":false,
"use_local_ovz":false,
"use_venv":false,
"glance_code_root":"/opt/stack/glance",
"glance_api_conf":"/vagrant/conf/glance-api.conf",
"glance_reg_conf":"/vagrant/conf/glance-reg.conf",
"glance_images_directory": "/glance_images",
"glance_image": "fakey_fakerson.tar.gz",
"instance_flavor_name":"m1.tiny",
"instance_bigger_flavor_name":"m1.rd-smaller",
"nova_code_root":"/opt/stack/nova",
"nova_conf":"/home/vagrant/nova.conf",
"keystone_code_root":"/opt/stack/keystone",
"keystone_conf":"/etc/keystone/keystone.conf",
"keystone_use_combined":true,
"reddwarf_code_root":"/opt/stack/reddwarf_lite",
"reddwarf_conf":"/tmp/reddwarf.conf",
"reddwarf_version":"v1.0",
"reddwarf_api_updated":"2012-08-01T00:00:00Z",
"reddwarf_must_have_volume":false,
"reddwarf_can_have_volume":true,
"reddwarf_main_instance_has_volume": true,
"reddwarf_max_accepted_volume_size": 1000,
"reddwarf_max_instances_per_user": 55,
"use_reaper":false,
"root_removed_from_instance_api": true,
"root_timestamp_disabled": false,
"openvz_disabled": false,
"management_api_disabled": true,
"dns_instance_entry_factory":"reddwarf.dns.rsdns.driver.RsDnsInstanceEntryFactory",
"sentinel": null
}

View File

@ -0,0 +1,95 @@
{
"include-files":["core.test.conf"],
"fake_mode": true,
"dbaas_url":"http://localhost:8779/v1.0",
"version_url":"http://localhost:8779",
"nova_auth_url":"http://localhost:8779/v1.0/auth",
"reddwarf_auth_url":"http://localhost:8779/v1.0/auth",
"reddwarf_client_insecure":false,
"auth_strategy":"fake",
"reddwarf_version":"v1.0",
"reddwarf_api_updated":"2012-08-01T00:00:00Z",
"reddwarf_dns_support":false,
"reddwarf_ip_support":false,
"nova_client": null,
"users": [
{
"auth_user":"admin",
"auth_key":"password",
"tenant":"admin-1000",
"requirements": {
"is_admin":true,
"services": ["reddwarf"]
}
},
{
"auth_user":"jsmith",
"auth_key":"password",
"tenant":"2500",
"requirements": {
"is_admin":false,
"services": ["reddwarf"]
}
},
{
"auth_user":"hub_cap",
"auth_key":"password",
"tenant":"3000",
"requirements": {
"is_admin":false,
"services": ["reddwarf"]
}
}
],
"flavors": [
{
"id": 1,
"name": "m1.tiny",
"ram": 512
},
{
"id": 2,
"name": "m1.small",
"ram": 2048
},
{
"id": 3,
"name": "m1.medium",
"ram": 4096
},
{
"id": 4,
"name": "m1.large",
"ram": 8192
},
{
"id": 5,
"name": "m1.xlarge",
"ram": 16384
},
{
"id": 6,
"name": "tinier",
"ram": 506
},
{
"id": 7,
"name": "m1.rd-tiny",
"ram": 512
},
{
"id": 8,
"name": "m1.rd-smaller",
"ram": 768
}
],
"sentinel": null
}

View File

@ -35,7 +35,7 @@ class API(ManagerAPI):
"""API for interacting with the task manager."""
def _fake_cast(self, method_name, **kwargs):
import eventlet
from reddwarf.tests.fakes.common import get_event_spawer
from reddwarf.taskmanager.manager import TaskManager
instance = TaskManager()
method = getattr(instance, method_name)
@ -49,7 +49,7 @@ class API(ManagerAPI):
logging.error((traceback.format_exception(type_, value, tb)))
raise type_, value, tb
eventlet.spawn_after(0, func)
get_event_spawer()(0, func)
def _get_routing_key(self):
"""Create the routing key for the taskmanager"""

View File

@ -1,121 +1,4 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 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.
# See http://code.google.com/p/python-nose/issues/detail?id=373
# The code below enables nosetests to work with i18n _() blocks
import __builtin__
setattr(__builtin__, '_', lambda x: x)
import os
import unittest
import urlparse
import mox
from reddwarf.db import db_api
from reddwarf.common import config
from reddwarf.common import utils
def reddwarf_root_path():
return os.path.join(os.path.dirname(__file__), "..", "..")
def reddwarf_bin_path(filename="."):
return os.path.join(reddwarf_root_path(), "bin", filename)
def reddwarf_etc_path(filename="."):
return os.path.join(reddwarf_root_path(), "etc", "reddwarf", filename)
def test_config_file():
return reddwarf_etc_path("reddwarf.conf.sample")
class BaseTest(unittest.TestCase):
def setUp(self):
#maxDiff=None ensures diff output of assert methods are not truncated
self.maxDiff = None
self.mock = mox.Mox()
conf, reddwarf_app = config.Config.load_paste_app(
'reddwarfapp',
{"config_file": test_config_file()},
None)
db_api.configure_db(conf)
db_api.clean_db()
super(BaseTest, self).setUp()
def tearDown(self):
self.mock.UnsetStubs()
self.mock.VerifyAll()
super(BaseTest, self).tearDown()
def assertRaisesExcMessage(self, exception, message,
func, *args, **kwargs):
"""This is similar to assertRaisesRegexp in python 2.7"""
try:
func(*args, **kwargs)
self.fail("Expected %r to raise %r" % (func, exception))
except exception as error:
self.assertIn(message, str(error))
def assertIn(self, expected, actual):
"""This is similar to assertIn in python 2.7"""
self.assertTrue(expected in actual,
"%r does not contain %r" % (actual, expected))
def assertNotIn(self, expected, actual):
self.assertFalse(expected in actual,
"%r does not contain %r" % (actual, expected))
def assertIsNone(self, actual):
"""This is similar to assertIsNone in python 2.7"""
self.assertEqual(actual, None)
def assertIsNotNone(self, actual):
"""This is similar to assertIsNotNone in python 2.7"""
self.assertNotEqual(actual, None)
def assertItemsEqual(self, expected, actual):
self.assertEqual(sorted(expected), sorted(actual))
def assertModelsEqual(self, expected, actual):
self.assertEqual(
sorted(expected, key=lambda model: model.id),
sorted(actual, key=lambda model: model.id))
def assertUrlEqual(self, expected, actual):
self.assertEqual(expected.partition("?")[0], actual.partition("?")[0])
#params ordering might be different in the urls
self.assertEqual(
urlparse.parse_qs(expected.partition("?")[2]),
urlparse.parse_qs(actual.partition("?")[2]))
def assertErrorResponse(self, response, error_type, expected_error):
self.assertEqual(response.status_int, error_type().code)
self.assertIn(expected_error, response.body)
def setup_uuid_with(self, fake_uuid):
self.mock.StubOutWithMock(utils, "generate_uuid")
utils.generate_uuid().MultipleTimes().AndReturn(fake_uuid)
DBAAS_API = "dbaas.api"
PRE_INSTANCES = "dbaas.api.pre_instances"
INSTANCES = "dbaas.api.instances"
POST_INSTANCES = "dbaas.api.post_instances"

View File

@ -0,0 +1,13 @@
# Copyright 2011 OpenStack LLC
#
# 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.

View File

@ -0,0 +1,199 @@
# Copyright 2011 OpenStack LLC
#
# 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 reddwarfclient import exceptions
from proboscis import before_class
from proboscis import test
from proboscis.asserts import assert_equal
from proboscis.asserts import assert_false
from proboscis.asserts import assert_not_equal
from proboscis.asserts import assert_raises
from proboscis.asserts import assert_true
from proboscis.asserts import fail
from proboscis.decorators import expect_exception
from proboscis.decorators import time_out
from reddwarf import tests
from reddwarf.tests import util
from reddwarf.tests.api.instances import GROUP_START
from reddwarf.tests.api.instances import instance_info
from reddwarf.tests.util import test_config
GROUP = "dbaas.api.databases"
FAKE = test_config.values['fake_mode']
@test(depends_on_groups=[GROUP_START],
groups=[tests.DBAAS_API, GROUP, tests.INSTANCES])
class TestDatabases(object):
"""
Test the creation and deletion of additional MySQL databases
"""
dbname = "third #?@some_-"
dbname_urlencoded = "third%20%23%3F%40some_-"
dbname2 = "seconddb"
created_dbs = [dbname, dbname2]
system_dbs = ['information_schema', 'mysql', 'lost+found']
@before_class
def setUp(self):
self.dbaas = util.create_dbaas_client(instance_info.user)
self.dbaas_admin = util.create_dbaas_client(instance_info.admin_user)
@test
def test_cannot_create_taboo_database_names(self):
for name in self.system_dbs:
databases = [{"name": name, "charset": "latin2",
"collate": "latin2_general_ci"}]
assert_raises(exceptions.BadRequest, self.dbaas.databases.create,
instance_info.id, databases)
assert_equal(400, self.dbaas.last_http_code)
@test
def test_create_database(self):
databases = []
databases.append({"name": self.dbname, "charset": "latin2",
"collate": "latin2_general_ci"})
databases.append({"name": self.dbname2})
self.dbaas.databases.create(instance_info.id, databases)
assert_equal(202, self.dbaas.last_http_code)
if not FAKE:
time.sleep(5)
@test(depends_on=[test_create_database])
def test_create_database_list(self):
databases = self.dbaas.databases.list(instance_info.id)
assert_equal(200, self.dbaas.last_http_code)
found = False
for db in self.created_dbs:
for result in databases:
if result.name == db:
found = True
assert_true(found, "Database '%s' not found in result" % db)
found = False
@test(depends_on=[test_create_database])
def test_fails_when_creating_a_db_twice(self):
databases = []
databases.append({"name": self.dbname, "charset": "latin2",
"collate": "latin2_general_ci"})
databases.append({"name": self.dbname2})
assert_raises(exceptions.BadRequest, self.dbaas.databases.create,
instance_info.id, databases)
assert_equal(400, self.dbaas.last_http_code)
@test
def test_create_database_list_system(self):
#Databases that should not be returned in the list
databases = self.dbaas.databases.list(instance_info.id)
assert_equal(200, self.dbaas.last_http_code)
found = False
for db in self.system_dbs:
found = any(result.name == db for result in databases)
msg = "Database '%s' SHOULD NOT be found in result" % db
assert_false(found, msg)
found = False
@test
def test_create_database_on_missing_instance(self):
databases = [{"name": "invalid_db", "charset": "latin2",
"collate": "latin2_general_ci"}]
assert_raises(exceptions.NotFound, self.dbaas.databases.create,
-1, databases)
assert_equal(404, self.dbaas.last_http_code)
@test(runs_after=[test_create_database])
def test_delete_database(self):
self.dbaas.databases.delete(instance_info.id, self.dbname_urlencoded)
assert_equal(202, self.dbaas.last_http_code)
if not FAKE:
time.sleep(5)
dbs = self.dbaas.databases.list(instance_info.id)
assert_equal(200, self.dbaas.last_http_code)
found = any(result.name == self.dbname_urlencoded for result in dbs)
assert_false(found, "Database '%s' SHOULD NOT be found in result" %
self.dbname_urlencoded)
@test(runs_after=[test_delete_database])
def test_cannot_delete_taboo_database_names(self):
for name in self.system_dbs:
assert_raises(exceptions.BadRequest, self.dbaas.databases.delete,
instance_info.id, name)
assert_equal(400, self.dbaas.last_http_code)
@test(runs_after=[test_delete_database])
def test_delete_database_on_missing_instance(self):
assert_raises(exceptions.NotFound, self.dbaas.databases.delete,
-1, self.dbname_urlencoded)
assert_equal(404, self.dbaas.last_http_code)
@test
def test_database_name_too_long(self):
databases = []
name = ("aasdlkhaglkjhakjdkjgfakjgadgfkajsg"
"34523dfkljgasldkjfglkjadsgflkjagsdd")
databases.append({"name": name})
assert_raises(exceptions.BadRequest, self.dbaas.databases.create,
instance_info.id, databases)
assert_equal(400, self.dbaas.last_http_code)
@test
def test_invalid_database_name(self):
databases = []
databases.append({"name": "sdfsd,"})
assert_raises(exceptions.BadRequest, self.dbaas.databases.create,
instance_info.id, databases)
assert_equal(400, self.dbaas.last_http_code)
@test
def test_pagination(self):
databases = []
databases.append({"name": "Sprockets", "charset": "latin2",
"collate": "latin2_general_ci"})
databases.append({"name": "Cogs"})
databases.append({"name": "Widgets"})
self.dbaas.databases.create(instance_info.id, databases)
assert_equal(202, self.dbaas.last_http_code)
if not FAKE:
time.sleep(5)
limit = 2
databases = self.dbaas.databases.list(instance_info.id, limit=limit)
assert_equal(200, self.dbaas.last_http_code)
marker = databases.next
# Better get only as many as we asked for
assert_true(len(databases) <= limit)
assert_true(databases.next is not None)
assert_equal(marker, databases[-1].name)
marker = databases.next
# I better get new databases if I use the marker I was handed.
databases = self.dbaas.databases.list(instance_info.id, limit=limit,
marker=marker)
assert_equal(200, self.dbaas.last_http_code)
assert_true(marker not in [database.name for database in databases])
# Now fetch again with a larger limit.
databases = self.dbaas.databases.list(instance_info.id)
assert_equal(200, self.dbaas.last_http_code)
assert_true(databases.next is None)

View File

@ -0,0 +1,165 @@
# Copyright (c) 2011 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
from nose.tools import assert_equal
from nose.tools import assert_false
from nose.tools import assert_true
from reddwarfclient import exceptions
from reddwarfclient.flavors import Flavor
from reddwarfclient.flavors import Flavors
from proboscis import before_class
from proboscis import test
from proboscis.asserts import assert_raises
from proboscis import SkipTest
from reddwarf import tests
from reddwarf.tests.util import create_dbaas_client
from reddwarf.tests.util import create_nova_client
from reddwarf.tests.util import test_config
from reddwarf.tests.util.users import Requirements
from reddwarf.tests.util.check import AttrCheck
GROUP = "dbaas.api.flavors"
servers_flavors = None
dbaas_flavors = None
user = None
def assert_attributes_equal(name, os_flavor, dbaas_flavor):
"""Given an attribute name and two objects,
ensures the attribute is equal."""
assert_true(hasattr(os_flavor, name),
"open stack flavor did not have attribute %s" % name)
assert_true(hasattr(dbaas_flavor, name),
"dbaas flavor did not have attribute %s" % name)
expected = getattr(os_flavor, name)
actual = getattr(dbaas_flavor, name)
assert_equal(expected, actual,
'DBaas flavor differs from Open Stack on attribute ' + name)
def assert_flavors_roughly_equivalent(os_flavor, dbaas_flavor):
assert_attributes_equal('name', os_flavor, dbaas_flavor)
assert_attributes_equal('ram', os_flavor, dbaas_flavor)
assert_false(hasattr(dbaas_flavor, 'disk'),
"The attribute 'disk' s/b absent from the dbaas API.")
def assert_link_list_is_equal(flavor):
assert_true(hasattr(flavor, 'links'))
assert_true(flavor.links)
for link in flavor.links:
href = link['href']
if "self" in link['rel']:
expected_href = os.path.join(test_config.dbaas_url, "flavors",
str(flavor.id))
url = test_config.dbaas_url.replace('http:', 'https:', 1)
msg = ("REL HREF %s doesn't start with %s" %
(href, test_config.dbaas_url))
assert_true(href.startswith(url), msg)
url = os.path.join("flavors", str(flavor.id))
msg = "REL HREF %s doesn't end in 'flavors/id'" % href
assert_true(href.endswith(url), msg)
elif "bookmark" in link['rel']:
base_url = test_config.version_url.replace('http:', 'https:', 1)
expected_href = os.path.join(base_url, "flavors", str(flavor.id))
msg = 'bookmark "href" must be %s, not %s' % (expected_href, href)
assert_equal(href, expected_href, msg)
else:
assert_false(True, "Unexpected rel - %s" % link['rel'])
@test(groups=[tests.DBAAS_API, GROUP, tests.PRE_INSTANCES],
depends_on_groups=["services.initialize"])
class Flavors(object):
@before_class
def setUp(self):
rd_user = test_config.users.find_user(
Requirements(is_admin=False, services=["reddwarf"]))
self.rd_client = create_dbaas_client(rd_user)
if test_config.nova_client is not None:
nova_user = test_config.users.find_user(
Requirements(is_admin=False, services=["nova"]))
self.nova_client = create_nova_client(nova_user)
def get_expected_flavors(self):
# If we have access to the client, great! Let's use that as the flavors
# returned by Reddwarf should be identical.
if test_config.nova_client is not None:
return self.nova_client.flavors.list()
# If we don't have access to the client the flavors need to be spelled
# out in the config file.
flavors = [Flavor(Flavors, flavor_dict, loaded=True)
for flavor_dict in test_config.flavors]
return flavors
@test
def confirm_flavors_lists_nearly_identical(self):
os_flavors = self.get_expected_flavors()
dbaas_flavors = self.rd_client.flavors.list()
print("Open Stack Flavors:")
print(os_flavors)
print("DBaaS Flavors:")
print(dbaas_flavors)
#Length of both flavors list should be identical.
assert_equal(len(os_flavors), len(dbaas_flavors))
for os_flavor in os_flavors:
found_index = None
for index, dbaas_flavor in enumerate(dbaas_flavors):
if os_flavor.name == dbaas_flavor.name:
msg = ("Flavor ID '%s' appears in elements #%s and #%d." %
(dbaas_flavor.id, str(found_index), index))
assert_true(found_index is None, msg)
assert_flavors_roughly_equivalent(os_flavor, dbaas_flavor)
found_index = index
msg = "Some flavors from OS list were missing in DBAAS list."
assert_false(found_index is None, msg)
for flavor in dbaas_flavors:
assert_link_list_is_equal(flavor)
@test
def test_flavor_list_attrs(self):
expected_attrs = ['id', 'name', 'ram', 'links']
flavors = self.rd_client.flavors.list()
attrcheck = AttrCheck()
for flavor in flavors:
flavor_dict = flavor._info
attrcheck.attrs_exist(flavor_dict, expected_attrs,
msg="Flavors list")
attrcheck.links(flavor_dict['links'])
@test
def test_flavor_get_attrs(self):
expected_attrs = ['id', 'name', 'ram', 'links']
flavor = self.rd_client.flavors.get(1)
attrcheck = AttrCheck()
flavor_dict = flavor._info
attrcheck.attrs_exist(flavor_dict, expected_attrs,
msg="Flavor Get 1")
attrcheck.links(flavor_dict['links'])
@test
def test_flavor_not_found(self):
assert_raises(exceptions.NotFound,
self.rd_client.flavors.get, "detail")

View File

@ -0,0 +1,928 @@
# Copyright 2011 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 os
import re
import string
import time
import unittest
from reddwarf.tests import util
import urlparse
GROUP = "dbaas.guest"
GROUP_START = "dbaas.guest.initialize"
GROUP_TEST = "dbaas.guest.test"
GROUP_STOP = "dbaas.guest.shutdown"
GROUP_USERS = "dbaas.api.users"
GROUP_ROOT = "dbaas.api.root"
GROUP_DATABASES = "dbaas.api.databases"
from datetime import datetime
from nose.plugins.skip import SkipTest
from nose.tools import assert_true
from reddwarfclient import exceptions
from proboscis.decorators import time_out
from proboscis import before_class
from proboscis import test
from proboscis.asserts import assert_equal
from proboscis.asserts import assert_false
from proboscis.asserts import assert_not_equal
from proboscis.asserts import assert_raises
from proboscis.asserts import assert_is
from proboscis.asserts import assert_is_none
from proboscis.asserts import assert_is_not
from proboscis.asserts import assert_true
from proboscis.asserts import Check
from proboscis.asserts import fail
from reddwarf import tests
from reddwarf.tests.config import CONFIG
from reddwarf.tests.util import create_client
from reddwarf.tests.util import create_dbaas_client
from reddwarf.tests.util import create_nova_client
from reddwarf.tests.util import process
from reddwarf.tests.util.users import Requirements
from reddwarf.tests.util import string_in_list
from reddwarf.tests.util import poll_until
from reddwarf.tests.util.check import AttrCheck
class InstanceTestInfo(object):
"""Stores new instance information used by dependent tests."""
def __init__(self):
self.dbaas = None # The rich client instance used by these tests.
self.dbaas_admin = None # The rich client with admin access.
self.dbaas_flavor = None # The flavor object of the instance.
self.dbaas_flavor_href = None # The flavor of the instance.
self.dbaas_image = None # The image used to create the instance.
self.dbaas_image_href = None # The link of the image.
self.id = None # The ID of the instance in the database.
self.local_id = None
self.address = None
self.initial_result = None # The initial result from the create call.
self.user_ip = None # The IP address of the instance, given to user.
self.infra_ip = None # The infrastructure network IP address.
self.result = None # The instance info returned by the API
self.nova_client = None # The instance of novaclient.
self.volume_client = None # The instance of the volume client.
self.name = None # Test name, generated each test run.
self.pid = None # The process ID of the instance.
self.user = None # The user instance who owns the instance.
self.admin_user = None # The admin user for the management interfaces.
self.volume = None # The volume the instance will have.
self.volume_id = None # Id for the attached vo186lume
self.storage = None # The storage device info for the volumes.
self.databases = None # The databases created on the instance.
self.host_info = None # Host Info before creating instances
self.user_context = None # A regular user context
self.users = None # The users created on the instance.
def get_address(self):
result = self.dbaas_admin.mgmt.instances.show(self.id)
addresses = result.server['addresses']
if "infranet" in addresses:
address = addresses['infranet'][0]
else:
address = addresses['private'][0]
return address['addr']
def get_local_id(self):
mgmt_instance = self.dbaas_admin.management.show(self.id)
return mgmt_instance.server["local_id"]
# The two variables are used below by tests which depend on an instance
# existing.
instance_info = InstanceTestInfo()
dbaas = None # Rich client used throughout this test.
dbaas_admin = None # Same as above, with admin privs.
# This is like a cheat code which allows the tests to skip creating a new
# instance and use an old one.
def existing_instance():
return os.environ.get("TESTS_USE_INSTANCE_ID", None)
def do_not_delete_instance():
return os.environ.get("TESTS_DO_NOT_DELETE_INSTANCE", None) is not None
def create_new_instance():
return existing_instance() is None
@test(groups=[GROUP, GROUP_START, 'dbaas.setup'],
depends_on_groups=["services.initialize"])
class InstanceSetup(object):
"""Makes sure the client can hit the ReST service.
This test also uses the API to find the image and flavor to use.
"""
@before_class
def setUp(self):
"""Sets up the client."""
reqs = Requirements(is_admin=True)
instance_info.admin_user = CONFIG.users.find_user(reqs)
instance_info.dbaas_admin = create_dbaas_client(
instance_info.admin_user)
global dbaas_admin
dbaas_admin = instance_info.dbaas_admin
# Make sure we create the client as the correct user if we're using
# a pre-built instance.
if existing_instance():
mgmt_inst = dbaas_admin.mgmt.instances.show(existing_instance())
t_id = mgmt_inst.tenant_id
instance_info.user = CONFIG.users.find_user_by_tenant_id(t_id)
else:
reqs = Requirements(is_admin=False)
instance_info.user = CONFIG.users.find_user(reqs)
instance_info.dbaas = create_dbaas_client(instance_info.user)
if CONFIG.white_box:
instance_info.nova_client = create_nova_client(instance_info.user)
instance_info.volume_client = create_nova_client(
instance_info.user,
service_type=CONFIG.nova_client['volume_service_type'])
global dbaas
dbaas = instance_info.dbaas
if CONFIG.white_box:
user = instance_info.user.auth_user
tenant = instance_info.user.tenant
instance_info.user_context = context.RequestContext(user, tenant)
@test(enabled=CONFIG.white_box)
def find_image(self):
result = dbaas_admin.find_image_and_self_href(CONFIG.dbaas_image)
instance_info.dbaas_image, instance_info.dbaas_image_href = result
@test
def test_find_flavor(self):
flavor_name = CONFIG.values.get('instance_flavor_name', 'm1.tiny')
flavors = dbaas.find_flavors_by_name(flavor_name)
assert_equal(len(flavors), 1, "Number of flavors with name '%s' "
"found was '%d'." % (flavor_name, len(flavors)))
flavor = flavors[0]
assert_true(flavor is not None, "Flavor '%s' not found!" % flavor_name)
flavor_href = dbaas.find_flavor_self_href(flavor)
assert_true(flavor_href is not None,
"Flavor href '%s' not found!" % flavor_name)
instance_info.dbaas_flavor = flavor
instance_info.dbaas_flavor_href = flavor_href
@test(enabled=CONFIG.white_box)
def test_add_imageref_config(self):
#TODO(tim.simpson): I'm not sure why this is here. The default image
# setup should be in initialization test code that lives somewhere
# else, probably with the code that uploads the image.
key = "reddwarf_imageref"
value = 1
description = "Default Image for Reddwarf"
config = {'key': key, 'value': value, 'description': description}
try:
dbaas_admin.configs.create([config])
except exceptions.ClientException as e:
# configs.create will throw an exception if the config already
# exists we will check the value after to make sure it is correct
# and set
pass
result = dbaas_admin.configs.get(key)
assert_equal(result.value, str(value))
@test
def create_instance_name(self):
id = existing_instance()
if id is None:
instance_info.name = "TEST_" + str(datetime.now())
else:
instance_info.name = dbaas.instances.get(id).name
@test(depends_on_classes=[InstanceSetup], groups=[GROUP])
def test_delete_instance_not_found():
"""Deletes an instance that does not exist."""
# Looks for a random UUID that (most probably) does not exist.
assert_raises(exceptions.NotFound, dbaas.instances.delete,
"7016efb6-c02c-403e-9628-f6f57d0920d0")
@test(depends_on_classes=[InstanceSetup],
groups=[GROUP, GROUP_START, tests.INSTANCES],
runs_after_groups=[tests.PRE_INSTANCES])
class CreateInstance(unittest.TestCase):
"""Test to create a Database Instance
If the call returns without raising an exception this test passes.
"""
def test_instance_size_too_big(self):
if 'reddwarf_max_accepted_volume_size' in CONFIG.values:
too_big = CONFIG.values['reddwarf_max_accepted_volume_size']
assert_raises(exceptions.OverLimit, dbaas.instances.create,
"way_too_large", instance_info.dbaas_flavor_href,
{'size': too_big + 1}, [])
assert_equal(413, dbaas.last_http_code)
#else:
# raise SkipTest("N/A: No max accepted volume size defined.")
def test_create(self):
databases = []
databases.append({"name": "firstdb", "character_set": "latin2",
"collate": "latin2_general_ci"})
databases.append({"name": "db2"})
instance_info.databases = databases
users = []
users.append({"name": "lite", "password": "litepass",
"databases": [{"name": "firstdb"}]})
instance_info.users = users
if CONFIG.values['reddwarf_main_instance_has_volume']:
instance_info.volume = {'size': 1}
else:
instance_info.volume = None
if create_new_instance():
instance_info.initial_result = dbaas.instances.create(
instance_info.name,
instance_info.dbaas_flavor_href,
instance_info.volume,
databases,
users)
assert_equal(200, dbaas.last_http_code)
else:
id = existing_instance()
instance_info.initial_result = dbaas.instances.get(id)
result = instance_info.initial_result
instance_info.id = result.id
if CONFIG.white_box:
instance_info.local_id = dbapi.localid_from_uuid(result.id)
report = CONFIG.get_report()
report.log("Instance UUID = %s" % instance_info.id)
if create_new_instance():
if CONFIG.white_box:
building = dbaas_mapping[power_state.BUILDING]
assert_equal(result.status, building)
assert_equal("BUILD", instance_info.initial_result.status)
else:
report.log("Test was invoked with TESTS_USE_INSTANCE_ID=%s, so no "
"instance was actually created." % id)
report.log("Local id = %d" % instance_info.get_local_id())
# Check these attrs only are returned in create response
expected_attrs = ['created', 'flavor', 'addresses', 'id', 'links',
'name', 'status', 'updated']
if CONFIG.values['reddwarf_can_have_volume']:
expected_attrs.append('volume')
if CONFIG.values['reddwarf_dns_support']:
expected_attrs.append('hostname')
with CheckInstance(result._info) as check:
if create_new_instance():
check.attrs_exist(result._info, expected_attrs,
msg="Create response")
# Don't CheckInstance if the instance already exists.
check.flavor()
check.links(result._info['links'])
if CONFIG.values['reddwarf_can_have_volume']:
check.volume()
def test_create_failure_with_empty_volume(self):
if CONFIG.values['reddwarf_must_have_volume']:
instance_name = "instance-failure-with-no-volume-size"
databases = []
volume = {}
assert_raises(exceptions.BadRequest, dbaas.instances.create,
instance_name, instance_info.dbaas_flavor_href,
volume, databases)
assert_equal(400, dbaas.last_http_code)
def test_create_failure_with_no_volume_size(self):
if CONFIG.values['reddwarf_must_have_volume']:
instance_name = "instance-failure-with-no-volume-size"
databases = []
volume = {'size': None}
assert_raises(exceptions.BadRequest, dbaas.instances.create,
instance_name, instance_info.dbaas_flavor_href,
volume, databases)
assert_equal(400, dbaas.last_http_code)
def test_mgmt_get_instance_on_create(self):
if CONFIG.test_mgmt:
result = dbaas_admin.management.show(instance_info.id)
expected_attrs = ['account_id', 'addresses', 'created',
'databases', 'flavor', 'guest_status', 'host',
'hostname', 'id', 'name',
'server_state_description', 'status', 'updated',
'users', 'volume', 'root_enabled_at',
'root_enabled_by']
with CheckInstance(result._info) as check:
check.attrs_exist(result._info, expected_attrs,
msg="Mgmt get instance")
check.flavor()
check.guest_status()
def assert_unprocessable(func, *args):
try:
func(*args)
# If the exception didn't get raised, but the instance is still in
# the BUILDING state, that's a bug.
result = dbaas.instances.get(instance_info.id)
if result.status == "BUILD":
fail("When an instance is being built, this function should "
"always raise UnprocessableEntity.")
except exceptions.UnprocessableEntity:
assert_equal(422, dbaas.last_http_code)
pass # Good
@test(depends_on_classes=[CreateInstance],
groups=[GROUP, GROUP_START, 'dbaas.mgmt.hosts_post_install'],
enabled=create_new_instance())
class AfterInstanceCreation(unittest.TestCase):
# instance calls
def test_instance_delete_right_after_create(self):
assert_unprocessable(dbaas.instances.delete, instance_info.id)
# root calls
def test_root_create_root_user_after_create(self):
assert_unprocessable(dbaas.root.create, instance_info.id)
def test_root_is_root_enabled_after_create(self):
assert_unprocessable(dbaas.root.is_root_enabled, instance_info.id)
# database calls
def test_database_index_after_create(self):
assert_unprocessable(dbaas.databases.list, instance_info.id)
def test_database_delete_after_create(self):
assert_unprocessable(dbaas.databases.delete, instance_info.id,
"testdb")
def test_database_create_after_create(self):
assert_unprocessable(dbaas.databases.create, instance_info.id,
instance_info.databases)
# user calls
def test_users_index_after_create(self):
assert_unprocessable(dbaas.users.list, instance_info.id)
def test_users_delete_after_create(self):
assert_unprocessable(dbaas.users.delete, instance_info.id,
"testuser")
def test_users_create_after_create(self):
users = list()
users.append({"name": "testuser", "password": "password",
"database": "testdb"})
assert_unprocessable(dbaas.users.create, instance_info.id, users)
def test_resize_instance_after_create(self):
assert_unprocessable(dbaas.instances.resize_instance,
instance_info.id, 8)
def test_resize_volume_after_create(self):
assert_unprocessable(dbaas.instances.resize_volume,
instance_info.id, 2)
@test(depends_on_classes=[CreateInstance],
runs_after=[AfterInstanceCreation],
groups=[GROUP, GROUP_START],
enabled=create_new_instance())
class WaitForGuestInstallationToFinish(object):
"""
Wait until the Guest is finished installing. It takes quite a while...
"""
@test(groups=['lemon'])
@time_out(60 * 16)
def test_instance_created(self):
# This version just checks the REST API status.
def result_is_active():
instance = dbaas.instances.get(instance_info.id)
if instance.status == "ACTIVE":
return True
else:
# If its not ACTIVE, anything but BUILD must be
# an error.
assert_equal("BUILD", instance.status)
assert_equal(instance.volume.get('used', None), None)
return False
poll_until(result_is_active)
result = dbaas.instances.get(instance_info.id)
report = CONFIG.get_report()
report.log("Created an instance, ID = %s." % instance_info.id)
report.log("TIP:")
report.log("Rerun the tests with TESTS_USE_INSTANCE_ID=%s "
"to skip ahead to this point." % instance_info.id)
report.log("Add TESTS_DO_NOT_DELETE_INSTANCE=True to avoid deleting "
"the instance at the end of the tests.")
@test(depends_on_classes=[WaitForGuestInstallationToFinish],
groups=[GROUP, GROUP_START],
enabled=CONFIG.white_box and create_new_instance())
class VerifyGuestStarted(unittest.TestCase):
"""
Test to verify the guest instance is started and we can get the init
process pid.
"""
def test_instance_created(self):
def check_status_of_instance():
status, err = process("sudo vzctl status %s | awk '{print $5}'"
% str(instance_info.local_id))
if string_in_list(status, ["running"]):
self.assertEqual("running", status.strip())
return True
else:
return False
poll_until(check_status_of_instance, sleep_time=5, time_out=(60 * 8))
def test_get_init_pid(self):
def get_the_pid():
out, err = process("pgrep init | vzpid - | awk '/%s/{print $1}'"
% str(instance_info.local_id))
instance_info.pid = out.strip()
return len(instance_info.pid) > 0
poll_until(get_the_pid, sleep_time=10, time_out=(60 * 10))
@test(depends_on_classes=[WaitForGuestInstallationToFinish],
groups=[GROUP, GROUP_START], enabled=create_new_instance())
class TestGuestProcess(object):
"""
Test that the guest process is started with all the right parameters
"""
@test(enabled=CONFIG.values['use_local_ovz'])
@time_out(60 * 10)
def check_process_alive_via_local_ovz(self):
init_re = ("[\w\W\|\-\s\d,]*nova-guest "
"--flagfile=/etc/nova/nova.conf nova[\W\w\s]*")
init_proc = re.compile(init_re)
guest_re = ("[\w\W\|\-\s]*/usr/bin/nova-guest "
"--flagfile=/etc/nova/nova.conf[\W\w\s]*")
guest_proc = re.compile(guest_re)
apt = re.compile("[\w\W\|\-\s]*apt-get[\w\W\|\-\s]*")
while True:
guest_process, err = process("pstree -ap %s | grep nova-guest"
% instance_info.pid)
if not string_in_list(guest_process, ["nova-guest"]):
time.sleep(10)
else:
if apt.match(guest_process):
time.sleep(10)
else:
init = init_proc.match(guest_process)
guest = guest_proc.match(guest_process)
if init and guest:
assert_true(True, init.group())
else:
assert_false(False, guest_process)
break
@test
def check_hwinfo_before_tests(self):
hwinfo = dbaas_admin.hwinfo.get(instance_info.id)
print("hwinfo : %r" % hwinfo._info)
expected_attrs = ['hwinfo']
CheckInstance(None).attrs_exist(hwinfo._info, expected_attrs,
msg="Hardware information")
# TODO(pdmars): instead of just checking that these are int's, get the
# instance flavor and verify that the values are correct for the flavor
assert_true(isinstance(hwinfo.hwinfo['mem_total'], int))
assert_true(isinstance(hwinfo.hwinfo['num_cpus'], int))
@test
def grab_diagnostics_before_tests(self):
diagnostics = dbaas_admin.diagnostics.get(instance_info.id)
diagnostic_tests_helper(diagnostics)
@test(depends_on_classes=[CreateInstance],
groups=[GROUP, GROUP_START, GROUP_TEST, "nova.volumes.instance"],
enabled=CONFIG.white_box)
class TestVolume(unittest.TestCase):
"""Make sure the volume is attached to instance correctly."""
def test_db_should_have_instance_to_volume_association(self):
"""The compute manager should associate a volume to the instance."""
volumes = db.volume_get_all_by_instance(context.get_admin_context(),
instance_info.local_id)
self.assertEqual(1, len(volumes))
@test(depends_on_classes=[WaitForGuestInstallationToFinish],
groups=[GROUP, GROUP_TEST, "dbaas.guest.start.test"])
class TestAfterInstanceCreatedGuestData(object):
"""
Test the optional parameters (databases and users) passed in to create
instance call were created.
"""
@test
def test_databases(self):
databases = dbaas.databases.list(instance_info.id)
dbs = [database.name for database in databases]
for db in instance_info.databases:
assert_true(db["name"] in dbs)
@test
def test_users(self):
users = dbaas.users.list(instance_info.id)
usernames = [user.name for user in users]
for user in instance_info.users:
assert_true(user["name"] in usernames)
@test(depends_on_classes=[WaitForGuestInstallationToFinish],
groups=[GROUP, GROUP_START, "dbaas.listing"])
class TestInstanceListing(object):
""" Test the listing of the instance information """
@before_class
def setUp(self):
reqs = Requirements(is_admin=False)
self.other_user = CONFIG.users.find_user(
reqs,
black_list=[instance_info.user.auth_user])
self.other_client = create_dbaas_client(self.other_user)
@test
def test_index_list(self):
expected_attrs = ['id', 'links', 'name', 'status', 'flavor', 'volume']
instances = dbaas.instances.list()
assert_equal(200, dbaas.last_http_code)
for instance in instances:
instance_dict = instance._info
with CheckInstance(instance_dict) as check:
print("testing instance_dict=%s" % instance_dict)
check.attrs_exist(instance_dict, expected_attrs,
msg="Instance Index")
check.links(instance_dict['links'])
check.flavor()
check.volume()
@test
def test_get_instance(self):
expected_attrs = ['created', 'databases', 'flavor', 'hostname', 'id',
'links', 'name', 'status', 'updated', 'volume', 'ip']
instance = dbaas.instances.get(instance_info.id)
assert_equal(200, dbaas.last_http_code)
instance_dict = instance._info
print("instance_dict=%s" % instance_dict)
with CheckInstance(instance_dict) as check:
check.attrs_exist(instance_dict, expected_attrs,
msg="Get Instance")
check.flavor()
check.links(instance_dict['links'])
check.used_volume()
@test
def test_get_instance_status(self):
result = dbaas.instances.get(instance_info.id)
assert_equal(200, dbaas.last_http_code)
assert_equal("ACTIVE", result.status)
@test
def test_get_legacy_status(self):
result = dbaas.instances.get(instance_info.id)
assert_equal(200, dbaas.last_http_code)
assert_true(result is not None)
@test
def test_get_legacy_status_notfound(self):
assert_raises(exceptions.NotFound, dbaas.instances.get, -2)
@test(enabled=CONFIG.values["reddwarf_main_instance_has_volume"])
def test_volume_found(self):
instance = dbaas.instances.get(instance_info.id)
if create_new_instance():
assert_equal(instance_info.volume['size'], instance.volume['size'])
else:
assert_true(isinstance(instance_info.volume['size'], int))
if create_new_instance():
assert_true(0.12 < instance.volume['used'] < 0.25)
@test
def test_instance_not_shown_to_other_user(self):
daffy_ids = [instance.id for instance in
self.other_client.instances.list()]
assert_equal(200, self.other_client.last_http_code)
admin_ids = [instance.id for instance in dbaas.instances.list()]
assert_equal(200, dbaas.last_http_code)
assert_equal(len(daffy_ids), 0)
assert_not_equal(sorted(admin_ids), sorted(daffy_ids))
assert_raises(exceptions.NotFound,
self.other_client.instances.get, instance_info.id)
for id in admin_ids:
assert_equal(daffy_ids.count(id), 0)
@test
def test_instance_not_deleted_by_other_user(self):
assert_raises(exceptions.NotFound,
self.other_client.instances.get, instance_info.id)
assert_raises(exceptions.NotFound,
self.other_client.instances.delete, instance_info.id)
@test(enabled=CONFIG.values['test_mgmt'])
def test_mgmt_get_instance_after_started(self):
result = dbaas_admin.management.show(instance_info.id)
expected_attrs = ['account_id', 'addresses', 'created', 'databases',
'flavor', 'guest_status', 'host', 'hostname', 'id',
'name', 'root_enabled_at', 'root_enabled_by',
'server_state_description', 'status',
'updated', 'users', 'volume']
with CheckInstance(result._info) as check:
check.attrs_exist(result._info, expected_attrs,
msg="Mgmt get instance")
check.flavor()
check.guest_status()
check.addresses()
check.volume_mgmt()
@test(depends_on_groups=['dbaas.api.instances.actions'],
groups=[GROUP, tests.INSTANCES, "dbaas.diagnostics"])
class CheckDiagnosticsAfterTests(object):
""" Check the diagnostics after running api commands on an instance. """
@test
def test_check_diagnostics_on_instance_after_tests(self):
diagnostics = dbaas_admin.diagnostics.get(instance_info.id)
assert_equal(200, dbaas.last_http_code)
diagnostic_tests_helper(diagnostics)
msg = "Fat Pete has emerged. size (%s > 30MB)" % diagnostics.vmPeak
assert_true(diagnostics.vmPeak < (30 * 1024), msg)
@test(depends_on=[WaitForGuestInstallationToFinish],
depends_on_groups=[GROUP_USERS, GROUP_DATABASES, GROUP_ROOT],
groups=[GROUP, GROUP_STOP])
class DeleteInstance(object):
""" Delete the created instance """
@time_out(3)
@test(runs_after_groups=[GROUP_START, GROUP_TEST, tests.INSTANCES])
def test_delete(self):
if do_not_delete_instance():
CONFIG.get_report().log("TESTS_DO_NOT_DELETE_INSTANCE=True was "
"specified, skipping delete...")
raise SkipTest("TESTS_DO_NOT_DELETE_INSTANCE was specified.")
global dbaas
if not hasattr(instance_info, "initial_result"):
raise SkipTest("Instance was never created, skipping test...")
if CONFIG.white_box:
# Change this code to get the volume using the API.
# That way we can keep it while keeping it black box.
admin_context = context.get_admin_context()
volumes = db.volume_get_all_by_instance(admin_context(),
instance_info.local_id)
instance_info.volume_id = volumes[0].id
# Update the report so the logs inside the instance will be saved.
CONFIG.get_report().update()
dbaas.instances.delete(instance_info.id)
attempts = 0
try:
time.sleep(1)
result = True
while result is not None:
attempts += 1
time.sleep(1)
result = dbaas.instances.get(instance_info.id)
assert_equal(200, dbaas.last_http_code)
assert_equal("SHUTDOWN", result.status)
except exceptions.NotFound:
pass
except Exception as ex:
fail("A failure occured when trying to GET instance %s for the %d "
"time: %s" % (str(instance_info.id), attempts, str(ex)))
@time_out(30)
@test(enabled=CONFIG.values["reddwarf_can_have_volume"],
depends_on=[test_delete])
def test_volume_is_deleted(self):
raise SkipTest("Cannot test volume is deleted from db.")
try:
while True:
db.volume_get(instance_info.user_context,
instance_info.volume_id)
time.sleep(1)
except backend_exception.VolumeNotFound:
pass
#TODO: make sure that the actual instance, volume, guest status, and DNS
# entries are deleted.
@test(depends_on_classes=[CreateInstance, VerifyGuestStarted,
WaitForGuestInstallationToFinish], groups=[GROUP, GROUP_START],
enabled=CONFIG.values['test_mgmt'])
class VerifyInstanceMgmtInfo(object):
@before_class
def set_up(self):
self.mgmt_details = dbaas_admin.management.show(instance_info.id)
def _assert_key(self, k, expected):
v = getattr(self.mgmt_details, k)
err = "Key %r does not match expected value of %r (was %r)." \
% (k, expected, v)
assert_equal(str(v), str(expected), err)
@test
def test_id_matches(self):
self._assert_key('id', instance_info.id)
@test
def test_bogus_instance_mgmt_data(self):
# Make sure that a management call to a bogus API 500s.
# The client reshapes the exception into just an OpenStackException.
assert_raises(exceptions.NotFound,
dbaas_admin.management.show, -1)
@test
def test_mgmt_ips_associated(self):
# Test that the management index properly associates an instances with
# ONLY its IPs.
mgmt_index = dbaas_admin.management.index()
# Every instances has exactly one address.
for instance in mgmt_index:
assert_equal(1, len(instance.ips))
@test
def test_mgmt_data(self):
# Test that the management API returns all the values we expect it to.
info = instance_info
ir = info.initial_result
cid = ir.id
instance_id = instance_info.local_id
expected = {
'id': ir.id,
'name': ir.name,
'account_id': info.user.auth_user,
# TODO(hub-cap): fix this since its a flavor object now
#'flavorRef': info.dbaas_flavor_href,
'databases': [
{
'name': 'db2',
'character_set': 'utf8',
'collate': 'utf8_general_ci',
},
{
'name': 'firstdb',
'character_set': 'latin2',
'collate': 'latin2_general_ci',
}
],
}
if CONFIG.white_box:
admin_context = context.get_admin_context()
volumes = db.volume_get_all_by_instance(admin_context(),
instance_id)
assert_equal(len(volumes), 1)
volume = volumes[0]
expected['volume'] = {
'id': volume.id,
'name': volume.display_name,
'size': volume.size,
'description': volume.display_description,
}
expected_entry = info.expected_dns_entry()
if expected_entry:
expected['hostname'] = expected_entry.name
assert_true(self.mgmt_details is not None)
for (k, v) in expected.items():
msg = "Attr %r is missing." % k
assert_true(hasattr(self.mgmt_details, k), msg)
msg = ("Attr %r expected to be %r but was %r." %
(k, v, getattr(self.mgmt_details, k)))
assert_equal(getattr(self.mgmt_details, k), v, msg)
print(self.mgmt_details.users)
for user in self.mgmt_details.users:
assert_true('name' in user, "'name' not in users element.")
class CheckInstance(AttrCheck):
"""Class to check various attributes of Instance details"""
def __init__(self, instance):
super(CheckInstance, self).__init__()
self.instance = instance
def flavor(self):
if 'flavor' not in self.instance:
self.fail("'flavor' not found in instance.")
else:
expected_attrs = ['id', 'links']
self.attrs_exist(self.instance['flavor'], expected_attrs,
msg="Flavor")
self.links(self.instance['flavor']['links'])
def volume_key_exists(self):
if CONFIG.values['reddwarf_main_instance_has_volume']:
if 'volume' not in self.instance:
self.fail("'volume' not found in instance.")
return False
return True
def volume(self):
if not CONFIG.values["reddwarf_can_have_volume"]:
return
if self.volume_key_exists():
expected_attrs = ['size']
if not create_new_instance():
expected_attrs.append('used')
self.attrs_exist(self.instance['volume'], expected_attrs,
msg="Volumes")
def used_volume(self):
if not CONFIG.values["reddwarf_can_have_volume"]:
return
if self.volume_key_exists():
expected_attrs = ['size', 'used']
print self.instance
self.attrs_exist(self.instance['volume'], expected_attrs,
msg="Volumes")
def volume_mgmt(self):
if self.volume_key_exists():
expected_attrs = ['description', 'id', 'name', 'size']
self.attrs_exist(self.instance['volume'], expected_attrs,
msg="Volumes")
def addresses(self):
expected_attrs = ['addr', 'version']
print self.instance
networks = ['usernet']
for network in networks:
for address in self.instance['addresses'][network]:
self.attrs_exist(address, expected_attrs,
msg="Address")
def guest_status(self):
expected_attrs = ['created_at', 'deleted', 'deleted_at', 'instance_id',
'state', 'state_description', 'updated_at']
self.attrs_exist(self.instance['guest_status'], expected_attrs,
msg="Guest status")
def mgmt_volume(self):
expected_attrs = ['description', 'id', 'name', 'size']
self.attrs_exist(self.instance['volume'], expected_attrs,
msg="Volume")
def diagnostic_tests_helper(diagnostics):
print("diagnostics : %r" % diagnostics._info)
expected_attrs = ['version', 'fdSize', 'vmSize', 'vmHwm', 'vmRss',
'vmPeak', 'threads']
CheckInstance(None).attrs_exist(diagnostics._info, expected_attrs,
msg="Diagnostics")
assert_true(isinstance(diagnostics.fdSize, int))
assert_true(isinstance(diagnostics.threads, int))
assert_true(isinstance(diagnostics.vmHwm, int))
assert_true(isinstance(diagnostics.vmPeak, int))
assert_true(isinstance(diagnostics.vmRss, int))
assert_true(isinstance(diagnostics.vmSize, int))
actual_version = diagnostics.version
update_test_conf = CONFIG.values.get("guest-update-test", None)
if update_test_conf is not None:
if actual_version == update_test_conf['next-version']:
return # This is acceptable but may not match the regex.
version_pattern = re.compile(r'[a-f0-9]+')
msg = "Version %s does not match pattern %s." % (actual_version,
version_pattern)
assert_true(version_pattern.match(actual_version), msg)

View File

@ -0,0 +1,591 @@
# Copyright 2011 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 proboscis import after_class
from proboscis import before_class
from proboscis import test
from proboscis.asserts import assert_equal
from proboscis.asserts import assert_false
from proboscis.asserts import assert_not_equal
from proboscis.asserts import assert_raises
from proboscis.asserts import assert_true
from proboscis.asserts import fail
from proboscis.decorators import time_out
from proboscis import SkipTest
from reddwarf import tests
from reddwarf.tests.util.check import Checker
from reddwarfclient.exceptions import BadRequest
from reddwarfclient.exceptions import UnprocessableEntity
from reddwarf.tests.api.instances import GROUP as INSTANCE_GROUP
from reddwarf.tests.api.instances import GROUP_START
from reddwarf.tests.api.instances import instance_info
from reddwarf.tests.api.instances import assert_unprocessable
from reddwarf.tests import util
from reddwarf.tests.util import poll_until
from reddwarf.tests.config import CONFIG
from reddwarf.tests.util import LocalSqlClient
from sqlalchemy import create_engine
from sqlalchemy import exc as sqlalchemy_exc
from reddwarf.tests.util.check import TypeCheck
from sqlalchemy.sql.expression import text
GROUP = "dbaas.api.instances.actions"
GROUP_REBOOT = "dbaas.api.instances.actions.reboot"
GROUP_RESTART = "dbaas.api.instances.actions.restart"
GROUP_STOP_MYSQL = "dbaas.api.instances.actions.stop"
MYSQL_USERNAME = "test_user"
MYSQL_PASSWORD = "abcde"
FAKE_MODE = CONFIG.values['fake_mode']
# If true, then we will actually log into the database.
USE_IP = not CONFIG.values['fake_mode']
# If true, then we will actually search for the process
USE_LOCAL_OVZ = CONFIG.values['use_local_ovz']
class MySqlConnection(object):
def __init__(self, host):
self.host = host
def connect(self):
"""Connect to MySQL database."""
print("Connecting to MySQL, mysql --host %s -u %s -p%s"
% (self.host, MYSQL_USERNAME, MYSQL_PASSWORD))
self.client = LocalSqlClient(util.init_engine(
MYSQL_USERNAME, MYSQL_PASSWORD, self.host), use_flush=False)
def is_connected(self):
try:
with self.client:
self.client.execute(text("""SELECT "Hello.";"""))
return True
except (sqlalchemy_exc.OperationalError,
sqlalchemy_exc.DisconnectionError,
sqlalchemy_exc.TimeoutError):
return False
except Exception as ex:
print("EX WAS:")
print(type(ex))
print(ex)
raise ex
TIME_OUT_TIME = 10 * 60
USER_WAS_DELETED = False
class ActionTestBase(object):
"""Has some helpful functions for testing actions.
The test user must be created for some of these functions to work.
"""
def set_up(self):
"""If you're using this as a base class, call this method first."""
if USE_IP:
address = instance_info.get_address()
self.connection = MySqlConnection(address)
self.dbaas = instance_info.dbaas
@property
def instance(self):
return self.dbaas.instances.get(self.instance_id)
@property
def instance_local_id(self):
return instance_info.get_local_id()
@property
def instance_id(self):
return instance_info.id
def create_user(self):
"""Create a MySQL user we can use for this test."""
users = [{"name": MYSQL_USERNAME, "password": MYSQL_PASSWORD,
"database": MYSQL_USERNAME}]
self.dbaas.users.create(instance_info.id, users)
def has_user():
users = self.dbaas.users.list(instance_info.id)
return any([user.name == MYSQL_USERNAME for user in users])
poll_until(has_user, time_out=30)
if not FAKE_MODE:
time.sleep(5)
def ensure_mysql_is_running(self):
"""Make sure MySQL is accessible before restarting."""
with Checker() as check:
if USE_IP:
self.connection.connect()
check.true(self.connection.is_connected(),
"Able to connect to MySQL.")
if USE_LOCAL_OVZ:
self.proc_id = self.find_mysql_proc_on_instance()
check.true(self.proc_id is not None,
"MySQL process can be found.")
instance = self.instance
check.false(instance is None)
check.equal(instance.status, "ACTIVE")
def find_mysql_proc_on_instance(self):
return util.find_mysql_procid_on_instance(self.instance_local_id)
def log_current_users(self):
users = self.dbaas.users.list(self.instance_id)
CONFIG.get_report().log("Current user count = %d" % len(users))
for user in users:
CONFIG.get_report().log("\t" + str(user))
@test(depends_on_groups=[GROUP_START])
def create_user():
"""Create a test user so that subsequent tests can log in."""
helper = ActionTestBase()
helper.set_up()
if USE_IP:
try:
helper.create_user()
except BadRequest:
pass # Ignore this if the user already exists.
helper.connection.connect()
assert_true(helper.connection.is_connected(),
"Test user must be able to connect to MySQL.")
class RebootTestBase(ActionTestBase):
"""Tests restarting MySQL."""
def call_reboot(self):
raise NotImplementedError()
def wait_for_broken_connection(self):
"""Wait until our connection breaks."""
if not USE_IP:
return
poll_until(self.connection.is_connected,
lambda connected: not connected,
time_out=TIME_OUT_TIME)
def wait_for_successful_restart(self):
"""Wait until status becomes running."""
def is_finished_rebooting():
instance = self.instance
instance.get()
print(instance.status)
if instance.status == "REBOOT":
return False
assert_equal("ACTIVE", instance.status)
return True
poll_until(is_finished_rebooting, time_out=TIME_OUT_TIME)
def assert_mysql_proc_is_different(self):
if not USE_LOCAL_OVZ:
return
new_proc_id = self.find_mysql_proc_on_instance()
assert_not_equal(new_proc_id, self.proc_id,
"MySQL process ID should be different!")
def successful_restart(self):
"""Restart MySQL via the REST API successfully."""
self.fix_mysql()
self.call_reboot()
self.wait_for_broken_connection()
self.wait_for_successful_restart()
self.assert_mysql_proc_is_different()
def mess_up_mysql(self):
"""Ruin MySQL's ability to restart."""
self.fix_mysql() # kill files
cmd = """ssh %s 'sudo cp /dev/null /var/lib/mysql/ib_logfile%d'"""
for index in range(2):
full_cmd = cmd % (self.instance_local_id, index)
print("RUNNING COMMAND: %s" % full_cmd)
util.process(full_cmd)
def fix_mysql(self):
"""Fix MySQL's ability to restart."""
if not FAKE_MODE:
cmd = "ssh %s 'sudo rm /var/lib/mysql/ib_logfile%d'"
for index in range(2):
util.process(cmd % (self.instance_local_id, index))
def wait_for_failure_status(self):
"""Wait until status becomes running."""
def is_finished_rebooting():
instance = self.instance
if instance.status == "REBOOT":
return False
assert_equal("SHUTDOWN", instance.status)
return True
poll_until(is_finished_rebooting, time_out=TIME_OUT_TIME)
def unsuccessful_restart(self):
"""Restart MySQL via the REST when it should fail, assert it does."""
assert not FAKE_MODE
self.mess_up_mysql()
self.call_reboot()
self.wait_for_broken_connection()
self.wait_for_failure_status()
def restart_normally(self):
"""Fix iblogs and reboot normally."""
self.fix_mysql()
self.test_successful_restart()
@test(groups=[tests.INSTANCES, INSTANCE_GROUP, GROUP, GROUP_RESTART],
depends_on_groups=[GROUP_START], depends_on=[create_user])
class RestartTests(RebootTestBase):
"""Tests restarting MySQL."""
def call_reboot(self):
self.instance.restart()
assert_equal(202, self.dbaas.last_http_code)
@before_class
def test_set_up(self):
self.set_up()
@test
def test_ensure_mysql_is_running(self):
"""Make sure MySQL is accessible before restarting."""
self.ensure_mysql_is_running()
@test(depends_on=[test_ensure_mysql_is_running], enabled=not FAKE_MODE)
def test_unsuccessful_restart(self):
"""Restart MySQL via the REST when it should fail, assert it does."""
self.unsuccessful_restart()
@test(depends_on=[test_set_up],
runs_after=[test_ensure_mysql_is_running, test_unsuccessful_restart])
def test_successful_restart(self):
"""Restart MySQL via the REST API successfully."""
self.successful_restart()
@test(groups=[tests.INSTANCES, INSTANCE_GROUP, GROUP, GROUP_STOP_MYSQL],
depends_on_groups=[GROUP_START], depends_on=[create_user])
class StopTests(RebootTestBase):
"""Tests which involve stopping MySQL."""
def call_reboot(self):
self.instance.restart()
@before_class
def test_set_up(self):
self.set_up()
@test
def test_ensure_mysql_is_running(self):
"""Make sure MySQL is accessible before restarting."""
self.ensure_mysql_is_running()
@test(depends_on=[test_ensure_mysql_is_running])
def test_stop_mysql(self):
"""Stops MySQL."""
instance_info.dbaas_admin.management.stop(self.instance_id)
self.wait_for_broken_connection()
self.wait_for_failure_status()
@test(depends_on=[test_stop_mysql])
def test_instance_get_shows_volume_info_while_mysql_is_down(self):
"""
Confirms the get call behaves appropriately while an instance is
down.
"""
instance = self.dbaas.instances.get(self.instance_id)
with TypeCheck("instance", instance) as check:
check.has_field("volume", dict)
check.true('size' in instance.volume)
check.true('used' in instance.volume)
check.true(isinstance(instance.volume.get('size', None), int))
check.true(isinstance(instance.volume.get('used', None), float))
@test(depends_on=[test_set_up],
runs_after=[test_instance_get_shows_volume_info_while_mysql_is_down],
groups=['donut'])
def test_successful_restart_when_in_shutdown_state(self):
"""Restart MySQL via the REST API successfully when MySQL is down."""
self.successful_restart()
@test(groups=[tests.INSTANCES, INSTANCE_GROUP, GROUP, GROUP_REBOOT],
depends_on_groups=[GROUP_START], depends_on=[RestartTests, create_user])
class RebootTests(RebootTestBase):
"""Tests restarting instance."""
def call_reboot(self):
instance_info.dbaas_admin.management.reboot(self.instance_id)
@before_class
def test_set_up(self):
self.set_up()
@test
def test_ensure_mysql_is_running(self):
"""Make sure MySQL is accessible before restarting."""
self.ensure_mysql_is_running()
@test(depends_on=[test_ensure_mysql_is_running], enabled=not FAKE_MODE)
def test_unsuccessful_restart(self):
"""Restart MySQL via the REST when it should fail, assert it does."""
self.unsuccessful_restart()
@after_class(always_run=True)
def test_successful_restart(self):
"""Restart MySQL via the REST API successfully."""
self.successful_restart()
@test(groups=[tests.INSTANCES, INSTANCE_GROUP, GROUP,
GROUP + ".resize.instance"],
depends_on_groups=[GROUP_START], depends_on=[create_user],
runs_after=[RebootTests])
class ResizeInstanceTest(ActionTestBase):
"""
Integration Test cases for resize instance
"""
@property
def flavor_id(self):
return instance_info.dbaas_flavor_href
def get_flavor_href(self, flavor_id=2):
res = instance_info.dbaas.find_flavor_and_self_href(flavor_id)
dbaas_flavor, dbaas_flavor_href = res
return dbaas_flavor_href
def wait_for_resize(self):
def is_finished_resizing():
instance = self.instance
if instance.status == "RESIZE":
return False
assert_equal("ACTIVE", instance.status)
return True
poll_until(is_finished_resizing, time_out=TIME_OUT_TIME)
@before_class
def setup(self):
self.set_up()
if USE_IP:
self.connection.connect()
assert_true(self.connection.is_connected(),
"Should be able to connect before resize.")
self.user_was_deleted = False
@test
def test_instance_resize_same_size_should_fail(self):
assert_raises(BadRequest, self.dbaas.instances.resize_instance,
self.instance_id, self.flavor_id)
def obtain_flavor_ids(self):
old_id = self.instance.flavor['id']
self.expected_old_flavor_id = old_id
res = instance_info.dbaas.find_flavor_and_self_href(old_id)
self.expected_dbaas_flavor, _dontcare_ = res
flavor_name = CONFIG.values.get('instance_bigger_flavor_name',
'm1.small')
flavors = self.dbaas.find_flavors_by_name(flavor_name)
assert_equal(len(flavors), 1, "Number of flavors with name '%s' "
"found was '%d'." % (flavor_name, len(flavors)))
flavor = flavors[0]
assert_true(flavor is not None, "Flavor '%s' not found!" % flavor_name)
flavor_href = self.dbaas.find_flavor_self_href(flavor)
assert_true(flavor_href is not None,
"Flavor href '%s' not found!" % flavor_name)
self.expected_new_flavor_id = flavor.id
@test(depends_on=[test_instance_resize_same_size_should_fail])
def test_status_changed_to_resize(self):
self.log_current_users()
self.obtain_flavor_ids()
self.dbaas.instances.resize_instance(
self.instance_id,
self.get_flavor_href(flavor_id=self.expected_new_flavor_id))
assert_equal(202, self.dbaas.last_http_code)
#(WARNING) IF THE RESIZE IS WAY TOO FAST THIS WILL FAIL
assert_unprocessable(
self.dbaas.instances.resize_instance,
self.instance_id,
self.get_flavor_href(flavor_id=self.expected_new_flavor_id))
@test(depends_on=[test_status_changed_to_resize])
@time_out(TIME_OUT_TIME)
def test_instance_returns_to_active_after_resize(self):
self.wait_for_resize()
@test(depends_on=[test_instance_returns_to_active_after_resize])
def resize_should_not_delete_users(self):
"""Resize should not delete users."""
# Resize has an incredibly weird bug where users are deleted after
# a resize. The code below is an attempt to catch this while proceeding
# with the rest of the test (note the use of runs_after).
if USE_IP:
self.connection.connect()
if not self.connection.is_connected():
# Ok, this is def. a failure, but before we toss up an error
# lets recreate to see how far we can get.
CONFIG.get_report().log(
"Having to recreate the test_user! Resizing killed it!")
self.log_current_users()
self.create_user()
fail("Somehow, the resize made the test user disappear.")
@test(depends_on=[test_instance_returns_to_active_after_resize],
runs_after=[resize_should_not_delete_users])
def test_make_sure_mysql_is_running_after_resize(self):
self.ensure_mysql_is_running()
actual = self.get_flavor_href(self.instance.flavor['id'])
expected = self.get_flavor_href(flavor_id=self.expected_new_flavor_id)
assert_equal(actual, expected)
@test(depends_on=[test_make_sure_mysql_is_running_after_resize])
@time_out(TIME_OUT_TIME)
def test_resize_down(self):
expected_dbaas_flavor = self.expected_dbaas_flavor
self.dbaas.instances.resize_instance(
self.instance_id,
self.get_flavor_href(flavor_id=self.expected_old_flavor_id))
assert_equal(202, self.dbaas.last_http_code)
self.old_dbaas_flavor = instance_info.dbaas_flavor
instance_info.dbaas_flavor = expected_dbaas_flavor
self.wait_for_resize()
assert_equal(str(self.instance.flavor['id']),
str(self.expected_old_flavor_id))
@test(groups=[tests.INSTANCES, INSTANCE_GROUP, GROUP,
GROUP + ".resize.instance"],
depends_on_groups=[GROUP_START], depends_on=[create_user],
runs_after=[RebootTests, ResizeInstanceTest])
def resize_should_not_delete_users():
if USER_WAS_DELETED:
fail("Somehow, the resize made the test user disappear.")
@test(depends_on_classes=[ResizeInstanceTest], depends_on=[create_user],
groups=[GROUP, tests.INSTANCES])
class ResizeInstanceVolume(object):
""" Resize the volume of the instance """
@before_class
def setUp(self):
self.old_volume_size = int(instance_info.volume['size'])
self.new_volume_size = self.old_volume_size + 1
# Create some databases to check they still exist after the resize
self.expected_dbs = ['salmon', 'halibut']
databases = []
for name in self.expected_dbs:
databases.append({"name": name})
instance_info.dbaas.databases.create(instance_info.id, databases)
@test
@time_out(60)
def test_volume_resize(self):
instance_info.dbaas.instances.resize_volume(instance_info.id,
self.new_volume_size)
@test
@time_out(300)
def test_volume_resize_success(self):
def check_resize_status():
instance = instance_info.dbaas.instances.get(instance_info.id)
if instance.status == "ACTIVE":
return True
elif instance.status == "RESIZE":
return False
else:
fail("Status should not be %s" % instance.status)
poll_until(check_resize_status, sleep_time=2, time_out=300)
instance = instance_info.dbaas.instances.get(instance_info.id)
assert_equal(instance.volume['size'], self.new_volume_size)
@test
@time_out(300)
def test_volume_resize_success_databases(self):
databases = instance_info.dbaas.databases.list(instance_info.id)
db_list = []
for database in databases:
db_list.append(database.name)
for name in self.expected_dbs:
if not name in db_list:
fail("Database %s was not found after the volume resize. "
"Returned list: %s" % (name, databases))
# This tests the ability of the guest to upgrade itself.
# It is necessarily tricky because we need to be able to upload a new copy of
# the guest into an apt-repo in the middle of the test.
# "guest-update-test" is where the knowledge of how to do this is set in the
# test conf. If it is not specified this test never runs.
UPDATE_GUEST_CONF = CONFIG.values.get("guest-update-test", None)
@test(groups=[tests.INSTANCES, INSTANCE_GROUP, GROUP, GROUP + ".update_guest"],
depends_on=[create_user],
depends_on_groups=[GROUP_START])
class UpdateGuest(object):
def get_version(self):
info = instance_info.dbaas_admin.diagnostics.get(instance_info.id)
return info.version
@before_class(enabled=UPDATE_GUEST_CONF is not None)
def check_version_is_old(self):
"""Make sure we have the old version before proceeding."""
self.old_version = self.get_version()
self.next_version = UPDATE_GUEST_CONF["next-version"]
assert_not_equal(self.old_version, self.next_version)
@test(enabled=UPDATE_GUEST_CONF is not None)
def upload_update_to_repo(self):
cmds = UPDATE_GUEST_CONF["install-repo-cmd"]
utils.execute(*cmds, run_as_root=True)
@test(enabled=UPDATE_GUEST_CONF is not None,
depends_on=[upload_update_to_repo])
def update_and_wait_to_finish(self):
instance_info.dbaas_admin.management.update(instance_info.id)
def finished():
current_version = self.get_version()
if current_version == self.next_version:
return True
# The only valid thing for it to be aside from next_version is
# old version.
assert_equal(current_version, self.old_version)
poll_until(finished, sleep_time=1, time_out=3 * 60)
@test(enabled=UPDATE_GUEST_CONF is not None,
depends_on=[upload_update_to_repo])
@time_out(30)
def update_again(self):
"""Test the wait time of a pointless update."""
instance_info.dbaas_admin.management.update(instance_info.id)
# Make sure this isn't taking too long.
instance_info.dbaas_admin.diagnostics.get(instance_info.id)

View File

@ -0,0 +1,114 @@
import time
from proboscis import after_class
from proboscis import before_class
from proboscis import test
from proboscis.asserts import *
from proboscis.decorators import time_out
from reddwarfclient import exceptions
from reddwarf.tests.util import create_dbaas_client
from reddwarf.tests.util import poll_until
from reddwarf.tests.util import test_config
from reddwarf.tests.util.users import Requirements
class TestBase(object):
def set_up(self):
reqs = Requirements(is_admin=True)
self.user = test_config.users.find_user(reqs)
self.dbaas = create_dbaas_client(self.user)
def create_instance(self, name, size=1):
result = self.dbaas.instances.create(name, 1, {'size': size}, [], [])
return result.id
def wait_for_instance_status(self, instance_id, status="ACTIVE"):
poll_until(lambda: self.dbaas.instances.get(instance_id),
lambda instance: instance.status == status,
time_out=10)
def wait_for_instance_task_status(self, instance_id, description):
poll_until(lambda: self.dbaas.management.show(instance_id),
lambda instance: instance.task_description == description,
time_out=10)
def is_instance_deleted(self, instance_id):
while True:
try:
instance = self.dbaas.instances.get(instance_id)
except exceptions.NotFound:
return True
time.sleep(.5)
def get_task_info(self, instance_id):
instance = self.dbaas.management.show(instance_id)
return instance.status, instance.task_description
def delete_instance(self, instance_id, assert_deleted=True):
instance = self.dbaas.instances.get(instance_id)
instance.delete()
if assert_deleted:
assert_true(self.is_instance_deleted(instance_id))
def delete_errored_instance(self, instance_id):
self.wait_for_instance_status(instance_id, 'ERROR')
status, desc = self.get_task_info(instance_id)
assert_equal(status, "ERROR")
self.delete_instance(instance_id)
@test(runs_after_groups=["services.initialize"],
groups=['dbaas.api.instances.delete'])
class ErroredInstanceDelete(TestBase):
"""
Test that an instance in an ERROR state is actually deleted when delete
is called.
"""
@before_class
def set_up(self):
"""Create some flawed instances."""
super(ErroredInstanceDelete, self).set_up()
# Create an instance that fails during server prov.
self.server_error = self.create_instance('test_SERVER_ERROR')
# Create an instance that fails during volume prov.
self.volume_error = self.create_instance('test_VOLUME_ERROR', size=9)
# Create an instance that fails during DNS prov.
#self.dns_error = self.create_instance('test_DNS_ERROR')
# Create an instance that fails while it's been deleted the first time.
self.delete_error = self.create_instance('test_ERROR_ON_DELETE')
@test
@time_out(20)
def delete_server_error(self):
self.delete_errored_instance(self.server_error)
@test
@time_out(20)
def delete_volume_error(self):
self.delete_errored_instance(self.volume_error)
@test(enabled=False)
@time_out(20)
def delete_dns_error(self):
self.delete_errored_instance(self.dns_error)
@test
@time_out(20)
def delete_error_on_delete_instance(self):
id = self.delete_error
self.wait_for_instance_status(id, 'ACTIVE')
self.wait_for_instance_task_status(id, 'No tasks for the instance.')
instance = self.dbaas.management.show(id)
assert_equal(instance.status, "ACTIVE")
assert_equal(instance.task_description, 'No tasks for the instance.')
# Try to delete the instance. This fails the first time due to how
# the test fake is setup.
self.delete_instance(id, assert_deleted=False)
instance = self.dbaas.management.show(id)
assert_equal(instance.status, "SHUTDOWN")
assert_equal(instance.task_description, "Deleting the instance.")
# Try a second time. This will succeed.
self.delete_instance(id)

View File

@ -0,0 +1,119 @@
# 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.
"""
Extra tests to create an instance, shut down MySQL, and delete it.
"""
from proboscis.decorators import time_out
from proboscis import after_class
from proboscis import before_class
from proboscis import test
from proboscis.asserts import assert_equal
from proboscis.asserts import assert_false
from proboscis.asserts import assert_is
from proboscis.asserts import assert_is_not
from proboscis.asserts import assert_is_none
from proboscis.asserts import assert_not_equal
from proboscis.asserts import assert_raises
from proboscis.asserts import assert_true
from proboscis.asserts import Check
from proboscis.asserts import fail
import time
from datetime import datetime
from reddwarfclient import exceptions
from reddwarf.tests import util
from reddwarf.tests.util import create_client
from reddwarf.tests.util import poll_until
from reddwarf.tests.util import test_config
@test(groups=["dbaas.api.instances.down"])
class TestBase(object):
@before_class
def set_up(self):
self.client = create_client(is_admin=False)
self.mgmt_client = create_client(is_admin=True)
flavor_name = test_config.values.get('instance_flavor_name', 'm1.tiny')
flavors = self.client.find_flavors_by_name(flavor_name)
self.flavor_id = flavors[0].id
self.name = "TEST_" + str(datetime.now())
# Get the resize to flavor.
flavor2_name = test_config.values.get('instance_bigger_flavor_name',
'm1.small')
flavors2 = self.client.find_flavors_by_name(flavor2_name)
self.new_flavor_id = flavors2[0].id
assert_not_equal(self.flavor_id, self.new_flavor_id)
def _wait_for_active(self):
poll_until(lambda: self.client.instances.get(self.id),
lambda instance: instance.status == "ACTIVE",
time_out=(60 * 8))
def _wait_for_new_volume_size(self, new_size):
poll_until(lambda: self.client.instances.get(self.id),
lambda instance: instance.volume['size'] == new_size,
time_out=(60 * 8))
@test
def create_instance(self):
initial = self.client.instances.create(self.name, self.flavor_id,
{'size': 1}, [], [])
self.id = initial.id
self._wait_for_active()
@test(depends_on=[create_instance])
def put_into_shutdown_state(self):
instance = self.client.instances.get(self.id)
self.mgmt_client.management.stop(self.id)
@test(depends_on=[put_into_shutdown_state])
@time_out(60 * 5)
def resize_instance_in_shutdown_state(self):
self.client.instances.resize_instance(self.id, self.new_flavor_id)
self._wait_for_active()
@test(depends_on=[create_instance],
runs_after=[resize_instance_in_shutdown_state])
def put_into_shutdown_state_2(self):
instance = self.client.instances.get(self.id)
self.mgmt_client.management.stop(self.id)
@test(depends_on=[put_into_shutdown_state_2])
@time_out(60 * 5)
def resize_volume_in_shutdown_state(self):
self.client.instances.resize_volume(self.id, 2)
self._wait_for_new_volume_size(2)
@test(depends_on=[create_instance],
runs_after=[resize_volume_in_shutdown_state])
def put_into_shutdown_state_3(self):
instance = self.client.instances.get(self.id)
self.mgmt_client.management.stop(self.id)
@test(depends_on=[create_instance],
runs_after=[put_into_shutdown_state_3])
@time_out(2 * 60)
def delete_instances(self):
instance = self.client.instances.get(self.id)
instance.delete()
while True:
try:
instance = self.client.instances.get(self.id)
assert_equal("SHUTDOWN", instance.status)
except exceptions.NotFound:
break
time.sleep(0.25)

View File

@ -0,0 +1,227 @@
from proboscis.decorators import time_out
from proboscis import after_class
from proboscis import before_class
from proboscis import test
from proboscis.asserts import assert_equal
from proboscis.asserts import assert_false
from proboscis.asserts import assert_is
from proboscis.asserts import assert_is_not
from proboscis.asserts import assert_is_none
from proboscis.asserts import assert_not_equal
from proboscis.asserts import assert_raises
from proboscis.asserts import assert_true
from proboscis.asserts import Check
from proboscis.asserts import fail
import time
from reddwarfclient import exceptions
from reddwarf.tests import util
from reddwarf.tests.util import create_dbaas_client
from reddwarf.tests.util import test_config
from reddwarf.tests.util.users import Requirements
class TestBase(object):
def set_up(self):
"""Create a ton of instances."""
reqs = Requirements(is_admin=False)
self.user = test_config.users.find_user(reqs)
self.dbaas = create_dbaas_client(self.user)
def delete_instances(self):
chunk = 0
while True:
chunk += 1
attempts = 0
instances = self.dbaas.instances.list()
if len(instances) == 0:
break
# Sit around and try to delete this chunk.
while True:
instance_results = []
attempts += 1
deleted_count = 0
for instance in instances:
try:
instance.delete()
result = "[w]"
except exceptions.UnprocessableEntity:
result = "[W]"
except exceptions.NotFound:
result = "[O]"
deleted_count += 1
except Exception:
result = "[X]"
instance_results.append(result)
print("Chunk %d, attempt %d : %s"
% (chunk, attempts, ",".join(instance_results)))
if deleted_count == len(instances):
break
time.sleep(0.2)
def create_instances(self):
self.ids = []
for index in range(self.max):
name = "multi-%03d" % index
result = self.dbaas.instances.create(name, 1,
{'size': 1}, [], [])
self.ids.append(result.id)
# Sort the list of IDs in order, so we can confirm the lists pagination
# returns is also sorted correctly.
self.ids.sort()
@staticmethod
def assert_instances_sorted_by_ids(instances):
# Assert that the strings are always increasing.
last_id = ""
for instance in instances:
assert_true(last_id < instance.id)
def print_list(self, instances):
print("Length = %d" % len(instances))
print(",".join([instance.id for instance in instances]))
def test_pagination(self, requested_limit, requested_marker,
expected_length, expected_marker, expected_last_item):
instances = self.dbaas.instances.list(limit=requested_limit,
marker=requested_marker)
marker = instances.next
self.print_list(instances)
# Better get as many as we asked for.
assert_equal(len(instances), expected_length)
# The last one should be roughly this one in the list.
assert_equal(instances[-1].id, expected_last_item)
# Because limit < count, the marker must be something.
if expected_marker:
assert_is_not(marker, None)
assert_equal(marker, expected_marker)
else:
assert_is_none(marker)
self.assert_instances_sorted_by_ids(instances)
@test(runs_after_groups=["dbaas.guest.shutdown"],
groups=['dbaas.api.instances.pagination'])
class SimpleCreateAndDestroy(TestBase):
"""
It turns out a big part of guaranteeing pagination works is to make sure
we can create a big batch of instances and delete them without problems.
Even in fake mode though its worth it to check this is the case.
"""
max = 5
@before_class
def set_up(self):
"""Create a ton of instances."""
super(SimpleCreateAndDestroy, self).set_up()
self.delete_instances()
@test
def spin_up(self):
self.create_instances()
@after_class(always_run=True)
def tear_down(self):
self.delete_instances()
@test(runs_after_groups=["dbaas.guest.shutdown"],
groups=['dbaas.api.instances.pagination'])
class InstancePagination50(TestBase):
max = 50
@before_class
def set_up(self):
"""Create a ton of instances."""
super(InstancePagination50, self).set_up()
self.delete_instances()
self.create_instances()
@after_class(always_run=True)
def tear_down(self):
"""Tear down all instances."""
self.delete_instances()
@test
def pagination_short(self):
self.test_pagination(requested_limit=10, requested_marker=None,
expected_length=10, expected_marker=self.ids[9],
expected_last_item=self.ids[9])
@test
def pagination_default(self):
self.test_pagination(requested_limit=None, requested_marker=None,
expected_length=20, expected_marker=self.ids[19],
expected_last_item=self.ids[19])
@test
def pagination_full(self):
self.test_pagination(requested_limit=50, requested_marker=None,
expected_length=20, expected_marker=self.ids[19],
expected_last_item=self.ids[19])
@test(runs_after_groups=["dbaas.guest.shutdown"],
groups=['dbaas.api.instances.pagination'])
class InstancePagination20(TestBase):
max = 20
@before_class
def set_up(self):
"""Create a ton of instances."""
super(InstancePagination20, self).set_up()
self.delete_instances()
self.create_instances()
@after_class(always_run=True)
def tear_down(self):
"""Tear down all instances."""
self.delete_instances()
@test
def pagination_short(self):
self.test_pagination(requested_limit=10, requested_marker=None,
expected_length=10, expected_marker=self.ids[9],
expected_last_item=self.ids[9])
@test
def pagination_default(self):
self.test_pagination(requested_limit=None, requested_marker=None,
expected_length=20, expected_marker=None,
expected_last_item=self.ids[19])
@test
def pagination_full(self):
self.test_pagination(requested_limit=20, requested_marker=None,
expected_length=20, expected_marker=None,
expected_last_item=self.ids[19])
@test
def pagination_overkill(self):
self.test_pagination(requested_limit=30, requested_marker=None,
expected_length=20, expected_marker=None,
expected_last_item=self.ids[19])
@test
def pagination_last_half(self):
self.test_pagination(requested_limit=10, requested_marker=self.ids[9],
expected_length=10, expected_marker=None,
expected_last_item=self.ids[19])
@test
def pagination_third_quarter(self):
self.test_pagination(requested_limit=5, requested_marker=self.ids[9],
expected_length=5, expected_marker=self.ids[14],
expected_last_item=self.ids[14])
@test
def pagination_fourth_quarter(self):
self.test_pagination(requested_limit=20, requested_marker=self.ids[14],
expected_length=5, expected_marker=None,
expected_last_item=self.ids[19])

View File

@ -0,0 +1,13 @@
# Copyright 2011 OpenStack LLC
#
# 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.

View File

@ -0,0 +1,219 @@
# Copyright 2011 OpenStack LLC
#
# 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 reddwarfclient import exceptions
from nose.plugins.skip import SkipTest
from proboscis import after_class
from proboscis import before_class
from proboscis import test
from proboscis.asserts import *
from proboscis.decorators import time_out
from reddwarf import tests
from reddwarf.tests.api.instances import instance_info
from reddwarf.tests.util import test_config
from reddwarf.tests.util import create_dbaas_client
from reddwarf.tests.util import poll_until
from reddwarf.tests.util.users import Requirements
from reddwarf.tests.api.instances import existing_instance
GROUP = "dbaas.api.mgmt.accounts"
@test(groups=[tests.DBAAS_API, GROUP, tests.PRE_INSTANCES],
depends_on_groups=["services.initialize"])
class AccountsBeforeInstanceCreation(object):
@before_class
def setUp(self):
self.user = test_config.users.find_user(Requirements(is_admin=True))
self.client = create_dbaas_client(self.user)
@test
def test_invalid_account(self):
raise SkipTest("Don't have a good way to know if accounts are valid.")
assert_raises(exceptions.NotFound, self.client.accounts.show,
"asd#4#@fasdf")
@test
def test_invalid_account_fails(self):
account_info = self.client.accounts.show("badaccount")
assert_not_equal(self.user.tenant_id, account_info.id)
@test
def test_account_zero_instances(self):
account_info = self.client.accounts.show(self.user.tenant_id)
expected_instances = 0 if not existing_instance() else 1
assert_equal(expected_instances, len(account_info.instances))
expected = self.user.tenant_id
if expected is None:
expected = "None"
assert_equal(expected, account_info.id)
@test
def test_list_empty_accounts(self):
accounts_info = self.client.accounts.index()
expected_accounts = 0 if not existing_instance() else 1
assert_equal(expected_accounts, len(accounts_info.accounts))
@test(groups=[tests.INSTANCES, GROUP], depends_on_groups=["dbaas.listing"])
class AccountsAfterInstanceCreation(object):
@before_class
def setUp(self):
self.user = test_config.users.find_user(Requirements(is_admin=True))
self.client = create_dbaas_client(self.user)
@test
def test_account_details_available(self):
if test_config.auth_strategy == "fake":
raise SkipTest("Skipping this as auth is faked anyway.")
account_info = self.client.accounts.show(instance_info.user.tenant_id)
# Now check the results.
expected = instance_info.user.tenant_id
if expected is None:
expected = "None"
print("account_id.id = '%s'" % account_info.id)
print("expected = '%s'" % expected)
assert_equal(account_info.id, expected)
# Instances: Here we know we've only created one instance.
assert_equal(1, len(account_info.instances))
assert_is_not_none(account_info.instances[0]['host'])
# We know the there's only 1 instance
instance = account_info.instances[0]
print("instances in account: %s" % instance)
assert_equal(instance['id'], instance_info.id)
assert_equal(instance['name'], instance_info.name)
assert_equal(instance['status'], "ACTIVE")
assert_is_not_none(instance['host'])
@test
def test_list_accounts(self):
if test_config.auth_strategy == "fake":
raise SkipTest("Skipping this as auth is faked anyway.")
accounts_info = self.client.accounts.index()
assert_equal(1, len(accounts_info.accounts))
account = accounts_info.accounts[0]
assert_equal(1, account['num_instances'])
assert_equal(instance_info.user.tenant_id, account['id'])
@test(groups=[tests.POST_INSTANCES, GROUP],
depends_on_groups=["dbaas.guest.shutdown"])
class AccountsAfterInstanceDeletion(object):
@before_class
def setUp(self):
self.user = test_config.users.find_user(Requirements(is_admin=True))
self.client = create_dbaas_client(self.user)
@test
def test_no_details_empty_account(self):
account_info = self.client.accounts.show(instance_info.user.tenant_id)
assert_equal(0, len(account_info.instances))
@test(groups=["fake.dbaas.api.mgmt.allaccounts"],
depends_on_groups=["services.initialize"])
class AllAccounts(object):
max = 5
def _delete_instances_for_users(self):
for user in self.users:
user_client = create_dbaas_client(user)
while True:
deleted_count = 0
user_instances = user_client.instances.list()
for instance in user_instances:
try:
instance.delete()
except exceptions.NotFound:
deleted_count += 1
except Exception:
print "Failed to delete instance"
if deleted_count == len(user_instances):
break
def _create_instances_for_users(self):
for user in self.users:
user_client = create_dbaas_client(user)
for index in range(self.max):
name = "instance-%s-%03d" % (user.auth_user, index)
user_client.instances.create(name, 1, {'size': 1}, [], [])
@before_class
def setUp(self):
admin_req = Requirements(is_admin=True)
self.admin_user = test_config.users.find_user(admin_req)
self.admin_client = create_dbaas_client(self.admin_user)
user_req = Requirements(is_admin=False)
self.users = test_config.users.find_all_users_who_satisfy(user_req)
self.user_tenant_ids = [user.tenant_id for user in self.users]
self._create_instances_for_users()
@test
def test_list_accounts_with_multiple_users(self):
accounts_info = self.admin_client.accounts.index()
for account in accounts_info.accounts:
assert_true(account['id'] in self.user_tenant_ids)
assert_equal(self.max, account['num_instances'])
@after_class(always_run=True)
@time_out(60)
def tear_down(self):
self._delete_instances_for_users()
@test(groups=["fake.%s.broken" % GROUP],
depends_on_groups=["services.initialize"])
class AccountWithBrokenInstance(object):
@before_class
def setUp(self):
self.user = test_config.users.find_user(Requirements(is_admin=True))
self.client = create_dbaas_client(self.user)
self.name = 'test_SERVER_ERROR'
# Create an instance with a broken compute instance.
self.response = self.client.instances.create(self.name, 1,
{'size': 1}, [])
poll_until(lambda: self.client.instances.get(self.response.id),
lambda instance: instance.status == 'ERROR',
time_out=10)
self.instance = self.client.instances.get(self.response.id)
print "Status: %s" % self.instance.status
msg = "Instance did not drop to error after server prov failure."
assert_equal(self.instance.status, "ERROR", msg)
@test
def no_compute_instance_no_problem(self):
'''Get account by ID shows even instances lacking computes'''
if test_config.auth_strategy == "fake":
raise SkipTest("Skipping this as auth is faked anyway.")
account_info = self.client.accounts.show(self.user.tenant_id)
# All we care about is that accounts.show doesn't 500 on us
# for having a broken instance in the roster.
assert_equal(len(account_info.instances), 1)
instance = account_info.instances[0]
assert_true(isinstance(instance['id'], basestring))
assert_equal(len(instance['id']), 36)
assert_equal(instance['name'], self.name)
assert_equal(instance['status'], "ERROR")
assert_is_none(instance['host'])
@after_class
def tear_down(self):
self.client.instances.delete(self.response.id)

View File

@ -0,0 +1,93 @@
# Copyright 2011 OpenStack LLC
#
# 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 reddwarfclient.exceptions import Unauthorized
from proboscis import before_class
from proboscis import test
from proboscis.asserts import assert_raises
from reddwarf import tests
from reddwarf.tests.util import create_dbaas_client
from reddwarf.tests.util import test_config
from reddwarf.tests.util.users import Requirements
GROUP = "dbaas.api.mgmt.admin"
@test(groups=[tests.DBAAS_API, GROUP, tests.PRE_INSTANCES],
depends_on_groups=["services.initialize"])
class TestAdminRequired(object):
"""
These tests verify that admin privileges are checked
when calling management level functions.
"""
@before_class
def setUp(self):
""" Create the user and client for use in the subsequent tests."""
self.user = test_config.users.find_user(Requirements(is_admin=False))
self.dbaas = create_dbaas_client(self.user)
@test
def test_accounts_show(self):
""" A regular user may not view the details of any account. """
assert_raises(Unauthorized, self.dbaas.accounts.show, 0)
@test
def test_hosts_index(self):
""" A regular user may not view the list of hosts. """
assert_raises(Unauthorized, self.dbaas.hosts.index)
@test
def test_hosts_get(self):
""" A regular user may not view the details of any host. """
assert_raises(Unauthorized, self.dbaas.hosts.get, 0)
@test
def test_mgmt_show(self):
"""
A regular user may not view the management details
of any instance.
"""
assert_raises(Unauthorized, self.dbaas.management.show, 0)
@test
def test_mgmt_root_history(self):
"""
A regular user may not view the root access history of
any instance.
"""
assert_raises(Unauthorized,
self.dbaas.management.root_enabled_history, 0)
@test
def test_mgmt_instance_reboot(self):
""" A regular user may not perform an instance reboot. """
assert_raises(Unauthorized, self.dbaas.management.reboot, 0)
@test
def test_storage_index(self):
""" A regular user may not view the list of storage available. """
assert_raises(Unauthorized, self.dbaas.storage.index)
@test
def test_diagnostics_get(self):
""" A regular user may not view the diagnostics. """
assert_raises(Unauthorized, self.dbaas.diagnostics.get, 0)
@test
def test_hwinfo_get(self):
""" A regular user may not view the hardware info. """
assert_raises(Unauthorized, self.dbaas.hwinfo.get, 0)

View File

@ -0,0 +1,225 @@
# Copyright 2011 OpenStack LLC
#
# 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 reddwarfclient import exceptions
from proboscis import before_class
from proboscis import test
from proboscis.asserts import assert_equal
from proboscis.asserts import assert_raises
from proboscis.asserts import assert_true
from proboscis.check import Check
from reddwarf import tests
from reddwarf.tests.config import CONFIG
from reddwarf.tests.util import create_client
from reddwarf.tests.util import create_dbaas_client
from reddwarf.tests.util.users import Requirements
from reddwarf.tests.util.check import CollectionCheck
from reddwarf.tests.util.check import TypeCheck
from reddwarf.tests.api.instances import CreateInstance
from reddwarf.tests.api.instances import instance_info
from reddwarf.tests.api.instances import GROUP_START
from reddwarf.tests.api.instances import GROUP_TEST
from reddwarf.tests.util import poll_until
GROUP = "dbaas.api.mgmt.instances"
@test(groups=[GROUP])
def mgmt_index_requires_admin_account():
""" Verify that an admin context is required to call this function. """
client = create_client(is_admin=False)
assert_raises(exceptions.Unauthorized, client.management.index)
# These functions check some dictionaries in the returned response.
def flavor_check(flavor):
with CollectionCheck("flavor", flavor) as check:
check.has_element("id", basestring)
check.has_element("links", list)
def guest_status_check(guest_status):
with CollectionCheck("guest_status", guest_status) as check:
check.has_element("state_description", basestring)
def volume_check(volume):
with CollectionCheck("volume", volume) as check:
check.has_element("id", basestring)
check.has_element("size", int)
@test(depends_on_groups=[GROUP_START], groups=[GROUP, GROUP_TEST])
def mgmt_instance_get():
""" Tests the mgmt instances index method. """
reqs = Requirements(is_admin=True)
user = CONFIG.users.find_user(reqs)
client = create_dbaas_client(user)
mgmt = client.management
# Grab the info.id created by the main instance test which is stored in
# a global.
id = instance_info.id
api_instance = mgmt.show(id)
# Print out all fields for extra info if the test fails.
for name in dir(api_instance):
print(str(name) + "=" + str(getattr(api_instance, name)))
with TypeCheck("instance", api_instance) as instance:
instance.has_field('created', basestring)
instance.has_field('deleted', bool)
# If the instance hasn't been deleted, this should be false... but
# lets avoid creating more ordering work.
instance.has_field('deleted_at', (basestring, None))
instance.has_field('flavor', dict, flavor_check)
instance.has_field('guest_status', dict, guest_status_check)
instance.has_field('id', basestring)
instance.has_field('links', list)
instance.has_field('name', basestring)
#instance.has_field('server_status', basestring)
instance.has_field('status', basestring)
instance.has_field('tenant_id', basestring)
instance.has_field('updated', basestring)
# Can be None if no volume is given on this instance.
if CONFIG.values['reddwarf_main_instance_has_volume']:
instance.has_field('volume', dict, volume_check)
else:
instance.has_field('volume', None)
#TODO: Validate additional fields, assert no extra fields exist.
with CollectionCheck("server", api_instance.server) as server:
server.has_element("addresses", dict)
server.has_element("deleted", bool)
server.has_element("deleted_at", (basestring, None))
server.has_element("host", basestring)
server.has_element("id", basestring)
server.has_element("local_id", int)
server.has_element("name", basestring)
server.has_element("status", basestring)
server.has_element("tenant_id", basestring)
with CollectionCheck("volume", api_instance.volume) as volume:
volume.has_element("attachments", list)
volume.has_element("availability_zone", basestring)
volume.has_element("created_at", (basestring, None))
volume.has_element("id", basestring)
volume.has_element("size", int)
volume.has_element("status", basestring)
@test(groups=["fake." + GROUP])
class WhenMgmtInstanceGetIsCalledButServerIsNotReady(object):
@before_class
def set_up(self):
"""Create client for mgmt instance test (2)."""
if not CONFIG.fake_mode:
raise SkipTest("This test only works in fake mode.")
self.client = create_client(is_admin=True)
self.mgmt = self.client.management
# Fake nova will fail a server ending with 'test_SERVER_ERROR'."
response = self.client.instances.create('test_SERVER_ERROR', 1,
{'size': 1}, [])
poll_until(lambda: self.client.instances.get(response.id),
lambda instance: instance.status == 'ERROR',
time_out=10)
self.id = response.id
@test
def mgmt_instance_get(self):
"""Tests the mgmt get call works when the Nova server isn't ready."""
api_instance = self.mgmt.show(self.id)
# Print out all fields for extra info if the test fails.
for name in dir(api_instance):
print(str(name) + "=" + str(getattr(api_instance, name)))
# Print out all fields for extra info if the test fails.
for name in dir(api_instance):
print(str(name) + "=" + str(getattr(api_instance, name)))
with TypeCheck("instance", api_instance) as instance:
instance.has_field('created', basestring)
instance.has_field('deleted', bool)
# If the instance hasn't been deleted, this should be false... but
# lets avoid creating more ordering work.
instance.has_field('deleted_at', (basestring, None))
instance.has_field('flavor', dict, flavor_check)
instance.has_field('guest_status', dict, guest_status_check)
instance.has_field('id', basestring)
instance.has_field('links', list)
instance.has_field('name', basestring)
#instance.has_field('server_status', basestring)
instance.has_field('status', basestring)
instance.has_field('tenant_id', basestring)
instance.has_field('updated', basestring)
# Can be None if no volume is given on this instance.
instance.has_field('server', None)
instance.has_field('volume', None)
#TODO: Validate additional fields, assert no extra fields exist.
@test(depends_on_classes=[CreateInstance], groups=[GROUP])
class MgmtInstancesIndex(object):
""" Tests the mgmt instances index method. """
@before_class
def setUp(self):
"""Create client for mgmt instance test."""
reqs = Requirements(is_admin=True)
self.user = CONFIG.users.find_user(reqs)
self.client = create_dbaas_client(self.user)
@test
def test_mgmt_instance_index_fields_present(self):
"""
Verify that all the expected fields are returned by the index method.
"""
expected_fields = [
'created',
'deleted',
'deleted_at',
'flavor',
'id',
'links',
'name',
'server',
'status',
'task_description',
'tenant_id',
'updated',
'volume',
]
index = self.client.management.index()
for instance in index:
with Check() as check:
for field in expected_fields:
check.true(hasattr(instance, field),
"Index lacks field %s" % field)
@test
def test_mgmt_instance_index_check_filter(self):
"""
Make sure that the deleted= filter works as expected, and no instances
are excluded.
"""
instance_counts = []
for deleted_filter in (True, False):
filtered_index = self.client.management.index(
deleted=deleted_filter)
instance_counts.append(len(filtered_index))
for instance in filtered_index:
# Every instance listed here should have the proper value
# for 'deleted'.
assert_equal(deleted_filter, instance.deleted)
full_index = self.client.management.index()
# There should be no instances that are neither deleted or not-deleted.
assert_equal(len(full_index), sum(instance_counts))

View File

@ -0,0 +1,108 @@
# Copyright 2011 OpenStack LLC
#
# 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 reddwarfclient import exceptions
from nose.plugins.skip import SkipTest
from proboscis import before_class
from proboscis import test
from proboscis.asserts import *
from reddwarf import tests
from reddwarf.tests.api.instances import CheckInstance
from reddwarf.tests.api.instances import instance_info
from reddwarf.tests.util import test_config
from reddwarf.tests.util import create_dbaas_client
from reddwarf.tests.util.users import Requirements
FAKE_MODE = test_config.values['fake_mode']
GROUP = "dbaas.api.mgmt.storage"
@test(groups=[tests.DBAAS_API, GROUP, tests.PRE_INSTANCES],
depends_on_groups=["services.initialize"])
class StorageBeforeInstanceCreation(object):
@before_class
def setUp(self):
self.user = test_config.users.find_user(Requirements(is_admin=True))
self.client = create_dbaas_client(self.user)
@test
def test_storage_on_host(self):
if not FAKE_MODE:
raise SkipTest("Volume driver currently not working.")
storage = self.client.storage.index()
print("storage : %r" % storage)
for device in storage:
assert_true(hasattr(device, 'name'),
"device.name: %r" % device.name)
assert_true(hasattr(device, 'type'),
"device.type: %r" % device.name)
assert_true(hasattr(device, 'used'),
"device.used: %r" % device.used)
assert_true(hasattr(device, 'provision'),
"device.provision: %r" % device.provision)
provision = device.provision
assert_true('available' in provision,
"provision.available: %r" % provision['available'])
assert_true('percent' in provision,
"provision.percent: %r" % provision['percent'])
assert_true('total' in provision,
"provision.total: %r" % provision['total'])
assert_true(hasattr(device, 'capacity'),
"device.capacity: %r" % device.capacity)
capacity = device.capacity
assert_true('available' in capacity,
"capacity.available: %r" % capacity['available'])
assert_true('total' in capacity,
"capacity.total: %r" % capacity['total'])
instance_info.storage = storage
@test(groups=[tests.INSTANCES, GROUP],
depends_on_groups=["dbaas.listing"])
class StorageAfterInstanceCreation(object):
@before_class
def setUp(self):
self.user = test_config.users.find_user(Requirements(is_admin=True))
self.client = create_dbaas_client(self.user)
@test
def test_storage_on_host(self):
if not FAKE_MODE:
raise SkipTest("Volume driver currently not working.")
storage = self.client.storage.index()
print("storage : %r" % storage)
print("instance_info.storage : %r" % instance_info.storage)
expected_attrs = ['name', 'type', 'used', 'provision', 'capacity']
for index, device in enumerate(storage):
CheckInstance(None).attrs_exist(device._info, expected_attrs,
msg="Storage")
assert_equal(device.name, instance_info.storage[index].name)
assert_equal(device.used, instance_info.storage[index].used)
assert_equal(device.type, instance_info.storage[index].type)
provision = instance_info.storage[index].provision
assert_equal(device.provision['available'], provision['available'])
assert_equal(device.provision['percent'], provision['percent'])
assert_equal(device.provision['total'], provision['total'])
capacity = instance_info.storage[index].capacity
assert_equal(device.capacity['available'], capacity['available'])
assert_equal(device.capacity['total'], capacity['total'])

180
reddwarf/tests/api/root.py Normal file
View File

@ -0,0 +1,180 @@
# Copyright 2011 OpenStack LLC
#
# 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 reddwarfclient import exceptions
from nose.plugins.skip import SkipTest
from proboscis import before_class
from proboscis import test
from proboscis.asserts import assert_equal
from proboscis.asserts import assert_false
from proboscis.asserts import assert_not_equal
from proboscis.asserts import assert_raises
from proboscis.asserts import assert_true
from proboscis.asserts import fail
from proboscis.decorators import expect_exception
from proboscis.decorators import time_out
from reddwarf import tests
from reddwarf.tests.api.users import TestUsers
from reddwarf.tests.api.instances import GROUP_START
from reddwarf.tests.api.instances import instance_info
from reddwarf.tests import util
from reddwarf.tests.util import test_config
GROUP = "dbaas.api.root"
def log_in_as_root(root_password):
con = create_mysql_connection(instance_info.get_address(), 'root',
root_password)
return con
@test(depends_on_groups=[GROUP_START],
runs_after=[TestUsers],
groups=[tests.DBAAS_API, GROUP, tests.INSTANCES])
class TestRoot(object):
"""
Test the root operations
"""
root_enabled_timestamp = 'Never'
system_users = ['root', 'debian_sys_maint']
@before_class
def setUp(self):
self.dbaas = util.create_dbaas_client(instance_info.user)
self.dbaas_admin = util.create_dbaas_client(instance_info.admin_user)
def _verify_root_timestamp(self, id):
reh = self.dbaas_admin.management.root_enabled_history(id)
timestamp = reh.enabled
assert_equal(self.root_enabled_timestamp, timestamp)
assert_equal(id, reh.id)
def _root(self):
global root_password
host = "%"
user, password = self.dbaas.root.create(instance_info.id)
assert_equal(200, self.dbaas.last_http_code)
reh = self.dbaas_admin.management.root_enabled_history
self.root_enabled_timestamp = reh(instance_info.id).enabled
@test
def test_root_initially_disabled(self):
"""Test that root is disabled"""
enabled = self.dbaas.root.is_root_enabled(instance_info.id)
assert_equal(200, self.dbaas.last_http_code)
assert_false(enabled, "Root SHOULD NOT be enabled.")
@test
def test_create_user_os_admin_failure(self):
users = []
users.append({"name": "os_admin", "password": "12345"})
assert_raises(exceptions.BadRequest, self.dbaas.users.create,
instance_info.id, users)
@test
def test_delete_user_os_admin_failure(self):
assert_raises(exceptions.BadRequest, self.dbaas.users.delete,
instance_info.id, "os_admin")
@test(depends_on=[test_root_initially_disabled],
enabled=not test_config.values['root_removed_from_instance_api'])
def test_root_initially_disabled_details(self):
"""Use instance details to test that root is disabled."""
instance = self.dbaas.instances.get(instance_info.id)
assert_true(hasattr(instance, 'rootEnabled'),
"Instance has no rootEnabled property.")
assert_false(instance.rootEnabled, "Root SHOULD NOT be enabled.")
assert_equal(self.root_enabled_timestamp, 'Never')
@test(depends_on=[test_root_initially_disabled_details])
def test_root_disabled_in_mgmt_api(self):
"""Verifies in the management api that the timestamp exists"""
self._verify_root_timestamp(instance_info.id)
@test(depends_on=[test_root_initially_disabled_details])
def test_enable_root(self):
self._root()
@test(depends_on=[test_enable_root])
def test_enabled_timestamp(self):
assert_not_equal(self.root_enabled_timestamp, 'Never')
@test(depends_on=[test_enable_root])
def test_root_not_in_users_list(self):
"""
Tests that despite having enabled root, user root doesn't appear
in the users list for the instance.
"""
users = self.dbaas.users.list(instance_info.id)
usernames = [user.name for user in users]
assert_true('root' not in usernames)
@test(depends_on=[test_enable_root])
def test_root_now_enabled(self):
"""Test that root is now enabled."""
enabled = self.dbaas.root.is_root_enabled(instance_info.id)
assert_equal(200, self.dbaas.last_http_code)
assert_true(enabled, "Root SHOULD be enabled.")
@test(depends_on=[test_root_now_enabled],
enabled=not test_config.values['root_removed_from_instance_api'])
def test_root_now_enabled_details(self):
"""Use instance details to test that root is now enabled."""
instance = self.dbaas.instances.get(instance_info.id)
assert_true(hasattr(instance, 'rootEnabled'),
"Instance has no rootEnabled property.")
assert_true(instance.rootEnabled, "Root SHOULD be enabled.")
assert_not_equal(self.root_enabled_timestamp, 'Never')
self._verify_root_timestamp(instance_info.id)
@test(depends_on=[test_root_now_enabled_details])
def test_reset_root(self):
if test_config.values['root_timestamp_disabled']:
raise SkipTest("Enabled timestamp not enabled yet")
old_ts = self.root_enabled_timestamp
self._root()
assert_not_equal(self.root_enabled_timestamp, 'Never')
assert_equal(self.root_enabled_timestamp, old_ts)
@test(depends_on=[test_reset_root])
def test_root_still_enabled(self):
"""Test that after root was reset it's still enabled."""
enabled = self.dbaas.root.is_root_enabled(instance_info.id)
assert_equal(200, self.dbaas.last_http_code)
assert_true(enabled, "Root SHOULD still be enabled.")
@test(depends_on=[test_root_still_enabled],
enabled=not test_config.values['root_removed_from_instance_api'])
def test_root_still_enabled_details(self):
"""Use instance details to test that after root was reset,
it's still enabled."""
instance = self.dbaas.instances.get(instance_info.id)
assert_true(hasattr(instance, 'rootEnabled'),
"Instance has no rootEnabled property.")
assert_true(instance.rootEnabled, "Root SHOULD still be enabled.")
assert_not_equal(self.root_enabled_timestamp, 'Never')
self._verify_root_timestamp(instance_info.id)
@test(depends_on=[test_enable_root])
def test_root_cannot_be_deleted(self):
"""Even if root was enabled, the user root cannot be deleted."""
assert_raises(exceptions.BadRequest, self.dbaas.users.delete,
instance_info.id, "root")

274
reddwarf/tests/api/users.py Normal file
View File

@ -0,0 +1,274 @@
# Copyright 2011 OpenStack LLC
#
# 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 re
from reddwarfclient import exceptions
from proboscis import after_class
from proboscis import before_class
from proboscis import test
from proboscis.asserts import assert_equal
from proboscis.asserts import assert_false
from proboscis.asserts import assert_not_equal
from proboscis.asserts import assert_raises
from proboscis.asserts import assert_true
from proboscis.asserts import fail
from proboscis.decorators import expect_exception
from proboscis.decorators import time_out
from reddwarf import tests
from reddwarf.tests.api.databases import TestDatabases
from reddwarf.tests.api.instances import GROUP_START
from reddwarf.tests.api.instances import instance_info
from reddwarf.tests import util
from reddwarf.tests.util import test_config
GROUP = "dbaas.api.users"
FAKE = test_config.values['fake_mode']
@test(depends_on_groups=[GROUP_START],
groups=[tests.DBAAS_API, GROUP, tests.INSTANCES],
runs_after=[TestDatabases])
class TestUsers(object):
"""
Test the creation and deletion of users
"""
username = "tes!@#tuser"
username_urlencoded = "tes%21%40%23tuser"
password = "testpa$^%ssword"
username1 = "anous*&^er"
username1_urlendcoded = "anous%2A%26%5Eer"
password1 = "anopas*?.sword"
db1 = "firstdb"
db2 = "seconddb"
created_users = [username, username1]
system_users = ['root', 'debian_sys_maint']
@before_class
def setUp(self):
self.dbaas = util.create_dbaas_client(instance_info.user)
self.dbaas_admin = util.create_dbaas_client(instance_info.admin_user)
databases = [{"name": self.db1, "charset": "latin2",
"collate": "latin2_general_ci"},
{"name": self.db2}]
try:
self.dbaas.databases.create(instance_info.id, databases)
except exceptions.BadRequest:
pass # If the db already exists that's OK.
if not FAKE:
time.sleep(5)
@after_class
def tearDown(self):
self.dbaas.databases.delete(instance_info.id, self.db1)
self.dbaas.databases.delete(instance_info.id, self.db2)
@test()
def test_create_users(self):
users = []
users.append({"name": self.username, "password": self.password,
"databases": [{"name": self.db1}]})
users.append({"name": self.username1, "password": self.password1,
"databases": [{"name": self.db1}, {"name": self.db2}]})
self.dbaas.users.create(instance_info.id, users)
assert_equal(202, self.dbaas.last_http_code)
# Do we need this?
if not FAKE:
time.sleep(5)
self.check_database_for_user(self.username, self.password,
[self.db1])
self.check_database_for_user(self.username1, self.password1,
[self.db1, self.db2])
@test(depends_on=[test_create_users])
def test_create_users_list(self):
#tests for users that should be listed
users = self.dbaas.users.list(instance_info.id)
assert_equal(200, self.dbaas.last_http_code)
found = False
for user in self.created_users:
for result in users:
if user == result.name:
found = True
assert_true(found, "User '%s' not found in result" % user)
found = False
@test(depends_on=[test_create_users])
def test_fails_when_creating_user_twice(self):
users = []
users.append({"name": self.username, "password": self.password,
"databases": [{"name": self.db1}]})
users.append({"name": self.username1, "password": self.password1,
"databases": [{"name": self.db1}, {"name": self.db2}]})
assert_raises(exceptions.BadRequest, self.dbaas.users.create,
instance_info.id, users)
assert_equal(400, self.dbaas.last_http_code)
@test(depends_on=[test_create_users_list])
def test_cannot_create_root_user(self):
# Tests that the user root (in Config:ignore_users) cannot be created.
users = [{"name": "root", "password": "12345",
"databases": [{"name": self.db1}]}]
assert_raises(exceptions.BadRequest, self.dbaas.users.create,
instance_info.id, users)
@test(depends_on=[test_create_users_list])
def test_create_users_list_system(self):
#tests for users that should not be listed
users = self.dbaas.users.list(instance_info.id)
assert_equal(200, self.dbaas.last_http_code)
found = False
for user in self.system_users:
found = any(result.name == user for result in users)
msg = "User '%s' SHOULD NOT BE found in result" % user
assert_false(found, msg)
found = False
@test(depends_on=[test_create_users_list],
runs_after=[test_fails_when_creating_user_twice])
def test_delete_users(self):
self.dbaas.users.delete(instance_info.id, self.username_urlencoded)
assert_equal(202, self.dbaas.last_http_code)
self.dbaas.users.delete(instance_info.id, self.username1_urlendcoded)
assert_equal(202, self.dbaas.last_http_code)
if not FAKE:
time.sleep(5)
self._check_connection(self.username, self.password)
self._check_connection(self.username1, self.password1)
def show_databases(self, user, password):
print("Going to connect to %s, %s, %s"
% (instance_info.get_address(), user, password))
with create_mysql_connection(instance_info.get_address(),
user, password) as db:
print(db)
dbs = db.execute("show databases")
return [row['Database'] for row in dbs]
def check_database_for_user(self, user, password, dbs):
if not FAKE:
# Make the real call to the database to check things.
actual_list = self.show_databases(user, password)
for db in dbs:
assert_true(
db in actual_list,
"No match for db %s in dblist. %s :(" % (db, actual_list))
# Confirm via API.
result = self.dbaas.users.list(instance_info.id)
assert_equal(200, self.dbaas.last_http_code)
for item in result:
if item.name == user:
break
else:
fail("User %s not added to collection." % user)
@test
def test_username_too_long(self):
users = []
users.append({"name": "1233asdwer345tyg56", "password": self.password,
"database": self.db1})
assert_raises(exceptions.BadRequest, self.dbaas.users.create,
instance_info.id, users)
assert_equal(400, self.dbaas.last_http_code)
@test
def test_invalid_username(self):
users = []
users.append({"name": "user,", "password": self.password,
"database": self.db1})
assert_raises(exceptions.BadRequest, self.dbaas.users.create,
instance_info.id, users)
assert_equal(400, self.dbaas.last_http_code)
@test(enabled=False)
#TODO(hub_cap): Make this test work once python-routes is updated, if ever.
def test_delete_user_with_period_in_name(self):
"""Attempt to create/destroy a user with a period in its name"""
users = []
username_with_period = "user.name"
users.append({"name": username_with_period, "password": self.password,
"databases": [{"name": self.db1}]})
self.dbaas.users.create(instance_info.id, users)
assert_equal(202, self.dbaas.last_http_code)
if not FAKE:
time.sleep(5)
self.check_database_for_user(username_with_period, self.password,
[self.db1])
self.dbaas.users.delete(instance_info.id, username_with_period)
assert_equal(202, self.dbaas.last_http_code)
@test
def test_invalid_password(self):
users = []
users.append({"name": "anouser", "password": "sdf,;",
"database": self.db1})
assert_raises(exceptions.BadRequest, self.dbaas.users.create,
instance_info.id, users)
assert_equal(400, self.dbaas.last_http_code)
@test
def test_pagination(self):
users = []
users.append({"name": "Jetson", "password": "george",
"databases": [{"name": "Sprockets"}]})
users.append({"name": "Spacely", "password": "cosmo",
"databases": [{"name": "Sprockets"}]})
users.append({"name": "Uniblab", "password": "fired",
"databases": [{"name": "Sprockets"}]})
self.dbaas.users.create(instance_info.id, users)
assert_equal(202, self.dbaas.last_http_code)
if not FAKE:
time.sleep(5)
limit = 2
users = self.dbaas.users.list(instance_info.id, limit=limit)
assert_equal(200, self.dbaas.last_http_code)
marker = users.next
# Better get only as many as we asked for
assert_true(len(users) <= limit)
assert_true(users.next is not None)
assert_equal(marker, users[-1].name)
marker = users.next
# I better get new users if I use the marker I was handed.
users = self.dbaas.users.list(instance_info.id, limit=limit,
marker=marker)
assert_equal(200, self.dbaas.last_http_code)
assert_true(marker not in [user.name for user in users])
# Now fetch again with a larger limit.
users = self.dbaas.users.list(instance_info.id)
assert_equal(200, self.dbaas.last_http_code)
assert_true(users.next is None)
def _check_connection(self, username, password):
if not FAKE:
util.assert_mysql_connection_fails(username, password,
instance_info.get_address())
# Also determine the db is gone via API.
result = self.dbaas.users.list(instance_info.id)
assert_equal(200, self.dbaas.last_http_code)
for item in result:
if item.name == username:
fail("User %s was not deleted." % user)

View File

@ -0,0 +1,89 @@
# Copyright 2011 OpenStack LLC
#
# 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 proboscis import before_class
from proboscis import test
from proboscis.asserts import assert_equal
from proboscis import SkipTest
from reddwarfclient.exceptions import ClientException
from reddwarf import tests
from reddwarf.tests.util import test_config
from reddwarf.tests.util import create_dbaas_client
from reddwarf.tests.util.users import Requirements
GROUP = "dbaas.api.versions"
@test(groups=[tests.DBAAS_API, GROUP, tests.PRE_INSTANCES, 'DBAAS_VERSIONS'],
depends_on_groups=["services.initialize"])
class Versions(object):
"""Test listing all versions and verify the current version"""
@before_class
def setUp(self):
"""Sets up the client."""
user = test_config.users.find_user(Requirements(is_admin=False))
self.client = create_dbaas_client(user)
@test
def test_list_versions_index(self):
versions = self.client.versions.index(test_config.version_url)
assert_equal(1, len(versions))
assert_equal("CURRENT", versions[0].status,
message="Version status: %s" % versions[0].status)
expected_version = test_config.values['reddwarf_version']
assert_equal(expected_version, versions[0].id,
message="Version ID: %s" % versions[0].id)
expected_api_updated = test_config.values['reddwarf_api_updated']
assert_equal(expected_api_updated, versions[0].updated,
message="Version updated: %s" % versions[0].updated)
def _request(self, url, method='GET', response='200'):
resp, body = None, None
full_url = test_config.version_url + url
try:
resp, body = self.client.client.request(full_url, method)
assert_equal(resp.get('status', ''), response)
except ClientException as ce:
assert_equal(str(ce.http_status), response)
return body
@test
def test_no_slash_no_version(self):
body = self._request('')
@test
def test_no_slash_with_version(self):
if test_config.auth_strategy == "fake":
raise SkipTest("Skipping this test since auth is faked.")
body = self._request('/v1.0', response='401')
@test
def test_with_slash_no_version(self):
body = self._request('/')
@test
def test_with_slash_with_version(self):
if test_config.auth_strategy == "fake":
raise SkipTest("Skipping this test since auth is faked.")
body = self._request('/v1.0/', response='401')
@test
def test_request_no_version(self):
body = self._request('/dbaas/instances', response='404')
@test
def test_request_bogus_version(self):
body = self._request('/0.0/', response='404')

173
reddwarf/tests/config.py Normal file
View File

@ -0,0 +1,173 @@
# Copyright (c) 2011 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.
"""Handles configuration options for the tests.
The tests are capable of running in other contexts, such as in a VM or against
a real deployment. Using this configuration ensures we can run them in other
environments if we choose to.
"""
import json
import os
from collections import Mapping
from reddwarf.tests.fakes.common import event_simulator_sleep
#TODO(tim.simpson): I feel like this class already exists somewhere in core
# Python.
class FrozenDict(Mapping):
def __init__(self, original):
self.original = original
def __len__(self):
return self.original.__len__()
def __iter__(self, *args, **kwargs):
return self.original.__iter__(self, *args, **kwargs)
def __getitem__(self, *args, **kwargs):
return self.original.__getitem__(*args, **kwargs)
def __str__(self):
return self.original.__str__()
class TestConfig(object):
"""
Holds test configuration values which can be accessed as attributes
or using the values dictionary.
"""
def __init__(self):
"""
Create TestConfig, and set default values. These will be overwritten by
the "load_from" methods below.
"""
self._loaded_files = []
self._values = {
'clean_slate': os.environ.get("CLEAN_SLATE", "False") == "True",
'fake_mode': os.environ.get("FAKE_MODE", "False") == "True",
'nova_auth_url': "http://localhost:5000/v2.0",
'reddwarf_auth_url': "http://localhost:5000/v2.0/tokens",
'dbaas_url': "http://localhost:8775/v1.0/dbaas",
'version_url': "http://localhost:8775/",
'nova_url': "http://localhost:8774/v1.1",
'instance_create_time': 16 * 60,
'dbaas_image': None,
'mysql_connection_method': {"type": "direct"},
'typical_nova_image_name': None,
'white_box': os.environ.get("WHITE_BOX", "False") == "True",
'test_mgmt': False,
'use_local_ovz': False,
"known_bugs": {},
"in_proc_server": True,
"report_directory": os.environ.get("REPORT_DIRECTORY", None),
"sleep_mode": "simulated",
}
self._frozen_values = FrozenDict(self._values)
self._users = None
self._dawdler = None
@property
def dawdler(self):
"""Equivalent (in theory) to time.sleep.
Calling this in place of sleep allows the tests to run faster in
fake mode.
"""
if not self._dawdler:
if self.sleep_mode == "simulated":
self._dawdler = event_simulator_sleep
else:
self._dawdler = greenthread.sleep
return self._dawdler
def get(self, name, default_value):
return self.values.get(name, default_value)
def get_report(self):
return PrintReporter()
def load_from_line(self, line):
index = line.find("=")
if index >= 0:
key = line[:index]
value = line[index + 1:]
self._values[key] = value
def load_include_files(self, original_file, files):
directory = os.path.dirname(original_file)
for file_sub_path in files:
file_full_path = os.path.join(directory, file_sub_path)
self.load_from_file(file_full_path)
def load_from_file(self, file_path):
if file_path in self._loaded_files:
return
file_contents = open(file_path, "r").read()
try:
contents = json.loads(file_contents)
except Exception as exception:
raise RuntimeError("Error loading conf file \"%s\"." % file_path,
exception)
finally:
self._loaded_files.append(file_path)
if "include-files" in contents:
self.load_include_files(file_path, contents['include-files'])
del contents['include-files']
self._values.update(contents)
def __getattr__(self, name):
if name not in self._values:
raise AttributeError('Configuration value "%s" not found.' % name)
else:
return self._values[name]
def python_cmd_list(self):
"""The start of a command list to use when running Python scripts."""
commands = []
if self.use_venv:
commands.append("%s/tools/with_venv.sh" % self.nova_code_root)
return list
commands.append("python")
return commands
@property
def users(self):
if self._users is None:
from reddwarf.tests.util.users import Users
self._users = Users(self.values['users'])
return self._users
@property
def values(self):
return self._frozen_values
class PrintReporter(object):
def log(self, msg):
print("[REPORT] %s" % msg)
def update(self):
pass # Ignore. This is used in other reporters.
CONFIG = TestConfig()
del TestConfig.__init__

View File

@ -18,9 +18,12 @@
"""Common code to help in faking the models."""
import time
import stubout
from novaclient import exceptions as nova_exceptions
from reddwarf.common import config
CONFIG = config.Config
def authorize(context):
@ -28,19 +31,55 @@ def authorize(context):
raise nova_exceptions.Forbidden(403, "Forbidden")
class EventSimulator(object):
"""Simulates a resource that changes over time.
def get_event_spawer():
if CONFIG.get('fake_mode_events') == "simulated":
return event_simulator
else:
return eventlet_spawner
Has a list of events which execute in real time to change state.
The implementation is very dumb; if you give it two events at once the
last one wins.
"""
pending_events = []
sleep_entrance_count = 0
@staticmethod
def add_event(time_from_now_in_seconds, func):
if time_from_now_in_seconds <= 0:
func()
else:
import eventlet
eventlet.spawn_after(time_from_now_in_seconds, func)
def eventlet_spawner(time_from_now_in_seconds, func):
"""Uses eventlet to spawn events."""
if time_from_now_in_seconds <= 0:
func()
else:
import eventlet
eventlet.spawn_after(time_from_now_in_seconds, func)
def event_simulator(time_from_now_in_seconds, func):
"""Fakes events without doing any actual waiting."""
pending_events.append({"time": time_from_now_in_seconds, "func": func})
def event_simulator_sleep(time_to_sleep):
"""Simulates waiting for an event."""
global sleep_entrance_count
sleep_entrance_count += 1
time_to_sleep = float(time_to_sleep)
global pending_events
while time_to_sleep > 0:
itr_sleep = 0.5
for i in range(len(pending_events)):
event = pending_events[i]
event["time"] = event["time"] - itr_sleep
if event["func"] is not None and event["time"] < 0:
# Call event, but first delete it so this function can be
# reentrant.
func = event["func"]
event["func"] = None
try:
func()
except Exception:
pass # Ignore exceptions, which can potentially occur.
time_to_sleep -= itr_sleep
sleep_entrance_count -= 1
if sleep_entrance_count < 1:
# Clear out old events
pending_events = [event for event in pending_events
if event["func"] is not None]

View File

@ -18,7 +18,7 @@
import logging
import time
from reddwarf.tests.fakes.common import EventSimulator
from reddwarf.tests.fakes.common import get_event_spawer
DB = {}
LOG = logging.getLogger(__name__)
@ -32,6 +32,7 @@ class FakeGuest(object):
self.dbs = {}
self.root_was_enabled = False
self.version = 1
self.event_spawn = get_event_spawer()
def get_hwinfo(self):
return {'mem_total': 524288, 'num_cpus': 1}
@ -122,7 +123,7 @@ class FakeGuest(object):
status.status = ServiceStatuses.RUNNING
status.save()
AgentHeartBeat.create(instance_id=self.id)
EventSimulator.add_event(1.0, update_db)
self.event_spawn(1.0, update_db)
def restart(self):
from reddwarf.instance.models import InstanceServiceStatus

View File

@ -15,13 +15,13 @@
# License for the specific language governing permissions and limitations
# under the License.
import eventlet
import logging
from novaclient.v1_1.client import Client
from novaclient import exceptions as nova_exceptions
import eventlet
import uuid
from reddwarf.tests.fakes.common import authorize
from reddwarf.tests.fakes.common import EventSimulator
from reddwarf.tests.fakes.common import get_event_spawer
from reddwarf.common.utils import poll_until
from reddwarf.common.exception import PollTimeOut
@ -100,7 +100,7 @@ class FakeServer(object):
self.name = name
self.image_id = image_id
self.flavor_ref = flavor_ref
self.events = EventSimulator()
self.event_spawn = get_event_spawer()
self.schedule_status("BUILD", 0.0)
self.volumes = volumes
# This is used by "RdServers". Its easier to compute the
@ -127,9 +127,11 @@ class FakeServer(object):
def reboot(self):
LOG.debug("Rebooting server %s" % (self.id))
self._current_status = "REBOOT"
eventlet.sleep(1)
self._current_status = "ACTIVE"
self.parent.schedule_simulate_running_server(self.id, 1.5)
def set_to_active():
self._current_status = "ACTIVE"
self.parent.schedule_simulate_running_server(self.id, 1.5)
self.event_spawn(1, set_to_active)
def delete(self):
self.schedule_status = []
@ -169,15 +171,15 @@ class FakeServer(object):
else:
flavor = self.parent.flavors.get(new_flavor_id)
self.flavor_ref = flavor.links[0]['href']
self.events.add_event(1, set_to_confirm_mode)
self.event_spawn(1, set_to_confirm_mode)
self.events.add_event(1, set_flavor)
self.event_spawn(1, set_flavor)
def schedule_status(self, new_status, time_from_now):
"""Makes a new status take effect at the given time."""
def set_status():
self._current_status = new_status
self.events.add_event(time_from_now, set_status)
self.event_spawn(time_from_now, set_status)
@property
def status(self):
@ -211,7 +213,7 @@ class FakeServers(object):
self.context = context
self.db = FAKE_SERVERS_DB
self.flavors = flavors
self.events = EventSimulator()
self.event_spawn = get_event_spawer()
def can_see(self, id):
"""Can this FakeServers, with its context, see some resource?"""
@ -286,7 +288,7 @@ class FakeServers(object):
def delete_server():
LOG.info("Simulated event ended, deleting server %s." % id)
del self.db[id]
self.events.add_event(time_from_now, delete_server)
self.event_spawn(time_from_now, delete_server)
def schedule_simulate_running_server(self, id, time_from_now):
def set_server_running():
@ -298,7 +300,7 @@ class FakeServers(object):
status = InstanceServiceStatus.find_by(instance_id=instance.id)
status.status = ServiceStatuses.RUNNING
status.save()
self.events.add_event(time_from_now, set_server_running)
self.event_spawn(time_from_now, set_server_running)
class FakeRdServer(object):
@ -356,7 +358,7 @@ class FakeVolume(object):
self.size = size
self.display_name = display_name
self.display_description = display_description
self.events = EventSimulator()
self.event_spawn = get_event_spawer()
self._current_status = "BUILD"
# For some reason we grab this thing from device then call it mount
# point.
@ -384,7 +386,7 @@ class FakeVolume(object):
"""Makes a new status take effect at the given time."""
def set_status():
self._current_status = new_status
self.events.add_event(time_from_now, set_status)
self.event_spawn(time_from_now, set_status)
def set_attachment(self, server_id):
"""Fake method we've added to set attachments. Idempotent."""
@ -417,7 +419,7 @@ class FakeVolumes(object):
def __init__(self, context):
self.context = context
self.db = FAKE_VOLUMES_DB
self.events = EventSimulator()
self.event_spawn = get_event_spawer()
def can_see(self, id):
"""Can this FakeVolumes, with its context, see some resource?"""
@ -458,7 +460,7 @@ class FakeVolumes(object):
def finish_resize():
volume._current_status = "in-use"
volume.size = new_size
self.events.add_event(1.0, finish_resize)
self.event_spawn(1.0, finish_resize)
class FakeAccount(object):
@ -486,7 +488,7 @@ class FakeAccounts(object):
self.context = context
self.db = FAKE_SERVERS_DB
self.servers = servers
self.events = EventSimulator()
self.event_spawn = get_event_spawer()
def _belongs_to_tenant(self, tenant, id):
server = self.db[id]
@ -494,7 +496,6 @@ class FakeAccounts(object):
def get_instances(self, id):
authorize(self.context)
servers = [v for (k, v) in self.db.items()
if self._belongs_to_tenant(id, v.id)]
return FakeAccount(id, servers)

View File

@ -0,0 +1,254 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2011 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.
"""
:mod:`tests` -- Utility methods for tests.
===================================
.. automodule:: utils
:platform: Unix
:synopsis: Tests for Nova.
.. moduleauthor:: Nirmal Ranganathan <nirmal.ranganathan@rackspace.com>
.. moduleauthor:: Tim Simpson <tim.simpson@rackspace.com>
"""
# This emulates the old way we did things, which was to load the config
# as a module.
# TODO(tim.simpson): Change all references from "test_config" to CONFIG.
from reddwarf.tests.config import CONFIG as test_config
import re
import subprocess
import sys
import time
try:
from eventlet import event
from eventlet import greenthread
EVENT_AVAILABLE = True
except ImportError:
EVENT_AVAILABLE = False
from sqlalchemy import create_engine
from reddwarfclient import exceptions
from proboscis import test
from proboscis.asserts import assert_false
from proboscis.asserts import assert_raises
from proboscis.asserts import assert_true
from proboscis.asserts import Check
from proboscis.asserts import fail
from proboscis.asserts import ASSERTION_ERROR
from proboscis import SkipTest
from reddwarfclient import Dbaas
from reddwarfclient.client import ReddwarfHTTPClient
from reddwarf.tests.util import test_config
from reddwarf.tests.util.client import TestClient as TestClient
from reddwarf.tests.util.users import Requirements
WHITE_BOX = test_config.white_box
def assert_http_code(expected_http_code, func, *args, **kwargs):
try:
rtn_value = func(*args, **kwargs)
assert_equal(
expected_http_code,
200,
"Expected the function to return http code %s but instead got "
"no error (code 200?)." % expected_http_code)
return rtn_value
except exceptions.ClientException as ce:
assert_equal(
expected_http_code,
ce.code,
"Expected the function to return http code %s but instead got "
"code %s." % (expected_http_code, ce.code))
def create_client(*args, **kwargs):
"""
Using the User Requirements as arguments, finds a user and grabs a new
DBAAS client.
"""
reqs = Requirements(*args, **kwargs)
user = test_config.users.find_user(reqs)
return create_dbaas_client(user)
def create_dbaas_client(user):
"""Creates a rich client for the RedDwarf API using the test config."""
auth_strategy = None
kwargs = {
'service_type': 'reddwarf',
'insecure': test_config.values['reddwarf_client_insecure'],
}
def set_optional(kwargs_name, test_conf_name):
value = test_config.values.get(test_conf_name, None)
if value is not None:
kwargs[kwargs_name] = value
force_url = 'override_reddwarf_api_url' in test_config.values
service_url = test_config.get('override_reddwarf_api_url', None)
if user.requirements.is_admin:
service_url = test_config.get('override_admin_reddwarf_api_url',
service_url)
if service_url:
kwargs['service_url'] = service_url
auth_strategy = None
if user.requirements.is_admin:
auth_strategy = test_config.get('admin_auth_strategy',
test_config.auth_strategy)
else:
auth_strategy = test_config.auth_strategy
set_optional('region_name', 'reddwarf_client_region_name')
if test_config.values.get('override_reddwarf_api_url_append_tenant',
False):
kwargs['service_url'] += "/" + user.tenant
if auth_strategy == 'fake':
from reddwarfclient import auth
class FakeAuth(auth.Authenticator):
def authenticate(self):
class FakeCatalog(object):
def __init__(self, auth):
self.auth = auth
def get_public_url(self):
return "%s/%s" % (test_config.dbaas_url,
self.auth.tenant)
def get_token(self):
return self.auth.tenant
return FakeCatalog(self)
auth_strategy = FakeAuth
if auth_strategy:
kwargs['auth_strategy'] = auth_strategy
if not user.requirements.is_admin:
auth_url = test_config.reddwarf_auth_url
else:
auth_url = test_config.values.get('reddwarf_admin_auth_url',
test_config.reddwarf_auth_url)
dbaas = Dbaas(user.auth_user, user.auth_key, tenant=user.tenant,
auth_url=auth_url, **kwargs)
dbaas.authenticate()
with Check() as check:
check.is_not_none(dbaas.client.auth_token, "Auth token not set!")
if not force_url and user.requirements.is_admin:
expected_prefix = test_config.dbaas_url
actual = dbaas.client.service_url
msg = "Dbaas management url was expected to start with %s, but " \
"was %s." % (expected_prefix, actual)
check.true(actual.startswith(expected_prefix), msg)
return TestClient(dbaas)
def create_nova_client(user, service_type=None):
"""Creates a rich client for the Nova API using the test config."""
if test_config.nova_client is None:
raise SkipTest("No nova_client info specified in the Test Config "
"so this test will be skipped.")
from novaclient.v1_1.client import Client
if not service_type:
service_type = test_config.nova_client['nova_service_type']
openstack = Client(user.auth_user, user.auth_key,
user.tenant, test_config.nova_client['auth_url'],
service_type=service_type)
openstack.authenticate()
return TestClient(openstack)
def process(cmd):
process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
result = process.communicate()
return result
def string_in_list(str, substr_list):
"""Returns True if the string appears in the list."""
return any([str.find(x) >= 0 for x in substr_list])
class PollTimeOut(RuntimeError):
message = _("Polling request timed out.")
# Without event let, this just calls time.sleep.
def poll_until(retriever, condition=lambda value: value,
sleep_time=1, time_out=None):
"""Retrieves object until it passes condition, then returns it.
If time_out_limit is passed in, PollTimeOut will be raised once that
amount of time is eclipsed.
"""
start_time = time.time()
def check_timeout():
if time_out is not None and time.time() > start_time + time_out:
raise PollTimeOut
while True:
obj = retriever()
if condition(obj):
return
check_timeout()
time.sleep(sleep_time)
class LocalSqlClient(object):
"""A sqlalchemy wrapper to manage transactions"""
def __init__(self, engine, use_flush=True):
self.engine = engine
self.use_flush = use_flush
def __enter__(self):
self.conn = self.engine.connect()
self.trans = self.conn.begin()
return self.conn
def __exit__(self, type, value, traceback):
if self.trans:
if type is not None: # An error occurred
self.trans.rollback()
else:
if self.use_flush:
self.conn.execute(FLUSH)
self.trans.commit()
self.conn.close()
def execute(self, t, **kwargs):
try:
return self.conn.execute(t, kwargs)
except:
self.trans.rollback()
self.trans = None
raise

View File

@ -0,0 +1,206 @@
# Copyright (c) 2012 OpenStack
# 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.
"""Like asserts, but does not raise an exception until the end of a block."""
import traceback
from proboscis.asserts import ASSERTION_ERROR
from proboscis.asserts import assert_equal
from proboscis.asserts import assert_false
from proboscis.asserts import assert_not_equal
from proboscis.asserts import assert_true
from proboscis.asserts import Check
def get_stack_trace_of_caller(level_up):
"""Gets the stack trace at the point of the caller."""
level_up += 1
st = traceback.extract_stack()
caller_index = len(st) - level_up
if caller_index < 0:
caller_index = 0
new_st = st[0:caller_index]
return new_st
def raise_blame_caller(level_up, ex):
"""Raises an exception, changing the stack trace to point to the caller."""
new_st = get_stack_trace_of_caller(level_up + 2)
raise type(ex), ex, new_st
class Checker(object):
def __init__(self):
self.messages = []
self.odd = True
self.protected = False
def _add_exception(self, _type, value, tb):
"""Takes an exception, and adds it as a string."""
if self.odd:
prefix = "* "
else:
prefix = "- "
start = "Check failure! Traceback:"
middle = prefix.join(traceback.format_list(tb))
end = '\n'.join(traceback.format_exception_only(_type, value))
msg = '\n'.join([start, middle, end])
self.messages.append(msg)
self.odd = not self.odd
def equal(self, *args, **kwargs):
self._run_assertion(assert_equal, *args, **kwargs)
def false(self, *args, **kwargs):
self._run_assertion(assert_false, *args, **kwargs)
def not_equal(self, *args, **kwargs):
_run_assertion(assert_not_equal, *args, **kwargs)
def _run_assertion(self, assert_func, *args, **kwargs):
"""
Runs an assertion method, but catches any failure and adds it as a
string to the messages list.
"""
if self.protected:
try:
assert_func(*args, **kwargs)
except ASSERTION_ERROR as ae:
st = get_stack_trace_of_caller(2)
self._add_exception(ASSERTION_ERROR, ae, st)
else:
assert_func(*args, **kwargs)
def __enter__(self):
self.protected = True
return self
def __exit__(self, _type, value, tb):
self.protected = False
if len(self.messages) == 0:
final_message = None
else:
final_message = '\n'.join(self.messages)
if _type is not None: # An error occurred
if len(self.messages) == 0:
raise _type, value, tb
self._add_exception(_type, value, tb)
if len(self.messages) != 0:
final_message = '\n'.join(self.messages)
raise ASSERTION_ERROR(final_message)
def true(self, *args, **kwargs):
self._run_assertion(assert_true, *args, **kwargs)
class AttrCheck(Check):
"""Class for attr checks, links and other common items."""
def __init__(self):
super(AttrCheck, self).__init__()
def fail(self, msg):
self.true(False, msg)
def attrs_exist(self, list, expected_attrs, msg=None):
# Check these attrs only are returned in create response
for attr in list:
if attr not in expected_attrs:
self.fail("%s should not contain '%s'" % (msg, attr))
def links(self, links):
expected_attrs = ['href', 'rel']
for link in links:
self.attrs_exist(link, expected_attrs, msg="Links")
class CollectionCheck(Check):
"""Checks for elements in a dictionary."""
def __init__(self, name, collection):
self.name = name
self.collection = collection
super(CollectionCheck, self).__init__()
def element_equals(self, key, expected_value):
if key not in self.collection:
message = 'Element "%s.%s" does not exist.' % (self.name, key)
self.fail(message)
else:
value = self.collection[key]
self.equal(value, expected_value)
def has_element(self, key, element_type):
if key not in self.collection:
message = 'Element "%s.%s" does not exist.' % (self.name, key)
self.fail(message)
else:
value = self.collection[key]
match = False
if not isinstance(element_type, tuple):
type_list = [element_type]
else:
type_list = element_type
for possible_type in type_list:
if possible_type is None:
if value is None:
match = True
else:
if isinstance(value, possible_type):
match = True
if not match:
self.fail('Element "%s.%s" does not match any of these '
'expected types: %s' % (self.name, key, type_list))
class TypeCheck(Check):
"""Checks for attributes in an object."""
def __init__(self, name, instance):
self.name = name
self.instance = instance
super(TypeCheck, self).__init__()
def _check_type(value, attribute_type):
if not isinstance(value, attribute_type):
self.fail("%s attribute %s is of type %s (expected %s)."
% (self.name, attribute_name, type(value),
attribute_type))
def has_field(self, attribute_name, attribute_type,
additional_checks=None):
if not hasattr(self.instance, attribute_name):
self.fail("%s missing attribute %s." % (self.name, attribute_name))
else:
value = getattr(self.instance, attribute_name)
match = False
if isinstance(attribute_type, tuple):
type_list = attribute_type
else:
type_list = [attribute_type]
for possible_type in type_list:
if possible_type is None:
if value is None:
match = True
else:
if isinstance(value, possible_type):
match = True
if not match:
self.fail("%s attribute %s is of type %s (expected one of "
"the following: %s)." % (self.name, attribute_name,
type(value), attribute_type))
if match and additional_checks:
additional_checks(value)

View File

@ -0,0 +1,119 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2011 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.
"""
:mod:`tests` -- Utility methods for tests.
===================================
.. automodule:: utils
:platform: Unix
:synopsis: Tests for Nova.
.. moduleauthor:: Nirmal Ranganathan <nirmal.ranganathan@rackspace.com>
.. moduleauthor:: Tim Simpson <tim.simpson@rackspace.com>
"""
from nose.tools import assert_false
from nose.tools import assert_true
from reddwarf.tests.config import CONFIG
def add_report_event_to(home, name):
"""Takes a module, class, etc, and an attribute name to decorate."""
func = getattr(home, name)
def __cb(*args, **kwargs):
# While %s turns a var into a string but in some rare cases explicit
# str() is less likely to raise an exception.
arg_strs = [repr(arg) for arg in args]
arg_strs += ['%s=%s' % (repr(key), repr(value))
for (key, value) in kwargs.items()]
CONFIG.get_reporter().log("[RDC] Calling : %s(%s)..."
% (name, ','.join(arg_strs)))
value = func(*args, **kwargs)
CONFIG.get_reporter.log("[RDC] returned %s." % str(value))
return value
setattr(home, name, __cb)
class TestClient(object):
"""Decorates the rich clients with some extra methods.
These methods are filled with test asserts, meaning if you use this you
get the tests for free.
"""
def __init__(self, real_client):
"""Accepts a normal client."""
self.real_client = real_client
def assert_http_code(self, expected_http_code):
resp, body = self.real_client.client.last_response
assert_equal(resp.status, expected_http_code)
@property
def last_http_code(self):
resp, body = self.real_client.client.last_response
return resp.status
@staticmethod
def find_flavor_self_href(flavor):
self_links = [link for link in flavor.links if link['rel'] == 'self']
assert_true(len(self_links) > 0, "Flavor had no self href!")
flavor_href = self_links[0]['href']
assert_false(flavor_href is None, "Flavor link self href missing.")
return flavor_href
def find_flavors_by(self, condition, flavor_manager=None):
flavor_manager = flavor_manager or self.flavors
flavors = flavor_manager.list()
return [flavor for flavor in flavors if condition(flavor)]
def find_flavors_by_name(self, name, flavor_manager=None):
return self.find_flavors_by(lambda flavor: flavor.name == name,
flavor_manager)
def find_flavors_by_ram(self, ram, flavor_manager=None):
return self.find_flavors_by(lambda flavor: flavor.ram == ram,
flavor_manager)
def find_flavor_and_self_href(self, flavor_id, flavor_manager=None):
"""Given an ID, returns flavor and its self href."""
flavor_manager = flavor_manager or self.flavors
assert_false(flavor_id is None)
flavor = flavor_manager.get(flavor_id)
assert_false(flavor is None)
flavor_href = self.find_flavor_self_href(flavor)
return flavor, flavor_href
def find_image_and_self_href(self, image_id):
"""Given an ID, returns tuple with image and its self href."""
assert_false(image_id is None)
image = self.images.get(image_id)
assert_true(image is not None)
self_links = [link['href'] for link in image.links
if link['rel'] == 'self']
assert_true(len(self_links) > 0,
"Found image with ID %s but it had no self link!" %
str(image_id))
image_href = self_links[0]
assert_false(image_href is None, "Image link self href missing.")
return image, image_href
def __getattr__(self, item):
return getattr(self.real_client, item)

View File

@ -0,0 +1,134 @@
# Copyright (c) 2011 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.
"""Information on users / identities we can hit the services on behalf of.
This code allows tests to grab from a set of users based on the features they
possess instead of specifying exact identities in the test code.
"""
class Requirements(object):
"""Defines requirements a test has of a user."""
def __init__(self, is_admin, services=None):
self.is_admin = is_admin
self.services = services or ["reddwarf"]
# Make sure they're all the same kind of string.
self.services = [str(service) for service in self.services]
def satisfies(self, reqs):
"""True if these requirements conform to the given requirements."""
if reqs.is_admin != self.is_admin:
return False
for service in reqs.services:
if service not in self.services:
return False
return True
def __str__(self):
return "is_admin=%s, services=%s" % (self.is_admin, self.services)
class ServiceUser(object):
"""Represents a user who uses a service.
Importantly, this represents general information, such that a test can be
written to state the general information about a user it needs (for
example, if the user is an admin or not) rather than explicitly list
users.
"""
def __init__(self, auth_user=None, auth_key=None, services=None,
tenant=None, tenant_id=None, requirements=None):
"""Creates info on a user."""
self.auth_user = auth_user
self.auth_key = auth_key
self.tenant = tenant
self.tenant_id = tenant_id
self.requirements = requirements
self.test_count = 0
def __str__(self):
return "{ user_name=%s, tenant_id=%s, reqs=%s, tests=%d }" % (
self.auth_user, self.tenant_id, self.requirements, self.test_count)
self.auth_key = auth_key
self.tenant = tenant
self.tenant_id = tenant_id
self.requirements = requirements
self.test_count = 0
class Users(object):
"""Collection of users with methods to find them via requirements."""
def __init__(self, user_list):
self.users = []
for user_dict in user_list:
reqs = Requirements(**user_dict["requirements"])
user = ServiceUser(auth_user=user_dict["auth_user"],
auth_key=user_dict["auth_key"],
tenant=user_dict["tenant"],
tenant_id=user_dict.get("tenant_id", None),
requirements=reqs)
self.users.append(user)
def find_all_users_who_satisfy(self, requirements, black_list=None):
"""Returns a list of all users who satisfy the given requirements."""
black_list = black_list or []
print("Searching for a user who meets requirements %s in our list..."
% requirements)
print("Users:")
for user in self.users:
print("\t" + str(user))
print("Black list")
for item in black_list:
print("\t" + str(item))
black_list = black_list or []
return (user for user in self.users
if user.auth_user not in black_list and
user.requirements.satisfies(requirements))
def find_user(self, requirements, black_list=None):
"""Finds a user who meets the requirements and has been used least."""
users = self.find_all_users_who_satisfy(requirements, black_list)
try:
user = min(users, key=lambda user: user.test_count)
except ValueError: # Raised when "users" is empty.
raise RuntimeError("The test configuration data lacks a user "
"who meets these requirements: %s"
% requirements)
user.test_count += 1
return user
def _find_user_by_condition(self, condition):
users = (user for user in self.users if condition(user))
try:
user = min(users, key=lambda user: user.test_count)
except ValueError:
raise RuntimeError('Did not find a user with name "%s".' % name)
user.test_count += 1
return user
def find_user_by_name(self, name):
"""Finds a user who meets the requirements and has been used least."""
condition = lambda user: user.auth_user == name
return self._find_user_by_condition(condition)
def find_user_by_tenant_id(self, tenant_id):
condition = lambda user: user.tenant_id == tenant_id
return self._find_user_by_condition(condition)

View File

@ -1,367 +1,138 @@
#!/usr/bin/env python
# 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.
#
# 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.
# Colorizer Code is borrowed from Twisted:
# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""Unittest runner for Nova.
To run all tests
python run_tests.py
To run a single test:
python run_tests.py test_compute:ComputeTestCase.test_run_terminate
To run a single test module:
python run_tests.py test_compute
or
python run_tests.py api.test_wsgi
"""
import gettext
import heapq
import logging
import os
import unittest
import urllib
import sys
import time
gettext.install('reddwarf', unicode=1)
from nose import config
from nose import core
from nose import result
from reddwarf.tests.config import CONFIG
from wsgi_intercept.httplib2_intercept import install as wsgi_install
import proboscis
from eventlet import greenthread
import wsgi_intercept
class _AnsiColorizer(object):
def add_support_for_localization():
"""Adds support for localization in the logging.
If ../nova/__init__.py exists, add ../ to Python search path, so that
it will override what happens to be installed in
/usr/(local/)lib/python...
"""
A colorizer is an object that loosely wraps around a stream, allowing
callers to write text to the stream in a particular color.
path = os.path.join(os.path.abspath(sys.argv[0]), os.pardir, os.pardir)
possible_topdir = os.path.normpath(path)
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
sys.path.insert(0, possible_topdir)
Colorizer classes must implement C{supported()} and C{write(text, color)}.
"""
_colors = dict(black=30, red=31, green=32, yellow=33,
blue=34, magenta=35, cyan=36, white=37)
def __init__(self, stream):
self.stream = stream
def supported(cls, stream=sys.stdout):
"""
A class method that returns True if the current platform supports
coloring terminal output using this method. Returns False otherwise.
"""
if not stream.isatty():
return False # auto color only on TTYs
try:
import curses
except ImportError:
return False
else:
try:
try:
return curses.tigetnum("colors") > 2
except curses.error:
curses.setupterm()
return curses.tigetnum("colors") > 2
except:
raise
# guess false in case of error
return False
supported = classmethod(supported)
def write(self, text, color):
"""
Write the given text to the stream in the given color.
@param text: Text to be written to the stream.
@param color: A string label for a color. e.g. 'red', 'white'.
"""
color = self._colors[color]
self.stream.write('\x1b[%s;1m%s\x1b[0m' % (color, text))
gettext.install('nova', unicode=1)
class _Win32Colorizer(object):
"""
See _AnsiColorizer docstring.
"""
def __init__(self, stream):
from win32console import GetStdHandle, STD_OUT_HANDLE,\
FOREGROUND_RED, FOREGROUND_BLUE, FOREGROUND_GREEN,\
FOREGROUND_INTENSITY
red, green, blue, bold = (FOREGROUND_RED, FOREGROUND_GREEN,
FOREGROUND_BLUE, FOREGROUND_INTENSITY)
self.stream = stream
self.screenBuffer = GetStdHandle(STD_OUT_HANDLE)
self._colors = {
'normal': red | green | blue,
'red': red | bold,
'green': green | bold,
'blue': blue | bold,
'yellow': red | green | bold,
'magenta': red | blue | bold,
'cyan': green | blue | bold,
'white': red | green | blue | bold
}
def supported(cls, stream=sys.stdout):
try:
import win32console
screenBuffer = win32console.GetStdHandle(
win32console.STD_OUT_HANDLE)
except ImportError:
return False
import pywintypes
try:
screenBuffer.SetConsoleTextAttribute(
win32console.FOREGROUND_RED |
win32console.FOREGROUND_GREEN |
win32console.FOREGROUND_BLUE)
except pywintypes.error:
return False
else:
return True
supported = classmethod(supported)
def write(self, text, color):
color = self._colors[color]
self.screenBuffer.SetConsoleTextAttribute(color)
self.stream.write(text)
self.screenBuffer.SetConsoleTextAttribute(self._colors['normal'])
def initialize_reddwarf(config_file):
# The test version of poll_until doesn't utilize LoopingCall.
import optparse
from reddwarf.db import db_api
from reddwarf.common import config as rd_config
from reddwarf.common import wsgi
from reddwarf import version
class _NullColorizer(object):
"""
See _AnsiColorizer docstring.
"""
def __init__(self, stream):
self.stream = stream
def create_options(parser):
parser.add_option('-p', '--port', dest="port", metavar="PORT",
type=int, default=9898,
help="Port the Reddwarf API host listens on. "
"Default: %default")
rd_config.add_common_options(parser)
rd_config.add_log_options(parser)
def supported(cls, stream=sys.stdout):
return True
supported = classmethod(supported)
def usage():
usage = ""
def write(self, text, color):
self.stream.write(text)
oparser = optparse.OptionParser(version="%%prog %s"
% version.version_string(),
usage=usage())
create_options(oparser)
(options, args) = rd_config.parse_options(oparser, cli_args=[config_file])
rd_config.Config.load_paste_config('reddwarf', options, args)
# Modify these values by hand
rd_config.Config.instance['fake_mode_events'] = 'simulated'
rd_config.Config.instance['log_file'] = 'rdtest.log'
conf, app = rd_config.Config.load_paste_app('reddwarf', options, args)
rd_config.setup_logging(options, conf)
return conf, app
def get_elapsed_time_color(elapsed_time):
if elapsed_time > 1.0:
return 'red'
elif elapsed_time > 0.25:
return 'yellow'
else:
return 'green'
def initialize_database(rd_conf):
from reddwarf.db import db_api
from reddwarf.instance import models
from reddwarf.db.sqlalchemy import session
db_api.drop_db(rd_conf) # Destroys the database, if it exists.
db_api.db_sync(rd_conf)
session.configure_db(rd_conf)
# Adds the image for mysql (needed to make most calls work).
models.ServiceImage.create(service_name="mysql", image_id="fake")
db_api.configure_db(rd_conf)
class ReddwarfTestResult(result.TextTestResult):
def __init__(self, *args, **kw):
self.show_elapsed = kw.pop('show_elapsed')
result.TextTestResult.__init__(self, *args, **kw)
self.num_slow_tests = 5
self.slow_tests = [] # this is a fixed-sized heap
self._last_case = None
self.colorizer = None
# NOTE(vish): reset stdout for the terminal check
stdout = sys.stdout
sys.stdout = sys.__stdout__
for colorizer in [_Win32Colorizer, _AnsiColorizer, _NullColorizer]:
if colorizer.supported():
self.colorizer = colorizer(self.stream)
break
sys.stdout = stdout
def initialize_fakes(app):
# Set up WSGI interceptor. This sets up a fake host that responds each
# time httplib tries to communicate to localhost, port 8779.
def wsgi_interceptor(*args, **kwargs):
# NOTE(lorinh): Initialize start_time in case a sqlalchemy-migrate
# error results in it failing to be initialized later. Otherwise,
# _handleElapsedTime will fail, causing the wrong error message to
# be outputted.
self.start_time = time.time()
def call_back(env, start_response):
path_info = env.get('PATH_INFO')
if path_info:
env['PATH_INFO'] = urllib.unquote(path_info)
#print("%s %s" % (args, kwargs))
return app.__call__(env, start_response)
def getDescription(self, test):
return str(test)
return call_back
def _handleElapsedTime(self, test):
self.elapsed_time = time.time() - self.start_time
item = (self.elapsed_time, test)
# Record only the n-slowest tests using heap
if len(self.slow_tests) >= self.num_slow_tests:
heapq.heappushpop(self.slow_tests, item)
else:
heapq.heappush(self.slow_tests, item)
wsgi_intercept.add_wsgi_intercept('localhost', 8779, wsgi_interceptor)
def _writeElapsedTime(self, test):
color = get_elapsed_time_color(self.elapsed_time)
self.colorizer.write(" %.2f" % self.elapsed_time, color)
def _writeResult(self, test, long_result, color, short_result, success):
if self.showAll:
self.colorizer.write(long_result, color)
if self.show_elapsed and success:
self._writeElapsedTime(test)
self.stream.writeln()
elif self.dots:
self.stream.write(short_result)
self.stream.flush()
# NOTE(vish): copied from unittest with edit to add color
def addSuccess(self, test):
unittest.TestResult.addSuccess(self, test)
self._handleElapsedTime(test)
self._writeResult(test, 'OK', 'green', '.', True)
# NOTE(vish): copied from unittest with edit to add color
def addFailure(self, test, err):
unittest.TestResult.addFailure(self, test, err)
self._handleElapsedTime(test)
self._writeResult(test, 'FAIL', 'red', 'F', False)
# NOTE(vish): copied from nose with edit to add color
def addError(self, test, err):
"""Overrides normal addError to add support for
errorClasses. If the exception is a registered class, the
error will be added to the list for that class, not errors.
"""
self._handleElapsedTime(test)
stream = getattr(self, 'stream', None)
ec, ev, tb = err
try:
exc_info = self._exc_info_to_string(err, test)
except TypeError:
# 2.3 compat
exc_info = self._exc_info_to_string(err)
for cls, (storage, label, isfail) in self.errorClasses.items():
if result.isclass(ec) and issubclass(ec, cls):
if isfail:
test.passed = False
storage.append((test, exc_info))
# Might get patched into a streamless result
if stream is not None:
if self.showAll:
message = [label]
detail = result._exception_detail(err[1])
if detail:
message.append(detail)
stream.writeln(": ".join(message))
elif self.dots:
stream.write(label[:1])
return
self.errors.append((test, exc_info))
test.passed = False
if stream is not None:
self._writeResult(test, 'ERROR', 'red', 'E', False)
def startTest(self, test):
unittest.TestResult.startTest(self, test)
self.start_time = time.time()
current_case = test.test.__class__.__name__
if self.showAll:
if current_case != self._last_case:
self.stream.writeln(current_case)
self._last_case = current_case
self.stream.write(
' %s' % str(test.test._testMethodName).ljust(60))
self.stream.flush()
# Finally, engage in some truly evil monkey business. We want
# to change anything which spawns threads with eventlet to instead simply
# put those functions on a queue in memory. Then, we swap out any functions
# which might try to take a nap to instead call functions that go through
# this queue and call the functions that would normally run in seperate
# threads.
import eventlet
from reddwarf.tests.fakes.common import event_simulator_sleep
eventlet.sleep = event_simulator_sleep
greenthread.sleep = event_simulator_sleep
import time
time.sleep = event_simulator_sleep
class ReddwarfTestRunner(core.TextTestRunner):
def __init__(self, *args, **kwargs):
self.show_elapsed = kwargs.pop('show_elapsed')
core.TextTestRunner.__init__(self, *args, **kwargs)
def replace_poll_until():
from reddwarf.common import utils as rd_utils
from reddwarf.tests import util as test_utils
rd_utils.poll_until = test_utils.poll_until
def _makeResult(self):
return ReddwarfTestResult(self.stream,
self.descriptions,
self.verbosity,
self.config,
show_elapsed=self.show_elapsed)
if __name__=="__main__":
wsgi_install()
add_support_for_localization()
replace_poll_until()
# Load Reddwarf config file.
conf, app = initialize_reddwarf("etc/reddwarf/reddwarf.conf.test")
# Initialize sqlite database.
initialize_database(conf)
# Swap out WSGI, httplib, and several sleep functions with test doubles.
initialize_fakes(app)
# Initialize the test configuration.
CONFIG.load_from_file("etc/tests/localhost.test.conf")
def _writeSlowTests(self, result_):
# Pare out 'fast' tests
slow_tests = [item for item in result_.slow_tests
if get_elapsed_time_color(item[0]) != 'green']
if slow_tests:
slow_total_time = sum(item[0] for item in slow_tests)
self.stream.writeln("Slowest %i tests took %.2f secs:"
% (len(slow_tests), slow_total_time))
for elapsed_time, test in sorted(slow_tests, reverse=True):
time_str = "%.2f" % elapsed_time
self.stream.writeln(" %s %s" % (time_str.ljust(10), test))
from reddwarf.tests.api import flavors
from reddwarf.tests.api import versions
from reddwarf.tests.api import instances
from reddwarf.tests.api import instances_actions
from reddwarf.tests.api import instances_delete
from reddwarf.tests.api import instances_mysql_down
from reddwarf.tests.api import databases
from reddwarf.tests.api import root
from reddwarf.tests.api import users
from reddwarf.tests.api.mgmt import accounts
from reddwarf.tests.api.mgmt import admin_required
from reddwarf.tests.api.mgmt import instances
from reddwarf.tests.api.mgmt import storage
def run(self, test):
result_ = core.TextTestRunner.run(self, test)
if self.show_elapsed:
self._writeSlowTests(result_)
return result_
if __name__ == '__main__':
logger = logging.getLogger()
hdlr = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
hdlr.setFormatter(formatter)
logger.addHandler(hdlr)
logger.setLevel(logging.DEBUG)
# If any argument looks like a test name but doesn't have "reddwarf.tests"
# in front of it, automatically add that so we don't have to type as much
show_elapsed = True
argv = []
for x in sys.argv:
if x.startswith('test_'):
argv.append('reddwarf.tests.%s' % x)
elif x.startswith('--hide-elapsed'):
show_elapsed = False
else:
argv.append(x)
testdir = os.path.abspath(os.path.join("reddwarf", "tests"))
c = config.Config(stream=sys.stdout,
env=os.environ,
verbosity=3,
workingDir=testdir,
plugins=core.DefaultPluginManager())
runner = ReddwarfTestRunner(stream=c.stream,
verbosity=c.verbosity,
config=c,
show_elapsed=show_elapsed)
sys.exit(not core.run(config=c, testRunner=runner, argv=argv))
proboscis.TestProgram().run_and_exit()

View File

@ -1,11 +1,11 @@
SQLAlchemy
SQLAlchemy>=0.7.8,<=0.7.9
eventlet
kombu==1.5.1
routes
WebOb
PasteDeploy
paste
sqlalchemy-migrate
sqlalchemy-migrate>=0.7.2
netaddr
factory_boy
httplib2

View File

@ -9,3 +9,6 @@ openstack.nose_plugin
pep8==1.3.3
pylint
webtest
wsgi_intercept
proboscis
python-reddwarfclient

14
tox.ini
View File

@ -3,15 +3,10 @@ envlist = py26,py27,pep8
[testenv]
setenv = VIRTUAL_ENV={envdir}
NOSE_WITH_OPENSTACK=1
NOSE_OPENSTACK_COLOR=1
NOSE_OPENSTACK_RED=0.05
NOSE_OPENSTACK_YELLOW=0.025
NOSE_OPENSTACK_SHOW_ELAPSED=1
NOSE_OPENSTACK_STDOUT=1
deps = -r{toxinidir}/tools/pip-requires
-r{toxinidir}/tools/test-requires
setuptools_git>=0.4
commands = {envpython} run_tests.py {posargs}
[tox:jenkins]
sitepackages = True
@ -24,6 +19,13 @@ commands = pep8 --repeat --show-source --ignore=E125 --exclude=.venv,.tox,dist,d
[testenv:cover]
setenv = NOSE_WITH_COVERAGE=1
# TODO(tim.simpson): For some reason, this must run twice before the coverage
# report is accurate.
commands =
{toxinidir}/.tox/cover/bin/coverage erase
{toxinidir}/.tox/cover/bin/coverage run --timid -p run_tests.py {posargs}
{toxinidir}/.tox/cover/bin/coverage html -d covhtml -i --omit=*tests*,*openstack*,*reddwarfclient*
[testenv:venv]
commands = {posargs}