From abffaa2578b701a72fece33c51cb2cf02d54916d Mon Sep 17 00:00:00 2001
From: attardi <giuseppe.attardi@garr.it>
Date: Tue, 2 Jul 2019 15:15:41 +0000
Subject: [PATCH] Upgraded to charm Gnocchi #23

---
 LICENSE                                       |  26 +++
 Makefile                                      |  40 ++---
 bin/layer_option                              |   6 +-
 config.yaml                                   |   5 -
 hooks/amqp-relation-broken                    |   9 +-
 hooks/amqp-relation-changed                   |   9 +-
 hooks/amqp-relation-departed                  |   9 +-
 hooks/amqp-relation-joined                    |   9 +-
 hooks/cluster-relation-broken                 |   9 +-
 hooks/cluster-relation-changed                |   9 +-
 hooks/cluster-relation-departed               |   9 +-
 hooks/cluster-relation-joined                 |   9 +-
 hooks/config-changed                          |   9 +-
 hooks/coordinator-memcached-relation-broken   |   9 +-
 hooks/coordinator-memcached-relation-changed  |   9 +-
 hooks/coordinator-memcached-relation-departed |   9 +-
 hooks/coordinator-memcached-relation-joined   |   9 +-
 hooks/ha-relation-broken                      |   9 +-
 hooks/ha-relation-changed                     |   9 +-
 hooks/ha-relation-departed                    |   9 +-
 hooks/ha-relation-joined                      |   9 +-
 hooks/hook.template                           |   9 +-
 hooks/identity-service-relation-broken        |   9 +-
 hooks/identity-service-relation-changed       |   9 +-
 hooks/identity-service-relation-departed      |   9 +-
 hooks/identity-service-relation-joined        |   9 +-
 hooks/install                                 |   9 +-
 hooks/leader-elected                          |   9 +-
 hooks/leader-settings-changed                 |   9 +-
 hooks/metric-service-relation-broken          |   9 +-
 hooks/metric-service-relation-changed         |   9 +-
 hooks/metric-service-relation-departed        |   9 +-
 hooks/metric-service-relation-joined          |   9 +-
 hooks/relations/ceph-client/interface.yaml    |  12 +-
 hooks/relations/ceph-client/requires.py       |  59 ++++++-
 .../ceph-client/test-requirements.txt         |   2 -
 hooks/relations/ceph-client/tox.ini           |  43 -----
 hooks/relations/gnocchi/tox.ini               |   7 +-
 hooks/relations/hacluster/common.py           |  67 +++++++-
 hooks/relations/hacluster/interface.yaml      |   8 +
 hooks/relations/hacluster/requires.py         |  97 ++++++++++-
 .../relations/hacluster/test-requirements.txt |   7 +-
 hooks/relations/hacluster/tox.ini             |  39 -----
 hooks/relations/keystone/interface.yaml       |  11 +-
 hooks/relations/keystone/requires.py          |   7 +-
 .../relations/keystone/test-requirements.txt  |   2 -
 hooks/relations/keystone/tox.ini              |  40 -----
 hooks/relations/mysql-shared/.gitreview       |   2 +-
 hooks/relations/mysql-shared/requires.py      |   8 +
 hooks/relations/mysql-shared/tox.ini          |   7 +-
 hooks/relations/openstack-ha/.gitreview       |   2 +-
 hooks/relations/openstack-ha/tox.ini          |   5 +-
 hooks/relations/rabbitmq/.gitreview           |   2 +-
 hooks/relations/rabbitmq/tox.ini              |   5 +-
 hooks/shared-db-relation-broken               |   9 +-
 hooks/shared-db-relation-changed              |   9 +-
 hooks/shared-db-relation-departed             |   9 +-
 hooks/shared-db-relation-joined               |   9 +-
 hooks/start                                   |   9 +-
 hooks/stop                                    |   9 +-
 hooks/storage-ceph-relation-broken            |   9 +-
 hooks/storage-ceph-relation-changed           |   9 +-
 hooks/storage-ceph-relation-departed          |   9 +-
 hooks/storage-ceph-relation-joined            |   9 +-
 hooks/update-status                           |   9 +-
 hooks/upgrade-charm                           |  18 +-
 layer.yaml                                    |  30 +++-
 lib/charm/openstack/gnocchi.py                |  56 +------
 lib/charms/layer/__init__.py                  |  69 ++++++--
 lib/charms/layer/basic.py                     |  70 +++++++-
 metadata.yaml                                 |   7 +-
 reactive/gnocchi_handlers.py                  |  20 ++-
 reactive/layer_openstack.py                   |  27 ++-
 reactive/layer_openstack_api.py               |  22 +++
 rebuild                                       |   5 -
 repo-info                                     |   6 +-
 requirements.txt                              |  10 +-
 run-func-tests.sh                             |  72 --------
 templates/gnocchi.conf                        |   2 +
 templates/parts/section-database              |   2 +-
 templates/parts/section-keystone-authtoken    |   5 +
 tests/basic_deployment.py                     |   2 +-
 tox.ini                                       | 139 +++++-----------
 unit_tests/__init__.py                        |  42 -----
 unit_tests/test_gnocchi_handlers.py           | 154 ------------------
 85 files changed, 827 insertions(+), 800 deletions(-)
 delete mode 100644 hooks/relations/ceph-client/test-requirements.txt
 delete mode 100644 hooks/relations/ceph-client/tox.ini
 delete mode 100644 hooks/relations/hacluster/tox.ini
 delete mode 100644 hooks/relations/keystone/test-requirements.txt
 delete mode 100644 hooks/relations/keystone/tox.ini
 delete mode 100644 rebuild
 delete mode 100755 run-func-tests.sh
 delete mode 100644 unit_tests/__init__.py
 delete mode 100644 unit_tests/test_gnocchi_handlers.py

diff --git a/LICENSE b/LICENSE
index 68c771a0..d6456956 100644
--- a/LICENSE
+++ b/LICENSE
@@ -174,3 +174,29 @@
       incurred by, or claims asserted against, such Contributor by reason
       of your accepting any such warranty or additional liability.
 
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
diff --git a/Makefile b/Makefile
index 3b02e946..ddfef427 100644
--- a/Makefile
+++ b/Makefile
@@ -4,34 +4,30 @@ PYTHON := /usr/bin/env python
 USER = csd-garr
 NAME = gnocchi
 
-all: lint test
+all: lint unit_test
 
-lint:
-	@tox -e pep8
-
-test:
-	@# Bundletester expects unit tests here.
-	@tox -e py27
+.PHONY: clean
+clean:
+clean:
+	@rm -rf .coverage .tox .testrepository trusty .unit-state.db
+	find . -iname '*.pyc' -delete
 
-functional_test:
-	@echo Starting functional tests...
-	@tox -e func27
+.PHONY: apt_prereqs
+apt_prereqs:
+	@# Need tox, but don't install the apt version unless we have to (don't want to conflict with pip)
+	@which tox >/dev/null || (sudo apt-get install -y python-pip && sudo pip install tox)
 
-bin/charm_helpers_sync.py:
-	@mkdir -p bin
-	@curl -o bin/charm_helpers_sync.py https://raw.githubusercontent.com/juju/charm-helpers/master/tools/charm_helpers_sync/charm_helpers_sync.py
+.PHONY: lint
+lint: apt_prereqs
+	@tox --notest
 
-sync: bin/charm_helpers_sync.py
-	@$(PYTHON) bin/charm_helpers_sync.py -c charm-helpers-hooks.yaml
-	@$(PYTHON) bin/charm_helpers_sync.py -c charm-helpers-tests.yaml
+.PHONY: unit_test
+unit_test: apt_prereqs
+	@echo Starting tests...
+	tox
 
-publish: lint test
+publish: lint unit_test
 	export OUTPUT=`charm push . cs:~$(USER)/$(NAME)`; echo $$OUTPUT
 	export REV=`echo $$OUTPUT | sed 's/.*$(NAME)-\([0-9]*\).*/\1/'`
 	charm release cs:~$(USER)/$(NAME)-$(REV) --channel stable
 	charm grant cs:~$(USER)/$(NAME)-$(REV) everyone --channel stable
-
-.PHONY: clean
-clean:
-	@rm -rf .coverage .tox .testrepository trusty .unit-state.db
-	find . -iname '*.pyc' -delete
diff --git a/bin/layer_option b/bin/layer_option
index 90dc400e..3253ef8a 100755
--- a/bin/layer_option
+++ b/bin/layer_option
@@ -1,10 +1,8 @@
 #!/usr/bin/env python3
 
 import sys
-sys.path.append('lib')
-
 import argparse
-from charms.layer import options
+from charms import layer
 
 
 parser = argparse.ArgumentParser(description='Access layer options.')
@@ -14,7 +12,7 @@ parser.add_argument('option',
                     help='the option to access')
 
 args = parser.parse_args()
-value = options(args.section).get(args.option, '')
+value = layer.options.get(args.section, args.option)
 if isinstance(value, bool):
     sys.exit(0 if value else 1)
 elif isinstance(value, list):
diff --git a/config.yaml b/config.yaml
index 903d418c..df0615d9 100644
--- a/config.yaml
+++ b/config.yaml
@@ -13,11 +13,6 @@
     "description": |
       Setting this to True will allow supporting services to log to syslog.
   "incomingtofile":
-    "type": "boolean"
-    "default": !!bool "false"
-    "description": |
-      Use file driver for incoming measures.
-  "use-internal-endpoints":
     "type": "boolean"
     "default": !!bool "false"
     "description": |
