Merge "Config drive support for ceph radosgw"

This commit is contained in:
Jenkins 2017-04-17 17:59:06 +00:00 committed by Gerrit Code Review
commit 19ced862a7
15 changed files with 206 additions and 38 deletions

View File

@ -64,6 +64,9 @@ Configure Ironic and Glance with RADOS Gateway
swift_api_version = v1 swift_api_version = v1
swift_endpoint_url = http://RADOS_IP:PORT swift_endpoint_url = http://RADOS_IP:PORT
swift_temp_url_key = TEMP_URL_KEY swift_temp_url_key = TEMP_URL_KEY
temp_url_endpoint_type=radosgw
[deploy]
object_store_endpoint_type = radosgw
#. Restart Ironic conductor service(s). #. Restart Ironic conductor service(s).

View File

@ -1089,11 +1089,8 @@
# the check entirely. (integer value) # the check entirely. (integer value)
#sync_local_state_interval = 180 #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 # 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 #configdrive_swift_container = ironic_configdrive_container
# Timeout (seconds) for waiting for node inspection. 0 - # Timeout (seconds) for waiting for node inspection. 0 -
@ -1396,6 +1393,18 @@
# Allowed values: netboot, local # Allowed values: netboot, local
#default_boot_option = <None> #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] [dhcp]
@ -1645,12 +1654,6 @@
# downloads. Required for temporary URLs. (string value) # downloads. Required for temporary URLs. (string value)
#swift_temp_url_key = <None> #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 (string value)
#tenant_id = <None> #tenant_id = <None>

View File

@ -58,6 +58,54 @@ for example::
ironic node-set-provision-state --config-drive /dir/configdrive_files $node_identifier active 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 Accessing the configuration drive data
-------------------------------------- --------------------------------------
@ -81,10 +129,13 @@ the configuration drive and mount it, for example::
mount $CONFIG_DEV /mnt/config 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 on it instead of ISO 9660. But it's unlikely that it would be needed
since ISO 9660 is widely supported across operating systems. 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 Cloud-init integration
---------------------- ----------------------

View File

@ -147,7 +147,7 @@ class GlanceImageService(base_image_service.BaseImageService,
} }
endpoint_url = CONF.glance.swift_endpoint_url 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) chunks = urlparse.urlsplit(CONF.glance.swift_endpoint_url)
if not chunks.path: if not chunks.path:
endpoint_url = urlparse.urljoin( endpoint_url = urlparse.urljoin(
@ -184,7 +184,7 @@ class GlanceImageService(base_image_service.BaseImageService,
'Swift temporary URLs require a Swift endpoint URL. ' 'Swift temporary URLs require a Swift endpoint URL. '
'You must provide "swift_endpoint_url" as a config option.')) 'You must provide "swift_endpoint_url" as a config option.'))
if (not CONF.glance.swift_account and if (not CONF.glance.swift_account and
CONF.glance.temp_url_endpoint_type == 'swift'): CONF.deploy.object_store_endpoint_type == 'swift'):
raise exc.MissingParameterValue(_( raise exc.MissingParameterValue(_(
'Swift temporary URLs require a Swift account string. ' 'Swift temporary URLs require a Swift account string. '
'You must provide "swift_account" as a config option.')) 'You must provide "swift_account" as a config option.'))

View File

