Ver código fonte

[beeswax] Support mutliple Beeswaxd servers

Updated all the db_utils.get_client() to make the Query Server configurable
Adding server_name, server_host, server_port to 'QueryHistory' model
Adding a support_ddl flag for retrieving a Query Server supporting DDL
Adding QueryServer select box to 'Query Editor' and 'Configuration' pages
Displaying 'Query Server' name in 'History' page
Loading query servers clusters from config
Adding DB schema migration file
Adding cookie for remembering the previously selected 'Query Server'
INI files updated and old beeswax configuration made backward compatible
Romain Rigaux 13 anos atrás
pai
commit
aa2049edbd
39 arquivos alterados com 576 adições e 239 exclusões
  1. 34 2
      apps/beeswax/src/beeswax/conf.py
  2. 1 1
      apps/beeswax/src/beeswax/data_export.py
  3. 49 14
      apps/beeswax/src/beeswax/db_utils.py
  4. 16 4
      apps/beeswax/src/beeswax/forms.py
  5. 8 8
      apps/beeswax/src/beeswax/management/0003_auto__chg_field_queryhistory_query.py
  6. 0 1
      apps/beeswax/src/beeswax/management/commands/beeswax_install_examples.py
  7. 6 1
      apps/beeswax/src/beeswax/management/commands/beeswax_server.py
  8. 7 7
      apps/beeswax/src/beeswax/migrations/0001_initial.py
  9. 8 8
      apps/beeswax/src/beeswax/migrations/0002_auto__add_field_queryhistory_notify.py
  10. 110 0
      apps/beeswax/src/beeswax/migrations/0003_auto__add_field_queryhistory_server_name__add_field_queryhistory_serve.py
  11. 6 0
      apps/beeswax/src/beeswax/models.py
  12. 1 1
      apps/beeswax/src/beeswax/templates/choose_delimiter.mako
  13. 1 1
      apps/beeswax/src/beeswax/templates/choose_file.mako
  14. 26 8
      apps/beeswax/src/beeswax/templates/configuration.mako
  15. 1 1
      apps/beeswax/src/beeswax/templates/confirm.html
  16. 1 1
      apps/beeswax/src/beeswax/templates/create_table_index.mako
  17. 5 5
      apps/beeswax/src/beeswax/templates/create_table_manually.mako
  18. 1 1
      apps/beeswax/src/beeswax/templates/define_columns.mako
  19. 2 2
      apps/beeswax/src/beeswax/templates/describe_partitions.mako
  20. 5 3
      apps/beeswax/src/beeswax/templates/describe_table.mako
  21. 22 7
      apps/beeswax/src/beeswax/templates/execute.mako
  22. 1 1
      apps/beeswax/src/beeswax/templates/index.mako
  23. 1 1
      apps/beeswax/src/beeswax/templates/layout.mako
  24. 2 2
      apps/beeswax/src/beeswax/templates/list_designs.mako
  25. 80 75
      apps/beeswax/src/beeswax/templates/list_history.mako
  26. 1 1
      apps/beeswax/src/beeswax/templates/load_table.mako
  27. 2 2
      apps/beeswax/src/beeswax/templates/my_queries.mako
  28. 4 1
      apps/beeswax/src/beeswax/templates/parameterization.mako
  29. 1 1
      apps/beeswax/src/beeswax/templates/save_results.mako
  30. 2 2
      apps/beeswax/src/beeswax/templates/show_tables.mako
  31. 2 2
      apps/beeswax/src/beeswax/templates/watch_results.mako
  32. 2 2
      apps/beeswax/src/beeswax/templates/watch_wait.mako
  33. 10 4
      apps/beeswax/src/beeswax/test_base.py
  34. 38 17
      apps/beeswax/src/beeswax/tests.py
  35. 65 44
      apps/beeswax/src/beeswax/views.py
  36. 1 1
      apps/jobsub/src/jobsub/conf.py
  37. 27 3
      desktop/conf.dist/hue.ini
  38. 26 3
      desktop/conf/pseudo-distributed.ini.tmpl
  39. 1 1
      desktop/core/src/desktop/lib/thrift_util.py

+ 34 - 2
apps/beeswax/src/beeswax/conf.py

@@ -14,23 +14,55 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # See the License for the specific language governing permissions and
 # limitations under the License.
 # limitations under the License.
-"""Configuration options for the Hive UI (Beeswax)."""
-from desktop.lib.conf import Config, coerce_bool
 import os.path
 import os.path
+
 from django.utils.translation import ugettext_lazy as _
 from django.utils.translation import ugettext_lazy as _
 
 
+from desktop.lib.conf import Config, UnspecifiedConfigSection, ConfigSection, coerce_bool
+
+
+QUERY_SERVERS = UnspecifiedConfigSection(
+  "query_servers",
+  help=_("One entry for each Query Server that can execute some queries."),
+  each=ConfigSection(
+    help=_("Information about a single Query Server"),
+    members=dict(
+      SERVER_HOST = Config(
+        key="server_host",
+        help=_("Host where the Query Server Thrift daemon is running."),
+        private=True,
+        default="localhost"),
+      SERVER_PORT = Config(
+        key="server_port",
+        help=_("Configure the port the Query Server Thrift server."),
+        default=8002,
+        type=int),
+      SUPPORT_DDL = Config(
+        key='support_ddl',
+        default=True,
+        type=coerce_bool,
+        help=_('If DDL queries are supported (e.g. DROP can be sent directly to this server).'))
+    )
+  )
+)
+
+# Deprecated! To remove in Hue 3
+# Multiple sections are now available in QUERY_SERVERS
 BEESWAX_SERVER_HOST = Config(
 BEESWAX_SERVER_HOST = Config(
   key="beeswax_server_host",
   key="beeswax_server_host",
   help=_("Host where Beeswax server Thrift daemon is running."),
   help=_("Host where Beeswax server Thrift daemon is running."),
   private=True,
   private=True,
   default="localhost")
   default="localhost")
 
 
+# Deprecated! To remove in Hue 3
+# Multiple sections are now available in QUERY_SERVERS
 BEESWAX_SERVER_PORT = Config(
 BEESWAX_SERVER_PORT = Config(
   key="beeswax_server_port",
   key="beeswax_server_port",
   help=_("Configure the port the Beeswax Thrift server runs on."),
   help=_("Configure the port the Beeswax Thrift server runs on."),
   default=8002,
   default=8002,
   type=int)
   type=int)
 
 
+
 BEESWAX_META_SERVER_HOST = Config(
 BEESWAX_META_SERVER_HOST = Config(
   key="beeswax_meta_server_host",
   key="beeswax_meta_server_host",
   help=_("Host where Beeswax internal metastore Thrift daemon is running."),
   help=_("Host where Beeswax internal metastore Thrift daemon is running."),

+ 1 - 1
apps/beeswax/src/beeswax/data_export.py

@@ -78,7 +78,7 @@ def data_generator(query_model, formatter):
   while True:
   while True:
     # Make sure that we have the next batch of ready results
     # Make sure that we have the next batch of ready results
     while results is None or not results.ready:
     while results is None or not results.ready:
-      results = db_utils.db_client().fetch(handle, start_over=is_first_row, fetch_size=-1)
+      results = db_utils.db_client(query_model.get_query_server()).fetch(handle, start_over=is_first_row, fetch_size=-1)
       if not results.ready:
       if not results.ready:
         time.sleep(_DATA_WAIT_SLEEP)
         time.sleep(_DATA_WAIT_SLEEP)
 
 

+ 49 - 14
apps/beeswax/src/beeswax/db_utils.py

@@ -23,7 +23,6 @@ import logging
 import thrift
 import thrift
 import time
 import time
 
 
-import desktop.conf
 import hadoop.cluster
 import hadoop.cluster
 
 
 from beeswax import conf
 from beeswax import conf
@@ -41,7 +40,8 @@ from django.utils.translation import ugettext_lazy as _
 
 
 LOG = logging.getLogger(__name__)
 LOG = logging.getLogger(__name__)
 
 
-def execute_directly(user, query_msg, design=None, notify=False):
+
+def execute_directly(user, query_msg, query_server=None, design=None, notify=False):
   """
   """
   execute_directly(user, query_msg [,design]) -> QueryHistory object
   execute_directly(user, query_msg [,design]) -> QueryHistory object
 
 
@@ -51,9 +51,15 @@ def execute_directly(user, query_msg, design=None, notify=False):
   design - The SavedQuery object (i.e. design) associated with this query.
   design - The SavedQuery object (i.e. design) associated with this query.
   notify - Whether to notify the user upon completion.
   notify - Whether to notify the user upon completion.
   """
   """
+  if query_server is None:
+    query_server = get_query_server(support_ddl=True) # For convenience with DDL queries only
+
   query_history = QueryHistory(
   query_history = QueryHistory(
                             owner=user,
                             owner=user,
                             query=query_msg.query,
                             query=query_msg.query,
+                            server_host='%(server_host)s' % query_server,
+                            server_port='%(server_port)d' % query_server,
+                            server_name='%(server_name)s' % query_server,
                             last_state=QueryHistory.STATE.submitted.index,
                             last_state=QueryHistory.STATE.submitted.index,
                             design=design,
                             design=design,
                             notify=notify)
                             notify=notify)
@@ -63,7 +69,7 @@ def execute_directly(user, query_msg, design=None, notify=False):
 
 
   # Now submit it
   # Now submit it
   try:
   try:
-    handle = db_client().query(query_msg)
+    handle = db_client(query_server).query(query_msg)
     if not handle or not handle.id:
     if not handle or not handle.id:
       # It really shouldn't happen
       # It really shouldn't happen
       msg = _("BeeswaxServer returning invalid handle for query id %(id)d [%(query)s]...") % \
       msg = _("BeeswaxServer returning invalid handle for query id %(id)d [%(query)s]...") % \
@@ -104,7 +110,7 @@ def wait_for_results(query_history, timeout_sec=30.0):
   curr = time.time()
   curr = time.time()
   end = curr + timeout_sec
   end = curr + timeout_sec
   while curr <= end:
   while curr <= end:
-    results = db_client().fetch(handle, START_OVER, fetch_size=-1)
+    results = db_client(query_history.get_query_server()).fetch(handle, START_OVER, fetch_size=-1)
     if results.ready:
     if results.ready:
       return results
       return results
     time.sleep(SLEEP_INTERVAL)
     time.sleep(SLEEP_INTERVAL)
@@ -112,13 +118,13 @@ def wait_for_results(query_history, timeout_sec=30.0):
   return None
   return None
 
 
 
 
-def execute_and_wait(user, query_msg, timeout_sec=30.0):
+def execute_and_wait(user, query_msg, query_server=None, timeout_sec=30.0):
   """
   """
   execute_and_wait(user, query_msg) -> results or None
   execute_and_wait(user, query_msg) -> results or None
   Combine execute_directly() and wait_for_results()
   Combine execute_directly() and wait_for_results()
   May raise BeeswaxException
   May raise BeeswaxException
   """
   """
-  history = execute_directly(user, query_msg)
+  history = execute_directly(user, query_msg, query_server)
   result = wait_for_results(history, timeout_sec)
   result = wait_for_results(history, timeout_sec)
   return result
   return result
 
 
@@ -141,7 +147,7 @@ def get_query_state(query_history):
   # Now check the server state
   # Now check the server state
   handle = QueryHandle(id=server_id, log_context=query_history.log_context)
   handle = QueryHandle(id=server_id, log_context=query_history.log_context)
   try:
   try:
-    server_state = db_client().get_state(handle)
+    server_state = db_client(query_history.get_query_server()).get_state(handle)
     return models.QueryHistory.STATE_MAP[server_state]
     return models.QueryHistory.STATE_MAP[server_state]
   except QueryNotFoundException:
   except QueryNotFoundException:
     LOG.debug("Query id %s has expired" % (query_history.id,))
     LOG.debug("Query id %s has expired" % (query_history.id,))
@@ -152,10 +158,39 @@ def get_query_state(query_history):
     return None
     return None
 
 
 
 
+def get_query_server(name='default', support_ddl=False):
+  if 'beeswax_server_host' in conf.QUERY_SERVERS['bind_to'] or 'beeswax_server_port' in conf.QUERY_SERVERS['bind_to']:
+    return {
+          'server_name': 'default',
+          'server_host': conf.BEESWAX_SERVER_HOST.get(),
+          'server_port': conf.BEESWAX_SERVER_PORT.get(),
+          'support_ddl': True,
+      }
+
+  servers = conf.QUERY_SERVERS
+
+  if support_ddl:
+    if not servers[name].SUPPORT_DDL.get():
+      for key in servers.keys():
+        if servers[key].SUPPORT_DDL.get():
+          name = key
+          break
+
+  config = servers[name]
+
+  query_server = {
+      'server_name': name,
+      'server_host': config.SERVER_HOST.get(),
+      'server_port': config.SERVER_PORT.get(),
+      'support_ddl': config.SUPPORT_DDL.get(),
+  }
+
+  return query_server
+
 #
 #
 # Note that thrift_util does client connection caching for us.
 # Note that thrift_util does client connection caching for us.
 #
 #
-def db_client():
+def db_client(query_server):
   """Get the Thrift client to talk to beeswax server"""
   """Get the Thrift client to talk to beeswax server"""
 
 
   class UnicodeBeeswaxClient(object):
   class UnicodeBeeswaxClient(object):
@@ -218,12 +253,12 @@ def db_client():
   use_sasl = cluster_conf is not None and cluster_conf.SECURITY_ENABLED.get()
   use_sasl = cluster_conf is not None and cluster_conf.SECURITY_ENABLED.get()
 
 
   client = thrift_util.get_client(BeeswaxService.Client,
   client = thrift_util.get_client(BeeswaxService.Client,
-                                conf.BEESWAX_SERVER_HOST.get(),
-                                conf.BEESWAX_SERVER_PORT.get(),
-                                service_name="Beeswax (Hive UI) Server",
-                                kerberos_principal="hue",
-                                use_sasl=use_sasl,
-                                timeout_seconds=conf.BEESWAX_SERVER_CONN_TIMEOUT.get())
+                                  query_server['server_host'],
+                                  query_server['server_port'],
+                                  service_name=query_server['server_name'],
+                                  kerberos_principal="hue",
+                                  use_sasl=use_sasl,
+                                  timeout_seconds=conf.BEESWAX_SERVER_CONN_TIMEOUT.get())
   return UnicodeBeeswaxClient(client)
   return UnicodeBeeswaxClient(client)
 
 
 
 

+ 16 - 4
apps/beeswax/src/beeswax/forms.py

@@ -22,9 +22,7 @@ import hive_metastore
 
 
 from desktop.lib.django_forms import simple_formset_factory, DependencyAwareForm
 from desktop.lib.django_forms import simple_formset_factory, DependencyAwareForm
 from desktop.lib.django_forms import ChoiceOrOtherField, MultiForm, SubmitButton
 from desktop.lib.django_forms import ChoiceOrOtherField, MultiForm, SubmitButton
-from beeswax import common
-from beeswax import db_utils
-from beeswax import models
+from beeswax import db_utils, common, conf, models
 from filebrowser.forms import PathField
 from filebrowser.forms import PathField
 
 
 
 
@@ -35,7 +33,21 @@ def query_form():
       settings=SettingFormSet,
       settings=SettingFormSet,
       file_resources=FileResourceFormSet,
       file_resources=FileResourceFormSet,
       functions=FunctionFormSet,
       functions=FunctionFormSet,
-      saveform=SaveForm)
+      saveform=SaveForm,
+      query_servers=QueryServerForm)
+
+
+class QueryServerForm(forms.Form):
+  server = forms.CharField(max_length=128, help_text=_t('Host:port of the query server.'))
+
+  def __init__(self, *args, **kwargs):
+    super(QueryServerForm, self).__init__(*args, **kwargs)
+
+    if conf.QUERY_SERVERS:
+      servers = ((server, server) for server in conf.QUERY_SERVERS.keys())
+    else:
+      servers = (('default', 'default'),)
+    self.fields['server'].widget = forms.Select(choices=servers)
 
 
 
 
 class SaveForm(forms.Form):
 class SaveForm(forms.Form):

