Merge remote-tracking branch 'upstream/master'

This commit is contained in:
camille.rodriguez
2020-07-24 09:15:43 -05:00
34 changed files with 905 additions and 823 deletions

View File

@@ -30,3 +30,15 @@ class TestOpenStackBaseTest(unittest.TestCase):
MyTestClass.setUpClass('foo', 'bar')
_setUpClass.assert_called_with('foo', 'bar')
class TestUtils(unittest.TestCase):
def test_format_addr(self):
self.assertEquals('1.2.3.4', test_utils.format_addr('1.2.3.4'))
self.assertEquals(
'[2001:db8::42]', test_utils.format_addr('2001:db8::42'))
with self.assertRaises(ValueError):
test_utils.format_addr('999.999.999.999')
with self.assertRaises(ValueError):
test_utils.format_addr('2001:db8::g')

View File

@@ -1,312 +0,0 @@
# Copyright 2018 Canonical Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import mock
import unit_tests.utils as ut_utils
from zaza.openstack.utilities import juju as juju_utils
class TestJujuUtils(ut_utils.BaseTestCase):
def setUp(self):
super(TestJujuUtils, self).setUp()
# Juju Status Object and data
self.key = "instance-id"
self.key_data = "machine-uuid"
self.machine = "1"
self.machine_data = {self.key: self.key_data}
self.unit = "app/1"
self.unit_data = {"machine": self.machine}
self.application = "app"
self.application_data = {"units": {self.unit: self.unit_data}}
self.subordinate_application = "subordinate_application"
self.subordinate_application_data = {
"subordinate-to": [self.application]}
self.juju_status = mock.MagicMock()
self.juju_status.name = "juju_status_object"
self.juju_status.applications.get.return_value = self.application_data
self.juju_status.machines.get.return_value = self.machine_data
# Model
self.patch_object(juju_utils, "model")
self.model_name = "model-name"
self.model.get_juju_model.return_value = self.model_name
self.model.get_status.return_value = self.juju_status
self.run_output = {"Code": "0", "Stderr": "", "Stdout": "RESULT"}
self.error_run_output = {"Code": "1", "Stderr": "ERROR", "Stdout": ""}
self.model.run_on_unit.return_value = self.run_output
# Clouds
self.cloud_name = "FakeCloudName"
self.cloud_type = "FakeCloudType"
self.clouds = {
"clouds":
{self.cloud_name:
{"type": self.cloud_type}}}
# Controller
self.patch_object(juju_utils, "controller")
self.controller.get_cloud.return_value = self.cloud_name
def test_get_application_status(self):
self.patch_object(juju_utils, "get_full_juju_status")
self.get_full_juju_status.return_value = self.juju_status
# Full status juju object return
self.assertEqual(
juju_utils.get_application_status(), self.juju_status)
self.get_full_juju_status.assert_called_once()
# Application only dictionary return
self.assertEqual(
juju_utils.get_application_status(application=self.application),
self.application_data)
# Unit no application dictionary return
self.assertEqual(
juju_utils.get_application_status(unit=self.unit),
self.unit_data)
def test_get_cloud_configs(self):
self.patch_object(juju_utils.Path, "home")
self.patch_object(juju_utils.generic_utils, "get_yaml_config")
self.get_yaml_config.return_value = self.clouds
# All the cloud configs
self.assertEqual(juju_utils.get_cloud_configs(), self.clouds)
# With cloud specified
self.assertEqual(juju_utils.get_cloud_configs(self.cloud_name),
self.clouds["clouds"][self.cloud_name])
def test_get_full_juju_status(self):
self.assertEqual(juju_utils.get_full_juju_status(), self.juju_status)
self.model.get_status.assert_called_once_with(model_name=None)
def test_get_machines_for_application(self):
self.patch_object(juju_utils, "get_application_status")
self.get_application_status.return_value = self.application_data
# Machine data
self.assertEqual(
next(juju_utils.get_machines_for_application(self.application)),
self.machine)
self.get_application_status.assert_called_once()
# Subordinate application has no units
def _get_application_status(application, model_name=None):
_apps = {
self.application: self.application_data,
self.subordinate_application:
self.subordinate_application_data}
return _apps[application]
self.get_application_status.side_effect = _get_application_status
self.assertEqual(
next(juju_utils.get_machines_for_application(
self.subordinate_application)),
self.machine)
def test_get_unit_name_from_host_name(self):
unit_mock1 = mock.MagicMock()
unit_mock1.data = {'machine-id': 12}
unit_mock1.entity_id = 'myapp/2'
unit_mock2 = mock.MagicMock()
unit_mock2.data = {'machine-id': 15}
unit_mock2.entity_id = 'myapp/5'
self.model.get_units.return_value = [unit_mock1, unit_mock2]
self.assertEqual(
juju_utils.get_unit_name_from_host_name('juju-model-12', 'myapp'),
'myapp/2')
def test_get_machine_status(self):
self.patch_object(juju_utils, "get_full_juju_status")
self.get_full_juju_status.return_value = self.juju_status
# All machine data
self.assertEqual(
juju_utils.get_machine_status(self.machine),
self.machine_data)
self.get_full_juju_status.assert_called_once()
# Request a specific key
self.assertEqual(
juju_utils.get_machine_status(self.machine, self.key),
self.key_data)
def test_get_machine_uuids_for_application(self):
self.patch_object(juju_utils, "get_machines_for_application")
self.get_machines_for_application.return_value = [self.machine]
self.assertEqual(
next(juju_utils.get_machine_uuids_for_application(
self.application)),
self.machine_data.get("instance-id"))
self.get_machines_for_application.assert_called_once_with(
self.application, model_name=None)
def test_get_provider_type(self):
self.patch_object(juju_utils, "get_cloud_configs")
self.get_cloud_configs.return_value = {"type": self.cloud_type}
self.assertEqual(juju_utils.get_provider_type(),
self.cloud_type)
self.get_cloud_configs.assert_called_once_with(self.cloud_name)
def test_remote_run(self):
_cmd = "do the thing"
# Success
self.assertEqual(juju_utils.remote_run(self.unit, _cmd),
self.run_output["Stdout"])
self.model.run_on_unit.assert_called_once_with(
self.unit, _cmd, timeout=None, model_name=None)
# Non-fatal failure
self.model.run_on_unit.return_value = self.error_run_output
self.assertEqual(
juju_utils.remote_run(
self.unit,
_cmd,
fatal=False,
model_name=None),
self.error_run_output["Stderr"])
# Fatal failure
with self.assertRaises(Exception):
juju_utils.remote_run(self.unit, _cmd, fatal=True)
def test_get_unit_names(self):
self.patch('zaza.model.get_first_unit_name', new_callable=mock.Mock(),
name='_get_first_unit_name')
juju_utils._get_unit_names(['aunit/0', 'otherunit/0'])
self.assertFalse(self._get_first_unit_name.called)
def test_get_unit_names_called_with_application_name(self):
self.patch_object(juju_utils, 'model')
juju_utils._get_unit_names(['aunit', 'otherunit/0'])
self.model.get_first_unit_name.assert_called()
def test_get_relation_from_unit(self):
self.patch_object(juju_utils, '_get_unit_names')
self.patch_object(juju_utils, 'yaml')
self.patch_object(juju_utils, 'model')
self._get_unit_names.return_value = ['aunit/0', 'otherunit/0']
data = {'foo': 'bar'}
self.model.get_relation_id.return_value = 42
self.model.run_on_unit.return_value = {'Code': 0, 'Stdout': str(data)}
juju_utils.get_relation_from_unit('aunit/0', 'otherunit/0',
'arelation')
self.model.run_on_unit.assert_called_with(
'aunit/0',
'relation-get --format=yaml -r "42" - "otherunit/0"',
model_name=None)
self.yaml.safe_load.assert_called_with(str(data))
def test_get_relation_from_unit_fails(self):
self.patch_object(juju_utils, '_get_unit_names')
self.patch_object(juju_utils, 'yaml')
self.patch_object(juju_utils, 'model')
self._get_unit_names.return_value = ['aunit/0', 'otherunit/0']
self.model.get_relation_id.return_value = 42
self.model.run_on_unit.return_value = {'Code': 1, 'Stderr': 'ERROR'}
with self.assertRaises(Exception):
juju_utils.get_relation_from_unit('aunit/0', 'otherunit/0',
'arelation')
self.model.run_on_unit.assert_called_with(
'aunit/0',
'relation-get --format=yaml -r "42" - "otherunit/0"',
model_name=None)
self.assertFalse(self.yaml.safe_load.called)
def test_leader_get(self):
self.patch_object(juju_utils, 'yaml')
self.patch_object(juju_utils, 'model')
data = {'foo': 'bar'}
self.model.run_on_leader.return_value = {
'Code': 0, 'Stdout': str(data)}
juju_utils.leader_get('application')
self.model.run_on_leader.assert_called_with(
'application', 'leader-get --format=yaml ', model_name=None)
self.yaml.safe_load.assert_called_with(str(data))
def test_leader_get_key(self):
self.patch_object(juju_utils, 'yaml')
self.patch_object(juju_utils, 'model')
data = {'foo': 'bar'}
self.model.run_on_leader.return_value = {
'Code': 0, 'Stdout': data['foo']}
juju_utils.leader_get('application', 'foo')
self.model.run_on_leader.assert_called_with(
'application', 'leader-get --format=yaml foo', model_name=None)
self.yaml.safe_load.assert_called_with(data['foo'])
def test_leader_get_fails(self):
self.patch_object(juju_utils, 'yaml')
self.patch_object(juju_utils, 'model')
self.model.run_on_leader.return_value = {
'Code': 1, 'Stderr': 'ERROR'}
with self.assertRaises(Exception):
juju_utils.leader_get('application')
self.model.run_on_leader.assert_called_with(
'application', 'leader-get --format=yaml ',
model_name=None)
self.assertFalse(self.yaml.safe_load.called)
def test_get_machine_series(self):
self.patch(
'zaza.openstack.utilities.juju.get_machine_status',
new_callable=mock.MagicMock(),
name='_get_machine_status'
)
self._get_machine_status.return_value = 'xenial'
expected = 'xenial'
actual = juju_utils.get_machine_series('6')
self._get_machine_status.assert_called_with(
machine='6',
key='series',
model_name=None
)
self.assertEqual(expected, actual)
def test_get_subordinate_units(self):
juju_status = mock.MagicMock()
juju_status.applications = {
'nova-compute': {
'units': {
'nova-compute/0': {
'subordinates': {
'neutron-openvswitch/2': {
'charm': 'cs:neutron-openvswitch-22'}}}}},
'cinder': {
'units': {
'cinder/1': {
'subordinates': {
'cinder-hacluster/0': {
'charm': 'cs:hacluster-42'},
'cinder-ceph/3': {
'charm': 'cs:cinder-ceph-2'}}}}},
}
self.assertEqual(
sorted(juju_utils.get_subordinate_units(
['nova-compute/0', 'cinder/1'],
status=juju_status)),
sorted(['neutron-openvswitch/2', 'cinder-hacluster/0',
'cinder-ceph/3']))
self.assertEqual(
juju_utils.get_subordinate_units(
['nova-compute/0', 'cinder/1'],
charm_name='ceph',
status=juju_status),
['cinder-ceph/3'])

View File

