Ejemplo n.º 1
0
    def container_by_id(self, container_id):
        """Get container from new release by id

        :param container_id: id of container
        """
        filtered_containers = filter(lambda c: c['id'] == container_id,
                                     self.new_release_containers)

        if not filtered_containers:
            raise errors.CannotFindContainerError(
                'Cannot find container with id {0}'.format(container_id))

        return filtered_containers[0]
Ejemplo n.º 2
0
    def container_docker_id(self, name):
        """Returns running container with specified name

        :param name: name of the container
        :returns: id of the container or None if not found
        :raises CannotFindContainerError:
        """
        containers_with_name = self._get_containers_by_name(name)
        running_containers = filter(lambda c: c['Status'].startswith('Up'),
                                    containers_with_name)

        if not running_containers:
            raise errors.CannotFindContainerError(
                'Cannot find running container with name "{0}"'.format(name))

        return running_containers[0]['Id']
Ejemplo n.º 3
0
class TestDockerUpgrader(BaseTestCase):

    def setUp(self):
        # NOTE (eli): mocking doesn't work correctly
        # when we try to patch docker client with
        # class decorator, it's the reason why
        # we have to do it explicitly
        self.docker_patcher = mock.patch(
            'fuel_upgrade.engines.docker_engine.docker.Client')
        self.docker_mock_class = self.docker_patcher.start()
        self.docker_mock = mock.MagicMock()
        self.docker_mock_class.return_value = self.docker_mock

        self.supervisor_patcher = mock.patch(
            'fuel_upgrade.engines.docker_engine.SupervisorClient')
        self.supervisor_class = self.supervisor_patcher.start()
        self.supervisor_mock = mock.MagicMock()
        self.supervisor_class.return_value = self.supervisor_mock

        self.version_mock = mock.MagicMock()

        with mock.patch('fuel_upgrade.engines.docker_engine.utils'):
            with mock.patch('fuel_upgrade.engines.docker_engine.VersionFile',
                            return_value=self.version_mock):
                self.upgrader = DockerUpgrader(self.fake_config)
                self.upgrader.upgrade_verifier = mock.MagicMock()

        self.pg_dump_path = '/var/lib/fuel_upgrade/9999/pg_dump_all.sql'

    def tearDown(self):
        self.docker_patcher.stop()
        self.supervisor_patcher.stop()

    def mock_methods(self, obj, methods):
        for method in methods:
            setattr(obj, method, mock.MagicMock())

    def test_upgrade(self):
        mocked_methods = [
            'stop_fuel_containers',
            'save_db',
            'save_cobbler_configs',
            'save_astute_keys',
            'upload_images',
            'create_and_start_new_containers',
            'generate_configs',
            'switch_to_new_configs']

        self.mock_methods(self.upgrader, mocked_methods)
        self.upgrader.upgrade()

        self.assertEqual(
            self.upgrader.generate_configs.call_args_list,
            [mock.call(autostart=False),
             mock.call(autostart=True)])

        self.called_once(self.upgrader.stop_fuel_containers)
        self.called_once(self.supervisor_mock.stop_all_services)
        self.called_once(self.supervisor_mock.restart_and_wait)
        self.called_once(self.upgrader.upgrade_verifier.verify)
        self.called_once(self.version_mock.save_current)
        self.called_once(self.version_mock.switch_to_new)

    def test_rollback(self):
        self.upgrader.stop_fuel_containers = mock.MagicMock()
        self.upgrader.switch_version_file_to_previous_version = \
            mock.MagicMock()
        self.upgrader.rollback()

        self.called_times(self.upgrader.stop_fuel_containers, 1)
        self.called_once(self.supervisor_mock.switch_to_previous_configs)
        self.called_once(self.supervisor_mock.stop_all_services)
        self.called_once(self.supervisor_mock.restart_and_wait)
        self.called_once(self.version_mock.save_current)
        self.called_once(self.version_mock.switch_to_previous)

    @mock.patch('fuel_upgrade.engines.docker_engine.utils')
    @mock.patch('fuel_upgrade.engines.docker_engine.glob.glob',
                return_value=['file1', 'file2'])
    def test_on_success(self, glob_mock, utils_mock):
        self.upgrader.on_success()
        glob_mock.assert_called_once_with(self.fake_config.version_files_mask)
        self.assertEqual(
            utils_mock.remove.call_args_list,
            [mock.call('file1'), mock.call('file2')])

    def test_stop_fuel_containers(self):
        non_fuel_images = [
            'first_image_1.0', 'second_image_2.0', 'third_image_2.0']
        fuel_images = [
            'fuel/image_1.0', 'fuel/image_2.0']

        all_images = [{'Image': v, 'Id': i}
                      for i, v in enumerate(non_fuel_images + fuel_images)]

        ports = [1, 2, 3]
        self.upgrader._get_docker_container_public_ports = mock.MagicMock(
            return_value=ports)
        self.docker_mock.containers.return_value = all_images
        self.upgrader.stop_fuel_containers()
        self.assertEqual(
            self.docker_mock.stop.call_args_list,
            [mock.call(3, 20), mock.call(4, 20)])

    @mock.patch('fuel_upgrade.engines.docker_engine.utils.exec_cmd')
    @mock.patch('fuel_upgrade.engines.docker_engine.os.path.exists',
                return_value=True)
    def test_upload_images(self, _, exec_mock):
        self.upgrader.new_release_images = [
            {'docker_image': 'image1'},
            {'docker_image': 'image2'}]

        self.upgrader.upload_images()
        self.assertEqual(
            exec_mock.call_args_list,
            [mock.call('docker load < "image1"'),
             mock.call('docker load < "image2"')])

    def test_create_containers(self):
        fake_containers = [
            {'id': 'id1',
             'container_name': 'name1',
             'image_name': 'i_name1',
             'volumes_from': ['id2']},
            {'id': 'id2',
             'image_name': 'i_name2',
             'container_name': 'name2',
             'after_container_creation_command': 'cmd',
             'supervisor_config': True}]

        self.upgrader.new_release_containers = fake_containers

        def mocked_create_container(*args, **kwargs):
            """Return name of the container
            """
            return kwargs['name']

        self.upgrader.create_container = mock.MagicMock(
            side_effect=mocked_create_container)
        self.upgrader.start_container = mock.MagicMock()
        self.upgrader.run_after_container_creation_command = mock.MagicMock()
        self.upgrader.clean_iptables_rules = mock.MagicMock()
        self.upgrader.start_service_under_supervisor = mock.MagicMock()

        self.upgrader.create_and_start_new_containers()

        create_container_calls = [
            mock.call('i_name2', detach=False, ports=None,
                      volumes=None, name='name2'),
            mock.call('i_name1', detach=False, ports=None,
                      volumes=None, name='name1')]

        start_container_calls = [
            mock.call('name2', volumes_from=[],
                      binds=None, port_bindings=None,
                      privileged=False, links=[]),
            mock.call('name1', volumes_from=['name2'],
                      binds=None, port_bindings=None,
                      privileged=False, links=[])]

        self.upgrader.clean_iptables_rules.assert_called_once_with(
            fake_containers[-1])
        self.upgrader.start_service_under_supervisor.assert_called_once_with(
            'docker-id2')
        self.assertEqual(
            self.upgrader.create_container.call_args_list,
            create_container_calls)
        self.assertEqual(
            self.upgrader.start_container.call_args_list,
            start_container_calls)
        self.called_once(self.upgrader.run_after_container_creation_command)

    def test_run_after_container_creation_command(self):
        self.upgrader.exec_with_retries = mock.MagicMock()
        self.upgrader.run_after_container_creation_command({
            'after_container_creation_command': 'cmd',
            'container_name': 'name'})

        args, kwargs = self.upgrader.exec_with_retries.call_args

        self.assertEqual(args[1], errors.ExecutedErrorNonZeroExitCode)
        self.assertEqual(kwargs, {'retries': 30, 'interval': 4})

    def test_create_container(self):
        self.upgrader.create_container(
            'image_name', param1=1, param2=2, ports=[1234])

        self.docker_mock.create_container.assert_called_once_with(
            'image_name', param2=2, param1=1, ports=[1234])

    def test_start_container(self):
        self.upgrader.start_container(
            {'Id': 'container_id'}, param1=1, param2=2)

        self.docker_mock.start.assert_called_once_with(
            'container_id', param2=2, param1=1)

    def test_build_dependencies_graph(self):
        containers = [
            {'id': '1', 'volumes_from': ['2'], 'links': [{'id': '3'}]},
            {'id': '2', 'volumes_from': [], 'links': []},
            {'id': '3', 'volumes_from': [], 'links': [{'id': '2'}]}]

        actual_graph = self.upgrader.build_dependencies_graph(containers)
        expected_graph = {
            '1': ['2', '3'],
            '2': [],
            '3': ['2']}

        self.assertEqual(actual_graph, expected_graph)

    def test_get_container_links(self):
        fake_containers = [
            {'id': 'id1', 'container_name': 'container_name1',
             'links': [{'id': 'id2', 'alias': 'alias2'}]},
            {'id': 'id2', 'container_name': 'container_name2'}]
        self.upgrader.new_release_containers = fake_containers
        links = self.upgrader.get_container_links(fake_containers[0])
        self.assertEqual(links, [('container_name2', 'alias2')])

    def test_get_ports(self):
        ports = self.upgrader.get_ports({'ports': [[53, 'udp'], 100]})
        self.assertEqual([(53, 'udp'), 100], ports)

    def test_generate_configs(self):
        fake_containers = [
            {'id': 'id1', 'container_name': 'container_name1',
             'supervisor_config': False},
            {'id': 'id2', 'container_name': 'container_name2',
             'supervisor_config': True},
            {'id': 'cobbler', 'container_name': 'cobbler_container',
             'supervisor_config': False}]
        self.upgrader.new_release_containers = fake_containers
        self.upgrader.generate_configs()
        self.supervisor_mock.generate_configs.assert_called_once_with(
            [{'config_name': 'id2',
              'service_name': 'docker-id2',
              'command': 'docker start -a container_name2',
              'autostart': True}])
        self.supervisor_mock.generate_cobbler_config.assert_called_once_with(
            'cobbler',
            'docker-cobbler',
            'cobbler_container',
            autostart=True)

    def test_switch_to_new_configs(self):
        self.upgrader.switch_to_new_configs()
        self.supervisor_mock.switch_to_new_configs.assert_called_once_with()

    @mock.patch('fuel_upgrade.engines.docker_engine.utils.exec_cmd')
    def test_exec_cmd_in_container(self, exec_cmd_mock):
        name = 'container_name'
        cmd = 'some command'

        self.upgrader.container_docker_id = mock.MagicMock(return_value=name)
        self.upgrader.exec_cmd_in_container(name, cmd)

        self.called_once(self.upgrader.container_docker_id)
        exec_cmd_mock.assert_called_once_with(
            "lxc-attach --name {0} -- {1}".format(name, cmd))

    @mock.patch('fuel_upgrade.engines.docker_engine.'
                'utils.exec_cmd')
    @mock.patch('fuel_upgrade.engines.docker_engine.'
                'DockerUpgrader.verify_cobbler_configs')
    def test_save_cobbler_configs(self, verify_mock, exec_cmd_mock):
        self.upgrader.save_cobbler_configs()

        exec_cmd_mock.assert_called_once_with(
            'docker cp fuel-core-0-cobbler:/var/lib/cobbler/config '
            '/var/lib/fuel_upgrade/9999/cobbler_configs')
        self.called_once(verify_mock)

    @mock.patch('fuel_upgrade.engines.docker_engine.utils.rmtree')
    @mock.patch('fuel_upgrade.engines.docker_engine.utils.exec_cmd',
                side_effect=errors.ExecutedErrorNonZeroExitCode())
    def test_save_cobbler_configs_removes_dir_in_case_of_error(
            self, exec_cmd_mock, rm_mock):

        with self.assertRaises(errors.ExecutedErrorNonZeroExitCode):
            self.upgrader.save_cobbler_configs()

        cobbler_config_path = '/var/lib/fuel_upgrade/9999/cobbler_configs'
        exec_cmd_mock.assert_called_once_with(
            'docker cp fuel-core-0-cobbler:/var/lib/cobbler/config '
            '{0}'.format(cobbler_config_path))
        rm_mock.assert_called_once_with(cobbler_config_path)

    @mock.patch('fuel_upgrade.engines.docker_engine.utils')
    def test_save_astute_keys(self, utils_mock):
        self.upgrader.save_astute_keys()
        utils_mock.exec_cmd.assert_called_once_with(
            'docker cp fuel-core-0-astute:/var/lib/astute '
            '/var/lib/fuel_upgrade/9999')
        utils_mock.remove.assert_called_once_with(
            '/var/lib/fuel_upgrade/9999/astute')

    @mock.patch('fuel_upgrade.engines.docker_engine.utils.exec_cmd',
                side_effect=errors.ExecutedErrorNonZeroExitCode())
    @mock.patch('fuel_upgrade.engines.docker_engine.os')
    @mock.patch('fuel_upgrade.engines.docker_engine.utils.remove')
    def test_save_astute_keys_creates_dir_if_error(
            self, remove_mock, os_mock, exec_cmd_mock):
        self.upgrader.save_astute_keys()
        exec_cmd_mock.assert_called_once_with(
            'docker cp fuel-core-0-astute:/var/lib/astute '
            '/var/lib/fuel_upgrade/9999')
        remove_mock.assert_called_once_with(
            '/var/lib/fuel_upgrade/9999/astute')
        os_mock.mkdir.assert_called_once_with(
            '/var/lib/fuel_upgrade/9999/astute')

    @mock.patch('fuel_upgrade.engines.docker_engine.glob.glob',
                return_value=['1.json'])
    @mock.patch('fuel_upgrade.engines.docker_engine.utils.'
                'check_file_is_valid_json')
    def test_verify_cobbler_configs(self, json_checker_mock, glob_mock):
        self.upgrader.verify_cobbler_configs()
        glob_mock.assert_called_once_with(
            '/var/lib/fuel_upgrade/9999/'
            'cobbler_configs/config/systems.d/*.json')
        json_checker_mock.assert_called_once_with('1.json')

    @mock.patch('fuel_upgrade.engines.docker_engine.glob.glob',
                return_value=[])
    def test_verify_cobbler_configs_raises_error_if_not_enough_systems(
            self, glob_mock):

        with self.assertRaises(errors.WrongCobblerConfigsError):
            self.upgrader.verify_cobbler_configs()
        self.called_once(glob_mock)

    @mock.patch('fuel_upgrade.engines.docker_engine.glob.glob',
                return_value=['1.json'])
    @mock.patch('fuel_upgrade.engines.docker_engine.utils.'
                'check_file_is_valid_json', return_value=False)
    def test_verify_cobbler_configs_raises_error_if_invalid_file(
            self, json_checker_mock, glob_mock):

        with self.assertRaises(errors.WrongCobblerConfigsError):
            self.upgrader.verify_cobbler_configs()

        self.called_once(glob_mock)
        self.called_once(json_checker_mock)

    def test_get_docker_container_public_ports(self):
        docker_ports_mapping = [
            {'Ports': [
                {'PublicPort': 514},
                {'PublicPort': 515}]},
            {'Ports': [
                {'PublicPort': 516},
                {'PublicPort': 517}]}]

        self.assertEquals(
            [514, 515, 516, 517],
            self.upgrader._get_docker_container_public_ports(
                docker_ports_mapping))

    @mock.patch('fuel_upgrade.engines.docker_engine.utils.safe_exec_cmd')
    def test_clean_iptables_rules(self, exec_cmd_mock):
        containers = [
            {'id': 'astute', 'port_bindings': ['some_ports']},
            {'id': 'some_volume_container'},
            {'id': 'ostf', 'port_bindings': ['some_ports']}]

        for container in containers:
            with mock.patch('fuel_upgrade.engines.docker_engine.'
                            'DockerUpgrader._log_iptables') as log_mock:
                self.upgrader.clean_iptables_rules(container)

        self.called_times(log_mock, 2)
        self.assertEqual(
            exec_cmd_mock.call_args_list,
            [mock.call('dockerctl post_start_hooks astute'),
             mock.call('service iptables save'),
             mock.call('dockerctl post_start_hooks ostf'),
             mock.call('service iptables save')])

    @mock.patch('fuel_upgrade.engines.docker_engine.utils.files_size',
                return_value=5)
    def test_required_free_space(self, _):
        self.assertEqual(
            self.upgrader.required_free_space,
            {'/var/lib/fuel_upgrade/9999': 150,
             '/var/lib/docker': 5,
             '/etc/fuel/': 10,
             '/etc/supervisord.d/': 10})

    @mock.patch('fuel_upgrade.engines.docker_engine.utils')
    def test_save_db_succeed(self, mock_utils):
        with mock.patch('fuel_upgrade.engines.docker_engine.'
                        'utils.VersionedFile') as version_mock:
            version_mock.return_value.next_file_name.return_value = 'file3'
            version_mock.return_value.sorted_files.return_value = [
                'file3', 'file2', 'file1']

            self.upgrader.save_db()
        self.called_once(mock_utils.wait_for_true)
        mock_utils.hardlink.assert_called_once_with(
            'file3',
            '/var/lib/fuel_upgrade/9999/pg_dump_all.sql',
            overwrite=True)

    @mock.patch('fuel_upgrade.engines.docker_engine.utils')
    def test_save_db_error_first_dump_is_invalid(self, mock_utils):
        with mock.patch('fuel_upgrade.engines.docker_engine.'
                        'utils.VersionedFile') as version_mock:
            version_mock.return_value.filter_files.return_value = []
            self.assertRaises(errors.DatabaseDumpError, self.upgrader.save_db)

        self.method_was_not_called(mock_utils.hardlink)

    @mock.patch('fuel_upgrade.engines.docker_engine.utils')
    def test_save_db_removes_old_dump_files(self, mock_utils):
        mock_utils.file_exists.return_value = True
        with mock.patch('fuel_upgrade.engines.docker_engine.'
                        'utils.VersionedFile') as version_mock:
            version_mock.return_value.sorted_files.return_value = [
                'file1', 'file2', 'file3', 'file4', 'file5']
            self.upgrader.save_db()

        self.assertEqual(
            mock_utils.remove_if_exists.call_args_list,
            [mock.call('file4'), mock.call('file5')])

    @mock.patch('fuel_upgrade.engines.docker_engine.'
                'DockerUpgrader.exec_cmd_in_container')
    @mock.patch('fuel_upgrade.engines.docker_engine.utils')
    def test_make_pg_dump_succeed(self, mock_utils, exec_mock):
        self.assertTrue(
            self.upgrader.make_pg_dump('tmp_path', self.pg_dump_path))
        self.method_was_not_called(mock_utils.file_exists)
        self.method_was_not_called(mock_utils.remove_if_exists)

        exec_mock.assert_called_once_with(
            'fuel-core-0-postgres',
            "su postgres -c 'pg_dumpall --clean' > tmp_path")

    @mock.patch('fuel_upgrade.engines.docker_engine.'
                'DockerUpgrader.exec_cmd_in_container',
                side_effect=errors.ExecutedErrorNonZeroExitCode())
    @mock.patch('fuel_upgrade.engines.docker_engine.utils')
    def test_make_pg_dump_error_failed_to_execute_dump_command(
            self, mock_utils, _):
        mock_utils.file_exists.return_value = False
        self.assertFalse(
            self.upgrader.make_pg_dump('tmp_path', self.pg_dump_path))
        mock_utils.file_exists.assert_called_once_with(self.pg_dump_path)
        mock_utils.remove_if_exists.assert_called_once_with('tmp_path')

    @mock.patch('fuel_upgrade.engines.docker_engine.'
                'DockerUpgrader.exec_cmd_in_container',
                side_effect=errors.CannotFindContainerError())
    @mock.patch('fuel_upgrade.engines.docker_engine.utils')
    def test_make_pg_dump_error_failed_because_of_stopped_container(
            self, mock_utils, exec_cmd_mock):
        mock_utils.file_exists.return_value = False
        self.assertFalse(
            self.upgrader.make_pg_dump('tmp_path', self.pg_dump_path))
        mock_utils.file_exists.assert_called_once_with(self.pg_dump_path)
        mock_utils.remove_if_exists.assert_called_once_with('tmp_path')

    @mock.patch('fuel_upgrade.engines.docker_engine.'
                'DockerUpgrader.exec_cmd_in_container',
                side_effect=errors.ExecutedErrorNonZeroExitCode())
    @mock.patch('fuel_upgrade.engines.docker_engine.utils')
    def test_make_pg_dump_second_run_failed_to_execute_dump_command(
            self, mock_utils, exec_cmd_mock):
        mock_utils.file_exists.return_value = True

        with mock.patch('fuel_upgrade.engines.docker_engine.'
                        'utils.VersionedFile') as version_mock:
            version_mock.return_value.sorted_files.return_value = [
                'file1', 'file2']

            self.assertTrue(
                self.upgrader.make_pg_dump('tmp_path', self.pg_dump_path))

        mock_utils.file_exists.assert_called_once_with(self.pg_dump_path)
        self.called_once(mock_utils.remove_if_exists)

    @mock.patch('fuel_upgrade.engines.docker_engine.'
                'DockerUpgrader.exec_cmd_in_container',
                side_effect=errors.CannotFindContainerError())
    @mock.patch('fuel_upgrade.engines.docker_engine.utils')
    def test_make_pg_dump_second_run_failed_because_of_stopped_container(
            self, mock_utils, _):
        mock_utils.file_exists.return_value = True
        with mock.patch('fuel_upgrade.engines.docker_engine.'
                        'utils.VersionedFile') as version_mock:
            version_mock.return_value.sorted_files.return_value = ['file1']
            self.assertTrue(
                self.upgrader.make_pg_dump('tmp_path', self.pg_dump_path))