# Copyright 2013 Hewlett-Packard Development Company, L.P.
# All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.
#
import uuid

from mock import MagicMock, patch, ANY
from novaclient.client import Client
from novaclient.v2.flavors import FlavorManager, Flavor
from novaclient.v2.servers import Server, ServerManager
from oslo_config import cfg
from testtools.matchers import Equals, Is, Not

from trove.backup.models import Backup
from trove.common import exception
from trove.common import instance as rd_instance
from trove.common import remote
from trove.datastore import models as datastore_models
import trove.extensions.mgmt.instances.models as mgmtmodels
from trove.guestagent.api import API
from trove.instance.models import DBInstance
from trove.instance.models import InstanceServiceStatus
from trove.instance.tasks import InstanceTasks
from trove import rpc
from trove.tests.unittests import trove_testtools
from trove.tests.unittests.util import util

CONF = cfg.CONF


class MockMgmtInstanceTest(trove_testtools.TestCase):

    @classmethod
    def setUpClass(cls):
        util.init_db()
        cls.version_id = str(uuid.uuid4())
        cls.datastore = datastore_models.DBDatastore.create(
            id=str(uuid.uuid4()),
            name='mysql' + str(uuid.uuid4()),
            default_version_id=cls.version_id
        )
        cls.version = datastore_models.DBDatastoreVersion.create(
            id=cls.version_id,
            datastore_id=cls.datastore.id,
            name='5.5' + str(uuid.uuid4()),
            manager='mysql',
            image_id=str(uuid.uuid4()),
            active=1,
            packages="mysql-server-5.5"
        )
        super(MockMgmtInstanceTest, cls).setUpClass()

    @classmethod
    def tearDownClass(cls):
        cls.version.delete()
        cls.datastore.delete()
        super(MockMgmtInstanceTest, cls).tearDownClass()

    def setUp(self):
        self.context = trove_testtools.TroveTestContext(self)
        self.context.auth_token = 'some_secret_password'
        self.client = MagicMock(spec=Client)
        self.server_mgr = MagicMock(spec=ServerManager)
        self.client.servers = self.server_mgr
        self.flavor_mgr = MagicMock(spec=FlavorManager)
        self.client.flavors = self.flavor_mgr
        self.admin_client_patch = patch.object(
            remote, 'create_admin_nova_client', return_value=self.client)
        self.addCleanup(self.admin_client_patch.stop)
        self.admin_client_patch.start()
        CONF.set_override('host', '127.0.0.1')
        CONF.set_override('exists_notification_interval', 1)
        CONF.set_override('notification_service_id', {'mysql': '123'})

        super(MockMgmtInstanceTest, self).setUp()

    def do_cleanup(self, instance, status):
        instance.delete()
        status.delete()

    def build_db_instance(self, status, task_status=InstanceTasks.NONE):
        instance = DBInstance(InstanceTasks.NONE,
                              name='test_name',
                              id=str(uuid.uuid4()),
                              flavor_id='flavor_1',
                              datastore_version_id=self.version.id,
                              compute_instance_id='compute_id_1',
                              server_id='server_id_1',
                              tenant_id='tenant_id_1',
                              server_status=rd_instance.ServiceStatuses.
                              BUILDING.api_status,
                              deleted=False)
        instance.save()
        service_status = InstanceServiceStatus(
            rd_instance.ServiceStatuses.RUNNING,
            id=str(uuid.uuid4()),
            instance_id=instance.id,
        )
        service_status.save()
        instance.set_task_status(task_status)
        instance.server_status = status
        instance.save()
        return instance, service_status