+ 8 - 8
apps/beeswax/src/beeswax/management/0003_auto__chg_field_queryhistory_query.py

@@ -5,19 +5,19 @@ from south.v2 import SchemaMigration
 from django.db import models
 from django.db import models
 
 
 class Migration(SchemaMigration):
 class Migration(SchemaMigration):
-    
+
     def forwards(self, orm):
     def forwards(self, orm):
-        
+
         # Changing field 'QueryHistory.query'
         # Changing field 'QueryHistory.query'
         db.alter_column('beeswax_queryhistory', 'query', self.gf('django.db.models.fields.TextField')())
         db.alter_column('beeswax_queryhistory', 'query', self.gf('django.db.models.fields.TextField')())
-    
-    
+
+
     def backwards(self, orm):
     def backwards(self, orm):
-        
+
         # Changing field 'QueryHistory.query'
         # Changing field 'QueryHistory.query'
         db.alter_column('beeswax_queryhistory', 'query', self.gf('django.db.models.fields.CharField')(max_length=1024))
         db.alter_column('beeswax_queryhistory', 'query', self.gf('django.db.models.fields.CharField')(max_length=1024))
-    
-    
+
+
     models = {
     models = {
         'auth.group': {
         'auth.group': {
             'Meta': {'object_name': 'Group'},
             'Meta': {'object_name': 'Group'},
@@ -85,5 +85,5 @@ class Migration(SchemaMigration):
             'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
             'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
         }
         }
     }
     }
-    
+
     complete_apps = ['beeswax']
     complete_apps = ['beeswax']

+ 0 - 1
apps/beeswax/src/beeswax/management/commands/beeswax_install_examples.py

@@ -41,7 +41,6 @@ from django.core.management.base import NoArgsCommand
 from django.contrib.auth.models import User
 from django.contrib.auth.models import User
 
 
 import beeswax.conf
 import beeswax.conf
-import beeswax.design
 from beeswax import db_utils, models
 from beeswax import db_utils, models
 from beeswaxd import BeeswaxService
 from beeswaxd import BeeswaxService
 from beeswaxd.ttypes import BeeswaxException
 from beeswaxd.ttypes import BeeswaxException

+ 6 - 1
apps/beeswax/src/beeswax/management/commands/beeswax_server.py

@@ -60,10 +60,15 @@ class Command(NoArgsCommand):
     if dt_host == '0.0.0.0':
     if dt_host == '0.0.0.0':
       dt_host = '127.0.0.1'
       dt_host = '127.0.0.1'
 
 