@ -23,7 +23,7 @@ from swiftclient import utils as swift_utils
from ironic.common import exception from ironic.common import exception
from ironic.common.i18n import _ from ironic.common.i18n import _
from ironic.common import keystone from ironic.common import keystone
from ironic.conf import CONF
_SWIFT_SESSION = None _SWIFT_SESSION = None
@ -39,8 +39,22 @@ class SwiftAPI(object):
"""API for communicating with Swift.""" """API for communicating with Swift."""
def __init__(self): def __init__(self):
session = _get_swift_session() """Initialize the connection with swift or radosgw
self.connection = swift_client.Connection(session=session)
: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, def create_object(self, container, obj, filename,
object_headers=None): object_headers=None):

View File

@ -85,6 +85,8 @@ class BaseConductorManager(object):
:raises: DriverLoadError if an enabled driver cannot be loaded. :raises: DriverLoadError if an enabled driver cannot be loaded.
:raises: DriverNameConflict if a classic driver and a dynamic driver :raises: DriverNameConflict if a classic driver and a dynamic driver
are both enabled and have the same name. 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: if self._started:
raise RuntimeError(_('Attempt to start an already running ' raise RuntimeError(_('Attempt to start an already running '
@ -172,6 +174,18 @@ class BaseConductorManager(object):
self._periodic_task_callables, self._periodic_task_callables,
executor_factory=periodics.ExistingExecutor(self._executor)) 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 # clear all target_power_state with locks by this conductor
self.dbapi.clear_node_target_power_state(self.host) self.dbapi.clear_node_target_power_state(self.host)
# clear all locks held by this conductor before registering # clear all locks held by this conductor before registering

View File

@ -2695,17 +2695,20 @@ def _get_configdrive_obj_name(node):
def _store_configdrive(node, configdrive): def _store_configdrive(node, configdrive):
"""Handle the storage of the config drive. """Handle the storage of the config drive.
If configured, the config drive data are uploaded to Swift. The Node's If configured, the config drive data are uploaded to swift or radosgw.
instance_info is updated to include either the temporary Swift URL The Node's instance_info is updated to include either the temporary
from the upload, or if no upload, the actual config drive data. Swift URL from the upload, or if no upload, the actual config drive data.
:param node: an Ironic node object. :param node: an Ironic node object.
:param configdrive: A gzipped and base64 encoded configdrive. :param configdrive: A gzipped and base64 encoded configdrive.
:raises: SwiftOperationError if an error occur when uploading the :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 # NOTE(lucasagomes): No reason to use a different timeout than
# the one used for deploying the node # the one used for deploying the node
timeout = CONF.conductor.deploy_callback_timeout timeout = CONF.conductor.deploy_callback_timeout

View File

@ -107,13 +107,11 @@ opts = [
'conductor will check for nodes that it should ' 'conductor will check for nodes that it should '
'"take over". Set it to a negative value to disable ' '"take over". Set it to a negative value to disable '
'the check entirely.')), 'the check entirely.')),
cfg.BoolOpt('configdrive_use_swift',
default=False,
help=_('Whether to upload the config drive to Swift.')),
cfg.StrOpt('configdrive_swift_container', cfg.StrOpt('configdrive_swift_container',
default='ironic_configdrive_container', default='ironic_configdrive_container',
help=_('Name of the Swift container to store config drive ' 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', cfg.IntOpt('inspect_timeout',
default=1800, default=1800,
help=_('Timeout (seconds) for waiting for node inspection. ' help=_('Timeout (seconds) for waiting for node inspection. '

View File

@ -72,6 +72,20 @@ opts = [
'default is "netboot", but it will be changed to ' 'default is "netboot", but it will be changed to '
'"local" in the future. It is recommended to set ' '"local" in the future. It is recommended to set '
'an explicit value for this option.')), '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')),
] ]

View File

@ -103,12 +103,6 @@ opts = [
'value between 1 and 32, a single-tenant store will use ' 'value between 1 and 32, a single-tenant store will use '
'multiple containers to store images, and this value ' 'multiple containers to store images, and this value '
'will determine how many containers are created.')), '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', cfg.StrOpt('glance_host',
default='$my_ip', default='$my_ip',
help=_('Default glance hostname or IP address.')), help=_('Default glance hostname or IP address.')),

View File

@ -744,7 +744,7 @@ class TestGlanceSwiftTempURL(base.TestCase):
@mock.patch('swiftclient.utils.generate_temp_url', autospec=True) @mock.patch('swiftclient.utils.generate_temp_url', autospec=True)
def test_swift_temp_url_radosgw(self, tempurl_mock): 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' path = ('/v1'
'/glance' '/glance'
'/757274c4-2856-4bd2-bb20-9a4a231e187b') '/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): def test_swift_temp_url_radosgw_endpoint_with_swift(self, tempurl_mock):
self.config(swift_endpoint_url='https://swift.radosgw.com/swift', self.config(swift_endpoint_url='https://swift.radosgw.com/swift',
group='glance') group='glance')
self.config(temp_url_endpoint_type='radosgw', group='glance') self.config(object_store_endpoint_type='radosgw', group='deploy')
path = ('/v1' path = ('/v1'
'/glance' '/glance'
'/757274c4-2856-4bd2-bb20-9a4a231e187b') '/757274c4-2856-4bd2-bb20-9a4a231e187b')
@ -793,7 +793,7 @@ class TestGlanceSwiftTempURL(base.TestCase):
def test_swift_temp_url_radosgw_endpoint_invalid(self, tempurl_mock): def test_swift_temp_url_radosgw_endpoint_invalid(self, tempurl_mock):
self.config(swift_endpoint_url='https://swift.radosgw.com/eggs/', self.config(swift_endpoint_url='https://swift.radosgw.com/eggs/',
group='glance') 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.service._validate_temp_url_config = mock.Mock()
self.assertRaises(exception.InvalidParameterValue, self.assertRaises(exception.InvalidParameterValue,
@ -851,7 +851,7 @@ class TestGlanceSwiftTempURL(base.TestCase):
def test__validate_temp_url_no_account_exception_radosgw(self): def test__validate_temp_url_no_account_exception_radosgw(self):
self.config(swift_account=None, group='glance') 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() self.service._validate_temp_url_config()
def test__validate_temp_url_endpoint_less_than_download_delay(self): def test__validate_temp_url_endpoint_less_than_download_delay(self):

View File

@ -13,6 +13,7 @@
# under the License. # under the License.
import mock import mock
from oslo_config import cfg
import six import six
from six.moves import builtins as __builtin__ from six.moves import builtins as __builtin__
from six.moves import http_client from six.moves import http_client
@ -24,6 +25,7 @@ from ironic.common import exception
from ironic.common import swift from ironic.common import swift
from ironic.tests import base from ironic.tests import base
CONF = cfg.CONF
if six.PY3: if six.PY3:
import io import io
@ -39,10 +41,35 @@ class SwiftTestCase(base.TestCase):
self.swift_exception = swift_exception.ClientException('', '') self.swift_exception = swift_exception.ClientException('', '')
def test___init__(self, connection_mock, keystone_mock): def test___init__(self, connection_mock, keystone_mock):
"""Check if client is properly initialized with swift"""
swift.SwiftAPI() swift.SwiftAPI()
connection_mock.assert_called_once_with( connection_mock.assert_called_once_with(
session=keystone_mock.return_value) 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) @mock.patch.object(__builtin__, 'open', autospec=True)
def test_create_object(self, open_mock, connection_mock, keystone_mock): def test_create_object(self, open_mock, connection_mock, keystone_mock):
swiftapi = swift.SwiftAPI() swiftapi = swift.SwiftAPI()

View File

@ -250,6 +250,28 @@ class StartStopTestCase(mgr_utils.ServiceSetUpMixin, tests_db_base.DbTestCase):
self.service.del_host() self.service.del_host()
self.assertTrue(wait_mock.called) 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, class CheckInterfacesTestCase(mgr_utils.ServiceSetUpMixin,
tests_db_base.DbTestCase): tests_db_base.DbTestCase):

View File

@ -1433,7 +1433,8 @@ class DoNodeDeployTearDownTestCase(mgr_utils.ServiceSetUpMixin,
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.deploy') @mock.patch('ironic.drivers.modules.fake.FakeDeploy.deploy')
def test__do_node_deploy_configdrive_swift_error(self, mock_deploy, def test__do_node_deploy_configdrive_swift_error(self, mock_deploy,
mock_swift): mock_swift):
CONF.set_override('configdrive_use_swift', True, group='conductor') CONF.set_override('configdrive_use_object_store', True,
group='deploy')
self._start_service() self._start_service()
# test when driver.deploy.deploy returns DEPLOYDONE # test when driver.deploy.deploy returns DEPLOYDONE
mock_deploy.return_value = states.DEPLOYDONE mock_deploy.return_value = states.DEPLOYDONE
@ -5042,7 +5043,8 @@ class StoreConfigDriveTestCase(tests_base.TestCase):
expected_instance_info = {'configdrive': 'http://1.2.3.4'} expected_instance_info = {'configdrive': 'http://1.2.3.4'}
# set configs and mocks # 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, CONF.set_override('configdrive_swift_container', container_name,
group='conductor') group='conductor')
CONF.set_override('deploy_callback_timeout', timeout, CONF.set_override('deploy_callback_timeout', timeout,

View File

@ -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'.