From 9a3334bf0fc3cf1effdfa7ae45e40edb1ac7f913 Mon Sep 17 00:00:00 2001
From: Corey Bryant <corey.bryant@canonical.com>
Date: Fri, 10 Jun 2022 20:14:46 +0000
Subject: [PATCH] Add Kinetic and Zed support

* sync charm-helpers to classic charms
* change openstack-origin/source default to zed
* align testing with zed
* add new zed bundles
* add zed bundles to tests.yaml
* add zed tests to osci.yaml and .zuul.yaml
* update build-on and run-on bases
* add bindep.txt for py310
* sync tox.ini and requirements.txt for ruamel
* use charmcraft_channel 2.0/stable
* drop reactive plugin overrides
* move interface/layer env vars to charmcraft.yaml

Change-Id: I49cade430fa875816b785d72252bd3d34cb787df
---
 .zuul.yaml                                    |   2 +-
 bindep.txt                                    |   3 +
 charmcraft.yaml                               |   5 +-
 hooks/charmhelpers/contrib/network/ip.py      |   2 +-
 .../charmhelpers/contrib/openstack/context.py |  65 +++----
 hooks/charmhelpers/contrib/openstack/utils.py |   6 +-
 hooks/charmhelpers/core/host.py               |   6 +-
 .../charmhelpers/core/host_factory/ubuntu.py  |   1 +
 hooks/charmhelpers/core/services/base.py      |   3 +-
 hooks/charmhelpers/fetch/archiveurl.py        |  31 ++-
 hooks/charmhelpers/fetch/ubuntu.py            |  10 +
 metadata.yaml                                 |   1 -
 osci.yaml                                     |   5 +-
 requirements.txt                              |   7 +-
 test-requirements.txt                         |  16 +-
 tests/bundles/impish-xena.yaml                | 180 ------------------
 tests/bundles/jammy-yoga.yaml                 |  14 +-
 .../{focal-yoga.yaml => jammy-zed.yaml}       |  18 +-
 .../{focal-xena.yaml => kinetic-zed.yaml}     |  18 +-
 tests/tests.yaml                              |  11 +-
 tox.ini                                       |  33 +---
 21 files changed, 129 insertions(+), 308 deletions(-)
 create mode 100644 bindep.txt
 delete mode 100644 tests/bundles/impish-xena.yaml
 rename tests/bundles/{focal-yoga.yaml => jammy-zed.yaml} (92%)
 rename tests/bundles/{focal-xena.yaml => kinetic-zed.yaml} (92%)

diff --git a/.zuul.yaml b/.zuul.yaml
index 7ffc71c..23bf5f6 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -1,4 +1,4 @@
 - project:
     templates:
-      - openstack-python3-charm-yoga-jobs
+      - openstack-python3-charm-zed-jobs
       - openstack-cover-jobs
diff --git a/bindep.txt b/bindep.txt
new file mode 100644
index 0000000..bdbe8d5
--- /dev/null
+++ b/bindep.txt
@@ -0,0 +1,3 @@
+libffi-dev [platform:dpkg]
+libxml2-dev [platform:dpkg]
+libxslt1-dev [platform:dpkg]
diff --git a/charmcraft.yaml b/charmcraft.yaml
index b1e7f9f..35b6d04 100644
--- a/charmcraft.yaml
+++ b/charmcraft.yaml
@@ -21,13 +21,10 @@ parts:
 bases:
   - build-on:
       - name: ubuntu
-        channel: "20.04"
+        channel: "22.04"
         architectures:
           - amd64
     run-on:
-      - name: ubuntu
-        channel: "20.04"
-        architectures: [amd64, s390x, ppc64el, arm64]
       - name: ubuntu
         channel: "22.04"
         architectures: [amd64, s390x, ppc64el, arm64]
diff --git a/hooks/charmhelpers/contrib/network/ip.py b/hooks/charmhelpers/contrib/network/ip.py
index de56584..f8edf37 100644
--- a/hooks/charmhelpers/contrib/network/ip.py
+++ b/hooks/charmhelpers/contrib/network/ip.py
@@ -467,7 +467,7 @@ def ns_query(address):
 
     try:
         answers = dns.resolver.query(address, rtype)
