From b106b41843d83abb4df5467d05ae095f1df77b5a Mon Sep 17 00:00:00 2001
From: Corey Bryant <corey.bryant@canonical.com>
Date: Thu, 30 Jun 2016 12:11:55 +0000
Subject: [PATCH] Sync charm helpers and drop icehouse default

The following updates are picked up in the charm-helpers sync:
  * Drop icehouse from deploy from source defaults
  * Partial support for kilo-eol tag for deploy from source defaults
  * Set upper constraints for openstack when deploying from source
  * Improve systemd unit file generation for deploy from source

Icehouse is dropped from the openstack-origin-git defaults because
the default git repos don't work as-is for deploying icehouse so
let's not allow them to be deployed via default openstack-origin-git
values. The reason the default git repos don't work is because icehouse
didn't have the concept of upper-constraints so the depenencies aren't
capped, which causes problems.

Change-Id: Ia2e00b4a7a7076d4edcf143e194747ee2b1fdda8
---
 charm-helpers-hooks.yaml                      |  2 +-
 config.yaml                                   |  1 -
 .../charmhelpers/contrib/hahelpers/cluster.py |  6 +-
 .../contrib/openstack/amulet/deployment.py    |  2 +-
 .../contrib/openstack/ha/utils.py             | 21 +-----
 hooks/charmhelpers/contrib/openstack/utils.py | 66 ++++++++++++++++---
 hooks/charmhelpers/contrib/python/packages.py |  6 +-
 hooks/charmhelpers/core/hookenv.py            |  2 +-
 .../contrib/openstack/amulet/deployment.py    |  2 +-
 9 files changed, 71 insertions(+), 37 deletions(-)

diff --git a/charm-helpers-hooks.yaml b/charm-helpers-hooks.yaml
index 19d9c5b0..f3313a55 100644
--- a/charm-helpers-hooks.yaml
+++ b/charm-helpers-hooks.yaml
@@ -14,4 +14,4 @@ include:
     - contrib.network.ip
     - contrib.python.packages
     - contrib.charmsupport
-    - contrib.hardening|inc=*
\ No newline at end of file
+    - contrib.hardening|inc=*
diff --git a/config.yaml b/config.yaml
index 5b069221..cb0a5032 100644
--- a/config.yaml
+++ b/config.yaml
@@ -50,7 +50,6 @@ options:
 
       The default Openstack release name may be one of the following, where
       the corresponding OpenStack github branch will be used:
-        * icehouse
         * kilo
         * liberty
         * mitaka
diff --git a/hooks/charmhelpers/contrib/hahelpers/cluster.py b/hooks/charmhelpers/contrib/hahelpers/cluster.py
index 90e437aa..92325a96 100644
--- a/hooks/charmhelpers/contrib/hahelpers/cluster.py
+++ b/hooks/charmhelpers/contrib/hahelpers/cluster.py
@@ -280,14 +280,14 @@ def get_hacluster_config(exclude_keys=None):
     for initiating a relation to hacluster:
 
         ha-bindiface, ha-mcastport, vip, os-internal-hostname,