diff --git a/hooks/amqp-relation-broken b/hooks/amqp-relation-broken
index 64c84897..9858c6be 100755
--- a/hooks/amqp-relation-broken
+++ b/hooks/amqp-relation-broken
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/amqp-relation-changed b/hooks/amqp-relation-changed
index 64c84897..9858c6be 100755
--- a/hooks/amqp-relation-changed
+++ b/hooks/amqp-relation-changed
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/amqp-relation-departed b/hooks/amqp-relation-departed
index 64c84897..9858c6be 100755
--- a/hooks/amqp-relation-departed
+++ b/hooks/amqp-relation-departed
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/amqp-relation-joined b/hooks/amqp-relation-joined
index 64c84897..9858c6be 100755
--- a/hooks/amqp-relation-joined
+++ b/hooks/amqp-relation-joined
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/cluster-relation-broken b/hooks/cluster-relation-broken
index 64c84897..9858c6be 100755
--- a/hooks/cluster-relation-broken
+++ b/hooks/cluster-relation-broken
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/cluster-relation-changed b/hooks/cluster-relation-changed
index 64c84897..9858c6be 100755
--- a/hooks/cluster-relation-changed
+++ b/hooks/cluster-relation-changed
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/cluster-relation-departed b/hooks/cluster-relation-departed
index 64c84897..9858c6be 100755
--- a/hooks/cluster-relation-departed
+++ b/hooks/cluster-relation-departed
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/cluster-relation-joined b/hooks/cluster-relation-joined
index 64c84897..9858c6be 100755
--- a/hooks/cluster-relation-joined
+++ b/hooks/cluster-relation-joined
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/config-changed b/hooks/config-changed
index 64c84897..9858c6be 100755
--- a/hooks/config-changed
+++ b/hooks/config-changed
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/coordinator-memcached-relation-broken b/hooks/coordinator-memcached-relation-broken
index 64c84897..9858c6be 100755
--- a/hooks/coordinator-memcached-relation-broken
+++ b/hooks/coordinator-memcached-relation-broken
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/coordinator-memcached-relation-changed b/hooks/coordinator-memcached-relation-changed
index 64c84897..9858c6be 100755
--- a/hooks/coordinator-memcached-relation-changed
+++ b/hooks/coordinator-memcached-relation-changed
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/coordinator-memcached-relation-departed b/hooks/coordinator-memcached-relation-departed
index 64c84897..9858c6be 100755
--- a/hooks/coordinator-memcached-relation-departed
+++ b/hooks/coordinator-memcached-relation-departed
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/coordinator-memcached-relation-joined b/hooks/coordinator-memcached-relation-joined
index 64c84897..9858c6be 100755
--- a/hooks/coordinator-memcached-relation-joined
+++ b/hooks/coordinator-memcached-relation-joined
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/ha-relation-broken b/hooks/ha-relation-broken
index 64c84897..9858c6be 100755
--- a/hooks/ha-relation-broken
+++ b/hooks/ha-relation-broken
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/ha-relation-changed b/hooks/ha-relation-changed
index 64c84897..9858c6be 100755
--- a/hooks/ha-relation-changed
+++ b/hooks/ha-relation-changed
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/ha-relation-departed b/hooks/ha-relation-departed
index 64c84897..9858c6be 100755
--- a/hooks/ha-relation-departed
+++ b/hooks/ha-relation-departed
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/ha-relation-joined b/hooks/ha-relation-joined
index 64c84897..9858c6be 100755
--- a/hooks/ha-relation-joined
+++ b/hooks/ha-relation-joined
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/hook.template b/hooks/hook.template
index 64c84897..9858c6be 100644
--- a/hooks/hook.template
+++ b/hooks/hook.template
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/identity-service-relation-broken b/hooks/identity-service-relation-broken
index 64c84897..9858c6be 100755
--- a/hooks/identity-service-relation-broken
+++ b/hooks/identity-service-relation-broken
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/identity-service-relation-changed b/hooks/identity-service-relation-changed
index 64c84897..9858c6be 100755
--- a/hooks/identity-service-relation-changed
+++ b/hooks/identity-service-relation-changed
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/identity-service-relation-departed b/hooks/identity-service-relation-departed
index 64c84897..9858c6be 100755
--- a/hooks/identity-service-relation-departed
+++ b/hooks/identity-service-relation-departed
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/identity-service-relation-joined b/hooks/identity-service-relation-joined
index 64c84897..9858c6be 100755
--- a/hooks/identity-service-relation-joined
+++ b/hooks/identity-service-relation-joined
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/install b/hooks/install
index 64c84897..9858c6be 100755
--- a/hooks/install
+++ b/hooks/install
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/leader-elected b/hooks/leader-elected
index 64c84897..9858c6be 100755
--- a/hooks/leader-elected
+++ b/hooks/leader-elected
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/leader-settings-changed b/hooks/leader-settings-changed
index 64c84897..9858c6be 100755
--- a/hooks/leader-settings-changed
+++ b/hooks/leader-settings-changed
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/metric-service-relation-broken b/hooks/metric-service-relation-broken
index 64c84897..9858c6be 100755
--- a/hooks/metric-service-relation-broken
+++ b/hooks/metric-service-relation-broken
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/metric-service-relation-changed b/hooks/metric-service-relation-changed
index 64c84897..9858c6be 100755
--- a/hooks/metric-service-relation-changed
+++ b/hooks/metric-service-relation-changed
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/metric-service-relation-departed b/hooks/metric-service-relation-departed
index 64c84897..9858c6be 100755
--- a/hooks/metric-service-relation-departed
+++ b/hooks/metric-service-relation-departed
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/metric-service-relation-joined b/hooks/metric-service-relation-joined
index 64c84897..9858c6be 100755
--- a/hooks/metric-service-relation-joined
+++ b/hooks/metric-service-relation-joined
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/relations/ceph-client/interface.yaml b/hooks/relations/ceph-client/interface.yaml
index 3adb29b8..85788591 100644
--- a/hooks/relations/ceph-client/interface.yaml
+++ b/hooks/relations/ceph-client/interface.yaml
@@ -1,3 +1,13 @@
 name: ceph-client
 summary: Ceph Client Interface
-version: 1
\ No newline at end of file
+version: 1
+maintainer: OpenStack Charmers <openstack-discuss@lists.openstack.org>
+ignore:
+  - 'unit_tests'
+  - 'Makefile'
+  - '.testr.conf'
+  - 'test-requirements.txt'
+  - 'tox.ini'
+  - '.gitignore'
+  - '.gitreview'
+  - '.unit-state.db'
\ No newline at end of file
diff --git a/hooks/relations/ceph-client/requires.py b/hooks/relations/ceph-client/requires.py
index ddffd371..fc9a0562 100644
--- a/hooks/relations/ceph-client/requires.py
+++ b/hooks/relations/ceph-client/requires.py
@@ -67,13 +67,23 @@ class CephClientRequires(RelationBase):
         self.remove_state('{relation_name}.connected')
         self.remove_state('{relation_name}.pools.available')
 
-    def create_pool(self, name, replicas=3):
+    def create_pool(self, name, replicas=3, weight=None, pg_num=None,
+                    group=None, namespace=None):
         """
         Request pool setup
 
-        @param name: name of pool to create
-        @param replicas: number of replicas for supporting pools
+        @param name: Name of pool to create
+        @param replicas: Number of replicas for supporting pools
+        @param weight: The percentage of data the pool makes up
+        @param pg_num: 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 group: Group to add pool to.
+        @param namespace: A group can optionally have a namespace defined that
+                          will be used to further restrict pool access.
         """
+
         # json.dumps of the CephBrokerRq()
         json_rq = self.get_local(key='broker_req')
 
@@ -81,7 +91,10 @@ class CephClientRequires(RelationBase):
             rq = CephBrokerRq()
             rq.add_op_create_pool(name="{}".format(name),
                                   replica_count=replicas,
-                                  weight=None)
+                                  pg_num=pg_num,
+                                  weight=weight,
+                                  group=group,
+                                  namespace=namespace)
             self.set_local(key='broker_req', value=rq.request)
             send_request_if_needed(rq, relation=self.relation_name)
         else:
@@ -95,6 +108,35 @@ class CephClientRequires(RelationBase):
                 log("Unable to decode broker_req: {}.  Error: {}".format(
                     json_rq, err))
 
+    def request_access_to_group(self, name, namespace=None, permission=None,
+                                key_name=None, object_prefix_permissions=None):
+        """
+        Adds the requested permissions to service's Ceph key
+
+        Adds the requested permissions to the current service's Ceph key,
+        allowing the key to access only the specified pools or
+        object prefixes. object_prefix_permissions should be a dictionary
+        keyed on the permission with the corresponding value being a list
+        of prefixes to apply that permission to.
+            {
+                'rwx': ['prefix1', 'prefix2'],
+                'class-read': ['prefix3']}
+        @param name: Target group name for permissions request.
+        @param namespace: namespace to further restrict pool access.
+        @param permission: Permission to be requested against pool
+        @param key_name: userid to grant permission to
+        @param object_prefix_permissions: Add object_prefix permissions.
+        """
+        current_request = self.get_current_request()
+        current_request.add_op_request_access_to_group(
+            name,
+            namespace=namespace,
+            permission=permission,
+            key_name=key_name,
+            object_prefix_permissions=object_prefix_permissions)
+        self.set_local(key='broker_req', value=current_request.request)
+        send_request_if_needed(current_request, relation=self.relation_name)
+
     def get_remote_all(self, key, default=None):
         """Return a list of all values presented by remote units for key"""
         # TODO: might be a nicer way todo this - written a while back!
@@ -113,7 +155,12 @@ class CephClientRequires(RelationBase):
         """List of all monitor host public addresses"""
         hosts = []
         addrs = self.get_remote_all('ceph-public-address')
-        for addr in addrs:
-            hosts.append(format_ipv6_addr(addr) or addr)
+        for ceph_addrs in addrs:
+            # NOTE(jamespage): This looks odd but deals with
+            #                  use with ceph-proxy which
+            #                  presents all monitors in
+            #                  a single space delimited field.
+            for addr in ceph_addrs.split(' '):
+                hosts.append(format_ipv6_addr(addr) or addr)
         hosts.sort()
         return hosts
diff --git a/hooks/relations/ceph-client/test-requirements.txt b/hooks/relations/ceph-client/test-requirements.txt
deleted file mode 100644
index 1bae0c7d..00000000
--- a/hooks/relations/ceph-client/test-requirements.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-flake8>=2.2.4,<=2.4.1
-os-testr>=0.4.1
\ No newline at end of file
diff --git a/hooks/relations/ceph-client/tox.ini b/hooks/relations/ceph-client/tox.ini
deleted file mode 100644
index 0facda63..00000000
--- a/hooks/relations/ceph-client/tox.ini
+++ /dev/null
@@ -1,43 +0,0 @@
-[tox]
-envlist = pep8,py27,py34,py35
-skipsdist = True
-skip_missing_interpreters = True
-
-[testenv]
-setenv = VIRTUAL_ENV={envdir}
-         PYTHONHASHSEED=0
-install_command =
-  pip install --allow-unverified python-apt {opts} {packages}
-commands = ostestr {posargs}
-
-[testenv:py27]
-basepython = python2.7
-deps = -r{toxinidir}/test-requirements.txt
-# TODO: Need to write unit tests then remove the following command.
-# https://github.com/juju/charm-tools/issues/249
-commands = /bin/true
-
-[testenv:py34]
-basepython = python3.4
-deps = -r{toxinidir}/test-requirements.txt
-# TODO: Need to write unit tests then remove the following command.
-# https://github.com/juju/charm-tools/issues/249
-commands = /bin/true
-
-[testenv:py35]
-basepython = python3.5
-deps = -r{toxinidir}/test-requirements.txt
-# TODO: Need to write unit tests then remove the following command.
-# https://github.com/juju/charm-tools/issues/249
-commands = /bin/true
-
-[testenv:pep8]
-basepython = python2.7
-deps = -r{toxinidir}/test-requirements.txt
-commands = flake8 {posargs}
-
-[testenv:venv]
-commands = {posargs}
-
-[flake8]
-ignore = E402,E226
diff --git a/hooks/relations/gnocchi/tox.ini b/hooks/relations/gnocchi/tox.ini
index 0facda63..603dfe25 100644
--- a/hooks/relations/gnocchi/tox.ini
+++ b/hooks/relations/gnocchi/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = pep8,py27,py34,py35
+envlist = pep8,py27,py34,py35,py36
 skipsdist = True
 skip_missing_interpreters = True
 
@@ -7,7 +7,7 @@ skip_missing_interpreters = True
 setenv = VIRTUAL_ENV={envdir}
          PYTHONHASHSEED=0
 install_command =
-  pip install --allow-unverified python-apt {opts} {packages}
+  pip install {opts} {packages}
 commands = ostestr {posargs}
 
 [testenv:py27]
@@ -32,11 +32,12 @@ deps = -r{toxinidir}/test-requirements.txt
 commands = /bin/true
 
 [testenv:pep8]
-basepython = python2.7
+basepython = python3
 deps = -r{toxinidir}/test-requirements.txt
 commands = flake8 {posargs}
 
 [testenv:venv]
+basepython = python3
 commands = {posargs}
 
 [flake8]
