#    Copyright 2014 Rackspace
#
#    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 functools
import json
import time

from oslo_log import log as logging
from proboscis import asserts
from proboscis.asserts import assert_equal
from proboscis.asserts import assert_true
from proboscis.asserts import Check
from proboscis import before_class
from proboscis import SkipTest
from proboscis import test
from proboscis import TestProgram
import six
from troveclient.compat import client as trove_client
from troveclient.compat import Dbaas
from troveclient.compat import TroveHTTPClient

from trove.tests.config import CONFIG
from trove.tests.examples.client import JsonClient
from trove.tests.examples.client import SnippetWriter

# troveclient.compat.client._logger was changed to LOG in 2.11.0
if hasattr(trove_client, '_logger'):
    trove_client._logger.setLevel(logging.CRITICAL)
elif hasattr(trove_client, 'LOG'):
    trove_client.LOG.setLevel(logging.CRITICAL)

FAKE_INFO = {'m': 30, 's': 0, 'uuid': 'abcdef00-aaaa-aaaa-aaaa-bbbbbbbbbbbb'}
EXAMPLE_BACKUP_ID = "a9832168-7541-4536-b8d9-a8a9b79cf1b4"
EXAMPLE_BACKUP_INCREMENTAL_ID = "2e351a71-dd28-4bcb-a7d6-d36a5b487173"
EXAMPLE_CONFIG_ID = "43a6ea86-e959-4735-9e46-a6a5d4a2d80f"
EXAMPLE_INSTANCE_ID = "44b277eb-39be-4921-be31-3d61b43651d7"
EXAMPLE_INSTANCE_ID_2 = "d5a9db64-7ef7-41c5-8e1e-4013166874bc"
EXAMPLE_CONFIG_SERVER_ID = "271898715"


def get_now():
    from datetime import datetime
    return datetime(2014, 10, 30, hour=12, minute=FAKE_INFO['m'],
                    second=FAKE_INFO['s'])


def get_uuid():
    return FAKE_INFO['uuid']


def set_fake_stuff(uuid=None, minute=None, unique_id=None):
    if uuid:
        FAKE_INFO['uuid'] = uuid
    if minute:
        FAKE_INFO['minute'] = minute
    if unique_id:
        from trove.common.template import SingleInstanceConfigTemplate

        def fake_calc_id(self):
            return unique_id

        SingleInstanceConfigTemplate._calculate_unique_id = fake_calc_id


def monkey_patch_uuid_and_date():
    import uuid
    uuid.uuid4 = get_uuid
    from trove.common import timeutils
    from trove.common import utils
    timeutils.utcnow = get_now
    utils.generate_uuid = get_uuid


@test
def load_config_file():
    global conf
    if CONFIG.get("examples", None) is None:
        fail("Missing 'examples' config in test config.")
    conf = CONFIG.examples
    global normal_user
    normal_user = CONFIG.users.find_user_by_name(conf['normal_user_name'])
    global admin_user
    admin_user = CONFIG.users.find_user_by_name(conf['admin_user_name'])


def create_client_args(user):

    auth_strategy = None

    kwargs = {
        'service_type': 'trove',
        'insecure': CONFIG.values['trove_client_insecure'],
    }

    def set_optional(kwargs_name, test_conf_name):
        value = CONFIG.values.get(test_conf_name, None)
        if value is not None:
            kwargs[kwargs_name] = value

    service_url = CONFIG.get('override_trove_api_url', None)
    if user.requirements.is_admin:
        service_url = CONFIG.get('override_admin_trove_api_url',
                                 service_url)
    if service_url:
        kwargs['service_url'] = service_url

    auth_strategy = None
    if user.requirements.is_admin:
        auth_strategy = CONFIG.get('admin_auth_strategy',
                                   CONFIG.auth_strategy)
    else:
        auth_strategy = CONFIG.auth_strategy
    set_optional('region_name', 'trove_client_region_name')
    if CONFIG.values.get('override_trove_api_url_append_tenant',
                         False):
        kwargs['service_url'] += "/" + user.tenant

    if auth_strategy == 'fake':
        from troveclient.compat 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" % (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 = CONFIG.trove_auth_url
    else:
        auth_url = CONFIG.values.get('trove_admin_auth_url',
                                     CONFIG.trove_auth_url)

    if CONFIG.values.get('trove_client_cls'):
        cls_name = CONFIG.trove_client_cls
        kwargs['client_cls'] = import_class(cls_name)

    kwargs['tenant'] = user.tenant
    kwargs['auth_url'] = auth_url
    return (user.auth_user, user.auth_key), kwargs


def create_client(cls, user):
    args, kwargs = create_client_args(user)
    kwargs['client_cls'] = cls
    client = Dbaas(*args, **kwargs)
    return client


def make_client(user):
    args, kwargs = create_client_args(user)
    kwargs['client_cls'] = JsonClient
    client = Dbaas(*args, **kwargs)
    client.client.name = "auth"
    client.authenticate()
    return client