+    if 'beeswax_server_port' in beeswax.conf.QUERY_SERVERS['bind_to']:
+      server_port = beeswax.conf.BEESWAX_SERVER_PORT.get()
+    else:
+      server_port = beeswax.conf.QUERY_SERVERS['default'].SERVER_PORT.get()
+
     args = [
     args = [
       os.path.basename(bin),
       os.path.basename(bin),
       '--beeswax',
       '--beeswax',
-      str(beeswax.conf.BEESWAX_SERVER_PORT.get()),
+      str(server_port),
       '--desktop-host',
       '--desktop-host',
       str(dt_host),
       str(dt_host),
       '--desktop-port',
       '--desktop-port',

+ 7 - 7
apps/beeswax/src/beeswax/migrations/0001_initial.py

@@ -21,9 +21,9 @@ from south.v2 import SchemaMigration
 from django.db import models
 from django.db import models
 
 
 class Migration(SchemaMigration):
 class Migration(SchemaMigration):
- 
+
     def forwards(self, orm):
     def forwards(self, orm):
-     
+
         # Adding model 'MetaInstall'
         # Adding model 'MetaInstall'
         try:
         try:
             db.create_table('beeswax_metainstall', (
             db.create_table('beeswax_metainstall', (
@@ -34,7 +34,7 @@ class Migration(SchemaMigration):
         except:
         except:
             logging.warning("Initial db creation being skipped, likely because table already exists.", exc_info=True)
             logging.warning("Initial db creation being skipped, likely because table already exists.", exc_info=True)
             return
             return
- 
+
         # Adding model 'QueryHistory'
         # Adding model 'QueryHistory'
         db.create_table('beeswax_queryhistory', (
         db.create_table('beeswax_queryhistory', (
             ('submission_date', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
             ('submission_date', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
@@ -65,7 +65,7 @@ class Migration(SchemaMigration):
 
 
 
 
     def backwards(self, orm):
     def backwards(self, orm):
-     
+
         # Deleting model 'QueryHistory'
         # Deleting model 'QueryHistory'
         db.delete_table('beeswax_queryhistory')
         db.delete_table('beeswax_queryhistory')
 
 
@@ -74,8 +74,8 @@ class Migration(SchemaMigration):
 
 
         # Deleting model 'MetaInstall'
         # Deleting model 'MetaInstall'
         db.delete_table('beeswax_metainstall')
         db.delete_table('beeswax_metainstall')
- 
- 
+
+
     models = {
     models = {
         'auth.group': {
         'auth.group': {
             'Meta': {'object_name': 'Group'},
             'Meta': {'object_name': 'Group'},
@@ -142,5 +142,5 @@ class Migration(SchemaMigration):
             'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
             'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
         }
         }
     }
     }
- 
+
     complete_apps = ['beeswax']
     complete_apps = ['beeswax']

+ 8 - 8
apps/beeswax/src/beeswax/migrations/0002_auto__add_field_queryhistory_notify.py

@@ -20,19 +20,19 @@ from south.v2 import SchemaMigration
 from django.db import models
 from django.db import models
 
 
 class Migration(SchemaMigration):
 class Migration(SchemaMigration):
- 
+
     def forwards(self, orm):
     def forwards(self, orm):
-     
+
         # Adding field 'QueryHistory.notify'
         # Adding field 'QueryHistory.notify'
         db.add_column('beeswax_queryhistory', 'notify', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True), keep_default=False)
         db.add_column('beeswax_queryhistory', 'notify', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True), keep_default=False)
- 
- 
+
+
     def backwards(self, orm):
     def backwards(self, orm):
-     
+
         # Deleting field 'QueryHistory.notify'
         # Deleting field 'QueryHistory.notify'
         db.delete_column('beeswax_queryhistory', 'notify')
         db.delete_column('beeswax_queryhistory', 'notify')
- 
- 
+
+
     models = {
     models = {
         'auth.group': {
         'auth.group': {
             'Meta': {'object_name': 'Group'},
             'Meta': {'object_name': 'Group'},
@@ -100,5 +100,5 @@ class Migration(SchemaMigration):
             'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
             'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
         }
         }
     }
     }
- 
+
     complete_apps = ['beeswax']
     complete_apps = ['beeswax']

+ 110 - 0
apps/beeswax/src/beeswax/migrations/0003_auto__add_field_queryhistory_server_name__add_field_queryhistory_serve.py

@@ -0,0 +1,110 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+
+        # Adding field 'QueryHistory.server_name'
+        db.add_column('beeswax_queryhistory', 'server_name', self.gf('django.db.models.fields.CharField')(default='', max_length=128), keep_default=False)
+
+        # Adding field 'QueryHistory.server_host'
+        db.add_column('beeswax_queryhistory', 'server_host', self.gf('django.db.models.fields.CharField')(default='', max_length=128), keep_default=False)
+
+        # Adding field 'QueryHistory.server_port'
+        db.add_column('beeswax_queryhistory', 'server_port', self.gf('django.db.models.fields.SmallIntegerField')(default=''), keep_default=False)
+
+        # Changing field 'QueryHistory.query'
+        db.alter_column('beeswax_queryhistory', 'query', self.gf('django.db.models.fields.TextField')())
+
+
+    def backwards(self, orm):
+
+        # Deleting field 'QueryHistory.server_name'
+        db.delete_column('beeswax_queryhistory', 'server_name')
+
+        # Deleting field 'QueryHistory.server_host'
+        db.delete_column('beeswax_queryhistory', 'server_host')
+
+        # Deleting field 'QueryHistory.server_port'
+        db.delete_column('beeswax_queryhistory', 'server_port')
+
+        # Changing field 'QueryHistory.query'
+        db.alter_column('beeswax_queryhistory', 'query', self.gf('django.db.models.fields.CharField')(max_length=1024))
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'beeswax.metainstall': {
+            'Meta': {'object_name': 'MetaInstall'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'installed_example': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'})
+        },
+        'beeswax.queryhistory': {
+            'Meta': {'object_name': 'QueryHistory'},
+            'design': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['beeswax.SavedQuery']", 'null': 'True'}),
+            'has_results': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'last_state': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
+            'log_context': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'}),
+            'notify': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+            'query': ('django.db.models.fields.TextField', [], {}),
+            'server_host': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+            'server_id': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'}),
+            'server_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128'}),
+            'server_port': ('django.db.models.fields.SmallIntegerField', [], {'default': "''"}),
+            'submission_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
+        },
+        'beeswax.savedquery': {
+            'Meta': {'object_name': 'SavedQuery'},
+            'data': ('django.db.models.fields.TextField', [], {'max_length': '65536'}),
+            'desc': ('django.db.models.fields.TextField', [], {'max_length': '1024'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_auto': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True', 'blank': 'True'}),
+            'mtime': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+            'type': ('django.db.models.fields.IntegerField', [], {})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        }
+    }
+
+    complete_apps = ['beeswax']

+ 6 - 0
apps/beeswax/src/beeswax/models.py

@@ -58,6 +58,9 @@ class QueryHistory(models.Model):
   # Only query in the "submitted" state is allowed to have no server_id
   # Only query in the "submitted" state is allowed to have no server_id
   server_id = models.CharField(max_length=1024, null=True)
   server_id = models.CharField(max_length=1024, null=True)
   log_context = models.CharField(max_length=1024, null=True)
   log_context = models.CharField(max_length=1024, null=True)
+  server_host = models.CharField(max_length=128, help_text=_('Host of the query server.'), default='')
+  server_port = models.SmallIntegerField(help_text=_('Port of the query server.'), default=0)
+  server_name = models.CharField(max_length=128, help_text=_('Name of the query server.'), default='')
   # Some queries (like read/drop table) don't have a design
   # Some queries (like read/drop table) don't have a design
   design = models.ForeignKey('SavedQuery', to_field='id', null=True)
   design = models.ForeignKey('SavedQuery', to_field='id', null=True)
   # Notify on completion
   # Notify on completion
@@ -112,6 +115,9 @@ class QueryHistory(models.Model):
       self.save_state(QueryHistory.STATE.expired)
       self.save_state(QueryHistory.STATE.expired)
       return (False, None)
       return (False, None)
 
 
+  def get_query_server(self):
+    return dict(zip(['server_name', 'server_host', 'server_port'],
+                    [self.server_name, self.server_host, self.server_port]))
 
 
 
 
 class SavedQuery(models.Model):
 class SavedQuery(models.Model):

+ 1 - 1
apps/beeswax/src/beeswax/templates/choose_delimiter.mako

@@ -20,7 +20,7 @@ from django.utils.translation import ugettext as _
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="comps" file="beeswax_components.mako" />
 <%namespace name="comps" file="beeswax_components.mako" />
 <%namespace name="util" file="util.mako" />
 <%namespace name="util" file="util.mako" />
-${commonheader(_('Beeswax: Create table from file'), "beeswax", "100px")}
+${commonheader(_('Create table from file'), "beeswax", "100px")}
 ${layout.menubar(section='tables')}
 ${layout.menubar(section='tables')}
 
 
 <div class="container-fluid">
 <div class="container-fluid">

+ 1 - 1
apps/beeswax/src/beeswax/templates/choose_file.mako

@@ -19,7 +19,7 @@ from django.utils.translation import ugettext as _
 %>
 %>
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="comps" file="beeswax_components.mako" />
 <%namespace name="comps" file="beeswax_components.mako" />
-${commonheader(_('Beeswax: Create table from file'), "beeswax", "100px")}
+${commonheader(_('Create table from file'), "beeswax", "100px")}
 ${layout.menubar(section='tables')}
 ${layout.menubar(section='tables')}
 
 
 
 

+ 26 - 8
apps/beeswax/src/beeswax/templates/configuration.mako

@@ -19,14 +19,22 @@ from django.utils.translation import ugettext as _
 %>
 %>
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="util" file="util.mako" />
 <%namespace name="util" file="util.mako" />
-${commonheader(_('Hive Configuration Variables'), "beeswax", "100px")}
-${layout.menubar(section='hive configuration')}
+
+${commonheader(_('Configuration Variables'), "beeswax", "100px")}
+${layout.menubar(section='configuration')}
+
 <div class="container-fluid">
 <div class="container-fluid">
-	<h1>${_('Hive Configuration Variables')}</h1>
+	<h1>${_('Configuration Variables')}</h1>
 	<div class="well">
 	<div class="well">
-		<form class="form-search">
-            ${_('Filter:')} <input id="filterInput" class="input-xlarge search-query" placeholder="${_('Search for key, value, etc...')}">
-		    <a href="#" id="clearFilterBtn" class="btn">${_('Clear')}</a>
+		<form class="form-search" method="POST">
+		  <span>
+		    ${server_form['server']}
+		    <button type="submit" class="btn primary">${_('Look')}</button>
+		   </span>
+		   <span class="pull-right">
+              ${_('Filter:')} <input id="filterInput" class="input-xlarge search-query" placeholder="${_('Search for key, value, etc...')}">
+		      <a href="#" id="clearFilterBtn" class="btn">${_('Clear')}</a>
+		   <span>
 		</form>
 		</form>
 	</div>
 	</div>
 	<table class="table table-striped table-condensed datatables">
 	<table class="table table-striped table-condensed datatables">
@@ -47,8 +55,10 @@ ${layout.menubar(section='hive configuration')}
 	</table>
 	</table>
 </div>
 </div>
 
 
-<script type="text/javascript" charset="utf-8">
 
 
+<script src="/static/ext/js/jquery/plugins/jquery.cookie.js"></script>
+
+<script type="text/javascript" charset="utf-8">
 	$(document).ready(function(){
 	$(document).ready(function(){
 		$(".datatables").dataTable({
 		$(".datatables").dataTable({
 			"bPaginate": false,
 			"bPaginate": false,
@@ -75,7 +85,15 @@ ${layout.menubar(section='hive configuration')}
 	            $(value).show();
 	            $(value).show();
 	        });
 	        });
 	    });
 	    });
-	});
 
 
+        $("#id_server").change(function(){
+            $.cookie("hueBeeswaxLastQueryServer", $(this).val(), {expires: 90});
+        });
+
+        if ($.cookie("hueBeeswaxLastQueryServer") != null) {
+            $("#id_server").val($.cookie("hueBeeswaxLastQueryServer"));
+        }
+	});
 </script>
 </script>
+
 ${commonfooter(messages)}
 ${commonfooter(messages)}

+ 1 - 1
apps/beeswax/src/beeswax/templates/confirm.html

@@ -21,7 +21,7 @@ limitations under the License.
 	<a href="#" class="close">&times;</a>
 	<a href="#" class="close">&times;</a>
 	<h3>Confirm action</h3>
 	<h3>Confirm action</h3>
 </div>
 </div>
-<div class="modal-body">  
+<div class="modal-body">
   <div class="alert-message block-message warning">
   <div class="alert-message block-message warning">
         {{title}}
         {{title}}
   </div>
   </div>

+ 1 - 1
apps/beeswax/src/beeswax/templates/create_table_index.mako

@@ -18,7 +18,7 @@ from desktop.views import commonheader, commonfooter
 from django.utils.translation import ugettext as _
 from django.utils.translation import ugettext as _
 %>
 %>
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="layout" file="layout.mako" />
-${commonheader(_('Beeswax: Create Table'), "beeswax", "100px")}
+${commonheader(_('Create Table'), "beeswax", "100px")}
 <div class="container-fluid">
 <div class="container-fluid">
   <div class="toolbar">
   <div class="toolbar">
   </div>
   </div>

+ 5 - 5
apps/beeswax/src/beeswax/templates/create_table_manually.mako

@@ -19,7 +19,7 @@
 %>
 %>
 <%namespace name="comps" file="beeswax_components.mako" />
 <%namespace name="comps" file="beeswax_components.mako" />
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="layout" file="layout.mako" />
-${commonheader(_("Beeswax: Create table manually"), "beeswax", "100px")}
+${commonheader(_("Create table manually"), "beeswax", "100px")}
 ${layout.menubar(section='tables')}
 ${layout.menubar(section='tables')}
 
 
 <div class="container-fluid">
 <div class="container-fluid">
@@ -119,7 +119,7 @@ ${layout.menubar(section='tables')}
 	    <fieldset>
 	    <fieldset>
 	        <div id="step3Delimited" class="stepDetailsInner">
 	        <div id="step3Delimited" class="stepDetailsInner">
 	            <div class="alert alert-info"><h3>${_('Configure Record Serialization')}</h3>
 	            <div class="alert alert-info"><h3>${_('Configure Record Serialization')}</h3>
-	                ${_('Hive only supports single-character delimiters.')}
+	                ${_('Only supports single-character delimiters.')}
 	            </div>
 	            </div>
 	            <div class="control-group">
 	            <div class="control-group">
 	                ${comps.bootstrapLabel(table_form["field_terminator"])}
 	                ${comps.bootstrapLabel(table_form["field_terminator"])}
@@ -283,10 +283,10 @@ ${layout.menubar(section='tables')}
 	    <br/><br/>
 	    <br/><br/>
 	    <fieldset>
 	    <fieldset>
 	        <div class="alert alert-info"><h3>${_('Configure Partitions')}</h3>
 	        <div class="alert alert-info"><h3>${_('Configure Partitions')}</h3>
-	            ${_('If your data is naturally partitioned (by date, for example), partitions are a way to tell Hive that data for a specific partition value are stored together.')}
-	            ${_('Hive establishes a mapping between directories on disk')}
+	            ${_('If your data is naturally partitioned (by date, for example), partitions are a way to tell the query server that data for a specific partition value are stored together.')}
+	            ${_('The query server establishes a mapping between directories on disk')}
 	            (<em>${_('e.g.')},</em> <code>/user/hive/warehouse/logs/dt=20100101/</code>)
 	            (<em>${_('e.g.')},</em> <code>/user/hive/warehouse/logs/dt=20100101/</code>)
-	            ${_('and the data for that day.  Partitions are virtual columns; they are not represented in the data itself, but are determined by the data location. Hive implements query optimizations such that queries that are specific to a single partition need not read the data in other partitions.')}
+	            ${_('and the data for that day.  Partitions are virtual columns; they are not represented in the data itself, but are determined by the data location. The query server implements query optimizations such that queries that are specific to a single partition need not read the data in other partitions.')}
 	        </div>
 	        </div>
 	        % for form in partitions_form.forms:
 	        % for form in partitions_form.forms:
 	            ${render_column(form, True)}
 	            ${render_column(form, True)}

+ 1 - 1
apps/beeswax/src/beeswax/templates/define_columns.mako

@@ -20,7 +20,7 @@ from django.utils.translation import ugettext as _
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="comps" file="beeswax_components.mako" />
 <%namespace name="comps" file="beeswax_components.mako" />
 <%namespace name="util" file="util.mako" />
 <%namespace name="util" file="util.mako" />
-${commonheader(_('Beeswax: Create table from file'), "beeswax", "100px")}
+${commonheader(_('Create table from file'), "beeswax", "100px")}
 ${layout.menubar(section='tables')}
 ${layout.menubar(section='tables')}
 
 
 <div class="container-fluid">
 <div class="container-fluid">

+ 2 - 2
apps/beeswax/src/beeswax/templates/describe_partitions.mako

@@ -13,9 +13,9 @@
 ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 ## See the License for the specific language governing permissions and
 ## See the License for the specific language governing permissions and
 ## limitations under the License.
 ## limitations under the License.
-<%! 
+<%!
   from urllib import quote
   from urllib import quote
-  from filebrowser.views import location_to_url 
+  from filebrowser.views import location_to_url
   from desktop.views import commonheader, commonfooter
   from desktop.views import commonheader, commonfooter
   from django.utils.translation import ugettext as _
   from django.utils.translation import ugettext as _
 %>
 %>

+ 5 - 3
apps/beeswax/src/beeswax/templates/describe_table.mako

@@ -17,18 +17,20 @@
 from desktop.views import commonheader, commonfooter
 from desktop.views import commonheader, commonfooter
 from django.utils.translation import ugettext as _
 from django.utils.translation import ugettext as _
 %>
 %>
+
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="comps" file="beeswax_components.mako" />
 <%namespace name="comps" file="beeswax_components.mako" />
+
 <%
 <%
   if is_view:
   if is_view:
     view_or_table_noun = _("View")
     view_or_table_noun = _("View")
   else:
   else:
     view_or_table_noun = _("Table")
     view_or_table_noun = _("Table")
 %>
 %>
-${commonheader(_("Beeswax %s Metadata: %s") % (view_or_table_noun, table.tableName), "beeswax", "100px")}
+${commonheader(_("%s Metadata: %s") % (view_or_table_noun, table.tableName), "beeswax", "100px")}
 ${layout.menubar(section='tables')}
 ${layout.menubar(section='tables')}
-<%def name="column_table(cols)">
 
 
+<%def name="column_table(cols)">
     <table class="table table-striped table-condensed datatables">
     <table class="table table-striped table-condensed datatables">
       <thead>
       <thead>
         <tr>
         <tr>
@@ -51,7 +53,7 @@ ${layout.menubar(section='tables')}
 </%def>
 </%def>
 
 
 <div class="container-fluid">
 <div class="container-fluid">
-	<h1>${_('Beeswax Table Metadata:')} ${table.tableName}</h1>
+	<h1>${_('Table Metadata:')} ${table.tableName}</h1>
 	<div class="row-fluid">
 	<div class="row-fluid">
 		<div class="span3">
 		<div class="span3">
 			<div class="well sidebar-nav">
 			<div class="well sidebar-nav">

+ 22 - 7
apps/beeswax/src/beeswax/templates/execute.mako

@@ -24,13 +24,13 @@
 <%namespace name="util" file="util.mako" />
 <%namespace name="util" file="util.mako" />
 
 
 <%def name="query()">
 <%def name="query()">
-    <h1>${_('Hive Query')}</h1>
+    <h1>${_('Query Editor')}</h1>
     <fieldset>
     <fieldset>
         % if design and not design.is_auto and design.name:
         % if design and not design.is_auto and design.name:
-              <legend>${design.name}</legend>
-              % if design.desc:
-                <p>${design.desc}</p>
-              % endif
+            <legend>${design.name}</legend>
+            % if design.desc:
+              <p>${design.desc}</p>
+            % endif
 
 
           % else:
           % else:
             <legend>${_('Query')}</legend>
             <legend>${_('Query')}</legend>
@@ -60,7 +60,7 @@
 </%def>
 </%def>
 
 
 
 
-${commonheader(_('Hive Query'), "beeswax", "100px")}
+${commonheader(_('Query'), "beeswax", "100px")}
 ${layout.menubar(section='query')}
 ${layout.menubar(section='query')}
 
 
 <div class="container-fluid">
 <div class="container-fluid">
@@ -69,7 +69,11 @@ ${layout.menubar(section='query')}
             <div class="well sidebar-nav">
             <div class="well sidebar-nav">
                 <form id="advancedSettingsForm" action="${action}" method="POST" class="form form-horizontal noPadding">
                 <form id="advancedSettingsForm" action="${action}" method="POST" class="form form-horizontal noPadding">
                     <ul class="nav nav-list">
                     <ul class="nav nav-list">
-                        <li class="nav-header">${_('Hive settings')}</li>
+                        <li class="nav-header">${_('Query server')}</li>
+                        <li>
+                          ${form.query_servers['server']}
+                        </li>
+                        <li class="nav-header">${_('settings')}</li>
                         <li>
                         <li>
                             % for i, f in enumerate(form.settings.forms):
                             % for i, f in enumerate(form.settings.forms):
                             <div class="param">
                             <div class="param">
@@ -317,6 +321,9 @@ ${layout.menubar(section='query')}
 </style>
 </style>
 
 
 
 
+
+<script src="/static/ext/js/jquery/plugins/jquery.cookie.js"></script>
+
 <script type="text/javascript" charset="utf-8">
 <script type="text/javascript" charset="utf-8">
     $(document).ready(function(){
     $(document).ready(function(){
         $("*[rel=tooltip]").tooltip({
         $("*[rel=tooltip]").tooltip({
@@ -413,6 +420,14 @@ ${layout.menubar(section='query')}
             $(".query").val($("#queryField").val());
             $(".query").val($("#queryField").val());
             $("#advancedSettingsForm").submit();
             $("#advancedSettingsForm").submit();
         }
         }
+
+        $("#id_query_servers-server").change(function(){
+            $.cookie("hueBeeswaxLastQueryServer", $(this).val(), {expires: 90});
+        });
+
+        if ($.cookie("hueBeeswaxLastQueryServer") != null) {
+            $("#id_query_servers-server").val($.cookie("hueBeeswaxLastQueryServer"));
+        }
     });
     });
 </script>
 </script>
 
 

+ 1 - 1
apps/beeswax/src/beeswax/templates/index.mako

@@ -40,7 +40,7 @@ ${layout.menubar(section='tables')}
                     <li><a href="${ url('beeswax.views.execute_query') }">${_('Execute Query')}</a></li>
                     <li><a href="${ url('beeswax.views.execute_query') }">${_('Execute Query')}</a></li>
                     <li><a href="${ url('beeswax.views.list_query_history') }">${_('Query History')}</a></li>
                     <li><a href="${ url('beeswax.views.list_query_history') }">${_('Query History')}</a></li>
                     <li class="nav-header">${_('Configuration')}</li>
                     <li class="nav-header">${_('Configuration')}</li>
-                    <li><a href="${ url('beeswax.views.configuration') }">${_('Hive Configuration')}</a></li>
+                    <li><a href="${ url('beeswax.views.configuration') }">${_('Configuration')}</a></li>
                     <li><a href="${ url('beeswax.views.configuration') }?include_hadoop=1">${_('Extended Configuration')}</a></li>
                     <li><a href="${ url('beeswax.views.configuration') }?include_hadoop=1">${_('Extended Configuration')}</a></li>
                 </ul>
                 </ul>
             </div>
             </div>

+ 1 - 1
apps/beeswax/src/beeswax/templates/layout.mako

@@ -36,7 +36,7 @@ def is_selected(section, matcher):
 			<li class="${is_selected(section, 'saved queries')}"><a href="/beeswax/list_designs">${_('Saved Queries')}</a></li>
 			<li class="${is_selected(section, 'saved queries')}"><a href="/beeswax/list_designs">${_('Saved Queries')}</a></li>
 			<li class="${is_selected(section, 'history')}"><a href="/beeswax/query_history">${_('History')}</a></li>
 			<li class="${is_selected(section, 'history')}"><a href="/beeswax/query_history">${_('History')}</a></li>
 			<li class="${is_selected(section, 'tables')}"><a href="/beeswax/tables">${_('Tables')}</a></li>
 			<li class="${is_selected(section, 'tables')}"><a href="/beeswax/tables">${_('Tables')}</a></li>
-			<li class="${is_selected(section, 'hive configuration')}"><a href="/beeswax/configuration">${_('Settings')}</a></li>
+			<li class="${is_selected(section, 'configuration')}"><a href="/beeswax/configuration">${_('Settings')}</a></li>
 		</ul>
 		</ul>
 	</div>
 	</div>
 </div>
 </div>

+ 2 - 2
apps/beeswax/src/beeswax/templates/list_designs.mako

@@ -21,10 +21,10 @@
 %>
 %>
 <%namespace name="comps" file="beeswax_components.mako" />
 <%namespace name="comps" file="beeswax_components.mako" />
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="layout" file="layout.mako" />
-${commonheader(_('Beeswax: Queries'), "beeswax", "100px")}
+${commonheader(_('Queries'), "beeswax", "100px")}
 ${layout.menubar(section='saved queries')}
 ${layout.menubar(section='saved queries')}
 <div class="container-fluid">
 <div class="container-fluid">
-    <h1>${_('Beeswax: Queries')}</h1>
+    <h1>${_('Queries')}</h1>
     <table class="table table-striped table-condensed datatables">
     <table class="table table-striped table-condensed datatables">
         <thead>
         <thead>
         <tr>
         <tr>

+ 80 - 75
apps/beeswax/src/beeswax/templates/list_history.mako

@@ -18,11 +18,14 @@ import time
 from desktop.views import commonheader, commonfooter
 from desktop.views import commonheader, commonfooter
 from django.utils.translation import ugettext as _
 from django.utils.translation import ugettext as _
 %>
 %>
+
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="comps" file="beeswax_components.mako" />
 <%namespace name="comps" file="beeswax_components.mako" />
 <%!  from beeswax.views import collapse_whitespace %>
 <%!  from beeswax.views import collapse_whitespace %>
-${commonheader(_('Beeswax: Query History'), "beeswax", "100px")}
+
+${commonheader(_('Query History'), "beeswax", "100px")}
 ${layout.menubar(section='history')}
 ${layout.menubar(section='history')}
+
 <%def name="show_saved_query(design, history)">
 <%def name="show_saved_query(design, history)">
   % if design:
   % if design:
     % if request.user == design.owner:
     % if request.user == design.owner:
@@ -45,12 +48,12 @@ ${layout.menubar(section='history')}
 </%def>
 </%def>
 
 
 <div class="container-fluid">
 <div class="container-fluid">
-	<h1>${_('Beeswax: Query History')}</h1>
-	<div class="row-fluid">
-		<div class="span3">
-			<div class="well sidebar-nav">
-				<ul class="nav nav-list">
-					<li class="nav-header">${_('Actions')}</li>
+    <h1>${_('Query History')}</h1>
+    <div class="row-fluid">
+        <div class="span3">
+            <div class="well sidebar-nav">
+                <ul class="nav nav-list">
+                    <li class="nav-header">${_('Actions')}</li>
                     % if share_queries:
                     % if share_queries:
                         % if filter_params.get('user') == ':all':
                         % if filter_params.get('user') == ':all':
                           <%
                           <%
@@ -67,76 +70,78 @@ ${layout.menubar(section='history')}
                         % endif
                         % endif
                     % endif
                     % endif
 
 
-				 	% if filter_params.get('auto_query', None):
-				      <%
-				        my_querydict = filter_params.copy()
-				        my_querydict['auto_query'] = ''
-				      %>
-				      <li><a href="?${my_querydict.urlencode()}">${_('Show user queries')}</a></li>
-				    % else:
-				      <%
-				        my_querydict = filter_params.copy()
-				        my_querydict['auto_query'] = 'on'
-				      %>
-				      <li><a href="?${my_querydict.urlencode()}">${_('Show auto actions')}</a></li>
-				    % endif
-				</ul>
-			</div>
-		</div>
-		<div class="span9">
+                     % if filter_params.get('auto_query', None):
+                      <%
+                        my_querydict = filter_params.copy()
+                        my_querydict['auto_query'] = ''
+                      %>
+                      <li><a href="?${my_querydict.urlencode()}">${_('Show user queries')}</a></li>
+                    % else:
+                      <%
+                        my_querydict = filter_params.copy()
+                        my_querydict['auto_query'] = 'on'
+                      %>
+                      <li><a href="?${my_querydict.urlencode()}">${_('Show auto actions')}</a></li>
+                    % endif
+                </ul>
+            </div>
+        </div>
+        <div class="span9">
 
 
-		<table class="table table-striped table-condensed datatables">
-		    <thead>
-		      <tr>
-		        <th>${_('Time')}</th>
-		        <th>${_('Name')}</th>
-		        <th>${_('Query')}</th>
-		        <th>${_('User')}</th>
-		        <th>${_('State')}</th>
-		        <th>${_('Result')}</th>
-		      </tr>
-		    </thead>
-		    <tbody>
-		    <%!
-		      from beeswax import models, views
-		    %>
-		    % for query in page.object_list:
-		      <%
-		        qcontext = ""
-			try:
-			  design = query.design
-		          qcontext = views.make_query_context('design', design.id)
-			except:
-			  pass
-		      %>
-		      <tr class="histRow">
+        <table class="table table-striped table-condensed datatables">
+            <thead>
+              <tr>
+                <th width="10%">${_('Time')}</th>
+                <th width="10%">${_('Query Server')}</th>
+                <th width="15%">${_('Name')}</th>
+                <th width="45%">${_('Query')}</th>
+                <th width="10%">${_('User')}</th>
+                <th width="5%">${_('State')}</th>
+                <th width="5%">${_('Result')}</th>
+              </tr>
+            </thead>
+            <tbody>
+            <%!
+              from beeswax import models, views
+            %>
+            % for query in page.object_list:
+              <%
+                qcontext = ""
+                try:
+                  design = query.design
+                  qcontext = views.make_query_context('design', design.id)
+                except:
+                  pass
+              %>
+              <tr class="histRow">
                 <td data-sort-value="${time.mktime(query.submission_date.timetuple())}">${query.submission_date.strftime("%x %X")}</td>
                 <td data-sort-value="${time.mktime(query.submission_date.timetuple())}">${query.submission_date.strftime("%x %X")}</td>
-		        <td>${show_saved_query(design, query)}</td>
-		        <td>
-		          <p>
-		            % if len(query.query) > 100:
-		              <code>${collapse_whitespace(query.query[:100])}...</code>
-		            % else:
-		              <code>${collapse_whitespace(query.query)}</code>
-		            % endif
-		          </p>
-		        </td>
-		        <td>${query.owner}</td>
-		        <td>${models.QueryHistory.STATE[query.last_state]}</td>
-		        <td>
-		          % if qcontext and query.last_state != models.QueryHistory.STATE.expired.index:
-		            <a href="${ url('beeswax.views.watch_query', id=query.id) }?context=${qcontext|u}" data-row-selector="true">${_('Results')}</a>
-		          % else:
-		            ~
-		          % endif
-		        </td>
-		      </tr>
-		    % endfor
-		    </tbody>
-		  </table>
-		 ${comps.pagination(page)}
-		</div>
-	</div>
+                <td>${query.server_name}</td>
+                <td>${show_saved_query(design, query)}</td>
+                <td>
+                  <p>
+                    % if len(query.query) > 100:
+                      <code>${collapse_whitespace(query.query[:100])}...</code>
+                    % else:
+                      <code>${collapse_whitespace(query.query)}</code>
+                    % endif
+                  </p>
+                </td>
+                <td>${query.owner}</td>
+                <td>${models.QueryHistory.STATE[query.last_state]}</td>
+                <td>
+                  % if qcontext and query.last_state != models.QueryHistory.STATE.expired.index:
+                    <a href="${ url('beeswax.views.watch_query', id=query.id) }?context=${qcontext|u}" data-row-selector="true">${_('Results')}</a>
+                  % else:
+                    ~
+                  % endif
+                </td>
+              </tr>
+            % endfor
+            </tbody>
+          </table>
+         ${comps.pagination(page)}
+        </div>
+    </div>
 </div>
 </div>
 
 
 <script type="text/javascript" charset="utf-8">
 <script type="text/javascript" charset="utf-8">

+ 1 - 1
apps/beeswax/src/beeswax/templates/load_table.mako

@@ -19,7 +19,7 @@ from django.utils.translation import ugettext as _
 %>
 %>
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="util" file="util.mako" />
 <%namespace name="util" file="util.mako" />
-${commonheader(_('Beeswax: Load Data into %(table)s') % dict(table=table), "beeswax", "100px")}
+${commonheader(_('Load Data into %(table)s') % dict(table=table), "beeswax", "100px")}
 ${layout.menubar()}
 ${layout.menubar()}
 <div class="container-fluid">
 <div class="container-fluid">
   <h1>${_('Load Data into %(table)s') % dict(table=table)}</h1>
   <h1>${_('Load Data into %(table)s') % dict(table=table)}</h1>

+ 2 - 2
apps/beeswax/src/beeswax/templates/my_queries.mako

@@ -22,7 +22,7 @@ from django.utils.translation import ugettext as _
 <%namespace name="comps" file="beeswax_components.mako" />
 <%namespace name="comps" file="beeswax_components.mako" />
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="layout" file="layout.mako" />
 <%!  from beeswax.views import collapse_whitespace %>
 <%!  from beeswax.views import collapse_whitespace %>
-${commonheader(_('Beeswax: My Queries'), "beeswax", "100px")}
+${commonheader(_('My Queries'), "beeswax", "100px")}
 ${layout.menubar(section='my queries')}
 ${layout.menubar(section='my queries')}
 <style>
 <style>
     .tab-content {
     .tab-content {
@@ -30,7 +30,7 @@ ${layout.menubar(section='my queries')}
     }
     }
 </style>
 </style>
 <div class="container-fluid">
 <div class="container-fluid">
-    <h1>${_('Beeswax: My Queries')}</h1>
+    <h1>${_('My Queries')}</h1>
 
 
     <div class="well hueWell">
     <div class="well hueWell">
         <div class="btn-group pull-right">
         <div class="btn-group pull-right">

+ 4 - 1
apps/beeswax/src/beeswax/templates/parameterization.mako

@@ -20,7 +20,7 @@ from django.utils.translation import ugettext as _
 <%namespace name="comps" file="beeswax_components.mako" />
 <%namespace name="comps" file="beeswax_components.mako" />
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="layout" file="layout.mako" />
 
 
-${commonheader(_('Parameterize Hive Query'), "beeswax", "100px")}
+${commonheader(_('Parameterize Query'), "beeswax", "100px")}
 
 
 ${layout.menubar()}
 ${layout.menubar()}
 
 
@@ -44,6 +44,9 @@ ${layout.menubar()}
                     </div>
                     </div>
                 </div>
                 </div>
             % endfor
             % endfor
+            <div class="hide">
+              ${query_servers}
+            </div>
             <div class="form-actions">
             <div class="form-actions">
                 <button type="submit" class="btn btn-primary">${btn}</button>
                 <button type="submit" class="btn btn-primary">${btn}</button>
                 <a class="btn" href="javascript:history.go(-1);">${_('Cancel')}</a>
                 <a class="btn" href="javascript:history.go(-1);">${_('Cancel')}</a>

+ 1 - 1
apps/beeswax/src/beeswax/templates/save_results.mako

@@ -20,7 +20,7 @@ from django.utils.translation import ugettext as _
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="comps" file="beeswax_components.mako" />
 <%namespace name="comps" file="beeswax_components.mako" />
 <%namespace name="util" file="util.mako" />
 <%namespace name="util" file="util.mako" />
-${commonheader(_('Beeswax: Create table from file'), "beeswax", "100px")}
+${commonheader(_('Create table from file'), "beeswax", "100px")}
 ${layout.menubar(section='history')}
 ${layout.menubar(section='history')}
 <div class="container-fluid">
 <div class="container-fluid">
 % if error_msg:
 % if error_msg:

+ 2 - 2
apps/beeswax/src/beeswax/templates/show_tables.mako

@@ -18,11 +18,11 @@ from desktop.views import commonheader, commonfooter
 from django.utils.translation import ugettext as _
 from django.utils.translation import ugettext as _
 %>
 %>
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="layout" file="layout.mako" />
-${commonheader(_('Beeswax: Table List'), "beeswax", "100px")}
+${commonheader(_('Table List'), "beeswax", "100px")}
 ${layout.menubar(section='tables')}
 ${layout.menubar(section='tables')}
 
 
 <div class="container-fluid">
 <div class="container-fluid">
-	<h1>${_('Beeswax: Table List')}</h1>
+	<h1>${_('Table List')}</h1>
 	<div class="row-fluid">
 	<div class="row-fluid">
 		<div class="span3">
 		<div class="span3">
 			<div class="well sidebar-nav">
 			<div class="well sidebar-nav">

+ 2 - 2
apps/beeswax/src/beeswax/templates/watch_results.mako

@@ -20,11 +20,11 @@ from django.utils.translation import ugettext as _
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="util" file="util.mako" />
 <%namespace name="util" file="util.mako" />
 <%namespace name="comps" file="beeswax_components.mako" />
 <%namespace name="comps" file="beeswax_components.mako" />
-${commonheader(_('Beeswax: Query Results'), "beeswax", "100px")}
+${commonheader(_('Query Results'), "beeswax", "100px")}
 ${layout.menubar(section='query')}
 ${layout.menubar(section='query')}
 
 
 <div class="container-fluid">
 <div class="container-fluid">
-	<h1>${_('Beeswax: Query Results:')} ${util.render_query_context(query_context)}</h1>
+	<h1>${_('Query Results:')} ${util.render_query_context(query_context)}</h1>
 	<div class="row-fluid">
 	<div class="row-fluid">
 		<div class="span3">
 		<div class="span3">
 			<div class="well sidebar-nav">
 			<div class="well sidebar-nav">

+ 2 - 2
apps/beeswax/src/beeswax/templates/watch_wait.mako

@@ -19,13 +19,13 @@ from django.utils.translation import ugettext as _
 %>
 %>
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="layout" file="layout.mako" />
 <%namespace name="util" file="util.mako" />
 <%namespace name="util" file="util.mako" />
-${commonheader(_('Beeswax: Waiting for query...'), "beeswax", "100px")}
+${commonheader(_('Waiting for query...'), "beeswax", "100px")}
 ${layout.menubar(section='query')}
 ${layout.menubar(section='query')}
 
 
 <meta http-equiv="refresh" content="3;${url('beeswax.views.watch_query', query.id)}?${fwd_params}" />
 <meta http-equiv="refresh" content="3;${url('beeswax.views.watch_query', query.id)}?${fwd_params}" />
 
 
 <div class="container-fluid">
 <div class="container-fluid">
-	<h1>${_('Beeswax: Waiting for query...')} ${util.render_query_context(query_context)}</h1>
+	<h1>${_('Waiting for query...')} ${util.render_query_context(query_context)}</h1>
 	<div class="row-fluid">
 	<div class="row-fluid">
 		<div class="span3">
 		<div class="span3">
 			<div class="well sidebar-nav">
 			<div class="well sidebar-nav">

+ 10 - 4
apps/beeswax/src/beeswax/test_base.py

@@ -38,6 +38,7 @@ from hadoop import pseudo_hdfs4
 import hadoop.conf
 import hadoop.conf
 
 
 import beeswax.conf
 import beeswax.conf
+from beeswax.db_utils import get_query_server
 
 
 
 
 _INITIALIZED = False
 _INITIALIZED = False
@@ -90,8 +91,9 @@ def get_shared_beeswax_server():
     default_xml = file(beeswax.conf.BEESWAX_HIVE_CONF_DIR.get()+"/hive-default.xml.template").read()
     default_xml = file(beeswax.conf.BEESWAX_HIVE_CONF_DIR.get()+"/hive-default.xml.template").read()
 
 
     finish = (
     finish = (
-      beeswax.conf.BEESWAX_SERVER_HOST.set_for_testing("localhost"),
-      beeswax.conf.BEESWAX_SERVER_PORT.set_for_testing(BEESWAXD_TEST_PORT),
+      beeswax.conf.QUERY_SERVERS['default'].SERVER_HOST.set_for_testing("localhost"),
+      beeswax.conf.QUERY_SERVERS['default'].SERVER_PORT.set_for_testing(BEESWAXD_TEST_PORT),
+      beeswax.conf.QUERY_SERVERS['default'].SUPPORT_DDL.set_for_testing(True),
       beeswax.conf.BEESWAX_META_SERVER_HOST.set_for_testing("localhost"),
       beeswax.conf.BEESWAX_META_SERVER_HOST.set_for_testing("localhost"),
       beeswax.conf.BEESWAX_META_SERVER_PORT.set_for_testing(BEESWAXD_TEST_PORT + 1),
       beeswax.conf.BEESWAX_META_SERVER_PORT.set_for_testing(BEESWAXD_TEST_PORT + 1),
       # Use a bogus path to avoid loading the normal hive-site.xml
       # Use a bogus path to avoid loading the normal hive-site.xml
@@ -122,7 +124,8 @@ def get_shared_beeswax_server():
       sleep = 0.001
       sleep = 0.001
       while not started and time.time() - start < 20.0:
       while not started and time.time() - start < 20.0:
         try:
         try:
-          client = beeswax.db_utils.db_client()
+          query_server = get_query_server(support_ddl=True)
+          client = beeswax.db_utils.db_client(query_server)
           meta_client = beeswax.db_utils.meta_client()
           meta_client = beeswax.db_utils.meta_client()
 
 
           client.echo("echo")
           client.echo("echo")
@@ -160,6 +163,7 @@ def get_shared_beeswax_server():
 
 
 REFRESH_RE = re.compile('<\s*meta\s+http-equiv="refresh"\s+content="\d*;([^"]*)"\s*/>', re.I)
 REFRESH_RE = re.compile('<\s*meta\s+http-equiv="refresh"\s+content="\d*;([^"]*)"\s*/>', re.I)
 
 
+
 def wait_for_query_to_finish(client, response, max=30.0):
 def wait_for_query_to_finish(client, response, max=30.0):
   logging.info(str(response.template) + ": " + str(response.content))
   logging.info(str(response.template) + ": " + str(response.content))
   start = time.time()
   start = time.time()
@@ -188,7 +192,7 @@ def wait_for_query_to_finish(client, response, max=30.0):
 def make_query(client, query, submission_type="Execute",
 def make_query(client, query, submission_type="Execute",
                udfs=None, settings=None, resources=None,
                udfs=None, settings=None, resources=None,
                wait=False, name=None, desc=None, local=True,
                wait=False, name=None, desc=None, local=True,
-               is_parameterized=True, max=30.0, **kwargs):
+               is_parameterized=True, max=30.0, server='default', **kwargs):
   """
   """
   Prepares arguments for the execute view.
   Prepares arguments for the execute view.
 
 
@@ -204,8 +208,10 @@ def make_query(client, query, submission_type="Execute",
   # Prepares arguments for the execute view.
   # Prepares arguments for the execute view.
   parameters = {
   parameters = {
     'query-query': query,
     'query-query': query,
+    'query_servers-server': server,
     'query-is_parameterized': is_parameterized and "on"
     'query-is_parameterized': is_parameterized and "on"
   }
   }
+
   if submission_type == 'Execute':
   if submission_type == 'Execute':
     parameters['button-submit'] = 'Whatever'
     parameters['button-submit'] = 'Whatever'
   elif submission_type == 'Explain':
   elif submission_type == 'Explain':

+ 38 - 17
apps/beeswax/src/beeswax/tests.py

@@ -42,7 +42,8 @@ import beeswax.models
 import beeswax.views
 import beeswax.views
 from beeswax import conf
 from beeswax import conf
 from beeswax.views import parse_results, collapse_whitespace
 from beeswax.views import parse_results, collapse_whitespace
-from beeswax.test_base import make_query, wait_for_query_to_finish, verify_history
+from beeswax.test_base import make_query, wait_for_query_to_finish, verify_history, get_query_server,\
+  BEESWAXD_TEST_PORT
 from beeswax.test_base import BeeswaxSampleProvider
 from beeswax.test_base import BeeswaxSampleProvider
 from beeswaxd import BeeswaxService
 from beeswaxd import BeeswaxService
 from beeswaxd import ttypes
 from beeswaxd import ttypes
@@ -100,8 +101,10 @@ class TestBeeswaxWithHadoop(BeeswaxSampleProvider):
     assert_true("Table test already exists" in response.context["error_message"])
     assert_true("Table test already exists" in response.context["error_message"])
 
 
   def test_configuration(self):
   def test_configuration(self):
-    response = self.client.get("/beeswax/configuration")
-    response_verbose = self.client.get("/beeswax/configuration?include_hadoop=true")
+    params = {'server': 'default'}
+
+    response = self.client.post("/beeswax/configuration", params)
+    response_verbose = self.client.post("/beeswax/configuration?include_hadoop=true", params)
 
 
     assert_true("hive.exec.scratchdir" in response.content)
     assert_true("hive.exec.scratchdir" in response.content)
     assert_true("hive.exec.scratchdir" in response_verbose.content)
     assert_true("hive.exec.scratchdir" in response_verbose.content)
@@ -166,7 +169,7 @@ for x in sys.stdin:
     Test basic query submission
     Test basic query submission
     """
     """
     # Minimal server operation
     # Minimal server operation
-    assert_equal("echo", beeswax.db_utils.db_client().echo("echo"))
+    assert_equal("echo", beeswax.db_utils.db_client(get_query_server()).echo("echo"))
 
 
     # Table should have been created
     # Table should have been created
     response = self.client.get("/beeswax/tables")
     response = self.client.get("/beeswax/tables")
@@ -294,14 +297,14 @@ for x in sys.stdin:
     """
     """
     query_msg.configuration = []
     query_msg.configuration = []
     query_msg.hadoop_user = "test"
     query_msg.hadoop_user = "test"
-    handle = beeswax.db_utils.db_client().executeAndWait(query_msg, "")
+    handle = beeswax.db_utils.db_client(get_query_server()).executeAndWait(query_msg, "")
 
 
-    results = beeswax.db_utils.db_client().fetch(handle, True, 5)
+    results = beeswax.db_utils.db_client(get_query_server()).fetch(handle, True, 5)
     row_list = list(parse_results(results.data))
     row_list = list(parse_results(results.data))
     assert_equal(len(row_list), 5)
     assert_equal(len(row_list), 5)
 
 
-    beeswax.db_utils.db_client().close(handle)
-    beeswax.db_utils.db_client().clean(handle.log_context)
+    beeswax.db_utils.db_client(get_query_server()).close(handle)
+    beeswax.db_utils.db_client(get_query_server()).clean(handle.log_context)
 
 
 
 
   def test_sync_query_error(self):
   def test_sync_query_error(self):
@@ -317,7 +320,7 @@ for x in sys.stdin:
     query_msg.configuration = []
     query_msg.configuration = []
     query_msg.hadoop_user = "test"
     query_msg.hadoop_user = "test"
     try:
     try:
-      handle = beeswax.db_utils.db_client().executeAndWait(query_msg, "")
+      handle = beeswax.db_utils.db_client(get_query_server()).executeAndWait(query_msg, "")
     except ttypes.BeeswaxException, bex:
     except ttypes.BeeswaxException, bex:
       assert_equal(bex.errorCode, 40000)
       assert_equal(bex.errorCode, 40000)
       assert_equal(bex.SQLState, "42000")
       assert_equal(bex.SQLState, "42000")
@@ -339,7 +342,7 @@ for x in sys.stdin:
       def __init__(self, **entries):
       def __init__(self, **entries):
         self.__dict__.update(entries)
         self.__dict__.update(entries)
 
 
-    client = beeswax.db_utils.db_client()
+    client = beeswax.db_utils.db_client(get_query_server())
 
 
     prev_get_default_configuration = client.get_default_configuration
     prev_get_default_configuration = client.get_default_configuration
     prev_client = client._client
     prev_client = client._client
@@ -379,12 +382,12 @@ for x in sys.stdin:
     design_id = response.context["design"].id
     design_id = response.context["design"].id
 
 
     # Don't fill out the form
     # Don't fill out the form
-    response = self.client.post("/beeswax/execute_parameterized/%d" % design_id)
+    response = self.client.post("/beeswax/execute_parameterized/%d" % design_id, { 'query_servers-server': 'default' })
     assert_true("parameterization.mako", response.template)
     assert_true("parameterization.mako", response.template)
 
 
     # Now fill it out
     # Now fill it out
     response = self.client.post("/beeswax/execute_parameterized/%d" % design_id,
     response = self.client.post("/beeswax/execute_parameterized/%d" % design_id,
-      { "parameterization-x": str(1), "parameterization-y": str(2) }, follow=True)
+      { "parameterization-x": str(1), "parameterization-y": str(2), 'query_servers-server': 'default' }, follow=True)
 
 
     assert_true("watch_wait.mako" in response.template)
     assert_true("watch_wait.mako" in response.template)
     # Check that substitution happened!
     # Check that substitution happened!
@@ -393,7 +396,8 @@ for x in sys.stdin:
 
 
     # Check that error handling is reasonable
     # Check that error handling is reasonable
     response = self.client.post("/beeswax/execute_parameterized/%d" % design_id,
     response = self.client.post("/beeswax/execute_parameterized/%d" % design_id,
-      { "parameterization-x": "'_this_is_not SQL ", "parameterization-y": str(2) }, follow=True)
+      { "parameterization-x": "'_this_is_not SQL ", "parameterization-y": str(2), 'query_servers-server': 'default' },
+      follow=True)
     assert_true("execute.mako" in response.template)
     assert_true("execute.mako" in response.template)
     log = response.context["log"]
     log = response.context["log"]
     assert_true(search_log_line('ql.Driver', 'FAILED: ParseException', log), log)
     assert_true(search_log_line('ql.Driver', 'FAILED: ParseException', log), log)
@@ -488,8 +492,10 @@ for x in sys.stdin:
     query_msg.query = 'SELECT foo FROM test limit %d' % (limit,)
     query_msg.query = 'SELECT foo FROM test limit %d' % (limit,)
     query_msg.configuration = []
     query_msg.configuration = []
     query_msg.hadoop_user = "test"
     query_msg.hadoop_user = "test"
-    handle = beeswax.db_utils.db_client().query(query_msg)
+    handle = beeswax.db_utils.db_client(get_query_server()).query(query_msg)
     query_data = beeswax.models.QueryHistory(server_id=handle.id, log_context=handle.log_context)
     query_data = beeswax.models.QueryHistory(server_id=handle.id, log_context=handle.log_context)
+    query_data.server_host = beeswax.conf.QUERY_SERVERS['default'].SERVER_HOST.get() # Needed as we query directly
+    query_data.server_port = beeswax.conf.QUERY_SERVERS['default'].SERVER_PORT.get()
     # Get the result in csv. Should have 3 + 1 header row.
     # Get the result in csv. Should have 3 + 1 header row.
     csv_resp = beeswax.data_export.download(query_data, 'csv')
     csv_resp = beeswax.data_export.download(query_data, 'csv')
     assert_equal(len(csv_resp.content.strip().split('\n')), limit + 1)
     assert_equal(len(csv_resp.content.strip().split('\n')), limit + 1)
@@ -499,14 +505,18 @@ for x in sys.stdin:
     query_msg.query = 'SELECT * FROM test'
     query_msg.query = 'SELECT * FROM test'
     query_msg.configuration = []
     query_msg.configuration = []
     query_msg.hadoop_user = "test"
     query_msg.hadoop_user = "test"
-    handle = beeswax.db_utils.db_client().query(query_msg)
+    handle = beeswax.db_utils.db_client(get_query_server()).query(query_msg)
     query_data = beeswax.models.QueryHistory(server_id=handle.id, log_context=handle.log_context)
     query_data = beeswax.models.QueryHistory(server_id=handle.id, log_context=handle.log_context)
+    query_data.server_host = beeswax.conf.QUERY_SERVERS['default'].SERVER_HOST.get() # Needed as we query directly
+    query_data.server_port = beeswax.conf.QUERY_SERVERS['default'].SERVER_PORT.get()
     # Get the result in xls. Then translate it into csv.
     # Get the result in xls. Then translate it into csv.
     xls_resp = beeswax.data_export.download(query_data, 'xls')
     xls_resp = beeswax.data_export.download(query_data, 'xls')
     translated_csv = xls2csv(xls_resp.content)
     translated_csv = xls2csv(xls_resp.content)
     # It should have 257 lines (256 + header)
     # It should have 257 lines (256 + header)
     assert_equal(len(translated_csv.strip('\r\n').split('\r\n')), 257)
     assert_equal(len(translated_csv.strip('\r\n').split('\r\n')), 257)
-    handle = beeswax.db_utils.db_client().query(query_msg)
+    handle = beeswax.db_utils.db_client(get_query_server()).query(query_msg)
+    query_data.server_host = beeswax.conf.QUERY_SERVERS['default'].SERVER_HOST.get() # Needed as we query directly
+    query_data.server_port = beeswax.conf.QUERY_SERVERS['default'].SERVER_PORT.get()
     # Get the result in csv.
     # Get the result in csv.
     csv_resp = beeswax.data_export.download(query_data, 'csv')
     csv_resp = beeswax.data_export.download(query_data, 'csv')
     assert_equal(csv_resp.content, translated_csv)
     assert_equal(csv_resp.content, translated_csv)
@@ -1031,10 +1041,21 @@ for x in sys.stdin:
     resp = self.client.get('/beeswax/table/myview')
     resp = self.client.get('/beeswax/table/myview')
     assert_equal(None, resp.context['top_rows'])
     assert_equal(None, resp.context['top_rows'])
     assert_true(resp.context['is_view'])
     assert_true(resp.context['is_view'])
-    assert_true("Beeswax View Metadata" in resp.content)
+    assert_true("View Metadata" in resp.content)
     assert_true("Drop View" in resp.content)
     assert_true("Drop View" in resp.content)
 
 
 
 
+  def test_select_query_server(self):
+    c = make_logged_in_client()
+
+    _make_query(c, 'SELECT bogus FROM test', server='default') # Improvement: mock another server
+
+    history = beeswax.models.QueryHistory.objects.latest('id')
+    assert_equal('default', history.server_name)
+    assert_equal('localhost', history.server_host)
+    assert_equal(BEESWAXD_TEST_PORT, history.server_port)
+
+
 def test_import_gzip_reader():
 def test_import_gzip_reader():
   """Test the gzip reader in create table"""
   """Test the gzip reader in create table"""
   # Make gzipped data
   # Make gzipped data

+ 65 - 44
apps/beeswax/src/beeswax/views.py

@@ -14,9 +14,6 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # See the License for the specific language governing permissions and
 # limitations under the License.
 # limitations under the License.
-"""
-Beeswax is a UI for Hive.
-"""
 
 
 import logging
 import logging
 import re
 import re
@@ -29,6 +26,7 @@ from django.http import HttpResponse, QueryDict
 from django.shortcuts import redirect
 from django.shortcuts import redirect
 from django.utils.encoding import force_unicode
 from django.utils.encoding import force_unicode
 from django.utils import simplejson
 from django.utils import simplejson
+from django.utils.translation import ugettext as _
 
 
 from desktop.lib import django_mako
 from desktop.lib import django_mako
 from desktop.lib.paginator import Paginator
 from desktop.lib.paginator import Paginator
@@ -37,10 +35,13 @@ from desktop.lib.django_util import login_notrequired, get_desktop_uri_prefix
 from desktop.lib.django_util import PopupException
 from desktop.lib.django_util import PopupException
 
 
 from hadoop.fs.exceptions import WebHdfsException
 from hadoop.fs.exceptions import WebHdfsException
+from jobsub.parameterization import find_variables, substitute_variables
+from filebrowser.views import location_to_url
 
 
 import beeswax.forms
 import beeswax.forms
 import beeswax.design
 import beeswax.design
 import beeswax.management.commands.beeswax_install_examples
 import beeswax.management.commands.beeswax_install_examples
+
 from beeswaxd import BeeswaxService
 from beeswaxd import BeeswaxService
 from beeswaxd.ttypes import QueryHandle, BeeswaxException, QueryNotFoundException
 from beeswaxd.ttypes import QueryHandle, BeeswaxException, QueryNotFoundException
 from beeswax import common
 from beeswax import common
@@ -48,12 +49,8 @@ from beeswax import data_export
 from beeswax import db_utils
 from beeswax import db_utils
 from beeswax import models
 from beeswax import models
 from beeswax import conf
 from beeswax import conf
+from beeswax.forms import QueryServerForm
 
 
-from jobsub.parameterization import find_variables, substitute_variables
-
-from filebrowser.views import location_to_url
-
-from django.utils.translation import ugettext as _
 
 
 LOG = logging.getLogger(__name__)
 LOG = logging.getLogger(__name__)
 
 
@@ -156,8 +153,8 @@ def drop_table(request, table):
     query_msg = make_beeswax_query(request, hql)
     query_msg = make_beeswax_query(request, hql)
     try:
     try:
       return execute_directly(request,
       return execute_directly(request,
-                               query_msg,
-                               on_success_url=urlresolvers.reverse(show_tables))
+                              query_msg,
+                              on_success_url=urlresolvers.reverse(show_tables))
     except BeeswaxException, ex:
     except BeeswaxException, ex:
       # Note that this state is difficult to get to.
       # Note that this state is difficult to get to.
       error_message, log = expand_exception(ex)
       error_message, log = expand_exception(ex)
@@ -262,7 +259,7 @@ def make_beeswax_query(request, hql, query_form=None):
   return query_msg
   return query_msg
 
 
 
 
-def execute_directly(request, query_msg, design=None, tablename=None,
+def execute_directly(request, query_msg, query_server=None, design=None, tablename=None,
                      on_success_url=None, on_success_params=None, **kwargs):
                      on_success_url=None, on_success_params=None, **kwargs):
   """
   """
   execute_directly(request, query_msg, tablename, design) -> HTTP response for execution
   execute_directly(request, query_msg, tablename, design) -> HTTP response for execution
@@ -273,6 +270,10 @@ def execute_directly(request, query_msg, design=None, tablename=None,
     query_msg
     query_msg
       The thrift Query object.
       The thrift Query object.
 
 
+    query_server
+      To which Query Server to submit the query.
+      Dictionary with keys: ['server_name', 'server_host', 'server_port'].
+
     design
     design
       The design associated with the query.
       The design associated with the query.
 
 
@@ -293,7 +294,8 @@ def execute_directly(request, query_msg, design=None, tablename=None,
   """
   """
   if design is not None:
   if design is not None:
     authorized_get_design(request, design.id)
     authorized_get_design(request, design.id)
-  history_obj = db_utils.execute_directly(request.user, query_msg, design, **kwargs)
+
+  history_obj = db_utils.execute_directly(request.user, query_msg, query_server, design, **kwargs)
   watch_url = urlresolvers.reverse("beeswax.views.watch_query", kwargs=dict(id=history_obj.id))
   watch_url = urlresolvers.reverse("beeswax.views.watch_query", kwargs=dict(id=history_obj.id))
 
 
   # Prepare the GET params for the watch_url
   # Prepare the GET params for the watch_url
@@ -360,23 +362,24 @@ def execute_query(request, design_id=None):
         break
         break
 
 
       query_str = _strip_trailing_semicolon(form.query.cleaned_data["query"])
       query_str = _strip_trailing_semicolon(form.query.cleaned_data["query"])
+      query_server = db_utils.get_query_server(form.query_servers.cleaned_data["server"])
 
 
       # (Optional) Parameterization.
       # (Optional) Parameterization.
-      parameterization = get_parameterization(request, query_str, form.query, design, to_explain)
+      parameterization = get_parameterization(request, query_str, form, design, to_explain)
       if parameterization:
       if parameterization:
         return parameterization
         return parameterization
 
 
       query_msg = make_beeswax_query(request, query_str, form)
       query_msg = make_beeswax_query(request, query_str, form)
       try:
       try:
         if to_explain:
         if to_explain:
-          return explain_directly(request, query_str, query_msg, design)
+          return explain_directly(request, query_str, query_msg, design, query_server)
         else:
         else:
           notify = form.query.cleaned_data.get('email_notify', False)
           notify = form.query.cleaned_data.get('email_notify', False)
-          return execute_directly(request, query_msg, design,
+          return execute_directly(request, query_msg, query_server, design,
                                   on_success_url=on_success_url,
                                   on_success_url=on_success_url,
                                   notify=notify)
                                   notify=notify)
       except BeeswaxException, ex:
       except BeeswaxException, ex:
-        error_message, log = expand_exception(ex)
+        error_message, log = expand_exception(ex, query_server)
         # Fall through to re-render the execute form.
         # Fall through to re-render the execute form.
     else:
     else:
       # GET request
       # GET request
@@ -397,20 +400,20 @@ def execute_query(request, design_id=None):
     'on_success_url': on_success_url,
     'on_success_url': on_success_url,
   })
   })
 
 
-def get_parameterization(request, query_str, query_form, design, is_explain):
+def get_parameterization(request, query_str, form, design, is_explain):
   """
   """
   Figures out whether a design is parameterizable, and, if so,
   Figures out whether a design is parameterizable, and, if so,
   returns a form to fill out.  Returns None if there's no parameterization
   returns a form to fill out.  Returns None if there's no parameterization
   to do.
   to do.
   """
   """
-  if query_form.cleaned_data["is_parameterized"]:
-    variables = find_variables(query_str)
-    form = make_parameterization_form(query_str)
-    if form:
+  if form.query.cleaned_data["is_parameterized"]:
+    parameters_form = make_parameterization_form(query_str)
+    if parameters_form:
       return render("parameterization.mako", request, dict(
       return render("parameterization.mako", request, dict(
-        form=form(prefix="parameterization"),
+        form=parameters_form(prefix="parameterization"),
         design=design,
         design=design,
-        explain=is_explain))
+        explain=is_explain,
+        query_servers=form.query_servers))
   return None
   return None
 
 
 def make_parameterization_form(query_str):
 def make_parameterization_form(query_str):
@@ -446,23 +449,29 @@ def _run_parameterized_query(request, design_id, explain):
   # Reconstitute the form
   # Reconstitute the form
   design_obj = beeswax.design.HQLdesign.loads(design.data)
   design_obj = beeswax.design.HQLdesign.loads(design.data)
   query_form = beeswax.forms.query_form()
   query_form = beeswax.forms.query_form()
-  query_form.bind(design_obj.get_query_dict())
+  params = design_obj.get_query_dict()
+  params.update(request.POST)
+  query_form.bind(params)
   assert query_form.is_valid()
   assert query_form.is_valid()
+
   query_str = _strip_trailing_semicolon(query_form.query.cleaned_data["query"])
   query_str = _strip_trailing_semicolon(query_form.query.cleaned_data["query"])
+  query_server = db_utils.get_query_server(query_form.query_servers.cleaned_data["server"])
+
   parameterization_form_cls = make_parameterization_form(query_str)
   parameterization_form_cls = make_parameterization_form(query_str)
   if not parameterization_form_cls:
   if not parameterization_form_cls:
     raise PopupException(_("Query is not parameterizable."))
     raise PopupException(_("Query is not parameterizable."))
+
   parameterization_form = parameterization_form_cls(request.REQUEST, prefix="parameterization")
   parameterization_form = parameterization_form_cls(request.REQUEST, prefix="parameterization")
   if parameterization_form.is_valid():
   if parameterization_form.is_valid():
     real_query = substitute_variables(query_str, parameterization_form.cleaned_data)
     real_query = substitute_variables(query_str, parameterization_form.cleaned_data)
     query_msg = make_beeswax_query(request, real_query, query_form)
     query_msg = make_beeswax_query(request, real_query, query_form)
     try:
     try:
       if explain:
       if explain:
-        return explain_directly(request, query_str, query_msg, design)
+        return explain_directly(request, query_str, query_msg, design, query_server)
       else:
       else:
-        return execute_directly(request, query_msg, design)
+        return execute_directly(request, query_msg, query_server, design)
     except BeeswaxException, ex:
     except BeeswaxException, ex:
-      error_message, log = expand_exception(ex)
+      error_message, log = expand_exception(ex, query_server)
       return render('execute.mako', request, {
       return render('execute.mako', request, {
         'action': urlresolvers.reverse(execute_query),
         'action': urlresolvers.reverse(execute_query),
         'design': design,
         'design': design,
@@ -471,12 +480,13 @@ def _run_parameterized_query(request, design_id, explain):
         'log': log,
         'log': log,
       })
       })
   else:
   else:
-    return render("parameterization.mako", request, dict(form=parameterization_form, design=design, explain=explain))
+    return render("parameterization.mako", request, dict(form=parameterization_form, design=design, explain=explain,
+                                                         query_servers=query_form.query_servers))
 
 
-def expand_exception(exc):
+def expand_exception(exc, query_server):
   """expand_exception(exc) -> (error msg, log message)"""
   """expand_exception(exc) -> (error msg, log message)"""
   try:
   try:
-    log = db_utils.db_client().get_log(exc.log_context)
+    log = db_utils.db_client(query_server).get_log(exc.log_context)
   except:
   except:
     # Always show something, even if server has died on the job.
     # Always show something, even if server has died on the job.
     log = _("Could not retrieve log.")
     log = _("Could not retrieve log.")
@@ -782,7 +792,7 @@ def watch_query(request, id):
     return format_preserving_redirect(request, results_url, request.GET)
     return format_preserving_redirect(request, results_url, request.GET)
 
 
   # Still running
   # Still running
-  log = db_utils.db_client().get_log(server_id)
+  log = db_utils.db_client(query_history.get_query_server()).get_log(server_id)
 
 
   # Keep waiting
   # Keep waiting
   # - Translate context into something more meaningful (type, data)
   # - Translate context into something more meaningful (type, data)
@@ -889,7 +899,7 @@ def view_results(request, id, first_row=0):
 
 
   # Retrieve query results
   # Retrieve query results
   try:
   try:
-    results = db_utils.db_client().fetch(handle, start_over, -1)
+    results = db_utils.db_client(query_history.get_query_server()).fetch(handle, start_over, -1)
     assert results.ready, _('Trying to display result that is not yet ready. Query id %(id)s') % {'id': id}
     assert results.ready, _('Trying to display result that is not yet ready. Query id %(id)s') % {'id': id}
     # We display the "Download" button only when we know
     # We display the "Download" button only when we know
     # that there are results:
     # that there are results:
@@ -897,7 +907,7 @@ def view_results(request, id, first_row=0):
     fetch_error = False
     fetch_error = False
   except BeeswaxException, ex:
   except BeeswaxException, ex:
     fetch_error = True
     fetch_error = True
-    error_message, log = expand_exception(ex)
+    error_message, log = expand_exception(ex, query_history.get_query_server())
 
 
   # Handle errors
   # Handle errors
   if fetch_error:
   if fetch_error:
@@ -911,7 +921,7 @@ def view_results(request, id, first_row=0):
       'can_save': False,
       'can_save': False,
     })
     })
 
 
-  log = db_utils.db_client().get_log(query_history.server_id)
+  log = db_utils.db_client(query_history.get_query_server()).get_log(query_history.server_id)
   download_urls = {}
   download_urls = {}
   if downloadable:
   if downloadable:
     for format in common.DL_FORMATS:
     for format in common.DL_FORMATS:
@@ -973,7 +983,7 @@ def save_results(request, id):
       assert request.POST.get('save')
       assert request.POST.get('save')
       handle = QueryHandle(id=query_history.server_id, log_context=query_history.log_context)
       handle = QueryHandle(id=query_history.server_id, log_context=query_history.log_context)
       try:
       try:
-        result_meta = db_utils.db_client().get_results_metadata(handle)
+        result_meta = db_utils.db_client(query_history.get_query_server()).get_results_metadata(handle)
       except QueryNotFoundException, ex:
       except QueryNotFoundException, ex:
         LOG.exception(ex)
         LOG.exception(ex)
         raise PopupException(_('Cannot find query.'))
         raise PopupException(_('Cannot find query.'))
@@ -1004,7 +1014,7 @@ def save_results(request, id):
                                       result_meta)
                                       result_meta)
           except BeeswaxException, bex:
           except BeeswaxException, bex:
             LOG.exception(bex)
             LOG.exception(bex)
-            error_msg, log = expand_exception(bex)
+            error_msg, log = expand_exception(bex, query_history.get_query_server())
       except WebHdfsException, ex:
       except WebHdfsException, ex:
         raise PopupException(_('The table could not be saved.'), detail=ex)
         raise PopupException(_('The table could not be saved.'), detail=ex)
       except IOError, ex:
       except IOError, ex:
@@ -1030,12 +1040,14 @@ def _save_results_ctas(request, query_history, target_table, result_meta):
   Handle saving results as a new table. Returns HTTP response.
   Handle saving results as a new table. Returns HTTP response.
   May raise BeeswaxException, IOError.
   May raise BeeswaxException, IOError.
   """
   """
+  query_server = query_history.get_query_server() # Query server requires DDL support
+
   # Case 1: The results are straight from an existing table
   # Case 1: The results are straight from an existing table
   if result_meta.in_tablename:
   if result_meta.in_tablename:
     hql = 'CREATE TABLE `%s` AS SELECT * FROM %s' % (target_table, result_meta.in_tablename)
     hql = 'CREATE TABLE `%s` AS SELECT * FROM %s' % (target_table, result_meta.in_tablename)
     query_msg = make_beeswax_query(request, hql)
     query_msg = make_beeswax_query(request, hql)
     # Display the CTAS running. Could take a long time.
     # Display the CTAS running. Could take a long time.
-    return execute_directly(request, query_msg, on_success_url=urlresolvers.reverse(show_tables))
+    return execute_directly(request, query_msg, query_server, on_success_url=urlresolvers.reverse(show_tables))
 
 
   # Case 2: The results are in some temporary location
   # Case 2: The results are in some temporary location
   # 1. Create table
   # 1. Create table
@@ -1063,7 +1075,7 @@ def _save_results_ctas(request, query_history, target_table, result_meta):
         ''' % (target_table, cols, delim.zfill(3))
         ''' % (target_table, cols, delim.zfill(3))
 
 
   query_msg = make_beeswax_query(request, hql)
   query_msg = make_beeswax_query(request, hql)
-  db_utils.execute_and_wait(request.user, query_msg)
+  db_utils.execute_and_wait(request.user, query_msg, query_server)
 
 
   try:
   try:
     # 2. Move the results into the table's storage
     # 2. Move the results into the table's storage
@@ -1077,7 +1089,7 @@ def _save_results_ctas(request, query_history, target_table, result_meta):
     LOG.error('Error moving data into storage of table %s. Will drop table.' % (target_table,))
     LOG.error('Error moving data into storage of table %s. Will drop table.' % (target_table,))
     query_msg = make_beeswax_query(request, 'DROP TABLE `%s`' % (target_table,))
     query_msg = make_beeswax_query(request, 'DROP TABLE `%s`' % (target_table,))
     try:
     try:
-      db_utils.execute_directly(request.user, query_msg)        # Don't wait for results
+      db_utils.execute_directly(request.user, query_msg, query_server)        # Don't wait for results
     except Exception, double_trouble:
     except Exception, double_trouble:
       LOG.exception('Failed to drop table "%s" as well: %s' % (target_table, double_trouble))
       LOG.exception('Failed to drop table "%s" as well: %s' % (target_table, double_trouble))
     raise ex
     raise ex
@@ -1149,9 +1161,18 @@ def describe_partitions(request, table):
 
 
 
 
 def configuration(request):
 def configuration(request):
-  config_values = db_utils.db_client().get_default_configuration(
-                      bool(request.REQUEST.get("include_hadoop", False)))
-  return render("configuration.mako", request, dict(config_values=config_values))
+  if request.method == 'POST':
+    server_form = QueryServerForm(request.POST)
+    if server_form.is_valid():
+      query_server = db_utils.get_query_server(server_form.cleaned_data["server"])
+      config_values = db_utils.db_client(query_server).get_default_configuration(
+                          bool(request.REQUEST.get("include_hadoop", False)))
+  else:
+    server_form = QueryServerForm()
+    config_values = {}
+
+  return render("configuration.mako", request, {'config_values': config_values,
+                                                'server_form': server_form})
 
 
 def _copy_prefix(prefix, base_dict):
 def _copy_prefix(prefix, base_dict):
   """Copy keys starting with ``prefix``"""
   """Copy keys starting with ``prefix``"""
@@ -1334,11 +1355,11 @@ def collapse_whitespace(s):
   return WHITESPACE.sub(" ", s).strip()
   return WHITESPACE.sub(" ", s).strip()
 
 
 
 
-def explain_directly(request, query_str, query_msg, design):
+def explain_directly(request, query_str, query_msg, design, query_server):
   """
   """
   Runs explain query.
   Runs explain query.
   """
   """
-  explanation = db_utils.db_client().explain(query_msg)
+  explanation = db_utils.db_client(query_server).explain(query_msg)
   context = ("design", design)
   context = ("design", design)
   return render('explain.mako', request,
   return render('explain.mako', request,
     dict(query=query_str, explanation=explanation.textual, query_context=context))
     dict(query=query_str, explanation=explanation.textual, query_context=context))

+ 1 - 1
apps/jobsub/src/jobsub/conf.py

@@ -40,7 +40,7 @@ SAMPLE_DATA_DIR = Config(
   private=True)
   private=True)
 
 
 
 
