Browse Source

HUE-5553 [metadata] Add new python API lib

Romain Rigaux 9 years ago
parent
commit
0e9ec14

+ 74 - 0
desktop/core/ext-py/navoptapi-0.1.0/PKG-INFO

@@ -0,0 +1,74 @@
+Metadata-Version: 1.1
+Name: navoptapi
+Version: 0.1.0
+Summary: Cloudera Navigator Optimizer Api
+Home-page: http://www.cloudera.com/
+Author: UNKNOWN
+Author-email: UNKNOWN
+License: Apache License 2.0
+Description: Cloudera Navigator Optimizer Api SDK
+        ==============================================
+        
+        This package provides a API SDK for Cloudera Navigator Optimizer.
+        
+        Install the package
+        ====================
+        sudo pip install navoptapi-0.1.0.tar.gz
+        
+        Example Usage for the Api
+        =========================
+        
+        * Import the module
+          from navoptapi.api_lib import *
+        
+        * Create NavOpt object passing hostname, access key and private key:
+            nav = ApiLib("navopt", "121.0.0.1", "e0819f3a-1e6f-4904-be69-5b704b299bbb", "-----BEGIN PRIVATE KEY-----\n..............\n-----END PRIVATE KEY-----")
+        
+        * Get Top Tables for a workload:
+          resp = nav.call_api("getTopTables", {"tenant" : "d6d54b73-2bab-e413-5376-a805f5d4ae53"})
+          print json.dumps(resp.json(), indent=2)
+        
+        * Get Top Databases for a workload:
+          resp = nav.call_api("getTopDataBases", {"tenant" : "d6d54b73-2bab-e413-5376-a805f5d4ae53"})
+          print json.dumps(resp.json(), indent=2)
+        
+        * Get Top Queries for a workload:
+            resp = nav.call_api("getTopQueries", {"tenant" : "d6d54b73-2bab-e413-5376-a805f5d4ae53"})
+            print json.dumps(resp.json(), indent=2)
+        
+        * Get Top Columns for a workload:
+            resp = nav.call_api("getTopColumns", {"tenant" : "d6d54b73-2bab-e413-5376-a805f5d4ae53", "dbTableList":["default.sample_07"]})
+            print json.dumps(resp.json(), indent=2)
+        
+        * Get Top Filters for a workload:
+            resp = nav.call_api("getTopFilters", {"tenant" : "d6d54b73-2bab-e413-5376-a805f5d4ae53", "dbTableList":["default.sample_07"]})
+            print json.dumps(resp.json(), indent=2)
+        
+        * Get Top Aggregates for a workload:
+            resp = nav.call_api("getTopAggs", {"tenant" : "d6d54b73-2bab-e413-5376-a805f5d4ae53", "dbTableList":["default.sample_07"]})
+            print json.dumps(resp.json(), indent=2)
+        
+        * Get Top Joins for a workload:
+            resp = nav.call_api("getTopJoins", {"tenant" : "d6d54b73-2bab-e413-5376-a805f5d4ae53", "dbTableList":["default.sample_07"]})
+            print json.dumps(resp.json(), indent=2)
+        
+        * Get Query Compatibility:
+            resp = nav.call_api("getQueryCompatible", {"tenant" : "d6d54b73-2bab-e413-5376-a805f5d4ae53", "query":"select supplier.s_acctbal, supplier.s_name, nation.n_name, part.p_partkey, part.p_mfgr, supplier.s_address, supplier.s_phone, supplier.s_comment from part, supplier, partsupp, nation, region where part.p_partkey = partsupp.ps_partkey and supplier.s_suppkey = partsupp.ps_suppkey and part.p_size = 15 and part.p_type like '%BRASS' and supplier.s_nationkey = nation.n_nationkey and nation.n_regionkey = region.r_regionkey and region.r_name = 'EUROPE' and partsupp.ps_supplycost = ( select min(partsupp.ps_supplycost) from partsupp, supplier, nation, region where part.p_partkey = partsupp.ps_partkey and supplier.s_suppkey = partsupp.ps_suppkey and supplier.s_nationkey = nation.n_nationkey and nation.n_regionkey = region.r_regionkey and region.r_name = 'EUROPE' ) order by supplier.s_acctbal desc, nation.n_name, supplier.s_name, part.p_partkey", "sourcePlatform":"teradata", "targetPlatform":"hive"})
+            print json.dumps(resp.json(), indent=2)
+        
+        * Get Query Risk Analysis:
+            resp = nav.call_api("getQueryRisk", {"tenant" : "d6d54b73-2bab-e413-5376-a805f5d4ae53", "query": "select * from web_logs limit 10"})
+            print json.dumps(resp.json(), indent=2)
+        
+        
+Platform: UNKNOWN
+Classifier: Development Status :: 3 - Alpha
+Classifier: Intended Audience :: Developers
+Classifier: Topic :: Software Development :: System Administrators
+Classifier: License :: OSI Approved :: Apache License 2.0
+Classifier: Natural Language :: English
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7,Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4