def write_snippet(get_replace_list, client, name, url, method, status, reason,
                  func, *func_args):
    """
    'name' is the name of the file, while 'url,' 'method,' 'status,'
    and 'reason' are expected values that are asserted against.
    If func_args is present, it is a list of lists, each one of which
    is passed as the *args to the two invocations of "func".
    """
    func_args = func_args or []
    snippet_writer = SnippetWriter(conf, get_replace_list)
    results = []
    client.client.snippet_writer = snippet_writer
    client.client.name = name
    args = func_args
    result = func(client, *args)

    # Now write the snippet (if this happens earlier we can't replace
    # data such as the instance ID).
    client.client.write_snippet()
    with Check() as check:
        check.equal(client.client.old_info['url'], url)
        check.equal(client.client.old_info['method'], method)
        check.equal(client.client.old_info['response_headers'].status,
                    status)
        check.equal(client.client.old_info['response_headers'].reason,
                    reason)
    results.append(result)
    # To prevent this from writing a snippet somewhere else...
    client.client.name = "junk"

    return results


JSON_INDEX = 0


class Example(object):

    @classmethod
    def get_replace_list(cls):
        return []

    def snippet(self, *args, **kwargs):
        return write_snippet(self.get_replace_list, self.client,
                             *args, **kwargs)


@test(depends_on=[load_config_file], enabled=False)
class Versions(Example):

    @before_class
    def setup(self):
        self.client = make_client(normal_user)

    @test
    def get_versions(self):
        self.snippet(
            "versions",
            "", "GET", 200, "OK",
            lambda client: client.versions.index(conf['version_url']))

    @test
    def get_version(self):
        def version_call(client):
            return client.versions.index(conf['version_url'] + "/v1.0/")

        self.snippet("versions", "/v1.0", "GET", 200, "OK", get_version)


@test(depends_on=[load_config_file])
class Flavors(Example):

    @before_class
    def setup(self):
        self.client = make_client(normal_user)

    @test
    def get_flavors(self):
        self.snippet(
            "flavors",
            "/flavors", "GET", 200, "OK",
            lambda client: client.flavors.list())

    @test
    def get_flavor_by_id(self):
        self.snippet(
            "flavors_by_id",
            "/flavors/1", "GET", 200, "OK",
            lambda client: client.flavors.get(1))


@test(depends_on=[load_config_file])
def clean_slate():
    client = create_client(TroveHTTPClient, admin_user)
    client.client.name = "list"
    instances = client.instances.list()
    assert_equal(0, len(instances), "Instance count must be zero.")


@test(depends_on=[clean_slate])
class CreateInstance(Example):

    @before_class
    def setup(self):
        self.client = make_client(normal_user)

    @test
    def post_create_instance(self):
        set_fake_stuff(uuid=EXAMPLE_INSTANCE_ID)

        def create_instance(client, name):
            instance = client.instances.create(
                name, 1,
                volume={'size': 2},
                databases=[
                    {
                        "name": "sampledb",
                        "character_set": "utf8",
                        "collate": "utf8_general_ci"
                    },
                    {
                        "name": "nextround"
                    }
                ],
                users=[
                    {
                        "databases": [{"name": "sampledb"}],
                        "name": "demouser",
                        "password": "demopassword"
                    }
                ])
            assert_equal(instance.status, "BUILD")
            return instance
        self.instances = self.snippet(
            "create_instance",
            "/instances", "POST", 200, "OK",
            create_instance,
            "json_rack_instance")

    def an_instance_is_not_active(self):
        for instance in self.instances:
            instance = self.client.instances.get(instance.id)
            if instance.status not in CONFIG.running_status:
                assert_equal(instance.status, "BUILD")
                return True
        return False

    @test(depends_on=[post_create_instance])
    def wait_for_instances(self):
        while self.an_instance_is_not_active():
            time.sleep(1)
        global json_instance
        json_instance = self.instances[0]


@test(depends_on=[CreateInstance], groups=['uses_instances'])
class Databases(Example):

    @before_class
    def setup(self):
        self.client = make_client(normal_user)

    @test
    def post_create_databases(self):
        self.snippet(
            "create_databases",
            "/instances/%s/databases" % json_instance.id,
            "POST", 202, "Accepted",
            lambda client: client.databases.create(
                json_instance.id,
                databases=[
                    {
                        "name": "testingdb",
                        "character_set": "utf8",
                        "collate": "utf8_general_ci"
                    }, {
                        "name": "anotherdb"
                    }, {
                        "name": "oneMoreDB"
                    }]))

    @test(depends_on=[post_create_databases])
    def get_list_databases(self):
        self.snippet(
            "list_databases",
            "/instances/%s/databases" % json_instance.id,
            "GET", 200, "OK",
            lambda client: client.databases.list(json_instance.id))

    @test(depends_on=[post_create_databases])
    def get_list_databases_limit_two(self):
        results = self.snippet(
            "list_databases_pagination",
            "/instances/%s/databases?limit=1" % json_instance.id,
            "GET", 200, "OK",
            lambda client: client.databases.list(json_instance.id, limit=1))
        assert_equal(1, len(results[JSON_INDEX]))
        assert_equal("anotherdb", results[JSON_INDEX].next)

    @test(depends_on=[post_create_databases],
          runs_after=[get_list_databases, get_list_databases_limit_two])
    def delete_databases(self):
        self.snippet(
            "delete_databases",
            "/instances/%s/databases/testingdb" % json_instance.id,
            "DELETE", 202, "Accepted",
            lambda client:
            client.databases.delete(json_instance.id, 'testingdb'))


