891 lines
26 KiB
Python
891 lines
26 KiB
Python
#
|
|
# Copyright 2016 Canonical Ltd
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import json
|
|
import functools
|
|
import subprocess
|
|
import socket
|
|
import utils
|
|
|
|
import charmhelpers.core.hookenv as hookenv
|
|
import charmhelpers.core.decorators as decorators
|
|
|
|
RGW_ADMIN = 'radosgw-admin'
|
|
|
|
|
|
@decorators.retry_on_exception(num_retries=10, base_delay=5,
|
|
exc_type=subprocess.CalledProcessError)
|
|
def _check_output(cmd):
|
|
"""Logging wrapper for subprocess.check_ouput"""
|
|
hookenv.log("Executing: {}".format(' '.join(cmd)), level=hookenv.DEBUG)
|
|
return subprocess.check_output(cmd).decode('UTF-8')
|
|
|
|
|
|
@decorators.retry_on_exception(num_retries=5, base_delay=3,
|
|
exc_type=subprocess.CalledProcessError)
|
|
def _check_call(cmd):
|
|
"""Logging wrapper for subprocess.check_call"""
|
|
hookenv.log("Executing: {}".format(' '.join(cmd)), level=hookenv.DEBUG)
|
|
return subprocess.check_call(cmd)
|
|
|
|
|
|
def _call(cmd):
|
|
"""Logging wrapper for subprocess.call"""
|
|
hookenv.log("Executing: {}".format(' '.join(cmd)), level=hookenv.DEBUG)
|
|
return subprocess.call(cmd)
|
|
|
|
|
|
def _key_name():
|
|
"""Determine the name of the cephx key for the local unit"""
|
|
if utils.request_per_unit_key():
|
|
return 'rgw.{}'.format(socket.gethostname())
|
|
else:
|
|
return 'radosgw.gateway'
|
|
|
|
|
|
def _list(key):
|
|
"""
|
|
Internal implementation for list_* functions
|
|
|
|
:param key: string for required entity (zone, zonegroup, realm, user)
|
|
:type key: str
|
|
:return: List of specified entities found
|
|
:rtype: list
|
|
"""
|
|
cmd = [
|
|
RGW_ADMIN, '--id={}'.format(_key_name()),
|
|
key, 'list'
|
|
]
|
|
try:
|
|
result = json.loads(_check_output(cmd))
|
|
hookenv.log("Results: {}".format(
|
|
result),
|
|
level=hookenv.DEBUG)
|
|
if isinstance(result, dict):
|
|
return result['{}s'.format(key)]
|
|
else:
|
|
return result
|
|
except TypeError:
|
|
return []
|
|
|
|
|
|
def plain_list(key):
|
|
"""Simple Implementation for list_*, where execution may fail expectedly.
|
|
|
|
On failure, retries are not attempted and empty list is returned.
|
|
|
|
:param key: string for required resource (zone, zonegroup, realm, user)
|
|
:type key: str
|
|
:return: list of specified entities found
|
|
:rtype: list
|
|
"""
|
|
cmd = [
|
|
RGW_ADMIN, '--id={}'.format(_key_name()),
|
|
key, 'list'
|
|
]
|
|
try:
|
|
result = json.loads(subprocess.check_output(
|
|
cmd, stderr=subprocess.PIPE
|
|
).decode('UTF-8'))
|
|
hookenv.log("Results: {}".format(result), level=hookenv.DEBUG)
|
|
if isinstance(result, dict):
|
|
return result['{}s'.format(key)]
|
|
else:
|
|
return result
|
|
except subprocess.CalledProcessError:
|
|
return []
|
|
except TypeError:
|
|
return []
|
|
|
|
|
|
@decorators.retry_on_exception(num_retries=5, base_delay=3,
|
|
exc_type=ValueError)
|
|
def list_zones(retry_on_empty=False):
|
|
"""
|
|
List zones
|
|
|
|
:param retry_on_empty: Whether to retry if no zones are returned.
|
|
:type retry_on_empty: bool
|
|
:return: List of specified entities found
|
|
:rtype: list
|
|
:raises: ValueError
|
|
"""
|
|
_zones = _list('zone')
|
|
if retry_on_empty and not _zones:
|
|
hookenv.log("No zones found", level=hookenv.DEBUG)
|
|
raise ValueError("No zones found")
|
|
return _zones
|
|
|
|
|
|
list_realms = functools.partial(_list, 'realm')
|
|
list_zonegroups = functools.partial(_list, 'zonegroup')
|
|
list_users = functools.partial(_list, 'user')
|
|
|
|
|
|
def list_buckets(zone, zonegroup):
|
|
"""List Buckets served under the provided zone and zonegroup pair.
|
|
|
|
:param zonegroup: Parent zonegroup.
|
|
:type zonegroup: str
|
|
:param zone: Parent zone.
|
|
:type zone: str
|
|
:returns: List of buckets found
|
|
:rtype: list
|
|
"""
|
|
cmd = [
|
|
RGW_ADMIN, '--id={}'.format(_key_name()),
|
|
'bucket', 'list',
|
|
'--rgw-zone={}'.format(zone),
|
|
'--rgw-zonegroup={}'.format(zonegroup),
|
|
]
|
|
try:
|
|
return json.loads(_check_output(cmd))
|
|
except subprocess.CalledProcessError:
|
|
hookenv.log("Bucket queried for incorrect zone({})-zonegroup({}) "
|
|
"pair".format(zone, zonegroup), level=hookenv.ERROR)
|
|
return None
|
|
except TypeError:
|
|
return None
|
|
|
|
|
|
def create_realm(name, default=False):
|
|
"""
|
|
Create a new RADOS Gateway Realm.
|
|
|
|
:param name: name of realm to create
|
|
:type name: str
|
|
:param default: set new realm as the default realm
|
|
:type default: boolean
|
|
:return: realm configuration
|
|
:rtype: dict
|
|
"""
|
|
cmd = [
|
|
RGW_ADMIN, '--id={}'.format(_key_name()),
|
|
'realm', 'create',
|
|
'--rgw-realm={}'.format(name)
|
|
]
|
|
if default:
|
|
cmd += ['--default']
|
|
try:
|
|
return json.loads(_check_output(cmd))
|
|
except TypeError:
|
|
return None
|
|
|
|
|
|
def set_default_realm(name):
|
|
"""
|
|
Set the default RADOS Gateway Realm
|
|
|
|
:param name: name of realm to create
|
|
:type name: str
|
|
"""
|
|
cmd = [
|
|
RGW_ADMIN, '--id={}'.format(_key_name()),
|
|
'realm', 'default',
|
|
'--rgw-realm={}'.format(name)
|
|
]
|
|
_check_call(cmd)
|
|
|
|
|
|
def create_zonegroup(name, endpoints, default=False, master=False, realm=None):
|
|
"""
|
|
Create a new RADOS Gateway zone Group
|
|
|
|
:param name: name of zonegroup to create
|
|
:type name: str
|
|
:param endpoints: list of URLs to endpoints for zonegroup
|
|
:type endpoints: list[str]
|
|
:param default: set new zonegroup as the default zonegroup
|
|
:type default: boolean
|
|
:param master: set new zonegroup as the master zonegroup
|
|
:type master: boolean
|
|
:param realm: realm to use for zonegroup
|
|
:type realm: str
|
|
:return: zonegroup configuration
|
|
:rtype: dict
|
|
"""
|
|
cmd = [
|
|
RGW_ADMIN, '--id={}'.format(_key_name()),
|
|
'zonegroup', 'create',
|
|
'--rgw-zonegroup={}'.format(name),
|
|
'--endpoints={}'.format(','.join(endpoints)),
|
|
]
|
|
if realm:
|
|
cmd.append('--rgw-realm={}'.format(realm))
|
|
if default:
|
|
cmd.append('--default')
|
|
if master:
|
|
cmd.append('--master')
|
|
try:
|
|
return json.loads(_check_output(cmd))
|
|
except TypeError:
|
|
return None
|
|
|
|
|
|
def modify_zonegroup(name, endpoints=None, default=False,
|
|
master=False, realm=None):
|
|
"""Modify an existing RADOS Gateway zonegroup
|
|
|
|
An empty list of endpoints would cause NO-CHANGE in the configured
|
|
endpoints for the zonegroup.
|
|
|
|
:param name: name of zonegroup to modify
|
|
:type name: str
|
|
:param endpoints: list of URLs to endpoints for zonegroup
|
|
:type endpoints: list[str]
|
|
:param default: set zonegroup as the default zonegroup
|
|
:type default: boolean
|
|
:param master: set zonegroup as the master zonegroup
|
|
:type master: boolean
|
|
:param realm: realm name for provided zonegroup
|
|
:type realm: str
|
|
:return: zonegroup configuration
|
|
:rtype: dict
|
|
"""
|
|
cmd = [
|
|
RGW_ADMIN, '--id={}'.format(_key_name()),
|
|
'zonegroup', 'modify',
|
|
'--rgw-zonegroup={}'.format(name),
|
|
]
|
|
if realm:
|
|
cmd.append('--rgw-realm={}'.format(realm))
|
|
if endpoints:
|
|
cmd.append('--endpoints={}'.format(','.join(endpoints)))
|
|
if default:
|
|
cmd.append('--default')
|
|
if master:
|
|
cmd.append('--master')
|
|
try:
|
|
return json.loads(_check_output(cmd))
|
|
except TypeError:
|
|
return None
|
|
|
|
|
|
def create_zone(name, endpoints, default=False, master=False, zonegroup=None,
|
|
access_key=None, secret=None, readonly=False):
|
|
"""
|
|
Create a new RADOS Gateway zone
|
|
|
|
:param name: name of zone to create
|
|
:type name: str
|
|
:param endpoints: list of URLs to endpoints for zone
|
|
:type endpoints: list[str]
|
|
:param default: set new zone as the default zone
|
|
:type default: boolean
|
|
:param master: set new zone as the master zone
|
|
:type master: boolean
|
|
:param zonegroup: zonegroup to use for zone
|
|
:type zonegroup: str
|
|
:param access_key: access-key to use for the zone
|
|
:type access_key: str
|
|
:param secret: secret to use with access-key for the zone
|
|
:type secret: str
|
|
:param readonly: set zone as read only
|
|
:type: readonly: boolean
|
|
:return: dict of zone configuration
|
|
:rtype: dict
|
|
"""
|
|
cmd = [
|
|
RGW_ADMIN, '--id={}'.format(_key_name()),
|
|
'zone', 'create',
|
|
'--rgw-zone={}'.format(name),
|
|
'--endpoints={}'.format(','.join(endpoints)),
|
|
]
|
|
if zonegroup:
|
|
cmd.append('--rgw-zonegroup={}'.format(zonegroup))
|
|
if default:
|
|
cmd.append('--default')
|
|
if master:
|
|
cmd.append('--master')
|
|
if access_key and secret:
|
|
cmd.append('--access-key={}'.format(access_key))
|
|
cmd.append('--secret={}'.format(secret))
|
|
cmd.append('--read-only={}'.format(1 if readonly else 0))
|
|
try:
|
|
return json.loads(_check_output(cmd))
|
|
except TypeError:
|
|
return None
|
|
|
|
|
|
def modify_zone(name, endpoints=None, default=False, master=False,
|
|
access_key=None, secret=None, readonly=False,
|
|
realm=None, zonegroup=None):
|
|
"""Modify an existing RADOS Gateway zone
|
|
|
|
:param name: name of zone to create
|
|
:type name: str
|
|
:param endpoints: list of URLs to endpoints for zone
|
|
:type endpoints: list[str]
|
|
:param default: set zone as the default zone
|
|
:type default: boolean
|
|
:param master: set zone as the master zone
|
|
:type master: boolean
|
|
:param access_key: access-key to use for the zone
|
|
:type access_key: str
|
|
:param secret: secret to use with access-key for the zone
|
|
:type secret: str
|
|
:param readonly: set zone as read only
|
|
:type readonly: boolean
|
|
:param realm: realm to use for zone
|
|
:type realm: str
|
|
:param zonegroup: zonegroup to use for zone
|
|
:type zonegroup: str
|
|
:return: zone configuration
|
|
:rtype: dict
|
|
"""
|
|
cmd = [
|
|
RGW_ADMIN, '--id={}'.format(_key_name()),
|
|
'zone', 'modify',
|
|
'--rgw-zone={}'.format(name),
|
|
]
|
|
if realm:
|
|
cmd.append('--rgw-realm={}'.format(realm))
|
|
if zonegroup:
|
|
cmd.append('--rgw-zonegroup={}'.format(zonegroup))
|
|
if endpoints:
|
|
cmd.append('--endpoints={}'.format(','.join(endpoints)))
|
|
if access_key and secret:
|
|
cmd.append('--access-key={}'.format(access_key))
|
|
cmd.append('--secret={}'.format(secret))
|
|
if master:
|
|
cmd.append('--master')
|
|
if default:
|
|
cmd.append('--default')
|
|
cmd.append('--read-only={}'.format(1 if readonly else 0))
|
|
try:
|
|
return json.loads(_check_output(cmd))
|
|
except TypeError:
|
|
return None
|
|
|
|
|
|
def remove_zone_from_zonegroup(zone, zonegroup):
|
|
"""Remove RADOS Gateway zone from provided parent zonegroup
|
|
|
|
Removal is different from deletion, this operation removes zone/zonegroup
|
|
affiliation but does not delete the actual zone.
|
|
|
|
:param zonegroup: parent zonegroup name
|
|
:type zonegroup: str
|
|
:param zone: zone name
|
|
:type zone: str
|
|
:return: modified zonegroup config
|
|
:rtype: dict
|
|
"""
|
|
cmd = [
|
|
RGW_ADMIN, '--id={}'.format(_key_name()),
|
|
'zonegroup', 'remove',
|
|
'--rgw-zonegroup={}'.format(zonegroup),
|
|
'--rgw-zone={}'.format(zone),
|
|
]
|
|
try:
|
|
result = _check_output(cmd)
|
|
return json.loads(result)
|
|
except (TypeError, subprocess.CalledProcessError) as exc:
|
|
raise RuntimeError(
|
|
"Error removing zone {} from zonegroup {}. Result: {}"
|
|
.format(zone, zonegroup, result)) from exc
|
|
|
|
|
|
def add_zone_to_zonegroup(zone, zonegroup):
|
|
"""Add RADOS Gateway zone to provided zonegroup
|
|
|
|
:param zonegroup: parent zonegroup name
|
|
:type zonegroup: str
|
|
:param zone: zone name
|
|
:type zone: str
|
|
:return: modified zonegroup config
|
|
:rtype: dict
|
|
"""
|
|
cmd = [
|
|
RGW_ADMIN, '--id={}'.format(_key_name()),
|
|
'zonegroup', 'add',
|
|
'--rgw-zonegroup={}'.format(zonegroup),
|
|
'--rgw-zone={}'.format(zone),
|
|
]
|
|
try:
|
|
result = _check_output(cmd)
|
|
return json.loads(result)
|
|
except (TypeError, subprocess.CalledProcessError) as exc:
|
|
raise RuntimeError(
|
|
"Error adding zone {} from zonegroup {}. Result: {}"
|
|
.format(zone, zonegroup, result)) from exc
|
|
|
|
|
|
def update_period(fatal=True, zonegroup=None, zone=None, realm=None):
|
|
"""Update RADOS Gateway configuration period
|
|
|
|
:param fatal: In failure case, whether CalledProcessError is to be raised.
|
|
:type fatal: boolean
|
|
:param zonegroup: zonegroup name
|
|
:type zonegroup: str
|
|
:param zone: zone name
|
|
:type zone: str
|
|
:param realm: realm name
|
|
:type realm: str
|
|
"""
|
|
cmd = [
|
|
RGW_ADMIN, '--id={}'.format(_key_name()),
|
|
'period', 'update', '--commit'
|
|
]
|
|
if zonegroup is not None:
|
|
cmd.append('--rgw-zonegroup={}'.format(zonegroup))
|
|
if zone is not None:
|
|
cmd.append('--rgw-zone={}'.format(zone))
|
|
if realm is not None:
|
|
cmd.append('--rgw-realm={}'.format(realm))
|
|
if fatal:
|
|
_check_call(cmd)
|
|
else:
|
|
_call(cmd)
|
|
|
|
|
|
def tidy_defaults():
|
|
"""
|
|
Purge any default zonegroup and zone definitions
|
|
"""
|
|
if ('default' in list_zonegroups() and
|
|
'default' in list_zones()):
|
|
cmd = [
|
|
RGW_ADMIN, '--id={}'.format(_key_name()),
|
|
'zonegroup', 'remove',
|
|
'--rgw-zonegroup=default',
|
|
'--rgw-zone=default'
|
|
]
|
|
_call(cmd)
|
|
update_period()
|
|
|
|
if 'default' in list_zones():
|
|
cmd = [
|
|
RGW_ADMIN, '--id={}'.format(_key_name()),
|
|
'zone', 'delete',
|
|
'--rgw-zone=default'
|
|
]
|
|
_call(cmd)
|
|
update_period()
|
|
|
|
if 'default' in list_zonegroups():
|
|
cmd = [
|
|
RGW_ADMIN, '--id={}'.format(_key_name()),
|
|
'zonegroup', 'delete',
|
|
'--rgw-zonegroup=default'
|
|
]
|
|
_call(cmd)
|
|
update_period()
|
|
|
|
|
|
def get_user_creds(username):
|
|
cmd = [
|
|
RGW_ADMIN, '--id={}'.format(_key_name()),
|
|
'user', 'info',
|
|
'--uid={}'.format(username)
|
|
]
|
|
result = json.loads(_check_output(cmd))
|
|
return (result['keys'][0]['access_key'],
|
|
result['keys'][0]['secret_key'])
|
|
|
|
|
|
def suspend_user(username):
|
|
"""
|
|
Suspend a RADOS Gateway user
|
|
|
|
:param username: username of user to create
|
|
:type username: str
|
|
"""
|
|
if username not in list_users():
|
|
hookenv.log(
|
|
"Cannot suspended user {}. User not found.".format(username),
|
|
level=hookenv.DEBUG)
|
|
return
|
|
cmd = [
|
|
RGW_ADMIN, '--id={}'.format(_key_name()),
|
|
'user', 'suspend',
|
|
'--uid={}'.format(username)
|
|
]
|
|
_check_output(cmd)
|
|
hookenv.log(
|
|
"Suspended user {}".format(username),
|
|
level=hookenv.DEBUG)
|
|
|
|
|
|
def create_user(username, system_user=False):
|
|
"""
|
|
Create a RADOS Gateway user
|
|
|
|
:param username: username of user to create
|
|
:type username: str
|
|
:param system_user: Whether to grant system user role
|
|
:type system_user: bool
|
|
:return: access key and secret
|
|
:rtype: (str, str)
|
|
"""
|
|
cmd = [
|
|
RGW_ADMIN, '--id={}'.format(_key_name()),
|
|
'user', 'create',
|
|
'--uid={}'.format(username),
|
|
'--display-name=Synchronization User'
|
|
]
|
|
if system_user:
|
|
cmd.append('--system')
|
|
try:
|
|
result = json.loads(_check_output(cmd))
|
|
return (result['keys'][0]['access_key'],
|
|
result['keys'][0]['secret_key'])
|
|
except TypeError:
|
|
return (None, None)
|
|
|
|
|
|
def create_system_user(username):
|
|
"""
|
|
Create a RADOS Gateway system user
|
|
|
|
:param username: username of user to create
|
|
:type username: str
|
|
:return: access key and secret
|
|
:rtype: (str, str)
|
|
"""
|
|
return create_user(username, system_user=True)
|
|
|
|
|
|
def pull_realm(url, access_key, secret):
|
|
"""
|
|
Pull in a RADOS Gateway Realm from a master RGW instance
|
|
|
|
:param url: url of remote rgw deployment
|
|
:type url: str
|
|
:param access_key: access-key for remote rgw deployment
|
|
:type access_key: str
|
|
:param secret: secret for remote rgw deployment
|
|
:type secret: str
|
|
:return: realm configuration
|
|
:rtype: dict
|
|
"""
|
|
cmd = [
|
|
RGW_ADMIN, '--id={}'.format(_key_name()),
|
|
'realm', 'pull',
|
|
'--url={}'.format(url),
|
|
'--access-key={}'.format(access_key),
|
|
'--secret={}'.format(secret),
|
|
]
|
|
try:
|
|
return json.loads(_check_output(cmd))
|
|
except TypeError:
|
|
return None
|
|
|
|
|
|
def pull_period(url, access_key, secret):
|
|
"""
|
|
Pull in a RADOS Gateway period from a master RGW instance
|
|
|
|
:param url: url of remote rgw deployment
|
|
:type url: str
|
|
:param access_key: access-key for remote rgw deployment
|
|
:type access_key: str
|
|
:param secret: secret for remote rgw deployment
|
|
:type secret: str
|
|
:return: realm configuration
|
|
:rtype: dict
|
|
"""
|
|
cmd = [
|
|
RGW_ADMIN, '--id={}'.format(_key_name()),
|
|
'period', 'pull',
|
|
'--url={}'.format(url),
|
|
'--access-key={}'.format(access_key),
|
|
'--secret={}'.format(secret),
|
|
]
|
|
try:
|
|
return json.loads(_check_output(cmd))
|
|
except TypeError:
|
|
return None
|
|
|
|
|
|
def rename_zone(name, new_name, zonegroup):
|
|
"""Rename an existing RADOS Gateway zone
|
|
|
|
If the command execution succeeds, 0 is returned, otherwise
|
|
None is returned to the caller.
|
|
|
|
:param name: current name for the zone being renamed
|
|
:type name: str
|
|
:param new_name: new name for the zone being renamed
|
|
:type new_name: str
|
|
:rtype: int
|
|
"""
|
|
cmd = [
|
|
RGW_ADMIN, '--id={}'.format(_key_name()),
|
|
'zone', 'rename',
|
|
'--rgw-zone={}'.format(name),
|
|
'--zone-new-name={}'.format(new_name),
|
|
'--rgw-zonegroup={}'.format(zonegroup)
|
|
]
|
|
result = _call(cmd)
|
|
return 0 if result == 0 else None
|
|
|
|
|
|
def rename_zonegroup(name, new_name):
|
|
"""Rename an existing RADOS Gateway zonegroup
|
|
|
|
If the command execution succeeds, 0 is returned, otherwise
|
|
None is returned to the caller.
|
|
|
|
:param name: current name for the zonegroup being renamed
|
|
:type name: str
|
|
:param new_name: new name for the zonegroup being renamed
|
|
:type new_name: str
|
|
:rtype: int
|
|
"""
|
|
cmd = [
|
|
RGW_ADMIN, '--id={}'.format(_key_name()),
|
|
'zonegroup', 'rename',
|
|
'--rgw-zonegroup={}'.format(name),
|
|
'--zonegroup-new-name={}'.format(new_name),
|
|
]
|
|
result = _call(cmd)
|
|
return 0 if result == 0 else None
|
|
|
|
|
|
def get_zonegroup_info(zonegroup):
|
|
"""Fetch detailed info for the provided zonegroup
|
|
|
|
:param zonegroup: zonegroup Name for detailed query
|
|
:type zonegroup: str
|
|
:rtype: dict
|
|
"""
|
|
cmd = [
|
|
RGW_ADMIN, '--id={}'.format(_key_name()),
|
|
'zonegroup', 'get',
|
|
'--rgw-zonegroup={}'.format(zonegroup),
|
|
]
|
|
try:
|
|
return json.loads(_check_output(cmd))
|
|
except TypeError:
|
|
return None
|
|
|
|
|
|
def get_sync_status():
|
|
"""
|
|
Get sync status
|
|
:returns: Sync Status Report from radosgw-admin
|
|
:rtype: str
|
|
"""
|
|
cmd = [
|
|
RGW_ADMIN, '--id={}'.format(_key_name()),
|
|
'sync', 'status',
|
|
]
|
|
try:
|
|
return _check_output(cmd)
|
|
except subprocess.CalledProcessError:
|
|
hookenv.log("Failed to fetch sync status", level=hookenv.ERROR)
|
|
return None
|
|
|
|
|
|
def is_multisite_configured(zone, zonegroup):
|
|
"""Check if system is already multisite configured
|
|
|
|
Checks if zone and zonegroup are configured appropriately and
|
|
remote data sync source is detected in sync status
|
|
|
|
:rtype: Boolean
|
|
"""
|
|
local_zones = list_zones()
|
|
if zone not in local_zones:
|
|
hookenv.log("zone {} not found in local zones {}"
|
|
.format(zone, local_zones), level=hookenv.ERROR)
|
|
return False
|
|
|
|
local_zonegroups = list_zonegroups()
|
|
if zonegroup not in local_zonegroups:
|
|
hookenv.log("zonegroup {} not found in local zonegroups {}"
|
|
.format(zonegroup, local_zonegroups), level=hookenv.ERROR)
|
|
return False
|
|
|
|
sync_status = get_sync_status()
|
|
hookenv.log("Multisite sync status {}".format(sync_status),
|
|
level=hookenv.DEBUG)
|
|
if sync_status is not None:
|
|
return ('data sync source:' in sync_status)
|
|
|
|
return False
|
|
|
|
|
|
def get_local_zone(zonegroup):
|
|
"""Get local zone to provided parent zonegroup.
|
|
|
|
In multisite systems, zonegroup contains both local and remote zone info
|
|
this method is used to fetch the zone local to querying site.
|
|
|
|
:param zonegroup: parent zonegroup name.
|
|
:type zonegroup: str
|
|
:returns: tuple with parent zonegroup and local zone name
|
|
:rtype: tuple
|
|
"""
|
|
local_zones = list_zones()
|
|
zonegroup_info = get_zonegroup_info(zonegroup)
|
|
|
|
if zonegroup_info is None:
|
|
hookenv.log("Failed to fetch zonegroup ({}) info".format(zonegroup),
|
|
level=hookenv.ERROR)
|
|
return None, None
|
|
|
|
# zonegroup info always contains self name and zones list so fetching
|
|
# directly is safe.
|
|
master_zonegroup = zonegroup_info['name']
|
|
for zone_info in zonegroup_info['zones']:
|
|
zone = zone_info['name']
|
|
if zone in local_zones:
|
|
return zone, master_zonegroup
|
|
|
|
hookenv.log(
|
|
"No local zone configured for zonegroup ({})".format(zonegroup),
|
|
level=hookenv.ERROR
|
|
)
|
|
return None, None
|
|
|
|
|
|
def rename_multisite_config(zonegroups, new_zonegroup_name,
|
|
zones, new_zone_name):
|
|
"""Rename zone and zonegroup to provided new names.
|
|
|
|
If zone list (zones) or zonegroup list (zonegroups) contain 1 element
|
|
rename the only element present in the list to provided (new_) value.
|
|
|
|
:param zonegroups: List of zonegroups available at site.
|
|
:type zonegroups: list[str]
|
|
:param new_zonegroup_name: Desired new name for master zonegroup.
|
|
:type new_zonegroup_name: str
|
|
:param zones: List of zones available at site.
|
|
:type zones: list[str]
|
|
:param new_zonegroup_name: Desired new name for master zone.
|
|
:type new_zonegroup_name: str
|
|
|
|
:return: Whether any of the zone or zonegroup is renamed.
|
|
:rtype: Boolean
|
|
"""
|
|
mutation = False
|
|
if (len(zonegroups) == 1) and (len(zones) == 1):
|
|
if new_zonegroup_name not in zonegroups:
|
|
result = rename_zonegroup(zonegroups[0], new_zonegroup_name)
|
|
if result is None:
|
|
hookenv.log(
|
|
"Failed renaming zonegroup from {} to {}"
|
|
.format(zonegroups[0], new_zonegroup_name),
|
|
level=hookenv.ERROR
|
|
)
|
|
return None
|
|
mutation = True
|
|
|
|
if new_zone_name not in zones:
|
|
result = rename_zone(zones[0], new_zone_name, new_zonegroup_name)
|
|
if result is None:
|
|
hookenv.log(
|
|
"Failed renaming zone from {} to {}"
|
|
.format(zones[0], new_zone_name), level=hookenv.ERROR
|
|
)
|
|
return None
|
|
mutation = True
|
|
|
|
if mutation:
|
|
hookenv.log("Renamed zonegroup {} to {}, and zone {} to {}".format(
|
|
zonegroups[0], new_zonegroup_name,
|
|
zones[0], new_zone_name))
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def modify_multisite_config(zone, zonegroup, endpoints=None, realm=None):
|
|
"""Configure zone and zonegroup as master for multisite system.
|
|
|
|
:param zonegroup: zonegroup name being configured for multisite
|
|
:type zonegroup: str
|
|
:param zone: zone name being configured for multisite
|
|
:type zone: str
|
|
:param endpoints: list of URLs to RGW endpoints
|
|
:type endpoints: list[str]
|
|
:param realm: realm to use for multisite
|
|
:type realm: str
|
|
:rtype: Boolean
|
|
"""
|
|
if modify_zonegroup(zonegroup, endpoints=endpoints, default=True,
|
|
master=True, realm=realm) is None:
|
|
hookenv.log(
|
|
"Failed configuring zonegroup {}".format(zonegroup),
|
|
level=hookenv.ERROR
|
|
)
|
|
return None
|
|
|
|
if modify_zone(zone, endpoints=endpoints, default=True,
|
|
master=True, zonegroup=zonegroup, realm=realm) is None:
|
|
hookenv.log(
|
|
"Failed configuring zone {}".format(zone), level=hookenv.ERROR
|
|
)
|
|
return None
|
|
|
|
update_period(zonegroup=zonegroup, zone=zone)
|
|
hookenv.log("Configured zonegroup {}, and zone {} for multisite".format(
|
|
zonegroup, zone))
|
|
return True
|
|
|
|
|
|
def check_zone_has_buckets(zone, zonegroup):
|
|
"""Checks whether provided zone-zonegroup pair contains any bucket.
|
|
|
|
:param zone: zone name to query buckets in.
|
|
:type zone: str
|
|
:param zonegroup: Parent zonegroup of zone.
|
|
:type zonegroup: str
|
|
:rtype: Boolean
|
|
"""
|
|
buckets = list_buckets(zone, zonegroup)
|
|
if buckets is not None:
|
|
return (len(buckets) > 0)
|
|
hookenv.log(
|
|
"Failed to query buckets for zone {} zonegroup {}"
|
|
.format(zone, zonegroup),
|
|
level=hookenv.WARNING
|
|
)
|
|
return False
|
|
|
|
|
|
def check_zonegroup_has_buckets(zonegroup):
|
|
"""Checks whether any bucket exists in the master zone of a zonegroup.
|
|
|
|
:param zone: zonegroup name to query buckets.
|
|
:type zone: str
|
|
:rtype: Boolean
|
|
"""
|
|
# NOTE(utkarshbhatthere): sometimes querying against a particular
|
|
# zonegroup results in info of an entirely different zonegroup, thus to
|
|
# prevent a query against an incorrect pair in such cases, both zone and
|
|
# zonegroup names are taken from zonegroup info.
|
|
master_zone, master_zonegroup = get_local_zone(zonegroup)
|
|
|
|
# If master zone is not configured for zonegroup
|
|
if master_zone is None:
|
|
hookenv.log("No master zone configured for zonegroup {}"
|
|
.format(master_zonegroup), level=hookenv.WARNING)
|
|
return False
|
|
return check_zone_has_buckets(master_zone, master_zonegroup)
|
|
|
|
|
|
def check_cluster_has_buckets():
|
|
"""Iteratively check if ANY zonegroup has buckets on cluster.
|
|
|
|
:rtype: Boolean
|
|
"""
|
|
for zonegroup in list_zonegroups():
|
|
if check_zonegroup_has_buckets(zonegroup):
|
|
return True
|
|
return False
|