+ 54 - 0
desktop/core/ext-py/navoptapi-0.1.0/README.rst

@@ -0,0 +1,54 @@
+Cloudera Navigator Optimizer Api SDK
+==============================================
+
+This package provides a API SDK for Cloudera Navigator Optimizer.
+
+Install the package
+====================
+sudo pip install navoptapi-0.1.0.tar.gz
+
+Example Usage for the Api
+=========================
+
+* Import the module
+  from navoptapi.api_lib import *
+
+* Create NavOpt object passing hostname, access key and private key:
+  nav = ApiLib("navopt", "121.0.0.1", "e0819f3a-1e6f-4904-be69-5b704b299bbb", "-----BEGIN PRIVATE KEY-----\n..............\n-----END PRIVATE KEY-----")
+
+* Get Top Tables for a workload:
+  resp = nav.call_api("getTopTables", {"tenant" : "d6d54b73-2bab-e413-5376-a805f5d4ae53"})
+  print json.dumps(resp.json(), indent=2)
+
+* Get Top Databases for a workload:
+  resp = nav.call_api("getTopDataBases", {"tenant" : "d6d54b73-2bab-e413-5376-a805f5d4ae53"})
+  print json.dumps(resp.json(), indent=2)
+
+* Get Top Queries for a workload:
+    resp = nav.call_api("getTopQueries", {"tenant" : "d6d54b73-2bab-e413-5376-a805f5d4ae53"})
+    print json.dumps(resp.json(), indent=2)
+
+* Get Top Columns for a workload:
+    resp = nav.call_api("getTopColumns", {"tenant" : "d6d54b73-2bab-e413-5376-a805f5d4ae53", "dbTableList":["default.sample_07"]})
+    print json.dumps(resp.json(), indent=2)
+
+* Get Top Filters for a workload:
+    resp = nav.call_api("getTopFilters", {"tenant" : "d6d54b73-2bab-e413-5376-a805f5d4ae53", "dbTableList":["default.sample_07"]})
+    print json.dumps(resp.json(), indent=2)
+
+* Get Top Aggregates for a workload:
+    resp = nav.call_api("getTopAggs", {"tenant" : "d6d54b73-2bab-e413-5376-a805f5d4ae53", "dbTableList":["default.sample_07"]})
+    print json.dumps(resp.json(), indent=2)
+
+* Get Top Joins for a workload:
+    resp = nav.call_api("getTopJoins", {"tenant" : "d6d54b73-2bab-e413-5376-a805f5d4ae53", "dbTableList":["default.sample_07"]})
+    print json.dumps(resp.json(), indent=2)
+
+* Get Query Compatibility:
+    resp = nav.call_api("getQueryCompatible", {"tenant" : "d6d54b73-2bab-e413-5376-a805f5d4ae53", "query":"select supplier.s_acctbal, supplier.s_name, nation.n_name, part.p_partkey, part.p_mfgr, supplier.s_address, supplier.s_phone, supplier.s_comment from part, supplier, partsupp, nation, region where part.p_partkey = partsupp.ps_partkey and supplier.s_suppkey = partsupp.ps_suppkey and part.p_size = 15 and part.p_type like '%BRASS' and supplier.s_nationkey = nation.n_nationkey and nation.n_regionkey = region.r_regionkey and region.r_name = 'EUROPE' and partsupp.ps_supplycost = ( select min(partsupp.ps_supplycost) from partsupp, supplier, nation, region where part.p_partkey = partsupp.ps_partkey and supplier.s_suppkey = partsupp.ps_suppkey and supplier.s_nationkey = nation.n_nationkey and nation.n_regionkey = region.r_regionkey and region.r_name = 'EUROPE' ) order by supplier.s_acctbal desc, nation.n_name, supplier.s_name, part.p_partkey", "sourcePlatform":"teradata", "targetPlatform":"hive"})
+    print json.dumps(resp.json(), indent=2)
+
+* Get Query Risk Analysis:
+    resp = nav.call_api("getQueryRisk", {"tenant" : "d6d54b73-2bab-e413-5376-a805f5d4ae53", "query": "select * from web_logs limit 10"})
+    print json.dumps(resp.json(), indent=2)
+