@test(depends_on=[CreateInstance], groups=['uses_instances'])
class Users(Example):

    @before_class
    def setup(self):
        self.client = make_client(normal_user)

    @test
    def post_create_users(self):
        self.snippet(
            "create_users",
            "/instances/%s/users" % json_instance.id,
            "POST", 202, "Accepted",
            lambda client: client.users.create(
                json_instance.id,
                [{
                    "name": "dbuser1",
                    "password": "password",
                    "databases": [
                        {
                            "name": "databaseA"
                        }
                    ]
                }, {
                    "name": "dbuser2",
                    "password": "password",
                    "databases": [
                        {
                            "name": "databaseB"
                        },
                        {
                            "name": "databaseC"
                        }
                    ]
                }, {
                    "name": "dbuser3",
                    "password": "password",
                    "databases": [
                        {
                            "name": "databaseD"
                        }
                    ]
                }]))

    @test(depends_on=[post_create_users])
    def get_list_users(self):
        self.snippet(
            "list_users",
            "/instances/%s/users" % json_instance.id,
            "GET", 200, "OK",
            lambda client: client.users.list(json_instance.id))

    @test(depends_on=[post_create_users])
    def get_list_users_limit_two(self):
        self.snippet(
            "list_users_pagination",
            "/instances/%s/users?limit=2" % json_instance.id,
            "GET", 200, "OK",
            lambda client: client.users.list(json_instance.id, limit=2))

    @test(depends_on=[post_create_users],
          runs_after=[get_list_users, get_list_users_limit_two])
    def delete_users(self):
        user_name = "demouser"
        self.snippet(
            "delete_users",
            "/instances/%s/users/%s" % (json_instance.id, user_name),
            "DELETE", 202, "Accepted",
            lambda client: client.users.delete(json_instance.id,
                                               username=user_name))

    @test(depends_on=[post_create_users])
    def modify_user_attributes(self):
        old_user_name = "dbuser1"
        self.snippet(
            "change_user_attributes",
            "/instances/%s/users/%s" % (json_instance.id, old_user_name),
            "PUT", 202, "Accepted",
            lambda client: client.users.update_attributes(
                json_instance.id,
                username=old_user_name,
                newuserattr={
                    "name": "new_username",
                    "password": "new_password"
                }
            )
        )


@test(depends_on=[CreateInstance], groups=['uses_instances'])
class Root(Example):

    @before_class
    def setup(self):
        self.client = make_client(normal_user)

    @test
    def post_enable_root_access(self):
        self.snippet(
            "enable_root_user",
            "/instances/%s/root" % json_instance.id,
            "POST", 200, "OK",
            lambda client: client.root.create(json_instance.id))

    @test(depends_on=[post_enable_root_access])
    def get_check_root_access(self):
        results = self.snippet(
            "check_root_user",
            "/instances/%s/root" % json_instance.id,
            "GET", 200, "OK",
            lambda client: client.root.is_root_enabled(json_instance.id))
        assert_equal(results[JSON_INDEX].rootEnabled, True)

    @test(depends_on=[get_check_root_access])
    def delete_disable_root_access(self):
        self.snippet(
            "disable_root_user",
            "/instances/%s/root" % json_instance.id,
            "DELETE", 204, "No Content",
            lambda client: client.root.delete(json_instance.id))

        # restore root for subsequent tests
        self.post_enable_root_access()


class ActiveMixin(Example):
    """Adds a method to wait for instance status to become ACTIVE."""

    def _wait_for_active(self, *acceptable_states):
        global json_instance
        json_instance = self.client.instances.get(json_instance.id)
        while json_instance.status not in CONFIG.running_status:
            assert_true(
                json_instance.status in acceptable_states,
                "Instance status == %s; expected it to be one of: %s"
                % (json_instance.status, acceptable_states))
            time.sleep(0.1)
            json_instance = self.client.instances.get(json_instance.id)

    def _wait_for_restore_active(self, *acceptable_states):
        for instance in (self.json_restore, ):
            instance = self.client.instances.get(instance.id)
            while instance.status not in CONFIG.running_status:
                assert_true(
                    instance.status in acceptable_states,
                    "Instance status == %s; expected it to be one of: %s"
                    % (instance.status, acceptable_states))
                time.sleep(0.1)
                instance = self.client.instances.get(instance.id)


STATE = {
    "CONFIGURATION": None,
    "DATASTORE_ID": None,
    "DATASTORE_VERSION_ID": None,
}