@@ -581,21 +581,27 @@ class TestOpenStackUtils(ut_utils.BaseTestCase):
nova_mock.keypairs.create.assert_called_once_with(name='mykeys')
def test_get_private_key_file(self):
self.patch_object(openstack_utils.deployment_env, 'get_tmpdir',
return_value='/tmp/zaza-model1')
self.assertEqual(
openstack_utils.get_private_key_file('mykeys'),
'tests/id_rsa_mykeys')
'/tmp/zaza-model1/id_rsa_mykeys')
def test_write_private_key(self):
self.patch_object(openstack_utils.deployment_env, 'get_tmpdir',
return_value='/tmp/zaza-model1')
m = mock.mock_open()
with mock.patch(
'zaza.openstack.utilities.openstack.open', m, create=False
):
openstack_utils.write_private_key('mykeys', 'keycontents')
m.assert_called_once_with('tests/id_rsa_mykeys', 'w')
m.assert_called_once_with('/tmp/zaza-model1/id_rsa_mykeys', 'w')
handle = m()
handle.write.assert_called_once_with('keycontents')
def test_get_private_key(self):
self.patch_object(openstack_utils.deployment_env, 'get_tmpdir',
return_value='/tmp/zaza-model1')
self.patch_object(openstack_utils.os.path, "isfile",
return_value=True)
m = mock.mock_open(read_data='myprivkey')
@@ -607,6 +613,8 @@ class TestOpenStackUtils(ut_utils.BaseTestCase):
'myprivkey')
def test_get_private_key_file_missing(self):
self.patch_object(openstack_utils.deployment_env, 'get_tmpdir',
return_value='/tmp/zaza-model1')
self.patch_object(openstack_utils.os.path, "isfile",
return_value=False)
self.assertIsNone(openstack_utils.get_private_key('mykeys'))
@@ -765,7 +773,7 @@ class TestOpenStackUtils(ut_utils.BaseTestCase):
privkey='myprivkey')
paramiko_mock.connect.assert_called_once_with(
'10.0.0.10',
password='',
password=None,
pkey='akey',
username='bob')
@@ -809,12 +817,12 @@ class TestOpenStackUtils(ut_utils.BaseTestCase):
name='_get_os_version'
)
self.patch(
'zaza.openstack.utilities.juju.get_machines_for_application',
'zaza.utilities.juju.get_machines_for_application',
new_callable=mock.MagicMock(),
name='_get_machines'
)
self.patch(
'zaza.openstack.utilities.juju.get_machine_series',
'zaza.utilities.juju.get_machine_series',
new_callable=mock.MagicMock(),
name='_get_machine_series'
)

View File

@@ -101,7 +101,7 @@ class CeilometerTest(test_utils.OpenStackBaseTest):
def test_400_api_connection(self):
"""Simple api calls to check service is up and responding."""
if self.current_release >= CeilometerTest.XENIAL_PIKE:
if self.current_release >= CeilometerTest.XENIAL_OCATA:
logging.info('Skipping API checks as ceilometer api has been '
'removed')
return

View File

@@ -14,10 +14,10 @@
"""Encapsulate CephFS testing."""
import logging
from tenacity import Retrying, stop_after_attempt, wait_exponential
import zaza.model as model
import zaza.openstack.charm_tests.glance.setup as glance_setup
import zaza.openstack.charm_tests.neutron.tests as neutron_tests
import zaza.openstack.charm_tests.nova.utils as nova_utils
import zaza.openstack.charm_tests.test_utils as test_utils
@@ -63,27 +63,10 @@ write_files:
conf = model.run_on_leader(
'ceph-mon', 'cat /etc/ceph/ceph.conf')['Stdout']
# Spawn Servers
for attempt in Retrying(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=2, max=10)):
with attempt:
instance_1 = guest.launch_instance(
glance_setup.LTS_IMAGE_NAME,
vm_name='{}-ins-1'.format(self.RESOURCE_PREFIX),
userdata=self.INSTANCE_USERDATA.format(
_indent(conf, 8),
_indent(keyring, 8)))
for attempt in Retrying(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=2, max=10)):
with attempt:
instance_2 = guest.launch_instance(
glance_setup.LTS_IMAGE_NAME,
vm_name='{}-ins-2'.format(self.RESOURCE_PREFIX),
userdata=self.INSTANCE_USERDATA.format(
_indent(conf, 8),
_indent(keyring, 8)))
instance_1, instance_2 = self.launch_guests(
userdata=self.INSTANCE_USERDATA.format(
_indent(conf, 8),
_indent(keyring, 8)))
# Write a file on instance_1
def verify_setup(stdin, stdout, stderr):
@@ -124,3 +107,18 @@ write_files:
def _indent(text, amount, ch=' '):
padding = amount * ch
return ''.join(padding+line for line in text.splitlines(True))
class CharmOperationTest(test_utils.BaseCharmTest):
"""CephFS Charm operation tests."""
def test_pause_resume(self):
"""Run pause and resume tests.
Pause service and check services are stopped, then resume and check
they are started.
"""
services = ['ceph-mds']
with self.pause_resume(services):
logging.info('Testing pause resume (services="{}")'
.format(services))

View File

@@ -14,7 +14,23 @@
"""Setup for ceph-osd deployments."""
import logging
import zaza.model
def basic_setup():
"""Run basic setup for ceph-osd."""
pass
def ceph_ready():
"""Wait for ceph to be ready.
Wait for ceph to be ready. This is useful if the target_deploy_status in
the tests.yaml is expecting ceph to be in a blocked state. After ceph
has been unblocked the deploy may need to wait for ceph to be ready.
"""
logging.info("Waiting for ceph units to settle")
zaza.model.wait_for_application_states()
zaza.model.block_until_all_units_idle()
logging.info("Ceph units settled")

View File

@@ -544,7 +544,7 @@ class CephRGWTest(test_utils.OpenStackBaseTest):
@classmethod
def setUpClass(cls):
"""Run class setup for running ceph low level tests."""
super(CephRGWTest, cls).setUpClass()
super(CephRGWTest, cls).setUpClass(application_name='ceph-radosgw')
@property
def expected_apps(self):
@@ -622,7 +622,9 @@ class CephRGWTest(test_utils.OpenStackBaseTest):
'multisite configuration')
logging.info('Checking Swift REST API')
keystone_session = zaza_openstack.get_overcloud_keystone_session()
region_name = 'RegionOne'
region_name = zaza_model.get_application_config(
self.application_name,
model_name=self.model_name)['region']['value']
swift_client = zaza_openstack.get_swift_session_client(
keystone_session,
region_name,

View File

@@ -217,12 +217,22 @@ class CinderTests(test_utils.OpenStackBaseTest):
@property
def services(self):
"""Return a list services for the selected OpenStack release."""
services = ['cinder-scheduler', 'cinder-volume']
if (openstack_utils.get_os_release() >=
openstack_utils.get_os_release('xenial_ocata')):
services.append('apache2')
current_value = zaza.model.get_application_config(
self.application_name)['enabled-services']['value']
if current_value == "all":
services = ['cinder-scheduler', 'cinder-volume', 'cinder-api']
else:
services.append('cinder-api')
services = ['cinder-{}'.format(svc)
for svc in ('api', 'scheduler', 'volume')
if svc in current_value]
if ('cinder-api' in services and
(openstack_utils.get_os_release() >=
openstack_utils.get_os_release('xenial_ocata'))):
services.remove('cinder-api')
services.append('apache2')
return services
def test_900_restart_on_config_change(self):
@@ -246,13 +256,7 @@ class CinderTests(test_utils.OpenStackBaseTest):
Pause service and check services are stopped then resume and check
they are started
"""
services = ['cinder-scheduler', 'cinder-volume']
if (openstack_utils.get_os_release() >=
openstack_utils.get_os_release('xenial_ocata')):
services.append('apache2')
else:
services.append('cinder-api')
with self.pause_resume(services):
with self.pause_resume(self.services):
logging.info("Testing pause resume")

View File

@@ -16,6 +16,7 @@
import logging
import zaza.openstack.utilities.openstack as openstack_utils
import zaza.utilities.deployment_env as deployment_env
CIRROS_IMAGE_NAME = "cirros"
CIRROS_ALT_IMAGE_NAME = "cirros_alt"
@@ -31,7 +32,8 @@ def basic_setup():
"""
def add_image(image_url, glance_client=None, image_name=None, tags=[]):
def add_image(image_url, glance_client=None, image_name=None, tags=[],
properties=None):
"""Retrieve image from ``image_url`` and add it to glance.
:param image_url: Retrievable URL with image data
@@ -42,6 +44,8 @@ def add_image(image_url, glance_client=None, image_name=None, tags=[]):
:type image_name: str
:param tags: List of tags to add to image
:type tags: list of str
:param properties: Properties to add to image
:type properties: dict
"""
if not glance_client:
keystone_session = openstack_utils.get_overcloud_keystone_session()
@@ -60,7 +64,8 @@ def add_image(image_url, glance_client=None, image_name=None, tags=[]):
glance_client,
image_url,
image_name,
tags=tags)
tags=tags,
properties=properties)
def add_cirros_image(glance_client=None, image_name=None):
@@ -90,7 +95,8 @@ def add_cirros_alt_image(glance_client=None, image_name=None):
add_cirros_image(glance_client, image_name)
def add_lts_image(glance_client=None, image_name=None, release=None):
def add_lts_image(glance_client=None, image_name=None, release=None,
properties=None):
"""Add an Ubuntu LTS image to the current deployment.
:param glance: Authenticated glanceclient
@@ -99,12 +105,22 @@ def add_lts_image(glance_client=None, image_name=None, release=None):
:type image_name: str
:param release: Name of ubuntu release.
:type release: str
:param properties: Custom image properties
:type properties: dict
"""
deploy_ctxt = deployment_env.get_deployment_context()
image_arch = deploy_ctxt.get('TEST_IMAGE_ARCH', 'amd64')
arch_image_properties = {
'arm64': {'hw_firmware_type': 'uefi'},
'ppc64el': {'architecture': 'ppc64'}}
properties = properties or arch_image_properties.get(image_arch)
logging.info("Image architecture set to {}".format(image_arch))
image_name = image_name or LTS_IMAGE_NAME
release = release or LTS_RELEASE
image_url = openstack_utils.find_ubuntu_image(
release=release,
arch='amd64')
arch=image_arch)
add_image(image_url,
glance_client=glance_client,
image_name=image_name)
image_name=image_name,
properties=properties)

View File

@@ -0,0 +1,40 @@
#!/usr/bin/env python3
# Copyright 2019 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.
"""Code for configuring glance-simplestreams-sync."""
import logging
import zaza.model as zaza_model
import zaza.openstack.utilities.generic as generic_utils
def sync_images():
"""Run image sync using an action.
Execute an initial image sync using an action to ensure that the
cloud is populated with images at the right point in time during
deployment.
"""
logging.info("Synchronising images using glance-simplestreams-sync")
generic_utils.assertActionRanOK(
zaza_model.run_action_on_leader(
"glance-simplestreams-sync",
"sync-images",
raise_on_failure=True,
action_params={},
)
)

View File

