You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kylin.apache.org by sh...@apache.org on 2016/02/26 09:28:29 UTC

kylin git commit: KYLIN-579 Unload Hive table from kylin

Repository: kylin
Updated Branches:
  refs/heads/2.x-staging 6ee409ccc -> 2e1d2f6b6


KYLIN-579 Unload Hive table from kylin

Signed-off-by: wangxianbin1987 <wa...@gmail.com>


Project: http://git-wip-us.apache.org/repos/asf/kylin/repo
Commit: http://git-wip-us.apache.org/repos/asf/kylin/commit/2e1d2f6b
Tree: http://git-wip-us.apache.org/repos/asf/kylin/tree/2e1d2f6b
Diff: http://git-wip-us.apache.org/repos/asf/kylin/diff/2e1d2f6b

Branch: refs/heads/2.x-staging
Commit: 2e1d2f6b62903b16b17bd2442e3456107dc0aa6a
Parents: 6ee409c
Author: wangxianbin1987 <wa...@gmail.com>
Authored: Thu Feb 25 19:22:34 2016 +0800
Committer: shaofengshi <sh...@apache.org>
Committed: Fri Feb 26 16:18:50 2016 +0800

----------------------------------------------------------------------
 .../java/org/apache/kylin/cube/CubeManager.java |  2 +
 .../apache/kylin/metadata/MetadataManager.java  | 30 +++++++++
 .../kylin/metadata/project/ProjectManager.java  | 12 ++++
 .../kylin/rest/controller/TableController.java  | 59 ++++++++++++++++-
 .../apache/kylin/rest/service/CubeService.java  | 14 +++++
 .../apache/kylin/rest/service/ModelService.java | 15 +++++
 .../kylin/rest/service/ProjectService.java      | 20 ++++++
 .../source/hive/HiveSourceTableLoader.java      |  6 ++
 webapp/app/js/controllers/sourceMeta.js         | 66 ++++++++++++++++++++
 webapp/app/js/services/tables.js                |  1 +
 .../app/partials/tables/source_table_tree.html  |  6 +-
 webapp/app/partials/tables/table_unload.html    | 33 ++++++++++
 12 files changed, 259 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/kylin/blob/2e1d2f6b/core-cube/src/main/java/org/apache/kylin/cube/CubeManager.java
----------------------------------------------------------------------
diff --git a/core-cube/src/main/java/org/apache/kylin/cube/CubeManager.java b/core-cube/src/main/java/org/apache/kylin/cube/CubeManager.java
index 84dd30a..4951ce6 100644
--- a/core-cube/src/main/java/org/apache/kylin/cube/CubeManager.java
+++ b/core-cube/src/main/java/org/apache/kylin/cube/CubeManager.java
@@ -46,7 +46,9 @@ import org.apache.kylin.metadata.MetadataManager;
 import org.apache.kylin.metadata.model.SegmentStatusEnum;
 import org.apache.kylin.metadata.model.TableDesc;
 import org.apache.kylin.metadata.model.TblColRef;
+import org.apache.kylin.metadata.project.ProjectInstance;
 import org.apache.kylin.metadata.project.ProjectManager;
+import org.apache.kylin.metadata.project.RealizationEntry;
 import org.apache.kylin.metadata.realization.IRealization;
 import org.apache.kylin.metadata.realization.IRealizationConstants;
 import org.apache.kylin.metadata.realization.IRealizationProvider;

http://git-wip-us.apache.org/repos/asf/kylin/blob/2e1d2f6b/core-metadata/src/main/java/org/apache/kylin/metadata/MetadataManager.java
----------------------------------------------------------------------
diff --git a/core-metadata/src/main/java/org/apache/kylin/metadata/MetadataManager.java b/core-metadata/src/main/java/org/apache/kylin/metadata/MetadataManager.java
index 80ee8b3..9f2a934 100644
--- a/core-metadata/src/main/java/org/apache/kylin/metadata/MetadataManager.java
+++ b/core-metadata/src/main/java/org/apache/kylin/metadata/MetadataManager.java
@@ -197,6 +197,12 @@ public class MetadataManager {
         srcTableMap.put(srcTable.getIdentity(), srcTable);
     }
 
