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.install_supervisor()
145 self.engine.install_vpp()
146 self.engine.restart_vpp()
148 def restart_vpp_in_all_containers(self):
149 """Restart VPP on all containers."""
150 for container in self.containers:
151 self.engine.container = self.containers[container]
152 self.engine.restart_vpp()
154 def configure_vpp_in_all_containers(self, chain_topology, **kwargs):
155 """Configure VPP in all containers.
157 :param chain_topology: Topology used for chaining containers can be
158 chain or cross_horiz. Chain topology is using 1 memif pair per
159 container. Cross_horiz topology is using 1 memif and 1 physical
160 interface in container (only single container can be configured).
161 :param kwargs: Named parameters.
162 :type chain_topology: str
165 # Count number of DUTs based on node's host information
166 dut_cnt = len(Counter([self.containers[container].node['host']
167 for container in self.containers]))
168 mod = len(self.containers)/dut_cnt
169 container_vat_template = 'memif_create_{topology}.vat'.format(
170 topology=chain_topology)
172 if chain_topology == 'chain':
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 self.engine.create_vpp_startup_config()
180 c_name = self.engine.container.name
181 guest_dir = self.engine.container.mnt[0].split(':')[1]
182 self.engine.create_vpp_exec_config(
183 container_vat_template,
184 mid1=mid1, mid2=mid2, sid1=sid1, sid2=sid2,
185 socket1='{dir}/memif-{c_name}-{sid}'.
186 format(c_name=c_name, sid=sid1, dir=guest_dir),
187 socket2='{dir}/memif-{c_name}-{sid}'.
188 format(c_name=c_name, sid=sid2, dir=guest_dir))
189 elif chain_topology == 'cross_horiz':
191 raise RuntimeError('Container chain topology {topology} '
192 'supports only single container.'.
193 format(topology=chain_topology))
194 for i, container in enumerate(self.containers):
196 sid1 = i % mod * 2 + 1
198 dut1_if = kwargs['dut1_if']
200 raise AttributeError('Missing dut1_if parameter.')
202 dut2_if = kwargs['dut2_if']
204 raise AttributeError('Missing dut2_if parameter.')
205 self.engine.container = self.containers[container]
206 c_name = self.engine.container.name
207 guest_dir = self.engine.container.mnt[0].split(':')[1]
208 if 'DUT1' in self.engine.container.name:
209 if_pci = Topology.get_interface_pci_addr( \
210 self.engine.container.node, dut1_if)
211 if_name = Topology.get_interface_name( \
212 self.engine.container.node, dut1_if)
213 if 'DUT2' in self.engine.container.name:
214 if_pci = Topology.get_interface_pci_addr( \
215 self.engine.container.node, dut2_if)
216 if_name = Topology.get_interface_name( \
217 self.engine.container.node, dut2_if)
218 self.engine.create_vpp_startup_config_dpdk_dev(if_pci)
219 self.engine.create_vpp_exec_config(
220 container_vat_template,
221 mid1=mid1, sid1=sid1, if_name=if_name,
222 socket1='{dir}/memif-{c_name}-{sid}'.
223 format(c_name=c_name, sid=sid1, dir=guest_dir))
224 elif chain_topology == 'chain_functional':
225 for i, container in enumerate(self.containers):
228 sid1 = i % mod * 2 + 1
229 sid2 = i % mod * 2 + 2
230 memif_rx_mode = 'interrupt'
231 self.engine.container = self.containers[container]
232 self.engine.create_vpp_startup_config_func_dev()
233 c_name = self.engine.container.name
234 guest_dir = self.engine.container.mnt[0].split(':')[1]
235 self.engine.create_vpp_exec_config(
236 container_vat_template,
237 mid1=mid1, mid2=mid2, sid1=sid1, sid2=sid2,
238 socket1='{dir}/memif-{c_name}-{sid}'.
239 format(c_name=c_name, sid=sid1, dir=guest_dir),
240 socket2='{dir}/memif-{c_name}-{sid}'.
241 format(c_name=c_name, sid=sid2, dir=guest_dir),
242 rx_mode=memif_rx_mode)
244 raise RuntimeError('Container topology {topology} not implemented'.
245 format(topology=chain_topology))
247 def stop_all_containers(self):
248 """Stop all containers."""
249 for container in self.containers:
250 self.engine.container = self.containers[container]
253 def destroy_all_containers(self):
254 """Destroy all containers."""
255 for container in self.containers:
256 self.engine.container = self.containers[container]
257 self.engine.destroy()
260 class ContainerEngine(object):
261 """Abstract class for container engine."""
264 """Init ContainerEngine object."""
265 self.container = None
267 def initialize(self):
268 """Initialize container object."""
269 self.container = Container()
271 def acquire(self, force):
272 """Acquire/download container.
274 :param force: Destroy a container if exists and create.
277 raise NotImplementedError
280 """Build container (compile)."""
281 raise NotImplementedError
284 """Create/deploy container."""
285 raise NotImplementedError
287 def execute(self, command):
288 """Execute process inside container.
290 :param command: Command to run inside container.
293 raise NotImplementedError
296 """Stop container."""
297 raise NotImplementedError
300 """Destroy/remove container."""
301 raise NotImplementedError
304 """Info about container."""
305 raise NotImplementedError
307 def system_info(self):
309 raise NotImplementedError
311 def install_supervisor(self):
312 """Install supervisord inside a container."""
313 self.execute('sleep 3')
314 self.execute('apt-get update')
315 self.execute('apt-get install -y supervisor')
316 self.execute('echo "{config}" > {config_file}'.
318 config='[unix_http_server]\n'
319 'file = /tmp/supervisor.sock\n\n'
320 '[rpcinterface:supervisor]\n'
321 'supervisor.rpcinterface_factory = '
322 'supervisor.rpcinterface:make_main_rpcinterface\n\n'
324 'serverurl = unix:///tmp/supervisor.sock\n\n'
326 'pidfile = /tmp/supervisord.pid\n'
327 'identifier = supervisor\n'
329 'logfile=/tmp/supervisord.log\n'
331 'nodaemon=false\n\n',
332 config_file=SUPERVISOR_CONF))
333 self.execute('supervisord -c {config_file}'.
334 format(config_file=SUPERVISOR_CONF))
336 def install_vpp(self):
337 """Install VPP inside a container."""
338 self.execute('ln -s /dev/null /etc/sysctl.d/80-vpp.conf')
339 self.execute('apt-get update')
340 # Workaround for install xenial vpp build on bionic ubuntu.
341 self.execute('apt-get install -y wget')
342 self.execute('deb=$(mktemp) && wget -O "${deb}" '
343 'http://launchpadlibrarian.net/336117627/'
344 'libmbedcrypto0_2.5.1-1ubuntu1_amd64.deb && '
345 'dpkg -i "${deb}" && '
347 self.execute('deb=$(mktemp) && wget -O "${deb}" '
348 'http://launchpadlibrarian.net/252876048/'
349 'libboost-system1.58.0_1.58.0+dfsg-5ubuntu3_amd64.deb && '
350 'dpkg -i "${deb}" && '
353 'dpkg -i --force-all '
354 '{guest_dir}/openvpp-testing/download_dir/*.deb'.
355 format(guest_dir=self.container.mnt[0].split(':')[1]))
356 self.execute('apt-get -f install -y')
357 self.execute('apt-get install -y ca-certificates')
358 self.execute('echo "{config}" >> {config_file}'.
360 config='[program:vpp]\n'
361 'command=/usr/bin/vpp -c /etc/vpp/startup.conf\n'
362 'autorestart=false\n'
363 'redirect_stderr=true\n'
365 config_file=SUPERVISOR_CONF))
366 self.execute('supervisorctl reload')
367 self.execute('supervisorctl restart vpp')
369 def restart_vpp(self):
370 """Restart VPP service inside a container."""
371 self.execute('supervisorctl restart vpp')
372 self.execute('cat /tmp/supervisord.log')
374 def create_base_vpp_startup_config(self):
375 """Create base startup configuration of VPP on container.
377 :returns: Base VPP startup configuration.
378 :rtype: VppConfigGenerator
380 cpuset_cpus = self.container.cpuset_cpus
382 # Create config instance
383 vpp_config = VppConfigGenerator()
384 vpp_config.set_node(self.container.node)
385 vpp_config.add_unix_cli_listen()
386 vpp_config.add_unix_nodaemon()
387 vpp_config.add_unix_exec('/tmp/running.exec')
388 # We will pop the first core from the list to be a main core
389 vpp_config.add_cpu_main_core(str(cpuset_cpus.pop(0)))
390 # If more cores in the list, the rest will be used as workers.
392 corelist_workers = ','.join(str(cpu) for cpu in cpuset_cpus)
393 vpp_config.add_cpu_corelist_workers(corelist_workers)
397 def create_vpp_startup_config(self):
398 """Create startup configuration of VPP without DPDK on container.
400 vpp_config = self.create_base_vpp_startup_config()
401 vpp_config.add_plugin('disable', 'dpdk_plugin.so')
403 # Apply configuration
404 self.execute('mkdir -p /etc/vpp/')
405 self.execute('echo "{config}" | tee /etc/vpp/startup.conf'
406 .format(config=vpp_config.get_config_str()))
408 def create_vpp_startup_config_dpdk_dev(self, *devices):
409 """Create startup configuration of VPP with DPDK on container.
411 :param devices: List of PCI devices to add.
414 vpp_config = self.create_base_vpp_startup_config()
415 vpp_config.add_dpdk_dev(*devices)
416 vpp_config.add_dpdk_no_tx_checksum_offload()
417 vpp_config.add_dpdk_log_level('debug')
418 vpp_config.add_plugin('disable', 'default')
419 vpp_config.add_plugin('enable', 'dpdk_plugin.so')
420 vpp_config.add_plugin('enable', 'memif_plugin.so')
422 # Apply configuration
423 self.execute('mkdir -p /etc/vpp/')
424 self.execute('echo "{config}" | tee /etc/vpp/startup.conf'
425 .format(config=vpp_config.get_config_str()))
427 def create_vpp_startup_config_func_dev(self):
428 """Create startup configuration of VPP on container for functional
431 # Create config instance
432 vpp_config = VppConfigGenerator()
433 vpp_config.set_node(self.container.node)
434 vpp_config.add_unix_cli_listen()
435 vpp_config.add_unix_nodaemon()
436 vpp_config.add_unix_exec('/tmp/running.exec')
437 vpp_config.add_plugin('disable', 'dpdk_plugin.so')
439 # Apply configuration
440 self.execute('mkdir -p /etc/vpp/')
441 self.execute('echo "{config}" | tee /etc/vpp/startup.conf'
442 .format(config=vpp_config.get_config_str()))
444 def create_vpp_exec_config(self, vat_template_file, **kwargs):
445 """Create VPP exec configuration on container.
447 :param vat_template_file: File name of a VAT template script.
448 :param kwargs: Parameters for VAT script.
449 :type vat_template_file: str
452 vat_file_path = '{p}/{f}'.format(p=Constants.RESOURCES_TPL_VAT,
455 with open(vat_file_path, 'r') as template_file:
456 cmd_template = template_file.readlines()
457 for line_tmpl in cmd_template:
458 vat_cmd = line_tmpl.format(**kwargs)
459 self.execute('echo "{c}" >> /tmp/running.exec'
460 .format(c=vat_cmd.replace('\n', '')))
462 def is_container_running(self):
463 """Check if container is running."""
464 raise NotImplementedError
466 def is_container_present(self):
467 """Check if container is present."""
468 raise NotImplementedError
470 def _configure_cgroup(self, name):
471 """Configure the control group associated with a container.
473 By default the cpuset cgroup is using exclusive CPU/MEM. When Docker/LXC
474 container is initialized a new cgroup /docker or /lxc is created under
475 cpuset parent tree. This newly created cgroup is inheriting parent
476 setting for cpu/mem exclusive parameter and thus cannot be overriden
477 within /docker or /lxc cgroup. This function is supposed to set cgroups
478 to allow coexistence of both engines.
480 :param name: Name of cgroup.
482 :raises RuntimeError: If applying cgroup settings via cgset failed.
484 ret, _, _ = self.container.ssh.exec_command_sudo(
485 'cgset -r cpuset.cpu_exclusive=0 /')
487 raise RuntimeError('Failed to apply cgroup settings.')
489 ret, _, _ = self.container.ssh.exec_command_sudo(
490 'cgset -r cpuset.mem_exclusive=0 /')
492 raise RuntimeError('Failed to apply cgroup settings.')
494 ret, _, _ = self.container.ssh.exec_command_sudo(
495 'cgcreate -g cpuset:/{name}'.format(name=name))
497 raise RuntimeError('Failed to copy cgroup settings from root.')
499 ret, _, _ = self.container.ssh.exec_command_sudo(
500 'cgset -r cpuset.cpu_exclusive=0 /{name}'.format(name=name))
502 raise RuntimeError('Failed to apply cgroup settings.')
504 ret, _, _ = self.container.ssh.exec_command_sudo(
505 'cgset -r cpuset.mem_exclusive=0 /{name}'.format(name=name))
507 raise RuntimeError('Failed to apply cgroup settings.')
510 class LXC(ContainerEngine):
511 """LXC implementation."""
513 # Implicit constructor is inherited.
515 def acquire(self, force=True):
516 """Acquire a privileged system object where configuration is stored.
518 :param force: If a container exists, destroy it and create a new
521 :raises RuntimeError: If creating the container or writing the container
524 if self.is_container_present():
530 image = self.container.image if self.container.image else\
531 "-d ubuntu -r xenial -a amd64"
533 cmd = 'lxc-create -t download --name {c.name} -- {image} '\
534 '--no-validate'.format(c=self.container, image=image)
536 ret, _, _ = self.container.ssh.exec_command_sudo(cmd, timeout=1800)
538 raise RuntimeError('Failed to create container.')
540 self._configure_cgroup('lxc')
543 """Create/deploy an application inside a container on system.
545 :raises RuntimeError: If creating the container fails.
547 if self.container.mnt:
548 for mount in self.container.mnt:
549 host_dir, guest_dir = mount.split(':')
550 entry = 'lxc.mount.entry = {host_dir} '\
551 '/var/lib/lxc/{c.name}/rootfs{guest_dir} none ' \
552 'bind,create=dir 0 0'.format(c=self.container,
555 ret, _, _ = self.container.ssh.exec_command_sudo(
556 "sh -c 'echo \"{e}\" >> /var/lib/lxc/{c.name}/config'".
557 format(e=entry, c=self.container))
559 raise RuntimeError('Failed to write {c.name} config.'
560 .format(c=self.container))
562 cpuset_cpus = '{0}'.format(
563 ','.join('%s' % cpu for cpu in self.container.cpuset_cpus))\
564 if self.container.cpuset_cpus else ''
566 ret, _, _ = self.container.ssh.exec_command_sudo(
567 'lxc-start --name {c.name} --daemon'.
568 format(c=self.container))
570 raise RuntimeError('Failed to start container {c.name}.'.
571 format(c=self.container))
572 self._lxc_wait('RUNNING')
574 # Workaround for LXC to be able to allocate all cpus including isolated.
575 ret, _, _ = self.container.ssh.exec_command_sudo(
576 'cgset --copy-from / lxc/')
578 raise RuntimeError('Failed to copy cgroup to LXC')
580 ret, _, _ = self.container.ssh.exec_command_sudo(
581 'lxc-cgroup --name {c.name} cpuset.cpus {cpus}'.
582 format(c=self.container, cpus=cpuset_cpus))
584 raise RuntimeError('Failed to set cpuset.cpus to container '
585 '{c.name}.'.format(c=self.container))
587 def execute(self, command):
588 """Start a process inside a running container.
590 Runs the specified command inside the container specified by name. The
591 container has to be running already.
593 :param command: Command to run inside container.
595 :raises RuntimeError: If running the command failed.
597 env = '--keep-env {0}'.format(
598 ' '.join('--set-var %s' % env for env in self.container.env))\
599 if self.container.env else ''
601 cmd = "lxc-attach {env} --name {c.name} -- /bin/sh -c '{command}; "\
602 "exit $?'".format(env=env, c=self.container, command=command)
604 ret, _, _ = self.container.ssh.exec_command_sudo(cmd, timeout=180)
606 raise RuntimeError('Failed to run command inside container '
607 '{c.name}.'.format(c=self.container))
612 :raises RuntimeError: If stopping the container failed.
614 cmd = 'lxc-stop --name {c.name}'.format(c=self.container)
616 ret, _, _ = self.container.ssh.exec_command_sudo(cmd)
618 raise RuntimeError('Failed to stop container {c.name}.'
619 .format(c=self.container))
620 self._lxc_wait('STOPPED|FROZEN')
623 """Destroy a container.
625 :raises RuntimeError: If destroying container failed.
627 cmd = 'lxc-destroy --force --name {c.name}'.format(c=self.container)
629 ret, _, _ = self.container.ssh.exec_command_sudo(cmd)
631 raise RuntimeError('Failed to destroy container {c.name}.'
632 .format(c=self.container))
635 """Query and shows information about a container.
637 :raises RuntimeError: If getting info about a container failed.
639 cmd = 'lxc-info --name {c.name}'.format(c=self.container)
641 ret, _, _ = self.container.ssh.exec_command_sudo(cmd)
643 raise RuntimeError('Failed to get info about container {c.name}.'
644 .format(c=self.container))
646 def system_info(self):
647 """Check the current kernel for LXC support.
649 :raises RuntimeError: If checking LXC support failed.
651 cmd = 'lxc-checkconfig'
653 ret, _, _ = self.container.ssh.exec_command_sudo(cmd)
655 raise RuntimeError('Failed to check LXC support.')
657 def is_container_running(self):
658 """Check if container is running on node.
660 :returns: True if container is running.
662 :raises RuntimeError: If getting info about a container failed.
664 cmd = 'lxc-info --no-humanize --state --name {c.name}'\
665 .format(c=self.container)
667 ret, stdout, _ = self.container.ssh.exec_command_sudo(cmd)
669 raise RuntimeError('Failed to get info about container {c.name}.'
670 .format(c=self.container))
671 return True if 'RUNNING' in stdout else False
673 def is_container_present(self):
674 """Check if container is existing on node.
676 :returns: True if container is present.
678 :raises RuntimeError: If getting info about a container failed.
680 cmd = 'lxc-info --no-humanize --name {c.name}'.format(c=self.container)
682 ret, _, _ = self.container.ssh.exec_command_sudo(cmd)
683 return False if int(ret) else True
685 def _lxc_wait(self, state):
686 """Wait for a specific container state.
688 :param state: Specify the container state(s) to wait for.
690 :raises RuntimeError: If waiting for state of a container failed.
692 cmd = 'lxc-wait --name {c.name} --state "{s}"'\
693 .format(c=self.container, s=state)
695 ret, _, _ = self.container.ssh.exec_command_sudo(cmd)
697 raise RuntimeError('Failed to wait for state "{s}" of container '
698 '{c.name}.'.format(s=state, c=self.container))
701 class Docker(ContainerEngine):
702 """Docker implementation."""
704 # Implicit constructor is inherited.
706 def acquire(self, force=True):
707 """Pull an image or a repository from a registry.
709 :param force: Destroy a container if exists.
711 :raises RuntimeError: If pulling a container failed.
713 if self.is_container_present():
719 if not self.container.image:
720 setattr(self.container, 'image', 'snergster/csit-sut:latest')
722 cmd = 'docker pull {image}'.format(image=self.container.image)
724 ret, _, _ = self.container.ssh.exec_command_sudo(cmd, timeout=1800)
726 raise RuntimeError('Failed to create container {c.name}.'
727 .format(c=self.container))
728 if self.container.cpuset_cpus:
729 self._configure_cgroup('docker')
732 """Create/deploy container.
734 :raises RuntimeError: If creating a container failed.
736 cpuset_cpus = '--cpuset-cpus={0}'.format(
737 ','.join('%s' % cpu for cpu in self.container.cpuset_cpus))\
738 if self.container.cpuset_cpus else ''
740 cpuset_mems = '--cpuset-mems={0}'.format(self.container.cpuset_mems)\
741 if self.container.cpuset_mems is not None else ''
742 # Temporary workaround - disabling due to bug in memif
746 ' '.join('--env %s' % env for env in self.container.env))\
747 if self.container.env else ''
749 command = '{0}'.format(self.container.command)\
750 if self.container.command else ''
752 publish = '{0}'.format(
753 ' '.join('--publish %s' % var for var in self.container.publish))\
754 if self.container.publish else ''
756 volume = '{0}'.format(
757 ' '.join('--volume %s' % mnt for mnt in self.container.mnt))\
758 if self.container.mnt else ''
761 '--privileged --detach --interactive --tty --rm '\
762 '--cgroup-parent docker {cpuset_cpus} {cpuset_mems} {publish} '\
763 '{env} {volume} --name {container.name} {container.image} '\
764 '{command}'.format(cpuset_cpus=cpuset_cpus, cpuset_mems=cpuset_mems,
765 container=self.container, command=command,
766 env=env, publish=publish, volume=volume)
768 ret, _, _ = self.container.ssh.exec_command_sudo(cmd)
770 raise RuntimeError('Failed to create container {c.name}'
771 .format(c=self.container))
775 def execute(self, command):
776 """Start a process inside a running container.
778 Runs the specified command inside the container specified by name. The
779 container has to be running already.
781 :param command: Command to run inside container.
783 :raises RuntimeError: If runnig the command in a container failed.
785 cmd = "docker exec --interactive {c.name} /bin/sh -c '{command}; "\
786 "exit $?'".format(c=self.container, command=command)
788 ret, _, _ = self.container.ssh.exec_command_sudo(cmd, timeout=180)
790 raise RuntimeError('Failed to execute command in container '
791 '{c.name}.'.format(c=self.container))
794 """Stop running container.
796 :raises RuntimeError: If stopping a container failed.
798 cmd = 'docker stop {c.name}'.format(c=self.container)
800 ret, _, _ = self.container.ssh.exec_command_sudo(cmd)
802 raise RuntimeError('Failed to stop container {c.name}.'
803 .format(c=self.container))
806 """Remove a container.
808 :raises RuntimeError: If removing a container failed.
810 cmd = 'docker rm --force {c.name}'.format(c=self.container)
812 ret, _, _ = self.container.ssh.exec_command_sudo(cmd)
814 raise RuntimeError('Failed to destroy container {c.name}.'
815 .format(c=self.container))
818 """Return low-level information on Docker objects.
820 :raises RuntimeError: If getting info about a container failed.
822 cmd = 'docker inspect {c.name}'.format(c=self.container)
824 ret, _, _ = self.container.ssh.exec_command_sudo(cmd)
826 raise RuntimeError('Failed to get info about container {c.name}.'
827 .format(c=self.container))
829 def system_info(self):
830 """Display the docker system-wide information.
832 :raises RuntimeError: If displaying system information failed.
834 cmd = 'docker system info'
836 ret, _, _ = self.container.ssh.exec_command_sudo(cmd)
838 raise RuntimeError('Failed to get system info.')
840 def is_container_present(self):
841 """Check if container is present on node.
843 :returns: True if container is present.
845 :raises RuntimeError: If getting info about a container failed.
847 cmd = 'docker ps --all --quiet --filter name={c.name}'\
848 .format(c=self.container)
850 ret, stdout, _ = self.container.ssh.exec_command_sudo(cmd)
852 raise RuntimeError('Failed to get info about container {c.name}.'
853 .format(c=self.container))
854 return True if stdout else False
856 def is_container_running(self):
857 """Check if container is running on node.
859 :returns: True if container is running.
861 :raises RuntimeError: If getting info about a container failed.
863 cmd = 'docker ps --quiet --filter name={c.name}'\
864 .format(c=self.container)
866 ret, stdout, _ = self.container.ssh.exec_command_sudo(cmd)
868 raise RuntimeError('Failed to get info about container {c.name}.'
869 .format(c=self.container))
870 return True if stdout else False
873 class Container(object):
874 """Container class."""
877 """Initialize Container object."""
880 def __getattr__(self, attr):
881 """Get attribute custom implementation.
883 :param attr: Attribute to get.
885 :returns: Attribute value or None.
889 return self.__dict__[attr]
893 def __setattr__(self, attr, value):
894 """Set attribute custom implementation.
896 :param attr: Attribute to set.
897 :param value: Value to set.
902 # Check if attribute exists
905 # Creating new attribute
907 self.__dict__['ssh'] = SSH()
908 self.__dict__['ssh'].connect(value)
909 self.__dict__[attr] = value
911 # Updating attribute base of type
912 if isinstance(self.__dict__[attr], list):
913 self.__dict__[attr].append(value)
915 self.__dict__[attr] = value