Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Policy|Plugin] Provide a Container Runtime Abstraction #1873

Closed
102 changes: 97 additions & 5 deletions sos/plugins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1309,28 +1309,120 @@ def collect_cmd_output(self, cmd, suggest_filename=None,

def exec_cmd(self, cmd, timeout=cmd_timeout, stderr=True, chroot=True,
runat=None, env=None, binary=False, pred=None,
foreground=False):
foreground=False, container=False):
"""Execute a command right now and return the output and status, but
do not save the output within the archive.

Use this method in a plugin's setup() if command output is needed to
build subsequent commands added to a report via add_cmd_output().
"""
_default = {'status': None, 'output': ''}
if not self.test_predicate(cmd=True, pred=pred):
return {
'status': None,
'output': ''
}
return _default

if chroot or self.commons['cmdlineopts'].chroot == 'always':
root = self.sysroot
else:
root = None

if container:
if self._get_container_runtime() is None:
self._log_info("Cannot run cmd '%s' in container %s: no "
"runtime detected on host." % (cmd, container))
return _default
if self.container_exists(container):
cmd = self.fmt_container_cmd(container, cmd)
else:
self._log_info("Cannot run cmd '%s' in container %s: no such "
"container is running." % (cmd, container))

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall not we call return _default here? (well, current behaviour will call docker/podman cmd on non-existing container that works "well" also).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm.. it might be valuable to let execute that command and get something like:

Error: error getting image "3848ec06ffb6": unable to find a name and tag match for 3848ec06ffb6 in repotags: no such image

No preferences from my side here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, fair point on executing it anyway and collecting the failure. I'll think this over a bit more.

return sos_get_command_output(cmd, timeout=timeout, chroot=root,
chdir=runat, binary=binary, env=env,
foreground=foreground)

def _get_container_runtime(self, runtime=None):
"""Based on policy and request by the plugin, return a usable
ContainerRuntime if one exists
"""
if runtime is None:
if 'default' in self.policy.runtimes.keys():
return self.policy.runtimes['default']
else:
for pol_runtime in list(self.policy.runtimes.keys()):
if runtime == pol_runtime:
return self.policy.runtimes[pol_runtime]
return None

def container_exists(self, name):
"""If a container runtime is present, check to see if a container with
a given name is currently running
"""
_runtime = self._get_container_runtime()
if _runtime is not None:
con = _runtime.get_container_by_name(name)
return con is not None
return False

def get_container_by_name(self, name):
_runtime = self._get_container_runtime()
if _runtime is not None:
return _runtime.get_container_by_name(name)
return None

def get_containers(self, runtime=None, get_all=False):
"""Return a list of all container IDs from the Policy's
ContainerRuntime.

If `runtime` is not provided, use the Policy default. If the specified
`runtime is not loaded, return empty.
"""
_runtime = self._get_container_runtime(runtime=runtime)
if _runtime is not None:
if get_all:
return _runtime.get_containers(get_all=True)
else:
return _runtime.containers
return []

def get_container_images(self, runtime=None):
"""Return a list of all image names from the Policy's
ContainerRuntime

If `runtime` is not provided, use the Policy default. If the specified
`runtime is not loaded, return empty.
"""
_runtime = self._get_container_runtime(runtime=runtime)
if _runtime is not None:
return _runtime.images
return []

def get_container_volumes(self, runtime=None):
"""Return a list of all volume names from the Policy's
ContainerRuntime

If `runtime` is not provided, use the Policy default. If the specified
`runtime is not loaded, return empty.
"""
_runtime = self._get_container_runtime(runtime=runtime)
if _runtime is not None:
return _runtime.volumes
return []

def get_container_logs(self, container, **kwargs):
"""Helper to get the `logs` output for a given container

Supports passthru of add_cmd_output() options
"""
_runtime = self._get_container_runtime()
if _runtime is not None:
self.add_cmd_output(_runtime.get_logs_command(container), **kwargs)

def fmt_container_cmd(self, container, cmd):
if self.container_exists(container):
_runtime = self._get_container_runtime()
return _runtime.fmt_container_cmd(container, cmd)
return cmd

def is_module_loaded(self, module_name):
"""Return whether specified module as module_name is loaded or not"""
return module_name in self.policy.kernel_mods
Expand Down
27 changes: 7 additions & 20 deletions sos/plugins/docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,17 +73,12 @@ def setup(self):
for net in n:
self.add_cmd_output("docker network inspect %s" % net)

ps_cmd = 'docker ps -q'
if self.get_option('all'):
ps_cmd = "%s -a" % ps_cmd

fmt = '{{lower .Repository}}:{{lower .Tag}} {{lower .ID}}'
img_cmd = "docker images --format='%s'" % fmt
vol_cmd = 'docker volume ls -q'

containers = self._get_docker_list(ps_cmd)
images = self._get_docker_list(img_cmd)
volumes = self._get_docker_list(vol_cmd)
containers = [
c[0] for c in self.get_containers(runtime='docker',
get_all=self.get_option('all'))
]
images = self.get_container_images(runtime='docker')
volumes = self.get_container_volumes(runtime='docker')

for container in containers:
self.add_cmd_output("docker inspect %s" % container,
Expand All @@ -93,22 +88,14 @@ def setup(self):
subdir='containers')

for img in images:
name, img_id = img.strip().split()
name, img_id = img
insp = name if 'none' not in name else img_id
self.add_cmd_output("docker inspect %s" % insp, subdir='images')

for vol in volumes:
self.add_cmd_output("docker volume inspect %s" % vol,
subdir='volumes')

def _get_docker_list(self, cmd):
ret = []
result = self.exec_cmd(cmd)
if result['status'] == 0:
for ent in result['output'].splitlines():
ret.append(ent)
return ret

def postproc(self):
# Attempts to match key=value pairs inside container inspect output
# for potentially sensitive items like env vars that contain passwords.
Expand Down
11 changes: 1 addition & 10 deletions sos/plugins/openstack_cinder.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def setup(self):
if in_ps:
break

in_container = self.running_in_container()
in_container = self.container_exists('cinder_api')
if in_container:
cinder_config = cinder_config_opt % self.var_puppet_gen

Expand Down Expand Up @@ -69,15 +69,6 @@ def setup(self):
"/var/log/httpd/cinder*.log",
])

def running_in_container(self):
for runtime in ["docker", "podman"]:
container_status = self.exec_cmd(runtime + " ps")
if container_status['status'] == 0:
for line in container_status['output'].splitlines():
if line.endswith("cinder_api"):
return True
return False

def apply_regex_sub(self, regexp, subst):
self.do_path_regex_sub("/etc/cinder/*", regexp, subst)
self.do_path_regex_sub(
Expand Down
11 changes: 1 addition & 10 deletions sos/plugins/openstack_glance.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def setup(self):

# collect commands output only if the openstack-glance-api service
# is running
in_container = self.running_in_container()
in_container = self.container_exists('glance_api')

if self.is_service_running('openstack-glance-api') or in_container:
glance_config = ""
Expand Down Expand Up @@ -70,15 +70,6 @@ def setup(self):
else:
self.add_cmd_output("openstack image list --long")

def running_in_container(self):
for runtime in ["docker", "podman"]:
container_status = self.exec_cmd(runtime + " ps")
if container_status['status'] == 0:
for line in container_status['output'].splitlines():
if line.endswith("glance_api"):
return True
return False

def apply_regex_sub(self, regexp, subst):
self.do_path_regex_sub("/etc/glance/*", regexp, subst)
self.do_path_regex_sub(
Expand Down
11 changes: 1 addition & 10 deletions sos/plugins/openstack_heat.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def setup(self):

# collect commands output only if the openstack-heat-api service
# is running
in_container = self.running_in_container()
in_container = self.container_exists('heat_api')

if self.is_service_running('openstack-heat-api') or in_container:
heat_config = ""
Expand Down Expand Up @@ -79,15 +79,6 @@ def setup(self):
self.var_puppet_gen + "_api_cfn/var/spool/cron/heat",
])

def running_in_container(self):
for runtime in ["docker", "podman"]:
container_status = self.exec_cmd(runtime + " ps")
if container_status['status'] == 0:
for line in container_status['output'].splitlines():
if line.endswith("heat_api"):
return True
return False

def apply_regex_sub(self, regexp, subst):
self.do_path_regex_sub(
"/etc/heat/*",
Expand Down
11 changes: 1 addition & 10 deletions sos/plugins/openstack_manila.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class OpenStackManila(Plugin):
def setup(self):

config_dir = "%s/etc/manila" % (
self.var_puppet_gen if self.running_in_container() else ''
self.var_puppet_gen if self.container_exists('manila_api') else ''
)
manila_cmd = "manila-manage --config-dir %s db version" % config_dir
self.add_cmd_output(manila_cmd, suggest_filename="manila_db_version")
Expand All @@ -46,15 +46,6 @@ def setup(self):
"/var/log/manila/*.log",
])

def running_in_container(self):
for runtime in ["docker", "podman"]:
container_status = self.exec_cmd(runtime + " ps")
if container_status['status'] == 0:
for line in container_status['output'].splitlines():
if line.endswith("manila_api"):
return True
return False

def apply_regex_sub(self, regexp, subst):
self.do_path_regex_sub("/etc/manila/*", regexp, subst)
self.do_path_regex_sub(
Expand Down
11 changes: 1 addition & 10 deletions sos/plugins/openstack_placement.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def setup(self):
# collect commands output only if the openstack-placement-api service
# is running

in_container = self.running_in_container()
in_container = self.container_exists('placement_api')

if self.is_service_running('openstack-placement-api') or in_container:
placement_config = ""
Expand Down Expand Up @@ -59,15 +59,6 @@ def setup(self):
self.var_puppet_gen + "/etc/httpd/conf.modules.d/*.conf",
])

def running_in_container(self):
for runtime in ["docker", "podman"]:
container_status = self.exec_cmd(runtime + " ps")
if container_status['status'] == 0:
for line in container_status['output'].splitlines():
if line.endswith("placement_api"):
return True
return False

def apply_regex_sub(self, regexp, subst):
self.do_path_regex_sub("/etc/placement/*", regexp, subst)
self.do_path_regex_sub(
Expand Down
30 changes: 8 additions & 22 deletions sos/plugins/ovn_central.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,12 @@ class OVNCentral(Plugin):
"""
plugin_name = "ovn_central"
profiles = ('network', 'virt')
_container_runtime = None
_container_name = None

def get_tables_from_schema(self, filename, skip=[]):
if self._container_name:
cmd = "%s exec %s cat %s" % (
self._container_runtime, self._container_name, filename)
res = self.exec_cmd(cmd, foreground=True)
cmd = "cat %s" % filename
res = self.exec_cmd(cmd, timeout=None, foreground=True,
container=self._container_name)
if res['status'] != 0:
self._log_error("Could not retrieve DB schema file from "
"container %s" % self._container_name)
Expand Down Expand Up @@ -61,23 +59,12 @@ def add_database_output(self, tables, cmds, ovn_cmd):
for table in tables:
cmds.append('%s list %s' % (ovn_cmd, table))

def running_in_container(self):
for runtime in ["podman", "docker"]:
container_status = self.exec_cmd(runtime + " ps")
if container_status['status'] == 0:
for line in container_status['output'].splitlines():
if "ovn-dbs-bundle" in line:
self._container_name = line.split()[-1]
self._container_runtime = runtime
return True
return False

def check_enabled(self):
return (self.running_in_container() or
return (self.container_exists('ovs-dbs-bundle.*') or
super(OVNCentral, self).check_enabled())

def setup(self):
containerized = self.running_in_container()
self._container_name = self.get_container_by_name('ovs-dbs-bundle.*')

ovs_rundir = os.environ.get('OVS_RUNDIR')
for pidfile in ['ovnnb_db.pid', 'ovnsb_db.pid', 'ovn-northd.pid']:
Expand Down Expand Up @@ -113,10 +100,9 @@ def setup(self):

# If OVN is containerized, we need to run the above commands inside
# the container.
if containerized:
cmds = ['%s exec %s %s' % (self._container_runtime,
self._container_name,
cmd) for cmd in cmds]
cmds = [
self.fmt_container_cmd(self._container_name, cmd) for cmd in cmds
]

self.add_cmd_output(cmds, foreground=True)

Expand Down
Loading