diff --git a/hooks/relations/hacluster/common.py b/hooks/relations/hacluster/common.py
index 3d637558..29bae5b4 100644
--- a/hooks/relations/hacluster/common.py
+++ b/hooks/relations/hacluster/common.py
@@ -11,6 +11,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import hashlib
 import ipaddress
 from six import string_types
 
@@ -138,8 +139,8 @@ class CRM(dict):
             if first:
                 results = results + ' '
                 first = False
-            results = results + ('%s %s' % (prefix, d))
-
+            results = results + ('%s %s ' % (prefix, d))
+        results = results.rstrip()
         return results
 
     def clone(self, name, resource, description=None, **kwargs):
@@ -274,7 +275,7 @@ class CRM(dict):
         """
         specs = ' '.join(resources)
         if 'description' in kwargs:
-            specs = specs + (' description="' % kwargs['description'])
+            specs = specs + (' description=%s"' % kwargs['description'])
 
         for key in 'meta', 'params':
             if key not in kwargs:
@@ -284,8 +285,24 @@ class CRM(dict):
 
         self['groups'][name] = specs
 
+    def remove_deleted_resources(self):
+        """Work through the existing resources and remove any mention of ones
+           which have been marked for deletion."""
+        for res in self['delete_resources']:
+            for key in self.keys():
+                if key == 'delete_resources':
+                    continue
+                if isinstance(self[key], dict) and res in self[key].keys():
+                    del self[key][res]
+                elif isinstance(self[key], list) and res in self[key]:
+                    self[key].remove(res)
+                elif isinstance(self[key], tuple) and res in self[key]:
+                    self[key] = tuple(x for x in self[key] if x != res)
+
     def delete_resource(self, *resources):
-        """Deletes one or more objects/resources within Pacemaker.
+        """Specify objects/resources to be deleted from within Pacemaker. This
+           is not additive, the list of resources is set to exaclty what was
+           passed in.
 
         Parameters
         ----------
@@ -300,7 +317,34 @@ class CRM(dict):
         --------
         http://crmsh.github.io/man/#cmdhelp_configure_delete
         """
-        self['delete_resources'].extend(resources)
+        self['delete_resources'] = resources
+        self.remove_deleted_resources()
+
+    def add_delete_resource(self, resource):
+        """Specify an object/resource to delete from within Pacemaker. It can
+           be called multiple times to add additional resources to the deletion
+           list.
+
+        Parameters
+        ----------
+        resources: str
+            the name or id of the specific resource to delete.
+
+        Returns
+        -------
+        None
+
+        See Also
+        --------
+        http://crmsh.github.io/man/#cmdhelp_configure_delete
+        """
+        if resource not in self['delete_resources']:
+            # NOTE(fnordahl): this unpleasant piece of code is regrettably
+            # necessary for Python3.4 (and trusty) compability see LP: #1814218
+            # and LP: #1813982
+            self['delete_resources'] = tuple(
+                self['delete_resources'] or ()) + (resource,)
+            self.remove_deleted_resources()
 
     def init_services(self, *resources):
         """Specifies that the service(s) is an init or upstart service.
@@ -317,7 +361,7 @@ class CRM(dict):
         -------
         None
         """
-        self['init_services'].extend(resources)
+        self['init_services'] = resources
 
     def ms(self, name, resource, description=None, **kwargs):
         """Create a master/slave resource type.
@@ -565,7 +609,12 @@ class VirtualIP(ResourceDescriptor):
         :param crm: CRM() instance - Config object for Pacemaker resources
         :returns: None
         """
-        vip_key = 'res_{}_{}_vip'.format(self.service_name, self.nic)
+        if self.nic:
+            vip_key = 'res_{}_{}_vip'.format(self.service_name, self.nic)
+        else:
+            vip_key = 'res_{}_{}_vip'.format(
+                self.service_name,
+                hashlib.sha1(self.vip.encode('UTF-8')).hexdigest()[:7])
         ipaddr = ipaddress.ip_address(self.vip)
         if isinstance(ipaddr, ipaddress.IPv4Address):
             res_type = 'ocf:heartbeat:IPaddr2'
@@ -581,7 +630,9 @@ class VirtualIP(ResourceDescriptor):
             res_params = '{} nic="{}"'.format(res_params, self.nic)
         if self.cidr:
             res_params = '{} cidr_netmask="{}"'.format(res_params, self.cidr)
-        crm.primitive(vip_key, res_type, params=res_params)
+        # Monitor the VIP
+        _op_monitor = 'monitor depth="0" timeout="20s" interval="10s"'
+        crm.primitive(vip_key, res_type, params=res_params, op=_op_monitor)
 
 
 class DNSEntry(ResourceDescriptor):
diff --git a/hooks/relations/hacluster/interface.yaml b/hooks/relations/hacluster/interface.yaml
index 833da97f..edd0c902 100644
--- a/hooks/relations/hacluster/interface.yaml
+++ b/hooks/relations/hacluster/interface.yaml
@@ -3,3 +3,11 @@ summary: |
  Provides the hacluster interface used for configuring Corosync
  and Pacemaker services.
 maintainer: OpenStack Charmers <openstack-charmers@lists.ubuntu.com>
+ignore:
+  - '.gitignore'
+  - '.gitreview'
+  - '.testr.conf'
+  - 'test-requirements'
+  - 'tox.ini'
+  - 'unit_tests'
+  - '.zuul.yaml'
diff --git a/hooks/relations/hacluster/requires.py b/hooks/relations/hacluster/requires.py
index ce0bf7ad..c3795a69 100644
--- a/hooks/relations/hacluster/requires.py
+++ b/hooks/relations/hacluster/requires.py
@@ -12,12 +12,14 @@
 # limitations under the License.
 
 import json
+import hashlib
 
 import relations.hacluster.common
 from charms.reactive import hook
 from charms.reactive import RelationBase
 from charms.reactive import scopes
 from charms.reactive.helpers import data_changed
+from charmhelpers.core import hookenv
 
 
 class HAClusterRequires(RelationBase):
@@ -32,13 +34,38 @@ class HAClusterRequires(RelationBase):
 
     @hook('{requires:hacluster}-relation-changed')
     def changed(self):
-        self.set_state('{relation_name}.available')
+        if self.is_clustered():
+            self.set_state('{relation_name}.available')
+        else:
+            self.remove_state('{relation_name}.available')
 
     @hook('{requires:hacluster}-relation-{broken,departed}')
     def departed(self):
         self.remove_state('{relation_name}.available')
         self.remove_state('{relation_name}.connected')
 
+    def is_clustered(self):
+        """Has the hacluster charm set clustered?
+
+        The hacluster charm sets cluster=True when it determines it is ready.
+        Check the relation data for clustered and force a boolean return.
+
+        :returns: boolean
+        """
+        clustered_values = self.get_remote_all('clustered')
+        if clustered_values:
+            # There is only ever one subordinate hacluster unit
+            clustered = clustered_values[0]
+            # Future versions of hacluster will return a bool
+            # Current versions return a string
+            if type(clustered) is bool:
+                return clustered
+            elif (clustered is not None and
+                    (clustered.lower() == 'true' or
+                     clustered.lower() == 'yes')):
+                return True
+        return False
+
     def bind_on(self, iface=None, mcastport=None):
         relation_data = {}
         if iface:
@@ -74,7 +101,7 @@ class HAClusterRequires(RelationBase):
             self.set_local(**relation_data)
             self.set_remote(**relation_data)
 
-    def bind_resources(self, iface, mcastport=None):
+    def bind_resources(self, iface=None, mcastport=None):
         """Inform the ha subordinate about each service it should manage. The
         child class specifies the services via self.ha_resources
 
@@ -88,7 +115,16 @@ class HAClusterRequires(RelationBase):
         self.bind_on(iface=iface, mcastport=mcastport)
         self.manage_resources(resources)
 
-    def add_vip(self, name, vip, iface, netmask):
+    def delete_resource(self, resource_name):
+        resource_dict = self.get_local('resources')
+        if resource_dict:
+            resources = relations.hacluster.common.CRM(**resource_dict)
+        else:
+            resources = relations.hacluster.common.CRM()
+        resources.add_delete_resource(resource_name)
+        self.set_local(resources=resources)
+
+    def add_vip(self, name, vip, iface=None, netmask=None):
         """Add a VirtualIP object for each user specified vip to self.resources
 
         :param name: string - Name of service
@@ -118,10 +154,24 @@ class HAClusterRequires(RelationBase):
                 for vip_res in vip_resources:
                     if 'vip' in vip_res:
                         vip_res_group_members.append(vip_res)
-                resources.group(group, *vip_res_group_members)
+                resources.group(group,
+                                *sorted(vip_res_group_members))
 
         self.set_local(resources=resources)
 
+    def remove_vip(self, name, vip, iface=None):
+        """Remove a virtual IP
+
+        :param name: string - Name of service
+        :param vip: string - Virtual IP
+        :param iface: string - Network interface vip bound to
+        """
+        if iface:
+            nic_name = iface
+        else:
+            nic_name = hashlib.sha1(vip.encode('UTF-8')).hexdigest()[:7]
+        self.delete_resource('res_{}_{}_vip'.format(name, nic_name))
+
     def add_init_service(self, name, service, clone=True):
         """Add a InitService object for haproxy to self.resources
 
@@ -138,6 +188,17 @@ class HAClusterRequires(RelationBase):
             relations.hacluster.common.InitService(name, service, clone))
         self.set_local(resources=resources)
 
+    def remove_init_service(self, name, service):
+        """Remove an init service
+
+        :param name: string - Name of service
+        :param service: string - Name of service used in init system
+        """
+        res_key = 'res_{}_{}'.format(
+            name.replace('-', '_'),
+            service.replace('-', '_'))
+        self.delete_resource(res_key)
+
     def add_dnsha(self, name, ip, fqdn, endpoint_type):
         """Add a DNS entry to self.resources
 
@@ -164,6 +225,32 @@ class HAClusterRequires(RelationBase):
                 for dns_res in dns_resources:
                     if 'hostname' in dns_res:
                         dns_res_group_members.append(dns_res)
-                resources.group(group, *dns_res_group_members)
+                resources.group(group,
+                                *sorted(dns_res_group_members))
 
         self.set_local(resources=resources)
+
+    def remove_dnsha(self, name, endpoint_type):
+        """Remove a DNS entry
+
+        :param name: string - Name of service
+        :param endpoint_type: string - Public, private, internal etc
+        :returns: None
+        """
+        res_key = 'res_{}_{}_hostname'.format(
+            self.service_name.replace('-', '_'),
+            self.endpoint_type)
+        self.delete_resource(res_key)
+
+    def get_remote_all(self, key, default=None):
+        """Return a list of all values presented by remote units for key"""
+        values = []
+        for conversation in self.conversations():
+            for relation_id in conversation.relation_ids:
+                for unit in hookenv.related_units(relation_id):
+                    value = hookenv.relation_get(key,
+                                                 unit,
+                                                 relation_id) or default
+                    if value:
+                        values.append(value)
+        return list(set(values))
diff --git a/hooks/relations/hacluster/test-requirements.txt b/hooks/relations/hacluster/test-requirements.txt
index 095ec9c9..4d72d9a8 100644
--- a/hooks/relations/hacluster/test-requirements.txt
+++ b/hooks/relations/hacluster/test-requirements.txt
@@ -1,2 +1,7 @@
-flake8>=2.2.4,<=2.4.1
+# Lint and unit test requirements
+flake8
 os-testr>=0.4.1
