v1auth: support endpoint_data_for() api

...so we can be used with openstacksdk.

Also, add a few functests that use openstacksdk.

Change-Id: Ie6987f5de48914ec8932254cde79a973a0264877
This commit is contained in:
Tim Burke 2019-10-09 16:59:23 -07:00
parent 1eda8f9f3e
commit c4bef14fc1
6 changed files with 221 additions and 63 deletions

View File

@ -21,6 +21,7 @@ mccabe==0.2.1
mock==1.2.0
netaddr==0.7.10
openstackdocstheme==1.20.0
openstacksdk==0.11.0
oslo.config==1.2.0
pbr==2.0.0
pep8==1.5.7

View File

@ -45,6 +45,7 @@ from six.moves.urllib.parse import urljoin
# Note that while we import keystoneauth1 here, we *don't* need to add it to
# requirements.txt -- this entire module only makes sense (and should only be
# loaded) if keystoneauth is already installed.
from keystoneauth1 import discover
from keystoneauth1 import plugin
from keystoneauth1 import exceptions
from keystoneauth1 import loading
@ -110,11 +111,20 @@ class ServiceCatalogV1(object):
]
def url_for(self, **kwargs):
return self.endpoint_data_for(**kwargs).url
def endpoint_data_for(self, **kwargs):
kwargs.setdefault('interface', 'public')
kwargs.setdefault('service_type', None)
if kwargs['service_type'] == 'object-store':
return self.storage_url
return discover.EndpointData(
service_type='object-store',
service_name='swift',
interface=kwargs['interface'],
region_name='default',
catalog_url=self.storage_url,
)
# Although our "catalog" includes an identity entry, nothing that uses
# url_for() (including `openstack endpoint list`) will know what to do

View File

@ -4,3 +4,4 @@ coverage!=4.4,>=4.0 # Apache-2.0
keystoneauth1>=3.4.0 # Apache-2.0
mock>=1.2.0 # BSD
stestr>=2.0.0 # Apache-2.0
openstacksdk>=0.11.0 # Apache-2.0

View File

@ -0,0 +1,93 @@
# Copyright (c) 2014 Christian Schwede <christian.schwede@enovance.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
from six.moves import configparser
TEST_CONFIG = None
def _load_config(force_reload=False):
global TEST_CONFIG
if not force_reload and TEST_CONFIG is not None:
return TEST_CONFIG
config_file = os.environ.get('SWIFT_TEST_CONFIG_FILE',
'/etc/swift/test.conf')
parser = configparser.ConfigParser({'auth_version': '1'})
parser.read(config_file)
conf = {}
if parser.has_section('func_test'):
if parser.has_option('func_test', 'auth_uri'):
conf['auth_url'] = parser.get('func_test', 'auth_uri')
try:
conf['auth_version'] = parser.get('func_test', 'auth_version')
except configparser.NoOptionError:
last_piece = conf['auth_url'].rstrip('/').rsplit('/', 1)[1]
if last_piece.endswith('.0'):
last_piece = last_piece[:-2]
if last_piece in ('1', '2', '3'):
conf['auth_version'] = last_piece
else:
raise
else:
auth_host = parser.get('func_test', 'auth_host')
auth_port = parser.getint('func_test', 'auth_port')
auth_ssl = parser.getboolean('func_test', 'auth_ssl')
auth_prefix = parser.get('func_test', 'auth_prefix')
conf['auth_version'] = parser.get('func_test', 'auth_version')
if auth_ssl:
auth_url = "https://"
else:
auth_url = "http://"
auth_url += "%s:%s%s" % (auth_host, auth_port, auth_prefix)
if conf['auth_version'] == "1":
auth_url += 'v1.0'
conf['auth_url'] = auth_url
try:
conf['account_username'] = parser.get('func_test',
'account_username')
except configparser.NoOptionError:
conf['account'] = parser.get('func_test', 'account')
conf['username'] = parser.get('func_test', 'username')
conf['account_username'] = "%s:%s" % (conf['account'],
conf['username'])
else:
# Still try to get separate account/usernames for keystone tests
try:
conf['account'] = parser.get('func_test', 'account')
conf['username'] = parser.get('func_test', 'username')
except configparser.NoOptionError:
pass
conf['password'] = parser.get('func_test', 'password')
# For keystone v3
try:
conf['account4'] = parser.get('func_test', 'account4')
conf['username4'] = parser.get('func_test', 'username4')
conf['domain4'] = parser.get('func_test', 'domain4')
conf['password4'] = parser.get('func_test', 'password4')
except configparser.NoOptionError:
pass
TEST_CONFIG = conf
try:
_load_config()
except configparser.NoOptionError:
TEST_CONFIG = None # sentinel used in test setup