class TestNotificationTransformer(MockMgmtInstanceTest):

    @classmethod
    def setUpClass(cls):
        super(TestNotificationTransformer, cls).setUpClass()

    @patch('trove.instance.models.LOG')
    def test_transformer(self, mock_logging):
        status = rd_instance.ServiceStatuses.BUILDING.api_status
        instance, service_status = self.build_db_instance(
            status, InstanceTasks.BUILDING)
        payloads = mgmtmodels.NotificationTransformer(
            context=self.context)()
        self.assertIsNotNone(payloads)
        payload = payloads[0]
        self.assertThat(payload['audit_period_beginning'],
                        Not(Is(None)))
        self.assertThat(payload['audit_period_ending'], Not(Is(None)))
        self.assertIn(status.lower(), [db['state'] for db in payloads])
        self.addCleanup(self.do_cleanup, instance, service_status)

    def test_get_service_id(self):
        id_map = {
            'mysql': '123',
            'percona': 'abc'
        }
        transformer = mgmtmodels.NotificationTransformer(context=self.context)
        self.assertThat(transformer._get_service_id('mysql', id_map),
                        Equals('123'))

    @patch('trove.extensions.mgmt.instances.models.LOG')
    def test_get_service_id_unknown(self, mock_logging):
        id_map = {
            'mysql': '123',
            'percona': 'abc'
        }
        transformer = mgmtmodels.NotificationTransformer(context=self.context)
        self.assertThat(transformer._get_service_id('m0ng0', id_map),
                        Equals('unknown-service-id-error'))