-        os-admin-hostname, os-public-hostname, os-access-hostname
+        os-admin-hostname, os-public-hostname
 
     param: exclude_keys: list of setting key(s) to be excluded.
     returns: dict: A dict containing settings keyed by setting name.
     raises: HAIncompleteConfig if settings are missing or incorrect.
     '''
     settings = ['ha-bindiface', 'ha-mcastport', 'vip', 'os-internal-hostname',
-                'os-admin-hostname', 'os-public-hostname', 'os-access-hostname']
+                'os-admin-hostname', 'os-public-hostname']
     conf = {}
     for setting in settings:
         if exclude_keys and setting in exclude_keys:
@@ -324,7 +324,7 @@ def valid_hacluster_config():
     # If dns-ha then one of os-*-hostname must be set
     if dns:
         dns_settings = ['os-internal-hostname', 'os-admin-hostname',
-                        'os-public-hostname', 'os-access-hostname']
+                        'os-public-hostname']
         # At this point it is unknown if one or all of the possible
         # network spaces are in HA. Validate at least one is set which is
         # the minimum required.
diff --git a/hooks/charmhelpers/contrib/openstack/amulet/deployment.py b/hooks/charmhelpers/contrib/openstack/amulet/deployment.py
index 6b917d0c..797b7807 100644
--- a/hooks/charmhelpers/contrib/openstack/amulet/deployment.py
+++ b/hooks/charmhelpers/contrib/openstack/amulet/deployment.py
@@ -73,7 +73,7 @@ class OpenStackAmuletDeployment(AmuletDeployment):
         base_charms = {
             'mysql': ['precise', 'trusty'],
             'mongodb': ['precise', 'trusty'],
-            'nrpe': ['precise', 'trusty'],
+            'nrpe': ['trusty'],
         }
 
         for svc in other_services:
diff --git a/hooks/charmhelpers/contrib/openstack/ha/utils.py b/hooks/charmhelpers/contrib/openstack/ha/utils.py
index 2a8a1291..34064237 100644
--- a/hooks/charmhelpers/contrib/openstack/ha/utils.py
+++ b/hooks/charmhelpers/contrib/openstack/ha/utils.py
@@ -36,10 +36,6 @@ from charmhelpers.core.hookenv import (
     DEBUG,
 )
 
-from charmhelpers.core.host import (
-    lsb_release
-)
-
 from charmhelpers.contrib.openstack.ip import (
     resolve_address,
 )
@@ -67,11 +63,8 @@ def update_dns_ha_resource_params(resources, resource_params,
                     DNS HA
     """
 
-    # Validate the charm environment for DNS HA
-    assert_charm_supports_dns_ha()
-
     settings = ['os-admin-hostname', 'os-internal-hostname',
-                'os-public-hostname', 'os-access-hostname']
+                'os-public-hostname']
 
     # Check which DNS settings are set and update dictionaries
     hostname_group = []
@@ -116,15 +109,3 @@ def update_dns_ha_resource_params(resources, resource_params,
         msg = 'DNS HA: Hostname group has no members.'
         status_set('blocked', msg)
         raise DNSHAException(msg)
-
-
-def assert_charm_supports_dns_ha():
-    """Validate prerequisites for DNS HA
-    The MAAS client is only available on Xenial or greater
-    """
-    if lsb_release().get('DISTRIB_RELEASE') < '16.04':
-        msg = ('DNS HA is only supported on 16.04 and greater '
-               'versions of Ubuntu.')
-        status_set('blocked', msg)
-        raise DNSHAException(msg)
-    return True
diff --git a/hooks/charmhelpers/contrib/openstack/utils.py b/hooks/charmhelpers/contrib/openstack/utils.py
index 8da5c5ed..6707d492 100644
--- a/hooks/charmhelpers/contrib/openstack/utils.py
+++ b/hooks/charmhelpers/contrib/openstack/utils.py
@@ -222,7 +222,6 @@ GIT_DEFAULT_REPOS = {
 }
 
 GIT_DEFAULT_BRANCHES = {
-    'icehouse': 'icehouse-eol',
     'kilo': 'stable/kilo',
     'liberty': 'stable/liberty',
     'mitaka': 'stable/mitaka',
@@ -743,12 +742,18 @@ def git_default_repos(projects_yaml):
             }
             repos = [repo]
 
+            # NOTE(coreycb): This is a temp work-around until the requirements
+            # repo moves from stable/kilo branch to kilo-eol tag. The core
+            # repos have already done this.
+            if default == 'kilo':
+                branch = 'kilo-eol'
+
             # neutron-* and nova-* charms require some additional repos
             if service in ['neutron-api', 'neutron-gateway',
                            'neutron-openvswitch']:
                 core_project = 'neutron'
                 for project in ['neutron-fwaas', 'neutron-lbaas',
-                                'neutron-vpnaas']:
+                                'neutron-vpnaas', 'nova']:
                     repo = {
                         'name': project,
                         'repository': GIT_DEFAULT_REPOS[project],
@@ -837,6 +842,7 @@ def git_clone_and_install(projects_yaml, core_project):
         pip_install(p, upgrade=True, proxy=http_proxy,
                     venv=os.path.join(parent_dir, 'venv'))
 
+    constraints = None
     for p in projects['repositories']:
         repo = p['repository']
         branch = p['branch']
@@ -848,10 +854,15 @@ def git_clone_and_install(projects_yaml, core_project):
                                                      parent_dir, http_proxy,
                                                      update_requirements=False)
             requirements_dir = repo_dir
+            constraints = os.path.join(repo_dir, "upper-constraints.txt")
+            # upper-constraints didn't exist until after icehouse
+            if not os.path.isfile(constraints):
+                constraints = None
         else:
             repo_dir = _git_clone_and_install_single(repo, branch, depth,
                                                      parent_dir, http_proxy,
-                                                     update_requirements=True)
+                                                     update_requirements=True,
+                                                     constraints=constraints)
 
     os.environ = old_environ
 
@@ -883,7 +894,7 @@ def _git_ensure_key_exists(key, keys):
 
 
 def _git_clone_and_install_single(repo, branch, depth, parent_dir, http_proxy,
-                                  update_requirements):
+                                  update_requirements, constraints=None):
     """
     Clone and install a single git repository.
     """
@@ -906,9 +917,10 @@ def _git_clone_and_install_single(repo, branch, depth, parent_dir, http_proxy,
 
     juju_log('Installing git repo from dir: {}'.format(repo_dir))
     if http_proxy:
-        pip_install(repo_dir, proxy=http_proxy, venv=venv)
+        pip_install(repo_dir, proxy=http_proxy, venv=venv,
+                    constraints=constraints)
     else:
-        pip_install(repo_dir, venv=venv)
+        pip_install(repo_dir, venv=venv, constraints=constraints)
 
     return repo_dir
 
@@ -988,6 +1000,7 @@ def git_generate_systemd_init_files(templates_dir):
     script generation, which is used by the OpenStack packages.
     """
     for f in os.listdir(templates_dir):
+        # Create the init script and systemd unit file from the template
         if f.endswith(".init.in"):
             init_in_file = f
             init_file = f[:-8]
@@ -1013,10 +1026,47 @@ def git_generate_systemd_init_files(templates_dir):
                 os.remove(init_dest)
             if os.path.exists(service_dest):
                 os.remove(service_dest)
-            shutil.move(init_source, init_dest)
-            shutil.move(service_source, service_dest)
+            shutil.copyfile(init_source, init_dest)
+            shutil.copyfile(service_source, service_dest)
             os.chmod(init_dest, 0o755)
 
+    for f in os.listdir(templates_dir):
+        # If there's a service.in file, use it instead of the generated one
+        if f.endswith(".service.in"):
+            service_in_file = f
+            service_file = f[:-3]
+
+            service_in_source = os.path.join(templates_dir, service_in_file)
+            service_source = os.path.join(templates_dir, service_file)
+            service_dest = os.path.join('/lib/systemd/system', service_file)
+
+            shutil.copyfile(service_in_source, service_source)
+
+            if os.path.exists(service_dest):
+                os.remove(service_dest)
+            shutil.copyfile(service_source, service_dest)
+
+    for f in os.listdir(templates_dir):
+        # Generate the systemd unit if there's no existing .service.in
+        if f.endswith(".init.in"):
+            init_in_file = f
+            init_file = f[:-8]
+            service_in_file = "{}.service.in".format(init_file)
+            service_file = "{}.service".format(init_file)
+
+            init_in_source = os.path.join(templates_dir, init_in_file)
+            service_in_source = os.path.join(templates_dir, service_in_file)
+            service_source = os.path.join(templates_dir, service_file)
+            service_dest = os.path.join('/lib/systemd/system', service_file)
+
+            if not os.path.exists(service_in_source):
+                cmd = ['pkgos-gen-systemd-unit', init_in_source]
+                subprocess.check_call(cmd)
+
+                if os.path.exists(service_dest):
+                    os.remove(service_dest)
+                shutil.copyfile(service_source, service_dest)
+
 
 def os_workload_status(configs, required_interfaces, charm_func=None):
     """
diff --git a/hooks/charmhelpers/contrib/python/packages.py b/hooks/charmhelpers/contrib/python/packages.py
index a2411c37..790341ac 100644
--- a/hooks/charmhelpers/contrib/python/packages.py
+++ b/hooks/charmhelpers/contrib/python/packages.py
@@ -80,7 +80,8 @@ def pip_install_requirements(requirements, constraints=None, **options):
     pip_execute(command)
 
 
-def pip_install(package, fatal=False, upgrade=False, venv=None, **options):
+def pip_install(package, fatal=False, upgrade=False, venv=None,
+                constraints=None, **options):
     """Install a python package"""
     if venv:
         venv_python = os.path.join(venv, 'bin/pip')
@@ -95,6 +96,9 @@ def pip_install(package, fatal=False, upgrade=False, venv=None, **options):
     if upgrade:
         command.append('--upgrade')
 
+    if constraints:
+        command.extend(['-c', constraints])
+
     if isinstance(package, list):
         command.extend(package)
     else:
diff --git a/hooks/charmhelpers/core/hookenv.py b/hooks/charmhelpers/core/hookenv.py
index db117f9e..01321296 100644
--- a/hooks/charmhelpers/core/hookenv.py
+++ b/hooks/charmhelpers/core/hookenv.py
@@ -1006,4 +1006,4 @@ def network_get_primary_address(binding):
     :raise: NotImplementedError if run on Juju < 2.0
     '''
     cmd = ['network-get', '--primary-address', binding]
-    return subprocess.check_output(cmd).decode('UTF-8').strip()
+    return subprocess.check_output(cmd).strip()
diff --git a/tests/charmhelpers/contrib/openstack/amulet/deployment.py b/tests/charmhelpers/contrib/openstack/amulet/deployment.py
index 6b917d0c..797b7807 100644
--- a/tests/charmhelpers/contrib/openstack/amulet/deployment.py
+++ b/tests/charmhelpers/contrib/openstack/amulet/deployment.py
@@ -73,7 +73,7 @@ class OpenStackAmuletDeployment(AmuletDeployment):
         base_charms = {
             'mysql': ['precise', 'trusty'],
             'mongodb': ['precise', 'trusty'],
-            'nrpe': ['precise', 'trusty'],
+            'nrpe': ['trusty'],
         }
 
         for svc in other_services: