]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
mgr/rgw: add realm reconcile command
authorYehuda Sadeh <yehuda@redhat.com>
Tue, 15 Jun 2021 19:11:35 +0000 (12:11 -0700)
committerYehuda Sadeh <yehuda@redhat.com>
Wed, 24 Nov 2021 20:54:30 +0000 (12:54 -0800)
reconcile endpoints defined in orchestrator vs realm period, and optionally
update realm period

Signed-off-by: Yehuda Sadeh <yehuda@redhat.com>
src/pybind/mgr/rgw/diff.py [new file with mode: 0644]
src/pybind/mgr/rgw/module.py
src/pybind/mgr/rgw/rgwam.py
src/pybind/mgr/rgw/types.py

diff --git a/src/pybind/mgr/rgw/diff.py b/src/pybind/mgr/rgw/diff.py
new file mode 100644 (file)
index 0000000..0876ea2
--- /dev/null
@@ -0,0 +1,98 @@
+class ZoneEPs:
+    def __init__(self):
+        self.endpoints = set()
+
+    def add(self, ep):
+        if not ep:
+            return
+
+        self.endpoints.add(ep)
+
+    def diff(self, zep):
+        return list(self.endpoints.difference(zep.endpoints))
+
+    def get_all(self):
+        for ep in self.endpoints:
+            yield ep
+
+
+
+class RealmEPs:
+    def __init__(self):
+        self.zones = {}
+
+    def add(self, zone, ep = None):
+        if not zone:
+            return
+
+        z = self.zones.get(zone)
+        if not z:
+            z = ZoneEPs()
+            self.zones[zone] = z
+
+        z.add(ep)
+
+    def diff(self, rep):
+        result = {}
+        for z, zep in rep.zones.items():
+            myzep = self.zones.get(z)
+            if not myzep:
+                continue
+
+
+            d = myzep.diff(zep)
+            if len(d) > 0:
+                result[z] = myzep.diff(zep)
+
+        return result
+
+    def get_all(self):
+        for z, zep in self.zones.items():
+            eps = []
+            for ep in zep.get_all():
+                eps.append(ep)
+            yield z, eps
+
+
+
+class RealmsEPs:
+    def __init__(self):
+        self.realms = {}
+
+    def add(self, realm, zone = None, ep = None):
+        if not realm:
+            return
+
+        r = self.realms.get(realm)
+        if not r:
+            r = RealmEPs()
+            self.realms[realm] = r
+
+        r.add(zone, ep)
+
+    def diff(self, rep):
+        result = {}
+
+        for r, rep in rep.realms.items():
+            myrealm = self.realms.get(r)
+            if not myrealm:
+                continue
+
+            d = myrealm.diff(rep)
+            if d:
+                result[r] = d
+
+        return result
+
+    def get_all(self):
+        result = {}
+        for r, rep in self.realms.items():
+            zs = {}
+            for z, eps in rep.get_all():
+                zs[z] = eps
+
+            result[r] = zs
+
+        return result
+
+
index 96db1f7bbd8ca0ffbb5601690083f2ac67a0941f..20bd759cd22a25b08ddc3d384258d21bea47bd48 100644 (file)
@@ -134,6 +134,23 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule):
 
         return HandleCommandResult(retval=retval, stdout=out, stderr=err)
 
