Browse Source

HUE-7026 [editor] Add an HBase connector

Connector is enabled even if not in Hue ini.

[hbase]
  hbase_clusters=(Clusterteam|hue-team-c511-1.gce.cloudera.com:9090)

     $.post("/notebook/api/autocomplete/", {
        snippet: ko.mapping.toJSON({"type":"hbase"})
      }, function(data) {
        console.log(ko.mapping.toJSON(data));
      });

{"status":0,"databases":["Clusterteam"]}

     $.post("/notebook/api/autocomplete/Clusterteam", {
        snippet: ko.mapping.toJSON({"type":"hbase"})
      }, function(data) {
        console.log(ko.mapping.toJSON(data));
      });

{"status":0,"tables_meta":["analytics_demo","document_demo"]}
Romain Rigaux 8 years ago
parent
commit
90cc1a8f01

+ 11 - 10
apps/hbase/src/hbase/api.py

@@ -91,16 +91,17 @@ class HbaseApi(object):
   def connectCluster(self, name):
     _security = self._get_security()
     target = self.getCluster(name)
-    client = thrift_util.get_client(get_client_type(),
-                                  target['host'],
-                                  target['port'],
-                                  service_name="Hue HBase Thrift Client for %s" % name,
-                                  kerberos_principal=_security['kerberos_principal_short_name'],
-                                  use_sasl=_security['use_sasl'],
-                                  timeout_seconds=30,
-                                  transport=conf.THRIFT_TRANSPORT.get(),
-                                  transport_mode='http' if is_using_thrift_http() else 'socket',
-                                  http_url=('https://' if is_using_thrift_ssl() else 'http://') + target['host'] + ':' + str(target['port'])
+    client = thrift_util.get_client(
+        get_client_type(),
+        target['host'],
+        target['port'],
+        service_name="Hue HBase Thrift Client for %s" % name,
+        kerberos_principal=_security['kerberos_principal_short_name'],
+        use_sasl=_security['use_sasl'],
+        timeout_seconds=30,
+        transport=conf.THRIFT_TRANSPORT.get(),
+        transport_mode='http' if is_using_thrift_http() else 'socket',
+        http_url=('https://' if is_using_thrift_ssl() else 'http://') + target['host'] + ':' + str(target['port'])
     )
 
     return client

+ 12 - 1
desktop/libs/notebook/src/notebook/connectors/base.py

@@ -210,6 +210,7 @@ class Notebook(object):
 
 def get_api(request, snippet):
   from notebook.connectors.dataeng import DataEngApi
+  from notebook.connectors.hbase import HBaseApi
   from notebook.connectors.hiveserver2 import HS2Api
   from notebook.connectors.jdbc import JdbcApi
   from notebook.connectors.rdbms import RdbmsApi
@@ -223,7 +224,15 @@ def get_api(request, snippet):
     return OozieApi(user=request.user, request=request)
 
   interpreter = [interpreter for interpreter in get_ordered_interpreters(request.user) if interpreter['type'] == snippet['type']]
-  if not interpreter:
+  if snippet['type'] == 'hbase':
+    interpreter = [{
+      'name': 'hbase',
+      'type': 'hbase',
+      'interface': 'hbase',
+      'options': {},
+      'is_sql': False
+    }]
+  elif not interpreter:
     raise PopupException(_('Snippet type %(type)s is not configured in hue.ini') % snippet)
   interpreter = interpreter[0]
   interface = interpreter['interface']
@@ -251,6 +260,8 @@ def get_api(request, snippet):
     return JdbcApi(request.user, interpreter=interpreter)
   elif interface == 'solr':
     return SolrApi(request.user, interpreter=interpreter)
+  elif interface == 'hbase':
+    return HBaseApi(request.user)
   elif interface == 'pig':
     return OozieApi(user=request.user, request=request) # Backward compatibility until Hue 4
   else:

+ 75 - 0
desktop/libs/notebook/src/notebook/connectors/hbase.py

@@ -0,0 +1,75 @@
+#!/usr/bin/env python
+# Licensed to Cloudera, Inc. under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  Cloudera, Inc. licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License 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 __future__ import absolute_import
+
+import logging
+
+from django.core.urlresolvers import reverse
+from django.utils.translation import ugettext as _
+
+from desktop.lib.exceptions_renderable import PopupException
+from desktop.lib.i18n import force_unicode
+
+from notebook.connectors.base import Api, QueryError
+
+
+LOG = logging.getLogger(__name__)
+
+
+try:
+  from hbase.api import HbaseApi
+except ImportError, e:
+  LOG.warn("HBase app is not enabled: %s" % e)
+
+
+def query_error_handler(func):
+  def decorator(*args, **kwargs):
+    try:
+      return func(*args, **kwargs)
+    except Exception, e:
+      message = force_unicode(str(e))
+      raise QueryError(message)
+  return decorator
+
+
+class HBaseApi(Api):
+
+  @query_error_handler
+  def autocomplete(self, snippet, database=None, table=None, column=None, nested=None):
+    db = HbaseApi(self.user)
+    cluster_name = database
+
+    response = {}
+
+    try:
+      if database is None:
+        response['databases'] = [cluster['name'] for cluster in db.getClusters()]
+      elif table is None:
+        tables_meta = db.getTableList(cluster_name)
+        response['tables_meta'] = [_table['name'] for _table in tables_meta if _table['enabled']]
+      elif column is None:
+        tables_meta = db.get(cluster_name, table)
+        response['columns'] = []
+      else:
+        raise PopupException('Could not find column `%s`.`%s`.`%s`' % (database, table, column))
+    except Exception, e:
+      LOG.warn('Autocomplete data fetching error: %s' % e)
+      response['code'] = 500
+      response['error'] = e.message
+
+    return response