Merge ~bitfehler/cloud-init:bitfehler/load_seed into cloud-init:master
- Git
- lp:~bitfehler/cloud-init
- bitfehler/load_seed
- Merge into master
Status: | Needs review |
---|---|
Proposed branch: | ~bitfehler/cloud-init:bitfehler/load_seed |
Merge into: | cloud-init:master |
Diff against target: |
272 lines (+115/-58) 5 files modified
cloudinit/sources/DataSourceCloudStack.py (+9/-4) cloudinit/sources/DataSourceNoCloud.py (+18/-8) cloudinit/sources/DataSourceOVF.py (+3/-2) cloudinit/util.py (+23/-40) tests/unittests/test_util.py (+62/-4) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Scott Moser | Needs Information | ||
Server Team CI bot | continuous-integration | Approve | |
Ryan Harper | Needs Information | ||
Review via email: mp+369814@code.launchpad.net |
Commit message
Support network-config in nocloud(-net) data source
This proposal adds a new function `load_seed` that can be used as a replacement for `read_seeded` and `read_optional_
The nocloud(-net) data source is updated to make use of it, adding support for network-config in user-supplied seeds. This addresses the following bugs:
https:/
https:/
The OVH and CloudStack datasources are also updated to use the new function, but without changing the data source semantics. Finally, as all previous callers have been updated, the old functions `read_seeded` and `read_optional_
Description of the change
Ryan Harper (raharper) wrote : | # |
Thanks for sending in the merge proposal.
On Mon, Jul 8, 2019 at 7:55 AM Conrad Hoffmann <email address hidden> wrote:
> Hi,
>
> I previously wrote a message to the mailing list, but without any response
> so far, so I figured I'd write some more code and try this way. Happy about
> any suggestions for improvements or guidance for other approaches.
>
> Some notes right away:
> - The "xenial" tox env does not work for me, even on clean master, is
> this a known issue?
>
That's not know, so if you want to file a bug with your output/failure etc
we can take a look.
> - I thought it would be nice to make a holistic change (i.e. changing all
> callers to the new function), but in fact I do not have access to a test
> setup for the OVH or CloudStack data sources. How is this usually handled?
> Is it preferable to not touch any data sources you can not test? Or are
> there means for this to be tested before it lands in a release?
>
We typically do functional behavior changes only in the development release
and then disable them in older releases to prevent behavioral changes.
Depending on the change, we may or maynot test directly on those platforms;
most platform changes are driven from platform owners. That doesn't
preclude such a change so we'd take it case-by-case.
>
> Thanks a bunch,
> Conrad
> --
>
> https:/
> Your team cloud-init commiters is requested to review the proposed merge
> of ~bitfehler/
>
> _______
> Mailing list: https:/
> Post to : <email address hidden>
> Unsubscribe : https:/
> More help : https:/
>
Conrad Hoffmann (bitfehler) wrote : | # |
> Thanks for sending in the merge proposal.
>
>
>
> On Mon, Jul 8, 2019 at 7:55 AM Conrad Hoffmann <email address hidden> wrote:
>
> > Hi,
> >
> > I previously wrote a message to the mailing list, but without any response
> > so far, so I figured I'd write some more code and try this way. Happy about
> > any suggestions for improvements or guidance for other approaches.
> >
> > Some notes right away:
> > - The "xenial" tox env does not work for me, even on clean master, is
> > this a known issue?
> >
>
> That's not know, so if you want to file a bug with your output/failure etc
> we can take a look.
Will do.
> > - I thought it would be nice to make a holistic change (i.e. changing all
> > callers to the new function), but in fact I do not have access to a test
> > setup for the OVH or CloudStack data sources. How is this usually handled?
> > Is it preferable to not touch any data sources you can not test? Or are
> > there means for this to be tested before it lands in a release?
> >
>
> We typically do functional behavior changes only in the development release
> and then disable them in older releases to prevent behavioral changes.
> Depending on the change, we may or maynot test directly on those platforms;
> most platform changes are driven from platform owners. That doesn't
> preclude such a change so we'd take it case-by-case.
What would that mean for this PR? Should I leave it as is? Or should I change it so that only the nocloud data source uses the new function? I don't mind this waiting for the next release, I do understand it is a slight change in behavior.
Thanks again,
Conrad
> > Thanks a bunch,
> > Conrad
> > --
> >
> > https:/
> init/+merge/369814
> > Your team cloud-init commiters is requested to review the proposed merge
> > of ~bitfehler/
> >
> > _______
> > Mailing list: https:/
> > Post to : <email address hidden>
> > Unsubscribe : https:/
> > More help : https:/
> >
Ryan Harper (raharper) wrote : | # |
> What would that mean for this PR? Should I leave it as is? Or should I
> change it so that only the nocloud data source uses the new function?
> I don't mind this waiting for the next release, I do understand it is
> a slight change in behavior.
In case you missed, I had some comments you can see below in the diff output
about the unittests. Let's address those first.
As for this branch; it appears to me to be a super-set of capabilities
more than a change in existing behavior. That is, I expect the datasource
given the same inputs to produce the same behavior;
We are likely to take the updated behavior into master, and include that in
the Ubuntu E release; it could be disabled by default on previous releases
but allowing users to opt-in to the new behavior.
Conrad Hoffmann (bitfehler) : | # |
Ryan Harper (raharper) wrote : | # |
Thanks for updating the unittests. I've done some additional review and added some inline comments in the diff below. Please take a look.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:90186f8113d
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild:
https:/
Conrad Hoffmann (bitfehler) wrote : | # |
Hi,
sorry it took so long, I think I addressed all your comments in the latest commit.
Cheers,
Conrad
Ryan Harper (raharper) wrote : | # |
Thanks for the update. I've one more question in-line; The new unittests look great.
- 8ba83bb... by Conrad Hoffmann <email address hidden>
-
Return empty dict on invalid yaml in load_seed()
Also add a test for this to emphasize that this is the expected
behavior.
Conrad Hoffmann (bitfehler) wrote : | # |
Good catch, I fixed that and also added a test for this for the future.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:8ba83bb8912
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild:
https:/
Scott Moser (smoser) wrote : | # |
I'm not sure that I understand the intent here.
The subject line says:
Support network-config in nocloud(-net) data source
But nocloud-net cannot ever really provide network-data.
It simply runs too late. No "network" datasources can apply network config as before they have the opportunity to read information the network is already up.
At this point in cluod-init none of the renderers are idempotent. They have to be written before networking is brought up by the OS.
So what we *can* accomplish now (and maybe what this does?) is:
a.) datasourceNoCloud could read networking files from a 'seedfrom'. It already can read network-config from /var/lib/
b.) dataSourceNoCloud could use ephemeral networking to attempt 'seedfrom' of http/https.
But what we can't (I don't think) do:
c.) dataSourceNoClo
Last thing... In your commit message, if you believe that you fix those bugs,
please use:
LP: #XXXXX
LP: #YYYYY
then all the tools and such will read that and handle closing/fixing bugs.
Ryan Harper (raharper) wrote : | # |
Looking at the first bug:
https:/
I understand it to:
1) boot NoCloud (fallback networking is utilized)
2) at cloud-init init stage, NoCloud-Net is loaded, which now supports reading URLs
including network-config files from URL.
3) during init stage the .network_config property on NoCloudNet is provided via loading the seed and reading the URLs
4) cloud-init init stage will render an updated network-config to the system.
This leaves a system with a fallback network config up (including dhclient) but the on-disk network-config is from the URL. On subsequent boots, it will utilize the network-config from the seed URL.
This appears to resolve the request; though it's not completely clear without more feedback from the submitter w.r.t the use-case "boot an image in a new environment without running cloud-init a second time".
A more complete fix would be to modify NoCloud to use EphemeralDHCP to bring up networking if a seed prefix included URLs (as Scott suggests in (b)).
The second bug suggests exactly (b).
I don't think it would be too much more effort to pull in the EphemeralDHCP bits.
Conrad Hoffmann (bitfehler) wrote : | # |
Thank you for the comments. I indeed somewhat misjudged the relation of the nocloud and nocloud-net data sources.
To clarify, my main objective was a) as mentioned by Scott and also the hope to maybe unify some of the approaches to reading the seed (like getting rid of read_optional_seed and potentially also pathprefix2dict in a follow-up).
However, I also think b) would be valuable. I am happy to look at adding ephemeral DHCP, but maybe in a different MR?
I now understand that this certainly doesn't fix https:/
Would you be ok with merging this if I update the description and commit messages to reflect the narrower scope?
Scott Moser (smoser) wrote : | # |
"if the nocloud-net does render the "new" network config even though the network is already up then that's fine."
Its *not* fine. network datasources should not render network information at this point. Doing so will only confuse things and create a system that "maybe works". We cannot really ensure that it will work, and we're not going to support if it didn't.
So just make it simpler, and only read network config in the local.
Scott Moser (smoser) wrote : | # |
(and just so i dont sound rude.... thank you for attempts to clean things up. for sure, there have been many iterative ways of doing things over time and you're seeing that here).
Conrad Hoffmann (bitfehler) wrote : | # |
No worries, I understand your point. I guess I am just surprised that that bug report is in state triaged then. It should probably be closed as won't fix with a link to this discussion maybe?
I'll update this MR accordingly.
Scott Moser (smoser) wrote : | # |
In the end, it doesn't really matter if its nocloud-net or nocloud.
If nocloud supports reading 'seedfrom' via the ephemeral network, then you get the exact same thing you would have gotten if nocloud-net could actually work like that.
Scott Moser (smoser) wrote : | # |
So lets do this...
stage 1) support seedfrom of local paths to get the network config
stage 2) use ephemeral networking to nocloud-net if seedfrom is http or https.
Sound reasonable?
- 64ce489... by Conrad Hoffmann <email address hidden>
-
Support seeded network-config in local mode only
Conrad Hoffmann (bitfehler) wrote : | # |
I added another commit which I think should achieve what you described as stage 1), while not allowing a seed network-config in nocloud-net. Let me know what you think.
Unmerged commits
- 64ce489... by Conrad Hoffmann <email address hidden>
-
Support seeded network-config in local mode only
- 8ba83bb... by Conrad Hoffmann <email address hidden>
-
Return empty dict on invalid yaml in load_seed()
Also add a test for this to emphasize that this is the expected
behavior. - efb12b3... by Conrad Hoffmann
-
More kwargs and better exception handling
- 90186f8... by Conrad Hoffmann
-
Improve tests for load_seed
Make test name more descriptive (again) and provide test for URL
handling capabilities. - 5a07ceb... by Conrad Hoffmann
-
Remove read_seeded and read_optional_seed
They are no longer used, mostly replaced by calls to `load_seed`.
- 475b563... by Conrad Hoffmann
-
Port several data sources to use load_seed
Use `load_seed` instead of either `read_seeded` or `read_optional_
seed`
for the following data sources: CloudStack, NoCloud, OVF. This removes
all usage of both of these functions.For the nocloud(-net) data source, this enables support for reading a
network-config from a user-supplied seed. This addresses the following
bugs:https:/
/bugs.launchpad .net/cloud- init/+bug/ 1809180
https://bugs.launchpad .net/cloud- init/+bug/ 1809277 For the other data sources, semantics were not changed, though this
change should facilitate e.g. supporting a seed network-config. - 48fd9c8... by Conrad Hoffmann
-
Add load_seed function to facilitate seed loading
It can handle both local files and remote URLS, and otherwise has an
interface similar to pathprefix2dict, supporting required and optional
files (e.g. to support network-config without requiring it). It parses
meta-data and network-config to YAML, others must be handled by the
caller.Intended to be a replacement for `read_seeded` and `read_optional_
seed`.
Includes some basic tests.
Preview Diff
1 | diff --git a/cloudinit/sources/DataSourceCloudStack.py b/cloudinit/sources/DataSourceCloudStack.py |
2 | index f185dc7..4bc40ae 100644 |
3 | --- a/cloudinit/sources/DataSourceCloudStack.py |
4 | +++ b/cloudinit/sources/DataSourceCloudStack.py |
5 | @@ -110,12 +110,17 @@ class DataSourceCloudStack(sources.DataSource): |
6 | return self.cfg |
7 | |
8 | def _get_data(self): |
9 | - seed_ret = {} |
10 | - if util.read_optional_seed(seed_ret, base=(self.seed_dir + "/")): |
11 | - self.userdata_raw = seed_ret['user-data'] |
12 | - self.metadata = seed_ret['meta-data'] |
13 | + try: |
14 | + # Using `required` wrapped in try because we want both or none |
15 | + seed = util.load_seed(self.seed_dir, |
16 | + required=['meta-data', 'user-data']) |
17 | + self.userdata_raw = seed['user-data'] |
18 | + self.metadata = seed['meta-data'] |
19 | LOG.debug("Using seeded cloudstack data from: %s", self.seed_dir) |
20 | return True |
21 | + except Exception: |
22 | + util.logexc(LOG, 'Failed to read seeded data from %s', |
23 | + self.seed_dir) |
24 | try: |
25 | if not self.wait_for_metadata_service(): |
26 | return False |
27 | diff --git a/cloudinit/sources/DataSourceNoCloud.py b/cloudinit/sources/DataSourceNoCloud.py |
28 | index 474773d..2b158fe 100644 |
29 | --- a/cloudinit/sources/DataSourceNoCloud.py |
30 | +++ b/cloudinit/sources/DataSourceNoCloud.py |
31 | @@ -163,15 +163,23 @@ class DataSourceNoCloud(sources.DataSource): |
32 | LOG.debug("Seed from %s not supported by %s", seedfrom, self) |
33 | return False |
34 | |
35 | - # This could throw errors, but the user told us to do it |
36 | - # so if errors are raised, let them raise |
37 | - (md_seed, ud) = util.read_seeded(seedfrom, timeout=None) |
38 | + # This could fail, but the user told us to do it, |
39 | + # so if it does, abort |
40 | + try: |
41 | + kwargs = {'required': ['user-data', 'meta-data'], |
42 | + 'optional': []} |
43 | + if self.dsmode == sources.DSMODE_LOCAL: |
44 | + kwargs['optional'] = ['network-config'] |
45 | + seed = util.load_seed(seedfrom, timeout=None, **kwargs) |
46 | + except Exception: |
47 | + util.logexc(LOG, 'Failed to read seeded data from %s', |
48 | + seedfrom) |
49 | + return False |
50 | + |
51 | LOG.debug("Using seeded cache data from %s", seedfrom) |
52 | |
53 | # Values in the command line override those from the seed |
54 | - mydata['meta-data'] = util.mergemanydict([mydata['meta-data'], |
55 | - md_seed]) |
56 | - mydata['user-data'] = ud |
57 | + mydata = _merge_new_seed(mydata, seed) |
58 | found.append(seedfrom) |
59 | |
60 | # Now that we have exhausted any other places merge in the defaults |
61 | @@ -357,8 +365,10 @@ def _merge_new_seed(cur, seeded): |
62 | ret['meta-data'] = util.mergemanydict([cur['meta-data'], newmd]) |
63 | |
64 | if seeded.get('network-config'): |
65 | - ret['network-config'] = _maybe_remove_top_network( |
66 | - util.load_yaml(seeded.get('network-config'))) |
67 | + newnc = seeded['network-config'] |
68 | + if not isinstance(newnc, dict): |
69 | + newnc = util.load_yaml(seeded['network-config']) |
70 | + ret['network-config'] = _maybe_remove_top_network(newnc) |
71 | |
72 | if 'user-data' in seeded: |
73 | ret['user-data'] = seeded['user-data'] |
74 | diff --git a/cloudinit/sources/DataSourceOVF.py b/cloudinit/sources/DataSourceOVF.py |
75 | index e7794aa..7ddd063 100644 |
76 | --- a/cloudinit/sources/DataSourceOVF.py |
77 | +++ b/cloudinit/sources/DataSourceOVF.py |
78 | @@ -286,10 +286,11 @@ class DataSourceOVF(sources.DataSource): |
79 | seedfrom, self) |
80 | return False |
81 | |
82 | - (md_seed, ud) = util.read_seeded(seedfrom, timeout=None) |
83 | + seed = util.load_seed(seedfrom, required=['meta-data'], |
84 | + timeout=None) |
85 | LOG.debug("Using seeded cache data from %s", seedfrom) |
86 | |
87 | - md = util.mergemanydict([md, md_seed]) |
88 | + md = util.mergemanydict([md, seed.get('meta-data', {})]) |
89 | found.append(seedfrom) |
90 | |
91 | # Now that we have exhausted any other places merge in the defaults |
92 | diff --git a/cloudinit/util.py b/cloudinit/util.py |
93 | index 0d338ca..6f11dec 100644 |
94 | --- a/cloudinit/util.py |
95 | +++ b/cloudinit/util.py |
96 | @@ -893,22 +893,6 @@ def runparts(dirp, skip_no_exist=True, exe_prefix=None): |
97 | % (len(failed), len(attempted))) |
98 | |
99 | |
100 | -# read_optional_seed |
101 | -# returns boolean indicating success or failure (presense of files) |
102 | -# if files are present, populates 'fill' dictionary with 'user-data' and |
103 | -# 'meta-data' entries |
104 | -def read_optional_seed(fill, base="", ext="", timeout=5): |
105 | - try: |
106 | - (md, ud) = read_seeded(base, ext, timeout) |
107 | - fill['user-data'] = ud |
108 | - fill['meta-data'] = md |
109 | - return True |
110 | - except url_helper.UrlError as e: |
111 | - if e.code == url_helper.NOT_FOUND: |
112 | - return False |
113 | - raise |
114 | - |
115 | - |
116 | def fetch_ssl_details(paths=None): |
117 | ssl_details = {} |
118 | # Lookup in these locations for ssl key/cert files |
119 | @@ -975,34 +959,33 @@ def load_yaml(blob, default=None, allowed=(dict,)): |
120 | return loaded |
121 | |
122 | |
123 | -def read_seeded(base="", ext="", timeout=5, retries=10, file_retries=0): |
124 | +def load_seed(base='', required=None, optional=None, timeout=5): |
125 | + result = {} |
126 | + if required is None: |
127 | + required = [] |
128 | + if optional is None: |
129 | + optional = [] |
130 | if base.startswith("/"): |
131 | base = "file://%s" % base |
132 | |
133 | - # default retries for file is 0. for network is 10 |
134 | - if base.startswith("file://"): |
135 | - retries = file_retries |
136 | + for item in required + optional: |
137 | + try: |
138 | + url = url_helper.combine_url(base, item) |
139 | + resp = url_helper.read_file_or_url(url, timeout) |
140 | + if resp.ok(): |
141 | + if item in ['meta-data', 'network-config']: |
142 | + result[item] = load_yaml(decode_binary(resp.contents), |
143 | + default={}) |
144 | + else: |
145 | + result[item] = resp.contents |
146 | + except url_helper.UrlError as e: |
147 | + LOG.debug('Failed to read %s: %s', url, e) |
148 | + if item in optional and e.code == url_helper.NOT_FOUND: |
149 | + pass |
150 | + else: |
151 | + raise e |
152 | |
153 | - if base.find("%s") >= 0: |
154 | - ud_url = base % ("user-data" + ext) |
155 | - md_url = base % ("meta-data" + ext) |
156 | - else: |
157 | - ud_url = "%s%s%s" % (base, "user-data", ext) |
158 | - md_url = "%s%s%s" % (base, "meta-data", ext) |
159 | - |
160 | - md_resp = url_helper.read_file_or_url(md_url, timeout, retries, |
161 | - file_retries) |
162 | - md = None |
163 | - if md_resp.ok(): |
164 | - md = load_yaml(decode_binary(md_resp.contents), default={}) |
165 | - |
166 | - ud_resp = url_helper.read_file_or_url(ud_url, timeout, retries, |
167 | - file_retries) |
168 | - ud = None |
169 | - if ud_resp.ok(): |
170 | - ud = ud_resp.contents |
171 | - |
172 | - return (md, ud) |
173 | + return result |
174 | |
175 | |
176 | def read_conf_d(confd): |
177 | diff --git a/tests/unittests/test_util.py b/tests/unittests/test_util.py |
178 | index 0e71db8..f5d2c63 100644 |
179 | --- a/tests/unittests/test_util.py |
180 | +++ b/tests/unittests/test_util.py |
181 | @@ -9,6 +9,7 @@ import shutil |
182 | import stat |
183 | import tempfile |
184 | |
185 | +import httpretty |
186 | import json |
187 | import six |
188 | import sys |
189 | @@ -724,9 +725,9 @@ class TestMessageFromString(helpers.TestCase): |
190 | self.assertNotIn('\x00', roundtripped) |
191 | |
192 | |
193 | -class TestReadSeeded(helpers.TestCase): |
194 | +class TestLoadSeed(helpers.HttprettyTestCase): |
195 | def setUp(self): |
196 | - super(TestReadSeeded, self).setUp() |
197 | + super(TestLoadSeed, self).setUp() |
198 | self.tmp = tempfile.mkdtemp() |
199 | self.addCleanup(shutil.rmtree, self.tmp) |
200 | |
201 | @@ -734,12 +735,69 @@ class TestReadSeeded(helpers.TestCase): |
202 | ud = b"userdatablob" |
203 | helpers.populate_dir( |
204 | self.tmp, {'meta-data': "key1: val1", 'user-data': ud}) |
205 | - sdir = self.tmp + os.path.sep |
206 | - (found_md, found_ud) = util.read_seeded(sdir) |
207 | + seed = util.load_seed(self.tmp, required=['meta-data', 'user-data']) |
208 | + found_md = seed.get('meta-data', {}) |
209 | + found_ud = seed.get('user-data', '') |
210 | |
211 | self.assertEqual(found_md, {'key1': 'val1'}) |
212 | self.assertEqual(found_ud, ud) |
213 | |
214 | + def test_non_yaml_returns_empty_dict(self): |
215 | + blob = b"randomblob" |
216 | + helpers.populate_dir(self.tmp, {'meta-data': blob}) |
217 | + seed = util.load_seed(self.tmp, required=['meta-data']) |
218 | + found_md = seed.get('meta-data') |
219 | + |
220 | + self.assertEqual(found_md, {}) |
221 | + |
222 | + def test_handles_urls(self): |
223 | + ud = b"userdatablob" |
224 | + base = 'http://foo.bar/datasource' |
225 | + httpretty.register_uri( |
226 | + httpretty.GET, |
227 | + base + '/meta-data', |
228 | + body='foo: bar') |
229 | + httpretty.register_uri( |
230 | + httpretty.GET, |
231 | + base + '/user-data', |
232 | + body=ud) |
233 | + seed = util.load_seed(base, required=['meta-data', 'user-data']) |
234 | + found_md = seed.get('meta-data', {}) |
235 | + found_ud = seed.get('user-data', '') |
236 | + |
237 | + self.assertEqual(found_md, {'foo': 'bar'}) |
238 | + self.assertEqual(found_ud, ud) |
239 | + |
240 | + def test_ignores_optional(self): |
241 | + ud = b"userdatablob" |
242 | + helpers.populate_dir( |
243 | + self.tmp, {'meta-data': 'key1: val1', 'user-data': ud}) |
244 | + seed = util.load_seed( |
245 | + self.tmp, ['meta-data', 'user-data'], ['network-config']) |
246 | + found_md = seed.get('meta-data', {}) |
247 | + found_ud = seed.get('user-data', '') |
248 | + |
249 | + self.assertEqual(found_md, {'key1': 'val1'}) |
250 | + self.assertEqual(found_ud, ud) |
251 | + |
252 | + def test_loads_optional(self): |
253 | + ud = b"userdatablob" |
254 | + helpers.populate_dir( |
255 | + self.tmp, { |
256 | + 'meta-data': 'key1: val1', |
257 | + 'user-data': ud, |
258 | + 'network-config': 'key: val' |
259 | + }) |
260 | + seed = util.load_seed( |
261 | + self.tmp, ['meta-data', 'user-data'], ['network-config']) |
262 | + found_md = seed.get('meta-data', {}) |
263 | + found_ud = seed.get('user-data', '') |
264 | + found_nc = seed.get('network-config', {}) |
265 | + |
266 | + self.assertEqual(found_md, {'key1': 'val1'}) |
267 | + self.assertEqual(found_ud, ud) |
268 | + self.assertEqual(found_nc, {'key': 'val'}) |
269 | + |
270 | |
271 | class TestSubp(helpers.CiTestCase): |
272 | with_logs = True |
Hi,
I previously wrote a message to the mailing list, but without any response so far, so I figured I'd write some more code and try this way. Happy about any suggestions for improvements or guidance for other approaches.
Some notes right away:
- The "xenial" tox env does not work for me, even on clean master, is this a known issue?
- I thought it would be nice to make a holistic change (i.e. changing all callers to the new function), but in fact I do not have access to a test setup for the OVH or CloudStack data sources. How is this usually handled? Is it preferable to not touch any data sources you can not test? Or are there means for this to be tested before it lands in a release?
Thanks a bunch,
Conrad