diff --git a/unit_tests/utilities/test_zaza_utilities_openstack.py b/unit_tests/utilities/test_zaza_utilities_openstack.py
index 472b35b..cc5fa68 100644
--- a/unit_tests/utilities/test_zaza_utilities_openstack.py
+++ b/unit_tests/utilities/test_zaza_utilities_openstack.py
@@ -345,6 +345,20 @@ class TestOpenStackUtils(ut_utils.BaseTestCase):
openstack_utils.get_images_by_name(glance_client, 'frank'),
[])
+ def test_get_volumes_by_name(self):
+ volume_mock1 = mock.MagicMock()
+ volume_mock1.name = 'bob'
+ volume_mock2 = mock.MagicMock()
+ volume_mock2.name = 'bill'
+ cinder_client = mock.MagicMock()
+ cinder_client.volumes.list.return_value = [volume_mock1, volume_mock2]
+ self.assertEqual(
+ openstack_utils.get_volumes_by_name(cinder_client, 'bob'),
+ [volume_mock1])
+ self.assertEqual(
+ openstack_utils.get_volumes_by_name(cinder_client, 'frank'),
+ [])
+
def test_find_cirros_image(self):
urllib_opener_mock = mock.MagicMock()
self.patch_object(openstack_utils, "get_urllib_opener")
@@ -942,6 +956,34 @@ class TestOpenStackUtils(ut_utils.BaseTestCase):
self.assertEqual(expected, result)
self._get_os_rel_pair.assert_called_once_with(application='myapp')
+ def test_get_keystone_ip__vip(self):
+ self.patch_object(openstack_utils, "get_application_config_option")
+ self.patch_object(openstack_utils.model, "get_units")
+ unit1 = mock.Mock(public_address='5.6.7.8')
+ self.get_application_config_option.return_value = "1.2.3.4"
+ self.get_units.return_value = [unit1]
+
+ self.assertEqual(
+ openstack_utils.get_keystone_ip(model_name='some-model'),
+ '1.2.3.4')
+ self.get_application_config_option.assert_called_once_with(
+ 'keystone', 'vip', model_name='some-model')
+ self.get_application_config_option.return_value = " 1.2.3.4 11"
+ self.assertEqual(openstack_utils.get_keystone_ip(), '1.2.3.4')
+
+ def test_get_keystone_ip__from_unit(self):
+ self.patch_object(openstack_utils, "get_application_config_option")
+ self.patch_object(openstack_utils.model, "get_units")
+ self.patch_object(openstack_utils.model, 'get_unit_public_address')
+ mock_unit1 = mock.Mock()
+ self.get_unit_public_address.return_value = '5.6.7.8'
+ self.get_application_config_option.return_value = None
+ self.get_units.return_value = [mock_unit1]
+
+ self.assertEqual(openstack_utils.get_keystone_ip(), '5.6.7.8')
+ self.get_units.assert_called_once_with('keystone', model_name=None)
+ self.get_unit_public_address.assert_called_once_with(mock_unit1)
+
def test_get_keystone_api_version(self):
self.patch_object(openstack_utils, "get_current_os_versions")
self.patch_object(openstack_utils, "get_application_config_option")
diff --git a/unit_tests/utilities/test_zaza_utilities_swift.py b/unit_tests/utilities/test_zaza_utilities_swift.py
index c5fc3da..f0e330b 100644
--- a/unit_tests/utilities/test_zaza_utilities_swift.py
+++ b/unit_tests/utilities/test_zaza_utilities_swift.py
@@ -91,12 +91,20 @@ class TestSwiftUtils(ut_utils.BaseTestCase):
self.patch_object(juju_utils, 'get_full_juju_status')
self.patch_object(zaza.model, 'get_application_config')
self.patch_object(zaza.model, 'get_units')
+ self.patch_object(zaza.model, 'get_unit_public_address')
+
+ def _get_unit_public_address(u):
+ return u.public_address
+
+ self.get_unit_public_address.side_effect = _get_unit_public_address
+
juju_status = mock.MagicMock()
juju_status.applications = {}
self.get_full_juju_status.return_value = juju_status
for app_name, units in app_units.items():
- expected_topology[units[0].public_address]['unit'] = units[0]
+ ip = units[0].public_address
+ expected_topology[ip]['unit'] = units[0]
app_config = {}
for app_name in app_units.keys():
diff --git a/unit_tests/utilities/test_zaza_utilities_upgrade_utils.py b/unit_tests/utilities/test_zaza_utilities_upgrade_utils.py
index f42ba5a..986ba38 100644
--- a/unit_tests/utilities/test_zaza_utilities_upgrade_utils.py
+++ b/unit_tests/utilities/test_zaza_utilities_upgrade_utils.py
@@ -116,6 +116,21 @@ class TestUpgradeUtils(ut_utils.BaseTestCase):
self.assertEqual(
actual,
expected)
+ # test that, at focal, there are no database services.
+ expected = [
+ ('Database Services', []),
+ ('Stateful Services', []),
+ ('Core Identity', []),
+ ('Control Plane', ['cinder']),
+ ('Data Plane', ['nova-compute']),
+ ('sweep_up', ['ntp'])]
+ actual = openstack_upgrade.get_series_upgrade_groups(
+ target_series='focal')
+ pprint.pprint(expected)
+ pprint.pprint(actual)
+ self.assertEqual(
+ actual,
+ expected)
def test_extract_charm_name_from_url(self):
self.assertEqual(
diff --git a/zaza/openstack/charm_tests/ceilometer/tests.py b/zaza/openstack/charm_tests/ceilometer/tests.py
index c460cdb..dde2eb8 100644
--- a/zaza/openstack/charm_tests/ceilometer/tests.py
+++ b/zaza/openstack/charm_tests/ceilometer/tests.py
@@ -160,5 +160,10 @@ class CeilometerTest(test_utils.OpenStackBaseTest):
Pause service and check services are stopped then resume and check
they are started.
"""
+ if self.application_name == 'ceilometer-agent':
+ logging.info("ceilometer-agent doesn't have pause/resume actions "
+ "anymore, skipping")
+ return
+
with self.pause_resume(self.restartable_services):
logging.info("Testing pause and resume")
diff --git a/zaza/openstack/charm_tests/ceph/dashboard/setup.py b/zaza/openstack/charm_tests/ceph/dashboard/setup.py
index 7a2488f..5c9e5d6 100644
--- a/zaza/openstack/charm_tests/ceph/dashboard/setup.py
+++ b/zaza/openstack/charm_tests/ceph/dashboard/setup.py
@@ -48,4 +48,5 @@ def set_grafana_url(model_name=None):
'ceph-dashboard',
{
'grafana-api-url': "https://{}:3000".format(
- unit.public_address)})
+ zaza.model.get_unit_public_address(unit))
+ })
diff --git a/zaza/openstack/charm_tests/ceph/dashboard/tests.py b/zaza/openstack/charm_tests/ceph/dashboard/tests.py
index 50cdbaf..5b88c42 100644
--- a/zaza/openstack/charm_tests/ceph/dashboard/tests.py
+++ b/zaza/openstack/charm_tests/ceph/dashboard/tests.py
@@ -103,11 +103,13 @@ class CephDashboardTest(test_utils.BaseCharmTest):
units = zaza.model.get_units(self.application_name)
for unit in units:
r = self._run_request_get(
- 'https://{}:8443'.format(unit.public_address),
+ 'https://{}:8443'.format(
+ zaza.model.get_unit_public_address(unit)),
verify=self.local_ca_cert,
allow_redirects=False)
if r.status_code == requests.codes.ok:
- return 'https://{}:8443'.format(unit.public_address)
+ return 'https://{}:8443'.format(
+ zaza.model.get_unit_public_address(unit))
def test_dashboard_units(self):
"""Check dashboard units are configured correctly."""
@@ -116,10 +118,11 @@ class CephDashboardTest(test_utils.BaseCharmTest):
rcs = collections.defaultdict(list)
for unit in units:
r = self._run_request_get(
- 'https://{}:8443'.format(unit.public_address),
+ 'https://{}:8443'.format(
+ zaza.model.get_unit_public_address(unit)),
verify=verify,
allow_redirects=False)
- rcs[r.status_code].append(unit.public_address)
+ rcs[r.status_code].append(zaza.model.get_unit_public_address(unit))
self.assertEqual(len(rcs[requests.codes.ok]), 1)
self.assertEqual(len(rcs[requests.codes.see_other]), len(units) - 1)
diff --git a/zaza/openstack/charm_tests/ceph/iscsi/tests.py b/zaza/openstack/charm_tests/ceph/iscsi/tests.py
index 0766e4a..c64b081 100644
--- a/zaza/openstack/charm_tests/ceph/iscsi/tests.py
+++ b/zaza/openstack/charm_tests/ceph/iscsi/tests.py
@@ -79,7 +79,7 @@ class CephISCSIGatewayTest(test_utils.BaseCharmTest):
ctxt['gateway_units'] = [
{
'entity_id': u.entity_id,
- 'ip': u.public_address,
+ 'ip': zaza.model.get_unit_public_address(u),
'hostname': host_names[u.entity_id]}
for u in zaza.model.get_units('ceph-iscsi')]
ctxt['gw_ip'] = sorted([g['ip'] for g in ctxt['gateway_units']])[0]
diff --git a/zaza/openstack/charm_tests/ceph/tests.py b/zaza/openstack/charm_tests/ceph/tests.py
index 6ee2113..543fc22 100644
--- a/zaza/openstack/charm_tests/ceph/tests.py
+++ b/zaza/openstack/charm_tests/ceph/tests.py
@@ -35,7 +35,6 @@ import zaza.openstack.utilities.exceptions as zaza_exceptions
import zaza.openstack.utilities.generic as zaza_utils
import zaza.utilities.juju as juju_utils
import zaza.openstack.utilities.openstack as zaza_openstack
-import zaza.openstack.utilities.juju as zaza_juju
class CephLowLevelTest(test_utils.OpenStackBaseTest):
@@ -124,7 +123,7 @@ class CephRelationTest(test_utils.OpenStackBaseTest):
remote_unit_name = 'ceph-mon/0'
relation_name = 'osd'
remote_unit = zaza_model.get_unit_from_name(remote_unit_name)
- remote_ip = remote_unit.public_address
+ remote_ip = zaza_model.get_unit_public_address(remote_unit)
relation = juju_utils.get_relation_from_unit(
unit_name,
remote_unit_name,
@@ -145,7 +144,7 @@ class CephRelationTest(test_utils.OpenStackBaseTest):
unit_name = 'ceph-osd/0'
relation_name = 'osd'
remote_unit = zaza_model.get_unit_from_name(remote_unit_name)
- remote_ip = remote_unit.public_address
+ remote_ip = zaza_model.get_unit_public_address(remote_unit)
cmd = 'leader-get fsid'
result = zaza_model.run_on_unit(remote_unit_name, cmd)
fsid = result.get('Stdout').strip()
@@ -546,20 +545,28 @@ class CephTest(test_utils.OpenStackBaseTest):
)
logging.debug('OK')
- def _get_local_osd_id(self, unit):
+ def get_local_osd_id(self, unit):
+ """Get the OSD id for a unit."""
ret = zaza_model.run_on_unit(unit,
'ceph-volume lvm list --format=json')
local = list(json.loads(ret['Stdout']))[-1]
return local if local.startswith('osd.') else 'osd.' + local
+ def get_num_osds(self, osd):
+ """Compute the number of active OSD's."""
+ result = zaza_model.run_on_unit(osd, 'ceph osd stat --format=json')
+ result = json.loads(result['Stdout'])
+ return int(result['num_osds'])
+
def test_cache_device(self):
"""Test replacing a disk in use."""
logging.info('Running add-disk action with a caching device')
+ mon = next(iter(zaza_model.get_units('ceph-mon'))).entity_id
osds = [x.entity_id for x in zaza_model.get_units('ceph-osd')]
params = []
for unit in osds:
- zaza_juju.add_storage(unit, 'cache-devices', 'cinder', 10)
- loop_dev = zaza_juju.add_loop_device(unit, 10).get('Stdout')
+ zaza_model.add_storage(unit, 'cache-devices', 'cinder', 10)
+ loop_dev = zaza_utils.add_loop_device(unit, 10)
params.append({'unit': unit})
action_obj = zaza_model.run_action(
unit_name=unit,
@@ -572,13 +579,13 @@ class CephTest(test_utils.OpenStackBaseTest):
logging.info('Removing previously added disks')
for param in params:
- osd_id = self._get_local_osd_id(param['unit'])
+ osd_id = self.get_local_osd_id(param['unit'])
param.update({'osd-id': osd_id})
action_obj = zaza_model.run_action(
unit_name=param['unit'],
action_name='remove-disk',
action_params={'osd-ids': osd_id, 'timeout': 5,
- 'format': 'json', 'purge': True}
+ 'format': 'json', 'purge': False}
)
zaza_utils.assertActionRanOK(action_obj)
results = json.loads(action_obj.data['results']['message'])
@@ -599,6 +606,7 @@ class CephTest(test_utils.OpenStackBaseTest):
)
zaza_utils.assertActionRanOK(action_obj)
zaza_model.wait_for_application_states()
+ self.assertEqual(len(osds) * 2, self.get_num_osds(mon))
class CephRGWTest(test_utils.OpenStackBaseTest):
@@ -861,7 +869,9 @@ class CephPrometheusTest(unittest.TestCase):
unit = zaza_model.get_unit_from_name(
zaza_model.get_lead_unit_name('prometheus2'))
self.assertEqual(
- '3', _get_mon_count_from_prometheus(unit.public_address))
+ '3',
+ _get_mon_count_from_prometheus(
+ zaza_model.get_unit_public_address(unit)))
class CephPoolConfig(Exception):
diff --git a/zaza/openstack/charm_tests/cinder_backend/__init__.py b/zaza/openstack/charm_tests/cinder_backend/__init__.py
new file mode 100644
index 0000000..bc8ad6a
--- /dev/null
+++ b/zaza/openstack/charm_tests/cinder_backend/__init__.py
@@ -0,0 +1,15 @@
+# Copyright 2022 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.
+
+"""Collection of code for setting up and testing cinder backends."""
diff --git a/zaza/openstack/charm_tests/cinder_backend/tests.py b/zaza/openstack/charm_tests/cinder_backend/tests.py
new file mode 100644
index 0000000..358d6a8
--- /dev/null
+++ b/zaza/openstack/charm_tests/cinder_backend/tests.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python3
+
+# Copyright 2021 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.
+
+"""Encapsulate cinder backend tests."""
+
+import uuid
+
+import zaza.model
+import zaza.openstack.charm_tests.test_utils as test_utils
+import zaza.openstack.utilities.openstack as openstack_utils
+
+
+class CinderBackendTest(test_utils.OpenStackBaseTest):
+ """Encapsulate cinder backend tests."""
+
+ expected_config_content = {}
+ backend_name = ""
+
+ @classmethod
+ def setUpClass(cls):
+ """Run class setup for running tests."""
+ super(CinderBackendTest, cls).setUpClass()
+ cls.keystone_session = openstack_utils.get_overcloud_keystone_session()
+ cls.model_name = zaza.model.get_juju_model()
+ cls.cinder_client = openstack_utils.get_cinder_session_client(
+ cls.keystone_session)
+
+ def test_cinder_config(self):
+ """Test that configuration options match our expectations."""
+ zaza.model.run_on_leader(
+ 'cinder',
+ 'sudo cp /etc/cinder/cinder.conf /tmp/')
+ zaza.model.block_until_oslo_config_entries_match(
+ 'cinder',
+ '/tmp/cinder.conf',
+ self.expected_config_content,
+ timeout=2)
+
+ def test_create_volume(self):
+ """Test creating a volume with basic configuration."""
+ test_vol_name = "zaza{}".format(uuid.uuid1().fields[0])
+ vol_new = self.cinder_client.volumes.create(
+ name=test_vol_name,
+ size='1')
+ try:
+ openstack_utils.resource_reaches_status(
+ self.cinder_client.volumes,
+ vol_new.id,
+ wait_iteration_max_time=12000,
+ stop_after_attempt=5,
+ expected_status='available',
+ msg='Volume status wait')
+ test_vol = self.cinder_client.volumes.find(name=test_vol_name)
+ self.assertEqual(
+ getattr(test_vol, 'os-vol-host-attr:host').split('#')[0],
+ 'cinder@{}'.format(self.backend_name))
+ finally:
+ self.cinder_client.volumes.delete(vol_new)
diff --git a/zaza/openstack/charm_tests/cinder_netapp/tests.py b/zaza/openstack/charm_tests/cinder_netapp/tests.py
index 41456ba..9e1ebc1 100644
--- a/zaza/openstack/charm_tests/cinder_netapp/tests.py
+++ b/zaza/openstack/charm_tests/cinder_netapp/tests.py
@@ -16,62 +16,19 @@
"""Encapsulate cinder-netapp testing."""
-import uuid
-
-import zaza.model
-import zaza.openstack.charm_tests.test_utils as test_utils
-import zaza.openstack.utilities.openstack as openstack_utils
+from zaza.openstack.charm_tests.cinder_backend.tests import CinderBackendTest
-class CinderNetAppTest(test_utils.OpenStackBaseTest):
+class CinderNetAppTest(CinderBackendTest):
"""Encapsulate netapp tests."""
- @classmethod
- def setUpClass(cls):
- """Run class setup for running tests."""
- super(CinderNetAppTest, cls).setUpClass()
- cls.keystone_session = openstack_utils.get_overcloud_keystone_session()
- cls.model_name = zaza.model.get_juju_model()
- cls.cinder_client = openstack_utils.get_cinder_session_client(
- cls.keystone_session)
+ backend_name = 'cinder-netapp'
- def test_cinder_config(self):
- """Test that configuration options match our expectations."""
- expected_contents = {
- 'cinder-netapp': {
- 'netapp_storage_family': ['ontap_cluster'],
- 'netapp_storage_protocol': ['iscsi'],
- 'volume_backend_name': ['cinder_netapp'],
- 'volume_driver':
+ expected_config_content = {
+ 'cinder-netapp': {
+ 'netapp_storage_family': ['ontap_cluster'],
+ 'netapp_storage_protocol': ['iscsi'],
+ 'volume_backend_name': ['cinder_netapp'],
+ 'volume_driver':
['cinder.volume.drivers.netapp.common.NetAppDriver'],
- }}
-
- zaza.model.run_on_leader(
- 'cinder',
- 'sudo cp /etc/cinder/cinder.conf /tmp/')
- zaza.model.block_until_oslo_config_entries_match(
- 'cinder',
- '/tmp/cinder.conf',
- expected_contents,
- timeout=2)
-
- def test_create_volume(self):
- """Test creating a volume with basic configuration."""
- test_vol_name = "zaza{}".format(uuid.uuid1().fields[0])
- vol_new = self.cinder_client.volumes.create(
- name=test_vol_name,
- size='1')
- try:
- openstack_utils.resource_reaches_status(
- self.cinder_client.volumes,
- vol_new.id,
- wait_iteration_max_time=12000,
- stop_after_attempt=5,
- expected_status='available',
- msg='Volume status wait')
- test_vol = self.cinder_client.volumes.find(name=test_vol_name)
- self.assertEqual(
- getattr(test_vol, 'os-vol-host-attr:host').split('#')[0],
- 'cinder@cinder-netapp')
- finally:
- self.cinder_client.volumes.delete(vol_new)
+ }}
diff --git a/zaza/openstack/charm_tests/glance/tests.py b/zaza/openstack/charm_tests/glance/tests.py
index a6da291..0f505df 100644
--- a/zaza/openstack/charm_tests/glance/tests.py
+++ b/zaza/openstack/charm_tests/glance/tests.py
@@ -17,6 +17,7 @@
"""Encapsulate glance testing."""
import logging
+import math
import boto3
import zaza.model as model
@@ -222,3 +223,46 @@ class GlanceExternalS3Test(test_utils.OpenStackBaseTest):
)
self.assertEqual(image["size"], response["ContentLength"])
openstack_utils.delete_image(self.glance_client, image["id"])
+
+
+class GlanceCinderBackendTest(test_utils.OpenStackBaseTest):
+ """Encapsulate glance tests using cinder backend."""
+
+ @classmethod
+ def setUpClass(cls):
+ """Run class setup for running glance tests with cinder backend."""
+ super(GlanceCinderBackendTest, cls).setUpClass()
+ cls.glance_client = openstack_utils.get_glance_session_client(
+ cls.keystone_session)
+ cls.cinder_client = openstack_utils.get_cinder_session_client(
+ cls.keystone_session)
+
+ def test_100_create_delete_image(self):
+ """Create an image and do a simple validation of it.
+
+ Validate the size of the image in both Glance API and Cinder API.
+ """
+ image_name = "zaza-cinder-test-image"
+ openstack_utils.create_image(
+ glance=self.glance_client,
+ image_url=openstack_utils.find_cirros_image(arch="x86_64"),
+ image_name=image_name,
+ backend="cinder",
+ )
+ images = openstack_utils.get_images_by_name(
+ self.glance_client, image_name)
+ self.assertEqual(len(images), 1)
+ image = images[0]
+
+ volume_name = 'image-'+image["id"]
+ volumes = openstack_utils.get_volumes_by_name(
+ self.cinder_client, volume_name)
+ self.assertEqual(len(volumes), 1)
+ volume = volumes[0]
+
+ logging.info(
+ "Checking glance image size {} matches volume size {} "
+ "GB".format(image["size"], volume.size))
+ image_size_in_gb = int(math.ceil(float(image["size"]) / 1024 ** 3))
+ self.assertEqual(image_size_in_gb, volume.size)
+ openstack_utils.delete_image(self.glance_client, image["id"])
diff --git a/zaza/openstack/charm_tests/manila_ganesha/setup.py b/zaza/openstack/charm_tests/manila_ganesha/setup.py
index a804958..c4edd97 100644
--- a/zaza/openstack/charm_tests/manila_ganesha/setup.py
+++ b/zaza/openstack/charm_tests/manila_ganesha/setup.py
@@ -42,5 +42,5 @@ def setup_ganesha_share_type(manila_client=None):
extra_specs={
'vendor_name': 'Ceph',
'storage_protocol': 'NFS',
- 'snapshot_support': False,
+ 'snapshot_support': True,
})
diff --git a/zaza/openstack/charm_tests/mysql/tests.py b/zaza/openstack/charm_tests/mysql/tests.py
index fbc6494..f040a87 100644
--- a/zaza/openstack/charm_tests/mysql/tests.py
+++ b/zaza/openstack/charm_tests/mysql/tests.py
@@ -90,7 +90,7 @@ class MySQLBaseTest(test_utils.OpenStackBaseTest):
_primary_ip = _primary_ip.split(':')[0]
units = zaza.model.get_units(self.application_name)
for unit in units:
- if _primary_ip in unit.public_address:
+ if _primary_ip in zaza.model.get_unit_public_address(unit):
return unit
def get_blocked_mysql_routers(self):
@@ -802,6 +802,7 @@ class MySQLInnoDBClusterScaleTest(MySQLBaseTest):
leader, nons = generic_utils.get_leaders_and_non_leaders(
self.application_name)
leader_unit = zaza.model.get_unit_from_name(leader)
+ leader_unit_ip = zaza.model.get_unit_public_address(leader_unit)
# Wait until we are idle in the hopes clients are not running
# update-status hooks
@@ -821,12 +822,12 @@ class MySQLInnoDBClusterScaleTest(MySQLBaseTest):
logging.info(
"Removing old unit from cluster: {} "
- .format(leader_unit.public_address))
+ .format(leader_unit_ip))
action = zaza.model.run_action(
nons[0],
"remove-instance",
action_params={
- "address": leader_unit.public_address,
+ "address": leader_unit_ip,
"force": True})
assert action.data.get("results") is not None, (
"Remove instance action failed: No results: {}"
@@ -877,6 +878,8 @@ class MySQLInnoDBClusterScaleTest(MySQLBaseTest):
leader, nons = generic_utils.get_leaders_and_non_leaders(
self.application_name)
non_leader_unit = zaza.model.get_unit_from_name(nons[0])
+ non_leader_unit_ip = zaza.model.get_unit_public_address(
+ non_leader_unit)
# Wait until we are idle in the hopes clients are not running
# update-status hooks
@@ -897,12 +900,12 @@ class MySQLInnoDBClusterScaleTest(MySQLBaseTest):
logging.info(
"Removing old unit from cluster: {} "
- .format(non_leader_unit.public_address))
+ .format(non_leader_unit_ip))
action = zaza.model.run_action(
leader,
"remove-instance",
action_params={
- "address": non_leader_unit.public_address,
+ "address": non_leader_unit_ip,
"force": True})
assert action.data.get("results") is not None, (
"Remove instance action failed: No results: {}"
@@ -925,7 +928,7 @@ class MySQLInnoDBClusterPartitionTest(MySQLBaseTest):
no_of_units = len(mysql_units)
for index, unit in enumerate(mysql_units):
next_unit = mysql_units[(index+1) % no_of_units]
- ip_address = next_unit.public_address
+ ip_address = zaza.model.get_unit_public_address(next_unit)
cmd = "sudo iptables -A INPUT -s {} -j DROP".format(ip_address)
zaza.model.async_run_on_unit(unit, cmd)
@@ -949,7 +952,7 @@ class MySQLInnoDBClusterPartitionTest(MySQLBaseTest):
leader_unit.entity_id,
"force-quorum-using-partition-of",
action_params={
- "address": leader_unit.public_address,
+ "address": zaza.model.get_unit_public_address(leader_unit),
'i-really-mean-it': True
})
diff --git a/zaza/openstack/charm_tests/neutron/setup.py b/zaza/openstack/charm_tests/neutron/setup.py
index 174c51d..290483a 100644
--- a/zaza/openstack/charm_tests/neutron/setup.py
+++ b/zaza/openstack/charm_tests/neutron/setup.py
@@ -23,10 +23,11 @@ from zaza.openstack.configure import (
from zaza.openstack.utilities import (
cli as cli_utils,
generic as generic_utils,
- juju as juju_utils,
openstack as openstack_utils,
)
+import zaza.utilities.juju as juju_utils
+
import zaza.charm_lifecycle.utils as lifecycle_utils
diff --git a/zaza/openstack/charm_tests/neutron/tests.py b/zaza/openstack/charm_tests/neutron/tests.py
index b3c8906..5ea8b76 100644
--- a/zaza/openstack/charm_tests/neutron/tests.py
+++ b/zaza/openstack/charm_tests/neutron/tests.py
@@ -492,14 +492,11 @@ class NeutronApiTest(NeutronCreateNetworkTest):
Pause service and check services are stopped then resume and check
they are started
"""
- bionic_stein = openstack_utils.get_os_release('bionic_stein')
- if openstack_utils.get_os_release() >= bionic_stein:
- pgrep_full = True
- else:
- pgrep_full = False
with self.pause_resume(
- ["neutron-server", "apache2", "haproxy"],
- pgrep_full=pgrep_full):
+ ["/usr/bin/neutron-server",
+ "/usr/sbin/apache2",
+ "/usr/sbin/haproxy"],
+ pgrep_full=True):
logging.info("Testing pause resume")
diff --git a/zaza/openstack/charm_tests/neutron_arista/utils.py b/zaza/openstack/charm_tests/neutron_arista/utils.py
index 6fb77b3..6ded31b 100644
--- a/zaza/openstack/charm_tests/neutron_arista/utils.py
+++ b/zaza/openstack/charm_tests/neutron_arista/utils.py
@@ -25,7 +25,8 @@ PLUGIN_APP_NAME = 'neutron-api-plugin-arista'
def fixture_ip_addr():
"""Return the public IP address of the Arista test fixture."""
- return zaza.model.get_units(FIXTURE_APP_NAME)[0].public_address
+ return zaza.model.get_unit_public_address(
+ zaza.model.get_units(FIXTURE_APP_NAME)[0])
_FIXTURE_LOGIN = 'admin'
diff --git a/zaza/openstack/charm_tests/nova/tests.py b/zaza/openstack/charm_tests/nova/tests.py
index 66ca957..00ca643 100644
--- a/zaza/openstack/charm_tests/nova/tests.py
+++ b/zaza/openstack/charm_tests/nova/tests.py
@@ -461,7 +461,7 @@ class NovaCloudControllerActionTest(test_utils.OpenStackBaseTest):
if juju_az:
zone = juju_az
- juju_units_az_map[unit.public_address] = zone
+ juju_units_az_map[zaza.model.get_unit_public_address(unit)] = zone
continue
session = openstack_utils.get_overcloud_keystone_session()
diff --git a/zaza/openstack/charm_tests/octavia/setup.py b/zaza/openstack/charm_tests/octavia/setup.py
index b04b696..9560e43 100644
--- a/zaza/openstack/charm_tests/octavia/setup.py
+++ b/zaza/openstack/charm_tests/octavia/setup.py
@@ -15,6 +15,7 @@
"""Code for configuring octavia."""
import os
+import re
import base64
import logging
@@ -141,6 +142,63 @@ def configure_octavia():
'octavia',
'configure-resources',
action_params={})
+ # When bug #1964117 is fix released for all affected releases this call can
+ # be removed.
+ bug_1964117_workaround()
+
+
+def disable_ohm_port_security():
+ """Disable port security on the health manager ports on octavia units."""
+ keystone_session = openstack.get_overcloud_keystone_session()
+ neutron_client = openstack.get_neutron_session_client(
+ keystone_session)
+ ports = [
+ p
+ for p in neutron_client.list_ports()['ports']
+ if re.match('octavia-health-manager-.*-listen-port', p['name'])]
+ for port in ports:
+ neutron_client.update_port(
+ port['id'],
+ {
+ 'port':
+ {
+ 'port_security_enabled': False,
+ 'security_groups': []}})
+
+
+def bug_1964117_workaround():
+ """Apply Bug #1964117 if allowed."""
+ if openstack.ovn_present():
+ # Issue only known to affect ml2 ovs so if do not apply work around
+ # to ovn deploys.
+ return
+ allow_pkg_list = ['2.16.0-0ubuntu2.1~cloud0']
+ allow_release_list = ['focal_xena']
+ _allow_release_list = [
+ openstack.get_os_release(r)
+ for r in allow_release_list
+ ]
+ current_release = openstack.get_os_release()
+ if current_release in _allow_release_list:
+ cmd_out = zaza.model.run_on_leader(
+ 'octavia',
+ """dpkg -l | awk '/openvswitch-switch/ {print $3;}'""")
+ pkg_version = cmd_out['Stdout'].strip()
+ if pkg_version in allow_pkg_list:
+ logging.info('Disabling port security to work around bug #1964117')
+ disable_ohm_port_security()
+ else:
+ msg = (
+ "Detected Xena deploy and package version {} is not in the "
+ "allow list {}. If you believe that bug #1964117 has been "
+ "resolved please remove the call to this function. If the "
+ "new package does not resolve bug #1964117 then please add "
+ "the new package version to the 'allow_pkg_list' defined at "
+ "the start of this function. If changes are required please "
+ "raise a PR againt "
+ "https://github.com/openstack-charmers/zaza-openstack-tests"
+ "".format(pkg_version, allow_pkg_list))
+ raise Exception(msg)
def centralized_fip_network():
diff --git a/zaza/openstack/charm_tests/octavia/tests.py b/zaza/openstack/charm_tests/octavia/tests.py
index 6158375..086932b 100644
--- a/zaza/openstack/charm_tests/octavia/tests.py
+++ b/zaza/openstack/charm_tests/octavia/tests.py
@@ -29,6 +29,10 @@ import zaza.openstack.utilities.openstack as openstack_utils
from zaza.openstack.utilities import generic as generic_utils
from zaza.openstack.utilities import ObjectRetrierWraps
+from zaza.openstack.utilities.exceptions import (
+ LoadBalancerUnexpectedState,
+ LoadBalancerUnrecoverableError,
+)
LBAAS_ADMIN_ROLE = 'load-balancer_admin'
@@ -279,24 +283,35 @@ class LBAASv2Test(test_utils.OpenStackBaseTest):
super(LBAASv2Test, self).resource_cleanup()
@staticmethod
- @tenacity.retry(retry=tenacity.retry_if_exception_type(AssertionError),
- wait=tenacity.wait_fixed(1), reraise=True,
- stop=tenacity.stop_after_delay(900))
+ @tenacity.retry(
+ retry=tenacity.retry_if_exception_type(LoadBalancerUnexpectedState),
+ wait=tenacity.wait_fixed(1), reraise=True,
+ stop=tenacity.stop_after_delay(900))
def wait_for_lb_resource(octavia_show_func, resource_id,
provisioning_status=None, operating_status=None):
"""Wait for loadbalancer resource to reach expected status."""
provisioning_status = provisioning_status or 'ACTIVE'
resp = octavia_show_func(resource_id)
- logging.info(resp['provisioning_status'])
- assert resp['provisioning_status'] == provisioning_status, (
- 'load balancer resource has not reached '
- 'expected provisioning status: {}'
- .format(resp))
+ logging.info("Current provisioning status: {}, waiting for {}"
+ .format(resp['provisioning_status'], provisioning_status))
+
+ msg = ('load balancer resource has not reached '
+ 'expected provisioning status: {}'.format(resp))
+
+ # ERROR is a final state, once it's reached there is no reason to keep
+ # retrying and delaying the failure.
+ if resp['provisioning_status'] == 'ERROR':
+ raise LoadBalancerUnrecoverableError(msg)
+ elif resp['provisioning_status'] != provisioning_status:
+ raise LoadBalancerUnexpectedState(msg)
+
if operating_status:
- logging.info(resp['operating_status'])
- assert resp['operating_status'] == operating_status, (
- 'load balancer resource has not reached '
- 'expected operating status: {}'.format(resp))
+ logging.info('Current operating status: {}, waiting for {}'
+ .format(resp['operating_status'], operating_status))
+ if not resp['operating_status'] == operating_status:
+ raise LoadBalancerUnexpectedState((
+ 'load balancer resource has not reached '
+ 'expected operating status: {}'.format(resp)))
return resp
diff --git a/zaza/openstack/charm_tests/openstack_dashboard/tests.py b/zaza/openstack/charm_tests/openstack_dashboard/tests.py
index 60ac7eb..d689581 100644
--- a/zaza/openstack/charm_tests/openstack_dashboard/tests.py
+++ b/zaza/openstack/charm_tests/openstack_dashboard/tests.py
@@ -178,7 +178,7 @@ class OpenStackDashboardBase():
else:
unit = zaza_model.get_unit_from_name(
zaza_model.get_lead_unit_name(self.application_name))
- ip = unit.public_address
+ ip = zaza_model.get_unit_public_address(unit)
logging.debug("Dashboard ip is:{}".format(ip))
scheme = 'http'
@@ -272,7 +272,8 @@ class OpenStackDashboardTests(test_utils.OpenStackBaseTest,
logging.info('Checking dashboard HAProxy settings...')
unit = zaza_model.get_unit_from_name(
zaza_model.get_lead_unit_name(self.application_name))
- logging.debug("... dashboard_ip is:{}".format(unit.public_address))
+ logging.debug("... dashboard_ip is:{}".format(
+ zaza_model.get_unit_public_address(unit)))
conf = '/etc/haproxy/haproxy.cfg'
port = '8888'
set_alternate = {
@@ -280,13 +281,18 @@ class OpenStackDashboardTests(test_utils.OpenStackBaseTest,
}
request = urllib.request.Request(
- 'http://{}:{}'.format(unit.public_address, port))
+ 'http://{}:{}'.format(
+ zaza_model.get_unit_public_address(unit), port))
output = str(generic_utils.get_file_contents(unit, conf))
+ password = None
for line in output.split('\n'):
if "stats auth" in line:
password = line.split(':')[1]
+ break
+ else:
+ raise ValueError("'stats auth' not found in output'")
base64string = base64.b64encode(
bytes('{}:{}'.format('admin', password), 'ascii'))
request.add_header(
@@ -493,7 +499,8 @@ class OpenStackDashboardPolicydTests(policyd.BasePolicydSpecialization,
"""
unit = zaza_model.get_unit_from_name(
zaza_model.get_lead_unit_name(self.application_name))
- logging.info("Dashboard is at {}".format(unit.public_address))
+ logging.info("Dashboard is at {}".format(
+ zaza_model.get_unit_public_address(unit)))
overcloud_auth = openstack_utils.get_overcloud_auth()
password = overcloud_auth['OS_PASSWORD']
logging.info("admin password is {}".format(password))
@@ -504,7 +511,7 @@ class OpenStackDashboardPolicydTests(policyd.BasePolicydSpecialization,
self.get_horizon_url(), domain, username, password,
cafile=self.cacert)
# now attempt to get the domains page
- _url = self.url.format(unit.public_address)
+ _url = self.url.format(zaza_model.get_unit_public_address(unit))
logging.info("URL is {}".format(_url))
result = client.get(_url)
if result.status_code == 403:
diff --git a/zaza/openstack/charm_tests/rabbitmq_server/tests.py b/zaza/openstack/charm_tests/rabbitmq_server/tests.py
index fed72df..027ef95 100644
--- a/zaza/openstack/charm_tests/rabbitmq_server/tests.py
+++ b/zaza/openstack/charm_tests/rabbitmq_server/tests.py
@@ -26,8 +26,8 @@ import zaza.model
import zaza.openstack.charm_tests.test_utils as test_utils
import zaza.openstack.utilities.generic as generic_utils
-from charmhelpers.core.host import CompareHostReleases
from zaza.openstack.utilities.generic import get_series
+from zaza.openstack.utilities.os_versions import CompareHostReleases
from . import utils as rmq_utils
from .utils import RmqNoMessageException
@@ -49,6 +49,7 @@ class RmqTests(test_utils.OpenStackBaseTest):
return '[{}-{}]'.format(uuid.uuid4(), time.time())
@tenacity.retry(
+ reraise=True,
retry=tenacity.retry_if_exception_type(RmqNoMessageException),
wait=tenacity.wait_fixed(10),
stop=tenacity.stop_after_attempt(2))
@@ -57,6 +58,40 @@ class RmqTests(test_utils.OpenStackBaseTest):
ssl=ssl,
port=port)
+ def _search_for_message(self, amqp_msg, check_unit, ssl, port,
+ amqp_msg_counter):
+ """Search for message in message queue.
+
+ WARNING: This will consume messages until it finds the target message.
+
+ :param amqp_msg: Message to search for
+ :type amqp_msg: string
+ :param check_unit: Unit to retrieve messages from
+ :type check_unit: juju.unit.Unit
+ :param ssl: Whether to use SSL when connecting to rabbit
+ :type ssl: bool
+ :param port: Port to use when connecting to rabbit
+ :type port: Union[int, None]
+ :param amqp_msg_counter: Number in test sequence of this message.
+ :type amqp_msg: int
+ :raises: RmqNoMessageException
+ """
+ for i in range(100):
+ amqp_msg_rcvd = self._retry_get_amqp_message(
+ check_unit,
+ ssl=ssl,
+ port=port)
+ if amqp_msg == amqp_msg_rcvd:
+ logging.info(
+ 'Message {} received OK.'.format(amqp_msg_counter))
+ break
+ else:
+ logging.info('Expected: {}'.format(amqp_msg))
+ logging.info('Actual: {}'.format(amqp_msg_rcvd))
+ else:
+ msg = 'Message {} not found.'.format(amqp_msg_counter)
+ raise RmqNoMessageException(msg)
+
def _test_rmq_amqp_messages_all_units(self, units,
ssl=False, port=None):
"""Reusable test to send/check amqp messages to every listed rmq unit.
@@ -85,7 +120,7 @@ class RmqTests(test_utils.OpenStackBaseTest):
for dest_unit in units:
dest_unit_name = dest_unit.entity_id
- dest_unit_host = dest_unit.public_address
+ dest_unit_host = zaza.model.get_unit_public_address(dest_unit)
dest_unit_host_name = host_names[dest_unit_name]
for check_unit in units:
@@ -93,7 +128,8 @@ class RmqTests(test_utils.OpenStackBaseTest):
if dest_unit_name == check_unit_name:
logging.info("Skipping check for this unit to itself.")
continue
- check_unit_host = check_unit.public_address
+ check_unit_host = zaza.model.get_unit_public_address(
+ check_unit)
check_unit_host_name = host_names[check_unit_name]
amqp_msg_stamp = self._get_uuid_epoch_stamp()
@@ -111,25 +147,22 @@ class RmqTests(test_utils.OpenStackBaseTest):
port=port)
# Get amqp message
- logging.info('Get message from: {} '
+ logging.info('Get messages from: {} '
'({} {})'.format(check_unit_host,
check_unit_name,
check_unit_host_name))
- amqp_msg_rcvd = self._retry_get_amqp_message(check_unit,
- ssl=ssl,
- port=port)
-
- # Validate amqp message content
- if amqp_msg == amqp_msg_rcvd:
- logging.info('Message {} received '
- 'OK.'.format(amqp_msg_counter))
- else:
- logging.error('Expected: {}'.format(amqp_msg))
- logging.error('Actual: {}'.format(amqp_msg_rcvd))
- msg = 'Message {} mismatch.'.format(amqp_msg_counter)
+ try:
+ self._search_for_message(
+ amqp_msg,
+ check_unit,
+ ssl,
+ port,
+ amqp_msg_counter)
+ except RmqNoMessageException:
+ msg = 'Failed to retrieve message {}.'.format(
+ amqp_msg_counter)
raise Exception(msg)
-
amqp_msg_counter += 1
# Delete the test user
diff --git a/zaza/openstack/charm_tests/rabbitmq_server/utils.py b/zaza/openstack/charm_tests/rabbitmq_server/utils.py
index d00f288..a0ced05 100644
--- a/zaza/openstack/charm_tests/rabbitmq_server/utils.py
+++ b/zaza/openstack/charm_tests/rabbitmq_server/utils.py
@@ -351,7 +351,7 @@ def configure_ssl_off(units, model_name=None, max_wait=60):
def is_ssl_enabled_on_unit(unit, port=None):
"""Check a single juju rmq unit for ssl and port in the config file."""
- host = unit.public_address
+ host = zaza.model.get_unit_public_address(unit)
unit_name = unit.entity_id
conf_file = '/etc/rabbitmq/rabbitmq.conf'
@@ -406,7 +406,7 @@ def connect_amqp_by_unit(unit, ssl=False,
:param password: amqp user password
:returns: pika amqp connection pointer or None if failed and non-fatal
"""
- host = unit.public_address
+ host = zaza.model.get_unit_public_address(unit)
unit_name = unit.entity_id
if ssl:
@@ -506,9 +506,9 @@ def get_amqp_message_by_unit(unit, queue="test",
password=password)
channel = connection.channel()
method_frame, _, body = channel.basic_get(queue)
- body = body.decode()
if method_frame:
+ body = body.decode()
logging.debug('Retreived message from {} queue:\n{}'.format(queue,
body))
channel.basic_ack(method_frame.delivery_tag)
diff --git a/zaza/openstack/charm_tests/saml_mellon/setup.py b/zaza/openstack/charm_tests/saml_mellon/setup.py
index 20b8cbd..0a7e159 100644
--- a/zaza/openstack/charm_tests/saml_mellon/setup.py
+++ b/zaza/openstack/charm_tests/saml_mellon/setup.py
@@ -228,7 +228,7 @@ def keystone_federation_setup_idp1():
"""Configure Keystone Federation for the local IdP #1."""
test_saml_idp_unit = zaza.model.get_units("test-saml-idp1")[0]
idp_remote_id = LOCAL_IDP_REMOTE_ID.format(
- test_saml_idp_unit.public_address)
+ zaza.model.get_unit_public_address(test_saml_idp_unit))
keystone_federation_setup(
federated_domain="federated_domain_idp1",
@@ -241,7 +241,7 @@ def keystone_federation_setup_idp2():
"""Configure Keystone Federation for the local IdP #2."""
test_saml_idp_unit = zaza.model.get_units("test-saml-idp2")[0]
idp_remote_id = LOCAL_IDP_REMOTE_ID.format(
- test_saml_idp_unit.public_address)
+ zaza.model.get_unit_public_address(test_saml_idp_unit))
keystone_federation_setup(
federated_domain="federated_domain_idp2",
diff --git a/zaza/openstack/charm_tests/saml_mellon/tests.py b/zaza/openstack/charm_tests/saml_mellon/tests.py
index 962fa92..64e9be7 100644
--- a/zaza/openstack/charm_tests/saml_mellon/tests.py
+++ b/zaza/openstack/charm_tests/saml_mellon/tests.py
@@ -54,7 +54,7 @@ class CharmKeystoneSAMLMellonTest(BaseKeystoneTest):
if self.vip:
ip = self.vip
else:
- ip = unit.public_address
+ ip = zaza.model.get_unit_public_address(unit)
action = zaza.model.run_action(unit.entity_id, self.action)
if "failed" in action.data["status"]:
@@ -81,7 +81,7 @@ class CharmKeystoneSAMLMellonTest(BaseKeystoneTest):
keystone_ip = self.vip
else:
unit = zaza.model.get_units(self.application_name)[0]
- keystone_ip = unit.public_address
+ keystone_ip = zaza.model.get_unit_public_address(unit)
horizon = "openstack-dashboard"
horizon_vip = (zaza.model.get_application_config(horizon)
@@ -90,7 +90,7 @@ class CharmKeystoneSAMLMellonTest(BaseKeystoneTest):
horizon_ip = horizon_vip
else:
unit = zaza.model.get_units("openstack-dashboard")[0]
- horizon_ip = unit.public_address
+ horizon_ip = zaza.model.get_unit_public_address(unit)
if self.tls_rid:
proto = "https"
@@ -258,7 +258,7 @@ class BaseCharmKeystoneSAMLMellonTest(BaseKeystoneTest):
def test_run_get_sp_metadata_action(self):
"""Validate the get-sp-metadata action."""
unit = zaza.model.get_units(self.application_name)[0]
- ip = self.vip if self.vip else unit.public_address
+ ip = self.vip if self.vip else zaza.model.get_unit_public_address(unit)
action = zaza.model.run_action(unit.entity_id, self.action)
self.assertNotIn(
@@ -283,14 +283,16 @@ class BaseCharmKeystoneSAMLMellonTest(BaseKeystoneTest):
def test_saml_mellon_redirects(self):
"""Validate the horizon -> keystone -> IDP redirects."""
unit = zaza.model.get_units(self.application_name)[0]
- keystone_ip = self.vip if self.vip else unit.public_address
+ keystone_ip = self.vip if self.vip else (
+ zaza.model.get_unit_public_address(unit))
horizon = "openstack-dashboard"
horizon_config = zaza.model.get_application_config(horizon)
horizon_vip = horizon_config.get("vip").get("value")
unit = zaza.model.get_units("openstack-dashboard")[0]
- horizon_ip = horizon_vip if horizon_vip else unit.public_address
+ horizon_ip = horizon_vip if horizon_vip else (
+ zaza.model.get_unit_public_address(unit))
proto = "https" if self.tls_rid else "http"
# Use Keystone URL for < Focal
@@ -299,8 +301,8 @@ class BaseCharmKeystoneSAMLMellonTest(BaseKeystoneTest):
else:
region = "default"
- idp_address = zaza.model.get_units(
- self.test_saml_idp_app_name)[0].public_address
+ idp_address = zaza.model.get_unit_public_address(
+ zaza.model.get_units(self.test_saml_idp_app_name)[0])
horizon_url = "{}://{}/horizon/auth/login/".format(proto, horizon_ip)
horizon_expect = ''.format(
diff --git a/zaza/openstack/charm_tests/series_upgrade/parallel_tests.py b/zaza/openstack/charm_tests/series_upgrade/parallel_tests.py
index e524fa2..4dd2432 100644
--- a/zaza/openstack/charm_tests/series_upgrade/parallel_tests.py
+++ b/zaza/openstack/charm_tests/series_upgrade/parallel_tests.py
@@ -73,7 +73,8 @@ class ParallelSeriesUpgradeTest(unittest.TestCase):
# Set Feature Flag
os.environ["JUJU_DEV_FEATURE_FLAGS"] = "upgrade-series"
upgrade_groups = upgrade_utils.get_series_upgrade_groups(
- extra_filters=[_filter_etcd, _filter_easyrsa])
+ extra_filters=[_filter_etcd, _filter_easyrsa],
+ target_series=self.to_series)
from_series = self.from_series
to_series = self.to_series
completed_machines = []
diff --git a/zaza/openstack/charm_tests/series_upgrade/tests.py b/zaza/openstack/charm_tests/series_upgrade/tests.py
index 122af11..3bb37ed 100644
--- a/zaza/openstack/charm_tests/series_upgrade/tests.py
+++ b/zaza/openstack/charm_tests/series_upgrade/tests.py
@@ -193,7 +193,8 @@ class ParallelSeriesUpgradeTest(unittest.TestCase):
os.environ["JUJU_DEV_FEATURE_FLAGS"] = "upgrade-series"
upgrade_groups = upgrade_utils.get_series_upgrade_groups(
extra_filters=[upgrade_utils._filter_etcd,
- upgrade_utils._filter_easyrsa])
+ upgrade_utils._filter_easyrsa],
+ target_series=self.to_series)
applications = model.get_status().applications
completed_machines = []
for group_name, group in upgrade_groups:
diff --git a/zaza/openstack/charm_tests/swift/tests.py b/zaza/openstack/charm_tests/swift/tests.py
index 3cb34df..7c731b6 100644
--- a/zaza/openstack/charm_tests/swift/tests.py
+++ b/zaza/openstack/charm_tests/swift/tests.py
@@ -72,6 +72,12 @@ class SwiftImageCreateTest(test_utils.OpenStackBaseTest):
class SwiftProxyTests(test_utils.OpenStackBaseTest):
"""Tests specific to swift proxy."""
+ TEST_SEARCH_TARGET = 'd0'
+ TEST_REMOVE_TARGET = 'd1'
+ TEST_EXPECTED_RING_HOSTS = 1
+ TEST_WEIGHT_TARGET = 999
+ TEST_WEIGHT_INITIAL = 100
+
def test_901_pause_resume(self):
"""Run pause and resume tests.
@@ -91,6 +97,59 @@ class SwiftProxyTests(test_utils.OpenStackBaseTest):
action_params={})
self.assertEqual(action.status, "completed")
+ def test_904_set_weight_action_and_validate_rebalance(self):
+ """Set weight of device in object ring."""
+ logging.info('Running set-weight action on leader')
+ action = zaza.model.run_action_on_leader(
+ 'swift-proxy',
+ 'set-weight',
+ action_params={'ring': 'object',
+ 'search-value': self.TEST_SEARCH_TARGET,
+ 'weight': self.TEST_WEIGHT_TARGET})
+ self.assertEqual(action.status, "completed")
+
+ logging.info('Validating builder updated as expected')
+ result = swift_utils.search_builder('swift-proxy', 'object',
+ self.TEST_SEARCH_TARGET)
+ # disk weight is the 9th field of the second line and is a float
+ disk_weight = int(result.split('\n')[1].split()[8].split('.')[0])
+ self.assertEqual(disk_weight, self.TEST_WEIGHT_TARGET)
+ self.assertTrue(swift_utils.is_proxy_ring_up_to_date('swift-proxy',
+ 'object'))
+
+ logging.info('Running set-weight on leader to reset weight back')
+ action = zaza.model.run_action_on_leader(
+ 'swift-proxy',
+ 'set-weight',
+ action_params={'ring': 'object',
+ 'search-value': self.TEST_SEARCH_TARGET,
+ 'weight': self.TEST_WEIGHT_INITIAL})
+ self.assertEqual(action.status, "completed")
+ self.assertTrue(
+ swift_utils.is_ring_synced('swift-proxy', 'object',
+ self.TEST_EXPECTED_RING_HOSTS))
+
+ def test_905_remove_device_action_and_validate_rebalance(self):
+ """Remove device from object ring."""
+ logging.info('Running remove-devices action on leader')
+ action = zaza.model.run_action_on_leader(
+ 'swift-proxy',
+ 'remove-devices',
+ action_params={'ring': 'object',
+ 'search-value': self.TEST_REMOVE_TARGET})
+ self.assertEqual(action.status, "completed")
+
+ logging.info('Validating builder updated as expected')
+ result = swift_utils.search_builder('swift-proxy', 'object',
+ self.TEST_REMOVE_TARGET)
+ expected = 'No matching devices found'
+ self.assertEqual(result.strip('\n'), expected)
+ self.assertTrue(swift_utils.is_proxy_ring_up_to_date('swift-proxy',
+ 'object'))
+ self.assertTrue(
+ swift_utils.is_ring_synced('swift-proxy', 'object',
+ self.TEST_EXPECTED_RING_HOSTS))
+
class SwiftProxyMultiZoneTests(test_utils.OpenStackBaseTest):
"""Tests specific to swift proxy in multi zone environment."""
diff --git a/zaza/openstack/charm_tests/test_utils.py b/zaza/openstack/charm_tests/test_utils.py
index be47dd9..a7c522d 100644
--- a/zaza/openstack/charm_tests/test_utils.py
+++ b/zaza/openstack/charm_tests/test_utils.py
@@ -1043,6 +1043,21 @@ class BaseDeferredRestartTest(BaseCharmTest):
# clear status message.
self.clear_hooks()
+ def get_service_timestamps(self, service):
+ """For units of self.application_name get start time of service.
+
+ :param service: Service to check, must be a systemd service
+ :type service: str
+ :returns: A dict timestamps keyed on unit name.
+ :rtype: dict
+ """
+ timestamps = {}
+ for unit in model.get_units(self.application_name):
+ timestamps[unit.entity_id] = model.get_systemd_service_active_time(
+ unit.entity_id,
+ service)
+ return timestamps
+
def run_package_change_test(self, restart_package, restart_package_svc):
"""Trigger a deferred restart by updating a package.
@@ -1055,8 +1070,32 @@ class BaseDeferredRestartTest(BaseCharmTest):
after restart_package has changed.
:type restart_package_service: str
"""
+ pre_timestamps = self.get_service_timestamps(
+ restart_package_svc)
self.trigger_deferred_restart_via_package(restart_package)
-
+ post_timestamps = self.get_service_timestamps(
+ restart_package_svc)
+ broken_units = []
+ for unit_name in post_timestamps.keys():
+ if pre_timestamps[unit_name] != post_timestamps[unit_name]:
+ logging.error(
+ "Service {} on unit {} should have start time of {} but"
+ " it has {}".format(
+ restart_package_svc,
+ unit_name,
+ pre_timestamps[unit_name],
+ post_timestamps[unit_name]))
+ broken_units.append(unit_name)
+ if broken_units:
+ msg = (
+ "Units {} restarted service {} when disallowed by "
+ "deferred_restarts").format(
+ ','.join(broken_units),
+ restart_package_svc)
+ raise Exception(msg)
+ else:
+ logging.info(
+ "Service was {} not restarted.".format(restart_package_svc))
self.check_show_deferred_restarts_wlm(restart_package_svc)
self.check_show_deferred_events_action_restart(
restart_package_svc,
diff --git a/zaza/openstack/configure/network.py b/zaza/openstack/configure/network.py
index 5d3a2f8..c0bbe1c 100755
--- a/zaza/openstack/configure/network.py
+++ b/zaza/openstack/configure/network.py
@@ -85,10 +85,11 @@ import sys
from zaza.openstack.utilities import (
cli as cli_utils,
generic as generic_utils,
- juju as juju_utils,
openstack as openstack_utils,
)
+import zaza.utilities.juju as juju_utils
+
def setup_sdn(network_config, keystone_session=None):
"""Perform setup for Software Defined Network.
diff --git a/zaza/openstack/utilities/exceptions.py b/zaza/openstack/utilities/exceptions.py
index 7f73be4..c638c2d 100644
--- a/zaza/openstack/utilities/exceptions.py
+++ b/zaza/openstack/utilities/exceptions.py
@@ -208,3 +208,15 @@ class CACERTNotFound(Exception):
"""Could not find cacert."""
pass
+
+
+class LoadBalancerUnexpectedState(Exception):
+ """The LoadBalancer is in a unexpected state."""
+
+ pass
+
+
+class LoadBalancerUnrecoverableError(Exception):
+ """The LoadBalancer has reached to an unrecoverable error state."""
+
+ pass
diff --git a/zaza/openstack/utilities/generic.py b/zaza/openstack/utilities/generic.py
index be19c31..126ef82 100644
--- a/zaza/openstack/utilities/generic.py
+++ b/zaza/openstack/utilities/generic.py
@@ -622,7 +622,7 @@ def port_knock_units(units, port=22, expect_success=True):
:returns: None if successful, Failure message otherwise
"""
for u in units:
- host = u.public_address
+ host = model.get_unit_public_address(u)
connected = is_port_open(port, host)
if not connected and expect_success:
return 'Socket connect failed.'
@@ -723,3 +723,22 @@ def get_leaders_and_non_leaders(application_name):
else:
non_leaders.append(unit)
return leader, non_leaders
+
+
+def add_loop_device(unit, size=10):
+ """Add a loopback device to a Juju unit.
+
+ :param unit: The unit name on which to create the device.
+ :type unit: str
+
+ :param size: The size in GB of the device.
+ :type size: int
+
+ :returns: The device name.
+ """
+ loop_name = '/home/ubuntu/loop.img'
+ truncate = 'truncate --size {}GB {}'.format(size, loop_name)
+ losetup = 'losetup --find {}'.format(loop_name)
+ lofind = 'losetup -a | grep {} | cut -f1 -d ":"'.format(loop_name)
+ cmd = "sudo sh -c '{} && {} && {}'".format(truncate, losetup, lofind)
+ return model.run_on_unit(unit, cmd)
diff --git a/zaza/openstack/utilities/juju.py b/zaza/openstack/utilities/juju.py
index e4646a5..073a26f 100644
--- a/zaza/openstack/utilities/juju.py
+++ b/zaza/openstack/utilities/juju.py
@@ -17,9 +17,7 @@
import logging
import functools
-import subprocess
-import zaza.model
import zaza.utilities.juju
@@ -312,67 +310,3 @@ def get_subordinate_units(unit_list, charm_name=None, status=None,
charm_name=charm_name,
status=status,
model_name=model_name)
-
-
-def add_storage(unit, label, pool, size):
- """Add storage to a Juju unit.
-
- :param unit: The unit name (i.e: ceph-osd/0)
- :type unit: str
-
- :param label: The storage label (i.e: osd-devices)
- :type label: str
-
- :param pool: The pool on which to allocate the storage (i.e: cinder)
- :type pool: str
-
- :size: The size in GB of the storage to attach.
- :type size: int
-
- :returns: The name of the allocated storage.
- """
- rv = subprocess.check_output(['juju', 'add-storage', unit,
- '{}={},{}'.format(label, pool,
- str(size) + 'GB')],
- stderr=subprocess.STDOUT)
- return rv.decode('UTF-8').replace('added storage ', '').split(' ')[0]
-
-
-def detach_storage(storage_name):
- """Detach previously allocated Juju storage."""
- subprocess.check_call(['juju', 'detach-storage', storage_name])
-
-
-def remove_storage(storage_name, force=False):
- """Remove Juju storage.
-
- :param storage_name: The name of the previously allocated Juju storage.
- :type storage_name: str
-
- :param force: If False (default), require that the storage be detached
- before it can be removed.
- :type force: bool
- """
- cmd = ['juju', 'remove-storage', storage_name]
- if force:
- cmd.append('--force')
- subprocess.check_call(cmd)
-
-
-def add_loop_device(unit, size=10):
- """Add a loopback device to a Juju unit.
-
- :param unit: The unit name on which to create the device.
- :type unit: str
-
- :param size: The size in GB of the device.
- :type size: int
-
- :returns: The device name.
- """
- loop_name = '/home/ubuntu/loop.img'
- truncate = 'truncate --size {}GB {}'.format(size, loop_name)
- losetup = 'losetup --find {}'.format(loop_name)
- lofind = 'losetup -a | grep {} | cut -f1 -d ":"'.format(loop_name)
- cmd = "sudo sh -c '{} && {} && {}'".format(truncate, losetup, lofind)
- return zaza.model.run_on_unit(unit, cmd)
diff --git a/zaza/openstack/utilities/openstack.py b/zaza/openstack/utilities/openstack.py
index 5359992..a12a1ac 100644
--- a/zaza/openstack/utilities/openstack.py
+++ b/zaza/openstack/utilities/openstack.py
@@ -1554,7 +1554,7 @@ def create_bgp_peer(neutron_client, peer_application_name='quagga',
:rtype: dict
"""
peer_unit = model.get_units(peer_application_name)[0]
- peer_ip = peer_unit.public_address
+ peer_ip = model.get_unit_public_address(peer_unit)
bgp_peers = neutron_client.list_bgp_peers(name=peer_application_name)
if len(bgp_peers['bgp_peers']) == 0:
logging.info('Creating BGP Peer')
@@ -2019,6 +2019,9 @@ def get_undercloud_auth():
def get_keystone_ip(model_name=None):
"""Return the IP address to use when communicating with keystone api.
+ If there are multiple VIP addresses specified in the 'vip' option for the
+ keystone unit, then ONLY the first one is returned.
+
:param model_name: Name of model to query.
:type model_name: str
:returns: IP address
@@ -2029,9 +2032,10 @@ def get_keystone_ip(model_name=None):
'vip',
model_name=model_name)
if vip_option:
- return vip_option
+ # strip the option, splits on whitespace and return the first one.
+ return vip_option.strip().split()[0]
unit = model.get_units('keystone', model_name=model_name)[0]
- return unit.public_address
+ return model.get_unit_public_address(unit)
def get_keystone_api_version(model_name=None):
@@ -2225,6 +2229,22 @@ def get_images_by_name(glance, image_name):
return [i for i in glance.images.list() if image_name == i.name]
+def get_volumes_by_name(cinder, volume_name):
+ """Get all cinder volume objects with the given name.
+
+ :param cinder: Authenticated cinderclient
+ :type cinder: cinderclient.Client
+ :param image_name: Name of volume
+ :type image_name: str
+ :returns: List of cinder volumes
+ :rtype: List[cinderclient.v3.volume, ...]
+ """
+ return [i for i in cinder.volumes.list() if volume_name == i.name]
+
+
+@tenacity.retry(wait=tenacity.wait_exponential(multiplier=1, max=60),
+ reraise=True,
+ retry=tenacity.retry_if_exception_type(urllib.error.URLError))
def find_cirros_image(arch):
"""Return the url for the latest cirros image for the given architecture.
@@ -2251,7 +2271,7 @@ def download_image(image_url, target_file):
:param image_url: URL to download image from
:type image_url: str
- :param target_file: Local file to savee image to
+ :param target_file: Local file to save image to
:type target_file: str
"""
opener = get_urllib_opener()
diff --git a/zaza/openstack/utilities/openstack_upgrade.py b/zaza/openstack/utilities/openstack_upgrade.py
index f75a05c..0451b8f 100755
--- a/zaza/openstack/utilities/openstack_upgrade.py
+++ b/zaza/openstack/utilities/openstack_upgrade.py
@@ -17,7 +17,7 @@
This module contains a number of functions for upgrading OpenStack.
"""
import logging
-import zaza.openstack.utilities.juju as juju_utils
+import zaza.utilities.juju as juju_utils
import zaza.model
from zaza import sync_wrapper
diff --git a/zaza/openstack/utilities/os_versions.py b/zaza/openstack/utilities/os_versions.py
index e67a97f..d69f9cf 100644
--- a/zaza/openstack/utilities/os_versions.py
+++ b/zaza/openstack/utilities/os_versions.py
@@ -278,3 +278,105 @@ PACKAGE_CODENAMES = {
('4', 'victoria'),
]),
}
+
+
+UBUNTU_RELEASES = (
+ 'lucid',
+ 'maverick',
+ 'natty',
+ 'oneiric',
+ 'precise',
+ 'quantal',
+ 'raring',
+ 'saucy',
+ 'trusty',
+ 'utopic',
+ 'vivid',
+ 'wily',
+ 'xenial',
+ 'yakkety',
+ 'zesty',
+ 'artful',
+ 'bionic',
+ 'cosmic',
+ 'disco',
+ 'eoan',
+ 'focal',
+ 'groovy',
+ 'hirsute',
+ 'impish',
+)
+
+
+class BasicStringComparator(object):
+ """Provides a class that will compare strings from an iterator type object.
+
+ Used to provide > and < comparisons on strings that may not necessarily be
+ alphanumerically ordered. e.g. OpenStack or Ubuntu releases AFTER the
+ z-wrap.
+ """
+
+ _list = None
+
+ def __init__(self, item):
+ """Do init."""
+ if self._list is None:
+ raise Exception("Must define the _list in the class definition!")
+ try:
+ self.index = self._list.index(item)
+ except Exception:
+ raise KeyError("Item '{}' is not in list '{}'"
+ .format(item, self._list))
+
+ def __eq__(self, other):
+ """Do equals."""
+ assert isinstance(other, str) or isinstance(other, self.__class__)
+ return self.index == self._list.index(other)
+
+ def __ne__(self, other):
+ """Do not equals."""
+ return not self.__eq__(other)
+
+ def __lt__(self, other):
+ """Do less than."""
+ assert isinstance(other, str) or isinstance(other, self.__class__)
+ return self.index < self._list.index(other)
+
+ def __ge__(self, other):
+ """Do greater than or equal."""
+ return not self.__lt__(other)
+
+ def __gt__(self, other):
+ """Do greater than."""
+ assert isinstance(other, str) or isinstance(other, self.__class__)
+ return self.index > self._list.index(other)
+
+ def __le__(self, other):
+ """Do less than or equals."""
+ return not self.__gt__(other)
+
+ def __str__(self):
+ """Give back the item at the index.
+
+ This is so it can be used in comparisons like:
+
+ s_mitaka = CompareOpenStack('mitaka')
+ s_newton = CompareOpenstack('newton')
+
+ assert s_newton > s_mitaka
+
+ :returns:
+ """
+ return self._list[self.index]
+
+
+class CompareHostReleases(BasicStringComparator):
+ """Provide comparisons of Ubuntu releases.
+
+ Use in the form of
+
+ if CompareHostReleases(release) > 'trusty':
+ # do something with mitaka
+ """
+
+ _list = UBUNTU_RELEASES
diff --git a/zaza/openstack/utilities/swift.py b/zaza/openstack/utilities/swift.py
index 2b1f46a..b7db584 100644
--- a/zaza/openstack/utilities/swift.py
+++ b/zaza/openstack/utilities/swift.py
@@ -207,7 +207,7 @@ def get_swift_storage_topology(model_name=None):
region = app_config['storage-region']['value']
zone = app_config['zone']['value']
for unit in zaza.model.get_units(app_name, model_name=model_name):
- topology[unit.public_address] = {
+ topology[zaza.model.get_unit_public_address(unit)] = {
'app_name': app_name,
'unit': unit,
'region': region,
@@ -298,3 +298,69 @@ def create_object(swift_client, proxy_app, storage_topology, resource_prefix,
storage_topology,
model_name=model_name)
return container_name, object_name, obj_replicas
+
+
+def search_builder(proxy_app, ring, search_target, model_name=None):
+ """Run a swift-ring-builder search.
+
+ :param proxy_app: Name of proxy application
+ :type proxy_app: str
+ :param ring: Name of ring (one of: object, account, container)
+ :type ring: str
+ :param search_target: device search string (see: man swift-ring-builder)
+ :type search_target: str
+ :param model_name: Model to point environment at
+ :type model_name: str
+ :returns: stdout - full stdout output from swift-ring-builder cmd
+ :rtype: str
+ """
+ cmd = ('swift-ring-builder /etc/swift/{}.builder search {}'
+ ''.format(ring, search_target))
+ result = zaza.model.run_on_leader(proxy_app, cmd,
+ model_name=model_name)
+ return result['Stdout']
+
+
+def is_proxy_ring_up_to_date(proxy_app, ring, model_name=None):
+ """Check if the ring file is up-to-date with changes of the builder.
+
+ :param proxy_app: Name of proxy application
+ :type proxy_app: str
+ :param ring: Name of ring (one of: object, account, container)
+ :type ring: str
+ :param model_name: Model to point environment at
+ :type model_name: str
+ :returns: True if swift-ring-builder denotes ring.gz file is up-to-date
+ :rtype: str
+ """
+ logging.info('Checking ring file matches builder file')
+ cmd = ('swift-ring-builder /etc/swift/{}.builder | '
+ 'grep "Ring file .* is"'.format(ring))
+ result = zaza.model.run_on_leader(proxy_app, cmd, model_name=model_name)
+ expected = ('Ring file /etc/swift/{}.ring.gz is up-to-date'
+ ''.format(ring))
+ return bool(result['Stdout'].strip('\n') == expected)
+
+
+def is_ring_synced(proxy_app, ring, expected_hosts, model_name=None):
+ """Check if md5sums of rings on swift-storage are synced to this proxy.
+
+ :param proxy_app: Name of proxy application
+ :type proxy_app: str
+ :param ring: Name of ring (one of: object, account, container)
+ :type ring: str
+ :param expoected_hosts: Number of swift-storage hosts in test environment
+ :type search_target: int
+ :param model_name: Model to point environment at
+ :type model_name: str
+ :returns: True if all expected_hosts matched md5sum of proxy ring file
+ :rtype: bool
+ """
+ logging.info('Checking ring md5sums on storage unit(s) against proxy')
+ zaza.model.block_until_all_units_idle()
+ cmd = ('swift-recon {} --md5 | '
+ 'grep -A1 "ring md5" | tail -1'.format(ring))
+ result = zaza.model.run_on_leader(proxy_app, cmd, model_name=model_name)
+ expected = ('{num}/{num} hosts matched, 0 error[s] while checking hosts.'
+ ''.format(num=expected_hosts))
+ return bool(result['Stdout'].strip('\n') == expected)
diff --git a/zaza/openstack/utilities/upgrade_utils.py b/zaza/openstack/utilities/upgrade_utils.py
index ecc0b8b..3add346 100644
--- a/zaza/openstack/utilities/upgrade_utils.py
+++ b/zaza/openstack/utilities/upgrade_utils.py
@@ -24,6 +24,7 @@ from zaza.openstack.utilities.os_versions import (
OPENSTACK_CODENAMES,
UBUNTU_OPENSTACK_RELEASE,
OPENSTACK_RELEASES_PAIRS,
+ CompareHostReleases,
)
"""
@@ -48,7 +49,11 @@ SERVICE_GROUPS = (
'nova-compute', 'ceph-osd',
'swift-proxy', 'swift-storage']))
-UPGRADE_EXCLUDE_LIST = ['rabbitmq-server', 'percona-cluster']
+UPGRADE_EXCLUDE_LIST = [
+ 'rabbitmq-server',
+ 'percona-cluster',
+ 'glance-simplestreams-sync',
+]
def get_upgrade_candidates(model_name=None, filters=None):
@@ -96,6 +101,25 @@ def _filter_openstack_upgrade_list(app, app_config, model_name=None):
return False
+def _make_filter_percona_cluster_at(target_series):
+ def _filter_percona_cluster(app, app_config, model_name=None):
+ charm_name = extract_charm_name_from_url(app_config['charm'])
+ if charm_name == "percona-cluster":
+ logging.warning(
+ "Excluding percona-cluster from upgrade, "
+ "as no candidate in %s", target_series)
+ return True
+ return False
+
+ def _noop_filter(*args, **kwargs):
+ return False
+
+ if target_series and CompareHostReleases(target_series) >= "focal":
+ return _filter_percona_cluster
+
+ return _noop_filter
+
+
def _filter_non_openstack_services(app, app_config, model_name=None):
charm_options = zaza.model.get_application_config(
app, model_name=model_name).keys()
@@ -168,7 +192,8 @@ def get_upgrade_groups(model_name=None, extra_filters=None):
return _build_service_groups(apps_in_model)
-def get_series_upgrade_groups(model_name=None, extra_filters=None):
+def get_series_upgrade_groups(model_name=None, extra_filters=None,
+ target_series=None):
"""Place apps in the model into their upgrade groups.
Place apps in the model into their upgrade groups. If an app is deployed
@@ -176,10 +201,17 @@ def get_series_upgrade_groups(model_name=None, extra_filters=None):
:param model_name: Name of model to query.
:type model_name: str
+ :param extra_filters: filters to apply to the upgrade groups
+ :type extra_filters: Callable
+ :param target_series: The series that will be series upgraded to.
+ :type target_series: Optional[str]
:returns: List of tuples(group name, applications)
:rtype: List[Tuple[str, Dict[str, ANY]]]
"""
- filters = [_filter_subordinates]
+ filters = [
+ _filter_subordinates,
+ _make_filter_percona_cluster_at(target_series),
+ ]
filters = _apply_extra_filters(filters, extra_filters)
apps_in_model = get_upgrade_candidates(
model_name=model_name,