@test(depends_on=[CreateInstance], groups=['uses_instances'])
class Datastores(Example):

    @before_class
    def setup(self):
        self.client = make_client(normal_user)

    @test
    def get_datastores_list(self):
        self.datastores = self.snippet(
            "datastores_list",
            "/datastores",
            "GET", 200, "OK",
            lambda client: client.datastores.list())
        for result in self.datastores:
            assert_equal(1, len(result))

    @test(depends_on=[get_datastores_list])
    def get_datastore_by_id(self):
        ds, = self.datastores
        mysql_ds = [x for x in ds if x.name == 'mysql']
        if not mysql_ds:
            fail('no mysql datastore found in list')
        ds_id = STATE["DATASTORE_ID"] = mysql_ds[JSON_INDEX].id
        self.datastore = self.snippet(
            "datastore_by_id",
            "/datastores/%s" % ds_id,
            "GET", 200, "OK",
            lambda client: client.datastores.get(ds_id))

    @test(depends_on=[get_datastore_by_id])
    def get_datastore_versions_list(self):
        ds_id = STATE["DATASTORE_ID"]
        self.datastore_versions = self.snippet(
            "datastore_versions_list",
            "/datastores/%s/versions" % ds_id,
            "GET", 200, "OK",
            lambda client: client.datastore_versions.list(ds_id))

    @test(depends_on=[get_datastore_versions_list])
    def get_datastore_version_by_id(self):
        ds_id = STATE["DATASTORE_ID"]
        ds_v_id = STATE["DATASTORE_VERSION_ID"] = (
            self.datastore_versions[JSON_INDEX][0].id
        )
        self.datastore_version = self.snippet(
            "datastore_version_by_id",
            "/datastores/%s/versions/%s" % (ds_id, ds_v_id),
            "GET", 200, "OK",
            lambda client: client.datastore_versions.get(ds_id, ds_v_id))


@test(depends_on=[Datastores], groups=['uses_instances'])
class Configurations(ActiveMixin):

    @before_class
    def setup(self):
        self.client = make_client(normal_user)

    @test
    def get_configuration_parameters_for_datastore_version(self):
        ds_id = STATE["DATASTORE_ID"]
        ds_v_id = STATE["DATASTORE_VERSION_ID"]
        self.snippet(
            "configuration_parameters_for_datastore_version",
            "/datastores/%s/versions/%s/parameters" % (ds_id, ds_v_id),
            "GET", 200, "OK",
            lambda client: client.configuration_parameters.parameters(
                ds_id, ds_v_id
            )
        )

    @test
    def get_configuration_parameters_without_datastore_version(self):
        ds_v_id = STATE["DATASTORE_VERSION_ID"]
        self.params = self.snippet(
            "configuration_parameters_without_datastore_version",
            "/datastores/versions/%s/parameters" % (ds_v_id),
            "GET", 200, "OK",
            lambda client: (
                client.configuration_parameters.parameters_by_version(ds_v_id)
            )
        )
        assert_true(self.params)

    @test(depends_on=[get_configuration_parameters_without_datastore_version])
    def get_configuration_parameter_for_datastore_version(self):
        ds_id = STATE["DATASTORE_ID"]
        ds_v_id = STATE["DATASTORE_VERSION_ID"]
        param = self.params[JSON_INDEX][0].name
        self.snippet(
            "configuration_parameter_for_datastore_version",
            "/datastores/%s/versions/%s/parameters/%s"
            % (ds_id, ds_v_id, param),
            "GET", 200, "OK",
            lambda client: client.configuration_parameters.get_parameter(
                ds_id, ds_v_id, param))

    @test(depends_on=[get_configuration_parameters_without_datastore_version])
    def get_configuration_parameter_without_datastore_version(self):
        ds_v_id = STATE["DATASTORE_VERSION_ID"]
        param = self.params[JSON_INDEX][0].name

        def get_param(client):
            return client.configuration_parameters.get_parameter_by_version(
                ds_v_id,
                param
            )

        self.params = self.snippet(
            "configuration_parameter_without_datastore_version",
            "/datastores/versions/%s/parameters/%s" % (ds_v_id, param),
            "GET", 200, "OK",
            get_param
        )

    @test(depends_on=[get_configuration_parameter_without_datastore_version])
    def create_configuration(self):
        set_fake_stuff(uuid=EXAMPLE_CONFIG_ID)
        ds_id = STATE["DATASTORE_ID"]
        ds_v_id = STATE["DATASTORE_VERSION_ID"]
        values = {
            "connect_timeout": 120,
            "collation_server": "latin1_swedish_ci"
        }

        def create(client):
            config = client.configurations.create(
                'example-configuration-name', json.dumps(values),
                'example description', ds_id, ds_v_id)
            return config

        self.configurations = self.snippet(
            "configuration_create",
            "/configurations",
            "POST", 200, "OK",
            create)
        STATE["CONFIGURATION"] = self.configurations[JSON_INDEX]

    @test(depends_on=[create_configuration])
    def get_configuration(self):
        config = STATE["CONFIGURATION"]
        self.config = self.snippet(
            "configuration_details",
            "/configurations/%s" % config.id,
            "GET", 200, "OK",
            lambda client: client.configurations.get(config.id))

    @test(depends_on=[create_configuration])
    def list_configurations(self):
        self.configs = self.snippet(
            "configuration_list",
            "/configurations",
            "GET", 200, "OK",
            lambda client: client.configurations.list())

    @test(depends_on=[list_configurations, get_configuration])
    def edit_configuration(self):
        config = STATE["CONFIGURATION"]
        values = {
            'connect_timeout': 300
        }
        self.snippet(
            "configuration_edit_parameters",
            "/configurations/%s" % config.id,
            "PATCH", 200, "OK",
            lambda client: client.configurations.edit(
                config.id, json.dumps(values)))

    @test(depends_on=[edit_configuration])
    def update_configuration(self):
        config = STATE["CONFIGURATION"]
        values = {
            'connect_timeout': 150,
            'collation_server': 'utf8_unicode_ci'
        }
        self.snippet(
            "configuration_update_parameters",
            "/configurations/%s" % config.id,
            "PUT", 202, "Accepted",
            lambda client: client.configurations.update(
                config.id, json.dumps(values),
                'example-updated-name', 'example updated description'))

    @test(depends_on=[update_configuration])
    def attach_configuration_to_instance(self):
        config = STATE["CONFIGURATION"]
        self.snippet(
            "configuration_attach_to_instance",
            "/instances/%s" % json_instance.id,
            "PUT", 202, "Accepted",
            lambda client: client.instances.modify(
                json_instance.id,
                config.id
            )
        )

    @test(depends_on=[attach_configuration_to_instance])
    def list_configurations_instances(self):
        config = STATE["CONFIGURATION"]
        self.config_instances = self.snippet(
            "configuration_list_instances",
            "/configurations/%s/instances" % config.id,
            "GET", 200, "OK",
            lambda client: client.configurations.instances(config.id))

    @test(depends_on=[list_configurations_instances])
    def detach_configuration_from_instance(self):
        self.snippet(
            "configuration_detach_from_instance",
            "/instances/%s" % json_instance.id,
            "PUT", 202, "Accepted",
            lambda client: client.instances.modify(
                json_instance.id, ""))

    @test(depends_on=[detach_configuration_from_instance])
    def instance_restart_after_configration_change(self):
        self.client.instances.restart(json_instance.id)
        self._wait_for_active("REBOOT")


