Merge branch 'master' into osd-remove-test

This commit is contained in:
Luciano Lo Giudice
2022-03-21 18:39:59 -03:00
38 changed files with 788 additions and 214 deletions
@@ -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")
@@ -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():
@@ -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(
@@ -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")
@@ -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))
})
@@ -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)
@@ -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]
+19 -9
View File
@@ -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):
@@ -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."""
@@ -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)
@@ -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)
}}
@@ -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"])
@@ -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,
})
+10 -7
View File
@@ -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
})
+2 -1
View File
@@ -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
+4 -7
View File
@@ -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")
@@ -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'
+1 -1
View File
@@ -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()
@@ -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():
+27 -12
View File
@@ -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
@@ -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:
@@ -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
@@ -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)
@@ -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",
@@ -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 = '<option value="{0}">{1}</option>'.format(
@@ -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 = []
@@ -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:
+59
View File
@@ -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."""
+40 -1
View File
@@ -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,
+2 -1
View File
@@ -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.
+12
View File
@@ -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
+20 -1
View File
@@ -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)
-66
View File
@@ -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)
+24 -4
View File
@@ -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()
@@ -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
+102
View File
@@ -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: <string>
"""
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
+67 -1
View File
@@ -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)
+35 -3
View File
@@ -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,