+ 77 - 0
desktop/core/ext-py/navoptapi-0.1.0/navoptapi/__init__.py

@@ -0,0 +1,77 @@
+# Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+#
+# Modifications made by Cloudera are:
+#     Copyright (c) 2016 Cloudera, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"). You
+# may not use this file except in compliance with the License. A copy of
+# the License is located at
+#
+#     http://aws.amazon.com/apache2.0/
+#
+# or in the "license" file accompanying this file. This file is
+# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
+# ANY KIND, either express or implied. See the License for the specific
+# language governing permissions and limitations under the License.
+
+import os
+import re
+
+VERSION = "0.1.0"
+
+CCSCLI_ROOT = os.path.dirname(os.path.abspath(__file__))
+
+
+# Used to specify anonymous (unsigned) request signature
+UNSIGNED = object()
+
+
+SCALAR_TYPES = set(['string',
+                    'float',
+                    'integer',
+                    'long',
+                    'boolean',
+                    'double',
+                    'blob',
+                    'timestamp'])
+
+LIST_TYPE = 'array'
+OBJECT_TYPE = 'object'
+REF_KEY = '$ref'
+REF_NAME_PREFIX = '#/definitions/'
+
+COMPLEX_TYPES = set([OBJECT_TYPE,
+                     LIST_TYPE])
+
+DEFAULT_PROFILE_NAME = 'default'
+CCS_ACCESS_KEY_ID_KEY_NAME = 'ccs_access_key_id'
+CCS_PRIVATE_KEY_KEY_NAME = 'ccs_private_key'
+
+# Prepopulate the cache with special cases that don't match our regular
+# transformation.
+_xform_cache = {}
+_first_cap_regex = re.compile('(.)([A-Z][a-z]+)')
+_number_cap_regex = re.compile('([a-z])([0-9]+)')
+_end_cap_regex = re.compile('([a-z0-9])([A-Z])')
+# The regex below handles the special case where some acryonym
+# name is pluralized, e.g GatewayARNs, ListWebACLs, SomeCNAMEs.
+_special_case_transform = re.compile('[A-Z]{3,}s$')
+
+
+def xform_name(name, sep='_', _xform_cache=_xform_cache):
+    if sep in name:
+        # If the sep is in the name, assume that it's already
+        # transformed and return the string unchanged.
+        return name
+    key = (name, sep)
+    if key not in _xform_cache:
+        if _special_case_transform.search(name) is not None:
+            is_special = _special_case_transform.search(name)
+            matched = is_special.group()
+            # Replace something like CRNs, ACLs with _arns, _acls.
+            name = name[:-len(matched)] + sep + matched.lower()
+        s1 = _first_cap_regex.sub(r'\1' + sep + r'\2', name)
+        s2 = _number_cap_regex.sub(r'\1' + sep + r'\2', s1)
+        transformed = _end_cap_regex.sub(r'\1' + sep + r'\2', s2).lower()
+        _xform_cache[key] = transformed
+    return _xform_cache[key]

