Support passing a dictionary for configdrive
API version 1.56 introduces support for building configdrive on the server side. In this case configdrive is a dictionary, this changes adds support for it. Change-Id: I2b870fc89aa31c13b33f76b14acd6ee959800554 Depends-On: https://review.openstack.org/639050 Story: #2005083 Task: #29841
This commit is contained in:
@@ -43,7 +43,7 @@ from ironicclient import exc
|
|||||||
# http://specs.openstack.org/openstack/ironic-specs/specs/kilo/api-microversions.html # noqa
|
# http://specs.openstack.org/openstack/ironic-specs/specs/kilo/api-microversions.html # noqa
|
||||||
# for full details.
|
# for full details.
|
||||||
DEFAULT_VER = '1.9'
|
DEFAULT_VER = '1.9'
|
||||||
LAST_KNOWN_API_VERSION = 55
|
LAST_KNOWN_API_VERSION = 56
|
||||||
LATEST_VERSION = '1.{}'.format(LAST_KNOWN_API_VERSION)
|
LATEST_VERSION = '1.{}'.format(LAST_KNOWN_API_VERSION)
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import itertools
|
import itertools
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from osc_lib.command import command
|
from osc_lib.command import command
|
||||||
@@ -30,8 +31,10 @@ from ironicclient.v1 import utils as v1_utils
|
|||||||
CONFIG_DRIVE_ARG_HELP = _(
|
CONFIG_DRIVE_ARG_HELP = _(
|
||||||
"A gzipped, base64-encoded configuration drive string OR "
|
"A gzipped, base64-encoded configuration drive string OR "
|
||||||
"the path to the configuration drive file OR the path to a "
|
"the path to the configuration drive file OR the path to a "
|
||||||
"directory containing the config drive files. In case it's "
|
"directory containing the config drive files OR a JSON object to build "
|
||||||
"a directory, a config drive will be generated from it.")
|
"config drive from. In case it's a directory, a config drive will be "
|
||||||
|
"generated from it. In case it's a JSON object, a config drive will "
|
||||||
|
"be generated on the server side.")
|
||||||
|
|
||||||
|
|
||||||
SUPPORTED_INTERFACES = ['bios', 'boot', 'console', 'deploy', 'inspect',
|
SUPPORTED_INTERFACES = ['bios', 'boot', 'console', 'deploy', 'inspect',
|
||||||
@@ -69,6 +72,15 @@ class ProvisionStateBaremetalNode(command.Command):
|
|||||||
clean_steps = utils.handle_json_arg(clean_steps, 'clean steps')
|
clean_steps = utils.handle_json_arg(clean_steps, 'clean steps')
|
||||||
|
|
||||||
config_drive = getattr(parsed_args, 'config_drive', None)
|
config_drive = getattr(parsed_args, 'config_drive', None)
|
||||||
|
if config_drive:
|
||||||
|
try:
|
||||||
|
config_drive_dict = json.loads(config_drive)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if isinstance(config_drive_dict, dict):
|
||||||
|
config_drive = config_drive_dict
|
||||||
|
|
||||||
rescue_password = getattr(parsed_args, 'rescue_password', None)
|
rescue_password = getattr(parsed_args, 'rescue_password', None)
|
||||||
|
|
||||||
baremetal_client.node.set_provision_state(
|
baremetal_client.node.set_provision_state(
|
||||||
|
@@ -1390,6 +1390,25 @@ class TestDeployBaremetalProvisionState(TestBaremetal):
|
|||||||
'node_uuid', 'active',
|
'node_uuid', 'active',
|
||||||
cleansteps=None, configdrive='path/to/drive', rescue_password=None)
|
cleansteps=None, configdrive='path/to/drive', rescue_password=None)
|
||||||
|
|
||||||
|
def test_deploy_baremetal_provision_state_active_and_configdrive_dict(
|
||||||
|
self):
|
||||||
|
arglist = ['node_uuid',
|
||||||
|
'--config-drive', '{"meta_data": {}}']
|
||||||
|
verifylist = [
|
||||||
|
('node', 'node_uuid'),
|
||||||
|
('provision_state', 'active'),
|
||||||
|
('config_drive', '{"meta_data": {}}'),
|
||||||
|
]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
self.baremetal_mock.node.set_provision_state.assert_called_once_with(
|
||||||
|
'node_uuid', 'active',
|
||||||
|
cleansteps=None, configdrive={'meta_data': {}},
|
||||||
|
rescue_password=None)
|
||||||
|
|
||||||
def test_deploy_no_wait(self):
|
def test_deploy_no_wait(self):
|
||||||
arglist = ['node_uuid']
|
arglist = ['node_uuid']
|
||||||
verifylist = [
|
verifylist = [
|
||||||
|
@@ -1388,6 +1388,16 @@ class NodeManagerTest(testtools.TestCase):
|
|||||||
]
|
]
|
||||||
self.assertEqual(expect, self.api.calls)
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
|
||||||
|
def test_node_set_provision_state_with_configdrive_as_dict(self):
|
||||||
|
target_state = 'active'
|
||||||
|
self.mgr.set_provision_state(NODE1['uuid'], target_state,
|
||||||
|
configdrive={'user_data': ''})
|
||||||
|
body = {'target': target_state, 'configdrive': {'user_data': ''}}
|
||||||
|
expect = [
|
||||||
|
('PUT', '/v1/nodes/%s/states/provision' % NODE1['uuid'], {}, body),
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
|
||||||
def test_node_set_provision_state_with_configdrive_file(self):
|
def test_node_set_provision_state_with_configdrive_file(self):
|
||||||
target_state = 'active'
|
target_state = 'active'
|
||||||
file_content = b'foo bar cat meow dog bark'
|
file_content = b'foo bar cat meow dog bark'
|
||||||
|
@@ -515,9 +515,11 @@ class NodeManager(base.CreateManager):
|
|||||||
'rescue', 'unrescue'.
|
'rescue', 'unrescue'.
|
||||||
:param configdrive: A gzipped, base64-encoded configuration drive
|
:param configdrive: A gzipped, base64-encoded configuration drive
|
||||||
string OR the path to the configuration drive file OR the path to
|
string OR the path to the configuration drive file OR the path to
|
||||||
a directory containing the config drive files. In case it's a
|
a directory containing the config drive files OR a dictionary to
|
||||||
directory, a config drive will be generated from it. This is only
|
build config drive from. In case it's a directory, a config drive
|
||||||
valid when setting state to 'active'.
|
will be generated from it. In case it's a dictionary, a config
|
||||||
|
drive will be generated on the server side (requires API version
|
||||||
|
1.56). This is only valid when setting state to 'active'.
|
||||||
:param cleansteps: The clean steps as a list of clean-step
|
:param cleansteps: The clean steps as a list of clean-step
|
||||||
dictionaries; each dictionary should have keys 'interface' and
|
dictionaries; each dictionary should have keys 'interface' and
|
||||||
'step', and optional key 'args'. This must be specified (and is
|
'step', and optional key 'args'. This must be specified (and is
|
||||||
@@ -534,11 +536,12 @@ class NodeManager(base.CreateManager):
|
|||||||
path = "%s/states/provision" % node_uuid
|
path = "%s/states/provision" % node_uuid
|
||||||
body = {'target': state}
|
body = {'target': state}
|
||||||
if configdrive:
|
if configdrive:
|
||||||
if os.path.isfile(configdrive):
|
if not isinstance(configdrive, dict):
|
||||||
with open(configdrive, 'rb') as f:
|
if os.path.isfile(configdrive):
|
||||||
configdrive = f.read()
|
with open(configdrive, 'rb') as f:
|
||||||
if os.path.isdir(configdrive):
|
configdrive = f.read()
|
||||||
configdrive = utils.make_configdrive(configdrive)
|
if os.path.isdir(configdrive):
|
||||||
|
configdrive = utils.make_configdrive(configdrive)
|
||||||
|
|
||||||
body['configdrive'] = configdrive
|
body['configdrive'] = configdrive
|
||||||
elif cleansteps:
|
elif cleansteps:
|
||||||
|
5
releasenotes/notes/configdrive-7bd2b67830691b2e.yaml
Normal file
5
releasenotes/notes/configdrive-7bd2b67830691b2e.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Supports passing a JSON object to ``--config-drive`` to build the config
|
||||||
|
drive on the server side (requires API version 1.56).
|
Reference in New Issue
Block a user