Merge lp:~bbaude/cloud-init/rh_subscription into lp:~cloud-init-dev/cloud-init/trunk

Proposed by Brent Baude
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
Reviewer Review Type Date Requested Status
Dan Watkins Approve
Review via email: mp+259159@code.launchpad.net

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

To post a comment you must log in.
1103. By Brent Baude

Adding an example file for rh_subscription in doc/examples

Revision history for this message
Scott Moser (smoser) wrote :

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/subscription-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.

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

Revision history for this message
Dan Watkins (oddbloke) wrote :

Some testing comments (in addition to those in IRC); will now review the code itself.

Revision history for this message
Dan Watkins (oddbloke) :
Revision history for this message
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

Revision history for this message
Dan Watkins (oddbloke) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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)