Merge lp:~bbaude/cloud-init/rh_subscription into lp:~cloud-init-dev/cloud-init/trunk
- rh_subscription
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 1113 |
Proposed branch: | lp:~bbaude/cloud-init/rh_subscription |
Merge into: | lp:~cloud-init-dev/cloud-init/trunk |
Diff against target: |
675 lines (+661/-0) 3 files modified
cloudinit/config/cc_rh_subscription.py (+404/-0) doc/examples/cloud-config-rh_subscription.txt (+49/-0) tests/unittests/test_rh_subscription.py (+208/-0) |
To merge this branch: | bzr merge lp:~bbaude/cloud-init/rh_subscription |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Dan Watkins | Approve | ||
Review via email: mp+259159@code.launchpad.net |
Commit message
Description of the change
This patch adds a cloud-init plugin for helping users register
and subscribe their RHEL based systems. As inputs, it can take:
- user and password OR activation key and org | requires on of the
two pair
- auto-attach: True or False | optional
- service-level: <string> | optional
- add-pool [list, of, pool, ids] | optional
- enable-repos [list, of, yum, repos, to, enable] | optional
- disable-repos [list, of, yum, repos, to, disable] | optional
You can also pass the following to influence your registration via rhsm.conf:
- rhsm-baseurl | optional
- server-hostname | optional
- 1103. By Brent Baude
-
Adding an example file for rh_subscription in doc/examples
Scott Moser (smoser) wrote : | # |
- 1104. By Brent Baude
-
This commit consists of three things based on feedback from smosher:
cc_rh_subscription: Use of self.log.info limited, uses the util.subp for subprocesses, removed full path for subscription-
manager cloud-config-
rh_subscription .txt: A heavily commented example file on how to use rh_subscription and its main keys test_rh_
subscription. py: a set of unittests for rh_subscription
Dan Watkins (oddbloke) wrote : | # |
Some testing comments (in addition to those in IRC); will now review the code itself.
Dan Watkins (oddbloke) : | # |
Dan Watkins (oddbloke) wrote : | # |
A bit more detail on self.assertRaises.
- 1105. By Brent Baude
-
Updated files with upstream review comments thanks to Dan and Scott
- 1106. By Brent Baude
-
Tightening up an error message and isinstance usage based on feedback from Dan
- 1107. By Brent Baude
-
Corrected spelling error on variable name
Dan Watkins (oddbloke) : | # |
Preview Diff
1 | === added file 'cloudinit/config/cc_rh_subscription.py' |
2 | --- cloudinit/config/cc_rh_subscription.py 1970-01-01 00:00:00 +0000 |
3 | +++ cloudinit/config/cc_rh_subscription.py 2015-05-29 14:19:21 +0000 |
4 | @@ -0,0 +1,404 @@ |
5 | +# vi: ts=4 expandtab |
6 | +# |
7 | +# Copyright (C) 2015 Red Hat, Inc. |
8 | +# |
9 | +# Author: Brent Baude <bbaude@redhat.com> |
10 | +# |
11 | +# This program is free software: you can redistribute it and/or modify |
12 | +# it under the terms of the GNU General Public License version 3, as |
13 | +# published by the Free Software Foundation. |
14 | +# |
15 | +# This program is distributed in the hope that it will be useful, |
16 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
18 | +# GNU General Public License for more details. |
19 | +# |
20 | +# You should have received a copy of the GNU General Public License |
21 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
22 | + |
23 | +from cloudinit import util |
24 | + |
25 | + |
26 | +def handle(_name, cfg, _cloud, log, _args): |
27 | + sm = SubscriptionManager(cfg) |
28 | + sm.log = log |
29 | + if not sm.is_registered: |
30 | + try: |
31 | + verify, verify_msg = sm._verify_keys() |
32 | + if verify is not True: |
33 | + raise SubscriptionError(verify_msg) |
34 | + cont = sm.rhn_register() |
35 | + if not cont: |
36 | + raise SubscriptionError("Registration failed or did not " |
37 | + "run completely") |
38 | + |
39 | + # Splitting up the registration, auto-attach, and servicelevel |
40 | + # commands because the error codes, messages from subman are not |
41 | + # specific enough. |
42 | + |
43 | + # Attempt to change the service level |
44 | + if sm.auto_attach and sm.servicelevel is not None: |
45 | + if not sm._set_service_level(): |
46 | + raise SubscriptionError("Setting of service-level " |
47 | + "failed") |
48 | + else: |
49 | + sm.log.debug("Completed auto-attach with service level") |
50 | + elif sm.auto_attach: |
51 | + if not sm._set_auto_attach(): |
52 | + raise SubscriptionError("Setting auto-attach failed") |
53 | + else: |
54 | + sm.log.debug("Completed auto-attach") |
55 | + |
56 | + if sm.pools is not None: |
57 | + if not isinstance(sm.pools, list): |
58 | + pool_fail = "Pools must in the format of a list" |
59 | + raise SubscriptionError(pool_fail) |
60 | + |
61 | + return_stat = sm.addPool(sm.pools) |
62 | + if not return_stat: |
63 | + raise SubscriptionError("Unable to attach pools {0}" |
64 | + .format(sm.pools)) |
65 | + if (sm.enable_repo is not None) or (sm.disable_repo is not None): |
66 | + return_stat = sm.update_repos(sm.enable_repo, sm.disable_repo) |
67 | + if not return_stat: |
68 | + raise SubscriptionError("Unable to add or remove repos") |
69 | + sm.log_success("rh_subscription plugin completed successfully") |
70 | + except SubscriptionError as e: |
71 | + sm.log_warn(str(e)) |
72 | + sm.log_warn("rh_subscription plugin did not complete successfully") |
73 | + else: |
74 | + sm.log_success("System is already registered") |
75 | + |
76 | + |
77 | +class SubscriptionError(Exception): |
78 | + pass |
79 | + |
80 | + |
81 | +class SubscriptionManager(object): |
82 | + valid_rh_keys = ['org', 'activation-key', 'username', 'password', |
83 | + 'disable-repo', 'enable-repo', 'add-pool', |
84 | + 'rhsm-baseurl', 'server-hostname', |
85 | + 'auto-attach', 'service-level'] |
86 | + |
87 | + def __init__(self, cfg): |
88 | + self.cfg = cfg |
89 | + self.rhel_cfg = self.cfg.get('rh_subscription', {}) |
90 | + self.rhsm_baseurl = self.rhel_cfg.get('rhsm-baseurl') |
91 | + self.server_hostname = self.rhel_cfg.get('server-hostname') |
92 | + self.pools = self.rhel_cfg.get('add-pool') |
93 | + self.activation_key = self.rhel_cfg.get('activation-key') |
94 | + self.org = self.rhel_cfg.get('org') |
95 | + self.userid = self.rhel_cfg.get('username') |
96 | + self.password = self.rhel_cfg.get('password') |
97 | + self.auto_attach = self.rhel_cfg.get('auto-attach') |
98 | + self.enable_repo = self.rhel_cfg.get('enable-repo') |
99 | + self.disable_repo = self.rhel_cfg.get('disable-repo') |
100 | + self.servicelevel = self.rhel_cfg.get('service-level') |
101 | + self.subman = ['subscription-manager'] |
102 | + self.is_registered = self._is_registered() |
103 | + |
104 | + def log_success(self, msg): |
105 | + '''Simple wrapper for logging info messages. Useful for unittests''' |
106 | + self.log.info(msg) |
107 | + |
108 | + def log_warn(self, msg): |
109 | + '''Simple wrapper for logging warning messages. Useful for unittests''' |
110 | + self.log.warn(msg) |
111 | + |
112 | + def _verify_keys(self): |
113 | + ''' |
114 | + Checks that the keys in the rh_subscription dict from the user-data |
115 | + are what we expect. |
116 | + ''' |
117 | + |
118 | + for k in self.rhel_cfg: |
119 | + if k not in self.valid_rh_keys: |
120 | + bad_key = "{0} is not a valid key for rh_subscription. "\ |
121 | + "Valid keys are: "\ |
122 | + "{1}".format(k, ', '.join(self.valid_rh_keys)) |
123 | + return False, bad_key |
124 | + |
125 | + # Check for bad auto-attach value |
126 | + if (self.auto_attach is not None) and \ |
127 | + not (util.is_true(self.auto_attach) or |
128 | + util.is_false(self.auto_attach)): |
129 | + not_bool = "The key auto-attach must be a boolean value "\ |
130 | + "(True/False " |
131 | + return False, not_bool |
132 | + |
133 | + if (self.servicelevel is not None) and \ |
134 | + ((not self.auto_attach) |
135 | + or (util.is_false(str(self.auto_attach)))): |
136 | + |
137 | + no_auto = "The service-level key must be used in conjunction with "\ |
138 | + "the auto-attach key. Please re-run with auto-attach: "\ |
139 | + "True" |
140 | + return False, no_auto |
141 | + return True, None |
142 | + |
143 | + def _is_registered(self): |
144 | + ''' |
145 | + Checks if the system is already registered and returns |
146 | + True if so, else False |
147 | + ''' |
148 | + cmd = ['identity'] |
149 | + |
150 | + try: |
151 | + self._sub_man_cli(cmd) |
152 | + except util.ProcessExecutionError: |
153 | + return False |
154 | + |
155 | + return True |
156 | + |
157 | + def _sub_man_cli(self, cmd, logstring_val=False): |
158 | + ''' |
159 | + Uses the prefered cloud-init subprocess def of util.subp |
160 | + and runs subscription-manager. Breaking this to a |
161 | + separate function for later use in mocking and unittests |
162 | + ''' |
163 | + cmd = self.subman + cmd |
164 | + return util.subp(cmd, logstring=logstring_val) |
165 | + |
166 | + def rhn_register(self): |
167 | + ''' |
168 | + Registers the system by userid and password or activation key |
169 | + and org. Returns True when successful False when not. |
170 | + ''' |
171 | + |
172 | + if (self.activation_key is not None) and (self.org is not None): |
173 | + # register by activation key |
174 | + cmd = ['register', '--activationkey={0}'. |
175 | + format(self.activation_key), '--org={0}'.format(self.org)] |
176 | + |
177 | + # If the baseurl and/or server url are passed in, we register |
178 | + # with them. |
179 | + |
180 | + if self.rhsm_baseurl is not None: |
181 | + cmd.append("--baseurl={0}".format(self.rhsm_baseurl)) |
182 | + |
183 | + if self.server_hostname is not None: |
184 | + cmd.append("--serverurl={0}".format(self.server_hostname)) |
185 | + |
186 | + try: |
187 | + return_out, return_err = self._sub_man_cli(cmd, |
188 | + logstring_val=True) |
189 | + except util.ProcessExecutionError as e: |
190 | + if e.stdout == "": |
191 | + self.log_warn("Registration failed due " |
192 | + "to: {0}".format(e.stderr)) |
193 | + return False |
194 | + |
195 | + elif (self.userid is not None) and (self.password is not None): |
196 | + # register by username and password |
197 | + cmd = ['register', '--username={0}'.format(self.userid), |
198 | + '--password={0}'.format(self.password)] |
199 | + |
200 | + # If the baseurl and/or server url are passed in, we register |
201 | + # with them. |
202 | + |
203 | + if self.rhsm_baseurl is not None: |
204 | + cmd.append("--baseurl={0}".format(self.rhsm_baseurl)) |
205 | + |
206 | + if self.server_hostname is not None: |
207 | + cmd.append("--serverurl={0}".format(self.server_hostname)) |
208 | + |
209 | + # Attempting to register the system only |
210 | + try: |
211 | + return_out, return_err = self._sub_man_cli(cmd, |
212 | + logstring_val=True) |
213 | + except util.ProcessExecutionError as e: |
214 | + if e.stdout == "": |
215 | + self.log_warn("Registration failed due " |
216 | + "to: {0}".format(e.stderr)) |
217 | + return False |
218 | + |
219 | + else: |
220 | + self.log_warn("Unable to register system due to incomplete " |
221 | + "information.") |
222 | + self.log_warn("Use either activationkey and org *or* userid " |
223 | + "and password") |
224 | + return False |
225 | + |
226 | + reg_id = return_out.split("ID: ")[1].rstrip() |
227 | + self.log.debug("Registered successfully with ID {0}".format(reg_id)) |
228 | + return True |
229 | + |
230 | + def _set_service_level(self): |
231 | + cmd = ['attach', '--auto', '--servicelevel={0}' |
232 | + .format(self.servicelevel)] |
233 | + |
234 | + try: |
235 | + return_out, return_err = self._sub_man_cli(cmd) |
236 | + except util.ProcessExecutionError as e: |
237 | + if e.stdout.rstrip() != '': |
238 | + for line in e.stdout.split("\n"): |
239 | + if line is not '': |
240 | + self.log_warn(line) |
241 | + else: |
242 | + self.log_warn("Setting the service level failed with: " |
243 | + "{0}".format(e.stderr.strip())) |
244 | + return False |
245 | + for line in return_out.split("\n"): |
246 | + if line is not "": |
247 | + self.log.debug(line) |
248 | + return True |
249 | + |
250 | + def _set_auto_attach(self): |
251 | + cmd = ['attach', '--auto'] |
252 | + try: |
253 | + return_out, return_err = self._sub_man_cli(cmd) |
254 | + except util.ProcessExecutionError: |
255 | + self.log_warn("Auto-attach failed with: " |
256 | + "{0}]".format(return_err.strip())) |
257 | + return False |
258 | + for line in return_out.split("\n"): |
259 | + if line is not "": |
260 | + self.log.debug(line) |
261 | + return True |
262 | + |
263 | + def _getPools(self): |
264 | + ''' |
265 | + Gets the list pools for the active subscription and returns them |
266 | + in list form. |
267 | + ''' |
268 | + available = [] |
269 | + consumed = [] |
270 | + |
271 | + # Get all available pools |
272 | + cmd = ['list', '--available', '--pool-only'] |
273 | + results, errors = self._sub_man_cli(cmd) |
274 | + available = (results.rstrip()).split("\n") |
275 | + |
276 | + # Get all consumed pools |
277 | + cmd = ['list', '--consumed', '--pool-only'] |
278 | + results, errors = self._sub_man_cli(cmd) |
279 | + consumed = (results.rstrip()).split("\n") |
280 | + |
281 | + return available, consumed |
282 | + |
283 | + def _getRepos(self): |
284 | + ''' |
285 | + Obtains the current list of active yum repositories and returns |
286 | + them in list form. |
287 | + ''' |
288 | + |
289 | + cmd = ['repos', '--list-enabled'] |
290 | + return_out, return_err = self._sub_man_cli(cmd) |
291 | + active_repos = [] |
292 | + for repo in return_out.split("\n"): |
293 | + if "Repo ID:" in repo: |
294 | + active_repos.append((repo.split(':')[1]).strip()) |
295 | + |
296 | + cmd = ['repos', '--list-disabled'] |
297 | + return_out, return_err = self._sub_man_cli(cmd) |
298 | + |
299 | + inactive_repos = [] |
300 | + for repo in return_out.split("\n"): |
301 | + if "Repo ID:" in repo: |
302 | + inactive_repos.append((repo.split(':')[1]).strip()) |
303 | + return active_repos, inactive_repos |
304 | + |
305 | + def addPool(self, pools): |
306 | + ''' |
307 | + Takes a list of subscription pools and "attaches" them to the |
308 | + current subscription |
309 | + ''' |
310 | + |
311 | + # An empty list was passed |
312 | + if len(pools) == 0: |
313 | + self.log.debug("No pools to attach") |
314 | + return True |
315 | + |
316 | + pool_available, pool_consumed = self._getPools() |
317 | + pool_list = [] |
318 | + cmd = ['attach'] |
319 | + for pool in pools: |
320 | + if (pool not in pool_consumed) and (pool in pool_available): |
321 | + pool_list.append('--pool={0}'.format(pool)) |
322 | + else: |
323 | + self.log_warn("Pool {0} is not available".format(pool)) |
324 | + if len(pool_list) > 0: |
325 | + cmd.extend(pool_list) |
326 | + try: |
327 | + self._sub_man_cli(cmd) |
328 | + self.log.debug("Attached the following pools to your " |
329 | + "system: %s" % (", ".join(pool_list)) |
330 | + .replace('--pool=', '')) |
331 | + return True |
332 | + except util.ProcessExecutionError as e: |
333 | + self.log_warn("Unable to attach pool {0} " |
334 | + "due to {1}".format(pool, e)) |
335 | + return False |
336 | + |
337 | + def update_repos(self, erepos, drepos): |
338 | + ''' |
339 | + Takes a list of yum repo ids that need to be disabled or enabled; then |
340 | + it verifies if they are already enabled or disabled and finally |
341 | + executes the action to disable or enable |
342 | + ''' |
343 | + |
344 | + if (erepos is not None) and (not isinstance(erepos, list)): |
345 | + self.log_warn("Repo IDs must in the format of a list.") |
346 | + return False |
347 | + |
348 | + if (drepos is not None) and (not isinstance(drepos, list)): |
349 | + self.log_warn("Repo IDs must in the format of a list.") |
350 | + return False |
351 | + |
352 | + # Bail if both lists are not populated |
353 | + if (len(erepos) == 0) and (len(drepos) == 0): |
354 | + self.log.debug("No repo IDs to enable or disable") |
355 | + return True |
356 | + |
357 | + active_repos, inactive_repos = self._getRepos() |
358 | + # Creating a list of repoids to be enabled |
359 | + enable_list = [] |
360 | + enable_list_fail = [] |
361 | + for repoid in erepos: |
362 | + if (repoid in inactive_repos): |
363 | + enable_list.append("--enable={0}".format(repoid)) |
364 | + else: |
365 | + enable_list_fail.append(repoid) |
366 | + |
367 | + # Creating a list of repoids to be disabled |
368 | + disable_list = [] |
369 | + disable_list_fail = [] |
370 | + for repoid in drepos: |
371 | + if repoid in active_repos: |
372 | + disable_list.append("--disable={0}".format(repoid)) |
373 | + else: |
374 | + disable_list_fail.append(repoid) |
375 | + |
376 | + # Logging any repos that are already enabled or disabled |
377 | + if len(enable_list_fail) > 0: |
378 | + for fail in enable_list_fail: |
379 | + # Check if the repo exists or not |
380 | + if fail in active_repos: |
381 | + self.log.debug("Repo {0} is already enabled".format(fail)) |
382 | + else: |
383 | + self.log_warn("Repo {0} does not appear to " |
384 | + "exist".format(fail)) |
385 | + if len(disable_list_fail) > 0: |
386 | + for fail in disable_list_fail: |
387 | + self.log.debug("Repo {0} not disabled " |
388 | + "because it is not enabled".format(fail)) |
389 | + |
390 | + cmd = ['repos'] |
391 | + if enable_list > 0: |
392 | + cmd.extend(enable_list) |
393 | + if disable_list > 0: |
394 | + cmd.extend(disable_list) |
395 | + |
396 | + try: |
397 | + self._sub_man_cli(cmd) |
398 | + except util.ProcessExecutionError as e: |
399 | + self.log_warn("Unable to alter repos due to {0}".format(e)) |
400 | + return False |
401 | + |
402 | + if enable_list > 0: |
403 | + self.log.debug("Enabled the following repos: %s" % |
404 | + (", ".join(enable_list)).replace('--enable=', '')) |
405 | + if disable_list > 0: |
406 | + self.log.debug("Disabled the following repos: %s" % |
407 | + (", ".join(disable_list)).replace('--disable=', '')) |
408 | + return True |
409 | |
410 | === added file 'doc/examples/cloud-config-rh_subscription.txt' |
411 | --- doc/examples/cloud-config-rh_subscription.txt 1970-01-01 00:00:00 +0000 |
412 | +++ doc/examples/cloud-config-rh_subscription.txt 2015-05-29 14:19:21 +0000 |
413 | @@ -0,0 +1,49 @@ |
414 | +#cloud-config |
415 | + |
416 | +# register your Red Hat Enterprise Linux based operating system |
417 | +# |
418 | +# this cloud-init plugin is capable of registering by username |
419 | +# and password *or* activation and org. Following a successfully |
420 | +# registration you can: |
421 | +# - auto-attach subscriptions |
422 | +# - set the service level |
423 | +# - add subscriptions based on its pool ID |
424 | +# - enable yum repositories based on its repo id |
425 | +# - disable yum repositories based on its repo id |
426 | +# - alter the rhsm_baseurl and server-hostname in the |
427 | +# /etc/rhsm/rhs.conf file |
428 | + |
429 | +rh_subscription: |
430 | + username: joe@foo.bar |
431 | + |
432 | + ## Quote your password if it has symbols to be safe |
433 | + password: '1234abcd' |
434 | + |
435 | + ## If you prefer, you can use the activation key and |
436 | + ## org instead of username and password. Be sure to |
437 | + ## comment out username and password |
438 | + |
439 | + #activation-key: foobar |
440 | + #org: 12345 |
441 | + |
442 | + ## Uncomment to auto-attach subscriptions to your system |
443 | + #auto-attach: True |
444 | + |
445 | + ## Uncomment to set the service level for your |
446 | + ## subscriptions |
447 | + #service-level: self-support |
448 | + |
449 | + ## Uncomment to add pools (needs to be a list of IDs) |
450 | + #add-pool: [] |
451 | + |
452 | + ## Uncomment to add or remove yum repos |
453 | + ## (needs to be a list of repo IDs) |
454 | + #enable-repo: [] |
455 | + #disable-repo: [] |
456 | + |
457 | + ## Uncomment to alter the baseurl in /etc/rhsm/rhsm.conf |
458 | + #rhsm-baseurl: http://url |
459 | + |
460 | + ## Uncomment to alter the server hostname in |
461 | + ## /etc/rhsm/rhsm.conf |
462 | + #server-hostname: foo.bar.com |
463 | |
464 | === added file 'tests/unittests/test_rh_subscription.py' |
465 | --- tests/unittests/test_rh_subscription.py 1970-01-01 00:00:00 +0000 |
466 | +++ tests/unittests/test_rh_subscription.py 2015-05-29 14:19:21 +0000 |
467 | @@ -0,0 +1,208 @@ |
468 | +from cloudinit import util |
469 | +from cloudinit.config import cc_rh_subscription |
470 | +import logging |
471 | +import mock |
472 | +import unittest |
473 | + |
474 | + |
475 | +class GoodTests(unittest.TestCase): |
476 | + def setUp(self): |
477 | + super(GoodTests, self).setUp() |
478 | + self.name = "cc_rh_subscription" |
479 | + self.cloud_init = None |
480 | + self.log = logging.getLogger("good_tests") |
481 | + self.args = [] |
482 | + self.handle = cc_rh_subscription.handle |
483 | + self.SM = cc_rh_subscription.SubscriptionManager |
484 | + |
485 | + self.config = {'rh_subscription': |
486 | + {'username': 'scooby@do.com', |
487 | + 'password': 'scooby-snacks' |
488 | + }} |
489 | + self.config_full = {'rh_subscription': |
490 | + {'username': 'scooby@do.com', |
491 | + 'password': 'scooby-snacks', |
492 | + 'auto-attach': True, |
493 | + 'service-level': 'self-support', |
494 | + 'add-pool': ['pool1', 'pool2', 'pool3'], |
495 | + 'enable-repo': ['repo1', 'repo2', 'repo3'], |
496 | + 'disable-repo': ['repo4', 'repo5'] |
497 | + }} |
498 | + |
499 | + def test_already_registered(self): |
500 | + ''' |
501 | + Emulates a system that is already registered. Ensure it gets |
502 | + a non-ProcessExecution error from is_registered() |
503 | + ''' |
504 | + with mock.patch.object(cc_rh_subscription.SubscriptionManager, |
505 | + '_sub_man_cli') as mockobj: |
506 | + self.SM.log_success = mock.MagicMock() |
507 | + self.handle(self.name, self.config, self.cloud_init, |
508 | + self.log, self.args) |
509 | + self.assertEqual(self.SM.log_success.call_count, 1) |
510 | + self.assertEqual(mockobj.call_count, 1) |
511 | + |
512 | + def test_simple_registration(self): |
513 | + ''' |
514 | + Simple registration with username and password |
515 | + ''' |
516 | + self.SM.log_success = mock.MagicMock() |
517 | + reg = "The system has been registered with ID:" \ |
518 | + " 12345678-abde-abcde-1234-1234567890abc" |
519 | + self.SM._sub_man_cli = mock.MagicMock( |
520 | + side_effect=[util.ProcessExecutionError, (reg, 'bar')]) |
521 | + self.handle(self.name, self.config, self.cloud_init, |
522 | + self.log, self.args) |
523 | + self.assertIn(mock.call(['identity']), |
524 | + self.SM._sub_man_cli.call_args_list) |
525 | + self.assertIn(mock.call(['register', '--username=scooby@do.com', |
526 | + '--password=scooby-snacks'], |
527 | + logstring_val=True), |
528 | + self.SM._sub_man_cli.call_args_list) |
529 | + |
530 | + self.assertEqual(self.SM.log_success.call_count, 1) |
531 | + self.assertEqual(self.SM._sub_man_cli.call_count, 2) |
532 | + |
533 | + def test_full_registration(self): |
534 | + ''' |
535 | + Registration with auto-attach, service-level, adding pools, |
536 | + and enabling and disabling yum repos |
537 | + ''' |
538 | + call_lists = [] |
539 | + call_lists.append(['attach', '--pool=pool1', '--pool=pool3']) |
540 | + call_lists.append(['repos', '--enable=repo2', '--enable=repo3', |
541 | + '--disable=repo5']) |
542 | + call_lists.append(['attach', '--auto', '--servicelevel=self-support']) |
543 | + self.SM.log_success = mock.MagicMock() |
544 | + reg = "The system has been registered with ID:" \ |
545 | + " 12345678-abde-abcde-1234-1234567890abc" |
546 | + self.SM._sub_man_cli = mock.MagicMock( |
547 | + side_effect=[util.ProcessExecutionError, (reg, 'bar'), |
548 | + ('Service level set to: self-support', ''), |
549 | + ('pool1\npool3\n', ''), ('pool2\n', ''), ('', ''), |
550 | + ('Repo ID: repo1\nRepo ID: repo5\n', ''), |
551 | + ('Repo ID: repo2\nRepo ID: repo3\nRepo ID: ' |
552 | + 'repo4', ''), |
553 | + ('', '')]) |
554 | + self.handle(self.name, self.config_full, self.cloud_init, |
555 | + self.log, self.args) |
556 | + for call in call_lists: |
557 | + self.assertIn(mock.call(call), self.SM._sub_man_cli.call_args_list) |
558 | + self.assertEqual(self.SM.log_success.call_count, 1) |
559 | + self.assertEqual(self.SM._sub_man_cli.call_count, 9) |
560 | + |
561 | + |
562 | +class TestBadInput(unittest.TestCase): |
563 | + name = "cc_rh_subscription" |
564 | + cloud_init = None |
565 | + log = logging.getLogger("bad_tests") |
566 | + args = [] |
567 | + SM = cc_rh_subscription.SubscriptionManager |
568 | + reg = "The system has been registered with ID:" \ |
569 | + " 12345678-abde-abcde-1234-1234567890abc" |
570 | + |
571 | + config_no_password = {'rh_subscription': |
572 | + {'username': 'scooby@do.com' |
573 | + }} |
574 | + |
575 | + config_no_key = {'rh_subscription': |
576 | + {'activation-key': '1234abcde', |
577 | + }} |
578 | + |
579 | + config_service = {'rh_subscription': |
580 | + {'username': 'scooby@do.com', |
581 | + 'password': 'scooby-snacks', |
582 | + 'service-level': 'self-support' |
583 | + }} |
584 | + |
585 | + config_badpool = {'rh_subscription': |
586 | + {'username': 'scooby@do.com', |
587 | + 'password': 'scooby-snacks', |
588 | + 'add-pool': 'not_a_list' |
589 | + }} |
590 | + config_badrepo = {'rh_subscription': |
591 | + {'username': 'scooby@do.com', |
592 | + 'password': 'scooby-snacks', |
593 | + 'enable-repo': 'not_a_list' |
594 | + }} |
595 | + config_badkey = {'rh_subscription': |
596 | + {'activation_key': 'abcdef1234', |
597 | + 'org': '123', |
598 | + }} |
599 | + |
600 | + def setUp(self): |
601 | + super(TestBadInput, self).setUp() |
602 | + self.handle = cc_rh_subscription.handle |
603 | + |
604 | + def test_no_password(self): |
605 | + ''' |
606 | + Attempt to register without the password key/value |
607 | + ''' |
608 | + self.input_is_missing_data(self.config_no_password) |
609 | + |
610 | + def test_no_org(self): |
611 | + ''' |
612 | + Attempt to register without the org key/value |
613 | + ''' |
614 | + self.input_is_missing_data(self.config_no_key) |
615 | + |
616 | + def test_service_level_without_auto(self): |
617 | + ''' |
618 | + Attempt to register using service-level without the auto-attach key |
619 | + ''' |
620 | + self.SM.log_warn = mock.MagicMock() |
621 | + self.SM._sub_man_cli = mock.MagicMock( |
622 | + side_effect=[util.ProcessExecutionError, (self.reg, 'bar')]) |
623 | + self.handle(self.name, self.config_service, self.cloud_init, |
624 | + self.log, self.args) |
625 | + self.assertEqual(self.SM._sub_man_cli.call_count, 1) |
626 | + self.assertEqual(self.SM.log_warn.call_count, 2) |
627 | + |
628 | + def test_pool_not_a_list(self): |
629 | + ''' |
630 | + Register with pools that are not in the format of a list |
631 | + ''' |
632 | + self.SM.log_warn = mock.MagicMock() |
633 | + self.SM._sub_man_cli = mock.MagicMock( |
634 | + side_effect=[util.ProcessExecutionError, (self.reg, 'bar')]) |
635 | + self.handle(self.name, self.config_badpool, self.cloud_init, |
636 | + self.log, self.args) |
637 | + self.assertEqual(self.SM._sub_man_cli.call_count, 2) |
638 | + self.assertEqual(self.SM.log_warn.call_count, 2) |
639 | + |
640 | + def test_repo_not_a_list(self): |
641 | + ''' |
642 | + Register with repos that are not in the format of a list |
643 | + ''' |
644 | + self.SM.log_warn = mock.MagicMock() |
645 | + self.SM._sub_man_cli = mock.MagicMock( |
646 | + side_effect=[util.ProcessExecutionError, (self.reg, 'bar')]) |
647 | + self.handle(self.name, self.config_badrepo, self.cloud_init, |
648 | + self.log, self.args) |
649 | + self.assertEqual(self.SM.log_warn.call_count, 3) |
650 | + self.assertEqual(self.SM._sub_man_cli.call_count, 2) |
651 | + |
652 | + def test_bad_key_value(self): |
653 | + ''' |
654 | + Attempt to register with a key that we don't know |
655 | + ''' |
656 | + self.SM.log_warn = mock.MagicMock() |
657 | + self.SM._sub_man_cli = mock.MagicMock( |
658 | + side_effect=[util.ProcessExecutionError, (self.reg, 'bar')]) |
659 | + self.handle(self.name, self.config_badkey, self.cloud_init, |
660 | + self.log, self.args) |
661 | + self.assertEqual(self.SM.log_warn.call_count, 2) |
662 | + self.assertEqual(self.SM._sub_man_cli.call_count, 1) |
663 | + |
664 | + def input_is_missing_data(self, config): |
665 | + ''' |
666 | + Helper def for tests that having missing information |
667 | + ''' |
668 | + self.SM.log_warn = mock.MagicMock() |
669 | + self.SM._sub_man_cli = mock.MagicMock( |
670 | + side_effect=[util.ProcessExecutionError]) |
671 | + self.handle(self.name, config, self.cloud_init, |
672 | + self.log, self.args) |
673 | + self.SM._sub_man_cli.assert_called_with(['identity']) |
674 | + self.assertEqual(self.SM.log_warn.call_count, 4) |
675 | + self.assertEqual(self.SM._sub_man_cli.call_count, 1) |
Brent,
this looks good, thanks for submitting.
a couple other minor things
a.) please don't use 'log.info' more than once.
that goes to the console or cloud-init's stdout. and i dont really want cloud-init to spam the console with "everything looks good" types of messages.
a single "registered with rhn" is probably fine.
others can be debug.
I do hope to have better rules in the future on what messages should go at what version.
b.) some unit tests would be nice.
c.) is there a reason for the hard coded /bin/subscripti on-manager ?
my preference is to always assume PATH is sane. if not, something else is probably wrong.
d.) instead of _captureRun, or even subprocess. use util.subp.
just nicer to have a single path for all that stuff.
thanks.