+    public void removeSourceTable(String tableIdentity) throws IOException {
+        String path = TableDesc.concatResourcePath(tableIdentity);
+        getStore().deleteResource(path);
+        srcTableMap.remove(tableIdentity);
+    }
+
     private void init(KylinConfig config) throws IOException {
         this.config = config;
         this.srcTableMap = new CaseInsensitiveStringCache<TableDesc>(config, Broadcaster.TYPE.TABLE);
@@ -336,6 +342,24 @@ public class MetadataManager {
         return new ArrayList<>(ret);
     }
 
+    public boolean isTableInModel(String tableName, String projectName) throws IOException {
+        for(DataModelDesc modelDesc : getModels(projectName)) {
+            if(modelDesc.getAllTables().contains(tableName.toUpperCase())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public boolean isTableInAnyModel(String tableName) {
+        for(DataModelDesc modelDesc : getModels()) {
+            if(modelDesc.getAllTables().contains(tableName.toUpperCase())){
+                return true;
+            }
+        }
+        return false;
+    }
+
     private void reloadAllDataModel() throws IOException {
         ResourceStore store = getStore();
         logger.debug("Reloading DataModel from folder " + store.getReadableResourcePath(ResourceStore.DATA_MODEL_DESC_RESOURCE_ROOT));
@@ -441,6 +465,12 @@ public class MetadataManager {
         srcTableExdMap.put(tableId, tableExdProperties);
     }
 
+    public void removeTableExd(String tableIdentity) throws IOException {
+        String path = TableDesc.concatExdResourcePath(tableIdentity);
+        getStore().deleteResource(path);
+        srcTableExdMap.remove(tableIdentity);
+    }
+
     public String appendDBName(String table) {
 
         if (table.indexOf(".") > 0)

http://git-wip-us.apache.org/repos/asf/kylin/blob/2e1d2f6b/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectManager.java
----------------------------------------------------------------------
diff --git a/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectManager.java b/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectManager.java
index 45bbb1b..f73239c 100644
--- a/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectManager.java
+++ b/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectManager.java
@@ -286,6 +286,18 @@ public class ProjectManager {
         return projectInstance;
     }
 
+    public void removeTableDescFromProject(String tableIdentities, String projectName) throws IOException {
+        MetadataManager metaMgr = getMetadataManager();
+        ProjectInstance projectInstance = getProject(projectName);
+        TableDesc table = metaMgr.getTableDesc(tableIdentities);
+        if (table == null) {
+            throw new IllegalStateException("Cannot find table '" + table + "' in metadata manager");
+        }
+
+        projectInstance.removeTable(table.getIdentity());
+        updateProject(projectInstance);
+    }
+
     public List<ProjectInstance> findProjects(RealizationType type, String realizationName) {
         List<ProjectInstance> result = Lists.newArrayList();
         for (ProjectInstance prj : projectMap.values()) {

http://git-wip-us.apache.org/repos/asf/kylin/blob/2e1d2f6b/server/src/main/java/org/apache/kylin/rest/controller/TableController.java
----------------------------------------------------------------------
diff --git a/server/src/main/java/org/apache/kylin/rest/controller/TableController.java b/server/src/main/java/org/apache/kylin/rest/controller/TableController.java
index 39af7db..98e8d58 100644
--- a/server/src/main/java/org/apache/kylin/rest/controller/TableController.java
+++ b/server/src/main/java/org/apache/kylin/rest/controller/TableController.java
@@ -21,6 +21,7 @@ package org.apache.kylin.rest.controller;
 import java.io.IOException;
 import java.util.*;
 
+import com.google.common.collect.Sets;
 import org.apache.commons.lang.StringUtils;
 import org.apache.kylin.common.KylinConfig;
 import org.apache.kylin.common.util.JsonUtil;
@@ -33,6 +34,8 @@ import org.apache.kylin.rest.request.CardinalityRequest;
 import org.apache.kylin.rest.request.StreamingRequest;
 import org.apache.kylin.rest.response.TableDescResponse;
 import org.apache.kylin.rest.service.CubeService;
+import org.apache.kylin.rest.service.ModelService;
+import org.apache.kylin.rest.service.ProjectService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -55,6 +58,10 @@ public class TableController extends BasicController {
 
     @Autowired
     private CubeService cubeMgmtService;
+    @Autowired
+    private ProjectService projectService;
+    @Autowired
+    private ModelService modelService;
 
     /**
      * Get available table list of the input database
@@ -124,21 +131,67 @@ public class TableController extends BasicController {
         cubeMgmtService.syncTableToProject(loaded, project);
         Map<String, String[]> result = new HashMap<String, String[]>();
         result.put("result.loaded", loaded);
-        result.put("result.unloaded", new String[] {});
+        result.put("result.unloaded", new String[]{});
+        return result;
+    }
+
+    @RequestMapping(value = "/{tables}/{project}", method = { RequestMethod.DELETE })
+    @ResponseBody
+    public Map<String, String[]> unLoadHiveTables(@PathVariable String tables, @PathVariable String project) {
+        Set<String> unLoadSuccess = Sets.newHashSet();
+        Set<String> unLoadFail = Sets.newHashSet();
+        Map<String, String[]> result = new HashMap<String, String[]>();
+        for (String tableName : tables.split(",")) {
+            if (unLoadHiveTable(tableName, project)) {
+                unLoadSuccess.add(tableName);
+            } else {
+                unLoadFail.add(tableName);
+            }
+        }
+        result.put("result.unload.success", (String[]) unLoadSuccess.toArray(new String[unLoadSuccess.size()]));
+        result.put("result.unload.fail", (String[]) unLoadFail.toArray(new String[unLoadFail.size()]));
         return result;
     }
 
+    /**
+     * table may referenced by several projects, and kylin only keep one copy of meta for each table,
+     * that's why we have two if statement here.
+     * @param tableName
+     * @param project
+     * @return
+     */
+    private boolean unLoadHiveTable(String tableName, String project) {
+        boolean rtn= false;
+		try {
+			if (!modelService.isTableInModel(tableName, project)) {
+				cubeMgmtService.removeTableFromProject(tableName, project);
+				rtn = true;
+			}
+		} catch (IOException e) {
+			logger.error(e.getMessage(), e);
+		}
+        if(!projectService.isTableInAnyProject(tableName) && !modelService.isTableInAnyModel(tableName)) {
+            try {
+                cubeMgmtService.unLoadHiveTable(tableName);
+                rtn = true;
+            } catch (IOException e) {
+                logger.error(e.getMessage(), e);
+                rtn = false;
+            }
+        }
+        return rtn;
+    }
 
     @RequestMapping(value = "/addStreamingSrc", method = { RequestMethod.POST })
     @ResponseBody
     public Map<String, String> addStreamingTable(@RequestBody StreamingRequest request) throws IOException {
         Map<String, String> result = new HashMap<String, String>();
         String project = request.getProject();
-        TableDesc desc = JsonUtil.readValue(request.getTableData(),TableDesc.class);
+        TableDesc desc = JsonUtil.readValue(request.getTableData(), TableDesc.class);
         desc.setUuid(UUID.randomUUID().toString());
         MetadataManager metaMgr = MetadataManager.getInstance(KylinConfig.getInstanceFromEnv());
         metaMgr.saveSourceTable(desc);
-        cubeMgmtService.syncTableToProject(new String[]{desc.getName()},project);
+        cubeMgmtService.syncTableToProject(new String[]{desc.getName()}, project);
         result.put("success","true");
         return result;
     }

http://git-wip-us.apache.org/repos/asf/kylin/blob/2e1d2f6b/server/src/main/java/org/apache/kylin/rest/service/CubeService.java
----------------------------------------------------------------------
diff --git a/server/src/main/java/org/apache/kylin/rest/service/CubeService.java b/server/src/main/java/org/apache/kylin/rest/service/CubeService.java
index 5d2776f..0c57d00 100644
--- a/server/src/main/java/org/apache/kylin/rest/service/CubeService.java
+++ b/server/src/main/java/org/apache/kylin/rest/service/CubeService.java
@@ -557,10 +557,24 @@ public class CubeService extends BasicService {
     }
 
     @PreAuthorize(Constant.ACCESS_HAS_ROLE_ADMIN)
+    public void unLoadHiveTable(String tableName) throws IOException {
+        String[] dbTableName = HadoopUtil.parseHiveTableName(tableName);
+        tableName = dbTableName[0] + "." + dbTableName[1];
+        HiveSourceTableLoader.unLoadHiveTable(tableName.toUpperCase());
+    }
+
+    @PreAuthorize(Constant.ACCESS_HAS_ROLE_ADMIN)
     public void syncTableToProject(String[] tables, String project) throws IOException {
         getProjectManager().addTableDescToProject(tables, project);
     }
 
+    @PreAuthorize(Constant.ACCESS_HAS_ROLE_ADMIN)
+    public void removeTableFromProject(String tableName, String projectName) throws IOException {
+        String[] dbTableName = HadoopUtil.parseHiveTableName(tableName);
+        tableName = dbTableName[0] + "." + dbTableName[1];
+        getProjectManager().removeTableDescFromProject(tableName, projectName);
+    }
+
     @PreAuthorize(Constant.ACCESS_HAS_ROLE_MODELER + " or " + Constant.ACCESS_HAS_ROLE_ADMIN)
     public void calculateCardinalityIfNotPresent(String[] tables, String submitter) throws IOException {
         MetadataManager metaMgr = getMetadataManager();

http://git-wip-us.apache.org/repos/asf/kylin/blob/2e1d2f6b/server/src/main/java/org/apache/kylin/rest/service/ModelService.java
----------------------------------------------------------------------
diff --git a/server/src/main/java/org/apache/kylin/rest/service/ModelService.java b/server/src/main/java/org/apache/kylin/rest/service/ModelService.java
index 9dae312..9d8ccfb 100644
--- a/server/src/main/java/org/apache/kylin/rest/service/ModelService.java
+++ b/server/src/main/java/org/apache/kylin/rest/service/ModelService.java
@@ -23,6 +23,7 @@ import java.util.ArrayList;
 import java.util.List;
 
 import org.apache.kylin.cube.model.CubeDesc;
+import org.apache.kylin.engine.mr.HadoopUtil;
 import org.apache.kylin.invertedindex.model.IIDesc;
 import org.apache.kylin.metadata.model.DataModelDesc;
 import org.apache.kylin.metadata.project.ProjectInstance;
@@ -128,4 +129,18 @@ public class ModelService extends BasicService {
 
         accessService.clean(desc, true);
     }
+
+    @PreAuthorize(Constant.ACCESS_HAS_ROLE_ADMIN + " or hasPermission(#desc, 'ADMINISTRATION') or hasPermission(#desc, 'MANAGEMENT')")
+    public boolean isTableInAnyModel(String tableName) {
+        String[] dbTableName = HadoopUtil.parseHiveTableName(tableName);
+        tableName = dbTableName[0] + "." + dbTableName[1];
+        return getMetadataManager().isTableInAnyModel(tableName);
+    }
+
+    @PreAuthorize(Constant.ACCESS_HAS_ROLE_ADMIN + " or hasPermission(#desc, 'ADMINISTRATION') or hasPermission(#desc, 'MANAGEMENT')")
+    public boolean isTableInModel(String tableName, String projectName) throws IOException {
+        String[] dbTableName = HadoopUtil.parseHiveTableName(tableName);
+        tableName = dbTableName[0] + "." + dbTableName[1];
+        return getMetadataManager().isTableInModel(tableName, projectName);
+    }
 }

http://git-wip-us.apache.org/repos/asf/kylin/blob/2e1d2f6b/server/src/main/java/org/apache/kylin/rest/service/ProjectService.java
----------------------------------------------------------------------
diff --git a/server/src/main/java/org/apache/kylin/rest/service/ProjectService.java b/server/src/main/java/org/apache/kylin/rest/service/ProjectService.java
index be70534..ad5a982 100644
--- a/server/src/main/java/org/apache/kylin/rest/service/ProjectService.java
+++ b/server/src/main/java/org/apache/kylin/rest/service/ProjectService.java
@@ -23,6 +23,7 @@ import java.util.Collections;
 import java.util.List;
 
 import org.apache.kylin.metadata.project.ProjectInstance;
+import org.apache.kylin.metadata.project.ProjectManager;
 import org.apache.kylin.rest.constant.Constant;
 import org.apache.kylin.rest.exception.InternalErrorException;
 import org.apache.kylin.rest.request.CreateProjectRequest;
@@ -104,4 +105,23 @@ public class ProjectService extends BasicService {
         accessService.clean(project, true);
     }
 
+    public boolean isTableInAnyProject(String tableName) {
+        for(ProjectInstance projectInstance : ProjectManager.getInstance(getConfig()).listAllProjects()) {
+            if(projectInstance.containsTable(tableName.toUpperCase())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public boolean isTableInProject(String tableName, String projectName) {
+        ProjectInstance projectInstance = ProjectManager.getInstance(getConfig()).getProject(projectName);
+        if(projectInstance != null) {
+            if(projectInstance.containsTable(tableName.toUpperCase())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/kylin/blob/2e1d2f6b/source-hive/src/main/java/org/apache/kylin/source/hive/HiveSourceTableLoader.java
----------------------------------------------------------------------
diff --git a/source-hive/src/main/java/org/apache/kylin/source/hive/HiveSourceTableLoader.java b/source-hive/src/main/java/org/apache/kylin/source/hive/HiveSourceTableLoader.java
index bc722b3..f2f2d2a 100644
--- a/source-hive/src/main/java/org/apache/kylin/source/hive/HiveSourceTableLoader.java
+++ b/source-hive/src/main/java/org/apache/kylin/source/hive/HiveSourceTableLoader.java
@@ -78,6 +78,12 @@ public class HiveSourceTableLoader {
         return loadedTables;
     }
 
+    public static void unLoadHiveTable(String hiveTable) throws IOException {
+        MetadataManager metaMgr = MetadataManager.getInstance(KylinConfig.getInstanceFromEnv());
+        metaMgr.removeSourceTable(hiveTable);
+        metaMgr.removeTableExd(hiveTable);
+    }
+
     private static List<String> extractHiveTables(String database, Set<String> tables, KylinConfig config) throws IOException {
 
         List<String> loadedTables = Lists.newArrayList();

http://git-wip-us.apache.org/repos/asf/kylin/blob/2e1d2f6b/webapp/app/js/controllers/sourceMeta.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/controllers/sourceMeta.js b/webapp/app/js/controllers/sourceMeta.js
index abdeeb8..cbd9f52 100755
--- a/webapp/app/js/controllers/sourceMeta.js
+++ b/webapp/app/js/controllers/sourceMeta.js
@@ -100,6 +100,25 @@ KylinApp
       });
     };
 
+    $scope.openUnLoadModal = function () {
+      $modal.open({
+        templateUrl: 'removeHiveTable.html',
+        controller: ModalInstanceCtrl,
+        backdrop : 'static',
+        resolve: {
+          tableNames: function () {
+            return $scope.tableNames;
+          },
+          projectName: function () {
+            return $scope.projectModel.selectedProject;
+          },
+          scope: function () {
+            return $scope;
+          }
+        }
+      });
+    };
+
     var ModalInstanceCtrl = function ($scope, $location, $modalInstance, tableNames, MessageService, projectName, scope) {
       $scope.tableNames = "";
       $scope.projectName = projectName;
@@ -152,6 +171,53 @@ KylinApp
           loadingRequest.hide();
         })
       }
+
+      $scope.remove = function () {
+        if ($scope.tableNames.trim() === "") {
+          SweetAlert.swal('', 'Please input table(s) you want to synchronize.', 'info');
+          return;
+        }
+
+        if (!$scope.projectName) {
+          SweetAlert.swal('', 'Please choose your project first!.', 'info');
+          return;
+        }
+
+        $scope.cancel();
+        loadingRequest.show();
+        TableService.unLoadHiveTable({tableName: $scope.tableNames, action: projectName}, {}, function (result) {
+          var removedTableInfo = "";
+          angular.forEach(result['result.unload.success'], function (table) {
+            removedTableInfo += "\n" + table;
+          })
+          var unRemovedTableInfo = "";
+          angular.forEach(result['result.unload.fail'], function (table) {
+            unRemovedTableInfo += "\n" + table;
+          })
+
+          if (result['result.unload.fail'].length != 0 && result['result.unload.success'].length == 0) {
+            SweetAlert.swal('Failed!', 'Failed to synchronize following table(s): ' + unRemovedTableInfo, 'error');
+          }
+          if (result['result.unload.success'].length != 0 && result['result.unload.fail'].length == 0) {
+            SweetAlert.swal('Success!', 'The following table(s) have been successfully synchronized: ' + removedTableInfo, 'success');
+          }
+          if (result['result.unload.success'].length != 0 && result['result.unload.fail'].length != 0) {
+            SweetAlert.swal('Partial unloaded!', 'The following table(s) have been successfully synchronized: ' + removedTableInfo + "\n\n Failed to synchronize following table(s):" + unRemovedTableInfo, 'warning');
+          }
+          loadingRequest.hide();
+          scope.aceSrcTbLoaded(true);
+
+        }, function (e) {
+          if (e.data && e.data.exception) {
+            var message = e.data.exception;
+            var msg = !!(message) ? message : 'Failed to take action.';
+            SweetAlert.swal('Oops...', msg, 'error');
+          } else {
+            SweetAlert.swal('Oops...', "Failed to take action.", 'error');
+          }
+          loadingRequest.hide();
+        })
+      }
     };
 
 

http://git-wip-us.apache.org/repos/asf/kylin/blob/2e1d2f6b/webapp/app/js/services/tables.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/services/tables.js b/webapp/app/js/services/tables.js
index 3b5e9f4..ca7fc42 100755
--- a/webapp/app/js/services/tables.js
+++ b/webapp/app/js/services/tables.js
@@ -23,6 +23,7 @@ KylinApp.factory('TableService', ['$resource', function ($resource, config) {
     getExd: {method: 'GET', params: {action: 'exd-map'}, isArray: false},
     reload: {method: 'PUT', params: {action: 'reload'}, isArray: false},
     loadHiveTable: {method: 'POST', params: {}, isArray: false},
+    unLoadHiveTable: {method: 'DELETE', params: {}, isArray: false},
     addStreamingSrc: {method: 'POST', params: {action:'addStreamingSrc'}, isArray: false},
     genCardinality: {method: 'PUT', params: {action: 'cardinality'}, isArray: false}
   });

http://git-wip-us.apache.org/repos/asf/kylin/blob/2e1d2f6b/webapp/app/partials/tables/source_table_tree.html
----------------------------------------------------------------------
diff --git a/webapp/app/partials/tables/source_table_tree.html b/webapp/app/partials/tables/source_table_tree.html
index 767eb43..4eddc4f 100755
--- a/webapp/app/partials/tables/source_table_tree.html
+++ b/webapp/app/partials/tables/source_table_tree.html
@@ -25,8 +25,9 @@
         <!--button-->
         <div class="col-xs-5" style="padding-left: 0px;margin-top: 20px;">
             <div class="pull-right">
-                <a class="btn btn-xs btn-primary" tooltip="Load Hive Table"  ng-if="userService.hasRole('ROLE_ADMIN')"  ng-click="openModal()"><i class="fa fa-download"></i></a>
-                <a class="btn btn-xs btn-primary" tooltip="Add Streaming Table"  ng-if="userService.hasRole('ROLE_ADMIN')"  ng-click="openStreamingSourceModal()"><i class="fa fa-area-chart"></i></a>
+              <a class="btn btn-xs btn-primary" tooltip="Load Hive Table"  ng-if="userService.hasRole('ROLE_ADMIN')"  ng-click="openModal()"><i class="fa fa-download"></i></a>
+              <a class="btn btn-xs btn-info" tooltip="UnLoad Hive Table"  ng-if="userService.hasRole('ROLE_ADMIN')"  ng-click="openUnLoadModal()"><i class="fa fa-remove"></i></a>
+              <a class="btn btn-xs btn-primary" tooltip="Add Streaming Table"  ng-if="userService.hasRole('ROLE_ADMIN')"  ng-click="openStreamingSourceModal()"><i class="fa fa-area-chart"></i></a>
             </div>
         </div>
 
@@ -47,3 +48,4 @@
 </div>
 
 <div ng-include="'partials/tables/table_load.html'"></div>
+<div ng-include="'partials/tables/table_unload.html'"></div>

http://git-wip-us.apache.org/repos/asf/kylin/blob/2e1d2f6b/webapp/app/partials/tables/table_unload.html
----------------------------------------------------------------------
diff --git a/webapp/app/partials/tables/table_unload.html b/webapp/app/partials/tables/table_unload.html
new file mode 100644
index 0000000..a1fcf6f
--- /dev/null
+++ b/webapp/app/partials/tables/table_unload.html
@@ -0,0 +1,33 @@
+<!--
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+-->
+
+  <script type="text/ng-template" id="removeHiveTable.html">
+    <div class="modal-header">
+      <h4>UnLoad Hive Table Metadata</h4>
+    </div>
+    <div class="modal-body">
+      <span><strong>Project: </strong>{{ $parent.projectName!=null?$parent.projectName:'NULL'}}</span>
+      <label for="tables"> Table Names:(Seperate with comma)</label>
+            <textarea ng-model="$parent.tableNames" class="form-control" id="tables"
+                      placeholder="table1,table2  By default,system will choose 'Default' as database,you can specify database like this 'database.table'"></textarea>
+    </div>
+    <div class="modal-footer">
+      <button class="btn btn-primary" ng-click="remove()">Sync</button>
+      <button class="btn btn-primary" ng-click="cancel()">Cancel</button>
+    </div>
+  </script>