Merge branch 'master' into hm-port
This commit is contained in:
4
pip.sh
Executable file
4
pip.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
pip install pip==20.2.3
|
||||
pip "$@"
|
||||
@@ -2,6 +2,7 @@
|
||||
# This is necessary for Xenial builders
|
||||
# BUG: https://github.com/openstack-charmers/zaza-openstack-tests/issues/530
|
||||
lxml<4.6.3
|
||||
pyparsing<3.0.0 # pin for aodhclient which is held for py35
|
||||
aiounittest
|
||||
async_generator
|
||||
boto3
|
||||
@@ -12,7 +13,7 @@ flake8>=2.2.4
|
||||
flake8-docstrings
|
||||
flake8-per-file-ignores
|
||||
pydocstyle<4.0.0
|
||||
coverage
|
||||
coverage<6.0.0 # coverage 6.0+ drops support for py3.5/py2.7
|
||||
mock>=1.2
|
||||
nose>=1.3.7
|
||||
pbr>=1.8.0,<1.9.0
|
||||
@@ -41,7 +42,6 @@ python-novaclient
|
||||
python-octaviaclient
|
||||
python-swiftclient
|
||||
tenacity
|
||||
distro-info
|
||||
paramiko
|
||||
|
||||
# Documentation requirements
|
||||
|
||||
1
setup.py
1
setup.py
@@ -43,6 +43,7 @@ install_require = [
|
||||
'PyYAML',
|
||||
'tenacity',
|
||||
'oslo.config<6.12.0',
|
||||
'pyparsing<3.0.0', # pin for aodhclient which is held for py35
|
||||
'aodhclient<1.4.0',
|
||||
'gnocchiclient>=7.0.5,<8.0.0',
|
||||
'pika>=1.1.0,<2.0.0',
|
||||
|
||||
2
tox.ini
2
tox.ini
@@ -22,7 +22,7 @@ minversion = 3.2.0
|
||||
setenv = VIRTUAL_ENV={envdir}
|
||||
PYTHONHASHSEED=0
|
||||
install_command =
|
||||
pip install {opts} {packages}
|
||||
{toxinidir}/pip.sh install {opts} {packages}
|
||||
|
||||
commands = nosetests --with-coverage --cover-package=zaza.openstack {posargs} {toxinidir}/unit_tests
|
||||
|
||||
|
||||
@@ -11,8 +11,3 @@
|
||||
# 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 sys
|
||||
import unittest.mock as mock
|
||||
|
||||
sys.modules['zaza.utilities.maas'] = mock.MagicMock()
|
||||
|
||||
@@ -15,25 +15,22 @@
|
||||
import mock
|
||||
import unittest
|
||||
|
||||
import zaza.openstack.charm_tests.tempest.setup as tempest_setup
|
||||
import zaza.openstack.charm_tests.tempest.utils as tempest_utils
|
||||
|
||||
|
||||
class TestTempestSetup(unittest.TestCase):
|
||||
"""Test class to encapsulate testing Mysql test utils."""
|
||||
|
||||
def setUp(self):
|
||||
super(TestTempestSetup, self).setUp()
|
||||
class TestTempestUtils(unittest.TestCase):
|
||||
"""Test class to encapsulate testing Tempest test utils."""
|
||||
|
||||
def test_add_environment_var_config_with_missing_variable(self):
|
||||
ctxt = {}
|
||||
with self.assertRaises(Exception) as context:
|
||||
tempest_setup.add_environment_var_config(ctxt, ['swift'])
|
||||
tempest_utils._add_environment_var_config(ctxt, ['swift'])
|
||||
self.assertEqual(
|
||||
('Environment variables [TEST_SWIFT_IP] must all be '
|
||||
'set to run this test'),
|
||||
str(context.exception))
|
||||
|
||||
@mock.patch.object(tempest_setup.deployment_env, 'get_deployment_context')
|
||||
@mock.patch.object(tempest_utils.deployment_env, 'get_deployment_context')
|
||||
def test_add_environment_var_config_with_all_variables(
|
||||
self,
|
||||
get_deployment_context):
|
||||
@@ -45,10 +42,10 @@ class TestTempestSetup(unittest.TestCase):
|
||||
'TEST_NAME_SERVER': 'test',
|
||||
'TEST_CIDR_PRIV': 'test',
|
||||
}
|
||||
tempest_setup.add_environment_var_config(ctxt, ['neutron'])
|
||||
tempest_utils._add_environment_var_config(ctxt, ['neutron'])
|
||||
self.assertEqual(ctxt['test_gateway'], 'test')
|
||||
|
||||
@mock.patch.object(tempest_setup.deployment_env, 'get_deployment_context')
|
||||
@mock.patch.object(tempest_utils.deployment_env, 'get_deployment_context')
|
||||
def test_add_environment_var_config_with_some_variables(
|
||||
self,
|
||||
get_deployment_context):
|
||||
@@ -59,7 +56,7 @@ class TestTempestSetup(unittest.TestCase):
|
||||
'TEST_CIDR_PRIV': 'test',
|
||||
}
|
||||
with self.assertRaises(Exception) as context:
|
||||
tempest_setup.add_environment_var_config(ctxt, ['neutron'])
|
||||
tempest_utils._add_environment_var_config(ctxt, ['neutron'])
|
||||
self.assertEqual(
|
||||
('Environment variables [TEST_CIDR_EXT, TEST_FIP_RANGE] must '
|
||||
'all be set to run this test'),
|
||||
|
||||
@@ -24,6 +24,7 @@ import tenacity
|
||||
import unit_tests.utils as ut_utils
|
||||
from zaza.openstack.utilities import openstack as openstack_utils
|
||||
from zaza.openstack.utilities import exceptions
|
||||
from zaza.utilities.maas import LinkMode, MachineInterfaceMac
|
||||
|
||||
|
||||
class TestOpenStackUtils(ut_utils.BaseTestCase):
|
||||
@@ -57,11 +58,12 @@ class TestOpenStackUtils(ut_utils.BaseTestCase):
|
||||
|
||||
self.network = {
|
||||
"network": {"id": "network_id",
|
||||
"name": self.ext_net,
|
||||
"tenant_id": self.project_id,
|
||||
"router:external": True,
|
||||
"provider:physical_network": "physnet1",
|
||||
"provider:network_type": "flat"}}
|
||||
"name": self.ext_net,
|
||||
"router:external": True,
|
||||
"shared": False,
|
||||
"tenant_id": self.project_id,
|
||||
"provider:physical_network": "physnet1",
|
||||
"provider:network_type": "flat"}}
|
||||
|
||||
self.networks = {
|
||||
"networks": [self.network["network"]]}
|
||||
@@ -156,12 +158,12 @@ class TestOpenStackUtils(ut_utils.BaseTestCase):
|
||||
self.neutronclient.create_address_scope.assert_called_once_with(
|
||||
address_scope_msg)
|
||||
|
||||
def test_create_external_network(self):
|
||||
def test_create_provider_network(self):
|
||||
self.patch_object(openstack_utils, "get_net_uuid")
|
||||
self.get_net_uuid.return_value = self.net_uuid
|
||||
|
||||
# Already exists
|
||||
network = openstack_utils.create_external_network(
|
||||
network = openstack_utils.create_provider_network(
|
||||
self.neutronclient, self.project_id)
|
||||
self.assertEqual(network, self.network["network"])
|
||||
self.neutronclient.create_network.assert_not_called()
|
||||
@@ -171,7 +173,7 @@ class TestOpenStackUtils(ut_utils.BaseTestCase):
|
||||
"networks": []}
|
||||
network_msg = copy.deepcopy(self.network)
|
||||
network_msg["network"].pop("id")
|
||||
network = openstack_utils.create_external_network(
|
||||
network = openstack_utils.create_provider_network(
|
||||
self.neutronclient, self.project_id)
|
||||
self.assertEqual(network, self.network["network"])
|
||||
self.neutronclient.create_network.assert_called_once_with(
|
||||
@@ -1428,6 +1430,34 @@ class TestOpenStackUtils(ut_utils.BaseTestCase):
|
||||
self.move.assert_called_once_with(
|
||||
'tempfilename', '/tmp/default/ca1.cert')
|
||||
|
||||
def test_configure_charmed_openstack_on_maas(self):
|
||||
self.patch_object(openstack_utils, 'get_charm_networking_data')
|
||||
self.patch_object(openstack_utils.zaza.utilities.maas,
|
||||
'get_macs_from_cidr')
|
||||
self.patch_object(openstack_utils.zaza.utilities.maas,
|
||||
'get_maas_client_from_juju_cloud_data')
|
||||
self.patch_object(openstack_utils.zaza.model, 'get_cloud_data')
|
||||
self.patch_object(openstack_utils, 'configure_networking_charms')
|
||||
self.get_charm_networking_data.return_value = 'fakenetworkingdata'
|
||||
self.get_macs_from_cidr.return_value = [
|
||||
MachineInterfaceMac('id_a', 'ens6', '00:53:00:00:00:01',
|
||||
'192.0.2.0/24', LinkMode.LINK_UP),
|
||||
MachineInterfaceMac('id_a', 'ens7', '00:53:00:00:00:02',
|
||||
'192.0.2.0/24', LinkMode.LINK_UP),
|
||||
MachineInterfaceMac('id_b', 'ens6', '00:53:00:00:01:01',
|
||||
'192.0.2.0/24', LinkMode.LINK_UP),
|
||||
|
||||
]
|
||||
network_config = {'external_net_cidr': '192.0.2.0/24'}
|
||||
expect = [
|
||||
'00:53:00:00:00:01',
|
||||
'00:53:00:00:01:01',
|
||||
]
|
||||
openstack_utils.configure_charmed_openstack_on_maas(
|
||||
network_config)
|
||||
self.configure_networking_charms.assert_called_once_with(
|
||||
'fakenetworkingdata', expect, use_juju_wait=False)
|
||||
|
||||
|
||||
class TestAsyncOpenstackUtils(ut_utils.AioTestCase):
|
||||
|
||||
|
||||
17
zaza/openstack/charm_tests/ceilometer_agent/__init__.py
Normal file
17
zaza/openstack/charm_tests/ceilometer_agent/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/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.
|
||||
|
||||
"""Collection of code for setting up and testing ceilometer-agent."""
|
||||
225
zaza/openstack/charm_tests/ceilometer_agent/tests.py
Normal file
225
zaza/openstack/charm_tests/ceilometer_agent/tests.py
Normal file
@@ -0,0 +1,225 @@
|
||||
#!/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 ceilometer-agent testing."""
|
||||
|
||||
import logging
|
||||
import time
|
||||
from gnocchiclient.v1 import client as gnocchi_client
|
||||
|
||||
import zaza.openstack.charm_tests.glance.setup as glance_setup
|
||||
import zaza.openstack.charm_tests.test_utils as test_utils
|
||||
import zaza.openstack.utilities.openstack as openstack_utils
|
||||
|
||||
|
||||
class CeilometerAgentTest(test_utils.OpenStackBaseTest):
|
||||
"""Encapsulate ceilometer-agent tests."""
|
||||
|
||||
def tearDown(self):
|
||||
"""Cleanup of VM guests."""
|
||||
self.resource_cleanup()
|
||||
|
||||
def test_400_gnocchi_metrics(self):
|
||||
"""Verify that ceilometer-agent publishes metrics to gnocchi."""
|
||||
current_os_release = openstack_utils.get_os_release()
|
||||
openstack_pike_or_older = (
|
||||
current_os_release <=
|
||||
openstack_utils.get_os_release('xenial_pike'))
|
||||
if openstack_pike_or_older:
|
||||
# Both the charm and Ceilometer itself had different behaviors in
|
||||
# terms of which metrics were published and how fast, which would
|
||||
# lead to a combinatorial explosion if we had to maintain test
|
||||
# expectations for these old releases.
|
||||
logging.info(
|
||||
'OpenStack Pike or older, skipping')
|
||||
return
|
||||
|
||||
# ceilometer-agent-compute reports metrics for each existing VM, so at
|
||||
# least one VM is needed:
|
||||
self.RESOURCE_PREFIX = 'zaza-ceilometer-agent'
|
||||
self.launch_guest(
|
||||
'ubuntu', instance_key=glance_setup.LTS_IMAGE_NAME)
|
||||
|
||||
logging.info('Instantiating gnocchi client...')
|
||||
overcloud_auth = openstack_utils.get_overcloud_auth()
|
||||
keystone = openstack_utils.get_keystone_client(overcloud_auth)
|
||||
gnocchi_ep = keystone.service_catalog.url_for(
|
||||
service_type='metric',
|
||||
interface='publicURL'
|
||||
)
|
||||
gnocchi = gnocchi_client.Client(
|
||||
session=openstack_utils.get_overcloud_keystone_session(),
|
||||
adapter_options={
|
||||
'endpoint_override': gnocchi_ep,
|
||||
}
|
||||
)
|
||||
|
||||
expected_metric_names = self.__get_expected_metric_names(
|
||||
current_os_release)
|
||||
|
||||
min_timeout_seconds = 500
|
||||
polling_interval_seconds = (
|
||||
openstack_utils.get_application_config_option(
|
||||
self.application_name, 'polling-interval'))
|
||||
timeout_seconds = max(10 * polling_interval_seconds,
|
||||
min_timeout_seconds)
|
||||
logging.info('Giving ceilometer-agent {}s to publish all metrics to '
|
||||
'gnocchi...'.format(timeout_seconds))
|
||||
|
||||
max_time = time.time() + timeout_seconds
|
||||
while time.time() < max_time:
|
||||
found_metric_names = {metric['name']
|
||||
for metric in gnocchi.metric.list()}
|
||||
missing_metric_names = expected_metric_names - found_metric_names
|
||||
if len(missing_metric_names) == 0:
|
||||
logging.info('All expected metrics found.')
|
||||
break
|
||||
time.sleep(polling_interval_seconds)
|
||||
|
||||
unexpected_found_metric_names = (
|
||||
found_metric_names - expected_metric_names)
|
||||
if len(unexpected_found_metric_names) > 0:
|
||||
self.fail(
|
||||
'Unexpected metrics '
|
||||
'published: ' + ', '.join(unexpected_found_metric_names))
|
||||
|
||||
if len(missing_metric_names) > 0:
|
||||
self.fail('These metrics should have been published but '
|
||||
"weren't: " + ', '.join(missing_metric_names))
|
||||
|
||||
def __get_expected_metric_names(self, current_os_release):
|
||||
expected_metric_names = {
|
||||
'compute.instance.booting.time',
|
||||
'disk.ephemeral.size',
|
||||
'disk.root.size',
|
||||
'image.download',
|
||||
'image.serve',
|
||||
'image.size',
|
||||
'memory',
|
||||
'vcpus',
|
||||
}
|
||||
|
||||
all_polsters_are_enabled = (
|
||||
openstack_utils.get_application_config_option(
|
||||
self.application_name, 'enable-all-pollsters'))
|
||||
|
||||
if all_polsters_are_enabled:
|
||||
expected_metric_names |= {
|
||||
'disk.device.allocation',
|
||||
'disk.device.capacity',
|
||||
'disk.device.read.latency',
|
||||
'disk.device.usage',
|
||||
'disk.device.write.latency',
|
||||
'memory.resident',
|
||||
'memory.swap.in',
|
||||
'memory.swap.out',
|
||||
'network.incoming.packets.drop',
|
||||
'network.incoming.packets.error',
|
||||
'network.outgoing.packets.drop',
|
||||
'network.outgoing.packets.error',
|
||||
}
|
||||
|
||||
openstack_queens_or_older = (
|
||||
current_os_release <=
|
||||
openstack_utils.get_os_release('bionic_queens'))
|
||||
openstack_rocky_or_older = (
|
||||
current_os_release <=
|
||||
openstack_utils.get_os_release('bionic_rocky'))
|
||||
openstack_victoria_or_older = (
|
||||
current_os_release <=
|
||||
openstack_utils.get_os_release('groovy_victoria'))
|
||||
|
||||
if openstack_victoria_or_older:
|
||||
expected_metric_names |= {
|
||||
'cpu',
|
||||
'disk.device.read.bytes',
|
||||
'disk.device.read.requests',
|
||||
'disk.device.write.bytes',
|
||||
'disk.device.write.requests',
|
||||
'memory.usage',
|
||||
'network.incoming.bytes',
|
||||
'network.incoming.packets',
|
||||
'network.outgoing.bytes',
|
||||
'network.outgoing.packets',
|
||||
}
|
||||
|
||||
if openstack_rocky_or_older:
|
||||
expected_metric_names |= {
|
||||
'cpu.delta',
|
||||
'cpu_util',
|
||||
'disk.device.read.bytes.rate',
|
||||
'disk.device.read.requests.rate',
|
||||
'disk.device.write.bytes.rate',
|
||||
'disk.device.write.requests.rate',
|
||||
'network.incoming.bytes.rate',
|
||||
'network.incoming.packets.rate',
|
||||
'network.outgoing.bytes.rate',
|
||||
'network.outgoing.packets.rate',
|
||||
}
|
||||
if all_polsters_are_enabled:
|
||||
expected_metric_names |= {
|
||||
'disk.allocation',
|
||||
'disk.capacity',
|
||||
'disk.read.bytes',
|
||||
'disk.read.bytes.rate',
|
||||
'disk.read.requests',
|
||||
'disk.read.requests.rate',
|
||||
'disk.usage',
|
||||
'disk.write.bytes',
|
||||
'disk.write.bytes.rate',
|
||||
'disk.write.requests',
|
||||
'disk.write.requests.rate',
|
||||
}
|
||||
|
||||
if openstack_queens_or_older:
|
||||
expected_metric_names |= {
|
||||
'cpu_l3_cache',
|
||||
'disk.allocation',
|
||||
'disk.capacity',
|
||||
'disk.device.allocation',
|
||||
'disk.device.capacity',
|
||||
'disk.device.iops',
|
||||
'disk.device.latency',
|
||||
'disk.device.read.latency',
|
||||
'disk.device.usage',
|
||||
'disk.device.write.latency',
|
||||
'disk.iops',
|
||||
'disk.latency',
|
||||
'disk.read.bytes',
|
||||
'disk.read.bytes.rate',
|
||||
'disk.read.requests',
|
||||
'disk.read.requests.rate',
|
||||
'disk.usage',
|
||||
'disk.write.bytes',
|
||||
'disk.write.bytes.rate',
|
||||
'disk.write.requests',
|
||||
'disk.write.requests.rate',
|
||||
'memory.bandwidth.local',
|
||||
'memory.bandwidth.total',
|
||||
'memory.resident',
|
||||
'memory.swap.in',
|
||||
'memory.swap.out',
|
||||
'network.incoming.packets.drop',
|
||||
'network.incoming.packets.error',
|
||||
'network.outgoing.packets.drop',
|
||||
'network.outgoing.packets.error',
|
||||
'perf.cache.misses',
|
||||
'perf.cache.references',
|
||||
'perf.cpu.cycles',
|
||||
'perf.instructions',
|
||||
}
|
||||
|
||||
return expected_metric_names
|
||||
51
zaza/openstack/charm_tests/ceph/dashboard/setup.py
Normal file
51
zaza/openstack/charm_tests/ceph/dashboard/setup.py
Normal file
@@ -0,0 +1,51 @@
|
||||
# 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.
|
||||
|
||||
"""Code for setting up Ceph Dashboard."""
|
||||
|
||||
import logging
|
||||
|
||||
import zaza.model
|
||||
import zaza.openstack.utilities.openstack
|
||||
|
||||
|
||||
def check_dashboard_cert(model_name=None):
|
||||
"""Wait for Dashboard to be ready.
|
||||
|
||||
:param model_name: Name of model to query.
|
||||
:type model_name: str
|
||||
"""
|
||||
logging.info("Check dashbaord Waiting for cacert")
|
||||
zaza.openstack.utilities.openstack.block_until_ca_exists(
|
||||
'ceph-dashboard',
|
||||
'CERTIFICATE',
|
||||
model_name=model_name)
|
||||
zaza.model.block_until_all_units_idle(model_name=model_name)
|
||||
|
||||
|
||||
def set_grafana_url(model_name=None):
|
||||
"""Set the url for the grafana api.
|
||||
|
||||
:param model_name: Name of model to query.
|
||||
:type model_name: str
|
||||
"""
|
||||
try:
|
||||
unit = zaza.model.get_units('grafana')[0]
|
||||
except KeyError:
|
||||
return
|
||||
zaza.model.set_application_config(
|
||||
'ceph-dashboard',
|
||||
{
|
||||
'grafana-api-url': "https://{}:3000".format(
|
||||
unit.public_address)})
|
||||
@@ -15,12 +15,15 @@
|
||||
"""Encapsulating `ceph-dashboard` testing."""
|
||||
|
||||
import collections
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
import requests
|
||||
import tenacity
|
||||
import uuid
|
||||
|
||||
import zaza
|
||||
import zaza.openstack.charm_tests.test_utils as test_utils
|
||||
import zaza.utilities.deployment_env as deployment_env
|
||||
import zaza.openstack.utilities.openstack as openstack_utils
|
||||
|
||||
|
||||
class CephDashboardTest(test_utils.BaseCharmTest):
|
||||
@@ -34,33 +37,85 @@ class CephDashboardTest(test_utils.BaseCharmTest):
|
||||
"""Run class setup for running ceph dashboard tests."""
|
||||
super().setUpClass()
|
||||
cls.application_name = 'ceph-dashboard'
|
||||
cls.local_ca_cert = cls.collect_ca()
|
||||
cls.local_ca_cert = openstack_utils.get_remote_ca_cert_file(
|
||||
cls.application_name)
|
||||
|
||||
@classmethod
|
||||
def collect_ca(cls):
|
||||
"""Collect CA from ceph-dashboard unit."""
|
||||
local_ca_cert = os.path.join(
|
||||
deployment_env.get_tmpdir(),
|
||||
os.path.basename(cls.REMOTE_CERT_FILE))
|
||||
if not os.path.isfile(local_ca_cert):
|
||||
units = zaza.model.get_units(cls.application_name)
|
||||
zaza.model.scp_from_unit(
|
||||
units[0].entity_id,
|
||||
cls.REMOTE_CERT_FILE,
|
||||
local_ca_cert)
|
||||
return local_ca_cert
|
||||
@tenacity.retry(wait=tenacity.wait_exponential(multiplier=1,
|
||||
min=5, max=10),
|
||||
retry=tenacity.retry_if_exception_type(
|
||||
requests.exceptions.ConnectionError),
|
||||
reraise=True)
|
||||
def _run_request_get(self, url, verify, allow_redirects):
|
||||
"""Run a GET request against `url` with tenacity retries.
|
||||
|
||||
:param url: url to access
|
||||
:type url: str
|
||||
:param verify: Path to a CA_BUNDLE file or directory with certificates
|
||||
of trusted CAs or False to ignore verifying the SSL
|
||||
certificate.
|
||||
:type verify: Union[str, bool]
|
||||
:param allow_redirects: Set to True if redirect following is allowed.
|
||||
:type allow_redirects: bool
|
||||
:returns: Request response
|
||||
:rtype: requests.models.Response
|
||||
"""
|
||||
return requests.get(
|
||||
url,
|
||||
verify=verify,
|
||||
allow_redirects=allow_redirects)
|
||||
|
||||
@tenacity.retry(wait=tenacity.wait_exponential(multiplier=1,
|
||||
min=5, max=10),
|
||||
retry=tenacity.retry_if_exception_type(
|
||||
requests.exceptions.ConnectionError),
|
||||
reraise=True)
|
||||
def _run_request_post(self, url, verify, data, headers):
|
||||
"""Run a POST request against `url` with tenacity retries.
|
||||
|
||||
:param url: url to access
|
||||
:type url: str
|
||||
:param verify: Path to a CA_BUNDLE file or directory with certificates
|
||||
of trusted CAs or False to ignore verifying the SSL
|
||||
certificate.
|
||||
:type verify: Union[str, bool]
|
||||
:param data: Data to post to url
|
||||
:type data: str
|
||||
:param headers: Headers to set when posting
|
||||
:type headers: dict
|
||||
:returns: Request response
|
||||
:rtype: requests.models.Response
|
||||
"""
|
||||
return requests.post(
|
||||
url,
|
||||
data=data,
|
||||
headers=headers,
|
||||
verify=verify)
|
||||
|
||||
def get_master_dashboard_url(self):
|
||||
"""Get the url of the dashboard servicing requests.
|
||||
|
||||
Only one unit serves requests at any one time, the other units
|
||||
redirect to that unit.
|
||||
|
||||
:returns: URL of dashboard on unit
|
||||
:rtype: Union[str, None]
|
||||
"""
|
||||
units = zaza.model.get_units(self.application_name)
|
||||
for unit in units:
|
||||
r = self._run_request_get(
|
||||
'https://{}:8443'.format(unit.public_address),
|
||||
verify=self.local_ca_cert,
|
||||
allow_redirects=False)
|
||||
if r.status_code == requests.codes.ok:
|
||||
return 'https://{}:8443'.format(unit.public_address)
|
||||
|
||||
def test_dashboard_units(self):
|
||||
"""Check dashboard units are configured correctly."""
|
||||
# XXX: Switch to using CA for verification when
|
||||
# https://bugs.launchpad.net/cloud-archive/+bug/1933410
|
||||
# is fix released.
|
||||
# verify = self.local_ca_cert
|
||||
verify = False
|
||||
verify = self.local_ca_cert
|
||||
units = zaza.model.get_units(self.application_name)
|
||||
rcs = collections.defaultdict(list)
|
||||
for unit in units:
|
||||
r = requests.get(
|
||||
r = self._run_request_get(
|
||||
'https://{}:8443'.format(unit.public_address),
|
||||
verify=verify,
|
||||
allow_redirects=False)
|
||||
@@ -86,12 +141,70 @@ class CephDashboardTest(test_utils.BaseCharmTest):
|
||||
'role': role})
|
||||
return action
|
||||
|
||||
def get_random_username(self):
|
||||
"""Generate a username to use in tests.
|
||||
|
||||
:returns: Username
|
||||
:rtype: str
|
||||
"""
|
||||
return "zazauser-{}".format(uuid.uuid1())
|
||||
|
||||
def test_create_user(self):
|
||||
"""Test create user action."""
|
||||
test_user = 'marvin'
|
||||
test_user = self.get_random_username()
|
||||
action = self.create_user(test_user)
|
||||
self.assertEqual(action.status, "completed")
|
||||
self.assertTrue(action.data['results']['password'])
|
||||
action = self.create_user(test_user)
|
||||
# Action should fail as the user already exists
|
||||
self.assertEqual(action.status, "failed")
|
||||
|
||||
def access_dashboard(self, dashboard_url):
|
||||
"""Test logging via a dashboard url.
|
||||
|
||||
:param dashboard_url: Base url to use to login to
|
||||
:type dashboard_url: str
|
||||
"""
|
||||
user = self.get_random_username()
|
||||
action = self.create_user(username=user)
|
||||
self.assertEqual(action.status, "completed")
|
||||
password = action.data['results']['password']
|
||||
path = "api/auth"
|
||||
headers = {
|
||||
'Content-type': 'application/json',
|
||||
'Accept': 'application/vnd.ceph.api.v1.0'}
|
||||
payload = {"username": user, "password": password}
|
||||
verify = self.local_ca_cert
|
||||
r = self._run_request_post(
|
||||
"{}/{}".format(dashboard_url, path),
|
||||
verify=verify,
|
||||
data=json.dumps(payload),
|
||||
headers=headers)
|
||||
self.assertEqual(r.status_code, requests.codes.created)
|
||||
|
||||
def test_access_dashboard(self):
|
||||
"""Test logging in to the dashboard."""
|
||||
self.access_dashboard(self.get_master_dashboard_url())
|
||||
|
||||
def test_ceph_keys(self):
|
||||
"""Check that ceph services are properly registered."""
|
||||
status = zaza.model.get_status()
|
||||
applications = status.applications.keys()
|
||||
dashboard_keys = []
|
||||
ceph_keys = []
|
||||
if 'ceph-radosgw' in applications:
|
||||
dashboard_keys.extend(['RGW_API_ACCESS_KEY', 'RGW_API_SECRET_KEY'])
|
||||
if 'grafana' in applications:
|
||||
dashboard_keys.append('GRAFANA_API_URL')
|
||||
if 'prometheus' in applications:
|
||||
dashboard_keys.append('PROMETHEUS_API_HOST')
|
||||
ceph_keys.extend(
|
||||
['config/mgr/mgr/dashboard/{}'.format(k) for k in dashboard_keys])
|
||||
if 'ceph-iscsi' in applications:
|
||||
ceph_keys.append('mgr/dashboard/_iscsi_config')
|
||||
for key in ceph_keys:
|
||||
logging.info("Checking key {} exists".format(key))
|
||||
check_out = zaza.model.run_on_leader(
|
||||
'ceph-dashboard',
|
||||
'ceph config-key exists {}'.format(key))
|
||||
self.assertEqual(check_out['Code'], '0')
|
||||
|
||||
15
zaza/openstack/charm_tests/cinder_lvm/__init__.py
Normal file
15
zaza/openstack/charm_tests/cinder_lvm/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# 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.
|
||||
|
||||
"""Collection of code for setting up and testing cinder-lvm."""
|
||||
120
zaza/openstack/charm_tests/cinder_lvm/tests.py
Normal file
120
zaza/openstack/charm_tests/cinder_lvm/tests.py
Normal file
@@ -0,0 +1,120 @@
|
||||
#!/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.
|
||||
|
||||
"""Encapsulate cinder-lvm testing."""
|
||||
|
||||
import logging
|
||||
import uuid
|
||||
|
||||
import zaza.model
|
||||
import zaza.openstack.charm_tests.test_utils as test_utils
|
||||
import zaza.openstack.utilities.openstack as openstack_utils
|
||||
|
||||
|
||||
class CinderLVMTest(test_utils.OpenStackBaseTest):
|
||||
"""Encapsulate cinder-lvm tests."""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""Run class setup for running tests."""
|
||||
super(CinderLVMTest, cls).setUpClass(application_name='cinder-lvm')
|
||||
cls.model_name = zaza.model.get_juju_model()
|
||||
cls.cinder_client = openstack_utils.get_cinder_session_client(
|
||||
cls.keystone_session)
|
||||
cls.block_device = openstack_utils.get_application_config_option(
|
||||
'cinder-lvm', 'block-device', model_name=cls.model_name)
|
||||
|
||||
@classmethod
|
||||
def tearDown(cls):
|
||||
"""Remove test resources."""
|
||||
volumes = cls.cinder_client.volumes
|
||||
for volume in volumes.list():
|
||||
if volume.name.startswith('zaza'):
|
||||
try:
|
||||
volume.detach()
|
||||
volumes.delete(volume)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def test_cinder_config(self):
|
||||
"""Test that configuration options match our expectations."""
|
||||
logging.info('cinder-lvm')
|
||||
expected_contents = {
|
||||
'LVM-zaza-lvm': {
|
||||
'volume_clear': ['zero'],
|
||||
'volumes_dir': ['/var/lib/cinder/volumes'],
|
||||
'volume_name_template': ['volume-%s'],
|
||||
'volume_clear_size': ['0'],
|
||||
'volume_driver': ['cinder.volume.drivers.lvm.LVMVolumeDriver'],
|
||||
}}
|
||||
|
||||
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=10)
|
||||
|
||||
def _create_volume(self):
|
||||
"""Create a volume via the LVM backend."""
|
||||
test_vol_name = "zaza{}".format(uuid.uuid1().fields[0])
|
||||
vol_new = self.cinder_client.volumes.create(
|
||||
name=test_vol_name,
|
||||
size='1')
|
||||
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')
|
||||
return self.cinder_client.volumes.find(name=test_vol_name)
|
||||
|
||||
def test_create_volume(self):
|
||||
"""Test creating a volume with basic configuration."""
|
||||
test_vol = self._create_volume()
|
||||
self.assertTrue(test_vol)
|
||||
|
||||
host = getattr(test_vol, 'os-vol-host-attr:host').split('#')[0]
|
||||
self.assertIn('@LVM', host)
|
||||
|
||||
def test_volume_overwrite(self):
|
||||
"""Test creating a volume by overwriting one on a loop device."""
|
||||
with self.config_change({'overwrite': 'false',
|
||||
'block-device': self.block_device},
|
||||
{'overwrite': 'true',
|
||||
'block-device': '/tmp/vol|2G'}):
|
||||
self._create_volume()
|
||||
|
||||
def test_device_none(self):
|
||||
"""Test creating a volume in a dummy device (set as 'none')."""
|
||||
with self.config_change({'block-device': self.block_device},
|
||||
{'block-device': 'none'}):
|
||||
self._create_volume()
|
||||
|
||||
def test_remove_missing_volume(self):
|
||||
"""Test creating a volume after remove missing ones in a group."""
|
||||
with self.config_change({'remove-missing': 'false'},
|
||||
{'remove-missing': 'true'}):
|
||||
self._create_volume()
|
||||
|
||||
def test_remove_missing_force(self):
|
||||
"""Test creating a volume by forcefully removing missing ones."""
|
||||
with self.config_change({'remove-missing-force': 'false'},
|
||||
{'remove-missing-force': 'true'}):
|
||||
self._create_volume()
|
||||
15
zaza/openstack/charm_tests/cinder_netapp/__init__.py
Normal file
15
zaza/openstack/charm_tests/cinder_netapp/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# 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.
|
||||
|
||||
"""Collection of code for setting up and testing cinder-netapp."""
|
||||
77
zaza/openstack/charm_tests/cinder_netapp/tests.py
Normal file
77
zaza/openstack/charm_tests/cinder_netapp/tests.py
Normal file
@@ -0,0 +1,77 @@
|
||||
#!/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-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
|
||||
|
||||
|
||||
class CinderNetAppTest(test_utils.OpenStackBaseTest):
|
||||
"""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)
|
||||
|
||||
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':
|
||||
['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)
|
||||
@@ -25,6 +25,7 @@ import designateclient.v1.servers as servers
|
||||
import zaza.model
|
||||
import zaza.utilities.juju as juju_utils
|
||||
import zaza.openstack.charm_tests.test_utils as test_utils
|
||||
import zaza.openstack.utilities.generic as generic_utils
|
||||
import zaza.openstack.utilities.openstack as openstack_utils
|
||||
import zaza.openstack.charm_tests.designate.utils as designate_utils
|
||||
import zaza.charm_lifecycle.utils as lifecycle_utils
|
||||
@@ -33,6 +34,8 @@ import zaza.charm_lifecycle.utils as lifecycle_utils
|
||||
class BaseDesignateTest(test_utils.OpenStackBaseTest):
|
||||
"""Base for Designate charm tests."""
|
||||
|
||||
DESIGNATE_CONF = '/etc/designate/designate.conf'
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls, application_name=None, model_alias=None):
|
||||
"""Run class setup for running Designate charm operation tests."""
|
||||
@@ -88,6 +91,14 @@ class BaseDesignateTest(test_utils.OpenStackBaseTest):
|
||||
cls.server_create = cls.designate.servers.create
|
||||
cls.server_delete = cls.designate.servers.delete
|
||||
|
||||
@tenacity.retry(
|
||||
retry=tenacity.retry_if_result(lambda ret: ret is not None),
|
||||
# sleep for 2mins to allow 1min cron job to run...
|
||||
wait=tenacity.wait_fixed(120),
|
||||
stop=tenacity.stop_after_attempt(2))
|
||||
def _retry_check_commands_on_units(self, cmds, units):
|
||||
return generic_utils.check_commands_on_units(cmds, units)
|
||||
|
||||
|
||||
class DesignateAPITests(BaseDesignateTest):
|
||||
"""Tests interact with designate api."""
|
||||
@@ -119,6 +130,41 @@ class DesignateAPITests(BaseDesignateTest):
|
||||
self.server_delete(server_id)
|
||||
return wait()
|
||||
|
||||
def test_300_default_soa_config_options(self):
|
||||
"""Configure default SOA options."""
|
||||
test_domain = "test_300_example.com."
|
||||
DEFAULT_TTL = 60
|
||||
alternate_config = {'default-soa-minimum': 600,
|
||||
'default-ttl': DEFAULT_TTL,
|
||||
'default-soa-refresh-min': 300,
|
||||
'default-soa-refresh-max': 400,
|
||||
'default-soa-retry': 30}
|
||||
with self.config_change({}, alternate_config, "designate",
|
||||
reset_to_charm_default=True):
|
||||
for key, value in alternate_config.items():
|
||||
expected = "\n%s = %s\n" % (key.replace('-', '_'), value)
|
||||
zaza.model.block_until_file_has_contents(self.application_name,
|
||||
self.DESIGNATE_CONF,
|
||||
expected)
|
||||
logging.debug('Creating domain %s' % test_domain)
|
||||
domain = domains.Domain(name=test_domain,
|
||||
email="fred@amuletexample.com")
|
||||
|
||||
if self.post_xenial_queens:
|
||||
new_domain = self.domain_create(
|
||||
name=domain.name, email=domain.email)
|
||||
domain_id = new_domain['id']
|
||||
else:
|
||||
new_domain = self.domain_create(domain)
|
||||
domain_id = new_domain.id
|
||||
|
||||
self.assertIsNotNone(new_domain)
|
||||
self.assertEqual(new_domain['ttl'], DEFAULT_TTL)
|
||||
|
||||
logging.debug('Tidy up delete test record %s' % domain_id)
|
||||
self._wait_on_domain_gone(domain_id)
|
||||
logging.debug('Done with deletion of domain %s' % domain_id)
|
||||
|
||||
def test_400_server_creation(self):
|
||||
"""Simple api calls to create a server."""
|
||||
# Designate does not allow the last server to be deleted so ensure
|
||||
@@ -257,7 +303,26 @@ class DesignateCharmTests(BaseDesignateTest):
|
||||
logging.info("Testing pause resume")
|
||||
|
||||
|
||||
class DesignateTests(DesignateAPITests, DesignateCharmTests):
|
||||
class DesignateMonitoringTests(BaseDesignateTest):
|
||||
"""Designate charm monitoring tests."""
|
||||
|
||||
def test_nrpe_configured(self):
|
||||
"""Confirm that the NRPE service check files are created."""
|
||||
units = zaza.model.get_units(self.application_name)
|
||||
cmds = []
|
||||
for check_name in self.designate_svcs:
|
||||
cmds.append(
|
||||
'egrep -oh /usr/local.* /etc/nagios/nrpe.d/'
|
||||
'check_{}.cfg'.format(check_name)
|
||||
)
|
||||
ret = self._retry_check_commands_on_units(cmds, units)
|
||||
if ret:
|
||||
logging.info(ret)
|
||||
self.assertIsNone(ret, msg=ret)
|
||||
|
||||
|
||||
class DesignateTests(DesignateAPITests, DesignateCharmTests,
|
||||
DesignateMonitoringTests):
|
||||
"""Collection of all Designate test classes."""
|
||||
|
||||
pass
|
||||
|
||||
@@ -23,6 +23,7 @@ from manilaclient import client as manilaclient
|
||||
|
||||
import zaza.model
|
||||
import zaza.openstack.configure.guest as guest
|
||||
import zaza.openstack.utilities.generic as generic_utils
|
||||
import zaza.openstack.utilities.openstack as openstack_utils
|
||||
import zaza.openstack.charm_tests.test_utils as test_utils
|
||||
import zaza.openstack.charm_tests.nova.utils as nova_utils
|
||||
@@ -85,6 +86,27 @@ class ManilaTests(test_utils.OpenStackBaseTest):
|
||||
def _list_shares(self):
|
||||
return self.manila_client.shares.list()
|
||||
|
||||
def test_902_nrpe_service_checks(self):
|
||||
"""Confirm that the NRPE service check files are created."""
|
||||
units = zaza.model.get_units('manila')
|
||||
services = ['apache2', 'haproxy', 'manila-scheduler', 'manila-data']
|
||||
|
||||
cmds = []
|
||||
for check_name in services:
|
||||
cmds.append(
|
||||
'egrep -oh /usr/local.* /etc/nagios/nrpe.d/'
|
||||
'check_{}.cfg'.format(check_name)
|
||||
)
|
||||
|
||||
for attempt in tenacity.Retrying(
|
||||
wait=tenacity.wait_fixed(20),
|
||||
stop=tenacity.stop_after_attempt(2),
|
||||
reraise=True
|
||||
):
|
||||
with attempt:
|
||||
ret = generic_utils.check_commands_on_units(cmds, units)
|
||||
self.assertIsNone(ret, msg=ret)
|
||||
|
||||
|
||||
class ManilaBaseTest(test_utils.OpenStackBaseTest):
|
||||
"""Encapsulate a Manila basic functionality test."""
|
||||
|
||||
@@ -40,15 +40,19 @@ class ManilaGaneshaTests(manila_tests.ManilaBaseTest):
|
||||
def _restart_share_instance(self):
|
||||
logging.info('Restarting manila-share and nfs-ganesha')
|
||||
# It would be better for thie to derive the application name,
|
||||
# manila-ganesha-az1, from deployed instances fo the manila-ganesha
|
||||
# manila-ganesha-az1, from deployed instances of the manila-ganesha
|
||||
# charm; however, that functionality isn't present yet in zaza, so
|
||||
# this is hard coded to the application name used in that charm's
|
||||
# test bundles.
|
||||
for unit in zaza.model.get_units('manila-ganesha-az1'):
|
||||
# While we really only need to run this on the machine hosting
|
||||
# nfs-ganesha and manila-share, running it everywhere isn't
|
||||
# harmful. Pacemaker handles restarting the services
|
||||
zaza.model.run_on_unit(
|
||||
unit.entity_id,
|
||||
"systemctl stop manila-share nfs-ganesha")
|
||||
# this is a best-guestimate arrived at by looking for applications
|
||||
# with the word 'ganesha' in their names.
|
||||
ganeshas = [
|
||||
app for app in zaza.model.sync_deployed()
|
||||
if 'ganesha' in app and 'mysql' not in app]
|
||||
for ganesha in ganeshas:
|
||||
for unit in zaza.model.get_units(ganesha):
|
||||
# While we really only need to run this on the machine hosting
|
||||
# nfs-ganesha and manila-share, running it everywhere isn't
|
||||
# harmful. Pacemaker handles restarting the services
|
||||
zaza.model.run_on_unit(
|
||||
unit.entity_id,
|
||||
"systemctl stop manila-share nfs-ganesha")
|
||||
return True
|
||||
|
||||
@@ -203,7 +203,7 @@ class MySQLCommonTests(MySQLBaseTest):
|
||||
logging.info("Wait till model is idle ...")
|
||||
zaza.model.block_until_all_units_idle()
|
||||
|
||||
# If there are any blocekd mysql routers restart them.
|
||||
# If there are any blocked mysql routers restart them.
|
||||
self.restart_blocked_mysql_routers()
|
||||
assert not self.get_blocked_mysql_routers(), (
|
||||
"Should no longer be blocked mysql-router units")
|
||||
|
||||
@@ -44,6 +44,13 @@ OVERCLOUD_NETWORK_CONFIG = {
|
||||
"subnetpool_prefix": "192.168.0.0/16",
|
||||
}
|
||||
|
||||
OVERCLOUD_PROVIDER_VLAN_NETWORK_CONFIG = {
|
||||
"provider_vlan_net_name": "provider_vlan",
|
||||
"provider_vlan_subnet_name": "provider_vlan_subnet",
|
||||
"provider_vlan_cidr": "10.42.33.0/24",
|
||||
"provider_vlan_id": "2933",
|
||||
}
|
||||
|
||||
# The undercloud network configuration settings are substrate specific to
|
||||
# the environment where the tests are being executed. These settings may be
|
||||
# overridden by environment variables. See the doc string documentation for
|
||||
@@ -82,7 +89,7 @@ def basic_overcloud_network(limit_gws=None):
|
||||
# Get keystone session
|
||||
keystone_session = openstack_utils.get_overcloud_keystone_session()
|
||||
|
||||
# Get optional use_juju_wait for netw ork option
|
||||
# Get optional use_juju_wait for network option
|
||||
options = (lifecycle_utils
|
||||
.get_charm_config(fatal=False)
|
||||
.get('configure_options', {}))
|
||||
@@ -110,10 +117,31 @@ def basic_overcloud_network(limit_gws=None):
|
||||
' charm network configuration.'
|
||||
.format(provider_type))
|
||||
|
||||
# Confugre the overcloud network
|
||||
# Configure the overcloud network
|
||||
network.setup_sdn(network_config, keystone_session=keystone_session)
|
||||
|
||||
|
||||
def vlan_provider_overcloud_network():
|
||||
"""Run setup to create a VLAN provider network."""
|
||||
cli_utils.setup_logging()
|
||||
|
||||
# Get network configuration settings
|
||||
network_config = {}
|
||||
# Declared overcloud settings
|
||||
network_config.update(OVERCLOUD_NETWORK_CONFIG)
|
||||
# Declared provider vlan overcloud settings
|
||||
network_config.update(OVERCLOUD_PROVIDER_VLAN_NETWORK_CONFIG)
|
||||
# Environment specific settings
|
||||
network_config.update(generic_utils.get_undercloud_env_vars())
|
||||
|
||||
# Get keystone session
|
||||
keystone_session = openstack_utils.get_overcloud_keystone_session()
|
||||
|
||||
# Configure the overcloud network
|
||||
network.setup_sdn_provider_vlan(network_config,
|
||||
keystone_session=keystone_session)
|
||||
|
||||
|
||||
# Configure function to get one gateway with external network
|
||||
overcloud_network_one_gw = functools.partial(
|
||||
basic_overcloud_network,
|
||||
|
||||
@@ -26,6 +26,7 @@ import tenacity
|
||||
|
||||
from neutronclient.common import exceptions as neutronexceptions
|
||||
|
||||
import yaml
|
||||
import zaza
|
||||
import zaza.openstack.charm_tests.nova.utils as nova_utils
|
||||
import zaza.openstack.charm_tests.test_utils as test_utils
|
||||
@@ -248,6 +249,135 @@ class NeutronGatewayTest(NeutronPluginApiSharedTests):
|
||||
return services
|
||||
|
||||
|
||||
class NeutronGatewayShowActionsTest(test_utils.OpenStackBaseTest):
|
||||
"""Test "show" actions of Neutron Gateway Charm.
|
||||
|
||||
actions:
|
||||
* show-routers
|
||||
* show-dhcp-networks
|
||||
* show-loadbalancers
|
||||
"""
|
||||
|
||||
SKIP_LBAAS_TESTS = True
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls, application_name='neutron-gateway', model_alias=None):
|
||||
"""Run class setup for running Neutron Gateway tests."""
|
||||
super(NeutronGatewayShowActionsTest, cls).setUpClass(
|
||||
application_name, model_alias)
|
||||
# set up clients
|
||||
cls.neutron_client = (
|
||||
openstack_utils.get_neutron_session_client(cls.keystone_session))
|
||||
|
||||
# Loadbalancer tests not supported on Train and above and on
|
||||
# releases Mitaka and below
|
||||
current = openstack_utils.get_os_release()
|
||||
bionic_train = openstack_utils.get_os_release('bionic_train')
|
||||
xenial_mitaka = openstack_utils.get_os_release('xenial_mitaka')
|
||||
cls.SKIP_LBAAS_TESTS = not (xenial_mitaka > current < bionic_train)
|
||||
|
||||
def _assert_result_match(self, action_result, resource_list,
|
||||
resource_name):
|
||||
"""Assert that action_result contains same data as resource_list."""
|
||||
# make sure that action completed successfully
|
||||
if action_result.status != 'completed':
|
||||
self.fail('Juju Action failed: {}'.format(action_result.message))
|
||||
|
||||
# extract data from juju action
|
||||
action_data = action_result.data.get('results', {}).get(resource_name)
|
||||
resources_from_action = yaml.load(action_data)
|
||||
|
||||
# pull resource IDs from expected resource list and juju action data
|
||||
expected_resource_ids = {resource['id'] for resource in resource_list}
|
||||
result_resource_ids = resources_from_action.keys()
|
||||
|
||||
# assert that juju action returned expected resources
|
||||
self.assertEqual(result_resource_ids, expected_resource_ids)
|
||||
|
||||
def test_show_routers(self):
|
||||
"""Test that show-routers action reports correct neutron routers."""
|
||||
# fetch neutron routers using neutron client
|
||||
ngw_unit = zaza.model.get_units(self.application_name,
|
||||
model_name=self.model_name)[0]
|
||||
routers_from_client = self.neutron_client.list_routers().get(
|
||||
'routers', [])
|
||||
|
||||
if not routers_from_client:
|
||||
self.fail('At least one router must be configured for this test '
|
||||
'to pass.')
|
||||
|
||||
# fetch neutron routers using juju-action
|
||||
result = zaza.model.run_action(ngw_unit.entity_id,
|
||||
'show-routers',
|
||||
model_name=self.model_name)
|
||||
|
||||
# assert that data from neutron client match data from juju action
|
||||
self._assert_result_match(result, routers_from_client, 'router-list')
|
||||
|
||||
def test_show_dhcp_networks(self):
|
||||
"""Test that show-dhcp-networks reports correct DHCP networks."""
|
||||
# fetch DHCP networks using neutron client
|
||||
ngw_unit = zaza.model.get_units(self.application_name,
|
||||
model_name=self.model_name)[0]
|
||||
networks_from_client = self.neutron_client.list_networks().get(
|
||||
'networks', [])
|
||||
|
||||
if not networks_from_client:
|
||||
self.fail('At least one network must be configured for this test '
|
||||
'to pass.')
|
||||
|
||||
# fetch DHCP networks using juju-action
|
||||
result = zaza.model.run_action(ngw_unit.entity_id,
|
||||
'show-dhcp-networks',
|
||||
model_name=self.model_name)
|
||||
|
||||
# assert that data from neutron client match data from juju action
|
||||
self._assert_result_match(result, networks_from_client,
|
||||
'dhcp-networks')
|
||||
|
||||
def test_show_load_balancers(self):
|
||||
"""Test that show-loadbalancers reports correct loadbalancers."""
|
||||
if self.SKIP_LBAAS_TESTS:
|
||||
self.skipTest('LBaasV2 is not supported in this version.')
|
||||
|
||||
loadbalancer_id = None
|
||||
|
||||
try:
|
||||
# create LBaasV2 for the purpose of this test
|
||||
lbaas_name = 'test_lbaas'
|
||||
subnet_list = self.neutron_client.list_subnets(
|
||||
name='private_subnet').get('subnets', [])
|
||||
|
||||
if not subnet_list:
|
||||
raise RuntimeError('Expected subnet "private_subnet" is not '
|
||||
'configured.')
|
||||
|
||||
subnet = subnet_list[0]
|
||||
loadbalancer_data = {'loadbalancer': {'name': lbaas_name,
|
||||
'vip_subnet_id': subnet['id']
|
||||
}
|
||||
}
|
||||
loadbalancer = self.neutron_client.create_loadbalancer(
|
||||
body=loadbalancer_data)
|
||||
loadbalancer_id = loadbalancer['loadbalancer']['id']
|
||||
|
||||
# test that client and action report same data
|
||||
ngw_unit = zaza.model.get_units(self.application_name,
|
||||
model_name=self.model_name)[0]
|
||||
lbaas_from_client = self.neutron_client.list_loadbalancers().get(
|
||||
'loadbalancers', [])
|
||||
|
||||
result = zaza.model.run_action(ngw_unit.entity_id,
|
||||
'show-load-balancers',
|
||||
model_name=self.model_name)
|
||||
|
||||
self._assert_result_match(result, lbaas_from_client,
|
||||
'load-balancers')
|
||||
finally:
|
||||
if loadbalancer_id:
|
||||
self.neutron_client.delete_loadbalancer(loadbalancer_id)
|
||||
|
||||
|
||||
class NeutronCreateNetworkTest(test_utils.OpenStackBaseTest):
|
||||
"""Test creating a Neutron network through the API.
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ def _login(dashboard_url, domain, username, password, cafile=None):
|
||||
|
||||
# start session, get csrftoken
|
||||
client = requests.session()
|
||||
client.get(auth_url, verify=cafile)
|
||||
client.get(auth_url, verify=cafile, timeout=30)
|
||||
if 'csrftoken' in client.cookies:
|
||||
csrftoken = client.cookies['csrftoken']
|
||||
else:
|
||||
@@ -495,17 +495,17 @@ class OpenStackDashboardPolicydTests(policyd.BasePolicydSpecialization,
|
||||
zaza_model.get_lead_unit_name(self.application_name))
|
||||
logging.info("Dashboard is at {}".format(unit.public_address))
|
||||
overcloud_auth = openstack_utils.get_overcloud_auth()
|
||||
password = overcloud_auth['OS_PASSWORD'],
|
||||
password = overcloud_auth['OS_PASSWORD']
|
||||
logging.info("admin password is {}".format(password))
|
||||
# try to get the url which will either pass or fail with a 403
|
||||
overcloud_auth = openstack_utils.get_overcloud_auth()
|
||||
domain = 'admin_domain',
|
||||
username = 'admin',
|
||||
password = overcloud_auth['OS_PASSWORD'],
|
||||
domain = 'admin_domain'
|
||||
username = 'admin'
|
||||
client, response = _login(
|
||||
self.get_horizon_url(), domain, username, password)
|
||||
self.get_horizon_url(), domain, username, password,
|
||||
cafile=self.cacert)
|
||||
# now attempt to get the domains page
|
||||
_url = self.url.format(unit.public_address)
|
||||
logging.info("URL is {}".format(_url))
|
||||
result = client.get(_url)
|
||||
if result.status_code == 403:
|
||||
raise policyd.PolicydOperationFailedException("Not authenticated")
|
||||
|
||||
@@ -19,16 +19,21 @@
|
||||
import logging
|
||||
import unittest
|
||||
|
||||
import zaza.model
|
||||
import zaza.global_options
|
||||
|
||||
from zaza.openstack.utilities import (
|
||||
cli as cli_utils,
|
||||
upgrade_utils as upgrade_utils,
|
||||
openstack as openstack_utils,
|
||||
openstack_upgrade as openstack_upgrade,
|
||||
exceptions,
|
||||
generic,
|
||||
)
|
||||
from zaza.openstack.charm_tests.nova.tests import LTSGuestCreateTest
|
||||
|
||||
|
||||
class OpenStackUpgradeVMLaunchBase(object):
|
||||
class OpenStackUpgradeVMLaunchBase(unittest.TestCase):
|
||||
"""A base class to peform a simple validation on the cloud.
|
||||
|
||||
This wraps an OpenStack upgrade with a VM launch before and after the
|
||||
@@ -44,7 +49,6 @@ class OpenStackUpgradeVMLaunchBase(object):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""Run setup for OpenStack Upgrades."""
|
||||
print("Running OpenStackUpgradeMixin setUpClass")
|
||||
super().setUpClass()
|
||||
cls.lts = LTSGuestCreateTest()
|
||||
cls.lts.setUpClass()
|
||||
@@ -71,7 +75,6 @@ class WaitForMySQL(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""Set up class."""
|
||||
print("Running OpenstackUpgradeTests setUpClass")
|
||||
super().setUpClass()
|
||||
cli_utils.setup_logging()
|
||||
|
||||
@@ -88,7 +91,6 @@ class OpenStackUpgradeTestsFocalUssuri(OpenStackUpgradeVMLaunchBase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""Run setup for OpenStack Upgrades."""
|
||||
print("Running OpenstackUpgradeTests setUpClass")
|
||||
super().setUpClass()
|
||||
cli_utils.setup_logging()
|
||||
|
||||
@@ -97,21 +99,24 @@ class OpenStackUpgradeTestsFocalUssuri(OpenStackUpgradeVMLaunchBase):
|
||||
openstack_upgrade.run_upgrade_tests("cloud:focal-victoria")
|
||||
|
||||
|
||||
class OpenStackUpgradeTests(OpenStackUpgradeVMLaunchBase):
|
||||
class OpenStackUpgradeTestsByOption(OpenStackUpgradeVMLaunchBase):
|
||||
"""A Principal Class to encapsulate OpenStack Upgrade Tests.
|
||||
|
||||
A generic Test class that can discover which Ubuntu version and OpenStack
|
||||
version to upgrade from.
|
||||
A generic Test class that uses the options in the tests.yaml to use a charm
|
||||
to detect the Ubuntu and OpenStack versions and then workout what to
|
||||
upgrade to.
|
||||
|
||||
TODO: Not used at present. Use the declarative tests directly that choose
|
||||
the version to upgrade to. The functions that this class depends on need a
|
||||
bit more work regarding how the determine which version to go to.
|
||||
tests_options:
|
||||
openstack-upgrade:
|
||||
detect-using-charm: keystone
|
||||
|
||||
This will use the octavia application, detect the ubuntu version and then
|
||||
read the config to discover the current OpenStack version.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""Run setup for OpenStack Upgrades."""
|
||||
print("Running OpenstackUpgradeTests setUpClass")
|
||||
super().setUpClass()
|
||||
cli_utils.setup_logging()
|
||||
|
||||
@@ -122,16 +127,31 @@ class OpenStackUpgradeTests(OpenStackUpgradeVMLaunchBase):
|
||||
determine which ubuntu version to work from. Don't use until we can
|
||||
make it better.
|
||||
"""
|
||||
# TODO: work out the most recent Ubuntu version; we assume this is the
|
||||
# version that OpenStack is running on.
|
||||
ubuntu_version = "focal"
|
||||
logging.info("Getting all principle applications ...")
|
||||
principle_services = upgrade_utils.get_all_principal_applications()
|
||||
# get the tests_options / openstack-upgrade.detect-using-charm so that
|
||||
# the ubuntu version and OpenStack version can be detected.
|
||||
try:
|
||||
detect_charm = (
|
||||
zaza.global_options.get_options()
|
||||
.openstack_upgrade.detect_using_charm)
|
||||
except KeyError:
|
||||
raise exceptions.InvalidTestConfig(
|
||||
"Missing tests_options.openstack-upgrade.detect-using-charm "
|
||||
"config.")
|
||||
|
||||
unit = zaza.model.get_lead_unit(detect_charm)
|
||||
ubuntu_version = generic.get_series(unit)
|
||||
logging.info("Current version detected from {} is {}"
|
||||
.format(detect_charm, ubuntu_version))
|
||||
|
||||
logging.info(
|
||||
"Getting OpenStack vesions from principal applications ...")
|
||||
"Getting OpenStack version from %s ..." % detect_charm)
|
||||
current_versions = openstack_utils.get_current_os_versions(
|
||||
principle_services)
|
||||
logging.info("current versions: %s" % current_versions)
|
||||
[detect_charm])
|
||||
if not current_versions:
|
||||
raise exceptions.ApplicationNotFound(
|
||||
"No version found for {}?".format(detect_charm))
|
||||
|
||||
logging.info("current version: %s" % current_versions[detect_charm])
|
||||
# Find the lowest value openstack release across all services and make
|
||||
# sure all servcies are upgraded to one release higher than the lowest
|
||||
from_version = upgrade_utils.get_lowest_openstack_version(
|
||||
@@ -140,7 +160,6 @@ class OpenStackUpgradeTests(OpenStackUpgradeVMLaunchBase):
|
||||
to_version = upgrade_utils.determine_next_openstack_release(
|
||||
from_version)[1]
|
||||
logging.info("to version: %s" % to_version)
|
||||
# TODO: need to determine the ubuntu base verion that is being upgraded
|
||||
target_source = upgrade_utils.determine_new_source(
|
||||
ubuntu_version, from_version, to_version, single_increment=True)
|
||||
logging.info("target source: %s" % target_source)
|
||||
|
||||
@@ -168,6 +168,28 @@ class ChassisCharmOperationTest(BaseCharmOperationTest):
|
||||
'{}: "{}" no longer present'
|
||||
.format(unit.entity_id, expected_key))
|
||||
|
||||
def test_wrong_bridge_config(self):
|
||||
"""Confirm that ovn-chassis units block with wrong bridge config."""
|
||||
stored_target_deploy_status = self.test_config.get(
|
||||
'target_deploy_status', {})
|
||||
new_target_deploy_status = stored_target_deploy_status.copy()
|
||||
new_target_deploy_status[self.application_name] = {
|
||||
'ovn-chassis': 'blocked',
|
||||
}
|
||||
if 'target_deploy_status' in self.test_config:
|
||||
self.test_config['target_deploy_status'].update(
|
||||
new_target_deploy_status)
|
||||
else:
|
||||
self.test_config['target_deploy_status'] = new_target_deploy_status
|
||||
|
||||
with self.config_change(
|
||||
{'bridge-interface-mappings': ''},
|
||||
{'bridge-interface-mappings': 'incorrect'}):
|
||||
logging.info('Charm went into blocked state as expected, restore '
|
||||
'configuration')
|
||||
self.test_config[
|
||||
'target_deploy_status'] = stored_target_deploy_status
|
||||
|
||||
|
||||
class OVSOVNMigrationTest(test_utils.BaseCharmTest):
|
||||
"""OVS to OVN migration tests."""
|
||||
|
||||
@@ -14,315 +14,9 @@
|
||||
|
||||
"""Code for configuring and initializing tempest."""
|
||||
|
||||
import jinja2
|
||||
import urllib.parse
|
||||
import os
|
||||
import subprocess
|
||||
import logging
|
||||
|
||||
import zaza.utilities.deployment_env as deployment_env
|
||||
import zaza.openstack.utilities.juju as juju_utils
|
||||
import zaza.openstack.utilities.openstack as openstack_utils
|
||||
import zaza.openstack.charm_tests.tempest.utils as tempest_utils
|
||||
import zaza.openstack.charm_tests.glance.setup as glance_setup
|
||||
|
||||
SETUP_ENV_VARS = {
|
||||
'neutron': ['TEST_GATEWAY', 'TEST_CIDR_EXT', 'TEST_FIP_RANGE',
|
||||
'TEST_NAME_SERVER', 'TEST_CIDR_PRIV'],
|
||||
'swift': ['TEST_SWIFT_IP'],
|
||||
}
|
||||
|
||||
IGNORABLE_VARS = ['TEST_CIDR_PRIV']
|
||||
|
||||
TEMPEST_FLAVOR_NAME = 'm1.tempest'
|
||||
TEMPEST_ALT_FLAVOR_NAME = 'm2.tempest'
|
||||
TEMPEST_SVC_LIST = ['ceilometer', 'cinder', 'glance', 'heat', 'horizon',
|
||||
'ironic', 'neutron', 'nova', 'octavia', 'sahara', 'swift',
|
||||
'trove', 'zaqar']
|
||||
|
||||
|
||||
def add_application_ips(ctxt):
|
||||
"""Add application access IPs to context.
|
||||
|
||||
:param ctxt: Context dictionary
|
||||
:type ctxt: dict
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
ctxt['keystone'] = juju_utils.get_application_ip('keystone')
|
||||
ctxt['dashboard'] = juju_utils.get_application_ip('openstack-dashboard')
|
||||
ctxt['ncc'] = juju_utils.get_application_ip('nova-cloud-controller')
|
||||
|
||||
|
||||
def add_nova_config(ctxt, keystone_session):
|
||||
"""Add nova config to context.
|
||||
|
||||
:param ctxt: Context dictionary
|
||||
:type ctxt: dict
|
||||
:param keystone_session: keystoneauth1.session.Session object
|
||||
:type: keystoneauth1.session.Session
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
nova_client = openstack_utils.get_nova_session_client(
|
||||
keystone_session)
|
||||
for flavor in nova_client.flavors.list():
|
||||
if flavor.name == TEMPEST_FLAVOR_NAME:
|
||||
ctxt['flavor_ref'] = flavor.id
|
||||
if flavor.name == TEMPEST_ALT_FLAVOR_NAME:
|
||||
ctxt['flavor_ref_alt'] = flavor.id
|
||||
|
||||
|
||||
def add_neutron_config(ctxt, keystone_session):
|
||||
"""Add neutron config to context.
|
||||
|
||||
:param ctxt: Context dictionary
|
||||
:type ctxt: dict
|
||||
:param keystone_session: keystoneauth1.session.Session object
|
||||
:type: keystoneauth1.session.Session
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
current_release = openstack_utils.get_os_release()
|
||||
focal_ussuri = openstack_utils.get_os_release('focal_ussuri')
|
||||
neutron_client = openstack_utils.get_neutron_session_client(
|
||||
keystone_session)
|
||||
net = neutron_client.find_resource("network", "ext_net")
|
||||
ctxt['ext_net'] = net['id']
|
||||
router = neutron_client.find_resource("router", "provider-router")
|
||||
ctxt['provider_router_id'] = router['id']
|
||||
# For focal+ with OVN, we use the same settings as upstream gate.
|
||||
# This is because the l3_agent_scheduler extension is only
|
||||
# applicable for OVN when conventional layer-3 agent enabled:
|
||||
# https://docs.openstack.org/networking-ovn/2.0.1/features.html
|
||||
# This enables test_list_show_extensions to run successfully.
|
||||
if current_release >= focal_ussuri:
|
||||
extensions = ('address-scope,agent,allowed-address-pairs,'
|
||||
'auto-allocated-topology,availability_zone,'
|
||||
'binding,default-subnetpools,external-net,'
|
||||
'extra_dhcp_opt,multi-provider,net-mtu,'
|
||||
'network_availability_zone,network-ip-availability,'
|
||||
'port-security,provider,quotas,rbac-address-scope,'
|
||||
'rbac-policies,standard-attr-revisions,security-group,'
|
||||
'standard-attr-description,subnet_allocation,'
|
||||
'standard-attr-tag,standard-attr-timestamp,trunk,'
|
||||
'quota_details,router,extraroute,ext-gw-mode,'
|
||||
'fip-port-details,pagination,sorting,project-id,'
|
||||
'dns-integration,qos')
|
||||
ctxt['neutron_api_extensions'] = extensions
|
||||
else:
|
||||
ctxt['neutron_api_extensions'] = 'all'
|
||||
|
||||
|
||||
def add_glance_config(ctxt, keystone_session):
|
||||
"""Add glance config to context.
|
||||
|
||||
:param ctxt: Context dictionary
|
||||
:type ctxt: dict
|
||||
:param keystone_session: keystoneauth1.session.Session object
|
||||
:type: keystoneauth1.session.Session
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
glance_client = openstack_utils.get_glance_session_client(
|
||||
keystone_session)
|
||||
image = openstack_utils.get_images_by_name(
|
||||
glance_client, glance_setup.CIRROS_IMAGE_NAME)
|
||||
image_alt = openstack_utils.get_images_by_name(
|
||||
glance_client, glance_setup.CIRROS_ALT_IMAGE_NAME)
|
||||
if image:
|
||||
ctxt['image_id'] = image[0].id
|
||||
if image_alt:
|
||||
ctxt['image_alt_id'] = image_alt[0].id
|
||||
|
||||
|
||||
def add_cinder_config(ctxt, keystone_session):
|
||||
"""Add cinder config to context.
|
||||
|
||||
:param ctxt: Context dictionary
|
||||
:type ctxt: dict
|
||||
:param keystone_session: keystoneauth1.session.Session object
|
||||
:type: keystoneauth1.session.Session
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
volume_types = ['volumev2', 'volumev3']
|
||||
keystone_client = openstack_utils.get_keystone_session_client(
|
||||
keystone_session)
|
||||
for volume_type in volume_types:
|
||||
service = keystone_client.services.list(type=volume_type)
|
||||
if service:
|
||||
ctxt['catalog_type'] = volume_type
|
||||
break
|
||||
|
||||
|
||||
def add_keystone_config(ctxt, keystone_session):
|
||||
"""Add keystone config to context.
|
||||
|
||||
:param ctxt: Context dictionary
|
||||
:type ctxt: dict
|
||||
:param keystone_session: keystoneauth1.session.Session object
|
||||
:type: keystoneauth1.session.Session
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
keystone_client = openstack_utils.get_keystone_session_client(
|
||||
keystone_session)
|
||||
domain = keystone_client.domains.find(name="admin_domain")
|
||||
ctxt['default_domain_id'] = domain.id
|
||||
|
||||
|
||||
def add_octavia_config(ctxt):
|
||||
"""Add octavia config to context.
|
||||
|
||||
:param ctxt: Context dictionary
|
||||
:type ctxt: dict
|
||||
:returns: None
|
||||
:rtype: None
|
||||
:raises: subprocess.CalledProcessError
|
||||
"""
|
||||
subprocess.check_call([
|
||||
'curl',
|
||||
"{}:80/swift/v1/fixtures/test_server.bin".format(
|
||||
ctxt['test_swift_ip']),
|
||||
'-o', "{}/test_server.bin".format(ctxt['workspace_path'])
|
||||
])
|
||||
subprocess.check_call([
|
||||
'chmod', '+x',
|
||||
"{}/test_server.bin".format(ctxt['workspace_path'])
|
||||
])
|
||||
|
||||
|
||||
def get_service_list(keystone_session):
|
||||
"""Retrieve list of services from keystone.
|
||||
|
||||
:param keystone_session: keystoneauth1.session.Session object
|
||||
:type: keystoneauth1.session.Session
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
keystone_client = openstack_utils.get_keystone_session_client(
|
||||
keystone_session)
|
||||
return [s.name for s in keystone_client.services.list() if s.enabled]
|
||||
|
||||
|
||||
def add_environment_var_config(ctxt, services):
|
||||
"""Add environment variable config to context.
|
||||
|
||||
:param ctxt: Context dictionary
|
||||
:type ctxt: dict
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
deploy_env = deployment_env.get_deployment_context()
|
||||
missing_vars = []
|
||||
for svc, env_vars in SETUP_ENV_VARS.items():
|
||||
if svc in services:
|
||||
for var in env_vars:
|
||||
value = deploy_env.get(var)
|
||||
if value:
|
||||
ctxt[var.lower()] = value
|
||||
else:
|
||||
if var not in IGNORABLE_VARS:
|
||||
missing_vars.append(var)
|
||||
if missing_vars:
|
||||
raise ValueError(
|
||||
("Environment variables [{}] must all be set to run this"
|
||||
" test").format(', '.join(missing_vars)))
|
||||
|
||||
|
||||
def add_auth_config(ctxt):
|
||||
"""Add authorization config to context.
|
||||
|
||||
:param ctxt: Context dictionary
|
||||
:type ctxt: dict
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
overcloud_auth = openstack_utils.get_overcloud_auth()
|
||||
ctxt['proto'] = urllib.parse.urlparse(overcloud_auth['OS_AUTH_URL']).scheme
|
||||
ctxt['admin_username'] = overcloud_auth['OS_USERNAME']
|
||||
ctxt['admin_password'] = overcloud_auth['OS_PASSWORD']
|
||||
if overcloud_auth['API_VERSION'] == 3:
|
||||
ctxt['admin_project_name'] = overcloud_auth['OS_PROJECT_NAME']
|
||||
ctxt['admin_domain_name'] = overcloud_auth['OS_DOMAIN_NAME']
|
||||
ctxt['default_credentials_domain_name'] = (
|
||||
overcloud_auth['OS_PROJECT_DOMAIN_NAME'])
|
||||
|
||||
|
||||
def get_tempest_context(workspace_path):
|
||||
"""Generate the tempest config context.
|
||||
|
||||
:returns: Context dictionary
|
||||
:rtype: dict
|
||||
"""
|
||||
keystone_session = openstack_utils.get_overcloud_keystone_session()
|
||||
ctxt = {}
|
||||
ctxt['workspace_path'] = workspace_path
|
||||
ctxt_funcs = {
|
||||
'nova': add_nova_config,
|
||||
'neutron': add_neutron_config,
|
||||
'glance': add_glance_config,
|
||||
'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)
|
||||
for svc_name, ctxt_func in ctxt_funcs.items():
|
||||
if svc_name in ctxt['enabled_services']:
|
||||
ctxt_func(ctxt, keystone_session)
|
||||
add_environment_var_config(ctxt, ctxt['enabled_services'])
|
||||
add_auth_config(ctxt)
|
||||
if 'octavia' in ctxt['enabled_services']:
|
||||
add_octavia_config(ctxt)
|
||||
return ctxt
|
||||
|
||||
|
||||
def render_tempest_config(target_file, ctxt, template_name):
|
||||
"""Render tempest config for specified config file and template.
|
||||
|
||||
:param target_file: Name of file to render config to
|
||||
:type target_file: str
|
||||
:param ctxt: Context dictionary
|
||||
:type ctxt: dict
|
||||
:param template_name: Name of template file
|
||||
:type template_name: str
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
jenv = jinja2.Environment(loader=jinja2.PackageLoader(
|
||||
'zaza.openstack',
|
||||
'charm_tests/tempest/templates'))
|
||||
template = jenv.get_template(template_name)
|
||||
with open(target_file, 'w') as f:
|
||||
f.write(template.render(ctxt))
|
||||
|
||||
|
||||
def setup_tempest(tempest_template, accounts_template):
|
||||
"""Initialize tempest and render tempest config.
|
||||
|
||||
:param tempest_template: tempest.conf template
|
||||
:type tempest_template: module
|
||||
:param accounts_template: accounts.yaml template
|
||||
:type accounts_template: module
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
workspace_name, workspace_path = tempest_utils.get_workspace()
|
||||
tempest_utils.destroy_workspace(workspace_name, workspace_path)
|
||||
tempest_utils.init_workspace(workspace_path)
|
||||
context = get_tempest_context(workspace_path)
|
||||
render_tempest_config(
|
||||
os.path.join(workspace_path, 'etc/tempest.conf'),
|
||||
context,
|
||||
tempest_template)
|
||||
render_tempest_config(
|
||||
os.path.join(workspace_path, 'etc/accounts.yaml'),
|
||||
context,
|
||||
accounts_template)
|
||||
|
||||
|
||||
def render_tempest_config_keystone_v2():
|
||||
@@ -331,7 +25,11 @@ def render_tempest_config_keystone_v2():
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
setup_tempest('tempest_v2.j2', 'accounts.j2')
|
||||
logging.warning(
|
||||
'The render_tempest_config_keystone_v2 config step is deprecated. '
|
||||
'This is now directly done by the TempestTestWithKeystoneV2 test '
|
||||
'class.')
|
||||
tempest_utils.render_tempest_config_keystone_v2()
|
||||
|
||||
|
||||
def render_tempest_config_keystone_v3():
|
||||
@@ -340,4 +38,8 @@ def render_tempest_config_keystone_v3():
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
setup_tempest('tempest_v3.j2', 'accounts.j2')
|
||||
logging.warning(
|
||||
'The render_tempest_config_keystone_v3 config step is deprecated. '
|
||||
'This is now directly done by the TempestTestWithKeystoneV3 test '
|
||||
'class.')
|
||||
tempest_utils.render_tempest_config_keystone_v3()
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
"""Code for running tempest tests."""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
@@ -24,8 +25,8 @@ import zaza.openstack.charm_tests.tempest.utils as tempest_utils
|
||||
import tempfile
|
||||
|
||||
|
||||
class TempestTest():
|
||||
"""Tempest test class."""
|
||||
class TempestTestBase():
|
||||
"""Tempest test base class."""
|
||||
|
||||
test_runner = zaza.charm_lifecycle.test.DIRECT
|
||||
|
||||
@@ -33,8 +34,13 @@ class TempestTest():
|
||||
"""Run tempest tests as specified in tests/tests.yaml.
|
||||
|
||||
Test keys are parsed from ['tests_options']['tempest']['model'], where
|
||||
valid test keys are: smoke (bool), whitelist (list of tests), blacklist
|
||||
(list of tests), regex (list of regex's), and keep-workspace (bool).
|
||||
valid test keys are:
|
||||
- smoke (bool)
|
||||
- include-list (list of tests)
|
||||
- exclude-list (list of tests)
|
||||
- regex (list of regex's)
|
||||
- exclude-regex (list of regex's)
|
||||
- keep-workspace (bool)
|
||||
|
||||
:returns: Status of tempest run
|
||||
:rtype: bool
|
||||
@@ -57,23 +63,23 @@ class TempestTest():
|
||||
tempest_options.extend(
|
||||
['--regex',
|
||||
' '.join([reg for reg in config.get('regex')])])
|
||||
if config.get('black-regex'):
|
||||
if config.get('exclude-regex'):
|
||||
tempest_options.extend(
|
||||
['--black-regex',
|
||||
' '.join([reg for reg in config.get('black-regex')])])
|
||||
['--exclude-regex',
|
||||
' '.join([reg for reg in config.get('exclude-regex')])])
|
||||
with tempfile.TemporaryDirectory() as tmpdirname:
|
||||
if config.get('whitelist'):
|
||||
white_file = os.path.join(tmpdirname, 'white.cfg')
|
||||
with open(white_file, 'w') as f:
|
||||
f.write('\n'.join(config.get('whitelist')))
|
||||
if config.get('include-list'):
|
||||
include_file = os.path.join(tmpdirname, 'include.cfg')
|
||||
with open(include_file, 'w') as f:
|
||||
f.write('\n'.join(config.get('include-list')))
|
||||
f.write('\n')
|
||||
tempest_options.extend(['--whitelist-file', white_file])
|
||||
if config.get('blacklist'):
|
||||
black_file = os.path.join(tmpdirname, 'black.cfg')
|
||||
with open(black_file, 'w') as f:
|
||||
f.write('\n'.join(config.get('blacklist')))
|
||||
tempest_options.extend(['--include-list', include_file])
|
||||
if config.get('exclude-list'):
|
||||
exclude_file = os.path.join(tmpdirname, 'exclude.cfg')
|
||||
with open(exclude_file, 'w') as f:
|
||||
f.write('\n'.join(config.get('exclude-list')))
|
||||
f.write('\n')
|
||||
tempest_options.extend(['--blacklist-file', black_file])
|
||||
tempest_options.extend(['--exclude-list', exclude_file])
|
||||
print(tempest_options)
|
||||
try:
|
||||
subprocess.check_call(tempest_options)
|
||||
@@ -84,3 +90,54 @@ class TempestTest():
|
||||
if not keep_workspace or keep_workspace is not True:
|
||||
tempest_utils.destroy_workspace(workspace_name, workspace_path)
|
||||
return result
|
||||
|
||||
|
||||
class TempestTestWithKeystoneV2(TempestTestBase):
|
||||
"""Tempest test class to validate an OpenStack setup with Keystone V2."""
|
||||
|
||||
def run(self):
|
||||
"""Run tempest tests as specified in tests/tests.yaml.
|
||||
|
||||
See TempestTestBase.run() for the available test options.
|
||||
|
||||
:returns: Status of tempest run
|
||||
:rtype: bool
|
||||
"""
|
||||
tempest_utils.render_tempest_config_keystone_v2()
|
||||
return super().run()
|
||||
|
||||
|
||||
class TempestTestWithKeystoneV3(TempestTestBase):
|
||||
"""Tempest test class to validate an OpenStack setup with Keystone V2."""
|
||||
|
||||
def run(self):
|
||||
"""Run tempest tests as specified in tests/tests.yaml.
|
||||
|
||||
See TempestTestBase.run() for the available test options.
|
||||
|
||||
:returns: Status of tempest run
|
||||
:rtype: bool
|
||||
"""
|
||||
tempest_utils.render_tempest_config_keystone_v3()
|
||||
return super().run()
|
||||
|
||||
|
||||
class TempestTest(TempestTestBase):
|
||||
"""Tempest test class.
|
||||
|
||||
Requires running one of the render_tempest_config_keystone_v? Zaza
|
||||
configuration steps before.
|
||||
"""
|
||||
|
||||
def run(self):
|
||||
"""Run tempest tests as specified in tests/tests.yaml.
|
||||
|
||||
See TempestTestBase.run() for the available test options.
|
||||
|
||||
:returns: Status of tempest run
|
||||
:rtype: bool
|
||||
"""
|
||||
logging.warning(
|
||||
'The TempestTest test class is deprecated. Please use one of the '
|
||||
'TempestTestWithKeystoneV? test classes instead.')
|
||||
return super().run()
|
||||
|
||||
@@ -14,12 +14,50 @@
|
||||
|
||||
"""Utility code for working with tempest workspaces."""
|
||||
|
||||
import jinja2
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import urllib.parse
|
||||
|
||||
import zaza.model as model
|
||||
import zaza.utilities.deployment_env as deployment_env
|
||||
import zaza.openstack.utilities.juju as juju_utils
|
||||
import zaza.openstack.utilities.openstack as openstack_utils
|
||||
import zaza.openstack.charm_tests.glance.setup as glance_setup
|
||||
|
||||
SETUP_ENV_VARS = {
|
||||
'neutron': ['TEST_GATEWAY', 'TEST_CIDR_EXT', 'TEST_FIP_RANGE',
|
||||
'TEST_NAME_SERVER', 'TEST_CIDR_PRIV'],
|
||||
'swift': ['TEST_SWIFT_IP'],
|
||||
}
|
||||
|
||||
IGNORABLE_VARS = ['TEST_CIDR_PRIV']
|
||||
|
||||
TEMPEST_FLAVOR_NAME = 'm1.tempest'
|
||||
TEMPEST_ALT_FLAVOR_NAME = 'm2.tempest'
|
||||
TEMPEST_SVC_LIST = ['ceilometer', 'cinder', 'glance', 'heat', 'horizon',
|
||||
'ironic', 'neutron', 'nova', 'octavia', 'sahara', 'swift',
|
||||
'trove', 'zaqar']
|
||||
|
||||
|
||||
def render_tempest_config_keystone_v2():
|
||||
"""Render tempest config for Keystone V2 API.
|
||||
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
_setup_tempest('tempest_v2.j2', 'accounts.j2')
|
||||
|
||||
|
||||
def render_tempest_config_keystone_v3():
|
||||
"""Render tempest config for Keystone V3 API.
|
||||
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
_setup_tempest('tempest_v3.j2', 'accounts.j2')
|
||||
|
||||
|
||||
def get_workspace():
|
||||
@@ -53,7 +91,7 @@ def destroy_workspace(workspace_name, workspace_path):
|
||||
shutil.rmtree(workspace_path)
|
||||
|
||||
|
||||
def init_workspace(workspace_path):
|
||||
def _init_workspace(workspace_path):
|
||||
"""Initialize tempest workspace.
|
||||
|
||||
:param workspace_path: directory path where workspace is stored
|
||||
@@ -65,3 +103,289 @@ def init_workspace(workspace_path):
|
||||
subprocess.check_call(['tempest', 'init', workspace_path])
|
||||
except subprocess.CalledProcessError:
|
||||
pass
|
||||
|
||||
|
||||
def _setup_tempest(tempest_template, accounts_template):
|
||||
"""Initialize tempest and render tempest config.
|
||||
|
||||
:param tempest_template: tempest.conf template
|
||||
:type tempest_template: module
|
||||
:param accounts_template: accounts.yaml template
|
||||
:type accounts_template: module
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
workspace_name, workspace_path = get_workspace()
|
||||
destroy_workspace(workspace_name, workspace_path)
|
||||
_init_workspace(workspace_path)
|
||||
context = _get_tempest_context(workspace_path)
|
||||
_render_tempest_config(
|
||||
os.path.join(workspace_path, 'etc/tempest.conf'),
|
||||
context,
|
||||
tempest_template)
|
||||
_render_tempest_config(
|
||||
os.path.join(workspace_path, 'etc/accounts.yaml'),
|
||||
context,
|
||||
accounts_template)
|
||||
|
||||
|
||||
def _get_tempest_context(workspace_path):
|
||||
"""Generate the tempest config context.
|
||||
|
||||
:returns: Context dictionary
|
||||
:rtype: dict
|
||||
"""
|
||||
keystone_session = openstack_utils.get_overcloud_keystone_session()
|
||||
ctxt = {}
|
||||
ctxt['workspace_path'] = workspace_path
|
||||
ctxt_funcs = {
|
||||
'nova': _add_nova_config,
|
||||
'neutron': _add_neutron_config,
|
||||
'glance': _add_glance_config,
|
||||
'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)
|
||||
for svc_name, ctxt_func in ctxt_funcs.items():
|
||||
if svc_name in ctxt['enabled_services']:
|
||||
ctxt_func(ctxt, keystone_session)
|
||||
_add_environment_var_config(ctxt, ctxt['enabled_services'])
|
||||
_add_auth_config(ctxt)
|
||||
if 'octavia' in ctxt['enabled_services']:
|
||||
_add_octavia_config(ctxt)
|
||||
return ctxt
|
||||
|
||||
|
||||
def _render_tempest_config(target_file, ctxt, template_name):
|
||||
"""Render tempest config for specified config file and template.
|
||||
|
||||
:param target_file: Name of file to render config to
|
||||
:type target_file: str
|
||||
:param ctxt: Context dictionary
|
||||
:type ctxt: dict
|
||||
:param template_name: Name of template file
|
||||
:type template_name: str
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
jenv = jinja2.Environment(loader=jinja2.PackageLoader(
|
||||
'zaza.openstack',
|
||||
'charm_tests/tempest/templates'))
|
||||
template = jenv.get_template(template_name)
|
||||
with open(target_file, 'w') as f:
|
||||
f.write(template.render(ctxt))
|
||||
|
||||
|
||||
def _add_application_ips(ctxt):
|
||||
"""Add application access IPs to context.
|
||||
|
||||
:param ctxt: Context dictionary
|
||||
:type ctxt: dict
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
ctxt['keystone'] = juju_utils.get_application_ip('keystone')
|
||||
ctxt['dashboard'] = juju_utils.get_application_ip('openstack-dashboard')
|
||||
ctxt['ncc'] = juju_utils.get_application_ip('nova-cloud-controller')
|
||||
|
||||
|
||||
def _add_nova_config(ctxt, keystone_session):
|
||||
"""Add nova config to context.
|
||||
|
||||
:param ctxt: Context dictionary
|
||||
:type ctxt: dict
|
||||
:param keystone_session: keystoneauth1.session.Session object
|
||||
:type: keystoneauth1.session.Session
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
nova_client = openstack_utils.get_nova_session_client(
|
||||
keystone_session)
|
||||
for flavor in nova_client.flavors.list():
|
||||
if flavor.name == TEMPEST_FLAVOR_NAME:
|
||||
ctxt['flavor_ref'] = flavor.id
|
||||
if flavor.name == TEMPEST_ALT_FLAVOR_NAME:
|
||||
ctxt['flavor_ref_alt'] = flavor.id
|
||||
|
||||
|
||||
def _add_neutron_config(ctxt, keystone_session):
|
||||
"""Add neutron config to context.
|
||||
|
||||
:param ctxt: Context dictionary
|
||||
:type ctxt: dict
|
||||
:param keystone_session: keystoneauth1.session.Session object
|
||||
:type: keystoneauth1.session.Session
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
current_release = openstack_utils.get_os_release()
|
||||
focal_ussuri = openstack_utils.get_os_release('focal_ussuri')
|
||||
neutron_client = openstack_utils.get_neutron_session_client(
|
||||
keystone_session)
|
||||
net = neutron_client.find_resource("network", "ext_net")
|
||||
ctxt['ext_net'] = net['id']
|
||||
router = neutron_client.find_resource("router", "provider-router")
|
||||
ctxt['provider_router_id'] = router['id']
|
||||
# For focal+ with OVN, we use the same settings as upstream gate.
|
||||
# This is because the l3_agent_scheduler extension is only
|
||||
# applicable for OVN when conventional layer-3 agent enabled:
|
||||
# https://docs.openstack.org/networking-ovn/2.0.1/features.html
|
||||
# This enables test_list_show_extensions to run successfully.
|
||||
if current_release >= focal_ussuri:
|
||||
extensions = ('address-scope,agent,allowed-address-pairs,'
|
||||
'auto-allocated-topology,availability_zone,'
|
||||
'binding,default-subnetpools,external-net,'
|
||||
'extra_dhcp_opt,multi-provider,net-mtu,'
|
||||
'network_availability_zone,network-ip-availability,'
|
||||
'port-security,provider,quotas,rbac-address-scope,'
|
||||
'rbac-policies,standard-attr-revisions,security-group,'
|
||||
'standard-attr-description,subnet_allocation,'
|
||||
'standard-attr-tag,standard-attr-timestamp,trunk,'
|
||||
'quota_details,router,extraroute,ext-gw-mode,'
|
||||
'fip-port-details,pagination,sorting,project-id,'
|
||||
'dns-integration,qos')
|
||||
ctxt['neutron_api_extensions'] = extensions
|
||||
else:
|
||||
ctxt['neutron_api_extensions'] = 'all'
|
||||
|
||||
|
||||
def _add_glance_config(ctxt, keystone_session):
|
||||
"""Add glance config to context.
|
||||
|
||||
:param ctxt: Context dictionary
|
||||
:type ctxt: dict
|
||||
:param keystone_session: keystoneauth1.session.Session object
|
||||
:type: keystoneauth1.session.Session
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
glance_client = openstack_utils.get_glance_session_client(
|
||||
keystone_session)
|
||||
image = openstack_utils.get_images_by_name(
|
||||
glance_client, glance_setup.CIRROS_IMAGE_NAME)
|
||||
image_alt = openstack_utils.get_images_by_name(
|
||||
glance_client, glance_setup.CIRROS_ALT_IMAGE_NAME)
|
||||
if image:
|
||||
ctxt['image_id'] = image[0].id
|
||||
if image_alt:
|
||||
ctxt['image_alt_id'] = image_alt[0].id
|
||||
|
||||
|
||||
def _add_cinder_config(ctxt, keystone_session):
|
||||
"""Add cinder config to context.
|
||||
|
||||
:param ctxt: Context dictionary
|
||||
:type ctxt: dict
|
||||
:param keystone_session: keystoneauth1.session.Session object
|
||||
:type: keystoneauth1.session.Session
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
# The most most recent API version must be listed first.
|
||||
volume_types = ['volumev3', 'volumev2']
|
||||
keystone_client = openstack_utils.get_keystone_session_client(
|
||||
keystone_session)
|
||||
for volume_type in volume_types:
|
||||
service = keystone_client.services.list(type=volume_type)
|
||||
if service:
|
||||
ctxt['catalog_type'] = volume_type
|
||||
break
|
||||
|
||||
|
||||
def _add_keystone_config(ctxt, keystone_session):
|
||||
"""Add keystone config to context.
|
||||
|
||||
:param ctxt: Context dictionary
|
||||
:type ctxt: dict
|
||||
:param keystone_session: keystoneauth1.session.Session object
|
||||
:type: keystoneauth1.session.Session
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
keystone_client = openstack_utils.get_keystone_session_client(
|
||||
keystone_session)
|
||||
domain = keystone_client.domains.find(name="admin_domain")
|
||||
ctxt['default_domain_id'] = domain.id
|
||||
|
||||
|
||||
def _add_octavia_config(ctxt):
|
||||
"""Add octavia config to context.
|
||||
|
||||
:param ctxt: Context dictionary
|
||||
:type ctxt: dict
|
||||
:returns: None
|
||||
:rtype: None
|
||||
:raises: subprocess.CalledProcessError
|
||||
"""
|
||||
subprocess.check_call([
|
||||
'curl',
|
||||
"{}:80/swift/v1/fixtures/test_server.bin".format(
|
||||
ctxt['test_swift_ip']),
|
||||
'-o', "{}/test_server.bin".format(ctxt['workspace_path'])
|
||||
])
|
||||
subprocess.check_call([
|
||||
'chmod', '+x',
|
||||
"{}/test_server.bin".format(ctxt['workspace_path'])
|
||||
])
|
||||
|
||||
|
||||
def _add_environment_var_config(ctxt, services):
|
||||
"""Add environment variable config to context.
|
||||
|
||||
:param ctxt: Context dictionary
|
||||
:type ctxt: dict
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
deploy_env = deployment_env.get_deployment_context()
|
||||
missing_vars = []
|
||||
for svc, env_vars in SETUP_ENV_VARS.items():
|
||||
if svc in services:
|
||||
for var in env_vars:
|
||||
value = deploy_env.get(var)
|
||||
if value:
|
||||
ctxt[var.lower()] = value
|
||||
else:
|
||||
if var not in IGNORABLE_VARS:
|
||||
missing_vars.append(var)
|
||||
if missing_vars:
|
||||
raise ValueError(
|
||||
("Environment variables [{}] must all be set to run this"
|
||||
" test").format(', '.join(missing_vars)))
|
||||
|
||||
|
||||
def _add_auth_config(ctxt):
|
||||
"""Add authorization config to context.
|
||||
|
||||
:param ctxt: Context dictionary
|
||||
:type ctxt: dict
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
overcloud_auth = openstack_utils.get_overcloud_auth()
|
||||
ctxt['proto'] = urllib.parse.urlparse(overcloud_auth['OS_AUTH_URL']).scheme
|
||||
ctxt['admin_username'] = overcloud_auth['OS_USERNAME']
|
||||
ctxt['admin_password'] = overcloud_auth['OS_PASSWORD']
|
||||
if overcloud_auth['API_VERSION'] == 3:
|
||||
ctxt['admin_project_name'] = overcloud_auth['OS_PROJECT_NAME']
|
||||
ctxt['admin_domain_name'] = overcloud_auth['OS_DOMAIN_NAME']
|
||||
ctxt['default_credentials_domain_name'] = (
|
||||
overcloud_auth['OS_PROJECT_DOMAIN_NAME'])
|
||||
|
||||
|
||||
def _get_service_list(keystone_session):
|
||||
"""Retrieve list of services from keystone.
|
||||
|
||||
:param keystone_session: keystoneauth1.session.Session object
|
||||
:type: keystoneauth1.session.Session
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
keystone_client = openstack_utils.get_keystone_session_client(
|
||||
keystone_session)
|
||||
return [s.name for s in keystone_client.services.list() if s.enabled]
|
||||
|
||||
@@ -635,7 +635,7 @@ class OpenStackBaseTest(BaseCharmTest):
|
||||
|
||||
def launch_guest(self, guest_name, userdata=None, use_boot_volume=False,
|
||||
instance_key=None):
|
||||
"""Launch two guests to use in tests.
|
||||
"""Launch one guest 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.
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import logging
|
||||
import tenacity
|
||||
import unittest
|
||||
|
||||
import zaza.model as zaza_model
|
||||
|
||||
@@ -262,7 +263,7 @@ class WorkloadmgrCLIHelper(object):
|
||||
|
||||
retryer = tenacity.Retrying(
|
||||
wait=tenacity.wait_exponential(multiplier=1, max=30),
|
||||
stop=tenacity.stop_after_delay(900),
|
||||
stop=tenacity.stop_after_delay(1200),
|
||||
reraise=True,
|
||||
)
|
||||
|
||||
@@ -424,6 +425,22 @@ class TrilioBaseTest(test_utils.OpenStackBaseTest):
|
||||
logging.info("Initiating restore")
|
||||
workloadmgrcli.oneclick_restore(snapshot_id)
|
||||
|
||||
def test_update_trilio_action(self):
|
||||
"""Test that the action runs successfully."""
|
||||
action_name = 'update-trilio'
|
||||
actions = zaza_model.get_actions(
|
||||
self.application_name)
|
||||
if action_name not in actions:
|
||||
raise unittest.SkipTest(
|
||||
'Action {} not defined'.format(action_name))
|
||||
|
||||
generic_utils.assertActionRanOK(zaza_model.run_action(
|
||||
self.lead_unit,
|
||||
action_name,
|
||||
action_params={},
|
||||
model_name=self.model_name)
|
||||
)
|
||||
|
||||
|
||||
class TrilioGhostNFSShareTest(TrilioBaseTest):
|
||||
"""Tests for Trilio charms providing the ghost-share action."""
|
||||
|
||||
@@ -17,14 +17,12 @@
|
||||
import base64
|
||||
import functools
|
||||
import logging
|
||||
import requests
|
||||
import tempfile
|
||||
|
||||
import zaza.charm_lifecycle.utils as lifecycle_utils
|
||||
import zaza.openstack.charm_tests.vault.utils as vault_utils
|
||||
import zaza.model
|
||||
import zaza.openstack.utilities.cert
|
||||
import zaza.openstack.utilities.openstack
|
||||
import zaza.openstack.utilities.generic
|
||||
import zaza.openstack.utilities.exceptions as zaza_exceptions
|
||||
import zaza.utilities.juju as juju_utils
|
||||
@@ -95,7 +93,7 @@ def mojo_unseal_by_unit():
|
||||
def unseal_by_unit(cacert=None):
|
||||
"""Unseal any units reported as sealed using mojo cacert."""
|
||||
cacert = cacert or get_cacert_file()
|
||||
vault_creds = vault_utils.get_credentails()
|
||||
vault_creds = vault_utils.get_credentials()
|
||||
for client in vault_utils.get_clients(cacert=cacert):
|
||||
if client.hvac_client.is_sealed():
|
||||
client.hvac_client.unseal(vault_creds['keys'][0])
|
||||
@@ -126,7 +124,7 @@ async def async_mojo_unseal_by_unit():
|
||||
async def async_unseal_by_unit(cacert=None):
|
||||
"""Unseal any units reported as sealed using vault cacert."""
|
||||
cacert = cacert or get_cacert_file()
|
||||
vault_creds = vault_utils.get_credentails()
|
||||
vault_creds = vault_utils.get_credentials()
|
||||
for client in vault_utils.get_clients(cacert=cacert):
|
||||
if client.hvac_client.is_sealed():
|
||||
client.hvac_client.unseal(vault_creds['keys'][0])
|
||||
@@ -137,7 +135,8 @@ async def async_unseal_by_unit(cacert=None):
|
||||
unit_name, './hooks/update-status')
|
||||
|
||||
|
||||
def auto_initialize(cacert=None, validation_application='keystone', wait=True):
|
||||
def auto_initialize(cacert=None, validation_application='keystone', wait=True,
|
||||
skip_on_absent=False):
|
||||
"""Auto initialize vault for testing.
|
||||
|
||||
Generate a csr and uploading a signed certificate.
|
||||
@@ -149,9 +148,16 @@ def auto_initialize(cacert=None, validation_application='keystone', wait=True):
|
||||
:param validation_application: Name of application to be used as a
|
||||
client for validation.
|
||||
:type validation_application: str
|
||||
:param skip_on_absent: Non-fatal skip initialise if vault absent.
|
||||
:type validation_application: bool
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
if skip_on_absent:
|
||||
status = zaza.model.get_status()
|
||||
if 'vault' not in status.applications.keys():
|
||||
logging.info('Skipping auto_initialize, vault not in model')
|
||||
return
|
||||
logging.info('Running auto_initialize')
|
||||
basic_setup(cacert=cacert, unseal_and_authorize=True)
|
||||
|
||||
@@ -199,6 +205,24 @@ def auto_initialize(cacert=None, validation_application='keystone', wait=True):
|
||||
pass
|
||||
|
||||
|
||||
auto_initialize_opportunistic = functools.partial(
|
||||
auto_initialize,
|
||||
skip_on_absent=True)
|
||||
|
||||
|
||||
auto_initialize_opportunistic_no_validation = functools.partial(
|
||||
auto_initialize,
|
||||
validation_application=None,
|
||||
skip_on_absent=True)
|
||||
|
||||
|
||||
auto_initialize_opportunistic_no_validation_no_wait = functools.partial(
|
||||
auto_initialize,
|
||||
validation_application=None,
|
||||
wait=False,
|
||||
skip_on_absent=True)
|
||||
|
||||
|
||||
auto_initialize_no_validation = functools.partial(
|
||||
auto_initialize,
|
||||
validation_application=None)
|
||||
@@ -222,16 +246,4 @@ def validate_ca(cacertificate, application="keystone", port=5000):
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
zaza.openstack.utilities.openstack.block_until_ca_exists(
|
||||
application,
|
||||
cacertificate.decode().strip())
|
||||
vip = (zaza.model.get_application_config(application)
|
||||
.get("vip").get("value"))
|
||||
if vip:
|
||||
ip = vip
|
||||
else:
|
||||
ip = zaza.model.get_app_ips(application)[0]
|
||||
with tempfile.NamedTemporaryFile(mode='w') as fp:
|
||||
fp.write(cacertificate.decode())
|
||||
fp.flush()
|
||||
requests.get('https://{}:{}'.format(ip, str(port)), verify=fp.name)
|
||||
vault_utils.validate_ca(cacertificate, application, port)
|
||||
|
||||
@@ -21,9 +21,7 @@ import json
|
||||
import logging
|
||||
import unittest
|
||||
import uuid
|
||||
import tempfile
|
||||
|
||||
import requests
|
||||
import tenacity
|
||||
from hvac.exceptions import InternalServerError
|
||||
|
||||
@@ -64,7 +62,7 @@ class BaseVaultTest(test_utils.OpenStackBaseTest):
|
||||
cls.vip_client = vault_utils.get_vip_client()
|
||||
if cls.vip_client:
|
||||
cls.clients.append(cls.vip_client)
|
||||
cls.vault_creds = vault_utils.get_credentails()
|
||||
cls.vault_creds = vault_utils.get_credentials()
|
||||
vault_utils.unseal_all(cls.clients, cls.vault_creds['keys'][0])
|
||||
vault_utils.auth_all(cls.clients, cls.vault_creds['root_token'])
|
||||
vault_utils.ensure_secret_backend(cls.clients[0])
|
||||
@@ -180,26 +178,10 @@ class VaultTest(BaseVaultTest):
|
||||
except KeyError:
|
||||
# Already removed
|
||||
pass
|
||||
zaza.openstack.utilities.openstack.block_until_ca_exists(
|
||||
'keystone',
|
||||
cacert.decode().strip())
|
||||
zaza.model.wait_for_application_states(
|
||||
states=test_config.get('target_deploy_status', {}))
|
||||
ip = zaza.model.get_app_ips(
|
||||
'keystone')[0]
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode='w') as fp:
|
||||
fp.write(cacert.decode())
|
||||
fp.flush()
|
||||
# Avoid race condition and retry
|
||||
for attempt in tenacity.Retrying(
|
||||
stop=tenacity.stop_after_attempt(3),
|
||||
wait=tenacity.wait_exponential(
|
||||
multiplier=2, min=2, max=10)):
|
||||
with attempt:
|
||||
logging.info(
|
||||
"Attempting to connect to https://{}:5000".format(ip))
|
||||
requests.get('https://{}:5000'.format(ip), verify=fp.name)
|
||||
vault_utils.validate_ca(cacert)
|
||||
|
||||
def test_all_clients_authenticated(self):
|
||||
"""Check all vault clients are authenticated."""
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import base64
|
||||
import hvac
|
||||
import logging
|
||||
import requests
|
||||
import tempfile
|
||||
import urllib3
|
||||
@@ -27,6 +28,7 @@ import tenacity
|
||||
import collections
|
||||
|
||||
import zaza.model
|
||||
import zaza.openstack.utilities.openstack
|
||||
import zaza.utilities.networking as network_utils
|
||||
|
||||
AUTH_FILE = "vault_tests.yaml"
|
||||
@@ -70,10 +72,10 @@ class VaultFacade:
|
||||
def initialize(self):
|
||||
"""Initialise vault and store resulting credentials."""
|
||||
if self.is_initialized:
|
||||
self.vault_creds = get_credentails()
|
||||
self.vault_creds = get_credentials()
|
||||
else:
|
||||
self.vault_creds = init_vault(self.unseal_client)
|
||||
store_credentails(self.vault_creds)
|
||||
store_credentials(self.vault_creds)
|
||||
self.initialized = is_initialized(self.unseal_client)
|
||||
|
||||
def unseal(self):
|
||||
@@ -294,7 +296,7 @@ def find_unit_with_creds():
|
||||
return unit
|
||||
|
||||
|
||||
def get_credentails():
|
||||
def get_credentials():
|
||||
"""Retrieve vault token and keys from unit.
|
||||
|
||||
Retrieve vault token and keys from unit. These are stored on a unit
|
||||
@@ -315,7 +317,7 @@ def get_credentails():
|
||||
return creds
|
||||
|
||||
|
||||
def store_credentails(creds):
|
||||
def store_credentials(creds):
|
||||
"""Store the supplied credentials.
|
||||
|
||||
Store the supplied credentials on a vault unit. ONLY USE FOR FUNCTIONAL
|
||||
@@ -334,7 +336,7 @@ def store_credentails(creds):
|
||||
'~/{}'.format(AUTH_FILE))
|
||||
|
||||
|
||||
def get_credentails_from_file(auth_file):
|
||||
def get_credentials_from_file(auth_file):
|
||||
"""Read the vault credentials from the auth_file.
|
||||
|
||||
:param auth_file: Path to file with credentials
|
||||
@@ -347,7 +349,7 @@ def get_credentails_from_file(auth_file):
|
||||
return vault_creds
|
||||
|
||||
|
||||
def write_credentails(auth_file, vault_creds):
|
||||
def write_credentials(auth_file, vault_creds):
|
||||
"""Write the vault credentials to the auth_file.
|
||||
|
||||
:param auth_file: Path to file to write credentials
|
||||
@@ -434,3 +436,37 @@ def run_upload_signed_csr(pem, root_ca, allowed_domains):
|
||||
'root-ca': base64.b64encode(root_ca).decode(),
|
||||
'allowed-domains=': allowed_domains,
|
||||
'ttl': '24h'})
|
||||
|
||||
|
||||
@tenacity.retry(
|
||||
reraise=True,
|
||||
wait=tenacity.wait_exponential(multiplier=2, min=2, max=10),
|
||||
stop=tenacity.stop_after_attempt(3))
|
||||
def validate_ca(cacertificate, application="keystone", port=5000):
|
||||
"""Validate Certificate Authority against application.
|
||||
|
||||
:param cacertificate: PEM formatted CA certificate
|
||||
:type cacertificate: str
|
||||
:param application: Which application to validate against.
|
||||
:type application: str
|
||||
:param port: Port to validate against.
|
||||
:type port: int
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
zaza.openstack.utilities.openstack.block_until_ca_exists(
|
||||
application,
|
||||
cacertificate.decode().strip())
|
||||
vip = (zaza.model.get_application_config(application)
|
||||
.get("vip").get("value"))
|
||||
if vip:
|
||||
ip = vip
|
||||
else:
|
||||
ip = zaza.model.get_app_ips(application)[0]
|
||||
with tempfile.NamedTemporaryFile(mode='w') as fp:
|
||||
fp.write(cacertificate.decode())
|
||||
fp.flush()
|
||||
keystone_url = 'https://{}:{}'.format(ip, str(port))
|
||||
logging.info(
|
||||
'Attempting to connect to {}'.format(keystone_url))
|
||||
requests.get(keystone_url, verify=fp.name)
|
||||
|
||||
@@ -125,6 +125,9 @@ def launch_instance(instance_key, use_boot_volume=False, vm_name=None,
|
||||
nova_client.servers,
|
||||
instance.id,
|
||||
expected_status='ACTIVE',
|
||||
# NOTE(lourot): in some models this may sometimes take more than 15
|
||||
# minutes. See lp:1945991
|
||||
wait_iteration_max_time=120,
|
||||
stop_after_attempt=16)
|
||||
|
||||
logging.info('Checking cloud init is complete')
|
||||
|
||||
@@ -126,19 +126,19 @@ def setup_sdn(network_config, keystone_session=None):
|
||||
|
||||
logging.info("Configuring overcloud network")
|
||||
# Create the external network
|
||||
ext_network = openstack_utils.create_external_network(
|
||||
ext_network = openstack_utils.create_provider_network(
|
||||
neutron_client,
|
||||
project_id,
|
||||
network_config["external_net_name"])
|
||||
openstack_utils.create_external_subnet(
|
||||
openstack_utils.create_provider_subnet(
|
||||
neutron_client,
|
||||
project_id,
|
||||
ext_network,
|
||||
network_config["external_subnet_name"],
|
||||
network_config["default_gateway"],
|
||||
network_config["external_net_cidr"],
|
||||
network_config["start_floating_ip"],
|
||||
network_config["end_floating_ip"],
|
||||
network_config["external_subnet_name"])
|
||||
network_config["end_floating_ip"])
|
||||
provider_router = (
|
||||
openstack_utils.create_provider_router(neutron_client, project_id))
|
||||
openstack_utils.plug_extnet_into_router(
|
||||
@@ -183,6 +183,61 @@ def setup_sdn(network_config, keystone_session=None):
|
||||
openstack_utils.add_neutron_secgroup_rules(neutron_client, project_id)
|
||||
|
||||
|
||||
def setup_sdn_provider_vlan(network_config, keystone_session=None):
|
||||
"""Perform setup for Software Defined Network, specifically a provider VLAN.
|
||||
|
||||
:param network_config: Network configuration settings dictionary
|
||||
:type network_config: dict
|
||||
:param keystone_session: Keystone session object for overcloud
|
||||
:type keystone_session: keystoneauth1.session.Session object
|
||||
:returns: None
|
||||
:rtype: None
|
||||
"""
|
||||
# If a session has not been provided, acquire one
|
||||
if not keystone_session:
|
||||
keystone_session = openstack_utils.get_overcloud_keystone_session()
|
||||
|
||||
# Get authenticated clients
|
||||
keystone_client = openstack_utils.get_keystone_session_client(
|
||||
keystone_session)
|
||||
neutron_client = openstack_utils.get_neutron_session_client(
|
||||
keystone_session)
|
||||
|
||||
admin_domain = None
|
||||
if openstack_utils.get_keystone_api_version() > 2:
|
||||
admin_domain = "admin_domain"
|
||||
# Resolve the project name from the overcloud openrc into a project id
|
||||
project_id = openstack_utils.get_project_id(
|
||||
keystone_client,
|
||||
"admin",
|
||||
domain_name=admin_domain,
|
||||
)
|
||||
|
||||
logging.info("Configuring VLAN provider network")
|
||||
# Create the external network
|
||||
provider_vlan_network = openstack_utils.create_provider_network(
|
||||
neutron_client,
|
||||
project_id,
|
||||
net_name=network_config["provider_vlan_net_name"],
|
||||
external=False,
|
||||
shared=True,
|
||||
network_type='vlan',
|
||||
vlan_id=network_config["provider_vlan_id"])
|
||||
provider_vlan_subnet = openstack_utils.create_provider_subnet(
|
||||
neutron_client,
|
||||
project_id,
|
||||
provider_vlan_network,
|
||||
network_config["provider_vlan_subnet_name"],
|
||||
cidr=network_config["provider_vlan_cidr"],
|
||||
dhcp=True)
|
||||
openstack_utils.plug_subnet_into_router(
|
||||
neutron_client,
|
||||
network_config["router_name"],
|
||||
provider_vlan_network,
|
||||
provider_vlan_subnet)
|
||||
openstack_utils.add_neutron_secgroup_rules(neutron_client, project_id)
|
||||
|
||||
|
||||
def setup_gateway_ext_port(network_config, keystone_session=None,
|
||||
limit_gws=None,
|
||||
use_juju_wait=True):
|
||||
|
||||
@@ -15,6 +15,12 @@
|
||||
"""Module of exceptions that zaza may raise."""
|
||||
|
||||
|
||||
class InvalidTestConfig(Exception):
|
||||
"""Exception when the test configuration is invalid."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class MissingOSAthenticationException(Exception):
|
||||
"""Exception when some data needed to authenticate is missing."""
|
||||
|
||||
|
||||
@@ -986,8 +986,11 @@ def configure_networking_charms(networking_data, macs, use_juju_wait=True):
|
||||
current_data_port = get_application_config_option(
|
||||
application_name,
|
||||
networking_data.port_config_key)
|
||||
if current_data_port == config[networking_data.port_config_key]:
|
||||
logging.info('Config already set to value')
|
||||
if current_data_port:
|
||||
logging.info("Skip update of external network data port config."
|
||||
"Config '{}' already set to value: {}".format(
|
||||
networking_data.port_config_key,
|
||||
current_data_port))
|
||||
return
|
||||
|
||||
model.set_application_config(
|
||||
@@ -997,7 +1000,7 @@ def configure_networking_charms(networking_data, macs, use_juju_wait=True):
|
||||
# to deal with all the non ['active', 'idle', 'Unit is ready.']
|
||||
# workload/agent states and msgs that our mojo specs are exposed to.
|
||||
if use_juju_wait:
|
||||
juju_wait.wait(wait_for_workload=True)
|
||||
juju_wait.wait(wait_for_workload=True, max_wait=2700)
|
||||
else:
|
||||
zaza.model.wait_for_agent_status()
|
||||
# TODO: shouldn't access get_charm_config() here as it relies on
|
||||
@@ -1056,14 +1059,23 @@ def configure_charmed_openstack_on_maas(network_config, limit_gws=None):
|
||||
:type limit_gws: Optional[int]
|
||||
"""
|
||||
networking_data = get_charm_networking_data(limit_gws=limit_gws)
|
||||
macs = [
|
||||
mim.mac
|
||||
for mim in zaza.utilities.maas.get_macs_from_cidr(
|
||||
macs = []
|
||||
machines = set()
|
||||
for mim in zaza.utilities.maas.get_macs_from_cidr(
|
||||
zaza.utilities.maas.get_maas_client_from_juju_cloud_data(
|
||||
zaza.model.get_cloud_data()),
|
||||
network_config['external_net_cidr'],
|
||||
link_mode=zaza.utilities.maas.LinkMode.LINK_UP)
|
||||
]
|
||||
link_mode=zaza.utilities.maas.LinkMode.LINK_UP):
|
||||
if mim.machine_id in machines:
|
||||
logging.warning("Machine {} has multiple unconfigured interfaces, "
|
||||
"ignoring interface {} ({})."
|
||||
.format(mim.machine_id, mim.ifname, mim.mac))
|
||||
continue
|
||||
logging.info("Machine {} selected {} ({}) for external networking."
|
||||
.format(mim.machine_id, mim.ifname, mim.mac))
|
||||
machines.add(mim.machine_id)
|
||||
macs.append(mim.mac)
|
||||
|
||||
if macs:
|
||||
configure_networking_charms(
|
||||
networking_data, macs, use_juju_wait=False)
|
||||
@@ -1127,8 +1139,10 @@ def create_project_network(neutron_client, project_id, net_name='private',
|
||||
return network
|
||||
|
||||
|
||||
def create_external_network(neutron_client, project_id, net_name='ext_net'):
|
||||
"""Create the external network.
|
||||
def create_provider_network(neutron_client, project_id, net_name='ext_net',
|
||||
external=True, shared=False, network_type='flat',
|
||||
vlan_id=None):
|
||||
"""Create a provider network.
|
||||
|
||||
:param neutron_client: Authenticated neutronclient
|
||||
:type neutron_client: neutronclient.Client object
|
||||
@@ -1136,25 +1150,35 @@ def create_external_network(neutron_client, project_id, net_name='ext_net'):
|
||||
:type project_id: string
|
||||
:param net_name: Network name
|
||||
:type net_name: string
|
||||
:param shared: The network should be external
|
||||
:type shared: boolean
|
||||
:param shared: The network should be shared between projects
|
||||
:type shared: boolean
|
||||
:param net_type: Network type: GRE, VXLAN, local, VLAN
|
||||
:type net_type: string
|
||||
:param net_name: VLAN ID
|
||||
:type net_name: string
|
||||
:returns: Network object
|
||||
:rtype: dict
|
||||
"""
|
||||
networks = neutron_client.list_networks(name=net_name)
|
||||
if len(networks['networks']) == 0:
|
||||
logging.info('Configuring external network')
|
||||
logging.info('Creating %s %s network: %s', network_type,
|
||||
'external' if external else 'provider', net_name)
|
||||
network_msg = {
|
||||
'name': net_name,
|
||||
'router:external': True,
|
||||
'router:external': external,
|
||||
'shared': shared,
|
||||
'tenant_id': project_id,
|
||||
'provider:physical_network': 'physnet1',
|
||||
'provider:network_type': 'flat',
|
||||
'provider:network_type': network_type,
|
||||
}
|
||||
|
||||
logging.info('Creating new external network definition: %s',
|
||||
net_name)
|
||||
if network_type == 'vlan':
|
||||
network_msg['provider:segmentation_id'] = int(vlan_id)
|
||||
network = neutron_client.create_network(
|
||||
{'network': network_msg})['network']
|
||||
logging.info('New external network created: %s', network['id'])
|
||||
logging.info('Network %s created: %s', net_name, network['id'])
|
||||
else:
|
||||
logging.warning('Network %s already exists.', net_name)
|
||||
network = networks['networks'][0]
|
||||
@@ -1214,11 +1238,12 @@ def create_project_subnet(neutron_client, project_id, network, cidr, dhcp=True,
|
||||
return subnet
|
||||
|
||||
|
||||
def create_external_subnet(neutron_client, project_id, network,
|
||||
def create_provider_subnet(neutron_client, project_id, network,
|
||||
subnet_name='ext_net_subnet',
|
||||
default_gateway=None, cidr=None,
|
||||
start_floating_ip=None, end_floating_ip=None,
|
||||
subnet_name='ext_net_subnet'):
|
||||
"""Create the external subnet.
|
||||
dhcp=False):
|
||||
"""Create the provider subnet.
|
||||
|
||||
:param neutron_client: Authenticated neutronclient
|
||||
:type neutron_client: neutronclient.Client object
|
||||
@@ -1228,14 +1253,16 @@ def create_external_subnet(neutron_client, project_id, network,
|
||||
:type network: dict
|
||||
:param default_gateway: Deafault gateway IP address
|
||||
:type default_gateway: string
|
||||
:param subnet_name: Subnet name
|
||||
:type subnet_name: string
|
||||
:param cidr: Network CIDR
|
||||
:type cidr: string
|
||||
:param start_floating_ip: Start of floating IP range: IP address
|
||||
:type start_floating_ip: string or None
|
||||
:param end_floating_ip: End of floating IP range: IP address
|
||||
:type end_floating_ip: string or None
|
||||
:param subnet_name: Subnet name
|
||||
:type subnet_name: string
|
||||
:param dhcp: Run DHCP on this subnet
|
||||
:type dhcp: boolean
|
||||
:returns: Subnet object
|
||||
:rtype: dict
|
||||
"""
|
||||
@@ -1244,7 +1271,7 @@ def create_external_subnet(neutron_client, project_id, network,
|
||||
subnet_msg = {
|
||||
'name': subnet_name,
|
||||
'network_id': network['id'],
|
||||
'enable_dhcp': False,
|
||||
'enable_dhcp': dhcp,
|
||||
'ip_version': 4,
|
||||
'tenant_id': project_id
|
||||
}
|
||||
@@ -2194,8 +2221,9 @@ def find_cirros_image(arch):
|
||||
:returns: URL for latest cirros image
|
||||
:rtype: str
|
||||
"""
|
||||
http_connection_timeout = 10 # seconds
|
||||
opener = get_urllib_opener()
|
||||
f = opener.open(CIRROS_RELEASE_URL)
|
||||
f = opener.open(CIRROS_RELEASE_URL, timeout=http_connection_timeout)
|
||||
version = f.read().strip().decode()
|
||||
cirros_img = 'cirros-{}-{}-disk.img'.format(version, arch)
|
||||
return '{}/{}/{}'.format(CIRROS_IMAGE_URL, version, cirros_img)
|
||||
@@ -2819,7 +2847,7 @@ def ssh_test(username, ip, vm_name, password=None, privkey=None, retry=True):
|
||||
return_string = stdout.readlines()[0].strip()
|
||||
|
||||
if return_string == vm_name:
|
||||
logging.info('SSH to %s(%s) succesfull' % (vm_name, ip))
|
||||
logging.info('SSH to %s(%s) successful' % (vm_name, ip))
|
||||
else:
|
||||
logging.info('SSH to %s(%s) failed (%s != %s)' % (vm_name, ip,
|
||||
return_string,
|
||||
|
||||
@@ -132,12 +132,23 @@ def action_upgrade_apps(applications, model_name=None):
|
||||
status=status,
|
||||
model_name=model_name)
|
||||
|
||||
# NOTE(lourot): we're more likely to time out while waiting for the
|
||||
# action's result if we launch an action while the model is still
|
||||
# executing. Thus it's safer to wait for the model to settle between
|
||||
# actions.
|
||||
zaza.model.block_until_all_units_idle(model_name)
|
||||
pause_units(hacluster_units, model_name=model_name)
|
||||
|
||||
zaza.model.block_until_all_units_idle(model_name)
|
||||
pause_units(target, model_name=model_name)
|
||||
|
||||
zaza.model.block_until_all_units_idle(model_name)
|
||||
action_unit_upgrade(target, model_name=model_name)
|
||||
|
||||
zaza.model.block_until_all_units_idle(model_name)
|
||||
resume_units(target, model_name=model_name)
|
||||
|
||||
zaza.model.block_until_all_units_idle(model_name)
|
||||
resume_units(hacluster_units, model_name=model_name)
|
||||
|
||||
done.extend(target)
|
||||
|
||||
@@ -37,6 +37,8 @@ UBUNTU_OPENSTACK_RELEASE = OrderedDict([
|
||||
('focal', 'ussuri'),
|
||||
('groovy', 'victoria'),
|
||||
('hirsute', 'wallaby'),
|
||||
('impish', 'xena'),
|
||||
('jammy', 'yoga'),
|
||||
])
|
||||
|
||||
|
||||
@@ -60,6 +62,9 @@ OPENSTACK_CODENAMES = OrderedDict([
|
||||
('2019.2', 'train'),
|
||||
('2020.1', 'ussuri'),
|
||||
('2020.2', 'victoria'),
|
||||
('2021.1', 'wallaby'),
|
||||
('2021.2', 'xena'),
|
||||
('2022.1', 'yoga'),
|
||||
])
|
||||
|
||||
OPENSTACK_RELEASES_PAIRS = [
|
||||
@@ -71,7 +76,10 @@ OPENSTACK_RELEASES_PAIRS = [
|
||||
'bionic_stein', 'disco_stein', 'bionic_train',
|
||||
'eoan_train', 'bionic_ussuri', 'focal_ussuri',
|
||||
'focal_victoria', 'groovy_victoria',
|
||||
'focal_wallaby', 'hirsute_wallaby']
|
||||
'focal_wallaby', 'hirsute_wallaby',
|
||||
'focal_xena', 'impish_xena',
|
||||
'focal_yoga', 'jammy_yoga',
|
||||
]
|
||||
|
||||
SWIFT_CODENAMES = OrderedDict([
|
||||
('diablo',
|
||||
|
||||
Reference in New Issue
Block a user