예제 #1
0
    def connect_network_to_service(self, service, network):
        """
        Connect the given network to the given service. To do this, the service is updated with the network.

        Args:
            service: docker.models.services.Service
            network: docker.models.networks.Network

        Returns:
            docker.models.containers.Container

        """
        try:
            wait_on_service_replication(self._docker, service)
            wait_on_service_container_status(self, service)

            n = network if isinstance(network,
                                      Network) else self.get_network(network)
            s = service if isinstance(service,
                                      Service) else self.get_service(service)
        except (docker.errors.NotFound, docker.errors.APIError,
                NotFoundError) as exc:
            raise DeploymentError('Entity not found: {}'.format(exc))

        BuiltIn().log('Connecting network {} to service {}...'.format(
            n.name, s.name),
                      level='INFO',
                      console=Settings.to_console)
        try:
            return self.update_service(s, networks=[n.name])
        except docker.errors.APIError as exc:
            raise DeploymentError(
                'Could not connect network to container: {}'.format(exc))
예제 #2
0
    def connect_volume_to_service(self, service, volume):
        """
        Connect a given volume to a given service.
        In case the volume is already connect, just return the service.

        Args:
            service: docker.models.services.Service
            volume: docker.models.volumes.Volume

        Returns:
            Container

        """
        if not volume:
            raise DeploymentError(
                'You must provide a volume to connect it to a service.')
        if not service:
            raise DeploymentError(
                'You must provide a service to connect a volume.')

        try:
            wait_on_service_replication(self._docker, service)
            wait_on_service_container_status(self, service)

            v = volume if isinstance(volume,
                                     Volume) else self.get_volume(volume)
            s = service if isinstance(service,
                                      Service) else self.get_service(service)
        except (docker.errors.NotFound, docker.errors.APIError,
                NotFoundError) as exc:
            raise DeploymentError('Entity not found: {}'.format(exc))

        try:
            attrs = getattr(s, 'attrs')
            mounts = attrs.get('Spec', {}).get('TaskTemplate',
                                               {}).get('ContainerSpec',
                                                       {}).get('Mounts', [])
            for mount in mounts:
                if v.name in mount.get('Source', {}):
                    return self.get_containers_for_service(s)[0]
        except (AttributeError, ValueError):
            pass

        BuiltIn().log('Connecting volume {} to service {}...'.format(
            v.name, s.name),
                      level='INFO',
                      console=Settings.to_console)
        try:
            return self.update_service(s,
                                       mounts=['{}:/goss:ro'.format(v.name)])
        except docker.errors.APIError as exc:
            raise DeploymentError(
                'Could not connect network to container: {}'.format(exc))
    def _get_deployment(self, deployment_name=None):
        if self.robot_instance.suite_source is None:
            raise SetupError('\nCannot determine directory of robot file.')

        deployment_name = deployment_name if deployment_name else self.robot_instance.deployment_name

        try:
            self.controller.find_stack(deployment_name)
        except DeploymentError:
            raise SetupError('\nExisting deployment "{}" not found.'.format(deployment_name))

        try:
            # retrieve and store services that belong to the deployment
            self.robot_instance.services.extend(self.controller.get_services(deployment_name))
            assert len(self.robot_instance.services) > 0, \
                "instance.services should not be empty after get_or_create_deployment()"

            # retrieve and store containers that belong to the deployment
            for service in self.robot_instance.services:
                self.robot_instance.containers.extend(self.controller.get_containers_for_service(service.name))
            # set_breakpoint()
            if len(self.robot_instance.containers) < len(self.robot_instance.services):
                BuiltIn().log("There are not enough containers for the number of services. Something is wrong "
                              "with the deployment", level='ERROR', console=True)
                raise DeploymentError('Not all containers are alive and well.')

            self._health_check_services(self.robot_instance)
            self.robot_instance.deployment_name = deployment_name
        except DeploymentError as exc:
            raise SetupError('\nError during health check: {}'.format(exc.message))
예제 #4
0
    def run_busybox(self, **kwargs):
        """
        Helper method for conftest.py to create a running dummy container.

        Args:
            **kwargs: any argument that a Container object takes except for image, command, name and detach

        Returns:
            Container

        """

        try:
            name = namesgenerator.get_random_name()
            command = kwargs.pop(
                'command',
                'sh -c "while true; do $(echo date "GET / HTTP/1.1"); sleep 1; done"'
            )
            self._docker.containers.run('busybox',
                                        command=command,
                                        name=name,
                                        detach=True,
                                        **kwargs)
            return self._docker.containers.get(name)
        except docker.errors.NotFound as exc:
            raise NotFoundError(exc)
        except docker.errors.APIError as exc:
            raise DeploymentError(exc)
