def test_raise_exception_on_circular_dependency(self):
        collection = ServiceCollection()

        class NewServiceBase(Service):
            name = "not used"
            image = "not used"

        collection._base_class = NewServiceBase

        class ServiceOne(NewServiceBase):
            name = "hello"
            image = "hello"
            dependencies = ["howareyou"]

        class ServiceTwo(NewServiceBase):
            name = "goodbye"
            image = "hello"
            dependencies = ["hello"]

        class ServiceThree(NewServiceBase):
            name = "howareyou"
            image = "hello"
            dependencies = ["goodbye"]

        with pytest.raises(ServiceLoadError):
            collection.load_definitions()
    def test_stop_without_remove(self):
        container1 = FakeContainer(name='service1-testing-1234',
                                   network='the-network',
                                   status='running')
        container2 = FakeContainer(name='service2-testing-5678',
                                   network='the-network',
                                   status='exited')
        self.docker._existing_containers = [container1, container2]
        collection = ServiceCollection()

        class NewServiceBase(Service):
            name = "not used"
            image = "not used"

        class ServiceOne(NewServiceBase):
            name = "service1"
            image = "howareyou/image"

        class ServiceTwo(NewServiceBase):
            name = "service2"
            image = "howareyou/image"

        collection._base_class = NewServiceBase
        collection.load_definitions()
        collection.stop_all(DEFAULT_OPTIONS)
        assert container1.stopped
        assert container1.timeout == 1
        assert container1.removed_at is None
        assert not container2.stopped
        assert self.docker._networks_removed == []
    def test_start_all(self):
        # This test does not fake threading, which is somehow dangerous, but the
        # aim is to make sure that the error handling etc. works also when there
        # is an exception in the service agent thread, and the
        # collection.start_all method does not hang.
        collection = ServiceCollection()

        class NewServiceBase(Service):
            name = "not used"
            image = "not used"

        collection._base_class = NewServiceBase

        class ServiceOne(NewServiceBase):
            name = "hello"
            image = "hello/image"
            dependencies = ["howareyou"]

        class ServiceTwo(NewServiceBase):
            name = "goodbye"
            image = "goodbye/image"
            dependencies = ["hello"]

        class ServiceThree(NewServiceBase):
            name = "howareyou"
            image = "howareyou/image"

        collection.load_definitions()
        retval = collection.start_all(DEFAULT_OPTIONS)
        assert set(retval) == {"hello", "goodbye", "howareyou"}
        assert len(self.docker._services_started) == 3
        # The one without dependencies should have been started first
        name_prefix, service, network_name = self.docker._services_started[0]
        assert service.image == 'howareyou/image'
        assert name_prefix == "howareyou-testing"
    def test_start_all_with_build(self):
        collection = ServiceCollection()

        class NewServiceBase(Service):
            name = "not used"
            image = "not used"

        collection._base_class = NewServiceBase

        class ServiceTwo(NewServiceBase):
            name = "goodbye"
            image = "goodbye/image"
            build_from = "goodbye/dir"
            dockerfile = "Dockerfile.alt"

        collection.load_definitions()
        options = attr.evolve(DEFAULT_OPTIONS, build=['goodbye'])
        retval = collection.start_all(options)
        assert len(self.docker._images_built) == 1
        build_dir, dockerfile, image_tag = self.docker._images_built[0]
        assert build_dir == "/etc/goodbye/dir"
        assert dockerfile == 'Dockerfile.alt'
        assert image_tag.startswith("goodbye-")
        service = collection.all_by_name['goodbye']
        assert service.image == image_tag
    def test_stop_dependency_and_dependant_excluded(self):
        collection = ServiceCollection()

        class NewServiceBase(Service):
            name = "not used"
            image = "not used"

        collection._base_class = NewServiceBase

        class ServiceOne(NewServiceBase):
            name = "hello"
            image = "hello"
            dependencies = ["howareyou"]

        class ServiceTwo(NewServiceBase):
            name = "goodbye"
            image = "hello"
            dependencies = ["hello"]

        class ServiceThree(NewServiceBase):
            name = "howareyou"
            image = "hello"

        collection.load_definitions()
        collection.exclude_for_stop(['howareyou', 'hello'])
    def test_populate_dependants(self):
        collection = ServiceCollection()

        class NewServiceBase(Service):
            name = "not used"
            image = "not used"

        collection._base_class = NewServiceBase

        class ServiceOne(NewServiceBase):
            name = "hello"
            image = "not/used"
            dependencies = ["howareyou"]

        class ServiceTwo(NewServiceBase):
            name = "goodbye"
            image = "not/used"
            dependencies = ["hello", "howareyou"]

        class ServiceThree(NewServiceBase):
            name = "howareyou"
            image = "not/used"

        collection.load_definitions()
        assert len(collection.all_by_name) == 3
        hello = collection.all_by_name['hello']
        assert len(hello.dependants) == 1
        assert hello.dependants[0].name == 'goodbye'
        howareyou = collection.all_by_name['howareyou']
        assert len(howareyou.dependants) == 2
        names = [x.name for x in howareyou.dependants]
        assert 'hello' in names
        assert 'goodbye' in names
    def test_error_on_stop_dependency_excluded(self):
        collection = ServiceCollection()

        class NewServiceBase(Service):
            name = "not used"
            image = "not used"

        collection._base_class = NewServiceBase

        class ServiceOne(NewServiceBase):
            name = "hello"
            image = "hello"
            dependencies = ["howareyou"]

        class ServiceTwo(NewServiceBase):
            name = "goodbye"
            image = "hello"
            dependencies = ["hello"]

        class ServiceThree(NewServiceBase):
            name = "howareyou"
            image = "hello"

        collection.load_definitions()
        with pytest.raises(ServiceLoadError):
            collection.exclude_for_stop(['goodbye'])
    def test_start_dependency_and_dependant_excluded(self):
        collection = ServiceCollection()

        class NewServiceBase(Service):
            name = "not used"
            image = "not used"

        collection._base_class = NewServiceBase

        class ServiceOne(NewServiceBase):
            name = "hello"
            image = "hello"
            dependencies = ["howareyou"]

        class ServiceTwo(NewServiceBase):
            name = "goodbye"
            image = "hello"
            dependencies = ["hello"]

        class ServiceThree(NewServiceBase):
            name = "howareyou"
            image = "hello"

        collection.load_definitions()
        # There shouldn't be an exception, since we are excluding both hello and
        # goodbye
        collection.exclude_for_start(['hello', 'goodbye'])
    def test_exclude_for_start(self):
        collection = ServiceCollection()

        class NewServiceBase(Service):
            name = "not used"
            image = "not used"

        collection._base_class = NewServiceBase

        class ServiceOne(NewServiceBase):
            name = "hello"
            image = "hello"
            dependencies = ["howareyou"]

        class ServiceTwo(NewServiceBase):
            name = "goodbye"
            image = "hello"
            dependencies = ["hello"]

        class ServiceThree(NewServiceBase):
            name = "howareyou"
            image = "hello"

        collection.load_definitions()
        collection.exclude_for_start(['goodbye'])
        assert len(collection) == 2
    def test_continue_if_start_failed(self):
        """If a service fails, those that don't depend on it should still be started"""
        collection = ServiceCollection()

        class NewServiceBase(Service):
            name = "not used"
            image = "not used"

        class FirstService(NewServiceBase):
            name = "first-service"
            image = "howareyou/image"

            def ping(self):
                raise ValueError("I failed miserably")

        class SecondService(NewServiceBase):
            name = "second-service"
            image = "howareyou/image"

            def ping(self):
                time.sleep(0.5)
                return True

        collection._base_class = NewServiceBase
        collection.load_definitions()
        started = collection.start_all(DEFAULT_OPTIONS)
        assert started == ["second-service"]
    def test_raise_exception_on_no_services(self):
        collection = ServiceCollection()

        class NewServiceBase(Service):
            name = "not used"
            image = "not used"

        collection._base_class = NewServiceBase
        with pytest.raises(ServiceLoadError):
            collection.load_definitions()
    def test_start_all_create_network(self):
        collection = ServiceCollection()

        class NewServiceBase(Service):
            name = "not used"
            image = "not used"

        class ServiceTwo(NewServiceBase):
            name = "goodbye"
            image = "goodbye/image"

        collection._base_class = NewServiceBase
        collection.load_definitions()
        collection.start_all(DEFAULT_OPTIONS)
        assert self.docker._networks_created == ["the-network"]
    def test_stop_with_remove_and_order(self):
        container1 = FakeContainer(name='service1-testing-1234',
                                   network='the-network',
                                   status='running')
        container2 = FakeContainer(name='service2-testing-5678',
                                   network='the-network',
                                   status='running')
        container3 = FakeContainer(name='service3-testing-5678',
                                   network='the-network',
                                   status='running')
        self.docker._existing_containers = [container1, container2, container3]
        collection = ServiceCollection()

        class NewServiceBase(Service):
            name = "not used"
            image = "not used"

        class ServiceOne(NewServiceBase):
            name = "service1"
            image = "howareyou/image"

        class ServiceTwo(NewServiceBase):
            name = "service2"
            image = "howareyou/image"
            dependencies = ['service1']

        class ServiceThree(NewServiceBase):
            name = "service3"
            image = "howareyou/image"
            dependencies = ['service2']

        collection._base_class = NewServiceBase
        collection.load_definitions()
        options = Options(network=Network(name='the-network',
                                          id='the-network-id'),
                          timeout=50,
                          remove=True,
                          run_dir='/etc',
                          build=[])
        collection.stop_all(options)
        assert container1.stopped
        assert container1.removed_at is not None
        assert container2.stopped
        assert container2.removed_at is not None
        assert container3.stopped
        assert container3.removed_at is not None
        assert container1.removed_at > container2.removed_at > container3.removed_at
        assert self.docker._networks_removed == ['the-network']
    def test_stop_on_fail(self):
        collection = ServiceCollection()

        class NewServiceBase(Service):
            name = "not used"
            image = "not used"

        class TheService(NewServiceBase):
            name = "howareyou"
            image = "howareyou/image"

            def ping(self):
                raise ValueError("I failed miserably")

        collection._base_class = NewServiceBase
        collection.load_definitions()
        started = collection.start_all(DEFAULT_OPTIONS)
        assert started == []
    def test_update_for_base_service(self):
        container1 = FakeContainer(name='service1-testing-1234',
                                   network='the-network',
                                   status='running')
        container2 = FakeContainer(name='service2-testing-5678',
                                   network='the-network',
                                   status='running')
        container3 = FakeContainer(name='service3-testing-5678',
                                   network='the-network',
                                   status='running')
        self.docker._existing_containers = [container1, container2, container3]
        collection = ServiceCollection()

        class NewServiceBase(Service):
            name = "not used"
            image = "not used"

        class ServiceOne(NewServiceBase):
            name = "service1"
            image = "howareyou/image"

        class ServiceTwo(NewServiceBase):
            name = "service2"
            image = "howareyou/image"
            dependencies = ['service1']

        class ServiceThree(NewServiceBase):
            name = 'service3'
            image = 'howareyou/image'
            dependencies = ['service1', 'service2']

        collection._base_class = NewServiceBase
        collection.load_definitions()
        collection.update_for_base_service('service2')
        assert collection.all_by_name == {
            'service2': ServiceTwo(),
            'service3': ServiceThree()
        }
        collection.stop_all(DEFAULT_OPTIONS)
        assert not container1.stopped
        assert container2.stopped
        assert container3.stopped
    def test_stop_with_remove_and_exclude(self):
        container1 = FakeContainer(name='service1-testing-1234',
                                   network='the-network',
                                   status='running')
        container2 = FakeContainer(name='service2-testing-5678',
                                   network='the-network',
                                   status='running')
        self.docker._existing_containers = [container1, container2]
        collection = ServiceCollection()

        class NewServiceBase(Service):
            name = "not used"
            image = "not used"

        class ServiceOne(NewServiceBase):
            name = "service1"
            image = "howareyou/image"

        class ServiceTwo(NewServiceBase):
            name = "service2"
            image = "howareyou/image"

        collection._base_class = NewServiceBase
        collection.load_definitions()
        collection.exclude_for_stop(['service2'])
        options = Options(network=Network(name='the-network',
                                          id='the-network-id'),
                          timeout=50,
                          remove=True,
                          run_dir='/etc',
                          build=[])
        collection.stop_all(options)
        assert container1.stopped
        assert container1.removed_at is not None
        # service2 was excluded
        assert not container2.stopped
        assert container2.removed_at is None
        # If excluded is not empty, network should not be removed
        assert self.docker._networks_removed == []
    def test_check_can_be_built(self):
        collection = ServiceCollection()

        class NewServiceBase(Service):
            name = "not used"
            image = "not used"

        class ServiceOne(NewServiceBase):
            name = "service1"
            image = "howareyou/image"

        class ServiceTwo(NewServiceBase):
            name = "service2"
            image = "howareyou/image"
            build_from = "the/service/dir"

        collection._base_class = NewServiceBase
        collection.load_definitions()
        with pytest.raises(ServiceDefinitionError):
            collection.check_can_be_built('no-such-service')
        with pytest.raises(ServiceDefinitionError):
            collection.check_can_be_built('service1')
        collection.check_can_be_built('service2')
    def test_dont_return_failed_services(self):
        collection = ServiceCollection()

        class NewServiceBase(Service):
            name = "not used"
            image = "not used"

        class TheFirstService(NewServiceBase):
            name = "howareyou"
            image = "howareyou/image"

        class TheService(NewServiceBase):
            name = "imok"
            image = "howareyou/image"
            dependencies = ["howareyou"]

            def ping(self):
                raise ValueError("I failed miserably")

        collection._base_class = NewServiceBase
        collection.load_definitions()
        started = collection.start_all(DEFAULT_OPTIONS)
        assert started == ["howareyou"]