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))
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))
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)
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))
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))
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
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 ''))
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))
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))
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
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
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)
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))
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)
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)
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))