]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/cephadm: convert tags to repo_digest
authorSebastian Wagner <sebastian.wagner@suse.com>
Mon, 3 Aug 2020 12:36:56 +0000 (14:36 +0200)
committerNathan Cutler <ncutler@suse.com>
Tue, 6 Oct 2020 09:40:52 +0000 (11:40 +0200)
Signed-off-by: Sebastian Wagner <sebastian.wagner@suse.com>
(cherry picked from commit 19e23bef288de590d47bb4147603b422aa96ef06)

src/pybind/mgr/cephadm/module.py
src/pybind/mgr/cephadm/tests/test_cephadm.py
src/pybind/mgr/cephadm/upgrade.py
src/pybind/mgr/cephadm/utils.py

index ee900791ef4d02cf37f23a187af77dba7b8ed227..8808d83aae16f0f8ab894e92c435db8bdeb9ed8e 100644 (file)
@@ -47,7 +47,7 @@ from .schedule import HostAssignment, HostPlacementSpec
 from .inventory import Inventory, SpecStore, HostCache, EventStore
 from .upgrade import CEPH_UPGRADE_ORDER, CephadmUpgrade
 from .template import TemplateMgr
-from .utils import forall_hosts, CephadmNoImage, cephadmNoImage
+from .utils import forall_hosts, CephadmNoImage, cephadmNoImage, is_repo_digest
 
 try:
     import remoto
@@ -257,6 +257,12 @@ class CephadmOrchestrator(orchestrator.Orchestrator, MgrModule):
             'default': None,
             'desc': 'Custom repository password'
         },
+        {
+            'name': 'use_repo_digest',
+            'type': 'bool',
+            'default': False,
+            'desc': 'Automatically convert image tags to image digest. Make sure all daemons use the same image',
+        }
     ]
 
     def __init__(self, *args, **kwargs):
@@ -296,6 +302,7 @@ class CephadmOrchestrator(orchestrator.Orchestrator, MgrModule):
             self.registry_url: Optional[str] = None
             self.registry_username: Optional[str] = None
             self.registry_password: Optional[str] = None
+            self.use_repo_digest = False
 
         self._cons: Dict[str, Tuple[remoto.backends.BaseConnection,
                                     remoto.backends.LegacyModuleExecute]] = {}
@@ -496,6 +503,8 @@ class CephadmOrchestrator(orchestrator.Orchestrator, MgrModule):
 
             try:
 
+                self.convert_tags_to_repo_digest()
+
                 # refresh daemons
                 self.log.debug('refreshing hosts and daemons')
                 self._refresh_hosts_and_daemons()
@@ -526,6 +535,24 @@ class CephadmOrchestrator(orchestrator.Orchestrator, MgrModule):
             self._serve_sleep()
         self.log.debug("serve exit")
 