+charms.reactive
+mock>=1.2
+coverage>=3.6
+netifaces
diff --git a/hooks/relations/hacluster/tox.ini b/hooks/relations/hacluster/tox.ini
deleted file mode 100644
index 8ae4d782..00000000
--- a/hooks/relations/hacluster/tox.ini
+++ /dev/null
@@ -1,39 +0,0 @@
-[tox]
-envlist = pep8,py27
-skipsdist = True
-
-[testenv]
-setenv = VIRTUAL_ENV={envdir}
-         PYTHONHASHSEED=0
-install_command =
-  pip install --allow-unverified python-apt {opts} {packages}
-commands = ostestr {posargs}
-
-[testenv:py27]
-basepython = python2.7
-deps = -r{toxinidir}/test-requirements.txt
-# TODO: Need to write unit tests then remove the following command.
-commands = /bin/true  
-
-[testenv:py34]
-basepython = python3.4
-deps = -r{toxinidir}/test-requirements.txt
-# TODO: Need to write unit tests then remove the following command.
-commands = /bin/true
-
-[testenv:py35]
-basepython = python3.5
-deps = -r{toxinidir}/test-requirements.txt
-# TODO: Need to write unit tests then remove the following command.
-commands = /bin/true
-
-[testenv:pep8]
-basepython = python2.7
-deps = -r{toxinidir}/test-requirements.txt
-commands = flake8 {posargs}
-
-[testenv:venv]
-commands = {posargs}
-
-[flake8]
-ignore = E402,E226
diff --git a/hooks/relations/keystone/interface.yaml b/hooks/relations/keystone/interface.yaml
index f01f858d..23a355d1 100644
--- a/hooks/relations/keystone/interface.yaml
+++ b/hooks/relations/keystone/interface.yaml
@@ -1,3 +1,12 @@
 name: keystone
 summary: Interface for integrating with Keystone identity service
-maintainer: OpenStack Charmers <openstack-dev@lists.openstack.org>
+maintainer: OpenStack Charmers <openstack-discuss@lists.openstack.org>
+ignore:
+  - 'unit_tests'
+  - 'Makefile'
+  - '.testr.conf'
+  - 'test-requirements.txt'
+  - 'tox.ini'
+  - '.gitignore'
+  - '.gitreview'
+  - '.unit-state.db'
diff --git a/hooks/relations/keystone/requires.py b/hooks/relations/keystone/requires.py
index 67906520..876e075e 100644
--- a/hooks/relations/keystone/requires.py
+++ b/hooks/relations/keystone/requires.py
@@ -31,7 +31,7 @@ class KeystoneRequires(RelationBase):
                       'ca_cert', 'ssl_cert', 'https_keystone',
                       'ssl_cert_admin', 'ssl_cert_internal',
                       'ssl_cert_public', 'ssl_key_admin', 'ssl_key_internal',
-                      'ssl_key_public', 'api_version']
+                      'ssl_key_public', 'api_version', 'service_domain']
 
     @hook('{requires:keystone}-relation-joined')
     def joined(self):
@@ -119,7 +119,7 @@ class KeystoneRequires(RelationBase):
         return True
 
     def register_endpoints(self, service, region, public_url, internal_url,
-                           admin_url):
+                           admin_url, requested_roles=None):
         """
         Register this service with keystone
         """
@@ -130,6 +130,9 @@ class KeystoneRequires(RelationBase):
             'admin_url': admin_url,
             'region': region,
         }
+        if requested_roles:
+            relation_info.update(
+                {'requested_roles': ','.join(requested_roles)})
         self.set_local(**relation_info)
         self.set_remote(**relation_info)
 
diff --git a/hooks/relations/keystone/test-requirements.txt b/hooks/relations/keystone/test-requirements.txt
deleted file mode 100644
index 095ec9c9..00000000
--- a/hooks/relations/keystone/test-requirements.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-flake8>=2.2.4,<=2.4.1
-os-testr>=0.4.1
diff --git a/hooks/relations/keystone/tox.ini b/hooks/relations/keystone/tox.ini
deleted file mode 100644
index c3951382..00000000
--- a/hooks/relations/keystone/tox.ini
+++ /dev/null
@@ -1,40 +0,0 @@
-[tox]
-envlist = pep8,py27,py34,py35
-skipsdist = True
-skip_missing_interpreters = True
-
-[testenv]
-setenv = VIRTUAL_ENV={envdir}
-         PYTHONHASHSEED=0
-install_command =
-  pip install --allow-unverified python-apt {opts} {packages}
-commands = ostestr {posargs}
-
-[testenv:py27]
-basepython = python2.7
-deps = -r{toxinidir}/test-requirements.txt
-# TODO: Need to write unit tests then remove the following command.
-commands = /bin/true
-
-[testenv:py34]
-basepython = python3.4
-deps = -r{toxinidir}/test-requirements.txt
-# TODO: Need to write unit tests then remove the following command.
-commands = /bin/true
-
-[testenv:py35]
-basepython = python3.5
-deps = -r{toxinidir}/test-requirements.txt
-# TODO: Need to write unit tests then remove the following command.
-commands = /bin/true
-
-[testenv:pep8]
-basepython = python2.7
-deps = -r{toxinidir}/test-requirements.txt
-commands = flake8 {posargs}
-
-[testenv:venv]
-commands = {posargs}
-
-[flake8]
-ignore = E402,E226
diff --git a/hooks/relations/mysql-shared/.gitreview b/hooks/relations/mysql-shared/.gitreview
index 057f8613..4c583ab4 100644
--- a/hooks/relations/mysql-shared/.gitreview
+++ b/hooks/relations/mysql-shared/.gitreview
@@ -1,4 +1,4 @@
 [gerrit]
-host=review.openstack.org
+host=review.opendev.org
 port=29418
 project=openstack/charm-interface-mysql-shared
diff --git a/hooks/relations/mysql-shared/requires.py b/hooks/relations/mysql-shared/requires.py
index 0b2ea8c9..8c61b1e7 100644
--- a/hooks/relations/mysql-shared/requires.py
+++ b/hooks/relations/mysql-shared/requires.py
@@ -27,10 +27,18 @@ class MySQLSharedRequires(RelationBase):
 
     @hook('{requires:mysql-shared}-relation-{broken,departed}')
     def departed(self):
+        # Clear state
         self.remove_state('{relation_name}.connected')
         self.remove_state('{relation_name}.available')
         self.remove_state('{relation_name}.available.access_network')
         self.remove_state('{relation_name}.available.ssl')
