diff --git a/requirements.txt b/requirements.txt
index c0a25f569..cf25ff199 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,6 @@
 pbr>=0.5.21,<1.0
 
+jsonpatch
 os-client-config>=0.7.0
 six
 
diff --git a/shade/__init__.py b/shade/__init__.py
index 7cceea2e6..070c5cad3 100644
--- a/shade/__init__.py
+++ b/shade/__init__.py
@@ -24,6 +24,7 @@ import glanceclient
 import glanceclient.exc
 from ironicclient import client as ironic_client
 from ironicclient import exceptions as ironic_exceptions
+import jsonpatch
 from keystoneclient import auth as ksc_auth
 from keystoneclient import session as ksc_session
 from keystoneclient import client as keystone_client
@@ -2294,6 +2295,100 @@ class OperatorCloud(OpenStackCloud):
                 "Error updating machine via patch operation. node: %s. "
                 "%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):
         try:
             ifaces = self.ironic_client.node.validate(uuid)
diff --git a/shade/tests/unit/test_shade.py b/shade/tests/unit/test_shade.py
index 98f49b520..4d436e617 100644
--- a/shade/tests/unit/test_shade.py
+++ b/shade/tests/unit/test_shade.py
@@ -211,6 +211,250 @@ class TestShadeOperator(base.TestCase):
         self.cloud.patch_machine(node_id, patch)
         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')
     def test_register_machine(self, mock_client):
         class fake_node: