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

import copy
from unittest import mock

from senlin.common import consts
from senlin.common import exception as exc
from senlin.objects import node as node_obj
from senlin.profiles.os.nova import server
from senlin.tests.unit.common import base
from senlin.tests.unit.common import utils


class TestServerNameChecking(base.SenlinTestCase):

    scenarios = [
        ('none-none', dict(
            old_name=None,
            new_name=None,
            result=(False, 'NODE_NAME'))),
        ('none-new', dict(
            old_name=None,
            new_name='NEW_NAME',
            result=(True, 'NEW_NAME'))),
        ('old-none', dict(
            old_name='OLD_NAME',
            new_name=None,
            result=(True, 'NODE_NAME'))),
        ('old-new', dict(
            old_name='OLD_NAME',
            new_name='NEW_NAME',
            result=(True, 'NEW_NAME')))
    ]

    def setUp(self):
        super(TestServerNameChecking, self).setUp()
        self.old_spec = {
            'type': 'os.nova.server',
            'version': '1.0',
            'properties': {
                'flavor': 'FLAVOR',
            }
        }
        self.new_spec = copy.deepcopy(self.old_spec)
        obj = mock.Mock()
        obj.name = 'NODE_NAME'
        self.obj = obj

    def test_check_server_name(self):
        if self.old_name:
            self.old_spec['properties']['name'] = self.old_name
        if self.new_name:
            self.new_spec['properties']['name'] = self.new_name

        profile = server.ServerProfile('t', self.old_spec)
        new_profile = server.ServerProfile('t1', self.new_spec)

        res = profile._check_server_name(self.obj, new_profile)

        self.assertEqual(self.result, res)


class TestPasswordChecking(base.SenlinTestCase):

    scenarios = [
        ('none-none', dict(
            old_passwd=None,
            new_passwd=None,
            result=(False, ''))),
        ('none-new', dict(
            old_passwd=None,
            new_passwd='NEW_PASSWD',
            result=(True, 'NEW_PASSWD'))),
        ('old-none', dict(
            old_passwd='OLD_PASSWD',
            new_passwd=None,
            result=(True, ''))),
        ('old-new', dict(
            old_passwd='OLD_PASSWD',
            new_passwd='NEW_PASSWD',
            result=(True, 'NEW_PASSWD')))
    ]

    def setUp(self):
        super(TestPasswordChecking, self).setUp()
        self.old_spec = {
            'type': 'os.nova.server',
            'version': '1.0',
            'properties': {
                'flavor': 'FLAVOR',
            }
        }
        self.new_spec = copy.deepcopy(self.old_spec)
        self.obj = mock.Mock()

    def test_check_password(self):
        if self.old_passwd:
            self.old_spec['properties']['admin_pass'] = self.old_passwd
        if self.new_passwd:
            self.new_spec['properties']['admin_pass'] = self.new_passwd

        profile = server.ServerProfile('t', self.old_spec)
        new_profile = server.ServerProfile('t1', self.new_spec)

        res = profile._check_password(self.obj, new_profile)

        self.assertEqual(self.result, res)


class TestNovaServerUpdate(base.SenlinTestCase):
    def setUp(self):
        super(TestNovaServerUpdate, self).setUp()

        self.context = utils.dummy_context()
        self.spec = {
            'type': 'os.nova.server',
            'version': '1.0',
            'properties': {
                'context': {},
                'admin_pass': 'adminpass',
                'auto_disk_config': True,
                'availability_zone': 'FAKE_AZ',
                'block_device_mapping': [{
                    'device_name': 'FAKE_NAME',
                    'volume_size': 1000,
                }],
                'config_drive': False,
                'flavor': 'FLAV',
                'image': 'FAKE_IMAGE',
                'key_name': 'FAKE_KEYNAME',
                "metadata": {"meta var": "meta val"},
                'name': 'FAKE_SERVER_NAME',
                'networks': [{
                    'port': 'FAKE_PORT',
                    'fixed_ip': 'FAKE_IP',
                    'network': 'FAKE_NET',
                }],
                'personality': [{
                    'path': '/etc/motd',
                    'contents': 'foo',
                }],
                'scheduler_hints': {
                    'same_host': 'HOST_ID',
                },
                'security_groups': ['HIGH_SECURITY_GROUP'],
                'user_data': 'FAKE_USER_DATA',
            }
        }
        self.patchobject(node_obj.Node, 'update')

    def test_update_name(self):
        profile = server.ServerProfile('t', self.spec)
        cc = mock.Mock()
        profile._computeclient = cc
        obj = mock.Mock(physical_id='NOVA_ID')

        res = profile._update_name(obj, 'NEW_NAME')

        self.assertIsNone(res)
        cc.server_update.assert_called_once_with('NOVA_ID', name='NEW_NAME')

    def test_update_name_nova_failure(self):
        profile = server.ServerProfile('t', self.spec)
        cc = mock.Mock()
        profile._computeclient = cc
        cc.server_update.side_effect = exc.InternalError(message='BOOM')
        obj = mock.Mock(physical_id='NOVA_ID')

        ex = self.assertRaises(exc.EResourceUpdate,
                               profile._update_name,
                               obj, 'NEW_NAME')

        self.assertEqual("Failed in updating server 'NOVA_ID': BOOM.",
                         str(ex))
        cc.server_update.assert_called_once_with('NOVA_ID', name='NEW_NAME')

    def test_update_password(self):
        profile = server.ServerProfile('t', self.spec)
        cc = mock.Mock()
        profile._computeclient = cc
        obj = mock.Mock(physical_id='NOVA_ID')

        res = profile._update_password(obj, 'NEW_PASSWORD')

        self.assertIsNone(res)
        cc.server_change_password.assert_called_once_with(
            'NOVA_ID', 'NEW_PASSWORD')

    def test_update_password_nova_failure(self):
        profile = server.ServerProfile('t', self.spec)
        cc = mock.Mock()
        profile._computeclient = cc
        err = exc.InternalError(message='BOOM')
        cc.server_change_password.side_effect = err
        obj = mock.Mock(physical_id='NOVA_ID')

        ex = self.assertRaises(exc.EResourceUpdate,
                               profile._update_password,
                               obj, 'NEW_PASSWORD')

        self.assertEqual("Failed in updating server 'NOVA_ID': BOOM.",
                         str(ex))
        cc.server_change_password.assert_called_once_with(
            'NOVA_ID', 'NEW_PASSWORD')

    def test_update_metadata(self):
        obj = mock.Mock(id='NODE_ID', physical_id='NOVA_ID',
                        cluster_id='CLUSTER_ID', index=456)
        cc = mock.Mock()
        profile = server.ServerProfile('t', self.spec)
        profile._computeclient = cc
        new_spec = copy.deepcopy(self.spec)
        new_spec['properties']['metadata'] = {'new_key': 'new_value'}
        new_profile = server.ServerProfile('t', new_spec)

        res = profile._update_metadata(obj, new_profile)

        self.assertIsNone(res)
        cc.server_metadata_update.assert_called_once_with(
            'NOVA_ID',
            {
                'new_key': 'new_value',
                'cluster_node_id': 'NODE_ID',
                'cluster_id': 'CLUSTER_ID',
                'cluster_node_index': '456',
            }
        )

    def test__update_metadata_no_change(self):
        obj = mock.Mock(id='NODE_ID')
        profile = server.ServerProfile('t', self.spec)
        cc = mock.Mock()
        profile._computeclient = cc
        new_spec = copy.deepcopy(self.spec)
        new_profile = server.ServerProfile('t', new_spec)

        res = profile._update_metadata(obj, new_profile)

        self.assertIsNone(res)
        self.assertEqual(0, cc.server_metadata_update.call_count)

    def test_update_metadata_nova_failure(self):
        obj = mock.Mock(id='NODE_ID', physical_id='NOVA_ID', cluster_id='')
        err = exc.InternalError(code=500, message='Nova Error')
        cc = mock.Mock()
        cc.server_metadata_update.side_effect = err
        profile = server.ServerProfile('t', self.spec)
        profile._computeclient = cc

        # new profile with new metadata
        new_spec = copy.deepcopy(self.spec)
        new_spec['properties']['metadata'] = {'fooa': 'baaar'}
        new_profile = server.ServerProfile('t', new_spec)

        ex = self.assertRaises(exc.EResourceUpdate,
                               profile._update_metadata,
                               obj, new_profile)

        self.assertEqual("Failed in updating server 'NOVA_ID': "
                         "Nova Error.", str(ex))
        cc.server_metadata_update.assert_called_once_with(
            'NOVA_ID', {'fooa': 'baaar', 'cluster_node_id': 'NODE_ID'}
        )

    def test_update_flavor(self):
        obj = mock.Mock(physical_id='NOVA_ID')
        cc = mock.Mock()
        cc.server_get.return_value = mock.Mock(status=consts.VS_ACTIVE)
        profile = server.ServerProfile('t', self.spec)
        profile.stop_timeout = 123
        profile._computeclient = cc
        x_flavors = [mock.Mock(id='123'), mock.Mock(id='456')]
        mock_validate = self.patchobject(profile, '_validate_flavor',
                                         side_effect=x_flavors)
        new_spec = copy.deepcopy(self.spec)
        new_spec['properties']['flavor'] = 'new_flavor'
        new_profile = server.ServerProfile('t1', new_spec)
        profile._update_flavor(obj, new_profile)

        mock_validate.assert_has_calls([
            mock.call(obj, 'FLAV', 'update'),
            mock.call(obj, 'new_flavor', 'update')
        ])
        cc.server_resize.assert_called_once_with('NOVA_ID', '456')
        cc.server_resize_confirm.assert_called_once_with('NOVA_ID')
        cc.wait_for_server.assert_has_calls([
            mock.call('NOVA_ID', consts.VS_SHUTOFF,
                      timeout=profile.stop_timeout),
            mock.call('NOVA_ID', 'VERIFY_RESIZE'),
            mock.call('NOVA_ID', consts.VS_SHUTOFF)])

    # update flavor on server that is already stopped
    def test_update_flavor_stopped_server(self):
        obj = mock.Mock(physical_id='NOVA_ID')
        cc = mock.Mock()
        cc.server_get.return_value = mock.Mock(status=consts.VS_SHUTOFF)
        profile = server.ServerProfile('t', self.spec)
        profile._computeclient = cc
        x_flavors = [mock.Mock(id='123'), mock.Mock(id='456')]
        mock_validate = self.patchobject(profile, '_validate_flavor',
                                         side_effect=x_flavors)
        new_spec = copy.deepcopy(self.spec)
        new_spec['properties']['flavor'] = 'new_flavor'
        new_profile = server.ServerProfile('t1', new_spec)
        profile._update_flavor(obj, new_profile)

        mock_validate.assert_has_calls([
            mock.call(obj, 'FLAV', 'update'),
            mock.call(obj, 'new_flavor', 'update')
        ])
        cc.server_resize.assert_called_once_with('NOVA_ID', '456')
        cc.server_resize_confirm.assert_called_once_with('NOVA_ID')
        cc.wait_for_server.assert_has_calls([
            mock.call('NOVA_ID', 'VERIFY_RESIZE'),
            mock.call('NOVA_ID', consts.VS_SHUTOFF)])

    def test_update_flavor_failed_validation(self):
        obj = mock.Mock(physical_id='NOVA_ID')
        cc = mock.Mock()
        profile = server.ServerProfile('t', self.spec)
        profile._computeclient = cc
        new_spec = copy.deepcopy(self.spec)
        new_spec['properties']['flavor'] = 'new_flavor'
        new_profile = server.ServerProfile('t1', new_spec)
        err = exc.EResourceUpdate(type='server', id='NOVA_ID', message='BOOM')
        mock_validate = self.patchobject(profile, '_validate_flavor',
                                         side_effect=err)

        self.assertRaises(exc.EResourceUpdate,
                          profile._update_flavor,
                          obj, new_profile)

        mock_validate.assert_called_once_with(obj, 'FLAV', 'update')

    def test_update_flavor_failed_validation_2(self):
        obj = mock.Mock(physical_id='NOVA_ID')
        cc = mock.Mock()
        profile = server.ServerProfile('t', self.spec)
        profile._computeclient = cc
        new_spec = copy.deepcopy(self.spec)
        new_spec['properties']['flavor'] = 'new_flavor'
        new_profile = server.ServerProfile('t1', new_spec)
        result = [
            mock.Mock(),
            exc.EResourceUpdate(type='server', id='NOVA_ID', message='BOOM')
        ]
        mock_validate = self.patchobject(profile, '_validate_flavor',
                                         side_effect=result)

        self.assertRaises(exc.EResourceUpdate,
                          profile._update_flavor,
                          obj, new_profile)

        mock_validate.assert_has_calls([
            mock.call(obj, 'FLAV', 'update'),
            mock.call(obj, 'new_flavor', 'update'),
        ])

    def test_update_flavor_same(self):
        obj = mock.Mock(physical_id='NOVA_ID')
        cc = mock.Mock()
        profile = server.ServerProfile('t', self.spec)
        profile._computeclient = cc
        new_spec = copy.deepcopy(self.spec)
        new_profile = server.ServerProfile('t1', new_spec)

        x_flavors = [mock.Mock(id=123), mock.Mock(id=123)]
        mock_validate = self.patchobject(profile, '_validate_flavor',
                                         side_effect=x_flavors)

        res = profile._update_flavor(obj, new_profile)

        self.assertFalse(res)
        mock_validate.assert_has_calls([
            mock.call(obj, 'FLAV', 'update'),
            mock.call(obj, 'FLAV', 'update'),
        ])
        self.assertEqual(0, cc.server_resize.call_count)

    def test_update_flavor_server_stop_failed(self):
        obj = mock.Mock(physical_id='NOVA_ID')
        cc = mock.Mock()
        cc.server_get.return_value = mock.Mock(status=consts.VS_ACTIVE)
        cc.server_stop.side_effect = [
            exc.InternalError(code=500, message='Stop failed')]
        profile = server.ServerProfile('t', self.spec)
        profile._computeclient = cc
        new_spec = copy.deepcopy(self.spec)
        new_spec['properties']['flavor'] = 'new_flavor'
        new_profile = server.ServerProfile('t1', new_spec)
        x_flavors = [mock.Mock(id='123'), mock.Mock(id='456')]
        mock_validate = self.patchobject(profile, '_validate_flavor',
                                         side_effect=x_flavors)

        ex = self.assertRaises(exc.EResourceUpdate,
                               profile._update_flavor,
                               obj, new_profile)

        mock_validate.assert_has_calls([
            mock.call(obj, 'FLAV', 'update'),
            mock.call(obj, 'new_flavor', 'update'),
        ])
        cc.server_resize.assert_not_called()
        cc.server_resize_revert.assert_not_called()
        cc.wait_for_server.assert_not_called()
        self.assertEqual("Failed in updating server 'NOVA_ID': Stop "
                         "failed.", str(ex))

    def test_update_flavor_server_paused(self):
        obj = mock.Mock(physical_id='NOVA_ID')
        cc = mock.Mock()
        cc.server_get.return_value = mock.Mock(status=consts.VS_PAUSED)
        profile = server.ServerProfile('t', self.spec)
        profile._computeclient = cc
        new_spec = copy.deepcopy(self.spec)
        new_spec['properties']['flavor'] = 'new_flavor'
        new_profile = server.ServerProfile('t1', new_spec)
        x_flavors = [mock.Mock(id='123'), mock.Mock(id='456')]
        mock_validate = self.patchobject(profile, '_validate_flavor',
                                         side_effect=x_flavors)

        ex = self.assertRaises(exc.EResourceUpdate,
                               profile._update_flavor,
                               obj, new_profile)

        mock_validate.assert_has_calls([
            mock.call(obj, 'FLAV', 'update'),
            mock.call(obj, 'new_flavor', 'update'),
        ])
        cc.server_resize.assert_not_called()
        cc.server_resize_revert.assert_not_called()
        cc.wait_for_server.assert_not_called()
        self.assertEqual("Failed in updating server 'NOVA_ID': Server needs "
                         "to be ACTIVE or STOPPED in order to update flavor.",
                         str(ex))

    def test_update_flavor_resize_failed(self):
        obj = mock.Mock(physical_id='NOVA_ID')
        cc = mock.Mock()
        cc.server_get.side_effect = [
            mock.Mock(status=consts.VS_ACTIVE),
            mock.Mock(status='RESIZE')]
        cc.server_resize.side_effect = [
            exc.InternalError(code=500, message='Resize failed')]
        profile = server.ServerProfile('t', self.spec)
        profile._computeclient = cc
        new_spec = copy.deepcopy(self.spec)
        new_spec['properties']['flavor'] = 'new_flavor'
        new_profile = server.ServerProfile('t1', new_spec)
        x_flavors = [mock.Mock(id='123'), mock.Mock(id='456')]
        mock_validate = self.patchobject(profile, '_validate_flavor',
                                         side_effect=x_flavors)

        ex = self.assertRaises(exc.EResourceUpdate,
                               profile._update_flavor,
                               obj, new_profile)

        mock_validate.assert_has_calls([
            mock.call(obj, 'FLAV', 'update'),
            mock.call(obj, 'new_flavor', 'update'),
        ])
        cc.server_resize.assert_called_once_with('NOVA_ID', '456')
        cc.server_resize_revert.assert_called_once_with('NOVA_ID')
        cc.wait_for_server.assert_has_calls([
            mock.call('NOVA_ID', consts.VS_SHUTOFF, timeout=600),
            mock.call('NOVA_ID', consts.VS_SHUTOFF),
            mock.call('NOVA_ID', consts.VS_ACTIVE)
        ])
        self.assertEqual("Failed in updating server 'NOVA_ID': Resize "
                         "failed.", str(ex))

    def test_update_flavor_first_wait_for_server_failed(self):
        obj = mock.Mock(physical_id='NOVA_ID')
        cc = mock.Mock()
        cc.server_get.return_value = mock.Mock(status=consts.VS_ACTIVE)
        cc.wait_for_server.side_effect = [
            exc.InternalError(code=500, message='TIMEOUT')
        ]

        profile = server.ServerProfile('t', self.spec)
        profile._computeclient = cc
        new_spec = copy.deepcopy(self.spec)
        new_spec['properties']['flavor'] = 'new_flavor'
        new_profile = server.ServerProfile('t1', new_spec)
        x_flavors = [mock.Mock(id='123'), mock.Mock(id='456')]
        mock_validate = self.patchobject(profile, '_validate_flavor',
                                         side_effect=x_flavors)
        # do it
        ex = self.assertRaises(exc.EResourceUpdate,
                               profile._update_flavor,
                               obj, new_profile)

        # assertions
        mock_validate.assert_has_calls([
            mock.call(obj, 'FLAV', 'update'),
            mock.call(obj, 'new_flavor', 'update'),
        ])
        cc.server_resize.assert_not_called()
        cc.wait_for_server.has_calls([
            mock.call('NOVA_ID', consts.VS_SHUTOFF, timeout=600)])
        self.assertEqual("Failed in updating server 'NOVA_ID': "
                         "TIMEOUT.", str(ex))

    def test_update_flavor_second_wait_for_server_failed(self):
        obj = mock.Mock(physical_id='NOVA_ID')
        cc = mock.Mock()
        cc.server_get.side_effect = [
            mock.Mock(status=consts.VS_ACTIVE),
            mock.Mock(status='RESIZE')]
        cc.wait_for_server.side_effect = [
            None,
            exc.InternalError(code=500, message='TIMEOUT'),
            None,
            None
        ]

        profile = server.ServerProfile('t', self.spec)
        profile._computeclient = cc
        new_spec = copy.deepcopy(self.spec)
        new_spec['properties']['flavor'] = 'new_flavor'
        new_profile = server.ServerProfile('t1', new_spec)
        x_flavors = [mock.Mock(id='123'), mock.Mock(id='456')]
        mock_validate = self.patchobject(profile, '_validate_flavor',
                                         side_effect=x_flavors)
        # do it
        ex = self.assertRaises(exc.EResourceUpdate,
                               profile._update_flavor,
                               obj, new_profile)

        # assertions
        mock_validate.assert_has_calls([
            mock.call(obj, 'FLAV', 'update'),
            mock.call(obj, 'new_flavor', 'update'),
        ])
        cc.server_resize.assert_called_once_with('NOVA_ID', '456')
        cc.wait_for_server.has_calls([
            mock.call('NOVA_ID', consts.VS_SHUTOFF, timeout=600),
            mock.call('NOVA_ID', 'VERIFY_RESIZE'),
            mock.call('NOVA_ID', consts.VS_SHUTOFF),
            mock.call('NOVA_ID', consts.VS_ACTIVE),
        ])
        cc.server_resize_revert.assert_called_once_with('NOVA_ID')
        self.assertEqual("Failed in updating server 'NOVA_ID': "
                         "TIMEOUT.", str(ex))

    def test_update_flavor_resize_failed_revert_failed(self):
        obj = mock.Mock(physical_id='NOVA_ID')
        cc = mock.Mock()
        cc.server_get.side_effect = [
            mock.Mock(status=consts.VS_ACTIVE),
            mock.Mock(status='RESIZE')]
        err_resize = exc.InternalError(code=500, message='Resize')
        cc.server_resize.side_effect = err_resize
        err_revert = exc.InternalError(code=500, message='Revert')
        cc.server_resize_revert.side_effect = err_revert
        profile = server.ServerProfile('t', self.spec)
        profile._computeclient = cc
        new_spec = copy.deepcopy(self.spec)
        new_spec['properties']['flavor'] = 'new_flavor'
        new_profile = server.ServerProfile('t1', new_spec)
        x_flavors = [mock.Mock(id='123'), mock.Mock(id='456')]
        mock_validate = self.patchobject(profile, '_validate_flavor',
                                         side_effect=x_flavors)

        # do it
        ex = self.assertRaises(exc.EResourceUpdate,
                               profile._update_flavor,
                               obj, new_profile)

        # assertions
        mock_validate.assert_has_calls([
            mock.call(obj, 'FLAV', 'update'),
            mock.call(obj, 'new_flavor', 'update'),
        ])
        cc.server_resize.assert_called_once_with('NOVA_ID', '456')
        cc.server_resize_revert.assert_called_once_with('NOVA_ID')
        cc.wait_for_server.has_calls([
            mock.call('NOVA_ID', consts.VS_SHUTOFF, timeout=600),
        ])
        self.assertEqual("Failed in updating server 'NOVA_ID': "
                         "Revert.", str(ex))

    def test_update_flavor_confirm_failed(self):
        obj = mock.Mock(physical_id='NOVA_ID')
        cc = mock.Mock()
        cc.server_get.return_value = mock.Mock(status=consts.VS_ACTIVE)
        err_confirm = exc.InternalError(code=500, message='Confirm')
        cc.server_resize_confirm.side_effect = err_confirm
        profile = server.ServerProfile('t', self.spec)
        profile._computeclient = cc
        new_spec = copy.deepcopy(self.spec)
        new_spec['properties']['flavor'] = 'new_flavor'
        new_profile = server.ServerProfile('t1', new_spec)
        x_flavors = [mock.Mock(id='123'), mock.Mock(id='456')]
        mock_validate = self.patchobject(profile, '_validate_flavor',
                                         side_effect=x_flavors)

        # do it
        ex = self.assertRaises(exc.EResourceUpdate,
                               profile._update_flavor,
                               obj, new_profile)

        # assertions
        mock_validate.assert_has_calls([
            mock.call(obj, 'FLAV', 'update'),
            mock.call(obj, 'new_flavor', 'update'),
        ])
        cc.server_resize.assert_called_once_with('NOVA_ID', '456')
        cc.server_resize_confirm.assert_called_once_with('NOVA_ID')
        cc.wait_for_server.has_calls([
            mock.call('NOVA_ID', consts.VS_SHUTOFF, timeout=600),
            mock.call('NOVA_ID', 'VERIFY_RESIZE'),
        ])
        self.assertEqual("Failed in updating server 'NOVA_ID': Confirm.",
                         str(ex))

    def test_update_flavor_wait_confirm_failed(self):
        obj = mock.Mock(physical_id='NOVA_ID')
        cc = mock.Mock()
        cc.server_get.return_value = mock.Mock(status=consts.VS_SHUTOFF)
        err_wait = exc.InternalError(code=500, message='Wait')
        cc.wait_for_server.side_effect = [None, err_wait]
        profile = server.ServerProfile('t', self.spec)
        profile._computeclient = cc
        new_spec = copy.deepcopy(self.spec)
        new_spec['properties']['flavor'] = 'new_flavor'
        new_profile = server.ServerProfile('t1', new_spec)
        x_flavors = [mock.Mock(id='123'), mock.Mock(id='456')]
        mock_validate = self.patchobject(profile, '_validate_flavor',
                                         side_effect=x_flavors)

        # do it
        ex = self.assertRaises(exc.InternalError,
                               profile._update_flavor,
                               obj, new_profile)

        # assertions
        mock_validate.assert_has_calls([
            mock.call(obj, 'FLAV', 'update'),
            mock.call(obj, 'new_flavor', 'update'),
        ])
        cc.server_resize.assert_called_once_with('NOVA_ID', '456')
        cc.server_resize_confirm.assert_called_once_with('NOVA_ID')
        cc.wait_for_server.assert_has_calls([
            mock.call('NOVA_ID', 'VERIFY_RESIZE'),
            mock.call('NOVA_ID', consts.VS_SHUTOFF)
        ])
        self.assertEqual("Failed in updating server 'NOVA_ID': Wait.",
                         str(ex))

    def test_update_image(self):
        profile = server.ServerProfile('t', self.spec)
        profile.stop_timeout = 123
        x_image = {'id': '123'}
        x_server = mock.Mock(image=x_image, status=consts.VS_ACTIVE)
        cc = mock.Mock()
        cc.server_get.return_value = x_server
        profile._computeclient = cc
        x_new_image = mock.Mock(id='456')
        x_images = [x_new_image]
        mock_check = self.patchobject(profile, '_validate_image',
                                      side_effect=x_images)
        obj = mock.Mock(physical_id='NOVA_ID')
        new_spec = copy.deepcopy(self.spec)
        new_spec['properties']['image'] = 'new_image'
        new_profile = server.ServerProfile('t1', new_spec)

        profile._update_image(obj, new_profile, 'new_name', 'new_pass')

        mock_check.assert_has_calls([
            mock.call(obj, 'new_image', reason='update'),
        ])
        cc.server_rebuild.assert_called_once_with(
            'NOVA_ID', '456', 'new_name', 'new_pass')
        cc.wait_for_server.assert_has_calls([
            mock.call('NOVA_ID', consts.VS_SHUTOFF,
                      timeout=profile.stop_timeout),
            mock.call('NOVA_ID', consts.VS_SHUTOFF),
        ])

    def test_update_image_server_stopped(self):
        profile = server.ServerProfile('t', self.spec)
        x_image = {'id': '123'}
        x_server = mock.Mock(image=x_image, status=consts.VS_SHUTOFF)
        cc = mock.Mock()
        cc.server_get.return_value = x_server
        profile._computeclient = cc
        x_new_image = mock.Mock(id='456')
        x_images = [x_new_image]
        mock_check = self.patchobject(profile, '_validate_image',
                                      side_effect=x_images)
        obj = mock.Mock(physical_id='NOVA_ID')
        new_spec = copy.deepcopy(self.spec)
        new_spec['properties']['image'] = 'new_image'
        new_profile = server.ServerProfile('t1', new_spec)

        profile._update_image(obj, new_profile, 'new_name', 'new_pass')

        mock_check.assert_has_calls([
            mock.call(obj, 'new_image', reason='update'),
        ])
        cc.server_rebuild.assert_called_once_with(
            'NOVA_ID', '456', 'new_name', 'new_pass')
        cc.wait_for_server.assert_has_calls([
            mock.call('NOVA_ID', consts.VS_SHUTOFF),
        ])

    def test_update_image_server_paused(self):
        profile = server.ServerProfile('t', self.spec)
        x_image = {'id': '123'}
        x_server = mock.Mock(image=x_image, status=consts.VS_PAUSED)
        cc = mock.Mock()
        cc.server_get.return_value = x_server
        profile._computeclient = cc
        x_new_image = mock.Mock(id='456')
        x_images = [x_new_image]
        mock_check = self.patchobject(profile, '_validate_image',
                                      side_effect=x_images)
        obj = mock.Mock(physical_id='NOVA_ID')
        new_spec = copy.deepcopy(self.spec)
        new_spec['properties']['image'] = 'new_image'
        new_profile = server.ServerProfile('t1', new_spec)

        ex = self.assertRaises(exc.EResourceUpdate,
                               profile._update_image,
                               obj, new_profile, 'new_name', '')

        msg = ("Failed in updating server 'NOVA_ID': Server needs to be ACTIVE"
               " or STOPPED in order to update image.")
        self.assertEqual(msg, str(ex))

        mock_check.assert_has_calls([
            mock.call(obj, 'new_image', reason='update'),
        ])
        cc.server_rebuild.assert_not_called()
        cc.wait_for_server.assert_not_called()

    def test_update_image_new_image_is_none(self):
        profile = server.ServerProfile('t', self.spec)
        obj = mock.Mock(physical_id='NOVA_ID')
        new_spec = copy.deepcopy(self.spec)
        del new_spec['properties']['image']
        new_profile = server.ServerProfile('t1', new_spec)

        ex = self.assertRaises(exc.EResourceUpdate,
                               profile._update_image,
                               obj, new_profile, 'new_name', '')

        msg = ("Failed in updating server 'NOVA_ID': Updating Nova server"
               " with image set to None is not supported by Nova.")
        self.assertEqual(msg, str(ex))

    def test_update_image_new_image_invalid(self):
        # NOTE: The image invalid could be caused by a non-existent image or
        # a compute driver failure
        profile = server.ServerProfile('t', self.spec)
        # _validate_image will always throw EResourceUpdate if driver fails
        err = exc.EResourceUpdate(type='server', id='NOVA_ID', message='BAD')
        mock_check = self.patchobject(profile, '_validate_image',
                                      side_effect=err)
        obj = mock.Mock(physical_id='NOVA_ID')
        new_spec = copy.deepcopy(self.spec)
        new_spec['properties']['image'] = 'new_image'
        new_profile = server.ServerProfile('t1', new_spec)

        ex = self.assertRaises(exc.EResourceUpdate,
                               profile._update_image,
                               obj, new_profile, 'new_name', 'new_pass')

        msg = ("Failed in updating server 'NOVA_ID': BAD.")
        self.assertEqual(msg, str(ex))
        mock_check.assert_called_once_with(obj, 'new_image', reason='update')

    def test_update_image_old_image_invalid(self):
        # NOTE: The image invalid could be caused by a non-existent image or
        # a compute driver failure
        profile = server.ServerProfile('t', self.spec)
        x_image = {'id': '123'}
        x_server = mock.Mock(image=x_image)
        cc = mock.Mock()
        cc.server_get.return_value = x_server
        profile._computeclient = cc
        # _validate_image will always throw EResourceUpdate if driver fails
        results = [
            exc.EResourceUpdate(type='server', id='NOVA_ID', message='BAD')
        ]
        mock_check = self.patchobject(profile, '_validate_image',
                                      side_effect=results)
        obj = mock.Mock(physical_id='NOVA_ID')
        new_spec = copy.deepcopy(self.spec)
        new_spec['properties']['image'] = 'new_image'
        new_profile = server.ServerProfile('t1', new_spec)

        ex = self.assertRaises(exc.EResourceUpdate,
                               profile._update_image,
                               obj, new_profile, 'new_name', 'new_pass')

        msg = ("Failed in updating server 'NOVA_ID': BAD.")
        self.assertEqual(msg, str(ex))
        mock_check.assert_has_calls([
            mock.call(obj, 'new_image', reason='update'),
        ])

    def test_update_image_old_image_is_none_but_succeeded(self):
        old_spec = copy.deepcopy(self.spec)
        del old_spec['properties']['image']
        profile = server.ServerProfile('t', old_spec)
        cc = mock.Mock()
        profile._computeclient = cc
        x_server = mock.Mock(image={'id': '123'}, status=consts.VS_ACTIVE)
        cc.server_get.return_value = x_server
        # this is the new one
        x_image = mock.Mock(id='456')
        mock_check = self.patchobject(profile, '_validate_image',
                                      return_value=x_image)
        obj = mock.Mock(physical_id='NOVA_ID')
        new_spec = copy.deepcopy(self.spec)
        new_spec['properties']['image'] = 'new_image'
        new_profile = server.ServerProfile('t1', new_spec)

        res = profile._update_image(obj, new_profile, 'new_name', 'new_pass')

        self.assertTrue(res)
        mock_check.assert_called_once_with(obj, 'new_image', reason='update')
        cc.server_get.assert_called_once_with('NOVA_ID')
        cc.server_rebuild.assert_called_once_with(
            'NOVA_ID', '456', 'new_name', 'new_pass')
        cc.wait_for_server.assert_has_calls([
            # first wait is from active to shutoff and has custom timeout
            mock.call('NOVA_ID', consts.VS_SHUTOFF, timeout=600),
            mock.call('NOVA_ID', consts.VS_SHUTOFF),
        ])

    def test_update_image_old_image_is_none_but_failed(self):
        old_spec = copy.deepcopy(self.spec)
        del old_spec['properties']['image']
        profile = server.ServerProfile('t', old_spec)
        cc = mock.Mock()
        profile._computeclient = cc
        # this is about the new one
        x_image = mock.Mock(id='456')
        mock_check = self.patchobject(profile, '_validate_image',
                                      return_value=x_image)
        cc.server_get.side_effect = exc.InternalError(message='DRIVER')
        obj = mock.Mock(physical_id='NOVA_ID')
        new_spec = copy.deepcopy(self.spec)
        new_spec['properties']['image'] = 'new_image'
        new_profile = server.ServerProfile('t1', new_spec)

        ex = self.assertRaises(exc.EResourceUpdate,
                               profile._update_image,
                               obj, new_profile, 'new_name', 'new_pass')

        self.assertEqual("Failed in updating server 'NOVA_ID': DRIVER.",
                         str(ex))
        mock_check.assert_called_once_with(obj, 'new_image', reason='update')
        cc.server_get.assert_called_once_with('NOVA_ID')

    def test_update_image_updating_to_same_image(self):
        profile = server.ServerProfile('t', self.spec)
        x_image = {'id': '123'}
        x_server = mock.Mock(image=x_image)
        cc = mock.Mock()
        cc.server_get.return_value = x_server
        profile._computeclient = cc
        x_new_image = mock.Mock(id='123')
        x_images = [x_new_image]
        mock_check = self.patchobject(profile, '_validate_image',
                                      side_effect=x_images)
        obj = mock.Mock(physical_id='NOVA_ID')
        new_spec = copy.deepcopy(self.spec)
        new_spec['properties']['image'] = 'new_image'
        new_profile = server.ServerProfile('t1', new_spec)

        res = profile._update_image(obj, new_profile, 'new_name', 'new_pass')

        self.assertFalse(res)
        mock_check.assert_has_calls([
            mock.call(obj, 'new_image', reason='update'),
        ])
        self.assertEqual(0, cc.server_rebuild.call_count)
        self.assertEqual(0, cc.wait_for_server.call_count)

    def test_update_image_failed_stopping(self):
        profile = server.ServerProfile('t', self.spec)
        x_image = {'id': '123'}
        x_server = mock.Mock(image=x_image)
        cc = mock.Mock()
        cc.server_get.return_value = x_server
        cc.server_stop.side_effect = exc.InternalError(message='FAILED')
        profile._computeclient = cc
        x_new_image = mock.Mock(id='456')
        x_images = [x_new_image]
        mock_check = self.patchobject(profile, '_validate_image',
                                      side_effect=x_images)
        obj = mock.Mock(physical_id='NOVA_ID')
        new_spec = copy.deepcopy(self.spec)
        new_spec['properties']['image'] = 'new_image'
        new_profile = server.ServerProfile('t1', new_spec)

        ex = self.assertRaises(exc.EResourceUpdate,
                               profile._update_image,
                               obj, new_profile, 'new_name', 'new_pass')

        self.assertEqual("Failed in updating server 'NOVA_ID': Server needs to"
                         " be ACTIVE or STOPPED in order to update image.",
                         str(ex))
        mock_check.assert_has_calls([
            mock.call(obj, 'new_image', reason='update'),
        ])
        cc.server_rebuild.assert_not_called()
        cc.wait_for_server.assert_not_called()

    def test_update_image_failed_rebuilding(self):
        profile = server.ServerProfile('t', self.spec)
        x_image = {'id': '123'}
        x_server = mock.Mock(image=x_image, status=consts.VS_ACTIVE)
        cc = mock.Mock()
        cc.server_get.return_value = x_server
        cc.server_rebuild.side_effect = exc.InternalError(message='FAILED')
        profile._computeclient = cc
        x_new_image = mock.Mock(id='456')
        x_images = [x_new_image]
        mock_check = self.patchobject(profile, '_validate_image',
                                      side_effect=x_images)
        obj = mock.Mock(physical_id='NOVA_ID')
        new_spec = copy.deepcopy(self.spec)
        new_spec['properties']['image'] = 'new_image'
        new_profile = server.ServerProfile('t1', new_spec)

        ex = self.assertRaises(exc.EResourceUpdate,
                               profile._update_image,
                               obj, new_profile, 'new_name', 'new_pass')

        self.assertEqual("Failed in updating server 'NOVA_ID': FAILED.",
                         str(ex))
        mock_check.assert_has_calls([
            mock.call(obj, 'new_image', reason='update'),
        ])
        cc.server_rebuild.assert_called_once_with(
            'NOVA_ID', '456', 'new_name', 'new_pass')
        cc.wait_for_server.assert_has_calls([
            mock.call('NOVA_ID', consts.VS_SHUTOFF, timeout=600),
        ])

    def test_update_image_failed_first_waiting(self):
        profile = server.ServerProfile('t', self.spec)
        x_image = {'id': '123'}
        x_server = mock.Mock(image=x_image, status=consts.VS_ACTIVE)
        cc = mock.Mock()
        cc.server_get.return_value = x_server
        cc.wait_for_server.side_effect = exc.InternalError(message='TIMEOUT')
        profile._computeclient = cc
        x_new_image = mock.Mock(id='456')
        x_images = [x_new_image]
        mock_check = self.patchobject(profile, '_validate_image',
                                      side_effect=x_images)
        obj = mock.Mock(physical_id='NOVA_ID')
        new_spec = copy.deepcopy(self.spec)
        new_spec['properties']['image'] = 'new_image'
        new_profile = server.ServerProfile('t1', new_spec)

        ex = self.assertRaises(exc.EResourceUpdate,
                               profile._update_image,
                               obj, new_profile, 'new_name', 'new_pass')

        self.assertEqual("Failed in updating server 'NOVA_ID': TIMEOUT.",
                         str(ex))
        mock_check.assert_has_calls([
            mock.call(obj, 'new_image', reason='update'),
        ])
        cc.server_rebuild.assert_not_called()
        cc.wait_for_server.assert_called_once_with(
            'NOVA_ID', consts.VS_SHUTOFF, timeout=600)

    def test_update_image_failed_second_waiting(self):
        profile = server.ServerProfile('t', self.spec)
        x_image = {'id': '123'}
        x_server = mock.Mock(image=x_image, status=consts.VS_ACTIVE)
        cc = mock.Mock()
        cc.server_get.return_value = x_server
        cc.wait_for_server.side_effect = [
            None,
            exc.InternalError(message='TIMEOUT')]
        profile._computeclient = cc
        x_new_image = mock.Mock(id='456')
        x_images = [x_new_image]
        mock_check = self.patchobject(profile, '_validate_image',
                                      side_effect=x_images)
        obj = mock.Mock(physical_id='NOVA_ID')
        new_spec = copy.deepcopy(self.spec)
        new_spec['properties']['image'] = 'new_image'
        new_profile = server.ServerProfile('t1', new_spec)

        ex = self.assertRaises(exc.EResourceUpdate,
                               profile._update_image,
                               obj, new_profile, 'new_name', 'new_pass')

        self.assertEqual("Failed in updating server 'NOVA_ID': TIMEOUT.",
                         str(ex))
        mock_check.assert_has_calls([
            mock.call(obj, 'new_image', reason='update'),
        ])
        cc.server_rebuild.assert_called_once_with(
            'NOVA_ID', '456', 'new_name', 'new_pass')
        cc.wait_for_server.assert_has_calls([
            mock.call('NOVA_ID', consts.VS_SHUTOFF, timeout=600),
            mock.call('NOVA_ID', consts.VS_SHUTOFF)])

    def test_create_interfaces(self):
        cc = mock.Mock()
        server_obj = mock.Mock()
        cc.server_get.return_value = server_obj
        profile = server.ServerProfile('t', self.spec)
        profile._computeclient = cc
        validation_results = [
            {'network': 'net1_id', 'fixed_ip': 'ip2'},
            {'network': 'net2_id'},
            {'port': 'port4'}
        ]
        mock_validate = self.patchobject(profile, '_validate_network',
                                         side_effect=validation_results)
        ports_results = [
            (mock.Mock(
                id='port1_id', network_id='net1_id',
                fixed_ips=[{'ip_address': 'ip2'}], security_group_ids=[]),
             None),
            (mock.Mock(
                id='port2_id', network_id='net2_id',
                fixed_ips=[{'ip_address': 'ip3'}], security_group_ids=[]),
             None),
            (mock.Mock(
                id='port4_id', network_id='net3_id',
                fixed_ips=[{'ip_address': 'ip4'}], security_group_ids=[]),
             None)
        ]
        mock_get_port = self.patchobject(profile, '_get_port',
                                         side_effect=ports_results)
        networks = [
            {'network': 'net1', 'port': None, 'fixed_ip': 'ip2'},
            {'network': 'net2', 'port': None, 'fixed_ip': None},
            {'network': None, 'port': 'port4', 'fixed_ip': None}
        ]
        obj = mock.Mock(physical_id='NOVA_ID', data={})

        res = profile._update_network_add_port(obj, networks)

        self.assertIsNone(res)
        cc.server_get.assert_called_with('NOVA_ID')
        validation_calls = [
            mock.call(obj,
                      {'network': 'net1', 'port': None, 'fixed_ip': 'ip2'},
                      'update'),
            mock.call(obj,
                      {'network': 'net2', 'port': None, 'fixed_ip': None},
                      'update'),
            mock.call(obj,
                      {'network': None, 'port': 'port4', 'fixed_ip': None},
                      'update')
        ]
        mock_validate.assert_has_calls(validation_calls)
        mock_get_port.assert_called_with(obj, {'port': 'port4'})
        create_calls = [
            mock.call(server_obj, port_id='port1_id'),
            mock.call(server_obj, port_id='port2_id'),
            mock.call(server_obj, port_id='port4_id'),
        ]
        cc.server_interface_create.assert_has_calls(create_calls)

    def test_create_interfaces_failed_getting_server(self):
        cc = mock.Mock()
        cc.server_get.side_effect = exc.InternalError(message='Not valid')
        profile = server.ServerProfile('t', self.spec)
        profile._computeclient = cc
        self.patchobject(profile, '_create_ports_from_properties')

        obj = mock.Mock(physical_id='NOVA_ID')
        networks = [{'foo': 'bar'}]  # not used

        ex = self.assertRaises(exc.EResourceUpdate,
                               profile._update_network_add_port,
                               obj, networks)

        self.assertEqual("Failed in updating server 'NOVA_ID': Not valid.",
                         str(ex))
        cc.server_get.assert_called_once_with('NOVA_ID')
        self.assertEqual(0, profile._create_ports_from_properties.call_count)

    def test_create_interfaces_failed_validation(self):
        cc = mock.Mock()
        server_obj = mock.Mock()
        cc.server_get.return_value = server_obj
        profile = server.ServerProfile('t', self.spec)
        profile._computeclient = cc
        err = exc.EResourceUpdate(type='server', id='NOVA_ID',
                                  message='Driver error')
        mock_validate = self.patchobject(profile, '_validate_network',
                                         side_effect=err)
        networks = [{'network': 'net1', 'port': None, 'fixed_ip': 'ip2'}]
        obj = mock.Mock(physical_id='NOVA_ID')

        ex = self.assertRaises(exc.EResourceUpdate,
                               profile._update_network_add_port,
                               obj, networks)

        self.assertEqual("Failed in updating server 'NOVA_ID': Driver error.",
                         str(ex))
        cc.server_get.assert_called_once_with('NOVA_ID')
        mock_validate.assert_called_once_with(obj, networks[0], 'update')
        self.assertEqual(0, cc.server_interface_create.call_count)

    def test_delete_interfaces(self):
        cc = mock.Mock()
        cc.server_get.return_value = mock.Mock(status=consts.VS_ACTIVE)
        nc = mock.Mock()
        net1 = mock.Mock(id='net1')
        nc.network_get.return_value = net1
        nc.port_find.return_value = mock.Mock(id='port3', status='DOWN')
        profile = server.ServerProfile('t', self.spec)
        profile.stop_timeout = 232
        profile._computeclient = cc
        profile._networkclient = nc
        obj = mock.Mock(physical_id='NOVA_ID', data={'internal_ports': [
            {'id': 'port1', 'network_id': 'net1', 'remove': True,
             'fixed_ips': [{'ip_address': 'ip1'}]},
            {'id': 'port2', 'network_id': 'net1', 'remove': True,
             'fixed_ips': [{'ip_address': 'ip-random2'}]},
            {'id': 'port3', 'network_id': 'net1', 'remove': True,
             'fixed_ips': [{'ip_address': 'ip3'}]}]})
        networks = [
            {'network': 'net1', 'port': None, 'fixed_ip': 'ip1'},
            {'network': 'net1', 'port': None, 'fixed_ip': None},
            {'network': None, 'port': 'port3', 'fixed_ip': None}
        ]

        res = profile._update_network_remove_port(obj, networks)

        self.assertIsNone(res)

        nc.network_get.assert_has_calls([
            mock.call('net1'), mock.call('net1')
        ])
        cc.wait_for_server.assert_has_calls([
            mock.call('NOVA_ID', consts.VS_SHUTOFF,
                      timeout=profile.stop_timeout),
        ])
        cc.server_interface_delete.assert_has_calls([
            mock.call('port1', 'NOVA_ID'),
            mock.call('port2', 'NOVA_ID'),
            mock.call('port3', 'NOVA_ID'),
        ])
        nc.port_delete.assert_has_calls([
            mock.call('port1', ignore_missing=True),
            mock.call('port2', ignore_missing=True),
            mock.call('port3', ignore_missing=True),
        ])

    def test_delete_interfaces_failed_delete(self):
        cc = mock.Mock()
        profile = server.ServerProfile('t', self.spec)
        profile._computeclient = cc
        profile._networkclient = mock.Mock()
        candidate_ports = [
            [{'id': 'port1', 'network_id': 'net1',
             'fixed_ips': [{'ip_address': 'ip1'}]}],
        ]
        self.patchobject(profile, '_find_port_by_net_spec',
                         side_effect=candidate_ports)
        err = exc.InternalError(message='BANG')
        cc.server_interface_delete.side_effect = err
        internal_ports = [
            {'id': 'port1', 'remove': True}
        ]
        obj = mock.Mock(physical_id='NOVA_ID',
                        data={'internal_ports': internal_ports})
        networks = [
            {'network': 'net1', 'port': None, 'fixed_ip': 'ip1'},
        ]

        ex = self.assertRaises(exc.EResourceUpdate,
                               profile._update_network_remove_port,
                               obj, networks)

        self.assertEqual("Failed in updating server 'NOVA_ID': BANG.",
                         str(ex))
        cc.server_interface_delete.assert_called_once_with('port1', 'NOVA_ID')

    def test_update_port(self):
        cc = mock.Mock()
        cc.server_get.return_value = mock.Mock(status=consts.VS_ACTIVE)
        nc = mock.Mock()
        net1 = mock.Mock(id='net1')
        nc.network_get.return_value = net1
        nc.port_find.return_value = mock.Mock(id='port3', status='DOWN')
        profile = server.ServerProfile('t', self.spec)
        profile.stop_timeout = 232
        profile._computeclient = cc
        profile._networkclient = nc
        validation_results = [
            {'network': 'net1_id', 'fixed_ip': 'ip1',
             'security_groups': ['sg1_id']},
            {'network': 'net1_id', 'fixed_ip': 'ip1',
             'security_groups': ['sg1_id', 'sg2_id']},
            {'network': 'net1_id', 'fixed_ip': 'ip1'}
        ]
        mock_validate = self.patchobject(profile, '_validate_network',
                                         side_effect=validation_results)
        candidate_ports = [
            [{'id': 'port1_id', 'network_id': 'net1_id',
              'fixed_ips': [{'ip_address': 'ip1'}]}],
            [{'id': 'port2_id', 'network_id': 'net1_id',
              'fixed_ips': [{'ip_address': 'ip1'}]}],
            [{'id': 'port3_id', 'network_id': 'net1_id',
              'fixed_ips': [{'ip_address': 'ip1'}]}]
        ]
        self.patchobject(profile, '_find_port_by_net_spec',
                         side_effect=candidate_ports)

        obj = mock.Mock(physical_id='NOVA_ID', data={'internal_ports': [
            {'id': 'port1', 'network_id': 'net1',
             'fixed_ips': [{'ip_address': 'ip1'}]},
            {'id': 'port2', 'network_id': 'net1', 'remove': True,
             'fixed_ips': [{'ip_address': 'ip-random2'}],
             'security_groups': ['default']},
            {'id': 'port3', 'network_id': 'net1', 'remove': True,
             'fixed_ips': [{'ip_address': 'ip3'}],
             'security_groups': ['default']}
        ]})
        networks = [
            {'network': 'net1', 'port': None, 'fixed_ip': 'ip1',
             'security_groups': ['default'], 'floating_network': None,
             'floating_ip': None},
            {'network': 'net1', 'port': None, 'fixed_ip': 'ip1',
             'security_groups': ['default', 'blah'], 'floating_network': None,
             'floating_ip': None},
            {'network': 'net1', 'port': None, 'fixed_ip': 'ip1',
             'security_groups': None, 'floating_network': None,
             'floating_ip': None},
        ]

        res = profile._update_network_update_port(obj, networks)

        self.assertIsNone(res)
        validation_calls = [
            mock.call(obj,
                      {'network': 'net1', 'port': None, 'fixed_ip': 'ip1',
                       'security_groups': ['default'],
                       'floating_network': None, 'floating_ip': None},
                      'update'),
            mock.call(obj,
                      {'network': 'net1', 'port': None, 'fixed_ip': 'ip1',
                       'security_groups': ['default', 'blah'],
                       'floating_network': None, 'floating_ip': None},
                      'update'),
            mock.call(obj,
                      {'network': 'net1', 'port': None, 'fixed_ip': 'ip1',
                       'security_groups': None, 'floating_network': None,
                       'floating_ip': None},
                      'update')
        ]
        mock_validate.assert_has_calls(validation_calls)
        update_calls = [
            mock.call('port1_id', security_groups=['sg1_id']),
            mock.call('port2_id', security_groups=['sg1_id', 'sg2_id']),
            mock.call('port3_id', security_groups=[]),
        ]
        nc.port_update.assert_has_calls(update_calls)

    @mock.patch.object(server.ServerProfile, '_update_network_update_port')
    @mock.patch.object(server.ServerProfile, '_update_network_remove_port')
    @mock.patch.object(server.ServerProfile, '_update_network_add_port')
    def test_update_network(self, mock_create, mock_delete, mock_update):
        obj = mock.Mock(physical_id='FAKE_ID')

        old_spec = copy.deepcopy(self.spec)
        old_spec['properties']['networks'] = [
            {'network': 'net1', 'fixed_ip': 'ip1'},
            {'network': 'net1'},
            {'port': 'port3'},
            # sg only changes:
            {'network': 'net3', 'fixed_ip': 'ip1'},
            {'network': 'net4', 'fixed_ip': 'ip1',
             'security_groups': ['blah']},
            {'port': 'port5', 'security_groups': ['default']},
        ]
        profile = server.ServerProfile('t', old_spec)
        new_spec = copy.deepcopy(self.spec)
        new_spec['properties']['networks'] = [
            {'network': 'net1', 'fixed_ip': 'ip2'},
            {'network': 'net2'},
            {'port': 'port4'},
            # sg only changes:
            {'network': 'net3', 'fixed_ip': 'ip1',
             'security_groups': ['default']},
            {'network': 'net4', 'fixed_ip': 'ip1',
             'security_groups': ['default']},
            {'port': 'port5', 'security_groups': ['default', 'blah']},
        ]
        new_profile = server.ServerProfile('t1', new_spec)

        networks_created, networks_deleted = profile._update_network(
            obj, new_profile)

        self.assertTrue(networks_created)
        self.assertTrue(networks_deleted)

        networks_create = [
            {'floating_network': None, 'network': 'net1', 'fixed_ip': 'ip2',
             'floating_ip': None, 'port': None, 'vnic_type': None,
             'security_groups': None},
            {'floating_network': None, 'network': 'net2', 'fixed_ip': None,
             'floating_ip': None, 'port': None, 'vnic_type': None,
             'security_groups': None},
            {'floating_network': None, 'network': None, 'fixed_ip': None,
             'floating_ip': None, 'port': 'port4', 'vnic_type': None,
             'security_groups': None}
        ]
        mock_create.assert_called_once_with(obj, networks_create)
        networks_delete = [
            {'floating_network': None, 'network': 'net1', 'fixed_ip': 'ip1',
             'floating_ip': None, 'port': None, 'vnic_type': None,
             'security_groups': None},
            {'floating_network': None, 'network': 'net1', 'fixed_ip': None,
             'floating_ip': None, 'port': None, 'vnic_type': None,
             'security_groups': None},
            {'floating_network': None, 'network': None, 'fixed_ip': None,
             'floating_ip': None, 'port': 'port3', 'vnic_type': None,
             'security_groups': None}
        ]
        mock_delete.assert_called_once_with(obj, networks_delete)
        networks_update = [
            {'network': 'net3', 'port': None, 'fixed_ip': 'ip1',
             'security_groups': ['default'], 'floating_network': None,
             'vnic_type': None, 'floating_ip': None},
            {'network': 'net4', 'port': None, 'fixed_ip': 'ip1',
             'security_groups': ['default'], 'floating_network': None,
             'vnic_type': None, 'floating_ip': None},
            {'network': None, 'port': 'port5', 'fixed_ip': None,
             'security_groups': ['default', 'blah'], 'floating_network': None,
             'vnic_type': None, 'floating_ip': None}
        ]
        mock_update.assert_called_once_with(obj, networks_update)

    @mock.patch.object(server.ServerProfile, '_update_password')
    @mock.patch.object(server.ServerProfile, '_check_password')
    @mock.patch.object(server.ServerProfile, '_update_name')
    @mock.patch.object(server.ServerProfile, '_check_server_name')
    @mock.patch.object(server.ServerProfile, '_update_flavor')
    @mock.patch.object(server.ServerProfile, '_update_metadata')
    @mock.patch.object(server.ServerProfile, '_update_image')
    @mock.patch.object(server.ServerProfile, '_update_network')
    def test_do_update_name_succeeded(self, mock_update_network,
                                      mock_update_image, mock_update_metadata,
                                      mock_update_flavor, mock_check_name,
                                      mock_update_name, mock_check_password,
                                      mock_update_password):
        mock_check_name.return_value = True, 'NEW_NAME'
        mock_check_password.return_value = True, 'NEW_PASSWORD'
        mock_update_image.return_value = False
        mock_update_flavor.return_value = False
        mock_update_network.return_value = False, False
        obj = mock.Mock(physical_id='FAKE_ID')

        profile = server.ServerProfile('t', self.spec)
        profile._computeclient = mock.Mock()
        profile._computeclient.server_get = mock.Mock()
        profile._computeclient.server_start = mock.Mock()
        new_profile = server.ServerProfile('t', self.spec)

        res = profile.do_update(obj, new_profile)

        self.assertTrue(res)
        mock_check_name.assert_called_once_with(obj, new_profile)
        mock_update_metadata.assert_called_once_with(obj, new_profile)
        mock_update_image.assert_called_once_with(
            obj, new_profile, 'NEW_NAME', 'NEW_PASSWORD')
        mock_update_name.assert_called_once_with(obj, 'NEW_NAME')
        mock_update_password.assert_called_once_with(obj, 'NEW_PASSWORD')
        mock_update_flavor.assert_called_once_with(obj, new_profile)
        mock_update_network.assert_called_once_with(obj, new_profile)

    @mock.patch.object(server.ServerProfile, '_update_password')
    @mock.patch.object(server.ServerProfile, '_check_password')
    @mock.patch.object(server.ServerProfile, '_update_name')
    @mock.patch.object(server.ServerProfile, '_check_server_name')
    @mock.patch.object(server.ServerProfile, '_update_flavor')
    @mock.patch.object(server.ServerProfile, '_update_metadata')
    @mock.patch.object(server.ServerProfile, '_update_image')
    @mock.patch.object(server.ServerProfile, '_update_network')
    def test_do_update_name_no_change(self, mock_update_network,
                                      mock_update_image, mock_update_metadata,
                                      mock_update_flavor, mock_check_name,
                                      mock_update_name, mock_check_password,
                                      mock_update_password):
        mock_check_name.return_value = False, 'NEW_NAME'
        mock_check_password.return_value = False, 'OLD_PASS'
        mock_update_network.return_value = False, False
        obj = mock.Mock(physical_id='NOVA_ID')

        profile = server.ServerProfile('t', self.spec)
        profile._computeclient = mock.Mock()
        new_profile = server.ServerProfile('t', self.spec)

        res = profile.do_update(obj, new_profile)

        self.assertTrue(res)
        mock_check_name.assert_called_once_with(obj, new_profile)
        mock_check_password.assert_called_once_with(obj, new_profile)
        mock_update_image.assert_called_once_with(
            obj, new_profile, 'NEW_NAME', 'OLD_PASS')
        self.assertEqual(0, mock_update_name.call_count)
        self.assertEqual(0, mock_update_password.call_count)
        mock_update_flavor.assert_called_once_with(obj, new_profile)
        mock_update_network.assert_called_once_with(obj, new_profile)
        mock_update_metadata.assert_called_once_with(obj, new_profile)

    @mock.patch.object(server.ServerProfile, '_update_password')
    @mock.patch.object(server.ServerProfile, '_check_password')
    @mock.patch.object(server.ServerProfile, '_update_name')
    @mock.patch.object(server.ServerProfile, '_check_server_name')
    @mock.patch.object(server.ServerProfile, '_update_flavor')
    @mock.patch.object(server.ServerProfile, '_update_metadata')
    @mock.patch.object(server.ServerProfile, '_update_image')
    @mock.patch.object(server.ServerProfile, '_update_network')
    def test_do_update_name_failed(self, mock_update_network,
                                   mock_update_image, mock_update_metadata,
                                   mock_update_flavor, mock_check_name,
                                   mock_update_name, mock_check_password,
                                   mock_update_password):
        mock_check_name.return_value = True, 'NEW_NAME'
        mock_check_password.return_value = False, 'OLD_PASS'
        mock_update_image.return_value = False
        err = exc.EResourceUpdate(type='server', id='NOVA_ID', message='BANG')
        mock_update_name.side_effect = err
        obj = mock.Mock(physical_id='NOVA_ID')

        profile = server.ServerProfile('t', self.spec)
        profile._computeclient = mock.Mock()
        new_profile = server.ServerProfile('t', self.spec)

        ex = self.assertRaises(exc.EResourceUpdate,
                               profile.do_update,
                               obj, new_profile)

        self.assertEqual("Failed in updating server 'NOVA_ID': BANG.",
                         str(ex))
        mock_check_name.assert_called_once_with(obj, new_profile)
        mock_check_password.assert_called_once_with(obj, new_profile)
        mock_update_image.assert_called_once_with(
            obj, new_profile, 'NEW_NAME', 'OLD_PASS')
        mock_update_name.assert_called_once_with(obj, 'NEW_NAME')
        self.assertEqual(0, mock_update_password.call_count)
        self.assertEqual(0, mock_update_flavor.call_count)
        self.assertEqual(0, mock_update_metadata.call_count)

    @mock.patch.object(server.ServerProfile, '_update_password')
    @mock.patch.object(server.ServerProfile, '_update_name')
    @mock.patch.object(server.ServerProfile, '_check_password')
    @mock.patch.object(server.ServerProfile, '_check_server_name')
    @mock.patch.object(server.ServerProfile, '_update_flavor')
    @mock.patch.object(server.ServerProfile, '_update_image')
    def test_do_update_image_succeeded(self, mock_update_image,
                                       mock_update_flavor, mock_check_name,
                                       mock_check_password, mock_update_name,
                                       mock_update_password):
        mock_check_name.return_value = False, 'OLD_NAME'
        mock_check_password.return_value = False, 'OLD_PASS'
        mock_update_image.return_value = True
        obj = mock.Mock()
        obj.physical_id = 'FAKE_ID'

        profile = server.ServerProfile('t', self.spec)
        profile._computeclient = mock.Mock()
        profile._computeclient.server_get = mock.Mock()
        profile._computeclient.server_get.return_value = mock.Mock(
            status=consts.VS_SHUTOFF)
        new_spec = copy.deepcopy(self.spec)
        new_spec['properties']['image'] = 'FAKE_IMAGE_NEW'
        new_profile = server.ServerProfile('t', new_spec)

        res = profile.do_update(obj, new_profile)

        self.assertTrue(res)
        mock_update_image.assert_called_with(
            obj, new_profile, 'OLD_NAME', 'OLD_PASS')
        self.assertEqual(0, mock_update_name.call_count)
        self.assertEqual(0, mock_update_password.call_count)
        profile._computeclient.server_get.assert_called_once_with(
            obj.physical_id)
        profile._computeclient.server_start.assert_called_once_with(
            obj.physical_id)

    @mock.patch.object(server.ServerProfile, '_update_flavor')
    @mock.patch.object(server.ServerProfile, '_update_name')
    @mock.patch.object(server.ServerProfile, '_update_metadata')
    @mock.patch.object(server.ServerProfile, '_update_image')
    @mock.patch.object(server.ServerProfile, '_check_password')
    @mock.patch.object(server.ServerProfile, '_check_server_name')
    def test_do_update_image_failed(self, mock_check_name, mock_check_password,
                                    mock_update_image, mock_update_meta,
                                    mock_update_name, mock_update_flavor):
        mock_check_name.return_value = False, 'OLD_NAME'
        mock_check_password.return_value = False, 'OLD_PASS'
        # _update_image always throw EResourceUpdate
        ex = exc.EResourceUpdate(type='server', id='NOVA_ID',
                                 message='Image Not Found')
        mock_update_image.side_effect = ex
        obj = mock.Mock(physical_id='NOVA_ID')

        profile = server.ServerProfile('t', self.spec)
        profile._computeclient = mock.Mock()
        # don't need to invent a new spec
        new_spec = copy.deepcopy(self.spec)
        new_profile = server.ServerProfile('t', new_spec)

        ex = self.assertRaises(exc.EResourceUpdate,
                               profile.do_update,
                               obj, new_profile)

        mock_update_image.assert_called_with(
            obj, new_profile, 'OLD_NAME', 'OLD_PASS')
        self.assertEqual("Failed in updating server 'NOVA_ID': "
                         "Image Not Found.", str(ex))

    @mock.patch.object(server.ServerProfile, '_update_flavor')
    def test_do_update_update_flavor_succeeded(self, mock_update_flavor):
        mock_update_flavor.return_value = True
        obj = mock.Mock(physical_id='FAKE_ID')
        profile = server.ServerProfile('t', self.spec)
        x_image = {'id': '123'}
        x_server = mock.Mock(image=x_image, status=consts.VS_SHUTOFF)
        cc = mock.Mock()
        cc.server_get.return_value = x_server
        gc = mock.Mock()
        profile._computeclient = cc
        profile._glanceclient = gc
        new_spec = copy.deepcopy(self.spec)
        new_spec['properties']['flavor'] = 'FAKE_FLAVOR_NEW'
        new_profile = server.ServerProfile('t', new_spec)

        res = profile.do_update(obj, new_profile)
        self.assertTrue(res)
        mock_update_flavor.assert_called_with(obj, new_profile)
        gc.image_find.assert_called_with('FAKE_IMAGE', False)
        cc.server_start.assert_called_once_with(obj.physical_id)

    @mock.patch.object(server.ServerProfile, '_update_flavor')
    def test_do_update_update_flavor_failed(self, mock_update_flavor):
        ex = exc.EResourceUpdate(type='server', id='NOVA_ID',
                                 message='Flavor Not Found')
        mock_update_flavor.side_effect = ex
        obj = mock.Mock(physical_id='NOVA_ID')
        profile = server.ServerProfile('t', self.spec)
        x_image = {'id': '123'}
        x_server = mock.Mock(image=x_image, status=consts.VS_ACTIVE)
        cc = mock.Mock()
        cc.server_get.return_value = x_server
        gc = mock.Mock()
        profile._computeclient = cc
        profile._glanceclient = gc
        new_spec = copy.deepcopy(self.spec)
        new_spec['properties']['flavor'] = 'FAKE_FLAVOR_NEW'
        new_profile = server.ServerProfile('t', new_spec)

        ex = self.assertRaises(exc.EResourceUpdate,
                               profile.do_update,
                               obj, new_profile)

        mock_update_flavor.assert_called_with(obj, new_profile)
        self.assertEqual("Failed in updating server 'NOVA_ID': "
                         "Flavor Not Found.",
                         str(ex))
        gc.image_find.assert_called_with('FAKE_IMAGE', False)

    @mock.patch.object(server.ServerProfile, '_update_flavor')
    @mock.patch.object(server.ServerProfile, '_update_network')
    def test_do_update_update_network_succeeded(
            self, mock_update_network, mock_update_flavor):
        mock_update_network.return_value = True, True
        profile = server.ServerProfile('t', self.spec)
        x_image = {'id': '123'}
        x_server = mock.Mock(image=x_image, status=consts.VS_SHUTOFF)
        cc = mock.Mock()
        gc = mock.Mock()
        cc.server_get.return_value = x_server
        profile._computeclient = cc
        profile._glanceclient = gc

        obj = mock.Mock(physical_id='NOVA_ID')

        new_spec = copy.deepcopy(self.spec)
        new_spec['properties']['networks'] = [
            {'network': 'new_net', 'port': 'new_port', 'fixed_ip': 'new-ip'}
        ]
        new_profile = server.ServerProfile('t', new_spec)

        params = {'cluster.stop_timeout_before_update': 134}

        res = profile.do_update(obj, new_profile=new_profile, **params)

        self.assertTrue(res)
        gc.image_find.assert_called_with('FAKE_IMAGE', False)
        mock_update_network.assert_called_with(obj, new_profile)
        cc.server_start.assert_called_once_with(obj.physical_id)
        self.assertEqual(profile.stop_timeout,
                         params['cluster.stop_timeout_before_update'])

    @mock.patch.object(server.ServerProfile, '_update_password')
    @mock.patch.object(server.ServerProfile, '_check_password')
    @mock.patch.object(server.ServerProfile, '_update_name')
    @mock.patch.object(server.ServerProfile, '_check_server_name')
    @mock.patch.object(server.ServerProfile, '_update_flavor')
    @mock.patch.object(server.ServerProfile, '_update_metadata')
    @mock.patch.object(server.ServerProfile, '_update_image')
    @mock.patch.object(server.ServerProfile, '_update_network')
    def test_do_update_update_network_failed(
            self, mock_update_network, mock_update_image, mock_update_metadata,
            mock_update_flavor, mock_check_name, mock_update_name,
            mock_check_password, mock_update_password):

        mock_check_name.return_value = True, 'NEW_NAME'
        mock_check_password.return_value = True, 'NEW_PASSWORD'
        mock_update_image.return_value = True
        err = exc.EResourceUpdate(type='server', id='NOVA_ID', message='BOOM')
        mock_update_network.side_effect = err

        profile = server.ServerProfile('t', self.spec)
        profile._computeclient = mock.Mock()
        new_network = {
            'port': 'FAKE_PORT_NEW',
            'fixed_ip': 'FAKE_IP_NEW',
            'network': 'FAKE_NET_NEW',
        }
        new_spec = copy.deepcopy(self.spec)
        new_spec['properties']['networks'] = [new_network]
        new_profile = server.ServerProfile('t', new_spec)
        obj = mock.Mock(physical_id='NOVA_ID')

        ex = self.assertRaises(exc.EResourceUpdate,
                               profile.do_update,
                               obj, new_profile)

        self.assertEqual("Failed in updating server 'NOVA_ID': BOOM.",
                         str(ex))
        mock_check_name.assert_called_once_with(obj, new_profile)
        mock_check_password.assert_called_once_with(obj, new_profile)
        mock_update_image.assert_called_once_with(
            obj, new_profile, 'NEW_NAME', 'NEW_PASSWORD')
        self.assertEqual(0, mock_update_name.call_count)
        self.assertEqual(0, mock_update_password.call_count)
        mock_update_flavor.assert_called_once_with(obj, new_profile)
        mock_update_network.assert_called_with(obj, new_profile)
        self.assertEqual(0, mock_update_metadata.call_count)

    def test_do_update_without_profile(self):
        profile = server.ServerProfile('t', self.spec)
        obj = mock.Mock()
        obj.physical_id = 'FAKE_ID'
        new_profile = None
        res = profile.do_update(obj, new_profile)
        self.assertFalse(res)

    def test_do_update_no_physical_id(self):
        profile = server.ServerProfile('t', self.spec)
        profile._computeclient = mock.Mock()
        node_obj = mock.Mock(physical_id=None)
        new_profile = mock.Mock()

        # Test path where server doesn't exist
        res = profile.do_update(node_obj, new_profile)

        self.assertFalse(res)

    def test_do_update_invalid_stop_timeout(self):
        profile = server.ServerProfile('t', self.spec)
        profile._computeclient = mock.Mock()
        node_obj = mock.Mock(physical_id='NOVA_ID')
        new_spec = copy.deepcopy(self.spec)
        new_profile = server.ServerProfile('t', new_spec)

        params = {'cluster.stop_timeout_before_update': '123'}
        ex = self.assertRaises(exc.EResourceUpdate,
                               profile.do_update,
                               node_obj, new_profile, **params)

        self.assertEqual("Failed in updating server 'NOVA_ID': "
                         "cluster.stop_timeout_before_update value must be of "
                         "type int.",
                         str(ex))
