You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@impala.apache.org by st...@apache.org on 2021/03/09 01:26:58 UTC

[impala] branch master updated (a47700e -> d89c04b)

This is an automated email from the ASF dual-hosted git repository.

stigahuang pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/impala.git.


    from a47700e  IMPALA-10450: Catalogd crashes due to exception in ThriftDebugString
     new ca17e30  IMPALA-10550: Add External Frontend service port
     new d89c04b  IMPALA-10529: Fix hit DCHECK in DiskIoMgr::AssignQueue in core-s3 build

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 be/src/rpc/authentication.cc          | 12 +++++++
 be/src/rpc/authentication.h           |  7 ++++
 be/src/runtime/io/disk-io-mgr.cc      | 21 ++++++------
 be/src/runtime/io/disk-io-mgr.h       |  6 ++--
 be/src/runtime/io/scan-range.cc       |  4 +--
 be/src/runtime/test-env.h             |  3 ++
 be/src/runtime/tmp-file-mgr-test.cc   | 28 ++++++++++++++++
 be/src/runtime/tmp-file-mgr.cc        | 10 ++++--
 be/src/service/impala-server.cc       | 38 ++++++++++++++++++++--
 be/src/service/impala-server.h        |  7 +++-
 be/src/service/impalad-main.cc        |  5 +--
 be/src/testutil/in-process-servers.cc |  2 +-
 bin/start-impala-cluster.py           | 12 +++++--
 common/thrift/metrics.json            | 60 +++++++++++++++++++++++++++++++++++
 docker/impalad_coordinator/Dockerfile |  2 ++
 tests/common/impala_cluster.py        |  1 +
 16 files changed, 193 insertions(+), 25 deletions(-)


[impala] 02/02: IMPALA-10529: Fix hit DCHECK in DiskIoMgr::AssignQueue in core-s3 build

Posted by st...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

stigahuang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/impala.git

commit d89c04bf806682d3449c566ce979632bd2ac5b29
Author: Yida Wu <wy...@gmail.com>
AuthorDate: Sat Feb 27 18:54:09 2021 -0800

    IMPALA-10529: Fix hit DCHECK in DiskIoMgr::AssignQueue in core-s3 build
    
    For start option "scratch_dirs", it only considers local filesystem as
    the default filesystem, regardless of the setting of DefaultFS(for a
    remote scratch dir, it needs to explicitly set it with the remote fs
    prefix). However, the function AssignQueue() would assign the queue
    based on not only the path string but also the default filesystem
    setting. For example, if scratch_dirs is set as "/tmp", the scratch dir
    is supposed to be in the local filesystem, but the AssignQueue() would
    consider it as "s3a://xxx/tmp" if a s3 path is set as the default fs.
    To fix this, the solution is to add a bool variable to AssignQueue() to
    decide whether or not to check the default fs setting when parsing the
    file path. For all of the scratch dirs, AssignQueue() won't check the
    default fs.
    
    Tests:
    Added a unit testcase: TmpFileMgrTest::TestSpillingWithRemoteDefaultFS.
    Ran and Passed TmpFileMgrTest.
    
    Change-Id: Ic07945abe65d90235aa8dea92dd3c3821a4f1f53
    Reviewed-on: http://gerrit.cloudera.org:8080/17136
    Reviewed-by: Impala Public Jenkins <im...@cloudera.com>
    Tested-by: Impala Public Jenkins <im...@cloudera.com>
---
 be/src/runtime/io/disk-io-mgr.cc    | 21 +++++++++++----------
 be/src/runtime/io/disk-io-mgr.h     |  6 ++++--
 be/src/runtime/io/scan-range.cc     |  4 ++--
 be/src/runtime/test-env.h           |  3 +++
 be/src/runtime/tmp-file-mgr-test.cc | 28 ++++++++++++++++++++++++++++
 be/src/runtime/tmp-file-mgr.cc      | 10 ++++++++--
 6 files changed, 56 insertions(+), 16 deletions(-)

diff --git a/be/src/runtime/io/disk-io-mgr.cc b/be/src/runtime/io/disk-io-mgr.cc
index f963de4..f05afae 100644
--- a/be/src/runtime/io/disk-io-mgr.cc
+++ b/be/src/runtime/io/disk-io-mgr.cc
@@ -810,22 +810,23 @@ Status DiskIoMgr::WriteRangeHelper(FILE* file_handle, WriteRange* write_range) {
   return Status::OK();
 }
 
-int DiskIoMgr::AssignQueue(const char* file, int disk_id, bool expected_local) {
+int DiskIoMgr::AssignQueue(
+    const char* file, int disk_id, bool expected_local, bool check_default_fs) {
   // If it's a remote range, check for an appropriate remote disk queue.
   if (!expected_local) {
-    if (IsHdfsPath(file) && FLAGS_num_remote_hdfs_io_threads > 0) {
+    if (IsHdfsPath(file, check_default_fs) && FLAGS_num_remote_hdfs_io_threads > 0) {
       return RemoteDfsDiskId();
     }
-    if (IsS3APath(file)) return RemoteS3DiskId();
-    if (IsABFSPath(file)) return RemoteAbfsDiskId();
-    if (IsADLSPath(file)) return RemoteAdlsDiskId();
-    if (IsOzonePath(file)) return RemoteOzoneDiskId();
+    if (IsS3APath(file, check_default_fs)) return RemoteS3DiskId();
+    if (IsABFSPath(file, check_default_fs)) return RemoteAbfsDiskId();
+    if (IsADLSPath(file, check_default_fs)) return RemoteAdlsDiskId();
+    if (IsOzonePath(file, check_default_fs)) return RemoteOzoneDiskId();
   }
   // Assign to a local disk queue.
-  DCHECK(!IsS3APath(file)); // S3 is always remote.
-  DCHECK(!IsABFSPath(file)); // ABFS is always remote.
-  DCHECK(!IsADLSPath(file)); // ADLS is always remote.
-  DCHECK(!IsOzonePath(file)); // Ozone is always remote.
+  DCHECK(!IsS3APath(file, check_default_fs)); // S3 is always remote.
+  DCHECK(!IsABFSPath(file, check_default_fs)); // ABFS is always remote.
+  DCHECK(!IsADLSPath(file, check_default_fs)); // ADLS is always remote.
+  DCHECK(!IsOzonePath(file, check_default_fs)); // Ozone is always remote.
   if (disk_id == -1) {
     // disk id is unknown, assign it an arbitrary one.
     disk_id = next_disk_id_.Add(1);
diff --git a/be/src/runtime/io/disk-io-mgr.h b/be/src/runtime/io/disk-io-mgr.h
index 32a50e4..400702a 100644
--- a/be/src/runtime/io/disk-io-mgr.h
+++ b/be/src/runtime/io/disk-io-mgr.h
@@ -288,8 +288,10 @@ class DiskIoMgr : public CacheLineAligned {
   /// Determine which disk queue this file should be assigned to.  Returns an index into
   /// disk_queues_.  The disk_id is the volume ID for the local disk that holds the
   /// files, or -1 if unknown.  Flag expected_local is true iff this impalad is
-  /// co-located with the datanode for this file.
-  int AssignQueue(const char* file, int disk_id, bool expected_local = false);
+  /// co-located with the datanode for this file. Flag check_default_fs is false iff
+  /// the file is a temporary file.
+  int AssignQueue(
+      const char* file, int disk_id, bool expected_local, bool check_default_fs);
 
   int64_t min_buffer_size() const { return min_buffer_size_; }
   int64_t max_buffer_size() const { return max_buffer_size_; }
diff --git a/be/src/runtime/io/scan-range.cc b/be/src/runtime/io/scan-range.cc
index d1d75c9..87b5273 100644
--- a/be/src/runtime/io/scan-range.cc
+++ b/be/src/runtime/io/scan-range.cc
@@ -506,8 +506,8 @@ ScanRange* ScanRange::AllocateScanRange(ObjectPool* obj_pool, hdfsFS fs, const c
   DCHECK_GE(disk_id, -1);
   DCHECK_GE(offset, 0);
   DCHECK_GE(len, 0);
-  disk_id =
-      ExecEnv::GetInstance()->disk_io_mgr()->AssignQueue(file, disk_id, expected_local);
+  disk_id = ExecEnv::GetInstance()->disk_io_mgr()->AssignQueue(
+      file, disk_id, expected_local, /* check_default_fs */ true);
   ScanRange* range = obj_pool->Add(new ScanRange);
   range->Reset(fs, file, len, offset, disk_id, expected_local, mtime, buffer_opts,
       move(sub_ranges), metadata);
diff --git a/be/src/runtime/test-env.h b/be/src/runtime/test-env.h
index 6e50fd0..5dec325 100644
--- a/be/src/runtime/test-env.h
+++ b/be/src/runtime/test-env.h
@@ -52,6 +52,9 @@ class TestEnv {
   /// If not called, a process memory tracker with no limit is created.
   void SetProcessMemTrackerArgs(int64_t bytes_limit, bool use_metrics);
 
+  /// Set the Default FS of ExecEnv.
+  void SetDefaultFS(const string& fs) { exec_env_->default_fs_ = fs; }
+
   /// Initialize the TestEnv with the specified arguments.
   Status Init();
 
diff --git a/be/src/runtime/tmp-file-mgr-test.cc b/be/src/runtime/tmp-file-mgr-test.cc
index 22d0ce0e..e598b91 100644
--- a/be/src/runtime/tmp-file-mgr-test.cc
+++ b/be/src/runtime/tmp-file-mgr-test.cc
@@ -1757,4 +1757,32 @@ TEST_F(TmpFileMgrTest, TestTmpFileBufferPoolOneWriteDone) {
   TestTmpFileBufferPoolTearDown(tmp_file_mgr);
 }
 
+/// Test setting a remote fs for the default fs, but should not affect the spilling.
+TEST_F(TmpFileMgrTest, TestSpillingWithRemoteDefaultFS) {
+  vector<string> tmp_dirs({"/tmp/tmp-file-mgr-test.1"});
+  TmpFileMgr tmp_file_mgr;
+  RemoveAndCreateDirs(tmp_dirs);
+  string org_default_fs = test_env_->exec_env()->default_fs();
+  string fake_remote_default_fs = "s3a://fake_s3";
+  test_env_->SetDefaultFS(fake_remote_default_fs);
+
+  ASSERT_OK(tmp_file_mgr.InitCustom(tmp_dirs, true, "", false, metrics_.get()));
+  TUniqueId id;
+  TmpFileGroup file_group(&tmp_file_mgr, io_mgr(), profile_, id);
+  string data = "arbitrary data";
+  MemRange data_mem_range(reinterpret_cast<uint8_t*>(&data[0]), data.size());
+
+  unique_ptr<TmpWriteHandle> handle;
+  WriteRange::WriteDoneCallback callback = [this](const Status& status) {
+    EXPECT_TRUE(status.ok());
+    SignalCallback(status);
+  };
+  ASSERT_OK(file_group.Write(data_mem_range, callback, &handle));
+  WaitForWrite(handle.get());
+  WaitForCallbacks(1);
+  file_group.Close();
+  test_env_->SetDefaultFS(org_default_fs);
+  test_env_->TearDownQueries();
+}
+
 } // namespace impala
diff --git a/be/src/runtime/tmp-file-mgr.cc b/be/src/runtime/tmp-file-mgr.cc
index 738cc72..afc2436 100644
--- a/be/src/runtime/tmp-file-mgr.cc
+++ b/be/src/runtime/tmp-file-mgr.cc
@@ -657,10 +657,14 @@ TmpFile::TmpFile(
     blacklisted_(false) {}
 
 int TmpFile::AssignDiskQueue(bool is_local_buffer) const {
+  // The file paths of TmpFiles are absolute paths, doesn't support default fs.
   if (is_local_buffer) {
-    return file_group_->io_mgr_->AssignQueue(local_buffer_path_.c_str(), -1, true);
+    // Assign a disk queue for a local buffer, which is associated with a remote file.
+    return file_group_->io_mgr_->AssignQueue(local_buffer_path_.c_str(),
+        /* disk_id */ -1, /* expected_local */ true, /* check_default_fs */ false);
   }
-  return file_group_->io_mgr_->AssignQueue(path_.c_str(), disk_id_, expected_local_);
+  return file_group_->io_mgr_->AssignQueue(
+      path_.c_str(), disk_id_, expected_local_, /* check_default_fs */ false);
 }
 
 bool TmpFile::Blacklist(const ErrorMsg& msg) {
@@ -1518,6 +1522,8 @@ Status TmpWriteHandle::Write(RequestContext* io_ctx, MemRange buffer,
 
   // Set all member variables before calling AddWriteRange(): after it succeeds,
   // WriteComplete() may be called concurrently with the remainder of this function.
+  // If the TmpFile is not local, the disk queue assigned should be for the
+  // buffer.
   data_len_ = buffer.len();
   file_ = tmp_file;
   write_range_.reset(new WriteRange(tmp_file->path(), file_offset,


[impala] 01/02: IMPALA-10550: Add External Frontend service port

Posted by st...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

stigahuang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/impala.git

commit ca17e307ab3abb2c95c27b3ba749adf6bf16efc7
Author: John Sherman <jf...@cloudera.com>
AuthorDate: Thu Apr 2 22:46:17 2020 +0000

    IMPALA-10550: Add External Frontend service port
    
    - If external_fe_port flag is >0, spins up a new HS2 compatible
      service port
    - Added enable_external_fe_support option to start-impala-cluster.py
      - which when detected will start impala clusters with
      external_fe_port on 21150-21152
    - Modify impalad_coordinator Dockerfile to expose external frontend
      port at 21150
    - The intent of this commit is to separate external frontend
      connections from normal hs2 connections
      - This allows different security policy to be applied to
      each type of connection. The external_fe_port should be considered
      a privileged service and should only be exposed to an external
      frontend that does user authentication and does authorization
      checks on generated plans
    
    Change-Id: I991b5b05e12e37d8739e18ed1086bbb0228acc40
    Reviewed-by: Aman Sinha <am...@cloudera.com>
    Reviewed-on: http://gerrit.cloudera.org:8080/17125
    Reviewed-by: Impala Public Jenkins <im...@cloudera.com>
    Tested-by: Impala Public Jenkins <im...@cloudera.com>
---
 be/src/rpc/authentication.cc          | 12 +++++++
 be/src/rpc/authentication.h           |  7 ++++
 be/src/service/impala-server.cc       | 38 ++++++++++++++++++++--
 be/src/service/impala-server.h        |  7 +++-
 be/src/service/impalad-main.cc        |  5 +--
 be/src/testutil/in-process-servers.cc |  2 +-
 bin/start-impala-cluster.py           | 12 +++++--
 common/thrift/metrics.json            | 60 +++++++++++++++++++++++++++++++++++
 docker/impalad_coordinator/Dockerfile |  2 ++
 tests/common/impala_cluster.py        |  1 +
 10 files changed, 137 insertions(+), 9 deletions(-)

diff --git a/be/src/rpc/authentication.cc b/be/src/rpc/authentication.cc
index 47bcebb..9931e2f 100644
--- a/be/src/rpc/authentication.cc
+++ b/be/src/rpc/authentication.cc
@@ -87,6 +87,7 @@ DECLARE_string(be_principal);
 DECLARE_string(krb5_ccname);
 DECLARE_string(krb5_conf);
 DECLARE_string(krb5_debug_file);
+DECLARE_int32(external_fe_port);
 
 // Defined in kudu/security/init.cc
 DECLARE_bool(use_system_auth_to_local);
@@ -1399,6 +1400,12 @@ Status AuthManager::Init() {
   }
   RETURN_IF_ERROR(internal_auth_provider_->Start());
   RETURN_IF_ERROR(external_auth_provider_->Start());
+  if (FLAGS_external_fe_port > 0 ||
+      (TestInfo::is_test() && FLAGS_external_fe_port == 0)) {
+    external_fe_auth_provider_.reset(new NoAuthProvider());
+    LOG(INFO) << "External Frontend communication is not authenticated";
+    RETURN_IF_ERROR(external_fe_auth_provider_->Start());
+  }
   return Status::OK();
 }
 
@@ -1407,6 +1414,11 @@ AuthProvider* AuthManager::GetExternalAuthProvider() {
   return external_auth_provider_.get();
 }
 
+AuthProvider* AuthManager::GetExternalFrontendAuthProvider() {
+  DCHECK(external_fe_auth_provider_.get() != NULL);
+  return external_fe_auth_provider_.get();
+}
+
 AuthProvider* AuthManager::GetInternalAuthProvider() {
   DCHECK(internal_auth_provider_.get() != NULL);
   return internal_auth_provider_.get();
diff --git a/be/src/rpc/authentication.h b/be/src/rpc/authentication.h
index cfd28c1..313d5d0 100644
--- a/be/src/rpc/authentication.h
+++ b/be/src/rpc/authentication.h
@@ -67,6 +67,12 @@ class AuthManager {
   /// internal process.
   AuthProvider* GetExternalAuthProvider();
 
+  /// Returns the authentication provider to use for "external frontend" communication.
+  /// This only applies to the server side of a connection; the client side of said
+  /// connection is never an internal process. Currently this is either null if
+  /// external_fe_port <= 0 or NoAuthProvider.
+  AuthProvider* GetExternalFrontendAuthProvider();
+
   /// Returns the authentication provider to use for internal daemon <-> daemon
   /// connections.  This goes for both the client and server sides.  An example
   /// connection this applies to would be backend <-> statestore.
@@ -89,6 +95,7 @@ class AuthManager {
   boost::scoped_ptr<AuthProvider> internal_auth_provider_;
   boost::scoped_ptr<AuthProvider> external_auth_provider_;
   boost::scoped_ptr<AuthProvider> external_http_auth_provider_;
+  boost::scoped_ptr<AuthProvider> external_fe_auth_provider_;
 
   /// Used to authenticate usernames and passwords to LDAP.
   std::unique_ptr<ImpalaLdap> ldap_;
diff --git a/be/src/service/impala-server.cc b/be/src/service/impala-server.cc
index 09e6dc2..0e8bfe2 100644
--- a/be/src/service/impala-server.cc
+++ b/be/src/service/impala-server.cc
@@ -147,6 +147,10 @@ DEFINE_int32(hs2_port, 21050, "port on which HiveServer2 client requests are ser
     "If 0 or less, the HiveServer2 server is not started.");
 DEFINE_int32(hs2_http_port, 28000, "port on which HiveServer2 HTTP(s) client "
     "requests are served. If 0 or less, the HiveServer2 http server is not started.");
+DEFINE_int32(external_fe_port, 0, "port on which External Frontend requests are served. "
+    "If 0 or less, the External Frontend server is not started. Careful consideration "
+    "must be taken when enabling due to the fact that this port is currently always "
+    "unauthenticated.");
 
 DEFINE_int32(fe_service_threads, 64,
     "number of threads available to serve client requests");
@@ -2341,7 +2345,8 @@ void ImpalaServer::ConnectionEnd(
     }
   } else {
     DCHECK(connection_context.server_name ==  HS2_SERVER_NAME
-        || connection_context.server_name == HS2_HTTP_SERVER_NAME);
+        || connection_context.server_name == HS2_HTTP_SERVER_NAME
+        || connection_context.server_name == EXTERNAL_FRONTEND_SERVER_NAME);
     for (const TUniqueId& session_id : disconnected_sessions) {
       shared_ptr<SessionState> state;
       Status status = GetSessionState(session_id, SecretArg::SkipSecretCheck(), &state);
@@ -2703,8 +2708,8 @@ void ImpalaServer::ExpireQuery(ClientRequestState* crs, const Status& status) {
   crs->set_expired();
 }
 
-Status ImpalaServer::Start(
-    int32_t beeswax_port, int32_t hs2_port, int32_t hs2_http_port) {
+Status ImpalaServer::Start(int32_t beeswax_port, int32_t hs2_port,
+    int32_t hs2_http_port, int32_t external_fe_port) {
   exec_env_->SetImpalaServer(this);
 
   // We must register the HTTP handlers after registering the ImpalaServer with the
@@ -2801,6 +2806,28 @@ Status ImpalaServer::Start(
       hs2_server_->SetConnectionHandler(this);
     }
 
+    if (external_fe_port > 0 || (TestInfo::is_test() && external_fe_port == 0)) {
+      boost::shared_ptr<TProcessor> external_fe_processor(
+          new ImpalaHiveServer2ServiceProcessor(handler));
+      boost::shared_ptr<TProcessorEventHandler> event_handler(
+          new RpcEventHandler("external_frontend", exec_env_->metrics()));
+      external_fe_processor->setEventHandler(event_handler);
+
+      ThriftServerBuilder builder(EXTERNAL_FRONTEND_SERVER_NAME, external_fe_processor,
+          external_fe_port);
+      ThriftServer* server;
+      RETURN_IF_ERROR(
+          builder.auth_provider(
+              AuthManager::GetInstance()->GetExternalFrontendAuthProvider())
+          .metrics(exec_env_->metrics())
+          .max_concurrent_connections(FLAGS_fe_service_threads)
+          .queue_timeout_ms(FLAGS_accepted_client_cnxn_timeout)
+          .idle_poll_period_ms(FLAGS_idle_client_poll_period_s * MILLIS_PER_SEC)
+          .Build(&server));
+      external_fe_server_.reset(server);
+      external_fe_server_->SetConnectionHandler(this);
+    }
+
     if (hs2_http_port > 0 || (TestInfo::is_test() && hs2_http_port == 0)) {
       boost::shared_ptr<TProcessor> hs2_http_processor(
           new ImpalaHiveServer2ServiceProcessor(handler));
@@ -2846,6 +2873,11 @@ Status ImpalaServer::Start(
     LOG(INFO) << "Impala HiveServer2 Service (HTTP) listening on "
               << hs2_http_server_->port();
   }
+  if (external_fe_server_.get()) {
+    RETURN_IF_ERROR(external_fe_server_->Start());
+    LOG(INFO) << "Impala External Frontend Service listening on "
+              << external_fe_server_->port();
+  }
   if (beeswax_server_.get()) {
     RETURN_IF_ERROR(beeswax_server_->Start());
     LOG(INFO) << "Impala Beeswax Service listening on " << beeswax_server_->port();
diff --git a/be/src/service/impala-server.h b/be/src/service/impala-server.h
index 4971dfe..f280374 100644
--- a/be/src/service/impala-server.h
+++ b/be/src/service/impala-server.h
@@ -209,7 +209,8 @@ class ImpalaServer : public ImpalaServiceIf,
   /// ephemeral port in tests and to not start the service in a daemon. A port < 0
   /// always means to not start the service. The port values can be obtained after
   /// Start() by calling GetBeeswaxPort() or GetHS2Port().
-  Status Start(int32_t beeswax_port, int32_t hs2_port, int32_t hs2_http_port);
+  Status Start(int32_t beeswax_port, int32_t hs2_port, int32_t hs2_http_port,
+      int32_t external_fe_port);
 
   /// Blocks until the server shuts down.
   void Join();
@@ -632,6 +633,9 @@ class ImpalaServer : public ImpalaServiceIf,
   friend class ImpalaServerTest;
   friend class QueryDriver;
 
+  // Used to identify external frontend RPC calls
+  const string EXTERNAL_FRONTEND_SERVER_NAME = "external-frontend";
+
   boost::scoped_ptr<ImpalaHttpHandler> http_handler_;
 
   /// Relevant ODBC SQL State code; for more info,
@@ -1570,6 +1574,7 @@ class ImpalaServer : public ImpalaServiceIf,
   boost::scoped_ptr<ThriftServer> beeswax_server_;
   boost::scoped_ptr<ThriftServer> hs2_server_;
   boost::scoped_ptr<ThriftServer> hs2_http_server_;
+  boost::scoped_ptr<ThriftServer> external_fe_server_;
 
   /// Flag that records if backend and/or client services have been started. The flag is
   /// set after all services required for the server have been started.
diff --git a/be/src/service/impalad-main.cc b/be/src/service/impalad-main.cc
index 9704067..76a8157 100644
--- a/be/src/service/impalad-main.cc
+++ b/be/src/service/impalad-main.cc
@@ -53,6 +53,7 @@ using namespace impala;
 DECLARE_int32(beeswax_port);
 DECLARE_int32(hs2_port);
 DECLARE_int32(hs2_http_port);
+DECLARE_int32(external_fe_port);
 DECLARE_bool(is_coordinator);
 
 int ImpaladMain(int argc, char** argv) {
@@ -83,8 +84,8 @@ int ImpaladMain(int argc, char** argv) {
   InitRpcEventTracing(exec_env.webserver(), exec_env.rpc_mgr());
 
   boost::shared_ptr<ImpalaServer> impala_server(new ImpalaServer(&exec_env));
-  Status status =
-      impala_server->Start(FLAGS_beeswax_port, FLAGS_hs2_port, FLAGS_hs2_http_port);
+  Status status = impala_server->Start(FLAGS_beeswax_port, FLAGS_hs2_port,
+      FLAGS_hs2_http_port, FLAGS_external_fe_port);
   if (!status.ok()) {
     LOG(ERROR) << "Impalad services did not start correctly, exiting.  Error: "
         << status.GetDetail();
diff --git a/be/src/testutil/in-process-servers.cc b/be/src/testutil/in-process-servers.cc
index 337932d..7e72ff4 100644
--- a/be/src/testutil/in-process-servers.cc
+++ b/be/src/testutil/in-process-servers.cc
@@ -80,7 +80,7 @@ Status InProcessImpalaServer::StartWithClientServers(
 
   impala_server_.reset(new ImpalaServer(exec_env_.get()));
   SetCatalogIsReady();
-  RETURN_IF_ERROR(impala_server_->Start(beeswax_port, hs2_port, hs2_http_port));
+  RETURN_IF_ERROR(impala_server_->Start(beeswax_port, hs2_port, hs2_http_port_, 0));
 
   // Wait for up to 1s for the backend server to start
   RETURN_IF_ERROR(WaitForServer(FLAGS_hostname, krpc_port_, 10, 100));
diff --git a/bin/start-impala-cluster.py b/bin/start-impala-cluster.py
index 7767730..99d901f 100755
--- a/bin/start-impala-cluster.py
+++ b/bin/start-impala-cluster.py
@@ -40,7 +40,8 @@ from tests.common.impala_cluster import (ImpalaCluster, DEFAULT_BEESWAX_PORT,
     DEFAULT_STATE_STORE_SUBSCRIBER_PORT, DEFAULT_IMPALAD_WEBSERVER_PORT,
     DEFAULT_STATESTORED_WEBSERVER_PORT, DEFAULT_CATALOGD_WEBSERVER_PORT,
     DEFAULT_ADMISSIOND_WEBSERVER_PORT, DEFAULT_CATALOGD_JVM_DEBUG_PORT,
-    DEFAULT_IMPALAD_JVM_DEBUG_PORT, find_user_processes, run_daemon)
+    DEFAULT_EXTERNAL_FE_PORT, DEFAULT_IMPALAD_JVM_DEBUG_PORT,
+    find_user_processes, run_daemon)
 
 LOG = logging.getLogger(os.path.splitext(os.path.basename(__file__))[0])
 LOG.setLevel(level=logging.DEBUG)
@@ -137,6 +138,9 @@ parser.add_option("--enable_admission_service", dest="enable_admission_service",
                   help="If true, enables the Admissison Control Service - the cluster "
                   "will be launched with an admissiond and all coordinators configured "
                   "to use it for admission control.")
+parser.add_option("--enable_external_fe_support", dest="enable_external_fe_support",
+                  action="store_true", default=False,
+                  help="If true, impalads will start with the external_fe_port defined.")
 
 # For testing: list of comma-separated delays, in milliseconds, that delay impalad catalog
 # replica initialization. The ith delay is applied to the ith impalad.
@@ -231,6 +235,7 @@ def choose_impalad_ports(instance_num):
           'hs2_port': DEFAULT_HS2_PORT + instance_num,
           'hs2_http_port': DEFAULT_HS2_HTTP_PORT + instance_num,
           'krpc_port': DEFAULT_KRPC_PORT + instance_num,
+          'external_fe_port': DEFAULT_EXTERNAL_FE_PORT + instance_num,
           'state_store_subscriber_port':
               DEFAULT_STATE_STORE_SUBSCRIBER_PORT + instance_num,
           'webserver_port': DEFAULT_IMPALAD_WEBSERVER_PORT + instance_num}
@@ -244,6 +249,8 @@ def build_impalad_port_args(instance_num):
       "-krpc_port={krpc_port} "
       "-state_store_subscriber_port={state_store_subscriber_port} "
       "-webserver_port={webserver_port}")
+  if options.enable_external_fe_support:
+    IMPALAD_PORTS += " -external_fe_port={external_fe_port}"
   return IMPALAD_PORTS.format(**choose_impalad_ports(instance_num))
 
 
@@ -613,7 +620,8 @@ class DockerMiniClusterOperations(object):
       port_map = {DEFAULT_BEESWAX_PORT: chosen_ports['beeswax_port'],
                   DEFAULT_HS2_PORT: chosen_ports['hs2_port'],
                   DEFAULT_HS2_HTTP_PORT: chosen_ports['hs2_http_port'],
-                  DEFAULT_IMPALAD_WEBSERVER_PORT: chosen_ports['webserver_port']}
+                  DEFAULT_IMPALAD_WEBSERVER_PORT: chosen_ports['webserver_port'],
+                  DEFAULT_EXTERNAL_FE_PORT: chosen_ports['external_fe_port']}
       self.__run_container__("impalad_coord_exec", impalad_arg_lists[i], port_map, i,
           mem_limit=mem_limit, supports_data_cache=True)
 
diff --git a/common/thrift/metrics.json b/common/thrift/metrics.json
index 3b6d3f1..3ac2d8b 100644
--- a/common/thrift/metrics.json
+++ b/common/thrift/metrics.json
@@ -1182,6 +1182,66 @@
     "key": "impala.thrift-server.hiveserver2-frontend.timedout-cnxn-requests"
   },
   {
+    "description": "The number of active External Frontend API connections to this Impala Daemon.",
+    "contexts": [
+      "IMPALAD"
+    ],
+    "label": "External Frontend API Active Connections",
+    "units": "NONE",
+    "kind": "GAUGE",
+    "key": "impala.thrift-server.external-frontend.connections-in-use"
+  },
+  {
+    "description": "Amount of time clients of External Frontend API spent waiting for connection to be set up",
+    "contexts": [
+      "IMPALAD"
+    ],
+    "label": "External Frontend API client connection setup time",
+    "units": "TIME_US",
+    "kind": "HISTOGRAM",
+    "key": "impala.thrift-server.external-frontend.connection-setup-time"
+  },
+  {
+    "description": "Amount of time clients of External Frontend API spent waiting for service threads",
+    "contexts": [
+      "IMPALAD"
+    ],
+    "label": "External Frontend API clients' wait time for service threads",
+    "units": "TIME_US",
+    "kind": "HISTOGRAM",
+    "key": "impala.thrift-server.external-frontend.svc-thread-wait-time"
+  },
+  {
+    "description": "The total number of External Frontend API connections made to this Impala Daemon over its lifetime.",
+    "contexts": [
+      "IMPALAD"
+    ],
+    "label": "External Frontend API Total Connections",
+    "units": "UNIT",
+    "kind": "COUNTER",
+    "key": "impala.thrift-server.external-frontend.total-connections"
+  },
+  {
+    "description": "The number of External Frontend API connections to this Impala Daemon that have been accepted and are waiting to be setup.",
+    "contexts": [
+      "IMPALAD"
+    ],
+    "label": "External Frontend API Connections Queued for Setup",
+    "units": "NONE",
+    "kind": "GAUGE",
+    "key": "impala.thrift-server.external-frontend.connection-setup-queue-size"
+  },
+  {
+    "description": "The number of External Frontend API connection requests to this Impala Daemon that have been timed out waiting to be setup.",
+    "contexts": [
+      "IMPALAD"
+    ],
+    "label": "External Frontend API Connection Requests Timed Out",
+    "units": "NONE",
+    "kind": "GAUGE",
+    "key": "impala.thrift-server.external-frontend.timedout-cnxn-requests"
+  },
+  {
     "description": "The number of active HiveServer2 HTTP API connections to this Impala Daemon.",
     "contexts": [
       "IMPALAD"
diff --git a/docker/impalad_coordinator/Dockerfile b/docker/impalad_coordinator/Dockerfile
index 3a7b445..bf6de31 100644
--- a/docker/impalad_coordinator/Dockerfile
+++ b/docker/impalad_coordinator/Dockerfile
@@ -29,6 +29,8 @@ EXPOSE 21050
 EXPOSE 28000
 # Debug webserver
 EXPOSE 25000
+# External Frontend
+EXPOSE 21150
 
 ENTRYPOINT ["/opt/impala/bin/daemon_entrypoint.sh", "/opt/impala/bin/impalad",\
      "-log_dir=/opt/impala/logs",\
diff --git a/tests/common/impala_cluster.py b/tests/common/impala_cluster.py
index 305b461..89167b7 100644
--- a/tests/common/impala_cluster.py
+++ b/tests/common/impala_cluster.py
@@ -51,6 +51,7 @@ START_DAEMON_PATH = os.path.join(IMPALA_HOME, 'bin/start-daemon.sh')
 
 DEFAULT_BEESWAX_PORT = 21000
 DEFAULT_HS2_PORT = 21050
+DEFAULT_EXTERNAL_FE_PORT = 21150
 DEFAULT_HS2_HTTP_PORT = 28000
 DEFAULT_KRPC_PORT = 27000
 DEFAULT_CATALOG_SERVICE_PORT = 26000