Merge remote-tracking branch 'upstream/master' into ovn-downscale

This commit is contained in:
Martin Kalcok
2022-11-22 12:59:49 +01:00
28 changed files with 1069 additions and 130 deletions
+3 -2
View File
@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.5, 3.6, 3.7, 3.8, 3.9]
python-version: ["3.8", "3.9", "3.10"]
steps:
- uses: actions/checkout@v1
@@ -19,9 +19,10 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
sudo apt-get install -q --yes libxml2-dev libxslt1-dev
python -m pip install --upgrade pip
pip install tox tox-gh-actions
- name: Lint with tox
run: tox -e pep8
- name: Test with tox
run: tox -e py${{ matrix.python-version }}
run: tox -e py${{ matrix.python-version }}
+4 -2
View File
@@ -11,14 +11,16 @@ async_generator
pyopenssl<22.1.0
boto3<1.25
PyYAML<=4.2,>=3.0
PyYAML<=4.2,>=3.0; python_version < '3.10'
PyYAML>=5.1; python_version >= '3.10'
flake8>=2.2.4
flake8-docstrings
flake8-per-file-ignores
pydocstyle<4.0.0
coverage<6.0.0 # coverage 6.0+ drops support for py3.5/py2.7
mock>=1.2
nose>=1.3.7
pytest
pytest-cov
pbr>=1.8.0,<1.9.0
simplejson>=2.2.0
netifaces>=0.10.4
+6
View File
@@ -29,6 +29,11 @@ install_require = [
'async_generator',
'boto3',
# pyopenssl depends on a newer version of cryptography since 22.1.0
# TypeError: deprecated() got an unexpected keyword argument 'name'
# https://github.com/pyca/pyopenssl/commit/a145fc3bc6d2e943434beb2f04bbf9b18930296f
'pyopenssl<22.1.0',
# Newer versions require a Rust compiler to build, see
# * https://github.com/openstack-charmers/zaza/issues/421
# * https://mail.python.org/pipermail/cryptography-dev/2021-January/001003.html
@@ -46,6 +51,7 @@ install_require = [
'gnocchiclient>=7.0.5,<8.0.0',
'pika>=1.1.0,<2.0.0',
'python-barbicanclient>=4.0.1,<5.0.0',
'python-cloudkittyclient',
'python-designateclient>=1.5,<3.0.0',
'python-heatclient<2.0.0',
'python-ironicclient',
+5 -13
View File
@@ -24,24 +24,12 @@ setenv = VIRTUAL_ENV={envdir}
install_command =
{toxinidir}/pip.sh install {opts} {packages}
commands = nosetests --with-coverage --cover-package=zaza.openstack {posargs} {toxinidir}/unit_tests
commands = pytest --cov=zaza.openstack {posargs} {toxinidir}/unit_tests
[testenv:py3]
basepython = python3
deps = -r{toxinidir}/requirements.txt
[testenv:py3.5]
basepython = python3.5
deps = -r{toxinidir}/requirements.txt
[testenv:py3.6]
basepython = python3.6
deps = -r{toxinidir}/requirements.txt
[testenv:py3.7]
basepython = python3.7
deps = -r{toxinidir}/requirements.txt
[testenv:py3.8]
basepython = python3.8
deps = -r{toxinidir}/requirements.txt
@@ -50,6 +38,10 @@ deps = -r{toxinidir}/requirements.txt
basepython = python3.9
deps = -r{toxinidir}/requirements.txt
[testenv:py3.10]
basepython = python3.10
deps = -r{toxinidir}/requirements.txt
[testenv:pep8]
basepython = python3
deps = -r{toxinidir}/requirements.txt
@@ -180,13 +180,6 @@ class TestOpenStackUtils(ut_utils.BaseTestCase):
network_msg)
def test_get_keystone_scope(self):
self.patch_object(openstack_utils, "get_current_os_versions")
# <= Liberty
self.get_current_os_versions.return_value = {"keystone": "liberty"}
self.assertEqual(openstack_utils.get_keystone_scope(), "DOMAIN")
# > Liberty
self.get_current_os_versions.return_value = {"keystone": "mitaka"}
self.assertEqual(openstack_utils.get_keystone_scope(), "PROJECT")
def _test_get_overcloud_auth(self, tls_relation=False, ssl_cert=False,
@@ -197,6 +190,8 @@ class TestOpenStackUtils(ut_utils.BaseTestCase):
self.patch_object(openstack_utils, "get_current_os_versions")
self.patch_object(openstack_utils, "get_remote_ca_cert_file")
self.patch_object(openstack_utils.juju_utils, 'leader_get')
self.patch_object(openstack_utils.juju_utils, 'is_k8s_deployment')
self.is_k8s_deployment.return_value = False
if tls_relation:
self.patch_object(openstack_utils.model, "scp_from_unit")
self.patch_object(openstack_utils.model, "get_first_unit_name")
+2 -45
View File
@@ -62,6 +62,7 @@ class CephLowLevelTest(test_utils.OpenStackBaseTest):
# Process name and quantity of processes to expect on each unit
ceph_mon_processes = {
'ceph-mon': 1,
'ceph-mgr': 1,
}
ceph_osd_processes = {
@@ -90,7 +91,7 @@ class CephLowLevelTest(test_utils.OpenStackBaseTest):
"""
logging.info('Checking ceph-osd and ceph-mon services...')
services = {}
ceph_services = ['ceph-mon']
ceph_services = ['ceph-mon', 'ceph-mgr']
services['ceph-osd/0'] = ['ceph-osd']
services['ceph-mon/0'] = ceph_services
@@ -141,50 +142,6 @@ class CephRelationTest(test_utils.OpenStackBaseTest):
# The private address in relation should match ceph-mon/0 address
self.assertEqual(rel_private_ip, remote_ip)
def _ceph_to_ceph_osd_relation(self, remote_unit_name):
"""Verify the cephX to ceph-osd relation data.
Helper function to test the relation.
"""
logging.info('Checking {}:ceph-osd mon relation data...'.
format(remote_unit_name))
unit_name = 'ceph-osd/0'
relation_name = 'osd'
remote_unit = zaza_model.get_unit_from_name(remote_unit_name)
remote_ip = zaza_model.get_unit_public_address(remote_unit)
cmd = 'leader-get fsid'
result = zaza_model.run_on_unit(remote_unit_name, cmd)
fsid = result.get('Stdout').strip()
expected = {
'private-address': remote_ip,
'ceph-public-address': remote_ip,
'fsid': fsid,
}
relation = juju_utils.get_relation_from_unit(
unit_name,
remote_unit_name,
relation_name
)
for e_key, e_value in expected.items():
a_value = relation[e_key]
self.assertEqual(e_value, a_value)
self.assertTrue(relation['osd_bootstrap_key'] is not None)
def test_ceph0_to_ceph_osd_relation(self):
"""Verify the ceph0 to ceph-osd relation data."""
remote_unit_name = 'ceph-mon/0'
self._ceph_to_ceph_osd_relation(remote_unit_name)
def test_ceph1_to_ceph_osd_relation(self):
"""Verify the ceph1 to ceph-osd relation data."""
remote_unit_name = 'ceph-mon/1'
self._ceph_to_ceph_osd_relation(remote_unit_name)
def test_ceph2_to_ceph_osd_relation(self):
"""Verify the ceph2 to ceph-osd relation data."""
remote_unit_name = 'ceph-mon/2'
self._ceph_to_ceph_osd_relation(remote_unit_name)
class CephTest(test_utils.OpenStackBaseTest):
"""Ceph common functional tests."""
@@ -0,0 +1,17 @@
#!/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.
"""Collection of code for setting up and testing cloudkitty."""
@@ -0,0 +1,26 @@
#!/usr/bin/env python3
# Copyright 2019 Canonical Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Code for configuring Cloudkitty."""
def basic_setup():
"""Run setup for testing Cloudkitty.
Setup for testing Cloudkitty is currently part of functional
tests.
"""
pass
@@ -0,0 +1,117 @@
#!/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 Cloudkitty testing."""
import logging
import zaza.openstack.charm_tests.test_utils as test_utils
import zaza.openstack.utilities.openstack as openstack_utils
from cloudkittyclient import client
class CloudkittyTest(test_utils.OpenStackBaseTest):
"""Encapsulate Cloudkitty tests."""
API_VERSION = '1'
@classmethod
def setUpClass(cls):
"""Run class setup for running Cloudkitty tests."""
super(CloudkittyTest, cls).setUpClass()
cls.current_release = openstack_utils.get_os_release()
logging.info('Instantiating cloudkitty client...')
cls.cloudkitty = client.Client(
CloudkittyTest.API_VERSION,
session=cls.keystone_session
)
def tearDown(self):
"""Run teardown for test class."""
rating = self.cloudkitty.rating
if not rating.get_module(module_id='hashmap').get('enabled'):
rating.update_module(module_id='hashmap', enabled=True)
hashmap = rating.hashmap
for service in hashmap.get_service().get('services'):
service_id = service.get('service_id')
fields = hashmap.get_field(service_id=service_id)
for field in fields.get('fields'):
hashmap.delete_field(field_id=field.get('field_id'))
mappings = hashmap.get_mapping(service_id=service_id)
for mapping in mappings.get('mappings'):
hashmap.delete_mapping(mapping_id=mapping.get('mapping_id'))
hashmap.delete_service(service_id=service_id)
for group in hashmap.get_group().get('groups'):
hashmap.delete_group(group_id=group.get('group_id'))
def test_400_api_connection(self):
"""Simple api calls to check service is up and responding."""
report = self.cloudkitty.report
tenants_list = report.get_tenants()
assert tenants_list == []
def test_401_module_enable_and_disable(self):
"""Test enable and disable module via API."""
rating = self.cloudkitty.rating
modules = rating.get_module()
for module in modules.get('modules'):
module_id = module.get('module_id')
# noop module can't be disabled
if module_id == 'noop':
continue
logging.info('Enabling {} module'.format(module_id))
rating.update_module(module_id=module_id, enabled=True)
module = rating.get_module(module_id=module_id)
assert module.get('enabled')
logging.info('Disabling {} module'.format(module_id))
rating.update_module(module_id=module_id, enabled=False)
module = rating.get_module(module_id=module_id)
assert not module.get('enabled')
def test_402_create_mapping(self):
"""Test mapping create via API."""
rating = self.cloudkitty.rating
if not rating.get_module(module_id='hashmap').get('enabled'):
rating.update_module(module_id='hashmap', enabled=True)
hashmap = rating.hashmap
service = hashmap.create_service(name='test-service')
service_id = service.get('service_id')
field = hashmap.create_field(name='test-field', service_id=service_id)
field_id = field.get('field_id')
group = hashmap.create_group(name='test-group')
group_id = group.get('group_id')
hashmap.create_mapping(
type='flat', field_id=field_id,
group_id=group_id, value='test-value', cost=0.1)
@@ -16,6 +16,13 @@
import logging
import os
from tenacity import (
Retrying,
retry_if_exception_type,
stop_after_attempt,
wait_fixed,
)
import zaza.model as zaza_model
import zaza.openstack.charm_tests.test_utils as test_utils
@@ -43,8 +50,14 @@ class DesignateBindServiceIPsTest(test_utils.OpenStackBaseTest):
zaza_model.set_application_config(self.APPLICATION, config)
zaza_model.wait_for_application_states()
configured_ips = zaza_model.run_on_unit(self.UNIT, "ip addr")
self.assertIn(self.VIP, configured_ips["Stdout"])
for attempt in Retrying(wait=wait_fixed(2),
retry=retry_if_exception_type(AssertionError),
reraise=True,
stop=stop_after_attempt(10)):
with attempt:
configured_ips = zaza_model.run_on_unit(self.UNIT,
"ip addr")
self.assertIn(self.VIP, configured_ips["Stdout"])
logging.info("Removing service IP configuration from %s unit.",
self.UNIT)
@@ -21,6 +21,7 @@ import tenacity
import pprint
import zaza.model as zaza_model
import zaza.charm_lifecycle.utils as lifecycle_utils
import zaza.openstack.utilities.generic as generic_utils
import zaza.openstack.utilities.openstack as openstack_utils
@@ -77,3 +78,17 @@ def sync_images():
logging.info('Contents of Keystone service catalog: "{}"'
.format(pprint.pformat(catalog)))
raise
def set_latest_property_config():
"""Enable set_latest_property config.
This config adds `latest=true` to new synced images.
"""
logging.info("Change config `set_latest_property=true`")
zaza_model.set_application_config('glance-simplestreams-sync',
{'set_latest_property': 'true',
'snap-channel': 'edge'})
test_config = lifecycle_utils.get_charm_config(fatal=False)
zaza_model.wait_for_application_states(
states=test_config.get('target_deploy_status', {}))
@@ -125,3 +125,23 @@ class GlanceSimpleStreamsSyncTest(test_utils.OpenStackBaseTest):
_check_local_product_streams(expected_images)
logging.debug("Local product stream successful")
class GlanceSimpleStreamsSyncWithPropertiesTest(GlanceSimpleStreamsSyncTest):
"""Glance Simple Streams Sync Test with Image property.
`setup.py:set_latest_property_config()` is required by this test and it is
called during charm-glance-simplestreams-sync/tests/tests.yaml:configure
phase.
"""
# TODO(guimalufb) test if the latest property gets removed from old images
def test_200_check_image_latest_property(self):
"""Verify that images had metadata property set."""
logging.debug("Checking images with latest=true property...")
filter_properties = {'filters': {'latest': 'true'}}
images = self.glance_client.images.list(**filter_properties)
self.assertTrue(len(list(images)) > 0,
"'latest=true' property not found in glance images"
" list")
@@ -15,6 +15,8 @@
"""Code for setting up keystone."""
import logging
import requests
import tenacity
import keystoneauth1
@@ -167,3 +169,43 @@ def add_tempest_roles():
:rtype: None
"""
_add_additional_roles(TEMPEST_ROLES)
def wait_for_url(url, ok_codes=None):
"""Wait for url to return acceptable return code.
:param url: url to test
:type url: str
:param ok_codes: HTTP codes that are acceptable
:type ok_codes: Optional[List[int]]
:raises: AssertionError
"""
if not ok_codes:
ok_codes = [requests.codes.ok]
for attempt in tenacity.Retrying(
stop=tenacity.stop_after_attempt(10),
wait=tenacity.wait_exponential(
multiplier=1, min=2, max=60)):
with attempt:
r = requests.get(url)
logging.info("{} returned {}".format(url, r.status_code))
assert r.status_code in ok_codes
def wait_for_all_endpoints(interface='public'):
"""Check all endpoints are returning an acceptable return code.
:param interface: Endpoint type to check. public, admin or internal
:type interface: str
:raises: AssertionError
"""
overcloud_auth = openstack_utils.get_overcloud_auth()
wait_for_url(overcloud_auth['OS_AUTH_URL'])
session = openstack_utils.get_overcloud_keystone_session()
keystone_client = openstack_utils.get_keystone_session_client(session)
for service in keystone_client.services.list():
for ep in keystone_client.endpoints.list(service=service,
interface=interface):
wait_for_url(
ep.url,
[requests.codes.ok, requests.codes.multiple_choices])
@@ -0,0 +1,14 @@
# Copyright 2022 Canonical Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Collection of code to setup Keystone Federation."""
@@ -0,0 +1,101 @@
# Copyright 2022 Canonical Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Code for setting up a Keystone Federation Provider."""
import json
import logging
import keystoneauth1
from zaza.openstack.utilities import (
cli as cli_utils,
openstack as openstack_utils,
)
def keystone_federation_setup(federated_domain: str,
federated_group: str,
idp_name: str,
idp_remote_id: str,
protocol_name: str,
map_template: str,
role_name: str = 'Member',
):
"""Configure Keystone Federation."""
cli_utils.setup_logging()
keystone_session = openstack_utils.get_overcloud_keystone_session()
keystone_client = openstack_utils.get_keystone_session_client(
keystone_session)
try:
domain = keystone_client.domains.find(name=federated_domain)
logging.info('Reusing domain %s with id %s',
federated_domain, domain.id)
except keystoneauth1.exceptions.http.NotFound:
logging.info('Creating domain %s', federated_domain)
domain = keystone_client.domains.create(
federated_domain,
description="Federated Domain",
enabled=True)
try:
group = keystone_client.groups.find(
name=federated_group, domain=domain)
logging.info('Reusing group %s with id %s', federated_group, group.id)
except keystoneauth1.exceptions.http.NotFound:
logging.info('Creating group %s', federated_group)
group = keystone_client.groups.create(
federated_group,
domain=domain,
enabled=True)
role = keystone_client.roles.find(name=role_name)
assert role is not None, 'Role %s not found' % role_name
logging.info('Granting %s role to group %s on domain %s',
role.name, group.name, domain.name)
keystone_client.roles.grant(role, group=group, domain=domain)
try:
idp = keystone_client.federation.identity_providers.get(idp_name)
logging.info('Reusing identity provider %s with id %s',
idp_name, idp.id)
except keystoneauth1.exceptions.http.NotFound:
logging.info('Creating identity provider %s', idp_name)
idp = keystone_client.federation.identity_providers.create(
idp_name,
remote_ids=[idp_remote_id],
domain_id=domain.id,
enabled=True)
JSON_RULES = json.loads(map_template.format(
domain_id=domain.id, group_id=group.id, role_name=role_name))
map_name = "{}_mapping".format(idp_name)
try:
keystone_client.federation.mappings.get(map_name)
logging.info('Reusing mapping %s', map_name)
except keystoneauth1.exceptions.http.NotFound:
logging.info('Creating mapping %s', map_name)
keystone_client.federation.mappings.create(
map_name, rules=JSON_RULES)
try:
keystone_client.federation.protocols.get(idp_name, protocol_name)
logging.info('Reusing protocol %s from identity provider %s',
protocol_name, idp_name)
except keystoneauth1.exceptions.http.NotFound:
logging.info(('Creating protocol %s for identity provider %s with '
'mapping %s'), protocol_name, idp_name, map_name)
keystone_client.federation.protocols.create(
protocol_name, mapping=map_name, identity_provider=idp)
+1 -1
View File
@@ -71,7 +71,7 @@ class MasakariTest(test_utils.OpenStackBaseTest):
except novaclient.exceptions.NotFound:
logging.info('Launching new guest')
guest = zaza.openstack.configure.guest.launch_instance(
'bionic',
'jammy',
use_boot_volume=True,
meta={'HA_Enabled': 'True'},
vm_name=vm_name)
@@ -0,0 +1,125 @@
# Copyright 2022 Canonical Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""MySQL Prometheus Exporter Testing."""
import json
import urllib.request
import zaza.model as zaza_model
from zaza.openstack.charm_tests.mysql.tests import MySQLBaseTest
class PrometheusMySQLExporterTest(MySQLBaseTest):
"""Functional tests check prometheus exporter."""
@classmethod
def setUpClass(cls, application_name=None):
"""Run class setup for running mysql tests."""
super().setUpClass(application_name="mysql-innodb-cluster")
cls.application = "mysql-innodb-cluster"
cls.snap_name = "mysqld-exporter"
cls.service_name = "snap.mysqld-exporter.mysqld-exporter.service"
def _exporter_http_check(
self,
cmd,
expected,
):
"""Exec check cmd on each unit in the application.
:param cmd: The check command run on unit
:type cmd: str
:param expected: Expected result code
:type expected: str
"""
for unit in zaza_model.get_units(self.application):
result = zaza_model.run_on_unit(unit.name, cmd)
self.assertEqual(result.get("Code"), expected)
def _check_service_status_is(
self,
active=True,
):
cmd = "systemctl is-active {}".format(
self.service_name
)
excepted = "active\n"
if not active:
excepted = "inactive\n"
for unit in zaza_model.get_units(self.application):
result = zaza_model.run_on_unit(unit.name, cmd)
self.assertEqual(result.get("stdout"), excepted)
def test_01_exporter_http_check(self):
"""Check exporter endpoint is working."""
self._exporter_http_check(
cmd="curl http://localhost:9104",
expected="0",
)
for unit in zaza_model.get_units(self.application):
url = "http://{}:9104/metrics".format(
unit.public_address)
with urllib.request.urlopen(url) as resp:
metrics = resp.read().decode("utf-8")
if not any(
str(line) == "mysql_up 1"
for line in metrics.split("\n")
):
self.fail(
"Exporter permission not correct on {}".format(
unit.public_address
)
)
def test_02_exporter_service_relation_trigger(self):
"""Relation trigger exporter service start/stop."""
zaza_model.remove_relation(
self.application,
"prometheus2:target",
"mysql-innodb-cluster:prometheus",
)
for unit in zaza_model.get_units(self.application):
zaza_model.block_until_unit_wl_status(unit.name, "active")
zaza_model.block_until_all_units_idle()
self._check_service_status_is(active=False)
# Recover
zaza_model.add_relation(
self.application,
"prometheus2:target",
"mysql-innodb-cluster:prometheus",
)
for unit in zaza_model.get_units(self.application):
zaza_model.block_until_unit_wl_status(unit.name, "active")
zaza_model.block_until_all_units_idle()
self._check_service_status_is(active=True)
def test_03_snap_config(self):
"""Check snap set config is working."""
cmd = "sudo snap get {} mysql -d".format(self.snap_name)
for unit in zaza_model.get_units(self.application):
result = zaza_model.run_on_unit(unit.name, cmd)
json_mysql_config = json.loads(
result.get("stdout")).get("mysql")
json_mysql_config.pop("password")
self.assertEqual(
json_mysql_config,
{
"host": unit.public_address,
"port": 3306,
"user": "prom_exporter"
}
)
+2 -1
View File
@@ -31,9 +31,10 @@ import zaza.openstack.charm_tests.nova.utils as nova_utils
def ensure_lts_images():
"""Ensure that bionic and focal images are available for the tests."""
"""Ensure LTS images are available for the tests."""
glance_setup.add_lts_image(image_name='bionic', release='bionic')
glance_setup.add_lts_image(image_name='focal', release='focal')
glance_setup.add_lts_image(image_name='jammy', release='jammy')
def add_amphora_image(image_url=None):
@@ -0,0 +1,15 @@
# Copyright 2022 Canonical Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Collection of code for setting up and testing Keystone OpenID Connect."""
+178
View File
@@ -0,0 +1,178 @@
# Copyright 2022 Canonical Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Code for setting up Keystone OpenID Connect federation."""
import logging
import zaza.model
from zaza.charm_lifecycle import utils as lifecycle_utils
from zaza.openstack.charm_tests.keystone_federation.utils import (
keystone_federation_setup,
)
from zaza.openstack.utilities import (
cli as cli_utils,
openstack as openstack_utils,
)
APP_NAME = 'keystone-openidc'
FEDERATED_DOMAIN = "federated_domain"
FEDERATED_GROUP = "federated_users"
MEMBER = "Member"
IDP = "openid"
LOCAL_IDP_REMOTE_ID = 'https://{}:8443/realms/demorealm'
REMOTE_ID = "http://openidc"
PROTOCOL_NAME = "openid"
MAP_TEMPLATE = '''
[{{
"local": [
{{
"user": {{
"name": "{{1}}",
"email": "{{2}}"
}},
"group": {{
"name": "{group_id}",
"domain": {{
"id": "{domain_id}"
}}
}},
"projects": [
{{
"name": "{{1}}_project",
"roles": [
{{
"name": "{role_name}"
}}
]
}}
]
}}
],
"remote": [
{{
"type": "HTTP_OIDC_SUB"
}},
{{
"type": "HTTP_OIDC_USERNAME"
}},
{{
"type": "HTTP_OIDC_EMAIL"
}}
]
}}]
'''
REQUIRED_KEYS_MSG = 'required keys: oidc_client_id, oidc_provider_metadata_url'
# Default objects created by openidc-test-fixture charm
DEFAULT_CLIENT_ID = 'keystone'
DEFAULT_CLIENT_SECRET = 'ubuntu11'
DEFAULT_REALM = 'demorealm'
OPENIDC_TEST_FIXTURE = 'openidc-test-fixture' # app's name
# NOTE(freyes): workaround for bug http://pad.lv/1982948
def relate_keystone_openidc():
"""Add relation between keystone and keystone-openidc.
.. note: This is a workaround for the bug http://pad.lv/1982948
"""
cli_utils.setup_logging()
relations_added = False
if not zaza.model.get_relation_id(APP_NAME, 'keystone'):
logging.info('Adding relation keystone-openidc -> keystone')
zaza.model.add_relation(APP_NAME,
'keystone-fid-service-provider',
'keystone:keystone-fid-service-provider')
relations_added = True
if not zaza.model.get_relation_id(APP_NAME, 'openstack-dashboard'):
logging.info('Adding relation keystone-openidc -> openstack-dashboard')
zaza.model.add_relation(
APP_NAME,
'websso-fid-service-provider',
'openstack-dashboard:websso-fid-service-provider'
)
relations_added = True
if relations_added:
zaza.model.wait_for_agent_status()
# NOTE: the test bundle has been deployed with a non-related
# keystone-opendic subordinate application, and thus Zaza is expecting no
# unit from this application. We are now relating it to a principal
# keystone application with 3 units. We now need to make sure we wait for
# the units to get fully deployed before proceeding:
test_config = lifecycle_utils.get_charm_config(fatal=False)
target_deploy_status = test_config.get('target_deploy_status', {})
try:
# this is a HA deployment
target_deploy_status['keystone-openidc']['num-expected-units'] = 3
opts = {
'workload-status-message-prefix': REQUIRED_KEYS_MSG,
'workload-status': 'blocked',
}
target_deploy_status['keystone-openidc'].update(opts)
except KeyError:
# num-expected-units wasn't set to 0, no expectation to be
# fixed, let's move on.
pass
zaza.model.wait_for_application_states(
states=target_deploy_status)
def configure_keystone_openidc():
"""Configure OpenIDC testing fixture certificate."""
units = zaza.model.get_units(OPENIDC_TEST_FIXTURE)
assert len(units) > 0, 'openidc-test-fixture units not found'
ip = zaza.model.get_unit_public_address(units[0])
url = 'https://{ip}:8443/realms/{realm}/.well-known/openid-configuration'
cfg = {'oidc-client-id': DEFAULT_CLIENT_ID,
'oidc-client-secret': DEFAULT_CLIENT_SECRET,
'oidc-provider-metadata-url': url.format(ip=ip,
realm=DEFAULT_REALM)}
zaza.model.set_application_config(APP_NAME, cfg)
zaza.model.wait_for_agent_status()
test_config = lifecycle_utils.get_charm_config(fatal=False)
target_deploy_status = test_config.get('target_deploy_status', {})
target_deploy_status.update({
'keystone-openidc': {
'workload-status': 'active',
'workload-status-message': 'Unit is ready'
},
})
zaza.model.wait_for_application_states(states=target_deploy_status)
def keystone_federation_setup_site1():
"""Configure Keystone Federation for the local IdP #1."""
idp_unit = zaza.model.get_units("openidc-test-fixture")[0]
idp_remote_id = LOCAL_IDP_REMOTE_ID.format(
zaza.model.get_unit_public_address(idp_unit))
keystone_session = openstack_utils.get_overcloud_keystone_session()
keystone_client = openstack_utils.get_keystone_session_client(
keystone_session)
role = keystone_client.roles.find(name=MEMBER)
logging.info('Using role name %s with id %s', role.name, role.id)
keystone_federation_setup(
federated_domain=FEDERATED_DOMAIN,
federated_group=FEDERATED_GROUP,
idp_name=IDP,
idp_remote_id=idp_remote_id,
protocol_name=PROTOCOL_NAME,
map_template=MAP_TEMPLATE,
role_name=role.name,
)
+174
View File
@@ -0,0 +1,174 @@
# Copyright 2022 Canonical Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Keystone OpenID Connect Testing."""
import copy
import logging
import pprint
import zaza.model
from zaza.openstack.charm_tests.glance.setup import CIRROS_IMAGE_NAME
from zaza.openstack.charm_tests.keystone import BaseKeystoneTest
from zaza.openstack.charm_tests.neutron.setup import (
OVERCLOUD_NETWORK_CONFIG,
DEFAULT_UNDERCLOUD_NETWORK_CONFIG,
)
from zaza.openstack.charm_tests.nova.setup import manage_ssh_key
from zaza.openstack.charm_tests.openidc.setup import (
FEDERATED_DOMAIN,
IDP,
PROTOCOL_NAME,
)
from zaza.openstack.utilities import (
generic as generic_utils,
openstack as openstack_utils,
)
# static users created by openidc-test-fixture charm
OIDC_TEST_USER = 'johndoe'
OIDC_TEST_USER_PASSWORD = 'f00bar'
class BaseCharmKeystoneOpenIDC(BaseKeystoneTest):
"""Charm Keystone OpenID Connect tests."""
run_resource_cleanup = True
RESOURCE_PREFIX = 'zaza-openidc'
@classmethod
def setUpClass(cls):
"""Define openrc credentials for OIDC_TEST_USER."""
super().setUpClass()
charm_config = zaza.model.get_application_config('keystone-openidc')
client_id = charm_config['oidc-client-id']['value']
client_secret = charm_config['oidc-client-secret']['value']
metadata_url = charm_config['oidc-provider-metadata-url']['value']
cls.oidc_test_openrc = {
'API_VERSION': 3,
'OS_USERNAME': OIDC_TEST_USER,
'OS_PASSWORD': OIDC_TEST_USER_PASSWORD,
# using the first keystone ip by default, for environments with
# HA+TLS enabled this is the virtual IP, otherwise it will be one
# of the keystone units.
'OS_AUTH_URL': 'https://{}:5000/v3'.format(cls.keystone_ips[0]),
'OS_PROJECT_DOMAIN_NAME': FEDERATED_DOMAIN,
'OS_PROJECT_NAME': '{}_project'.format(OIDC_TEST_USER),
'OS_CACERT': openstack_utils.get_cacert(),
# openid specific info
'OS_AUTH_TYPE': 'v3oidcpassword',
'OS_DISCOVERY_ENDPOINT': metadata_url,
'OS_OPENID_SCOPE': 'openid email profile',
'OS_CLIENT_ID': client_id,
'OS_CLIENT_SECRET': client_secret,
'OS_IDENTITY_PROVIDER': IDP,
'OS_PROTOCOL': PROTOCOL_NAME,
}
logging.info('openrc: %s', pprint.pformat(cls.oidc_test_openrc))
class TestToken(BaseCharmKeystoneOpenIDC):
"""Test tokens for user's backed by OpenID Connect via Federation."""
def test_token_issue(self):
"""Test token issue with a federated user via openidc."""
openrc = copy.deepcopy(self.oidc_test_openrc)
with self.v3_keystone_preferred():
for ip in self.keystone_ips:
logging.info('keystone IP %s', ip)
openrc['AUTH_URL'] = 'https://{}:5000/v3'.format(ip)
keystone_session = openstack_utils.get_keystone_session(
openrc, scope='PROJECT')
logging.info('Retrieving token for federated user')
token = keystone_session.get_token()
logging.info('Token: %s', token)
self.assertIsNotNone(token)
logging.info('OK')
class TestLaunchInstance(BaseCharmKeystoneOpenIDC):
"""Test instance launching in a project defined by Federation mapping."""
@classmethod
def setUpClass(cls):
"""Configure user's project network backed by OpenID Connect."""
super().setUpClass()
# Get network configuration settings
network_config = {"private_net_cidr": "192.168.21.0/24"}
# Declared overcloud settings
network_config.update(OVERCLOUD_NETWORK_CONFIG)
# Default undercloud settings
network_config.update(DEFAULT_UNDERCLOUD_NETWORK_CONFIG)
# Environment specific settings
network_config.update(generic_utils.get_undercloud_env_vars())
ip_version = network_config.get("ip_version") or 4
keystone_session = openstack_utils.get_keystone_session(
cls.oidc_test_openrc, scope='PROJECT')
# find user's project id
project_id = keystone_session.get_project_id()
# Get authenticated clients
neutron_client = openstack_utils.get_neutron_session_client(
keystone_session)
nova_client = openstack_utils.get_nova_session_client(
keystone_session)
# create 'zaza' key in user's project
manage_ssh_key(nova_client)
# create a router attached to the external network
ext_net_name = network_config["external_net_name"]
networks = neutron_client.list_networks(name=ext_net_name)
ext_network = networks['networks'][0]
provider_router = openstack_utils.create_provider_router(
neutron_client, project_id)
openstack_utils.plug_extnet_into_router(
neutron_client,
provider_router,
ext_network)
# create project's private network
project_network = openstack_utils.create_project_network(
neutron_client,
project_id,
shared=False,
network_type=network_config["network_type"],
net_name=network_config["project_net_name"])
project_subnet = openstack_utils.create_project_subnet(
neutron_client,
project_id,
project_network,
network_config["private_net_cidr"],
ip_version=ip_version,
subnet_name=network_config["project_subnet_name"])
openstack_utils.update_subnet_dns(
neutron_client,
project_subnet,
network_config["external_dns"])
openstack_utils.plug_subnet_into_router(
neutron_client,
provider_router['name'],
project_network,
project_subnet)
openstack_utils.add_neutron_secgroup_rules(neutron_client, project_id)
def test_20_launch_instance(self):
"""Test launching an instance in a project defined by mapping rules."""
keystone_session = openstack_utils.get_keystone_session(
self.oidc_test_openrc, scope='PROJECT')
self.launch_guest('test-42',
instance_key=CIRROS_IMAGE_NAME,
keystone_session=keystone_session)
@@ -121,3 +121,6 @@ test_with_ipv6 = false
test_server_path = {{ workspace_path }}/test_server.bin
provider = amphora
{% endif %}
[dns]
nameservers = {{ test_name_server }}
@@ -122,6 +122,23 @@ class TempestTestWithKeystoneV3(TempestTestBase):
return super().run()
class TempestTestWithKeystoneMinimal(TempestTestBase):
"""Tempest test class to validate an OpenStack setup with Keystone V2."""
def run(self):
"""Run tempest tests as specified in tests/tests.yaml.
Allow test to run even if some components are missing (like
external network setup).
See TempestTestBase.run() for the available test options.
:returns: Status of tempest run
:rtype: bool
"""
tempest_utils.render_tempest_config_keystone_v3(minimal=True)
return super().run()
class TempestTest(TempestTestBase):
"""Tempest test class.
+71 -42
View File
@@ -21,6 +21,8 @@ import shutil
import subprocess
import urllib.parse
from neutronclient.common import exceptions as neutronexceptions
import zaza.model as model
import zaza.utilities.deployment_env as deployment_env
import zaza.openstack.utilities.juju as juju_utils
@@ -51,13 +53,18 @@ def render_tempest_config_keystone_v2():
_setup_tempest('tempest_v2.j2', 'accounts.j2')
def render_tempest_config_keystone_v3():
def render_tempest_config_keystone_v3(minimal=False):
"""Render tempest config for Keystone V3 API.
:param minimal: Run in minimal mode eg ignore missing setup
:type minimal: bool
:returns: None
:rtype: None
"""
_setup_tempest('tempest_v3.j2', 'accounts.j2')
_setup_tempest(
'tempest_v3.j2',
'accounts.j2',
minimal=minimal)
def get_workspace():
@@ -105,20 +112,22 @@ def _init_workspace(workspace_path):
pass
def _setup_tempest(tempest_template, accounts_template):
def _setup_tempest(tempest_template, accounts_template, minimal=False):
"""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
:param minimal: Run in minimal mode eg ignore missing setup
:type minimal: bool
: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)
context = _get_tempest_context(workspace_path, missing_fatal=not minimal)
_render_tempest_config(
os.path.join(workspace_path, 'etc/tempest.conf'),
context,
@@ -129,9 +138,13 @@ def _setup_tempest(tempest_template, accounts_template):
accounts_template)
def _get_tempest_context(workspace_path):
def _get_tempest_context(workspace_path, missing_fatal=True):
"""Generate the tempest config context.
:param workspace_path: path to workspace directory
:type workspace_path: str
:param missing_fatal: Raise an exception if a resource is missing
:type missing_fatal: bool
:returns: Context dictionary
:rtype: dict
"""
@@ -153,8 +166,14 @@ def _get_tempest_context(workspace_path):
_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'])
ctxt_func(
ctxt,
keystone_session,
missing_fatal=missing_fatal)
_add_environment_var_config(
ctxt,
ctxt['enabled_services'],
missing_fatal=missing_fatal)
_add_auth_config(ctxt)
if 'octavia' in ctxt['enabled_services']:
_add_octavia_config(ctxt)
@@ -194,13 +213,15 @@ def _add_application_ips(ctxt):
ctxt['ncc'] = juju_utils.get_application_ip('nova-cloud-controller')
def _add_nova_config(ctxt, keystone_session):
def _add_nova_config(ctxt, keystone_session, missing_fatal=True):
"""Add nova config to context.
:param ctxt: Context dictionary
:type ctxt: dict
:param keystone_session: keystoneauth1.session.Session object
:type: keystoneauth1.session.Session
:param missing_fatal: Raise an exception if a resource is missing
:type missing_fatal: bool
:returns: None
:rtype: None
"""
@@ -213,54 +234,52 @@ def _add_nova_config(ctxt, keystone_session):
ctxt['flavor_ref_alt'] = flavor.id
def _add_neutron_config(ctxt, keystone_session):
def _add_neutron_config(ctxt, keystone_session, missing_fatal=True):
"""Add neutron config to context.
:param ctxt: Context dictionary
:type ctxt: dict
:param keystone_session: keystoneauth1.session.Session object
:type: keystoneauth1.session.Session
:param missing_fatal: Raise an exception if a resource is missing
:type missing_fatal: bool
: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'
try:
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']
except neutronexceptions.NotFound:
if missing_fatal:
raise
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
def _add_glance_config(ctxt, keystone_session):
def _add_glance_config(ctxt, keystone_session, missing_fatal=True):
"""Add glance config to context.
:param ctxt: Context dictionary
:type ctxt: dict
:param keystone_session: keystoneauth1.session.Session object
:type: keystoneauth1.session.Session
:param missing_fatal: Raise an exception if a resource is missing
:type missing_fatal: bool
:returns: None
:rtype: None
"""
@@ -276,13 +295,15 @@ def _add_glance_config(ctxt, keystone_session):
ctxt['image_alt_id'] = image_alt[0].id
def _add_cinder_config(ctxt, keystone_session):
def _add_cinder_config(ctxt, keystone_session, missing_fatal=True):
"""Add cinder config to context.
:param ctxt: Context dictionary
:type ctxt: dict
:param keystone_session: keystoneauth1.session.Session object
:type: keystoneauth1.session.Session
:param missing_fatal: Raise an exception if a resource is missing
:type missing_fatal: bool
:returns: None
:rtype: None
"""
@@ -297,13 +318,15 @@ def _add_cinder_config(ctxt, keystone_session):
break
def _add_keystone_config(ctxt, keystone_session):
def _add_keystone_config(ctxt, keystone_session, missing_fatal=True):
"""Add keystone config to context.
:param ctxt: Context dictionary
:type ctxt: dict
:param keystone_session: keystoneauth1.session.Session object
:type: keystoneauth1.session.Session
:param missing_fatal: Raise an exception if a resource is missing
:type missing_fatal: bool
:returns: None
:rtype: None
"""
@@ -313,11 +336,13 @@ def _add_keystone_config(ctxt, keystone_session):
ctxt['default_domain_id'] = domain.id
def _add_octavia_config(ctxt):
def _add_octavia_config(ctxt, missing_fatal=True):
"""Add octavia config to context.
:param ctxt: Context dictionary
:type ctxt: dict
:param missing_fatal: Raise an exception if a resource is missing
:type missing_fatal: bool
:returns: None
:rtype: None
:raises: subprocess.CalledProcessError
@@ -334,11 +359,15 @@ def _add_octavia_config(ctxt):
])
def _add_environment_var_config(ctxt, services):
def _add_environment_var_config(ctxt, services, missing_fatal=True):
"""Add environment variable config to context.
:param ctxt: Context dictionary
:type ctxt: dict
:param services: List of services
:type services: List[str]
:param missing_fatal: Raise an exception if a resource is missing
:type missing_fatal: bool
:returns: None
:rtype: None
"""
@@ -353,7 +382,7 @@ def _add_environment_var_config(ctxt, services):
else:
if var not in IGNORABLE_VARS:
missing_vars.append(var)
if missing_vars:
if missing_vars and missing_fatal:
raise ValueError(
("Environment variables [{}] must all be set to run this"
" test").format(', '.join(missing_vars)))
+6 -2
View File
@@ -752,7 +752,8 @@ class OpenStackBaseTest(BaseCharmTest):
def launch_guest(self, guest_name, userdata=None, use_boot_volume=False,
instance_key=None, flavor_name=None,
attach_to_external_network=False):
attach_to_external_network=False,
keystone_session=None):
"""Launch one guest to use in tests.
Note that it is up to the caller to have set the RESOURCE_PREFIX class
@@ -772,6 +773,8 @@ class OpenStackBaseTest(BaseCharmTest):
:param attach_to_external_network: Attach instance directly to external
network.
:type attach_to_external_network: bool
:param keystone_session: Keystone session to use.
:type keystone_session: Optional[keystoneauth1.session.Session]
:returns: Nova instance objects
:rtype: Server
"""
@@ -801,7 +804,8 @@ class OpenStackBaseTest(BaseCharmTest):
use_boot_volume=use_boot_volume,
userdata=userdata,
flavor_name=flavor_name,
attach_to_external_network=attach_to_external_network)
attach_to_external_network=attach_to_external_network,
keystone_session=keystone_session)
def launch_guests(self, userdata=None, attach_to_external_network=False,
flavor_name=None):
+7 -2
View File
@@ -94,7 +94,8 @@ def launch_instance_retryer(instance_key, **kwargs):
def launch_instance(instance_key, use_boot_volume=False, vm_name=None,
private_network_name=None, image_name=None,
flavor_name=None, external_network_name=None, meta=None,
userdata=None, attach_to_external_network=False):
userdata=None, attach_to_external_network=False,
keystone_session=None):
"""Launch an instance.
:param instance_key: Key to collect associated config data with.
@@ -120,10 +121,14 @@ def launch_instance(instance_key, use_boot_volume=False, vm_name=None,
:param attach_to_external_network: Attach instance directly to external
network.
:type attach_to_external_network: bool
:param keystone_session: Keystone session to use.
:type keystone_session: Optional[keystoneauth1.session.Session]
:returns: the created instance
:rtype: novaclient.Server
"""
keystone_session = openstack_utils.get_overcloud_keystone_session()
if not keystone_session:
keystone_session = openstack_utils.get_overcloud_keystone_session()
nova_client = openstack_utils.get_nova_session_client(keystone_session)
neutron_client = openstack_utils.get_neutron_session_client(
keystone_session)
+5
View File
@@ -21,6 +21,7 @@ and recovery.
import logging
import openstack.exceptions as ostack_except
import tenacity
import urllib3
import zaza.model
import zaza.openstack.utilities.openstack as openstack_utils
@@ -57,6 +58,10 @@ HOST_ASSIGNMENT_METHODS = {
}
@tenacity.retry(
wait=tenacity.wait_exponential(multiplier=2, max=60),
reraise=True, stop=tenacity.stop_after_attempt(10),
retry=tenacity.retry_if_exception_type(urllib3.connection.HTTPSConnection))
def create_segments(segment_number=1, host_assignment_method=None):
"""Create a masakari segment and populate it with hypervisors.
+76 -11
View File
@@ -299,10 +299,26 @@ def get_ks_creds(cloud_creds, scope='PROJECT'):
'username': cloud_creds['OS_USERNAME'],
'password': cloud_creds['OS_PASSWORD'],
'auth_url': cloud_creds['OS_AUTH_URL'],
'user_domain_name': cloud_creds['OS_USER_DOMAIN_NAME'],
'project_domain_name': cloud_creds['OS_PROJECT_DOMAIN_NAME'],
'project_name': cloud_creds['OS_PROJECT_NAME'],
}
# the FederationBaseAuth class doesn't support the
# 'user_domain_name' argument, so only setting it in the 'auth'
# dict when it's passed in the cloud_creds.
if cloud_creds.get('OS_USER_DOMAIN_NAME'):
auth['user_domain_name'] = cloud_creds['OS_USER_DOMAIN_NAME']
if cloud_creds.get('OS_AUTH_TYPE') == 'v3oidcpassword':
auth.update({
'identity_provider': cloud_creds['OS_IDENTITY_PROVIDER'],
'protocol': cloud_creds['OS_PROTOCOL'],
'client_id': cloud_creds['OS_CLIENT_ID'],
'client_secret': cloud_creds['OS_CLIENT_SECRET'],
# optional configuration options:
'access_token_endpoint': cloud_creds.get(
'OS_ACCESS_TOKEN_ENDPOINT'),
'discovery_endpoint': cloud_creds.get('OS_DISCOVERY_ENDPOINT')
})
return auth
@@ -487,15 +503,7 @@ def get_keystone_scope(model_name=None):
:returns: String keystone scope
:rtype: string
"""
os_version = get_current_os_versions("keystone",
model_name=model_name)["keystone"]
# Keystone policy.json shipped the charm with liberty requires a domain
# scoped token. Bug #1649106
if os_version == "liberty":
scope = "DOMAIN"
else:
scope = "PROJECT"
return scope
return "PROJECT"
def get_keystone_session(openrc_creds, scope='PROJECT', verify=None):
@@ -519,7 +527,10 @@ def get_keystone_session(openrc_creds, scope='PROJECT', verify=None):
if openrc_creds.get('API_VERSION', 2) == 2:
auth = v2.Password(**keystone_creds)
else:
auth = v3.Password(**keystone_creds)
if openrc_creds.get('OS_AUTH_TYPE') == 'v3oidcpassword':
auth = v3.OidcPassword(**keystone_creds)
else:
auth = v3.Password(**keystone_creds)
return session.Session(auth=auth, verify=verify)
@@ -2102,6 +2113,60 @@ def get_keystone_api_version(model_name=None):
def get_overcloud_auth(address=None, model_name=None):
"""Get overcloud OpenStack authentication from the environment.
:param model_name: Name of model to query.
:type model_name: str
:returns: Dictionary of authentication settings
:rtype: dict
"""
if juju_utils.is_k8s_deployment():
return _get_overcloud_auth_k8s(address=address, model_name=None)
else:
return _get_overcloud_auth(address=address, model_name=None)
def _get_overcloud_auth_k8s(address=None, model_name=None):
"""Get overcloud OpenStack authentication from the k8s environment.
:param model_name: Name of model to query.
:type model_name: str
:returns: Dictionary of authentication settings
:rtype: dict
"""
logging.warning('Assuming http keystone endpoint')
transport = 'http'
port = 5000
if not address:
address = zaza.model.get_status()[
'applications']['keystone'].public_address
address = network_utils.format_addr(address)
logging.info('Retrieving admin password from keystone')
action = zaza.model.run_action_on_leader(
'keystone',
'get-admin-password',
action_params={}
)
password = action.data['results']['password']
# V3 or later
logging.info('Using keystone API V3 (or later) for overcloud auth')
auth_settings = {
'OS_AUTH_URL': '%s://%s:%i/v3' % (transport, address, port),
'OS_USERNAME': 'admin',
'OS_PASSWORD': password,
'OS_REGION_NAME': 'RegionOne',
'OS_DOMAIN_NAME': 'admin_domain',
'OS_USER_DOMAIN_NAME': 'admin_domain',
'OS_PROJECT_NAME': 'admin',
'OS_PROJECT_DOMAIN_NAME': 'admin_domain',
'API_VERSION': 3,
}
return auth_settings
def _get_overcloud_auth(address=None, model_name=None):
"""Get overcloud OpenStack authentication from the environment.
:param model_name: Name of model to query.
:type model_name: str
:returns: Dictionary of authentication settings