@test(depends_on=[CreateInstance], groups=['uses_instances'])
class InstanceList(Example):

    @before_class
    def setup(self):
        self.client = make_client(normal_user)

    @test
    def get_list_instance_index(self):
        results = self.snippet(
            "instances_index",
            "/instances", "GET", 200, "OK",
            lambda client: client.instances.list())
        for result in results:
            assert_equal(1, len(result))

    @test
    def get_instance_details(self):
        results = self.snippet(
            "instance_status_detail",
            "/instances/%s" % json_instance.id,
            "GET", 200, "OK",
            lambda client: client.instances.get(json_instance.id))
        assert_equal(results[JSON_INDEX].id, json_instance.id)

    @test
    def get_default_instance_configuration(self):
        set_fake_stuff(unique_id=EXAMPLE_CONFIG_SERVER_ID)
        self.snippet(
            "get_default_instance_configuration",
            "/instances/%s/configuration" % json_instance.id,
            "GET", 200, "OK",
            lambda client: client.instances.configuration(json_instance.id))

    @test
    def get_list_instance_index_limit_two(self):
        third_instance = self.client.instances.create(
            "The Third Instance", 1, volume={'size': 2})
        third_instance = self.client.instances.get(third_instance.id)
        while third_instance.status not in CONFIG.running_status:
            time.sleep(0.1)
            third_instance = self.client.instances.get(third_instance.id)

        results = self.snippet(
            "instances_index_pagination",
            "/instances?limit=2", "GET", 200, "OK",
            lambda client: client.instances.list(limit=2))
        for result in results:
            assert_equal(2, len(result))

        self.client.instances.delete(third_instance.id)


