Config drive support for ceph radosgw
Currently config drive can be stored in swift with keystone authentication. This change allows ironic to store the config drive in ceph radosgw and use radosgw authentication mechanism that is not currently supported. It uses swift API compatibility for ceph radosgw. New options: [deploy]/configdrive_use_object_store [deploy]/object_store_endpoint_type Deprecations: [conductor]/configdrive_use_swift Replaced by: [deploy]/configdrive_use_object_store [glance]/temp_url_endpoint_type Replaced by: [deploy]/object_store_endpoint_type Change-Id: I9204c718505376cfb73632b0d0f31cea00d5e4d8 Closes-Bug: #1642719
This commit is contained in:
parent
8aec5fb6a5
commit
58b34b0b30
@ -64,6 +64,9 @@ Configure Ironic and Glance with RADOS Gateway
|
||||
swift_api_version = v1
|
||||
swift_endpoint_url = http://RADOS_IP:PORT
|
||||
swift_temp_url_key = TEMP_URL_KEY
|
||||
temp_url_endpoint_type=radosgw
|
||||
|
||||
[deploy]
|
||||
|
||||
object_store_endpoint_type = radosgw
|
||||
|
||||
#. Restart Ironic conductor service(s).
|
||||
|
@ -996,11 +996,8 @@
|
||||
# the check entirely. (integer value)
|
||||
#sync_local_state_interval = 180
|
||||
|
||||
# Whether to upload the config drive to Swift. (boolean value)
|
||||
#configdrive_use_swift = false
|
||||
|
||||
# Name of the Swift container to store config drive data. Used
|
||||
# when configdrive_use_swift is True. (string value)
|
||||
# when configdrive_use_object_store is True. (string value)
|
||||
#configdrive_swift_container = ironic_configdrive_container
|
||||
|
||||
# Timeout (seconds) for waiting for node inspection. 0 -
|
||||
@ -1312,6 +1309,18 @@
|
||||
# Allowed values: netboot, local
|
||||
#default_boot_option = <None>
|
||||
|
||||
# Whether to upload the config drive to object store. Set this
|
||||
# option to True to store config drive in swift or radosgw.
|
||||
# (boolean value)
|
||||
# Deprecated group/name - [conductor]/configdrive_use_swift
|
||||
#configdrive_use_object_store = false
|
||||
|
||||
# Type of object store endpoint type to be used as a backend
|
||||
# (string value)
|
||||
# Allowed values: swift, radosgw
|
||||
# Deprecated group/name - [glance]/temp_url_endpoint_type
|
||||
#object_store_endpoint_type = swift
|
||||
|
||||
|
||||
[dhcp]
|
||||
|
||||
@ -1561,12 +1570,6 @@
|
||||
# downloads. Required for temporary URLs. (string value)
|
||||
#swift_temp_url_key = <None>
|
||||
|
||||
# Type of endpoint to use for temporary URLs. If the Glance
|
||||
# backend is Swift, use "swift"; if it is CEPH with RADOS
|
||||
# gateway, use "radosgw". (string value)
|
||||
# Allowed values: swift, radosgw
|
||||
#temp_url_endpoint_type = swift
|
||||
|
||||
# Tenant ID (string value)
|
||||
#tenant_id = <None>
|
||||
|
||||
|
@ -58,6 +58,54 @@ for example::
|
||||
ironic node-set-provision-state --config-drive /dir/configdrive_files $node_identifier active
|
||||
|
||||
|
||||
Configuration drive storage in an object store
|
||||
----------------------------------------------
|
||||
|
||||
Under normal circumstances, the configuration drive can be stored in the
|
||||
Bare Metal service when the size is less than 64KB. Optionally, if the size
|
||||
is larger than 64KB there is support to store it in swift or radosgw backed
|
||||
object store. Both swift and radosgw use swift-style APIs.
|
||||
|
||||
The following option in ``/etc/ironic/ironic.conf`` enables swift as an object
|
||||
store backend to store config drive. This uses the Identity service to
|
||||
establish a session between the Bare Metal service and the
|
||||
Object Storage service. ::
|
||||
|
||||
[deploy]
|
||||
...
|
||||
|
||||
configdrive_use_object_store = True
|
||||
|
||||
Use the following options in ``/etc/ironic/ironic.conf`` to enable radosgw.
|
||||
Credentials in the swift section are needed because radosgw will not use the
|
||||
Identity service and relies on radosgw's username and password authentication
|
||||
instead. ::
|
||||
|
||||
[deploy]
|
||||
...
|
||||
|
||||
configdrive_use_object_store = True
|
||||
object_store_endpoint_type = radosgw
|
||||
|
||||
[swift]
|
||||
...
|
||||
|
||||
username = USERNAME
|
||||
password = PASSWORD
|
||||
auth_url = http://RADOSGW_IP:8000/auth/v1
|
||||
|
||||
Make sure that if an agent_* driver is being used, edit
|
||||
``/etc/glance/glance-api.conf`` to store the instance images in respective
|
||||
object store (radosgw or swift) as well::
|
||||
|
||||
[glance_store]
|
||||
...
|
||||
|
||||
swift_store_user = USERNAME
|
||||
swift_store_key = PASSWORD
|
||||
swift_store_auth_address = http://RADOSGW_OR_SWIFT_IP:PORT/auth/v1
|
||||
|
||||
|
||||
Accessing the configuration drive data
|
||||
--------------------------------------
|
||||
|
||||
@ -81,10 +129,13 @@ the configuration drive and mount it, for example::
|
||||
mount $CONFIG_DEV /mnt/config
|
||||
|
||||
|
||||
.. [*] A config drive could also be a data block with a VFAT filesystem
|
||||
.. [*] A configuration drive could also be a data block with a VFAT filesystem
|
||||
on it instead of ISO 9660. But it's unlikely that it would be needed
|
||||
since ISO 9660 is widely supported across operating systems.
|
||||
|
||||
For more information see `Store metadata on a configuration drive
|
||||
<http://docs.openstack.org/user-guide/cli-config-drive.html>`_.
|
||||
|
||||
|
||||
Cloud-init integration
|
||||
----------------------
|
||||
|
@ -147,7 +147,7 @@ class GlanceImageService(base_image_service.BaseImageService,
|
||||
}
|
||||
|
||||
endpoint_url = CONF.glance.swift_endpoint_url
|
||||
if CONF.glance.temp_url_endpoint_type == 'radosgw':
|
||||
if CONF.deploy.object_store_endpoint_type == 'radosgw':
|
||||
chunks = urlparse.urlsplit(CONF.glance.swift_endpoint_url)
|
||||
if not chunks.path:
|
||||
endpoint_url = urlparse.urljoin(
|
||||
@ -184,7 +184,7 @@ class GlanceImageService(base_image_service.BaseImageService,
|
||||
'Swift temporary URLs require a Swift endpoint URL. '
|
||||
'You must provide "swift_endpoint_url" as a config option.'))
|
||||
if (not CONF.glance.swift_account and
|
||||
CONF.glance.temp_url_endpoint_type == 'swift'):
|
||||
CONF.deploy.object_store_endpoint_type == 'swift'):
|
||||
raise exc.MissingParameterValue(_(
|
||||
'Swift temporary URLs require a Swift account string. '
|
||||
'You must provide "swift_account" as a config option.'))
|
||||
|
@ -23,7 +23,7 @@ from swiftclient import utils as swift_utils
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _
|
||||
from ironic.common import keystone
|
||||
|
||||
from ironic.conf import CONF
|
||||
|
||||
_SWIFT_SESSION = None
|
||||
|
||||
@ -39,8 +39,22 @@ class SwiftAPI(object):
|
||||
"""API for communicating with Swift."""
|
||||
|
||||
def __init__(self):
|
||||
session = _get_swift_session()
|
||||
self.connection = swift_client.Connection(session=session)
|
||||
"""Initialize the connection with swift or radosgw
|
||||
|
||||
:raises: ConfigInvalid if required keystone authorization credentials
|
||||
with swift are missing.
|
||||
"""
|
||||
params = {}
|
||||
if CONF.deploy.object_store_endpoint_type == 'radosgw':
|
||||
params = {'authurl': CONF.swift.auth_url,
|
||||
'user': CONF.swift.username,
|
||||
'key': CONF.swift.password}
|
||||
else:
|
||||
# NOTE(aNuposic): Session will be initiated only when connection
|
||||
# with swift is initialized. Since v3.2.0 swiftclient supports
|
||||
# instantiating the API client from keystoneauth session.
|
||||
params = {'session': _get_swift_session()}
|
||||
self.connection = swift_client.Connection(**params)
|
||||
|
||||
def create_object(self, container, obj, filename,
|
||||
object_headers=None):
|
||||
|
@ -85,6 +85,8 @@ class BaseConductorManager(object):
|
||||
:raises: DriverLoadError if an enabled driver cannot be loaded.
|
||||
:raises: DriverNameConflict if a classic driver and a dynamic driver
|
||||
are both enabled and have the same name.
|
||||
:raises: ConfigInvalid if required config options for connection with
|
||||
radosgw are missing while storing config drive.
|
||||
"""
|
||||
if self._started:
|
||||
raise RuntimeError(_('Attempt to start an already running '
|
||||
@ -172,6 +174,18 @@ class BaseConductorManager(object):
|
||||
self._periodic_task_callables,
|
||||
executor_factory=periodics.ExistingExecutor(self._executor))
|
||||
|
||||
# Check for required config options if object_store_endpoint_type is
|
||||
# radosgw
|
||||
if (CONF.deploy.configdrive_use_object_store and
|
||||
CONF.deploy.object_store_endpoint_type == "radosgw"):
|
||||
if (None in (CONF.swift.auth_url, CONF.swift.username,
|
||||
CONF.swift.password)):
|
||||
msg = _("Parameters missing to make a connection with "
|
||||
"radosgw. Ensure that [swift]/auth_url, "
|
||||
"[swift]/username, and [swift]/password are all "
|
||||
"configured.")
|
||||
raise exception.ConfigInvalid(msg)
|
||||
|
||||
# clear all target_power_state with locks by this conductor
|
||||
self.dbapi.clear_node_target_power_state(self.host)
|
||||
# clear all locks held by this conductor before registering
|
||||
|
@ -2698,17 +2698,20 @@ def _get_configdrive_obj_name(node):
|
||||
def _store_configdrive(node, configdrive):
|
||||
"""Handle the storage of the config drive.
|
||||
|
||||
If configured, the config drive data are uploaded to Swift. The Node's
|
||||
instance_info is updated to include either the temporary Swift URL
|
||||
from the upload, or if no upload, the actual config drive data.
|
||||
If configured, the config drive data are uploaded to swift or radosgw.
|
||||
The Node's instance_info is updated to include either the temporary
|
||||
Swift URL from the upload, or if no upload, the actual config drive data.
|
||||
|
||||
:param node: an Ironic node object.
|
||||
:param configdrive: A gzipped and base64 encoded configdrive.
|
||||
:raises: SwiftOperationError if an error occur when uploading the
|
||||
config drive to Swift.
|
||||
config drive to swift or radosgw.
|
||||
:raises: ConfigInvalid if required keystone authorization credentials
|
||||
with swift are missing.
|
||||
|
||||
|
||||
"""
|
||||
if CONF.conductor.configdrive_use_swift:
|
||||
if CONF.deploy.configdrive_use_object_store:
|
||||
# NOTE(lucasagomes): No reason to use a different timeout than
|
||||
# the one used for deploying the node
|
||||
timeout = CONF.conductor.deploy_callback_timeout
|
||||
|
@ -107,13 +107,11 @@ opts = [
|
||||
'conductor will check for nodes that it should '
|
||||
'"take over". Set it to a negative value to disable '
|
||||
'the check entirely.')),
|
||||
cfg.BoolOpt('configdrive_use_swift',
|
||||
default=False,
|
||||
help=_('Whether to upload the config drive to Swift.')),
|
||||
cfg.StrOpt('configdrive_swift_container',
|
||||
default='ironic_configdrive_container',
|
||||
help=_('Name of the Swift container to store config drive '
|
||||
'data. Used when configdrive_use_swift is True.')),
|
||||
'data. Used when configdrive_use_object_store is '
|
||||
'True.')),
|
||||
cfg.IntOpt('inspect_timeout',
|
||||
default=1800,
|
||||
help=_('Timeout (seconds) for waiting for node inspection. '
|
||||
|
@ -72,6 +72,20 @@ opts = [
|
||||
'default is "netboot", but it will be changed to '
|
||||
'"local" in the future. It is recommended to set '
|
||||
'an explicit value for this option.')),
|
||||
cfg.BoolOpt('configdrive_use_object_store',
|
||||
default=False,
|
||||
deprecated_group='conductor',
|
||||
deprecated_name='configdrive_use_swift',
|
||||
help=_('Whether to upload the config drive to object store. '
|
||||
'Set this option to True to store config drive '
|
||||
'in swift or radosgw.')),
|
||||
cfg.StrOpt('object_store_endpoint_type',
|
||||
default='swift',
|
||||
deprecated_group='glance',
|
||||
deprecated_name='temp_url_endpoint_type',
|
||||
choices=['swift', 'radosgw'],
|
||||
help=_('Type of object store endpoint type to be '
|
||||
'used as a backend')),
|
||||
]
|
||||
|
||||
|
||||
|
@ -103,12 +103,6 @@ opts = [
|
||||
'value between 1 and 32, a single-tenant store will use '
|
||||
'multiple containers to store images, and this value '
|
||||
'will determine how many containers are created.')),
|
||||
cfg.StrOpt('temp_url_endpoint_type',
|
||||
default='swift',
|
||||
choices=['swift', 'radosgw'],
|
||||
help=_('Type of endpoint to use for temporary URLs. If the '
|
||||
'Glance backend is Swift, use "swift"; if it is CEPH '
|
||||
'with RADOS gateway, use "radosgw".')),
|
||||
cfg.StrOpt('glance_host',
|
||||
default='$my_ip',
|
||||
help=_('Default glance hostname or IP address.')),
|
||||
|
@ -744,7 +744,7 @@ class TestGlanceSwiftTempURL(base.TestCase):
|
||||
|
||||
@mock.patch('swiftclient.utils.generate_temp_url', autospec=True)
|
||||
def test_swift_temp_url_radosgw(self, tempurl_mock):
|
||||
self.config(temp_url_endpoint_type='radosgw', group='glance')
|
||||
self.config(object_store_endpoint_type='radosgw', group='deploy')
|
||||
path = ('/v1'
|
||||
'/glance'
|
||||
'/757274c4-2856-4bd2-bb20-9a4a231e187b')
|
||||
@ -769,7 +769,7 @@ class TestGlanceSwiftTempURL(base.TestCase):
|
||||
def test_swift_temp_url_radosgw_endpoint_with_swift(self, tempurl_mock):
|
||||
self.config(swift_endpoint_url='https://swift.radosgw.com/swift',
|
||||
group='glance')
|
||||
self.config(temp_url_endpoint_type='radosgw', group='glance')
|
||||
self.config(object_store_endpoint_type='radosgw', group='deploy')
|
||||
path = ('/v1'
|
||||
'/glance'
|
||||
'/757274c4-2856-4bd2-bb20-9a4a231e187b')
|
||||
@ -793,7 +793,7 @@ class TestGlanceSwiftTempURL(base.TestCase):
|
||||
def test_swift_temp_url_radosgw_endpoint_invalid(self, tempurl_mock):
|
||||
self.config(swift_endpoint_url='https://swift.radosgw.com/eggs/',
|
||||
group='glance')
|
||||
self.config(temp_url_endpoint_type='radosgw', group='glance')
|
||||
self.config(object_store_endpoint_type='radosgw', group='deploy')
|
||||
self.service._validate_temp_url_config = mock.Mock()
|
||||
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
@ -851,7 +851,7 @@ class TestGlanceSwiftTempURL(base.TestCase):
|
||||
|
||||
def test__validate_temp_url_no_account_exception_radosgw(self):
|
||||
self.config(swift_account=None, group='glance')
|
||||
self.config(temp_url_endpoint_type='radosgw', group='glance')
|
||||
self.config(object_store_endpoint_type='radosgw', group='deploy')
|
||||
self.service._validate_temp_url_config()
|
||||
|
||||
def test__validate_temp_url_endpoint_less_than_download_delay(self):
|
||||
|
@ -13,6 +13,7 @@
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
import six
|
||||
from six.moves import builtins as __builtin__
|
||||
from six.moves import http_client
|
||||
@ -24,6 +25,7 @@ from ironic.common import exception
|
||||
from ironic.common import swift
|
||||
from ironic.tests import base
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
if six.PY3:
|
||||
import io
|
||||
@ -39,10 +41,35 @@ class SwiftTestCase(base.TestCase):
|
||||
self.swift_exception = swift_exception.ClientException('', '')
|
||||
|
||||
def test___init__(self, connection_mock, keystone_mock):
|
||||
"""Check if client is properly initialized with swift"""
|
||||
|
||||
swift.SwiftAPI()
|
||||
connection_mock.assert_called_once_with(
|
||||
session=keystone_mock.return_value)
|
||||
|
||||
def test___init___radosgw(self, connection_mock, swift_session_mock):
|
||||
"""Check if client is properly initialized with radosgw"""
|
||||
|
||||
auth_url = 'http://1.2.3.4'
|
||||
username = 'foo'
|
||||
password = 'foo_password'
|
||||
CONF.set_override('object_store_endpoint_type', 'radosgw',
|
||||
group='deploy')
|
||||
opts = [cfg.StrOpt('auth_url'), cfg.StrOpt('username'),
|
||||
cfg.StrOpt('password')]
|
||||
CONF.register_opts(opts, group='swift')
|
||||
|
||||
CONF.set_override('auth_url', auth_url, group='swift')
|
||||
CONF.set_override('username', username, group='swift')
|
||||
CONF.set_override('password', password, group='swift')
|
||||
|
||||
swift.SwiftAPI()
|
||||
params = {'authurl': auth_url,
|
||||
'user': username,
|
||||
'key': password}
|
||||
connection_mock.assert_called_once_with(**params)
|
||||
swift_session_mock.assert_not_called()
|
||||
|
||||
@mock.patch.object(__builtin__, 'open', autospec=True)
|
||||
def test_create_object(self, open_mock, connection_mock, keystone_mock):
|
||||
swiftapi = swift.SwiftAPI()
|
||||
|
@ -250,6 +250,28 @@ class StartStopTestCase(mgr_utils.ServiceSetUpMixin, tests_db_base.DbTestCase):
|
||||
self.service.del_host()
|
||||
self.assertTrue(wait_mock.called)
|
||||
|
||||
def test_start_fails_on_missing_config_for_configdrive(self):
|
||||
"""Check to fail conductor on missing config options"""
|
||||
|
||||
missing_parameters_error = ("Parameters missing to make a "
|
||||
"connection with radosgw")
|
||||
CONF.set_override('configdrive_use_object_store', True,
|
||||
group='deploy')
|
||||
CONF.set_override('object_store_endpoint_type', 'radosgw',
|
||||
group='deploy')
|
||||
params = {'auth_url': 'http://1.2.3.4',
|
||||
'username': 'foo', 'password': 'foo_pass'}
|
||||
CONF.register_opts((cfg.StrOpt(x) for x in params),
|
||||
group='swift')
|
||||
for key, value in params.items():
|
||||
test_params = params.copy()
|
||||
test_params[key] = None
|
||||
for test_key, test_value in test_params.items():
|
||||
CONF.set_override(key, test_value, group='swift')
|
||||
with self.assertRaisesRegex(exception.ConfigInvalid,
|
||||
missing_parameters_error):
|
||||
self._start_service()
|
||||
|
||||
|
||||
class CheckInterfacesTestCase(mgr_utils.ServiceSetUpMixin,
|
||||
tests_db_base.DbTestCase):
|
||||
|
@ -1433,7 +1433,8 @@ class DoNodeDeployTearDownTestCase(mgr_utils.ServiceSetUpMixin,
|
||||
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.deploy')
|
||||
def test__do_node_deploy_configdrive_swift_error(self, mock_deploy,
|
||||
mock_swift):
|
||||
CONF.set_override('configdrive_use_swift', True, group='conductor')
|
||||
CONF.set_override('configdrive_use_object_store', True,
|
||||
group='deploy')
|
||||
self._start_service()
|
||||
# test when driver.deploy.deploy returns DEPLOYDONE
|
||||
mock_deploy.return_value = states.DEPLOYDONE
|
||||
@ -5052,7 +5053,8 @@ class StoreConfigDriveTestCase(tests_base.TestCase):
|
||||
expected_instance_info = {'configdrive': 'http://1.2.3.4'}
|
||||
|
||||
# set configs and mocks
|
||||
CONF.set_override('configdrive_use_swift', True, group='conductor')
|
||||
CONF.set_override('configdrive_use_object_store', True,
|
||||
group='deploy')
|
||||
CONF.set_override('configdrive_swift_container', container_name,
|
||||
group='conductor')
|
||||
CONF.set_override('deploy_callback_timeout', timeout,
|
||||
|
@ -0,0 +1,23 @@
|
||||
---
|
||||
features:
|
||||
- Adds support for storing the configdrive in radosgw using
|
||||
the swift API.
|
||||
- |
|
||||
Adds support to use the radosgw authentication mechanism that relies
|
||||
on username and password instead of auth token.
|
||||
The following options must be specified in ironic configuration file:
|
||||
|
||||
* ``[swift]/auth_url``
|
||||
* ``[swift]/username``
|
||||
* ``[swift]/password``
|
||||
|
||||
deprecations:
|
||||
- The ``[conductor]/configdrive_use_swift`` and
|
||||
``[glance]/temp_url_endpoint_type`` options are deprecated and will be
|
||||
removed in the Queens release.
|
||||
Use ``[deploy]/configdrive_use_object_store`` and
|
||||
``[deploy]/object_store_endpoint_type`` respectively instead.
|
||||
upgrade:
|
||||
- Adds a ``[deploy]/object_store_endpoint_type`` option to specify the
|
||||
type of endpoint to use for instance images and configdrive storage.
|
||||
Allowed values are 'swift' or 'radosgw'. The default is 'swift'.
|
Loading…
Reference in New Issue
Block a user