# -*- coding: utf-8 -*-
'''
Unit tests for the dockerng state
'''

# Import Python Libs
from __future__ import absolute_import

# Import Salt Testing Libs
from salttesting import skipIf, TestCase
from salttesting.helpers import ensure_in_syspath
from salttesting.mock import (
    MagicMock,
    Mock,
    NO_MOCK,
    NO_MOCK_REASON,
    patch
)

ensure_in_syspath('../../')

# Import Salt Libs
from salt.exceptions import CommandExecutionError
from salt.modules import dockerng as dockerng_mod
from salt.states import dockerng as dockerng_state

dockerng_mod.__context__ = {'docker.docker_version': ''}
dockerng_mod.__salt__ = {}
dockerng_state.__context__ = {}
dockerng_state.__opts__ = {'test': False}


@skipIf(NO_MOCK, NO_MOCK_REASON)
class DockerngTestCase(TestCase):
    '''
    Validate dockerng state
    '''

    def test_running_with_no_predifined_volume(self):
        '''
        Test dockerng.running function with an image
        that doens't have VOLUME defined.

        The ``binds`` argument, should create a container
        with respective volumes extracted from ``binds``.
        '''
        dockerng_create = Mock()
        dockerng_start = Mock()
        __salt__ = {'dockerng.list_containers': MagicMock(),
                    'dockerng.list_tags': MagicMock(),
                    'dockerng.pull': MagicMock(),
                    'dockerng.state': MagicMock(),
                    'dockerng.inspect_image': MagicMock(),
                    'dockerng.create': dockerng_create,
                    'dockerng.start': dockerng_start,
                    }
        with patch.dict(dockerng_state.__dict__,
                        {'__salt__': __salt__}):
            dockerng_state.running(
                'cont',
                image='image:latest',
                binds=['/host-0:/container-0:ro'])
        dockerng_create.assert_called_with(
            'image:latest',
            validate_input=False,
            name='cont',
            binds={'/host-0': {'bind': '/container-0', 'ro': True}},
            volumes=['/container-0'],
            validate_ip_addrs=False,
            client_timeout=60)
        dockerng_start.assert_called_with('cont')

    def test_running_with_predifined_volume(self):
        '''
        Test dockerng.running function with an image
        that already have VOLUME defined.

        The ``binds`` argument, should create a container
        with ``volumes`` extracted from ``binds``.
        '''
        dockerng_create = Mock()
        dockerng_start = Mock()
        dockerng_inspect_image = Mock(return_value={
            'Id': 'abcd',
            'Config': {'Config': {'Volumes': ['/host-1']}},
        })
        __salt__ = {'dockerng.list_containers': MagicMock(),
                    'dockerng.list_tags': MagicMock(),
                    'dockerng.pull': MagicMock(),
                    'dockerng.state': MagicMock(),
                    'dockerng.inspect_image': dockerng_inspect_image,
                    'dockerng.create': dockerng_create,
                    'dockerng.start': dockerng_start,
                    }
        with patch.dict(dockerng_state.__dict__,
                        {'__salt__': __salt__}):
            dockerng_state.running(
                'cont',
                image='image:latest',
                binds=['/host-0:/container-0:ro'])
        dockerng_create.assert_called_with(
            'image:latest',
            validate_input=False,
            binds={'/host-0': {'bind': '/container-0', 'ro': True}},
            volumes=['/container-0'],
            validate_ip_addrs=False,
            name='cont',
            client_timeout=60)
        dockerng_start.assert_called_with('cont')

    def test_running_with_no_predifined_ports(self):
        '''
        Test dockerng.running function with an image
        that doens't have EXPOSE defined.

        The ``port_bindings`` argument, should create a container
        with ``ports`` extracted from ``port_bindings``.
        '''
        dockerng_create = Mock()
        dockerng_start = Mock()
        dockerng_inspect_image = Mock(return_value={
            'Id': 'abcd',
            'Config': {'Config': {'ExposedPorts': {}}},
        })
        __salt__ = {'dockerng.list_containers': MagicMock(),
                    'dockerng.list_tags': MagicMock(),
                    'dockerng.pull': MagicMock(),
                    'dockerng.state': MagicMock(),
                    'dockerng.inspect_image': dockerng_inspect_image,
                    'dockerng.create': dockerng_create,
                    'dockerng.start': dockerng_start,
                    }
        with patch.dict(dockerng_state.__dict__,
                        {'__salt__': __salt__}):
            dockerng_state.running(
                'cont',
                image='image:latest',
                port_bindings=['9090:9797/tcp'])
        dockerng_create.assert_called_with(
            'image:latest',
            validate_input=False,
            name='cont',
            ports=[9797],
            port_bindings={9797: [9090]},
            validate_ip_addrs=False,
            client_timeout=60)
        dockerng_start.assert_called_with('cont')

    def test_running_with_predifined_ports(self):
        '''
        Test dockerng.running function with an image
        that expose ports (via Dockerfile EXPOSE statement).

        Check that `ports` contains ports defined on Image and by
        `port_bindings` argument.

        Inside Dockerfile:

        .. code-block::

            EXPOSE 9898

        In sls:

        .. code-block:: yaml

            container:
                dockerng.running:
                    - port_bindings:
                        - '9090:9797/tcp'

        '''
        dockerng_create = Mock()
        dockerng_start = Mock()
        dockerng_inspect_image = Mock(return_value={
            'Id': 'abcd',
            'Config': {'ExposedPorts': {'9898/tcp': {}}}
        })
        __salt__ = {'dockerng.list_containers': MagicMock(),
                    'dockerng.list_tags': MagicMock(),
                    'dockerng.pull': MagicMock(),
                    'dockerng.state': MagicMock(),
                    'dockerng.inspect_image': dockerng_inspect_image,
                    'dockerng.create': dockerng_create,
                    'dockerng.start': dockerng_start,
                    }
        with patch.dict(dockerng_state.__dict__,
                        {'__salt__': __salt__}):
            dockerng_state.running(
                'cont',
                image='image:latest',
                port_bindings=['9090:9797/tcp'])
        dockerng_create.assert_called_with(
            'image:latest',
            validate_input=False,
            name='cont',
            ports=[9797],
            port_bindings={9797: [9090]},
            validate_ip_addrs=False,
            client_timeout=60)
        dockerng_start.assert_called_with('cont')

    def test_running_with_udp_bindings(self):
        '''
        Check that `ports` contains ports defined from `port_bindings` with
        protocol declaration passed as tuple. As stated by docker-py
        documentation

        https://docker-py.readthedocs.org/en/latest/port-bindings/

        In sls:

        .. code-block:: yaml

            container:
                dockerng.running:
                    - port_bindings:
                        - '9090:9797/udp'

        is equivalent of:

        .. code-block:: yaml

            container:
                dockerng.running:
                    - ports:
                        - 9797/udp
                    - port_bindings:
                        - '9090:9797/udp'
        '''
        dockerng_create = Mock()
        dockerng_start = Mock()
        dockerng_inspect_image = Mock(return_value={
            'Id': 'abcd',
            'Config': {'ExposedPorts': {}}
        })
        __salt__ = {'dockerng.list_containers': MagicMock(),
                    'dockerng.list_tags': MagicMock(),
                    'dockerng.pull': MagicMock(),
                    'dockerng.state': MagicMock(),
                    'dockerng.inspect_image': dockerng_inspect_image,
                    'dockerng.create': dockerng_create,
                    'dockerng.start': dockerng_start,
                    }
        with patch.dict(dockerng_state.__dict__,
                        {'__salt__': __salt__}):
            dockerng_state.running(
                'cont',
                image='image:latest',
                port_bindings=['9090:9797/udp'])
        dockerng_create.assert_called_with(
            'image:latest',
            validate_input=False,
            name='cont',
            ports=[(9797, 'udp')],
            port_bindings={'9797/udp': [9090]},
            validate_ip_addrs=False,
            client_timeout=60)
        dockerng_start.assert_called_with('cont')

    def test_running_compare_images_by_id(self):
        '''
        Make sure the container is running
        against expected image.

        Here the local image is named 'image:latest' and the container
        is also running against an image called 'image:latest'.
        Therefore the image ids are diverging because the tag 'image:latest'
        moved to a fresher image.
        Thus this test make sure the old container is droped and recreated.
        '''
        new_fake_image_id = 'abcdefgh'
        old_fake_image_id = '123456789'
        dockerng_inspect_image = Mock(return_value={'Id': new_fake_image_id})
        dockerng_inspect_container = Mock(
            return_value={'Image': old_fake_image_id,
                          'Config': {'Image': 'image:latest'}})
        dockerng_list_containers = Mock(return_value=['cont'])
        dockerng__state = Mock(return_value='running')
        dockerng_stop = Mock(return_value={'result': True})
        dockerng_rm = Mock(return_value=['container-id'])
        __salt__ = {'dockerng.list_containers': dockerng_list_containers,
                    'dockerng.inspect_container': dockerng_inspect_container,
                    'dockerng.inspect_image': dockerng_inspect_image,
                    'dockerng.list_tags': MagicMock(),
                    'dockerng.state': dockerng__state,
                    'dockerng.pull': MagicMock(return_value=new_fake_image_id),
                    'dockerng.create': MagicMock(return_value='new_container'),
                    'dockerng.start': MagicMock(),
                    'dockerng.stop': dockerng_stop,
                    'dockerng.rm': dockerng_rm,
                    }
        with patch.dict(dockerng_state.__dict__,
                        {'__salt__': __salt__}):
            ret = dockerng_state.running(
                'cont',
                image='image:latest',
                )
            dockerng_stop.assert_called_with('cont', timeout=10, unpause=True)
            dockerng_rm.assert_called_with('cont')
        self.assertEqual(ret, {'name': 'cont',
                               'comment': "Container 'cont' was replaced",
                               'result': True,
                               'changes': {'added': 'new_container',
                                           'image': new_fake_image_id,
                                           'removed': ['container-id']}
                               })

    def test_image_present_already_local(self):
        '''
        According following sls,

        .. code-block:: yaml

            image:latest:
              dockerng.image_present:
                - force: true

        if ``image:latest`` is already downloaded locally the state
        should not report changes.
        '''
        dockerng_inspect_image = Mock(
            return_value={'Id': 'abcdefghijk'})
        dockerng_pull = Mock(
            return_value={'Layers':
                          {'Already_Pulled': ['abcdefghijk'],
                           'Pulled': []},
                          'Status': 'Image is up to date for image:latest',
                          'Time_Elapsed': 1.1})
        dockerng_list_tags = Mock(
            return_value=['image:latest']
        )
        __salt__ = {'dockerng.list_tags': dockerng_list_tags,
                    'dockerng.pull': dockerng_pull,
                    'dockerng.inspect_image': dockerng_inspect_image,
                    }
        with patch.dict(dockerng_state.__dict__,
                        {'__salt__': __salt__}):
            ret = dockerng_state.image_present('image:latest', force=True)
            self.assertEqual(ret,
                             {'changes': {},
                              'result': True,
                              'comment': "Image 'image:latest' was pulled, "
                              "but there were no changes",
                              'name': 'image:latest',
                              })

    def test_image_present_and_force(self):
        '''
        According following sls,

        .. code-block:: yaml

            image:latest:
              dockerng.image_present:
                - force: true

        if ``image:latest`` is not downloaded and force is true
        should pull a new image successfuly.
        '''
        dockerng_inspect_image = Mock(
            side_effect=CommandExecutionError(
                'Error 404: No such image/container: image:latest'))
        dockerng_pull = Mock(
            return_value={'Layers':
                          {'Already_Pulled': ['abcdefghijk'],
                           'Pulled': ['abcdefghijk']},
                          'Status': "Image 'image:latest' was pulled",
                          'Time_Elapsed': 1.1})
        dockerng_list_tags = Mock(
            side_effect=[[], ['image:latest']]
        )
        __salt__ = {'dockerng.list_tags': dockerng_list_tags,
                    'dockerng.pull': dockerng_pull,
                    'dockerng.inspect_image': dockerng_inspect_image,
                    }
        with patch.dict(dockerng_state.__dict__,
                        {'__salt__': __salt__}):
            ret = dockerng_state.image_present('image:latest', force=True)
        self.assertEqual(ret,
                         {'changes': {
                             'Layers': {'Already_Pulled': ['abcdefghijk'],
                                        'Pulled': ['abcdefghijk']},
                             'Status': "Image 'image:latest' was pulled",
                             'Time_Elapsed': 1.1},
                             'result': True,
                             'comment': "Image 'image:latest' was pulled",
                             'name': 'image:latest',
                         })

    def test_check_start_false(self):
        '''
        If start is False, then dockerng.running will not try
        to start a container that is stopped.
        '''
        image_id = 'abcdefg'
        dockerng_create = Mock()
        dockerng_start = Mock()
        dockerng_list_containers = Mock(return_value=['cont'])
        dockerng_inspect_container = Mock(
            return_value={
                'Config': {
                    'Image': 'image:latest',
                    'Tty': False,
                    'Labels': {},
                    'Domainname': '',
                    'User': '',
                    'AttachStderr': True,
                    'AttachStdout': True,
                    'Hostname': 'saltstack-container',
                    'Env': [],
                    'WorkingDir': '/',
                    'Cmd': ['bash'],
                    'Volumes': {},
                    'Entrypoint': None,
                    'ExposedPorts': {},
                    'OpenStdin': False,
                },
                'HostConfig': {
                    'PublishAllPorts': False,
                    'Dns': [],
                    'Links': None,
                    'CpusetCpus': '',
                    'RestartPolicy': {'MaximumRetryCount': 0, 'Name': ''},
                    'CapAdd': None,
                    'NetworkMode': 'default',
                    'PidMode': '',
                    'MemorySwap': 0,
                    'ExtraHosts': None,
                    'PortBindings': None,
                    'LxcConf': None,
                    'DnsSearch': [],
                    'Privileged': False,
                    'Binds': None,
                    'Memory': 0,
                    'VolumesFrom': None,
                    'CpuShares': 0,
                    'CapDrop': None,
                },
                'NetworkSettings': {
                    'MacAddress': '00:00:00:00:00:01',
                },
                'Image': image_id})
        dockerng_inspect_image = MagicMock(
            return_value={
                'Id': image_id,
                'Config': {
                    'Hostname': 'saltstack-container',
                    'WorkingDir': '/',
                    'Cmd': ['bash'],
                    'Volumes': {},
                    'Entrypoint': None,
                    'ExposedPorts': {},
                },
                })
        __salt__ = {'dockerng.list_containers': dockerng_list_containers,
                    'dockerng.inspect_container': dockerng_inspect_container,
                    'dockerng.inspect_image': dockerng_inspect_image,
                    'dockerng.list_tags': MagicMock(
                        return_value=['image:latest']),
                    'dockerng.pull': MagicMock(),
                    'dockerng.state': MagicMock(side_effect=['stopped',
                                                             'running']),
                    'dockerng.create': dockerng_create,
                    'dockerng.start': dockerng_start,
                    }
        with patch.dict(dockerng_state.__dict__,
                        {'__salt__': __salt__}):
            ret = dockerng_state.running(
                'cont',
                image='image:latest',
                start=False,
                )
        self.assertEqual(ret, {'name': 'cont',
                               'comment': "Container 'cont' is already "
                               "configured as specified",
                               'changes': {},
                               'result': True,
                               })

    def test_check_start_true(self):
        '''
        If start is True, then dockerng.running will try
        to start a container that is stopped.
        '''
        image_id = 'abcdefg'
        dockerng_create = Mock()
        dockerng_start = Mock()
        dockerng_list_containers = Mock(return_value=['cont'])
        dockerng_inspect_container = Mock(
            return_value={
                'Config': {
                    'Image': 'image:latest',
                    'Tty': False,
                    'Labels': {},
                    'Domainname': '',
                    'User': '',
                    'AttachStderr': True,
                    'AttachStdout': True,
                    'Hostname': 'saltstack-container',
                    'Env': [],
                    'WorkingDir': '/',
                    'Cmd': ['bash'],
                    'Volumes': {},
                    'Entrypoint': None,
                    'ExposedPorts': {},
                    'OpenStdin': False,
                },
                'HostConfig': {
                    'PublishAllPorts': False,
                    'Dns': [],
                    'Links': None,
                    'CpusetCpus': '',
                    'RestartPolicy': {'MaximumRetryCount': 0, 'Name': ''},
                    'CapAdd': None,
                    'NetworkMode': 'default',
                    'PidMode': '',
                    'MemorySwap': 0,
                    'ExtraHosts': None,
                    'PortBindings': None,
                    'LxcConf': None,
                    'DnsSearch': [],
                    'Privileged': False,
                    'Binds': None,
                    'Memory': 0,
                    'VolumesFrom': None,
                    'CpuShares': 0,
                    'CapDrop': None,
                },
                'NetworkSettings': {
                    'MacAddress': '00:00:00:00:00:01',
                },
                'Image': image_id})
        dockerng_inspect_image = MagicMock(
            return_value={
                'Id': image_id,
                'Config': {
                    'Hostname': 'saltstack-container',
                    'WorkingDir': '/',
                    'Cmd': ['bash'],
                    'Volumes': {},
                    'Entrypoint': None,
                    'ExposedPorts': {},
                },
                })
        __salt__ = {'dockerng.list_containers': dockerng_list_containers,
                    'dockerng.inspect_container': dockerng_inspect_container,
                    'dockerng.inspect_image': dockerng_inspect_image,
                    'dockerng.list_tags': MagicMock(),
                    'dockerng.pull': MagicMock(return_value=True),
                    'dockerng.state': MagicMock(side_effect=['stopped',
                                                             'running']),
                    'dockerng.create': dockerng_create,
                    'dockerng.start': dockerng_start,
                    }
        with patch.dict(dockerng_state.__dict__,
                        {'__salt__': __salt__}):
            ret = dockerng_state.running(
                'cont',
                image='image:latest',
                start=True,
                )
        self.assertEqual(ret, {'name': 'cont',
                               'comment': "Container 'cont' changed state.",
                               'changes': {'state': {'new': 'running',
                                                     'old': 'stopped'},
                                           'image': True},
                               'result': True,
                               })

    def test_running_discard_wrong_environemnt_values(self):
        '''
        environment values should be string.
        It is easy to write wrong sls this way

        .. code-block:: yaml

            container:
                dockerng.running:
                    - environment:
                        - KEY: 1

        instead of:

        .. code-block:: yaml

            container:
                dockerng.running:
                    - environment:
                        - KEY: "1"
        '''
        __salt__ = {'dockerng.list_containers': MagicMock(),
                    'dockerng.list_tags': MagicMock(
                        return_value=['image:latest']),
                    'dockerng.inspect_image': MagicMock(),
                    'dockerng.pull': MagicMock(),
                    'dockerng.state': MagicMock(),
                    'dockerng.create': MagicMock(),
                    'dockerng.start': MagicMock(),
                    }
        with patch.dict(dockerng_state.__dict__,
                        {'__salt__': __salt__}):
            for wrong_value in (1, .2, (), [], {}):
                ret = dockerng_state.running(
                    'cont',
                    image='image:latest',
                    environment=[{'KEY': wrong_value}])
                self.assertEqual(ret,
                                 {'changes': {},
                                  'comment': 'Environment values must'
                                  ' be strings KEY={0!r}'.format(wrong_value),
                                  'name': 'cont',
                                  'result': False})

    def test_running_with_labels(self):
        '''
        Test dockerng.running with labels parameter.
        '''
        dockerng_create = Mock()
        __salt__ = {'dockerng.list_containers': MagicMock(),
                    'dockerng.list_tags': MagicMock(),
                    'dockerng.pull': MagicMock(),
                    'dockerng.state': MagicMock(),
                    'dockerng.inspect_image': MagicMock(),
                    'dockerng.create': dockerng_create,
                    }
        with patch.dict(dockerng_state.__dict__,
                        {'__salt__': __salt__}):
            dockerng_state.running(
                'cont',
                image='image:latest',
                labels=['LABEL1', 'LABEL2'],
                )
        dockerng_create.assert_called_with(
            'image:latest',
            validate_input=False,
            validate_ip_addrs=False,
            name='cont',
            labels=['LABEL1', 'LABEL2'],
            client_timeout=60)

    def test_running_with_labels_from_image(self):
        '''
        Test dockerng.running with labels parameter supports also
        labels carried by the image.
        '''
        dockerng_create = Mock()

        image_id = 'a' * 128
        dockerng_inspect_image = MagicMock(
            return_value={
                'Id': image_id,
                'Config': {
                    'Hostname': 'saltstack-container',
                    'WorkingDir': '/',
                    'Cmd': ['bash'],
                    'Volumes': {'/path': {}},
                    'Entrypoint': None,
                    'ExposedPorts': {},
                    'Labels': {'IMAGE_LABEL': 'image_foo',
                               'LABEL1': 'label1'},
                },
                })
        __salt__ = {'dockerng.list_containers': MagicMock(),
                    'dockerng.list_tags': MagicMock(),
                    'dockerng.pull': MagicMock(),
                    'dockerng.state': MagicMock(),
                    'dockerng.inspect_image': dockerng_inspect_image,
                    'dockerng.create': dockerng_create,
                    }
        with patch.dict(dockerng_state.__dict__,
                        {'__salt__': __salt__}):
            dockerng_state.running(
                'cont',
                image='image:latest',
                labels=[{'LABEL1': 'foo1'}, {'LABEL2': 'foo2'}],
                )
        dockerng_create.assert_called_with(
            'image:latest',
            validate_input=False,
            validate_ip_addrs=False,
            name='cont',
            labels={'LABEL1': 'foo1', 'LABEL2': 'foo2'},
            client_timeout=60)

    def test_network_present(self):
        '''
        Test dockerng.network_present
        '''
        dockerng_create_network = Mock(return_value='created')
        dockerng_connect_container_to_network = Mock(return_value='connected')
        dockerng_inspect_container = Mock(return_value={'Id': 'abcd'})
        __salt__ = {'dockerng.create_network': dockerng_create_network,
                    'dockerng.inspect_container': dockerng_inspect_container,
                    'dockerng.connect_container_to_network': dockerng_connect_container_to_network,
                    'dockerng.networks': Mock(return_value=[]),
                    }
        with patch.dict(dockerng_state.__dict__,
                        {'__salt__': __salt__}):
            ret = dockerng_state.network_present(
                'network_foo',
                containers=['container'],
                )
        dockerng_create_network.assert_called_with('network_foo', driver=None)
        dockerng_connect_container_to_network.assert_called_with('abcd',
                                                                 'network_foo')
        self.assertEqual(ret, {'name': 'network_foo',
                               'comment': '',
                               'changes': {'connected': 'connected',
                                           'created': 'created'},
                               'result': True})

    def test_network_absent(self):
        '''
        Test dockerng.network_absent
        '''
        dockerng_remove_network = Mock(return_value='removed')
        dockerng_disconnect_container_from_network = Mock(return_value='disconnected')
        __salt__ = {'dockerng.remove_network': dockerng_remove_network,
                    'dockerng.disconnect_container_from_network': dockerng_disconnect_container_from_network,
                    'dockerng.networks': Mock(return_value=[{'Containers': {'container': {}}}]),
                    }
        with patch.dict(dockerng_state.__dict__,
                        {'__salt__': __salt__}):
            ret = dockerng_state.network_absent(
                'network_foo',
                )
        dockerng_disconnect_container_from_network.assert_called_with('container',
                                                                      'network_foo')
        dockerng_remove_network.assert_called_with('network_foo')
        self.assertEqual(ret, {'name': 'network_foo',
                               'comment': '',
                               'changes': {'disconnected': 'disconnected',
                                           'removed': 'removed'},
                               'result': True})

    def test_volume_present(self):
        '''
        Test dockerng.volume_present
        '''
        volumes = []
        default_driver = 'dummy_default'

        def create_volume(name, driver=None, driver_opts=None):
            for v in volumes:
                # volume_present should never try to add a conflicting
                # volume
                self.assertNotEqual(v['Name'], name)
            if driver is None:
                driver = default_driver
            new = {'Name': name, 'Driver': driver}
            volumes.append(new)
            return new

        def remove_volume(name):
            old_len = len(volumes)
            removed = [v for v in volumes if v['Name'] == name]
            # volume_present should not have tried to remove a volume
            # that didn't exist
            self.assertEqual(1, len(removed))
            volumes.remove(removed[0])
            return removed[0]

        dockerng_create_volume = Mock(side_effect=create_volume)
        __salt__ = {'dockerng.create_volume': dockerng_create_volume,
                    'dockerng.volumes': Mock(return_value={'Volumes': volumes}),
                    'dockerng.remove_volume': Mock(side_effect=remove_volume),
                    }
        with patch.dict(dockerng_state.__dict__,
                        {'__salt__': __salt__}):
            ret = dockerng_state.volume_present(
                'volume_foo',
                )
            dockerng_create_volume.assert_called_with('volume_foo',
                                                      driver=None,
                                                      driver_opts=None)
            self.assertEqual(
                {
                    'name': 'volume_foo',
                    'comment': '',
                    'changes': {
                        'created': {
                            'Driver': default_driver,
                            'Name': 'volume_foo',
                        },
                    },
                    'result': True,
                },
                ret)
            self.assertEqual(len(volumes), 1)
            self.assertEqual(volumes[0]['Name'], 'volume_foo')
            self.assertIs(volumes[0]['Driver'], default_driver)

            # run it again with the same arguments
            orig_volumes = [volumes[0].copy()]
            ret = dockerng_state.volume_present('volume_foo')
            self.assertEqual(
                {
                    'name': 'volume_foo',
                    'comment': "Volume 'volume_foo' already exists.",
                    'changes': {},
                    'result': True,
                },
                ret)
            self.assertEqual(orig_volumes, volumes)

            # run it again with a different driver but don't force
            ret = dockerng_state.volume_present('volume_foo', driver='local')
            self.assertEqual(
                {
                    'name': 'volume_foo',
                    'comment': ("Driver for existing volume 'volume_foo'"
                                " ('dummy_default') does not match specified"
                                " driver ('local') and force is False"),
                    'changes': {},
                    'result': False,
                },
                ret)
            self.assertEqual(orig_volumes, volumes)

            # run it again with a different driver and force
            ret = dockerng_state.volume_present(
                'volume_foo', driver='local', force=True)
            self.assertEqual(
                {
                    'name': 'volume_foo',
                    'comment': "",
                    'changes': {
                        'removed': {
                            'Driver': default_driver,
                            'Name': 'volume_foo',
                        },
                        'created': {
                            'Driver': 'local',
                            'Name': 'volume_foo',
                        },
                    },
                    'result': True,
                },
                ret)
            mod_orig_volumes = [orig_volumes[0].copy()]
            mod_orig_volumes[0]['Driver'] = 'local'
            self.assertEqual(mod_orig_volumes, volumes)

    def test_volume_present_with_another_driver(self):
        '''
        Test dockerng.volume_present
        '''
        dockerng_create_volume = Mock(return_value='created')
        dockerng_remove_volume = Mock(return_value='removed')
        __salt__ = {'dockerng.create_volume': dockerng_create_volume,
                    'dockerng.remove_volume': dockerng_remove_volume,
                    'dockerng.volumes': Mock(return_value={
                        'Volumes': [{'Name': 'volume_foo',
                                     'Driver': 'foo'}]}),
                    }
        with patch.dict(dockerng_state.__dict__,
                        {'__salt__': __salt__}):
            ret = dockerng_state.volume_present(
                'volume_foo',
                driver='bar',
                force=True,
                )
        dockerng_remove_volume.assert_called_with('volume_foo')
        dockerng_create_volume.assert_called_with('volume_foo',
                                                  driver='bar',
                                                  driver_opts=None)
        self.assertEqual(ret, {'name': 'volume_foo',
                               'comment': '',
                               'changes': {'created': 'created',
                                           'removed': 'removed'},
                               'result': True})

    def test_volume_present_wo_existing_volumes(self):
        '''
        Test dockerng.volume_present without existing volumes.
        '''
        dockerng_create_volume = Mock(return_value='created')
        dockerng_remove_volume = Mock(return_value='removed')
        __salt__ = {'dockerng.create_volume': dockerng_create_volume,
                    'dockerng.remove_volume': dockerng_remove_volume,
                    'dockerng.volumes': Mock(return_value={'Volumes': None}),
                    }
        with patch.dict(dockerng_state.__dict__,
                        {'__salt__': __salt__}):
            ret = dockerng_state.volume_present(
                'volume_foo',
                driver='bar',
                force=True,
                )
        dockerng_create_volume.assert_called_with('volume_foo',
                                                  driver='bar',
                                                  driver_opts=None)
        self.assertEqual(ret, {'name': 'volume_foo',
                               'comment': '',
                               'changes': {'created': 'created'},
                               'result': True})

    def test_volume_absent(self):
        '''
        Test dockerng.volume_absent
        '''
        dockerng_remove_volume = Mock(return_value='removed')
        __salt__ = {'dockerng.remove_volume': dockerng_remove_volume,
                    'dockerng.volumes': Mock(return_value={
                        'Volumes': [{'Name': 'volume_foo'}]}),
                    }
        with patch.dict(dockerng_state.__dict__,
                        {'__salt__': __salt__}):
            ret = dockerng_state.volume_absent(
                'volume_foo',
                )
        dockerng_remove_volume.assert_called_with('volume_foo')
        self.assertEqual(ret, {'name': 'volume_foo',
                               'comment': '',
                               'changes': {'removed': 'removed'},
                               'result': True})

    def test_removal_of_parameter_is_detected(self):
        '''
        Test dockerng.running with deleted parameter.

        1. define your sls

        .. code-block:: yaml

            container:
                dockerng.running:
                    - name: super-container
                    - binds:
                        - /path:/path:ro

        2. run state.highstate

        3. modify your sls by removing `- binds:`

        .. code-block:: yaml

            container:
                dockerng.running:
                    - name: super-container

        4. enjoy your new created container without mounted volumes.
        '''
        image_id = 'abcdefg'
        dockerng_create = Mock(return_value=True)
        dockerng_start = Mock()
        dockerng_list_containers = Mock(return_value=['cont'])
        dockerng_inspect_container = Mock(
            side_effect=[{
                'Config': {
                    'Image': 'image:latest',
                    'Tty': False,
                    'Labels': {},
                    'Domainname': '',
                    'User': '',
                    'AttachStderr': True,
                    'AttachStdout': True,
                    'Hostname': 'saltstack-container',
                    'Env': [],
                    'WorkingDir': '/',
                    'Cmd': ['bash'],
                    'Volumes': {'/path': {}},
                    'Entrypoint': None,
                    'ExposedPorts': {},
                    'OpenStdin': False,
                },
                'HostConfig': {
                    'PublishAllPorts': False,
                    'Dns': [],
                    'Links': None,
                    'CpusetCpus': '',
                    'RestartPolicy': {'MaximumRetryCount': 0, 'Name': ''},
                    'CapAdd': None,
                    'NetworkMode': 'default',
                    'PidMode': '',
                    'MemorySwap': 0,
                    'ExtraHosts': None,
                    'PortBindings': None,
                    'LxcConf': None,
                    'DnsSearch': [],
                    'Privileged': False,
                    'Binds': ['/path:/path:ro'],
                    'Memory': 0,
                    'VolumesFrom': None,
                    'CpuShares': 0,
                    'CapDrop': None,
                },
                'NetworkSettings': {
                    'MacAddress': '00:00:00:00:00:01',
                },
                'Image': image_id},
                {'Config': {
                    'Image': 'image:latest',
                    'Tty': False,
                    'Labels': {},
                    'Domainname': '',
                    'User': '',
                    'AttachStderr': True,
                    'AttachStdout': True,
                    'Hostname': 'saltstack-container',
                    'Env': [],
                    'WorkingDir': '/',
                    'Cmd': ['bash'],
                    'Volumes': {'/path': {}},
                    'Entrypoint': None,
                    'ExposedPorts': {},
                    'OpenStdin': False,
                },
                'HostConfig': {
                    'PublishAllPorts': False,
                    'Dns': [],
                    'Links': None,
                    'CpusetCpus': '',
                    'RestartPolicy': {'MaximumRetryCount': 0, 'Name': ''},
                    'CapAdd': None,
                    'NetworkMode': 'default',
                    'PidMode': '',
                    'MemorySwap': 0,
                    'ExtraHosts': None,
                    'PortBindings': None,
                    'LxcConf': None,
                    'DnsSearch': [],
                    'Privileged': False,
                    'Binds': None,
                    'Memory': 0,
                    'VolumesFrom': None,
                    'CpuShares': 0,
                    'CapDrop': None,
                },
                'NetworkSettings': {
                    'MacAddress': '00:00:00:00:00:01',
                },
                'Image': image_id}]
        )
        dockerng_inspect_image = MagicMock(
            return_value={
                'Id': image_id,
                'Config': {
                    'Hostname': 'saltstack-container',
                    'WorkingDir': '/',
                    'Cmd': ['bash'],
                    'Volumes': {'/path': {}},
                    'Entrypoint': None,
                    'ExposedPorts': {},
                },
                })
        __salt__ = {'dockerng.list_containers': dockerng_list_containers,
                    'dockerng.inspect_container': dockerng_inspect_container,
                    'dockerng.inspect_image': dockerng_inspect_image,
                    'dockerng.list_tags': MagicMock(),
                    'dockerng.pull': MagicMock(return_value=True),
                    'dockerng.state': MagicMock(side_effect=['stopped',
                                                             'running']),
                    'dockerng.rm': MagicMock(return_value='cont'),
                    'dockerng.create': dockerng_create,
                    'dockerng.start': dockerng_start,
                    }
        with patch.dict(dockerng_state.__dict__,
                        {'__salt__': __salt__}):
            ret = dockerng_state.running(
                'cont',
                image='image:latest',
                )
        self.assertEqual(ret, {'name': 'cont',
                               'comment': "Container 'cont' changed state.."
                               " Container 'cont' was replaced.",
                               'changes': {
                                   'diff': {'binds':
                                            {'new': [],
                                             'old': ['/path:/path:ro']}},
                                   'image': True,
                                   'removed': 'cont',
                                   'state': {'new': 'running',
                                             'old': 'stopped'},
                                   'added': True,
                               },
                               'result': True,
                               })
        dockerng_create.assert_called_with('image:latest',
                                           validate_ip_addrs=False,
                                           validate_input=False,
                                           name='cont',
                                           client_timeout=60)

    def test_command_defined_on_image_layer_dont_diff_if_attempted_to_blank(self):
        '''
        Assuming the docker image has a command defined, like ``sh``.
        Erasing this value on sls level will not delete the command
        in the container. And such the diff shouldn't be reported.
        Assuming also the container is already running.

        1. define your sls

        .. code-block:: yaml

            cont:
                dockerng.running:
                    - image: image:latest


        2. run state.highstate

        No diff should be reported
        '''
        image_id = 'abcdefg'
        dockerng_create = Mock(return_value=True)
        dockerng_start = Mock()
        dockerng_list_containers = Mock(return_value=['cont'])
        dockerng_inspect_container = Mock(
            side_effect=[{
                'Config': {
                    'Image': 'image:latest',
                    'Tty': False,
                    'Labels': {},
                    'Domainname': '',
                    'User': '',
                    'AttachStderr': True,
                    'AttachStdout': True,
                    'Hostname': 'saltstack-container',
                    'Env': [],
                    'WorkingDir': '/',
                    'Cmd': None,
                    'Volumes': {},
                    'Entrypoint': None,
                    'ExposedPorts': {},
                    'OpenStdin': False,
                },
                'HostConfig': {
                    'PublishAllPorts': False,
                    'Dns': [],
                    'Links': None,
                    'CpusetCpus': '',
                    'RestartPolicy': {'MaximumRetryCount': 0, 'Name': ''},
                    'CapAdd': None,
                    'NetworkMode': 'default',
                    'PidMode': '',
                    'MemorySwap': 0,
                    'ExtraHosts': None,
                    'PortBindings': None,
                    'LxcConf': None,
                    'DnsSearch': [],
                    'Privileged': False,
                    'Binds': [],
                    'Memory': 0,
                    'VolumesFrom': None,
                    'CpuShares': 0,
                    'CapDrop': None,
                },
                'NetworkSettings': {
                    'MacAddress': '00:00:00:00:00:01',
                },
                'Image': image_id}]
        )
        dockerng_inspect_image = MagicMock(
            return_value={
                'Id': image_id,
                'Config': {
                    'Hostname': 'saltstack-container',
                    'WorkingDir': '/',
                    'Cmd': ['bash'],  # !!! Cmd defined on Image
                    'Volumes': {},
                    'Entrypoint': None,
                    'ExposedPorts': {},
                },
                })
        __salt__ = {'dockerng.list_containers': dockerng_list_containers,
                    'dockerng.inspect_container': dockerng_inspect_container,
                    'dockerng.inspect_image': dockerng_inspect_image,
                    'dockerng.list_tags': MagicMock(side_effect=[['image:latest']]),
                    'dockerng.state': MagicMock(side_effect=['running']),
                    }
        with patch.dict(dockerng_state.__dict__,
                        {'__salt__': __salt__}):
            ret = dockerng_state.running(
                'cont',
                image='image:latest',
                )
        self.assertEqual(ret, {'name': 'cont',
                               'comment': "Container 'cont' is already"
                               " configured as specified",
                               'changes': {},
                               'result': True,
                               })

if __name__ == '__main__':
    from integration import run_tests
    run_tests(DockerngTestCase, needs_daemon=False)
