diff --git a/.zuul.yaml b/.zuul.yaml index aa9c508..7051aee 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -1,4 +1,3 @@ - project: templates: - - python-charm-jobs - - openstack-python35-jobs-nonvoting + - python35-charm-jobs diff --git a/actions/create-cache-tier b/actions/create-cache-tier deleted file mode 120000 index 2a7e434..0000000 --- a/actions/create-cache-tier +++ /dev/null @@ -1 +0,0 @@ -create-cache-tier.py \ No newline at end of file diff --git a/actions/create-cache-tier b/actions/create-cache-tier new file mode 100755 index 0000000..e8170cf --- /dev/null +++ b/actions/create-cache-tier @@ -0,0 +1,41 @@ +#!/usr/bin/python +__author__ = 'chris' +from subprocess import CalledProcessError +import sys + +sys.path.append('hooks') + +from charmhelpers.contrib.storage.linux.ceph import Pool, pool_exists +from charmhelpers.core.hookenv import action_get, log, action_fail + + +def make_cache_tier(): + backer_pool = action_get("backer-pool") + cache_pool = action_get("cache-pool") + cache_mode = action_get("cache-mode") + + # Pre flight checks + if not pool_exists('admin', backer_pool): + log("Please create {} pool before calling create-cache-tier".format( + backer_pool)) + action_fail("create-cache-tier failed. Backer pool {} must exist " + "before calling this".format(backer_pool)) + + if not pool_exists('admin', cache_pool): + log("Please create {} pool before calling create-cache-tier".format( + cache_pool)) + action_fail("create-cache-tier failed. Cache pool {} must exist " + "before calling this".format(cache_pool)) + + pool = Pool(service='admin', name=backer_pool) + try: + pool.add_cache_tier(cache_pool=cache_pool, mode=cache_mode) + except CalledProcessError as err: + log("Add cache tier failed with message: {}".format( + err.message)) + action_fail("create-cache-tier failed. Add cache tier failed with " + "message: {}".format(err.message)) + + +if __name__ == '__main__': + make_cache_tier() diff --git a/actions/create-cache-tier.py b/actions/create-cache-tier.py index e8170cf..928e941 100755 --- a/actions/create-cache-tier.py +++ b/actions/create-cache-tier.py @@ -1,9 +1,21 @@ -#!/usr/bin/python +#!/usr/bin/env python3 __author__ = 'chris' +import os from subprocess import CalledProcessError import sys -sys.path.append('hooks') +_path = os.path.dirname(os.path.realpath(__file__)) +_hooks = os.path.abspath(os.path.join(_path, '../hooks')) +_root = os.path.abspath(os.path.join(_path, '..')) + + +def _add_path(path): + if path not in sys.path: + sys.path.insert(1, path) + +_add_path(_hooks) +_add_path(_root) + from charmhelpers.contrib.storage.linux.ceph import Pool, pool_exists from charmhelpers.core.hookenv import action_get, log, action_fail diff --git a/actions/create-erasure-profile b/actions/create-erasure-profile index 2b00b58..7400ccd 100755 --- a/actions/create-erasure-profile +++ b/actions/create-erasure-profile @@ -1,8 +1,20 @@ -#!/usr/bin/python +#!/usr/bin/env python3 +import os from subprocess import CalledProcessError import sys -sys.path.append('hooks') +_path = os.path.dirname(os.path.realpath(__file__)) +_hooks = os.path.abspath(os.path.join(_path, '../hooks')) +_root = os.path.abspath(os.path.join(_path, '..')) + + +def _add_path(path): + if path not in sys.path: + sys.path.insert(1, path) + +_add_path(_hooks) +_add_path(_root) + from charmhelpers.contrib.storage.linux.ceph import create_erasure_profile from charmhelpers.core.hookenv import action_get, log, action_fail @@ -29,7 +41,7 @@ def make_erasure_profile(): coding_chunks=m, failure_domain=failure_domain) except CalledProcessError as e: - log(e) + log(str(e)) action_fail("Create erasure profile failed with " "message: {}".format(e.message)) elif plugin == "isa": @@ -43,7 +55,7 @@ def make_erasure_profile(): coding_chunks=m, failure_domain=failure_domain) except CalledProcessError as e: - log(e) + log(str(e)) action_fail("Create erasure profile failed with " "message: {}".format(e.message)) elif plugin == "local": @@ -59,7 +71,7 @@ def make_erasure_profile(): locality=l, failure_domain=failure_domain) except CalledProcessError as e: - log(e) + log(str(e)) action_fail("Create erasure profile failed with " "message: {}".format(e.message)) elif plugin == "shec": @@ -75,7 +87,7 @@ def make_erasure_profile(): durability_estimator=c, failure_domain=failure_domain) except CalledProcessError as e: - log(e) + log(str(e)) action_fail("Create erasure profile failed with " "message: {}".format(e.message)) else: diff --git a/actions/create-pool b/actions/create-pool index 4d1d214..0dd0be3 100755 --- a/actions/create-pool +++ b/actions/create-pool @@ -1,7 +1,19 @@ -#!/usr/bin/python +#!/usr/bin/env python3 +import os import sys -sys.path.append('hooks') +_path = os.path.dirname(os.path.realpath(__file__)) +_hooks = os.path.abspath(os.path.join(_path, '../hooks')) +_root = os.path.abspath(os.path.join(_path, '..')) + + +def _add_path(path): + if path not in sys.path: + sys.path.insert(1, path) + +_add_path(_hooks) +_add_path(_root) + from subprocess import CalledProcessError from charmhelpers.core.hookenv import action_get, log, action_fail from charmhelpers.contrib.storage.linux.ceph import ErasurePool, ReplicatedPool @@ -31,7 +43,7 @@ def create_pool(): "is allowed".format(pool_type)) except CalledProcessError as e: action_fail("Pool creation failed because of a failed process. " - "Ret Code: {} Message: {}".format(e.returncode, e.message)) + "Ret Code: {} Message: {}".format(e.returncode, str(e))) if __name__ == '__main__': diff --git a/actions/delete-erasure-profile b/actions/delete-erasure-profile index 075c410..8651d07 100755 --- a/actions/delete-erasure-profile +++ b/actions/delete-erasure-profile @@ -1,10 +1,21 @@ -#!/usr/bin/python +#!/usr/bin/env python3 from subprocess import CalledProcessError __author__ = 'chris' +import os import sys -sys.path.append('hooks') +_path = os.path.dirname(os.path.realpath(__file__)) +_hooks = os.path.abspath(os.path.join(_path, '../hooks')) +_root = os.path.abspath(os.path.join(_path, '..')) + + +def _add_path(path): + if path not in sys.path: + sys.path.insert(1, path) + +_add_path(_hooks) +_add_path(_root) from charmhelpers.contrib.storage.linux.ceph import remove_erasure_profile from charmhelpers.core.hookenv import action_get, log, action_fail diff --git a/actions/delete-pool b/actions/delete-pool index 3d65507..68b89b2 100755 --- a/actions/delete-pool +++ b/actions/delete-pool @@ -1,7 +1,18 @@ -#!/usr/bin/python +#!/usr/bin/env python3 +import os import sys -sys.path.append('hooks') +_path = os.path.dirname(os.path.realpath(__file__)) +_hooks = os.path.abspath(os.path.join(_path, '../hooks')) +_root = os.path.abspath(os.path.join(_path, '..')) + + +def _add_path(path): + if path not in sys.path: + sys.path.insert(1, path) + +_add_path(_hooks) +_add_path(_root) import rados from ceph_ops import connect @@ -20,8 +31,8 @@ def remove_pool(): rados.NoData, rados.NoSpace, rados.PermissionError) as e: - log(e) - action_fail(e) + log(str(e)) + action_fail(str(e)) if __name__ == '__main__': diff --git a/actions/get-erasure-profile b/actions/get-erasure-profile index 29ece59..39947bb 100755 --- a/actions/get-erasure-profile +++ b/actions/get-erasure-profile @@ -1,8 +1,19 @@ -#!/usr/bin/python +#!/usr/bin/env python3 __author__ = 'chris' +import os import sys -sys.path.append('hooks') +_path = os.path.dirname(os.path.realpath(__file__)) +_hooks = os.path.abspath(os.path.join(_path, '../hooks')) +_root = os.path.abspath(os.path.join(_path, '..')) + + +def _add_path(path): + if path not in sys.path: + sys.path.insert(1, path) + +_add_path(_hooks) +_add_path(_root) from charmhelpers.contrib.storage.linux.ceph import get_erasure_profile from charmhelpers.core.hookenv import action_get, action_set diff --git a/actions/list-erasure-profiles b/actions/list-erasure-profiles index cf6dfa0..fd0586f 100755 --- a/actions/list-erasure-profiles +++ b/actions/list-erasure-profiles @@ -1,9 +1,20 @@ -#!/usr/bin/python +#!/usr/bin/env python3 __author__ = 'chris' -import sys +import os from subprocess import check_output, CalledProcessError +import sys -sys.path.append('hooks') +_path = os.path.dirname(os.path.realpath(__file__)) +_hooks = os.path.abspath(os.path.join(_path, '../hooks')) +_root = os.path.abspath(os.path.join(_path, '..')) + + +def _add_path(path): + if path not in sys.path: + sys.path.insert(1, path) + +_add_path(_hooks) +_add_path(_root) from charmhelpers.core.hookenv import action_get, log, action_set, action_fail @@ -17,6 +28,6 @@ if __name__ == '__main__': 'ls']).decode('UTF-8') action_set({'message': out}) except CalledProcessError as e: - log(e) + log(str(e)) action_fail("Listing erasure profiles failed with error: {}".format( - e.message)) + str(e))) diff --git a/actions/list-pools b/actions/list-pools index 102667c..67c1aed 100755 --- a/actions/list-pools +++ b/actions/list-pools @@ -1,9 +1,20 @@ -#!/usr/bin/python +#!/usr/bin/env python3 __author__ = 'chris' -import sys +import os from subprocess import check_output, CalledProcessError +import sys -sys.path.append('hooks') +_path = os.path.dirname(os.path.realpath(__file__)) +_hooks = os.path.abspath(os.path.join(_path, '../hooks')) +_root = os.path.abspath(os.path.join(_path, '..')) + + +def _add_path(path): + if path not in sys.path: + sys.path.insert(1, path) + +_add_path(_hooks) +_add_path(_root) from charmhelpers.core.hookenv import log, action_set, action_fail @@ -13,5 +24,5 @@ if __name__ == '__main__': 'osd', 'lspools']).decode('UTF-8') action_set({'message': out}) except CalledProcessError as e: - log(e) - action_fail("List pools failed with error: {}".format(e.message)) + log(str(e)) + action_fail("List pools failed with error: {}".format(str(e))) diff --git a/actions/pool-get b/actions/pool-get index e4f924b..3a42ab4 100755 --- a/actions/pool-get +++ b/actions/pool-get @@ -1,9 +1,20 @@ -#!/usr/bin/python +#!/usr/bin/env python3 __author__ = 'chris' -import sys +import os from subprocess import check_output, CalledProcessError +import sys -sys.path.append('hooks') +_path = os.path.dirname(os.path.realpath(__file__)) +_hooks = os.path.abspath(os.path.join(_path, '../hooks')) +_root = os.path.abspath(os.path.join(_path, '..')) + + +def _add_path(path): + if path not in sys.path: + sys.path.insert(1, path) + +_add_path(_hooks) +_add_path(_root) from charmhelpers.core.hookenv import log, action_set, action_get, action_fail @@ -15,5 +26,5 @@ if __name__ == '__main__': 'osd', 'pool', 'get', name, key]).decode('UTF-8') action_set({'message': out}) except CalledProcessError as e: - log(e) - action_fail("Pool get failed with message: {}".format(e.message)) + log(str(e)) + action_fail("Pool get failed with message: {}".format(str(e))) diff --git a/actions/pool-set b/actions/pool-set index 1f6e13b..8963c90 100755 --- a/actions/pool-set +++ b/actions/pool-set @@ -1,8 +1,19 @@ -#!/usr/bin/python +#!/usr/bin/env python3 +import os from subprocess import CalledProcessError import sys -sys.path.append('hooks') +_path = os.path.dirname(os.path.realpath(__file__)) +_hooks = os.path.abspath(os.path.join(_path, '../hooks')) +_root = os.path.abspath(os.path.join(_path, '..')) + + +def _add_path(path): + if path not in sys.path: + sys.path.insert(1, path) + +_add_path(_hooks) +_add_path(_root) from charmhelpers.core.hookenv import action_get, log, action_fail from ceph_broker import handle_set_pool_value @@ -18,6 +29,6 @@ if __name__ == '__main__': try: handle_set_pool_value(service='admin', request=request) except CalledProcessError as e: - log(e.message) + log(str(e)) action_fail("Setting pool key: {} and value: {} failed with " - "message: {}".format(key, value, e.message)) + "message: {}".format(key, value, str(e))) diff --git a/actions/pool-statistics b/actions/pool-statistics index 536c889..403267f 100755 --- a/actions/pool-statistics +++ b/actions/pool-statistics @@ -1,7 +1,19 @@ -#!/usr/bin/python +#!/usr/bin/env python3 +import os import sys -sys.path.append('hooks') +_path = os.path.dirname(os.path.realpath(__file__)) +_hooks = os.path.abspath(os.path.join(_path, '../hooks')) +_root = os.path.abspath(os.path.join(_path, '..')) + + +def _add_path(path): + if path not in sys.path: + sys.path.insert(1, path) + +_add_path(_hooks) +_add_path(_root) + from subprocess import check_output, CalledProcessError from charmhelpers.core.hookenv import log, action_set, action_fail @@ -11,5 +23,5 @@ if __name__ == '__main__': 'df']).decode('UTF-8') action_set({'message': out}) except CalledProcessError as e: - log(e) - action_fail("ceph df failed with message: {}".format(e.message)) + log(str(e)) + action_fail("ceph df failed with message: {}".format(str(e))) diff --git a/actions/remove-cache-tier b/actions/remove-cache-tier deleted file mode 120000 index 136c0f0..0000000 --- a/actions/remove-cache-tier +++ /dev/null @@ -1 +0,0 @@ -remove-cache-tier.py \ No newline at end of file diff --git a/actions/remove-cache-tier b/actions/remove-cache-tier new file mode 100755 index 0000000..79db9cf --- /dev/null +++ b/actions/remove-cache-tier @@ -0,0 +1,41 @@ +#!/usr/bin/python +from subprocess import CalledProcessError +import sys + +sys.path.append('hooks') + +from charmhelpers.contrib.storage.linux.ceph import Pool, pool_exists +from charmhelpers.core.hookenv import action_get, log, action_fail + +__author__ = 'chris' + + +def delete_cache_tier(): + backer_pool = action_get("backer-pool") + cache_pool = action_get("cache-pool") + + # Pre flight checks + if not pool_exists('admin', backer_pool): + log("Backer pool {} must exist before calling this".format( + backer_pool)) + action_fail("remove-cache-tier failed. Backer pool {} must exist " + "before calling this".format(backer_pool)) + + if not pool_exists('admin', cache_pool): + log("Cache pool {} must exist before calling this".format( + cache_pool)) + action_fail("remove-cache-tier failed. Cache pool {} must exist " + "before calling this".format(cache_pool)) + + pool = Pool(service='admin', name=backer_pool) + try: + pool.remove_cache_tier(cache_pool=cache_pool) + except CalledProcessError as err: + log("Removing the cache tier failed with message: {}".format( + err.message)) + action_fail("remove-cache-tier failed. Removing the cache tier failed " + "with message: {}".format(err.message)) + + +if __name__ == '__main__': + delete_cache_tier() diff --git a/actions/remove-cache-tier.py b/actions/remove-cache-tier.py index 79db9cf..8c9b937 100755 --- a/actions/remove-cache-tier.py +++ b/actions/remove-cache-tier.py @@ -1,8 +1,19 @@ -#!/usr/bin/python +#!/usr/bin/env python3 +import os from subprocess import CalledProcessError import sys -sys.path.append('hooks') +_path = os.path.dirname(os.path.realpath(__file__)) +_hooks = os.path.abspath(os.path.join(_path, '../hooks')) +_root = os.path.abspath(os.path.join(_path, '..')) + + +def _add_path(path): + if path not in sys.path: + sys.path.insert(1, path) + +_add_path(_hooks) +_add_path(_root) from charmhelpers.contrib.storage.linux.ceph import Pool, pool_exists from charmhelpers.core.hookenv import action_get, log, action_fail @@ -32,9 +43,9 @@ def delete_cache_tier(): pool.remove_cache_tier(cache_pool=cache_pool) except CalledProcessError as err: log("Removing the cache tier failed with message: {}".format( - err.message)) + str(err))) action_fail("remove-cache-tier failed. Removing the cache tier failed " - "with message: {}".format(err.message)) + "with message: {}".format(str(err))) if __name__ == '__main__': diff --git a/actions/remove-pool-snapshot b/actions/remove-pool-snapshot index 387849e..645ff07 100755 --- a/actions/remove-pool-snapshot +++ b/actions/remove-pool-snapshot @@ -1,7 +1,19 @@ -#!/usr/bin/python +#!/usr/bin/env python3 +import os import sys -sys.path.append('hooks') +_path = os.path.dirname(os.path.realpath(__file__)) +_hooks = os.path.abspath(os.path.join(_path, '../hooks')) +_root = os.path.abspath(os.path.join(_path, '..')) + + +def _add_path(path): + if path not in sys.path: + sys.path.insert(1, path) + +_add_path(_hooks) +_add_path(_root) + from subprocess import CalledProcessError from charmhelpers.core.hookenv import action_get, log, action_fail from charmhelpers.contrib.storage.linux.ceph import remove_pool_snapshot @@ -14,6 +26,6 @@ if __name__ == '__main__': pool_name=name, snapshot_name=snapname) except CalledProcessError as e: - log(e) + log(str(e)) action_fail("Remove pool snapshot failed with message: {}".format( - e.message)) + str(e))) diff --git a/actions/rename-pool b/actions/rename-pool index 6fe088e..3301830 100755 --- a/actions/rename-pool +++ b/actions/rename-pool @@ -1,7 +1,19 @@ -#!/usr/bin/python +#!/usr/bin/env python3 +import os import sys -sys.path.append('hooks') +_path = os.path.dirname(os.path.realpath(__file__)) +_hooks = os.path.abspath(os.path.join(_path, '../hooks')) +_root = os.path.abspath(os.path.join(_path, '..')) + + +def _add_path(path): + if path not in sys.path: + sys.path.insert(1, path) + +_add_path(_hooks) +_add_path(_root) + from subprocess import CalledProcessError from charmhelpers.core.hookenv import action_get, log, action_fail from charmhelpers.contrib.storage.linux.ceph import rename_pool @@ -12,5 +24,5 @@ if __name__ == '__main__': try: rename_pool(service='admin', old_name=name, new_name=new_name) except CalledProcessError as e: - log(e) - action_fail("Renaming pool failed with message: {}".format(e.message)) + log(str(e)) + action_fail("Renaming pool failed with message: {}".format(str(e))) diff --git a/actions/set-pool-max-bytes b/actions/set-pool-max-bytes index 8636088..c1550d4 100755 --- a/actions/set-pool-max-bytes +++ b/actions/set-pool-max-bytes @@ -1,7 +1,19 @@ -#!/usr/bin/python +#!/usr/bin/env python3 +import os import sys -sys.path.append('hooks') +_path = os.path.dirname(os.path.realpath(__file__)) +_hooks = os.path.abspath(os.path.join(_path, '../hooks')) +_root = os.path.abspath(os.path.join(_path, '..')) + + +def _add_path(path): + if path not in sys.path: + sys.path.insert(1, path) + +_add_path(_hooks) +_add_path(_root) + from subprocess import CalledProcessError from charmhelpers.core.hookenv import action_get, log, action_fail from charmhelpers.contrib.storage.linux.ceph import set_pool_quota @@ -12,5 +24,5 @@ if __name__ == '__main__': try: set_pool_quota(service='admin', pool_name=name, max_bytes=max_bytes) except CalledProcessError as e: - log(e) - action_fail("Set pool quota failed with message: {}".format(e.message)) + log(str(e)) + action_fail("Set pool quota failed with message: {}".format(str(e))) diff --git a/actions/snapshot-pool b/actions/snapshot-pool index a02619b..0191bcc 100755 --- a/actions/snapshot-pool +++ b/actions/snapshot-pool @@ -1,7 +1,19 @@ -#!/usr/bin/python +#!/usr/bin/env python3 +import os import sys -sys.path.append('hooks') +_path = os.path.dirname(os.path.realpath(__file__)) +_hooks = os.path.abspath(os.path.join(_path, '../hooks')) +_root = os.path.abspath(os.path.join(_path, '..')) + + +def _add_path(path): + if path not in sys.path: + sys.path.insert(1, path) + +_add_path(_hooks) +_add_path(_root) + from subprocess import CalledProcessError from charmhelpers.core.hookenv import action_get, log, action_fail from charmhelpers.contrib.storage.linux.ceph import snapshot_pool @@ -14,5 +26,5 @@ if __name__ == '__main__': pool_name=name, snapshot_name=snapname) except CalledProcessError as e: - log(e) - action_fail("Snapshot pool failed with message: {}".format(e.message)) + log(str(e)) + action_fail("Snapshot pool failed with message: {}".format(str(e))) diff --git a/charm-helpers-hooks.yaml b/charm-helpers-hooks.yaml index e4767c5..8f484eb 100644 --- a/charm-helpers-hooks.yaml +++ b/charm-helpers-hooks.yaml @@ -1,5 +1,5 @@ repo: https://github.com/juju/charm-helpers -destination: hooks/charmhelpers +destination: charmhelpers include: - core - cli diff --git a/hooks/charmhelpers/__init__.py b/charmhelpers/__init__.py similarity index 100% rename from hooks/charmhelpers/__init__.py rename to charmhelpers/__init__.py diff --git a/hooks/charmhelpers/cli/__init__.py b/charmhelpers/cli/__init__.py similarity index 100% rename from hooks/charmhelpers/cli/__init__.py rename to charmhelpers/cli/__init__.py diff --git a/hooks/charmhelpers/cli/benchmark.py b/charmhelpers/cli/benchmark.py similarity index 100% rename from hooks/charmhelpers/cli/benchmark.py rename to charmhelpers/cli/benchmark.py diff --git a/hooks/charmhelpers/cli/commands.py b/charmhelpers/cli/commands.py similarity index 100% rename from hooks/charmhelpers/cli/commands.py rename to charmhelpers/cli/commands.py diff --git a/hooks/charmhelpers/cli/hookenv.py b/charmhelpers/cli/hookenv.py similarity index 100% rename from hooks/charmhelpers/cli/hookenv.py rename to charmhelpers/cli/hookenv.py diff --git a/hooks/charmhelpers/cli/host.py b/charmhelpers/cli/host.py similarity index 100% rename from hooks/charmhelpers/cli/host.py rename to charmhelpers/cli/host.py diff --git a/hooks/charmhelpers/cli/unitdata.py b/charmhelpers/cli/unitdata.py similarity index 80% rename from hooks/charmhelpers/cli/unitdata.py rename to charmhelpers/cli/unitdata.py index c572858..acce846 100644 --- a/hooks/charmhelpers/cli/unitdata.py +++ b/charmhelpers/cli/unitdata.py @@ -19,9 +19,16 @@ from charmhelpers.core import unitdata @cmdline.subcommand_builder('unitdata', description="Store and retrieve data") def unitdata_cmd(subparser): nested = subparser.add_subparsers() + get_cmd = nested.add_parser('get', help='Retrieve data') get_cmd.add_argument('key', help='Key to retrieve the value of') get_cmd.set_defaults(action='get', value=None) + + getrange_cmd = nested.add_parser('getrange', help='Retrieve multiple data') + getrange_cmd.add_argument('key', metavar='prefix', + help='Prefix of the keys to retrieve') + getrange_cmd.set_defaults(action='getrange', value=None) + set_cmd = nested.add_parser('set', help='Store data') set_cmd.add_argument('key', help='Key to set') set_cmd.add_argument('value', help='Value to store') @@ -30,6 +37,8 @@ def unitdata_cmd(subparser): def _unitdata_cmd(action, key, value): if action == 'get': return unitdata.kv().get(key) + elif action == 'getrange': + return unitdata.kv().getrange(key) elif action == 'set': unitdata.kv().set(key, value) unitdata.kv().flush() diff --git a/hooks/charmhelpers/contrib/__init__.py b/charmhelpers/contrib/__init__.py similarity index 100% rename from hooks/charmhelpers/contrib/__init__.py rename to charmhelpers/contrib/__init__.py diff --git a/hooks/charmhelpers/contrib/charmsupport/__init__.py b/charmhelpers/contrib/charmsupport/__init__.py similarity index 100% rename from hooks/charmhelpers/contrib/charmsupport/__init__.py rename to charmhelpers/contrib/charmsupport/__init__.py diff --git a/hooks/charmhelpers/contrib/charmsupport/nrpe.py b/charmhelpers/contrib/charmsupport/nrpe.py similarity index 100% rename from hooks/charmhelpers/contrib/charmsupport/nrpe.py rename to charmhelpers/contrib/charmsupport/nrpe.py diff --git a/hooks/charmhelpers/contrib/charmsupport/volumes.py b/charmhelpers/contrib/charmsupport/volumes.py similarity index 100% rename from hooks/charmhelpers/contrib/charmsupport/volumes.py rename to charmhelpers/contrib/charmsupport/volumes.py diff --git a/hooks/charmhelpers/contrib/hardening/README.hardening.md b/charmhelpers/contrib/hardening/README.hardening.md similarity index 100% rename from hooks/charmhelpers/contrib/hardening/README.hardening.md rename to charmhelpers/contrib/hardening/README.hardening.md diff --git a/hooks/charmhelpers/contrib/hardening/__init__.py b/charmhelpers/contrib/hardening/__init__.py similarity index 100% rename from hooks/charmhelpers/contrib/hardening/__init__.py rename to charmhelpers/contrib/hardening/__init__.py diff --git a/hooks/charmhelpers/contrib/hardening/apache/__init__.py b/charmhelpers/contrib/hardening/apache/__init__.py similarity index 100% rename from hooks/charmhelpers/contrib/hardening/apache/__init__.py rename to charmhelpers/contrib/hardening/apache/__init__.py diff --git a/hooks/charmhelpers/contrib/hardening/apache/checks/__init__.py b/charmhelpers/contrib/hardening/apache/checks/__init__.py similarity index 100% rename from hooks/charmhelpers/contrib/hardening/apache/checks/__init__.py rename to charmhelpers/contrib/hardening/apache/checks/__init__.py diff --git a/hooks/charmhelpers/contrib/hardening/apache/checks/config.py b/charmhelpers/contrib/hardening/apache/checks/config.py similarity index 100% rename from hooks/charmhelpers/contrib/hardening/apache/checks/config.py rename to charmhelpers/contrib/hardening/apache/checks/config.py diff --git a/hooks/charmhelpers/contrib/hardening/apache/templates/99-hardening.conf b/charmhelpers/contrib/hardening/apache/templates/99-hardening.conf similarity index 100% rename from hooks/charmhelpers/contrib/hardening/apache/templates/99-hardening.conf rename to charmhelpers/contrib/hardening/apache/templates/99-hardening.conf diff --git a/hooks/charmhelpers/contrib/hardening/apache/templates/__init__.py b/charmhelpers/contrib/hardening/apache/templates/__init__.py similarity index 100% rename from hooks/charmhelpers/contrib/hardening/apache/templates/__init__.py rename to charmhelpers/contrib/hardening/apache/templates/__init__.py diff --git a/hooks/charmhelpers/contrib/hardening/apache/templates/alias.conf b/charmhelpers/contrib/hardening/apache/templates/alias.conf similarity index 100% rename from hooks/charmhelpers/contrib/hardening/apache/templates/alias.conf rename to charmhelpers/contrib/hardening/apache/templates/alias.conf diff --git a/hooks/charmhelpers/contrib/hardening/audits/__init__.py b/charmhelpers/contrib/hardening/audits/__init__.py similarity index 100% rename from hooks/charmhelpers/contrib/hardening/audits/__init__.py rename to charmhelpers/contrib/hardening/audits/__init__.py diff --git a/hooks/charmhelpers/contrib/hardening/audits/apache.py b/charmhelpers/contrib/hardening/audits/apache.py similarity index 100% rename from hooks/charmhelpers/contrib/hardening/audits/apache.py rename to charmhelpers/contrib/hardening/audits/apache.py diff --git a/hooks/charmhelpers/contrib/hardening/audits/apt.py b/charmhelpers/contrib/hardening/audits/apt.py similarity index 100% rename from hooks/charmhelpers/contrib/hardening/audits/apt.py rename to charmhelpers/contrib/hardening/audits/apt.py diff --git a/hooks/charmhelpers/contrib/hardening/audits/file.py b/charmhelpers/contrib/hardening/audits/file.py similarity index 100% rename from hooks/charmhelpers/contrib/hardening/audits/file.py rename to charmhelpers/contrib/hardening/audits/file.py diff --git a/hooks/charmhelpers/contrib/hardening/defaults/__init__.py b/charmhelpers/contrib/hardening/defaults/__init__.py similarity index 100% rename from hooks/charmhelpers/contrib/hardening/defaults/__init__.py rename to charmhelpers/contrib/hardening/defaults/__init__.py diff --git a/hooks/charmhelpers/contrib/hardening/defaults/apache.yaml b/charmhelpers/contrib/hardening/defaults/apache.yaml similarity index 100% rename from hooks/charmhelpers/contrib/hardening/defaults/apache.yaml rename to charmhelpers/contrib/hardening/defaults/apache.yaml diff --git a/hooks/charmhelpers/contrib/hardening/defaults/apache.yaml.schema b/charmhelpers/contrib/hardening/defaults/apache.yaml.schema similarity index 100% rename from hooks/charmhelpers/contrib/hardening/defaults/apache.yaml.schema rename to charmhelpers/contrib/hardening/defaults/apache.yaml.schema diff --git a/hooks/charmhelpers/contrib/hardening/defaults/mysql.yaml b/charmhelpers/contrib/hardening/defaults/mysql.yaml similarity index 100% rename from hooks/charmhelpers/contrib/hardening/defaults/mysql.yaml rename to charmhelpers/contrib/hardening/defaults/mysql.yaml diff --git a/hooks/charmhelpers/contrib/hardening/defaults/mysql.yaml.schema b/charmhelpers/contrib/hardening/defaults/mysql.yaml.schema similarity index 100% rename from hooks/charmhelpers/contrib/hardening/defaults/mysql.yaml.schema rename to charmhelpers/contrib/hardening/defaults/mysql.yaml.schema diff --git a/hooks/charmhelpers/contrib/hardening/defaults/os.yaml b/charmhelpers/contrib/hardening/defaults/os.yaml similarity index 100% rename from hooks/charmhelpers/contrib/hardening/defaults/os.yaml rename to charmhelpers/contrib/hardening/defaults/os.yaml diff --git a/hooks/charmhelpers/contrib/hardening/defaults/os.yaml.schema b/charmhelpers/contrib/hardening/defaults/os.yaml.schema similarity index 100% rename from hooks/charmhelpers/contrib/hardening/defaults/os.yaml.schema rename to charmhelpers/contrib/hardening/defaults/os.yaml.schema diff --git a/hooks/charmhelpers/contrib/hardening/defaults/ssh.yaml b/charmhelpers/contrib/hardening/defaults/ssh.yaml similarity index 100% rename from hooks/charmhelpers/contrib/hardening/defaults/ssh.yaml rename to charmhelpers/contrib/hardening/defaults/ssh.yaml diff --git a/hooks/charmhelpers/contrib/hardening/defaults/ssh.yaml.schema b/charmhelpers/contrib/hardening/defaults/ssh.yaml.schema similarity index 100% rename from hooks/charmhelpers/contrib/hardening/defaults/ssh.yaml.schema rename to charmhelpers/contrib/hardening/defaults/ssh.yaml.schema diff --git a/hooks/charmhelpers/contrib/hardening/harden.py b/charmhelpers/contrib/hardening/harden.py similarity index 100% rename from hooks/charmhelpers/contrib/hardening/harden.py rename to charmhelpers/contrib/hardening/harden.py diff --git a/hooks/charmhelpers/contrib/hardening/host/__init__.py b/charmhelpers/contrib/hardening/host/__init__.py similarity index 100% rename from hooks/charmhelpers/contrib/hardening/host/__init__.py rename to charmhelpers/contrib/hardening/host/__init__.py diff --git a/hooks/charmhelpers/contrib/hardening/host/checks/__init__.py b/charmhelpers/contrib/hardening/host/checks/__init__.py similarity index 100% rename from hooks/charmhelpers/contrib/hardening/host/checks/__init__.py rename to charmhelpers/contrib/hardening/host/checks/__init__.py diff --git a/hooks/charmhelpers/contrib/hardening/host/checks/apt.py b/charmhelpers/contrib/hardening/host/checks/apt.py similarity index 100% rename from hooks/charmhelpers/contrib/hardening/host/checks/apt.py rename to charmhelpers/contrib/hardening/host/checks/apt.py diff --git a/hooks/charmhelpers/contrib/hardening/host/checks/limits.py b/charmhelpers/contrib/hardening/host/checks/limits.py similarity index 100% rename from hooks/charmhelpers/contrib/hardening/host/checks/limits.py rename to charmhelpers/contrib/hardening/host/checks/limits.py diff --git a/hooks/charmhelpers/contrib/hardening/host/checks/login.py b/charmhelpers/contrib/hardening/host/checks/login.py similarity index 100% rename from hooks/charmhelpers/contrib/hardening/host/checks/login.py rename to charmhelpers/contrib/hardening/host/checks/login.py diff --git a/hooks/charmhelpers/contrib/hardening/host/checks/minimize_access.py b/charmhelpers/contrib/hardening/host/checks/minimize_access.py similarity index 100% rename from hooks/charmhelpers/contrib/hardening/host/checks/minimize_access.py rename to charmhelpers/contrib/hardening/host/checks/minimize_access.py diff --git a/hooks/charmhelpers/contrib/hardening/host/checks/pam.py b/charmhelpers/contrib/hardening/host/checks/pam.py similarity index 100% rename from hooks/charmhelpers/contrib/hardening/host/checks/pam.py rename to charmhelpers/contrib/hardening/host/checks/pam.py diff --git a/hooks/charmhelpers/contrib/hardening/host/checks/profile.py b/charmhelpers/contrib/hardening/host/checks/profile.py similarity index 100% rename from hooks/charmhelpers/contrib/hardening/host/checks/profile.py rename to charmhelpers/contrib/hardening/host/checks/profile.py diff --git a/hooks/charmhelpers/contrib/hardening/host/checks/securetty.py b/charmhelpers/contrib/hardening/host/checks/securetty.py similarity index 100% rename from hooks/charmhelpers/contrib/hardening/host/checks/securetty.py rename to charmhelpers/contrib/hardening/host/checks/securetty.py diff --git a/hooks/charmhelpers/contrib/hardening/host/checks/suid_sgid.py b/charmhelpers/contrib/hardening/host/checks/suid_sgid.py similarity index 100% rename from hooks/charmhelpers/contrib/hardening/host/checks/suid_sgid.py rename to charmhelpers/contrib/hardening/host/checks/suid_sgid.py diff --git a/hooks/charmhelpers/contrib/hardening/host/checks/sysctl.py b/charmhelpers/contrib/hardening/host/checks/sysctl.py similarity index 100% rename from hooks/charmhelpers/contrib/hardening/host/checks/sysctl.py rename to charmhelpers/contrib/hardening/host/checks/sysctl.py diff --git a/hooks/charmhelpers/contrib/hardening/host/templates/10.hardcore.conf b/charmhelpers/contrib/hardening/host/templates/10.hardcore.conf similarity index 100% rename from hooks/charmhelpers/contrib/hardening/host/templates/10.hardcore.conf rename to charmhelpers/contrib/hardening/host/templates/10.hardcore.conf diff --git a/hooks/charmhelpers/contrib/hardening/host/templates/99-hardening.sh b/charmhelpers/contrib/hardening/host/templates/99-hardening.sh similarity index 100% rename from hooks/charmhelpers/contrib/hardening/host/templates/99-hardening.sh rename to charmhelpers/contrib/hardening/host/templates/99-hardening.sh diff --git a/hooks/charmhelpers/contrib/hardening/host/templates/99-juju-hardening.conf b/charmhelpers/contrib/hardening/host/templates/99-juju-hardening.conf similarity index 100% rename from hooks/charmhelpers/contrib/hardening/host/templates/99-juju-hardening.conf rename to charmhelpers/contrib/hardening/host/templates/99-juju-hardening.conf diff --git a/hooks/charmhelpers/contrib/hardening/host/templates/__init__.py b/charmhelpers/contrib/hardening/host/templates/__init__.py similarity index 100% rename from hooks/charmhelpers/contrib/hardening/host/templates/__init__.py rename to charmhelpers/contrib/hardening/host/templates/__init__.py diff --git a/hooks/charmhelpers/contrib/hardening/host/templates/login.defs b/charmhelpers/contrib/hardening/host/templates/login.defs similarity index 100% rename from hooks/charmhelpers/contrib/hardening/host/templates/login.defs rename to charmhelpers/contrib/hardening/host/templates/login.defs diff --git a/hooks/charmhelpers/contrib/hardening/host/templates/modules b/charmhelpers/contrib/hardening/host/templates/modules similarity index 100% rename from hooks/charmhelpers/contrib/hardening/host/templates/modules rename to charmhelpers/contrib/hardening/host/templates/modules diff --git a/hooks/charmhelpers/contrib/hardening/host/templates/passwdqc.conf b/charmhelpers/contrib/hardening/host/templates/passwdqc.conf similarity index 100% rename from hooks/charmhelpers/contrib/hardening/host/templates/passwdqc.conf rename to charmhelpers/contrib/hardening/host/templates/passwdqc.conf diff --git a/hooks/charmhelpers/contrib/hardening/host/templates/pinerolo_profile.sh b/charmhelpers/contrib/hardening/host/templates/pinerolo_profile.sh similarity index 100% rename from hooks/charmhelpers/contrib/hardening/host/templates/pinerolo_profile.sh rename to charmhelpers/contrib/hardening/host/templates/pinerolo_profile.sh diff --git a/hooks/charmhelpers/contrib/hardening/host/templates/securetty b/charmhelpers/contrib/hardening/host/templates/securetty similarity index 100% rename from hooks/charmhelpers/contrib/hardening/host/templates/securetty rename to charmhelpers/contrib/hardening/host/templates/securetty diff --git a/hooks/charmhelpers/contrib/hardening/host/templates/tally2 b/charmhelpers/contrib/hardening/host/templates/tally2 similarity index 100% rename from hooks/charmhelpers/contrib/hardening/host/templates/tally2 rename to charmhelpers/contrib/hardening/host/templates/tally2 diff --git a/hooks/charmhelpers/contrib/hardening/mysql/__init__.py b/charmhelpers/contrib/hardening/mysql/__init__.py similarity index 100% rename from hooks/charmhelpers/contrib/hardening/mysql/__init__.py rename to charmhelpers/contrib/hardening/mysql/__init__.py diff --git a/hooks/charmhelpers/contrib/hardening/mysql/checks/__init__.py b/charmhelpers/contrib/hardening/mysql/checks/__init__.py similarity index 100% rename from hooks/charmhelpers/contrib/hardening/mysql/checks/__init__.py rename to charmhelpers/contrib/hardening/mysql/checks/__init__.py diff --git a/hooks/charmhelpers/contrib/hardening/mysql/checks/config.py b/charmhelpers/contrib/hardening/mysql/checks/config.py similarity index 100% rename from hooks/charmhelpers/contrib/hardening/mysql/checks/config.py rename to charmhelpers/contrib/hardening/mysql/checks/config.py diff --git a/hooks/charmhelpers/contrib/hardening/mysql/templates/__init__.py b/charmhelpers/contrib/hardening/mysql/templates/__init__.py similarity index 100% rename from hooks/charmhelpers/contrib/hardening/mysql/templates/__init__.py rename to charmhelpers/contrib/hardening/mysql/templates/__init__.py diff --git a/hooks/charmhelpers/contrib/hardening/mysql/templates/hardening.cnf b/charmhelpers/contrib/hardening/mysql/templates/hardening.cnf similarity index 100% rename from hooks/charmhelpers/contrib/hardening/mysql/templates/hardening.cnf rename to charmhelpers/contrib/hardening/mysql/templates/hardening.cnf diff --git a/hooks/charmhelpers/contrib/hardening/ssh/__init__.py b/charmhelpers/contrib/hardening/ssh/__init__.py similarity index 100% rename from hooks/charmhelpers/contrib/hardening/ssh/__init__.py rename to charmhelpers/contrib/hardening/ssh/__init__.py diff --git a/hooks/charmhelpers/contrib/hardening/ssh/checks/__init__.py b/charmhelpers/contrib/hardening/ssh/checks/__init__.py similarity index 100% rename from hooks/charmhelpers/contrib/hardening/ssh/checks/__init__.py rename to charmhelpers/contrib/hardening/ssh/checks/__init__.py diff --git a/hooks/charmhelpers/contrib/hardening/ssh/checks/config.py b/charmhelpers/contrib/hardening/ssh/checks/config.py similarity index 100% rename from hooks/charmhelpers/contrib/hardening/ssh/checks/config.py rename to charmhelpers/contrib/hardening/ssh/checks/config.py diff --git a/hooks/charmhelpers/contrib/hardening/ssh/templates/__init__.py b/charmhelpers/contrib/hardening/ssh/templates/__init__.py similarity index 100% rename from hooks/charmhelpers/contrib/hardening/ssh/templates/__init__.py rename to charmhelpers/contrib/hardening/ssh/templates/__init__.py diff --git a/hooks/charmhelpers/contrib/hardening/ssh/templates/ssh_config b/charmhelpers/contrib/hardening/ssh/templates/ssh_config similarity index 100% rename from hooks/charmhelpers/contrib/hardening/ssh/templates/ssh_config rename to charmhelpers/contrib/hardening/ssh/templates/ssh_config diff --git a/hooks/charmhelpers/contrib/hardening/ssh/templates/sshd_config b/charmhelpers/contrib/hardening/ssh/templates/sshd_config similarity index 100% rename from hooks/charmhelpers/contrib/hardening/ssh/templates/sshd_config rename to charmhelpers/contrib/hardening/ssh/templates/sshd_config diff --git a/hooks/charmhelpers/contrib/hardening/templating.py b/charmhelpers/contrib/hardening/templating.py similarity index 100% rename from hooks/charmhelpers/contrib/hardening/templating.py rename to charmhelpers/contrib/hardening/templating.py diff --git a/hooks/charmhelpers/contrib/hardening/utils.py b/charmhelpers/contrib/hardening/utils.py similarity index 100% rename from hooks/charmhelpers/contrib/hardening/utils.py rename to charmhelpers/contrib/hardening/utils.py diff --git a/hooks/charmhelpers/contrib/network/__init__.py b/charmhelpers/contrib/network/__init__.py similarity index 100% rename from hooks/charmhelpers/contrib/network/__init__.py rename to charmhelpers/contrib/network/__init__.py diff --git a/hooks/charmhelpers/contrib/network/ip.py b/charmhelpers/contrib/network/ip.py similarity index 100% rename from hooks/charmhelpers/contrib/network/ip.py rename to charmhelpers/contrib/network/ip.py diff --git a/hooks/charmhelpers/contrib/openstack/__init__.py b/charmhelpers/contrib/openstack/__init__.py similarity index 100% rename from hooks/charmhelpers/contrib/openstack/__init__.py rename to charmhelpers/contrib/openstack/__init__.py diff --git a/hooks/charmhelpers/contrib/openstack/alternatives.py b/charmhelpers/contrib/openstack/alternatives.py similarity index 100% rename from hooks/charmhelpers/contrib/openstack/alternatives.py rename to charmhelpers/contrib/openstack/alternatives.py diff --git a/hooks/charmhelpers/contrib/openstack/exceptions.py b/charmhelpers/contrib/openstack/exceptions.py similarity index 100% rename from hooks/charmhelpers/contrib/openstack/exceptions.py rename to charmhelpers/contrib/openstack/exceptions.py diff --git a/hooks/charmhelpers/contrib/openstack/utils.py b/charmhelpers/contrib/openstack/utils.py similarity index 99% rename from hooks/charmhelpers/contrib/openstack/utils.py rename to charmhelpers/contrib/openstack/utils.py index 86b011b..e5e2536 100644 --- a/hooks/charmhelpers/contrib/openstack/utils.py +++ b/charmhelpers/contrib/openstack/utils.py @@ -194,7 +194,7 @@ SWIFT_CODENAMES = OrderedDict([ ('rocky', ['2.18.0', '2.19.0']), ('stein', - ['2.19.0']), + ['2.20.0']), ]) # >= Liberty version->codename mapping @@ -656,7 +656,7 @@ def openstack_upgrade_available(package): else: avail_vers = get_os_version_install_source(src) apt.init() - return apt.version_compare(avail_vers, cur_vers) == 1 + return apt.version_compare(avail_vers, cur_vers) >= 1 def ensure_block_device(block_device): diff --git a/hooks/charmhelpers/contrib/python.py b/charmhelpers/contrib/python.py similarity index 100% rename from hooks/charmhelpers/contrib/python.py rename to charmhelpers/contrib/python.py diff --git a/hooks/charmhelpers/contrib/storage/__init__.py b/charmhelpers/contrib/storage/__init__.py similarity index 100% rename from hooks/charmhelpers/contrib/storage/__init__.py rename to charmhelpers/contrib/storage/__init__.py diff --git a/hooks/charmhelpers/contrib/storage/linux/__init__.py b/charmhelpers/contrib/storage/linux/__init__.py similarity index 100% rename from hooks/charmhelpers/contrib/storage/linux/__init__.py rename to charmhelpers/contrib/storage/linux/__init__.py diff --git a/hooks/charmhelpers/contrib/storage/linux/ceph.py b/charmhelpers/contrib/storage/linux/ceph.py similarity index 89% rename from hooks/charmhelpers/contrib/storage/linux/ceph.py rename to charmhelpers/contrib/storage/linux/ceph.py index 63c9304..2c62092 100644 --- a/hooks/charmhelpers/contrib/storage/linux/ceph.py +++ b/charmhelpers/contrib/storage/linux/ceph.py @@ -59,6 +59,7 @@ from charmhelpers.core.host import ( service_stop, service_running, umount, + cmp_pkgrevno, ) from charmhelpers.fetch import ( apt_install, @@ -178,7 +179,6 @@ class Pool(object): """ # read-only is easy, writeback is much harder mode = get_cache_mode(self.service, cache_pool) - version = ceph_version() if mode == 'readonly': check_call(['ceph', '--id', self.service, 'osd', 'tier', 'cache-mode', cache_pool, 'none']) check_call(['ceph', '--id', self.service, 'osd', 'tier', 'remove', self.name, cache_pool]) @@ -186,7 +186,7 @@ class Pool(object): elif mode == 'writeback': pool_forward_cmd = ['ceph', '--id', self.service, 'osd', 'tier', 'cache-mode', cache_pool, 'forward'] - if version >= '10.1': + if cmp_pkgrevno('ceph-common', '10.1') >= 0: # Jewel added a mandatory flag pool_forward_cmd.append('--yes-i-really-mean-it') @@ -196,7 +196,8 @@ class Pool(object): check_call(['ceph', '--id', self.service, 'osd', 'tier', 'remove-overlay', self.name]) check_call(['ceph', '--id', self.service, 'osd', 'tier', 'remove', self.name, cache_pool]) - def get_pgs(self, pool_size, percent_data=DEFAULT_POOL_WEIGHT): + def get_pgs(self, pool_size, percent_data=DEFAULT_POOL_WEIGHT, + device_class=None): """Return the number of placement groups to use when creating the pool. Returns the number of placement groups which should be specified when @@ -229,6 +230,9 @@ class Pool(object): increased. NOTE: the default is primarily to handle the scenario where related charms requiring pools has not been upgraded to include an update to indicate their relative usage of the pools. + :param device_class: str. class of storage to use for basis of pgs + calculation; ceph supports nvme, ssd and hdd by default based + on presence of devices of each type in the deployment. :return: int. The number of pgs to use. """ @@ -243,17 +247,20 @@ class Pool(object): # If the expected-osd-count is specified, then use the max between # the expected-osd-count and the actual osd_count - osd_list = get_osds(self.service) + osd_list = get_osds(self.service, device_class) expected = config('expected-osd-count') or 0 if osd_list: - osd_count = max(expected, len(osd_list)) + if device_class: + osd_count = len(osd_list) + else: + osd_count = max(expected, len(osd_list)) # Log a message to provide some insight if the calculations claim # to be off because someone is setting the expected count and # there are more OSDs in reality. Try to make a proper guess # based upon the cluster itself. - if expected and osd_count != expected: + if not device_class and expected and osd_count != expected: log("Found more OSDs than provided expected count. " "Using the actual count instead", INFO) elif expected: @@ -575,21 +582,24 @@ def remove_pool_snapshot(service, pool_name, snapshot_name): raise -# max_bytes should be an int or long -def set_pool_quota(service, pool_name, max_bytes): +def set_pool_quota(service, pool_name, max_bytes=None, max_objects=None): """ - :param service: six.string_types. The Ceph user name to run the command under - :param pool_name: six.string_types - :param max_bytes: int or long - :return: None. Can raise CalledProcessError + :param service: The Ceph user name to run the command under + :type service: str + :param pool_name: Name of pool + :type pool_name: str + :param max_bytes: Maximum bytes quota to apply + :type max_bytes: int + :param max_objects: Maximum objects quota to apply + :type max_objects: int + :raises: subprocess.CalledProcessError """ - # Set a byte quota on a RADOS pool in ceph. - cmd = ['ceph', '--id', service, 'osd', 'pool', 'set-quota', pool_name, - 'max_bytes', str(max_bytes)] - try: - check_call(cmd) - except CalledProcessError: - raise + cmd = ['ceph', '--id', service, 'osd', 'pool', 'set-quota', pool_name] + if max_bytes: + cmd = cmd + ['max_bytes', str(max_bytes)] + if max_objects: + cmd = cmd + ['max_objects', str(max_objects)] + check_call(cmd) def remove_pool_quota(service, pool_name): @@ -626,7 +636,8 @@ def remove_erasure_profile(service, profile_name): def create_erasure_profile(service, profile_name, erasure_plugin_name='jerasure', failure_domain='host', data_chunks=2, coding_chunks=1, - locality=None, durability_estimator=None): + locality=None, durability_estimator=None, + device_class=None): """ Create a new erasure code profile if one does not already exist for it. Updates the profile if it exists. Please see http://docs.ceph.com/docs/master/rados/operations/erasure-code-profile/ @@ -640,10 +651,9 @@ def create_erasure_profile(service, profile_name, erasure_plugin_name='jerasure' :param coding_chunks: int :param locality: int :param durability_estimator: int + :param device_class: six.string_types :return: None. Can raise CalledProcessError """ - version = ceph_version() - # Ensure this failure_domain is allowed by Ceph validator(failure_domain, six.string_types, ['chassis', 'datacenter', 'host', 'osd', 'pdu', 'pod', 'rack', 'region', 'room', 'root', 'row']) @@ -654,12 +664,20 @@ def create_erasure_profile(service, profile_name, erasure_plugin_name='jerasure' if locality is not None and durability_estimator is not None: raise ValueError("create_erasure_profile should be called with k, m and one of l or c but not both.") + luminous_or_later = cmp_pkgrevno('ceph-common', '12.0.0') >= 0 # failure_domain changed in luminous - if version and version >= '12.0.0': + if luminous_or_later: cmd.append('crush-failure-domain=' + failure_domain) else: cmd.append('ruleset-failure-domain=' + failure_domain) + # device class new in luminous + if luminous_or_later and device_class: + cmd.append('crush-device-class={}'.format(device_class)) + else: + log('Skipping device class configuration (ceph < 12.0.0)', + level=DEBUG) + # Add plugin specific information if locality is not None: # For local erasure codes @@ -744,20 +762,26 @@ def pool_exists(service, name): return name in out.split() -def get_osds(service): +def get_osds(service, device_class=None): """Return a list of all Ceph Object Storage Daemons currently in the - cluster. + cluster (optionally filtered by storage device class). + + :param device_class: Class of storage device for OSD's + :type device_class: str """ - version = ceph_version() - if version and version >= '0.56': + luminous_or_later = cmp_pkgrevno('ceph-common', '12.0.0') >= 0 + if luminous_or_later and device_class: + out = check_output(['ceph', '--id', service, + 'osd', 'crush', 'class', + 'ls-osd', device_class, + '--format=json']) + else: out = check_output(['ceph', '--id', service, 'osd', 'ls', '--format=json']) - if six.PY3: - out = out.decode('UTF-8') - return json.loads(out) - - return None + if six.PY3: + out = out.decode('UTF-8') + return json.loads(out) def install(): @@ -811,7 +835,7 @@ def set_app_name_for_pool(client, pool, name): :raises: CalledProcessError if ceph call fails """ - if ceph_version() >= '12.0.0': + if cmp_pkgrevno('ceph-common', '12.0.0') >= 0: cmd = ['ceph', '--id', client, 'osd', 'pool', 'application', 'enable', pool, name] check_call(cmd) @@ -1091,22 +1115,6 @@ def ensure_ceph_keyring(service, user=None, group=None, return True -def ceph_version(): - """Retrieve the local version of ceph.""" - if os.path.exists('/usr/bin/ceph'): - cmd = ['ceph', '-v'] - output = check_output(cmd) - if six.PY3: - output = output.decode('UTF-8') - output = output.split() - if len(output) > 3: - return output[2] - else: - return None - else: - return None - - class CephBrokerRq(object): """Ceph broker request. @@ -1147,14 +1155,47 @@ class CephBrokerRq(object): 'object-prefix-permissions': object_prefix_permissions}) def add_op_create_pool(self, name, replica_count=3, pg_num=None, - weight=None, group=None, namespace=None): - """Adds an operation to create a pool. + weight=None, group=None, namespace=None, + app_name=None, max_bytes=None, max_objects=None): + """DEPRECATED: Use ``add_op_create_replicated_pool()`` or + ``add_op_create_erasure_pool()`` instead. + """ + return self.add_op_create_replicated_pool( + name, replica_count=replica_count, pg_num=pg_num, weight=weight, + group=group, namespace=namespace, app_name=app_name, + max_bytes=max_bytes, max_objects=max_objects) - @param pg_num setting: optional setting. If not provided, this value - will be calculated by the broker based on how many OSDs are in the - cluster at the time of creation. Note that, if provided, this value - will be capped at the current available maximum. - @param weight: the percentage of data the pool makes up + def add_op_create_replicated_pool(self, name, replica_count=3, pg_num=None, + weight=None, group=None, namespace=None, + app_name=None, max_bytes=None, + max_objects=None): + """Adds an operation to create a replicated pool. + + :param name: Name of pool to create + :type name: str + :param replica_count: Number of copies Ceph should keep of your data. + :type replica_count: int + :param pg_num: Request specific number of Placement Groups to create + for pool. + :type pg_num: int + :param weight: The percentage of data that is expected to be contained + in the pool from the total available space on the OSDs. + Used to calculate number of Placement Groups to create + for pool. + :type weight: float + :param group: Group to add pool to + :type group: str + :param namespace: Group namespace + :type namespace: str + :param app_name: (Optional) Tag pool with application name. Note that + there is certain protocols emerging upstream with + regard to meaningful application names to use. + Examples are ``rbd`` and ``rgw``. + :type app_name: str + :param max_bytes: Maximum bytes quota to apply + :type max_bytes: int + :param max_objects: Maximum objects quota to apply + :type max_objects: int """ if pg_num and weight: raise ValueError('pg_num and weight are mutually exclusive') @@ -1162,7 +1203,41 @@ class CephBrokerRq(object): self.ops.append({'op': 'create-pool', 'name': name, 'replicas': replica_count, 'pg_num': pg_num, 'weight': weight, 'group': group, - 'group-namespace': namespace}) + 'group-namespace': namespace, 'app-name': app_name, + 'max-bytes': max_bytes, 'max-objects': max_objects}) + + def add_op_create_erasure_pool(self, name, erasure_profile=None, + weight=None, group=None, app_name=None, + max_bytes=None, max_objects=None): + """Adds an operation to create a erasure coded pool. + + :param name: Name of pool to create + :type name: str + :param erasure_profile: Name of erasure code profile to use. If not + set the ceph-mon unit handling the broker + request will set its default value. + :type erasure_profile: str + :param weight: The percentage of data that is expected to be contained + in the pool from the total available space on the OSDs. + :type weight: float + :param group: Group to add pool to + :type group: str + :param app_name: (Optional) Tag pool with application name. Note that + there is certain protocols emerging upstream with + regard to meaningful application names to use. + Examples are ``rbd`` and ``rgw``. + :type app_name: str + :param max_bytes: Maximum bytes quota to apply + :type max_bytes: int + :param max_objects: Maximum objects quota to apply + :type max_objects: int + """ + self.ops.append({'op': 'create-pool', 'name': name, + 'pool-type': 'erasure', + 'erasure-profile': erasure_profile, + 'weight': weight, + 'group': group, 'app-name': app_name, + 'max-bytes': max_bytes, 'max-objects': max_objects}) def set_ops(self, ops): """Set request ops to provided value. diff --git a/hooks/charmhelpers/contrib/storage/linux/loopback.py b/charmhelpers/contrib/storage/linux/loopback.py similarity index 100% rename from hooks/charmhelpers/contrib/storage/linux/loopback.py rename to charmhelpers/contrib/storage/linux/loopback.py diff --git a/hooks/charmhelpers/contrib/storage/linux/lvm.py b/charmhelpers/contrib/storage/linux/lvm.py similarity index 100% rename from hooks/charmhelpers/contrib/storage/linux/lvm.py rename to charmhelpers/contrib/storage/linux/lvm.py diff --git a/hooks/charmhelpers/contrib/storage/linux/utils.py b/charmhelpers/contrib/storage/linux/utils.py similarity index 100% rename from hooks/charmhelpers/contrib/storage/linux/utils.py rename to charmhelpers/contrib/storage/linux/utils.py diff --git a/hooks/charmhelpers/core/__init__.py b/charmhelpers/core/__init__.py similarity index 100% rename from hooks/charmhelpers/core/__init__.py rename to charmhelpers/core/__init__.py diff --git a/hooks/charmhelpers/core/decorators.py b/charmhelpers/core/decorators.py similarity index 100% rename from hooks/charmhelpers/core/decorators.py rename to charmhelpers/core/decorators.py diff --git a/hooks/charmhelpers/core/files.py b/charmhelpers/core/files.py similarity index 100% rename from hooks/charmhelpers/core/files.py rename to charmhelpers/core/files.py diff --git a/hooks/charmhelpers/core/fstab.py b/charmhelpers/core/fstab.py similarity index 100% rename from hooks/charmhelpers/core/fstab.py rename to charmhelpers/core/fstab.py diff --git a/hooks/charmhelpers/core/hookenv.py b/charmhelpers/core/hookenv.py similarity index 93% rename from hooks/charmhelpers/core/hookenv.py rename to charmhelpers/core/hookenv.py index 2e28765..4744eb4 100644 --- a/hooks/charmhelpers/core/hookenv.py +++ b/charmhelpers/core/hookenv.py @@ -50,6 +50,11 @@ TRACE = "TRACE" MARKER = object() SH_MAX_ARG = 131071 + +RANGE_WARNING = ('Passing NO_PROXY string that includes a cidr. ' + 'This may not be compatible with software you are ' + 'running in your shell.') + cache = {} @@ -1414,3 +1419,72 @@ def unit_doomed(unit=None): # I don't think 'dead' units ever show up in the goal-state, but # check anyway in addition to 'dying'. return units[unit]['status'] in ('dying', 'dead') + + +def env_proxy_settings(selected_settings=None): + """Get proxy settings from process environment variables. + + Get charm proxy settings from environment variables that correspond to + juju-http-proxy, juju-https-proxy and juju-no-proxy (available as of 2.4.2, + see lp:1782236) in a format suitable for passing to an application that + reacts to proxy settings passed as environment variables. Some applications + support lowercase or uppercase notation (e.g. curl), some support only + lowercase (e.g. wget), there are also subjectively rare cases of only + uppercase notation support. no_proxy CIDR and wildcard support also varies + between runtimes and applications as there is no enforced standard. + + Some applications may connect to multiple destinations and expose config + options that would affect only proxy settings for a specific destination + these should be handled in charms in an application-specific manner. + + :param selected_settings: format only a subset of possible settings + :type selected_settings: list + :rtype: Option(None, dict[str, str]) + """ + SUPPORTED_SETTINGS = { + 'http': 'HTTP_PROXY', + 'https': 'HTTPS_PROXY', + 'no_proxy': 'NO_PROXY', + 'ftp': 'FTP_PROXY' + } + if selected_settings is None: + selected_settings = SUPPORTED_SETTINGS + + selected_vars = [v for k, v in SUPPORTED_SETTINGS.items() + if k in selected_settings] + proxy_settings = {} + for var in selected_vars: + var_val = os.getenv(var) + if var_val: + proxy_settings[var] = var_val + proxy_settings[var.lower()] = var_val + # Now handle juju-prefixed environment variables. The legacy vs new + # environment variable usage is mutually exclusive + charm_var_val = os.getenv('JUJU_CHARM_{}'.format(var)) + if charm_var_val: + proxy_settings[var] = charm_var_val + proxy_settings[var.lower()] = charm_var_val + if 'no_proxy' in proxy_settings: + if _contains_range(proxy_settings['no_proxy']): + log(RANGE_WARNING, level=WARNING) + return proxy_settings if proxy_settings else None + + +def _contains_range(addresses): + """Check for cidr or wildcard domain in a string. + + Given a string comprising a comma seperated list of ip addresses + and domain names, determine whether the string contains IP ranges + or wildcard domains. + + :param addresses: comma seperated list of domains and ip addresses. + :type addresses: str + """ + return ( + # Test for cidr (e.g. 10.20.20.0/24) + "/" in addresses or + # Test for wildcard domains (*.foo.com or .foo.com) + "*" in addresses or + addresses.startswith(".") or + ",." in addresses or + " ." in addresses) diff --git a/hooks/charmhelpers/core/host.py b/charmhelpers/core/host.py similarity index 100% rename from hooks/charmhelpers/core/host.py rename to charmhelpers/core/host.py diff --git a/hooks/charmhelpers/core/host_factory/__init__.py b/charmhelpers/core/host_factory/__init__.py similarity index 100% rename from hooks/charmhelpers/core/host_factory/__init__.py rename to charmhelpers/core/host_factory/__init__.py diff --git a/hooks/charmhelpers/core/host_factory/centos.py b/charmhelpers/core/host_factory/centos.py similarity index 100% rename from hooks/charmhelpers/core/host_factory/centos.py rename to charmhelpers/core/host_factory/centos.py diff --git a/hooks/charmhelpers/core/host_factory/ubuntu.py b/charmhelpers/core/host_factory/ubuntu.py similarity index 100% rename from hooks/charmhelpers/core/host_factory/ubuntu.py rename to charmhelpers/core/host_factory/ubuntu.py diff --git a/hooks/charmhelpers/core/hugepage.py b/charmhelpers/core/hugepage.py similarity index 100% rename from hooks/charmhelpers/core/hugepage.py rename to charmhelpers/core/hugepage.py diff --git a/hooks/charmhelpers/core/kernel.py b/charmhelpers/core/kernel.py similarity index 100% rename from hooks/charmhelpers/core/kernel.py rename to charmhelpers/core/kernel.py diff --git a/hooks/charmhelpers/core/kernel_factory/__init__.py b/charmhelpers/core/kernel_factory/__init__.py similarity index 100% rename from hooks/charmhelpers/core/kernel_factory/__init__.py rename to charmhelpers/core/kernel_factory/__init__.py diff --git a/hooks/charmhelpers/core/kernel_factory/centos.py b/charmhelpers/core/kernel_factory/centos.py similarity index 100% rename from hooks/charmhelpers/core/kernel_factory/centos.py rename to charmhelpers/core/kernel_factory/centos.py diff --git a/hooks/charmhelpers/core/kernel_factory/ubuntu.py b/charmhelpers/core/kernel_factory/ubuntu.py similarity index 100% rename from hooks/charmhelpers/core/kernel_factory/ubuntu.py rename to charmhelpers/core/kernel_factory/ubuntu.py diff --git a/hooks/charmhelpers/core/services/__init__.py b/charmhelpers/core/services/__init__.py similarity index 100% rename from hooks/charmhelpers/core/services/__init__.py rename to charmhelpers/core/services/__init__.py diff --git a/hooks/charmhelpers/core/services/base.py b/charmhelpers/core/services/base.py similarity index 100% rename from hooks/charmhelpers/core/services/base.py rename to charmhelpers/core/services/base.py diff --git a/hooks/charmhelpers/core/services/helpers.py b/charmhelpers/core/services/helpers.py similarity index 100% rename from hooks/charmhelpers/core/services/helpers.py rename to charmhelpers/core/services/helpers.py diff --git a/hooks/charmhelpers/core/strutils.py b/charmhelpers/core/strutils.py similarity index 100% rename from hooks/charmhelpers/core/strutils.py rename to charmhelpers/core/strutils.py diff --git a/hooks/charmhelpers/core/sysctl.py b/charmhelpers/core/sysctl.py similarity index 100% rename from hooks/charmhelpers/core/sysctl.py rename to charmhelpers/core/sysctl.py diff --git a/hooks/charmhelpers/core/templating.py b/charmhelpers/core/templating.py similarity index 100% rename from hooks/charmhelpers/core/templating.py rename to charmhelpers/core/templating.py diff --git a/hooks/charmhelpers/core/unitdata.py b/charmhelpers/core/unitdata.py similarity index 100% rename from hooks/charmhelpers/core/unitdata.py rename to charmhelpers/core/unitdata.py diff --git a/hooks/charmhelpers/fetch/__init__.py b/charmhelpers/fetch/__init__.py similarity index 100% rename from hooks/charmhelpers/fetch/__init__.py rename to charmhelpers/fetch/__init__.py diff --git a/hooks/charmhelpers/fetch/archiveurl.py b/charmhelpers/fetch/archiveurl.py similarity index 100% rename from hooks/charmhelpers/fetch/archiveurl.py rename to charmhelpers/fetch/archiveurl.py diff --git a/hooks/charmhelpers/fetch/bzrurl.py b/charmhelpers/fetch/bzrurl.py similarity index 100% rename from hooks/charmhelpers/fetch/bzrurl.py rename to charmhelpers/fetch/bzrurl.py diff --git a/hooks/charmhelpers/fetch/centos.py b/charmhelpers/fetch/centos.py similarity index 100% rename from hooks/charmhelpers/fetch/centos.py rename to charmhelpers/fetch/centos.py diff --git a/hooks/charmhelpers/fetch/giturl.py b/charmhelpers/fetch/giturl.py similarity index 100% rename from hooks/charmhelpers/fetch/giturl.py rename to charmhelpers/fetch/giturl.py diff --git a/hooks/charmhelpers/fetch/python/__init__.py b/charmhelpers/fetch/python/__init__.py similarity index 100% rename from hooks/charmhelpers/fetch/python/__init__.py rename to charmhelpers/fetch/python/__init__.py diff --git a/hooks/charmhelpers/fetch/python/debug.py b/charmhelpers/fetch/python/debug.py similarity index 100% rename from hooks/charmhelpers/fetch/python/debug.py rename to charmhelpers/fetch/python/debug.py diff --git a/hooks/charmhelpers/fetch/python/packages.py b/charmhelpers/fetch/python/packages.py similarity index 100% rename from hooks/charmhelpers/fetch/python/packages.py rename to charmhelpers/fetch/python/packages.py diff --git a/hooks/charmhelpers/fetch/python/rpdb.py b/charmhelpers/fetch/python/rpdb.py similarity index 100% rename from hooks/charmhelpers/fetch/python/rpdb.py rename to charmhelpers/fetch/python/rpdb.py diff --git a/hooks/charmhelpers/fetch/python/version.py b/charmhelpers/fetch/python/version.py similarity index 100% rename from hooks/charmhelpers/fetch/python/version.py rename to charmhelpers/fetch/python/version.py diff --git a/hooks/charmhelpers/fetch/snap.py b/charmhelpers/fetch/snap.py similarity index 100% rename from hooks/charmhelpers/fetch/snap.py rename to charmhelpers/fetch/snap.py diff --git a/hooks/charmhelpers/fetch/ubuntu.py b/charmhelpers/fetch/ubuntu.py similarity index 76% rename from hooks/charmhelpers/fetch/ubuntu.py rename to charmhelpers/fetch/ubuntu.py index 8a5cadf..c6d9341 100644 --- a/hooks/charmhelpers/fetch/ubuntu.py +++ b/charmhelpers/fetch/ubuntu.py @@ -19,15 +19,14 @@ import re import six import time import subprocess -from tempfile import NamedTemporaryFile -from charmhelpers.core.host import ( - lsb_release -) +from charmhelpers.core.host import get_distrib_codename + from charmhelpers.core.hookenv import ( log, DEBUG, WARNING, + env_proxy_settings, ) from charmhelpers.fetch import SourceConfigError, GPGKeyError @@ -303,12 +302,17 @@ def import_key(key): """Import an ASCII Armor key. A Radix64 format keyid is also supported for backwards - compatibility, but should never be used; the key retrieval - mechanism is insecure and subject to man-in-the-middle attacks - voiding all signature checks using that key. + compatibility. In this case Ubuntu keyserver will be + queried for a key via HTTPS by its keyid. This method + is less preferrable because https proxy servers may + require traffic decryption which is equivalent to a + man-in-the-middle attack (a proxy server impersonates + keyserver TLS certificates and has to be explicitly + trusted by the system). - :param keyid: The key in ASCII armor format, - including BEGIN and END markers. + :param key: A GPG key in ASCII armor format, + including BEGIN and END markers or a keyid. + :type key: (bytes, str) :raises: GPGKeyError if the key could not be imported """ key = key.strip() @@ -319,35 +323,131 @@ def import_key(key): log("PGP key found (looks like ASCII Armor format)", level=DEBUG) if ('-----BEGIN PGP PUBLIC KEY BLOCK-----' in key and '-----END PGP PUBLIC KEY BLOCK-----' in key): - log("Importing ASCII Armor PGP key", level=DEBUG) - with NamedTemporaryFile() as keyfile: - with open(keyfile.name, 'w') as fd: - fd.write(key) - fd.write("\n") - cmd = ['apt-key', 'add', keyfile.name] - try: - subprocess.check_call(cmd) - except subprocess.CalledProcessError: - error = "Error importing PGP key '{}'".format(key) - log(error) - raise GPGKeyError(error) + log("Writing provided PGP key in the binary format", level=DEBUG) + if six.PY3: + key_bytes = key.encode('utf-8') + else: + key_bytes = key + key_name = _get_keyid_by_gpg_key(key_bytes) + key_gpg = _dearmor_gpg_key(key_bytes) + _write_apt_gpg_keyfile(key_name=key_name, key_material=key_gpg) else: raise GPGKeyError("ASCII armor markers missing from GPG key") else: - # We should only send things obviously not a keyid offsite - # via this unsecured protocol, as it may be a secret or part - # of one. log("PGP key found (looks like Radix64 format)", level=WARNING) - log("INSECURLY importing PGP key from keyserver; " + log("SECURELY importing PGP key from keyserver; " "full key not provided.", level=WARNING) - cmd = ['apt-key', 'adv', '--keyserver', - 'hkp://keyserver.ubuntu.com:80', '--recv-keys', key] - try: - _run_with_retries(cmd) - except subprocess.CalledProcessError: - error = "Error importing PGP key '{}'".format(key) - log(error) - raise GPGKeyError(error) + # as of bionic add-apt-repository uses curl with an HTTPS keyserver URL + # to retrieve GPG keys. `apt-key adv` command is deprecated as is + # apt-key in general as noted in its manpage. See lp:1433761 for more + # history. Instead, /etc/apt/trusted.gpg.d is used directly to drop + # gpg + key_asc = _get_key_by_keyid(key) + # write the key in GPG format so that apt-key list shows it + key_gpg = _dearmor_gpg_key(key_asc) + _write_apt_gpg_keyfile(key_name=key, key_material=key_gpg) + + +def _get_keyid_by_gpg_key(key_material): + """Get a GPG key fingerprint by GPG key material. + Gets a GPG key fingerprint (40-digit, 160-bit) by the ASCII armor-encoded + or binary GPG key material. Can be used, for example, to generate file + names for keys passed via charm options. + + :param key_material: ASCII armor-encoded or binary GPG key material + :type key_material: bytes + :raises: GPGKeyError if invalid key material has been provided + :returns: A GPG key fingerprint + :rtype: str + """ + # Use the same gpg command for both Xenial and Bionic + cmd = 'gpg --with-colons --with-fingerprint' + ps = subprocess.Popen(cmd.split(), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + stdin=subprocess.PIPE) + out, err = ps.communicate(input=key_material) + if six.PY3: + out = out.decode('utf-8') + err = err.decode('utf-8') + if 'gpg: no valid OpenPGP data found.' in err: + raise GPGKeyError('Invalid GPG key material provided') + # from gnupg2 docs: fpr :: Fingerprint (fingerprint is in field 10) + return re.search(r"^fpr:{9}([0-9A-F]{40}):$", out, re.MULTILINE).group(1) + + +def _get_key_by_keyid(keyid): + """Get a key via HTTPS from the Ubuntu keyserver. + Different key ID formats are supported by SKS keyservers (the longer ones + are more secure, see "dead beef attack" and https://evil32.com/). Since + HTTPS is used, if SSLBump-like HTTPS proxies are in place, they will + impersonate keyserver.ubuntu.com and generate a certificate with + keyserver.ubuntu.com in the CN field or in SubjAltName fields of a + certificate. If such proxy behavior is expected it is necessary to add the + CA certificate chain containing the intermediate CA of the SSLBump proxy to + every machine that this code runs on via ca-certs cloud-init directive (via + cloudinit-userdata model-config) or via other means (such as through a + custom charm option). Also note that DNS resolution for the hostname in a + URL is done at a proxy server - not at the client side. + + 8-digit (32 bit) key ID + https://keyserver.ubuntu.com/pks/lookup?search=0x4652B4E6 + 16-digit (64 bit) key ID + https://keyserver.ubuntu.com/pks/lookup?search=0x6E85A86E4652B4E6 + 40-digit key ID: + https://keyserver.ubuntu.com/pks/lookup?search=0x35F77D63B5CEC106C577ED856E85A86E4652B4E6 + + :param keyid: An 8, 16 or 40 hex digit keyid to find a key for + :type keyid: (bytes, str) + :returns: A key material for the specified GPG key id + :rtype: (str, bytes) + :raises: subprocess.CalledProcessError + """ + # options=mr - machine-readable output (disables html wrappers) + keyserver_url = ('https://keyserver.ubuntu.com' + '/pks/lookup?op=get&options=mr&exact=on&search=0x{}') + curl_cmd = ['curl', keyserver_url.format(keyid)] + # use proxy server settings in order to retrieve the key + return subprocess.check_output(curl_cmd, + env=env_proxy_settings(['https'])) + + +def _dearmor_gpg_key(key_asc): + """Converts a GPG key in the ASCII armor format to the binary format. + + :param key_asc: A GPG key in ASCII armor format. + :type key_asc: (str, bytes) + :returns: A GPG key in binary format + :rtype: (str, bytes) + :raises: GPGKeyError + """ + ps = subprocess.Popen(['gpg', '--dearmor'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + stdin=subprocess.PIPE) + out, err = ps.communicate(input=key_asc) + # no need to decode output as it is binary (invalid utf-8), only error + if six.PY3: + err = err.decode('utf-8') + if 'gpg: no valid OpenPGP data found.' in err: + raise GPGKeyError('Invalid GPG key material. Check your network setup' + ' (MTU, routing, DNS) and/or proxy server settings' + ' as well as destination keyserver status.') + else: + return out + + +def _write_apt_gpg_keyfile(key_name, key_material): + """Writes GPG key material into a file at a provided path. + + :param key_name: A key name to use for a key file (could be a fingerprint) + :type key_name: str + :param key_material: A GPG key material (binary) + :type key_material: (str, bytes) + """ + with open('/etc/apt/trusted.gpg.d/{}.gpg'.format(key_name), + 'wb') as keyf: + keyf.write(key_material) def add_source(source, key=None, fail_invalid=False): @@ -442,13 +542,13 @@ def add_source(source, key=None, fail_invalid=False): def _add_proposed(): """Add the PROPOSED_POCKET as /etc/apt/source.list.d/proposed.list - Uses lsb_release()['DISTRIB_CODENAME'] to determine the correct staza for + Uses get_distrib_codename to determine the correct stanza for the deb line. For intel architecutres PROPOSED_POCKET is used for the release, but for other architectures PROPOSED_PORTS_POCKET is used for the release. """ - release = lsb_release()['DISTRIB_CODENAME'] + release = get_distrib_codename() arch = platform.machine() if arch not in six.iterkeys(ARCH_TO_PROPOSED_POCKET): raise SourceConfigError("Arch {} not supported for (distro-)proposed" @@ -461,11 +561,16 @@ def _add_apt_repository(spec): """Add the spec using add_apt_repository :param spec: the parameter to pass to add_apt_repository + :type spec: str """ if '{series}' in spec: - series = lsb_release()['DISTRIB_CODENAME'] + series = get_distrib_codename() spec = spec.replace('{series}', series) - _run_with_retries(['add-apt-repository', '--yes', spec]) + # software-properties package for bionic properly reacts to proxy settings + # passed as environment variables (See lp:1433761). This is not the case + # LTS and non-LTS releases below bionic. + _run_with_retries(['add-apt-repository', '--yes', spec], + cmd_env=env_proxy_settings(['https'])) def _add_cloud_pocket(pocket): @@ -534,7 +639,7 @@ def _verify_is_ubuntu_rel(release, os_release): :raises: SourceConfigError if the release is not the same as the ubuntu release. """ - ubuntu_rel = lsb_release()['DISTRIB_CODENAME'] + ubuntu_rel = get_distrib_codename() if release != ubuntu_rel: raise SourceConfigError( 'Invalid Cloud Archive release specified: {}-{} on this Ubuntu' diff --git a/hooks/charmhelpers/osplatform.py b/charmhelpers/osplatform.py similarity index 100% rename from hooks/charmhelpers/osplatform.py rename to charmhelpers/osplatform.py diff --git a/hooks/charmhelpers/payload/__init__.py b/charmhelpers/payload/__init__.py similarity index 100% rename from hooks/charmhelpers/payload/__init__.py rename to charmhelpers/payload/__init__.py diff --git a/hooks/charmhelpers/payload/execd.py b/charmhelpers/payload/execd.py similarity index 100% rename from hooks/charmhelpers/payload/execd.py rename to charmhelpers/payload/execd.py diff --git a/hooks/ceph.py b/hooks/ceph.py index de1e48e..8000a00 100644 --- a/hooks/ceph.py +++ b/hooks/ceph.py @@ -128,7 +128,7 @@ def is_quorum(): ] if os.path.exists(asok): try: - result = json.loads(subprocess.check_output(cmd)) + result = json.loads(subprocess.check_output(cmd).decode('utf-8')) except subprocess.CalledProcessError: return False except ValueError: @@ -155,7 +155,7 @@ def is_leader(): ] if os.path.exists(asok): try: - result = json.loads(subprocess.check_output(cmd)) + result = json.loads(subprocess.check_output(cmd).decode('utf-8')) except subprocess.CalledProcessError: return False except ValueError: @@ -201,7 +201,9 @@ DISK_FORMATS = [ def is_osd_disk(dev): try: - info = subprocess.check_output(['sgdisk', '-i', '1', dev]) + info = (subprocess + .check_output(['sgdisk', '-i', '1', dev]) + .decode('utf-8')) info = info.split("\n") # IGNORE:E1103 for line in info: if line.startswith( @@ -266,7 +268,7 @@ def generate_monitor_secret(): '--name=mon.', '--gen-key' ] - res = subprocess.check_output(cmd) + res = subprocess.check_output(cmd).decode('utf-8') return "{}==".format(res.split('=')[1].strip()) @@ -403,12 +405,15 @@ def get_named_key(name, caps=None): 'auth', 'get-or-create', 'client.{}'.format(name), ] # Add capabilities - for subsystem, subcaps in caps.iteritems(): + for subsystem, subcaps in caps.items(): cmd.extend([ subsystem, '; '.join(subcaps), ]) - return parse_key(subprocess.check_output(cmd).strip()) # IGNORE:E1103 + return parse_key(subprocess + .check_output(cmd) + .decode('utf-8') + .strip()) # IGNORE:E1103 def upgrade_key_caps(key, caps): @@ -419,7 +424,7 @@ def upgrade_key_caps(key, caps): cmd = [ "sudo", "-u", ceph_user(), 'ceph', 'auth', 'caps', key ] - for subsystem, subcaps in caps.iteritems(): + for subsystem, subcaps in caps.items(): cmd.extend([subsystem, '; '.join(subcaps)]) subprocess.check_call(cmd) diff --git a/hooks/ceph_hooks.py b/hooks/ceph_hooks.py index 6347e0e..322ccb0 100755 --- a/hooks/ceph_hooks.py +++ b/hooks/ceph_hooks.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python3 # # Copyright 2012 Canonical Ltd. @@ -13,6 +13,18 @@ import os import shutil import sys + +_path = os.path.dirname(os.path.realpath(__file__)) +_root = os.path.abspath(os.path.join(_path, '..')) + + +def _add_path(path): + if path not in sys.path: + sys.path.insert(1, path) + + +_add_path(_root) + import ceph from charmhelpers.core.hookenv import ( log, diff --git a/hooks/install b/hooks/install index 86d4885..eb05824 100755 --- a/hooks/install +++ b/hooks/install @@ -11,7 +11,7 @@ check_and_install() { fi } -PYTHON="python" +PYTHON="python3" for dep in ${DEPS[@]}; do check_and_install ${PYTHON} ${dep} diff --git a/hooks/utils.py b/hooks/utils.py index 5b68a1e..d1cf500 100644 --- a/hooks/utils.py +++ b/hooks/utils.py @@ -43,9 +43,9 @@ except ImportError: def enable_pocket(pocket): apt_sources = "/etc/apt/sources.list" - with open(apt_sources, "r") as sources: + with open(apt_sources, "rt") as sources: lines = sources.readlines() - with open(apt_sources, "w") as sources: + with open(apt_sources, "wt") as sources: for line in lines: if pocket in line: sources.write(re.sub('^# deb', 'deb', line)) diff --git a/tox.ini b/tox.ini index 68ab28e..3a8edbf 100644 --- a/tox.ini +++ b/tox.ini @@ -2,7 +2,7 @@ # This file is managed centrally by release-tools and should not be modified # within individual charm repos. [tox] -envlist = pep8,py27 +envlist = pep8,py3{5,6} skipsdist = True [testenv] @@ -20,6 +20,7 @@ passenv = HOME TERM AMULET_* CS_API_* basepython = python2.7 deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt +commands = /bin/true [testenv:py35] basepython = python3.5 diff --git a/unit_tests/__init__.py b/unit_tests/__init__.py index f80aab3..ba8fe96 100644 --- a/unit_tests/__init__.py +++ b/unit_tests/__init__.py @@ -1,2 +1,19 @@ +import os import sys -sys.path.append('hooks') + + +_path = os.path.dirname(os.path.realpath(__file__)) +_actions = os.path.abspath(os.path.join(_path, '../actions')) +_hooks = os.path.abspath(os.path.join(_path, '../hooks')) +_charmhelpers = os.path.abspath(os.path.join(_path, '../charmhelpers')) +_unit_tests = os.path.abspath(os.path.join(_path, '../unit_tests')) + + +def _add_path(path): + if path not in sys.path: + sys.path.insert(1, path) + +_add_path(_actions) +_add_path(_hooks) +_add_path(_charmhelpers) +_add_path(_unit_tests) diff --git a/unit_tests/test_ceph.py b/unit_tests/test_ceph.py index 9ed36c0..4cb55b2 100644 --- a/unit_tests/test_ceph.py +++ b/unit_tests/test_ceph.py @@ -1,3 +1,4 @@ +import collections import unittest import mock @@ -70,10 +71,8 @@ class CephTestCase(unittest.TestCase): expected_key = 'AQCnjmtbuEACMxAA7joUmgLIGI4/3LKkPzUy8g==' expected_output = ('[client.testuser]\n key = {}' .format(expected_key)) - caps = { - 'mon': ['allow rw'], - 'osd': ['allow rwx'] - } + caps = collections.OrderedDict([('mon', ['allow rw']), + ('osd', ['allow rwx'])]) ceph_user = 'ceph' ceph_proxy_host = 'cephproxy' mock_get_unit_hostname.return_value = ceph_proxy_host @@ -86,7 +85,8 @@ class CephTestCase(unittest.TestCase): '/var/lib/ceph/mon/ceph-{}/keyring'.format( ceph_proxy_host), 'auth', 'get-or-create', user_spec, 'mon', - 'allow rw', 'osd', 'allow rwx']): expected_output + 'allow rw', 'osd', 'allow rwx']): (expected_output + .encode('utf-8')) }[' '.join(cmd)] mock_check_output.side_effect = check_output_side_effect mock_config.side_effect = self.empty_config_side_effect diff --git a/unit_tests/test_ceph_hooks.py b/unit_tests/test_ceph_hooks.py index 0b394cf..1bf97df 100644 --- a/unit_tests/test_ceph_hooks.py +++ b/unit_tests/test_ceph_hooks.py @@ -58,7 +58,7 @@ class TestHooks(test_utils.CharmTestCase): 'auth': 'cephx', 'fsid': 'some-fsid'} - mock_check_output.return_value = CEPH_GET_KEY + mock_check_output.return_value = CEPH_GET_KEY.encode() self.relation_get.return_value = {} self.test_config.set('monitor-hosts', settings['ceph-public-address']) self.test_config.set('fsid', settings['fsid']) @@ -123,7 +123,7 @@ class TestHooks(test_utils.CharmTestCase): @mock.patch('subprocess.check_output') def test_client_relation_joined(self, mock_check_output): - mock_check_output.return_value = CEPH_GET_KEY + mock_check_output.return_value = CEPH_GET_KEY.encode() self.test_config.set('monitor-hosts', '127.0.0.1:1234') self.test_config.set('fsid', 'abc123') self.test_config.set('admin-key', 'some-admin-key') diff --git a/unit_tests/test_utils.py b/unit_tests/test_utils.py index 0a77482..ed0e7a1 100644 --- a/unit_tests/test_utils.py +++ b/unit_tests/test_utils.py @@ -36,7 +36,7 @@ def get_default_config(): ''' default_config = {} config = load_config() - for k, v in config.iteritems(): + for k, v in config.items(): if 'default' in v: default_config[k] = v['default'] else: