From 3945fa4c2bda59ceac7c08a1228c09970f4685eb Mon Sep 17 00:00:00 2001
From: Ruby Loo <rloo@verizonmedia.com>
Date: Tue, 25 Aug 2020 18:11:04 +0000
Subject: [PATCH] driver_internal_info in provision notifications

Adds the node's driver_internal_info to the
baremetal.node.provision_set.* notifications. The
driver_internal_info includes useful information
such as deploy and clean steps.

Change-Id: I98784f72e6f93cbc602753ef2da0dbef5ad2c9cf
Story: #2008054
---
 ironic/objects/node.py                        |  7 +-
 ironic/objects/notification.py                | 12 +--
 ironic/tests/unit/objects/test_node.py        |  2 +
 .../tests/unit/objects/test_notification.py   | 83 +++++++++++++++++++
 ironic/tests/unit/objects/test_objects.py     |  2 +-
 ...driver_internal_info-3012f9834b6ade6b.yaml |  5 ++
 6 files changed, 103 insertions(+), 8 deletions(-)
 create mode 100644 releasenotes/notes/notifications_driver_internal_info-3012f9834b6ade6b.yaml

diff --git a/ironic/objects/node.py b/ironic/objects/node.py
index 0392ee283c..013d1b50d6 100644
--- a/ironic/objects/node.py
+++ b/ironic/objects/node.py
@@ -916,13 +916,16 @@ class NodeSetProvisionStatePayload(NodePayload):
     # Version 1.13: Parent NodePayload version 1.13
     # Version 1.14: Parent NodePayload version 1.14
     # Version 1.15: Parent NodePayload version 1.15
-    VERSION = '1.15'
+    # Version 1.16: add driver_internal_info
+    VERSION = '1.16'
 
     SCHEMA = dict(NodePayload.SCHEMA,
-                  **{'instance_info': ('node', 'instance_info')})
+                  **{'instance_info': ('node', 'instance_info'),
+                     'driver_internal_info': ('node', 'driver_internal_info')})
 
     fields = {
         'instance_info': object_fields.FlexibleDictField(nullable=True),
+        'driver_internal_info': object_fields.FlexibleDictField(nullable=True),
         'event': object_fields.StringField(nullable=True),
         'previous_provision_state': object_fields.StringField(nullable=True),
         'previous_target_provision_state':
diff --git a/ironic/objects/notification.py b/ironic/objects/notification.py
index 076396c417..e8ac9b667e 100644
--- a/ironic/objects/notification.py
+++ b/ironic/objects/notification.py
@@ -188,11 +188,13 @@ class NotificationPublisher(base.IronicObject):
 def mask_secrets(payload):
     """Remove secrets from payload object."""
     mask = '******'
+
+    dict_fields = ['instance_info', 'driver_info', 'driver_internal_info']
+    for f in dict_fields:
+        if hasattr(payload, f):
+            masked = strutils.mask_dict_password(getattr(payload, f), mask)
+            setattr(payload, f, masked)
+
     if hasattr(payload, 'instance_info'):
-        payload.instance_info = strutils.mask_dict_password(
-            payload.instance_info, mask)
         if 'image_url' in payload.instance_info:
             payload.instance_info['image_url'] = mask
-    if hasattr(payload, 'driver_info'):
-        payload.driver_info = strutils.mask_dict_password(
-            payload.driver_info, mask)
diff --git a/ironic/tests/unit/objects/test_node.py b/ironic/tests/unit/objects/test_node.py
index 707d09e8d7..dd23995b9d 100644
--- a/ironic/tests/unit/objects/test_node.py
+++ b/ironic/tests/unit/objects/test_node.py
@@ -1282,6 +1282,8 @@ class TestNodePayloads(db_base.DbTestCase):
                                                        'DEPLOYING', 'DEPLOY')
         self._test_node_payload(payload)
         self.assertEqual(self.node.instance_info, payload.instance_info)
+        self.assertEqual(self.node.driver_internal_info,
+                         payload.driver_internal_info)
         self.assertEqual('DEPLOY', payload.event)
         self.assertEqual('AVAILABLE', payload.previous_provision_state)
         self.assertEqual('DEPLOYING', payload.previous_target_provision_state)
diff --git a/ironic/tests/unit/objects/test_notification.py b/ironic/tests/unit/objects/test_notification.py
index 82c2a8dd92..255845debf 100644
--- a/ironic/tests/unit/objects/test_notification.py
+++ b/ironic/tests/unit/objects/test_notification.py
@@ -36,6 +36,16 @@ class TestNotificationBase(test_base.TestCase):
             'fake_field_1': fields.StringField(nullable=True),
         }
 
+    @base.IronicObjectRegistry.register_if(False)
+    class TestObjectMaskSecrets(base.IronicObject):
+        VERSION = '1.0'
+        fields = {
+            'instance_info': fields.FlexibleDictField(nullable=True),
+            'driver_info': fields.FlexibleDictField(nullable=True),
+            'driver_internal_info': fields.FlexibleDictField(nullable=True),
+            'some_dict': fields.FlexibleDictField(nullable=True),
+        }
+
     @base.IronicObjectRegistry.register_if(False)
     class TestNotificationPayload(notification.NotificationPayloadBase):
         VERSION = '1.0'
@@ -61,6 +71,25 @@ class TestNotificationBase(test_base.TestCase):
             'fake_field': fields.StringField()
         }
 