View File

@ -0,0 +1,92 @@
# Copyright (c) 2019 Tim Burke <tim@swiftstack.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import unittest
import uuid
import openstack
from . import TEST_CONFIG
PREFIX = 'test-swiftclient-'
class TestOpenStackSDK(unittest.TestCase):
@classmethod
def setUpClass(cls):
# NB: Only runs for v1 auth, to exercise our keystoneauth plugin
cls.skip_tests = (TEST_CONFIG is None or
TEST_CONFIG['auth_version'] != '1')
if not cls.skip_tests:
cls.conn = openstack.connect(
auth_type='v1password',
auth_url=TEST_CONFIG['auth_url'],
username=TEST_CONFIG['account_username'],
password=TEST_CONFIG['password'],
)
cls.object_store = cls.conn.object_store
def setUp(self):
if self.skip_tests:
raise unittest.SkipTest('SKIPPING V1-AUTH TESTS')
def tearDown(self):
if self.skip_tests:
return
for c in self.object_store.containers():
if c.name.startswith(PREFIX):
for o in self.object_store.objects(c.name):
self.object_store.delete_object(
o.name, container=c.name)
self.object_store.delete_container(c.name)
def test_containers(self):
meta = self.object_store.get_account_metadata()
count_before = meta.account_container_count
containers = sorted(PREFIX + str(uuid.uuid4())
for _ in range(10))
for c in containers:
self.object_store.create_container(c)
self.assertEqual([
c.name for c in self.object_store.containers()
if c.name.startswith(PREFIX)
], containers)
meta = self.object_store.get_account_metadata()
self.assertEqual(count_before + len(containers),
meta.account_container_count)
def test_objects(self):
container = PREFIX + str(uuid.uuid4())
self.object_store.create_container(container)
objects = sorted(str(uuid.uuid4()) for _ in range(10))
for o in objects:
self.object_store.create_object(container, o, data=b'x')
self.assertEqual([
o.name for o in self.object_store.objects(container)
], objects)
meta = self.object_store.get_container_metadata(container)
self.assertEqual(len(objects), meta.object_count)
def test_object_metadata(self):
container = PREFIX + str(uuid.uuid4())
self.object_store.create_container(container)
obj = str(uuid.uuid4())
obj_meta = {str(uuid.uuid4()): str(uuid.uuid4()) for _ in range(10)}
# NB: as of 0.36.0, create_object() doesn't play well with passing
# both data and metadata, so we do a PUT then POST
self.object_store.create_object(container, obj, data=b'x')
self.object_store.set_object_metadata(obj, container, **obj_meta)
meta = self.object_store.get_object_metadata(obj, container)
self.assertEqual(obj_meta, meta.metadata)

View File