+    @CLICommand('rgw realm reconcile', perm='rw')
+    def _cmd_rgw_realm_reconcile(self,
+                             realm_name : Optional[str] = None,
+                             zonegroup_name: Optional[str] = None,
+                             zone_name: Optional[str] = None,
+                             update: Optional[bool] = False):
+        """Bootstrap new rgw zone that syncs with existing zone"""
+
+        try:
+            retval, out, err = RGWAM(self.env).realm_reconcile(realm_name, zonegroup_name,
+                                                               zone_name, update)
+        except RGWAMException as e:
+            self.log.error('cmd run exception: (%d) %s' % (e.retcode, e.message))
+            return (e.retcode, e.message, e.stderr)
+
+        return HandleCommandResult(retval=retval, stdout=out, stderr=err)
+
     def shutdown(self) -> None:
         """
         This method is called by the mgr when the module needs to shut
index cda15b08ee08b3124eac465fc4dcd04f44992033..521b86fe3429ba807b4712a3cad27bd0af7d7090 100644 (file)
@@ -22,6 +22,7 @@ import orchestrator
 from urllib.parse import urlparse
 
 from .types import RGWAMException, RGWAMCmdRunException, RGWPeriod, RGWUser, RealmToken
+from .diff import RealmsEPs
 
 from ceph.deployment.service_spec import RGWSpec
 
@@ -190,6 +191,15 @@ class RGWCmd(RGWCmdBase):
 class RealmOp:
     def __init__(self, env : EnvArgs):
         self.env = env
+
+    def list(self):
+        ze = ZoneEnv(self.env)
+
+        params = [ 'realm',
+                   'list' ]
+
+        return RGWAdminJSONCmd(ze).run(params)
+
         
     def get(self, realm : EntityKey = None):
 
@@ -592,6 +602,130 @@ class RGWAM:
 
         return (0, success_message, '')
 
+
+    def _get_daemon_eps(self, realm_name = None, zonegroup_name = None, zone_name = None):
+        # get running daemons info
+        service_name = None
+        if realm_name and zone_name:
+            service_name = 'rgw.%s.%s' % (realm_name, zone_name)
+
+        daemon_type = 'rgw'
+        daemon_id = None
+        hostname = None
+        refresh = True
+
+        completion = self.env.mgr.list_daemons(service_name,
+                                               daemon_type,
+                                               daemon_id=daemon_id,
+                                               host=hostname,
+                                               refresh=refresh)
+
+        daemons = orchestrator.raise_if_exception(completion)
+
+        rep = RealmsEPs()
+
+        for s in daemons:
+            for p in s.ports:
+                svc_id = s.service_id()
+                l = svc_id.split('.')
+                if len(l) < 2:
+                    log.error('ERROR: service id cannot be parsed: (svc_id=%s)' % svc_id)
+                    continue
+
+                svc_realm = l[0]
+                svc_zone = l[1]
+
+                if realm_name and svc_realm != realm_name:
+                    log.debug('skipping realm %s' % svc_realm)
+                    continue
+
+                if zone_name and svc_zone != zone_name:
+                    log.debug('skipping zone %s' % svc_zone)
+                    continue
+
+                ep = 'http://%s:%d' % (s.hostname, p) # ssl?
+
+                rep.add(svc_realm, svc_zone, ep)
+
+        return rep
+
+
+
+    def _get_rgw_eps(self, realm_name = None, zonegroup_name = None, zone_name = None):
+        rep = RealmsEPs()
+
+        try:
+            realm_list_ret = self.realm_op().list()
+        except RGWAMException as e:
+            raise RGWAMException('failed to list realms', e)
+
+        realms = realm_list_ret.get('realms') or []
+
+        zones_map = {}
+
+        for realm in realms:
+            if realm_name and realm != realm_name:
+                log.debug('skipping realm %s' % realm)
+                continue
+
+            period_info = self.period_op().get(EntityName(realm))
+
+            period = RGWPeriod(period_info)
+
+            zones_map[realm] = {}
+
+            for zg in period.iter_zonegroups():
+                if zonegroup_name and zg.name != zonegroup_name:
+                    log.debug('skipping zonegroup %s' % zg.name)
+                    continue
+
+                for zone in zg.iter_zones():
+                    if zone_name and zone.name != zone_name:
+                        log.debug('skipping zone %s' % zone.name)
+                        continue
+
+                    zones_map[realm][zone.name] = zg.name
+
+                    if len(zone.endpoints) == 0:
+                        rep.add(realm, zone.name, None)
+                        continue
+
+                    for ep in zone.endpoints:
+                        rep.add(realm, zone.name, ep)
+
+        return (rep, zones_map)
+
+    def realm_reconcile(self, realm_name = None, zonegroup_name = None, zone_name = None, update = False):
+
+        daemon_rep = self._get_daemon_eps(realm_name, zonegroup_name, zone_name)
+
+        rgw_rep, zones_map = self._get_rgw_eps(realm_name, zonegroup_name, zone_name)
+
+        diff = daemon_rep.diff(rgw_rep)
+
+        diffj = json.dumps(diff)
+
+        if not update:
+            return (0, diffj, '')
+
+        for realm, realm_diff in diff.items():
+            for zone, endpoints in realm_diff.items():
+
+                zg = zones_map[realm][zone]
+
+                try:
+                    zone_info = self.zone_op().modify(EntityName(zone), EntityName(zg), endpoints = ','.join(diff[realm][zone]))
+                except RGWAMException as e:
+                    raise RGWAMException('failed to modify zone', e)
+
+            try:
+                period_info = self.period_op().update(EntityName(realm), EntityName(zg), EntityName(zone), True)
+            except RGWAMException as e:
+                raise RGWAMException('failed to update period', e)
+
+        return (0, 'Updated: ' + diffj, '')
+
+
     def run_radosgw(self, port = None, log_file = None, debug_ms = None, debug_rgw = None):
 
         fe_cfg = 'beast'
index f9f5448af8fec9e51d9a0c23bd594269e0126592..8ef0feaf76442215bdd66b843f70b098edaf851a 100644 (file)
@@ -69,6 +69,10 @@ class RGWZoneGroup(JSONObj):
 
         return z.endpoints
 
+    def iter_zones(self):
+        for zone in self.zones_by_id.values():
+            yield zone
+
 class RGWPeriod(JSONObj):
     def __init__(self, period_dict):
         self.id = period_dict['id']
@@ -104,6 +108,10 @@ class RGWPeriod(JSONObj):
 
         return zg.get_zone_endpoints(zone_id)
 
+    def iter_zonegroups(self):
+        for zg in self.zonegroups_by_id.values():
+            yield zg
+
         
 
 class RGWAccessKey(JSONObj):