-    except dns.resolver.NXDOMAIN:
+    except (dns.resolver.NXDOMAIN, dns.resolver.NoNameservers):
         return None
 
     if answers:
diff --git a/hooks/charmhelpers/contrib/openstack/context.py b/hooks/charmhelpers/contrib/openstack/context.py
index 32c69ff..970a657 100644
--- a/hooks/charmhelpers/contrib/openstack/context.py
+++ b/hooks/charmhelpers/contrib/openstack/context.py
@@ -2560,14 +2560,18 @@ class OVSDPDKDeviceContext(OSContextGenerator):
         :rtype: List[int]
         """
         cores = []
-        ranges = cpulist.split(',')
-        for cpu_range in ranges:
-            if "-" in cpu_range:
-                cpu_min_max = cpu_range.split('-')
-                cores += range(int(cpu_min_max[0]),
-                               int(cpu_min_max[1]) + 1)
-            else:
-                cores.append(int(cpu_range))
+        if cpulist and re.match(r"^[0-9,\-^]*$", cpulist):
+            ranges = cpulist.split(',')
+            for cpu_range in ranges:
+                if "-" in cpu_range:
+                    cpu_min_max = cpu_range.split('-')
+                    cores += range(int(cpu_min_max[0]),
+                                   int(cpu_min_max[1]) + 1)
+                elif "^" in cpu_range:
+                    cpu_rm = cpu_range.split('^')
+                    cores.remove(int(cpu_rm[1]))
+                else:
+                    cores.append(int(cpu_range))
         return cores
 
     def _numa_node_cores(self):
@@ -2586,36 +2590,32 @@ class OVSDPDKDeviceContext(OSContextGenerator):
 
     def cpu_mask(self):
         """Get hex formatted CPU mask
-
         The mask is based on using the first config:dpdk-socket-cores
         cores of each NUMA node in the unit.
         :returns: hex formatted CPU mask
         :rtype: str
         """
-        return self.cpu_masks()['dpdk_lcore_mask']
-
-    def cpu_masks(self):
-        """Get hex formatted CPU masks
-
-        The mask is based on using the first config:dpdk-socket-cores
-        cores of each NUMA node in the unit, followed by the
-        next config:pmd-socket-cores
-
-        :returns: Dict of hex formatted CPU masks
-        :rtype: Dict[str, str]
-        """
-        num_lcores = config('dpdk-socket-cores')
-        pmd_cores = config('pmd-socket-cores')
-        lcore_mask = 0
-        pmd_mask = 0
+        num_cores = config('dpdk-socket-cores')
+        mask = 0
         for cores in self._numa_node_cores().values():
-            for core in cores[:num_lcores]:
-                lcore_mask = lcore_mask | 1 << core
-            for core in cores[num_lcores:][:pmd_cores]:
-                pmd_mask = pmd_mask | 1 << core
-        return {
-            'pmd_cpu_mask': format(pmd_mask, '#04x'),
-            'dpdk_lcore_mask': format(lcore_mask, '#04x')}
+            for core in cores[:num_cores]:
+                mask = mask | 1 << core
+        return format(mask, '#04x')
+
+    @classmethod
+    def pmd_cpu_mask(cls):
+        """Get hex formatted pmd CPU mask
+
+        The mask is based on config:pmd-cpu-set.
+        :returns: hex formatted CPU mask
+        :rtype: str
+        """
+        mask = 0
+        cpu_list = cls._parse_cpu_list(config('pmd-cpu-set'))
+        if cpu_list:
+            for core in cpu_list:
+                mask = mask | 1 << core
+        return format(mask, '#x')
 
     def socket_memory(self):
         """Formatted list of socket memory configuration per socket.
@@ -2694,6 +2694,7 @@ class OVSDPDKDeviceContext(OSContextGenerator):
             ctxt['device_whitelist'] = self.device_whitelist()
             ctxt['socket_memory'] = self.socket_memory()
             ctxt['cpu_mask'] = self.cpu_mask()
+            ctxt['pmd_cpu_mask'] = self.pmd_cpu_mask()
         return ctxt
 
 