@ -13,23 +13,23 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import unittest
import time
from io import BytesIO
import six
from six.moves import configparser
import swiftclient
from . import TEST_CONFIG
class TestFunctional(unittest.TestCase):
def __init__(self, *args, **kwargs):
super(TestFunctional, self).__init__(*args, **kwargs)
self.skip_tests = False
self._get_config()
self.skip_tests = (TEST_CONFIG is None)
if not self.skip_tests:
self._get_config()
self.test_data = b'42' * 10
self.etag = '2704306ec982238d85d4b235c925d58e'
@ -41,50 +41,10 @@ class TestFunctional(unittest.TestCase):
self.objectname_2 = self.objectname + '_second'
def _get_config(self):
config_file = os.environ.get('SWIFT_TEST_CONFIG_FILE',
'/etc/swift/test.conf')
config = configparser.ConfigParser({'auth_version': '1'})
config.read(config_file)
self.config = config
if config.has_section('func_test'):
if config.has_option('func_test', 'auth_uri'):
self.auth_url = config.get('func_test', 'auth_uri')
try:
self.auth_version = config.get('func_test', 'auth_version')
except configparser.NoOptionError:
last_piece = self.auth_url.rstrip('/').rsplit('/', 1)[1]
if last_piece.endswith('.0'):
last_piece = last_piece[:-2]
if last_piece in ('1', '2', '3'):
self.auth_version = last_piece
else:
raise
else:
auth_host = config.get('func_test', 'auth_host')
auth_port = config.getint('func_test', 'auth_port')
auth_ssl = config.getboolean('func_test', 'auth_ssl')
auth_prefix = config.get('func_test', 'auth_prefix')
self.auth_version = config.get('func_test', 'auth_version')
self.auth_url = ""
if auth_ssl:
self.auth_url += "https://"
else:
self.auth_url += "http://"
self.auth_url += "%s:%s%s" % (
auth_host, auth_port, auth_prefix)
if self.auth_version == "1":
self.auth_url += 'v1.0'
try:
self.account_username = config.get('func_test',
'account_username')
except configparser.NoOptionError:
account = config.get('func_test', 'account')
username = config.get('func_test', 'username')
self.account_username = "%s:%s" % (account, username)
self.password = config.get('func_test', 'password')
else:
self.skip_tests = True
self.auth_url = TEST_CONFIG['auth_url']
self.auth_version = TEST_CONFIG['auth_version']
self.account_username = TEST_CONFIG['account_username']
self.password = TEST_CONFIG['password']
def _get_connection(self):
"""
@ -514,20 +474,20 @@ class TestUsingKeystone(TestFunctional):
"""
def _get_connection(self):
account = username = password = None
account = username = None
if self.auth_version not in ('2', '3'):
self.skipTest('SKIPPING KEYSTONE-SPECIFIC FUNCTIONAL TESTS')
try:
account = self.config.get('func_test', 'account')
username = self.config.get('func_test', 'username')
password = self.config.get('func_test', 'password')
except Exception:
account = TEST_CONFIG['account']
username = TEST_CONFIG['username']
except KeyError:
self.skipTest('SKIPPING KEYSTONE-SPECIFIC FUNCTIONAL TESTS' +
' - NO CONFIG')
os_options = {'tenant_name': account}
return swiftclient.Connection(
self.auth_url, username, password, auth_version=self.auth_version,
os_options=os_options)
self.auth_url, username, self.password,
auth_version=self.auth_version,
os_options={'tenant_name': account})
class TestUsingKeystoneV3(TestFunctional):
@ -539,13 +499,14 @@ class TestUsingKeystoneV3(TestFunctional):
account = username = password = project_domain = user_domain = None
if self.auth_version != '3':
self.skipTest('SKIPPING KEYSTONE-V3-SPECIFIC FUNCTIONAL TESTS')
try:
account = self.config.get('func_test', 'account4')
username = self.config.get('func_test', 'username4')
user_domain = self.config.get('func_test', 'domain4')
project_domain = self.config.get('func_test', 'domain4')
password = self.config.get('func_test', 'password4')
except Exception:
account = TEST_CONFIG['account4']
username = TEST_CONFIG['username4']
user_domain = TEST_CONFIG['domain4']
project_domain = TEST_CONFIG['domain4']
password = TEST_CONFIG['password4']
except KeyError:
self.skipTest('SKIPPING KEYSTONE-V3-SPECIFIC FUNCTIONAL TESTS' +
' - NO CONFIG')