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

Proposed by Brent Baude on 2015-05-14
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 2015-05-14 Approve on 2015-06-10
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 on 2015-05-14

Adding an example file for rh_subscription in doc/examples

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 on 2015-05-21

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 (daniel-thewatkins) wrote :

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

Dan Watkins (daniel-thewatkins) wrote :

A bit more detail on self.assertRaises.

1105. By Brent Baude on 2015-05-27

Updated files with upstream review comments thanks to Dan and Scott

1106. By Brent Baude on 2015-05-28

Tightening up an error message and isinstance usage based on feedback from Dan

1107. By Brent Baude on 2015-05-29

Corrected spelling error on variable name

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)