예제 #5
0
    def create_volume(self, name):
        """
        Creates a volume.

        Args:
            name: str

        Returns:
            docker.models.volumes.Volume

        """
        try:
            return self._docker.volumes.create(name)
        except docker.errors.NotFound:
            raise DeploymentError('Could not create volume {}'.format(name))
        except docker.errors.APIError as exc:
            raise DeploymentError('Could not create volume {}: {}'.format(
                name, exc))
예제 #6
0
    def delete_volume(self, name):
        """
        Deletes a volume.

        Args:
            name: str

        Returns:
            None

        """
        try:
            v = self._docker.volumes.get(name)
            v.remove(force=True)
        except docker.errors.NotFound:
            raise DeploymentError(
                'Could not remove volume {}: Not found'.format(name))
        except docker.errors.APIError as exc:
            raise DeploymentError('Could not remove volume {}: {}'.format(
                name, exc))
예제 #7
0
    def condition():
        if isinstance(services, list) and len(services) > 0:
            state_ok = 0
            for service in services:
                service_name = service.name if isinstance(service,
                                                          Service) else service

                res = client._docker.containers.list(
                    filters={
                        'label':
                        'com.docker.swarm.service.name={}'.format(service_name)
                    })

                if res:
                    # logger.console('Found container {} belonging to service {}...'.format(res[0].name, service))
                    state_ok += 1
                else:
                    # BuiltIn().log('Waiting for service {}...'.format(service_name),
                    #               level='DEBUG',
                    #               console=Settings.to_console)

                    # try to find out if an error occured
                    tasks = service.tasks(filters={"desired-state": "Ready"})
                    if len(tasks) > 0:
                        task = tasks[0]
                        err = task.get('Status', {}).get('Err')
                        if err:
                            if 'failed to allocate gateway' in err:
                                client._docker_api.prune_networks()
                            elif 'No such image' in err:
                                raise DeploymentError('Service {}: {}'.format(
                                    service_name, err))
                            else:
                                raise DeploymentError(
                                    'Could not deploy {}: {}'.format(
                                        service.name, err))
            return len(services) == state_ok
        return False
예제 #8
0
    def get_volume(self, name):
        """
        Retrieves a volume by name.

        Args:
            name: str

        Returns:
            docker.models.volumes.Volume

        """
        try:
            return self._docker.volumes.get(name)
        except (docker.errors.NotFound, docker.errors.APIError) as exc:
            raise DeploymentError('Could not find volume {}: {}'.format(
                name, exc if exc else ''))
예제 #9
0
    def get_services(self, stack):
        """
        Retrieve services for a stack.

        Args:
            stack: str - name of deployment

        Returns:
            [Service]

        """
        try:
            return self._docker.services.list(
                filters={'name': '{}_'.format(stack)})
        except docker.errors.APIError as exc:
            raise DeploymentError('Could not get services for {}: {}'.format(
                stack, exc))
예제 #10
0
    def delete_container(self, name):
        """
        Deletes a container.

        Args:
            name: str

        Returns:
            None

        """
        try:
            c = self._docker.containers.get(name)
            c.remove()
        except (docker.errors.APIError, docker.errors.NotFound) as exc:
            raise DeploymentError('Could not delete container {}: exc'.format(
                name, exc))
예제 #11
0
    def find_stack(self, deployment_name):
        """
        Find a stack by name.

        Args:
            deployment_name: str - name of the stack

        Returns:
            True

        """
        res = self._dispatch(
            ['stack', 'ps', deployment_name, '--format', '" {{ .Name }}"'])

        if deployment_name not in res.stdout.strip('\n\r'):
            raise DeploymentError(
                'Stack {} not found.'.format(deployment_name))
        return True
예제 #12
0
    def deploy_stack(self, descriptor, name):
        """
        Deploy a docker-compose.yml file on a Docker Swarm.

        Args:
            descriptor: str
            name: str

        Returns:
            True

        """
        assert name, "name is required for deploy_stack"
        assert descriptor, "descriptor is required for deploy_stack"
        res = self._dispatch(['stack', 'deploy', '-c', descriptor, name])
        if res.stderr:
            raise DeploymentError(res.stderr)
        return True
예제 #13
0
    def get_file(self, entity, path, filename):
        """
        Retrieves a file from a container.

        Args:
            entity: docker.models.containers.Container
            path: path to file
            filename: filename on container

        Returns:
            Archive

        """
        try:
            strm, stat = self._docker_api.get_archive(
                entity, '{}/{}'.format(path, filename))
        except docker.errors.APIError as exc:
            raise DeploymentError(exc)

        return Archive('r', strm.read()).get_text_file(filename)
예제 #14
0
    def update_service(self, service, **kwargs):
        """
        Update a service using the map of configuration values.

        Args:
            service: docker.models.services.Service
            **kwargs: any parameters available for updating a service

        Returns:
            Container: Instance of Container

        """
        assert isinstance(service, Service)
        try:
            current_instances = frozenset(
                self.get_containers_for_service(service))
            service.update(**kwargs)
            return self._wait_on_service_update(service, current_instances)
        except docker.errors.APIError as exc:
            raise DeploymentError('Could not update service {}: {}'.format(
                service.name, exc))
