From 9c12812735cc0b0a6d1bc74ca595d931d43f3245 Mon Sep 17 00:00:00 2001
From: Alex Kavanagh <alex@ajkavanagh.co.uk>
Date: Mon, 15 Oct 2018 16:39:39 +0100
Subject: [PATCH] Switch the charm to support py3

Some major changes:
* the charm has been rebased (from a Python perspective) to be rooted in
  the charm directory.  This is a single root.
* Imports have been changed so that the don't add lots of imports to the
  namespace of the module doing the import.
* The code that used to run at module import time has been made lazy
  such that it only has to run if the relevant functions are called.
  This includes restart_on_change parameters, the harden function and
  the parameters to the guard_map.  Appropriate changes will be
  submitted to charm-helpers.
* Several tests had to be re-written as (incorrect) mocking meant that
  text fixtures didn't actually match what the code was doing.  Thus,
  the tests were meaningless.
* This has had a net positive impact on the unit tests wrt to importing
  modules and mocking.

Change-Id: Id07d9d1caaa9b29453a63c2e49ba831071e9457f
---
 actions/actions.py                            |  37 +-
 actions/openstack_upgrade.py                  |  46 +-
 charm-helpers-hooks.yaml                      |   2 +-
 .../charmhelpers => charmhelpers}/__init__.py |   0
 .../cli/__init__.py                           |   0
 .../cli/benchmark.py                          |   0
 .../cli/commands.py                           |   0
 .../cli/hookenv.py                            |   0
 .../charmhelpers => charmhelpers}/cli/host.py |   0
 .../cli/unitdata.py                           |   0
 .../contrib/__init__.py                       |   0
 .../contrib/charmsupport/__init__.py          |   0
 .../contrib/charmsupport/nrpe.py              |   0
 .../contrib/charmsupport/volumes.py           |   0
 .../contrib/hahelpers/__init__.py             |   0
 .../contrib/hahelpers/apache.py               |   0
 .../contrib/hahelpers/cluster.py              |   0
 .../contrib/hardening/README.hardening.md     |   0
 .../contrib/hardening/__init__.py             |   0
 .../contrib/hardening/apache/__init__.py      |   0
 .../hardening/apache/checks/__init__.py       |   0
 .../contrib/hardening/apache/checks/config.py |   0
 .../apache/templates/99-hardening.conf        |   0
 .../hardening/apache/templates/__init__.py    |   0
 .../hardening/apache/templates/alias.conf     |   0
 .../contrib/hardening/audits/__init__.py      |   0
 .../contrib/hardening/audits/apache.py        |   0
 .../contrib/hardening/audits/apt.py           |   0
 .../contrib/hardening/audits/file.py          |   0
 .../contrib/hardening/defaults/__init__.py    |   0
 .../contrib/hardening/defaults/apache.yaml    |   0
 .../hardening/defaults/apache.yaml.schema     |   0
 .../contrib/hardening/defaults/mysql.yaml     |   0
 .../hardening/defaults/mysql.yaml.schema      |   0
 .../contrib/hardening/defaults/os.yaml        |   0
 .../contrib/hardening/defaults/os.yaml.schema |   0
 .../contrib/hardening/defaults/ssh.yaml       |   0
 .../hardening/defaults/ssh.yaml.schema        |   0
 .../contrib/hardening/harden.py               |  13 +-
 .../contrib/hardening/host/__init__.py        |   0
 .../contrib/hardening/host/checks/__init__.py |   0
 .../contrib/hardening/host/checks/apt.py      |   0
 .../contrib/hardening/host/checks/limits.py   |   0
 .../contrib/hardening/host/checks/login.py    |   0
 .../hardening/host/checks/minimize_access.py  |   0
 .../contrib/hardening/host/checks/pam.py      |   0
 .../contrib/hardening/host/checks/profile.py  |   0
 .../hardening/host/checks/securetty.py        |   0
 .../hardening/host/checks/suid_sgid.py        |   0
 .../contrib/hardening/host/checks/sysctl.py   |   0
 .../hardening/host/templates/10.hardcore.conf |   0
 .../hardening/host/templates/99-hardening.sh  |   0
 .../host/templates/99-juju-hardening.conf     |   0
 .../hardening/host/templates/__init__.py      |   0
 .../hardening/host/templates/login.defs       |   0
 .../contrib/hardening/host/templates/modules  |   0
 .../hardening/host/templates/passwdqc.conf    |   0
 .../host/templates/pinerolo_profile.sh        |   0
 .../hardening/host/templates/securetty        |   0
 .../contrib/hardening/host/templates/tally2   |   0
 .../contrib/hardening/mysql/__init__.py       |   0
 .../hardening/mysql/checks/__init__.py        |   0
 .../contrib/hardening/mysql/checks/config.py  |   0
 .../hardening/mysql/templates/__init__.py     |   0
 .../hardening/mysql/templates/hardening.cnf   |   0
 .../contrib/hardening/ssh/__init__.py         |   0
 .../contrib/hardening/ssh/checks/__init__.py  |   0
 .../contrib/hardening/ssh/checks/config.py    |   0
 .../hardening/ssh/templates/__init__.py       |   0
 .../hardening/ssh/templates/ssh_config        |   0
 .../hardening/ssh/templates/sshd_config       |   0
 .../contrib/hardening/templating.py           |   0
 .../contrib/hardening/utils.py                |   0
 .../contrib/network/__init__.py               |   0
 .../contrib/network/ip.py                     |   0
 .../contrib/openstack/__init__.py             |   0
 .../contrib/openstack/alternatives.py         |   0
 .../contrib/openstack/amulet/__init__.py      |   0
 .../contrib/openstack/amulet/deployment.py    |   0
 .../contrib/openstack/amulet/utils.py         |   0
 .../contrib/openstack/cert_utils.py           |   0
 .../contrib/openstack/context.py              |   0
 .../contrib/openstack/exceptions.py           |   0
 .../contrib/openstack/files/__init__.py       |   0
 .../contrib/openstack/files/check_haproxy.sh  |   0
 .../files/check_haproxy_queue_depth.sh        |   0
 .../contrib/openstack/ha/__init__.py          |   0
 .../contrib/openstack/ha/utils.py             |   0
 .../contrib/openstack/ip.py                   |   0
 .../contrib/openstack/keystone.py             |   0
 .../contrib/openstack/neutron.py              |   0
 .../contrib/openstack/ssh_migrations.py       |   0
 .../contrib/openstack/templates/__init__.py   |   0
 .../contrib/openstack/templates/ceph.conf     |   0
 .../contrib/openstack/templates/git.upstart   |   0
 .../contrib/openstack/templates/haproxy.cfg   |   0
 .../openstack/templates/memcached.conf        |   0
 .../templates/openstack_https_frontend        |   0
 .../templates/openstack_https_frontend.conf   |   0
 .../templates/section-keystone-authtoken      |   0
 .../section-keystone-authtoken-legacy         |   0
 .../section-keystone-authtoken-mitaka         |   0
 .../openstack/templates/section-oslo-cache    |   0
 .../templates/section-oslo-middleware         |   0
 .../templates/section-oslo-notifications      |   0
 .../openstack/templates/section-rabbitmq-oslo |   0
 .../openstack/templates/section-zeromq        |   0
 .../templates/wsgi-openstack-api.conf         |   0
 .../templates/wsgi-openstack-metadata.conf    |   0
 .../contrib/openstack/templating.py           |   0
 .../contrib/openstack/utils.py                |  18 +-
 .../contrib/openstack/vaultlocker.py          |   0
 .../contrib/peerstorage/__init__.py           |   0
 .../contrib/python/__init__.py                |   0
 .../contrib/python/packages.py                |   0
 .../contrib/storage/__init__.py               |   0
 .../contrib/storage/linux/__init__.py         |   0
 .../contrib/storage/linux/bcache.py           |   0
 .../contrib/storage/linux/ceph.py             |   0
 .../contrib/storage/linux/loopback.py         |   0
 .../contrib/storage/linux/lvm.py              |   0
 .../contrib/storage/linux/utils.py            |   0
 .../core/__init__.py                          |   0
 .../core/decorators.py                        |   0
 .../core/files.py                             |   0
 .../core/fstab.py                             |   0
 .../core/hookenv.py                           |   0
 .../core/host.py                              |   0
 .../core/host_factory/__init__.py             |   0
 .../core/host_factory/centos.py               |   0
 .../core/host_factory/ubuntu.py               |   0
 .../core/hugepage.py                          |   0
 .../core/kernel.py                            |   0
 .../core/kernel_factory/__init__.py           |   0
 .../core/kernel_factory/centos.py             |   0
 .../core/kernel_factory/ubuntu.py             |   0
 .../core/services/__init__.py                 |   0
 .../core/services/base.py                     |   0
 .../core/services/helpers.py                  |   0
 .../core/strutils.py                          |   0
 .../core/sysctl.py                            |   0
 .../core/templating.py                        |   0
 .../core/unitdata.py                          |   0
 .../fetch/__init__.py                         |   0
 .../fetch/archiveurl.py                       |   0
 .../fetch/bzrurl.py                           |   0
 .../fetch/centos.py                           |   0
 .../fetch/giturl.py                           |   0
 .../fetch/snap.py                             |   0
 .../fetch/ubuntu.py                           |   0
 .../osplatform.py                             |   0
 .../payload/__init__.py                       |   0
 .../payload/archive.py                        |   0
 .../payload/execd.py                          |   0
 files/README.txt                              |   3 +
 hooks/install                                 |   2 +-
 hooks/nova_cc_common.py                       |  77 ++
 hooks/nova_cc_context.py                      | 272 +++--
 hooks/nova_cc_hooks.py                        | 998 +++++++++---------
 hooks/nova_cc_utils.py                        | 899 ++++++++--------
 templates/icehouse/nova.conf                  |   6 +-
 templates/juno/nova.conf                      |   6 +-
 templates/kilo/nova.conf                      |   6 +-
 templates/liberty/nova.conf                   |   6 +-
 templates/mitaka/nova.conf                    |   6 +-
 templates/ocata/nova.conf                     |   6 +-
 templates/parts/database-api                  |   2 +-
 templates/parts/database-v2                   |   2 +-
 templates/parts/section-database              |   2 +-
 templates/pike/nova.conf                      |   6 +-
 tox.ini                                       |   6 +-
 unit_tests/__init__.py                        |  11 +-
 unit_tests/test_actions.py                    |  45 +-
 unit_tests/test_actions_openstack_upgrade.py  |  26 +-
 unit_tests/test_nova_cc_contexts.py           | 185 ++--
 unit_tests/test_nova_cc_hooks.py              | 554 +++++-----
 unit_tests/test_nova_cc_utils.py              | 336 +++---
 unit_tests/test_utils.py                      |  29 +-
 178 files changed, 1838 insertions(+), 1769 deletions(-)
 rename {hooks/charmhelpers => charmhelpers}/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/cli/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/cli/benchmark.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/cli/commands.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/cli/hookenv.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/cli/host.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/cli/unitdata.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/charmsupport/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/charmsupport/nrpe.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/charmsupport/volumes.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hahelpers/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hahelpers/apache.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hahelpers/cluster.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/README.hardening.md (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/apache/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/apache/checks/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/apache/checks/config.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/apache/templates/99-hardening.conf (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/apache/templates/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/apache/templates/alias.conf (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/audits/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/audits/apache.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/audits/apt.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/audits/file.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/defaults/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/defaults/apache.yaml (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/defaults/apache.yaml.schema (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/defaults/mysql.yaml (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/defaults/mysql.yaml.schema (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/defaults/os.yaml (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/defaults/os.yaml.schema (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/defaults/ssh.yaml (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/defaults/ssh.yaml.schema (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/harden.py (85%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/host/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/host/checks/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/host/checks/apt.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/host/checks/limits.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/host/checks/login.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/host/checks/minimize_access.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/host/checks/pam.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/host/checks/profile.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/host/checks/securetty.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/host/checks/suid_sgid.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/host/checks/sysctl.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/host/templates/10.hardcore.conf (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/host/templates/99-hardening.sh (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/host/templates/99-juju-hardening.conf (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/host/templates/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/host/templates/login.defs (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/host/templates/modules (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/host/templates/passwdqc.conf (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/host/templates/pinerolo_profile.sh (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/host/templates/securetty (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/host/templates/tally2 (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/mysql/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/mysql/checks/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/mysql/checks/config.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/mysql/templates/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/mysql/templates/hardening.cnf (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/ssh/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/ssh/checks/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/ssh/checks/config.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/ssh/templates/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/ssh/templates/ssh_config (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/ssh/templates/sshd_config (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/templating.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/hardening/utils.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/network/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/network/ip.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/alternatives.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/amulet/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/amulet/deployment.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/amulet/utils.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/cert_utils.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/context.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/exceptions.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/files/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/files/check_haproxy.sh (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/files/check_haproxy_queue_depth.sh (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/ha/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/ha/utils.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/ip.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/keystone.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/neutron.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/ssh_migrations.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/templates/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/templates/ceph.conf (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/templates/git.upstart (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/templates/haproxy.cfg (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/templates/memcached.conf (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/templates/openstack_https_frontend (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/templates/openstack_https_frontend.conf (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/templates/section-keystone-authtoken (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/templates/section-keystone-authtoken-legacy (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/templates/section-keystone-authtoken-mitaka (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/templates/section-oslo-cache (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/templates/section-oslo-middleware (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/templates/section-oslo-notifications (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/templates/section-rabbitmq-oslo (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/templates/section-zeromq (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/templates/wsgi-openstack-api.conf (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/templates/wsgi-openstack-metadata.conf (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/templating.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/utils.py (98%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/openstack/vaultlocker.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/peerstorage/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/python/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/python/packages.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/storage/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/storage/linux/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/storage/linux/bcache.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/storage/linux/ceph.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/storage/linux/loopback.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/storage/linux/lvm.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/contrib/storage/linux/utils.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/core/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/core/decorators.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/core/files.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/core/fstab.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/core/hookenv.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/core/host.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/core/host_factory/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/core/host_factory/centos.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/core/host_factory/ubuntu.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/core/hugepage.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/core/kernel.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/core/kernel_factory/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/core/kernel_factory/centos.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/core/kernel_factory/ubuntu.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/core/services/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/core/services/base.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/core/services/helpers.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/core/strutils.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/core/sysctl.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/core/templating.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/core/unitdata.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/fetch/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/fetch/archiveurl.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/fetch/bzrurl.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/fetch/centos.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/fetch/giturl.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/fetch/snap.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/fetch/ubuntu.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/osplatform.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/payload/__init__.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/payload/archive.py (100%)
 rename {hooks/charmhelpers => charmhelpers}/payload/execd.py (100%)
 create mode 100644 files/README.txt
 create mode 100644 hooks/nova_cc_common.py

diff --git a/actions/actions.py b/actions/actions.py
index c6e43c81..df80b4dc 100755
--- a/actions/actions.py
+++ b/actions/actions.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python3
 #
 # Copyright 2016 Canonical Ltd
 #
@@ -17,40 +17,39 @@
 import os
 import sys
 
-sys.path.append('hooks/')
+_path = os.path.dirname(os.path.realpath(__file__))
+_root = os.path.abspath(os.path.join(_path, '..'))
 
-from charmhelpers.core.hookenv import (
-    action_fail,
-    action_get,
-    action_set,
-)
-from nova_cc_utils import (
-    pause_unit_helper,
-    resume_unit_helper,
-    register_configs,
-    archive_deleted_rows,
-)
+
+def _add_path(path):
+    if path not in sys.path:
+        sys.path.insert(1, path)
+
+_add_path(_root)
+
+import charmhelpers.core.hookenv as hookenv
+import hooks.nova_cc_utils as utils
 
 
 def pause(args):
     """Pause the Ceilometer services.
     @raises Exception should the service fail to stop.
     """
-    pause_unit_helper(register_configs())
+    utils.pause_unit_helper(utils.register_configs())
 
 
 def resume(args):
     """Resume the Ceilometer services.
     @raises Exception should the service fail to start."""
-    resume_unit_helper(register_configs())
+    utils.resume_unit_helper(utils.register_configs())
 
 
 def archive_data(args):
     """Run data archival process
     @raises Exception should the archival fail"""
-    action_set({
-        'archive-deleted-rows': archive_deleted_rows(
-            max_rows=action_get('batch-size'))})
+    hookenv.action_set({
+        'archive-deleted-rows': utils.archive_deleted_rows(
+            max_rows=hookenv.action_get('batch-size'))})
 
 
 # A dictionary of all the defined actions to callables (which take
@@ -71,7 +70,7 @@ def main(args):
         try:
             action(args)
         except Exception as e:
-            action_fail(str(e))
+            hookenv.action_fail(str(e))
 
 
 if __name__ == "__main__":
diff --git a/actions/openstack_upgrade.py b/actions/openstack_upgrade.py
index 694155e9..ee0c702e 100755
--- a/actions/openstack_upgrade.py
+++ b/actions/openstack_upgrade.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python3
 #
 # Copyright 2016 Canonical Ltd
 #
@@ -14,28 +14,23 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import os
 import sys
 
-sys.path.append('hooks/')
+_path = os.path.dirname(os.path.realpath(__file__))
+_root = os.path.abspath(os.path.join(_path, '..'))
 
-from charmhelpers.contrib.openstack.utils import (
-    do_action_openstack_upgrade,
-)
 
-from charmhelpers.core.hookenv import (
-    relation_ids,
-)
+def _add_path(path):
+    if path not in sys.path:
+        sys.path.insert(1, path)
 
-from nova_cc_utils import (
-    do_openstack_upgrade,
-)
+_add_path(_root)
 
-from nova_cc_hooks import (
-    config_changed,
-    CONFIGS,
-    neutron_api_relation_joined,
-    db_joined,
-)
+import charmhelpers.contrib.openstack.utils as ch_utils
+import charmhelpers.core.hookenv as hookenv
+import hooks.nova_cc_utils as utils
+import hooks.nova_cc_hooks as hooks
 
 
 def openstack_upgrade():
@@ -46,16 +41,17 @@ def openstack_upgrade():
     code to run, otherwise a full service level upgrade will fire
     on config-changed."""
 
-    if (do_action_openstack_upgrade('nova-common',
-                                    do_openstack_upgrade,
-                                    CONFIGS)):
-        [neutron_api_relation_joined(rid=rid, remote_restart=True)
-            for rid in relation_ids('neutron-api')]
+    if (ch_utils.do_action_openstack_upgrade('nova-common',
+                                             utils.do_openstack_upgrade,
+                                             hooks.CONFIGS)):
+        for rid in hookenv.relation_ids('neutron-api'):
+            hooks.neutron_api_relation_joined(rid=rid, remote_restart=True)
         # NOTE(thedac): Force re-fire of shared-db joined hook
         # to ensure that nova_api database is setup if required.
-        [db_joined(relation_id=r_id)
-            for r_id in relation_ids('shared-db')]
-        config_changed()
+        for r_id in hookenv.relation_ids('shared-db'):
+            hooks.db_joined(relation_id=r_id)
+        hooks.config_changed()
 
 if __name__ == '__main__':
+    hooks.resolve_CONFIGS()
     openstack_upgrade()
diff --git a/charm-helpers-hooks.yaml b/charm-helpers-hooks.yaml
index 85ed4407..3fdc3313 100644
--- a/charm-helpers-hooks.yaml
+++ b/charm-helpers-hooks.yaml
@@ -1,5 +1,5 @@
 repo: https://github.com/juju/charm-helpers
-destination: hooks/charmhelpers
+destination: charmhelpers
 include:
     - core
     - osplatform
diff --git a/hooks/charmhelpers/__init__.py b/charmhelpers/__init__.py
similarity index 100%
rename from hooks/charmhelpers/__init__.py
rename to charmhelpers/__init__.py
diff --git a/hooks/charmhelpers/cli/__init__.py b/charmhelpers/cli/__init__.py
similarity index 100%
rename from hooks/charmhelpers/cli/__init__.py
rename to charmhelpers/cli/__init__.py
diff --git a/hooks/charmhelpers/cli/benchmark.py b/charmhelpers/cli/benchmark.py
similarity index 100%
rename from hooks/charmhelpers/cli/benchmark.py
rename to charmhelpers/cli/benchmark.py
diff --git a/hooks/charmhelpers/cli/commands.py b/charmhelpers/cli/commands.py
similarity index 100%
rename from hooks/charmhelpers/cli/commands.py
rename to charmhelpers/cli/commands.py
diff --git a/hooks/charmhelpers/cli/hookenv.py b/charmhelpers/cli/hookenv.py
similarity index 100%
rename from hooks/charmhelpers/cli/hookenv.py
rename to charmhelpers/cli/hookenv.py
diff --git a/hooks/charmhelpers/cli/host.py b/charmhelpers/cli/host.py
similarity index 100%
rename from hooks/charmhelpers/cli/host.py
rename to charmhelpers/cli/host.py
diff --git a/hooks/charmhelpers/cli/unitdata.py b/charmhelpers/cli/unitdata.py
similarity index 100%
rename from hooks/charmhelpers/cli/unitdata.py
rename to charmhelpers/cli/unitdata.py
diff --git a/hooks/charmhelpers/contrib/__init__.py b/charmhelpers/contrib/__init__.py
similarity index 100%
rename from hooks/charmhelpers/contrib/__init__.py
rename to charmhelpers/contrib/__init__.py
diff --git a/hooks/charmhelpers/contrib/charmsupport/__init__.py b/charmhelpers/contrib/charmsupport/__init__.py
similarity index 100%
rename from hooks/charmhelpers/contrib/charmsupport/__init__.py
rename to charmhelpers/contrib/charmsupport/__init__.py
diff --git a/hooks/charmhelpers/contrib/charmsupport/nrpe.py b/charmhelpers/contrib/charmsupport/nrpe.py
similarity index 100%
rename from hooks/charmhelpers/contrib/charmsupport/nrpe.py
rename to charmhelpers/contrib/charmsupport/nrpe.py
diff --git a/hooks/charmhelpers/contrib/charmsupport/volumes.py b/charmhelpers/contrib/charmsupport/volumes.py
similarity index 100%
rename from hooks/charmhelpers/contrib/charmsupport/volumes.py
rename to charmhelpers/contrib/charmsupport/volumes.py
diff --git a/hooks/charmhelpers/contrib/hahelpers/__init__.py b/charmhelpers/contrib/hahelpers/__init__.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hahelpers/__init__.py
rename to charmhelpers/contrib/hahelpers/__init__.py
diff --git a/hooks/charmhelpers/contrib/hahelpers/apache.py b/charmhelpers/contrib/hahelpers/apache.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hahelpers/apache.py
rename to charmhelpers/contrib/hahelpers/apache.py
diff --git a/hooks/charmhelpers/contrib/hahelpers/cluster.py b/charmhelpers/contrib/hahelpers/cluster.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hahelpers/cluster.py
rename to charmhelpers/contrib/hahelpers/cluster.py
diff --git a/hooks/charmhelpers/contrib/hardening/README.hardening.md b/charmhelpers/contrib/hardening/README.hardening.md
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/README.hardening.md
rename to charmhelpers/contrib/hardening/README.hardening.md
diff --git a/hooks/charmhelpers/contrib/hardening/__init__.py b/charmhelpers/contrib/hardening/__init__.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/__init__.py
rename to charmhelpers/contrib/hardening/__init__.py
diff --git a/hooks/charmhelpers/contrib/hardening/apache/__init__.py b/charmhelpers/contrib/hardening/apache/__init__.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/apache/__init__.py
rename to charmhelpers/contrib/hardening/apache/__init__.py
diff --git a/hooks/charmhelpers/contrib/hardening/apache/checks/__init__.py b/charmhelpers/contrib/hardening/apache/checks/__init__.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/apache/checks/__init__.py
rename to charmhelpers/contrib/hardening/apache/checks/__init__.py
diff --git a/hooks/charmhelpers/contrib/hardening/apache/checks/config.py b/charmhelpers/contrib/hardening/apache/checks/config.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/apache/checks/config.py
rename to charmhelpers/contrib/hardening/apache/checks/config.py
diff --git a/hooks/charmhelpers/contrib/hardening/apache/templates/99-hardening.conf b/charmhelpers/contrib/hardening/apache/templates/99-hardening.conf
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/apache/templates/99-hardening.conf
rename to charmhelpers/contrib/hardening/apache/templates/99-hardening.conf
diff --git a/hooks/charmhelpers/contrib/hardening/apache/templates/__init__.py b/charmhelpers/contrib/hardening/apache/templates/__init__.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/apache/templates/__init__.py
rename to charmhelpers/contrib/hardening/apache/templates/__init__.py
diff --git a/hooks/charmhelpers/contrib/hardening/apache/templates/alias.conf b/charmhelpers/contrib/hardening/apache/templates/alias.conf
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/apache/templates/alias.conf
rename to charmhelpers/contrib/hardening/apache/templates/alias.conf
diff --git a/hooks/charmhelpers/contrib/hardening/audits/__init__.py b/charmhelpers/contrib/hardening/audits/__init__.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/audits/__init__.py
rename to charmhelpers/contrib/hardening/audits/__init__.py
diff --git a/hooks/charmhelpers/contrib/hardening/audits/apache.py b/charmhelpers/contrib/hardening/audits/apache.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/audits/apache.py
rename to charmhelpers/contrib/hardening/audits/apache.py
diff --git a/hooks/charmhelpers/contrib/hardening/audits/apt.py b/charmhelpers/contrib/hardening/audits/apt.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/audits/apt.py
rename to charmhelpers/contrib/hardening/audits/apt.py
diff --git a/hooks/charmhelpers/contrib/hardening/audits/file.py b/charmhelpers/contrib/hardening/audits/file.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/audits/file.py
rename to charmhelpers/contrib/hardening/audits/file.py
diff --git a/hooks/charmhelpers/contrib/hardening/defaults/__init__.py b/charmhelpers/contrib/hardening/defaults/__init__.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/defaults/__init__.py
rename to charmhelpers/contrib/hardening/defaults/__init__.py
diff --git a/hooks/charmhelpers/contrib/hardening/defaults/apache.yaml b/charmhelpers/contrib/hardening/defaults/apache.yaml
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/defaults/apache.yaml
rename to charmhelpers/contrib/hardening/defaults/apache.yaml
diff --git a/hooks/charmhelpers/contrib/hardening/defaults/apache.yaml.schema b/charmhelpers/contrib/hardening/defaults/apache.yaml.schema
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/defaults/apache.yaml.schema
rename to charmhelpers/contrib/hardening/defaults/apache.yaml.schema
diff --git a/hooks/charmhelpers/contrib/hardening/defaults/mysql.yaml b/charmhelpers/contrib/hardening/defaults/mysql.yaml
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/defaults/mysql.yaml
rename to charmhelpers/contrib/hardening/defaults/mysql.yaml
diff --git a/hooks/charmhelpers/contrib/hardening/defaults/mysql.yaml.schema b/charmhelpers/contrib/hardening/defaults/mysql.yaml.schema
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/defaults/mysql.yaml.schema
rename to charmhelpers/contrib/hardening/defaults/mysql.yaml.schema
diff --git a/hooks/charmhelpers/contrib/hardening/defaults/os.yaml b/charmhelpers/contrib/hardening/defaults/os.yaml
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/defaults/os.yaml
rename to charmhelpers/contrib/hardening/defaults/os.yaml
diff --git a/hooks/charmhelpers/contrib/hardening/defaults/os.yaml.schema b/charmhelpers/contrib/hardening/defaults/os.yaml.schema
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/defaults/os.yaml.schema
rename to charmhelpers/contrib/hardening/defaults/os.yaml.schema
diff --git a/hooks/charmhelpers/contrib/hardening/defaults/ssh.yaml b/charmhelpers/contrib/hardening/defaults/ssh.yaml
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/defaults/ssh.yaml
rename to charmhelpers/contrib/hardening/defaults/ssh.yaml
diff --git a/hooks/charmhelpers/contrib/hardening/defaults/ssh.yaml.schema b/charmhelpers/contrib/hardening/defaults/ssh.yaml.schema
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/defaults/ssh.yaml.schema
rename to charmhelpers/contrib/hardening/defaults/ssh.yaml.schema
diff --git a/hooks/charmhelpers/contrib/hardening/harden.py b/charmhelpers/contrib/hardening/harden.py
similarity index 85%
rename from hooks/charmhelpers/contrib/hardening/harden.py
rename to charmhelpers/contrib/hardening/harden.py
index b55764cd..2559daf6 100644
--- a/hooks/charmhelpers/contrib/hardening/harden.py
+++ b/charmhelpers/contrib/hardening/harden.py
@@ -27,6 +27,8 @@ from charmhelpers.contrib.hardening.ssh.checks import run_ssh_checks
 from charmhelpers.contrib.hardening.mysql.checks import run_mysql_checks
 from charmhelpers.contrib.hardening.apache.checks import run_apache_checks
 
+_DISABLE_HARDENING_FOR_UNIT_TEST = False
+
 
 def harden(overrides=None):
     """Hardening decorator.
@@ -48,9 +50,18 @@ def harden(overrides=None):
     :returns: Returns value returned by decorated function once executed.
     """
     def _harden_inner1(f):
-        log("Hardening function '%s'" % (f.__name__), level=DEBUG)
+        # As this has to be py2.7 compat, we can't use nonlocal.  Use a trick
+        # to capture the dictionary that can then be updated.
+        _logged = {'done': False}
 
         def _harden_inner2(*args, **kwargs):
+            # knock out hardening via a config var; normally it won't get
+            # disabled.
+            if _DISABLE_HARDENING_FOR_UNIT_TEST:
+                return f(*args, **kwargs)
+            if not _logged['done']:
+                log("Hardening function '%s'" % (f.__name__), level=DEBUG)
+                _logged['done'] = True
             RUN_CATALOG = OrderedDict([('os', run_os_checks),
                                        ('ssh', run_ssh_checks),
                                        ('mysql', run_mysql_checks),
diff --git a/hooks/charmhelpers/contrib/hardening/host/__init__.py b/charmhelpers/contrib/hardening/host/__init__.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/host/__init__.py
rename to charmhelpers/contrib/hardening/host/__init__.py
diff --git a/hooks/charmhelpers/contrib/hardening/host/checks/__init__.py b/charmhelpers/contrib/hardening/host/checks/__init__.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/host/checks/__init__.py
rename to charmhelpers/contrib/hardening/host/checks/__init__.py
diff --git a/hooks/charmhelpers/contrib/hardening/host/checks/apt.py b/charmhelpers/contrib/hardening/host/checks/apt.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/host/checks/apt.py
rename to charmhelpers/contrib/hardening/host/checks/apt.py
diff --git a/hooks/charmhelpers/contrib/hardening/host/checks/limits.py b/charmhelpers/contrib/hardening/host/checks/limits.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/host/checks/limits.py
rename to charmhelpers/contrib/hardening/host/checks/limits.py
diff --git a/hooks/charmhelpers/contrib/hardening/host/checks/login.py b/charmhelpers/contrib/hardening/host/checks/login.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/host/checks/login.py
rename to charmhelpers/contrib/hardening/host/checks/login.py
diff --git a/hooks/charmhelpers/contrib/hardening/host/checks/minimize_access.py b/charmhelpers/contrib/hardening/host/checks/minimize_access.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/host/checks/minimize_access.py
rename to charmhelpers/contrib/hardening/host/checks/minimize_access.py
diff --git a/hooks/charmhelpers/contrib/hardening/host/checks/pam.py b/charmhelpers/contrib/hardening/host/checks/pam.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/host/checks/pam.py
rename to charmhelpers/contrib/hardening/host/checks/pam.py
diff --git a/hooks/charmhelpers/contrib/hardening/host/checks/profile.py b/charmhelpers/contrib/hardening/host/checks/profile.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/host/checks/profile.py
rename to charmhelpers/contrib/hardening/host/checks/profile.py
diff --git a/hooks/charmhelpers/contrib/hardening/host/checks/securetty.py b/charmhelpers/contrib/hardening/host/checks/securetty.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/host/checks/securetty.py
rename to charmhelpers/contrib/hardening/host/checks/securetty.py
diff --git a/hooks/charmhelpers/contrib/hardening/host/checks/suid_sgid.py b/charmhelpers/contrib/hardening/host/checks/suid_sgid.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/host/checks/suid_sgid.py
rename to charmhelpers/contrib/hardening/host/checks/suid_sgid.py
diff --git a/hooks/charmhelpers/contrib/hardening/host/checks/sysctl.py b/charmhelpers/contrib/hardening/host/checks/sysctl.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/host/checks/sysctl.py
rename to charmhelpers/contrib/hardening/host/checks/sysctl.py
diff --git a/hooks/charmhelpers/contrib/hardening/host/templates/10.hardcore.conf b/charmhelpers/contrib/hardening/host/templates/10.hardcore.conf
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/host/templates/10.hardcore.conf
rename to charmhelpers/contrib/hardening/host/templates/10.hardcore.conf
diff --git a/hooks/charmhelpers/contrib/hardening/host/templates/99-hardening.sh b/charmhelpers/contrib/hardening/host/templates/99-hardening.sh
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/host/templates/99-hardening.sh
rename to charmhelpers/contrib/hardening/host/templates/99-hardening.sh
diff --git a/hooks/charmhelpers/contrib/hardening/host/templates/99-juju-hardening.conf b/charmhelpers/contrib/hardening/host/templates/99-juju-hardening.conf
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/host/templates/99-juju-hardening.conf
rename to charmhelpers/contrib/hardening/host/templates/99-juju-hardening.conf
diff --git a/hooks/charmhelpers/contrib/hardening/host/templates/__init__.py b/charmhelpers/contrib/hardening/host/templates/__init__.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/host/templates/__init__.py
rename to charmhelpers/contrib/hardening/host/templates/__init__.py
diff --git a/hooks/charmhelpers/contrib/hardening/host/templates/login.defs b/charmhelpers/contrib/hardening/host/templates/login.defs
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/host/templates/login.defs
rename to charmhelpers/contrib/hardening/host/templates/login.defs
diff --git a/hooks/charmhelpers/contrib/hardening/host/templates/modules b/charmhelpers/contrib/hardening/host/templates/modules
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/host/templates/modules
rename to charmhelpers/contrib/hardening/host/templates/modules
diff --git a/hooks/charmhelpers/contrib/hardening/host/templates/passwdqc.conf b/charmhelpers/contrib/hardening/host/templates/passwdqc.conf
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/host/templates/passwdqc.conf
rename to charmhelpers/contrib/hardening/host/templates/passwdqc.conf
diff --git a/hooks/charmhelpers/contrib/hardening/host/templates/pinerolo_profile.sh b/charmhelpers/contrib/hardening/host/templates/pinerolo_profile.sh
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/host/templates/pinerolo_profile.sh
rename to charmhelpers/contrib/hardening/host/templates/pinerolo_profile.sh
diff --git a/hooks/charmhelpers/contrib/hardening/host/templates/securetty b/charmhelpers/contrib/hardening/host/templates/securetty
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/host/templates/securetty
rename to charmhelpers/contrib/hardening/host/templates/securetty
diff --git a/hooks/charmhelpers/contrib/hardening/host/templates/tally2 b/charmhelpers/contrib/hardening/host/templates/tally2
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/host/templates/tally2
rename to charmhelpers/contrib/hardening/host/templates/tally2
diff --git a/hooks/charmhelpers/contrib/hardening/mysql/__init__.py b/charmhelpers/contrib/hardening/mysql/__init__.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/mysql/__init__.py
rename to charmhelpers/contrib/hardening/mysql/__init__.py
diff --git a/hooks/charmhelpers/contrib/hardening/mysql/checks/__init__.py b/charmhelpers/contrib/hardening/mysql/checks/__init__.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/mysql/checks/__init__.py
rename to charmhelpers/contrib/hardening/mysql/checks/__init__.py
diff --git a/hooks/charmhelpers/contrib/hardening/mysql/checks/config.py b/charmhelpers/contrib/hardening/mysql/checks/config.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/mysql/checks/config.py
rename to charmhelpers/contrib/hardening/mysql/checks/config.py
diff --git a/hooks/charmhelpers/contrib/hardening/mysql/templates/__init__.py b/charmhelpers/contrib/hardening/mysql/templates/__init__.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/mysql/templates/__init__.py
rename to charmhelpers/contrib/hardening/mysql/templates/__init__.py
diff --git a/hooks/charmhelpers/contrib/hardening/mysql/templates/hardening.cnf b/charmhelpers/contrib/hardening/mysql/templates/hardening.cnf
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/mysql/templates/hardening.cnf
rename to charmhelpers/contrib/hardening/mysql/templates/hardening.cnf
diff --git a/hooks/charmhelpers/contrib/hardening/ssh/__init__.py b/charmhelpers/contrib/hardening/ssh/__init__.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/ssh/__init__.py
rename to charmhelpers/contrib/hardening/ssh/__init__.py
diff --git a/hooks/charmhelpers/contrib/hardening/ssh/checks/__init__.py b/charmhelpers/contrib/hardening/ssh/checks/__init__.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/ssh/checks/__init__.py
rename to charmhelpers/contrib/hardening/ssh/checks/__init__.py
diff --git a/hooks/charmhelpers/contrib/hardening/ssh/checks/config.py b/charmhelpers/contrib/hardening/ssh/checks/config.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/ssh/checks/config.py
rename to charmhelpers/contrib/hardening/ssh/checks/config.py
diff --git a/hooks/charmhelpers/contrib/hardening/ssh/templates/__init__.py b/charmhelpers/contrib/hardening/ssh/templates/__init__.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/ssh/templates/__init__.py
rename to charmhelpers/contrib/hardening/ssh/templates/__init__.py
diff --git a/hooks/charmhelpers/contrib/hardening/ssh/templates/ssh_config b/charmhelpers/contrib/hardening/ssh/templates/ssh_config
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/ssh/templates/ssh_config
rename to charmhelpers/contrib/hardening/ssh/templates/ssh_config
diff --git a/hooks/charmhelpers/contrib/hardening/ssh/templates/sshd_config b/charmhelpers/contrib/hardening/ssh/templates/sshd_config
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/ssh/templates/sshd_config
rename to charmhelpers/contrib/hardening/ssh/templates/sshd_config
diff --git a/hooks/charmhelpers/contrib/hardening/templating.py b/charmhelpers/contrib/hardening/templating.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/templating.py
rename to charmhelpers/contrib/hardening/templating.py
diff --git a/hooks/charmhelpers/contrib/hardening/utils.py b/charmhelpers/contrib/hardening/utils.py
similarity index 100%
rename from hooks/charmhelpers/contrib/hardening/utils.py
rename to charmhelpers/contrib/hardening/utils.py
diff --git a/hooks/charmhelpers/contrib/network/__init__.py b/charmhelpers/contrib/network/__init__.py
similarity index 100%
rename from hooks/charmhelpers/contrib/network/__init__.py
rename to charmhelpers/contrib/network/__init__.py
diff --git a/hooks/charmhelpers/contrib/network/ip.py b/charmhelpers/contrib/network/ip.py
similarity index 100%
rename from hooks/charmhelpers/contrib/network/ip.py
rename to charmhelpers/contrib/network/ip.py
diff --git a/hooks/charmhelpers/contrib/openstack/__init__.py b/charmhelpers/contrib/openstack/__init__.py
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/__init__.py
rename to charmhelpers/contrib/openstack/__init__.py
diff --git a/hooks/charmhelpers/contrib/openstack/alternatives.py b/charmhelpers/contrib/openstack/alternatives.py
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/alternatives.py
rename to charmhelpers/contrib/openstack/alternatives.py
diff --git a/hooks/charmhelpers/contrib/openstack/amulet/__init__.py b/charmhelpers/contrib/openstack/amulet/__init__.py
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/amulet/__init__.py
rename to charmhelpers/contrib/openstack/amulet/__init__.py
diff --git a/hooks/charmhelpers/contrib/openstack/amulet/deployment.py b/charmhelpers/contrib/openstack/amulet/deployment.py
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/amulet/deployment.py
rename to charmhelpers/contrib/openstack/amulet/deployment.py
diff --git a/hooks/charmhelpers/contrib/openstack/amulet/utils.py b/charmhelpers/contrib/openstack/amulet/utils.py
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/amulet/utils.py
rename to charmhelpers/contrib/openstack/amulet/utils.py
diff --git a/hooks/charmhelpers/contrib/openstack/cert_utils.py b/charmhelpers/contrib/openstack/cert_utils.py
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/cert_utils.py
rename to charmhelpers/contrib/openstack/cert_utils.py
diff --git a/hooks/charmhelpers/contrib/openstack/context.py b/charmhelpers/contrib/openstack/context.py
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/context.py
rename to charmhelpers/contrib/openstack/context.py
diff --git a/hooks/charmhelpers/contrib/openstack/exceptions.py b/charmhelpers/contrib/openstack/exceptions.py
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/exceptions.py
rename to charmhelpers/contrib/openstack/exceptions.py
diff --git a/hooks/charmhelpers/contrib/openstack/files/__init__.py b/charmhelpers/contrib/openstack/files/__init__.py
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/files/__init__.py
rename to charmhelpers/contrib/openstack/files/__init__.py
diff --git a/hooks/charmhelpers/contrib/openstack/files/check_haproxy.sh b/charmhelpers/contrib/openstack/files/check_haproxy.sh
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/files/check_haproxy.sh
rename to charmhelpers/contrib/openstack/files/check_haproxy.sh
diff --git a/hooks/charmhelpers/contrib/openstack/files/check_haproxy_queue_depth.sh b/charmhelpers/contrib/openstack/files/check_haproxy_queue_depth.sh
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/files/check_haproxy_queue_depth.sh
rename to charmhelpers/contrib/openstack/files/check_haproxy_queue_depth.sh
diff --git a/hooks/charmhelpers/contrib/openstack/ha/__init__.py b/charmhelpers/contrib/openstack/ha/__init__.py
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/ha/__init__.py
rename to charmhelpers/contrib/openstack/ha/__init__.py
diff --git a/hooks/charmhelpers/contrib/openstack/ha/utils.py b/charmhelpers/contrib/openstack/ha/utils.py
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/ha/utils.py
rename to charmhelpers/contrib/openstack/ha/utils.py
diff --git a/hooks/charmhelpers/contrib/openstack/ip.py b/charmhelpers/contrib/openstack/ip.py
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/ip.py
rename to charmhelpers/contrib/openstack/ip.py
diff --git a/hooks/charmhelpers/contrib/openstack/keystone.py b/charmhelpers/contrib/openstack/keystone.py
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/keystone.py
rename to charmhelpers/contrib/openstack/keystone.py
diff --git a/hooks/charmhelpers/contrib/openstack/neutron.py b/charmhelpers/contrib/openstack/neutron.py
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/neutron.py
rename to charmhelpers/contrib/openstack/neutron.py
diff --git a/hooks/charmhelpers/contrib/openstack/ssh_migrations.py b/charmhelpers/contrib/openstack/ssh_migrations.py
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/ssh_migrations.py
rename to charmhelpers/contrib/openstack/ssh_migrations.py
diff --git a/hooks/charmhelpers/contrib/openstack/templates/__init__.py b/charmhelpers/contrib/openstack/templates/__init__.py
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/templates/__init__.py
rename to charmhelpers/contrib/openstack/templates/__init__.py
diff --git a/hooks/charmhelpers/contrib/openstack/templates/ceph.conf b/charmhelpers/contrib/openstack/templates/ceph.conf
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/templates/ceph.conf
rename to charmhelpers/contrib/openstack/templates/ceph.conf
diff --git a/hooks/charmhelpers/contrib/openstack/templates/git.upstart b/charmhelpers/contrib/openstack/templates/git.upstart
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/templates/git.upstart
rename to charmhelpers/contrib/openstack/templates/git.upstart
diff --git a/hooks/charmhelpers/contrib/openstack/templates/haproxy.cfg b/charmhelpers/contrib/openstack/templates/haproxy.cfg
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/templates/haproxy.cfg
rename to charmhelpers/contrib/openstack/templates/haproxy.cfg
diff --git a/hooks/charmhelpers/contrib/openstack/templates/memcached.conf b/charmhelpers/contrib/openstack/templates/memcached.conf
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/templates/memcached.conf
rename to charmhelpers/contrib/openstack/templates/memcached.conf
diff --git a/hooks/charmhelpers/contrib/openstack/templates/openstack_https_frontend b/charmhelpers/contrib/openstack/templates/openstack_https_frontend
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/templates/openstack_https_frontend
rename to charmhelpers/contrib/openstack/templates/openstack_https_frontend
diff --git a/hooks/charmhelpers/contrib/openstack/templates/openstack_https_frontend.conf b/charmhelpers/contrib/openstack/templates/openstack_https_frontend.conf
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/templates/openstack_https_frontend.conf
rename to charmhelpers/contrib/openstack/templates/openstack_https_frontend.conf
diff --git a/hooks/charmhelpers/contrib/openstack/templates/section-keystone-authtoken b/charmhelpers/contrib/openstack/templates/section-keystone-authtoken
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/templates/section-keystone-authtoken
rename to charmhelpers/contrib/openstack/templates/section-keystone-authtoken
diff --git a/hooks/charmhelpers/contrib/openstack/templates/section-keystone-authtoken-legacy b/charmhelpers/contrib/openstack/templates/section-keystone-authtoken-legacy
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/templates/section-keystone-authtoken-legacy
rename to charmhelpers/contrib/openstack/templates/section-keystone-authtoken-legacy
diff --git a/hooks/charmhelpers/contrib/openstack/templates/section-keystone-authtoken-mitaka b/charmhelpers/contrib/openstack/templates/section-keystone-authtoken-mitaka
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/templates/section-keystone-authtoken-mitaka
rename to charmhelpers/contrib/openstack/templates/section-keystone-authtoken-mitaka
diff --git a/hooks/charmhelpers/contrib/openstack/templates/section-oslo-cache b/charmhelpers/contrib/openstack/templates/section-oslo-cache
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/templates/section-oslo-cache
rename to charmhelpers/contrib/openstack/templates/section-oslo-cache
diff --git a/hooks/charmhelpers/contrib/openstack/templates/section-oslo-middleware b/charmhelpers/contrib/openstack/templates/section-oslo-middleware
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/templates/section-oslo-middleware
rename to charmhelpers/contrib/openstack/templates/section-oslo-middleware
diff --git a/hooks/charmhelpers/contrib/openstack/templates/section-oslo-notifications b/charmhelpers/contrib/openstack/templates/section-oslo-notifications
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/templates/section-oslo-notifications
rename to charmhelpers/contrib/openstack/templates/section-oslo-notifications
diff --git a/hooks/charmhelpers/contrib/openstack/templates/section-rabbitmq-oslo b/charmhelpers/contrib/openstack/templates/section-rabbitmq-oslo
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/templates/section-rabbitmq-oslo
rename to charmhelpers/contrib/openstack/templates/section-rabbitmq-oslo
diff --git a/hooks/charmhelpers/contrib/openstack/templates/section-zeromq b/charmhelpers/contrib/openstack/templates/section-zeromq
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/templates/section-zeromq
rename to charmhelpers/contrib/openstack/templates/section-zeromq
diff --git a/hooks/charmhelpers/contrib/openstack/templates/wsgi-openstack-api.conf b/charmhelpers/contrib/openstack/templates/wsgi-openstack-api.conf
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/templates/wsgi-openstack-api.conf
rename to charmhelpers/contrib/openstack/templates/wsgi-openstack-api.conf
diff --git a/hooks/charmhelpers/contrib/openstack/templates/wsgi-openstack-metadata.conf b/charmhelpers/contrib/openstack/templates/wsgi-openstack-metadata.conf
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/templates/wsgi-openstack-metadata.conf
rename to charmhelpers/contrib/openstack/templates/wsgi-openstack-metadata.conf
diff --git a/hooks/charmhelpers/contrib/openstack/templating.py b/charmhelpers/contrib/openstack/templating.py
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/templating.py
rename to charmhelpers/contrib/openstack/templating.py
diff --git a/hooks/charmhelpers/contrib/openstack/utils.py b/charmhelpers/contrib/openstack/utils.py
similarity index 98%
rename from hooks/charmhelpers/contrib/openstack/utils.py
rename to charmhelpers/contrib/openstack/utils.py
index ae48d6b4..2a681d81 100644
--- a/hooks/charmhelpers/contrib/openstack/utils.py
+++ b/charmhelpers/contrib/openstack/utils.py
@@ -1450,20 +1450,32 @@ def pausable_restart_on_change(restart_map, stopstart=False,
 
     see core.utils.restart_on_change() for more details.
 
+    Note restart_map can be a callable, in which case, restart_map is only
+    evaluated at runtime.  This means that it is lazy and the underlying
+    function won't be called if the decorated function is never called.  Note,
+    retains backwards compatibility for passing a non-callable dictionary.
+
     @param f: the function to decorate
-    @param restart_map: the restart map {conf_file: [services]}
+    @param restart_map: (optionally callable, which then returns the
+        restart_map) the restart map {conf_file: [services]}
     @param stopstart: DEFAULT false; whether to stop, start or just restart
     @returns decorator to use a restart_on_change with pausability
     """
     def wrap(f):
+        # py27 compatible nonlocal variable.  When py3 only, replace with
+        # nonlocal keyword
+        __restart_map_cache = {'cache': None}
         @functools.wraps(f)
         def wrapped_f(*args, **kwargs):
             if is_unit_paused_set():
                 return f(*args, **kwargs)
+            if __restart_map_cache['cache'] is None:
+                __restart_map_cache['cache'] = restart_map() \
+                    if callable(restart_map) else restart_map
             # otherwise, normal restart_on_change functionality
             return restart_on_change_helper(
-                (lambda: f(*args, **kwargs)), restart_map, stopstart,
-                restart_functions)
+                (lambda: f(*args, **kwargs)), __restart_map_cache['cache'],
+                 stopstart, restart_functions)
         return wrapped_f
     return wrap
 
diff --git a/hooks/charmhelpers/contrib/openstack/vaultlocker.py b/charmhelpers/contrib/openstack/vaultlocker.py
similarity index 100%
rename from hooks/charmhelpers/contrib/openstack/vaultlocker.py
rename to charmhelpers/contrib/openstack/vaultlocker.py
diff --git a/hooks/charmhelpers/contrib/peerstorage/__init__.py b/charmhelpers/contrib/peerstorage/__init__.py
similarity index 100%
rename from hooks/charmhelpers/contrib/peerstorage/__init__.py
rename to charmhelpers/contrib/peerstorage/__init__.py
diff --git a/hooks/charmhelpers/contrib/python/__init__.py b/charmhelpers/contrib/python/__init__.py
similarity index 100%
rename from hooks/charmhelpers/contrib/python/__init__.py
rename to charmhelpers/contrib/python/__init__.py
diff --git a/hooks/charmhelpers/contrib/python/packages.py b/charmhelpers/contrib/python/packages.py
similarity index 100%
rename from hooks/charmhelpers/contrib/python/packages.py
rename to charmhelpers/contrib/python/packages.py
diff --git a/hooks/charmhelpers/contrib/storage/__init__.py b/charmhelpers/contrib/storage/__init__.py
similarity index 100%
rename from hooks/charmhelpers/contrib/storage/__init__.py
rename to charmhelpers/contrib/storage/__init__.py
diff --git a/hooks/charmhelpers/contrib/storage/linux/__init__.py b/charmhelpers/contrib/storage/linux/__init__.py
similarity index 100%
rename from hooks/charmhelpers/contrib/storage/linux/__init__.py
rename to charmhelpers/contrib/storage/linux/__init__.py
diff --git a/hooks/charmhelpers/contrib/storage/linux/bcache.py b/charmhelpers/contrib/storage/linux/bcache.py
similarity index 100%
rename from hooks/charmhelpers/contrib/storage/linux/bcache.py
rename to charmhelpers/contrib/storage/linux/bcache.py
diff --git a/hooks/charmhelpers/contrib/storage/linux/ceph.py b/charmhelpers/contrib/storage/linux/ceph.py
similarity index 100%
rename from hooks/charmhelpers/contrib/storage/linux/ceph.py
rename to charmhelpers/contrib/storage/linux/ceph.py
diff --git a/hooks/charmhelpers/contrib/storage/linux/loopback.py b/charmhelpers/contrib/storage/linux/loopback.py
similarity index 100%
rename from hooks/charmhelpers/contrib/storage/linux/loopback.py
rename to charmhelpers/contrib/storage/linux/loopback.py
diff --git a/hooks/charmhelpers/contrib/storage/linux/lvm.py b/charmhelpers/contrib/storage/linux/lvm.py
similarity index 100%
rename from hooks/charmhelpers/contrib/storage/linux/lvm.py
rename to charmhelpers/contrib/storage/linux/lvm.py
diff --git a/hooks/charmhelpers/contrib/storage/linux/utils.py b/charmhelpers/contrib/storage/linux/utils.py
similarity index 100%
rename from hooks/charmhelpers/contrib/storage/linux/utils.py
rename to charmhelpers/contrib/storage/linux/utils.py
diff --git a/hooks/charmhelpers/core/__init__.py b/charmhelpers/core/__init__.py
similarity index 100%
rename from hooks/charmhelpers/core/__init__.py
rename to charmhelpers/core/__init__.py
diff --git a/hooks/charmhelpers/core/decorators.py b/charmhelpers/core/decorators.py
similarity index 100%
rename from hooks/charmhelpers/core/decorators.py
rename to charmhelpers/core/decorators.py
diff --git a/hooks/charmhelpers/core/files.py b/charmhelpers/core/files.py
similarity index 100%
rename from hooks/charmhelpers/core/files.py
rename to charmhelpers/core/files.py
diff --git a/hooks/charmhelpers/core/fstab.py b/charmhelpers/core/fstab.py
similarity index 100%
rename from hooks/charmhelpers/core/fstab.py
rename to charmhelpers/core/fstab.py
diff --git a/hooks/charmhelpers/core/hookenv.py b/charmhelpers/core/hookenv.py
similarity index 100%
rename from hooks/charmhelpers/core/hookenv.py
rename to charmhelpers/core/hookenv.py
diff --git a/hooks/charmhelpers/core/host.py b/charmhelpers/core/host.py
similarity index 100%
rename from hooks/charmhelpers/core/host.py
rename to charmhelpers/core/host.py
diff --git a/hooks/charmhelpers/core/host_factory/__init__.py b/charmhelpers/core/host_factory/__init__.py
similarity index 100%
rename from hooks/charmhelpers/core/host_factory/__init__.py
rename to charmhelpers/core/host_factory/__init__.py
diff --git a/hooks/charmhelpers/core/host_factory/centos.py b/charmhelpers/core/host_factory/centos.py
similarity index 100%
rename from hooks/charmhelpers/core/host_factory/centos.py
rename to charmhelpers/core/host_factory/centos.py
diff --git a/hooks/charmhelpers/core/host_factory/ubuntu.py b/charmhelpers/core/host_factory/ubuntu.py
similarity index 100%
rename from hooks/charmhelpers/core/host_factory/ubuntu.py
rename to charmhelpers/core/host_factory/ubuntu.py
diff --git a/hooks/charmhelpers/core/hugepage.py b/charmhelpers/core/hugepage.py
similarity index 100%
rename from hooks/charmhelpers/core/hugepage.py
rename to charmhelpers/core/hugepage.py
diff --git a/hooks/charmhelpers/core/kernel.py b/charmhelpers/core/kernel.py
similarity index 100%
rename from hooks/charmhelpers/core/kernel.py
rename to charmhelpers/core/kernel.py
diff --git a/hooks/charmhelpers/core/kernel_factory/__init__.py b/charmhelpers/core/kernel_factory/__init__.py
similarity index 100%
rename from hooks/charmhelpers/core/kernel_factory/__init__.py
rename to charmhelpers/core/kernel_factory/__init__.py
diff --git a/hooks/charmhelpers/core/kernel_factory/centos.py b/charmhelpers/core/kernel_factory/centos.py
similarity index 100%
rename from hooks/charmhelpers/core/kernel_factory/centos.py
rename to charmhelpers/core/kernel_factory/centos.py
diff --git a/hooks/charmhelpers/core/kernel_factory/ubuntu.py b/charmhelpers/core/kernel_factory/ubuntu.py
similarity index 100%
rename from hooks/charmhelpers/core/kernel_factory/ubuntu.py
rename to charmhelpers/core/kernel_factory/ubuntu.py
diff --git a/hooks/charmhelpers/core/services/__init__.py b/charmhelpers/core/services/__init__.py
similarity index 100%
rename from hooks/charmhelpers/core/services/__init__.py
rename to charmhelpers/core/services/__init__.py
diff --git a/hooks/charmhelpers/core/services/base.py b/charmhelpers/core/services/base.py
similarity index 100%
rename from hooks/charmhelpers/core/services/base.py
rename to charmhelpers/core/services/base.py
diff --git a/hooks/charmhelpers/core/services/helpers.py b/charmhelpers/core/services/helpers.py
similarity index 100%
rename from hooks/charmhelpers/core/services/helpers.py
rename to charmhelpers/core/services/helpers.py
diff --git a/hooks/charmhelpers/core/strutils.py b/charmhelpers/core/strutils.py
similarity index 100%
rename from hooks/charmhelpers/core/strutils.py
rename to charmhelpers/core/strutils.py
diff --git a/hooks/charmhelpers/core/sysctl.py b/charmhelpers/core/sysctl.py
similarity index 100%
rename from hooks/charmhelpers/core/sysctl.py
rename to charmhelpers/core/sysctl.py
diff --git a/hooks/charmhelpers/core/templating.py b/charmhelpers/core/templating.py
similarity index 100%
rename from hooks/charmhelpers/core/templating.py
rename to charmhelpers/core/templating.py
diff --git a/hooks/charmhelpers/core/unitdata.py b/charmhelpers/core/unitdata.py
similarity index 100%
rename from hooks/charmhelpers/core/unitdata.py
rename to charmhelpers/core/unitdata.py
diff --git a/hooks/charmhelpers/fetch/__init__.py b/charmhelpers/fetch/__init__.py
similarity index 100%
rename from hooks/charmhelpers/fetch/__init__.py
rename to charmhelpers/fetch/__init__.py
diff --git a/hooks/charmhelpers/fetch/archiveurl.py b/charmhelpers/fetch/archiveurl.py
similarity index 100%
rename from hooks/charmhelpers/fetch/archiveurl.py
rename to charmhelpers/fetch/archiveurl.py
diff --git a/hooks/charmhelpers/fetch/bzrurl.py b/charmhelpers/fetch/bzrurl.py
similarity index 100%
rename from hooks/charmhelpers/fetch/bzrurl.py
rename to charmhelpers/fetch/bzrurl.py
diff --git a/hooks/charmhelpers/fetch/centos.py b/charmhelpers/fetch/centos.py
similarity index 100%
rename from hooks/charmhelpers/fetch/centos.py
rename to charmhelpers/fetch/centos.py
diff --git a/hooks/charmhelpers/fetch/giturl.py b/charmhelpers/fetch/giturl.py
similarity index 100%
rename from hooks/charmhelpers/fetch/giturl.py
rename to charmhelpers/fetch/giturl.py
diff --git a/hooks/charmhelpers/fetch/snap.py b/charmhelpers/fetch/snap.py
similarity index 100%
rename from hooks/charmhelpers/fetch/snap.py
rename to charmhelpers/fetch/snap.py
diff --git a/hooks/charmhelpers/fetch/ubuntu.py b/charmhelpers/fetch/ubuntu.py
similarity index 100%
rename from hooks/charmhelpers/fetch/ubuntu.py
rename to charmhelpers/fetch/ubuntu.py
diff --git a/hooks/charmhelpers/osplatform.py b/charmhelpers/osplatform.py
similarity index 100%
rename from hooks/charmhelpers/osplatform.py
rename to charmhelpers/osplatform.py
diff --git a/hooks/charmhelpers/payload/__init__.py b/charmhelpers/payload/__init__.py
similarity index 100%
rename from hooks/charmhelpers/payload/__init__.py
rename to charmhelpers/payload/__init__.py
diff --git a/hooks/charmhelpers/payload/archive.py b/charmhelpers/payload/archive.py
similarity index 100%
rename from hooks/charmhelpers/payload/archive.py
rename to charmhelpers/payload/archive.py
diff --git a/hooks/charmhelpers/payload/execd.py b/charmhelpers/payload/execd.py
similarity index 100%
rename from hooks/charmhelpers/payload/execd.py
rename to charmhelpers/payload/execd.py
diff --git a/files/README.txt b/files/README.txt
new file mode 100644
index 00000000..61891b1d
--- /dev/null
+++ b/files/README.txt
@@ -0,0 +1,3 @@
+Note that the python files in THIS directory depend on the payload python version
+and not the charm python version (which is PY3).  These files will need to be modified
+when the payload changes to PY3.
diff --git a/hooks/install b/hooks/install
index 29ff6894..50b8cad9 100755
--- a/hooks/install
+++ b/hooks/install
@@ -11,7 +11,7 @@ check_and_install() {
     fi
 }
 
-PYTHON="python"
+PYTHON="python3"
 
 for dep in ${DEPS[@]}; do
     check_and_install ${PYTHON} ${dep}
diff --git a/hooks/nova_cc_common.py b/hooks/nova_cc_common.py
new file mode 100644
index 00000000..1a89a610
--- /dev/null
+++ b/hooks/nova_cc_common.py
@@ -0,0 +1,77 @@
+# Copyright 2018 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.
+
+# Note that *this* file exists to break a circular import dependency issue
+# between nova_cc_utils and nova_cc_context.  It may be beneficial to migrate
+# other constants to this file.
+
+import charmhelpers.core.hookenv as hookenv
+
+API_PORTS = {
+    'nova-api-ec2': 8773,
+    'nova-api-os-compute': 8774,
+    'nova-api-metadata': 8775,
+    'nova-api-os-volume': 8776,
+    'nova-placement-api': 8778,
+    'nova-objectstore': 3333,
+}
+
+
+CONSOLE_CONFIG = {
+    'spice': {
+        'packages': ['nova-spiceproxy', 'nova-consoleauth'],
+        'services': ['nova-spiceproxy', 'nova-consoleauth'],
+        'proxy-page': '/spice_auto.html',
+        'proxy-port': 6082,
+    },
+    'novnc': {
+        'packages': ['nova-novncproxy', 'nova-consoleauth'],
+        'services': ['nova-novncproxy', 'nova-consoleauth'],
+        'proxy-page': '/vnc_auto.html',
+        'proxy-port': 6080,
+    },
+    'xvpvnc': {
+        'packages': ['nova-xvpvncproxy', 'nova-consoleauth'],
+        'services': ['nova-xvpvncproxy', 'nova-consoleauth'],
+        'proxy-page': '/console',
+        'proxy-port': 6081,
+    },
+}
+
+
+def api_port(service):
+    return API_PORTS[service]
+
+
+def console_attributes(attr, proto=None):
+    '''Leave proto unset to query attributes of the protocal specified at
+    runtime'''
+    if proto:
+        console_proto = proto
+    else:
+        console_proto = hookenv.config('console-access-protocol')
+        if console_proto is not None and console_proto.lower() in ('none', ''):
+            console_proto = None
+    if attr == 'protocol':
+        return console_proto
+    # 'vnc' is a virtual type made up of novnc and xvpvnc
+    if console_proto == 'vnc':
+        if attr in ['packages', 'services']:
+            return list(set(CONSOLE_CONFIG['novnc'][attr] +
+                        CONSOLE_CONFIG['xvpvnc'][attr]))
+        else:
+            return None
+    if console_proto in CONSOLE_CONFIG:
+        return CONSOLE_CONFIG[console_proto][attr]
+    return None
diff --git a/hooks/nova_cc_context.py b/hooks/nova_cc_context.py
index bb3a3c42..ce61292f 100644
--- a/hooks/nova_cc_context.py
+++ b/hooks/nova_cc_context.py
@@ -15,77 +15,61 @@
 import json
 import os
 
-from base64 import b64decode
-from charmhelpers.core.hookenv import (
-    config,
-    relation_ids,
-    relation_set,
-    leader_get,
-    log,
-    DEBUG,
-    related_units,
-    relations_for_id,
-    relation_get,
-    unit_get,
-)
-from charmhelpers.contrib.openstack import (
-    context,
-    neutron,
-)
-from charmhelpers.contrib.hahelpers.cluster import (
-    determine_apache_port,
-    determine_api_port,
-    https,
-    is_clustered,
-)
-from charmhelpers.contrib.network.ip import (
-    format_ipv6_addr,
-)
-from charmhelpers.contrib.openstack.ip import (
-    resolve_address,
-    INTERNAL,
-    PUBLIC,
-)
-from charmhelpers.contrib.openstack.utils import (
-    os_release,
-    CompareOpenStackReleases,
-)
+import base64
+
+import charmhelpers.contrib.hahelpers.cluster as ch_cluster
+import charmhelpers.contrib.network.ip as ch_network_ip
+import charmhelpers.contrib.openstack.context as ch_context
+import charmhelpers.contrib.openstack.ip as ch_ip
+import charmhelpers.contrib.openstack.neutron as ch_neutron
+import charmhelpers.contrib.openstack.utils as ch_utils
+import charmhelpers.core.hookenv as hookenv
+
+import hooks.nova_cc_common as common
 
 
 def context_complete(ctxt):
     _missing = []
-    for k, v in ctxt.iteritems():
+    for k, v in ctxt.items():
         if v is None or v == '':
             _missing.append(k)
     if _missing:
-        log('Missing required data: %s' % ' '.join(_missing), level='INFO')
+        hookenv.log('Missing required data: %s' % ' '.join(_missing),
+                    level='INFO')
         return False
     return True
 
 
-class ApacheSSLContext(context.ApacheSSLContext):
+class ApacheSSLContext(ch_context.ApacheSSLContext):
 
     interfaces = ['https']
     external_ports = []
     service_namespace = 'nova'
 
+    def __init__(self, _external_ports_maybe_callable):
+        self._external_ports_maybe_callable = _external_ports_maybe_callable
+        self.external_ports = None
+        super(ApacheSSLContext, self).__init__()
+
     def __call__(self):
-        # late import to work around circular dependency
-        from nova_cc_utils import determine_ports
-        self.external_ports = determine_ports()
+        if self.external_ports is None:
+            if callable(self._external_ports_maybe_callable):
+                self.external_ports = self._external_ports_maybe_callable()
+            else:
+                self.external_ports = self._external_ports_maybe_callable
         return super(ApacheSSLContext, self).__call__()
 
 
-class NovaCellV2Context(context.OSContextGenerator):
+class NovaCellV2Context(ch_context.OSContextGenerator):
 
     interfaces = ['nova-cell-api']
 
     def __call__(self):
         ctxt = {}
         required_keys = ['cell-name', 'amqp-service', 'db-service']
-        for rid in relation_ids('nova-cell-api'):
-            for unit in related_units(rid):
-                data = relation_get(rid=rid, unit=unit)
+        for rid in hookenv.relation_ids('nova-cell-api'):
+            for unit in hookenv.related_units(rid):
+                data = hookenv.relation_get(rid=rid, unit=unit)
                 if set(required_keys).issubset(data.keys()):
                     ctxt[data['cell-name']] = {
                         'amqp_service': data['amqp-service'],
@@ -93,46 +77,46 @@ class NovaCellV2Context(context.OSContextGenerator):
         return ctxt
 
 
-class NovaCellV2SharedDBContext(context.OSContextGenerator):
+class NovaCellV2SharedDBContext(ch_context.OSContextGenerator):
     interfaces = ['shared-db']
 
     def __call__(self):
-        log('Generating template context for cell v2 share-db')
+        hookenv.log('Generating template context for cell v2 share-db')
         ctxt = {}
-        for rid in relation_ids('shared-db'):
-            for unit in related_units(rid):
-                rdata = relation_get(rid=rid, unit=unit)
+        for rid in hookenv.relation_ids('shared-db'):
+            for unit in hookenv.related_units(rid):
+                rdata = hookenv.relation_get(rid=rid, unit=unit)
                 ctxt = {
                     'novaapi_password': rdata.get('novaapi_password'),
                     'novacell0_password': rdata.get('novacell0_password'),
                     'nova_password': rdata.get('nova_password'),
                 }
-                if context.context_complete(ctxt):
+                if ch_context.context_complete(ctxt):
                     return ctxt
         return {}
 
 
-class CloudComputeContext(context.OSContextGenerator):
+class CloudComputeContext(ch_context.OSContextGenerator):
     "Dummy context used by service status to check relation exists"
     interfaces = ['nova-compute']
 
     def __call__(self):
         ctxt = {}
-        rids = [rid for rid in relation_ids('cloud-compute')]
+        rids = [rid for rid in hookenv.relation_ids('cloud-compute')]
         if rids:
             ctxt['rids'] = rids
         return ctxt
 
 
-class NeutronAPIContext(context.OSContextGenerator):
+class NeutronAPIContext(ch_context.OSContextGenerator):
     interfaces = ['neutron-api']
 
     def __call__(self):
-        log('Generating template context from neutron api relation')
+        hookenv.log('Generating template context from neutron api relation')
         ctxt = {}
-        for rid in relation_ids('neutron-api'):
-            for unit in related_units(rid):
-                rdata = relation_get(rid=rid, unit=unit)
+        for rid in hookenv.relation_ids('neutron-api'):
+            for unit in hookenv.related_units(rid):
+                rdata = hookenv.relation_get(rid=rid, unit=unit)
                 ctxt = {
                     'neutron_url': rdata.get('neutron-url'),
                     'neutron_plugin': rdata.get('neutron-plugin'),
@@ -147,20 +131,20 @@ class NeutronAPIContext(context.OSContextGenerator):
         return {}
 
 
-class VolumeServiceContext(context.OSContextGenerator):
+class VolumeServiceContext(ch_context.OSContextGenerator):
     interfaces = ['cinder-volume-service']
 
     def __call__(self):
         ctxt = {}
-        if relation_ids('cinder-volume-service'):
+        if hookenv.relation_ids('cinder-volume-service'):
             ctxt['volume_service'] = 'cinder'
             # kick all compute nodes to know they should use cinder now.
-            [relation_set(relation_id=rid, volume_service='cinder')
-             for rid in relation_ids('cloud-compute')]
+            for rid in hookenv.relation_ids('cloud-compute'):
+                hookenv.relation_set(relation_id=rid, volume_service='cinder')
         return ctxt
 
 
-class HAProxyContext(context.HAProxyContext):
+class HAProxyContext(ch_context.HAProxyContext):
     interfaces = ['ceph']
 
     def __call__(self):
@@ -169,32 +153,31 @@ class HAProxyContext(context.HAProxyContext):
         specific to this charm.
         Also used to extend nova.conf context with correct api_listening_ports
         '''
-        from nova_cc_utils import api_port
         ctxt = super(HAProxyContext, self).__call__()
 
         # determine which port api processes should bind to, depending
         # on existence of haproxy + apache frontends
-        compute_api = determine_api_port(api_port('nova-api-os-compute'),
-                                         singlenode_mode=True)
-        ec2_api = determine_api_port(api_port('nova-api-ec2'),
-                                     singlenode_mode=True)
-        s3_api = determine_api_port(api_port('nova-objectstore'),
-                                    singlenode_mode=True)
-        placement_api = determine_api_port(api_port('nova-placement-api'),
-                                           singlenode_mode=True)
-        metadata_api = determine_api_port(api_port('nova-api-metadata'),
-                                          singlenode_mode=True)
+        compute_api = ch_cluster.determine_api_port(
+            common.api_port('nova-api-os-compute'), singlenode_mode=True)
+        ec2_api = ch_cluster.determine_api_port(
+            common.api_port('nova-api-ec2'), singlenode_mode=True)
+        s3_api = ch_cluster.determine_api_port(
+            common.api_port('nova-objectstore'), singlenode_mode=True)
+        placement_api = ch_cluster.determine_api_port(
+            common.api_port('nova-placement-api'), singlenode_mode=True)
+        metadata_api = ch_cluster.determine_api_port(
+            common.api_port('nova-api-metadata'), singlenode_mode=True)
         # Apache ports
-        a_compute_api = determine_apache_port(api_port('nova-api-os-compute'),
-                                              singlenode_mode=True)
-        a_ec2_api = determine_apache_port(api_port('nova-api-ec2'),
-                                          singlenode_mode=True)
-        a_s3_api = determine_apache_port(api_port('nova-objectstore'),
-                                         singlenode_mode=True)
-        a_placement_api = determine_apache_port(api_port('nova-placement-api'),
-                                                singlenode_mode=True)
-        a_metadata_api = determine_apache_port(api_port('nova-api-metadata'),
-                                               singlenode_mode=True)
+        a_compute_api = ch_cluster.determine_apache_port(
+            common.api_port('nova-api-os-compute'), singlenode_mode=True)
+        a_ec2_api = ch_cluster.determine_apache_port(
+            common.api_port('nova-api-ec2'), singlenode_mode=True)
+        a_s3_api = ch_cluster.determine_apache_port(
+            common.api_port('nova-objectstore'), singlenode_mode=True)
+        a_placement_api = ch_cluster.determine_apache_port(
+            common.api_port('nova-placement-api'), singlenode_mode=True)
+        a_metadata_api = ch_cluster.determine_apache_port(
+            common.api_port('nova-api-metadata'), singlenode_mode=True)
         # to be set in nova.conf accordingly.
         listen_ports = {
             'osapi_compute_listen_port': compute_api,
@@ -206,15 +189,15 @@ class HAProxyContext(context.HAProxyContext):
 
         port_mapping = {
             'nova-api-os-compute': [
-                api_port('nova-api-os-compute'), a_compute_api],
+                common.api_port('nova-api-os-compute'), a_compute_api],
             'nova-api-ec2': [
-                api_port('nova-api-ec2'), a_ec2_api],
+                common.api_port('nova-api-ec2'), a_ec2_api],
             'nova-objectstore': [
-                api_port('nova-objectstore'), a_s3_api],
+                common.api_port('nova-objectstore'), a_s3_api],
             'nova-placement-api': [
-                api_port('nova-placement-api'), a_placement_api],
+                common.api_port('nova-placement-api'), a_placement_api],
             'nova-api-metadata': [
-                api_port('nova-api-metadata'), a_metadata_api],
+                common.api_port('nova-api-metadata'), a_metadata_api],
         }
 
         # for haproxy.conf
@@ -239,19 +222,19 @@ def canonical_url():
     configuration and hacluster.
     """
     scheme = 'http'
-    if https():
+    if ch_cluster.https():
         scheme = 'https'
 
-    addr = resolve_address(INTERNAL)
-    return '%s://%s' % (scheme, format_ipv6_addr(addr) or addr)
+    addr = ch_ip.resolve_address(ch_ip.INTERNAL)
+    return '%s://%s' % (scheme, ch_network_ip.format_ipv6_addr(addr) or addr)
 
 
-class NeutronCCContext(context.NeutronContext):
+class NeutronCCContext(ch_context.NeutronContext):
     interfaces = ['quantum-network-service', 'neutron-network-service']
 
     @property
     def network_manager(self):
-        return neutron.network_manager()
+        return ch_neutron.network_manager()
 
     def _ensure_packages(self):
         # Only compute nodes need to ensure packages here, to install
@@ -260,12 +243,12 @@ class NeutronCCContext(context.NeutronContext):
 
     def __call__(self):
         ctxt = super(NeutronCCContext, self).__call__()
-        ctxt['external_network'] = config('neutron-external-network')
+        ctxt['external_network'] = hookenv.config('neutron-external-network')
         ctxt['nova_url'] = "{}:8774/v2".format(canonical_url())
         return ctxt
 
 
-class IdentityServiceContext(context.IdentityServiceContext):
+class IdentityServiceContext(ch_context.IdentityServiceContext):
 
     def __call__(self):
         ctxt = super(IdentityServiceContext, self).__call__()
@@ -280,60 +263,63 @@ class IdentityServiceContext(context.IdentityServiceContext):
             ctxt['service_port']
         )
         ctxt['keystone_ec2_url'] = ec2_tokens
-        ctxt['region'] = config('region')
+        ctxt['region'] = hookenv.config('region')
 
         return ctxt
 
 
-class NovaConfigContext(context.WorkerConfigContext):
+class NovaConfigContext(ch_context.WorkerConfigContext):
     def __call__(self):
         ctxt = super(NovaConfigContext, self).__call__()
-        ctxt['scheduler_default_filters'] = config('scheduler-default-filters')
-        if config('pci-alias'):
-            aliases = json.loads(config('pci-alias'))
+        ctxt['scheduler_default_filters'] = (
+            hookenv.config('scheduler-default-filters'))
+        if hookenv.config('pci-alias'):
+            aliases = json.loads(hookenv.config('pci-alias'))
             if isinstance(aliases, list):
                 ctxt['pci_aliases'] = [json.dumps(x, sort_keys=True)
                                        for x in aliases]
             else:
                 ctxt['pci_alias'] = json.dumps(aliases, sort_keys=True)
 
-        ctxt['disk_allocation_ratio'] = config('disk-allocation-ratio')
-        ctxt['cpu_allocation_ratio'] = config('cpu-allocation-ratio')
-        ctxt['ram_allocation_ratio'] = config('ram-allocation-ratio')
-        addr = resolve_address(INTERNAL)
-        ctxt['host_ip'] = format_ipv6_addr(addr) or addr
+        ctxt['disk_allocation_ratio'] = hookenv.config('disk-allocation-ratio')
+        ctxt['cpu_allocation_ratio'] = hookenv.config('cpu-allocation-ratio')
+        ctxt['ram_allocation_ratio'] = hookenv.config('ram-allocation-ratio')
+        addr = ch_ip.resolve_address(ch_ip.INTERNAL)
+        ctxt['host_ip'] = ch_network_ip.format_ipv6_addr(addr) or addr
         return ctxt
 
 
-class NovaIPv6Context(context.BindHostContext):
+class NovaIPv6Context(ch_context.BindHostContext):
     def __call__(self):
         ctxt = super(NovaIPv6Context, self).__call__()
-        ctxt['use_ipv6'] = config('prefer-ipv6')
+        ctxt['use_ipv6'] = hookenv.config('prefer-ipv6')
         return ctxt
 
 
-class InstanceConsoleContext(context.OSContextGenerator):
+class InstanceConsoleContext(ch_context.OSContextGenerator):
     interfaces = []
 
     def __call__(self):
         ctxt = {}
         servers = []
         try:
-            for rid in relation_ids('memcache'):
-                for rel in relations_for_id(rid):
+            for rid in hookenv.relation_ids('memcache'):
+                for rel in hookenv.relations_for_id(rid):
                     priv_addr = rel['private-address']
                     # Format it as IPv6 address if needed
-                    priv_addr = format_ipv6_addr(priv_addr) or priv_addr
+                    priv_addr = (ch_network_ip.format_ipv6_addr(priv_addr) or
+                                 priv_addr)
                     servers.append("%s:%s" % (priv_addr, rel['port']))
         except Exception as ex:
-            log("Could not get memcache servers: %s" % (ex), level='WARNING')
+            hookenv.log("Could not get memcache servers: %s" % (ex),
+                        level='WARNING')
             servers = []
 
         ctxt['memcached_servers'] = ','.join(servers)
 
         # Configure nova-novncproxy https if nova-api is using https.
-        if https():
-            cn = resolve_address(endpoint_type=INTERNAL)
+        if ch_cluster.https():
+            cn = ch_ip.resolve_address(endpoint_type=ch_ip.INTERNAL)
             if cn:
                 cert_filename = 'cert_{}'.format(cn)
                 key_filename = 'key_{}'.format(cn)
@@ -351,26 +337,27 @@ class InstanceConsoleContext(context.OSContextGenerator):
         return ctxt
 
 
-class ConsoleSSLContext(context.OSContextGenerator):
+class ConsoleSSLContext(ch_context.OSContextGenerator):
     interfaces = []
 
     def __call__(self):
         ctxt = {}
-        from nova_cc_utils import console_attributes
 
-        if (config('console-ssl-cert') and
-            config('console-ssl-key') and
-                config('console-access-protocol')):
+        if (hookenv.config('console-ssl-cert') and
+                hookenv.config('console-ssl-key') and
+                hookenv.config('console-access-protocol')):
             ssl_dir = '/etc/nova/ssl/'
             if not os.path.exists(ssl_dir):
-                log('Creating %s.' % ssl_dir, level=DEBUG)
+                hookenv.log('Creating %s.' % ssl_dir, level=hookenv.DEBUG)
                 os.mkdir(ssl_dir)
 
             cert_path = os.path.join(ssl_dir, 'nova_cert.pem')
-            decode_ssl_cert = b64decode(config('console-ssl-cert'))
+            decode_ssl_cert = base64.b64decode(
+                hookenv.config('console-ssl-cert'))
 
             key_path = os.path.join(ssl_dir, 'nova_key.pem')
-            decode_ssl_key = b64decode(config('console-ssl-key'))
+            decode_ssl_key = base64.b64decode(
+                hookenv.config('console-ssl-key'))
 
             with open(cert_path, 'w') as fh:
                 fh.write(decode_ssl_cert)
@@ -381,18 +368,18 @@ class ConsoleSSLContext(context.OSContextGenerator):
             ctxt['ssl_cert'] = cert_path
             ctxt['ssl_key'] = key_path
 
-            if is_clustered():
-                ip_addr = resolve_address(endpoint_type=PUBLIC)
+            if ch_cluster.is_clustered():
+                ip_addr = ch_ip.resolve_address(endpoint_type=ch_ip.PUBLIC)
             else:
-                ip_addr = unit_get('private-address')
+                ip_addr = hookenv.unit_get('private-address')
 
-            ip_addr = format_ipv6_addr(ip_addr) or ip_addr
+            ip_addr = ch_network_ip.format_ipv6_addr(ip_addr) or ip_addr
 
-            _proto = config('console-access-protocol')
+            _proto = hookenv.config('console-access-protocol')
             url = "https://%s:%s%s" % (
                 ip_addr,
-                console_attributes('proxy-port', proto=_proto),
-                console_attributes('proxy-page', proto=_proto))
+                common.console_attributes('proxy-port', proto=_proto),
+                common.console_attributes('proxy-page', proto=_proto))
 
             if _proto == 'novnc':
                 ctxt['novncproxy_base_url'] = url
@@ -402,31 +389,31 @@ class ConsoleSSLContext(context.OSContextGenerator):
         return ctxt
 
 
-class SerialConsoleContext(context.OSContextGenerator):
+class SerialConsoleContext(ch_context.OSContextGenerator):
     interfaces = []
 
     def __call__(self):
-        ip_addr = resolve_address(endpoint_type=PUBLIC)
-        ip_addr = format_ipv6_addr(ip_addr) or ip_addr
+        ip_addr = ch_ip.resolve_address(endpoint_type=ch_ip.PUBLIC)
+        ip_addr = ch_network_ip.format_ipv6_addr(ip_addr) or ip_addr
 
         ctxt = {
             'enable_serial_console':
-                str(config('enable-serial-console')).lower(),
+                str(hookenv.config('enable-serial-console')).lower(),
             'serial_console_base_url': 'ws://{}:6083/'.format(ip_addr)
         }
         return ctxt
 
 
-class APIRateLimitingContext(context.OSContextGenerator):
+class APIRateLimitingContext(ch_context.OSContextGenerator):
     def __call__(self):
         ctxt = {}
-        rate_rules = config('api-rate-limit-rules')
+        rate_rules = hookenv.config('api-rate-limit-rules')
         if rate_rules:
             ctxt['api_rate_limit_rules'] = rate_rules
         return ctxt
 
 
-class NovaAPISharedDBContext(context.SharedDBContext):
+class NovaAPISharedDBContext(ch_context.SharedDBContext):
     '''
     Wrapper context to support multiple database connections being
     represented to a single config file
@@ -441,17 +428,18 @@ class NovaAPISharedDBContext(context.SharedDBContext):
         return ctxt
 
 
-class NovaMetadataContext(context.OSContextGenerator):
+class NovaMetadataContext(ch_context.OSContextGenerator):
     '''
     Context used for configuring the nova metadata service.
     '''
     def __call__(self):
-        cmp_os_release = CompareOpenStackReleases(os_release('nova-common'))
+        cmp_os_release = ch_utils.CompareOpenStackReleases(
+            ch_utils.os_release('nova-common'))
         ctxt = {}
         if cmp_os_release >= 'rocky':
             ctxt['vendordata_providers'] = []
-            vdata = config('vendor-data')
-            vdata_url = config('vendor-data-url')
+            vdata = hookenv.config('vendor-data')
+            vdata_url = hookenv.config('vendor-data-url')
 
             if vdata:
                 ctxt['vendor_data'] = True
@@ -460,7 +448,7 @@ class NovaMetadataContext(context.OSContextGenerator):
             if vdata_url:
                 ctxt['vendor_data_url'] = vdata_url
                 ctxt['vendordata_providers'].append('DynamicJSON')
-            ctxt['metadata_proxy_shared_secret'] = leader_get(
+            ctxt['metadata_proxy_shared_secret'] = hookenv.leader_get(
                 'shared-metadata-secret')
             ctxt['enable_metadata'] = True
         else:
diff --git a/hooks/nova_cc_hooks.py b/hooks/nova_cc_hooks.py
index 2cd3c331..ec8f968a 100755
--- a/hooks/nova_cc_hooks.py
+++ b/hooks/nova_cc_hooks.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python3
 #
 # Copyright 2016 Canonical Ltd
 #
@@ -16,148 +16,42 @@
 
 import os
 import shutil
+import subprocess
 import sys
+from urllib.parse import urlparse
 import uuid
 
-from subprocess import (
-    check_call,
-)
 
-from urlparse import urlparse
+_path = os.path.dirname(os.path.realpath(__file__))
+_root = os.path.abspath(os.path.join(_path, '..'))
 
-from charmhelpers.core.hookenv import (
-    Hooks,
-    UnregisteredHookError,
-    config,
-    charm_dir,
-    is_leader,
-    is_relation_made,
-    log,
-    local_unit,
-    DEBUG,
-    WARNING,
-    relation_get,
-    relation_ids,
-    relation_set,
-    related_units,
-    open_port,
-    unit_get,
-    status_set,
-)
 
-from charmhelpers.core.host import (
-    service_pause,
-    service_reload,
-    service_resume,
-)
+def _add_path(path):
+    if path not in sys.path:
+        sys.path.insert(1, path)
 
-from charmhelpers.fetch import (
-    apt_install,
-    apt_update,
-    filter_installed_packages
-)
+_add_path(_root)
 
-from charmhelpers.contrib.openstack.utils import (
-    config_value_changed,
-    configure_installation_source,
-    openstack_upgrade_available,
-    os_release,
-    sync_db_with_multi_ipv6_addresses,
-    pausable_restart_on_change as restart_on_change,
-    is_unit_paused_set,
-    CompareOpenStackReleases,
-    series_upgrade_prepare,
-    series_upgrade_complete,
-)
 
-from charmhelpers.contrib.openstack.neutron import (
-    network_manager,
-)
+import charmhelpers.contrib.charmsupport.nrpe as nrpe
+import charmhelpers.contrib.hahelpers.cluster as ch_cluster
+import charmhelpers.contrib.hardening.harden as ch_harden
+import charmhelpers.contrib.network.ip as ch_network_ip
+import charmhelpers.contrib.openstack.cert_utils as cert_utils
+import charmhelpers.contrib.openstack.context as ch_context
+import charmhelpers.contrib.openstack.ha.utils as ch_ha_utils
+import charmhelpers.contrib.openstack.ip as ch_ip
+import charmhelpers.contrib.openstack.neutron as ch_neutron
+import charmhelpers.contrib.openstack.utils as ch_utils
+import charmhelpers.contrib.peerstorage as ch_peerstorage
+import charmhelpers.core.hookenv as hookenv
+import charmhelpers.core.host as ch_host
+import charmhelpers.fetch as ch_fetch
+import charmhelpers.payload.execd as execd
 
-from nova_cc_context import (
-    NeutronAPIContext,
-)
-
-from charmhelpers.contrib.peerstorage import (
-    peer_retrieve,
-    peer_echo,
-)
-
-from nova_cc_utils import (
-    add_hosts_to_cell,
-    auth_token_config,
-    determine_endpoints,
-    determine_packages,
-    determine_ports,
-    disable_package_apache_site,
-    do_openstack_upgrade,
-    get_metadata_settings,
-    get_shared_metadatasecret,
-    is_api_ready,
-    is_cellv2_init_ready,
-    keystone_ca_cert_b64,
-    migrate_nova_databases,
-    placement_api_enabled,
-    save_script_rc,
-    services,
-    set_shared_metadatasecret,
-    ssh_compute_add,
-    ssh_compute_remove,
-    ssh_known_hosts_lines,
-    ssh_authorized_keys_lines,
-    update_child_cell,
-    register_configs,
-    restart_map,
-    update_cell_database,
-    NOVA_CONF,
-    console_attributes,
-    service_guard,
-    guard_map,
-    setup_ipv6,
-    is_db_initialised,
-    assess_status,
-    update_aws_compat_services,
-    serial_console_settings,
-    pause_unit_helper,
-    resume_unit_helper,
-    write_vendordata,
-)
-
-from charmhelpers.contrib.hahelpers.cluster import (
-    get_hacluster_config,
-    https,
-    is_clustered,
-)
-
-from charmhelpers.contrib.openstack.ha.utils import (
-    update_dns_ha_resource_params,
-)
-
-from charmhelpers.payload.execd import execd_preinstall
-
-from charmhelpers.contrib.openstack.ip import (
-    canonical_url,
-    PUBLIC, INTERNAL, ADMIN,
-    resolve_address,
-)
-
-from charmhelpers.contrib.network.ip import (
-    format_ipv6_addr,
-    get_iface_for_address,
-    get_netmask_for_address,
-    is_ipv6,
-    get_relation_ip,
-)
-
-from charmhelpers.contrib.openstack.cert_utils import (
-    get_certificate_request,
-    process_certificates,
-)
-
-from charmhelpers.contrib.openstack.context import ADDRESS_TYPES
-
-from charmhelpers.contrib.charmsupport import nrpe
-from charmhelpers.contrib.hardening.harden import harden
+import hooks.nova_cc_common as common
+import hooks.nova_cc_context as nova_cc_context
+import hooks.nova_cc_utils as ncc_utils
 
 try:
     FileNotFoundError
@@ -165,39 +59,69 @@ except NameError:
     # python3 compatibility
     FileNotFoundError = OSError
 
-hooks = Hooks()
-CONFIGS = register_configs()
+hooks = hookenv.Hooks()
+# Note that CONFIGS is now set up via resolve_CONFIGS so that it is not a
+# module load time constraint.
+CONFIGS = None
 COLO_CONSOLEAUTH = 'inf: res_nova_consoleauth grp_nova_vips'
 AGENT_CONSOLEAUTH = 'ocf:openstack:nova-consoleauth'
 AGENT_CA_PARAMS = 'op monitor interval="5s"'
 
 
+def deferred_config(k):
+    """Returns a callable that will return the config.  To be used with
+    functions that need lazy evaluation because the run at loadtime, but the
+    evaluation should happen at runtime.
+
+    :param k: the config key to lookup
+    :type k: Option[String, None]
+    :returns: the result of config(k)
+    """
+    return lambda: hookenv.config(k)
+
+
+def resolve_CONFIGS():
+    """lazy function to resolve the CONFIGS so that it doesn't have to evaluate
+    at module load time.  Note that it also returns the CONFIGS so that it can
+    be used in other, module loadtime, functions.
+
+    :returns: CONFIGS variable
+    :rtype: `:class:templating.OSConfigRenderer`
+    """
+    global CONFIGS
+    if CONFIGS is None:
+        CONFIGS = ncc_utils.register_configs()
+    return CONFIGS
+
+
 def leader_init_db_if_ready(skip_acl_check=False, db_rid=None, unit=None):
     """Initialise db if leader and db not yet intialised.
 
     NOTE: must be called from database context.
     """
-    if not is_leader():
-        log("Not leader - skipping db init", level=DEBUG)
+    if not hookenv.is_leader():
+        hookenv.log("Not leader - skipping db init", level=hookenv.DEBUG)
         return
 
-    if is_db_initialised():
-        log("Database already initialised - skipping db init", level=DEBUG)
+    if ncc_utils.is_db_initialised():
+        hookenv.log("Database already initialised - skipping db init",
+                    level=hookenv.DEBUG)
         return
 
     # Bugs 1353135 & 1187508. Dbs can appear to be ready before the units
     # acl entry has been added. So, if the db supports passing a list of
     # permitted units then check if we're in the list.
-    allowed_units = relation_get('nova_allowed_units', rid=db_rid, unit=unit)
-    if skip_acl_check or (allowed_units and local_unit() in
+    allowed_units = hookenv.relation_get('nova_allowed_units',
+                                         rid=db_rid, unit=unit)
+    if skip_acl_check or (allowed_units and hookenv.local_unit() in
                           allowed_units.split()):
-        status_set('maintenance', 'Running nova db migration')
-        migrate_nova_databases()
-        log('Triggering remote restarts.')
+        hookenv.status_set('maintenance', 'Running nova db migration')
+        ncc_utils.migrate_nova_databases()
+        hookenv.log('Triggering remote restarts.')
         update_nova_relation(remote_restart=True)
     else:
-        log('allowed_units either not presented, or local unit '
-            'not in acl list: %s' % repr(allowed_units))
+        hookenv.log('allowed_units either not presented, or local unit '
+                    'not in acl list: %s' % repr(allowed_units))
 
 
 def leader_init_db_if_ready_allowed_units():
@@ -209,30 +133,32 @@ def leader_init_db_if_ready_allowed_units():
     """
     rels = ['shared-db']
     for rname in rels:
-        for rid in relation_ids(rname):
-            for unit in related_units(rid):
+        for rid in hookenv.relation_ids(rname):
+            for unit in hookenv.related_units(rid):
                 leader_init_db_if_ready(db_rid=rid, unit=unit)
 
 
 def update_cell_db_if_ready(skip_acl_check=False, db_rid=None, unit=None):
     """Update the cells db if leader and db's are already intialised"""
-    if not is_leader():
+    if not hookenv.is_leader():
         return
 
-    if not is_db_initialised():
-        log("Database not initialised - skipping cell db update", level=DEBUG)
+    if not ncc_utils.is_db_initialised():
+        hookenv.log("Database not initialised - skipping cell db update",
+                    level=hookenv.DEBUG)
         return
 
-    if not is_cellv2_init_ready():
+    if not ncc_utils.is_cellv2_init_ready():
         return
 
-    allowed_units = relation_get('nova_allowed_units', rid=db_rid, unit=unit)
-    if skip_acl_check or (allowed_units and local_unit() in
+    allowed_units = hookenv.relation_get('nova_allowed_units',
+                                         rid=db_rid, unit=unit)
+    if skip_acl_check or (allowed_units and hookenv.local_unit() in
                           allowed_units.split()):
-        update_cell_database()
+        ncc_utils.update_cell_database()
     else:
-        log('allowed_units either not presented, or local unit '
-            'not in acl list: %s' % repr(allowed_units))
+        hookenv.log('allowed_units either not presented, or local unit '
+                    'not in acl list: %s' % repr(allowed_units))
 
 
 def update_cell_db_if_ready_allowed_units():
@@ -244,145 +170,153 @@ def update_cell_db_if_ready_allowed_units():
     """
     rels = ['shared-db']
     for rname in rels:
-        for rid in relation_ids(rname):
-            for unit in related_units(rid):
+        for rid in hookenv.relation_ids(rname):
+            for unit in hookenv.related_units(rid):
                 update_cell_db_if_ready(db_rid=rid, unit=unit)
 
 
 def update_child_cell_records():
-    for r_id in relation_ids('nova-cell-api'):
-        for unit in related_units(relid=r_id):
+    for r_id in hookenv.relation_ids('nova-cell-api'):
+        for unit in hookenv.related_units(relid=r_id):
             nova_cell_api_relation_changed(rid=r_id, unit=unit)
 
 
 @hooks.hook('install.real')
-@harden()
+@ch_harden.harden()
 def install():
-    status_set('maintenance', 'Executing pre-install')
-    execd_preinstall()
-    configure_installation_source(config('openstack-origin'))
+    hookenv.status_set('maintenance', 'Executing pre-install')
+    execd.execd_preinstall()
+    ch_utils.configure_installation_source(hookenv.config('openstack-origin'))
 
-    status_set('maintenance', 'Installing apt packages')
-    apt_update()
-    apt_install(determine_packages(), fatal=True)
+    hookenv.status_set('maintenance', 'Installing apt packages')
+    ch_fetch.apt_update()
+    ch_fetch.apt_install(ncc_utils.determine_packages(), fatal=True)
 
-    if placement_api_enabled():
-        disable_package_apache_site()
+    if ncc_utils.placement_api_enabled():
+        ncc_utils.disable_package_apache_site()
 
-    _files = os.path.join(charm_dir(), 'files')
+    _files = os.path.join(hookenv.charm_dir(), 'files')
     if os.path.isdir(_files):
         for f in os.listdir(_files):
             f = os.path.join(_files, f)
             if os.path.isfile(f):
-                log('Installing %s to /usr/bin' % f)
+                hookenv.log('Installing %s to /usr/bin' % f)
                 shutil.copy2(f, '/usr/bin')
-    [open_port(port) for port in determine_ports()]
+    for port in ncc_utils.determine_ports():
+        hookenv.open_port(port)
     msg = 'Disabling services into db relation joined'
-    log(msg)
-    status_set('maintenance', msg)
-    if not is_unit_paused_set():
-        for svc in services():
-            service_pause(svc)
+    hookenv.log(msg)
+    hookenv.status_set('maintenance', msg)
+    if not ch_utils.is_unit_paused_set():
+        for svc in ncc_utils.services():
+            ch_host.service_pause(svc)
     else:
-        log('Unit is in paused state, not issuing stop/pause to all services')
+        hookenv.log('Unit is in paused state, not issuing stop/pause '
+                    'to all services')
 
 
 @hooks.hook('config-changed')
-@service_guard(guard_map(), CONFIGS,
-               active=config('service-guard'))
-@restart_on_change(restart_map(), stopstart=True)
-@harden()
+@ncc_utils.service_guard(ncc_utils.guard_map, resolve_CONFIGS,
+                         active=deferred_config('service-guard'))
+@ch_utils.pausable_restart_on_change(ncc_utils.restart_map, stopstart=True)
+@ch_harden.harden()
 def config_changed():
     # if we are paused, delay doing any config changed hooks.
     # It is forced on the resume.
-    if is_unit_paused_set():
-        log("Unit is pause or upgrading. Skipping config_changed", "WARN")
+    if ch_utils.is_unit_paused_set():
+        hookenv.log("Unit is pause or upgrading. Skipping config_changed",
+                    hookenv.WARNING)
         return
 
     # neutron-server runs if < juno. Neutron-server creates mysql tables
     # which will subsequently cause db migrations to fail if >= juno.
     # Disable neutron-server if >= juno
-    if CompareOpenStackReleases(os_release('nova-common')) >= 'juno':
+    if ch_utils.CompareOpenStackReleases(
+            ch_utils.os_release('nova-common')) >= 'juno':
         try:
-            service_pause('neutron-server')
+            ch_host.service_pause('neutron-server')
         except ValueError:
             # neutron-server service not installed, ignore.
             pass
-    if config('prefer-ipv6'):
-        status_set('maintenance', 'configuring ipv6')
-        setup_ipv6()
-        sync_db_with_multi_ipv6_addresses(config('database'),
-                                          config('database-user'),
-                                          relation_prefix='nova')
+    if hookenv.config('prefer-ipv6'):
+        hookenv.status_set('maintenance', 'configuring ipv6')
+        ncc_utils.setup_ipv6()
+        ch_utils.sync_db_with_multi_ipv6_addresses(
+            hookenv.config('database'),
+            hookenv.config('database-user'),
+            relation_prefix='nova')
 
     global CONFIGS
-    if not config('action-managed-upgrade'):
-        if openstack_upgrade_available('nova-common'):
-            status_set('maintenance', 'Running openstack upgrade')
-            do_openstack_upgrade(CONFIGS)
-            [neutron_api_relation_joined(rid=rid, remote_restart=True)
-                for rid in relation_ids('neutron-api')]
+    if not hookenv.config('action-managed-upgrade'):
+        if ch_utils.openstack_upgrade_available('nova-common'):
+            hookenv.status_set('maintenance', 'Running openstack upgrade')
+            ncc_utils.do_openstack_upgrade(CONFIGS)
+            for rid in hookenv.relation_ids('neutron-api'):
+                neutron_api_relation_joined(rid=rid, remote_restart=True)
             # NOTE(jamespage): Force re-fire of shared-db joined hook
             # to ensure that nova_api database is setup if required.
-            [db_joined(relation_id=r_id)
-                for r_id in relation_ids('shared-db')]
+            for r_id in hookenv.relation_ids('shared-db'):
+                db_joined(relation_id=r_id)
 
-    save_script_rc()
+    ncc_utils.save_script_rc()
     configure_https()
     CONFIGS.write_all()
 
     # NOTE(jamespage): deal with any changes to the console and serial
     #                  console configuration options
-    filtered = filter_installed_packages(determine_packages())
+    filtered = ch_fetch.filter_installed_packages(
+        ncc_utils.determine_packages())
     if filtered:
-        apt_install(filtered, fatal=True)
+        ch_fetch.apt_install(filtered, fatal=True)
 
-    for r_id in relation_ids('identity-service'):
+    for r_id in hookenv.relation_ids('identity-service'):
         identity_joined(rid=r_id)
-    [cluster_joined(rid) for rid in relation_ids('cluster')]
+    for rid in hookenv.relation_ids('cluster'):
+        cluster_joined(rid)
     update_nova_relation()
 
     update_nrpe_config()
 
     # If the region value has changed, notify the cloud-compute relations
     # to ensure the value is propagated to the compute nodes.
-    if config_value_changed('region'):
-        for rid in relation_ids('cloud-compute'):
-            for unit in related_units(rid):
+    if ch_utils.config_value_changed('region'):
+        for rid in hookenv.relation_ids('cloud-compute'):
+            for unit in hookenv.related_units(rid):
                 compute_changed(rid, unit)
 
     update_nova_consoleauth_config()
-    update_aws_compat_services()
+    ncc_utils.update_aws_compat_services()
 
-    if config('vendor-data'):
-        write_vendordata(config('vendor-data'))
-    if is_leader() and not get_shared_metadatasecret():
-        set_shared_metadatasecret()
+    if hookenv.config('vendor-data'):
+        ncc_utils.write_vendordata(hookenv.config('vendor-data'))
+    if hookenv.is_leader() and not ncc_utils.get_shared_metadatasecret():
+        ncc_utils.set_shared_metadatasecret()
 
 
 @hooks.hook('amqp-relation-joined')
 def amqp_joined(relation_id=None):
-    relation_set(relation_id=relation_id,
-                 username=config('rabbit-user'), vhost=config('rabbit-vhost'))
+    hookenv.relation_set(relation_id=relation_id,
+                         username=hookenv.config('rabbit-user'),
+                         vhost=hookenv.config('rabbit-vhost'))
 
 
 @hooks.hook('amqp-relation-changed')
 @hooks.hook('amqp-relation-departed')
-@service_guard(guard_map(), CONFIGS,
-               active=config('service-guard'))
-@restart_on_change(restart_map())
+@ncc_utils.service_guard(ncc_utils.guard_map, resolve_CONFIGS,
+                         active=deferred_config('service-guard'))
+@ch_utils.pausable_restart_on_change(ncc_utils.restart_map)
 def amqp_changed():
     if 'amqp' not in CONFIGS.complete_contexts():
-        log('amqp relation incomplete. Peer not ready?')
+        hookenv.log('amqp relation incomplete. Peer not ready?')
         return
-    CONFIGS.write(NOVA_CONF)
+    CONFIGS.write(ncc_utils.NOVA_CONF)
     leader_init_db_if_ready_allowed_units()
     # db init for cells v2 requires amqp transport_url and db connections
     # to be set in nova.conf, so we attempt db init in here as well as the
     # db relation-changed hooks.
     update_cell_db_if_ready_allowed_units()
 
-    for r_id in relation_ids('nova-api'):
+    for r_id in hookenv.relation_ids('nova-api'):
         nova_api_relation_joined(rid=r_id)
 
     update_child_cell_records()
@@ -390,66 +324,73 @@ def amqp_changed():
     # NOTE: trigger restart on nova-api-metadata on
     #       neutron-gateway units once nova-cc has working
     #       amqp connection (avoiding service down on n-gateway)
-    for rid in relation_ids('quantum-network-service'):
+    for rid in hookenv.relation_ids('quantum-network-service'):
         quantum_joined(rid=rid, remote_restart=True)
 
 
 @hooks.hook('shared-db-relation-joined')
 def db_joined(relation_id=None):
-    cmp_os_release = CompareOpenStackReleases(os_release('nova-common'))
-    if config('prefer-ipv6'):
-        sync_db_with_multi_ipv6_addresses(config('database'),
-                                          config('database-user'),
-                                          relation_prefix='nova')
+    cmp_os_release = ch_utils.CompareOpenStackReleases(
+        ch_utils.os_release('nova-common'))
+    if hookenv.config('prefer-ipv6'):
+        ch_utils.sync_db_with_multi_ipv6_addresses(
+            hookenv.config('database'),
+            hookenv.config('database-user'),
+            relation_prefix='nova')
 
         if cmp_os_release >= 'mitaka':
             # NOTE: mitaka uses a second nova-api database as well
-            sync_db_with_multi_ipv6_addresses('nova_api',
-                                              config('database-user'),
-                                              relation_prefix='novaapi')
+            ch_utils.sync_db_with_multi_ipv6_addresses(
+                'nova_api',
+                hookenv.config('database-user'),
+                relation_prefix='novaapi')
 
         if cmp_os_release >= 'ocata':
             # NOTE: ocata requires cells v2
-            sync_db_with_multi_ipv6_addresses('nova_cell0',
-                                              config('database-user'),
-                                              relation_prefix='novacell0')
+            ch_utils.sync_db_with_multi_ipv6_addresses(
+                'nova_cell0',
+                hookenv.config('database-user'),
+                relation_prefix='novacell0')
     else:
         # Avoid churn check for access-network early
         access_network = None
-        for unit in related_units(relid=relation_id):
-            access_network = relation_get(rid=relation_id, unit=unit,
-                                          attribute='access-network')
+        for unit in hookenv.related_units(relid=relation_id):
+            access_network = hookenv.relation_get(rid=relation_id, unit=unit,
+                                                  attribute='access-network')
             if access_network:
                 break
-        host = get_relation_ip('shared-db', cidr_network=access_network)
+        host = ch_network_ip.get_relation_ip('shared-db',
+                                             cidr_network=access_network)
 
-        relation_set(nova_database=config('database'),
-                     nova_username=config('database-user'),
-                     nova_hostname=host,
-                     relation_id=relation_id)
+        hookenv.relation_set(nova_database=hookenv.config('database'),
+                             nova_username=hookenv.config('database-user'),
+                             nova_hostname=host,
+                             relation_id=relation_id)
 
         if cmp_os_release >= 'mitaka':
             # NOTE: mitaka uses a second nova-api database as well
-            relation_set(novaapi_database='nova_api',
-                         novaapi_username=config('database-user'),
-                         novaapi_hostname=host,
-                         relation_id=relation_id)
+            hookenv.relation_set(
+                novaapi_database='nova_api',
+                novaapi_username=hookenv.config('database-user'),
+                novaapi_hostname=host,
+                relation_id=relation_id)
 
         if cmp_os_release >= 'ocata':
             # NOTE: ocata requires cells v2
-            relation_set(novacell0_database='nova_cell0',
-                         novacell0_username=config('database-user'),
-                         novacell0_hostname=host,
-                         relation_id=relation_id)
+            hookenv.relation_set(
+                novacell0_database='nova_cell0',
+                novacell0_username=hookenv.config('database-user'),
+                novacell0_hostname=host,
+                relation_id=relation_id)
 
 
 @hooks.hook('shared-db-relation-changed')
-@service_guard(guard_map(), CONFIGS,
-               active=config('service-guard'))
-@restart_on_change(restart_map())
+@ncc_utils.service_guard(ncc_utils.guard_map, resolve_CONFIGS,
+                         active=deferred_config('service-guard'))
+@ch_utils.pausable_restart_on_change(ncc_utils.restart_map)
 def db_changed():
     if 'shared-db' not in CONFIGS.complete_contexts():
-        log('shared-db relation incomplete. Peer not ready?')
+        hookenv.log('shared-db relation incomplete. Peer not ready?')
         return
 
     CONFIGS.write_all()
@@ -462,88 +403,91 @@ def db_changed():
 
 
 @hooks.hook('image-service-relation-changed')
-@service_guard(guard_map(), CONFIGS,
-               active=config('service-guard'))
-@restart_on_change(restart_map())
+@ncc_utils.service_guard(ncc_utils.guard_map, resolve_CONFIGS,
+                         active=deferred_config('service-guard'))
+@ch_utils.pausable_restart_on_change(ncc_utils.restart_map)
 def image_service_changed():
     if 'image-service' not in CONFIGS.complete_contexts():
-        log('image-service relation incomplete. Peer not ready?')
+        hookenv.log('image-service relation incomplete. Peer not ready?')
         return
-    CONFIGS.write(NOVA_CONF)
+    CONFIGS.write(ncc_utils.NOVA_CONF)
     # TODO: special case config flag for essex (strip protocol)
 
-    for r_id in relation_ids('nova-api'):
+    for r_id in hookenv.relation_ids('nova-api'):
         nova_api_relation_joined(rid=r_id)
 
 
 @hooks.hook('identity-service-relation-joined')
 def identity_joined(rid=None):
-    if config('vip') and not is_clustered():
-        log('Defering registration until clustered', level=DEBUG)
+    if hookenv.config('vip') and not ch_cluster.is_clustered():
+        hookenv.log('Defering registration until clustered',
+                    level=hookenv.DEBUG)
         return
-
-    public_url = canonical_url(CONFIGS, PUBLIC)
-    internal_url = canonical_url(CONFIGS, INTERNAL)
-    admin_url = canonical_url(CONFIGS, ADMIN)
-    relation_set(
-        relation_id=rid,
-        **determine_endpoints(public_url,
-                              internal_url,
-                              admin_url))
+    public_url = ch_ip.canonical_url(CONFIGS, ch_ip.PUBLIC)
+    internal_url = ch_ip.canonical_url(CONFIGS, ch_ip.INTERNAL)
+    admin_url = ch_ip.canonical_url(CONFIGS, ch_ip.ADMIN)
+    hookenv.relation_set(relation_id=rid,
+                         **ncc_utils.determine_endpoints(public_url,
+                                                         internal_url,
+                                                         admin_url))
 
 
 @hooks.hook('identity-service-relation-changed')
-@service_guard(guard_map(), CONFIGS,
-               active=config('service-guard'))
-@restart_on_change(restart_map())
+@ncc_utils.service_guard(ncc_utils.guard_map, resolve_CONFIGS,
+                         active=deferred_config('service-guard'))
+@ch_utils.pausable_restart_on_change(ncc_utils.restart_map)
 def identity_changed():
     if 'identity-service' not in CONFIGS.complete_contexts():
-        log('identity-service relation incomplete. Peer not ready?')
+        hookenv.log('identity-service relation incomplete. Peer not ready?')
         return
     CONFIGS.write('/etc/nova/api-paste.ini')
-    CONFIGS.write(NOVA_CONF)
+    CONFIGS.write(ncc_utils.NOVA_CONF)
     update_nova_relation()
-    [nova_vmware_relation_joined(rid) for rid in relation_ids('nova-vmware')]
-    [neutron_api_relation_joined(rid) for rid in relation_ids('neutron-api')]
+    for rid in hookenv.relation_ids('nova-vmware'):
+        nova_vmware_relation_joined(rid)
+    for rid in hookenv.relation_ids('neutron-api'):
+        neutron_api_relation_joined(rid)
     configure_https()
 
-    for r_id in relation_ids('nova-api'):
+    for r_id in hookenv.relation_ids('nova-api'):
         nova_api_relation_joined(rid=r_id)
 
 
 @hooks.hook('nova-volume-service-relation-joined',
             'cinder-volume-service-relation-joined')
-@service_guard(guard_map(), CONFIGS,
-               active=config('service-guard'))
-@restart_on_change(restart_map())
+@ncc_utils.service_guard(ncc_utils.guard_map, resolve_CONFIGS,
+                         active=deferred_config('service-guard'))
+@ch_utils.pausable_restart_on_change(ncc_utils.restart_map)
 def volume_joined():
-    CONFIGS.write(NOVA_CONF)
+    CONFIGS.write(ncc_utils.NOVA_CONF)
     # kick identity_joined() to publish possibly new nova-volume endpoint.
-    [identity_joined(rid) for rid in relation_ids('identity-service')]
+    for rid in hookenv.relation_ids('identity-service'):
+        identity_joined(rid)
 
 
 def _auth_config():
     '''Grab all KS auth token config from api-paste.ini, or return empty {}'''
-    ks_auth_host = auth_token_config('auth_host')
+    ks_auth_host = ncc_utils.auth_token_config('auth_host')
     if not ks_auth_host:
         # if there is no auth_host set, identity-service changed hooks
         # have not fired, yet.
         return {}
     cfg = {
         'auth_host': ks_auth_host,
-        'auth_port': auth_token_config('auth_port'),
-        'auth_protocol': auth_token_config('auth_protocol'),
-        'service_protocol': auth_token_config('service_protocol'),
-        'service_port': auth_token_config('service_port'),
-        'service_username': auth_token_config('admin_user'),
-        'service_password': auth_token_config('admin_password'),
-        'service_tenant_name': auth_token_config('admin_tenant_name'),
-        'auth_uri': auth_token_config('auth_uri'),
+        'auth_port': ncc_utils.auth_token_config('auth_port'),
+        'auth_protocol': ncc_utils.auth_token_config('auth_protocol'),
+        'service_protocol': ncc_utils.auth_token_config('service_protocol'),
+        'service_port': ncc_utils.auth_token_config('service_port'),
+        'service_username': ncc_utils.auth_token_config('admin_user'),
+        'service_password': ncc_utils.auth_token_config('admin_password'),
+        'service_tenant_name': ncc_utils.auth_token_config(
+            'admin_tenant_name'),
+        'auth_uri': ncc_utils.auth_token_config('auth_uri'),
         # quantum-gateway interface deviates a bit.
         'keystone_host': ks_auth_host,
-        'service_tenant': auth_token_config('admin_tenant_name'),
+        'service_tenant': ncc_utils.auth_token_config('admin_tenant_name'),
         # add api version if found
-        'api_version': auth_token_config('api_version') or '2.0',
+        'api_version': ncc_utils.auth_token_config('api_version') or '2.0',
     }
     return cfg
 
@@ -560,17 +504,17 @@ def save_novarc():
         out.write('export OS_PASSWORD=%s\n' % auth['service_password'])
         out.write('export OS_TENANT_NAME=%s\n' % auth['service_tenant_name'])
         out.write('export OS_AUTH_URL=%s\n' % ks_url)
-        out.write('export OS_REGION_NAME=%s\n' % config('region'))
+        out.write('export OS_REGION_NAME=%s\n' % hookenv.config('region'))
 
 
 def neutron_settings():
     neutron_settings = {}
-    if is_relation_made('neutron-api', 'neutron-plugin'):
-        neutron_api_info = NeutronAPIContext()()
+    if hookenv.is_relation_made('neutron-api', 'neutron-plugin'):
+        neutron_api_info = nova_cc_context.NeutronAPIContext()()
         neutron_settings.update({
             # XXX: Rename these relations settings?
             'quantum_plugin': neutron_api_info['neutron_plugin'],
-            'region': config('region'),
+            'region': hookenv.config('region'),
             'quantum_security_groups':
             neutron_api_info['neutron_security_groups'],
             'quantum_url': neutron_api_info['neutron_url'],
@@ -585,11 +529,11 @@ def keystone_compute_settings():
     ks_auth_config = _auth_config()
     rel_settings = {}
 
-    if network_manager() == 'neutron':
+    if ch_neutron.network_manager() == 'neutron':
         if ks_auth_config:
             rel_settings.update(ks_auth_config)
         rel_settings.update(neutron_settings())
-    ks_ca = keystone_ca_cert_b64()
+    ks_ca = ncc_utils.keystone_ca_cert_b64()
     if ks_auth_config and ks_ca:
         rel_settings['ca_cert'] = ks_ca
     return rel_settings
@@ -597,61 +541,64 @@ def keystone_compute_settings():
 
 def console_settings():
     rel_settings = {}
-    proto = console_attributes('protocol')
+    proto = common.console_attributes('protocol')
     if not proto:
         return {}
-    rel_settings['console_keymap'] = config('console-keymap')
+    rel_settings['console_keymap'] = hookenv.config('console-keymap')
     rel_settings['console_access_protocol'] = proto
 
     console_ssl = False
-    if config('console-ssl-cert') and config('console-ssl-key'):
+    if (hookenv.config('console-ssl-cert') and
+            hookenv.config('console-ssl-key')):
         console_ssl = True
 
-    if config('console-proxy-ip') == 'local':
+    if hookenv.config('console-proxy-ip') == 'local':
         if console_ssl:
-            address = resolve_address(endpoint_type=PUBLIC)
-            address = format_ipv6_addr(address) or address
+            address = ch_ip.resolve_address(endpoint_type=ch_ip.PUBLIC)
+            address = ch_network_ip.format_ipv6_addr(address) or address
             proxy_base_addr = 'https://%s' % address
         else:
             # canonical_url will only return 'https:' if API SSL are enabled.
-            proxy_base_addr = canonical_url(CONFIGS, PUBLIC)
+            proxy_base_addr = ch_ip.canonical_url(CONFIGS, ch_ip.PUBLIC)
     else:
-        if console_ssl or https():
+        if console_ssl or ch_cluster.https():
             schema = "https"
         else:
             schema = "http"
 
-        proxy_base_addr = "%s://%s" % (schema, config('console-proxy-ip'))
+        proxy_base_addr = ("{}://{}"
+                           .format(schema, hookenv.config('console-proxy-ip')))
 
     if proto == 'vnc':
         protocols = ['novnc', 'xvpvnc']
     else:
         protocols = [proto]
     for _proto in protocols:
-        rel_settings['console_proxy_%s_address' % (_proto)] = \
-            "%s:%s%s" % (proxy_base_addr,
-                         console_attributes('proxy-port', proto=_proto),
-                         console_attributes('proxy-page', proto=_proto))
+        rel_settings['console_proxy_{}_address'.format(_proto)] = \
+            "{}:{}{}".format(
+                proxy_base_addr,
+                common.console_attributes('proxy-port', proto=_proto),
+                common.console_attributes('proxy-page', proto=_proto))
         rel_settings['console_proxy_%s_host' % (_proto)] = \
             urlparse(proxy_base_addr).hostname
         rel_settings['console_proxy_%s_port' % (_proto)] = \
-            console_attributes('proxy-port', proto=_proto)
+            common.console_attributes('proxy-port', proto=_proto)
 
     return rel_settings
 
 
 def get_compute_config(rid=None, remote_restart=False):
     cons_settings = console_settings()
-    relation_set(relation_id=rid, **cons_settings)
+    hookenv.relation_set(relation_id=rid, **cons_settings)
     rel_settings = {
-        'network_manager': network_manager(),
+        'network_manager': ch_neutron.network_manager(),
         'volume_service': 'cinder',
         # (comment from bash vers) XXX Should point to VIP if clustered, or
         # this may not even be needed.
-        'ec2_host': unit_get('private-address'),
-        'region': config('region'),
+        'ec2_host': hookenv.unit_get('private-address'),
+        'region': hookenv.config('region'),
     }
-    rel_settings.update(serial_console_settings())
+    rel_settings.update(ncc_utils.serial_console_settings())
     # update relation setting if we're attempting to restart remote
     # services
     if remote_restart:
@@ -661,11 +608,11 @@ def get_compute_config(rid=None, remote_restart=False):
 
 
 def update_nova_relation(remote_restart=False):
-    for rid in relation_ids('cloud-compute'):
+    for rid in hookenv.relation_ids('cloud-compute'):
         compute_joined(rid=rid, remote_restart=remote_restart)
-    for rid in relation_ids('quantum-network-service'):
+    for rid in hookenv.relation_ids('quantum-network-service'):
         quantum_joined(rid=rid, remote_restart=remote_restart)
-    for rid in relation_ids('nova-cell-api'):
+    for rid in hookenv.relation_ids('nova-cell-api'):
         nova_cell_api_relation_joined(rid=rid, remote_restart=remote_restart)
 
 
@@ -673,82 +620,87 @@ def update_nova_relation(remote_restart=False):
 def compute_joined(rid=None, remote_restart=False):
     rel_settings = get_compute_config(rid=rid, remote_restart=remote_restart)
     rel_settings.update(keystone_compute_settings())
-    relation_set(relation_id=rid, **rel_settings)
+    hookenv.relation_set(relation_id=rid, **rel_settings)
 
 
 @hooks.hook('cloud-compute-relation-changed')
 def compute_changed(rid=None, unit=None):
-    for r_id in relation_ids('nova-api'):
+    for r_id in hookenv.relation_ids('nova-api'):
         nova_api_relation_joined(rid=r_id)
 
-    rel_settings = relation_get(rid=rid, unit=unit)
-    if not rel_settings.get('region', None) == config('region'):
-        relation_set(relation_id=rid, region=config('region'))
+    rel_settings = hookenv.relation_get(rid=rid, unit=unit)
+    if not rel_settings.get('region', None) == hookenv.config('region'):
+        hookenv.relation_set(relation_id=rid, region=hookenv.config('region'))
 
-    if is_leader() and is_cellv2_init_ready() and is_db_initialised():
-        add_hosts_to_cell()
+    if (hookenv.is_leader() and
+            ncc_utils.is_cellv2_init_ready() and
+            ncc_utils.is_db_initialised()):
+        ncc_utils.add_hosts_to_cell()
 
     if 'migration_auth_type' not in rel_settings:
         return
     if rel_settings['migration_auth_type'] == 'ssh':
-        status_set('maintenance', 'configuring live migration')
+        hookenv.status_set('maintenance', 'configuring live migration')
         key = rel_settings.get('ssh_public_key')
         if not key:
-            log('SSH migration set but peer did not publish key.')
+            hookenv.log('SSH migration set but peer did not publish key.')
             return
-        ssh_compute_add(key, rid=rid, unit=unit)
+        ncc_utils.ssh_compute_add(key, rid=rid, unit=unit)
         index = 0
-        for line in ssh_known_hosts_lines(unit=unit):
-            relation_set(
-                relation_id=rid,
-                relation_settings={
-                    'known_hosts_{}'.format(index): line})
+        for line in ncc_utils.ssh_known_hosts_lines(unit=unit):
+            hookenv.relation_set(relation_id=rid,
+                                 relation_settings={
+                                     'known_hosts_{}'.format(index): line
+                                 })
             index += 1
-        relation_set(relation_id=rid, known_hosts_max_index=index)
+        hookenv.relation_set(relation_id=rid, known_hosts_max_index=index)
         index = 0
-        for line in ssh_authorized_keys_lines(unit=unit):
-            relation_set(
-                relation_id=rid,
-                relation_settings={
-                    'authorized_keys_{}'.format(index): line})
+        for line in ncc_utils.ssh_authorized_keys_lines(unit=unit):
+            hookenv.relation_set(relation_id=rid,
+                                 relation_settings={
+                                     'authorized_keys_{}'.format(index): line
+                                 })
             index += 1
-        relation_set(relation_id=rid, authorized_keys_max_index=index)
+        hookenv.relation_set(relation_id=rid, authorized_keys_max_index=index)
     if 'nova_ssh_public_key' not in rel_settings:
         return
     if rel_settings['nova_ssh_public_key']:
-        ssh_compute_add(rel_settings['nova_ssh_public_key'],
-                        rid=rid, unit=unit, user='nova')
+        ncc_utils.ssh_compute_add(rel_settings['nova_ssh_public_key'],
+                                  rid=rid, unit=unit, user='nova')
         index = 0
-        for line in ssh_known_hosts_lines(unit=unit, user='nova'):
-            relation_set(
-                relation_id=rid,
-                relation_settings={
-                    '{}_known_hosts_{}'.format(
-                        'nova',
-                        index): line})
+        for line in ncc_utils.ssh_known_hosts_lines(unit=unit, user='nova'):
+            hookenv.relation_set(relation_id=rid,
+                                 relation_settings={
+                                     '{}_known_hosts_{}'.format('nova',
+                                                                index): line
+                                 })
             index += 1
-        relation_set(
-            relation_id=rid,
-            relation_settings={
-                '{}_known_hosts_max_index'.format('nova'): index})
+        hookenv.relation_set(relation_id=rid,
+                             relation_settings={
+                                 ('{}_known_hosts_max_index'
+                                  .format('nova')): index
+                             })
         index = 0
-        for line in ssh_authorized_keys_lines(unit=unit, user='nova'):
-            relation_set(
-                relation_id=rid,
-                relation_settings={
-                    '{}_authorized_keys_{}'.format(
-                        'nova',
-                        index): line})
+        for line in ncc_utils.ssh_authorized_keys_lines(
+                unit=unit, user='nova'):
+            hookenv.relation_set(relation_id=rid,
+                                 relation_settings={
+                                     '{}_authorized_keys_{}'.format(
+                                         'nova',
+                                         index): line
+                                 })
             index += 1
-        relation_set(
-            relation_id=rid,
-            relation_settings={
-                '{}_authorized_keys_max_index'.format('nova'): index})
+        hookenv.relation_set(relation_id=rid,
+                             relation_settings={
+                                 '{}_authorized_keys_max_index'.format(
+                                     'nova'): index
+                             })
 
 
 @hooks.hook('cloud-compute-relation-departed')
 def compute_departed():
-    ssh_compute_remove(public_key=relation_get('ssh_public_key'))
+    ncc_utils.ssh_compute_remove(
+        public_key=hookenv.relation_get('ssh_public_key'))
 
 
 @hooks.hook('neutron-network-service-relation-joined',
@@ -761,7 +713,7 @@ def quantum_joined(rid=None, remote_restart=False):
     rel_settings.update(ks_auth_config)
 
     # must pass the keystone CA cert, if it exists.
-    ks_ca = keystone_ca_cert_b64()
+    ks_ca = ncc_utils.keystone_ca_cert_b64()
     if ks_auth_config and ks_ca:
         rel_settings['ca_cert'] = ks_ca
 
@@ -770,62 +722,63 @@ def quantum_joined(rid=None, remote_restart=False):
     if remote_restart:
         rel_settings['restart_trigger'] = str(uuid.uuid4())
 
-    rel_settings.update(get_metadata_settings(CONFIGS))
-    relation_set(relation_id=rid, **rel_settings)
+    rel_settings.update(ncc_utils.get_metadata_settings(CONFIGS))
+    hookenv.relation_set(relation_id=rid, **rel_settings)
 
 
 @hooks.hook('cluster-relation-joined')
 def cluster_joined(relation_id=None):
     settings = {}
 
-    for addr_type in ADDRESS_TYPES:
-        address = get_relation_ip(
+    for addr_type in ch_context.ADDRESS_TYPES:
+        address = ch_network_ip.get_relation_ip(
             addr_type,
-            cidr_network=config('os-{}-network'.format(addr_type)))
+            cidr_network=hookenv.config('os-{}-network'.format(addr_type)))
         if address:
             settings['{}-address'.format(addr_type)] = address
 
-    settings['private-address'] = get_relation_ip('cluster')
+    settings['private-address'] = ch_network_ip.get_relation_ip('cluster')
 
-    relation_set(relation_id=relation_id, relation_settings=settings)
+    hookenv.relation_set(relation_id=relation_id, relation_settings=settings)
 
 
 @hooks.hook('cluster-relation-changed',
             'cluster-relation-departed',
             'leader-settings-changed')
-@service_guard(guard_map(), CONFIGS,
-               active=config('service-guard'))
-@restart_on_change(restart_map(), stopstart=True)
+@ncc_utils.service_guard(ncc_utils.guard_map, resolve_CONFIGS,
+                         active=deferred_config('service-guard'))
+@ch_utils.pausable_restart_on_change(ncc_utils.restart_map, stopstart=True)
 def cluster_changed():
     CONFIGS.write_all()
-    if relation_ids('cluster'):
-        peer_echo(includes=['dbsync_state'])
-        dbsync_state = peer_retrieve('dbsync_state')
+    if hookenv.relation_ids('cluster'):
+        ch_peerstorage.peer_echo(includes=['dbsync_state'])
+        dbsync_state = ch_peerstorage.peer_retrieve('dbsync_state')
         if dbsync_state == 'complete':
-            if not is_unit_paused_set():
-                for svc in services():
-                    service_resume(svc)
+            if not ch_utils.is_unit_paused_set():
+                for svc in ncc_utils.services():
+                    ch_host.service_resume(svc)
             else:
-                log('Unit is in paused state, not issuing start/resume to all '
-                    'services')
+                hookenv.log('Unit is in paused state, not issuing '
+                            'start/resume to all services')
         else:
-            if not is_unit_paused_set():
-                log('Database sync not ready. Shutting down services')
-                for svc in services():
-                    service_pause(svc)
+            if not ch_utils.is_unit_paused_set():
+                hookenv.log('Database sync not ready. Shutting down services')
+                for svc in ncc_utils.services():
+                    ch_host.service_pause(svc)
             else:
-                log('Database sync not ready. Would shut down services but '
+                hookenv.log(
+                    'Database sync not ready. Would shut down services but '
                     'unit is in paused state, not issuing stop/pause to all '
                     'services')
     # The shared metadata secret is stored in the leader-db and if its changed
     # the gateway needs to know.
-    for rid in relation_ids('quantum-network-service'):
+    for rid in hookenv.relation_ids('quantum-network-service'):
         quantum_joined(rid=rid, remote_restart=False)
 
 
 @hooks.hook('ha-relation-joined')
 def ha_joined(relation_id=None):
-    cluster_config = get_hacluster_config()
+    cluster_config = ch_cluster.get_hacluster_config()
     resources = {
         'res_nova_haproxy': 'lsb:haproxy',
     }
@@ -840,24 +793,25 @@ def ha_joined(relation_id=None):
     }
     colocations = {}
 
-    if config('dns-ha'):
-        update_dns_ha_resource_params(relation_id=relation_id,
-                                      resources=resources,
-                                      resource_params=resource_params)
+    if hookenv.config('dns-ha'):
+        ch_ha_utils.update_dns_ha_resource_params(
+            relation_id=relation_id,
+            resources=resources,
+            resource_params=resource_params)
     else:
         vip_group = []
         for vip in cluster_config['vip'].split():
-            if is_ipv6(vip):
+            if ch_network_ip.is_ipv6(vip):
                 res_nova_vip = 'ocf:heartbeat:IPv6addr'
                 vip_params = 'ipv6addr'
             else:
                 res_nova_vip = 'ocf:heartbeat:IPaddr2'
                 vip_params = 'ip'
 
-            iface = (get_iface_for_address(vip) or
-                     config('vip_iface'))
-            netmask = (get_netmask_for_address(vip) or
-                       config('vip_cidr'))
+            iface = (ch_network_ip.get_iface_for_address(vip) or
+                     hookenv.config('vip_iface'))
+            netmask = (ch_network_ip.get_netmask_for_address(vip) or
+                       hookenv.config('vip_cidr'))
 
             if iface is not None:
                 vip_key = 'res_nova_{}_vip'.format(iface)
@@ -865,8 +819,10 @@ def ha_joined(relation_id=None):
                     if vip not in resource_params[vip_key]:
                         vip_key = '{}_{}'.format(vip_key, vip_params)
                     else:
-                        log("Resource '%s' (vip='%s') already exists in "
-                            "vip group - skipping" % (vip_key, vip), WARNING)
+                        hookenv.log(
+                            "Resource '%s' (vip='%s') already exists in "
+                            "vip group - skipping" % (vip_key, vip),
+                            hookenv.WARNING)
                         continue
 
                 resources[vip_key] = res_nova_vip
@@ -880,56 +836,57 @@ def ha_joined(relation_id=None):
                 vip_group.append(vip_key)
 
             if len(vip_group) >= 1:
-                relation_set(groups={'grp_nova_vips': ' '.join(vip_group)})
+                hookenv.relation_set(
+                    groups={'grp_nova_vips': ' '.join(vip_group)})
 
-        if (config('single-nova-consoleauth') and
-                console_attributes('protocol')):
+        if (hookenv.config('single-nova-consoleauth') and
+                common.console_attributes('protocol')):
             colocations['vip_consoleauth'] = COLO_CONSOLEAUTH
             init_services['res_nova_consoleauth'] = 'nova-consoleauth'
             resources['res_nova_consoleauth'] = AGENT_CONSOLEAUTH
             resource_params['res_nova_consoleauth'] = AGENT_CA_PARAMS
 
-    relation_set(relation_id=relation_id,
-                 init_services=init_services,
-                 corosync_bindiface=cluster_config['ha-bindiface'],
-                 corosync_mcastport=cluster_config['ha-mcastport'],
-                 resources=resources,
-                 resource_params=resource_params,
-                 clones=clones,
-                 colocations=colocations)
+    hookenv.relation_set(relation_id=relation_id,
+                         init_services=init_services,
+                         corosync_bindiface=cluster_config['ha-bindiface'],
+                         corosync_mcastport=cluster_config['ha-mcastport'],
+                         resources=resources,
+                         resource_params=resource_params,
+                         clones=clones,
+                         colocations=colocations)
 
 
 @hooks.hook('ha-relation-changed')
 def ha_changed():
-    clustered = relation_get('clustered')
+    clustered = hookenv.relation_get('clustered')
     if not clustered or clustered in [None, 'None', '']:
-        log('ha_changed: hacluster subordinate not fully clustered.')
+        hookenv.log('ha_changed: hacluster subordinate not fully clustered.')
         return
 
-    CONFIGS.write(NOVA_CONF)
+    CONFIGS.write(ncc_utils.NOVA_CONF)
 
-    log('Cluster configured, notifying other services and updating '
-        'keystone endpoint configuration')
-    for rid in relation_ids('identity-service'):
+    hookenv.log('Cluster configured, notifying other services and updating '
+                'keystone endpoint configuration')
+    for rid in hookenv.relation_ids('identity-service'):
         identity_joined(rid=rid)
 
     update_nova_consoleauth_config()
 
 
 @hooks.hook('shared-db-relation-broken')
-@service_guard(guard_map(), CONFIGS,
-               active=config('service-guard'))
+@ncc_utils.service_guard(ncc_utils.guard_map, resolve_CONFIGS,
+                         active=deferred_config('service-guard'))
 def db_departed():
     CONFIGS.write_all()
     update_cell_db_if_ready(skip_acl_check=True)
-    for r_id in relation_ids('cluster'):
-        relation_set(relation_id=r_id, dbsync_state='incomplete')
-    if not is_unit_paused_set():
-        for svc in services():
-            service_pause(svc)
+    for r_id in hookenv.relation_ids('cluster'):
+        hookenv.relation_set(relation_id=r_id, dbsync_state='incomplete')
+    if not ch_utils.is_unit_paused_set():
+        for svc in ncc_utils.services():
+            ch_host.service_pause(svc)
     else:
-        log('Unit is in paused state, not issuing stop/pause to all '
-            'services')
+        hookenv.log('Unit is in paused state, not issuing stop/pause to all '
+                    'services')
 
 
 @hooks.hook('amqp-relation-broken',
@@ -938,8 +895,8 @@ def db_departed():
             'image-service-relation-broken',
             'nova-volume-service-relation-broken',
             'quantum-network-service-relation-broken')
-@service_guard(guard_map(), CONFIGS,
-               active=config('service-guard'))
+@ncc_utils.service_guard(ncc_utils.guard_map, resolve_CONFIGS,
+                         active=deferred_config('service-guard'))
 def relation_broken():
     CONFIGS.write_all()
 
@@ -954,53 +911,54 @@ def configure_https():
     CONFIGS.write_all()
     if 'https' in CONFIGS.complete_contexts():
         cmd = ['a2ensite', 'openstack_https_frontend']
-        check_call(cmd)
+        subprocess.check_call(cmd)
     else:
         cmd = ['a2dissite', 'openstack_https_frontend']
-        check_call(cmd)
+        subprocess.check_call(cmd)
 
     # TODO: improve this by checking if local CN certs are available
     # first then checking reload status (see LP #1433114).
-    if not is_unit_paused_set():
-        service_reload('apache2', restart_on_failure=True)
+    if not ch_utils.is_unit_paused_set():
+        ch_host.service_reload('apache2', restart_on_failure=True)
 
-    for rid in relation_ids('identity-service'):
+    for rid in hookenv.relation_ids('identity-service'):
         identity_joined(rid=rid)
 
 
 @hooks.hook()
 def nova_vmware_relation_joined(rid=None):
-    rel_settings = {'network_manager': network_manager()}
+    rel_settings = {'network_manager': ch_neutron.network_manager()}
 
     ks_auth = _auth_config()
     if ks_auth:
         rel_settings.update(ks_auth)
         rel_settings.update(neutron_settings())
 
-    relation_set(relation_id=rid, **rel_settings)
+    hookenv.relation_set(relation_id=rid, **rel_settings)
 
 
 @hooks.hook('nova-vmware-relation-changed')
-@service_guard(guard_map(), CONFIGS,
-               active=config('service-guard'))
-@restart_on_change(restart_map())
+@ncc_utils.service_guard(ncc_utils.guard_map, resolve_CONFIGS,
+                         active=deferred_config('service-guard'))
+@ch_utils.pausable_restart_on_change(ncc_utils.restart_map)
 def nova_vmware_relation_changed():
     CONFIGS.write('/etc/nova/nova.conf')
 
 
 @hooks.hook('upgrade-charm')
-@harden()
+@ch_harden.harden()
 def upgrade_charm():
-    apt_install(filter_installed_packages(determine_packages()),
-                fatal=True)
-    for r_id in relation_ids('amqp'):
+    ch_fetch.apt_install(
+        ch_fetch.filter_installed_packages(
+            ncc_utils.determine_packages()), fatal=True)
+    for r_id in hookenv.relation_ids('amqp'):
         amqp_joined(relation_id=r_id)
-    for r_id in relation_ids('identity-service'):
+    for r_id in hookenv.relation_ids('identity-service'):
         identity_joined(rid=r_id)
-    for r_id in relation_ids('cloud-compute'):
-        for unit in related_units(r_id):
+    for r_id in hookenv.relation_ids('cloud-compute'):
+        for unit in hookenv.related_units(r_id):
             compute_changed(r_id, unit)
-    for r_id in relation_ids('shared-db'):
+    for r_id in hookenv.relation_ids('shared-db'):
         db_joined(relation_id=r_id)
 
     leader_init_db_if_ready_allowed_units()
@@ -1011,29 +969,29 @@ def upgrade_charm():
 
 @hooks.hook('neutron-api-relation-joined')
 def neutron_api_relation_joined(rid=None, remote_restart=False):
-    for id_rid in relation_ids('identity-service'):
+    for id_rid in hookenv.relation_ids('identity-service'):
         identity_joined(rid=id_rid)
     rel_settings = {
-        'nova_url': canonical_url(CONFIGS, INTERNAL) + ":8774/v2"
+        'nova_url': ch_ip.canonical_url(CONFIGS, ch_ip.INTERNAL) + ":8774/v2"
     }
     if remote_restart:
         rel_settings['restart_trigger'] = str(uuid.uuid4())
-    relation_set(relation_id=rid, **rel_settings)
+    hookenv.relation_set(relation_id=rid, **rel_settings)
 
 
 @hooks.hook('neutron-api-relation-changed')
-@service_guard(guard_map(), CONFIGS,
-               active=config('service-guard'))
-@restart_on_change(restart_map())
+@ncc_utils.service_guard(ncc_utils.guard_map, resolve_CONFIGS,
+                         active=deferred_config('service-guard'))
+@ch_utils.pausable_restart_on_change(ncc_utils.restart_map)
 def neutron_api_relation_changed():
-    CONFIGS.write(NOVA_CONF)
+    CONFIGS.write(ncc_utils.NOVA_CONF)
     update_nova_relation()
 
 
 @hooks.hook('neutron-api-relation-broken')
-@service_guard(guard_map(), CONFIGS,
-               active=config('service-guard'))
-@restart_on_change(restart_map())
+@ncc_utils.service_guard(ncc_utils.guard_map, resolve_CONFIGS,
+                         active=deferred_config('service-guard'))
+@ch_utils.pausable_restart_on_change(ncc_utils.restart_map)
 def neutron_api_relation_broken():
     CONFIGS.write_all()
     update_nova_relation()
@@ -1043,12 +1001,14 @@ def neutron_api_relation_broken():
             'nrpe-external-master-relation-changed')
 def update_nrpe_config():
     # python-dbus is used by check_upstart_job
-    apt_install('python-dbus')
+    ch_fetch.apt_install('python-dbus')
     hostname = nrpe.get_nagios_hostname()
     current_unit = nrpe.get_nagios_unit_name()
     nrpe_setup = nrpe.NRPE(hostname=hostname)
     nrpe.copy_nrpe_checks()
-    nrpe.add_init_service_checks(nrpe_setup, services(), current_unit)
+    nrpe.add_init_service_checks(nrpe_setup,
+                                 ncc_utils.services(),
+                                 current_unit)
     nrpe.add_haproxy_checks(nrpe_setup, current_unit)
     nrpe_setup.write()
 
@@ -1059,9 +1019,11 @@ def memcached_joined():
     spaces address rather than leaving it as the unit address.  This is to
     support network spaces in the memcached charm.
     """
-    relation_set(
+    hookenv.relation_set(
         relation_id=None,
-        relation_settings={'private-address': get_relation_ip('memcache')})
+        relation_settings={
+            'private-address': ch_network_ip.get_relation_ip('memcache')
+        })
     memcached_common()
 
 
@@ -1072,36 +1034,38 @@ def memcached_other_hooks():
     memcached_common()
 
 
-@restart_on_change(restart_map())
+@ch_utils.pausable_restart_on_change(ncc_utils.restart_map)
 def memcached_common():
-    CONFIGS.write(NOVA_CONF)
+    CONFIGS.write(ncc_utils.NOVA_CONF)
 
 
 @hooks.hook('zeromq-configuration-relation-changed')
-@restart_on_change(restart_map(), stopstart=True)
+@ch_utils.pausable_restart_on_change(ncc_utils.restart_map, stopstart=True)
 def zeromq_configuration_relation_changed():
-    CONFIGS.write(NOVA_CONF)
+    CONFIGS.write(ncc_utils.NOVA_CONF)
 
 
 def update_nova_consoleauth_config():
     """
     Configure nova-consoleauth pacemaker resources
     """
-    relids = relation_ids('ha')
+    relids = hookenv.relation_ids('ha')
     if len(relids) == 0:
-        log('Related to {} ha services'.format(len(relids)), level='DEBUG')
+        hookenv.log('Related to {} ha services'.format(len(relids)),
+                    level=hookenv.DEBUG)
         ha_relid = None
         data = {}
     else:
         ha_relid = relids[0]
-        data = relation_get(rid=ha_relid) or {}
+        data = hookenv.relation_get(rid=ha_relid) or {}
 
     # initialize keys in case this is a new dict
     data.setdefault('delete_resources', [])
     for k in ['colocations', 'init_services', 'resources', 'resource_params']:
         data.setdefault(k, {})
 
-    if config('single-nova-consoleauth') and console_attributes('protocol'):
+    if (hookenv.config('single-nova-consoleauth') and
+            common.console_attributes('protocol')):
         for item in ['vip_consoleauth', 'res_nova_consoleauth']:
             try:
                 data['delete_resources'].remove(item)
@@ -1114,16 +1078,16 @@ def update_nova_consoleauth_config():
         data['resources']['res_nova_consoleauth'] = AGENT_CONSOLEAUTH
         data['resource_params']['res_nova_consoleauth'] = AGENT_CA_PARAMS
 
-        for rid in relation_ids('ha'):
-            relation_set(rid, **data)
+        for rid in hookenv.relation_ids('ha'):
+            hookenv.relation_set(rid, **data)
 
         # nova-consoleauth will be managed by pacemaker, so stop it
         # and prevent it to be started again at boot. (LP: #1693629).
-        if relation_ids('ha'):
-            service_pause('nova-consoleauth')
+        if hookenv.relation_ids('ha'):
+            ch_host.service_pause('nova-consoleauth')
 
-    elif (not config('single-nova-consoleauth') and
-          console_attributes('protocol')):
+    elif (not hookenv.config('single-nova-consoleauth') and
+          common.console_attributes('protocol')):
         for item in ['vip_consoleauth', 'res_nova_consoleauth']:
             if item not in data['delete_resources']:
                 data['delete_resources'].append(item)
@@ -1135,61 +1099,62 @@ def update_nova_consoleauth_config():
         data['resources'].pop('res_nova_consoleauth', None)
         data['resource_params'].pop('res_nova_consoleauth', None)
 
-        for rid in relation_ids('ha'):
-            relation_set(rid, **data)
+        for rid in hookenv.relation_ids('ha'):
+            hookenv.relation_set(rid, **data)
 
-        service_resume('nova-consoleauth')
+        ch_host.service_resume('nova-consoleauth')
 
 
 def nova_api_relation_joined(rid=None):
     rel_data = {
-        'nova-api-ready': 'yes' if is_api_ready(CONFIGS) else 'no'
+        'nova-api-ready': 'yes' if ncc_utils.is_api_ready(CONFIGS) else 'no'
     }
-    relation_set(rid, **rel_data)
+    hookenv.relation_set(rid, **rel_data)
 
 
 @hooks.hook('certificates-relation-joined')
 def certs_joined(relation_id=None):
-    relation_set(
+    hookenv.relation_set(
         relation_id=relation_id,
-        relation_settings=get_certificate_request())
+        relation_settings=cert_utils.get_certificate_request())
 
 
 @hooks.hook('certificates-relation-changed')
-@restart_on_change(restart_map(), stopstart=True)
+@ch_utils.pausable_restart_on_change(ncc_utils.restart_map, stopstart=True)
 def certs_changed(relation_id=None, unit=None):
-    process_certificates('nova', relation_id, unit)
+    cert_utils.process_certificates('nova', relation_id, unit)
     configure_https()
 
 
 @hooks.hook('amqp-cell-relation-joined')
 def amqp_cell_joined(relation_id=None):
-    relation_set(relation_id=relation_id,
-                 username='nova', vhost='nova')
+    hookenv.relation_set(relation_id=relation_id,
+                         username='nova', vhost='nova')
 
 
 @hooks.hook('shared-db-cell-relation-joined')
 def shared_db_cell_joined(relation_id=None):
     access_network = None
-    for unit in related_units(relid=relation_id):
-        access_network = relation_get(rid=relation_id, unit=unit,
-                                      attribute='access-network')
+    for unit in hookenv.related_units(relid=relation_id):
+        access_network = hookenv.relation_get(rid=relation_id, unit=unit,
+                                              attribute='access-network')
         if access_network:
             break
-        host = get_relation_ip('shared-db', cidr_network=access_network)
+        host = ch_network_ip.get_relation_ip('shared-db',
+                                             cidr_network=access_network)
     cell_db = {
         'nova_database': 'nova',
-        'nova_username': config('database-user'),
+        'nova_username': hookenv.config('database-user'),
         'nova_hostname': host}
-    relation_set(relation_id=relation_id, **cell_db)
+    hookenv.relation_set(relation_id=relation_id, **cell_db)
 
 
 @hooks.hook('nova-cell-api-relation-joined')
 def nova_cell_api_relation_joined(rid=None, remote_restart=False):
     rel_settings = get_compute_config(rid=rid, remote_restart=remote_restart)
-    if network_manager() == 'neutron':
+    if ch_neutron.network_manager() == 'neutron':
         rel_settings.update(neutron_settings())
-    relation_set(relation_id=rid, **rel_settings)
+    hookenv.relation_set(relation_id=rid, **rel_settings)
 
 
 @hooks.hook('shared-db-cell-relation-changed')
@@ -1204,50 +1169,49 @@ def amqp_cell_changed(relation_id=None):
 
 @hooks.hook('nova-cell-api-relation-changed')
 def nova_cell_api_relation_changed(rid=None, unit=None):
-    data = relation_get(rid=rid, unit=unit)
-    log("Data: {}".format(data, level=DEBUG))
+    data = hookenv.relation_get(rid=rid, unit=unit)
+    ch_neutron.log("Data: {}".format(data, level=hookenv.DEBUG))
     if not data.get('cell-name'):
         return
-    cell_updated = update_child_cell(
+    cell_updated = ncc_utils.update_child_cell(
         name=data['cell-name'],
         db_service=data['db-service'],
         amqp_service=data['amqp-service'])
     if cell_updated:
-        log(
+        hookenv.log(
             "Cell registration data changed, triggering a remote restart",
-            level=DEBUG)
-        relation_set(
+            level=hookenv.DEBUG)
+        hookenv.relation_set(
             relation_id=rid,
             restart_trigger=str(uuid.uuid4()))
 
 
 @hooks.hook('update-status')
-@harden()
+@ch_harden.harden()
 def update_status():
-    log('Updating status.')
+    hookenv.log('Updating status.')
 
 
 @hooks.hook('pre-series-upgrade')
 def pre_series_upgrade():
-    log("Running prepare series upgrade hook", "INFO")
-    series_upgrade_prepare(
-        pause_unit_helper, CONFIGS)
+    hookenv.log("Running prepare series upgrade hook", "INFO")
+    ch_utils.series_upgrade_prepare(ncc_utils.pause_unit_helper, CONFIGS)
 
 
 @hooks.hook('post-series-upgrade')
 def post_series_upgrade():
-    log("Running complete series upgrade hook", "INFO")
-    series_upgrade_complete(
-        resume_unit_helper, CONFIGS)
+    hookenv.log("Running complete series upgrade hook", "INFO")
+    ch_utils.series_upgrade_complete(ncc_utils.resume_unit_helper, CONFIGS)
 
 
 def main():
     try:
         hooks.execute(sys.argv)
-    except UnregisteredHookError as e:
-        log('Unknown hook {} - skipping.'.format(e))
-    assess_status(CONFIGS)
+    except hookenv.UnregisteredHookError as e:
+        hookenv.log('Unknown hook {} - skipping.'.format(e))
+    ncc_utils.assess_status(CONFIGS)
 
 
 if __name__ == '__main__':
+    resolve_CONFIGS()
     main()
diff --git a/hooks/nova_cc_utils.py b/hooks/nova_cc_utils.py
index deb9c397..847b6400 100644
--- a/hooks/nova_cc_utils.py
+++ b/hooks/nova_cc_utils.py
@@ -12,104 +12,30 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import base64
+import collections
+import configparser
+import copy
+import json
 import os
 import subprocess
-import ConfigParser
+from urllib.parse import urlparse
 import uuid
 
-from base64 import b64encode
-from collections import OrderedDict
-from copy import deepcopy
-from urlparse import urlparse
-from uuid import uuid1
-import json
+import charmhelpers.contrib.hahelpers.cluster as ch_cluster
+import charmhelpers.contrib.network.ip as ch_ip
+import charmhelpers.contrib.openstack.context as ch_context
+import charmhelpers.contrib.openstack.ip as ch_openstack_ip
+import charmhelpers.contrib.openstack.templating as ch_templating
+import charmhelpers.contrib.openstack.utils as ch_utils
+import charmhelpers.contrib.peerstorage as ch_peerstorage
+import charmhelpers.core.decorators as ch_decorators
+import charmhelpers.core.hookenv as hookenv
+import charmhelpers.core.host as ch_host
+import charmhelpers.fetch as ch_fetch
 
-from charmhelpers.contrib.openstack import context, templating
-
-from charmhelpers.contrib.hahelpers.cluster import (
-    get_hacluster_config,
-)
-
-from charmhelpers.contrib.peerstorage import (
-    peer_retrieve,
-    peer_store,
-)
-
-from charmhelpers.contrib.openstack.utils import (
-    configure_installation_source,
-    get_host_ip,
-    get_hostname,
-    get_os_codename_install_source,
-    incomplete_relation_data,
-    is_ip,
-    os_release,
-    reset_os_release,
-    save_script_rc as _save_script_rc,
-    is_unit_paused_set,
-    make_assess_status_func,
-    pause_unit,
-    resume_unit,
-    os_application_version_set,
-    token_cache_pkgs,
-    enable_memcache,
-    CompareOpenStackReleases,
-)
-
-from charmhelpers.fetch import (
-    apt_upgrade,
-    apt_update,
-    apt_install,
-    apt_purge,
-    apt_autoremove,
-    add_source,
-    filter_installed_packages,
-    filter_missing_packages,
-)
-
-from charmhelpers.core.hookenv import (
-    config,
-    is_leader,
-    log,
-    leader_get,
-    leader_set,
-    relation_get,
-    relation_id,
-    relation_ids,
-    remote_unit,
-    DEBUG,
-    ERROR,
-    INFO,
-    status_set,
-    related_units,
-    local_unit,
-)
-
-from charmhelpers.core.host import (
-    service_pause,
-    service_resume,
-    service_running,
-    service_start,
-    service_stop,
-    service_restart,
-    lsb_release,
-    CompareHostReleases,
-)
-
-from charmhelpers.contrib.network.ip import (
-    is_ipv6,
-    ns_query,
-)
-
-from charmhelpers.core.decorators import (
-    retry_on_exception,
-)
-
-from charmhelpers.contrib.openstack.ip import (
-    canonical_url,
-    INTERNAL,
-)
-
-import nova_cc_context
+import hooks.nova_cc_common as common
+import hooks.nova_cc_context as nova_cc_context
 
 TEMPLATES = 'templates/'
 
@@ -168,14 +94,8 @@ SERVICE_BLACKLIST = {
     'newton': ['nova-cert'],
 }
 
-API_PORTS = {
-    'nova-api-ec2': 8773,
-    'nova-api-os-compute': 8774,
-    'nova-api-metadata': 8775,
-    'nova-api-os-volume': 8776,
-    'nova-placement-api': 8778,
-    'nova-objectstore': 3333,
-}
+# API_PORTS is now in nova_cc_common.py to break the circular dependency
+# between nova_cc_utils.py and nova_cc_context.py
 
 NOVA_CONF_DIR = "/etc/nova"
 NEUTRON_CONF_DIR = "/etc/neutron"
@@ -196,97 +116,98 @@ VENDORDATA_FILE = '/etc/nova/vendor_data.json'
 
 
 def resolve_services():
-    _services = deepcopy(BASE_SERVICES)
-    os_rel = os_release('nova-common')
+    _services = copy.deepcopy(BASE_SERVICES)
+    os_rel = ch_utils.os_release('nova-common')
+    cmp_os_release = ch_utils.CompareOpenStackReleases(os_rel)
     for release in SERVICE_BLACKLIST:
-        if os_rel >= release or config('disable-aws-compat'):
-            [_services.remove(service)
-             for service in SERVICE_BLACKLIST[release]]
+        if cmp_os_release >= release or hookenv.config('disable-aws-compat'):
+            for service in SERVICE_BLACKLIST[release]:
+                _services.remove(service)
     return _services
 
 
-BASE_RESOURCE_MAP = OrderedDict([
-    (NOVA_CONF, {
-        'services': resolve_services(),
-        'contexts': [context.AMQPContext(ssl_dir=NOVA_CONF_DIR),
-                     context.SharedDBContext(
-                         relation_prefix='nova', ssl_dir=NOVA_CONF_DIR),
-                     context.OSConfigFlagContext(
-                         charm_flag='nova-alchemy-flags',
-                         template_flag='nova_alchemy_flags'),
-                     context.ImageServiceContext(),
-                     context.OSConfigFlagContext(),
-                     context.SubordinateConfigContext(
-                         interface='nova-vmware',
-                         service='nova',
-                         config_file=NOVA_CONF),
-                     context.SyslogContext(),
-                     context.LogLevelContext(),
-                     nova_cc_context.HAProxyContext(),
-                     nova_cc_context.IdentityServiceContext(
-                         service='nova',
-                         service_user='nova'),
-                     nova_cc_context.VolumeServiceContext(),
-                     context.ZeroMQContext(),
-                     context.NotificationDriverContext(),
-                     nova_cc_context.NovaIPv6Context(),
-                     nova_cc_context.NeutronCCContext(),
-                     nova_cc_context.NovaConfigContext(),
-                     nova_cc_context.InstanceConsoleContext(),
-                     nova_cc_context.ConsoleSSLContext(),
-                     nova_cc_context.CloudComputeContext(),
-                     context.InternalEndpointContext(),
-                     context.VolumeAPIContext('nova-common'),
-                     nova_cc_context.NeutronAPIContext(),
-                     nova_cc_context.SerialConsoleContext(),
-                     context.MemcacheContext(),
-                     nova_cc_context.NovaMetadataContext()],
-    }),
-    (NOVA_API_PASTE, {
-        'services': [s for s in resolve_services() if 'api' in s],
-        'contexts': [nova_cc_context.IdentityServiceContext(),
-                     nova_cc_context.APIRateLimitingContext()],
-    }),
-    (HAPROXY_CONF, {
-        'contexts': [context.HAProxyContext(singlenode_mode=True),
-                     nova_cc_context.HAProxyContext()],
-        'services': ['haproxy'],
-    }),
-    (APACHE_CONF, {
-        'contexts': [nova_cc_context.ApacheSSLContext()],
-        'services': ['apache2'],
-    }),
-    (APACHE_24_CONF, {
-        'contexts': [nova_cc_context.ApacheSSLContext()],
-        'services': ['apache2'],
-    }),
-])
+# _BASE_RESOURCE_MAP is a caching global that is set up by
+# get_base_resource_map()
+_BASE_RESOURCE_MAP = None
+
+
+def get_base_resource_map():
+    """Return the base resource map.  Note that it is cached in the
+    _BASE_RESOURCE_MAP global.
+
+    :returns: The base resource map
+    :rtype: collections.OrderedDict
+    """
+    global _BASE_RESOURCE_MAP
+    if _BASE_RESOURCE_MAP is None:
+        _BASE_RESOURCE_MAP = collections.OrderedDict([
+            (NOVA_CONF, {
+                'services': resolve_services(),
+                'contexts': [
+                    ch_context.AMQPContext(ssl_dir=NOVA_CONF_DIR),
+                    ch_context.SharedDBContext(
+                        relation_prefix='nova',
+                        ssl_dir=NOVA_CONF_DIR),
+                    ch_context.OSConfigFlagContext(
+                        charm_flag='nova-alchemy-flags',
+                        template_flag='nova_alchemy_flags'),
+                    ch_context.ImageServiceContext(),
+                    ch_context.OSConfigFlagContext(),
+                    ch_context.SubordinateConfigContext(
+                        interface='nova-vmware',
+                        service='nova',
+                        config_file=NOVA_CONF),
+                    ch_context.SyslogContext(),
+                    ch_context.LogLevelContext(),
+                    nova_cc_context.HAProxyContext(),
+                    nova_cc_context.IdentityServiceContext(
+                        service='nova',
+                        service_user='nova'),
+                    nova_cc_context.VolumeServiceContext(),
+                    ch_context.ZeroMQContext(),
+                    ch_context.NotificationDriverContext(),
+                    nova_cc_context.NovaIPv6Context(),
+                    nova_cc_context.NeutronCCContext(),
+                    nova_cc_context.NovaConfigContext(),
+                    nova_cc_context.InstanceConsoleContext(),
+                    nova_cc_context.ConsoleSSLContext(),
+                    nova_cc_context.CloudComputeContext(),
+                    ch_context.InternalEndpointContext(),
+                    ch_context.VolumeAPIContext('nova-common'),
+                    nova_cc_context.NeutronAPIContext(),
+                    nova_cc_context.SerialConsoleContext(),
+                    ch_context.MemcacheContext(),
+                    nova_cc_context.NovaMetadataContext()],
+            }),
+            (NOVA_API_PASTE, {
+                'services': [s for s in resolve_services() if 'api' in s],
+                'contexts': [nova_cc_context.IdentityServiceContext(),
+                             nova_cc_context.APIRateLimitingContext()],
+            }),
+            (HAPROXY_CONF, {
+                'contexts': [
+                    ch_context.HAProxyContext(singlenode_mode=True),
+                    nova_cc_context.HAProxyContext()],
+                'services': ['haproxy'],
+            }),
+            (APACHE_CONF, {
+                'contexts': [nova_cc_context.ApacheSSLContext(
+                    determine_ports)],
+                'services': ['apache2'],
+            }),
+            (APACHE_24_CONF, {
+                'contexts': [nova_cc_context.ApacheSSLContext(
+                    determine_ports)],
+                'services': ['apache2'],
+            }),
+        ])
+    return _BASE_RESOURCE_MAP
+
 
 CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
 
 NOVA_SSH_DIR = '/etc/nova/compute_ssh/'
 
-CONSOLE_CONFIG = {
-    'spice': {
-        'packages': ['nova-spiceproxy', 'nova-consoleauth'],
-        'services': ['nova-spiceproxy', 'nova-consoleauth'],
-        'proxy-page': '/spice_auto.html',
-        'proxy-port': 6082,
-    },
-    'novnc': {
-        'packages': ['nova-novncproxy', 'nova-consoleauth'],
-        'services': ['nova-novncproxy', 'nova-consoleauth'],
-        'proxy-page': '/vnc_auto.html',
-        'proxy-port': 6080,
-    },
-    'xvpvnc': {
-        'packages': ['nova-xvpvncproxy', 'nova-consoleauth'],
-        'services': ['nova-xvpvncproxy', 'nova-consoleauth'],
-        'proxy-page': '/console',
-        'proxy-port': 6081,
-    },
-}
-
 SERIAL_CONSOLE = {
     'packages': ['nova-serialproxy', 'nova-consoleauth',
                  'websockify'],
@@ -303,70 +224,72 @@ def resource_map(actual_services=True):
         unit (ie. apache2) or the services defined in BASE_SERVICES
         (ie.nova-placement-api).
     '''
-    resource_map = deepcopy(BASE_RESOURCE_MAP)
+    _resource_map = copy.deepcopy(get_base_resource_map())
 
     if os.path.exists('/etc/apache2/conf-available'):
-        resource_map.pop(APACHE_CONF)
+        _resource_map.pop(APACHE_CONF)
     else:
-        resource_map.pop(APACHE_24_CONF)
+        _resource_map.pop(APACHE_24_CONF)
 
-    resource_map[NOVA_CONF]['contexts'].append(
+    _resource_map[NOVA_CONF]['contexts'].append(
         nova_cc_context.NeutronCCContext())
 
-    release = os_release('nova-common')
-    cmp_os_release = CompareOpenStackReleases(release)
+    release = ch_utils.os_release('nova-common')
+    cmp_os_release = ch_utils.CompareOpenStackReleases(release)
     if cmp_os_release >= 'mitaka':
-        resource_map[NOVA_CONF]['contexts'].append(
+        _resource_map[NOVA_CONF]['contexts'].append(
             nova_cc_context.NovaAPISharedDBContext(relation_prefix='novaapi',
                                                    database='nova_api',
                                                    ssl_dir=NOVA_CONF_DIR)
         )
 
-    if console_attributes('services'):
-        resource_map[NOVA_CONF]['services'] += console_attributes('services')
+    if common.console_attributes('services'):
+        _resource_map[NOVA_CONF]['services'] += (
+            common.console_attributes('services'))
         # nova-consoleauth will be managed by pacemaker, if
         # single-nova-consoleauth is used, then don't monitor for the
         # nova-consoleauth service to be started (LP: #1660244).
-        if config('single-nova-consoleauth') and relation_ids('ha'):
-            services = resource_map[NOVA_CONF]['services']
+        if (hookenv.config('single-nova-consoleauth') and
+                hookenv.relation_ids('ha')):
+            services = _resource_map[NOVA_CONF]['services']
             if 'nova-consoleauth' in services:
                 services.remove('nova-consoleauth')
 
-    if (config('enable-serial-console') and cmp_os_release >= 'juno'):
-        resource_map[NOVA_CONF]['services'] += SERIAL_CONSOLE['services']
+    if (hookenv.config('enable-serial-console') and cmp_os_release >= 'juno'):
+        _resource_map[NOVA_CONF]['services'] += SERIAL_CONSOLE['services']
 
     # also manage any configs that are being updated by subordinates.
-    vmware_ctxt = context.SubordinateConfigContext(interface='nova-vmware',
-                                                   service='nova',
-                                                   config_file=NOVA_CONF)
+    vmware_ctxt = ch_context.SubordinateConfigContext(
+        interface='nova-vmware', service='nova', config_file=NOVA_CONF)
     vmware_ctxt = vmware_ctxt()
     if vmware_ctxt and 'services' in vmware_ctxt:
         for s in vmware_ctxt['services']:
-            if s not in resource_map[NOVA_CONF]['services']:
-                resource_map[NOVA_CONF]['services'].append(s)
+            if s not in _resource_map[NOVA_CONF]['services']:
+                _resource_map[NOVA_CONF]['services'].append(s)
 
-    if enable_memcache(release=release):
-        resource_map[MEMCACHED_CONF] = {
-            'contexts': [context.MemcacheContext()],
+    if ch_utils.enable_memcache(release=release):
+        _resource_map[MEMCACHED_CONF] = {
+            'contexts': [ch_context.MemcacheContext()],
             'services': ['memcached']}
 
     if actual_services and placement_api_enabled():
-        for cfile in resource_map:
-            svcs = resource_map[cfile]['services']
+        for cfile in _resource_map:
+            svcs = _resource_map[cfile]['services']
             if 'nova-placement-api' in svcs:
                 svcs.remove('nova-placement-api')
                 if 'apache2' not in svcs:
                     svcs.append('apache2')
         wsgi_script = "/usr/bin/nova-placement-api"
-        resource_map[WSGI_NOVA_PLACEMENT_API_CONF] = {
-            'contexts': [context.WSGIWorkerConfigContext(name="nova",
-                                                         script=wsgi_script),
-                         nova_cc_context.HAProxyContext()],
+        _resource_map[WSGI_NOVA_PLACEMENT_API_CONF] = {
+            'contexts': [
+                ch_context.WSGIWorkerConfigContext(
+                    name="nova", script=wsgi_script),
+                nova_cc_context.HAProxyContext()],
             'services': ['apache2']
         }
     elif not placement_api_enabled():
-        for cfile in resource_map:
-            svcs = resource_map[cfile]['services']
+        for cfile in _resource_map:
+            svcs = _resource_map[cfile]['services']
             if 'nova-placement-api' in svcs:
                 svcs.remove('nova-placement-api')
     if enable_metadata_api():
@@ -374,23 +297,23 @@ def resource_map(actual_services=True):
             svcs = ['apache2']
         else:
             svcs = ['nova-api-metadata']
-        resource_map[WSGI_NOVA_METADATA_API_CONF] = {
+        _resource_map[WSGI_NOVA_METADATA_API_CONF] = {
             'contexts': [
-                context.WSGIWorkerConfigContext(
+                ch_context.WSGIWorkerConfigContext(
                     name="nova_meta",
                     user='nova',
                     group='nova',
                     script='/usr/bin/nova-metadata-wsgi'),
                 nova_cc_context.MetaDataHAProxyContext()],
             'services': svcs}
-    return resource_map
+    return _resource_map
 
 
 def register_configs(release=None):
-    release = release or os_release('nova-common')
-    configs = templating.OSConfigRenderer(templates_dir=TEMPLATES,
-                                          openstack_release=release)
-    for cfg, rscs in resource_map().iteritems():
+    release = release or ch_utils.os_release('nova-common')
+    configs = ch_templating.OSConfigRenderer(
+        templates_dir=TEMPLATES, openstack_release=release)
+    for cfg, rscs in resource_map().items():
         configs.register(cfg, rscs['contexts'])
     return configs
 
@@ -403,9 +326,9 @@ def restart_map(actual_services=True):
         unit (ie. apache2) or the services defined in BASE_SERVICES
         (ie.nova-placement-api).
     '''
-    return OrderedDict(
+    return collections.OrderedDict(
         [(cfg, v['services'])
-         for cfg, v in resource_map(actual_services).iteritems()
+         for cfg, v in resource_map(actual_services).items()
          if v['services']])
 
 
@@ -423,43 +346,17 @@ def determine_ports():
     for services in restart_map(actual_services=False).values():
         for svc in services:
             try:
-                ports.append(API_PORTS[svc])
+                ports.append(common.api_port(svc))
             except KeyError:
                 pass
     return list(set(ports))
 
 
-def api_port(service):
-    return API_PORTS[service]
-
-
-def console_attributes(attr, proto=None):
-    '''Leave proto unset to query attributes of the protocal specified at
-    runtime'''
-    if proto:
-        console_proto = proto
-    else:
-        console_proto = config('console-access-protocol')
-        if console_proto is not None and console_proto.lower() in ('none', ''):
-            console_proto = None
-    if attr == 'protocol':
-        return console_proto
-    # 'vnc' is a virtual type made up of novnc and xvpvnc
-    if console_proto == 'vnc':
-        if attr in ['packages', 'services']:
-            return list(set(CONSOLE_CONFIG['novnc'][attr] +
-                        CONSOLE_CONFIG['xvpvnc'][attr]))
-        else:
-            return None
-    if console_proto in CONSOLE_CONFIG:
-        return CONSOLE_CONFIG[console_proto][attr]
-    return None
-
-
 def determine_packages():
     # currently all packages match service names
-    release = CompareOpenStackReleases(os_release('nova-common'))
-    packages = deepcopy(BASE_PACKAGES)
+    release = ch_utils.CompareOpenStackReleases(
+        ch_utils.os_release('nova-common'))
+    packages = copy.deepcopy(BASE_PACKAGES)
     for v in resource_map(actual_services=False).values():
         packages.extend(v['services'])
     # The nova-api-metadata service is served via wsgi and the package is
@@ -469,11 +366,12 @@ def determine_packages():
         packages.remove("nova-api-metadata")
     except ValueError:
         pass
-    if console_attributes('packages'):
-        packages.extend(console_attributes('packages'))
-    if (config('enable-serial-console') and release >= 'juno'):
+    if common.console_attributes('packages'):
+        packages.extend(common.console_attributes('packages'))
+    if (hookenv.config('enable-serial-console') and release >= 'juno'):
         packages.extend(SERIAL_CONSOLE['packages'])
-    packages.extend(token_cache_pkgs(source=config('openstack-origin')))
+    packages.extend(
+        ch_utils.token_cache_pkgs(source=hookenv.config('openstack-origin')))
     if release >= 'rocky':
         packages = [p for p in packages if not p.startswith('python-')]
         packages.extend(PY3_PACKAGES)
@@ -489,7 +387,8 @@ def determine_purge_packages():
 
     :returns: list of package names
     '''
-    release = CompareOpenStackReleases(os_release('keystone'))
+    release = ch_utils.CompareOpenStackReleases(
+        ch_utils.os_release('keystone'))
     if release >= 'rocky':
         pkgs = [p for p in BASE_PACKAGES if p.startswith('python-')]
         pkgs.extend(['python-nova', 'python-memcache', 'libapache2-mod-wsgi'])
@@ -499,7 +398,7 @@ def determine_purge_packages():
 
 def save_script_rc():
     env_vars = {
-        'OPENSTACK_PORT_MCASTPORT': config('ha-mcastport'),
+        'OPENSTACK_PORT_MCASTPORT': hookenv.config('ha-mcastport'),
         'OPENSTACK_SERVICE_API_EC2': 'nova-api-ec2',
         'OPENSTACK_SERVICE_API_OS_COMPUTE': 'nova-api-os-compute',
         'OPENSTACK_SERVICE_CERT': 'nova-cert',
@@ -507,9 +406,9 @@ def save_script_rc():
         'OPENSTACK_SERVICE_OBJECTSTORE': 'nova-objectstore',
         'OPENSTACK_SERVICE_SCHEDULER': 'nova-scheduler',
     }
-    if relation_ids('nova-volume-service'):
+    if hookenv.relation_ids('nova-volume-service'):
         env_vars['OPENSTACK_SERVICE_API_OS_VOL'] = 'nova-api-os-volume'
-    _save_script_rc(**env_vars)
+    ch_utils.save_script_rc(**env_vars)
 
 
 def get_step_upgrade_source(new_src):
@@ -530,17 +429,17 @@ def get_step_upgrade_source(new_src):
         'xenial-ocata': ('*', 'cloud:xenial-newton'),  # LP: #1711209
     }
     try:
-        os_codename = get_os_codename_install_source(new_src)
-        ubuntu_series = lsb_release()['DISTRIB_CODENAME'].lower()
+        os_codename = ch_utils.get_os_codename_install_source(new_src)
+        ubuntu_series = ch_host.lsb_release()['DISTRIB_CODENAME'].lower()
         cur_pocket, step_src = sources['%s-%s' % (ubuntu_series, os_codename)]
-        current_src = os_release('nova-common')
-        step_src_codename = get_os_codename_install_source(step_src)
+        current_src = ch_utils.os_release('nova-common')
+        step_src_codename = ch_utils.get_os_codename_install_source(step_src)
         if cur_pocket == '*' and step_src_codename > current_src:
             return step_src
     except KeyError:
         pass
 
-    configure_installation_source(new_src)
+    ch_utils.configure_installation_source(new_src)
 
     # charmhelpers.contrib.openstack.utils.configure_installation_source()
     # configures the repository in juju_deb.list, while
@@ -549,10 +448,10 @@ def get_step_upgrade_source(new_src):
     for fname in ['cloud-archive.list', 'juju_deb.list']:
         fpath = os.path.join('/etc/apt/sources.list.d/', fname)
         if not os.path.isfile(fpath):
-            log('Missing %s skipping it' % fpath, level=DEBUG)
+            hookenv.log('Missing %s skipping it' % fpath, level=hookenv.DEBUG)
             continue
 
-        with open(fpath, 'r') as f:
+        with open(fpath, 'rt') as f:
             for line in f.readlines():
                 for target_src, (cur_pocket, step_src) in sources.items():
                     if target_src != new_src:
@@ -590,13 +489,13 @@ def disable_policy_rcd():
 
 
 def is_db_initialised():
-    if relation_ids('cluster'):
-        dbsync_state = peer_retrieve('dbsync_state')
+    if hookenv.relation_ids('cluster'):
+        dbsync_state = ch_peerstorage.peer_retrieve('dbsync_state')
         if dbsync_state == 'complete':
-            log("Database is initialised", level=DEBUG)
+            hookenv.log("Database is initialised", level=hookenv.DEBUG)
             return True
 
-    log("Database is NOT initialised", level=DEBUG)
+    hookenv.log("Database is NOT initialised", level=hookenv.DEBUG)
     return False
 
 
@@ -606,14 +505,16 @@ def is_cellv2_init_ready():
     Cells v2 init requires transport_url and database connections to be set
     in nova.conf.
     """
-    amqp = context.AMQPContext()
+    amqp = ch_context.AMQPContext()
     shared_db = nova_cc_context.NovaCellV2SharedDBContext()
-    if (CompareOpenStackReleases(os_release('nova-common')) >= 'ocata' and
+    if (ch_utils.CompareOpenStackReleases(
+            ch_utils.os_release('nova-common')) >= 'ocata' and
             amqp() and shared_db()):
         return True
 
-    log("OpenStack release, database, or rabbitmq not ready for Cells V2",
-        level=DEBUG)
+    hookenv.log(
+        "OpenStack release, database, or rabbitmq not ready for Cells V2",
+        level=hookenv.DEBUG)
     return False
 
 
@@ -622,8 +523,9 @@ def _do_openstack_upgrade(new_src):
     # All upgrades to Liberty are forced to step through Kilo. Liberty does
     # not have the migrate_flavor_data option (Bug #1511466) available so it
     # must be done pre-upgrade
-    if (CompareOpenStackReleases(os_release('nova-common')) == 'kilo' and
-            is_leader()):
+    if (ch_utils.CompareOpenStackReleases(
+            ch_utils.os_release('nova-common')) == 'kilo' and
+            hookenv.is_leader()):
         migrate_nova_flavors()
 
     # 'nova-manage db online_data_migrations' needs to be run before moving to
@@ -631,25 +533,26 @@ def _do_openstack_upgrade(new_src):
     # step was not being executed (LP: #1711209).
     online_data_migrations_if_needed()
 
-    new_os_rel = get_os_codename_install_source(new_src)
-    cmp_new_os_rel = CompareOpenStackReleases(new_os_rel)
-    log('Performing OpenStack upgrade to %s.' % (new_os_rel))
+    new_os_rel = ch_utils.get_os_codename_install_source(new_src)
+    cmp_new_os_rel = ch_utils.CompareOpenStackReleases(new_os_rel)
+    hookenv.log('Performing OpenStack upgrade to %s.' % (new_os_rel))
 
-    configure_installation_source(new_src)
+    ch_utils.configure_installation_source(new_src)
     dpkg_opts = [
         '--option', 'Dpkg::Options::=--force-confnew',
         '--option', 'Dpkg::Options::=--force-confdef',
     ]
 
-    apt_update(fatal=True)
-    apt_upgrade(options=dpkg_opts, fatal=True, dist=True)
-    reset_os_release()
-    apt_install(determine_packages(), fatal=True)
+    ch_fetch.apt_update(fatal=True)
+    ch_fetch.apt_upgrade(options=dpkg_opts, fatal=True, dist=True)
+    ch_utils.reset_os_release()
+    ch_fetch.apt_install(determine_packages(), fatal=True)
 
-    installed_pkgs = filter_missing_packages(determine_purge_packages())
+    installed_pkgs = ch_fetch.filter_missing_packages(
+        determine_purge_packages())
     if installed_pkgs:
-        apt_purge(installed_pkgs, fatal=True)
-        apt_autoremove(purge=True, fatal=True)
+        ch_fetch.apt_purge(installed_pkgs, fatal=True)
+        ch_fetch.apt_autoremove(purge=True, fatal=True)
 
     disable_policy_rcd()
 
@@ -662,27 +565,27 @@ def _do_openstack_upgrade(new_src):
     if cmp_new_os_rel >= 'mitaka' and not database_setup(prefix='novaapi'):
         # NOTE: Defer service restarts and database migrations for now
         #       as nova_api database is not yet created
-        if (relation_ids('cluster') and is_leader()):
+        if (hookenv.relation_ids('cluster') and hookenv.is_leader()):
             # NOTE: reset dbsync state so that migration will complete
             #       when the nova_api database is setup.
-            peer_store('dbsync_state', None)
+            ch_peerstorage.peer_store('dbsync_state', None)
         return configs
 
     if cmp_new_os_rel >= 'ocata' and not database_setup(prefix='novacell0'):
         # NOTE: Defer service restarts and database migrations for now
         #       as nova_cell0 database is not yet created
-        if (relation_ids('cluster') and is_leader()):
+        if (hookenv.relation_ids('cluster') and hookenv.is_leader()):
             # NOTE: reset dbsync state so that migration will complete
             #       when the novacell0 database is setup.
-            peer_store('dbsync_state', None)
+            ch_peerstorage.peer_store('dbsync_state', None)
         return configs
 
-    if is_leader():
-        status_set('maintenance', 'Running nova db migration')
+    if hookenv.is_leader():
+        hookenv.status_set('maintenance', 'Running nova db migration')
         migrate_nova_databases()
 
-    if not is_unit_paused_set():
-        [service_start(s) for s in services()]
+    if not ch_utils.is_unit_paused_set():
+        [ch_host.service_start(s) for s in services()]
 
     return configs
 
@@ -696,16 +599,16 @@ def database_setup(prefix):
     relation name using the provided prefix.
     '''
     key = '{}_allowed_units'.format(prefix)
-    for db_rid in relation_ids('shared-db'):
-        for unit in related_units(db_rid):
-            allowed_units = relation_get(key, rid=db_rid, unit=unit)
-            if allowed_units and local_unit() in allowed_units.split():
+    for db_rid in hookenv.relation_ids('shared-db'):
+        for unit in hookenv.related_units(db_rid):
+            allowed_units = hookenv.relation_get(key, rid=db_rid, unit=unit)
+            if allowed_units and hookenv.local_unit() in allowed_units.split():
                 return True
     return False
 
 
 def do_openstack_upgrade(configs):
-    new_src = config('openstack-origin')
+    new_src = hookenv.config('openstack-origin')
 
     step_src = get_step_upgrade_source(new_src)
     if step_src is not None:
@@ -713,55 +616,61 @@ def do_openstack_upgrade(configs):
     return _do_openstack_upgrade(new_src)
 
 
-@retry_on_exception(5, base_delay=3, exc_type=subprocess.CalledProcessError)
+@ch_decorators.retry_on_exception(
+    5, base_delay=3, exc_type=subprocess.CalledProcessError)
 def migrate_nova_flavors():
     '''Runs nova-manage to migrate flavor data if needed'''
-    log('Migrating nova flavour information in database.', level=INFO)
+    hookenv.log('Migrating nova flavour information in database.',
+                level=hookenv.INFO)
     cmd = ['nova-manage', 'db', 'migrate_flavor_data']
     try:
         subprocess.check_output(cmd)
     except subprocess.CalledProcessError as e:
-        log('migrate_flavor_data failed\n{}'.format(e.output), level=ERROR)
+        hookenv.log('migrate_flavor_data failed\n{}'.format(e.output),
+                    level=hookenv.ERROR)
         raise
 
 
-@retry_on_exception(5, base_delay=3, exc_type=subprocess.CalledProcessError)
+@ch_decorators.retry_on_exception(
+    5, base_delay=3, exc_type=subprocess.CalledProcessError)
 def online_data_migrations_if_needed():
     '''Runs nova-manage to run online data migrations available since Mitaka'''
-    if (is_leader() and
-            CompareOpenStackReleases(os_release('nova-common')) >= 'mitaka'):
-        log('Running online_data_migrations', level=INFO)
+    if (hookenv.is_leader() and
+            ch_utils.CompareOpenStackReleases(
+                ch_utils.os_release('nova-common')) >= 'mitaka'):
+        hookenv.log('Running online_data_migrations', level=hookenv.INFO)
         cmd = ['nova-manage', 'db', 'online_data_migrations']
         try:
             subprocess.check_output(cmd)
         except subprocess.CalledProcessError as e:
-            log('online_data_migrations failed\n{}'.format(e.output),
-                level=ERROR)
+            hookenv.log('online_data_migrations failed\n{}'.format(e.output),
+                        level=hookenv.ERROR)
             raise
 
 
 def migrate_nova_api_database():
     '''Initialize or migrate the nova_api database'''
-    if CompareOpenStackReleases(os_release('nova-common')) >= 'mitaka':
-        log('Migrating the nova-api database.', level=INFO)
+    if ch_utils.CompareOpenStackReleases(
+            ch_utils.os_release('nova-common')) >= 'mitaka':
+        hookenv.log('Migrating the nova-api database.', level=hookenv.INFO)
         cmd = ['nova-manage', 'api_db', 'sync']
         try:
             subprocess.check_output(cmd)
         except subprocess.CalledProcessError as e:
             # NOTE(coreycb): sync of api_db on upgrade from newton->ocata
             # fails but cell init is successful.
-            log('Ignoring CalledProcessError during nova-api database '
-                'migration\n{}'.format(e.output), level=INFO)
+            hookenv.log('Ignoring CalledProcessError during nova-api database '
+                        'migration\n{}'.format(e.output), level=hookenv.INFO)
 
 
 def migrate_nova_database():
     '''Initialize or migrate the nova database'''
-    log('Migrating the nova database.', level=INFO)
+    hookenv.log('Migrating the nova database.', level=hookenv.INFO)
     cmd = ['nova-manage', 'db', 'sync']
     try:
         subprocess.check_output(cmd)
     except subprocess.CalledProcessError as e:
-        log('db sync failed\n{}'.format(e.output), level=ERROR)
+        hookenv.log('db sync failed\n{}'.format(e.output), level=hookenv.ERROR)
         raise
 
 
@@ -771,27 +680,30 @@ def initialize_cell_databases():
     cell0 is stored in the database named 'nova_cell0'.
     cell1 is stored in the database named 'nova'.
     '''
-    log('Creating cell0 database records', level=INFO)
+    hookenv.log('Creating cell0 database records', level=hookenv.INFO)
     cmd = ['nova-manage', 'cell_v2', 'map_cell0']
     try:
         subprocess.check_output(cmd)
     except subprocess.CalledProcessError as e:
-        log('map_cell0 failed\n{}'.format(e.output), level=ERROR)
+        hookenv.log('map_cell0 failed\n{}'.format(e.output),
+                    level=hookenv.ERROR)
         raise
 
-    log('Creating cell1 database records', level=INFO)
+    hookenv.log('Creating cell1 database records', level=hookenv.INFO)
     cmd = ['nova-manage', 'cell_v2', 'create_cell', '--name', 'cell1',
            '--verbose']
     try:
         subprocess.check_output(cmd)
-        log('cell1 was successfully created', level=INFO)
+        hookenv.log('cell1 was successfully created', level=hookenv.INFO)
     except subprocess.CalledProcessError as e:
         if e.returncode == 1:
-            log('Cell1 create_cell failed\n{}'.format(e.output), level=ERROR)
+            hookenv.log('Cell1 create_cell failed\n{}'.format(e.output),
+                        level=hookenv.ERROR)
             raise
         elif e.returncode == 2:
-            log('Cell1 create_cell failure ignored - a cell is already using '
-                'the transport_url/database combination.', level=INFO)
+            hookenv.log(
+                'Cell1 create_cell failure ignored - a cell is already using '
+                'the transport_url/database combination.', level=hookenv.INFO)
 
 
 def get_cell_uuid(cell, fatal=True):
@@ -799,7 +711,7 @@ def get_cell_uuid(cell, fatal=True):
     :param cell: string cell name i.e. 'cell1'
     :returns: string cell uuid
     '''
-    log("Listing cell, '{}'".format(cell), level=INFO)
+    hookenv.log("Listing cell, '{}'".format(cell), level=hookenv.INFO)
     cells = get_cell_details()
     cell_info = cells.get(cell)
     if not cell_info:
@@ -814,13 +726,14 @@ def get_cell_details():
     '''Get cell details
     :returns: string cell uuid
     '''
-    log("Getting details of cells", level=INFO)
+    hookenv.log("Getting details of cells", level=hookenv.INFO)
     cells = {}
     cmd = ['sudo', 'nova-manage', 'cell_v2', 'list_cells', '--verbose']
     try:
-        out = subprocess.check_output(cmd)
+        out = subprocess.check_output(cmd).decode('utf-8')
     except subprocess.CalledProcessError as e:
-        log('list_cells failed\n{}'.format(e.output), level=ERROR)
+        hookenv.log('list_cells failed\n{}'.format(e.output),
+                    level=hookenv.ERROR)
         raise
     for line in out.split('\n'):
         columns = line.split('|')
@@ -844,20 +757,22 @@ def update_cell_database():
     This should be called whenever a database or rabbitmq-server relation is
     changed to update the transport_url in the nova_api cell_mappings table.
     '''
-    log('Updating cell1 properties', level=INFO)
+    hookenv.log('Updating cell1 properties', level=hookenv.INFO)
     cell1_uuid = get_cell_uuid('cell1')
     cmd = ['nova-manage', 'cell_v2', 'update_cell', '--cell_uuid', cell1_uuid]
     try:
         subprocess.check_output(cmd)
     except subprocess.CalledProcessError as e:
         if e.returncode == 1:
-            log('Cell1 update_cell failed\n{}'.format(e.output), level=ERROR)
+            hookenv.log('Cell1 update_cell failed\n{}'.format(e.output),
+                        level=hookenv.ERROR)
             raise
         elif e.returncode == 2:
-            log('Cell1 update_cell failure ignored - the properties cannot '
-                'be set.', level=INFO)
+            hookenv.log(
+                'Cell1 update_cell failure ignored - the properties cannot '
+                'be set.', level=hookenv.INFO)
     else:
-        log('cell1 was successfully updated', level=INFO)
+        hookenv.log('cell1 was successfully updated', level=hookenv.INFO)
 
 
 def map_instances():
@@ -878,8 +793,8 @@ def map_instances():
     # still need mapping.
     while exit_code == 1:
         msg = 'Mapping instances. Batch number: {}'.format(iteration)
-        status_set('maintenance', msg)
-        log(msg, level=INFO)
+        hookenv.status_set('maintenance', msg)
+        hookenv.log(msg, level=hookenv.INFO)
         process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
         stdout, stderr = process.communicate()
         exit_code = process.wait()
@@ -887,16 +802,16 @@ def map_instances():
             msg = 'Cell1 map_instances failed\nstdout: {}\nstderr: {}'.format(
                 stdout,
                 stderr)
-            log(msg, level=ERROR)
+            hookenv.log(msg, level=hookenv.ERROR)
             raise Exception(msg)
         iteration += 1
     msg = 'Mapping instances complete'
-    status_set('maintenance', msg)
-    log(msg, level=INFO)
+    hookenv.status_set('maintenance', msg)
+    hookenv.log(msg, level=hookenv.INFO)
 
 
 def archive_deleted_rows(max_rows=None):
-    log('Archiving deleted rows', level=INFO)
+    hookenv.log('Archiving deleted rows', level=hookenv.INFO)
     cmd = ['nova-manage', 'db', 'archive_deleted_rows', '--verbose']
     if max_rows:
         cmd.extend(['--max_rows', str(max_rows)])
@@ -907,7 +822,7 @@ def archive_deleted_rows(max_rows=None):
         msg = 'Archiving deleted rows failed\nstdout: {}\nstderr: {}'.format(
             stdout,
             stderr)
-        log(msg, level=ERROR)
+        hookenv.log(msg, level=hookenv.ERROR)
         raise Exception(msg)
     else:
         return stdout
@@ -915,36 +830,40 @@ def archive_deleted_rows(max_rows=None):
 
 def add_hosts_to_cell():
     '''Map compute hosts to cell'''
-    log('Cell1 discover_hosts', level=INFO)
+    hookenv.log('Cell1 discover_hosts', level=hookenv.INFO)
     cell1_uuid = get_cell_uuid('cell1')
     cmd = ['nova-manage', 'cell_v2', 'discover_hosts', '--cell_uuid',
            cell1_uuid, '--verbose']
     try:
         subprocess.check_output(cmd)
     except subprocess.CalledProcessError as e:
-        log('Cell1 discover_hosts failed\n{}'.format(e.output), level=ERROR)
+        hookenv.log('Cell1 discover_hosts failed\n{}'.format(e.output),
+                    level=hookenv.ERROR)
         raise
 
 
 def finalize_migrate_nova_databases():
-    if relation_ids('cluster'):
-        log('Informing peers that dbsync is complete', level=INFO)
-        peer_store('dbsync_state', 'complete')
-    log('Enabling services', level=INFO)
-    if not is_unit_paused_set():
+    if hookenv.relation_ids('cluster'):
+        hookenv.log('Informing peers that dbsync is complete',
+                    level=hookenv.INFO)
+        ch_peerstorage.peer_store('dbsync_state', 'complete')
+    hookenv.log('Enabling services', level=hookenv.INFO)
+    if not ch_utils.is_unit_paused_set():
         for svc in services():
-            service_resume(svc)
+            ch_host.service_resume(svc)
     else:
-        log('Unit is in paused state, not issuing start/resume to all '
-            'services')
+        hookenv.log('Unit is in paused state, not issuing start/resume to all '
+                    'services')
 
 
 # NOTE(jamespage): Retry deals with sync issues during one-shot HA deploys.
 #                  mysql might be restarting or suchlike.
-@retry_on_exception(5, base_delay=3, exc_type=subprocess.CalledProcessError)
+@ch_decorators.retry_on_exception(
+    5, base_delay=3, exc_type=subprocess.CalledProcessError)
 def migrate_nova_databases():
     '''Runs nova-manage to initialize new databases or migrate existing'''
-    release = CompareOpenStackReleases(os_release('nova-common'))
+    release = ch_utils.CompareOpenStackReleases(
+        ch_utils.os_release('nova-common'))
     if release < 'ocata':
         migrate_nova_api_database()
         migrate_nova_database()
@@ -970,10 +889,10 @@ def auth_token_config(setting):
     Returns currently configured value for setting in api-paste.ini's
     authtoken section, or None.
     """
-    config = ConfigParser.RawConfigParser()
-    config.read('/etc/nova/api-paste.ini')
+    _config = configparser.RawConfigParser()
+    _config.read('/etc/nova/api-paste.ini')
     try:
-        value = config.get('filter:authtoken', setting)
+        value = _config.get('filter:authtoken', setting)
     except:
         return None
     if value.startswith('%'):
@@ -986,14 +905,14 @@ def keystone_ca_cert_b64():
     if not os.path.isfile(CA_CERT_PATH):
         return None
     with open(CA_CERT_PATH) as _in:
-        return b64encode(_in.read())
+        return base64.b64encode(_in.read())
 
 
 def ssh_directory_for_unit(unit=None, user=None):
     if unit:
         remote_service = unit.split('/')[0]
     else:
-        remote_service = remote_unit().split('/')[0]
+        remote_service = hookenv.remote_unit().split('/')[0]
     if user:
         remote_service = "{}_{}".format(remote_service, user)
     _dir = os.path.join(NOVA_SSH_DIR, remote_service)
@@ -1020,7 +939,7 @@ def ssh_known_host_key(host, unit=None, user=None):
     try:
         # The first line of output is like '# Host xx found: line 1 type RSA',
         # which should be excluded.
-        output = subprocess.check_output(cmd).strip()
+        output = subprocess.check_output(cmd).decode('utf-8').strip()
     except subprocess.CalledProcessError:
         return None
 
@@ -1034,7 +953,7 @@ def ssh_known_host_key(host, unit=None, user=None):
 
 
 def remove_known_host(host, unit=None, user=None):
-    log('Removing SSH known host entry for compute host at %s' % host)
+    hookenv.log('Removing SSH known host entry for compute host at %s' % host)
     cmd = ['ssh-keygen', '-f', known_hosts(unit, user), '-R', host]
     subprocess.check_call(cmd)
 
@@ -1053,20 +972,23 @@ def add_known_host(host, unit=None, user=None):
     '''Add variations of host to a known hosts file.'''
     cmd = ['ssh-keyscan', '-H', '-t', 'rsa', host]
     try:
-        remote_key = subprocess.check_output(cmd).strip()
+        remote_key = subprocess.check_output(cmd).decode('utf-8').strip()
     except Exception as e:
-        log('Could not obtain SSH host key from %s' % host, level=ERROR)
+        hookenv.log('Could not obtain SSH host key from %s' % host,
+                    level=hookenv.ERROR)
         raise e
 
     current_key = ssh_known_host_key(host, unit, user)
     if current_key and remote_key:
         if is_same_key(remote_key, current_key):
-            log('Known host key for compute host %s up to date.' % host)
+            hookenv.log(
+                'Known host key for compute host %s up to date.' % host)
             return
         else:
             remove_known_host(host, unit, user)
 
-    log('Adding SSH host key to known hosts for compute node at %s.' % host)
+    hookenv.log('Adding SSH host key to known hosts for compute node at {}.'
+                .format(host))
     with open(known_hosts(unit, user), 'a') as out:
         out.write(remote_key + '\n')
 
@@ -1084,29 +1006,29 @@ def add_authorized_key(public_key, unit=None, user=None):
 def ssh_compute_add(public_key, rid=None, unit=None, user=None):
     # If remote compute node hands us a hostname, ensure we have a
     # known hosts entry for its IP, hostname and FQDN.
-    private_address = relation_get(rid=rid, unit=unit,
-                                   attribute='private-address')
+    private_address = hookenv.relation_get(rid=rid, unit=unit,
+                                           attribute='private-address')
     hosts = []
 
-    if not is_ipv6(private_address):
-        hostname = relation_get(rid=rid, unit=unit,
-                                attribute='hostname')
+    if not ch_ip.is_ipv6(private_address):
+        hostname = hookenv.relation_get(rid=rid, unit=unit,
+                                        attribute='hostname')
         if hostname:
             hosts.append(hostname.lower())
 
-        if not is_ip(private_address):
+        if not ch_utils.is_ip(private_address):
             hosts.append(private_address.lower())
-            hosts.append(get_host_ip(private_address))
+            hosts.append(ch_utils.get_host_ip(private_address))
             short = private_address.split('.')[0]
-            if ns_query(short):
+            if ch_ip.ns_query(short):
                 hosts.append(short.lower())
         else:
             hosts.append(private_address)
-            hn = get_hostname(private_address)
+            hn = ch_utils.get_hostname(private_address)
             if hn:
                 hosts.append(hn.lower())
                 short = hn.split('.')[0]
-                if ns_query(short):
+                if ch_ip.ns_query(short):
                     hosts.append(short.lower())
     else:
         hosts.append(private_address)
@@ -1115,8 +1037,8 @@ def ssh_compute_add(public_key, rid=None, unit=None, user=None):
         add_known_host(host, unit, user)
 
     if not ssh_authorized_key_exists(public_key, unit, user):
-        log('Saving SSH authorized key for compute host at %s.' %
-            private_address)
+        hookenv.log('Saving SSH authorized key for compute host at %s.' %
+                    private_address)
         add_authorized_key(public_key, unit, user)
 
 
@@ -1145,7 +1067,7 @@ def ssh_compute_remove(public_key, unit=None, user=None):
             os.path.isfile(known_hosts(unit, user))):
         return
 
-    with open(authorized_keys(unit, user)) as _keys:
+    with open(authorized_keys(unit, user), 'rt') as _keys:
         keys = [k.strip() for k in _keys.readlines()]
 
     if public_key not in keys:
@@ -1153,7 +1075,7 @@ def ssh_compute_remove(public_key, unit=None, user=None):
 
     [keys.remove(key) for key in keys if key == public_key]
 
-    with open(authorized_keys(unit, user), 'w') as _keys:
+    with open(authorized_keys(unit, user), 'wt') as _keys:
         keys = '\n'.join(keys)
         if not keys.endswith('\n'):
             keys += '\n'
@@ -1163,48 +1085,50 @@ def ssh_compute_remove(public_key, unit=None, user=None):
 def determine_endpoints(public_url, internal_url, admin_url):
     '''Generates a dictionary containing all relevant endpoints to be
     passed to keystone as relation settings.'''
-    region = config('region')
-    os_rel = os_release('nova-common')
-    cmp_os_rel = CompareOpenStackReleases(os_rel)
+    region = hookenv.config('region')
+    os_rel = ch_utils.os_release('nova-common')
+    cmp_os_rel = ch_utils.CompareOpenStackReleases(os_rel)
 
     nova_public_url = ('%s:%s/v2/$(tenant_id)s' %
-                       (public_url, api_port('nova-api-os-compute')))
+                       (public_url, common.api_port('nova-api-os-compute')))
     nova_internal_url = ('%s:%s/v2/$(tenant_id)s' %
-                         (internal_url, api_port('nova-api-os-compute')))
+                         (internal_url,
+                          common.api_port('nova-api-os-compute')))
     nova_admin_url = ('%s:%s/v2/$(tenant_id)s' %
-                      (admin_url, api_port('nova-api-os-compute')))
+                      (admin_url, common.api_port('nova-api-os-compute')))
     if cmp_os_rel >= 'queens':
         nova_public_url = (
             '%s:%s/v2.1' %
-            (public_url, api_port('nova-api-os-compute'))
+            (public_url, common.api_port('nova-api-os-compute'))
         )
         nova_internal_url = (
             '%s:%s/v2.1' %
-            (internal_url, api_port('nova-api-os-compute'))
+            (internal_url, common.api_port('nova-api-os-compute'))
         )
         nova_admin_url = (
             '%s:%s/v2.1' %
-            (admin_url, api_port('nova-api-os-compute'))
+            (admin_url, common.api_port('nova-api-os-compute'))
         )
 
     ec2_public_url = '%s:%s/services/Cloud' % (
-        public_url, api_port('nova-api-ec2'))
+        public_url, common.api_port('nova-api-ec2'))
     ec2_internal_url = '%s:%s/services/Cloud' % (
-        internal_url, api_port('nova-api-ec2'))
+        internal_url, common.api_port('nova-api-ec2'))
     ec2_admin_url = '%s:%s/services/Cloud' % (admin_url,
-                                              api_port('nova-api-ec2'))
+                                              common.api_port('nova-api-ec2'))
 
-    s3_public_url = '%s:%s' % (public_url, api_port('nova-objectstore'))
-    s3_internal_url = '%s:%s' % (internal_url, api_port('nova-objectstore'))
-    s3_admin_url = '%s:%s' % (admin_url, api_port('nova-objectstore'))
+    s3_public_url = '%s:%s' % (public_url, common.api_port('nova-objectstore'))
+    s3_internal_url = '%s:%s' % (internal_url,
+                                 common.api_port('nova-objectstore'))
+    s3_admin_url = '%s:%s' % (admin_url, common.api_port('nova-objectstore'))
 
     if cmp_os_rel >= 'ocata':
         placement_public_url = '%s:%s' % (
-            public_url, api_port('nova-placement-api'))
+            public_url, common.api_port('nova-placement-api'))
         placement_internal_url = '%s:%s' % (
-            internal_url, api_port('nova-placement-api'))
+            internal_url, common.api_port('nova-placement-api'))
         placement_admin_url = '%s:%s' % (
-            admin_url, api_port('nova-placement-api'))
+            admin_url, common.api_port('nova-placement-api'))
 
     # the base endpoints
     endpoints = {
@@ -1255,11 +1179,15 @@ def determine_endpoints(public_url, internal_url, admin_url):
 
 
 def guard_map():
-    '''Map of services and required interfaces that must be present before
-    the service should be allowed to start'''
+    """Map of services and required interfaces that must be present before
+    the service should be allowed to start
+
+    :returns: A map of service names to interface names
+    :rtype: Dict[String, String]
+    """
     gmap = {}
     nova_services = resolve_services()
-    if os_release('nova-common') not in ['essex', 'folsom']:
+    if ch_utils.os_release('nova-common') not in ['essex', 'folsom']:
         nova_services.append('nova-conductor')
 
     nova_interfaces = ['identity-service', 'amqp']
@@ -1272,42 +1200,86 @@ def guard_map():
 
 
 def service_guard(guard_map, contexts, active=False):
-    '''Inhibit services in guard_map from running unless
-    required interfaces are found complete in contexts.'''
+    """Inhibit services in guard_map from running unless required interfaces
+    are found complete in contexts.
+
+    `guard_map`, `contexts` and `active` are all optionally callable so that
+    they don't have to run when the module is loaded.  This allows them to be
+    lazy and ensure that they only need to be evaluated if the decorated
+    function is actually called.
+
+    If `active` is not "truthy" then this decorator just returns the decorated
+    function with no changes.
+
+    :param guard_map: a callable that returns a dict or a dictionary of nova
+        service names <-> interface names
+    :type guard_map: Option[Callable, Dict[String, String]]
+    :param contexts: the map of file name -> {'services' -> [names]},
+        {'contexts' -> context objects}
+    :type contexts: Option[Callable, `:class:templating.OSConfigRenderer`]
+    :param active: Whether this service guard is active or not, optionally
+        callable
+    :type active: Option[Callable, Boolean]
+    :returns: wrapped function
+    :rtype: Callable
+    """
     def wrap(f):
-        def wrapped_f(*args):
-            if active is True:
+        _guard_map = None
+        _contexts = None
+        _active = None
+
+        def wrapped_f(*args, **kwargs):
+            nonlocal _active, _contexts, _guard_map
+            if _active is None:
+                if callable(active):
+                    _active = True if active() else False
+                else:
+                    _active = True if active else False
+            if _active:
+                if _guard_map is None:
+                    if callable(guard_map):
+                        _guard_map = guard_map()
+                    else:
+                        _guard_map = guard_map
+                if _contexts is None:
+                    if callable(contexts):
+                        _contexts = contexts()
+                    else:
+                        _contexts = contexts
                 incomplete_services = []
-                for svc in guard_map:
+                for svc in _guard_map:
                     for interface in guard_map[svc]:
-                        if interface not in contexts.complete_contexts():
+                        if interface not in _contexts.complete_contexts():
                             incomplete_services.append(svc)
-                f(*args)
+                ret = f(*args, **kwargs)
                 for svc in incomplete_services:
-                    if service_running(svc):
-                        log('Service {} has unfulfilled '
+                    if ch_host.service_running(svc):
+                        hookenv.log(
+                            'Service {} has unfulfilled '
                             'interface requirements, stopping.'.format(svc))
-                        service_stop(svc)
+                        ch_host.service_stop(svc)
+                return ret
             else:
-                f(*args)
+                return f(*args, **kwargs)
         return wrapped_f
     return wrap
 
 
 def setup_ipv6():
-    ubuntu_rel = lsb_release()['DISTRIB_CODENAME'].lower()
-    if CompareHostReleases(ubuntu_rel) < "trusty":
+    ubuntu_rel = ch_host.lsb_release()['DISTRIB_CODENAME'].lower()
+    if ch_host.CompareHostReleases(ubuntu_rel) < "trusty":
         raise Exception("IPv6 is not supported in the charms for Ubuntu "
                         "versions less than Trusty 14.04")
 
     # Need haproxy >= 1.5.3 for ipv6 so for Trusty if we are <= Kilo we need to
     # use trusty-backports otherwise we can use the UCA.
     if (ubuntu_rel == 'trusty' and
-            CompareOpenStackReleases(os_release('nova-api')) < 'liberty'):
-        add_source('deb http://archive.ubuntu.com/ubuntu trusty-backports '
-                   'main')
-        apt_update()
-        apt_install('haproxy/trusty-backports', fatal=True)
+            ch_utils.CompareOpenStackReleases(
+                ch_utils.os_release('nova-api')) < 'liberty'):
+        ch_fetch.add_source(
+            'deb http://archive.ubuntu.com/ubuntu trusty-backports main')
+        ch_fetch.apt_update()
+        ch_fetch.apt_install('haproxy/trusty-backports', fatal=True)
 
 
 def get_optional_interfaces():
@@ -1317,11 +1289,11 @@ def get_optional_interfaces():
     :returns: {general_interface: [specific_int1, specific_int2, ...], ...}
     """
     optional_interfaces = {}
-    if relation_ids('quantum-network-service'):
+    if hookenv.relation_ids('quantum-network-service'):
         optional_interfaces['quantum'] = ['quantum-network-service']
-    if relation_ids('cinder-volume-service'):
+    if hookenv.relation_ids('cinder-volume-service'):
         optional_interfaces['cinder'] = ['cinder-volume-service']
-    if relation_ids('neutron-api'):
+    if hookenv.relation_ids('neutron-api'):
         optional_interfaces['neutron-api'] = ['neutron-api']
 
     return optional_interfaces
@@ -1338,9 +1310,9 @@ def check_optional_relations(configs):
     :param configs: an OSConfigRender() instance.
     :return 2-tuple: (string, string) = (status, message)
     """
-    if relation_ids('ha'):
+    if hookenv.relation_ids('ha'):
         try:
-            get_hacluster_config()
+            ch_cluster.get_hacluster_config()
         except:
             return ('blocked',
                     'hacluster missing configuration: '
@@ -1351,7 +1323,8 @@ def check_optional_relations(configs):
 
 
 def is_api_ready(configs):
-    return (not incomplete_relation_data(configs, REQUIRED_INTERFACES))
+    return (not ch_utils.incomplete_relation_data(configs,
+                                                  REQUIRED_INTERFACES))
 
 
 def assess_status(configs):
@@ -1368,7 +1341,7 @@ def assess_status(configs):
     # assessing status.
     configs.register('', [nova_cc_context.NovaCellV2Context()])
     assess_status_func(configs)()
-    os_application_version_set(VERSION_PACKAGE)
+    ch_utils.os_application_version_set(VERSION_PACKAGE)
 
 
 def assess_status_func(configs):
@@ -1391,7 +1364,7 @@ def assess_status_func(configs):
     """
     required_interfaces = REQUIRED_INTERFACES.copy()
     required_interfaces.update(get_optional_interfaces())
-    return make_assess_status_func(
+    return ch_utils.make_assess_status_func(
         configs, required_interfaces,
         charm_func=check_optional_relations,
         services=services(), ports=None)
@@ -1404,7 +1377,7 @@ def pause_unit_helper(configs):
     @param configs: a templating.OSConfigRenderer() object
     @returns None - this function is executed for its side-effect
     """
-    _pause_resume_helper(pause_unit, configs)
+    _pause_resume_helper(ch_utils.pause_unit, configs)
 
 
 def resume_unit_helper(configs):
@@ -1414,7 +1387,7 @@ def resume_unit_helper(configs):
     @param configs: a templating.OSConfigRenderer() object
     @returns None - this function is executed for its side-effect
     """
-    _pause_resume_helper(resume_unit, configs)
+    _pause_resume_helper(ch_utils.resume_unit, configs)
 
 
 def _pause_resume_helper(f, configs):
@@ -1439,16 +1412,16 @@ def update_aws_compat_services():
     `nova-objectstore` services.
     """
     # if packages aren't installed, then there is nothing to do
-    if filter_installed_packages(AWS_COMPAT_SERVICES) != []:
+    if ch_fetch.filter_installed_packages(AWS_COMPAT_SERVICES) != []:
         return
 
-    if config('disable-aws-compat'):
+    if hookenv.config('disable-aws-compat'):
         # TODO: the endpoints have to removed from keystone
         for service_ in AWS_COMPAT_SERVICES:
-            service_pause(service_)
+            ch_host.service_pause(service_)
     else:
         for service_ in AWS_COMPAT_SERVICES:
-            service_resume(service_)
+            ch_host.service_resume(service_)
 
 
 def serial_console_settings():
@@ -1460,14 +1433,15 @@ def serial_console_settings():
 
 def placement_api_enabled():
     """Return true if nova-placement-api is enabled in this release"""
-    return CompareOpenStackReleases(os_release('nova-common')) >= 'ocata'
+    return ch_utils.CompareOpenStackReleases(
+        ch_utils.os_release('nova-common')) >= 'ocata'
 
 
 def enable_metadata_api(release=None):
     """Should nova-metadata-api be running on this unit for this release."""
     if not release:
-        release = os_release('nova-common')
-    return CompareOpenStackReleases(os_release('nova-common')) >= 'rocky'
+        release = ch_utils.os_release('nova-common')
+    return ch_utils.CompareOpenStackReleases(release) >= 'rocky'
 
 
 def disable_package_apache_site():
@@ -1480,22 +1454,23 @@ def disable_package_apache_site():
 
 def get_shared_metadatasecret():
     """Return the shared metadata secret."""
-    return leader_get(SHARED_METADATA_SECRET_KEY)
+    return hookenv.leader_get(SHARED_METADATA_SECRET_KEY)
 
 
 def set_shared_metadatasecret():
     """Store the shared metadata secret."""
-    leader_set({SHARED_METADATA_SECRET_KEY: uuid1()})
+    hookenv.leader_set({SHARED_METADATA_SECRET_KEY: uuid.uuid1()})
 
 
 def get_metadata_settings(configs):
     """Return the settings for accessing the metadata service."""
     if enable_metadata_api():
-        url = urlparse(canonical_url(configs, INTERNAL))
+        url = urlparse(
+            ch_openstack_ip.canonical_url(configs, ch_openstack_ip.INTERNAL))
         settings = {
             'nova-metadata-host': url.netloc,
             'nova-metadata-protocol': url.scheme,
-            'nova-metadata-port': API_PORTS['nova-api-metadata'],
+            'nova-metadata-port': common.api_port('nova-api-metadata'),
             'shared-metadata-secret': get_shared_metadatasecret()}
     else:
         settings = {}
@@ -1507,7 +1482,8 @@ def write_vendordata(vdata):
     try:
         json_vdata = json.loads(vdata)
     except (TypeError, json.decoder.JSONDecodeError) as e:
-        log('Error decoding vendor-data. {}'.format(e), level=ERROR)
+        hookenv.log('Error decoding vendor-data. {}'.format(e),
+                    level=hookenv.ERROR)
         return False
     with open(VENDORDATA_FILE, 'w') as vdata_file:
         vdata_file.write(json.dumps(json_vdata, sort_keys=True, indent=2))
@@ -1515,12 +1491,12 @@ def write_vendordata(vdata):
 
 def get_cell_db_context(db_service):
     """Return the database context for the given service name"""
-    db_rid = relation_id(
+    db_rid = hookenv.relation_id(
         relation_name='shared-db-cell',
         service_or_unit=db_service)
     if not db_rid:
         return {}
-    return context.SharedDBContext(
+    return ch_context.SharedDBContext(
         relation_prefix='nova',
         ssl_dir=NOVA_CONF_DIR,
         relation_id=db_rid)()
@@ -1528,12 +1504,12 @@ def get_cell_db_context(db_service):
 
 def get_cell_amqp_context(amqp_service):
     """Return the amqp context for the given service name"""
-    amq_rid = relation_id(
+    amq_rid = hookenv.relation_id(
         relation_name='amqp-cell',
         service_or_unit=amqp_service)
     if not amq_rid:
         return {}
-    return context.AMQPContext(
+    return ch_context.AMQPContext(
         ssl_dir=NOVA_CONF_DIR,
         relation_id=amq_rid)()
 
@@ -1560,31 +1536,31 @@ def update_child_cell(name, db_service, amqp_service, skip_acl_check=True):
         3) Complete relation with cells amqp service.
     """
     if not is_db_initialised():
-        log(
+        hookenv.log(
             'Defering registering Cell {}, api db not ready.'.format(name),
-            level=DEBUG)
+            level=hookenv.DEBUG)
         return False
 
     existing_cells = get_cell_details()
     if not existing_cells.get('cell1'):
-        log('Defering registering cell {}, api cell setup is not complete.'
-            ''.format(name),
-            level=DEBUG)
+        hookenv.log(
+            'Defering registering cell {}, api cell setup is not complete.'
+            .format(name), level=hookenv.DEBUG)
         return False
 
     db_ctxt = get_cell_db_context(db_service)
     if not db_ctxt:
-        log('Defering registering cell {}, cell db relation not '
-            'ready.'.format(name),
-            level=DEBUG)
+        hookenv.log(
+            'Defering registering cell {}, cell db relation not ready.'
+            .format(name), level=hookenv.DEBUG)
         return False
     sql_connection = get_sql_uri(db_ctxt)
 
     amqp_ctxt = get_cell_amqp_context(amqp_service)
     if not amqp_ctxt:
-        log('Defering registering cell {}, cell amqp relation not '
-            'ready.'.format(name),
-            level=DEBUG)
+        hookenv.log(
+            'Defering registering cell {}, cell amqp relation not ready.'
+            .format(name), level=hookenv.DEBUG)
         return False
 
     cmd = [
@@ -1593,21 +1569,23 @@ def update_child_cell(name, db_service, amqp_service, skip_acl_check=True):
     ]
 
     if existing_cells.get(name):
-        log('Cell {} already registered, checking if details are correct.'
-            ''.format(name), level=DEBUG)
+        hookenv.log(
+            'Cell {} already registered, checking if details are correct.'
+            .format(name), level=hookenv.DEBUG)
         if (amqp_ctxt['transport_url'] == existing_cells[name]['amqp'] and
            sql_connection == existing_cells[name]['db']):
-            log('Cell details are correct no update needed', level=DEBUG)
+            hookenv.log('Cell details are correct no update needed',
+                        level=hookenv.DEBUG)
             return False
         else:
-            log('Cell details have changed', level=DEBUG)
+            hookenv.log('Cell details have changed', level=hookenv.DEBUG)
             cmd.extend([
                 'update_cell',
                 '--cell_uuid', existing_cells[name]['uuid']])
     else:
-        log(
+        hookenv.log(
             'Cell {} is new and needs to be created.'.format(name),
-            level=DEBUG)
+            level=hookenv.DEBUG)
         cmd.extend(['create_cell', '--verbose'])
 
     cmd.extend([
@@ -1615,10 +1593,11 @@ def update_child_cell(name, db_service, amqp_service, skip_acl_check=True):
         '--transport-url', amqp_ctxt['transport_url'],
         '--database_connection', sql_connection])
     try:
-        log('Updating cell {}'.format(name), level=DEBUG)
+        hookenv.log('Updating cell {}'.format(name), level=hookenv.DEBUG)
         subprocess.check_output(cmd)
     except subprocess.CalledProcessError as e:
-        log('Register cell failed\n{}'.format(e.output), level=ERROR)
+        hookenv.log('Register cell failed\n{}'.format(e.output),
+                    level=hookenv.ERROR)
         raise
-    service_restart('nova-scheduler')
+    ch_host.service_restart('nova-scheduler')
     return True
diff --git a/templates/icehouse/nova.conf b/templates/icehouse/nova.conf
index c6b41f3b..ef8ab9e1 100644
--- a/templates/icehouse/nova.conf
+++ b/templates/icehouse/nova.conf
@@ -96,7 +96,7 @@ firewall_driver = nova.virt.firewall.NoopFirewallDriver
 {% endif -%}
 
 {% if network_manager_config -%}
-{% for key, value in network_manager_config.iteritems() -%}
+{% for key, value in network_manager_config.items() -%}
 {{ key }} = {{ value }}
 {% endfor -%}
 {% endif -%}
@@ -140,13 +140,13 @@ os_region_name = {{ region }}
 {% endif -%}
 
 {% if user_config_flags -%}
-{% for key, value in user_config_flags.iteritems() -%}
+{% for key, value in user_config_flags.items() -%}
 {{ key }} = {{ value }}
 {% endfor -%}
 {% endif -%}
 
 {% if listen_ports -%}
-{% for key, value in listen_ports.iteritems() -%}
+{% for key, value in listen_ports.items() -%}
 {{ key }} = {{ value }}
 {% endfor -%}
 {% endif -%}
diff --git a/templates/juno/nova.conf b/templates/juno/nova.conf
index cae921e3..774181da 100644
--- a/templates/juno/nova.conf
+++ b/templates/juno/nova.conf
@@ -96,7 +96,7 @@ firewall_driver = nova.virt.firewall.NoopFirewallDriver
 {% endif -%}
 
 {% if network_manager_config -%}
-{% for key, value in network_manager_config.iteritems() -%}
+{% for key, value in network_manager_config.items() -%}
 {{ key }} = {{ value }}
 {% endfor -%}
 {% endif -%}
@@ -134,13 +134,13 @@ volume_api_class=nova.volume.cinder.API
 {% endif -%}
 
 {% if user_config_flags -%}
-{% for key, value in user_config_flags.iteritems() -%}
+{% for key, value in user_config_flags.items() -%}
 {{ key }} = {{ value }}
 {% endfor -%}
 {% endif -%}
 
 {% if listen_ports -%}
-{% for key, value in listen_ports.iteritems() -%}
+{% for key, value in listen_ports.items() -%}
 {{ key }} = {{ value }}
 {% endfor -%}
 {% endif -%}
diff --git a/templates/kilo/nova.conf b/templates/kilo/nova.conf
index 195f6d64..2c60096e 100644
--- a/templates/kilo/nova.conf
+++ b/templates/kilo/nova.conf
@@ -99,7 +99,7 @@ firewall_driver = nova.virt.firewall.NoopFirewallDriver
 {% endif -%}
 
 {% if network_manager_config -%}
-{% for key, value in network_manager_config.iteritems() -%}
+{% for key, value in network_manager_config.items() -%}
 {{ key }} = {{ value }}
 {% endfor -%}
 {% endif -%}
@@ -119,13 +119,13 @@ volume_api_class=nova.volume.cinder.API
 {% endif -%}
 
 {% if user_config_flags -%}
-{% for key, value in user_config_flags.iteritems() -%}
+{% for key, value in user_config_flags.items() -%}
 {{ key }} = {{ value }}
 {% endfor -%}
 {% endif -%}
 
 {% if listen_ports -%}
-{% for key, value in listen_ports.iteritems() -%}
+{% for key, value in listen_ports.items() -%}
 {{ key }} = {{ value }}
 {% endfor -%}
 {% endif -%}
diff --git a/templates/liberty/nova.conf b/templates/liberty/nova.conf
index 7c1efa87..d93bce35 100644
--- a/templates/liberty/nova.conf
+++ b/templates/liberty/nova.conf
@@ -99,7 +99,7 @@ firewall_driver = nova.virt.firewall.NoopFirewallDriver
 {% endif -%}
 
 {% if network_manager_config -%}
-{% for key, value in network_manager_config.iteritems() -%}
+{% for key, value in network_manager_config.items() -%}
 {{ key }} = {{ value }}
 {% endfor -%}
 {% endif -%}
@@ -119,13 +119,13 @@ volume_api_class=nova.volume.cinder.API
 {% endif -%}
 
 {% if user_config_flags -%}
-{% for key, value in user_config_flags.iteritems() -%}
+{% for key, value in user_config_flags.items() -%}
 {{ key }} = {{ value }}
 {% endfor -%}
 {% endif -%}
 
 {% if listen_ports -%}
-{% for key, value in listen_ports.iteritems() -%}
+{% for key, value in listen_ports.items() -%}
 {{ key }} = {{ value }}
 {% endfor -%}
 {% endif -%}
diff --git a/templates/mitaka/nova.conf b/templates/mitaka/nova.conf
index a03ca65a..1fd5f9ac 100644
--- a/templates/mitaka/nova.conf
+++ b/templates/mitaka/nova.conf
@@ -88,7 +88,7 @@ firewall_driver = nova.virt.firewall.NoopFirewallDriver
 {% endif -%}
 
 {% if network_manager_config -%}
-{% for key, value in network_manager_config.iteritems() -%}
+{% for key, value in network_manager_config.items() -%}
 {{ key }} = {{ value }}
 {% endfor -%}
 {% endif -%}
@@ -109,7 +109,7 @@ volume_api_class=nova.volume.cinder.API
 {% endif -%}
 
 {% if user_config_flags -%}
-{% for key, value in user_config_flags.iteritems() -%}
+{% for key, value in user_config_flags.items() -%}
 {{ key }} = {{ value }}
 {% endfor -%}
 {% endif -%}
@@ -120,7 +120,7 @@ vendordata_jsonfile_path = /etc/nova/vendor_data.json
 {% endif -%}
 
 {% if listen_ports -%}
-{% for key, value in listen_ports.iteritems() -%}
+{% for key, value in listen_ports.items() -%}
 {{ key }} = {{ value }}
 {% endfor -%}
 {% endif -%}
diff --git a/templates/ocata/nova.conf b/templates/ocata/nova.conf
index 394cf373..97c757b2 100644
--- a/templates/ocata/nova.conf
+++ b/templates/ocata/nova.conf
@@ -78,7 +78,7 @@ firewall_driver = nova.virt.firewall.NoopFirewallDriver
 {% endif -%}
 
 {% if network_manager_config -%}
-{% for key, value in network_manager_config.iteritems() -%}
+{% for key, value in network_manager_config.items() -%}
 {{ key }} = {{ value }}
 {% endfor -%}
 {% endif -%}
@@ -98,13 +98,13 @@ volume_api_class=nova.volume.cinder.API
 {% endif -%}
 
 {% if user_config_flags -%}
-{% for key, value in user_config_flags.iteritems() -%}
+{% for key, value in user_config_flags.items() -%}
 {{ key }} = {{ value }}
 {% endfor -%}
 {% endif -%}
 
 {% if listen_ports -%}
-{% for key, value in listen_ports.iteritems() -%}
+{% for key, value in listen_ports.items() -%}
 {{ key }} = {{ value }}
 {% endfor -%}
 {% endif -%}
diff --git a/templates/parts/database-api b/templates/parts/database-api
index 5cf6319c..bbd5cf57 100644
--- a/templates/parts/database-api
+++ b/templates/parts/database-api
@@ -6,7 +6,7 @@ connection = {{ nova_api_database_type }}://{{ nova_api_database_user }}:{{ nova
 max_pool_size = {{ workers }}
 {% endif -%}
 {% if nova_alchemy_flags -%}
-{% for key, value in nova_alchemy_flags.iteritems() -%}
+{% for key, value in nova_alchemy_flags.items() -%}
 {{ key }} = {{ value }}
 {% endfor -%}
 {% endif -%}
diff --git a/templates/parts/database-v2 b/templates/parts/database-v2
index 05e59368..87468670 100644
--- a/templates/parts/database-v2
+++ b/templates/parts/database-v2
@@ -6,7 +6,7 @@ connection = {{ database_type }}://{{ database_user }}:{{ database_password }}@{
 max_pool_size = {{ workers }}
 {% endif -%}
 {% if nova_alchemy_flags -%}
-{% for key, value in nova_alchemy_flags.iteritems() -%}
+{% for key, value in nova_alchemy_flags.items() -%}
 {{ key }} = {{ value }}
 {% endfor -%}
 {% endif -%}
diff --git a/templates/parts/section-database b/templates/parts/section-database
index a5f94f28..b06eb9f0 100644
--- a/templates/parts/section-database
+++ b/templates/parts/section-database
@@ -2,7 +2,7 @@
 [database]
 connection = {{ database_type }}://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ 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 %}
 {% if neutron_alchemy_flags -%}
-{% for key, value in neutron_alchemy_flags.iteritems() -%}
+{% for key, value in neutron_alchemy_flags.items() -%}
 {{ key }} = {{ value }}
 {% endfor -%}
 {% endif -%}
diff --git a/templates/pike/nova.conf b/templates/pike/nova.conf
index a1bd7026..99f527ab 100644
--- a/templates/pike/nova.conf
+++ b/templates/pike/nova.conf
@@ -78,7 +78,7 @@ firewall_driver = nova.virt.firewall.NoopFirewallDriver
 {% endif -%}
 
 {% if network_manager_config -%}
-{% for key, value in network_manager_config.iteritems() -%}
+{% for key, value in network_manager_config.items() -%}
 {{ key }} = {{ value }}
 {% endfor -%}
 {% endif -%}
@@ -98,13 +98,13 @@ volume_api_class=nova.volume.cinder.API
 {% endif -%}
 
 {% if user_config_flags -%}
-{% for key, value in user_config_flags.iteritems() -%}
+{% for key, value in user_config_flags.items() -%}
 {{ key }} = {{ value }}
 {% endfor -%}
 {% endif -%}
 
 {% if listen_ports -%}
-{% for key, value in listen_ports.iteritems() -%}
+{% for key, value in listen_ports.items() -%}
 {{ key }} = {{ value }}
 {% endfor -%}
 {% endif -%}
diff --git a/tox.ini b/tox.ini
index b6e41e7e..3fa366a8 100644
--- a/tox.ini
+++ b/tox.ini
@@ -17,9 +17,7 @@ whitelist_externals = juju
 passenv = HOME TERM AMULET_* CS_API_*
 
 [testenv:py27]
-basepython = python2.7
-deps = -r{toxinidir}/requirements.txt
-       -r{toxinidir}/test-requirements.txt
+commands = /bin/true
 
 [testenv:py35]
 basepython = python3.5
@@ -32,7 +30,7 @@ deps = -r{toxinidir}/requirements.txt
        -r{toxinidir}/test-requirements.txt
 
 [testenv:pep8]
-basepython = python2.7
+basepython = python3
 deps = -r{toxinidir}/requirements.txt
        -r{toxinidir}/test-requirements.txt
 commands = flake8 {posargs} hooks unit_tests tests actions lib
diff --git a/unit_tests/__init__.py b/unit_tests/__init__.py
index ad1102f1..0a8932d9 100644
--- a/unit_tests/__init__.py
+++ b/unit_tests/__init__.py
@@ -12,6 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import os
 import sys
 import mock
 
@@ -21,6 +22,12 @@ mock_apt = mock.MagicMock()
 sys.modules['apt'] = mock_apt
 mock_apt.apt_pkg = mock.MagicMock()
 
+_path = os.path.dirname(os.path.realpath(__file__))
+_root = os.path.abspath(os.path.join(_path, '..'))
 
-sys.path.append('actions/')
-sys.path.append('hooks/')
+
+def _add_path(path):
+    if path not in sys.path:
+        sys.path.insert(1, path)
+
+_add_path(_root)
diff --git a/unit_tests/test_actions.py b/unit_tests/test_actions.py
index fb857f11..d76b56f5 100644
--- a/unit_tests/test_actions.py
+++ b/unit_tests/test_actions.py
@@ -14,7 +14,7 @@
 
 import mock
 
-from test_utils import (
+from unit_tests.test_utils import (
     CharmTestCase,
     get_default_config,
 )
@@ -24,29 +24,9 @@ __default_config = get_default_config()
 # depending on where it's being executed
 __default_config['openstack-origin'] = ''
 
-with mock.patch('charmhelpers.core.hookenv.config') as config:
-    with mock.patch('charmhelpers.contrib.openstack.utils.get_os_codename_package'):  # noqa
-        # this makes the config behave more similar to the real config()
-        config.side_effect = lambda k: __default_config.get(k)
+import hooks.nova_cc_utils as utils  # noqa
+import actions.actions as actions
 
-        import nova_cc_utils as utils  # noqa
-
-# Need to do some early patching to get the module loaded.
-_reg = utils.register_configs
-_map = utils.restart_map
-
-utils.register_configs = mock.MagicMock()
-utils.restart_map = mock.MagicMock()
-
-with mock.patch('nova_cc_utils.guard_map') as gmap:
-    with mock.patch('charmhelpers.core.hookenv.config') as config:
-        config.return_value = False
-        gmap.return_value = {}
-        import actions
-
-# Unpatch it now that its loaded.
-utils.register_configs = _reg
-utils.restart_map = _map
 
 TO_PATCH = [
 ]
@@ -56,7 +36,11 @@ class PauseTestCase(CharmTestCase):
 
     def setUp(self):
         super(PauseTestCase, self).setUp(
-            actions, ["register_configs", "pause_unit_helper"])
+            actions,
+            [
+                "hooks.nova_cc_utils.register_configs",
+                "hooks.nova_cc_utils.pause_unit_helper"
+            ])
         self.register_configs.return_value = 'test-config'
 
     def test_pauses_services(self):
@@ -68,7 +52,10 @@ class ResumeTestCase(CharmTestCase):
 
     def setUp(self):
         super(ResumeTestCase, self).setUp(
-            actions, ["register_configs", "resume_unit_helper"])
+            actions, [
+                "hooks.nova_cc_utils.register_configs",
+                "hooks.nova_cc_utils.resume_unit_helper"
+            ])
         self.register_configs.return_value = 'test-config'
 
     def test_resumes_services(self):
@@ -79,8 +66,12 @@ class ResumeTestCase(CharmTestCase):
 class MainTestCase(CharmTestCase):
 
     def setUp(self):
-        super(MainTestCase, self).setUp(actions, ["register_configs",
-                                                  "action_fail"])
+        super(MainTestCase, self).setUp(
+            actions,
+            [
+                "charmhelpers.core.hookenv.action_fail",
+                "hooks.nova_cc_utils.register_configs",
+            ])
         self.register_configs.return_value = 'test-config'
 
     def test_invokes_action(self):
diff --git a/unit_tests/test_actions_openstack_upgrade.py b/unit_tests/test_actions_openstack_upgrade.py
index 0830f794..d2520322 100644
--- a/unit_tests/test_actions_openstack_upgrade.py
+++ b/unit_tests/test_actions_openstack_upgrade.py
@@ -12,30 +12,18 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import os
-
 from mock import patch
+from unit_tests.test_utils import CharmTestCase
 
-os.environ['JUJU_UNIT_NAME'] = 'nova-cloud-controller'
-with patch('charmhelpers.core.hookenv.config') as config:
-    with patch('charmhelpers.contrib.openstack.utils.get_os_codename_package'):
-        config.return_value = 'nova'
-        import nova_cc_utils as utils  # noqa
+import actions.openstack_upgrade as openstack_upgrade
 
-with patch('charmhelpers.core.hookenv.config') as config:
-    with patch('nova_cc_utils.restart_map'):
-        config.return_value = 'ovs'
-        with patch('nova_cc_utils.register_configs') as register_configs:
-            import openstack_upgrade
-
-from test_utils import CharmTestCase
 
 TO_PATCH = [
-    'do_openstack_upgrade',
-    'relation_ids',
-    'neutron_api_relation_joined',
-    'db_joined',
-    'config_changed',
+    'charmhelpers.core.hookenv.relation_ids',
+    'hooks.nova_cc_hooks.config_changed',
+    'hooks.nova_cc_hooks.db_joined',
+    'hooks.nova_cc_hooks.neutron_api_relation_joined',
+    'hooks.nova_cc_utils.do_openstack_upgrade',
 ]
 
 
diff --git a/unit_tests/test_nova_cc_contexts.py b/unit_tests/test_nova_cc_contexts.py
index 2f4965d6..b2be4c16 100644
--- a/unit_tests/test_nova_cc_contexts.py
+++ b/unit_tests/test_nova_cc_contexts.py
@@ -12,30 +12,25 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from __future__ import print_function
-
 import json
 import mock
 
-import nova_cc_context as context
-with mock.patch('charmhelpers.core.hookenv.config'):
-    with mock.patch('charmhelpers.contrib.openstack.utils.get_os_codename_package'):  # noqa
-        import nova_cc_utils as _utils  # noqa
+import hooks.nova_cc_context as context
 
 from charmhelpers.contrib.openstack import neutron
 from charmhelpers.contrib.openstack import utils
-from test_utils import CharmTestCase
+from unit_tests.test_utils import CharmTestCase
 
 TO_PATCH = [
-    'config',
-    'https',
-    'leader_get',
-    'log',
-    'os_release',
-    'related_units',
-    'relation_get',
-    'relation_ids',
-    'relations_for_id',
+    'charmhelpers.contrib.hahelpers.cluster.https',
+    'charmhelpers.contrib.openstack.utils.os_release',
+    'charmhelpers.core.hookenv.config',
+    'charmhelpers.core.hookenv.leader_get',
+    'charmhelpers.core.hookenv.log',
+    'charmhelpers.core.hookenv.related_units',
+    'charmhelpers.core.hookenv.relation_get',
+    'charmhelpers.core.hookenv.relation_ids',
+    'charmhelpers.core.hookenv.relations_for_id',
 ]
 
 
@@ -51,8 +46,8 @@ class NovaComputeContextTests(CharmTestCase):
         self.config.side_effect = self.test_config.get
         self.log.side_effect = fake_log
 
-    @mock.patch.object(context, 'resolve_address',
-                       lambda *args, **kwargs: None)
+    @mock.patch('charmhelpers.contrib.openstack.ip.resolve_address',
+                lambda *args, **kwargs: None)
     @mock.patch.object(utils, 'os_release')
     @mock.patch('charmhelpers.contrib.network.ip.log')
     def test_instance_console_context_without_memcache(self, os_release, log_):
@@ -63,8 +58,8 @@ class NovaComputeContextTests(CharmTestCase):
         self.assertEqual({'memcached_servers': ''},
                          instance_console())
 
-    @mock.patch.object(context, 'resolve_address',
-                       lambda *args, **kwargs: None)
+    @mock.patch('charmhelpers.contrib.openstack.ip.resolve_address',
+                lambda *args, **kwargs: None)
     @mock.patch.object(utils, 'os_release')
     @mock.patch('charmhelpers.contrib.network.ip.log')
     def test_instance_console_context_with_memcache(self, os_release, log_):
@@ -72,8 +67,8 @@ class NovaComputeContextTests(CharmTestCase):
                                                           '127.0.1.1',
                                                           '127.0.1.1')
 
-    @mock.patch.object(context, 'resolve_address',
-                       lambda *args, **kwargs: None)
+    @mock.patch('charmhelpers.contrib.openstack.ip.resolve_address',
+                lambda *args, **kwargs: None)
     @mock.patch.object(utils, 'os_release')
     @mock.patch('charmhelpers.contrib.network.ip.log')
     def test_instance_console_context_with_memcache_ipv6(self, os_release,
@@ -94,40 +89,49 @@ class NovaComputeContextTests(CharmTestCase):
         self.assertEqual({'memcached_servers': "%s:11211" % (formated_ip, )},
                          instance_console())
 
-    @mock.patch('charmhelpers.contrib.openstack.neutron.os_release')
     @mock.patch('charmhelpers.contrib.openstack.ip.config')
+    @mock.patch('charmhelpers.contrib.openstack.neutron.config')
+    @mock.patch('charmhelpers.contrib.openstack.context.config')
+    @mock.patch('charmhelpers.contrib.openstack.neutron.os_release')
     @mock.patch('charmhelpers.contrib.openstack.ip.is_clustered')
-    def test_neutron_context_single_vip(self, mock_is_clustered, mock_config,
-                                        _os_release):
+    def test_neutron_context_single_vip(
+            self, mock_is_clustered, _os_release, mock_config,
+            mock_config_neutron, mock_config_ip):
         self.https.return_value = False
         mock_is_clustered.return_value = True
-        config = {'vip': '10.0.0.1',
-                  'os-internal-network': '10.0.0.1/24',
-                  'os-admin-network': '10.0.1.0/24',
-                  'os-public-network': '10.0.2.0/24'}
-        mock_config.side_effect = lambda key: config.get(key)
+        _config = {'vip': '10.0.0.1',
+                   'os-internal-network': '10.0.0.1/24',
+                   'os-admin-network': '10.0.1.0/24',
+                   'os-public-network': '10.0.2.0/24',
+                   'network-manager': 'FlatDHCPManager'}
+        mock_config.side_effect = lambda key: _config.get(key)
+        mock_config_neutron.side_effect = lambda key: _config.get(key)
+        mock_config_ip.side_effect = lambda key: _config.get(key)
 
         ctxt = context.NeutronCCContext()()
         self.assertEqual(ctxt['nova_url'], 'http://10.0.0.1:8774/v2')
         self.assertFalse('neutron_url' in ctxt)
 
-    @mock.patch('charmhelpers.contrib.openstack.neutron.os_release')
     @mock.patch('charmhelpers.contrib.openstack.ip.config')
+    @mock.patch('charmhelpers.contrib.openstack.neutron.config')
+    @mock.patch('charmhelpers.contrib.openstack.neutron.os_release')
     @mock.patch('charmhelpers.contrib.openstack.ip.is_clustered')
-    def test_neutron_context_multi_vip(self, mock_is_clustered, mock_config,
-                                       _os_release):
+    def test_neutron_context_multi_vip(
+            self, mock_is_clustered, _os_release, mock_config, mock_config_ip):
         self.https.return_value = False
         mock_is_clustered.return_value = True
-        config = {'vip': '10.0.0.1 10.0.1.1 10.0.2.1',
-                  'os-internal-network': '10.0.1.0/24',
-                  'os-admin-network': '10.0.0.0/24',
-                  'os-public-network': '10.0.2.0/24'}
-        mock_config.side_effect = lambda key: config.get(key)
-
+        _config = {'vip': '10.0.0.1 10.0.1.1 10.0.2.1',
+                   'os-internal-network': '10.0.1.0/24',
+                   'os-admin-network': '10.0.0.0/24',
+                   'os-public-network': '10.0.2.0/24',
+                   'network-manager': 'FlatDHCPManager'}
+        mock_config.side_effect = lambda key: _config.get(key)
+        mock_config_ip.side_effect = lambda key: _config.get(key)
         ctxt = context.NeutronCCContext()()
         self.assertEqual(ctxt['nova_url'], 'http://10.0.1.1:8774/v2')
         self.assertFalse('neutron_url' in ctxt)
 
+    @mock.patch('charmhelpers.contrib.openstack.context.config')
     @mock.patch('charmhelpers.contrib.openstack.context.get_relation_ip')
     @mock.patch('charmhelpers.contrib.openstack.context.mkdir')
     @mock.patch.object(neutron, 'network_manager')
@@ -145,7 +149,8 @@ class NovaComputeContextTests(CharmTestCase):
                              mock_local_unit, mock_get_netmask_for_address,
                              mock_get_address_in_network, mock_kv, mock_https,
                              mock_unit_get, mock_network_manager, mock_mkdir,
-                             mock_get_relation_ip):
+                             mock_get_relation_ip, mock_config):
+        mock_config.side_effect = self.test_config.get
         mock_https.return_value = False
         mock_unit_get.return_value = '127.0.0.1'
         mock_network_manager.return_value = 'neutron'
@@ -153,7 +158,7 @@ class NovaComputeContextTests(CharmTestCase):
         self.assertEqual(ctxt['service_ports']['nova-api-os-compute'],
                          [8774, 8764])
 
-    @mock.patch.object(context, 'config')
+    @mock.patch('charmhelpers.contrib.openstack.context.config')
     def test_console_ssl_disabled(self, mock_config):
         config = {'console-ssl-cert': 'LS0tLS1CRUdJTiBDRV',
                   'console-ssl-key': 'LS0tLS1CRUdJTiBQUk'}
@@ -177,21 +182,20 @@ class NovaComputeContextTests(CharmTestCase):
         ctxt = context.ConsoleSSLContext()()
         self.assertEqual(ctxt, {})
 
-    @mock.patch('__builtin__.open')
+    @mock.patch('builtins.open')
     @mock.patch('os.path.exists')
-    @mock.patch.object(context, 'config')
-    @mock.patch.object(context, 'unit_get')
-    @mock.patch.object(context, 'is_clustered')
-    @mock.patch.object(context, 'resolve_address')
-    @mock.patch.object(context, 'b64decode')
+    @mock.patch('charmhelpers.core.hookenv.unit_get')
+    @mock.patch('charmhelpers.contrib.hahelpers.cluster.is_clustered')
+    @mock.patch('charmhelpers.contrib.openstack.ip.resolve_address')
+    @mock.patch('base64.b64decode')
     def test_noVNC_ssl_enabled(self, mock_b64decode,
                                mock_resolve_address,
                                mock_is_clustered, mock_unit_get,
-                               mock_config, mock_exists, mock_open):
+                               mock_exists, mock_open):
         config = {'console-ssl-cert': 'LS0tLS1CRUdJTiBDRV',
                   'console-ssl-key': 'LS0tLS1CRUdJTiBQUk',
                   'console-access-protocol': 'novnc'}
-        mock_config.side_effect = lambda key: config.get(key)
+        self.test_config.update(config)
         mock_exists.return_value = True
         mock_unit_get.return_value = '127.0.0.1'
         mock_is_clustered.return_value = True
@@ -208,21 +212,20 @@ class NovaComputeContextTests(CharmTestCase):
         self.assertEqual(ctxt['novncproxy_base_url'],
                          'https://10.5.100.1:6080/vnc_auto.html')
 
-    @mock.patch('__builtin__.open')
+    @mock.patch('builtins.open')
     @mock.patch('os.path.exists')
-    @mock.patch.object(context, 'config')
-    @mock.patch.object(context, 'unit_get')
-    @mock.patch.object(context, 'is_clustered')
-    @mock.patch.object(context, 'resolve_address')
-    @mock.patch.object(context, 'b64decode')
+    @mock.patch('charmhelpers.core.hookenv.unit_get')
+    @mock.patch('charmhelpers.contrib.hahelpers.cluster.is_clustered')
+    @mock.patch('charmhelpers.contrib.openstack.ip.resolve_address')
+    @mock.patch('base64.b64decode')
     def test_noVNC_ssl_enabled_no_cluster(self, mock_b64decode,
                                           mock_resolve_address,
                                           mock_is_clustered, mock_unit_get,
-                                          mock_config, mock_exists, mock_open):
+                                          mock_exists, mock_open):
         config = {'console-ssl-cert': 'LS0tLS1CRUdJTiBDRV',
                   'console-ssl-key': 'LS0tLS1CRUdJTiBQUk',
                   'console-access-protocol': 'novnc'}
-        mock_config.side_effect = lambda key: config.get(key)
+        self.test_config.update(config)
         mock_exists.return_value = True
         mock_unit_get.return_value = '10.5.0.1'
         mock_is_clustered.return_value = False
@@ -238,21 +241,20 @@ class NovaComputeContextTests(CharmTestCase):
         self.assertEqual(ctxt['novncproxy_base_url'],
                          'https://10.5.0.1:6080/vnc_auto.html')
 
-    @mock.patch('__builtin__.open')
+    @mock.patch('builtins.open')
     @mock.patch('os.path.exists')
-    @mock.patch.object(context, 'config')
-    @mock.patch.object(context, 'unit_get')
-    @mock.patch.object(context, 'is_clustered')
-    @mock.patch.object(context, 'resolve_address')
-    @mock.patch.object(context, 'b64decode')
+    @mock.patch('charmhelpers.core.hookenv.unit_get')
+    @mock.patch('charmhelpers.contrib.hahelpers.cluster.is_clustered')
+    @mock.patch('charmhelpers.contrib.openstack.ip.resolve_address')
+    @mock.patch('base64.b64decode')
     def test_spice_html5_ssl_enabled(self, mock_b64decode,
                                      mock_resolve_address,
                                      mock_is_clustered, mock_unit_get,
-                                     mock_config, mock_exists, mock_open):
+                                     mock_exists, mock_open):
         config = {'console-ssl-cert': 'LS0tLS1CRUdJTiBDRV',
                   'console-ssl-key': 'LS0tLS1CRUdJTiBQUk',
                   'console-access-protocol': 'spice'}
-        mock_config.side_effect = lambda key: config.get(key)
+        self.test_config.update(config)
         mock_exists.return_value = True
         mock_unit_get.return_value = '127.0.0.1'
         mock_is_clustered.return_value = True
@@ -269,23 +271,22 @@ class NovaComputeContextTests(CharmTestCase):
         self.assertEqual(ctxt['html5proxy_base_url'],
                          'https://10.5.100.1:6082/spice_auto.html')
 
-    @mock.patch('__builtin__.open')
+    @mock.patch('builtins.open')
     @mock.patch('os.path.exists')
-    @mock.patch.object(context, 'config')
-    @mock.patch.object(context, 'unit_get')
-    @mock.patch.object(context, 'is_clustered')
-    @mock.patch.object(context, 'resolve_address')
-    @mock.patch.object(context, 'b64decode')
+    @mock.patch('charmhelpers.core.hookenv.unit_get')
+    @mock.patch('charmhelpers.contrib.hahelpers.cluster.is_clustered')
+    @mock.patch('charmhelpers.contrib.openstack.ip.resolve_address')
+    @mock.patch('base64.b64decode')
     def test_spice_html5_ssl_enabled_no_cluster(self, mock_b64decode,
                                                 mock_resolve_address,
                                                 mock_is_clustered,
                                                 mock_unit_get,
-                                                mock_config, mock_exists,
+                                                mock_exists,
                                                 mock_open):
         config = {'console-ssl-cert': 'LS0tLS1CRUdJTiBDRV',
                   'console-ssl-key': 'LS0tLS1CRUdJTiBQUk',
                   'console-access-protocol': 'spice'}
-        mock_config.side_effect = lambda key: config.get(key)
+        self.test_config.update(config)
         mock_exists.return_value = True
         mock_unit_get.return_value = '10.5.0.1'
         mock_is_clustered.return_value = False
@@ -301,14 +302,17 @@ class NovaComputeContextTests(CharmTestCase):
         self.assertEqual(ctxt['html5proxy_base_url'],
                          'https://10.5.0.1:6082/spice_auto.html')
 
+    @mock.patch('charmhelpers.contrib.openstack.ip.config')
     @mock.patch('charmhelpers.contrib.openstack.ip.unit_get')
     @mock.patch('charmhelpers.contrib.hahelpers.cluster.relation_ids')
     @mock.patch('charmhelpers.core.hookenv.local_unit')
     @mock.patch('charmhelpers.contrib.openstack.context.config')
     def test_nova_config_context(self, mock_config, local_unit,
-                                 mock_relation_ids, mock_unit_get):
+                                 mock_relation_ids, mock_unit_get,
+                                 mock_config_ip):
         local_unit.return_value = 'nova-cloud-controller/0'
         mock_config.side_effect = self.test_config.get
+        mock_config_ip.side_effect = self.test_config.get
         mock_unit_get.return_value = '127.0.0.1'
         ctxt = context.NovaConfigContext()()
         self.assertEqual(ctxt['scheduler_default_filters'],
@@ -336,6 +340,7 @@ class NovaComputeContextTests(CharmTestCase):
 
     _pci_alias_list = [_pci_alias1, _pci_alias2]
 
+    @mock.patch('charmhelpers.contrib.openstack.ip.resolve_address')
     @mock.patch('charmhelpers.contrib.openstack.ip.unit_get')
     @mock.patch('charmhelpers.contrib.hahelpers.cluster.relation_ids')
     @mock.patch('charmhelpers.core.hookenv.local_unit')
@@ -343,7 +348,8 @@ class NovaComputeContextTests(CharmTestCase):
     def test_nova_config_context_multi_pci_alias(self, mock_config,
                                                  local_unit,
                                                  mock_relation_ids,
-                                                 mock_unit_get):
+                                                 mock_unit_get,
+                                                 mock_resolve_address):
         local_unit.return_value = 'nova-cloud-controller/0'
         mock_config.side_effect = self.test_config.get
         mock_unit_get.return_value = '127.0.0.1'
@@ -356,14 +362,17 @@ class NovaComputeContextTests(CharmTestCase):
              '"name": "IntelNIC", "product_id": "1111", '
              '"vendor_id": "8086"}'))
 
+    @mock.patch('charmhelpers.contrib.openstack.ip.resolve_address')
     @mock.patch('charmhelpers.contrib.openstack.ip.unit_get')
     @mock.patch('charmhelpers.contrib.hahelpers.cluster.relation_ids')
     @mock.patch('charmhelpers.core.hookenv.local_unit')
     @mock.patch('charmhelpers.contrib.openstack.context.config')
-    def test_nova_config_context_multi_pci_aliases(self, mock_config,
+    def test_nova_config_context_multi_pci_aliases(self,
+                                                   mock_config,
                                                    local_unit,
                                                    mock_relation_ids,
-                                                   mock_unit_get):
+                                                   mock_unit_get,
+                                                   mock_resolve_address):
         local_unit.return_value = 'nova-cloud-controller/0'
         mock_config.side_effect = self.test_config.get
         mock_unit_get.return_value = '127.0.0.1'
@@ -381,13 +390,11 @@ class NovaComputeContextTests(CharmTestCase):
              '"name": " Cirrus Logic ", "product_id": "0ff2", '
              '"vendor_id": "10de"}'))
 
-    @mock.patch.object(context, 'format_ipv6_addr')
-    @mock.patch.object(context, 'resolve_address')
-    @mock.patch.object(context, 'config')
-    def test_serial_console_context(self, mock_config,
+    @mock.patch('charmhelpers.contrib.network.ip.format_ipv6_addr')
+    @mock.patch('charmhelpers.contrib.openstack.ip.resolve_address')
+    def test_serial_console_context(self,
                                     mock_resolve_address,
                                     mock_format_ipv6_address):
-        mock_config.side_effect = self.test_config.get
         mock_format_ipv6_address.return_value = None
         mock_resolve_address.return_value = '10.10.10.1'
         ctxt = context.SerialConsoleContext()()
@@ -396,15 +403,14 @@ class NovaComputeContextTests(CharmTestCase):
             {'serial_console_base_url': 'ws://10.10.10.1:6083/',
              'enable_serial_console': 'false'}
         )
-        mock_resolve_address.assert_called_with(endpoint_type=context.PUBLIC)
+        mock_resolve_address.assert_called_with(
+            endpoint_type=context.ch_ip.PUBLIC)
 
-    @mock.patch.object(context, 'format_ipv6_addr')
-    @mock.patch.object(context, 'resolve_address')
-    @mock.patch.object(context, 'config')
-    def test_serial_console_context_enabled(self, mock_config,
+    @mock.patch('charmhelpers.contrib.network.ip.format_ipv6_addr')
+    @mock.patch('charmhelpers.contrib.openstack.ip.resolve_address')
+    def test_serial_console_context_enabled(self,
                                             mock_resolve_address,
                                             mock_format_ipv6_address):
-        mock_config.side_effect = self.test_config.get
         self.test_config.set('enable-serial-console', True)
         mock_format_ipv6_address.return_value = None
         mock_resolve_address.return_value = '10.10.10.1'
@@ -414,7 +420,8 @@ class NovaComputeContextTests(CharmTestCase):
             {'serial_console_base_url': 'ws://10.10.10.1:6083/',
              'enable_serial_console': 'true'}
         )
-        mock_resolve_address.assert_called_with(endpoint_type=context.PUBLIC)
+        mock_resolve_address.assert_called_with(
+            endpoint_type=context.ch_ip.PUBLIC)
 
     def test_nova_cellv2_shared_db_context(self):
         self.relation_ids.return_value = ['shared-db:0']
diff --git a/unit_tests/test_nova_cc_hooks.py b/unit_tests/test_nova_cc_hooks.py
index 9b7e5272..70eb77a8 100644
--- a/unit_tests/test_nova_cc_hooks.py
+++ b/unit_tests/test_nova_cc_hooks.py
@@ -12,83 +12,64 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from mock import MagicMock, patch, call
 import os
 import tempfile
 
-from mock import MagicMock, patch, call
-from test_utils import CharmTestCase
+from unit_tests.test_utils import CharmTestCase
 
-with patch('charmhelpers.core.hookenv.config') as config:
-    with patch('charmhelpers.contrib.openstack.utils.get_os_codename_package'):
-        config.return_value = 'neutron'
-        import nova_cc_utils as utils
+import charmhelpers.contrib.hardening.harden as harden
 
-_reg = utils.register_configs
-_map = utils.restart_map
+import hooks.nova_cc_utils as utils
+import hooks.nova_cc_hooks as hooks
 
-utils.register_configs = MagicMock()
-utils.restart_map = MagicMock()
-
-with patch('charmhelpers.contrib.hardening.harden.harden') as mock_dec:
-    mock_dec.side_effect = (lambda *dargs, **dkwargs: lambda f:
-                            lambda *args, **kwargs: f(*args, **kwargs))
-    with patch('nova_cc_utils.guard_map') as gmap:
-        with patch('charmhelpers.core.hookenv.config') as config:
-            config.return_value = False
-            gmap.return_value = {}
-            import nova_cc_hooks as hooks
-
-utils.register_configs = _reg
-utils.restart_map = _map
 
 TO_PATCH = [
-    'apt_update',
-    'apt_install',
-    'configure_installation_source',
-    'charm_dir',
-    'do_openstack_upgrade',
-    'openstack_upgrade_available',
-    'config',
-    'config_value_changed',
-    'determine_endpoints',
-    'determine_packages',
-    'determine_ports',
-    'open_port',
-    'is_relation_made',
-    'is_unit_paused_set',
-    'local_unit',
-    'log',
-    'os_release',
-    'related_units',
-    'relation_get',
-    'relation_set',
-    'relation_ids',
-    'placement_api_enabled',
-    'ssh_compute_add',
-    'ssh_known_hosts_lines',
-    'ssh_authorized_keys_lines',
-    'save_script_rc',
-    'service_pause',
-    'service_reload',
-    'service_resume',
-    'services',
-    'execd_preinstall',
-    'network_manager',
-    'unit_get',
+    'charmhelpers.contrib.hahelpers.cluster.get_hacluster_config',
+    'charmhelpers.contrib.hahelpers.cluster.is_clustered',
+    'charmhelpers.contrib.network.ip.get_iface_for_address',
+    'charmhelpers.contrib.network.ip.get_netmask_for_address',
+    'charmhelpers.contrib.network.ip.get_relation_ip',
+    'charmhelpers.contrib.openstack.ha.utils.update_dns_ha_resource_params',
+    'charmhelpers.contrib.openstack.neutron.network_manager',
+    'charmhelpers.contrib.openstack.utils.configure_installation_source',
+    'charmhelpers.contrib.openstack.utils.config_value_changed',
+    'charmhelpers.contrib.openstack.utils.is_unit_paused_set',
+    'charmhelpers.contrib.openstack.utils.openstack_upgrade_available',
+    'charmhelpers.contrib.openstack.utils.os_release',
+    'charmhelpers.core.hookenv.charm_dir',
+    'charmhelpers.core.hookenv.config',
+    'charmhelpers.core.hookenv.is_leader',
+    'charmhelpers.core.hookenv.is_relation_made',
+    'charmhelpers.core.hookenv.local_unit',
+    'charmhelpers.core.hookenv.log',
+    'charmhelpers.core.hookenv.open_port',
+    'charmhelpers.core.hookenv.related_units',
+    'charmhelpers.core.hookenv.relation_get',
+    'charmhelpers.core.hookenv.relation_ids',
+    'charmhelpers.core.hookenv.relation_set',
+    'charmhelpers.core.hookenv.status_set',
+    'charmhelpers.core.hookenv.unit_get',
+    'charmhelpers.core.host.service_pause',
+    'charmhelpers.core.host.service_reload',
+    'charmhelpers.core.host.service_resume',
+    'charmhelpers.fetch.apt_install',
+    'charmhelpers.fetch.apt_update',
+    'charmhelpers.payload.execd.execd_preinstall',
+    'hooks.nova_cc_utils.determine_endpoints',
+    'hooks.nova_cc_utils.determine_packages',
+    'hooks.nova_cc_utils.determine_ports',
+    'hooks.nova_cc_utils.do_openstack_upgrade',
+    'hooks.nova_cc_utils.keystone_ca_cert_b64',
+    'hooks.nova_cc_utils.migrate_nova_databases',
+    'hooks.nova_cc_utils.placement_api_enabled',
+    'hooks.nova_cc_utils.save_script_rc',
+    'hooks.nova_cc_utils.serial_console_settings',
+    'hooks.nova_cc_utils.services',
+    'hooks.nova_cc_utils.ssh_authorized_keys_lines',
+    'hooks.nova_cc_utils.ssh_compute_add',
+    'hooks.nova_cc_utils.ssh_known_hosts_lines',
     'uuid',
-    'is_leader',
-    'keystone_ca_cert_b64',
-    'migrate_nova_databases',
-    'uuid',
-    'get_hacluster_config',
-    'get_iface_for_address',
-    'get_netmask_for_address',
-    'update_nrpe_config',
-    'status_set',
-    'update_dns_ha_resource_params',
-    'serial_console_settings',
-    'get_relation_ip',
-    'is_clustered',
 ]
 
 
@@ -112,10 +93,13 @@ class NovaCCHooksTests(CharmTestCase):
         super(NovaCCHooksTests, self).setUp(hooks, TO_PATCH)
         (tmpfd, hooks.NOVA_CONSOLEAUTH_OVERRIDE) = tempfile.mkstemp()
 
+        hooks.CONFIGS = None  # reset for each test
         self.config.side_effect = self.test_config.get
         self.relation_get.side_effect = self.test_relation.get
         self.charm_dir.return_value = '/var/lib/juju/charms/nova/charm'
         self.is_unit_paused_set.return_value = False
+        # disable hardening for unit tests
+        harden._DISABLE_HARDENING_FOR_UNIT_TEST = True
 
     def tearDown(self):
         try:
@@ -137,178 +121,192 @@ class NovaCCHooksTests(CharmTestCase):
         self.assertTrue(self.execd_preinstall.called)
         self.assertTrue(self.service_pause.called)
 
-    @patch.object(hooks, 'set_shared_metadatasecret')
-    @patch.object(hooks, 'get_shared_metadatasecret')
-    @patch.object(hooks, 'is_leader')
-    @patch.object(hooks, 'update_aws_compat_services')
+    @patch.object(utils, 'set_shared_metadatasecret')
+    @patch.object(utils, 'get_shared_metadatasecret')
+    @patch.object(hooks, 'update_nrpe_config')
+    @patch.object(utils, 'resource_map')
+    @patch('hooks.nova_cc_utils.update_aws_compat_services')
     @patch.object(hooks, 'update_nova_consoleauth_config')
-    @patch.object(hooks, 'is_db_initialised')
-    @patch.object(hooks, 'determine_packages')
-    @patch.object(utils, 'service_resume')
-    @patch.object(utils, 'config')
-    @patch.object(hooks, 'filter_installed_packages')
+    @patch('hooks.nova_cc_utils.is_db_initialised')
+    @patch('charmhelpers.fetch.filter_installed_packages')
     @patch.object(hooks, 'configure_https')
     def test_config_changed_no_upgrade(self, conf_https, mock_filter_packages,
-                                       utils_config, mock_service_resume,
-                                       mock_determine_packages,
                                        mock_is_db_initialised,
                                        mock_update_nova_consoleauth_config,
                                        mock_update_aws_compat_services,
-                                       mock_is_leader,
+                                       mock_resource_map,
+                                       mock_update_nrpe_config,
                                        mock_get_shared_metadatasecret,
                                        mock_set_shared_metadatasecret):
+        mock_resource_map.return_value = {}
         self.get_shared_metadatasecret = None
-        mock_determine_packages.return_value = []
-        utils_config.side_effect = self.test_config.get
+        self.determine_packages.return_value = []
+        self.is_leader.return_value = True
         self.test_config.set('console-access-protocol', 'dummy')
         self.openstack_upgrade_available.return_value = False
         mock_is_db_initialised.return_value = False
         self.os_release.return_value = 'diablo'
+        hooks.resolve_CONFIGS()
         hooks.config_changed()
         self.assertTrue(self.save_script_rc.called)
         mock_filter_packages.assert_called_with([])
         self.assertTrue(mock_update_nova_consoleauth_config.called)
         self.assertTrue(mock_update_aws_compat_services.called)
 
-    @patch.object(hooks, 'set_shared_metadatasecret')
-    @patch.object(hooks, 'get_shared_metadatasecret')
-    @patch.object(hooks, 'update_aws_compat_services')
+    @patch.object(utils, 'set_shared_metadatasecret')
+    @patch.object(utils, 'get_shared_metadatasecret')
+    @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
+    @patch.object(hooks, 'update_nrpe_config')
+    @patch('hooks.nova_cc_utils.update_aws_compat_services')
     @patch.object(hooks, 'update_nova_consoleauth_config')
-    @patch.object(hooks, 'is_db_initialised')
-    @patch.object(hooks, 'determine_packages')
-    @patch.object(utils, 'service_resume')
-    @patch.object(utils, 'config')
-    @patch.object(hooks, 'filter_installed_packages')
+    @patch('hooks.nova_cc_utils.is_db_initialised')
+    @patch('charmhelpers.fetch.filter_installed_packages')
     @patch.object(hooks, 'configure_https')
-    def test_config_changed_ocata(self, conf_https, mock_filter_packages,
-                                  utils_config, mock_service_resume,
-                                  mock_determine_packages,
+    def test_config_changed_ocata(self,
+                                  conf_https,
+                                  mock_filter_packages,
                                   mock_is_db_initialised,
                                   mock_update_nova_consoleauth_config,
                                   mock_update_aws_compat_services,
+                                  mock_update_nrpe_config,
+                                  mock_sub_ctxt,
                                   mock_get_shared_metadatasecret,
                                   mock_set_shared_metadatasecret):
         mock_get_shared_metadatasecret.return_value = None
         self.is_leader.return_value = True
-        mock_determine_packages.return_value = []
-        utils_config.side_effect = self.test_config.get
+        self.determine_packages.return_value = []
         self.test_config.set('console-access-protocol', 'dummy')
         self.openstack_upgrade_available.return_value = False
         mock_is_db_initialised.return_value = False
         self.os_release.return_value = 'diablo'
-        hooks.config_changed()
+        hooks.resolve_CONFIGS()
+        # probably need the with patch.object from below
+        with patch.object(hooks.CONFIGS, 'write_all') as wa:
+            hooks.config_changed()
+            self.assertTrue(wa.called)
         self.assertTrue(self.save_script_rc.called)
         mock_filter_packages.assert_called_with([])
         self.assertTrue(mock_update_nova_consoleauth_config.called)
         self.assertTrue(mock_update_aws_compat_services.called)
         mock_set_shared_metadatasecret.assert_called_once_with()
 
-    @patch.object(hooks, 'set_shared_metadatasecret')
-    @patch.object(hooks, 'get_shared_metadatasecret')
-    @patch.object(hooks, 'update_aws_compat_services')
+    @patch.object(utils, 'set_shared_metadatasecret')
+    @patch.object(utils, 'get_shared_metadatasecret')
+    @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
+    @patch.object(hooks, 'update_nrpe_config')
+    @patch('hooks.nova_cc_utils.update_aws_compat_services')
     @patch.object(hooks, 'update_nova_consoleauth_config')
-    @patch.object(hooks, 'is_db_initialised')
-    @patch.object(hooks, 'determine_packages')
-    @patch.object(utils, 'service_resume')
-    @patch.object(utils, 'config')
-    @patch.object(hooks, 'filter_installed_packages')
+    @patch('hooks.nova_cc_utils.is_db_initialised')
+    @patch('charmhelpers.fetch.filter_installed_packages')
     @patch.object(hooks, 'configure_https')
     def test_config_changed_no_upgrade_juno(self, conf_https,
                                             mock_filter_packages,
-                                            utils_config, mock_service_resume,
-                                            mock_determine_packages,
                                             mock_is_db_initialised,
                                             mock_update_nova_consoleauth_cfg,
                                             mock_update_aws_compat_services,
+                                            mock_update_nrpe_config,
+                                            mock_sub_ctxt,
                                             mock_get_shared_metadatasecret,
                                             mock_set_shared_metadatasecret):
-        mock_determine_packages.return_value = []
-        utils_config.side_effect = self.test_config.get
+        self.determine_packages.return_value = []
         self.test_config.set('console-access-protocol', 'dummy')
         self.openstack_upgrade_available.return_value = False
         mock_is_db_initialised.return_value = False
         self.os_release.return_value = 'juno'
-        hooks.config_changed()
+        hooks.resolve_CONFIGS()
+        with patch.object(hooks.CONFIGS, 'write_all') as wa:
+            hooks.config_changed()
+            self.assertTrue(wa.called)
         self.assertTrue(self.save_script_rc.called)
         mock_filter_packages.assert_called_with([])
         self.assertTrue(mock_update_nova_consoleauth_cfg.called)
         self.assertTrue(mock_update_aws_compat_services.called)
         self.service_pause.assert_called_with('neutron-server')
 
-    @patch.object(hooks, 'set_shared_metadatasecret')
-    @patch.object(hooks, 'get_shared_metadatasecret')
-    @patch.object(hooks, 'update_aws_compat_services')
+    @patch.object(utils, 'set_shared_metadatasecret')
+    @patch.object(utils, 'get_shared_metadatasecret')
+    @patch.object(hooks, 'update_nrpe_config')
+    @patch.object(utils, 'resource_map')
+    @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
+    @patch('hooks.nova_cc_utils.update_aws_compat_services')
     @patch.object(hooks, 'update_nova_consoleauth_config')
-    @patch.object(hooks, 'is_db_initialised')
-    @patch.object(hooks, 'determine_packages')
-    @patch.object(utils, 'service_resume')
-    @patch.object(utils, 'config')
-    @patch.object(hooks, 'filter_installed_packages')
+    @patch('hooks.nova_cc_utils.is_db_initialised')
+    @patch('charmhelpers.fetch.filter_installed_packages')
     @patch.object(hooks, 'configure_https')
     def test_config_changed_no_upgrade_juno_no_neutron_server(
-            self, conf_https, mock_filter_packages,
-            utils_config, mock_service_resume,
-            mock_determine_packages,
+            self,
+            conf_https,
+            mock_filter_packages,
             mock_is_db_initialised,
             mock_update_nova_consoleauth_cfg,
             mock_update_aws_compat_services,
+            mock_sub_ctxt,
+            mock_resource_map,
+            mock_update_nrpe_config,
             mock_get_shared_metadatasecret,
             mock_set_shared_metadatasecret):
-        mock_determine_packages.return_value = []
-        utils_config.side_effect = self.test_config.get
+        mock_resource_map.return_value = {}
+        self.determine_packages.return_value = []
         self.test_config.set('console-access-protocol', 'dummy')
         self.openstack_upgrade_available.return_value = False
         mock_is_db_initialised.return_value = False
         self.os_release.return_value = 'juno'
         self.service_pause.side_effect = ValueError
-        hooks.config_changed()
+        hooks.resolve_CONFIGS()
+        with patch.object(hooks.CONFIGS, 'write_all'):
+            hooks.config_changed()
         self.assertTrue(self.save_script_rc.called)
         mock_filter_packages.assert_called_with([])
         self.assertTrue(mock_update_nova_consoleauth_cfg.called)
         self.assertTrue(mock_update_aws_compat_services.called)
         self.service_pause.assert_called_with('neutron-server')
 
-    @patch.object(hooks, 'set_shared_metadatasecret')
-    @patch.object(hooks, 'get_shared_metadatasecret')
-    @patch.object(hooks, 'update_aws_compat_services')
+    @patch.object(utils, 'set_shared_metadatasecret')
+    @patch.object(utils, 'get_shared_metadatasecret')
+    @patch.object(hooks, 'update_nrpe_config')
+    @patch.object(utils, 'resource_map')
+    @patch('hooks.nova_cc_utils.update_aws_compat_services')
     @patch.object(hooks, 'update_nova_consoleauth_config')
-    @patch.object(hooks, 'is_db_initialised')
+    @patch('hooks.nova_cc_utils.is_db_initialised')
     @patch.object(hooks, 'quantum_joined')
-    @patch.object(hooks, 'determine_packages')
-    @patch.object(utils, 'service_resume')
     @patch('charmhelpers.contrib.openstack.ip.unit_get')
     @patch('charmhelpers.contrib.hahelpers.cluster.relation_ids')
-    @patch.object(utils, 'config')
     @patch.object(hooks, 'db_joined')
-    @patch.object(hooks, 'filter_installed_packages')
+    @patch('charmhelpers.fetch.filter_installed_packages')
     @patch('charmhelpers.contrib.openstack.ip.service_name',
            lambda *args: 'nova-cloud-controller')
     @patch.object(hooks, 'cluster_joined')
     @patch.object(hooks, 'identity_joined')
     @patch.object(hooks, 'neutron_api_relation_joined')
     @patch.object(hooks, 'configure_https')
-    def test_config_changed_with_upgrade(self, conf_https, neutron_api_joined,
+    @patch.object(hooks, 'compute_joined')
+    @patch.object(hooks, 'nova_cell_api_relation_joined')
+    def test_config_changed_with_upgrade(self,
+                                         mock_nova_cell_api_relation_joined,
+                                         mock_compute_joined,
+                                         conf_https, neutron_api_joined,
                                          identity_joined, cluster_joined,
                                          mock_filter_packages, db_joined,
-                                         utils_config, mock_relids,
+                                         mock_relids,
                                          mock_unit_get,
-                                         mock_service_resume,
-                                         mock_determine_packages,
                                          mock_quantum_joined,
                                          mock_is_db_initialised,
                                          mock_update_nova_consoleauth_config,
                                          mock_update_aws_compat_services,
+                                         mock_resource_map,
+                                         mock_update_nrpe_config,
                                          mock_get_shared_metadatasecret,
                                          mock_set_shared_metadatasecret):
-        mock_determine_packages.return_value = []
+        mock_resource_map.return_value = {}
+        self.determine_packages.return_value = []
         mock_is_db_initialised.return_value = False
         self.openstack_upgrade_available.return_value = True
         self.relation_ids.return_value = ['generic_rid']
-        utils_config.side_effect = self.test_config.get
         self.test_config.set('console-access-protocol', 'dummy')
         mock_relids.return_value = []
         mock_unit_get.return_value = '127.0.0.1'
         self.os_release.return_value = 'diablo'
+        hooks.resolve_CONFIGS()
         hooks.config_changed()
         self.assertTrue(self.do_openstack_upgrade.called)
         self.assertTrue(neutron_api_joined.called)
@@ -321,24 +319,31 @@ class NovaCCHooksTests(CharmTestCase):
         self.assertTrue(mock_update_nova_consoleauth_config.called)
         self.assertTrue(mock_update_aws_compat_services.called)
 
-    @patch.object(hooks, 'set_shared_metadatasecret')
-    @patch.object(hooks, 'get_shared_metadatasecret')
-    @patch.object(hooks, 'update_aws_compat_services')
+    @patch.object(utils, 'set_shared_metadatasecret')
+    @patch.object(utils, 'get_shared_metadatasecret')
+    @patch.object(hooks, 'update_nrpe_config')
+    @patch.object(utils, 'resource_map')
+    @patch('charmhelpers.contrib.hahelpers.cluster.relation_ids')
+    @patch('hooks.nova_cc_utils.update_aws_compat_services')
     @patch.object(hooks, 'update_nova_consoleauth_config')
-    @patch.object(hooks, 'is_db_initialised')
-    @patch.object(utils, 'service_resume')
-    @patch.object(hooks, 'filter_installed_packages')
+    @patch('hooks.nova_cc_utils.is_db_initialised')
+    @patch('charmhelpers.fetch.filter_installed_packages')
     @patch.object(hooks, 'configure_https')
+    @patch.object(hooks, 'compute_joined')
     @patch.object(hooks, 'compute_changed')
     def test_config_changed_region_change(self, mock_compute_changed,
+                                          mock_compute_joined,
                                           mock_config_https,
                                           mock_filter_packages,
-                                          mock_service_resume,
                                           mock_is_db_initialised,
                                           mock_update_nova_consoleauth_config,
                                           mock_update_aws_compat_services,
+                                          mock_relation_ids,
+                                          mock_resource_map,
+                                          mock_update_nrpe_config,
                                           mock_get_shared_metadatasecret,
                                           mock_set_shared_metadatasecret):
+        mock_resource_map.return_value = {}
         self.openstack_upgrade_available.return_value = False
         self.config_value_changed.return_value = True
         self.related_units.return_value = ['unit/0']
@@ -346,13 +351,16 @@ class NovaCCHooksTests(CharmTestCase):
             lambda x: ['generic_rid'] if x == 'cloud-compute' else []
         mock_is_db_initialised.return_value = False
         self.os_release.return_value = 'diablo'
+        hooks.resolve_CONFIGS()
         hooks.config_changed()
         mock_compute_changed.assert_has_calls([call('generic_rid', 'unit/0')])
+        mock_compute_joined.assert_has_calls(
+            [call(rid='generic_rid', remote_restart=False)])
         self.assertTrue(mock_update_nova_consoleauth_config.called)
         self.assertTrue(mock_update_aws_compat_services.called)
 
-    @patch.object(hooks, 'is_cellv2_init_ready')
-    @patch.object(hooks, 'is_db_initialised')
+    @patch('hooks.nova_cc_utils.is_cellv2_init_ready')
+    @patch('hooks.nova_cc_utils.is_db_initialised')
     @patch.object(hooks, 'nova_api_relation_joined')
     def test_compute_changed_nova_api_trigger(self, api_joined,
                                               mock_is_db_initialised,
@@ -363,8 +371,8 @@ class NovaCCHooksTests(CharmTestCase):
         hooks.compute_changed()
         api_joined.assert_called_with(rid='nova-api/0')
 
-    @patch.object(hooks, 'is_cellv2_init_ready')
-    @patch.object(hooks, 'is_db_initialised')
+    @patch('hooks.nova_cc_utils.is_cellv2_init_ready')
+    @patch('hooks.nova_cc_utils.is_db_initialised')
     def test_compute_changed_ssh_migration(self, mock_is_db_initialised,
                                            mock_is_cellv2_init_ready):
         self.test_relation.set({
@@ -393,11 +401,10 @@ class NovaCCHooksTests(CharmTestCase):
                  relation_id=None),
             call(authorized_keys_max_index=3, relation_id=None),
             call(known_hosts_max_index=3, relation_id=None)]
-        self.assertEqual(sorted(self.relation_set.call_args_list),
-                         sorted(expected_relations))
+        self.relation_set.assert_has_calls(expected_relations, any_order=True)
 
-    @patch.object(hooks, 'is_cellv2_init_ready')
-    @patch.object(hooks, 'is_db_initialised')
+    @patch('hooks.nova_cc_utils.is_cellv2_init_ready')
+    @patch('hooks.nova_cc_utils.is_db_initialised')
     def test_compute_changed_nova_public_key(self, mock_is_db_initialised,
                                              mock_is_cellv2_init_ready):
         self.test_relation.set({
@@ -429,12 +436,11 @@ class NovaCCHooksTests(CharmTestCase):
                  relation_id=None),
             call(relation_settings={'nova_authorized_keys_max_index': 3},
                  relation_id=None)]
-        self.assertEqual(sorted(self.relation_set.call_args_list),
-                         sorted(expected_relations))
+        self.relation_set.assert_has_calls(expected_relations, any_order=True)
 
-    @patch.object(hooks, 'is_cellv2_init_ready')
-    @patch.object(hooks, 'is_db_initialised')
-    @patch.object(hooks, 'add_hosts_to_cell')
+    @patch('hooks.nova_cc_utils.is_cellv2_init_ready')
+    @patch('hooks.nova_cc_utils.is_db_initialised')
+    @patch('hooks.nova_cc_utils.add_hosts_to_cell')
     def test_compute_changed_add_hosts_leader(self,
                                               mock_add_hosts_to_cell,
                                               mock_is_db_initialised,
@@ -448,9 +454,9 @@ class NovaCCHooksTests(CharmTestCase):
         self.assertTrue(mock_is_cellv2_init_ready.called)
         self.assertTrue(mock_add_hosts_to_cell.called)
 
-    @patch.object(hooks, 'is_cellv2_init_ready')
-    @patch.object(hooks, 'is_db_initialised')
-    @patch.object(hooks, 'add_hosts_to_cell')
+    @patch('hooks.nova_cc_utils.is_cellv2_init_ready')
+    @patch('hooks.nova_cc_utils.is_db_initialised')
+    @patch('hooks.nova_cc_utils.add_hosts_to_cell')
     def test_compute_changed_add_hosts_nonleader(self,
                                                  mock_add_hosts_to_cell,
                                                  mock_is_db_initialised,
@@ -464,12 +470,9 @@ class NovaCCHooksTests(CharmTestCase):
         self.assertFalse(mock_is_cellv2_init_ready.called)
         self.assertFalse(mock_add_hosts_to_cell.called)
 
-    @patch.object(hooks, 'canonical_url')
-    @patch.object(utils, 'config')
+    @patch('charmhelpers.contrib.openstack.ip.canonical_url')
     @patch.object(hooks, '_auth_config')
-    def test_compute_joined_neutron(self, auth_config, _util_config,
-                                    _canonical_url):
-        _util_config.return_value = None
+    def test_compute_joined_neutron(self, auth_config, _canonical_url):
         self.is_relation_made.return_value = False
         self.network_manager.return_value = 'neutron'
         self.is_leader = True
@@ -494,19 +497,18 @@ class NovaCCHooksTests(CharmTestCase):
             serial_console_base_url='ws://controller:6803',
             **FAKE_KS_AUTH_CFG)
 
-    @patch.object(hooks, 'canonical_url')
-    @patch.object(utils, 'config')
-    @patch.object(hooks, 'NeutronAPIContext')
+    @patch('charmhelpers.contrib.openstack.ip.canonical_url')
+    @patch('hooks.nova_cc_context.NeutronAPIContext')
     @patch.object(hooks, '_auth_config')
     def test_compute_joined_neutron_api_rel(self, auth_config, napi,
-                                            _util_config, _canonical_url):
+                                            _canonical_url):
         def mock_NeutronAPIContext():
             return {
                 'neutron_plugin': 'bob',
                 'neutron_security_groups': 'yes',
                 'neutron_url': 'http://nova-cc-host1:9696',
             }
-        _util_config.return_value = None
+
         napi.return_value = mock_NeutronAPIContext
         self.is_relation_made.return_value = True
         self.network_manager.return_value = 'neutron'
@@ -536,7 +538,7 @@ class NovaCCHooksTests(CharmTestCase):
             serial_console_base_url='ws://controller:6803',
             **FAKE_KS_AUTH_CFG)
 
-    @patch.object(hooks, 'canonical_url')
+    @patch('charmhelpers.contrib.openstack.ip.canonical_url')
     @patch.object(hooks, '_auth_config')
     def test_nova_vmware_joined(self, auth_config, _canonical_url):
         auth_config.return_value = FAKE_KS_AUTH_CFG
@@ -591,23 +593,25 @@ class NovaCCHooksTests(CharmTestCase):
         self.get_relation_ip.assert_called_with('shared-db',
                                                 cidr_network=None)
 
-    @patch('charmhelpers.contrib.openstack.ip.service_name',
-           lambda *args: 'nova-cloud-controller')
-    @patch('charmhelpers.contrib.openstack.ip.unit_get')
+    @patch('charmhelpers.contrib.openstack.ip.canonical_url')
     @patch('charmhelpers.contrib.openstack.ip.is_clustered')
-    @patch('charmhelpers.contrib.openstack.ip.config')
-    def test_identity_joined(self, _ip_config, _is_clustered, _unit_get):
+    def test_identity_joined(self, _is_clustered, mock_canonical_url):
         _is_clustered.return_value = False
-        _unit_get.return_value = '127.0.0.1'
-        _ip_config.side_effect = self.test_config.get
-
+        mock_canonical_url.side_effect = [
+            'http://ncc.example.com',
+            'http://127.0.0.1',
+            'http://127.0.0.2',
+        ]
         self.test_config.set('os-public-hostname', 'ncc.example.com')
         hooks.identity_joined()
-
-        self.determine_endpoints.asssert_called_with(
-            public_url='http://ncc.example.com',
-            internal_url='http://127.0.0.1',
-            admin_url='http://127.0.0.1'
+        mock_canonical_url.assert_has_calls([
+            call(hooks.CONFIGS, hooks.ch_ip.PUBLIC),
+            call(hooks.CONFIGS, hooks.ch_ip.INTERNAL),
+            call(hooks.CONFIGS, hooks.ch_ip.ADMIN)])
+        self.determine_endpoints.assert_called_with(
+            'http://ncc.example.com',
+            'http://127.0.0.1',
+            'http://127.0.0.2'
         )
 
     def test_identity_joined_partial_cluster(self):
@@ -616,8 +620,11 @@ class NovaCCHooksTests(CharmTestCase):
         hooks.identity_joined()
         self.assertFalse(self.relation_set.called)
 
+    @patch.object(utils, 'resource_map')
     @patch.object(hooks, 'CONFIGS')
-    def test_db_changed_missing_relation_data(self, configs):
+    def test_db_changed_missing_relation_data(
+            self, configs, mock_resource_map):
+        mock_resource_map.return_value = {}
         configs.complete_contexts = MagicMock()
         configs.complete_contexts.return_value = []
         hooks.db_changed()
@@ -631,13 +638,15 @@ class NovaCCHooksTests(CharmTestCase):
         configs.write = MagicMock()
         hooks.db_changed()
 
+    @patch.object(utils, 'resource_map')
     @patch.object(hooks, 'nova_api_relation_joined')
-    @patch.object(hooks, 'is_db_initialised')
+    @patch('hooks.nova_cc_utils.is_db_initialised')
     @patch.object(hooks, 'CONFIGS')
     def test_db_changed(self, configs,
-                        mock_is_db_initialised, api_joined):
+                        mock_is_db_initialised, api_joined, mock_resource_map):
         self.relation_ids.return_value = ['nova-api/0']
         mock_is_db_initialised.return_value = False
+        mock_resource_map.return_value = {}
         'No database migration is attempted when ACL list is not present'
         self.os_release.return_value = 'diablo'
         self._shared_db_test(configs)
@@ -645,29 +654,31 @@ class NovaCCHooksTests(CharmTestCase):
         self.assertFalse(self.migrate_nova_databases.called)
         api_joined.asert_called_with(rid='nova-api/0')
 
-    @patch.object(utils, 'is_leader')
-    @patch.object(utils, 'os_release')
-    @patch.object(hooks, 'is_db_initialised')
+    @patch.object(utils, 'resource_map')
+    @patch('hooks.nova_cc_utils.is_db_initialised')
     @patch.object(hooks, 'CONFIGS')
     def test_db_changed_allowed(self, configs, mock_is_db_initialised,
-                                utils_os_release, utils_is_leader):
+                                mock_resource_map):
         mock_is_db_initialised.return_value = False
+        mock_resource_map.return_value = {}
         allowed_units = 'nova-cloud-controller/0 nova-cloud-controller/3'
         self.test_relation.set({
             'nova_allowed_units': allowed_units,
         })
         self.local_unit.return_value = 'nova-cloud-controller/3'
         self.os_release.return_value = 'diablo'
-        utils_os_release.return_value = 'diablo'
-        utils_is_leader.return_value = False
+        self.is_leader.return_value = True
         self._shared_db_test(configs)
         self.assertTrue(configs.write_all.called)
         self.migrate_nova_databases.assert_called_with()
 
-    @patch.object(hooks, 'is_db_initialised')
+    @patch.object(utils, 'resource_map')
+    @patch('hooks.nova_cc_utils.is_db_initialised')
     @patch.object(hooks, 'CONFIGS')
-    def test_db_changed_not_allowed(self, configs, mock_is_db_initialised):
+    def test_db_changed_not_allowed(self, configs, mock_is_db_initialised,
+                                    mock_resource_map):
         mock_is_db_initialised.return_value = False
+        mock_resource_map.return_value = {}
         allowed_units = 'nova-cloud-controller/0 nova-cloud-controller/3'
         self.test_relation.set({
             'nova_allowed_units': allowed_units,
@@ -678,17 +689,17 @@ class NovaCCHooksTests(CharmTestCase):
         self.assertTrue(configs.write_all.called)
         self.assertFalse(self.migrate_nova_databases.called)
 
-    @patch.object(utils, 'is_leader')
-    @patch.object(utils, 'os_release')
+    @patch.object(utils, 'resource_map')
     @patch.object(hooks, 'quantum_joined')
-    @patch.object(hooks, 'is_db_initialised')
+    @patch('hooks.nova_cc_utils.is_db_initialised')
     @patch.object(hooks, 'compute_joined')
     @patch.object(hooks, 'CONFIGS')
     def test_db_changed_remote_restarts(self, configs, comp_joined,
                                         mock_is_db_initialised,
-                                        quantum_joined, utils_os_release,
-                                        utils_is_leader):
+                                        quantum_joined,
+                                        mock_resource_map):
         mock_is_db_initialised.return_value = False
+        mock_resource_map.return_value = {}
 
         def _relation_ids(rel):
             relid = {
@@ -705,8 +716,7 @@ class NovaCCHooksTests(CharmTestCase):
         })
         self.local_unit.return_value = 'nova-cloud-controller/0'
         self.os_release.return_value = 'diablo'
-        utils_os_release.return_value = 'diablo'
-        utils_is_leader.return_value = False
+        self.is_leader.return_value = True
         self._shared_db_test(configs)
         comp_joined.assert_called_with(remote_restart=True,
                                        rid='nova-compute/0')
@@ -722,16 +732,19 @@ class NovaCCHooksTests(CharmTestCase):
         self.assertTrue(configs.write_all.called)
 
     @patch.object(hooks, 'update_child_cell_records')
+    @patch.object(utils, 'resource_map')
     @patch.object(hooks, 'leader_init_db_if_ready_allowed_units')
     @patch.object(hooks, 'update_cell_db_if_ready_allowed_units')
-    @patch.object(hooks, 'is_db_initialised')
+    @patch('hooks.nova_cc_utils.is_db_initialised')
     @patch.object(hooks, 'quantum_joined')
     @patch.object(hooks, 'nova_api_relation_joined')
     @patch.object(hooks, 'CONFIGS')
     def test_amqp_changed_api_rel(self, configs, api_joined,
                                   quantum_joined, mock_is_db_initialised,
                                   update_db_allowed, init_db_allowed,
+                                  mock_resource_map,
                                   mock_update_child_cell_records):
+        mock_resource_map.return_value = {}
         self.relation_ids.side_effect = [
             ['nova-api/0'],
             ['quantum-service/0'],
@@ -752,14 +765,21 @@ class NovaCCHooksTests(CharmTestCase):
     @patch.object(hooks, 'update_child_cell_records')
     @patch.object(hooks, 'leader_init_db_if_ready_allowed_units')
     @patch.object(hooks, 'update_cell_db_if_ready_allowed_units')
-    @patch.object(hooks, 'is_db_initialised')
+    @patch.object(utils, 'resource_map')
+    @patch('hooks.nova_cc_utils.is_db_initialised')
     @patch.object(hooks, 'quantum_joined')
     @patch.object(hooks, 'nova_api_relation_joined')
     @patch.object(hooks, 'CONFIGS')
-    def test_amqp_changed_noapi_rel(self, configs, api_joined,
-                                    quantum_joined, mock_is_db_initialised,
-                                    update_db_allowed, init_db_allowed,
+    def test_amqp_changed_noapi_rel(self,
+                                    configs,
+                                    api_joined,
+                                    quantum_joined,
+                                    mock_is_db_initialised,
+                                    mock_resource_map,
+                                    update_db_allowed,
+                                    init_db_allowed,
                                     mock_update_child_cell_records):
+        mock_resource_map.return_value = {}
         mock_is_db_initialised.return_value = False
         configs.complete_contexts = MagicMock()
         configs.complete_contexts.return_value = ['amqp']
@@ -778,7 +798,7 @@ class NovaCCHooksTests(CharmTestCase):
         quantum_joined.assert_called_with(rid='quantum-service/0',
                                           remote_restart=True)
 
-    @patch.object(hooks, 'canonical_url')
+    @patch('charmhelpers.contrib.openstack.ip.canonical_url')
     @patch.object(os, 'rename')
     @patch.object(os.path, 'isfile')
     @patch.object(hooks, 'CONFIGS')
@@ -796,8 +816,10 @@ class NovaCCHooksTests(CharmTestCase):
                                              nova_url=nova_url,
                                              restart_trigger='bob')
 
+    @patch('hooks.nova_cc_utils.resource_map')
     @patch.object(hooks, 'CONFIGS')
-    def test_neutron_api_relation_changed(self, configs):
+    def test_neutron_api_relation_changed(self, configs, mock_resource_map):
+        mock_resource_map.return_value = {}
         self.relation_ids.return_value = ['relid']
         _compute_joined = self.patch('compute_joined')
         _quantum_joined = self.patch('quantum_joined')
@@ -806,10 +828,13 @@ class NovaCCHooksTests(CharmTestCase):
         self.assertTrue(_compute_joined.called)
         self.assertTrue(_quantum_joined.called)
 
+    @patch.object(utils, 'resource_map')
     @patch.object(os, 'remove')
     @patch.object(os.path, 'isfile')
     @patch.object(hooks, 'CONFIGS')
-    def test_neutron_api_relation_broken(self, configs, isfile, remove):
+    def test_neutron_api_relation_broken(self, configs, isfile, remove,
+                                         mock_resource_map):
+        mock_resource_map.return_value = {}
         isfile.return_value = True
         self.relation_ids.return_value = ['relid']
         _compute_joined = self.patch('compute_joined')
@@ -819,12 +844,15 @@ class NovaCCHooksTests(CharmTestCase):
         self.assertTrue(_compute_joined.called)
         self.assertTrue(_quantum_joined.called)
 
-    @patch.object(hooks, 'canonical_url')
-    @patch.object(utils, 'config')
-    def test_console_settings_vnc(self, _utils_config, _canonical_url):
-        _utils_config.return_value = 'vnc'
+    @patch.object(utils, 'resource_map')
+    @patch('charmhelpers.contrib.openstack.ip.canonical_url')
+    def test_console_settings_vnc(self, _canonical_url, mock_resource_map):
+        self.test_config.set('console-access-protocol', 'vnc')
+        self.os_release.return_value = 'kilo'
+        mock_resource_map.return_value = {}
         _cc_host = "nova-cc-host1"
         _canonical_url.return_value = 'http://' + _cc_host
+        hooks.resolve_CONFIGS()
         _con_sets = hooks.console_settings()
         console_settings = {
             'console_proxy_novnc_address': 'http://%s:6080/vnc_auto.html' %
@@ -840,10 +868,9 @@ class NovaCCHooksTests(CharmTestCase):
         }
         self.assertEqual(_con_sets, console_settings)
 
-    @patch.object(hooks, 'canonical_url')
-    @patch.object(utils, 'config')
-    def test_console_settings_xvpvnc(self, _utils_config, _canonical_url):
-        _utils_config.return_value = 'xvpvnc'
+    @patch('charmhelpers.contrib.openstack.ip.canonical_url')
+    def test_console_settings_xvpvnc(self, _canonical_url):
+        self.test_config.set('console-access-protocol', 'xvpvnc')
         _cc_host = "nova-cc-host1"
         _canonical_url.return_value = 'http://' + _cc_host
         _con_sets = hooks.console_settings()
@@ -857,10 +884,9 @@ class NovaCCHooksTests(CharmTestCase):
         }
         self.assertEqual(_con_sets, console_settings)
 
-    @patch.object(hooks, 'canonical_url')
-    @patch.object(utils, 'config')
-    def test_console_settings_novnc(self, _utils_config, _canonical_url):
-        _utils_config.return_value = 'novnc'
+    @patch('charmhelpers.contrib.openstack.ip.canonical_url')
+    def test_console_settings_novnc(self, _canonical_url):
+        self.test_config.set('console-access-protocol', 'novnc')
         _cc_host = "nova-cc-host1"
         _canonical_url.return_value = 'http://' + _cc_host
         _con_sets = hooks.console_settings()
@@ -874,10 +900,9 @@ class NovaCCHooksTests(CharmTestCase):
         }
         self.assertEqual(_con_sets, console_settings)
 
-    @patch.object(hooks, 'canonical_url')
-    @patch.object(utils, 'config')
-    def test_console_settings_spice(self, _utils_config, _canonical_url):
-        _utils_config.return_value = 'spice'
+    @patch('charmhelpers.contrib.openstack.ip.canonical_url')
+    def test_console_settings_spice(self, _canonical_url):
+        self.test_config.set('console-access-protocol', 'spice')
         _cc_host = "nova-cc-host1"
         _canonical_url.return_value = 'http://' + _cc_host
         _con_sets = hooks.console_settings()
@@ -891,10 +916,9 @@ class NovaCCHooksTests(CharmTestCase):
         }
         self.assertEqual(_con_sets, console_settings)
 
-    @patch.object(hooks, 'https')
-    @patch.object(utils, 'config')
-    def test_console_settings_explicit_ip(self, _utils_config, _https):
-        _utils_config.return_value = 'spice'
+    @patch('charmhelpers.contrib.hahelpers.cluster.https')
+    def test_console_settings_explicit_ip(self, _https):
+        self.test_config.set('console-access-protocol', 'spice')
         _https.return_value = False
         _cc_public_host = "public-host"
         self.test_config.set('console-proxy-ip', _cc_public_host)
@@ -909,11 +933,9 @@ class NovaCCHooksTests(CharmTestCase):
         }
         self.assertEqual(_con_sets, console_settings)
 
-    @patch.object(hooks, 'https')
-    @patch.object(utils, 'config')
-    def test_console_settings_explicit_ip_with_https(self, _utils_config,
-                                                     _https):
-        _utils_config.return_value = 'spice'
+    @patch('charmhelpers.contrib.hahelpers.cluster.https')
+    def test_console_settings_explicit_ip_with_https(self, _https):
+        self.test_config.set('console-access-protocol', 'spice')
         _https.return_value = True
         _cc_public_host = "public-host"
         self.test_config.set('console-proxy-ip', _cc_public_host)
@@ -928,8 +950,7 @@ class NovaCCHooksTests(CharmTestCase):
         }
         self.assertEqual(_con_sets, console_settings)
 
-    @patch('nova_cc_utils.config')
-    def test_ha_relation_joined_no_bound_ip(self, config):
+    def test_ha_relation_joined_no_bound_ip(self):
         self.get_hacluster_config.return_value = {
             'ha-bindiface': 'em0',
             'ha-mcastport': '8080',
@@ -937,7 +958,6 @@ class NovaCCHooksTests(CharmTestCase):
         }
         self.test_config.set('vip_iface', 'eth120')
         self.test_config.set('vip_cidr', '21')
-        config.return_value = None
         self.get_iface_for_address.return_value = None
         self.get_netmask_for_address.return_value = None
         hooks.ha_joined()
@@ -996,8 +1016,7 @@ class NovaCCHooksTests(CharmTestCase):
         self.assertTrue(self.update_dns_ha_resource_params.called)
         self.relation_set.assert_called_with(**args)
 
-    @patch('nova_cc_utils.config')
-    def test_ha_relation_multi_consoleauth(self, config):
+    def test_ha_relation_multi_consoleauth(self):
         self.get_hacluster_config.return_value = {
             'ha-bindiface': 'em0',
             'ha-mcastport': '8080',
@@ -1006,7 +1025,6 @@ class NovaCCHooksTests(CharmTestCase):
         self.test_config.set('vip_iface', 'eth120')
         self.test_config.set('vip_cidr', '21')
         self.test_config.set('single-nova-consoleauth', False)
-        config.return_value = 'novnc'
         self.get_iface_for_address.return_value = None
         self.get_netmask_for_address.return_value = None
         hooks.ha_joined()
@@ -1029,8 +1047,7 @@ class NovaCCHooksTests(CharmTestCase):
             call(**args),
         ])
 
-    @patch('nova_cc_utils.config')
-    def test_ha_relation_single_consoleauth(self, config):
+    def test_ha_relation_single_consoleauth(self):
         self.get_hacluster_config.return_value = {
             'ha-bindiface': 'em0',
             'ha-mcastport': '8080',
@@ -1038,7 +1055,7 @@ class NovaCCHooksTests(CharmTestCase):
         }
         self.test_config.set('vip_iface', 'eth120')
         self.test_config.set('vip_cidr', '21')
-        config.return_value = 'novnc'
+        self.test_config.set('console-access-protocol', 'novnc')
         self.get_iface_for_address.return_value = None
         self.get_netmask_for_address.return_value = None
         hooks.ha_joined()
@@ -1067,41 +1084,36 @@ class NovaCCHooksTests(CharmTestCase):
             call(**args),
         ])
 
-    @patch.object(hooks, 'set_shared_metadatasecret')
-    @patch.object(hooks, 'get_shared_metadatasecret')
-    @patch.object(hooks, 'update_aws_compat_services')
-    @patch.object(hooks, 'is_db_initialised')
-    @patch.object(hooks, 'determine_packages')
-    @patch.object(hooks, 'service_pause')
-    @patch.object(hooks, 'filter_installed_packages')
-    @patch('nova_cc_hooks.configure_https')
-    @patch('nova_cc_utils.config')
-    def test_config_changed_single_consoleauth(self, mock_config,
+    @patch.object(utils, 'set_shared_metadatasecret')
+    @patch.object(utils, 'get_shared_metadatasecret')
+    @patch.object(hooks, 'update_nrpe_config')
+    @patch.object(utils, 'resource_map')
+    @patch('hooks.nova_cc_utils.update_aws_compat_services')
+    @patch('hooks.nova_cc_utils.is_db_initialised')
+    @patch('charmhelpers.fetch.filter_installed_packages')
+    @patch.object(hooks, 'configure_https')
+    def test_config_changed_single_consoleauth(self,
                                                mock_configure_https,
                                                mock_filter_packages,
-                                               mock_service_pause,
-                                               mock_determine_packages,
                                                mock_is_db_initialised,
                                                mock_update_aws_compat_svcs,
+                                               mock_resource_map,
+                                               mock_update_nrpe_config,
                                                mock_get_shared_metadatasecret,
                                                mock_set_shared_metadatasecret):
-        mock_determine_packages.return_value = []
+        mock_resource_map.return_value = {}
+        self.determine_packages.return_value = []
         mock_is_db_initialised.return_value = False
         self.config_value_changed.return_value = False
         self.os_release.return_value = 'diablo'
 
-        def cfg(k, v):
-            if k == "single-nova-authconsole":
-                return True
-            return 'novnc'
+        self.test_config.set('single-nova-consoleauth', True)
+        self.test_config.set('console-access-protocol', 'novnc')
 
-        config.side_effect = cfg
         rids = {'ha': ['ha:1']}
+        self.relation_ids.side_effect = lambda r: rids.get(r, [])
 
-        def f(r):
-            return rids.get(r, [])
-
-        self.relation_ids.side_effect = f
+        hooks.resolve_CONFIGS()
         hooks.config_changed()
         args = {
             'delete_resources': [],
@@ -1118,15 +1130,15 @@ class NovaCCHooksTests(CharmTestCase):
             call(v, **args) for v in rids['ha']
         ])
 
-        mock_service_pause.assert_has_calls([
+        self.service_pause.assert_has_calls([
             call('nova-consoleauth')]
         )
         mock_filter_packages.assert_called_with([])
 
         self.assertTrue(mock_update_aws_compat_svcs.called)
 
-    @patch.object(hooks, 'is_api_ready')
-    def _test_nova_api_relation_joined(self, tgt, is_api_ready):
+    @patch('hooks.nova_cc_utils.is_api_ready')
+    def helper_test_nova_api_relation_joined(self, tgt, is_api_ready):
         is_api_ready.return_value = tgt
         exp = 'yes' if tgt else 'no'
         hooks.nova_api_relation_joined(rid='foo')
@@ -1134,10 +1146,10 @@ class NovaCCHooksTests(CharmTestCase):
             'foo', **{'nova-api-ready': exp})
 
     def test_nova_api_relation_joined_ready(self):
-        self._test_nova_api_relation_joined(True)
+        self.helper_test_nova_api_relation_joined(True)
 
     def test_nova_api_relation_joined_not_ready(self):
-        self._test_nova_api_relation_joined(False)
+        self.helper_test_nova_api_relation_joined(False)
 
     @patch.object(hooks, 'memcached_common')
     def test_memcache_joined(self, _memcached_common):
diff --git a/unit_tests/test_nova_cc_utils.py b/unit_tests/test_nova_cc_utils.py
index 6d429022..9e2b7190 100644
--- a/unit_tests/test_nova_cc_utils.py
+++ b/unit_tests/test_nova_cc_utils.py
@@ -15,57 +15,51 @@
 from collections import OrderedDict
 from mock import patch, MagicMock, call, mock_open
 
-from test_utils import (
+from unit_tests.test_utils import (
     CharmTestCase,
-    get_default_config,
     patch_open,
 )
 
-__default_config = get_default_config()
+import hooks.nova_cc_utils as utils
 
-with patch('charmhelpers.core.hookenv.config') as config:
-    with patch('charmhelpers.contrib.openstack.utils.get_os_codename_package'):  # noqa
-        # this makes the config behave more similar to the real config()
-        config.side_effect = lambda k: __default_config[k]
-
-        import nova_cc_utils as utils
 
 TO_PATCH = [
-    'apt_update',
-    'apt_upgrade',
-    'apt_install',
-    'config',
-    'configure_installation_source',
-    'canonical_url',
+    'charmhelpers.contrib.openstack.ip.canonical_url',
+    'charmhelpers.contrib.openstack.utils.configure_installation_source',
+    'charmhelpers.contrib.openstack.utils.enable_memcache',
+    'charmhelpers.contrib.openstack.utils.get_os_codename_install_source',
+    'charmhelpers.contrib.openstack.utils.is_unit_paused_set',
+    'charmhelpers.contrib.openstack.utils.os_application_version_set',
+    'charmhelpers.contrib.openstack.utils.os_release',
+    'charmhelpers.contrib.openstack.utils.save_script_rc',
+    'charmhelpers.contrib.openstack.utils.token_cache_pkgs',
+    'charmhelpers.contrib.peerstorage.peer_store',
+    'charmhelpers.core.hookenv.config',
+    'charmhelpers.core.hookenv.is_leader',
+    'charmhelpers.core.hookenv.leader_get',
+    'charmhelpers.core.hookenv.leader_set',
+    'charmhelpers.core.hookenv.local_unit',
+    'charmhelpers.core.hookenv.log',
+    'charmhelpers.core.hookenv.related_units',
+    'charmhelpers.core.hookenv.relation_get',
+    'charmhelpers.core.hookenv.relation_ids',
+    'charmhelpers.core.hookenv.remote_unit',
+    'charmhelpers.core.hookenv.status_set',
+    'charmhelpers.core.host.lsb_release',
+    'charmhelpers.core.host.service_pause',
+    'charmhelpers.core.host.service_restart',
+    'charmhelpers.core.host.service_resume',
+    'charmhelpers.core.host.service_running',
+    'charmhelpers.core.host.service_start',
+    'charmhelpers.core.host.service_stop',
+    'charmhelpers.fetch.apt_install',
+    'charmhelpers.fetch.apt_update',
+    'charmhelpers.fetch.apt_upgrade',
     'disable_policy_rcd',
-    'is_leader',
-    'is_unit_paused_set',
-    'lsb_release',
-    'leader_get',
-    'leader_set',
     'enable_policy_rcd',
-    'get_os_codename_install_source',
-    'log',
-    'os_release',
-    'peer_store',
-    'register_configs',
-    'relation_ids',
-    'remote_unit',
-    '_save_script_rc',
-    'service_pause',
-    'service_resume',
-    'service_start',
-    'services',
-    'service_running',
-    'service_stop',
-    'related_units',
-    'local_unit',
-    'relation_get',
-    'os_application_version_set',
-    'token_cache_pkgs',
-    'enable_memcache',
-    'status_set',
-    'uuid1',
+    'hooks.nova_cc_utils.register_configs',
+    'hooks.nova_cc_utils.services',
+    'uuid.uuid1',
 ]
 
 SCRIPTRC_ENV_VARS = {
@@ -141,23 +135,20 @@ RESTART_MAP_ICEHOUSE = OrderedDict([
 ])
 RESTART_MAP_OCATA_ACTUAL = OrderedDict([
     ('/etc/nova/nova.conf', [
-        'nova-api-ec2', 'nova-api-os-compute', 'nova-objectstore',
-        'nova-cert', 'nova-scheduler', 'nova-conductor', 'apache2',
-    ]),
-    ('/etc/nova/api-paste.ini', [
-        'nova-api-ec2', 'nova-api-os-compute', 'apache2'
+        'nova-api-os-compute', 'nova-scheduler', 'nova-conductor', 'apache2',
     ]),
+    ('/etc/nova/api-paste.ini', ['nova-api-os-compute', 'apache2']),
     ('/etc/haproxy/haproxy.cfg', ['haproxy']),
     ('/etc/apache2/sites-available/openstack_https_frontend', ['apache2']),
     ('/etc/apache2/sites-enabled/wsgi-openstack-api.conf', ['apache2']),
 ])
 RESTART_MAP_OCATA_BASE = OrderedDict([
     ('/etc/nova/nova.conf', [
-        'nova-api-ec2', 'nova-api-os-compute', 'nova-placement-api',
-        'nova-objectstore', 'nova-cert', 'nova-scheduler', 'nova-conductor'
+        'nova-api-os-compute', 'nova-placement-api',
+        'nova-scheduler', 'nova-conductor'
     ]),
     ('/etc/nova/api-paste.ini', [
-        'nova-api-ec2', 'nova-api-os-compute', 'nova-placement-api'
+        'nova-api-os-compute', 'nova-placement-api'
     ]),
     ('/etc/haproxy/haproxy.cfg', ['haproxy']),
     ('/etc/apache2/sites-available/openstack_https_frontend', ['apache2'])
@@ -198,7 +189,7 @@ ubuntu-cloud-archive/liberty-staging/ubuntu trusty main
 %s
 """ % GPG_PPA_CLOUD_ARCHIVE
 
-NM_CELLS_LIST = """
+NM_CELLS_LIST = b"""
 +-------+--------------------------------------+--------------+-------------+
 | Name  | UUID                                 | Transport    | DB          |
 +-------+--------------------------------------+--------------+-------------+
@@ -213,13 +204,36 @@ class NovaCCUtilsTests(CharmTestCase):
     def setUp(self):
         super(NovaCCUtilsTests, self).setUp(utils, TO_PATCH)
         self.config.side_effect = self.test_config.get
+        utils._BASE_RESOURCE_MAP = None  # reset this for each test
         self.maxDiff = None
 
-    def _resource_map(self):
-        with patch('charmhelpers.contrib.openstack.context.'
-                   'SubordinateConfigContext'):
-            _map = utils.resource_map()
-            return _map
+    def test_resolve_services(self):
+        # Icehouse with disable-aws-compat = True
+        self.test_config.set('disable-aws-compat', True)
+        self.os_release.return_value = "icehouse"
+        _services = utils.resolve_services()
+        for _service in utils.AWS_COMPAT_SERVICES:
+            self.assertTrue(_service not in _services)
+
+        # Icehouse with disable-aws-compat = False
+        self.test_config.set('disable-aws-compat', False)
+        self.os_release.return_value = "icehouse"
+        _services = utils.resolve_services()
+        for _service in utils.AWS_COMPAT_SERVICES:
+            self.assertTrue(_service in _services)
+
+        # Liberty
+        self.os_release.return_value = "liberty"
+        _services = utils.resolve_services()
+        for _service in utils.AWS_COMPAT_SERVICES:
+            self.assertTrue(_service not in _services)
+
+        # Newton
+        self.os_release.return_value = "newton"
+        _services = utils.resolve_services()
+        for _service in utils.AWS_COMPAT_SERVICES:
+            self.assertTrue(_service not in _services)
+        self.assertTrue('nova-cert' not in _services)
 
     @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
     def test_resource_map_vmware(self, subcontext):
@@ -238,7 +252,6 @@ class NovaCCUtilsTests(CharmTestCase):
     @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
     def test_resource_map_neutron_no_agent_installed(self, subcontext):
         self.os_release.return_value = 'diablo'
-        self._resource_map()
         _map = utils.resource_map()
         services = []
         [services.extend(_map[c]['services'])for c in _map]
@@ -278,19 +291,19 @@ class NovaCCUtilsTests(CharmTestCase):
 
     def test_console_attributes_none(self):
         self.test_config.set('console-access-protocol', 'None')
-        _proto = utils.console_attributes('protocol')
+        _proto = utils.common.console_attributes('protocol')
         self.assertEqual(_proto, None)
         self.test_config.set('console-access-protocol', 'NONE')
-        _proto = utils.console_attributes('protocol')
+        _proto = utils.common.console_attributes('protocol')
         self.assertEqual(_proto, None)
         self.test_config.set('console-access-protocol', 'none')
-        _proto = utils.console_attributes('protocol')
+        _proto = utils.common.console_attributes('protocol')
         self.assertEqual(_proto, None)
         self.test_config.set('console-access-protocol', None)
-        _proto = utils.console_attributes('protocol')
+        _proto = utils.common.console_attributes('protocol')
         self.assertEqual(_proto, None)
         self.test_config.set('console-access-protocol', "")
-        _proto = utils.console_attributes('protocol')
+        _proto = utils.common.console_attributes('protocol')
         self.assertEqual(_proto, None)
 
     @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
@@ -316,13 +329,12 @@ class NovaCCUtilsTests(CharmTestCase):
     @patch('charmhelpers.contrib.openstack.neutron.os_release')
     @patch('os.path.exists')
     @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
-    def test_restart_map_api_before_frontends_icehouse(self, subcontext,
-                                                       _exists, _os_release):
+    def test_restart_map_api_before_frontends_icehouse(
+            self, subcontext, _exists, _os_release):
         _os_release.return_value = 'icehouse'
         self.os_release.return_value = 'icehouse'
         _exists.return_value = False
         self.enable_memcache.return_value = False
-        self._resource_map()
         _map = utils.restart_map()
         self.assertIsInstance(_map, OrderedDict)
         self.assertEqual(_map, RESTART_MAP_ICEHOUSE)
@@ -330,13 +342,12 @@ class NovaCCUtilsTests(CharmTestCase):
     @patch('charmhelpers.contrib.openstack.neutron.os_release')
     @patch('os.path.exists')
     @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
-    def test_restart_map_api_actual_ocata(self, subcontext,
-                                          _exists, _os_release):
+    def test_restart_map_api_actual_ocata(
+            self, subcontext, _exists, _os_release):
         _os_release.return_value = 'ocata'
         self.os_release.return_value = 'ocata'
         _exists.return_value = False
         self.enable_memcache.return_value = False
-        self._resource_map()
         _map = utils.restart_map()
         self.assertIsInstance(_map, OrderedDict)
         self.assertEqual(_map, RESTART_MAP_OCATA_ACTUAL)
@@ -344,13 +355,12 @@ class NovaCCUtilsTests(CharmTestCase):
     @patch('charmhelpers.contrib.openstack.neutron.os_release')
     @patch('os.path.exists')
     @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
-    def test_restart_map_api_ocata_base(self, subcontext,
-                                        _exists, _os_release):
+    def test_restart_map_api_ocata_base(
+            self, subcontext, _exists, _os_release):
         _os_release.return_value = 'ocata'
         self.os_release.return_value = 'ocata'
         _exists.return_value = False
         self.enable_memcache.return_value = False
-        self._resource_map()
         _map = utils.restart_map(actual_services=False)
         self.assertIsInstance(_map, OrderedDict)
         self.assertEqual(_map, RESTART_MAP_OCATA_BASE)
@@ -360,7 +370,6 @@ class NovaCCUtilsTests(CharmTestCase):
     def test_restart_map_apache24(self, _exists, subcontext):
         _exists.return_Value = True
         self.os_release.return_value = 'diablo'
-        self._resource_map()
         _map = utils.restart_map()
         self.assertTrue('/etc/apache2/sites-available/'
                         'openstack_https_frontend.conf' in _map)
@@ -368,20 +377,20 @@ class NovaCCUtilsTests(CharmTestCase):
                         'openstack_https_frontend' not in _map)
 
     def test_console_attributes_spice(self):
-        _proto = utils.console_attributes('protocol', proto='spice')
+        _proto = utils.common.console_attributes('protocol', proto='spice')
         self.assertEqual(_proto, 'spice')
 
     def test_console_attributes_vnc(self):
         self.test_config.set('console-access-protocol', 'vnc')
-        _proto = utils.console_attributes('protocol')
-        _servs = utils.console_attributes('services')
-        _pkgs = utils.console_attributes('packages')
-        _proxy_page = utils.console_attributes('proxy-page')
+        _proto = utils.common.console_attributes('protocol')
+        _servs = utils.common.console_attributes('services')
+        _pkgs = utils.common.console_attributes('packages')
+        _proxy_page = utils.common.console_attributes('proxy-page')
         vnc_pkgs = ['nova-novncproxy', 'nova-xvpvncproxy', 'nova-consoleauth']
         vnc_servs = ['nova-novncproxy', 'nova-xvpvncproxy', 'nova-consoleauth']
         self.assertEqual(_proto, 'vnc')
-        self.assertEqual(_servs, vnc_servs)
-        self.assertEqual(_pkgs, vnc_pkgs)
+        self.assertEqual(sorted(_servs), sorted(vnc_servs))
+        self.assertEqual(sorted(_pkgs), sorted(vnc_pkgs))
         self.assertEqual(_proxy_page, None)
 
     def test_database_setup(self):
@@ -428,7 +437,7 @@ class NovaCCUtilsTests(CharmTestCase):
         self.enable_memcache.return_value = False
         pkgs = utils.determine_packages()
         ex = list(set(utils.BASE_PACKAGES + utils.BASE_SERVICES))
-        # nova-placement-api is purposely dropped unless it's ocata
+        # nova-placement-api, et al, are purposely dropped unless it's ocata
         ex.remove('nova-placement-api')
         self.assertEqual(sorted(ex), sorted(pkgs))
 
@@ -440,6 +449,10 @@ class NovaCCUtilsTests(CharmTestCase):
         self.enable_memcache.return_value = False
         pkgs = utils.determine_packages()
         ex = list(set(utils.BASE_PACKAGES + utils.BASE_SERVICES))
+        # some packages still need to be removed
+        ex.remove('nova-cert')
+        ex.remove('nova-objectstore')
+        ex.remove('nova-api-ec2')
         self.assertEqual(sorted(ex), sorted(pkgs))
 
     @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
@@ -451,12 +464,16 @@ class NovaCCUtilsTests(CharmTestCase):
         pkgs = utils.determine_packages()
         ex = list(set([p for p in utils.BASE_PACKAGES + utils.BASE_SERVICES
                       if not p.startswith('python-')] + utils.PY3_PACKAGES))
+        # some packages still need to be removed
         ex.remove('libapache2-mod-wsgi')
+        ex.remove('nova-cert')
+        ex.remove('nova-objectstore')
+        ex.remove('nova-api-ec2')
         self.assertEqual(sorted(ex), sorted(pkgs))
+        self.assertEqual(ex, pkgs)
 
     @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
-    def test_determine_packages_serial_console(self,
-                                               subcontext):
+    def test_determine_packages_serial_console(self, subcontext):
         self.test_config.set('enable-serial-console', True)
         self.relation_ids.return_value = []
         self.os_release.return_value = 'juno'
@@ -466,8 +483,7 @@ class NovaCCUtilsTests(CharmTestCase):
             self.assertIn(console_pkg, pkgs)
 
     @patch('charmhelpers.contrib.openstack.context.SubordinateConfigContext')
-    def test_determine_packages_serial_console_icehouse(self,
-                                                        subcontext):
+    def test_determine_packages_serial_console_icehouse(self, subcontext):
         self.test_config.set('enable-serial-console', True)
         self.relation_ids.return_value = []
         self.os_release.return_value = 'icehouse'
@@ -503,13 +519,14 @@ class NovaCCUtilsTests(CharmTestCase):
     def test_save_script_rc_base(self):
         self.relation_ids.return_value = []
         utils.save_script_rc()
-        self._save_script_rc.called_with(**SCRIPTRC_ENV_VARS)
+        self.save_script_rc.called_with(**SCRIPTRC_ENV_VARS)
 
     @patch('charmhelpers.contrib.openstack.utils.lsb_release')
     def test_get_step_upgrade_source_target_liberty(self, lsb_release):
         self.lsb_release.return_value = {'DISTRIB_CODENAME': 'trusty'}
         lsb_release.return_value = {'DISTRIB_CODENAME': 'trusty'}
         self.get_os_codename_install_source.side_effect = self.originals[
+            'charmhelpers.contrib.openstack.utils.'
             'get_os_codename_install_source']
 
         # icehouse -> liberty
@@ -538,6 +555,7 @@ class NovaCCUtilsTests(CharmTestCase):
         lsb_release.return_value = {'DISTRIB_CODENAME': 'xenial'}
         self.os_release.return_value = 'mitaka'
         self.get_os_codename_install_source.side_effect = self.originals[
+            'charmhelpers.contrib.openstack.utils.'
             'get_os_codename_install_source']
 
         step_src = utils.get_step_upgrade_source(OS_ORIGIN_NEWTON_STAGING)
@@ -550,6 +568,7 @@ class NovaCCUtilsTests(CharmTestCase):
         lsb_release.return_value = {'DISTRIB_CODENAME': 'xenial'}
         self.os_release.return_value = 'mitaka'
         self.get_os_codename_install_source.side_effect = self.originals[
+            'charmhelpers.contrib.openstack.utils.'
             'get_os_codename_install_source']
 
         step_src = utils.get_step_upgrade_source("cloud:xenial-ocata")
@@ -562,6 +581,7 @@ class NovaCCUtilsTests(CharmTestCase):
         self.lsb_release.return_value = {'DISTRIB_CODENAME': 'trusty'}
         lsb_release.return_value = {'DISTRIB_CODENAME': 'trusty'}
         self.get_os_codename_install_source.side_effect = self.originals[
+            'charmhelpers.contrib.openstack.utils.'
             'get_os_codename_install_source']
         self.os_release.return_value = 'icehouse'
         step_src = utils.get_step_upgrade_source(OS_ORIGIN_LIBERTY_STAGING)
@@ -571,7 +591,7 @@ class NovaCCUtilsTests(CharmTestCase):
     @patch.object(utils, 'ssh_known_host_key')
     @patch('subprocess.check_output')
     def test_add_known_host_exists(self, check_output, host_key, rm):
-        check_output.return_value = '|1|= fookey'
+        check_output.return_value = b'|1|= fookey'
         host_key.return_value = '|1|= fookey'
         with patch_open() as (_open, _file):
             utils.add_known_host('foohost')
@@ -584,7 +604,7 @@ class NovaCCUtilsTests(CharmTestCase):
     @patch('subprocess.check_output')
     def test_add_known_host_exists_outdated(
             self, check_output, host_key, rm, known_hosts):
-        check_output.return_value = '|1|= fookey'
+        check_output.return_value = b'|1|= fookey'
         host_key.return_value = '|1|= fookey_old'
         with patch_open() as (_open, _file):
             utils.add_known_host('foohost', None, None)
@@ -596,7 +616,7 @@ class NovaCCUtilsTests(CharmTestCase):
     @patch('subprocess.check_output')
     def test_add_known_host_exists_added(
             self, check_output, host_key, rm, known_hosts):
-        check_output.return_value = '|1|= fookey'
+        check_output.return_value = b'|1|= fookey'
         host_key.return_value = None
         with patch_open() as (_open, _file):
             _file.write = MagicMock()
@@ -604,7 +624,7 @@ class NovaCCUtilsTests(CharmTestCase):
             self.assertFalse(rm.called)
             _file.write.assert_called_with('|1|= fookey\n')
 
-    @patch('__builtin__.open')
+    @patch('builtins.open')
     @patch('os.mkdir')
     @patch('os.path.isdir')
     def test_ssh_directory_for_unit(self, isdir, mkdir, _open):
@@ -716,7 +736,7 @@ class NovaCCUtilsTests(CharmTestCase):
         """On precise ssh-keygen does not error if host not found in file. So
          check charm processes empty output properly"""
         _known_hosts.return_value = '/foo/known_hosts'
-        _check_output.return_value = ''
+        _check_output.return_value = b''
         key = utils.ssh_known_host_key('test')
         self.assertEqual(key, None)
 
@@ -991,47 +1011,60 @@ class NovaCCUtilsTests(CharmTestCase):
 
     def test_service_guard_active_guard(self):
         '''Ensure services with incomplete interfaces are stopped'''
-        contexts = MagicMock()
-        contexts.complete_contexts.return_value = ['interfacea']
+        class MockContext(object):
+            called = False
+
+            def complete_contexts(self):
+                self.called = True
+                return ['interfacea']
+
+        _mc = MockContext()
         self.service_running.return_value = True
 
         @utils.service_guard({'test': ['interfacea', 'interfaceb']},
-                             contexts, True)
+                             _mc, True)
         def dummy_func():
             pass
         dummy_func()
         self.service_running.assert_called_with('test')
         self.service_stop.assert_called_with('test')
-        self.assertTrue(contexts.complete_contexts.called)
+        self.assertTrue(_mc.called)
 
     def test_service_guard_active_release(self):
         '''Ensure services with complete interfaces are not stopped'''
-        contexts = MagicMock()
-        contexts.complete_contexts.return_value = ['interfacea',
-                                                   'interfaceb']
+        class MockContext(object):
+            called = False
+
+            def complete_contexts(self):
+                self.called = True
+                return ['interfacea', 'interfaceb']
+
+        _mc = MockContext()
 
         @utils.service_guard({'test': ['interfacea', 'interfaceb']},
-                             contexts, True)
+                             _mc, True)
         def dummy_func():
             pass
+
         dummy_func()
         self.assertFalse(self.service_running.called)
         self.assertFalse(self.service_stop.called)
-        self.assertTrue(contexts.complete_contexts.called)
+        self.assertTrue(_mc.called)
 
-    def _test_is_api_ready(self, tgt):
+    def helper_test_is_api_ready(self, tgt):
         fake_config = MagicMock()
-        with patch.object(utils, 'incomplete_relation_data') as ird:
+        with patch('charmhelpers.contrib.openstack.utils.'
+                   'incomplete_relation_data') as ird:
             ird.return_value = (not tgt)
             self.assertEqual(utils.is_api_ready(fake_config), tgt)
             ird.assert_called_with(
                 fake_config, utils.REQUIRED_INTERFACES)
 
     def test_is_api_ready_true(self):
-        self._test_is_api_ready(True)
+        self.helper_test_is_api_ready(True)
 
     def test_is_api_ready_false(self):
-        self._test_is_api_ready(False)
+        self.helper_test_is_api_ready(False)
 
     def test_assess_status(self):
         with patch.object(utils, 'assess_status_func') as asf:
@@ -1050,7 +1083,7 @@ class NovaCCUtilsTests(CharmTestCase):
     @patch.object(utils, 'REQUIRED_INTERFACES')
     @patch.object(utils, 'services')
     @patch.object(utils, 'determine_ports')
-    @patch.object(utils, 'make_assess_status_func')
+    @patch.object(utils.ch_utils, 'make_assess_status_func')
     def test_assess_status_func(self,
                                 make_assess_status_func,
                                 determine_ports,
@@ -1073,10 +1106,12 @@ class NovaCCUtilsTests(CharmTestCase):
     def test_pause_unit_helper(self):
         with patch.object(utils, '_pause_resume_helper') as prh:
             utils.pause_unit_helper('random-config')
-            prh.assert_called_once_with(utils.pause_unit, 'random-config')
+            prh.assert_called_once_with(utils.ch_utils.pause_unit,
+                                        'random-config')
         with patch.object(utils, '_pause_resume_helper') as prh:
             utils.resume_unit_helper('random-config')
-            prh.assert_called_once_with(utils.resume_unit, 'random-config')
+            prh.assert_called_once_with(utils.ch_utils.resume_unit,
+                                        'random-config')
 
     @patch.object(utils, 'services')
     @patch.object(utils, 'determine_ports')
@@ -1091,45 +1126,35 @@ class NovaCCUtilsTests(CharmTestCase):
             # ports=None whilst port checks are disabled.
             f.assert_called_once_with('assessor', services='s1', ports=None)
 
-    @patch.object(utils, 'service_pause')
-    @patch.object(utils, 'service_resume')
-    @patch.object(utils, 'config')
-    @patch.object(utils, 'filter_installed_packages')
-    def test_disable_aws_compat_services_uinstalled(self,
-                                                    filter_installed_packages,
-                                                    config, service_resume,
-                                                    service_pause):
+    @patch('charmhelpers.fetch.filter_installed_packages')
+    def test_disable_aws_compat_services_uninstalled(
+            self, filter_installed_packages,):
         filter_installed_packages.return_value = utils.AWS_COMPAT_SERVICES
         utils.update_aws_compat_services()
-        config.assert_not_called()
-        service_pause.assert_not_called()
-        service_resume.assert_not_called()
+        self.config.assert_not_called()
+        self.service_pause.assert_not_called()
+        self.service_resume.assert_not_called()
 
-    @patch.object(utils, 'service_pause')
-    @patch.object(utils, 'service_resume')
-    @patch.object(utils, 'config')
-    @patch.object(utils, 'filter_installed_packages')
-    def test_disable_aws_compat_services_true(self, filter_installed_packages,
-                                              config, s_resume, s_pause):
+    @patch('charmhelpers.fetch.filter_installed_packages')
+    def test_disable_aws_compat_services_true(self, filter_installed_packages):
         filter_installed_packages.return_value = []
-        config.return_value = True
+        self.test_config.set('disable-aws-compat', True)
         utils.update_aws_compat_services()
 
-        s_resume.assert_not_called()
-        s_pause.assert_has_calls([call(s) for s in utils.AWS_COMPAT_SERVICES])
+        self.service_resume.assert_not_called()
+        self.service_pause.assert_has_calls(
+            [call(s) for s in utils.AWS_COMPAT_SERVICES])
 
-    @patch.object(utils, 'service_pause')
-    @patch.object(utils, 'service_resume')
-    @patch.object(utils, 'config')
-    @patch.object(utils, 'filter_installed_packages')
-    def test_disable_aws_compat_services_false(self, filter_installed_packages,
-                                               config, s_resume, s_pause):
+    @patch('charmhelpers.fetch.filter_installed_packages')
+    def test_disable_aws_compat_services_false(
+            self, filter_installed_packages):
         filter_installed_packages.return_value = []
-        config.return_value = False
+        self.test_config.set('disable-aws-compat', False)
         utils.update_aws_compat_services()
 
-        s_resume.assert_has_calls([call(s) for s in utils.AWS_COMPAT_SERVICES])
-        s_pause.assert_not_called()
+        self.service_resume.assert_has_calls(
+            [call(s) for s in utils.AWS_COMPAT_SERVICES])
+        self.service_pause.assert_not_called()
 
     @patch('subprocess.check_output')
     def test_get_cell_uuid(self, mock_check_call):
@@ -1249,7 +1274,7 @@ class NovaCCUtilsTests(CharmTestCase):
             ['nova-manage', 'cell_v2', 'discover_hosts', '--cell_uuid',
              'c83121db-f1c7-464a-b657-38c28fac84c6', '--verbose'])
 
-    @patch('nova_cc_context.NovaCellV2SharedDBContext')
+    @patch('hooks.nova_cc_context.NovaCellV2SharedDBContext')
     @patch('charmhelpers.contrib.openstack.context.AMQPContext')
     def test_is_cellv2_init_ready_mitaka(self, amqp, shared_db):
         self.os_release.return_value = 'mitaka'
@@ -1259,7 +1284,7 @@ class NovaCCUtilsTests(CharmTestCase):
         shared_db.assert_called_once()
         self.log.assert_called_once()
 
-    @patch('nova_cc_context.NovaCellV2SharedDBContext')
+    @patch('hooks.nova_cc_context.NovaCellV2SharedDBContext')
     @patch('charmhelpers.contrib.openstack.context.AMQPContext')
     def test_is_cellv2_init_ready_ocata(self, amqp, shared_db):
         self.os_release.return_value = 'ocata'
@@ -1320,8 +1345,8 @@ class NovaCCUtilsTests(CharmTestCase):
         for c in expected_calls:
             self.assertTrue(c in m.mock_calls)
 
-    @patch.object(utils.context, 'SharedDBContext')
-    @patch.object(utils, 'relation_id')
+    @patch.object(utils.ch_context, 'SharedDBContext')
+    @patch('charmhelpers.core.hookenv.relation_id')
     def test_get_cell_db_context(self, mock_relation_id, mock_SharedDBContext):
         mock_relation_id.return_value = 'dbid'
         utils.get_cell_db_context('mysql-cell2')
@@ -1333,8 +1358,8 @@ class NovaCCUtilsTests(CharmTestCase):
             relation_name='shared-db-cell',
             service_or_unit='mysql-cell2')
 
-    @patch.object(utils.context, 'AMQPContext')
-    @patch.object(utils, 'relation_id')
+    @patch.object(utils.ch_context, 'AMQPContext')
+    @patch('charmhelpers.core.hookenv.relation_id')
     def test_get_cell_amqp_context(self, mock_relation_id, mock_AMQPContext):
         mock_relation_id.return_value = 'amqpid'
         utils.get_cell_amqp_context('rabbitmq-server-cell2')
@@ -1375,10 +1400,12 @@ class NovaCCUtilsTests(CharmTestCase):
     @patch.object(utils, 'get_cell_amqp_context')
     @patch.object(utils, 'get_sql_uri')
     @patch.object(utils.subprocess, 'check_output')
-    @patch.object(utils, 'service_restart')
-    def test_update_child_cell(self, mock_service_restart, mock_check_output,
-                               mock_get_sql_uri, mock_get_cell_amqp_context,
-                               mock_get_cell_db_context, mock_get_cell_details,
+    def test_update_child_cell(self,
+                               mock_check_output,
+                               mock_get_sql_uri,
+                               mock_get_cell_amqp_context,
+                               mock_get_cell_db_context,
+                               mock_get_cell_details,
                                mock_is_db_initialised):
         mock_is_db_initialised.return_value = True
         mock_get_cell_details.return_value = {'cell1': 'cell1uuid'}
@@ -1396,25 +1423,22 @@ class NovaCCUtilsTests(CharmTestCase):
             '--name', 'cell2',
             '--transport-url', 'amqp-uri',
             '--database_connection', 'db-uri'])
-        mock_service_restart.assert_called_once_with('nova-scheduler')
+        self.service_restart.assert_called_once_with('nova-scheduler')
 
     @patch.object(utils, 'is_db_initialised')
     @patch.object(utils.subprocess, 'check_output')
-    @patch.object(utils, 'service_restart')
-    def test_update_child_cell_no_local_db(self, mock_service_restart,
+    def test_update_child_cell_no_local_db(self,
                                            mock_check_output,
                                            mock_is_db_initialised):
         mock_is_db_initialised.return_value = False
         utils.update_child_cell('cell2', 'mysql-cell2', 'amqp-cell2')
         self.assertFalse(mock_check_output.called)
-        self.assertFalse(mock_service_restart.called)
+        self.assertFalse(self.service_restart.called)
 
     @patch.object(utils, 'get_cell_details')
     @patch.object(utils, 'is_db_initialised')
     @patch.object(utils.subprocess, 'check_output')
-    @patch.object(utils, 'service_restart')
     def test_update_child_cell_api_cell_not_registered(self,
-                                                       mock_service_restart,
                                                        mock_check_output,
                                                        mock_is_db_initialised,
                                                        mock_get_cell_details):
@@ -1423,36 +1447,32 @@ class NovaCCUtilsTests(CharmTestCase):
         utils.update_child_cell('cell2', 'mysql-cell2', 'amqp-cell2')
         mock_get_cell_details.assert_called_once_with()
         self.assertFalse(mock_check_output.called)
-        self.assertFalse(mock_service_restart.called)
+        self.assertFalse(self.service_restart.called)
 
     @patch.object(utils.subprocess, 'check_output')
-    @patch.object(utils, 'service_restart')
     @patch.object(utils, 'get_cell_details')
     @patch.object(utils, 'is_db_initialised')
     @patch.object(utils, 'get_cell_db_context')
     def test_update_child_cell_no_cell_db(self, mock_get_cell_db_context,
                                           mock_is_db_initialised,
                                           mock_get_cell_details,
-                                          mock_service_restart,
                                           mock_check_output):
         mock_is_db_initialised.return_value = True
         mock_get_cell_details.return_value = {'cell1': 'uuid4cell1'}
         mock_get_cell_db_context.return_value = {}
         utils.update_child_cell('cell2', 'mysql-cell2', 'amqp-cell2')
         self.assertFalse(mock_check_output.called)
-        self.assertFalse(mock_service_restart.called)
+        self.assertFalse(self.service_restart.called)
 
     @patch.object(utils, 'get_cell_amqp_context')
     @patch.object(utils, 'get_sql_uri')
     @patch.object(utils.subprocess, 'check_output')
-    @patch.object(utils, 'service_restart')
     @patch.object(utils, 'get_cell_details')
     @patch.object(utils, 'is_db_initialised')
     @patch.object(utils, 'get_cell_db_context')
     def test_update_child_cell_no_cell_amqp(self, mock_get_cell_db_context,
                                             mock_is_db_initialised,
                                             mock_get_cell_details,
-                                            mock_service_restart,
                                             mock_check_output,
                                             mock_get_sql_uri,
                                             mock_get_cell_amqp_context):
@@ -1462,4 +1482,4 @@ class NovaCCUtilsTests(CharmTestCase):
         mock_get_cell_amqp_context.return_value = {}
         utils.update_child_cell('cell2', 'mysql-cell2', 'amqp-cell2')
         self.assertFalse(mock_check_output.called)
-        self.assertFalse(mock_service_restart.called)
+        self.assertFalse(self.service_restart.called)
diff --git a/unit_tests/test_utils.py b/unit_tests/test_utils.py
index 799c9ce4..2649f841 100644
--- a/unit_tests/test_utils.py
+++ b/unit_tests/test_utils.py
@@ -12,6 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import io
+import importlib
 import os
 import logging
 import unittest
@@ -51,7 +53,7 @@ def get_default_config():
     '''
     default_config = {}
     config = load_config()
-    for k, v in config.iteritems():
+    for k, v in config.items():
         if 'default' in v:
             default_config[k] = v['default']
         else:
@@ -71,15 +73,26 @@ class CharmTestCase(unittest.TestCase):
         self.patch_all()
 
     def patch(self, method):
-        self.originals[method] = getattr(self.obj, method)
-        _m = patch.object(self.obj, method)
+        if "." in method:
+            _mod = importlib.import_module('.'.join(method.split('.')[0:-1]))
+            _m = patch(method)
+            name = method.split('.')[-1]
+            self.originals[method] = getattr(_mod, name)
+        else:
+            self.originals[method] = getattr(self.obj, method)
+            _m = patch.object(self.obj, method)
+            name = method
         mock = _m.start()
         self.addCleanup(_m.stop)
         return mock
 
     def patch_all(self):
         for method in self.patches:
-            setattr(self, method, self.patch(method))
+            mock = self.patch(method)
+            if "." in method:
+                setattr(self, method.split('.')[-1], mock)
+            else:
+                setattr(self, method, mock)
 
 
 class TestConfig(object):
@@ -103,6 +116,10 @@ class TestConfig(object):
             raise KeyError
         self.config[attr] = value
 
+    def update(self, d):
+        for k, v in d.items():
+            self.set(k, v)
+
 
 class TestRelation(object):
 
@@ -127,12 +144,12 @@ def patch_open():
 
     Yields the mock for "open" and "file", respectively.'''
     mock_open = MagicMock(spec=open)
-    mock_file = MagicMock(spec=file)
+    mock_file = MagicMock(spec=io.FileIO)
 
     @contextmanager
     def stub_open(*args, **kwargs):
         mock_open(*args, **kwargs)
         yield mock_file
 
-    with patch('__builtin__.open', stub_open):
+    with patch('builtins.open', stub_open):
         yield mock_open, mock_file