# # 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 charmhelpers.core.hookenv as hookenv import charmhelpers.core.decorators as decorators RGW_ADMIN = 'radosgw-admin' @decorators.retry_on_exception(num_retries=5, base_delay=3, 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""" return 'rgw.{}'.format(socket.gethostname()) 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)) if isinstance(result, dict): return result['{}s'.format(key)] else: return result except TypeError: return [] list_realms = functools.partial(_list, 'realm') list_zonegroups = functools.partial(_list, 'zonegroup') list_zones = functools.partial(_list, 'zone') list_users = functools.partial(_list, 'user') 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 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): """ 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 :return: zone configuration :rtype: dict """ cmd = [ RGW_ADMIN, '--id={}'.format(_key_name()), 'zone', 'modify', '--rgw-zone={}'.format(name), ] 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 update_period(fatal=True): """ Update RADOS Gateway configuration period """ cmd = [ RGW_ADMIN, '--id={}'.format(_key_name()), 'period', 'update', '--commit' ] 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 create_system_user(username): """ Create a RADOS Gateway system use for sync usage :param username: username of user to create :type username: str :return: access key and secret :rtype: (str, str) """ cmd = [ RGW_ADMIN, '--id={}'.format(_key_name()), 'user', 'create', '--uid={}'.format(username), '--display-name=Synchronization User', '--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 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