aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJake Hunsaker <jhunsake@redhat.com>2019-11-21 14:17:21 -0500
committerJake Hunsaker <jhunsake@redhat.com>2020-04-07 16:43:35 -0400
commit49b1e05911c0095cb8a89e1ad2072d2a06054865 (patch)
tree8e10676e218e878437fb862f13123655f3f85942
parent6ada914302004f82cd8ca1876bb97c02bc97a0ef (diff)
downloadsos-49b1e05911c0095cb8a89e1ad2072d2a06054865.tar.gz
[Policy] Add Container Runtime abstraction class
Adds a `ContainerRuntime()` class to allow policies to specify a container runtime to allow plugins to utilize. The `ContainerRuntime` is intended to allow for the discovery of containers, including specific ones by name, and for the execution of commands inside those containers. This is meant to remove the overhead of manually defining ways to determine an active runtime and if a component is containerized within plugins. Related: #1866 Signed-off-by: Jake Hunsaker <jhunsake@redhat.com>
-rw-r--r--sos/policies/__init__.py150
-rw-r--r--sos/policies/redhat.py1
-rw-r--r--tests/plugin_tests.py102
3 files changed, 218 insertions, 35 deletions
diff --git a/sos/policies/__init__.py b/sos/policies/__init__.py
index ed3f0cc1..13211bfa 100644
--- a/sos/policies/__init__.py
+++ b/sos/policies/__init__.py
@@ -14,6 +14,7 @@ from getpass import getpass
from pwd import getpwuid
from sos.utilities import (ImporterHelper,
import_module,
+ is_executable,
shell_out,
sos_get_command_output)
from sos.plugins import IndependentPlugin, ExperimentalPlugin
@@ -22,6 +23,7 @@ from sos import SoSOptions, _arg_names
from textwrap import fill
from six import print_
from six.moves import input
+from pipes import quote
PRESETS_PATH = "/var/lib/sos/presets"
@@ -67,6 +69,126 @@ def load(cache={}, sysroot=None):
return cache['policy']
+class ContainerRuntime(object):
+ """Encapsulates a container runtime that provides the ability to plugins to
+ check runtime status, check for the presence of specific containers, and
+ to format commands to run in those containers
+ """
+
+ name = 'Undefined'
+ containers = []
+ images = []
+ volumes = []
+ binary = ''
+ active = False
+
+ def __init__(self, policy=None):
+ self.policy = policy
+ self.run_cmd = "%s exec " % self.binary
+
+ def load_container_info(self):
+ """If this runtime is found to be active, attempt to load information
+ on the objects existing in the runtime.
+ """
+ self.containers = self.get_containers()
+ self.images = self.get_images()
+ self.volumes = self.get_volumes()
+
+ def check_is_active(self):
+ """Check to see if the container runtime is both present AND active.
+
+ Active in this sense means that the runtime can be used to glean
+ information about the runtime itself and containers that are running.
+ """
+ if is_executable(self.binary):
+ self.active = True
+ return True
+ return False
+
+ def get_containers(self, get_all=False):
+ """Get a list of containers present on the system.
+
+ If `get_all` is `True`, also include non-running containers
+ """
+ containers = []
+ _cmd = "%s ps %s" % (self.binary, '-a' if get_all else '')
+ if self.active:
+ out = sos_get_command_output(_cmd)
+ if out['status'] == 0:
+ for ent in out['output'].splitlines()[1:]:
+ ent = ent.split()
+ # takes the form (container_id, container_name)
+ containers.append((ent[0], ent[-1]))
+ return containers
+
+ def get_container_by_name(self, name):
+ """Get the container ID for the container matching the provided
+ name
+ """
+ if not self.active or name is None:
+ return None
+ for c in self.containers:
+ if re.match(name, c[1]):
+ return c[1]
+ return None
+
+ def get_images(self):
+ """Get a list of images present on the system
+ """
+ images = []
+ fmt = '{{lower .Repository}}:{{lower .Tag}} {{lower .ID}}'
+ if self.active:
+ out = sos_get_command_output("%s images --format '%s'"
+ % (self.binary, fmt))
+ if out['status'] == 0:
+ for ent in out['output'].splitlines():
+ ent = ent.split()
+ # takes the form (image_name, image_id)
+ images.append((ent[0], ent[1]))
+ return images
+
+ def get_volumes(self):
+ """Get a list of container volumes present on the system
+ """
+ vols = []
+ if self.active:
+ out = sos_get_command_output("%s volume ls" % self.binary)
+ if out['status'] == 0:
+ for ent in out['output'].splitlines()[1:]:
+ ent = ent.split()
+ vols.append(ent[-1])
+ return vols
+
+ def fmt_container_cmd(self, container, cmd):
+ return "%s %s %s" % (self.run_cmd, container, quote(cmd))
+
+ def get_logs_command(self, container):
+ """Return the command string used to dump container logs from the
+ runtime
+ """
+ return "%s logs -t %s" % (self.binary, container)
+
+
+class DockerContainerRuntime(ContainerRuntime):
+
+ name = 'docker'
+ binary = 'docker'
+
+ def check_is_active(self):
+ # the daemon must be running
+ if (is_executable('docker') and
+ self.policy.init_system.is_running('docker')):
+ self.active = True
+ return True
+ return False
+
+
+class PodmanContainerRuntime(ContainerRuntime):
+
+ name = 'podman'
+ binary = 'podman'
+
+
class InitSystem(object):
"""Encapsulates an init system to provide service-oriented functions to
sos.
@@ -464,7 +586,7 @@ any third party.
_in_container = False
_host_sysroot = '/'
- def __init__(self, sysroot=None):
+ def __init__(self, sysroot=None, probe_runtime=True):
"""Subclasses that choose to override this initializer should call
super() to ensure that they get the required platform bits attached.
super(SubClass, self).__init__(). Policies that require runtime
@@ -472,6 +594,7 @@ any third party.
modifying PATH in their own initializer."""
self._parse_uname()
self.case_id = None
+ self.probe_runtime = probe_runtime
self.package_manager = PackageManager()
self._valid_subclasses = []
self.set_exec_path()
@@ -861,13 +984,15 @@ class LinuxPolicy(Policy):
_upload_user = None
_upload_password = None
_use_https_streaming = False
+ default_container_runtime = 'docker'
_preferred_hash_name = None
upload_url = None
upload_user = None
upload_password = None
- def __init__(self, sysroot=None, init=None):
- super(LinuxPolicy, self).__init__(sysroot=sysroot)
+ def __init__(self, sysroot=None, init=None, probe_runtime=True):
+ super(LinuxPolicy, self).__init__(sysroot=sysroot,
+ probe_runtime=probe_runtime)
self.init_kernel_modules()
if init is not None:
@@ -877,6 +1002,25 @@ class LinuxPolicy(Policy):
else:
self.init_system = InitSystem()
+ self.runtimes = {}
+ if self.probe_runtime:
+ _crun = [
+ PodmanContainerRuntime(policy=self),
+ DockerContainerRuntime(policy=self)
+ ]
+ for runtime in _crun:
+ if runtime.check_is_active():
+ self.runtimes[runtime.name] = runtime
+ if runtime.name == self.default_container_runtime:
+ self.runtimes['default'] = self.runtimes[runtime.name]
+ self.runtimes[runtime.name].load_container_info()
+
+ if self.runtimes and 'default' not in self.runtimes.keys():
+ # still allow plugins to query a runtime present on the system
+ # even if that is not the policy default one
+ idx = list(self.runtimes.keys())
+ self.runtimes['default'] = self.runtimes[idx[0]]
+
def get_preferred_hash_name(self):
if self._preferred_hash_name:
diff --git a/sos/policies/redhat.py b/sos/policies/redhat.py
index 9fbe7431..eb5b09d8 100644
--- a/sos/policies/redhat.py
+++ b/sos/policies/redhat.py
@@ -47,6 +47,7 @@ class RedHatPolicy(LinuxPolicy):
upload_url = 'dropbox.redhat.com'
upload_user = 'anonymous'
upload_directory = '/incoming'
+ default_container_runtime = 'podman'
def __init__(self, sysroot=None):
super(RedHatPolicy, self).__init__(sysroot=sysroot)
diff --git a/tests/plugin_tests.py b/tests/plugin_tests.py
index fd0d0f14..72ec895b 100644
--- a/tests/plugin_tests.py
+++ b/tests/plugin_tests.py
@@ -158,60 +158,86 @@ class PluginTests(unittest.TestCase):
def setUp(self):
self.mp = MockPlugin({
- 'cmdlineopts': MockOptions(),
- 'policy': LinuxPolicy(init=InitSystem()),
'sysroot': self.sysroot,
+ 'policy': LinuxPolicy(init=InitSystem(), probe_runtime=False),
'cmdlineopts': MockOptions()
})
self.mp.archive = MockArchive()
def test_plugin_default_name(self):
- p = MockPlugin({'sysroot': self.sysroot, 'policy': LinuxPolicy(init=InitSystem()),
- 'cmdlineopts': MockOptions()})
+ p = MockPlugin({
+ 'sysroot': self.sysroot,
+ 'policy': LinuxPolicy(init=InitSystem(), probe_runtime=False),
+ 'cmdlineopts': MockOptions()
+ })
self.assertEquals(p.name(), "mockplugin")
def test_plugin_set_name(self):
- p = NamedMockPlugin({'sysroot': self.sysroot, 'policy': LinuxPolicy(init=InitSystem()),
- 'cmdlineopts': MockOptions()})
+ p = NamedMockPlugin({
+ 'sysroot': self.sysroot,
+ 'policy': LinuxPolicy(init=InitSystem(), probe_runtime=False),
+ 'cmdlineopts': MockOptions()
+ })
self.assertEquals(p.name(), "testing")
def test_plugin_no_descrip(self):
- p = MockPlugin({'sysroot': self.sysroot, 'policy': LinuxPolicy(init=InitSystem()),
- 'cmdlineopts': MockOptions()})
+ p = MockPlugin({
+ 'sysroot': self.sysroot,
+ 'policy': LinuxPolicy(init=InitSystem(), probe_runtime=False),
+ 'cmdlineopts': MockOptions()
+ })
self.assertEquals(p.get_description(), "<no description available>")
def test_plugin_no_descrip(self):
- p = NamedMockPlugin({'sysroot': self.sysroot, 'policy': LinuxPolicy(init=InitSystem()),
- 'cmdlineopts': MockOptions()})
+ p = NamedMockPlugin({
+ 'sysroot': self.sysroot,
+ 'policy': LinuxPolicy(init=InitSystem(), probe_runtime=False),
+ 'cmdlineopts': MockOptions()
+ })
self.assertEquals(p.get_description(), "This plugin has a description.")
def test_set_plugin_option(self):
- p = MockPlugin({'sysroot': self.sysroot, 'policy': LinuxPolicy(init=InitSystem()),
- 'cmdlineopts': MockOptions()})
+ p = MockPlugin({
+ 'sysroot': self.sysroot,
+ 'policy': LinuxPolicy(init=InitSystem(), probe_runtime=False),
+ 'cmdlineopts': MockOptions()
+ })
p.set_option("opt", "testing")
self.assertEquals(p.get_option("opt"), "testing")
def test_set_nonexistant_plugin_option(self):
- p = MockPlugin({'sysroot': self.sysroot, 'policy': LinuxPolicy(init=InitSystem()),
- 'cmdlineopts': MockOptions()})
+ p = MockPlugin({
+ 'sysroot': self.sysroot,
+ 'policy': LinuxPolicy(init=InitSystem(), probe_runtime=False),
+ 'cmdlineopts': MockOptions()
+ })
self.assertFalse(p.set_option("badopt", "testing"))
def test_get_nonexistant_plugin_option(self):
- p = MockPlugin({'sysroot': self.sysroot, 'policy': LinuxPolicy(init=InitSystem()),
- 'cmdlineopts': MockOptions()})
+ p = MockPlugin({
+ 'sysroot': self.sysroot,
+ 'policy': LinuxPolicy(init=InitSystem(), probe_runtime=False),
+ 'cmdlineopts': MockOptions()
+ })
self.assertEquals(p.get_option("badopt"), 0)
def test_get_unset_plugin_option(self):
- p = MockPlugin({'sysroot': self.sysroot, 'policy': LinuxPolicy(init=InitSystem()),
- 'cmdlineopts': MockOptions()})
+ p = MockPlugin({
+ 'sysroot': self.sysroot,
+ 'policy': LinuxPolicy(init=InitSystem(), probe_runtime=False),
+ 'cmdlineopts': MockOptions()
+ })
self.assertEquals(p.get_option("opt"), 0)
def test_get_unset_plugin_option_with_default(self):
# this shows that even when we pass in a default to get,
# we'll get the option's default as set in the plugin
# this might not be what we really want
- p = MockPlugin({'sysroot': self.sysroot, 'policy': LinuxPolicy(init=InitSystem()),
- 'cmdlineopts': MockOptions()})
+ p = MockPlugin({
+ 'sysroot': self.sysroot,
+ 'policy': LinuxPolicy(init=InitSystem(), probe_runtime=False),
+ 'cmdlineopts': MockOptions()
+ })
self.assertEquals(p.get_option("opt", True), True)
def test_get_unset_plugin_option_with_default_not_none(self):
@@ -219,24 +245,36 @@ class PluginTests(unittest.TestCase):
# if the plugin default is not None
# we'll get the option's default as set in the plugin
# this might not be what we really want
- p = MockPlugin({'sysroot': self.sysroot, 'policy': LinuxPolicy(init=InitSystem()),
- 'cmdlineopts': MockOptions()})
+ p = MockPlugin({
+ 'sysroot': self.sysroot,
+ 'policy': LinuxPolicy(init=InitSystem(), probe_runtime=False),
+ 'cmdlineopts': MockOptions()
+ })
self.assertEquals(p.get_option("opt2", True), False)
def test_get_option_as_list_plugin_option(self):
- p = MockPlugin({'sysroot': self.sysroot, 'policy': LinuxPolicy(init=InitSystem()),
- 'cmdlineopts': MockOptions()})
+ p = MockPlugin({
+ 'sysroot': self.sysroot,
+ 'policy': LinuxPolicy(init=InitSystem(), probe_runtime=False),
+ 'cmdlineopts': MockOptions()
+ })
p.set_option("opt", "one,two,three")
self.assertEquals(p.get_option_as_list("opt"), ['one', 'two', 'three'])
def test_get_option_as_list_plugin_option_default(self):
- p = MockPlugin({'sysroot': self.sysroot, 'policy': LinuxPolicy(init=InitSystem()),
- 'cmdlineopts': MockOptions()})
+ p = MockPlugin({
+ 'sysroot': self.sysroot,
+ 'policy': LinuxPolicy(init=InitSystem(), probe_runtime=False),
+ 'cmdlineopts': MockOptions()
+ })
self.assertEquals(p.get_option_as_list("opt", default=[]), [])
def test_get_option_as_list_plugin_option_not_list(self):
- p = MockPlugin({'sysroot': self.sysroot, 'policy': LinuxPolicy(init=InitSystem()),
- 'cmdlineopts': MockOptions()})
+ p = MockPlugin({
+ 'sysroot': self.sysroot,
+ 'policy': LinuxPolicy(init=InitSystem(), probe_runtime=False),
+ 'cmdlineopts': MockOptions()
+ })
p.set_option("opt", "testing")
self.assertEquals(p.get_option_as_list("opt"), ['testing'])
@@ -252,7 +290,7 @@ class PluginTests(unittest.TestCase):
p = ForbiddenMockPlugin({
'cmdlineopts': MockOptions(),
'sysroot': self.sysroot,
- 'policy': LinuxPolicy(init=InitSystem())
+ 'policy': LinuxPolicy(init=InitSystem(), probe_runtime=False)
})
p.archive = MockArchive()
p.setup()
@@ -276,7 +314,7 @@ class AddCopySpecTests(unittest.TestCase):
def setUp(self):
self.mp = MockPlugin({
'cmdlineopts': MockOptions(),
- 'policy': LinuxPolicy(init=InitSystem()),
+ 'policy': LinuxPolicy(init=InitSystem(), probe_runtime=False),
'sysroot': os.getcwd(),
'cmdlineopts': MockOptions()
})
@@ -353,7 +391,7 @@ class CheckEnabledTests(unittest.TestCase):
def setUp(self):
self.mp = EnablerPlugin({
- 'policy': sos.policies.load(),
+ 'policy': LinuxPolicy(probe_runtime=False),
'sysroot': os.getcwd(),
'cmdlineopts': MockOptions()
})
@@ -382,7 +420,7 @@ class RegexSubTests(unittest.TestCase):
def setUp(self):
self.mp = MockPlugin({
'cmdlineopts': MockOptions(),
- 'policy': LinuxPolicy(init=InitSystem()),
+ 'policy': LinuxPolicy(init=InitSystem(), probe_runtime=False),
'sysroot': os.getcwd()
})
self.mp.archive = MockArchive()