+ 300 - 0
desktop/core/ext-py/navoptapi-0.1.0/navoptapi/api_lib.py

@@ -0,0 +1,300 @@
+
+from base64 import urlsafe_b64encode
+from collections import namedtuple
+from collections import OrderedDict
+
+from email.utils import formatdate
+
+import json
+import logging
+import platform
+
+from urlparse import urlsplit
+
+from Crypto.Hash import SHA256
+from Crypto.PublicKey import RSA
+from Crypto.Signature import PKCS1_v1_5
+
+from requests import Request, Session
+
+import six
+
+
+LOG = logging.getLogger('ccscli.navopt')
+ROOT_LOGGER = logging.getLogger('')
+LOG_FORMAT = ('%(asctime)s - %(threadName)s - %(name)s - %(levelname)s - %(message)s')
+
+# Used to specify anonymous (unsigned) request signature
+UNSIGNED = object()
+DEFAULT_PROFILE_NAME = 'default'
+ReadOnlyCredentials = namedtuple('ReadOnlyCredentials',
+                                 ['access_key_id', 'private_key', 'method'])
+VERSION = "0.1.0"
+
+
+class Serializer(object):
+    DEFAULT_ENCODING = 'utf-8'
+
+    def serialize_to_request(self, parameters, operation_model):
+        # Don't serialize any parameter with a None value.
+        filtered_parameters = OrderedDict(
+            (k, v) for k, v in parameters.items() if v is not None)
+
+        serialized = {}
+        # serialized['method'] = operation_model.http['method']
+        # serialized['headers'] = {'Content-Type': 'application/json'}
+        # serialized['url_path'] = operation_model.http['requestUri']
+
+        serialized_body = OrderedDict()
+        if len(filtered_parameters) != 0:
+            self._serialize(serialized_body, filtered_parameters, None)
+
+        serialized['body'] = json.dumps(serialized_body).encode(self.DEFAULT_ENCODING)
+
+        return serialized
+
+    def _serialize(self, serialized, value, shape, key=None):
+        # serialize_method_name = '_serialize_type_%s' % shape.type_name
+        # method = getattr(self, serialize_method_name, self._default_serialize)
+        self._default_serialize(serialized, value, shape, key)
+
+    def _serialize_type_object(self, serialized, value, shape, key):
+        if key is not None:
+            # If a key is provided, this is a result of a recursive call, so we
+            # need to add a new child dict as the value of the passed in dict.
+            # Below we will add all the structure members to the new serialized
+            # dictionary we just created.
+            serialized[key] = OrderedDict()
+            serialized = serialized[key]
+
+        for member_key, member_value in value.items():
+            member_shape = shape.members[member_key]
+            self._serialize(serialized, member_value, member_shape, member_key)
+
+    def _serialize_type_array(self, serialized, value, shape, key):
+        array_obj = []
+        serialized[key] = array_obj
+        for array_item in value:
+            wrapper = {}
+            # JSON list serialization is the only case where we aren't setting
+            # a key on a dict.  We handle this by using a __current__ key on a
+            # wrapper dict to serialize each list item before appending it to
+            # the serialized list.
+            self._serialize(wrapper, array_item, shape.member, "__current__")
+            array_obj.append(wrapper["__current__"])
+
+    def _default_serialize(self, serialized, value, shape, key):
+        if key:
+            serialized[key] = value
+        else:
+            for member_key, member_value in value.items():
+                serialized[member_key] = member_value
+
+
+class Credentials(object):
+    """
+    Holds the credentials needed to authenticate requests.
+    """
+
+    def __init__(self, access_key_id, private_key, method):
+        self.access_key_id = access_key_id
+        self.private_key = private_key
+        self.method = method
+        self._normalize()
+
+    def ensure_unicode(self, s, encoding='utf-8', errors='strict'):
+        if isinstance(s, six.text_type):
+            return s
+        return unicode(s, encoding, errors)
+
+    def _normalize(self):
+        self.access_key_id = self.ensure_unicode(self.access_key_id)
+        self.private_key = self.ensure_unicode(self.private_key)
+
+    def get_frozen_credentials(self):
+        return ReadOnlyCredentials(self.access_key_id,
+                                   self.private_key,
+                                   self.method)
+
+
+class RSAv1Auth(object):
+    """
+    RSA signing with a SHA-256 hash returning a base64 encoded signature.
+    """
+    AUTH_METHOD_NAME = 'rsav1'
+
+    def __init__(self, credentials):
+        self.credentials = credentials
+
+    def sign_string(self, string_to_sign):
+        try:
+            # We expect the private key to be the an PKCS8 pem formatted string.
+            key = RSA.importKey(self.credentials.private_key)
+        except:
+            message = \
+                "Failed to import private key from: '%s'. The private key is " \
+                "corrupted or it is not in PKCS8 PEM format. The private key " \
+                "was extracted either from 'env' (environment variables), " \
+                "'shared-credentials-file' (a profile in the shared " \
+                "credential file, by default under ~/.ccs/credentials), or " \
+                "'auth-config-file' (a file containing the credentials whose " \
+                "location was supplied on the command line.)" % \
+                self.credentials.method
+            LOG.debug(message, exc_info=True)
+            raise Exception(message)
+        # We sign the hash.
+        h = SHA256.new(string_to_sign.encode('utf-8'))
+        signer = PKCS1_v1_5.new(key)
+        return urlsafe_b64encode(signer.sign(h)).strip().decode('utf-8')
+
+    def canonical_standard_headers(self, headers):
+        interesting_headers = ['content-type', 'x-ccs-date']
+        hoi = []
+        if 'x-ccs-date' in headers:
+            raise Exception("x-ccs-date found in headers!")
+        headers['x-ccs-date'] = self._get_date()
+        for ih in interesting_headers:
+            found = False
+            for key in headers:
+                lk = key.lower()
+                if headers[key] is not None and lk == ih:
+                    hoi.append(headers[key].strip())
+                    found = True
+            if not found:
+                hoi.append('')
+        return '\n'.join(hoi)
+
+    def canonical_string(self, method, split, headers):
+        cs = method.upper() + '\n'
+        cs += self.canonical_standard_headers(headers) + '\n'
+        cs += split.path + '\n'
+        cs += RSAv1Auth.AUTH_METHOD_NAME
+        return cs
+
+    def get_signature(self, method, split, headers):
+        string_to_sign = self.canonical_string(method, split, headers)
+        LOG.debug('StringToSign:\n%s', string_to_sign)
+        return self.sign_string(string_to_sign)
+
+    def add_auth(self, request):
+        if self.credentials is None:
+            return
+        LOG.debug("Calculating signature using RSAv1Auth.")
+        LOG.debug('HTTP request method: %s', request.method)
+        split = urlsplit(request.url)
+        signature = self.get_signature(request.method,
+                                       split,
+                                       request.headers)
+        self._inject_signature(request, signature)
+
+    def _get_date(self):
+        return formatdate(usegmt=True)
+
+    def _inject_signature(self, request, signature):
+        if 'x-ccs-auth' in request.headers:
+            raise Exception("x-ccs-auth found in headers!")
+        request.headers['x-ccs-auth'] = self._get_signature_header(signature)
+
+    def _get_signature_header(self, signature):
+        auth_params = OrderedDict()
+        auth_params['access_key_id'] = self.credentials.access_key_id
+        auth_params['auth_method'] = RSAv1Auth.AUTH_METHOD_NAME
+        encoded_auth_params = json.dumps(auth_params).encode('utf-8')
+        return "%s.%s" % (
+            urlsafe_b64encode(encoded_auth_params).strip().decode('utf-8'),
+            signature)
+
+AUTH_TYPE_MAPS = {
+    RSAv1Auth.AUTH_METHOD_NAME: RSAv1Auth,
+}
+
+
+class RequestSigner(object):
+    """
+    An object to sign requests before they go out over the wire using
+    one of the authentication mechanisms defined in ``auth.py``.
+    """
+    def __init__(self, signature_version, credentials):
+        self._signature_version = signature_version
+        self._credentials = credentials
+
+    @property
+    def signature_version(self):
+        return self._signature_version
+
+    def sign(self, request):
+        """
+        Sign a request before it goes out over the wire.
+        """
+        if self._signature_version != UNSIGNED:
+            signer = self.get_auth_instance(self._signature_version)
+            signer.add_auth(request)
+
+    def get_auth_instance(self, signature_version, **kwargs):
+        """
+        Get an auth instance which can be used to sign a request
+        using the given signature version.
+        """
+        cls = AUTH_TYPE_MAPS.get(signature_version)
+        if cls is None:
+            return
+        # If there's no credentials provided (i.e credentials is None),
+        # then we'll pass a value of "None" over to the auth classes,
+        # which already handle the cases where no credentials have
+        # been provided.
+        frozen_credentials = self._credentials.get_frozen_credentials()
+        kwargs['credentials'] = frozen_credentials
+        auth = cls(**kwargs)
+        return auth
+
+
+class ApiLib(object):
+
+    def __init__(self, service_name, host_name, access_key, private_key):
+        # get Credentials
+        self._access_key = access_key
+        self._private_key = private_key
+        self._endpoint_url = "http://"+host_name+":8982/"+service_name+"/"
+        self._service_name = service_name
+        self._cred = Credentials(access_key, private_key,
+                                 method='shared-credentials-file')
+        self._signer = RequestSigner(RSAv1Auth.AUTH_METHOD_NAME, self._cred)
+
+    def _build_user_agent_header(self):
+        return 'CCSCLI/%s Python/%s %s/%s' % (VERSION,
+                                              platform.python_version(),
+                                              platform.system(),
+                                              platform.release())
+
+    def _encode_headers(self, headers):
+        # In place encoding of headers to utf-8 if they are unicode.
+        for key, value in headers.items():
+            if isinstance(value, six.text_type):
+                # We have to do this because request.headers is not
+                # normal dictionary.  It has the (unintuitive) behavior
+                # of aggregating repeated setattr calls for the same
+                # key value.  For example:
+                # headers['foo'] = 'a'; headers['foo'] = 'b'
+                # list(headers) will print ['foo', 'foo'].
+                del headers[key]
+                headers[key] = value.encode('utf-8')
+
+    def call_api(self, operation_name, params):
+        if not operation_name or not params:
+            return
+
+        api_session = Session()
+        api_url = self._endpoint_url + operation_name
+        req = Request('POST', api_url)
+        prepped = req.prepare()
+        self._encode_headers(prepped.headers)
+        prepped.headers['Content-Type'] = 'application/json'
+        prepped.headers['User-Agent'] = self._build_user_agent_header()
+        self._signer.sign(prepped)
+        # prepare the body
+        serializer = Serializer()
+        serial_obj = serializer.serialize_to_request(params, None)
+        prepped.prepare_body(serial_obj['body'], None)
+        print "The object:", serial_obj
+        resp = api_session.send(prepped)
+        return resp