-# Deprecated!
+# Deprecated! To remove in Hue 3
 # These configs were moved to desktop liboozie.
 # These configs were moved to desktop liboozie.
 # If liboozie is not configured these settings will be used.
 # If liboozie is not configured these settings will be used.
 # They also have priority if liboozie defines them.
 # They also have priority if liboozie defines them.

+ 27 - 3
desktop/conf.dist/hue.ini

@@ -306,12 +306,24 @@
 ###########################################################################
 ###########################################################################
 
 
 [beeswax]
 [beeswax]
+
+  # Deprecated! Will be removed in Hue 3
+  # Multiple sections are now available in query_servers
+  # Host where Beeswax internal metastore Thrift daemon is running
+  ## beeswax_meta_server_host=localhost
+
+  # Deprecated! Will be removed in Hue 3
+  # Multiple sections are now available in query_servers  
   # Configure the port the internal metastore daemon runs on. Used only if
   # Configure the port the internal metastore daemon runs on. Used only if
-  # hive.metastore.local is true.
+  # hive.metastore.local is true. 
   ## beeswax_meta_server_port=8003
   ## beeswax_meta_server_port=8003
 
 
-  # Configure the port the beeswax thrift server runs on
-  ## beeswax_server_port=8002
+  # Host where Beeswax internal metastore Thrift daemon is running
+  ## beeswax_meta_server_host=localhost
+  
+  # Configure the port the internal metastore daemon runs on. Used only if
+  # hive.metastore.local is true. 
+  ## beeswax_meta_server_port=8003
 
 
   # Hive home directory
   # Hive home directory
   ## hive_home_dir=/usr/lib/hive
   ## hive_home_dir=/usr/lib/hive