diff --git a/hooks/charmhelpers/contrib/openstack/utils.py b/hooks/charmhelpers/contrib/openstack/utils.py
index c8747c1..1fa2814 100644
--- a/hooks/charmhelpers/contrib/openstack/utils.py
+++ b/hooks/charmhelpers/contrib/openstack/utils.py
@@ -158,6 +158,7 @@ OPENSTACK_CODENAMES = OrderedDict([
     ('2021.1', 'wallaby'),
     ('2021.2', 'xena'),
     ('2022.1', 'yoga'),
+    ('2022.2', 'zed'),
 ])
 
 # The ugly duckling - must list releases oldest to newest
@@ -400,13 +401,16 @@ def get_os_codename_version(vers):
         error_out(e)
 
 
-def get_os_version_codename(codename, version_map=OPENSTACK_CODENAMES):
+def get_os_version_codename(codename, version_map=OPENSTACK_CODENAMES,
+                            raise_exception=False):
     '''Determine OpenStack version number from codename.'''
     for k, v in version_map.items():
         if v == codename:
             return k
     e = 'Could not derive OpenStack version for '\
         'codename: %s' % codename
+    if raise_exception:
+        raise ValueError(str(e))
     error_out(e)
 
 
diff --git a/hooks/charmhelpers/core/host.py b/hooks/charmhelpers/core/host.py
index ad2cab4..ef6c8ec 100644
--- a/hooks/charmhelpers/core/host.py
+++ b/hooks/charmhelpers/core/host.py
@@ -277,7 +277,7 @@ def service_resume(service_name, init_dir="/etc/init",
     return started
 
 
-def service(action, service_name, **kwargs):
+def service(action, service_name=None, **kwargs):
     """Control a system service.
 
     :param action: the action to take on the service
@@ -286,7 +286,9 @@ def service(action, service_name, **kwargs):
                     the form of key=value.
     """
     if init_is_systemd(service_name=service_name):
-        cmd = ['systemctl', action, service_name]
+        cmd = ['systemctl', action]
+        if service_name is not None:
+            cmd.append(service_name)
     else:
         cmd = ['service', service_name, action]
         for key, value in kwargs.items():
diff --git a/hooks/charmhelpers/core/host_factory/ubuntu.py b/hooks/charmhelpers/core/host_factory/ubuntu.py
index 0906c5c..cc2d89f 100644
--- a/hooks/charmhelpers/core/host_factory/ubuntu.py
+++ b/hooks/charmhelpers/core/host_factory/ubuntu.py
@@ -30,6 +30,7 @@ UBUNTU_RELEASES = (
     'hirsute',
     'impish',
     'jammy',
+    'kinetic',
 )
 
 
diff --git a/hooks/charmhelpers/core/services/base.py b/hooks/charmhelpers/core/services/base.py
index 7c37c65..8d217b5 100644
--- a/hooks/charmhelpers/core/services/base.py
+++ b/hooks/charmhelpers/core/services/base.py
@@ -15,7 +15,8 @@
 import os
 import json
 import inspect
-from collections import Iterable, OrderedDict
+from collections import OrderedDict
+from collections.abc import Iterable
 
 from charmhelpers.core import host
 from charmhelpers.core import hookenv
diff --git a/hooks/charmhelpers/fetch/archiveurl.py b/hooks/charmhelpers/fetch/archiveurl.py
index 2cb2e88..0e35c90 100644
--- a/hooks/charmhelpers/fetch/archiveurl.py
+++ b/hooks/charmhelpers/fetch/archiveurl.py
@@ -12,6 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import contextlib
 import os
 import hashlib
 import re
@@ -24,11 +25,15 @@ from charmhelpers.payload.archive import (
     get_archive_handler,
     extract,
 )
+from charmhelpers.core.hookenv import (
+    env_proxy_settings,
+)
 from charmhelpers.core.host import mkdir, check_hash
 
 from urllib.request import (
     build_opener, install_opener, urlopen, urlretrieve,
     HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler,
+    ProxyHandler
 )
 from urllib.parse import urlparse, urlunparse, parse_qs
 from urllib.error import URLError
@@ -50,6 +55,20 @@ def splitpasswd(user):
     return user, None
 
 
+@contextlib.contextmanager
+def proxy_env():
+    """
+    Creates a context which temporarily modifies the proxy settings in os.environ.
+    """
+    restore = {**os.environ}  # Copy the current os.environ
+    juju_proxies = env_proxy_settings() or {}
+    os.environ.update(**juju_proxies)  # Insert or Update the os.environ
+    yield os.environ
+    for key in juju_proxies:
+        del os.environ[key]  # remove any keys which were added or updated
+    os.environ.update(**restore)  # restore any original values
+
+
 class ArchiveUrlFetchHandler(BaseFetchHandler):
     """
     Handler to download archive files from arbitrary URLs.
@@ -80,6 +99,7 @@ class ArchiveUrlFetchHandler(BaseFetchHandler):
         # propagate all exceptions
         # URLError, OSError, etc
         proto, netloc, path, params, query, fragment = urlparse(source)
+        handlers = []
         if proto in ('http', 'https'):
             auth, barehost = splituser(netloc)
             if auth is not None:
@@ -89,10 +109,13 @@ class ArchiveUrlFetchHandler(BaseFetchHandler):
                 # Realm is set to None in add_password to force the username and password
                 # to be used whatever the realm
                 passman.add_password(None, source, username, password)
-                authhandler = HTTPBasicAuthHandler(passman)
-                opener = build_opener(authhandler)
-                install_opener(opener)
-        response = urlopen(source)
+                handlers.append(HTTPBasicAuthHandler(passman))
+
+        with proxy_env():
+            handlers.append(ProxyHandler())
+            opener = build_opener(*handlers)
+            install_opener(opener)
+            response = urlopen(source)
         try:
             with open(dest, 'wb') as dest_file:
                 dest_file.write(response.read())
diff --git a/hooks/charmhelpers/fetch/ubuntu.py b/hooks/charmhelpers/fetch/ubuntu.py
index e6f8a0a..93b9276 100644
--- a/hooks/charmhelpers/fetch/ubuntu.py
+++ b/hooks/charmhelpers/fetch/ubuntu.py
@@ -222,6 +222,14 @@ CLOUD_ARCHIVE_POCKETS = {
     'yoga/proposed': 'focal-proposed/yoga',
     'focal-yoga/proposed': 'focal-proposed/yoga',
     'focal-proposed/yoga': 'focal-proposed/yoga',
+    # Zed
+    'zed': 'jammy-updates/zed',
+    'jammy-zed': 'jammy-updates/zed',
+    'jammy-zed/updates': 'jammy-updates/zed',
+    'jammy-updates/zed': 'jammy-updates/zed',
+    'zed/proposed': 'jammy-proposed/zed',
+    'jammy-zed/proposed': 'jammy-proposed/zed',
+    'jammy-proposed/zed': 'jammy-proposed/zed',
 }
 
 
@@ -248,6 +256,7 @@ OPENSTACK_RELEASES = (
     'wallaby',
     'xena',
     'yoga',
+    'zed',
 )
 
 
@@ -274,6 +283,7 @@ UBUNTU_OPENSTACK_RELEASE = OrderedDict([
     ('hirsute', 'wallaby'),
     ('impish', 'xena'),
     ('jammy', 'yoga'),
+    ('kinetic', 'zed'),
 ])
 
 
diff --git a/metadata.yaml b/metadata.yaml
index d733b74..fe298b4 100644
--- a/metadata.yaml
+++ b/metadata.yaml
@@ -8,7 +8,6 @@ description: |
 tags:
 - miscellaneous
 series:
-- focal
 - jammy
 subordinate: true
 provides:
diff --git a/osci.yaml b/osci.yaml
index 20916cc..e042cd3 100644
--- a/osci.yaml
+++ b/osci.yaml
@@ -1,10 +1,9 @@
 - project:
     templates:
-      - charm-unit-jobs-py38
       - charm-unit-jobs-py310
-      - charm-xena-functional-jobs
-      - charm-yoga-functional-jobs
+      - charm-zed-functional-jobs
     vars:
       needs_charm_build: true
       charm_build_name: cinder-backup
       build_type: charmcraft
+      charmcraft_channel: 2.0/stable
diff --git a/requirements.txt b/requirements.txt
index 196ff38..3b1cb7b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -11,9 +11,6 @@ pbr==5.6.0
 simplejson>=2.2.0
 netifaces>=0.10.4
 
-# Build requirements
-cffi==1.14.6; python_version < '3.6'  # cffi 1.15.0 drops support for py35.
-
 # NOTE: newer versions of cryptography require a Rust compiler to build,
 # see
 # * https://github.com/openstack-charmers/zaza/issues/421
@@ -27,8 +24,6 @@ netaddr>0.7.16,<0.8.0
 Jinja2>=2.6  # BSD License (3 clause)
 six>=1.9.0
 
-# dnspython 2.0.0 dropped py3.5 support
-dnspython<2.0.0; python_version < '3.6'
-dnspython; python_version >= '3.6'
+dnspython
 
 psutil>=1.1.1,<2.0.0
diff --git a/test-requirements.txt b/test-requirements.txt
index 0aabe17..4ef87dc 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -8,7 +8,6 @@
 #       all of its own requirements and if it doesn't, fix it there.
 #
 pyparsing<3.0.0  # aodhclient is pinned in zaza and needs pyparsing < 3.0.0, but cffi also needs it, so pin here.
-cffi==1.14.6; python_version < '3.6'  # cffi 1.15.0 drops support for py35.
 setuptools<50.0.0  # https://github.com/pypa/setuptools/commit/04e3df22df840c6bb244e9b27bc56750c44b7c85
 
 requests>=2.18.4
@@ -19,25 +18,12 @@ stestr>=2.2.0
 # https://github.com/mtreinish/stestr/issues/145
 cliff<3.0.0
 
-# Dependencies of stestr. Newer versions use keywords that didn't exist in
-# python 3.5 yet (e.g. "ModuleNotFoundError")
-importlib-metadata<3.0.0; python_version < '3.6'
-importlib-resources<3.0.0; python_version < '3.6'
-
-# Some Zuul nodes sometimes pull newer versions of these dependencies which
-# dropped support for python 3.5:
-osprofiler<2.7.0;python_version<'3.6'
-stevedore<1.31.0;python_version<'3.6'
-debtcollector<1.22.0;python_version<'3.6'
-oslo.utils<=3.41.0;python_version<'3.6'
-
 coverage>=4.5.2
 pyudev              # for ceph-* charm unit tests (need to fix the ceph-* charm unit tests/mocking)
 git+https://github.com/openstack-charmers/zaza.git#egg=zaza
 git+https://github.com/openstack-charmers/zaza-openstack-tests.git#egg=zaza.openstack
 
 # Needed for charm-glance:
-git+https://opendev.org/openstack/tempest.git#egg=tempest;python_version>='3.6'
-tempest<24.0.0;python_version<'3.6'
+git+https://opendev.org/openstack/tempest.git#egg=tempest
 
 croniter            # needed for charm-rabbitmq-server unit tests
diff --git a/tests/bundles/impish-xena.yaml b/tests/bundles/impish-xena.yaml
deleted file mode 100644
index 7691d95..0000000
--- a/tests/bundles/impish-xena.yaml
+++ /dev/null
@@ -1,180 +0,0 @@
-variables:
-  openstack-origin: &openstack-origin distro
-
-series: impish
-
-comment:
-- 'machines section to decide order of deployment. database sooner = faster'
-machines:
-  '0':
-    constraints: mem=3072M
-  '1':
-    constraints: mem=3072M
-  '2':
-    constraints: mem=3072M
-  '3':
-  '4':
-  '5':
-  '6':
-  '7':
-  '8':
-  '9':
-  '10':
-  '11':
-  '12':
-  '13':
-
-applications:
-
-  keystone-mysql-router:
-    charm: ch:mysql-router
-    channel: latest/edge
-  cinder-mysql-router:
-    charm: ch:mysql-router
-    channel: latest/edge
-  glance-mysql-router:
-    charm: ch:mysql-router
-    channel: latest/edge
-
-  mysql-innodb-cluster:
-    charm: ch:mysql-innodb-cluster
-    num_units: 3
-    options:
-      source: *openstack-origin
-    to:
-      - '0'
-      - '1'
-      - '2'
-    channel: latest/edge
-
-  keystone:
-    charm: ch:keystone
-    num_units: 1
-    options:
-      openstack-origin: *openstack-origin
-    to:
-      - '3'
-    channel: yoga/edge
-
-  rabbitmq-server:
-    charm: ch:rabbitmq-server
-    num_units: 1
-    to:
-      - '4'
-    channel: latest/edge
-
-  ceph-mon:
-    charm: ch:ceph-mon
-    num_units: 3
-    options:
-      monitor-count: '3'
-      source: *openstack-origin
-    to:
-      - '5'
-      - '6'
-      - '7'
-    channel: quincy/edge
-
-  ceph-osd:
-    charm: ch:ceph-osd
-    num_units: 3
-    storage:
-      osd-devices: 'cinder,10G'
-    options:
-      osd-devices: '/dev/test-non-existent'
-      source: *openstack-origin
-    to:
-      - '8'
-      - '9'
-      - '10'
-    channel: quincy/edge
-
-  cinder:
-    charm: ch:cinder
-    num_units: 1
-    options:
-      block-device: 'None'
-      glance-api-version: '2'
-      openstack-origin: *openstack-origin
-    to:
-      - '11'
-    channel: yoga/edge
-
-  cinder-backup:
-    charm: ../../cinder-backup.charm
-    options:
-      ceph-osd-replication-count: 3
-
-  cinder-ceph:
-    charm: ch:cinder-ceph
-    options:
-      ceph-osd-replication-count: 3
-    channel: yoga/edge
-
-  glance:
-    charm: ch:glance
-    num_units: 1
-    options:
-      source: *openstack-origin
-    to:
-      - '12'
-    channel: yoga/edge
-
-  nova-compute:
-    charm: ch:nova-compute
-    num_units: 1
-    options:
-      source: *openstack-origin
-    to:
-      - '13'
-    channel: yoga/edge
-
-relations:
-
-  - - 'cinder-backup:ceph'
-    - 'ceph-mon:client'
-
-  - - 'cinder-ceph:ceph'
-    - 'ceph-mon:client'
-
-  - - 'ceph-osd:mon'
-    - 'ceph-mon:osd'
-
-  - - 'cinder:storage-backend'
-    - 'cinder-ceph:storage-backend'
-
-  - - 'cinder:backup-backend'
-    - 'cinder-backup:backup-backend'
-
-  - - 'keystone:shared-db'
-    - 'keystone-mysql-router:shared-db'
-  - - 'keystone-mysql-router:db-router'
-    - 'mysql-innodb-cluster:db-router'
-
-  - - 'cinder:shared-db'
-    - 'cinder-mysql-router:shared-db'
-  - - 'cinder-mysql-router:db-router'
-    - 'mysql-innodb-cluster:db-router'
-
-  - - 'cinder:identity-service'
-    - 'keystone:identity-service'
-
-  - - 'cinder:amqp'
-    - 'rabbitmq-server:amqp'
-
-  - - 'glance:image-service'
-    - 'nova-compute:image-service'
-
-  - - 'glance:identity-service'
-    - 'keystone:identity-service'
-
-  - - 'glance:shared-db'
-    - 'glance-mysql-router:shared-db'
-  - - 'glance-mysql-router:db-router'
-    - 'mysql-innodb-cluster:db-router'
-
-  - - 'nova-compute:ceph-access'
-    - 'cinder-ceph:ceph-access'
-
-  - - 'nova-compute:amqp'
-    - 'rabbitmq-server:amqp'
diff --git a/tests/bundles/jammy-yoga.yaml b/tests/bundles/jammy-yoga.yaml
index 58874e1..3a83ad4 100644
--- a/tests/bundles/jammy-yoga.yaml
+++ b/tests/bundles/jammy-yoga.yaml
@@ -54,7 +54,7 @@ applications:
       openstack-origin: *openstack-origin
     to:
       - '3'
-    channel: yoga/edge
+    channel: latest/edge
 
   rabbitmq-server:
     charm: ch:rabbitmq-server
@@ -73,7 +73,7 @@ applications:
       - '5'
       - '6'
       - '7'
-    channel: quincy/edge
+    channel: latest/edge
 
   ceph-osd:
     charm: ch:ceph-osd
@@ -87,7 +87,7 @@ applications:
       - '8'
       - '9'
       - '10'
-    channel: quincy/edge
+    channel: latest/edge
 
   cinder:
     charm: ch:cinder
@@ -98,7 +98,7 @@ applications:
       openstack-origin: *openstack-origin
     to:
       - '11'
-    channel: yoga/edge
+    channel: latest/edge
 
   cinder-backup:
     charm: ../../cinder-backup.charm
@@ -109,7 +109,7 @@ applications:
     charm: ch:cinder-ceph
     options:
       ceph-osd-replication-count: 3
-    channel: yoga/edge
+    channel: latest/edge
 
   glance:
     charm: ch:glance
@@ -118,7 +118,7 @@ applications:
       openstack-origin: *openstack-origin
     to:
       - '12'
-    channel: yoga/edge
+    channel: latest/edge
 
   nova-compute:
     charm: ch:nova-compute
@@ -127,7 +127,7 @@ applications:
       openstack-origin: *openstack-origin
     to:
       - '13'
-    channel: yoga/edge
+    channel: latest/edge
 
 relations:
 
diff --git a/tests/bundles/focal-yoga.yaml b/tests/bundles/jammy-zed.yaml
similarity index 92%
rename from tests/bundles/focal-yoga.yaml
rename to tests/bundles/jammy-zed.yaml
index 13c65f0..0373269 100644
--- a/tests/bundles/focal-yoga.yaml
+++ b/tests/bundles/jammy-zed.yaml
@@ -1,7 +1,7 @@
 variables:
-  openstack-origin: &openstack-origin cloud:focal-yoga
+  openstack-origin: &openstack-origin cloud:jammy-zed
 
-series: focal
+series: jammy
 
 comment:
 - 'machines section to decide order of deployment. database sooner = faster'
@@ -54,7 +54,7 @@ applications:
       openstack-origin: *openstack-origin
     to:
       - '3'
-    channel: yoga/edge
+    channel: latest/edge
 
   rabbitmq-server:
     charm: ch:rabbitmq-server
@@ -73,7 +73,7 @@ applications:
       - '5'
       - '6'
       - '7'
-    channel: quincy/edge
+    channel: latest/edge
 
   ceph-osd:
     charm: ch:ceph-osd
@@ -87,7 +87,7 @@ applications:
       - '8'
       - '9'
       - '10'
-    channel: quincy/edge
+    channel: latest/edge
 
   cinder:
     charm: ch:cinder
@@ -98,7 +98,7 @@ applications:
       openstack-origin: *openstack-origin
     to:
       - '11'
-    channel: yoga/edge
+    channel: latest/edge
 
   cinder-backup:
     charm: ../../cinder-backup.charm
@@ -109,7 +109,7 @@ applications:
     charm: ch:cinder-ceph
     options:
       ceph-osd-replication-count: 3
-    channel: yoga/edge
+    channel: latest/edge
 
   glance:
     charm: ch:glance
@@ -118,7 +118,7 @@ applications:
       openstack-origin: *openstack-origin
     to:
       - '12'
-    channel: yoga/edge
+    channel: latest/edge
 
   nova-compute:
     charm: ch:nova-compute
@@ -127,7 +127,7 @@ applications:
       openstack-origin: *openstack-origin
     to:
       - '13'
-    channel: yoga/edge
+    channel: latest/edge
 
 relations:
 
diff --git a/tests/bundles/focal-xena.yaml b/tests/bundles/kinetic-zed.yaml
similarity index 92%
rename from tests/bundles/focal-xena.yaml
rename to tests/bundles/kinetic-zed.yaml
index c80055b..9de4e2e 100644
--- a/tests/bundles/focal-xena.yaml
+++ b/tests/bundles/kinetic-zed.yaml
@@ -1,7 +1,7 @@
 variables:
-  openstack-origin: &openstack-origin cloud:focal-xena
+  openstack-origin: &openstack-origin distro
 
-series: focal
+series: kinetic
 
 comment:
 - 'machines section to decide order of deployment. database sooner = faster'
@@ -54,7 +54,7 @@ applications:
       openstack-origin: *openstack-origin
     to:
       - '3'
-    channel: yoga/edge
+    channel: latest/edge
 
   rabbitmq-server:
     charm: ch:rabbitmq-server
@@ -73,7 +73,7 @@ applications:
       - '5'
       - '6'
       - '7'
-    channel: quincy/edge
+    channel: latest/edge
 
   ceph-osd:
     charm: ch:ceph-osd
@@ -87,7 +87,7 @@ applications:
       - '8'
       - '9'
       - '10'
-    channel: quincy/edge
+    channel: latest/edge
 
   cinder:
     charm: ch:cinder
@@ -98,7 +98,7 @@ applications:
       openstack-origin: *openstack-origin
     to:
       - '11'
-    channel: yoga/edge
+    channel: latest/edge
 
   cinder-backup:
     charm: ../../cinder-backup.charm
@@ -109,7 +109,7 @@ applications:
     charm: ch:cinder-ceph
     options:
       ceph-osd-replication-count: 3
-    channel: yoga/edge
+    channel: latest/edge
 
   glance:
     charm: ch:glance
@@ -118,7 +118,7 @@ applications:
       openstack-origin: *openstack-origin
     to:
       - '12'
-    channel: yoga/edge
+    channel: latest/edge
 
   nova-compute:
     charm: ch:nova-compute
@@ -127,7 +127,7 @@ applications:
       openstack-origin: *openstack-origin
     to:
       - '13'
-    channel: yoga/edge
+    channel: latest/edge
 
 relations:
 
diff --git a/tests/tests.yaml b/tests/tests.yaml
index 4ca09d0..994dd1a 100644
--- a/tests/tests.yaml
+++ b/tests/tests.yaml
@@ -1,20 +1,19 @@
 charm_name: cinder-backup
 
 smoke_bundles:
-  - focal-xena
+  - jammy-yoga
 
 gate_bundles:
-  - focal-xena
-  - impish-xena
+  - jammy-yoga
 
 dev_bundles:
-  - focal-yoga
   - jammy-yoga
+  - jammy-zed
+  - kinetic-zed
 
 tests:
   - zaza.openstack.charm_tests.cinder_backup.tests.CinderBackupTest
 
 tests_options:
   force_deploy:
-    - impish-xena
-    - jammy-yoga
+    - kinetic-zed
diff --git a/tox.ini b/tox.ini
index c41077d..bddbd1f 100644
--- a/tox.ini
+++ b/tox.ini
@@ -48,42 +48,23 @@ basepython = python3
 deps = -r{toxinidir}/build-requirements.txt
 commands =
     charmcraft clean
-    charmcraft -v build
+    charmcraft -v pack
     {toxinidir}/rename.sh
 
+[testenv:py310]
+basepython = python3.10
+deps = -r{toxinidir}/requirements.txt
+       -r{toxinidir}/test-requirements.txt
+
 [testenv:py3]
 basepython = python3
 deps = -r{toxinidir}/requirements.txt
        -r{toxinidir}/test-requirements.txt
 
-[testenv:py36]
-basepython = python3.6
-deps = -r{toxinidir}/requirements.txt
-       -r{toxinidir}/test-requirements.txt
-commands = stestr run --slowest {posargs}
-
-[testenv:py38]
-basepython = python3.8
-deps = -r{toxinidir}/requirements.txt
-       -r{toxinidir}/test-requirements.txt
-commands = stestr run --slowest {posargs}
-
-[testenv:py39]
-basepython = python3.9
-deps = -r{toxinidir}/requirements.txt
-       -r{toxinidir}/test-requirements.txt
-commands = stestr run --slowest {posargs}
-
-[testenv:py310]
-basepython = python3.10
-deps = -r{toxinidir}/requirements.txt
-       -r{toxinidir}/test-requirements.txt
-commands = stestr run --slowest {posargs}
-
 [testenv:pep8]
 basepython = python3
 deps = flake8==3.9.2
-       charm-tools==2.8.3
+       git+https://github.com/juju/charm-tools.git
 commands = flake8 {posargs} hooks unit_tests tests actions lib files
            charm-proof