1 # Copyright (c) 2019 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
6 # http://www.apache.org/licenses/LICENSE-2.0
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
14 # Bug workaround in pylint for abstract classes.
15 # pylint: disable=W0223
17 """Library to manipulate Containers."""
19 from collections import OrderedDict, Counter
21 from resources.libraries.python.ssh import SSH
22 from resources.libraries.python.constants import Constants
23 from resources.libraries.python.topology import Topology
24 from resources.libraries.python.VppConfigGenerator import VppConfigGenerator
27 __all__ = ["ContainerManager", "ContainerEngine", "LXC", "Docker", "Container"]
29 SUPERVISOR_CONF = '/etc/supervisord.conf'
32 class ContainerManager(object):
33 """Container lifecycle management class."""
35 def __init__(self, engine):
36 """Initialize Container Manager class.
38 :param engine: Container technology used (LXC/Docker/...).
40 :raises NotImplementedError: If container technology is not implemented.
43 self.engine = globals()[engine]()
45 raise NotImplementedError('{engine} is not implemented.'.
46 format(engine=engine))
47 self.containers = OrderedDict()
49 def get_container_by_name(self, name):
50 """Get container instance.
52 :param name: Container name.
54 :returns: Container instance.
56 :raises RuntimeError: If failed to get container with name.
59 return self.containers[name]
61 raise RuntimeError('Failed to get container with name: {name}'.
64 def construct_container(self, **kwargs):
65 """Construct container object on node with specified parameters.
67 :param kwargs: Key-value pairs used to construct container.
71 self.engine.initialize()
74 setattr(self.engine.container, key, kwargs[key])
76 # Set additional environmental variables
77 setattr(self.engine.container, 'env',
78 'MICROSERVICE_LABEL={label}'.format(label=kwargs['name']))
80 # Store container instance
81 self.containers[kwargs['name']] = self.engine.container
83 def construct_containers(self, **kwargs):
84 """Construct 1..N container(s) on node with specified name.
86 Ordinal number is automatically added to the name of container as
89 :param kwargs: Named parameters.
93 for i in range(kwargs['count']):
94 # Name will contain ordinal suffix
95 kwargs['name'] = ''.join([name, str(i+1)])
97 self.construct_container(i=i, **kwargs)
99 def acquire_all_containers(self):
100 """Acquire all containers."""
101 for container in self.containers:
102 self.engine.container = self.containers[container]
103 self.engine.acquire()
105 def build_all_containers(self):
106 """Build all containers."""
107 for container in self.containers:
108 self.engine.container = self.containers[container]
111 def create_all_containers(self):
112 """Create all containers."""
113 for container in self.containers:
114 self.engine.container = self.containers[container]
117 def execute_on_container(self, name, command):
118 """Execute command on container with name.
120 :param name: Container name.
121 :param command: Command to execute.
125 self.engine.container = self.get_container_by_name(name)
126 self.engine.execute(command)
128 def execute_on_all_containers(self, command):
129 """Execute command on all containers.
131 :param command: Command to execute.
134 for container in self.containers:
135 self.engine.container = self.containers[container]
136 self.engine.execute(command)
138 def install_vpp_in_all_containers(self):
139 """Install VPP into all containers."""
140 for container in self.containers:
141 self.engine.container = self.containers[container]
142 # We need to install supervisor client/server system to control VPP
144 self.engine.execute('apt-get update')
145 self.engine.install_supervisor()
146 self.engine.install_vpp()
147 self.engine.restart_vpp()
149 def restart_vpp_in_all_containers(self):
150 """Restart VPP on all containers."""
151 for container in self.containers:
152 self.engine.container = self.containers[container]
153 self.engine.restart_vpp()
155 def configure_vpp_in_all_containers(self, chain_topology, **kwargs):
156 """Configure VPP in all containers.
158 :param chain_topology: Topology used for chaining containers can be
159 chain or cross_horiz. Chain topology is using 1 memif pair per
160 container. Cross_horiz topology is using 1 memif and 1 physical
161 interface in container (only single container can be configured).
162 :param kwargs: Named parameters.
163 :type chain_topology: str
166 # Count number of DUTs based on node's host information
167 dut_cnt = len(Counter([self.containers[container].node['host']
168 for container in self.containers]))
169 mod = len(self.containers)/dut_cnt
170 container_vat_template = 'memif_create_{topology}.vat'.format(
171 topology=chain_topology)
173 for i, container in enumerate(self.containers):
176 sid1 = i % mod * 2 + 1
177 sid2 = i % mod * 2 + 2
178 self.engine.container = self.containers[container]
179 guest_dir = self.engine.container.mnt[0].split(':')[1]
181 if chain_topology == 'chain':
182 self.engine.create_vpp_startup_config()
183 self.engine.create_vpp_exec_config(
184 container_vat_template,
185 mid1=mid1, mid2=mid2, sid1=sid1, sid2=sid2,
186 socket1='{dir}/memif-{c.name}-{sid}'.
187 format(c=self.engine.container, sid=sid1, dir=guest_dir),
188 socket2='{dir}/memif-{c.name}-{sid}'.
189 format(c=self.engine.container, sid=sid2, dir=guest_dir))
190 elif chain_topology == 'cross_horiz':
192 dut1_if = kwargs['dut1_if']
193 dut2_if = kwargs['dut2_if']
195 raise AttributeError('DUT interfaces not specified!')
196 if 'DUT1' in self.engine.container.name:
197 if_pci = Topology.get_interface_pci_addr(
198 self.engine.container.node, dut1_if)
199 if_name = Topology.get_interface_name(
200 self.engine.container.node, dut1_if)
201 if 'DUT2' in self.engine.container.name:
202 if_pci = Topology.get_interface_pci_addr(
203 self.engine.container.node, dut2_if)
204 if_name = Topology.get_interface_name(
205 self.engine.container.node, dut2_if)
206 self.engine.create_vpp_startup_config_dpdk_dev(if_pci)
207 self.engine.create_vpp_exec_config(
208 container_vat_template,
209 mid1=mid1, sid1=sid1, if_name=if_name,
210 socket1='{dir}/memif-{c.name}-{sid}'.
211 format(c=self.engine.container, sid=sid1, dir=guest_dir))
212 elif chain_topology == 'chain_functional':
213 memif_rx_mode = 'interrupt'
214 self.engine.create_vpp_startup_config_func_dev()
215 self.engine.create_vpp_exec_config(
216 container_vat_template,
217 mid1=mid1, mid2=mid2, sid1=sid1, sid2=sid2,
218 socket1='{dir}/memif-{c.name}-{sid}'.
219 format(c=self.engine.container, sid=sid1, dir=guest_dir),
220 socket2='{dir}/memif-{c.name}-{sid}'.
221 format(c=self.engine.container, sid=sid2, dir=guest_dir),
222 rx_mode=memif_rx_mode)
223 elif chain_topology == 'chain_ip4':
224 self.engine.create_vpp_startup_config()
225 vif1_mac = kwargs['tg_if1_mac'] \
226 if (mid1 - 1) % kwargs['nodes'] + 1 == 1 \
227 else '52:54:00:00:{0:02X}:02'.format(mid1-1)
228 vif2_mac = kwargs['tg_if2_mac'] \
229 if (mid2 - 1) % kwargs['nodes'] + 1 == kwargs['nodes'] \
230 else '52:54:00:00:{0:02X}:01'.format(mid2+1)
231 self.engine.create_vpp_exec_config(
232 container_vat_template,
233 mid1=mid1, mid2=mid2, sid1=sid1, sid2=sid2,
234 socket1='{dir}/memif-{c.name}-{sid}'.
235 format(c=self.engine.container, sid=sid1, dir=guest_dir),
236 socket2='{dir}/memif-{c.name}-{sid}'.
237 format(c=self.engine.container, sid=sid2, dir=guest_dir),
238 mac1='52:54:00:00:{0:02X}:01'.format(mid1),
239 mac2='52:54:00:00:{0:02X}:02'.format(mid2),
240 vif1_mac=vif1_mac, vif2_mac=vif2_mac)
242 raise RuntimeError('Container topology {topology} not '
244 format(topology=chain_topology))
246 def stop_all_containers(self):
247 """Stop all containers."""
248 for container in self.containers:
249 self.engine.container = self.containers[container]
252 def destroy_all_containers(self):
253 """Destroy all containers."""
254 for container in self.containers:
255 self.engine.container = self.containers[container]
256 self.engine.destroy()
259 class ContainerEngine(object):
260 """Abstract class for container engine."""
263 """Init ContainerEngine object."""
264 self.container = None
266 def initialize(self):
267 """Initialize container object."""
268 self.container = Container()
270 def acquire(self, force):
271 """Acquire/download container.
273 :param force: Destroy a container if exists and create.
276 raise NotImplementedError
279 """Build container (compile)."""
280 raise NotImplementedError
283 """Create/deploy container."""
284 raise NotImplementedError
286 def execute(self, command):
287 """Execute process inside container.
289 :param command: Command to run inside container.
292 raise NotImplementedError
295 """Stop container."""
296 raise NotImplementedError
299 """Destroy/remove container."""
300 raise NotImplementedError
303 """Info about container."""
304 raise NotImplementedError
306 def system_info(self):
308 raise NotImplementedError
310 def install_supervisor(self):
311 """Install supervisord inside a container."""
312 self.execute('apt-get install -y supervisor')
313 self.execute('echo "{config}" > {config_file} && '
314 'supervisord -c {config_file}'.
316 config='[unix_http_server]\n'
317 'file = /tmp/supervisor.sock\n\n'
318 '[rpcinterface:supervisor]\n'
319 'supervisor.rpcinterface_factory = '
320 'supervisor.rpcinterface:make_main_rpcinterface\n\n'
322 'serverurl = unix:///tmp/supervisor.sock\n\n'
324 'pidfile = /tmp/supervisord.pid\n'
325 'identifier = supervisor\n'
327 'logfile=/tmp/supervisord.log\n'
329 'nodaemon=false\n\n',
330 config_file=SUPERVISOR_CONF))
332 def install_vpp(self):
333 """Install VPP inside a container."""
334 self.execute('ln -s /dev/null /etc/sysctl.d/80-vpp.conf')
335 # Workaround for install xenial vpp build on bionic ubuntu.
336 self.execute('apt-get install -y wget')
337 self.execute('deb=$(mktemp) && wget -O "${deb}" '
338 'http://launchpadlibrarian.net/336117627/'
339 'libmbedcrypto0_2.5.1-1ubuntu1_amd64.deb && '
340 'dpkg -i "${deb}" && '
342 self.execute('deb=$(mktemp) && wget -O "${deb}" '
343 'http://launchpadlibrarian.net/252876048/'
344 'libboost-system1.58.0_1.58.0+dfsg-5ubuntu3_amd64.deb && '
345 'dpkg -i "${deb}" && '
348 'dpkg -i --force-all '
349 '{guest_dir}/openvpp-testing/download_dir/*.deb'.
350 format(guest_dir=self.container.mnt[0].split(':')[1]))
351 self.execute('apt-get -f install -y')
352 self.execute('apt-get install -y ca-certificates')
353 self.execute('echo "{config}" >> {config_file}'.
355 config='[program:vpp]\n'
356 'command=/usr/bin/vpp -c /etc/vpp/startup.conf\n'
357 'autorestart=false\n'
358 'redirect_stderr=true\n'
360 config_file=SUPERVISOR_CONF))
361 self.execute('supervisorctl reload')
363 def restart_vpp(self):
364 """Restart VPP service inside a container."""
365 self.execute('supervisorctl restart vpp')
366 self.execute('cat /tmp/supervisord.log')
368 def create_base_vpp_startup_config(self):
369 """Create base startup configuration of VPP on container.
371 :returns: Base VPP startup configuration.
372 :rtype: VppConfigGenerator
374 cpuset_cpus = self.container.cpuset_cpus
376 # Create config instance
377 vpp_config = VppConfigGenerator()
378 vpp_config.set_node(self.container.node)
379 vpp_config.add_unix_cli_listen()
380 vpp_config.add_unix_nodaemon()
381 vpp_config.add_unix_exec('/tmp/running.exec')
382 # We will pop the first core from the list to be a main core
383 vpp_config.add_cpu_main_core(str(cpuset_cpus.pop(0)))
384 # If more cores in the list, the rest will be used as workers.
386 corelist_workers = ','.join(str(cpu) for cpu in cpuset_cpus)
387 vpp_config.add_cpu_corelist_workers(corelist_workers)
391 def create_vpp_startup_config(self):
392 """Create startup configuration of VPP without DPDK on container.
394 vpp_config = self.create_base_vpp_startup_config()
395 vpp_config.add_plugin('disable', 'dpdk_plugin.so')
397 # Apply configuration
398 self.execute('mkdir -p /etc/vpp/')
399 self.execute('echo "{config}" | tee /etc/vpp/startup.conf'
400 .format(config=vpp_config.get_config_str()))
402 def create_vpp_startup_config_dpdk_dev(self, *devices):
403 """Create startup configuration of VPP with DPDK on container.
405 :param devices: List of PCI devices to add.
408 vpp_config = self.create_base_vpp_startup_config()
409 vpp_config.add_dpdk_dev(*devices)
410 vpp_config.add_dpdk_no_tx_checksum_offload()
411 vpp_config.add_dpdk_log_level('debug')
412 vpp_config.add_plugin('disable', 'default')
413 vpp_config.add_plugin('enable', 'dpdk_plugin.so')
414 vpp_config.add_plugin('enable', 'memif_plugin.so')
416 # Apply configuration
417 self.execute('mkdir -p /etc/vpp/')
418 self.execute('echo "{config}" | tee /etc/vpp/startup.conf'
419 .format(config=vpp_config.get_config_str()))
421 def create_vpp_startup_config_func_dev(self):
422 """Create startup configuration of VPP on container for functional
425 # Create config instance
426 vpp_config = VppConfigGenerator()
427 vpp_config.set_node(self.container.node)
428 vpp_config.add_unix_cli_listen()
429 vpp_config.add_unix_nodaemon()
430 vpp_config.add_unix_exec('/tmp/running.exec')
431 vpp_config.add_plugin('disable', 'dpdk_plugin.so')
433 # Apply configuration
434 self.execute('mkdir -p /etc/vpp/')
435 self.execute('echo "{config}" | tee /etc/vpp/startup.conf'
436 .format(config=vpp_config.get_config_str()))
438 def create_vpp_exec_config(self, vat_template_file, **kwargs):
439 """Create VPP exec configuration on container.
441 :param vat_template_file: File name of a VAT template script.
442 :param kwargs: Parameters for VAT script.
443 :type vat_template_file: str
446 vat_file_path = '{p}/{f}'.format(p=Constants.RESOURCES_TPL_VAT,
449 with open(vat_file_path, 'r') as template_file:
450 cmd_template = template_file.readlines()
451 for line_tmpl in cmd_template:
452 vat_cmd = line_tmpl.format(**kwargs)
453 self.execute('echo "{c}" >> /tmp/running.exec'
454 .format(c=vat_cmd.replace('\n', '')))
456 def is_container_running(self):
457 """Check if container is running."""
458 raise NotImplementedError
460 def is_container_present(self):
461 """Check if container is present."""
462 raise NotImplementedError
464 def _configure_cgroup(self, name):
465 """Configure the control group associated with a container.
467 By default the cpuset cgroup is using exclusive CPU/MEM. When Docker/LXC
468 container is initialized a new cgroup /docker or /lxc is created under
469 cpuset parent tree. This newly created cgroup is inheriting parent
470 setting for cpu/mem exclusive parameter and thus cannot be overriden
471 within /docker or /lxc cgroup. This function is supposed to set cgroups
472 to allow coexistence of both engines.
474 :param name: Name of cgroup.
476 :raises RuntimeError: If applying cgroup settings via cgset failed.
478 ret, _, _ = self.container.ssh.exec_command_sudo(
479 'cgset -r cpuset.cpu_exclusive=0 /')
481 raise RuntimeError('Failed to apply cgroup settings.')
483 ret, _, _ = self.container.ssh.exec_command_sudo(
484 'cgset -r cpuset.mem_exclusive=0 /')
486 raise RuntimeError('Failed to apply cgroup settings.')
488 ret, _, _ = self.container.ssh.exec_command_sudo(
489 'cgcreate -g cpuset:/{name}'.format(name=name))
491 raise RuntimeError('Failed to copy cgroup settings from root.')
493 ret, _, _ = self.container.ssh.exec_command_sudo(
494 'cgset -r cpuset.cpu_exclusive=0 /{name}'.format(name=name))
496 raise RuntimeError('Failed to apply cgroup settings.')
498 ret, _, _ = self.container.ssh.exec_command_sudo(
499 'cgset -r cpuset.mem_exclusive=0 /{name}'.format(name=name))
501 raise RuntimeError('Failed to apply cgroup settings.')
504 class LXC(ContainerEngine):
505 """LXC implementation."""
507 # Implicit constructor is inherited.
509 def acquire(self, force=True):
510 """Acquire a privileged system object where configuration is stored.
512 :param force: If a container exists, destroy it and create a new
515 :raises RuntimeError: If creating the container or writing the container
518 if self.is_container_present():
524 image = self.container.image if self.container.image else\
525 "-d ubuntu -r xenial -a amd64"
527 cmd = 'lxc-create -t download --name {c.name} -- {image} '\
528 '--no-validate'.format(c=self.container, image=image)
530 ret, _, _ = self.container.ssh.exec_command_sudo(cmd, timeout=1800)
532 raise RuntimeError('Failed to create container.')
534 self._configure_cgroup('lxc')
537 """Create/deploy an application inside a container on system.
539 :raises RuntimeError: If creating the container fails.
541 if self.container.mnt:
542 for mount in self.container.mnt:
543 host_dir, guest_dir = mount.split(':')
544 entry = 'lxc.mount.entry = {host_dir} '\
545 '/var/lib/lxc/{c.name}/rootfs{guest_dir} none ' \
546 'bind,create=dir 0 0'.format(c=self.container,
549 ret, _, _ = self.container.ssh.exec_command_sudo(
550 "sh -c 'echo \"{e}\" >> /var/lib/lxc/{c.name}/config'".
551 format(e=entry, c=self.container))
553 raise RuntimeError('Failed to write {c.name} config.'
554 .format(c=self.container))
556 cpuset_cpus = '{0}'.format(
557 ','.join('%s' % cpu for cpu in self.container.cpuset_cpus))\
558 if self.container.cpuset_cpus else ''
560 ret, _, _ = self.container.ssh.exec_command_sudo(
561 'lxc-start --name {c.name} --daemon'.
562 format(c=self.container))
564 raise RuntimeError('Failed to start container {c.name}.'.
565 format(c=self.container))
566 self._lxc_wait('RUNNING')
568 # Workaround for LXC to be able to allocate all cpus including isolated.
569 ret, _, _ = self.container.ssh.exec_command_sudo(
570 'cgset --copy-from / lxc/')
572 raise RuntimeError('Failed to copy cgroup to LXC')
574 ret, _, _ = self.container.ssh.exec_command_sudo(
575 'lxc-cgroup --name {c.name} cpuset.cpus {cpus}'.
576 format(c=self.container, cpus=cpuset_cpus))
578 raise RuntimeError('Failed to set cpuset.cpus to container '
579 '{c.name}.'.format(c=self.container))
581 def execute(self, command):
582 """Start a process inside a running container.
584 Runs the specified command inside the container specified by name. The
585 container has to be running already.
587 :param command: Command to run inside container.
589 :raises RuntimeError: If running the command failed.
591 env = '--keep-env {0}'.format(
592 ' '.join('--set-var %s' % env for env in self.container.env))\
593 if self.container.env else ''
595 cmd = "lxc-attach {env} --name {c.name} -- /bin/sh -c '{command}; "\
596 "exit $?'".format(env=env, c=self.container, command=command)
598 ret, _, _ = self.container.ssh.exec_command_sudo(cmd, timeout=180)
600 raise RuntimeError('Failed to run command inside container '
601 '{c.name}.'.format(c=self.container))
606 :raises RuntimeError: If stopping the container failed.
608 cmd = 'lxc-stop --name {c.name}'.format(c=self.container)
610 ret, _, _ = self.container.ssh.exec_command_sudo(cmd)
612 raise RuntimeError('Failed to stop container {c.name}.'
613 .format(c=self.container))
614 self._lxc_wait('STOPPED|FROZEN')
617 """Destroy a container.
619 :raises RuntimeError: If destroying container failed.
621 cmd = 'lxc-destroy --force --name {c.name}'.format(c=self.container)
623 ret, _, _ = self.container.ssh.exec_command_sudo(cmd)
625 raise RuntimeError('Failed to destroy container {c.name}.'
626 .format(c=self.container))
629 """Query and shows information about a container.
631 :raises RuntimeError: If getting info about a container failed.
633 cmd = 'lxc-info --name {c.name}'.format(c=self.container)
635 ret, _, _ = self.container.ssh.exec_command_sudo(cmd)
637 raise RuntimeError('Failed to get info about container {c.name}.'
638 .format(c=self.container))
640 def system_info(self):
641 """Check the current kernel for LXC support.
643 :raises RuntimeError: If checking LXC support failed.
645 cmd = 'lxc-checkconfig'
647 ret, _, _ = self.container.ssh.exec_command_sudo(cmd)
649 raise RuntimeError('Failed to check LXC support.')
651 def is_container_running(self):
652 """Check if container is running on node.
654 :returns: True if container is running.
656 :raises RuntimeError: If getting info about a container failed.
658 cmd = 'lxc-info --no-humanize --state --name {c.name}'\
659 .format(c=self.container)
661 ret, stdout, _ = self.container.ssh.exec_command_sudo(cmd)
663 raise RuntimeError('Failed to get info about container {c.name}.'
664 .format(c=self.container))
665 return True if 'RUNNING' in stdout else False
667 def is_container_present(self):
668 """Check if container is existing on node.
670 :returns: True if container is present.
672 :raises RuntimeError: If getting info about a container failed.
674 cmd = 'lxc-info --no-humanize --name {c.name}'.format(c=self.container)
676 ret, _, _ = self.container.ssh.exec_command_sudo(cmd)
677 return False if int(ret) else True
679 def _lxc_wait(self, state):
680 """Wait for a specific container state.
682 :param state: Specify the container state(s) to wait for.
684 :raises RuntimeError: If waiting for state of a container failed.
686 cmd = 'lxc-wait --name {c.name} --state "{s}"'\
687 .format(c=self.container, s=state)
689 ret, _, _ = self.container.ssh.exec_command_sudo(cmd)
691 raise RuntimeError('Failed to wait for state "{s}" of container '
692 '{c.name}.'.format(s=state, c=self.container))
695 class Docker(ContainerEngine):
696 """Docker implementation."""
698 # Implicit constructor is inherited.
700 def acquire(self, force=True):
701 """Pull an image or a repository from a registry.
703 :param force: Destroy a container if exists.
705 :raises RuntimeError: If pulling a container failed.
707 if self.is_container_present():
713 if not self.container.image:
714 setattr(self.container, 'image', 'snergster/csit-sut:latest')
716 cmd = 'docker pull {image}'.format(image=self.container.image)
718 ret, _, _ = self.container.ssh.exec_command_sudo(cmd, timeout=1800)
720 raise RuntimeError('Failed to create container {c.name}.'
721 .format(c=self.container))
722 if self.container.cpuset_cpus:
723 self._configure_cgroup('docker')
726 """Create/deploy container.
728 :raises RuntimeError: If creating a container failed.
730 cpuset_cpus = '--cpuset-cpus={0}'.format(
731 ','.join('%s' % cpu for cpu in self.container.cpuset_cpus))\
732 if self.container.cpuset_cpus else ''
734 cpuset_mems = '--cpuset-mems={0}'.format(self.container.cpuset_mems)\
735 if self.container.cpuset_mems is not None else ''
736 # Temporary workaround - disabling due to bug in memif
740 ' '.join('--env %s' % env for env in self.container.env))\
741 if self.container.env else ''
743 command = '{0}'.format(self.container.command)\
744 if self.container.command else ''
746 publish = '{0}'.format(
747 ' '.join('--publish %s' % var for var in self.container.publish))\
748 if self.container.publish else ''
750 volume = '{0}'.format(
751 ' '.join('--volume %s' % mnt for mnt in self.container.mnt))\
752 if self.container.mnt else ''
755 '--privileged --detach --interactive --tty --rm '\
756 '--cgroup-parent docker {cpuset_cpus} {cpuset_mems} {publish} '\
757 '{env} {volume} --name {container.name} {container.image} '\
758 '{command}'.format(cpuset_cpus=cpuset_cpus, cpuset_mems=cpuset_mems,
759 container=self.container, command=command,
760 env=env, publish=publish, volume=volume)
762 ret, _, _ = self.container.ssh.exec_command_sudo(cmd)
764 raise RuntimeError('Failed to create container {c.name}'
765 .format(c=self.container))
769 def execute(self, command):
770 """Start a process inside a running container.
772 Runs the specified command inside the container specified by name. The
773 container has to be running already.
775 :param command: Command to run inside container.
777 :raises RuntimeError: If runnig the command in a container failed.
779 cmd = "docker exec --interactive {c.name} /bin/sh -c '{command}; "\
780 "exit $?'".format(c=self.container, command=command)
782 ret, _, _ = self.container.ssh.exec_command_sudo(cmd, timeout=180)
784 raise RuntimeError('Failed to execute command in container '
785 '{c.name}.'.format(c=self.container))
788 """Stop running container.
790 :raises RuntimeError: If stopping a container failed.
792 cmd = 'docker stop {c.name}'.format(c=self.container)
794 ret, _, _ = self.container.ssh.exec_command_sudo(cmd)
796 raise RuntimeError('Failed to stop container {c.name}.'
797 .format(c=self.container))
800 """Remove a container.
802 :raises RuntimeError: If removing a container failed.
804 cmd = 'docker rm --force {c.name}'.format(c=self.container)
806 ret, _, _ = self.container.ssh.exec_command_sudo(cmd)
808 raise RuntimeError('Failed to destroy container {c.name}.'
809 .format(c=self.container))
812 """Return low-level information on Docker objects.
814 :raises RuntimeError: If getting info about a container failed.
816 cmd = 'docker inspect {c.name}'.format(c=self.container)
818 ret, _, _ = self.container.ssh.exec_command_sudo(cmd)
820 raise RuntimeError('Failed to get info about container {c.name}.'
821 .format(c=self.container))
823 def system_info(self):
824 """Display the docker system-wide information.
826 :raises RuntimeError: If displaying system information failed.
828 cmd = 'docker system info'
830 ret, _, _ = self.container.ssh.exec_command_sudo(cmd)
832 raise RuntimeError('Failed to get system info.')
834 def is_container_present(self):
835 """Check if container is present on node.
837 :returns: True if container is present.
839 :raises RuntimeError: If getting info about a container failed.
841 cmd = 'docker ps --all --quiet --filter name={c.name}'\
842 .format(c=self.container)
844 ret, stdout, _ = self.container.ssh.exec_command_sudo(cmd)
846 raise RuntimeError('Failed to get info about container {c.name}.'
847 .format(c=self.container))
848 return True if stdout else False
850 def is_container_running(self):
851 """Check if container is running on node.
853 :returns: True if container is running.
855 :raises RuntimeError: If getting info about a container failed.
857 cmd = 'docker ps --quiet --filter name={c.name}'\
858 .format(c=self.container)
860 ret, stdout, _ = self.container.ssh.exec_command_sudo(cmd)
862 raise RuntimeError('Failed to get info about container {c.name}.'
863 .format(c=self.container))
864 return True if stdout else False
867 class Container(object):
868 """Container class."""
871 """Initialize Container object."""
874 def __getattr__(self, attr):
875 """Get attribute custom implementation.
877 :param attr: Attribute to get.
879 :returns: Attribute value or None.
883 return self.__dict__[attr]
887 def __setattr__(self, attr, value):
888 """Set attribute custom implementation.
890 :param attr: Attribute to set.
891 :param value: Value to set.
896 # Check if attribute exists
899 # Creating new attribute
901 self.__dict__['ssh'] = SSH()
902 self.__dict__['ssh'].connect(value)
903 self.__dict__[attr] = value
905 # Updating attribute base of type
906 if isinstance(self.__dict__[attr], list):
907 self.__dict__[attr].append(value)
909 self.__dict__[attr] = value