@test(depends_on=[CreateInstance], groups=['uses_instances'])
class Backups(ActiveMixin):

    @before_class
    def setup(self):
        self.client = make_client(normal_user)

    @test
    def create_backup(self):
        set_fake_stuff(uuid=EXAMPLE_BACKUP_ID)
        results = self.snippet(
            "backup_create", "/backups", "POST", 202, "Accepted",
            lambda client: client.backups.create(
                name='snapshot',
                instance=json_instance.id,
                description="My Backup"
            )
        )
        self._wait_for_active("BACKUP")
        assert_equal(len(results), 1)
        self.json_backup = results[JSON_INDEX]

    @test(depends_on=[create_backup])
    def create_incremental_backup(self):
        set_fake_stuff(uuid=EXAMPLE_BACKUP_INCREMENTAL_ID)
        results = self.snippet(
            "backup_create_incremental", "/backups", "POST", 202, "Accepted",
            lambda client: client.backups.create(
                name='Incremental Snapshot',
                instance=json_instance.id,
                parent_id=EXAMPLE_BACKUP_ID,
                description="My Incremental Backup"
            )
        )

        self._wait_for_active("BACKUP")
        assert_equal(len(results), 1)
        self.json_backup2 = results[JSON_INDEX]

    @test(depends_on=[create_incremental_backup])
    def get_backup(self):
        results = self.snippet(
            "backup_get",
            "/backups/%s" % self.json_backup.id,
            "GET", 200, "OK",
            lambda client: client.backups.get(self.json_backup.id))
        assert_equal(len(results), 1)

    @test(depends_on=[create_incremental_backup])
    def get_backups_for_instance(self):
        results = self.snippet(
            "backups_by_instance",
            "/instances/%s/backups" % json_instance.id,
            "GET", 200, "OK",
            lambda client: client.instances.backups(json_instance.id))
        assert_equal(len(results), 1)

    @test(depends_on=[create_incremental_backup])
    def list_backups(self):
        results = self.snippet(
            "backup_list",
            "/backups", "GET", 200, "OK",
            lambda client: client.backups.list())
        assert_equal(len(results), 1)

    @test(depends_on=[create_backup])
    def restore(self):
        set_fake_stuff(uuid=EXAMPLE_INSTANCE_ID_2)

        def create_instance(client, name, backup):
            instance = client.instances.create(
                name, 1,
                volume={'size': 2},
                restorePoint={'backupRef': backup})
            assert_equal(instance.status, "BUILD")
            return instance
        results = self.snippet(
            "backup_restore",
            "/instances", "POST", 200, "OK",
            lambda client: create_instance(
                client, "backup_instance", self.json_backup.id))
        assert_equal(len(results), 1)
        self.json_restore = results[JSON_INDEX]
        self._wait_for_restore_active("BUILD")
        self.json_restore = self.client.instances.get(self.json_restore.id)
        asserts.assert_true(self.json_restore.status in CONFIG.running_status)

    @test(depends_on=[restore])
    def delete_restores(self):
        self.snippet(
            "restore_delete",
            "/instances/%s" % self.json_restore.id,
            "DELETE", 202, "Accepted",
            lambda client: client.instances.delete(self.json_restore.id))
        self.json_restore = self.client.instances.get(self.json_restore.id)
        assert_equal(self.json_restore.status, "SHUTDOWN")

    @test(depends_on=[create_backup],
          runs_after=[get_backup, list_backups, restore,
                      get_backups_for_instance])
    def delete_backup(self):
        results = self.snippet(
            "backup_delete",
            "/backups/%s" % self.json_backup.id,
            "DELETE", 202, "Accepted",
            lambda client: client.backups.delete(self.json_backup.id))
        assert_equal(len(results), 1)


@test(depends_on=[CreateInstance], groups=['uses_instances'])
class Actions(ActiveMixin):

    @before_class
    def setup(self):
        self.client = make_client(normal_user)

    @test
    def instance_restart(self):
        self.snippet(
            "instance_restart",
            "/instances/%s/action" % json_instance.id,
            "POST", 202, "Accepted",
            lambda client: client.instances.restart(json_instance.id))
        self._wait_for_active("REBOOT")

    @test
    def instance_resize_volume(self):
        self.snippet(
            "instance_resize_volume",
            "/instances/%s/action" % json_instance.id,
            "POST", 202, "Accepted",
            lambda client: client.instances.resize_volume(json_instance.id, 4))
        self._wait_for_active("RESIZE")
        assert_equal(json_instance.volume['size'], 4)

    @test
    def instance_resize_flavor(self):
        self.snippet(
            "instance_resize_flavor",
            ("/instances/%s/action" % json_instance.id),
            "POST", 202, "Accepted",
            lambda client: client.instances.resize_instance(
                json_instance.id, 3))
        self._wait_for_active("RESIZE")
        # TODO(imsplitbit): remove coercion when troveclient fixes are in
        assert_equal(int(json_instance.flavor['id']), 3)