+ 127 - 0
desktop/core/ext-py/navoptapi-0.1.0/navoptapi/auth.py

@@ -0,0 +1,127 @@
+# Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+#
+# Modifications made by Cloudera are:
+#     Copyright (c) 2016 Cloudera, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"). You
+# may not use this file except in compliance with the License. A copy of
+# the License is located at
+#
+#     http://aws.amazon.com/apache2.0/
+#
+# or in the "license" file accompanying this file. This file is
+# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
+# ANY KIND, either express or implied. See the License for the specific
+# language governing permissions and limitations under the License.
+from base64 import urlsafe_b64encode
+from email.utils import formatdate
+import logging
+
+from ccscli.compat import json
+from ccscli.compat import OrderedDict
+from ccscli.compat import urlsplit
+from ccscli.exceptions import NoCredentialsError
+from Crypto.Hash import SHA256
+from Crypto.PublicKey import RSA
+from Crypto.Signature import PKCS1_v1_5
+
+
+LOG = logging.getLogger('ccscli.auth')
+
+
+class BaseSigner(object):
+    def add_auth(self, request):
+        raise NotImplementedError("add_auth")
+
+
+class RSAv1Auth(BaseSigner):
+    """
+    RSA signing with a SHA-256 hash returning a base64 encoded signature.
+    """
+    AUTH_METHOD_NAME = 'rsav1'
+
+    def __init__(self, credentials):
+        self.credentials = credentials
+
+    def sign_string(self, string_to_sign):
+        try:
+            # We expect the private key to be the an PKCS8 pem formatted string.
+            key = RSA.importKey(self.credentials.private_key)
+        except:
+            message = \
+                "Failed to import private key from: '%s'. The private key is " \
+                "corrupted or it is not in PKCS8 PEM format. The private key " \
+                "was extracted either from 'env' (environment variables), " \
+                "'shared-credentials-file' (a profile in the shared " \
+                "credential file, by default under ~/.ccs/credentials), or " \
+                "'auth-config-file' (a file containing the credentials whose " \
+                "location was supplied on the command line.)" % \
+                self.credentials.method
+            LOG.debug(message, exc_info=True)
+            raise Exception(message)
+        # We sign the hash.
+        h = SHA256.new(string_to_sign.encode('utf-8'))
+        signer = PKCS1_v1_5.new(key)
+        return urlsafe_b64encode(signer.sign(h)).strip().decode('utf-8')
+
+    def canonical_standard_headers(self, headers):
+        interesting_headers = ['content-type', 'x-ccs-date']
+        hoi = []
+        if 'x-ccs-date' in headers:
+            raise Exception("x-ccs-date found in headers!")
+        headers['x-ccs-date'] = self._get_date()
+        for ih in interesting_headers:
+            found = False
+            for key in headers:
+                lk = key.lower()
+                if headers[key] is not None and lk == ih:
+                    hoi.append(headers[key].strip())
+                    found = True
+            if not found:
+                hoi.append('')
+        return '\n'.join(hoi)
+
+    def canonical_string(self, method, split, headers):
+        cs = method.upper() + '\n'
+        cs += self.canonical_standard_headers(headers) + '\n'
+        cs += split.path + '\n'
+        cs += RSAv1Auth.AUTH_METHOD_NAME
+        return cs
+
+    def get_signature(self, method, split, headers):
+        string_to_sign = self.canonical_string(method, split, headers)
+        LOG.debug('StringToSign:\n%s', string_to_sign)
+        return self.sign_string(string_to_sign)
+
+    def add_auth(self, request):
+        if self.credentials is None:
+            raise NoCredentialsError
+        LOG.debug("Calculating signature using RSAv1Auth.")
+        LOG.debug('HTTP request method: %s', request.method)
+        split = urlsplit(request.url)
+        signature = self.get_signature(request.method,
+                                       split,
+                                       request.headers)
+        self._inject_signature(request, signature)
+
+    def _get_date(self):
+        return formatdate(usegmt=True)
+
+    def _inject_signature(self, request, signature):
+        if 'x-ccs-auth' in request.headers:
+            raise Exception("x-ccs-auth found in headers!")
+        request.headers['x-ccs-auth'] = self._get_signature_header(signature)
+
+    def _get_signature_header(self, signature):
+        auth_params = OrderedDict()
+        auth_params['access_key_id'] = self.credentials.access_key_id
+        auth_params['auth_method'] = RSAv1Auth.AUTH_METHOD_NAME
+        encoded_auth_params = json.dumps(auth_params).encode('utf-8')
+        return "%s.%s" % (
+            urlsafe_b64encode(encoded_auth_params).strip().decode('utf-8'),
+            signature)
+
+
+AUTH_TYPE_MAPS = {
+    RSAv1Auth.AUTH_METHOD_NAME: RSAv1Auth,
+}

