Use openstacksdk for accessing Swift

This change replaces the swiftclient dependency with openstacksdk.
The ultimate goal is to use only openstacksdk for all cross-service
interactions.

The configuration option max_retires has been broken for several
releases since commit 7e8cdc0d0f,
this change deprecates it.

Change-Id: I635519f88e90f4267ff7a9cb063a697ff39e4710
This commit is contained in:
Dmitry Tantsur 2019-06-18 09:01:47 +02:00
parent 86cd1a6aee
commit 85a3186d50
7 changed files with 77 additions and 117 deletions

View File

@ -15,9 +15,9 @@
import json import json
import openstack
from openstack import exceptions as os_exc
from oslo_config import cfg from oslo_config import cfg
from swiftclient import client as swift_client
from swiftclient import exceptions as swift_exceptions
from ironic_inspector.common.i18n import _ from ironic_inspector.common.i18n import _
from ironic_inspector.common import keystone from ironic_inspector.common import keystone
@ -53,19 +53,9 @@ class SwiftAPI(object):
if not SWIFT_SESSION: if not SWIFT_SESSION:
SWIFT_SESSION = keystone.get_session('swift') SWIFT_SESSION = keystone.get_session('swift')
adapter = keystone.get_adapter('swift', session=SWIFT_SESSION) self.connection = openstack.connection.Connection(
except Exception as exc: session=SWIFT_SESSION,
raise utils.Error(_("Could not create an adapter to connect to " oslo_conf=CONF).object_store
"the object storage service: %s") % exc)
# TODO(pas-ha) reverse-construct SSL-related session options here
params = {
'os_options': {
'object_storage_url': adapter.get_endpoint()}}
try:
self.connection = swift_client.Connection(session=SWIFT_SESSION,
**params)
except Exception as exc: except Exception as exc:
raise utils.Error(_("Could not connect to the object storage " raise utils.Error(_("Could not connect to the object storage "
"service: %s") % exc) "service: %s") % exc)
@ -82,8 +72,8 @@ class SwiftAPI(object):
:raises: utils.Error, if any operation with Swift fails. :raises: utils.Error, if any operation with Swift fails.
""" """
try: try:
self.connection.put_container(container) self.connection.create_container(container)
except swift_exceptions.ClientException as e: except os_exc.SDKException as e:
err_msg = (_('Swift failed to create container %(container)s. ' err_msg = (_('Swift failed to create container %(container)s. '
'Error was: %(error)s') % 'Error was: %(error)s') %
{'container': container, 'error': e}) {'container': container, 'error': e})
@ -94,11 +84,9 @@ class SwiftAPI(object):
headers['X-Delete-After'] = CONF.swift.delete_after headers['X-Delete-After'] = CONF.swift.delete_after
try: try:
obj_uuid = self.connection.put_object(container, obj_uuid = self.connection.create_object(
object, container, object, data=data, headers=headers)
data, except os_exc.SDKException as e:
headers=headers)
except swift_exceptions.ClientException as e:
err_msg = (_('Swift failed to create object %(object)s in ' err_msg = (_('Swift failed to create object %(object)s in '
'container %(container)s. Error was: %(error)s') % 'container %(container)s. Error was: %(error)s') %
{'object': object, 'container': container, 'error': e}) {'object': object, 'container': container, 'error': e})
@ -115,8 +103,8 @@ class SwiftAPI(object):
:raises: utils.Error, if the Swift operation fails. :raises: utils.Error, if the Swift operation fails.
""" """
try: try:
headers, obj = self.connection.get_object(container, object) obj = self.connection.download_object(object, container=container)
except swift_exceptions.ClientException as e: except os_exc.SDKException as e:
err_msg = (_('Swift failed to get object %(object)s in ' err_msg = (_('Swift failed to get object %(object)s in '
'container %(container)s. Error was: %(error)s') % 'container %(container)s. Error was: %(error)s') %
{'object': object, 'container': container, 'error': e}) {'object': object, 'container': container, 'error': e})

View File

