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:
Julia Kreger 2015-04-15 13:16:30 -04:00
parent 2318c033e2
commit 6ac348dd13
3 changed files with 340 additions and 0 deletions

View File

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

View File

@ -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)

View File

@ -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: