diff options
author | Brent Baude <bbaude@redhat.com> | 2015-05-21 13:32:30 -0500 |
---|---|---|
committer | Brent Baude <bbaude@redhat.com> | 2015-05-21 13:32:30 -0500 |
commit | cf2b017c8bf2adb02a2c7a9c9f03754402cb73c4 (patch) | |
tree | 2b2c9c74700d8c8c9c2c00b81877f5fe54f4ff21 | |
parent | 7ea6b43ea863c2e0774be3ef562b47fc21dc510d (diff) | |
download | vyos-cloud-init-cf2b017c8bf2adb02a2c7a9c9f03754402cb73c4.tar.gz vyos-cloud-init-cf2b017c8bf2adb02a2c7a9c9f03754402cb73c4.zip |
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
-rw-r--r-- | cloudinit/config/cc_rh_subscription.py | 181 | ||||
-rw-r--r-- | tests/unittests/test_rh_subscription.py | 231 |
2 files changed, 323 insertions, 89 deletions
diff --git a/cloudinit/config/cc_rh_subscription.py b/cloudinit/config/cc_rh_subscription.py index b8056dbb..00a88456 100644 --- a/cloudinit/config/cc_rh_subscription.py +++ b/cloudinit/config/cc_rh_subscription.py @@ -16,15 +16,13 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -import os -import subprocess import itertools +from cloudinit import util def handle(_name, cfg, _cloud, log, _args): sm = SubscriptionManager(cfg) sm.log = log - if not sm.is_registered: try: verify, verify_msg = sm._verify_keys() @@ -41,21 +39,22 @@ def handle(_name, cfg, _cloud, log, _args): # Attempt to change the service level if sm.auto_attach and sm.servicelevel is not None: - if not sm._set_service_level(): - raise SubscriptionError("Setting of service-level " - "failed") - else: - sm.log.info("Completed auto-attach with service level") + if not sm._set_service_level(): + raise SubscriptionError("Setting of service-level " + "failed") + else: + sm.log.debug("Completed auto-attach with service level") elif sm.auto_attach: if not sm._set_auto_attach(): raise SubscriptionError("Setting auto-attach failed") else: - sm.log.info("Completed auto-attach") + sm.log.debug("Completed auto-attach") if sm.pools is not None: if type(sm.pools) is not list: - raise SubscriptionError("Pools must in the format of a " - "list.") + pool_fail = "Pools must in the format of a list" + raise SubscriptionError(pool_fail) + return_stat = sm.addPool(sm.pools) if not return_stat: raise SubscriptionError("Unable to attach pools {0}" @@ -66,8 +65,8 @@ def handle(_name, cfg, _cloud, log, _args): raise SubscriptionError("Unable to add or remove repos") sm.log.info("rh_subscription plugin completed successfully") except SubscriptionError as e: - sm.log.warn(e) - sm.log.info("rh_subscription plugin did not complete successfully") + sm.log.warn(str(e)) + sm.log.warn("rh_subscription plugin did not complete successfully") else: sm.log.info("System is already registered") @@ -91,7 +90,7 @@ class SubscriptionManager(object): self.enable_repo = self.rhel_cfg.get('enable-repo') self.disable_repo = self.rhel_cfg.get('disable-repo') self.servicelevel = self.rhel_cfg.get('service-level') - self.subman = ['/bin/subscription-manager'] + self.subman = ['subscription-manager'] self.valid_rh_keys = ['org', 'activation-key', 'username', 'password', 'disable-repo', 'enable-repo', 'add-pool', 'rhsm-baseurl', 'server-hostname', @@ -135,11 +134,22 @@ class SubscriptionManager(object): ''' cmd = list(itertools.chain(self.subman, ['identity'])) - if subprocess.call(cmd, stdout=open(os.devnull, 'wb'), - stderr=open(os.devnull, 'wb')) == 1: + try: + self._sub_man_cli(cmd) + except util.ProcessExecutionError: return False - else: - return True + + return True + + def _sub_man_cli(self, cmd, logstring_val=False): + ''' + Uses the prefered cloud-init subprocess def of util.subp + and runs subscription-manager. Breaking this to a + separate function for later use in mocking and unittests + ''' + return_out, return_err = util.subp(cmd, logstring=logstring_val) + + return return_out, return_err def rhn_register(self): ''' @@ -163,11 +173,13 @@ class SubscriptionManager(object): if self.server_hostname is not None: cmd.append("--serverurl={0}".format(self.server_hostname)) - return_msg, return_code = self._captureRun(cmd) - - if return_code is not 0: - self.log.warn("Registration with {0} and {1} failed.".format( - self.activation_key, self.org)) + try: + return_out, return_err = self._sub_man_cli(cmd, + logstring_val=True) + except util.ProcessExecutionError as e: + if e.stdout == "": + self.log.warn("Registration failed due " + "to: {0}".format(e.stderr)) return False elif (self.userid is not None) and (self.password is not None): @@ -186,14 +198,13 @@ class SubscriptionManager(object): cmd.append("--serverurl={0}".format(self.server_hostname)) # Attempting to register the system only - return_msg, return_code = self._captureRun(cmd) - - if return_code is not 0: - # Return message is in a set - if return_msg[0] == "": - self.log.warn("Registration failed") - if return_msg[1] is not "": - self.log.warn(return_msg[1]) + try: + return_out, return_err = self._sub_man_cli(cmd, + logstring_val=True) + except util.ProcessExecutionError as e: + if e.stdout == "": + self.log.warn("Registration failed due " + "to: {0}".format(e.stderr)) return False else: @@ -203,8 +214,8 @@ class SubscriptionManager(object): "and password") return False - reg_id = return_msg[0].split("ID: ")[1].rstrip() - self.log.info("Registered successfully with ID {0}".format(reg_id)) + reg_id = return_out.split("ID: ")[1].rstrip() + self.log.debug("Registered successfully with ID {0}".format(reg_id)) return True def _set_service_level(self): @@ -212,41 +223,34 @@ class SubscriptionManager(object): ['attach', '--auto', '--servicelevel={0}' .format(self.servicelevel)])) - return_msg, return_code = self._captureRun(cmd) - - if return_code is not 0: - self.log.warn("Setting the service level failed with: " - "{0}".format(return_msg[1].strip())) + try: + return_out, return_err = self._sub_man_cli(cmd) + except util.ProcessExecutionError as e: + if e.stdout.rstrip() != '': + for line in e.stdout.split("\n"): + if line is not '': + self.log.warn(line) + else: + self.log.warn("Setting the service level failed with: " + "{0}".format(e.stderr.strip())) return False - else: - for line in return_msg[0].split("\n"): - if line is not "": - self.log.info(line) - return True + for line in return_out.split("\n"): + if line is not "": + self.log.debug(line) + return True def _set_auto_attach(self): cmd = list(itertools.chain(self.subman, ['attach', '--auto'])) - return_msg, return_code = self._captureRun(cmd) - - if return_code is not 0: + try: + return_out, return_err = self._sub_man_cli(cmd) + except util.ProcessExecutionError: self.log.warn("Auto-attach failed with: " - "{0}]".format(return_msg[1].strip())) + "{0}]".format(return_err.strip())) return False - else: - for line in return_msg[0].split("\n"): - if line is not "": - self.log.info(line) - return True - - def _captureRun(self, cmd): - ''' - Subprocess command that captures and returns the output and - return code. - ''' - - r = subprocess.Popen(cmd, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - return r.communicate(), r.returncode + for line in return_out.split("\n"): + if line is not "": + self.log.debug(line) + return True def _getPools(self): ''' @@ -259,14 +263,15 @@ class SubscriptionManager(object): # Get all available pools cmd = list(itertools.chain(self.subman, ['list', '--available', '--pool-only'])) - results = subprocess.check_output(cmd) + results, errors = self._sub_man_cli(cmd) available = (results.rstrip()).split("\n") - # Get all available pools + # Get all consumed pools cmd = list(itertools.chain(self.subman, ['list', '--consumed', '--pool-only'])) - results = subprocess.check_output(cmd) + results, errors = self._sub_man_cli(cmd) consumed = (results.rstrip()).split("\n") + return available, consumed def _getRepos(self): @@ -276,21 +281,19 @@ class SubscriptionManager(object): ''' cmd = list(itertools.chain(self.subman, ['repos', '--list-enabled'])) - result, return_code = self._captureRun(cmd) - + return_out, return_err = self._sub_man_cli(cmd) active_repos = [] - for repo in result[0].split("\n"): + for repo in return_out.split("\n"): if "Repo ID:" in repo: active_repos.append((repo.split(':')[1]).strip()) cmd = list(itertools.chain(self.subman, ['repos', '--list-disabled'])) - result, return_code = self._captureRun(cmd) + return_out, return_err = self._sub_man_cli(cmd) inactive_repos = [] - for repo in result[0].split("\n"): + for repo in return_out.split("\n"): if "Repo ID:" in repo: inactive_repos.append((repo.split(':')[1]).strip()) - return active_repos, inactive_repos def addPool(self, pools): @@ -301,7 +304,7 @@ class SubscriptionManager(object): # An empty list was passed if len(pools) == 0: - self.log.info("No pools to attach") + self.log.debug("No pools to attach") return True pool_available, pool_consumed = self._getPools() @@ -315,13 +318,14 @@ class SubscriptionManager(object): if len(pool_list) > 0: cmd.extend(pool_list) try: - self._captureRun(cmd) - self.log.info("Attached the following pools to your " - "system: %s" % (", ".join(pool_list)) - .replace('--pool=', '')) + self._sub_man_cli(cmd) + self.log.debug("Attached the following pools to your " + "system: %s" % (", ".join(pool_list)) + .replace('--pool=', '')) return True - except subprocess.CalledProcessError: - self.log.warn("Unable to attach pool {0}".format(pool)) + except util.ProcessExecutionError as e: + self.log.warn("Unable to attach pool {0} " + "due to {1}".format(pool, e)) return False def update_repos(self, erepos, drepos): @@ -341,7 +345,7 @@ class SubscriptionManager(object): # Bail if both lists are not populated if (len(erepos) == 0) and (len(drepos) == 0): - self.log.info("No repo IDs to enable or disable") + self.log.debug("No repo IDs to enable or disable") return True active_repos, inactive_repos = self._getRepos() @@ -368,14 +372,14 @@ class SubscriptionManager(object): for fail in enable_list_fail: # Check if the repo exists or not if fail in active_repos: - self.log.info("Repo {0} is already enabled".format(fail)) + self.log.debug("Repo {0} is already enabled".format(fail)) else: self.log.warn("Repo {0} does not appear to " "exist".format(fail)) if len(disable_list_fail) > 0: for fail in disable_list_fail: - self.log.info("Repo {0} not disabled " - "because it is not enabled".format(fail)) + self.log.debug("Repo {0} not disabled " + "because it is not enabled".format(fail)) cmd = list(itertools.chain(self.subman, ['repos'])) if enable_list > 0: @@ -384,16 +388,15 @@ class SubscriptionManager(object): cmd.extend(disable_list) try: - return_msg, return_code = self._captureRun(cmd) - - except subprocess.CalledProcessError as e: + self._sub_man_cli(cmd) + except util.ProcessExecutionError as e: self.log.warn("Unable to alter repos due to {0}".format(e)) return False if enable_list > 0: - self.log.info("Enabled the following repos: %s" % - (", ".join(enable_list)).replace('--enable=', '')) + self.log.debug("Enabled the following repos: %s" % + (", ".join(enable_list)).replace('--enable=', '')) if disable_list > 0: - self.log.info("Disabled the following repos: %s" % - (", ".join(disable_list)).replace('--disable=', '')) + self.log.debug("Disabled the following repos: %s" % + (", ".join(disable_list)).replace('--disable=', '')) return True diff --git a/tests/unittests/test_rh_subscription.py b/tests/unittests/test_rh_subscription.py new file mode 100644 index 00000000..ba9181ec --- /dev/null +++ b/tests/unittests/test_rh_subscription.py @@ -0,0 +1,231 @@ +from cloudinit import util +from cloudinit.config import cc_rh_subscription +import logging +import mock +import unittest + + +class GoodTests(unittest.TestCase): + def setUp(self): + super(GoodTests, self).setUp() + self.name = "cc_rh_subscription" + self.cloud_init = None + self.log = logging.getLogger("good_tests") + self.args = [] + self.handle = cc_rh_subscription.handle + self.SM = cc_rh_subscription.SubscriptionManager + + self.config = {'rh_subscription': + {'username': 'scooby@do.com', + 'password': 'scooby-snacks' + }} + self.config_full = {'rh_subscription': + {'username': 'scooby@do.com', + 'password': 'scooby-snacks', + 'auto-attach': True, + 'service-level': 'self-support', + 'add-pool': ['pool1', 'pool2', 'pool3'], + 'enable-repo': ['repo1', 'repo2', 'repo3'], + 'disable-repo': ['repo4', 'repo5'] + }} + + def test_already_registered(self): + ''' + Emulates a system that is already registered. Ensure it gets + a non-ProcessExecution error from is_registered() + ''' + good_message = 'System is already registered' + with mock.patch.object(cc_rh_subscription.SubscriptionManager, + '_sub_man_cli') as mockobj: + self.log.info = mock.MagicMock(wraps=self.log.info) + self.handle(self.name, self.config, self.cloud_init, + self.log, self.args) + self.assertEqual(mockobj.call_count, 1) + self.log.info.assert_called_with(good_message) + + def test_simple_registration(self): + ''' + Simple registration with username and password + ''' + good_message = 'rh_subscription plugin completed successfully' + self.log.info = mock.MagicMock(wraps=self.log.info) + reg = "The system has been registered with ID:" \ + " 12345678-abde-abcde-1234-1234567890abc" + self.SM._sub_man_cli = mock.MagicMock( + side_effect=[util.ProcessExecutionError, (reg, 'bar')]) + self.handle(self.name, self.config, self.cloud_init, + self.log, self.args) + self.SM._sub_man_cli.assert_called_with_once(['subscription-manager', + 'identity']) + self.SM._sub_man_cli.assert_called_with_once( + ['subscription-manager', 'register', '--username=scooby@do.com', + '--password=scooby-snacks'], logstring_val=True) + + self.log.info.assert_called_with(good_message) + self.assertEqual(self.SM._sub_man_cli.call_count, 2) + + def test_full_registration(self): + ''' + Registration with auto-attach, service-level, adding pools, + and enabling and disabling yum repos + ''' + pool_message = 'Pool pool2 is not available' + repo_message1 = 'Repo repo1 is already enabled' + repo_message2 = 'Enabled the following repos: repo2, repo3' + good_message = 'rh_subscription plugin completed successfully' + self.log.warn = mock.MagicMock(wraps=self.log.warn) + self.log.debug = mock.MagicMock(wraps=self.log.debug) + reg = "The system has been registered with ID:" \ + " 12345678-abde-abcde-1234-1234567890abc" + self.SM._sub_man_cli = mock.MagicMock( + side_effect=[util.ProcessExecutionError, (reg, 'bar'), + ('Service level set to: self-support', ''), + ('pool1\npool3\n', ''), ('pool2\n', ''), ('', ''), + ('Repo ID: repo1\nRepo ID: repo5\n', ''), + ('Repo ID: repo2\nRepo ID: repo3\nRepo ID: ' + 'repo4', ''), + ('', '')]) + self.handle(self.name, self.config_full, self.cloud_init, + self.log, self.args) + self.log.warn.assert_any_call(pool_message) + self.log.debug.assert_any_call(repo_message1) + self.log.debug.assert_any_call(repo_message2) + self.log.info.assert_any_call(good_message) + self.SM._sub_man_cli.assert_called_with_once(['subscription-manager', + 'attach', '-pool=pool1', + '--pool=pool33']) + self.assertEqual(self.SM._sub_man_cli.call_count, 9) + + +class BadTests(unittest.TestCase): + def setUp(self): + super(BadTests, self).setUp() + self.name = "cc_rh_subscription" + self.cloud_init = None + self.log = logging.getLogger("bad_tests") + self.orig = self.log + self.args = [] + self.handle = cc_rh_subscription.handle + self.SM = cc_rh_subscription.SubscriptionManager + self.reg = "The system has been registered with ID:" \ + " 12345678-abde-abcde-1234-1234567890abc" + + self.config_no_password = {'rh_subscription': + {'username': 'scooby@do.com' + }} + + self.config_no_key = {'rh_subscription': + {'activation-key': '1234abcde', + }} + + self.config_service = {'rh_subscription': + {'username': 'scooby@do.com', + 'password': 'scooby-snacks', + 'service-level': 'self-support' + }} + + self.config_badpool = {'rh_subscription': + {'username': 'scooby@do.com', + 'password': 'scooby-snacks', + 'add-pool': 'not_a_list' + }} + self.config_badrepo = {'rh_subscription': + {'username': 'scooby@do.com', + 'password': 'scooby-snacks', + 'enable-repo': 'not_a_list' + }} + self.config_badkey = {'rh_subscription': + {'activation_key': 'abcdef1234', + 'org': '123', + }} + + def test_no_password(self): + ''' + Attempt to register without the password key/value + ''' + self.missing_info(self.config_no_password) + + def test_no_org(self): + ''' + Attempt to register without the org key/value + ''' + self.missing_info(self.config_no_key) + + def test_service_level_without_auto(self): + ''' + Attempt to register using service-level without the auto-attach key + ''' + good_message = 'The service-level key must be used in conjunction'\ + ' with the auto-attach key. Please re-run with '\ + 'auto-attach: True' + + self.log.warn = mock.MagicMock(wraps=self.log.warn) + self.SM._sub_man_cli = mock.MagicMock( + side_effect=[util.ProcessExecutionError, (self.reg, 'bar')]) + self.handle(self.name, self.config_service, self.cloud_init, + self.log, self.args) + self.log.warn.assert_any_call(good_message) + self.assertRaises(cc_rh_subscription.SubscriptionError) + self.assertEqual(self.SM._sub_man_cli.call_count, 1) + + def test_pool_not_a_list(self): + ''' + Register with pools that are not in the format of a list + ''' + good_message = "Pools must in the format of a list" + self.log.warn = mock.MagicMock(wraps=self.log.warn) + self.SM._sub_man_cli = mock.MagicMock( + side_effect=[util.ProcessExecutionError, (self.reg, 'bar')]) + self.handle(self.name, self.config_badpool, self.cloud_init, + self.log, self.args) + self.log.warn.assert_any_call(good_message) + self.assertRaises(cc_rh_subscription.SubscriptionError) + self.assertEqual(self.SM._sub_man_cli.call_count, 2) + + def test_repo_not_a_list(self): + ''' + Register with repos that are not in the format of a list + ''' + good_message = "Repo IDs must in the format of a list." + self.log.warn = mock.MagicMock(wraps=self.log.warn) + self.SM._sub_man_cli = mock.MagicMock( + side_effect=[util.ProcessExecutionError, (self.reg, 'bar')]) + self.handle(self.name, self.config_badrepo, self.cloud_init, + self.log, self.args) + self.log.warn.assert_any_call(good_message) + self.assertRaises(cc_rh_subscription.SubscriptionError) + self.assertEqual(self.SM._sub_man_cli.call_count, 2) + + def test_bad_key_value(self): + ''' + Attempt to register with a key that we don't know + ''' + good_message = 'activation_key is not a valid key for rh_subscription.'\ + ' Valid keys are: org, activation-key, username, '\ + 'password, disable-repo, enable-repo, add-pool,'\ + ' rhsm-baseurl, server-hostname, auto-attach, '\ + 'service-level' + self.log.warn = mock.MagicMock(wraps=self.log.warn) + self.SM._sub_man_cli = mock.MagicMock( + side_effect=[util.ProcessExecutionError, (self.reg, 'bar')]) + self.handle(self.name, self.config_badkey, self.cloud_init, + self.log, self.args) + self.assertRaises(cc_rh_subscription.SubscriptionError) + self.log.warn.assert_any_call(good_message) + self.assertEqual(self.SM._sub_man_cli.call_count, 1) + + def missing_info(self, config): + ''' + Helper def for tests that having missing information + ''' + good_message = "Unable to register system due to incomplete "\ + "information." + self.log.warn = mock.MagicMock(wraps=self.log.warn) + self.SM._sub_man_cli = mock.MagicMock( + side_effect=[util.ProcessExecutionError]) + self.handle(self.name, config, self.cloud_init, + self.log, self.args) + self.SM._sub_man_cli.assert_called_with(['subscription-manager', + 'identity']) + self.log.warn.assert_any_call(good_message) + self.assertEqual(self.SM._sub_man_cli.call_count, 1) |