From 486bbb6bea86ef2ebe6a65f0e0f24a2d1c94211d Mon Sep 17 00:00:00 2001 From: Yehuda Sadeh Date: Tue, 15 Jun 2021 12:11:35 -0700 Subject: [PATCH] mgr/rgw: add realm reconcile command reconcile endpoints defined in orchestrator vs realm period, and optionally update realm period Signed-off-by: Yehuda Sadeh --- src/pybind/mgr/rgw/diff.py | 98 +++++++++++++++++++++++++ src/pybind/mgr/rgw/module.py | 17 +++++ src/pybind/mgr/rgw/rgwam.py | 134 +++++++++++++++++++++++++++++++++++ src/pybind/mgr/rgw/types.py | 8 +++ 4 files changed, 257 insertions(+) create mode 100644 src/pybind/mgr/rgw/diff.py diff --git a/src/pybind/mgr/rgw/diff.py b/src/pybind/mgr/rgw/diff.py new file mode 100644 index 0000000000000..0876ea2bdfd49 --- /dev/null +++ b/src/pybind/mgr/rgw/diff.py @@ -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 + + diff --git a/src/pybind/mgr/rgw/module.py b/src/pybind/mgr/rgw/module.py index 96db1f7bbd8ca..20bd759cd22a2 100644 --- a/src/pybind/mgr/rgw/module.py +++ b/src/pybind/mgr/rgw/module.py @@ -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 diff --git a/src/pybind/mgr/rgw/rgwam.py b/src/pybind/mgr/rgw/rgwam.py index cda15b08ee08b..521b86fe3429b 100644 --- a/src/pybind/mgr/rgw/rgwam.py +++ b/src/pybind/mgr/rgw/rgwam.py @@ -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' diff --git a/src/pybind/mgr/rgw/types.py b/src/pybind/mgr/rgw/types.py index f9f5448af8fec..8ef0feaf76442 100644 --- a/src/pybind/mgr/rgw/types.py +++ b/src/pybind/mgr/rgw/types.py @@ -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): -- 2.39.5