Merge ~raharper/curtin:fix/tolerate-missing-probe-data-keys into curtin:master

Proposed by Ryan Harper
Status: Merged
Approved by: Ryan Harper
Approved revision: 9a329203cd4e00938744d4c64009803012cbc61e
Merge reported by: Server Team CI bot
Merged at revision: not available
Proposed branch: ~raharper/curtin:fix/tolerate-missing-probe-data-keys
Merge into: curtin:master
Diff against target: 165 lines (+48/-19)
2 files modified
curtin/storage_config.py (+19/-11)
tests/unittests/test_storage_config.py (+29/-8)
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Approve
Michael Hudson-Doyle Approve
curtin developers Pending
Review via email: mp+375463@code.launchpad.net

Commit message

block-discover: handle partial probe data

The probe_data provided to curtin may only include a subset of
the types of data that curtin knows how to parse. Currently if
one of the types is not present in the probe_data, curtin raises
Exceptions which prevent callers from getting any storage config
from curtin. This branch allows curtin to handle missing data
and will log the errors but not raise Exceptions, leaving the
caller to handle and decide what to do with the storage config
produced.

LP: #1852351

To post a comment you must log in.
Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

Thanks, just a couple of stray debugging prints but otherwise looks good to me.

review: Approve
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Ryan Harper (raharper) wrote :

Thanks for the review. Dropping the debug prints.

9a32920... by Ryan Harper