@ -31,7 +31,6 @@ def set_config_defaults():
'requests=WARNING', 'requests=WARNING',
'urllib3.connectionpool=WARNING', 'urllib3.connectionpool=WARNING',
'keystonemiddleware=WARNING', 'keystonemiddleware=WARNING',
'swiftclient=WARNING',
'keystoneauth=WARNING', 'keystoneauth=WARNING',
'ironicclient=WARNING']) 'ironicclient=WARNING'])
set_cors_middleware_defaults() set_cors_middleware_defaults()

View File

@ -23,9 +23,8 @@ SERVICE_TYPE = 'object-store'
_OPTS = [ _OPTS = [
cfg.IntOpt('max_retries', cfg.IntOpt('max_retries',
default=2, help=_('This option is deprecated and has no effect.'),
help=_('Maximum number of times to retry a Swift request, ' deprecated_for_removal=True),
'before failing.')),
cfg.IntOpt('delete_after', cfg.IntOpt('delete_after',
default=0, default=0,
help=_('Number of seconds that the Swift object will last ' help=_('Number of seconds that the Swift object will last '

View File

@ -15,10 +15,9 @@
# Mostly copied from ironic/tests/test_swift.py # Mostly copied from ironic/tests/test_swift.py
from keystoneauth1 import exceptions as ks_exc from keystoneauth1 import exceptions as ks_exc
from keystoneauth1 import loading as kloading
import mock import mock
from swiftclient import client as swift_client import openstack
from swiftclient import exceptions as swift_exception from openstack import exceptions as os_exc
from ironic_inspector.common import keystone from ironic_inspector.common import keystone
from ironic_inspector.common import swift from ironic_inspector.common import swift
@ -26,128 +25,99 @@ from ironic_inspector.test import base as test_base
from ironic_inspector import utils from ironic_inspector import utils
class BaseTest(test_base.NodeTest): @mock.patch.object(keystone, 'get_session', autospec=True)
def setUp(self): @mock.patch.object(openstack.connection, 'Connection', autospec=True)
super(BaseTest, self).setUp() class SwiftTestCase(test_base.NodeTest):
self.all_macs = self.macs + ['DE:AD:BE:EF:DE:AD']
self.pxe_mac = self.macs[1]
self.data = {
'ipmi_address': self.bmc_address,
'cpus': 2,
'cpu_arch': 'x86_64',
'memory_mb': 1024,
'local_gb': 20,
'interfaces': {
'em1': {'mac': self.macs[0], 'ip': '1.2.0.1'},
'em2': {'mac': self.macs[1], 'ip': '1.2.0.2'},
'em3': {'mac': self.all_macs[2]},
},
'boot_interface': '01-' + self.pxe_mac.replace(':', '-'),
}
@mock.patch.object(keystone, 'get_adapter', autospec=True)
@mock.patch.object(keystone, 'register_auth_opts')
@mock.patch.object(keystone, 'get_session')
@mock.patch.object(swift_client, 'Connection', autospec=True)
class SwiftTestCase(BaseTest):
def setUp(self): def setUp(self):
super(SwiftTestCase, self).setUp() super(SwiftTestCase, self).setUp()
swift.reset_swift_session() swift.reset_swift_session()
self.swift_exception = swift_exception.ClientException('', '')
self.cfg.config(group='swift', max_retries=2)
# NOTE(aarefiev) register keystoneauth dynamic options
adapter_opts = kloading.get_adapter_conf_options(
include_deprecated=False)
self.cfg.register_opts(adapter_opts, 'swift')
self.addCleanup(swift.reset_swift_session) self.addCleanup(swift.reset_swift_session)
def test___init__(self, connection_mock, load_mock, def test___init__(self, connection_mock, load_mock):
opts_mock, adapter_mock):
fake_endpoint = "http://localhost:6000"
adapter_mock.return_value.get_endpoint.return_value = fake_endpoint
swift.SwiftAPI() swift.SwiftAPI()
connection_mock.assert_called_once_with( connection_mock.assert_called_once_with(
session=load_mock.return_value, session=load_mock.return_value,
os_options={'object_storage_url': fake_endpoint}) oslo_conf=swift.CONF)
def test___init__keystone_failure(self, connection_mock, load_mock, def test___init__keystone_failure(self, connection_mock, load_mock):
opts_mock, adapter_mock): load_mock.side_effect = ks_exc.MissingRequiredOptions([])
adapter_mock.side_effect = ks_exc.MissingRequiredOptions([]) self.assertRaisesRegex(utils.Error, 'Could not connect',
self.assertRaisesRegex(utils.Error, 'Could not create an adapter',
swift.SwiftAPI) swift.SwiftAPI)
self.assertFalse(connection_mock.called) self.assertFalse(connection_mock.called)
def test___init__swift_failure(self, connection_mock, load_mock, def test___init__sdk_failure(self, connection_mock, load_mock):
opts_mock, adapter_mock):
fake_endpoint = "http://localhost:6000"
adapter_mock.return_value.get_endpoint.return_value = fake_endpoint
connection_mock.side_effect = RuntimeError() connection_mock.side_effect = RuntimeError()
self.assertRaisesRegex(utils.Error, 'Could not connect', self.assertRaisesRegex(utils.Error, 'Could not connect',
swift.SwiftAPI) swift.SwiftAPI)
connection_mock.assert_called_once_with( connection_mock.assert_called_once_with(
session=load_mock.return_value, session=load_mock.return_value,
os_options={'object_storage_url': fake_endpoint}) oslo_conf=swift.CONF)
def test_create_object(self, connection_mock, load_mock, def test_create_object(self, connection_mock, load_mock):
opts_mock, adapter_mock):
swiftapi = swift.SwiftAPI() swiftapi = swift.SwiftAPI()
connection_obj_mock = connection_mock.return_value swift_mock = connection_mock.return_value.object_store
swift_mock.create_object.return_value = 'object-uuid'
connection_obj_mock.put_object.return_value = 'object-uuid'
object_uuid = swiftapi.create_object('object', 'some-string-data') object_uuid = swiftapi.create_object('object', 'some-string-data')
connection_obj_mock.put_container.assert_called_once_with('ironic-' swift_mock.create_container.assert_called_once_with('ironic-inspector')
'inspector') swift_mock.create_object.assert_called_once_with(
connection_obj_mock.put_object.assert_called_once_with( 'ironic-inspector', 'object',
'ironic-inspector', 'object', 'some-string-data', headers=None) data='some-string-data', headers=None)
self.assertEqual('object-uuid', object_uuid)
def test_create_object_with_delete_after(self, connection_mock, load_mock):
swift.CONF.set_override('delete_after', 60, group='swift')
swiftapi = swift.SwiftAPI()
swift_mock = connection_mock.return_value.object_store
swift_mock.create_object.return_value = 'object-uuid'
object_uuid = swiftapi.create_object('object', 'some-string-data')
swift_mock.create_container.assert_called_once_with('ironic-inspector')
swift_mock.create_object.assert_called_once_with(
'ironic-inspector', 'object',
data='some-string-data', headers={'X-Delete-After': 60})
self.assertEqual('object-uuid', object_uuid) self.assertEqual('object-uuid', object_uuid)
def test_create_object_create_container_fails( def test_create_object_create_container_fails(
self, connection_mock, load_mock, opts_mock, adapter_mock): self, connection_mock, load_mock):
swiftapi = swift.SwiftAPI() swiftapi = swift.SwiftAPI()
connection_obj_mock = connection_mock.return_value swift_mock = connection_mock.return_value.object_store
connection_obj_mock.put_container.side_effect = self.swift_exception swift_mock.create_container.side_effect = os_exc.SDKException
self.assertRaises(utils.Error, swiftapi.create_object, 'object', self.assertRaises(utils.Error, swiftapi.create_object, 'object',
'some-string-data') 'some-string-data')
connection_obj_mock.put_container.assert_called_once_with('ironic-' swift_mock.create_container.assert_called_once_with('ironic-inspector')
'inspector') self.assertFalse(swift_mock.create_object.called)
self.assertFalse(connection_obj_mock.put_object.called)
def test_create_object_put_object_fails(self, connection_mock, load_mock, def test_create_object_put_object_fails(self, connection_mock, load_mock):
opts_mock, adapter_mock):
swiftapi = swift.SwiftAPI() swiftapi = swift.SwiftAPI()
connection_obj_mock = connection_mock.return_value swift_mock = connection_mock.return_value.object_store
connection_obj_mock.put_object.side_effect = self.swift_exception swift_mock.create_object.side_effect = os_exc.SDKException
self.assertRaises(utils.Error, swiftapi.create_object, 'object', self.assertRaises(utils.Error, swiftapi.create_object, 'object',
'some-string-data') 'some-string-data')
connection_obj_mock.put_container.assert_called_once_with('ironic-' swift_mock.create_container.assert_called_once_with('ironic-inspector')
'inspector') swift_mock.create_object.assert_called_once_with(
connection_obj_mock.put_object.assert_called_once_with( 'ironic-inspector', 'object',
'ironic-inspector', 'object', 'some-string-data', headers=None) data='some-string-data', headers=None)
def test_get_object(self, connection_mock, load_mock, def test_get_object(self, connection_mock, load_mock):
opts_mock, adapter_mock):
swiftapi = swift.SwiftAPI() swiftapi = swift.SwiftAPI()
connection_obj_mock = connection_mock.return_value swift_mock = connection_mock.return_value.object_store
expected_obj = self.data
connection_obj_mock.get_object.return_value = ('headers', expected_obj)
swift_obj = swiftapi.get_object('object') swift_obj = swiftapi.get_object('object')
connection_obj_mock.get_object.assert_called_once_with( swift_mock.download_object.assert_called_once_with(
'ironic-inspector', 'object') 'object', container='ironic-inspector')
self.assertEqual(expected_obj, swift_obj) self.assertIs(swift_mock.download_object.return_value, swift_obj)
def test_get_object_fails(self, connection_mock, load_mock, def test_get_object_fails(self, connection_mock, load_mock):
opts_mock, adapter_mock):
swiftapi = swift.SwiftAPI() swiftapi = swift.SwiftAPI()
connection_obj_mock = connection_mock.return_value swift_mock = connection_mock.return_value.object_store
connection_obj_mock.get_object.side_effect = self.swift_exception swift_mock.download_object.side_effect = os_exc.SDKException
self.assertRaises(utils.Error, swiftapi.get_object, self.assertRaises(utils.Error, swiftapi.get_object,
'object') 'object')
connection_obj_mock.get_object.assert_called_once_with( swift_mock.download_object.assert_called_once_with(
'ironic-inspector', 'object') 'object', container='ironic-inspector')

View File

@ -56,7 +56,7 @@ munch==2.2.0
netaddr==0.7.18 netaddr==0.7.18
netifaces==0.10.6 netifaces==0.10.6
openstackdocstheme==1.18.1 openstackdocstheme==1.18.1
openstacksdk==0.12.0 openstacksdk==0.30.0
os-api-ref==1.4.0 os-api-ref==1.4.0
os-client-config==1.29.0 os-client-config==1.29.0
os-service-types==1.2.0 os-service-types==1.2.0
@ -97,7 +97,6 @@ python-ironicclient==2.3.0
python-keystoneclient==3.15.0 python-keystoneclient==3.15.0
python-mimeparse==1.6.0 python-mimeparse==1.6.0
python-subunit==1.2.0 python-subunit==1.2.0
python-swiftclient==3.2.0
pytz==2013.6 pytz==2013.6
PyYAML==3.12 PyYAML==3.12
reno==2.5.0 reno==2.5.0

View File

@ -0,0 +1,5 @@
---
deprecations:
- |
The configuration option ``[swift]max_retries`` is deprecated. It has been
doing nothing for a few releases already.

View File

@ -16,8 +16,8 @@ keystonemiddleware>=4.17.0 # Apache-2.0
netaddr>=0.7.18 # BSD netaddr>=0.7.18 # BSD
pbr!=2.1.0,>=2.0.0 # Apache-2.0 pbr!=2.1.0,>=2.0.0 # Apache-2.0
python-ironicclient>=2.3.0 # Apache-2.0 python-ironicclient>=2.3.0 # Apache-2.0
python-swiftclient>=3.2.0 # Apache-2.0
pytz>=2013.6 # MIT pytz>=2013.6 # MIT
openstacksdk>=0.30.0 # Apache-2.0
oslo.concurrency>=3.26.0 # Apache-2.0 oslo.concurrency>=3.26.0 # Apache-2.0
oslo.config>=5.2.0 # Apache-2.0 oslo.config>=5.2.0 # Apache-2.0
oslo.context>=2.19.2 # Apache-2.0 oslo.context>=2.19.2 # Apache-2.0