@@ -334,6 +346,18 @@
   # visible only to the owner and administrators.
   # visible only to the owner and administrators.
   ## share_saved_queries=true
   ## share_saved_queries=true
 
 
+ # One entry for each Query Server that can execute some SQL queries.
+ # This must be in the bottom of the [beewax] section.
+ [[query_servers]]
+
+   [[[default]]]
+   # Host where the Query Server Thrift daemon is running
+   ## server_host=localhost
+   # Configure the port the Query Server Thrift server 
+   ## server_port=8002
+   # If DDL queries are supported (e.g. DROP can be sent directly to this server)
+   ## support_ddl=True
+
 
 
 ###########################################################################
 ###########################################################################
 # Settings to configure Job Designer
 # Settings to configure Job Designer

+ 26 - 3
desktop/conf/pseudo-distributed.ini.tmpl

@@ -309,12 +309,24 @@
 ###########################################################################
 ###########################################################################
 
 
 [beeswax]
 [beeswax]
+
+  # Deprecated! Will be removed in Hue 3
+  # Multiple sections are now available in query_servers
+  # Host where Beeswax internal metastore Thrift daemon is running
+  ## beeswax_meta_server_host=localhost
+
+  # Deprecated! Will be removed in Hue 3
+  # Multiple sections are now available in query_servers  
   # Configure the port the internal metastore daemon runs on. Used only if
   # Configure the port the internal metastore daemon runs on. Used only if
