diff --git a/python-rhsm.spec b/python-rhsm.spec index 0318cc1..d2e00c1 100644 --- a/python-rhsm.spec +++ b/python-rhsm.spec @@ -13,7 +13,7 @@ Name: python-rhsm Version: 1.15.4 -Release: 2%{?dist} +Release: 3%{?dist} Summary: A Python library to communicate with a Red Hat Unified Entitlement Platform Group: Development/Libraries @@ -74,6 +74,11 @@ rm -rf %{buildroot} %attr(644,root,root) %{_sysconfdir}/rhsm/ca/*.pem %changelog +* Wed Aug 12 2015 Chris Rog 1.15.4-3 +- Add user-agent to rhsm requests. (alikins@redhat.com) +- 1247890: KeyErrors are now caught when checking manager capabilities + (csnyder@redhat.com) + * Thu Jul 23 2015 Chris Rog 1.15.4-2 - Fedora 20 has been retired. (awood@redhat.com) diff --git a/rel-eng/packages/python-rhsm b/rel-eng/packages/python-rhsm index ace4dde..c370a05 100644 --- a/rel-eng/packages/python-rhsm +++ b/rel-eng/packages/python-rhsm @@ -1 +1 @@ -1.15.4-2 ./ +1.15.4-3 ./ diff --git a/rel-eng/tito.props b/rel-eng/tito.props index fd057f6..6c86a16 100644 --- a/rel-eng/tito.props +++ b/rel-eng/tito.props @@ -1,4 +1,4 @@ -[buildconfig] -builder = tito.builder.Builder -tagger = tito.tagger.VersionTagger +[globalconfig] +default_builder = tito.distributionbuilder.DistributionBuilder +default_tagger = tito.tagger.ReleaseTagger diff --git a/src/rhsm/connection.py b/src/rhsm/connection.py index 465405d..f5cbbdf 100644 --- a/src/rhsm/connection.py +++ b/src/rhsm/connection.py @@ -43,7 +43,7 @@ except ImportError: subman_version = "unknown" from rhsm import ourjson as json -from rhsm.utils import get_env_proxy_info +from rhsm import utils global_socket_timeout = 60 timeout_altered = None @@ -115,6 +115,7 @@ def drift_check(utc_time_string, hours=1): return drift + class ConnectionException(Exception): pass @@ -264,7 +265,6 @@ class ContentConnection(object): self.username = username self.password = password self.ssl_verify_depth = ssl_verify_depth - self.timeout_altered = False # get the proxy information from the environment variable @@ -275,13 +275,17 @@ class ContentConnection(object): 'proxy_port': '', 'proxy_password': ''} else: - info = get_env_proxy_info() + info = utils.get_env_proxy_info() self.proxy_hostname = proxy_hostname or config.get('server', 'proxy_hostname') or info['proxy_hostname'] self.proxy_port = proxy_port or config.get('server', 'proxy_port') or info['proxy_port'] self.proxy_user = proxy_user or config.get('server', 'proxy_user') or info['proxy_username'] self.proxy_password = proxy_password or config.get('server', 'proxy_password') or info['proxy_password'] + @property + def user_agent(self): + return "RHSM-content/1.0 (cmd=%s)" % utils.cmd_name(sys.argv) + def _request(self, request_type, handler, body=None): # See note in Restlib._request context = SSL.Context("sslv23") @@ -304,7 +308,11 @@ class ContentConnection(object): set_default_socket_timeout_if_python_2_3() - conn.request("GET", handler, body="", headers={"Host": "%s:%s" % (self.host, self.ssl_port), "Content-Length": "0"}) + conn.request("GET", handler, + body="", + headers={"Host": "%s:%s" % (self.host, self.ssl_port), + "Content-Length": "0", + "User-Agent": self.user_agent}) response = conn.getresponse() result = { "content": response.read(), @@ -383,6 +391,9 @@ class Restlib(object): self.apihandler = apihandler lc = _get_locale() + # Default, updated by UepConnection + self.user_agent = "python-rhsm-user-agent" + self.headers = {"Content-type": "application/json", "Accept": "application/json", "x-python-rhsm-version": python_rhsm_version, @@ -492,6 +503,7 @@ class Restlib(object): else: conn = httpslib.HTTPSConnection(self.host, self.ssl_port, ssl_context=context) + if info is not None: body = json.dumps(info, default=json.encode) else: @@ -499,6 +511,9 @@ class Restlib(object): log.debug("Making request: %s %s" % (request_type, handler)) + if self.user_agent: + self.headers['User-Agent'] = self.user_agent + headers = self.headers if body is None: headers = dict(self.headers.items() + @@ -665,7 +680,7 @@ class UEPConnection: 'proxy_port': '', 'proxy_password': ''} else: - info = get_env_proxy_info() + info = utils.get_env_proxy_info() self.proxy_hostname = proxy_hostname or config.get('server', 'proxy_hostname') or info['proxy_hostname'] self.proxy_port = proxy_port or config.get('server', 'proxy_port') or info['proxy_port'] @@ -731,6 +746,8 @@ class UEPConnection: ssl_verify_depth=self.ssl_verify_depth) auth_description = "auth=none" + self.conn.user_agent = "RHSM/1.0 (cmd=%s)" % utils.cmd_name(sys.argv) + self.resources = None self.capabilities = None connection_description = "" @@ -771,21 +788,27 @@ class UEPConnection: def _load_manager_capabilities(self): """ Loads manager capabilities by doing a GET on the status - resource located at '/status/' - """ - self.capabilities = {} - self.capabilities = self.conn.request_get("/status/") - self.capabilities = self.capabilities['managerCapabilities'] - log.debug("Server has the following capabilities: %s", - self.capabilities) + resource located at '/status' + """ + status = self.getStatus() + capabilities = status.get('managerCapabilities') + if capabilities is None: + log.debug("The status retrieved did not \ + include key 'managerCapabilities'.\nStatus:'%s'" % status) + capabilities = [] + elif isinstance(capabilities, list) and not capabilities: + log.debug("The managerCapabilities list \ + was empty\nStatus:'%s'" % status) + else: + log.debug("Server has the following capabilities: %s", capabilities) + return capabilities def has_capability(self, capability): """ Check if the server we're connected to has a particular capability. """ if self.capabilities is None: - self._load_manager_capabilities() - + self.capabilities = self._load_manager_capabilities() return capability in self.capabilities def shutDown(self): diff --git a/src/rhsm/utils.py b/src/rhsm/utils.py index dd41161..f1a7f4e 100644 --- a/src/rhsm/utils.py +++ b/src/rhsm/utils.py @@ -17,6 +17,7 @@ import gettext import os import re from urlparse import urlparse + from rhsm.config import DEFAULT_PROXY_PORT _ = lambda x: gettext.ldgettext("rhsm", x) @@ -232,3 +233,24 @@ def get_env_proxy_info(): else: the_proxy['proxy_port'] = int(info[3]) return the_proxy + + +def cmd_name(argv): + """Attempt to get a meaningful command name from argv. + + This handles cases where argv[0] isn't helpful (for + example, '/usr/bin/python' or '__main__.py'. + """ + argv0 = os.path.basename(argv[0]) + argvdir = os.path.dirname(argv[0]) + head, tail = os.path.split(argvdir) + + cmd_name_string = argv0 + # initial-setup is launched as 'python -m initial_setup', so + # sys.argv looks like + # ['/usr/lib/python2.7/site-packages/initial_setup/__main__.py'], + # so we look for initial_setup in the exe path. + if tail == "initial_setup": + cmd_name_string = "initial-setup" + + return cmd_name_string diff --git a/test/unit/connection-tests.py b/test/unit/connection-tests.py index 6e449ad..8a0091d 100644 --- a/test/unit/connection-tests.py +++ b/test/unit/connection-tests.py @@ -37,6 +37,26 @@ class ConnectionTests(unittest.TestCase): self.cp = UEPConnection(username="dummy", password="dummy", handler="/Test/", insecure=True) + def test_load_manager_capabilities(self): + expected_capabilities = ['hypervisors_async', 'cores'] + proper_status = {'version':'1', + 'result':True, + 'managerCapabilities':expected_capabilities} + improper_status = dict.copy(proper_status) + # Remove the managerCapabilities key from the dict + del improper_status['managerCapabilities'] + self.cp.conn = Mock() + # The first call will return the proper_status, the second, the improper + # status + original_getStatus = self.cp.getStatus + self.cp.getStatus = Mock(side_effect=[proper_status, + improper_status]) + actual_capabilities = self.cp._load_manager_capabilities() + self.assertEquals(sorted(actual_capabilities), + sorted(expected_capabilities)) + self.assertEquals([], self.cp._load_manager_capabilities()) + self.cp.getStatus = original_getStatus + def test_get_environment_by_name_requires_owner(self): self.assertRaises(Exception, self.cp.getEnvironment, None, {"name": "env name"}) diff --git a/test/unit/util-tests.py b/test/unit/util-tests.py index c650d74..4d4a65b 100644 --- a/test/unit/util-tests.py +++ b/test/unit/util-tests.py @@ -6,7 +6,7 @@ from rhsm.utils import remove_scheme, get_env_proxy_info, \ ServerUrlParseErrorEmpty, ServerUrlParseErrorNone, \ ServerUrlParseErrorPort, ServerUrlParseErrorScheme, \ ServerUrlParseErrorJustScheme, has_bad_scheme, has_good_scheme, \ - parse_url + parse_url, cmd_name from rhsm.config import DEFAULT_PORT, DEFAULT_PREFIX, DEFAULT_HOSTNAME, \ DEFAULT_CDN_HOSTNAME, DEFAULT_CDN_PORT, DEFAULT_CDN_PREFIX @@ -379,3 +379,45 @@ class TestProxyInfo(unittest.TestCase): self.assertEquals(None, proxy_info["proxy_password"]) self.assertEquals("host", proxy_info["proxy_hostname"]) self.assertEquals(int("1111"), proxy_info["proxy_port"]) + + +class TestCmdName(unittest.TestCase): + def test_usr_sbin(self): + argv = ['/usr/sbin/subscription-manager', 'list'] + self.assertEquals("subscription-manager", cmd_name(argv)) + + def test_bin(self): + argv = ['bin/subscription-manager', 'subscribe', '--auto'] + self.assertEquals("subscription-manager", cmd_name(argv)) + + def test_sbin(self): + argv = ['/sbin/subscription-manager', 'list'] + self.assertEquals("subscription-manager", cmd_name(argv)) + + def test_subscription_manager_gui(self): + argv = ['/sbin/subscription-manager-gui'] + self.assertEquals("subscription-manager-gui", cmd_name(argv)) + + def test_initial_setup(self): + argv = ['/usr/lib/python2.7/site-packages/initial_setup/__main__.py'] + self.assertEquals("initial-setup", cmd_name(argv)) + + def test_yum(self): + argv = ['/bin/yum', 'install', 'zsh'] + self.assertEquals("yum", cmd_name(argv)) + + def test_rhsmcertd_worker(self): + argv = ['/usr/libexec/rhsmcertd-worker'] + self.assertEquals("rhsmcertd-worker", cmd_name(argv)) + + def test_rhsm_debug(self): + argv = ['/bin/rhsm-debug'] + self.assertEquals("rhsm-debug", cmd_name(argv)) + + def test_virt_who(self): + argv = ['/usr/share/virt-who/virtwho.py'] + self.assertEquals("virtwho.py", cmd_name(argv)) + + def test_rhsmd(self): + argv = ['/usr/libexec/rhsmd', '-i', '-f', 'valid'] + self.assertEquals("rhsmd", cmd_name(argv))