Skip to content

Commit 336ef36

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Handle missing fields building storage model XML or list"
2 parents 9c21724 + 7e7a31a commit 336ef36

4 files changed

Lines changed: 69 additions & 12 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
fixes:
3+
- |
4+
Fixed the errors showing in the decision engine logs when generating the
5+
xml representation of the storage model when using some storage backends
6+
other than lvms (like ceph or nfs). Those drivers do not report pool
7+
fields like total_volumes or free_capacity_gb that the lvm driver does,
8+
so assuming those fields are always reported by the cinder api calls is
9+
wrong.
10+
11+
See: https://bugs.launchpad.net/watcher/+bug/2121147 for more details.

‎watcher/decision_engine/model/element/base.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,16 +50,19 @@ def accept(self, visitor):
5050

5151
def as_xml_element(self):
5252
sorted_fieldmap = []
53+
element_name = self.__class__.__name__
5354
for field in self.fields:
5455
try:
5556
value = str(self[field])
5657
sorted_fieldmap.append((field, value))
58+
except NotImplementedError:
59+
LOG.debug("Attribute %s for object %s: %s is not provided",
60+
field, element_name, self)
5761
except Exception as exc:
5862
LOG.exception(exc)
5963

6064
attrib = collections.OrderedDict(sorted_fieldmap)
6165

62-
element_name = self.__class__.__name__
6366
instance_el = etree.Element(element_name, attrib=attrib)
6467

6568
return instance_el

‎watcher/decision_engine/model/model_root.py‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,14 @@ def to_list(self):
268268
in_dict = {}
269269
for field in cn.fields:
270270
new_name = "node_"+str(field)
271-
in_dict[new_name] = cn[field]
271+
try:
272+
in_dict[new_name] = cn[field]
273+
# NotImplementedError means field not assigned
274+
# which is really not an issue as it can be an
275+
# optional field
276+
except NotImplementedError:
277+
LOG.debug("Attribute %s for Compute: %s is not provided",
278+
field, cn.hostname)
272279
node_instances = self.get_node_instances(cn)
273280
if not node_instances:
274281
deep_in_dict = in_dict.copy()
@@ -277,7 +284,11 @@ def to_list(self):
277284
for instance in sorted(node_instances, key=lambda x: x.uuid):
278285
for field in instance.fields:
279286
new_name = "server_"+str(field)
280-
in_dict[new_name] = instance[field]
287+
try:
288+
in_dict[new_name] = instance[field]
289+
except NotImplementedError:
290+
LOG.debug("Attribute %s for Instance: %s is not "
291+
"provided", field, instance.uuid)
281292
if in_dict != {}:
282293
deep_in_dict = in_dict.copy()
283294
ret_list.append(deep_in_dict)

‎watcher/tests/unit/decision_engine/model/test_model.py‎

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,36 +65,68 @@ def test_build_model_from_xml(self):
6565

6666
@mock.patch.object(model_root.ModelRoot, 'get_all_compute_nodes')
6767
@mock.patch.object(model_root.ModelRoot, 'get_node_instances')
68-
def test_get_model_to_list(self, mock_instances, mock_nodes):
69-
fake_compute_node = mock.MagicMock(
70-
uuid='fake_node_uuid',
71-
fields=['uuid'])
72-
fake_instance = mock.MagicMock(
73-
uuid='fake_instance_uuid',
74-
fields=['uuid'])
68+
@mock.patch('watcher.decision_engine.model.model_root.LOG')
69+
def test_get_model_to_list(self, mock_log, mock_instances, mock_nodes):
70+
fake_compute_node = element.ComputeNode(
71+
uuid='fake_node_uuid', hostname="hostname0")
72+
fake_instance = element.Instance(
73+
uuid='fake_instance_uuid')
7574

7675
mock_nodes.return_value = {'fake_node_uuid': fake_compute_node}
7776
mock_instances.return_value = [fake_instance]
7877

79-
expected_keys = ['server_uuid', 'node_uuid']
78+
expected_keys = [
79+
'server_uuid', 'node_uuid', 'node_state', 'node_hostname',
80+
'node_status', 'server_flavor_extra_specs', 'server_locked',
81+
'server_pinned_az', 'server_state', 'server_watcher_exclude'
82+
]
8083

8184
result = model_root.ModelRoot().to_list()
8285
self.assertEqual(1, len(result))
8386

8487
result_keys = result[0].keys()
8588
self.assertEqual(sorted(expected_keys), sorted(result_keys))
8689

90+
mock_log.debug.assert_any_call(
91+
'Attribute %s for Instance: %s is not provided', 'name',
92+
'fake_instance_uuid'
93+
)
94+
mock_log.debug.assert_any_call(
95+
'Attribute %s for Compute: %s is not provided', 'memory',
96+
'hostname0'
97+
)
98+
8799
# test compute node has no instance
88100
mock_instances.return_value = []
89101

90-
expected_keys = ['node_uuid']
102+
expected_keys = ['node_hostname', 'node_status',
103+
'node_state', 'node_uuid']
91104

92105
result = model_root.ModelRoot().to_list()
93106
self.assertEqual(1, len(result))
94107

95108
result_keys = result[0].keys()
96109
self.assertEqual(expected_keys, list(result_keys))
97110

111+
def test_model_to_list_all_fields(self):
112+
struct_str = self.load_data('scenario_1.xml')
113+
model = model_root.ModelRoot.from_xml(struct_str)
114+
model_list = model.to_list()
115+
self.assertIsInstance(model_list, list)
116+
self.assertEqual(len(model_list), 8)
117+
self.assertEqual(
118+
model_list[0]['server_uuid'],
119+
'd000ef1f-dc19-4982-9383-087498bfde03')
120+
121+
def test_model_to_xml_all_fields(self):
122+
struct_str = self.load_data('scenario_2_with_metrics.xml')
123+
124+
model = model_root.ModelRoot.from_xml(struct_str)
125+
model_xml = model.to_xml()
126+
self.assertIsInstance(model_xml, str)
127+
self.assertIn('Instance', model_xml)
128+
self.assertIn('Compute', model_xml)
129+
98130
def test_get_node_by_instance_uuid(self):
99131
model = model_root.ModelRoot()
100132
uuid_ = f"{uuidutils.generate_uuid()}"

0 commit comments

Comments
 (0)