+    def convert_tags_to_repo_digest(self):
+        if not self.use_repo_digest:
+            return
+        settings = self.upgrade.get_distinct_container_image_settings()
+        digests: Dict[str, ContainerInspectInfo] = {}
+        for container_image_ref in set(settings.values()):
+            if not is_repo_digest(container_image_ref):
+                image_info = self._get_container_image_info(container_image_ref)
+                if image_info.repo_digest:
+                    assert is_repo_digest(image_info.repo_digest), image_info
+                digests[container_image_ref] = image_info
+
+        for entity, container_image_ref in settings.items():
+            if not is_repo_digest(container_image_ref):
+                image_info = digests[container_image_ref]
+                if image_info.repo_digest:
+                    self.set_container_image(entity, image_info.repo_digest)
+
     def set_container_image(self, entity: str, image):
         self.check_mon_command({
             'prefix': 'config set',
@@ -2586,6 +2613,9 @@ To check that the host is reachable:
                         'current_id': dd.container_image_id,
                         'current_version': dd.version,
                     }
+        if self.use_repo_digest:
+            r['target_digest'] = image_info.repo_digest
+
         return json.dumps(r, indent=4, sort_keys=True)
 
     @trivial_completion
index 348a3d4ecf190088e7f16b3e0c2cc54c2465cc38..aee13bc81ffba0cf37ca0096e4858836ee29f4fe 100644 (file)
@@ -845,3 +845,30 @@ class TestCephadm(object):
             code, out, err = cephadm_module.registry_login('fail-url', 'fail-user', 'fail-password')
             assert err == 'Host test failed to login to fail-url as fail-user with given password'
             check_registry_credentials('json-url', 'json-user', 'json-pass')
+
+    @mock.patch("cephadm.module.CephadmOrchestrator._run_cephadm", _run_cephadm(json.dumps({
+        'image_id': 'image_id',
+                    'repo_digest': 'image@repo_digest',
+    })))
+    @pytest.mark.parametrize("use_repo_digest",
+                             [
+                                 False,
+                                 True
+                             ])
+    def test_upgrade_run(self, use_repo_digest, cephadm_module: CephadmOrchestrator):
+        with with_host(cephadm_module, 'test'):
+            cephadm_module.set_container_image('global', 'image')
+            if use_repo_digest:
+                cephadm_module.use_repo_digest = True
+
+            cephadm_module.convert_tags_to_repo_digest()
+
+            _, image, _ = cephadm_module.check_mon_command({
+                'prefix': 'config get',
+                'who': 'global',
+                'key': 'container_image',
+            })
+            if use_repo_digest:
+                assert image == 'image@repo_digest'
+            else:
+                assert image == 'image'
index 285fcbc89962cff87fbe4d92cdf73046161de5ac..2b5759514eddda0573526f4bb02a16cba6200bcc 100644 (file)
@@ -2,7 +2,7 @@ import json
 import logging
 import time
 import uuid
-from typing import TYPE_CHECKING, Optional
+from typing import TYPE_CHECKING, Optional, Dict
 
 import orchestrator
 from cephadm.utils import name_to_config_section
@@ -205,6 +205,19 @@ class CephadmUpgrade:
             return
         self.mgr.set_store('upgrade_state', json.dumps(self.upgrade_state.to_json()))
 
+    def get_distinct_container_image_settings(self) -> Dict[str, str]:
+        # get all distinct container_image settings
+        image_settings = {}
+        ret, out, err = self.mgr.check_mon_command({
+            'prefix': 'config dump',
+            'format': 'json',
+        })
+        config = json.loads(out)
+        for opt in config:
+            if opt['name'] == 'container_image':
+                image_settings[opt['section']] = opt['value']
+        return image_settings
+
     def _do_upgrade(self):
         # type: () -> None
         if not self.upgrade_state:
@@ -234,16 +247,7 @@ class CephadmUpgrade:
         logger.info('Upgrade: Target is %s with id %s' % (target_name,
                                                           target_id))
 
-        # get all distinct container_image settings
-        image_settings = {}
-        ret, out, err = self.mgr.check_mon_command({
-            'prefix': 'config dump',
-            'format': 'json',
-        })
-        config = json.loads(out)
-        for opt in config:
-            if opt['name'] == 'container_image':
-                image_settings[opt['section']] = opt['value']
+        image_settings = self.get_distinct_container_image_settings()
 
         daemons = self.mgr.cache.get_daemons()
         done = 0
index ca8bb48c04c587af60acd201ef5f984db48b3ac7..4efcf2f5725117d1ab700a8bb0ff54f9808e10f2 100644 (file)
@@ -103,3 +103,10 @@ def get_cluster_health(mgr: 'CephadmOrchestrator') -> str:
         raise OrchestratorError('failed to parse health status')
 
     return j['status']
+
+
+def is_repo_digest(image_name: str) -> bool:
+    """
+    repo digest are something like "ceph/ceph@sha256:blablabla"
+    """
+    return '@' in image_name