class TestNovaNotificationTransformer(MockMgmtInstanceTest):

    @classmethod
    def setUpClass(cls):
        super(TestNovaNotificationTransformer, cls).setUpClass()

    def test_transformer_cache(self):
        flavor = MagicMock(spec=Flavor)
        flavor.name = 'db.small'
        with patch.object(self.flavor_mgr, 'get', return_value=flavor):
            transformer = mgmtmodels.NovaNotificationTransformer(
                context=self.context)
            transformer2 = mgmtmodels.NovaNotificationTransformer(
                context=self.context)
            self.assertThat(transformer._flavor_cache,
                            Not(Is(transformer2._flavor_cache)))

    def test_lookup_flavor(self):
        flavor = MagicMock(spec=Flavor)
        flavor.name = 'flav_1'
        transformer = mgmtmodels.NovaNotificationTransformer(
            context=self.context)
        with patch.object(self.flavor_mgr, 'get', side_effect=[flavor, None]):
            self.assertThat(transformer._lookup_flavor('1'),
                            Equals(flavor.name))
            self.assertThat(transformer._lookup_flavor('2'),
                            Equals('unknown'))

    def test_transformer(self):
        status = rd_instance.ServiceStatuses.BUILDING.api_status
        instance, service_status = self.build_db_instance(
            status, InstanceTasks.BUILDING)

        flavor = MagicMock(spec=Flavor)
        flavor.name = 'db.small'

        server = MagicMock(spec=Server)
        server.user_id = 'test_user_id'
        transformer = mgmtmodels.NovaNotificationTransformer(
            context=self.context)
        mgmt_instance = mgmtmodels.SimpleMgmtInstance(self.context,
                                                      instance,
                                                      server,
                                                      service_status)

        with patch.object(mgmtmodels, 'load_mgmt_instances',
                          return_value=[mgmt_instance]):
            with patch.object(self.flavor_mgr, 'get', return_value=flavor):

                payloads = transformer()

                self.assertIsNotNone(payloads)
                payload = payloads[0]
                self.assertThat(payload['audit_period_beginning'],
                                Not(Is(None)))
                self.assertThat(payload['audit_period_ending'],
                                Not(Is(None)))
                self.assertThat(payload['state'], Not(Is(None)))
                self.assertThat(payload['instance_type'],
                                Equals('db.small'))
                self.assertThat(payload['instance_type_id'],
                                Equals('flavor_1'))
                self.assertThat(payload['user_id'], Equals('test_user_id'))
                self.assertThat(payload['service_id'], Equals('123'))
        self.addCleanup(self.do_cleanup, instance, service_status)

    @patch('trove.extensions.mgmt.instances.models.LOG')
    def test_transformer_invalid_datastore_manager(self, mock_logging):
        status = rd_instance.ServiceStatuses.BUILDING.api_status
        instance, service_status = self.build_db_instance(
            status, InstanceTasks.BUILDING)
        version = datastore_models.DBDatastoreVersion.get_by(
            id=instance.datastore_version_id)
        version.update(manager='something invalid')
        server = MagicMock(spec=Server)
        server.user_id = 'test_user_id'

        flavor = MagicMock(spec=Flavor)
        flavor.name = 'db.small'

        mgmt_instance = mgmtmodels.SimpleMgmtInstance(self.context,
                                                      instance,
                                                      server,
                                                      service_status)
        transformer = mgmtmodels.NovaNotificationTransformer(
            context=self.context)
        with patch.object(mgmtmodels, 'load_mgmt_instances',
                          return_value=[mgmt_instance]):
            with patch.object(self.flavor_mgr,
                              'get', return_value=flavor):
                payloads = transformer()
                # assertions
                self.assertIsNotNone(payloads)
                payload = payloads[0]
                self.assertThat(payload['audit_period_beginning'],
                                Not(Is(None)))
                self.assertThat(payload['audit_period_ending'],
                                Not(Is(None)))
                self.assertIn(status.lower(),
                              [db['state']
                               for db in payloads])
                self.assertThat(payload['instance_type'],
                                Equals('db.small'))
                self.assertThat(payload['instance_type_id'],
                                Equals('flavor_1'))
                self.assertThat(payload['user_id'],
                                Equals('test_user_id'))
                self.assertThat(payload['service_id'],
                                Equals('unknown-service-id-error'))
        version.update(manager='mysql')
        self.addCleanup(self.do_cleanup, instance, service_status)

    def test_transformer_shutdown_instance(self):
        status = rd_instance.ServiceStatuses.SHUTDOWN.api_status
        instance, service_status = self.build_db_instance(status)
        service_status.set_status(rd_instance.ServiceStatuses.SHUTDOWN)
        server = MagicMock(spec=Server)
        server.user_id = 'test_user_id'

        mgmt_instance = mgmtmodels.SimpleMgmtInstance(self.context,
                                                      instance,
                                                      server,
                                                      service_status)
        flavor = MagicMock(spec=Flavor)
        flavor.name = 'db.small'
        transformer = mgmtmodels.NovaNotificationTransformer(
            context=self.context)
        with patch.object(Backup, 'running', return_value=None):
            self.assertThat(mgmt_instance.status, Equals('SHUTDOWN'))
            with patch.object(mgmtmodels, 'load_mgmt_instances',
                              return_value=[mgmt_instance]):
                with patch.object(self.flavor_mgr, 'get', return_value=flavor):
                    payloads = transformer()
                    # assertion that SHUTDOWN instances are not reported
                    self.assertIsNotNone(payloads)
                    self.assertNotIn(status.lower(),
                                     [db['status']
                                      for db in payloads])
        self.addCleanup(self.do_cleanup, instance, service_status)

    def test_transformer_no_nova_instance(self):
        status = rd_instance.ServiceStatuses.SHUTDOWN.api_status
        instance, service_status = self.build_db_instance(status)
        service_status.set_status(rd_instance.ServiceStatuses.SHUTDOWN)
        mgmt_instance = mgmtmodels.SimpleMgmtInstance(self.context,
                                                      instance,
                                                      None,
                                                      service_status)
        flavor = MagicMock(spec=Flavor)
        flavor.name = 'db.small'
        transformer = mgmtmodels.NovaNotificationTransformer(
            context=self.context)
        with patch.object(Backup, 'running', return_value=None):
            self.assertThat(mgmt_instance.status, Equals('SHUTDOWN'))
            with patch.object(mgmtmodels, 'load_mgmt_instances',
                              return_value=[mgmt_instance]):
                with patch.object(self.flavor_mgr, 'get', return_value=flavor):
                    payloads = transformer()
                    # assertion that SHUTDOWN instances are not reported
                    self.assertIsNotNone(payloads)
                    self.assertNotIn(status.lower(),
                                     [db['status']
                                      for db in payloads])
        self.addCleanup(self.do_cleanup, instance, service_status)

    def test_transformer_flavor_cache(self):
        status = rd_instance.ServiceStatuses.BUILDING.api_status
        instance, service_status = self.build_db_instance(
            status, InstanceTasks.BUILDING)

        server = MagicMock(spec=Server)
        server.user_id = 'test_user_id'
        mgmt_instance = mgmtmodels.SimpleMgmtInstance(self.context,
                                                      instance,