+        # Check if this is the last unit
+        for conversation in self.conversations():
+            for rel_id in conversation.relation_ids:
+                if len(hookenv.related_units(rel_id)) > 0:
+                    # This is not the last unit so reevaluate state
+                    self.joined()
+                    self.changed()
 
     def configure(self, database, username, hostname=None, prefix=None):
         """
diff --git a/hooks/relations/mysql-shared/tox.ini b/hooks/relations/mysql-shared/tox.ini
index c3951382..f877d2cd 100644
--- a/hooks/relations/mysql-shared/tox.ini
+++ b/hooks/relations/mysql-shared/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = pep8,py27,py34,py35
+envlist = pep8,py27,py34,py35,py36
 skipsdist = True
 skip_missing_interpreters = True
 
@@ -7,7 +7,7 @@ skip_missing_interpreters = True
 setenv = VIRTUAL_ENV={envdir}
          PYTHONHASHSEED=0
 install_command =
-  pip install --allow-unverified python-apt {opts} {packages}
+  pip install {opts} {packages}
 commands = ostestr {posargs}
 
 [testenv:py27]
@@ -29,11 +29,12 @@ deps = -r{toxinidir}/test-requirements.txt
 commands = /bin/true
 
 [testenv:pep8]
-basepython = python2.7
+basepython = python3
 deps = -r{toxinidir}/test-requirements.txt
 commands = flake8 {posargs}
 
 [testenv:venv]
+basepython = python3
 commands = {posargs}
 
 [flake8]
diff --git a/hooks/relations/openstack-ha/.gitreview b/hooks/relations/openstack-ha/.gitreview
index 687b9fcb..c168b71f 100644
--- a/hooks/relations/openstack-ha/.gitreview
+++ b/hooks/relations/openstack-ha/.gitreview
@@ -1,4 +1,4 @@
 [gerrit]
-host=review.openstack.org
+host=review.opendev.org
 port=29418
 project=openstack/charm-interface-openstack-ha.git
diff --git a/hooks/relations/openstack-ha/tox.ini b/hooks/relations/openstack-ha/tox.ini
index c3951382..da2a1f12 100644
--- a/hooks/relations/openstack-ha/tox.ini
+++ b/hooks/relations/openstack-ha/tox.ini
@@ -7,7 +7,7 @@ skip_missing_interpreters = True
 setenv = VIRTUAL_ENV={envdir}
          PYTHONHASHSEED=0
 install_command =
-  pip install --allow-unverified python-apt {opts} {packages}
+  pip install {opts} {packages}
 commands = ostestr {posargs}
 
 [testenv:py27]
@@ -29,11 +29,12 @@ deps = -r{toxinidir}/test-requirements.txt
 commands = /bin/true
 
 [testenv:pep8]
-basepython = python2.7
+basepython = python3
 deps = -r{toxinidir}/test-requirements.txt
 commands = flake8 {posargs}
 
 [testenv:venv]
+basepython = python3
 commands = {posargs}
 
 [flake8]
diff --git a/hooks/relations/rabbitmq/.gitreview b/hooks/relations/rabbitmq/.gitreview
index 6d7ea5fc..487a7c09 100644
--- a/hooks/relations/rabbitmq/.gitreview
+++ b/hooks/relations/rabbitmq/.gitreview
@@ -1,4 +1,4 @@
 [gerrit]
-host=review.openstack.org
+host=review.opendev.org
 port=29418
 project=openstack/charm-interface-rabbitmq.git
diff --git a/hooks/relations/rabbitmq/tox.ini b/hooks/relations/rabbitmq/tox.ini
index a1247d16..c0f14f6d 100644
--- a/hooks/relations/rabbitmq/tox.ini
+++ b/hooks/relations/rabbitmq/tox.ini
@@ -6,7 +6,7 @@ skipsdist = True
 setenv = VIRTUAL_ENV={envdir}
          PYTHONHASHSEED=0
 install_command =
-  pip install --allow-unverified python-apt {opts} {packages}
+  pip install {opts} {packages}
 commands = ostestr {posargs}
 
 [testenv:py27]
@@ -22,11 +22,12 @@ deps = -r{toxinidir}/test-requirements.txt
 commands = /bin/true
 
 [testenv:pep8]
-basepython = python2.7
+basepython = python3
 deps = -r{toxinidir}/test-requirements.txt
 commands = flake8 {posargs}
 
 [testenv:venv]
+basepython = python3
 commands = {posargs}
 
 [flake8]
diff --git a/hooks/shared-db-relation-broken b/hooks/shared-db-relation-broken
index 64c84897..9858c6be 100755
--- a/hooks/shared-db-relation-broken
+++ b/hooks/shared-db-relation-broken
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/shared-db-relation-changed b/hooks/shared-db-relation-changed
index 64c84897..9858c6be 100755
--- a/hooks/shared-db-relation-changed
+++ b/hooks/shared-db-relation-changed
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/shared-db-relation-departed b/hooks/shared-db-relation-departed
index 64c84897..9858c6be 100755
--- a/hooks/shared-db-relation-departed
+++ b/hooks/shared-db-relation-departed
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/shared-db-relation-joined b/hooks/shared-db-relation-joined
index 64c84897..9858c6be 100755
--- a/hooks/shared-db-relation-joined
+++ b/hooks/shared-db-relation-joined
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/start b/hooks/start
index 64c84897..9858c6be 100755
--- a/hooks/start
+++ b/hooks/start
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/stop b/hooks/stop
index 64c84897..9858c6be 100755
--- a/hooks/stop
+++ b/hooks/stop
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/storage-ceph-relation-broken b/hooks/storage-ceph-relation-broken
index 64c84897..9858c6be 100755
--- a/hooks/storage-ceph-relation-broken
+++ b/hooks/storage-ceph-relation-broken
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/storage-ceph-relation-changed b/hooks/storage-ceph-relation-changed
index 64c84897..9858c6be 100755
--- a/hooks/storage-ceph-relation-changed
+++ b/hooks/storage-ceph-relation-changed
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/storage-ceph-relation-departed b/hooks/storage-ceph-relation-departed
index 64c84897..9858c6be 100755
--- a/hooks/storage-ceph-relation-departed
+++ b/hooks/storage-ceph-relation-departed
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/storage-ceph-relation-joined b/hooks/storage-ceph-relation-joined
index 64c84897..9858c6be 100755
--- a/hooks/storage-ceph-relation-joined
+++ b/hooks/storage-ceph-relation-joined
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/update-status b/hooks/update-status
index 64c84897..9858c6be 100755
--- a/hooks/update-status
+++ b/hooks/update-status
@@ -4,9 +4,12 @@
 import sys
 sys.path.append('lib')
 
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -15,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/hooks/upgrade-charm b/hooks/upgrade-charm
index 85817d8f..9858c6be 100755
--- a/hooks/upgrade-charm
+++ b/hooks/upgrade-charm
@@ -1,21 +1,15 @@
 #!/usr/bin/env python3
 
 # Load modules from $JUJU_CHARM_DIR/lib
-import os
 import sys
 sys.path.append('lib')
 
-# This is an upgrade-charm context, make sure we install latest deps
-if not os.path.exists('wheelhouse/.upgrade'):
-    open('wheelhouse/.upgrade', 'w').close()
-    if os.path.exists('wheelhouse/.bootstrapped'):
-        os.unlink('wheelhouse/.bootstrapped')
-else:
-    os.unlink('wheelhouse/.upgrade')
-
-from charms.layer import basic
+from charms.layer import basic  # noqa
 basic.bootstrap_charm_deps()
-basic.init_config_states()
+
+from charmhelpers.core import hookenv  # noqa
+hookenv.atstart(basic.init_config_states)
+hookenv.atexit(basic.clear_config_states)
 
 
 # This will load and run the appropriate @hook and other decorated
@@ -24,5 +18,5 @@ basic.init_config_states()
 #
 # See https://jujucharms.com/docs/stable/authors-charm-building
 # for more information on this pattern.
-from charms.reactive import main
+from charms.reactive import main  # noqa
 main()
diff --git a/layer.yaml b/layer.yaml
index 2e2c3e2a..d265da56 100644
--- a/layer.yaml
+++ b/layer.yaml
@@ -1,24 +1,36 @@
-"options":
-  "basic":
-    "use_venv": !!bool "true"
-    "include_system_packages": !!bool "true"
-    "packages": []
-  "openstack-principle": {}
-  "openstack": {}
-  "openstack-api": {}
-  "gnocchi": {}
 "includes":
+- "layer:options"
 - "layer:basic"
 - "layer:openstack"
+- "layer:debug"
+- "interface:tls-certificates"
 - "layer:openstack-principle"
 - "interface:mysql-shared"
 - "interface:rabbitmq"
 - "interface:keystone"
 - "interface:hacluster"
 - "interface:openstack-ha"
+- "layer:tls-client"
 - "layer:openstack-api"
 - "interface:ceph-client"
 - "interface:gnocchi"
 - "interface:memcache"
+"options":
+  "basic":
+    "use_venv": !!bool "true"
+    "include_system_packages": !!bool "true"
+    "packages": []
+    "python_packages": []
+  "tls-client":
+    "ca_certificate_path": ""
+    "client_certificate_path": ""
+    "client_key_path": ""
+    "server_key_path": ""
+    "server_certificate_path": ""
+  "openstack-principle": {}
+  "openstack-api": {}
+  "debug": {}
+  "openstack": {}
+  "gnocchi": {}
 "repo": "https://github.com/openstack/charm-gnocchi"
 "is": "gnocchi"
diff --git a/lib/charm/openstack/gnocchi.py b/lib/charm/openstack/gnocchi.py
index 2ca377b3..be1d34e2 100644
--- a/lib/charm/openstack/gnocchi.py
+++ b/lib/charm/openstack/gnocchi.py
@@ -12,10 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import errno
 import os
-import pwd
-import grp
 import collections
 import subprocess
 
@@ -26,7 +23,6 @@ import charmhelpers.core.host as host
 import charms_openstack.charm
 import charms_openstack.adapters as adapters
 import charms_openstack.ip as os_ip
-import charms_openstack.plugins
 
 
 GNOCCHI_DIR = '/etc/gnocchi'
@@ -65,18 +61,10 @@ charms_openstack.charm.use_defaults('charm.default-select-release')
 @charms_openstack.adapters.config_property
 def log_config(config):
     if ch_utils.snap_install_requested():
-        log_config_file = os.path.join(SNAP_PREFIX,
-                                       'log/gnocchi-api.log')
+        return os.path.join(SNAP_PREFIX,
+                            'log/gnocchi-api.log')
     else:
-        log_config_file = '/var/log/gnocchi/gnocchi-api.log'
-    usr = GnocchiCharm.user
-    gru = GnocchiCharm.group
-    uid = pwd.getpwnam(usr).pw_uid
-    gid = grp.getgrnam(gru).gr_gid
-    # touch file
-    open(log_config_file, 'a').close()
-    os.chown(log_config_file, uid, gid)
-    return log_config_file
+        return '/var/log/gnocchi/gnocchi-api.log'
 
 
 @charms_openstack.adapters.config_property
@@ -87,37 +75,6 @@ def ceph_config(config):
         return CEPH_CONF
 
 
-@charms_openstack.adapters.config_property
-def file_pathbase(config):
-    file_pathbasedir = '/var/lib/gnocchi/measures'
-    # create path if it does not exist
-    try:
-        os.makedirs(file_pathbasedir)
-    except OSError as exc:
-        if exc.errno == errno.EEXIST and os.path.isdir(file_pathbasedir):
-            pass
-        else:
-            raise
-    usr = GnocchiCharm.user
-    gru = GnocchiCharm.group
-    uid = pwd.getpwnam(usr).pw_uid
-    gid = grp.getgrnam(gru).gr_gid
-    os.chown(file_pathbasedir, uid, gid)
-    # mount tmpfs unless already mounted
-    do_mount = 1
-    cmd_out = subprocess.check_output(['cat', '/proc/mounts']).decode('utf-8')
-    lines = cmd_out.split('\n')
-    for line in lines:
-        if file_pathbasedir in line:
-            do_mount = 0
-            break
-    tmpfs_size = 'size=128m'
-    if do_mount == 1:
-        subprocess.check_call(['mount', '-t', 'tmpfs', '-o', tmpfs_size,
-                               'tmpfs', file_pathbasedir])
-    return file_pathbasedir
-
-
 # TODO(jamespage): charms.openstack
 class StorageCephRelationAdapter(adapters.OpenStackRelationAdapter):
 
@@ -147,15 +104,14 @@ class GnocchiCharmRelationAdapaters(adapters.OpenStackAPIRelationAdapters):
     """
 
     relation_adapters = {
-        'storage_ceph': charms_openstack.plugins.CephRelationAdapter,
+        'storage_ceph': StorageCephRelationAdapter,
         'shared_db': adapters.DatabaseRelationAdapter,
         'cluster': adapters.PeerHARelationAdapter,
         'coordinator_memcached': adapters.MemcacheRelationAdapter,
     }
 
 