+ 9 - 0
desktop/core/ext-py/navoptapi-0.1.0/setup.cfg

@@ -0,0 +1,9 @@
+[flake8]
+max-line-length = 90
+import-order-style = google
+
+[egg_info]
+tag_build = 
+tag_date = 0
+tag_svn_revision = 0
+

+ 64 - 0
desktop/core/ext-py/navoptapi-0.1.0/setup.py

@@ -0,0 +1,64 @@
+# Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+#
+# Modifications made by Cloudera are:
+#     Copyright (c) 2016 Cloudera, Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"). You
+# may not use this file except in compliance with the License. A copy of
+# the License is located at
+#
+#     http://aws.amazon.com/apache2.0/
+#
+# or in the "license" file accompanying this file. This file is
+# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
+# ANY KIND, either express or implied. See the License for the specific
+# language governing permissions and limitations under the License.
+
+from codecs import open
+from os import path
+import sys
+
+from setuptools import find_packages
+from setuptools import setup
+
+here = path.abspath(path.dirname(__file__))
+
+# Get the long description from the README file
+with open(path.join(here, 'README.rst'), encoding='utf-8') as f:
+    long_description = f.read()
+
+requirements = ["python-dateutil>=2.1,<3.0.0",
+                "docutils>=0.10",
+                "pyyaml>=3.11",
+                "colorama>=0.2.5,<=0.3.3",
+                "pycrypto>=2.6.1",
+                "requests>=2.9.1"]
+if sys.version_info[:2] == (2, 6):
+    requirements.append("argparse>=1.1")
+    requirements.append("ordereddict==1.1")
+    requirements.append("simplejson==3.3.0")
+
+setup(
+    name='navoptapi',
+    version='0.1.0',
+    description='Cloudera Navigator Optimizer Api',
+    long_description=long_description,
+    url='http://www.cloudera.com/',
+    license='Apache License 2.0',
+    classifiers=[
+        'Development Status :: 3 - Alpha',
+        'Intended Audience :: Developers',
+        'Topic :: Software Development :: System Administrators',
+        'License :: OSI Approved :: Apache License 2.0',
+        'Natural Language :: English',
+        'Programming Language :: Python',
+        'Programming Language :: Python :: 2.6',
+        'Programming Language :: Python :: 2.7,'
+        'Programming Language :: Python :: 3',
+        'Programming Language :: Python :: 3.3',
+        'Programming Language :: Python :: 3.4'
+    ],
+    packages=find_packages(exclude=['tests']),
+    include_package_data=True,
+    install_requires=requirements,
+)

+ 1 - 2
desktop/libs/metadata/src/metadata/optimizer_client.py

@@ -191,7 +191,6 @@ class OptimizerApi(object):
 
     return self._token
 
-
   def _exec(self, command, args):
     data = None
     response = {'status': 'error'}
@@ -322,7 +321,7 @@ class OptimizerApi(object):
     ])
 
 
-  def top_tables(self, workfloadId=None, database_name='default'):
+  def top_tables(self, workfloadId=None, database_name='default'):        
     return self._exec('get-top-tables', [
         '--tenant', self._product_name,
         '--db-name', database_name.lower()