예제 #15
0
    def _send_file(self, content, destination, entity, filename):
        """
        Helper method for put_file().
        Creates a temporary tar file with the specified content.

        Args:
            content:
            destination:
            entity:
            filename:

        Returns:

        """
        to_send = Archive('w').add_text_file(filename, content).close()
        try:
            res = self._docker_api.put_archive(entity, destination,
                                               to_send.buffer)
            assert res
            # self.file_exists(entity, filename)
        except docker.errors.APIError as exc:
            raise DeploymentError(exc)
예제 #16
0
    def run_sidecar(self,
                    name='',
                    sidecar=None,
                    image='busybox',
                    command='true',
                    volumes=None,
                    network=None):
        """
        Run a sidecar container with the specified parameters. It waits for the command to finish and returns stdout.
        If stderr is not empty, a DeploymentError is raised with stderr.

        Args:
            name: str - name of the container
            sidecar: docker.models.containers.Container
            image: str - name of the image
            command: str - command to run
            volumes:
            network:

        Returns:
            str - stdout

        """

        # maybe try this:
        # docker service create --network dockercoins_default --name set_breakpoint \
        #        --constraint node.hostname==$HOSTNAME alpine sleep 1000000000
        #
        #
        #

        try:
            if not sidecar:
                sidecar = self.get_or_create_sidecar(image, command, name,
                                                     volumes, network)
            wait_on_container_status(self, sidecar, ['Created', 'Exited'])
            sidecar.start()
            sidecar.wait()
            stdout = sidecar.logs(stdout=True, stderr=False) or ''
            stderr = sidecar.logs(stdout=False, stderr=True) or ''

            # BuiltIn().log(stdout, level='DEBUG', console=Settings.to_console)

            if stderr:
                raise DeploymentError('Found stderr: {}'.format(stderr))
            return {'code': 0, 'res': stdout}
        except docker.errors.NotFound:
            raise DeploymentError(
                'Sidecar {} not found.'.format(sidecar if sidecar else 'None'))
        except docker.errors.APIError as exc:
            if 'executable file not found' in exc.explanation:
                raise DeploymentError('Could not run command: {}'.format(exc))
            else:
                raise DeploymentError('Could not run sidecar: {}'.format(exc))
        except docker.errors.ContainerError as exc:
            BuiltIn().log('run_sidecar(): {}'.format(exc),
                          level='DEBUG',
                          console=Settings.to_console)
            raise DeploymentError('Error: {}'.format(exc))
        finally:
            self._kill_and_delete_container(sidecar)
예제 #17
0
    def get_or_create_sidecar(self,
                              image='busybox',
                              command='true',
                              name='',
                              volumes=None,
                              network=None):
        """
        Helper method for run_sidecar().
        A container is created with the provided parameters.
        An existing sidecar with an identical name is removed first.

        Args:
            image: str - image
            command: str - command
            name: str - name
            volumes:
            network:

        Returns:
            docker.models.containers.Container

        """

        # Do not reuse for the moment being as we are reading stdout and the stdout history is not reset between
        # different testtools runs. We end up with multiple json objects we cannot easily decode for now
        # # try to get an existing sidecar
        # if name:
        #     try:
        #         c = self._docker.containers.get(name)
        #         if isinstance(c, Container):
        #             logger.console('Found container {}. Re-using it as sidecar.'.format(name))
        #             return c
        #     except docker.errors.NotFound:
        #         pass

        # remove existing sidecar
        try:
            self.delete_container(name)
            BuiltIn().log(
                'Removing existing sidecar container {}'.format(name),
                level='DEBUG',
                console=Settings.to_console)
        except DeploymentError:
            pass

        try:
            self.get_or_pull_image(image)
            BuiltIn().log(
                'Creating sidecar container: name={}, image={}, command={}, volumes={}, networks={}'
                .format(name, image, command, volumes, network),
                level='INFO',
                console=Settings.to_console)
            return self._docker.containers.create(name=name if name else None,
                                                  image=image,
                                                  command=command,
                                                  auto_remove=False,
                                                  volumes=volumes,
                                                  network=network,
                                                  tty=True)
        except docker.errors.APIError as exc:
            raise DeploymentError('Could not deploy sidecar: {}'.format(exc))
        except NotFoundError as exc:
            raise DeploymentError('Image {} not found: {}'.format(
                image if image else '', exc))
        except docker.errors.ContainerError as exc:
            BuiltIn().log('get_or_create_sidecar(): {}'.format(exc),
                          level='INFO',
                          console=Settings.to_console)
            raise DeploymentError('Error: {}'.format(exc))