-class GnochiCharmBase(charms_openstack.charm.HAOpenStackCharm,
-                      charms_openstack.plugins.BaseOpenStackCephCharm):
+class GnochiCharmBase(charms_openstack.charm.HAOpenStackCharm):
 
     """
     Base class for shared charm functions for all package types
@@ -273,7 +229,6 @@ class GnocchiCharm(GnochiCharmBase):
     }
 
     sync_cmd = ['gnocchi-upgrade',
-                '--config-file=' + GNOCCHI_CONF,
                 '--log-file=/var/log/gnocchi/gnocchi-upgrade.log']
 
     # User and group for permissions management
@@ -351,7 +306,6 @@ class GnocchiSnapCharm(GnochiCharmBase):
 
     sync_cmd = [
         '/snap/bin/gnocchi.upgrade',
-        '--config-file=' + GNOCCHI_CONF,
         '--log-file=/var/snap/gnocchi/common/log/gnocchi-upgrade.log'
     ]
 
diff --git a/lib/charms/layer/__init__.py b/lib/charms/layer/__init__.py
index 9d1048d0..a8e0c640 100644
--- a/lib/charms/layer/__init__.py
+++ b/lib/charms/layer/__init__.py
@@ -1,21 +1,60 @@
-import os
+import sys
+from importlib import import_module
+from pathlib import Path
 
 
-class LayerOptions(dict):
-    def __init__(self, layer_file, section=None):
-        import yaml  # defer, might not be available until bootstrap
-        with open(layer_file) as f:
-            layer = yaml.safe_load(f.read())
-        opts = layer.get('options', {})
-        if section and section in opts:
-            super(LayerOptions, self).__init__(opts.get(section))
+def import_layer_libs():
+    """
+    Ensure that all layer libraries are imported.
+
+    This makes it possible to do the following:
+
+        from charms import layer
+
+        layer.foo.do_foo_thing()
+
+    Note: This function must be called after bootstrap.
+    """
+    for module_file in Path('lib/charms/layer').glob('*'):
+        module_name = module_file.stem
+        if module_name in ('__init__', 'basic', 'execd') or not (
+            module_file.suffix == '.py' or module_file.is_dir()
+        ):
+            continue
+        import_module('charms.layer.{}'.format(module_name))
+
+
+# Terrible hack to support the old terrible interface.
+# Try to get people to call layer.options.get() instead so
+# that we can remove this garbage.
+# Cribbed from https://stackoverfLow.com/a/48100440/4941864
+class OptionsBackwardsCompatibilityHack(sys.modules[__name__].__class__):
+    def __call__(self, section=None, layer_file=None):
+        if layer_file is None:
+            return self.get(section=section)
         else:
-            super(LayerOptions, self).__init__(opts)
+            return self.get(section=section,
+                            layer_file=Path(layer_file))
+
 
+def patch_options_interface():
+    from charms.layer import options
+    if sys.version_info.minor >= 5:
+        options.__class__ = OptionsBackwardsCompatibilityHack
+    else:
+        # Py 3.4 doesn't support changing the __class__, so we have to do it
+        # another way.  The last line is needed because we already have a
+        # reference that doesn't get updated with sys.modules.
+        name = options.__name__
+        hack = OptionsBackwardsCompatibilityHack(name)
+        hack.get = options.get
+        sys.modules[name] = hack
+        sys.modules[__name__].options = hack
 
-def options(section=None, layer_file=None):
-    if not layer_file:
-        base_dir = os.environ.get('JUJU_CHARM_DIR', os.getcwd())
-        layer_file = os.path.join(base_dir, 'layer.yaml')
 
-    return LayerOptions(layer_file, section)
+try:
+    patch_options_interface()
+except ImportError:
+    # This may fail if pyyaml hasn't been installed yet. But in that
+    # case, the bootstrap logic will try it again once it has.
+    pass
diff --git a/lib/charms/layer/basic.py b/lib/charms/layer/basic.py
index f1ec007b..1a6ea9fc 100644
--- a/lib/charms/layer/basic.py
+++ b/lib/charms/layer/basic.py
@@ -5,6 +5,7 @@ from glob import glob
 from subprocess import check_call, CalledProcessError
 from time import sleep
 
+from charms import layer
 from charms.layer.execd import execd_preinstall
 
 
@@ -35,9 +36,28 @@ def bootstrap_charm_deps():
     vbin = os.path.join(venv, 'bin')
     vpip = os.path.join(vbin, 'pip')
     vpy = os.path.join(vbin, 'python')
-    if os.path.exists('wheelhouse/.bootstrapped'):
+    hook_name = os.path.basename(sys.argv[0])
+    is_bootstrapped = os.path.exists('wheelhouse/.bootstrapped')
+    is_charm_upgrade = hook_name == 'upgrade-charm'
+    is_series_upgrade = hook_name == 'post-series-upgrade'
+    post_upgrade = os.path.exists('wheelhouse/.upgrade')
+    is_upgrade = not post_upgrade and (is_charm_upgrade or is_series_upgrade)
+    if is_bootstrapped and not is_upgrade:
         activate_venv()
+        # the .upgrade file prevents us from getting stuck in a loop
+        # when re-execing to activate the venv; at this point, we've
+        # activated the venv, so it's safe to clear it
+        if post_upgrade:
+            os.unlink('wheelhouse/.upgrade')
         return
+    if is_series_upgrade and os.path.exists(venv):
+        # series upgrade should do a full clear of the venv, rather than just
+        # updating it, to bring in updates to Python itself
+        shutil.rmtree(venv)
+    if is_upgrade:
+        if os.path.exists('wheelhouse/.bootstrapped'):
+            os.unlink('wheelhouse/.bootstrapped')
+        open('wheelhouse/.upgrade', 'w').close()
     # bootstrap wheelhouse
     if os.path.exists('wheelhouse'):
         with open('/root/.pydistutils.cfg', 'w') as fp:
@@ -53,9 +73,11 @@ def bootstrap_charm_deps():
             'python3-setuptools',
             'python3-yaml',
             'python3-dev',
+            'python3-wheel',
+            'build-essential',
         ])
-        from charms import layer
-        cfg = layer.options('basic')
+        from charms.layer import options
+        cfg = options.get('basic')
         # include packages defined in layer.yaml
         apt_install(cfg.get('packages', []))
         # if we're using a venv, set it up
@@ -82,16 +104,41 @@ def bootstrap_charm_deps():
         # https://github.com/pypa/pip/issues/56
         check_call([pip, 'install', '-U', '--no-index', '-f', 'wheelhouse',
                     'pip'])
+        # per https://github.com/juju-solutions/layer-basic/issues/110
+        # this replaces the setuptools that was copied over from the system on
+        # venv create with latest setuptools and adds setuptools_scm
+        check_call([pip, 'install', '-U', '--no-index', '-f', 'wheelhouse',
+                    'setuptools', 'setuptools-scm'])
         # install the rest of the wheelhouse deps
         check_call([pip, 'install', '-U', '--no-index', '-f', 'wheelhouse'] +
                    glob('wheelhouse/*'))
+        # re-enable installation from pypi
+        os.remove('/root/.pydistutils.cfg')
+        # install python packages from layer options
+        if cfg.get('python_packages'):
+            check_call([pip, 'install', '-U'] + cfg.get('python_packages'))
         if not cfg.get('use_venv'):
             # restore system pip to prevent `pip3 install -U pip`
             # from changing it
             if os.path.exists('/usr/bin/pip.save'):
                 shutil.copy2('/usr/bin/pip.save', '/usr/bin/pip')
                 os.remove('/usr/bin/pip.save')
-        os.remove('/root/.pydistutils.cfg')
+        # setup wrappers to ensure envs are used for scripts
+        shutil.copy2('bin/charm-env', '/usr/local/sbin/')
+        for wrapper in ('charms.reactive', 'charms.reactive.sh',
+                        'chlp', 'layer_option'):
+            src = os.path.join('/usr/local/sbin', 'charm-env')
+            dst = os.path.join('/usr/local/sbin', wrapper)
+            if not os.path.exists(dst):
+                os.symlink(src, dst)
+        if cfg.get('use_venv'):
+            shutil.copy2('bin/layer_option', vbin)
+        else:
+            shutil.copy2('bin/layer_option', '/usr/local/bin/')
+        # re-link the charm copy to the wrapper in case charms
+        # call bin/layer_option directly (as was the old pattern)
+        os.remove('bin/layer_option')
+        os.symlink('/usr/local/sbin/layer_option', 'bin/layer_option')
         # flag us as having already bootstrapped so we don't do it again
         open('wheelhouse/.bootstrapped', 'w').close()
         # Ensure that the newly bootstrapped libs are available.
@@ -118,15 +165,17 @@ def activate_venv():
     This will ensure that modules installed in the charm's
     virtual environment are available to the action.
     """
+    from charms.layer import options
     venv = os.path.abspath('../.venv')
     vbin = os.path.join(venv, 'bin')
     vpy = os.path.join(vbin, 'python')
-    from charms import layer
-    cfg = layer.options('basic')
-    if cfg.get('use_venv') and '.venv' not in sys.executable:
+    use_venv = options.get('basic', 'use_venv')
+    if use_venv and '.venv' not in sys.executable:
         # activate the venv
         os.environ['PATH'] = ':'.join([vbin, os.environ['PATH']])
         reload_interpreter(vpy)
+    layer.patch_options_interface()
+    layer.import_layer_libs()
 
 
 def reload_interpreter(python):
@@ -164,6 +213,12 @@ def apt_install(packages):
         except CalledProcessError:
             if attempt == 2:  # third attempt
                 raise
+            try:
+                # sometimes apt-get update needs to be run
+                check_call(['apt-get', 'update'])
+            except CalledProcessError:
+                # sometimes it's a dpkg lock issue
+                pass
             sleep(5)
         else:
             break
@@ -190,7 +245,6 @@ def init_config_states():
         toggle_state('config.set.{}'.format(opt), config.get(opt))
         toggle_state('config.default.{}'.format(opt),
                      config.get(opt) == config_defaults[opt])
-    hookenv.atexit(clear_config_states)
 
 
 def clear_config_states():
diff --git a/metadata.yaml b/metadata.yaml
index 243ff372..81c89b27 100644
--- a/metadata.yaml
+++ b/metadata.yaml
@@ -9,12 +9,15 @@
   metrics and resources information and history.
 "tags":
 - "openstack"
+- "misc"
 "series":
 - "xenial"
 - "bionic"
-- "artful"
-- "trusty"
+- "cosmic"
+- "disco"
 "requires":
+  "certificates":
+    "interface": "tls-certificates"
   "shared-db":
     "interface": "mysql-shared"
   "amqp":
diff --git a/reactive/gnocchi_handlers.py b/reactive/gnocchi_handlers.py
index 2c9d9773..d118d114 100644
--- a/reactive/gnocchi_handlers.py
+++ b/reactive/gnocchi_handlers.py
@@ -12,12 +12,16 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import os
+
 import charms_openstack.charm as charm
 import charms.reactive as reactive
 
 import charm.openstack.gnocchi as gnocchi  # noqa
 
+import charmhelpers.contrib.storage.linux.ceph as ceph_helper
 import charmhelpers.core.hookenv as hookenv