+    @base.IronicObjectRegistry.register_if(False)
+    class TestNotificationPayloadMaskSecrets(
+            notification.NotificationPayloadBase):
+        VERSION = '1.0'
+
+        SCHEMA = {
+            'instance_info': ('test_obj', 'instance_info'),
+            'driver_info': ('test_obj', 'driver_info'),
+            'driver_internal_info': ('test_obj', 'driver_internal_info'),
+            'some_dict': ('test_obj', 'some_dict'),
+        }
+
+        fields = {
+            'instance_info': fields.FlexibleDictField(nullable=True),
+            'driver_info': fields.FlexibleDictField(nullable=True),
+            'driver_internal_info': fields.FlexibleDictField(nullable=True),
+            'some_dict': fields.FlexibleDictField(nullable=True),
+        }
+
     @base.IronicObjectRegistry.register_if(False)
     class TestNotification(notification.NotificationBase):
         VERSION = '1.0'
@@ -281,3 +310,57 @@ class TestNotificationBase(test_base.TestCase):
         event_type = notification.EventType(
             object='test_object', action='test', status='start')
         self.assertRaises(ValueError, make_status_invalid)
+
+    def test_mask_secrets_not_affected(self):
+        payload = self.TestNotificationPayload(an_extra_field='extra',
+                                               an_optional_field=1)
+        payload.populate_schema(test_obj=self.fake_obj)
+        notification.mask_secrets(payload)
+        self.assertEqual('extra', payload.an_extra_field)
+        self.assertEqual(1, payload.an_optional_field)
+        self.assertEqual(self.fake_obj.fake_field_1, payload.fake_field_a)
+        self.assertEqual(self.fake_obj.fake_field_2, payload.fake_field_b)
+
+    def test_mask_secrets_no_secrets(self):
+        instance_info = {'inst1': 'v1'}
+        driver_info = {'driver_i1': 'd1'}
+        driver_internal_info = {'driver_int1': 'dii1'}
+        some_dict = {'key1': 'v1'}
+        test_obj = self.TestObjectMaskSecrets(
+            instance_info=instance_info,
+            driver_info=driver_info,
+            driver_internal_info=driver_internal_info,
+            some_dict=some_dict)
+        payload = self.TestNotificationPayloadMaskSecrets()
+        payload.populate_schema(test_obj=test_obj)
+        notification.mask_secrets(payload)
+        self.assertEqual(test_obj.instance_info, payload.instance_info)
+        self.assertEqual(test_obj.driver_info, payload.driver_info)
+        self.assertEqual(test_obj.driver_internal_info,
+                         payload.driver_internal_info)
+        self.assertEqual(test_obj.some_dict, payload.some_dict)
+
+    def test_mask_secrets_has_secrets(self):
+        instance_info = {'configdrive': 'somestuffhere',
+                         'image_url': 'http://image_to_fetch'}
+        driver_info = {'password': 'some password'}
+        driver_internal_info = {'agent_secret_token': '123532234145'}
+        some_dict = {'password': 'another password'}
+        test_obj = self.TestObjectMaskSecrets(
+            instance_info=instance_info,
+            driver_info=driver_info,
+            driver_internal_info=driver_internal_info,
+            some_dict=some_dict)
+        payload = self.TestNotificationPayloadMaskSecrets()
+        payload.populate_schema(test_obj=test_obj)
+        notification.mask_secrets(payload)
+        self.assertNotEqual(test_obj.instance_info, payload.instance_info)
+        self.assertEqual('******', payload.instance_info['configdrive'])
+        self.assertEqual('******', payload.instance_info['image_url'])
+        self.assertNotEqual(test_obj.driver_info, payload.driver_info)
+        self.assertEqual('******', payload.driver_info['password'])
+        self.assertNotEqual(test_obj.driver_internal_info,
+                            payload.driver_internal_info)
+        self.assertEqual('******',
+                         payload.driver_internal_info['agent_secret_token'])
+        self.assertEqual(test_obj.some_dict, payload.some_dict)
diff --git a/ironic/tests/unit/objects/test_objects.py b/ironic/tests/unit/objects/test_objects.py
index ffdf375fda..3852a9abd3 100644
--- a/ironic/tests/unit/objects/test_objects.py
+++ b/ironic/tests/unit/objects/test_objects.py
@@ -692,7 +692,7 @@ expected_object_fingerprints = {
     'NodeCorrectedPowerStatePayload': '1.15-59a224a9191cdc9f1acc2e0dcd2d3adb',
     'NodeSetProvisionStateNotification':
         '1.0-59acc533c11d306f149846f922739c15',
-    'NodeSetProvisionStatePayload': '1.15-488a3d62a0643d17e288ecf89ed5bbb4',
+    'NodeSetProvisionStatePayload': '1.16-c5a8eea43c514baf721fc61ce5d9d5a4',
     'VolumeConnector': '1.0-3e0252c0ab6e6b9d158d09238a577d97',
     'VolumeTarget': '1.0-0b10d663d8dae675900b2c7548f76f5e',
     'ChassisCRUDNotification': '1.0-59acc533c11d306f149846f922739c15',
diff --git a/releasenotes/notes/notifications_driver_internal_info-3012f9834b6ade6b.yaml b/releasenotes/notes/notifications_driver_internal_info-3012f9834b6ade6b.yaml
new file mode 100644
index 0000000000..aaf39ddd79
--- /dev/null
+++ b/releasenotes/notes/notifications_driver_internal_info-3012f9834b6ade6b.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - |
+    Adds ``driver_internal_info`` field to the node-related notification
+    ``baremetal.node.provision_set.*``, new payload version 1.16.