@@ -24,7 +24,7 @@ import zaza.openstack.utilities.openstack as openstack_utils
@tenacity.retry(
retry=tenacity.retry_if_result(lambda images: len(images) < 3),
retry=tenacity.retry_if_result(lambda images: len(images) < 4),
wait=tenacity.wait_fixed(6), # interval between retries
stop=tenacity.stop_after_attempt(100)) # retry times
def retry_image_sync(glance_client):
@@ -42,7 +42,7 @@ def get_product_streams(url):
# There is a race between the images being available in glance and any
# metadata being written. Use tenacity to avoid this race.
client = requests.session()
json_data = client.get(url).text
json_data = client.get(url, verify=openstack_utils.get_cacert()).text
return json.loads(json_data)
@@ -61,7 +61,7 @@ class GlanceSimpleStreamsSyncTest(test_utils.OpenStackBaseTest):
cls.keystone_session)
def test_010_wait_for_image_sync(self):
"""Wait for images to be synced. Expect at least three."""
"""Wait for images to be synced. Expect at least four."""
self.assertTrue(retry_image_sync(self.glance_client))
def test_050_gss_permissions_regression_check_lp1611987(self):
@@ -94,31 +94,34 @@ class GlanceSimpleStreamsSyncTest(test_utils.OpenStackBaseTest):
'com.ubuntu.cloud:server:14.04:amd64',
'com.ubuntu.cloud:server:16.04:amd64',
'com.ubuntu.cloud:server:18.04:amd64',
'com.ubuntu.cloud:server:20.04:amd64',
]
uri = "streams/v1/auto.sync.json"
key = "url"
xenial_pike = openstack_utils.get_os_release('xenial_pike')
if openstack_utils.get_os_release() <= xenial_pike:
key = "publicURL"
catalog = self.keystone_client.service_catalog.get_endpoints()
ps_interface = catalog["product-streams"][0][key]
url = "{}/{}".format(ps_interface, uri)
# There is a race between the images being available in glance and the
# metadata being written for each image. Use tenacity to avoid this
# race and make the test idempotent.
@tenacity.retry(
retry=tenacity.retry_if_exception_type(AssertionError),
retry=tenacity.retry_if_exception_type(
(AssertionError, KeyError)
),
wait=tenacity.wait_fixed(10), reraise=True,
stop=tenacity.stop_after_attempt(10))
def _check_local_product_streams(url, expected_images):
stop=tenacity.stop_after_attempt(25))
def _check_local_product_streams(expected_images):
# Refresh from catalog as URL may change if swift in use.
ps_interface = self.keystone_client.service_catalog.url_for(
service_type='product-streams', interface='publicURL'
)
url = "{}/{}".format(ps_interface, uri)
logging.info('Retrieving product stream information'
' from {}'.format(url))
product_streams = get_product_streams(url)
logging.debug(product_streams)
images = product_streams["products"]
for image in expected_images:
self.assertIn(image, images)
_check_local_product_streams(url, expected_images)
_check_local_product_streams(expected_images)
logging.debug("Local product stream successful")

View File

@@ -14,8 +14,12 @@
"""Code for setting up keystone."""
import logging
import keystoneauth1
import zaza.charm_lifecycle.utils as lifecycle_utils
import zaza.model
import zaza.openstack.utilities.openstack as openstack_utils
from zaza.openstack.charm_tests.keystone import (
BaseKeystoneTest,
@@ -30,6 +34,25 @@ from zaza.openstack.charm_tests.keystone import (
)
def wait_for_cacert(model_name=None):
"""Wait for keystone to install a cacert.
:param model_name: Name of model to query.
:type model_name: str
"""
logging.info("Waiting for cacert")
zaza.model.block_until_file_has_contents(
'keystone',
openstack_utils.KEYSTONE_REMOTE_CACERT,
'CERTIFICATE',
model_name=model_name)
zaza.model.block_until_all_units_idle(model_name=model_name)
test_config = lifecycle_utils.get_charm_config(fatal=False)
zaza.model.wait_for_application_states(
states=test_config.get('target_deploy_status', {}),
model_name=model_name)
def add_demo_user():
"""Add a demo user to the current deployment."""
def _v2():

View File

@@ -21,7 +21,7 @@ import keystoneauth1
import zaza.model
import zaza.openstack.utilities.exceptions as zaza_exceptions
import zaza.openstack.utilities.juju as juju_utils
import zaza.utilities.juju as juju_utils
import zaza.openstack.utilities.openstack as openstack_utils
import zaza.charm_lifecycle.utils as lifecycle_utils
import zaza.openstack.charm_tests.test_utils as test_utils
@@ -262,6 +262,7 @@ class AuthenticationAuthorizationTest(BaseKeystoneTest):
openrc['OS_CACERT'] = openstack_utils.KEYSTONE_LOCAL_CACERT
openrc['OS_AUTH_URL'] = (
openrc['OS_AUTH_URL'].replace('http', 'https'))
logging.info('keystone IP {}'.format(ip))
keystone_session = openstack_utils.get_keystone_session(
openrc)
keystone_client = openstack_utils.get_keystone_session_client(
@@ -319,10 +320,7 @@ class AuthenticationAuthorizationTest(BaseKeystoneTest):
'OS_PROJECT_DOMAIN_NAME': DEMO_DOMAIN,
'OS_PROJECT_NAME': DEMO_PROJECT,
}
with self.config_change(
{'preferred-api-version': self.default_api_version},
{'preferred-api-version': self.api_v3},
application_name="keystone"):
with self.v3_keystone_preferred():
for ip in self.keystone_ips:
openrc.update(
{'OS_AUTH_URL': 'http://{}:5000/v3'.format(ip)})

View File

@@ -38,7 +38,7 @@ class MasakariTest(test_utils.OpenStackBaseTest):
@classmethod
def setUpClass(cls):
"""Run class setup for running tests."""
super(MasakariTest, cls).setUpClass()
super(MasakariTest, cls).setUpClass(application_name="masakari")
cls.current_release = openstack_utils.get_os_release()
cls.keystone_session = openstack_utils.get_overcloud_keystone_session()
cls.model_name = zaza.model.get_juju_model()
@@ -134,6 +134,26 @@ class MasakariTest(test_utils.OpenStackBaseTest):
vm_uuid,
model_name=self.model_name)
@tenacity.retry(wait=tenacity.wait_exponential(multiplier=2, max=60),
reraise=True, stop=tenacity.stop_after_attempt(5),
retry=tenacity.retry_if_exception_type(AssertionError))
def wait_for_guest_ready(self, vm_name):
"""Wait for the guest to be ready.
:param vm_name: Name of guest to check.
:type vm_name: str
"""
guest_ready_attr_checks = [
('OS-EXT-STS:task_state', None),
('status', 'ACTIVE'),
('OS-EXT-STS:power_state', 1),
('OS-EXT-STS:vm_state', 'active')]
guest = self.nova_client.servers.find(name=vm_name)
logging.info('Checking guest {} attributes'.format(vm_name))
for (attr, required_state) in guest_ready_attr_checks:
logging.info('Checking {} is {}'.format(attr, required_state))
assert getattr(guest, attr) == required_state
def test_instance_failover(self):
"""Test masakari managed guest migration."""
# Workaround for Bug #1874719
@@ -168,6 +188,7 @@ class MasakariTest(test_utils.OpenStackBaseTest):
model_name=self.model_name)
openstack_utils.enable_all_nova_services(self.nova_client)
zaza.openstack.configure.masakari.enable_hosts()
self.wait_for_guest_ready(vm_name)
def test_instance_restart_on_fail(self):
"""Test single guest crash and recovery."""
@@ -178,6 +199,7 @@ class MasakariTest(test_utils.OpenStackBaseTest):
self.current_release))
vm_name = 'zaza-test-instance-failover'
vm = self.ensure_guest(vm_name)
self.wait_for_guest_ready(vm_name)
_, unit_name = self.get_guests_compute_info(vm_name)
logging.info('{} is running on {}'.format(vm_name, unit_name))
guest_pid = self.get_guest_qemu_pid(

View File

@@ -149,7 +149,7 @@ class MySQLCommonTests(MySQLBaseTest):
set_alternate = {"max-connections": "1000"}
# Make config change, check for service restarts
logging.debug("Setting max connections ...")
logging.info("Setting max connections ...")
self.restart_on_changed(
self.conf_file,
set_default,
@@ -198,7 +198,7 @@ class PerconaClusterBaseTest(MySQLBaseTest):
output = zaza.model.run_on_leader(
self.application, cmd)["Stdout"].strip()
value = re.search(r"^.+?\s+(.+)", output).group(1)
logging.debug("%s = %s" % (attr, value))
logging.info("%s = %s" % (attr, value))
return value
def is_pxc_bootstrapped(self):
@@ -236,7 +236,7 @@ class PerconaClusterBaseTest(MySQLBaseTest):
cmd = "ip -br addr"
result = zaza.model.run_on_unit(unit.entity_id, cmd)
output = result.get("Stdout").strip()
logging.debug(output)
logging.info(output)
if self.vip in output:
logging.info("vip ({}) running in {}".format(
self.vip,
@@ -333,12 +333,12 @@ class PerconaClusterColdStartTest(PerconaClusterBaseTest):
juju_utils.get_machine_uuids_for_application(self.application))
# Stop Nodes
# Avoid hitting an update-status hook
logging.debug("Wait till model is idle ...")
logging.info("Wait till model is idle ...")
zaza.model.block_until_all_units_idle()
logging.info("Stopping instances: {}".format(_machines))
for uuid in _machines:
self.nova_client.servers.stop(uuid)
logging.debug("Wait till all machines are shutoff ...")
logging.info("Wait till all machines are shutoff ...")
for uuid in _machines:
openstack_utils.resource_reaches_status(self.nova_client.servers,
uuid,
@@ -357,7 +357,7 @@ class PerconaClusterColdStartTest(PerconaClusterBaseTest):
'unknown',
negate_match=True)
logging.debug("Wait till model is idle ...")
logging.info("Wait till model is idle ...")
# XXX If a hook was executing on a unit when it was powered off
# it comes back in an error state.
try:
@@ -366,7 +366,7 @@ class PerconaClusterColdStartTest(PerconaClusterBaseTest):
self.resolve_update_status_errors()
zaza.model.block_until_all_units_idle()
logging.debug("Wait for application states ...")
logging.info("Wait for application states ...")
for unit in zaza.model.get_units(self.application):
try:
zaza.model.run_on_unit(unit.entity_id, "hooks/update-status")
@@ -389,7 +389,7 @@ class PerconaClusterColdStartTest(PerconaClusterBaseTest):
_non_leaders[0],
"bootstrap-pxc",
action_params={})
logging.debug("Wait for application states ...")
logging.info("Wait for application states ...")
for unit in zaza.model.get_units(self.application):
zaza.model.run_on_unit(unit.entity_id, "hooks/update-status")
states = {"percona-cluster": {
@@ -403,7 +403,7 @@ class PerconaClusterColdStartTest(PerconaClusterBaseTest):
self.application,
"notify-bootstrapped",
action_params={})
logging.debug("Wait for application states ...")
logging.info("Wait for application states ...")
for unit in zaza.model.get_units(self.application):
zaza.model.run_on_unit(unit.entity_id, "hooks/update-status")
test_config = lifecycle_utils.get_charm_config(fatal=False)
@@ -521,7 +521,7 @@ class MySQLInnoDBClusterColdStartTest(MySQLBaseTest):
zaza.model.resolve_units(
application_name=self.application,
erred_hook='update-status',
wait=True)
wait=True, timeout=180)
def test_100_reboot_cluster_from_complete_outage(self):
"""Reboot cluster from complete outage.
@@ -532,12 +532,12 @@ class MySQLInnoDBClusterColdStartTest(MySQLBaseTest):
juju_utils.get_machine_uuids_for_application(self.application))
# Stop Nodes
# Avoid hitting an update-status hook
logging.debug("Wait till model is idle ...")
logging.info("Wait till model is idle ...")
zaza.model.block_until_all_units_idle()
logging.info("Stopping instances: {}".format(_machines))
for uuid in _machines:
self.nova_client.servers.stop(uuid)
logging.debug("Wait till all machines are shutoff ...")
logging.info("Wait till all machines are shutoff ...")
for uuid in _machines:
openstack_utils.resource_reaches_status(self.nova_client.servers,
uuid,
@@ -550,38 +550,37 @@ class MySQLInnoDBClusterColdStartTest(MySQLBaseTest):
for uuid in _machines:
self.nova_client.servers.start(uuid)
logging.info(
"Wait till all {} units are in state 'unkown' ..."
.format(self.application))
for unit in zaza.model.get_units(self.application):
zaza.model.block_until_unit_wl_status(
unit.entity_id,
'unknown',
negate_match=True)
logging.debug("Wait till model is idle ...")
logging.info("Wait till model is idle ...")
try:
zaza.model.block_until_all_units_idle()
except zaza.model.UnitError:
self.resolve_update_status_errors()
zaza.model.block_until_all_units_idle()
logging.debug("Clear error hooks after reboot ...")
logging.info("Clear error hooks after reboot ...")
for unit in zaza.model.get_units(self.application):
try:
zaza.model.run_on_unit(unit.entity_id, "hooks/update-status")
except zaza.model.UnitError:
self.resolve_update_status_errors()
zaza.model.run_on_unit(unit.entity_id, "hooks/update-status")
logging.debug("Wait for application states blocked ...")
states = {
self.application: {
"workload-status": "blocked",
"workload-status-message":
"MySQL InnoDB Cluster not healthy: None"},
"mysql-router": {
"workload-status": "blocked",
"workload-status-message":
"Failed to connect to MySQL"}}
zaza.model.wait_for_application_states(states=states)
logging.info(
"Wait till all {} units are in state 'blocked' ..."
.format(self.application))
for unit in zaza.model.get_units(self.application):
zaza.model.block_until_unit_wl_status(
unit.entity_id,
'blocked')
logging.info("Execute reboot-cluster-from-complete-outage "
"action after cold boot ...")
@@ -592,15 +591,15 @@ class MySQLInnoDBClusterColdStartTest(MySQLBaseTest):
unit.entity_id,
"reboot-cluster-from-complete-outage",
action_params={})
if "Success" in action.data["results"].get("outcome"):
if "Success" in action.data.get("results", {}).get("outcome", ""):
break
else:
logging.info(action.data["results"].get("output"))
logging.info(action.data.get("results", {}).get("output", ""))
assert "Success" in action.data["results"]["outcome"], (
"Reboot cluster from complete outage action failed: {}"
.format(action.data))
logging.debug("Wait for application states ...")
logging.info("Wait for application states ...")
for unit in zaza.model.get_units(self.application):
zaza.model.run_on_unit(unit.entity_id, "hooks/update-status")
test_config = lifecycle_utils.get_charm_config(fatal=False)

View File

@@ -25,10 +25,7 @@ import logging
import tenacity
import unittest
import novaclient
import zaza
import zaza.openstack.charm_tests.glance.setup as glance_setup
import zaza.openstack.charm_tests.nova.utils as nova_utils
import zaza.openstack.charm_tests.test_utils as test_utils
import zaza.openstack.configure.guest as guest
@@ -292,36 +289,44 @@ class NeutronCreateNetworkTest(test_utils.OpenStackBaseTest):
# set up clients
cls.neutron_client = (
openstack_utils.get_neutron_session_client(cls.keystone_session))
cls.neutron_client.format = 'json'
_TEST_NET_NAME = 'test_net'
def test_400_create_network(self):
"""Create a network, verify that it exists, and then delete it."""
self._assert_test_network_doesnt_exist()
self._create_test_network()
net_id = self._assert_test_network_exists_and_return_id()
self._delete_test_network(net_id)
self._assert_test_network_doesnt_exist()
def _create_test_network(self):
logging.debug('Creating neutron network...')
self.neutron_client.format = 'json'
net_name = 'test_net'
# Verify that the network doesn't exist
networks = self.neutron_client.list_networks(name=net_name)
net_count = len(networks['networks'])
assert net_count == 0, (
"Expected zero networks, found {}".format(net_count))
# Create a network and verify that it exists
network = {'name': net_name}
network = {'name': self._TEST_NET_NAME}
self.neutron_client.create_network({'network': network})
networks = self.neutron_client.list_networks(name=net_name)
def _delete_test_network(self, net_id):
logging.debug('Deleting neutron network...')
self.neutron_client.delete_network(net_id)
def _assert_test_network_exists_and_return_id(self):
logging.debug('Confirming new neutron network...')
networks = self.neutron_client.list_networks(name=self._TEST_NET_NAME)
logging.debug('Networks: {}'.format(networks))
net_len = len(networks['networks'])
assert net_len == 1, (
"Expected 1 network, found {}".format(net_len))
logging.debug('Confirming new neutron network...')
network = networks['networks'][0]
assert network['name'] == net_name, "network ext_net not found"
assert network['name'] == self._TEST_NET_NAME, \
"network {} not found".format(self._TEST_NET_NAME)
return network['id']
# Cleanup
logging.debug('Deleting neutron network...')
self.neutron_client.delete_network(network['id'])
def _assert_test_network_doesnt_exist(self):
networks = self.neutron_client.list_networks(name=self._TEST_NET_NAME)
net_count = len(networks['networks'])
assert net_count == 0, (
"Expected zero networks, found {}".format(net_count))
class NeutronApiTest(NeutronCreateNetworkTest):
@@ -600,7 +605,7 @@ class NeutronOpenvSwitchTest(NeutronPluginApiSharedTests):
logging.info('Testing pause resume')
class NeutronNetworkingBase(unittest.TestCase):
class NeutronNetworkingBase(test_utils.OpenStackBaseTest):
"""Base for checking openstack instances have valid networking."""
RESOURCE_PREFIX = 'zaza-neutrontests'
@@ -608,30 +613,10 @@ class NeutronNetworkingBase(unittest.TestCase):
@classmethod
def setUpClass(cls):
"""Run class setup for running Neutron API Networking tests."""
cls.keystone_session = (
openstack_utils.get_overcloud_keystone_session())
cls.nova_client = (
openstack_utils.get_nova_session_client(cls.keystone_session))
super(NeutronNetworkingBase, cls).setUpClass(
application_name='neutron-api')
cls.neutron_client = (
openstack_utils.get_neutron_session_client(cls.keystone_session))
# NOTE(fnordahl): in the event of a test failure we do not want to run
# tear down code as it will make debugging a problem virtually
# impossible. To alleviate each test method will set the
# `run_tearDown` instance variable at the end which will let us run
# tear down only when there were no failure.
cls.run_tearDown = False
@classmethod
def tearDown(cls):
"""Remove test resources."""
if cls.run_tearDown:
logging.info('Running teardown')
for server in cls.nova_client.servers.list():
if server.name.startswith(cls.RESOURCE_PREFIX):
openstack_utils.delete_resource(
cls.nova_client.servers,
server.id,
msg="server")
@tenacity.retry(wait=tenacity.wait_exponential(multiplier=1, max=60),
reraise=True, stop=tenacity.stop_after_attempt(8))
@@ -729,44 +714,6 @@ class NeutronNetworkingBase(unittest.TestCase):
assert agent['admin_state_up']
assert agent['alive']
def launch_guests(self):
"""Launch two guests to use in tests."""
guest.launch_instance(
glance_setup.LTS_IMAGE_NAME,
vm_name='{}-ins-1'.format(self.RESOURCE_PREFIX))
guest.launch_instance(
glance_setup.LTS_IMAGE_NAME,
vm_name='{}-ins-2'.format(self.RESOURCE_PREFIX))
def retrieve_guest(self, nova_client, guest_name):
"""Return guest matching name.
:param nova_client: Nova client to use when checking status
:type nova_client: Nova client
:returns: the matching guest
:rtype: Union[novaclient.Server, None]
"""
try:
return nova_client.servers.find(name=guest_name)
except novaclient.exceptions.NotFound:
return None
def retrieve_guests(self, nova_client):
"""Return test guests.
:param nova_client: Nova client to use when checking status
:type nova_client: Nova client
:returns: the matching guest
:rtype: Union[novaclient.Server, None]
"""
instance_1 = self.retrieve_guest(
nova_client,
'{}-ins-1'.format(self.RESOURCE_PREFIX))
instance_2 = self.retrieve_guest(
nova_client,
'{}-ins-1'.format(self.RESOURCE_PREFIX))
return instance_1, instance_2
def check_connectivity(self, instance_1, instance_2):
"""Run North/South and East/West connectivity tests."""
def verify(stdin, stdout, stderr):
@@ -837,9 +784,9 @@ class NeutronNetworkingTest(NeutronNetworkingBase):
def test_instances_have_networking(self):
"""Validate North/South and East/West networking."""
self.launch_guests()
instance_1, instance_2 = self.retrieve_guests(self.nova_client)
instance_1, instance_2 = self.retrieve_guests()
self.check_connectivity(instance_1, instance_2)
self.run_tearDown = True
self.run_resource_cleanup = True
class NeutronNetworkingVRRPTests(NeutronNetworkingBase):
@@ -847,10 +794,10 @@ class NeutronNetworkingVRRPTests(NeutronNetworkingBase):
def test_gateway_failure(self):
"""Validate networking in the case of a gateway failure."""
instance_1, instance_2 = self.retrieve_guests(self.nova_client)
instance_1, instance_2 = self.retrieve_guests()
if not all([instance_1, instance_2]):
self.launch_guests()
instance_1, instance_2 = self.retrieve_guests(self.nova_client)
instance_1, instance_2 = self.retrieve_guests()
self.check_connectivity(instance_1, instance_2)
routers = self.neutron_client.list_routers(

View File

@@ -0,0 +1,15 @@
# Copyright 2020 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 neutron-api-plugin-arista."""

View File

@@ -0,0 +1,79 @@
# Copyright 2020 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.
"""Code for setting up neutron-api-plugin-arista."""
import logging
import os
import tenacity
import zaza
import zaza.openstack.charm_tests.neutron_arista.utils as arista_utils
import zaza.openstack.utilities.openstack as openstack_utils
def download_arista_image():
"""Download arista-cvx-virt-test.qcow2 from a web server.
The download will happen only if the env var TEST_ARISTA_IMAGE_REMOTE has
been set, so you don't have to set it if you already have the image
locally.
If the env var TEST_ARISTA_IMAGE_LOCAL isn't set, it will be set to
`/tmp/arista-cvx-virt-test.qcow2`. This is where the image will be
downloaded to if TEST_ARISTA_IMAGE_REMOTE has been set.
"""
try:
os.environ['TEST_ARISTA_IMAGE_LOCAL']
except KeyError:
os.environ['TEST_ARISTA_IMAGE_LOCAL'] = ''
if not os.environ['TEST_ARISTA_IMAGE_LOCAL']:
os.environ['TEST_ARISTA_IMAGE_LOCAL'] \
= '/tmp/arista-cvx-virt-test.qcow2'
try:
if os.environ['TEST_ARISTA_IMAGE_REMOTE']:
logging.info('Downloading Arista image from {}'
.format(os.environ['TEST_ARISTA_IMAGE_REMOTE']))
openstack_utils.download_image(
os.environ['TEST_ARISTA_IMAGE_REMOTE'],
os.environ['TEST_ARISTA_IMAGE_LOCAL'])
except KeyError:
# TEST_ARISTA_IMAGE_REMOTE isn't set, which means the image is already
# available at TEST_ARISTA_IMAGE_LOCAL
pass
logging.info('Arista image can be found at {}'
.format(os.environ['TEST_ARISTA_IMAGE_LOCAL']))
def test_fixture():
"""Pass arista-virt-test-fixture's IP address to Neutron."""
fixture_ip_addr = arista_utils.fixture_ip_addr()
logging.info(
"{}'s IP address is '{}'. Passing it to {}..."
.format(arista_utils.FIXTURE_APP_NAME, fixture_ip_addr,
arista_utils.PLUGIN_APP_NAME))
zaza.model.set_application_config(arista_utils.PLUGIN_APP_NAME,
{'eapi-host': fixture_ip_addr})
logging.info('Waiting for {} to become ready...'.format(
arista_utils.PLUGIN_APP_NAME))
zaza.model.wait_for_agent_status()
zaza.model.wait_for_application_states()
for attempt in tenacity.Retrying(
wait=tenacity.wait_fixed(10), # seconds
stop=tenacity.stop_after_attempt(30),
reraise=True):
with attempt:
arista_utils.query_fixture_networks(fixture_ip_addr)

View File

@@ -0,0 +1,74 @@
#!/usr/bin/env python3
# Copyright 2020 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.
"""Encapsulating `neutron-api-plugin-arista` testing."""
import logging
import tenacity
import zaza.openstack.charm_tests.neutron.tests as neutron_tests
import zaza.openstack.charm_tests.neutron_arista.utils as arista_utils
class NeutronCreateAristaNetworkTest(neutron_tests.NeutronCreateNetworkTest):
"""Test creating an Arista Neutron network through the API."""
@classmethod
def setUpClass(cls):
"""Run class setup for running Neutron Arista tests."""
super(NeutronCreateAristaNetworkTest, cls).setUpClass()
logging.info('Waiting for Neutron to become ready...')
for attempt in tenacity.Retrying(
wait=tenacity.wait_fixed(5), # seconds
stop=tenacity.stop_after_attempt(12),
reraise=True):
with attempt:
cls.neutron_client.list_networks()
def _assert_test_network_exists_and_return_id(self):
logging.info('Checking that the test network exists on the Arista '
'test fixture...')
# Sometimes the API call from Neutron to Arista fails and Neutron
# retries a couple of seconds later, which is why the newly created
# test network may not be immediately visible on Arista's API.
for attempt in tenacity.Retrying(
wait=tenacity.wait_fixed(10), # seconds
stop=tenacity.stop_after_attempt(3),
reraise=True):
with attempt:
actual_network_names = arista_utils.query_fixture_networks(
arista_utils.fixture_ip_addr())
self.assertEqual(actual_network_names, [self._TEST_NET_NAME])
return super(NeutronCreateAristaNetworkTest,
self)._assert_test_network_exists_and_return_id()
def _assert_test_network_doesnt_exist(self):
logging.info("Checking that the test network doesn't exist on the "
"Arista test fixture...")
for attempt in tenacity.Retrying(
wait=tenacity.wait_fixed(10), # seconds
stop=tenacity.stop_after_attempt(3),
reraise=True):
with attempt:
actual_network_names = arista_utils.query_fixture_networks(
arista_utils.fixture_ip_addr())
self.assertEqual(actual_network_names, [])
super(NeutronCreateAristaNetworkTest,
self)._assert_test_network_doesnt_exist()

View File

@@ -0,0 +1,68 @@
# Copyright 2020 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.
"""Common Arista-related utils."""
import json
import requests
import urllib3
import zaza
FIXTURE_APP_NAME = 'arista-virt-test-fixture'
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
_FIXTURE_LOGIN = 'admin'
_FIXTURE_PASSWORD = 'password123'
def query_fixture_networks(ip_addr):
"""Query the Arista test fixture's list of networks."""
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
session = requests.Session()
session.headers['Content-Type'] = 'application/json'
session.headers['Accept'] = 'application/json'
session.verify = False
session.auth = (_FIXTURE_LOGIN, _FIXTURE_PASSWORD)
data = {
'id': 'Zaza {} tests'.format(PLUGIN_APP_NAME),
'method': 'runCmds',
'jsonrpc': '2.0',
'params': {
'timestamps': False,
'format': 'json',
'version': 1,
'cmds': ['show openstack networks']
}
}
response = session.post(
'https://{}/command-api/'.format(ip_addr),
data=json.dumps(data),
timeout=10 # seconds
)
result = []
for region in response.json()['result'][0]['regions'].values():
for tenant in region['tenants'].values():
for network in tenant['tenantNetworks'].values():
result.append(network['networkName'])
return result

View File

@@ -56,6 +56,16 @@ class LTSGuestCreateTest(BaseGuestCreateTest):
glance_setup.LTS_IMAGE_NAME)
class LTSGuestCreateVolumeBackedTest(BaseGuestCreateTest):
"""Tests to launch a LTS image."""
def test_launch_small_instance(self):
"""Launch a Bionic instance and test connectivity."""
zaza.openstack.configure.guest.launch_instance(
glance_setup.LTS_IMAGE_NAME,
use_boot_volume=True)
class NovaCompute(test_utils.OpenStackBaseTest):
"""Run nova-compute specific tests."""

View File

@@ -98,25 +98,6 @@ def configure_octavia():
pass
def prepare_payload_instance():
"""Prepare a instance we can use as payload test."""
session = openstack.get_overcloud_keystone_session()
keystone = openstack.get_keystone_session_client(session)
neutron = openstack.get_neutron_session_client(session)
project_id = openstack.get_project_id(
keystone, 'admin', domain_name='admin_domain')
openstack.add_neutron_secgroup_rules(
neutron,
project_id,
[{'protocol': 'tcp',
'port_range_min': '80',
'port_range_max': '80',
'direction': 'ingress'}])
zaza.openstack.configure.guest.launch_instance(
glance_setup.LTS_IMAGE_NAME,
userdata='#cloud-config\npackages:\n - apache2\n')
def centralized_fip_network():
"""Create network with centralized router for connecting lb and fips.

View File

@@ -38,7 +38,20 @@ class CharmOperationTest(test_utils.OpenStackBaseTest):
Pause service and check services are stopped, then resume and check
they are started.
"""
self.pause_resume(['apache2'])
services = [
'apache2',
'octavia-health-manager',
'octavia-housekeeping',
'octavia-worker',
]
if openstack_utils.ovn_present():
services.append('octavia-driver-agent')
logging.info('Skipping pause resume test LP: #1886202...')
return
logging.info('Testing pause resume (services="{}")'
.format(services))
with self.pause_resume(services, pgrep_full=True):
pass
class LBAASv2Test(test_utils.OpenStackBaseTest):
@@ -48,12 +61,13 @@ class LBAASv2Test(test_utils.OpenStackBaseTest):
def setUpClass(cls):
"""Run class setup for running LBaaSv2 service tests."""
super(LBAASv2Test, cls).setUpClass()
cls.keystone_session = openstack_utils.get_overcloud_keystone_session()
cls.keystone_client = openstack_utils.get_keystone_session_client(
cls.keystone_session)
cls.neutron_client = openstack_utils.get_neutron_session_client(
cls.keystone_session)
cls.octavia_client = openstack_utils.get_octavia_session_client(
cls.keystone_session)
cls.RESOURCE_PREFIX = 'zaza-octavia'
# NOTE(fnordahl): in the event of a test failure we do not want to run
# tear down code as it will make debugging a problem virtually
@@ -63,28 +77,24 @@ class LBAASv2Test(test_utils.OpenStackBaseTest):
cls.run_tearDown = False
# List of load balancers created by this test
cls.loadbalancers = []
# LIst of floating IPs created by this test
# List of floating IPs created by this test
cls.fips = []
@classmethod
def tearDown(cls):
"""Remove resources created during test execution.
Note that resources created in the configure step prior to executing
the test should not be touched here.
"""
if not cls.run_tearDown:
return
for lb in cls.loadbalancers:
cls.octavia_client.load_balancer_delete(lb['id'], cascade=True)
def resource_cleanup(self):
"""Remove resources created during test execution."""
for lb in self.loadbalancers:
self.octavia_client.load_balancer_delete(lb['id'], cascade=True)
try:
cls.wait_for_lb_resource(
cls.octavia_client.load_balancer_show, lb['id'],
self.wait_for_lb_resource(
self.octavia_client.load_balancer_show, lb['id'],
provisioning_status='DELETED')
except osc_lib.exceptions.NotFound:
pass
for fip in cls.fips:
cls.neutron_client.delete_floatingip(fip)
for fip in self.fips:
self.neutron_client.delete_floatingip(fip)
# we run the parent resource_cleanup last as it will remove instances
# referenced as members in the above cleaned up load balancers
super(LBAASv2Test, self).resource_cleanup()
@staticmethod
@tenacity.retry(retry=tenacity.retry_if_exception_type(AssertionError),
@@ -238,12 +248,27 @@ class LBAASv2Test(test_utils.OpenStackBaseTest):
def test_create_loadbalancer(self):
"""Create load balancer."""
nova_client = openstack_utils.get_nova_session_client(
self.keystone_session)
# Prepare payload instances
# First we allow communication to port 80 by adding a security group
# rule
project_id = openstack_utils.get_project_id(
self.keystone_client, 'admin', domain_name='admin_domain')
openstack_utils.add_neutron_secgroup_rules(
self.neutron_client,
project_id,
[{'protocol': 'tcp',
'port_range_min': '80',
'port_range_max': '80',
'direction': 'ingress'}])
# Then we request two Ubuntu instances with the Apache web server
# installed
instance_1, instance_2 = self.launch_guests(
userdata='#cloud-config\npackages:\n - apache2\n')
# Get IP of the prepared payload instances
payload_ips = []
for server in nova_client.servers.list():
for server in (instance_1, instance_2):
payload_ips.append(server.networks['private'][0])
self.assertTrue(len(payload_ips) > 0)
@@ -274,4 +299,4 @@ class LBAASv2Test(test_utils.OpenStackBaseTest):
lb_fp['floating_ip_address']))
# If we get here, it means the tests passed
self.run_tearDown = True
self.run_resource_cleanup = True

View File

@@ -401,6 +401,10 @@ class BasePolicydSpecialization(PolicydTest,
def test_003_test_overide_is_observed(self):
"""Test that the override is observed by the underlying service."""
if (openstack_utils.get_os_release() <
openstack_utils.get_os_release('groovy_victoria')):
raise unittest.SkipTest(
"Test skipped until Bug #1880959 is fix released")
if self._test_name is None:
logging.info("Doing policyd override for {}"
.format(self._service_name))
@@ -655,7 +659,7 @@ class HeatTests(BasePolicydSpecialization):
class OctaviaTests(BasePolicydSpecialization):
"""Test the policyd override using the octavia client."""
_rule = {'rule.yaml': "{'os_load-balancer_api:loadbalancer:get_one': '!'}"}
_rule = {'rule.yaml': "{'os_load-balancer_api:provider:get_all': '!'}"}
@classmethod
def setUpClass(cls, application_name=None):
@@ -663,89 +667,8 @@ class OctaviaTests(BasePolicydSpecialization):
super(OctaviaTests, cls).setUpClass(application_name="octavia")
cls.application_name = "octavia"
def setup_for_attempt_operation(self, ip):
"""Create a loadbalancer.
This is necessary so that the attempt is to show the load-balancer and
this is an operator that the policy can stop. Unfortunately, octavia,
whilst it has a policy for just listing load-balancers, unfortunately,
it doesn't work; whereas showing the load-balancer can be stopped.
NB this only works if the setup phase of the octavia tests have been
completed.
:param ip: the ip of for keystone.
:type ip: str
"""
logging.info("Setting up loadbalancer.")
auth = openstack_utils.get_overcloud_auth(address=ip)
sess = openstack_utils.get_keystone_session(auth)
octavia_client = openstack_utils.get_octavia_session_client(sess)
neutron_client = openstack_utils.get_neutron_session_client(sess)
if openstack_utils.dvr_enabled():
network_name = 'private_lb_fip_network'
else:
network_name = 'private'
resp = neutron_client.list_networks(name=network_name)
vip_subnet_id = resp['networks'][0]['subnets'][0]
res = octavia_client.load_balancer_create(
json={
'loadbalancer': {
'description': 'Created by Zaza',
'admin_state_up': True,
'vip_subnet_id': vip_subnet_id,
'name': 'zaza-lb-0',
}})
self.lb_id = res['loadbalancer']['id']
# now wait for it to get to the active state
@tenacity.retry(wait=tenacity.wait_fixed(1),
reraise=True, stop=tenacity.stop_after_delay(900))
def wait_for_lb_resource(client, resource_id):
resp = client.load_balancer_show(resource_id)
logging.info(resp['provisioning_status'])
assert resp['provisioning_status'] == 'ACTIVE', (
'load balancer resource has not reached '
'expected provisioning status: {}'
.format(resp))
return resp
logging.info('Awaiting loadbalancer to reach provisioning_status '
'"ACTIVE"')
resp = wait_for_lb_resource(octavia_client, self.lb_id)
logging.info(resp)
logging.info("Setup loadbalancer complete.")
def cleanup_for_attempt_operation(self, ip):
"""Remove the loadbalancer.
:param ip: the ip of for keystone.
:type ip: str
"""
logging.info("Deleting loadbalancer {}.".format(self.lb_id))
auth = openstack_utils.get_overcloud_auth(address=ip)
sess = openstack_utils.get_keystone_session(auth)
octavia_client = openstack_utils.get_octavia_session_client(sess)
octavia_client.load_balancer_delete(self.lb_id)
logging.info("Deleting loadbalancer in progress ...")
@tenacity.retry(wait=tenacity.wait_fixed(1),
reraise=True, stop=tenacity.stop_after_delay(900))
def wait_til_deleted(client, lb_id):
lb_list = client.load_balancer_list()
ids = [lb['id'] for lb in lb_list['loadbalancers']]
assert lb_id not in ids, 'load balancer still deleting'
wait_til_deleted(octavia_client, self.lb_id)
logging.info("Deleted loadbalancer.")
def get_client_and_attempt_operation(self, ip):
"""Attempt to show the loadbalancer as a policyd override.
"""Attempt to list available provider drivers.
This operation should pass normally, and fail when
the rule has been overriden (see the `rule` class variable.
@@ -757,6 +680,6 @@ class OctaviaTests(BasePolicydSpecialization):
octavia_client = openstack_utils.get_octavia_session_client(
self.get_keystone_session_admin_user(ip))
try:
octavia_client.load_balancer_show(self.lb_id)
octavia_client.provider_list()
except octaviaclient.OctaviaClientException:
raise PolicydOperationFailedException()

View File

@@ -178,15 +178,28 @@ class SwiftGlobalReplicationTests(test_utils.OpenStackBaseTest):
logging.info('Deleting container {}'.format(container['name']))
cls.swift_region1.delete_container(container['name'])
def test_two_regions_any_zones_two_replicas(self):
"""Create an object with two replicas across two regions."""
def test_901_two_regions_any_zones_two_replicas(self):
"""Create an object with two replicas across two regions.
We set write affinity to write the first copy in the local
region of the proxy used to perform the write, the other
replica will land in the remote region.
"""
swift_utils.apply_proxy_config(
self.region1_proxy_app,
{
'write-affinity': 'r1, r2',
'write-affinity': 'r1',
'write-affinity-node-count': '1',
'replicas': '2'},
self.region1_model_name)
swift_utils.apply_proxy_config(
self.region2_proxy_app,
{
'write-affinity': 'r2',
'write-affinity-node-count': '1',
'replicas': '2'},
self.region2_model_name)
logging.info('Proxy configs updated in both regions')
container_name, obj_name, obj_replicas = swift_utils.create_object(
self.swift_region1,
self.region1_proxy_app,
@@ -204,15 +217,29 @@ class SwiftGlobalReplicationTests(test_utils.OpenStackBaseTest):
len(obj_replicas.all_zones),
2)
def test_two_regions_any_zones_three_replicas(self):
"""Create an object with three replicas across two regions."""
def test_902_two_regions_any_zones_three_replicas(self):
"""Create an object with three replicas across two regions.
We set write affinity to write the first copy in the local
region of the proxy used to perform the write, at least one
of the other two replicas will end up in the opposite region
based on primary partitions in the ring.
"""
swift_utils.apply_proxy_config(
self.region1_proxy_app,
{
'write-affinity': 'r1, r2',
'write-affinity': 'r1',
'write-affinity-node-count': '1',
'replicas': '3'},
self.region1_model_name)
swift_utils.apply_proxy_config(
self.region2_proxy_app,
{
'write-affinity': 'r2',
'write-affinity-node-count': '1',
'replicas': '3'},
self.region2_model_name)
logging.info('Proxy configs updated in both regions')
container_name, obj_name, obj_replicas = swift_utils.create_object(
self.swift_region1,
self.region1_proxy_app,
@@ -258,7 +285,7 @@ class S3APITest(test_utils.OpenStackBaseTest):
# Create AWS compatible application credentials in Keystone
cls.ec2_creds = ks_client.ec2.create(user_id, project_id)
def test_s3_list_buckets(self):
def test_901_s3_list_buckets(self):
"""Use S3 API to list buckets."""
# We use a mix of the high- and low-level API with common arguments
kwargs = {

View File

@@ -235,6 +235,9 @@ def get_tempest_context():
'cinder': add_cinder_config,
'keystone': add_keystone_config}
ctxt['enabled_services'] = get_service_list(keystone_session)
if set(['cinderv2', 'cinderv3']) \
.intersection(set(ctxt['enabled_services'])):
ctxt['enabled_services'].append('cinder')
ctxt['disabled_services'] = list(
set(TEMPEST_SVC_LIST) - set(ctxt['enabled_services']))
add_application_ips(ctxt)

View File

@@ -14,13 +14,19 @@
"""Module containing base class for implementing charm tests."""
import contextlib
import logging
import ipaddress
import subprocess
import tenacity
import unittest
import novaclient
import zaza.model as model
import zaza.charm_lifecycle.utils as lifecycle_utils
import zaza.openstack.configure.guest as configure_guest
import zaza.openstack.utilities.openstack as openstack_utils
import zaza.openstack.utilities.generic as generic_utils
import zaza.openstack.charm_tests.glance.setup as glance_setup
def skipIfNotHA(service_name):
@@ -94,8 +100,7 @@ class BaseCharmTest(unittest.TestCase):
run_resource_cleanup = False
@classmethod
def resource_cleanup(cls):
def resource_cleanup(self):
"""Cleanup any resources created during the test run.
Override this method with a method which removes any resources
@@ -105,12 +110,13 @@ class BaseCharmTest(unittest.TestCase):
"""
pass
@classmethod
def tearDown(cls):
# this must be a class instance method otherwise descentents will not be
# able to influence if cleanup should be run.
def tearDown(self):
"""Run teardown for test class."""
if cls.run_resource_cleanup:
if self.run_resource_cleanup:
logging.info('Running resource cleanup')
cls.resource_cleanup()
self.resource_cleanup()
@classmethod
def setUpClass(cls, application_name=None, model_alias=None):
@@ -431,3 +437,124 @@ class OpenStackBaseTest(BaseCharmTest):
cls.keystone_session = openstack_utils.get_overcloud_keystone_session(
model_name=cls.model_name)
cls.cacert = openstack_utils.get_cacert()
cls.nova_client = (
openstack_utils.get_nova_session_client(cls.keystone_session))
def resource_cleanup(self):
"""Remove test resources."""
try:
logging.info('Removing instances launched by test ({}*)'
.format(self.RESOURCE_PREFIX))
for server in self.nova_client.servers.list():
if server.name.startswith(self.RESOURCE_PREFIX):
openstack_utils.delete_resource(
self.nova_client.servers,
server.id,
msg="server")
except AttributeError:
# Test did not define self.RESOURCE_PREFIX, ignore.
pass
def launch_guest(self, guest_name, userdata=None):
"""Launch two guests to use in tests.
Note that it is up to the caller to have set the RESOURCE_PREFIX class
variable prior to calling this method.
Also note that this method will remove any already existing instance
with same name as what is requested.
:param guest_name: Name of instance
:type guest_name: str
:param userdata: Userdata to attach to instance
:type userdata: Optional[str]
:returns: Nova instance objects
:rtype: Server
"""
instance_name = '{}-{}'.format(self.RESOURCE_PREFIX, guest_name)
instance = self.retrieve_guest(instance_name)
if instance:
logging.info('Removing already existing instance ({}) with '
'requested name ({})'
.format(instance.id, instance_name))
openstack_utils.delete_resource(
self.nova_client.servers,
instance.id,
msg="server")
return configure_guest.launch_instance(
glance_setup.LTS_IMAGE_NAME,
vm_name=instance_name,
userdata=userdata)
def launch_guests(self, userdata=None):
"""Launch two guests to use in tests.
Note that it is up to the caller to have set the RESOURCE_PREFIX class
variable prior to calling this method.
:param userdata: Userdata to attach to instance
:type userdata: Optional[str]
:returns: List of launched Nova instance objects
:rtype: List[Server]
"""
launched_instances = []
for guest_number in range(1, 2+1):
for attempt in tenacity.Retrying(
stop=tenacity.stop_after_attempt(3),
wait=tenacity.wait_exponential(
multiplier=1, min=2, max=10)):
with attempt:
launched_instances.append(
self.launch_guest(
guest_name='ins-{}'.format(guest_number),
userdata=userdata))
return launched_instances
def retrieve_guest(self, guest_name):
"""Return guest matching name.
:param nova_client: Nova client to use when checking status
:type nova_client: Nova client
:returns: the matching guest
:rtype: Union[novaclient.Server, None]
"""
try:
return self.nova_client.servers.find(name=guest_name)
except novaclient.exceptions.NotFound:
return None
def retrieve_guests(self):
"""Return test guests.
Note that it is up to the caller to have set the RESOURCE_PREFIX class
variable prior to calling this method.
:param nova_client: Nova client to use when checking status
:type nova_client: Nova client
:returns: the matching guest
:rtype: Union[novaclient.Server, None]
"""
instance_1 = self.retrieve_guest(
'{}-ins-1'.format(self.RESOURCE_PREFIX))
instance_2 = self.retrieve_guest(
'{}-ins-1'.format(self.RESOURCE_PREFIX))
return instance_1, instance_2
def format_addr(addr):
"""Validate and format IP address.
:param addr: IPv6 or IPv4 address
:type addr: str
:returns: Address string, optionally encapsulated in brackets([])
:rtype: str
:raises: ValueError
"""
ipaddr = ipaddress.ip_address(addr)
if isinstance(ipaddr, ipaddress.IPv6Address):
fmt = '[{}]'
else:
fmt = '{}'
return fmt.format(ipaddr)

View File

@@ -16,6 +16,7 @@
import base64
import functools
import logging
import requests
import tempfile
@@ -99,7 +100,7 @@ async def async_mojo_unseal_by_unit():
unit_name, './hooks/update-status')
def auto_initialize(cacert=None, validation_application='keystone'):
def auto_initialize(cacert=None, validation_application='keystone', wait=True):
"""Auto initialize vault for testing.
Generate a csr and uploading a signed certificate.
@@ -114,6 +115,7 @@ def auto_initialize(cacert=None, validation_application='keystone'):
:returns: None
:rtype: None
"""
logging.info('Running auto_initialize')
basic_setup(cacert=cacert, unseal_and_authorize=True)
action = vault_utils.run_get_csr()
@@ -131,10 +133,11 @@ def auto_initialize(cacert=None, validation_application='keystone'):
root_ca=cacertificate,
allowed_domains='openstack.local')
zaza.model.wait_for_agent_status()
test_config = lifecycle_utils.get_charm_config(fatal=False)
zaza.model.wait_for_application_states(
states=test_config.get('target_deploy_status', {}))
if wait:
zaza.model.wait_for_agent_status()
test_config = lifecycle_utils.get_charm_config(fatal=False)
zaza.model.wait_for_application_states(
states=test_config.get('target_deploy_status', {}))
if validation_application:
validate_ca(cacertificate, application=validation_application)
@@ -163,6 +166,12 @@ auto_initialize_no_validation = functools.partial(
validation_application=None)
auto_initialize_no_validation_no_wait = functools.partial(
auto_initialize,
validation_application=None,
wait=False)
def validate_ca(cacertificate, application="keystone", port=5000):
"""Validate Certificate Authority against application.

View File

@@ -27,6 +27,7 @@ import yaml
import collections
import zaza.model
import zaza.openstack.charm_tests.test_utils as test_utils
AUTH_FILE = "vault_tests.yaml"
CharmVaultClient = collections.namedtuple(
@@ -101,7 +102,7 @@ def get_unit_api_url(ip):
transport = 'http'
if vault_config['ssl-cert']['value']:
transport = 'https'
return '{}://{}:8200'.format(transport, ip)
return '{}://{}:8200'.format(transport, test_utils.format_addr(ip))
def get_hvac_client(vault_url, cacert=None):

View File

@@ -14,19 +14,19 @@ def set_cidr_certs():
"""Create certs and keys for deploy using IP SANS from CIDR.
Create a certificate authority certificate and key. The CA cert and key
are then base 64 encoded and assigned to the OS_TEST_CAKEY and
OS_TEST_CACERT environment variables.
are then base 64 encoded and assigned to the TEST_CAKEY and
TEST_CACERT environment variables.
Using the CA key a second certificate and key are generated. The new
certificate has a SAN entry for the first 2^11 IPs in the CIDR.
The cert and key are then base 64 encoded and assigned to the OS_TEST_KEY
and OS_TEST_CERT environment variables.
The cert and key are then base 64 encoded and assigned to the TEST_KEY
and TEST_CERT environment variables.
"""
(cakey, cacert) = zaza.openstack.utilities.cert.generate_cert(
ISSUER_NAME,
generate_ca=True)
os.environ['OS_TEST_CAKEY'] = base64.b64encode(cakey).decode()
os.environ['OS_TEST_CACERT'] = base64.b64encode(cacert).decode()
os.environ['TEST_CAKEY'] = base64.b64encode(cakey).decode()
os.environ['TEST_CACERT'] = base64.b64encode(cacert).decode()
# We need to restrain the number of SubjectAlternativeNames we attempt to
# put # in the certificate. There is a hard limit for what length the sum
# of all extensions in the certificate can have.
@@ -34,37 +34,37 @@ def set_cidr_certs():
# - 2^11 ought to be enough for anybody
alt_names = []
for addr in itertools.islice(
ipaddress.IPv4Network(os.environ.get('OS_CIDR_EXT')), 2**11):
ipaddress.IPv4Network(os.environ.get('TEST_CIDR_EXT')), 2**11):
alt_names.append(str(addr))
(key, cert) = zaza.openstack.utilities.cert.generate_cert(
'*.serverstack',
alternative_names=alt_names,
issuer_name=ISSUER_NAME,
signing_key=cakey)
os.environ['OS_TEST_KEY'] = base64.b64encode(key).decode()
os.environ['OS_TEST_CERT'] = base64.b64encode(cert).decode()
os.environ['TEST_KEY'] = base64.b64encode(key).decode()
os.environ['TEST_CERT'] = base64.b64encode(cert).decode()
def set_certs_per_vips():
"""Create certs and keys for deploy using VIPS.
Create a certificate authority certificate and key. The CA cert and key
are then base 64 encoded and assigned to the OS_TEST_CAKEY and
OS_TEST_CACERT environment variables.
are then base 64 encoded and assigned to the TEST_CAKEY and
TEST_CACERT environment variables.
Using the CA key a certificate and key is generated for each VIP specified
via environment variables. eg if OS_VIP06=172.20.0.107 is set in the
via environment variables. eg if TEST_VIP06=172.20.0.107 is set in the
environment then a cert with a SAN entry for 172.20.0.107 is generated.
The cert and key are then base 64 encoded and assigned to the OS_VIP06_KEY
and OS_VIP06_CERT environment variables.
The cert and key are then base 64 encoded and assigned to the
TEST_VIP06_KEY and TEST_VIP06_CERT environment variables.
"""
(cakey, cacert) = zaza.openstack.utilities.cert.generate_cert(
ISSUER_NAME,
generate_ca=True)
os.environ['OS_TEST_CAKEY'] = base64.b64encode(cakey).decode()
os.environ['OS_TEST_CACERT'] = base64.b64encode(cacert).decode()
os.environ['TEST_CAKEY'] = base64.b64encode(cakey).decode()
os.environ['TEST_CACERT'] = base64.b64encode(cacert).decode()
for vip_name, vip_ip in os.environ.items():
if vip_name.startswith('OS_VIP'):
if vip_name.startswith('TEST_VIP'):
(key, cert) = zaza.openstack.utilities.cert.generate_cert(
'*.serverstack',
alternative_names=[vip_ip],

View File

@@ -23,9 +23,9 @@ import telnetlib
import yaml
from zaza import model
from zaza.openstack.utilities import juju as juju_utils
from zaza.openstack.utilities import exceptions as zaza_exceptions
from zaza.openstack.utilities.os_versions import UBUNTU_OPENSTACK_RELEASE
from zaza.utilities import juju as juju_utils
def assertActionRanOK(action):

View File

@@ -13,18 +13,31 @@
# 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.
"""Module for interacting with juju."""
import os
from pathlib import Path
import yaml
"""Deprecated, please use zaza.utilities.juju."""
from zaza import (
model,
controller,
)
from zaza.openstack.utilities import generic as generic_utils
import logging
import functools
import zaza.utilities.juju
def deprecate():
"""Add a deprecation warning to wrapped function."""
def wrap(f):
@functools.wraps(f)
def wrapped_f(*args, **kwargs):
msg = (
"{} from zaza.openstack.utilities.juju is deprecated. "
"Please use the equivalent from zaza.utilities.juju".format(
f.__name__))
logging.warning(msg)
return f(*args, **kwargs)
return wrapped_f
return wrap
@deprecate()
def get_application_status(application=None, unit=None, model_name=None):
"""Return the juju status for an application.
@@ -37,39 +50,29 @@ def get_application_status(application=None, unit=None, model_name=None):
:returns: Juju status output for an application
:rtype: dict
"""
status = get_full_juju_status()
if unit and not application:
application = unit.split("/")[0]
if application:
status = status.applications.get(application)
if unit:
status = status.get("units").get(unit)
return status
return zaza.utilities.juju.get_application_status(
application=application,
unit=unit,
model_name=model_name)
def get_application_ip(application):
@deprecate()
def get_application_ip(application, model_name=None):
"""Get the application's IP address.
:param application: Application name
:type application: str
:param model_name: Name of model to query.
:type model_name: str
:returns: Application's IP address
:rtype: str
"""
try:
app_config = model.get_application_config(application)
except KeyError:
return ''
vip = app_config.get("vip").get("value")
if vip:
ip = vip
else:
unit = model.get_units(application)[0]
ip = unit.public_address
return ip
return zaza.utilities.juju.get_application_ip(
application,
model_name=model_name)
@deprecate()
def get_cloud_configs(cloud=None):
"""Get cloud configuration from local clouds.yaml.
@@ -81,14 +84,11 @@ def get_cloud_configs(cloud=None):
:returns: Dictionary of cloud configuration
:rtype: dict
"""
home = str(Path.home())
cloud_config = os.path.join(home, ".local", "share", "juju", "clouds.yaml")
if cloud:
return generic_utils.get_yaml_config(cloud_config)["clouds"].get(cloud)
else:
return generic_utils.get_yaml_config(cloud_config)
return zaza.utilities.juju.get_cloud_configs(
cloud=cloud)
@deprecate()
def get_full_juju_status(model_name=None):
"""Return the full juju status output.
@@ -97,10 +97,11 @@ def get_full_juju_status(model_name=None):
:returns: Full juju status output
:rtype: dict
"""
status = model.get_status(model_name=model_name)
return status
return zaza.utilities.juju.get_full_juju_status(
model_name=model_name)
@deprecate()
def get_machines_for_application(application, model_name=None):
"""Return machines for a given application.
@@ -111,20 +112,12 @@ def get_machines_for_application(application, model_name=None):
:returns: machines for an application
:rtype: Iterator[str]
"""
status = get_application_status(application, model_name=model_name)
if not status:
return
# libjuju juju status no longer has units for subordinate charms
# Use the application it is subordinate-to to find machines
if not status.get("units") and status.get("subordinate-to"):
status = get_application_status(status.get("subordinate-to")[0],
model_name=model_name)
for unit in status.get("units").keys():
yield status.get("units").get(unit).get("machine")
return zaza.utilities.juju.get_machines_for_application(
application,
model_name=model_name)
@deprecate()
def get_unit_name_from_host_name(host_name, application, model_name=None):
"""Return the juju unit name corresponding to a hostname.
@@ -135,17 +128,13 @@ def get_unit_name_from_host_name(host_name, application, model_name=None):
:param model_name: Name of model to query.
:type model_name: str
"""
# Assume that a juju managed hostname always ends in the machine number and
# remove the domain name if it present.
machine_number = host_name.split('-')[-1].split('.')[0]
unit_names = [
u.entity_id
for u in model.get_units(application_name=application,
model_name=model_name)
if int(u.data['machine-id']) == int(machine_number)]
return unit_names[0]
return zaza.utilities.juju.get_unit_name_from_host_name(
host_name,
application,
model_name=model_name)
@deprecate()
def get_machine_status(machine, key=None, model_name=None):
"""Return the juju status for a machine.
@@ -158,17 +147,13 @@ def get_machine_status(machine, key=None, model_name=None):
:returns: Juju status output for a machine
:rtype: dict
"""
status = get_full_juju_status(model_name=model_name)
if "lxd" in machine:
host = machine.split('/')[0]
status = status.machines.get(host)['containers'][machine]
else:
status = status.machines.get(machine)
if key:
status = status.get(key)
return status
return zaza.utilities.juju.get_machine_status(
machine,
key=key,
model_name=model_name)
@deprecate()
def get_machine_series(machine, model_name=None):
"""Return the juju series for a machine.
@@ -179,13 +164,12 @@ def get_machine_series(machine, model_name=None):
:returns: Juju series
:rtype: string
"""
return get_machine_status(
machine=machine,
key='series',
model_name=model_name
)
return zaza.utilities.juju.get_machine_series(
machine,
model_name=model_name)
@deprecate()
def get_machine_uuids_for_application(application, model_name=None):
"""Return machine uuids for a given application.
@@ -196,30 +180,22 @@ def get_machine_uuids_for_application(application, model_name=None):
:returns: machine uuuids for an application
:rtype: Iterator[str]
"""
for machine in get_machines_for_application(application,
model_name=model_name):
yield get_machine_status(machine, key="instance-id",
model_name=model_name)
return zaza.utilities.juju.get_machine_uuids_for_application(
application,
model_name=model_name)
@deprecate()
def get_provider_type():
"""Get the type of the undercloud.
:returns: Name of the undercloud type
:rtype: string
"""
cloud = controller.get_cloud()
if cloud:
# If the controller was deployed from this system with
# the cloud configured in ~/.local/share/juju/clouds.yaml
# Determine the cloud type directly
return get_cloud_configs(cloud)["type"]
else:
# If the controller was deployed elsewhere
# For now assume openstack
return "openstack"
return zaza.utilities.juju.get_provider_type()
@deprecate()
def remote_run(unit, remote_cmd, timeout=None, fatal=None, model_name=None):
"""Run command on unit and return the output.
@@ -238,46 +214,15 @@ def remote_run(unit, remote_cmd, timeout=None, fatal=None, model_name=None):
:rtype: string
:raises: model.CommandRunFailed
"""
if fatal is None:
fatal = True
result = model.run_on_unit(
return zaza.utilities.juju.remote_run(
unit,
remote_cmd,
timeout=timeout,
fatal=fatal,
model_name=model_name)
if result:
if int(result.get("Code")) == 0:
return result.get("Stdout")
else:
if fatal:
raise model.CommandRunFailed(remote_cmd, result)
return result.get("Stderr")
def _get_unit_names(names, model_name=None):
"""Resolve given application names to first unit name of said application.
Helper function that resolves application names to first unit name of
said application. Any already resolved unit names are returned as-is.
:param names: List of units/applications to translate
:type names: list(str)
:param model_name: Name of model to query.
:type model_name: str
:returns: List of units
:rtype: list(str)
"""
result = []
for name in names:
if '/' in name:
result.append(name)
else:
result.append(model.get_first_unit_name(
name,
model_name=model_name))
return result
@deprecate()
def get_relation_from_unit(entity, remote_entity, remote_interface_name,
model_name=None):
"""Get relation data passed between two units.
@@ -302,22 +247,14 @@ def get_relation_from_unit(entity, remote_entity, remote_interface_name,
:rtype: dict
:raises: model.CommandRunFailed
"""
application = entity.split('/')[0]
remote_application = remote_entity.split('/')[0]
rid = model.get_relation_id(application, remote_application,
remote_interface_name=remote_interface_name,
model_name=model_name)
(unit, remote_unit) = _get_unit_names(
[entity, remote_entity],
return zaza.utilities.juju.get_relation_from_unit(
entity,
remote_entity,
remote_interface_name,
model_name=model_name)
cmd = 'relation-get --format=yaml -r "{}" - "{}"' .format(rid, remote_unit)
result = model.run_on_unit(unit, cmd, model_name=model_name)
if result and int(result.get('Code')) == 0:
return yaml.safe_load(result.get('Stdout'))
else:
raise model.CommandRunFailed(cmd, result)
@deprecate()
def leader_get(application, key='', model_name=None):
"""Get leader settings from leader unit of named application.
@@ -331,14 +268,13 @@ def leader_get(application, key='', model_name=None):
:rtype: dict
:raises: model.CommandRunFailed
"""
cmd = 'leader-get --format=yaml {}'.format(key)
result = model.run_on_leader(application, cmd, model_name=model_name)
if result and int(result.get('Code')) == 0:
return yaml.safe_load(result.get('Stdout'))
else:
raise model.CommandRunFailed(cmd, result)
return zaza.utilities.juju.leader_get(
application,
key=key,
model_name=model_name)
@deprecate()
def get_subordinate_units(unit_list, charm_name=None, status=None,
model_name=None):
"""Get a list of all subordinate units associated with units in unit_list.
@@ -369,17 +305,8 @@ def get_subordinate_units(unit_list, charm_name=None, status=None,
:returns: List of matching unit names.
:rtype: []
"""
if not status:
status = model.get_status(model_name=model_name)
sub_units = []
for unit_name in unit_list:
app_name = unit_name.split('/')[0]
subs = status.applications[app_name]['units'][unit_name].get(
'subordinates') or {}
if charm_name:
for unit_name, unit_data in subs.items():
if charm_name in unit_data['charm']:
sub_units.append(unit_name)
else:
sub_units.extend([n for n in subs.keys()])
return sub_units
return zaza.utilities.juju.get_subordinate_units(
unit_list,
charm_name=charm_name,
status=status,
model_name=model_name)

View File

@@ -40,6 +40,7 @@ from keystoneauth1.identity import (
)
import zaza.openstack.utilities.cert as cert
import zaza.utilities.deployment_env as deployment_env
import zaza.utilities.juju as juju_utils
from novaclient import client as novaclient_client
from neutronclient.v2_0 import client as neutronclient
from neutronclient.common import exceptions as neutronexceptions
@@ -66,7 +67,6 @@ from zaza import model
from zaza.openstack.utilities import (
exceptions,
generic as generic_utils,
juju as juju_utils,
)
CIRROS_RELEASE_URL = 'http://download.cirros-cloud.net/version/released'
@@ -163,7 +163,7 @@ WORKLOAD_STATUS_EXCEPTIONS = {
KEYSTONE_CACERT = "keystone_juju_ca_cert.crt"
KEYSTONE_REMOTE_CACERT = (
"/usr/local/share/ca-certificates/{}".format(KEYSTONE_CACERT))
KEYSTONE_LOCAL_CACERT = ("/tmp/{}".format(KEYSTONE_CACERT))
KEYSTONE_LOCAL_CACERT = ("tests/{}".format(KEYSTONE_CACERT))
def get_cacert():
@@ -1609,7 +1609,7 @@ def get_undercloud_auth():
'API_VERSION': 3,
}
if domain:
auth_settings['OS_DOMAIN_NAME': 'admin_domain'] = domain
auth_settings['OS_DOMAIN_NAME'] = domain
else:
auth_settings['OS_USER_DOMAIN_NAME'] = (
os.environ.get('OS_USER_DOMAIN_NAME'))
@@ -1621,6 +1621,10 @@ def get_undercloud_auth():
if os_project_id is not None:
auth_settings['OS_PROJECT_ID'] = os_project_id
_os_cacert = os.environ.get('OS_CACERT')
if _os_cacert:
auth_settings.update({'OS_CACERT': _os_cacert})
# Validate settings
for key, settings in list(auth_settings.items()):
if settings is None:
@@ -1733,6 +1737,15 @@ def get_overcloud_auth(address=None, model_name=None):
}
if tls_rid:
unit = model.get_first_unit_name('keystone', model_name=model_name)
# ensure that the path to put the local cacert in actually exists. The
# assumption that 'tests/' exists for, say, mojo is false.
# Needed due to:
# commit: 537473ad3addeaa3d1e4e2d0fd556aeaa4018eb2
_dir = os.path.dirname(KEYSTONE_LOCAL_CACERT)
if not os.path.exists(_dir):
os.makedirs(_dir)
model.scp_from_unit(
unit,
KEYSTONE_REMOTE_CACERT,
@@ -2032,7 +2045,8 @@ def upload_image_to_glance(glance, local_path, image_name, disk_format='qcow2',
return image
def create_image(glance, image_url, image_name, image_cache_dir=None, tags=[]):
def create_image(glance, image_url, image_name, image_cache_dir=None, tags=[],
properties=None):
"""Download the image and upload it to glance.
Download an image from image_url and upload it to glance labelling
@@ -2049,6 +2063,8 @@ def create_image(glance, image_url, image_name, image_cache_dir=None, tags=[]):
:type image_cache_dir: Option[str, None]
:param tags: Tags to add to image
:type tags: list of str
:param properties: Properties and values to add to image
:type properties: dict
:returns: glance image pointer
:rtype: glanceclient.common.utils.RequestIdProxy
"""
@@ -2070,6 +2086,11 @@ def create_image(glance, image_url, image_name, image_cache_dir=None, tags=[]):
logging.debug(
'applying tag to image: glance.image_tags.update({}, {}) = {}'
.format(image.id, tags, result))
logging.info("Setting image properties: {}".format(properties))
if properties:
result = glance.images.update(image.id, **properties)
return image
@@ -2198,7 +2219,8 @@ def get_private_key_file(keypair_name):
:returns: Path to file containing key
:rtype: str
"""
return 'tests/id_rsa_{}'.format(keypair_name)
tmp_dir = deployment_env.get_tmpdir()
return '{}/id_rsa_{}'.format(tmp_dir, keypair_name)
def write_private_key(keypair_name, key):
@@ -2279,7 +2301,7 @@ def get_ports_from_device_id(neutron_client, device_id):
@tenacity.retry(wait=tenacity.wait_exponential(multiplier=1, max=120),
reraise=True, stop=tenacity.stop_after_attempt(12))
reraise=True, stop=tenacity.stop_after_delay(1800))
def cloud_init_complete(nova_client, vm_id, bootstring):
"""Wait for cloud init to complete on the given vm.
@@ -2396,7 +2418,7 @@ def ssh_command(username,
ssh.connect(ip, username=username, password=password)
else:
key = paramiko.RSAKey.from_private_key(io.StringIO(privkey))
ssh.connect(ip, username=username, password='', pkey=key)
ssh.connect(ip, username=username, password=None, pkey=key)
logging.info("Running {} on {}".format(command, vm_name))
stdin, stdout, stderr = ssh.exec_command(command)
if verify and callable(verify):

View File

@@ -35,6 +35,7 @@ UBUNTU_OPENSTACK_RELEASE = OrderedDict([
('disco', 'stein'),
('eoan', 'train'),
('focal', 'ussuri'),
('groovy', 'victoria'),
])
@@ -57,6 +58,7 @@ OPENSTACK_CODENAMES = OrderedDict([
('2019.1', 'stein'),
('2019.2', 'train'),
('2020.1', 'ussuri'),
('2020.2', 'victoria'),
])
OPENSTACK_RELEASES_PAIRS = [
@@ -66,7 +68,8 @@ OPENSTACK_RELEASES_PAIRS = [
'xenial_pike', 'artful_pike', 'xenial_queens',
'bionic_queens', 'bionic_rocky', 'cosmic_rocky',
'bionic_stein', 'disco_stein', 'bionic_train',
'eoan_train', 'bionic_ussuri', 'focal_ussuri']
'eoan_train', 'bionic_ussuri', 'focal_ussuri',
'focal_victoria', 'groovy_victoria']
# The ugly duckling - must list releases oldest to newest
SWIFT_CODENAMES = OrderedDict([
@@ -105,7 +108,9 @@ SWIFT_CODENAMES = OrderedDict([
('train',
['2.22.0']),
('ussuri',
['2.24.0']),
['2.24.0', '2.25.0']),
('victoria',
['2.25.0']),
])
# >= Liberty version->codename mapping
@@ -121,6 +126,7 @@ PACKAGE_CODENAMES = {
('19', 'stein'),
('20', 'train'),
('21', 'ussuri'),
('22', 'victoria'),
]),
'neutron-common': OrderedDict([
('7', 'liberty'),
@@ -133,6 +139,7 @@ PACKAGE_CODENAMES = {
('14', 'stein'),
('15', 'train'),
('16', 'ussuri'),
('17', 'victoria'),
]),
'cinder-common': OrderedDict([
('7', 'liberty'),
@@ -145,6 +152,7 @@ PACKAGE_CODENAMES = {
('14', 'stein'),
('15', 'train'),
('16', 'ussuri'),
('17', 'victoria'),
]),
'keystone': OrderedDict([
('8', 'liberty'),
@@ -157,6 +165,7 @@ PACKAGE_CODENAMES = {
('15', 'stein'),
('16', 'train'),
('17', 'ussuri'),
('18', 'victoria'),
]),
'horizon-common': OrderedDict([
('8', 'liberty'),
@@ -168,7 +177,8 @@ PACKAGE_CODENAMES = {
('14', 'rocky'),
('15', 'stein'),
('16', 'train'),
('17', 'ussuri'),
('18', 'ussuri'),
('19', 'victoria'),
]),
'ceilometer-common': OrderedDict([
('5', 'liberty'),
@@ -181,6 +191,7 @@ PACKAGE_CODENAMES = {
('12', 'stein'),
('13', 'train'),
('14', 'ussuri'),
('15', 'victoria'),
]),
'heat-common': OrderedDict([
('5', 'liberty'),
@@ -193,6 +204,7 @@ PACKAGE_CODENAMES = {
('12', 'stein'),
('13', 'train'),
('14', 'ussuri'),
('15', 'victoria'),
]),
'glance-common': OrderedDict([
('11', 'liberty'),
@@ -205,6 +217,7 @@ PACKAGE_CODENAMES = {
('18', 'stein'),
('19', 'train'),
('20', 'ussuri'),
('21', 'victoria'),
]),
'openstack-dashboard': OrderedDict([
('8', 'liberty'),
@@ -216,7 +229,8 @@ PACKAGE_CODENAMES = {
('14', 'rocky'),
('15', 'stein'),
('16', 'train'),
('17', 'ussuri'),
('18', 'ussuri'),
('19', 'victoria'),
]),
'designate-common': OrderedDict([
('1', 'liberty'),
@@ -229,6 +243,7 @@ PACKAGE_CODENAMES = {
('8', 'stein'),
('9', 'train'),
('10', 'ussuri'),
('11', 'victoria'),
]),
'ovn-common': OrderedDict([
('2', 'train'),