+import charmhelpers.core.host as host
 
 charm.use_defaults(
     'charm.installed',
@@ -81,14 +85,22 @@ def storage_ceph_connected(ceph):
 
 @reactive.when('storage-ceph.available')
 def configure_ceph(ceph):
-    with charm.provide_charm_instance() as charm_instance:
-        charm_instance.configure_ceph_keyring(ceph.key())
+    with charm.provide_charm_instance() as charm_class:
+        # TODO(jamespage): refactor to avoid massaging helper
+        ceph_helper.KEYRING = charm_class.ceph_keyring
+        host.mkdir(os.path.dirname(charm_class.ceph_keyring))
+        ceph_helper.ensure_ceph_keyring(service=hookenv.service_name(),
+                                        key=ceph.key(),
+                                        user=charm_class.gnocchi_user,
+                                        group=charm_class.gnocchi_group)
 
 
 @reactive.when_not('storage-ceph.connected')
 def storage_ceph_disconnected():
-    with charm.provide_charm_instance() as charm_instance:
-        charm_instance.delete_ceph_keyring()
+    with charm.provide_charm_instance() as charm_class:
+        # TODO(jamespage): refactor to avoid massaging helper
+        ceph_helper.KEYRING = charm_class.ceph_keyring
+        ceph_helper.delete_keyring(hookenv.service_name())
 
 
 @reactive.when('metric-service.connected')
diff --git a/reactive/layer_openstack.py b/reactive/layer_openstack.py
index 65337bb2..21f02206 100644
--- a/reactive/layer_openstack.py
+++ b/reactive/layer_openstack.py
@@ -13,7 +13,7 @@ def default_install():
     The instance automagically becomes the derived OpenStackCharm instance.
     The kv() key charmers.openstack-release-version' is used to cache the
     release being used for this charm.  It is determined by the
-    default_select_release() function below, unless this is overriden by
+    default_select_release() function below, unless this is overridden by
     the charm author
     """
     unitdata.kv().unset(defaults.OPENSTACK_RELEASE_KEY)
@@ -64,3 +64,28 @@ def run_default_update_status():
     with charm.provide_charm_instance() as instance:
         instance.assess_status()
     reactive.remove_state('run-default-update-status')
+
+
+@reactive.when('storage-backend.connected',
+               'charms.openstack.do-default-storage-backend.connected')
+def run_storage_backend():
+    with charm.provide_charm_instance() as instance:
+        instance.send_storage_backend_data()
+
+
+# Series upgrade hooks are a special case and reacting to the hook directly
+# makes sense as we may not want other charm code to run
+@reactive.hook('pre-series-upgrade')
+def default_pre_series_upgrade():
+    """Default handler for pre-series-upgrade.
+    """
+    with charm.provide_charm_instance() as instance:
+        instance.series_upgrade_prepare()
+
+
+@reactive.hook('post-series-upgrade')
+def default_post_series_upgrade():
+    """Default handler for post-series-upgrade.
+    """
+    with charm.provide_charm_instance() as instance:
+        instance.series_upgrade_complete()
diff --git a/reactive/layer_openstack_api.py b/reactive/layer_openstack_api.py
index 8111bb9c..13d1247e 100644
--- a/reactive/layer_openstack_api.py
+++ b/reactive/layer_openstack_api.py
@@ -62,3 +62,25 @@ def default_setup_endpoint_available(keystone):
     with charm.provide_charm_instance() as instance:
         instance.configure_ssl(keystone)
         instance.assess_status()
+
+
+@reactive.when('certificates.available')
+def default_setup_certificates(tls):
+    """When the identity-service interface is available, this default
+    handler switches on the SSL support.
+    """
+    with charm.provide_charm_instance() as instance:
+        for cn, req in instance.get_certificate_requests().items():
+            tls.add_request_server_cert(cn, req['sans'])
+        tls.request_server_certs()
+        instance.assess_status()
+
+
+@reactive.when('certificates.batch.cert.available')
+def default_setup_endpoint_available(tls):
+    """When the identity-service interface is available, this default
+    handler switches on the SSL support.
+    """
+    with charm.provide_charm_instance() as instance:
+        instance.configure_ssl(tls)
+        instance.assess_status()
diff --git a/rebuild b/rebuild
deleted file mode 100644
index 551ab478..00000000
--- a/rebuild
+++ /dev/null
@@ -1,5 +0,0 @@
-# This file is used to trigger rebuilds
-# when dependencies of the charm change,
-# but nothing in the charm needs to.
-# simply change the uuid to something new
-120650ec-5aab-11e9-a87e-fbc92e9be59b
diff --git a/repo-info b/repo-info
index 11c2b433..69367159 100644
--- a/repo-info
+++ b/repo-info
@@ -1,6 +1,6 @@
-commit-sha-1: 1eb98df671de730c199ef42b432d5229ca33a0c1
-commit-short: 1eb98df
+commit-sha-1: 6d429c05f327d3ef46358d573a105fc022f23a56
+commit-short: 6d429c0
 branch: HEAD
 remote: https://github.com/openstack/charm-gnocchi
-info-generated: Fri Feb  9 14:49:01 UTC 2018
+info-generated: Tue May  7 12:28:07 UTC 2019
 note: This file should exist only in a built or released charm artifact (not in the charm source code tree).
diff --git a/requirements.txt b/requirements.txt
index 20f335d2..56253896 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,7 +1,5 @@
-# This file is managed centrally.  If you find the need to modify this as a
-# one-off, please don't.  Intead, consult #openstack-charms and ask about
-# requirements management in charms via bot-control.  Thank you.
-#
-# Build requirements
-charm-tools>=2.4.4
+# Requirements to build the layer
+charm-tools
+ruamel.yaml==0.10.12
 simplejson
+flake8
diff --git a/run-func-tests.sh b/run-func-tests.sh
deleted file mode 100755
index da261380..00000000
--- a/run-func-tests.sh
+++ /dev/null
@@ -1,72 +0,0 @@
-#!/bin/bash -x
-set -e
-
-cleanup(){
-    type -t indexer_stop >/dev/null && indexer_stop || true
-    type -t storage_stop >/dev/null && storage_stop || true
-}
-trap cleanup EXIT
-
-check_empty_var() {
-    local x=$(eval echo `echo \\$${1}`)
-    if [ -z "$x" ]; then
-        echo "Variable \$${1} is unset"
-        exit 15
-    fi
-}
-
-PYTHON_VERSION_MAJOR=$(python -c 'import sys; print(sys.version_info.major)')
-
-GNOCCHI_TEST_STORAGE_DRIVERS=${GNOCCHI_TEST_STORAGE_DRIVERS:-file}
-GNOCCHI_TEST_INDEXER_DRIVERS=${GNOCCHI_TEST_INDEXER_DRIVERS:-postgresql}
-for storage in ${GNOCCHI_TEST_STORAGE_DRIVERS}; do
-    if [ "$storage" == "swift" ] && [ "$PYTHON_VERSION_MAJOR" == "3" ]; then
-        echo "WARNING: swift does not support python 3 skipping"
-        continue
-    fi
-    for indexer in ${GNOCCHI_TEST_INDEXER_DRIVERS}; do
-        unset STORAGE_URL
-        unset INDEXER_URL
-        case $storage in
-            ceph)
-                eval $(pifpaf -e STORAGE run ceph)
-                check_empty_var STORAGE_URL
-                rados -c $STORAGE_CEPH_CONF mkpool gnocchi
-                STORAGE_URL=ceph://$STORAGE_CEPH_CONF
-                ;;
-            s3)
-                if ! which s3rver >/dev/null 2>&1
-                then
-                    mkdir -p npm-s3rver
-                    export NPM_CONFIG_PREFIX=npm-s3rver
-                    npm install s3rver --global
-                    export PATH=$PWD/npm-s3rver/bin:$PATH
-                fi
-                eval $(pifpaf -e STORAGE run s3rver)
-                ;;
-            file)
-                STORAGE_URL=file://
-                ;;
-
-            swift|redis)
-                eval $(pifpaf -e STORAGE run $storage)
-                ;;
-            *)
-                echo "Unsupported storage backend by functional tests: $storage"
-                exit 1
-                ;;
-        esac
-
-        check_empty_var STORAGE_URL
-
-        eval $(pifpaf -e INDEXER run $indexer)
-        check_empty_var INDEXER_URL
-
-        export GNOCCHI_SERVICE_TOKEN="" # Just make gabbi happy
-        export GNOCCHI_AUTHORIZATION="basic YWRtaW46" # admin in base64
-        export GNOCCHI_TEST_PATH=gnocchi/tests/functional_live
-        pifpaf -e GNOCCHI run gnocchi --indexer-url $INDEXER_URL --storage-url $STORAGE_URL --coordination-driver redis -- ./tools/pretty_tox.sh $*
-
-        cleanup
-    done
-done
diff --git a/templates/gnocchi.conf b/templates/gnocchi.conf
index 66f94fd8..1734545c 100644
--- a/templates/gnocchi.conf
+++ b/templates/gnocchi.conf
@@ -41,3 +41,5 @@ ceph_conffile = {{ options.ceph_config }}
 {%- endif %}
 
 {% include "parts/section-keystone-authtoken" %}
+
+{% include "parts/section-oslo-middleware" %}
diff --git a/templates/parts/section-database b/templates/parts/section-database
index 7046295d..a4fa7e71 100644
--- a/templates/parts/section-database
+++ b/templates/parts/section-database
@@ -1,5 +1,5 @@
 {% if shared_db.host -%}
 [database]
 
-connection = {{ shared_db.type }}://{{ shared_db.username }}:{{ shared_db.password }}@{{ shared_db.host }}/{{ shared_db.database }}{% if database_ssl_ca %}?ssl_ca={{ database_ssl_ca }}{% if database_ssl_cert %}&ssl_cert={{ database_ssl_cert }}&ssl_key={{ database_ssl_key }}{% endif %}{% endif %}
+connection = {{ shared_db.uri }}
 {% endif -%}
diff --git a/templates/parts/section-keystone-authtoken b/templates/parts/section-keystone-authtoken
index 40354c62..dffe478c 100644
--- a/templates/parts/section-keystone-authtoken
+++ b/templates/parts/section-keystone-authtoken
@@ -3,8 +3,13 @@
 auth_uri = {{ identity_service.service_protocol }}://{{ identity_service.service_host }}:{{ identity_service.service_port }}
 auth_url = {{ identity_service.auth_protocol }}://{{ identity_service.auth_host }}:{{ identity_service.auth_port }}
 auth_type = password
+{% if identity_service.service_domain -%}
+project_domain_name = {{ identity_service.service_domain }}
+user_domain_name = {{ identity_service.service_domain }}
+{% else %}
 project_domain_name = default
 user_domain_name = default
+{% endif -%}
 project_name = services
 username = {{ identity_service.service_username }}
 password = {{ identity_service.service_password }}
diff --git a/tests/basic_deployment.py b/tests/basic_deployment.py
index 6dcd0881..1e876524 100644
--- a/tests/basic_deployment.py
+++ b/tests/basic_deployment.py
@@ -36,7 +36,7 @@ class GnocchiCharmDeployment(amulet_deployment.OpenStackAmuletDeployment):
     no_origin = ['memcached', 'percona-cluster', 'rabbitmq-server',
                  'ceph-mon', 'ceph-osd']
 
-    def __init__(self, series, openstack=None, source=None, stable=False,
+    def __init__(self, series, openstack=None, source=None, stable=True,
                  snap_source=None):
         """Deploy the entire test environment."""
         super(GnocchiCharmDeployment, self).__init__(series, openstack,
diff --git a/tox.ini b/tox.ini
index 7e54929b..0e36e84b 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,114 +1,53 @@
+# Source charm: ./src/tox.ini
+# This file is managed centrally by release-tools and should not be modified
+# within individual charm repos.
 [tox]
-minversion = 2.4
-envlist = py{37,27}-{postgresql,mysql}{,-file,-swift,-ceph,-s3},pep8
+envlist = pep8
 skipsdist = True
 
 [testenv]
-skip_install = True
-sitepackages = False
-passenv = LANG GNOCCHI_TEST_* AWS_*
-setenv =
-    GNOCCHI_TEST_STORAGE_DRIVER=file
-    GNOCCHI_TEST_INDEXER_DRIVER=postgresql
-    GNOCCHI_TEST_STORAGE_DRIVERS=file swift ceph s3 redis
-    GNOCCHI_TEST_INDEXER_DRIVERS=postgresql mysql
-    file: GNOCCHI_TEST_STORAGE_DRIVERS=file
-    swift: GNOCCHI_TEST_STORAGE_DRIVERS=swift
-    ceph: GNOCCHI_TEST_STORAGE_DRIVERS=ceph
-    redis: GNOCCHI_TEST_STORAGE_DRIVERS=redis
-    s3: GNOCCHI_TEST_STORAGE_DRIVERS=s3
-    postgresql: GNOCCHI_TEST_INDEXER_DRIVERS=postgresql
-    mysql: GNOCCHI_TEST_INDEXER_DRIVERS=mysql
+setenv = VIRTUAL_ENV={envdir}
+         PYTHONHASHSEED=0
+         AMULET_SETUP_TIMEOUT=5400
+whitelist_externals = juju
+passenv = HOME TERM AMULET_* CS_*
+deps = -r{toxinidir}/test-requirements.txt
+install_command =
+  pip install {opts} {packages}
 
-    GNOCCHI_STORAGE_DEPS=file,swift,test-swift,s3,ceph,redis
-    ceph: GNOCCHI_STORAGE_DEPS=ceph
-    swift: GNOCCHI_STORAGE_DEPS=swift,test-swift
-    file: GNOCCHI_STORAGE_DEPS=file
-    redis: GNOCCHI_STORAGE_DEPS=redis
-    s3: GNOCCHI_STORAGE_DEPS=s3
-
-    GNOCCHI_INDEXER_DEPS=mysql,postgresql
-    mysql: GNOCCHI_INDEXER_DEPS=mysql
-    postgresql: GNOCCHI_INDEXER_DEPS=postgresql
+[testenv:pep8]
+basepython = python2.7
+commands = charm-proof
 
-    # FIXME(sileht): pbr doesn't support url in setup.cfg extras, so we do this crap
-    GNOCCHI_TEST_TARBALLS=http://tarballs.openstack.org/swift/swift-master.tar.gz#egg=swift
-    ceph: GNOCCHI_TEST_TARBALLS=
-    swift: GNOCCHI_TEST_TARBALLS=http://tarballs.openstack.org/swift/swift-master.tar.gz#egg=swift
-    s3: GNOCCHI_TEST_TARBALLS=
-    redis: GNOCCHI_TEST_TARBALLS=
-    file: GNOCCHI_TEST_TARBALLS=
-# NOTE(jd) Install redis as a test dependency since it is used as a
-# coordination driver in functional tests (--coordination-driver is passed to
-# pifpaf)
-deps =
-   -e
-   .[test,redis,prometheus,amqp1,{env:GNOCCHI_STORAGE_DEPS:},{env:GNOCCHI_INDEXER_DEPS:}]
-   {env:GNOCCHI_TEST_TARBALLS:}
-   cliff!=2.9.0
+[testenv:func27-noop]
+# DRY RUN - For Debug
+basepython = python2.7
 commands =
-    {toxinidir}/run-tests.sh {posargs}
-    {toxinidir}/run-func-tests.sh {posargs}
-
-[testenv:py37-postgresql-file-upgrade-from-4.3]
-# We should always recreate since the script upgrade
-# Gnocchi we can't reuse the virtualenv
-recreate = True
-setenv = GNOCCHI_VARIANT=test,postgresql,file
-deps = gnocchi[{env:GNOCCHI_VARIANT}]>=4.3,<4.4
-  pifpaf[gnocchi]>=0.13
-  gnocchiclient>=2.8.0
-  xattr!=0.9.4
-commands = pifpaf --env-prefix INDEXER run postgresql {toxinidir}/run-upgrade-tests.sh {posargs}
-
-[testenv:py27-mysql-ceph-upgrade-from-4.3]
-# We should always recreate since the script upgrade
-# Gnocchi we can't reuse the virtualenv
-recreate = True
-setenv = GNOCCHI_VARIANT=test,mysql,ceph,ceph_recommended_lib
-deps = gnocchi[{env:GNOCCHI_VARIANT}]>=4.3,<4.4
-  gnocchiclient>=2.8.0
-  pifpaf[ceph,gnocchi]>=0.13
-  xattr!=0.9.4
-commands = pifpaf --env-prefix INDEXER run mysql -- pifpaf --env-prefix STORAGE run ceph {toxinidir}/run-upgrade-tests.sh {posargs}
-
-[testenv:pep8]
-deps = hacking>=0.12,<0.13
-commands = flake8
+    bundletester -vl DEBUG -r json -o func-results.json --test-pattern "gate-*" -n --no-destroy
 
-[testenv:py27-cover]
-commands = pifpaf -g GNOCCHI_INDEXER_URL run postgresql -- python setup.py testr --coverage --testr-args="{posargs}"
+[testenv:func27]
+# Run all gate tests which are +x (expected to always pass)
+basepython = python2.7
+commands =
+    bundletester -vl DEBUG -r json -o func-results.json --test-pattern "gate-*" --no-destroy
 
-[flake8]
-exclude = .tox,.eggs,doc,gnocchi/rest/prometheus/remote_pb2.py
-show-source = true
-enable-extensions = H904
+[testenv:func27-smoke]
+# Run a specific test as an Amulet smoke test (expected to always pass)
+basepython = python2.7
+commands =
+    bundletester -vl DEBUG -r json -o func-results.json gate-basic-bionic-queens --no-destroy
 
-[testenv:docs]
-basepython = python3
-## This does not work, see: https://github.com/tox-dev/tox/issues/509
-# deps = {[testenv]deps}
-#        .[postgresql,doc]
-# setenv = GNOCCHI_STORAGE_DEPS=file
-deps =
-    -e
-    .[test,file,postgresql,doc]
-    doc8
-setenv = GNOCCHI_TEST_DEBUG=1
-commands = doc8 --ignore-path doc/source/rest.rst,doc/source/comparison-table.rst doc/source
-           pifpaf -g GNOCCHI_INDEXER_URL run postgresql -- python setup.py build_sphinx -W
+[testenv:func27-dfs]
+# Run all deploy-from-source tests which are +x (may not always pass!)
+basepython = python2.7
+commands =
+    bundletester -vl DEBUG -r json -o func-results.json --test-pattern "dfs-*" --no-destroy
 
-[testenv:docs-gnocchi.xyz]
+[testenv:func27-dev]
+# Run all development test targets which are +x (may not always pass!)
 basepython = python2.7
-whitelist_externals = bash rm
-setenv = GNOCCHI_STORAGE_DEPS=file
-         GNOCCHI_TEST_DEBUG=1
-install_command = pip install -U {opts} {packages}
-deps = {[testenv:docs]deps}
-       setuptools
 commands =
-    rm -rf doc/build/html
-    pifpaf -g GNOCCHI_INDEXER_URL run postgresql -- python setup.py build_sphinx
+    bundletester -vl DEBUG -r json -o func-results.json --test-pattern "dev-*" --no-destroy
 
-[doc8]
-ignore-path = doc/source/rest.rst,doc/source/comparison-table.rst
+[testenv:venv]
+commands = {posargs}
diff --git a/unit_tests/__init__.py b/unit_tests/__init__.py
deleted file mode 100644
index d7887757..00000000
--- a/unit_tests/__init__.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# 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 sys
-
-sys.path.append('src')
-sys.path.append('src/lib')
-
-# Mock out charmhelpers so that we can test without it.
-import charms_openstack.test_mocks  # noqa
-charms_openstack.test_mocks.mock_charmhelpers()
-
-
-def mock_more_stuff():
-    charmhelpers = charms_openstack.test_mocks.charmhelpers
-    sys.modules['charmhelpers.core.hookenv.charm_dir'] = (
-        charmhelpers.core.hookenv.charm_dir
-    )
-    charmhelpers.core.hookenv.charm_dir.return_value = "/tmp"
-    sys.modules['charmhelpers.contrib.storage'] = (
-        charmhelpers.contrib.storage
-    )
-    sys.modules['charmhelpers.contrib.storage.linux'] = (
-        charmhelpers.contrib.storage.linux
-    )
-    sys.modules['charmhelpers.contrib.storage.linux.ceph'] = (
-        charmhelpers.contrib.storage.linux.ceph
-    )
-
-
-mock_more_stuff()
diff --git a/unit_tests/test_gnocchi_handlers.py b/unit_tests/test_gnocchi_handlers.py
deleted file mode 100644
index 99000e2c..00000000
--- a/unit_tests/test_gnocchi_handlers.py
+++ /dev/null
@@ -1,154 +0,0 @@
-# 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.
-
-from __future__ import absolute_import
-from __future__ import print_function
-
-import mock
-
-import charms_openstack.test_utils as test_utils
-
-import reactive.gnocchi_handlers as handlers
-
-
-class TestRegisteredHooks(test_utils.TestRegisteredHooks):
-
-    def test_hooks(self):
-        defaults = [
-            'charm.installed',
-            'shared-db.connected',
-            'ha.connected',
-            'identity-service.connected',
-            'identity-service.available',
-            'config.changed',
-            'update-status',
-            'charm.default-select-release']
-        hook_set = {
-            'when': {
-                'render_config': (
-                    'coordinator-memcached.available',
-                    'shared-db.available',
-                    'identity-service.available',
-                    'storage-ceph.pools.available',
-                ),
-                'init_db': (
-                    'config.rendered',
-                ),
-                'cluster_connected': (
-                    'ha.connected',
-                ),
-                'provide_gnocchi_url': (
-                    'metric-service.connected',
-                    'config.rendered',
-                    'db.synced',
-                ),
-                'configure_ceph': (
-                    'storage-ceph.available',
-                ),
-                'storage_ceph_connected': (
-                    'storage-ceph.connected',
-                ),
-            },
-            'when_not': {
-                'storage_ceph_disconnected': (
-                    'storage-ceph.connected',
-                ),
-                'disable_services': (
-                    'config.rendered',
-                ),
-                'cluster_connected': (
-                    'ha.available',
-                ),
-                'init_db': (
-                    'db.synced',
-                ),
-            },
-        }
-        # test that the hooks were registered via the
-        # reactive.gnocchi_handlers
-        self.registered_hooks_test_helper(handlers, hook_set, defaults)
-
-
-class TestHandlers(test_utils.PatchHelper):
-
-    def setUp(self):
-        super(TestHandlers, self).setUp()
-        self.gnocchi_charm = mock.MagicMock()
-        self.gnocchi_charm.gnocchi_user = 'gnocchi'
-        self.gnocchi_charm.gnocchi_group = 'gnocchi'
-        self.patch_object(handlers.charm, 'provide_charm_instance',
-                          new=mock.MagicMock())
-        self.provide_charm_instance().__enter__.return_value = \
-            self.gnocchi_charm
-        self.provide_charm_instance().__exit__.return_value = None
-
-    def test_render_stuff(self):
-        handlers.render_config('arg1', 'arg2')
-        self.gnocchi_charm.render_with_interfaces.assert_called_once_with(
-            ('arg1', 'arg2')
-        )
-        self.gnocchi_charm.assess_status.assert_called_once_with()
-        self.gnocchi_charm.enable_webserver_site.assert_called_once_with()
-
-    def test_init_db(self):
-        handlers.init_db()
-        self.gnocchi_charm.db_sync.assert_called_once_with()
-
-    @mock.patch.object(handlers, 'hookenv')
-    def test_storage_ceph_connected(self, hookenv):
-        mock_ceph = mock.MagicMock()
-        hookenv.service_name.return_value = 'mygnocchi'
-        handlers.storage_ceph_connected(mock_ceph)
-        mock_ceph.create_pool.assert_called_once_with(
-            'mygnocchi',
-        )
-
-    def test_configure_ceph(self):
-        mock_ceph = mock.MagicMock()
-        mock_ceph.key.return_value = 'testkey'
-        handlers.configure_ceph(mock_ceph)
-        self.gnocchi_charm.configure_ceph_keyring.assert_called_once_with(
-            'testkey')
-        mock_ceph.key.assert_called_once_with()
-
-    def test_storage_ceph_disconnected(self):
-        handlers.storage_ceph_disconnected()
-        self.gnocchi_charm.delete_ceph_keyring.assert_called_once_with()
-
-    @mock.patch.object(handlers.reactive.flags, 'is_flag_set')
-    def test_provide_gnocchi_url(self, mock_is_flag_set):
-        mock_is_flag_set.return_value = False
-        mock_gnocchi = mock.MagicMock()
-        self.gnocchi_charm.public_url = "http://gnocchi:8041"
-        handlers.provide_gnocchi_url(mock_gnocchi)
-        mock_gnocchi.set_gnocchi_url.assert_called_once_with(
-            "http://gnocchi:8041"
-        )
-
-    @mock.patch.object(handlers.reactive.flags, 'is_flag_set')
-    def test_provide_gnocchi_url_ha_connected(self, mock_is_flag_set):
-        mock_is_flag_set.side_effect = [True, False]
-        mock_gnocchi = mock.MagicMock()
-        handlers.provide_gnocchi_url(mock_gnocchi)
-        mock_gnocchi.set_gnocchi_url.assert_not_called()
-
-    @mock.patch.object(handlers.reactive.flags, 'is_flag_set')
-    def test_provide_gnocchi_url_ha_available(self, mock_is_flag_set):
-        mock_is_flag_set.side_effect = [True, True]
-        mock_gnocchi = mock.MagicMock()
-        self.gnocchi_charm.public_url = "http://gnocchi:8041"
-        handlers.provide_gnocchi_url(mock_gnocchi)
-        mock_gnocchi.set_gnocchi_url.assert_called_once_with(
-            "http://gnocchi:8041"
-        )
-- 
GitLab