@test(depends_on=[CreateInstance], groups=['uses_instances', "MgmtHosts"])
class MgmtHosts(Example):

    @before_class
    def setup(self):
        self.client = make_client(admin_user)

    @test
    def mgmt_list_hosts(self):
        results = self.snippet(
            "mgmt_list_hosts",
            "/mgmt/hosts", "GET", 200, "OK",
            lambda client: client.mgmt.hosts.index())

        with Check() as check:
            for hosts in results:
                check.equal(2, len(hosts))
                check.true("fake_host_1" == hosts[0].name
                           or "fake_host_1" == hosts[1].name)
                check.true("fake_host_2" == hosts[0].name
                           or "fake_host_2" == hosts[1].name)
                check.true(1 == results[0][1].instanceCount
                           or 1 == results[0][0].instanceCount)

    @test
    def mgmt_get_host_detail(self):
        results = self.snippet(
            "mgmt_get_host_detail",
            "/mgmt/hosts/fake_host_1", "GET", 200, "OK",
            lambda client: client.mgmt.hosts.get("fake_host_1"))
        with Check() as check:
            for host in results:
                check.equal(results[0].name, "fake_host_1")
                # XML entries won't come back as these types. :(
                check.true(isinstance(results[0].percentUsed, int)),
                check.true(isinstance(results[0].totalRAM, int)),
                check.true(isinstance(results[0].usedRAM, int)),
        with Check() as check:
            for host in results:
                check.equal(1, len(host.instances))
                for instance in host.instances:
                    check.equal(instance['status'], 'HEALTHY')
                    check.true(isinstance(instance['name'], six.string_types))
                    check.true(isinstance(instance['id'], six.string_types))
                    check.true(isinstance(instance['server_id'],
                               six.string_types))
                    check.true(isinstance(instance['tenant_id'],
                               six.string_types))

    @test
    def mgmt_host_update_all(self):
        raise SkipTest("This isn't working... :(")
        self.snippet(
            "mgmt_host_update",
            "/mgmt/hosts/fake_host_1/instances/action",
            "POST", 202, "Accepted",
            lambda client: client.mgmt.hosts.update_all("fake_host_1"))


@test(depends_on=[CreateInstance], groups=['uses_instances'])
class MgmtStorage(Example):

    @before_class
    def setup(self):
        self.client = make_client(admin_user)

    @test
    def mgmt_get_storage(self):
        results = self.snippet(
            "mgmt_get_storage",
            "/mgmt/storage", "GET", 200, "OK",
            lambda client: client.mgmt.storage.index())
        for index, devices in enumerate(results):
            with Check() as check:
                check.equal(1, len(devices))
                device = devices[0]
                check.equal(int(device.capacity['available']), 90)
                check.equal(int(device.capacity['total']), 100)
                check.equal(device.name, "fake_storage")
                check.equal(int(device.provision['available']), 40)
                check.equal(int(device.provision['percent']), 10)
                check.equal(int(device.provision['total']), 50)
                check.equal(device.type, "test_type")
                check.equal(int(device.used), 10)
                if index == JSON_INDEX:
                    check.true(isinstance(device.capacity['available'], int))
                    check.true(isinstance(device.capacity['total'], int))
                    check.true(isinstance(device.provision['available'], int))
                    check.true(isinstance(device.provision['percent'], int))
                    check.true(isinstance(device.provision['total'], int))
                    check.true(isinstance(device.used, int))


@test(depends_on=[CreateInstance], groups=['uses_instances'])
class MgmtAccount(Example):

    @before_class
    def setup(self):
        self.client = make_client(admin_user)

    @test
    def mgmt_get_account_details(self):
        results = self.snippet(
            "mgmt_get_account_details",
            "/mgmt/accounts/%s" % conf['normal_user_tenant'],
            "GET", 200, "OK",
            lambda client: client.mgmt.accounts.show(
                conf['normal_user_tenant'], ))
        with Check() as check:
            for account_info in results:
                check.equal(conf['normal_user_tenant'], account_info.id)

    @test
    def mgmt_get_account_list(self):
        results = self.snippet(
            "mgmt_list_accounts",
            "/mgmt/accounts", "GET", 200, "OK",
            lambda client: client.mgmt.accounts.index())
        matches = {conf['normal_user_tenant']: 2,
                   conf['admin_user_tenant']: 0}
        for index, result in enumerate(results):
            for account in result.accounts:
                if account['id'] not in matches:
                    fail("Did not expect this account ID: %s" % account['id'])
                expected_count = matches[account['id']]
                if index == JSON_INDEX:
                    assert_equal(2, expected_count)
                else:
                    assert_equal(2, expected_count)


def for_both(func):
    @functools.wraps(func)
    def both(self):
        for result in self.results:
            func(self, result)
    return both


@test(depends_on=[CreateInstance], groups=['uses_instances'])
class MgmtInstance(Example):

    @before_class
    def mgmt_get_instance_details(self):
        self.client = make_client(admin_user)
        self.results = self.snippet(
            "mgmt_get_instance_details",
            ("/mgmt/instances/%s" % json_instance.id),
            "GET", 200, "OK",
            lambda client: client.mgmt.instances.show(json_instance.id))

    @test
    @for_both
    def created(self, result):
        assert_true(isinstance(result.created, six.string_types))

    @test
    def deleted(self):
        assert_equal(self.results[JSON_INDEX].deleted, False)

    @test
    @for_both
    def flavor(self, result):
        # TODO(imsplitbit): remove the coercion when python-troveclient fixes
        # land in the public.
        assert_true(
            int(result.flavor['id']) == 1 or int(result.flavor['id']) == 3)
        assert_equal(len(result.flavor['links']), 2)

    @test
    @for_both
    def guest_status(self, result):
        assert_equal(result.guest_status['state_description'], 'running')

    @test(enabled=False)
    @for_both
    def host(self, result):
        assert_equal(result.host, 'fake_host_1')

    @test
    def id(self):
        assert_equal(self.results[JSON_INDEX].id, json_instance.id)

    @test
    @for_both
    def links(self, result):
        assert_true(isinstance(result.links, list))
        for link in result.links:
            assert_true(isinstance(link, dict))
            assert_true(isinstance(link['href'], six.string_types))
            assert_true(isinstance(link['rel'], six.string_types))

    @test
    def local_id(self):
        assert_true(isinstance(self.results[JSON_INDEX].server['local_id'],
                    int))

    @test
    @for_both
    def name(self, result):
        assert_true(isinstance(result.name, six.string_types))

    @test
    @for_both
    def server_id(self, result):
        assert_true(isinstance(result.server['id'], six.string_types))

    @test
    @for_both
    def status(self, result):
        assert_equal("ACTIVE", result.status)

    @test
    @for_both
    def task_description(self, result):
        assert_equal(result.task_description, "No tasks for the instance.")

    @test
    @for_both
    def tenant_id(self, result):
        assert_equal(result.tenant_id, conf['normal_user_tenant'])

    @test
    @for_both
    def updated(self, result):
        assert_true(isinstance(result.updated, six.string_types))

    @test
    @for_both
    def volume(self, result):
        assert_true(isinstance(result.volume, dict))
        assert_true('id' in result.volume)
        assert_true('size' in result.volume)