Drop debug prints

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/curtin/storage_config.py b/curtin/storage_config.py
2index 03cfb4e..6177f48 100644
3--- a/curtin/storage_config.py
4+++ b/curtin/storage_config.py
5@@ -389,15 +389,15 @@ class ProbertParser(object):
6 data = {}
7 self.class_data = data
8 else:
9- raise ValueError('probe_data missing %s data' %
10- self.probe_data_key)
11+ LOG.warning('probe_data missing %s data', self.probe_data_key)
12+ self.class_data = {}
13
14 # We keep a reference to the blockdev_data on the superclass
15 # as each specific parser has common needs to reference
16 # this data separate from the BlockdevParser class.
17- self.blockdev_data = self.probe_data.get('blockdev')
18+ self.blockdev_data = self.probe_data.get('blockdev', {})
19 if not self.blockdev_data:
20- raise ValueError('probe_data missing valid "blockdev" data')
21+ LOG.warning('probe_data missing valid "blockdev" data')
22
23 def parse(self):
24 raise NotImplementedError()
25@@ -575,7 +575,8 @@ class BcacheParser(ProbertParser):
26 msg = ('Invalid "blockdev" value for cache device '
27 'uuid=%s' % cset_uuid)
28 if not cset_uuid:
29- raise ValueError(msg)
30+ LOG.warning(msg)
31+ return None
32
33 for devuuid, config in cache_data.items():
34 cache = _sb_get(config, 'cset.uuid')
35@@ -588,6 +589,8 @@ class BcacheParser(ProbertParser):
36 by_uuid = '/dev/bcache/by-uuid/' + uuid
37 label = _sb_get(backing_data, 'dev.label')
38 for devname, data in blockdev_data.items():
39+ if not devname:
40+ continue
41 if devname.startswith('/dev/bcache'):
42 # DEVLINKS is a space separated list
43 devlinks = data.get('DEVLINKS', '').split()
44@@ -595,7 +598,7 @@ class BcacheParser(ProbertParser):
45 return devname
46 if label:
47 return label
48- raise ValueError('Failed to find bcache %s ' % (by_uuid))
49+ LOG.warning('Failed to find bcache %s ' % (by_uuid))
50
51 def _cache_mode(dev_data):
52 # "1 [writeback]" -> "writeback"
53@@ -605,12 +608,14 @@ class BcacheParser(ProbertParser):
54
55 return None
56
57+ if not self.blockdev_data:
58+ return None
59+
60 backing_device = backing_data.get('blockdev')
61 cache_device = _find_cache_device(backing_data, self.caching)
62 cache_mode = _cache_mode(backing_data)
63- bcache_name = os.path.basename(
64- _find_bcache_devname(backing_uuid, backing_data,
65- self.blockdev_data))
66+ bcache_name = os.path.basename(_find_bcache_devname(backing_uuid,
67+ backing_data, self.blockdev_data))
68 bcache_entry = {'type': 'bcache', 'id': 'disk-%s' % bcache_name,
69 'name': bcache_name}
70
71@@ -1181,7 +1186,7 @@ class ZfsParser(ProbertParser):
72 return (zpool_configs + zfs_configs, errors)
73
74
75-def extract_storage_config(probe_data):
76+def extract_storage_config(probe_data, strict=False):
77 """ Examine a probert storage dictionary and extract a curtin
78 storage configuration that would recreate all of the
79 storage devices present in the provided data.
80@@ -1233,7 +1238,10 @@ def extract_storage_config(probe_data):
81 for e in errors:
82 LOG.exception('Validation error: %s\n' % e)
83 if len(errors) > 0:
84- raise RuntimeError("Extract storage config does not validate.")
85+ errmsg = "Extract storage config does not validate."
86+ LOG.warning(errmsg)
87+ if strict:
88+ raise RuntimeError(errmsg)
89
90 # build and merge probed data into a valid storage config by
91 # generating a config tree for each item in the probed data
92diff --git a/tests/unittests/test_storage_config.py b/tests/unittests/test_storage_config.py
93index d4113ea..f01f440 100644
94--- a/tests/unittests/test_storage_config.py
95+++ b/tests/unittests/test_storage_config.py
96@@ -1,4 +1,5 @@
97 # This file is part of curtin. See LICENSE file for copyright and license info.
98+import copy
99 import json
100 from .helpers import CiTestCase, skipUnlessJsonSchema
101 from curtin import storage_config
102@@ -81,16 +82,15 @@ class TestProbertParser(CiTestCase):
103 self.assertDictEqual(probe_data['blockdev'],
104 getattr(bdp, 'blockdev_data'))
105
106- def test_probert_parser_missing_required_probe_data_key_raises(self):
107- """ ProbertParser raises ValueError when probe_data_key_data gone. """
108+ def test_probert_parser_handles_missing_required_probe_data_key(self):
109+ """ ProbertParser handles missing probe_data_key_data. """
110 key = self.random_string()
111 probe_data = {'blockdev': {self.random_string(): self.random_string()}}
112
113 class bdparser(baseparser):
114 probe_data_key = key
115
116- with self.assertRaises(ValueError):
117- bdparser(probe_data)
118+ self.assertIsNotNone(bdparser(probe_data))
119
120
121 def _get_data(datafile):
122@@ -132,11 +132,13 @@ class TestBcacheParser(CiTestCase):
123 self.assertDictEqual(self.probe_data['bcache']['caching'],
124 bcachep.caching)
125
126- def test_bcache_parse_raise_err_no_blockdev_data(self):
127- """ BcacheParser raises ValueError on missing 'blockdev' dict."""
128+ def test_bcache_parse_tolerates_missing_blockdev_data(self):
129+ """ BcacheParser ValueError on missing 'blockdev' dict."""
130 del(self.probe_data['blockdev'])
131- with self.assertRaises(ValueError):
132- BcacheParser(self.probe_data)
133+ b = BcacheParser(self.probe_data)
134+ (configs, errors) = b.parse()
135+ self.assertEqual([], configs)
136+ self.assertEqual([], errors)
137
138 @skipUnlessJsonSchema()
139 def test_bcache_parse_extracts_bcache(self):
140@@ -716,6 +718,25 @@ class TestExtractStorageConfig(CiTestCase):
141 'type': 'disk'}]}}, extracted)
142
143 @skipUnlessJsonSchema()
144+ def test_probe_handles_missing_keys(self):
145+ """ verify extract handles missing probe_data keys """
146+ for missing_key in self.probe_data.keys():
147+ probe_data = copy.deepcopy(self.probe_data)
148+ del probe_data[missing_key]
149+ extracted = storage_config.extract_storage_config(probe_data)
150+ if missing_key != 'blockdev':
151+ self.assertEqual(
152+ {'storage':
153+ {'version': 1,
154+ 'config': [{'id': 'disk-sda', 'path': '/dev/sda',
155+ 'serial': 'QEMU_HARDDISK_QM00001',
156+ 'type': 'disk'}]}}, extracted)
157+ else:
158+ # empty config without blockdev data
159+ self.assertEqual({'storage': {'config': [], 'version': 1}},
160+ extracted)
161+
162+ @skipUnlessJsonSchema()
163 def test_find_all_multipath(self):
164 """ verify probed multipath paths are included in config. """
165 self.probe_data = _get_data('probert_storage_multipath.json')

Subscribers

People subscribed via source and target branches