From 510f0913539e92d2e874ae97efe0606fd277ad4b Mon Sep 17 00:00:00 2001 From: Bogdan Dobrelya Date: Mon, 3 Dec 2018 16:23:32 +0100 Subject: [PATCH] Implement podman rename via re-apply of containers To w/a the missing a container rename feature of podman, implement renaming via removing of the original container and re-applying it from the same configs but using the new name. This fixes idempotency issues when service containers are executed under ephemeral names created via the paunch's unique containers names generator, while it is expected to have them executed under its wanted config names. Change-Id: If851604d25b6c7982d950bb9e13dceada3bfc161 Closes-Bug: #1805410 Signed-off-by: Bogdan Dobrelya --- paunch/runner.py | 78 ++++++++++++++++++++++++++++++++++++++++++++++-- requirements.txt | 1 + 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/paunch/runner.py b/paunch/runner.py index d0f0765..1597fdb 100644 --- a/paunch/runner.py +++ b/paunch/runner.py @@ -12,11 +12,13 @@ # import collections +import jmespath import json import random import string import subprocess +from paunch.builder import podman from paunch.utils import common from paunch.utils import systemd @@ -157,6 +159,28 @@ class BaseRunner(object): conf_id) self.remove_containers(conf_id) + def discover_container_config(self, configs, container, name): + '''Find the paunch and runtime configs of a container by name.''' + for conf_id in self.current_config_ids(): + jquerry = ("[] | [?(Name=='%s' && " + "Config.Labels.container_name=='%s' && " + "Config.Labels.config_id=='%s')]" % + (container, name, conf_id)) + runtime_conf = None + try: + runtime_conf = jmespath.search(jquerry, + configs[conf_id])[0] + result = (conf_id, runtime_conf) + except Exception: + self.log.error("Failed searching container %s " + "for config %s" % (container, conf_id)) + result = (None, None) + if runtime_conf: + self.log.debug("Found container %s " + "for config %s" % (container, conf_id)) + break + return result + def list_configs(self): configs = collections.defaultdict(list) for conf_id in self.current_config_ids(): @@ -259,6 +283,54 @@ class PodmanRunner(BaseRunner): def rename_container(self, container, name): # TODO(emilien) podman doesn't support rename, we'll handle it - # in paunch itself, probably. - self.log.warning("container renaming isn't supported by podman") - pass + # in paunch itself for now + configs = self.list_configs() + config_id, config = self.discover_container_config( + configs, container, name) + # Get config_data dict by the discovered conf ID, + # paunch needs it for maintaining idempotency within a conf ID + filter_names = ("[] | [?(Name!='%s' && " + "Config.Labels.config_id=='%s')]" + ".Name" % (container, config_id)) + filter_cdata = ("[] | [?(Name!='%s' && " + "Config.Labels.config_id=='%s')]" + ".Config.Labels.config_data" % (container, config_id)) + names = None + cdata = None + try: + names = jmespath.search(filter_names, configs[config_id]) + cdata = jmespath.search(filter_cdata, configs[config_id]) + except jmespath.exceptions.LexerError: + self.log.error("Failed to rename a container %s into %s: " + "used a bad search pattern" % (container, name)) + return + + if not names or not cdata: + self.log.error("Failed to rename a container %s into %s: " + "no config_data was found" % (container, name)) + return + + # Rename the wanted container in the config_data fetched from the + # discovered config + config_data = dict(zip(names, map(json.loads, cdata))) + config_data[name] = json.loads( + config.get('Config').get('Labels').get('config_data')) + + # Re-apply a container under its amended name using the fetched configs + self.log.debug("Renaming a container known as %s into %s, " + "via re-applying its original config" % + (container, name)) + self.log.debug("Removing the destination container %s" % name) + self.stop_container(name) + self.remove_container(name) + self.log.debug("Removing a container known as %s" % container) + self.stop_container(container) + self.remove_container(container) + builder = podman.PodmanBuilder( + config_id=config_id, + config=config_data, + runner=self, + labels=None, + log=self.log + ) + builder.apply() diff --git a/requirements.txt b/requirements.txt index 44a9f90..fd8f739 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,4 @@ pbr>=2.0.0,!=2.1.0 # Apache-2.0 cliff>=2.6.0 # Apache-2.0 tenacity>=3.2.1 # Apache-2.0 +jmespath>=0.9.0 # MIT