@test(depends_on=[CreateInstance], groups=['uses_instances'])
class MgmtInstanceIndex(Example):

    @before_class
    def setup(self):
        self.client = make_client(admin_user)

    @test
    def mgmt_instance_index(self, deleted=False):
        self.snippet(
            "mgmt_instance_index",
            "/mgmt/instances?deleted=false", "GET", 200, "OK",
            lambda client: client.mgmt.instances.index(deleted=False))


@test(depends_on=[CreateInstance], groups=['uses_instances'])
class MgmtInstanceDiagnostics(Example):

    @before_class
    def setup(self):
        self.client = make_client(admin_user)

    @test
    def mgmt_get_instance_diagnostics(self):
        self.snippet(
            "mgmt_instance_diagnostics",
            ("/mgmt/instances/%s/diagnostics" % json_instance.id),
            "GET", 200, "OK",
            lambda client: client.diagnostics.get(json_instance.id))


@test(depends_on=[CreateInstance])
class MgmtInstanceRoot(Example):

    @before_class
    def setup(self):
        self.client = make_client(admin_user)

    @test
    def mgmt_get_root_details(self):
        self.snippet(
            "mgmt_get_root_details",
            ("/mgmt/instances/%s/root" % json_instance.id),
            "GET", 200, "OK",
            lambda client: client.mgmt.instances.root_enabled_history(
                json_instance.id)
        )


@test(depends_on=[CreateInstance], enabled=False)
class MgmtInstanceHWInfo(Example):

    @before_class
    def setup(self):
        self.client = make_client(admin_user)

    @test
    def mgmt_get_hw_info(self):
        self.snippet(
            "mgmt_get_hw_info",
            ("/mgmt/instances/%s/hwinfo" % json_instance.id),
            "GET", 200, "OK",
            lambda client, id: client.hw_info.get(id),
            ([json_instance.id], ))


@test(depends_on=[CreateInstance], groups=['uses_instances'])
class MgmtInstanceReboot(Example):

    @before_class
    def setup(self):
        self.client = make_client(admin_user)

    @test
    def mgmt_instance_reboot(self):
        self.snippet(
            "instance_reboot",
            ("/mgmt/instances/%s/action" % json_instance.id),
            "POST", 202, "Accepted",
            lambda client: client.mgmt.instances.reboot(json_instance.id))


@test(depends_on=[CreateInstance],
      groups=['uses_instances'], enabled=False)
class MgmtInstanceGuestUpdate(Example):

    @before_class
    def setup(self):
        self.client = make_client(admin_user)

    @test
    def mgmt_instance_guest_update(self):
        self.snippet(
            "guest_update",
            ("/mgmt/instances/%s/action" % json_instance.id),
            "POST", 202, "Accepted",
            lambda client: client.mgmt.instances.update(json_instance.id))


@test(depends_on=[CreateInstance], runs_after_groups=['uses_instances'])
class ZzzDeleteInstance(Example):

    @before_class
    def setup(self):
        self.client = make_client(normal_user)

    @test
    def zzz_delete_instance(self):
        global json_instance
        self.snippet(
            "delete_instance",
            "/instances/%s" % json_instance.id,
            "DELETE", 202, "Accepted",
            lambda client: client.instances.delete(json_instance.id))
        json_instance = self.client.instances.get(json_instance.id)
        assert_equal(json_instance.status, "SHUTDOWN")

    @test(depends_on=[zzz_delete_instance])
    def delete_configuration(self):
        config = STATE["CONFIGURATION"]
        self.configs = self.snippet(
            "configuration_delete",
            ("/configurations/%s" % config.id),
            "DELETE", 202, "Accepted",
            lambda client: client.configurations.delete(config.id))


if __name__ == "__main__":
    CONFIG.load_from_file("etc/tests/localhost.test.conf")
    TestProgram().run_and_exit()