-  # hive.metastore.local is true.
+  # hive.metastore.local is true. 
   ## beeswax_meta_server_port=8003
   ## beeswax_meta_server_port=8003
 
 
-  # Configure the port the beeswax thrift server runs on
-  ## beeswax_server_port=8002
+  # Host where Beeswax internal metastore Thrift daemon is running
+  ## beeswax_meta_server_host=localhost
+  
+  # Configure the port the internal metastore daemon runs on. Used only if
+  # hive.metastore.local is true. 
+  ## beeswax_meta_server_port=8003
 
 
   # Hive home directory
   # Hive home directory
   ## hive_home_dir=/usr/lib/hive
   ## hive_home_dir=/usr/lib/hive
@@ -337,6 +349,17 @@
   # visible only to the owner and administrators.
   # visible only to the owner and administrators.
   ## share_saved_queries=true
   ## share_saved_queries=true
 
 
+ # One entry for each Query Server that can execute some SQL queries.
+ # This must be in the bottom of the [beewax] section.
+ [[query_servers]]
+
+   [[[default]]]
+   # Host where the Query Server Thrift daemon is running
+   ## server_host=localhost
+   # Configure the port the Query Server Thrift server 
+   ## server_port=8002
+   # If DDL queries are supported (e.g. DROP can be sent directly to this server)
+   ## support_ddl=True
 
 
 ###########################################################################
 ###########################################################################
 # Settings to configure Job Designer
 # Settings to configure Job Designer

+ 1 - 1
desktop/core/src/desktop/lib/thrift_util.py

@@ -458,7 +458,7 @@ def thrift2json(tft):
 
 
 def _jsonable2thrift_helper(jsonable, type_enum, spec_args, default, recursion_depth=0):
 def _jsonable2thrift_helper(jsonable, type_enum, spec_args, default, recursion_depth=0):
   """
   """
-  Recursive impelementation method of jsonable2thrift.
+  Recursive implementation method of jsonable2thrift.
 
 
   type_enum corresponds to TType.  spec_args is part of the
   type_enum corresponds to TType.  spec_args is part of the
   thrift_spec explained in Thrift's code generator.  See 
   thrift_spec explained in Thrift's code generator.  See