Add update_machine method
Additional method which leverages jsonpatch to appropriately update a machine's configuration as-necessary. Change-Id: I401829be21484519eb4880e7108c7693c9501df8
This commit is contained in:
parent
2318c033e2
commit
6ac348dd13
@ -1,5 +1,6 @@
|
|||||||
pbr>=0.5.21,<1.0
|
pbr>=0.5.21,<1.0
|
||||||
|
|
||||||
|
jsonpatch
|
||||||
os-client-config>=0.7.0
|
os-client-config>=0.7.0
|
||||||
six
|
six
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ import glanceclient
|
|||||||
import glanceclient.exc
|
import glanceclient.exc
|
||||||
from ironicclient import client as ironic_client
|
from ironicclient import client as ironic_client
|
||||||
from ironicclient import exceptions as ironic_exceptions
|
from ironicclient import exceptions as ironic_exceptions
|
||||||
|
import jsonpatch
|
||||||
from keystoneclient import auth as ksc_auth
|
from keystoneclient import auth as ksc_auth
|
||||||
from keystoneclient import session as ksc_session
|
from keystoneclient import session as ksc_session
|
||||||
from keystoneclient import client as keystone_client
|
from keystoneclient import client as keystone_client
|
||||||
@ -2294,6 +2295,100 @@ class OperatorCloud(OpenStackCloud):
|
|||||||
"Error updating machine via patch operation. node: %s. "
|
"Error updating machine via patch operation. node: %s. "
|
||||||
"%s" % (name_or_id, e))
|
"%s" % (name_or_id, e))
|
||||||
|
|
||||||
|
def update_machine(self, name_or_id, chassis_uuid=None, driver=None,
|
||||||
|
driver_info=None, name=None, instance_info=None,
|
||||||
|
instance_uuid=None, properties=None):
|
||||||
|
"""Update a machine with new configuration information
|
||||||
|
|
||||||
|
A user-friendly method to perform updates of a machine, in whole or
|
||||||
|
part.
|
||||||
|
|
||||||
|
:param string name_or_id: A machine name or UUID to be updated.
|
||||||
|
:param string chassis_uuid: Assign a chassis UUID to the machine.
|
||||||
|
NOTE: As of the Kilo release, this value
|
||||||
|
cannot be changed once set. If a user
|
||||||
|
attempts to change this value, then the
|
||||||
|
Ironic API, as of Kilo, will reject the
|
||||||
|
request.
|
||||||
|
:param string driver: The driver name for controlling the machine.
|
||||||
|
:param dict driver_info: The dictonary defining the configuration
|
||||||
|
that the driver will utilize to control
|
||||||
|
the machine. Permutations of this are
|
||||||
|
dependent upon the specific driver utilized.
|
||||||
|
:param string name: A human relatable name to represent the machine.
|
||||||
|
:param dict instance_info: A dictonary of configuration information
|
||||||
|
that conveys to the driver how the host
|
||||||
|
is to be configured when deployed.
|
||||||
|
be deployed to the machine.
|
||||||
|
:param string instance_uuid: A UUID value representing the instance
|
||||||
|
that the deployed machine represents.
|
||||||
|
:param dict properties: A dictonary defining the properties of a
|
||||||
|
machine.
|
||||||
|
|
||||||
|
:raises: OpenStackCloudException on operation error.
|
||||||
|
|
||||||
|
:returns: Dictonary containing a machine sub-dictonary consisting
|
||||||
|
of the updated data returned from the API update operation,
|
||||||
|
and a list named changes which contains all of the API paths
|
||||||
|
that received updates.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
machine = self.get_machine(name_or_id)
|
||||||
|
|
||||||
|
machine_config = {}
|
||||||
|
new_config = {}
|
||||||
|
|
||||||
|
if chassis_uuid:
|
||||||
|
machine_config['chassis_uuid'] = machine['chassis_uuid']
|
||||||
|
new_config['chassis_uuid'] = chassis_uuid
|
||||||
|
|
||||||
|
if driver:
|
||||||
|
machine_config['driver'] = machine['driver']
|
||||||
|
new_config['driver'] = driver
|
||||||
|
|
||||||
|
if driver_info:
|
||||||
|
machine_config['driver_info'] = machine['driver_info']
|
||||||
|
new_config['driver_info'] = driver_info
|
||||||
|
|
||||||
|
if name:
|
||||||
|
machine_config['name'] = machine['name']
|
||||||
|
new_config['name'] = name
|
||||||
|
|
||||||
|
if instance_info:
|
||||||
|
machine_config['instance_info'] = machine['instance_info']
|
||||||
|
new_config['instance_info'] = instance_info
|
||||||
|
|
||||||
|
if instance_uuid:
|
||||||
|
machine_config['instance_uuid'] = machine['instance_uuid']
|
||||||
|
new_config['instance_uuid'] = instance_uuid
|
||||||
|
|
||||||
|
if properties:
|
||||||
|
machine_config['properties'] = machine['properties']
|
||||||
|
new_config['properties'] = properties
|
||||||
|
|
||||||
|
patch = jsonpatch.JsonPatch.from_diff(machine_config, new_config)
|
||||||
|
|
||||||
|
if not patch:
|
||||||
|
return dict(
|
||||||
|
node=machine,
|
||||||
|
changes=None
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
machine = self.patch_machine(machine['uuid'], list(patch))
|
||||||
|
change_list = []
|
||||||
|
for change in list(patch):
|
||||||
|
change_list.append(change['path'])
|
||||||
|
return dict(
|
||||||
|
node=machine,
|
||||||
|
changes=change_list
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
self.log.debug(
|
||||||
|
"Machine update failed", exc_info=True)
|
||||||
|
raise OpenStackCloudException(
|
||||||
|
"Error updating machine node %s. "
|
||||||
|
"%s" % (name_or_id, e))
|
||||||
|
|
||||||
def validate_node(self, uuid):
|
def validate_node(self, uuid):
|
||||||
try:
|
try:
|
||||||
ifaces = self.ironic_client.node.validate(uuid)
|
ifaces = self.ironic_client.node.validate(uuid)
|
||||||
|
@ -211,6 +211,250 @@ class TestShadeOperator(base.TestCase):
|
|||||||
self.cloud.patch_machine(node_id, patch)
|
self.cloud.patch_machine(node_id, patch)
|
||||||
self.assertTrue(mock_client.node.update.called)
|
self.assertTrue(mock_client.node.update.called)
|
||||||
|
|
||||||
|
@mock.patch.object(shade.OperatorCloud, 'ironic_client')
|
||||||
|
@mock.patch.object(shade.OperatorCloud, 'patch_machine')
|
||||||
|
def test_update_machine_patch_no_action(self, mock_patch, mock_client):
|
||||||
|
class client_return_value:
|
||||||
|
uuid = '00000000-0000-0000-0000-000000000000'
|
||||||
|
name = 'node01'
|
||||||
|
|
||||||
|
expected_machine = dict(
|
||||||
|
uuid='00000000-0000-0000-0000-000000000000',
|
||||||
|
name='node01'
|
||||||
|
)
|
||||||
|
mock_client.node.get.return_value = client_return_value
|
||||||
|
|
||||||
|
update_dict = self.cloud.update_machine('node01')
|
||||||
|
self.assertIsNone(update_dict['changes'])
|
||||||
|
self.assertFalse(mock_patch.called)
|
||||||
|
self.assertDictEqual(expected_machine, update_dict['node'])
|
||||||
|
|
||||||
|
@mock.patch.object(shade.OperatorCloud, 'ironic_client')
|
||||||
|
@mock.patch.object(shade.OperatorCloud, 'patch_machine')
|
||||||
|
def test_update_machine_patch_no_action_name(self, mock_patch,
|
||||||
|
mock_client):
|
||||||
|
class client_return_value:
|
||||||
|
uuid = '00000000-0000-0000-0000-000000000000'
|
||||||
|
name = 'node01'
|
||||||
|
|
||||||
|
expected_machine = dict(
|
||||||
|
uuid='00000000-0000-0000-0000-000000000000',
|
||||||
|
name='node01'
|
||||||
|
)
|
||||||
|
mock_client.node.get.return_value = client_return_value
|
||||||
|
|
||||||
|
update_dict = self.cloud.update_machine('node01', name='node01')
|
||||||
|
self.assertIsNone(update_dict['changes'])
|
||||||
|
self.assertFalse(mock_patch.called)
|
||||||
|
self.assertDictEqual(expected_machine, update_dict['node'])
|
||||||
|
|
||||||
|
@mock.patch.object(shade.OperatorCloud, 'ironic_client')
|
||||||
|
@mock.patch.object(shade.OperatorCloud, 'patch_machine')
|
||||||
|
def test_update_machine_patch_action_name(self, mock_patch,
|
||||||
|
mock_client):
|
||||||
|
class client_return_value:
|
||||||
|
uuid = '00000000-0000-0000-0000-000000000000'
|
||||||
|
name = 'evil'
|
||||||
|
|
||||||
|
expected_patch = [dict(op='replace', path='/name', value='good')]
|
||||||
|
|
||||||
|
mock_client.node.get.return_value = client_return_value
|
||||||
|
|
||||||
|
update_dict = self.cloud.update_machine('evil', name='good')
|
||||||
|
self.assertIsNotNone(update_dict['changes'])
|
||||||
|
self.assertEqual('/name', update_dict['changes'][0])
|
||||||
|
self.assertTrue(mock_patch.called)
|
||||||
|
mock_patch.assert_called_with(
|
||||||
|
'00000000-0000-0000-0000-000000000000',
|
||||||
|
expected_patch)
|
||||||
|
|
||||||
|
@mock.patch.object(shade.OperatorCloud, 'ironic_client')
|
||||||
|
@mock.patch.object(shade.OperatorCloud, 'patch_machine')
|
||||||
|
def test_update_machine_patch_update_name(self, mock_patch,
|
||||||
|
mock_client):
|
||||||
|
class client_return_value:
|
||||||
|
uuid = '00000000-0000-0000-0000-000000000000'
|
||||||
|
name = 'evil'
|
||||||
|
|
||||||
|
expected_patch = [dict(op='replace', path='/name', value='good')]
|
||||||
|
|
||||||
|
mock_client.node.get.return_value = client_return_value
|
||||||
|
|
||||||
|
update_dict = self.cloud.update_machine('evil', name='good')
|
||||||
|
self.assertIsNotNone(update_dict['changes'])
|
||||||
|
self.assertEqual('/name', update_dict['changes'][0])
|
||||||
|
self.assertTrue(mock_patch.called)
|
||||||
|
mock_patch.assert_called_with(
|
||||||
|
'00000000-0000-0000-0000-000000000000',
|
||||||
|
expected_patch)
|
||||||
|
|
||||||
|
@mock.patch.object(shade.OperatorCloud, 'ironic_client')
|
||||||
|
@mock.patch.object(shade.OperatorCloud, 'patch_machine')
|
||||||
|
def test_update_machine_patch_update_chassis_uuid(self, mock_patch,
|
||||||
|
mock_client):
|
||||||
|
class client_return_value:
|
||||||
|
uuid = '00000000-0000-0000-0000-000000000000'
|
||||||
|
chassis_uuid = None
|
||||||
|
|
||||||
|
expected_patch = [
|
||||||
|
dict(
|
||||||
|
op='replace',
|
||||||
|
path='/chassis_uuid',
|
||||||
|
value='00000000-0000-0000-0000-000000000001'
|
||||||
|
)]
|
||||||
|
|
||||||
|
mock_client.node.get.return_value = client_return_value
|
||||||
|
|
||||||
|
update_dict = self.cloud.update_machine(
|
||||||
|
'00000000-0000-0000-0000-000000000000',
|
||||||
|
chassis_uuid='00000000-0000-0000-0000-000000000001')
|
||||||
|
self.assertIsNotNone(update_dict['changes'])
|
||||||
|
self.assertEqual('/chassis_uuid', update_dict['changes'][0])
|
||||||
|
self.assertTrue(mock_patch.called)
|
||||||
|
mock_patch.assert_called_with(
|
||||||
|
'00000000-0000-0000-0000-000000000000',
|
||||||
|
expected_patch)
|
||||||
|
|
||||||
|
@mock.patch.object(shade.OperatorCloud, 'ironic_client')
|
||||||
|
@mock.patch.object(shade.OperatorCloud, 'patch_machine')
|
||||||
|
def test_update_machine_patch_update_driver(self, mock_patch,
|
||||||
|
mock_client):
|
||||||
|
class client_return_value:
|
||||||
|
uuid = '00000000-0000-0000-0000-000000000000'
|
||||||
|
driver = None
|
||||||
|
|
||||||
|
expected_patch = [
|
||||||
|
dict(
|
||||||
|
op='replace',
|
||||||
|
path='/driver',
|
||||||
|
value='fake'
|
||||||
|
)]
|
||||||
|
|
||||||
|
mock_client.node.get.return_value = client_return_value
|
||||||
|
|
||||||
|
update_dict = self.cloud.update_machine(
|
||||||
|
'00000000-0000-0000-0000-000000000000',
|
||||||
|
driver='fake'
|
||||||
|
)
|
||||||
|
self.assertIsNotNone(update_dict['changes'])
|
||||||
|
self.assertEqual('/driver', update_dict['changes'][0])
|
||||||
|
self.assertTrue(mock_patch.called)
|
||||||
|
mock_patch.assert_called_with(
|
||||||
|
'00000000-0000-0000-0000-000000000000',
|
||||||
|
expected_patch)
|
||||||
|
|
||||||
|
@mock.patch.object(shade.OperatorCloud, 'ironic_client')
|
||||||
|
@mock.patch.object(shade.OperatorCloud, 'patch_machine')
|
||||||
|
def test_update_machine_patch_update_driver_info(self, mock_patch,
|
||||||
|
mock_client):
|
||||||
|
class client_return_value:
|
||||||
|
uuid = '00000000-0000-0000-0000-000000000000'
|
||||||
|
driver_info = None
|
||||||
|
|
||||||
|
expected_patch = [
|
||||||
|
dict(
|
||||||
|
op='replace',
|
||||||
|
path='/driver_info',
|
||||||
|
value=dict(var='fake')
|
||||||
|
)]
|
||||||
|
|
||||||
|
mock_client.node.get.return_value = client_return_value
|
||||||
|
|
||||||
|
update_dict = self.cloud.update_machine(
|
||||||
|
'00000000-0000-0000-0000-000000000000',
|
||||||
|
driver_info=dict(var="fake")
|
||||||
|
)
|
||||||
|
self.assertIsNotNone(update_dict['changes'])
|
||||||
|
self.assertEqual('/driver_info', update_dict['changes'][0])
|
||||||
|
self.assertTrue(mock_patch.called)
|
||||||
|
mock_patch.assert_called_with(
|
||||||
|
'00000000-0000-0000-0000-000000000000',
|
||||||
|
expected_patch)
|
||||||
|
|
||||||
|
@mock.patch.object(shade.OperatorCloud, 'ironic_client')
|
||||||
|
@mock.patch.object(shade.OperatorCloud, 'patch_machine')
|
||||||
|
def test_update_machine_patch_update_instance_info(self, mock_patch,
|
||||||
|
mock_client):
|
||||||
|
class client_return_value:
|
||||||
|
uuid = '00000000-0000-0000-0000-000000000000'
|
||||||
|
instance_info = None
|
||||||
|
|
||||||
|
expected_patch = [
|
||||||
|
dict(
|
||||||
|
op='replace',
|
||||||
|
path='/instance_info',
|
||||||
|
value=dict(var='fake')
|
||||||
|
)]
|
||||||
|
|
||||||
|
mock_client.node.get.return_value = client_return_value
|
||||||
|
|
||||||
|
update_dict = self.cloud.update_machine(
|
||||||
|
'00000000-0000-0000-0000-000000000000',
|
||||||
|
instance_info=dict(var="fake")
|
||||||
|
)
|
||||||
|
self.assertIsNotNone(update_dict['changes'])
|
||||||
|
self.assertEqual('/instance_info', update_dict['changes'][0])
|
||||||
|
self.assertTrue(mock_patch.called)
|
||||||
|
mock_patch.assert_called_with(
|
||||||
|
'00000000-0000-0000-0000-000000000000',
|
||||||
|
expected_patch)
|
||||||
|
|
||||||
|
@mock.patch.object(shade.OperatorCloud, 'ironic_client')
|
||||||
|
@mock.patch.object(shade.OperatorCloud, 'patch_machine')
|
||||||
|
def test_update_machine_patch_update_instance_uuid(self, mock_patch,
|
||||||
|
mock_client):
|
||||||
|
class client_return_value:
|
||||||
|
uuid = '00000000-0000-0000-0000-000000000000'
|
||||||
|
instance_uuid = None
|
||||||
|
|
||||||
|
expected_patch = [
|
||||||
|
dict(
|
||||||
|
op='replace',
|
||||||
|
path='/instance_uuid',
|
||||||
|
value='00000000-0000-0000-0000-000000000002'
|
||||||
|
)]
|
||||||
|
|
||||||
|
mock_client.node.get.return_value = client_return_value
|
||||||
|
|
||||||
|
update_dict = self.cloud.update_machine(
|
||||||
|
'00000000-0000-0000-0000-000000000000',
|
||||||
|
instance_uuid='00000000-0000-0000-0000-000000000002'
|
||||||
|
)
|
||||||
|
self.assertIsNotNone(update_dict['changes'])
|
||||||
|
self.assertEqual('/instance_uuid', update_dict['changes'][0])
|
||||||
|
self.assertTrue(mock_patch.called)
|
||||||
|
mock_patch.assert_called_with(
|
||||||
|
'00000000-0000-0000-0000-000000000000',
|
||||||
|
expected_patch)
|
||||||
|
|
||||||
|
@mock.patch.object(shade.OperatorCloud, 'ironic_client')
|
||||||
|
@mock.patch.object(shade.OperatorCloud, 'patch_machine')
|
||||||
|
def test_update_machine_patch_update_properties(self, mock_patch,
|
||||||
|
mock_client):
|
||||||
|
class client_return_value:
|
||||||
|
uuid = '00000000-0000-0000-0000-000000000000'
|
||||||
|
properties = None
|
||||||
|
|
||||||
|
expected_patch = [
|
||||||
|
dict(
|
||||||
|
op='replace',
|
||||||
|
path='/properties',
|
||||||
|
value=dict(var='fake')
|
||||||
|
)]
|
||||||
|
|
||||||
|
mock_client.node.get.return_value = client_return_value
|
||||||
|
|
||||||
|
update_dict = self.cloud.update_machine(
|
||||||
|
'00000000-0000-0000-0000-000000000000',
|
||||||
|
properties=dict(var="fake")
|
||||||
|
)
|
||||||
|
self.assertIsNotNone(update_dict['changes'])
|
||||||
|
self.assertEqual('/properties', update_dict['changes'][0])
|
||||||
|
self.assertTrue(mock_patch.called)
|
||||||
|
mock_patch.assert_called_with(
|
||||||
|
'00000000-0000-0000-0000-000000000000',
|
||||||
|
expected_patch)
|
||||||
|
|
||||||
@mock.patch.object(shade.OperatorCloud, 'ironic_client')
|
@mock.patch.object(shade.OperatorCloud, 'ironic_client')
|
||||||
def test_register_machine(self, mock_client):
|
def test_register_machine(self, mock_client):
|
||||||
class fake_node:
|
class fake_node:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user