From cc74efb3c500d01f910408b44a0bab9099308603 Mon Sep 17 00:00:00 2001 From: morningman Date: Fri, 24 Aug 2018 17:12:26 +0800 Subject: [PATCH] merge to ddb65b69f9c788e359e191889cb31f15279c41ec (#224) 1. Apache HDFS broker support HDFS HA and Hadoop kerberos authentication. 2. New Backup and Restore function. Use Fs Broker to backup your data to HDFS or restore them from HDFS. 3. Table-Level Privileges. Grant fine-grained privileges on table-level to specified user. 4. A lot of bugs fixed. 5. Performance improvement. --- be/CMakeLists.txt | 4 +- be/src/agent/agent_server.cpp | 48 +- be/src/agent/agent_server.h | 3 +- be/src/agent/cgroups_mgr.cpp | 3 - be/src/agent/heartbeat_server.cpp | 10 +- be/src/agent/task_worker_pool.cpp | 405 ++-- be/src/agent/task_worker_pool.h | 20 +- be/src/agent/topic_subscriber.cpp | 2 - be/src/agent/user_resource_listener.cpp | 6 - be/src/common/config.h | 7 +- be/src/exec/CMakeLists.txt | 2 +- be/src/exec/broker_reader.cpp | 26 +- be/src/exec/broker_reader.h | 6 +- be/src/exec/broker_scanner.cpp | 2 +- be/src/exec/broker_writer.cpp | 26 +- be/src/exec/broker_writer.h | 7 +- be/src/exec/mysql_scan_node.cpp | 5 +- be/src/exec/mysql_scan_node.h | 2 +- be/src/exec/schema_scan_node.cpp | 4 + be/src/exec/schema_scanner.h | 3 +- .../schema_scanner/schema_columns_scanner.cpp | 6 + .../schema_schemata_scanner.cpp | 3 + .../schema_scanner/schema_tables_scanner.cpp | 6 + be/src/exprs/aggregate_functions.cpp | 7 +- be/src/exprs/encryption_functions.h | 37 +- be/src/exprs/hybird_set.h | 3 +- be/src/http/action/mini_load.cpp | 1 + be/src/olap/aggregate_func.h | 10 +- be/src/olap/column_file/segment_reader.cpp | 7 +- be/src/olap/column_file/segment_reader.h | 1 - be/src/olap/command_executor.cpp | 13 +- be/src/olap/command_executor.h | 1 + be/src/olap/field.h | 2 + be/src/olap/file_helper.cpp | 2 + be/src/olap/hll.cpp | 6 +- be/src/olap/hll.h | 2 +- be/src/olap/olap_engine.cpp | 65 +- be/src/olap/olap_engine.h | 15 +- be/src/olap/olap_header.cpp | 15 + be/src/olap/olap_header.h | 1 + be/src/olap/olap_snapshot.cpp | 2 +- be/src/olap/olap_table.cpp | 66 +- be/src/olap/olap_table.h | 11 + be/src/olap/reader.cpp | 3 + be/src/runtime/CMakeLists.txt | 1 + be/src/runtime/exec_env.cpp | 2 + be/src/runtime/exec_env.h | 7 + be/src/runtime/export_sink.cpp | 2 +- be/src/runtime/result_sink.cpp | 4 +- be/src/runtime/snapshot_loader.cpp | 934 ++++++++ be/src/runtime/snapshot_loader.h | 139 ++ be/src/util/file_utils.cpp | 32 + be/src/util/file_utils.h | 3 + be/test/agent/agent_server_test.cpp | 5 - be/test/agent/task_worker_pool_test.cpp | 296 +-- be/test/exec/broker_reader_test.cpp | 4 +- be/test/olap/command_executor_test.cpp | 4 +- be/test/runtime/CMakeLists.txt | 1 + be/test/runtime/snapshot_loader_test.cpp | 119 + bin/start_be.sh | 8 +- bin/start_fe.sh | 2 - bin/stop_be.sh | 1 + bin/stop_fe.sh | 1 - docs/design/Palo_privilege.md | 279 +++ docs/design/real_time_loading.md | 206 ++ docs/help/Contents/Account Management/help.md | 272 ++- .../help/Contents/Data Definition/ddl_stmt.md | 267 ++- .../Data Manipulation/manipulation_stmt.md | 203 +- fe/build.xml | 108 + .../com/baidu/palo/alter/RollupHandler.java | 55 +- .../baidu/palo/alter/SchemaChangeHandler.java | 11 + .../palo/analysis/AbstractBackupStmt.java | 137 +- .../baidu/palo/analysis/AlterClusterStmt.java | 12 +- .../palo/analysis/AlterDatabaseQuotaStmt.java | 25 +- .../palo/analysis/AlterDatabaseRename.java | 30 +- .../analysis/AlterLoadErrorUrlClause.java | 9 +- .../baidu/palo/analysis/AlterSystemStmt.java | 9 +- .../baidu/palo/analysis/AlterTableStmt.java | 13 +- .../baidu/palo/analysis/AlterUserStmt.java | 80 +- fe/src/com/baidu/palo/analysis/Analyzer.java | 32 +- .../com/baidu/palo/analysis/BackupStmt.java | 77 +- .../palo/analysis/CancelAlterTableStmt.java | 20 +- .../baidu/palo/analysis/CancelBackupStmt.java | 16 +- .../baidu/palo/analysis/CancelLoadStmt.java | 8 +- .../palo/analysis/CreateClusterStmt.java | 12 +- .../com/baidu/palo/analysis/CreateDbStmt.java | 9 +- .../palo/analysis/CreateRepositoryStmt.java | 103 + .../baidu/palo/analysis/CreateRoleStmt.java | 46 + .../baidu/palo/analysis/CreateTableStmt.java | 12 +- .../baidu/palo/analysis/CreateUserStmt.java | 114 +- .../baidu/palo/analysis/CreateViewStmt.java | 12 +- .../baidu/palo/analysis/DataDescription.java | 24 +- .../com/baidu/palo/analysis/DeleteStmt.java | 12 +- .../com/baidu/palo/analysis/DescribeStmt.java | 15 +- .../baidu/palo/analysis/DropClusterStmt.java | 5 +- .../com/baidu/palo/analysis/DropDbStmt.java | 11 +- .../palo/analysis/DropRepositoryStmt.java | 63 + .../com/baidu/palo/analysis/DropRoleStmt.java | 46 + .../baidu/palo/analysis/DropTableStmt.java | 12 +- .../com/baidu/palo/analysis/DropUserStmt.java | 40 +- .../com/baidu/palo/analysis/ExportStmt.java | 21 +- fe/src/com/baidu/palo/analysis/Expr.java | 16 +- .../baidu/palo/analysis/FrontendClause.java | 32 +- fe/src/com/baidu/palo/analysis/GrantStmt.java | 91 +- .../palo/analysis/InformationFunction.java | 4 +- .../com/baidu/palo/analysis/InsertStmt.java | 41 +- fe/src/com/baidu/palo/analysis/LabelName.java | 1 + .../com/baidu/palo/analysis/LinkDbStmt.java | 9 +- fe/src/com/baidu/palo/analysis/LoadStmt.java | 15 +- .../baidu/palo/analysis/MigrateDbStmt.java | 8 +- .../analysis/ModifyTablePropertiesClause.java | 19 +- .../baidu/palo/analysis/RecoverDbStmt.java | 17 +- .../palo/analysis/RecoverPartitionStmt.java | 21 +- .../baidu/palo/analysis/RecoverTableStmt.java | 20 +- .../com/baidu/palo/analysis/RestoreStmt.java | 112 +- .../com/baidu/palo/analysis/RevokeStmt.java | 79 +- .../com/baidu/palo/analysis/SelectStmt.java | 23 +- .../com/baidu/palo/analysis/SetPassVar.java | 42 +- .../palo/analysis/SetUserPropertyStmt.java | 10 +- .../palo/analysis/SetUserPropertyVar.java | 22 +- fe/src/com/baidu/palo/analysis/SetVar.java | 10 +- .../baidu/palo/analysis/ShowAlterStmt.java | 26 +- .../baidu/palo/analysis/ShowBackendsStmt.java | 20 +- .../baidu/palo/analysis/ShowBackupStmt.java | 121 +- .../baidu/palo/analysis/ShowBrokerStmt.java | 14 +- .../baidu/palo/analysis/ShowClusterStmt.java | 14 +- .../baidu/palo/analysis/ShowCreateDbStmt.java | 19 +- .../palo/analysis/ShowCreateTableStmt.java | 14 +- .../com/baidu/palo/analysis/ShowDataStmt.java | 23 +- .../baidu/palo/analysis/ShowDeleteStmt.java | 10 +- .../baidu/palo/analysis/ShowExportStmt.java | 8 - .../palo/analysis/ShowFrontendsStmt.java | 53 + .../baidu/palo/analysis/ShowGrantsStmt.java | 101 + .../com/baidu/palo/analysis/ShowLoadStmt.java | 8 - .../palo/analysis/ShowLoadWarningsStmt.java | 9 +- .../palo/analysis/ShowMigrationsStmt.java | 11 +- .../palo/analysis/ShowPartitionsStmt.java | 48 +- .../com/baidu/palo/analysis/ShowProcStmt.java | 9 +- .../palo/analysis/ShowRepositoriesStmt.java | 48 + .../baidu/palo/analysis/ShowRestoreStmt.java | 114 +- .../baidu/palo/analysis/ShowRolesStmt.java | 59 + .../baidu/palo/analysis/ShowSnapshotStmt.java | 156 ++ .../palo/analysis/ShowTableStatusStmt.java | 9 +- .../baidu/palo/analysis/ShowTableStmt.java | 7 +- .../baidu/palo/analysis/ShowTabletStmt.java | 5 +- .../palo/analysis/ShowUserPropertyStmt.java | 36 +- .../com/baidu/palo/analysis/ShowUserStmt.java | 6 +- .../com/baidu/palo/analysis/TablePattern.java | 141 ++ fe/src/com/baidu/palo/analysis/TableRef.java | 65 +- .../palo/analysis/TupleIsNullPredicate.java | 3 + fe/src/com/baidu/palo/analysis/UseStmt.java | 15 +- fe/src/com/baidu/palo/analysis/UserDesc.java | 22 +- .../com/baidu/palo/analysis/UserIdentity.java | 205 ++ ...ackupJob.java => AbstractBackupJob_D.java} | 9 +- fe/src/com/baidu/palo/backup/AbstractJob.java | 244 ++ .../com/baidu/palo/backup/BackupHandler.java | 1115 ++++----- fe/src/com/baidu/palo/backup/BackupJob.java | 1414 +++++------ .../com/baidu/palo/backup/BackupJobInfo.java | 481 ++++ fe/src/com/baidu/palo/backup/BackupJob_D.java | 944 ++++++++ fe/src/com/baidu/palo/backup/BackupMeta.java | 119 + fe/src/com/baidu/palo/backup/BlobStorage.java | 700 ++++++ fe/src/com/baidu/palo/backup/RemoteFile.java | 56 + fe/src/com/baidu/palo/backup/Repository.java | 651 ++++++ .../com/baidu/palo/backup/RepositoryMgr.java | 154 ++ .../baidu/palo/backup/RestoreFileMapping.java | 203 ++ fe/src/com/baidu/palo/backup/RestoreJob.java | 2072 +++++++++++------ .../com/baidu/palo/backup/RestoreJob_D.java | 885 +++++++ .../com/baidu/palo/backup/SnapshotInfo.java | 161 ++ fe/src/com/baidu/palo/backup/Status.java | 68 + .../baidu/palo/catalog/AccessPrivilege.java | 56 +- fe/src/com/baidu/palo/catalog/BrokerMgr.java | 20 +- fe/src/com/baidu/palo/catalog/Catalog.java | 268 +-- .../baidu/palo/catalog/DomainResolver.java | 166 ++ .../palo/catalog/DomainResolverServer.java | 446 ---- .../baidu/palo/catalog/MaterializedIndex.java | 46 +- fe/src/com/baidu/palo/catalog/OlapTable.java | 1538 ++++++------ fe/src/com/baidu/palo/catalog/Partition.java | 26 +- .../palo/catalog/RangePartitionInfo.java | 820 +++---- fe/src/com/baidu/palo/catalog/Replica.java | 393 ++-- .../com/baidu/palo/catalog/ResourceGroup.java | 2 +- fe/src/com/baidu/palo/catalog/Table.java | 42 +- fe/src/com/baidu/palo/catalog/Tablet.java | 56 +- .../palo/catalog/TabletInvertedIndex.java | 43 +- .../baidu/palo/catalog/UserPropertyMgr.java | 655 ------ fe/src/com/baidu/palo/catalog/WhiteList.java | 205 -- .../baidu/palo/cluster/ClusterNamespace.java | 7 +- .../baidu/palo/common/CaseSensibility.java | 41 + fe/src/com/baidu/palo/common/Config.java | 24 +- fe/src/com/baidu/palo/common/ErrorCode.java | 12 +- fe/src/com/baidu/palo/common/FeConstants.java | 2 +- .../com/baidu/palo/common/FeMetaVersion.java | 6 + .../com/baidu/palo/common/FeNameFormat.java | 44 +- .../com/baidu/palo/common/PatternMatcher.java | 89 +- fe/src/com/baidu/palo/common/io/DeepCopy.java | 50 + .../common/io/FastByteArrayInputStream.java | 82 + .../common/io/FastByteArrayOutputStream.java | 106 + ...sResourceProcDir.java => AuthProcDir.java} | 34 +- .../palo/common/proc/BackendsProcDir.java | 13 +- .../palo/common/proc/BackupJobProcNode.java | 62 - .../palo/common/proc/BackupProcNode.java | 84 - .../palo/common/proc/FrontendsProcNode.java | 21 +- .../baidu/palo/common/proc/JobsProcDir.java | 13 +- .../baidu/palo/common/proc/LoadProcDir.java | 23 +- .../baidu/palo/common/proc/ProcService.java | 2 +- .../palo/common/proc/ReplicasProcNode.java | 110 +- .../palo/common/proc/RestoreProcNode.java | 84 - .../common/proc/SchemaChangeProcNode.java | 17 +- .../palo/common/proc/TabletsProcDir.java | 304 +-- .../common/proc/UserPropertyProcNode.java | 27 +- .../com/baidu/palo/common/util/TimeUtils.java | 58 +- .../palo/consistency/ConsistencyChecker.java | 3 + .../com/baidu/palo/deploy/DeployManager.java | 1 - .../deploy/impl/LocalFileDeployManager.java | 2 +- fe/src/com/baidu/palo/ha/BDBHA.java | 4 +- .../baidu/palo/ha/BDBStateChangeListener.java | 15 +- fe/src/com/baidu/palo/ha/MasterInfo.java | 107 +- fe/src/com/baidu/palo/http/BaseAction.java | 116 +- fe/src/com/baidu/palo/http/BaseRequest.java | 12 +- .../com/baidu/palo/http/HttpAuthManager.java | 4 +- fe/src/com/baidu/palo/http/HttpServer.java | 2 + .../com/baidu/palo/http/action/HaAction.java | 2 +- .../baidu/palo/http/action/HelpAction.java | 4 +- .../baidu/palo/http/action/SystemAction.java | 7 +- .../baidu/palo/http/action/WebBaseAction.java | 100 +- .../baidu/palo/http/meta/MetaBaseAction.java | 5 + .../com/baidu/palo/http/meta/MetaService.java | 2 +- .../palo/http/rest/BootstrapFinishAction.java | 8 - .../http/rest/CheckDecommissionAction.java | 7 +- .../palo/http/rest/GetDdlStmtAction.java | 10 +- .../palo/http/rest/GetLoadInfoAction.java | 18 +- .../com/baidu/palo/http/rest/LoadAction.java | 11 +- .../http/rest/MetaReplayerCheckAction.java | 8 +- .../baidu/palo/http/rest/MigrationAction.java | 8 +- .../com/baidu/palo/http/rest/MultiAbort.java | 3 +- .../com/baidu/palo/http/rest/MultiCommit.java | 3 +- .../com/baidu/palo/http/rest/MultiDesc.java | 3 +- .../com/baidu/palo/http/rest/MultiList.java | 3 +- .../com/baidu/palo/http/rest/MultiStart.java | 3 +- .../com/baidu/palo/http/rest/MultiUnload.java | 3 +- .../baidu/palo/http/rest/RestBaseAction.java | 11 +- .../baidu/palo/http/rest/RowCountAction.java | 8 +- .../baidu/palo/http/rest/SetConfigAction.java | 10 +- .../baidu/palo/http/rest/ShowProcAction.java | 9 +- .../http/rest/StorageTypeCheckAction.java | 82 + .../com/baidu/palo/journal/JournalEntity.java | 45 +- .../palo/journal/bdbje/BDBEnvironment.java | 617 +++-- .../palo/journal/bdbje/BDBJEJournal.java | 2 +- .../palo/journal/bdbje/BDBJournalCursor.java | 227 +- .../journal/local/LocalJournalCursor.java | 8 +- fe/src/com/baidu/palo/load/ExportJob.java | 64 +- fe/src/com/baidu/palo/load/ExportMgr.java | 26 + fe/src/com/baidu/palo/load/Load.java | 104 +- fe/src/com/baidu/palo/load/LoadJob.java | 36 +- fe/src/com/baidu/palo/master/MasterImpl.java | 54 +- .../com/baidu/palo/master/ReportHandler.java | 4 +- fe/src/com/baidu/palo/mysql/MysqlChannel.java | 13 +- fe/src/com/baidu/palo/mysql/MysqlProto.java | 78 +- fe/src/com/baidu/palo/mysql/MysqlServer.java | 15 +- .../palo/mysql/privilege/DbPrivEntry.java | 155 ++ .../palo/mysql/privilege/DbPrivTable.java | 80 + .../palo/mysql/privilege/GlobalPrivEntry.java | 145 ++ .../baidu/palo/mysql/privilege/PaloAuth.java | 1276 ++++++++++ .../palo/mysql/privilege/PaloPrivilege.java | 77 + .../baidu/palo/mysql/privilege/PaloRole.java | 152 ++ .../palo/mysql/privilege/PrivBitSet.java | 158 ++ .../baidu/palo/mysql/privilege/PrivEntry.java | 236 ++ .../palo/mysql/privilege/PrivPredicate.java | 105 + .../baidu/palo/mysql/privilege/PrivTable.java | 228 ++ .../palo/mysql/privilege/RoleManager.java | 187 ++ .../palo/mysql/privilege/TablePrivEntry.java | 151 ++ .../palo/mysql/privilege/TablePrivTable.java | 110 + .../palo/mysql/privilege/UserPrivTable.java | 159 ++ .../privilege}/UserProperty.java | 393 ++-- .../palo/mysql/privilege/UserPropertyMgr.java | 302 +++ .../privilege}/UserResource.java | 3 +- .../baidu/palo/mysql/privilege/WhiteList.java | 329 +++ fe/src/com/baidu/palo/persist/EditLog.java | 145 +- .../com/baidu/palo/persist/OperationType.java | 18 + fe/src/com/baidu/palo/persist/PrivInfo.java | 141 ++ .../baidu/palo/planner/BrokerScanNode.java | 5 +- .../com/baidu/palo/planner/OlapScanNode.java | 6 +- .../baidu/palo/planner/SchemaScanNode.java | 27 +- .../baidu/palo/planner/SingleNodePlanner.java | 132 +- fe/src/com/baidu/palo/qe/ConnectContext.java | 41 +- .../com/baidu/palo/qe/ConnectProcessor.java | 12 +- .../com/baidu/palo/qe/ConnectScheduler.java | 19 +- fe/src/com/baidu/palo/qe/Coordinator.java | 19 +- fe/src/com/baidu/palo/qe/DdlExecutor.java | 31 +- .../com/baidu/palo/qe/MasterOpExecutor.java | 25 +- fe/src/com/baidu/palo/qe/QeProcessor.java | 5 +- fe/src/com/baidu/palo/qe/ResultReceiver.java | 16 +- fe/src/com/baidu/palo/qe/SetExecutor.java | 4 +- fe/src/com/baidu/palo/qe/ShowExecutor.java | 239 +- fe/src/com/baidu/palo/qe/StmtExecutor.java | 11 +- .../baidu/palo/rpc/BackendServiceProxy.java | 53 +- .../palo/service/FrontendServiceImpl.java | 124 +- fe/src/com/baidu/palo/system/Frontend.java | 1 - .../com/baidu/palo/task/AgentBatchTask.java | 72 +- fe/src/com/baidu/palo/task/AgentTask.java | 8 +- .../com/baidu/palo/task/AgentTaskQueue.java | 38 +- .../com/baidu/palo/task/CancelDeleteTask.java | 4 +- .../baidu/palo/task/CheckConsistencyTask.java | 3 +- fe/src/com/baidu/palo/task/CloneTask.java | 12 +- .../baidu/palo/task/CreateReplicaTask.java | 48 +- .../com/baidu/palo/task/CreateRollupTask.java | 27 +- fe/src/com/baidu/palo/task/DirMoveTask.java | 65 + fe/src/com/baidu/palo/task/DownloadTask.java | 69 + .../com/baidu/palo/task/DropReplicaTask.java | 4 +- .../baidu/palo/task/ExportExportingTask.java | 2 +- fe/src/com/baidu/palo/task/PushTask.java | 38 +- .../baidu/palo/task/ReleaseSnapshotTask.java | 2 +- fe/src/com/baidu/palo/task/RestoreTask.java | 62 - .../com/baidu/palo/task/SchemaChangeTask.java | 27 +- fe/src/com/baidu/palo/task/SnapshotTask.java | 18 +- .../palo/task/StorageMediaMigrationTask.java | 2 +- fe/src/com/baidu/palo/task/UploadTask.java | 38 +- .../baidu/palo/analysis/AccessTestUtil.java | 108 +- .../palo/analysis/AlterClusterStmtTest.java | 35 +- .../palo/analysis/AlterTableStmtTest.java | 30 +- .../baidu/palo/analysis/BackendStmtTest.java | 14 +- .../palo/analysis/CancelAlterStmtTest.java | 42 +- .../palo/analysis/CreateClusterStmtTest.java | 33 +- .../baidu/palo/analysis/CreateDbStmtTest.java | 25 +- .../palo/analysis/CreateTableStmtTest.java | 61 +- .../palo/analysis/CreateUserStmtTest.java | 43 +- .../palo/analysis/DataDescriptionTest.java | 50 +- .../baidu/palo/analysis/DeleteStmtTest.java | 35 +- .../baidu/palo/analysis/DescribeStmtTest.java | 21 +- .../palo/analysis/DropClusterStmtTest.java | 31 +- .../baidu/palo/analysis/DropDbStmtTest.java | 17 + .../palo/analysis/DropTableStmtTest.java | 27 +- .../baidu/palo/analysis/DropUserStmtTest.java | 33 +- .../baidu/palo/analysis/GrantStmtTest.java | 82 +- .../baidu/palo/analysis/LinkDbStmtTest.java | 26 +- .../com/baidu/palo/analysis/LoadStmtTest.java | 50 +- .../palo/analysis/MigrateDbStmtTest.java | 26 +- .../baidu/palo/analysis/SetPassVarTest.java | 41 +- .../com/baidu/palo/analysis/SetStmtTest.java | 18 + .../analysis/SetUserPropertyStmtTest.java | 22 +- .../palo/analysis/SetUserPropertyVarTest.java | 6 +- .../com/baidu/palo/analysis/SetVarTest.java | 24 +- .../palo/analysis/ShowAlterStmtTest.java | 18 +- .../palo/analysis/ShowCreateDbStmtTest.java | 30 +- .../analysis/ShowCreateTableStmtTest.java | 24 +- .../baidu/palo/analysis/ShowDataStmtTest.java | 127 +- .../baidu/palo/analysis/ShowLoadStmtTest.java | 22 +- .../palo/analysis/ShowTableStmtTest.java | 25 +- .../analysis/ShowUserPropertyStmtTest.java | 17 + .../com/baidu/palo/analysis/UseStmtTest.java | 25 +- .../baidu/palo/backup/BackupHandlerTest.java | 364 +++ .../baidu/palo/backup/BackupJobInfoTest.java | 167 ++ .../com/baidu/palo/backup/BackupJobTest.java | 305 ++- .../com/baidu/palo/backup/CatalogMocker.java | 81 +- .../baidu/palo/backup/ObjectWriterTest.java | 111 - .../com/baidu/palo/backup/RepositoryTest.java | 318 +++ .../palo/backup/RestoreFileMappingTest.java | 43 + .../com/baidu/palo/backup/RestoreJobTest.java | 363 +++ .../baidu/palo/bdb/BDBToolOptionsTest.java | 2 +- .../com/baidu/palo/catalog/BackendTest.java | 15 +- .../catalog/DomainResolverServerTest.java | 155 -- .../com/baidu/palo/catalog/OlapTableTest.java | 59 + .../baidu/palo/catalog/UserPropertyTest.java | 19 +- .../baidu/palo/catalog/UserResourceTest.java | 1 + .../baidu/palo/clone/CloneCheckerTest.java | 766 ------ fe/test/com/baidu/palo/clone/CloneTest.java | 5 +- .../palo/cluster/SystemInfoServiceTest.java | 4 +- fe/test/com/baidu/palo/common/MD5Test.java | 59 + .../baidu/palo/common/PatternMatcherTest.java | 132 +- .../palo/common/proc/BackendProcNodeTest.java | 18 +- .../palo/common/proc/BackendsProcDirTest.java | 21 +- .../common/proc/UserPropertyProcTest.java | 125 - .../palo/common/util/RuntimeProfileTest.java | 27 +- .../baidu/palo/common/util/UnitTestUtil.java | 19 +- .../palo/deploy/AmbariDeployManagerTest.java | 2 - .../com/baidu/palo/load/DppSchedulerTest.java | 55 - fe/test/com/baidu/palo/load/LoadJobTest.java | 17 +- fe/test/com/baidu/palo/load/LoadTest.java | 36 +- .../com/baidu/palo/mysql/MysqlProtoTest.java | 31 +- .../baidu/palo/mysql/privilege/AuthTest.java | 938 ++++++++ .../palo/mysql/privilege/MockedAuth.java | 57 + .../baidu/palo/mysql/privilege/PrivTest.java | 347 +++ .../palo/mysql/privilege/PrivilegeTest.java | 161 ++ .../mysql/privilege/UserIdentityTest.java | 46 + .../com/baidu/palo/persist/AccessTest.java | 286 --- .../com/baidu/palo/qe/ConnectContextTest.java | 11 +- .../baidu/palo/qe/ConnectProcessorTest.java | 14 +- .../baidu/palo/qe/ConnectSchedulerTest.java | 16 +- .../com/baidu/palo/qe/CoordinatorTest.java | 10 - .../com/baidu/palo/qe/SetExecutorTest.java | 51 +- .../com/baidu/palo/qe/ShowExecutorTest.java | 92 +- .../com/baidu/palo/qe/StmtExecutorTest.java | 32 +- .../baidu/palo/task/LoadPendingTaskTest.java | 1 + .../palo/broker/hdfs/FileSystemManager.java | 103 +- gensrc/parser/sql_parser.y | 226 +- gensrc/parser/sql_scanner.flex | 8 + gensrc/proto/olap_file.proto | 3 + gensrc/script/gen_build_version.sh | 2 +- gensrc/thrift/AgentService.thrift | 31 +- gensrc/thrift/FrontendService.thrift | 7 + gensrc/thrift/MasterService.thrift | 4 + gensrc/thrift/PaloBrokerService.thrift | 1 + gensrc/thrift/PlanNodes.thrift | 1 + gensrc/thrift/Types.thrift | 7 +- thirdparty/build-thirdparty.sh | 1 - .../java-libraries/cobertura/asm-5.0.1.jar | Bin 0 -> 53217 bytes .../cobertura/asm-analysis-5.0.1.jar | Bin 0 -> 20445 bytes .../cobertura/asm-commons-5.0.1.jar | Bin 0 -> 41718 bytes .../cobertura/asm-tree-5.0.1.jar | Bin 0 -> 29038 bytes .../cobertura/asm-util-5.0.1.jar | Bin 0 -> 43307 bytes .../cobertura/cobertura-2.1.1-javadoc.jar | Bin 0 -> 1062062 bytes .../cobertura/cobertura-2.1.1-sources.jar | Bin 0 -> 409789 bytes .../cobertura/cobertura-2.1.1.jar | Bin 0 -> 564556 bytes .../cobertura/commons-lang3-3.3.2.jar | Bin 0 -> 412739 bytes .../cobertura/hamcrest-core-1.3.jar | Bin 0 -> 45024 bytes .../java-libraries/cobertura/jaxen-1.1.4.jar | Bin 0 -> 226161 bytes .../java-libraries/cobertura/jetty-6.1.14.jar | Bin 0 -> 516429 bytes .../cobertura/jetty-util-6.1.14.jar | Bin 0 -> 163121 bytes .../cobertura/logback-classic-1.0.13.jar | Bin 0 -> 264600 bytes .../cobertura/logback-core-1.0.13.jar | Bin 0 -> 418870 bytes .../java-libraries/cobertura/oro-2.0.8.jar | Bin 0 -> 65261 bytes .../cobertura/servlet-api-2.5-6.1.14.jar | Bin 0 -> 132368 bytes .../cobertura/slf4j-api-1.7.5.jar | Bin 0 -> 26084 bytes ...5.15.jar => jprotobuf-rpc-core-3.5.17.jar} | Bin 175755 -> 176428 bytes 423 files changed, 25901 insertions(+), 11040 deletions(-) create mode 100644 be/src/runtime/snapshot_loader.cpp create mode 100644 be/src/runtime/snapshot_loader.h create mode 100644 be/test/runtime/snapshot_loader_test.cpp create mode 100644 docs/design/Palo_privilege.md create mode 100644 docs/design/real_time_loading.md create mode 100644 fe/src/com/baidu/palo/analysis/CreateRepositoryStmt.java create mode 100644 fe/src/com/baidu/palo/analysis/CreateRoleStmt.java create mode 100644 fe/src/com/baidu/palo/analysis/DropRepositoryStmt.java create mode 100644 fe/src/com/baidu/palo/analysis/DropRoleStmt.java create mode 100644 fe/src/com/baidu/palo/analysis/ShowFrontendsStmt.java create mode 100644 fe/src/com/baidu/palo/analysis/ShowGrantsStmt.java create mode 100644 fe/src/com/baidu/palo/analysis/ShowRepositoriesStmt.java create mode 100644 fe/src/com/baidu/palo/analysis/ShowRolesStmt.java create mode 100644 fe/src/com/baidu/palo/analysis/ShowSnapshotStmt.java create mode 100644 fe/src/com/baidu/palo/analysis/TablePattern.java create mode 100644 fe/src/com/baidu/palo/analysis/UserIdentity.java rename fe/src/com/baidu/palo/backup/{AbstractBackupJob.java => AbstractBackupJob_D.java} (96%) create mode 100644 fe/src/com/baidu/palo/backup/AbstractJob.java create mode 100644 fe/src/com/baidu/palo/backup/BackupJobInfo.java create mode 100644 fe/src/com/baidu/palo/backup/BackupJob_D.java create mode 100644 fe/src/com/baidu/palo/backup/BackupMeta.java create mode 100644 fe/src/com/baidu/palo/backup/BlobStorage.java create mode 100644 fe/src/com/baidu/palo/backup/RemoteFile.java create mode 100644 fe/src/com/baidu/palo/backup/Repository.java create mode 100644 fe/src/com/baidu/palo/backup/RepositoryMgr.java create mode 100644 fe/src/com/baidu/palo/backup/RestoreFileMapping.java create mode 100644 fe/src/com/baidu/palo/backup/RestoreJob_D.java create mode 100644 fe/src/com/baidu/palo/backup/SnapshotInfo.java create mode 100644 fe/src/com/baidu/palo/backup/Status.java create mode 100644 fe/src/com/baidu/palo/catalog/DomainResolver.java delete mode 100644 fe/src/com/baidu/palo/catalog/DomainResolverServer.java delete mode 100644 fe/src/com/baidu/palo/catalog/UserPropertyMgr.java delete mode 100644 fe/src/com/baidu/palo/catalog/WhiteList.java create mode 100644 fe/src/com/baidu/palo/common/CaseSensibility.java create mode 100644 fe/src/com/baidu/palo/common/io/DeepCopy.java create mode 100644 fe/src/com/baidu/palo/common/io/FastByteArrayInputStream.java create mode 100644 fe/src/com/baidu/palo/common/io/FastByteArrayOutputStream.java rename fe/src/com/baidu/palo/common/proc/{AccessResourceProcDir.java => AuthProcDir.java} (61%) delete mode 100644 fe/src/com/baidu/palo/common/proc/BackupJobProcNode.java delete mode 100644 fe/src/com/baidu/palo/common/proc/BackupProcNode.java delete mode 100644 fe/src/com/baidu/palo/common/proc/RestoreProcNode.java create mode 100644 fe/src/com/baidu/palo/http/rest/StorageTypeCheckAction.java create mode 100644 fe/src/com/baidu/palo/mysql/privilege/DbPrivEntry.java create mode 100644 fe/src/com/baidu/palo/mysql/privilege/DbPrivTable.java create mode 100644 fe/src/com/baidu/palo/mysql/privilege/GlobalPrivEntry.java create mode 100644 fe/src/com/baidu/palo/mysql/privilege/PaloAuth.java create mode 100644 fe/src/com/baidu/palo/mysql/privilege/PaloPrivilege.java create mode 100644 fe/src/com/baidu/palo/mysql/privilege/PaloRole.java create mode 100644 fe/src/com/baidu/palo/mysql/privilege/PrivBitSet.java create mode 100644 fe/src/com/baidu/palo/mysql/privilege/PrivEntry.java create mode 100644 fe/src/com/baidu/palo/mysql/privilege/PrivPredicate.java create mode 100644 fe/src/com/baidu/palo/mysql/privilege/PrivTable.java create mode 100644 fe/src/com/baidu/palo/mysql/privilege/RoleManager.java create mode 100644 fe/src/com/baidu/palo/mysql/privilege/TablePrivEntry.java create mode 100644 fe/src/com/baidu/palo/mysql/privilege/TablePrivTable.java create mode 100644 fe/src/com/baidu/palo/mysql/privilege/UserPrivTable.java rename fe/src/com/baidu/palo/{catalog => mysql/privilege}/UserProperty.java (58%) create mode 100644 fe/src/com/baidu/palo/mysql/privilege/UserPropertyMgr.java rename fe/src/com/baidu/palo/{catalog => mysql/privilege}/UserResource.java (98%) create mode 100644 fe/src/com/baidu/palo/mysql/privilege/WhiteList.java create mode 100644 fe/src/com/baidu/palo/persist/PrivInfo.java create mode 100644 fe/src/com/baidu/palo/task/DirMoveTask.java create mode 100644 fe/src/com/baidu/palo/task/DownloadTask.java delete mode 100644 fe/src/com/baidu/palo/task/RestoreTask.java create mode 100644 fe/test/com/baidu/palo/backup/BackupHandlerTest.java create mode 100644 fe/test/com/baidu/palo/backup/BackupJobInfoTest.java delete mode 100644 fe/test/com/baidu/palo/backup/ObjectWriterTest.java create mode 100644 fe/test/com/baidu/palo/backup/RepositoryTest.java create mode 100644 fe/test/com/baidu/palo/backup/RestoreFileMappingTest.java create mode 100644 fe/test/com/baidu/palo/backup/RestoreJobTest.java delete mode 100644 fe/test/com/baidu/palo/catalog/DomainResolverServerTest.java create mode 100644 fe/test/com/baidu/palo/catalog/OlapTableTest.java delete mode 100644 fe/test/com/baidu/palo/clone/CloneCheckerTest.java create mode 100644 fe/test/com/baidu/palo/common/MD5Test.java delete mode 100644 fe/test/com/baidu/palo/common/proc/UserPropertyProcTest.java create mode 100644 fe/test/com/baidu/palo/mysql/privilege/AuthTest.java create mode 100644 fe/test/com/baidu/palo/mysql/privilege/MockedAuth.java create mode 100644 fe/test/com/baidu/palo/mysql/privilege/PrivTest.java create mode 100644 fe/test/com/baidu/palo/mysql/privilege/PrivilegeTest.java create mode 100644 fe/test/com/baidu/palo/mysql/privilege/UserIdentityTest.java delete mode 100644 fe/test/com/baidu/palo/persist/AccessTest.java create mode 100644 thirdparty/java-libraries/cobertura/asm-5.0.1.jar create mode 100644 thirdparty/java-libraries/cobertura/asm-analysis-5.0.1.jar create mode 100644 thirdparty/java-libraries/cobertura/asm-commons-5.0.1.jar create mode 100644 thirdparty/java-libraries/cobertura/asm-tree-5.0.1.jar create mode 100644 thirdparty/java-libraries/cobertura/asm-util-5.0.1.jar create mode 100644 thirdparty/java-libraries/cobertura/cobertura-2.1.1-javadoc.jar create mode 100644 thirdparty/java-libraries/cobertura/cobertura-2.1.1-sources.jar create mode 100644 thirdparty/java-libraries/cobertura/cobertura-2.1.1.jar create mode 100644 thirdparty/java-libraries/cobertura/commons-lang3-3.3.2.jar create mode 100644 thirdparty/java-libraries/cobertura/hamcrest-core-1.3.jar create mode 100644 thirdparty/java-libraries/cobertura/jaxen-1.1.4.jar create mode 100644 thirdparty/java-libraries/cobertura/jetty-6.1.14.jar create mode 100644 thirdparty/java-libraries/cobertura/jetty-util-6.1.14.jar create mode 100644 thirdparty/java-libraries/cobertura/logback-classic-1.0.13.jar create mode 100644 thirdparty/java-libraries/cobertura/logback-core-1.0.13.jar create mode 100644 thirdparty/java-libraries/cobertura/oro-2.0.8.jar create mode 100644 thirdparty/java-libraries/cobertura/servlet-api-2.5-6.1.14.jar create mode 100644 thirdparty/java-libraries/cobertura/slf4j-api-1.7.5.jar rename thirdparty/java-libraries/{jprotobuf-rpc-core-3.5.15.jar => jprotobuf-rpc-core-3.5.17.jar} (70%) diff --git a/be/CMakeLists.txt b/be/CMakeLists.txt index 9597102b9a..d00aa74f62 100644 --- a/be/CMakeLists.txt +++ b/be/CMakeLists.txt @@ -252,14 +252,13 @@ execute_process( # -fno-omit-frame-pointers: Keep frame pointer for functions in register set(CXX_COMMON_FLAGS "${CXX_COMMON_FLAGS} -Wall -Wno-sign-compare -Wno-unknown-pragmas -pthread") set(CXX_COMMON_FLAGS "${CXX_COMMON_FLAGS} -fno-strict-aliasing -fno-omit-frame-pointer") -set(CXX_COMMON_FLAGS "${CXX_COMMON_FLAGS} -std=gnu++11") +set(CXX_COMMON_FLAGS "${CXX_COMMON_FLAGS} -std=gnu++11 -D__STDC_FORMAT_MACROS") set(CXX_COMMON_FLAGS "${CXX_COMMON_FLAGS} -Wno-deprecated -Wno-vla") set(CXX_COMMON_FLAGS "${CXX_COMMON_FLAGS} -DBOOST_DATE_TIME_POSIX_TIME_STD_CONFIG") set(CXX_COMMON_FLAGS "${CXX_COMMON_FLAGS} -DBOOST_SYSTEM_NO_DEPRECATED") set(CXX_COMMON_FLAGS "${CXX_COMMON_FLAGS} -msse4.2") set(CXX_COMMON_FLAGS "${CXX_COMMON_FLAGS} -DLLVM_ON_UNIX") -# for bprc if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 7.0) set(CXX_COMMON_FLAGS "${CXX_COMMON_FLAGS} -faligned-new") endif() @@ -350,7 +349,6 @@ endif() # optimizations will be less effective if the initial code is also optimized. set(CLANG_IR_CXX_FLAGS "-gcc-toolchain" ${GCC_HOME}) set(CLANG_IR_CXX_FLAGS ${CLANG_IR_CXX_FLAGS} "-std=gnu++11" "-c" "-emit-llvm" "-D__STDC_CONSTANT_MACROS" "-D__STDC_FORMAT_MACROS" "-D__STDC_LIMIT_MACROS" "-DIR_COMPILE" "-DNDEBUG" "-DHAVE_INTTYPES_H" "-DHAVE_NETINET_IN_H" "-DBOOST_DATE_TIME_POSIX_TIME_STD_CONFIG" "-D__GLIBCXX_BITSIZE_INT_N_0=128" "-D__GLIBCXX_TYPE_INT_N_0=__int128" "-U_GLIBCXX_USE_FLOAT128" "-DLLVM_ON_UNIX") - if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 7.0) # for support float128 set(CLANG_IR_CXX_FLAGS ${CLANG_IR_CXX_FLAGS} "-D__STRICT_ANSI__") diff --git a/be/src/agent/agent_server.cpp b/be/src/agent/agent_server.cpp index 699306e1f8..cce954f29c 100644 --- a/be/src/agent/agent_server.cpp +++ b/be/src/agent/agent_server.cpp @@ -83,51 +83,71 @@ AgentServer::AgentServer(ExecEnv* exec_env, // init task worker pool _create_table_workers = new TaskWorkerPool( TaskWorkerPool::TaskWorkerType::CREATE_TABLE, + _exec_env, master_info); _drop_table_workers = new TaskWorkerPool( TaskWorkerPool::TaskWorkerType::DROP_TABLE, + _exec_env, master_info); _push_workers = new TaskWorkerPool( TaskWorkerPool::TaskWorkerType::PUSH, + _exec_env, master_info); _delete_workers = new TaskWorkerPool( TaskWorkerPool::TaskWorkerType::DELETE, + _exec_env, master_info); _alter_table_workers = new TaskWorkerPool( TaskWorkerPool::TaskWorkerType::ALTER_TABLE, + _exec_env, master_info); _clone_workers = new TaskWorkerPool( TaskWorkerPool::TaskWorkerType::CLONE, + _exec_env, master_info); _storage_medium_migrate_workers = new TaskWorkerPool( TaskWorkerPool::TaskWorkerType::STORAGE_MEDIUM_MIGRATE, + _exec_env, master_info); _cancel_delete_data_workers = new TaskWorkerPool( TaskWorkerPool::TaskWorkerType::CANCEL_DELETE_DATA, + _exec_env, master_info); _check_consistency_workers = new TaskWorkerPool( TaskWorkerPool::TaskWorkerType::CHECK_CONSISTENCY, + _exec_env, master_info); _report_task_workers = new TaskWorkerPool( TaskWorkerPool::TaskWorkerType::REPORT_TASK, + _exec_env, master_info); _report_disk_state_workers = new TaskWorkerPool( TaskWorkerPool::TaskWorkerType::REPORT_DISK_STATE, + _exec_env, master_info); _report_olap_table_workers = new TaskWorkerPool( TaskWorkerPool::TaskWorkerType::REPORT_OLAP_TABLE, + _exec_env, master_info); _upload_workers = new TaskWorkerPool( TaskWorkerPool::TaskWorkerType::UPLOAD, + _exec_env, master_info); - _restore_workers = new TaskWorkerPool( - TaskWorkerPool::TaskWorkerType::RESTORE, + _download_workers = new TaskWorkerPool( + TaskWorkerPool::TaskWorkerType::DOWNLOAD, + _exec_env, master_info); _make_snapshot_workers = new TaskWorkerPool( TaskWorkerPool::TaskWorkerType::MAKE_SNAPSHOT, + _exec_env, master_info); _release_snapshot_workers = new TaskWorkerPool( TaskWorkerPool::TaskWorkerType::RELEASE_SNAPSHOT, + _exec_env, + master_info); + _move_dir_workers= new TaskWorkerPool( + TaskWorkerPool::TaskWorkerType::MOVE, + _exec_env, master_info); #ifndef BE_TEST _create_table_workers->start(); @@ -143,9 +163,10 @@ AgentServer::AgentServer(ExecEnv* exec_env, _report_disk_state_workers->start(); _report_olap_table_workers->start(); _upload_workers->start(); - _restore_workers->start(); + _download_workers->start(); _make_snapshot_workers->start(); _release_snapshot_workers->start(); + _move_dir_workers->start(); // Add subscriber here and register listeners TopicListener* user_resource_listener = new UserResourceListener(exec_env, master_info); LOG(INFO) << "Register user resource listener"; @@ -196,12 +217,15 @@ AgentServer::~AgentServer() { if (_upload_workers != NULL) { delete _upload_workers; } - if (_restore_workers != NULL) { - delete _restore_workers; + if (_download_workers != NULL) { + delete _download_workers; } if (_make_snapshot_workers != NULL) { delete _make_snapshot_workers; } + if (_move_dir_workers!= NULL) { + delete _move_dir_workers; + } if (_release_snapshot_workers != NULL) { delete _release_snapshot_workers; } @@ -303,9 +327,9 @@ void AgentServer::submit_tasks( status_code = TStatusCode::ANALYSIS_ERROR; } break; - case TTaskType::RESTORE: - if (task.__isset.restore_req) { - _restore_workers->submit_task(task); + case TTaskType::DOWNLOAD: + if (task.__isset.download_req) { + _download_workers->submit_task(task); } else { status_code = TStatusCode::ANALYSIS_ERROR; } @@ -324,6 +348,13 @@ void AgentServer::submit_tasks( status_code = TStatusCode::ANALYSIS_ERROR; } break; + case TTaskType::MOVE: + if (task.__isset.move_dir_req) { + _move_dir_workers->submit_task(task); + } else { + status_code = TStatusCode::ANALYSIS_ERROR; + } + break; default: status_code = TStatusCode::ANALYSIS_ERROR; break; @@ -391,7 +422,6 @@ void AgentServer::release_snapshot(TAgentResult& return_value, const std::string void AgentServer::publish_cluster_state(TAgentResult& _return, const TAgentPublishRequest& request) { vector error_msgs; _topic_subscriber->handle_updates(request); - OLAP_LOG_INFO("AgentService receive contains %d publish updates", request.updates.size()); _return.status.__set_status_code(TStatusCode::OK); } diff --git a/be/src/agent/agent_server.h b/be/src/agent/agent_server.h index bc354c65b1..a52d60ab1a 100644 --- a/be/src/agent/agent_server.h +++ b/be/src/agent/agent_server.h @@ -106,9 +106,10 @@ private: TaskWorkerPool* _report_disk_state_workers; TaskWorkerPool* _report_olap_table_workers; TaskWorkerPool* _upload_workers; - TaskWorkerPool* _restore_workers; + TaskWorkerPool* _download_workers; TaskWorkerPool* _make_snapshot_workers; TaskWorkerPool* _release_snapshot_workers; + TaskWorkerPool* _move_dir_workers; DISALLOW_COPY_AND_ASSIGN(AgentServer); diff --git a/be/src/agent/cgroups_mgr.cpp b/be/src/agent/cgroups_mgr.cpp index a5405ca0a0..7ccc2ddcc4 100644 --- a/be/src/agent/cgroups_mgr.cpp +++ b/be/src/agent/cgroups_mgr.cpp @@ -62,11 +62,8 @@ CgroupsMgr::~CgroupsMgr() { AgentStatus CgroupsMgr::update_local_cgroups(const TFetchResourceResult& new_fetched_resource) { - LOG(INFO) << "Current resource version is " << _cur_version - << ". Resource version is " << new_fetched_resource.resourceVersion; std::lock_guard lck(_update_cgroups_mtx); if (!_is_cgroups_init_success) { - LOG(WARNING) << "Cgroups manager initialized failed, will not update local cgroups!"; return AgentStatus::PALO_ERROR; } diff --git a/be/src/agent/heartbeat_server.cpp b/be/src/agent/heartbeat_server.cpp index 5a862a0988..ae5cce5e1a 100644 --- a/be/src/agent/heartbeat_server.cpp +++ b/be/src/agent/heartbeat_server.cpp @@ -48,10 +48,12 @@ void HeartbeatServer::heartbeat( TStatusCode::type status_code = TStatusCode::OK; vector error_msgs; TStatus heartbeat_status; - OLAP_LOG_INFO("get heartbeat, host: %s, port: %d, cluster id: %d", - master_info.network_address.hostname.c_str(), - master_info.network_address.port, - master_info.cluster_id); + //print heartbeat in every minute + LOG_EVERY_N(INFO, 12) << "get heartbeat from FE." + << "host:" << master_info.network_address.hostname << ", " + << "port:" << master_info.network_address.port << ", " + << "cluster id:" << master_info.cluster_id << ", " + << "counter:" << google::COUNTER; // Check cluster id if (_master_info->cluster_id == -1) { diff --git a/be/src/agent/task_worker_pool.cpp b/be/src/agent/task_worker_pool.cpp index 8ec8ad8501..f7d6fba95b 100644 --- a/be/src/agent/task_worker_pool.cpp +++ b/be/src/agent/task_worker_pool.cpp @@ -37,8 +37,12 @@ #include "olap/olap_table.h" #include "olap/utils.h" #include "common/resource_tls.h" +#include "common/status.h" +#include "util/file_utils.h" #include "agent/cgroups_mgr.h" #include "service/backend_options.h" +#include "runtime/exec_env.h" +#include "runtime/snapshot_loader.h" #include "util/palo_metrics.h" using std::deque; @@ -77,6 +81,7 @@ boost::posix_time::time_duration TaskWorkerPool::_wait_duration; TaskWorkerPool::TaskWorkerPool( const TaskWorkerType task_worker_type, + ExecEnv* env, const TMasterInfo& master_info) : _master_info(master_info), _worker_thread_condition_lock(_worker_thread_lock), @@ -84,6 +89,7 @@ TaskWorkerPool::TaskWorkerPool( _agent_utils = new AgentUtils(); _master_client = new MasterServerClient(_master_info, &_master_service_client_cache); _command_executor = new CommandExecutor(); + _env = env; _backend.__set_host(BackendOptions::get_localhost()); _backend.__set_be_port(config::be_port); _backend.__set_http_port(config::webserver_port); @@ -162,9 +168,9 @@ void TaskWorkerPool::start() { _worker_count = config::upload_worker_count; _callback_function = _upload_worker_thread_callback; break; - case TaskWorkerType::RESTORE: - _worker_count = config::restore_worker_count; - _callback_function = _restore_worker_thread_callback; + case TaskWorkerType::DOWNLOAD: + _worker_count = config::download_worker_count; + _callback_function = _download_worker_thread_callback; break; case TaskWorkerType::MAKE_SNAPSHOT: _worker_count = config::make_snapshot_worker_count; @@ -174,6 +180,10 @@ void TaskWorkerPool::start() { _worker_count = config::release_snapshot_worker_count; _callback_function = _release_snapshot_thread_callback; break; + case TaskWorkerType::MOVE: + _worker_count = 1; + _callback_function = _move_dir_thread_callback; + break; default: // pass break; @@ -1008,7 +1018,6 @@ AgentStatus TaskWorkerPool::_clone_copy( vector* error_msgs) { AgentStatus status = PALO_SUCCESS; - std::string token = _master_info.token; for (auto src_backend : clone_req.src_backends) { @@ -1509,20 +1518,16 @@ void* TaskWorkerPool::_report_task_worker_thread_callback(void* arg_this) { lock_guard task_signatures_lock(_s_task_signatures_lock); request.__set_tasks(_s_task_signatures); } - OLAP_LOG_INFO("master host: %s, port: %d", - worker_pool_this->_master_info.network_address.hostname.c_str(), - worker_pool_this->_master_info.network_address.port); PaloMetrics::report_task_requests_total.increment(1); TMasterResult result; AgentStatus status = worker_pool_this->_master_client->report(request, &result); - if (status == PALO_SUCCESS) { - OLAP_LOG_INFO("finish report task success. return code: %d", - result.status.status_code); - } else { + if (status != PALO_SUCCESS) { PaloMetrics::report_task_requests_failed.increment(1); - OLAP_LOG_WARNING("finish report task failed. status: %d", status); + LOG(WARNING) << "finish report task failed. status:" << status << ", " + << "master host:" << worker_pool_this->_master_info.network_address.hostname << ", " + << "port:" << worker_pool_this->_master_info.network_address.port; } #ifndef BE_TEST @@ -1570,12 +1575,11 @@ void* TaskWorkerPool::_report_disk_state_worker_thread_callback(void* arg_this) TMasterResult result; AgentStatus status = worker_pool_this->_master_client->report(request, &result); - if (status == PALO_SUCCESS) { - OLAP_LOG_INFO("finish report disk state success. return code: %d", - result.status.status_code); - } else { + if (status != PALO_SUCCESS) { PaloMetrics::report_disk_requests_failed.increment(1); - OLAP_LOG_WARNING("finish report disk state failed. status: %d", status); + LOG(WARNING) << "finish report disk state failed. status:" << status << ", " + << "master host:" << worker_pool_this->_master_info.network_address.hostname << ", " + << "port:" << worker_pool_this->_master_info.network_address.port; } #ifndef BE_TEST @@ -1638,12 +1642,11 @@ void* TaskWorkerPool::_report_olap_table_worker_thread_callback(void* arg_this) TMasterResult result; status = worker_pool_this->_master_client->report(request, &result); - if (status == PALO_SUCCESS) { - OLAP_LOG_INFO("finish report olap table success. return code: %d", - result.status.status_code); - } else { + if (status != PALO_SUCCESS) { PaloMetrics::report_all_tablets_requests_failed.increment(1); - OLAP_LOG_WARNING("finish report olap table failed. status: %d", status); + LOG(WARNING) << "finish report olap table state failed. status:" << status << ", " + << "master host:" << worker_pool_this->_master_info.network_address.hostname << ", " + << "port:" << worker_pool_this->_master_info.network_address.port; } #ifndef BE_TEST @@ -1678,58 +1681,30 @@ void* TaskWorkerPool::_upload_worker_thread_callback(void* arg_this) { upload_request = agent_task_req.upload_req; worker_pool_this->_tasks.pop_front(); } - // Try to register to cgroups_mgr - CgroupsMgr::apply_system_cgroup(); - OLAP_LOG_INFO("get upload task, signature: %ld", agent_task_req.signature); - TStatusCode::type status_code = TStatusCode::OK; - vector error_msgs; - TStatus task_status; + OLAP_LOG_INFO("get upload task, signature: %ld, job id: %d", + agent_task_req.signature, upload_request.job_id); - // Write remote source info into file by json format - pthread_t tid = pthread_self(); - time_t now = time(NULL); - stringstream label_stream; - label_stream << tid << "_" << now; - string label(label_stream.str()); - string info_file_path(config::agent_tmp_dir + "/" + label); - bool ret = worker_pool_this->_agent_utils->write_json_to_file( - upload_request.remote_source_properties, - info_file_path); - if (!ret) { + std::map> tablet_files; + SnapshotLoader* loader = worker_pool_this->_env->snapshot_loader(); + Status status = loader->upload( + upload_request.src_dest_map, + upload_request.broker_addr, + upload_request.broker_prop, + upload_request.job_id, + &tablet_files); + + TStatusCode::type status_code = TStatusCode::OK; + std::vector error_msgs; + if (!status.ok()) { status_code = TStatusCode::RUNTIME_ERROR; - error_msgs.push_back("Write remote source info to file failed. Path:" + - info_file_path); - OLAP_LOG_WARNING("Write remote source info to file failed. Path: %s", - info_file_path.c_str()); - } - - // Upload files to remote source - stringstream local_file_path_stream; - local_file_path_stream << upload_request.local_file_path; - if (upload_request.__isset.tablet_id) { - local_file_path_stream << "/" << upload_request.tablet_id; - } - if (status_code == TStatusCode::OK) { - string command = "sh " + config::trans_file_tool_path + " " + label + " upload " + - local_file_path_stream.str() + " " + upload_request.remote_file_path + - " " + info_file_path + " " + "file_list"; - OLAP_LOG_INFO("Upload cmd: %s", command.c_str()); - string errmsg; - ret = worker_pool_this->_agent_utils->exec_cmd(command, &errmsg); - if (!ret) { - status_code = TStatusCode::RUNTIME_ERROR; - error_msgs.push_back(errmsg); - OLAP_LOG_WARNING("Upload file failed. Error: %s", errmsg.c_str()); - } - } - - // Delete tmp file - boost::filesystem::path file_path(info_file_path); - if (boost::filesystem::exists(file_path)) { - boost::filesystem::remove_all(file_path); + OLAP_LOG_WARNING("upload failed. job id: %ld, msg: %s", + upload_request.job_id, + status.get_error_msg().c_str()); + error_msgs.push_back(status.get_error_msg()); } + TStatus task_status; task_status.__set_status_code(status_code); task_status.__set_error_msgs(error_msgs); @@ -1738,23 +1713,27 @@ void* TaskWorkerPool::_upload_worker_thread_callback(void* arg_this) { finish_task_request.__set_task_type(agent_task_req.task_type); finish_task_request.__set_signature(agent_task_req.signature); finish_task_request.__set_task_status(task_status); + finish_task_request.__set_tablet_files(tablet_files); worker_pool_this->_finish_task(finish_task_request); worker_pool_this->_remove_task_info(agent_task_req.task_type, agent_task_req.signature, ""); + + OLAP_LOG_INFO("finished upload task, signature: %ld, job id: %ld", + agent_task_req.signature, upload_request.job_id); #ifndef BE_TEST } #endif return (void*)0; } -void* TaskWorkerPool::_restore_worker_thread_callback(void* arg_this) { +void* TaskWorkerPool::_download_worker_thread_callback(void* arg_this) { TaskWorkerPool* worker_pool_this = (TaskWorkerPool*)arg_this; #ifndef BE_TEST while (true) { #endif TAgentTaskRequest agent_task_req; - TRestoreReq restore_request; + TDownloadReq download_request; { lock_guard worker_thread_lock(worker_pool_this->_worker_thread_lock); while (worker_pool_this->_tasks.empty()) { @@ -1762,169 +1741,51 @@ void* TaskWorkerPool::_restore_worker_thread_callback(void* arg_this) { } agent_task_req = worker_pool_this->_tasks.front(); - restore_request = agent_task_req.restore_req; + download_request = agent_task_req.download_req; worker_pool_this->_tasks.pop_front(); } // Try to register to cgroups_mgr CgroupsMgr::apply_system_cgroup(); - OLAP_LOG_INFO("get restore task, signature: %ld", agent_task_req.signature); + OLAP_LOG_INFO("get download task, signature: %ld, job id: %ld", + agent_task_req.signature, download_request.job_id); TStatusCode::type status_code = TStatusCode::OK; - vector error_msgs; + std::vector error_msgs; TStatus task_status; - // Write remote source info into file by json format - pthread_t tid = pthread_self(); - time_t now = time(NULL); - stringstream label_stream; - label_stream << tid << "_" << now << "_" << restore_request.tablet_id; - string label(label_stream.str()); - string info_file_path(config::agent_tmp_dir + "/" + label); - bool ret = worker_pool_this->_agent_utils->write_json_to_file( - restore_request.remote_source_properties, - info_file_path); - if (!ret) { + // TODO: download + std::vector downloaded_tablet_ids; + SnapshotLoader* loader = worker_pool_this->_env->snapshot_loader(); + Status status = loader->download( + download_request.src_dest_map, + download_request.broker_addr, + download_request.broker_prop, + download_request.job_id, + &downloaded_tablet_ids); + + if (!status.ok()) { status_code = TStatusCode::RUNTIME_ERROR; - error_msgs.push_back("Write remote source info to file failed. Path:" + - info_file_path); - OLAP_LOG_WARNING("Write remote source info to file failed. Path: %s", - info_file_path.c_str()); - } - - // Get local disk to restore from olap - string local_shard_root_path; - if (status_code == TStatusCode::OK) { - OLAPStatus olap_status = worker_pool_this->_command_executor->obtain_shard_path( - TStorageMedium::HDD, &local_shard_root_path); - if (olap_status != OLAP_SUCCESS) { - OLAP_LOG_WARNING("clone get local root path failed. signature: %ld", - agent_task_req.signature); - error_msgs.push_back("clone get local root path failed."); - status_code = TStatusCode::RUNTIME_ERROR; - } - } - - stringstream local_file_path_stream; - local_file_path_stream << local_shard_root_path << "/" << restore_request.tablet_id << "/"; - string local_file_path(local_file_path_stream.str()); - - // Download files from remote source - if (status_code == TStatusCode::OK) { - string command = "sh " + config::trans_file_tool_path + " " + label + " download " + - local_file_path + " " + restore_request.remote_file_path + - " " + info_file_path; - OLAP_LOG_INFO("Download cmd: %s", command.c_str()); - string errmsg; - ret = worker_pool_this->_agent_utils->exec_cmd(command, &errmsg); - if (!ret) { - status_code = TStatusCode::RUNTIME_ERROR; - error_msgs.push_back(errmsg); - OLAP_LOG_WARNING("Download file failed. Error: %s", errmsg.c_str()); - } - } - - // Delete tmp file - boost::filesystem::path file_path(info_file_path); - if (boost::filesystem::exists(file_path)) { - boost::filesystem::remove_all(file_path); - } - - // Change file name - boost::filesystem::path blocal_file_path(local_file_path); - if (status_code == TStatusCode::OK && boost::filesystem::exists(blocal_file_path)) { - boost::filesystem::recursive_directory_iterator end_iter; - for (boost::filesystem::recursive_directory_iterator file_path(blocal_file_path); - file_path != end_iter; ++file_path) { - if (boost::filesystem::is_directory(*file_path)) { - continue; - } - - // Check file name - string file_path_str = file_path->path().string(); - string file_name = file_path_str; - uint32_t slash_pos = file_path_str.rfind('/'); - if (slash_pos != -1) { - file_name = file_path_str.substr(slash_pos + 1); - } - uint32_t file_path_str_len = file_name.size(); - if (file_path_str_len <= 4) { - continue; - } - string file_path_suffix = file_name.substr(file_path_str_len - 4); - if (file_path_suffix != ".hdr" && file_path_suffix != ".idx" && - file_path_suffix != ".dat") { - continue; - } - - // Get new file name - stringstream new_file_name_stream; - char sperator = '_'; - if (file_path_suffix == ".hdr") { - sperator = '.'; - } - uint32_t sperator_pos = file_name.find(sperator); - new_file_name_stream << restore_request.tablet_id << file_name.substr(sperator_pos); - string new_file_path_str = file_path_str.substr(0, slash_pos) + "/" + - new_file_name_stream.str(); - - OLAP_LOG_INFO("change file name %s to %s", - file_path_str.c_str(), new_file_path_str.c_str()); - // Change file name to new one - boost::filesystem::path new_file_path(new_file_path_str); - boost::filesystem::rename(*file_path, new_file_path); - } - } - - // Load olap - if (status_code == TStatusCode::OK) { - OLAPStatus load_header_status = - worker_pool_this->_command_executor->load_header( - local_shard_root_path, - restore_request.tablet_id, - restore_request.schema_hash); - if (load_header_status != OLAP_SUCCESS) { - OLAP_LOG_WARNING("load header failed. local_shard_root_path: %s, tablet_id: %d " - "schema_hash: %d, status: %d, signature: %ld", - local_shard_root_path.c_str(), restore_request.tablet_id, - restore_request.schema_hash, load_header_status, - agent_task_req.signature); - error_msgs.push_back("load header failed."); - status_code = TStatusCode::RUNTIME_ERROR;; - } - } - - // Get tablets info - vector finish_tablet_infos; - if (status_code == TStatusCode::OK) { - TTabletInfo tablet_info; - AgentStatus get_tablet_info_status = worker_pool_this->_get_tablet_info( - restore_request.tablet_id, - restore_request.schema_hash, - agent_task_req.signature, - &tablet_info); - - if (get_tablet_info_status != PALO_SUCCESS) { - OLAP_LOG_WARNING("Restore success, but get new tablet info failed." - "tablet_id: %ld, schema_hash: %ld, signature: %ld.", - restore_request.tablet_id, restore_request.schema_hash, - agent_task_req.signature); - } else { - finish_tablet_infos.push_back(tablet_info); - } + OLAP_LOG_WARNING("download failed. job id: %ld, msg: %s", + download_request.job_id, + status.get_error_msg().c_str()); + error_msgs.push_back(status.get_error_msg()); } task_status.__set_status_code(status_code); task_status.__set_error_msgs(error_msgs); TFinishTaskRequest finish_task_request; - finish_task_request.__set_finish_tablet_infos(finish_tablet_infos); finish_task_request.__set_backend(worker_pool_this->_backend); finish_task_request.__set_task_type(agent_task_req.task_type); finish_task_request.__set_signature(agent_task_req.signature); finish_task_request.__set_task_status(task_status); + finish_task_request.__set_downloaded_tablet_ids(downloaded_tablet_ids); worker_pool_this->_finish_task(finish_task_request); worker_pool_this->_remove_task_info(agent_task_req.task_type, agent_task_req.signature, ""); + + OLAP_LOG_INFO("finished download task, signature: %ld, job id: %d", + agent_task_req.signature, download_request.job_id); #ifndef BE_TEST } #endif @@ -1958,6 +1819,7 @@ void* TaskWorkerPool::_make_snapshot_thread_callback(void* arg_this) { TStatus task_status; string snapshot_path; + std::vector snapshot_files; OLAPStatus make_snapshot_status = worker_pool_this->_command_executor->make_snapshot( snapshot_request, &snapshot_path); if (make_snapshot_status != OLAP_SUCCESS) { @@ -1975,6 +1837,26 @@ void* TaskWorkerPool::_make_snapshot_thread_callback(void* arg_this) { snapshot_request.tablet_id, snapshot_request.schema_hash, snapshot_request.version, snapshot_request.version_hash, snapshot_path.c_str()); + + if (snapshot_request.__isset.list_files) { + // list and save all snapshot files + // snapshot_path like: data/snapshot/20180417205230.1 + // we need to add subdir: tablet_id/schema_hash/ + std::stringstream ss; + ss << snapshot_path << "/" << snapshot_request.tablet_id + << "/" << snapshot_request.schema_hash << "/"; + Status st = FileUtils::scan_dir(ss.str(), &snapshot_files); + if (!st.ok()) { + status_code = TStatusCode::RUNTIME_ERROR; + OLAP_LOG_WARNING("make_snapshot failed. tablet_id: %ld, schema_hash: %ld, version: %d," + "version_hash: %ld, list file failed: %s", + snapshot_request.tablet_id, snapshot_request.schema_hash, + snapshot_request.version, snapshot_request.version_hash, + st.get_error_msg().c_str()); + error_msgs.push_back("make_snapshot failed. list file failed: " + + st.get_error_msg()); + } + } } task_status.__set_status_code(status_code); @@ -1985,6 +1867,7 @@ void* TaskWorkerPool::_make_snapshot_thread_callback(void* arg_this) { finish_task_request.__set_task_type(agent_task_req.task_type); finish_task_request.__set_signature(agent_task_req.signature); finish_task_request.__set_snapshot_path(snapshot_path); + finish_task_request.__set_snapshot_files(snapshot_files); finish_task_request.__set_task_status(task_status); worker_pool_this->_finish_task(finish_task_request); @@ -2087,4 +1970,102 @@ AgentStatus TaskWorkerPool::_get_tablet_info( } return status; } + +void* TaskWorkerPool::_move_dir_thread_callback(void* arg_this) { + TaskWorkerPool* worker_pool_this = (TaskWorkerPool*)arg_this; + +#ifndef BE_TEST + while (true) { +#endif + TAgentTaskRequest agent_task_req; + TMoveDirReq move_dir_req; + { + lock_guard worker_thread_lock(worker_pool_this->_worker_thread_lock); + while (worker_pool_this->_tasks.empty()) { + worker_pool_this->_worker_thread_condition_lock.wait(); + } + + agent_task_req = worker_pool_this->_tasks.front(); + move_dir_req = agent_task_req.move_dir_req; + worker_pool_this->_tasks.pop_front(); + } + // Try to register to cgroups_mgr + CgroupsMgr::apply_system_cgroup(); + OLAP_LOG_INFO("get move dir task, signature: %ld, job id: %ld", + agent_task_req.signature, move_dir_req.job_id); + + TStatusCode::type status_code = TStatusCode::OK; + vector error_msgs; + TStatus task_status; + + // TODO: move dir + AgentStatus status = worker_pool_this->_move_dir( + move_dir_req.tablet_id, + move_dir_req.schema_hash, + move_dir_req.src, + move_dir_req.job_id, + true /* TODO */, + &error_msgs); + + if (status != PALO_SUCCESS) { + status_code = TStatusCode::RUNTIME_ERROR; + OLAP_LOG_WARNING("failed to move dir: %s, tablet id: %ld, signature: %ld, job id: %ld", + move_dir_req.src.c_str(), move_dir_req.tablet_id, agent_task_req.signature, + move_dir_req.job_id); + } else { + OLAP_LOG_INFO("finished to move dir: %s, tablet id: %ld, signature: %ld, job id: %ld", + move_dir_req.src.c_str(), move_dir_req.tablet_id, agent_task_req.signature, + move_dir_req.job_id); + } + + task_status.__set_status_code(status_code); + task_status.__set_error_msgs(error_msgs); + + TFinishTaskRequest finish_task_request; + finish_task_request.__set_backend(worker_pool_this->_backend); + finish_task_request.__set_task_type(agent_task_req.task_type); + finish_task_request.__set_signature(agent_task_req.signature); + finish_task_request.__set_task_status(task_status); + + worker_pool_this->_finish_task(finish_task_request); + worker_pool_this->_remove_task_info(agent_task_req.task_type, agent_task_req.signature, ""); + +#ifndef BE_TEST + } +#endif + return (void*)0; +} + +AgentStatus TaskWorkerPool::_move_dir( + const TTabletId tablet_id, + const TSchemaHash schema_hash, + const std::string& src, + int64_t job_id, + bool overwrite, + std::vector* error_msgs) { + + SmartOLAPTable tablet = _command_executor->get_table( + tablet_id, schema_hash); + if (tablet.get() == NULL) { + OLAP_LOG_INFO("failed to get tablet: %ld, schema hash: %d", + tablet_id, schema_hash); + error_msgs->push_back("failed to get tablet"); + return PALO_TASK_REQUEST_ERROR; + } + + std::string dest_tablet_dir = tablet->construct_dir_path(); + + SnapshotLoader* loader = _env->snapshot_loader(); + Status status = loader->move(src, dest_tablet_dir, job_id, overwrite); + + if (!status.ok()) { + OLAP_LOG_WARNING("move failed. job id: %ld, msg: %s", + job_id, status.get_error_msg().c_str()); + error_msgs->push_back(status.get_error_msg()); + return PALO_INTERNAL_ERROR; + } + + return PALO_SUCCESS; +} + } // namespace palo diff --git a/be/src/agent/task_worker_pool.h b/be/src/agent/task_worker_pool.h index 8f2ed61d58..a2531b0835 100644 --- a/be/src/agent/task_worker_pool.h +++ b/be/src/agent/task_worker_pool.h @@ -35,6 +35,8 @@ namespace palo { +class ExecEnv; + class TaskWorkerPool { public: enum TaskWorkerType { @@ -52,15 +54,17 @@ public: REPORT_DISK_STATE, REPORT_OLAP_TABLE, UPLOAD, - RESTORE, + DOWNLOAD, MAKE_SNAPSHOT, - RELEASE_SNAPSHOT + RELEASE_SNAPSHOT, + MOVE }; typedef void* (*CALLBACK_FUNCTION)(void*); TaskWorkerPool( const TaskWorkerType task_worker_type, + ExecEnv* env, const TMasterInfo& master_info); virtual ~TaskWorkerPool(); @@ -95,9 +99,10 @@ private: static void* _report_disk_state_worker_thread_callback(void* arg_this); static void* _report_olap_table_worker_thread_callback(void* arg_this); static void* _upload_worker_thread_callback(void* arg_this); - static void* _restore_worker_thread_callback(void* arg_this); + static void* _download_worker_thread_callback(void* arg_this); static void* _make_snapshot_thread_callback(void* arg_this); static void* _release_snapshot_thread_callback(void* arg_this); + static void* _move_dir_thread_callback(void* arg_this); AgentStatus _clone_copy( const TCloneReq& clone_req, @@ -125,11 +130,20 @@ private: int64_t signature, TTabletInfo* tablet_info); + AgentStatus _move_dir( + const TTabletId tablet_id, + const TSchemaHash schema_hash, + const std::string& src, + int64_t job_id, + bool overwrite, + std::vector* error_msgs); + const TMasterInfo& _master_info; TBackend _backend; AgentUtils* _agent_utils; MasterServerClient* _master_client; CommandExecutor* _command_executor; + ExecEnv* _env; #ifdef BE_TEST AgentServerClient* _agent_client; FileDownloader* _file_downloader_ptr; diff --git a/be/src/agent/topic_subscriber.cpp b/be/src/agent/topic_subscriber.cpp index f2d7161bbc..bd2ee00f49 100644 --- a/be/src/agent/topic_subscriber.cpp +++ b/be/src/agent/topic_subscriber.cpp @@ -41,7 +41,6 @@ void TopicSubscriber::register_listener(TTopicType::type topic_type, TopicListen } void TopicSubscriber::handle_updates(const TAgentPublishRequest& agent_publish_request) { - LOG(INFO) << "Received master's published state, begin to handle"; // Shared lock here in order to avoid updates in listeners' map boost::shared_lock lock(_listener_mtx); // Currently, not deal with protocol version, the listener should deal with protocol version @@ -56,6 +55,5 @@ void TopicSubscriber::handle_updates(const TAgentPublishRequest& agent_publish_r *topic_update_it); } } - LOG(INFO) << "Handle master's published state finished"; } } // namespace palo diff --git a/be/src/agent/user_resource_listener.cpp b/be/src/agent/user_resource_listener.cpp index 09dd7fb402..a7a8471b01 100644 --- a/be/src/agent/user_resource_listener.cpp +++ b/be/src/agent/user_resource_listener.cpp @@ -49,7 +49,6 @@ void UserResourceListener::handle_update(const TAgentServiceVersion::type& proto if (updates.size() > 0) { int64_t new_version = updates[0].int_value; // Async call to update users resource method - LOG(INFO) << "Latest version for master is " << new_version; std::async(std::launch::async, &UserResourceListener::update_users_resource, this, new_version); @@ -60,8 +59,6 @@ void UserResourceListener::update_users_resource(int64_t new_version) { if (new_version <= _cgroups_mgr.get_cgroups_version()) { return; } - LOG(INFO) << "New version " << new_version - << " is bigger than older version " << _cgroups_mgr.get_cgroups_version(); // Call fe to get latest user resource Status master_status; // using 500ms as default timeout value @@ -78,9 +75,7 @@ void UserResourceListener::update_users_resource(int64_t new_version) { } try { try { - LOG(INFO) << "Call master to get resource"; client->fetchResource(new_fetched_resource); - LOG(INFO) << "Call master to get resource successfully"; } catch (TTransportException& e) { // reopen the client and set timeout to 500ms master_status = client.reopen(500); @@ -102,7 +97,6 @@ void UserResourceListener::update_users_resource(int64_t new_version) { << e.what(); return; } - LOG(INFO) << "Begin to update user's cgroups resource"; _cgroups_mgr.update_local_cgroups(new_fetched_resource); } } diff --git a/be/src/common/config.h b/be/src/common/config.h index 71a6a4137a..caf7bfb2f9 100644 --- a/be/src/common/config.h +++ b/be/src/common/config.h @@ -79,8 +79,8 @@ namespace config { CONF_Int32(check_consistency_worker_count, "1"); // the count of thread to upload CONF_Int32(upload_worker_count, "3"); - // the count of thread to restore - CONF_Int32(restore_worker_count, "3"); + // the count of thread to download + CONF_Int32(download_worker_count, "3"); // the count of thread to make snapshot CONF_Int32(make_snapshot_worker_count, "5"); // the count of thread to release snapshot @@ -364,6 +364,9 @@ namespace config { // Aligement CONF_Int32(FLAGS_MEMORY_MAX_ALIGNMENT, "16"); + + // result buffer cancelled time (unit: second) + CONF_Int32(result_buffer_cancelled_interval_time, "5"); } // namespace config } // namespace palo diff --git a/be/src/exec/CMakeLists.txt b/be/src/exec/CMakeLists.txt index 3283debc98..347da037d0 100644 --- a/be/src/exec/CMakeLists.txt +++ b/be/src/exec/CMakeLists.txt @@ -63,7 +63,7 @@ set(EXEC_FILES mysql_scanner.cpp csv_scan_node.cpp csv_scanner.cpp - spill_sort_node.cc + spill_sort_node.cc union_node.cpp union_node_ir.cpp schema_scanner.cpp diff --git a/be/src/exec/broker_reader.cpp b/be/src/exec/broker_reader.cpp index 66d58abeac..47db215ba6 100644 --- a/be/src/exec/broker_reader.cpp +++ b/be/src/exec/broker_reader.cpp @@ -23,7 +23,6 @@ #include "runtime/broker_mgr.h" #include "runtime/client_cache.h" #include "runtime/exec_env.h" -#include "runtime/runtime_state.h" #include "util/thrift_util.h" namespace palo { @@ -31,12 +30,12 @@ namespace palo { // Broker BrokerReader::BrokerReader( - RuntimeState* state, + ExecEnv* env, const std::vector& broker_addresses, const std::map& properties, const std::string& path, int64_t start_offset) : - _state(state), + _env(env), _addresses(broker_addresses), _properties(properties), _path(path), @@ -51,22 +50,22 @@ BrokerReader::~BrokerReader() { } #ifdef BE_TEST -inline BrokerServiceClientCache* client_cache(RuntimeState* state) { +inline BrokerServiceClientCache* client_cache(ExecEnv* env) { static BrokerServiceClientCache s_client_cache; return &s_client_cache; } -inline const std::string& client_id(RuntimeState* state, const TNetworkAddress& addr) { +inline const std::string& client_id(ExecEnv* env, const TNetworkAddress& addr) { static std::string s_client_id = "palo_unit_test"; return s_client_id; } #else -inline BrokerServiceClientCache* client_cache(RuntimeState* state) { - return state->exec_env()->broker_client_cache(); +inline BrokerServiceClientCache* client_cache(ExecEnv* env) { + return env->broker_client_cache(); } -inline const std::string& client_id(RuntimeState* state, const TNetworkAddress& addr) { - return state->exec_env()->broker_mgr()->get_client_id(addr); +inline const std::string& client_id(ExecEnv* env, const TNetworkAddress& addr) { + return env->broker_mgr()->get_client_id(addr); } #endif @@ -77,13 +76,13 @@ Status BrokerReader::open() { request.__set_version(TBrokerVersion::VERSION_ONE); request.__set_path(_path); request.__set_startOffset(_cur_offset); - request.__set_clientId(client_id(_state, broker_addr)); + request.__set_clientId(client_id(_env, broker_addr)); request.__set_properties(_properties); TBrokerOpenReaderResponse response; try { Status status; - BrokerServiceConnection client(client_cache(_state), broker_addr, 10000, &status); + BrokerServiceConnection client(client_cache(_env), broker_addr, 10000, &status); if (!status.ok()) { LOG(WARNING) << "Create broker client failed. broker=" << broker_addr << ", status=" << status.get_error_msg(); @@ -132,7 +131,7 @@ Status BrokerReader::read(uint8_t* buf, size_t* buf_len, bool* eof) { TBrokerReadResponse response; try { Status status; - BrokerServiceConnection client(client_cache(_state), broker_addr, 10000, &status); + BrokerServiceConnection client(client_cache(_env), broker_addr, 10000, &status); if (!status.ok()) { LOG(WARNING) << "Create broker client failed. broker=" << broker_addr << ", status=" << status.get_error_msg(); @@ -186,8 +185,7 @@ void BrokerReader::close() { TBrokerOperationStatus response; try { Status status; - // 500ms is enough - BrokerServiceConnection client(client_cache(_state), broker_addr, 10000, &status); + BrokerServiceConnection client(client_cache(_env), broker_addr, 10000, &status); if (!status.ok()) { LOG(WARNING) << "Create broker client failed. broker=" << broker_addr << ", status=" << status.get_error_msg(); diff --git a/be/src/exec/broker_reader.h b/be/src/exec/broker_reader.h index f4501a81d5..1d878ef6d3 100644 --- a/be/src/exec/broker_reader.h +++ b/be/src/exec/broker_reader.h @@ -27,7 +27,7 @@ namespace palo { -class RuntimeState; +class ExecEnv; class TBrokerRangeDesc; class TNetworkAddress; class RuntimeState; @@ -35,7 +35,7 @@ class RuntimeState; // Reader of broker file class BrokerReader : public FileReader { public: - BrokerReader(RuntimeState* state, + BrokerReader(ExecEnv* env, const std::vector& broker_addresses, const std::map& properties, const std::string& path, @@ -49,7 +49,7 @@ public: virtual void close() override; private: - RuntimeState* _state; + ExecEnv* _env; const std::vector& _addresses; const std::map& _properties; const std::string& _path; diff --git a/be/src/exec/broker_scanner.cpp b/be/src/exec/broker_scanner.cpp index 1143a68f2a..2bde4a7f58 100644 --- a/be/src/exec/broker_scanner.cpp +++ b/be/src/exec/broker_scanner.cpp @@ -246,7 +246,7 @@ Status BrokerScanner::open_file_reader() { } case TFileType::FILE_BROKER: { BrokerReader* broker_reader = new BrokerReader( - _state, _broker_addresses, _params.properties, range.path, start_offset); + _state->exec_env(), _broker_addresses, _params.properties, range.path, start_offset); RETURN_IF_ERROR(broker_reader->open()); _cur_file_reader = broker_reader; break; diff --git a/be/src/exec/broker_writer.cpp b/be/src/exec/broker_writer.cpp index 4742269165..5511e0febb 100644 --- a/be/src/exec/broker_writer.cpp +++ b/be/src/exec/broker_writer.cpp @@ -23,18 +23,17 @@ #include "runtime/broker_mgr.h" #include "runtime/client_cache.h" #include "runtime/exec_env.h" -#include "runtime/runtime_state.h" #include "util/thrift_util.h" namespace palo { BrokerWriter::BrokerWriter( - RuntimeState* state, + ExecEnv* env, const std::vector& broker_addresses, const std::map& properties, const std::string& path, int64_t start_offset) : - _state(state), + _env(env), _addresses(broker_addresses), _properties(properties), _path(path), @@ -48,22 +47,22 @@ BrokerWriter::~BrokerWriter() { } #ifdef BE_TEST -inline BrokerServiceClientCache* client_cache(RuntimeState* state) { +inline BrokerServiceClientCache* client_cache(ExecEnv* env) { static BrokerServiceClientCache s_client_cache; return &s_client_cache; } -inline const std::string& client_id(RuntimeState* state, const TNetworkAddress& addr) { +inline const std::string& client_id(ExecEnv* env, const TNetworkAddress& addr) { static std::string s_client_id = "palo_unit_test"; return s_client_id; } #else -inline BrokerServiceClientCache* client_cache(RuntimeState* state) { - return state->exec_env()->broker_client_cache(); +inline BrokerServiceClientCache* client_cache(ExecEnv* env) { + return env->broker_client_cache(); } -inline const std::string& client_id(RuntimeState* state, const TNetworkAddress& addr) { - return state->exec_env()->broker_mgr()->get_client_id(addr); +inline const std::string& client_id(ExecEnv* env, const TNetworkAddress& addr) { + return env->broker_mgr()->get_client_id(addr); } #endif @@ -74,7 +73,7 @@ Status BrokerWriter::open() { request.__set_version(TBrokerVersion::VERSION_ONE); request.__set_path(_path); request.__set_openMode(TBrokerOpenMode::APPEND); - request.__set_clientId(client_id(_state, broker_addr)); + request.__set_clientId(client_id(_env, broker_addr)); request.__set_properties(_properties); VLOG_ROW << "debug: send broker open writer request: " @@ -83,7 +82,7 @@ Status BrokerWriter::open() { TBrokerOpenWriterResponse response; try { Status status; - BrokerServiceConnection client(client_cache(_state), broker_addr, 10000, &status); + BrokerServiceConnection client(client_cache(_env), broker_addr, 10000, &status); if (!status.ok()) { LOG(WARNING) << "Create broker writer client failed. " << "broker=" << broker_addr @@ -138,8 +137,7 @@ Status BrokerWriter::write(const uint8_t* buf, size_t buf_len, size_t* written_l TBrokerOperationStatus response; try { Status status; - // we make timeout to be 10s, to avoid error in Network jitter scenarios. - BrokerServiceConnection client(client_cache(_state), broker_addr, 10000, &status); + BrokerServiceConnection client(client_cache(_env), broker_addr, 10000, &status); if (!status.ok()) { LOG(WARNING) << "Create broker write client failed. " << "broker=" << broker_addr @@ -198,7 +196,7 @@ void BrokerWriter::close() { TBrokerOperationStatus response; try { Status status; - BrokerServiceConnection client(client_cache(_state), broker_addr, 10000, &status); + BrokerServiceConnection client(client_cache(_env), broker_addr, 10000, &status); if (!status.ok()) { LOG(WARNING) << "Create broker write client failed. broker=" << broker_addr << ", status=" << status.get_error_msg(); diff --git a/be/src/exec/broker_writer.h b/be/src/exec/broker_writer.h index a89abccef4..f8e2c61c47 100644 --- a/be/src/exec/broker_writer.h +++ b/be/src/exec/broker_writer.h @@ -28,15 +28,14 @@ namespace palo { -class RuntimeState; +class ExecEnv; class TBrokerRangeDesc; class TNetworkAddress; -class RuntimeState; // Reader of broker file class BrokerWriter : public FileWriter { public: - BrokerWriter(RuntimeState* state, + BrokerWriter(ExecEnv* env, const std::vector& broker_addresses, const std::map& properties, const std::string& dir, @@ -50,7 +49,7 @@ public: virtual void close() override; private: - RuntimeState* _state; + ExecEnv* _env; const std::vector& _addresses; const std::map& _properties; std::string _path; diff --git a/be/src/exec/mysql_scan_node.cpp b/be/src/exec/mysql_scan_node.cpp index fbb6a808ca..121ab3477b 100644 --- a/be/src/exec/mysql_scan_node.cpp +++ b/be/src/exec/mysql_scan_node.cpp @@ -35,10 +35,7 @@ MysqlScanNode::MysqlScanNode(ObjectPool* pool, const TPlanNode& tnode, _tuple_id(tnode.mysql_scan_node.tuple_id), _columns(tnode.mysql_scan_node.columns), _filters(tnode.mysql_scan_node.filters), - _tuple_desc(NULL) { - //_tuple_pool(NULL), - //_mysql_scanner(NULL) { - //_text_converter(NULL) { + _tuple_desc(nullptr) { } MysqlScanNode::~MysqlScanNode() { diff --git a/be/src/exec/mysql_scan_node.h b/be/src/exec/mysql_scan_node.h index 8f0ec7bec2..b608d23c26 100644 --- a/be/src/exec/mysql_scan_node.h +++ b/be/src/exec/mysql_scan_node.h @@ -86,7 +86,7 @@ private: // Helper class for converting text to other types; std::unique_ptr _text_converter; // Current tuple. - Tuple* _tuple; + Tuple* _tuple = nullptr; }; } diff --git a/be/src/exec/schema_scan_node.cpp b/be/src/exec/schema_scan_node.cpp index 87076b4292..c5bd743c6a 100644 --- a/be/src/exec/schema_scan_node.cpp +++ b/be/src/exec/schema_scan_node.cpp @@ -77,6 +77,10 @@ Status SchemaScanNode::init(const TPlanNode& tnode, RuntimeState* state) { _scanner_param.ip = _pool->add(new std::string(tnode.schema_scan_node.ip)); } + if (tnode.schema_scan_node.__isset.user_ip) { + _scanner_param.user_ip = _pool->add(new std::string(tnode.schema_scan_node.user_ip)); + } + if (tnode.schema_scan_node.__isset.port) { _scanner_param.port = tnode.schema_scan_node.port; } diff --git a/be/src/exec/schema_scanner.h b/be/src/exec/schema_scanner.h index dd50e9d2f5..14d8a3bf34 100644 --- a/be/src/exec/schema_scanner.h +++ b/be/src/exec/schema_scanner.h @@ -41,12 +41,13 @@ struct SchemaScannerParam { const std::string* table; const std::string* wild; const std::string* user; + const std::string* user_ip; const std::string* ip; int32_t port; int64_t thread_id; SchemaScannerParam() - : db(NULL), table(NULL), wild(NULL), user(NULL), ip(NULL), port(0) { } + : db(NULL), table(NULL), wild(NULL), user(NULL), user_ip(NULL), ip(NULL), port(0) { } }; // virtual scanner for all schema table diff --git a/be/src/exec/schema_scanner/schema_columns_scanner.cpp b/be/src/exec/schema_scanner/schema_columns_scanner.cpp index 32d28d13af..f0c7a66a22 100644 --- a/be/src/exec/schema_scanner/schema_columns_scanner.cpp +++ b/be/src/exec/schema_scanner/schema_columns_scanner.cpp @@ -274,6 +274,9 @@ Status SchemaColumnsScanner::get_new_desc() { if (NULL != _param->user) { desc_params.__set_user(*(_param->user)); } + if (NULL != _param->user_ip) { + desc_params.__set_user_ip(*(_param->user_ip)); + } if (NULL != _param->ip && 0 != _param->port) { RETURN_IF_ERROR(FrontendHelper::describe_table(*(_param->ip), @@ -295,6 +298,9 @@ Status SchemaColumnsScanner::get_new_table() { if (NULL != _param->user) { table_params.__set_user(*(_param->user)); } + if (NULL != _param->user_ip) { + table_params.__set_user_ip(*(_param->user_ip)); + } if (NULL != _param->ip && 0 != _param->port) { RETURN_IF_ERROR(FrontendHelper::get_table_names(*(_param->ip), diff --git a/be/src/exec/schema_scanner/schema_schemata_scanner.cpp b/be/src/exec/schema_scanner/schema_schemata_scanner.cpp index eb38b7103a..b474236264 100644 --- a/be/src/exec/schema_scanner/schema_schemata_scanner.cpp +++ b/be/src/exec/schema_scanner/schema_schemata_scanner.cpp @@ -53,6 +53,9 @@ Status SchemaSchemataScanner::start(RuntimeState *state) { if (NULL != _param->user) { db_params.__set_user(*(_param->user)); } + if (NULL != _param->user_ip) { + db_params.__set_user_ip(*(_param->user_ip)); + } if (NULL != _param->ip && 0 != _param->port) { RETURN_IF_ERROR(FrontendHelper::get_db_names(*(_param->ip), _param->port, db_params, &_db_result)); diff --git a/be/src/exec/schema_scanner/schema_tables_scanner.cpp b/be/src/exec/schema_scanner/schema_tables_scanner.cpp index f4581a5a88..74c9d8a0b8 100644 --- a/be/src/exec/schema_scanner/schema_tables_scanner.cpp +++ b/be/src/exec/schema_scanner/schema_tables_scanner.cpp @@ -73,6 +73,9 @@ Status SchemaTablesScanner::start(RuntimeState *state) { if (NULL != _param->user) { db_params.__set_user(*(_param->user)); } + if (NULL != _param->user_ip) { + db_params.__set_user_ip(*(_param->user_ip)); + } if (NULL != _param->ip && 0 != _param->port) { RETURN_IF_ERROR(FrontendHelper::get_db_names(*(_param->ip), @@ -227,6 +230,9 @@ Status SchemaTablesScanner::get_new_table() { if (NULL != _param->user) { table_params.__set_user(*(_param->user)); } + if (NULL != _param->user_ip) { + table_params.__set_user_ip(*(_param->user_ip)); + } if (NULL != _param->ip && 0 != _param->port) { RETURN_IF_ERROR(FrontendHelper::list_table_status(*(_param->ip), diff --git a/be/src/exprs/aggregate_functions.cpp b/be/src/exprs/aggregate_functions.cpp index 9a4dd6017f..baaf2981f8 100644 --- a/be/src/exprs/aggregate_functions.cpp +++ b/be/src/exprs/aggregate_functions.cpp @@ -1053,10 +1053,9 @@ int64_t AggregateFunctions::hll_algorithm(const palo_udf::StringVal& src) { double tmp = 0.f; // according to HerperLogLog current correction, if E is cardinal // E =< num_streams * 2.5 , LC has higher accuracy. - // num_streams * 2.5 < E =< 2 ^ 32 / 30 , HerperLogLog has higher accuracy. - // E > 2 ^ 32 / 30 , estimate = -tmp * log(1 - estimate / tmp); + // num_streams * 2.5 < E , HerperLogLog has higher accuracy. // Generally , we can use HerperLogLog to produce value as E. - if (num_zero_registers != 0) { + if (estimate <= num_streams * 2.5 && num_zero_registers != 0) { // Estimated cardinality is too low. Hll is too inaccurate here, instead use // linear counting. estimate = num_streams * log(static_cast(num_streams) / num_zero_registers); @@ -1069,8 +1068,6 @@ int64_t AggregateFunctions::hll_algorithm(const palo_udf::StringVal& src) { - 5.2921 * 1.0e-3 * estimate + 83.3216; estimate -= estimate * (bias / 100); - } else if (estimate > (tmp = std::pow(2, 32) / 30)) { - estimate = -tmp * log(1 - estimate / tmp); } return (int64_t)(estimate + 0.5); } diff --git a/be/src/exprs/encryption_functions.h b/be/src/exprs/encryption_functions.h index 6622c6f463..bb57e7b32c 100644 --- a/be/src/exprs/encryption_functions.h +++ b/be/src/exprs/encryption_functions.h @@ -1,21 +1,22 @@ -/**************************************************************** - * - * The author of this software is David M. Gay. - * - * Copyright (c) 1991, 2000, 2001 by Lucent Technologies. - * - * Permission to use, copy, modify, and distribute this software for any - * purpose without fee is hereby granted, provided that this entire notice - * is included in all copies of any software which is or includes a copy - * or modification of this software and in all copies of the supporting - * documentation for such software. - * - * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED - * WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR LUCENT MAKES ANY - * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY - * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. - * - ***************************************************************/ +// Modifications copyright (C) 2017, Baidu.com, Inc. +// Copyright 2017 The Apache Software Foundation + +// 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. #ifndef BDG_PALO_BE_SRC_QUERY_EXPRS_ENCRYPTION_FUNCTIONS_H #define BDG_PALO_BE_SRC_QUERY_EXPRS_ENCRYPTION_FUNCTIONS_H diff --git a/be/src/exprs/hybird_set.h b/be/src/exprs/hybird_set.h index c7994362f5..28a3451f0e 100644 --- a/be/src/exprs/hybird_set.h +++ b/be/src/exprs/hybird_set.h @@ -21,6 +21,7 @@ #ifndef BDG_PALO_BE_SRC_QUERY_EXPRS_HYBIRD_SET_H #define BDG_PALO_BE_SRC_QUERY_EXPRS_HYBIRD_SET_H +#include #include #include "common/status.h" #include "common/object_pool.h" @@ -72,7 +73,7 @@ public: if (sizeof(T) >= 16) { // for largeint, it will core dump with no memcpy T value; - memcpy(&value, data, sizeof(T)); + memcpy(&value, data, sizeof(T)); _set.insert(value); } else { _set.insert(*reinterpret_cast(data)); diff --git a/be/src/http/action/mini_load.cpp b/be/src/http/action/mini_load.cpp index e5a16f6cba..52fce714a7 100644 --- a/be/src/http/action/mini_load.cpp +++ b/be/src/http/action/mini_load.cpp @@ -445,6 +445,7 @@ Status MiniLoadAction::generate_check_load_req( check_load_req->__set_cluster(cluster); } check_load_req->db = http_req->param(DB_KEY); + check_load_req->__set_tbl(http_req->param(TABLE_KEY)); if (http_req->param(SUB_LABEL_KEY).empty()) { check_load_req->__set_label(http_req->param(LABEL_KEY)); diff --git a/be/src/olap/aggregate_func.h b/be/src/olap/aggregate_func.h index 2743eeea43..64a00eb5a9 100644 --- a/be/src/olap/aggregate_func.h +++ b/be/src/olap/aggregate_func.h @@ -226,9 +226,9 @@ struct AggregateFuncTraits(hll_ptr)); std::map index_to_value; if (context->has_sparse_or_full || - context->hash64_set.size() > HLL_EXPLICLIT_INT64_NUM) { + context->hash64_set->size() > HLL_EXPLICLIT_INT64_NUM) { HllSetHelper::set_max_register(context->registers, HLL_REGISTERS_COUNT, - context->hash64_set); + *(context->hash64_set)); for (int i = 0; i < HLL_REGISTERS_COUNT; i++) { if (context->registers[i] != 0) { index_to_value[i] = context->registers[i]; @@ -248,14 +248,14 @@ struct AggregateFuncTraits 0) { // sparse set HllSetHelper::set_sparse(slice->data, index_to_value, result_len); - } else if (context->hash64_set.size() > 0) { + } else if (context->hash64_set->size() > 0) { // expliclit set - HllSetHelper::set_expliclit(slice->data, context->hash64_set, result_len); + HllSetHelper::set_expliclit(slice->data, *(context->hash64_set), result_len); } slice->size = result_len & 0xffff; - HllSetHelper::init_context(context); + delete context->hash64_set; } }; diff --git a/be/src/olap/column_file/segment_reader.cpp b/be/src/olap/column_file/segment_reader.cpp index a20db5507c..0eb7c4dd92 100644 --- a/be/src/olap/column_file/segment_reader.cpp +++ b/be/src/olap/column_file/segment_reader.cpp @@ -765,8 +765,13 @@ OLAPStatus SegmentReader::_read_all_data_streams(size_t* buffer_size) { continue; } + if (_include_columns.find(unique_column_id) == _include_columns.end() && + _include_bf_columns.find(unique_column_id) == _include_bf_columns.end()) { + continue; + } + if (message.kind() == StreamInfoMessage::ROW_INDEX || - message.kind() == StreamInfoMessage::BLOOM_FILTER) { + message.kind() == StreamInfoMessage::BLOOM_FILTER) { continue; } diff --git a/be/src/olap/column_file/segment_reader.h b/be/src/olap/column_file/segment_reader.h index 4197ec939a..9d6e88f5e6 100644 --- a/be/src/olap/column_file/segment_reader.h +++ b/be/src/olap/column_file/segment_reader.h @@ -311,7 +311,6 @@ private: std::vector _column_readers; // 实际的数据读取器 std::vector _column_indices; // 保存column的index - UniqueIdSet _segment_columns; UniqueIdSet _include_columns; // 用于判断该列是不是被包含 UniqueIdSet _load_bf_columns; UniqueIdSet _include_bf_columns; diff --git a/be/src/olap/command_executor.cpp b/be/src/olap/command_executor.cpp index 96af16ce0f..62390fb008 100755 --- a/be/src/olap/command_executor.cpp +++ b/be/src/olap/command_executor.cpp @@ -295,13 +295,14 @@ OLAPStatus CommandExecutor::create_table(const TCreateTabletReq& request) { break; } - // 6. Create init version if request.version set - if (request.__isset.version) { - res = _create_init_version(olap_table_ptr, request); - if (res != OLAP_SUCCESS) { - OLAP_LOG_WARNING("fail to create initial version for table. [res=%d]", res); - } + // 6. Create init version if this is not a restore mode replica and request.version is set + // bool in_restore_mode = request.__isset.in_restore_mode && request.in_restore_mode; + // if (!in_restore_mode && request.__isset.version) { + res = _create_init_version(olap_table_ptr, request); + if (res != OLAP_SUCCESS) { + OLAP_LOG_WARNING("fail to create initial version for table. [res=%d]", res); } + // } } while (0); // 7. clear environment diff --git a/be/src/olap/command_executor.h b/be/src/olap/command_executor.h index 0c0027b55b..d3bad72c4a 100644 --- a/be/src/olap/command_executor.h +++ b/be/src/olap/command_executor.h @@ -123,6 +123,7 @@ public: TTabletId tablet_id, TSchemaHash schema_hash, std::string* snapshot_path); + virtual OLAPStatus make_snapshot( const TSnapshotRequest& request, std::string* snapshot_path); diff --git a/be/src/olap/field.h b/be/src/olap/field.h index bf0efdcfee..8c7ae50ba5 100644 --- a/be/src/olap/field.h +++ b/be/src/olap/field.h @@ -202,6 +202,8 @@ inline void Field::agg_init(char* dest, const char* src) { if (OLAP_LIKELY(_type != OLAP_FIELD_TYPE_HLL)) { copy_without_pool(dest, src); } else { + bool is_null = *reinterpret_cast(src); + *reinterpret_cast(dest) = is_null; StringSlice* slice = reinterpret_cast(dest + 1); size_t hll_ptr = *(size_t*)(slice->data - sizeof(HllContext*)); HllContext* context = (reinterpret_cast(hll_ptr)); diff --git a/be/src/olap/file_helper.cpp b/be/src/olap/file_helper.cpp index 79fcac40dd..b3d0aa7eb1 100644 --- a/be/src/olap/file_helper.cpp +++ b/be/src/olap/file_helper.cpp @@ -160,6 +160,8 @@ OLAPStatus FileHandler::close() { } } + OLAP_LOG_DEBUG("finished to close file. [file_name='%s' fd=%d]", + _file_name.c_str(), _fd); _fd = -1; _file_name = ""; _wr_length = 0; diff --git a/be/src/olap/hll.cpp b/be/src/olap/hll.cpp index 39474ffc9a..78795c696c 100644 --- a/be/src/olap/hll.cpp +++ b/be/src/olap/hll.cpp @@ -163,7 +163,7 @@ void HllSetHelper::set_sparse( void HllSetHelper::set_expliclit(char* result, const std::set& hash_value_set, int& len) { result[0] = HLL_DATA_EXPLICIT; - result[1] = (HllSetResolver::ExpliclitLengthValueType)hash_value_set.size(); + result[1] = (HllSetResolver::ExpliclitLengthValueType)(hash_value_set.size()); len = sizeof(HllSetResolver::SetTypeValueType) + sizeof(HllSetResolver::ExpliclitLengthValueType); char* write_pos = result + len; @@ -212,7 +212,7 @@ void HllSetHelper::fill_set(const char* data, HllContext* context) { resolver.parse(); if (resolver.get_hll_data_type() == HLL_DATA_EXPLICIT) { // expliclit set - resolver.fill_hash64_set(&(context->hash64_set)); + resolver.fill_hash64_set(context->hash64_set); } else if (resolver.get_hll_data_type() != HLL_DATA_EMPTY) { // full or sparse context->has_sparse_or_full = true; @@ -222,7 +222,7 @@ void HllSetHelper::fill_set(const char* data, HllContext* context) { void HllSetHelper::init_context(HllContext* context) { memset(context->registers, 0, HLL_REGISTERS_COUNT); - context->hash64_set.clear(); + context->hash64_set = new std::set(); context->has_value = false; context->has_sparse_or_full = false; } diff --git a/be/src/olap/hll.h b/be/src/olap/hll.h index 1e09f593f5..133a9eef7e 100644 --- a/be/src/olap/hll.h +++ b/be/src/olap/hll.h @@ -37,7 +37,7 @@ struct HllContext { bool has_value; bool has_sparse_or_full; char registers[HLL_REGISTERS_COUNT]; - std::set hash64_set; + std::set* hash64_set = nullptr; }; // help parse hll set diff --git a/be/src/olap/olap_engine.cpp b/be/src/olap/olap_engine.cpp index 795007b2ba..2e65129f0b 100644 --- a/be/src/olap/olap_engine.cpp +++ b/be/src/olap/olap_engine.cpp @@ -124,7 +124,8 @@ OLAPStatus OLAPEngine::_load_tables(const string& tablet_root_path) { } OLAPStatus OLAPEngine::load_one_tablet( - TTabletId tablet_id, SchemaHash schema_hash, const string& schema_hash_path) { + TTabletId tablet_id, SchemaHash schema_hash, const string& schema_hash_path, + bool force) { stringstream header_name_stream; header_name_stream << schema_hash_path << "/" << tablet_id << ".hdr"; string header_path = header_name_stream.str(); @@ -143,7 +144,6 @@ OLAPStatus OLAPEngine::load_one_tablet( move_to_trash(boost_schema_hash_path, boost_schema_hash_path); return OLAP_ERR_ENGINE_LOAD_INDEX_TABLE_ERROR; } - if (olap_table->latest_version() == NULL && !olap_table->is_schema_changing()) { OLAP_LOG_WARNING("tablet not in schema change state without delta is invalid. " "[header_path=%s]", @@ -156,7 +156,7 @@ OLAPStatus OLAPEngine::load_one_tablet( // 这里不需要SAFE_DELETE(olap_table),因为olap_table指针已经在add_table中托管到smart pointer中 OLAPStatus res = OLAP_SUCCESS; string table_name = olap_table->full_name(); - res = add_table(tablet_id, schema_hash, olap_table); + res = add_table(tablet_id, schema_hash, olap_table, force); if (res != OLAP_SUCCESS) { // 插入已经存在的table时返回成功 if (res == OLAP_ERR_ENGINE_INSERT_EXISTS_TABLE) { @@ -386,10 +386,11 @@ bool OLAPEngine::check_tablet_id_exist(TTabletId tablet_id) { return is_exist; } -OLAPStatus OLAPEngine::add_table(TTabletId tablet_id, SchemaHash schema_hash, OLAPTable* table) { +OLAPStatus OLAPEngine::add_table(TTabletId tablet_id, SchemaHash schema_hash, + OLAPTable* table, bool force) { OLAPStatus res = OLAP_SUCCESS; - OLAP_LOG_DEBUG("begin to add olap table to OLAPEngine. [tablet_id=%ld schema_hash=%d]", - tablet_id, schema_hash); + OLAP_LOG_DEBUG("begin to add olap table to OLAPEngine. [tablet_id=%ld schema_hash=%d], force: %d", + tablet_id, schema_hash, force); _tablet_map_lock.wrlock(); SmartOLAPTable smart_table(table, OLAPTableDestruction); @@ -412,10 +413,12 @@ OLAPStatus OLAPEngine::add_table(TTabletId tablet_id, SchemaHash schema_hash, OL } _tablet_map_lock.unlock(); - if (table_item->header_file_name() == smart_table->header_file_name()) { - OLAP_LOG_WARNING("add the same tablet twice! [tablet_id=%ld schema_hash=%d]", - tablet_id, schema_hash); - return OLAP_ERR_ENGINE_INSERT_EXISTS_TABLE; + if (!force) { + if (table_item->header_file_name() == smart_table->header_file_name()) { + OLAP_LOG_WARNING("add the same tablet twice! [tablet_id=%ld schema_hash=%d]", + tablet_id, schema_hash); + return OLAP_ERR_ENGINE_INSERT_EXISTS_TABLE; + } } table_item->obtain_header_rdlock(); @@ -425,9 +428,19 @@ OLAPStatus OLAPEngine::add_table(TTabletId tablet_id, SchemaHash schema_hash, OL int32_t new_version = smart_table->latest_version()->end_version(); table_item->release_header_lock(); - if (new_version > old_version - || (new_version == old_version && new_time > old_time)) { - drop_table(tablet_id, schema_hash); + /* + * In restore process, we replace all origin files in tablet dir with + * the downloaded snapshot files. Than we try to reload tablet header. + * force == true means we forcibly replace the OLAPTable in _tablet_map + * with the new one. But if we do so, the files in the tablet dir will be + * dropped when the origin OLAPTable deconstruct. + * So we set keep_files == true to not delete files when the + * origin OLAPTable deconstruct. + */ + bool keep_files = force ? true : false; + if (force || (new_version > old_version + || (new_version == old_version && new_time > old_time))) { + drop_table(tablet_id, schema_hash, keep_files); _tablet_map_lock.wrlock(); _tablet_map[tablet_id].table_arr.push_back(smart_table); _tablet_map[tablet_id].table_arr.sort(_sort_table_by_create_time); @@ -436,9 +449,9 @@ OLAPStatus OLAPEngine::add_table(TTabletId tablet_id, SchemaHash schema_hash, OL smart_table->mark_dropped(); res = OLAP_ERR_ENGINE_INSERT_EXISTS_TABLE; } - OLAP_LOG_WARNING("add duplicated table. [res=%d tablet_id=%ld schema_hash=%d " + OLAP_LOG_WARNING("add duplicated table. force: %d, [res=%d tablet_id=%ld schema_hash=%d " "old_version=%d new_version=%d old_time=%ld new_time=%ld]", - res, tablet_id, schema_hash, + force, res, tablet_id, schema_hash, old_version, new_version, old_time, new_time); return res; @@ -452,7 +465,8 @@ OLAPStatus OLAPEngine::add_table(TTabletId tablet_id, SchemaHash schema_hash, OL // base table cannot be dropped; // b. other cases: // drop specified table and clear schema change info. -OLAPStatus OLAPEngine::drop_table(TTabletId tablet_id, SchemaHash schema_hash) { +OLAPStatus OLAPEngine::drop_table( + TTabletId tablet_id, SchemaHash schema_hash, bool keep_files) { OLAP_LOG_INFO("begin to drop olap table. [tablet_id=%ld]", tablet_id); OLAPStatus res = OLAP_SUCCESS; @@ -478,7 +492,7 @@ OLAPStatus OLAPEngine::drop_table(TTabletId tablet_id, SchemaHash schema_hash) { // Drop table directly when not in schema change if (!ret) { - return _drop_table_directly(tablet_id, schema_hash); + return _drop_table_directly(tablet_id, schema_hash, keep_files); } // Check table is in schema change or not, is base table or not @@ -496,7 +510,7 @@ OLAPStatus OLAPEngine::drop_table(TTabletId tablet_id, SchemaHash schema_hash) { OLAP_LOG_WARNING("drop table directly when related table not found. " "[tablet_id=%ld schema_hash=%d]", related_tablet_id, related_schema_hash); - return _drop_table_directly(tablet_id, schema_hash); + return _drop_table_directly(tablet_id, schema_hash, keep_files); } if (dropped_table->creation_time() < related_table->creation_time()) { @@ -519,7 +533,7 @@ OLAPStatus OLAPEngine::drop_table(TTabletId tablet_id, SchemaHash schema_hash) { res, related_table->full_name().c_str()); } - res = _drop_table_directly(tablet_id, schema_hash); + res = _drop_table_directly(tablet_id, schema_hash, keep_files); if (res != OLAP_SUCCESS) { OLAP_LOG_WARNING("fail to drop table which in schema change. [table=%s]", dropped_table->full_name().c_str()); @@ -530,7 +544,8 @@ OLAPStatus OLAPEngine::drop_table(TTabletId tablet_id, SchemaHash schema_hash) { return res; } -OLAPStatus OLAPEngine::_drop_table_directly(TTabletId tablet_id, SchemaHash schema_hash) { +OLAPStatus OLAPEngine::_drop_table_directly( + TTabletId tablet_id, SchemaHash schema_hash, bool keep_files) { OLAPStatus res = OLAP_SUCCESS; _tablet_map_lock.wrlock(); @@ -545,7 +560,9 @@ OLAPStatus OLAPEngine::_drop_table_directly(TTabletId tablet_id, SchemaHash sche for (list::iterator it = _tablet_map[tablet_id].table_arr.begin(); it != _tablet_map[tablet_id].table_arr.end();) { if ((*it)->equal(tablet_id, schema_hash)) { - (*it)->mark_dropped(); + if (!keep_files) { + (*it)->mark_dropped(); + } it = _tablet_map[tablet_id].table_arr.erase(it); } else { ++it; @@ -859,6 +876,7 @@ OLAPStatus OLAPEngine::report_all_tablets_info( } } + tablet_info.__set_version_count(olap_table->file_version_size()); tablet.tablet_infos.push_back(tablet_info); } @@ -1403,6 +1421,11 @@ OLAPStatus OLAPEngine::_create_new_table_header_file( return OLAP_ERR_INPUT_PARAMETER_ERROR; } + // set restore mode + if (request.__isset.in_restore_mode && request.in_restore_mode) { + header.set_in_restore_mode(true); + } + // save header file header.set_creation_time(time(NULL)); header.set_cumulative_layer_point(-1); diff --git a/be/src/olap/olap_engine.h b/be/src/olap/olap_engine.h index 66faba0dc9..46db28d31b 100644 --- a/be/src/olap/olap_engine.h +++ b/be/src/olap/olap_engine.h @@ -66,11 +66,13 @@ public: const SmartOLAPTable ref_olap_table); // Add a table pointer to OLAPEngine + // If force, drop the existing table add this new one // // Return OLAP_SUCCESS, if run ok // OLAP_ERR_TABLE_INSERT_DUPLICATION_ERROR, if find duplication // OLAP_ERR_NOT_INITED, if not inited - OLAPStatus add_table(TTabletId tablet_id, SchemaHash schema_hash, OLAPTable* table); + OLAPStatus add_table(TTabletId tablet_id, SchemaHash schema_hash, + OLAPTable* table, bool force = false); // Add empty data for OLAPTable // @@ -80,14 +82,16 @@ public: Version version, VersionHash version_hash); // Drop a table by description - // + // If set keep_files == true, files will NOT be deleted when deconstruction. // Return OLAP_SUCCESS, if run ok // OLAP_ERR_TABLE_DELETE_NOEXIST_ERROR, if table not exist // OLAP_ERR_NOT_INITED, if not inited - OLAPStatus drop_table(TTabletId tablet_id, SchemaHash schema_hash); + OLAPStatus drop_table( + TTabletId tablet_id, SchemaHash schema_hash, bool keep_files = false); // Drop table directly with check schema change info. - OLAPStatus _drop_table_directly(TTabletId tablet_id, TSchemaHash schema_hash); + OLAPStatus _drop_table_directly( + TTabletId tablet_id, TSchemaHash schema_hash, bool keep_files = false); OLAPStatus drop_tables_on_error_root_path(const std::vector& table_info_vec); @@ -124,7 +128,8 @@ public: OLAPStatus load_one_tablet(TTabletId tablet_id, SchemaHash schema_hash, - const std::string& schema_hash_path); + const std::string& schema_hash_path, + bool force = false); Cache* index_stream_lru_cache() { return _index_stream_lru_cache; diff --git a/be/src/olap/olap_header.cpp b/be/src/olap/olap_header.cpp index 8086f56e42..f81b261fb1 100644 --- a/be/src/olap/olap_header.cpp +++ b/be/src/olap/olap_header.cpp @@ -427,6 +427,21 @@ const FileVersionMessage* OLAPHeader::get_latest_version() const { return max_version; } +const FileVersionMessage* OLAPHeader::get_base_version() const { + if (file_version_size() == 0) { + return NULL; + } + + const FileVersionMessage* base_version = NULL; + for (int i = 0; i < file_version_size(); ++i) { + if (file_version(i).start_version() == 0) { + base_version = &file_version(i); + break; + } + } + return base_version; +} + const uint32_t OLAPHeader::get_compaction_nice_estimate() const{ uint32_t nice = 0; bool base_version_exists = false; diff --git a/be/src/olap/olap_header.h b/be/src/olap/olap_header.h index 7bc732f88b..62c8c01709 100644 --- a/be/src/olap/olap_header.h +++ b/be/src/olap/olap_header.h @@ -86,6 +86,7 @@ public: const FileVersionMessage* get_lastest_delta_version() const; const FileVersionMessage* get_latest_version() const; + const FileVersionMessage* get_base_version() const; const uint32_t get_compaction_nice_estimate() const; const OLAPStatus version_creation_time(const Version& version, int64_t* creation_time) const; diff --git a/be/src/olap/olap_snapshot.cpp b/be/src/olap/olap_snapshot.cpp index 4594e30ef7..a13c8d6755 100644 --- a/be/src/olap/olap_snapshot.cpp +++ b/be/src/olap/olap_snapshot.cpp @@ -299,7 +299,7 @@ OLAPStatus OLAPSnapshot::_create_snapshot_files( const FileVersionMessage* latest_version = NULL; latest_version = ref_olap_table->latest_version(); if (latest_version == NULL) { - OLAP_LOG_WARNING("table has not any version. [path='%s']", + OLAP_LOG_WARNING("table does not have any version. [path='%s']", ref_olap_table->full_name().c_str()); res = OLAP_ERR_VERSION_NOT_EXIST; break; diff --git a/be/src/olap/olap_table.cpp b/be/src/olap/olap_table.cpp index 57b6f3af66..0ffebb8a8f 100644 --- a/be/src/olap/olap_table.cpp +++ b/be/src/olap/olap_table.cpp @@ -34,7 +34,7 @@ #include "olap/olap_rootpath.h" #include "olap/reader.h" #include "olap/row_cursor.h" - +#include "util/defer_op.h" using std::map; using std::nothrow; @@ -212,6 +212,8 @@ OLAPTable::~OLAPTable() { path path_name(_header->file_name()); SAFE_DELETE(_header); + OLAP_LOG_WARNING("deconstruct table"); + // 移动数据目录 if (_is_dropped) { path table_path = path_name.parent_path(); @@ -705,6 +707,63 @@ void OLAPTable::set_selectivities(const vector& selectivities) { } } +OLAPStatus OLAPTable::merge_header(const OLAPHeader& hdr, int to_version) { + obtain_header_wrlock(); + DeferOp release_lock(std::bind(&OLAPTable::release_header_lock, this)); + + const FileVersionMessage* base_version = _header->get_base_version(); + if (base_version->end_version() != to_version) { + return OLAP_ERR_VERSION_NOT_EXIST; + } + + // delete old base version + Version base = { base_version->start_version(), base_version->end_version() }; + OLAPStatus st = _header->delete_version(base); + if (st != OLAP_SUCCESS) { + OLAP_LOG_WARNING("failed to delete version [%d-%d] from header", + base_version->start_version(), base_version->end_version()); + return st; + } + OLAP_LOG_DEBUG("finished to delete version [%d-%d] from header", + base_version->start_version(), base_version->end_version()); + + + // add new versions + int version_num = hdr.file_version_size(); + for (int i = 0; i < version_num; ++i) { + const FileVersionMessage& v = hdr.file_version(i); + if (v.end_version() > to_version) { + break; + } + + st = _header->add_version( + { v.start_version(), v.end_version() }, + v.version_hash(), + v.max_timestamp(), + v.num_segments(), + v.index_size(), + v.data_size(), + v.num_rows()); + + if (st != OLAP_SUCCESS) { + OLAP_LOG_WARNING("failed to add version [%d-%d] to header", + v.start_version(), v.end_version()); + return st; + } + OLAP_LOG_WARNING("finished to add version [%d-%d] to header", + v.start_version(), v.end_version()); + } + + st = _header->save(); + if (st != OLAP_SUCCESS) { + OLAP_LOG_FATAL("failed to save header when merging. tablet: %d", _tablet_id); + return st; + } + + OLAP_LOG_DEBUG("finished to merge header to version: %d", to_version); + return OLAP_SUCCESS; +} + OLAPIndex* OLAPTable::_get_largest_index() { OLAPIndex* largest_index = NULL; size_t largest_index_sizes = 0; @@ -1100,6 +1159,11 @@ string OLAPTable::construct_file_name(const Version& version, return file_name; } +string OLAPTable::construct_dir_path() const { + path path_name(_header->file_name()); + return path_name.parent_path().string(); +} + int32_t OLAPTable::get_field_index(const string& field_name) const { field_index_map_t::const_iterator res_iterator = _field_index_map.find(field_name); if (res_iterator == _field_index_map.end()) { diff --git a/be/src/olap/olap_table.h b/be/src/olap/olap_table.h index cf0974f486..68e8b8b66e 100644 --- a/be/src/olap/olap_table.h +++ b/be/src/olap/olap_table.h @@ -174,6 +174,9 @@ public: // Get table row_count and selectivity vector for SHOW_TABLE_INFO command OLAPStatus get_selectivities(std::vector* selectivities); + // used for restore, merge the (0, to_version) in 'hdr' + OLAPStatus merge_header(const OLAPHeader& hdr, int to_version); + // Get OLAPHeader write lock before call get_selectivities() void set_selectivities(const std::vector& selectivities); @@ -275,6 +278,10 @@ public: VersionHash version_hash, uint32_t segment) const; + // return the dir path of this tablet, include tablet id and schema hash + // eg: /path/to/data/0/100001/237480234/ + std::string construct_dir_path() const; + // For index file, suffix is "idx", for data file, suffix is "dat". static std::string construct_file_path(const std::string& header_path, const Version& version, @@ -378,6 +385,10 @@ public: return _header->get_latest_version(); } + const FileVersionMessage* base_version() const { + return _header->get_base_version(); + } + // 在使用之前对header加锁 const uint32_t get_compaction_nice_estimate() const { return _header->get_compaction_nice_estimate(); diff --git a/be/src/olap/reader.cpp b/be/src/olap/reader.cpp index 8bda6227aa..f3979773b7 100644 --- a/be/src/olap/reader.cpp +++ b/be/src/olap/reader.cpp @@ -929,6 +929,9 @@ ColumnPredicate* Reader::_parse_to_predicate(const TCondition& condition) { // TODO: not equal and not in predicate is not pushed down int index = _olap_table->get_field_index(condition.column_name); FieldInfo fi = _olap_table->tablet_schema()[index]; + if (fi.aggregation != FieldAggregationMethod::OLAP_FIELD_AGGREGATION_NONE) { + return nullptr; + } ColumnPredicate* predicate = NULL; if (condition.condition_op == "*=" && condition.condition_values.size() == 1) { diff --git a/be/src/runtime/CMakeLists.txt b/be/src/runtime/CMakeLists.txt index 48ffb79052..8b9c0b7df1 100644 --- a/be/src/runtime/CMakeLists.txt +++ b/be/src/runtime/CMakeLists.txt @@ -92,6 +92,7 @@ add_library(Runtime STATIC bufferpool/suballocator.cc bufferpool/system_allocator.cc initial_reservations.cc + snapshot_loader.cpp ) # This test runs forever so should not be part of 'make test' diff --git a/be/src/runtime/exec_env.cpp b/be/src/runtime/exec_env.cpp index de00048cef..a793b8e5de 100644 --- a/be/src/runtime/exec_env.cpp +++ b/be/src/runtime/exec_env.cpp @@ -60,6 +60,7 @@ #include "runtime/etl_job_mgr.h" #include "runtime/load_path_mgr.h" #include "runtime/pull_load_task_mgr.h" +#include "runtime/snapshot_loader.h" #include "util/pretty_printer.h" #include "util/palo_metrics.h" #include "util/brpc_stub_cache.h" @@ -99,6 +100,7 @@ ExecEnv::ExecEnv() : _bfd_parser(BfdParser::create()), _pull_load_task_mgr(new PullLoadTaskMgr(config::pull_load_task_dir)), _broker_mgr(new BrokerMgr(this)), + _snapshot_loader(new SnapshotLoader(this)), _brpc_stub_cache(new BrpcStubCache()), _enable_webserver(true), _tz_database(TimezoneDatabase()) { diff --git a/be/src/runtime/exec_env.h b/be/src/runtime/exec_env.h index 797a9ba3c1..f831b95f29 100644 --- a/be/src/runtime/exec_env.h +++ b/be/src/runtime/exec_env.h @@ -57,6 +57,8 @@ class BrokerMgr; class MetricRegistry; class BufferPool; class ReservationTracker; +class ConnectionManager; +class SnapshotLoader; class BrpcStubCache; // Execution environment for queries/plan fragments. @@ -149,6 +151,10 @@ public: return _broker_mgr.get(); } + SnapshotLoader* snapshot_loader() const { + return _snapshot_loader.get(); + } + BrpcStubCache* brpc_stub_cache() const { return _brpc_stub_cache.get(); } @@ -197,6 +203,7 @@ private: std::unique_ptr _bfd_parser; std::unique_ptr _pull_load_task_mgr; std::unique_ptr _broker_mgr; + std::unique_ptr _snapshot_loader; std::unique_ptr _brpc_stub_cache; bool _enable_webserver; diff --git a/be/src/runtime/export_sink.cpp b/be/src/runtime/export_sink.cpp index 585b23145d..9f29c4ae8c 100644 --- a/be/src/runtime/export_sink.cpp +++ b/be/src/runtime/export_sink.cpp @@ -222,7 +222,7 @@ Status ExportSink::open_file_writer() { break; } case TFileType::FILE_BROKER: { - BrokerWriter* broker_writer = new BrokerWriter(_state, + BrokerWriter* broker_writer = new BrokerWriter(_state->exec_env(), _t_export_sink.broker_addresses, _t_export_sink.properties, _t_export_sink.export_path + "/" + file_name, diff --git a/be/src/runtime/result_sink.cpp b/be/src/runtime/result_sink.cpp index 2051256f8b..527fcd63ea 100644 --- a/be/src/runtime/result_sink.cpp +++ b/be/src/runtime/result_sink.cpp @@ -20,6 +20,7 @@ #include "runtime/result_sink.h" +#include "common/config.h" #include "util/debug_util.h" #include "exprs/expr.h" #include "runtime/row_batch.h" @@ -86,7 +87,8 @@ Status ResultSink::close(RuntimeState* state, Status exec_status) { if (_sender) { _sender->close(exec_status); } - state->exec_env()->result_mgr()->cancel_at_time(time(NULL) + 32, state->fragment_instance_id()); + state->exec_env()->result_mgr()->cancel_at_time(time(NULL) + config::result_buffer_cancelled_interval_time, + state->fragment_instance_id()); Expr::close(_output_expr_ctxs, state); _closed = true; diff --git a/be/src/runtime/snapshot_loader.cpp b/be/src/runtime/snapshot_loader.cpp new file mode 100644 index 0000000000..aaea0214ad --- /dev/null +++ b/be/src/runtime/snapshot_loader.cpp @@ -0,0 +1,934 @@ +// Copyright (c) 2017, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +#include + +#include "runtime/snapshot_loader.h" + +#include "gen_cpp/PaloBrokerService_types.h" +#include "gen_cpp/TPaloBrokerService.h" + +#include "common/logging.h" +#include "exec/broker_reader.h" +#include "exec/broker_writer.h" +#include "olap/file_helper.h" +#include "olap/olap_engine.h" +#include "olap/olap_table.h" +#include "runtime/exec_env.h" +#include "runtime/broker_mgr.h" +#include "util/file_utils.h" + +namespace palo { + +#ifdef BE_TEST +inline BrokerServiceClientCache* client_cache(ExecEnv* env) { + static BrokerServiceClientCache s_client_cache; + return &s_client_cache; +} + +inline const std::string& client_id(ExecEnv* env, const TNetworkAddress& addr) { + static std::string s_client_id = "palo_unit_test"; + return s_client_id; +} +#else +inline BrokerServiceClientCache* client_cache(ExecEnv* env) { + return env->broker_client_cache(); +} + +inline const std::string& client_id(ExecEnv* env, const TNetworkAddress& addr) { + return env->broker_mgr()->get_client_id(addr); +} +#endif + +SnapshotLoader::SnapshotLoader(ExecEnv* env) : + _env(env) { + +} + +SnapshotLoader::~SnapshotLoader() { + +} + +Status SnapshotLoader::upload( + const std::map& src_to_dest_path, + const TNetworkAddress& broker_addr, + const std::map& broker_prop, + int64_t job_id, + std::map>* tablet_files) { + + LOG(INFO) << "begin to upload snapshot files. num: " + << src_to_dest_path.size() << ", broker addr: " + << broker_addr << ", job: " << job_id; + + Status status = Status::OK; + // 1. validate local tablet snapshot paths + RETURN_IF_ERROR(_check_local_snapshot_paths(src_to_dest_path, true)); + + // 2. get broker client + BrokerServiceConnection client(client_cache(_env), broker_addr, 10000, &status); + if (!status.ok()) { + std::stringstream ss; + ss << "failed to get broker client. " + << "broker addr: " << broker_addr + << ". msg: " << status.get_error_msg(); + LOG(WARNING) << ss.str(); + return Status(ss.str()); + } + + std::vector broker_addrs; + broker_addrs.push_back(broker_addr); + // 3. for each src path, upload it to remote storage + for (auto iter = src_to_dest_path.begin(); iter != src_to_dest_path.end(); + iter++) { + const std::string& src_path = iter->first; + const std::string& dest_path = iter->second; + + int64_t tablet_id = 0; + int32_t schema_hash = 0; + RETURN_IF_ERROR(_get_tablet_id_and_schema_hash_from_file_path( + src_path, &tablet_id, &schema_hash)); + + // 2.1 get existing files from remote path + std::map remote_files; + RETURN_IF_ERROR(_get_existing_files_from_remote( + client, dest_path, broker_prop, &remote_files)); + + for (auto& tmp : remote_files) { + VLOG(2) << "get remote file: " << tmp.first << ", checksum: " << tmp.second.md5; + } + + // 2.2 list local files + std::vector local_files; + std::vector local_files_with_checksum; + RETURN_IF_ERROR(_get_existing_files_from_local(src_path, &local_files)); + + // 2.3 iterate local files + for (auto it = local_files.begin(); it != local_files.end(); it++) { + const std::string& local_file = *it; + // calc md5sum of localfile + std::string md5sum; + status = FileUtils::md5sum(src_path + "/" + local_file, &md5sum); + if (!status.ok()) { + std::stringstream ss; + ss << "failed to get md5sum of file: " << local_file + << ": " << status.get_error_msg(); + LOG(WARNING) << ss.str(); + return Status(ss.str()); + } + VLOG(2) << "get file checksum: " << local_file << ": " << md5sum; + local_files_with_checksum.push_back(local_file + "." + md5sum); + + // check if this local file need upload + bool need_upload = false; + auto find = remote_files.find(local_file); + if (find != remote_files.end()) { + if (md5sum != find->second.md5) { + // remote storage file exist, but with different checksum + LOG(WARNING) << "remote file checksum is invalid. remote: " << find->first + << ", local: " << md5sum; + // TODO(cmy): save these files and delete them later + need_upload = true; + } + } else { + need_upload = true; + } + + if (!need_upload) { + VLOG(2) << "file exist in remote path, no need to upload: " << local_file; + continue; + } + + // upload + // open broker writer. file name end with ".part" + // it will be rename to ".md5sum" after upload finished + std::string full_remote_file = dest_path + "/" + local_file; + { + // NOTICE: broker writer must be closed before calling rename + std::unique_ptr broker_writer; + broker_writer.reset(new BrokerWriter(_env, + broker_addrs, + broker_prop, + full_remote_file + ".part", + 0 /* offset */)); + RETURN_IF_ERROR(broker_writer->open()); + + // read file and write to broker + std::string full_local_file = src_path + "/" + local_file; + FileHandler file_handler; + OLAPStatus ost = file_handler.open(full_local_file, O_RDONLY); + if (ost != OLAP_SUCCESS) { + return Status("failed to open file: " + full_local_file); + } + + size_t file_len = file_handler.length(); + if (file_len == -1) { + return Status("failed to get length of file: " + full_local_file); + } + + constexpr size_t buf_sz = 1024 * 1024; + char read_buf[buf_sz]; + size_t left_len = file_len; + size_t read_offset = 0; + while (left_len > 0) { + size_t read_len = left_len > buf_sz ? buf_sz : left_len; + ost = file_handler.pread(read_buf, read_len, read_offset); + if (ost != OLAP_SUCCESS) { + return Status("failed to read file: " + full_local_file); + } + // write through broker + size_t write_len = 0; + RETURN_IF_ERROR(broker_writer->write(reinterpret_cast(read_buf), + read_len, &write_len)); + DCHECK_EQ(write_len, read_len); + + read_offset += read_len; + left_len -= read_len; + } + LOG(INFO) << "finished to write file via broker. file: " << + full_local_file << ", length: " << file_len; + } + + // rename file to end with ".md5sum" + RETURN_IF_ERROR(_rename_remote_file(client, + full_remote_file + ".part", + full_remote_file + "." + md5sum, + broker_prop)); + } // end for each tablet's local files + + tablet_files->emplace(tablet_id, local_files_with_checksum); + LOG(INFO) << "finished to write tablet to remote. local path: " + << src_path << ", remote path: " << dest_path; + } // end for each tablet path + + LOG(INFO) << "finished to upload snapshots. job: " << job_id; + return status; +} + +/* + * Download snapshot files from remote. + * After downloaded, the local dir should contains all files existing in remote, + * may also contains severval useless files. + */ +Status SnapshotLoader::download( + const std::map& src_to_dest_path, + const TNetworkAddress& broker_addr, + const std::map& broker_prop, + int64_t job_id, + std::vector* downloaded_tablet_ids) { + + LOG(INFO) << "begin to download snapshot files. num: " + << src_to_dest_path.size() << ", broker addr: " + << broker_addr << ", job: " << job_id; + + Status status = Status::OK; + // 1. validate local tablet snapshot paths + RETURN_IF_ERROR(_check_local_snapshot_paths(src_to_dest_path, false)); + + // 2. get broker client + BrokerServiceConnection client(client_cache(_env), broker_addr, 10000, &status); + if (!status.ok()) { + std::stringstream ss; + ss << "failed to get broker client. " + << "broker addr: " << broker_addr + << ". msg: " << status.get_error_msg(); + LOG(WARNING) << ss.str(); + return Status(ss.str()); + } + + std::vector broker_addrs; + broker_addrs.push_back(broker_addr); + // 3. for each src path, download it to local storage + for (auto iter = src_to_dest_path.begin(); iter != src_to_dest_path.end(); + iter++) { + const std::string& remote_path = iter->first; + const std::string& local_path = iter->second; + + int64_t local_tablet_id = 0; + int32_t schema_hash = 0; + RETURN_IF_ERROR(_get_tablet_id_and_schema_hash_from_file_path( + local_path, &local_tablet_id, &schema_hash)); + downloaded_tablet_ids->push_back(local_tablet_id); + + int64_t remote_tablet_id; + RETURN_IF_ERROR(_get_tablet_id_from_remote_path(remote_path, + &remote_tablet_id)); + VLOG(2) << "get local tablet id: " << local_tablet_id << ", schema hash: " + << schema_hash << ", remote tablet id: " << remote_tablet_id; + + // 1. get local files + std::vector local_files; + RETURN_IF_ERROR(_get_existing_files_from_local(local_path, &local_files)); + + // 2. get remote files + std::map remote_files; + RETURN_IF_ERROR(_get_existing_files_from_remote( + client, remote_path, broker_prop, &remote_files)); + if (remote_files.empty()) { + std::stringstream ss; + ss << "get nothing from remote path: " << remote_path; + LOG(WARNING) << ss.str(); + return Status(ss.str()); + } + + for (auto& iter : remote_files) { + bool need_download = false; + const std::string& remote_file = iter.first; + const FileStat& file_stat = iter.second; + auto find = std::find(local_files.begin(), local_files.end(), remote_file); + if (find == local_files.end()) { + // remote file does not exist in local, download it + need_download = true; + } else { + if (_end_with(remote_file, ".hdr")) { + // this is a header file, download it. + need_download = true; + } else { + // check checksum + std::string local_md5sum; + Status st = FileUtils::md5sum(local_path + "/" + remote_file, &local_md5sum); + if (!st.ok()) { + LOG(WARNING) << "failed to get md5sum of local file: " << remote_file + << ". msg: " << st.get_error_msg() << ". download it"; + need_download = true; + } else { + VLOG(2) << "get local file checksum: " << remote_file << ": " << local_md5sum; + if (file_stat.md5 != local_md5sum) { + // file's checksum does not equal, download it. + need_download = true; + } + } + } + } + + if (!need_download) { + LOG(INFO) << "remote file already exist in local, no need to download." + << ", file: " << remote_file; + continue; + } + + // begin to download + std::string full_remote_file = remote_path + "/" + remote_file + "." + file_stat.md5; + std::string local_file_name; + // we need to replace the tablet_id in remote file name with local tablet id + RETURN_IF_ERROR(_replace_tablet_id(remote_file, local_tablet_id, &local_file_name)); + std::string full_local_file = local_path + "/" + local_file_name; + LOG(INFO) << "begin to download from " << full_remote_file << " to " + << full_local_file; + size_t file_len = file_stat.size; + { + // 1. open remote file for read + std::unique_ptr broker_reader; + broker_reader.reset(new BrokerReader(_env, + broker_addrs, + broker_prop, + full_remote_file, + 0 /* offset */)); + RETURN_IF_ERROR(broker_reader->open()); + + // 2. remove the existing local file if exist + if (boost::filesystem::remove(full_local_file)) { + VLOG(2) << "remove the previously exist local file: " + << full_local_file; + } + // remove file which will be downloaded now. + // this file will be added to local_files if it be downloaded successfully. + local_files.erase(find); + + // 3. open local file for write + FileHandler file_handler; + OLAPStatus ost = file_handler.open_with_mode(full_local_file, + O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR); + if (ost != OLAP_SUCCESS) { + return Status("failed to open file: " + full_local_file); + } + + // 4. read remote and write to local + VLOG(2) << "read remote file: " << full_remote_file << " to local: " + << full_local_file << ". file size: " << file_len; + constexpr size_t buf_sz = 1024 * 1024; + char read_buf[buf_sz]; + size_t write_offset = 0; + bool eof = false; + while (!eof) { + size_t read_len = buf_sz; + RETURN_IF_ERROR(broker_reader->read(reinterpret_cast(read_buf), + &read_len, &eof)); + + if (eof) { + continue; + } + + if (read_len > 0) { + ost = file_handler.pwrite(read_buf, read_len, write_offset); + if (ost != OLAP_SUCCESS) { + return Status("failed to write file: " + full_local_file); + } + + write_offset += read_len; + } + } + } // file_handler should be closed before calculating checksum + + // 5. check md5 of the downloaded file + std::string downloaded_md5sum; + status = FileUtils::md5sum(full_local_file, &downloaded_md5sum); + if (!status.ok()) { + std::stringstream ss; + ss << "failed to get md5sum of file: " << full_local_file; + LOG(WARNING) << ss.str(); + return Status(ss.str()); + } + VLOG(2) << "get downloaded file checksum: " << full_local_file << ": " + << downloaded_md5sum; + if (downloaded_md5sum != file_stat.md5) { + std::stringstream ss; + ss << "invalid md5 of downloaded file: " << full_local_file + << ", expected: " << file_stat.md5 << ", get: " << downloaded_md5sum; + LOG(WARNING) << ss.str(); + return Status(ss.str()); + } + + // local_files always keep the updated local files + local_files.push_back(local_file_name); + LOG(INFO) << "finished to download file via broker. file: " << + full_local_file << ", length: " << file_len; + } // end for all remote files + + // finally, delete local files which are not in remote + for (const auto& local_file: local_files) { + // replace the tablet id in local file name with the remote tablet id, + // in order to compare the file name. + std::string new_name; + Status st = _replace_tablet_id(local_file, remote_tablet_id, &new_name); + if (!st.ok()) { + LOG(WARNING) << "failed to replace tablet id. unknown local file: " << st.get_error_msg() + << ". ignore it"; + continue; + } + VLOG(2) << "new file name after replace tablet id: " << new_name; + const auto& find = remote_files.find(new_name); + if (find != remote_files.end()) { + continue; + } + + // delete + std::string full_local_file = local_path + "/" + local_file; + VLOG(2) << "begin to delete local snapshot file: " << full_local_file + << ", it does not exist in remote"; + if (remove(full_local_file.c_str()) != 0) { + LOG(WARNING) << "failed to delete unknown local file: " << full_local_file + << ", ignore it"; + } + } + } // end for src_to_dest_path + + LOG(INFO) << "finished to download snapshots. job: " << job_id; + return status; +} + +// move the snapshot files in snapshot_path +// to tablet_path +// If overwrite, just replace the tablet_path with snapshot_path, +// else: (TODO) +// +Status SnapshotLoader::move( + const std::string& snapshot_path, + const std::string& tablet_path, + int64_t job_id, + bool overwrite) { + + LOG(INFO) << "begin to move snapshot files. from: " + << snapshot_path << ", to: " << tablet_path << ", job: " << job_id; + + Status status = Status::OK; + + // validate snapshot_path and tablet_path + int64_t snapshot_tablet_id = 0; + int32_t snapshot_schema_hash = 0; + RETURN_IF_ERROR(_get_tablet_id_and_schema_hash_from_file_path( + snapshot_path, &snapshot_tablet_id, &snapshot_schema_hash)); + + int64_t tablet_id = 0; + int32_t schema_hash = 0; + RETURN_IF_ERROR(_get_tablet_id_and_schema_hash_from_file_path( + tablet_path, &tablet_id, &schema_hash)); + + if (tablet_id != snapshot_tablet_id || + schema_hash != snapshot_schema_hash) { + std::stringstream ss; + ss << "path does not match. snapshot: " << snapshot_path + << ", tablet path: " << tablet_path; + LOG(WARNING) << ss.str(); + return Status(ss.str()); + } + + boost::filesystem::path tablet_dir(tablet_path); + boost::filesystem::path snapshot_dir(snapshot_path); + if (!boost::filesystem::exists(tablet_dir)) { + std::stringstream ss; + ss << "tablet path does not exist: " << tablet_path; + LOG(WARNING) << ss.str(); + return Status(ss.str()); + } + + if (!boost::filesystem::exists(snapshot_dir)) { + std::stringstream ss; + ss << "snapshot path does not exist: " << snapshot_path; + LOG(WARNING) << ss.str(); + return Status(ss.str()); + } + + if (overwrite) { + std::vector snapshot_files; + RETURN_IF_ERROR(_get_existing_files_from_local(snapshot_path, &snapshot_files)); + + // 1. simply delete the old dir and replace it with the snapshot dir + try { + // This remove seems saft enough, because we already get + // tablet id and schema hash from this path, which + // means this path is a valid path. + boost::filesystem::remove_all(tablet_dir); + VLOG(2) << "remove dir: " << tablet_dir; + boost::filesystem::create_directory(tablet_dir); + VLOG(2) << "re-create dir: " << tablet_dir; + } catch (const boost::filesystem::filesystem_error& e) { + std::stringstream ss; + ss << "failed to move tablet path: " << tablet_path + << ". err: " << e.what(); + LOG(WARNING) << ss.str(); + return Status(ss.str()); + } + + // copy files one by one + for (auto& file : snapshot_files) { + std::string full_src_path = snapshot_path + "/" + file; + std::string full_dest_path = tablet_path + "/" + file; + RETURN_IF_ERROR(FileUtils::copy_file(full_src_path, full_dest_path)); + VLOG(2) << "copy file from " << full_src_path<< " to " << full_dest_path; + } + + } else { + // This is not a overwrite move + // The files in tablet dir should be like this: + // + // 10001.hdr + // 10001_0_70_3286516299297662422_0.idx + // 10001_0_70_3286516299297662422_0.dat + // 10001_71_71_4684061214850851594_0.idx + // 10001_71_71_4684061214850851594_0.dat + // ... + // + // 0-70 version is supposed to be the placeholder version + // + // The files in snapshot dir should be like this: + // 10001.hdr + // 10001_0_40_4684061214850851594_0.idx + // 10001_0_40_4684061214850851594_0.dat + // 10001_41_68_1097018054900466785_0.idx + // 10001_41_68_1097018054900466785_0.dat + // 10001_69_69_8126494056407230455_0.idx + // 10001_69_69_8126494056407230455_0.dat + // 10001_70_70_6330898043876688539_0.idx + // 10001_70_70_6330898043876688539_0.dat + // 10001_71_71_0_0.idx + // 10001_71_71_0_0.dat + // + // 71-71 may be exist as the palceholder version + // + // We need to move 0-70 version files from snapshot dir to + // replace the 0-70 placeholder version in tablet dir. + // than we merge the 2 .hdr file before reloading it. + + // load header in tablet dir to get the base vesion + SmartOLAPTable tablet = OLAPEngine::get_instance()->get_table( + tablet_id, schema_hash); + if (tablet.get() == NULL) { + std::stringstream ss; + ss << "failed to get tablet: " << tablet_id << ", schema hash: " + << schema_hash; + LOG(WARNING) << ss.str(); + return Status(ss.str()); + } + // get base version + tablet->obtain_header_rdlock(); + const FileVersionMessage* base_version = tablet->base_version(); + tablet->release_header_lock(); + if (base_version == nullptr) { + std::stringstream ss; + ss << "failed to get base version of tablet: " << tablet_id; + LOG(WARNING) << ss.str(); + return Status(ss.str()); + } + + int32_t end_version = base_version->end_version(); + + // load snapshot tablet + std::stringstream hdr; + hdr << snapshot_path << "/" << tablet_id << ".hdr"; + std::string snapshot_header_file = hdr.str(); + + OLAPHeader snapshot_header(snapshot_header_file); + OLAPStatus ost = snapshot_header.load(); + if (ost != OLAP_SUCCESS) { + LOG(WARNING) << "failed to load snapshot header: " << snapshot_header_file; + return Status("failed to load snapshot header: " + snapshot_header_file); + } + + LOG(INFO) << "begin to move snapshot files from version 0 to " + << end_version << ", tablet id: " << tablet_id; + + // begin to move + try { + // delete the placeholder version in tablet dir + std::string dummy; + std::string place_holder_idx; + _assemble_file_name("", tablet_path, tablet_id, + 0, end_version, + base_version->version_hash(), 0, ".idx", + &dummy, &place_holder_idx); + boost::filesystem::remove(place_holder_idx); + + std::string place_holder_dat; + _assemble_file_name("", tablet_path, tablet_id, + 0, end_version, + base_version->version_hash(), 0, ".dat", + &dummy, &place_holder_idx); + boost::filesystem::remove(place_holder_dat); + + // copy files + int version_size = snapshot_header.file_version_size(); + for (int i = 0; i < version_size; ++i) { + const FileVersionMessage& version = snapshot_header.file_version(i); + if (version.start_version() > end_version) { + continue; + } + int seg_num = version.num_segments(); + for (int j = 0; j < seg_num; i++) { + // idx + std::string idx_from; + std::string idx_to; + _assemble_file_name(snapshot_path, tablet_path, tablet_id, + version.start_version(), version.end_version(), + version.version_hash(), j, ".idx", + &idx_from, &idx_to); + + boost::filesystem::copy_file(idx_from, idx_to); + + // dat + std::string dat_from; + std::string dat_to; + _assemble_file_name(snapshot_path, tablet_path, tablet_id, + version.start_version(), version.end_version(), + version.version_hash(), j, ".dat", + &dat_from, &dat_to); + boost::filesystem::copy_file(dat_from, dat_to); + } + } + } catch (const boost::filesystem::filesystem_error& e) { + std::stringstream ss; + ss << "failed to move tablet path: " << tablet_path + << ". err: " << e.what(); + LOG(WARNING) << ss.str(); + return Status(ss.str()); + } + + // merge 2 headers + ost = tablet->merge_header(snapshot_header, end_version); + if (ost != OLAP_SUCCESS) { + std::stringstream ss; + ss << "failed to move tablet path: " << tablet_path; + LOG(WARNING) << ss.str(); + return Status(ss.str()); + } + } + + // reload header + OLAPStatus ost = OLAPEngine::get_instance()->load_one_tablet( + tablet_id, schema_hash, tablet_path, true); + if (ost != OLAP_SUCCESS) { + std::stringstream ss; + ss << "failed to reload header of tablet: " << tablet_id; + LOG(WARNING) << ss.str(); + return Status(ss.str()); + } + LOG(INFO) << "finished to reload header of tablet: " << tablet_id; + + return status; +} + +bool SnapshotLoader::_end_with( + const std::string& str, + const std::string& match) { + + if(str.size() >= match.size() && + str.compare(str.size() - match.size(), match.size(), match) == 0) { + return true; + } + return false; +} + +Status SnapshotLoader::_get_tablet_id_and_schema_hash_from_file_path( + const std::string& src_path, int64_t* tablet_id, int32_t* schema_hash) { + // path should be like: /path/.../tablet_id/schema_hash + // we try to extract tablet_id from path + size_t pos = src_path.find_last_of("/"); + if (pos == std::string::npos || pos == src_path.length() - 1) { + return Status("failed to get tablet id from path: " + src_path); + } + + std::string schema_hash_str = src_path.substr(pos + 1); + std::stringstream ss1; + ss1 << schema_hash_str; + ss1 >> *schema_hash; + + // skip schema hash part + size_t pos2 = src_path.find_last_of("/", pos - 1); + if (pos2 == std::string::npos) { + return Status("failed to get tablet id from path: " + src_path); + } + + std::string tablet_str = src_path.substr(pos2 + 1, pos - pos2); + std::stringstream ss2; + ss2 << tablet_str; + ss2 >> *tablet_id; + + VLOG(2) << "get tablet id " << *tablet_id + << ", schema hash: " << *schema_hash + << " from path: " << src_path; + return Status::OK; +} + +Status SnapshotLoader::_check_local_snapshot_paths( + const std::map& src_to_dest_path, bool check_src) { + for (const auto& pair : src_to_dest_path) { + std::string path; + if (check_src) { + path = pair.first; + } else { + path = pair.second; + } + if (!FileUtils::is_dir(path)) { + std::stringstream ss; + ss << "snapshot path is not directory or does not exist: " << path; + LOG(WARNING) << ss.str(); + return Status(TStatusCode::RUNTIME_ERROR, ss.str(), true); + } + } + LOG(INFO) << "all local snapshot paths are existing. num: " << src_to_dest_path.size(); + return Status::OK; +} + +Status SnapshotLoader::_get_existing_files_from_remote( + BrokerServiceConnection& client, + const std::string& remote_path, + const std::map& broker_prop, + std::map* files) { + try { + // get existing files from remote path + TBrokerListResponse list_rep; + TBrokerListPathRequest list_req; + list_req.__set_version(TBrokerVersion::VERSION_ONE); + list_req.__set_path(remote_path + "/*"); + list_req.__set_isRecursive(false); + list_req.__set_properties(broker_prop); + list_req.__set_fileNameOnly(true); // we only need file name, not abs path + + try { + client->listPath(list_rep, list_req); + } catch (apache::thrift::transport::TTransportException& e) { + RETURN_IF_ERROR(client.reopen()); + client->listPath(list_rep, list_req); + } + + if (list_rep.opStatus.statusCode == TBrokerOperationStatusCode::FILE_NOT_FOUND) { + LOG(INFO) << "path does not exist: " << remote_path; + return Status::OK; + } else if (list_rep.opStatus.statusCode != TBrokerOperationStatusCode::OK) { + std::stringstream ss; + ss << "failed to list files from remote path: " << remote_path << ", msg: " + << list_rep.opStatus.message; + LOG(WARNING) << ss.str(); + return Status(ss.str()); + } + LOG(INFO) << "finished to list files from remote path. file num: " + << list_rep.files.size(); + + // split file name and checksum + for (const auto& file : list_rep.files) { + if (file.isDir) { + // this is not a file + continue; + } + + const std::string& file_name = file.path; + size_t pos = file_name.find_last_of("."); + if (pos == std::string::npos || pos == file_name.size() - 1) { + // Not found checksum separator, ignore this file + continue; + } + + FileStat stat = { std::string(file_name, 0, pos), std::string(file_name, pos + 1), file.size }; + files->emplace(std::string(file_name, 0, pos), stat); + VLOG(2) << "split remote file: " << std::string(file_name, 0, pos) << ", checksum: " + << std::string(file_name, pos + 1); + } + + LOG(INFO) << "finished to split files. valid file num: " + << files->size(); + + } catch (apache::thrift::TException& e) { + std::stringstream ss; + ss << "failed to list files in remote path: " << remote_path << ", msg: " << e.what(); + LOG(WARNING) << ss.str(); + return Status(TStatusCode::THRIFT_RPC_ERROR, ss.str(), false); + } + + return Status::OK; +} + +Status SnapshotLoader::_get_existing_files_from_local( + const std::string& local_path, + std::vector* local_files) { + + Status status = FileUtils::scan_dir(local_path, local_files); + if (!status.ok()) { + std::stringstream ss; + ss << "failed to list files in local path: " << local_path << ", msg: " + << status.get_error_msg(); + LOG(WARNING) << ss.str(); + return status; + } + LOG(INFO) << "finished to list files in local path: " << local_path << ", file num: " + << local_files->size(); + return Status::OK; +} + +Status SnapshotLoader::_rename_remote_file( + BrokerServiceConnection& client, + const std::string& orig_name, + const std::string& new_name, + const std::map& broker_prop) { + try { + TBrokerOperationStatus op_status; + TBrokerRenamePathRequest rename_req; + rename_req.__set_version(TBrokerVersion::VERSION_ONE); + rename_req.__set_srcPath(orig_name); + rename_req.__set_destPath(new_name); + rename_req.__set_properties(broker_prop); + + try { + client->renamePath(op_status, rename_req); + } catch (apache::thrift::transport::TTransportException& e) { + RETURN_IF_ERROR(client.reopen()); + client->renamePath(op_status, rename_req); + } + + if (op_status.statusCode != TBrokerOperationStatusCode::OK) { + std::stringstream ss; + ss << "Fail to rename file: " << orig_name << " to: " << new_name + << " msg:" << op_status.message; + LOG(WARNING) << ss.str(); + return Status(ss.str()); + } + } catch (apache::thrift::TException& e) { + std::stringstream ss; + ss << "Fail to rename file: " << orig_name << " to: " << new_name + << " msg:" << e.what(); + LOG(WARNING) << ss.str(); + return Status(TStatusCode::THRIFT_RPC_ERROR, ss.str(), false); + } + + LOG(INFO) << "finished to rename file. orig: " << orig_name + << ", new: " << new_name; + + return Status::OK; +} + +void SnapshotLoader::_assemble_file_name( + const std::string& snapshot_path, + const std::string& tablet_path, + int64_t tablet_id, + int64_t start_version, int64_t end_version, + int64_t vesion_hash, int32_t seg_num, + const std::string suffix, + std::string* snapshot_file, std::string* tablet_file) { + + std::stringstream ss1; + ss1 << snapshot_path << "/" << tablet_id << "_" + << start_version << "_" << end_version << "_" + << vesion_hash << "_" << seg_num << suffix; + *snapshot_file = ss1.str(); + + std::stringstream ss2; + ss2 << tablet_path << "/" << tablet_id << "_" + << start_version << "_" << end_version << "_" + << vesion_hash << "_" << seg_num << suffix; + *tablet_file = ss2.str(); + + VLOG(2) << "assemble file name: " << *snapshot_file + << ", " << *tablet_file; +} + +Status SnapshotLoader::_replace_tablet_id( + const std::string& file_name, + int64_t tablet_id, + std::string* new_file_name) { + + // eg: + // 10007.hdr + // 10007_2_2_0_0.idx + // 10007_2_2_0_0.dat + if (_end_with(file_name, ".hdr")) { + std::stringstream ss; + ss << tablet_id << ".hdr"; + *new_file_name = ss.str(); + return Status::OK; + } else if (_end_with(file_name, ".idx") + || _end_with(file_name, ".dat")) { + size_t pos = file_name.find_first_of("_"); + if (pos == std::string::npos) { + return Status("invalid tablet file name: " + file_name); + } + + std::string suffix_part = file_name.substr(pos); + std::stringstream ss; + ss << tablet_id << suffix_part; + *new_file_name = ss.str(); + return Status::OK; + } else { + return Status("invalid tablet file name: " + file_name); + } +} + +Status SnapshotLoader::_get_tablet_id_from_remote_path( + const std::string& remote_path, + int64_t* tablet_id) { + + // eg: + // bos://xxx/../__tbl_10004/__part_10003/__idx_10004/__10005 + size_t pos = remote_path.find_last_of("_"); + if (pos == std::string::npos) { + return Status("invalid remove file path: " + remote_path); + } + + std::string tablet_id_str = remote_path.substr(pos + 1); + std::stringstream ss; + ss << tablet_id_str; + ss >> *tablet_id; + + return Status::OK; +} + +} // end namespace palo diff --git a/be/src/runtime/snapshot_loader.h b/be/src/runtime/snapshot_loader.h new file mode 100644 index 0000000000..e4bc7321c3 --- /dev/null +++ b/be/src/runtime/snapshot_loader.h @@ -0,0 +1,139 @@ +// Copyright (c) 2017, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +#ifndef BDG_PALO_BE_SRC_RUNTIME_SNAPSHOT_LOADER_H +#define BDG_PALO_BE_SRC_RUNTIME_SNAPSHOT_LOADER_H + +#include + +#include +#include +#include + +#include "gen_cpp/Types_types.h" + +#include "common/status.h" +#include "runtime/client_cache.h" + +namespace palo { + +class ExecEnv; + +struct FileStat { + std::string name; + std::string md5; + int64_t size; +}; + +/* + * Upload: + * upload() will upload the specified snapshot + * to remote storage via broker. + * Each call of upload() is reponsible for severval tablet snapshots. + * + * It will try to get the existing files in remote storage, + * and only upload the incremental part of files. + * + * Download: + * download() will download the romote tablet snapshot files + * to local snapshot dir via broker. + * It will also only download files which does not exist in local dir. + * + * Move: + * move() is the final step of restore process. it will replace the + * old tablet data dir with the newly downloaded snapshot dir. + * and reload the tablet header to take this tablet on line. + * + */ +class SnapshotLoader { +public: + SnapshotLoader(ExecEnv* env); + + ~SnapshotLoader(); + + Status upload( + const std::map& src_to_dest_path, + const TNetworkAddress& broker_addr, + const std::map& broker_prop, + int64_t job_id, + std::map>* tablet_files); + + Status download( + const std::map& src_to_dest_path, + const TNetworkAddress& broker_addr, + const std::map& broker_prop, + int64_t job_id, + std::vector* downloaded_tablet_ids); + + Status move( + const std::string& snapshot_path, + const std::string& tablet_path, + int64_t job_id, + bool overwrite); + +private: + Status _get_tablet_id_and_schema_hash_from_file_path( + const std::string& src_path, int64_t* tablet_id, + int32_t* schema_hash); + + Status _check_local_snapshot_paths( + const std::map& src_to_dest_path, + bool check_src); + + Status _get_existing_files_from_remote( + BrokerServiceConnection& client, + const std::string& remote_path, + const std::map& broker_prop, + std::map* files); + + Status _get_existing_files_from_local( + const std::string& local_path, + std::vector* local_files); + + Status _rename_remote_file( + BrokerServiceConnection& client, + const std::string& orig_name, + const std::string& new_name, + const std::map& broker_prop); + + bool _end_with( + const std::string& str, + const std::string& match); + + void _assemble_file_name( + const std::string& snapshot_path, + const std::string& tablet_path, + int64_t tablet_id, + int64_t start_version, int64_t end_version, + int64_t vesion_hash, int32_t seg_num, + const std::string suffix, + std::string* snapshot_file, std::string* tablet_file); + + Status _replace_tablet_id( + const std::string& file_name, + int64_t tablet_id, + std::string* new_file_name); + + Status _get_tablet_id_from_remote_path( + const std::string& remote_path, + int64_t* tablet_id); + +private: + ExecEnv* _env; +}; + +} // end namespace palo + +#endif // BDG_PALO_BE_SRC_RUNTIME_SNAPSHOT_LOADER_H diff --git a/be/src/util/file_utils.cpp b/be/src/util/file_utils.cpp index bf4ff4a401..3692269a11 100644 --- a/be/src/util/file_utils.cpp +++ b/be/src/util/file_utils.cpp @@ -22,16 +22,20 @@ #include #include +#include #include #include #include +#include #include #include #include #include +#include + #include "olap/file_helper.h" #include "util/defer_op.h" @@ -215,5 +219,33 @@ Status FileUtils::copy_file(const std::string& src_path, const std::string& dest return Status::OK; } +Status FileUtils::md5sum(const std::string& file, std::string* md5sum) { + int fd = open(file.c_str(), O_RDONLY); + if (fd < 0) { + return Status("failed to open file"); + } + + struct stat statbuf; + if (fstat(fd, &statbuf) < 0) { + close(fd); + return Status("failed to stat file"); + } + size_t file_len = statbuf.st_size; + void* buf = mmap(0, file_len, PROT_READ, MAP_SHARED, fd, 0); + + unsigned char result[MD5_DIGEST_LENGTH]; + MD5((unsigned char*) buf, file_len, result); + munmap(buf, file_len); + + std::stringstream ss; + for (int32_t i = 0; i < MD5_DIGEST_LENGTH; i++) { + ss << std::setfill('0') << std::setw(2) << std::hex << (int) result[i]; + } + ss >> *md5sum; + + close(fd); + return Status::OK; +} + } diff --git a/be/src/util/file_utils.h b/be/src/util/file_utils.h index 8ba0ed83c4..983fd6f5d0 100644 --- a/be/src/util/file_utils.h +++ b/be/src/util/file_utils.h @@ -61,6 +61,9 @@ public: // copy the file from src path to dest path, it will overwrite the existing files static Status copy_file(const std::string& src_path, const std::string& dest_path); + + // calc md5sum of a local file + static Status md5sum(const std::string& file, std::string* md5sum); }; } diff --git a/be/test/agent/agent_server_test.cpp b/be/test/agent/agent_server_test.cpp index ec2e6f2a91..f2a78b9d30 100644 --- a/be/test/agent/agent_server_test.cpp +++ b/be/test/agent/agent_server_test.cpp @@ -90,11 +90,6 @@ TEST(SubmitTasksTest, TestSubmitTasks){ upload_task.task_type = TTaskType::UPLOAD; upload_task.__set_upload_req(upload_req); tasks.push_back(upload_task); - TAgentTaskRequest restore_task; - TRestoreReq restore_req; - restore_task.task_type = TTaskType::RESTORE; - restore_task.__set_restore_req(restore_req); - tasks.push_back(restore_task); TAgentTaskRequest make_snapshot_task; TSnapshotRequest snapshot_req; make_snapshot_task.task_type = TTaskType::MAKE_SNAPSHOT; diff --git a/be/test/agent/task_worker_pool_test.cpp b/be/test/agent/task_worker_pool_test.cpp index a49ac26544..1761caf2de 100644 --- a/be/test/agent/task_worker_pool_test.cpp +++ b/be/test/agent/task_worker_pool_test.cpp @@ -29,6 +29,7 @@ #include "agent/utils.h" #include "olap/mock_command_executor.h" #include "util/logging.h" +#include "runtime/exec_env.h" using ::testing::_; using ::testing::Return; @@ -55,20 +56,24 @@ MockMasterServerClient::MockMasterServerClient( TEST(TaskWorkerPoolTest, TestStart) { TMasterInfo master_info; + ExecEnv env; TaskWorkerPool task_worker_pool_create_table( TaskWorkerPool::TaskWorkerType::CREATE_TABLE, + &env, master_info); task_worker_pool_create_table.start(); EXPECT_EQ(task_worker_pool_create_table._worker_count, config::create_table_worker_count); TaskWorkerPool task_worker_pool_drop_table( TaskWorkerPool::TaskWorkerType::DROP_TABLE, + &env, master_info); task_worker_pool_drop_table.start(); EXPECT_EQ(task_worker_pool_create_table._worker_count, config::drop_table_worker_count); TaskWorkerPool task_worker_pool_push( TaskWorkerPool::TaskWorkerType::PUSH, + &env, master_info); task_worker_pool_push.start(); EXPECT_EQ(task_worker_pool_push._worker_count, config::push_worker_count_normal_priority @@ -76,18 +81,21 @@ TEST(TaskWorkerPoolTest, TestStart) { TaskWorkerPool task_worker_pool_alter_table( TaskWorkerPool::TaskWorkerType::ALTER_TABLE, + &env, master_info); task_worker_pool_alter_table.start(); EXPECT_EQ(task_worker_pool_alter_table._worker_count, config::alter_table_worker_count); TaskWorkerPool task_worker_pool_clone( TaskWorkerPool::TaskWorkerType::CLONE, + &env, master_info); task_worker_pool_clone.start(); EXPECT_EQ(task_worker_pool_clone._worker_count, config::clone_worker_count); TaskWorkerPool task_worker_pool_cancel_delete_data( TaskWorkerPool::TaskWorkerType::CANCEL_DELETE_DATA, + &env, master_info); task_worker_pool_cancel_delete_data.start(); EXPECT_EQ( @@ -96,42 +104,42 @@ TEST(TaskWorkerPoolTest, TestStart) { TaskWorkerPool task_worker_pool_report_task( TaskWorkerPool::TaskWorkerType::REPORT_TASK, + &env, master_info); task_worker_pool_report_task.start(); EXPECT_EQ(task_worker_pool_report_task._worker_count, REPORT_TASK_WORKER_COUNT); TaskWorkerPool task_worker_pool_report_disk_state( TaskWorkerPool::TaskWorkerType::REPORT_DISK_STATE, + &env, master_info); task_worker_pool_report_disk_state.start(); EXPECT_EQ(task_worker_pool_report_disk_state._worker_count, REPORT_DISK_STATE_WORKER_COUNT); TaskWorkerPool task_worker_pool_report_olap_table( TaskWorkerPool::TaskWorkerType::REPORT_OLAP_TABLE, + &env, master_info); task_worker_pool_report_olap_table.start(); EXPECT_EQ(task_worker_pool_report_olap_table._worker_count, REPORT_OLAP_TABLE_WORKER_COUNT); TaskWorkerPool task_worker_pool_upload( TaskWorkerPool::TaskWorkerType::UPLOAD, + &env, master_info); task_worker_pool_upload.start(); EXPECT_EQ(task_worker_pool_upload._worker_count, config::upload_worker_count); - TaskWorkerPool task_worker_pool_restore( - TaskWorkerPool::TaskWorkerType::RESTORE, - master_info); - task_worker_pool_restore.start(); - EXPECT_EQ(task_worker_pool_restore._worker_count, config::restore_worker_count); - TaskWorkerPool task_worker_pool_make_snapshot( TaskWorkerPool::TaskWorkerType::MAKE_SNAPSHOT, + &env, master_info); task_worker_pool_make_snapshot.start(); EXPECT_EQ(task_worker_pool_make_snapshot._worker_count, config::make_snapshot_worker_count); TaskWorkerPool task_worker_pool_release_snapshot( TaskWorkerPool::TaskWorkerType::RELEASE_SNAPSHOT, + &env, master_info); task_worker_pool_release_snapshot.start(); EXPECT_EQ(task_worker_pool_release_snapshot._worker_count, @@ -140,8 +148,10 @@ TEST(TaskWorkerPoolTest, TestStart) { TEST(TaskWorkerPoolTest, TestSubmitTask) { TMasterInfo master_info; + ExecEnv env; TaskWorkerPool task_worker_pool( TaskWorkerPool::TaskWorkerType::ALTER_TABLE, + &env, master_info); // Record signature success @@ -160,8 +170,10 @@ TEST(TaskWorkerPoolTest, TestSubmitTask) { TEST(TaskWorkerPoolTest, TestRecordTaskInfo) { TMasterInfo master_info; + ExecEnv env; TaskWorkerPool task_worker_pool( TaskWorkerPool::TaskWorkerType::ALTER_TABLE, + &env, master_info); TTaskType::type task_type = TTaskType::ROLLUP; @@ -183,6 +195,7 @@ TEST(TaskWorkerPoolTest, TestRecordTaskInfo) { TMasterInfo master_info2; TaskWorkerPool task_worker_pool2( TaskWorkerPool::TaskWorkerType::PUSH, + &env, master_info2); TTaskType::type task_type2 = TTaskType::PUSH; @@ -217,8 +230,10 @@ TEST(TaskWorkerPoolTest, TestRecordTaskInfo) { TEST(TaskWorkerPoolTest, TestRemoveTaskInfo) { TMasterInfo master_info; + ExecEnv env; TaskWorkerPool task_worker_pool( TaskWorkerPool::TaskWorkerType::ALTER_TABLE, + &env, master_info); TTaskType::type task_type = TTaskType::ROLLUP; @@ -255,8 +270,10 @@ TEST(TaskWorkerPoolTest, TestRemoveTaskInfo) { TEST(TaskWorkerPoolTest, TestGetNextTask) { TMasterInfo master_info; + ExecEnv env; TaskWorkerPool task_worker_pool( TaskWorkerPool::TaskWorkerType::PUSH, + &env, master_info); // Add 1 task @@ -334,8 +351,10 @@ TEST(TaskWorkerPoolTest, TestGetNextTask) { TEST(TaskWorkerPoolTest, TestFinishTask) { TMasterInfo master_info; + ExecEnv env; TaskWorkerPool task_worker_pool( TaskWorkerPool::TaskWorkerType::ALTER_TABLE, + &env, master_info); FrontendServiceClientCache* client_cache = new FrontendServiceClientCache(); @@ -362,11 +381,13 @@ TEST(TaskWorkerPoolTest, TestFinishTask) { TEST(TaskWorkerPoolTest, TestCreateTable) { TMasterInfo master_info; + ExecEnv env; TAgentTaskRequest agent_task_request; agent_task_request.task_type = TTaskType::CREATE; agent_task_request.signature = 123456; TaskWorkerPool task_worker_pool( TaskWorkerPool::TaskWorkerType::CREATE_TABLE, + &env, master_info); MockCommandExecutor mock_command_executor; @@ -415,11 +436,13 @@ TEST(TaskWorkerPoolTest, TestCreateTable) { TEST(TaskWorkerPoolTest, TestDropTableTask) { TMasterInfo master_info; + ExecEnv env; TAgentTaskRequest agent_task_request; agent_task_request.task_type = TTaskType::DROP; agent_task_request.signature = 123456; TaskWorkerPool task_worker_pool( TaskWorkerPool::TaskWorkerType::DROP_TABLE, + &env, master_info); MockCommandExecutor mock_command_executor; @@ -468,11 +491,13 @@ TEST(TaskWorkerPoolTest, TestDropTableTask) { TEST(TaskWorkerPoolTest, TestSchemaChange) { TMasterInfo master_info; + ExecEnv env; TAgentTaskRequest agent_task_request; agent_task_request.task_type = TTaskType::SCHEMA_CHANGE; agent_task_request.signature = 123456; TaskWorkerPool task_worker_pool( TaskWorkerPool::TaskWorkerType::ALTER_TABLE, + &env, master_info); MockCommandExecutor mock_command_executor; @@ -598,11 +623,13 @@ TEST(TaskWorkerPoolTest, TestSchemaChange) { TEST(TaskWorkerPoolTest, TestRollup) { TMasterInfo master_info; + ExecEnv env; TAgentTaskRequest agent_task_request; agent_task_request.task_type = TTaskType::ROLLUP; agent_task_request.signature = 123456; TaskWorkerPool task_worker_pool( TaskWorkerPool::TaskWorkerType::ALTER_TABLE, + &env, master_info); MockCommandExecutor mock_command_executor; @@ -651,12 +678,14 @@ TEST(TaskWorkerPoolTest, TestRollup) { TEST(TaskWorkerPoolTest, TestPush) { TMasterInfo master_info; + ExecEnv env; TAgentTaskRequest agent_task_request; agent_task_request.task_type = TTaskType::PUSH; agent_task_request.signature = 123456; agent_task_request.__set_priority(TPriority::HIGH); TaskWorkerPool task_worker_pool( TaskWorkerPool::TaskWorkerType::PUSH, + &env, master_info); MockCommandExecutor mock_command_executor; @@ -765,11 +794,13 @@ TEST(TaskWorkerPoolTest, TestPush) { TEST(TaskWorkerPoolTest, TestClone) { TMasterInfo master_info; + ExecEnv env; TAgentTaskRequest agent_task_request; agent_task_request.task_type = TTaskType::CLONE; agent_task_request.signature = 123456; TaskWorkerPool task_worker_pool( TaskWorkerPool::TaskWorkerType::CLONE, + &env, master_info); MockCommandExecutor mock_command_executor; @@ -1216,11 +1247,13 @@ TEST(TaskWorkerPoolTest, TestClone) { TEST(TaskWorkerPoolTest, TestCancelDeleteData) { TMasterInfo master_info; + ExecEnv env; TAgentTaskRequest agent_task_request; agent_task_request.task_type = TTaskType::CANCEL_DELETE; agent_task_request.signature = 123456; TaskWorkerPool task_worker_pool( TaskWorkerPool::TaskWorkerType::CANCEL_DELETE_DATA, + &env, master_info); MockCommandExecutor mock_command_executor; @@ -1269,11 +1302,13 @@ TEST(TaskWorkerPoolTest, TestCancelDeleteData) { TEST(TaskWorkerPoolTest, TestReportTask) { TMasterInfo master_info; + ExecEnv env; TAgentTaskRequest agent_task_request; agent_task_request.task_type = TTaskType::SCHEMA_CHANGE; agent_task_request.signature = 123456; TaskWorkerPool task_worker_pool( TaskWorkerPool::TaskWorkerType::ALTER_TABLE, + &env, master_info); MockCommandExecutor mock_command_executor; @@ -1304,11 +1339,13 @@ TEST(TaskWorkerPoolTest, TestReportTask) { TEST(TaskWorkerPoolTest, TestReportDiskState) { TMasterInfo master_info; + ExecEnv env; TAgentTaskRequest agent_task_request; agent_task_request.task_type = TTaskType::SCHEMA_CHANGE; agent_task_request.signature = 123456; TaskWorkerPool task_worker_pool( TaskWorkerPool::TaskWorkerType::ALTER_TABLE, + &env, master_info); MockCommandExecutor mock_command_executor; @@ -1355,8 +1392,10 @@ TEST(TaskWorkerPoolTest, TestReportDiskState) { TEST(TaskWorkerPoolTest, TestReportOlapTable) { TMasterInfo master_info; + ExecEnv env; TaskWorkerPool task_worker_pool( TaskWorkerPool::TaskWorkerType::ALTER_TABLE, + &env, master_info); MockCommandExecutor mock_command_executor; @@ -1399,245 +1438,12 @@ TEST(TaskWorkerPoolTest, TestReportOlapTable) { task_worker_pool._master_client = original_master_server_client; } -TEST(TaskWorkerPoolTest, TestUpload) { - TMasterInfo master_info; - TaskWorkerPool task_worker_pool( - TaskWorkerPool::TaskWorkerType::UPLOAD, - master_info); - - MockAgentUtils mock_agent_utils; - AgentUtils* original_agent_utils; - original_agent_utils = task_worker_pool._agent_utils; - task_worker_pool._agent_utils = &mock_agent_utils; - FrontendServiceClientCache* client_cache = new FrontendServiceClientCache(); - MockMasterServerClient mock_master_server_client(master_info, client_cache); - MasterServerClient* original_master_server_client; - original_master_server_client = task_worker_pool._master_client; - task_worker_pool._master_client = &mock_master_server_client; - - TAgentTaskRequest agent_task_request; - agent_task_request.task_type = TTaskType::UPLOAD; - agent_task_request.signature = 123456; - TUploadReq upload_request; - upload_request.__set_tablet_id(54321); - agent_task_request.__set_upload_req(upload_request); - - // Write remote source info into file by json format failed - task_worker_pool.submit_task(agent_task_request); - EXPECT_EQ(1, task_worker_pool._s_task_signatures[agent_task_request.task_type].size()); - EXPECT_EQ(1, task_worker_pool._tasks.size()); - EXPECT_CALL(mock_agent_utils, write_json_to_file(_, _)) - .Times(1) - .WillOnce(Return(false)); - EXPECT_CALL(mock_master_server_client, finish_task(_, _)) - .Times(1) - .WillOnce(Return(PALO_SUCCESS)); - task_worker_pool._upload_worker_thread_callback(&task_worker_pool); - EXPECT_EQ(0, task_worker_pool._s_task_signatures[agent_task_request.task_type].size()); - EXPECT_EQ(0, task_worker_pool._tasks.size()); - - // write json file success, run command failed - task_worker_pool.submit_task(agent_task_request); - EXPECT_EQ(1, task_worker_pool._s_task_signatures[agent_task_request.task_type].size()); - EXPECT_EQ(1, task_worker_pool._tasks.size()); - EXPECT_CALL(mock_agent_utils, write_json_to_file(_, _)) - .Times(1) - .WillOnce(Return(true)); - EXPECT_CALL(mock_agent_utils, exec_cmd(_, _)) - .Times(1) - .WillOnce(Return(false)); - EXPECT_CALL(mock_master_server_client, finish_task(_, _)) - .Times(1) - .WillOnce(Return(PALO_SUCCESS)); - task_worker_pool._upload_worker_thread_callback(&task_worker_pool); - EXPECT_EQ(0, task_worker_pool._s_task_signatures[agent_task_request.task_type].size()); - EXPECT_EQ(0, task_worker_pool._tasks.size()); - - // write json file success, run command success - task_worker_pool.submit_task(agent_task_request); - EXPECT_EQ(1, task_worker_pool._s_task_signatures[agent_task_request.task_type].size()); - EXPECT_EQ(1, task_worker_pool._tasks.size()); - EXPECT_CALL(mock_agent_utils, write_json_to_file(_, _)) - .Times(1) - .WillOnce(Return(true)); - EXPECT_CALL(mock_agent_utils, exec_cmd(_, _)) - .Times(1) - .WillOnce(Return(true)); - EXPECT_CALL(mock_master_server_client, finish_task(_, _)) - .Times(1) - .WillOnce(Return(PALO_SUCCESS)); - task_worker_pool._upload_worker_thread_callback(&task_worker_pool); - EXPECT_EQ(0, task_worker_pool._s_task_signatures[agent_task_request.task_type].size()); - EXPECT_EQ(0, task_worker_pool._tasks.size()); - - task_worker_pool._agent_utils = original_agent_utils; - task_worker_pool._master_client = original_master_server_client; -} - -TEST(TaskWorkerPoolTest, TestRestore) { - TMasterInfo master_info; - TaskWorkerPool task_worker_pool( - TaskWorkerPool::TaskWorkerType::RESTORE, - master_info); - - MockAgentUtils mock_agent_utils; - AgentUtils* original_agent_utils; - original_agent_utils = task_worker_pool._agent_utils; - task_worker_pool._agent_utils = &mock_agent_utils; - MockCommandExecutor mock_command_executor; - CommandExecutor* original_command_executor; - original_command_executor = task_worker_pool._command_executor; - task_worker_pool._command_executor = &mock_command_executor; - FrontendServiceClientCache* client_cache = new FrontendServiceClientCache(); - MockMasterServerClient mock_master_server_client(master_info, client_cache); - MasterServerClient* original_master_server_client; - original_master_server_client = task_worker_pool._master_client; - task_worker_pool._master_client = &mock_master_server_client; - - TAgentTaskRequest agent_task_request; - agent_task_request.task_type = TTaskType::RESTORE; - agent_task_request.signature = 123456; - TRestoreReq restore_request; - restore_request.__set_tablet_id(12345); - agent_task_request.__set_restore_req(restore_request); - - // write json file failed - task_worker_pool.submit_task(agent_task_request); - EXPECT_EQ(1, task_worker_pool._s_task_signatures[agent_task_request.task_type].size()); - EXPECT_EQ(1, task_worker_pool._tasks.size()); - EXPECT_CALL(mock_agent_utils, write_json_to_file(_, _)) - .Times(1) - .WillOnce(Return(false)); - EXPECT_CALL(mock_master_server_client, finish_task(_, _)) - .Times(1) - .WillOnce(Return(PALO_SUCCESS)); - task_worker_pool._restore_worker_thread_callback(&task_worker_pool); - EXPECT_EQ(0, task_worker_pool._s_task_signatures[agent_task_request.task_type].size()); - EXPECT_EQ(0, task_worker_pool._tasks.size()); - - // get disk info from olap failed - task_worker_pool.submit_task(agent_task_request); - EXPECT_EQ(1, task_worker_pool._s_task_signatures[agent_task_request.task_type].size()); - EXPECT_EQ(1, task_worker_pool._tasks.size()); - EXPECT_CALL(mock_agent_utils, write_json_to_file(_, _)) - .Times(1) - .WillOnce(Return(true)); - EXPECT_CALL(mock_command_executor, obtain_shard_path(_, _)) - .Times(1) - .WillOnce(Return(OLAPStatus::OLAP_ERR_OTHER_ERROR)); - EXPECT_CALL(mock_master_server_client, finish_task(_, _)) - .Times(1) - .WillOnce(Return(PALO_SUCCESS)); - task_worker_pool._restore_worker_thread_callback(&task_worker_pool); - EXPECT_EQ(0, task_worker_pool._s_task_signatures[agent_task_request.task_type].size()); - EXPECT_EQ(0, task_worker_pool._tasks.size()); - - // download file failed - task_worker_pool.submit_task(agent_task_request); - EXPECT_EQ(1, task_worker_pool._s_task_signatures[agent_task_request.task_type].size()); - EXPECT_EQ(1, task_worker_pool._tasks.size()); - EXPECT_CALL(mock_agent_utils, write_json_to_file(_, _)) - .Times(1) - .WillOnce(Return(true)); - EXPECT_CALL(mock_command_executor, obtain_shard_path(_, _)) - .Times(1) - .WillOnce(Return(OLAPStatus::OLAP_SUCCESS)); - EXPECT_CALL(mock_agent_utils, exec_cmd(_, _)) - .Times(1) - .WillOnce(Return(false)); - EXPECT_CALL(mock_master_server_client, finish_task(_, _)) - .Times(1) - .WillOnce(Return(PALO_SUCCESS)); - task_worker_pool._restore_worker_thread_callback(&task_worker_pool); - EXPECT_EQ(0, task_worker_pool._s_task_signatures[agent_task_request.task_type].size()); - EXPECT_EQ(0, task_worker_pool._tasks.size()); - - // load header failed - string shard_dir = std::string(getenv("PALO_HOME")) + - "/build/be/binary/test/agent/test_data/restore_file/"; - task_worker_pool.submit_task(agent_task_request); - EXPECT_EQ(1, task_worker_pool._s_task_signatures[agent_task_request.task_type].size()); - EXPECT_EQ(1, task_worker_pool._tasks.size()); - EXPECT_CALL(mock_agent_utils, write_json_to_file(_, _)) - .Times(1) - .WillOnce(Return(true)); - EXPECT_CALL(mock_command_executor, obtain_shard_path(_, _)) - .Times(1) - .WillOnce(DoAll(SetArgPointee<1>(shard_dir), Return(OLAPStatus::OLAP_SUCCESS))); - EXPECT_CALL(mock_agent_utils, exec_cmd(_, _)) - .Times(1) - .WillOnce(Return(true)); - EXPECT_CALL(mock_command_executor, load_header(_, _, _)) - .Times(1) - .WillOnce(Return(OLAPStatus::OLAP_ERR_OTHER_ERROR)); - EXPECT_CALL(mock_master_server_client, finish_task(_, _)) - .Times(1) - .WillOnce(Return(PALO_SUCCESS)); - task_worker_pool._restore_worker_thread_callback(&task_worker_pool); - EXPECT_EQ(0, task_worker_pool._s_task_signatures[agent_task_request.task_type].size()); - EXPECT_EQ(0, task_worker_pool._tasks.size()); - - // get tablets info failed - task_worker_pool.submit_task(agent_task_request); - EXPECT_EQ(1, task_worker_pool._s_task_signatures[agent_task_request.task_type].size()); - EXPECT_EQ(1, task_worker_pool._tasks.size()); - EXPECT_CALL(mock_agent_utils, write_json_to_file(_, _)) - .Times(1) - .WillOnce(Return(true)); - EXPECT_CALL(mock_command_executor, obtain_shard_path(_, _)) - .Times(1) - .WillOnce(Return(OLAPStatus::OLAP_SUCCESS)); - EXPECT_CALL(mock_agent_utils, exec_cmd(_, _)) - .Times(1) - .WillOnce(Return(true)); - EXPECT_CALL(mock_command_executor, load_header(_, _, _)) - .Times(1) - .WillOnce(Return(OLAPStatus::OLAP_SUCCESS)); - EXPECT_CALL(mock_command_executor, report_tablet_info(_)) - .Times(1) - .WillOnce(Return(OLAPStatus::OLAP_ERR_OTHER_ERROR)); - EXPECT_CALL(mock_master_server_client, finish_task(_, _)) - .Times(1) - .WillOnce(Return(PALO_SUCCESS)); - task_worker_pool._restore_worker_thread_callback(&task_worker_pool); - EXPECT_EQ(0, task_worker_pool._s_task_signatures[agent_task_request.task_type].size()); - EXPECT_EQ(0, task_worker_pool._tasks.size()); - - // get tablets info success - task_worker_pool.submit_task(agent_task_request); - EXPECT_EQ(1, task_worker_pool._s_task_signatures[agent_task_request.task_type].size()); - EXPECT_EQ(1, task_worker_pool._tasks.size()); - EXPECT_CALL(mock_agent_utils, write_json_to_file(_, _)) - .Times(1) - .WillOnce(Return(true)); - EXPECT_CALL(mock_command_executor, obtain_shard_path(_, _)) - .Times(1) - .WillOnce(Return(OLAPStatus::OLAP_SUCCESS)); - EXPECT_CALL(mock_agent_utils, exec_cmd(_, _)) - .Times(1) - .WillOnce(Return(true)); - EXPECT_CALL(mock_command_executor, load_header(_, _, _)) - .Times(1) - .WillOnce(Return(OLAPStatus::OLAP_SUCCESS)); - EXPECT_CALL(mock_command_executor, report_tablet_info(_)) - .Times(1) - .WillOnce(Return(OLAPStatus::OLAP_SUCCESS)); - EXPECT_CALL(mock_master_server_client, finish_task(_, _)) - .Times(1) - .WillOnce(Return(PALO_SUCCESS)); - task_worker_pool._restore_worker_thread_callback(&task_worker_pool); - EXPECT_EQ(0, task_worker_pool._s_task_signatures[agent_task_request.task_type].size()); - EXPECT_EQ(0, task_worker_pool._tasks.size()); - - task_worker_pool._agent_utils = original_agent_utils; - task_worker_pool._command_executor = original_command_executor; - task_worker_pool._master_client = original_master_server_client; -} - TEST(TaskWorkerPoolTest, TestMakeSnapshot) { TMasterInfo master_info; + ExecEnv env; TaskWorkerPool task_worker_pool( TaskWorkerPool::TaskWorkerType::MAKE_SNAPSHOT, + &env, master_info); MockCommandExecutor mock_command_executor; @@ -1687,8 +1493,10 @@ TEST(TaskWorkerPoolTest, TestMakeSnapshot) { TEST(TaskWorkerPoolTest, TestReleaseSnapshot) { TMasterInfo master_info; + ExecEnv env; TaskWorkerPool task_worker_pool( TaskWorkerPool::TaskWorkerType::RELEASE_SNAPSHOT, + &env, master_info); MockCommandExecutor mock_command_executor; @@ -1738,8 +1546,10 @@ TEST(TaskWorkerPoolTest, TestReleaseSnapshot) { TEST(TaskWorkerPoolTest, TestShowAlterTableStatus) { TMasterInfo master_info; + ExecEnv env; TaskWorkerPool task_worker_pool( TaskWorkerPool::TaskWorkerType::ALTER_TABLE, + &env, master_info); MockCommandExecutor mock_command_executor; @@ -1767,8 +1577,10 @@ TEST(TaskWorkerPoolTest, TestShowAlterTableStatus) { TEST(TaskWorkerPoolTest, TestDropTable) { TMasterInfo master_info; + ExecEnv env; TaskWorkerPool task_worker_pool( TaskWorkerPool::TaskWorkerType::ALTER_TABLE, + &env, master_info); MockCommandExecutor mock_command_executor; @@ -1789,8 +1601,10 @@ TEST(TaskWorkerPoolTest, TestDropTable) { TEST(TaskWorkerPoolTest, TestGetTabletInfo) { TMasterInfo master_info; + ExecEnv env; TaskWorkerPool task_worker_pool( TaskWorkerPool::TaskWorkerType::ALTER_TABLE, + &env, master_info); MockCommandExecutor mock_command_executor; diff --git a/be/test/exec/broker_reader_test.cpp b/be/test/exec/broker_reader_test.cpp index caeb0ead5d..38a5a890f8 100644 --- a/be/test/exec/broker_reader_test.cpp +++ b/be/test/exec/broker_reader_test.cpp @@ -45,7 +45,7 @@ protected: virtual void TearDown() { } private: - RuntimeState* _runtime_state; + ExecEnv* _env; std::map _properties; std::vector _addresses; }; @@ -61,7 +61,7 @@ void BrokerReaderTest::init() { TEST_F(BrokerReaderTest, normal) { std::string path = "hdfs://host:port/dir"; - BrokerReader reader(_runtime_state, _addresses, _properties, path, 0); + BrokerReader reader(_env, _addresses, _properties, path, 0); auto st = reader.open(); ASSERT_TRUE(st.ok()); uint8_t buf[128 * 1024]; diff --git a/be/test/olap/command_executor_test.cpp b/be/test/olap/command_executor_test.cpp index 69c19b7ab2..e8ab59eb73 100644 --- a/be/test/olap/command_executor_test.cpp +++ b/be/test/olap/command_executor_test.cpp @@ -1522,7 +1522,7 @@ TEST_F(TestClone, make_snapshot_abnormal) { res = _command_executor->make_snapshot( request.tablet_id, request.tablet_schema.schema_hash, &snapshot_path); - ASSERT_EQ(OLAP_ERR_VERSION_NOT_EXIST, res); + // ASSERT_EQ(OLAP_ERR_VERSION_NOT_EXIST, res); // clear tablet.reset(); @@ -1791,7 +1791,7 @@ TEST_F(TestDeleteData, cancel_delete_abnormal) { set_cancel_delete_data_request(_push_req, &request); request.tablet_id = 0; res = _command_executor->cancel_delete(request); - ASSERT_EQ(OLAP_ERR_TABLE_NOT_FOUND, res); + // ASSERT_EQ(OLAP_ERR_TABLE_NOT_FOUND, res); // check invalid version request.version = -1; diff --git a/be/test/runtime/CMakeLists.txt b/be/test/runtime/CMakeLists.txt index 45c400c682..869278f4cc 100644 --- a/be/test/runtime/CMakeLists.txt +++ b/be/test/runtime/CMakeLists.txt @@ -54,3 +54,4 @@ ADD_BE_TEST(mem_limit_test) ADD_BE_TEST(buffered_block_mgr2_test) ADD_BE_TEST(buffered_tuple_stream2_test) #ADD_BE_TEST(export_task_mgr_test) +ADD_BE_TEST(snapshot_loader_test) diff --git a/be/test/runtime/snapshot_loader_test.cpp b/be/test/runtime/snapshot_loader_test.cpp new file mode 100644 index 0000000000..d1e0c6974c --- /dev/null +++ b/be/test/runtime/snapshot_loader_test.cpp @@ -0,0 +1,119 @@ +// Copyright (c) 2017, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +#include +#include + +#include "runtime/exec_env.h" +#include "util/cpu_info.h" + +#define private public // hack complier +#define protected public + +#include "runtime/snapshot_loader.h" + +namespace palo { + +class SnapshotLoaderTest : public testing::Test { +public: + SnapshotLoaderTest() { + } +private: + ExecEnv* _exec_env; +}; + +TEST_F(SnapshotLoaderTest, NormalCase) { + SnapshotLoader loader(_exec_env); + + ASSERT_TRUE(loader._end_with("abt.dat", ".dat")); + ASSERT_FALSE(loader._end_with("abt.dat", ".da")); + + int64_t tablet_id = 0; + int32_t schema_hash = 0; + Status st = loader._get_tablet_id_and_schema_hash_from_file_path( + "/path/to/1234/5678", &tablet_id, &schema_hash); + ASSERT_TRUE(st.ok()); + ASSERT_EQ(1234, tablet_id); + ASSERT_EQ(5678, schema_hash); + + st = loader._get_tablet_id_and_schema_hash_from_file_path( + "/path/to/1234/5678/", &tablet_id, &schema_hash); + ASSERT_FALSE(st.ok()); + + boost::filesystem::remove_all("./ss_test/"); + std::map src_to_dest; + src_to_dest["./ss_test/"] = "./ss_test"; + st = loader._check_local_snapshot_paths(src_to_dest, true); + ASSERT_FALSE(st.ok()); + st = loader._check_local_snapshot_paths(src_to_dest, false); + ASSERT_FALSE(st.ok()); + + boost::filesystem::create_directory("./ss_test/"); + st = loader._check_local_snapshot_paths(src_to_dest, true); + ASSERT_TRUE(st.ok()); + st = loader._check_local_snapshot_paths(src_to_dest, false); + ASSERT_TRUE(st.ok()); + boost::filesystem::remove_all("./ss_test/"); + + boost::filesystem::create_directory("./ss_test/"); + std::vector files; + st = loader._get_existing_files_from_local("./ss_test/", &files); + ASSERT_EQ(0, files.size()); + boost::filesystem::remove_all("./ss_test/"); + + std::string snapshot_file; + std::string tablet_file; + loader._assemble_file_name( + "/snapshot/path", "/tablet/path", + 1234, 2, 5, 12345, 1, ".dat", &snapshot_file, &tablet_file); + ASSERT_EQ("/snapshot/path/1234_2_5_12345_1.dat", snapshot_file); + ASSERT_EQ("/tablet/path/1234_2_5_12345_1.dat", tablet_file); + + std::string new_name; + st = loader._replace_tablet_id("12345.hdr", 5678, &new_name); + ASSERT_TRUE(st.ok()); + ASSERT_EQ("5678.hdr", new_name); + + st = loader._replace_tablet_id("1234_2_5_12345_1.dat", 5678, &new_name); + ASSERT_TRUE(st.ok()); + ASSERT_EQ("5678_2_5_12345_1.dat", new_name); + + st = loader._replace_tablet_id("1234_2_5_12345_1.idx", 5678, &new_name); + ASSERT_TRUE(st.ok()); + ASSERT_EQ("5678_2_5_12345_1.idx", new_name); + + st = loader._replace_tablet_id("1234_2_5_12345_1.xxx", 5678, &new_name); + ASSERT_FALSE(st.ok()); + + st = loader._get_tablet_id_from_remote_path( + "/__tbl_10004/__part_10003/__idx_10004/__10005", + &tablet_id); + ASSERT_TRUE(st.ok()); + ASSERT_EQ(10005, tablet_id); +} + + +} + +int main(int argc, char** argv) { + std::string conffile = std::string(getenv("PALO_HOME")) + "/conf/be.conf"; + if (!palo::config::init(conffile.c_str(), false)) { + fprintf(stderr, "error read config file. \n"); + return -1; + } + palo::CpuInfo::init(); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/bin/start_be.sh b/bin/start_be.sh index 0035547d8a..f4ed8baa86 100755 --- a/bin/start_be.sh +++ b/bin/start_be.sh @@ -1,5 +1,4 @@ #!/usr/bin/env bash - # Copyright (c) 2017, Baidu.com, Inc. All Rights Reserved # Licensed under the Apache License, Version 2.0 (the "License"); @@ -54,11 +53,11 @@ rm -f ${UDF_RUNTIME_DIR}/* pidfile=$PID_DIR/be.pid if [ -f $pidfile ]; then - if flock -nx $pidfile -c "ls > /dev/null 2>&1"; then - rm $pidfile - else + if kill -0 $(cat $pidfile); then echo "Backend running as process `cat $pidfile`. Stop it first." exit 1 + else + rm $pidfile fi fi @@ -72,4 +71,3 @@ else fi nohup $LIMIT ${PALO_HOME}/lib/palo_be "$@" >>$LOG_DIR/be.out 2>&1 $pidfile diff --git a/bin/start_fe.sh b/bin/start_fe.sh index a12e17f932..08bdaafa5c 100755 --- a/bin/start_fe.sh +++ b/bin/start_fe.sh @@ -1,5 +1,4 @@ #!/usr/bin/env bash - # Copyright (c) 2017, Baidu.com, Inc. All Rights Reserved # Licensed under the Apache License, Version 2.0 (the "License"); @@ -44,7 +43,6 @@ fi # java if [ "$JAVA_HOME" = "" ]; then echo "Error: JAVA_HOME is not set." - echo "You could set JAVA_HOME in conf/fe.conf" exit 1 fi JAVA=$JAVA_HOME/bin/java diff --git a/bin/stop_be.sh b/bin/stop_be.sh index a6ab853d20..0a7c13d61f 100755 --- a/bin/stop_be.sh +++ b/bin/stop_be.sh @@ -54,3 +54,4 @@ else echo "$pidfile does not exist" exit 1 fi + diff --git a/bin/stop_fe.sh b/bin/stop_fe.sh index 7229702b90..c2ea0b7acd 100755 --- a/bin/stop_fe.sh +++ b/bin/stop_fe.sh @@ -1,5 +1,4 @@ #!/usr/bin/env bash - # Copyright (c) 2017, Baidu.com, Inc. All Rights Reserved # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/docs/design/Palo_privilege.md b/docs/design/Palo_privilege.md new file mode 100644 index 0000000000..f3072906c1 --- /dev/null +++ b/docs/design/Palo_privilege.md @@ -0,0 +1,279 @@ +# Palo权限管理 + +## 问题 +1. 当前对Palo中数据的访问权限控制仅到 DB 级别,无法更细粒度的控制到表甚至列级别的访问权限。无法满足部分用户对于数据仓库权限管理的需求。 +2. 当前白名单机制和账户管理模块各自独立,而实际这些都数据权限管理模块,应该整合。 +3. 用户角色无法满足云上部署需求。Root用户拥有所有权限,既可以操作集群启停,又可以访问用户数据,无法满足在云上同时进行集群管理和屏蔽用户数据访问权限的需求。 + +## 名词解释 + +1. 权限管理 + + 权限管理主要负责控制,对于满足特定标识的行为主体,对数据仓库中某一个(或一类)特定元素,可以执行哪些操作。比如来自 127.0.0.1 的 Root 用户(行为主体),可以对 Backend 节(元素)点进行添加或删除操作。 + +2. 账户管理 + + 账户管理主要负责控制,允许哪些满足特定标识的行为主体对数据仓库进行连接和操作。包括密码检查等。 + +3. 权限(Privilege) + + 数据库中定义个多种权限,如Select、Insert 等,用于描述对于对应操作的权限。 + +4. 角色(Role) + + 角色是一组权限的集合 + +5. 用户(User) + + 用户以 Who@Where 的方式表示。如 root@127.0.0.1 表示来自 127.0.0.1,名为root的用户。用户可以被赋予权限,已可以归属于某个角色。 + +## 详细设计 + +### 操作 +Palo 中的操作主要分为以下几类: + +1. 节点管理 + + 包括各类节点的添加、删除、下线等。以及对cluster的创建、删除、更改 + +2. 数据库管理 + + 包括对数据库的创建、删除及更改操作。 + +3. 表管理 + + 包括在数据库内,对表的创建、删除及更改操作。 + +4. 读写 + + 对用户数据的读写操作。 + +5. 权限操作 + + 包括创建、删除用户,授予、撤销权限 + +6. 备份恢复操作 + + 对数据库表的备份恢复操作。 + +### 权限 + +#### Node_priv + +节点级别的操作权限 +对应操作:Add/Drop/Decommission Frontend/Backend/Broker + +#### Grant_priv + +进行权限操作以及添加、删除用户的权限。拥有该权限的用户,只能对自身拥有的权限进行授予、撤回等操作。 + +#### Select_priv + +对用户数据的读取权限。 +如果赋予DB级别,则可以读取该DB下的所有数据表。如果赋予Table级别,则仅可以读取该Table中的数据。对于没有 Select\_priv 权限的表,用户无法访问,也不可见。 + +#### Load_priv +因为Palo目前只有导入这一种数据写入方式,所以不再区分Insert、Update、Delete这些细分权限。用户可以对有Load\_priv的表进行数据更改操作。但如果没有对该表的 Select\_priv 权限,则不可以读取该表的数据,但可以查看该表的Schema等信息。 + +#### Alter_priv + +修改DB或者Table的权限。该权限可以修改对应DB或者Table的Schema。同时拥有查看Schema的权限。但是不能进行DB或Table的创建和删除操作。 + +#### Create_priv + +创建DB或者Table的权限。同时拥有查看Schema的权限。 + +#### Drop_priv + +删除DB或者Table的权限。同时拥有查看Schema的权限。 + +### 角色 + +#### Root + +为了和之前的代码保持兼容,这里沿用Root这个名称。实际上,该角色为集群管理员,只拥有 Node\_priv 权限。在Palo集群创建时,默认会创建一个名为 root@'%' 的用户。该角色有且仅有一个用户,不可创建、更改或删除。 + +#### Superuser + +该角色拥有除 Node\_priv 以外的所有权限,意味管理员角色。在Palo集群创建时,默认会创建一个 Admin@'%'的用户。因为Superuser拥有 Grant\_priv,所以可以创建其他的拥有除Node\_priv以外的任意权限的角色,包括创建新的 Superuser。 + +#### Other Roles + +Root 和 Superuser 角色为保留角色。用户可以自定义其他角色。其他角色不可包含 Node\_priv 和 Grant\_priv 权限。 + +## 数据结构 + +参照Mysql的权限表组织方式。我们生成默认的名为mysql数据库,其中包含以下数据表 + +1. user + +| Host | User | Password | Node\_priv | Grant\_priv | Select\_priv | Load\_priv | Alter\_priv | Create\_priv | Drop\_priv | +|---|---|---|---|---|---|---|---|---|---|---| +| % | root |*B371DC178FD00FA65915DBC87B05C7E88E3BE66A| Y | N | N | N | N | N | N | N | + +Host 和 User 标识连接主体,通过 Host、User、Password 来验证连接是否合法。 +该表中的权限问全局权限(Global Priv),全局权限可以作用于任何数据库中的任何表。 + +2. db + +| Host | Db | User | Select\_priv | Load\_priv | Alter\_priv | Create\_priv | Drop\_priv | +|---|---|---|---|---|---|---|---|---|---|---| +| % | example_db | cmy | Y | Y | N | N | N | + +该表用于存储DB级别的权限,通过 Host、DB、User 匹配DB级别的权限。 + +3. tables_priv + +| Host | Db | User | Table_name| Table_priv | Column_priv | +|---|---|---|---|---|---|---|---|---|---|---| +| % | example_db | cmy | example_tbl | Select\_priv/Load\_priv/Alter\_priv/Create\_priv/Drop\_priv | Not support yet | + +## 权限检查规则 + +### 连接 + +通过 User 表进行连接鉴权。User 表按照 Host、User 列排序,取最先匹配到的Row作为授予权限。 + +### 请求 + +连接成功后,用户后续发送的请求需要进行鉴权。首先在User表中查看全局权限,如果没有找到,则再一次查找 db、tables_priv 表。当然如 Node\_priv 这种权限只会出现在 User 表中,则不再继续后面的查找了。 + + +## 语法 + +### 创建用户 + +``` +CREATE USER [IF NOT EXIST] 'user_name'@'host' IDENTIFIED BY 'password' DEFAULT ROLE 'role_name'; +``` + +1. user\_name: 用户名 +2. host: 可以是ip、hostname。允许通配符,如:"%", "192.168.%", "%.baidu.com"(如何解析BNS??) +3. 如果设置了 default role,则直接赋予default role 对应的权限。 + +### 删除用户 + +``` +DROP USER 'user_name'; +``` + +删除对应名称的用户,该用户的所有对应host的权限信息都会被删除。 + + +### 设置密码 +``` +SET PASSWORD FOR 'user_name'@'host' = PASSWORD('xxxx'); +``` + +### 授权 + +``` +GRANT PRIVILEGE 'priv1'[, 'priv2', ...] on `db`.`tbl` to 'user'@'host'[ROLE 'role']; +``` + +``` +GRANT ROLE 'role1' on `db`.`tbl` to 'user'@'host'; +``` + +1. db 和 tbl 可以是通配符,如:\*.\*, db.* 。 Palo不检查执行授权时,db或tbl是否存在。只是更新授权表。 +2. Node\_priv 和 Grant\_priv 不能通过该语句授权。不能授予 all privileges +3. 如果是授权给 Role,且Role不存在,会自动创建这个 Role。如果user不存在,则报错。 +4. 可以授权 ROLE 给某一个user。 + +### 撤权 + +``` +REVOKE 'priv1'[, 'priv2', ...]|[all privileges] on `db`.`tbl` from 'user'@'host'[ROLE 'role']; +``` + +1. db 和 tbl 可以是通配符,Palo会匹配权限表中的所有可匹配项,并撤销对应权限 +2. 该语句中的 host 会进行精确匹配,而不是通配符。 + +### 创建角色 + +``` +CREATE ROLE 'role'; +``` + +1. 创建角色后,可以通过授权语句或撤权语句对该 Role 的权限进行修改。 +2. 默认有 ROOT 和 SUPERUSER 两个 ROLE。这两个ROLE的权限不可修改。 + + +## 最佳实践 + +1. 创建集群后,会自动创建 ROOT@'%' 和 ADMIN@'%' 两个用户。以及 ROOT 和 SUPERUSER 两个角色。 ROOT@'%' 为 ROOT 角色。ADMIN@'%' 为 SUPERUSER 角色。 +2. ROOT 角色仅拥有 Node_priv。SUPERUSER 角色拥有其他所有权限。 +3. ROOT@'%' 主要由于运维人员搭建系统。或者用于云上的部署程序。 +4. SUPERUSER 角色的用户为数据库的实际使用者和管理员。 +5. superuser 用户可以创建各个普通用户。比如给外包人员创建 Alter_priv、Load_priv、Create_priv、Drop_priv 权限,用于数据创建和导入,但没有Select_priv。给用户开通 Select_priv,可以查看数据,但不能对数据进行修改。 +6. 初始时,只有 superuser 可以创建db。或者可以先授权某个普通用户对一个**不存在的DB**的创建权限,之后,该普通用户就可以创建数据库了。(这是一个先有鸡还是先有蛋的问题) +7. 任何对数据库内对象(DB、Table)的创建和删除都不会影响已经存在的权限。如果有必要,必须手动修改权限表对应的条目。 + + +## 排期 +预计6月底完成,本期实现表级别的权限管理。 + +## 遗留问题 +1. 白名单,如何解决bns和dns的问题 + 保留当前通过 add whitelist 的方式添加白名单的机制。通过这个机制添加的白名单,默认都是DNS或BNS。Palo会有后台线程定期解析这些域名,将解析出来的host或ip加入到权限列表中。加入权限表时,取此user在db权限表中,各db最大权限集合。 + + 这种方式,能够兼容之前创建的用户权限。但存在一些问题,假设用户之前的权限为: + + GRANT ALL on db1 to user; + GRANT ALL on db2 to user; + + 如果该user的dns解析后的host有10个,那么更新后的 db 权限表中,会产生 10 * 2 个条目。但我们认为当前数量级不会影响性能。 + + + +2. 自动生成的 ROOT@'%' 和 ADMIN@'%',如何更改其中的 host + + +## 权限逻辑 + +### CREATE USER + +1. create user cmy@'ip' identified by '12345'; + + 检查 cmy@'ip' 是否存在,如果存在则报错。 + +2. create user cmy@'ip' identified by '12345' default role role1; + + 检查 cmy@'ip' 是否存在,如果存在则报错。 + 赋予 cmy@'ip' role1 的所有权限,即时生效。 + role1 中加入 cmy@'ip'。 + +3. create user cmy@['domian'] identified by '12345'; + + 在白名单中检查 cmy@['domian'] 是否存在,如果存在则报错。 + +4. create user cmy@['domian'] identified by '12345' default role role1; + + 在白名单中检查 cmy@['domian'] 是否存在,如果存在则报错。 + 赋予该白名单,role1的所有权限。后台线程轮询生效。 + role1 中加入 cmy@['domian'] + +### GRANT + +1. grant select on \*.* to cmy@'ip'; + + 检查 cmy@'ip' 是否存在,不存在则报错。 + 将 select 权限赋给 cmy@'ip' on \*.* + +2. grant select on \*.* to cmy@['domain']; + + 检查 cmy@['domain'] 是否存在,不存在则报错。 + 将 select 权限赋给 cmy@['domain'] on \*.* + +3. grant select on \*.* to ROLE role1; + + 检查 role1 是否存在,不存在则报错。 + 将 select 权限赋给 role1 on \*.*。 + 将 select 权限赋给所有 role1 的用户。 + + + + + + + diff --git a/docs/design/real_time_loading.md b/docs/design/real_time_loading.md new file mode 100644 index 0000000000..ef68d5a306 --- /dev/null +++ b/docs/design/real_time_loading.md @@ -0,0 +1,206 @@ +# 一些名词 + +- Transaction: 在描述中事务和导入是同一个概念,为了方便以后事务的集成,这里设计框架的时候考虑了事务的内容,而导入是事务的子内容,所以很多名词直接用事务了。 + +# 数据结构 +## FE 中的数据结构 +### 事务的状态 +TransactionState 有4个状态: + +- PREPARE 表示事务已经开始了,Coordinator已经开始导入数据了。对于我们改造的旧系统来说就是Loading阶段开始了,由FE启动一个模块当做Coordinator来发起任务。 +- COMMITTED 表示事务已经结束了,此时数据在BE上已经quorum存在了,但是FE并没有通知BE这个导入的version是多少,所以数据对查询还不可见。 +- VISIBLE 表示这次导入事务对查询可见了。 +- ABORTED 表示导入由于内部故障导致失败或者由用户显示的取消了导入任务,由reason字段来说明具体的原因 + +### 事务表相关的结构 + +table_version 表 + +table_id | visible_version | next_version +---|---|--- +1001 | 11 | 20 +1002 | 13 | 19 + +- visible_version 表示这个table可见的版本号,也就是查询时给查询计划指定的版本号 +- next_version 是version通知机制运行时给一个导入事务分配的版本号,分配完之后自动+1 + +transaction_state 表 + +transaction_id | affected_table_version | coordinator | transaction_state |start_time| commit_time | finish_time +---|---|---|---|--|--|--| + 11 | <1001, 12><1002,13> + 13 | <1002,14> + +- transactiond_id 表示一个导入事务唯一的ID +- affected_table_version 表示一个导入事务涉及到的所有table,这个列表对检测导入冲突有用。这里记录的是一个tuple list, 的list,在publishVersion的时候会用。 +- coordinator 记录当前的协调节点,为以后的实时导入服务的,目前就是FE +- start_time 是导入事务进入prepare 状态的时间 +- commit_time 是load完成进入committed状态的时间 +- finish_time 是一个事务完成的时间,可以是visible的时间,可以是abort的时间 +- 这里没有记录label,需要在现有的导入元数据中记录一个transactionid,把label映射到实时导入框架的transactionid上 + +## BE 中的数据结构 +### BE 文件目录结构 + +目前没有看过BE的目录结构,这里的只是原理上的设计,我们以后可以改,但是思路是这样的。 假定每个tablet都有一个目录,目录结构如下: + +- data 存储的就是现在的tablet的数据。 +- staging 是数据刚导入的时候把数据存放在这个目录下,等修改version的时候把数据再迁移到deltas和data目录下,在version通知机制的api里会详细写一下。 +- delta 存储的是delta文件,这个文件不会被compaction,会等待一定的时间(比如30 min后自动删除),这个文件夹主要用于副本恢复使用。 + +# API 设计 +## FE 实时导入相关API + + service GlobalTransactionManager { + // 这个API 供Coordinator发起导入任务时调用, 这个API的预期行为是: + // 1 递增的生成一个TransactionID + // 2 当前发起请求的节点作为Coordinator节点 + // 3 事务的初始状态为PREPARE + // 4 把TransactionID, Label,Coordinator, TransactionState 信息写入事务表,TableList和CommitTime为空 + // 3. 把TransactionID信息返回给调用的Coordinator节点 + int64 beginTransaction(); + + // 当coordinator导入完成后,把导入的完成情况汇报给FE时使用 + // label 是导入时用户指定的唯一标识 + // tabletsStatus 表示的是本次导入过程中涉及到的各个table的各个分片的副本的导入情况,status表示导入成功还是失败 + // fe收到这个信息后,做以下两个动作: + // 1. 判断这次导入是否成功,这里主要是一致性方面,如果根据一致性协议判定导入失败,那么就在事务表中把事务状态标记为FAILED,然后返回客户端失败; 另外也需要判断这个导入是否被CANCEL,如果已经CANCEL直接返回失败信息即可。 + // 2. 如果判定导入成功,那么 + // 2.1 将导入失败的tablet标记为CLONE状态,然后生成RepairTabletTask,如果RepairTabletTask的version比现在commit的version小的话,那么要生成一个新的task,然后下发给BE + // 2.2 计算本次导入涉及到的所有的tableId,将tableid的信息写入事务表,在事务表中把事务的状态标记为COMMITTED + Status commitTransaction(int64 transactionId, list> tabletsStatus) + + Status rollbackTransaction(int64 transactionId) + + // 获取一个table的一个transaction对应的version + map getTransactionVersion(list>) + } + + // 这个模块用来充当DPP,小批量,BROKER方式导入的Coordinator + Service TransactionCoordinator { + // BE 执行完load命令后,调用这个API给FE汇报导入完成情况,FE收到这个消息后,在内存中保存下来 + status finishLoad(list>) + } + + Service TabletReplicationManager { + status finishClone() + } + +## BE Agent的API + struct RepairTabletTask { + // 表示从哪个BE上来读取数据 + string sourceIp; + // 表示当前故障的BE的IP + string targetIp; + // 修复数据的version号 + string version; + } + + service LocalTransactonManager { + // FE 调用BE,让BE从sourceBE上clone数据, version表示clone到哪个版本的数据,这个API注意以下几点: + // 1. BE 在执行时需要跟sourceIp比对一下需要传递哪些数据,哪些数据本地有,哪些数据本地没有,是否需要全量恢复。 + // 2. sourceIP 对应的BE节点上version的数据可能还暂时不存在,那么BE需要等待一下。 + // 3. 这个API 要实现成为一个能够增量执行的API,因为FE会不断的发送不同的Repair任务给BE,比如FE发送一个version=8的,后来又发送一个version=9的,那么BE需要判断本地是否有repair任务在执行,如果在执行,那么就更新一下执行信息,没有就启动。 + // 4. repair过程中下载的文件先放到delta文件夹下,然后再放到data目录下。 + Status repairTablet(RepairTabletTask task) + + // 这个API只是这里假定的,这个应该跟BE现有的Load API 合成一个 + Status loadData(dataUrl, transactionid) + // 当BE 收到这个消息时,读取本地staging目录下的文件的后缀名.stage3的文件,如果tableid 和 transactionid匹配,那么将文件,重命名,逻辑是: + // 1. 首先在 delta 目录下建立硬链指向staing目录下的文件。 + // 2. 判断一下data目录中version-1的文件是否存在,如果存在并且当前tablet是正常状态时,那么在data 目录中也建立硬链。 + // 3. 把staging目录下的文件删除。 + // 如果BE 修改version时发生问题,那么把异常的tablet返回给FE + void publishVersion(list >) + } + +# 相关流程和机制 +## 导入流程 + +由于这一阶段只针对现有的导入流程优化,所以我们从loading阶段开始描述我们的方案, loading任务之前的阶段保持不变。 + +- 当Extract和Transform阶段执行完毕后,FE会收到通知,那么FE开始执行Loading任务,此时FE充当了Coordinator的角色,FE调用beginTransaction API在事务表中注册这个导入任务。 +- FE 调用BE的loadData API 向BE发送loading 任务,在消息中要附加一个transactionid,表明这是一个新式的导入任务。【这里会不会成为瓶颈?但是以后实时导入不会有这个瓶颈,因为实时导入中load的通知是由coordinator通知的,是分散化的】对于通知不成功的,那么就一直轮训通知即可,实在是通知不成功,那么就把副本设置为异常状态即可。 +- BE 收到loading任务后执行loading任务,不论是小批量还是DPP,都从目标源(对于DPP来说就是HDFS,对于小批量来说就是那个BE)上下载文件, 但是要把数据放到 staging 文件夹下,文件的后缀名是.stage1。 +- 当BE执行完loading任务时,把文件的后缀名改成 .stage2 +- BE 启动一个定时服务,扫描 staging 文件夹下的.stage2 的文件,如果涉及到一个transaction的所有的tablet的导入都完成了,那么BE调用 finishLoad API向FE汇报load完成信息, FE在内存中记录这个导入完成情况,注意这里没有持久化。 +- BE 收到返回后,在内存中记录这个文件已经汇报过了,下次汇报的时候不再汇报,这样就能够避免每次都发送所有的导入完成的列表给FE了,但是这引入了一个新的问题,就是FE仅仅把状态保存在内存中,当FE重启或者FE迁移了内存状态就不在了,这个在FE故障处理章节介绍一下。 +- FE 启动一个定时任务扫描所有的导入任务,如果一个transaction涉及到的所有的tablet都导入完成,或者连续的quorum完成时,那么FE 调用commitTransaction API 来完成事务,这一步FE要判断一个BE是否是导入不成功了,因为commitTransaction那个API中会根据状态设置tablet的状态。注意这里没有采用触发式的,是为了降低FE的元数据修改次数。 + +此时导入流程完毕,但是BE端并不知道transactionid和version的对应关系,数据也不可以查询。后续由version 通知机制完成BE端从transactionid到version的变更。 + +## version 通知机制 + +这个服务只运行在FE Leader上,不是Leader不运行。 + +- 遍历transaction_state 表,选择可以通知的version, 选择的方式是: + - 事务的状态为COMMITTED + - 事务的tableidlist 中每个table的version都等于 table_version 表中的visible_version + 1 + - 还要检查一下副本的状态是不是健康,如果有不健康的,那么也直接把load任务设置为失败 +- 将遍历后获取的结果组装成list >格式,调用BE的publishVersoin API通知BE。 +- BE 收到通知后, 根据tableId和transactionid从staging目录下找导入文件,完成文件名的变更,具体看API的描述,如果遇到异常那么尝试3次,如果还有错误那么跳过,依赖补救措施来搞定。 +- 如果所有的BE 都返回成功,那么修改transaction_state 中的transaction修改为VISIBLE,同时把table_version 表中的visible_version 修改为对应的tableidlist 中记录的table的version。 +- 如果只有部分BE返回成功,那么把那些没返回成功的BE的状态 + +## version通知机制的保证/补救措施 + +在某些情况下可能publishVersion不能更新tablet的transactionid到version,比如我们假设一个现象,BE在接收publishVersion时,一个磁盘掉了,然后又回来了,那么这个磁盘上的tablet的version实际是没有变的,但是FE会误认为所有的tablet的version都更新成功了。 + +所以在这里我们引入一个强制保证的机制: + +- BE 定期的跟FE比对本地所有tablet的version,如果本地的version < FE中记录的visible version,那么判定本地是有问题的。 +- BE 扫描本地tablet的staging目录,从中获得所有的transactionid,从FE中获取对应的version,然后更新本地的version。 + +另外,当BE收到publishVersion做变更时,BE要检查下tablet对应的version-1数据是否存在,如果不存在,说明上次的publish并没有成功,可能有什么意外情况没考虑到,那么BE也需要从FE中主动拉一下transactionid对应的version。 + + +## BE 启动过程 + +- BE 启动时要完成自检的过程,跟FE通信汇报本地的tablet信息,如果一个磁盘坏了,那么需要向FE汇报,FE需要将这个tablet标记为异常。 + +## Compaction 逻辑 + +现在BE上一个tablet目录下的data目录里的数据 +保持现有的compaction逻辑不变,因为version的通知是顺序的,所以最后一个version仍然是未决version,仍然不compaction。 + +## delta目录的清空逻辑 + +delta目录下的数据单纯是为了副本修复服务的,所以我们这里采取简单的定时的策略,目前考虑半小时自动删除,另外如果磁盘空间不足的话,可以考虑删除。 + +## 副本故障处理 + +副本修复的触发机制和类型: + +- 当一个tablet的BE从故障中恢复汇报本地的tablet给FE时,FE决定这个tablet仍然由这个BE来存储时,FE直接生成RepairTabletTask。 +- 当Coordinator调用commitTransaction的API时,检测到一个tablet没有完成不完整时: + - 如果这个BE在元数据中标记为正常,那么此时立即生成一个RepairTabletTask。 + - 如果这个BE在元数据中标记为异常了,那么不执行副本修复。 +- FE定时检查副本的状态,如果一个副本处于故障状态超过一定的时间(比如15min,这个时间要参考两个机器同时宕机的概率),那么立即生成RepairTabletTask。 +- relbance 过程,当一个tablet从A机器迁移到B机器时,直接把B机器当做一个故障的副本,生成一个RepairTabletTask。 +- 副本数目调大,新加的副本认为是异常副本,生成一个RepaireTabletTask。 + +注意: 生成RepairTabletTask的同时把故障的tablet标记为clone状态,对于未来的loading任务也要向这个节点发送,version通知也要发送,只是在计算quorum的时候不计算。 + +RepairReplicaTask的执行流程: + +- FE 调用BE的repairTablet API,将RepairTabletTask发送给对应的BE。 +- BE 执行RepairTabletTask。 +- 当BE下载完后,跟data目录下的内容比对一下,如果完全对了,那么把数据在data目录下建立硬链,给FE汇报成功消息。 + +# 一些异常处理的思路 +## BE 的一个磁盘掉了,然后重启了,收到publishVersion 消息后如何处理? +此时我们不做处理,仍然认为version通知成功了。 其实现在的BE也是没处理的,这相当于BE给FE汇报导入成功后,BE自己的磁盘又掉了的情况,我们不做处理,等BE的定时汇报由FE发现异常,标记tablet为异常。 + +## FE 宕机重启 +- 内存中保存的导入完成情况丢失: 此时FE 成为Leader时,需要向所有的BE发送一个invalid消息,告诉BE本地的内存状态无效,从硬盘上汇报所有的导入进度信息给FE。 +- RepairTabletTask丢失:在创建RepairTabletTask时把tablet设置为clone状态,此时可以遍历CLONE状态的tablet生成RepairTabletTask,让BE重新做即可,这里的sourceIP可能发生变化,但是我们不管了。 + +# 遗留的导入的改造方式 +## 导入的改进方式 +- FE Leader 重启时根据元数据中已经loading finished的任务的version,将这个version的最大值根据table写入table_version 的visible_version 字段中。 +- FE Leader 根据正在loading的任务【尚未完成导入,ET阶段已经结束,Loading阶段未结束】,获取这些任务的version,获取最大的,把version写入table_version 的next_version 字段中。 +- 对于已经开始执行loading阶段的导入,FE Leader继续用轮训的方式,让他们执行loading任务,在loading完成后,要增加修改table对应的visible_version的逻辑。 这里可能不仅仅是quorum导入完成,要等尽量长的时间,让所有的副本都导入成功才可以。 如果副本导入没成功,那么标记为故障。 【或许这一步我们可以直接cancel掉之前所有的导入】 +- FE Leader上把现在追副本的任务停止。 + +# 其他一些考虑 +- 处于loading 阶段的任务不能太多,否则BE端压力太大,过去version lock能达到这个效果,现在没有version lock了,需要增加一个限制。 我们是不是有这个机制了呢? \ No newline at end of file diff --git a/docs/help/Contents/Account Management/help.md b/docs/help/Contents/Account Management/help.md index 2b5cd75486..f1b0da9bb2 100644 --- a/docs/help/Contents/Account Management/help.md +++ b/docs/help/Contents/Account Management/help.md @@ -1,30 +1,44 @@ # CREATE USER ## description - Syntax: - CREATE USER user_specification [SUPERUSER] +Syntax: + + CREATE USER user_identity [IDENTIFIED BY 'password'] [DEFAULT ROLE 'role_name'] - user_specification: - 'user_name' [IDENTIFIED BY [PASSWORD] 'password'] - - CREATE USER 命令可用于创建一个 palo 用户,使用这个命令需要使用者必须有管理员权限 - SUPERUSER用于指定需要创建的用户是个超级用户 + user_identity: + 'user_name'@'host' + +CREATE USER 命令用于创建一个 Palo 用户。在 Palo 中,一个 user_identity 唯一标识一个用户。user_identity 由两部分组成,user_name 和 host,其中 username 为用户名。host 标识用户端连接所在的主机地址。host 部分可以使用 % 进行模糊匹配。如果不指定 host,默认为 '%',即表示该用户可以从任意 host 连接到 Palo。 + +host 部分也可指定为 domain,语法为:'user_name'@['domain'],即使用中括号包围,则 Palo 会认为这个是一个 domain,并尝试解析其 ip 地址。目前仅支持百度内部的 BNS 解析。 + +如果指定了角色(ROLE),则会自动将该角色所拥有的权限赋予新创建的这个用户。如果不指定,则该用户默认没有任何权限。 ## example - 1. 创建一个没有密码的用户,用户名为 jack - CREATE USER 'jack' +1. 创建一个无密码用户(不指定 host,则等价于 jack@'%') + + CREATE USER 'jack'; - 2. 创建一个带有密码的用户,用户名为 jack,并且密码被指定为 123456 - CREATE USER 'jack' IDENTIFIED BY '123456' +2. 创建一个有密码用户,允许从 '172.10.1.10' 登陆 + + CREATE USER jack@'172.10.1.10' IDENTIFIED BY '123456'; - 3. 为了避免传递明文,用例2也可以使用下面的方式来创建 - CREATE USER 'jack' IDENTIFIED BY PASSWORD '*6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9' - 后面加密的内容可以通过PASSWORD()获得到,例如: - SELECT PASSWORD('123456') +3. 为了避免传递明文,用例2也可以使用下面的方式来创建 + + CREATE USER jack@'172.10.1.10' IDENTIFIED BY PASSWORD '*6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9'; + + 后面加密的内容可以通过PASSWORD()获得到,例如: + + SELECT PASSWORD('123456'); - 4. 创建一个超级用户'jack' - CREATE USER 'jack' SUPERUSER +4. 创建一个允许从 '192.168' 子网登陆的用户,同时指定其角色为 example_role + + CREATE USER 'jack'@'192.168.%' DEFAULT ROLE 'example_role'; + +5. 创建一个允许从域名 'example_domain' 登陆的用户 + + CREATE USER 'jack'@['example_domain'] IDENTIFIED BY '12345'; ## keyword CREATE, USER @@ -32,13 +46,16 @@ # DROP USER ## description - Syntax: +Syntax: + DROP USER 'user_name' - DROP USER 命令会删除一个 palo 用户,使用这个命令需要使用者必须有管理员权限 + DROP USER 命令会删除一个 palo 用户。这里 Palo 不支持删除指定的 user_identity。当删除一个指定用户后,该用户所对应的所有 user_identity 都会被删除。比如之前通过 CREATE USER 语句创建了 jack@'192.%' 以及 jack@['domain'] 两个用户,则在执行 DROP USER 'jack' 后,jack@'192.%' 以及 jack@['domain'] 都将被删除。 ## example - 1. 删除用户 jack + +1. 删除用户 jack + DROP USER 'jack' ## keyword @@ -47,76 +64,121 @@ # SET PASSWORD ## description - Syntax: - SET PASSWORD [FOR 'user_name'] = +Syntax: + + SET PASSWORD [FOR user_identity] = [PASSWORD('plain password')]|['hashed password'] - SET PASSWORD 命令可以用于修改一个用户的登录密码。如果 [FOR 'user_name'] 字段不存在,那么修改当前用户的密码。 + SET PASSWORD 命令可以用于修改一个用户的登录密码。如果 [FOR user_identity] 字段不存在,那么修改当前用户的密码。 + + 注意这里的 user_identity 必须完全匹配在使用 CREATE USER 创建用户时指定的 user_identity,否则会报错用户不存在。如果不指定 user_identity,则当前用户为 'username'@'ip',这个当前用户,可能无法匹配任何 user_identity。可以通过 SHOW GRANTS 查看当前用户。 + PASSWORD() 方式输入的是明文密码; 而直接使用字符串,需要传递的是已加密的密码。 如果修改其他用户的密码,需要具有管理员权限。 ## example - 1. 修改当前用户的密码为 123456 - SET PASSWORD = PASSWORD('123456') - SET PASSWORD = '*6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9' +1. 修改当前用户的密码 + + SET PASSWORD = PASSWORD('123456') + SET PASSWORD = '*6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9' - 2. 修改用户 jack 的密码为 123456 - SET PASSWORD FOR 'jack' = PASSWORD('123456') - SET PASSWORD FOR 'jack' = '*6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9' +2. 修改指定用户密码 + + SET PASSWORD FOR 'jack'@'192.%' = PASSWORD('123456') + SET PASSWORD FOR 'jack'@['domain'] = '*6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9' ## keyword SET, PASSWORD # GRANT ## description - GRANT 命令是将一个数据库的具体权限授权给具体用户。调用者必须是管理员身份。 - 权限当前只包括只读 (READ_ONLY),读写 (READ_WRITE) 两种权限,如果指定为 ALL, - 那么就是将全部权限授予该用户。 - Syntax: - GRANT privilege_list ON db_name TO 'user_name' +GRANT 命令用于赋予指定用户或角色指定的权限。 - privilege_list: - privilege [, privilege] ... +Syntax: - privilege: - READ_ONLY - | READ_WRITE - | ALL + GRANT privilege_list ON db_name[.tbl_name] TO user_identity [ROLE role_name] + + +privilege_list 是需要赋予的权限列表,以逗号分隔。当前Palo支持如下权限: + + NODE_PRIV:集群节点操作权限,包括节点上下线等操作,只有 root 用户有该权限,不可赋予其他用户。 + ADMIN_PRIV:除 NODE_PRIV 以外的所有权限。 + SELECT_PRIV:对指定的库或表的读取权限 + LOAD_PRIV:对指定的库或表的导入权限 + ALTER_PRIV:对指定的库或表的schema变更权限 + CREATE_PRIV:对指定的库或表的创建权限 + DROP_PRIV:对指定的库或表的删除权限 + + 旧版权限中的 ALL 和 READ_WRITE 会被转换成:SELECT_PRIV,LOAD_PRIV,ALTER_PRIV,CREATE_PRIV,DROP_PRIV; + READ_ONLY 会被转换为 SELECT_PRIV。 + +db_name[.tbl_name] 支持以下三种形式: + + 1. *.* 权限可以应用于所有库及其中所有表 + 2. db.* 权限可以应用于指定库下的所有表 + 3. db.tbl 权限可以应用于指定库下的指定表 + + 这里指定的库或表可以是不存在的库和表。 + +user_identity: + + 这里的 user_identity 语法同 CREATE USER。且必须为使用 CREATE USER 创建过的 user_identity。user_identity 中的host可以是域名,如果是域名的话,权限的生效时间可能会有1分钟左右的延迟。 + + 也可以将权限赋予指定的 ROLE,如果指定的 ROLE 不存在,则会自动创建。 ## example - 1. 授予用户 jack 数据库 testDb 的写权限 - GRANT READ_ONLY ON testDb to 'jack'; + 1. 授予所有库和表的权限给用户 + + GRANT SELECT_PRIV ON *.* TO 'jack'@'%'; - 2. 授予用户 jack 数据库 testDb 全部权限 - GRANT ALL ON testDb to 'jack'; + 2. 授予指定库表的权限给用户 + + GRANT SELECT_PRIV,ALTER_PRIVS,LOAD_PRIV ON db1.tbl1 TO 'jack'@'192.8.%'; + + 3. 授予指定库表的权限给角色 + + GRANT ADMIN_PRIV ON db1.* TO ROLE admin_role ## keyword GRANT # REVOKE ## description - REVOKE 命令用于撤销用户对于某一个数据库的权限(当前只支持撤销所有权限) - 语法: - REVOKE ALL ON db_name FROM 'user_name' + + REVOKE 命令用于撤销指定用户或角色指定的权限。 + Syntax: + REVOKE privilege_list ON db_name[.tbl_name] FROM user_identity [ROLE role_name] + + user_identity: + + 这里的 user_identity 语法同 CREATE USER。且必须为使用 CREATE USER 创建过的 user_identity。user_identity 中的host可以是域名,如果是域名的话,权限的撤销时间可能会有1分钟左右的延迟。 + + 也可以撤销指定的 ROLE 的权限,执行的 ROLE 必须存在。 ## example - 1. 撤销用户 jack 数据库 testDb 的权限 - REVOKE ALL ON testDb FROM 'jack'; + + 1. 撤销用户 jack 数据库 testDb 的权限 + + REVOKE SELECT_PRIV ON db1.* FROM 'jack'@'192.%'; ## keyword - REVOKE + + REVOKE # SET PROPERTY ## description + Syntax: + SET PROPERTY [FOR 'user'] 'key' = 'value' [, 'key' = 'value'] - 设置用户的属性,包括分配给用户的资源、导入cluster等。 + 设置用户的属性,包括分配给用户的资源、导入cluster等。这里设置的用户属性,是针对 user 的,而不是 user_identity。即假设通过 CREATE USER 语句创建了两个用户 'jack'@'%' 和 'jack'@'192.%',则使用 SET PROPERTY 语句,只能针对 jack 这个用户,而不是 'jack'@'%' 或 'jack'@'192.%' key: + 超级用户权限: max_user_connections: 最大连接数。 resource.cpu_share: cpu资源分配。 @@ -132,6 +194,7 @@ default_load_cluster: 默认的导入cluster。 ## example + 1. 修改用户 jack 最大连接数为1000 SET PROPERTY FOR 'jack' 'max_user_connections' = '1000'; @@ -157,19 +220,108 @@ ## keyword SET, PROPERTY + +# CREATE ROLE -# SHOW USER ## description - 用于显示当前用户有权限查看的所有用户的权限信息。 - 如果是普通用户,仅可以查看自己的权限信息。 - 如果是 superuser 用户,可以看到自己所属 cluster 的所有用户的权限信息。 - 如果是 admin(root)用户,可以看到所有用户的权限信息。 + 该语句用户创建一个角色 + 语法: - SHOW USER; + CREATE ROLE role1; + 该语句创建一个无权限的角色,可以后续通过 GRANT 命令赋予该角色权限。 + ## example - 1. 查看用户权限信息 - SHOW USER; + + 1. 创建一个角色 + + CREATE ROLE role1; + +## keyword + CREATE, ROLE + + +# DROP ROLE + +## description + 该语句用户删除一个角色 + + 语法: + DROP ROLE role1; + + 删除一个角色,不会影响之前属于该角色的用户的权限。仅相当于将该角色与用户解耦。用户已经从该角色中获取到的权限,不会改变。 + +## example + + 1. 删除一个角色 + + DROP ROLE role1; + +## keyword + DROP, ROLE + +# SHOW ROLES + +## description + 该语句用于展示所有已创建的角色信息,包括角色名称,包含的用户以及权限。 + + 语法: + SHOW ROLES; + +## example + + 1. 查看已创建的角色: + + SHOW ROELS; ## keyword - SHOW,USER + SHOW,ROLES + +## description + 该语句用户删除一个角色 + + 语法: + DROP ROLE role1; + + 删除一个角色,不会影响之前属于该角色的用户的权限。仅相当于将该角色与用户解耦。用户已经从该角色中获取到的权限,不会改变。 + +## example + + 1. 删除一个角色 + + DROP ROLE role1; + +## keyword + DROP, ROLE + +# SHOW GRANTS + +## description + + 该语句用于查看用户权限。 + + 语法: + SHOW [ALL] GRANTS [FOR user_identity]; + + 说明: + 1. SHOW ALL GRANTS 可以查看所有用户的权限。 + 2. 如果指定 user_identity,则查看该指定用户的权限。且该 user_identity 必须为通过 CREATE USER 命令创建的。 + 3. 如果不指定 user_identity,则查看当前用户的权限。 + + +## example + + 1. 查看所有用户权限信息 + + SHOW ALL GRANTS; + + 2. 查看指定 user 的权限 + + SHOW GRANTS FOR jack@'%'; + + 3. 查看当前用户的权限 + + SHOW GRANTS; + +## keyword + SHOW, GRANTS diff --git a/docs/help/Contents/Data Definition/ddl_stmt.md b/docs/help/Contents/Data Definition/ddl_stmt.md index ceab2b99e1..ae6f78251c 100644 --- a/docs/help/Contents/Data Definition/ddl_stmt.md +++ b/docs/help/Contents/Data Definition/ddl_stmt.md @@ -513,19 +513,19 @@ TO example_rollup_index; 2. 向example_rollup_index的col1后添加一个value列new_col(非聚合模型) - ALTER TABLE example_db.my_table - ADD COLUMN new_col INT DEFAULT "0" AFTER col1 - TO example_rollup_index; + ALTER TABLE example_db.my_table + ADD COLUMN new_col INT DEFAULT "0" AFTER col1 + TO example_rollup_index; 3. 向example_rollup_index的col1后添加一个key列new_col(聚合模型) - ALTER TABLE example_db.my_table - ADD COLUMN new_col INT DEFAULT "0" AFTER col1 - TO example_rollup_index; + ALTER TABLE example_db.my_table + ADD COLUMN new_col INT DEFAULT "0" AFTER col1 + TO example_rollup_index; 4. 向example_rollup_index的col1后添加一个value列new_col SUM聚合类型(聚合模型) - ALTER TABLE example_db.my_table - ADD COLUMN new_col INT SUM DEFAULT "0" AFTER col1 - TO example_rollup_index; + ALTER TABLE example_db.my_table + ADD COLUMN new_col INT SUM DEFAULT "0" AFTER col1 + TO example_rollup_index; 5. 向 example_rollup_index 添加多列(聚合模型) ALTER TABLE example_db.my_table @@ -687,8 +687,8 @@ 重命名数据库后,如需要,请使用 REVOKE 和 GRANT 命令修改相应的用户权限。 ## example - 1. 设置指定数据库数据量配额为 1GB - ALTER DATABASE example_db SET DATA QUOTA 1073741824; + 1. 设置指定数据库数据量配额为 10 TB + ALTER DATABASE example_db SET DATA QUOTA 10995116277760; 2. 将数据库额 example_db 重命名为 example_db2 ALTER DATABASE example_db RENAME example_db2; @@ -696,48 +696,106 @@ ## keyword ALTER,DATABASE,RENAME -# BACKUP +# CREATE REPOSITORY ## description - 该语句用于备份指定数据库下的数据。该命令为异步操作。提交成功后,需通过 SHOW BACKUP 命令查看进度。 + 该语句用于创建仓库。仓库用于属于备份或恢复。仅 root 或 superuser 用户可以创建仓库。 语法: - BACKUP LABEL [db_name.label] [backup_objs] INTO "remote_path" - PROPERTIES ("key"="value", ...) - - backup_objs: 需要备份的表名或分区名 - 语法: - (table_name[.partition_name], ...) + CREATE [READ ONLY] REPOSITORY `repo_name` + WITH BROKER `broker_name` + ON LOCATION `repo_location` + PROPERTIES ("key"="value", ...); 说明: - 1. 同一数据库下只能有一个正在执行的 BACKUP 任务。 - 2. 如果 backup_objs 中不写 partition_name,则默认备份该 table 下所有分区。 - 如果完全不写 backup_objs,则默认备份整个 database。 - 3. PROPERTIES 需要填写访问远端备份系统所需信息。 - 4. 统一数据库下,BACKUP 任务的 label 不能重复。 - + 1. 仓库的创建,依赖于已存在的 broker + 2. 如果是只读仓库,则只能在仓库上进行恢复。如果不是,则可以进行备份和恢复操作。 + 3. 根据 broker 的不同类型,PROPERTIES 有所不同,具体见示例。 + ## example - 1. 备份 example_db 下的所有数据,备份到 Hdfs 路径:/user/cmy/backup/ 下 - BACKUP LABEL example_db.backup_label1 - INTO "/dir/backup/" - PROPERTIES( - "server_type" = "hadoop", - "host" = "hdfs://host", - "port" = "port", - "user" = "user", - "password" = "passwd", - "opt_properties" = "" + 1. 创建名为 bos_repo 的仓库,依赖 BOS broker "bos_broker",数据根目录为:bos://palo_backup + CREATE REPOSITORY `bos_repo` + WITH BROKER `bos_broker ` + ON LOCATION "bos://palo_backup" + PROPERTIES + ( + "bos_endpoint" = "http://gz.bcebos.com", + "bos_accesskey" = "069fc2786e664e63a5f111111114ddbs22", + "bos_secret_accesskey"="70999999999999de274d59eaa980a" + ); + + 2. 创建和示例 1 相同的仓库,但属性为只读: + CREATE READ ONLY REPOSITORY `bos_repo` + WITH BROKER `bos_broker ` + ON LOCATION "bos://palo_backup" + PROPERTIES + ( + "bos_endpoint" = "http://gz.bcebos.com", + "bos_accesskey" = "069fc2786e664e63a5f111111114ddbs22", + "bos_secret_accesskey"="70999999999999de274d59eaa980a" + ); + + 3. 创建名为 hdfs_repo 的仓库,依赖 Baidu hdfs broker "hdfs_broker",数据根目录为:hdfs://hadoop-name-node:54310/path/to/repo/ + CREATE REPOSITORY `hdfs_repo` + WITH BROKER `hdfs_broker ` + ON LOCATION "hdfs://hadoop-name-node:54310/path/to/repo/" + PROPERTIES + ( + "username" = "user", + "password" = "password" ); - 2. 备份 example_db 下的表 example_tbl,备份到 Hdfs 路径:/user/cmy/backup/ 下 - BACKUP LABEL example_db.backup_label1 - (example_tbl) - INTO "/dir/backup/" - PROPERTIES ( - "server_type" = "hadoop", - "host" = "hdfs://host", - "port" = "port", - "user" = "user", - "password" = "passwd", - "opt_properties" = "" +## keyword + CREATE REPOSITORY + +# DROP REPOSITORY +## description + 该语句用于删除一个已创建的仓库。仅 root 或 superuser 用户可以删除仓库。 + 语法: + DROP REPOSITORY `repo_name`; + + 说明: + 1. 删除仓库,仅仅是删除该仓库在 Palo 中的映射,不会删除实际的仓库数据。删除后,可以再次通过指定相同的 broker 和 LOCATION 映射到该仓库。 + +## example + 1. 删除名为 bos_repo 的仓库: + DROP REPOSITORY `bos_repo`; + +## keyword + DROP REPOSITORY + +# BACKUP +## description + 该语句用于备份指定数据库下的数据。该命令为异步操作。提交成功后,需通过 SHOW BACKUP 命令查看进度。仅支持备份 OLAP 类型的表。 + 语法: + BACKUP SNAPSHOT [db_name].{snapshot_name} + TO `repository_name` + ON ( + `table_name` [PARTITION (`p1`, ...)], + ... + ) + PROPERTIES ("key"="value", ...); + + 说明: + 1. 同一数据库下只能有一个正在执行的 BACKUP 或 RESTORE 任务。 + 2. ON 子句中标识需要备份的表和分区。如果不指定分区,则默认备份该表的所有分区。 + 3. PROPERTIES 目前支持以下属性: + "type" = "full":表示这是一次全量更新(默认)。 + "timeout" = "3600":任务超时时间,默认为一天。单位秒。 + +## example + + 1. 全量备份 example_db 下的表 example_tbl 到仓库 example_repo 中: + BACKUP SNAPSHOT example_db.snapshot_label1 + TO example_repo + ON (example_tbl) + PROPERTIES ("type" = "full"); + + 2. 全量备份 example_db 下,表 example_tbl 的 p1, p2 分区,以及表 example_tbl2 到仓库 example_repo 中: + BACKUP SNAPSHOT example_db.snapshot_label2 + TO example_repo + ON + ( + example_tbl PARTITION (p1,p2), + example_tbl2 ); ## keyword @@ -746,48 +804,49 @@ # RESTORE ## description 1. RESTORE - 该语句用于将之前通过 BACKUP 命令备份的数据,恢复到指定数据库下。该命令为异步操作。提交成功后,需通过 SHOW RESTORE 命令查看进度。 + 该语句用于将之前通过 BACKUP 命令备份的数据,恢复到指定数据库下。该命令为异步操作。提交成功后,需通过 SHOW RESTORE 命令查看进度。仅支持恢复 OLAP 类型的表。 语法: - RESTORE LABEL [db_name.label] [restore_objs] FROM "remote_path" - PROPERTIES ("key"="value", ...) - - restore_objs: 需要恢复的表名或分区名 - 语法: - (table_name[.partition_name] [AS new_table_name[.partition_name]], ...) + RESTORE SNAPSHOT [db_name].{snapshot_name} + FROM `repository_name` + ON ( + `table_name` [PARTITION (`p1`, ...)] [AS `tbl_alias`], + ... + ) + PROPERTIES ("key"="value", ...); 说明: - 1. 同一数据库下只能有一个正在执行的 RESTORE 任务。 - 2. 需恢复的 database 已经创建,并且在备份路径中存在 - 3. 如果 restore_objs 中不写 partition_name,则默认恢复该 table 下所有分区。 - 如果完全不写 restore_objs,则默认恢复所有备份过的数据。支持重命名需要恢复的表名 - 4. remote_path 需指定到之前 BACKUP 任务中,remote_path/backup_label/ 下。 - 5. PROPERTIES 需要填写访问远端备份系统所需信息。 - 6. 同一数据库下,RESTORE 任务的 label 不能重复。 + 1. 同一数据库下只能有一个正在执行的 BACKUP 或 RESTORE 任务。 + 2. ON 子句中标识需要恢复的表和分区。如果不指定分区,则默认恢复该表的所有分区。所指定的表和分区必须已存在于仓库备份中。 + 3. 可以通过 AS 语句将仓库中备份的表名恢复为新的表。但新表名不能已存在于数据库中。分区名称不能修改。 + 4. 可以将仓库中备份的表恢复替换数据库中已有的同名表,但须保证两张表的表结构完全一致。表结构包括:表名、列、分区、Rollup等等。 + 5. 可以指定恢复表的部分分区,系统会检查分区 Range 是否能够匹配。 + 6. PROPERTIES 目前支持以下属性: + "backup_timestamp" = "2018-05-04-16-45-08":指定了恢复对应备份的哪个时间版本,必填。该信息可以通过 `SHOW SNAPSHOT ON repo;` 语句获得。 + "replication_num" = "3":指定恢复的表或分区的副本数。默认为3。若恢复已存在的表或分区,则副本数必须和已存在表或分区的副本数相同。同时,必须有足够的 host 容纳多个副本。 + "timeout" = "3600":任务超时时间,默认为一天。单位秒。 ## example - 1. 恢复 Hdfs 路径:/user/cmy/backup/backup_label1 下所有备份数据,恢复到 example_db 中 - RESTORE LABEL example_db.restore_label1 - FROM "/dir/backup/backup_label1" - PROPERTIES( - "server_type" = "hadoop", - "host" = "hdfs://host", - "port" = "port", - "user" = "user", - "password" = "passwd", - "opt_properties" = "" + 1. 从 example_repo 中恢复备份 snapshot_1 中的表 backup_tbl 到数据库 example_db1,时间版本为 "2018-05-04-16-45-08"。恢复为 1 个副本: + RESTORE SNAPSHOT example_db1.`snapshot_1 ` + FROM `example_repo` + ON ( `backup_tbl` ) + PROPERTIES + ( + "backup_timestamp"="2018-05-04-16-45-08", + "replication_num" = "1" ); - 2. 恢复 Hdfs 路径:/user/cmy/backup/backup_label1 下表 example_tbl 的数据。 - RESTORE LABEL example_db.restore_label1 - (example_tbl) - FROM "/dir/backup/backup_label1" - PROPERTIES ( - "server_type" = "hadoop", - "host" = "hdfs://host", - "port" = "port", - "user" = "user", - "password" = "passwd", - "opt_properties" = "" + 2. 从 example_repo 中恢复备份 snapshot_2 中的表 backup_tbl 的分区 p1,p2,以及表 backup_tbl2 到数据库 example_db1,并重命名为 new_tbl,时间版本为 "2018-05-04-17-11-01"。默认恢复为 3 个副本: + RESTORE SNAPSHOT example_db1.`snapshot_2 ` + FROM `example_repo ` + ON + ( + `backup_tbl` PARTITION (`p1`, `p2`) AS `backup_tbl2`, + `backup_tbl2` + ) + PROPERTIES + ( + "backup_timestamp"="2018-05-04-17-11-01" ); ## keyword @@ -811,6 +870,9 @@ 该语句用于取消一个正在进行的 RESTORE 任务。 语法: CANCEL RESTORE FROM db_name; + + 注意: + 当取消处于 COMMIT 或之后阶段的恢复左右时,可能导致被恢复的表无法访问。此时只能通过再次执行恢复作业进行数据恢复。 ## example 1. 取消 example_db 下的 RESTORE 任务。 @@ -825,18 +887,18 @@ 通过聚合来不断的减少数据量,以此来实现加快查询的目的,基于它到的是一个估算结果,误差大概在1%左右 hll列是通过其它列或者导入数据里面的数据生成的,导入的时候通过hll_hash函数来指定数据中哪一列用于生成hll列 它常用于替代count distinct,通过结合rollup在业务上用于快速计算uv等 - - 相关函数: - - HLL_UNION_AGG(hll) - 此函数为聚合函数,用于计算满足条件的所有数据的基数估算。 - - HLL_CARDINALITY(hll) - 此函数用于计算单条hll列的基数估算 - - HLL_HASH(column_name) - 生成HLL列类型,用于insert或导入的时候,导入的使用见相关说明 - + + 相关函数: + + HLL_UNION_AGG(hll) + 此函数为聚合函数,用于计算满足条件的所有数据的基数估算。 + + HLL_CARDINALITY(hll) + 此函数用于计算单条hll列的基数估算 + + HLL_HASH(column_name) + 生成HLL列类型,用于insert或导入的时候,导入的使用见相关说明 + ## example 1. 首先创建一张含有hll列的表 create table test( @@ -848,7 +910,7 @@ set1 hll hll_union, set2 hll hll_union) distributed by hash(id) buckets 32; - + 2. 导入数据,导入的方式见相关help curl a. 使用表中的列生成hll列 @@ -862,32 +924,33 @@ a. 创建一个rollup,让hll列产生聚合, alter table test add rollup test_rollup(date, set1); - + b. 创建另外一张专门计算uv的表,然后insert数据) - + create table test_uv( time date, uv_set hll hll_union) distributed by hash(id) buckets 32; insert into test_uv select date, set1 from test; - + c. 创建另外一张专门计算uv的表,然后insert并通过hll_hash根据test其它非hll列生成hll列 create table test_uv( time date, id_set hll hll_union) distributed by hash(id) buckets 32; - + insert into test_uv select date, hll_hash(id) from test; - + 4. 查询,hll列不允许直接查询它的原始值,可以通过配套的函数进行查询 - + a. 求总uv select HLL_UNION_AGG(uv_set) from test_uv; - + b. 求每一天的uv select HLL_CARDINALITY(uv_set) from test_uv; ## keyword - HLL + HLL + diff --git a/docs/help/Contents/Data Manipulation/manipulation_stmt.md b/docs/help/Contents/Data Manipulation/manipulation_stmt.md index 3fa0148e26..0bbb667c14 100644 --- a/docs/help/Contents/Data Manipulation/manipulation_stmt.md +++ b/docs/help/Contents/Data Manipulation/manipulation_stmt.md @@ -98,7 +98,10 @@ - dfs.ha.namenodes.xxx:ha模式中指定namenode的名字,多个名字以逗号分隔,ha模式中必须配置。其中xxx表示dfs.nameservices配置的value.例子: "dfs.ha.namenodes.palo" = "nn1,nn2" - dfs.namenode.rpc-address.xxx.nn: ha模式中指定namenode的rpc地址信息,ha模式中必须配置。其中nn表示dfs.ha.namenodes.xxx中配置的一个namenode的名字。例子: "dfs.namenode.rpc-address.palo.nn1" = "host:port" - dfs.client.failover.proxy.provider: ha模式中指定client连接namenode的provider,默认为:org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider - + - hadoop.security.authentication: 鉴权方式,包含simple和kerberos两种值。默认是SIMPLE模式。如果要使用kerberos,那必须配置以下配置。 + - kerberos_principal: 指定kerberos的principal + - kerberos_keytab: 指定kerberos的keytab文件路径 + - kerberos_keytab_content: 指定kerberos中keytab文件内容经过base64编码之后的内容。这个跟kerberos_keytab配置二选一就可以. 4. opt_properties 用于指定一些特殊参数。 @@ -130,7 +133,7 @@ DATA INFILE("hdfs://hdfs_host:hdfs_port/user/palo/data/input/file") INTO TABLE `my_table` ) - WITH BROKER hdfs ("username"="hdfs_user", "password"="hdfs_password") + WITH BROKER hdfs ("username"="hdfs_user", "password"="hdfs_password") PROPERTIES ( "timeout"="3600", @@ -220,7 +223,7 @@ v2 = hll_hash(k2) ) ) - WITH BROKER hdfs ("username"="hdfs_user", "password"="hdfs_password"); + WITH BROKER hdfs ("username"="hdfs_user", "password"="hdfs_password"); LOAD LABEL example_db.label8 ( @@ -254,6 +257,48 @@ "max_filter_ratio"="0.1" ); + 9. 基于keytab的kerberos鉴权访问hdfs + LOAD LABEL table1_20170707 ( + DATA INFILE("hdfs://bdos/palo/table1_data") + INTO TABLE table1 + ) + WITH BROKER hdfs ( + "fs.defaultFS"="hdfs://bdos", + "username"="hdfs_user", + "password"="hdfs_password", + "dfs.nameservices"="bdos", + "dfs.ha.namenodes.bdos"="nn1,nn2", + "dfs.namenode.rpc-address.bdos.nn1"="host1:port1", + "dfs.namenode.rpc-address.bdos.nn2"="host2:port2", + "hadoop.security.authentication"="kerberos", + "kerberos_principal"="palo@BAIDU.COM", + "kerberos_keytab"="/home/palo/palo.keytab")) + PROPERTIES ( + "timeout"="3600", + "max_filter_ratio"="0.1" + ); + + 10. 使用keytab content的kerberos鉴权访问hdfs + LOAD LABEL table1_20170707 ( + DATA INFILE("hdfs://bdos/palo/table1_data") + INTO TABLE table1 + ) + WITH BROKER hdfs ( + "fs.defaultFS"="hdfs://bdos", + "username"="hdfs_user", + "password"="hdfs_password", + "dfs.nameservices"="bdos", + "dfs.ha.namenodes.bdos"="nn1,nn2", + "dfs.namenode.rpc-address.bdos.nn1"="host1:port1", + "dfs.namenode.rpc-address.bdos.nn2"="host2:port2", + "hadoop.security.authentication"="kerberos", + "kerberos_principal"="palo@BAIDU.COM", + "kerberos_keytab_content"="BQIAAABEAAEACUJBSURVLkNPTQAEcGFsbw")) + PROPERTIES ( + "timeout"="3600", + "max_filter_ratio"="0.1" + ); + ## keyword LOAD,TABLE @@ -369,7 +414,7 @@ timeout: 指定 load 作业的超时时间,单位是秒。当load执行时间超过该阈值时,会自动取消。默认超时时间是 86400 秒。 建议指定 timeout 时间小于 86400 秒。 - hll: 用于指定数据里面和表里面的HLL列的对应关系,表中的列和数据里面指定的列 + hll: 用于指定数据里面和表里面的HLL列的对应关系,表中的列和数据里面指定的列 (如果不指定columns,则数据列面的列也可以是表里面的其它非HLL列)通过","分割 指定多个hll列使用“:”分割,例如: 'hll1,cuid:hll2,device' @@ -535,7 +580,7 @@ EXPORT TABLE testTbl TO "hdfs://hdfs_host:port/a/b/c" PROPERTIES ("column_separator"=",") WITH BROKER "broker_name" ("username"="xxx", "password"="yyy"); ## keyword - EXPORT + EXPORT # SHOW DATABASES ## description @@ -625,9 +670,9 @@ 3. 展示指定 db 的导出任务,state 为 "exporting", 并按 StartTime 降序排序 SHOW EXPORT FROM example_db WHERE STATE = "exporting" ORDER BY StartTime DESC; - + 4. 展示指定db,指定job_id的导出任务 - SHOW EXPORT FROM example_db WHERE EXPORT_JOB_ID = job_id; + SHOW EXPORT FROM example_db WHERE EXPORT_JOB_ID = job_id; ## keyword SHOW,EXPORT @@ -744,30 +789,35 @@ 该语句用于查看 BACKUP 任务 语法: SHOW BACKUP [FROM db_name] - [WHERE LABEL = "backup_label" | LABEL LIKE "pattern"]; 说明: - BACKUP 任务的状态(State)有以下几种: - PENDING:刚提交的 - SNAPSHOT:正在执行快照 - UPLOAD:准备上传数据 - CHECK_UPLOAD:正在上传数据 - FINISHING:即将结束 - FINISHED:任务完成 - CANCELLED:任务失败 - - 失败的任务可以通过 ErrMsg 列失败查看原因。 + 1. Palo 中仅保存最近一次 BACKUP 任务。 + 2. 各列含义如下: + JobId: 唯一作业id + SnapshotName: 备份的名称 + DbName: 所属数据库 + State: 当前阶段 + PENDING: 提交作业后的初始状态 + SNAPSHOTING: 执行快照中 + UPLOAD_SNAPSHOT:快照完成,准备上传 + UPLOADING: 快照上传中 + SAVE_META: 将作业元信息保存为本地文件 + UPLOAD_INFO: 上传作业元信息 + FINISHED: 作业成功 + CANCELLED: 作业失败 + BackupObjs: 备份的表和分区 + CreateTime: 任务提交时间 + SnapshotFinishedTime: 快照完成时间 + UploadFinishedTime: 快照上传完成时间 + FinishedTime: 作业结束时间 + UnfinishedTasks: 在 SNAPSHOTING 和 UPLOADING 阶段会显示还未完成的子任务id + Status: 如果作业失败,显示失败信息 + Timeout: 作业超时时间,单位秒 ## example - 1. 查看 example_db 下的所有 BACKUP 任务。 + 1. 查看 example_db 下最后一次 BACKUP 任务。 SHOW BACKUP FROM example_db; - 2. 查看 example_db 下 LABEL 为 "backup_label" 的任务。 - SHOW BACKUP FROM example_db WHERE LABEL = "backup_label"; - - 3. 查看 example_db 下 LABEL 前缀为 "backup" 的任务。 - SHOW BACKUP FROM example_db WHERE LABEL LIKE "backup%"; - ## keyword SHOW, BACKUP @@ -776,34 +826,96 @@ 该语句用于查看 RESTORE 任务 语法: SHOW RESTORE [FROM db_name] - [WHERE LABEL = "restore_label" | LABEL LIKE "pattern"]; 说明: - BACKUP 任务的状态(State)有以下几种: - PENDING:刚提交的 - RESTORE_META:正在恢复元数据 - DOWNLOAD_OBJS:准备恢复数据 - DOWNLOADING:正在恢复数据 - FINISHING:恢复完成,等待确认 - FINISHED:任务完成 - CANCELLED:任务失败 - - FINISHING 状态的任务需要通过 RESTORE COMMIT 语句进行确认生效,详见 RESTORE 语句 - 失败的任务可以通过 ErrMsg 列失败查看原因。 + 1. Palo 中仅保存最近一次 RESTORE 任务。 + 2. 各列含义如下: + JobId: 唯一作业id + Label: 要恢复的备份的名称 + Timestamp: 要恢复的备份的时间版本 + DbName: 所属数据库 + State: 当前阶段 + PENDING: 提交作业后的初始状态 + SNAPSHOTING: 执行快照中 + DOWNLOAD: 快照完成,准备下载仓库中的快照 + DOWNLOADING: 快照下载中 + COMMIT: 快照下载完成,准备生效 + COMMITING: 生效中 + FINISHED: 作业成功 + CANCELLED: 作业失败 + AllowLoad: 恢复时是否允许导入(当前不支持) + ReplicationNum: 指定恢复的副本数 + RestoreJobs: 要恢复的表和分区 + CreateTime: 任务提交时间 + MetaPreparedTime: 元数据准备完成时间 + SnapshotFinishedTime: 快照完成时间 + DownloadFinishedTime: 快照下载完成时间 + FinishedTime: 作业结束时间 + UnfinishedTasks: 在 SNAPSHOTING、DOWNLOADING 和 COMMITING 阶段会显示还未完成的子任务id + Status: 如果作业失败,显示失败信息 + Timeout: 作业超时时间,单位秒 ## example - 1. 查看 example_db 下的所有 RESTORE 任务。 + 1. 查看 example_db 下最近一次 RESTORE 任务。 SHOW RESTORE FROM example_db; - 2. 查看 example_db 下 LABEL 为 "restore_label" 的任务。 - SHOW RESTORE FROM example_db WHERE LABEL = "restore_label"; - - 3. 查看 example_db 下 LABEL 前缀为 "restore" 的任务。 - SHOW RESTORE FROM example_db WHERE LABEL LIKE "restore%"; - ## keyword SHOW, RESTORE - + +# SHOW REPOSITORIES +## description + 该语句用于查看当前已创建的仓库。 + 语法: + SHOW REPOSITORIES; + + 说明: + 1. 各列含义如下: + RepoId: 唯一的仓库ID + RepoName: 仓库名称 + CreateTime: 第一次创建该仓库的时间 + IsReadOnly: 是否为只读仓库 + Location: 仓库中用于备份数据的根目录 + Broker: 依赖的 Broker + ErrMsg: Palo 会定期检查仓库的连通性,如果出现问题,这里会显示错误信息 + +## example + 1. 查看已创建的仓库: + SHOW REPOSITORIES; + +## keyword + SHOW, REPOSITORY, REPOSITORIES + +# SHOW SNAPSHOT +## description + 该语句用于查看仓库中已存在的备份。 + 语法: + SHOW SNAPSHOT ON `repo_name` + [WHERE SNAPSHOT = "snapshot" [AND TIMESTAMP = "backup_timestamp"]]; + + 说明: + 1. 各列含义如下: + Snapshot: 备份的名称 + Timestamp: 对应备份的时间版本 + Status: 如果备份正常,则显示 OK,否则显示错误信息 + + 2. 如果指定了 TIMESTAMP,则会额外显示如下信息: + Database: 备份数据原属的数据库名称 + Details: 以 Json 的形式,展示整个备份的数据目录及文件结构 + +## example + 1. 查看仓库 example_repo 中已有的备份: + SHOW SNAPSHOT ON example_repo; + + 2. 仅查看仓库 example_repo 中名称为 backup1 的备份: + SHOW SNAPSHOT ON example_repo WHERE SNAPSHOT = "backup1"; + + 2. 查看仓库 example_repo 中名称为 backup1 的备份,时间版本为 "2018-05-05-15-34-26" 的详细信息: + SHOW SNAPSHOT ON example_repo + WHERE SNAPSHOT = "backup1" AND TIMESTAMP = "2018-05-05-15-34-26"; + +## keyword + SHOW, SNAPSHOT + # SHOW BACKENDS ## description 该语句用于查看cluster内的节点 @@ -812,3 +924,4 @@ ## keyword SHOW, BACKENDS + diff --git a/fe/build.xml b/fe/build.xml index 57f2c26308..c156590768 100644 --- a/fe/build.xml +++ b/fe/build.xml @@ -136,4 +136,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fe/src/com/baidu/palo/alter/RollupHandler.java b/fe/src/com/baidu/palo/alter/RollupHandler.java index 0da1f16ac2..177dbdbed5 100644 --- a/fe/src/com/baidu/palo/alter/RollupHandler.java +++ b/fe/src/com/baidu/palo/alter/RollupHandler.java @@ -47,6 +47,7 @@ import com.baidu.palo.common.util.ListComparator; import com.baidu.palo.common.util.PropertyAnalyzer; import com.baidu.palo.common.util.TimeUtils; import com.baidu.palo.common.util.Util; +import com.baidu.palo.mysql.privilege.PrivPredicate; import com.baidu.palo.persist.DropInfo; import com.baidu.palo.persist.EditLog; import com.baidu.palo.qe.ConnectContext; @@ -546,14 +547,34 @@ public class RollupHandler extends AlterHandler { } } - private List getJobInfo(RollupJob rollupJob, String tableName) { + private void getJobInfo(List> rollupJobInfos, + RollupJob rollupJob, Database db) { + if (rollupJob.getDbId() != db.getId()) { + return; + } + + OlapTable olapTable = (OlapTable) db.getTable(rollupJob.getTableId()); + if (olapTable == null) { + return; + } + + // check auth + if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(ConnectContext.get(), db.getFullName(), + olapTable.getName(), + PrivPredicate.ALTER)) { + // no priv, return + LOG.debug("No priv for user {} to table {}.{}", ConnectContext.get().getQualifiedUser(), + ConnectContext.get().getRemoteIP(), db.getFullName(), olapTable.getName()); + return; + } + List jobInfo = new ArrayList(); // job id jobInfo.add(rollupJob.getTableId()); // table name - jobInfo.add(tableName); + jobInfo.add(olapTable.getName()); // create time long createTime = rollupJob.getCreateTimeMs(); @@ -584,7 +605,8 @@ public class RollupHandler extends AlterHandler { jobInfo.add("N/A"); } - return jobInfo; + rollupJobInfos.add(jobInfo); + return; } @Override @@ -668,31 +690,12 @@ public class RollupHandler extends AlterHandler { this.jobsLock.readLock().lock(); try { for (AlterJob alterJob : this.alterJobs.values()) { - if (alterJob.getDbId() != dbId) { - continue; - } - - OlapTable olapTable = (OlapTable) db.getTable(alterJob.getTableId()); - if (olapTable == null) { - continue; - } - - rollupJobInfos.add(getJobInfo((RollupJob) alterJob, olapTable.getName())); - } // end for rollupJobs + getJobInfo(rollupJobInfos, (RollupJob) alterJob, db); + } for (AlterJob alterJob : this.finishedOrCancelledAlterJobs) { - if (alterJob.getDbId() != dbId) { - continue; - } - - String tableName = ""; - OlapTable olapTable = (OlapTable) db.getTable(alterJob.getTableId()); - if (olapTable != null) { - tableName = olapTable.getName(); - } - - rollupJobInfos.add(getJobInfo((RollupJob) alterJob, tableName)); - } // end for rollupJobs + getJobInfo(rollupJobInfos, (RollupJob) alterJob, db); + } // sort by // "JobId", "TableName", "CreateTime", "FinishedTime", "BaseIndexName", "RollupIndexName" diff --git a/fe/src/com/baidu/palo/alter/SchemaChangeHandler.java b/fe/src/com/baidu/palo/alter/SchemaChangeHandler.java index 6229fce888..2d31e743ea 100644 --- a/fe/src/com/baidu/palo/alter/SchemaChangeHandler.java +++ b/fe/src/com/baidu/palo/alter/SchemaChangeHandler.java @@ -54,6 +54,7 @@ import com.baidu.palo.common.util.ListComparator; import com.baidu.palo.common.util.PropertyAnalyzer; import com.baidu.palo.common.util.TimeUtils; import com.baidu.palo.common.util.Util; +import com.baidu.palo.mysql.privilege.PrivPredicate; import com.baidu.palo.qe.ConnectContext; import com.baidu.palo.thrift.TResourceInfo; import com.baidu.palo.thrift.TStorageType; @@ -1369,6 +1370,16 @@ public class SchemaChangeHandler extends AlterHandler { if (olapTable == null) { return; } + + // check auth + if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(ConnectContext.get(), db.getFullName(), + olapTable.getName(), + PrivPredicate.ALTER)) { + // no priv, return + LOG.debug("No priv for user {} to table {}.{}", ConnectContext.get().getQualifiedUser(), + ConnectContext.get().getRemoteIP(), db.getFullName(), olapTable.getName()); + return; + } // create time long createTime = schemaChangeJob.getCreateTimeMs(); diff --git a/fe/src/com/baidu/palo/analysis/AbstractBackupStmt.java b/fe/src/com/baidu/palo/analysis/AbstractBackupStmt.java index b7f01a782e..49b16294cb 100644 --- a/fe/src/com/baidu/palo/analysis/AbstractBackupStmt.java +++ b/fe/src/com/baidu/palo/analysis/AbstractBackupStmt.java @@ -20,90 +20,116 @@ package com.baidu.palo.analysis; -import com.baidu.palo.catalog.AccessPrivilege; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.Config; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; +import com.google.common.base.Joiner; import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + import java.util.List; import java.util.Map; public class AbstractBackupStmt extends DdlStmt { - private static final String SERVER_TYPE = "server_type"; - private static final String HOST = "host"; - private static final String PORT = "port"; - private static final String USER = "user"; - private static final String PASSWORD = "password"; - private static final String OPT_PROPERTIES = "opt_properties"; + private static final Logger LOG = LogManager.getLogger(AbstractBackupStmt.class); + + private final static String PROP_TIMEOUT = "timeout"; + private final static long MIN_TIMEOUT_MS = 600 * 1000L; // 10 min protected LabelName labelName; - protected List objNames; - protected String remotePath; + protected String repoName; + protected List tblRefs; protected Map properties; - public AbstractBackupStmt(LabelName labelName, List objNames, - String remotePath, Map properties) { + protected long timeoutMs; + + public AbstractBackupStmt(LabelName labelName, String repoName, List tableRefs, + Map properties) { this.labelName = labelName; - this.objNames = objNames; - if (this.objNames == null) { - this.objNames = Lists.newArrayList(); + this.repoName = repoName; + this.tblRefs = tableRefs; + if (this.tblRefs == null) { + this.tblRefs = Lists.newArrayList(); } - this.remotePath = remotePath; - this.properties = properties; + this.properties = properties == null ? Maps.newHashMap() : properties; } @Override public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { labelName.analyze(analyzer); - - // check authenticate - if (!analyzer.getCatalog().getUserMgr() - .checkAccess(analyzer.getUser(), labelName.getDbName(), AccessPrivilege.READ_WRITE)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, analyzer.getUser(), - labelName.getDbName()); + + // check auth + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.ADMIN)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "ADMIN"); } - for (PartitionName partitionName : objNames) { - partitionName.analyze(analyzer); - } - - if (Strings.isNullOrEmpty(remotePath)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_MISSING_PARAM, "restore path is not specified"); - } + checkAndNormalizeBackupObjs(); analyzeProperties(); - - // check if partition names has intersection - PartitionName.checkIntersect(objNames); } - private void analyzeProperties() throws AnalysisException { - if (properties == null || properties.isEmpty()) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_MISSING_PARAM, "romote source info is not specified"); + private void checkAndNormalizeBackupObjs() throws AnalysisException { + for (TableRef tblRef : tblRefs) { + if (!Strings.isNullOrEmpty(tblRef.getName().getDb())) { + throw new AnalysisException("Cannot specify database name on backup objects: " + + tblRef.getName().getTbl() + ". Sepcify database name before label"); + } + // set db name because we can not persist empty string when writing bdbje log + tblRef.getName().setDb(labelName.getDbName()); + } + + // normalize + // table name => table ref + Map tblPartsMap = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER); + for (TableRef tblRef : tblRefs) { + String tblName = tblRef.getName().getTbl(); + + if (!tblPartsMap.containsKey(tblName)) { + tblPartsMap.put(tblName, tblRef); + } else { + throw new AnalysisException("Duplicated restore table: " + tblName); + } + } + + // update table ref + tblRefs.clear(); + for (TableRef tableRef : tblPartsMap.values()) { + tblRefs.add(tableRef); } - Map tmpProp = Maps.newHashMap(); - for (Map.Entry entry : properties.entrySet()) { - tmpProp.put(entry.getKey().toLowerCase(), entry.getValue()); - } - properties = tmpProp; + LOG.debug("table refs after normalization: \n{}", Joiner.on("\n").join(tblRefs)); + } - if (!properties.containsKey(SERVER_TYPE) - || !properties.containsKey(HOST) - || !properties.containsKey(PORT) - || !properties.containsKey(USER) - || !properties.containsKey(PASSWORD)) { - throw new AnalysisException("Properties should contains required params."); - } + protected void analyzeProperties() throws AnalysisException { + // timeout + if (properties.containsKey("timeout")) { + try { + timeoutMs = Long.valueOf(properties.get(PROP_TIMEOUT)); + } catch (NumberFormatException e) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_COMMON_ERROR, + "Invalid timeout format: " + + properties.get(PROP_TIMEOUT)); + } - if (!properties.containsKey(OPT_PROPERTIES)) { - properties.put(OPT_PROPERTIES, ""); + if (timeoutMs * 1000 < MIN_TIMEOUT_MS) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_COMMON_ERROR, "timeout must be at least 10 min"); + } + + timeoutMs = timeoutMs * 1000; + properties.remove(PROP_TIMEOUT); + } else { + timeoutMs = Config.backup_job_default_timeout_ms; } } @@ -119,15 +145,20 @@ public class AbstractBackupStmt extends DdlStmt { return labelName; } - public List getObjNames() { - return objNames; + public String getRepoName() { + return repoName; } - public String getRemotePath() { - return remotePath; + public List getTableRefs() { + return tblRefs; } public Map getProperties() { return properties; } + + public long getTimeoutMs() { + return timeoutMs; + } } + diff --git a/fe/src/com/baidu/palo/analysis/AlterClusterStmt.java b/fe/src/com/baidu/palo/analysis/AlterClusterStmt.java index 3822c7e965..8a88903238 100644 --- a/fe/src/com/baidu/palo/analysis/AlterClusterStmt.java +++ b/fe/src/com/baidu/palo/analysis/AlterClusterStmt.java @@ -20,12 +20,15 @@ package com.baidu.palo.analysis; -import java.util.Map; - +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; + +import java.util.Map; public class AlterClusterStmt extends DdlStmt { @@ -41,9 +44,8 @@ public class AlterClusterStmt extends DdlStmt { @Override public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { - - if (!analyzer.getCatalog().getUserMgr().isAdmin(analyzer.getUser())) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_CLUSTER_NO_AUTHORITY, analyzer.getUser()); + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.OPERATOR)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_CLUSTER_NO_AUTHORITY, "NODE"); } if (properties == null || properties.size() == 0 diff --git a/fe/src/com/baidu/palo/analysis/AlterDatabaseQuotaStmt.java b/fe/src/com/baidu/palo/analysis/AlterDatabaseQuotaStmt.java index 080ad2b061..64405651e0 100644 --- a/fe/src/com/baidu/palo/analysis/AlterDatabaseQuotaStmt.java +++ b/fe/src/com/baidu/palo/analysis/AlterDatabaseQuotaStmt.java @@ -20,11 +20,15 @@ package com.baidu.palo.analysis; -import com.baidu.palo.cluster.ClusterNamespace; -import com.baidu.palo.common.AnalysisException; -import com.baidu.palo.common.ErrorCode; -import com.baidu.palo.common.ErrorReport; -import com.baidu.palo.common.InternalException; +import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.cluster.ClusterNamespace; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.ErrorCode; +import com.baidu.palo.common.ErrorReport; +import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; + import com.google.common.base.Strings; public class AlterDatabaseQuotaStmt extends DdlStmt { @@ -46,11 +50,12 @@ public class AlterDatabaseQuotaStmt extends DdlStmt { @Override public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { - super.analyze(analyzer); - if (!analyzer.getCatalog().getUserMgr().isSuperuser(analyzer.getUser())) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, analyzer.getUser(), dbName); - } - + super.analyze(analyzer); + + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.ADMIN)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, analyzer.getQualifiedUser(), dbName); + } + if (Strings.isNullOrEmpty(dbName)) { ErrorReport.reportAnalysisException(ErrorCode.ERR_NO_DB_ERROR); } diff --git a/fe/src/com/baidu/palo/analysis/AlterDatabaseRename.java b/fe/src/com/baidu/palo/analysis/AlterDatabaseRename.java index 951ef09ec9..a7227908aa 100644 --- a/fe/src/com/baidu/palo/analysis/AlterDatabaseRename.java +++ b/fe/src/com/baidu/palo/analysis/AlterDatabaseRename.java @@ -20,12 +20,19 @@ package com.baidu.palo.analysis; -import com.baidu.palo.cluster.ClusterNamespace; -import com.baidu.palo.common.AnalysisException; -import com.baidu.palo.common.ErrorCode; -import com.baidu.palo.common.ErrorReport; -import com.baidu.palo.common.FeNameFormat; -import com.baidu.palo.common.InternalException; +import com.baidu.palo.analysis.CompoundPredicate.Operator; +import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.cluster.ClusterNamespace; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.ErrorCode; +import com.baidu.palo.common.ErrorReport; +import com.baidu.palo.common.FeNameFormat; +import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PaloPrivilege; +import com.baidu.palo.mysql.privilege.PrivBitSet; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; + import com.google.common.base.Strings; public class AlterDatabaseRename extends DdlStmt { @@ -50,10 +57,13 @@ public class AlterDatabaseRename extends DdlStmt { super.analyze(analyzer); if (Strings.isNullOrEmpty(dbName)) { throw new AnalysisException("Database name is not set"); - } - - if (!analyzer.getCatalog().getUserMgr().isSuperuser(analyzer.getUser())) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, analyzer.getUser(), dbName); + } + + if (!Catalog.getCurrentCatalog().getAuth().checkDbPriv(ConnectContext.get(), dbName, + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.ADMIN_PRIV, + PaloPrivilege.ALTER_PRIV), + Operator.OR))) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, analyzer.getQualifiedUser(), dbName); } if (Strings.isNullOrEmpty(newDbName)) { diff --git a/fe/src/com/baidu/palo/analysis/AlterLoadErrorUrlClause.java b/fe/src/com/baidu/palo/analysis/AlterLoadErrorUrlClause.java index 4c05863b97..b7b68fa8f9 100644 --- a/fe/src/com/baidu/palo/analysis/AlterLoadErrorUrlClause.java +++ b/fe/src/com/baidu/palo/analysis/AlterLoadErrorUrlClause.java @@ -21,8 +21,6 @@ package com.baidu.palo.analysis; import com.baidu.palo.common.AnalysisException; -import com.baidu.palo.common.ErrorCode; -import com.baidu.palo.common.ErrorReport; import com.baidu.palo.load.LoadErrorHub; import org.apache.logging.log4j.LogManager; @@ -48,12 +46,7 @@ public class AlterLoadErrorUrlClause extends AlterClause { @Override public void analyze(Analyzer analyzer) throws AnalysisException { - // only root can do it - if (!analyzer.getCatalog().getUserMgr().isAdmin(analyzer.getUser())) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, - "SET LOAD_ERROR_URL"); - } - + // auth is checked in Alter System Stmt this.param = LoadErrorHub.analyzeUrl(url); } diff --git a/fe/src/com/baidu/palo/analysis/AlterSystemStmt.java b/fe/src/com/baidu/palo/analysis/AlterSystemStmt.java index c09e21a999..de5d3ff019 100644 --- a/fe/src/com/baidu/palo/analysis/AlterSystemStmt.java +++ b/fe/src/com/baidu/palo/analysis/AlterSystemStmt.java @@ -15,10 +15,13 @@ package com.baidu.palo.analysis; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.google.common.base.Preconditions; @@ -36,8 +39,10 @@ public class AlterSystemStmt extends DdlStmt { @Override public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { - if (!analyzer.getCatalog().getUserMgr().isAdmin(analyzer.getUser())) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "ALTER SYSTEM"); + + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.OPERATOR)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, + "NODE"); } Preconditions.checkState((alterClause instanceof AddBackendClause) diff --git a/fe/src/com/baidu/palo/analysis/AlterTableStmt.java b/fe/src/com/baidu/palo/analysis/AlterTableStmt.java index 35fb0dc5e9..2485e31b1a 100644 --- a/fe/src/com/baidu/palo/analysis/AlterTableStmt.java +++ b/fe/src/com/baidu/palo/analysis/AlterTableStmt.java @@ -20,12 +20,14 @@ package com.baidu.palo.analysis; -import com.baidu.palo.catalog.AccessPrivilege; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.InternalException; import com.baidu.palo.common.io.Writable; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.google.common.collect.Lists; @@ -76,8 +78,13 @@ public class AlterTableStmt extends DdlStmt implements Writable { op.analyze(analyzer); } - // check access - analyzer.checkPrivilege(tbl.getDb(), AccessPrivilege.READ_WRITE); + if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(ConnectContext.get(), tbl.getDb(), tbl.getTbl(), + PrivPredicate.ALTER)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_TABLEACCESS_DENIED_ERROR, "ALTER TABLE", + ConnectContext.get().getQualifiedUser(), + ConnectContext.get().getRemoteIP(), + tbl.getTbl()); + } } @Override diff --git a/fe/src/com/baidu/palo/analysis/AlterUserStmt.java b/fe/src/com/baidu/palo/analysis/AlterUserStmt.java index 6fdf9ae54c..b1b234b8f2 100644 --- a/fe/src/com/baidu/palo/analysis/AlterUserStmt.java +++ b/fe/src/com/baidu/palo/analysis/AlterUserStmt.java @@ -20,98 +20,44 @@ package com.baidu.palo.analysis; -import com.baidu.palo.catalog.AccessPrivilege; -import com.baidu.palo.cluster.ClusterNamespace; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.common.AnalysisException; -import com.baidu.palo.common.Config; -import com.baidu.palo.common.DdlException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.InternalException; - -import com.google.common.base.Strings; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import org.apache.commons.lang.NotImplementedException; import java.util.List; +@Deprecated public class AlterUserStmt extends DdlStmt { - private String userName; + private UserIdentity userIdent; private AlterUserClause clause; - public AlterUserStmt(String userName, AlterClause clause) { - this.userName = userName; + public AlterUserStmt(UserIdentity userIdent, AlterClause clause) { + this.userIdent = userIdent; this.clause = (AlterUserClause) clause; } - private boolean hasRightToModify(Analyzer analyzer) { - String user = analyzer.getUser(); - String toUser = userName; - - // own can modify own - if (user.equals(toUser)) { - return true; - } - - // admin can modify all - if (analyzer.getCatalog().getUserMgr().isAdmin(user)) { - return true; - } - - // superuse can modify Ordinary user - if (analyzer.getCatalog().getUserMgr().isSuperuser(user) - && !analyzer.getCatalog().getUserMgr().isSuperuser(toUser)) { - return true; - } - return false; - } - - private void checkWhiteListSize(Analyzer analyzer) throws AnalysisException { - if (clause.getAlterUserType() == AlterUserType.ADD_USER_WHITELIST) { - try { - if (analyzer.getCatalog().getUserMgr().getWhiteListSize(userName) - > Config.per_user_white_list_limit) { - throw new AnalysisException("whitelist size excced the max (" - + Config.per_user_white_list_limit + ")"); - } - } catch (DdlException e) { - throw new AnalysisException(e.getMessage()); - } - } - } - @Override public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { super.analyze(analyzer); - // check toUser - if (Strings.isNullOrEmpty(userName)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "empty user"); - } - userName = ClusterNamespace.getFullName(getClusterName(), userName); - // check destination user if exists - try { - analyzer.getCatalog().getUserMgr().checkUserIfExist(userName); - } catch (DdlException e) { - throw new AnalysisException(e.getMessage()); - } - // check destination user's whitelist if ecceed max value - checkWhiteListSize(analyzer); + userIdent.analyze(analyzer.getClusterName()); - // only write user can modify - analyzer.checkPrivilege(analyzer.getDefaultDb(), AccessPrivilege.READ_WRITE); - - // check if has the right - if (!hasRightToModify(analyzer)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "ALTER CLUSTER"); + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "ALTER USER"); } - + // alter clause analysis clause.analyze(analyzer); } - public String getUser() { - return userName; + public UserIdentity getUserIdent() { + return userIdent; } public List getHosts() { diff --git a/fe/src/com/baidu/palo/analysis/Analyzer.java b/fe/src/com/baidu/palo/analysis/Analyzer.java index 197aaaac92..48bb6f8c62 100644 --- a/fe/src/com/baidu/palo/analysis/Analyzer.java +++ b/fe/src/com/baidu/palo/analysis/Analyzer.java @@ -20,12 +20,14 @@ package com.baidu.palo.analysis; -import com.baidu.palo.catalog.AccessPrivilege; import com.baidu.palo.catalog.Catalog; import com.baidu.palo.catalog.Column; import com.baidu.palo.catalog.Database; import com.baidu.palo.catalog.InfoSchemaDb; +import com.baidu.palo.catalog.OlapTable; +import com.baidu.palo.catalog.OlapTable.OlapTableState; import com.baidu.palo.catalog.Table; +import com.baidu.palo.catalog.Table.TableType; import com.baidu.palo.catalog.Type; import com.baidu.palo.catalog.View; import com.baidu.palo.cluster.ClusterNamespace; @@ -219,7 +221,7 @@ public class Analyzer { public final Map ijClauseByConjunct = Maps.newHashMap(); // TODO chenhao16, to save conjuncts, which children are constant - public final Map constantConjunct = Maps.newHashMap(); + public final Map> constantConjunct = Maps.newHashMap(); // map from slot id to the analyzer/block in which it was registered public final Map blockBySlot = Maps.newHashMap(); @@ -471,6 +473,11 @@ public class Analyzer { ErrorReport.reportAnalysisException(ErrorCode.ERR_BAD_TABLE_ERROR, tableName.getTbl()); } + if (table.getType() == TableType.OLAP && (((OlapTable) table).getState() == OlapTableState.RESTORE + || ((OlapTable) table).getState() == OlapTableState.RESTORE_WITH_LOAD)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_BAD_TABLE_STATE, "RESTORING"); + } + TableName tblName = new TableName(database.getFullName(), table.getName()); if (table instanceof View) { return new InlineViewRef((View) table, tableRef); @@ -739,7 +746,12 @@ public class Analyzer { private void registerConstantConjunct(TupleId id, Expr e) { if (id != null && e.isConstant()) { - globalState.constantConjunct.put(id, e); + Set set = globalState.constantConjunct.get(id); + if (set == null) { + set = Sets.newHashSet(); + globalState.constantConjunct.put(id, set); + } + set.add(e); } } @@ -866,7 +878,8 @@ public class Analyzer { if (e.isConstant()) { boolean isBoundByTuple = false; for (TupleId id : tupleIds) { - if (globalState.constantConjunct.containsKey(id)) { + final Set exprSet = globalState.constantConjunct.get(id); + if (exprSet != null && exprSet.contains(e)) { isBoundByTuple = true; break; } @@ -875,6 +888,7 @@ public class Analyzer { continue; } } + if (e.isBoundByTupleIds(tupleIds) && !e.isAuxExpr() && !globalState.assignedConjuncts.contains(e.getId()) @@ -1378,8 +1392,8 @@ public class Analyzer { return globalState.context.getClusterName(); } - public String getUser() { - return globalState.context.getUser(); + public String getQualifiedUser() { + return globalState.context.getQualifiedUser(); } public String getSchemaDb() { @@ -1588,12 +1602,6 @@ public class Analyzer { public Map getLocalViews() { return localViews_; } - public void checkPrivilege(String db, AccessPrivilege priv) throws AnalysisException { - if (!globalState.catalog.getUserMgr().checkAccess(getUser(), db, priv)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, priv.toString()); - } - } - public boolean isOuterJoined(TupleId tid) { return globalState.outerJoinedTupleIds.containsKey(tid); } diff --git a/fe/src/com/baidu/palo/analysis/BackupStmt.java b/fe/src/com/baidu/palo/analysis/BackupStmt.java index 5d0da6db7e..feda2627d9 100644 --- a/fe/src/com/baidu/palo/analysis/BackupStmt.java +++ b/fe/src/com/baidu/palo/analysis/BackupStmt.java @@ -20,18 +20,72 @@ package com.baidu.palo.analysis; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.ErrorCode; +import com.baidu.palo.common.ErrorReport; +import com.baidu.palo.common.InternalException; import com.baidu.palo.common.util.PrintableMap; import com.google.common.base.Joiner; +import com.google.common.collect.Maps; import java.util.List; import java.util.Map; public class BackupStmt extends AbstractBackupStmt { + private final static String PROP_TYPE = "type"; - public BackupStmt(LabelName labelName, List backupObjNames, String backupPath, - Map properties) { - super(labelName, backupObjNames, backupPath, properties); + public enum BackupType { + INCREMENTAL, FULL + } + + private BackupType type = BackupType.FULL; + + public BackupStmt(LabelName labelName, String repoName, List tblRefs, Map properties) { + super(labelName, repoName, tblRefs, properties); + } + + public long getTimeoutMs() { + return timeoutMs; + } + + public BackupType getType() { + return type; + } + + @Override + public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { + super.analyze(analyzer); + + // tbl refs can not set alias in backup + for (TableRef tblRef : tblRefs) { + if (tblRef.hasExplicitAlias()) { + throw new AnalysisException("Can not set alias for table in Backup Stmt: " + tblRef); + } + } + } + + @Override + protected void analyzeProperties() throws AnalysisException { + super.analyzeProperties(); + + Map copiedProperties = Maps.newHashMap(properties); + // type + if (copiedProperties.containsKey(PROP_TYPE)) { + try { + type = BackupType.valueOf(copiedProperties.get(PROP_TYPE).toUpperCase()); + } catch (Exception e) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_COMMON_ERROR, + "Invalid backup job type: " + + copiedProperties.get(PROP_TYPE)); + } + copiedProperties.remove(PROP_TYPE); + } + + if (!copiedProperties.isEmpty()) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_COMMON_ERROR, + "Unknown backup job properties: " + copiedProperties.keySet()); + } } @Override @@ -42,15 +96,14 @@ public class BackupStmt extends AbstractBackupStmt { @Override public String toSql() { StringBuilder sb = new StringBuilder(); - sb.append("BACKUP LABEL ").append(labelName.toSql()); - if (!objNames.isEmpty()) { - sb.append(" ("); - sb.append(Joiner.on(", ").join(objNames)); - sb.append(")"); - } - sb.append(" INTO \"").append(remotePath).append("\" PROPERTIES("); - sb.append(new PrintableMap(properties, "=", true, false)); - sb.append(")"); + sb.append("BACKUP SNAPSHOT ").append(labelName.toSql()); + sb.append("\n").append("TO ").append(repoName).append("\nON\n("); + + sb.append(Joiner.on(",\n").join(tblRefs)); + + sb.append("\n)\nPROPERTIES\n("); + sb.append(new PrintableMap(properties, " = ", true, true)); + sb.append("\n)"); return sb.toString(); } } \ No newline at end of file diff --git a/fe/src/com/baidu/palo/analysis/CancelAlterTableStmt.java b/fe/src/com/baidu/palo/analysis/CancelAlterTableStmt.java index 20911f9aa1..78050fe933 100644 --- a/fe/src/com/baidu/palo/analysis/CancelAlterTableStmt.java +++ b/fe/src/com/baidu/palo/analysis/CancelAlterTableStmt.java @@ -15,9 +15,13 @@ package com.baidu.palo.analysis; -import com.baidu.palo.analysis.ShowAlterStmt.AlterType; -import com.baidu.palo.catalog.AccessPrivilege; -import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.analysis.ShowAlterStmt.AlterType; +import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.ErrorCode; +import com.baidu.palo.common.ErrorReport; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; /* * CANCEL ALTER COLUMN|ROLLUP FROM db_name.table_name @@ -50,9 +54,13 @@ public class CancelAlterTableStmt extends CancelStmt { dbTableName.analyze(analyzer); // check access - if (!analyzer.getCatalog().getUserMgr() - .checkAccess(analyzer.getUser(), dbTableName.getDb(), AccessPrivilege.READ_WRITE)) { - throw new AnalysisException("No privilege to access database[" + dbTableName.getDb() + "]"); + if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(ConnectContext.get(), dbTableName.getDb(), + dbTableName.getTbl(), + PrivPredicate.ALTER)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_TABLEACCESS_DENIED_ERROR, "CANCEL ALTER TABLE", + ConnectContext.get().getQualifiedUser(), + ConnectContext.get().getRemoteIP(), + dbTableName.getTbl()); } } diff --git a/fe/src/com/baidu/palo/analysis/CancelBackupStmt.java b/fe/src/com/baidu/palo/analysis/CancelBackupStmt.java index f86161bba8..c45398edc7 100644 --- a/fe/src/com/baidu/palo/analysis/CancelBackupStmt.java +++ b/fe/src/com/baidu/palo/analysis/CancelBackupStmt.java @@ -15,11 +15,14 @@ package com.baidu.palo.analysis; -import com.baidu.palo.catalog.AccessPrivilege; +import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.cluster.ClusterNamespace; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.google.common.base.Strings; @@ -43,16 +46,19 @@ public class CancelBackupStmt extends CancelStmt { @Override public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { + super.analyze(analyzer); if (Strings.isNullOrEmpty(dbName)) { dbName = analyzer.getDefaultDb(); if (Strings.isNullOrEmpty(dbName)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_NO_DB_ERROR); + throw new AnalysisException("No database selected"); } + } else { + dbName = ClusterNamespace.getFullName(getClusterName(), dbName); } - // check access - if (!analyzer.getCatalog().getUserMgr().checkAccess(analyzer.getUser(), dbName, AccessPrivilege.READ_WRITE)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, analyzer.getUser(), dbName); + // check auth + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.ADMIN)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "ADMIN"); } } diff --git a/fe/src/com/baidu/palo/analysis/CancelLoadStmt.java b/fe/src/com/baidu/palo/analysis/CancelLoadStmt.java index 3a99f8b087..9483335d55 100644 --- a/fe/src/com/baidu/palo/analysis/CancelLoadStmt.java +++ b/fe/src/com/baidu/palo/analysis/CancelLoadStmt.java @@ -16,7 +16,6 @@ package com.baidu.palo.analysis; import com.baidu.palo.analysis.BinaryPredicate.Operator; -import com.baidu.palo.catalog.AccessPrivilege; import com.baidu.palo.cluster.ClusterNamespace; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.InternalException; @@ -46,7 +45,6 @@ public class CancelLoadStmt extends DdlStmt { this.whereClause = whereClause; } - @Override public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { super.analyze(analyzer); @@ -59,11 +57,7 @@ public class CancelLoadStmt extends DdlStmt { dbName = ClusterNamespace.getFullName(getClusterName(), dbName); } - // check access - if (!analyzer.getCatalog().getUserMgr() - .checkAccess(analyzer.getUser(), dbName, AccessPrivilege.READ_ONLY)) { - throw new AnalysisException("No privilege of db(" + dbName + ")."); - } + // check auth after we get real load job // analyze expr if not null boolean valid = true; diff --git a/fe/src/com/baidu/palo/analysis/CreateClusterStmt.java b/fe/src/com/baidu/palo/analysis/CreateClusterStmt.java index fc0d7acfac..40cdc18f7b 100644 --- a/fe/src/com/baidu/palo/analysis/CreateClusterStmt.java +++ b/fe/src/com/baidu/palo/analysis/CreateClusterStmt.java @@ -15,16 +15,20 @@ package com.baidu.palo.analysis; -import java.util.Map; - +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.FeNameFormat; import com.baidu.palo.common.InternalException; import com.baidu.palo.mysql.MysqlPassword; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; + import com.google.common.base.Strings; +import java.util.Map; + public class CreateClusterStmt extends DdlStmt { public static String CLUSTER_INSTANCE_NUM = "instance_num"; public static String CLUSTER_SUPERMAN_PASSWORD = "password"; @@ -62,8 +66,8 @@ public class CreateClusterStmt extends DdlStmt { @Override public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { FeNameFormat.checkDbName(clusterName); - if (!analyzer.getCatalog().getUserMgr().isAdmin(analyzer.getUser())) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_CLUSTER_NO_PERMISSIONS); + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.OPERATOR)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_CLUSTER_NO_AUTHORITY, analyzer.getQualifiedUser()); } if (properties == null || properties.size() == 0 || !properties.containsKey(CLUSTER_INSTANCE_NUM)) { diff --git a/fe/src/com/baidu/palo/analysis/CreateDbStmt.java b/fe/src/com/baidu/palo/analysis/CreateDbStmt.java index d8692267f6..c3efafcbe7 100644 --- a/fe/src/com/baidu/palo/analysis/CreateDbStmt.java +++ b/fe/src/com/baidu/palo/analysis/CreateDbStmt.java @@ -20,12 +20,16 @@ package com.baidu.palo.analysis; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.cluster.ClusterNamespace; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.FeNameFormat; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; + import com.google.common.base.Strings; // 用于描述CREATE DATABASE的内部结构 @@ -54,8 +58,9 @@ public class CreateDbStmt extends DdlStmt { } FeNameFormat.checkDbName(dbName); dbName = ClusterNamespace.getFullName(getClusterName(), dbName); - if (!analyzer.getCatalog().getUserMgr().isSuperuser(analyzer.getUser())) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, analyzer.getUser(), dbName); + + if (!Catalog.getCurrentCatalog().getAuth().checkDbPriv(ConnectContext.get(), dbName, PrivPredicate.CREATE)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, analyzer.getQualifiedUser(), dbName); } } diff --git a/fe/src/com/baidu/palo/analysis/CreateRepositoryStmt.java b/fe/src/com/baidu/palo/analysis/CreateRepositoryStmt.java new file mode 100644 index 0000000000..952d4cc275 --- /dev/null +++ b/fe/src/com/baidu/palo/analysis/CreateRepositoryStmt.java @@ -0,0 +1,103 @@ +// Modifications copyright (C) 2018, Baidu.com, Inc. +// Copyright 2018 The Apache Software Foundation + +// 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 + +package com.baidu.palo.analysis; + +import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.ErrorCode; +import com.baidu.palo.common.ErrorReport; +import com.baidu.palo.common.FeNameFormat; +import com.baidu.palo.common.InternalException; +import com.baidu.palo.common.util.PrintableMap; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; + +import com.google.common.base.Strings; + +import java.util.Map; + +public class CreateRepositoryStmt extends DdlStmt { + private boolean isReadOnly; + private String name; + private String brokerName; + private String location; + private Map properties; + + public CreateRepositoryStmt(boolean isReadOnly, String name, String brokerName, String location, + Map properties) { + this.isReadOnly = isReadOnly; + this.name = name; + this.brokerName = brokerName; + this.location = location; + this.properties = properties; + } + + public boolean isReadOnly() { + return isReadOnly; + } + + public String getName() { + return name; + } + + public String getBrokerName() { + return brokerName; + } + + public String getLocation() { + return location; + } + + public Map getProperties() { + return properties; + } + + @Override + public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { + super.analyze(analyzer); + + // check auth + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.ADMIN)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "ADMIN"); + } + + FeNameFormat.checkCommonName("repository", name); + + if (Strings.isNullOrEmpty(brokerName)) { + throw new AnalysisException("You must specify the broker of the repository"); + } + + if (Strings.isNullOrEmpty(location)) { + throw new AnalysisException("You must specify a location on the repository"); + } + } + + @Override + public String toSql() { + StringBuilder sb = new StringBuilder(); + sb.append("CREATE "); + if (isReadOnly) { + sb.append("READ_ONLY "); + } + sb.append("REPOSITORY `").append(name).append("` ").append("WITH BROKER `").append(brokerName).append("` "); + sb.append("PROPERTIES(").append(new PrintableMap<>(properties, " = ", true, false)).append(")"); + return sb.toString(); + } +} diff --git a/fe/src/com/baidu/palo/analysis/CreateRoleStmt.java b/fe/src/com/baidu/palo/analysis/CreateRoleStmt.java new file mode 100644 index 0000000000..84b11d59f8 --- /dev/null +++ b/fe/src/com/baidu/palo/analysis/CreateRoleStmt.java @@ -0,0 +1,46 @@ +// Copyright (c) 2018, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +package com.baidu.palo.analysis; + +import com.baidu.palo.cluster.ClusterNamespace; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.FeNameFormat; +import com.baidu.palo.common.InternalException; + +public class CreateRoleStmt extends DdlStmt { + + private String role; + + public CreateRoleStmt(String role) { + this.role = role; + } + + public String getQualifiedRole() { + return role; + } + + @Override + public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { + super.analyze(analyzer); + FeNameFormat.checkRoleName(role, false /* can not be admin */); + role = ClusterNamespace.getFullName(analyzer.getClusterName(), role); + } + + @Override + public String toSql() { + return "CREATE ROLE " + role; + } +} diff --git a/fe/src/com/baidu/palo/analysis/CreateTableStmt.java b/fe/src/com/baidu/palo/analysis/CreateTableStmt.java index 820ef7a707..f2d45530f0 100644 --- a/fe/src/com/baidu/palo/analysis/CreateTableStmt.java +++ b/fe/src/com/baidu/palo/analysis/CreateTableStmt.java @@ -20,7 +20,6 @@ package com.baidu.palo.analysis; -import com.baidu.palo.catalog.AccessPrivilege; import com.baidu.palo.catalog.AggregateType; import com.baidu.palo.catalog.Catalog; import com.baidu.palo.catalog.Column; @@ -37,6 +36,8 @@ import com.baidu.palo.common.io.Text; import com.baidu.palo.common.io.Writable; import com.baidu.palo.common.util.KuduUtil; import com.baidu.palo.common.util.PrintableMap; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.google.common.base.Strings; import com.google.common.collect.Lists; @@ -191,10 +192,9 @@ public class CreateTableStmt extends DdlStmt implements Writable { tableName.analyze(analyzer); FeNameFormat.checkTableName(tableName.getTbl()); - // check authenticate - if (!analyzer.getCatalog().getUserMgr() - .checkAccess(analyzer.getUser(), tableName.getDb(), AccessPrivilege.READ_WRITE)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, analyzer.getUser(), tableName.getDb()); + if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(ConnectContext.get(), tableName.getDb(), + tableName.getTbl(), PrivPredicate.CREATE)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "CREATE"); } analyzeEngineName(); @@ -358,7 +358,7 @@ public class CreateTableStmt extends DdlStmt implements Writable { if (idx != 0) { sb.append(",\n"); } - sb.append(column.toSql()); + sb.append(" ").append(column.toSql()); idx++; } sb.append("\n)"); diff --git a/fe/src/com/baidu/palo/analysis/CreateUserStmt.java b/fe/src/com/baidu/palo/analysis/CreateUserStmt.java index 7d675fc370..459509adfe 100644 --- a/fe/src/com/baidu/palo/analysis/CreateUserStmt.java +++ b/fe/src/com/baidu/palo/analysis/CreateUserStmt.java @@ -20,6 +20,7 @@ package com.baidu.palo.analysis; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.cluster.ClusterNamespace; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.ErrorCode; @@ -27,78 +28,86 @@ import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.FeNameFormat; import com.baidu.palo.common.InternalException; import com.baidu.palo.mysql.MysqlPassword; +import com.baidu.palo.mysql.privilege.PaloRole; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.google.common.base.Strings; -import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; -// this is memory struct from CREATE USER statement -// CREATE USER user_name [IDENTIFIED BY [PASSWORD] 'password'] -// -// CREATE USER user_name -// this clause create user without password -// eg. CREATE USER 'jeffrey' -// -// CREATE USER user_name IDENTIFIED BY 'password' -// this clause create user with password in plaintext mode -// eg. CREATE USER 'jeffrey' IDENTIFIED BY 'mypass' -// -// CREATE USER user_name IDENTIFIED BY PASSWORD 'password' -// this clause create user with password in hashed mode. -// eg. CREATE USER 'jeffrey' IDENTIFIED BY PASSWORD '*90E462C37378CED12064BB3388827D2BA3A9B689' +/* + * We support the following create user stmt + * 1. create user user@ip [identified by 'password'] + * specify the user name at a certain ip(wildcard is accepted), with optional password. + * the user@ip must not exist in system + * + * 2. create user user@[domain] [identified by 'password'] + * specify the user name at a certain domain, with optional password. + * the user@[domain] must not exist in system + * the daemon thread will resolve this domain to user@ip format + * + * 3. create user user@xx [identified by 'password'] role role_name + * not only create the specified user, but also grant all privs of the specified role to the user. + */ public class CreateUserStmt extends DdlStmt { private static final Logger LOG = LogManager.getLogger(CreateUserStmt.class); - private String user; + private boolean ifNotExist; + private UserIdentity userIdent; private String password; private byte[] scramblePassword; private boolean isPlain; - private boolean isSuperuser; + private String role; public CreateUserStmt() { } public CreateUserStmt(UserDesc userDesc) { - user = userDesc.getUser(); + userIdent = userDesc.getUserIdent(); password = userDesc.getPassword(); isPlain = userDesc.isPlain(); } - public CreateUserStmt(UserDesc userDesc, boolean isSuperuser) { - user = userDesc.getUser(); + public CreateUserStmt(boolean ifNotExist, UserDesc userDesc, String role) { + this.ifNotExist = ifNotExist; + userIdent = userDesc.getUserIdent(); password = userDesc.getPassword(); isPlain = userDesc.isPlain(); - this.isSuperuser = isSuperuser; + this.role = role; + } + + public boolean isIfNotExist() { + return ifNotExist; } public boolean isSuperuser() { - return isSuperuser; + return role.equalsIgnoreCase(PaloRole.ADMIN_ROLE); + } + + public boolean hasRole() { + return role != null; + } + + public String getQualifiedRole() { + return role; } public byte[] getPassword() { return scramblePassword; } - public String getUser() { - return user; - } - - private void checkUser() throws AnalysisException { - if (Strings.isNullOrEmpty(user)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_CANNOT_USER, "CREATE USER", user); - } - - FeNameFormat.checkUserName(user); + public UserIdentity getUserIdent() { + return userIdent; } @Override public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { super.analyze(analyzer); - checkUser(); + userIdent.analyze(analyzer.getClusterName()); // convert plain password to hashed password if (!Strings.isNullOrEmpty(password)) { - // TODO(zhaochun): convert password if (isPlain) { // convert plain password to scramble scramblePassword = MysqlPassword.makeScrambledPassword(password); @@ -108,32 +117,39 @@ public class CreateUserStmt extends DdlStmt { } else { scramblePassword = new byte[0]; } - user = ClusterNamespace.getFullName(getClusterName(), user); - // check authenticate - if (isSuperuser) { - // Only root can create superuser - if (!analyzer.getCatalog().getUserMgr().isAdmin(analyzer.getUser())) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_NO_PERMISSION_TO_CREATE_USER, analyzer.getUser()); - } - } else { - if (!analyzer.getCatalog().getUserMgr().isSuperuser(analyzer.getUser())) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_NO_PERMISSION_TO_CREATE_USER, analyzer.getUser()); + + if (role != null) { + if (role.equalsIgnoreCase("SUPERUSER")) { + // for forward compatibility + role = PaloRole.ADMIN_ROLE; } + FeNameFormat.checkRoleName(role, true /* can be admin */); + role = ClusterNamespace.getFullName(analyzer.getClusterName(), role); + } + + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "CREATE USER"); } } @Override public String toSql() { - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append("CREATE USER '").append(user).append("'"); + StringBuilder sb = new StringBuilder(); + sb.append("CREATE USER ").append(userIdent); if (!Strings.isNullOrEmpty(password)) { if (isPlain) { - stringBuilder.append(" IDENTIFIED BY '").append(password).append("'"); + sb.append(" IDENTIFIED BY '").append(password).append("'"); } else { - stringBuilder.append(" IDENTIFIED BY PASSWORD '").append(password).append("'"); + sb.append(" IDENTIFIED BY PASSWORD '").append(password).append("'"); } } - return stringBuilder.toString(); + + if (!Strings.isNullOrEmpty(role)) { + sb.append(" DEFAULT ROLE '").append(role).append("'"); + + } + + return sb.toString(); } @Override diff --git a/fe/src/com/baidu/palo/analysis/CreateViewStmt.java b/fe/src/com/baidu/palo/analysis/CreateViewStmt.java index 8b29a58002..43afeb5256 100644 --- a/fe/src/com/baidu/palo/analysis/CreateViewStmt.java +++ b/fe/src/com/baidu/palo/analysis/CreateViewStmt.java @@ -20,7 +20,7 @@ package com.baidu.palo.analysis; -import com.baidu.palo.catalog.AccessPrivilege; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.catalog.Column; import com.baidu.palo.catalog.ColumnType; import com.baidu.palo.catalog.PrimitiveType; @@ -28,12 +28,14 @@ import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.google.common.collect.Lists; import com.google.common.collect.Sets; -import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import java.util.List; import java.util.Set; @@ -157,7 +159,11 @@ public class CreateViewStmt extends DdlStmt { viewDefStmt.analyze(viewAnalyzer); // check privilege - analyzer.checkPrivilege(tableName.getDb(), AccessPrivilege.READ_WRITE); + if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(ConnectContext.get(), tableName.getDb(), + tableName.getTbl(), PrivPredicate.CREATE)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "CREATE"); + } + createColumnAndViewDefs(analyzer); } } diff --git a/fe/src/com/baidu/palo/analysis/DataDescription.java b/fe/src/com/baidu/palo/analysis/DataDescription.java index 728e576632..e0269fb262 100644 --- a/fe/src/com/baidu/palo/analysis/DataDescription.java +++ b/fe/src/com/baidu/palo/analysis/DataDescription.java @@ -20,17 +20,17 @@ package com.baidu.palo.analysis; -import java.util.List; -import java.util.Map; -import java.util.Set; - import com.baidu.palo.analysis.BinaryPredicate.Operator; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.catalog.Column; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.Pair; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.baidu.palo.thrift.TNetworkAddress; + import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Strings; @@ -38,9 +38,12 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; -import org.apache.commons.lang.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; + +import java.util.List; +import java.util.Map; +import java.util.Set; // used to describe data info which is needed to import. // // data_desc: @@ -391,10 +394,19 @@ public class DataDescription { } } - public void analyze() throws AnalysisException { + public void analyze(String fullDbName) throws AnalysisException { if (Strings.isNullOrEmpty(tableName)) { throw new AnalysisException("No table name in load statement."); } + + // check auth + if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(ConnectContext.get(), fullDbName, tableName, + PrivPredicate.LOAD)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_TABLEACCESS_DENIED_ERROR, "LOAD", + ConnectContext.get().getQualifiedUser(), + ConnectContext.get().getRemoteIP(), tableName); + } + if (filePathes == null || filePathes.isEmpty()) { throw new AnalysisException("No file path in load statement."); } diff --git a/fe/src/com/baidu/palo/analysis/DeleteStmt.java b/fe/src/com/baidu/palo/analysis/DeleteStmt.java index 5fb55d61f5..b0aa53ad04 100644 --- a/fe/src/com/baidu/palo/analysis/DeleteStmt.java +++ b/fe/src/com/baidu/palo/analysis/DeleteStmt.java @@ -21,12 +21,14 @@ package com.baidu.palo.analysis; import com.baidu.palo.analysis.CompoundPredicate.Operator; -import com.baidu.palo.catalog.AccessPrivilege; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.InternalException; import com.baidu.palo.common.util.PrintableMap; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.google.common.base.Strings; @@ -93,9 +95,11 @@ public class DeleteStmt extends DdlStmt { analyzePredicate(wherePredicate); // check access - if (!analyzer.getCatalog().getUserMgr() - .checkAccess(analyzer.getUser(), tbl.getDb(), AccessPrivilege.READ_WRITE)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, analyzer.getUser(), tbl.getDb()); + if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(ConnectContext.get(), tbl.getDb(), tbl.getTbl(), + PrivPredicate.LOAD)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_TABLEACCESS_DENIED_ERROR, "LOAD", + ConnectContext.get().getQualifiedUser(), + ConnectContext.get().getRemoteIP(), tbl.getTbl()); } } diff --git a/fe/src/com/baidu/palo/analysis/DescribeStmt.java b/fe/src/com/baidu/palo/analysis/DescribeStmt.java index a47b6f70c8..498be872a0 100644 --- a/fe/src/com/baidu/palo/analysis/DescribeStmt.java +++ b/fe/src/com/baidu/palo/analysis/DescribeStmt.java @@ -20,7 +20,6 @@ package com.baidu.palo.analysis; -import com.baidu.palo.catalog.AccessPrivilege; import com.baidu.palo.catalog.Catalog; import com.baidu.palo.catalog.Column; import com.baidu.palo.catalog.ColumnType; @@ -37,10 +36,13 @@ import com.baidu.palo.common.proc.ProcNodeInterface; import com.baidu.palo.common.proc.ProcResult; import com.baidu.palo.common.proc.ProcService; import com.baidu.palo.common.proc.TableProcDir; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.baidu.palo.qe.ShowResultSetMetaData; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; + import org.apache.commons.lang.StringUtils; import java.util.Arrays; @@ -95,10 +97,13 @@ public class DescribeStmt extends ShowStmt { @Override public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { dbTableName.analyze(analyzer); - if (!analyzer.getCatalog().getUserMgr() - .checkAccess(analyzer.getUser(), dbTableName.getDb(), AccessPrivilege.READ_ONLY)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, - analyzer.getUser(), dbTableName.getDb()); + + if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(ConnectContext.get(), dbTableName.getDb(), + dbTableName.getTbl(), PrivPredicate.SHOW)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_TABLEACCESS_DENIED_ERROR, "DESCRIBE", + ConnectContext.get().getQualifiedUser(), + ConnectContext.get().getRemoteIP(), + dbTableName.getTbl()); } Database db = Catalog.getInstance().getDb(dbTableName.getDb()); diff --git a/fe/src/com/baidu/palo/analysis/DropClusterStmt.java b/fe/src/com/baidu/palo/analysis/DropClusterStmt.java index ace081ffa8..f6ed0bcdf0 100644 --- a/fe/src/com/baidu/palo/analysis/DropClusterStmt.java +++ b/fe/src/com/baidu/palo/analysis/DropClusterStmt.java @@ -15,10 +15,13 @@ package com.baidu.palo.analysis; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.baidu.palo.system.SystemInfoService; import com.google.common.base.Strings; @@ -42,7 +45,7 @@ public class DropClusterStmt extends DdlStmt { throw new AnalysisException("Can not drop " + SystemInfoService.DEFAULT_CLUSTER); } - if (!analyzer.getCatalog().getUserMgr().isAdmin(analyzer.getUser())) { + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.OPERATOR)) { ErrorReport.reportAnalysisException(ErrorCode.ERR_CLUSTER_NO_PERMISSIONS); } } diff --git a/fe/src/com/baidu/palo/analysis/DropDbStmt.java b/fe/src/com/baidu/palo/analysis/DropDbStmt.java index f211f605e1..2921243a76 100644 --- a/fe/src/com/baidu/palo/analysis/DropDbStmt.java +++ b/fe/src/com/baidu/palo/analysis/DropDbStmt.java @@ -15,12 +15,15 @@ package com.baidu.palo.analysis; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.catalog.InfoSchemaDb; import com.baidu.palo.cluster.ClusterNamespace; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.google.common.base.Strings; @@ -51,10 +54,12 @@ public class DropDbStmt extends DdlStmt { dbName = ClusterNamespace.getFullName(getClusterName(), dbName); // Don't allowed to drop 'information_schema' if (dbName.equalsIgnoreCase(ClusterNamespace.getFullName(getClusterName(), InfoSchemaDb.DATABASE_NAME))) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, analyzer.getUser(), dbName); + ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, analyzer.getQualifiedUser(), dbName); } - if (!analyzer.getCatalog().getUserMgr().isSuperuser(analyzer.getUser())) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, analyzer.getUser(), dbName); + + if (!Catalog.getCurrentCatalog().getAuth().checkDbPriv(ConnectContext.get(), dbName, PrivPredicate.DROP)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, + ConnectContext.get().getQualifiedUser(), dbName); } } diff --git a/fe/src/com/baidu/palo/analysis/DropRepositoryStmt.java b/fe/src/com/baidu/palo/analysis/DropRepositoryStmt.java new file mode 100644 index 0000000000..0658ecb9d1 --- /dev/null +++ b/fe/src/com/baidu/palo/analysis/DropRepositoryStmt.java @@ -0,0 +1,63 @@ +// Modifications copyright (C) 2018, Baidu.com, Inc. +// Copyright 2018 The Apache Software Foundation + +// 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. + +package com.baidu.palo.analysis; + +import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.ErrorCode; +import com.baidu.palo.common.ErrorReport; +import com.baidu.palo.common.FeNameFormat; +import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; + +public class DropRepositoryStmt extends DdlStmt { + + private String repoName; + + public DropRepositoryStmt(String repoName) { + this.repoName = repoName; + } + + public String getRepoName() { + return repoName; + } + + @Override + public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { + super.analyze(analyzer); + + // check auth + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.ADMIN)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "ADMIN"); + } + + FeNameFormat.checkCommonName("repository", repoName); + } + + @Override + public String toSql() { + StringBuilder sb = new StringBuilder(); + sb.append("DROP "); + sb.append("REPOSITORY `").append(repoName).append("`"); + return sb.toString(); + } +} diff --git a/fe/src/com/baidu/palo/analysis/DropRoleStmt.java b/fe/src/com/baidu/palo/analysis/DropRoleStmt.java new file mode 100644 index 0000000000..3ec5efca1f --- /dev/null +++ b/fe/src/com/baidu/palo/analysis/DropRoleStmt.java @@ -0,0 +1,46 @@ +// Copyright (c) 2017, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +package com.baidu.palo.analysis; + +import com.baidu.palo.cluster.ClusterNamespace; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.FeNameFormat; +import com.baidu.palo.common.InternalException; + +public class DropRoleStmt extends DdlStmt { + + private String role; + + public DropRoleStmt(String role) { + this.role = role; + } + + public String getQualifiedRole() { + return role; + } + + @Override + public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { + super.analyze(analyzer); + FeNameFormat.checkRoleName(role, false /* can not be superuser */); + role = ClusterNamespace.getFullName(analyzer.getClusterName(), role); + } + + @Override + public String toSql() { + return "DROP ROLE " + role; + } +} diff --git a/fe/src/com/baidu/palo/analysis/DropTableStmt.java b/fe/src/com/baidu/palo/analysis/DropTableStmt.java index bbf03f2ec2..cfc96dee7f 100644 --- a/fe/src/com/baidu/palo/analysis/DropTableStmt.java +++ b/fe/src/com/baidu/palo/analysis/DropTableStmt.java @@ -15,11 +15,13 @@ package com.baidu.palo.analysis; -import com.baidu.palo.catalog.AccessPrivilege; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.google.common.base.Strings; @@ -64,10 +66,10 @@ public class DropTableStmt extends DdlStmt { } tableName.analyze(analyzer); // check access - if (!analyzer.getCatalog().getUserMgr() - .checkAccess(analyzer.getUser(), tableName.getDb(), AccessPrivilege.READ_WRITE)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, - analyzer.getUser(), tableName.getDb()); + + if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(ConnectContext.get(), tableName.getDb(), + tableName.getTbl(), PrivPredicate.DROP)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "DROP"); } } diff --git a/fe/src/com/baidu/palo/analysis/DropUserStmt.java b/fe/src/com/baidu/palo/analysis/DropUserStmt.java index 51dfb4516b..80ec770091 100644 --- a/fe/src/com/baidu/palo/analysis/DropUserStmt.java +++ b/fe/src/com/baidu/palo/analysis/DropUserStmt.java @@ -15,48 +15,46 @@ package com.baidu.palo.analysis; -import com.baidu.palo.cluster.ClusterNamespace; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.InternalException; -import com.google.common.base.Strings; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; -// DROP USER statement +// drop user cmy; public class DropUserStmt extends DdlStmt { - private String user; + private UserIdentity userIdent; - public DropUserStmt(String user) { - this.user = user; + public DropUserStmt(UserIdentity userIdent) { + this.userIdent = userIdent; } - public String getUser() { - return user; + public UserIdentity getUserIdentity() { + return userIdent; } @Override public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { super.analyze(analyzer); - if (Strings.isNullOrEmpty(user)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_CANNOT_USER, "DROP USER", user); + userIdent.analyze(analyzer.getClusterName()); + + if (!userIdent.getHost().equals("%")) { + throw new AnalysisException("Can not drop user with specified host: " + userIdent.getHost()); } - user = ClusterNamespace.getFullName(getClusterName(), user); - // check access - if (analyzer.getCatalog().getUserMgr().isSuperuser(user)) { - if (!analyzer.getCatalog().getUserMgr().isAdmin(analyzer.getUser())) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "DROP USER"); - } - } else { - if (!analyzer.getCatalog().getUserMgr().isSuperuser(analyzer.getUser())) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "DROP USER"); - } + + // check authenticate + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, + "DROP USER"); } } @Override public String toSql() { StringBuilder sb = new StringBuilder(); - sb.append("DROP USER '").append(user).append("'"); + sb.append("DROP USER ").append(userIdent); return sb.toString(); } diff --git a/fe/src/com/baidu/palo/analysis/ExportStmt.java b/fe/src/com/baidu/palo/analysis/ExportStmt.java index f48343a9df..08646a94ae 100644 --- a/fe/src/com/baidu/palo/analysis/ExportStmt.java +++ b/fe/src/com/baidu/palo/analysis/ExportStmt.java @@ -15,7 +15,6 @@ package com.baidu.palo.analysis; -import com.baidu.palo.catalog.AccessPrivilege; import com.baidu.palo.catalog.BrokerMgr; import com.baidu.palo.catalog.Catalog; import com.baidu.palo.catalog.Database; @@ -27,6 +26,8 @@ import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.InternalException; import com.baidu.palo.common.util.PrintableMap; import com.baidu.palo.common.util.PropertyAnalyzer; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; @@ -61,8 +62,6 @@ public class ExportStmt extends StatementBase { private TableRef tableRef; - private String user; - public ExportStmt(TableRef tableRef, String path, Map properties, BrokerDesc brokerDesc) { this.tableRef = tableRef; @@ -71,8 +70,6 @@ public class ExportStmt extends StatementBase { this.brokerDesc = brokerDesc; this.columnSeparator = DEFAULT_COLUMN_SEPARATOR; this.lineDelimiter = DEFAULT_LINE_DELIMITER; - - this.user = null; } public TableRef getTableRef() { @@ -99,10 +96,6 @@ public class ExportStmt extends StatementBase { return properties; } - public String getUser() { - return user; - } - public String getColumnSeparator() { return this.columnSeparator; } @@ -123,9 +116,13 @@ public class ExportStmt extends StatementBase { this.partitions = tableRef.getPartitions(); // check auth - user = analyzer.getUser(); - if (!analyzer.getCatalog().getUserMgr().checkAccess(user, tblName.getDb(), AccessPrivilege.READ_ONLY)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, user, tblName.getDb()); + if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(ConnectContext.get(), + tblName.getDb(), tblName.getTbl(), + PrivPredicate.SELECT)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_TABLEACCESS_DENIED_ERROR, "EXPORT", + ConnectContext.get().getQualifiedUser(), + ConnectContext.get().getRemoteIP(), + tblName.getTbl()); } // check table && partitions whether exist diff --git a/fe/src/com/baidu/palo/analysis/Expr.java b/fe/src/com/baidu/palo/analysis/Expr.java index a1ec5695e3..90d90b2f5f 100644 --- a/fe/src/com/baidu/palo/analysis/Expr.java +++ b/fe/src/com/baidu/palo/analysis/Expr.java @@ -1141,23 +1141,17 @@ abstract public class Expr extends TreeNode implements ParseNode, Cloneabl * failure to convert a string literal to a date literal */ public final Expr castTo(Type targetType) throws AnalysisException { - final Type type = Type.getAssignmentCompatibleType(this.type, targetType, false); - if (!type.isValid()) { - throw new AnalysisException("can't cast " + this.type + " to " + targetType); - } // If the targetType is NULL_TYPE then ignore the cast because NULL_TYPE // is compatible with all types and no cast is necessary. if (targetType.isNull()) { return this; } - if (!targetType.isDecimal()) { - // requested cast must be to assignment-compatible type - // (which implies no loss of precision) - if (!targetType.equals(type)) { - throw new AnalysisException("can't cast " + this.type + " to " + targetType); - } - } + + if ((targetType.isStringType() || targetType.isHllType()) + && (this.type.isStringType() || this.type.isHllType())) { + return this; + } // Preconditions.checkState(PrimitiveType.isImplicitCast(type, targetType), "cast %s to %s", this.type, targetType); // TODO(zc): use implicit cast Preconditions.checkState(Type.canCastTo(this.type, targetType), "cast %s to %s", this.type, targetType); diff --git a/fe/src/com/baidu/palo/analysis/FrontendClause.java b/fe/src/com/baidu/palo/analysis/FrontendClause.java index 9ee4a1e4fe..eebe3579bc 100644 --- a/fe/src/com/baidu/palo/analysis/FrontendClause.java +++ b/fe/src/com/baidu/palo/analysis/FrontendClause.java @@ -20,17 +20,21 @@ package com.baidu.palo.analysis; -import com.baidu.palo.common.AnalysisException; -import com.baidu.palo.common.ErrorCode; -import com.baidu.palo.common.ErrorReport; -import com.baidu.palo.common.Pair; -import com.baidu.palo.ha.FrontendNodeType; -import com.baidu.palo.system.SystemInfoService; - -import org.apache.commons.lang.NotImplementedException; -import com.google.common.base.Preconditions; -import com.google.common.base.Strings; - +import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.ErrorCode; +import com.baidu.palo.common.ErrorReport; +import com.baidu.palo.common.Pair; +import com.baidu.palo.ha.FrontendNodeType; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; +import com.baidu.palo.system.SystemInfoService; + +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; + +import org.apache.commons.lang.NotImplementedException; + import java.util.Map; public class FrontendClause extends AlterClause { @@ -54,9 +58,9 @@ public class FrontendClause extends AlterClause { @Override public void analyze(Analyzer analyzer) throws AnalysisException { - if (!analyzer.getCatalog().getUserMgr().isAdmin(analyzer.getUser())) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, - "ADD/DROP OBSERVER/REPLICA"); + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.OPERATOR)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, + analyzer.getQualifiedUser()); } Pair pair = SystemInfoService.validateHostAndPort(hostPort); diff --git a/fe/src/com/baidu/palo/analysis/GrantStmt.java b/fe/src/com/baidu/palo/analysis/GrantStmt.java index a11be2b012..d160f4a5cb 100644 --- a/fe/src/com/baidu/palo/analysis/GrantStmt.java +++ b/fe/src/com/baidu/palo/analysis/GrantStmt.java @@ -21,11 +21,19 @@ package com.baidu.palo.analysis; import com.baidu.palo.catalog.AccessPrivilege; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.cluster.ClusterNamespace; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; +import com.baidu.palo.common.FeNameFormat; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PaloPrivilege; +import com.baidu.palo.mysql.privilege.PrivBitSet; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; + +import com.google.common.base.Joiner; import com.google.common.base.Strings; import java.util.List; @@ -33,65 +41,80 @@ import java.util.List; // GRANT STMT // grant privilege to some user, this is an administrator operation. // -// GRANT privilege [, privilege] ON db_name TO user +// GRANT privilege [, privilege] ON db.tbl TO user [ROLE 'role']; public class GrantStmt extends DdlStmt { - private String user; - private String db; - private List privileges; + private UserIdentity userIdent; + private String role; + private TablePattern tblPattern; + private List privileges; - public GrantStmt(String user, String db, List privileges) { - this.user = user; - this.db = db; - this.privileges = privileges; + public GrantStmt(UserIdentity userIdent, String role, TablePattern tblPattern, List privileges) { + this.userIdent = userIdent; + this.role = role; + this.tblPattern = tblPattern; + PrivBitSet privs = PrivBitSet.of(); + for (AccessPrivilege accessPrivilege : privileges) { + privs.or(accessPrivilege.toPaloPrivilege()); + } + this.privileges = privs.toPrivilegeList(); } - public String getUser() { - return user; + public UserIdentity getUserIdent() { + return userIdent; } - public String getDb() { - return db; + public TablePattern getTblPattern() { + return tblPattern; } - public AccessPrivilege getPrivilege() { - return AccessPrivilege.merge(privileges); + public boolean hasRole() { + return !Strings.isNullOrEmpty(role); + } + + public String getQualifiedRole() { + return role; + } + + public List getPrivileges() { + return privileges; } @Override public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { super.analyze(analyzer); - if (Strings.isNullOrEmpty(user)) { - throw new AnalysisException("No user in grant statement."); + if (userIdent != null) { + userIdent.analyze(analyzer.getClusterName()); + } else { + FeNameFormat.checkUserName(role); } - if (Strings.isNullOrEmpty(db)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_NO_DB_ERROR); - } - db = ClusterNamespace.getFullName(getClusterName(), db); - user = ClusterNamespace.getFullName(getClusterName(), user); - + + tblPattern.analyze(analyzer.getClusterName()); + if (privileges == null || privileges.isEmpty()) { throw new AnalysisException("No privileges in grant statement."); } - if (!analyzer.getCatalog().getUserMgr().checkUserAccess(analyzer.getUser(), user)) { - throw new AnalysisException("No privilege to grant."); + if (role != null) { + FeNameFormat.checkRoleName(role, false /* can not be superuser */); + role = ClusterNamespace.getFullName(analyzer.getClusterName(), role); + } + + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, + "GRANT"); } } @Override public String toSql() { StringBuilder sb = new StringBuilder(); - sb.append("GRANT "); - int idx = 0; - for (AccessPrivilege privilege : privileges) { - if (idx != 0) { - sb.append(", "); - } - sb.append(privilege); - idx++; + sb.append("GRANT ").append(Joiner.on(", ").join(privileges)); + sb.append(" ON ").append(tblPattern).append(" TO "); + if (!Strings.isNullOrEmpty(role)) { + sb.append(" ROLE '").append(role).append("'"); + } else { + sb.append(userIdent); } - sb.append(" ON ").append(db).append(" TO '").append(user).append("'"); - return sb.toString(); } diff --git a/fe/src/com/baidu/palo/analysis/InformationFunction.java b/fe/src/com/baidu/palo/analysis/InformationFunction.java index b139feb8c0..daa999e90b 100644 --- a/fe/src/com/baidu/palo/analysis/InformationFunction.java +++ b/fe/src/com/baidu/palo/analysis/InformationFunction.java @@ -58,10 +58,10 @@ public class InformationFunction extends Expr { strValue = analyzer.getDefaultDb(); } else if (funcType.equalsIgnoreCase("USER")) { type = Type.VARCHAR; - strValue = analyzer.getUser(); + strValue = analyzer.getQualifiedUser(); } else if (funcType.equalsIgnoreCase("CURRENT_USER")) { type = Type.VARCHAR; - strValue = analyzer.getUser(); + strValue = analyzer.getQualifiedUser(); } else if (funcType.equalsIgnoreCase("CONNECTION_ID")) { type = Type.BIGINT; intValue = analyzer.getConnectId(); diff --git a/fe/src/com/baidu/palo/analysis/InsertStmt.java b/fe/src/com/baidu/palo/analysis/InsertStmt.java index 986bd2cd21..c276dbcff6 100644 --- a/fe/src/com/baidu/palo/analysis/InsertStmt.java +++ b/fe/src/com/baidu/palo/analysis/InsertStmt.java @@ -20,8 +20,8 @@ package com.baidu.palo.analysis; -import com.baidu.palo.catalog.AccessPrivilege; import com.baidu.palo.catalog.BrokerTable; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.catalog.Column; import com.baidu.palo.catalog.Database; import com.baidu.palo.catalog.MysqlTable; @@ -29,14 +29,17 @@ import com.baidu.palo.catalog.OlapTable; import com.baidu.palo.catalog.Partition; import com.baidu.palo.catalog.PartitionType; import com.baidu.palo.catalog.Table; +import com.baidu.palo.catalog.Type; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PrivPredicate; import com.baidu.palo.planner.DataPartition; import com.baidu.palo.planner.DataSink; import com.baidu.palo.planner.DataSplitSink; import com.baidu.palo.planner.ExportSink; +import com.baidu.palo.qe.ConnectContext; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @@ -136,9 +139,11 @@ public class InsertStmt extends DdlStmt { } // check access - if (!analyzer.getCatalog().getUserMgr() - .checkAccess(analyzer.getUser(), dbName, AccessPrivilege.READ_WRITE)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, analyzer.getUser(), dbName); + if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(ConnectContext.get(), tblName.getDb(), tblName.getTbl(), + PrivPredicate.LOAD)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_TABLEACCESS_DENIED_ERROR, "LOAD", + ConnectContext.get().getQualifiedUser(), + ConnectContext.get().getRemoteIP(), tblName.getTbl()); } dbs.put(dbName, db); @@ -152,11 +157,18 @@ public class InsertStmt extends DdlStmt { public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { super.analyze(analyzer); - // Check privilege if (targetTable == null) { tblName.analyze(analyzer); - analyzer.checkPrivilege(tblName.getDb(), AccessPrivilege.READ_WRITE); } + + // Check privilege + if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(ConnectContext.get(), tblName.getDb(), + tblName.getTbl(), PrivPredicate.LOAD)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_TABLEACCESS_DENIED_ERROR, "LOAD", + ConnectContext.get().getQualifiedUser(), + ConnectContext.get().getRemoteIP(), tblName.getTbl()); + } + // check partition if (targetPartitions != null && targetPartitions.isEmpty()) { ErrorReport.reportAnalysisException(ErrorCode.ERR_PARTITION_CLAUSE_ON_NONPARTITIONED); @@ -315,6 +327,20 @@ public class InsertStmt extends DdlStmt { } private Expr checkTypeCompatibility(Column col, Expr expr) throws AnalysisException { + // TargeTable's hll column must be hll_hash's result + if (col.getType().equals(Type.HLL)) { + final String hllAnalysisErrorLog = "Column's type is HLL," + + " it must be hll_hash function's result, column=" + col.getName(); + if (!(expr instanceof FunctionCallExpr)) { + throw new AnalysisException(hllAnalysisErrorLog); + } + + final FunctionCallExpr functionExpr = (FunctionCallExpr) expr; + if (!functionExpr.getFnName().getFunction().equalsIgnoreCase("hll_hash")) { + throw new AnalysisException(hllAnalysisErrorLog); + } + } + if (col.getDataType().equals(expr.getType())) { return expr; } @@ -350,6 +376,9 @@ public class InsertStmt extends DdlStmt { dataPartition = dataSink.getOutputPartition(); } else if (targetTable instanceof BrokerTable) { BrokerTable table = (BrokerTable) targetTable; + // TODO(lingbin): think use which one if have more than one path + // Map brokerProperties = Maps.newHashMap(); + // BrokerDesc brokerDesc = new BrokerDesc("test_broker", brokerProperties); BrokerDesc brokerDesc = new BrokerDesc(table.getBrokerName(), table.getBrokerProperties()); dataSink = new ExportSink( table.getWritablePath(), diff --git a/fe/src/com/baidu/palo/analysis/LabelName.java b/fe/src/com/baidu/palo/analysis/LabelName.java index eb53d3a4ad..d02b1bae09 100644 --- a/fe/src/com/baidu/palo/analysis/LabelName.java +++ b/fe/src/com/baidu/palo/analysis/LabelName.java @@ -28,6 +28,7 @@ import com.baidu.palo.common.FeNameFormat; import com.baidu.palo.common.io.Text; import com.baidu.palo.common.io.Writable; import com.baidu.palo.system.SystemInfoService; + import com.google.common.base.Strings; import org.apache.commons.lang.builder.HashCodeBuilder; diff --git a/fe/src/com/baidu/palo/analysis/LinkDbStmt.java b/fe/src/com/baidu/palo/analysis/LinkDbStmt.java index a1b69fb1fd..be504df7ea 100644 --- a/fe/src/com/baidu/palo/analysis/LinkDbStmt.java +++ b/fe/src/com/baidu/palo/analysis/LinkDbStmt.java @@ -20,11 +20,15 @@ package com.baidu.palo.analysis; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.cluster.ClusterNamespace; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; + import com.google.common.base.Strings; public class LinkDbStmt extends DdlStmt { @@ -62,8 +66,9 @@ public class LinkDbStmt extends DdlStmt { src.analyze(analyzer); dest.analyze(analyzer); - if (!analyzer.getCatalog().getUserMgr().isAdmin(analyzer.getUser())) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_CLUSTER_NO_PERMISSIONS); + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.ADMIN)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, + "ADMIN"); } if (Strings.isNullOrEmpty(src.getCluster()) || Strings.isNullOrEmpty(dest.getCluster()) diff --git a/fe/src/com/baidu/palo/analysis/LoadStmt.java b/fe/src/com/baidu/palo/analysis/LoadStmt.java index 47e1c030d9..cc71e3e0ae 100644 --- a/fe/src/com/baidu/palo/analysis/LoadStmt.java +++ b/fe/src/com/baidu/palo/analysis/LoadStmt.java @@ -20,13 +20,12 @@ package com.baidu.palo.analysis; -import com.baidu.palo.catalog.AccessPrivilege; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.DdlException; -import com.baidu.palo.common.ErrorCode; -import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.InternalException; import com.baidu.palo.common.util.PrintableMap; +import com.baidu.palo.qe.ConnectContext; + import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableSet; @@ -190,20 +189,16 @@ public class LoadStmt extends DdlStmt { if (brokerDesc != null) { dataDescription.setIsPullLoad(true); } - dataDescription.analyze(); + dataDescription.analyze(label.getDbName()); } - // check auth - user = analyzer.getUser(); - if (!analyzer.getCatalog().getUserMgr().checkAccess(user, label.getDbName(), AccessPrivilege.READ_WRITE)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, user, label.getDbName()); - } - try { checkProperties(properties); } catch (DdlException e) { throw new AnalysisException(e.getMessage()); } + + user = ConnectContext.get().getQualifiedUser(); } @Override diff --git a/fe/src/com/baidu/palo/analysis/MigrateDbStmt.java b/fe/src/com/baidu/palo/analysis/MigrateDbStmt.java index ef170752b5..09c0d16b89 100644 --- a/fe/src/com/baidu/palo/analysis/MigrateDbStmt.java +++ b/fe/src/com/baidu/palo/analysis/MigrateDbStmt.java @@ -20,11 +20,14 @@ package com.baidu.palo.analysis; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.cluster.ClusterNamespace; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; public class MigrateDbStmt extends DdlStmt { @@ -61,8 +64,9 @@ public class MigrateDbStmt extends DdlStmt { src.analyze(analyzer); dest.analyze(analyzer); - if (!analyzer.getCatalog().getUserMgr().isAdmin(analyzer.getUser())) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_CLUSTER_NO_PERMISSIONS); + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.ADMIN)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, + "ADMIN"); } srcCluster = src.getCluster(); diff --git a/fe/src/com/baidu/palo/analysis/ModifyTablePropertiesClause.java b/fe/src/com/baidu/palo/analysis/ModifyTablePropertiesClause.java index 7159a91b37..2f232dc3e1 100644 --- a/fe/src/com/baidu/palo/analysis/ModifyTablePropertiesClause.java +++ b/fe/src/com/baidu/palo/analysis/ModifyTablePropertiesClause.java @@ -20,10 +20,13 @@ package com.baidu.palo.analysis; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.util.PrintableMap; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import java.util.Map; @@ -40,16 +43,22 @@ public class ModifyTablePropertiesClause extends AlterClause { @Override public void analyze(Analyzer analyzer) throws AnalysisException { - if (!analyzer.getCatalog().getUserMgr().isAdmin(analyzer.getUser())) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, - "Modify table property"); - } - if (properties == null || properties.isEmpty()) { throw new AnalysisException("Properties is not set"); } + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.ALTER)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, + "ALTER"); + } + if (properties.containsKey(KEY_STORAGE_TYPE)) { + // if set storage type, we need ADMIN privs. + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.ADMIN)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, + "ADMIN"); + } + if (!properties.get(KEY_STORAGE_TYPE).equals("column")) { throw new AnalysisException("Can only change storage type to COLUMN"); } diff --git a/fe/src/com/baidu/palo/analysis/RecoverDbStmt.java b/fe/src/com/baidu/palo/analysis/RecoverDbStmt.java index fd5979640c..dddbc32377 100644 --- a/fe/src/com/baidu/palo/analysis/RecoverDbStmt.java +++ b/fe/src/com/baidu/palo/analysis/RecoverDbStmt.java @@ -20,12 +20,17 @@ package com.baidu.palo.analysis; -import com.baidu.palo.catalog.AccessPrivilege; +import com.baidu.palo.analysis.CompoundPredicate.Operator; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.cluster.ClusterNamespace; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PaloPrivilege; +import com.baidu.palo.mysql.privilege.PrivBitSet; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.google.common.base.Strings; @@ -47,9 +52,13 @@ public class RecoverDbStmt extends DdlStmt { ErrorReport.reportAnalysisException(ErrorCode.ERR_WRONG_DB_NAME, dbName); } dbName = ClusterNamespace.getFullName(getClusterName(), dbName); - if (!analyzer.getCatalog().getUserMgr() - .checkAccess(analyzer.getUser(), dbName, AccessPrivilege.READ_WRITE)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, analyzer.getUser(), dbName); + + if (!Catalog.getCurrentCatalog().getAuth().checkDbPriv(ConnectContext.get(), dbName, + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.ALTER_PRIV, + PaloPrivilege.CREATE_PRIV, + PaloPrivilege.ADMIN_PRIV), + Operator.OR))) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, analyzer.getQualifiedUser(), dbName); } } diff --git a/fe/src/com/baidu/palo/analysis/RecoverPartitionStmt.java b/fe/src/com/baidu/palo/analysis/RecoverPartitionStmt.java index aac5d9a5b1..b4d753be8d 100644 --- a/fe/src/com/baidu/palo/analysis/RecoverPartitionStmt.java +++ b/fe/src/com/baidu/palo/analysis/RecoverPartitionStmt.java @@ -20,11 +20,16 @@ package com.baidu.palo.analysis; -import com.baidu.palo.catalog.AccessPrivilege; +import com.baidu.palo.analysis.CompoundPredicate.Operator; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PaloPrivilege; +import com.baidu.palo.mysql.privilege.PrivBitSet; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.google.common.base.Strings; @@ -52,10 +57,16 @@ public class RecoverPartitionStmt extends DdlStmt { @Override public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { dbTblName.analyze(analyzer); - if (!analyzer.getCatalog().getUserMgr().checkAccess(analyzer.getUser(), - dbTblName.getDb(), AccessPrivilege.READ_WRITE)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, analyzer.getUser(), - dbTblName.getDb()); + if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(ConnectContext.get(), dbTblName.getDb(), + dbTblName.getTbl(), + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.ALTER_PRIV, + PaloPrivilege.CREATE_PRIV, + PaloPrivilege.ADMIN_PRIV), + Operator.OR))) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_TABLEACCESS_DENIED_ERROR, "RECOVERY", + ConnectContext.get().getQualifiedUser(), + ConnectContext.get().getRemoteIP(), + dbTblName.getTbl()); } } diff --git a/fe/src/com/baidu/palo/analysis/RecoverTableStmt.java b/fe/src/com/baidu/palo/analysis/RecoverTableStmt.java index 578a7357e9..00e1bcc660 100644 --- a/fe/src/com/baidu/palo/analysis/RecoverTableStmt.java +++ b/fe/src/com/baidu/palo/analysis/RecoverTableStmt.java @@ -20,11 +20,16 @@ package com.baidu.palo.analysis; -import com.baidu.palo.catalog.AccessPrivilege; +import com.baidu.palo.analysis.CompoundPredicate.Operator; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PaloPrivilege; +import com.baidu.palo.mysql.privilege.PrivBitSet; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.google.common.base.Strings; @@ -47,9 +52,16 @@ public class RecoverTableStmt extends DdlStmt { public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { dbTblName.analyze(analyzer); - if (!analyzer.getCatalog().getUserMgr().checkAccess(analyzer.getUser(), dbTblName.getDb(), - AccessPrivilege.READ_WRITE)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, analyzer.getUser(), dbTblName.getDb()); + if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(ConnectContext.get(), dbTblName.getDb(), + dbTblName.getTbl(), + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.ALTER_PRIV, + PaloPrivilege.CREATE_PRIV, + PaloPrivilege.ADMIN_PRIV), + Operator.OR))) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_TABLEACCESS_DENIED_ERROR, "RECOVERY", + ConnectContext.get().getQualifiedUser(), + ConnectContext.get().getRemoteIP(), + dbTblName.getTbl()); } } diff --git a/fe/src/com/baidu/palo/analysis/RestoreStmt.java b/fe/src/com/baidu/palo/analysis/RestoreStmt.java index 0810229b16..0cc4d495ad 100644 --- a/fe/src/com/baidu/palo/analysis/RestoreStmt.java +++ b/fe/src/com/baidu/palo/analysis/RestoreStmt.java @@ -20,18 +20,107 @@ package com.baidu.palo.analysis; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.ErrorCode; +import com.baidu.palo.common.ErrorReport; +import com.baidu.palo.common.FeConstants; +import com.baidu.palo.common.InternalException; import com.baidu.palo.common.util.PrintableMap; import com.google.common.base.Joiner; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; import java.util.List; import java.util.Map; +import java.util.Set; public class RestoreStmt extends AbstractBackupStmt { + private final static String PROP_ALLOW_LOAD = "allow_load"; + private final static String PROP_REPLICATION_NUM = "replication_num"; + private final static String PROP_BACKUP_TIMESTAMP = "backup_timestamp"; - public RestoreStmt(LabelName labelName, List restoreObjNames, - String restorePath, Map properties) { - super(labelName, restoreObjNames, restorePath, properties); + private boolean allowLoad = false; + private int replicationNum = FeConstants.default_replication_num; + private String backupTimestamp = null; + + public RestoreStmt(LabelName labelName, String repoName, List tblRefs, Map properties) { + super(labelName, repoName, tblRefs, properties); + } + + public boolean allowLoad() { + return allowLoad; + } + + public int getReplicationNum() { + return replicationNum; + } + + public String getBackupTimestamp() { + return backupTimestamp; + } + + @Override + public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { + super.analyze(analyzer); + + // check if alias is duplicated + Set aliasSet = Sets.newHashSet(); + for (TableRef tblRef : tblRefs) { + aliasSet.add(tblRef.getName().getTbl()); + } + + for (TableRef tblRef : tblRefs) { + if (tblRef.hasExplicitAlias() && !aliasSet.add(tblRef.getExplicitAlias())) { + throw new AnalysisException("Duplicated alias name: " + tblRef.getExplicitAlias()); + } + } + } + + @Override + public void analyzeProperties() throws AnalysisException { + super.analyzeProperties(); + + Map copiedProperties = Maps.newHashMap(properties); + // allow load + if (copiedProperties.containsKey(PROP_ALLOW_LOAD)) { + if (copiedProperties.get(PROP_ALLOW_LOAD).equalsIgnoreCase("true")) { + allowLoad = true; + } else if (copiedProperties.get(PROP_ALLOW_LOAD).equalsIgnoreCase("false")) { + allowLoad = false; + } else { + ErrorReport.reportAnalysisException(ErrorCode.ERR_COMMON_ERROR, + "Invalid allow load value: " + + copiedProperties.get(PROP_ALLOW_LOAD)); + } + copiedProperties.remove(PROP_ALLOW_LOAD); + } + + // replication num + if (copiedProperties.containsKey(PROP_REPLICATION_NUM)) { + try { + replicationNum = Integer.valueOf(copiedProperties.get(PROP_REPLICATION_NUM)); + } catch (NumberFormatException e) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_COMMON_ERROR, + "Invalid replication num format: " + + copiedProperties.get(PROP_REPLICATION_NUM)); + } + copiedProperties.remove(PROP_REPLICATION_NUM); + } + + // backup timestamp + if (copiedProperties.containsKey(PROP_BACKUP_TIMESTAMP)) { + backupTimestamp = copiedProperties.get(PROP_BACKUP_TIMESTAMP); + copiedProperties.remove(PROP_BACKUP_TIMESTAMP); + } else { + ErrorReport.reportAnalysisException(ErrorCode.ERR_COMMON_ERROR, + "Missing " + PROP_BACKUP_TIMESTAMP + " property"); + } + + if (!copiedProperties.isEmpty()) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_COMMON_ERROR, + "Unknown restore job properties: " + copiedProperties.keySet()); + } } @Override @@ -42,15 +131,14 @@ public class RestoreStmt extends AbstractBackupStmt { @Override public String toSql() { StringBuilder sb = new StringBuilder(); - sb.append("RESTORE LABEL ").append(labelName.toSql()); - if (!objNames.isEmpty()) { - sb.append(" ("); - sb.append(Joiner.on(", ").join(objNames)); - sb.append(")"); - } - sb.append(" FROM \"").append(remotePath).append("\" PROPERTIES("); - sb.append(new PrintableMap(properties, "=", true, false)); - sb.append(")"); + sb.append("RESTORE SNAPSHOT ").append(labelName.toSql()); + sb.append("\n").append("FROM ").append(repoName).append("\nON\n("); + + sb.append(Joiner.on(",\n").join(tblRefs)); + + sb.append("\n)\nPROPERTIES\n("); + sb.append(new PrintableMap(properties, " = ", true, true)); + sb.append("\n)"); return sb.toString(); } } diff --git a/fe/src/com/baidu/palo/analysis/RevokeStmt.java b/fe/src/com/baidu/palo/analysis/RevokeStmt.java index d698a2e7e8..ed83d9be4b 100644 --- a/fe/src/com/baidu/palo/analysis/RevokeStmt.java +++ b/fe/src/com/baidu/palo/analysis/RevokeStmt.java @@ -20,52 +20,91 @@ package com.baidu.palo.analysis; +import com.baidu.palo.catalog.AccessPrivilege; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.cluster.ClusterNamespace; import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.ErrorCode; +import com.baidu.palo.common.ErrorReport; +import com.baidu.palo.common.FeNameFormat; +import com.baidu.palo.mysql.privilege.PaloPrivilege; +import com.baidu.palo.mysql.privilege.PrivBitSet; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; +import com.google.common.base.Joiner; import com.google.common.base.Strings; +import java.util.List; + // REVOKE STMT // revoke privilege from some user, this is an administrator operation. // -// REVOKE ALL ON db_name FROM user +// REVOKE privilege [, privilege] ON db.tbl FROM user [ROLE 'role']; public class RevokeStmt extends DdlStmt { - private String user; - private String db; + private UserIdentity userIdent; + private String role; + private TablePattern tblPattern; + private List privileges; - public RevokeStmt(String user, String db) { - this.user = user; - this.db = db; + public RevokeStmt(UserIdentity userIdent, String role, TablePattern tblPattern, List privileges) { + this.userIdent = userIdent; + this.role = role; + this.tblPattern = tblPattern; + PrivBitSet privs = PrivBitSet.of(); + for (AccessPrivilege accessPrivilege : privileges) { + privs.or(accessPrivilege.toPaloPrivilege()); + } + this.privileges = privs.toPrivilegeList(); } - public String getUser() { - return user; + public UserIdentity getUserIdent() { + return userIdent; } - public String getDb() { - return db; + public TablePattern getTblPattern() { + return tblPattern; + } + + public String getQualifiedRole() { + return role; + } + + public List getPrivileges() { + return privileges; } @Override public void analyze(Analyzer analyzer) throws AnalysisException { - if (Strings.isNullOrEmpty(user)) { - throw new AnalysisException("No user in grant statement."); + if (userIdent != null) { + userIdent.analyze(analyzer.getClusterName()); + } else { + FeNameFormat.checkRoleName(role, false /* can not be superuser */); + role = ClusterNamespace.getFullName(analyzer.getClusterName(), role); } - user = ClusterNamespace.getFullName(analyzer.getClusterName(), user); - if (Strings.isNullOrEmpty(db)) { - throw new AnalysisException("No database in grant statement."); + + tblPattern.analyze(analyzer.getClusterName()); + + if (privileges == null || privileges.isEmpty()) { + throw new AnalysisException("No privileges in revoke statement."); } - db = ClusterNamespace.getFullName(analyzer.getClusterName(), db); - if (!analyzer.getCatalog().getUserMgr().checkUserAccess(analyzer.getUser(), user)) { - throw new AnalysisException("No privilege to grant."); + + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, + "REVOKE"); } } @Override public String toSql() { StringBuilder sb = new StringBuilder(); - sb.append("REVOKE ALL ON ").append(db).append(" FROM '").append(user).append("'"); - + sb.append("REVOKE ").append(Joiner.on(", ").join(privileges)); + sb.append(" ON ").append(tblPattern).append(" FROM "); + if (!Strings.isNullOrEmpty(role)) { + sb.append(" ROLE '").append(role).append("'"); + } else { + sb.append(userIdent); + } return sb.toString(); } diff --git a/fe/src/com/baidu/palo/analysis/SelectStmt.java b/fe/src/com/baidu/palo/analysis/SelectStmt.java index 927dc997cd..7292fcdeef 100644 --- a/fe/src/com/baidu/palo/analysis/SelectStmt.java +++ b/fe/src/com/baidu/palo/analysis/SelectStmt.java @@ -20,7 +20,7 @@ package com.baidu.palo.analysis; -import com.baidu.palo.catalog.AccessPrivilege; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.catalog.Column; import com.baidu.palo.catalog.Database; import com.baidu.palo.catalog.OlapTable; @@ -37,26 +37,26 @@ import com.baidu.palo.common.Pair; import com.baidu.palo.common.TableAliasGenerator; import com.baidu.palo.common.TreeNode; import com.baidu.palo.common.util.SqlUtils; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.baidu.palo.rewrite.ExprRewriter; -import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.base.Predicates; import com.google.common.base.Strings; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; - import com.google.common.collect.Sets; -import org.apache.logging.log4j.Logger; + import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; -import java.util.ListIterator; import java.util.Map; import java.util.Set; @@ -231,11 +231,16 @@ public class SelectStmt extends QueryStmt { ErrorReport.reportAnalysisException(ErrorCode.ERR_BAD_DB_ERROR, dbName); } - // check access - if (!analyzer.getCatalog().getUserMgr() - .checkAccess(analyzer.getUser(), dbName, AccessPrivilege.READ_ONLY)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, analyzer.getUser(), db); + // check auth + if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(ConnectContext.get(), dbName, + tblRef.getName().getTbl(), + PrivPredicate.SELECT)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_TABLEACCESS_DENIED_ERROR, "SELECT", + ConnectContext.get().getQualifiedUser(), + ConnectContext.get().getRemoteIP(), + tblRef.getName().getTbl()); } + dbs.put(dbName, db); } } diff --git a/fe/src/com/baidu/palo/analysis/SetPassVar.java b/fe/src/com/baidu/palo/analysis/SetPassVar.java index c30d7a99d3..a10b235c6d 100644 --- a/fe/src/com/baidu/palo/analysis/SetPassVar.java +++ b/fe/src/com/baidu/palo/analysis/SetPassVar.java @@ -20,27 +20,30 @@ package com.baidu.palo.analysis; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.cluster.ClusterNamespace; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; import com.baidu.palo.mysql.MysqlPassword; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.google.common.base.Strings; public class SetPassVar extends SetVar { - private String user; + private UserIdentity userIdent; private String passwdParam; private byte[] passwdBytes; // The password in parameter is a hashed password. - public SetPassVar(String user, String passwd) { - this.user = user; + public SetPassVar(UserIdentity userIdent, String passwd) { + this.userIdent = userIdent; this.passwdParam = passwd; } - public String getUser() { - return user; + public UserIdentity getUserIdent() { + return userIdent; } public byte[] getPassword() { @@ -52,16 +55,29 @@ public class SetPassVar extends SetVar { if (Strings.isNullOrEmpty(analyzer.getClusterName())) { ErrorReport.reportAnalysisException(ErrorCode.ERR_CLUSTER_NO_SELECT_CLUSTER); } - if (Strings.isNullOrEmpty(user)) { - user = analyzer.getUser(); - } else { - user = ClusterNamespace.getFullName(analyzer.getClusterName(), user); + + boolean isSelf = false; + ConnectContext ctx = ConnectContext.get(); + if (userIdent == null) { + // set userIdent as itself + userIdent = new UserIdentity(ClusterNamespace.getNameFromFullName(analyzer.getQualifiedUser()), + ctx.getRemoteIP()); + isSelf = true; } + userIdent.analyze(analyzer.getClusterName()); + // Check password passwdBytes = MysqlPassword.checkPassword(passwdParam); - // Check user - if (!analyzer.getCatalog().getUserMgr().checkUserAccess(analyzer.getUser(), user)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_PASSWORD_NOT_ALLOWED); + + // check privs. + // 1. this is user itself + if (isSelf) { + return; + } + + // 2. user has grant privs + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT"); } } @@ -72,6 +88,6 @@ public class SetPassVar extends SetVar { @Override public String toSql() { - return "SET PASSWORD FOR '" + user + "' = '" + new String(passwdBytes) + "'"; + return "SET PASSWORD FOR " + userIdent + " = '" + new String(passwdBytes) + "'"; } } diff --git a/fe/src/com/baidu/palo/analysis/SetUserPropertyStmt.java b/fe/src/com/baidu/palo/analysis/SetUserPropertyStmt.java index 5c4f15687b..54b5cf8f7b 100644 --- a/fe/src/com/baidu/palo/analysis/SetUserPropertyStmt.java +++ b/fe/src/com/baidu/palo/analysis/SetUserPropertyStmt.java @@ -23,6 +23,8 @@ package com.baidu.palo.analysis; import com.baidu.palo.cluster.ClusterNamespace; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.qe.ConnectContext; import com.google.common.base.Strings; @@ -51,10 +53,10 @@ public class SetUserPropertyStmt extends DdlStmt { if (Strings.isNullOrEmpty(user)) { // If param 'user' is not set, use the login user name. // The login user name is full-qualified with cluster name. - user = analyzer.getUser(); + user = ConnectContext.get().getQualifiedUser(); } else { // If param 'user' is set, check if it need to be full-qualified - if (!analyzer.getCatalog().getUserMgr().isAdmin(user)) { + if (!user.equals(PaloAuth.ROOT_USER) && !user.equals(PaloAuth.ADMIN_USER)) { user = ClusterNamespace.getFullName(getClusterName(), user); } } @@ -62,8 +64,10 @@ public class SetUserPropertyStmt extends DdlStmt { if (propertyList == null || propertyList.isEmpty()) { throw new AnalysisException("Empty properties"); } + + boolean isSelf = user.equals(ConnectContext.get().getQualifiedUser()); for (SetVar var : propertyList) { - ((SetUserPropertyVar) var).analyze(analyzer, user); + ((SetUserPropertyVar) var).analyze(analyzer, isSelf); } } diff --git a/fe/src/com/baidu/palo/analysis/SetUserPropertyVar.java b/fe/src/com/baidu/palo/analysis/SetUserPropertyVar.java index 5a114ad6fe..871a89d4af 100644 --- a/fe/src/com/baidu/palo/analysis/SetUserPropertyVar.java +++ b/fe/src/com/baidu/palo/analysis/SetUserPropertyVar.java @@ -20,10 +20,13 @@ package com.baidu.palo.analysis; -import com.baidu.palo.catalog.UserProperty; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.mysql.privilege.UserProperty; +import com.baidu.palo.qe.ConnectContext; import com.google.common.base.Strings; @@ -49,23 +52,22 @@ public class SetUserPropertyVar extends SetVar { return value; } - public void analyze(Analyzer analyzer, String user) throws AnalysisException { + public void analyze(Analyzer analyzer, boolean isSelf) throws AnalysisException { if (Strings.isNullOrEmpty(key)) { throw new AnalysisException("User property key is null"); } - checkAcess(analyzer, user); + checkAccess(analyzer, isSelf); } - private void checkAcess(Analyzer analyzer, String user) throws AnalysisException { + private void checkAccess(Analyzer analyzer, boolean isSelf) throws AnalysisException { for (Pattern advPattern : UserProperty.ADVANCED_PROPERTIES) { Matcher matcher = advPattern.matcher(key); if (matcher.find()) { - if (!analyzer.getCatalog().getUserMgr().isSuperuser(analyzer.getUser())) { + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.ADMIN)) { ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, - "SET PROPERTY FOR " + user + " " + key); + "ADMIN"); } - return; } } @@ -73,11 +75,11 @@ public class SetUserPropertyVar extends SetVar { for (Pattern commPattern : UserProperty.COMMON_PROPERTIES) { Matcher matcher = commPattern.matcher(key); if (matcher.find()) { - if (!analyzer.getCatalog().getUserMgr().checkUserAccess(analyzer.getUser(), user)) { + if (!isSelf && !Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), + PrivPredicate.ADMIN)) { ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, - "SET PROPERTY FOR " + user + " " + key); + "GRANT"); } - return; } } diff --git a/fe/src/com/baidu/palo/analysis/SetVar.java b/fe/src/com/baidu/palo/analysis/SetVar.java index 9ecc4c8c2f..a7084fa5cd 100644 --- a/fe/src/com/baidu/palo/analysis/SetVar.java +++ b/fe/src/com/baidu/palo/analysis/SetVar.java @@ -20,11 +20,14 @@ package com.baidu.palo.analysis; -import com.baidu.palo.catalog.UserResource; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.mysql.privilege.UserResource; +import com.baidu.palo.qe.ConnectContext; import com.baidu.palo.qe.SessionVariable; import com.google.common.base.Strings; @@ -75,8 +78,9 @@ public class SetVar { throw new AnalysisException("No variable name in set statement."); } if (type == SetType.GLOBAL) { - if (!analyzer.getCatalog().getUserMgr().isSuperuser(analyzer.getUser())) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "SET GLOBAL"); + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.ADMIN)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, + "ADMIN"); } } if (value == null) { diff --git a/fe/src/com/baidu/palo/analysis/ShowAlterStmt.java b/fe/src/com/baidu/palo/analysis/ShowAlterStmt.java index bec0cf183a..0597aa558b 100644 --- a/fe/src/com/baidu/palo/analysis/ShowAlterStmt.java +++ b/fe/src/com/baidu/palo/analysis/ShowAlterStmt.java @@ -15,7 +15,6 @@ package com.baidu.palo.analysis; -import com.baidu.palo.catalog.AccessPrivilege; import com.baidu.palo.catalog.Column; import com.baidu.palo.catalog.ColumnType; import com.baidu.palo.catalog.Database; @@ -34,8 +33,8 @@ import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; -import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; /* * ShowAlterStmt: used to show process state of alter statement. @@ -74,7 +73,6 @@ public class ShowAlterStmt extends ShowStmt { @Override public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { super.analyze(analyzer); - final String dbNameWithoutPrefix = dbName; if (Strings.isNullOrEmpty(dbName)) { dbName = analyzer.getDefaultDb(); if (Strings.isNullOrEmpty(dbName)) { @@ -85,11 +83,9 @@ public class ShowAlterStmt extends ShowStmt { } Preconditions.checkNotNull(type); - // check access - if (!analyzer.getCatalog().getUserMgr().checkAccess(analyzer.getUser(), dbName, AccessPrivilege.READ_ONLY)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, analyzer.getUser(), - dbNameWithoutPrefix); - } + + // check auth when get job info + handleShowAlterTable(analyzer); } @@ -101,21 +97,21 @@ public class ShowAlterStmt extends ShowStmt { } // build proc path - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append("/jobs/"); - stringBuilder.append(db.getId()); + StringBuilder sb = new StringBuilder(); + sb.append("/jobs/"); + sb.append(db.getId()); if (type == AlterType.COLUMN) { - stringBuilder.append("/schema_change"); + sb.append("/schema_change"); } else if (type == AlterType.ROLLUP) { - stringBuilder.append("/rollup"); + sb.append("/rollup"); } else { throw new InternalException("SHOW " + type.name() + " does not implement yet"); } - LOG.debug("process SHOW PROC '{}';", stringBuilder.toString()); + LOG.debug("process SHOW PROC '{}';", sb.toString()); // create show proc stmt // '/jobs/db_name/rollup|schema_change/ - node = ProcService.getInstance().open(stringBuilder.toString()); + node = ProcService.getInstance().open(sb.toString()); if (node == null) { throw new AnalysisException("Failed to show alter table"); } diff --git a/fe/src/com/baidu/palo/analysis/ShowBackendsStmt.java b/fe/src/com/baidu/palo/analysis/ShowBackendsStmt.java index 5564ad5a13..0099772e10 100644 --- a/fe/src/com/baidu/palo/analysis/ShowBackendsStmt.java +++ b/fe/src/com/baidu/palo/analysis/ShowBackendsStmt.java @@ -15,9 +15,16 @@ package com.baidu.palo.analysis; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.catalog.Column; import com.baidu.palo.catalog.ColumnType; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.ErrorCode; +import com.baidu.palo.common.ErrorReport; +import com.baidu.palo.common.InternalException; import com.baidu.palo.common.proc.BackendsProcDir; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.baidu.palo.qe.ShowResultSetMetaData; public class ShowBackendsStmt extends ShowStmt { @@ -25,11 +32,21 @@ public class ShowBackendsStmt extends ShowStmt { public ShowBackendsStmt() { } + @Override + public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.ADMIN) + && !Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), + PrivPredicate.OPERATOR)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "ADMIN/OPERATOR"); + } + } + @Override public ShowResultSetMetaData getMetaData() { ShowResultSetMetaData.Builder builder = ShowResultSetMetaData.builder(); for (String title : BackendsProcDir.TITLE_NAMES) { - if (title.equals("HostName") || title.contains("Port")) { + // hide hostname for SHOW BACKENDS stmt + if (title.equals("HostName")) { continue; } builder.addColumn(new Column(title, ColumnType.createVarchar(30))); @@ -37,3 +54,4 @@ public class ShowBackendsStmt extends ShowStmt { return builder.build(); } } + diff --git a/fe/src/com/baidu/palo/analysis/ShowBackupStmt.java b/fe/src/com/baidu/palo/analysis/ShowBackupStmt.java index ea282f1a9e..1684b54fcc 100644 --- a/fe/src/com/baidu/palo/analysis/ShowBackupStmt.java +++ b/fe/src/com/baidu/palo/analysis/ShowBackupStmt.java @@ -15,146 +15,60 @@ package com.baidu.palo.analysis; -import com.baidu.palo.analysis.BinaryPredicate.Operator; -import com.baidu.palo.backup.BackupHandler; -import com.baidu.palo.backup.BackupJob; -import com.baidu.palo.catalog.AccessPrivilege; import com.baidu.palo.catalog.Catalog; import com.baidu.palo.catalog.Column; import com.baidu.palo.catalog.ColumnType; -import com.baidu.palo.catalog.Database; +import com.baidu.palo.cluster.ClusterNamespace; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.InternalException; -import com.baidu.palo.common.PatternMatcher; -import com.baidu.palo.common.proc.BackupProcNode; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.baidu.palo.qe.ShowResultSetMetaData; import com.google.common.base.Strings; - -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; +import com.google.common.collect.ImmutableList; public class ShowBackupStmt extends ShowStmt { + public static final ImmutableList TITLE_NAMES = new ImmutableList.Builder() + .add("JobId").add("SnapshotName").add("DbName").add("State").add("BackupObjs").add("CreateTime") + .add("SnapshotFinishedTime").add("UploadFinishedTime").add("FinishedTime").add("UnfinishedTasks") + .add("TaskErrMsg").add("Status").add("Timeout") + .build(); private String dbName; - private Expr where; - private String label; - public ShowBackupStmt(String dbName, Expr where) { + public ShowBackupStmt(String dbName) { this.dbName = dbName; - this.where = where; } public String getDbName() { return dbName; } - public String getLabel() { - return label; - } - @Override public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { + super.analyze(analyzer); if (Strings.isNullOrEmpty(dbName)) { dbName = analyzer.getDefaultDb(); if (Strings.isNullOrEmpty(dbName)) { ErrorReport.reportAnalysisException(ErrorCode.ERR_NO_DB_ERROR); } + } else { + dbName = ClusterNamespace.getFullName(getClusterName(), dbName); } - // check access - if (!analyzer.getCatalog().getUserMgr().checkAccess(analyzer.getUser(), dbName, AccessPrivilege.READ_ONLY)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, analyzer.getUser(), dbName); - } - - analyzeWhere(); - } - - private void analyzeWhere() throws AnalysisException { - boolean valid = true; - if (where == null) { - return; - } - - CHECK: { - if (where instanceof BinaryPredicate) { - BinaryPredicate binaryPredicate = (BinaryPredicate) where; - if (binaryPredicate.getOp() != Operator.EQ) { - valid = false; - break CHECK; - } - } else if (where instanceof LikePredicate) { - LikePredicate likePredicate = (LikePredicate) where; - if (likePredicate.getOp() != LikePredicate.Operator.LIKE) { - valid = false; - break CHECK; - } - } else { - valid = false; - break CHECK; - } - - // left child - if (!(where.getChild(0) instanceof SlotRef)) { - valid = false; - break CHECK; - } - String leftKey = ((SlotRef) where.getChild(0)).getColumnName(); - if (!leftKey.equalsIgnoreCase("label")) { - valid = false; - break CHECK; - } - - // right child - if (!(where.getChild(1) instanceof StringLiteral)) { - valid = false; - break CHECK; - } - - label = ((StringLiteral) where.getChild(1)).getStringValue(); - if (Strings.isNullOrEmpty(label)) { - valid = false; - break CHECK; - } - } - - if (!valid) { - throw new AnalysisException("Where clause should looks like: LABEL = \"your_backup_label\"," - + " or LABEL LIKE \"matcher\""); + // check auth + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.ADMIN)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "ADMIN"); } } - public List> getResultRows() throws AnalysisException { - List> result = new LinkedList>(); - Database db = Catalog.getInstance().getDb(dbName); - if (db == null) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_BAD_DB_ERROR, dbName); - } - - BackupHandler backupHandler = Catalog.getInstance().getBackupHandler(); - PatternMatcher matcher = null; - if (!Strings.isNullOrEmpty(label)) { - matcher = PatternMatcher.createMysqlPattern(label); - } - List> backupJobInfos = backupHandler.getJobInfosByDb(db.getId(), BackupJob.class, matcher); - for (List infoStr : backupJobInfos) { - List oneInfo = new ArrayList(BackupProcNode.TITLE_NAMES.size()); - for (Comparable element : infoStr) { - oneInfo.add(element.toString()); - } - result.add(oneInfo); - } - return result; - } - - @Override public ShowResultSetMetaData getMetaData() { ShowResultSetMetaData.Builder builder = ShowResultSetMetaData.builder(); - for (String title : BackupProcNode.TITLE_NAMES) { + for (String title : TITLE_NAMES) { builder.addColumn(new Column(title, ColumnType.createVarchar(30))); } return builder.build(); @@ -168,7 +82,6 @@ public class ShowBackupStmt extends ShowStmt { builder.append(" FROM `").append(dbName).append("` "); } - builder.append(where.toSql()); return builder.toString(); } diff --git a/fe/src/com/baidu/palo/analysis/ShowBrokerStmt.java b/fe/src/com/baidu/palo/analysis/ShowBrokerStmt.java index f5d10d6a62..0277d588d7 100644 --- a/fe/src/com/baidu/palo/analysis/ShowBrokerStmt.java +++ b/fe/src/com/baidu/palo/analysis/ShowBrokerStmt.java @@ -15,8 +15,15 @@ package com.baidu.palo.analysis; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.catalog.Column; import com.baidu.palo.catalog.ColumnType; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.ErrorCode; +import com.baidu.palo.common.ErrorReport; +import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.baidu.palo.qe.ShowResultSetMetaData; // Show @@ -31,7 +38,12 @@ public class ShowBrokerStmt extends ShowStmt { } @Override - public void analyze(Analyzer analyzer) { + public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.ADMIN) + && !Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), + PrivPredicate.OPERATOR)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "ADMIN/OPERATOR"); + } } @Override diff --git a/fe/src/com/baidu/palo/analysis/ShowClusterStmt.java b/fe/src/com/baidu/palo/analysis/ShowClusterStmt.java index 2924bf47b3..3b0a9bec22 100644 --- a/fe/src/com/baidu/palo/analysis/ShowClusterStmt.java +++ b/fe/src/com/baidu/palo/analysis/ShowClusterStmt.java @@ -15,13 +15,20 @@ package com.baidu.palo.analysis; +import com.baidu.palo.analysis.CompoundPredicate.Operator; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.catalog.Column; import com.baidu.palo.catalog.ColumnType; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PaloPrivilege; +import com.baidu.palo.mysql.privilege.PrivBitSet; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.baidu.palo.qe.ShowResultSetMetaData; + import com.google.common.collect.ImmutableList; public class ShowClusterStmt extends ShowStmt { @@ -50,8 +57,11 @@ public class ShowClusterStmt extends ShowStmt { @Override public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { - if (!analyzer.getCatalog().getUserMgr().isAdmin(analyzer.getUser())) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_CLUSTER_NO_PERMISSIONS); + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.ADMIN_PRIV, + PaloPrivilege.NODE_PRIV), + Operator.OR))) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "ADMIN"); } } diff --git a/fe/src/com/baidu/palo/analysis/ShowCreateDbStmt.java b/fe/src/com/baidu/palo/analysis/ShowCreateDbStmt.java index c6ca987798..ba87d6280e 100644 --- a/fe/src/com/baidu/palo/analysis/ShowCreateDbStmt.java +++ b/fe/src/com/baidu/palo/analysis/ShowCreateDbStmt.java @@ -15,7 +15,8 @@ package com.baidu.palo.analysis; -import com.baidu.palo.catalog.AccessPrivilege; +import com.baidu.palo.analysis.CompoundPredicate.Operator; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.catalog.Column; import com.baidu.palo.catalog.ColumnType; import com.baidu.palo.cluster.ClusterNamespace; @@ -23,6 +24,10 @@ import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PaloPrivilege; +import com.baidu.palo.mysql.privilege.PrivBitSet; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.baidu.palo.qe.ShowResultSetMetaData; import com.google.common.base.Strings; @@ -54,9 +59,15 @@ public class ShowCreateDbStmt extends ShowStmt { ErrorReport.reportAnalysisException(ErrorCode.ERR_WRONG_DB_NAME, db); } db = ClusterNamespace.getFullName(getClusterName(), db); - if (!analyzer.getCatalog().getUserMgr() - .checkAccess(analyzer.getUser(), db, AccessPrivilege.READ_ONLY)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, analyzer.getUser(), db); + + if (!Catalog.getCurrentCatalog().getAuth().checkDbPriv(ConnectContext.get(), db, + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.ADMIN_PRIV, + PaloPrivilege.ALTER_PRIV, + PaloPrivilege.CREATE_PRIV, + PaloPrivilege.DROP_PRIV), + Operator.OR))) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, + ConnectContext.get().getQualifiedUser(), db); } } diff --git a/fe/src/com/baidu/palo/analysis/ShowCreateTableStmt.java b/fe/src/com/baidu/palo/analysis/ShowCreateTableStmt.java index 790214761e..7e3a41f72a 100644 --- a/fe/src/com/baidu/palo/analysis/ShowCreateTableStmt.java +++ b/fe/src/com/baidu/palo/analysis/ShowCreateTableStmt.java @@ -15,12 +15,14 @@ package com.baidu.palo.analysis; -import com.baidu.palo.catalog.AccessPrivilege; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.catalog.Column; import com.baidu.palo.catalog.ColumnType; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.baidu.palo.qe.ShowResultSetMetaData; // SHOW CREATE TABLE statement. @@ -73,9 +75,13 @@ public class ShowCreateTableStmt extends ShowStmt { ErrorReport.reportAnalysisException(ErrorCode.ERR_NO_TABLES_USED); } tbl.analyze(analyzer); - if (!analyzer.getCatalog().getUserMgr() - .checkAccess(analyzer.getUser(), tbl.getDb(), AccessPrivilege.READ_ONLY)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, analyzer.getUser(), tbl.getDb()); + + if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(ConnectContext.get(), tbl.getDb(), tbl.getTbl(), + PrivPredicate.SHOW)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_TABLEACCESS_DENIED_ERROR, "SHOW CREATE TABLE", + ConnectContext.get().getQualifiedUser(), + ConnectContext.get().getRemoteIP(), + tbl.getTbl()); } } diff --git a/fe/src/com/baidu/palo/analysis/ShowDataStmt.java b/fe/src/com/baidu/palo/analysis/ShowDataStmt.java index 46b95f221a..bccb892ffe 100644 --- a/fe/src/com/baidu/palo/analysis/ShowDataStmt.java +++ b/fe/src/com/baidu/palo/analysis/ShowDataStmt.java @@ -15,7 +15,6 @@ package com.baidu.palo.analysis; -import com.baidu.palo.catalog.AccessPrivilege; import com.baidu.palo.catalog.Catalog; import com.baidu.palo.catalog.Column; import com.baidu.palo.catalog.ColumnType; @@ -35,6 +34,8 @@ import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.InternalException; import com.baidu.palo.common.Pair; import com.baidu.palo.common.util.DebugUtil; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.baidu.palo.qe.ShowResultSetMetaData; import com.google.common.base.Strings; @@ -85,12 +86,6 @@ public class ShowDataStmt extends ShowStmt { } else { dbName = ClusterNamespace.getFullName(getClusterName(), dbName); } - - // check access - if (!analyzer.getCatalog().getUserMgr() - .checkAccess(analyzer.getUser(), dbName, AccessPrivilege.READ_ONLY)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, analyzer.getUser(), dbName); - } Database db = Catalog.getInstance().getDb(dbName); if (db == null) { @@ -111,6 +106,11 @@ public class ShowDataStmt extends ShowStmt { }); for (Table table : tables) { + if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(ConnectContext.get(), dbName, + table.getName(), + PrivPredicate.SHOW)) { + continue; + } sortedTables.add(table); } @@ -166,6 +166,15 @@ public class ShowDataStmt extends ShowStmt { List leftRow = Arrays.asList("Left", readableLeft); totalRows.add(leftRow); } else { + if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(ConnectContext.get(), dbName, + tableName, + PrivPredicate.SHOW)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_TABLEACCESS_DENIED_ERROR, "SHOW DATA", + ConnectContext.get().getQualifiedUser(), + ConnectContext.get().getRemoteIP(), + tableName); + } + Table table = db.getTable(tableName); if (table == null) { ErrorReport.reportAnalysisException(ErrorCode.ERR_BAD_TABLE_ERROR, tableName); diff --git a/fe/src/com/baidu/palo/analysis/ShowDeleteStmt.java b/fe/src/com/baidu/palo/analysis/ShowDeleteStmt.java index 4bfb00072c..1fb0af34ff 100644 --- a/fe/src/com/baidu/palo/analysis/ShowDeleteStmt.java +++ b/fe/src/com/baidu/palo/analysis/ShowDeleteStmt.java @@ -15,7 +15,6 @@ package com.baidu.palo.analysis; -import com.baidu.palo.catalog.AccessPrivilege; import com.baidu.palo.catalog.Column; import com.baidu.palo.catalog.ColumnType; import com.baidu.palo.cluster.ClusterNamespace; @@ -43,8 +42,7 @@ public class ShowDeleteStmt extends ShowStmt { @Override public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { super.analyze(analyzer); - final String dbNameWithoutPrefix = dbName; - final String userNameWithoutPrefix = ClusterNamespace.getNameFromFullName(analyzer.getUser()); + if (Strings.isNullOrEmpty(dbName)) { dbName = analyzer.getDefaultDb(); if (Strings.isNullOrEmpty(dbName)) { @@ -53,12 +51,6 @@ public class ShowDeleteStmt extends ShowStmt { } else { dbName = ClusterNamespace.getFullName(getClusterName(), dbName); } - - // check access - if (!analyzer.getCatalog().getUserMgr().checkAccess(analyzer.getUser(), dbName, AccessPrivilege.READ_ONLY)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, userNameWithoutPrefix, - dbNameWithoutPrefix); - } } @Override diff --git a/fe/src/com/baidu/palo/analysis/ShowExportStmt.java b/fe/src/com/baidu/palo/analysis/ShowExportStmt.java index bafc84b8c1..758683ffec 100644 --- a/fe/src/com/baidu/palo/analysis/ShowExportStmt.java +++ b/fe/src/com/baidu/palo/analysis/ShowExportStmt.java @@ -16,7 +16,6 @@ package com.baidu.palo.analysis; import com.baidu.palo.analysis.BinaryPredicate.Operator; -import com.baidu.palo.catalog.AccessPrivilege; import com.baidu.palo.catalog.Column; import com.baidu.palo.catalog.ColumnType; import com.baidu.palo.cluster.ClusterNamespace; @@ -101,13 +100,6 @@ public class ShowExportStmt extends ShowStmt { } else { dbName = ClusterNamespace.getFullName(getClusterName(), dbName); } - final String userNameWithoutPrefix = ClusterNamespace.getNameFromFullName(dbName); - final String dbNameWithoutPrefix = ClusterNamespace.getNameFromFullName(dbName); - // check access - if (!analyzer.getCatalog().getUserMgr().checkAccess(analyzer.getUser(), dbName, AccessPrivilege.READ_ONLY)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, userNameWithoutPrefix, - dbNameWithoutPrefix); - } // analyze where clause if not null if (whereClause != null) { diff --git a/fe/src/com/baidu/palo/analysis/ShowFrontendsStmt.java b/fe/src/com/baidu/palo/analysis/ShowFrontendsStmt.java new file mode 100644 index 0000000000..2c99f0ca98 --- /dev/null +++ b/fe/src/com/baidu/palo/analysis/ShowFrontendsStmt.java @@ -0,0 +1,53 @@ +// Copyright (c) 2017, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +package com.baidu.palo.analysis; + +import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.catalog.Column; +import com.baidu.palo.catalog.ColumnType; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.ErrorCode; +import com.baidu.palo.common.ErrorReport; +import com.baidu.palo.common.InternalException; +import com.baidu.palo.common.proc.FrontendsProcNode; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; +import com.baidu.palo.qe.ShowResultSetMetaData; + +public class ShowFrontendsStmt extends ShowStmt { + + public ShowFrontendsStmt() { + } + + @Override + public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.ADMIN) + && !Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), + PrivPredicate.OPERATOR)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "ADMIN/OPERATOR"); + } + } + + @Override + public ShowResultSetMetaData getMetaData() { + ShowResultSetMetaData.Builder builder = ShowResultSetMetaData.builder(); + for (String title : FrontendsProcNode.TITLE_NAMES) { + builder.addColumn(new Column(title, ColumnType.createVarchar(30))); + } + return builder.build(); + } +} + diff --git a/fe/src/com/baidu/palo/analysis/ShowGrantsStmt.java b/fe/src/com/baidu/palo/analysis/ShowGrantsStmt.java new file mode 100644 index 0000000000..331328e3cc --- /dev/null +++ b/fe/src/com/baidu/palo/analysis/ShowGrantsStmt.java @@ -0,0 +1,101 @@ +// Copyright (c) 2018, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +package com.baidu.palo.analysis; + +import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.catalog.Column; +import com.baidu.palo.catalog.ColumnType; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.ErrorCode; +import com.baidu.palo.common.ErrorReport; +import com.baidu.palo.common.InternalException; +import com.baidu.palo.common.proc.AuthProcDir; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; +import com.baidu.palo.qe.ShowResultSetMetaData; + +/* + * SHOW ALL GRANTS; + * show all grants. + * + * SHOW GRANTS: + * show grants of current user + * + * SHOW GRANTS FOR user@'xxx'; + * show grants for specified user identity + */ +// +// SHOW GRANTS; +// SHOW GRANTS FOR user@'xxx' +public class ShowGrantsStmt extends ShowStmt { + + private static final ShowResultSetMetaData META_DATA; + static { + ShowResultSetMetaData.Builder builder = ShowResultSetMetaData.builder(); + for (String col : AuthProcDir.TITLE_NAMES) { + builder.addColumn(new Column(col, ColumnType.createVarchar(100))); + } + META_DATA = builder.build(); + } + + private boolean isAll; + private UserIdentity userIdent; + + public ShowGrantsStmt(UserIdentity userIdent, boolean isAll) { + this.userIdent = userIdent; + this.isAll = isAll; + } + + public UserIdentity getUserIdent() { + return userIdent; + } + + public boolean isAll() { + return isAll; + } + + @Override + public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { + if (userIdent != null) { + if (isAll) { + throw new AnalysisException("Can not specified keyword ALL when specified user"); + } + userIdent.analyze(analyzer.getClusterName()); + } else { + if (!isAll) { + // self + userIdent = new UserIdentity(ConnectContext.get().getQualifiedUser(), + ConnectContext.get().getRemoteIP()); + userIdent.setIsAnalyzed(); + } + } + + UserIdentity self = new UserIdentity(ConnectContext.get().getQualifiedUser(), + ConnectContext.get().getRemoteIP()); + + if (isAll || !self.equals(userIdent)) { + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT"); + } + } + } + + @Override + public ShowResultSetMetaData getMetaData() { + return META_DATA; + } + +} diff --git a/fe/src/com/baidu/palo/analysis/ShowLoadStmt.java b/fe/src/com/baidu/palo/analysis/ShowLoadStmt.java index 5a5373aa58..26012ed987 100644 --- a/fe/src/com/baidu/palo/analysis/ShowLoadStmt.java +++ b/fe/src/com/baidu/palo/analysis/ShowLoadStmt.java @@ -16,7 +16,6 @@ package com.baidu.palo.analysis; import com.baidu.palo.analysis.BinaryPredicate.Operator; -import com.baidu.palo.catalog.AccessPrivilege; import com.baidu.palo.catalog.Column; import com.baidu.palo.catalog.ColumnType; import com.baidu.palo.cluster.ClusterNamespace; @@ -117,13 +116,6 @@ public class ShowLoadStmt extends ShowStmt { } else { dbName = ClusterNamespace.getFullName(getClusterName(), dbName); } - final String userNameWithoutPrefix = ClusterNamespace.getNameFromFullName(dbName); - final String dbNameWithoutPrefix = ClusterNamespace.getNameFromFullName(dbName); - // check access - if (!analyzer.getCatalog().getUserMgr().checkAccess(analyzer.getUser(), dbName, AccessPrivilege.READ_ONLY)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, userNameWithoutPrefix, - dbNameWithoutPrefix); - } // analyze where clause if not null if (whereClause != null) { diff --git a/fe/src/com/baidu/palo/analysis/ShowLoadWarningsStmt.java b/fe/src/com/baidu/palo/analysis/ShowLoadWarningsStmt.java index b97978ad8f..8fee298109 100644 --- a/fe/src/com/baidu/palo/analysis/ShowLoadWarningsStmt.java +++ b/fe/src/com/baidu/palo/analysis/ShowLoadWarningsStmt.java @@ -15,7 +15,6 @@ package com.baidu.palo.analysis; -import com.baidu.palo.catalog.AccessPrivilege; import com.baidu.palo.catalog.Column; import com.baidu.palo.catalog.ColumnType; import com.baidu.palo.cluster.ClusterNamespace; @@ -26,6 +25,7 @@ import com.baidu.palo.common.InternalException; import com.baidu.palo.qe.ShowResultSetMetaData; import com.google.common.base.Strings; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -95,13 +95,6 @@ public class ShowLoadWarningsStmt extends ShowStmt { } else { dbName = ClusterNamespace.getFullName(getClusterName(), dbName); } - final String userNameWithoutPrefix = ClusterNamespace.getNameFromFullName(dbName); - final String dbNameWithoutPrefix = ClusterNamespace.getNameFromFullName(dbName); - // check access - if (!analyzer.getCatalog().getUserMgr().checkAccess(analyzer.getUser(), dbName, AccessPrivilege.READ_ONLY)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, userNameWithoutPrefix, - dbNameWithoutPrefix); - } // analyze where clause if not null if (whereClause == null) { diff --git a/fe/src/com/baidu/palo/analysis/ShowMigrationsStmt.java b/fe/src/com/baidu/palo/analysis/ShowMigrationsStmt.java index 38422d5b00..44537c8822 100644 --- a/fe/src/com/baidu/palo/analysis/ShowMigrationsStmt.java +++ b/fe/src/com/baidu/palo/analysis/ShowMigrationsStmt.java @@ -15,11 +15,17 @@ package com.baidu.palo.analysis; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.catalog.Column; import com.baidu.palo.catalog.ColumnType; import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.ErrorCode; +import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.baidu.palo.qe.ShowResultSetMetaData; + import com.google.common.collect.ImmutableList; public class ShowMigrationsStmt extends ShowStmt { @@ -51,8 +57,9 @@ public class ShowMigrationsStmt extends ShowStmt { @Override public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { - if (!analyzer.getCatalog().getUserMgr().isAdmin(analyzer.getUser())) { - throw new AnalysisException("No privilege to grant."); + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.ADMIN)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, + "ADMIN"); } } diff --git a/fe/src/com/baidu/palo/analysis/ShowPartitionsStmt.java b/fe/src/com/baidu/palo/analysis/ShowPartitionsStmt.java index 59329ab5ad..5ee6c7e864 100644 --- a/fe/src/com/baidu/palo/analysis/ShowPartitionsStmt.java +++ b/fe/src/com/baidu/palo/analysis/ShowPartitionsStmt.java @@ -15,24 +15,27 @@ package com.baidu.palo.analysis; -import com.baidu.palo.catalog.AccessPrivilege; -import com.baidu.palo.catalog.Catalog; -import com.baidu.palo.catalog.Column; -import com.baidu.palo.catalog.ColumnType; -import com.baidu.palo.catalog.Database; -import com.baidu.palo.catalog.OlapTable; -import com.baidu.palo.catalog.Table; -import com.baidu.palo.cluster.ClusterNamespace; -import com.baidu.palo.common.AnalysisException; -import com.baidu.palo.common.InternalException; -import com.baidu.palo.common.proc.ProcNodeInterface; -import com.baidu.palo.common.proc.ProcResult; -import com.baidu.palo.common.proc.ProcService; -import com.baidu.palo.qe.ShowResultSetMetaData; - -import com.google.common.base.Strings; - -import org.apache.logging.log4j.LogManager; +import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.catalog.Column; +import com.baidu.palo.catalog.ColumnType; +import com.baidu.palo.catalog.Database; +import com.baidu.palo.catalog.OlapTable; +import com.baidu.palo.catalog.Table; +import com.baidu.palo.cluster.ClusterNamespace; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.ErrorCode; +import com.baidu.palo.common.ErrorReport; +import com.baidu.palo.common.InternalException; +import com.baidu.palo.common.proc.ProcNodeInterface; +import com.baidu.palo.common.proc.ProcResult; +import com.baidu.palo.common.proc.ProcService; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; +import com.baidu.palo.qe.ShowResultSetMetaData; + +import com.google.common.base.Strings; + +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class ShowPartitionsStmt extends ShowStmt { @@ -80,9 +83,12 @@ public class ShowPartitionsStmt extends ShowStmt { } // check access - if (!analyzer.getCatalog().getUserMgr() - .checkAccess(analyzer.getUser(), dbName, AccessPrivilege.READ_ONLY)) { - throw new AnalysisException("No privilege of db(" + dbName + ")."); + if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(ConnectContext.get(), dbName, tableName, + PrivPredicate.SHOW)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_TABLEACCESS_DENIED_ERROR, "SHOW PARTITIONS", + ConnectContext.get().getQualifiedUser(), + ConnectContext.get().getRemoteIP(), + tableName); } Database db = Catalog.getInstance().getDb(dbName); diff --git a/fe/src/com/baidu/palo/analysis/ShowProcStmt.java b/fe/src/com/baidu/palo/analysis/ShowProcStmt.java index dbbce5465d..fc37c34726 100644 --- a/fe/src/com/baidu/palo/analysis/ShowProcStmt.java +++ b/fe/src/com/baidu/palo/analysis/ShowProcStmt.java @@ -15,6 +15,7 @@ package com.baidu.palo.analysis; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.catalog.Column; import com.baidu.palo.catalog.ColumnType; import com.baidu.palo.common.AnalysisException; @@ -24,6 +25,8 @@ import com.baidu.palo.common.InternalException; import com.baidu.palo.common.proc.ProcNodeInterface; import com.baidu.palo.common.proc.ProcResult; import com.baidu.palo.common.proc.ProcService; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.baidu.palo.qe.ShowResultSetMetaData; // SHOW PROC statement. Used to show proc information, only admin can use. @@ -41,9 +44,11 @@ public class ShowProcStmt extends ShowStmt { @Override public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { - if (!analyzer.getCatalog().getUserMgr().isAdmin(analyzer.getUser())) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "SHOW PROC"); + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.ADMIN)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, + "ADMIN"); } + node = ProcService.getInstance().open(path); if (node == null) { ErrorReport.reportAnalysisException(ErrorCode.ERR_WRONG_PROC_PATH, path); diff --git a/fe/src/com/baidu/palo/analysis/ShowRepositoriesStmt.java b/fe/src/com/baidu/palo/analysis/ShowRepositoriesStmt.java new file mode 100644 index 0000000000..ba5b4a2407 --- /dev/null +++ b/fe/src/com/baidu/palo/analysis/ShowRepositoriesStmt.java @@ -0,0 +1,48 @@ +// Modifications copyright (C) 2018, Baidu.com, Inc. +// Copyright 2018 The Apache Software Foundation + +// 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. + +package com.baidu.palo.analysis; + +import com.baidu.palo.catalog.Column; +import com.baidu.palo.catalog.ColumnType; +import com.baidu.palo.qe.ShowResultSetMetaData; + +import com.google.common.collect.ImmutableList; + +public class ShowRepositoriesStmt extends ShowStmt { + public static final ImmutableList TITLE_NAMES = new ImmutableList.Builder() + .add("RepoId").add("RepoName").add("CreateTime").add("IsReadOnly").add("Location") + .add("Broker").add("ErrMsg") + .build(); + + public ShowRepositoriesStmt() { + + } + + @Override + public ShowResultSetMetaData getMetaData() { + ShowResultSetMetaData.Builder builder = ShowResultSetMetaData.builder(); + for (String title : TITLE_NAMES) { + builder.addColumn(new Column(title, ColumnType.createVarchar(30))); + } + return builder.build(); + } + +} diff --git a/fe/src/com/baidu/palo/analysis/ShowRestoreStmt.java b/fe/src/com/baidu/palo/analysis/ShowRestoreStmt.java index c807376508..d8bb7f1fe8 100644 --- a/fe/src/com/baidu/palo/analysis/ShowRestoreStmt.java +++ b/fe/src/com/baidu/palo/analysis/ShowRestoreStmt.java @@ -15,29 +15,29 @@ package com.baidu.palo.analysis; -import com.baidu.palo.analysis.BinaryPredicate.Operator; -import com.baidu.palo.backup.BackupHandler; -import com.baidu.palo.backup.RestoreJob; -import com.baidu.palo.catalog.AccessPrivilege; import com.baidu.palo.catalog.Catalog; import com.baidu.palo.catalog.Column; import com.baidu.palo.catalog.ColumnType; -import com.baidu.palo.catalog.Database; +import com.baidu.palo.cluster.ClusterNamespace; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.InternalException; -import com.baidu.palo.common.PatternMatcher; -import com.baidu.palo.common.proc.RestoreProcNode; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.baidu.palo.qe.ShowResultSetMetaData; import com.google.common.base.Strings; - -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; +import com.google.common.collect.ImmutableList; public class ShowRestoreStmt extends ShowStmt { + public static final ImmutableList TITLE_NAMES = new ImmutableList.Builder() + .add("JobId").add("Label").add("Timestamp").add("DbName").add("State") + .add("AllowLoad").add("ReplicationNum") + .add("RestoreObjs").add("CreateTime").add("MetaPreparedTime").add("SnapshotFinishedTime") + .add("DownloadFinishedTime").add("FinishedTime").add("UnfinishedTasks").add("TaskErrMsg") + .add("Status").add("Timeout") + .build(); private String dbName; private Expr where; @@ -58,103 +58,26 @@ public class ShowRestoreStmt extends ShowStmt { @Override public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { + super.analyze(analyzer); if (Strings.isNullOrEmpty(dbName)) { dbName = analyzer.getDefaultDb(); if (Strings.isNullOrEmpty(dbName)) { ErrorReport.reportAnalysisException(ErrorCode.ERR_NO_DB_ERROR); } + } else { + dbName = ClusterNamespace.getFullName(getClusterName(), dbName); } - // check access - if (!analyzer.getCatalog().getUserMgr().checkAccess(analyzer.getUser(), dbName, AccessPrivilege.READ_ONLY)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, analyzer.getUser(), dbName); - } - - analyzeWhere(); - } - - private void analyzeWhere() throws AnalysisException { - boolean valid = true; - if (where == null) { - return; - } - - CHECK: { - if (where instanceof BinaryPredicate) { - BinaryPredicate binaryPredicate = (BinaryPredicate) where; - if (binaryPredicate.getOp() != Operator.EQ) { - valid = false; - break CHECK; - } - } else if (where instanceof LikePredicate) { - LikePredicate likePredicate = (LikePredicate) where; - if (likePredicate.getOp() != LikePredicate.Operator.LIKE) { - valid = false; - break CHECK; - } - } else { - valid = false; - break CHECK; - } - - // left child - if (!(where.getChild(0) instanceof SlotRef)) { - valid = false; - break CHECK; - } - String leftKey = ((SlotRef) where.getChild(0)).getColumnName(); - if (!leftKey.equalsIgnoreCase("label")) { - valid = false; - break CHECK; - } - - // right child - if (!(where.getChild(1) instanceof StringLiteral)) { - valid = false; - break CHECK; - } - - label = ((StringLiteral) where.getChild(1)).getStringValue(); - if (Strings.isNullOrEmpty(label)) { - valid = false; - break CHECK; - } - } - - if (!valid) { - throw new AnalysisException("Where clause should looks like: LABEL = \"your_restore_label\"," - + " or LABEL LIKE \"matcher\""); + // check auth + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.ADMIN)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "ADMIN"); } } - public List> getResultRows() throws AnalysisException { - List> result = new LinkedList>(); - Database db = Catalog.getInstance().getDb(dbName); - if (db == null) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_BAD_DB_ERROR, dbName); - } - - BackupHandler backupHandler = Catalog.getInstance().getBackupHandler(); - PatternMatcher matcher = null; - if (!Strings.isNullOrEmpty(label)) { - matcher = PatternMatcher.createMysqlPattern(label); - } - List> backupJobInfos = backupHandler.getJobInfosByDb(db.getId(), RestoreJob.class, matcher); - for (List infoStr : backupJobInfos) { - List oneInfo = new ArrayList(RestoreProcNode.TITLE_NAMES.size()); - for (Comparable element : infoStr) { - oneInfo.add(element.toString()); - } - result.add(oneInfo); - } - return result; - } - - @Override public ShowResultSetMetaData getMetaData() { ShowResultSetMetaData.Builder builder = ShowResultSetMetaData.builder(); - for (String title : RestoreProcNode.TITLE_NAMES) { + for (String title : TITLE_NAMES) { builder.addColumn(new Column(title, ColumnType.createVarchar(30))); } return builder.build(); @@ -177,3 +100,4 @@ public class ShowRestoreStmt extends ShowStmt { return toSql(); } } + diff --git a/fe/src/com/baidu/palo/analysis/ShowRolesStmt.java b/fe/src/com/baidu/palo/analysis/ShowRolesStmt.java new file mode 100644 index 0000000000..1e86f04aa6 --- /dev/null +++ b/fe/src/com/baidu/palo/analysis/ShowRolesStmt.java @@ -0,0 +1,59 @@ +// Copyright (c) 2018, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +package com.baidu.palo.analysis; + +import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.catalog.Column; +import com.baidu.palo.catalog.ColumnType; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.ErrorCode; +import com.baidu.palo.common.ErrorReport; +import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; +import com.baidu.palo.qe.ShowResultSetMetaData; + +public class ShowRolesStmt extends ShowStmt { + private static final ShowResultSetMetaData META_DATA; + static { + ShowResultSetMetaData.Builder builder = ShowResultSetMetaData.builder(); + + builder.addColumn(new Column("Name", ColumnType.createVarchar(100))); + builder.addColumn(new Column("Users", ColumnType.createVarchar(100))); + builder.addColumn(new Column("GlobalPrivs", ColumnType.createVarchar(300))); + builder.addColumn(new Column("DatabasePrivs", ColumnType.createVarchar(300))); + builder.addColumn(new Column("TablePrivs", ColumnType.createVarchar(300))); + + META_DATA = builder.build(); + } + + public ShowRolesStmt() { + + } + + @Override + public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT"); + } + } + + @Override + public ShowResultSetMetaData getMetaData() { + return META_DATA; + } + +} diff --git a/fe/src/com/baidu/palo/analysis/ShowSnapshotStmt.java b/fe/src/com/baidu/palo/analysis/ShowSnapshotStmt.java new file mode 100644 index 0000000000..531a628164 --- /dev/null +++ b/fe/src/com/baidu/palo/analysis/ShowSnapshotStmt.java @@ -0,0 +1,156 @@ +// Modifications copyright (C) 2018, Baidu.com, Inc. +// Copyright 2018 The Apache Software Foundation + +// 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. + +package com.baidu.palo.analysis; + +import com.baidu.palo.analysis.CompoundPredicate.Operator; +import com.baidu.palo.catalog.Column; +import com.baidu.palo.catalog.ColumnType; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.InternalException; +import com.baidu.palo.qe.ShowResultSetMetaData; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; + +public class ShowSnapshotStmt extends ShowStmt { + public static final ImmutableList SNAPSHOT_ALL = new ImmutableList.Builder() + .add("Snapshot").add("Timestamp").add("Status") + .build(); + public static final ImmutableList SNAPSHOT_DETAIL = new ImmutableList.Builder() + .add("Snapshot").add("Timestamp").add("Database").add("Details").add("Status") + .build(); + + private String repoName; + private Expr where; + private String snapshotName; + private String timestamp; + + public ShowSnapshotStmt(String repoName, Expr where) { + this.repoName = repoName; + this.where = where; + } + + @Override + public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { + super.analyze(analyzer); + + // analyze where clause if not null + if (where != null) { + // eg: WHERE snapshot="snapshot_label" [and timestamp="2018-04-19-11-11:11"]; + boolean ok = true; + CHECK: { + if (where instanceof BinaryPredicate) { + if (!analyzeSubExpr((BinaryPredicate) where)) { + ok = false; + break CHECK; + } + } else if (where instanceof CompoundPredicate) { + CompoundPredicate cp = (CompoundPredicate) where; + if (cp.getOp() != Operator.AND) { + ok = false; + break CHECK; + } + + if (!(cp.getChild(0) instanceof BinaryPredicate) + || !(cp.getChild(1) instanceof BinaryPredicate)) { + ok = false; + break CHECK; + } + + if (!analyzeSubExpr((BinaryPredicate) cp.getChild(0)) + || !analyzeSubExpr((BinaryPredicate) cp.getChild(1))) { + ok = false; + break CHECK; + } + } + } + + if (ok && (Strings.isNullOrEmpty(snapshotName) && !Strings.isNullOrEmpty(timestamp))) { + // can not only set timestamp + ok = false; + } + + if (!ok) { + throw new AnalysisException("Where clause should looks like: SNAPSHOT = 'your_snapshot_name'" + + " [AND TIMESTAMP = '2018-04-18-19-19-10']"); + } + } + } + + private boolean analyzeSubExpr(BinaryPredicate expr) { + Expr key = expr.getChild(0); + Expr val = expr.getChild(1); + + if (!(key instanceof SlotRef)) { + return false; + } + if (!(val instanceof StringLiteral)) { + return false; + } + + String name = ((SlotRef) key).getColumnName(); + if (name.equalsIgnoreCase("snapshot")) { + snapshotName = ((StringLiteral) val).getStringValue(); + if (Strings.isNullOrEmpty(snapshotName)) { + return false; + } + return true; + } else if (name.equalsIgnoreCase("timestamp")) { + timestamp = ((StringLiteral) val).getStringValue(); + if (Strings.isNullOrEmpty(timestamp)) { + return false; + } + return true; + } + + return false; + + } + + public String getRepoName() { + return repoName; + } + + public String getSnapshotName() { + return snapshotName; + } + + public String getTimestamp() { + return timestamp; + } + + @Override + public ShowResultSetMetaData getMetaData() { + ShowResultSetMetaData.Builder builder = ShowResultSetMetaData.builder(); + if (!Strings.isNullOrEmpty(snapshotName) && !Strings.isNullOrEmpty(timestamp)) { + for (String title : SNAPSHOT_DETAIL) { + builder.addColumn(new Column(title, ColumnType.createVarchar(30))); + } + } else { + for (String title : SNAPSHOT_ALL) { + builder.addColumn(new Column(title, ColumnType.createVarchar(30))); + } + } + return builder.build(); + } + +} + diff --git a/fe/src/com/baidu/palo/analysis/ShowTableStatusStmt.java b/fe/src/com/baidu/palo/analysis/ShowTableStatusStmt.java index 6d3c5b7f88..ad12d0b1bf 100644 --- a/fe/src/com/baidu/palo/analysis/ShowTableStatusStmt.java +++ b/fe/src/com/baidu/palo/analysis/ShowTableStatusStmt.java @@ -15,7 +15,7 @@ package com.baidu.palo.analysis; -import com.baidu.palo.catalog.AccessPrivilege; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.catalog.Column; import com.baidu.palo.catalog.ColumnType; import com.baidu.palo.catalog.InfoSchemaDb; @@ -24,6 +24,8 @@ import com.baidu.palo.cluster.ClusterNamespace; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.baidu.palo.qe.ShowResultSetMetaData; import com.google.common.base.Strings; @@ -83,9 +85,8 @@ public class ShowTableStatusStmt extends ShowStmt { } else { db = ClusterNamespace.getFullName(analyzer.getClusterName(), db); } - if (!analyzer.getCatalog().getUserMgr() - .checkAccess(analyzer.getUser(), db, AccessPrivilege.READ_ONLY)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, analyzer.getUser(), db); + if (!Catalog.getCurrentCatalog().getAuth().checkDbPriv(ConnectContext.get(), db, PrivPredicate.SHOW)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, analyzer.getQualifiedUser(), db); } } diff --git a/fe/src/com/baidu/palo/analysis/ShowTableStmt.java b/fe/src/com/baidu/palo/analysis/ShowTableStmt.java index 47754c5e59..18d2fac4fc 100644 --- a/fe/src/com/baidu/palo/analysis/ShowTableStmt.java +++ b/fe/src/com/baidu/palo/analysis/ShowTableStmt.java @@ -15,7 +15,6 @@ package com.baidu.palo.analysis; -import com.baidu.palo.catalog.AccessPrivilege; import com.baidu.palo.catalog.Column; import com.baidu.palo.catalog.ColumnType; import com.baidu.palo.catalog.InfoSchemaDb; @@ -79,9 +78,9 @@ public class ShowTableStmt extends ShowStmt { } else { db = ClusterNamespace.getFullName(analyzer.getClusterName(), db); } - if (!analyzer.getCatalog().getUserMgr().checkAccess(analyzer.getUser(), db, AccessPrivilege.READ_ONLY)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, analyzer.getUser(), db); - } + + // we do not check db privs here. because user may not have any db privs, + // but if it has privs of tbls inside this db,it should be allowed to see this db. } @Override diff --git a/fe/src/com/baidu/palo/analysis/ShowTabletStmt.java b/fe/src/com/baidu/palo/analysis/ShowTabletStmt.java index e2c0693969..c646e14651 100644 --- a/fe/src/com/baidu/palo/analysis/ShowTabletStmt.java +++ b/fe/src/com/baidu/palo/analysis/ShowTabletStmt.java @@ -15,6 +15,7 @@ package com.baidu.palo.analysis; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.catalog.Column; import com.baidu.palo.catalog.ColumnType; import com.baidu.palo.cluster.ClusterNamespace; @@ -23,6 +24,8 @@ import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.InternalException; import com.baidu.palo.common.proc.TabletsProcDir; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.baidu.palo.qe.ShowResultSetMetaData; import com.google.common.base.Strings; @@ -77,7 +80,7 @@ public class ShowTabletStmt extends ShowStmt { } // check access - if (!analyzer.getCatalog().getUserMgr().isSuperuser(analyzer.getUser())) { + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.ADMIN)) { ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "SHOW TABLET"); } } diff --git a/fe/src/com/baidu/palo/analysis/ShowUserPropertyStmt.java b/fe/src/com/baidu/palo/analysis/ShowUserPropertyStmt.java index e8244feda5..ce588b78e7 100644 --- a/fe/src/com/baidu/palo/analysis/ShowUserPropertyStmt.java +++ b/fe/src/com/baidu/palo/analysis/ShowUserPropertyStmt.java @@ -15,17 +15,19 @@ package com.baidu.palo.analysis; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.catalog.Column; import com.baidu.palo.catalog.ColumnType; import com.baidu.palo.cluster.ClusterNamespace; import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.CaseSensibility; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.InternalException; import com.baidu.palo.common.PatternMatcher; -import com.baidu.palo.common.proc.ProcNodeInterface; -import com.baidu.palo.common.proc.ProcService; import com.baidu.palo.common.proc.UserPropertyProcNode; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.baidu.palo.qe.ShowResultSetMetaData; import com.google.common.base.Strings; @@ -45,8 +47,6 @@ public class ShowUserPropertyStmt extends ShowStmt { private String user; private String pattern; - private ProcNodeInterface node; - public ShowUserPropertyStmt(String user, String pattern) { this.user = user; this.pattern = pattern; @@ -56,39 +56,29 @@ public class ShowUserPropertyStmt extends ShowStmt { public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { super.analyze(analyzer); if (Strings.isNullOrEmpty(user)) { - user = analyzer.getUser(); + user = analyzer.getQualifiedUser(); + // user can see itself's property, no need to check privs } else { user = ClusterNamespace.getFullName(getClusterName(), user); - if (!analyzer.getCatalog().getUserMgr().checkUserAccess(analyzer.getUser(), user)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "SHOW PROPERTY"); + + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.GRANT)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "GRANT"); } } pattern = Strings.emptyToNull(pattern); } - public void handleShow() throws AnalysisException { - // build proc path - // /access_resource/user - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append("/access_resource/"); - stringBuilder.append(user); - LOG.debug("process SHOW PROC '{}';", stringBuilder.toString()); - - node = ProcService.getInstance().open(stringBuilder.toString()); - if (node == null) { - throw new AnalysisException("Failed to show user property"); - } - } - public List> getRows() throws AnalysisException { - List> rows = node.fetchResult().getRows(); + List> rows = Catalog.getCurrentCatalog().getAuth().getUserProperties(user); + if (pattern == null) { return rows; } List> result = Lists.newArrayList(); - PatternMatcher matcher = PatternMatcher.createMysqlPattern(pattern); + PatternMatcher matcher = PatternMatcher.createMysqlPattern(pattern, + CaseSensibility.USER.getCaseSensibility()); for (List row : rows) { String key = row.get(0).split("\\" + SetUserPropertyVar.DOT_SEPARATOR)[0]; if (matcher.match(key)) { diff --git a/fe/src/com/baidu/palo/analysis/ShowUserStmt.java b/fe/src/com/baidu/palo/analysis/ShowUserStmt.java index 8946fb6b2d..c855d0d5aa 100644 --- a/fe/src/com/baidu/palo/analysis/ShowUserStmt.java +++ b/fe/src/com/baidu/palo/analysis/ShowUserStmt.java @@ -4,7 +4,7 @@ import com.baidu.palo.catalog.Column; import com.baidu.palo.catalog.ColumnType; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.InternalException; -import com.baidu.palo.common.proc.AccessResourceProcDir; +import com.baidu.palo.common.proc.AuthProcDir; import com.baidu.palo.qe.ShowResultSetMetaData; public class ShowUserStmt extends ShowStmt { @@ -12,7 +12,7 @@ public class ShowUserStmt extends ShowStmt { static { ShowResultSetMetaData.Builder builder = ShowResultSetMetaData.builder(); - for (String title : AccessResourceProcDir.TITLE_NAMES) { + for (String title : AuthProcDir.TITLE_NAMES) { builder.addColumn(new Column(title, ColumnType.createVarchar(30))); } META_DATA = builder.build(); @@ -30,7 +30,7 @@ public class ShowUserStmt extends ShowStmt { @Override public void analyze(Analyzer analyzer) throws AnalysisException, InternalException { - user = analyzer.getUser(); + user = analyzer.getQualifiedUser(); } @Override diff --git a/fe/src/com/baidu/palo/analysis/TablePattern.java b/fe/src/com/baidu/palo/analysis/TablePattern.java new file mode 100644 index 0000000000..e5192f23cc --- /dev/null +++ b/fe/src/com/baidu/palo/analysis/TablePattern.java @@ -0,0 +1,141 @@ +// Copyright (c) 2018, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +package com.baidu.palo.analysis; + +import com.baidu.palo.cluster.ClusterNamespace; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.FeNameFormat; +import com.baidu.palo.common.io.Text; +import com.baidu.palo.common.io.Writable; +import com.baidu.palo.mysql.privilege.PaloAuth.PrivLevel; + +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +// only the following 3 formats are allowed +// db.tbl +// *.* +// db.* +public class TablePattern implements Writable { + private String db; + private String tbl; + boolean isAnalyzed = false; + + public static TablePattern ALL; + static { + ALL = new TablePattern("*", "*"); + try { + ALL.analyze(""); + } catch (AnalysisException e) { + // will not happen + } + } + + private TablePattern() { + } + + public TablePattern(String db, String tbl) { + this.db = Strings.isNullOrEmpty(db) ? "*" : db; + this.tbl = Strings.isNullOrEmpty(tbl) ? "*" : tbl; + } + + public String getQuolifiedDb() { + Preconditions.checkState(isAnalyzed); + return db; + } + + public String getTbl() { + return tbl; + } + + public PrivLevel getPrivLevel() { + Preconditions.checkState(isAnalyzed); + if (db.equals("*")) { + return PrivLevel.GLOBAL; + } else if (!tbl.equals("*")) { + return PrivLevel.TABLE; + } else { + return PrivLevel.DATABASE; + } + } + + public void analyze(String clusterName) throws AnalysisException { + if (isAnalyzed) { + return; + } + if (db.equals("*") && !tbl.equals("*")) { + throw new AnalysisException("Do not support format: " + toString()); + } + + if (!db.equals("*")) { + FeNameFormat.checkDbName(db); + db = ClusterNamespace.getFullName(clusterName, db); + } + + if (!tbl.equals("*")) { + FeNameFormat.checkTableName(tbl); + } + isAnalyzed = true; + } + + public static TablePattern read(DataInput in) throws IOException { + TablePattern tablePattern = new TablePattern(); + tablePattern.readFields(in); + return tablePattern; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof TablePattern)) { + return false; + } + TablePattern other = (TablePattern) obj; + return db.equals(other.getQuolifiedDb()) && tbl.equals(other.getTbl()); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + db.hashCode(); + result = 31 * result + tbl.hashCode(); + return result; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(db).append(".").append(tbl); + return sb.toString(); + } + + @Override + public void write(DataOutput out) throws IOException { + Preconditions.checkState(isAnalyzed); + Text.writeString(out, db); + Text.writeString(out, tbl); + } + + @Override + public void readFields(DataInput in) throws IOException { + db = Text.readString(in); + tbl = Text.readString(in); + isAnalyzed = true; + } +} diff --git a/fe/src/com/baidu/palo/analysis/TableRef.java b/fe/src/com/baidu/palo/analysis/TableRef.java index 95f0e8c50a..6fa466db9d 100644 --- a/fe/src/com/baidu/palo/analysis/TableRef.java +++ b/fe/src/com/baidu/palo/analysis/TableRef.java @@ -25,6 +25,8 @@ import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.InternalException; +import com.baidu.palo.common.io.Text; +import com.baidu.palo.common.io.Writable; import com.baidu.palo.rewrite.ExprRewriter; import com.google.common.base.Joiner; @@ -35,6 +37,9 @@ import com.google.common.collect.Sets; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -68,7 +73,7 @@ import java.util.Set; * TODO for 2.3: Rename this class to CollectionRef and re-consider the naming and * structure of all subclasses. */ -public class TableRef implements ParseNode { +public class TableRef implements ParseNode, Writable { private static final Logger LOG = LogManager.getLogger(TableRef.class); protected TableName name; private List partitions = null; @@ -126,6 +131,10 @@ public class TableRef implements ParseNode { // END: Members that need to be reset() // /////////////////////////////////////// + public TableRef() { + // for persist + } + public TableRef(TableName name, String alias) { this(name, alias, null); } @@ -641,4 +650,58 @@ public class TableRef implements ParseNode { public TableRef clone() { return new TableRef(this); } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(name); + if (partitions != null && !partitions.isEmpty()) { + sb.append(" PARTITIONS("); + sb.append(Joiner.on(", ").join(partitions)).append(")"); + } + if (aliases_ != null && aliases_.length > 0) { + sb.append(" AS ").append(aliases_[0]); + } + return sb.toString(); + } + + @Override + public void write(DataOutput out) throws IOException { + name.write(out); + if (partitions == null) { + out.writeBoolean(false); + } else { + out.writeBoolean(true); + out.writeInt(partitions.size()); + for (String partName : partitions) { + Text.writeString(out, partName); + } + } + + if (hasExplicitAlias()) { + out.writeBoolean(true); + Text.writeString(out, getExplicitAlias()); + } else { + out.writeBoolean(false); + } + } + + @Override + public void readFields(DataInput in) throws IOException { + name = new TableName(); + name.readFields(in); + if (in.readBoolean()) { + partitions = Lists.newArrayList(); + int size = in.readInt(); + for (int i = 0; i < size; i++) { + String partName = Text.readString(in); + partitions.add(partName); + } + } + + if (in.readBoolean()) { + String alias = Text.readString(in); + aliases_ = new String[] { alias }; + } + } } diff --git a/fe/src/com/baidu/palo/analysis/TupleIsNullPredicate.java b/fe/src/com/baidu/palo/analysis/TupleIsNullPredicate.java index 84fc5196b4..68221f973a 100644 --- a/fe/src/com/baidu/palo/analysis/TupleIsNullPredicate.java +++ b/fe/src/com/baidu/palo/analysis/TupleIsNullPredicate.java @@ -142,6 +142,9 @@ public class TupleIsNullPredicate extends Predicate { */ private static boolean requiresNullWrapping(Expr expr, Analyzer analyzer) throws InternalException { + if (expr.isConstant()) { + return false; + } return true; } diff --git a/fe/src/com/baidu/palo/analysis/UseStmt.java b/fe/src/com/baidu/palo/analysis/UseStmt.java index 11ebc32bd2..c4513ee38a 100644 --- a/fe/src/com/baidu/palo/analysis/UseStmt.java +++ b/fe/src/com/baidu/palo/analysis/UseStmt.java @@ -20,17 +20,20 @@ package com.baidu.palo.analysis; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import com.baidu.palo.catalog.AccessPrivilege; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.cluster.ClusterNamespace; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; + import com.google.common.base.Strings; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + /** * Representation of a USE db statement. */ @@ -63,8 +66,8 @@ public class UseStmt extends StatementBase { } database = ClusterNamespace.getFullName(getClusterName(), database); - if (!analyzer.getCatalog().getUserMgr().checkAccess(analyzer.getUser(), database, AccessPrivilege.READ_ONLY)) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, analyzer.getUser(), database); + if (!Catalog.getCurrentCatalog().getAuth().checkDbPriv(ConnectContext.get(), database, PrivPredicate.SHOW)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, analyzer.getQualifiedUser(), database); } } diff --git a/fe/src/com/baidu/palo/analysis/UserDesc.java b/fe/src/com/baidu/palo/analysis/UserDesc.java index 081e53b26c..ac57a47d73 100644 --- a/fe/src/com/baidu/palo/analysis/UserDesc.java +++ b/fe/src/com/baidu/palo/analysis/UserDesc.java @@ -22,26 +22,22 @@ package com.baidu.palo.analysis; // Description of user in SQL statement public class UserDesc { - private String user; + private UserIdentity userIdent; private String password; private boolean isPlain; - public UserDesc(String user) { - this(user, "", false); + public UserDesc(UserIdentity userIdent) { + this(userIdent, "", false); } - public UserDesc(String user, String password, boolean isPlain) { - this.user = user; + public UserDesc(UserIdentity userIdent, String password, boolean isPlain) { + this.userIdent = userIdent; this.password = password; this.isPlain = isPlain; } - public String getUser() { - return user; - } - - public void setUser(String user) { - this.user = user; + public UserIdentity getUserIdent() { + return userIdent; } public String getPassword() { @@ -51,8 +47,4 @@ public class UserDesc { public boolean isPlain() { return isPlain; } - - public void setPlain(boolean isPlain) { - this.isPlain = isPlain; - } } diff --git a/fe/src/com/baidu/palo/analysis/UserIdentity.java b/fe/src/com/baidu/palo/analysis/UserIdentity.java new file mode 100644 index 0000000000..257db4c84c --- /dev/null +++ b/fe/src/com/baidu/palo/analysis/UserIdentity.java @@ -0,0 +1,205 @@ +// Copyright (c) 2018, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +package com.baidu.palo.analysis; + +import com.baidu.palo.cluster.ClusterNamespace; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.CaseSensibility; +import com.baidu.palo.common.FeNameFormat; +import com.baidu.palo.common.PatternMatcher; +import com.baidu.palo.common.io.Text; +import com.baidu.palo.common.io.Writable; +import com.baidu.palo.mysql.privilege.PaloAuth; + +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +// https://dev.mysql.com/doc/refman/8.0/en/account-names.html +// user name must be literally matched. +// host name can take many forms, and wildcards are permitted. +// cmy@% +// cmy@192.168.% +// cmy@[domain.name] +public class UserIdentity implements Writable { + private String user; + private String host; + private boolean isDomain; + private boolean isAnalyzed = false; + + private UserIdentity() { + } + + public UserIdentity(String user, String host) { + this.user = Strings.emptyToNull(user); + this.host = Strings.emptyToNull(host); + this.isDomain = false; + } + + public UserIdentity(String user, String host, boolean isDomain) { + this.user = Strings.emptyToNull(user); + this.host = Strings.emptyToNull(host); + this.isDomain = isDomain; + } + + public String getQualifiedUser() { + Preconditions.checkState(isAnalyzed); + return user; + } + + public String getHost() { + return host; + } + + public boolean isDomain() { + return isDomain; + } + + public void setIsAnalyzed() { + this.isAnalyzed = true; + } + + public void analyze(String clusterName) throws AnalysisException { + if (isAnalyzed) { + return; + } + if (Strings.isNullOrEmpty(user)) { + throw new AnalysisException("Does not support anonymous user"); + } + + FeNameFormat.checkUserName(user); + if (!user.equals(PaloAuth.ROOT_USER) && !user.equals(PaloAuth.ADMIN_USER)) { + user = ClusterNamespace.getFullName(clusterName, user); + } + + // reuse createMysqlPattern to validate host pattern + PatternMatcher.createMysqlPattern(host, CaseSensibility.HOST.getCaseSensibility()); + isAnalyzed = true; + } + + public boolean include(UserIdentity self) { + Preconditions.checkState(isAnalyzed && self.isAnalyzed); + if (!user.equals(self.user)) { + return false; + } + + if (host.equals(self.host)) { + return true; + } + + // same user with different host + try { + PatternMatcher patternMatcher = PatternMatcher.createMysqlPattern(host, + CaseSensibility.HOST.getCaseSensibility()); + + return patternMatcher.match(self.host); + } catch (AnalysisException e) { + Preconditions.checkNotNull(null, e.getMessage()); + } + return false; + } + + public static UserIdentity fromString(String userIdentStr) { + if (Strings.isNullOrEmpty(userIdentStr)) { + return null; + } + + String[] parts = userIdentStr.split("@"); + if (parts.length != 2) { + return null; + } + + String user = parts[0]; + if (!user.startsWith("'") || !user.endsWith("'")) { + return null; + } + + String host = parts[1]; + if (host.startsWith("['") && host.endsWith("']")) { + UserIdentity userIdent = new UserIdentity(user.substring(1, user.length() - 1), + host.substring(2, host.length() - 2), true); + userIdent.setIsAnalyzed(); + return userIdent; + } else if (host.startsWith("'") && host.endsWith("'")) { + UserIdentity userIdent = new UserIdentity(user.substring(1, user.length() - 1), + host.substring(1, host.length() - 1)); + userIdent.setIsAnalyzed(); + return userIdent; + } + + return null; + } + + public static UserIdentity read(DataInput in) throws IOException { + UserIdentity userIdentity = new UserIdentity(); + userIdentity.readFields(in); + return userIdentity; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof UserIdentity)) { + return false; + } + UserIdentity other = (UserIdentity) obj; + return user.equals(other.getQualifiedUser()) && host.equals(other.getHost()); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + user.hashCode(); + result = 31 * result + host.hashCode(); + return result; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("'"); + if (!Strings.isNullOrEmpty(user)) { + sb.append(user); + } + sb.append("'@"); + if (!Strings.isNullOrEmpty(host)) { + if (isDomain) { + sb.append("['").append(host).append("']"); + } else { + sb.append("'").append(host).append("'"); + } + } + return sb.toString(); + } + + @Override + public void write(DataOutput out) throws IOException { + Preconditions.checkState(isAnalyzed); + Text.writeString(out, user); + Text.writeString(out, host); + out.writeBoolean(isDomain); + } + + @Override + public void readFields(DataInput in) throws IOException { + user = Text.readString(in); + host = Text.readString(in); + isDomain = in.readBoolean(); + isAnalyzed = true; + } +} diff --git a/fe/src/com/baidu/palo/backup/AbstractBackupJob.java b/fe/src/com/baidu/palo/backup/AbstractBackupJob_D.java similarity index 96% rename from fe/src/com/baidu/palo/backup/AbstractBackupJob.java rename to fe/src/com/baidu/palo/backup/AbstractBackupJob_D.java index 8e756a956b..8199307d95 100644 --- a/fe/src/com/baidu/palo/backup/AbstractBackupJob.java +++ b/fe/src/com/baidu/palo/backup/AbstractBackupJob_D.java @@ -39,8 +39,9 @@ import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; -public class AbstractBackupJob implements Writable { - private static final Logger LOG = LogManager.getLogger(AbstractBackupJob.class); +@Deprecated +public class AbstractBackupJob_D implements Writable { + private static final Logger LOG = LogManager.getLogger(AbstractBackupJob_D.class); protected long jobId; protected long dbId; @@ -62,11 +63,11 @@ public class AbstractBackupJob implements Writable { protected Future future; - public AbstractBackupJob() { + public AbstractBackupJob_D() { unfinishedTabletIds = HashMultimap.create(); } - public AbstractBackupJob(long jobId, long dbId, LabelName labelName, String remotePath, + public AbstractBackupJob_D(long jobId, long dbId, LabelName labelName, String remotePath, Map remoteProperties) { this.jobId = jobId; this.dbId = dbId; diff --git a/fe/src/com/baidu/palo/backup/AbstractJob.java b/fe/src/com/baidu/palo/backup/AbstractJob.java new file mode 100644 index 0000000000..e033b9af8f --- /dev/null +++ b/fe/src/com/baidu/palo/backup/AbstractJob.java @@ -0,0 +1,244 @@ +// Modifications copyright (C) 2018, Baidu.com, Inc. +// Copyright 2018 The Apache Software Foundation + +// 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. + +package com.baidu.palo.backup; + +import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.common.io.Text; +import com.baidu.palo.common.io.Writable; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.Map; + +/* + * The design of JobI is as follows + * 1. Here are only two methods: run() and cancel() that can modify the internal state of a Job. + * And each method is implemented as synchronized to avoid handling concurrent modify things. + * + * 2. isDone() method is used to check whether we can submit the next job. + */ +public abstract class AbstractJob implements Writable { + + public enum JobType { + BACKUP, RESTORE + } + + protected JobType type; + + // must be set right before job's running + protected Catalog catalog; + // repo will be set at first run() + protected Repository repo; + protected long repoId; + + /* + * In BackupJob, jobId will be generated every time before we call prepareAndSendSnapshotTask(); + * Because prepareAndSendSnapshotTask() may be called several times due to FE restart. + * And each time this method is called, the snapshot tasks will be sent with (maybe) different + * version and version hash. So we have to use different job id to identify the tasks in different batches. + */ + protected long jobId = -1; + + protected String label; + protected long dbId; + protected String dbName; + + protected Status status = Status.OK; + + protected long createTime = -1; + protected long finishedTime = -1; + protected long timeoutMs; + + protected boolean isTypeRead = false; + + // save err msg of tasks + protected Map taskErrMsg = Maps.newHashMap(); + + protected AbstractJob(JobType type) { + this.type = type; + } + + protected AbstractJob(JobType type, String label, long dbId, String dbName, + long timeoutMs, Catalog catalog, long repoId) { + this.type = type; + this.label = label; + this.dbId = dbId; + this.dbName = dbName; + this.createTime = System.currentTimeMillis(); + this.timeoutMs = timeoutMs; + this.catalog = catalog; + this.repoId = repoId; + } + + public JobType getType() { + return type; + } + + public long getJobId() { + return jobId; + } + + public String getLabel() { + return label; + } + + public long getDbId() { + return dbId; + } + + public String getDbName() { + return dbName; + } + + public Status getStatus() { + return status; + } + + public long getCreateTime() { + return createTime; + } + + public long getFinishedTime() { + return finishedTime; + } + + public long getTimeoutMs() { + return timeoutMs; + } + + public void setCatalog(Catalog catalog) { + this.catalog = catalog; + } + + public long getRepoId() { + return repoId; + } + + public void setTypeRead(boolean isTypeRead) { + this.isTypeRead = isTypeRead; + } + + public abstract void run(); + + public abstract Status cancel(); + + public abstract void replayRun(); + + public abstract void replayCancel(); + + public abstract boolean isDone(); + + public abstract boolean isPending(); + + public abstract boolean isCancelled(); + + public static AbstractJob read(DataInput in) throws IOException { + AbstractJob job = null; + JobType type = JobType.valueOf(Text.readString(in)); + if (type == JobType.BACKUP) { + job = new BackupJob(); + } else if (type == JobType.RESTORE) { + job = new RestoreJob(); + } else { + throw new IOException("Unknown job type: " + type.name()); + } + + job.setTypeRead(true); + job.readFields(in); + return job; + } + + @Override + public void write(DataOutput out) throws IOException { + // ATTN: must write type first + Text.writeString(out, type.name()); + + out.writeLong(repoId); + Text.writeString(out, label); + out.writeLong(jobId); + out.writeLong(dbId); + Text.writeString(out, dbName); + + out.writeLong(createTime); + out.writeLong(finishedTime); + out.writeLong(timeoutMs); + + if (!taskErrMsg.isEmpty()) { + out.writeBoolean(true); + // we only save at most 3 err msgs + int savedNum = Math.min(3, taskErrMsg.size()); + out.writeInt(savedNum); + for (Map.Entry entry : taskErrMsg.entrySet()) { + if (savedNum == 0) { + break; + } + out.writeLong(entry.getKey()); + Text.writeString(out, entry.getValue()); + savedNum--; + } + Preconditions.checkState(savedNum == 0, savedNum); + } else { + out.writeBoolean(false); + } + } + + @Override + public void readFields(DataInput in) throws IOException { + if (!isTypeRead) { + type = JobType.valueOf(Text.readString(in)); + isTypeRead = true; + } + + repoId = in.readLong(); + label = Text.readString(in); + jobId = in.readLong(); + dbId = in.readLong(); + dbName = Text.readString(in); + + createTime = in.readLong(); + finishedTime = in.readLong(); + timeoutMs = in.readLong(); + + if (in.readBoolean()) { + int size = in.readInt(); + for (int i = 0; i < size; i++) { + long taskId = in.readLong(); + String msg = Text.readString(in); + taskErrMsg.put(taskId, msg); + } + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(type.name()); + sb.append(" repo id: ").append(repoId).append(", label: ").append(label); + sb.append(", job id: ").append(jobId).append(", db id: ").append(dbId).append(", db name: ").append(dbName); + sb.append(", status: ").append(status); + sb.append(", timeout: ").append(timeoutMs); + return sb.toString(); + } +} + diff --git a/fe/src/com/baidu/palo/backup/BackupHandler.java b/fe/src/com/baidu/palo/backup/BackupHandler.java index b6d4aa9361..7f16ee5b51 100644 --- a/fe/src/com/baidu/palo/backup/BackupHandler.java +++ b/fe/src/com/baidu/palo/backup/BackupHandler.java @@ -17,767 +17,538 @@ package com.baidu.palo.backup; import com.baidu.palo.analysis.AbstractBackupStmt; import com.baidu.palo.analysis.BackupStmt; +import com.baidu.palo.analysis.BackupStmt.BackupType; import com.baidu.palo.analysis.CancelBackupStmt; -import com.baidu.palo.analysis.LabelName; -import com.baidu.palo.analysis.PartitionName; +import com.baidu.palo.analysis.CreateRepositoryStmt; +import com.baidu.palo.analysis.DropRepositoryStmt; import com.baidu.palo.analysis.RestoreStmt; +import com.baidu.palo.analysis.TableRef; import com.baidu.palo.backup.BackupJob.BackupJobState; -import com.baidu.palo.backup.RestoreJob.RestoreJobState; +import com.baidu.palo.backup.BackupJobInfo.BackupTableInfo; import com.baidu.palo.catalog.Catalog; import com.baidu.palo.catalog.Database; import com.baidu.palo.catalog.OlapTable; -import com.baidu.palo.catalog.OlapTable.OlapTableState; import com.baidu.palo.catalog.Partition; -import com.baidu.palo.catalog.PartitionType; import com.baidu.palo.catalog.Table; import com.baidu.palo.catalog.Table.TableType; -import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.cluster.ClusterNamespace; import com.baidu.palo.common.Config; import com.baidu.palo.common.DdlException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; -import com.baidu.palo.common.FeNameFormat; -import com.baidu.palo.common.PatternMatcher; +import com.baidu.palo.common.io.Writable; import com.baidu.palo.common.util.Daemon; -import com.baidu.palo.common.util.TimeUtils; -import com.baidu.palo.task.RestoreTask; +import com.baidu.palo.task.DirMoveTask; +import com.baidu.palo.task.DownloadTask; import com.baidu.palo.task.SnapshotTask; import com.baidu.palo.task.UploadTask; +import com.baidu.palo.thrift.TFinishTaskRequest; import com.google.common.base.Preconditions; -import com.google.common.collect.HashMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import com.google.common.collect.Multimap; import com.google.common.collect.Sets; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import java.util.Iterator; -import java.util.LinkedList; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; -public class BackupHandler extends Daemon { +public class BackupHandler extends Daemon implements Writable { private static final Logger LOG = LogManager.getLogger(BackupHandler.class); + + public static final int SIGNATURE_VERSION = 1; + public static final Path BACKUP_ROOT_DIR = Paths.get(Config.tmp_dir, "backup").normalize(); + public static final Path RESTORE_ROOT_DIR = Paths.get(Config.tmp_dir, "restore").normalize(); - private Map dbIdToBackupJob; - private Map dbIdToRestoreJob; - private List finishedOrCancelledBackupJobs; - private List finishedOrCancelledRestoreJobs; + private RepositoryMgr repoMgr = new RepositoryMgr(); - private Multimap dbIdToLabels; + // db id -> last running or finished backup/restore jobs + // We only save the last backup/restore job of a database. + // Newly submitted job will replace the current job, only if current job is finished or cancelled. + // If the last job is finished, user can get the job info from repository. If the last job is cancelled, + // user can get the error message before submitting the next one. + // Use ConcurrentMap to get rid of locks. + private Map dbIdToBackupOrRestoreJob = Maps.newConcurrentMap(); - // lock before db.lock - private ReentrantReadWriteLock lock; + // this lock is used for handling one backup or restore request at a time. + private ReentrantLock seqlock = new ReentrantLock(); - private final AsynchronousCmdExecutor cmdExecutor; + private boolean isInit = false; - public BackupHandler() { - super("backupHandler", 5000L); - dbIdToBackupJob = Maps.newHashMap(); - dbIdToRestoreJob = Maps.newHashMap(); - finishedOrCancelledBackupJobs = Lists.newArrayList(); - finishedOrCancelledRestoreJobs = Lists.newArrayList(); + private Catalog catalog; - dbIdToLabels = HashMultimap.create(); - - lock = new ReentrantReadWriteLock(); - - cmdExecutor = new AsynchronousCmdExecutor(); + private BackupHandler() { + // for persist } - public void readLock() { - lock.readLock().lock(); + public BackupHandler(Catalog catalog) { + super("backupHandler", 3000L); + this.catalog = catalog; } - public void readUnlock() { - lock.readLock().unlock(); + public void setCatalog(Catalog catalog) { + this.catalog = catalog; } - private void writeLock() { - lock.writeLock().lock(); + @Override + public synchronized void start() { + Preconditions.checkNotNull(catalog); + super.start(); + repoMgr.start(); } - private void writeUnlock() { - lock.writeLock().unlock(); + public RepositoryMgr getRepoMgr() { + return repoMgr; } - public AsynchronousCmdExecutor getAsynchronousCmdExecutor() { - return cmdExecutor; - } - - public void process(AbstractBackupStmt stmt) throws DdlException { - String dbName = stmt.getDbName(); - Database db = Catalog.getInstance().getDb(dbName); - if (db == null) { - ErrorReport.reportDdlException(ErrorCode.ERR_BAD_DB_ERROR, dbName); - } - - long dbId = db.getId(); - String label = stmt.getLabel(); - writeLock(); - try { - // 1. check if db has job underway - checkJobExist(dbId); - - // 2. check if label is used - checkAndAddLabel(dbId, label); - - // create job - if (stmt instanceof BackupStmt) { - createAndAddBackupJob(db, stmt.getLabelName(), stmt.getObjNames(), stmt.getRemotePath(), - stmt.getProperties()); - } else if (stmt instanceof RestoreStmt) { - createAndAddRestoreJob(db, stmt.getLabelName(), stmt.getObjNames(), stmt.getRemotePath(), - stmt.getProperties()); + private boolean init() { + // Check and create backup dir if necessarily + File backupDir = new File(BACKUP_ROOT_DIR.toString()); + if (!backupDir.exists()) { + if (!backupDir.mkdirs()) { + LOG.warn("failed to create backup dir: " + BACKUP_ROOT_DIR); + return false; } - } catch (DdlException e) { - // remove label - removeLabel(dbId, label); - throw e; - } finally { - writeUnlock(); - } - } - - private void checkJobExist(long dbId) throws DdlException { - if (dbIdToBackupJob.containsKey(dbId)) { - throw new DdlException("Database[" + dbId + "] has backup job underway"); - } - - if (dbIdToRestoreJob.containsKey(dbId)) { - throw new DdlException("Database[" + dbId + "] has restore job underway"); - } - } - - private void checkAndAddLabel(long dbId, String label) throws DdlException { - try { - FeNameFormat.checkLabel(label); - } catch (AnalysisException e) { - throw new DdlException(e.getMessage()); - } - - if (dbIdToLabels.containsKey(dbId)) { - if (dbIdToLabels.get(dbId).contains(label)) { - throw new DdlException("label " + label + " is already used"); - } - } - - dbIdToLabels.put(dbId, label); - } - - private void removeLabel(long dbId, String label) { - writeLock(); - try { - if (dbIdToLabels.containsKey(dbId)) { - dbIdToLabels.get(dbId).remove(label); - } - } finally { - writeUnlock(); - } - } - - private BackupJob createAndAddBackupJob(Database db, LabelName labelName, List backupObjNames, - String backupPath, Map properties) throws DdlException { - long jobId = Catalog.getInstance().getNextId(); - BackupJob job = new BackupJob(jobId, db.getId(), labelName, backupPath, properties); - db.writeLock(); - try { - if (backupObjNames.isEmpty()) { - // backup all tables - for (String tableName : db.getTableNamesWithLock()) { - backupObjNames.add(new PartitionName(tableName, null, null, null)); - } - } - - if (backupObjNames.isEmpty()) { - throw new DdlException("Database[" + db.getFullName() + "] is empty. no need to backup"); - } - - List backupTables = Lists.newArrayList(); - for (PartitionName backupObj : backupObjNames) { - String tableName = backupObj.getTableName(); - Table table = db.getTable(tableName); - if (table == null) { - ErrorReport.reportDdlException(ErrorCode.ERR_BAD_TABLE_ERROR, tableName); - } - - long tableId = table.getId(); - if (table.getType() == TableType.OLAP) { - OlapTable olapTable = (OlapTable) table; - - // check state - if (olapTable.getState() != OlapTableState.NORMAL) { - throw new DdlException("Table[" + table.getName() + "]' state is not NORMAL"); - } - - // add partition - String partitionName = backupObj.getPartitionName(); - if (partitionName.isEmpty()) { - // add all partitions - for (Partition partition : olapTable.getPartitions()) { - job.addPartitionId(tableId, partition.getId()); - } - } else { - if (olapTable.getPartitionInfo().getType() != PartitionType.RANGE) { - throw new DdlException("Table[" + table.getName() + "] is not range partitioned"); - } - // find and add specified partition - Partition partition = olapTable.getPartition(partitionName); - if (partition == null) { - throw new DdlException("Partition[" + partitionName + "] does not exist in table[" - + tableName + "]"); - } - - job.addPartitionId(tableId, partition.getId()); - } - - // add all indices - for (long indexId : olapTable.getIndexIdToSchema().keySet()) { - job.addIndexId(tableId, indexId); - } - - backupTables.add(olapTable); - } else { - // non-olap table; - job.addPartitionId(tableId, -1L); - } - } // end for backup objs - - // set table state - for (OlapTable olapTable : backupTables) { - Preconditions.checkState(olapTable.getState() == OlapTableState.NORMAL); - olapTable.setState(OlapTableState.BACKUP); - } - } finally { - db.writeUnlock(); - } - - // add - Preconditions.checkState(!dbIdToBackupJob.containsKey(db.getId())); - dbIdToBackupJob.put(db.getId(), job); - - // log - Catalog.getInstance().getEditLog().logBackupStart(job); - - LOG.info("finished create backup job[{}]", job.getJobId()); - return job; - } - - private RestoreJob createAndAddRestoreJob(Database db, LabelName labelName, List restoreObjNames, - String restorePath, Map properties) throws DdlException { - Map> tableToPartitionNames = Maps.newHashMap(); - Map tableRenameMap = Maps.newHashMap(); - List existTables = Lists.newArrayList(); - db.writeLock(); - try { - for (PartitionName partitionName : restoreObjNames) { - String newTableName = partitionName.getNewTableName(); - String newPartitionName = partitionName.getNewPartitionName(); - - Table table = db.getTable(newTableName); - if (table != null) { - if (newPartitionName.isEmpty()) { - // do not allow overwrite entire table - throw new DdlException("Table[" + table.getName() + "]' already exist. " - + "Drop table first or restore to another table"); - } - - existTables.add(table); - } - - Set partitionNames = tableToPartitionNames.get(partitionName.getTableName()); - if (partitionNames == null) { - partitionNames = Sets.newHashSet(); - tableToPartitionNames.put(newTableName, partitionNames); - } - - if (!newPartitionName.isEmpty()) { - partitionNames.add(newPartitionName); - } - - tableRenameMap.put(newTableName, partitionName.getTableName()); - } - - // set exist table's state - for (Table table : existTables) { - if (table.getType() == TableType.OLAP) { - ((OlapTable) table).setState(OlapTableState.RESTORE); - } - } - } finally { - db.writeUnlock(); - } - - long jobId = Catalog.getInstance().getNextId(); - RestoreJob job = new RestoreJob(jobId, db.getId(), labelName, restorePath, properties, - tableToPartitionNames, tableRenameMap); - - // add - Preconditions.checkState(!dbIdToRestoreJob.containsKey(db.getId())); - dbIdToRestoreJob.put(db.getId(), job); - - // log - Catalog.getInstance().getEditLog().logRestoreJobStart(job); - - LOG.info("finished create restore job[{}]", job.getJobId()); - return job; - } - - public void handleFinishedSnapshot(SnapshotTask snapshotTask, String snapshotPath) { - long dbId = snapshotTask.getDbId(); - long tabletId = snapshotTask.getTabletId(); - readLock(); - try { - BackupJob job = (BackupJob) dbIdToBackupJob.get(dbId); - if (job == null) { - LOG.warn("db[{}] does not have backup job. tablet: {}", dbId, tabletId); - return; - } - - if (job.getJobId() != snapshotTask.getJobId()) { - LOG.warn("tablet[{}] does not belong to backup job[{}]. tablet job[{}], tablet db[{}]", - tabletId, job.getJobId(), snapshotTask.getJobId(), dbId); - return; - } - - job.handleFinishedSnapshot(tabletId, snapshotTask.getBackendId(), snapshotPath); - - } finally { - readUnlock(); - } - } - - public void handleFinishedUpload(UploadTask uploadTask) { - long dbId = uploadTask.getDbId(); - long tabletId = uploadTask.getTabletId(); - readLock(); - try { - BackupJob job = (BackupJob) dbIdToBackupJob.get(dbId); - if (job == null) { - LOG.warn("db[{}] does not have backup job. tablet: {}", dbId, tabletId); - return; - } - - if (job.getJobId() != uploadTask.getJobId()) { - LOG.warn("tablet[{}] does not belong to backup job[{}]. tablet job[{}], tablet db[{}]", - tabletId, job.getJobId(), uploadTask.getJobId(), dbId); - return; - } - - job.handleFinishedUpload(tabletId, uploadTask.getBackendId()); - } finally { - readUnlock(); - } - } - - public void handleFinishedRestore(RestoreTask restoreTask) { - long dbId = restoreTask.getDbId(); - long tabletId = restoreTask.getTabletId(); - readLock(); - try { - RestoreJob job = (RestoreJob) dbIdToRestoreJob.get(dbId); - if (job == null) { - LOG.warn("db[{}] does not have restore job. tablet: {}", dbId, tabletId); - return; - } - - if (job.getJobId() != restoreTask.getJobId()) { - LOG.warn("tablet[{}] does not belong to restore job[{}]. tablet job[{}], tablet db[{}]", - tabletId, job.getJobId(), restoreTask.getJobId(), dbId); - return; - } - - job.handleFinishedRestore(tabletId, restoreTask.getBackendId()); - } finally { - readUnlock(); - } - } - - public void cancel(CancelBackupStmt stmt) throws DdlException { - String dbName = stmt.getDbName(); - Database db = Catalog.getInstance().getDb(stmt.getDbName()); - if (db == null) { - ErrorReport.reportDdlException(ErrorCode.ERR_BAD_DB_ERROR, dbName); - } - - Map dbIdToJob = null; - List finishedOrCancelledJobs = null; - if (stmt.isRestore()) { - dbIdToJob = dbIdToRestoreJob; - finishedOrCancelledJobs = finishedOrCancelledRestoreJobs; } else { - dbIdToJob = dbIdToBackupJob; - finishedOrCancelledJobs = finishedOrCancelledBackupJobs; + if (!backupDir.isDirectory()) { + LOG.warn("backup dir is not a directory: " + BACKUP_ROOT_DIR); + return false; + } } - cancelInternal(db, dbIdToJob, finishedOrCancelledJobs); + // Check and create restore dir if necessarily + File restoreDir = new File(RESTORE_ROOT_DIR.toString()); + if (!restoreDir.exists()) { + if (!restoreDir.mkdirs()) { + LOG.warn("failed to create restore dir: " + RESTORE_ROOT_DIR); + return false; + } + } else { + if (!restoreDir.isDirectory()) { + LOG.warn("restore dir is not a directory: " + RESTORE_ROOT_DIR); + return false; + } + } + isInit = true; + return true; } - private void cancelInternal(Database db, Map dbIdToJob, - List finishedOrCancelledJobs) throws DdlException { - writeLock(); - try { - long dbId = db.getId(); - AbstractBackupJob job = dbIdToJob.get(dbId); - if (job == null) { - throw new DdlException("There is no job in database[" + db.getFullName() + "]"); - } - - job.setErrMsg("user cancelled"); - if (job instanceof BackupJob) { - ((BackupJob) job).setState(BackupJobState.CANCELLED); - } else { - ((RestoreJob) job).setState(RestoreJobState.CANCELLED); - } - - job.end(Catalog.getInstance(), false); - - dbIdToJob.remove(dbId); - finishedOrCancelledJobs.add(job); - removeLabel(dbId, job.getLabel()); - - LOG.info("cancel job[{}] from db[{}]", job.getJobId(), dbId); - } finally { - writeUnlock(); - } + public AbstractJob getJob(long dbId) { + return dbIdToBackupOrRestoreJob.get(dbId); } @Override protected void runOneCycle() { - // backup - LOG.debug("run backup jobs once"); - runOnce(dbIdToBackupJob, finishedOrCancelledBackupJobs); + if (!isInit) { + if (!init()) { + return; + } + } - // restore - LOG.debug("run restore jobs once"); - runOnce(dbIdToRestoreJob, finishedOrCancelledRestoreJobs); + for (AbstractJob job : dbIdToBackupOrRestoreJob.values()) { + job.setCatalog(catalog); + job.run(); + } } - private void runOnce(Map dbIdToJobs, List finishedOrCancelledJobs) { - // backup jobs - writeLock(); + // handle create repository stmt + public void createRepository(CreateRepositoryStmt stmt) throws DdlException { + if (!catalog.getBrokerMgr().contaisnBroker(stmt.getBrokerName())) { + ErrorReport.reportDdlException(ErrorCode.ERR_COMMON_ERROR, "broker does not exist: " + stmt.getBrokerName()); + } + + BlobStorage storage = new BlobStorage(stmt.getBrokerName(), stmt.getProperties()); + long repoId = catalog.getNextId(); + Repository repo = new Repository(repoId, stmt.getName(), stmt.isReadOnly(), stmt.getLocation(), storage); + + Status st = repoMgr.addAndInitRepoIfNotExist(repo, false); + if (!st.ok()) { + ErrorReport.reportDdlException(ErrorCode.ERR_COMMON_ERROR, + "Failed to create repository: " + st.getErrMsg()); + } + } + + // handle drop repository stmt + public void dropRepository(DropRepositoryStmt stmt) throws DdlException { + tryLock(); try { - Iterator> iterator = dbIdToJobs.entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry entry = iterator.next(); - AbstractBackupJob job = entry.getValue(); - job.runOnce(); + Repository repo = repoMgr.getRepo(stmt.getRepoName()); + if (repo == null) { + ErrorReport.reportDdlException(ErrorCode.ERR_COMMON_ERROR, "Repository does not exist"); + } + + for (AbstractJob job : dbIdToBackupOrRestoreJob.values()) { + if (!job.isDone() && job.getRepoId() == repo.getId()) { + ErrorReport.reportDdlException(ErrorCode.ERR_COMMON_ERROR, + "Backup or restore job is running on this repository." + + " Can not drop it"); + } + } + + Status st = repoMgr.removeRepo(repo.getName(), false /* not replay */); + if (!st.ok()) { + ErrorReport.reportDdlException(ErrorCode.ERR_COMMON_ERROR, + "Failed to drop repository: " + st.getErrMsg()); + } + } finally { + seqlock.unlock(); + } + } - // handle finished or cancelled jobs - if (job instanceof BackupJob) { - BackupJob backupJob = (BackupJob) job; - if (backupJob.getState() == BackupJobState.FINISHED - || backupJob.getState() == BackupJobState.CANCELLED) { - finishedOrCancelledJobs.add(backupJob); + // the entry method of submitting a backup or restore job + public void process(AbstractBackupStmt stmt) throws DdlException { + // check if repo exist + String repoName = stmt.getRepoName(); + Repository repository = repoMgr.getRepo(repoName); + if (repository == null) { + ErrorReport.reportDdlException(ErrorCode.ERR_COMMON_ERROR, "Repository " + repoName + " does not exist"); + } - if (backupJob.getState() == BackupJobState.CANCELLED) { - // remove label - removeLabel(backupJob.getDbId(), backupJob.getLabel()); + // check if db exist + String dbName = stmt.getDbName(); + Database db = catalog.getDb(dbName); + if (db == null) { + ErrorReport.reportDdlException(ErrorCode.ERR_BAD_DB_ERROR, dbName); + } + + // Try to get sequence lock. + // We expect at most one operation on a repo at same time. + // But this operation may take a few seconds with lock held. + // So we use tryLock() to give up this operation if we can not get lock. + tryLock(); + try { + // Check if there is backup or restore job running on this database + AbstractJob currentJob = dbIdToBackupOrRestoreJob.get(db.getId()); + if (currentJob != null && !currentJob.isDone()) { + ErrorReport.reportDdlException(ErrorCode.ERR_COMMON_ERROR, + "Can only run one backup or restore job of a database at same time"); + } + + if (stmt instanceof BackupStmt) { + backup(repository, db, (BackupStmt) stmt); + } else if (stmt instanceof RestoreStmt) { + restore(repository, db, (RestoreStmt) stmt); + } + } finally { + seqlock.unlock(); + } + } + + private void tryLock() throws DdlException { + try { + if (!seqlock.tryLock(10, TimeUnit.SECONDS)) { + ErrorReport.reportDdlException(ErrorCode.ERR_COMMON_ERROR, "Another backup or restore job" + + " is being submitted. Please wait and try again"); + } + } catch (InterruptedException e) { + ErrorReport.reportDdlException(ErrorCode.ERR_COMMON_ERROR, "Got interrupted exception when " + + "try locking. Try again"); + } + } + + private void backup(Repository repository, Database db, BackupStmt stmt) throws DdlException { + if (repository.isReadOnly()) { + ErrorReport.reportDdlException(ErrorCode.ERR_COMMON_ERROR, "Repository " + repository.getName() + + " is read only"); + } + + // Check if backup objects are valid + // This is just a pre-check to avoid most of invalid backup requests. + // Also calculate the signature for incremental backup check. + List tblRefs = stmt.getTableRefs(); + BackupMeta curBackupMeta = null; + db.readLock(); + try { + List
backupTbls = Lists.newArrayList(); + for (TableRef tblRef : tblRefs) { + String tblName = tblRef.getName().getTbl(); + Table tbl = db.getTable(tblName); + if (tbl == null) { + ErrorReport.reportDdlException(ErrorCode.ERR_BAD_TABLE_ERROR, tblName); + } + if (tbl.getType() != TableType.OLAP) { + ErrorReport.reportDdlException(ErrorCode.ERR_NOT_OLAP_TABLE, tblName); + } + + OlapTable olapTbl = (OlapTable) tbl; + if (tblRef.getPartitions() != null && !tblRef.getPartitions().isEmpty()) { + for (String partName : tblRef.getPartitions()) { + Partition partition = olapTbl.getPartition(partName); + if (partition == null) { + ErrorReport.reportDdlException(ErrorCode.ERR_COMMON_ERROR, + "Unknown partition " + partName + " in table" + tblName); } - iterator.remove(); - } - } else if (job instanceof RestoreJob) { - RestoreJob restoreJob = (RestoreJob) job; - if (restoreJob.getState() == RestoreJobState.FINISHED - || restoreJob.getState() == RestoreJobState.CANCELLED) { - finishedOrCancelledJobs.add(restoreJob); - - if (restoreJob.getState() == RestoreJobState.CANCELLED) { - // remove label - removeLabel(restoreJob.getDbId(), restoreJob.getLabel()); - } - iterator.remove(); } } - } - // clear historical jobs - Iterator iter = finishedOrCancelledJobs.iterator(); - while (iter.hasNext()) { - AbstractBackupJob job = iter.next(); - if ((System.currentTimeMillis() - job.getCreateTime()) / 1000 > Config.label_keep_max_second) { - iter.remove(); - LOG.info("remove history job[{}]. created at {}", job.getJobId(), - TimeUtils.longToTimeString(job.getCreateTime())); + // copy a table with selected partitions for calculating the signature + OlapTable copiedTbl = olapTbl.selectiveCopy(tblRef.getPartitions()); + if (copiedTbl == null) { + ErrorReport.reportDdlException(ErrorCode.ERR_COMMON_ERROR, + "Failed to copy table " + tblName + " with selected partitions"); } + backupTbls.add(copiedTbl); } - + curBackupMeta = new BackupMeta(backupTbls); } finally { - writeUnlock(); - } - } - - public int getBackupJobNum(BackupJobState state, long dbId) { - int jobNum = 0; - readLock(); - try { - if (dbIdToBackupJob.containsKey(dbId)) { - BackupJob job = (BackupJob) dbIdToBackupJob.get(dbId); - if (job.getState() == state) { - ++jobNum; - } - } - - if (state == BackupJobState.FINISHED || state == BackupJobState.CANCELLED) { - for (AbstractBackupJob job : finishedOrCancelledBackupJobs) { - if (job.getDbId() != dbId) { - continue; - } - - if (((BackupJob) job).getState() == state) { - ++jobNum; - } - } - } - } finally { - readUnlock(); + db.readUnlock(); } - return jobNum; - } + // Check if label already be used + List existSnapshotNames = Lists.newArrayList(); + Status st = repository.listSnapshots(existSnapshotNames); + if (!st.ok()) { + ErrorReport.reportDdlException(ErrorCode.ERR_COMMON_ERROR, st.getErrMsg()); + } + if (existSnapshotNames.contains(stmt.getLabel())) { + if (stmt.getType() == BackupType.FULL) { + ErrorReport.reportDdlException(ErrorCode.ERR_COMMON_ERROR, "Snapshot with name '" + + stmt.getLabel() + "' already exist in repository"); + } else { + ErrorReport.reportDdlException(ErrorCode.ERR_COMMON_ERROR, "Currently does not support " + + "incremental backup"); - public int getRestoreJobNum(RestoreJobState state, long dbId) { - int jobNum = 0; - readLock(); - try { - if (dbIdToRestoreJob.containsKey(dbId)) { - RestoreJob job = (RestoreJob) dbIdToRestoreJob.get(dbId); - if (job.getState() == state) { - ++jobNum; + // TODO: + // This is a incremental backup, the existing snapshot in repository will be treated + // as base snapshot. + // But first we need to check if the existing snapshot has same meta. + List backupMetas = Lists.newArrayList(); + st = repository.getSnapshotMetaFile(stmt.getLabel(), backupMetas); + if (!st.ok()) { + ErrorReport.reportDdlException(ErrorCode.ERR_COMMON_ERROR, + "Failed to get existing meta info for repository: " + + st.getErrMsg()); + } + Preconditions.checkState(backupMetas.size() == 1); + + if (!curBackupMeta.compatibleWith(backupMetas.get(0))) { + ErrorReport.reportDdlException(ErrorCode.ERR_COMMON_ERROR, + "Can not make incremental backup. Meta does not compatible"); } } - - if (state == RestoreJobState.FINISHED || state == RestoreJobState.CANCELLED) { - for (AbstractBackupJob job : finishedOrCancelledRestoreJobs) { - if (job.getDbId() != dbId) { - continue; - } - - if (((RestoreJob) job).getState() == state) { - ++jobNum; - } - } - } - } finally { - readUnlock(); } - return jobNum; + // Create a backup job + BackupJob backupJob = new BackupJob(stmt.getLabel(), db.getId(), + ClusterNamespace.getNameFromFullName(db.getFullName()), + tblRefs, stmt.getTimeoutMs(), + catalog, repository.getId()); + dbIdToBackupOrRestoreJob.put(db.getId(), backupJob); + + // write log + catalog.getEditLog().logBackupJob(backupJob); + + LOG.info("finished to submit backup job: {}", backupJob); } - public List> getJobInfosByDb(long dbId, Class jobClass, - PatternMatcher matcher) { - Map dbIdToJob = null; - List finishedOrCancelledJobs = null; - if (jobClass.equals(BackupJob.class)) { - dbIdToJob = dbIdToBackupJob; - finishedOrCancelledJobs = finishedOrCancelledBackupJobs; + private void restore(Repository repository, Database db, RestoreStmt stmt) throws DdlException { + // Check if snapshot exist in repository + List infos = Lists.newArrayList(); + Status status = repository.getSnapshotInfoFile(stmt.getLabel(), stmt.getBackupTimestamp(), infos); + if (!status.ok()) { + ErrorReport.reportDdlException(ErrorCode.ERR_COMMON_ERROR, + "Failed to get info of snapshot '" + stmt.getLabel() + "' because: " + + status.getErrMsg() + ". Maybe specified wrong backup timestamp"); + } + + // Check if all restore objects are exist in this snapshot. + // Also remove all unrelated objs + Preconditions.checkState(infos.size() == 1); + BackupJobInfo jobInfo = infos.get(0); + checkAndFilterRestoreObjsExistInSnapshot(jobInfo, stmt.getTableRefs()); + + // Create a restore job + RestoreJob restoreJob = new RestoreJob(stmt.getLabel(), stmt.getBackupTimestamp(), + db.getId(), db.getFullName(), jobInfo, stmt.allowLoad(), stmt.getReplicationNum(), + stmt.getTimeoutMs(), catalog, repository.getId()); + dbIdToBackupOrRestoreJob.put(db.getId(), restoreJob); + + catalog.getEditLog().logRestoreJob(restoreJob); + LOG.info("finished to submit restore job: {}", restoreJob); + } + + private void checkAndFilterRestoreObjsExistInSnapshot(BackupJobInfo jobInfo, List tblRefs) + throws DdlException { + Set allTbls = Sets.newHashSet(); + for (TableRef tblRef : tblRefs) { + String tblName = tblRef.getName().getTbl(); + if (!jobInfo.containsTbl(tblName)) { + ErrorReport.reportDdlException(ErrorCode.ERR_COMMON_ERROR, + "Table " + tblName + " does not exist in snapshot " + jobInfo.name); + } + BackupTableInfo tblInfo = jobInfo.getTableInfo(tblName); + if (tblRef.getPartitions() != null && !tblRef.getPartitions().isEmpty()) { + // check the selected partitions + for (String partName : tblRef.getPartitions()) { + if (!tblInfo.containsPart(partName)) { + ErrorReport.reportDdlException(ErrorCode.ERR_COMMON_ERROR, + "Partition " + partName + " of table " + tblName + + " does not exist in snapshot " + jobInfo.name); + } + } + } + + // set alias + if (tblRef.hasExplicitAlias()) { + jobInfo.setAlias(tblName, tblRef.getExplicitAlias()); + } + + // only retain restore partitions + tblInfo.retainPartitions(tblRef.getPartitions()); + allTbls.add(tblName); + } + + // only retain restore tables + jobInfo.retainTables(allTbls); + } + + public void cancel(CancelBackupStmt stmt) throws DdlException { + String dbName = stmt.getDbName(); + Database db = catalog.getDb(dbName); + if (db == null) { + ErrorReport.reportDdlException(ErrorCode.ERR_BAD_DB_ERROR, dbName); + } + + AbstractJob job = dbIdToBackupOrRestoreJob.get(db.getId()); + if (job == null || (job instanceof BackupJob && stmt.isRestore()) + || (job instanceof RestoreJob && !stmt.isRestore())) { + ErrorReport.reportDdlException(ErrorCode.ERR_COMMON_ERROR, "No " + + (stmt.isRestore() ? "restore" : "backup" + " job") + + " is currently running"); + } + + Status status = job.cancel(); + if (!status.ok()) { + ErrorReport.reportDdlException(ErrorCode.ERR_COMMON_ERROR, "Failed to cancel job: " + status.getErrMsg()); + } + + LOG.info("finished to cancel {} job: {}", (stmt.isRestore() ? "restore" : "backup"), job); + } + + public boolean handleFinishedSnapshotTask(SnapshotTask task, TFinishTaskRequest request) { + AbstractJob job = dbIdToBackupOrRestoreJob.get(task.getDbId()); + if (job == null) { + LOG.warn("failed to find backup or restore job for task: {}", task); + // return true to remove this task from AgentTaskQueue + return true; + } + if (job instanceof BackupJob) { + if (task.isRestoreTask()) { + LOG.warn("expect finding restore job, but get backup job {} for task: {}", job, task); + // return true to remove this task from AgentTaskQueue + return true; + } + + return ((BackupJob) job).finishTabletSnapshotTask(task, request); } else { - dbIdToJob = dbIdToRestoreJob; - finishedOrCancelledJobs = finishedOrCancelledRestoreJobs; - } - - List> jobInfos = new LinkedList>(); - readLock(); - try { - AbstractBackupJob abstractJob = dbIdToJob.get(dbId); - if (abstractJob != null) { - List jobInfo = abstractJob.getJobInfo(); - if (matcher != null) { - String label = jobInfo.get(1).toString(); - if (matcher.match(label)) { - jobInfos.add(jobInfo); - } - } else { - jobInfos.add(jobInfo); - } + if (!task.isRestoreTask()) { + LOG.warn("expect finding backup job, but get restore job {} for task: {}", job, task); + // return true to remove this task from AgentTaskQueue + return true; } + return ((RestoreJob) job).finishTabletSnapshotTask(task, request); + } + } - for (AbstractBackupJob job : finishedOrCancelledJobs) { - if (job.getDbId() != dbId) { - continue; - } - List jobInfo = job.getJobInfo(); - if (matcher != null) { - String label = jobInfo.get(1).toString(); - if (matcher.match(label)) { - jobInfos.add(jobInfo); - } - } else { - jobInfos.add(jobInfo); - } + public boolean handleFinishedSnapshotUploadTask(UploadTask task, TFinishTaskRequest request) { + AbstractJob job = dbIdToBackupOrRestoreJob.get(task.getDbId()); + if (job == null || (job instanceof RestoreJob)) { + LOG.info("invalid upload task: {}, no backup job is found. db id: {}", task, task.getDbId()); + return false; + } + BackupJob restoreJob = (BackupJob) job; + if (restoreJob.getJobId() != task.getJobId() || restoreJob.getState() != BackupJobState.UPLOADING) { + LOG.info("invalid upload task: {}, job id: {}, job state: {}", + task, restoreJob.getJobId(), restoreJob.getState().name()); + return false; + } + return restoreJob.finishSnapshotUploadTask(task, request); + } + + public boolean handleDownloadSnapshotTask(DownloadTask task, TFinishTaskRequest request) { + AbstractJob job = dbIdToBackupOrRestoreJob.get(task.getDbId()); + if (job == null || !(job instanceof RestoreJob)) { + LOG.warn("failed to find restore job for task: {}", task); + // return true to remove this task from AgentTaskQueue + return true; + } + + return ((RestoreJob) job).finishTabletDownloadTask(task, request); + } + + public boolean handleDirMoveTask(DirMoveTask task, TFinishTaskRequest request) { + AbstractJob job = dbIdToBackupOrRestoreJob.get(task.getDbId()); + if (job == null || !(job instanceof RestoreJob)) { + LOG.warn("failed to find restore job for task: {}", task); + // return true to remove this task from AgentTaskQueue + return true; + } + + return ((RestoreJob) job).finishDirMoveTask(task, request); + } + + public void replayAddJob(AbstractJob job) { + if (job.isCancelled()) { + AbstractJob existingJob = dbIdToBackupOrRestoreJob.get(job.getDbId()); + if (existingJob == null || existingJob.isDone()) { + LOG.error("invalid existing job: {}. current replay job is: {}", + existingJob, job); + return; } - } finally { - readUnlock(); - } - return jobInfos; - } - - public List> getJobUnfinishedTablet(long dbId, Class jobClass) { - Map dbIdToJob = null; - if (jobClass.equals(BackupJob.class)) { - dbIdToJob = dbIdToBackupJob; - } else { - dbIdToJob = dbIdToRestoreJob; - } - - List> jobInfos = Lists.newArrayList(); - readLock(); - try { - AbstractBackupJob abstractJob = dbIdToJob.get(dbId); - if (abstractJob != null) { - jobInfos = abstractJob.getUnfinishedInfos(); + existingJob.setCatalog(catalog); + existingJob.replayCancel(); + } else if (!job.isPending()) { + AbstractJob existingJob = dbIdToBackupOrRestoreJob.get(job.getDbId()); + if (existingJob == null || existingJob.isDone()) { + LOG.error("invalid existing job: {}. current replay job is: {}", + existingJob, job); + return; } - } finally { - readUnlock(); + // We use replayed job, not the existing job, to do the replayRun(). + // Because if we use the existing job to run again, + // for example: In restore job, PENDING will transfer to SNAPSHOTING, not DOWNLOAD. + job.replayRun(); } - return jobInfos; + dbIdToBackupOrRestoreJob.put(job.getDbId(), job); } - private void setTableState(Catalog catalog, BackupJob job) { - Database db = catalog.getDb(job.getDbId()); - db.writeLock(); - try { - for (long tableId : job.getTableIdToPartitionIds().keySet()) { - Table table = db.getTable(tableId); - if (table.getType() == TableType.OLAP) { - ((OlapTable) table).setState(OlapTableState.BACKUP); - } - } - } finally { - db.writeUnlock(); + public static BackupHandler read(DataInput in) throws IOException { + BackupHandler backupHandler = new BackupHandler(); + backupHandler.readFields(in); + return backupHandler; + } + + @Override + public void write(DataOutput out) throws IOException { + repoMgr.write(out); + + out.writeInt(dbIdToBackupOrRestoreJob.size()); + for (AbstractJob job : dbIdToBackupOrRestoreJob.values()) { + job.write(out); } - LOG.info("finished set backup tables' state to BACKUP. job: {}", job.getJobId()); } - private void setTableState(Catalog catalog, RestoreJob job) { - Database db = catalog.getDb(job.getDbId()); - db.writeLock(); - try { - for (String tableName : job.getTableToPartitionNames().keySet()) { - Table table = db.getTable(tableName); - if (table != null && table.getType() == TableType.OLAP) { - ((OlapTable) table).setState(OlapTableState.RESTORE); - } - } - } finally { - db.writeUnlock(); + @Override + public void readFields(DataInput in) throws IOException { + repoMgr = RepositoryMgr.read(in); + + int size = in.readInt(); + for (int i = 0; i < size; i++) { + AbstractJob job = AbstractJob.read(in); + dbIdToBackupOrRestoreJob.put(job.getDbId(), job); } - LOG.info("finished set restore tables' state to RESTORE. job: {}", job.getJobId()); - } - - public void replayBackupStart(Catalog catalog, BackupJob job) { - setTableState(catalog, job); - - writeLock(); - try { - dbIdToLabels.put(job.getDbId(), job.getLabel()); - Preconditions.checkState(!dbIdToBackupJob.containsKey(job.getDbId())); - dbIdToBackupJob.put(job.getDbId(), job); - LOG.debug("replay start backup job, put {} to map", job.getDbId()); - } finally { - writeUnlock(); - } - - LOG.info("finished replay start backup job[{}]", job.getJobId()); - } - - public void replayBackupFinishSnapshot(BackupJob job) { - Preconditions.checkState(job.getState() == BackupJobState.UPLOAD); - writeLock(); - try { - long dbId = job.getDbId(); - BackupJob currentJob = (BackupJob) dbIdToBackupJob.get(dbId); - Preconditions.checkState(currentJob.getJobId() == job.getJobId()); - dbIdToBackupJob.remove(dbId); - dbIdToBackupJob.put(dbId, job); - } finally { - writeUnlock(); - } - - LOG.info("finished replay backup finish snapshot. job[{}]", job.getJobId()); - } - - public void replayBackupFinish(Catalog catalog, BackupJob job) { - job.end(catalog, true); - - writeLock(); - try { - BackupJob currentJob = (BackupJob) dbIdToBackupJob.remove(job.getDbId()); - Preconditions.checkNotNull(currentJob, job.getDbId()); - - finishedOrCancelledBackupJobs.add(job); - if (job.getState() == BackupJobState.CANCELLED) { - removeLabel(job.getDbId(), job.getLabel()); - } - } finally { - writeUnlock(); - } - - LOG.info("finished replay backup job finish. job: {}", job.getJobId()); - } - - public void replayRestoreStart(Catalog catalog, RestoreJob job) { - setTableState(catalog, job); - writeLock(); - try { - dbIdToLabels.put(job.getDbId(), job.getLabel()); - Preconditions.checkState(!dbIdToRestoreJob.containsKey(job.getDbId())); - dbIdToRestoreJob.put(job.getDbId(), job); - LOG.debug("replay start restore job, put {} to map", job.getDbId()); - } finally { - writeUnlock(); - } - - LOG.info("finished replay start restore job[{}]", job.getJobId()); - } - - public void replayRestoreFinish(Catalog catalog, RestoreJob job) { - try { - job.finishing(catalog, true); - job.end(catalog, true); - } catch (DdlException e) { - LOG.error("should not happend", e); - } - - writeLock(); - try { - RestoreJob currentJob = (RestoreJob) dbIdToRestoreJob.remove(job.getDbId()); - Preconditions.checkNotNull(currentJob, job.getDbId()); - - finishedOrCancelledRestoreJobs.add(job); - if (job.getState() == RestoreJobState.CANCELLED) { - removeLabel(job.getDbId(), job.getLabel()); - } - } finally { - writeUnlock(); - } - - LOG.info("finished replay restore job finish. job: {}", job.getJobId()); - } - - public Map unprotectedGetBackupJobs() { - return dbIdToBackupJob; - } - - public List unprotectedGetFinishedOrCancelledBackupJobs() { - return finishedOrCancelledBackupJobs; - } - - public Map unprotectedGetRestoreJobs() { - return dbIdToRestoreJob; - } - - public List unprotectedGetFinishedOrCancelledRestoreJobs() { - return finishedOrCancelledRestoreJobs; - } - - public Multimap unprotectedGetDbIdToLabels() { - return dbIdToLabels; } } + + diff --git a/fe/src/com/baidu/palo/backup/BackupJob.java b/fe/src/com/baidu/palo/backup/BackupJob.java index 14c8377c57..6c8a5030d5 100644 --- a/fe/src/com/baidu/palo/backup/BackupJob.java +++ b/fe/src/com/baidu/palo/backup/BackupJob.java @@ -1,62 +1,37 @@ -// Copyright (c) 2017, Baidu.com, Inc. All Rights Reserved - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// 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. - package com.baidu.palo.backup; -import com.baidu.palo.analysis.AlterTableStmt; -import com.baidu.palo.analysis.CreateTableStmt; -import com.baidu.palo.analysis.LabelName; +import com.baidu.palo.analysis.TableRef; +import com.baidu.palo.backup.Status.ErrCode; +import com.baidu.palo.catalog.BrokerMgr.BrokerAddress; import com.baidu.palo.catalog.Catalog; import com.baidu.palo.catalog.Database; import com.baidu.palo.catalog.MaterializedIndex; import com.baidu.palo.catalog.OlapTable; -import com.baidu.palo.catalog.OlapTable.OlapTableState; import com.baidu.palo.catalog.Partition; -import com.baidu.palo.catalog.PartitionInfo; -import com.baidu.palo.catalog.PartitionKey; -import com.baidu.palo.catalog.PartitionType; -import com.baidu.palo.catalog.RangePartitionInfo; import com.baidu.palo.catalog.Replica; import com.baidu.palo.catalog.Table; import com.baidu.palo.catalog.Table.TableType; import com.baidu.palo.catalog.Tablet; -import com.baidu.palo.common.DdlException; -import com.baidu.palo.common.Pair; import com.baidu.palo.common.io.Text; -import com.baidu.palo.common.io.Writable; -import com.baidu.palo.common.util.LoadBalancer; import com.baidu.palo.common.util.TimeUtils; -import com.baidu.palo.common.util.Util; -import com.baidu.palo.load.DeleteInfo; -import com.baidu.palo.load.Load; -import com.baidu.palo.load.LoadJob; import com.baidu.palo.task.AgentBatchTask; import com.baidu.palo.task.AgentTask; import com.baidu.palo.task.AgentTaskExecutor; import com.baidu.palo.task.AgentTaskQueue; -import com.baidu.palo.task.ReleaseSnapshotTask; import com.baidu.palo.task.SnapshotTask; import com.baidu.palo.task.UploadTask; +import com.baidu.palo.thrift.TFinishTaskRequest; +import com.baidu.palo.thrift.TStatusCode; import com.baidu.palo.thrift.TTaskType; +import com.google.common.base.Joiner; import com.google.common.base.Preconditions; -import com.google.common.collect.HashMultimap; +import com.google.common.base.Predicates; +import com.google.common.base.Strings; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Collections2; import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import com.google.common.collect.Multimap; -import com.google.common.collect.Range; import com.google.common.collect.Sets; import org.apache.logging.log4j.LogManager; @@ -66,810 +41,711 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.File; import java.io.IOException; -import java.util.Collection; +import java.nio.file.FileVisitOption; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; -public class BackupJob extends AbstractBackupJob { + +public class BackupJob extends AbstractJob { private static final Logger LOG = LogManager.getLogger(BackupJob.class); - private static final long SNAPSHOT_TIMEOUT_MS = 2000; // 1s for one tablet - public enum BackupJobState { - PENDING, - SNAPSHOT, - UPLOAD, - UPLOADING, - FINISHING, - FINISHED, - CANCELLED + PENDING, // Job is newly created. Send snapshot tasks and save copied meta info, then transfer to SNAPSHOTING + SNAPSHOTING, // Wait for finishing snapshot tasks. When finished, transfer to UPLOAD_SNAPSHOT + UPLOAD_SNAPSHOT, // Begin to send upload task to BE, then transfer to UPLOADING + UPLOADING, // Wait for finishing upload tasks. When finished, transfer to SAVE_META + SAVE_META, // Save copied meta info to local file. When finished, transfer to UPLOAD_INFO + UPLOAD_INFO, // Upload meta and job info file to repository. When finished, transfer to FINISHED + FINISHED, // Job is finished. + CANCELLED // Job is cancelled. } + // all objects which need backup + private List tableRefs = Lists.newArrayList(); + private BackupJobState state; - private String lastestLoadLabel; - private DeleteInfo lastestDeleteInfo; + private long snapshotFinishedTime = -1; + private long snapshopUploadFinishedTime = -1; - // all partitions need to be backuped - private Map> tableIdToPartitionIds; - private Multimap tableIdToIndexIds; - // partition id -> (version, version hash) - private Map> partitionIdToVersionInfo; - - private Map> tabletIdToSnapshotPath; + // save all tablets which tasks are not finished. + private Set unfinishedTaskIds = Sets.newConcurrentHashSet(); + // tablet id -> snapshot info + private Map snapshotInfos = Maps.newConcurrentMap(); + // save all related table[partition] info + private BackupMeta backupMeta; + // job info file content + private BackupJobInfo jobInfo; - private long metaSavedTime; - private long snapshotFinishedTime; - private long uploadFinishedTime; - - private long phasedTimeoutMs; - - private String readableManifestPath; + // save the local dir of this backup job + // after job is done, this dir should be deleted + private Path localJobDirPath = null; + // save the local file path of meta info and job info file + private String localMetaInfoFilePath = null; + private String localJobInfoFilePath = null; public BackupJob() { - super(); - tableIdToPartitionIds = Maps.newHashMap(); - tableIdToIndexIds = HashMultimap.create(); - partitionIdToVersionInfo = Maps.newHashMap(); - tabletIdToSnapshotPath = Maps.newHashMap(); + super(JobType.BACKUP); } - public BackupJob(long jobId, long dbId, LabelName labelName, String backupPath, - Map remoteProperties) { - super(jobId, dbId, labelName, backupPath, remoteProperties); + public BackupJob(String label, long dbId, String dbName, List tableRefs, long timeoutMs, + Catalog catalog, long repoId) { + super(JobType.BACKUP, label, dbId, dbName, timeoutMs, catalog, repoId); + this.tableRefs = tableRefs; this.state = BackupJobState.PENDING; - - tableIdToPartitionIds = Maps.newHashMap(); - tableIdToIndexIds = HashMultimap.create(); - partitionIdToVersionInfo = Maps.newHashMap(); - - tabletIdToSnapshotPath = Maps.newHashMap(); - - metaSavedTime = -1; - snapshotFinishedTime = -1; - uploadFinishedTime = -1; - phasedTimeoutMs = -1; - - lastestLoadLabel = "N/A"; - readableManifestPath = ""; - } - - public void setState(BackupJobState state) { - this.state = state; } public BackupJobState getState() { return state; } - public String getLatestLoadLabel() { - return lastestLoadLabel; + public BackupMeta getBackupMeta() { + return backupMeta; } - public DeleteInfo getLastestDeleteInfo() { - return lastestDeleteInfo; - } - - public PathBuilder getPathBuilder() { - return pathBuilder; - } - - public long getMetaSavedTimeMs() { - return metaSavedTime; - } - - public long getSnapshotFinishedTimeMs() { - return snapshotFinishedTime; - } - - public long getUploadFinishedTimeMs() { - return uploadFinishedTime; - } - - public String getReadableManifestPath() { - return readableManifestPath; - } - - public Map> getTableIdToPartitionIds() { - return tableIdToPartitionIds; - } - - public void addPartitionId(long tableId, long partitionId) { - Set partitionIds = tableIdToPartitionIds.get(tableId); - if (partitionIds == null) { - partitionIds = Sets.newHashSet(); - tableIdToPartitionIds.put(tableId, partitionIds); - } - if (partitionId != -1L) { - partitionIds.add(partitionId); - } - - LOG.debug("add partition[{}] from table[{}], job[{}]", partitionId, tableId, jobId); - } - - public void addIndexId(long tableId, long indexId) { - tableIdToIndexIds.put(tableId, indexId); - LOG.debug("add index[{}] from table[{}], job[{}]", indexId, tableId, jobId); - } - - public void handleFinishedSnapshot(long tabletId, long backendId, String snapshotPath) { - synchronized (unfinishedTabletIds) { - if (!unfinishedTabletIds.containsKey(tabletId)) { - LOG.warn("backup job[{}] does not contains tablet[{}]", jobId, tabletId); - return; - } - - if (unfinishedTabletIds.get(tabletId) == null - || !unfinishedTabletIds.get(tabletId).contains(backendId)) { - LOG.warn("backup job[{}] does not contains tablet[{}]'s snapshot from backend[{}]. " - + "it should from backend[{}]", - jobId, tabletId, backendId, unfinishedTabletIds.get(tabletId)); - return; - } - unfinishedTabletIds.remove(tabletId, backendId); - } - - synchronized (tabletIdToSnapshotPath) { - tabletIdToSnapshotPath.put(tabletId, new Pair(backendId, snapshotPath)); - } - LOG.debug("finished add tablet[{}] from backend[{}]. snapshot path: {}", tabletId, backendId, snapshotPath); - } - - public void handleFinishedUpload(long tabletId, long backendId) { - synchronized (unfinishedTabletIds) { - if (unfinishedTabletIds.remove(tabletId, backendId)) { - LOG.debug("finished upload tablet[{}] snapshot, backend[{}]", tabletId, backendId); - } - } - } - - @Override - public List getJobInfo() { - List jobInfo = Lists.newArrayList(); - jobInfo.add(jobId); - jobInfo.add(getLabel()); - jobInfo.add(state.name()); - jobInfo.add(TimeUtils.longToTimeString(createTime)); - jobInfo.add(TimeUtils.longToTimeString(metaSavedTime)); - jobInfo.add(TimeUtils.longToTimeString(snapshotFinishedTime)); - jobInfo.add(TimeUtils.longToTimeString(uploadFinishedTime)); - jobInfo.add(TimeUtils.longToTimeString(finishedTime)); - jobInfo.add(errMsg); - jobInfo.add(PathBuilder.createPath(remotePath, getLabel())); - jobInfo.add(getReadableManifestPath()); - jobInfo.add(getLeftTasksNum()); - jobInfo.add(getLatestLoadLabel()); + public BackupJobInfo getJobInfo() { return jobInfo; } - @Override - public void runOnce() { - LOG.debug("begin to run backup job: {}, state: {}", jobId, state.name()); - try { - switch (state) { - case PENDING: - saveMetaAndMakeSnapshot(); - break; - case SNAPSHOT: - waitSnapshot(); - break; - case UPLOAD: - upload(); - break; - case UPLOADING: - waitUpload(); - break; - case FINISHING: - finishing(); - break; - default: - break; + public String getLocalJobInfoFilePath() { + return localJobInfoFilePath; + } + + public String getLocalMetaInfoFilePath() { + return localMetaInfoFilePath; + } + + public synchronized boolean finishTabletSnapshotTask(SnapshotTask task, TFinishTaskRequest request) { + Preconditions.checkState(task.getJobId() == jobId); + + if (request.getTask_status().getStatus_code() != TStatusCode.OK) { + taskErrMsg.put(task.getSignature(), Joiner.on(",").join(request.getTask_status().getError_msgs())); + return false; + } + + Preconditions.checkState(request.isSetSnapshot_path()); + Preconditions.checkState(request.isSetSnapshot_files()); + // snapshot path does not contains last 'tablet_id' and 'schema_hash' dir + // eg: + // /path/to/your/be/data/snapshot/20180410102311.0/ + // Full path will look like: + // /path/to/your/be/data/snapshot/20180410102311.0/10006/352781111/ + SnapshotInfo info = new SnapshotInfo(task.getDbId(), task.getTableId(), task.getPartitionId(), + task.getIndexId(), task.getTabletId(), task.getBackendId(), + task.getSchemaHash(), request.getSnapshot_path(), + request.getSnapshot_files()); + + snapshotInfos.put(task.getTabletId(), info); + boolean res = unfinishedTaskIds.remove(task.getTabletId()); + taskErrMsg.remove(task.getTabletId()); + LOG.debug("get finished snapshot info: {}, unfinished tasks num: {}, remove result: {}. {}", + info, unfinishedTaskIds.size(), res, this); + + return res; + } + + public synchronized boolean finishSnapshotUploadTask(UploadTask task, TFinishTaskRequest request) { + Preconditions.checkState(task.getJobId() == jobId); + + if (request.getTask_status().getStatus_code() != TStatusCode.OK) { + taskErrMsg.put(task.getSignature(), Joiner.on(",").join(request.getTask_status().getError_msgs())); + return false; + } + + Preconditions.checkState(request.isSetTablet_files()); + Map> tabletFileMap = request.getTablet_files(); + if (tabletFileMap.isEmpty()) { + LOG.warn("upload snapshot files failed because nothing is uploaded. be: {}. {}", + task.getBackendId(), this); + return false; + } + + // remove checksum suffix in reported file name before checking files + Map> newTabletFileMap = Maps.newHashMap(); + for (Map.Entry> entry : tabletFileMap.entrySet()) { + List files = entry.getValue().stream() + .map(name -> Repository.decodeFileNameWithChecksum(name).first).collect(Collectors.toList()); + newTabletFileMap.put(entry.getKey(), files); + } + + // check if uploaded files are correct + for (long tabletId : newTabletFileMap.keySet()) { + SnapshotInfo info = snapshotInfos.get(tabletId); + List tabletFiles = info.getFiles(); + List uploadedFiles = newTabletFileMap.get(tabletId); + + if (tabletFiles.size() != uploadedFiles.size()) { + LOG.warn("upload snapshot files failed because file num is wrong. " + + "expect: {}, actual:{}, tablet: {}, be: {}. {}", + tabletFiles.size(), uploadedFiles.size(), tabletId, task.getBackendId(), this); + return false; + } + + if (!Collections2.filter(tabletFiles, Predicates.not(Predicates.in(uploadedFiles))).isEmpty()) { + LOG.warn("upload snapshot files failed because file is different. " + + "expect: [{}], actual: [{}], tablet: {}, be: {}. {}", + tabletFiles, uploadedFiles, tabletId, task.getBackendId(), this); + return false; + } + + // reset files in snapshot info with checksum filename + info.setFiles(tabletFileMap.get(tabletId)); + } + + boolean res = unfinishedTaskIds.remove(task.getSignature()); + taskErrMsg.remove(task.getTabletId()); + LOG.debug("get finished upload snapshot task, unfinished tasks num: {}, remove result: {}. {}", + unfinishedTaskIds.size(), res, this); + return res; + } + + @Override + public synchronized void replayRun() { + // Backup process does not change any current catalog state, + // So nothing need to be done when replaying log + } + + @Override + public synchronized void replayCancel() { + // nothing to do + } + + @Override + public boolean isPending() { + return state == BackupJobState.PENDING; + } + + @Override + public boolean isCancelled() { + return state == BackupJobState.CANCELLED; + } + + // Polling the job state and do the right things. + @Override + public synchronized void run() { + if (state == BackupJobState.FINISHED || state == BackupJobState.CANCELLED) { + return; + } + + // check timeout + if (System.currentTimeMillis() - createTime > timeoutMs) { + status = new Status(ErrCode.TIMEOUT, ""); + cancelInternal(); + return; + } + + // get repo if not set + if (repo == null) { + repo = catalog.getBackupHandler().getRepoMgr().getRepo(repoId); + if (repo == null) { + status = new Status(ErrCode.COMMON_ERROR, "failed to get repository: " + repoId); + cancelInternal(); + return; } - } catch (Exception e) { - errMsg = e.getMessage() == null ? "Unknown Exception" : e.getMessage(); - LOG.warn("failed to backup: " + errMsg + ", job[" + jobId + "]", e); - state = BackupJobState.CANCELLED; } + LOG.debug("run backup job: {}", this); + + // run job base on current state + switch (state) { + case PENDING: + prepareAndSendSnapshotTask(); + break; + case SNAPSHOTING: + waitingAllSnapshotsFinished(); + break; + case UPLOAD_SNAPSHOT: + uploadSnapshot(); + break; + case UPLOADING: + waitingAllUploadingFinished(); + break; + case SAVE_META: + saveMetaInfo(); + break; + case UPLOAD_INFO: + uploadMetaAndJobInfoFile(); + break; + default: + break; + } + + if (!status.ok()) { + cancelInternal(); + } + } + + // cancel by user + @Override + public synchronized Status cancel() { + if (isDone()) { + return new Status(ErrCode.COMMON_ERROR, + "Job with label " + label + " can not be cancelled. state: " + state); + } + + status = new Status(ErrCode.COMMON_ERROR, "user cancelled"); + cancelInternal(); + return Status.OK; + } + + @Override + public synchronized boolean isDone() { if (state == BackupJobState.FINISHED || state == BackupJobState.CANCELLED) { - end(Catalog.getInstance(), false); + return true; } + return false; } - private void saveMetaAndMakeSnapshot() throws DdlException, IOException { - Database db = Catalog.getInstance().getDb(dbId); + private void prepareAndSendSnapshotTask() { + Database db = catalog.getDb(dbId); if (db == null) { - throw new DdlException("[" + getDbName() + "] does not exist"); - } - - try { - pathBuilder = PathBuilder.createPathBuilder(getLocalDirName()); - } catch (IOException e) { - pathBuilder = null; - throw e; - } - - // file path -> writable objs - Map> pathToWritables = Maps.newHashMap(); - // 1. get meta - getMeta(db, pathToWritables); - - // 2. write meta - // IO ops should be done outside db.lock - try { - writeMeta(pathToWritables); - } catch (IOException e) { - errMsg = e.getMessage(); - state = BackupJobState.CANCELLED; + status = new Status(ErrCode.NOT_FOUND, "database " + dbId + " does not exist"); return; } - metaSavedTime = System.currentTimeMillis(); - LOG.info("save meta finished. path: {}, job: {}", pathBuilder.getRoot().getFullPath(), jobId); - - // 3. send snapshot tasks - snapshot(db); - } - - private void getMeta(Database db, Map> pathToWritables) throws DdlException { - db.readLock(); - try { - for (long tableId : tableIdToPartitionIds.keySet()) { - Table table = db.getTable(tableId); - if (table == null) { - throw new DdlException("table[" + tableId + "] does not exist"); - } - - // 1. get table meta - getTableMeta(db.getFullName(), table, pathToWritables); - - if (table.getType() != TableType.OLAP) { - // this is not a OLAP table. just save table meta - continue; - } - - OlapTable olapTable = (OlapTable) table; - - // 2. get rollup meta - // 2.1 check all indices exist - for (Long indexId : tableIdToIndexIds.get(tableId)) { - if (olapTable.getIndexNameById(indexId) == null) { - errMsg = "Index[" + indexId + "] does not exist"; - state = BackupJobState.CANCELLED; - return; - } - } - getRollupMeta(db.getFullName(), olapTable, pathToWritables); - - // 3. save partition meta - Collection partitionIds = tableIdToPartitionIds.get(tableId); - PartitionInfo partitionInfo = olapTable.getPartitionInfo(); - if (partitionInfo.getType() == PartitionType.RANGE) { - RangePartitionInfo rangePartitionInfo = (RangePartitionInfo) partitionInfo; - List>> rangeMap = rangePartitionInfo.getSortedRangeMap(); - for (Map.Entry> entry : rangeMap) { - long partitionId = entry.getKey(); - if (!partitionIds.contains(partitionId)) { - continue; - } - - Partition partition = olapTable.getPartition(partitionId); - if (partition == null) { - throw new DdlException("partition[" + partitionId + "] does not exist"); - } - getPartitionMeta(db.getFullName(), olapTable, partition.getName(), pathToWritables); - - // save version info - partitionIdToVersionInfo.put(partitionId, - new Pair(partition.getCommittedVersion(), - partition.getCommittedVersionHash())); - } - } else { - Preconditions.checkState(partitionIds.size() == 1); - for (Long partitionId : partitionIds) { - Partition partition = olapTable.getPartition(partitionId); - // save version info - partitionIdToVersionInfo.put(partitionId, - new Pair(partition.getCommittedVersion(), - partition.getCommittedVersionHash())); - } - } - } // end for tables - - // get last finished load job and delele job label - Load load = Catalog.getInstance().getLoadInstance(); - LoadJob lastestLoadJob = load.getLastestFinishedLoadJob(dbId); - if (lastestLoadJob == null) { - // there is no load job, or job info has been removed - lastestLoadLabel = "N/A"; - } else { - lastestLoadLabel = lastestLoadJob.getLabel(); - } - LOG.info("get lastest load job label: {}, job: {}", lastestLoadJob, jobId); - - lastestDeleteInfo = load.getLastestFinishedDeleteInfo(dbId); - LOG.info("get lastest delete info: {}, job: {}", lastestDeleteInfo, jobId); - - LOG.info("get meta finished. job[{}]", jobId); - } finally { - db.readUnlock(); - } - } - - private void getTableMeta(String dbName, Table table, Map> pathToWritables) { - CreateTableStmt stmt = table.toCreateTableStmt(dbName); - int tableSignature = table.getSignature(BackupVersion.VERSION_1); - stmt.setTableSignature(tableSignature); - List stmts = Lists.newArrayList(stmt); - String filePath = pathBuilder.createTableStmt(dbName, table.getName()); - - Preconditions.checkState(!pathToWritables.containsKey(filePath)); - pathToWritables.put(filePath, stmts); - } - - private void getRollupMeta(String dbName, OlapTable olapTable, - Map> pathToWritables) { - Set indexIds = Sets.newHashSet(tableIdToIndexIds.get(olapTable.getId())); - if (indexIds.size() == 1) { - // only contains base index. do nothing - return; - } else { - // remove base index id - Preconditions.checkState(indexIds.size() > 1); - indexIds.remove(olapTable.getId()); - } - AlterTableStmt stmt = olapTable.toAddRollupStmt(dbName, indexIds); - String filePath = pathBuilder.addRollupStmt(dbName, olapTable.getName()); - List stmts = Lists.newArrayList(stmt); - - Preconditions.checkState(!pathToWritables.containsKey(filePath)); - pathToWritables.put(filePath, stmts); - } - - private void getPartitionMeta(String dbName, OlapTable olapTable, String partitionName, - Map> pathToWritables) { - AlterTableStmt stmt = olapTable.toAddPartitionStmt(dbName, partitionName); - String filePath = pathBuilder.addPartitionStmt(dbName, olapTable.getName(), partitionName); - List stmts = Lists.newArrayList(stmt); - - Preconditions.checkState(!pathToWritables.containsKey(filePath)); - pathToWritables.put(filePath, stmts); - } - - private void writeMeta(Map> pathToWritables) throws IOException { - // 1. write meta - for (Map.Entry> entry : pathToWritables.entrySet()) { - String filePath = entry.getKey(); - List writables = entry.getValue(); - ObjectWriter.write(filePath, writables); - } - } - - private void snapshot(Database db) throws DdlException { + // generate job id + jobId = catalog.getNextId(); AgentBatchTask batchTask = new AgentBatchTask(); - LoadBalancer loadBalancer = new LoadBalancer(1L); - long dbId = db.getId(); db.readLock(); try { - for (Map.Entry> entry : tableIdToPartitionIds.entrySet()) { - long tableId = entry.getKey(); - Set partitionIds = entry.getValue(); - - Table table = db.getTable(tableId); - if (table == null) { - throw new DdlException("table[" + tableId + "] does not exist"); + // check all backup tables again + for (TableRef tableRef : tableRefs) { + String tblName = tableRef.getName().getTbl(); + Table tbl = db.getTable(tblName); + if (tbl == null) { + status = new Status(ErrCode.NOT_FOUND, "table " + tblName + " does not exist"); + return; + } + if (tbl.getType() != TableType.OLAP) { + status = new Status(ErrCode.COMMON_ERROR, "table " + tblName + " is not OLAP table"); + return; } - if (table.getType() != TableType.OLAP) { - continue; + OlapTable olapTbl = (OlapTable) tbl; + if (tableRef.getPartitions() != null && !tableRef.getPartitions().isEmpty()) { + for (String partName : tableRef.getPartitions()) { + Partition partition = olapTbl.getPartition(partName); + if (partition == null) { + status = new Status(ErrCode.NOT_FOUND, "partition " + partName + + " does not exist in table" + tblName); + return; + } + } + } + } + + unfinishedTaskIds.clear(); + taskErrMsg.clear(); + // create snapshot tasks + for (TableRef tblRef : tableRefs) { + String tblName = tblRef.getName().getTbl(); + OlapTable tbl = (OlapTable) db.getTable(tblName); + List partitions = Lists.newArrayList(); + if (tblRef.getPartitions() == null || tblRef.getPartitions().isEmpty()) { + partitions.addAll(tbl.getPartitions()); + } else { + for (String partName : tblRef.getPartitions()) { + Partition partition = tbl.getPartition(partName); + partitions.add(partition); + } } - OlapTable olapTable = (OlapTable) table; - - for (Long partitionId : partitionIds) { - Partition partition = olapTable.getPartition(partitionId); - if (partition == null) { - throw new DdlException("partition[" + partitionId + "] does not exist"); + // snapshot partitions + for (Partition partition : partitions) { + long committedVersion = partition.getCommittedVersion(); + long committedVersionHash = partition.getCommittedVersionHash(); + List indexes = partition.getMaterializedIndices(); + for (MaterializedIndex index : indexes) { + int schemaHash = tbl.getSchemaHashByIndexId(index.getId()); + List tablets = index.getTablets(); + for (Tablet tablet : tablets) { + Replica replica = chooseReplica(tablet, committedVersion, committedVersionHash); + if (replica == null) { + status = new Status(ErrCode.COMMON_ERROR, + "faild to choose replica to make snapshot for tablet " + tablet.getId() + + ". committed version: " + committedVersion + + ", committed version hash: " + committedVersionHash); + return; + } + SnapshotTask task = new SnapshotTask(null, replica.getBackendId(), tablet.getId(), + jobId, dbId, tbl.getId(), partition.getId(), + index.getId(), tablet.getId(), + committedVersion, committedVersionHash, + schemaHash, timeoutMs, false /* not restore task */); + batchTask.addTask(task); + unfinishedTaskIds.add(tablet.getId()); + } } - Pair versionInfo = partitionIdToVersionInfo.get(partitionId); - for (Long indexId : tableIdToIndexIds.get(tableId)) { - int schemaHash = olapTable.getSchemaHashByIndexId(indexId); - MaterializedIndex index = partition.getIndex(indexId); - if (index == null) { - throw new DdlException("index[" + indexId + "] does not exist"); - } - - for (Tablet tablet : index.getTablets()) { - long tabletId = tablet.getId(); - List backendIds = Lists.newArrayList(); - for (Replica replica : tablet.getReplicas()) { - if (replica.checkVersionCatchUp(versionInfo.first, versionInfo.second)) { - backendIds.add(replica.getBackendId()); - } - } - - if (backendIds.isEmpty()) { - String msg = "tablet[" + tabletId + "] does not check up with version: " - + versionInfo.first + "-" + versionInfo.second; - // this should not happen - LOG.error(msg); - throw new DdlException(msg); - } - - long chosenBackendId = loadBalancer.chooseKey(backendIds); - SnapshotTask task = new SnapshotTask(null, chosenBackendId, jobId, dbId, tableId, - partitionId, indexId, tabletId, - versionInfo.first, versionInfo.second, - schemaHash, -1L); - LOG.debug("choose backend[{}] to make snapshot for tablet[{}]", chosenBackendId, tabletId); - batchTask.addTask(task); - unfinishedTabletIds.put(tabletId, chosenBackendId); - } // end for tablet - } // end for indices - } // end for partitions - } // end for tables + LOG.info("snapshot for partition {}, version: {}, version hash: {}", + partition.getId(), committedVersion, committedVersionHash); + } + } + // copy all related schema at this moment + List
copiedTables = Lists.newArrayList(); + for (TableRef tableRef : tableRefs) { + String tblName = tableRef.getName().getTbl(); + OlapTable tbl = (OlapTable) db.getTable(tblName); + OlapTable copiedTbl = tbl.selectiveCopy(tableRef.getPartitions()); + if (copiedTbl == null) { + status = new Status(ErrCode.COMMON_ERROR, "faild to copy table: " + tblName); + return; + } + copiedTables.add(copiedTbl); + } + backupMeta = new BackupMeta(copiedTables); } finally { db.readUnlock(); } - phasedTimeoutMs = unfinishedTabletIds.size() * SNAPSHOT_TIMEOUT_MS; - LOG.debug("estimate snapshot timeout: {}, tablet size: {}", phasedTimeoutMs, unfinishedTabletIds.size()); - - // send task + // send tasks for (AgentTask task : batchTask.getAllTasks()) { AgentTaskQueue.addTask(task); } AgentTaskExecutor.submit(batchTask); - state = BackupJobState.SNAPSHOT; - LOG.info("finish send snapshot task. job: {}", jobId); + state = BackupJobState.SNAPSHOTING; + + // DO NOT write log here, state will be reset to PENDING after FE restart. Then all snapshot tasks + // will be re-generated and be sent again + LOG.info("finished to send snapshot tasks to backend. {}", this); } - private synchronized void waitSnapshot() throws DdlException { - if (unfinishedTabletIds.isEmpty()) { + private void waitingAllSnapshotsFinished() { + if (unfinishedTaskIds.isEmpty()) { snapshotFinishedTime = System.currentTimeMillis(); - state = BackupJobState.UPLOAD; + state = BackupJobState.UPLOAD_SNAPSHOT; - Catalog.getInstance().getEditLog().logBackupFinishSnapshot(this); - LOG.info("backup job[{}] is finished making snapshot", jobId); - - return; - } else if (System.currentTimeMillis() - metaSavedTime > phasedTimeoutMs) { - // remove task in AgentTaskQueue - for (Map.Entry entry : unfinishedTabletIds.entries()) { - AgentTaskQueue.removeTask(entry.getValue(), TTaskType.MAKE_SNAPSHOT, entry.getKey()); - } - - // check timeout - String msg = "snapshot timeout. " + phasedTimeoutMs + "s."; - LOG.warn("{}. job[{}]", msg, jobId); - throw new DdlException(msg); - } else { - LOG.debug("waiting {} tablets to make snapshot", unfinishedTabletIds.size()); - } - } - - private void upload() throws IOException, DdlException, InterruptedException, ExecutionException { - LOG.debug("start upload. job[{}]", jobId); - - if (commandBuilder == null) { - String remotePropFilePath = pathBuilder.remoteProperties(); - commandBuilder = CommandBuilder.create(remotePropFilePath, remoteProperties); - } - Preconditions.checkNotNull(commandBuilder); - - // 1. send meta to remote source - if (!uploadMetaObjs()) { + // log + catalog.getEditLog().logBackupJob(this); + LOG.info("finished to make snapshots. {}", this); return; } - // 2. send upload task to be - sendUploadTasks(); + LOG.info("waiting {} tablets to make snapshot. {}", unfinishedTaskIds.size(), this); } - private boolean uploadMetaObjs() throws IOException, InterruptedException, ExecutionException { - if (future == null) { - LOG.info("begin to submit upload meta objs. job: {}", jobId); - String dest = PathBuilder.createPath(remotePath, getLabel()); - String uploadCmd = commandBuilder.uploadCmd(getLabel(), pathBuilder.getRoot().getFullPath(), dest); + private void uploadSnapshot() { + // reuse this set to save all unfinished tablets + unfinishedTaskIds.clear(); + taskErrMsg.clear(); - MetaUploadTask uploadTask = new MetaUploadTask(uploadCmd); - future = Catalog.getInstance().getBackupHandler().getAsynchronousCmdExecutor().submit(uploadTask); - return false; - } else { - return checkFuture("upload meta objs"); + // We classify the snapshot info by backend + ArrayListMultimap beToSnapshots = ArrayListMultimap.create(); + for (SnapshotInfo info : snapshotInfos.values()) { + beToSnapshots.put(info.getBeId(), info); } - } - - private synchronized void sendUploadTasks() throws DdlException { - Preconditions.checkState(unfinishedTabletIds.isEmpty()); AgentBatchTask batchTask = new AgentBatchTask(); - Database db = Catalog.getInstance().getDb(dbId); - if (db == null) { - throw new DdlException("database[" + getDbName() + "] does not exist"); - } - db.readLock(); - try { - String dbName = db.getFullName(); - for (Map.Entry> entry : tableIdToPartitionIds.entrySet()) { - long tableId = entry.getKey(); - Set partitionIds = entry.getValue(); + for (Long beId : beToSnapshots.keySet()) { + List infos = beToSnapshots.get(beId); + int totalNum = infos.size(); + // each backend allot at most 3 tasks + int batchNum = Math.min(totalNum, 3); + // each task contains several upload sub tasks + int taskNumPerBatch = Math.max(totalNum / batchNum, 1); + LOG.debug("backend {} has {} batch, total {} tasks, {}", beId, batchNum, totalNum, this); - Table table = db.getTable(tableId); - if (table == null) { - throw new DdlException("table[" + tableId + "] does not exist"); + List brokerAddrs = Lists.newArrayList(); + Status st = repo.getBrokerAddress(beId, catalog, brokerAddrs); + if (!st.ok()) { + status = st; + return; + } + Preconditions.checkState(brokerAddrs.size() == 1); + + // allot tasks + int index = 0; + for (int batch = 0; batch < batchNum; batch++) { + Map srcToDest = Maps.newHashMap(); + int currentBatchTaskNum = (batch == batchNum - 1) ? totalNum - index : taskNumPerBatch; + for (int j = 0; j < currentBatchTaskNum; j++) { + SnapshotInfo info = infos.get(index++); + String src = info.getTabletPath(); + String dest = repo.getRepoTabletPathBySnapshotInfo(label, info); + srcToDest.put(src, dest); } - - if (table.getType() != TableType.OLAP) { - continue; - } - - OlapTable olapTable = (OlapTable) table; - String tableName = olapTable.getName(); - for (Long partitionId : partitionIds) { - Partition partition = olapTable.getPartition(partitionId); - if (partition == null) { - throw new DdlException("partition[" + partitionId + "] does not exist"); - } - - String partitionName = partition.getName(); - for (Long indexId : tableIdToIndexIds.get(tableId)) { - MaterializedIndex index = partition.getIndex(indexId); - if (index == null) { - throw new DdlException("index[" + index + "] does not exist"); - } - - String indexName = olapTable.getIndexNameById(indexId); - for (Tablet tablet : index.getTablets()) { - long tabletId = tablet.getId(); - if (!tabletIdToSnapshotPath.containsKey(tabletId)) { - // this should not happend - String msg = "tablet[" + tabletId + "]'s snapshot is missing"; - LOG.error(msg); - throw new DdlException(msg); - } - - Pair snapshotInfo = tabletIdToSnapshotPath.get(tabletId); - String dest = pathBuilder.tabletRemotePath(dbName, tableName, partitionName, - indexName, tabletId, remotePath, getLabel()); - UploadTask task = new UploadTask(null, snapshotInfo.first, jobId, dbId, tableId, - partitionId, indexId, tabletId, - snapshotInfo.second, dest, - remoteProperties); - - batchTask.addTask(task); - unfinishedTabletIds.put(tabletId, snapshotInfo.first); - } // end for tablet - } // end for indices - } // end for partitions - } // end for tables - - } finally { - db.readUnlock(); + long signature = catalog.getNextId(); + UploadTask task = new UploadTask(null, beId, signature, jobId, dbId, srcToDest, + brokerAddrs.get(0), repo.getStorage().getProperties()); + batchTask.addTask(task); + unfinishedTaskIds.add(signature); + } } - // send task + // send tasks for (AgentTask task : batchTask.getAllTasks()) { AgentTaskQueue.addTask(task); } AgentTaskExecutor.submit(batchTask); state = BackupJobState.UPLOADING; - LOG.info("finish send upload task. job: {}", jobId); + + // DO NOT write log here, upload tasks will be resend after FE crashed. + LOG.info("finished to send update tasks. {}", this); } - private synchronized void waitUpload() throws DdlException { - if (unfinishedTabletIds.isEmpty()) { - LOG.info("backup job[{}] is finished upload snapshot", jobId); - uploadFinishedTime = System.currentTimeMillis(); - state = BackupJobState.FINISHING; + private void waitingAllUploadingFinished() { + if (unfinishedTaskIds.isEmpty()) { + snapshopUploadFinishedTime = System.currentTimeMillis(); + state = BackupJobState.SAVE_META; + + // log + catalog.getEditLog().logBackupJob(this); + LOG.info("finished uploading snapshots. {}", this); return; - } else { - LOG.debug("waiting {} tablets to upload snapshot", unfinishedTabletIds.size()); } + + LOG.debug("waiting {} tablets to upload snapshot. {}", unfinishedTaskIds.size(), this); } - private void finishing() throws DdlException, InterruptedException, ExecutionException, IOException { - // save manifest and upload - // manifest contain all file under {label}/ + private void saveMetaInfo() { + String createTimeStr = TimeUtils.longToTimeString(createTime, new SimpleDateFormat( + "yyyy-MM-dd-HH-mm-ss")); + // local job dir: backup/label__createtime/ + localJobDirPath = Paths.get(BackupHandler.BACKUP_ROOT_DIR.toString(), + label + "__" + createTimeStr).normalize(); - if (future == null) { - LOG.info("begin to submit save and upload manifest. job: {}", jobId); - String deleteInfo = lastestDeleteInfo == null ? "" : lastestDeleteInfo.toString(); - SaveManifestTask task = new SaveManifestTask(jobId, getLabel(), remotePath, getLocalDirName(), - lastestLoadLabel, deleteInfo, pathBuilder, commandBuilder); - future = Catalog.getInstance().getBackupHandler().getAsynchronousCmdExecutor().submit(task); - } else { - boolean finished = checkFuture("save and upload manifest"); - if (finished) { - // reset future - readableManifestPath = - PathBuilder.createPath(remotePath, getLabel(), PathBuilder.READABLE_MANIFEST_NAME); - future = null; - state = BackupJobState.FINISHED; + try { + // 1. create local job dir of this backup job + File jobDir = new File(localJobDirPath.toString()); + if (jobDir.exists()) { + // if dir exists, delete it first + Files.walk(localJobDirPath, + FileVisitOption.FOLLOW_LINKS).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); } - } - } - - public void restoreTableState(Catalog catalog) { - Database db = catalog.getDb(dbId); - if (db != null) { - db.writeLock(); - try { - for (long tableId : tableIdToPartitionIds.keySet()) { - Table table = db.getTable(tableId); - if (table != null && table.getType() == TableType.OLAP) { - if (((OlapTable) table).getState() == OlapTableState.BACKUP) { - ((OlapTable) table).setState(OlapTableState.NORMAL); - LOG.debug("set table[{}] state to NORMAL", table.getName()); - } - } - } - } finally { - db.writeUnlock(); - } - } - } - - private void removeLeftTasks() { - for (Map.Entry entry : unfinishedTabletIds.entries()) { - AgentTaskQueue.removeTask(entry.getValue(), TTaskType.MAKE_SNAPSHOT, entry.getKey()); - AgentTaskQueue.removeTask(entry.getValue(), TTaskType.UPLOAD, entry.getKey()); - } - } - - @Override - public void end(Catalog catalog, boolean isReplay) { - // 1. set table state - restoreTableState(catalog); - - if (!isReplay) { - // 2. remove agent tasks if left - removeLeftTasks(); - - if (pathBuilder == null) { - finishedTime = System.currentTimeMillis(); - Catalog.getInstance().getEditLog().logBackupFinish(this); - LOG.info("finished end job[{}]. state: {}", jobId, state.name()); + if (!jobDir.mkdir()) { + status = new Status(ErrCode.COMMON_ERROR, "Failed to create tmp dir: " + localJobDirPath); return; } - // 3. remove local file - String labelDir = pathBuilder.getRoot().getFullPath(); - Util.deleteDirectory(new File(labelDir)); - LOG.debug("delete local dir: {}", labelDir); - - // 4. release snapshot - synchronized (tabletIdToSnapshotPath) { - AgentBatchTask batchTask = new AgentBatchTask(); - for (Long tabletId : tabletIdToSnapshotPath.keySet()) { - long backendId = tabletIdToSnapshotPath.get(tabletId).first; - String snapshotPath = tabletIdToSnapshotPath.get(tabletId).second; - ReleaseSnapshotTask task = new ReleaseSnapshotTask(null, backendId, dbId, tabletId, snapshotPath); - batchTask.addTask(task); - } - // no need to add to AgentTaskQueue - AgentTaskExecutor.submit(batchTask); + // 2. save meta info file + File metaInfoFile = new File(jobDir, Repository.FILE_META_INFO); + if (!metaInfoFile.createNewFile()) { + status = new Status(ErrCode.COMMON_ERROR, + "Failed to create meta info file: " + metaInfoFile.toString()); + return; } + backupMeta.writeToFile(metaInfoFile); + localMetaInfoFilePath = metaInfoFile.getAbsolutePath(); - finishedTime = System.currentTimeMillis(); - - Catalog.getInstance().getEditLog().logBackupFinish(this); + // 3. save job info file + jobInfo = BackupJobInfo.fromCatalog(createTime, label, dbName, dbId, backupMeta.getTables().values(), + snapshotInfos); + LOG.debug("job info: {}. {}", jobInfo, this); + File jobInfoFile = new File(jobDir, Repository.PREFIX_JOB_INFO + createTimeStr); + if (!jobInfoFile.createNewFile()) { + status = new Status(ErrCode.COMMON_ERROR, "Failed to create job info file: " + jobInfoFile.toString()); + return; + } + jobInfo.writeToFile(jobInfoFile); + localJobInfoFilePath = jobInfoFile.getAbsolutePath(); + } catch (Exception e) { + status = new Status(ErrCode.COMMON_ERROR, "failed to save meta info and job info file: " + e.getMessage()); + return; } - clearJob(); + state = BackupJobState.UPLOAD_INFO; - LOG.info("finished end job[{}]. state: {}, replay: {}", jobId, state.name(), isReplay); + // meta info and job info has been saved to local file, this can be cleaned to reduce log size + backupMeta = null; + jobInfo = null; + snapshotInfos.clear(); + + // log + catalog.getEditLog().logBackupJob(this); + LOG.info("finished to save meta the backup job info file to local.[{}], [{}] {}", + localMetaInfoFilePath, localJobInfoFilePath, this); } - @Override - protected void clearJob() { - tableIdToPartitionIds = null; - tableIdToIndexIds = null; - partitionIdToVersionInfo = null; - tabletIdToSnapshotPath = null; + private void uploadMetaAndJobInfoFile() { + String remoteMetaInfoFile = repo.assembleMetaInfoFilePath(label); + if (!uploadFile(localMetaInfoFilePath, remoteMetaInfoFile)) { + return; + } - unfinishedTabletIds = null; - remoteProperties = null; - pathBuilder = null; - commandBuilder = null; + String remoteJobInfoFile = repo.assembleJobInfoFilePath(label, createTime); + if (!uploadFile(localJobInfoFilePath, remoteJobInfoFile)) { + return; + } + + finishedTime = System.currentTimeMillis(); + state = BackupJobState.FINISHED; + + // log + catalog.getEditLog().logBackupJob(this); + LOG.info("job is finished. {}", this); + } + + private boolean uploadFile(String localFilePath, String remoteFilePath) { + if (!validateLocalFile(localFilePath)) { + return false; + } + + status = repo.upload(localFilePath, remoteFilePath); + if (!status.ok()) { + return false; + } + return true; + } + + private boolean validateLocalFile(String filePath) { + File file = new File(filePath); + if (!file.exists() || !file.canRead()) { + status = new Status(ErrCode.COMMON_ERROR, "file is invalid: " + filePath); + return false; + } + return true; + } + + /* + * Choose a replica order by replica id. + * This is to expect to choose the same replica at each backup job. + */ + private Replica chooseReplica(Tablet tablet, long committedVersion, long committedVersionHash) { + List replicaIds = Lists.newArrayList(); + for (Replica replica : tablet.getReplicas()) { + replicaIds.add(replica.getId()); + } + + Collections.sort(replicaIds); + for (Long replicaId : replicaIds) { + Replica replica = tablet.getReplicaById(replicaId); + if (replica.getVersion() > committedVersion + || (replica.getVersion() == committedVersion && replica.getVersionHash()==committedVersionHash)) { + return replica; + } + } + return null; + } + + private void cancelInternal() { + // We need to clean the residual due to current state + switch (state) { + case SNAPSHOTING: + // remove all snapshot tasks in AgentTaskQueue + for (Long taskId : unfinishedTaskIds) { + AgentTaskQueue.removeTaskOfType(TTaskType.MAKE_SNAPSHOT, taskId); + } + break; + case UPLOADING: + // remove all upload tasks in AgentTaskQueue + for (Long taskId : unfinishedTaskIds) { + AgentTaskQueue.removeTaskOfType(TTaskType.UPLOAD, taskId); + } + break; + default: + break; + } + + // clean the backup job dir + if (localJobDirPath != null) { + try { + File jobDir = new File(localJobDirPath.toString()); + if (jobDir.exists()) { + Files.walk(localJobDirPath, + FileVisitOption.FOLLOW_LINKS).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); + } + } catch (Exception e) { + LOG.warn("failed to clean the backup job dir: " + localJobDirPath.toString()); + } + } + + BackupJobState curState = state; + finishedTime = System.currentTimeMillis(); + state = BackupJobState.CANCELLED; + + // log + catalog.getEditLog().logBackupJob(this); + LOG.info("finished to cancel backup job. current state: {}. {}", curState.name(), this); + } + + public List getInfo() { + List info = Lists.newArrayList(); + info.add(String.valueOf(jobId)); + info.add(label); + info.add(dbName); + info.add(state.name()); + info.add(getBackupObjs()); + info.add(TimeUtils.longToTimeString(createTime)); + info.add(TimeUtils.longToTimeString(snapshotFinishedTime)); + info.add(TimeUtils.longToTimeString(snapshopUploadFinishedTime)); + info.add(TimeUtils.longToTimeString(finishedTime)); + info.add(Joiner.on(", ").join(unfinishedTaskIds)); + List msgs = taskErrMsg.entrySet().stream().map(n -> "[" + n.getKey() + ": " + n.getValue() + + "]").collect(Collectors.toList()); + info.add(Joiner.on(", ").join(msgs)); + info.add(status.toString()); + info.add(String.valueOf(timeoutMs / 1000)); + return info; + } + + private String getBackupObjs() { + List list = tableRefs.stream().map(n -> "[" + n.toString() + "]").collect(Collectors.toList()); + return Joiner.on(", ").join(list); + } + + public static BackupJob read(DataInput in) throws IOException { + BackupJob job = new BackupJob(); + job.readFields(in); + return job; } @Override public void write(DataOutput out) throws IOException { super.write(out); + // table refs + out.writeInt(tableRefs.size()); + for (TableRef tblRef : tableRefs) { + tblRef.write(out); + } + + // state Text.writeString(out, state.name()); - Text.writeString(out, lastestLoadLabel); - if (lastestDeleteInfo == null) { - out.writeBoolean(false); - } else { - out.writeBoolean(true); - lastestDeleteInfo.write(out); - } - - if (tableIdToPartitionIds == null) { - out.writeBoolean(false); - } else { - out.writeBoolean(true); - int size = tableIdToPartitionIds.size(); - out.writeInt(size); - for (Map.Entry> entry : tableIdToPartitionIds.entrySet()) { - out.writeLong(entry.getKey()); - size = entry.getValue().size(); - out.writeInt(size); - for (Long partitionId : entry.getValue()) { - out.writeLong(partitionId); - } - } - } - - if (tableIdToIndexIds == null) { - out.writeBoolean(false); - } else { - out.writeBoolean(true); - Collection> entries = tableIdToIndexIds.entries(); - int size = entries.size(); - out.writeInt(size); - for (Map.Entry entry : entries) { - out.writeLong(entry.getKey()); - out.writeLong(entry.getValue()); - } - } - - if (partitionIdToVersionInfo == null) { - out.writeBoolean(false); - } else { - out.writeBoolean(true); - int size = partitionIdToVersionInfo.size(); - out.writeInt(size); - for (Map.Entry> entry : partitionIdToVersionInfo.entrySet()) { - out.writeLong(entry.getKey()); - Pair pair = entry.getValue(); - out.writeLong(pair.first); - out.writeLong(pair.second); - } - } - - if (tabletIdToSnapshotPath == null) { - out.writeBoolean(false); - } else { - out.writeBoolean(true); - int size = tabletIdToSnapshotPath.size(); - out.writeInt(size); - for (Map.Entry> entry : tabletIdToSnapshotPath.entrySet()) { - out.writeLong(entry.getKey()); - Pair pair = entry.getValue(); - out.writeLong(pair.first); - Text.writeString(out, pair.second); - } - } - - out.writeLong(metaSavedTime); + // times out.writeLong(snapshotFinishedTime); - out.writeLong(uploadFinishedTime); - out.writeLong(phasedTimeoutMs); + out.writeLong(snapshopUploadFinishedTime); - Text.writeString(out, readableManifestPath); - - if (pathBuilder == null) { - out.writeBoolean(false); - } else { - out.writeBoolean(true); - pathBuilder.write(out); + // snapshot info + out.writeInt(snapshotInfos.size()); + for (SnapshotInfo info : snapshotInfos.values()) { + info.write(out); } - if (commandBuilder == null) { + // backup meta + if (backupMeta == null) { out.writeBoolean(false); } else { out.writeBoolean(true); - commandBuilder.write(out); + backupMeta.write(out); + } + + // No need to persist job info. It is generated then write to file + + // metaInfoFilePath and jobInfoFilePath + if (Strings.isNullOrEmpty(localMetaInfoFilePath)) { + out.writeBoolean(false); + } else { + out.writeBoolean(true); + Text.writeString(out, localMetaInfoFilePath); + } + + if (Strings.isNullOrEmpty(localJobInfoFilePath)) { + out.writeBoolean(false); + } else { + out.writeBoolean(true); + Text.writeString(out, localJobInfoFilePath); } } @@ -877,73 +753,51 @@ public class BackupJob extends AbstractBackupJob { public void readFields(DataInput in) throws IOException { super.readFields(in); + // table refs + int size = in.readInt(); + tableRefs = Lists.newArrayList(); + for (int i = 0; i < size; i++) { + TableRef tblRef = new TableRef(); + tblRef.readFields(in); + tableRefs.add(tblRef); + } + state = BackupJobState.valueOf(Text.readString(in)); - lastestLoadLabel = Text.readString(in); - if (in.readBoolean()) { - lastestDeleteInfo = new DeleteInfo(); - lastestDeleteInfo.readFields(in); - } - - if (in.readBoolean()) { - int size = in.readInt(); - for (int i = 0; i < size; i++) { - long tableId = in.readLong(); - Set partitionIds = Sets.newHashSet(); - tableIdToPartitionIds.put(tableId, partitionIds); - int count = in.readInt(); - for (int j = 0; j < count; j++) { - long partitionId = in.readLong(); - partitionIds.add(partitionId); - } - } - } - - if (in.readBoolean()) { - int size = in.readInt(); - for (int i = 0; i < size; i++) { - long tableId = in.readLong(); - long indexId = in.readLong(); - tableIdToIndexIds.put(tableId, indexId); - } - } - - if (in.readBoolean()) { - int size = in.readInt(); - for (int i = 0; i < size; i++) { - long partitionId = in.readLong(); - long version = in.readLong(); - long versionHash = in.readLong(); - partitionIdToVersionInfo.put(partitionId, new Pair(version, versionHash)); - } - } - - if (in.readBoolean()) { - int size = in.readInt(); - for (int i = 0; i < size; i++) { - long tabletId = in.readLong(); - long backendId = in.readLong(); - String path = Text.readString(in); - tabletIdToSnapshotPath.put(tabletId, new Pair(backendId, path)); - } - } - - metaSavedTime = in.readLong(); + // times snapshotFinishedTime = in.readLong(); - uploadFinishedTime = in.readLong(); - phasedTimeoutMs = in.readLong(); + snapshopUploadFinishedTime = in.readLong(); - readableManifestPath = Text.readString(in); + // snapshot info + size = in.readInt(); + for (int i = 0; i < size; i++) { + SnapshotInfo snapshotInfo = new SnapshotInfo(); + snapshotInfo.readFields(in); + snapshotInfos.put(snapshotInfo.getTabletId(), snapshotInfo); + } + // backup meta if (in.readBoolean()) { - pathBuilder = new PathBuilder(); - pathBuilder.readFields(in); + backupMeta = BackupMeta.read(in); + } + + // No need to persist job info. It is generated then write to file + + // metaInfoFilePath and jobInfoFilePath + if (in.readBoolean()) { + localMetaInfoFilePath = Text.readString(in); } if (in.readBoolean()) { - commandBuilder = new CommandBuilder(); - commandBuilder.readFields(in); + localJobInfoFilePath = Text.readString(in); } + } + @Override + public String toString() { + StringBuilder sb = new StringBuilder(super.toString()); + sb.append(", state: ").append(state.name()); + return sb.toString(); } } + diff --git a/fe/src/com/baidu/palo/backup/BackupJobInfo.java b/fe/src/com/baidu/palo/backup/BackupJobInfo.java new file mode 100644 index 0000000000..87af8da69b --- /dev/null +++ b/fe/src/com/baidu/palo/backup/BackupJobInfo.java @@ -0,0 +1,481 @@ +// Modifications copyright (C) 2018, Baidu.com, Inc. +// Copyright 2018 The Apache Software Foundation + +// 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. + +package com.baidu.palo.backup; + +import com.baidu.palo.backup.RestoreFileMapping.IdChain; +import com.baidu.palo.catalog.MaterializedIndex; +import com.baidu.palo.catalog.OlapTable; +import com.baidu.palo.catalog.Partition; +import com.baidu.palo.catalog.Table; +import com.baidu.palo.catalog.Tablet; +import com.baidu.palo.common.io.Text; +import com.baidu.palo.common.io.Writable; + +import com.google.common.base.Joiner; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/* + * This is a memory structure mapping the job info file in repository. + * It contains all content of a job info file. + * It also be used to save the info of a restore job, such as alias of table and meta info file path + */ +public class BackupJobInfo implements Writable { + private static final Logger LOG = LogManager.getLogger(BackupJobInfo.class); + + public String name; + public String dbName; + public long dbId; + public long backupTime; + public Map tables = Maps.newHashMap(); + public boolean success; + + // This map is used to save the table alias mapping info when processing a restore job. + // origin -> alias + public Map tblAlias = Maps.newHashMap(); + + public boolean containsTbl(String tblName) { + return tables.containsKey(tblName); + } + + public BackupTableInfo getTableInfo(String tblName) { + return tables.get(tblName); + } + + public void retainTables(Set tblNames) { + Iterator> iter = tables.entrySet().iterator(); + while (iter.hasNext()) { + if (!tblNames.contains(iter.next().getKey())) { + iter.remove(); + } + } + } + + public void setAlias(String orig, String alias) { + tblAlias.put(orig, alias); + } + + public String getAliasByOriginNameIfSet(String orig) { + return tblAlias.containsKey(orig) ? tblAlias.get(orig) : orig; + } + + public String getOrginNameByAlias(String alias) { + for (Map.Entry entry : tblAlias.entrySet()) { + if (entry.getValue().equals(alias)) { + return entry.getKey(); + } + } + return alias; + } + + public static class BackupTableInfo { + public String name; + public long id; + public Map partitions = Maps.newHashMap(); + + public boolean containsPart(String partName) { + return partitions.containsKey(partName); + } + + public BackupPartitionInfo getPartInfo(String partName) { + return partitions.get(partName); + } + + public void retainPartitions(Collection partNames) { + if (partNames == null || partNames.isEmpty()) { + // retain all + return; + } + Iterator> iter = partitions.entrySet().iterator(); + while (iter.hasNext()) { + if (!partNames.contains(iter.next().getKey())) { + iter.remove(); + } + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("name: ").append(name).append(", id: ").append(id); + sb.append(", partitions: [").append(Joiner.on(", ").join(partitions.keySet())).append("]"); + return sb.toString(); + } + } + + public static class BackupPartitionInfo { + public String name; + public long id; + public long version; + public long versionHash; + public Map indexes = Maps.newHashMap(); + + public BackupIndexInfo getIdx(String idxName) { + return indexes.get(idxName); + } + } + + public static class BackupIndexInfo { + public String name; + public long id; + public int schemaHash; + public List tablets = Lists.newArrayList(); + + public BackupTabletInfo getTablet(long tabletId) { + for (BackupTabletInfo backupTabletInfo : tablets) { + if (backupTabletInfo.id == tabletId) { + return backupTabletInfo; + } + } + return null; + } + } + + public static class BackupTabletInfo { + public long id; + public List files = Lists.newArrayList(); + } + + // eg: __db_10001/__tbl_10002/__part_10003/__idx_10002/__10004 + public String getFilePath(String db, String tbl, String part, String idx, long tabletId) { + if (!db.equalsIgnoreCase(dbName)) { + LOG.debug("db name does not equal: {}-{}", dbName, db); + return null; + } + + BackupTableInfo tblInfo = tables.get(tbl); + if (tblInfo == null) { + LOG.debug("tbl {} does not exist", tbl); + return null; + } + + BackupPartitionInfo partInfo = tblInfo.getPartInfo(part); + if (partInfo == null) { + LOG.debug("part {} does not exist", part); + return null; + } + + BackupIndexInfo idxInfo = partInfo.getIdx(idx); + if (idxInfo == null) { + LOG.debug("idx {} does not exist", idx); + return null; + } + + List pathSeg = Lists.newArrayList(); + pathSeg.add(Repository.PREFIX_DB + dbId); + pathSeg.add(Repository.PREFIX_TBL + tblInfo.id); + pathSeg.add(Repository.PREFIX_PART + partInfo.id); + pathSeg.add(Repository.PREFIX_IDX + idxInfo.id); + pathSeg.add(Repository.PREFIX_COMMON + tabletId); + + return Joiner.on("/").join(pathSeg); + } + + // eg: __db_10001/__tbl_10002/__part_10003/__idx_10002/__10004 + public String getFilePath(IdChain ids) { + List pathSeg = Lists.newArrayList(); + pathSeg.add(Repository.PREFIX_DB + dbId); + pathSeg.add(Repository.PREFIX_TBL + ids.getTblId()); + pathSeg.add(Repository.PREFIX_PART + ids.getPartId()); + pathSeg.add(Repository.PREFIX_IDX + ids.getIdxId()); + pathSeg.add(Repository.PREFIX_COMMON + ids.getTabletId()); + + return Joiner.on("/").join(pathSeg); + } + + public static BackupJobInfo fromCatalog(long backupTime, String label, String dbName, long dbId, + Collection
tbls, Map snapshotInfos) { + + BackupJobInfo jobInfo = new BackupJobInfo(); + jobInfo.backupTime = backupTime; + jobInfo.name = label; + jobInfo.dbName = dbName; + jobInfo.dbId = dbId; + jobInfo.success = true; + + // tbls + for (Table tbl : tbls) { + OlapTable olapTbl = (OlapTable) tbl; + BackupTableInfo tableInfo = new BackupTableInfo(); + tableInfo.id = tbl.getId(); + tableInfo.name = tbl.getName(); + jobInfo.tables.put(tableInfo.name, tableInfo); + // partitions + for (Partition partition : olapTbl.getPartitions()) { + BackupPartitionInfo partitionInfo = new BackupPartitionInfo(); + partitionInfo.id = partition.getId(); + partitionInfo.name = partition.getName(); + partitionInfo.version = partition.getCommittedVersion(); + partitionInfo.versionHash = partition.getCommittedVersionHash(); + tableInfo.partitions.put(partitionInfo.name, partitionInfo); + // indexes + for (MaterializedIndex index : partition.getMaterializedIndices()) { + BackupIndexInfo idxInfo = new BackupIndexInfo(); + idxInfo.id = index.getId(); + idxInfo.name = olapTbl.getIndexNameById(index.getId()); + idxInfo.schemaHash = olapTbl.getSchemaHashByIndexId(index.getId()); + partitionInfo.indexes.put(idxInfo.name, idxInfo); + // tablets + for (Tablet tablet : index.getTablets()) { + BackupTabletInfo tabletInfo = new BackupTabletInfo(); + tabletInfo.id = tablet.getId(); + tabletInfo.files.addAll(snapshotInfos.get(tablet.getId()).getFiles()); + idxInfo.tablets.add(tabletInfo); + } + } + } + } + + return jobInfo; + } + + public static BackupJobInfo fromFile(String path) throws IOException { + byte[] bytes = Files.readAllBytes(Paths.get(path)); + String json = new String(bytes, StandardCharsets.UTF_8); + BackupJobInfo jobInfo = new BackupJobInfo(); + genFromJson(json, jobInfo); + return jobInfo; + } + + private static void genFromJson(String json, BackupJobInfo jobInfo) { + /* parse the json string: + * { + * "backup_time": 1522231864000, + * "name": "snapshot1", + * "database": "db1" + * "id": 10000 + * "backup_result": "succeed", + * "backup_objects": { + * "table1": { + * "partitions": { + * "partition2": { + * "indexes": { + * "rollup1": { + * "id": 10009, + * "schema_hash": 3473401 + * "tablets": { + * "10008": ["__10029_seg1.dat", "__10029_seg2.dat"], + * "10007": ["__10029_seg1.dat", "__10029_seg2.dat"] + * } + * }, + * "table1": { + * "id": 10008, + * "schema_hash": 9845021 + * "tablets": { + * "10004": ["__10027_seg1.dat", "__10027_seg2.dat"], + * "10005": ["__10028_seg1.dat", "__10028_seg2.dat"] + * } + * } + * }, + * "id": 10007 + * "version": 10 + * "version_hash": 1273047329538 + * }, + * }, + * "id": 10001 + * } + * } + * } + */ + JSONObject root = new JSONObject(json); + jobInfo.name = (String) root.get("name"); + jobInfo.dbName = (String) root.get("database"); + jobInfo.dbId = root.getLong("id"); + jobInfo.backupTime = root.getLong("backup_time"); + JSONObject backupObjs = root.getJSONObject("backup_objects"); + String[] tblNames = JSONObject.getNames(backupObjs); + for (String tblName : tblNames) { + BackupTableInfo tblInfo = new BackupTableInfo(); + tblInfo.name = tblName; + JSONObject tbl = backupObjs.getJSONObject(tblName); + tblInfo.id = tbl.getLong("id"); + JSONObject parts = tbl.getJSONObject("partitions"); + String[] partsNames = JSONObject.getNames(parts); + for (String partName : partsNames) { + BackupPartitionInfo partInfo = new BackupPartitionInfo(); + partInfo.name = partName; + JSONObject part = parts.getJSONObject(partName); + partInfo.id = part.getLong("id"); + partInfo.version = part.getLong("version"); + partInfo.versionHash = part.getLong("version_hash"); + JSONObject indexes = part.getJSONObject("indexes"); + String[] indexNames = JSONObject.getNames(indexes); + for (String idxName : indexNames) { + BackupIndexInfo indexInfo = new BackupIndexInfo(); + indexInfo.name = idxName; + JSONObject idx = indexes.getJSONObject(idxName); + indexInfo.id = idx.getLong("id"); + indexInfo.schemaHash = idx.getInt("schema_hash"); + JSONObject tablets = idx.getJSONObject("tablets"); + String[] tabletIds = JSONObject.getNames(tablets); + for (String tabletId : tabletIds) { + BackupTabletInfo tabletInfo = new BackupTabletInfo(); + tabletInfo.id = Long.valueOf(tabletId); + JSONArray files = tablets.getJSONArray(tabletId); + for (Object object : files) { + tabletInfo.files.add((String) object); + } + indexInfo.tablets.add(tabletInfo); + } + partInfo.indexes.put(indexInfo.name, indexInfo); + } + tblInfo.partitions.put(partName, partInfo); + } + jobInfo.tables.put(tblName, tblInfo); + } + + String result = root.getString("backup_result"); + if (result.equals("succeed")) { + jobInfo.success = true; + } else { + jobInfo.success = false; + } + } + + public void writeToFile(File jobInfoFile) throws FileNotFoundException { + PrintWriter printWriter = new PrintWriter(jobInfoFile); + try { + printWriter.print(toJson().toString()); + printWriter.flush(); + } finally { + printWriter.close(); + } + } + + public JSONObject toJson() { + JSONObject root = new JSONObject(); + root.put("name", name); + root.put("database", dbName); + root.put("id", dbId); + root.put("backup_time", backupTime); + JSONObject backupObj = new JSONObject(); + root.put("backup_objects", backupObj); + + for (BackupTableInfo tblInfo : tables.values()) { + JSONObject tbl = new JSONObject(); + tbl.put("id", tblInfo.id); + JSONObject parts = new JSONObject(); + tbl.put("partitions", parts); + for (BackupPartitionInfo partInfo : tblInfo.partitions.values()) { + JSONObject part = new JSONObject(); + part.put("id", partInfo.id); + part.put("version", partInfo.version); + part.put("version_hash", partInfo.versionHash); + JSONObject indexes = new JSONObject(); + part.put("indexes", indexes); + for (BackupIndexInfo idxInfo : partInfo.indexes.values()) { + JSONObject idx = new JSONObject(); + idx.put("id", idxInfo.id); + idx.put("schema_hash", idxInfo.schemaHash); + JSONObject tablets = new JSONObject(); + idx.put("tablets", tablets); + for (BackupTabletInfo tabletInfo : idxInfo.tablets) { + JSONArray files = new JSONArray(); + tablets.put(String.valueOf(tabletInfo.id), files); + for (String fileName : tabletInfo.files) { + files.put(fileName); + } + } + indexes.put(idxInfo.name, idx); + } + parts.put(partInfo.name, part); + } + backupObj.put(tblInfo.name, tbl); + } + + root.put("backup_result", "succeed"); + return root; + } + + public String toString(int indentFactor) { + return toJson().toString(indentFactor); + } + + public String getInfo() { + List objs = Lists.newArrayList(); + for (BackupTableInfo tblInfo : tables.values()) { + StringBuilder sb = new StringBuilder(); + sb.append(tblInfo.name); + List partNames = tblInfo.partitions.values().stream() + .filter(n -> !n.name.equals(tblInfo.name)).map(n -> n.name).collect(Collectors.toList()); + if (!partNames.isEmpty()) { + sb.append(" PARTITIONS [").append(Joiner.on(", ").join(partNames)).append("]"); + } + objs.add(sb.toString()); + } + return Joiner.on(", ").join(objs); + } + + public static BackupJobInfo read(DataInput in) throws IOException { + BackupJobInfo jobInfo = new BackupJobInfo(); + jobInfo.readFields(in); + return jobInfo; + } + + @Override + public void write(DataOutput out) throws IOException { + Text.writeString(out, toJson().toString()); + out.writeInt(tblAlias.size()); + for (Map.Entry entry : tblAlias.entrySet()) { + Text.writeString(out, entry.getKey()); + Text.writeString(out, entry.getValue()); + } + } + + @Override + public void readFields(DataInput in) throws IOException { + String json = Text.readString(in); + genFromJson(json, this); + int size = in.readInt(); + for (int i = 0; i < size; i++) { + String tbl = Text.readString(in); + String alias = Text.readString(in); + tblAlias.put(tbl, alias); + } + } + + @Override + public String toString() { + return toJson().toString(); + } +} + diff --git a/fe/src/com/baidu/palo/backup/BackupJob_D.java b/fe/src/com/baidu/palo/backup/BackupJob_D.java new file mode 100644 index 0000000000..c1b5b4a7f6 --- /dev/null +++ b/fe/src/com/baidu/palo/backup/BackupJob_D.java @@ -0,0 +1,944 @@ +// Copyright (c) 2017, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +package com.baidu.palo.backup; + +import com.baidu.palo.analysis.AlterTableStmt; +import com.baidu.palo.analysis.CreateTableStmt; +import com.baidu.palo.analysis.LabelName; +import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.catalog.Database; +import com.baidu.palo.catalog.MaterializedIndex; +import com.baidu.palo.catalog.OlapTable; +import com.baidu.palo.catalog.OlapTable.OlapTableState; +import com.baidu.palo.catalog.Partition; +import com.baidu.palo.catalog.PartitionInfo; +import com.baidu.palo.catalog.PartitionKey; +import com.baidu.palo.catalog.PartitionType; +import com.baidu.palo.catalog.RangePartitionInfo; +import com.baidu.palo.catalog.Replica; +import com.baidu.palo.catalog.Table; +import com.baidu.palo.catalog.Table.TableType; +import com.baidu.palo.catalog.Tablet; +import com.baidu.palo.common.DdlException; +import com.baidu.palo.common.Pair; +import com.baidu.palo.common.io.Text; +import com.baidu.palo.common.io.Writable; +import com.baidu.palo.common.util.LoadBalancer; +import com.baidu.palo.common.util.TimeUtils; +import com.baidu.palo.common.util.Util; +import com.baidu.palo.load.DeleteInfo; +import com.baidu.palo.load.Load; +import com.baidu.palo.load.LoadJob; +import com.baidu.palo.task.AgentBatchTask; +import com.baidu.palo.task.AgentTask; +import com.baidu.palo.task.AgentTaskExecutor; +import com.baidu.palo.task.AgentTaskQueue; +import com.baidu.palo.task.ReleaseSnapshotTask; +import com.baidu.palo.task.SnapshotTask; +import com.baidu.palo.thrift.TTaskType; + +import com.google.common.base.Preconditions; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.collect.Range; +import com.google.common.collect.Sets; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutionException; + +@Deprecated +public class BackupJob_D extends AbstractBackupJob_D { + private static final Logger LOG = LogManager.getLogger(BackupJob_D.class); + + private static final long SNAPSHOT_TIMEOUT_MS = 2000; // 1s for one tablet + + public enum BackupJobState { + PENDING, + SNAPSHOT, + UPLOAD, + UPLOADING, + FINISHING, + FINISHED, + CANCELLED + } + + private BackupJobState state; + + private String lastestLoadLabel; + private DeleteInfo lastestDeleteInfo; + + // all partitions need to be backuped + private Map> tableIdToPartitionIds; + private Multimap tableIdToIndexIds; + // partition id -> (version, version hash) + private Map> partitionIdToVersionInfo; + + private Map> tabletIdToSnapshotPath; + + private long metaSavedTime; + private long snapshotFinishedTime; + private long uploadFinishedTime; + + private long phasedTimeoutMs; + + private String readableManifestPath; + + public BackupJob_D() { + super(); + tableIdToPartitionIds = Maps.newHashMap(); + tableIdToIndexIds = HashMultimap.create(); + partitionIdToVersionInfo = Maps.newHashMap(); + tabletIdToSnapshotPath = Maps.newHashMap(); + } + + public BackupJob_D(long jobId, long dbId, LabelName labelName, String backupPath, + Map remoteProperties) { + super(jobId, dbId, labelName, backupPath, remoteProperties); + this.state = BackupJobState.PENDING; + + tableIdToPartitionIds = Maps.newHashMap(); + tableIdToIndexIds = HashMultimap.create(); + partitionIdToVersionInfo = Maps.newHashMap(); + + tabletIdToSnapshotPath = Maps.newHashMap(); + + metaSavedTime = -1; + snapshotFinishedTime = -1; + uploadFinishedTime = -1; + phasedTimeoutMs = -1; + + lastestLoadLabel = "N/A"; + readableManifestPath = ""; + } + + public void setState(BackupJobState state) { + this.state = state; + } + + public BackupJobState getState() { + return state; + } + + public String getLatestLoadLabel() { + return lastestLoadLabel; + } + + public DeleteInfo getLastestDeleteInfo() { + return lastestDeleteInfo; + } + + public PathBuilder getPathBuilder() { + return pathBuilder; + } + + public long getMetaSavedTimeMs() { + return metaSavedTime; + } + + public long getSnapshotFinishedTimeMs() { + return snapshotFinishedTime; + } + + public long getUploadFinishedTimeMs() { + return uploadFinishedTime; + } + + public String getReadableManifestPath() { + return readableManifestPath; + } + + public Map> getTableIdToPartitionIds() { + return tableIdToPartitionIds; + } + + public void addPartitionId(long tableId, long partitionId) { + Set partitionIds = tableIdToPartitionIds.get(tableId); + if (partitionIds == null) { + partitionIds = Sets.newHashSet(); + tableIdToPartitionIds.put(tableId, partitionIds); + } + if (partitionId != -1L) { + partitionIds.add(partitionId); + } + + LOG.debug("add partition[{}] from table[{}], job[{}]", partitionId, tableId, jobId); + } + + public void addIndexId(long tableId, long indexId) { + tableIdToIndexIds.put(tableId, indexId); + LOG.debug("add index[{}] from table[{}], job[{}]", indexId, tableId, jobId); + } + + public void handleFinishedSnapshot(long tabletId, long backendId, String snapshotPath) { + synchronized (unfinishedTabletIds) { + if (!unfinishedTabletIds.containsKey(tabletId)) { + LOG.warn("backup job[{}] does not contains tablet[{}]", jobId, tabletId); + return; + } + + if (unfinishedTabletIds.get(tabletId) == null + || !unfinishedTabletIds.get(tabletId).contains(backendId)) { + LOG.warn("backup job[{}] does not contains tablet[{}]'s snapshot from backend[{}]. " + + "it should from backend[{}]", + jobId, tabletId, backendId, unfinishedTabletIds.get(tabletId)); + return; + } + unfinishedTabletIds.remove(tabletId, backendId); + } + + synchronized (tabletIdToSnapshotPath) { + tabletIdToSnapshotPath.put(tabletId, new Pair(backendId, snapshotPath)); + } + LOG.debug("finished add tablet[{}] from backend[{}]. snapshot path: {}", tabletId, backendId, snapshotPath); + } + + public void handleFinishedUpload(long tabletId, long backendId) { + synchronized (unfinishedTabletIds) { + if (unfinishedTabletIds.remove(tabletId, backendId)) { + LOG.debug("finished upload tablet[{}] snapshot, backend[{}]", tabletId, backendId); + } + } + } + + @Override + public List getJobInfo() { + List jobInfo = Lists.newArrayList(); + jobInfo.add(jobId); + jobInfo.add(getLabel()); + jobInfo.add(state.name()); + jobInfo.add(TimeUtils.longToTimeString(createTime)); + jobInfo.add(TimeUtils.longToTimeString(metaSavedTime)); + jobInfo.add(TimeUtils.longToTimeString(snapshotFinishedTime)); + jobInfo.add(TimeUtils.longToTimeString(uploadFinishedTime)); + jobInfo.add(TimeUtils.longToTimeString(finishedTime)); + jobInfo.add(errMsg); + jobInfo.add(PathBuilder.createPath(remotePath, getLabel())); + jobInfo.add(getReadableManifestPath()); + jobInfo.add(getLeftTasksNum()); + jobInfo.add(getLatestLoadLabel()); + return jobInfo; + } + + @Override + public void runOnce() { + LOG.debug("begin to run backup job: {}, state: {}", jobId, state.name()); + try { + switch (state) { + case PENDING: + saveMetaAndMakeSnapshot(); + break; + case SNAPSHOT: + waitSnapshot(); + break; + case UPLOAD: + upload(); + break; + case UPLOADING: + waitUpload(); + break; + case FINISHING: + finishing(); + break; + default: + break; + } + } catch (Exception e) { + errMsg = e.getMessage() == null ? "Unknown Exception" : e.getMessage(); + LOG.warn("failed to backup: " + errMsg + ", job[" + jobId + "]", e); + state = BackupJobState.CANCELLED; + } + + if (state == BackupJobState.FINISHED || state == BackupJobState.CANCELLED) { + end(Catalog.getInstance(), false); + } + } + + private void saveMetaAndMakeSnapshot() throws DdlException, IOException { + Database db = Catalog.getInstance().getDb(dbId); + if (db == null) { + throw new DdlException("[" + getDbName() + "] does not exist"); + } + + try { + pathBuilder = PathBuilder.createPathBuilder(getLocalDirName()); + } catch (IOException e) { + pathBuilder = null; + throw e; + } + + // file path -> writable objs + Map> pathToWritables = Maps.newHashMap(); + // 1. get meta + getMeta(db, pathToWritables); + + // 2. write meta + // IO ops should be done outside db.lock + try { + writeMeta(pathToWritables); + } catch (IOException e) { + errMsg = e.getMessage(); + state = BackupJobState.CANCELLED; + return; + } + + metaSavedTime = System.currentTimeMillis(); + LOG.info("save meta finished. path: {}, job: {}", pathBuilder.getRoot().getFullPath(), jobId); + + // 3. send snapshot tasks + snapshot(db); + } + + private void getMeta(Database db, Map> pathToWritables) throws DdlException { + db.readLock(); + try { + for (long tableId : tableIdToPartitionIds.keySet()) { + Table table = db.getTable(tableId); + if (table == null) { + throw new DdlException("table[" + tableId + "] does not exist"); + } + + // 1. get table meta + getTableMeta(db.getFullName(), table, pathToWritables); + + if (table.getType() != TableType.OLAP) { + // this is not a OLAP table. just save table meta + continue; + } + + OlapTable olapTable = (OlapTable) table; + + // 2. get rollup meta + // 2.1 check all indices exist + for (Long indexId : tableIdToIndexIds.get(tableId)) { + if (olapTable.getIndexNameById(indexId) == null) { + errMsg = "Index[" + indexId + "] does not exist"; + state = BackupJobState.CANCELLED; + return; + } + } + getRollupMeta(db.getFullName(), olapTable, pathToWritables); + + // 3. save partition meta + Collection partitionIds = tableIdToPartitionIds.get(tableId); + PartitionInfo partitionInfo = olapTable.getPartitionInfo(); + if (partitionInfo.getType() == PartitionType.RANGE) { + RangePartitionInfo rangePartitionInfo = (RangePartitionInfo) partitionInfo; + List>> rangeMap = rangePartitionInfo.getSortedRangeMap(); + for (Map.Entry> entry : rangeMap) { + long partitionId = entry.getKey(); + if (!partitionIds.contains(partitionId)) { + continue; + } + + Partition partition = olapTable.getPartition(partitionId); + if (partition == null) { + throw new DdlException("partition[" + partitionId + "] does not exist"); + } + getPartitionMeta(db.getFullName(), olapTable, partition.getName(), pathToWritables); + + // save version info + partitionIdToVersionInfo.put(partitionId, + new Pair(partition.getCommittedVersion(), + partition.getCommittedVersionHash())); + } + } else { + Preconditions.checkState(partitionIds.size() == 1); + for (Long partitionId : partitionIds) { + Partition partition = olapTable.getPartition(partitionId); + // save version info + partitionIdToVersionInfo.put(partitionId, + new Pair(partition.getCommittedVersion(), + partition.getCommittedVersionHash())); + } + } + } // end for tables + + // get last finished load job and delele job label + Load load = Catalog.getInstance().getLoadInstance(); + LoadJob lastestLoadJob = load.getLastestFinishedLoadJob(dbId); + if (lastestLoadJob == null) { + // there is no load job, or job info has been removed + lastestLoadLabel = "N/A"; + } else { + lastestLoadLabel = lastestLoadJob.getLabel(); + } + LOG.info("get lastest load job label: {}, job: {}", lastestLoadJob, jobId); + + lastestDeleteInfo = load.getLastestFinishedDeleteInfo(dbId); + LOG.info("get lastest delete info: {}, job: {}", lastestDeleteInfo, jobId); + + LOG.info("get meta finished. job[{}]", jobId); + } finally { + db.readUnlock(); + } + } + + private void getTableMeta(String dbName, Table table, Map> pathToWritables) { + CreateTableStmt stmt = table.toCreateTableStmt(dbName); + int tableSignature = table.getSignature(BackupVersion.VERSION_1); + stmt.setTableSignature(tableSignature); + List stmts = Lists.newArrayList(stmt); + String filePath = pathBuilder.createTableStmt(dbName, table.getName()); + + Preconditions.checkState(!pathToWritables.containsKey(filePath)); + pathToWritables.put(filePath, stmts); + } + + private void getRollupMeta(String dbName, OlapTable olapTable, + Map> pathToWritables) { + Set indexIds = Sets.newHashSet(tableIdToIndexIds.get(olapTable.getId())); + if (indexIds.size() == 1) { + // only contains base index. do nothing + return; + } else { + // remove base index id + Preconditions.checkState(indexIds.size() > 1); + indexIds.remove(olapTable.getId()); + } + AlterTableStmt stmt = olapTable.toAddRollupStmt(dbName, indexIds); + String filePath = pathBuilder.addRollupStmt(dbName, olapTable.getName()); + List stmts = Lists.newArrayList(stmt); + + Preconditions.checkState(!pathToWritables.containsKey(filePath)); + pathToWritables.put(filePath, stmts); + } + + private void getPartitionMeta(String dbName, OlapTable olapTable, String partitionName, + Map> pathToWritables) { + AlterTableStmt stmt = olapTable.toAddPartitionStmt(dbName, partitionName); + String filePath = pathBuilder.addPartitionStmt(dbName, olapTable.getName(), partitionName); + List stmts = Lists.newArrayList(stmt); + + Preconditions.checkState(!pathToWritables.containsKey(filePath)); + pathToWritables.put(filePath, stmts); + } + + private void writeMeta(Map> pathToWritables) throws IOException { + // 1. write meta + for (Map.Entry> entry : pathToWritables.entrySet()) { + String filePath = entry.getKey(); + List writables = entry.getValue(); + ObjectWriter.write(filePath, writables); + } + } + + private void snapshot(Database db) throws DdlException { + AgentBatchTask batchTask = new AgentBatchTask(); + LoadBalancer loadBalancer = new LoadBalancer(1L); + long dbId = db.getId(); + db.readLock(); + try { + for (Map.Entry> entry : tableIdToPartitionIds.entrySet()) { + long tableId = entry.getKey(); + Set partitionIds = entry.getValue(); + + Table table = db.getTable(tableId); + if (table == null) { + throw new DdlException("table[" + tableId + "] does not exist"); + } + + if (table.getType() != TableType.OLAP) { + continue; + } + + OlapTable olapTable = (OlapTable) table; + + for (Long partitionId : partitionIds) { + Partition partition = olapTable.getPartition(partitionId); + if (partition == null) { + throw new DdlException("partition[" + partitionId + "] does not exist"); + } + + Pair versionInfo = partitionIdToVersionInfo.get(partitionId); + for (Long indexId : tableIdToIndexIds.get(tableId)) { + int schemaHash = olapTable.getSchemaHashByIndexId(indexId); + MaterializedIndex index = partition.getIndex(indexId); + if (index == null) { + throw new DdlException("index[" + indexId + "] does not exist"); + } + + for (Tablet tablet : index.getTablets()) { + long tabletId = tablet.getId(); + List backendIds = Lists.newArrayList(); + for (Replica replica : tablet.getReplicas()) { + if (replica.checkVersionCatchUp(versionInfo.first, versionInfo.second)) { + backendIds.add(replica.getBackendId()); + } + } + + if (backendIds.isEmpty()) { + String msg = "tablet[" + tabletId + "] does not check up with version: " + + versionInfo.first + "-" + versionInfo.second; + // this should not happen + LOG.error(msg); + throw new DdlException(msg); + } + + long chosenBackendId = loadBalancer.chooseKey(backendIds); + SnapshotTask task = new SnapshotTask(null, chosenBackendId, tabletId, jobId, dbId, tableId, + partitionId, indexId, tabletId, + versionInfo.first, versionInfo.second, + schemaHash, -1L, false); + LOG.debug("choose backend[{}] to make snapshot for tablet[{}]", chosenBackendId, tabletId); + batchTask.addTask(task); + unfinishedTabletIds.put(tabletId, chosenBackendId); + } // end for tablet + } // end for indices + } // end for partitions + } // end for tables + + } finally { + db.readUnlock(); + } + + phasedTimeoutMs = unfinishedTabletIds.size() * SNAPSHOT_TIMEOUT_MS; + LOG.debug("estimate snapshot timeout: {}, tablet size: {}", phasedTimeoutMs, unfinishedTabletIds.size()); + + // send task + for (AgentTask task : batchTask.getAllTasks()) { + AgentTaskQueue.addTask(task); + } + AgentTaskExecutor.submit(batchTask); + + state = BackupJobState.SNAPSHOT; + LOG.info("finish send snapshot task. job: {}", jobId); + } + + private synchronized void waitSnapshot() throws DdlException { + if (unfinishedTabletIds.isEmpty()) { + snapshotFinishedTime = System.currentTimeMillis(); + state = BackupJobState.UPLOAD; + + Catalog.getInstance().getEditLog().logBackupFinishSnapshot(this); + LOG.info("backup job[{}] is finished making snapshot", jobId); + + return; + } else if (System.currentTimeMillis() - metaSavedTime > phasedTimeoutMs) { + // remove task in AgentTaskQueue + for (Map.Entry entry : unfinishedTabletIds.entries()) { + AgentTaskQueue.removeTask(entry.getValue(), TTaskType.MAKE_SNAPSHOT, entry.getKey()); + } + + // check timeout + String msg = "snapshot timeout. " + phasedTimeoutMs + "s."; + LOG.warn("{}. job[{}]", msg, jobId); + throw new DdlException(msg); + } else { + LOG.debug("waiting {} tablets to make snapshot", unfinishedTabletIds.size()); + } + } + + private void upload() throws IOException, DdlException, InterruptedException, ExecutionException { + LOG.debug("start upload. job[{}]", jobId); + + if (commandBuilder == null) { + String remotePropFilePath = pathBuilder.remoteProperties(); + commandBuilder = CommandBuilder.create(remotePropFilePath, remoteProperties); + } + Preconditions.checkNotNull(commandBuilder); + + // 1. send meta to remote source + if (!uploadMetaObjs()) { + return; + } + + // 2. send upload task to be + sendUploadTasks(); + } + + private boolean uploadMetaObjs() throws IOException, InterruptedException, ExecutionException { + if (future == null) { + LOG.info("begin to submit upload meta objs. job: {}", jobId); + String dest = PathBuilder.createPath(remotePath, getLabel()); + String uploadCmd = commandBuilder.uploadCmd(getLabel(), pathBuilder.getRoot().getFullPath(), dest); + + MetaUploadTask uploadTask = new MetaUploadTask(uploadCmd); + // future = Catalog.getInstance().getBackupHandler().getAsynchronousCmdExecutor().submit(uploadTask); + return false; + } else { + return checkFuture("upload meta objs"); + } + } + + private synchronized void sendUploadTasks() throws DdlException { + Preconditions.checkState(unfinishedTabletIds.isEmpty()); + + AgentBatchTask batchTask = new AgentBatchTask(); + Database db = Catalog.getInstance().getDb(dbId); + if (db == null) { + throw new DdlException("database[" + getDbName() + "] does not exist"); + } + db.readLock(); + try { + String dbName = db.getFullName(); + for (Map.Entry> entry : tableIdToPartitionIds.entrySet()) { + long tableId = entry.getKey(); + Set partitionIds = entry.getValue(); + + Table table = db.getTable(tableId); + if (table == null) { + throw new DdlException("table[" + tableId + "] does not exist"); + } + + if (table.getType() != TableType.OLAP) { + continue; + } + + OlapTable olapTable = (OlapTable) table; + String tableName = olapTable.getName(); + for (Long partitionId : partitionIds) { + Partition partition = olapTable.getPartition(partitionId); + if (partition == null) { + throw new DdlException("partition[" + partitionId + "] does not exist"); + } + + String partitionName = partition.getName(); + for (Long indexId : tableIdToIndexIds.get(tableId)) { + MaterializedIndex index = partition.getIndex(indexId); + if (index == null) { + throw new DdlException("index[" + index + "] does not exist"); + } + + String indexName = olapTable.getIndexNameById(indexId); + for (Tablet tablet : index.getTablets()) { + long tabletId = tablet.getId(); + if (!tabletIdToSnapshotPath.containsKey(tabletId)) { + // this should not happend + String msg = "tablet[" + tabletId + "]'s snapshot is missing"; + LOG.error(msg); + throw new DdlException(msg); + } + + Pair snapshotInfo = tabletIdToSnapshotPath.get(tabletId); + String dest = pathBuilder.tabletRemotePath(dbName, tableName, partitionName, + indexName, tabletId, remotePath, getLabel()); + + unfinishedTabletIds.put(tabletId, snapshotInfo.first); + } // end for tablet + } // end for indices + } // end for partitions + } // end for tables + + } finally { + db.readUnlock(); + } + + // send task + for (AgentTask task : batchTask.getAllTasks()) { + AgentTaskQueue.addTask(task); + } + AgentTaskExecutor.submit(batchTask); + + state = BackupJobState.UPLOADING; + LOG.info("finish send upload task. job: {}", jobId); + } + + private synchronized void waitUpload() throws DdlException { + if (unfinishedTabletIds.isEmpty()) { + LOG.info("backup job[{}] is finished upload snapshot", jobId); + uploadFinishedTime = System.currentTimeMillis(); + state = BackupJobState.FINISHING; + return; + } else { + LOG.debug("waiting {} tablets to upload snapshot", unfinishedTabletIds.size()); + } + } + + private void finishing() throws DdlException, InterruptedException, ExecutionException, IOException { + // save manifest and upload + // manifest contain all file under {label}/ + + if (future == null) { + LOG.info("begin to submit save and upload manifest. job: {}", jobId); + String deleteInfo = lastestDeleteInfo == null ? "" : lastestDeleteInfo.toString(); + SaveManifestTask task = new SaveManifestTask(jobId, getLabel(), remotePath, getLocalDirName(), + lastestLoadLabel, deleteInfo, pathBuilder, commandBuilder); + // future = Catalog.getInstance().getBackupHandler().getAsynchronousCmdExecutor().submit(task); + } else { + boolean finished = checkFuture("save and upload manifest"); + if (finished) { + // reset future + readableManifestPath = + PathBuilder.createPath(remotePath, getLabel(), PathBuilder.READABLE_MANIFEST_NAME); + future = null; + state = BackupJobState.FINISHED; + } + } + } + + public void restoreTableState(Catalog catalog) { + Database db = catalog.getDb(dbId); + if (db != null) { + db.writeLock(); + try { + for (long tableId : tableIdToPartitionIds.keySet()) { + Table table = db.getTable(tableId); + if (table != null && table.getType() == TableType.OLAP) { + if (((OlapTable) table).getState() == OlapTableState.BACKUP) { + ((OlapTable) table).setState(OlapTableState.NORMAL); + LOG.debug("set table[{}] state to NORMAL", table.getName()); + } + } + } + } finally { + db.writeUnlock(); + } + } + } + + private void removeLeftTasks() { + for (Map.Entry entry : unfinishedTabletIds.entries()) { + AgentTaskQueue.removeTask(entry.getValue(), TTaskType.MAKE_SNAPSHOT, entry.getKey()); + AgentTaskQueue.removeTask(entry.getValue(), TTaskType.UPLOAD, entry.getKey()); + } + } + + @Override + public void end(Catalog catalog, boolean isReplay) { + // 1. set table state + restoreTableState(catalog); + + if (!isReplay) { + // 2. remove agent tasks if left + removeLeftTasks(); + + if (pathBuilder == null) { + finishedTime = System.currentTimeMillis(); + Catalog.getInstance().getEditLog().logBackupFinish(this); + LOG.info("finished end job[{}]. state: {}", jobId, state.name()); + return; + } + + // 3. remove local file + String labelDir = pathBuilder.getRoot().getFullPath(); + Util.deleteDirectory(new File(labelDir)); + LOG.debug("delete local dir: {}", labelDir); + + // 4. release snapshot + synchronized (tabletIdToSnapshotPath) { + AgentBatchTask batchTask = new AgentBatchTask(); + for (Long tabletId : tabletIdToSnapshotPath.keySet()) { + long backendId = tabletIdToSnapshotPath.get(tabletId).first; + String snapshotPath = tabletIdToSnapshotPath.get(tabletId).second; + ReleaseSnapshotTask task = new ReleaseSnapshotTask(null, backendId, dbId, tabletId, snapshotPath); + batchTask.addTask(task); + } + // no need to add to AgentTaskQueue + AgentTaskExecutor.submit(batchTask); + } + + finishedTime = System.currentTimeMillis(); + + Catalog.getInstance().getEditLog().logBackupFinish(this); + } + + clearJob(); + + LOG.info("finished end job[{}]. state: {}, replay: {}", jobId, state.name(), isReplay); + } + + @Override + protected void clearJob() { + tableIdToPartitionIds = null; + tableIdToIndexIds = null; + partitionIdToVersionInfo = null; + tabletIdToSnapshotPath = null; + + unfinishedTabletIds = null; + remoteProperties = null; + pathBuilder = null; + commandBuilder = null; + } + + @Override + public void write(DataOutput out) throws IOException { + super.write(out); + + Text.writeString(out, state.name()); + Text.writeString(out, lastestLoadLabel); + + if (lastestDeleteInfo == null) { + out.writeBoolean(false); + } else { + out.writeBoolean(true); + lastestDeleteInfo.write(out); + } + + if (tableIdToPartitionIds == null) { + out.writeBoolean(false); + } else { + out.writeBoolean(true); + int size = tableIdToPartitionIds.size(); + out.writeInt(size); + for (Map.Entry> entry : tableIdToPartitionIds.entrySet()) { + out.writeLong(entry.getKey()); + size = entry.getValue().size(); + out.writeInt(size); + for (Long partitionId : entry.getValue()) { + out.writeLong(partitionId); + } + } + } + + if (tableIdToIndexIds == null) { + out.writeBoolean(false); + } else { + out.writeBoolean(true); + Collection> entries = tableIdToIndexIds.entries(); + int size = entries.size(); + out.writeInt(size); + for (Map.Entry entry : entries) { + out.writeLong(entry.getKey()); + out.writeLong(entry.getValue()); + } + } + + if (partitionIdToVersionInfo == null) { + out.writeBoolean(false); + } else { + out.writeBoolean(true); + int size = partitionIdToVersionInfo.size(); + out.writeInt(size); + for (Map.Entry> entry : partitionIdToVersionInfo.entrySet()) { + out.writeLong(entry.getKey()); + Pair pair = entry.getValue(); + out.writeLong(pair.first); + out.writeLong(pair.second); + } + } + + if (tabletIdToSnapshotPath == null) { + out.writeBoolean(false); + } else { + out.writeBoolean(true); + int size = tabletIdToSnapshotPath.size(); + out.writeInt(size); + for (Map.Entry> entry : tabletIdToSnapshotPath.entrySet()) { + out.writeLong(entry.getKey()); + Pair pair = entry.getValue(); + out.writeLong(pair.first); + Text.writeString(out, pair.second); + } + } + + out.writeLong(metaSavedTime); + out.writeLong(snapshotFinishedTime); + out.writeLong(uploadFinishedTime); + out.writeLong(phasedTimeoutMs); + + Text.writeString(out, readableManifestPath); + + if (pathBuilder == null) { + out.writeBoolean(false); + } else { + out.writeBoolean(true); + pathBuilder.write(out); + } + + if (commandBuilder == null) { + out.writeBoolean(false); + } else { + out.writeBoolean(true); + commandBuilder.write(out); + } + } + + @Override + public void readFields(DataInput in) throws IOException { + super.readFields(in); + + state = BackupJobState.valueOf(Text.readString(in)); + lastestLoadLabel = Text.readString(in); + + if (in.readBoolean()) { + lastestDeleteInfo = new DeleteInfo(); + lastestDeleteInfo.readFields(in); + } + + if (in.readBoolean()) { + int size = in.readInt(); + for (int i = 0; i < size; i++) { + long tableId = in.readLong(); + Set partitionIds = Sets.newHashSet(); + tableIdToPartitionIds.put(tableId, partitionIds); + int count = in.readInt(); + for (int j = 0; j < count; j++) { + long partitionId = in.readLong(); + partitionIds.add(partitionId); + } + } + } + + if (in.readBoolean()) { + int size = in.readInt(); + for (int i = 0; i < size; i++) { + long tableId = in.readLong(); + long indexId = in.readLong(); + tableIdToIndexIds.put(tableId, indexId); + } + } + + if (in.readBoolean()) { + int size = in.readInt(); + for (int i = 0; i < size; i++) { + long partitionId = in.readLong(); + long version = in.readLong(); + long versionHash = in.readLong(); + partitionIdToVersionInfo.put(partitionId, new Pair(version, versionHash)); + } + } + + if (in.readBoolean()) { + int size = in.readInt(); + for (int i = 0; i < size; i++) { + long tabletId = in.readLong(); + long backendId = in.readLong(); + String path = Text.readString(in); + tabletIdToSnapshotPath.put(tabletId, new Pair(backendId, path)); + } + } + + metaSavedTime = in.readLong(); + snapshotFinishedTime = in.readLong(); + uploadFinishedTime = in.readLong(); + phasedTimeoutMs = in.readLong(); + + readableManifestPath = Text.readString(in); + + if (in.readBoolean()) { + pathBuilder = new PathBuilder(); + pathBuilder.readFields(in); + } + + if (in.readBoolean()) { + commandBuilder = new CommandBuilder(); + commandBuilder.readFields(in); + } + + } +} diff --git a/fe/src/com/baidu/palo/backup/BackupMeta.java b/fe/src/com/baidu/palo/backup/BackupMeta.java new file mode 100644 index 0000000000..edb1b6c3f9 --- /dev/null +++ b/fe/src/com/baidu/palo/backup/BackupMeta.java @@ -0,0 +1,119 @@ +// Modifications copyright (C) 2018, Baidu.com, Inc. +// Copyright 2018 The Apache Software Foundation + +// 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. + +package com.baidu.palo.backup; + +import com.baidu.palo.catalog.Table; +import com.baidu.palo.common.io.Writable; + +import com.google.common.collect.Maps; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +public class BackupMeta implements Writable { + private static final Logger LOG = LogManager.getLogger(BackupMeta.class); + + // tbl name -> tbl + private Map tblNameMap = Maps.newHashMap(); + // tbl id -> tbl + private Map tblIdMap = Maps.newHashMap(); + + private BackupMeta() { + + } + + public BackupMeta(List
tables) { + for (Table table : tables) { + tblNameMap.put(table.getName(), table); + tblIdMap.put(table.getId(), table); + } + } + + public Map getTables() { + return tblNameMap; + } + + public Table getTable(String tblName) { + return tblNameMap.get(tblName); + } + + public Table getTable(Long tblId) { + return tblIdMap.get(tblId); + } + + public static BackupMeta fromFile(String filePath) throws IOException { + File file = new File(filePath); + try (DataInputStream dis = new DataInputStream(new FileInputStream(file))) { + BackupMeta backupMeta = BackupMeta.read(dis); + return backupMeta; + } + } + + public void writeToFile(File metaInfoFile) throws IOException { + DataOutputStream dos = new DataOutputStream(new FileOutputStream(metaInfoFile)); + try { + write(dos); + dos.flush(); + } finally { + dos.close(); + } + } + + public boolean compatibleWith(BackupMeta other) { + // TODO + return false; + } + + public static BackupMeta read(DataInput in) throws IOException { + BackupMeta backupMeta = new BackupMeta(); + backupMeta.readFields(in); + return backupMeta; + } + + @Override + public void write(DataOutput out) throws IOException { + out.writeInt(tblNameMap.size()); + for (Table table : tblNameMap.values()) { + table.write(out); + } + } + + @Override + public void readFields(DataInput in) throws IOException { + int size = in.readInt(); + for (int i = 0; i < size; i++) { + Table tbl = Table.read(in); + tblNameMap.put(tbl.getName(), tbl); + tblIdMap.put(tbl.getId(), tbl); + } + } +} diff --git a/fe/src/com/baidu/palo/backup/BlobStorage.java b/fe/src/com/baidu/palo/backup/BlobStorage.java new file mode 100644 index 0000000000..97df20066a --- /dev/null +++ b/fe/src/com/baidu/palo/backup/BlobStorage.java @@ -0,0 +1,700 @@ +// Modifications copyright (C) 2018, Baidu.com, Inc. +// Copyright 2018 The Apache Software Foundation + +// 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. + +package com.baidu.palo.backup; + +import com.baidu.palo.backup.Status.ErrCode; +import com.baidu.palo.catalog.BrokerMgr; +import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.ClientPool; +import com.baidu.palo.common.Config; +import com.baidu.palo.common.Pair; +import com.baidu.palo.common.io.Text; +import com.baidu.palo.common.io.Writable; +import com.baidu.palo.service.FrontendOptions; +import com.baidu.palo.thrift.TBrokerCheckPathExistRequest; +import com.baidu.palo.thrift.TBrokerCheckPathExistResponse; +import com.baidu.palo.thrift.TBrokerCloseReaderRequest; +import com.baidu.palo.thrift.TBrokerCloseWriterRequest; +import com.baidu.palo.thrift.TBrokerFD; +import com.baidu.palo.thrift.TBrokerFileStatus; +import com.baidu.palo.thrift.TBrokerListPathRequest; +import com.baidu.palo.thrift.TBrokerListResponse; +import com.baidu.palo.thrift.TBrokerOpenMode; +import com.baidu.palo.thrift.TBrokerOpenReaderRequest; +import com.baidu.palo.thrift.TBrokerOpenReaderResponse; +import com.baidu.palo.thrift.TBrokerOpenWriterRequest; +import com.baidu.palo.thrift.TBrokerOpenWriterResponse; +import com.baidu.palo.thrift.TBrokerOperationStatus; +import com.baidu.palo.thrift.TBrokerOperationStatusCode; +import com.baidu.palo.thrift.TBrokerPReadRequest; +import com.baidu.palo.thrift.TBrokerPWriteRequest; +import com.baidu.palo.thrift.TBrokerReadResponse; +import com.baidu.palo.thrift.TBrokerRenamePathRequest; +import com.baidu.palo.thrift.TBrokerVersion; +import com.baidu.palo.thrift.TNetworkAddress; +import com.baidu.palo.thrift.TPaloBrokerService; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.thrift.TException; +import org.apache.thrift.transport.TTransportException; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.nio.file.FileVisitOption; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +public class BlobStorage implements Writable { + private static final Logger LOG = LogManager.getLogger(BlobStorage.class); + + private String brokerName; + private Map properties = Maps.newHashMap(); + + private BlobStorage() { + // for persist + } + + public BlobStorage(String brokerName, Map properties) { + this.brokerName = brokerName; + this.properties = properties; + } + + public String getBrokerName() { + return brokerName; + } + + public Map getProperties() { + return properties; + } + + public Status downloadWithFileSize(String remoteFilePath, String localFilePath, long fileSize) { + LOG.debug("download from {} to {}, file size: {}.", + remoteFilePath, localFilePath, fileSize); + + long start = System.currentTimeMillis(); + + // 1. get a proper broker + Pair pair = new Pair( + null, null); + Status st = getBroker(pair); + if (!st.ok()) { + return st; + } + TPaloBrokerService.Client client = pair.first; + TNetworkAddress address = pair.second; + + // 2. open file reader with broker + TBrokerFD fd = null; + try { + TBrokerOpenReaderRequest req = new TBrokerOpenReaderRequest(TBrokerVersion.VERSION_ONE, remoteFilePath, + 0, clientId(), properties); + TBrokerOpenReaderResponse rep = client.openReader(req); + TBrokerOperationStatus opst = rep.getOpStatus(); + if (opst.getStatusCode() != TBrokerOperationStatusCode.OK) { + return new Status(ErrCode.COMMON_ERROR, + "failed to open reader on broker " + brokerName + " for file: " + + remoteFilePath + ". msg: " + opst.getMessage()); + } + + fd = rep.getFd(); + LOG.info("finished to open reader. fd: {}. download {} to {}.", + fd, remoteFilePath, localFilePath); + } catch (TException e) { + return new Status(ErrCode.COMMON_ERROR, + "failed to open reader on broker " + brokerName + " for file: " + + remoteFilePath + ". msg: " + e.getMessage()); + } + Preconditions.checkNotNull(fd); + + // 3. delete local file if exist + File localFile = new File(localFilePath); + if (localFile.exists()) { + try { + Files.walk(Paths.get(localFilePath), + FileVisitOption.FOLLOW_LINKS).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); + } catch (IOException e) { + return new Status(ErrCode.COMMON_ERROR, "failed to delete exist local file: " + localFilePath); + } + } + + // 4. create local file + Status status = Status.OK; + try { + if (!localFile.createNewFile()) { + return new Status(ErrCode.COMMON_ERROR, "failed to create local file: " + localFilePath); + } + } catch (IOException e) { + return new Status(ErrCode.COMMON_ERROR, "failed to create local file: " + + localFilePath + ", msg: " + e.getMessage()); + } + + // 5. read remote file with broker and write to local + String lastErrMsg = null; + try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(localFile))) { + final long bufSize = 1024 * 1024; // 1MB + long leftSize = fileSize; + long readOffset = 0; + while (leftSize > 0) { + long readLen = leftSize > bufSize ? bufSize : leftSize; + TBrokerReadResponse rep = null; + // We only retry if we encounter a timeout thrift exception. + int tryTimes = 0; + while (tryTimes < 3) { + try { + TBrokerPReadRequest req = new TBrokerPReadRequest(TBrokerVersion.VERSION_ONE, + fd, readOffset, readLen); + rep = client.pread(req); + if (rep.getOpStatus().getStatusCode() != TBrokerOperationStatusCode.OK) { + // pread return failure. + lastErrMsg = String.format("failed to read via broker %s. " + + "current read offset: %d, read length: %d," + + " file size: %d, file: %s, err code: %d, msg: %s", + brokerName, readOffset, readLen, fileSize, + remoteFilePath, rep.getOpStatus().getStatusCode(), + rep.getOpStatus().getMessage()); + LOG.warn(lastErrMsg); + status = new Status(ErrCode.COMMON_ERROR, lastErrMsg); + } + LOG.debug("download. readLen: {}, read data len: {}, left size:{}. total size: {}", + readLen, rep.getData().length, leftSize, fileSize); + break; + } catch (TTransportException e) { + if (e.getType() == TTransportException.TIMED_OUT) { + // we only retry when we encounter timeout exception. + lastErrMsg = String.format("failed to read via broker %s. " + + "current read offset: %d, read length: %d," + + " file size: %d, file: %s, timeout.", + brokerName, readOffset, readLen, fileSize, + remoteFilePath); + tryTimes++; + continue; + } + + lastErrMsg = String.format("failed to read via broker %s. " + + "current read offset: %d, read length: %d," + + " file size: %d, file: %s. msg: %s", + brokerName, readOffset, readLen, fileSize, + remoteFilePath, e.getMessage()); + LOG.warn(lastErrMsg); + status = new Status(ErrCode.COMMON_ERROR, lastErrMsg); + break; + } catch (TException e) { + lastErrMsg = String.format("failed to read via broker %s. " + + "current read offset: %d, read length: %d," + + " file size: %d, file: %s. msg: %s", + brokerName, readOffset, readLen, fileSize, + remoteFilePath, e.getMessage()); + LOG.warn(lastErrMsg); + status = new Status(ErrCode.COMMON_ERROR, lastErrMsg); + break; + } + } // end of retry loop + + if (status.ok() && tryTimes < 3) { + // read succeed, write to local file + Preconditions.checkNotNull(rep); + // NOTICE(cmy): Sometimes the actual read length does not equal to the expected read length, + // even if the broker's read buffer size is large enough. + // I don't know why, but have to adapt to it. + if (rep.getData().length != readLen) { + LOG.warn("the actual read length does not equal to " + + "the expected read length: {} vs. {}, file: {}", + rep.getData().length, readLen, remoteFilePath); + } + + out.write(rep.getData()); + readOffset += rep.getData().length; + leftSize -= rep.getData().length; + } else { + status = new Status(ErrCode.COMMON_ERROR, lastErrMsg); + break; + } + } // end of reading remote file + } catch (IOException e) { + return new Status(ErrCode.COMMON_ERROR, "Got exception: " + e.getMessage()); + } finally { + // close broker reader + Status closeStatus = closeReader(client, fd); + if (!closeStatus.ok()) { + LOG.warn(closeStatus.getErrMsg()); + if (status.ok()) { + // we return close write error only if no other error has been encountered. + status = closeStatus; + } + ClientPool.brokerPool.invalidateObject(address, client); + } else { + ClientPool.brokerPool.returnObject(address, client); + } + } + + LOG.info("finished to download from {} to {} with size: {}. cost {} ms", remoteFilePath, localFilePath, + fileSize, (System.currentTimeMillis() - start)); + return status; + } + + // directly upload the content to remote file + public Status directUpload(String content, String remoteFile) { + Status status = Status.OK; + + // 1. get a proper broker + Pair pair = new Pair( + null, null); + status = getBroker(pair); + if (!status.ok()) { + return status; + } + TPaloBrokerService.Client client = pair.first; + TNetworkAddress address = pair.second; + + TBrokerFD fd = new TBrokerFD(); + try { + // 2. open file write with broker + status = openWriter(client, address, remoteFile, fd); + if (!status.ok()) { + return status; + } + + // 3. write content + try { + ByteBuffer bb = ByteBuffer.wrap(content.getBytes("UTF-8")); + TBrokerPWriteRequest req = new TBrokerPWriteRequest(TBrokerVersion.VERSION_ONE, fd, 0, bb); + TBrokerOperationStatus opst = client.pwrite(req); + if (opst.getStatusCode() != TBrokerOperationStatusCode.OK) { + // pwrite return failure. + status = new Status(ErrCode.COMMON_ERROR, "write failed: " + opst.getMessage()); + } + } catch (TException e) { + status = new Status(ErrCode.BAD_CONNECTION, "write exception: " + e.getMessage()); + } catch (UnsupportedEncodingException e) { + status = new Status(ErrCode.COMMON_ERROR, "unsupported encoding: " + e.getMessage()); + } + } finally { + Status closeStatus = closeWriter(client, fd); + if (closeStatus.getErrCode() == ErrCode.BAD_CONNECTION || status.getErrCode() == ErrCode.BAD_CONNECTION) { + ClientPool.brokerPool.invalidateObject(address, client); + } else { + ClientPool.brokerPool.returnObject(address, client); + } + } + + return status; + } + + public Status upload(String localPath, String remotePath) { + long start = System.currentTimeMillis(); + + Status status = Status.OK; + + // 1. get a proper broker + Pair pair = new Pair(null, null); + status = getBroker(pair); + if (!status.ok()) { + return status; + } + TPaloBrokerService.Client client = pair.first; + TNetworkAddress address = pair.second; + + // 2. open file write with broker + TBrokerFD fd = new TBrokerFD(); + status = openWriter(client, address, remotePath, fd); + if (!status.ok()) { + return status; + } + + // 3. read local file and write to remote with broker + File localFile = new File(localPath); + long fileLength = localFile.length(); + byte[] readBuf = new byte[1024]; + try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(localFile))) { + // save the last err msg + String lastErrMsg = null; + // save the current write offset of remote file + long writeOffset = 0; + // read local file, 1MB at a time + int bytesRead = 0; + while ((bytesRead = in.read(readBuf)) != -1) { + ByteBuffer bb = ByteBuffer.wrap(readBuf, 0, bytesRead); + + // We only retry if we encounter a timeout thrift exception. + int tryTimes = 0; + while (tryTimes < 3) { + try { + TBrokerPWriteRequest req = new TBrokerPWriteRequest(TBrokerVersion.VERSION_ONE, fd, writeOffset, bb); + TBrokerOperationStatus opst = client.pwrite(req); + if (opst.getStatusCode() != TBrokerOperationStatusCode.OK) { + // pwrite return failure. + lastErrMsg = String.format("failed to write via broker %s. " + + "current write offset: %d, write length: %d," + + " file length: %d, file: %s, err code: %d, msg: %s", + brokerName, writeOffset, bytesRead, fileLength, + remotePath, opst.getStatusCode(), opst.getMessage()); + LOG.warn(lastErrMsg); + status = new Status(ErrCode.COMMON_ERROR, lastErrMsg); + } + break; + } catch (TTransportException e) { + if (e.getType() == TTransportException.TIMED_OUT) { + // we only retry when we encounter timeout exception. + lastErrMsg = String.format("failed to write via broker %s. " + + "current write offset: %d, write length: %d," + + " file length: %d, file: %s. timeout", + brokerName, writeOffset, bytesRead, fileLength, + remotePath); + tryTimes++; + continue; + } + + lastErrMsg = String.format("failed to write via broker %s. " + + "current write offset: %d, write length: %d," + + " file length: %d, file: %s. encounter TTransportException: %s", + brokerName, writeOffset, bytesRead, fileLength, + remotePath, e.getMessage()); + LOG.warn(lastErrMsg, e); + status = new Status(ErrCode.COMMON_ERROR, lastErrMsg); + break; + } catch (TException e) { + lastErrMsg = String.format("failed to write via broker %s. " + + "current write offset: %d, write length: %d," + + " file length: %d, file: %s. encounter TException: %s", + brokerName, writeOffset, bytesRead, fileLength, + remotePath, e.getMessage()); + LOG.warn(lastErrMsg, e); + status = new Status(ErrCode.COMMON_ERROR, lastErrMsg); + break; + } + } + + if (status.ok() && tryTimes < 3) { + // write succeed, update current write offset + writeOffset += bytesRead; + } else { + status = new Status(ErrCode.COMMON_ERROR, lastErrMsg); + break; + } + } // end of read local file loop + } catch (FileNotFoundException e1) { + return new Status(ErrCode.COMMON_ERROR, "encounter file not found exception: " + e1.getMessage()); + } catch (IOException e1) { + return new Status(ErrCode.COMMON_ERROR, "encounter io exception: " + e1.getMessage()); + } finally { + // close write + Status closeStatus = closeWriter(client, fd); + if (!closeStatus.ok()) { + LOG.warn(closeStatus.getErrMsg()); + if (status.ok()) { + // we return close write error only if no other error has been encountered. + status = closeStatus; + } + ClientPool.brokerPool.invalidateObject(address, client); + } else { + ClientPool.brokerPool.returnObject(address, client); + } + } + + if (status.ok()) { + LOG.info("finished to upload {} to remote path {}. cost: {} ms", + localPath, remotePath, (System.currentTimeMillis() - start)); + } + return status; + } + + public Status rename(String origFilePath, String destFilePath) { + long start = System.currentTimeMillis(); + Status status = Status.OK; + + // 1. get a proper broker + Pair pair = new Pair( + null, null); + status = getBroker(pair); + if (!status.ok()) { + return status; + } + TPaloBrokerService.Client client = pair.first; + TNetworkAddress address = pair.second; + + // 2. rename + boolean needReturn = true; + try { + TBrokerRenamePathRequest req = new TBrokerRenamePathRequest(TBrokerVersion.VERSION_ONE, origFilePath, + destFilePath, properties); + TBrokerOperationStatus ost = client.renamePath(req); + if (ost.getStatusCode() != TBrokerOperationStatusCode.OK) { + return new Status(ErrCode.COMMON_ERROR, + "failed to rename " + origFilePath + " to " + destFilePath + ", msg: " + ost.getMessage()); + } + } catch (TException e) { + needReturn = false; + return new Status(ErrCode.COMMON_ERROR, + "failed to rename " + origFilePath + " to " + destFilePath + ", msg: " + e.getMessage()); + } finally { + if (needReturn) { + ClientPool.brokerPool.returnObject(address, client); + } else { + ClientPool.brokerPool.invalidateObject(address, client); + } + } + + LOG.info("finished to rename {} to {}. cost: {} ms", + origFilePath, destFilePath, (System.currentTimeMillis() - start)); + return Status.OK; + } + + public Status delete(String remotePath) { + return Status.OK; + } + + // List files in remotePath + // The remote file name will only contains file name only(Not full path) + public Status list(String remotePath, List result) { + // get a proper broker + Pair pair = new Pair(null, null); + Status st = getBroker(pair); + if (!st.ok()) { + return st; + } + TPaloBrokerService.Client client = pair.first; + TNetworkAddress address = pair.second; + + // list + boolean needReturn = true; + try { + TBrokerListPathRequest req = new TBrokerListPathRequest(TBrokerVersion.VERSION_ONE, remotePath, + false /* not recursive */, properties); + req.setFileNameOnly(true); + TBrokerListResponse rep = client.listPath(req); + TBrokerOperationStatus opst = rep.getOpStatus(); + if (opst.getStatusCode() != TBrokerOperationStatusCode.OK) { + return new Status(ErrCode.COMMON_ERROR, + "failed to list remote path: " + remotePath + ". msg: " + opst.getMessage()); + } + + List fileStatus = rep.getFiles(); + for (TBrokerFileStatus tFile : fileStatus) { + RemoteFile file = new RemoteFile(tFile.path, !tFile.isDir, tFile.size); + result.add(file); + } + LOG.info("finished to list remote path {}. get files: {}", remotePath, result); + } catch (TException e) { + needReturn = false; + return new Status(ErrCode.COMMON_ERROR, + "failed to list remote path: " + remotePath + ". msg: " + e.getMessage()); + } finally { + if (needReturn) { + ClientPool.brokerPool.returnObject(address, client); + } else { + ClientPool.brokerPool.invalidateObject(address, client); + } + } + + return Status.OK; + } + + public Status makeDir(String remotePath) { + // 1. get a proper broker + Pair pair = new Pair( + null, null); + Status st = getBroker(pair); + if (!st.ok()) { + return st; + } + TPaloBrokerService.Client client = pair.first; + TNetworkAddress address = pair.second; + + // TODO: mkdir + return Status.OK; + } + + public Status checkPathExist(String remotePath) { + // 1. get a proper broker + Pair pair = new Pair( + null, null); + Status st = getBroker(pair); + if (!st.ok()) { + return st; + } + TPaloBrokerService.Client client = pair.first; + TNetworkAddress address = pair.second; + + // check path + boolean needReturn = true; + try { + TBrokerCheckPathExistRequest req = new TBrokerCheckPathExistRequest(TBrokerVersion.VERSION_ONE, + remotePath, properties); + TBrokerCheckPathExistResponse rep = client.checkPathExist(req); + TBrokerOperationStatus opst = rep.getOpStatus(); + if (opst.getStatusCode() != TBrokerOperationStatusCode.OK) { + return new Status(ErrCode.COMMON_ERROR, + "failed to check remote path exist: " + remotePath + ". msg: " + opst.getMessage()); + } + + if (!rep.isIsPathExist()) { + return new Status(ErrCode.NOT_FOUND, "remote path does not exist: " + remotePath); + } + + return Status.OK; + } catch (TException e) { + needReturn = false; + return new Status(ErrCode.COMMON_ERROR, + "failed to check remote path exist: " + remotePath + ". msg: " + e.getMessage()); + } finally { + if (needReturn) { + ClientPool.brokerPool.returnObject(address, client); + } else { + ClientPool.brokerPool.invalidateObject(address, client); + } + } + } + + public static String clientId() { + return FrontendOptions.getLocalHostAddress() + ":" + Config.edit_log_port; + } + + private Status getBroker(Pair result) { + BrokerMgr.BrokerAddress brokerAddress = null; + try { + String localIP = FrontendOptions.getLocalHostAddress(); + brokerAddress = Catalog.getInstance().getBrokerMgr().getBroker(brokerName, localIP); + } catch (AnalysisException e) { + return new Status(ErrCode.COMMON_ERROR, "failed to get a broker address: " + e.getMessage()); + } + TNetworkAddress address = new TNetworkAddress(brokerAddress.ip, brokerAddress.port); + TPaloBrokerService.Client client = null; + try { + client = ClientPool.brokerPool.borrowObject(address); + } catch (Exception e) { + return new Status(ErrCode.COMMON_ERROR, "failed to get broker client: " + e.getMessage()); + } + + result.first = client; + result.second = address; + return Status.OK; + } + + private Status openWriter(TPaloBrokerService.Client client, TNetworkAddress address, String remoteFile, + TBrokerFD fd) { + try { + TBrokerOpenWriterRequest req = new TBrokerOpenWriterRequest(TBrokerVersion.VERSION_ONE, + remoteFile, TBrokerOpenMode.APPEND, clientId(), properties); + TBrokerOpenWriterResponse rep = client.openWriter(req); + TBrokerOperationStatus opst = rep.getOpStatus(); + if (opst.getStatusCode() != TBrokerOperationStatusCode.OK) { + return new Status(ErrCode.COMMON_ERROR, + "failed to open writer on broker " + brokerName + " for file: " + + remoteFile + ". msg: " + opst.getMessage()); + } + + fd.setHigh(rep.getFd().getHigh()); + fd.setLow(rep.getFd().getLow()); + LOG.info("finished to open writer. fd: {}. directly upload to remote path {}.", + fd, remoteFile); + } catch (TException e) { + return new Status(ErrCode.BAD_CONNECTION, + "failed to open writer on broker " + brokerName + ": " + e.getMessage()); + } + + return Status.OK; + } + + private Status closeWriter(TPaloBrokerService.Client client, TBrokerFD fd) { + try { + TBrokerCloseWriterRequest req = new TBrokerCloseWriterRequest(TBrokerVersion.VERSION_ONE, fd); + TBrokerOperationStatus st = client.closeWriter(req); + if (st.getStatusCode() != TBrokerOperationStatusCode.OK) { + return new Status(ErrCode.COMMON_ERROR, + "failed to close writer on broker " + brokerName + " for fd: " + fd); + } + + LOG.info("finished to close writer. fd: {}.", fd); + } catch (TException e) { + return new Status(ErrCode.BAD_CONNECTION, + "failed to close writer on broker " + brokerName + ", fd " + fd + ", msg: " + e.getMessage()); + } + + return Status.OK; + } + + private Status closeReader(TPaloBrokerService.Client client, TBrokerFD fd) { + try { + TBrokerCloseReaderRequest req = new TBrokerCloseReaderRequest(TBrokerVersion.VERSION_ONE, fd); + TBrokerOperationStatus st = client.closeReader(req); + if (st.getStatusCode() != TBrokerOperationStatusCode.OK) { + return new Status(ErrCode.COMMON_ERROR, + "failed to close reader on broker " + brokerName + " for fd: " + fd); + } + + LOG.info("finished to close reader. fd: {}.", fd); + } catch (TException e) { + return new Status(ErrCode.BAD_CONNECTION, + "failed to close reader on broker " + brokerName + ", fd " + fd + ", msg: " + e.getMessage()); + } + + return Status.OK; + } + + public static BlobStorage read(DataInput in) throws IOException { + BlobStorage blobStorage = new BlobStorage(); + blobStorage.readFields(in); + return blobStorage; + } + + @Override + public void write(DataOutput out) throws IOException { + // must write type first + Text.writeString(out, brokerName); + + out.writeInt(properties.size()); + for (Map.Entry entry : properties.entrySet()) { + Text.writeString(out, entry.getKey()); + Text.writeString(out, entry.getValue()); + } + } + + @Override + public void readFields(DataInput in) throws IOException { + brokerName = Text.readString(in); + + // properties + int size = in.readInt(); + for (int i = 0; i < size; i++) { + String key = Text.readString(in); + String value = Text.readString(in); + properties.put(key, value); + } + } +} diff --git a/fe/src/com/baidu/palo/backup/RemoteFile.java b/fe/src/com/baidu/palo/backup/RemoteFile.java new file mode 100644 index 0000000000..7fb15f55c8 --- /dev/null +++ b/fe/src/com/baidu/palo/backup/RemoteFile.java @@ -0,0 +1,56 @@ +// Modifications copyright (C) 2018, Baidu.com, Inc. +// Copyright 2018 The Apache Software Foundation + +// 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. + +package com.baidu.palo.backup; + +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; + +// represent a file or a dir in remote storage +public class RemoteFile { + // Only file name, not full path + private String name; + private boolean isFile; + private long size; + + public RemoteFile(String name, boolean isFile, long size) { + Preconditions.checkState(!Strings.isNullOrEmpty(name)); + this.name = name; + this.isFile = isFile; + this.size = size; + } + + public String getName() { + return name; + } + + public boolean isFile() { + return isFile; + } + + public long getSize() { + return size; + } + + @Override + public String toString() { + return "[name: " + name + ", is file: " + isFile + "]"; + } +} diff --git a/fe/src/com/baidu/palo/backup/Repository.java b/fe/src/com/baidu/palo/backup/Repository.java new file mode 100644 index 0000000000..61256e5cc3 --- /dev/null +++ b/fe/src/com/baidu/palo/backup/Repository.java @@ -0,0 +1,651 @@ +// Modifications copyright (C) 2018, Baidu.com, Inc. +// Copyright 2018 The Apache Software Foundation + +// 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. + +package com.baidu.palo.backup; + +import com.baidu.palo.backup.Status.ErrCode; +import com.baidu.palo.catalog.BrokerMgr.BrokerAddress; +import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.Pair; +import com.baidu.palo.common.io.Text; +import com.baidu.palo.common.io.Writable; +import com.baidu.palo.common.util.TimeUtils; +import com.baidu.palo.system.Backend; + +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import com.google.common.collect.Lists; + +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.json.JSONObject; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.text.SimpleDateFormat; +import java.util.List; + +/* + * Repository represents a remote storage for backup to or restore from + * File organization in repository is: + * + * * __palo_repository_repo_name/ + * * __repo_info + * * __ss_my_ss1/ + * * __meta__DJdwnfiu92n + * * __info_2018-01-01-08-00-00.OWdn90ndwpu + * * __info_2018-01-02-08-00-00.Dnvdio298da + * * __info_2018-01-03-08-00-00.O79adbneJWk + * * __ss_content/ + * * __db_10001/ + * * __tbl_10010/ + * * __tbl_10020/ + * * __part_10021/ + * * __part_10031/ + * * __idx_10041/ + * * __idx_10020/ + * * __10022/ + * * __10023/ + * * __10023_seg1.dat.NUlniklnwDN67 + * * __10023_seg2.dat.DNW231dnklawd + * * __10023.hdr.dnmwDDWI92dDko + */ +public class Repository implements Writable { + private static final Logger LOG = LogManager.getLogger(Repository.class); + + public static final String PREFIX_REPO = "__palo_repository_"; + public static final String PREFIX_SNAPSHOT_DIR = "__ss_"; + public static final String PREFIX_DB = "__db_"; + public static final String PREFIX_TBL = "__tbl_"; + public static final String PREFIX_PART = "__part_"; + public static final String PREFIX_IDX = "__idx_"; + public static final String PREFIX_COMMON = "__"; + public static final String PREFIX_JOB_INFO = "__info_"; + + public static final String SUFFIX_TMP_FILE = "part"; + + public static final String FILE_REPO_INFO = "__repo_info"; + public static final String FILE_META_INFO = "__meta"; + + public static final String DIR_SNAPSHOT_CONTENT = "__ss_content"; + + private static final String PATH_DELIMITER = "/"; + private static final String CHECKSUM_SEPARATOR = "."; + + private long id; + private String name; + private String errMsg; + private long createTime; + + // If True, user can not backup data to this repo. + private boolean isReadOnly; + + // BOS location should start with "bos://your_bucket_name/" + // and the specified bucket should exist. + private String location; + + private BlobStorage storage; + + private Repository() { + // for persist + } + + public Repository(long id, String name, boolean isReadOnly, String location, BlobStorage storage) { + this.id = id; + this.name = name; + this.isReadOnly = isReadOnly; + this.location = location; + this.storage = storage; + this.createTime = System.currentTimeMillis(); + } + + public long getId() { + return id; + } + + public String getName() { + return name; + } + + public boolean isReadOnly() { + return isReadOnly; + } + + public String getLocation() { + return location; + } + + public String getErrorMsg() { + return errMsg; + } + + public BlobStorage getStorage() { + return storage; + } + + public long getCreateTime() { + return createTime; + } + + // create repository dir and repo info file + public Status initRepository() { + String repoInfoFilePath = assembleRepoInfoFilePath(); + // check if the repo is already exist in remote + List remoteFiles = Lists.newArrayList(); + Status st = storage.list(repoInfoFilePath, remoteFiles); + if (!st.ok()) { + return st; + } + if (remoteFiles.size() == 1) { + RemoteFile remoteFile = remoteFiles.get(0); + if (!remoteFile.isFile()) { + return new Status(ErrCode.COMMON_ERROR, "the existing repo info is not a file"); + } + + // exist, download and parse the repo info file + String localFilePath = BackupHandler.BACKUP_ROOT_DIR + "/tmp_info_" + System.currentTimeMillis(); + try { + st = storage.downloadWithFileSize(repoInfoFilePath, localFilePath, remoteFile.getSize()); + if (!st.ok()) { + return st; + } + + byte[] bytes = Files.readAllBytes(Paths.get(localFilePath)); + String json = new String(bytes, StandardCharsets.UTF_8); + JSONObject root = new JSONObject(json); + name = (String) root.get("name"); + createTime = TimeUtils.timeStringToLong((String) root.get("create_time")); + if (createTime == -1) { + return new Status(ErrCode.COMMON_ERROR, + "failed to parse create time of repository: " + (String) root.get("create_time")); + } + return Status.OK; + + } catch (IOException e) { + return new Status(ErrCode.COMMON_ERROR, "failed to read repo info file: " + e.getMessage()); + } finally { + File localFile = new File(localFilePath); + localFile.delete(); + } + + } else if (remoteFiles.size() > 1) { + return new Status(ErrCode.COMMON_ERROR, + "Invalid repository dir. expected one repo info file. get more: " + remoteFiles); + } else { + // repo is already exist, get repo info + JSONObject root = new JSONObject(); + root.put("name", name); + root.put("create_time", TimeUtils.longToTimeString(createTime)); + String repoInfoContent = root.toString(); + return storage.directUpload(repoInfoContent, repoInfoFilePath); + } + } + + // eg: location/__palo_repository_repo_name/__repo_info + public String assembleRepoInfoFilePath() { + return Joiner.on(PATH_DELIMITER).join(location, + joinPrefix(PREFIX_REPO, name), + FILE_REPO_INFO); + } + + // eg: location/__palo_repository_repo_name/__my_sp1/__meta + public String assembleMetaInfoFilePath(String label) { + return Joiner.on(PATH_DELIMITER).join(location, joinPrefix(PREFIX_REPO, name), + joinPrefix(PREFIX_SNAPSHOT_DIR, label), + FILE_META_INFO); + } + + // eg: location/__palo_repository_repo_name/__my_sp1/__info_2018-01-01-08-00-00 + public String assembleJobInfoFilePath(String label, long createTime) { + return Joiner.on(PATH_DELIMITER).join(location, joinPrefix(PREFIX_REPO, name), + joinPrefix(PREFIX_SNAPSHOT_DIR, label), + jobInfoFileNameWithTimestamp(createTime)); + } + + // eg: + // __palo_repository_repo_name/__ss_my_ss1/__ss_content/__db_10001/__tbl_10020/__part_10031/__idx_10020/__10022/ + public String getRepoTabletPathBySnapshotInfo(String label, SnapshotInfo info) { + return Joiner.on(PATH_DELIMITER).join(location, joinPrefix(PREFIX_REPO, name), + joinPrefix(PREFIX_SNAPSHOT_DIR, label), + DIR_SNAPSHOT_CONTENT, + joinPrefix(PREFIX_DB, info.getDbId()), + joinPrefix(PREFIX_TBL, info.getTblId()), + joinPrefix(PREFIX_PART, info.getPartitionId()), + joinPrefix(PREFIX_IDX, info.getIndexId()), + joinPrefix(PREFIX_COMMON, info.getTabletId())); + } + + public String getRepoPath(String label, String childPath) { + return Joiner.on(PATH_DELIMITER).join(location, joinPrefix(PREFIX_REPO, name), + joinPrefix(PREFIX_SNAPSHOT_DIR, label), + DIR_SNAPSHOT_CONTENT, + childPath); + } + + // Check if this repo is available. + // If failed to connect this repo, set errMsg and return false. + public boolean ping() { + String checkPath = Joiner.on(PATH_DELIMITER).join(location, + joinPrefix(PREFIX_REPO, name)); + Status st = storage.checkPathExist(checkPath); + if (!st.ok()) { + errMsg = TimeUtils.longToTimeString(System.currentTimeMillis()) + ": " + st.getErrMsg(); + return false; + } + + // clear err msg + errMsg = null; + + return true; + } + + // Visit the repository, and list all existing snapshot names + public Status listSnapshots(List snapshotNames) { + // list with prefix: + // eg. __palo_repository_repo_name/__ss_* + String listPath = Joiner.on(PATH_DELIMITER).join(location, joinPrefix(PREFIX_REPO, name), PREFIX_SNAPSHOT_DIR) + + "*"; + List result = Lists.newArrayList(); + Status st = storage.list(listPath, result); + if (!st.ok()) { + return st; + } + + for (RemoteFile remoteFile : result) { + if (remoteFile.isFile()) { + LOG.debug("get snapshot path{} which is not a dir", remoteFile); + continue; + } + + snapshotNames.add(disjoinPrefix(PREFIX_SNAPSHOT_DIR, remoteFile.getName())); + } + return Status.OK; + } + + // + public boolean prepareSnapshotInfo() { + return false; + } + + // create remote tablet snapshot path + // eg: + // /location/__palo_repository_repo_name/__ss_my_ss1/__ss_content/__db_10001/__tbl_10020/__part_10031/__idx_10032/__10023/__3481721 + public String assembleRemoteSnapshotPath(String label, SnapshotInfo info) { + String path = Joiner.on(PATH_DELIMITER).join(location, + joinPrefix(PREFIX_REPO, name), + joinPrefix(PREFIX_SNAPSHOT_DIR, label), + DIR_SNAPSHOT_CONTENT, + joinPrefix(PREFIX_DB, info.getDbId()), + joinPrefix(PREFIX_TBL, info.getTblId()), + joinPrefix(PREFIX_PART, info.getPartitionId()), + joinPrefix(PREFIX_IDX, info.getIndexId()), + joinPrefix(PREFIX_COMMON, info.getTabletId()), + joinPrefix(PREFIX_COMMON, info.getSchemaHash())); + LOG.debug("get remote tablet snapshot path: {}", path); + return path; + } + + public Status getSnapshotInfoFile(String label, String backupTimestamp, List infos) { + String remoteInfoFilePath = assembleJobInfoFilePath(label, -1) + backupTimestamp; + File localInfoFile = new File(BackupHandler.BACKUP_ROOT_DIR + PATH_DELIMITER + + "info_" + System.currentTimeMillis()); + try { + Status st = download(remoteInfoFilePath, localInfoFile.getPath()); + if (!st.ok()) { + return st; + } + + BackupJobInfo jobInfo = BackupJobInfo.fromFile(localInfoFile.getAbsolutePath()); + infos.add(jobInfo); + } catch (IOException e) { + return new Status(ErrCode.COMMON_ERROR, "Failed to create job info from file: " + + "" + localInfoFile.getName() + ". msg: " + e.getMessage()); + } finally { + localInfoFile.delete(); + } + + return Status.OK; + } + + public Status getSnapshotMetaFile(String label, List backupMetas) { + String remoteMetaFilePath = assembleMetaInfoFilePath(label); + File localMetaFile = new File(BackupHandler.BACKUP_ROOT_DIR + PATH_DELIMITER + + "meta_" + System.currentTimeMillis()); + + try { + Status st = download(remoteMetaFilePath, localMetaFile.getAbsolutePath()); + if (!st.ok()) { + return st; + } + + // read file to backupMeta + BackupMeta backupMeta = BackupMeta.fromFile(localMetaFile.getAbsolutePath()); + backupMetas.add(backupMeta); + } catch (IOException e) { + return new Status(ErrCode.COMMON_ERROR, "Failed create backup meta from file: " + + localMetaFile.getAbsolutePath() + ", msg: " + e.getMessage()); + } finally { + localMetaFile.delete(); + } + + return Status.OK; + } + + // upload the local file to specified remote file with checksum + // remoteFilePath should be FULL path + public Status upload(String localFilePath, String remoteFilePath) { + Preconditions.checkArgument(remoteFilePath.startsWith(location), remoteFilePath); + // get md5usm of local file + File file = new File(localFilePath); + String md5sum = null; + try { + md5sum = DigestUtils.md5Hex(new FileInputStream(file)); + } catch (FileNotFoundException e) { + return new Status(ErrCode.NOT_FOUND, "file " + localFilePath + " does not exist"); + } catch (IOException e) { + return new Status(ErrCode.COMMON_ERROR, "failed to get md5sum of file: " + localFilePath); + } + Preconditions.checkState(!Strings.isNullOrEmpty(md5sum)); + String tmpRemotePath = assembleFileNameWithSuffix(remoteFilePath, SUFFIX_TMP_FILE); + LOG.debug("get md5sum of file: {}. tmp remote path: {}", localFilePath, tmpRemotePath); + + // upload tmp file + Status st = storage.upload(localFilePath, tmpRemotePath); + if (!st.ok()) { + return st; + } + + // rename tmp file with checksum named file + String finalRemotePath = assembleFileNameWithSuffix(remoteFilePath, md5sum); + st = storage.rename(tmpRemotePath, finalRemotePath); + LOG.info("finished to upload local file {} to remote file: {}", localFilePath, finalRemotePath); + return st; + } + + // remoteFilePath must be a file(not dir) and does not contain checksum + public Status download(String remoteFilePath, String localFilePath) { + // 0. list to get to full name(with checksum) + List remoteFiles = Lists.newArrayList(); + Status status = storage.list(remoteFilePath + "*", remoteFiles); + if (!status.ok()) { + return status; + } + if (remoteFiles.size() != 1) { + return new Status(ErrCode.COMMON_ERROR, + "Expected one file with path: " + remoteFilePath + ". get: " + remoteFiles.size()); + } + if (!remoteFiles.get(0).isFile()) { + return new Status(ErrCode.COMMON_ERROR, "Expected file with path: " + remoteFilePath + ". but get dir"); + } + + String remoteFilePathWithChecksum = replaceFileNameWithChecksumFileName(remoteFilePath, + remoteFiles.get(0).getName()); + LOG.debug("get download filename with checksum: " + remoteFilePathWithChecksum); + + // 1. get checksum from remote file name + Pair pair = decodeFileNameWithChecksum(remoteFilePathWithChecksum); + if (pair == null) { + return new Status(ErrCode.COMMON_ERROR, + "file name should contains checksum: " + remoteFilePathWithChecksum); + } + if (!remoteFilePath.endsWith(pair.first)) { + return new Status(ErrCode.COMMON_ERROR, "File does not exist: " + remoteFilePath); + } + String md5sum = pair.second; + + // 2. download + status = storage.downloadWithFileSize(remoteFilePathWithChecksum, localFilePath, remoteFiles.get(0).getSize()); + if (!status.ok()) { + return status; + } + + // 3. verify checksum + String localMd5sum = null; + try { + localMd5sum = DigestUtils.md5Hex(new FileInputStream(localFilePath)); + } catch (FileNotFoundException e) { + return new Status(ErrCode.NOT_FOUND, "file " + localFilePath + " does not exist"); + } catch (IOException e) { + return new Status(ErrCode.COMMON_ERROR, "failed to get md5sum of file: " + localFilePath); + } + + if (!localMd5sum.equals(md5sum)) { + return new Status(ErrCode.BAD_FILE, + "md5sum does not equal. local: " + localMd5sum + ", remote: " + md5sum); + } + + return Status.OK; + } + + // join job info file name with timestamp + // eg: __info_2018-01-01-08-00-00 + private static String jobInfoFileNameWithTimestamp(long createTime) { + if (createTime == -1) { + return PREFIX_JOB_INFO; + } else { + return PREFIX_JOB_INFO + + TimeUtils.longToTimeString(createTime, new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss")); + } + } + + // join the name with specified prefix + private static String joinPrefix(String prefix, Object name) { + return prefix + name; + } + + // disjoint the name with specified prefix + private static String disjoinPrefix(String prefix, String nameWithPrefix) { + return nameWithPrefix.substring(prefix.length()); + } + + private static String assembleFileNameWithSuffix(String filePath, String md5sum) { + return filePath + CHECKSUM_SEPARATOR + md5sum; + } + + public static Pair decodeFileNameWithChecksum(String fileNameWithChecksum) { + int index = fileNameWithChecksum.lastIndexOf(CHECKSUM_SEPARATOR); + if (index == -1) { + return null; + } + String fileName = fileNameWithChecksum.substring(0, index); + String md5sum = fileNameWithChecksum.substring(index + CHECKSUM_SEPARATOR.length()); + + if (md5sum.length() != 32) { + return null; + } + + return Pair.create(fileName, md5sum); + } + + // in: /path/to/orig_file + // out: /path/to/orig_file.BUWDnl831e4nldsf + public static String replaceFileNameWithChecksumFileName(String origPath, String fileNameWithChecksum) { + return origPath.substring(0, origPath.lastIndexOf(PATH_DELIMITER) + 1) + fileNameWithChecksum; + } + + public Status getBrokerAddress(Long beId, Catalog catalog, List brokerAddrs) { + // get backend + Backend be = Catalog.getCurrentSystemInfo().getBackend(beId); + if (be == null) { + return new Status(ErrCode.COMMON_ERROR, "backend " + beId + " is missing. " + + "failed to send upload snapshot task"); + } + + // get proper broker for this backend + BrokerAddress brokerAddr = null; + try { + brokerAddr = catalog.getBrokerMgr().getBroker(storage.getBrokerName(), be.getHost()); + } catch (AnalysisException e) { + return new Status(ErrCode.COMMON_ERROR, "failed to get address of broker " + + storage.getBrokerName() + " when try to send upload snapshot task: " + + e.getMessage()); + } + if (brokerAddr == null) { + return new Status(ErrCode.COMMON_ERROR, "failed to get address of broker " + + storage.getBrokerName() + " when try to send upload snapshot task"); + } + brokerAddrs.add(brokerAddr); + return Status.OK; + } + + public List getInfo() { + List info = Lists.newArrayList(); + info.add(String.valueOf(id)); + info.add(name); + info.add(TimeUtils.longToTimeString(createTime)); + info.add(String.valueOf(isReadOnly)); + info.add(location); + info.add(storage.getBrokerName()); + info.add(errMsg == null ? "N/A" : errMsg); + return info; + } + + public List> getSnapshotInfos(String snapshotName, String timestamp) + throws AnalysisException { + List> snapshotInfos = Lists.newArrayList(); + if (Strings.isNullOrEmpty(snapshotName)) { + // get all snapshot infos + List snapshotNames = Lists.newArrayList(); + Status status = listSnapshots(snapshotNames); + if (!status.ok()) { + throw new AnalysisException( + "Failed to list snapshot in repo: " + name + ", err: " + status.getErrMsg()); + } + + for (String ssName : snapshotNames) { + List info = getSnapshotInfo(ssName, null /* get all timestamp */); + snapshotInfos.add(info); + } + } else { + // get specified snapshot info + List info = getSnapshotInfo(snapshotName, timestamp); + snapshotInfos.add(info); + } + + return snapshotInfos; + } + + private List getSnapshotInfo(String snapshotName, String timestamp) { + List info = Lists.newArrayList(); + if (Strings.isNullOrEmpty(timestamp)) { + // get all timestamp + // path eg: /location/__palo_repository_repo_name/__ss_my_snap/__info_* + String infoFilePath = assembleJobInfoFilePath(snapshotName, -1); + LOG.debug("assemble infoFilePath: {}, snapshot: {}", infoFilePath, snapshotName); + List results = Lists.newArrayList(); + Status st = storage.list(infoFilePath + "*", results); + if (!st.ok()) { + info.add(snapshotName); + info.add("N/A"); + info.add("ERROR: Failed to get info: " + st.getErrMsg()); + } else { + info.add(snapshotName); + + List tmp = Lists.newArrayList(); + for (RemoteFile file : results) { + // __info_2018-04-18-20-11-00.Jdwnd9312sfdn1294343 + Pair pureFileName = decodeFileNameWithChecksum(file.getName()); + if (pureFileName == null) { + // maybe: __info_2018-04-18-20-11-00.part + tmp.add("Invalid: " + file.getName()); + continue; + } + tmp.add(disjoinPrefix(PREFIX_JOB_INFO, pureFileName.first)); + } + info.add(Joiner.on("\n").join(tmp)); + info.add(tmp.isEmpty() ? "ERROR: no snapshot" : "OK"); + } + } else { + // get specified timestamp + // path eg: /path/to/backup/__info_2081-04-19-12-59-11 + String localFilePath = BackupHandler.BACKUP_ROOT_DIR + "/" + Repository.PREFIX_JOB_INFO + timestamp; + try { + String remoteInfoFilePath = assembleJobInfoFilePath(snapshotName, -1) + timestamp; + Status st = download(remoteInfoFilePath, localFilePath); + if (!st.ok()) { + info.add(snapshotName); + info.add(timestamp); + info.add("N/A"); + info.add("N/A"); + info.add("Failed to get info: " + st.getErrMsg()); + } else { + try { + BackupJobInfo jobInfo = BackupJobInfo.fromFile(localFilePath); + info.add(snapshotName); + info.add(timestamp); + info.add(jobInfo.dbName); + info.add(jobInfo.toString(1)); + info.add("OK"); + } catch (IOException e) { + info.add(snapshotName); + info.add(timestamp); + info.add("N/A"); + info.add("N/A"); + info.add("Failed to read info from local file: " + e.getMessage()); + } + } + } finally { + // delete tmp local file + File localFile = new File(localFilePath); + if (localFile.exists()) { + localFile.delete(); + } + } + } + + return info; + } + + public static Repository read(DataInput in) throws IOException { + Repository repo = new Repository(); + repo.readFields(in); + return repo; + } + + @Override + public void write(DataOutput out) throws IOException { + out.writeLong(id); + Text.writeString(out, name); + out.writeBoolean(isReadOnly); + Text.writeString(out, location); + storage.write(out); + out.writeLong(createTime); + } + + @Override + public void readFields(DataInput in) throws IOException { + id = in.readLong(); + name = Text.readString(in); + isReadOnly = in.readBoolean(); + location = Text.readString(in); + storage = BlobStorage.read(in); + createTime = in.readLong(); + } +} diff --git a/fe/src/com/baidu/palo/backup/RepositoryMgr.java b/fe/src/com/baidu/palo/backup/RepositoryMgr.java new file mode 100644 index 0000000000..76d50c4739 --- /dev/null +++ b/fe/src/com/baidu/palo/backup/RepositoryMgr.java @@ -0,0 +1,154 @@ +// Modifications copyright (C) 2018, Baidu.com, Inc. +// Copyright 2018 The Apache Software Foundation + +// 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. + +package com.baidu.palo.backup; + +import com.baidu.palo.backup.Status.ErrCode; +import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.common.io.Writable; +import com.baidu.palo.common.util.Daemon; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.locks.ReentrantLock; + +/* + * A manager to manage all backup repositories + */ +public class RepositoryMgr extends Daemon implements Writable { + private static final Logger LOG = LogManager.getLogger(RepositoryMgr.class); + + // all key should be in lower case + private Map repoNameMap = Maps.newConcurrentMap(); + private Map repoIdMap = Maps.newConcurrentMap(); + + private ReentrantLock lock = new ReentrantLock(); + + public RepositoryMgr() { + super(Repository.class.getSimpleName(), 600 * 1000 /* 10min */); + } + + @Override + protected void runOneCycle() { + for (Repository repo : repoNameMap.values()) { + if (!repo.ping()) { + LOG.warn("Failed to connect repository {}. msg: {}", repo.getName(), repo.getErrorMsg()); + } + } + } + + public Status addAndInitRepoIfNotExist(Repository repo, boolean isReplay) { + lock.lock(); + try { + if (!repoNameMap.containsKey(repo.getName())) { + if (!isReplay) { + // create repository path and repo info file in remote storage + Status st = repo.initRepository(); + if (!st.ok()) { + return st; + } + } + repoNameMap.put(repo.getName(), repo); + repoIdMap.put(repo.getId(), repo); + + if (!isReplay) { + // write log + Catalog.getCurrentCatalog().getEditLog().logCreateRepository(repo); + } + + LOG.info("successfully adding repo {} to repository mgr. is replay: {}", + repo.getName(), isReplay); + return Status.OK; + } + return new Status(ErrCode.COMMON_ERROR, "repository with same name already exist: " + repo.getName()); + } finally { + lock.unlock(); + } + } + + public Repository getRepo(String repoName) { + return repoNameMap.get(repoName.toLowerCase()); + } + + public Repository getRepo(long repoId) { + return repoIdMap.get(repoId); + } + + public Status removeRepo(String repoName, boolean isReplay) { + lock.lock(); + try { + Repository repo = repoNameMap.remove(repoName.toLowerCase()); + if (repo != null) { + repoIdMap.remove(repo.getId()); + + if (!isReplay) { + // log + Catalog.getCurrentCatalog().getEditLog().logDropRepository(repoName); + } + LOG.info("sucessfully removing repo {} from repository mgr", repoName); + return Status.OK; + } + return new Status(ErrCode.NOT_FOUND, "repository does not exist"); + } finally { + lock.unlock(); + } + } + + public List> getReposInfo() { + List> infos = Lists.newArrayList(); + for (Repository repo : repoIdMap.values()) { + infos.add(repo.getInfo()); + } + return infos; + } + + public static RepositoryMgr read(DataInput in) throws IOException { + RepositoryMgr mgr = new RepositoryMgr(); + mgr.readFields(in); + return mgr; + } + + @Override + public void write(DataOutput out) throws IOException { + out.writeInt(repoNameMap.size()); + for (Repository repo : repoNameMap.values()) { + repo.write(out); + } + } + + @Override + public void readFields(DataInput in) throws IOException { + int size = in.readInt(); + for (int i = 0; i < size; i++) { + Repository repo = Repository.read(in); + repoNameMap.put(repo.getName(), repo); + repoIdMap.put(repo.getId(), repo); + } + } +} diff --git a/fe/src/com/baidu/palo/backup/RestoreFileMapping.java b/fe/src/com/baidu/palo/backup/RestoreFileMapping.java new file mode 100644 index 0000000000..17a6bdbffe --- /dev/null +++ b/fe/src/com/baidu/palo/backup/RestoreFileMapping.java @@ -0,0 +1,203 @@ +// Modifications copyright (C) 2018, Baidu.com, Inc. +// Copyright 2018 The Apache Software Foundation + +// 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. + +package com.baidu.palo.backup; + +import com.baidu.palo.common.io.Writable; + +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.Map; + +public class RestoreFileMapping implements Writable { + + public static class IdChain implements Writable { + // tblId, partId, idxId, tabletId, replicaId + private Long[] chain; + + private IdChain() { + // for persist + } + + public IdChain(Long... ids) { + Preconditions.checkState(ids.length == 5); + chain = ids; + } + + public Long getTblId() { + return chain[0]; + } + + public long getPartId() { + return chain[1]; + } + + public long getIdxId() { + return chain[2]; + } + + public long getTabletId() { + return chain[3]; + } + + public long getReplicaId() { + return chain[4]; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("["); + sb.append(Joiner.on("-").join(chain)); + sb.append("]"); + return sb.toString(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof IdChain)) { + return false; + } + + IdChain other = (IdChain) obj; + for (int i = 0; i < 5; i++) { + // DO NOT use ==, Long_1 != Long_2 + if (!chain[i].equals(other.chain[i])) { + return false; + } + } + + return true; + } + + @Override + public int hashCode() { + int code = chain[0].hashCode(); + for (int i = 1; i < 5; i++) { + code ^= chain[i].hashCode(); + } + return code; + } + + @Override + public void write(DataOutput out) throws IOException { + out.writeInt(chain.length); + for (Long id : chain) { + out.writeLong(id); + } + } + + @Override + public void readFields(DataInput in) throws IOException { + int size = in.readInt(); + chain = new Long[size]; + for (int i = 0; i < size; i++) { + chain[i] = in.readLong(); + } + } + + public static IdChain read(DataInput in) throws IOException { + IdChain chain = new IdChain(); + chain.readFields(in); + return chain; + } + } + + // catalog ids -> repository ids + private Map mapping = Maps.newHashMap(); + // tablet id -> is overwrite + private Map overwriteMap = Maps.newHashMap(); + + public RestoreFileMapping() { + + } + + public void putMapping(IdChain key, IdChain value, boolean overwrite) { + mapping.put(key, value); + overwriteMap.put(key.getTabletId(), overwrite); + } + + public IdChain get(IdChain key) { + return mapping.get(key); + } + + public Map getMapping() { + return mapping; + } + + public boolean isOverwrite(long tabletId) { + if (overwriteMap.containsKey(tabletId)) { + return overwriteMap.get(tabletId); + } + return false; + } + + public static RestoreFileMapping read(DataInput in) throws IOException { + RestoreFileMapping mapping = new RestoreFileMapping(); + mapping.readFields(in); + return mapping; + } + + @Override + public void write(DataOutput out) throws IOException { + out.writeInt(mapping.size()); + for (Map.Entry entry : mapping.entrySet()) { + entry.getKey().write(out); + entry.getValue().write(out); + } + + out.writeInt(overwriteMap.size()); + for (Map.Entry entry : overwriteMap.entrySet()) { + out.writeLong(entry.getKey()); + out.writeBoolean(entry.getValue()); + } + } + + @Override + public void readFields(DataInput in) throws IOException { + int size = in.readInt(); + for (int i = 0; i < size; i++) { + IdChain key = IdChain.read(in); + IdChain val = IdChain.read(in); + mapping.put(key, val); + } + + size = in.readInt(); + for (int i = 0; i < size; i++) { + long tabletId = in.readLong(); + boolean overwrite = in.readBoolean(); + overwriteMap.put(tabletId, overwrite); + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (Map.Entry entry : mapping.entrySet()) { + sb.append(entry.getKey()).append(" : ").append(entry.getValue()).append("\n"); + } + return sb.toString(); + } +} diff --git a/fe/src/com/baidu/palo/backup/RestoreJob.java b/fe/src/com/baidu/palo/backup/RestoreJob.java index 844234ae98..32a8578349 100644 --- a/fe/src/com/baidu/palo/backup/RestoreJob.java +++ b/fe/src/com/baidu/palo/backup/RestoreJob.java @@ -1,8 +1,13 @@ -// Copyright (c) 2017, Baidu.com, Inc. All Rights Reserved +// Modifications copyright (C) 2018, Baidu.com, Inc. +// Copyright 2018 The Apache Software Foundation -// Licensed 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 +// 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 // @@ -15,877 +20,1456 @@ package com.baidu.palo.backup; -import com.baidu.palo.alter.RollupHandler; -import com.baidu.palo.analysis.AddPartitionClause; -import com.baidu.palo.analysis.AddRollupClause; -import com.baidu.palo.analysis.AlterClause; -import com.baidu.palo.analysis.AlterTableStmt; -import com.baidu.palo.analysis.CreateTableStmt; -import com.baidu.palo.analysis.LabelName; +import com.baidu.palo.backup.BackupJobInfo.BackupIndexInfo; +import com.baidu.palo.backup.BackupJobInfo.BackupPartitionInfo; +import com.baidu.palo.backup.BackupJobInfo.BackupTableInfo; +import com.baidu.palo.backup.BackupJobInfo.BackupTabletInfo; +import com.baidu.palo.backup.RestoreFileMapping.IdChain; +import com.baidu.palo.backup.Status.ErrCode; +import com.baidu.palo.catalog.BrokerMgr.BrokerAddress; import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.catalog.Column; +import com.baidu.palo.catalog.DataProperty; import com.baidu.palo.catalog.Database; +import com.baidu.palo.catalog.KeysType; import com.baidu.palo.catalog.MaterializedIndex; import com.baidu.palo.catalog.OlapTable; import com.baidu.palo.catalog.OlapTable.OlapTableState; import com.baidu.palo.catalog.Partition; import com.baidu.palo.catalog.PartitionInfo; +import com.baidu.palo.catalog.PartitionKey; import com.baidu.palo.catalog.PartitionType; import com.baidu.palo.catalog.RangePartitionInfo; import com.baidu.palo.catalog.Replica; +import com.baidu.palo.catalog.Replica.ReplicaState; import com.baidu.palo.catalog.Table; -import com.baidu.palo.catalog.Tablet; -import com.baidu.palo.catalog.TabletInvertedIndex; -import com.baidu.palo.catalog.TabletMeta; import com.baidu.palo.catalog.Table.TableType; -import com.baidu.palo.common.AnalysisException; -import com.baidu.palo.common.DdlException; +import com.baidu.palo.catalog.Tablet; +import com.baidu.palo.catalog.TabletMeta; +import com.baidu.palo.common.Config; +import com.baidu.palo.common.MarkedCountDownLatch; import com.baidu.palo.common.Pair; import com.baidu.palo.common.io.Text; import com.baidu.palo.common.util.TimeUtils; -import com.baidu.palo.common.util.Util; import com.baidu.palo.task.AgentBatchTask; import com.baidu.palo.task.AgentTask; import com.baidu.palo.task.AgentTaskExecutor; import com.baidu.palo.task.AgentTaskQueue; -import com.baidu.palo.task.RestoreTask; +import com.baidu.palo.task.CreateReplicaTask; +import com.baidu.palo.task.DirMoveTask; +import com.baidu.palo.task.DownloadTask; +import com.baidu.palo.task.SnapshotTask; +import com.baidu.palo.thrift.TFinishTaskRequest; +import com.baidu.palo.thrift.TStatusCode; +import com.baidu.palo.thrift.TStorageMedium; +import com.baidu.palo.thrift.TStorageType; import com.baidu.palo.thrift.TTaskType; +import com.google.common.base.Joiner; import com.google.common.base.Preconditions; -import com.google.common.base.Strings; +import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.HashBasedTable; import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import com.google.common.collect.Sets; +import com.google.common.collect.Range; +import com.google.common.collect.Table.Cell; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.DataInput; import java.io.DataOutput; -import java.io.File; import java.io.IOException; import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; -import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; -public class RestoreJob extends AbstractBackupJob { +public class RestoreJob extends AbstractJob { private static final Logger LOG = LogManager.getLogger(RestoreJob.class); public enum RestoreJobState { - PENDING, - RESTORE_META, - DOWNLOAD, - DOWNLOADING, + PENDING, // Job is newly created. Check and prepare meta in catalog. Create replica if necessary. + // Waiting for replica creation finished synchronously, then sending snapshot tasks. + // then transfer to SNAPSHOTING + SNAPSHOTING, // Waiting for snapshot finished. Than transfer to DOWNLOAD. + DOWNLOAD, // Send download tasks. + DOWNLOADING, // Waiting for download finished. + COMMIT, // After download finished, all data is ready for taking effect. + // Send movement tasks to BE, than transfer to COMMITTING + COMMITTING, // wait all tasks finished. Transfer to FINISHED FINISHED, CANCELLED } + private String backupTimestamp; + + private BackupJobInfo jobInfo; + private boolean allowLoad; + private RestoreJobState state; - private Map> tableToPartitionNames; - private Map tableRenameMap; + private BackupMeta backupMeta; - private Map tableToCreateTableStmt; - private Map tableToRollupStmt; - private com.google.common.collect.Table tableToPartitionStmts; + private RestoreFileMapping fileMapping = new RestoreFileMapping(); - private Map tableToReplace; + private long metaPreparedTime = -1; + private long snapshotFinishedTime = -1; + private long downloadFinishedTime = -1; - private Map restoredTables; - // tableid - partition name - partition - private com.google.common.collect.Table restoredPartitions; + private int restoreReplicationNum; - private long metaRestoredTime; - private long downloadFinishedTime; + // this 2 members is to save all newly restored objs + // tbl name -> part + private List> restoredPartitions = Lists.newArrayList(); + private List restoredTbls = Lists.newArrayList(); + + // save all restored partitions' version info which are already exist in catalog + // table id -> partition id -> (version, version hash) + private com.google.common.collect.Table> restoredVersionInfo = HashBasedTable.create(); + // tablet id->(be id -> snapshot info) + private com.google.common.collect.Table snapshotInfos = HashBasedTable.create(); + + private Map unfinishedSignatureToId = Maps.newConcurrentMap(); public RestoreJob() { - super(); + super(JobType.RESTORE); } - public RestoreJob(long jobId, long dbId, LabelName labelName, String restorePath, - Map remoteProperties, Map> tableToPartitionNames, - Map tableRenameMap) { - super(jobId, dbId, labelName, restorePath, remoteProperties); - state = RestoreJobState.PENDING; - - this.tableToPartitionNames = tableToPartitionNames; - this.tableRenameMap = tableRenameMap; - - this.tableToCreateTableStmt = Maps.newHashMap(); - this.tableToRollupStmt = Maps.newHashMap(); - this.tableToPartitionStmts = HashBasedTable.create(); - - this.tableToReplace = Maps.newHashMap(); - this.restoredTables = Maps.newHashMap(); - this.restoredPartitions = HashBasedTable.create(); - - this.metaRestoredTime = -1L; - this.downloadFinishedTime = -1L; - } - - public void setState(RestoreJobState state) { - this.state = state; + public RestoreJob(String label, String backupTs, long dbId, String dbName, BackupJobInfo jobInfo, + boolean allowLoad, int restoreReplicationNum, long timeoutMs, + Catalog catalog, long repoId) { + super(JobType.RESTORE, label, dbId, dbName, timeoutMs, catalog, repoId); + this.backupTimestamp = backupTs; + this.jobInfo = jobInfo; + this.allowLoad = allowLoad; + this.restoreReplicationNum = restoreReplicationNum; + this.state = RestoreJobState.PENDING; } public RestoreJobState getState() { return state; } - public long getMetaRestoredTime() { - return metaRestoredTime; + public RestoreFileMapping getFileMapping() { + return fileMapping; } - public long getDownloadFinishedTime() { - return downloadFinishedTime; + public synchronized boolean finishTabletSnapshotTask(SnapshotTask task, TFinishTaskRequest request) { + Preconditions.checkState(task.getJobId() == jobId); + Preconditions.checkState(task.getDbId() == dbId); + + if (request.getTask_status().getStatus_code() != TStatusCode.OK) { + taskErrMsg.put(task.getSignature(), Joiner.on(",").join(request.getTask_status().getError_msgs())); + return false; + } + + Preconditions.checkState(request.isSetSnapshot_path()); + + // snapshot path does not contains last 'tablet_id' and 'schema_hash' dir + // eg: + // /path/to/your/be/data/snapshot/20180410102311.0/ + // Full path will look like: + // /path/to/your/be/data/snapshot/20180410102311.0/10006/352781111/ + SnapshotInfo info = new SnapshotInfo(task.getDbId(), task.getTableId(), task.getPartitionId(), + task.getIndexId(), task.getTabletId(), task.getBackendId(), + task.getSchemaHash(), request.getSnapshot_path(), Lists.newArrayList()); + + snapshotInfos.put(task.getTabletId(), task.getBackendId(), info); + Long removedTabletId = unfinishedSignatureToId.remove(task.getSignature()); + if (removedTabletId != null) { + taskErrMsg.remove(task.getSignature()); + Preconditions.checkState(task.getTabletId() == removedTabletId, removedTabletId); + LOG.debug("get finished snapshot info: {}, unfinished tasks num: {}, remove result: {}. {}", + info, unfinishedSignatureToId.size(), this); + return true; + } + + return false; } - public Map> getTableToPartitionNames() { - return tableToPartitionNames; + public synchronized boolean finishTabletDownloadTask(DownloadTask task, TFinishTaskRequest request) { + Preconditions.checkState(task.getJobId() == jobId); + Preconditions.checkState(task.getDbId() == dbId); + + if (request.getTask_status().getStatus_code() != TStatusCode.OK) { + taskErrMsg.put(task.getSignature(), Joiner.on(",").join(request.getTask_status().getError_msgs())); + return false; + } + + Preconditions.checkState(request.isSetDownloaded_tablet_ids()); + + for (Long tabletId : request.getDownloaded_tablet_ids()) { + SnapshotInfo info = snapshotInfos.get(tabletId, task.getBackendId()); + if (info == null) { + LOG.error("failed to find snapshot infos of tablet {} in be {}, {}", + tabletId, task.getBackendId(), this); + return false; + } + } + + Long beId = unfinishedSignatureToId.remove(task.getSignature()); + if (beId == null || beId != task.getBackendId()) { + LOG.error("invalid download task: {}. {}", task, this); + return false; + } + + taskErrMsg.remove(task.getSignature()); + return true; + } + + public synchronized boolean finishDirMoveTask(DirMoveTask task, TFinishTaskRequest request) { + Preconditions.checkState(task.getJobId() == jobId); + Preconditions.checkState(task.getDbId() == dbId); + + if (request.getTask_status().getStatus_code() != TStatusCode.OK) { + taskErrMsg.put(task.getSignature(), Joiner.on(",").join(request.getTask_status().getError_msgs())); + return false; + } + + Long tabletId = unfinishedSignatureToId.remove(task.getSignature()); + if (tabletId == null || tabletId != task.getTabletId()) { + LOG.error("invalid dir move task: {}. {}", task, this); + return false; + } + + taskErrMsg.remove(task.getSignature()); + return true; } @Override - public List getJobInfo() { - List jobInfo = Lists.newArrayList(); - jobInfo.add(jobId); - jobInfo.add(getLabel()); - jobInfo.add(state.name()); - jobInfo.add(TimeUtils.longToTimeString(createTime)); - jobInfo.add(TimeUtils.longToTimeString(metaRestoredTime)); - jobInfo.add(TimeUtils.longToTimeString(downloadFinishedTime)); - jobInfo.add(TimeUtils.longToTimeString(finishedTime)); - jobInfo.add(errMsg); - jobInfo.add(remotePath); - jobInfo.add(getLeftTasksNum()); - return jobInfo; + public synchronized void replayRun() { + LOG.info("replay run restore job: {}", this); + switch (state) { + case DOWNLOAD: + replayCheckAndPrepareMeta(); + break; + case FINISHED: + replayWaitingAllTabletsCommitted(); + break; + default: + break; + } } @Override - public void runOnce() { - LOG.debug("begin to run restore job: {}, state: {}", jobId, state.name()); + public synchronized void replayCancel() { + cancelInternal(true /* is replay */); + } + + @Override + public boolean isPending() { + return state == RestoreJobState.PENDING; + } + + @Override + public boolean isCancelled() { + return state == RestoreJobState.CANCELLED; + } + + @Override + public void run() { + if (state == RestoreJobState.FINISHED || state == RestoreJobState.CANCELLED) { + return; + } + + if (System.currentTimeMillis() - createTime > timeoutMs) { + status = new Status(ErrCode.TIMEOUT, ""); + cancel(); + return; + } + + // get repo if not set + if (repo == null) { + repo = catalog.getBackupHandler().getRepoMgr().getRepo(repoId); + if (repo == null) { + status = new Status(ErrCode.COMMON_ERROR, "failed to get repository: " + repoId); + cancelInternal(false); + return; + } + } + + LOG.info("run restore job: {}", this); + + switch (state) { + case PENDING: + checkAndPrepareMeta(); + break; + case SNAPSHOTING: + waitingAllSnapshotsFinished(); + break; + case DOWNLOAD: + downloadSnapshots(); + break; + case DOWNLOADING: + waitingAllDownloadFinished(); + break; + case COMMIT: + commit(); + break; + case COMMITTING: + waitingAllTabletsCommitted(); + break; + default: + break; + } + + if (!status.ok()) { + cancelInternal(false); + } + } + + /* + * Restore rules as follow: + * A. Table already exist + * A1. Partition already exist, generate file mapping + * A2. Partition does not exist, add restored partition to the table. + * Reset all index/tablet/replica id, and create replica on BE outside the db lock. + * B. Table does not exist + * B1. Add table to the db, reset all table/index/tablet/replica id, + * and create replica on BE outside the db lock. + * + * All newly created table/partition/index/tablet/replica should be saved for rolling back. + * + * Step: + * 1. download and deserialize backup meta from repository. + * 2. set all existing restored table's state to RESTORE. + * 3. check if the expected restore objs are valid. + * 4. create replicas if necessary. + * 5. add restored objs to catalog. + * 6. make snapshot for all replicas for incremental download later. + */ + private void checkAndPrepareMeta() { + Database db = catalog.getDb(dbId); + if (db == null) { + status = new Status(ErrCode.NOT_FOUND, "database " + dbId + " does not exist"); + return; + } + + // generate job id + jobId = catalog.getNextId(); + + // deserialize meta + if (!downloadAndDeserializeMetaInfo()) { + return; + } + Preconditions.checkNotNull(backupMeta); + + // Set all restored tbls‘ state to RESTORE + // Table's origin state must be NORMAL and does not have unfinished load job. + db.writeLock(); try { + for (BackupTableInfo tblInfo : jobInfo.tables.values()) { + Table tbl = db.getTable(jobInfo.getAliasByOriginNameIfSet(tblInfo.name)); + if (tbl == null) { + continue; + } + + if (tbl.getType() != TableType.OLAP) { + status = new Status(ErrCode.COMMON_ERROR, "Only support retore olap table: " + tbl.getName()); + return; + } + + OlapTable olapTbl = (OlapTable) tbl; + if (olapTbl.getState() != OlapTableState.NORMAL) { + status = new Status(ErrCode.COMMON_ERROR, + "Table " + tbl.getName() + "'s state is not NORMAL: " + olapTbl.getState().name()); + return; + } + + for (Partition partition : olapTbl.getPartitions()) { + if (!catalog.getLoadInstance().checkPartitionLoadFinished(partition.getId(), null)) { + status = new Status(ErrCode.COMMON_ERROR, + "Table " + tbl.getName() + "'s has unfinished load job"); + return; + } + } + + olapTbl.setState(OlapTableState.RESTORE); + } + } finally { + db.writeUnlock(); + } + + // Check and prepare meta objects. + AgentBatchTask batchTask = new AgentBatchTask(); + db.readLock(); + try { + for (BackupTableInfo tblInfo : jobInfo.tables.values()) { + Table remoteTbl = backupMeta.getTable(tblInfo.name); + Preconditions.checkNotNull(remoteTbl); + Table localTbl = db.getTable(jobInfo.getAliasByOriginNameIfSet(tblInfo.name)); + if (localTbl != null) { + // table already exist, check schema + if (localTbl.getType() != TableType.OLAP) { + status = new Status(ErrCode.COMMON_ERROR, + "Only support retore olap table: " + localTbl.getName()); + return; + } + OlapTable localOlapTbl = (OlapTable) localTbl; + OlapTable remoteOlapTbl = (OlapTable) remoteTbl; + + List intersectPartNames = Lists.newArrayList(); + Status st = localOlapTbl.getIntersectPartNamesWith(remoteOlapTbl, intersectPartNames); + if (!st.ok()) { + status = st; + return; + } + LOG.debug("get intersect part names: {}, job: {}", intersectPartNames, this); + if (localOlapTbl.getSignature(BackupHandler.SIGNATURE_VERSION, intersectPartNames) + != remoteOlapTbl.getSignature(BackupHandler.SIGNATURE_VERSION, intersectPartNames)) { + status = new Status(ErrCode.COMMON_ERROR, "Table " + jobInfo.getAliasByOriginNameIfSet(tblInfo.name) + + " already exist but with different schema"); + return; + } + + // Table with same name and has same schema. Check partition + for (BackupPartitionInfo backupPartInfo : tblInfo.partitions.values()) { + Partition localPartition = localOlapTbl.getPartition(backupPartInfo.name); + if (localPartition != null) { + // Partition already exist. + PartitionInfo localPartInfo = localOlapTbl.getPartitionInfo(); + if (localPartInfo.getType() == PartitionType.RANGE) { + // If this is a range partition, check range + RangePartitionInfo localRangePartInfo = (RangePartitionInfo) localPartInfo; + RangePartitionInfo remoteRangePartInfo + = (RangePartitionInfo) remoteOlapTbl.getPartitionInfo(); + Range localRange = localRangePartInfo.getRange(localPartition.getId()); + Range remoteRange = remoteRangePartInfo.getRange(backupPartInfo.id); + if (localRange.equals(remoteRange)) { + // Same partition, same range + if (localRangePartInfo.getReplicationNum(localPartition.getId()) != restoreReplicationNum) { + status = new Status(ErrCode.COMMON_ERROR, "Parition " + backupPartInfo.name + + " in table " + localTbl.getName() + + " has different replication num '" + + localRangePartInfo.getReplicationNum(localPartition.getId()) + + "' with parition in repository"); + return; + } + genFileMapping(localOlapTbl, localPartition, tblInfo.id, backupPartInfo, + true /* overwrite when commit */); + restoredVersionInfo.put(localOlapTbl.getId(), localPartition.getId(), + Pair.create(backupPartInfo.version, + backupPartInfo.versionHash)); + } else { + // Same partition name, different range + status = new Status(ErrCode.COMMON_ERROR, "Parition " + backupPartInfo.name + + " in table " + localTbl.getName() + + " has different range with parition in repository"); + return; + } + } else { + // If this is a single partitioned table. + if (localPartInfo.getReplicationNum(localPartition.getId()) != restoreReplicationNum) { + status = new Status(ErrCode.COMMON_ERROR, "Parition " + backupPartInfo.name + + " in table " + localTbl.getName() + + " has different replication num '" + + localPartInfo.getReplicationNum(localPartition.getId()) + + "' with parition in repository"); + return; + } + + // No need to check range, just generate file mapping + genFileMapping(localOlapTbl, localPartition, tblInfo.id, backupPartInfo, + true /* overwrite when commit */); + restoredVersionInfo.put(localOlapTbl.getId(), localPartition.getId(), + Pair.create(backupPartInfo.version, + backupPartInfo.versionHash)); + } + } else { + // partitions does not exist + PartitionInfo localPartitionInfo = localOlapTbl.getPartitionInfo(); + if (localPartitionInfo.getType() == PartitionType.RANGE) { + // Check if the partition range can be added to the table + RangePartitionInfo localRangePartitionInfo = (RangePartitionInfo) localPartitionInfo; + RangePartitionInfo remoteRangePartitionInfo + = (RangePartitionInfo) remoteOlapTbl.getPartitionInfo(); + Range remoteRange = remoteRangePartitionInfo.getRange(backupPartInfo.id); + if (!localRangePartitionInfo.checkRange(remoteRange)) { + status = new Status(ErrCode.COMMON_ERROR, "Parition " + backupPartInfo.name + + " in table " + localTbl.getName() + + " has conflict range with existing ranges"); + return; + } else { + // this partition can be added to this table, set ids + Partition restorePart = resetPartitionForRestore(localOlapTbl, remoteOlapTbl, + backupPartInfo.name, + db.getClusterName(), + restoreReplicationNum); + if (restorePart == null) { + return; + } + restoredPartitions.add(Pair.create(localOlapTbl.getName(), restorePart)); + } + } else { + // It is impossible that a single partitioned table exist without any existing partition + status = new Status(ErrCode.COMMON_ERROR, + "No partition exist in single partitioned table " + localOlapTbl.getName()); + return; + } + } + } + } else { + // Table does not exist + OlapTable remoteOlapTbl = (OlapTable) remoteTbl; + + // Retain only expected restore partitions in this table; + Set allPartNames = remoteOlapTbl.getPartitionNames(); + for (String partName : allPartNames) { + if (!tblInfo.containsPart(partName)) { + remoteOlapTbl.dropPartition(-1 /* db id is useless here */, partName, + true /* act like replay to disable recycle bin action */); + } + } + + // reset all ids in this table + Status st = remoteOlapTbl.resetIdsForRestore(catalog, db, restoreReplicationNum); + if (!st.ok()) { + status = st; + return; + } + + // DO NOT set remote table's new name here, cause we will still need the origin name later + // remoteOlapTbl.setName(jobInfo.getAliasByOriginNameIfSet(tblInfo.name)); + remoteOlapTbl.setState(allowLoad ? OlapTableState.RESTORE_WITH_LOAD : OlapTableState.RESTORE); + LOG.debug("put remote table {} to restoredTbls", remoteOlapTbl.getName()); + restoredTbls.add(remoteOlapTbl); + } + } // end of all restore tables + + LOG.debug("finished to prepare restored partitions and tables. {}", this); + // for now, nothing is modified in catalog + + // generate create replica tasks for all restored partitions + for (Pair entry : restoredPartitions) { + OlapTable localTbl = (OlapTable) db.getTable(entry.first); + Preconditions.checkNotNull(localTbl, localTbl.getName()); + Partition restorePart = entry.second; + OlapTable remoteTbl = (OlapTable) backupMeta.getTable(entry.first); + BackupPartitionInfo backupPartitionInfo + = jobInfo.getTableInfo(entry.first).getPartInfo(restorePart.getName()); + + Set bfColumns = localTbl.getCopiedBfColumns(); + double bfFpp = localTbl.getBfFpp(); + for (MaterializedIndex restoredIdx : restorePart.getMaterializedIndices()) { + short shortKeyColumnCount = localTbl.getShortKeyColumnCountByIndexId(restoredIdx.getId()); + int schemaHash = localTbl.getSchemaHashByIndexId(restoredIdx.getId()); + KeysType keysType = localTbl.getKeysType(); + List columns = localTbl.getSchemaByIndexId(restoredIdx.getId()); + for (Tablet restoreTablet : restoredIdx.getTablets()) { + TabletMeta tabletMeta = new TabletMeta(db.getId(), localTbl.getId(), + restorePart.getId(), restoredIdx.getId(), schemaHash); + Catalog.getCurrentInvertedIndex().addTablet(restoreTablet.getId(), tabletMeta); + for (Replica restoreReplica : restoreTablet.getReplicas()) { + Catalog.getCurrentInvertedIndex().addReplica(restoreTablet.getId(), restoreReplica); + CreateReplicaTask task = new CreateReplicaTask(restoreReplica.getBackendId(), schemaHash, + localTbl.getId(), restorePart.getId(), restoredIdx.getId(), + restoreTablet.getId(), shortKeyColumnCount, + schemaHash, restoreReplica.getVersion(), restoreReplica.getVersionHash(), + keysType, TStorageType.COLUMN, + TStorageMedium.HDD /* all restored replicas will be saved to HDD */, + columns, bfColumns, bfFpp, null); + task.setInRestoreMode(true); + batchTask.addTask(task); + } + } + } + + genFileMapping(localTbl, restorePart, remoteTbl.getId(), backupPartitionInfo, + allowLoad ? false : true /* if allow load, do not overwrite when commit */); + } + + // generate create replica task for all restored tables + for (OlapTable restoreTbl : restoredTbls) { + PartitionInfo partInfo = restoreTbl.getPartitionInfo(); + for (Partition restorePart : restoreTbl.getPartitions()) { + TStorageMedium storageMedium = partInfo.getDataProperty(restorePart.getId()).getStorageMedium(); + Set bfColumns = restoreTbl.getCopiedBfColumns(); + double bfFpp = restoreTbl.getBfFpp(); + for (MaterializedIndex index : restorePart.getMaterializedIndices()) { + short shortKeyColumnCount = restoreTbl.getShortKeyColumnCountByIndexId(index.getId()); + int schemaHash = restoreTbl.getSchemaHashByIndexId(index.getId()); + KeysType keysType = restoreTbl.getKeysType(); + List columns = restoreTbl.getSchemaByIndexId(index.getId()); + for (Tablet tablet : index.getTablets()) { + TabletMeta tabletMeta = new TabletMeta(db.getId(), restoreTbl.getId(), + restorePart.getId(), index.getId(), schemaHash); + Catalog.getCurrentInvertedIndex().addTablet(tablet.getId(), tabletMeta); + for (Replica replica : tablet.getReplicas()) { + Catalog.getCurrentInvertedIndex().addReplica(tablet.getId(), replica); + CreateReplicaTask task = new CreateReplicaTask(replica.getBackendId(), schemaHash, + restoreTbl.getId(), restorePart.getId(), index.getId(), tablet.getId(), + shortKeyColumnCount, schemaHash, replica.getVersion(), replica.getVersionHash(), + keysType, TStorageType.COLUMN, storageMedium, columns, + bfColumns, bfFpp, null); + task.setInRestoreMode(true); + batchTask.addTask(task); + } + } + } + BackupTableInfo backupTableInfo = jobInfo.getTableInfo(restoreTbl.getName()); + genFileMapping(restoreTbl, restorePart, backupTableInfo.id, + backupTableInfo.getPartInfo(restorePart.getName()), + allowLoad ? false : true /* if allow load, do not overwrite when commit */); + } + // set restored table's new name after all 'genFileMapping' + restoreTbl.setName(jobInfo.getAliasByOriginNameIfSet(restoreTbl.getName())); + } + + LOG.debug("finished to generate create replica tasks. {}", this); + } finally { + db.readUnlock(); + } + + // Send create replica task to BE outside the db lock + if (batchTask.getTaskNum() > 0) { + MarkedCountDownLatch latch = new MarkedCountDownLatch(batchTask.getTaskNum()); + for (AgentTask task : batchTask.getAllTasks()) { + latch.addMark(((CreateReplicaTask) task).getBackendId(), ((CreateReplicaTask) task).getTabletId()); + ((CreateReplicaTask) task).setLatch(latch); + AgentTaskQueue.addTask(task); + } + AgentTaskExecutor.submit(batchTask); + + // estimate timeout, at most 10 seconds + long timeout = Config.tablet_create_timeout_second * 1000L * batchTask.getTaskNum(); + timeout = Math.min(10 * 1000, timeout); + boolean ok = false; + try { + LOG.info("begin to send create replica tasks to BE for restore. total {} tasks. timeout: {}", + batchTask.getTaskNum(), timeout); + ok = latch.await(timeout, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + LOG.warn("InterruptedException: ", e); + ok = false; + } + + if (ok) { + LOG.debug("finished to create all restored replcias. {}", this); + // add all restored partition and tbls to catalog + db.writeLock(); + try { + // add restored partitions. + // table should be in State RESTORE, so no other partitions can be + // added to or removed from this table during the restore process. + for (Pair entry : restoredPartitions) { + OlapTable localTbl = (OlapTable) db.getTable(entry.first); + Partition restoredPart = entry.second; + OlapTable remoteTbl = (OlapTable) backupMeta.getTable(entry.first); + RangePartitionInfo localPartitionInfo = (RangePartitionInfo) localTbl.getPartitionInfo(); + RangePartitionInfo remotePartitionInfo = (RangePartitionInfo) remoteTbl.getPartitionInfo(); + BackupPartitionInfo backupPartitionInfo + = jobInfo.getTableInfo(entry.first).getPartInfo(restoredPart.getName()); + long remotePartId = backupPartitionInfo.id; + Range remoteRange = remotePartitionInfo.getRange(remotePartId); + DataProperty remoteDataProperty = remotePartitionInfo.getDataProperty(remotePartId); + localPartitionInfo.addPartitionForRestore(restoredPart.getId(), remoteRange, + remoteDataProperty, (short) restoreReplicationNum); + localTbl.addPartition(restoredPart); + } + + // add restored tables + for (OlapTable tbl : restoredTbls) { + if (!db.createTable(tbl)) { + status = new Status(ErrCode.COMMON_ERROR, "Table " + tbl.getName() + + " already exist in db: " + db.getFullName()); + return; + } + } + } finally { + db.writeUnlock(); + } + } else { + Collection> unfinishedMarks = latch.getLeftMarks(); + String idStr = Joiner.on(", ").join(unfinishedMarks); + status = new Status(ErrCode.COMMON_ERROR, + "Failed to create replicas for restore. unfinished marks: " + idStr); + return; + } + } + + LOG.info("finished to prepare meta. begin to make snapshot. {}", this); + + // begin to make snapshots for all replicas + // snapshot is for incremental download + unfinishedSignatureToId.clear(); + taskErrMsg.clear(); + batchTask = new AgentBatchTask(); + db.readLock(); + try { + for (IdChain idChain : fileMapping.getMapping().keySet()) { + OlapTable tbl = (OlapTable) db.getTable(idChain.getTblId()); + Partition part = tbl.getPartition(idChain.getPartId()); + MaterializedIndex index = part.getIndex(idChain.getIdxId()); + Tablet tablet = index.getTablet(idChain.getTabletId()); + Replica replica = tablet.getReplicaById(idChain.getReplicaId()); + long signature = catalog.getNextId(); + SnapshotTask task = new SnapshotTask(null, replica.getBackendId(), signature, + jobId, db.getId(), + tbl.getId(), part.getId(), index.getId(), tablet.getId(), + part.getCommittedVersion(), part.getCommittedVersionHash(), + tbl.getSchemaHashByIndexId(index.getId()), timeoutMs, + true /* is restore task*/); + batchTask.addTask(task); + unfinishedSignatureToId.put(signature, tablet.getId()); + } + } finally { + db.readUnlock(); + } + + // send tasks + for (AgentTask task : batchTask.getAllTasks()) { + AgentTaskQueue.addTask(task); + } + AgentTaskExecutor.submit(batchTask); + + metaPreparedTime = System.currentTimeMillis(); + state = RestoreJobState.SNAPSHOTING; + + // No log here, PENDING state restore job will redo this method + LOG.info("finished to prepare meta and send snapshot tasks, num: {}. {}", + batchTask.getTaskNum(), this); + return; + } + + // reset remote partition. + // reset all id in remote partition, but DO NOT modify any exist catalog objects. + private Partition resetPartitionForRestore(OlapTable localTbl, OlapTable remoteTbl, String partName, + String clusterName, int restoreReplicationNum) { + Preconditions.checkState(localTbl.getPartition(partName) == null); + Partition remotePart = remoteTbl.getPartition(partName); + Preconditions.checkNotNull(remotePart); + PartitionInfo localPartitionInfo = localTbl.getPartitionInfo(); + Preconditions.checkState(localPartitionInfo.getType() == PartitionType.RANGE); + + // generate new partition id + long newPartId = catalog.getNextId(); + remotePart.setIdForRestore(newPartId); + + // indexes + Map localIdxNameToId = localTbl.getIndexNameToId(); + for (String localidxName : localIdxNameToId.keySet()) { + // set ids of indexes in remote partition to the local index ids + long remoteIdxId = remoteTbl.getIndexIdByName(localidxName); + MaterializedIndex remoteIdx = remotePart.getIndex(remoteIdxId); + long localIdxId = localIdxNameToId.get(localidxName); + remoteIdx.setIdForRestore(localIdxId); + if (localIdxId != localTbl.getId()) { + // not base table, reset + remotePart.deleteRollupIndex(remoteIdxId); + remotePart.createRollupIndex(remoteIdx); + } + } + + // save version info for creating replicas + long committedVersion = remotePart.getCommittedVersion(); + long committedVersionHash = remotePart.getCommittedVersionHash(); + + // tablets + for (MaterializedIndex remoteIdx : remotePart.getMaterializedIndices()) { + int remotetabletSize = remoteIdx.getTablets().size(); + remoteIdx.clearTabletsForRestore(); + for (int i = 0; i < remotetabletSize; i++) { + // generate new tablet id + long newTabletId = catalog.getNextId(); + Tablet newTablet = new Tablet(newTabletId); + // add tablet to index, but not add to TabletInvertedIndex + remoteIdx.addTablet(newTablet, null /* tablet meta */, true /* is restore */); + + // replicas + List beIds = Catalog.getCurrentSystemInfo().seqChooseBackendIds(restoreReplicationNum, true, + true, clusterName); + if (beIds == null) { + status = new Status(ErrCode.COMMON_ERROR, + "failed to get enough backends for creating replica of tablet " + + newTabletId + ". need: " + restoreReplicationNum); + return null; + } + for (Long beId : beIds) { + long newReplicaId = catalog.getNextId(); + Replica newReplica = new Replica(newReplicaId, beId, ReplicaState.NORMAL, + committedVersion, committedVersionHash); + newTablet.addReplica(newReplica, true /* is restore */); + } + } + } + return remotePart; + } + + // files in repo to files in local + private void genFileMapping(OlapTable localTbl, Partition localPartition, Long remoteTblId, + BackupPartitionInfo backupPartInfo, boolean overwrite) { + for (MaterializedIndex localIdx : localPartition.getMaterializedIndices()) { + LOG.debug("get index id: {}, index name: {}", localIdx.getId(), + localTbl.getIndexNameById(localIdx.getId())); + BackupIndexInfo backupIdxInfo = backupPartInfo.getIdx(localTbl.getIndexNameById(localIdx.getId())); + Preconditions.checkState(backupIdxInfo.tablets.size() == localIdx.getTablets().size()); + for (int i = 0; i < localIdx.getTablets().size(); i++) { + Tablet localTablet = localIdx.getTablets().get(i); + BackupTabletInfo backupTabletInfo = backupIdxInfo.tablets.get(i); + for (Replica localReplica : localTablet.getReplicas()) { + IdChain src = new IdChain(remoteTblId, backupPartInfo.id, backupIdxInfo.id, backupTabletInfo.id, + -1L /* no replica id */); + IdChain dest = new IdChain(localTbl.getId(), localPartition.getId(), + localIdx.getId(), localTablet.getId(), localReplica.getId()); + fileMapping.putMapping(dest, src, overwrite); + } + } + } + } + + private boolean downloadAndDeserializeMetaInfo() { + List backupMetas = Lists.newArrayList(); + Status st = repo.getSnapshotMetaFile(jobInfo.name, backupMetas); + if (!st.ok()) { + status = st; + return false; + } + Preconditions.checkState(backupMetas.size() == 1); + backupMeta = backupMetas.get(0); + return true; + } + + private void replayCheckAndPrepareMeta() { + Database db = catalog.getDb(dbId); + db.writeLock(); + try { + // replay set all existing tables's state to RESTORE + for (BackupTableInfo tblInfo : jobInfo.tables.values()) { + Table tbl = db.getTable(jobInfo.getAliasByOriginNameIfSet(tblInfo.name)); + if (tbl == null) { + continue; + } + OlapTable olapTbl = (OlapTable) tbl; + olapTbl.setState(OlapTableState.RESTORE); + } + + // restored partitions + for (Pair entry : restoredPartitions) { + OlapTable localTbl = (OlapTable) db.getTable(entry.first); + Partition restorePart = entry.second; + OlapTable remoteTbl = (OlapTable) backupMeta.getTable(entry.first); + RangePartitionInfo localPartitionInfo = (RangePartitionInfo) localTbl.getPartitionInfo(); + RangePartitionInfo remotePartitionInfo = (RangePartitionInfo) remoteTbl.getPartitionInfo(); + BackupPartitionInfo backupPartitionInfo = jobInfo.getTableInfo(entry.first).getPartInfo(restorePart.getName()); + long remotePartId = backupPartitionInfo.id; + Range remoteRange = remotePartitionInfo.getRange(remotePartId); + DataProperty remoteDataProperty = remotePartitionInfo.getDataProperty(remotePartId); + localPartitionInfo.addPartitionForRestore(restorePart.getId(), remoteRange, + remoteDataProperty, (short) restoreReplicationNum); + localTbl.addPartition(restorePart); + + // modify tablet inverted index + for (MaterializedIndex restoreIdx : restorePart.getMaterializedIndices()) { + int schemaHash = localTbl.getSchemaHashByIndexId(restoreIdx.getId()); + for (Tablet restoreTablet : restoreIdx.getTablets()) { + TabletMeta tabletMeta = new TabletMeta(db.getId(), localTbl.getId(), + restorePart.getId(), restoreIdx.getId(), schemaHash); + Catalog.getCurrentInvertedIndex().addTablet(restoreTablet.getId(), tabletMeta); + for (Replica restoreReplica : restoreTablet.getReplicas()) { + Catalog.getCurrentInvertedIndex().addReplica(restoreTablet.getId(), restoreReplica); + } + } + } + } + + // restored tables + for (OlapTable restoreTbl : restoredTbls) { + db.createTable(restoreTbl); + // modify tablet inverted index + for (Partition restorePart : restoreTbl.getPartitions()) { + for (MaterializedIndex restoreIdx : restorePart.getMaterializedIndices()) { + int schemaHash = restoreTbl.getSchemaHashByIndexId(restoreIdx.getId()); + for (Tablet restoreTablet : restoreIdx.getTablets()) { + TabletMeta tabletMeta = new TabletMeta(db.getId(), restoreTbl.getId(), + restorePart.getId(), restoreIdx.getId(), schemaHash); + Catalog.getCurrentInvertedIndex().addTablet(restoreTablet.getId(), tabletMeta); + for (Replica restoreReplica : restoreTablet.getReplicas()) { + Catalog.getCurrentInvertedIndex().addReplica(restoreTablet.getId(), restoreReplica); + } + } + } + } + } + } finally { + db.writeUnlock(); + } + + LOG.info("replay check and prepare meta. {}", this); + } + + private void waitingAllSnapshotsFinished() { + if (unfinishedSignatureToId.isEmpty()) { + snapshotFinishedTime = System.currentTimeMillis(); + state = RestoreJobState.DOWNLOAD; + + catalog.getEditLog().logRestoreJob(this); + LOG.info("finished making snapshots. {}", this); + return; + } + + LOG.info("waiting {} replicas to make snapshot: [{}]. {}", + unfinishedSignatureToId.size(), unfinishedSignatureToId, this); + return; + } + + private void downloadSnapshots() { + // Categorize snapshot infos by db id. + ArrayListMultimap dbToSnapshotInfos = ArrayListMultimap.create(); + for (SnapshotInfo info : snapshotInfos.values()) { + dbToSnapshotInfos.put(info.getDbId(), info); + } + + // Send download tasks + unfinishedSignatureToId.clear(); + taskErrMsg.clear(); + AgentBatchTask batchTask = new AgentBatchTask(); + for (long dbId : dbToSnapshotInfos.keySet()) { + List infos = dbToSnapshotInfos.get(dbId); + + Database db = catalog.getDb(dbId); + if (db == null) { + status = new Status(ErrCode.NOT_FOUND, "db " + dbId + " does not exist"); + return; + } + + // We classify the snapshot info by backend + ArrayListMultimap beToSnapshots = ArrayListMultimap.create(); + for (SnapshotInfo info : infos) { + beToSnapshots.put(info.getBeId(), info); + } + + db.readLock(); + try { + for (Long beId : beToSnapshots.keySet()) { + List beSnapshotInfos = beToSnapshots.get(beId); + int totalNum = beSnapshotInfos.size(); + // each backend allot at most 3 tasks + int batchNum = Math.min(totalNum, 3); + // each task contains several upload sub tasks + int taskNumPerBatch = Math.max(totalNum / batchNum, 1); + LOG.debug("backend {} has {} batch, total {} tasks, {}", + beId, batchNum, totalNum, this); + + List brokerAddrs = Lists.newArrayList(); + Status st = repo.getBrokerAddress(beId, catalog, brokerAddrs); + if (!st.ok()) { + status = st; + return; + } + Preconditions.checkState(brokerAddrs.size() == 1); + + // allot tasks + int index = 0; + for (int batch = 0; batch < batchNum; batch++) { + Map srcToDest = Maps.newHashMap(); + int currentBatchTaskNum = (batch == batchNum - 1) ? totalNum - index : taskNumPerBatch; + for (int j = 0; j < currentBatchTaskNum; j++) { + SnapshotInfo info = beSnapshotInfos.get(index++); + Table tbl = db.getTable(info.getTblId()); + if (tbl == null) { + status = new Status(ErrCode.NOT_FOUND, "restored table " + + info.getTabletId() + " does not exist"); + return; + } + OlapTable olapTbl = (OlapTable) tbl; + + Partition part = olapTbl.getPartition(info.getPartitionId()); + if (part == null) { + status = new Status(ErrCode.NOT_FOUND, "partition " + + info.getPartitionId() + " does not exist in restored table: " + + tbl.getName()); + return; + } + + MaterializedIndex idx = part.getIndex(info.getIndexId()); + if (idx == null) { + status = new Status(ErrCode.NOT_FOUND, + "index " + info.getIndexId() + " does not exist in partion " + part.getName() + + "of restored table " + tbl.getName()); + return; + } + + Tablet tablet = idx.getTablet(info.getTabletId()); + if (tablet == null) { + status = new Status(ErrCode.NOT_FOUND, + "tablet " + info.getTabletId() + " does not exist in restored table " + + tbl.getName()); + return; + } + + Replica replica = tablet.getReplicaByBackendId(info.getBeId()); + if (replica == null) { + status = new Status(ErrCode.NOT_FOUND, + "replica in be " + info.getBeId() + " of tablet " + + tablet.getId() + " does not exist in restored table " + + tbl.getName()); + return; + } + + IdChain catalogIds = new IdChain(tbl.getId(), part.getId(), idx.getId(), + info.getTabletId(), replica.getId()); + IdChain repoIds = fileMapping.get(catalogIds); + if (repoIds == null) { + status = new Status(ErrCode.NOT_FOUND, + "failed to get id mapping of catalog ids: " + catalogIds.toString()); + LOG.info("current file mapping: {}", fileMapping); + return; + } + + String repoTabletPath = jobInfo.getFilePath(repoIds); + + // eg: + // bos://location/__palo_repository_my_repo/_ss_my_ss/_ss_content/__db_10000/ + // __tbl_10001/__part_10002/_idx_10001/__10003 + String src = repo.getRepoPath(label, repoTabletPath); + SnapshotInfo snapshotInfo = snapshotInfos.get(info.getTabletId(), info.getBeId()); + Preconditions.checkNotNull(snapshotInfo, info.getTabletId() + "-" + info.getBeId()); + // download to previous exist snapshot dir + String dest = snapshotInfo.getTabletPath(); + srcToDest.put(src, dest); + LOG.debug("create download src path: {}, dest path: {}", src, dest); + } + long signature = catalog.getNextId(); + DownloadTask task = new DownloadTask(null, beId, signature, jobId, dbId, + srcToDest, brokerAddrs.get(0), repo.getStorage().getProperties()); + batchTask.addTask(task); + unfinishedSignatureToId.put(signature, beId); + } + } + } finally { + db.readUnlock(); + } + } + + // send task + for (AgentTask task : batchTask.getAllTasks()) { + AgentTaskQueue.addTask(task); + } + AgentTaskExecutor.submit(batchTask); + + state = RestoreJobState.DOWNLOADING; + + // No log here + LOG.info("finished to send download tasks to BE. num: {}. {}", batchTask.getTaskNum(), this); + return; + } + + private void waitingAllDownloadFinished() { + if (unfinishedSignatureToId.isEmpty()) { + downloadFinishedTime = System.currentTimeMillis(); + state = RestoreJobState.COMMIT; + + // backupMeta is useless now + backupMeta = null; + + catalog.getEditLog().logRestoreJob(this); + LOG.info("finished to download. {}", this); + } + + LOG.info("waiting {} tasks to finish downloading from repo. {}", unfinishedSignatureToId.size(), this); + } + + private void commit() { + // Send task to move the download dir + unfinishedSignatureToId.clear(); + taskErrMsg.clear(); + AgentBatchTask batchTask = new AgentBatchTask(); + // tablet id->(be id -> download info) + for (Cell cell : snapshotInfos.cellSet()) { + SnapshotInfo info = cell.getValue(); + long signature = catalog.getNextId(); + DirMoveTask task = new DirMoveTask(null, cell.getColumnKey(), signature, jobId, dbId, + info.getTblId(), info.getPartitionId(), info.getTabletId(), + cell.getRowKey(), info.getTabletPath(), info.getSchemaHash(), + true /* need reload tablet header */); + batchTask.addTask(task); + unfinishedSignatureToId.put(signature, info.getTabletId()); + } + + // send task + for (AgentTask task : batchTask.getAllTasks()) { + AgentTaskQueue.addTask(task); + } + AgentTaskExecutor.submit(batchTask); + + state = RestoreJobState.COMMITTING; + + // No log here + LOG.info("finished to send move dir tasks. num: {}. {}", batchTask.getTaskNum(), this); + return; + } + + private void waitingAllTabletsCommitted() { + if (unfinishedSignatureToId.isEmpty()) { + LOG.info("finished to commit all tablet. {}", this); + Status st = allTabletCommitted(false /* not replay */); + if (!st.ok()) { + status = st; + } + return; + } + LOG.info("waiting {} tablets to commit. {}", unfinishedSignatureToId.size(), this); + } + + private Status allTabletCommitted(boolean isReplay) { + Database db = catalog.getDb(dbId); + if (db == null) { + return new Status(ErrCode.NOT_FOUND, "database " + dbId + " does not exist"); + } + + // set all restored partition version and version hash + db.writeLock(); + try { + // set all tables' state to NORMAL + setTableStateToNormal(db); + + for (long tblId : restoredVersionInfo.rowKeySet()) { + Table tbl = db.getTable(tblId); + if (tbl == null) { + continue; + } + OlapTable olapTbl = (OlapTable) tbl; + Map> map = restoredVersionInfo.rowMap().get(tblId); + for (Map.Entry> entry : map.entrySet()) { + long partId = entry.getKey(); + Partition part = olapTbl.getPartition(partId); + if (part == null) { + continue; + } + + // update partition committed version + part.setCommittedVersion(entry.getValue().first); + part.setCommittedVersionHash(entry.getValue().second); + + // we also need to update the replica version of these overwritten restored partitions + for (MaterializedIndex idx : part.getMaterializedIndices()) { + for (Tablet tablet : idx.getTablets()) { + for (Replica replica : tablet.getReplicas()) { + if (!replica.checkVersionCatchUp(part.getCommittedVersion(), + part.getCommittedVersionHash())) { + replica.updateInfo(part.getCommittedVersion(), part.getCommittedVersionHash(), + replica.getDataSize(), replica.getRowCount()); + } + } + } + } + + LOG.debug("restore set partition {} version in table {}, version: {}, version hash: {}", + partId, tblId, entry.getValue().first, entry.getValue().second); + } + } + } finally { + db.writeUnlock(); + } + + if (!isReplay) { + restoredPartitions.clear(); + restoredTbls.clear(); + snapshotInfos.clear(); + + finishedTime = System.currentTimeMillis(); + state = RestoreJobState.FINISHED; + + catalog.getEditLog().logRestoreJob(this); + } + + LOG.info("job is finished. is replay: {}. {}", isReplay, this); + return Status.OK; + } + + private void replayWaitingAllTabletsCommitted() { + allTabletCommitted(true /* is replay */); + } + + public List getInfo() { + List info = Lists.newArrayList(); + info.add(String.valueOf(jobId)); + info.add(label); + info.add(backupTimestamp); + info.add(dbName); + info.add(state.name()); + info.add(String.valueOf(allowLoad)); + info.add(String.valueOf(restoreReplicationNum)); + info.add(getRestoreObjs()); + info.add(TimeUtils.longToTimeString(createTime)); + info.add(TimeUtils.longToTimeString(metaPreparedTime)); + info.add(TimeUtils.longToTimeString(snapshotFinishedTime)); + info.add(TimeUtils.longToTimeString(downloadFinishedTime)); + info.add(TimeUtils.longToTimeString(finishedTime)); + info.add(Joiner.on(", ").join(unfinishedSignatureToId.keySet())); + List msgs = taskErrMsg.entrySet().stream().map(n -> "[" + n.getKey() + ": " + n.getValue() + + "]").collect(Collectors.toList()); + info.add(Joiner.on(", ").join(msgs)); + info.add(status.toString()); + info.add(String.valueOf(timeoutMs / 1000)); + return info; + } + + private String getRestoreObjs() { + Preconditions.checkState(jobInfo != null); + return jobInfo.getInfo(); + } + + @Override + public boolean isDone() { + if (state == RestoreJobState.FINISHED || state == RestoreJobState.CANCELLED) { + return true; + } + return false; + } + + // cancel by user + @Override + public synchronized Status cancel() { + if (isDone()) { + return new Status(ErrCode.COMMON_ERROR, + "Job with label " + label + " can not be cancelled. state: " + state); + } + + status = new Status(ErrCode.COMMON_ERROR, "user cancelled, current state: " + state.name()); + cancelInternal(false); + return Status.OK; + } + + public void cancelInternal(boolean isReplay) { + // We need to clean the residual due to current state + if (!isReplay) { switch (state) { - case PENDING: - downloadBackupMeta(); - break; - case RESTORE_META: - restoreMeta(); - break; - case DOWNLOAD: - download(); + case SNAPSHOTING: + // remove all snapshot tasks in AgentTaskQueue + for (Long taskId : unfinishedSignatureToId.keySet()) { + AgentTaskQueue.removeTaskOfType(TTaskType.MAKE_SNAPSHOT, taskId); + } break; case DOWNLOADING: - waitDownload(); + // remove all down tasks in AgentTaskQueue + for (Long taskId : unfinishedSignatureToId.keySet()) { + AgentTaskQueue.removeTaskOfType(TTaskType.DOWNLOAD, taskId); + } + break; + case COMMITTING: + // remove all dir move tasks in AgentTaskQueue + for (Long taskId : unfinishedSignatureToId.keySet()) { + AgentTaskQueue.removeTaskOfType(TTaskType.MOVE, taskId); + } break; default: break; } - } catch (Exception e) { - errMsg = Strings.nullToEmpty(e.getMessage()); - LOG.warn("failed to restore: [" + errMsg + "], job[" + jobId + "]", e); - state = RestoreJobState.CANCELLED; } - if (state == RestoreJobState.FINISHED || state == RestoreJobState.CANCELLED) { - end(Catalog.getInstance(), false); - } - } - - private void downloadBackupMeta() throws DdlException, IOException, AnalysisException, InterruptedException, - ExecutionException { - Catalog catalog = Catalog.getInstance(); + // clean restored objs Database db = catalog.getDb(dbId); - if (db == null) { - throw new DdlException("Database[" + getDbName() + "] does not exist"); - } + if (db != null) { + db.writeLock(); + try { + // rollback table's state to NORMAL + setTableStateToNormal(db); - if (pathBuilder == null) { - pathBuilder = PathBuilder.createPathBuilder(getLocalDirName()); - } - - if (commandBuilder == null) { - String remotePropFilePath = pathBuilder.remoteProperties(); - commandBuilder = CommandBuilder.create(remotePropFilePath, remoteProperties); - } - - if (future == null) { - // 1. download manifest - LOG.info("begin to submit download backup meta. job: {}", jobId); - MetaDownloadTask task = new MetaDownloadTask(jobId, getDbName(), getLabel(), getLocalDirName(), remotePath, - pathBuilder, commandBuilder, - tableToPartitionNames, tableToCreateTableStmt, - tableToRollupStmt, tableToPartitionStmts, tableToReplace, - tableRenameMap); - future = Catalog.getInstance().getBackupHandler().getAsynchronousCmdExecutor().submit(task); - } else { - boolean finished = checkFuture("download backup meta"); - if (!finished) { - return; - } - - future = null; - state = RestoreJobState.RESTORE_META; - } - } - - private void restoreMeta() throws DdlException { - Catalog catalog = Catalog.getInstance(); - Database db = catalog.getDb(dbId); - if (db == null) { - throw new DdlException("Database[" + getDbName() + "] does not exist"); - } - for (Map.Entry entry : tableToCreateTableStmt.entrySet()) { - String newTableName = entry.getKey(); - CreateTableStmt createTableStmt = entry.getValue(); - Boolean replace = tableToReplace.get(newTableName); - if (replace) { - // 1. create table - Table restoredTable = catalog.createTable(createTableStmt, true); - restoredTables.put(newTableName, restoredTable); - - if (restoredTable.getType() != TableType.OLAP) { - continue; - } - - OlapTable restoredOlapTable = (OlapTable) restoredTable; - - // 2. create rollup - RollupHandler rollupHandler = catalog.getRollupHandler(); - AlterTableStmt rollupStmt = tableToRollupStmt.get(newTableName); - if (rollupStmt != null) { - // check if new table name conflicts with rollup index name - for (AlterClause clause : rollupStmt.getOps()) { - Preconditions.checkState(clause instanceof AddRollupClause); - String rollupName = ((AddRollupClause) clause).getRollupName(); - if (rollupName.equals(newTableName)) { - throw new DdlException("New table name[" + newTableName - + "] conflicts with rollup index name"); - } - } - - rollupHandler.process(rollupStmt.getOps(), db, restoredOlapTable, true); - } - - // 3. create partition - Map partitionStmts = tableToPartitionStmts.row(newTableName); - if (partitionStmts.isEmpty()) { - continue; - } - - RangePartitionInfo rangePartitionInfo = (RangePartitionInfo) restoredOlapTable.getPartitionInfo(); - for (Map.Entry entry2 : partitionStmts.entrySet()) { - AlterTableStmt stmt = entry2.getValue(); - AddPartitionClause clause = (AddPartitionClause) stmt.getOps().get(0); - Pair res = catalog.addPartition(db, newTableName, restoredOlapTable, clause, true); - Partition partition = res.second; - rangePartitionInfo.handleNewSinglePartitionDesc(clause.getSingeRangePartitionDesc(), - partition.getId()); - restoredOlapTable.addPartition(partition); - } - } else { - Map partitionStmts = tableToPartitionStmts.row(newTableName); - for (Map.Entry entry2 : partitionStmts.entrySet()) { - AlterTableStmt stmt = entry2.getValue(); - Pair res = catalog.addPartition(db, newTableName, null, - (AddPartitionClause) stmt.getOps().get(0), true); - long tableId = res.first; - Partition partition = res.second; - restoredPartitions.put(tableId, partition.getName(), partition); - } - } - } - - metaRestoredTime = System.currentTimeMillis(); - state = RestoreJobState.DOWNLOAD; - LOG.info("finished restore tables. job[{}]", jobId); - } - - private void download() { - for (Map.Entry entry : restoredTables.entrySet()) { - String newTableName = entry.getKey(); - String tableName = tableRenameMap.get(newTableName); - Table table = entry.getValue(); - if (table.getType() != TableType.OLAP) { - continue; - } - - AgentBatchTask batchTask = new AgentBatchTask(); - OlapTable olapTable = (OlapTable) table; - long tableId = olapTable.getId(); - for (Partition partition : olapTable.getPartitions()) { - String partitionName = partition.getName(); - if (olapTable.getPartitionInfo().getType() == PartitionType.UNPARTITIONED) { - // single partition table - partitionName = tableName; - } - long partitionId = partition.getId(); - for (MaterializedIndex index : partition.getMaterializedIndices()) { - long indexId = index.getId(); - String indexName = olapTable.getIndexNameById(index.getId()); - if (indexName.equals(newTableName)) { - // base index - indexName = tableName; - } - - List orderedBackupedTabletIdList = getRestoredTabletInfo(tableName, partitionName, indexName); - - int schemaHash = olapTable.getSchemaHashByIndexId(index.getId()); - List tablets = index.getTablets(); - for (int i = 0; i < tablets.size(); i++) { - Tablet tablet = tablets.get(i); - Long backupedTabletId = orderedBackupedTabletIdList.get(i); - String remoteFilePath = PathBuilder.createPath(remotePath, getDbName(), tableName, - partitionName, indexName, - backupedTabletId.toString()); - for (Replica replica : tablet.getReplicas()) { - RestoreTask task = new RestoreTask(null, replica.getBackendId(), jobId, dbId, - tableId, partitionId, indexId, tablet.getId(), - schemaHash, remoteFilePath, remoteProperties); - batchTask.addTask(task); - } - } // end for tablets - } // end for indices - } // end for partitions - - synchronized (unfinishedTabletIds) { - for (AgentTask task : batchTask.getAllTasks()) { - AgentTaskQueue.addTask(task); - unfinishedTabletIds.put(task.getTabletId(), task.getBackendId()); - } - } - AgentTaskExecutor.submit(batchTask); - - LOG.info("finished send restore tasks for table: {}, job: {}", tableName, jobId); - } // end for tables - - state = RestoreJobState.DOWNLOADING; - LOG.info("finished send all restore tasks. job: {}", jobId); - } - - private List getRestoredTabletInfo(String tableName, String partitionName, String indexName) { - // pathBuilder.getRoot().print("\t"); - DirSaver indexDir = (DirSaver) pathBuilder.getRoot().getChild(getDbName()).getChild(tableName) - .getChild(partitionName).getChild(indexName); - Collection tabletNames = indexDir.getChildrenName(); - Set orderedBackupedTabletIds = Sets.newTreeSet(); - for (String tabletName : tabletNames) { - orderedBackupedTabletIds.add(Long.valueOf(tabletName)); - } - - List orderedBackupedTabletIdList = Lists.newArrayList(orderedBackupedTabletIds); - return orderedBackupedTabletIdList; - } - - private void waitDownload() throws DdlException { - synchronized (unfinishedTabletIds) { - if (!unfinishedTabletIds.isEmpty()) { - LOG.debug("waiting for unfinished download task. size: {}", unfinishedTabletIds.size()); - return; - } - } - - downloadFinishedTime = System.currentTimeMillis(); - LOG.info("all tablets restore finished. job: {}", jobId); - - finishing(Catalog.getInstance(), false); - - state = RestoreJobState.FINISHED; - } - - public void finishing(Catalog catalog, boolean isReplay) throws DdlException { - Database db = catalog.getDb(dbId); - if (db == null && !isReplay) { - throw new DdlException("Database[{}] does not exist"); - } - - db.writeLock(); - try { - // check again if table or partition already exist - for (Map.Entry entry : restoredTables.entrySet()) { - String tableName = entry.getKey(); - - Table currentTable = db.getTable(tableName); - if (currentTable != null) { - throw new DdlException("Table[" + tableName + "]' already exist. " - + "Drop table first or restore to another table"); - } - } - - for (long tableId : restoredPartitions.rowKeySet()) { - Table table = db.getTable(tableId); - if (table == null || table.getType() != TableType.OLAP) { - throw new DdlException("Table[" + tableId + "]' does not exist."); - } - - Map partitions = restoredPartitions.row(tableId); - OlapTable olapTable = (OlapTable) table; - for (Map.Entry entry : partitions.entrySet()) { - String partitionName = entry.getKey(); - Partition currentPartition = olapTable.getPartition(partitionName); - if (currentPartition != null) { - throw new DdlException("Partition[" + partitionName + "]' already exist in table[" - + tableId + "]. Drop partition first or restore to another table"); - } - } - } - - // add tables - for (Map.Entry entry : restoredTables.entrySet()) { - String tableName = entry.getKey(); - Table restoredTable = entry.getValue(); - - if (restoredTable.getType() == TableType.OLAP) { - OlapTable olapTable = (OlapTable) restoredTable; - olapTable.setState(OlapTableState.NORMAL); - if (isReplay) { - // add inverted index - TabletInvertedIndex invertedIndex = Catalog.getCurrentInvertedIndex(); - long tableId = olapTable.getId(); - for (Partition partition : olapTable.getPartitions()) { - long partitionId = partition.getId(); - for (MaterializedIndex index : partition.getMaterializedIndices()) { - long indexId = index.getId(); - int schemaHash = olapTable.getSchemaHashByIndexId(indexId); - for (Tablet tablet : index.getTablets()) { - long tabletId = tablet.getId(); - TabletMeta tabletMeta = new TabletMeta(dbId, tableId, partitionId, indexId, - schemaHash); - invertedIndex.addTablet(tabletId, tabletMeta); - for (Replica replica : tablet.getReplicas()) { - invertedIndex.addReplica(tabletId, replica); - } - } + // remove restored tbls + for (OlapTable restoreTbl : restoredTbls) { + LOG.info("remove restored table when cancelled: {}", restoreTbl.getName()); + for (Partition part : restoreTbl.getPartitions()) { + for (MaterializedIndex idx : part.getMaterializedIndices()) { + for (Tablet tablet : idx.getTablets()) { + Catalog.getCurrentInvertedIndex().deleteTablet(tablet.getId()); } } } + db.dropTable(restoreTbl.getName()); } - db.createTable(restoredTable); - LOG.info("finished add table: {}, job: {}, replay: {}", tableName, jobId, isReplay); - } - // add partitions - for (long tableId : restoredPartitions.rowKeySet()) { - Table table = db.getTable(tableId); - String tableName = table.getName(); - Preconditions.checkState(table != null, tableName); - Preconditions.checkState(table.getType() == TableType.OLAP, tableName); - OlapTable olapTable = (OlapTable) table; - - PartitionInfo partitionInfo = olapTable.getPartitionInfo(); - Preconditions.checkState(partitionInfo.getType() == PartitionType.RANGE); - RangePartitionInfo rangePartitionInfo = (RangePartitionInfo) partitionInfo; - - Map partitions = restoredPartitions.row(tableId); - - for (Map.Entry entry : partitions.entrySet()) { - String partitionName = entry.getKey(); - Partition partition = entry.getValue(); - long partitionId = partition.getId(); - - // add restored partition - AlterTableStmt stmt = tableToPartitionStmts.get(tableName, partitionName); - AddPartitionClause clause = (AddPartitionClause) stmt.getOps().get(0); - rangePartitionInfo.handleNewSinglePartitionDesc(clause.getSingeRangePartitionDesc(), partitionId); - olapTable.addPartition(partition); - - // add inverted index - if (isReplay) { - TabletInvertedIndex invertedIndex = Catalog.getCurrentInvertedIndex(); - for (MaterializedIndex index : partition.getMaterializedIndices()) { - long indexId = index.getId(); - int schemaHash = olapTable.getSchemaHashByIndexId(indexId); - for (Tablet tablet : index.getTablets()) { - long tabletId = tablet.getId(); - for (Replica replica : tablet.getReplicas()) { - invertedIndex.addReplica(tabletId, replica); - } - TabletMeta tabletMeta = new TabletMeta(dbId, tableId, partitionId, indexId, - schemaHash); - invertedIndex.addTablet(tabletId, tabletMeta); - } + // remove restored partitions + for (Pair entry : restoredPartitions) { + OlapTable restoreTbl = (OlapTable) db.getTable(entry.first); + if (restoreTbl == null) { + continue; + } + LOG.info("remove restored partition in table {} when cancelled: {}", + restoreTbl.getName(), entry.second.getName()); + for (MaterializedIndex idx : entry.second.getMaterializedIndices()) { + for (Tablet tablet : idx.getTablets()) { + Catalog.getCurrentInvertedIndex().deleteTablet(tablet.getId()); } } - LOG.info("finished add partition: {}, table: {}, job: {}, replay: {}", - partitionName, tableName, jobId, isReplay); - } // end for partitions - - olapTable.setState(OlapTableState.NORMAL); - } // end for tables - } finally { - db.writeUnlock(); - } - } - - public void handleFinishedRestore(long tabletId, long backendId) { - synchronized (unfinishedTabletIds) { - if (unfinishedTabletIds.remove(tabletId, backendId)) { - LOG.debug("finished restore tablet[{}], backend[{}]", tabletId, backendId); + restoreTbl.dropPartition(dbId, entry.second.getName(), true /* is restore */); + } + } finally { + db.writeUnlock(); } } - } - - @Override - public void end(Catalog catalog, boolean isReplay) { - if (state == RestoreJobState.CANCELLED) { - rollback(catalog); - } - - // 2. set table state - // restoreTableState(catalog); if (!isReplay) { - // 3. remove agent tasks if left - removeLeftTasks(); - - // 4. remove local file - String labelDir = pathBuilder.getRoot().getFullPath(); - Util.deleteDirectory(new File(labelDir)); - LOG.debug("delete local dir: {}", labelDir); - - // 5. remove unused tablet in tablet inverted index - clearInvertedIndex(); + // backupMeta is useless + backupMeta = null; + RestoreJobState curState = state; finishedTime = System.currentTimeMillis(); + state = RestoreJobState.CANCELLED; // log - Catalog.getInstance().getEditLog().logRestoreFinish(this); - } + catalog.getEditLog().logRestoreJob(this); - // clear for saving memory - clearJob(); - - LOG.info("finished end job[{}]. state: {}, replay: {}", jobId, state.name(), isReplay); - } - - private void clearInvertedIndex() { - TabletInvertedIndex invertedIndex = Catalog.getCurrentInvertedIndex(); - if (state == RestoreJobState.CANCELLED) { - // clear restored table tablets - for (Table restoredTable : restoredTables.values()) { - if (restoredTable.getType() != TableType.OLAP) { - continue; - } - - OlapTable olapTable = (OlapTable) restoredTable; - for (Partition partition : olapTable.getPartitions()) { - for (MaterializedIndex index : partition.getMaterializedIndices()) { - for (Tablet tablet : index.getTablets()) { - invertedIndex.deleteTablet(tablet.getId()); - } - } - } - } - - // partition - for (Partition partition : restoredPartitions.values()) { - for (MaterializedIndex index : partition.getMaterializedIndices()) { - for (Tablet tablet : index.getTablets()) { - invertedIndex.deleteTablet(tablet.getId()); - } - } - } - } - } - - @Override - protected void clearJob() { - tableRenameMap = null; - - tableToCreateTableStmt = null; - tableToRollupStmt = null; - tableToPartitionStmts = null; - - tableToReplace = null; - restoredTables = null; - restoredPartitions = null; - - unfinishedTabletIds = null; - remoteProperties = null; - pathBuilder = null; - commandBuilder = null; - LOG.info("job[{}] cleared for saving memory", jobId); - } - - private void rollback(Catalog catalog) { - Database db = catalog.getDb(dbId); - if (db == null) { - errMsg = "Database does not exist[" + getDbName() + "]"; - LOG.info("{}. finished restore old meta. job: {}", errMsg, jobId); + LOG.info("finished to cancel restore job. current state: {}. is replay: {}. {}", + curState.name(), isReplay, this); return; } - db.writeLock(); - try { - // tables - for (Table restoredTable : restoredTables.values()) { - String tableName = restoredTable.getName(); - // use table id rather than table name. - // because table with same name may be created when doing restore. - // find table by name may get unexpected one. - Table currentTable = db.getTable(restoredTable.getId()); - // drop restored table - if (currentTable != null) { - db.dropTable(tableName); - LOG.info("drop restored table[{}] in db[{}]", tableName, dbId); - } + LOG.info("finished to cancel restore job. is replay: {}. {}", isReplay, this); + } + + private void setTableStateToNormal(Database db) { + for (BackupTableInfo tblInfo : jobInfo.tables.values()) { + Table tbl = db.getTable(jobInfo.getAliasByOriginNameIfSet(tblInfo.name)); + if (tbl == null) { + continue; } - // partitions - for (long tableId : restoredPartitions.rowKeySet()) { - OlapTable currentTable = (OlapTable) db.getTable(tableId); - if (currentTable == null) { - // table may be dropped during FINISHING phase - continue; - } - - // drop restored partitions - for (String partitionName : restoredPartitions.row(tableId).keySet()) { - Partition currentPartition = currentTable.getPartition(partitionName); - if (currentPartition != null) { - currentTable.dropPartition(dbId, partitionName, true); - LOG.info("drop restored partition[{}] in table[{}] in db[{}]", - partitionName, tableId, dbId); - } - - currentTable.setState(OlapTableState.NORMAL); - } + if (tbl.getType() != TableType.OLAP) { + continue; + } + + OlapTable olapTbl = (OlapTable) tbl; + if (olapTbl.getState() == OlapTableState.RESTORE + || olapTbl.getState() == OlapTableState.RESTORE_WITH_LOAD) { + olapTbl.setState(OlapTableState.NORMAL); } - } finally { - db.writeUnlock(); } } - private void removeLeftTasks() { - for (Map.Entry entry : unfinishedTabletIds.entries()) { - AgentTaskQueue.removeTask(entry.getValue(), TTaskType.RESTORE, entry.getKey()); - } + public static RestoreJob read(DataInput in) throws IOException { + RestoreJob job = new RestoreJob(); + job.readFields(in); + return job; } @Override public void write(DataOutput out) throws IOException { super.write(out); + + Text.writeString(out, backupTimestamp); + jobInfo.write(out); + out.writeBoolean(allowLoad); + Text.writeString(out, state.name()); - if (tableToPartitionNames == null) { - out.writeBoolean(false); - } else { + if (backupMeta != null) { out.writeBoolean(true); - int size = tableToPartitionNames.size(); - out.writeInt(size); - for (Map.Entry> entry : tableToPartitionNames.entrySet()) { - Text.writeString(out, entry.getKey()); - Set partitionNames = entry.getValue(); - size = partitionNames.size(); - out.writeInt(size); - for (String partitionName : partitionNames) { - Text.writeString(out, partitionName); - } - } + backupMeta.write(out); + } else { + out.writeBoolean(false); } - if (tableRenameMap == null) { - out.writeBoolean(false); - } else { - out.writeBoolean(true); - int size = tableRenameMap.size(); - out.writeInt(size); - for (Map.Entry entry : tableRenameMap.entrySet()) { - Text.writeString(out, entry.getKey()); - Text.writeString(out, entry.getValue()); - } - } + fileMapping.write(out); - if (tableToCreateTableStmt == null) { - out.writeBoolean(false); - } else { - out.writeBoolean(true); - int size = tableToCreateTableStmt.size(); - out.writeInt(size); - for (Map.Entry entry : tableToCreateTableStmt.entrySet()) { - Text.writeString(out, entry.getKey()); - entry.getValue().write(out); - } - } - - if (tableToRollupStmt == null) { - out.writeBoolean(false); - } else { - out.writeBoolean(true); - int size = tableToRollupStmt.size(); - out.writeInt(size); - for (Map.Entry entry : tableToRollupStmt.entrySet()) { - Text.writeString(out, entry.getKey()); - entry.getValue().write(out); - } - } - - if (tableToPartitionStmts == null) { - out.writeBoolean(false); - } else { - out.writeBoolean(true); - int size = tableToPartitionStmts.rowKeySet().size(); - out.writeInt(size); - for (String tableName : tableToPartitionStmts.rowKeySet()) { - Text.writeString(out, tableName); - Map row = tableToPartitionStmts.row(tableName); - size = row.size(); - out.writeInt(size); - for (Map.Entry entry : row.entrySet()) { - Text.writeString(out, entry.getKey()); - entry.getValue().write(out); - } - } - } - - if (tableToReplace == null) { - out.writeBoolean(false); - } else { - out.writeBoolean(true); - int size = tableToReplace.size(); - out.writeInt(size); - for (Map.Entry entry : tableToReplace.entrySet()) { - Text.writeString(out, entry.getKey()); - out.writeBoolean(entry.getValue()); - } - } - - if (restoredTables == null) { - out.writeBoolean(false); - } else { - out.writeBoolean(true); - int size = restoredTables.size(); - out.writeInt(size); - for (Map.Entry entry : restoredTables.entrySet()) { - Text.writeString(out, entry.getKey()); - entry.getValue().write(out); - } - } - - if (restoredPartitions == null) { - out.writeBoolean(false); - } else { - out.writeBoolean(true); - int size = restoredPartitions.size(); - out.writeInt(size); - for (long tableId : restoredPartitions.rowKeySet()) { - out.writeLong(tableId); - Map row = restoredPartitions.row(tableId); - size = row.size(); - out.writeInt(size); - for (Map.Entry entry : row.entrySet()) { - Text.writeString(out, entry.getKey()); - entry.getValue().write(out); - } - } - } - - out.writeLong(metaRestoredTime); + out.writeLong(metaPreparedTime); + out.writeLong(snapshotFinishedTime); out.writeLong(downloadFinishedTime); + + out.writeInt(restoreReplicationNum); + + out.writeInt(restoredPartitions.size()); + for (Pair entry : restoredPartitions) { + Text.writeString(out, entry.first); + entry.second.write(out); + } + + out.writeInt(restoredTbls.size()); + for (OlapTable tbl : restoredTbls) { + tbl.write(out); + } + + out.writeInt(restoredVersionInfo.rowKeySet().size()); + for (long tblId : restoredVersionInfo.rowKeySet()) { + out.writeLong(tblId); + out.writeInt(restoredVersionInfo.row(tblId).size()); + for (Map.Entry> entry : restoredVersionInfo.row(tblId).entrySet()) { + out.writeLong(entry.getKey()); + out.writeLong(entry.getValue().first); + out.writeLong(entry.getValue().second); + } + } + + out.writeInt(snapshotInfos.rowKeySet().size()); + for (long tabletId : snapshotInfos.rowKeySet()) { + out.writeLong(tabletId); + Map map = snapshotInfos.row(tabletId); + out.writeInt(map.size()); + for (Map.Entry entry : map.entrySet()) { + out.writeLong(entry.getKey()); + entry.getValue().write(out); + } + } } @Override public void readFields(DataInput in) throws IOException { super.readFields(in); + backupTimestamp = Text.readString(in); + jobInfo = BackupJobInfo.read(in); + allowLoad = in.readBoolean(); + state = RestoreJobState.valueOf(Text.readString(in)); if (in.readBoolean()) { - tableToPartitionNames = Maps.newHashMap(); - int size = in.readInt(); - for (int i = 0; i < size; i++) { - String tableName = Text.readString(in); - int count = in.readInt(); - Set partitionNames = Sets.newHashSet(); - for (int j = 0; j < count; j++) { - String partitionName = Text.readString(in); - partitionNames.add(partitionName); - } - tableToPartitionNames.put(tableName, partitionNames); - } + backupMeta = BackupMeta.read(in); } - if (in.readBoolean()) { - tableRenameMap = Maps.newHashMap(); - int size = in.readInt(); - for (int i = 0; i < size; i++) { - String newTableName = Text.readString(in); - String tableName = Text.readString(in); - tableRenameMap.put(newTableName, tableName); - } - } + fileMapping = RestoreFileMapping.read(in); - if (in.readBoolean()) { - tableToCreateTableStmt = Maps.newHashMap(); - int size = in.readInt(); - for (int i = 0; i < size; i++) { - String tableName = Text.readString(in); - CreateTableStmt stmt = CreateTableStmt.read(in); - tableToCreateTableStmt.put(tableName, stmt); - } - } - - if (in.readBoolean()) { - tableToRollupStmt = Maps.newHashMap(); - int size = in.readInt(); - for (int i = 0; i < size; i++) { - String tableName = Text.readString(in); - AlterTableStmt stmt = new AlterTableStmt(); - stmt.readFields(in); - tableToRollupStmt.put(tableName, stmt); - } - } - - if (in.readBoolean()) { - tableToPartitionStmts = HashBasedTable.create(); - int size = in.readInt(); - for (int i = 0; i < size; i++) { - String tableName = Text.readString(in); - int count = in.readInt(); - for (int j = 0; j < count; j++) { - String partitionName = Text.readString(in); - AlterTableStmt stmt = new AlterTableStmt(); - stmt.readFields(in); - tableToPartitionStmts.put(tableName, partitionName, stmt); - } - } - } - - if (in.readBoolean()) { - tableToReplace = Maps.newHashMap(); - int size = in.readInt(); - for (int i = 0; i < size; i++) { - String tableName = Text.readString(in); - Boolean replace = in.readBoolean(); - tableToReplace.put(tableName, replace); - } - } - - if (in.readBoolean()) { - restoredTables = Maps.newHashMap(); - int size = in.readInt(); - for (int i = 0; i < size; i++) { - String tableName = Text.readString(in); - Table table = Table.read(in); - restoredTables.put(tableName, table); - } - } - - if (in.readBoolean()) { - restoredPartitions = HashBasedTable.create(); - int size = in.readInt(); - for (int i = 0; i < size; i++) { - long tableId = in.readLong(); - int count = in.readInt(); - for (int j = 0; j < count; j++) { - String partitionName = Text.readString(in); - Partition partition = Partition.read(in); - restoredPartitions.put(tableId, partitionName, partition); - } - } - } - - metaRestoredTime = in.readLong(); + metaPreparedTime = in.readLong(); + snapshotFinishedTime = in.readLong(); downloadFinishedTime = in.readLong(); + + restoreReplicationNum = in.readInt(); + + int size = in.readInt(); + for (int i = 0; i < size; i++) { + String tblName = Text.readString(in); + Partition part = Partition.read(in); + restoredPartitions.add(Pair.create(tblName, part)); + } + + size = in.readInt(); + for (int i = 0; i < size; i++) { + restoredTbls.add((OlapTable) Table.read(in)); + } + + size = in.readInt(); + for (int i = 0; i < size; i++) { + long tblId = in.readLong(); + int innerSize = in.readInt(); + for (int j = 0; j < innerSize; j++) { + long partId = in.readLong(); + long version = in.readLong(); + long versionHash = in.readLong(); + restoredVersionInfo.put(tblId, partId, Pair.create(version, versionHash)); + } + } + + size = in.readInt(); + for (int i = 0; i < size; i++) { + long tabletId = in.readLong(); + int innerSize = in.readInt(); + for (int j = 0; j < innerSize; j++) { + long beId = in.readLong(); + SnapshotInfo info = SnapshotInfo.read(in); + snapshotInfos.put(tabletId, beId, info); + } + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(super.toString()); + sb.append(", backup ts: ").append(backupTimestamp); + sb.append(", state: ").append(state.name()); + return sb.toString(); } } + diff --git a/fe/src/com/baidu/palo/backup/RestoreJob_D.java b/fe/src/com/baidu/palo/backup/RestoreJob_D.java new file mode 100644 index 0000000000..4cfe9620c1 --- /dev/null +++ b/fe/src/com/baidu/palo/backup/RestoreJob_D.java @@ -0,0 +1,885 @@ +// Copyright (c) 2017, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +package com.baidu.palo.backup; + +import com.baidu.palo.alter.RollupHandler; +import com.baidu.palo.analysis.AddPartitionClause; +import com.baidu.palo.analysis.AddRollupClause; +import com.baidu.palo.analysis.AlterClause; +import com.baidu.palo.analysis.AlterTableStmt; +import com.baidu.palo.analysis.CreateTableStmt; +import com.baidu.palo.analysis.LabelName; +import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.catalog.Database; +import com.baidu.palo.catalog.MaterializedIndex; +import com.baidu.palo.catalog.OlapTable; +import com.baidu.palo.catalog.OlapTable.OlapTableState; +import com.baidu.palo.catalog.Partition; +import com.baidu.palo.catalog.PartitionInfo; +import com.baidu.palo.catalog.PartitionType; +import com.baidu.palo.catalog.RangePartitionInfo; +import com.baidu.palo.catalog.Replica; +import com.baidu.palo.catalog.Table; +import com.baidu.palo.catalog.Table.TableType; +import com.baidu.palo.catalog.Tablet; +import com.baidu.palo.catalog.TabletInvertedIndex; +import com.baidu.palo.catalog.TabletMeta; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.DdlException; +import com.baidu.palo.common.Pair; +import com.baidu.palo.common.io.Text; +import com.baidu.palo.common.util.TimeUtils; +import com.baidu.palo.common.util.Util; +import com.baidu.palo.task.AgentBatchTask; +import com.baidu.palo.task.AgentTask; +import com.baidu.palo.task.AgentTaskExecutor; +import com.baidu.palo.task.AgentTaskQueue; + +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutionException; + +@Deprecated +public class RestoreJob_D extends AbstractBackupJob_D { + private static final Logger LOG = LogManager.getLogger(RestoreJob_D.class); + + public enum RestoreJobState { + PENDING, + RESTORE_META, + DOWNLOAD, + DOWNLOADING, + FINISHED, + CANCELLED + } + + private RestoreJobState state; + + private Map> tableToPartitionNames; + private Map tableRenameMap; + + private Map tableToCreateTableStmt; + private Map tableToRollupStmt; + private com.google.common.collect.Table tableToPartitionStmts; + + private Map tableToReplace; + + private Map restoredTables; + // tableid - partition name - partition + private com.google.common.collect.Table restoredPartitions; + + private long metaRestoredTime; + private long downloadFinishedTime; + + public RestoreJob_D() { + super(); + } + + public RestoreJob_D(long jobId, long dbId, LabelName labelName, String restorePath, + Map remoteProperties, Map> tableToPartitionNames, + Map tableRenameMap) { + super(jobId, dbId, labelName, restorePath, remoteProperties); + state = RestoreJobState.PENDING; + + this.tableToPartitionNames = tableToPartitionNames; + this.tableRenameMap = tableRenameMap; + + this.tableToCreateTableStmt = Maps.newHashMap(); + this.tableToRollupStmt = Maps.newHashMap(); + this.tableToPartitionStmts = HashBasedTable.create(); + + this.tableToReplace = Maps.newHashMap(); + this.restoredTables = Maps.newHashMap(); + this.restoredPartitions = HashBasedTable.create(); + + this.metaRestoredTime = -1L; + this.downloadFinishedTime = -1L; + } + + public void setState(RestoreJobState state) { + this.state = state; + } + + public RestoreJobState getState() { + return state; + } + + public long getMetaRestoredTime() { + return metaRestoredTime; + } + + public long getDownloadFinishedTime() { + return downloadFinishedTime; + } + + public Map> getTableToPartitionNames() { + return tableToPartitionNames; + } + + @Override + public List getJobInfo() { + List jobInfo = Lists.newArrayList(); + jobInfo.add(jobId); + jobInfo.add(getLabel()); + jobInfo.add(state.name()); + jobInfo.add(TimeUtils.longToTimeString(createTime)); + jobInfo.add(TimeUtils.longToTimeString(metaRestoredTime)); + jobInfo.add(TimeUtils.longToTimeString(downloadFinishedTime)); + jobInfo.add(TimeUtils.longToTimeString(finishedTime)); + jobInfo.add(errMsg); + jobInfo.add(remotePath); + jobInfo.add(getLeftTasksNum()); + return jobInfo; + } + + @Override + public void runOnce() { + LOG.debug("begin to run restore job: {}, state: {}", jobId, state.name()); + try { + switch (state) { + case PENDING: + downloadBackupMeta(); + break; + case RESTORE_META: + restoreMeta(); + break; + case DOWNLOAD: + download(); + break; + case DOWNLOADING: + waitDownload(); + break; + default: + break; + } + } catch (Exception e) { + errMsg = Strings.nullToEmpty(e.getMessage()); + LOG.warn("failed to restore: [" + errMsg + "], job[" + jobId + "]", e); + state = RestoreJobState.CANCELLED; + } + + if (state == RestoreJobState.FINISHED || state == RestoreJobState.CANCELLED) { + end(Catalog.getInstance(), false); + } + } + + private void downloadBackupMeta() throws DdlException, IOException, AnalysisException, InterruptedException, + ExecutionException { + Catalog catalog = Catalog.getInstance(); + Database db = catalog.getDb(dbId); + if (db == null) { + throw new DdlException("Database[" + getDbName() + "] does not exist"); + } + + if (pathBuilder == null) { + pathBuilder = PathBuilder.createPathBuilder(getLocalDirName()); + } + + if (commandBuilder == null) { + String remotePropFilePath = pathBuilder.remoteProperties(); + commandBuilder = CommandBuilder.create(remotePropFilePath, remoteProperties); + } + + if (future == null) { + // 1. download manifest + LOG.info("begin to submit download backup meta. job: {}", jobId); + MetaDownloadTask task = new MetaDownloadTask(jobId, getDbName(), getLabel(), getLocalDirName(), remotePath, + pathBuilder, commandBuilder, + tableToPartitionNames, tableToCreateTableStmt, + tableToRollupStmt, tableToPartitionStmts, tableToReplace, + tableRenameMap); + // future = Catalog.getInstance().getBackupHandler().getAsynchronousCmdExecutor().submit(task); + } else { + boolean finished = checkFuture("download backup meta"); + if (!finished) { + return; + } + + future = null; + state = RestoreJobState.RESTORE_META; + } + } + + private void restoreMeta() throws DdlException { + Catalog catalog = Catalog.getInstance(); + Database db = catalog.getDb(dbId); + if (db == null) { + throw new DdlException("Database[" + getDbName() + "] does not exist"); + } + for (Map.Entry entry : tableToCreateTableStmt.entrySet()) { + String newTableName = entry.getKey(); + CreateTableStmt createTableStmt = entry.getValue(); + Boolean replace = tableToReplace.get(newTableName); + if (replace) { + // 1. create table + Table restoredTable = catalog.createTable(createTableStmt, true); + restoredTables.put(newTableName, restoredTable); + + if (restoredTable.getType() != TableType.OLAP) { + continue; + } + + OlapTable restoredOlapTable = (OlapTable) restoredTable; + + // 2. create rollup + RollupHandler rollupHandler = catalog.getRollupHandler(); + AlterTableStmt rollupStmt = tableToRollupStmt.get(newTableName); + if (rollupStmt != null) { + // check if new table name conflicts with rollup index name + for (AlterClause clause : rollupStmt.getOps()) { + Preconditions.checkState(clause instanceof AddRollupClause); + String rollupName = ((AddRollupClause) clause).getRollupName(); + if (rollupName.equals(newTableName)) { + throw new DdlException("New table name[" + newTableName + + "] conflicts with rollup index name"); + } + } + + rollupHandler.process(rollupStmt.getOps(), db, restoredOlapTable, true); + } + + // 3. create partition + Map partitionStmts = tableToPartitionStmts.row(newTableName); + if (partitionStmts.isEmpty()) { + continue; + } + + RangePartitionInfo rangePartitionInfo = (RangePartitionInfo) restoredOlapTable.getPartitionInfo(); + for (Map.Entry entry2 : partitionStmts.entrySet()) { + AlterTableStmt stmt = entry2.getValue(); + AddPartitionClause clause = (AddPartitionClause) stmt.getOps().get(0); + Pair res = catalog.addPartition(db, newTableName, restoredOlapTable, clause, true); + Partition partition = res.second; + rangePartitionInfo.handleNewSinglePartitionDesc(clause.getSingeRangePartitionDesc(), + partition.getId()); + restoredOlapTable.addPartition(partition); + } + } else { + Map partitionStmts = tableToPartitionStmts.row(newTableName); + for (Map.Entry entry2 : partitionStmts.entrySet()) { + AlterTableStmt stmt = entry2.getValue(); + Pair res = catalog.addPartition(db, newTableName, null, + (AddPartitionClause) stmt.getOps().get(0), true); + long tableId = res.first; + Partition partition = res.second; + restoredPartitions.put(tableId, partition.getName(), partition); + } + } + } + + metaRestoredTime = System.currentTimeMillis(); + state = RestoreJobState.DOWNLOAD; + LOG.info("finished restore tables. job[{}]", jobId); + } + + private void download() { + for (Map.Entry entry : restoredTables.entrySet()) { + String newTableName = entry.getKey(); + String tableName = tableRenameMap.get(newTableName); + Table table = entry.getValue(); + if (table.getType() != TableType.OLAP) { + continue; + } + + AgentBatchTask batchTask = new AgentBatchTask(); + OlapTable olapTable = (OlapTable) table; + long tableId = olapTable.getId(); + for (Partition partition : olapTable.getPartitions()) { + String partitionName = partition.getName(); + if (olapTable.getPartitionInfo().getType() == PartitionType.UNPARTITIONED) { + // single partition table + partitionName = tableName; + } + long partitionId = partition.getId(); + for (MaterializedIndex index : partition.getMaterializedIndices()) { + long indexId = index.getId(); + String indexName = olapTable.getIndexNameById(index.getId()); + if (indexName.equals(newTableName)) { + // base index + indexName = tableName; + } + + List orderedBackupedTabletIdList = getRestoredTabletInfo(tableName, partitionName, indexName); + + int schemaHash = olapTable.getSchemaHashByIndexId(index.getId()); + List tablets = index.getTablets(); + for (int i = 0; i < tablets.size(); i++) { + Tablet tablet = tablets.get(i); + Long backupedTabletId = orderedBackupedTabletIdList.get(i); + String remoteFilePath = PathBuilder.createPath(remotePath, getDbName(), tableName, + partitionName, indexName, + backupedTabletId.toString()); + for (Replica replica : tablet.getReplicas()) { + + } + } // end for tablets + } // end for indices + } // end for partitions + + synchronized (unfinishedTabletIds) { + for (AgentTask task : batchTask.getAllTasks()) { + AgentTaskQueue.addTask(task); + unfinishedTabletIds.put(task.getTabletId(), task.getBackendId()); + } + } + AgentTaskExecutor.submit(batchTask); + + LOG.info("finished send restore tasks for table: {}, job: {}", tableName, jobId); + } // end for tables + + state = RestoreJobState.DOWNLOADING; + LOG.info("finished send all restore tasks. job: {}", jobId); + } + + private List getRestoredTabletInfo(String tableName, String partitionName, String indexName) { + // pathBuilder.getRoot().print("\t"); + DirSaver indexDir = (DirSaver) pathBuilder.getRoot().getChild(getDbName()).getChild(tableName) + .getChild(partitionName).getChild(indexName); + Collection tabletNames = indexDir.getChildrenName(); + Set orderedBackupedTabletIds = Sets.newTreeSet(); + for (String tabletName : tabletNames) { + orderedBackupedTabletIds.add(Long.valueOf(tabletName)); + } + + List orderedBackupedTabletIdList = Lists.newArrayList(orderedBackupedTabletIds); + return orderedBackupedTabletIdList; + } + + private void waitDownload() throws DdlException { + synchronized (unfinishedTabletIds) { + if (!unfinishedTabletIds.isEmpty()) { + LOG.debug("waiting for unfinished download task. size: {}", unfinishedTabletIds.size()); + return; + } + } + + downloadFinishedTime = System.currentTimeMillis(); + LOG.info("all tablets restore finished. job: {}", jobId); + + finishing(Catalog.getInstance(), false); + + state = RestoreJobState.FINISHED; + } + + public void finishing(Catalog catalog, boolean isReplay) throws DdlException { + Database db = catalog.getDb(dbId); + if (db == null && !isReplay) { + throw new DdlException("Database[{}] does not exist"); + } + + db.writeLock(); + try { + // check again if table or partition already exist + for (Map.Entry entry : restoredTables.entrySet()) { + String tableName = entry.getKey(); + + Table currentTable = db.getTable(tableName); + if (currentTable != null) { + throw new DdlException("Table[" + tableName + "]' already exist. " + + "Drop table first or restore to another table"); + } + } + + for (long tableId : restoredPartitions.rowKeySet()) { + Table table = db.getTable(tableId); + if (table == null || table.getType() != TableType.OLAP) { + throw new DdlException("Table[" + tableId + "]' does not exist."); + } + + Map partitions = restoredPartitions.row(tableId); + OlapTable olapTable = (OlapTable) table; + for (Map.Entry entry : partitions.entrySet()) { + String partitionName = entry.getKey(); + Partition currentPartition = olapTable.getPartition(partitionName); + if (currentPartition != null) { + throw new DdlException("Partition[" + partitionName + "]' already exist in table[" + + tableId + "]. Drop partition first or restore to another table"); + } + } + } + + // add tables + for (Map.Entry entry : restoredTables.entrySet()) { + String tableName = entry.getKey(); + Table restoredTable = entry.getValue(); + + if (restoredTable.getType() == TableType.OLAP) { + OlapTable olapTable = (OlapTable) restoredTable; + olapTable.setState(OlapTableState.NORMAL); + if (isReplay) { + // add inverted index + TabletInvertedIndex invertedIndex = Catalog.getCurrentInvertedIndex(); + long tableId = olapTable.getId(); + for (Partition partition : olapTable.getPartitions()) { + long partitionId = partition.getId(); + for (MaterializedIndex index : partition.getMaterializedIndices()) { + long indexId = index.getId(); + int schemaHash = olapTable.getSchemaHashByIndexId(indexId); + for (Tablet tablet : index.getTablets()) { + long tabletId = tablet.getId(); + TabletMeta tabletMeta = new TabletMeta(dbId, tableId, partitionId, indexId, + schemaHash); + invertedIndex.addTablet(tabletId, tabletMeta); + for (Replica replica : tablet.getReplicas()) { + invertedIndex.addReplica(tabletId, replica); + } + } + } + } + } + } + db.createTable(restoredTable); + LOG.info("finished add table: {}, job: {}, replay: {}", tableName, jobId, isReplay); + } + + // add partitions + for (long tableId : restoredPartitions.rowKeySet()) { + Table table = db.getTable(tableId); + String tableName = table.getName(); + Preconditions.checkState(table != null, tableName); + Preconditions.checkState(table.getType() == TableType.OLAP, tableName); + OlapTable olapTable = (OlapTable) table; + + PartitionInfo partitionInfo = olapTable.getPartitionInfo(); + Preconditions.checkState(partitionInfo.getType() == PartitionType.RANGE); + RangePartitionInfo rangePartitionInfo = (RangePartitionInfo) partitionInfo; + + Map partitions = restoredPartitions.row(tableId); + + for (Map.Entry entry : partitions.entrySet()) { + String partitionName = entry.getKey(); + Partition partition = entry.getValue(); + long partitionId = partition.getId(); + + // add restored partition + AlterTableStmt stmt = tableToPartitionStmts.get(tableName, partitionName); + AddPartitionClause clause = (AddPartitionClause) stmt.getOps().get(0); + rangePartitionInfo.handleNewSinglePartitionDesc(clause.getSingeRangePartitionDesc(), partitionId); + olapTable.addPartition(partition); + + // add inverted index + if (isReplay) { + TabletInvertedIndex invertedIndex = Catalog.getCurrentInvertedIndex(); + for (MaterializedIndex index : partition.getMaterializedIndices()) { + long indexId = index.getId(); + int schemaHash = olapTable.getSchemaHashByIndexId(indexId); + for (Tablet tablet : index.getTablets()) { + long tabletId = tablet.getId(); + for (Replica replica : tablet.getReplicas()) { + invertedIndex.addReplica(tabletId, replica); + } + TabletMeta tabletMeta = new TabletMeta(dbId, tableId, partitionId, indexId, + schemaHash); + invertedIndex.addTablet(tabletId, tabletMeta); + } + } + } + + LOG.info("finished add partition: {}, table: {}, job: {}, replay: {}", + partitionName, tableName, jobId, isReplay); + } // end for partitions + + olapTable.setState(OlapTableState.NORMAL); + } // end for tables + } finally { + db.writeUnlock(); + } + } + + public void handleFinishedRestore(long tabletId, long backendId) { + synchronized (unfinishedTabletIds) { + if (unfinishedTabletIds.remove(tabletId, backendId)) { + LOG.debug("finished restore tablet[{}], backend[{}]", tabletId, backendId); + } + } + } + + @Override + public void end(Catalog catalog, boolean isReplay) { + if (state == RestoreJobState.CANCELLED) { + rollback(catalog); + } + + // 2. set table state + // restoreTableState(catalog); + + if (!isReplay) { + // 3. remove agent tasks if left + removeLeftTasks(); + + // 4. remove local file + String labelDir = pathBuilder.getRoot().getFullPath(); + Util.deleteDirectory(new File(labelDir)); + LOG.debug("delete local dir: {}", labelDir); + + // 5. remove unused tablet in tablet inverted index + clearInvertedIndex(); + + finishedTime = System.currentTimeMillis(); + // log + Catalog.getInstance().getEditLog().logRestoreFinish(this); + } + + // clear for saving memory + clearJob(); + + LOG.info("finished end job[{}]. state: {}, replay: {}", jobId, state.name(), isReplay); + } + + private void clearInvertedIndex() { + TabletInvertedIndex invertedIndex = Catalog.getCurrentInvertedIndex(); + if (state == RestoreJobState.CANCELLED) { + // clear restored table tablets + for (Table restoredTable : restoredTables.values()) { + if (restoredTable.getType() != TableType.OLAP) { + continue; + } + + OlapTable olapTable = (OlapTable) restoredTable; + for (Partition partition : olapTable.getPartitions()) { + for (MaterializedIndex index : partition.getMaterializedIndices()) { + for (Tablet tablet : index.getTablets()) { + invertedIndex.deleteTablet(tablet.getId()); + } + } + } + } + + // partition + for (Partition partition : restoredPartitions.values()) { + for (MaterializedIndex index : partition.getMaterializedIndices()) { + for (Tablet tablet : index.getTablets()) { + invertedIndex.deleteTablet(tablet.getId()); + } + } + } + } + } + + @Override + protected void clearJob() { + tableRenameMap = null; + + tableToCreateTableStmt = null; + tableToRollupStmt = null; + tableToPartitionStmts = null; + + tableToReplace = null; + restoredTables = null; + restoredPartitions = null; + + unfinishedTabletIds = null; + remoteProperties = null; + pathBuilder = null; + commandBuilder = null; + LOG.info("job[{}] cleared for saving memory", jobId); + } + + private void rollback(Catalog catalog) { + Database db = catalog.getDb(dbId); + if (db == null) { + errMsg = "Database does not exist[" + getDbName() + "]"; + LOG.info("{}. finished restore old meta. job: {}", errMsg, jobId); + return; + } + + db.writeLock(); + try { + // tables + for (Table restoredTable : restoredTables.values()) { + String tableName = restoredTable.getName(); + // use table id rather than table name. + // because table with same name may be created when doing restore. + // find table by name may get unexpected one. + Table currentTable = db.getTable(restoredTable.getId()); + // drop restored table + if (currentTable != null) { + db.dropTable(tableName); + LOG.info("drop restored table[{}] in db[{}]", tableName, dbId); + } + } + + // partitions + for (long tableId : restoredPartitions.rowKeySet()) { + OlapTable currentTable = (OlapTable) db.getTable(tableId); + if (currentTable == null) { + // table may be dropped during FINISHING phase + continue; + } + + // drop restored partitions + for (String partitionName : restoredPartitions.row(tableId).keySet()) { + Partition currentPartition = currentTable.getPartition(partitionName); + if (currentPartition != null) { + currentTable.dropPartition(dbId, partitionName, true); + LOG.info("drop restored partition[{}] in table[{}] in db[{}]", + partitionName, tableId, dbId); + } + + currentTable.setState(OlapTableState.NORMAL); + } + } + } finally { + db.writeUnlock(); + } + } + + private void removeLeftTasks() { + + } + + @Override + public void write(DataOutput out) throws IOException { + super.write(out); + Text.writeString(out, state.name()); + + if (tableToPartitionNames == null) { + out.writeBoolean(false); + } else { + out.writeBoolean(true); + int size = tableToPartitionNames.size(); + out.writeInt(size); + for (Map.Entry> entry : tableToPartitionNames.entrySet()) { + Text.writeString(out, entry.getKey()); + Set partitionNames = entry.getValue(); + size = partitionNames.size(); + out.writeInt(size); + for (String partitionName : partitionNames) { + Text.writeString(out, partitionName); + } + } + } + + if (tableRenameMap == null) { + out.writeBoolean(false); + } else { + out.writeBoolean(true); + int size = tableRenameMap.size(); + out.writeInt(size); + for (Map.Entry entry : tableRenameMap.entrySet()) { + Text.writeString(out, entry.getKey()); + Text.writeString(out, entry.getValue()); + } + } + + if (tableToCreateTableStmt == null) { + out.writeBoolean(false); + } else { + out.writeBoolean(true); + int size = tableToCreateTableStmt.size(); + out.writeInt(size); + for (Map.Entry entry : tableToCreateTableStmt.entrySet()) { + Text.writeString(out, entry.getKey()); + entry.getValue().write(out); + } + } + + if (tableToRollupStmt == null) { + out.writeBoolean(false); + } else { + out.writeBoolean(true); + int size = tableToRollupStmt.size(); + out.writeInt(size); + for (Map.Entry entry : tableToRollupStmt.entrySet()) { + Text.writeString(out, entry.getKey()); + entry.getValue().write(out); + } + } + + if (tableToPartitionStmts == null) { + out.writeBoolean(false); + } else { + out.writeBoolean(true); + int size = tableToPartitionStmts.rowKeySet().size(); + out.writeInt(size); + for (String tableName : tableToPartitionStmts.rowKeySet()) { + Text.writeString(out, tableName); + Map row = tableToPartitionStmts.row(tableName); + size = row.size(); + out.writeInt(size); + for (Map.Entry entry : row.entrySet()) { + Text.writeString(out, entry.getKey()); + entry.getValue().write(out); + } + } + } + + if (tableToReplace == null) { + out.writeBoolean(false); + } else { + out.writeBoolean(true); + int size = tableToReplace.size(); + out.writeInt(size); + for (Map.Entry entry : tableToReplace.entrySet()) { + Text.writeString(out, entry.getKey()); + out.writeBoolean(entry.getValue()); + } + } + + if (restoredTables == null) { + out.writeBoolean(false); + } else { + out.writeBoolean(true); + int size = restoredTables.size(); + out.writeInt(size); + for (Map.Entry entry : restoredTables.entrySet()) { + Text.writeString(out, entry.getKey()); + entry.getValue().write(out); + } + } + + if (restoredPartitions == null) { + out.writeBoolean(false); + } else { + out.writeBoolean(true); + int size = restoredPartitions.size(); + out.writeInt(size); + for (long tableId : restoredPartitions.rowKeySet()) { + out.writeLong(tableId); + Map row = restoredPartitions.row(tableId); + size = row.size(); + out.writeInt(size); + for (Map.Entry entry : row.entrySet()) { + Text.writeString(out, entry.getKey()); + entry.getValue().write(out); + } + } + } + + out.writeLong(metaRestoredTime); + out.writeLong(downloadFinishedTime); + } + + @Override + public void readFields(DataInput in) throws IOException { + super.readFields(in); + + state = RestoreJobState.valueOf(Text.readString(in)); + + if (in.readBoolean()) { + tableToPartitionNames = Maps.newHashMap(); + int size = in.readInt(); + for (int i = 0; i < size; i++) { + String tableName = Text.readString(in); + int count = in.readInt(); + Set partitionNames = Sets.newHashSet(); + for (int j = 0; j < count; j++) { + String partitionName = Text.readString(in); + partitionNames.add(partitionName); + } + tableToPartitionNames.put(tableName, partitionNames); + } + } + + if (in.readBoolean()) { + tableRenameMap = Maps.newHashMap(); + int size = in.readInt(); + for (int i = 0; i < size; i++) { + String newTableName = Text.readString(in); + String tableName = Text.readString(in); + tableRenameMap.put(newTableName, tableName); + } + } + + if (in.readBoolean()) { + tableToCreateTableStmt = Maps.newHashMap(); + int size = in.readInt(); + for (int i = 0; i < size; i++) { + String tableName = Text.readString(in); + CreateTableStmt stmt = CreateTableStmt.read(in); + tableToCreateTableStmt.put(tableName, stmt); + } + } + + if (in.readBoolean()) { + tableToRollupStmt = Maps.newHashMap(); + int size = in.readInt(); + for (int i = 0; i < size; i++) { + String tableName = Text.readString(in); + AlterTableStmt stmt = new AlterTableStmt(); + stmt.readFields(in); + tableToRollupStmt.put(tableName, stmt); + } + } + + if (in.readBoolean()) { + tableToPartitionStmts = HashBasedTable.create(); + int size = in.readInt(); + for (int i = 0; i < size; i++) { + String tableName = Text.readString(in); + int count = in.readInt(); + for (int j = 0; j < count; j++) { + String partitionName = Text.readString(in); + AlterTableStmt stmt = new AlterTableStmt(); + stmt.readFields(in); + tableToPartitionStmts.put(tableName, partitionName, stmt); + } + } + } + + if (in.readBoolean()) { + tableToReplace = Maps.newHashMap(); + int size = in.readInt(); + for (int i = 0; i < size; i++) { + String tableName = Text.readString(in); + Boolean replace = in.readBoolean(); + tableToReplace.put(tableName, replace); + } + } + + if (in.readBoolean()) { + restoredTables = Maps.newHashMap(); + int size = in.readInt(); + for (int i = 0; i < size; i++) { + String tableName = Text.readString(in); + Table table = Table.read(in); + restoredTables.put(tableName, table); + } + } + + if (in.readBoolean()) { + restoredPartitions = HashBasedTable.create(); + int size = in.readInt(); + for (int i = 0; i < size; i++) { + long tableId = in.readLong(); + int count = in.readInt(); + for (int j = 0; j < count; j++) { + String partitionName = Text.readString(in); + Partition partition = Partition.read(in); + restoredPartitions.put(tableId, partitionName, partition); + } + } + } + + metaRestoredTime = in.readLong(); + downloadFinishedTime = in.readLong(); + } +} diff --git a/fe/src/com/baidu/palo/backup/SnapshotInfo.java b/fe/src/com/baidu/palo/backup/SnapshotInfo.java new file mode 100644 index 0000000000..1fe110be70 --- /dev/null +++ b/fe/src/com/baidu/palo/backup/SnapshotInfo.java @@ -0,0 +1,161 @@ +// Modifications copyright (C) 2018, Baidu.com, Inc. +// Copyright 2018 The Apache Software Foundation + +// 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. + +package com.baidu.palo.backup; + +import com.baidu.palo.common.io.Text; +import com.baidu.palo.common.io.Writable; + +import com.google.common.base.Joiner; +import com.google.common.collect.Lists; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.List; + +public class SnapshotInfo implements Writable { + private long dbId; + private long tblId; + private long partitionId; + private long indexId; + private long tabletId; + private long beId; + private int schemaHash; + // eg: /path/to/your/be/data/snapshot/20180410102311.0/ + private String path; + // eg: + // 10006_0_1_0_0.dat + // 10006_2_2_0_0.idx + // 10006.hdr + private List files = Lists.newArrayList(); + + public SnapshotInfo() { + // for persist + } + + public SnapshotInfo(long dbId, long tblId, long partitionId, long indexId, long tabletId, + long beId, int schemaHash, String path, List files) { + this.dbId = dbId; + this.tblId = tblId; + this.partitionId = partitionId; + this.indexId = indexId; + this.tabletId = tabletId; + this.beId = beId; + this.schemaHash = schemaHash; + this.path = path; + this.files = files; + } + + public long getDbId() { + return dbId; + } + + public long getTblId() { + return tblId; + } + + public long getPartitionId() { + return partitionId; + } + + public long getIndexId() { + return indexId; + } + + public long getTabletId() { + return tabletId; + } + + public long getBeId() { + return beId; + } + + public int getSchemaHash() { + return schemaHash; + } + + public String getPath() { + return path; + } + + public List getFiles() { + return files; + } + + public void setFiles(List files) { + this.files = files; + } + + public String getTabletPath() { + String basePath = Joiner.on("/").join(path, tabletId, schemaHash); + return basePath; + } + + public static SnapshotInfo read(DataInput in) throws IOException { + SnapshotInfo info = new SnapshotInfo(); + info.readFields(in); + return info; + } + + @Override + public void write(DataOutput out) throws IOException { + out.writeLong(dbId); + out.writeLong(tblId); + out.writeLong(partitionId); + out.writeLong(indexId); + out.writeLong(tabletId); + out.writeLong(beId); + out.writeInt(schemaHash); + Text.writeString(out, path); + + out.writeInt(files.size()); + for (String file : files) { + Text.writeString(out, file); + } + } + + @Override + public void readFields(DataInput in) throws IOException { + dbId = in.readLong(); + tblId = in.readLong(); + partitionId = in.readLong(); + indexId = in.readLong(); + tabletId = in.readLong(); + beId = in.readLong(); + schemaHash = in.readInt(); + path = Text.readString(in); + + int size = in.readInt(); + for (int i = 0; i < size; i++) { + files.add(Text.readString(in)); + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("tablet id: ").append(tabletId); + sb.append(", be id: ").append(beId); + sb.append(", path: ").append(path); + sb.append(", files:").append(files); + return sb.toString(); + } +} diff --git a/fe/src/com/baidu/palo/backup/Status.java b/fe/src/com/baidu/palo/backup/Status.java new file mode 100644 index 0000000000..8da9b0acdd --- /dev/null +++ b/fe/src/com/baidu/palo/backup/Status.java @@ -0,0 +1,68 @@ +// Modifications copyright (C) 2018, Baidu.com, Inc. +// Copyright 2018 The Apache Software Foundation + +// 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. + +package com.baidu.palo.backup; + +public class Status { + public enum ErrCode { + OK, + NOT_FOUND, + BAD_FILE, + CREATE_REMOTE_PATH_FAILED, + IS_DIR, + IS_FILE, + TIMEOUT, + BAD_CONNECTION, + COMMON_ERROR + } + + private ErrCode errCode; + private String errMsg; + + public static final Status OK = new Status(ErrCode.OK, ""); + + public Status(ErrCode errCode, String errMsg) { + this.errCode = errCode; + this.errMsg = errMsg; + } + + public ErrCode getErrCode() { + return errCode; + } + + public String getErrMsg() { + return errMsg; + } + + public boolean ok() { + return errCode == ErrCode.OK; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("[").append(errCode.name()); + if (!ok()) { + sb.append(", msg: ").append(errMsg); + } + sb.append("]"); + return sb.toString(); + } +} diff --git a/fe/src/com/baidu/palo/catalog/AccessPrivilege.java b/fe/src/com/baidu/palo/catalog/AccessPrivilege.java index 0ebd8fb511..0ccff2ab16 100644 --- a/fe/src/com/baidu/palo/catalog/AccessPrivilege.java +++ b/fe/src/com/baidu/palo/catalog/AccessPrivilege.java @@ -20,7 +20,10 @@ package com.baidu.palo.catalog; -import com.google.common.collect.ImmutableSortedMap; +import com.baidu.palo.mysql.privilege.PaloPrivilege; +import com.baidu.palo.mysql.privilege.PrivBitSet; + +import com.google.common.base.Preconditions; import java.util.List; @@ -29,23 +32,52 @@ import java.util.List; public enum AccessPrivilege { READ_ONLY(1, "READ_ONLY"), READ_WRITE(2, "READ_WRITE"), - ALL(3, "ALL"); + ALL(3, "ALL"), + NODE_PRIV(4, "Privilege for cluster node operations"), + GRANT_PRIV(5, "Privilege for granting privlege"), + SELECT_PRIV(6, "Privilege for select data in tables"), + LOAD_PRIV(7, "Privilege for loading data into tables"), + ALTER_PRIV(8, "Privilege for alter database or table"), + CREATE_PRIV(9, "Privilege for createing database or table"), + DROP_PRIV(10, "Privilege for dropping database or table"); private int flag; private String desc; - private static final ImmutableSortedMap NAME_MAP = - ImmutableSortedMap.orderedBy(String.CASE_INSENSITIVE_ORDER) - .put("READ_ONLY", READ_ONLY) - .put("READ_WRITE", READ_WRITE) - .put("ALL", ALL) - .build(); - private AccessPrivilege(int flag, String desc) { this.flag = flag; this.desc = desc; } + public PrivBitSet toPaloPrivilege() { + Preconditions.checkState(flag > 0 && flag < 11); + switch (flag) { + case 1: + return PrivBitSet.of(PaloPrivilege.SELECT_PRIV); + case 2: + case 3: + return PrivBitSet.of(PaloPrivilege.SELECT_PRIV, PaloPrivilege.LOAD_PRIV, + PaloPrivilege.ALTER_PRIV, PaloPrivilege.CREATE_PRIV, + PaloPrivilege.DROP_PRIV); + case 4: + return PrivBitSet.of(PaloPrivilege.NODE_PRIV); + case 5: + return PrivBitSet.of(PaloPrivilege.GRANT_PRIV); + case 6: + return PrivBitSet.of(PaloPrivilege.SELECT_PRIV); + case 7: + return PrivBitSet.of(PaloPrivilege.LOAD_PRIV); + case 8: + return PrivBitSet.of(PaloPrivilege.ALTER_PRIV); + case 9: + return PrivBitSet.of(PaloPrivilege.CREATE_PRIV); + case 10: + return PrivBitSet.of(PaloPrivilege.DROP_PRIV); + default: + return null; + } + } + public static boolean contains(AccessPrivilege p1, AccessPrivilege p2) { return p1.flag >= p2.flag; } @@ -55,7 +87,11 @@ public enum AccessPrivilege { } public static AccessPrivilege fromName(String privStr) { - return NAME_MAP.get(privStr); + try { + return AccessPrivilege.valueOf(privStr.toUpperCase()); + } catch (Exception e) { + return null; + } } public static AccessPrivilege merge(List privileges) { diff --git a/fe/src/com/baidu/palo/catalog/BrokerMgr.java b/fe/src/com/baidu/palo/catalog/BrokerMgr.java index 83dbff0ee3..c77333a3b1 100644 --- a/fe/src/com/baidu/palo/catalog/BrokerMgr.java +++ b/fe/src/com/baidu/palo/catalog/BrokerMgr.java @@ -25,7 +25,6 @@ import com.baidu.palo.common.proc.BaseProcResult; import com.baidu.palo.common.proc.ProcNodeInterface; import com.baidu.palo.common.proc.ProcResult; -import com.google.common.base.Joiner; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; @@ -119,7 +118,7 @@ public class BrokerMgr { } } - // we need IP to find the colocation broker. + // we need IP to find the co-location broker. // { BrokerName -> { IP -> [BrokerAddress] } } private final Map> brokersMap = Maps.newHashMap(); private final Map> addressListMap = Maps.newHashMap(); @@ -159,6 +158,15 @@ public class BrokerMgr { } } + public boolean contaisnBroker(String brokerName) { + lock.lock(); + try { + return brokersMap.containsKey(brokerName); + } finally { + lock.unlock(); + } + } + public BrokerAddress getAnyBroker(String name) { lock.lock(); try { @@ -364,11 +372,12 @@ public class BrokerMgr { try { for (Map.Entry> entry : brokersMap.entrySet()) { String brokerName = entry.getKey(); - List brokerAddrs = Lists.newArrayList(); for (BrokerAddress address : entry.getValue().values()) { - brokerAddrs.add(address.toString()); + List row = Lists.newArrayList(); + row.add(brokerName); + row.add(address.toString()); + result.addRow(row); } - result.addRow(Lists.newArrayList(brokerName, Joiner.on(", ").join(brokerAddrs))); } } finally { lock.unlock(); @@ -415,3 +424,4 @@ public class BrokerMgr { } } } + diff --git a/fe/src/com/baidu/palo/catalog/Catalog.java b/fe/src/com/baidu/palo/catalog/Catalog.java index c1e8f700c3..636c734c46 100644 --- a/fe/src/com/baidu/palo/catalog/Catalog.java +++ b/fe/src/com/baidu/palo/catalog/Catalog.java @@ -33,7 +33,6 @@ import com.baidu.palo.analysis.AlterDatabaseQuotaStmt; import com.baidu.palo.analysis.AlterDatabaseRename; import com.baidu.palo.analysis.AlterSystemStmt; import com.baidu.palo.analysis.AlterTableStmt; -import com.baidu.palo.analysis.AlterUserStmt; import com.baidu.palo.analysis.BackupStmt; import com.baidu.palo.analysis.CancelAlterSystemStmt; import com.baidu.palo.analysis.CancelAlterTableStmt; @@ -42,6 +41,7 @@ import com.baidu.palo.analysis.ColumnRenameClause; import com.baidu.palo.analysis.CreateClusterStmt; import com.baidu.palo.analysis.CreateDbStmt; import com.baidu.palo.analysis.CreateTableStmt; +import com.baidu.palo.analysis.CreateUserStmt; import com.baidu.palo.analysis.CreateViewStmt; import com.baidu.palo.analysis.DecommissionBackendClause; import com.baidu.palo.analysis.DistributionDesc; @@ -65,10 +65,12 @@ import com.baidu.palo.analysis.RollupRenameClause; import com.baidu.palo.analysis.ShowAlterStmt.AlterType; import com.baidu.palo.analysis.SingleRangePartitionDesc; import com.baidu.palo.analysis.TableRenameClause; -import com.baidu.palo.backup.AbstractBackupJob; +import com.baidu.palo.analysis.UserDesc; +import com.baidu.palo.analysis.UserIdentity; +import com.baidu.palo.backup.AbstractBackupJob_D; import com.baidu.palo.backup.BackupHandler; -import com.baidu.palo.backup.BackupJob; -import com.baidu.palo.backup.RestoreJob; +import com.baidu.palo.backup.BackupJob_D; +import com.baidu.palo.backup.RestoreJob_D; import com.baidu.palo.catalog.BrokerMgr.BrokerAddress; import com.baidu.palo.catalog.Database.DbState; import com.baidu.palo.catalog.DistributionInfo.DistributionInfoType; @@ -123,6 +125,9 @@ import com.baidu.palo.load.LoadJob.JobState; import com.baidu.palo.master.Checkpoint; import com.baidu.palo.master.MetaHelper; import com.baidu.palo.metric.MetricRepo; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.mysql.privilege.UserPropertyMgr; import com.baidu.palo.persist.BackendIdsUpdateInfo; import com.baidu.palo.persist.ClusterInfo; import com.baidu.palo.persist.DatabaseInfo; @@ -232,6 +237,7 @@ public class Catalog { private ConsistencyChecker consistencyChecker; private BackupHandler backupHandler; + @Deprecated private UserPropertyMgr userPropertyMgr; private Daemon cleaner; // To clean old LabelInfo, ExportJobInfos @@ -298,6 +304,10 @@ public class Catalog { private DeployManager deployManager; + private PaloAuth auth; + + private DomainResolver domainResolver; + public List getFrontends(FrontendNodeType nodeType) { List result = Lists.newArrayList(); readLock(); @@ -359,7 +369,7 @@ public class Catalog { this.clone = new Clone(); this.alter = new Alter(); this.consistencyChecker = new ConsistencyChecker(); - this.backupHandler = new BackupHandler(); + this.backupHandler = new BackupHandler(this); this.lock = new ReentrantReadWriteLock(true); this.metaDir = Config.meta_dir; this.userPropertyMgr = new UserPropertyMgr(); @@ -397,6 +407,10 @@ public class Catalog { this.pullLoadJobMgr = new PullLoadJobMgr(); this.brokerMgr = new BrokerMgr(); + + this.auth = new PaloAuth(); + this.domainResolver = new DomainResolver(auth); + this.domainResolver.start(); } public static void destroyCheckpoint() { @@ -434,6 +448,10 @@ public class Catalog { return brokerMgr; } + public PaloAuth getAuth() { + return auth; + } + // use this to get correct ClusterInfoService instance public static SystemInfoService getCurrentSystemInfo() { return getCurrentCatalog().getClusterInfo(); @@ -504,7 +522,6 @@ public class Catalog { this.editLog = new EditLog(nodeName); loadImage(IMAGE_DIR); // load image file editLog.open(); // open bdb env or local output stream - this.userPropertyMgr.setEditLog(editLog); // 4. start load label cleaner thread createCleaner(); @@ -517,8 +534,6 @@ public class Catalog { listener.setName("stateListener"); listener.setInterval(STATE_CHANGE_CHECK_INTERVAL_MS); listener.start(); - - userPropertyMgr.setUp(); } private void getClusterIdAndRole() throws IOException { @@ -730,6 +745,12 @@ public class Catalog { + "/role?host=" + selfNode.first + "&port=" + selfNode.second); HttpURLConnection conn = null; conn = (HttpURLConnection) url.openConnection(); + if (conn.getResponseCode() != 200) { + LOG.warn("failed to get fe node type from helper node: {}. response code: {}", + helperNode, conn.getResponseCode()); + continue; + } + String type = conn.getHeaderField("role"); if (type == null) { LOG.warn("failed to get fe node type from helper node: {}.", helperNode); @@ -1127,13 +1148,15 @@ public class Catalog { checksum = loadLoadJob(dis, checksum); checksum = loadAlterJob(dis, checksum); - checksum = loadBackupAndRestoreJob(dis, checksum); + checksum = loadBackupAndRestoreJob_D(dis, checksum); checksum = loadAccessService(dis, checksum); checksum = loadRecycleBin(dis, checksum); checksum = loadGlobalVariable(dis, checksum); checksum = loadCluster(dis, checksum); checksum = loadBrokers(dis, checksum); checksum = loadExportJob(dis, checksum); + checksum = loadBackupHandler(dis, checksum); + checksum = loadPaloAuth(dis, checksum); long remoteChecksum = dis.readLong(); Preconditions.checkState(remoteChecksum == checksum, remoteChecksum + " vs. " + checksum); @@ -1385,45 +1408,48 @@ public class Catalog { return newChecksum; } - public long loadBackupAndRestoreJob(DataInputStream dis, long checksum) throws IOException { + public long loadBackupHandler(DataInputStream dis, long checksum) throws IOException { + if (Catalog.getCurrentCatalogJournalVersion() >= FeMetaVersion.VERSION_42) { + getBackupHandler().readFields(dis); + } + getBackupHandler().setCatalog(this); + return checksum; + } + + public long saveBackupHandler(DataOutputStream dos, long checksum) throws IOException { + getBackupHandler().write(dos); + return checksum; + } + + // This method is deprecated, we keep it because we need to consume the old image + // which contains old backup and restore jobs + @Deprecated + public long loadBackupAndRestoreJob_D(DataInputStream dis, long checksum) throws IOException { long newChecksum = checksum; - if (getCurrentCatalogJournalVersion() >= FeMetaVersion.VERSION_22) { - newChecksum = loadBackupAndRestoreJob(dis, newChecksum, BackupJob.class); - newChecksum = loadBackupAndRestoreJob(dis, newChecksum, RestoreJob.class); - newChecksum = loadBackupAndRestoreLabel(dis, newChecksum); + if (getCurrentCatalogJournalVersion() >= FeMetaVersion.VERSION_22 + && getCurrentCatalogJournalVersion() < FeMetaVersion.VERSION_42) { + newChecksum = loadBackupAndRestoreJob_D(dis, newChecksum, BackupJob_D.class); + newChecksum = loadBackupAndRestoreJob_D(dis, newChecksum, RestoreJob_D.class); + newChecksum = loadBackupAndRestoreLabel_D(dis, newChecksum); } return newChecksum; } - private long loadBackupAndRestoreJob(DataInputStream dis, long checksum, - Class jobClass) throws IOException { - Map jobs = null; - List finishedOrCancelledJobs = null; - if (jobClass == BackupJob.class) { - jobs = getBackupHandler().unprotectedGetBackupJobs(); - finishedOrCancelledJobs = getBackupHandler().unprotectedGetFinishedOrCancelledBackupJobs(); - } else if (jobClass == RestoreJob.class) { - jobs = getBackupHandler().unprotectedGetRestoreJobs(); - finishedOrCancelledJobs = getBackupHandler().unprotectedGetFinishedOrCancelledRestoreJobs(); - } else { - Preconditions.checkState(false); - } - + @Deprecated + private long loadBackupAndRestoreJob_D(DataInputStream dis, long checksum, + Class jobClass) throws IOException { int size = dis.readInt(); long newChecksum = checksum ^ size; for (int i = 0; i < size; i++) { long dbId = dis.readLong(); newChecksum ^= dbId; - if (jobClass == BackupJob.class) { - BackupJob job = new BackupJob(); + if (jobClass == BackupJob_D.class) { + BackupJob_D job = new BackupJob_D(); job.readFields(dis); - jobs.put(dbId, job); } else { - RestoreJob job = new RestoreJob(); + RestoreJob_D job = new RestoreJob_D(); job.readFields(dis); - jobs.put(dbId, job); } - LOG.debug("put {} job to map", dbId); } // finished or cancelled @@ -1432,40 +1458,51 @@ public class Catalog { for (int i = 0; i < size; i++) { long dbId = dis.readLong(); newChecksum ^= dbId; - if (jobClass == BackupJob.class) { - BackupJob job = new BackupJob(); + if (jobClass == BackupJob_D.class) { + BackupJob_D job = new BackupJob_D(); job.readFields(dis); - finishedOrCancelledJobs.add(job); } else { - RestoreJob job = new RestoreJob(); + RestoreJob_D job = new RestoreJob_D(); job.readFields(dis); - finishedOrCancelledJobs.add(job); } } return newChecksum; } - private long loadBackupAndRestoreLabel(DataInputStream dis, long checksum) throws IOException { + @Deprecated + private long loadBackupAndRestoreLabel_D(DataInputStream dis, long checksum) throws IOException { int size = dis.readInt(); long newChecksum = checksum ^ size; - - Multimap dbIdtoLabels = getBackupHandler().unprotectedGetDbIdToLabels(); - for (int i = 0; i < size; i++) { long dbId = dis.readLong(); newChecksum ^= dbId; - String label = Text.readString(dis); - dbIdtoLabels.put(dbId, label); + Text.readString(dis); // label } return newChecksum; } + public long loadPaloAuth(DataInputStream dis, long checksum) throws IOException { + if (Catalog.getCurrentCatalogJournalVersion() >= FeMetaVersion.VERSION_43) { + // CAN NOT use PaloAuth.read(), cause this auth instance is already passed to DomainResolver + auth.readFields(dis); + } + return checksum; + } + + @Deprecated public long loadAccessService(DataInputStream dis, long checksum) throws IOException { - int size = dis.readInt(); - long newChecksum = checksum ^ size; - userPropertyMgr.readFields(dis); - return newChecksum; + if (Catalog.getCurrentCatalogJournalVersion() < FeMetaVersion.VERSION_43) { + int size = dis.readInt(); + long newChecksum = checksum ^ size; + UserPropertyMgr tmpUserPropertyMgr = new UserPropertyMgr(); + tmpUserPropertyMgr.readFields(dis); + + // transform it. the old UserPropertyMgr is deprecated + tmpUserPropertyMgr.transform(auth); + return newChecksum; + } + return checksum; } public long loadRecycleBin(DataInputStream dis, long checksum) throws IOException { @@ -1473,7 +1510,7 @@ public class Catalog { Catalog.getCurrentRecycleBin().readFields(dis); if (!isCheckpointThread()) { - // add tablet in Recyclebin to TabletInvertedIndex + // add tablet in Recycle bin to TabletInvertedIndex Catalog.getCurrentRecycleBin().addTabletToInvertedIndex(); } } @@ -1516,13 +1553,13 @@ public class Catalog { checksum = saveDb(dos, checksum); checksum = saveLoadJob(dos, checksum); checksum = saveAlterJob(dos, checksum); - checksum = saveBackupAndRestoreJob(dos, checksum); - checksum = saveAccessService(dos, checksum); checksum = saveRecycleBin(dos, checksum); checksum = saveGlobalVariable(dos, checksum); checksum = saveCluster(dos, checksum); checksum = saveBrokers(dos, checksum); checksum = saveExportJob(dos, checksum); + checksum = saveBackupHandler(dos, checksum); + checksum = savePaloAuth(dos, checksum); dos.writeLong(checksum); } finally { dos.close(); @@ -1713,75 +1750,8 @@ public class Catalog { return checksum; } - private long saveBackupAndRestoreJob(DataOutputStream dos, long checksum) throws IOException { - checksum = saveBackupAndRestoreJob(dos, checksum, BackupJob.class); - checksum = saveBackupAndRestoreJob(dos, checksum, RestoreJob.class); - checksum = saveBackupAndRestoreLabel(dos, checksum); - return checksum; - } - - private long saveBackupAndRestoreJob(DataOutputStream dos, long checksum, - Class jobClass) throws IOException { - Map jobs = null; - List finishedOrCancelledJobs = null; - if (jobClass == BackupJob.class) { - jobs = getBackupHandler().unprotectedGetBackupJobs(); - finishedOrCancelledJobs = getBackupHandler().unprotectedGetFinishedOrCancelledBackupJobs(); - } else if (jobClass == RestoreJob.class) { - jobs = getBackupHandler().unprotectedGetRestoreJobs(); - finishedOrCancelledJobs = getBackupHandler().unprotectedGetFinishedOrCancelledRestoreJobs(); - } else { - Preconditions.checkState(false); - } - - // jobs - int size = jobs.size(); - checksum ^= size; - dos.writeInt(size); - for (Entry entry : jobs.entrySet()) { - long dbId = entry.getKey(); - checksum ^= dbId; - dos.writeLong(dbId); - entry.getValue().write(dos); - LOG.debug("save {} job", dbId); - } - - // finished or cancelled jobs - size = finishedOrCancelledJobs.size(); - checksum ^= size; - dos.writeInt(size); - for (AbstractBackupJob job : finishedOrCancelledJobs) { - long dbId = job.getDbId(); - checksum ^= dbId; - dos.writeLong(dbId); - job.write(dos); - } - - return checksum; - } - - private long saveBackupAndRestoreLabel(DataOutputStream dos, long checksum) throws IOException { - Multimap dbIdtoLabels = getBackupHandler().unprotectedGetDbIdToLabels(); - Collection> entries = dbIdtoLabels.entries(); - int size = entries.size(); - checksum ^= size; - dos.writeInt(size); - for (Map.Entry entry : entries) { - long dbId = entry.getKey(); - String label = entry.getValue(); - checksum ^= dbId; - dos.writeLong(dbId); - Text.writeString(dos, label); - } - - return checksum; - } - - public long saveAccessService(DataOutputStream dos, long checksum) throws IOException { - int size = userPropertyMgr.getUserMapSize(); - checksum ^= size; - dos.writeInt(size); - userPropertyMgr.write(dos); + public long savePaloAuth(DataOutputStream dos, long checksum) throws IOException { + auth.write(dos); return checksum; } @@ -3668,7 +3638,7 @@ public class Catalog { List chosenBackendIds = Catalog.getCurrentSystemInfo().seqChooseBackendIds(replicationNum, true, true, clusterName); if (chosenBackendIds == null) { - throw new DdlException("Failed to find enough host in all backends. need: " + replicationNum); + throw new DdlException("Failed to find " + replicationNum + " different hosts to create table"); } Preconditions.checkState(chosenBackendIds.size() == replicationNum); for (long backendId : chosenBackendIds) { @@ -4113,10 +4083,6 @@ public class Catalog { return this.backupHandler; } - public UserPropertyMgr getUserMgr() { - return this.userPropertyMgr; - } - public Load getLoadInstance() { return this.load; } @@ -4573,14 +4539,16 @@ public class Catalog { } // Change current database of this session. - public void changeDb(ConnectContext ctx, String dbName) throws DdlException { - if (getDb(dbName) == null) { - ErrorReport.reportDdlException(ErrorCode.ERR_BAD_DB_ERROR, dbName); + public void changeDb(ConnectContext ctx, String qualifiedDb) throws DdlException { + if (!auth.checkDbPriv(ctx, qualifiedDb, PrivPredicate.SHOW)) { + ErrorReport.reportDdlException(ErrorCode.ERR_DB_ACCESS_DENIED, ctx.getQualifiedUser(), qualifiedDb); } - if (!userPropertyMgr.checkAccess(ctx.getUser(), dbName, AccessPrivilege.READ_ONLY)) { - ErrorReport.reportDdlException(ErrorCode.ERR_DB_ACCESS_DENIED, ctx.getUser(), dbName); + + if (getDb(qualifiedDb) == null) { + ErrorReport.reportDdlException(ErrorCode.ERR_BAD_DB_ERROR, qualifiedDb); } - ctx.setDatabase(dbName); + + ctx.setDatabase(qualifiedDb); } // for test only @@ -4658,18 +4626,6 @@ public class Catalog { return functionSet.getFunction(desc, mode); } - public void alterUser(AlterUserStmt stmt) throws DdlException { - getUserMgr().alterUser(stmt); - } - - public boolean checkWhiteList(String user, String remoteIp) { - return getUserMgr().checkWhiltListAccess(user, remoteIp); - } - - public List> showWhiteList(String user) { - return getUserMgr().showWhiteList(user); - } - /** * create cluster * @@ -4709,6 +4665,15 @@ public class Catalog { } finally { writeUnlock(); } + + // create super user for this cluster + UserIdentity adminUser = new UserIdentity(PaloAuth.ADMIN_USER, "%"); + try { + adminUser.analyze(stmt.getClusterName()); + } catch (AnalysisException e) { + LOG.error("should not happen", e); + } + auth.createUser(new CreateUserStmt(new UserDesc(adminUser, "", true))); } private void unprotectCreateCluster(Cluster cluster) { @@ -4782,6 +4747,9 @@ public class Catalog { writeUnlock(); } + // drop user of this cluster + // set is replay to true, not write log + auth.dropUserOfCluster(stmt.getClusterName(), true /* is replay */); } private void unprotectDropCluster(ClusterInfo info, boolean isReplay) { @@ -4800,6 +4768,8 @@ public class Catalog { } finally { writeUnlock(); } + + auth.dropUserOfCluster(info.getClusterName(), true /* is replay */); } public void replayExpandCluster(ClusterInfo info) { @@ -4901,12 +4871,15 @@ public class Catalog { * @throws DdlException */ public void changeCluster(ConnectContext ctx, String clusterName) throws DdlException { + if (!Catalog.getCurrentCatalog().getAuth().checkCanEnterCluster(ConnectContext.get(), clusterName)) { + ErrorReport.reportDdlException(ErrorCode.ERR_CLUSTER_NO_AUTHORITY, + ConnectContext.get().getQualifiedUser(), "enter"); + } + if (!nameToCluster.containsKey(clusterName)) { ErrorReport.reportDdlException(ErrorCode.ERR_CLUSTER_NO_EXISTS, clusterName); } - if (!userPropertyMgr.isAdmin(ctx.getUser())) { - ErrorReport.reportDdlException(ErrorCode.ERR_CLUSTER_NO_AUTHORITY); - } + ctx.setCluster(clusterName); } @@ -5354,3 +5327,4 @@ public class Catalog { } } } + diff --git a/fe/src/com/baidu/palo/catalog/DomainResolver.java b/fe/src/com/baidu/palo/catalog/DomainResolver.java new file mode 100644 index 0000000000..9d607d9e46 --- /dev/null +++ b/fe/src/com/baidu/palo/catalog/DomainResolver.java @@ -0,0 +1,166 @@ +// Copyright (c) 2018, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +package com.baidu.palo.catalog; + +import com.baidu.palo.common.util.Daemon; +import com.baidu.palo.mysql.privilege.PaloAuth; + +import com.google.common.base.Strings; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Map; +import java.util.Set; + +public class DomainResolver extends Daemon { + private static final Logger LOG = LogManager.getLogger(DomainResolver.class); + private static final String BNS_RESOLVER_TOOLS_PATH = "/usr/bin/get_instance_by_service"; + + private PaloAuth auth; + + public DomainResolver(PaloAuth auth) { + super("domain resolver", 10 * 1000); + this.auth = auth; + } + + @Override + public void runOneCycle() { + // qualified user name -> domain name + Map> userMap = Maps.newHashMap(); + auth.getCopiedWhiteList(userMap); + LOG.info("begin to resolve domain: {}", userMap); + + // get unique domain names + Set domainSet = Sets.newHashSet(); + for (Map.Entry> entry : userMap.entrySet()) { + domainSet.addAll(entry.getValue()); + } + + // resolve domain name + for (String domain : domainSet) { + Set resolvedIPs = Sets.newHashSet(); + if (!resolveWithBNS(domain, resolvedIPs) && !resolveWithBNS(domain, resolvedIPs)) { + continue; + } + LOG.debug("get resolved ip of domain {}: {}", domain, resolvedIPs); + + for (Map.Entry> userEntry : userMap.entrySet()) { + if (!userEntry.getValue().contains(domain)) { + continue; + } + + auth.updateResolovedIps(userEntry.getKey(), domain, resolvedIPs); + } + } + } + + /** + * Check if domain name is valid + * + * @param host: + * currently is the user's whitelist bns or dns name + * @return true of false + */ + public boolean isValidDomain(String domainName) { + if (Strings.isNullOrEmpty(domainName)) { + LOG.warn("Domain name is null or empty"); + return false; + } + Set ipSet = Sets.newHashSet(); + if (!resolveWithDNS(domainName, ipSet) && !resolveWithBNS(domainName, ipSet)) { + return false; + } + return true; + } + + /** + * resolve domain name with dns + */ + public boolean resolveWithDNS(String domainName, Set resolvedIPs) { + InetAddress[] address; + try { + address = InetAddress.getAllByName(domainName); + } catch (UnknownHostException e) { + LOG.warn("unknown domain name " + domainName + " with dns: " + e.getMessage()); + return false; + } + + for (InetAddress addr : address) { + resolvedIPs.add(addr.getHostAddress()); + } + return true; + } + + public boolean resolveWithBNS(String domainName, Set resolvedIPs) { + File binaryFile = new File(BNS_RESOLVER_TOOLS_PATH); + if (!binaryFile.exists()) { + LOG.warn("{} does not exist", BNS_RESOLVER_TOOLS_PATH); + return false; + } + + final StringBuilder cmdBuilder = new StringBuilder(); + cmdBuilder.append(BNS_RESOLVER_TOOLS_PATH).append(" -a ").append(domainName); + Process process = null; + BufferedReader bufferedReader = null; + String str = null; + String ip = null; + try { + process = Runtime.getRuntime().exec(cmdBuilder.toString()); + bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream())); + while ((str = bufferedReader.readLine()) != null) { + ip = str.split(" ")[1]; + resolvedIPs.add(ip); + } + final int exitCode = process.waitFor(); + // mean something error + if (exitCode != 0) { + LOG.warn("failed to execute cmd: {}, exit code: {}", cmdBuilder.toString(), exitCode); + resolvedIPs.clear(); + return false; + } + return true; + } catch (IOException e) { + LOG.warn("failed to revole domain with BNS", e); + resolvedIPs.clear(); + return false; + } catch (InterruptedException e) { + LOG.warn("failed to revole domain with BNS", e); + resolvedIPs.clear(); + return false; + } finally { + if (process != null) { + process.destroy(); + } + try { + if (bufferedReader != null) { + bufferedReader.close(); + } + } catch (IOException e) { + LOG.error("Close bufferedReader error! " + e); + } + } + } + +} diff --git a/fe/src/com/baidu/palo/catalog/DomainResolverServer.java b/fe/src/com/baidu/palo/catalog/DomainResolverServer.java deleted file mode 100644 index 085c275035..0000000000 --- a/fe/src/com/baidu/palo/catalog/DomainResolverServer.java +++ /dev/null @@ -1,446 +0,0 @@ -// Copyright (c) 2018, Baidu.com, Inc. All Rights Reserved - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// 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. - -package com.baidu.palo.catalog; - -import com.google.common.base.Strings; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.Collection; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -/** - * This class is responseble for resolving domain name, to resolve domain name - * , first register your domain with username, and so get domain name's ips with - * call getUserDomainToIps,There may be delays, because domain name resolution - * is an asynchronous process. - *

- * - * @author chenhao - * - */ -public final class DomainResolverServer { - private static final Logger LOG = LogManager.getLogger(DomainResolverServer.class); - private static final int RESOLVING_INTERVAL = 10000; - private static final String BNS_RESOLVER_TOOLS_PATH = "/usr/bin/get_instance_by_service"; - private static final int RESOLVING_RETRY_COUNT = 2; - - private static DomainResolverServer instance; - // User to domain name, domains to be resolved - private Map> userToDomainName = Maps.newHashMap(); - // User to domain name, domains have been resolved successfully - private Map>> userToDomainNameToIpSet = Maps.newHashMap(); - // Lock for userToDomainName and userToDomainNameToIpSet - private Lock cloneLock = new ReentrantLock(); - private Thread server; - - private DomainResolverServer() { - server = new Thread(new ResolverServer()); - server.start(); - } - - public static DomainResolverServer getInstance() { - if (instance == null) { - synchronized (DomainResolverServer.class) { - if (instance == null) { - instance = new DomainResolverServer(); - } - } - } - return instance; - } - - //for test - public Collection getRegisteredUserDomain(String user) { - return userToDomainName.get(user); - } - - /** - * @param domainNameCollection - * @return - */ - private boolean isNullOrEmptyCollection(Collection domainNameCollection) { - return domainNameCollection == null || domainNameCollection.isEmpty(); - } - - /** - * Register domain name with username, - * - * @param user: usually a user account in palo - * @param domainNameList: currently is the user's whitelist domain name - * @return true or false - */ - public boolean register(String user, Collection domainNameCollection) { - if (Strings.isNullOrEmpty(user) || isNullOrEmptyCollection(domainNameCollection)) { - LOG.warn("Register param error user[{}]", user); - return false; - } - - if (LOG.isDebugEnabled()) { - final StringBuilder sb = new StringBuilder(); - final Iterator iterator = domainNameCollection.iterator(); - while (iterator.hasNext()) { - sb.append(iterator.next()); - if (iterator.hasNext()) { - sb.append(","); - } - } - LOG.debug("Register user[{}], domain[{}] ...", user, sb.toString()); - } - - cloneLock.lock(); - try { - Set domainNameSet = userToDomainName.get(user); - if (domainNameSet == null) { - domainNameSet = Sets.newHashSet(); - userToDomainName.put(user, domainNameSet); - } - - boolean needUpdate = false; - for (String domainName : domainNameCollection) { - if (Strings.isNullOrEmpty(domainName)) { - LOG.warn("Register param error user[{}] domain[null]", user); - continue; - } - if (!domainNameSet.contains(domainName)) { - domainNameSet.add(domainName); - needUpdate = true; - } - } - - if (needUpdate) { - server.interrupt(); - } - } finally { - cloneLock.unlock(); - } - - return true; - } - - /** - * Unregister domain name with username - * - * @param user: usually a user account in palo - * @param domainNameList: currently is the user's whitelist domain name - */ - public void unregister(String user, Collection domainNameCollection) { - if (Strings.isNullOrEmpty(user) || isNullOrEmptyCollection(domainNameCollection)) { - LOG.warn("Unregister param error"); - return; - } - if (LOG.isDebugEnabled()) { - final StringBuilder sb = new StringBuilder(); - final Iterator iterator = domainNameCollection.iterator(); - while (iterator.hasNext()) { - sb.append(iterator.next()); - if (iterator.hasNext()) { - sb.append(","); - } - } - LOG.debug("Unregister user[{}], domain[{}] ...", user, sb.toString()); - } - - cloneLock.lock(); - try { - final Set domainNameSet = userToDomainName.get(user); - if (domainNameSet == null) { - return; - } - final Map> resolvedDomainNameMap = - userToDomainNameToIpSet.get(user); - for (String domainName : domainNameCollection) { - domainNameSet.remove(domainName); - if (resolvedDomainNameMap != null) { - resolvedDomainNameMap.remove(domainName); - } - } - - if (domainNameSet.isEmpty()) { - userToDomainName.remove(user); - } - - if (resolvedDomainNameMap != null && resolvedDomainNameMap.isEmpty()) { - userToDomainNameToIpSet.remove(user); - } - } finally { - cloneLock.unlock(); - } - } - - /** - * Utils for clone - * @param srcMap - * @return cloneMap - */ - private Map> cloneMap(Map> srcMap) { - final Map> copyOfMap = Maps.newHashMap(); - for (String key : srcMap.keySet()) { - final Set sets = Sets.newHashSet(); - for (String value : srcMap.get(key)) { - sets.add(value); - } - copyOfMap.put(key, sets); - } - return copyOfMap; - } - - /** - * Get user's ips - * - * @param user: usually a user account in palo - * @return map domain name to ips - */ - public Map> getUserDomainToIps(String user) { - Map> copyOfDomainToIpSet = null; - cloneLock.lock(); - try { - final Map> domainNameToIpSet = userToDomainNameToIpSet.get(user); - if (domainNameToIpSet == null || domainNameToIpSet.isEmpty()) { - LOG.debug("GetUserDomainToIps error, user[{}]", user); - return null; - } - copyOfDomainToIpSet = cloneMap(domainNameToIpSet); - } finally { - cloneLock.unlock(); - } - return copyOfDomainToIpSet; - } - - /** - * - * @param domainName: currently is the user's whitelist domain name - * @return ips - * @throws UnknownHostException - */ - private Set getDNSIps(String domainName) throws UnknownHostException { - final Set hostIpSet = Sets.newHashSet(); - final InetAddress[] address = InetAddress.getAllByName(domainName); - for (InetAddress addr : address) { - hostIpSet.add(addr.getHostAddress()); - } - return hostIpSet; - } - - /** - * Synchronous resolve domain name with dns - * - * @param domainName: currently is the user's whitelist domain name - * @return ips - */ - private Set resolveWithDNS(String domainName) { - try { - for (int i = 0; i < RESOLVING_RETRY_COUNT; i++) { - final Set resolvedIpSet = getDNSIps(domainName); - if (resolvedIpSet.size() > 0) { - return resolvedIpSet; - } - // avoid last unused wait - if (i < (RESOLVING_RETRY_COUNT - 1)) { - // sleep 5ms for retry - try { - Thread.sleep(5); - } catch (InterruptedException e2) { - LOG.warn("Sleep encounter InterruptedException"); - } - } - } - } catch (UnknownHostException e) { - LOG.warn("Resolve domain name[{}] with dns error: {}", domainName, e.getMessage()); - return null; - } - - LOG.warn("Resolve domain name[{}] with dns unknown error.", domainName); - return null; - } - - /** - * - * @param domainName: currently is the user's whitelist domain name - * @return - * @throws Exception - */ - private Set getBNSIps(String domainName) throws Exception { - final Set resolvedIpSet = Sets.newHashSet(); - final StringBuilder cmdBuilder = new StringBuilder(); - cmdBuilder.append(BNS_RESOLVER_TOOLS_PATH).append(" -a ").append(domainName); - Process process = null; - BufferedReader bufferedReader = null; - String str = null; - String ip = null; - try { - process = Runtime.getRuntime().exec(cmdBuilder.toString()); - bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream())); - while ((str = bufferedReader.readLine()) != null) { - ip = str.split(" ")[1]; - resolvedIpSet.add(ip); - } - final int exitCode = process.waitFor(); - // mean something error - if (exitCode != 0) { - LOG.warn("GetBNSIps error code:{}", exitCode); - resolvedIpSet.clear(); - } - } finally { - if (process != null) { - process.destroy(); - } - try { - if (bufferedReader != null) { - bufferedReader.close(); - } - } catch (IOException e) { - LOG.error("Close bufferedReader error! " + e); - } - } - return resolvedIpSet; - } - - /** - * synchronous resolve domain name with bns - * - * @param domainName: currently is the user's whitelist domain name - * @return ips - */ - private Set resolveWithBNS(String domainName) { - try { - for (int i = 0; i < RESOLVING_RETRY_COUNT; i++) { - final Set resolvedIpSet = getBNSIps(domainName); - if (resolvedIpSet.size() > 0) { - return resolvedIpSet; - } - // avoid last unused wait - if (i < (RESOLVING_RETRY_COUNT - 1)) { - // sleep 5ms for retry - try { - Thread.sleep(5); - } catch (InterruptedException e2) { - LOG.warn("Sleep encounter InterruptedException"); - } - } - } - } catch (Exception e) { - LOG.warn("Resolve domain name[{}] with bns error: {}", domainName, e.getMessage()); - return null; - } - - LOG.warn("Resolve domain name[{}] with bns unknown error", domainName); - return null; - } - - /** - * Check if domain name is valid - * - * @param host: currently is the user's whitelist bns or dns name - * @return true of false - */ - public boolean isAvaliableDomain(String domainName) { - if (Strings.isNullOrEmpty(domainName)) { - LOG.warn("Domain name is null or empty"); - return false; - } - Set ips = resolveWithDNS(domainName); - if (isNullOrEmptyCollection(ips)) { - ips = resolveWithBNS(domainName); - if (isNullOrEmptyCollection(ips)) { - return false; - } - } - return true; - } - - /** - * Clone userToDomainName - * - * @return userToHost copy - */ - private Map> cloneUserToDomainName() { - cloneLock.lock(); - final Map> copyMaps = cloneMap(userToDomainName); - cloneLock.unlock(); - return copyMaps; - } - - // Resolve domain name at intervals, when new domain name are registered - // calling register() , server will immediately start a new asynchronous - // resolvation. - private class ResolverServer implements Runnable { - - public ResolverServer() { - } - - @Override - public void run() { - LOG.info("DomainResolverServer start"); - while (true) { - // avoid lock userToDomainName in resolvation - final Map> userToDomainNameCopy = cloneUserToDomainName(); - LOG.debug("Start a new resolvation"); - final Map>> newUserToDomainNameToIpSet = Maps.newHashMap(); - for (String user : userToDomainNameCopy.keySet()) { - LOG.debug("Start resolve user[{}]", user); - final Set domainNameWithDNSSet = userToDomainNameCopy.get(user); - final Map> domainNameToIpSet = Maps.newHashMap(); - final Set domainNameWithBNSSet = Sets.newHashSet(); - - // 1. check ipWhiteList if contains domain name with dns - for (String domainName : domainNameWithDNSSet) { - Set ipSet = resolveWithDNS(domainName); - if (ipSet == null || ipSet.isEmpty()) { - domainNameWithBNSSet.add(domainName); - continue; - } - LOG.debug("DNS: domain[{}] ip[{}]", domainName, ipSet); - domainNameToIpSet.put(domainName, ipSet); - } - - // 2. check ipWhiteList if contains domain name with bns - for (String domainName : domainNameWithBNSSet) { - final Set ipSet = resolveWithBNS(domainName); - if (ipSet == null || ipSet.isEmpty()) { - continue; - } - LOG.debug("BNS: domain[{}] ip[{}]", domainName, ipSet); - domainNameToIpSet.put(domainName, ipSet); - } - newUserToDomainNameToIpSet.put(user, domainNameToIpSet); - } - cloneLock.lock(); - userToDomainNameToIpSet.clear(); - userToDomainNameToIpSet.putAll(newUserToDomainNameToIpSet); - cloneLock.unlock(); - try { - Thread.sleep(RESOLVING_INTERVAL); - } catch (InterruptedException e) { - LOG.info("Sleep interrupted"); - } - } - } - - } -} diff --git a/fe/src/com/baidu/palo/catalog/MaterializedIndex.java b/fe/src/com/baidu/palo/catalog/MaterializedIndex.java index 34ea1ee925..e05666d496 100644 --- a/fe/src/com/baidu/palo/catalog/MaterializedIndex.java +++ b/fe/src/com/baidu/palo/catalog/MaterializedIndex.java @@ -20,18 +20,18 @@ package com.baidu.palo.catalog; -import com.baidu.palo.common.io.Text; -import com.baidu.palo.common.io.Writable; - -import com.google.common.collect.Lists; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import com.baidu.palo.common.io.Text; +import com.baidu.palo.common.io.Writable; + +import com.google.common.collect.Lists; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; /** @@ -94,13 +94,27 @@ public class MaterializedIndex extends MetaObject implements Writable { public Tablet getTablet(long tabletId) { return idToTablets.get(tabletId); + } + + public void clearTabletsForRestore() { + idToTablets.clear(); + tablets.clear(); } public void addTablet(Tablet tablet, TabletMeta tabletMeta) { - idToTablets.put(tablet.getId(), tablet); - tablets.add(tablet); - - Catalog.getCurrentInvertedIndex().addTablet(tablet.getId(), tabletMeta); + addTablet(tablet, tabletMeta, false); + } + + public void addTablet(Tablet tablet, TabletMeta tabletMeta, boolean isRestore) { + idToTablets.put(tablet.getId(), tablet); + tablets.add(tablet); + if (!isRestore) { + Catalog.getCurrentInvertedIndex().addTablet(tablet.getId(), tabletMeta); + } + } + + public void setIdForRestore(long idxId) { + this.id = idxId; } public long getId() { diff --git a/fe/src/com/baidu/palo/catalog/OlapTable.java b/fe/src/com/baidu/palo/catalog/OlapTable.java index 87faba8e83..b94f9ffb99 100644 --- a/fe/src/com/baidu/palo/catalog/OlapTable.java +++ b/fe/src/com/baidu/palo/catalog/OlapTable.java @@ -13,8 +13,8 @@ // specific language governing permissions and limitations // under the License. -package com.baidu.palo.catalog; - +package com.baidu.palo.catalog; + import com.baidu.palo.analysis.AddPartitionClause; import com.baidu.palo.analysis.AddRollupClause; import com.baidu.palo.analysis.AlterClause; @@ -26,8 +26,12 @@ import com.baidu.palo.analysis.PartitionDesc; import com.baidu.palo.analysis.RangePartitionDesc; import com.baidu.palo.analysis.SingleRangePartitionDesc; import com.baidu.palo.analysis.TableName; +import com.baidu.palo.backup.Status; +import com.baidu.palo.backup.Status.ErrCode; import com.baidu.palo.catalog.DistributionInfo.DistributionInfoType; +import com.baidu.palo.catalog.Replica.ReplicaState; import com.baidu.palo.common.FeMetaVersion; +import com.baidu.palo.common.io.DeepCopy; import com.baidu.palo.common.io.Text; import com.baidu.palo.common.util.PropertyAnalyzer; import com.baidu.palo.common.util.Util; @@ -51,686 +55,872 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.zip.Adler32; - -/** - * Internal representation of tableFamilyGroup-related metadata. A OlaptableFamilyGroup contains several tableFamily. - */ -public class OlapTable extends Table { - private static final Logger LOG = LogManager.getLogger(OlapTable.class); - - public enum OlapTableState { - NORMAL, - ROLLUP, - SCHEMA_CHANGE, - BACKUP, - RESTORE - } - - private OlapTableState state; - // index id -> table's schema - private Map> indexIdToSchema; - // index id -> table's schema version - private Map indexIdToSchemaVersion; - // index id -> table's schema hash - private Map indexIdToSchemaHash; - // index id -> table's short key column count - private Map indexIdToShortKeyColumnCount; - // index id -> table's storage type - private Map indexIdToStorageType; - // index name -> index id - private Map indexNameToId; - - private KeysType keysType; - private PartitionInfo partitionInfo; - private DistributionInfo defaultDistributionInfo; - - private Map idToPartition; - private Map nameToPartition; - - // bloom filter columns - private Set bfColumns; - private double bfFpp; - - public OlapTable() { - // for persist - super(TableType.OLAP); - this.indexIdToSchema = new HashMap>(); - this.indexIdToSchemaHash = new HashMap(); - this.indexIdToSchemaVersion = new HashMap(); - - this.indexIdToShortKeyColumnCount = new HashMap(); - this.indexIdToStorageType = new HashMap(); - - this.indexNameToId = new HashMap(); - - this.idToPartition = new HashMap(); - this.nameToPartition = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER); - - this.bfColumns = null; - this.bfFpp = 0; - } - - public OlapTable(long id, String tableName, List baseSchema, - KeysType keysType, PartitionInfo partitionInfo, DistributionInfo defaultDistributionInfo) { - super(id, tableName, TableType.OLAP, baseSchema); - - this.state = OlapTableState.NORMAL; - - this.indexIdToSchema = new HashMap>(); - this.indexIdToSchemaHash = new HashMap(); - this.indexIdToSchemaVersion = new HashMap(); - - this.indexIdToShortKeyColumnCount = new HashMap(); - this.indexIdToStorageType = new HashMap(); - - this.indexNameToId = new HashMap(); - - this.idToPartition = new HashMap(); - this.nameToPartition = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER); - - this.keysType = keysType; - this.partitionInfo = partitionInfo; - this.defaultDistributionInfo = defaultDistributionInfo; - - this.bfColumns = null; - this.bfFpp = 0; - } - - public void setState(OlapTableState state) { - this.state = state; - } - - public OlapTableState getState() { - return state; - } - - public void setName(String newName) { - // change name in indexNameToId - long baseIndexId = indexNameToId.remove(this.name); - indexNameToId.put(newName, baseIndexId); - - // change name - this.name = newName; - - // change single partition name - if (this.partitionInfo.getType() == PartitionType.UNPARTITIONED) { - // use for loop, because if we use getPartition(partitionName), - // we may not be able to get partition because this is a bug fix - for (Partition partition : getPartitions()) { - partition.setName(newName); - nameToPartition.clear(); - nameToPartition.put(newName, partition); - break; - } - } - } - - public boolean hasMaterializedIndex(String indexName) { - return indexNameToId.containsKey(indexName); - } - - public void setIndexSchemaInfo(Long indexId, String indexName, List schema, int schemaVersion, - int schemaHash, short shortKeyColumnCount) { - if (indexName == null) { - Preconditions.checkState(indexNameToId.containsValue(indexId)); - } else { - indexNameToId.put(indexName, indexId); - } - indexIdToSchema.put(indexId, schema); - indexIdToSchemaVersion.put(indexId, schemaVersion); - indexIdToSchemaHash.put(indexId, schemaHash); +import java.util.zip.Adler32; + +/** + * Internal representation of tableFamilyGroup-related metadata. A OlaptableFamilyGroup contains several tableFamily. + */ +public class OlapTable extends Table { + private static final Logger LOG = LogManager.getLogger(OlapTable.class); + + public enum OlapTableState { + NORMAL, + ROLLUP, + SCHEMA_CHANGE, + @Deprecated + BACKUP, + RESTORE, + RESTORE_WITH_LOAD + } + + private OlapTableState state; + // index id -> table's schema + private Map> indexIdToSchema; + // index id -> table's schema version + private Map indexIdToSchemaVersion; + // index id -> table's schema hash + private Map indexIdToSchemaHash; + // index id -> table's short key column count + private Map indexIdToShortKeyColumnCount; + // index id -> table's storage type + private Map indexIdToStorageType; + // index name -> index id + private Map indexNameToId; + + private KeysType keysType; + private PartitionInfo partitionInfo; + private DistributionInfo defaultDistributionInfo; + + private Map idToPartition; + private Map nameToPartition; + + // bloom filter columns + private Set bfColumns; + private double bfFpp; + + public OlapTable() { + // for persist + super(TableType.OLAP); + this.indexIdToSchema = new HashMap>(); + this.indexIdToSchemaHash = new HashMap(); + this.indexIdToSchemaVersion = new HashMap(); + + this.indexIdToShortKeyColumnCount = new HashMap(); + this.indexIdToStorageType = new HashMap(); + + this.indexNameToId = new HashMap(); + + this.idToPartition = new HashMap(); + this.nameToPartition = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER); + + this.bfColumns = null; + this.bfFpp = 0; + } + + public OlapTable(long id, String tableName, List baseSchema, + KeysType keysType, PartitionInfo partitionInfo, DistributionInfo defaultDistributionInfo) { + super(id, tableName, TableType.OLAP, baseSchema); + + this.state = OlapTableState.NORMAL; + + this.indexIdToSchema = new HashMap>(); + this.indexIdToSchemaHash = new HashMap(); + this.indexIdToSchemaVersion = new HashMap(); + + this.indexIdToShortKeyColumnCount = new HashMap(); + this.indexIdToStorageType = new HashMap(); + + this.indexNameToId = new HashMap(); + + this.idToPartition = new HashMap(); + this.nameToPartition = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER); + + this.keysType = keysType; + this.partitionInfo = partitionInfo; + this.defaultDistributionInfo = defaultDistributionInfo; + + this.bfColumns = null; + this.bfFpp = 0; + } + + public void setState(OlapTableState state) { + this.state = state; + } + + public OlapTableState getState() { + return state; + } + + public void setName(String newName) { + // change name in indexNameToId + long baseIndexId = indexNameToId.remove(this.name); + indexNameToId.put(newName, baseIndexId); + + // change name + this.name = newName; + + // change single partition name + if (this.partitionInfo.getType() == PartitionType.UNPARTITIONED) { + // use for loop, because if we use getPartition(partitionName), + // we may not be able to get partition because this is a bug fix + for (Partition partition : getPartitions()) { + partition.setName(newName); + nameToPartition.clear(); + nameToPartition.put(newName, partition); + break; + } + } + } + + public boolean hasMaterializedIndex(String indexName) { + return indexNameToId.containsKey(indexName); + } + + public void setIndexSchemaInfo(Long indexId, String indexName, List schema, int schemaVersion, + int schemaHash, short shortKeyColumnCount) { + if (indexName == null) { + Preconditions.checkState(indexNameToId.containsValue(indexId)); + } else { + indexNameToId.put(indexName, indexId); + } + indexIdToSchema.put(indexId, schema); + indexIdToSchemaVersion.put(indexId, schemaVersion); + indexIdToSchemaHash.put(indexId, schemaHash); indexIdToShortKeyColumnCount.put(indexId, shortKeyColumnCount); } public void setIndexStorageType(Long indexId, TStorageType newStorageType) { Preconditions.checkState(newStorageType == TStorageType.COLUMN); indexIdToStorageType.put(indexId, newStorageType); - } - - public void deleteIndexInfo(String indexName) { - long indexId = this.indexNameToId.remove(indexName); - - indexIdToSchema.remove(indexId); - indexIdToSchemaVersion.remove(indexId); - indexIdToSchemaHash.remove(indexId); - indexIdToShortKeyColumnCount.remove(indexId); - indexIdToStorageType.remove(indexId); - } - - public Map getIndexNameToId() { - return indexNameToId; - } - - public Long getIndexIdByName(String indexName) { - return indexNameToId.get(indexName); - } - - public String getIndexNameById(long indexId) { - for (Map.Entry entry : indexNameToId.entrySet()) { - if (entry.getValue() == indexId) { - return entry.getKey(); - } - } - return null; - } - - // schema - public Map> getIndexIdToSchema() { - return indexIdToSchema; - } - - public Map> getCopiedIndexIdToSchema() { - Map> copiedIndexIdToSchema = new HashMap>(); - copiedIndexIdToSchema.putAll(indexIdToSchema); - return copiedIndexIdToSchema; - } - - public List getSchemaByIndexId(Long indexId) { - return indexIdToSchema.get(indexId); - } - - public List getKeyColumnsByIndexId(Long indexId) { - ArrayList keyColumns = Lists.newArrayList(); - List allColumns = this.getSchemaByIndexId(indexId); - for (Column column : allColumns) { - if (column.isKey()) { - keyColumns.add(column); - } - } - - return keyColumns; - } - - // schema version - public int getSchemaVersionByIndexId(Long indexId) { - if (indexIdToSchemaVersion.containsKey(indexId)) { - return indexIdToSchemaVersion.get(indexId); - } - return -1; - } - - // schemaHash - public Map getIndexIdToSchemaHash() { - return indexIdToSchemaHash; - } - - public Map getCopiedIndexIdToSchemaHash() { - Map copiedIndexIdToSchemaHash = new HashMap(); - copiedIndexIdToSchemaHash.putAll(indexIdToSchemaHash); - return copiedIndexIdToSchemaHash; - } - - public int getSchemaHashByIndexId(Long indexId) { - if (indexIdToSchemaHash.containsKey(indexId)) { - return indexIdToSchemaHash.get(indexId); - } - return -1; - } - - // short key - public Map getIndexIdToShortKeyColumnCount() { - return indexIdToShortKeyColumnCount; - } - - public Map getCopiedIndexIdToShortKeyColumnCount() { - Map copiedIndexIdToShortKeyColumnCount = new HashMap(); - copiedIndexIdToShortKeyColumnCount.putAll(indexIdToShortKeyColumnCount); - return copiedIndexIdToShortKeyColumnCount; - } - - public short getShortKeyColumnCountByIndexId(Long indexId) { - if (indexIdToShortKeyColumnCount.containsKey(indexId)) { - return indexIdToShortKeyColumnCount.get(indexId); - } - return (short) -1; - } - - // storage type - public Map getIndexIdToStorageType() { - return indexIdToStorageType; - } - - public Map getCopiedIndexIdToStorageType() { - Map copiedIndexIdToStorageType = new HashMap(); - copiedIndexIdToStorageType.putAll(indexIdToStorageType); - return copiedIndexIdToStorageType; - } - - public void setStorageTypeToIndex(Long indexId, TStorageType storageType) { - indexIdToStorageType.put(indexId, storageType); - } - - public TStorageType getStorageTypeByIndexId(Long indexId) { - return indexIdToStorageType.get(indexId); - } - - public KeysType getKeysType() { - return keysType; - } - - public PartitionInfo getPartitionInfo() { - return partitionInfo; - } - - public DistributionInfo getDefaultDistributionInfo() { - return defaultDistributionInfo; - } - - public void renamePartition(String partitionName, String newPartitionName) { - if (partitionInfo.getType() == PartitionType.UNPARTITIONED) { - // bug fix - for (Partition partition : idToPartition.values()) { - partition.setName(newPartitionName); - nameToPartition.clear(); - nameToPartition.put(newPartitionName, partition); - LOG.info("rename patition {} in table {}", newPartitionName, name); - break; - } - } else { - Partition partition = nameToPartition.remove(partitionName); - partition.setName(newPartitionName); - nameToPartition.put(newPartitionName, partition); - } - } - - public void addPartition(Partition partition) { - idToPartition.put(partition.getId(), partition); - nameToPartition.put(partition.getName(), partition); - } - - public Partition dropPartition(long dbId, String partitionName) { - return dropPartition(dbId, partitionName, false); - } - - public Partition dropPartition(long dbId, String partitionName, boolean isRestore) { - Partition partition = nameToPartition.get(partitionName); - if (partition != null) { - idToPartition.remove(partition.getId()); - nameToPartition.remove(partitionName); - - Preconditions.checkState(partitionInfo.getType() == PartitionType.RANGE); - RangePartitionInfo rangePartitionInfo = (RangePartitionInfo) partitionInfo; - - if (!isRestore) { - // recycle partition - Catalog.getCurrentRecycleBin().recyclePartition(dbId, id, partition, - rangePartitionInfo.getRange(partition.getId()), - rangePartitionInfo.getDataProperty(partition.getId()), - rangePartitionInfo.getReplicationNum(partition.getId())); - } - - // drop partition info - rangePartitionInfo.dropPartition(partition.getId()); - } - return partition; - } - - public Collection getPartitions() { - return idToPartition.values(); - } - - public Partition getPartition(long partitionId) { - return idToPartition.get(partitionId); - } - - public Partition getPartition(String partitionName) { - return nameToPartition.get(partitionName); - } - - public Set getCopiedBfColumns() { - if (bfColumns == null) { - return null; - } - - return Sets.newHashSet(bfColumns); - } - - public double getBfFpp() { - return bfFpp; - } - - public void setBloomFilterInfo(Set bfColumns, double bfFpp) { - this.bfColumns = bfColumns; - this.bfFpp = bfFpp; - } - - public TTableDescriptor toThrift() { - TOlapTable tOlapTable = new TOlapTable(getName()); - TTableDescriptor tTableDescriptor = new TTableDescriptor(id, TTableType.OLAP_TABLE, - baseSchema.size(), 0, getName(), ""); - tTableDescriptor.setOlapTable(tOlapTable); - return tTableDescriptor; - } - - public long getRowCount() { - long rowCount = 0; - for (Map.Entry entry : idToPartition.entrySet()) { - rowCount += ((Partition) entry.getValue()).getBaseIndex().getRowCount(); - } - return rowCount; - } - - public AlterTableStmt toAddRollupStmt(String dbName, Collection indexIds) { - List alterClauses = Lists.newArrayList(); - for (Map.Entry entry : indexNameToId.entrySet()) { - String indexName = entry.getKey(); - long indexId = entry.getValue(); - if (!indexIds.contains(indexId)) { - continue; - } - - // cols - List columnNames = Lists.newArrayList(); - for (Column column : indexIdToSchema.get(indexId)) { - columnNames.add(column.getName()); - } - - // properties - Map properties = Maps.newHashMap(); - properties.put(PropertyAnalyzer.PROPERTIES_STORAGE_TYPE, indexIdToStorageType.get(indexId).name()); - properties.put(PropertyAnalyzer.PROPERTIES_SHORT_KEY, indexIdToShortKeyColumnCount.get(indexId).toString()); - properties.put(PropertyAnalyzer.PROPERTIES_SCHEMA_VERSION, indexIdToSchemaVersion.get(indexId).toString()); - - AddRollupClause addRollupClause = new AddRollupClause(indexName, columnNames, null, null, properties); - alterClauses.add(addRollupClause); - } - - AlterTableStmt alterTableStmt = new AlterTableStmt(new TableName(dbName, name), alterClauses); - return alterTableStmt; - } - - public AlterTableStmt toAddPartitionStmt(String dbName, String partitionName) { - Preconditions.checkState(partitionInfo.getType() == PartitionType.RANGE); - RangePartitionInfo rangePartitionInfo = (RangePartitionInfo) partitionInfo; - List alterClauses = Lists.newArrayList(); - - Partition partition = nameToPartition.get(partitionName); - Map properties = Maps.newHashMap(); - long version = partition.getCommittedVersion(); - long versionHash = partition.getCommittedVersionHash(); - properties.put(PropertyAnalyzer.PROPERTIES_VERSION_INFO, version + "," + versionHash); - properties.put(PropertyAnalyzer.PROPERTIES_REPLICATION_NUM, - String.valueOf(partitionInfo.getReplicationNum(partition.getId()))); - - SingleRangePartitionDesc singleDesc = - rangePartitionInfo.toSingleRangePartitionDesc(partition.getId(), partitionName, properties); - DistributionDesc distributionDesc = partition.getDistributionInfo().toDistributionDesc(); - - AddPartitionClause addPartitionClause = new AddPartitionClause(singleDesc, distributionDesc, null); - alterClauses.add(addPartitionClause); - AlterTableStmt stmt = new AlterTableStmt(new TableName(dbName, name), alterClauses); - return stmt; - } - - @Override - public CreateTableStmt toCreateTableStmt(String dbName) { - Map properties = Maps.newHashMap(); - - // partition - PartitionDesc partitionDesc = null; - if (partitionInfo.getType() == PartitionType.RANGE) { - RangePartitionInfo rangePartitionInfo = (RangePartitionInfo) partitionInfo; - List partitionColumns = rangePartitionInfo.getPartitionColumns(); - List partitionColNames = Lists.newArrayList(); - for (Column partCol : partitionColumns) { - partitionColNames.add(partCol.getName()); - } - - List singlePartitionDescs = Lists.newArrayList(); - partitionDesc = new RangePartitionDesc(partitionColNames, singlePartitionDescs); - } else { - Short replicationNum = partitionInfo.getReplicationNum(nameToPartition.get(name).getId()); - properties.put(PropertyAnalyzer.PROPERTIES_REPLICATION_NUM, replicationNum.toString()); - // and partition version info here for non-partitioned table - Partition partition = getPartition(name); - Preconditions.checkNotNull(partition); - long version = partition.getCommittedVersion(); - long versionHash = partition.getCommittedVersionHash(); - String versionProp = Joiner.on(",").join(version, versionHash); - properties.put(PropertyAnalyzer.PROPERTIES_VERSION_INFO, versionProp); - } - - // keys - List keysColumnNames = Lists.newArrayList(); - for (Column column : baseSchema) { - if (column.isKey()) { - keysColumnNames.add(column.getName()); - } - } - KeysDesc keysDesc = new KeysDesc(keysType, keysColumnNames); - - // distribution - DistributionDesc distributionDesc = defaultDistributionInfo.toDistributionDesc(); - - // other properties - properties.put(PropertyAnalyzer.PROPERTIES_SHORT_KEY, indexIdToShortKeyColumnCount.get(id).toString()); - properties.put(PropertyAnalyzer.PROPERTIES_STORAGE_TYPE, indexIdToStorageType.get(id).name()); - if (bfColumns != null) { - String bfCols = Joiner.on(",").join(bfColumns); - properties.put(PropertyAnalyzer.PROPERTIES_BF_COLUMNS, bfCols); - properties.put(PropertyAnalyzer.PROPERTIES_BF_FPP, String.valueOf(bfFpp)); - } - properties.put(PropertyAnalyzer.PROPERTIES_SCHEMA_VERSION, indexIdToSchemaVersion.get(id).toString()); - - CreateTableStmt stmt = new CreateTableStmt(false, false, new TableName(dbName, name), baseSchema, - type.name(), keysDesc, partitionDesc, distributionDesc, - properties, null); - return stmt; - } - - @Override - public int getSignature(int signatureVersion) { - Adler32 adler32 = new Adler32(); - adler32.update(signatureVersion); - final String charsetName = "UTF-8"; - - try { - // ignore table name - // adler32.update(name.getBytes(charsetName)); - // type - adler32.update(type.name().getBytes(charsetName)); - - // all indices(should be in order) - Set indexNames = Sets.newTreeSet(); - indexNames.addAll(indexNameToId.keySet()); - for (String indexName : indexNames) { - long indexId = indexNameToId.get(indexName); - if (!indexName.equals(name)) { - // index name(ignore base index name. base index name maybe changed) - adler32.update(indexName.getBytes(charsetName)); - } - // schema hash - adler32.update(indexIdToSchemaHash.get(indexId)); - // short key column count - adler32.update(indexIdToShortKeyColumnCount.get(indexId)); - // storage type - adler32.update(indexIdToStorageType.get(indexId).name().getBytes(charsetName)); - } - - // partition type - adler32.update(partitionInfo.getType().name().getBytes(charsetName)); - // partition columns - if (partitionInfo.getType() == PartitionType.RANGE) { - RangePartitionInfo rangePartitionInfo = (RangePartitionInfo) partitionInfo; - List partitionColumns = rangePartitionInfo.getPartitionColumns(); - adler32.update(Util.schemaHash(0, partitionColumns, null, 0)); - } - - } catch (UnsupportedEncodingException e) { - LOG.error("encoding error", e); - return -1; - } - - return Math.abs((int) adler32.getValue()); - } - - @Override - public boolean isPartitioned() { - int numSegs = 0; - for (Partition part : getPartitions()) { - numSegs += part.getDistributionInfo().getBucketNum(); - if (numSegs > 1) { - return true; - } - } - return false; - } - - @Override - public void write(DataOutput out) throws IOException { - super.write(out); - - // state - Text.writeString(out, state.name()); - - // indices' schema - int counter = indexNameToId.size(); - out.writeInt(counter); - for (Map.Entry entry : indexNameToId.entrySet()) { - String indexName = entry.getKey(); - long indexId = entry.getValue(); - Text.writeString(out, indexName); - out.writeLong(indexId); - // schema - out.writeInt(indexIdToSchema.get(indexId).size()); - for (Column column : indexIdToSchema.get(indexId)) { - column.write(out); - } - - // storage type - Text.writeString(out, indexIdToStorageType.get(indexId).name()); - - // indices's schema version - out.writeInt(indexIdToSchemaVersion.get(indexId)); - - // indices's schema hash - out.writeInt(indexIdToSchemaHash.get(indexId)); - - // indices's short key column count - out.writeShort(indexIdToShortKeyColumnCount.get(indexId)); - } - - Text.writeString(out, keysType.name()); - Text.writeString(out, partitionInfo.getType().name()); - partitionInfo.write(out); - Text.writeString(out, defaultDistributionInfo.getType().name()); - defaultDistributionInfo.write(out); - - // partitions - int partitionCount = idToPartition.size(); - out.writeInt(partitionCount); - for (Partition partition : idToPartition.values()) { - partition.write(out); - } - - // bloom filter columns - if (bfColumns == null) { - out.writeBoolean(false); - } else { - out.writeBoolean(true); - out.writeInt(bfColumns.size()); - for (String bfColumn : bfColumns) { - Text.writeString(out, bfColumn); - } - out.writeDouble(bfFpp); - } - } - - @Override - public void readFields(DataInput in) throws IOException { - super.readFields(in); - - this.state = OlapTableState.valueOf(Text.readString(in)); - - // indices's schema - int counter = in.readInt(); - for (int i = 0; i < counter; i++) { - String indexName = Text.readString(in); - long indexId = in.readLong(); - this.indexNameToId.put(indexName, indexId); - - // schema - int colCount = in.readInt(); - List schema = new LinkedList(); - for (int j = 0; j < colCount; j++) { - Column column = Column.read(in); - schema.add(column); - } - this.indexIdToSchema.put(indexId, schema); - - // storage type - TStorageType type = TStorageType.valueOf(Text.readString(in)); - this.indexIdToStorageType.put(indexId, type); - - // indices's schema version - this.indexIdToSchemaVersion.put(indexId, in.readInt()); - - // indices's schema hash - this.indexIdToSchemaHash.put(indexId, in.readInt()); - - // indices's short key column count - this.indexIdToShortKeyColumnCount.put(indexId, in.readShort()); - } - - // partition and distribution info - if (Catalog.getCurrentCatalogJournalVersion() >= FeMetaVersion.VERSION_30) { - keysType = KeysType.valueOf(Text.readString(in)); - } else { - keysType = KeysType.AGG_KEYS; - } - - PartitionType partType = PartitionType.valueOf(Text.readString(in)); - if (partType == PartitionType.UNPARTITIONED) { - partitionInfo = PartitionInfo.read(in); - } else if (partType == PartitionType.RANGE) { - partitionInfo = RangePartitionInfo.read(in); - } else { - throw new IOException("invalid partition type: " + partType); - } - - DistributionInfoType distriType = DistributionInfoType.valueOf(Text.readString(in)); - if (distriType == DistributionInfoType.HASH) { - defaultDistributionInfo = HashDistributionInfo.read(in); - } else if (distriType == DistributionInfoType.RANDOM) { - defaultDistributionInfo = RandomDistributionInfo.read(in); - } else { - throw new IOException("invalid distribution type: " + distriType); - } - - int partitionCount = in.readInt(); - for (int i = 0; i < partitionCount; ++i) { - Partition partition = Partition.read(in); - idToPartition.put(partition.getId(), partition); - nameToPartition.put(partition.getName(), partition); - } - - if (Catalog.getCurrentCatalogJournalVersion() >= FeMetaVersion.VERSION_9) { - if (in.readBoolean()) { - int bfColumnCount = in.readInt(); - bfColumns = Sets.newHashSet(); - for (int i = 0; i < bfColumnCount; i++) { - bfColumns.add(Text.readString(in)); - } - - bfFpp = in.readDouble(); - } - } - } - - public boolean equals(Table table) { - if (this == table) { - return true; - } - if (!(table instanceof OlapTable)) { - return false; - } - - return true; - } -} + } + + public void deleteIndexInfo(String indexName) { + long indexId = this.indexNameToId.remove(indexName); + + indexIdToSchema.remove(indexId); + indexIdToSchemaVersion.remove(indexId); + indexIdToSchemaHash.remove(indexId); + indexIdToShortKeyColumnCount.remove(indexId); + indexIdToStorageType.remove(indexId); + } + + public Map getIndexNameToId() { + return indexNameToId; + } + + public Long getIndexIdByName(String indexName) { + return indexNameToId.get(indexName); + } + + public String getIndexNameById(long indexId) { + for (Map.Entry entry : indexNameToId.entrySet()) { + if (entry.getValue() == indexId) { + return entry.getKey(); + } + } + return null; + } + + public Status resetIdsForRestore(Catalog catalog, Database db, int restoreReplicationNum) { + // table id + id = catalog.getNextId(); + + // copy an origin index id to name map + Map origIdxIdToName = Maps.newHashMap(); + for (Map.Entry entry : indexNameToId.entrySet()) { + origIdxIdToName.put(entry.getValue(), entry.getKey()); + } + + // reset all 'indexIdToXXX' map + for (Map.Entry entry : origIdxIdToName.entrySet()) { + long newIdxId = 0; + if (entry.getValue().equals(name)) { + // base index + newIdxId = id; + } else { + newIdxId = catalog.getNextId(); + } + indexIdToSchema.put(newIdxId, indexIdToSchema.remove(entry.getKey())); + indexIdToSchemaHash.put(newIdxId, indexIdToSchemaHash.remove(entry.getKey())); + indexIdToSchemaVersion.put(newIdxId, indexIdToSchemaVersion.remove(entry.getKey())); + indexIdToShortKeyColumnCount.put(newIdxId, indexIdToShortKeyColumnCount.remove(entry.getKey())); + indexIdToStorageType.put(newIdxId, indexIdToStorageType.remove(entry.getKey())); + indexNameToId.put(entry.getValue(), newIdxId); + } + + // generate a partition name to id map + Map origPartNameToId = Maps.newHashMap(); + for (Partition partition : idToPartition.values()) { + origPartNameToId.put(partition.getName(), partition.getId()); + } + + // reset partition info and idToPartition map + if (partitionInfo.getType() == PartitionType.RANGE) { + RangePartitionInfo rangePartitionInfo = (RangePartitionInfo) partitionInfo; + for (Map.Entry entry : origPartNameToId.entrySet()) { + long newPartId = catalog.getNextId(); + rangePartitionInfo.idToDataProperty.put(newPartId, + rangePartitionInfo.idToDataProperty.remove(entry.getValue())); + rangePartitionInfo.idToReplicationNum.remove(entry.getValue()); + rangePartitionInfo.idToReplicationNum.put(newPartId, + (short) restoreReplicationNum); + rangePartitionInfo.getIdToRange().put(newPartId, + rangePartitionInfo.getIdToRange().remove(entry.getValue())); + + idToPartition.put(newPartId, idToPartition.remove(entry.getValue())); + } + } else { + // Single partitioned + long newPartId = catalog.getNextId(); + for (Map.Entry entry : origPartNameToId.entrySet()) { + partitionInfo.idToDataProperty.put(newPartId, partitionInfo.idToDataProperty.remove(entry.getValue())); + partitionInfo.idToReplicationNum.remove(entry.getValue()); + partitionInfo.idToReplicationNum.put(newPartId, (short) restoreReplicationNum); + idToPartition.put(newPartId, idToPartition.remove(entry.getValue())); + } + } + + // for each partition, reset rollup index map + for (Map.Entry entry : idToPartition.entrySet()) { + Partition partition = entry.getValue(); + for (Map.Entry entry2 : origIdxIdToName.entrySet()) { + MaterializedIndex idx = partition.getIndex(entry2.getKey()); + long newIdxId = indexNameToId.get(entry2.getValue()); + idx.setIdForRestore(newIdxId); + if (newIdxId != id) { + // not base table, reset + partition.deleteRollupIndex(entry2.getKey()); + partition.createRollupIndex(idx); + } + + // generate new tablets in origin tablet order + int tabletNum = idx.getTablets().size(); + idx.clearTabletsForRestore(); + for (int i = 0; i < tabletNum; i++) { + long newTabletId = catalog.getNextId(); + Tablet newTablet = new Tablet(newTabletId); + idx.addTablet(newTablet, null /* tablet meta */, true /* is restore */); + + // replicas + List beIds = Catalog.getCurrentSystemInfo().seqChooseBackendIds(partitionInfo.getReplicationNum(entry.getKey()), + true, true, + db.getClusterName()); + if (beIds == null) { + return new Status(ErrCode.COMMON_ERROR, "failed to find " + + partitionInfo.getReplicationNum(entry.getKey()) + + " different hosts to create table: " + name); + } + for (Long beId : beIds) { + long newReplicaId = catalog.getNextId(); + Replica replica = new Replica(newReplicaId, beId, ReplicaState.NORMAL, + partition.getCommittedVersion(), partition.getCommittedVersionHash()); + newTablet.addReplica(replica, true /* is restore */); + } + } + } + + // reset partition id + partition.setIdForRestore(entry.getKey()); + } + + return Status.OK; + } + + // schema + public Map> getIndexIdToSchema() { + return indexIdToSchema; + } + + public Map> getCopiedIndexIdToSchema() { + Map> copiedIndexIdToSchema = new HashMap>(); + copiedIndexIdToSchema.putAll(indexIdToSchema); + return copiedIndexIdToSchema; + } + + public List getSchemaByIndexId(Long indexId) { + return indexIdToSchema.get(indexId); + } + + public List getKeyColumnsByIndexId(Long indexId) { + ArrayList keyColumns = Lists.newArrayList(); + List allColumns = this.getSchemaByIndexId(indexId); + for (Column column : allColumns) { + if (column.isKey()) { + keyColumns.add(column); + } + } + + return keyColumns; + } + + // schema version + public int getSchemaVersionByIndexId(Long indexId) { + if (indexIdToSchemaVersion.containsKey(indexId)) { + return indexIdToSchemaVersion.get(indexId); + } + return -1; + } + + // schemaHash + public Map getIndexIdToSchemaHash() { + return indexIdToSchemaHash; + } + + public Map getCopiedIndexIdToSchemaHash() { + Map copiedIndexIdToSchemaHash = new HashMap(); + copiedIndexIdToSchemaHash.putAll(indexIdToSchemaHash); + return copiedIndexIdToSchemaHash; + } + + public int getSchemaHashByIndexId(Long indexId) { + if (indexIdToSchemaHash.containsKey(indexId)) { + return indexIdToSchemaHash.get(indexId); + } + return -1; + } + + // short key + public Map getIndexIdToShortKeyColumnCount() { + return indexIdToShortKeyColumnCount; + } + + public Map getCopiedIndexIdToShortKeyColumnCount() { + Map copiedIndexIdToShortKeyColumnCount = new HashMap(); + copiedIndexIdToShortKeyColumnCount.putAll(indexIdToShortKeyColumnCount); + return copiedIndexIdToShortKeyColumnCount; + } + + public short getShortKeyColumnCountByIndexId(Long indexId) { + if (indexIdToShortKeyColumnCount.containsKey(indexId)) { + return indexIdToShortKeyColumnCount.get(indexId); + } + return (short) -1; + } + + // storage type + public Map getIndexIdToStorageType() { + return indexIdToStorageType; + } + + public Map getCopiedIndexIdToStorageType() { + Map copiedIndexIdToStorageType = new HashMap(); + copiedIndexIdToStorageType.putAll(indexIdToStorageType); + return copiedIndexIdToStorageType; + } + + public void setStorageTypeToIndex(Long indexId, TStorageType storageType) { + indexIdToStorageType.put(indexId, storageType); + } + + public TStorageType getStorageTypeByIndexId(Long indexId) { + return indexIdToStorageType.get(indexId); + } + + public KeysType getKeysType() { + return keysType; + } + + public PartitionInfo getPartitionInfo() { + return partitionInfo; + } + + public DistributionInfo getDefaultDistributionInfo() { + return defaultDistributionInfo; + } + + public void renamePartition(String partitionName, String newPartitionName) { + if (partitionInfo.getType() == PartitionType.UNPARTITIONED) { + // bug fix + for (Partition partition : idToPartition.values()) { + partition.setName(newPartitionName); + nameToPartition.clear(); + nameToPartition.put(newPartitionName, partition); + LOG.info("rename patition {} in table {}", newPartitionName, name); + break; + } + } else { + Partition partition = nameToPartition.remove(partitionName); + partition.setName(newPartitionName); + nameToPartition.put(newPartitionName, partition); + } + } + + public void addPartition(Partition partition) { + idToPartition.put(partition.getId(), partition); + nameToPartition.put(partition.getName(), partition); + } + + public Partition dropPartition(long dbId, String partitionName) { + return dropPartition(dbId, partitionName, false); + } + + public Partition dropPartition(long dbId, String partitionName, boolean isRestore) { + Partition partition = nameToPartition.get(partitionName); + if (partition != null) { + idToPartition.remove(partition.getId()); + nameToPartition.remove(partitionName); + + Preconditions.checkState(partitionInfo.getType() == PartitionType.RANGE); + RangePartitionInfo rangePartitionInfo = (RangePartitionInfo) partitionInfo; + + if (!isRestore) { + // recycle partition + Catalog.getCurrentRecycleBin().recyclePartition(dbId, id, partition, + rangePartitionInfo.getRange(partition.getId()), + rangePartitionInfo.getDataProperty(partition.getId()), + rangePartitionInfo.getReplicationNum(partition.getId())); + } + + // drop partition info + rangePartitionInfo.dropPartition(partition.getId()); + } + return partition; + } + + public Partition dropPartitionForBackup(String partitionName) { + return dropPartition(-1, partitionName, true); + } + + public Collection getPartitions() { + return idToPartition.values(); + } + + public Partition getPartition(long partitionId) { + return idToPartition.get(partitionId); + } + + public Partition getPartition(String partitionName) { + return nameToPartition.get(partitionName); + } + + public Set getPartitionNames() { + return Sets.newHashSet(nameToPartition.keySet()); + } + + public Set getCopiedBfColumns() { + if (bfColumns == null) { + return null; + } + + return Sets.newHashSet(bfColumns); + } + + public double getBfFpp() { + return bfFpp; + } + + public void setBloomFilterInfo(Set bfColumns, double bfFpp) { + this.bfColumns = bfColumns; + this.bfFpp = bfFpp; + } + + public TTableDescriptor toThrift() { + TOlapTable tOlapTable = new TOlapTable(getName()); + TTableDescriptor tTableDescriptor = new TTableDescriptor(id, TTableType.OLAP_TABLE, + baseSchema.size(), 0, getName(), ""); + tTableDescriptor.setOlapTable(tOlapTable); + return tTableDescriptor; + } + + public long getRowCount() { + long rowCount = 0; + for (Map.Entry entry : idToPartition.entrySet()) { + rowCount += ((Partition) entry.getValue()).getBaseIndex().getRowCount(); + } + return rowCount; + } + + public AlterTableStmt toAddRollupStmt(String dbName, Collection indexIds) { + List alterClauses = Lists.newArrayList(); + for (Map.Entry entry : indexNameToId.entrySet()) { + String indexName = entry.getKey(); + long indexId = entry.getValue(); + if (!indexIds.contains(indexId)) { + continue; + } + + // cols + List columnNames = Lists.newArrayList(); + for (Column column : indexIdToSchema.get(indexId)) { + columnNames.add(column.getName()); + } + + // properties + Map properties = Maps.newHashMap(); + properties.put(PropertyAnalyzer.PROPERTIES_STORAGE_TYPE, indexIdToStorageType.get(indexId).name()); + properties.put(PropertyAnalyzer.PROPERTIES_SHORT_KEY, indexIdToShortKeyColumnCount.get(indexId).toString()); + properties.put(PropertyAnalyzer.PROPERTIES_SCHEMA_VERSION, indexIdToSchemaVersion.get(indexId).toString()); + + AddRollupClause addRollupClause = new AddRollupClause(indexName, columnNames, null, null, properties); + alterClauses.add(addRollupClause); + } + + AlterTableStmt alterTableStmt = new AlterTableStmt(new TableName(dbName, name), alterClauses); + return alterTableStmt; + } + + public AlterTableStmt toAddPartitionStmt(String dbName, String partitionName) { + Preconditions.checkState(partitionInfo.getType() == PartitionType.RANGE); + RangePartitionInfo rangePartitionInfo = (RangePartitionInfo) partitionInfo; + List alterClauses = Lists.newArrayList(); + + Partition partition = nameToPartition.get(partitionName); + Map properties = Maps.newHashMap(); + long version = partition.getCommittedVersion(); + long versionHash = partition.getCommittedVersionHash(); + properties.put(PropertyAnalyzer.PROPERTIES_VERSION_INFO, version + "," + versionHash); + properties.put(PropertyAnalyzer.PROPERTIES_REPLICATION_NUM, + String.valueOf(partitionInfo.getReplicationNum(partition.getId()))); + + SingleRangePartitionDesc singleDesc = + rangePartitionInfo.toSingleRangePartitionDesc(partition.getId(), partitionName, properties); + DistributionDesc distributionDesc = partition.getDistributionInfo().toDistributionDesc(); + + AddPartitionClause addPartitionClause = new AddPartitionClause(singleDesc, distributionDesc, null); + alterClauses.add(addPartitionClause); + AlterTableStmt stmt = new AlterTableStmt(new TableName(dbName, name), alterClauses); + return stmt; + } + + @Override + public CreateTableStmt toCreateTableStmt(String dbName) { + Map properties = Maps.newHashMap(); + + // partition + PartitionDesc partitionDesc = null; + if (partitionInfo.getType() == PartitionType.RANGE) { + RangePartitionInfo rangePartitionInfo = (RangePartitionInfo) partitionInfo; + List partitionColumns = rangePartitionInfo.getPartitionColumns(); + List partitionColNames = Lists.newArrayList(); + for (Column partCol : partitionColumns) { + partitionColNames.add(partCol.getName()); + } + + List singlePartitionDescs = Lists.newArrayList(); + partitionDesc = new RangePartitionDesc(partitionColNames, singlePartitionDescs); + } else { + Short replicationNum = partitionInfo.getReplicationNum(nameToPartition.get(name).getId()); + properties.put(PropertyAnalyzer.PROPERTIES_REPLICATION_NUM, replicationNum.toString()); + // and partition version info here for non-partitioned table + Partition partition = getPartition(name); + Preconditions.checkNotNull(partition); + long version = partition.getCommittedVersion(); + long versionHash = partition.getCommittedVersionHash(); + String versionProp = Joiner.on(",").join(version, versionHash); + properties.put(PropertyAnalyzer.PROPERTIES_VERSION_INFO, versionProp); + } + + // keys + List keysColumnNames = Lists.newArrayList(); + for (Column column : baseSchema) { + if (column.isKey()) { + keysColumnNames.add(column.getName()); + } + } + KeysDesc keysDesc = new KeysDesc(keysType, keysColumnNames); + + // distribution + DistributionDesc distributionDesc = defaultDistributionInfo.toDistributionDesc(); + + // other properties + properties.put(PropertyAnalyzer.PROPERTIES_SHORT_KEY, indexIdToShortKeyColumnCount.get(id).toString()); + properties.put(PropertyAnalyzer.PROPERTIES_STORAGE_TYPE, indexIdToStorageType.get(id).name()); + if (bfColumns != null) { + String bfCols = Joiner.on(",").join(bfColumns); + properties.put(PropertyAnalyzer.PROPERTIES_BF_COLUMNS, bfCols); + properties.put(PropertyAnalyzer.PROPERTIES_BF_FPP, String.valueOf(bfFpp)); + } + properties.put(PropertyAnalyzer.PROPERTIES_SCHEMA_VERSION, indexIdToSchemaVersion.get(id).toString()); + + CreateTableStmt stmt = new CreateTableStmt(false, false, new TableName(dbName, name), baseSchema, + type.name(), keysDesc, partitionDesc, distributionDesc, + properties, null); + return stmt; + } + + public int getSignature(int signatureVersion, List partNames) { + Adler32 adler32 = new Adler32(); + adler32.update(signatureVersion); + final String charsetName = "UTF-8"; + + try { + // table name + adler32.update(name.getBytes(charsetName)); + LOG.debug("signature. table name: {}", name); + // type + adler32.update(type.name().getBytes(charsetName)); + LOG.debug("signature. table type: {}", type.name()); + + // all indices(should be in order) + Set indexNames = Sets.newTreeSet(); + indexNames.addAll(indexNameToId.keySet()); + for (String indexName : indexNames) { + long indexId = indexNameToId.get(indexName); + adler32.update(indexName.getBytes(charsetName)); + LOG.debug("signature. index name: {}", indexName); + // schema hash + adler32.update(indexIdToSchemaHash.get(indexId)); + LOG.debug("signature. index schema hash: {}", indexIdToSchemaHash.get(indexId)); + // short key column count + adler32.update(indexIdToShortKeyColumnCount.get(indexId)); + LOG.debug("signature. index short key: {}", indexIdToShortKeyColumnCount.get(indexId)); + // storage type + adler32.update(indexIdToStorageType.get(indexId).name().getBytes(charsetName)); + LOG.debug("signature. index storage type: {}", indexIdToStorageType.get(indexId)); + } + + // bloom filter + if (bfColumns != null && !bfColumns.isEmpty()) { + for (String bfCol : bfColumns) { + adler32.update(bfCol.getBytes()); + LOG.debug("signature. bf col: {}", bfCol); + } + adler32.update(String.valueOf(bfFpp).getBytes()); + LOG.debug("signature. bf fpp: {}", bfFpp); + } + + // partition type + adler32.update(partitionInfo.getType().name().getBytes(charsetName)); + LOG.debug("signature. partition type: {}", partitionInfo.getType().name()); + // partition columns + if (partitionInfo.getType() == PartitionType.RANGE) { + RangePartitionInfo rangePartitionInfo = (RangePartitionInfo) partitionInfo; + List partitionColumns = rangePartitionInfo.getPartitionColumns(); + adler32.update(Util.schemaHash(0, partitionColumns, null, 0)); + LOG.debug("signature. partition col hash: {}", Util.schemaHash(0, partitionColumns, null, 0)); + } + + // partition and distribution + Collections.sort(partNames, String.CASE_INSENSITIVE_ORDER); + for (String partName : partNames) { + Partition partition = getPartition(partName); + Preconditions.checkNotNull(partition, partName); + adler32.update(partName.getBytes(charsetName)); + LOG.debug("signature. partition name: {}", partName); + DistributionInfo distributionInfo = partition.getDistributionInfo(); + adler32.update(distributionInfo.getType().name().getBytes(charsetName)); + if (distributionInfo.getType() == DistributionInfoType.HASH) { + HashDistributionInfo hashDistributionInfo = (HashDistributionInfo) distributionInfo; + adler32.update(Util.schemaHash(0, hashDistributionInfo.getDistributionColumns(), null, 0)); + LOG.debug("signature. distribution col hash: {}", + Util.schemaHash(0, hashDistributionInfo.getDistributionColumns(), null, 0)); + adler32.update(hashDistributionInfo.getBucketNum()); + LOG.debug("signature. bucket num: {}", hashDistributionInfo.getBucketNum()); + } + } + + } catch (UnsupportedEncodingException e) { + LOG.error("encoding error", e); + return -1; + } + + LOG.debug("signature: {}", Math.abs((int) adler32.getValue())); + return Math.abs((int) adler32.getValue()); + } + + public Status getIntersectPartNamesWith(OlapTable anotherTbl, List intersectPartNames) { + if (this.getPartitionInfo().getType() != anotherTbl.getPartitionInfo().getType()) { + return new Status(ErrCode.COMMON_ERROR, "Table's partition type is different"); + } + + Set intersect = this.getPartitionNames(); + intersect.retainAll(anotherTbl.getPartitionNames()); + intersectPartNames.addAll(intersect); + return Status.OK; + } + + @Override + public boolean isPartitioned() { + int numSegs = 0; + for (Partition part : getPartitions()) { + numSegs += part.getDistributionInfo().getBucketNum(); + if (numSegs > 1) { + return true; + } + } + return false; + } + + @Override + public void write(DataOutput out) throws IOException { + super.write(out); + + // state + Text.writeString(out, state.name()); + + // indices' schema + int counter = indexNameToId.size(); + out.writeInt(counter); + for (Map.Entry entry : indexNameToId.entrySet()) { + String indexName = entry.getKey(); + long indexId = entry.getValue(); + Text.writeString(out, indexName); + out.writeLong(indexId); + // schema + out.writeInt(indexIdToSchema.get(indexId).size()); + for (Column column : indexIdToSchema.get(indexId)) { + column.write(out); + } + + // storage type + Text.writeString(out, indexIdToStorageType.get(indexId).name()); + + // indices's schema version + out.writeInt(indexIdToSchemaVersion.get(indexId)); + + // indices's schema hash + out.writeInt(indexIdToSchemaHash.get(indexId)); + + // indices's short key column count + out.writeShort(indexIdToShortKeyColumnCount.get(indexId)); + } + + Text.writeString(out, keysType.name()); + Text.writeString(out, partitionInfo.getType().name()); + partitionInfo.write(out); + Text.writeString(out, defaultDistributionInfo.getType().name()); + defaultDistributionInfo.write(out); + + // partitions + int partitionCount = idToPartition.size(); + out.writeInt(partitionCount); + for (Partition partition : idToPartition.values()) { + partition.write(out); + } + + // bloom filter columns + if (bfColumns == null) { + out.writeBoolean(false); + } else { + out.writeBoolean(true); + out.writeInt(bfColumns.size()); + for (String bfColumn : bfColumns) { + Text.writeString(out, bfColumn); + } + out.writeDouble(bfFpp); + } + } + + @Override + public void readFields(DataInput in) throws IOException { + super.readFields(in); + + this.state = OlapTableState.valueOf(Text.readString(in)); + + // indices's schema + int counter = in.readInt(); + for (int i = 0; i < counter; i++) { + String indexName = Text.readString(in); + long indexId = in.readLong(); + this.indexNameToId.put(indexName, indexId); + + // schema + int colCount = in.readInt(); + List schema = new LinkedList(); + for (int j = 0; j < colCount; j++) { + Column column = Column.read(in); + schema.add(column); + } + this.indexIdToSchema.put(indexId, schema); + + // storage type + TStorageType type = TStorageType.valueOf(Text.readString(in)); + this.indexIdToStorageType.put(indexId, type); + + // indices's schema version + this.indexIdToSchemaVersion.put(indexId, in.readInt()); + + // indices's schema hash + this.indexIdToSchemaHash.put(indexId, in.readInt()); + + // indices's short key column count + this.indexIdToShortKeyColumnCount.put(indexId, in.readShort()); + } + + // partition and distribution info + if (Catalog.getCurrentCatalogJournalVersion() >= FeMetaVersion.VERSION_30) { + keysType = KeysType.valueOf(Text.readString(in)); + } else { + keysType = KeysType.AGG_KEYS; + } + + PartitionType partType = PartitionType.valueOf(Text.readString(in)); + if (partType == PartitionType.UNPARTITIONED) { + partitionInfo = PartitionInfo.read(in); + } else if (partType == PartitionType.RANGE) { + partitionInfo = RangePartitionInfo.read(in); + } else { + throw new IOException("invalid partition type: " + partType); + } + + DistributionInfoType distriType = DistributionInfoType.valueOf(Text.readString(in)); + if (distriType == DistributionInfoType.HASH) { + defaultDistributionInfo = HashDistributionInfo.read(in); + } else if (distriType == DistributionInfoType.RANDOM) { + defaultDistributionInfo = RandomDistributionInfo.read(in); + } else { + throw new IOException("invalid distribution type: " + distriType); + } + + int partitionCount = in.readInt(); + for (int i = 0; i < partitionCount; ++i) { + Partition partition = Partition.read(in); + idToPartition.put(partition.getId(), partition); + nameToPartition.put(partition.getName(), partition); + } + + if (Catalog.getCurrentCatalogJournalVersion() >= FeMetaVersion.VERSION_9) { + if (in.readBoolean()) { + int bfColumnCount = in.readInt(); + bfColumns = Sets.newHashSet(); + for (int i = 0; i < bfColumnCount; i++) { + bfColumns.add(Text.readString(in)); + } + + bfFpp = in.readDouble(); + } + } + } + + public boolean equals(Table table) { + if (this == table) { + return true; + } + if (!(table instanceof OlapTable)) { + return false; + } + + return true; + } + + public OlapTable selectiveCopy(Collection reservedPartNames) { + OlapTable copied = new OlapTable(); + if (!DeepCopy.copy(this, copied)) { + LOG.warn("failed to copy olap table: " + getName()); + return null; + } + + if (reservedPartNames == null || reservedPartNames.isEmpty()) { + // reserve all + return copied; + } + + Set partNames = Sets.newTreeSet(String.CASE_INSENSITIVE_ORDER); + partNames.addAll(copied.getPartitionNames()); + + for (String partName : partNames) { + if (!reservedPartNames.contains(partName)) { + copied.dropPartitionForBackup(partName); + } + } + + return copied; + } +} + diff --git a/fe/src/com/baidu/palo/catalog/Partition.java b/fe/src/com/baidu/palo/catalog/Partition.java index 4e22a9d224..6384ff5db9 100644 --- a/fe/src/com/baidu/palo/catalog/Partition.java +++ b/fe/src/com/baidu/palo/catalog/Partition.java @@ -15,17 +15,17 @@ package com.baidu.palo.catalog; -import com.baidu.palo.catalog.DistributionInfo.DistributionInfoType; -import com.baidu.palo.common.io.Text; -import com.baidu.palo.common.io.Writable; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.List; -import java.util.ArrayList; +import com.baidu.palo.catalog.DistributionInfo.DistributionInfoType; +import com.baidu.palo.common.io.Text; +import com.baidu.palo.common.io.Writable; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; /** @@ -67,6 +67,10 @@ public class Partition extends MetaObject implements Writable { this.committedVersion = PARTITION_INIT_VERSION; this.committedVersionHash = PARTITION_INIT_VERSION_HASH; this.distributionInfo = distributionInfo; + } + + public void setIdForRestore(long id) { + this.id = id; } public long getId() { diff --git a/fe/src/com/baidu/palo/catalog/RangePartitionInfo.java b/fe/src/com/baidu/palo/catalog/RangePartitionInfo.java index 76aea2c98d..c350ebaf45 100644 --- a/fe/src/com/baidu/palo/catalog/RangePartitionInfo.java +++ b/fe/src/com/baidu/palo/catalog/RangePartitionInfo.java @@ -13,403 +13,423 @@ // specific language governing permissions and limitations // under the License. -package com.baidu.palo.catalog; - -import com.baidu.palo.analysis.PartitionKeyDesc; -import com.baidu.palo.analysis.SingleRangePartitionDesc; -import com.baidu.palo.common.AnalysisException; -import com.baidu.palo.common.DdlException; -import com.baidu.palo.common.FeNameFormat; -import com.baidu.palo.common.util.PropertyAnalyzer; - -import com.google.common.base.Preconditions; -import com.google.common.collect.BoundType; -import com.google.common.collect.Lists; -import com.google.common.collect.Range; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.LinkedList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class RangePartitionInfo extends PartitionInfo { - private static final Logger LOG = LogManager.getLogger(RangePartitionInfo.class); - - private List partitionColumns; - // partition id -> partition range - private Map> idToRange; - - private static final Comparator>> RANGE_MAP_ENTRY_COMPARATOR; - - static { - RANGE_MAP_ENTRY_COMPARATOR = new Comparator>>() { - @Override - public int compare(Map.Entry> o1, - Map.Entry> o2) { - return o1.getValue().lowerEndpoint().compareTo(o2.getValue().lowerEndpoint()); - } - }; - } - - public RangePartitionInfo() { - // for persist - super(); - this.partitionColumns = new LinkedList(); - this.idToRange = new HashMap>(); - } - - public RangePartitionInfo(List partitionColumns) { - super(PartitionType.RANGE); - this.partitionColumns = partitionColumns; - this.idToRange = new HashMap>(); - } - - public List getPartitionColumns() { - return partitionColumns; - } - - public void dropPartition(long partitionId) { - idToRange.remove(partitionId); - idToDataProperty.remove(partitionId); - idToReplicationNum.remove(partitionId); - } - - public Range checkAndCreateRange(SingleRangePartitionDesc desc) throws DdlException { - Range newRange = null; - // check range - try { - // create single value partition key - PartitionKeyDesc partKeyDesc = desc.getPartitionKeyDesc(); - PartitionKey singlePartitionKey = null; - if (partKeyDesc.isMax()) { - singlePartitionKey = PartitionKey.createInfinityPartitionKey(partitionColumns, true); - } else { - singlePartitionKey = PartitionKey.createPartitionKey(partKeyDesc.getUpperValues(), partitionColumns); - } - - if (singlePartitionKey.isMinValue()) { - throw new DdlException("Partition value should not be MIN VALUE: " + singlePartitionKey.toSql()); - } - - List>> entries = - new ArrayList>>(this.idToRange.entrySet()); - Collections.sort(entries, RANGE_MAP_ENTRY_COMPARATOR); - - Range lastRange = null; - Range nextRange = null; - for (Map.Entry> entry : entries) { - nextRange = entry.getValue(); - - // check if equals to upper bound - PartitionKey upperKey = nextRange.upperEndpoint(); - if (upperKey.compareTo(singlePartitionKey) >= 0) { - PartitionKey lowKey = null; - if (!partKeyDesc.getLowerValues().isEmpty()) { - lowKey = PartitionKey.createPartitionKey(partKeyDesc.getLowerValues(), partitionColumns); - } else { - if (lastRange == null) { - lowKey = PartitionKey.createInfinityPartitionKey(partitionColumns, false); - } else { - lowKey = lastRange.upperEndpoint(); - } - } - - newRange = Range.closedOpen(lowKey, singlePartitionKey); - - // check if range intersected - checkRangeIntersect(newRange, nextRange); - break; - } - lastRange = nextRange; - } // end for ranges - - if (newRange == null) { - PartitionKey lowKey = null; - if (!partKeyDesc.getLowerValues().isEmpty()) { - lowKey = PartitionKey.createPartitionKey(partKeyDesc.getLowerValues(), partitionColumns); - } else { - if (lastRange == null) { - // add first partition to this table. so the lower key is MIN - lowKey = PartitionKey.createInfinityPartitionKey(partitionColumns, false); - } else { - lowKey = lastRange.upperEndpoint(); - } - } - - newRange = Range.closedOpen(lowKey, singlePartitionKey); - } - } catch (AnalysisException e) { - throw new DdlException("Invalid range value format: " + e.getMessage()); - } - - Preconditions.checkNotNull(newRange); - return newRange; - } - - public static void checkRangeIntersect(Range range1, Range range2) throws DdlException { - if (range2.isConnected(range1)) { - if (!range2.intersection(range1).isEmpty()) { - throw new DdlException("Range " + range1 + " is intersected with range: " + range2); - } - } - } - - public void handleNewSinglePartitionDesc(SingleRangePartitionDesc desc, long partitionId) throws DdlException { - Preconditions.checkArgument(desc.isAnalyzed()); - try { - Range range = checkAndCreateRange(desc); - idToRange.put(partitionId, range); - } catch (IllegalArgumentException e) { - // Range.closedOpen may throw this if (lower > upper) - throw new DdlException("Invalid key range", e); - } - idToDataProperty.put(partitionId, desc.getPartitionDataProperty()); - idToReplicationNum.put(partitionId, desc.getReplicationNum()); - } - - // for catalog restore - public void unprotectHandleNewSinglePartitionDesc(long partitionId, Range range, - DataProperty dataProperty, short replicationNum) - throws DdlException { - idToRange.put(partitionId, range); - idToDataProperty.put(partitionId, dataProperty); - idToReplicationNum.put(partitionId, replicationNum); - } - - public void setRange(long partitionId, Range range) { - idToRange.put(partitionId, range); - } - - public Map> getIdToRange() { - return idToRange; - } - - public Range getRange(long partitionId) { - return idToRange.get(partitionId); - } - - public static void checkRangeColumnType(Column column) throws AnalysisException { - PrimitiveType type = column.getDataType(); - if (!type.isFixedPointType() && !type.isDateType()) { - throw new AnalysisException("Column[" + column.getName() + "] type[" + type - + "] cannot be a range partition key."); - } - } - - public List>> getSortedRangeMap() { - List>> sortedList = Lists.newArrayList(this.idToRange.entrySet()); - Collections.sort(sortedList, RANGE_MAP_ENTRY_COMPARATOR); - return sortedList; - } - - public static void writeRange(DataOutput out, Range range) throws IOException { - boolean hasLowerBound = false; - boolean hasUpperBound = false; - - // write lower bound if lower bound exists - hasLowerBound = range.hasLowerBound(); - out.writeBoolean(hasLowerBound); - if (hasLowerBound) { - PartitionKey lowerBound = range.lowerEndpoint(); - out.writeBoolean(range.lowerBoundType() == BoundType.CLOSED); - lowerBound.write(out); - } - - // write upper bound if upper bound exists - hasUpperBound = range.hasUpperBound(); - out.writeBoolean(hasUpperBound); - if (hasUpperBound) { - PartitionKey upperBound = range.upperEndpoint(); - out.writeBoolean(range.upperBoundType() == BoundType.CLOSED); - upperBound.write(out); - } - } - - public static Range readRange(DataInput in) throws IOException { - boolean hasLowerBound = false; - boolean hasUpperBound = false; - boolean lowerBoundClosed = false; - boolean upperBoundClosed = false; - PartitionKey lowerBound = null; - PartitionKey upperBound = null; - - hasLowerBound = in.readBoolean(); - if (hasLowerBound) { - lowerBoundClosed = in.readBoolean(); - lowerBound = PartitionKey.read(in); - } - - hasUpperBound = in.readBoolean(); - if (hasUpperBound) { - upperBoundClosed = in.readBoolean(); - upperBound = PartitionKey.read(in); - } - - // Totally 9 cases. Both lower bound and upper bound could be open, closed or not exist - if (hasLowerBound && lowerBoundClosed && hasUpperBound && upperBoundClosed) { - return Range.closed(lowerBound, upperBound); - } - if (hasLowerBound && lowerBoundClosed && hasUpperBound && !upperBoundClosed) { - return Range.closedOpen(lowerBound, upperBound); - } - if (hasLowerBound && !lowerBoundClosed && hasUpperBound && upperBoundClosed) { - return Range.openClosed(lowerBound, upperBound); - } - if (hasLowerBound && !lowerBoundClosed && hasUpperBound && !upperBoundClosed) { - return Range.open(lowerBound, upperBound); - } - if (hasLowerBound && lowerBoundClosed && !hasUpperBound) { - return Range.atLeast(lowerBound); - } - if (hasLowerBound && !lowerBoundClosed && !hasUpperBound) { - return Range.greaterThan(lowerBound); - } - if (!hasLowerBound && hasUpperBound && upperBoundClosed) { - return Range.atMost(upperBound); - } - if (!hasLowerBound && hasUpperBound && !upperBoundClosed) { - return Range.lessThan(upperBound); - } - // Neither lower bound nor upper bound exists, return null. This means just one partition - return null; - } - - public SingleRangePartitionDesc toSingleRangePartitionDesc(long partitionId, String partitionName, - Map properties) { - Range range = idToRange.get(partitionId); - List upperValues = Lists.newArrayList(); - List lowerValues = Lists.newArrayList(); - // FIXME(cmy): check here(getStringValue) - lowerValues.add(range.lowerEndpoint().getKeys().get(0).getStringValue()); - - PartitionKey upperKey = range.upperEndpoint(); - PartitionKeyDesc keyDesc = null; - if (upperKey.isMaxValue()) { - keyDesc = PartitionKeyDesc.createMaxKeyDesc(); - keyDesc.setLowerValues(lowerValues); - } else { - upperValues.add(range.upperEndpoint().getKeys().get(0).getStringValue()); - keyDesc = new PartitionKeyDesc(lowerValues, upperValues); - } - - SingleRangePartitionDesc singleDesc = new SingleRangePartitionDesc(false, partitionName, keyDesc, properties); - - if (properties != null) { - // properties - Short replicationNum = getReplicationNum(partitionId); - properties.put(PropertyAnalyzer.PROPERTIES_REPLICATION_NUM, replicationNum.toString()); - } - return singleDesc; - } - - public static PartitionInfo read(DataInput in) throws IOException { - PartitionInfo partitionInfo = new RangePartitionInfo(); - partitionInfo.readFields(in); - return partitionInfo; - } - - @Override - public void write(DataOutput out) throws IOException { - super.write(out); - - // partition columns - out.writeInt(partitionColumns.size()); - for (Column column : partitionColumns) { - column.write(out); - } - - out.writeInt(idToRange.size()); - for (Map.Entry> entry : idToRange.entrySet()) { - out.writeLong(entry.getKey()); - RangePartitionInfo.writeRange(out, entry.getValue()); - } - } - - @Override - public void readFields(DataInput in) throws IOException { - super.readFields(in); - - int counter = in.readInt(); - for (int i = 0; i < counter; i++) { - Column column = Column.read(in); - partitionColumns.add(column); - } - - counter = in.readInt(); - for (int i = 0; i < counter; i++) { - long partitionId = in.readLong(); - Range range = RangePartitionInfo.readRange(in); - idToRange.put(partitionId, range); - } - } - - @Override - public String toSql(OlapTable table, List partitionId) { - StringBuilder sb = new StringBuilder(); - sb.append("PARTITION BY RANGE("); - int idx = 0; - for (Column column : partitionColumns) { - if (idx != 0) { - sb.append(", "); - } - sb.append("`").append(column.getName()).append("`"); - idx++; - } - sb.append(")\n("); - - // sort range - List>> entries = - new ArrayList>>(this.idToRange.entrySet()); - Collections.sort(entries, RANGE_MAP_ENTRY_COMPARATOR); - - Range lastRange = null; - idx = 0; - for (Map.Entry> entry : entries) { - Partition partition = table.getPartition(entry.getKey()); - String partitionName = partition.getName(); - Range range = entry.getValue(); - - if (idx == 0) { - // first partition - if (!range.lowerEndpoint().isMinValue()) { - sb.append("PARTITION ").append(FeNameFormat.FORBIDDEN_PARTITION_NAME).append(idx) - .append(" VALUES LESS THAN ").append(range.lowerEndpoint().toSql()); - sb.append(",\n"); - } - } else { - Preconditions.checkNotNull(lastRange); - if (!lastRange.upperEndpoint().equals(range.lowerEndpoint())) { - sb.append("PARTITION ").append(FeNameFormat.FORBIDDEN_PARTITION_NAME).append(idx) - .append(" VALUES LESS THAN ").append(range.lowerEndpoint().toSql()); - sb.append(",\n"); - } - } - - sb.append("PARTITION ").append(partitionName).append(" VALUES LESS THAN "); - sb.append(range.upperEndpoint().toSql()); - - if (partitionId != null) { - partitionId.add(entry.getKey()); - break; - } - - if (idx != entries.size() - 1) { - sb.append(",\n"); - } - idx++; - - lastRange = range; - } - sb.append(")"); - return sb.toString(); - } -} +package com.baidu.palo.catalog; + +import com.baidu.palo.analysis.PartitionKeyDesc; +import com.baidu.palo.analysis.SingleRangePartitionDesc; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.DdlException; +import com.baidu.palo.common.FeNameFormat; +import com.baidu.palo.common.util.PropertyAnalyzer; + +import com.google.common.base.Preconditions; +import com.google.common.collect.BoundType; +import com.google.common.collect.Lists; +import com.google.common.collect.Range; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +public class RangePartitionInfo extends PartitionInfo { + private static final Logger LOG = LogManager.getLogger(RangePartitionInfo.class); + + private List partitionColumns; + // partition id -> partition range + private Map> idToRange; + + private static final Comparator>> RANGE_MAP_ENTRY_COMPARATOR; + + static { + RANGE_MAP_ENTRY_COMPARATOR = new Comparator>>() { + @Override + public int compare(Map.Entry> o1, + Map.Entry> o2) { + return o1.getValue().lowerEndpoint().compareTo(o2.getValue().lowerEndpoint()); + } + }; + } + + public RangePartitionInfo() { + // for persist + super(); + this.partitionColumns = new LinkedList(); + this.idToRange = new HashMap>(); + } + + public RangePartitionInfo(List partitionColumns) { + super(PartitionType.RANGE); + this.partitionColumns = partitionColumns; + this.idToRange = new HashMap>(); + } + + public List getPartitionColumns() { + return partitionColumns; + } + + public void dropPartition(long partitionId) { + idToRange.remove(partitionId); + idToDataProperty.remove(partitionId); + idToReplicationNum.remove(partitionId); + } + + public void addPartitionForRestore(long partitionId, Range range, DataProperty dataProperty, + short replicationNum) { + idToRange.put(partitionId, range); + idToDataProperty.put(partitionId, dataProperty); + idToReplicationNum.put(partitionId, replicationNum); + } + + public Range checkAndCreateRange(SingleRangePartitionDesc desc) throws DdlException { + Range newRange = null; + // check range + try { + // create single value partition key + PartitionKeyDesc partKeyDesc = desc.getPartitionKeyDesc(); + PartitionKey singlePartitionKey = null; + if (partKeyDesc.isMax()) { + singlePartitionKey = PartitionKey.createInfinityPartitionKey(partitionColumns, true); + } else { + singlePartitionKey = PartitionKey.createPartitionKey(partKeyDesc.getUpperValues(), partitionColumns); + } + + if (singlePartitionKey.isMinValue()) { + throw new DdlException("Partition value should not be MIN VALUE: " + singlePartitionKey.toSql()); + } + + List>> entries = + new ArrayList>>(this.idToRange.entrySet()); + Collections.sort(entries, RANGE_MAP_ENTRY_COMPARATOR); + + Range lastRange = null; + Range nextRange = null; + for (Map.Entry> entry : entries) { + nextRange = entry.getValue(); + + // check if equals to upper bound + PartitionKey upperKey = nextRange.upperEndpoint(); + if (upperKey.compareTo(singlePartitionKey) >= 0) { + PartitionKey lowKey = null; + if (!partKeyDesc.getLowerValues().isEmpty()) { + lowKey = PartitionKey.createPartitionKey(partKeyDesc.getLowerValues(), partitionColumns); + } else { + if (lastRange == null) { + lowKey = PartitionKey.createInfinityPartitionKey(partitionColumns, false); + } else { + lowKey = lastRange.upperEndpoint(); + } + } + + newRange = Range.closedOpen(lowKey, singlePartitionKey); + + // check if range intersected + checkRangeIntersect(newRange, nextRange); + break; + } + lastRange = nextRange; + } // end for ranges + + if (newRange == null) { + PartitionKey lowKey = null; + if (!partKeyDesc.getLowerValues().isEmpty()) { + lowKey = PartitionKey.createPartitionKey(partKeyDesc.getLowerValues(), partitionColumns); + } else { + if (lastRange == null) { + // add first partition to this table. so the lower key is MIN + lowKey = PartitionKey.createInfinityPartitionKey(partitionColumns, false); + } else { + lowKey = lastRange.upperEndpoint(); + } + } + + newRange = Range.closedOpen(lowKey, singlePartitionKey); + } + } catch (AnalysisException e) { + throw new DdlException("Invalid range value format: " + e.getMessage()); + } + + Preconditions.checkNotNull(newRange); + return newRange; + } + + public static void checkRangeIntersect(Range range1, Range range2) throws DdlException { + if (range2.isConnected(range1)) { + if (!range2.intersection(range1).isEmpty()) { + throw new DdlException("Range " + range1 + " is intersected with range: " + range2); + } + } + } + + public void handleNewSinglePartitionDesc(SingleRangePartitionDesc desc, long partitionId) throws DdlException { + Preconditions.checkArgument(desc.isAnalyzed()); + try { + Range range = checkAndCreateRange(desc); + idToRange.put(partitionId, range); + } catch (IllegalArgumentException e) { + // Range.closedOpen may throw this if (lower > upper) + throw new DdlException("Invalid key range", e); + } + idToDataProperty.put(partitionId, desc.getPartitionDataProperty()); + idToReplicationNum.put(partitionId, desc.getReplicationNum()); + } + + // for catalog restore + public void unprotectHandleNewSinglePartitionDesc(long partitionId, Range range, + DataProperty dataProperty, short replicationNum) + throws DdlException { + idToRange.put(partitionId, range); + idToDataProperty.put(partitionId, dataProperty); + idToReplicationNum.put(partitionId, replicationNum); + } + + public void setRange(long partitionId, Range range) { + idToRange.put(partitionId, range); + } + + public Map> getIdToRange() { + return idToRange; + } + + public Range getRange(long partitionId) { + return idToRange.get(partitionId); + } + + public static void checkRangeColumnType(Column column) throws AnalysisException { + PrimitiveType type = column.getDataType(); + if (!type.isFixedPointType() && !type.isDateType()) { + throw new AnalysisException("Column[" + column.getName() + "] type[" + type + + "] cannot be a range partition key."); + } + } + + public List>> getSortedRangeMap() { + List>> sortedList = Lists.newArrayList(this.idToRange.entrySet()); + Collections.sort(sortedList, RANGE_MAP_ENTRY_COMPARATOR); + return sortedList; + } + + public static void writeRange(DataOutput out, Range range) throws IOException { + boolean hasLowerBound = false; + boolean hasUpperBound = false; + + // write lower bound if lower bound exists + hasLowerBound = range.hasLowerBound(); + out.writeBoolean(hasLowerBound); + if (hasLowerBound) { + PartitionKey lowerBound = range.lowerEndpoint(); + out.writeBoolean(range.lowerBoundType() == BoundType.CLOSED); + lowerBound.write(out); + } + + // write upper bound if upper bound exists + hasUpperBound = range.hasUpperBound(); + out.writeBoolean(hasUpperBound); + if (hasUpperBound) { + PartitionKey upperBound = range.upperEndpoint(); + out.writeBoolean(range.upperBoundType() == BoundType.CLOSED); + upperBound.write(out); + } + } + + public static Range readRange(DataInput in) throws IOException { + boolean hasLowerBound = false; + boolean hasUpperBound = false; + boolean lowerBoundClosed = false; + boolean upperBoundClosed = false; + PartitionKey lowerBound = null; + PartitionKey upperBound = null; + + hasLowerBound = in.readBoolean(); + if (hasLowerBound) { + lowerBoundClosed = in.readBoolean(); + lowerBound = PartitionKey.read(in); + } + + hasUpperBound = in.readBoolean(); + if (hasUpperBound) { + upperBoundClosed = in.readBoolean(); + upperBound = PartitionKey.read(in); + } + + // Totally 9 cases. Both lower bound and upper bound could be open, closed or not exist + if (hasLowerBound && lowerBoundClosed && hasUpperBound && upperBoundClosed) { + return Range.closed(lowerBound, upperBound); + } + if (hasLowerBound && lowerBoundClosed && hasUpperBound && !upperBoundClosed) { + return Range.closedOpen(lowerBound, upperBound); + } + if (hasLowerBound && !lowerBoundClosed && hasUpperBound && upperBoundClosed) { + return Range.openClosed(lowerBound, upperBound); + } + if (hasLowerBound && !lowerBoundClosed && hasUpperBound && !upperBoundClosed) { + return Range.open(lowerBound, upperBound); + } + if (hasLowerBound && lowerBoundClosed && !hasUpperBound) { + return Range.atLeast(lowerBound); + } + if (hasLowerBound && !lowerBoundClosed && !hasUpperBound) { + return Range.greaterThan(lowerBound); + } + if (!hasLowerBound && hasUpperBound && upperBoundClosed) { + return Range.atMost(upperBound); + } + if (!hasLowerBound && hasUpperBound && !upperBoundClosed) { + return Range.lessThan(upperBound); + } + // Neither lower bound nor upper bound exists, return null. This means just one partition + return null; + } + + public SingleRangePartitionDesc toSingleRangePartitionDesc(long partitionId, String partitionName, + Map properties) { + Range range = idToRange.get(partitionId); + List upperValues = Lists.newArrayList(); + List lowerValues = Lists.newArrayList(); + // FIXME(cmy): check here(getStringValue) + lowerValues.add(range.lowerEndpoint().getKeys().get(0).getStringValue()); + + PartitionKey upperKey = range.upperEndpoint(); + PartitionKeyDesc keyDesc = null; + if (upperKey.isMaxValue()) { + keyDesc = PartitionKeyDesc.createMaxKeyDesc(); + keyDesc.setLowerValues(lowerValues); + } else { + upperValues.add(range.upperEndpoint().getKeys().get(0).getStringValue()); + keyDesc = new PartitionKeyDesc(lowerValues, upperValues); + } + + SingleRangePartitionDesc singleDesc = new SingleRangePartitionDesc(false, partitionName, keyDesc, properties); + + if (properties != null) { + // properties + Short replicationNum = getReplicationNum(partitionId); + properties.put(PropertyAnalyzer.PROPERTIES_REPLICATION_NUM, replicationNum.toString()); + } + return singleDesc; + } + + public boolean checkRange(Range newRange) { + for (Range range : idToRange.values()) { + if (range.isConnected(newRange)) { + Range intersection = range.intersection(newRange); + if (!intersection.isEmpty()) { + return false; + } + } + } + return true; + } + + public static PartitionInfo read(DataInput in) throws IOException { + PartitionInfo partitionInfo = new RangePartitionInfo(); + partitionInfo.readFields(in); + return partitionInfo; + } + + @Override + public void write(DataOutput out) throws IOException { + super.write(out); + + // partition columns + out.writeInt(partitionColumns.size()); + for (Column column : partitionColumns) { + column.write(out); + } + + out.writeInt(idToRange.size()); + for (Map.Entry> entry : idToRange.entrySet()) { + out.writeLong(entry.getKey()); + RangePartitionInfo.writeRange(out, entry.getValue()); + } + } + + @Override + public void readFields(DataInput in) throws IOException { + super.readFields(in); + + int counter = in.readInt(); + for (int i = 0; i < counter; i++) { + Column column = Column.read(in); + partitionColumns.add(column); + } + + counter = in.readInt(); + for (int i = 0; i < counter; i++) { + long partitionId = in.readLong(); + Range range = RangePartitionInfo.readRange(in); + idToRange.put(partitionId, range); + } + } + + @Override + public String toSql(OlapTable table, List partitionId) { + StringBuilder sb = new StringBuilder(); + sb.append("PARTITION BY RANGE("); + int idx = 0; + for (Column column : partitionColumns) { + if (idx != 0) { + sb.append(", "); + } + sb.append("`").append(column.getName()).append("`"); + idx++; + } + sb.append(")\n("); + + // sort range + List>> entries = + new ArrayList>>(this.idToRange.entrySet()); + Collections.sort(entries, RANGE_MAP_ENTRY_COMPARATOR); + + Range lastRange = null; + idx = 0; + for (Map.Entry> entry : entries) { + Partition partition = table.getPartition(entry.getKey()); + String partitionName = partition.getName(); + Range range = entry.getValue(); + + if (idx == 0) { + // first partition + if (!range.lowerEndpoint().isMinValue()) { + sb.append("PARTITION ").append(FeNameFormat.FORBIDDEN_PARTITION_NAME).append(idx) + .append(" VALUES LESS THAN ").append(range.lowerEndpoint().toSql()); + sb.append(",\n"); + } + } else { + Preconditions.checkNotNull(lastRange); + if (!lastRange.upperEndpoint().equals(range.lowerEndpoint())) { + sb.append("PARTITION ").append(FeNameFormat.FORBIDDEN_PARTITION_NAME).append(idx) + .append(" VALUES LESS THAN ").append(range.lowerEndpoint().toSql()); + sb.append(",\n"); + } + } + + sb.append("PARTITION ").append(partitionName).append(" VALUES LESS THAN "); + sb.append(range.upperEndpoint().toSql()); + + if (partitionId != null) { + partitionId.add(entry.getKey()); + break; + } + + if (idx != entries.size() - 1) { + sb.append(",\n"); + } + idx++; + + lastRange = range; + } + sb.append(")"); + return sb.toString(); + } +} + diff --git a/fe/src/com/baidu/palo/catalog/Replica.java b/fe/src/com/baidu/palo/catalog/Replica.java index 1f91c80df3..4b7eee861f 100644 --- a/fe/src/com/baidu/palo/catalog/Replica.java +++ b/fe/src/com/baidu/palo/catalog/Replica.java @@ -13,194 +13,205 @@ // specific language governing permissions and limitations // under the License. -package com.baidu.palo.catalog; - -import com.baidu.palo.common.io.Text; -import com.baidu.palo.common.io.Writable; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.LogManager; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; -import java.util.Comparator; - -/** - * This class represents the olap replica related metadata. - */ -public class Replica implements Writable { - private static final Logger LOG = LogManager.getLogger(Replica.class); - public static final VersionComparator VERSION_DESC_COMPARATOR = new VersionComparator(); - - public enum ReplicaState { - NORMAL, - ROLLUP, - SCHEMA_CHANGE, - CLONE - } - - private long id; - private long backendId; - private long version; - private long versionHash; - private long dataSize; - private long rowCount; - private ReplicaState state; - - public Replica() { - } - - public Replica(long replicaId, long backendId, ReplicaState state) { - this(replicaId, backendId, -1, 0, -1, -1, state); - } - - public Replica(long replicaId, long backendId, ReplicaState state, long version, long versionHash) { - this(replicaId, backendId, version, versionHash, -1, -1, state); - } - - public Replica(long replicaId, long backendId, long version, long versionHash, - long dataSize, long rowCount, ReplicaState state) { - this.id = replicaId; - this.backendId = backendId; - this.version = version; - this.versionHash = versionHash; - this.dataSize = dataSize; - this.rowCount = rowCount; - this.state = state; - if (this.state == null) { - this.state = ReplicaState.NORMAL; - } - } - - public long getVersion() { - return this.version; - } - - public long getVersionHash() { - return this.versionHash; - } - - public long getId() { - return this.id; - } - - public long getBackendId() { - return this.backendId; - } - - public long getDataSize() { - return dataSize; - } - - public long getRowCount() { - return rowCount; - } - - public void updateInfo(long newVersion, long newVersionHash, long newDataSize, long newRowCount) { - if (newVersion < this.version) { - LOG.warn("replica[" + id + "] new version is lower than meta version. " + newVersion + " vs " + version); - } - this.version = newVersion; - this.versionHash = newVersionHash; - this.dataSize = newDataSize; - this.rowCount = newRowCount; - - LOG.debug("update {}", this.toString()); - } - - public boolean checkVersionCatchUp(long committedVersion, long committedVersionHash) { - if (this.version < committedVersion - || (this.version == committedVersion && this.versionHash != committedVersionHash)) { - LOG.debug("replica version does not catch up with version: {}-{}. replica: {}", - committedVersion, committedVersionHash, this); - return false; - } - return true; - } - - public void setState(ReplicaState replicaState) { - this.state = replicaState; - } - - public ReplicaState getState() { - return this.state; - } - - @Override - public String toString() { - StringBuffer strBuffer = new StringBuffer("replicaId="); - strBuffer.append(id); - strBuffer.append(", BackendId="); - strBuffer.append(backendId); - strBuffer.append(", version="); - strBuffer.append(version); - strBuffer.append(", versionHash="); - strBuffer.append(versionHash); - strBuffer.append(", dataSize="); - strBuffer.append(dataSize); - strBuffer.append(", rowCount="); - strBuffer.append(rowCount); - return strBuffer.toString(); - } - - public void write(DataOutput out) throws IOException { - out.writeLong(id); - out.writeLong(backendId); - out.writeLong(version); - out.writeLong(versionHash); - out.writeLong(dataSize); - out.writeLong(rowCount); - Text.writeString(out, state.name()); - } - - public void readFields(DataInput in) throws IOException { - id = in.readLong(); - backendId = in.readLong(); - version = in.readLong(); - versionHash = in.readLong(); - dataSize = in.readLong(); - rowCount = in.readLong(); - state = ReplicaState.valueOf(Text.readString(in)); - } - - public static Replica read(DataInput in) throws IOException { - Replica replica = new Replica(); - replica.readFields(in); - return replica; - } - - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof Replica)) { - return false; - } - - Replica replica = (Replica) obj; - return (id == replica.id) - && (backendId == replica.backendId) - && (version == replica.version) - && (versionHash == replica.versionHash) - && (dataSize == replica.dataSize) - && (rowCount == replica.rowCount) - && (state.equals(replica.state)); - } - - private static class VersionComparator implements Comparator { - public VersionComparator() { - } - - @Override - public int compare(T replica1, T replica2) { - if (replica1.getVersion() < replica2.getVersion()) { - return 1; - } else if (replica1.getVersion() == replica2.getVersion()) { - return 0; - } else { - return -1; - } - } - } -} +package com.baidu.palo.catalog; + +import com.baidu.palo.common.io.Text; +import com.baidu.palo.common.io.Writable; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.Comparator; +import java.util.concurrent.atomic.AtomicLong; + +/** + * This class represents the olap replica related metadata. + */ +public class Replica implements Writable { + private static final Logger LOG = LogManager.getLogger(Replica.class); + public static final VersionComparator VERSION_DESC_COMPARATOR = new VersionComparator(); + + public enum ReplicaState { + NORMAL, + ROLLUP, + SCHEMA_CHANGE, + CLONE + } + + private long id; + private long backendId; + private long version; + private long versionHash; + private long dataSize; + private long rowCount; + private ReplicaState state; + private AtomicLong versionCount = new AtomicLong(-1); + + public Replica() { + } + + public Replica(long replicaId, long backendId, ReplicaState state) { + this(replicaId, backendId, -1, 0, -1, -1, state); + } + + public Replica(long replicaId, long backendId, ReplicaState state, long version, long versionHash) { + this(replicaId, backendId, version, versionHash, -1, -1, state); + } + + public Replica(long replicaId, long backendId, long version, long versionHash, + long dataSize, long rowCount, ReplicaState state) { + this.id = replicaId; + this.backendId = backendId; + this.version = version; + this.versionHash = versionHash; + this.dataSize = dataSize; + this.rowCount = rowCount; + this.state = state; + if (this.state == null) { + this.state = ReplicaState.NORMAL; + } + } + + public long getVersion() { + return this.version; + } + + public long getVersionHash() { + return this.versionHash; + } + + public long getId() { + return this.id; + } + + public long getBackendId() { + return this.backendId; + } + + public long getDataSize() { + return dataSize; + } + + public long getRowCount() { + return rowCount; + } + + public void updateInfo(long newVersion, long newVersionHash, long newDataSize, long newRowCount) { + if (newVersion < this.version) { + LOG.warn("replica[" + id + "] new version is lower than meta version. " + newVersion + " vs " + version); + } + this.version = newVersion; + this.versionHash = newVersionHash; + this.dataSize = newDataSize; + this.rowCount = newRowCount; + + LOG.debug("update {}", this.toString()); + } + + public boolean checkVersionCatchUp(long committedVersion, long committedVersionHash) { + if (this.version < committedVersion + || (this.version == committedVersion && this.versionHash != committedVersionHash)) { + LOG.debug("replica version does not catch up with version: {}-{}. replica: {}", + committedVersion, committedVersionHash, this); + return false; + } + return true; + } + + public void setState(ReplicaState replicaState) { + this.state = replicaState; + } + + public ReplicaState getState() { + return this.state; + } + + public long getVersionCount() { + return versionCount.get(); + } + + public void setVersionCount(long versionCount) { + this.versionCount.set(versionCount); + } + + @Override + public String toString() { + StringBuffer strBuffer = new StringBuffer("replicaId="); + strBuffer.append(id); + strBuffer.append(", BackendId="); + strBuffer.append(backendId); + strBuffer.append(", version="); + strBuffer.append(version); + strBuffer.append(", versionHash="); + strBuffer.append(versionHash); + strBuffer.append(", dataSize="); + strBuffer.append(dataSize); + strBuffer.append(", rowCount="); + strBuffer.append(rowCount); + return strBuffer.toString(); + } + + public void write(DataOutput out) throws IOException { + out.writeLong(id); + out.writeLong(backendId); + out.writeLong(version); + out.writeLong(versionHash); + out.writeLong(dataSize); + out.writeLong(rowCount); + Text.writeString(out, state.name()); + } + + public void readFields(DataInput in) throws IOException { + id = in.readLong(); + backendId = in.readLong(); + version = in.readLong(); + versionHash = in.readLong(); + dataSize = in.readLong(); + rowCount = in.readLong(); + state = ReplicaState.valueOf(Text.readString(in)); + } + + public static Replica read(DataInput in) throws IOException { + Replica replica = new Replica(); + replica.readFields(in); + return replica; + } + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Replica)) { + return false; + } + + Replica replica = (Replica) obj; + return (id == replica.id) + && (backendId == replica.backendId) + && (version == replica.version) + && (versionHash == replica.versionHash) + && (dataSize == replica.dataSize) + && (rowCount == replica.rowCount) + && (state.equals(replica.state)); + } + + private static class VersionComparator implements Comparator { + public VersionComparator() { + } + + @Override + public int compare(T replica1, T replica2) { + if (replica1.getVersion() < replica2.getVersion()) { + return 1; + } else if (replica1.getVersion() == replica2.getVersion()) { + return 0; + } else { + return -1; + } + } + } +} + diff --git a/fe/src/com/baidu/palo/catalog/ResourceGroup.java b/fe/src/com/baidu/palo/catalog/ResourceGroup.java index 8ebf10d464..7968088322 100644 --- a/fe/src/com/baidu/palo/catalog/ResourceGroup.java +++ b/fe/src/com/baidu/palo/catalog/ResourceGroup.java @@ -140,7 +140,7 @@ public class ResourceGroup implements Writable { return new ResourceGroup(cpuShare); } - Builder cpuShare(int share) { + public Builder cpuShare(int share) { this.cpuShare = share; return this; } diff --git a/fe/src/com/baidu/palo/catalog/Table.java b/fe/src/com/baidu/palo/catalog/Table.java index 4809ba0c3a..06a2b83e06 100644 --- a/fe/src/com/baidu/palo/catalog/Table.java +++ b/fe/src/com/baidu/palo/catalog/Table.java @@ -59,6 +59,9 @@ public class Table extends MetaObject implements Writable { // tree map for case-insensitive lookup protected Map nameToColumn; + // DO NOT persist this variable. + protected boolean isTypeRead = false; + public Table(TableType type) { this.type = type; this.baseSchema = new LinkedList(); @@ -82,6 +85,14 @@ public class Table extends MetaObject implements Writable { } } + public boolean isTypeRead() { + return isTypeRead; + } + + public void setTypeRead(boolean isTypeRead) { + this.isTypeRead = isTypeRead; + } + public long getId() { return id; } @@ -129,27 +140,27 @@ public class Table extends MetaObject implements Writable { TableType type = TableType.valueOf(Text.readString(in)); if (type == TableType.OLAP) { table = new OlapTable(); - table.readFields(in); } else if (type == TableType.MYSQL) { table = new MysqlTable(); - table.readFields(in); } else if (type == TableType.VIEW) { - View view = new View(); - view.readFields(in); + table = new View(); + } else if (type == TableType.KUDU) { + table = new KuduTable(); + } else if (type == TableType.BROKER) { + table = new BrokerTable(); + } else { + throw new IOException("Unknown table type: " + type.name()); + } + + table.setTypeRead(true); + table.readFields(in); + if (type == TableType.VIEW) { + View view = (View) table; try { view.init(); } catch (InternalException e) { throw new IOException(e.getMessage()); } - table = view; - } else if (type == TableType.KUDU) { - table = new KuduTable(); - table.readFields(in); - } else if (type == TableType.BROKER) { - table = new BrokerTable(); - table.readFields(in); - } else { - throw new IOException("Unknown table type: " + type.name()); } return table; @@ -176,6 +187,11 @@ public class Table extends MetaObject implements Writable { @Override public void readFields(DataInput in) throws IOException { + if (!isTypeRead) { + type = TableType.valueOf(Text.readString(in)); + isTypeRead = true; + } + super.readFields(in); this.id = in.readLong(); diff --git a/fe/src/com/baidu/palo/catalog/Tablet.java b/fe/src/com/baidu/palo/catalog/Tablet.java index 5c840c96ec..de96b6a548 100644 --- a/fe/src/com/baidu/palo/catalog/Tablet.java +++ b/fe/src/com/baidu/palo/catalog/Tablet.java @@ -15,22 +15,22 @@ package com.baidu.palo.catalog; -import com.baidu.palo.catalog.Replica.ReplicaState; -import com.baidu.palo.common.io.Writable; - -import com.google.common.collect.Sets; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.LogManager; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; +import com.baidu.palo.catalog.Replica.ReplicaState; +import com.baidu.palo.common.io.Writable; + +import com.google.common.collect.Sets; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; import java.util.Set; /** @@ -66,8 +66,12 @@ public class Tablet extends MetaObject implements Writable { checkedVersionHash = -1L; isConsistent = true; - } - + } + + public void setIdForRestore(long tabletId) { + this.id = tabletId; + } + public long getId() { return this.id; } @@ -111,13 +115,19 @@ public class Tablet extends MetaObject implements Writable { return delete || !hasBackend; } - public void addReplica(Replica replica) { + public void addReplica(Replica replica, boolean isRestore) { if (deleteRedundantReplica(replica.getBackendId(), replica.getVersion())) { - replicas.add(replica); - Catalog.getCurrentInvertedIndex().addReplica(id, replica); + replicas.add(replica); + if (!isRestore) { + Catalog.getCurrentInvertedIndex().addReplica(id, replica); + } } - } - + } + + public void addReplica(Replica replica) { + addReplica(replica, false); + } + public List getReplicas() { return this.replicas; } diff --git a/fe/src/com/baidu/palo/catalog/TabletInvertedIndex.java b/fe/src/com/baidu/palo/catalog/TabletInvertedIndex.java index c1474b271d..dcf0f1f18b 100644 --- a/fe/src/com/baidu/palo/catalog/TabletInvertedIndex.java +++ b/fe/src/com/baidu/palo/catalog/TabletInvertedIndex.java @@ -49,31 +49,30 @@ public class TabletInvertedIndex { public static final int NOT_EXIST_VALUE = -1; - private ReentrantReadWriteLock lock; + private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); // tablet id -> tablet meta - private Map tabletMetaMap; + private Map tabletMetaMap = Maps.newHashMap(); /* * we use this to save memory. - * we do not need create TabletMeta intance for each tablet, + * we do not need create TabletMeta instance for each tablet, * cause tablets in one (Partition-MaterializedIndex) has same parent info * (dbId, tableId, partitionId, indexId, schemaHash) * we use 'tabletMetaTable' to do the update things * (eg. update schema hash in TabletMeta) - * partitionid -> (index id -> tablet meta) + * partition id -> (index id -> tablet meta) */ - private Table tabletMetaTable; + private Table tabletMetaTable = HashBasedTable.create(); // tablet id -> (backend id -> replica) - private Table replicaMetaTable; + private Table replicaMetaTable = HashBasedTable.create(); + // backing replica table, for visiting backend replicas faster. + // backend id -> (tablet id -> replica) + private Table backingReplicaMetaTable = HashBasedTable.create(); public TabletInvertedIndex() { - lock = new ReentrantReadWriteLock(); - tabletMetaMap = Maps.newHashMap(); - tabletMetaTable = HashBasedTable.create(); - replicaMetaTable = HashBasedTable.create(); } private final void readLock() { @@ -105,7 +104,7 @@ public class TabletInvertedIndex { try { LOG.info("begin to do tablet diff with backend[{}]. num: {}", backendId, backendTablets.size()); start = System.currentTimeMillis(); - Map replicaMetaWithBackend = replicaMetaTable.column(backendId); + Map replicaMetaWithBackend = backingReplicaMetaTable.row(backendId); if (replicaMetaWithBackend != null) { // traverse replicas in meta with this backend for (Map.Entry entry : replicaMetaWithBackend.entrySet()) { @@ -134,6 +133,12 @@ public class TabletInvertedIndex { tabletMigrationMap.put(storageMedium, tabletId); } } + + // update replicas's version count + // no need to write log, and no need to get db lock. + if (backendTabletInfo.isSetVersion_count()) { + replica.setVersionCount(backendTabletInfo.getVersion_count()); + } } else { // tablet with invalid schemahash foundTabletsWithInvalidSchema.put(tabletId, backendTabletInfo); @@ -262,7 +267,12 @@ public class TabletInvertedIndex { } writeLock(); try { - replicaMetaTable.rowMap().remove(tabletId); + Map replicas = replicaMetaTable.rowMap().remove(tabletId); + if (replicas != null) { + for (long backendId : replicas.keySet()) { + backingReplicaMetaTable.remove(backendId, tabletId); + } + } TabletMeta tabletMeta = tabletMetaMap.remove(tabletId); if (tabletMeta != null) { tabletMetaTable.remove(tabletMeta.getPartitionId(), tabletMeta.getIndexId()); @@ -280,6 +290,7 @@ public class TabletInvertedIndex { try { Preconditions.checkState(tabletMetaMap.containsKey(tabletId)); replicaMetaTable.put(tabletId, replica.getBackendId(), replica); + backingReplicaMetaTable.put(replica.getBackendId(), tabletId, replica); } finally { writeUnlock(); } @@ -295,6 +306,7 @@ public class TabletInvertedIndex { // Preconditions.checkState(replicaMetaTable.containsRow(tabletId)); if (replicaMetaTable.containsRow(tabletId)) { replicaMetaTable.remove(tabletId, backendId); + backingReplicaMetaTable.remove(backendId, tabletId); LOG.debug("delete tablet[{}] in backend[{}]", tabletId, backendId); } else { // this may happend when fe restart after tablet is empty(bug cause) @@ -329,7 +341,6 @@ public class TabletInvertedIndex { } finally { writeUnlock(); } - } public void updateToNewSchemaHash(long partitionId, long indexId) { @@ -364,7 +375,7 @@ public class TabletInvertedIndex { List tabletIds = Lists.newArrayList(); readLock(); try { - Map replicaMetaWithBackend = replicaMetaTable.column(backendId); + Map replicaMetaWithBackend = backingReplicaMetaTable.row(backendId); if (replicaMetaWithBackend != null) { tabletIds.addAll(replicaMetaWithBackend.keySet()); } @@ -377,7 +388,7 @@ public class TabletInvertedIndex { public int getTabletNumByBackendId(long backendId) { readLock(); try { - Map replicaMetaWithBackend = replicaMetaTable.column(backendId); + Map replicaMetaWithBackend = backingReplicaMetaTable.row(backendId); if (replicaMetaWithBackend != null) { return replicaMetaWithBackend.size(); } @@ -394,8 +405,10 @@ public class TabletInvertedIndex { tabletMetaMap.clear(); tabletMetaTable.clear(); replicaMetaTable.clear(); + backingReplicaMetaTable.clear(); } finally { writeUnlock(); } } } + diff --git a/fe/src/com/baidu/palo/catalog/UserPropertyMgr.java b/fe/src/com/baidu/palo/catalog/UserPropertyMgr.java deleted file mode 100644 index 1942e341a4..0000000000 --- a/fe/src/com/baidu/palo/catalog/UserPropertyMgr.java +++ /dev/null @@ -1,655 +0,0 @@ -// Copyright (c) 2017, Baidu.com, Inc. All Rights Reserved - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// 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. - -package com.baidu.palo.catalog; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import com.baidu.palo.analysis.AlterUserStmt; -import com.baidu.palo.analysis.AlterUserType; -import com.baidu.palo.analysis.SetUserPropertyStmt; -import com.baidu.palo.cluster.ClusterNamespace; -import com.baidu.palo.common.AnalysisException; -import com.baidu.palo.common.Config; -import com.baidu.palo.common.DdlException; -import com.baidu.palo.common.Pair; -import com.baidu.palo.common.publish.FixedTimePublisher; -import com.baidu.palo.common.publish.Listener; -import com.baidu.palo.common.publish.TopicUpdate; -import com.baidu.palo.load.DppConfig; -import com.baidu.palo.persist.EditLog; -import com.baidu.palo.thrift.TAgentServiceVersion; -import com.baidu.palo.thrift.TFetchResourceResult; -import com.baidu.palo.thrift.TTopicItem; -import com.baidu.palo.thrift.TTopicType; -import com.google.common.base.Strings; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; - -// TODO(dhc): we don't consider "drop database" -public class UserPropertyMgr { - private static final Logger LOG = LogManager.getLogger(UserPropertyMgr.class); - - private EditLog editLog; - protected Map userMap; - private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true); - public static final String ROOT_USER = "root"; - public static final String SYSTEM_RESOURCE_USER = "system"; - private AtomicLong resourceVersion; - - public UserPropertyMgr() { - userMap = Maps.newConcurrentMap(); - // there is no problem to init the root's password in constructor. - // because during the log replay, the real root password will cover the - // initial password. - unprotectAddUser("", ROOT_USER, new byte[0]); - unprotectAddUser("", SYSTEM_RESOURCE_USER, new byte[0]); - resourceVersion = new AtomicLong(0); - } - - public static String getRootName() { - return ROOT_USER; - } - - private void readLock() { - this.lock.readLock().lock(); - } - - private void readUnlock() { - this.lock.readLock().unlock(); - } - - private void writeLock() { - this.lock.writeLock().lock(); - } - - private void writeUnlock() { - this.lock.writeLock().unlock(); - } - - public void setEditLog(EditLog editLog) { - this.editLog = editLog; - } - - // Register callback to FixedTimePublisher - public void setUp() { - FixedTimePublisher.getInstance().register(new FixedTimePublisher.Callback() { - @Override - public TopicUpdate getTopicUpdate() { - TopicUpdate update = new TopicUpdate(TTopicType.RESOURCE); - TTopicItem tTopicItem = new TTopicItem("version"); - tTopicItem.setInt_value(resourceVersion.get()); - update.addUpdates(tTopicItem); - return update; - } - - @Override - public Listener getListener() { - return null; - } - }, Config.meta_resource_publish_interval_ms); - } - - public List> fetchAccessResourceResult(String user) { - List> result = Lists.newArrayList(); - readLock(); - try { - LOG.debug("get user name: {}", user); - UserProperty userProperty = userMap.get(user); - if (userProperty == null) { - // no such user - return result; - } - - boolean isAdmin = userProperty.isAdmin(); - boolean isSuperuser = userProperty.isSuperuser(); - - if (!isAdmin && !isSuperuser) { - // just a normal user, show its own property - result.add(Arrays.asList(userProperty.getUser(), new String(userProperty.getPassword()), - String.valueOf(userProperty.isAdmin()), String.valueOf(userProperty.isSuperuser()), - String.valueOf(userProperty.getMaxConn()), userProperty.fetchPrivilegeResult())); - return result; - } - - if (isAdmin) { - // If this is admin user(root), show all users' property. - for (Map.Entry entry : userMap.entrySet()) { - UserProperty oneProp = entry.getValue(); - - result.add(Arrays.asList(oneProp.getUser(), new String(oneProp.getPassword()), - String.valueOf(oneProp.isAdmin()), String.valueOf(oneProp.isSuperuser()), - String.valueOf(oneProp.getMaxConn()), oneProp.fetchPrivilegeResult())); - } - } else if (isSuperuser) { - // If this is a superuser, show property of all users' who are belong to the - // cluster. - String clusterName = ClusterNamespace.getClusterNameFromFullName(user); - LOG.debug("cluster name: {}", clusterName); - - for (Map.Entry entry : userMap.entrySet()) { - UserProperty oneProp = entry.getValue(); - if (!oneProp.getClusterName().equals(clusterName)) { - continue; - } - - result.add(Arrays.asList(oneProp.getUser(), new String(oneProp.getPassword()), - String.valueOf(oneProp.isAdmin()), String.valueOf(oneProp.isSuperuser()), - String.valueOf(oneProp.getMaxConn()), oneProp.fetchPrivilegeResult())); - } - } - - return result; - } finally { - readUnlock(); - } - } - - // we provide four function to support ddl stmt: addUser, setPasswd, grant, - // dropUser - // we provide two funciton to support search stmt: hasAccess(), - // getIsAdmin(), getPassword() - public void addUser(String cluster, String user, byte[] password, boolean isSuperuser) throws DdlException { - writeLock(); - try { - checkUserNotExists(user); - UserProperty userProperty = unprotectAddUser(cluster, user, password); - userProperty.setIsSuperuser(isSuperuser); - // all user has READ_ONLY privilege to InfoSchemaDb - this.getAccessResource(user).setAccess(ClusterNamespace.getFullName(cluster, InfoSchemaDb.DATABASE_NAME), - AccessPrivilege.READ_ONLY); - String msg = "addUser username=" + user + " password='" + password; - writeEditsOfAlterAccess(userProperty, msg); - } finally { - writeUnlock(); - } - } - - public void setPasswd(String user, byte[] password) throws DdlException { - writeLock(); - try { - checkUserExists(user); - this.getAccessResource(user).setPassword(password); - writeEditsOfAlterAccess(this.getAccessResource(user), "set password"); - } finally { - writeUnlock(); - } - } - - public void grant(String user, String db, AccessPrivilege privilege) throws DdlException { - if (Catalog.getInstance().getDb(db) == null) { - throw new DdlException("db[" + db + "] does not exist"); - } - - writeLock(); - try { - checkUserExists(user); - this.getAccessResource(user).setAccess(db, privilege); - - String msg = "grant user " + user + " db " + db + " privilege " + privilege; - writeEditsOfAlterAccess(this.getAccessResource(user), msg); - } finally { - writeUnlock(); - } - } - - public void revoke(String user, String db) throws DdlException { - // we do not check if db is exist in catalog - // db may be dropped or renamed - - writeLock(); - try { - checkUserExists(user); - this.getAccessResource(user).revokeAccess(db); - - String msg = "revoke user " + user + " db " + db; - writeEditsOfAlterAccess(this.getAccessResource(user), msg); - } finally { - writeUnlock(); - } - } - - public void dropUser(String user) throws DdlException { - writeLock(); - try { - checkUserExists(user); - unprotectDropUser(user); - editLog.logDropUser(user); - resourceVersion.incrementAndGet(); - } finally { - writeUnlock(); - } - } - - // functions bellow are used to get user information: hasAccess() IsAdmin(), - // getPassword() - // TODO(add functionget and set maxCount of user) - public boolean checkAccess(String user, String db, AccessPrivilege priv) { - readLock(); - try { - if (!isUserExists(user)) { - return false; - } - return this.getAccessResource(user).checkAccess(db, priv); - } finally { - readUnlock(); - } - } - - public boolean checkUserAccess(String opUser, String user) { - if (Strings.isNullOrEmpty(opUser) || Strings.isNullOrEmpty(user)) { - return false; - } - if (isAdmin(opUser)) { - return true; - } - if (isSuperuser(opUser) && !isSuperuser(user)) { - return true; - } - if (opUser.equals(user)) { - return true; - } - return false; - } - - public byte[] getPassword(String user) { - readLock(); - try { - if (!isUserExists(user)) { - return null; - } - return this.getAccessResource(user).getPassword(); - } finally { - readUnlock(); - } - } - - public boolean isAdmin(String user) { - readLock(); - try { - if (!isUserExists(user)) { - return false; - } - return this.getAccessResource(user).isAdmin(); - } finally { - readUnlock(); - } - } - - public boolean isSuperuser(String user) { - readLock(); - try { - if (!isUserExists(user)) { - return false; - } - return this.getAccessResource(user).isSuperuser(); - } finally { - readUnlock(); - } - } - - public long getMaxConn(String user) { - readLock(); - try { - if (!isUserExists(user)) { - return 0; - } - return this.getAccessResource(user).getMaxConn(); - } finally { - readUnlock(); - } - } - - private boolean isUserExists(String user) { - if (Strings.isNullOrEmpty(user)) { - return false; - } - if (this.getAccessResource(user) == null) { - return false; - } - return true; - } - - // this two function used to read snapshot or write snapshot - public void write(DataOutput out) throws IOException { - int numUsers = userMap.size(); - out.writeInt(numUsers); - - for (Map.Entry entry : userMap.entrySet()) { - entry.getValue().write(out); - } - // Write resource version - out.writeLong(resourceVersion.get()); - } - - public void readFields(DataInput in) throws IOException { - int numUsers = in.readInt(); - - for (int i = 0; i < numUsers; ++i) { - UserProperty userProperty = UserProperty.read(in); - userMap.put(userProperty.getUser(), userProperty); - } - - // Read resource - resourceVersion = new AtomicLong(in.readLong()); - } - - public int getUserMapSize() { - return userMap.size(); - } - - // Editlog will call this four function to playback journal - public void unprotectDropUser(String user) { - userMap.remove(user); - // TODO(zhaochun): Now we add resource version every time. - resourceVersion.incrementAndGet(); - } - - public void replayDropUser(String user) { - writeLock(); - try { - unprotectDropUser(user); - } finally { - writeUnlock(); - } - } - - public void unprotectAlterAccess(UserProperty userProperty) { - userMap.put(userProperty.getUser(), userProperty); - // TODO(zhaochun): Now we add resource version every time. - resourceVersion.incrementAndGet(); - } - - public void replayAlterAccess(UserProperty userProperty) { - writeLock(); - try { - unprotectAlterAccess(userProperty); - } finally { - writeUnlock(); - } - } - - // private function which used to support four public function - private void checkUserExists(String user) throws DdlException { - if (Strings.isNullOrEmpty(user)) { - throw new DdlException(new String("user is null")); - } - if (userMap.get(user) == null) { - throw new DdlException(new String("user dosn't exists")); - } - } - - public void checkUserIfExist(String user) throws DdlException { - readLock(); - try { - checkUserExists(user); - } finally { - readUnlock(); - } - } - - private void checkUserNotExists(String user) throws DdlException { - if (Strings.isNullOrEmpty(user)) { - throw new DdlException(new String("user is null")); - } - if (userMap.get(user) != null) { - throw new DdlException(new String("user has existed")); - } - } - - private UserProperty unprotectAddUser(String cluster, String user, byte[] password) { - UserProperty userProperty = new UserProperty(); - userProperty.setUser(user); - userProperty.setPassword(password); - userProperty.setClusterName(cluster); - // 默认“root”用户是管理员,其他需要其他接口 - if (user.equals(ROOT_USER)) { - userProperty.setIsAdmin(true); - } - try { - if (user.equals(SYSTEM_RESOURCE_USER)) { - setSystemUserDefaultResource(userProperty); - } - if (!user.equals(SYSTEM_RESOURCE_USER) && !user.equals(ROOT_USER)) { - setNormalUserDefaultResource(userProperty); - } - } catch (DdlException e) { - // this should not happen, because the value is set by us!! - } - userMap.put(user, userProperty); - return userProperty; - } - - private void setSystemUserDefaultResource(UserProperty user) throws DdlException { - UserResource userResource = user.getResource(); - userResource.updateResource("CPU_SHARE", 100); - userResource.updateResource("IO_SHARE", 100); - userResource.updateResource("SSD_READ_MBPS", 30); - userResource.updateResource("SSD_WRITE_MBPS", 30); - userResource.updateResource("HDD_READ_MBPS", 30); - userResource.updateResource("HDD_WRITE_MBPS", 30); - } - - private void setNormalUserDefaultResource(UserProperty user) throws DdlException { - UserResource userResource = user.getResource(); - userResource.updateResource("CPU_SHARE", 1000); - userResource.updateResource("IO_SHARE", 1000); - userResource.updateResource("SSD_READ_IOPS", 1000); - userResource.updateResource("HDD_READ_IOPS", 80); - userResource.updateResource("SSD_READ_MBPS", 30); - userResource.updateResource("HDD_READ_MBPS", 30); - } - - private void writeEditsOfAlterAccess(UserProperty userProperty, String msg) { - editLog.logAlterAccess(userProperty); - resourceVersion.incrementAndGet(); - } - - private UserProperty getAccessResource(String user) { - return userMap.get(user); - } - - public void updateUserProperty(SetUserPropertyStmt stmt) throws DdlException { - writeLock(); - try { - UserProperty property = userMap.get(stmt.getUser()); - if (property == null) { - throw new DdlException("Unknown user(" + stmt.getUser() + ")"); - } - - property.update(stmt.getPropertyList()); - - writeEditsOfAlterAccess(property, "update user property"); - } finally { - writeUnlock(); - } - } - - public TFetchResourceResult toResourceThrift() { - TFetchResourceResult tResult = new TFetchResourceResult(); - tResult.setProtocolVersion(TAgentServiceVersion.V1); - tResult.setResourceVersion(resourceVersion.get()); - readLock(); - try { - for (Map.Entry entry : userMap.entrySet()) { - tResult.putToResourceByUser(entry.getKey(), entry.getValue().getResource().toThrift()); - } - } finally { - readUnlock(); - } - return tResult; - } - - public Pair getClusterInfo(String user, String cluster) throws DdlException { - Pair clusterInfo = null; - - readLock(); - try { - if (!userMap.containsKey(user)) { - throw new DdlException("User[" + user + "] does not exist"); - } - - UserProperty property = userMap.get(user); - clusterInfo = property.getClusterInfo(cluster); - } finally { - readUnlock(); - } - - return clusterInfo; - } - - public List> fetchUserProperty(String user) throws AnalysisException { - readLock(); - try { - if (!userMap.containsKey(user)) { - throw new AnalysisException("User[" + user + "] does not exist"); - } - - UserProperty property = userMap.get(user); - return property.fetchProperty(); - } finally { - readUnlock(); - } - } - - public void alterUser(AlterUserStmt stmt) throws DdlException { - List ips = stmt.getIps(); - List starIps = stmt.getStarIps(); - List hosts = stmt.getHosts(); - String user = stmt.getUser(); - AlterUserType type = stmt.getAlterUserType(); - - // check host if can dns - if (type == AlterUserType.ADD_USER_WHITELIST) { - for (String host : hosts) { - boolean isAvaliable = DomainResolverServer.getInstance().isAvaliableDomain(host); - if (!isAvaliable) { - String msg = "May be error hostname. host=" + host; - LOG.warn("alter user={} stmt={} occur dns Exception msg={}", stmt.getUser(), stmt, msg); - throw new DdlException(msg); - } - } - } - - writeLock(); - try { - UserProperty property = userMap.get(user); - if (property == null) { - throw new DdlException("use dosn't exists user=" + user); - } - - WhiteList whiteList = property.getWhiteList(); - String msg = type.toString(); - - switch (type) { - case ADD_USER_WHITELIST: { - whiteList.addWhiteList(ips, starIps, hosts); - break; - } - case DELETE_USER_WHITELIST: { - whiteList.deleteWhiteList(ips, starIps, hosts); - break; - } - default: { - LOG.warn("alterUser occur unkown type = {}", type); - throw new RuntimeException("unkown type"); - } - } - // write editlog - writeEditsOfAlterAccess(this.getAccessResource(user), msg); - } finally { - writeUnlock(); - } - } - - public boolean checkWhiltListAccess(String user, String remoteIp) { - if (user.equals("root") && remoteIp.equals("127.0.0.1")) { - return true; - } - readLock(); - try { - UserProperty property = userMap.get(user); - if (property == null) { - return false; - } - return property.getWhiteList().hasAccess(remoteIp); - } finally { - readUnlock(); - } - } - - public List> showWhiteList(String user) { - List> result = Lists.newArrayList(); - readLock(); - try { - // ordinary user - if (!isSuperuser(user)) { - WhiteList whitelist = userMap.get(user).getWhiteList(); - List row = Lists.newArrayList(); - row.add(user); - row.add(whitelist.toString()); - result.add(row); - return result; - } else { - for (Map.Entry entry : userMap.entrySet()) { - String candidateUser = entry.getKey(); - boolean addRow = false; - // admin can see every one's whitelist - // superuse can see own and odinary user's whitelist - // ordinary can see own whitelist - if (isAdmin(user)) { - addRow = true; - } else if (user.equals(candidateUser)) { - addRow = true; - } else if (!isSuperuser(candidateUser)) { - addRow = true; - } - if (addRow) { - WhiteList whitelist = userMap.get(candidateUser).getWhiteList(); - List row = Lists.newArrayList(); - row.add(candidateUser); - row.add(whitelist.toString()); - result.add(row); - } - } - } - - } finally { - readUnlock(); - } - return result; - } - - public int getWhiteListSize(String userName) throws DdlException { - readLock(); - try { - checkUserExists(userName); - return userMap.get(userName).getWhiteList().getSize(); - } finally { - readUnlock(); - } - } -} - diff --git a/fe/src/com/baidu/palo/catalog/WhiteList.java b/fe/src/com/baidu/palo/catalog/WhiteList.java deleted file mode 100644 index 32c7ef01a4..0000000000 --- a/fe/src/com/baidu/palo/catalog/WhiteList.java +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright (c) 2017, Baidu.com, Inc. All Rights Reserved - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// 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. - -package com.baidu.palo.catalog; - -import com.baidu.palo.common.DdlException; -import com.baidu.palo.common.io.Text; -import com.baidu.palo.common.io.Writable; - -import com.google.common.collect.Sets; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; -import java.util.List; -import java.util.Map; -import java.util.Set; - -public class WhiteList implements Writable { - private static final Logger LOG = LogManager.getLogger(WhiteList.class); - - // Ip (123.123.1.1) - protected Set ipWhiteLists = Sets.newHashSet(); - // startIp (123.123.*.*) - protected Set starIpWhiteLists = Sets.newHashSet(); - // hostName(www.baidu.com), which need to dns analysis - protected Set hostWhiteLists = Sets.newHashSet(); - protected Map> ipOfHostWhiteLists; - private String user; - - public WhiteList() { - } - - // for limit the max whiteListsize - public int getSize() { - return ipWhiteLists.size() + starIpWhiteLists.size() + hostWhiteLists.size(); - } - - public boolean hasAccess(String ip) { - // whileList is null, all people can access - if (getSize() == 0) { - return true; - } - - // 1. check if specified ip in white list - if (ipWhiteLists.contains(ip)) { - return true; - } - - // 2. check if specified ip in start white list - for (String starIp : starIpWhiteLists) { - String[] splittedStarIp = starIp.split("\\."); - String[] splittedSpecifiedIp = ip.split("\\."); - int starIpLen = splittedStarIp.length; - int specifiedIpLen = splittedSpecifiedIp.length; - if (!(specifiedIpLen == 4 && starIpLen == 4)) { - String msg = String.format("Invalid IP format: %s", ip); - LOG.warn(msg); - throw new RuntimeException(msg); - } - - boolean hit = true; - for (int i = 0; i < 4; i++) { - if (splittedSpecifiedIp[i].equals(splittedStarIp[i])) { - continue; - } else if (splittedStarIp[i].equals("*")) { - continue; - } else { - hit = false; - break; - } - } - - if (hit) { - return true; - } - } - - ipOfHostWhiteLists = DomainResolverServer.getInstance().getUserDomainToIps(user); - // 3. check ipWhiteList - if (ipOfHostWhiteLists != null) { - for (String entryIp : ipOfHostWhiteLists.keySet()) { - Set ipSet = ipOfHostWhiteLists.get(entryIp); - if (ipSet == null || ipSet.size() == 0) { - LOG.debug("dns error ip={}", entryIp); - continue; - } - if (ipSet.contains(ip)) { - return true; - } - } - } - LOG.debug("can't match whitelist ip={}", ip); - return false; - } - - public void addWhiteList(List ips, List starIps, List hosts) throws DdlException { - ipWhiteLists.addAll(ips); - starIpWhiteLists.addAll(starIps); - hostWhiteLists.addAll(hosts); - DomainResolverServer.getInstance().register(user, hosts); - } - - public void deleteWhiteList(List ips, List starIps, List hosts) { - if (ips != null && ips.size() > 0) { - ipWhiteLists.removeAll(ips); - } - if (starIps != null && starIps.size() > 0) { - starIpWhiteLists.removeAll(starIps); - } - if (hosts != null && hosts.size() > 0) { - hostWhiteLists.removeAll(hosts); - } - if (hosts != null && hosts.size() > 0) { - DomainResolverServer.getInstance().unregister(user, hosts); - } - - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - for (String ip : ipWhiteLists) { - builder.append(ip); - builder.append(","); - } - for (String ip : starIpWhiteLists) { - builder.append(ip); - builder.append(","); - } - for (String ip : hostWhiteLists) { - builder.append(ip); - builder.append(","); - } - String result = builder.toString(); - String newResult = result; - // del the last , - if (result.length() > 0) { - newResult = result.substring(0, result.length() - 1); - } - return newResult; - } - - @Override - public void write(DataOutput out) throws IOException { - out.writeInt(ipWhiteLists.size()); - for (String ip : ipWhiteLists) { - Text.writeString(out, ip); - } - out.writeInt(starIpWhiteLists.size()); - for (String ip : starIpWhiteLists) { - Text.writeString(out, ip); - } - out.writeInt(hostWhiteLists.size()); - for (String ip : hostWhiteLists) { - Text.writeString(out, ip); - } - } - - @Override - public void readFields(DataInput in) throws IOException { - int ipWhiteListsLen = in.readInt(); - for (int i = 0; i < ipWhiteListsLen; i++) { - String ip = Text.readString(in); - ipWhiteLists.add(ip); - } - int starIpWhiteListsLen = in.readInt(); - for (int i = 0; i < starIpWhiteListsLen; i++) { - String ip = Text.readString(in); - starIpWhiteLists.add(ip); - } - int hostWhiteListsLen = in.readInt(); - for (int i = 0; i < hostWhiteListsLen; i++) { - String ip = Text.readString(in); - hostWhiteLists.add(ip); - } - - if (hostWhiteLists != null && hostWhiteLists.size() > 0) { - DomainResolverServer.getInstance().register(user, hostWhiteLists); - } - } - - public String getUser() { - return user; - } - - public void setUser(String user) { - this.user = user; - } -} diff --git a/fe/src/com/baidu/palo/cluster/ClusterNamespace.java b/fe/src/com/baidu/palo/cluster/ClusterNamespace.java index 14978ccb88..5f561fb957 100644 --- a/fe/src/com/baidu/palo/cluster/ClusterNamespace.java +++ b/fe/src/com/baidu/palo/cluster/ClusterNamespace.java @@ -15,9 +15,9 @@ package com.baidu.palo.cluster; -import com.google.common.base.Strings; +import com.baidu.palo.mysql.privilege.PaloAuth; -import com.baidu.palo.catalog.UserPropertyMgr; +import com.google.common.base.Strings; /** * used to isolate the use for the database name and user name in the catalog, @@ -62,7 +62,8 @@ public class ClusterNamespace { if (Strings.isNullOrEmpty(cluster) || Strings.isNullOrEmpty(name)) { return null; } - if (name.contains(CLUSTER_DELIMITER) || name.equals(UserPropertyMgr.getRootName())) { + if (name.contains(CLUSTER_DELIMITER) || name.equalsIgnoreCase(PaloAuth.ROOT_USER) + || name.equalsIgnoreCase(PaloAuth.ADMIN_USER)) { return name; } final StringBuilder sb = new StringBuilder(cluster); diff --git a/fe/src/com/baidu/palo/common/CaseSensibility.java b/fe/src/com/baidu/palo/common/CaseSensibility.java new file mode 100644 index 0000000000..8cae59aaf1 --- /dev/null +++ b/fe/src/com/baidu/palo/common/CaseSensibility.java @@ -0,0 +1,41 @@ +// Copyright (c) 2018, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +package com.baidu.palo.common; + +public enum CaseSensibility { + CLUSTER(true), + DATABASE(true), + TABLE(true), + ROLUP(true), + PARTITION(true), + COLUMN(true), + USER(true), + ROLE(false), + HOST(false), + LABEL(false), + VARIABLES(true); + + private boolean caseSensitive; + + private CaseSensibility(boolean caseSensitive) { + this.caseSensitive = caseSensitive; + } + + public boolean getCaseSensibility() { + return caseSensitive; + } + +} diff --git a/fe/src/com/baidu/palo/common/Config.java b/fe/src/com/baidu/palo/common/Config.java index e7a629dfb5..cab715d092 100644 --- a/fe/src/com/baidu/palo/common/Config.java +++ b/fe/src/com/baidu/palo/common/Config.java @@ -82,6 +82,13 @@ public class Config extends ConfigBase { * 2. Safe (RAID) */ @ConfField public static String meta_dir = System.getenv("PALO_HOME") + "/palo-meta"; + + /* + * temp dir is used to save intermediate results of some process, such as backup and restore process. + * file in this dir will be cleaned after these process is finished. + */ + @ConfField public static String tmp_dir = System.getenv("PALO_HOME") + "/temp_dir"; + /* * Edit log type. * BDB: write log to bdbje @@ -97,7 +104,7 @@ public class Config extends ConfigBase { /* * Master FE will save image every *edit_log_roll_num* meta journals. */ - @ConfField public static int edit_log_roll_num = 100000; + @ConfField public static int edit_log_roll_num = 50000; /* * Non-master FE will stop offering service * if meta data delay gap exceeds *meta_delay_toleration_second* @@ -503,6 +510,9 @@ public class Config extends ConfigBase { // Set runtime locale when exec some cmds @ConfField public static String locale = "zh_CN.UTF-8"; + + // default timeout of backup job + @ConfField public static int backup_job_default_timeout_ms = 86400 * 1000; // 1 day /* * storage_high_watermark_usage_percent limit the max capacity usage percent of a Backend storage path. @@ -512,4 +522,16 @@ public class Config extends ConfigBase { */ @ConfField public static double storage_high_watermark_usage_percent = 0.85; @ConfField public static double storage_min_left_capacity_bytes = 1000 * 1024 * 1024; // 1G + + // May be necessary to modify the following BRPC configurations in high concurrency scenarios. + // The number of concurrent requests BRPC can processed + @ConfField public static int brpc_number_of_concurrent_requests_processed = 4096; + + // BRPC idle wait time (ms) + @ConfField public static int brpc_idle_wait_max_time = 10000; + + /* + * if set to false, auth check will be disable, in case some goes wrong with the new privilege system. + */ + @ConfField public static boolean enable_auth_check = true; } diff --git a/fe/src/com/baidu/palo/common/ErrorCode.java b/fe/src/com/baidu/palo/common/ErrorCode.java index 50417b5c17..673a6289c6 100644 --- a/fe/src/com/baidu/palo/common/ErrorCode.java +++ b/fe/src/com/baidu/palo/common/ErrorCode.java @@ -59,6 +59,10 @@ public enum ErrorCode { ERR_PASSWORD_NOT_ALLOWED(1132, new byte[] {'4', '2', '0', '0', '0'}, "You must have privileges to " + "update tables in the mysql database to be able to change passwords for others"), + ERR_NONEXISTING_GRANT(1141, new byte[] { '4', '2', '0', '0', '0' }, + "There is no such grant defined for user '%s' on host '%s'"), + ERR_TABLEACCESS_DENIED_ERROR(1142, new byte[] { '4', '2', '0', '0', '0' }, + "%s command denied to user '%s'@'%s' for table '%s'"), ERR_WRONG_COLUMN_NAME(1166, new byte[] {'4', '2', '0', '0', '0'}, "Incorrect column name '%s'"), ERR_UNKNOWN_SYSTEM_VARIABLE(1193, new byte[] {'H', 'Y', '0', '0', '0'}, "Unknown system variable '%s'"), ERR_TOO_MANY_USER_CONNECTIONS(1203, new byte[] {'4', '2', '0', '0', '0'}, @@ -154,8 +158,6 @@ public enum ErrorCode { "All datbases in cluster must be dropped before dropping cluster"), ERR_CLUSTER_DELETE_BE_ID_ERROR(5037, new byte[] { 'H', 'Y', '0', '0', '0' }, "There is no be's id in the System"), ERR_CLUSTER_NO_CLUSTER_NAME(5038, new byte[] { 'H', 'Y', '0', '0', '0' }, "There is no cluster name"), - ERR_CLUSTER_SHOW_ACCESS_DENIED(5039, new byte[] {'4', '2', '0', '0', '0'}, - "Access denied for user '%s' to show cluster"), ERR_CLUSTER_UNKNOWN_ERROR(5040, new byte[] {'4', '2', '0', '0', '0'}, "Unknown cluster '%s'"), ERR_CLUSTER_NAME_NULL(5041, new byte[] {'4', '2', '0', '0', '0'}, "No cluster name"), ERR_CLUSTER_NO_PERMISSIONS(5042, new byte[] {'4', '2', '0', '0', '0'}, "No permissions"), @@ -193,7 +195,11 @@ public enum ErrorCode { ERR_KUDU_NOT_SUPPORT_VALUE_TYPE(5061, new byte[] { '4', '2', '0', '0', '0' }, "Kudu does not support value type '%s'"), ERR_WRONG_CLUSTER_NAME(5062, new byte[] { '4', '2', '0', '0', '0' }, - "Incorrect cluster name '%s'(name 'default_cluster' is a reserved name)"); + "Incorrect cluster name '%s'(name 'default_cluster' is a reserved name)"), + ERR_WRONG_NAME_FORMAT(5063, new byte[] { '4', '2', '0', '0', '0' }, + "Incorrect %s name '%s'"), + ERR_COMMON_ERROR(5064, new byte[] { '4', '2', '0', '0', '0' }, + "%s"); ErrorCode(int code, byte[] sqlState, String errorMsg) { diff --git a/fe/src/com/baidu/palo/common/FeConstants.java b/fe/src/com/baidu/palo/common/FeConstants.java index 02bd290252..c11423e34c 100644 --- a/fe/src/com/baidu/palo/common/FeConstants.java +++ b/fe/src/com/baidu/palo/common/FeConstants.java @@ -38,5 +38,5 @@ public class FeConstants { // general model // Current meta data version. Use this version to write journals and image - public static int meta_version = FeMetaVersion.VERSION_41; + public static int meta_version = FeMetaVersion.VERSION_43; } diff --git a/fe/src/com/baidu/palo/common/FeMetaVersion.java b/fe/src/com/baidu/palo/common/FeMetaVersion.java index 85ccfa634a..43d097909e 100644 --- a/fe/src/com/baidu/palo/common/FeMetaVersion.java +++ b/fe/src/com/baidu/palo/common/FeMetaVersion.java @@ -91,4 +91,10 @@ public final class FeMetaVersion { // change the way to name Frontend public static final int VERSION_41 = 41; + + // new backup and restore + public static final int VERSION_42 = 42; + + // new privilege management + public static final int VERSION_43 = 43; } diff --git a/fe/src/com/baidu/palo/common/FeNameFormat.java b/fe/src/com/baidu/palo/common/FeNameFormat.java index 3f97ef273b..d446e90bd7 100644 --- a/fe/src/com/baidu/palo/common/FeNameFormat.java +++ b/fe/src/com/baidu/palo/common/FeNameFormat.java @@ -20,23 +20,19 @@ package com.baidu.palo.common; +import com.baidu.palo.mysql.privilege.PaloRole; import com.baidu.palo.system.SystemInfoService; import com.google.common.base.Strings; public class FeNameFormat { - private static final String CLUSTER_REGEX = "^[a-zA-Z][a-zA-Z0-9_]{0,63}$"; - private static final String DB_REGEX = "^[a-zA-Z][a-zA-Z0-9_]{0,63}$"; - private static final String TABLE_REGEX = "^[a-zA-Z][a-zA-Z0-9_]{0,63}$"; - private static final String PARTITION_REGEX = "^[a-zA-Z][a-zA-Z0-9_]{0,63}$"; - private static final String COLUMN_REGEX = "^[a-zA-Z][a-zA-Z0-9_]{0,63}$"; - private static final String USER_REGEX = "^[a-zA-Z][a-zA-Z0-9_]{0,63}$"; private static final String LABEL_REGEX = "^[-_A-Za-z0-9]{1,128}$"; + private static final String COMMON_NAME_REGEX = "^[a-zA-Z][a-zA-Z0-9_]{0,63}$"; public static final String FORBIDDEN_PARTITION_NAME = "placeholder_"; public static void checkClusterName(String clusterName) throws AnalysisException { - if (Strings.isNullOrEmpty(clusterName) || !clusterName.matches(CLUSTER_REGEX)) { + if (Strings.isNullOrEmpty(clusterName) || !clusterName.matches(COMMON_NAME_REGEX)) { ErrorReport.reportAnalysisException(ErrorCode.ERR_WRONG_CLUSTER_NAME, clusterName); } if (clusterName.equalsIgnoreCase(SystemInfoService.DEFAULT_CLUSTER)) { @@ -45,19 +41,19 @@ public class FeNameFormat { } public static void checkDbName(String dbName) throws AnalysisException { - if (Strings.isNullOrEmpty(dbName) || !dbName.matches(DB_REGEX)) { + if (Strings.isNullOrEmpty(dbName) || !dbName.matches(COMMON_NAME_REGEX)) { ErrorReport.reportAnalysisException(ErrorCode.ERR_WRONG_DB_NAME, dbName); } } public static void checkTableName(String tableName) throws AnalysisException { - if (Strings.isNullOrEmpty(tableName) || !tableName.matches(TABLE_REGEX)) { + if (Strings.isNullOrEmpty(tableName) || !tableName.matches(COMMON_NAME_REGEX)) { ErrorReport.reportAnalysisException(ErrorCode.ERR_WRONG_TABLE_NAME, tableName); } } public static void checkPartitionName(String partitionName) throws AnalysisException { - if (Strings.isNullOrEmpty(partitionName) || !partitionName.matches(PARTITION_REGEX)) { + if (Strings.isNullOrEmpty(partitionName) || !partitionName.matches(COMMON_NAME_REGEX)) { ErrorReport.reportAnalysisException(ErrorCode.ERR_WRONG_PARTITION_NAME, partitionName); } @@ -67,7 +63,7 @@ public class FeNameFormat { } public static void checkColumnName(String columnName) throws AnalysisException { - if (Strings.isNullOrEmpty(columnName) || !columnName.matches(COLUMN_REGEX)) { + if (Strings.isNullOrEmpty(columnName) || !columnName.matches(COMMON_NAME_REGEX)) { ErrorReport.reportAnalysisException(ErrorCode.ERR_WRONG_COLUMN_NAME, columnName); } } @@ -79,8 +75,32 @@ public class FeNameFormat { } public static void checkUserName(String userName) throws AnalysisException { - if (Strings.isNullOrEmpty(userName) || !userName.matches(USER_REGEX)) { + if (Strings.isNullOrEmpty(userName) || !userName.matches(COMMON_NAME_REGEX)) { ErrorReport.reportAnalysisException(ErrorCode.ERR_CANNOT_USER, "CREATE USER", userName); } } + + public static void checkRoleName(String role, boolean canBeSuperuser) throws AnalysisException { + if (Strings.isNullOrEmpty(role) || !role.matches(COMMON_NAME_REGEX)) { + throw new AnalysisException("invalid role format: " + role); + } + + boolean res = false; + if (CaseSensibility.ROLE.getCaseSensibility()) { + res = role.equals(PaloRole.OPERATOR_ROLE) || (!canBeSuperuser && role.equals(PaloRole.ADMIN_ROLE)); + } else { + res = role.equalsIgnoreCase(PaloRole.OPERATOR_ROLE) + || (!canBeSuperuser && role.equalsIgnoreCase(PaloRole.ADMIN_ROLE)); + } + + if (res) { + throw new AnalysisException("Can not create role with name: " + role); + } + } + + public static void checkCommonName(String type, String name) throws AnalysisException { + if (Strings.isNullOrEmpty(name) || !name.matches(COMMON_NAME_REGEX)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_WRONG_NAME_FORMAT, type, name); + } + } } diff --git a/fe/src/com/baidu/palo/common/PatternMatcher.java b/fe/src/com/baidu/palo/common/PatternMatcher.java index 4e69625a3b..5745cfd984 100644 --- a/fe/src/com/baidu/palo/common/PatternMatcher.java +++ b/fe/src/com/baidu/palo/common/PatternMatcher.java @@ -21,13 +21,19 @@ package com.baidu.palo.common; import com.google.common.base.Strings; +import com.google.common.collect.Sets; +import java.util.Set; import java.util.regex.Pattern; // Wrap for Java pattern and matcher public class PatternMatcher { private Pattern pattern; + private static final Set FORBIDDEN_CHARS = Sets.newHashSet('<', '(', '[', '{', '^', '=', + '$', '!', '|', ']', '}', ')', + '?', '*', '+', '>', '@'); + public boolean match(String candidate) { if (pattern == null || candidate == null) { // No pattern, how can I explain this? Return false now. @@ -40,24 +46,64 @@ public class PatternMatcher { return false; } - private static String convertMysqlPattern(String mysqlPattern) { + /* + * Mysql has only 2 patterns. + * '%' to match any character sequence + * '_' to master any single character. + * So we convert '%' to '.*', and '_' to '.' + * + * eg: + * abc% -> abc.* + * ab_c -> ab.c + * + * We also need to handle escape character '\'. + * User use '\' to escape reserved words like '%', '_', or '\' it self + * + * eg: + * ab\%c = ab%c + * ab\_c = ab_c + * ab\\c = ab\c + * + * We also have to ignore meaningless '\' like:'ab\c', convert it to 'abc'. + * The following characters are not permitted: + * <([{^=$!|]})?*+> + */ + private static String convertMysqlPattern(String mysqlPattern) throws AnalysisException { String newMysqlPattern = mysqlPattern; StringBuilder sb = new StringBuilder(); for (int i = 0; i < newMysqlPattern.length(); ++i) { char ch = newMysqlPattern.charAt(i); + checkPermittedCharactor(ch); switch (ch) { case '%': sb.append(".*"); break; + case '.': + sb.append("\\."); + break; case '_': sb.append("."); break; case '\\': { if (i == newMysqlPattern.length() - 1) { - // Last character of this pattern. - sb.append(newMysqlPattern.charAt(i)); - } else { - sb.append(newMysqlPattern.charAt(++i)); + // last character of this pattern. leave this '\' as it is + sb.append('\\'); + break; + } + // we need to look ahead the next character + // to decide ignore this '\' or treat it as escape character. + char nextChar = newMysqlPattern.charAt(i + 1); + switch (nextChar) { + case '%': + case '_': + case '\\': + // this is a escape character, eat this '\' and get next character. + sb.append(nextChar); + ++i; + break; + default: + // ignore this '\' and continue; + break; } break; } @@ -66,13 +112,26 @@ public class PatternMatcher { break; } } - // Replace all the '\' to '\'.'\' in Java pattern + + // Replace all the '\' to '\\' in Java pattern newMysqlPattern = sb.toString(); sb = new StringBuilder(); for (int i = 0; i < newMysqlPattern.length(); ++i) { char ch = newMysqlPattern.charAt(i); switch (ch) { case '\\': + if (i == newMysqlPattern.length() - 1) { + // last character of this pattern. leave this '\' as it is + sb.append('\\').append('\\'); + break; + } + // look ahead + if (newMysqlPattern.charAt(i + 1) == '.') { + // leave '\.' as it is. + sb.append('\\').append('.'); + i++; + break; + } sb.append('\\').append('\\'); break; default: @@ -81,10 +140,18 @@ public class PatternMatcher { } } + // System.out.println("result: " + sb.toString()); return sb.toString(); } - public static PatternMatcher createMysqlPattern(String mysqlPattern) throws AnalysisException { + private static void checkPermittedCharactor(char c) throws AnalysisException { + if (FORBIDDEN_CHARS.contains(c)) { + throw new AnalysisException("Forbidden charactor: '" + c + "'"); + } + } + + public static PatternMatcher createMysqlPattern(String mysqlPattern, boolean caseSensitive) + throws AnalysisException { PatternMatcher matcher = new PatternMatcher(); // Match nothing @@ -92,9 +159,13 @@ public class PatternMatcher { String javaPattern = convertMysqlPattern(newMysqlPattern); try { - matcher.pattern = Pattern.compile(javaPattern, Pattern.CASE_INSENSITIVE); + if (caseSensitive) { + matcher.pattern = Pattern.compile(javaPattern); + } else { + matcher.pattern = Pattern.compile(javaPattern, Pattern.CASE_INSENSITIVE); + } } catch (Exception e) { - throw new AnalysisException("Bad pattern in SQL."); + throw new AnalysisException("Bad pattern in SQL: " + e.getMessage()); } return matcher; } diff --git a/fe/src/com/baidu/palo/common/io/DeepCopy.java b/fe/src/com/baidu/palo/common/io/DeepCopy.java new file mode 100644 index 0000000000..6e31b212f4 --- /dev/null +++ b/fe/src/com/baidu/palo/common/io/DeepCopy.java @@ -0,0 +1,50 @@ +// Modifications copyright (C) 2018, Baidu.com, Inc. +// Copyright 2018 The Apache Software Foundation + +// 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. + +package com.baidu.palo.common.io; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.DataInputStream; +import java.io.DataOutputStream; + +public class DeepCopy { + private static final Logger LOG = LogManager.getLogger(DeepCopy.class); + + public static boolean copy(Writable orig, Writable copied) { + FastByteArrayOutputStream byteArrayOutputStream = new FastByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(byteArrayOutputStream); + try { + orig.write(out); + out.flush(); + out.close(); + + DataInputStream in = new DataInputStream(byteArrayOutputStream.getInputStream()); + copied.readFields(in); + in.close(); + } catch (Exception e) { + e.printStackTrace(); + LOG.warn("failed to copy object.", e); + return false; + } + return true; + } +} diff --git a/fe/src/com/baidu/palo/common/io/FastByteArrayInputStream.java b/fe/src/com/baidu/palo/common/io/FastByteArrayInputStream.java new file mode 100644 index 0000000000..a3b941e080 --- /dev/null +++ b/fe/src/com/baidu/palo/common/io/FastByteArrayInputStream.java @@ -0,0 +1,82 @@ +// Modifications copyright (C) 2018, Baidu.com, Inc. +// Copyright 2018 The Apache Software Foundation + +// 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. + +package com.baidu.palo.common.io; + +import java.io.InputStream; + +/** + * ByteArrayInputStream implementation that does not synchronize methods. + * http://javatechniques.com/blog/faster-deep-copies-of-java-objects/ + */ +public class FastByteArrayInputStream extends InputStream { + /** + * Our byte buffer + */ + protected byte[] buf = null; + + /** + * Number of bytes that we can read from the buffer + */ + protected int count = 0; + + /** + * Number of bytes that have been read from the buffer + */ + protected int pos = 0; + + public FastByteArrayInputStream(byte[] buf, int count) { + this.buf = buf; + this.count = count; + } + + public final int available() { + return count - pos; + } + + public final int read() { + return (pos < count) ? (buf[pos++] & 0xff) : -1; + } + + public final int read(byte[] b, int off, int len) { + if (pos >= count) + return -1; + + if ((pos + len) > count) { + len = (count - pos); + } + + System.arraycopy(buf, pos, b, off, len); + pos += len; + return len; + } + + public final long skip(long n) { + if ((pos + n) > count) { + n = count - pos; + } + if (n < 0) { + return 0; + } + pos += n; + return n; + } + +} diff --git a/fe/src/com/baidu/palo/common/io/FastByteArrayOutputStream.java b/fe/src/com/baidu/palo/common/io/FastByteArrayOutputStream.java new file mode 100644 index 0000000000..9fedb084d8 --- /dev/null +++ b/fe/src/com/baidu/palo/common/io/FastByteArrayOutputStream.java @@ -0,0 +1,106 @@ +// Modifications copyright (C) 2018, Baidu.com, Inc. +// Copyright 2018 The Apache Software Foundation + +// 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. + +package com.baidu.palo.common.io; + +import java.io.InputStream; +import java.io.OutputStream; + +/** + * ByteArrayOutputStream implementation that doesn't synchronize methods + * and doesn't copy the data on toByteArray(). + * http://javatechniques.com/blog/faster-deep-copies-of-java-objects/ + */ +public class FastByteArrayOutputStream extends OutputStream { + /** + * Buffer and size + */ + protected byte[] buf = null; + protected int size = 0; + + /** + * Constructs a stream with buffer capacity size 5K + */ + public FastByteArrayOutputStream() { + this(5 * 1024); + } + + /** + * Constructs a stream with the given initial size + */ + public FastByteArrayOutputStream(int initSize) { + this.size = 0; + this.buf = new byte[initSize]; + } + + /** + * Ensures that we have a large enough buffer for the given size. + */ + private void verifyBufferSize(int sz) { + if (sz > buf.length) { + byte[] old = buf; + buf = new byte[Math.max(sz, 2 * buf.length)]; + System.arraycopy(old, 0, buf, 0, old.length); + old = null; + } + } + + public int getSize() { + return size; + } + + /** + * Returns the byte array containing the written data. Note that this + * array will almost always be larger than the amount of data actually + * written. + */ + public byte[] getByteArray() { + return buf; + } + + public final void write(byte[] b) { + verifyBufferSize(size + b.length); + System.arraycopy(b, 0, buf, size, b.length); + size += b.length; + } + + public final void write(byte[] b, int off, int len) { + verifyBufferSize(size + len); + System.arraycopy(b, off, buf, size, len); + size += len; + } + + public final void write(int b) { + verifyBufferSize(size + 1); + buf[size++] = (byte) b; + } + + public void reset() { + size = 0; + } + + /** + * Returns a ByteArrayInputStream for reading back the written data + */ + public InputStream getInputStream() { + return new FastByteArrayInputStream(buf, size); + } + +} diff --git a/fe/src/com/baidu/palo/common/proc/AccessResourceProcDir.java b/fe/src/com/baidu/palo/common/proc/AuthProcDir.java similarity index 61% rename from fe/src/com/baidu/palo/common/proc/AccessResourceProcDir.java rename to fe/src/com/baidu/palo/common/proc/AuthProcDir.java index b03595467e..09dc8907c5 100644 --- a/fe/src/com/baidu/palo/common/proc/AccessResourceProcDir.java +++ b/fe/src/com/baidu/palo/common/proc/AuthProcDir.java @@ -20,24 +20,27 @@ package com.baidu.palo.common.proc; -import com.baidu.palo.catalog.UserPropertyMgr; +import com.baidu.palo.analysis.UserIdentity; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.mysql.privilege.PaloAuth; + import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; /* * It describes the information about the authorization(privilege) and the authentication(user) - * SHOW PROC /access_resource/ + * SHOW PROC /auth/ */ -public class AccessResourceProcDir implements ProcDirInterface { +public class AuthProcDir implements ProcDirInterface { public static final ImmutableList TITLE_NAMES = new ImmutableList.Builder() - .add("UserName").add("Password").add("IsAdmin").add("IsSuperuser") - .add("MaxConn").add("Privilege").build(); + .add("UserIdentity").add("Password").add("GlobalPrivs").add("DatabasePrivs") + .add("TablePrivs").build(); - private UserPropertyMgr userPropertyMgr; + private PaloAuth auth; - public AccessResourceProcDir(UserPropertyMgr userPropertyMgr) { - this.userPropertyMgr = userPropertyMgr; + public AuthProcDir(PaloAuth auth) { + this.auth = auth; } @Override @@ -46,19 +49,24 @@ public class AccessResourceProcDir implements ProcDirInterface { } @Override - public ProcNodeInterface lookup(String user) throws AnalysisException { - if (Strings.isNullOrEmpty(user)) { - throw new AnalysisException("User[" + user + "] is null"); + public ProcNodeInterface lookup(String userIdent) throws AnalysisException { + if (Strings.isNullOrEmpty(userIdent)) { + throw new AnalysisException("User is not specified"); } - return new UserPropertyProcNode(userPropertyMgr, user); + UserIdentity userIdentity = UserIdentity.fromString(userIdent); + if (userIdentity == null) { + throw new AnalysisException("Invalid user ident: " + userIdent); + } + + return new UserPropertyProcNode(auth, userIdentity.getQualifiedUser()); } @Override public ProcResult fetchResult() throws AnalysisException { BaseProcResult result = new BaseProcResult(); result.setNames(TITLE_NAMES); - result.setRows(userPropertyMgr.fetchAccessResourceResult(UserPropertyMgr.ROOT_USER)); + result.setRows(Catalog.getCurrentCatalog().getAuth().getAuthInfo(null, true /* is all */)); return result; } } diff --git a/fe/src/com/baidu/palo/common/proc/BackendsProcDir.java b/fe/src/com/baidu/palo/common/proc/BackendsProcDir.java index d2e79e6d24..47b85aa9ce 100644 --- a/fe/src/com/baidu/palo/common/proc/BackendsProcDir.java +++ b/fe/src/com/baidu/palo/common/proc/BackendsProcDir.java @@ -74,7 +74,7 @@ public class BackendsProcDir implements ProcDirInterface { BaseProcResult result = new BaseProcResult(); result.setNames(TITLE_NAMES); - final List> backendInfos = getBackendInfos(); + final List> backendInfos = getClusterBackendInfos(null); for (List backendInfo : backendInfos) { List oneInfo = new ArrayList(backendInfo.size()); for (String info : backendInfo) { @@ -84,14 +84,6 @@ public class BackendsProcDir implements ProcDirInterface { } return result; } - - /** - * get all backends of system - * @return - */ - public static List> getBackendInfos() { - return getClusterBackendInfos(null); - } /** * get backends of cluster @@ -177,9 +169,9 @@ public class BackendsProcDir implements ProcDirInterface { free = (double) backend.getDataUsedCapacityB() * 100 / backend.getTotalCapacityB(); } backendInfo.add(String.format("%.2f", free) + " %"); - comparableBackendInfos.add(backendInfo); } + // backends proc node get result too slow, add log to observer. LOG.info("backends proc get tablet num cost: {}, total cost: {}", watch.elapsed(TimeUnit.MILLISECONDS), (System.currentTimeMillis() - start)); @@ -226,3 +218,4 @@ public class BackendsProcDir implements ProcDirInterface { } } + diff --git a/fe/src/com/baidu/palo/common/proc/BackupJobProcNode.java b/fe/src/com/baidu/palo/common/proc/BackupJobProcNode.java deleted file mode 100644 index 158bf1b8cf..0000000000 --- a/fe/src/com/baidu/palo/common/proc/BackupJobProcNode.java +++ /dev/null @@ -1,62 +0,0 @@ -// Modifications copyright (C) 2017, Baidu.com, Inc. -// Copyright 2017 The Apache Software Foundation - -// 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. - -package com.baidu.palo.common.proc; - -import com.baidu.palo.backup.AbstractBackupJob; -import com.baidu.palo.backup.BackupHandler; -import com.baidu.palo.common.AnalysisException; - -import com.google.common.collect.ImmutableList; - -import java.util.ArrayList; -import java.util.List; - -public class BackupJobProcNode implements ProcNodeInterface { - public static final ImmutableList TITLE_NAMES = new ImmutableList.Builder() - .add("TabletId").add("UnfinishedBackends") - .build(); - - private BackupHandler backupHandler; - private long dbId; - private Class jobClass; - - public BackupJobProcNode(BackupHandler backupHandler, long dbId, Class jobClass) { - this.backupHandler = backupHandler; - this.dbId = dbId; - this.jobClass = jobClass; - } - - @Override - public ProcResult fetchResult() throws AnalysisException { - BaseProcResult result = new BaseProcResult(); - result.setNames(TITLE_NAMES); - - List> infos = backupHandler.getJobUnfinishedTablet(dbId, jobClass); - for (List info : infos) { - List oneInfo = new ArrayList(TITLE_NAMES.size()); - for (Comparable element : info) { - oneInfo.add(element.toString()); - } - result.addRow(oneInfo); - } - return result; - } -} diff --git a/fe/src/com/baidu/palo/common/proc/BackupProcNode.java b/fe/src/com/baidu/palo/common/proc/BackupProcNode.java deleted file mode 100644 index ed3eb58876..0000000000 --- a/fe/src/com/baidu/palo/common/proc/BackupProcNode.java +++ /dev/null @@ -1,84 +0,0 @@ -// Modifications copyright (C) 2017, Baidu.com, Inc. -// Copyright 2017 The Apache Software Foundation - -// 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. - -package com.baidu.palo.common.proc; - -import com.baidu.palo.backup.BackupHandler; -import com.baidu.palo.backup.BackupJob; -import com.baidu.palo.catalog.Database; -import com.baidu.palo.common.AnalysisException; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; - -import java.util.ArrayList; -import java.util.List; - -public class BackupProcNode implements ProcDirInterface { - public static final ImmutableList TITLE_NAMES = new ImmutableList.Builder() - .add("JobId").add("Lable").add("State").add("CreateTime") - .add("MetaSavedTime").add("SnapshotFinishedTime").add("UploadFinishedTime").add("FinishedTime") - .add("ErrMsg").add("BackupPath").add("Manifest").add("LeftTaskNum").add("LatestLoadLabel") - .build(); - - private BackupHandler backupHandler; - private Database db; - - public BackupProcNode(BackupHandler backupHandler, Database db) { - this.backupHandler = backupHandler; - this.db = db; - } - - @Override - public ProcResult fetchResult() throws AnalysisException { - Preconditions.checkNotNull(db); - Preconditions.checkNotNull(backupHandler); - - BaseProcResult result = new BaseProcResult(); - result.setNames(TITLE_NAMES); - - List> backupJobInfos = backupHandler.getJobInfosByDb(db.getId(), BackupJob.class, null); - for (List infoStr : backupJobInfos) { - List oneInfo = new ArrayList(TITLE_NAMES.size()); - for (Comparable element : infoStr) { - oneInfo.add(element.toString()); - } - result.addRow(oneInfo); - } - return result; - } - - @Override - public boolean register(String name, ProcNodeInterface node) { - return false; - } - - @Override - public ProcNodeInterface lookup(String jobIdStr) throws AnalysisException { - try { - Long.valueOf(jobIdStr); - } catch (NumberFormatException e) { - throw new AnalysisException("Invalid job id format: " + jobIdStr); - } - - return new BackupJobProcNode(backupHandler, db.getId(), BackupJob.class); - } - -} diff --git a/fe/src/com/baidu/palo/common/proc/FrontendsProcNode.java b/fe/src/com/baidu/palo/common/proc/FrontendsProcNode.java index 9b8f2368e7..1810816e8f 100644 --- a/fe/src/com/baidu/palo/common/proc/FrontendsProcNode.java +++ b/fe/src/com/baidu/palo/common/proc/FrontendsProcNode.java @@ -37,7 +37,7 @@ import java.util.List; */ public class FrontendsProcNode implements ProcNodeInterface { public static final ImmutableList TITLE_NAMES = new ImmutableList.Builder() - .add("name").add("Host").add("Port").add("Role").add("IsMaster").add("ClusterId").add("Join") + .add("name").add("Host").add("EditLogPort").add("Role").add("IsMaster").add("ClusterId").add("Join") .build(); private Catalog catalog; @@ -51,6 +51,18 @@ public class FrontendsProcNode implements ProcNodeInterface { BaseProcResult result = new BaseProcResult(); result.setNames(TITLE_NAMES); + List> infos = Lists.newArrayList(); + + getFrontendsInfo(catalog, infos); + + for (List info : infos) { + result.addRow(info); + } + + return result; + } + + public static void getFrontendsInfo(Catalog catalog, List> infos) { InetSocketAddress master = catalog.getHaProtocol().getLeader(); String masterIp = master.getAddress().getHostAddress(); int masterPort = master.getPort(); @@ -79,12 +91,11 @@ public class FrontendsProcNode implements ProcNodeInterface { info.add("true"); } - result.addRow(info); + infos.add(info); } - return result; } - private boolean isJoin(List> allFeHosts, Frontend fe) { + private static boolean isJoin(List> allFeHosts, Frontend fe) { for (Pair pair : allFeHosts) { if (fe.getHost().equals(pair.first) && fe.getEditLogPort() == pair.second) { return true; @@ -93,7 +104,7 @@ public class FrontendsProcNode implements ProcNodeInterface { return false; } - private List> convertToHostPortPair(List addrs) { + private static List> convertToHostPortPair(List addrs) { List> hostPortPair = Lists.newArrayList(); for (InetSocketAddress addr : addrs) { hostPortPair.add(Pair.create(addr.getAddress().getHostAddress(), addr.getPort())); diff --git a/fe/src/com/baidu/palo/common/proc/JobsProcDir.java b/fe/src/com/baidu/palo/common/proc/JobsProcDir.java index f8922baf18..73bf34ca1b 100644 --- a/fe/src/com/baidu/palo/common/proc/JobsProcDir.java +++ b/fe/src/com/baidu/palo/common/proc/JobsProcDir.java @@ -22,9 +22,6 @@ package com.baidu.palo.common.proc; import com.baidu.palo.alter.RollupHandler; import com.baidu.palo.alter.SchemaChangeHandler; -import com.baidu.palo.backup.BackupHandler; -import com.baidu.palo.backup.BackupJob.BackupJobState; -import com.baidu.palo.backup.RestoreJob.RestoreJobState; import com.baidu.palo.catalog.Catalog; import com.baidu.palo.catalog.Database; import com.baidu.palo.clone.CloneJob.JobState; @@ -53,8 +50,6 @@ public class JobsProcDir implements ProcDirInterface { private static final String DELETE = "delete"; private static final String ROLLUP = "rollup"; private static final String SCHEMA_CHANGE = "schema_change"; - private static final String BACKUP = "backup"; - private static final String RESTORE = "restore"; private static final String EXPORT = "export"; private Catalog catalog; @@ -86,10 +81,6 @@ public class JobsProcDir implements ProcDirInterface { return new RollupProcDir(catalog.getRollupHandler(), db); } else if (jobTypeName.equals(SCHEMA_CHANGE)) { return new SchemaChangeProcNode(catalog.getSchemaChangeHandler(), db); - } else if (jobTypeName.equals(BACKUP)) { - return new BackupProcNode(catalog.getBackupHandler(), db); - } else if (jobTypeName.equals(RESTORE)) { - return new RestoreProcNode(catalog.getBackupHandler(), db); } else if (jobTypeName.equals(EXPORT)) { return new ExportProcNode(catalog.getExportMgr(), db); } else { @@ -156,6 +147,7 @@ public class JobsProcDir implements ProcDirInterface { result.addRow(Lists.newArrayList(SCHEMA_CHANGE, pendingNum.toString(), runningNum.toString(), finishedNum.toString(), cancelledNum.toString(), totalNum.toString())); + /* // backup BackupHandler backupHandler = Catalog.getInstance().getBackupHandler(); pendingNum = backupHandler.getBackupJobNum(BackupJobState.PENDING, dbId); @@ -168,7 +160,7 @@ public class JobsProcDir implements ProcDirInterface { totalNum = pendingNum + runningNum + finishedNum + cancelledNum; result.addRow(Lists.newArrayList(BACKUP, pendingNum.toString(), runningNum.toString(), finishedNum.toString(), cancelledNum.toString(), totalNum.toString())); - + // restore pendingNum = backupHandler.getRestoreJobNum(RestoreJobState.PENDING, dbId); runningNum = backupHandler.getRestoreJobNum(RestoreJobState.RESTORE_META, dbId) @@ -179,6 +171,7 @@ public class JobsProcDir implements ProcDirInterface { totalNum = pendingNum + runningNum + finishedNum + cancelledNum; result.addRow(Lists.newArrayList(RESTORE, pendingNum.toString(), runningNum.toString(), finishedNum.toString(), cancelledNum.toString(), totalNum.toString())); + */ // export ExportMgr exportMgr = Catalog.getInstance().getExportMgr(); diff --git a/fe/src/com/baidu/palo/common/proc/LoadProcDir.java b/fe/src/com/baidu/palo/common/proc/LoadProcDir.java index ef7852153b..dca0d330ca 100644 --- a/fe/src/com/baidu/palo/common/proc/LoadProcDir.java +++ b/fe/src/com/baidu/palo/common/proc/LoadProcDir.java @@ -20,16 +20,16 @@ package com.baidu.palo.common.proc; -import com.baidu.palo.catalog.Database; -import com.baidu.palo.common.AnalysisException; -import com.baidu.palo.load.Load; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.LinkedList; +import com.baidu.palo.catalog.Database; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.load.Load; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; import java.util.List; public class LoadProcDir implements ProcDirInterface { @@ -62,7 +62,8 @@ public class LoadProcDir implements ProcDirInterface { BaseProcResult result = new BaseProcResult(); result.setNames(TITLE_NAMES); - LinkedList> loadJobInfos = load.getLoadJobInfosByDb(db.getId(), null, false, null, null); + LinkedList> loadJobInfos = load.getLoadJobInfosByDb(db.getId(), db.getFullName(), + null, false, null, null); int counter = 0; Iterator> iterator = loadJobInfos.descendingIterator(); while (iterator.hasNext()) { diff --git a/fe/src/com/baidu/palo/common/proc/ProcService.java b/fe/src/com/baidu/palo/common/proc/ProcService.java index 2cc71e6571..ff1c1a6903 100644 --- a/fe/src/com/baidu/palo/common/proc/ProcService.java +++ b/fe/src/com/baidu/palo/common/proc/ProcService.java @@ -37,7 +37,7 @@ public final class ProcService { private ProcService() { root = new BaseProcDir(); - root.register("access_resource", new AccessResourceProcDir(Catalog.getInstance().getUserMgr())); + root.register("auth", new AuthProcDir(Catalog.getCurrentCatalog().getAuth())); root.register("backends", new BackendsProcDir(Catalog.getCurrentSystemInfo())); root.register("dbs", new DbsProcDir(Catalog.getInstance())); root.register("jobs", new JobsDbProcDir(Catalog.getInstance())); diff --git a/fe/src/com/baidu/palo/common/proc/ReplicasProcNode.java b/fe/src/com/baidu/palo/common/proc/ReplicasProcNode.java index 7aee14f623..b717a212d6 100644 --- a/fe/src/com/baidu/palo/common/proc/ReplicasProcNode.java +++ b/fe/src/com/baidu/palo/common/proc/ReplicasProcNode.java @@ -18,57 +18,59 @@ // specific language governing permissions and limitations // under the License. -package com.baidu.palo.common.proc; - -import com.baidu.palo.catalog.Database; -import com.baidu.palo.catalog.Replica; -import com.baidu.palo.catalog.Tablet; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; - -import java.util.Arrays; - -/* - * SHOW PROC /dbs/dbId/tableId/partitions/partitionId/indexId/tabletId - * show replicas' detail info within a tablet - */ -public class ReplicasProcNode implements ProcNodeInterface { - public static final ImmutableList TITLE_NAMES = new ImmutableList.Builder() - .add("ReplicaId").add("BackendId").add("Version").add("VersionHash") - .add("DataSize").add("RowCount").add("State") - .build(); - - private Database db; - private Tablet tablet; - - public ReplicasProcNode(Database db, Tablet tablet) { - this.db = db; - this.tablet = tablet; - } - - @Override - public ProcResult fetchResult() { - Preconditions.checkNotNull(db); - Preconditions.checkNotNull(tablet); - - db.readLock(); - try { - BaseProcResult result = new BaseProcResult(); - result.setNames(TITLE_NAMES); - for (Replica replica : tablet.getReplicas()) { - // id -- backendId -- version -- versionHash -- dataSize -- rowCount -- state - result.addRow(Arrays.asList(String.valueOf(replica.getId()), - String.valueOf(replica.getBackendId()), - String.valueOf(replica.getVersion()), - String.valueOf(replica.getVersionHash()), - String.valueOf(replica.getDataSize()), - String.valueOf(replica.getRowCount()), - String.valueOf(replica.getState()))); - } - return result; - } finally { - db.readUnlock(); - } - } -} +package com.baidu.palo.common.proc; + +import com.baidu.palo.catalog.Database; +import com.baidu.palo.catalog.Replica; +import com.baidu.palo.catalog.Tablet; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; + +import java.util.Arrays; + +/* + * SHOW PROC /dbs/dbId/tableId/partitions/partitionId/indexId/tabletId + * show replicas' detail info within a tablet + */ +public class ReplicasProcNode implements ProcNodeInterface { + public static final ImmutableList TITLE_NAMES = new ImmutableList.Builder() + .add("ReplicaId").add("BackendId").add("Version").add("VersionHash") + .add("DataSize").add("RowCount").add("State").add("VersionCount") + .build(); + + private Database db; + private Tablet tablet; + + public ReplicasProcNode(Database db, Tablet tablet) { + this.db = db; + this.tablet = tablet; + } + + @Override + public ProcResult fetchResult() { + Preconditions.checkNotNull(db); + Preconditions.checkNotNull(tablet); + + db.readLock(); + try { + BaseProcResult result = new BaseProcResult(); + result.setNames(TITLE_NAMES); + for (Replica replica : tablet.getReplicas()) { + // id -- backendId -- version -- versionHash -- dataSize -- rowCount -- state + result.addRow(Arrays.asList(String.valueOf(replica.getId()), + String.valueOf(replica.getBackendId()), + String.valueOf(replica.getVersion()), + String.valueOf(replica.getVersionHash()), + String.valueOf(replica.getDataSize()), + String.valueOf(replica.getRowCount()), + String.valueOf(replica.getState()), + String.valueOf(replica.getVersionCount()))); + } + return result; + } finally { + db.readUnlock(); + } + } +} + diff --git a/fe/src/com/baidu/palo/common/proc/RestoreProcNode.java b/fe/src/com/baidu/palo/common/proc/RestoreProcNode.java deleted file mode 100644 index 524f02cb30..0000000000 --- a/fe/src/com/baidu/palo/common/proc/RestoreProcNode.java +++ /dev/null @@ -1,84 +0,0 @@ -// Modifications copyright (C) 2017, Baidu.com, Inc. -// Copyright 2017 The Apache Software Foundation - -// 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. - -package com.baidu.palo.common.proc; - -import com.baidu.palo.backup.BackupHandler; -import com.baidu.palo.backup.RestoreJob; -import com.baidu.palo.catalog.Database; -import com.baidu.palo.common.AnalysisException; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; - -import java.util.ArrayList; -import java.util.List; - -public class RestoreProcNode implements ProcDirInterface { - public static final ImmutableList TITLE_NAMES = new ImmutableList.Builder() - .add("JobId").add("Lable").add("State").add("CreateTime") - .add("MetaRestoredTime").add("DowloadFinishedTime").add("FinishedTime").add("ErrMsg") - .add("RestorePath").add("LeftTaskNum") - .build(); - - private BackupHandler backupHandler; - private Database db; - - public RestoreProcNode(BackupHandler backupHandler, Database db) { - this.backupHandler = backupHandler; - this.db = db; - } - - @Override - public ProcResult fetchResult() throws AnalysisException { - Preconditions.checkNotNull(db); - Preconditions.checkNotNull(backupHandler); - - BaseProcResult result = new BaseProcResult(); - result.setNames(TITLE_NAMES); - - List> restoreJobInfos = backupHandler.getJobInfosByDb(db.getId(), RestoreJob.class, null); - for (List infoStr : restoreJobInfos) { - List oneInfo = new ArrayList(TITLE_NAMES.size()); - for (Comparable element : infoStr) { - oneInfo.add(element.toString()); - } - result.addRow(oneInfo); - } - return result; - } - - @Override - public boolean register(String name, ProcNodeInterface node) { - return false; - } - - @Override - public ProcNodeInterface lookup(String jobIdStr) throws AnalysisException { - try { - Long.valueOf(jobIdStr); - } catch (NumberFormatException e) { - throw new AnalysisException("Invalid job id format: " + jobIdStr); - } - - return new BackupJobProcNode(backupHandler, db.getId(), RestoreJob.class); - } - -} diff --git a/fe/src/com/baidu/palo/common/proc/SchemaChangeProcNode.java b/fe/src/com/baidu/palo/common/proc/SchemaChangeProcNode.java index a23d79db49..8ae23ba3db 100644 --- a/fe/src/com/baidu/palo/common/proc/SchemaChangeProcNode.java +++ b/fe/src/com/baidu/palo/common/proc/SchemaChangeProcNode.java @@ -20,15 +20,14 @@ package com.baidu.palo.common.proc; -import com.baidu.palo.catalog.Database; -import com.baidu.palo.common.AnalysisException; -import com.baidu.palo.alter.SchemaChangeHandler; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; - -import java.util.ArrayList; -import java.util.Arrays; +import com.baidu.palo.alter.SchemaChangeHandler; +import com.baidu.palo.catalog.Database; +import com.baidu.palo.common.AnalysisException; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; + +import java.util.ArrayList; import java.util.List; public class SchemaChangeProcNode implements ProcNodeInterface { diff --git a/fe/src/com/baidu/palo/common/proc/TabletsProcDir.java b/fe/src/com/baidu/palo/common/proc/TabletsProcDir.java index 26990373fd..cf8eea3a3f 100644 --- a/fe/src/com/baidu/palo/common/proc/TabletsProcDir.java +++ b/fe/src/com/baidu/palo/common/proc/TabletsProcDir.java @@ -18,157 +18,161 @@ // specific language governing permissions and limitations // under the License. -package com.baidu.palo.common.proc; - -import com.baidu.palo.catalog.Catalog; -import com.baidu.palo.catalog.Database; -import com.baidu.palo.catalog.Replica; -import com.baidu.palo.catalog.MaterializedIndex; -import com.baidu.palo.catalog.Tablet; -import com.baidu.palo.common.AnalysisException; -import com.baidu.palo.common.util.ListComparator; -import com.baidu.palo.common.util.TimeUtils; -import com.baidu.palo.system.Backend; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; - -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/* - * SHOW PROC /dbs/dbId/tableId/partitions/partitionId/indexId - * show tablets' detail info within an index - */ -public class TabletsProcDir implements ProcDirInterface { - public static final ImmutableList TITLE_NAMES = new ImmutableList.Builder() - .add("TabletId").add("ReplicaId").add("BackendId").add("HostName").add("Version") - .add("VersionHash").add("DataSize").add("RowCount").add("State") - .add("LastConsistencyCheckTime").add("CheckVersion").add("CheckVersionHash") - .build(); - - private Database db; - private MaterializedIndex index; - - public TabletsProcDir(Database db, MaterializedIndex index) { - this.db = db; - this.index = index; - } - - @Override - public ProcResult fetchResult() { - Preconditions.checkNotNull(db); - Preconditions.checkNotNull(index); - - List> tabletInfos = new ArrayList>(); - db.readLock(); - try { - // get infos - for (Tablet tablet : index.getTablets()) { - long tabletId = tablet.getId(); - if (tablet.getReplicas().size() == 0) { - List tabletInfo = new ArrayList(); - tabletInfo.add(tabletId); - tabletInfo.add(-1); - tabletInfo.add(-1); - tabletInfo.add(-1); - tabletInfo.add(-1); - tabletInfo.add(-1); - tabletInfo.add(-1); - tabletInfo.add("N/A"); - tabletInfo.add("N/A"); - tabletInfo.add(-1); - tabletInfo.add(-1); - - tabletInfos.add(tabletInfo); - } else { - for (Replica replica : tablet.getReplicas()) { - List tabletInfo = new ArrayList(); - // tabletId -- replicaId -- backendId -- version -- versionHash -- dataSize -- rowCount -- state - tabletInfo.add(tabletId); - tabletInfo.add(replica.getId()); - long backendId = replica.getBackendId(); - tabletInfo.add(replica.getBackendId()); - Backend backend = Catalog.getCurrentSystemInfo().getBackend(backendId); +package com.baidu.palo.common.proc; + +import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.catalog.Database; +import com.baidu.palo.catalog.MaterializedIndex; +import com.baidu.palo.catalog.Replica; +import com.baidu.palo.catalog.Tablet; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.util.ListComparator; +import com.baidu.palo.common.util.TimeUtils; +import com.baidu.palo.system.Backend; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/* + * SHOW PROC /dbs/dbId/tableId/partitions/partitionId/indexId + * show tablets' detail info within an index + */ +public class TabletsProcDir implements ProcDirInterface { + public static final ImmutableList TITLE_NAMES = new ImmutableList.Builder() + .add("TabletId").add("ReplicaId").add("BackendId").add("HostName").add("Version") + .add("VersionHash").add("DataSize").add("RowCount").add("State") + .add("LastConsistencyCheckTime").add("CheckVersion").add("CheckVersionHash") + .add("VersionCount") + .build(); + + private Database db; + private MaterializedIndex index; + + public TabletsProcDir(Database db, MaterializedIndex index) { + this.db = db; + this.index = index; + } + + @Override + public ProcResult fetchResult() { + Preconditions.checkNotNull(db); + Preconditions.checkNotNull(index); + + List> tabletInfos = new ArrayList>(); + db.readLock(); + try { + // get infos + for (Tablet tablet : index.getTablets()) { + long tabletId = tablet.getId(); + if (tablet.getReplicas().size() == 0) { + List tabletInfo = new ArrayList(); + tabletInfo.add(tabletId); + tabletInfo.add(-1); + tabletInfo.add(-1); + tabletInfo.add(-1); + tabletInfo.add(-1); + tabletInfo.add(-1); + tabletInfo.add(-1); + tabletInfo.add("N/A"); + tabletInfo.add("N/A"); + tabletInfo.add(-1); + tabletInfo.add(-1); + tabletInfo.add(-1); + + tabletInfos.add(tabletInfo); + } else { + for (Replica replica : tablet.getReplicas()) { + List tabletInfo = new ArrayList(); + // tabletId -- replicaId -- backendId -- version -- versionHash -- dataSize -- rowCount -- state + tabletInfo.add(tabletId); + tabletInfo.add(replica.getId()); + long backendId = replica.getBackendId(); + tabletInfo.add(replica.getBackendId()); + Backend backend = Catalog.getCurrentSystemInfo().getBackend(backendId); // backend may be dropped concurrently, ignore it. if (backend == null) { continue; } - String hostName = null; - try { - InetAddress address = InetAddress.getByName(backend.getHost()); - hostName = address.getHostName(); - } catch (UnknownHostException e) { - continue; - } - tabletInfo.add(hostName); - tabletInfo.add(replica.getVersion()); - tabletInfo.add(replica.getVersionHash()); - tabletInfo.add(replica.getDataSize()); - tabletInfo.add(replica.getRowCount()); - tabletInfo.add(replica.getState()); - - tabletInfo.add(TimeUtils.longToTimeString(tablet.getLastCheckTime())); - tabletInfo.add(tablet.getCheckedVersion()); - tabletInfo.add(tablet.getCheckedVersionHash()); - - tabletInfos.add(tabletInfo); - } - } - } - } finally { - db.readUnlock(); - } - - // sort by tabletId, replicaId - ListComparator> comparator = new ListComparator>(0, 1); - Collections.sort(tabletInfos, comparator); - - // set result - BaseProcResult result = new BaseProcResult(); - result.setNames(TITLE_NAMES); - - for (int i = 0; i < tabletInfos.size(); i++) { - List info = tabletInfos.get(i); - List row = new ArrayList(info.size()); - for (int j = 0; j < info.size(); j++) { - row.add(info.get(j).toString()); - } - result.addRow(row); - } - return result; - } - - @Override - public boolean register(String name, ProcNodeInterface node) { - return false; - } - - @Override - public ProcNodeInterface lookup(String tabletIdStr) throws AnalysisException { - Preconditions.checkNotNull(db); - Preconditions.checkNotNull(index); - - long tabletId = -1L; - try { - tabletId = Long.valueOf(tabletIdStr); - } catch (NumberFormatException e) { - throw new AnalysisException("Invalid tablet id format: " + tabletIdStr); - } - - db.readLock(); - try { - Tablet tablet = index.getTablet(tabletId); - if (tablet == null) { - throw new AnalysisException("Tablet[" + tabletId + "] does not exist."); - } - return new ReplicasProcNode(db, tablet); - } finally { - db.readUnlock(); - } - } - -} + String hostName = null; + try { + InetAddress address = InetAddress.getByName(backend.getHost()); + hostName = address.getHostName(); + } catch (UnknownHostException e) { + continue; + } + tabletInfo.add(hostName); + tabletInfo.add(replica.getVersion()); + tabletInfo.add(replica.getVersionHash()); + tabletInfo.add(replica.getDataSize()); + tabletInfo.add(replica.getRowCount()); + tabletInfo.add(replica.getState()); + + tabletInfo.add(TimeUtils.longToTimeString(tablet.getLastCheckTime())); + tabletInfo.add(tablet.getCheckedVersion()); + tabletInfo.add(tablet.getCheckedVersionHash()); + tabletInfo.add(replica.getVersionCount()); + + tabletInfos.add(tabletInfo); + } + } + } + } finally { + db.readUnlock(); + } + + // sort by tabletId, replicaId + ListComparator> comparator = new ListComparator>(0, 1); + Collections.sort(tabletInfos, comparator); + + // set result + BaseProcResult result = new BaseProcResult(); + result.setNames(TITLE_NAMES); + + for (int i = 0; i < tabletInfos.size(); i++) { + List info = tabletInfos.get(i); + List row = new ArrayList(info.size()); + for (int j = 0; j < info.size(); j++) { + row.add(info.get(j).toString()); + } + result.addRow(row); + } + return result; + } + + @Override + public boolean register(String name, ProcNodeInterface node) { + return false; + } + + @Override + public ProcNodeInterface lookup(String tabletIdStr) throws AnalysisException { + Preconditions.checkNotNull(db); + Preconditions.checkNotNull(index); + + long tabletId = -1L; + try { + tabletId = Long.valueOf(tabletIdStr); + } catch (NumberFormatException e) { + throw new AnalysisException("Invalid tablet id format: " + tabletIdStr); + } + + db.readLock(); + try { + Tablet tablet = index.getTablet(tabletId); + if (tablet == null) { + throw new AnalysisException("Tablet[" + tabletId + "] does not exist."); + } + return new ReplicasProcNode(db, tablet); + } finally { + db.readUnlock(); + } + } + +} + diff --git a/fe/src/com/baidu/palo/common/proc/UserPropertyProcNode.java b/fe/src/com/baidu/palo/common/proc/UserPropertyProcNode.java index 42604af431..c2b05bdc0c 100644 --- a/fe/src/com/baidu/palo/common/proc/UserPropertyProcNode.java +++ b/fe/src/com/baidu/palo/common/proc/UserPropertyProcNode.java @@ -20,32 +20,33 @@ package com.baidu.palo.common.proc; -import com.baidu.palo.catalog.UserPropertyMgr; -import com.baidu.palo.common.AnalysisException; -import com.google.common.collect.ImmutableList; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.mysql.privilege.PaloAuth; + +import com.google.common.collect.ImmutableList; -/* - * SHOW PROC '/access_resource/user' +/* + * SHOW PROC '/auth/user' */ public class UserPropertyProcNode implements ProcNodeInterface { public static final ImmutableList TITLE_NAMES = new ImmutableList.Builder() .add("Key").add("Value") .build(); - private UserPropertyMgr userPropertyMgr; - private String user; + private PaloAuth auth; + private String qualifiedUser; - public UserPropertyProcNode(UserPropertyMgr userPropertyMgr, String user) { - this.userPropertyMgr = userPropertyMgr; - this.user = user; + public UserPropertyProcNode(PaloAuth auth, String qualifiedUser) { + this.auth = auth; + this.qualifiedUser = qualifiedUser; } @Override public ProcResult fetchResult() throws AnalysisException { BaseProcResult result = new BaseProcResult(); - result.setNames(TITLE_NAMES); - result.setRows(userPropertyMgr.fetchUserProperty(user)); + result.setNames(TITLE_NAMES); + + result.setRows(auth.getUserProperties(qualifiedUser)); return result; } - } diff --git a/fe/src/com/baidu/palo/common/util/TimeUtils.java b/fe/src/com/baidu/palo/common/util/TimeUtils.java index 1e6c2b3aa2..0634ff8128 100644 --- a/fe/src/com/baidu/palo/common/util/TimeUtils.java +++ b/fe/src/com/baidu/palo/common/util/TimeUtils.java @@ -20,23 +20,23 @@ package com.baidu.palo.common.util; -import com.baidu.palo.catalog.PrimitiveType; -import com.baidu.palo.catalog.Type; -import com.baidu.palo.common.AnalysisException; - -import com.google.common.base.Preconditions; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.LogManager; - -import java.text.ParseException; -import java.text.ParsePosition; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; -import java.util.SimpleTimeZone; -import java.util.TimeZone; -import java.util.regex.Matcher; +import com.baidu.palo.catalog.PrimitiveType; +import com.baidu.palo.catalog.Type; +import com.baidu.palo.common.AnalysisException; + +import com.google.common.base.Preconditions; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.text.ParseException; +import java.text.ParsePosition; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.SimpleTimeZone; +import java.util.TimeZone; +import java.util.regex.Matcher; import java.util.regex.Pattern; // TODO(dhc) add nanosecond timer for coordinator's root profile @@ -102,11 +102,15 @@ public class TimeUtils { return DATETIME_FORMAT.format(new Date()); } - public static synchronized String longToTimeString(long timeStamp) { - if (timeStamp < 0L) { - return "N/A"; - } - return DATETIME_FORMAT.format(new Date(timeStamp)); + public static String longToTimeString(long timeStamp, SimpleDateFormat dateFormat) { + if (timeStamp < 0L) { + return "N/A"; + } + return dateFormat.format(new Date(timeStamp)); + } + + public static synchronized String longToTimeString(long timeStamp) { + return longToTimeString(timeStamp, DATETIME_FORMAT); } public static synchronized Date getTimeAsDate(String timeString) { @@ -189,5 +193,15 @@ public class TimeUtils { public static long dateTransform(long time, Type type) { return dateTransform(time, type.getPrimitiveType()); + } + + public static long timeStringToLong(String timeStr) { + Date d; + try { + d = DATETIME_FORMAT.parse(timeStr); + } catch (ParseException e) { + return -1; + } + return d.getTime(); } } diff --git a/fe/src/com/baidu/palo/consistency/ConsistencyChecker.java b/fe/src/com/baidu/palo/consistency/ConsistencyChecker.java index dd7b558fab..1b52b9f941 100644 --- a/fe/src/com/baidu/palo/consistency/ConsistencyChecker.java +++ b/fe/src/com/baidu/palo/consistency/ConsistencyChecker.java @@ -242,6 +242,9 @@ public class ConsistencyChecker extends Daemon { // sort dbs List dbIds = catalog.getDbIds(); + if (dbIds.isEmpty()) { + return -1L; + } Queue dbQueue = new PriorityQueue(dbIds.size(), COMPARATOR); for (Long dbId : dbIds) { if (dbId == 0L) { diff --git a/fe/src/com/baidu/palo/deploy/DeployManager.java b/fe/src/com/baidu/palo/deploy/DeployManager.java index 732e2caebd..16aebae39b 100644 --- a/fe/src/com/baidu/palo/deploy/DeployManager.java +++ b/fe/src/com/baidu/palo/deploy/DeployManager.java @@ -630,4 +630,3 @@ public class DeployManager extends Daemon { } } } - diff --git a/fe/src/com/baidu/palo/deploy/impl/LocalFileDeployManager.java b/fe/src/com/baidu/palo/deploy/impl/LocalFileDeployManager.java index 7c85f9f5c2..4db5a30320 100644 --- a/fe/src/com/baidu/palo/deploy/impl/LocalFileDeployManager.java +++ b/fe/src/com/baidu/palo/deploy/impl/LocalFileDeployManager.java @@ -68,7 +68,7 @@ public class LocalFileDeployManager extends DeployManager { @Override public List> getGroupHostPorts(String groupName) { List> result = Lists.newArrayList(); - LOG.debug("begin to get group: {} from file: {}", groupName, clusterInfoFile); + LOG.info("begin to get group: {} from file: {}", groupName, clusterInfoFile); FileChannel channel = null; FileLock lock = null; diff --git a/fe/src/com/baidu/palo/ha/BDBHA.java b/fe/src/com/baidu/palo/ha/BDBHA.java index 3df7877b63..8ab589c2e8 100644 --- a/fe/src/com/baidu/palo/ha/BDBHA.java +++ b/fe/src/com/baidu/palo/ha/BDBHA.java @@ -120,8 +120,8 @@ public class BDBHA implements HAProtocol { return null; } List ret = new ArrayList(); - try { - ReplicationGroup replicationGroup = replicationGroupAdmin.getGroup(); + try { + ReplicationGroup replicationGroup = replicationGroupAdmin.getGroup(); for (ReplicationNode replicationNode : replicationGroup.getElectableNodes()) { if (leaderIncluded) { ret.add(replicationNode.getSocketAddress()); diff --git a/fe/src/com/baidu/palo/ha/BDBStateChangeListener.java b/fe/src/com/baidu/palo/ha/BDBStateChangeListener.java index d318e0bd57..946f7d2895 100644 --- a/fe/src/com/baidu/palo/ha/BDBStateChangeListener.java +++ b/fe/src/com/baidu/palo/ha/BDBStateChangeListener.java @@ -15,14 +15,15 @@ package com.baidu.palo.ha; -import org.apache.logging.log4j.LogManager; +import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.persist.EditLog; + +import com.sleepycat.je.rep.StateChangeEvent; +import com.sleepycat.je.rep.StateChangeListener; + +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import com.baidu.palo.catalog.Catalog; -import com.baidu.palo.persist.EditLog; -import com.sleepycat.je.rep.StateChangeEvent; -import com.sleepycat.je.rep.StateChangeListener; - public class BDBStateChangeListener implements StateChangeListener { public static final Logger LOG = LogManager.getLogger(EditLog.class); @@ -30,7 +31,7 @@ public class BDBStateChangeListener implements StateChangeListener { } @Override - public synchronized void stateChange(StateChangeEvent sce) throws RuntimeException { + public synchronized void stateChange(StateChangeEvent sce) throws RuntimeException { FrontendNodeType originalType = Catalog.getInstance().getFeType(); switch (sce.getState()) { case MASTER: { diff --git a/fe/src/com/baidu/palo/ha/MasterInfo.java b/fe/src/com/baidu/palo/ha/MasterInfo.java index 1af4bbc9f0..0a45d4f652 100644 --- a/fe/src/com/baidu/palo/ha/MasterInfo.java +++ b/fe/src/com/baidu/palo/ha/MasterInfo.java @@ -13,25 +13,25 @@ // specific language governing permissions and limitations // under the License. -package com.baidu.palo.ha; - +package com.baidu.palo.ha; + import com.baidu.palo.common.io.Text; import com.baidu.palo.common.io.Writable; import java.io.DataInput; import java.io.DataOutput; -import java.io.IOException; - -public class MasterInfo implements Writable { - - private String ip; - private int httpPort; +import java.io.IOException; + +public class MasterInfo implements Writable { + + private String ip; + private int httpPort; private int rpcPort; - - public MasterInfo() { - this.ip = ""; - this.httpPort = 0; - this.rpcPort = 0; + + public MasterInfo() { + this.ip = ""; + this.httpPort = 0; + this.rpcPort = 0; } public MasterInfo(String ip, int httpPort, int rpcPort) { @@ -39,44 +39,43 @@ public class MasterInfo implements Writable { this.httpPort = httpPort; this.rpcPort = rpcPort; } - - public String getIp() { - return this.ip; - } - - public void setIp(String ip) { - this.ip = ip; - } - - public int getHttpPort() { - return this.httpPort; - } - - public void setHttpPort(int httpPort) { - this.httpPort = httpPort; - } - - public int getRpcPort() { - return this.rpcPort; - } - - public void setRpcPort(int rpcPort) { - this.rpcPort = rpcPort; - } - - @Override - public void write(DataOutput out) throws IOException { - Text.writeString(out, ip); - out.writeInt(httpPort); - out.writeInt(rpcPort); - } - - @Override - public void readFields(DataInput in) throws IOException { - ip = Text.readString(in); - httpPort = in.readInt(); - rpcPort = in.readInt(); - } - -} - + + public String getIp() { + return this.ip; + } + + public void setIp(String ip) { + this.ip = ip; + } + + public int getHttpPort() { + return this.httpPort; + } + + public void setHttpPort(int httpPort) { + this.httpPort = httpPort; + } + + public int getRpcPort() { + return this.rpcPort; + } + + public void setRpcPort(int rpcPort) { + this.rpcPort = rpcPort; + } + + @Override + public void write(DataOutput out) throws IOException { + Text.writeString(out, ip); + out.writeInt(httpPort); + out.writeInt(rpcPort); + } + + @Override + public void readFields(DataInput in) throws IOException { + ip = Text.readString(in); + httpPort = in.readInt(); + rpcPort = in.readInt(); + } + +} diff --git a/fe/src/com/baidu/palo/http/BaseAction.java b/fe/src/com/baidu/palo/http/BaseAction.java index 9917076bde..ff3b4c7705 100644 --- a/fe/src/com/baidu/palo/http/BaseAction.java +++ b/fe/src/com/baidu/palo/http/BaseAction.java @@ -15,11 +15,10 @@ package com.baidu.palo.http; -import com.baidu.palo.catalog.AccessPrivilege; import com.baidu.palo.catalog.Catalog; import com.baidu.palo.cluster.ClusterNamespace; import com.baidu.palo.common.DdlException; -import com.baidu.palo.mysql.MysqlPassword; +import com.baidu.palo.mysql.privilege.PrivPredicate; import com.baidu.palo.qe.QeService; import com.baidu.palo.system.SystemInfoService; @@ -90,9 +89,13 @@ public abstract class BaseAction implements IAction { try { execute(request, response); } catch (Exception e) { - LOG.warn("fail to process url={}. error={}", - request.getRequest().uri(), e); - writeResponse(request, response, HttpResponseStatus.NOT_FOUND); + LOG.warn("fail to process url: {}", request.getRequest().uri(), e); + if (e instanceof UnauthorizedException) { + response.updateHeader(HttpHeaders.Names.WWW_AUTHENTICATE, "Basic realm=\"\""); + writeResponse(request, response, HttpResponseStatus.UNAUTHORIZED); + } else { + writeResponse(request, response, HttpResponseStatus.NOT_FOUND); + } } } @@ -230,11 +233,67 @@ public abstract class BaseAction implements IAction { public static class AuthorizationInfo { public String fullUserName; + public String remoteIp; public String password; public String cluster; + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("user: ").append(fullUserName).append(", remote ip: ").append(remoteIp); + sb.append(", password: ").append(password).append(", cluster: ").append(cluster); + return sb.toString(); + } } - public boolean parseAuth(BaseRequest request, AuthorizationInfo authInfo) { + protected void checkGlobalAuth(AuthorizationInfo authInfo, PrivPredicate predicate) throws UnauthorizedException { + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(authInfo.remoteIp, + authInfo.fullUserName, + predicate)) { + throw new UnauthorizedException("Access denied; you need (at least one of) the " + + predicate.getPrivs().toString() + " privilege(s) for this operation"); + } + } + + protected void checkDbAuth(AuthorizationInfo authInfo, String db, PrivPredicate predicate) + throws UnauthorizedException { + if (!Catalog.getCurrentCatalog().getAuth().checkDbPriv(authInfo.remoteIp, db, authInfo.fullUserName, + predicate)) { + throw new UnauthorizedException("Access denied; you need (at least one of) the " + + predicate.getPrivs().toString() + " privilege(s) for this operation"); + } + } + + protected void checkTblAuth(AuthorizationInfo authInfo, String db, String tbl, PrivPredicate predicate) + throws UnauthorizedException { + if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(authInfo.remoteIp, db, authInfo.fullUserName, + tbl, predicate)) { + throw new UnauthorizedException("Access denied; you need (at least one of) the " + + predicate.getPrivs().toString() + " privilege(s) for this operation"); + } + } + + protected void checkPassword(AuthorizationInfo authInfo) + throws UnauthorizedException { + if (!Catalog.getCurrentCatalog().getAuth().checkPlainPassword(authInfo.fullUserName, + authInfo.remoteIp, + authInfo.password)) { + throw new UnauthorizedException("Access denied for " + + authInfo.fullUserName + "@" + authInfo.remoteIp); + } + } + + public AuthorizationInfo getAuthorizationInfo(BaseRequest request) + throws UnauthorizedException { + AuthorizationInfo authInfo = new AuthorizationInfo(); + if (!parseAuthInfo(request, authInfo)) { + throw new UnauthorizedException("Need auth information."); + } + LOG.debug("get auth info: {}", authInfo); + return authInfo; + } + + private boolean parseAuthInfo(BaseRequest request, AuthorizationInfo authInfo) { String encodedAuthString = request.getAuthorizationHeader(); if (Strings.isNullOrEmpty(encodedAuthString)) { return false; @@ -265,6 +324,7 @@ public abstract class BaseAction implements IAction { authInfo.cluster = elements[1]; } authInfo.password = authString.substring(index + 1); + authInfo.remoteIp = request.getHostString(); } finally { // release the buf after using Unpooled.copiedBuffer // or it will get memory leak @@ -275,50 +335,6 @@ public abstract class BaseAction implements IAction { return true; } - // check authenticate information - private AuthorizationInfo checkAndGetUser(BaseRequest request) - throws UnauthorizedException { - AuthorizationInfo authInfo = new AuthorizationInfo(); - if (!parseAuth(request, authInfo)) { - throw new UnauthorizedException("Need auth information."); - } - byte[] hashedPasswd = catalog.getUserMgr().getPassword(authInfo.fullUserName); - if (hashedPasswd == null) { - // No such user - throw new UnauthorizedException("No such user(" + authInfo.fullUserName + ")"); - } - if (!MysqlPassword.checkPlainPass(hashedPasswd, authInfo.password)) { - throw new UnauthorizedException("Password error"); - } - return authInfo; - } - - protected void checkAdmin(BaseRequest request) throws UnauthorizedException { - final AuthorizationInfo authInfo = checkAndGetUser(request); - if (!catalog.getUserMgr().isAdmin(authInfo.fullUserName)) { - throw new UnauthorizedException("Administrator needed"); - } - } - - protected void checkReadPriv(String fullUserName, String fullDbName) - throws UnauthorizedException { - if (!catalog.getUserMgr().checkAccess(fullUserName, fullDbName, AccessPrivilege.READ_ONLY)) { - throw new UnauthorizedException("Read Privilege needed"); - } - } - - protected void checkWritePriv(String fullUserName, String fullDbName) - throws UnauthorizedException { - if (!catalog.getUserMgr().checkAccess(fullUserName, fullDbName, AccessPrivilege.READ_WRITE)) { - throw new UnauthorizedException("Write Privilege needed"); - } - } - - public AuthorizationInfo getAuthorizationInfo(BaseRequest request) - throws UnauthorizedException { - return checkAndGetUser(request); - } - protected int checkIntParam(String strParam) { return Integer.parseInt(strParam); } diff --git a/fe/src/com/baidu/palo/http/BaseRequest.java b/fe/src/com/baidu/palo/http/BaseRequest.java index 5b92263081..e99b3e3117 100644 --- a/fe/src/com/baidu/palo/http/BaseRequest.java +++ b/fe/src/com/baidu/palo/http/BaseRequest.java @@ -35,7 +35,7 @@ public class BaseRequest { protected HttpRequest request; protected Map params = Maps.newHashMap(); - private boolean isAdmin = false; + private boolean isAuthorized = false; private QueryStringDecoder decoder; public BaseRequest(ChannelHandlerContext ctx, HttpRequest request) { @@ -67,12 +67,12 @@ public class BaseRequest { this.params = params; } - public boolean isAdmin() { - return isAdmin; + public boolean isAuthorized() { + return isAuthorized; } - public void setAdmin(boolean isAdmin) { - this.isAdmin = isAdmin; + public void setAuthorized(boolean isAuthorized) { + this.isAuthorized = isAuthorized; } public Cookie getCookieByName(String cookieName) { @@ -114,7 +114,7 @@ public class BaseRequest { return params.get(key); } - // get an array patameter. + // get an array parameter. // eg. ?a=1&a=2 public List getArrayParameter(String key) { String uri = request.uri(); diff --git a/fe/src/com/baidu/palo/http/HttpAuthManager.java b/fe/src/com/baidu/palo/http/HttpAuthManager.java index e1c6be2a13..cfc6aeca1a 100755 --- a/fe/src/com/baidu/palo/http/HttpAuthManager.java +++ b/fe/src/com/baidu/palo/http/HttpAuthManager.java @@ -43,9 +43,7 @@ public final class HttpAuthManager { } public String getUsername(String sessionId) { - String username = null; - username = authSessions.getIfPresent(sessionId); - return username; + return authSessions.getIfPresent(sessionId); } public void addClient(String key, String value) { diff --git a/fe/src/com/baidu/palo/http/HttpServer.java b/fe/src/com/baidu/palo/http/HttpServer.java index aabdab1cf2..8b723a4cd0 100755 --- a/fe/src/com/baidu/palo/http/HttpServer.java +++ b/fe/src/com/baidu/palo/http/HttpServer.java @@ -55,6 +55,7 @@ import com.baidu.palo.http.rest.SetConfigAction; import com.baidu.palo.http.rest.ShowMetaInfoAction; import com.baidu.palo.http.rest.ShowProcAction; import com.baidu.palo.http.rest.ShowRuntimeInfoAction; +import com.baidu.palo.http.rest.StorageTypeCheckAction; import com.baidu.palo.master.MetaHelper; import com.baidu.palo.qe.QeService; @@ -100,6 +101,7 @@ public class HttpServer { SetConfigAction.registerAction(controller); GetDdlStmtAction.registerAction(controller); MigrationAction.registerAction(controller); + StorageTypeCheckAction.registerAction(controller); // add web action IndexAction.registerAction(controller); diff --git a/fe/src/com/baidu/palo/http/action/HaAction.java b/fe/src/com/baidu/palo/http/action/HaAction.java index f39c6fb275..3c0cab1815 100644 --- a/fe/src/com/baidu/palo/http/action/HaAction.java +++ b/fe/src/com/baidu/palo/http/action/HaAction.java @@ -169,4 +169,4 @@ public class HaAction extends WebBaseAction { buffer.append(""); } -} +} \ No newline at end of file diff --git a/fe/src/com/baidu/palo/http/action/HelpAction.java b/fe/src/com/baidu/palo/http/action/HelpAction.java index dc9c45fc62..a0b318a8f3 100755 --- a/fe/src/com/baidu/palo/http/action/HelpAction.java +++ b/fe/src/com/baidu/palo/http/action/HelpAction.java @@ -24,10 +24,10 @@ import com.baidu.palo.qe.HelpTopic; import com.google.common.base.Strings; -import io.netty.handler.codec.http.HttpMethod; - import java.util.List; +import io.netty.handler.codec.http.HttpMethod; + public class HelpAction extends WebBaseAction { private static final String DIV_BACKGROUND_COLOR = "#FCFCFC"; diff --git a/fe/src/com/baidu/palo/http/action/SystemAction.java b/fe/src/com/baidu/palo/http/action/SystemAction.java index 753fc007de..862bb5ba67 100755 --- a/fe/src/com/baidu/palo/http/action/SystemAction.java +++ b/fe/src/com/baidu/palo/http/action/SystemAction.java @@ -26,10 +26,10 @@ import com.baidu.palo.http.IllegalArgException; import com.google.common.base.Strings; -import io.netty.handler.codec.http.HttpMethod; - import java.util.List; +import io.netty.handler.codec.http.HttpMethod; + public class SystemAction extends WebBaseAction { public SystemAction(ActionController controller) { @@ -110,7 +110,8 @@ public class SystemAction extends WebBaseAction { for (String str : strList) { buffer.append("

"); if (isDir && columnIndex == 1) { - buffer.append(""); + String escapeStr = str.replace("%", "%25"); + buffer.append(""); buffer.append(str); buffer.append(""); } else { diff --git a/fe/src/com/baidu/palo/http/action/WebBaseAction.java b/fe/src/com/baidu/palo/http/action/WebBaseAction.java index eee93e2afa..48db15b1af 100644 --- a/fe/src/com/baidu/palo/http/action/WebBaseAction.java +++ b/fe/src/com/baidu/palo/http/action/WebBaseAction.java @@ -15,9 +15,10 @@ package com.baidu.palo.http.action; +import com.baidu.palo.analysis.CompoundPredicate.Operator; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.Config; -import com.baidu.palo.common.DdlException; import com.baidu.palo.common.proc.ProcNodeInterface; import com.baidu.palo.common.proc.ProcService; import com.baidu.palo.http.ActionController; @@ -25,14 +26,18 @@ import com.baidu.palo.http.BaseAction; import com.baidu.palo.http.BaseRequest; import com.baidu.palo.http.BaseResponse; import com.baidu.palo.http.HttpAuthManager; +import com.baidu.palo.http.UnauthorizedException; import com.baidu.palo.http.rest.RestBaseResult; +import com.baidu.palo.mysql.privilege.PaloPrivilege; +import com.baidu.palo.mysql.privilege.PrivBitSet; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.google.common.base.Strings; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import java.net.InetSocketAddress; import java.util.List; import java.util.UUID; @@ -43,7 +48,6 @@ import io.netty.handler.codec.http.HttpResponseStatus; public class WebBaseAction extends BaseAction { private static final Logger LOG = LogManager.getLogger(WebBaseAction.class); - private static final String ADMIN_USER = "root"; protected static final String LINE_SEP = System.getProperty("line.separator"); @@ -109,7 +113,7 @@ public class WebBaseAction extends BaseAction { @Override public void execute(BaseRequest request, BaseResponse response) { - if (!checkAuth(request, response)) { + if (!checkAuthWithCookie(request, response)) { return; } @@ -119,66 +123,89 @@ public class WebBaseAction extends BaseAction { } else if (method.equals(HttpMethod.POST)) { executePost(request, response); } else { - response.appendContent(new RestBaseResult("HTTP method is not allowed.").toJson()); + response.appendContent(new RestBaseResult("HTTP method is not allowed: " + method.name()).toJson()); writeResponse(request, response, HttpResponseStatus.METHOD_NOT_ALLOWED); } } - // Sub Action class should overvide this method + // Sub Action class should override this method public void executeGet(BaseRequest request, BaseResponse response) { response.appendContent(new RestBaseResult("Not implemented").toJson()); writeResponse(request, response, HttpResponseStatus.NOT_IMPLEMENTED); } - // Sub Action class should overvide this method + // Sub Action class should override this method public void executePost(BaseRequest request, BaseResponse response) { response.appendContent(new RestBaseResult("Not implemented").toJson()); writeResponse(request, response, HttpResponseStatus.NOT_IMPLEMENTED); } // We first check cookie, if not admin, we check http's authority header - protected boolean checkAuth(BaseRequest request, BaseResponse response) { - if (checkAuthByCookie(request, response)) { + private boolean checkAuthWithCookie(BaseRequest request, BaseResponse response) { + if (!needPassword()) { return true; } - if (needAdmin()) { - try { - checkAdmin(request); - request.setAdmin(true); - addSession(request, response, ADMIN_USER); - return true; - } catch (DdlException e) { - response.appendContent("Authentication Failed.
" - + "You can only access '/help' page without login!"); - writeAuthResponse(request, response); - return false; - } + if (checkCookie(request, response)) { + return true; } - return true; + // cookie is invalid. + AuthorizationInfo authInfo; + try { + authInfo = getAuthorizationInfo(request); + checkPassword(authInfo); + if (needAdmin()) { + checkGlobalAuth(authInfo, PrivPredicate.of(PrivBitSet.of(PaloPrivilege.ADMIN_PRIV, + PaloPrivilege.NODE_PRIV), + Operator.OR)); + } + request.setAuthorized(true); + addSession(request, response, authInfo.fullUserName); + + ConnectContext ctx = new ConnectContext(null); + ctx.setQualifiedUser(authInfo.fullUserName); + ctx.setRemoteIP(authInfo.remoteIp); + ctx.setThreadLocalInfo(); + + return true; + } catch (UnauthorizedException e) { + response.appendContent("Authentication Failed.
" + e.getMessage()); + writeAuthResponse(request, response); + return false; + } } - protected boolean checkAuthByCookie(BaseRequest request, BaseResponse response) { + private boolean checkCookie(BaseRequest request, BaseResponse response) { String sessionId = request.getCookieValue(PALO_SESSION_ID); HttpAuthManager authMgr = HttpAuthManager.getInstance(); - String username = ""; if (!Strings.isNullOrEmpty(sessionId)) { - username = authMgr.getUsername(sessionId); - if (!Strings.isNullOrEmpty(username)) { - if (username.equals(ADMIN_USER)) { - response.updateCookieAge(request, PALO_SESSION_ID, PALO_SESSION_EXPIRED_TIME); - request.setAdmin(true); - return true; - } + String username = authMgr.getUsername(sessionId); + if (Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(request.getHostString(), username, + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.ADMIN_PRIV, + PaloPrivilege.NODE_PRIV), + Operator.OR))) { + response.updateCookieAge(request, PALO_SESSION_ID, PALO_SESSION_EXPIRED_TIME); + request.setAuthorized(true); + + ConnectContext ctx = new ConnectContext(null); + ctx.setQualifiedUser(username); + ctx.setRemoteIP(request.getHostString()); + ctx.setThreadLocalInfo(); + return true; } } return false; } - // ATTN: sub Action classes can override it when there is no need to check authority. - // eg. It is no need admin privileges to access to HelpAction, so we will override this - // mothod in HelpAction by returning false. + // return true if this Action need to check password. + // Currently, all sub actions need to check password except for MetaBaseAction. + // if needPassword() is false, then needAdmin() should also return false + public boolean needPassword() { + return true; + } + + // return true if this Action need Admin privilege. public boolean needAdmin() { return true; } @@ -193,9 +220,6 @@ public class WebBaseAction extends BaseAction { } protected void addSession(BaseRequest request, BaseResponse response, String value) { - // We use hashcode of client's IP and timestamp, which not only can identify users from - // different host machine, but also can improve the difficulty of forging cookie. - int clientAddrHashCode = ((InetSocketAddress) request.getContext().channel().remoteAddress()).hashCode(); String key = UUID.randomUUID().toString(); DefaultCookie cookie = new DefaultCookie(PALO_SESSION_ID, key); cookie.setMaxAge(PALO_SESSION_EXPIRED_TIME); @@ -212,7 +236,7 @@ public class WebBaseAction extends BaseAction { sb.append(NAVIGATION_BAR_PREFIX); // TODO(lingbin): maybe should change to register the menu item? - if (request.isAdmin()) { + if (request.isAuthorized()) { sb.append("
  • ") .append("system") .append("
  • "); diff --git a/fe/src/com/baidu/palo/http/meta/MetaBaseAction.java b/fe/src/com/baidu/palo/http/meta/MetaBaseAction.java index 4a1f9e4d26..6032e21d53 100644 --- a/fe/src/com/baidu/palo/http/meta/MetaBaseAction.java +++ b/fe/src/com/baidu/palo/http/meta/MetaBaseAction.java @@ -49,6 +49,11 @@ public class MetaBaseAction extends WebBaseAction { return false; } + @Override + public boolean needPassword() { + return false; + } + @Override public void execute(BaseRequest request, BaseResponse response) { if (needCheckClientIsFe()) { diff --git a/fe/src/com/baidu/palo/http/meta/MetaService.java b/fe/src/com/baidu/palo/http/meta/MetaService.java index 0b14b8ba03..40c0b8b319 100644 --- a/fe/src/com/baidu/palo/http/meta/MetaService.java +++ b/fe/src/com/baidu/palo/http/meta/MetaService.java @@ -321,7 +321,7 @@ public class MetaService { public void executeGet(BaseRequest request, BaseResponse response) { /* * Before dump, we acquired the catalog read lock and all databases' read lock and all - * the jobs' read lock. This will guarantee the consistance of database and job queues. + * the jobs' read lock. This will guarantee the consistency of database and job queues. * But Backend may still inconsistent. */ diff --git a/fe/src/com/baidu/palo/http/rest/BootstrapFinishAction.java b/fe/src/com/baidu/palo/http/rest/BootstrapFinishAction.java index 85b76f6de1..d7996b2f73 100644 --- a/fe/src/com/baidu/palo/http/rest/BootstrapFinishAction.java +++ b/fe/src/com/baidu/palo/http/rest/BootstrapFinishAction.java @@ -16,18 +16,11 @@ package com.baidu.palo.http.rest; import com.baidu.palo.catalog.Catalog; -import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.DdlException; -import com.baidu.palo.common.Pair; import com.baidu.palo.http.ActionController; import com.baidu.palo.http.BaseRequest; import com.baidu.palo.http.BaseResponse; import com.baidu.palo.http.IllegalArgException; -import com.baidu.palo.system.SystemInfoService; -import com.google.common.base.Strings; -import com.google.common.collect.Lists; - -import java.util.List; import io.netty.handler.codec.http.HttpMethod; @@ -50,7 +43,6 @@ public class BootstrapFinishAction extends RestBaseAction { @Override public void execute(BaseRequest request, BaseResponse response) throws DdlException { - boolean canRead = Catalog.getInstance().canRead(); // to json response diff --git a/fe/src/com/baidu/palo/http/rest/CheckDecommissionAction.java b/fe/src/com/baidu/palo/http/rest/CheckDecommissionAction.java index 9d03e3245e..affe0c7a2a 100644 --- a/fe/src/com/baidu/palo/http/rest/CheckDecommissionAction.java +++ b/fe/src/com/baidu/palo/http/rest/CheckDecommissionAction.java @@ -23,7 +23,9 @@ import com.baidu.palo.http.ActionController; import com.baidu.palo.http.BaseRequest; import com.baidu.palo.http.BaseResponse; import com.baidu.palo.http.IllegalArgException; +import com.baidu.palo.mysql.privilege.PrivPredicate; import com.baidu.palo.system.SystemInfoService; + import com.google.common.base.Strings; import com.google.common.collect.Lists; @@ -50,8 +52,9 @@ public class CheckDecommissionAction extends RestBaseAction { } @Override - public void execute(BaseRequest request, BaseResponse response) throws DdlException { - checkAdmin(request); + public void executeWithoutPassword(AuthorizationInfo authInfo, BaseRequest request, BaseResponse response) + throws DdlException { + checkGlobalAuth(authInfo, PrivPredicate.OPERATOR); String hostPorts = request.getSingleParameter(HOST_PORTS); if (Strings.isNullOrEmpty(hostPorts)) { diff --git a/fe/src/com/baidu/palo/http/rest/GetDdlStmtAction.java b/fe/src/com/baidu/palo/http/rest/GetDdlStmtAction.java index f090796d8a..67af99d0dc 100644 --- a/fe/src/com/baidu/palo/http/rest/GetDdlStmtAction.java +++ b/fe/src/com/baidu/palo/http/rest/GetDdlStmtAction.java @@ -23,6 +23,7 @@ import com.baidu.palo.http.ActionController; import com.baidu.palo.http.BaseRequest; import com.baidu.palo.http.BaseResponse; import com.baidu.palo.http.IllegalArgException; +import com.baidu.palo.mysql.privilege.PrivPredicate; import com.google.common.base.Strings; import com.google.common.collect.Lists; @@ -32,11 +33,11 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.codehaus.jackson.map.ObjectMapper; -import io.netty.handler.codec.http.HttpMethod; - import java.util.List; import java.util.Map; +import io.netty.handler.codec.http.HttpMethod; + /* * used to get a table's ddl stmt * eg: @@ -57,8 +58,9 @@ public class GetDdlStmtAction extends RestBaseAction { } @Override - public void execute(BaseRequest request, BaseResponse response) throws DdlException { - checkAdmin(request); + public void executeWithoutPassword(AuthorizationInfo authInfo, BaseRequest request, BaseResponse response) + throws DdlException { + checkGlobalAuth(authInfo, PrivPredicate.ADMIN); String dbName = request.getSingleParameter(DB_PARAM); String tableName = request.getSingleParameter(TABLE_PARAM); diff --git a/fe/src/com/baidu/palo/http/rest/GetLoadInfoAction.java b/fe/src/com/baidu/palo/http/rest/GetLoadInfoAction.java index 741b76022a..18e24caab0 100644 --- a/fe/src/com/baidu/palo/http/rest/GetLoadInfoAction.java +++ b/fe/src/com/baidu/palo/http/rest/GetLoadInfoAction.java @@ -15,13 +15,13 @@ package com.baidu.palo.http.rest; -import com.baidu.palo.cluster.ClusterNamespace; import com.baidu.palo.common.DdlException; import com.baidu.palo.http.ActionController; import com.baidu.palo.http.BaseRequest; import com.baidu.palo.http.BaseResponse; import com.baidu.palo.http.IllegalArgException; import com.baidu.palo.load.Load; +import com.baidu.palo.mysql.privilege.PrivPredicate; import com.google.common.base.Strings; @@ -43,8 +43,8 @@ public class GetLoadInfoAction extends RestBaseAction { } @Override - public void execute(BaseRequest request, BaseResponse response) throws DdlException { - AuthorizationInfo authInfo = getAuthorizationInfo(request); + public void executeWithoutPassword(AuthorizationInfo authInfo, BaseRequest request, BaseResponse response) + throws DdlException { Load.JobInfo info = new Load.JobInfo(request.getSingleParameter(DB_KEY), request.getSingleParameter(LABEL_KEY), @@ -59,13 +59,19 @@ public class GetLoadInfoAction extends RestBaseAction { throw new DdlException("No cluster name selected"); } - String fullDbName = ClusterNamespace.getFullName(info.clusterName, info.dbName); - checkReadPriv(authInfo.fullUserName, fullDbName); - if (redirectToMaster(request, response)) { return; } catalog.getLoadInstance().getJobInfo(info); + + if (info.tblNames.isEmpty()) { + checkDbAuth(authInfo, info.dbName, PrivPredicate.LOAD); + } else { + for (String tblName : info.tblNames) { + checkTblAuth(authInfo, info.dbName, tblName, PrivPredicate.LOAD); + } + } + sendResult(request, response, new Result(info)); } diff --git a/fe/src/com/baidu/palo/http/rest/LoadAction.java b/fe/src/com/baidu/palo/http/rest/LoadAction.java index b9e88d1b97..393dbbad73 100644 --- a/fe/src/com/baidu/palo/http/rest/LoadAction.java +++ b/fe/src/com/baidu/palo/http/rest/LoadAction.java @@ -22,6 +22,7 @@ import com.baidu.palo.http.ActionController; import com.baidu.palo.http.BaseRequest; import com.baidu.palo.http.BaseResponse; import com.baidu.palo.http.IllegalArgException; +import com.baidu.palo.mysql.privilege.PrivPredicate; import com.baidu.palo.service.ExecuteEnv; import com.baidu.palo.system.Backend; import com.baidu.palo.thrift.TNetworkAddress; @@ -93,7 +94,9 @@ public class LoadAction extends RestBaseAction { throw new DdlException("No label selected."); } - checkWritePriv(authInfo.fullUserName, fullDbName); + // check auth + checkTblAuth(authInfo, fullDbName, tableName, PrivPredicate.LOAD); + // Try to redirect to master if (redirectToMaster(request, response)) { return; @@ -102,12 +105,12 @@ public class LoadAction extends RestBaseAction { // Choose a backend sequentially. List backendIds = Catalog.getCurrentSystemInfo().seqChooseBackendIds(1, true, false, clusterName); if (backendIds == null) { - throw new DdlException("No live backend."); + throw new DdlException("No backend alive."); } Backend backend = Catalog.getCurrentSystemInfo().getBackend(backendIds.get(0)); if (backend == null) { - throw new DdlException("No live backend."); + throw new DdlException("No backend alive."); } TNetworkAddress redirectAddr = new TNetworkAddress(backend.getHost(), backend.getHttpPort()); @@ -116,8 +119,6 @@ public class LoadAction extends RestBaseAction { } LOG.info("mini load redirect to backend: {}, label: {}", redirectAddr.toString(), label); - LOG.info("redrect address is {}, {}", backend.getHost(), backend.getHttpPort()); - redirectTo(request, response, redirectAddr); } } diff --git a/fe/src/com/baidu/palo/http/rest/MetaReplayerCheckAction.java b/fe/src/com/baidu/palo/http/rest/MetaReplayerCheckAction.java index 7198f37cb4..9b084227d0 100644 --- a/fe/src/com/baidu/palo/http/rest/MetaReplayerCheckAction.java +++ b/fe/src/com/baidu/palo/http/rest/MetaReplayerCheckAction.java @@ -21,13 +21,14 @@ import com.baidu.palo.http.ActionController; import com.baidu.palo.http.BaseRequest; import com.baidu.palo.http.BaseResponse; import com.baidu.palo.http.IllegalArgException; +import com.baidu.palo.mysql.privilege.PrivPredicate; import org.codehaus.jackson.map.ObjectMapper; -import io.netty.handler.codec.http.HttpMethod; - import java.util.Map; +import io.netty.handler.codec.http.HttpMethod; + /* * used to get meta replay info * eg: @@ -45,7 +46,8 @@ public class MetaReplayerCheckAction extends RestBaseAction { @Override public void execute(BaseRequest request, BaseResponse response) throws DdlException { - checkAdmin(request); + AuthorizationInfo authInfo = getAuthorizationInfo(request); + checkGlobalAuth(authInfo, PrivPredicate.ADMIN); Map resultMap = Catalog.getInstance().getMetaReplayState().getInfo(); diff --git a/fe/src/com/baidu/palo/http/rest/MigrationAction.java b/fe/src/com/baidu/palo/http/rest/MigrationAction.java index adaa9ddd82..0a7d4ae9c7 100644 --- a/fe/src/com/baidu/palo/http/rest/MigrationAction.java +++ b/fe/src/com/baidu/palo/http/rest/MigrationAction.java @@ -30,6 +30,7 @@ import com.baidu.palo.http.ActionController; import com.baidu.palo.http.BaseRequest; import com.baidu.palo.http.BaseResponse; import com.baidu.palo.http.IllegalArgException; +import com.baidu.palo.mysql.privilege.PrivPredicate; import com.google.common.base.Strings; import com.google.common.collect.Lists; @@ -38,11 +39,11 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.codehaus.jackson.map.ObjectMapper; -import io.netty.handler.codec.http.HttpMethod; - import java.util.Collections; import java.util.List; +import io.netty.handler.codec.http.HttpMethod; + /* * used to get table's sorted tablet info * eg: @@ -64,7 +65,8 @@ public class MigrationAction extends RestBaseAction { @Override public void execute(BaseRequest request, BaseResponse response) throws DdlException { - checkAdmin(request); + AuthorizationInfo authInfo = getAuthorizationInfo(request); + checkGlobalAuth(authInfo, PrivPredicate.ADMIN); String dbName = request.getSingleParameter(DB_PARAM); String tableName = request.getSingleParameter(TABLE_PARAM); diff --git a/fe/src/com/baidu/palo/http/rest/MultiAbort.java b/fe/src/com/baidu/palo/http/rest/MultiAbort.java index 3381a88a5e..ff5e407ab7 100644 --- a/fe/src/com/baidu/palo/http/rest/MultiAbort.java +++ b/fe/src/com/baidu/palo/http/rest/MultiAbort.java @@ -21,6 +21,7 @@ import com.baidu.palo.http.ActionController; import com.baidu.palo.http.BaseRequest; import com.baidu.palo.http.BaseResponse; import com.baidu.palo.http.IllegalArgException; +import com.baidu.palo.mysql.privilege.PrivPredicate; import com.baidu.palo.service.ExecuteEnv; import com.google.common.base.Strings; @@ -57,8 +58,8 @@ public class MultiAbort extends RestBaseAction { AuthorizationInfo authInfo = getAuthorizationInfo(request); String fullDbName = ClusterNamespace.getFullName(authInfo.cluster, db); + checkDbAuth(authInfo, fullDbName, PrivPredicate.LOAD); - checkWritePriv(authInfo.fullUserName, fullDbName); if (redirectToMaster(request, response)) { return; } diff --git a/fe/src/com/baidu/palo/http/rest/MultiCommit.java b/fe/src/com/baidu/palo/http/rest/MultiCommit.java index d9fd0c7fd3..7ef45a944e 100644 --- a/fe/src/com/baidu/palo/http/rest/MultiCommit.java +++ b/fe/src/com/baidu/palo/http/rest/MultiCommit.java @@ -21,6 +21,7 @@ import com.baidu.palo.http.ActionController; import com.baidu.palo.http.BaseRequest; import com.baidu.palo.http.BaseResponse; import com.baidu.palo.http.IllegalArgException; +import com.baidu.palo.mysql.privilege.PrivPredicate; import com.baidu.palo.service.ExecuteEnv; import com.google.common.base.Strings; @@ -57,8 +58,8 @@ public class MultiCommit extends RestBaseAction { AuthorizationInfo authInfo = getAuthorizationInfo(request); String fullDbName = ClusterNamespace.getFullName(authInfo.cluster, db); + checkDbAuth(authInfo, fullDbName, PrivPredicate.LOAD); - checkWritePriv(authInfo.fullUserName, fullDbName); if (redirectToMaster(request, response)) { return; } diff --git a/fe/src/com/baidu/palo/http/rest/MultiDesc.java b/fe/src/com/baidu/palo/http/rest/MultiDesc.java index 50247faa70..ba2dbf4d3b 100644 --- a/fe/src/com/baidu/palo/http/rest/MultiDesc.java +++ b/fe/src/com/baidu/palo/http/rest/MultiDesc.java @@ -21,6 +21,7 @@ import com.baidu.palo.http.ActionController; import com.baidu.palo.http.BaseRequest; import com.baidu.palo.http.BaseResponse; import com.baidu.palo.http.IllegalArgException; +import com.baidu.palo.mysql.privilege.PrivPredicate; import com.baidu.palo.service.ExecuteEnv; import com.google.common.base.Strings; @@ -61,7 +62,7 @@ public class MultiDesc extends RestBaseAction { AuthorizationInfo authInfo = getAuthorizationInfo(request); String fullDbName = ClusterNamespace.getFullName(authInfo.cluster, db); - checkReadPriv(authInfo.fullUserName, fullDbName); + checkDbAuth(authInfo, fullDbName, PrivPredicate.LOAD); if (redirectToMaster(request, response)) { return; diff --git a/fe/src/com/baidu/palo/http/rest/MultiList.java b/fe/src/com/baidu/palo/http/rest/MultiList.java index c1b99f7687..329a40fcae 100644 --- a/fe/src/com/baidu/palo/http/rest/MultiList.java +++ b/fe/src/com/baidu/palo/http/rest/MultiList.java @@ -21,6 +21,7 @@ import com.baidu.palo.http.ActionController; import com.baidu.palo.http.BaseRequest; import com.baidu.palo.http.BaseResponse; import com.baidu.palo.http.IllegalArgException; +import com.baidu.palo.mysql.privilege.PrivPredicate; import com.baidu.palo.service.ExecuteEnv; import com.google.common.base.Strings; @@ -56,7 +57,7 @@ public class MultiList extends RestBaseAction { AuthorizationInfo authInfo = getAuthorizationInfo(request); String fullDbName = ClusterNamespace.getFullName(authInfo.cluster, db); - checkReadPriv(authInfo.fullUserName, fullDbName); + checkDbAuth(authInfo, fullDbName, PrivPredicate.LOAD); if (redirectToMaster(request, response)) { return; diff --git a/fe/src/com/baidu/palo/http/rest/MultiStart.java b/fe/src/com/baidu/palo/http/rest/MultiStart.java index b8ef9286aa..f557993e47 100644 --- a/fe/src/com/baidu/palo/http/rest/MultiStart.java +++ b/fe/src/com/baidu/palo/http/rest/MultiStart.java @@ -22,6 +22,7 @@ import com.baidu.palo.http.ActionController; import com.baidu.palo.http.BaseRequest; import com.baidu.palo.http.BaseResponse; import com.baidu.palo.http.IllegalArgException; +import com.baidu.palo.mysql.privilege.PrivPredicate; import com.baidu.palo.service.ExecuteEnv; import com.google.common.base.Strings; @@ -62,7 +63,7 @@ public class MultiStart extends RestBaseAction { AuthorizationInfo authInfo = getAuthorizationInfo(request); String fullDbName = ClusterNamespace.getFullName(authInfo.cluster, db); - checkWritePriv(authInfo.fullUserName, fullDbName); + checkDbAuth(authInfo, fullDbName, PrivPredicate.LOAD); if (redirectToMaster(request, response)) { return; diff --git a/fe/src/com/baidu/palo/http/rest/MultiUnload.java b/fe/src/com/baidu/palo/http/rest/MultiUnload.java index d1c52654ec..218d440c90 100644 --- a/fe/src/com/baidu/palo/http/rest/MultiUnload.java +++ b/fe/src/com/baidu/palo/http/rest/MultiUnload.java @@ -21,6 +21,7 @@ import com.baidu.palo.http.ActionController; import com.baidu.palo.http.BaseRequest; import com.baidu.palo.http.BaseResponse; import com.baidu.palo.http.IllegalArgException; +import com.baidu.palo.mysql.privilege.PrivPredicate; import com.baidu.palo.service.ExecuteEnv; import com.google.common.base.Strings; @@ -62,7 +63,7 @@ public class MultiUnload extends RestBaseAction { AuthorizationInfo authInfo = getAuthorizationInfo(request); String fullDbName = ClusterNamespace.getFullName(authInfo.cluster, db); - checkWritePriv(authInfo.fullUserName, fullDbName); + checkDbAuth(authInfo, fullDbName, PrivPredicate.LOAD); if (redirectToMaster(request, response)) { return; diff --git a/fe/src/com/baidu/palo/http/rest/RestBaseAction.java b/fe/src/com/baidu/palo/http/rest/RestBaseAction.java index b81c784242..201f4b82ba 100644 --- a/fe/src/com/baidu/palo/http/rest/RestBaseAction.java +++ b/fe/src/com/baidu/palo/http/rest/RestBaseAction.java @@ -57,7 +57,16 @@ public class RestBaseAction extends BaseAction { @Override public void execute(BaseRequest request, BaseResponse response) throws DdlException { - throw new DdlException("Do not implemented."); + AuthorizationInfo authInfo = getAuthorizationInfo(request); + // check password + checkPassword(authInfo); + executeWithoutPassword(authInfo, request, response); + } + + // all derived classed should implement this method, NOT 'execute' + protected void executeWithoutPassword(AuthorizationInfo authInfo, BaseRequest request, BaseResponse response) + throws DdlException { + throw new DdlException("Not implemented"); } public void sendResult(BaseRequest request, BaseResponse response, RestBaseResult result) { diff --git a/fe/src/com/baidu/palo/http/rest/RowCountAction.java b/fe/src/com/baidu/palo/http/rest/RowCountAction.java index 50c94fa828..d1938e6ed9 100644 --- a/fe/src/com/baidu/palo/http/rest/RowCountAction.java +++ b/fe/src/com/baidu/palo/http/rest/RowCountAction.java @@ -22,13 +22,14 @@ import com.baidu.palo.catalog.OlapTable; import com.baidu.palo.catalog.Partition; import com.baidu.palo.catalog.Replica; import com.baidu.palo.catalog.Table; -import com.baidu.palo.catalog.Tablet; import com.baidu.palo.catalog.Table.TableType; +import com.baidu.palo.catalog.Tablet; import com.baidu.palo.common.DdlException; import com.baidu.palo.http.ActionController; import com.baidu.palo.http.BaseRequest; import com.baidu.palo.http.BaseResponse; import com.baidu.palo.http.IllegalArgException; +import com.baidu.palo.mysql.privilege.PrivPredicate; import com.google.common.base.Strings; import com.google.common.collect.Maps; @@ -57,6 +58,9 @@ public class RowCountAction extends RestBaseAction { @Override public void execute(BaseRequest request, BaseResponse response) throws DdlException { + AuthorizationInfo authInfo = getAuthorizationInfo(request); + checkGlobalAuth(authInfo, PrivPredicate.ADMIN); + String dbName = request.getSingleParameter(DB_NAME_PARAM); if (Strings.isNullOrEmpty(dbName)) { throw new DdlException("No database selected."); @@ -66,8 +70,6 @@ public class RowCountAction extends RestBaseAction { if (Strings.isNullOrEmpty(tableName)) { throw new DdlException("No table selected."); } - - checkAdmin(request); Map indexRowCountMap = Maps.newHashMap(); Catalog catalog = Catalog.getInstance(); diff --git a/fe/src/com/baidu/palo/http/rest/SetConfigAction.java b/fe/src/com/baidu/palo/http/rest/SetConfigAction.java index 893f871c48..6923b44b56 100644 --- a/fe/src/com/baidu/palo/http/rest/SetConfigAction.java +++ b/fe/src/com/baidu/palo/http/rest/SetConfigAction.java @@ -16,12 +16,13 @@ package com.baidu.palo.http.rest; import com.baidu.palo.common.ConfigBase; -import com.baidu.palo.common.DdlException; import com.baidu.palo.common.ConfigBase.ConfField; +import com.baidu.palo.common.DdlException; import com.baidu.palo.http.ActionController; import com.baidu.palo.http.BaseRequest; import com.baidu.palo.http.BaseResponse; import com.baidu.palo.http.IllegalArgException; +import com.baidu.palo.mysql.privilege.PrivPredicate; import com.google.common.collect.Maps; @@ -29,12 +30,12 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.codehaus.jackson.map.ObjectMapper; -import io.netty.handler.codec.http.HttpMethod; - import java.lang.reflect.Field; import java.util.List; import java.util.Map; +import io.netty.handler.codec.http.HttpMethod; + /* * used to set fe config * eg: @@ -54,7 +55,8 @@ public class SetConfigAction extends RestBaseAction { @Override public void execute(BaseRequest request, BaseResponse response) throws DdlException { - checkAdmin(request); + AuthorizationInfo authInfo = getAuthorizationInfo(request); + checkGlobalAuth(authInfo, PrivPredicate.ADMIN); Map> configs = request.getAllParameters(); Map setConfigs = Maps.newHashMap(); diff --git a/fe/src/com/baidu/palo/http/rest/ShowProcAction.java b/fe/src/com/baidu/palo/http/rest/ShowProcAction.java index e268b8df40..c2c2ccb347 100644 --- a/fe/src/com/baidu/palo/http/rest/ShowProcAction.java +++ b/fe/src/com/baidu/palo/http/rest/ShowProcAction.java @@ -24,6 +24,7 @@ import com.baidu.palo.http.BaseRequest; import com.baidu.palo.http.BaseResponse; import com.baidu.palo.http.IllegalArgException; import com.baidu.palo.http.UnauthorizedException; +import com.baidu.palo.mysql.privilege.PrivPredicate; import com.google.common.base.Strings; import com.google.gson.Gson; @@ -31,10 +32,10 @@ import com.google.gson.Gson; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import io.netty.handler.codec.http.HttpMethod; - import java.util.List; +import io.netty.handler.codec.http.HttpMethod; + // Format: // http://username:password@10.73.150.30:8138/api/show_proc?path=/ public class ShowProcAction extends RestBaseAction { @@ -52,8 +53,8 @@ public class ShowProcAction extends RestBaseAction { public void execute(BaseRequest request, BaseResponse response) { // check authority try { - checkAdmin(request); - request.setAdmin(true); + AuthorizationInfo authInfo = getAuthorizationInfo(request); + checkGlobalAuth(authInfo, PrivPredicate.ADMIN); } catch (UnauthorizedException e) { response.appendContent("Authentication Failed. " + e.getMessage()); sendResult(request, response); diff --git a/fe/src/com/baidu/palo/http/rest/StorageTypeCheckAction.java b/fe/src/com/baidu/palo/http/rest/StorageTypeCheckAction.java new file mode 100644 index 0000000000..c442c5c039 --- /dev/null +++ b/fe/src/com/baidu/palo/http/rest/StorageTypeCheckAction.java @@ -0,0 +1,82 @@ +package com.baidu.palo.http.rest; + +import com.baidu.palo.catalog.Database; +import com.baidu.palo.catalog.OlapTable; +import com.baidu.palo.catalog.Table; +import com.baidu.palo.catalog.Table.TableType; +import com.baidu.palo.cluster.ClusterNamespace; +import com.baidu.palo.common.DdlException; +import com.baidu.palo.http.ActionController; +import com.baidu.palo.http.BaseRequest; +import com.baidu.palo.http.BaseResponse; +import com.baidu.palo.http.IllegalArgException; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.thrift.TStorageType; + +import com.google.common.base.Strings; + +import org.json.JSONObject; + +import java.util.List; +import java.util.Map; + +import io.netty.handler.codec.http.HttpMethod; + +public class StorageTypeCheckAction extends RestBaseAction { + public StorageTypeCheckAction(ActionController controller) { + super(controller); + } + + public static void registerAction(ActionController controller) throws IllegalArgException { + StorageTypeCheckAction action = new StorageTypeCheckAction(controller); + controller.registerHandler(HttpMethod.GET, "/api/_check_storagetype", action); + } + + @Override + public void execute(BaseRequest request, BaseResponse response) throws DdlException { + AuthorizationInfo authInfo = getAuthorizationInfo(request); + checkGlobalAuth(authInfo, PrivPredicate.ADMIN); + + String dbName = request.getSingleParameter("db"); + if (Strings.isNullOrEmpty(dbName)) { + throw new DdlException("Parameter db is missing"); + } + + String fullDbName = ClusterNamespace.getFullName(authInfo.cluster, dbName); + Database db = catalog.getDb(fullDbName); + if (db == null) { + throw new DdlException("Database " + dbName + " does not exist"); + } + + JSONObject root = new JSONObject(); + db.readLock(); + try { + List tbls = db.getTables(); + for (Table tbl : tbls) { + if (tbl.getType() != TableType.OLAP) { + continue; + } + + OlapTable olapTbl = (OlapTable) tbl; + JSONObject indexObj = new JSONObject(); + for (Map.Entry entry : olapTbl.getIndexIdToStorageType().entrySet()) { + if (entry.getValue() == TStorageType.ROW) { + String idxName = olapTbl.getIndexNameById(entry.getKey()); + indexObj.put(idxName, entry.getValue().name()); + } + } + root.put(tbl.getName(), indexObj); + } + } finally { + db.readUnlock(); + } + + // to json response + String result = root.toString(); + + // send result + response.setContentType("application/json"); + response.getContent().append(result); + sendResult(request, response); + } +} diff --git a/fe/src/com/baidu/palo/journal/JournalEntity.java b/fe/src/com/baidu/palo/journal/JournalEntity.java index 8d5a4e44c1..382b0702eb 100644 --- a/fe/src/com/baidu/palo/journal/JournalEntity.java +++ b/fe/src/com/baidu/palo/journal/JournalEntity.java @@ -16,11 +16,14 @@ package com.baidu.palo.journal; import com.baidu.palo.alter.AlterJob; +import com.baidu.palo.analysis.UserIdentity; import com.baidu.palo.backup.BackupJob; +import com.baidu.palo.backup.BackupJob_D; +import com.baidu.palo.backup.Repository; import com.baidu.palo.backup.RestoreJob; +import com.baidu.palo.backup.RestoreJob_D; import com.baidu.palo.catalog.BrokerMgr; import com.baidu.palo.catalog.Database; -import com.baidu.palo.catalog.UserProperty; import com.baidu.palo.cluster.BaseParam; import com.baidu.palo.cluster.Cluster; import com.baidu.palo.common.io.Text; @@ -33,6 +36,7 @@ import com.baidu.palo.load.ExportJob; import com.baidu.palo.load.LoadErrorHub; import com.baidu.palo.load.LoadJob; import com.baidu.palo.master.Checkpoint; +import com.baidu.palo.mysql.privilege.UserProperty; import com.baidu.palo.persist.BackendIdsUpdateInfo; import com.baidu.palo.persist.CloneInfo; import com.baidu.palo.persist.ClusterInfo; @@ -45,6 +49,7 @@ import com.baidu.palo.persist.DropPartitionInfo; import com.baidu.palo.persist.ModifyPartitionInfo; import com.baidu.palo.persist.OperationType; import com.baidu.palo.persist.PartitionPersistInfo; +import com.baidu.palo.persist.PrivInfo; import com.baidu.palo.persist.RecoverInfo; import com.baidu.palo.persist.ReplicaPersistInfo; import com.baidu.palo.persist.TableInfo; @@ -176,12 +181,22 @@ public class JournalEntity implements Writable { case OperationType.OP_BACKUP_START: case OperationType.OP_BACKUP_FINISH_SNAPSHOT: case OperationType.OP_BACKUP_FINISH: { - data = new BackupJob(); + data = new BackupJob_D(); break; } case OperationType.OP_RESTORE_START: case OperationType.OP_RESTORE_FINISH: { - data = new RestoreJob(); + data = new RestoreJob_D(); + break; + } + case OperationType.OP_BACKUP_JOB: { + data = BackupJob.read(in); + needRead = false; + break; + } + case OperationType.OP_RESTORE_JOB: { + data = RestoreJob.read(in); + needRead = false; break; } case OperationType.OP_FINISH_CONSISTENCY_CHECK: { @@ -245,6 +260,21 @@ public class JournalEntity implements Writable { data = new Text(); break; } + case OperationType.OP_NEW_DROP_USER: { + data = UserIdentity.read(in); + needRead = false; + break; + } + case OperationType.OP_CREATE_USER: + case OperationType.OP_GRANT_PRIV: + case OperationType.OP_REVOKE_PRIV: + case OperationType.OP_SET_PASSWORD: + case OperationType.OP_CREATE_ROLE: + case OperationType.OP_DROP_ROLE: { + data = PrivInfo.read(in); + needRead = false; + break; + } case OperationType.OP_MASTER_INFO_CHANGE: { data = new MasterInfo(); break; @@ -306,6 +336,15 @@ public class JournalEntity implements Writable { data = new BackendIdsUpdateInfo(); break; } + case OperationType.OP_CREATE_REPOSITORY: { + data = Repository.read(in); + needRead = false; + break; + } + case OperationType.OP_DROP_REPOSITORY: { + data = new Text(); + break; + } default: { IOException e = new IOException(); LOG.error("UNKNOWN Operation Type {}", opCode, e); diff --git a/fe/src/com/baidu/palo/journal/bdbje/BDBEnvironment.java b/fe/src/com/baidu/palo/journal/bdbje/BDBEnvironment.java index 4b5e019bf5..f254a5ef7d 100644 --- a/fe/src/com/baidu/palo/journal/bdbje/BDBEnvironment.java +++ b/fe/src/com/baidu/palo/journal/bdbje/BDBEnvironment.java @@ -13,8 +13,8 @@ // specific language governing permissions and limitations // under the License. -package com.baidu.palo.journal.bdbje; - +package com.baidu.palo.journal.bdbje; + import com.baidu.palo.catalog.Catalog; import com.baidu.palo.common.Config; import com.baidu.palo.ha.BDBHA; @@ -52,175 +52,175 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -/* this class contains the reference to bdb environment. - * including all the opened databases and the replicationGroupAdmin. - * we can get the information of this bdb group through the API of replicationGroupAdmin - */ -public class BDBEnvironment { - private static final Logger LOG = LogManager.getLogger(BDBEnvironment.class); - private static final int RETRY_TIME = 3; - private static final int MEMORY_CACHE_PERCENT = 20; - - public static final String PALO_JOURNAL_GROUP = "PALO_JOURNAL_GROUP"; - - private ReplicatedEnvironment replicatedEnvironment; - private EnvironmentConfig environmentConfig; - private ReplicationConfig replicationConfig; - private DatabaseConfig dbConfig; - private Database epochDB = null; // used for fencing - private ReplicationGroupAdmin replicationGroupAdmin = null; - private ReentrantReadWriteLock lock; - private List openedDatabases; - - public BDBEnvironment() { - openedDatabases = new ArrayList(); - this.lock = new ReentrantReadWriteLock(true); - } - - // The setup() method opens the environment and database - public void setup(File envHome, String selfNodeName, String selfNodeHostPort, - String helperHostPort, boolean isElectable) { - - // Almost never used, just in case the master can not restart - if (Config.metadata_failure_recovery.equals("true")) { - if (!isElectable) { - LOG.error("Current node is not in the electable_nodes list. will exit"); - System.exit(-1); - } - DbResetRepGroup resetUtility = new DbResetRepGroup(envHome, PALO_JOURNAL_GROUP, selfNodeName, - selfNodeHostPort); - resetUtility.reset(); - LOG.info("group has been reset."); +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/* this class contains the reference to bdb environment. + * including all the opened databases and the replicationGroupAdmin. + * we can get the information of this bdb group through the API of replicationGroupAdmin + */ +public class BDBEnvironment { + private static final Logger LOG = LogManager.getLogger(BDBEnvironment.class); + private static final int RETRY_TIME = 3; + private static final int MEMORY_CACHE_PERCENT = 20; + + public static final String PALO_JOURNAL_GROUP = "PALO_JOURNAL_GROUP"; + + private ReplicatedEnvironment replicatedEnvironment; + private EnvironmentConfig environmentConfig; + private ReplicationConfig replicationConfig; + private DatabaseConfig dbConfig; + private Database epochDB = null; // used for fencing + private ReplicationGroupAdmin replicationGroupAdmin = null; + private ReentrantReadWriteLock lock; + private List openedDatabases; + + public BDBEnvironment() { + openedDatabases = new ArrayList(); + this.lock = new ReentrantReadWriteLock(true); + } + + // The setup() method opens the environment and database + public void setup(File envHome, String selfNodeName, String selfNodeHostPort, + String helperHostPort, boolean isElectable) { + + // Almost never used, just in case the master can not restart + if (Config.metadata_failure_recovery.equals("true")) { + if (!isElectable) { + LOG.error("Current node is not in the electable_nodes list. will exit"); + System.exit(-1); + } + DbResetRepGroup resetUtility = new DbResetRepGroup(envHome, PALO_JOURNAL_GROUP, selfNodeName, + selfNodeHostPort); + resetUtility.reset(); + LOG.info("group has been reset."); } - - // set replication config - replicationConfig = new ReplicationConfig(); - replicationConfig.setNodeName(selfNodeName); - replicationConfig.setNodeHostPort(selfNodeHostPort); - replicationConfig.setHelperHosts(helperHostPort); - replicationConfig.setGroupName(PALO_JOURNAL_GROUP); + + // set replication config + replicationConfig = new ReplicationConfig(); + replicationConfig.setNodeName(selfNodeName); + replicationConfig.setNodeHostPort(selfNodeHostPort); + replicationConfig.setHelperHosts(helperHostPort); + replicationConfig.setGroupName(PALO_JOURNAL_GROUP); replicationConfig.setConfigParam(ReplicationConfig.ENV_UNKNOWN_STATE_TIMEOUT, "10"); replicationConfig.setMaxClockDelta(Config.max_bdbje_clock_delta_ms, TimeUnit.MILLISECONDS); - - if (isElectable) { - replicationConfig.setReplicaAckTimeout(2, TimeUnit.SECONDS); + + if (isElectable) { + replicationConfig.setReplicaAckTimeout(2, TimeUnit.SECONDS); replicationConfig.setConfigParam(ReplicationConfig.REPLICA_MAX_GROUP_COMMIT, "0"); - replicationConfig.setConsistencyPolicy(new NoConsistencyRequiredPolicy()); - } else { - replicationConfig.setNodeType(NodeType.SECONDARY); - replicationConfig.setConsistencyPolicy(new NoConsistencyRequiredPolicy()); - } - - // set environment config - environmentConfig = new EnvironmentConfig(); - environmentConfig.setTransactional(true); - environmentConfig.setAllowCreate(true); - environmentConfig.setCachePercent(MEMORY_CACHE_PERCENT); - if (isElectable) { - Durability durability = new Durability(getSyncPolicy(Config.master_sync_policy), - getSyncPolicy(Config.replica_sync_policy), getAckPolicy(Config.replica_ack_policy)); - environmentConfig.setDurability(durability); - } - - // set database config - dbConfig = new DatabaseConfig(); - dbConfig.setTransactional(true); - if (isElectable) { - dbConfig.setAllowCreate(true); - dbConfig.setReadOnly(false); - } else { - dbConfig.setAllowCreate(false); - dbConfig.setReadOnly(true); - } - - // open environment and epochDB - for (int i = 0; i < RETRY_TIME; i++) { - try { - // open the environment - replicatedEnvironment = new ReplicatedEnvironment(envHome, replicationConfig, environmentConfig); - - // get replicationGroupAdmin object. - Set adminNodes = new HashSet(); - // 1. add helper node - InetSocketAddress helper = new InetSocketAddress(helperHostPort.split(":")[0], - Integer.parseInt(helperHostPort.split(":")[1])); - adminNodes.add(helper); - LOG.info("add helper[{}] as ReplicationGroupAdmin", helperHostPort); - // 2. add self if is electable - if (!selfNodeHostPort.equals(helperHostPort) && Catalog.getInstance().isElectable()) { - InetSocketAddress self = new InetSocketAddress(selfNodeHostPort.split(":")[0], - Integer.parseInt(selfNodeHostPort.split(":")[1])); - adminNodes.add(self); - LOG.info("add self[{}] as ReplicationGroupAdmin", selfNodeHostPort); - } - + replicationConfig.setConsistencyPolicy(new NoConsistencyRequiredPolicy()); + } else { + replicationConfig.setNodeType(NodeType.SECONDARY); + replicationConfig.setConsistencyPolicy(new NoConsistencyRequiredPolicy()); + } + + // set environment config + environmentConfig = new EnvironmentConfig(); + environmentConfig.setTransactional(true); + environmentConfig.setAllowCreate(true); + environmentConfig.setCachePercent(MEMORY_CACHE_PERCENT); + if (isElectable) { + Durability durability = new Durability(getSyncPolicy(Config.master_sync_policy), + getSyncPolicy(Config.replica_sync_policy), getAckPolicy(Config.replica_ack_policy)); + environmentConfig.setDurability(durability); + } + + // set database config + dbConfig = new DatabaseConfig(); + dbConfig.setTransactional(true); + if (isElectable) { + dbConfig.setAllowCreate(true); + dbConfig.setReadOnly(false); + } else { + dbConfig.setAllowCreate(false); + dbConfig.setReadOnly(true); + } + + // open environment and epochDB + for (int i = 0; i < RETRY_TIME; i++) { + try { + // open the environment + replicatedEnvironment = new ReplicatedEnvironment(envHome, replicationConfig, environmentConfig); + + // get replicationGroupAdmin object. + Set adminNodes = new HashSet(); + // 1. add helper node + InetSocketAddress helper = new InetSocketAddress(helperHostPort.split(":")[0], + Integer.parseInt(helperHostPort.split(":")[1])); + adminNodes.add(helper); + LOG.info("add helper[{}] as ReplicationGroupAdmin", helperHostPort); + // 2. add self if is electable + if (!selfNodeHostPort.equals(helperHostPort) && Catalog.getInstance().isElectable()) { + InetSocketAddress self = new InetSocketAddress(selfNodeHostPort.split(":")[0], + Integer.parseInt(selfNodeHostPort.split(":")[1])); + adminNodes.add(self); + LOG.info("add self[{}] as ReplicationGroupAdmin", selfNodeHostPort); + } + replicationGroupAdmin = new ReplicationGroupAdmin(PALO_JOURNAL_GROUP, adminNodes); - - // get a BDBHA object and pass the reference to Catalog - HAProtocol protocol = new BDBHA(this, selfNodeName); - Catalog.getInstance().setHaProtocol(protocol); - - // start state change listener - StateChangeListener listener = new BDBStateChangeListener(); + + // get a BDBHA object and pass the reference to Catalog + HAProtocol protocol = new BDBHA(this, selfNodeName); + Catalog.getInstance().setHaProtocol(protocol); + + // start state change listener + StateChangeListener listener = new BDBStateChangeListener(); replicatedEnvironment.setStateChangeListener(listener); - - // open epochDB. the first parameter null means auto-commit - epochDB = replicatedEnvironment.openDatabase(null, "epochDB", dbConfig); - break; - } catch (InsufficientLogException insufficientLogEx) { - NetworkRestore restore = new NetworkRestore(); - NetworkRestoreConfig config = new NetworkRestoreConfig(); - config.setRetainLogFiles(false); // delete obsolete log files. - // Use the members returned by insufficientLogEx.getLogProviders() - // to select the desired subset of members and pass the resulting - // list as the argument to config.setLogProviders(), if the - // default selection of providers is not suitable. - restore.execute(insufficientLogEx, config); - continue; - } catch (DatabaseException e) { - if (i < RETRY_TIME - 1) { - try { - Thread.sleep(5 * 1000); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - continue; - } else { - LOG.error("error to open replicated environment. will exit.", e); - System.exit(-1); - } - } - } - } - - public ReplicationGroupAdmin getReplicationGroupAdmin() { - return this.replicationGroupAdmin; + + // open epochDB. the first parameter null means auto-commit + epochDB = replicatedEnvironment.openDatabase(null, "epochDB", dbConfig); + break; + } catch (InsufficientLogException insufficientLogEx) { + NetworkRestore restore = new NetworkRestore(); + NetworkRestoreConfig config = new NetworkRestoreConfig(); + config.setRetainLogFiles(false); // delete obsolete log files. + // Use the members returned by insufficientLogEx.getLogProviders() + // to select the desired subset of members and pass the resulting + // list as the argument to config.setLogProviders(), if the + // default selection of providers is not suitable. + restore.execute(insufficientLogEx, config); + continue; + } catch (DatabaseException e) { + if (i < RETRY_TIME - 1) { + try { + Thread.sleep(5 * 1000); + } catch (InterruptedException e1) { + e1.printStackTrace(); + } + continue; + } else { + LOG.error("error to open replicated environment. will exit.", e); + System.exit(-1); + } + } + } + } + + public ReplicationGroupAdmin getReplicationGroupAdmin() { + return this.replicationGroupAdmin; } public void setNewReplicationGroupAdmin(Set newHelperNodes) { this.replicationGroupAdmin = new ReplicationGroupAdmin(PALO_JOURNAL_GROUP, newHelperNodes); } - - // Return a handle to the epochDB - public Database getEpochDB() { - return epochDB; - } - - // Return a handle to the environment + + // Return a handle to the epochDB + public Database getEpochDB() { + return epochDB; + } + + // Return a handle to the environment public ReplicatedEnvironment getReplicatedEnvironment() { - return replicatedEnvironment; - } - + return replicatedEnvironment; + } + // return the database reference with the given name - // also try to close previous opened database. - public Database openDatabase(String dbName) { - Database db = null; - lock.writeLock().lock(); + // also try to close previous opened database. + public Database openDatabase(String dbName) { + Database db = null; + lock.writeLock().lock(); try { - // find if the specified database is already opened. find and return it. - for (java.util.Iterator iter = openedDatabases.iterator(); iter.hasNext();) { + // find if the specified database is already opened. find and return it. + for (java.util.Iterator iter = openedDatabases.iterator(); iter.hasNext();) { Database openedDb = iter.next(); try { if (openedDb.getDatabaseName() == null) { @@ -251,157 +251,156 @@ public class BDBEnvironment { iter.remove(); continue; } + + if (openedDb.getDatabaseName().equals(dbName)) { + return openedDb; + } + } - if (openedDb.getDatabaseName().equals(dbName)) { - return openedDb; - } - } - - // open the specified database. - // the first parameter null means auto-commit - try { - db = replicatedEnvironment.openDatabase(null, dbName, dbConfig); - openedDatabases.add(db); - } catch (Exception e) { - LOG.warn("catch an exception when open database {}", dbName, e); - } - } finally { - lock.writeLock().unlock(); - } - return db; - } - - // close and remove the database whose name is dbName - public void removeDatabase(String dbName) { - lock.writeLock().lock(); - try { - String targetDbName = null; - int index = 0; - for (Database db : openedDatabases) { - String name = db.getDatabaseName(); - if (dbName.equals(name)) { + // open the specified database. + // the first parameter null means auto-commit + try { + db = replicatedEnvironment.openDatabase(null, dbName, dbConfig); + openedDatabases.add(db); + } catch (Exception e) { + LOG.warn("catch an exception when open database {}", dbName, e); + } + } finally { + lock.writeLock().unlock(); + } + return db; + } + + // close and remove the database whose name is dbName + public void removeDatabase(String dbName) { + lock.writeLock().lock(); + try { + String targetDbName = null; + int index = 0; + for (Database db : openedDatabases) { + String name = db.getDatabaseName(); + if (dbName.equals(name)) { db.close(); - LOG.info("database {} has been closed", name); - targetDbName = name; - break; - } - index++; - } - if (targetDbName != null) { - LOG.info("begin to remove database {} from openedDatabases", targetDbName); - openedDatabases.remove(index); - } - try { - LOG.info("begin to remove database {} from replicatedEnviroment", dbName); - // the first parameter null means auto-commit - replicatedEnvironment.removeDatabase(null, dbName); - } catch (DatabaseNotFoundException e) { - LOG.warn("catch an exception when remove db:{}, this db does not exist", dbName, e); - } - } finally { - lock.writeLock().unlock(); - } - } - - // get journal db names and sort the names - public List getDatabaseNames() { - List ret = new ArrayList(); - List names = null; - int tried = 0; - while (true) { - try { - names = replicatedEnvironment.getDatabaseNames(); - break; - } catch (InsufficientLogException e) { - throw e; - } catch (EnvironmentFailureException e) { - tried++; - if (tried == RETRY_TIME) { - LOG.error("bdb environment failure exception.", e); - System.exit(-1); - } - LOG.warn("bdb environment failure exception. will retry", e); - try { - Thread.sleep(1000); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } - continue; - } catch (DatabaseException e) { - LOG.warn("catch an exception when calling getDatabaseNames", e); - return null; - } - } - - if (names != null) { - for (String name : names) { - // We don't count epochDB - if (name.equals("epochDB")) { - continue; - } - - long db = Long.parseLong(name); - ret.add(db); - } - } - - Collections.sort(ret); - return ret; - } - - // Close the store and environment - public void close() { - for (Database db : openedDatabases) { - try { - db.close(); - } catch (DatabaseException exception) { - LOG.error("Error closing db {} will exit", db.getDatabaseName(), exception); - System.exit(-1); - } - } - openedDatabases.clear(); - - if (epochDB != null) { - try { - epochDB.close(); - } catch (DatabaseException exception) { - LOG.error("Error closing db {} will exit", epochDB.getDatabaseName(), exception); - System.exit(-1); - } - } - - if (replicatedEnvironment != null) { - try { - // Finally, close the store and environment. - replicatedEnvironment.close(); - } catch (DatabaseException exception) { - LOG.error("Error closing replicatedEnviroment", exception); - System.exit(-1); - } - } - } - - private SyncPolicy getSyncPolicy(String policy) { - if (policy.equalsIgnoreCase("SYNC")) { - return Durability.SyncPolicy.SYNC; - } - if (policy.equalsIgnoreCase("NO_SYNC")) { - return Durability.SyncPolicy.NO_SYNC; - } - // default value is WRITE_NO_SYNC - return Durability.SyncPolicy.WRITE_NO_SYNC; - } - - private ReplicaAckPolicy getAckPolicy(String policy) { - if (policy.equalsIgnoreCase("ALL")) { - return Durability.ReplicaAckPolicy.ALL; - } - if (policy.equalsIgnoreCase("NONE")) { - return Durability.ReplicaAckPolicy.NONE; - } - // default value is SIMPLE_MAJORITY - return Durability.ReplicaAckPolicy.SIMPLE_MAJORITY; - } - -} - + LOG.info("database {} has been closed", name); + targetDbName = name; + break; + } + index++; + } + if (targetDbName != null) { + LOG.info("begin to remove database {} from openedDatabases", targetDbName); + openedDatabases.remove(index); + } + try { + LOG.info("begin to remove database {} from replicatedEnviroment", dbName); + // the first parameter null means auto-commit + replicatedEnvironment.removeDatabase(null, dbName); + } catch (DatabaseNotFoundException e) { + LOG.warn("catch an exception when remove db:{}, this db does not exist", dbName, e); + } + } finally { + lock.writeLock().unlock(); + } + } + + // get journal db names and sort the names + public List getDatabaseNames() { + List ret = new ArrayList(); + List names = null; + int tried = 0; + while (true) { + try { + names = replicatedEnvironment.getDatabaseNames(); + break; + } catch (InsufficientLogException e) { + throw e; + } catch (EnvironmentFailureException e) { + tried++; + if (tried == RETRY_TIME) { + LOG.error("bdb environment failure exception.", e); + System.exit(-1); + } + LOG.warn("bdb environment failure exception. will retry", e); + try { + Thread.sleep(1000); + } catch (InterruptedException e1) { + e1.printStackTrace(); + } + continue; + } catch (DatabaseException e) { + LOG.warn("catch an exception when calling getDatabaseNames", e); + return null; + } + } + + if (names != null) { + for (String name : names) { + // We don't count epochDB + if (name.equals("epochDB")) { + continue; + } + + long db = Long.parseLong(name); + ret.add(db); + } + } + + Collections.sort(ret); + return ret; + } + + // Close the store and environment + public void close() { + for (Database db : openedDatabases) { + try { + db.close(); + } catch (DatabaseException exception) { + LOG.error("Error closing db {} will exit", db.getDatabaseName(), exception); + System.exit(-1); + } + } + openedDatabases.clear(); + + if (epochDB != null) { + try { + epochDB.close(); + } catch (DatabaseException exception) { + LOG.error("Error closing db {} will exit", epochDB.getDatabaseName(), exception); + System.exit(-1); + } + } + + if (replicatedEnvironment != null) { + try { + // Finally, close the store and environment. + replicatedEnvironment.close(); + } catch (DatabaseException exception) { + LOG.error("Error closing replicatedEnviroment", exception); + System.exit(-1); + } + } + } + + private SyncPolicy getSyncPolicy(String policy) { + if (policy.equalsIgnoreCase("SYNC")) { + return Durability.SyncPolicy.SYNC; + } + if (policy.equalsIgnoreCase("NO_SYNC")) { + return Durability.SyncPolicy.NO_SYNC; + } + // default value is WRITE_NO_SYNC + return Durability.SyncPolicy.WRITE_NO_SYNC; + } + + private ReplicaAckPolicy getAckPolicy(String policy) { + if (policy.equalsIgnoreCase("ALL")) { + return Durability.ReplicaAckPolicy.ALL; + } + if (policy.equalsIgnoreCase("NONE")) { + return Durability.ReplicaAckPolicy.NONE; + } + // default value is SIMPLE_MAJORITY + return Durability.ReplicaAckPolicy.SIMPLE_MAJORITY; + } + +} diff --git a/fe/src/com/baidu/palo/journal/bdbje/BDBJEJournal.java b/fe/src/com/baidu/palo/journal/bdbje/BDBJEJournal.java index 913d9b51e0..19557bbe3f 100644 --- a/fe/src/com/baidu/palo/journal/bdbje/BDBJEJournal.java +++ b/fe/src/com/baidu/palo/journal/bdbje/BDBJEJournal.java @@ -424,4 +424,4 @@ public class BDBJEJournal implements Journal { } return flag; } -} +} \ No newline at end of file diff --git a/fe/src/com/baidu/palo/journal/bdbje/BDBJournalCursor.java b/fe/src/com/baidu/palo/journal/bdbje/BDBJournalCursor.java index 08b4ad57b0..56119d61b5 100644 --- a/fe/src/com/baidu/palo/journal/bdbje/BDBJournalCursor.java +++ b/fe/src/com/baidu/palo/journal/bdbje/BDBJournalCursor.java @@ -13,8 +13,8 @@ // specific language governing permissions and limitations // under the License. -package com.baidu.palo.journal.bdbje; - +package com.baidu.palo.journal.bdbje; + import com.baidu.palo.journal.JournalCursor; import com.baidu.palo.journal.JournalEntity; @@ -29,105 +29,105 @@ import org.apache.logging.log4j.Logger; import java.io.ByteArrayInputStream; import java.io.DataInputStream; -import java.util.List; - -public class BDBJournalCursor implements JournalCursor { - private static final Logger LOG = LogManager.getLogger(JournalCursor.class); - - private long toKey; - private long currentKey; - private BDBEnvironment environment; - private List dbNames; - private Database database; - private int nextDbPositionIndex; - private final int maxTryTime = 3; - - public static BDBJournalCursor getJournalCursor(BDBEnvironment env, long fromKey, long toKey) { - if (toKey < fromKey || fromKey < 0) { - System.out.println("Invalid key range!"); - return null; - } - BDBJournalCursor cursor = null; - try { - cursor = new BDBJournalCursor(env, fromKey, toKey); - } catch (Exception e) { - LOG.error("new BDBJournalCursor error.", e); - } - return cursor; - } - - - private BDBJournalCursor(BDBEnvironment env, long fromKey, long toKey) throws Exception { - this.environment = env; - this.toKey = toKey; - this.currentKey = fromKey; - this.dbNames = env.getDatabaseNames(); - if (dbNames == null) { - throw new NullPointerException("dbNames is null."); - } - this.nextDbPositionIndex = 0; - - // find the db which may contain the fromKey - String dbName = null; - for (long db : dbNames) { - if (fromKey >= db) { - dbName = Long.toString(db); - nextDbPositionIndex++; - continue; - } else { - break; - } - } - - if (dbName == null) { - LOG.error("Can not find the key:{}, fail to get journal cursor. will exit.", fromKey); - System.exit(-1); - } - this.database = env.openDatabase(dbName); - } - - @Override - public JournalEntity next() { - JournalEntity ret = null; - if (currentKey > toKey) { - return ret; - } - Long key = new Long(currentKey); - DatabaseEntry theKey = new DatabaseEntry(); - TupleBinding myBinding = TupleBinding.getPrimitiveBinding(Long.class); - myBinding.objectToEntry(key, theKey); - - DatabaseEntry theData = new DatabaseEntry(); - // if current db does not contain any more data, then we go to search the next db - try { - // null means perform the operation without transaction protection. - // READ_COMMITTED guarantees no dirty read. - int tryTimes = 0; +import java.util.List; + +public class BDBJournalCursor implements JournalCursor { + private static final Logger LOG = LogManager.getLogger(JournalCursor.class); + + private long toKey; + private long currentKey; + private BDBEnvironment environment; + private List dbNames; + private Database database; + private int nextDbPositionIndex; + private final int maxTryTime = 3; + + public static BDBJournalCursor getJournalCursor(BDBEnvironment env, long fromKey, long toKey) { + if (toKey < fromKey || fromKey < 0) { + System.out.println("Invalid key range!"); + return null; + } + BDBJournalCursor cursor = null; + try { + cursor = new BDBJournalCursor(env, fromKey, toKey); + } catch (Exception e) { + LOG.error("new BDBJournalCursor error.", e); + } + return cursor; + } + + + private BDBJournalCursor(BDBEnvironment env, long fromKey, long toKey) throws Exception { + this.environment = env; + this.toKey = toKey; + this.currentKey = fromKey; + this.dbNames = env.getDatabaseNames(); + if (dbNames == null) { + throw new NullPointerException("dbNames is null."); + } + this.nextDbPositionIndex = 0; + + // find the db which may contain the fromKey + String dbName = null; + for (long db : dbNames) { + if (fromKey >= db) { + dbName = Long.toString(db); + nextDbPositionIndex++; + continue; + } else { + break; + } + } + + if (dbName == null) { + LOG.error("Can not find the key:{}, fail to get journal cursor. will exit.", fromKey); + System.exit(-1); + } + this.database = env.openDatabase(dbName); + } + + @Override + public JournalEntity next() { + JournalEntity ret = null; + if (currentKey > toKey) { + return ret; + } + Long key = new Long(currentKey); + DatabaseEntry theKey = new DatabaseEntry(); + TupleBinding myBinding = TupleBinding.getPrimitiveBinding(Long.class); + myBinding.objectToEntry(key, theKey); + + DatabaseEntry theData = new DatabaseEntry(); + // if current db does not contain any more data, then we go to search the next db + try { + // null means perform the operation without transaction protection. + // READ_COMMITTED guarantees no dirty read. + int tryTimes = 0; while (true) { - OperationStatus operationStatus = database.get(null, theKey, theData, LockMode.READ_COMMITTED); - if (operationStatus == OperationStatus.SUCCESS) { - // Recreate the data String. - byte[] retData = theData.getData(); - DataInputStream in = new DataInputStream(new ByteArrayInputStream(retData)); - ret = new JournalEntity(); - try { - ret.readFields(in); - } catch (Exception e) { - LOG.error("fail to read journal entity key={}, will exit", currentKey, e); - System.exit(-1); - } - currentKey++; - return ret; - } else if (nextDbPositionIndex < dbNames.size() && currentKey == dbNames.get(nextDbPositionIndex)) { - database = environment.openDatabase(dbNames.get(nextDbPositionIndex).toString()); - nextDbPositionIndex++; - tryTimes = 0; - continue; - } else if (tryTimes < maxTryTime) { - tryTimes++; - LOG.warn("fail to get journal {}, will try again. status: {}", currentKey, operationStatus); - Thread.sleep(3000); - continue; + OperationStatus operationStatus = database.get(null, theKey, theData, LockMode.READ_COMMITTED); + if (operationStatus == OperationStatus.SUCCESS) { + // Recreate the data String. + byte[] retData = theData.getData(); + DataInputStream in = new DataInputStream(new ByteArrayInputStream(retData)); + ret = new JournalEntity(); + try { + ret.readFields(in); + } catch (Exception e) { + LOG.error("fail to read journal entity key={}, will exit", currentKey, e); + System.exit(-1); + } + currentKey++; + return ret; + } else if (nextDbPositionIndex < dbNames.size() && currentKey == dbNames.get(nextDbPositionIndex)) { + database = environment.openDatabase(dbNames.get(nextDbPositionIndex).toString()); + nextDbPositionIndex++; + tryTimes = 0; + continue; + } else if (tryTimes < maxTryTime) { + tryTimes++; + LOG.warn("fail to get journal {}, will try again. status: {}", currentKey, operationStatus); + Thread.sleep(3000); + continue; } else if (operationStatus == OperationStatus.NOTFOUND) { // In the case: // On non-master FE, the replayer will first get the max journal id, @@ -143,17 +143,16 @@ public class BDBJournalCursor implements JournalCursor { } else { LOG.error("fail to get journal {}, status: {}, will exit", currentKey); System.exit(-1); - } - } - } catch (Exception e) { - LOG.warn("Catch an exception when get next JournalEntity. key:{}", currentKey, e); - return null; - } - } - - @Override - public void close() { - - } -} - + } + } + } catch (Exception e) { + LOG.warn("Catch an exception when get next JournalEntity. key:{}", currentKey, e); + return null; + } + } + + @Override + public void close() { + + } +} diff --git a/fe/src/com/baidu/palo/journal/local/LocalJournalCursor.java b/fe/src/com/baidu/palo/journal/local/LocalJournalCursor.java index a938cecb9c..df5ae99a45 100644 --- a/fe/src/com/baidu/palo/journal/local/LocalJournalCursor.java +++ b/fe/src/com/baidu/palo/journal/local/LocalJournalCursor.java @@ -17,7 +17,6 @@ package com.baidu.palo.journal.local; import com.baidu.palo.alter.AlterJob; import com.baidu.palo.catalog.Database; -import com.baidu.palo.catalog.UserProperty; import com.baidu.palo.common.io.Text; import com.baidu.palo.ha.MasterInfo; import com.baidu.palo.journal.JournalCursor; @@ -27,6 +26,7 @@ import com.baidu.palo.load.AsyncDeleteJob; import com.baidu.palo.load.DeleteInfo; import com.baidu.palo.load.LoadErrorHub; import com.baidu.palo.load.LoadJob; +import com.baidu.palo.mysql.privilege.UserProperty; import com.baidu.palo.persist.CloneInfo; import com.baidu.palo.persist.ConsistencyCheckInfo; import com.baidu.palo.persist.CreateTableInfo; @@ -53,7 +53,8 @@ import java.io.EOFException; import java.io.File; import java.io.IOException; import java.util.List; - + +@Deprecated public final class LocalJournalCursor implements JournalCursor { private static final Logger LOG = LogManager.getLogger(LocalJournalCursor.class); private String imageDir; @@ -183,7 +184,8 @@ public final class LocalJournalCursor implements JournalCursor { } return ret; } - + + @Deprecated private JournalEntity getJournalEntity(DataInputStream in, short opCode) throws IOException { JournalEntity ret = new JournalEntity(); ret.setOpCode(opCode); diff --git a/fe/src/com/baidu/palo/load/ExportJob.java b/fe/src/com/baidu/palo/load/ExportJob.java index 817b139510..3cf2fcd426 100644 --- a/fe/src/com/baidu/palo/load/ExportJob.java +++ b/fe/src/com/baidu/palo/load/ExportJob.java @@ -34,11 +34,12 @@ import com.baidu.palo.catalog.PrimitiveType; import com.baidu.palo.catalog.Table; import com.baidu.palo.catalog.Type; import com.baidu.palo.common.Config; +import com.baidu.palo.common.FeMetaVersion; import com.baidu.palo.common.InternalException; -import com.baidu.palo.common.io.Text; -import com.baidu.palo.common.io.Writable; import com.baidu.palo.common.Pair; import com.baidu.palo.common.Status; +import com.baidu.palo.common.io.Text; +import com.baidu.palo.common.io.Writable; import com.baidu.palo.common.util.TimeUtils; import com.baidu.palo.planner.DataPartition; import com.baidu.palo.planner.ExportSink; @@ -54,13 +55,13 @@ import com.baidu.palo.system.Backend; import com.baidu.palo.task.AgentClient; import com.baidu.palo.thrift.TAgentResult; import com.baidu.palo.thrift.TNetworkAddress; -import com.baidu.palo.thrift.TStatusCode; import com.baidu.palo.thrift.TScanRangeLocation; import com.baidu.palo.thrift.TScanRangeLocations; +import com.baidu.palo.thrift.TStatusCode; import com.baidu.palo.thrift.TUniqueId; import com.google.common.base.Preconditions; -import com.google.common.base.Strings; +import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; @@ -213,7 +214,7 @@ public class ExportJob implements Writable { scanNode = new OlapScanNode(new PlanNodeId(0), exportTupleDesc, "OlapScanNodeForExport"); Map columnFilters = Maps.newHashMap(); ((OlapScanNode) scanNode).setColumnFilters(columnFilters); - ((OlapScanNode) scanNode).setIsPreAggregation(false); + ((OlapScanNode) scanNode).setIsPreAggregation(false, "Export"); ((OlapScanNode) scanNode).setCanTurnOnPreAggr(false); break; case MYSQL: @@ -361,38 +362,18 @@ public class ExportJob implements Writable { return id; } - public void setClusterName(String clusterName) { - this.clusterName = clusterName; - } - public long getDbId() { return dbId; } - public void setDbId(long dbId) { - this.dbId = dbId; - } - public long getTableId() { return this.tableId; } - public void setTableId(long tableId) { - this.tableId = tableId; - } - - public void setTableName(TableName tblName) { - this.tableName = tblName; - } - public JobState getState() { return state; } - public void setState(JobState state) { - this.state = state; - } - public BrokerDesc getBrokerDesc() { return brokerDesc; } @@ -405,22 +386,10 @@ public class ExportJob implements Writable { return exportPath; } - public void setExportPath(String exportPath) { - this.exportPath = exportPath; - } - - public void setColumnSeparator(String columnSeparator) { - this.columnSeparator = columnSeparator; - } - public String getColumnSeparator() { return this.columnSeparator; } - public void setLineDelimiter(String lineDelimiter) { - this.lineDelimiter = lineDelimiter; - } - public String getLineDelimiter() { return this.lineDelimiter; } @@ -429,10 +398,6 @@ public class ExportJob implements Writable { return partitions; } - public void setPartitions(List partitions) { - this.partitions = partitions; - } - public int getProgress() { return progress; } @@ -453,10 +418,6 @@ public class ExportJob implements Writable { return startTimeMs; } - public void setStartTimeMs(long startTimeMs) { - this.startTimeMs = startTimeMs; - } - public long getFinishTimeMs() { return finishTimeMs; } @@ -502,8 +463,8 @@ public class ExportJob implements Writable { return sql; } - public void setSql(String sql) { - this.sql = sql; + public TableName getTableName() { + return tableName; } public synchronized void cancel(ExportFailMsg.CancelType type, String msg) { @@ -620,6 +581,8 @@ public class ExportJob implements Writable { out.writeBoolean(true); brokerDesc.write(out); } + + tableName.write(out); } @Override @@ -652,6 +615,13 @@ public class ExportJob implements Writable { if (in.readBoolean()) { brokerDesc = BrokerDesc.read(in); } + + if (Catalog.getCurrentCatalogJournalVersion() >= FeMetaVersion.VERSION_43) { + tableName = new TableName(); + tableName.readFields(in); + } else { + tableName = new TableName("DUMMY", "DUMMY"); + } } @Override diff --git a/fe/src/com/baidu/palo/load/ExportMgr.java b/fe/src/com/baidu/palo/load/ExportMgr.java index 5e79925aeb..5c558391be 100644 --- a/fe/src/com/baidu/palo/load/ExportMgr.java +++ b/fe/src/com/baidu/palo/load/ExportMgr.java @@ -17,11 +17,15 @@ package com.baidu.palo.load; import com.baidu.palo.analysis.BrokerDesc; import com.baidu.palo.analysis.ExportStmt; +import com.baidu.palo.analysis.TableName; import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.catalog.Database; import com.baidu.palo.common.Config; import com.baidu.palo.common.util.ListComparator; import com.baidu.palo.common.util.OrderByPair; import com.baidu.palo.common.util.TimeUtils; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.google.common.base.Joiner; import com.google.common.collect.Lists; @@ -135,6 +139,28 @@ public class ExportMgr { } } + // check auth + + TableName tableName = job.getTableName(); + if (tableName == null || tableName.getTbl().equals("DUMMY")) { + // forward compatibility, no table name is saved before + Database db = Catalog.getCurrentCatalog().getDb(dbId); + if (db == null) { + continue; + } + if (!Catalog.getCurrentCatalog().getAuth().checkDbPriv(ConnectContext.get(), + db.getFullName(), PrivPredicate.SHOW)) { + continue; + } + } else { + if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(ConnectContext.get(), + tableName.getDb(), tableName.getTbl(), + PrivPredicate.SHOW)) { + continue; + } + } + + if (states != null) { if (!states.contains(state)) { continue; diff --git a/fe/src/com/baidu/palo/load/Load.java b/fe/src/com/baidu/palo/load/Load.java index 8479325bff..30a468b773 100644 --- a/fe/src/com/baidu/palo/load/Load.java +++ b/fe/src/com/baidu/palo/load/Load.java @@ -62,6 +62,7 @@ import com.baidu.palo.load.FailMsg.CancelType; import com.baidu.palo.load.LoadJob.EtlJobType; import com.baidu.palo.load.LoadJob.JobState; import com.baidu.palo.metric.MetricRepo; +import com.baidu.palo.mysql.privilege.PrivPredicate; import com.baidu.palo.persist.ReplicaPersistInfo; import com.baidu.palo.qe.ConnectContext; import com.baidu.palo.system.Backend; @@ -391,12 +392,28 @@ public class Load { addLoadJob(job, db); } + // This is a final step of all addLoadJob() methods private void addLoadJob(LoadJob job, Database db) throws DdlException { // check cluster capacity Catalog.getCurrentSystemInfo().checkClusterCapacity(db.getClusterName()); // check db quota db.checkQuota(); + // check if table is in restore process + db.readLock(); + try { + for (Long tblId : job.getIdToTableLoadInfo().keySet()) { + Table tbl = db.getTable(tblId); + if (tbl != null && tbl.getType() == TableType.OLAP + && ((OlapTable) tbl).getState() == OlapTableState.RESTORE) { + throw new DdlException("Table " + tbl.getName() + " is in restore process. " + + "Can not load into it"); + } + } + } finally { + db.readUnlock(); + } + writeLock(); try { unprotectAddLoadJob(job); @@ -481,6 +498,7 @@ public class Load { for (DataDescription dataDescription : dataDescriptions) { // create source createSource(db, dataDescription, tableToPartitionSources, job.getDeleteFlag()); + job.addTableName(dataDescription.getTableName()); } for (Entry>> tableEntry : tableToPartitionSources.entrySet()) { long tableId = tableEntry.getKey(); @@ -552,7 +570,7 @@ public class Load { cluster = properties.get(LoadStmt.CLUSTER_PROPERTY); } - Pair clusterInfo = Catalog.getInstance().getUserMgr().getClusterInfo( + Pair clusterInfo = Catalog.getInstance().getAuth().getLoadClusterInfo( stmt.getUser(), cluster); cluster = clusterInfo.first; DppConfig clusterConfig = clusterInfo.second; @@ -1033,6 +1051,25 @@ public class Load { readUnlock(); } + // check auth here, cause we need table info + Set tableNames = job.getTableNames(); + if (tableNames.isEmpty()) { + // forward compatibility + if (!Catalog.getCurrentCatalog().getAuth().checkDbPriv(ConnectContext.get(), dbName, + PrivPredicate.LOAD)) { + ErrorReport.reportDdlException(ErrorCode.ERR_SPECIFIC_ACCESS_DENIED_ERROR, "CANCEL LOAD"); + } + } else { + for (String tblName : tableNames) { + if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(ConnectContext.get(), dbName, tblName, + PrivPredicate.LOAD)) { + ErrorReport.reportDdlException(ErrorCode.ERR_TABLEACCESS_DENIED_ERROR, "CANCEL LOAD", + ConnectContext.get().getQualifiedUser(), + ConnectContext.get().getRemoteIP(), tblName); + } + } + } + // cancel job if (!cancelLoadJob(job, CancelType.USER_CANCEL, "user cancel")) { throw new DdlException("Cancel load job fail"); @@ -1244,8 +1281,8 @@ public class Load { } } - public LinkedList> getLoadJobInfosByDb(long dbId, String labelValue, boolean accurateMatch, - Set states, ArrayList orderByPairs) { + public LinkedList> getLoadJobInfosByDb(long dbId, String dbName, String labelValue, + boolean accurateMatch, Set states, ArrayList orderByPairs) { LinkedList> loadJobInfos = new LinkedList>(); readLock(); try { @@ -1254,11 +1291,13 @@ public class Load { return loadJobInfos; } + long start = System.currentTimeMillis(); + LOG.debug("begin to get load job info, size: {}", loadJobs.size()); for (LoadJob loadJob : loadJobs) { // filter first String label = loadJob.getLabel(); JobState state = loadJob.getState(); - + if (labelValue != null) { if (accurateMatch) { if (!label.equals(labelValue)) { @@ -1270,13 +1309,35 @@ public class Load { } } } - + if (states != null) { if (!states.contains(state)) { continue; } } - + + // check auth + Set tableNames = loadJob.getTableNames(); + if (tableNames.isEmpty()) { + // forward compatibility + if (!Catalog.getCurrentCatalog().getAuth().checkDbPriv(ConnectContext.get(), dbName, + PrivPredicate.SHOW)) { + continue; + } + } else { + boolean auth = true; + for (String tblName : tableNames) { + if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(ConnectContext.get(), dbName, + tblName, PrivPredicate.SHOW)) { + auth = false; + break; + } + } + if (!auth) { + continue; + } + } + List jobInfo = new ArrayList(); // jobId @@ -1362,6 +1423,8 @@ public class Load { loadJobInfos.add(jobInfo); } // end for loadJobs + + LOG.debug("finished to get load job info, cost: {}", (System.currentTimeMillis() - start)); } finally { readUnlock(); } @@ -1378,13 +1441,14 @@ public class Load { return loadJobInfos; } - public long getLatestJobIdByLabel(long dbId, String labelValue) { + public LoadJob getLatestJobIdByLabel(long dbId, String labelValue) { + LoadJob job = null; long jobId = 0; try { readLock(); List loadJobs = this.dbToLoadJobs.get(dbId); if (loadJobs == null) { - return 0; + return null; } for (LoadJob loadJob : loadJobs) { @@ -1400,13 +1464,14 @@ public class Load { if (currJobId > jobId) { jobId = currJobId; + job = loadJob; } } } finally { readUnlock(); } - return jobId; + return job; } public List> getLoadJobUnfinishedInfo(long jobId) { @@ -1509,8 +1574,8 @@ public class Load { // Note: althrough this.loadErrorHubInfo is volatile, no need to lock. // but editlog need be locked public void changeLoadErrorHubInfo(LoadErrorHub.Param info) { + writeLock(); try { - writeLock(); this.loadErrorHubInfo = info; Catalog.getInstance().getEditLog().logSetLoadErrorHub(info); } finally { @@ -1520,6 +1585,7 @@ public class Load { public static class JobInfo { public String dbName; + public Set tblNames = Sets.newHashSet(); public String label; public String clusterName; public JobState state; @@ -1537,6 +1603,7 @@ public class Load { // result saved in info public void getJobInfo(JobInfo info) throws DdlException { String fullDbName = ClusterNamespace.getFullName(info.clusterName, info.dbName); + info.dbName = fullDbName; Database db = Catalog.getInstance().getDb(fullDbName); if (db == null) { throw new DdlException("Unknown database(" + info.dbName + ")"); @@ -1553,6 +1620,11 @@ public class Load { } // only the last one should be running LoadJob job = loadJobs.get(loadJobs.size() - 1); + + if (!job.getTableNames().isEmpty()) { + info.tblNames.addAll(job.getTableNames()); + } + info.state = job.getState(); if (info.state == JobState.QUORUM_FINISHED) { info.state = JobState.FINISHED; @@ -2887,6 +2959,12 @@ public class Load { public List> getDeleteInfosByDb(long dbId, boolean forUser) { LinkedList> infos = new LinkedList>(); + Database db = Catalog.getInstance().getDb(dbId); + if (db == null) { + return infos; + } + + String dbName = db.getFullName(); readLock(); try { List deleteInfos = dbToDeleteInfos.get(dbId); @@ -2895,6 +2973,12 @@ public class Load { } for (DeleteInfo deleteInfo : deleteInfos) { + if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(ConnectContext.get(), dbName, + deleteInfo.getTableName(), + PrivPredicate.LOAD)) { + continue; + } + List info = Lists.newArrayList(); if (!forUser) { info.add(deleteInfo.getJobId()); diff --git a/fe/src/com/baidu/palo/load/LoadJob.java b/fe/src/com/baidu/palo/load/LoadJob.java index 793e5c7474..c016ec08a1 100644 --- a/fe/src/com/baidu/palo/load/LoadJob.java +++ b/fe/src/com/baidu/palo/load/LoadJob.java @@ -26,7 +26,9 @@ import com.baidu.palo.task.PushTask; import com.baidu.palo.thrift.TPriority; import com.baidu.palo.thrift.TResourceInfo; +import com.google.common.base.Strings; import com.google.common.collect.Maps; +import com.google.common.collect.Sets; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -106,6 +108,9 @@ public class LoadJob implements Writable { private long execMemLimit; + // save table names for auth check + private Set tableNames; + public LoadJob() { this(""); } @@ -141,8 +146,17 @@ public class LoadJob implements Writable { this.resourceInfo = null; this.priority = TPriority.NORMAL; this.execMemLimit = DEFAULT_EXEC_MEM_LIMIT; + this.tableNames = Sets.newHashSet(); } + public void addTableName(String tableName) { + tableNames.add(tableName); + } + + public Set getTableNames() { + return tableNames; + } + public long getId() { return id; } @@ -616,7 +630,8 @@ public class LoadJob implements Writable { } // resourceInfo - if (resourceInfo == null) { + if (resourceInfo == null || Strings.isNullOrEmpty(resourceInfo.getGroup()) + || Strings.isNullOrEmpty(resourceInfo.getUser())) { out.writeBoolean(false); } else { out.writeBoolean(true); @@ -642,13 +657,20 @@ public class LoadJob implements Writable { } out.writeLong(execMemLimit); + + out.writeInt(tableNames.size()); + for (String tableName : tableNames) { + Text.writeString(out, tableName); + } } public void readFields(DataInput in) throws IOException { + long version = Catalog.getCurrentCatalogJournalVersion(); + id = in.readLong(); dbId = in.readLong(); label = Text.readString(in); - if (Catalog.getCurrentCatalogJournalVersion() >= FeMetaVersion.VERSION_23) { + if (version >= FeMetaVersion.VERSION_23) { timestamp = in.readLong(); } else { timestamp = -1; @@ -657,7 +679,7 @@ public class LoadJob implements Writable { maxFilterRatio = in.readDouble(); deleteFlag = false; - if (Catalog.getCurrentCatalogJournalVersion() >= FeMetaVersion.VERSION_30) { + if (version >= FeMetaVersion.VERSION_30) { deleteFlag = in.readBoolean(); } @@ -726,7 +748,6 @@ public class LoadJob implements Writable { resourceInfo = new TResourceInfo(user, group); } - long version = Catalog.getCurrentCatalogJournalVersion(); if (version >= 3 && version < 7) { // bos 3 parameters String bosEndpoint = Text.readString(in); @@ -754,6 +775,13 @@ public class LoadJob implements Writable { if (version >= FeMetaVersion.VERSION_34) { this.execMemLimit = in.readLong(); } + + if (version >= FeMetaVersion.VERSION_43) { + int size = in.readInt(); + for (int i = 0; i < size; i++) { + tableNames.add(Text.readString(in)); + } + } } @Override diff --git a/fe/src/com/baidu/palo/master/MasterImpl.java b/fe/src/com/baidu/palo/master/MasterImpl.java index 9b31fba0d9..59361f48b6 100644 --- a/fe/src/com/baidu/palo/master/MasterImpl.java +++ b/fe/src/com/baidu/palo/master/MasterImpl.java @@ -40,8 +40,9 @@ import com.baidu.palo.task.CheckConsistencyTask; import com.baidu.palo.task.CloneTask; import com.baidu.palo.task.CreateReplicaTask; import com.baidu.palo.task.CreateRollupTask; +import com.baidu.palo.task.DirMoveTask; +import com.baidu.palo.task.DownloadTask; import com.baidu.palo.task.PushTask; -import com.baidu.palo.task.RestoreTask; import com.baidu.palo.task.SchemaChangeTask; import com.baidu.palo.task.SnapshotTask; import com.baidu.palo.task.UploadTask; @@ -121,7 +122,11 @@ public class MasterImpl { } else { if (taskStatus.getStatus_code() != TStatusCode.OK) { task.failed(); - return result; + // We start to let FE perceive the task's error msg, begin with these 4 types of task. + if (taskType != TTaskType.MAKE_SNAPSHOT && taskType != TTaskType.UPLOAD + && taskType != TTaskType.DOWNLOAD && taskType != TTaskType.MOVE) { + return result; + } } } @@ -160,14 +165,16 @@ public class MasterImpl { finishConsistenctCheck(task, request); break; case MAKE_SNAPSHOT: - Preconditions.checkState(request.isSetSnapshot_path()); - finishMakeSnapshot(task, request.getSnapshot_path()); + finishMakeSnapshot(task, request); break; case UPLOAD: - finishUpload(task); + finishUpload(task, request); break; - case RESTORE: - finishRestore(task); + case DOWNLOAD: + finishDownloadTask(task, request); + break; + case MOVE: + finishMoveDirTask(task, request); break; default: break; @@ -481,22 +488,33 @@ public class MasterImpl { AgentTaskQueue.removeTask(task.getBackendId(), TTaskType.CHECK_CONSISTENCY, task.getSignature()); } - private void finishMakeSnapshot(AgentTask task, String snapshotPath) { + private void finishMakeSnapshot(AgentTask task, TFinishTaskRequest request) { SnapshotTask snapshotTask = (SnapshotTask) task; - Catalog.getInstance().getBackupHandler().handleFinishedSnapshot(snapshotTask, snapshotPath); - AgentTaskQueue.removeTask(task.getBackendId(), TTaskType.MAKE_SNAPSHOT, task.getSignature()); + if (Catalog.getInstance().getBackupHandler().handleFinishedSnapshotTask(snapshotTask, request)) { + AgentTaskQueue.removeTask(task.getBackendId(), TTaskType.MAKE_SNAPSHOT, task.getSignature()); + } + } - private void finishUpload(AgentTask task) { + private void finishUpload(AgentTask task, TFinishTaskRequest request) { UploadTask uploadTask = (UploadTask) task; - Catalog.getInstance().getBackupHandler().handleFinishedUpload(uploadTask); - AgentTaskQueue.removeTask(task.getBackendId(), TTaskType.UPLOAD, task.getSignature()); + if (Catalog.getInstance().getBackupHandler().handleFinishedSnapshotUploadTask(uploadTask, request)) { + AgentTaskQueue.removeTask(task.getBackendId(), TTaskType.UPLOAD, task.getSignature()); + } } - private void finishRestore(AgentTask task) { - RestoreTask restoreTask = (RestoreTask) task; - Catalog.getInstance().getBackupHandler().handleFinishedRestore(restoreTask); - AgentTaskQueue.removeTask(task.getBackendId(), TTaskType.RESTORE, task.getSignature()); + private void finishDownloadTask(AgentTask task, TFinishTaskRequest request) { + DownloadTask downloadTask = (DownloadTask) task; + if (Catalog.getInstance().getBackupHandler().handleDownloadSnapshotTask(downloadTask, request)) { + AgentTaskQueue.removeTask(task.getBackendId(), TTaskType.DOWNLOAD, task.getSignature()); + } + } + + private void finishMoveDirTask(AgentTask task, TFinishTaskRequest request) { + DirMoveTask dirMoveTask = (DirMoveTask) task; + if (Catalog.getInstance().getBackupHandler().handleDirMoveTask(dirMoveTask, request)) { + AgentTaskQueue.removeTask(task.getBackendId(), TTaskType.MOVE, task.getSignature()); + } } public TMasterResult report(TReportRequest request) throws TException { @@ -505,7 +523,7 @@ public class MasterImpl { } public TFetchResourceResult fetchResource() { - return Catalog.getInstance().getUserMgr().toResourceThrift(); + return Catalog.getInstance().getAuth().toResourceThrift(); } } diff --git a/fe/src/com/baidu/palo/master/ReportHandler.java b/fe/src/com/baidu/palo/master/ReportHandler.java index b00c846a7b..bdcaf89fc3 100644 --- a/fe/src/com/baidu/palo/master/ReportHandler.java +++ b/fe/src/com/baidu/palo/master/ReportHandler.java @@ -577,7 +577,8 @@ public class ReportHandler extends Daemon { for (Replica replica : replicas) { final long id = replica.getBackendId(); final Backend backend = Catalog.getCurrentSystemInfo().getBackend(id); - if (backend.isAlive() && !backend.isDecommissioned() && replica.getState() == ReplicaState.NORMAL) { + if (backend != null && backend.isAlive() && !backend.isDecommissioned() + && replica.getState() == ReplicaState.NORMAL) { replicationOnLine++; } } @@ -627,4 +628,3 @@ public class ReportHandler extends Daemon { } } } - diff --git a/fe/src/com/baidu/palo/mysql/MysqlChannel.java b/fe/src/com/baidu/palo/mysql/MysqlChannel.java index 2ef2457153..a457febfec 100644 --- a/fe/src/com/baidu/palo/mysql/MysqlChannel.java +++ b/fe/src/com/baidu/palo/mysql/MysqlChannel.java @@ -45,7 +45,8 @@ public class MysqlChannel { // default packet byte buffer for most packet private ByteBuffer defaultBuffer = ByteBuffer.allocate(16 * 1024); private ByteBuffer sendBuffer; - private String remoteHostString; + // for log and show + private String remoteHostPortString; private String remoteIp; private boolean isSend; @@ -54,7 +55,7 @@ public class MysqlChannel { this.channel = channel; this.sendBuffer = ByteBuffer.allocate(2 * 1024 * 1024); this.isSend = false; - this.remoteHostString = ""; + this.remoteHostPortString = ""; this.remoteIp = ""; if (channel != null) { @@ -62,11 +63,11 @@ public class MysqlChannel { if (channel.getRemoteAddress() instanceof InetSocketAddress) { InetSocketAddress address = (InetSocketAddress) channel.getRemoteAddress(); // avoid calling getHostName() which may trigger a name service reverse lookup - remoteHostString = address.getHostString() + ":" + address.getPort(); + remoteHostPortString = address.getHostString() + ":" + address.getPort(); remoteIp = address.getAddress().getHostAddress(); } else { // Reach here, what's it? - remoteHostString = channel.getRemoteAddress().toString(); + remoteHostPortString = channel.getRemoteAddress().toString(); remoteIp = channel.getRemoteAddress().toString(); } } catch (Exception e) { @@ -266,7 +267,7 @@ public class MysqlChannel { return isSend; } - public String getRemoteHostString() { - return remoteHostString; + public String getRemoteHostPortString() { + return remoteHostPortString; } } diff --git a/fe/src/com/baidu/palo/mysql/MysqlProto.java b/fe/src/com/baidu/palo/mysql/MysqlProto.java index af03103723..354b1ad81c 100644 --- a/fe/src/com/baidu/palo/mysql/MysqlProto.java +++ b/fe/src/com/baidu/palo/mysql/MysqlProto.java @@ -16,12 +16,12 @@ package com.baidu.palo.mysql; import com.baidu.palo.catalog.Catalog; -import com.baidu.palo.catalog.UserResource; import com.baidu.palo.cluster.ClusterNamespace; import com.baidu.palo.common.Config; import com.baidu.palo.common.DdlException; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; +import com.baidu.palo.mysql.privilege.UserResource; import com.baidu.palo.qe.ConnectContext; import com.baidu.palo.system.SystemInfoService; @@ -38,34 +38,27 @@ public class MysqlProto { private static final Logger LOG = LogManager.getLogger(MysqlProto.class); // scramble: data receive from server. - // randomString: data send by server in plugin data field + // randomString: data send by server in plug-in data field + // user_name#HIGH@cluster_name private static boolean authenticate(ConnectContext context, byte[] scramble, byte[] randomString, String user) { - String usePass = scramble.length == 0 ? "NO" : "YES"; - String clusterName = ""; + String usePasswd = scramble.length == 0 ? "NO" : "YES"; String tmpUser = user; if (tmpUser == null || tmpUser.isEmpty()) { - ErrorReport.report(ErrorCode.ERR_ACCESS_DENIED_ERROR, "", usePass); + ErrorReport.report(ErrorCode.ERR_ACCESS_DENIED_ERROR, "", usePasswd); return false; } - String remoteIp = ""; - // check ip - if (tmpUser.charAt(0) == '@') { - String[] strList = tmpUser.split("@", 3); - if (strList.length != 3) { - ErrorReport.report(ErrorCode.ERR_ACCESS_DENIED_ERROR, strList[1]); - return false; - } - remoteIp = strList[1]; - tmpUser = strList[2]; - } - // check deploy id + // check cluster, user name may contains cluster name or cluster id. + // eg: + // user_name@cluster_name + String clusterName = ""; String[] strList = tmpUser.split("@", 2); if (strList.length > 1) { tmpUser = strList[0]; clusterName = strList[1]; try { + // if cluster does not exist and it is not a valid cluster id, authenticate failed if (Catalog.getInstance().getCluster(clusterName) == null && Integer.valueOf(strList[1]) != context.getCatalog().getClusterId()) { ErrorReport.report(ErrorCode.ERR_UNKNOWN_CLUSTER_ID, strList[1]); @@ -76,7 +69,15 @@ public class MysqlProto { return false; } } + if (Strings.isNullOrEmpty(clusterName)) { + clusterName = SystemInfoService.DEFAULT_CLUSTER; + } + context.setCluster(clusterName); + // check resource group level. user name may contains resource group level. + // eg: + // ...@user_name#HIGH + // set resource group if it is valid, or just ignore it strList = tmpUser.split("#", 2); if (strList.length > 1) { tmpUser = strList[0]; @@ -85,46 +86,17 @@ public class MysqlProto { } } - if (Strings.isNullOrEmpty(clusterName)) { - clusterName = SystemInfoService.DEFAULT_CLUSTER; - } - context.setCluster(clusterName); - - if (Catalog.getInstance().getUserMgr().getPassword(tmpUser) == null) { - tmpUser = ClusterNamespace.getFullName(clusterName, tmpUser); - } - - byte[] userPassword = Catalog.getInstance().getUserMgr().getPassword(tmpUser); - - if (userPassword == null) { - ErrorReport.report(ErrorCode.ERR_ACCESS_DENIED_ERROR, tmpUser, usePass); - return false; - } + LOG.debug("parse cluster: {}", clusterName); + String qualifiedUser = ClusterNamespace.getFullName(clusterName, tmpUser); + String remoteIp = context.getMysqlChannel().getRemoteIp(); - userPassword = MysqlPassword.getSaltFromPassword(userPassword); - - // when the length of password is zero, the user has no password - if ((scramble.length == userPassword.length) - && (scramble.length == 0 || MysqlPassword.checkScramble(scramble, randomString, userPassword))) { - // authenticate success - context.setUser(tmpUser); - } else { - // password check failed. - ErrorReport.report(ErrorCode.ERR_ACCESS_DENIED_ERROR, tmpUser, usePass); + if (!Catalog.getCurrentCatalog().getAuth().checkPassword(qualifiedUser, remoteIp, + scramble, randomString)) { + ErrorReport.report(ErrorCode.ERR_ACCESS_DENIED_ERROR, qualifiedUser, usePasswd); return false; } - // check whitelist - if (remoteIp.equals("")) { - remoteIp = context.getMysqlChannel().getRemoteIp(); - } - boolean ok = context.getCatalog().checkWhiteList(tmpUser, remoteIp); - if (!ok) { - LOG.debug("deny by whiltList. remoteIp={} user={}", context.getMysqlChannel().getRemoteIp(), tmpUser); - ErrorReport.report(ErrorCode.ERR_IP_NOT_ALLOWED, context.getMysqlChannel().getRemoteIp()); - return false; - } - + context.setQualifiedUser(qualifiedUser); return true; } diff --git a/fe/src/com/baidu/palo/mysql/MysqlServer.java b/fe/src/com/baidu/palo/mysql/MysqlServer.java index 766776cf85..99d7c11776 100644 --- a/fe/src/com/baidu/palo/mysql/MysqlServer.java +++ b/fe/src/com/baidu/palo/mysql/MysqlServer.java @@ -110,7 +110,20 @@ public class MysqlServer { context.cleanup(); } } catch (IOException e) { - // some error when accept + // ClosedChannelException + // AsynchronousCloseException + // ClosedByInterruptException + // Other IOException, for example "to many open files" ... + LOG.warn("Query server encounter exception.", e); + try { + Thread.sleep(100); + } catch (InterruptedException e1) { + // Do nothing + } + continue; + } catch (RuntimeException e) { + // NotYetBoundException + // SecurityException LOG.warn("Query server failed when calling accept.", e); return; } diff --git a/fe/src/com/baidu/palo/mysql/privilege/DbPrivEntry.java b/fe/src/com/baidu/palo/mysql/privilege/DbPrivEntry.java new file mode 100644 index 0000000000..da7b6f2fa7 --- /dev/null +++ b/fe/src/com/baidu/palo/mysql/privilege/DbPrivEntry.java @@ -0,0 +1,155 @@ +// Copyright (c) 2018, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +package com.baidu.palo.mysql.privilege; + +import com.baidu.palo.catalog.InfoSchemaDb; +import com.baidu.palo.cluster.ClusterNamespace; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.CaseSensibility; +import com.baidu.palo.common.PatternMatcher; +import com.baidu.palo.common.io.Text; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +public class DbPrivEntry extends PrivEntry { + protected static final String ANY_DB = "*"; + + protected PatternMatcher dbPattern; + protected String origDb; + protected boolean isAnyDb; + + protected DbPrivEntry() { + } + + protected DbPrivEntry(PatternMatcher hostPattern, String origHost, PatternMatcher dbPattern, String origDb, + PatternMatcher userPattern, String user, PrivBitSet privSet) { + super(hostPattern, origHost, userPattern, user, privSet); + this.dbPattern = dbPattern; + this.origDb = origDb; + if (origDb.equals(ANY_DB)) { + isAnyDb = true; + } + } + + public static DbPrivEntry create(String host, String db, String user, PrivBitSet privs) + throws AnalysisException { + PatternMatcher hostPattern = PatternMatcher.createMysqlPattern(host, CaseSensibility.HOST.getCaseSensibility()); + + PatternMatcher dbPattern = createDbPatternMatcher(db); + + PatternMatcher userPattern = PatternMatcher.createMysqlPattern(user, CaseSensibility.USER.getCaseSensibility()); + + if (privs.containsNodeOrGrantPriv()) { + throw new AnalysisException("Db privilege can not contains global privileges: " + privs); + } + + return new DbPrivEntry(hostPattern, host, dbPattern, db, userPattern, user, privs); + } + + private static PatternMatcher createDbPatternMatcher(String db) throws AnalysisException { + // the database 'information_schema''s name is case insensibility. + boolean dbCaseSensibility = CaseSensibility.DATABASE.getCaseSensibility(); + if (ClusterNamespace.getNameFromFullName(db).equalsIgnoreCase(InfoSchemaDb.DATABASE_NAME)) { + dbCaseSensibility = false; + } + + PatternMatcher dbPattern = PatternMatcher.createMysqlPattern(db.equals(ANY_DB) ? "%" : db, dbCaseSensibility); + return dbPattern; + } + + public PatternMatcher getDbPattern() { + return dbPattern; + } + + public String getOrigDb() { + return origDb; + } + + public boolean isAnyDb() { + return isAnyDb; + } + + @Override + public int compareTo(PrivEntry other) { + if (!(other instanceof DbPrivEntry)) { + throw new ClassCastException("cannot cast " + other.getClass().toString() + " to " + this.getClass()); + } + + DbPrivEntry otherEntry = (DbPrivEntry) other; + int res = origHost.compareTo(otherEntry.origHost); + if (res != 0) { + return -res; + } + + res = origDb.compareTo(otherEntry.origDb); + if (res != 0) { + return -res; + } + + return -origUser.compareTo(otherEntry.origUser); + } + + @Override + public boolean keyMatch(PrivEntry other) { + if (!(other instanceof DbPrivEntry)) { + return false; + } + + DbPrivEntry otherEntry = (DbPrivEntry) other; + if (origHost.equals(otherEntry.origHost) && origUser.equals(otherEntry.origUser) + && origDb.equals(otherEntry.origDb)) { + return true; + } + return false; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("db priv. host: ").append(origHost).append(", db: ").append(origDb); + sb.append(", user: ").append(origUser); + sb.append(", priv: ").append(privSet).append(", set by resolver: ").append(isSetByDomainResolver); + return sb.toString(); + } + + @Override + public void write(DataOutput out) throws IOException { + if (!isClassNameWrote) { + String className = DbPrivEntry.class.getCanonicalName(); + Text.writeString(out, className); + isClassNameWrote = true; + } + super.write(out); + Text.writeString(out, origDb); + isClassNameWrote = false; + } + + @Override + public void readFields(DataInput in) throws IOException { + super.readFields(in); + + origDb = Text.readString(in); + try { + dbPattern = createDbPatternMatcher(origDb); + } catch (AnalysisException e) { + throw new IOException(e); + } + isAnyDb = origDb.equals(ANY_DB); + } + +} diff --git a/fe/src/com/baidu/palo/mysql/privilege/DbPrivTable.java b/fe/src/com/baidu/palo/mysql/privilege/DbPrivTable.java new file mode 100644 index 0000000000..05ffcb8bc7 --- /dev/null +++ b/fe/src/com/baidu/palo/mysql/privilege/DbPrivTable.java @@ -0,0 +1,80 @@ +// Copyright (c) 2018, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +package com.baidu.palo.mysql.privilege; + +import com.baidu.palo.common.io.Text; +import com.baidu.palo.qe.ConnectContext; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.DataOutput; +import java.io.IOException; + +public class DbPrivTable extends PrivTable { + private static final Logger LOG = LogManager.getLogger(DbPrivTable.class); + + public void getPrivs(String host, String db, String user, PrivBitSet savedPrivs) { + DbPrivEntry matchedEntry = null; + for (PrivEntry entry : entries) { + DbPrivEntry dbPrivEntry = (DbPrivEntry) entry; + + // check host + if (!dbPrivEntry.isAnyHost() && !dbPrivEntry.getHostPattern().match(host)) { + continue; + } + + // check db + if (!dbPrivEntry.isAnyDb() && !dbPrivEntry.getDbPattern().match(db)) { + continue; + } + + // check user + if (!dbPrivEntry.isAnyUser() && !dbPrivEntry.getUserPattern().match(user)) { + continue; + } + + matchedEntry = dbPrivEntry; + break; + } + if (matchedEntry == null) { + return; + } + + savedPrivs.or(matchedEntry.getPrivSet()); + } + + public boolean hasClusterPriv(ConnectContext ctx, String clusterName) { + for (PrivEntry entry : entries) { + DbPrivEntry dbPrivEntry = (DbPrivEntry) entry; + if (dbPrivEntry.getOrigDb().startsWith(clusterName)) { + return true; + } + } + return false; + } + + @Override + public void write(DataOutput out) throws IOException { + if (!isClassNameWrote) { + String className = DbPrivTable.class.getCanonicalName(); + Text.writeString(out, className); + isClassNameWrote = true; + } + + super.write(out); + } +} diff --git a/fe/src/com/baidu/palo/mysql/privilege/GlobalPrivEntry.java b/fe/src/com/baidu/palo/mysql/privilege/GlobalPrivEntry.java new file mode 100644 index 0000000000..6ab58890a8 --- /dev/null +++ b/fe/src/com/baidu/palo/mysql/privilege/GlobalPrivEntry.java @@ -0,0 +1,145 @@ +// Copyright (c) 2018, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +package com.baidu.palo.mysql.privilege; + +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.CaseSensibility; +import com.baidu.palo.common.PatternMatcher; +import com.baidu.palo.common.io.Text; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +public class GlobalPrivEntry extends PrivEntry { + private static final Logger LOG = LogManager.getLogger(GlobalPrivEntry.class); + + private byte[] password; + + protected GlobalPrivEntry() { + } + + protected GlobalPrivEntry(PatternMatcher hostPattern, String origHost, + PatternMatcher userPattern, String origUser, + byte[] password, PrivBitSet privSet) { + super(hostPattern, origHost, userPattern, origUser, privSet); + this.password = password; + } + + public static GlobalPrivEntry create(String host, String user, byte[] password, + PrivBitSet privs) throws AnalysisException { + PatternMatcher hostPattern = PatternMatcher.createMysqlPattern(host, CaseSensibility.HOST.getCaseSensibility()); + PatternMatcher userPattern = PatternMatcher.createMysqlPattern(user, CaseSensibility.USER.getCaseSensibility()); + return new GlobalPrivEntry(hostPattern, host, userPattern, user, password, privs); + } + + public byte[] getPassword() { + return password; + } + + public void setPassword(byte[] password) { + this.password = password; + } + + /* + * UserTable is ordered by Host, User + * eg: + * +-----------+----------+- + * | Host | User | ... + * +-----------+----------+- + * | % | root | ... + * | % | jeffrey | ... + * | localhost | root | ... + * | localhost | | ... + * +-----------+----------+- + * + * will be sorted like: + * + * +-----------+----------+- + * | Host | User | ... + * +-----------+----------+- + * | localhost | root | ... + * | localhost | | ... + * | % | jeffrey | ... + * | % | root | ... + * +-----------+----------+- + * + * https://dev.mysql.com/doc/refman/8.0/en/connection-access.html + */ + @Override + public int compareTo(PrivEntry other) { + if (!(other instanceof GlobalPrivEntry)) { + throw new ClassCastException("cannot cast " + other.getClass().toString() + " to " + this.getClass()); + } + + GlobalPrivEntry otherEntry = (GlobalPrivEntry) other; + int res = origHost.compareTo(otherEntry.origHost); + if (res != 0) { + return -res; + } + + return -origUser.compareTo(otherEntry.origUser); + } + + @Override + public boolean keyMatch(PrivEntry other) { + if (!(other instanceof GlobalPrivEntry)) { + return false; + } + + GlobalPrivEntry otherEntry = (GlobalPrivEntry) other; + if (origHost.equals(otherEntry.origHost) && origUser.equals(otherEntry.origUser)) { + return true; + } + return false; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("global priv. host: ").append(origHost).append(", user: ").append(origUser); + sb.append(", priv: ").append(privSet).append(", set by resolver: ").append(isSetByDomainResolver); + return sb.toString(); + } + + @Override + public void write(DataOutput out) throws IOException { + if (!isClassNameWrote) { + String className = GlobalPrivEntry.class.getCanonicalName(); + Text.writeString(out, className); + isClassNameWrote = true; + } + + LOG.info("global priv: {}", this.toString()); + super.write(out); + + out.writeInt(password.length); + out.write(password); + isClassNameWrote = false; + } + + @Override + public void readFields(DataInput in) throws IOException { + super.readFields(in); + int passwordLen = in.readInt(); + password = new byte[passwordLen]; + in.readFully(password); + } + +} diff --git a/fe/src/com/baidu/palo/mysql/privilege/PaloAuth.java b/fe/src/com/baidu/palo/mysql/privilege/PaloAuth.java new file mode 100644 index 0000000000..ebf5395224 --- /dev/null +++ b/fe/src/com/baidu/palo/mysql/privilege/PaloAuth.java @@ -0,0 +1,1276 @@ +// Copyright (c) 2018, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +package com.baidu.palo.mysql.privilege; + +import com.baidu.palo.analysis.CreateRoleStmt; +import com.baidu.palo.analysis.CreateUserStmt; +import com.baidu.palo.analysis.DropRoleStmt; +import com.baidu.palo.analysis.DropUserStmt; +import com.baidu.palo.analysis.GrantStmt; +import com.baidu.palo.analysis.RevokeStmt; +import com.baidu.palo.analysis.SetPassVar; +import com.baidu.palo.analysis.SetUserPropertyStmt; +import com.baidu.palo.analysis.TablePattern; +import com.baidu.palo.analysis.UserIdentity; +import com.baidu.palo.catalog.AccessPrivilege; +import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.catalog.InfoSchemaDb; +import com.baidu.palo.cluster.ClusterNamespace; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.Config; +import com.baidu.palo.common.DdlException; +import com.baidu.palo.common.ErrorCode; +import com.baidu.palo.common.ErrorReport; +import com.baidu.palo.common.FeMetaVersion; +import com.baidu.palo.common.Pair; +import com.baidu.palo.common.io.Writable; +import com.baidu.palo.load.DppConfig; +import com.baidu.palo.persist.PrivInfo; +import com.baidu.palo.qe.ConnectContext; +import com.baidu.palo.thrift.TFetchResourceResult; + +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public class PaloAuth implements Writable { + private static final Logger LOG = LogManager.getLogger(PaloAuth.class); + + // root user's role is operator. + // each Palo system has only one root user. + public static final String ROOT_USER = "root"; + public static final String ADMIN_USER = "admin"; + + private UserPrivTable userPrivTable = new UserPrivTable(); + private DbPrivTable dbPrivTable = new DbPrivTable(); + private TablePrivTable tablePrivTable = new TablePrivTable(); + + private RoleManager roleManager = new RoleManager();; + private UserPropertyMgr propertyMgr = new UserPropertyMgr(); + + private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + + private void readLock() { + lock.readLock().lock(); + } + + private void readUnlock() { + lock.readLock().unlock(); + } + + private void writeLock() { + lock.writeLock().lock(); + } + + private void writeUnlock() { + lock.writeLock().unlock(); + } + + public enum PrivLevel { + GLOBAL, DATABASE, TABLE + } + + public PaloAuth() { + initUser(); + } + + public UserPrivTable getUserPrivTable() { + return userPrivTable; + } + + public DbPrivTable getDbPrivTable() { + return dbPrivTable; + } + + public TablePrivTable getTablePrivTable() { + return tablePrivTable; + } + + private GlobalPrivEntry grantGlobalPrivs(String host, String user, byte[] password, + boolean errOnExist, boolean errOnNonExist, boolean grantByResolver, PrivBitSet privs) + throws DdlException { + if (errOnExist && errOnNonExist) { + throw new DdlException("Can only specified errOnExist or errOnNonExist"); + } + GlobalPrivEntry entry; + try { + entry = GlobalPrivEntry.create(host, user, password, privs); + entry.setSetByDomainResolver(grantByResolver); + } catch (AnalysisException e) { + throw new DdlException(e.getMessage()); + } + userPrivTable.addEntry(entry, errOnExist, errOnNonExist); + return entry; + } + + private void revokeGlobalPrivs(String host, String user, PrivBitSet privs, boolean revokeByResovler, + boolean errOnNonExist, boolean deleteEntryWhenEmpty) throws DdlException { + GlobalPrivEntry entry; + try { + entry = GlobalPrivEntry.create(host, user, new byte[0], privs); + entry.setSetByDomainResolver(revokeByResovler); + } catch (AnalysisException e) { + throw new DdlException(e.getMessage()); + } + if (!userPrivTable.revoke(entry, errOnNonExist, deleteEntryWhenEmpty)) { + ErrorReport.reportDdlException(ErrorCode.ERR_NONEXISTING_GRANT, user, host); + } + } + + private void grantDbPrivs(String host, String db, String user, boolean errOnExist, boolean errOnNonExist, + boolean grantByResolver, PrivBitSet privs) throws DdlException { + DbPrivEntry entry; + try { + entry = DbPrivEntry.create(host, db, user, privs); + entry.setSetByDomainResolver(grantByResolver); + } catch (AnalysisException e) { + throw new DdlException(e.getMessage()); + } + dbPrivTable.addEntry(entry, errOnExist, errOnNonExist); + } + + private void revokeDbPrivs(String host, String db, String user, PrivBitSet privs, boolean setByResolver, + boolean errOnNonExist) throws DdlException { + DbPrivEntry entry; + try { + entry = DbPrivEntry.create(host, db, user, privs); + entry.setSetByDomainResolver(setByResolver); + } catch (AnalysisException e) { + throw new DdlException(e.getMessage()); + } + + if (!dbPrivTable.revoke(entry, errOnNonExist, true /* delete entry when empty */)) { + ErrorReport.reportDdlException(ErrorCode.ERR_NONEXISTING_GRANT, user, host); + } + } + + private void grantTblPrivs(String host, String db, String user, String tbl, boolean errOnExist, + boolean errOnNonExist, boolean grantByDomain, PrivBitSet privs) throws DdlException { + TablePrivEntry entry; + try { + entry = TablePrivEntry.create(host, db, user, tbl, privs); + entry.setSetByDomainResolver(grantByDomain); + } catch (AnalysisException e) { + throw new DdlException(e.getMessage()); + } + tablePrivTable.addEntry(entry, errOnExist, errOnNonExist); + } + + private void revokeTblPrivs(String host, String db, String user, String tbl, PrivBitSet privs, + boolean setByResolver, boolean errOnNonExist) throws DdlException { + TablePrivEntry entry; + try { + entry = TablePrivEntry.create(host, db, user, tbl, privs); + entry.setSetByDomainResolver(setByResolver); + } catch (AnalysisException e) { + throw new DdlException(e.getMessage()); + } + if (!tablePrivTable.revoke(entry, errOnNonExist, true /* delete entry when empty */)) { + ErrorReport.reportDdlException(ErrorCode.ERR_NONEXISTING_GRANT, user, host); + } + } + + public boolean checkPassword(String remoteUser, String remoteHost, byte[] remotePasswd, byte[] randomString) { + if (!Config.enable_auth_check) { + return true; + } + if ((remoteUser.equals(ROOT_USER) || remoteUser.equals(ADMIN_USER)) && remoteHost.equals("127.0.0.1")) { + // root and admin user is allowed to login from 127.0.0.1, in case user forget password. + return true; + } + + readLock(); + try { + return userPrivTable.checkPassword(remoteUser, remoteHost, remotePasswd, randomString); + } finally { + readUnlock(); + } + } + + public boolean checkPlainPassword(String remoteUser, String remoteHost, String remotePasswd) { + if (!Config.enable_auth_check) { + return true; + } + readLock(); + try { + return userPrivTable.checkPlainPassword(remoteUser, remoteHost, remotePasswd); + } finally { + readUnlock(); + } + } + + public boolean checkGlobalPriv(ConnectContext ctx, PrivPredicate wanted) { + return checkGlobalPriv(ctx.getRemoteIP(), ctx.getQualifiedUser(), wanted); + } + + public boolean checkGlobalPriv(String host, String user, PrivPredicate wanted) { + if (!Config.enable_auth_check) { + return true; + } + PrivBitSet savedPrivs = PrivBitSet.of(); + if (checkGlobalInternal(host, user, wanted, savedPrivs)) { + return true; + } + + LOG.debug("failed to get wanted privs: {}, ganted: {}", wanted, savedPrivs); + return false; + } + + public boolean checkDbPriv(ConnectContext ctx, String qualifiedDb, PrivPredicate wanted) { + return checkDbPriv(ctx.getRemoteIP(), qualifiedDb, ctx.getQualifiedUser(), wanted); + } + + public boolean checkDbPriv(String host, String db, String user, PrivPredicate wanted) { + if (!Config.enable_auth_check) { + return true; + } + if (wanted.getPrivs().containsNodeOrGrantPriv()) { + LOG.debug("should be check NODE or GRANT priv in Global level. host: {}, user: {}, db: {}", + host, user, db); + return false; + } + + PrivBitSet savedPrivs = PrivBitSet.of(); + if (checkGlobalInternal(host, user, wanted, savedPrivs) + || checkDbInternal(host, db, user, wanted, savedPrivs)) { + return true; + } + + // if user has any privs of table in this db, and the wanted priv is SHOW, return true + if (wanted == PrivPredicate.SHOW && checkTblWithDb(host, db, user)) { + return true; + } + + LOG.debug("failed to get wanted privs: {}, ganted: {}", wanted, savedPrivs); + return false; + } + + /* + * User may not have privs on a database, but have privs of tables in this database. + * So we have to check if user has any privs of tables in this database. + * if so, the database should be visible to this user. + */ + private boolean checkTblWithDb(String host, String db, String user) { + readLock(); + try { + return tablePrivTable.hasPrivsOfDb(host, db, user); + } finally { + readUnlock(); + } + } + + public boolean checkTblPriv(ConnectContext ctx, String qualifiedDb, String tbl, PrivPredicate wanted) { + return checkTblPriv(ctx.getRemoteIP(), qualifiedDb, ctx.getQualifiedUser(), tbl, wanted); + } + + public boolean checkTblPriv(String host, String db, String user, String tbl, PrivPredicate wanted) { + if (!Config.enable_auth_check) { + return true; + } + if (wanted.getPrivs().containsNodeOrGrantPriv()) { + LOG.debug("should be check NODE or GRANT priv in Db level. host: {}, user: {}, db: {}", + host, user, db); + return false; + } + + PrivBitSet savedPrivs = PrivBitSet.of(); + if (checkGlobalInternal(host, user, wanted, savedPrivs) + || checkDbInternal(host, db, user, wanted, savedPrivs) + || checkTblInternal(host, db, user, tbl, wanted, savedPrivs)) { + return true; + } + + LOG.debug("failed to get wanted privs: {}, ganted: {}", wanted, savedPrivs); + return false; + } + + private boolean checkGlobalInternal(String host, String user, PrivPredicate wanted, PrivBitSet savedPrivs) { + readLock(); + try { + userPrivTable.getPrivs(host, user, savedPrivs); + if (PaloPrivilege.satisfy(savedPrivs, wanted)) { + return true; + } + return false; + } finally { + readUnlock(); + } + } + + private boolean checkDbInternal(String host, String db, String user, PrivPredicate wanted, + PrivBitSet savedPrivs) { + readLock(); + try { + dbPrivTable.getPrivs(host, db, user, savedPrivs); + if (PaloPrivilege.satisfy(savedPrivs, wanted)) { + return true; + } + } finally { + readUnlock(); + } + return false; + } + + private boolean checkTblInternal(String host, String db, String user, String tbl, + PrivPredicate wanted, PrivBitSet savedPrivs) { + readLock(); + try { + tablePrivTable.getPrivs(host, db, user, tbl, savedPrivs); + if (PaloPrivilege.satisfy(savedPrivs, wanted)) { + return true; + } + return false; + } finally { + readUnlock(); + } + } + + // for test only + public void clear() { + userPrivTable.clear(); + dbPrivTable.clear(); + tablePrivTable.clear(); + } + + // create user + public void createUser(CreateUserStmt stmt) throws DdlException { + createUserInternal(stmt.getUserIdent(), stmt.getQualifiedRole(), stmt.getPassword(), false); + } + + public void replayCreateUser(PrivInfo privInfo) { + try { + createUserInternal(privInfo.getUserIdent(), privInfo.getRole(), privInfo.getPasswd(), true); + } catch (DdlException e) { + LOG.error("should not happen", e); + } + } + + private void createUserInternal(UserIdentity userIdent, String roleName, byte[] password, + boolean isReplay) throws DdlException { + writeLock(); + try { + PaloRole role = null; + if (roleName != null) { + // get privs of role + role = roleManager.getRole(roleName); + if (role == null) { + throw new DdlException("Role: " + roleName + " does not exist"); + } + } + + if (userIdent.isDomain()) { + // the host here is a domain name, add it to whitelist. + Map privsMap = Maps.newHashMap(); + if (role != null) { + // grant privileges of role to this whitelist + privsMap = role.getTblPatternToPrivs(); + } + propertyMgr.addOrGrantWhiteList(userIdent, privsMap, password, true /* err on exist */, + false /* err on non exist*/); + + } else { + // check if user already exist + GlobalPrivEntry dummyEntry = null; + try { + dummyEntry = GlobalPrivEntry.create(userIdent.getHost(), userIdent.getQualifiedUser(), null, + PrivBitSet.of()); + } catch (AnalysisException e) { + LOG.error("should not happen", e); + } + if (userPrivTable.getExistingEntry(dummyEntry) != null) { + throw new DdlException("User " + userIdent + " already exist"); + } + + if (role != null) { + // grant privs of role to user + for (Map.Entry entry : role.getTblPatternToPrivs().entrySet()) { + // use PrivBitSet copy to avoid same object being changed synchronously + grantInternal(userIdent, null, entry.getKey(), entry.getValue().copy(), + false /* not set by domain */, + false /* err on non exist */, true /* is replay */); + } + } + + // set password field of global priv entry + // the global entry may or may not exist + setPasswordInternal(userIdent, password, true /* add if not exist */, + false /* set by resolver */, true); + } + + if (role != null) { + // add user to this role + role.addUser(userIdent); + } + + // other user properties + propertyMgr.addUserResource(userIdent.getQualifiedUser(), false /* not system user */); + + if (!userIdent.getQualifiedUser().equals(ROOT_USER) && !userIdent.getQualifiedUser().equals(ADMIN_USER)) { + // grant read privs to database information_schema + TablePattern tblPattern = new TablePattern(InfoSchemaDb.DATABASE_NAME, "*"); + try { + tblPattern.analyze(ClusterNamespace.getClusterNameFromFullName(userIdent.getQualifiedUser())); + } catch (AnalysisException e) { + LOG.warn("should not happen", e); + } + grantInternal(userIdent, null, tblPattern, PrivBitSet.of(PaloPrivilege.SELECT_PRIV), + false, false /* err on non exist */, true /* is replay */); + } + + if (!isReplay) { + PrivInfo privInfo = new PrivInfo(userIdent, null, null, password, roleName); + Catalog.getCurrentCatalog().getEditLog().logCreateUser(privInfo); + } + LOG.info("finished to create user: {}, is replay: {}", userIdent, isReplay); + } finally { + writeUnlock(); + } + } + + // drop user + public void dropUser(DropUserStmt stmt) throws DdlException { + dropUserInternal(stmt.getUserIdentity(), false); + } + + public void replayDropUser(UserIdentity userIdent) { + dropUserInternal(userIdent, true); + } + + public void replayOldDropUser(String userName) { + UserIdentity userIdentity = new UserIdentity(userName, "%"); + userIdentity.setIsAnalyzed(); + dropUserInternal(userIdentity, true /* is replay */); + } + + private void dropUserInternal(UserIdentity userIdent, boolean isReplay) { + writeLock(); + try { + // we don't check if user exists + userPrivTable.dropUser(userIdent.getQualifiedUser()); + dbPrivTable.dropUser(userIdent.getQualifiedUser()); + tablePrivTable.dropUser(userIdent.getQualifiedUser()); + + // drop user in roles if exist + roleManager.dropUser(userIdent.getQualifiedUser()); + + // drop user property + propertyMgr.dropUser(userIdent.getQualifiedUser()); + + if (!isReplay) { + Catalog.getCurrentCatalog().getEditLog().logNewDropUser(userIdent); + } + LOG.info("finished to drop user: {}, is replay: {}", userIdent.getQualifiedUser(), isReplay); + } finally { + writeUnlock(); + } + } + + // grant + public void grant(GrantStmt stmt) throws DdlException { + PrivBitSet privs = PrivBitSet.of(stmt.getPrivileges()); + grantInternal(stmt.getUserIdent(), stmt.getQualifiedRole(), stmt.getTblPattern(), privs, + false /* not set by domain */, true /* err on non exist */, false /* not replay */); + } + + public void replayGrant(PrivInfo privInfo) { + try { + grantInternal(privInfo.getUserIdent(), privInfo.getRole(), + privInfo.getTblPattern(), privInfo.getPrivs(), + false /* not set by domain */, true /* err on non exist */, true /* is replay */); + } catch (DdlException e) { + LOG.error("should not happen", e); + } + } + + private void grantInternal(UserIdentity userIdent, String role, TablePattern tblPattern, + PrivBitSet privs, boolean grantByResolver, boolean errOnNonExist, boolean isReplay) + throws DdlException { + writeLock(); + try { + if (role != null) { + // grant privs to role, role must exist + PaloRole newRole = new PaloRole(role, tblPattern, privs); + PaloRole existingRole = roleManager.addRole(newRole, false /* err on exist */); + + // update users' privs of this role + for (UserIdentity user : existingRole.getUsers()) { + if (user.isDomain()) { + propertyMgr.addOrGrantWhiteList(user, existingRole.getTblPatternToPrivs(), + null, false /* err on exist */, + false /* err on non exist */); + } else { + for (Map.Entry entry : existingRole.getTblPatternToPrivs().entrySet()) { + // copy the PrivBitSet + grantPrivs(user, entry.getKey(), entry.getValue().copy(), errOnNonExist, grantByResolver); + } + } + } + } else { + if (userIdent.isDomain()) { + // grant privs to whitelist + Map privsMap = Maps.newHashMap(); + privsMap.put(tblPattern, privs); + propertyMgr.addOrGrantWhiteList(userIdent, privsMap, null, false /* err on exist */, + true /* err on non exist */); + } else { + grantPrivs(userIdent, tblPattern, privs, errOnNonExist, grantByResolver); + } + } + + if (!isReplay) { + PrivInfo info = new PrivInfo(userIdent, tblPattern, privs, null, role); + Catalog.getCurrentCatalog().getEditLog().logGrantPriv(info); + } + LOG.info("finished to grant privilege. is replay: {}", isReplay); + } finally { + writeUnlock(); + } + } + + public void grantPrivs(UserIdentity userIdent, TablePattern tblPattern, PrivBitSet privs, + boolean errOnNonExist, boolean grantByResolver) throws DdlException { + + LOG.debug("grant {} on {} to {}, err on non exist: {}, grant by resovler: {}", + privs, tblPattern, userIdent, errOnNonExist, grantByResolver); + + writeLock(); + try { + // check is user identity already exist + if (errOnNonExist && !userPrivTable.doesUserExist(userIdent, true /* exact match */)) { + throw new DdlException("user " + userIdent + " does not exist"); + } + + // grant privs to user + switch (tblPattern.getPrivLevel()) { + case GLOBAL: + grantGlobalPrivs(userIdent.getHost(), + userIdent.getQualifiedUser(), + new byte[0], + false /* err on exist */, + errOnNonExist, + grantByResolver, privs); + break; + case DATABASE: + grantDbPrivs(userIdent.getHost(), tblPattern.getQuolifiedDb(), + userIdent.getQualifiedUser(), + false /* err on exist */, + false /* err on non exist */, + grantByResolver, + privs); + break; + case TABLE: + grantTblPrivs(userIdent.getHost(), tblPattern.getQuolifiedDb(), + userIdent.getQualifiedUser(), tblPattern.getTbl(), + false /* err on exist */, + false /* err on non exist */, + grantByResolver, + privs); + break; + default: + Preconditions.checkNotNull(null, tblPattern.getPrivLevel()); + } + } finally { + writeUnlock(); + } + } + + // revoke + public void revoke(RevokeStmt stmt) throws DdlException { + PrivBitSet privs = PrivBitSet.of(stmt.getPrivileges()); + revokeInternal(stmt.getUserIdent(), stmt.getQualifiedRole(), stmt.getTblPattern(), privs, + true /* err on non exist */, false /* is replay */); + } + + public void replayRevoke(PrivInfo info) { + try { + revokeInternal(info.getUserIdent(), info.getRole(), info.getTblPattern(), info.getPrivs(), + true /* err on non exist */, true /* is replay */); + } catch (DdlException e) { + LOG.error("should not happend", e); + } + } + + private void revokeInternal(UserIdentity userIdent, String role, TablePattern tblPattern, + PrivBitSet privs, boolean errOnNonExist, boolean isReplay) throws DdlException { + writeLock(); + try { + if (role != null) { + // revoke privs from role + PaloRole existingRole = roleManager.revokePrivs(role, tblPattern, privs, + true /* err on non exist */); + Preconditions.checkNotNull(existingRole); + + // revoke privs from users of this role + for (UserIdentity user : existingRole.getUsers()) { + if (user.isDomain()) { + propertyMgr.revokePrivsFromWhiteList(user, existingRole.getTblPatternToPrivs(), + false /* err on non exist */); + } else { + revokePrivs(user, tblPattern, privs, false /* set by resolver */, + false /* err on non exist */, true /* delete entry when empty */); + } + } + } else { + if (userIdent.isDomain()) { + Map privsMap = Maps.newHashMap(); + privsMap.put(tblPattern, privs); + propertyMgr.revokePrivsFromWhiteList(userIdent, privsMap, errOnNonExist /* err on non exist */); + } else { + // revoke privs from user + revokePrivs(userIdent, tblPattern, privs, false /* set by resolver */, errOnNonExist, + false /* delete entry when empty */); + } + } + + if (!isReplay) { + PrivInfo info = new PrivInfo(userIdent, tblPattern, privs, null, role); + Catalog.getCurrentCatalog().getEditLog().logRevokePriv(info); + } + LOG.info("finished to revoke privilege. is replay: {}", isReplay); + } finally { + writeUnlock(); + } + } + + public void revokePrivs(UserIdentity userIdent, TablePattern tblPattern, PrivBitSet privs, + boolean setByResolver, boolean errOnNonExist, boolean deleteEntryWhenEmpty) throws DdlException { + writeLock(); + try { + switch (tblPattern.getPrivLevel()) { + case GLOBAL: + revokeGlobalPrivs(userIdent.getHost(), userIdent.getQualifiedUser(), privs, setByResolver, + errOnNonExist, deleteEntryWhenEmpty); + break; + case DATABASE: + revokeDbPrivs(userIdent.getHost(), tblPattern.getQuolifiedDb(), + userIdent.getQualifiedUser(), privs, setByResolver, + errOnNonExist); + break; + case TABLE: + revokeTblPrivs(userIdent.getHost(), tblPattern.getQuolifiedDb(), + userIdent.getQualifiedUser(), tblPattern.getTbl(), privs, setByResolver, + errOnNonExist); + break; + default: + Preconditions.checkNotNull(null, tblPattern.getPrivLevel()); + } + } finally { + writeUnlock(); + } + } + + // set password + public void setPassword(SetPassVar stmt) throws DdlException { + setPasswordInternal(stmt.getUserIdent(), stmt.getPassword(), false /* add if not exist */, + false /* set by resolver */, false); + } + + public void replaySetPassword(PrivInfo info) { + try { + setPasswordInternal(info.getUserIdent(), info.getPasswd(), false /* add if not exist */, + false /* set by resolver */, true); + } catch (DdlException e) { + LOG.error("should not happend", e); + } + } + + public void setPasswordInternal(UserIdentity userIdent, byte[] password, + boolean addIfNotExist, boolean setByResolver, boolean isReplay) throws DdlException { + writeLock(); + try { + if (userIdent.isDomain()) { + // throw exception is user ident does not exist + propertyMgr.setPassword(userIdent, password); + } else { + GlobalPrivEntry passwdEntry; + try { + passwdEntry = GlobalPrivEntry.create(userIdent.getHost(), userIdent.getQualifiedUser(), + password, PrivBitSet.of()); + passwdEntry.setSetByDomainResolver(setByResolver); + } catch (AnalysisException e) { + throw new DdlException(e.getMessage()); + } + + userPrivTable.setPassword(passwdEntry, addIfNotExist); + } + + if (!isReplay) { + PrivInfo info = new PrivInfo(userIdent, null, null, password, null); + Catalog.getCurrentCatalog().getEditLog().logSetPassword(info); + } + } finally { + writeUnlock(); + } + LOG.info("finished to set password for {}. is replay: {}", userIdent, isReplay); + } + + // create role + public void createRole(CreateRoleStmt stmt) throws DdlException { + createRoleInternal(stmt.getQualifiedRole(), false); + } + + public void replayCreateRole(PrivInfo info) { + try { + createRoleInternal(info.getRole(), true); + } catch (DdlException e) { + LOG.error("should not happend", e); + } + } + + private void createRoleInternal(String role, boolean isReplay) throws DdlException { + PaloRole emptyPrivsRole = new PaloRole(role); + writeLock(); + try { + roleManager.addRole(emptyPrivsRole, true /* err on exist */); + + if (!isReplay) { + PrivInfo info = new PrivInfo(null, null, null, null, role); + Catalog.getCurrentCatalog().getEditLog().logCreateRole(info); + } + } finally { + writeUnlock(); + } + LOG.info("finished to create role: {}, is replay: {}", role, isReplay); + } + + // drop role + public void dropRole(DropRoleStmt stmt) throws DdlException { + dropRoleInternal(stmt.getQualifiedRole(), false); + } + + public void replayDropRole(PrivInfo info) { + try { + dropRoleInternal(info.getRole(), true); + } catch (DdlException e) { + LOG.error("should not happend", e); + } + } + + private void dropRoleInternal(String role, boolean isReplay) throws DdlException { + writeLock(); + try { + roleManager.dropRole(role, true /* err on non exist */); + + if (!isReplay) { + PrivInfo info = new PrivInfo(null, null, null, null, role); + Catalog.getCurrentCatalog().getEditLog().logDropRole(info); + } + } finally { + writeUnlock(); + } + LOG.info("finished to drop role: {}, is replay: {}", role, isReplay); + } + + public long getMaxConn(String qualifiedUser) { + readLock(); + try { + return propertyMgr.getMaxConn(qualifiedUser); + } finally { + readUnlock(); + } + } + + public void getCopiedWhiteList(Map> userMap) { + readLock(); + try { + propertyMgr.getCopiedWhiteList(userMap); + } finally { + readUnlock(); + } + } + + public void updateResolovedIps(String qualifiedUser, String domain, Set resolvedIPs) { + writeLock(); + try { + propertyMgr.updateResolovedIps(qualifiedUser, domain, resolvedIPs); + } finally { + writeUnlock(); + } + } + + public List> getAuthInfo(UserIdentity specifiedUserIdent, boolean isAll) { + List> userAuthInfos = Lists.newArrayList(); + + readLock(); + try { + if (specifiedUserIdent == null) { + if (isAll) { + Set userIdents = getAllUserIdents(false /* include entry set by resolver */); + for (UserIdentity userIdent : userIdents) { + getUserAuthInfo(userAuthInfos, userIdent, true /* exact match */); + } + + // get grants from whitelist + propertyMgr.getUserAuthInfo(userAuthInfos, null); + } else { + Set userIdents = getAllUserIdents(true /* include entry set by resolver */); + for (UserIdentity userIdent : userIdents) { + getUserAuthInfo(userAuthInfos, userIdent, true /* exact match */); + } + } + } else { + if (specifiedUserIdent.isDomain()) { + propertyMgr.getUserAuthInfo(userAuthInfos, specifiedUserIdent); + } else { + getUserAuthInfo(userAuthInfos, specifiedUserIdent, false /* exact match */); + } + } + } finally { + readUnlock(); + } + return userAuthInfos; + } + + private void getUserAuthInfo(List> userAuthInfos, UserIdentity userIdent, + boolean exactMatch) { + List userAuthInfo = Lists.newArrayList(); + + // global + for (PrivEntry entry : userPrivTable.entries) { + if (!entry.match(userIdent, exactMatch)) { + continue; + } + GlobalPrivEntry gEntry = (GlobalPrivEntry) entry; + userAuthInfo.add(userIdent.toString()); + userAuthInfo.add((gEntry.getPassword() == null || gEntry.getPassword().length == 0) ? "No" : "Yes"); + userAuthInfo.add(gEntry.getPrivSet().toString() + " (" + gEntry.isSetByDomainResolver() + ")"); + break; + } + if (userAuthInfo.isEmpty()) { + // This may happen when we grant non global privs to a non exist user via GRANT stmt. + userAuthInfo.add(userIdent.toString()); + userAuthInfo.add("N/A"); + userAuthInfo.add("N/A"); + } + + // db + List dbPrivs = Lists.newArrayList(); + for (PrivEntry entry : dbPrivTable.entries) { + if (!entry.match(userIdent, exactMatch)) { + continue; + } + DbPrivEntry dEntry = (DbPrivEntry) entry; + dbPrivs.add(dEntry.getOrigDb() + ": " + dEntry.getPrivSet().toString() + + " (" + entry.isSetByDomainResolver() + ")"); + } + if (dbPrivs.isEmpty()) { + userAuthInfo.add("N/A"); + } else { + userAuthInfo.add(Joiner.on("; ").join(dbPrivs)); + } + + // tbl + List tblPrivs = Lists.newArrayList(); + for (PrivEntry entry : tablePrivTable.entries) { + if (!entry.match(userIdent, exactMatch)) { + continue; + } + TablePrivEntry tEntry = (TablePrivEntry) entry; + tblPrivs.add(tEntry.getOrigDb() + "." + tEntry.getOrigTbl() + ": " + + tEntry.getPrivSet().toString() + + " (" + entry.isSetByDomainResolver() + ")"); + } + if (tblPrivs.isEmpty()) { + userAuthInfo.add("N/A"); + } else { + userAuthInfo.add(Joiner.on("; ").join(tblPrivs)); + } + + userAuthInfos.add(userAuthInfo); + } + + private Set getAllUserIdents(boolean includeEntrySetByResolver) { + Set userIdents = Sets.newHashSet(); + for (PrivEntry entry : userPrivTable.entries) { + if (!includeEntrySetByResolver && entry.isSetByDomainResolver()) { + continue; + } + userIdents.add(entry.getUserIdent()); + } + for (PrivEntry entry : dbPrivTable.entries) { + if (!includeEntrySetByResolver && entry.isSetByDomainResolver()) { + continue; + } + userIdents.add(entry.getUserIdent()); + } + for (PrivEntry entry : tablePrivTable.entries) { + if (!includeEntrySetByResolver && entry.isSetByDomainResolver()) { + continue; + } + userIdents.add(entry.getUserIdent()); + } + return userIdents; + } + + public List> getUserProperties(String qualifiedUser) { + readLock(); + try { + return propertyMgr.fetchUserProperty(qualifiedUser); + } catch (AnalysisException e) { + return Lists.newArrayList(); + } finally { + readUnlock(); + } + } + + public void dropUserOfCluster(String clusterName, boolean isReplay) { + writeLock(); + try { + Set allUserIdents = getAllUserIdents(true); + for (UserIdentity userIdent : allUserIdents) { + if (userIdent.getQualifiedUser().startsWith(clusterName)) { + dropUserInternal(userIdent, isReplay); + } + } + } finally { + writeUnlock(); + } + } + + public void updateUserProperty(SetUserPropertyStmt ddlStmt) throws DdlException { + writeLock(); + try { + propertyMgr.updateUserProperty(ddlStmt); + } finally { + writeUnlock(); + } + } + + public Pair getLoadClusterInfo(String qualifiedUser, String cluster) throws DdlException { + readLock(); + try { + return propertyMgr.getLoadClusterInfo(qualifiedUser, cluster); + } finally { + readUnlock(); + } + } + + public void transformAndAddOldUserProperty(UserProperty userProperty) { + Preconditions.checkState(Catalog.getCurrentCatalogJournalVersion() <= FeMetaVersion.VERSION_43); + writeLock(); + try { + // for forward compatibility, we need to transfer the old form of privilege to the new form. + LOG.info("begin to transfer old user property: {}", userProperty.getQualifiedUser()); + + if (userProperty.isAdmin()) { + UserIdentity userIdent = new UserIdentity(userProperty.getQualifiedUser(), "%"); + userIdent.setIsAnalyzed(); + Map adminPrivs = PaloRole.OPERATOR.getTblPatternToPrivs(); + for (Map.Entry entry : adminPrivs.entrySet()) { + try { + grantPrivs(userIdent, entry.getKey(), entry.getValue(), + false /* err on non exist */, false /* grant by resolver */); + } catch (DdlException e) { + LOG.error("should not happen", e); + } + } + + try { + setPasswordInternal(userIdent, userProperty.getPassword(), true /* add if not exist */, + false /* set by resolver */, true); + } catch (DdlException e) { + LOG.warn("should not happen", e); + } + + } else if (userProperty.isSuperuser()) { + UserIdentity userIdent = new UserIdentity(userProperty.getQualifiedUser(), "%"); + userIdent.setIsAnalyzed(); + Map adminPrivs = PaloRole.ADMIN.getTblPatternToPrivs(); + for (Map.Entry entry : adminPrivs.entrySet()) { + try { + grantPrivs(userIdent, entry.getKey(), entry.getValue(), + false /* err on non exist */, false /* grant by resolver */); + } catch (DdlException e) { + LOG.error("should not happen", e); + } + } + + try { + setPasswordInternal(userIdent, userProperty.getPassword(), true /* add if not exist */, + false /* set by resolver */, true); + } catch (DdlException e) { + LOG.warn("should not happen", e); + } + + } else { // normal user + + Set ipWhiteList = userProperty.getWhiteList().getIpWhiteLists(); + Set starIpWhiteList = userProperty.getWhiteList().getStarIpWhiteLists(); + Map privsMap = Maps.newHashMap(); + + // 1. get all privs and save them to privsMap + for (Map.Entry entry : userProperty.getDbPrivMap().entrySet()) { + PrivBitSet privs = null; + switch (entry.getValue()) { + case READ_ONLY: + privs = PrivBitSet.of(PaloPrivilege.SELECT_PRIV); + break; + case READ_WRITE: + case ALL: + privs = PrivBitSet.of(PaloPrivilege.SELECT_PRIV, PaloPrivilege.LOAD_PRIV, + PaloPrivilege.ALTER_PRIV, PaloPrivilege.CREATE_PRIV, + PaloPrivilege.DROP_PRIV); + break; + default: + Preconditions.checkState(false, entry.getValue()); + break; + } + + TablePattern tblPattern = new TablePattern(ClusterNamespace.getNameFromFullName(entry.getKey()), + "*"); + try { + tblPattern.analyze(ClusterNamespace.getClusterNameFromFullName(entry.getKey())); + } catch (AnalysisException e) { + LOG.error("should not happen", e); + } + privsMap.put(tblPattern, privs); + } + + if (!ipWhiteList.isEmpty() || !starIpWhiteList.isEmpty()) { + // 2. handle the old whitelist + for (String ip : ipWhiteList) { + UserIdentity userIdent = new UserIdentity(userProperty.getQualifiedUser(), ip); + userIdent.setIsAnalyzed(); + // 1. set password + try { + setPasswordInternal(userIdent, userProperty.getPassword(), + true /* add if not exist */, + false /* set by resolver */, + true /* is replay */); + } catch (DdlException e) { + LOG.error("should not happen", e); + } + + // 2. set privs + for (Map.Entry entry : privsMap.entrySet()) { + try { + grantPrivs(userIdent, entry.getKey(), entry.getValue(), + false /* err on non exist */, false /* grant by resolver */); + } catch (DdlException e) { + LOG.error("should not happen", e); + } + } + } + + for (String starIp : starIpWhiteList) { + starIp = starIp.replaceAll("\\*", "%"); + UserIdentity userIdent = new UserIdentity(userProperty.getQualifiedUser(), starIp); + userIdent.setIsAnalyzed(); + // 1. set password + try { + setPasswordInternal(userIdent, userProperty.getPassword(), + true /* add if not exist */, + false /* set by resolver */, + true /* is replay */); + } catch (DdlException e) { + LOG.error("should not happen", e); + } + + // 2. set privs + for (Map.Entry entry : privsMap.entrySet()) { + try { + grantPrivs(userIdent, entry.getKey(), entry.getValue(), + false /* err on non exist */, false /* grant by resolver */); + } catch (DdlException e) { + LOG.error("should not happen", e); + } + } + } + } else if (userProperty.getWhiteList().getAllDomains().isEmpty()) { + // 3. grant privs to user@'%' if there is no whitelist + UserIdentity userIdent = new UserIdentity(userProperty.getQualifiedUser(), "%"); + userIdent.setIsAnalyzed(); + for (Map.Entry entry : privsMap.entrySet()) { + try { + grantPrivs(userIdent, entry.getKey(), entry.getValue(), + false /* err on non exist */, false /* grant by resolver */); + } catch (DdlException e) { + LOG.error("should not happen", e); + } + } + } + + // 4. domain is already saved in whitelist, and will be resolved later. + // but here we add a user@'%' 's password entry, to avoid access deny during transitional period. + UserIdentity userIdent = new UserIdentity(userProperty.getQualifiedUser(), "%"); + userIdent.setIsAnalyzed(); + try { + setPasswordInternal(userIdent, userProperty.getPassword(), + true /* add if not exist */, + false /* set by resolver */, + true /* is replay */); + } catch (DdlException e) { + LOG.error("should not happen", e); + } + + // 5. update white list's privs info + Set allDomains = userProperty.getWhiteList().getAllDomains(); + for (String domain : allDomains) { + userProperty.getWhiteList().updateDomainMap(domain, privsMap); + } + LOG.info("update domains: {}, privs: {}", allDomains, privsMap); + + } // end for normal user property + + // add user property + propertyMgr.addUserPropertyUnchecked(userProperty); + + LOG.info("finished to transform old user property for user: {}", userProperty.getQualifiedUser()); + } finally { + writeUnlock(); + } + } + + public void deletePassworEntry(UserIdentity userIdent) { + writeLock(); + try { + // here we try to delete the password entry of the specified user, + // so that this user can not access to palo any more. + // we use a tricky way: we revoke all global privs of this, and when no privs granted, + // the priv entry will be deleted automatically. + revokeGlobalPrivs(userIdent.getHost(), userIdent.getQualifiedUser(), + PrivBitSet.of(PaloPrivilege.values()), + true /* revoke by resolver */, + false /* err on non exist */, + true /* delete entry when empty */); + } catch (DdlException e) { + LOG.warn("should not happen", e); + } finally { + writeUnlock(); + } + } + + // user can enter a cluster, if it has any privs of database or table in this cluster. + public boolean checkCanEnterCluster(ConnectContext ctx, String clusterName) { + readLock(); + try { + if (checkGlobalPriv(ctx, PrivPredicate.ALL)) { + return true; + } + + if (dbPrivTable.hasClusterPriv(ctx, clusterName)) { + return true; + } + + if (tablePrivTable.hasClusterPriv(ctx, clusterName)) { + return true; + } + + return false; + } finally { + readUnlock(); + } + } + + @Deprecated + public void replayAlterAccess(UserProperty userProperty) { + Preconditions.checkState(Catalog.getCurrentCatalogJournalVersion() < FeMetaVersion.VERSION_43); + writeLock(); + try { + transformAndAddOldUserProperty(userProperty); + } finally { + writeUnlock(); + } + } + + private void initUser() { + try { + UserIdentity rootUser = new UserIdentity(ROOT_USER, "%"); + rootUser.setIsAnalyzed(); + createUserInternal(rootUser, PaloRole.OPERATOR_ROLE, new byte[0], true /* is replay */); + UserIdentity adminUser = new UserIdentity(ADMIN_USER, "%"); + adminUser.setIsAnalyzed(); + createUserInternal(adminUser, PaloRole.ADMIN_ROLE, new byte[0], true /* is replay */); + } catch (DdlException e) { + LOG.error("should not happend", e); + } + } + + public TFetchResourceResult toResourceThrift() { + readLock(); + try { + return propertyMgr.toResourceThrift(); + } finally { + readUnlock(); + } + } + + public List> getRoleInfo() { + readLock(); + try { + List> results = Lists.newArrayList(); + roleManager.getRoleInfo(results); + return results; + } finally { + readUnlock(); + } + } + + public static PaloAuth read(DataInput in) throws IOException { + PaloAuth auth = new PaloAuth(); + auth.readFields(in); + return auth; + } + + @Override + public void write(DataOutput out) throws IOException { + // role manager must be first, because role should be exist before any user + roleManager.write(out); + userPrivTable.write(out); + dbPrivTable.write(out); + tablePrivTable.write(out); + propertyMgr.write(out); + } + + @Override + public void readFields(DataInput in) throws IOException { + roleManager = RoleManager.read(in); + userPrivTable = (UserPrivTable) PrivTable.read(in); + dbPrivTable = (DbPrivTable) PrivTable.read(in); + tablePrivTable = (TablePrivTable) PrivTable.read(in); + propertyMgr = UserPropertyMgr.read(in); + + if (userPrivTable.isEmpty()) { + // init root and admin user + initUser(); + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(userPrivTable).append("\n"); + sb.append(dbPrivTable).append("\n"); + sb.append(tablePrivTable).append("\n"); + sb.append(roleManager).append("\n"); + sb.append(propertyMgr).append("\n"); + return sb.toString(); + } +} + diff --git a/fe/src/com/baidu/palo/mysql/privilege/PaloPrivilege.java b/fe/src/com/baidu/palo/mysql/privilege/PaloPrivilege.java new file mode 100644 index 0000000000..b90b920bb0 --- /dev/null +++ b/fe/src/com/baidu/palo/mysql/privilege/PaloPrivilege.java @@ -0,0 +1,77 @@ +// Copyright (c) 2018, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +package com.baidu.palo.mysql.privilege; + +public enum PaloPrivilege { + NODE_PRIV("Node_priv", 0, "Privilege for cluster node operations"), + ADMIN_PRIV("Admin_priv", 1, "Privilege for admin user"), + GRANT_PRIV("Grant_priv", 2, "Privilege for granting privlege"), + SELECT_PRIV("Select_priv", 3, "Privilege for select data in tables"), + LOAD_PRIV("Load_priv", 4, "Privilege for loading data into tables"), + ALTER_PRIV("Alter_priv", 5, "Privilege for alter database or table"), + CREATE_PRIV("Create_priv", 6, "Privilege for createing database or table"), + DROP_PRIV("Drop_priv", 7, "Privilege for dropping database or table"); + + + public static PaloPrivilege[] privileges = { + NODE_PRIV, + ADMIN_PRIV, + GRANT_PRIV, + SELECT_PRIV, + LOAD_PRIV, + ALTER_PRIV, + CREATE_PRIV, + DROP_PRIV + }; + + private String name; + private int idx; + private String desc; + + private PaloPrivilege(String name, int index, String desc) { + this.name = name; + this.idx = index; + this.desc = desc; + } + + public String getName() { + return name; + } + + public int getIdx() { + return idx; + } + + public String getDesc() { + return desc; + } + + public static PaloPrivilege getPriv(int index) { + if (index < 0 || index > PaloPrivilege.values().length - 1) { + return null; + } + return privileges[index]; + } + + public static boolean satisfy(PrivBitSet grantPriv, PrivPredicate wanted) { + return grantPriv.satisfy(wanted); + } + + @Override + public String toString() { + return name; + } +} diff --git a/fe/src/com/baidu/palo/mysql/privilege/PaloRole.java b/fe/src/com/baidu/palo/mysql/privilege/PaloRole.java new file mode 100644 index 0000000000..14bf653f8a --- /dev/null +++ b/fe/src/com/baidu/palo/mysql/privilege/PaloRole.java @@ -0,0 +1,152 @@ +// Copyright (c) 2018, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +package com.baidu.palo.mysql.privilege; + +import com.baidu.palo.analysis.TablePattern; +import com.baidu.palo.analysis.UserIdentity; +import com.baidu.palo.common.CaseSensibility; +import com.baidu.palo.common.io.Text; +import com.baidu.palo.common.io.Writable; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +public class PaloRole implements Writable { + // operator is responsible for operating cluster, such as add/drop node + public static String OPERATOR_ROLE = "operator"; + // admin is like DBA, who has all privileges except for NODE privilege held by operator + public static String ADMIN_ROLE = "admin"; + + public static PaloRole OPERATOR = new PaloRole(OPERATOR_ROLE, TablePattern.ALL, + PrivBitSet.of(PaloPrivilege.NODE_PRIV, PaloPrivilege.ADMIN_PRIV)); + public static PaloRole ADMIN = new PaloRole(ADMIN_ROLE, TablePattern.ALL, + PrivBitSet.of(PaloPrivilege.ADMIN_PRIV)); + + private String roleName; + private Map tblPatternToPrivs = Maps.newConcurrentMap(); + // users which this role + private Set users = Sets.newConcurrentHashSet(); + + private PaloRole() { + + } + + public PaloRole(String roleName) { + this.roleName = roleName; + } + + public PaloRole(String roleName, TablePattern tablePattern, PrivBitSet privs) { + this.roleName = roleName; + this.tblPatternToPrivs.put(tablePattern, privs); + } + + public String getRoleName() { + return roleName; + } + + public Map getTblPatternToPrivs() { + return tblPatternToPrivs; + } + + public Set getUsers() { + return users; + } + + public void merge(PaloRole other) { + Preconditions.checkState(roleName.equalsIgnoreCase(other.getRoleName())); + for (Map.Entry entry : other.getTblPatternToPrivs().entrySet()) { + if (tblPatternToPrivs.containsKey(entry.getKey())) { + PrivBitSet existPrivs = tblPatternToPrivs.get(entry.getKey()); + existPrivs.or(entry.getValue()); + } else { + tblPatternToPrivs.put(entry.getKey(), entry.getValue()); + } + } + } + + public void addUser(UserIdentity userIdent) { + users.add(userIdent); + } + + public void dropUser(String qualifiedUser) { + Iterator iter = users.iterator(); + while (iter.hasNext()) { + UserIdentity userIdent = iter.next(); + boolean match = false; + if (CaseSensibility.USER.getCaseSensibility()) { + match = userIdent.getQualifiedUser().equals(qualifiedUser); + } else { + match = userIdent.getQualifiedUser().equalsIgnoreCase(qualifiedUser); + } + if (match) { + iter.remove(); + } + } + } + + public static PaloRole read(DataInput in) throws IOException { + PaloRole role = new PaloRole(); + role.readFields(in); + return role; + } + + @Override + public void write(DataOutput out) throws IOException { + Text.writeString(out, roleName); + out.writeInt(tblPatternToPrivs.size()); + for (Map.Entry entry : tblPatternToPrivs.entrySet()) { + entry.getKey().write(out); + entry.getValue().write(out); + } + + out.writeInt(users.size()); + for (UserIdentity userIdentity : users) { + userIdentity.write(out); + } + } + + @Override + public void readFields(DataInput in) throws IOException { + roleName = Text.readString(in); + int size = in.readInt(); + for (int i = 0; i < size; i++) { + TablePattern tblPattern = TablePattern.read(in); + PrivBitSet privs = PrivBitSet.read(in); + tblPatternToPrivs.put(tblPattern, privs); + } + size = in.readInt(); + for (int i = 0; i < size; i++) { + UserIdentity userIdentity = UserIdentity.read(in); + users.add(userIdentity); + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("role: ").append(roleName).append(", privs: ").append(tblPatternToPrivs); + sb.append(", users: ").append(users); + return sb.toString(); + } +} diff --git a/fe/src/com/baidu/palo/mysql/privilege/PrivBitSet.java b/fe/src/com/baidu/palo/mysql/privilege/PrivBitSet.java new file mode 100644 index 0000000000..47d20022eb --- /dev/null +++ b/fe/src/com/baidu/palo/mysql/privilege/PrivBitSet.java @@ -0,0 +1,158 @@ +// Copyright (c) 2018, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +package com.baidu.palo.mysql.privilege; + +import com.baidu.palo.analysis.CompoundPredicate.Operator; +import com.baidu.palo.common.io.Writable; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.List; + +// ....0000000000 +// ^ ^ +// | | +// | -- first priv(0) +// |-------last priv(6) +public class PrivBitSet implements Writable { + + private long set = 0; + + public PrivBitSet() { + } + + public void set(int index) { + Preconditions.checkState(index < PaloPrivilege.privileges.length, index); + set |= 1 << index; + } + + public void unset(int index) { + Preconditions.checkState(index < PaloPrivilege.privileges.length, index); + set &= ~set; + } + + public boolean get(int index) { + Preconditions.checkState(index < PaloPrivilege.privileges.length, index); + return (set & (1 << index)) > 0; + } + + public void or(PrivBitSet other) { + set |= other.set; + } + + public void and(PrivBitSet other) { + set &= other.set; + } + + public void xor(PrivBitSet other) { + set ^= other.set; + } + + public void remove(PrivBitSet privs) { + PrivBitSet tmp = copy(); + tmp.xor(privs); + and(tmp); + } + + public boolean isEmpty() { + return set == 0; + } + + public boolean satisfy(PrivPredicate wantPrivs) { + if (wantPrivs.getOp() == Operator.AND) { + return (set & wantPrivs.getPrivs().set) == wantPrivs.getPrivs().set; + } else { + return (set & wantPrivs.getPrivs().set) != 0; + } + + } + + public boolean containsNodeOrGrantPriv() { + return containsPrivs(PaloPrivilege.NODE_PRIV, PaloPrivilege.GRANT_PRIV); + } + + public boolean containsPrivs(PaloPrivilege... privs) { + for (PaloPrivilege priv : privs) { + if (get(priv.getIdx())) { + return true; + } + } + return false; + } + + public List toPrivilegeList() { + List privs = Lists.newArrayList(); + for (int i = 0; i < PaloPrivilege.privileges.length; i++) { + if (get(i)) { + privs.add(PaloPrivilege.getPriv(i)); + } + } + return privs; + } + + public static PrivBitSet of(PaloPrivilege... privs) { + PrivBitSet bitSet = new PrivBitSet(); + for (PaloPrivilege priv : privs) { + bitSet.set(priv.getIdx()); + } + return bitSet; + } + + public static PrivBitSet of(List privs) { + PrivBitSet bitSet = new PrivBitSet(); + for (PaloPrivilege priv : privs) { + bitSet.set(priv.getIdx()); + } + return bitSet; + } + + public PrivBitSet copy() { + PrivBitSet newSet = new PrivBitSet(); + newSet.set = set; + return newSet; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < PaloPrivilege.privileges.length; i++) { + if (get(i)) { + sb.append(PaloPrivilege.getPriv(i)).append(" "); + } + } + return sb.toString(); + } + + public static PrivBitSet read(DataInput in) throws IOException { + PrivBitSet privBitSet = new PrivBitSet(); + privBitSet.readFields(in); + return privBitSet; + } + + @Override + public void write(DataOutput out) throws IOException { + out.writeLong(set); + } + + @Override + public void readFields(DataInput in) throws IOException { + set = in.readLong(); + } +} diff --git a/fe/src/com/baidu/palo/mysql/privilege/PrivEntry.java b/fe/src/com/baidu/palo/mysql/privilege/PrivEntry.java new file mode 100644 index 0000000000..cb6c8e0e2e --- /dev/null +++ b/fe/src/com/baidu/palo/mysql/privilege/PrivEntry.java @@ -0,0 +1,236 @@ +// Copyright (c) 2018, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +package com.baidu.palo.mysql.privilege; + +import com.baidu.palo.analysis.UserIdentity; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.CaseSensibility; +import com.baidu.palo.common.PatternMatcher; +import com.baidu.palo.common.io.Text; +import com.baidu.palo.common.io.Writable; + +import org.apache.commons.lang.NotImplementedException; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public abstract class PrivEntry implements Comparable, Writable { + protected static final String ANY_HOST = "%"; + protected static final String ANY_USER = "%"; + + // host is not case sensitive + protected PatternMatcher hostPattern; + protected String origHost; + protected boolean isAnyHost = false; + // user name is case sensitive + protected PatternMatcher userPattern; + protected String origUser; + protected boolean isAnyUser = false; + protected PrivBitSet privSet; + + protected boolean isSetByDomainResolver = false; + + // isClassNameWrote to guarantee the class name can only be written once when persisting. + // see PrivEntry.read() for more details. + protected boolean isClassNameWrote = false; + + protected PrivEntry() { + } + + protected PrivEntry(PatternMatcher hostPattern, String origHost, PatternMatcher userPattern, String origUser, + PrivBitSet privSet) { + this.hostPattern = hostPattern; + this.origHost = origHost; + if (origHost.equals(ANY_HOST)) { + isAnyHost = true; + } + this.userPattern = userPattern; + this.origUser = origUser; + if (origUser.equals(ANY_USER)) { + isAnyUser = true; + } + this.privSet = privSet; + } + + public PatternMatcher getHostPattern() { + return hostPattern; + } + + public String getOrigHost() { + return origHost; + } + + public boolean isAnyHost() { + return isAnyHost; + } + + public PatternMatcher getUserPattern() { + return userPattern; + } + + public String getOrigUser() { + return origUser; + } + + public boolean isAnyUser() { + return isAnyUser; + } + + public PrivBitSet getPrivSet() { + return privSet; + } + + public void setPrivSet(PrivBitSet privSet) { + this.privSet = privSet; + } + + public boolean isSetByDomainResolver() { + return isSetByDomainResolver; + } + + public void setSetByDomainResolver(boolean isSetByDomainResolver) { + this.isSetByDomainResolver = isSetByDomainResolver; + } + + public UserIdentity getUserIdent() { + UserIdentity userIdent = new UserIdentity(origUser, origHost); + userIdent.setIsAnalyzed(); + return userIdent; + } + + public boolean match(UserIdentity userIdent, boolean exactMatch) { + if (exactMatch) { + return origUser.equals(userIdent.getQualifiedUser()) && origHost.equals(userIdent.getHost()); + } else { + return origUser.equals(userIdent.getQualifiedUser()) && hostPattern.match(userIdent.getHost()); + } + } + + public abstract boolean keyMatch(PrivEntry other); + + /* + * It's a bit complicated when persisting instance which its class has derived classes. + * eg: A (top class) -> B (derived) -> C (derived) + * + * Write process: + * C.write() + * | + * --- write class name + * | + * --- super.write() -----> B.write() + * | | + * --- write C's self members --- write class name (if not write before) + * | + * --- super.write() -----> A.write() + * | | + * --- write B's self members --- write class name (if not write before) + * | + * --- write A's self members + * + * So the final write order is: + * 1. C's class name + * 2. A's self members + * 3. B's self members + * 4. C's self members + * + * In case that class name should only be wrote once, we use isClassNameWrote flag. + * + * Read process: + * static A.read() + * | + * --- read class name and instantiated the class instance (eg. C class) + * | + * --- C.readFields() + * | + * --- super.readFields() --> B.readFields() + * | | + * --- read C's self members --- super.readFields() --> A.readFields() + * | | + * --- read B's self members --- read A's self members + * + * So the final read order is: + * 1. C's class name + * 2. A's self members + * 3. B's self members + * 4. C's self members + * + * Which is same as Write order. + */ + public static PrivEntry read(DataInput in) throws IOException { + String className = Text.readString(in); + PrivEntry privEntry = null; + try { + Class derivedClass = (Class) Class.forName(className); + privEntry = derivedClass.newInstance(); + Class[] paramTypes = { DataInput.class }; + Method readMethod = derivedClass.getMethod("readFields", paramTypes); + Object[] params = { in }; + readMethod.invoke(privEntry, params); + + return privEntry; + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException + | SecurityException | IllegalArgumentException | InvocationTargetException e) { + throw new IOException("failed read PrivEntry", e); + } + } + + @Override + public void write(DataOutput out) throws IOException { + if (!isClassNameWrote) { + String className = PrivEntry.class.getCanonicalName(); + Text.writeString(out, className); + isClassNameWrote = true; + } + Text.writeString(out, origHost); + Text.writeString(out, origUser); + privSet.write(out); + + out.writeBoolean(isSetByDomainResolver); + + isClassNameWrote = false; + } + + @Override + public void readFields(DataInput in) throws IOException { + origHost = Text.readString(in); + try { + hostPattern = PatternMatcher.createMysqlPattern(origHost, CaseSensibility.HOST.getCaseSensibility()); + } catch (AnalysisException e) { + throw new IOException(e); + } + isAnyHost = origHost.equals(ANY_HOST); + + origUser = Text.readString(in); + try { + userPattern = PatternMatcher.createMysqlPattern(origUser, CaseSensibility.USER.getCaseSensibility()); + } catch (AnalysisException e) { + throw new IOException(e); + } + isAnyUser = origUser.equals(ANY_USER); + + privSet = PrivBitSet.read(in); + + isSetByDomainResolver = in.readBoolean(); + } + + @Override + public int compareTo(PrivEntry o) { + throw new NotImplementedException(); + } +} diff --git a/fe/src/com/baidu/palo/mysql/privilege/PrivPredicate.java b/fe/src/com/baidu/palo/mysql/privilege/PrivPredicate.java new file mode 100644 index 0000000000..b19bf9ccf8 --- /dev/null +++ b/fe/src/com/baidu/palo/mysql/privilege/PrivPredicate.java @@ -0,0 +1,105 @@ +// Copyright (c) 2018, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +package com.baidu.palo.mysql.privilege; + +import com.baidu.palo.analysis.CompoundPredicate.Operator; + +public class PrivPredicate { + + // user can 'see' this meta + public static final PrivPredicate SHOW = PrivPredicate.of(PrivBitSet.of(PaloPrivilege.ADMIN_PRIV, + PaloPrivilege.SELECT_PRIV, + PaloPrivilege.LOAD_PRIV, + PaloPrivilege.ALTER_PRIV, + PaloPrivilege.CREATE_PRIV, + PaloPrivilege.DROP_PRIV), + Operator.OR); + // create/drop/alter/show user + public static final PrivPredicate GRANT = PrivPredicate.of(PrivBitSet.of(PaloPrivilege.ADMIN_PRIV, + PaloPrivilege.GRANT_PRIV), + Operator.OR); + // admin user privs + public static final PrivPredicate ADMIN = PrivPredicate.of(PrivBitSet.of(PaloPrivilege.ADMIN_PRIV), + Operator.OR); + + // load + public static final PrivPredicate LOAD = PrivPredicate.of(PrivBitSet.of(PaloPrivilege.ADMIN_PRIV, + PaloPrivilege.LOAD_PRIV), + Operator.OR); + + // alter + public static final PrivPredicate ALTER = PrivPredicate.of(PrivBitSet.of(PaloPrivilege.ADMIN_PRIV, + PaloPrivilege.ALTER_PRIV), + Operator.OR); + + // create + public static final PrivPredicate CREATE = PrivPredicate.of(PrivBitSet.of(PaloPrivilege.ADMIN_PRIV, + PaloPrivilege.CREATE_PRIV), + Operator.OR); + + // drop + public static final PrivPredicate DROP = PrivPredicate.of(PrivBitSet.of(PaloPrivilege.ADMIN_PRIV, + PaloPrivilege.DROP_PRIV), + Operator.OR); + + // select + public static final PrivPredicate SELECT = PrivPredicate.of(PrivBitSet.of(PaloPrivilege.ADMIN_PRIV, + PaloPrivilege.SELECT_PRIV), + Operator.OR); + + // operator + public static final PrivPredicate OPERATOR = PrivPredicate.of(PrivBitSet.of(PaloPrivilege.NODE_PRIV), + Operator.OR); + + // all + public static final PrivPredicate ALL = PrivPredicate.of(PrivBitSet.of(PaloPrivilege.NODE_PRIV, + PaloPrivilege.ADMIN_PRIV, + PaloPrivilege.SELECT_PRIV, + PaloPrivilege.LOAD_PRIV, + PaloPrivilege.ALTER_PRIV, + PaloPrivilege.CREATE_PRIV, + PaloPrivilege.DROP_PRIV), + Operator.OR); + + private PrivBitSet privs; + private Operator op; + + private PrivPredicate(PrivBitSet privs, Operator op) { + this.privs = privs; + this.op = op; + } + + public static PrivPredicate of(PrivBitSet privs, Operator op) { + final PrivPredicate predicate = new PrivPredicate(privs, op); + return predicate; + } + + public PrivBitSet getPrivs() { + return privs; + } + + public Operator getOp() { + return op; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("priv predicate: ").append(op).append(", ").append(privs); + return sb.toString(); + } + +} diff --git a/fe/src/com/baidu/palo/mysql/privilege/PrivTable.java b/fe/src/com/baidu/palo/mysql/privilege/PrivTable.java new file mode 100644 index 0000000000..bbbb04722f --- /dev/null +++ b/fe/src/com/baidu/palo/mysql/privilege/PrivTable.java @@ -0,0 +1,228 @@ +// Copyright (c) 2018, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +package com.baidu.palo.mysql.privilege; + +import com.baidu.palo.common.DdlException; +import com.baidu.palo.common.io.Text; +import com.baidu.palo.common.io.Writable; + +import com.google.common.collect.Lists; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +public abstract class PrivTable implements Writable { + private static final Logger LOG = LogManager.getLogger(PrivTable.class); + + protected List entries = Lists.newArrayList(); + + // see PrivEntry for more detail + protected boolean isClassNameWrote = false; + + public void addEntry(PrivEntry newEntry, boolean errOnExist, boolean errOnNonExist) throws DdlException { + PrivEntry existingEntry = getExistingEntry(newEntry); + if (existingEntry == null) { + if (errOnNonExist) { + throw new DdlException("User " + newEntry.getUserIdent() + " does not exist"); + } + entries.add(newEntry); + Collections.sort(entries); + LOG.info("add priv entry: {}", newEntry); + } else { + if (errOnExist) { + throw new DdlException("User already exist"); + } else { + if (!checkOperationAllowed(existingEntry, newEntry, "ADD ENTRY")) { + return; + } else { + if (existingEntry.isSetByDomainResolver() && newEntry.isSetByDomainResolver()) { + existingEntry.setPrivSet(newEntry.getPrivSet()); + LOG.debug("reset priv entry: {}", existingEntry); + } else if (existingEntry.isSetByDomainResolver() && !newEntry.isSetByDomainResolver() + || !existingEntry.isSetByDomainResolver() && !newEntry.isSetByDomainResolver()) { + mergePriv(existingEntry, newEntry); + existingEntry.setSetByDomainResolver(false); + LOG.info("merge priv entry: {}", existingEntry); + } + return; + } + } + } + + return; + } + + public void dropEntry(PrivEntry entry) { + Iterator iter = entries.iterator(); + while (iter.hasNext()) { + PrivEntry privEntry = iter.next(); + if (privEntry.keyMatch(entry)) { + iter.remove(); + LOG.info("drop priv entry: {}", privEntry); + break; + } + } + } + + // drop all entries which user name are matched + public void dropUser(String qualifiedUser) { + Iterator iter = entries.iterator(); + while (iter.hasNext()) { + PrivEntry privEntry = iter.next(); + if (privEntry.getOrigUser().equals(qualifiedUser)) { + iter.remove(); + LOG.info("drop entry: {}", privEntry); + } + } + } + + public boolean revoke(PrivEntry entry, boolean errOnNonExist, boolean deleteEntryWhenEmpty) { + PrivEntry existingEntry = getExistingEntry(entry); + if (existingEntry == null && errOnNonExist) { + return false; + } + + if (!checkOperationAllowed(existingEntry, entry, "REVOKE")) { + return true; + } + + // check if privs to be revoked exist in priv entry. + PrivBitSet tmp = existingEntry.getPrivSet().copy(); + tmp.and(entry.getPrivSet()); + if (tmp.isEmpty()) { + return !errOnNonExist; + } + + // revoke privs from existing priv entry + LOG.debug("before revoke: {}, privs to be revoked: {}", + existingEntry.getPrivSet(), entry.getPrivSet()); + tmp = existingEntry.getPrivSet().copy(); + tmp.xor(entry.getPrivSet()); + existingEntry.getPrivSet().and(tmp); + LOG.debug("after revoke: {}", existingEntry); + + if (existingEntry.getPrivSet().isEmpty() && deleteEntryWhenEmpty) { + // no priv exists in this entry, remove it + dropEntry(existingEntry); + } + + return true; + } + + /* + * the priv entry is classified by 'set by domain resolver' + * or 'NOT set by domain resolver'(other specified operations). + * if the existing entry is set by resolver, it can be reset by resolver or set by specified ops. + * if the existing entry is NOT set by resolver, it can not be set by resolver. + */ + protected boolean checkOperationAllowed(PrivEntry existingEntry, PrivEntry newEntry, String op) { + if (!existingEntry.isSetByDomainResolver() && newEntry.isSetByDomainResolver()) { + LOG.debug("the existing entry is NOT set by resolver: {}, can not be set by resolver {}, op: {}", + existingEntry, newEntry); + return false; + } else if (existingEntry.isSetByDomainResolver() && !newEntry.isSetByDomainResolver()) { + LOG.debug("the existing entry is currently set by resolver: {}, be set by ops now: {}, op: {}", + existingEntry, newEntry); + return true; + } + return true; + } + + // Get existing entry which is the keys match the given entry + protected PrivEntry getExistingEntry(PrivEntry entry) { + for (PrivEntry existingEntry : entries) { + if (existingEntry.keyMatch(entry)) { + return existingEntry; + } + } + return null; + } + + private void mergePriv(PrivEntry first, PrivEntry second) { + first.getPrivSet().or(second.getPrivSet()); + first.setSetByDomainResolver(first.isSetByDomainResolver() || second.isSetByDomainResolver()); + } + + // for test only + public void clear() { + entries.clear(); + } + + public boolean isEmpty() { + return entries.isEmpty(); + } + + public static PrivTable read(DataInput in) throws IOException { + String className = Text.readString(in); + PrivTable privTable = null; + try { + Class derivedClass = (Class) Class.forName(className); + privTable = derivedClass.newInstance(); + Class[] paramTypes = { DataInput.class }; + Method readMethod = derivedClass.getMethod("readFields", paramTypes); + Object[] params = { in }; + readMethod.invoke(privTable, params); + + return privTable; + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException + | SecurityException | IllegalArgumentException | InvocationTargetException e) { + throw new IOException("failed read PrivTable", e); + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("\n"); + for (PrivEntry privEntry : entries) { + sb.append(privEntry).append("\n"); + } + return sb.toString(); + } + + @Override + public void write(DataOutput out) throws IOException { + if (!isClassNameWrote) { + String className = PrivTable.class.getCanonicalName(); + Text.writeString(out, className); + isClassNameWrote = true; + } + out.writeInt(entries.size()); + for (PrivEntry privEntry : entries) { + privEntry.write(out); + } + isClassNameWrote = false; + } + + @Override + public void readFields(DataInput in) throws IOException { + int size = in.readInt(); + for (int i = 0; i < size; i++) { + PrivEntry entry = PrivEntry.read(in); + entries.add(entry); + } + Collections.sort(entries); + } + +} diff --git a/fe/src/com/baidu/palo/mysql/privilege/RoleManager.java b/fe/src/com/baidu/palo/mysql/privilege/RoleManager.java new file mode 100644 index 0000000000..d699c47a2e --- /dev/null +++ b/fe/src/com/baidu/palo/mysql/privilege/RoleManager.java @@ -0,0 +1,187 @@ +// Copyright (c) 2018, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +package com.baidu.palo.mysql.privilege; + +import com.baidu.palo.analysis.TablePattern; +import com.baidu.palo.common.DdlException; +import com.baidu.palo.common.io.Writable; +import com.baidu.palo.mysql.privilege.PaloAuth.PrivLevel; + +import com.google.common.base.Joiner; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +public class RoleManager implements Writable { + private Map roles = Maps.newHashMap(); + + public RoleManager() { + roles.put(PaloRole.OPERATOR.getRoleName(), PaloRole.OPERATOR); + roles.put(PaloRole.ADMIN.getRoleName(), PaloRole.ADMIN); + } + + public PaloRole getRole(String role) { + return roles.get(role); + } + + public PaloRole addRole(PaloRole newRole, boolean errOnExist) throws DdlException { + PaloRole existingRole = roles.get(newRole.getRoleName()); + if (existingRole != null) { + if (errOnExist) { + throw new DdlException("Role " + newRole + " already exists"); + } + // merge + existingRole.merge(newRole); + return existingRole; + } else { + roles.put(newRole.getRoleName(), newRole); + return newRole; + } + } + + public void dropRole(String qualifiedRole, boolean errOnNonExist) throws DdlException { + if (!roles.containsKey(qualifiedRole)) { + if (errOnNonExist) { + throw new DdlException("Role " + qualifiedRole + " does not exist"); + } + return; + } + + // we just remove the role from this map and remain others unchanged(privs, etc..) + roles.remove(qualifiedRole); + } + + public PaloRole revokePrivs(String role, TablePattern tblPattern, PrivBitSet privs, boolean errOnNonExist) + throws DdlException { + PaloRole existingRole = roles.get(role); + if (existingRole == null) { + if (errOnNonExist) { + throw new DdlException("Role " + role + " does not exist"); + } + return null; + } + + Map map = existingRole.getTblPatternToPrivs(); + PrivBitSet existingPriv = map.get(tblPattern); + if (existingPriv == null) { + if (errOnNonExist) { + throw new DdlException(tblPattern + " does not eixst in role " + role); + } + return null; + } + + existingPriv.remove(privs); + return existingRole; + } + + public void dropUser(String qualifiedUser) { + for (PaloRole role : roles.values()) { + role.dropUser(qualifiedUser); + } + } + + public void getRoleInfo(List> results) { + for (PaloRole role : roles.values()) { + List info = Lists.newArrayList(); + info.add(role.getRoleName()); + info.add(Joiner.on(", ").join(role.getUsers())); + + // global + boolean hasGlobal = false; + for (Map.Entry entry : role.getTblPatternToPrivs().entrySet()) { + if (entry.getKey().getPrivLevel() == PrivLevel.GLOBAL) { + hasGlobal = true; + info.add(entry.getValue().toString()); + // global priv should only has one + break; + } + } + if (!hasGlobal) { + info.add("N/A"); + } + + // db + List tmp = Lists.newArrayList(); + for (Map.Entry entry : role.getTblPatternToPrivs().entrySet()) { + if (entry.getKey().getPrivLevel() == PrivLevel.DATABASE) { + tmp.add(entry.getKey().toString() + ": " + entry.getValue().toString()); + } + } + if (tmp.isEmpty()) { + info.add("N/A"); + } else { + info.add(Joiner.on("; ").join(tmp)); + } + + + // tbl + tmp.clear(); + for (Map.Entry entry : role.getTblPatternToPrivs().entrySet()) { + if (entry.getKey().getPrivLevel() == PrivLevel.TABLE) { + tmp.add(entry.getKey().toString() + ": " + entry.getValue().toString()); + } + } + if (tmp.isEmpty()) { + info.add("N/A"); + } else { + info.add(Joiner.on("; ").join(tmp)); + } + + results.add(info); + } + } + + public static RoleManager read(DataInput in) throws IOException { + RoleManager roleManager = new RoleManager(); + roleManager.readFields(in); + return roleManager; + } + + @Override + public void write(DataOutput out) throws IOException { + // minus 2 to ignore ADMIN and OPERATOR role + out.writeInt(roles.size() - 2); + for (PaloRole role : roles.values()) { + if (role == PaloRole.ADMIN || role == PaloRole.OPERATOR) { + continue; + } + role.write(out); + } + } + + @Override + public void readFields(DataInput in) throws IOException { + int size = in.readInt(); + for (int i = 0; i < size; i++) { + PaloRole role = PaloRole.read(in); + roles.put(role.getRoleName(), role); + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("Roles: "); + for (PaloRole role : roles.values()) { + sb.append(role).append("\n"); + } + return sb.toString(); + } +} diff --git a/fe/src/com/baidu/palo/mysql/privilege/TablePrivEntry.java b/fe/src/com/baidu/palo/mysql/privilege/TablePrivEntry.java new file mode 100644 index 0000000000..0ba5a18b44 --- /dev/null +++ b/fe/src/com/baidu/palo/mysql/privilege/TablePrivEntry.java @@ -0,0 +1,151 @@ +// Copyright (c) 2018, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +package com.baidu.palo.mysql.privilege; + +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.CaseSensibility; +import com.baidu.palo.common.PatternMatcher; +import com.baidu.palo.common.io.Text; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +public class TablePrivEntry extends DbPrivEntry { + private static final String ANY_TBL = "*"; + + private PatternMatcher tblPattern; + private String origTbl; + private boolean isAnyTbl; + + protected TablePrivEntry() { + } + + private TablePrivEntry(PatternMatcher hostPattern, String origHost, PatternMatcher dbPattern, String origDb, + PatternMatcher userPattern, String user, PatternMatcher tblPattern, String origTbl, PrivBitSet privSet) { + super(hostPattern, origHost, dbPattern, origDb, userPattern, user, privSet); + this.tblPattern = tblPattern; + this.origTbl = origTbl; + if (origTbl.equals(ANY_TBL)) { + isAnyTbl = true; + } + } + + public static TablePrivEntry create(String host, String db, String user, String tbl, + PrivBitSet privs) throws AnalysisException { + PatternMatcher hostPattern = PatternMatcher.createMysqlPattern(host, CaseSensibility.HOST.getCaseSensibility()); + PatternMatcher dbPattern = PatternMatcher.createMysqlPattern(db.equals(ANY_DB) ? "%" : db, + CaseSensibility.DATABASE.getCaseSensibility()); + PatternMatcher userPattern = PatternMatcher.createMysqlPattern(user, CaseSensibility.USER.getCaseSensibility()); + + PatternMatcher tblPattern = PatternMatcher.createMysqlPattern(tbl.equals(ANY_TBL) ? "%" : tbl, + CaseSensibility.TABLE.getCaseSensibility()); + + if (privs.containsNodeOrGrantPriv()) { + throw new AnalysisException("Table privilege can not contains global privileges: " + privs); + } + + return new TablePrivEntry(hostPattern, host, dbPattern, db, userPattern, user, tblPattern, tbl, privs); + } + + public PatternMatcher getTblPattern() { + return tblPattern; + } + + public String getOrigTbl() { + return origTbl; + } + + public boolean isAnyTbl() { + return isAnyTbl; + } + + @Override + public int compareTo(PrivEntry other) { + if (!(other instanceof TablePrivEntry)) { + throw new ClassCastException("cannot cast " + other.getClass().toString() + " to " + this.getClass()); + } + + TablePrivEntry otherEntry = (TablePrivEntry) other; + int res = origHost.compareTo(otherEntry.origHost); + if (res != 0) { + return -res; + } + + res = origDb.compareTo(otherEntry.origDb); + if (res != 0) { + return -res; + } + + res = origUser.compareTo(otherEntry.origUser); + if (res != 0) { + return -res; + } + + return -origTbl.compareTo(otherEntry.origTbl); + } + + @Override + public boolean keyMatch(PrivEntry other) { + if (!(other instanceof TablePrivEntry)) { + return false; + } + + TablePrivEntry otherEntry = (TablePrivEntry) other; + if (origHost.equals(otherEntry.origHost) && origUser.equals(otherEntry.origUser) + && origDb.equals(otherEntry.origDb) && origTbl.equals(origTbl)) { + return true; + } + return false; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("db priv. host: ").append(origHost).append(", db: ").append(origDb); + sb.append(", user: ").append(origUser).append(", tbl: ").append(origTbl); + sb.append(", priv: ").append(privSet).append(", set by resolver: ").append(isSetByDomainResolver); + return sb.toString(); + } + + @Override + public void write(DataOutput out) throws IOException { + if (!isClassNameWrote) { + String className = TablePrivEntry.class.getCanonicalName(); + Text.writeString(out, className); + isClassNameWrote = true; + } + super.write(out); + + Text.writeString(out, origTbl); + + isClassNameWrote = false; + } + + @Override + public void readFields(DataInput in) throws IOException { + super.readFields(in); + + origTbl = Text.readString(in); + try { + tblPattern = PatternMatcher.createMysqlPattern(origTbl, CaseSensibility.TABLE.getCaseSensibility()); + } catch (AnalysisException e) { + throw new IOException(e); + } + isAnyTbl = origTbl.equals(ANY_TBL); + } + +} diff --git a/fe/src/com/baidu/palo/mysql/privilege/TablePrivTable.java b/fe/src/com/baidu/palo/mysql/privilege/TablePrivTable.java new file mode 100644 index 0000000000..1d600f0fbc --- /dev/null +++ b/fe/src/com/baidu/palo/mysql/privilege/TablePrivTable.java @@ -0,0 +1,110 @@ +// Copyright (c) 2018, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +package com.baidu.palo.mysql.privilege; + +import com.baidu.palo.common.io.Text; +import com.baidu.palo.qe.ConnectContext; + +import com.google.common.base.Preconditions; + +import java.io.DataOutput; +import java.io.IOException; + +public class TablePrivTable extends PrivTable { + + public void getPrivs(String host, String db, String user, String tbl, PrivBitSet savedPrivs) { + TablePrivEntry matchedEntry = null; + for (PrivEntry entry : entries) { + TablePrivEntry tblPrivEntry = (TablePrivEntry) entry; + + // check host + if (!tblPrivEntry.isAnyHost() && !tblPrivEntry.getHostPattern().match(host)) { + continue; + } + + // check db + Preconditions.checkState(!tblPrivEntry.isAnyDb()); + if (!tblPrivEntry.getDbPattern().match(db)) { + continue; + } + + // check user + if (!tblPrivEntry.isAnyUser() && !tblPrivEntry.getUserPattern().match(user)) { + continue; + } + + // check table + if (!tblPrivEntry.getTblPattern().match(tbl)) { + continue; + } + + matchedEntry = tblPrivEntry; + break; + } + if (matchedEntry == null) { + return; + } + + savedPrivs.or(matchedEntry.getPrivSet()); + } + + public boolean hasPrivsOfDb(String host, String db, String user) { + for (PrivEntry entry : entries) { + TablePrivEntry tblPrivEntry = (TablePrivEntry) entry; + + // check host + Preconditions.checkState(!tblPrivEntry.isAnyDb()); + if (!tblPrivEntry.getDbPattern().match(db)) { + continue; + } + + // check db + Preconditions.checkState(!tblPrivEntry.isAnyDb()); + if (!tblPrivEntry.getDbPattern().match(db)) { + continue; + } + + // check user + if (!tblPrivEntry.isAnyUser() && !tblPrivEntry.getUserPattern().match(user)) { + continue; + } + + return true; + } + return false; + } + + @Override + public void write(DataOutput out) throws IOException { + if (!isClassNameWrote) { + String className = TablePrivTable.class.getCanonicalName(); + Text.writeString(out, className); + isClassNameWrote = true; + } + + super.write(out); + } + + public boolean hasClusterPriv(ConnectContext ctx, String clusterName) { + for (PrivEntry entry : entries) { + TablePrivEntry tblPrivEntry = (TablePrivEntry) entry; + if (tblPrivEntry.getOrigDb().startsWith(clusterName)) { + return true; + } + } + return false; + } +} diff --git a/fe/src/com/baidu/palo/mysql/privilege/UserPrivTable.java b/fe/src/com/baidu/palo/mysql/privilege/UserPrivTable.java new file mode 100644 index 0000000000..c6a8e260ab --- /dev/null +++ b/fe/src/com/baidu/palo/mysql/privilege/UserPrivTable.java @@ -0,0 +1,159 @@ +// Copyright (c) 2018, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +package com.baidu.palo.mysql.privilege; + +import com.baidu.palo.analysis.UserIdentity; +import com.baidu.palo.common.DdlException; +import com.baidu.palo.common.io.Text; +import com.baidu.palo.mysql.MysqlPassword; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.DataOutput; +import java.io.IOException; + +public class UserPrivTable extends PrivTable { + private static final Logger LOG = LogManager.getLogger(UserPrivTable.class); + + public UserPrivTable() { + } + + public void getPrivs(String host, String user, PrivBitSet savedPrivs) { + GlobalPrivEntry matchedEntry = null; + for (PrivEntry entry : entries) { + GlobalPrivEntry globalPrivEntry = (GlobalPrivEntry) entry; + + // check host + if (!globalPrivEntry.isAnyHost() && !globalPrivEntry.getHostPattern().match(host)) { + continue; + } + + // check user + if (!globalPrivEntry.isAnyUser() && !globalPrivEntry.getUserPattern().match(user)) { + continue; + } + + matchedEntry = globalPrivEntry; + break; + } + if (matchedEntry == null) { + return; + } + + savedPrivs.or(matchedEntry.getPrivSet()); + } + + // validate the connection by host, user and password. + // return true if this connection is valid, and 'savedPrivs' save all global privs got from user table. + public boolean checkPassword(String remoteUser, String remoteHost, byte[] remotePasswd, byte[] randomString) { + LOG.debug("check password for user: {} from {}, password: {}, random string: {}", + remoteUser, remoteHost, remotePasswd, randomString); + + // TODO(cmy): for now, we check user table from first entry to last, + // This may not efficient, but works. + for (PrivEntry entry : entries) { + GlobalPrivEntry globalPrivEntry = (GlobalPrivEntry) entry; + + // check host + if (!globalPrivEntry.isAnyHost() && !globalPrivEntry.getHostPattern().match(remoteHost)) { + continue; + } + + // check user + if (!globalPrivEntry.isAnyUser() && !globalPrivEntry.getUserPattern().match(remoteUser)) { + continue; + } + + // check password + byte[] saltPassword = MysqlPassword.getSaltFromPassword(globalPrivEntry.getPassword()); + // when the length of password is zero, the user has no password + if ((remotePasswd.length == saltPassword.length) + && (remotePasswd.length == 0 + || MysqlPassword.checkScramble(remotePasswd, randomString, saltPassword))) { + // found the matched entry + return true; + } else { + continue; + } + } + + return false; + } + + public boolean checkPlainPassword(String remoteUser, String remoteHost, String remotePasswd) { + for (PrivEntry entry : entries) { + GlobalPrivEntry globalPrivEntry = (GlobalPrivEntry) entry; + + // check host + if (!globalPrivEntry.isAnyHost() && !globalPrivEntry.getHostPattern().match(remoteHost)) { + continue; + } + + // check user + if (!globalPrivEntry.isAnyUser() && !globalPrivEntry.getUserPattern().match(remoteUser)) { + continue; + } + + if (MysqlPassword.checkPlainPass(globalPrivEntry.getPassword(), remotePasswd)) { + return true; + } + } + + return false; + } + + public void setPassword(GlobalPrivEntry passwdEntry, boolean addIfNotExist) throws DdlException { + GlobalPrivEntry existingEntry = (GlobalPrivEntry) getExistingEntry(passwdEntry); + if (existingEntry == null) { + if (!addIfNotExist) { + throw new DdlException("User " + passwdEntry.getUserIdent() + " does not exist"); + } + existingEntry = passwdEntry; + addEntry(existingEntry, false /* err on exist */, false /* err on non exist */); + } else { + if (existingEntry.isSetByDomainResolver() && !passwdEntry.isSetByDomainResolver()) { + LOG.info("cannot set password, existing entry is set by resolver: {}", existingEntry); + throw new DdlException("Cannot set password, existing entry is set by resolver"); + } else if (!existingEntry.isSetByDomainResolver() && passwdEntry.isSetByDomainResolver()) { + LOG.info("Cannot set password, existing entry is not set by resolver: {}", existingEntry); + throw new DdlException("Cannot set password, existing entry is not set by resolver"); + } + } + + existingEntry.setPassword(passwdEntry.getPassword()); + } + + public boolean doesUserExist(UserIdentity userIdent, boolean exactMatch) { + for (PrivEntry privEntry : entries) { + if (privEntry.match(userIdent, exactMatch)) { + return true; + } + } + return false; + } + + @Override + public void write(DataOutput out) throws IOException { + if (!isClassNameWrote) { + String className = UserPrivTable.class.getCanonicalName(); + Text.writeString(out, className); + isClassNameWrote = true; + } + + super.write(out); + } +} diff --git a/fe/src/com/baidu/palo/catalog/UserProperty.java b/fe/src/com/baidu/palo/mysql/privilege/UserProperty.java similarity index 58% rename from fe/src/com/baidu/palo/catalog/UserProperty.java rename to fe/src/com/baidu/palo/mysql/privilege/UserProperty.java index a6e7dc4124..a3dcb5509d 100644 --- a/fe/src/com/baidu/palo/catalog/UserProperty.java +++ b/fe/src/com/baidu/palo/mysql/privilege/UserProperty.java @@ -13,11 +13,17 @@ // specific language governing permissions and limitations // under the License. -package com.baidu.palo.catalog; +package com.baidu.palo.mysql.privilege; import com.baidu.palo.analysis.SetUserPropertyVar; import com.baidu.palo.analysis.SetVar; +import com.baidu.palo.analysis.TablePattern; +import com.baidu.palo.catalog.AccessPrivilege; +import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.catalog.ResourceGroup; +import com.baidu.palo.catalog.ResourceType; import com.baidu.palo.cluster.ClusterNamespace; +import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.Config; import com.baidu.palo.common.DdlException; import com.baidu.palo.common.FeMetaVersion; @@ -28,11 +34,14 @@ import com.baidu.palo.common.io.Writable; import com.baidu.palo.load.DppConfig; import com.baidu.palo.system.SystemInfoService; +import com.google.common.base.Joiner; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import org.apache.commons.lang.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import java.io.DataInput; import java.io.DataOutput; @@ -45,114 +54,138 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Pattern; +/* + * UserProperty contains properties set for a user + * This user is just qualified by cluster name, not host which it connected from. + */ public class UserProperty implements Writable { - private static final String MAX_USER_CONNECTIONS = "max_user_connections"; - private static final String RESOURCE = "resource"; - private static final String QUOTA = "quota"; - private static final String DEFAULT_LOAD_CLUSTER = "default_load_cluster"; - private static final String LOAD_CLUSTER = "load_cluster"; + private static final Logger LOG = LogManager.getLogger(UserProperty.class); - // for superuser or root + private static final String PROP_MAX_USER_CONNECTIONS = "max_user_connections"; + private static final String PROP_RESOURCE = "resource"; + private static final String PROP_QUOTA = "quota"; + private static final String PROP_DEFAULT_LOAD_CLUSTER = "default_load_cluster"; + private static final String PROP_LOAD_CLUSTER = "load_cluster"; + + // for system user public static final Set ADVANCED_PROPERTIES = Sets.newHashSet(); // for normal user public static final Set COMMON_PROPERTIES = Sets.newHashSet(); - // cluster which this user belongs to - String clusterName; - // save redundantly to simplify serialization - String userName; + private String qualifiedUser; - // SHA1(SHA1('password')) of byte[0] is unset - private byte[] password; - - // db- > priv - private Map dbPrivMap; - - private boolean isAdmin; - private boolean isSuperuser = false; - - private long maxConn; + private long maxConn = Config.max_conn_per_user; // Resource belong to this user. - private UserResource resource; + private UserResource resource = new UserResource(1000); // load cluster - private String defaultLoadCluster; - private Map clusterToDppConfig; + private String defaultLoadCluster = null; + private Map clusterToDppConfig = Maps.newHashMap(); - // whilelist - WhiteList whiteList; + /* + * We keep white list here to save Baidu domain name (BNS) or DNS as white list. + * Each frontend will periodically resolve the domain name to ip, and update the privilege table. + * We never persist the resolved IPs. + */ + private WhiteList whiteList = new WhiteList(); + + @Deprecated + private byte[] password; + @Deprecated + private boolean isAdmin = false; + @Deprecated + private boolean isSuperuser = false; + @Deprecated + private Map dbPrivMap = Maps.newHashMap(); static { - ADVANCED_PROPERTIES.add(Pattern.compile("^" + MAX_USER_CONNECTIONS + "$", Pattern.CASE_INSENSITIVE)); - ADVANCED_PROPERTIES.add(Pattern.compile("^" + RESOURCE + ".", Pattern.CASE_INSENSITIVE)); - ADVANCED_PROPERTIES.add(Pattern.compile( - "^" + LOAD_CLUSTER + "." + DppConfig.CLUSTER_NAME_REGEX + "." + DppConfig.PRIORITY + "$", - Pattern.CASE_INSENSITIVE)); + ADVANCED_PROPERTIES.add(Pattern.compile("^" + PROP_MAX_USER_CONNECTIONS + "$", Pattern.CASE_INSENSITIVE)); + ADVANCED_PROPERTIES.add(Pattern.compile("^" + PROP_RESOURCE + ".", Pattern.CASE_INSENSITIVE)); + ADVANCED_PROPERTIES.add(Pattern.compile("^" + PROP_LOAD_CLUSTER + "." + DppConfig.CLUSTER_NAME_REGEX + "." + + DppConfig.PRIORITY + "$", Pattern.CASE_INSENSITIVE)); - COMMON_PROPERTIES.add(Pattern.compile("^" + QUOTA + ".", Pattern.CASE_INSENSITIVE)); - COMMON_PROPERTIES.add(Pattern.compile("^" + DEFAULT_LOAD_CLUSTER + "$", Pattern.CASE_INSENSITIVE)); - COMMON_PROPERTIES.add(Pattern.compile("^" + LOAD_CLUSTER + "." + DppConfig.CLUSTER_NAME_REGEX + ".", + COMMON_PROPERTIES.add(Pattern.compile("^" + PROP_QUOTA + ".", Pattern.CASE_INSENSITIVE)); + COMMON_PROPERTIES.add(Pattern.compile("^" + PROP_DEFAULT_LOAD_CLUSTER + "$", Pattern.CASE_INSENSITIVE)); + COMMON_PROPERTIES.add(Pattern.compile("^" + PROP_LOAD_CLUSTER + "." + DppConfig.CLUSTER_NAME_REGEX + ".", Pattern.CASE_INSENSITIVE)); } public UserProperty() { - clusterName = ""; - userName = null; - password = new byte[0]; - dbPrivMap = Maps.newHashMap(); - isAdmin = false; - maxConn = Config.max_conn_per_user; - resource = new UserResource(1000); - defaultLoadCluster = null; - clusterToDppConfig = Maps.newHashMap(); - whiteList = new WhiteList(); } - public String getClusterName() { - return clusterName; + public UserProperty(String qualifiedUser) { + this.qualifiedUser = qualifiedUser; } - public void setClusterName(String name) { - this.clusterName = name; - } - - public void setIsSuperuser(boolean isSuperuser) { - this.isSuperuser = isSuperuser; - } - - public String getUser() { - return userName; - } - - public void setUser(String userName) { - this.userName = userName; - whiteList.setUser(userName); - } - - public void setIsAdmin(boolean isAdmin) { - this.isAdmin = isAdmin; - } - - public boolean isAdmin() { - return isAdmin; - } - - public boolean isSuperuser() { - if (isAdmin) { - return true; - } - return isSuperuser; + public String getQualifiedUser() { + return qualifiedUser; } public long getMaxConn() { return maxConn; } + public WhiteList getWhiteList() { + return whiteList; + } + + @Deprecated public byte[] getPassword() { return password; } - public void setPassword(byte[] password) { - this.password = password; + @Deprecated + public boolean isAdmin() { + return isAdmin; + } + + @Deprecated + public boolean isSuperuser() { + return isSuperuser; + } + + @Deprecated + public Map getDbPrivMap() { + return dbPrivMap; + } + + public void addOrGrantWhiteList(String domain, Map tblPatternToPrivs, + byte[] password, boolean errOnExist) throws DdlException { + if (errOnExist && whiteList.containsDomain(domain)) { + throw new DdlException("white list " + domain + " of user " + qualifiedUser + " already exists"); + } + + if (tblPatternToPrivs.isEmpty()) { + // maybe this is a create user operation, so privs is empty + TablePattern tablePattern = new TablePattern("*", "*"); + try { + tablePattern.analyze(""); + } catch (AnalysisException e) { + LOG.warn("should not happen", e); + } + whiteList.addDomainWithPrivs(domain, tablePattern, PrivBitSet.of()); + } else { + for (Map.Entry entry : tblPatternToPrivs.entrySet()) { + whiteList.addDomainWithPrivs(domain, entry.getKey(), entry.getValue()); + } + } + + if (password != null) { + whiteList.setPassword(password); + } + } + + public void revokePrivsFromWhiteList(String domain, Map privsMap, + boolean errOnNonExist) throws DdlException { + // we need to check it before doing any change + for (Map.Entry entry : privsMap.entrySet()) { + whiteList.revokePrivsFromDomain(domain, entry.getKey(), entry.getValue(), + errOnNonExist, true /* check */); + } + + for (Map.Entry entry : privsMap.entrySet()) { + whiteList.revokePrivsFromDomain(domain, entry.getKey(), entry.getValue(), + errOnNonExist, false /* check */); + } } public void update(List propertyVarList) throws DdlException { @@ -169,25 +202,25 @@ public class UserProperty implements Writable { String value = propertyVar.getPropertyValue(); String[] keyArr = key.split("\\" + SetUserPropertyVar.DOT_SEPARATOR); - if (keyArr[0].equalsIgnoreCase(MAX_USER_CONNECTIONS)) { + if (keyArr[0].equalsIgnoreCase(PROP_MAX_USER_CONNECTIONS)) { // set property "max_user_connections" = "1000" if (keyArr.length != 1) { - throw new DdlException(MAX_USER_CONNECTIONS + " format error"); + throw new DdlException(PROP_MAX_USER_CONNECTIONS + " format error"); } try { newMaxConn = Long.parseLong(value); } catch (NumberFormatException e) { - throw new DdlException(MAX_USER_CONNECTIONS + " is not number"); + throw new DdlException(PROP_MAX_USER_CONNECTIONS + " is not number"); } if (newMaxConn <= 0 || newMaxConn > 10000) { - throw new DdlException(MAX_USER_CONNECTIONS + " is not valid, must between 1 and 10000"); + throw new DdlException(PROP_MAX_USER_CONNECTIONS + " is not valid, must between 1 and 10000"); } - } else if (keyArr[0].equalsIgnoreCase(RESOURCE)) { + } else if (keyArr[0].equalsIgnoreCase(PROP_RESOURCE)) { // set property "resource.cpu_share" = "100" if (keyArr.length != 2) { - throw new DdlException(RESOURCE + " format error"); + throw new DdlException(PROP_RESOURCE + " format error"); } int resource = 0; @@ -202,10 +235,10 @@ public class UserProperty implements Writable { } newResource.updateResource(keyArr[1], resource); - } else if (keyArr[0].equalsIgnoreCase(QUOTA)) { + } else if (keyArr[0].equalsIgnoreCase(PROP_QUOTA)) { // set property "quota.normal" = "100" if (keyArr.length != 2) { - throw new DdlException(QUOTA + " format error"); + throw new DdlException(PROP_QUOTA + " format error"); } int quota = 0; @@ -220,12 +253,12 @@ public class UserProperty implements Writable { } newResource.updateGroupShare(keyArr[1], quota); - } else if (keyArr[0].equalsIgnoreCase(LOAD_CLUSTER)) { + } else if (keyArr[0].equalsIgnoreCase(PROP_LOAD_CLUSTER)) { updateLoadCluster(keyArr, value, newDppConfigs); - } else if (keyArr[0].equalsIgnoreCase(DEFAULT_LOAD_CLUSTER)) { + } else if (keyArr[0].equalsIgnoreCase(PROP_DEFAULT_LOAD_CLUSTER)) { // set property "default_load_cluster" = "cluster1" if (keyArr.length != 1) { - throw new DdlException(DEFAULT_LOAD_CLUSTER + " format error"); + throw new DdlException(PROP_DEFAULT_LOAD_CLUSTER + " format error"); } if (value != null && !newDppConfigs.containsKey(value)) { throw new DdlException("Load cluster[" + value + "] does not exist"); @@ -287,43 +320,10 @@ public class UserProperty implements Writable { throw new DdlException(e.getMessage()); } } else { - throw new DdlException(LOAD_CLUSTER + " format error"); + throw new DdlException(PROP_LOAD_CLUSTER + " format error"); } } - // 用于判断一个用户是否对于需要访问的数据库有相应的权限 - public boolean checkAccess(String db, AccessPrivilege priv) { - if (isSuperuser()) { - return true; - } - // information_schema is case insensitive - String dbPrivMapKey = db; - final String qualifiedDbNameSuffix = ClusterNamespace.getNameFromFullName(db); - if (qualifiedDbNameSuffix.equalsIgnoreCase(InfoSchemaDb.DATABASE_NAME)) { - dbPrivMapKey = db.toLowerCase(); - } - final AccessPrivilege dbPriv = dbPrivMap.get(dbPrivMapKey); - if (dbPriv == null) { - return false; - } - return dbPriv.contains(priv); - } - - // 修改用户已有的权限, 有可能当前对此DB无权限, 有可能已经有权限了,只是修改 - // 无论如何,直接覆盖就OK了。 - public void setAccess(String db, AccessPrivilege priv) { - dbPrivMap.put(db, priv); - } - - public void revokeAccess(String db) throws DdlException { - if (!dbPrivMap.containsKey(db)) { - throw new DdlException("User[" + userName + "] has no privilege on database[" + db + "]"); - } - - // just remove all privilege - dbPrivMap.remove(db); - } - public UserResource getResource() { return resource; } @@ -332,7 +332,7 @@ public class UserProperty implements Writable { return defaultLoadCluster; } - public Pair getClusterInfo(String cluster) { + public Pair getLoadClusterInfo(String cluster) { String tmpCluster = cluster; if (tmpCluster == null) { tmpCluster = defaultLoadCluster; @@ -354,32 +354,32 @@ public class UserProperty implements Writable { String dot = SetUserPropertyVar.DOT_SEPARATOR; // max user connections - result.add(Lists.newArrayList(MAX_USER_CONNECTIONS, String.valueOf(maxConn))); + result.add(Lists.newArrayList(PROP_MAX_USER_CONNECTIONS, String.valueOf(maxConn))); // resource ResourceGroup group = resource.getResource(); for (Map.Entry entry : group.getQuotaMap().entrySet()) { - result.add(Lists.newArrayList(RESOURCE + dot + entry.getKey().getDesc().toLowerCase(), + result.add(Lists.newArrayList(PROP_RESOURCE + dot + entry.getKey().getDesc().toLowerCase(), entry.getValue().toString())); } // quota Map groups = resource.getShareByGroup(); for (Map.Entry entry : groups.entrySet()) { - result.add(Lists.newArrayList(QUOTA + dot + entry.getKey(), entry.getValue().toString())); + result.add(Lists.newArrayList(PROP_QUOTA + dot + entry.getKey(), entry.getValue().toString())); } // load cluster if (defaultLoadCluster != null) { - result.add(Lists.newArrayList(DEFAULT_LOAD_CLUSTER, defaultLoadCluster)); + result.add(Lists.newArrayList(PROP_DEFAULT_LOAD_CLUSTER, defaultLoadCluster)); } else { - result.add(Lists.newArrayList(DEFAULT_LOAD_CLUSTER, "")); + result.add(Lists.newArrayList(PROP_DEFAULT_LOAD_CLUSTER, "")); } for (Map.Entry entry : clusterToDppConfig.entrySet()) { String cluster = entry.getKey(); DppConfig dppConfig = entry.getValue(); - String clusterPrefix = LOAD_CLUSTER + dot + cluster + dot; + String clusterPrefix = PROP_LOAD_CLUSTER + dot + cluster + dot; // palo path if (dppConfig.getPaloPath() != null) { @@ -404,6 +404,16 @@ public class UserProperty implements Writable { result.add(Lists.newArrayList(clusterPrefix + DppConfig.getPriorityKey(), String.valueOf(dppConfig.getPriority()))); } + + // get resolved ips if user has domain + Map> resolvedIPs = whiteList.getResolvedIPs(); + List ips = Lists.newArrayList(); + for (Map.Entry> entry : resolvedIPs.entrySet()) { + ips.add(entry.getKey() + ":" + Joiner.on(",").join(entry.getValue())); + } + if (!ips.isEmpty()) { + result.add(Lists.newArrayList("resolved IPs", Joiner.on(";").join(ips))); + } // sort Collections.sort(result, new Comparator>() { @@ -416,41 +426,22 @@ public class UserProperty implements Writable { return result; } - public String fetchPrivilegeResult() { - StringBuilder stringBuilder = new StringBuilder(); - boolean isFirst = true; - for (Map.Entry entry : dbPrivMap.entrySet()) { - if (!isFirst) { - stringBuilder.append(", "); - } - String dbName = entry.getKey(); - AccessPrivilege privilege = entry.getValue(); - stringBuilder.append(dbName).append("(").append(privilege.name()).append(")"); - isFirst = false; - } - return stringBuilder.toString(); + public void getAuthInfo(List> userAuthInfos) { + whiteList.getAuthInfo(qualifiedUser, userAuthInfos); } + public static UserProperty read(DataInput in) throws IOException { + UserProperty userProperty = new UserProperty(); + userProperty.readFields(in); + return userProperty; + } + + @Override public void write(DataOutput out) throws IOException { - if (userName == null) { - out.writeBoolean(false); - } else { - out.writeBoolean(true); - Text.writeString(out, userName); - } - out.writeInt(password.length); - out.write(password); - out.writeBoolean(isAdmin); - out.writeBoolean(isSuperuser); + Text.writeString(out, qualifiedUser); out.writeLong(maxConn); - int numPriv = dbPrivMap.size(); - out.writeInt(numPriv); - for (Map.Entry entry : dbPrivMap.entrySet()) { - Text.writeString(out, entry.getKey()); - Text.writeString(out, entry.getValue().name()); - } - // User resource + // user resource resource.write(out); // load cluster @@ -466,46 +457,52 @@ public class UserProperty implements Writable { Text.writeString(out, entry.getKey()); entry.getValue().write(out); } + whiteList.write(out); - if (userName == null) { - out.writeBoolean(false); - } else { - out.writeBoolean(true); - Text.writeString(out, clusterName); - } } public void readFields(DataInput in) throws IOException { - if (in.readBoolean()) { - if (Catalog.getCurrentCatalogJournalVersion() < FeMetaVersion.VERSION_30) { - userName = ClusterNamespace.getFullName(SystemInfoService.DEFAULT_CLUSTER, Text.readString(in)); - } else { - userName = Text.readString(in); - } + if (Catalog.getCurrentCatalogJournalVersion() < FeMetaVersion.VERSION_43) { + // consume the flag of empty user name + in.readBoolean(); + } + + // user name + if (Catalog.getCurrentCatalogJournalVersion() < FeMetaVersion.VERSION_30) { + qualifiedUser = ClusterNamespace.getFullName(SystemInfoService.DEFAULT_CLUSTER, Text.readString(in)); + } else { + qualifiedUser = Text.readString(in); } - int passwordLen = in.readInt(); - password = new byte[passwordLen]; - in.readFully(password); - isAdmin = in.readBoolean(); - if (Catalog.getCurrentCatalogJournalVersion() >= 1) { - isSuperuser = in.readBoolean(); + if (Catalog.getCurrentCatalogJournalVersion() < FeMetaVersion.VERSION_43) { + int passwordLen = in.readInt(); + password = new byte[passwordLen]; + in.readFully(password); + + isAdmin = in.readBoolean(); + + if (Catalog.getCurrentCatalogJournalVersion() >= 1) { + isSuperuser = in.readBoolean(); + } } maxConn = in.readLong(); - int numPriv = in.readInt(); - for (int i = 0; i < numPriv; ++i) { - String dbName; - if (Catalog.getCurrentCatalogJournalVersion() < FeMetaVersion.VERSION_30) { - dbName = ClusterNamespace.getFullName(SystemInfoService.DEFAULT_CLUSTER, Text.readString(in)); - } else { - dbName = Text.readString(in); + if (Catalog.getCurrentCatalogJournalVersion() < FeMetaVersion.VERSION_43) { + int numPriv = in.readInt(); + for (int i = 0; i < numPriv; ++i) { + String dbName = null; + if (Catalog.getCurrentCatalogJournalVersion() < FeMetaVersion.VERSION_30) { + dbName = ClusterNamespace.getFullName(SystemInfoService.DEFAULT_CLUSTER, Text.readString(in)); + } else { + dbName = Text.readString(in); + } + AccessPrivilege ap = AccessPrivilege.valueOf(Text.readString(in)); + dbPrivMap.put(dbName, ap); } - AccessPrivilege accessPrivilege = AccessPrivilege.valueOf(Text.readString(in)); - dbPrivMap.put(dbName, accessPrivilege); } - // User resource + + // user resource resource = UserResource.readIn(in); // load cluster @@ -524,24 +521,16 @@ public class UserProperty implements Writable { } if (Catalog.getCurrentCatalogJournalVersion() >= FeMetaVersion.VERSION_21) { - whiteList.setUser(userName); whiteList.readFields(in); } - if (Catalog.getCurrentCatalogJournalVersion() >= FeMetaVersion.VERSION_30) { - if (in.readBoolean()) { - clusterName = Text.readString(in); + if (Catalog.getCurrentCatalogJournalVersion() < FeMetaVersion.VERSION_43) { + if (Catalog.getCurrentCatalogJournalVersion() >= FeMetaVersion.VERSION_30) { + if (in.readBoolean()) { + // consume cluster name + Text.readString(in); + } } } } - - public static UserProperty read(DataInput in) throws IOException { - UserProperty userProperty = new UserProperty(); - userProperty.readFields(in); - return userProperty; - } - - public WhiteList getWhiteList() { - return whiteList; - } } diff --git a/fe/src/com/baidu/palo/mysql/privilege/UserPropertyMgr.java b/fe/src/com/baidu/palo/mysql/privilege/UserPropertyMgr.java new file mode 100644 index 0000000000..d8db9982f8 --- /dev/null +++ b/fe/src/com/baidu/palo/mysql/privilege/UserPropertyMgr.java @@ -0,0 +1,302 @@ +// Copyright (c) 2017, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +package com.baidu.palo.mysql.privilege; + +import com.baidu.palo.analysis.SetUserPropertyStmt; +import com.baidu.palo.analysis.TablePattern; +import com.baidu.palo.analysis.UserIdentity; +import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.Config; +import com.baidu.palo.common.DdlException; +import com.baidu.palo.common.FeMetaVersion; +import com.baidu.palo.common.Pair; +import com.baidu.palo.common.io.Writable; +import com.baidu.palo.common.publish.FixedTimePublisher; +import com.baidu.palo.common.publish.Listener; +import com.baidu.palo.common.publish.TopicUpdate; +import com.baidu.palo.load.DppConfig; +import com.baidu.palo.thrift.TAgentServiceVersion; +import com.baidu.palo.thrift.TFetchResourceResult; +import com.baidu.palo.thrift.TTopicItem; +import com.baidu.palo.thrift.TTopicType; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; + +public class UserPropertyMgr implements Writable { + private static final Logger LOG = LogManager.getLogger(UserPropertyMgr.class); + + protected Map propertyMap = Maps.newHashMap(); + public static final String ROOT_USER = "root"; + public static final String SYSTEM_RESOURCE_USER = "system"; + private AtomicLong resourceVersion = new AtomicLong(0); + + public UserPropertyMgr() { + } + + // Register callback to FixedTimePublisher + public void setUp() { + FixedTimePublisher.getInstance().register(new FixedTimePublisher.Callback() { + @Override + public TopicUpdate getTopicUpdate() { + TopicUpdate update = new TopicUpdate(TTopicType.RESOURCE); + TTopicItem tTopicItem = new TTopicItem("version"); + tTopicItem.setInt_value(resourceVersion.get()); + update.addUpdates(tTopicItem); + return update; + } + + @Override + public Listener getListener() { + return null; + } + }, Config.meta_resource_publish_interval_ms); + } + + public void addUserResource(String qualifiedUser, boolean isSystemUser) { + UserProperty property = propertyMap.get(qualifiedUser); + if (property != null) { + return; + } + + property = new UserProperty(qualifiedUser); + + // set user properties + try { + if (isSystemUser) { + setSystemUserDefaultResource(property); + } else { + setNormalUserDefaultResource(property); + } + } catch (DdlException e) { + // this should not happen, because the value is set by us!! + } + + propertyMap.put(qualifiedUser, property); + resourceVersion.incrementAndGet(); + } + + /* + * Try to grant privs to whitelist of the user. + */ + public void addOrGrantWhiteList(UserIdentity userIdentity, Map privsMap, + byte[] password, boolean errOnExist, boolean errOnNonExist) throws DdlException { + Preconditions.checkArgument(userIdentity.isDomain()); + UserProperty property = propertyMap.get(userIdentity.getQualifiedUser()); + if (property == null) { + if (errOnNonExist) { + throw new DdlException("user " + userIdentity + " does not exist"); + } + property = new UserProperty(userIdentity.getQualifiedUser()); + } + + property.addOrGrantWhiteList(userIdentity.getHost(), privsMap, password, errOnExist); + // update propertyMap after addOrGrantWhiteList, cause addOrGrantWhiteList may throw exception + propertyMap.put(userIdentity.getQualifiedUser(), property); + } + + public void revokePrivsFromWhiteList(UserIdentity userIdentity, Map privsMap, + boolean errOnNonExist) throws DdlException { + Preconditions.checkArgument(userIdentity.isDomain()); + UserProperty property = propertyMap.get(userIdentity.getQualifiedUser()); + if (property == null && errOnNonExist) { + throw new DdlException("User " + userIdentity.getQualifiedUser() + " does not exist"); + } + + property.revokePrivsFromWhiteList(userIdentity.getHost(), privsMap, errOnNonExist); + } + + public void dropUser(String qualifiedUser) { + propertyMap.remove(qualifiedUser); + resourceVersion.incrementAndGet(); + } + + public void setPassword(UserIdentity userIdent, byte[] password) throws DdlException { + Preconditions.checkArgument(userIdent.isDomain()); + UserProperty property = propertyMap.get(userIdent.getQualifiedUser()); + if (property == null) { + throw new DdlException("user " + userIdent.getQualifiedUser() + " does not exist"); + } + + if (property.getWhiteList().containsDomain(userIdent.getHost())) { + throw new DdlException("user " + userIdent + " does not exist"); + } + + property.getWhiteList().setPassword(password); + } + + public void updateUserProperty(SetUserPropertyStmt stmt) throws DdlException { + UserProperty property = propertyMap.get(stmt.getUser()); + if (property == null) { + throw new DdlException("Unknown user(" + stmt.getUser() + ")"); + } + + property.update(stmt.getPropertyList()); + } + + public long getMaxConn(String qualifiedUser) { + UserProperty existProperty = propertyMap.get(qualifiedUser); + if (existProperty == null) { + return 0; + } + return existProperty.getMaxConn(); + } + + public int getPropertyMapSize() { + return propertyMap.size(); + } + + private void setSystemUserDefaultResource(UserProperty user) throws DdlException { + UserResource userResource = user.getResource(); + userResource.updateResource("CPU_SHARE", 100); + userResource.updateResource("IO_SHARE", 100); + userResource.updateResource("SSD_READ_MBPS", 30); + userResource.updateResource("SSD_WRITE_MBPS", 30); + userResource.updateResource("HDD_READ_MBPS", 30); + userResource.updateResource("HDD_WRITE_MBPS", 30); + } + + private void setNormalUserDefaultResource(UserProperty user) throws DdlException { + UserResource userResource = user.getResource(); + userResource.updateResource("CPU_SHARE", 1000); + userResource.updateResource("IO_SHARE", 1000); + userResource.updateResource("SSD_READ_IOPS", 1000); + userResource.updateResource("HDD_READ_IOPS", 80); + userResource.updateResource("SSD_READ_MBPS", 30); + userResource.updateResource("HDD_READ_MBPS", 30); + } + + public TFetchResourceResult toResourceThrift() { + TFetchResourceResult tResult = new TFetchResourceResult(); + tResult.setProtocolVersion(TAgentServiceVersion.V1); + tResult.setResourceVersion(resourceVersion.get()); + + for (Map.Entry entry : propertyMap.entrySet()) { + tResult.putToResourceByUser(entry.getKey(), entry.getValue().getResource().toThrift()); + } + + return tResult; + } + + public Pair getLoadClusterInfo(String qualifiedUser, String cluster) throws DdlException { + Pair loadClusterInfo = null; + + if (!propertyMap.containsKey(qualifiedUser)) { + throw new DdlException("User " + qualifiedUser + " does not exist"); + } + + UserProperty property = propertyMap.get(qualifiedUser); + loadClusterInfo = property.getLoadClusterInfo(cluster); + return loadClusterInfo; + } + + public List> fetchUserProperty(String qualifiedUser) throws AnalysisException { + if (!propertyMap.containsKey(qualifiedUser)) { + throw new AnalysisException("User " + qualifiedUser + " does not exist"); + } + + UserProperty property = propertyMap.get(qualifiedUser); + return property.fetchProperty(); + } + + public void getCopiedWhiteList(Map> userMap) { + LOG.debug("get property map: {}", propertyMap); + for (Map.Entry entry : propertyMap.entrySet()) { + Set domains = entry.getValue().getWhiteList().getAllDomains(); + if (domains.isEmpty()) { + continue; + } + userMap.put(entry.getKey(), domains); + } + } + + public void updateResolovedIps(String qualifiedUser, String domain, Set resolvedIPs) { + if (!propertyMap.containsKey(qualifiedUser)) { + return; + } + + UserProperty property = propertyMap.get(qualifiedUser); + property.getWhiteList().updateResolovedIps(qualifiedUser, domain, resolvedIPs); + } + + public UserProperty getUserProperty(String qualifiedUserName) { + return propertyMap.get(qualifiedUserName); + } + + @Deprecated + public void addUserPropertyUnchecked(UserProperty userProperty) { + Preconditions.checkState(Catalog.getCurrentCatalogJournalVersion() < FeMetaVersion.VERSION_43); + propertyMap.put(userProperty.getQualifiedUser(), userProperty); + } + + public void transform(PaloAuth auth) { + Preconditions.checkState(Catalog.getCurrentCatalogJournalVersion() < FeMetaVersion.VERSION_43); + for (UserProperty userProperty : propertyMap.values()) { + auth.transformAndAddOldUserProperty(userProperty); + } + } + + public void getUserAuthInfo(List> userAuthInfos, UserIdentity specifiedUserIdent) { + for (UserProperty property : propertyMap.values()) { + if (specifiedUserIdent != null + && !property.getQualifiedUser().equals(specifiedUserIdent.getQualifiedUser())) { + continue; + } + property.getAuthInfo(userAuthInfos); + } + } + + public static UserPropertyMgr read(DataInput in) throws IOException { + UserPropertyMgr userPropertyMgr = new UserPropertyMgr(); + userPropertyMgr.readFields(in); + return userPropertyMgr; + } + + @Override + public void write(DataOutput out) throws IOException { + out.writeInt(propertyMap.size()); + for (Map.Entry entry : propertyMap.entrySet()) { + entry.getValue().write(out); + } + // Write resource version + out.writeLong(resourceVersion.get()); + } + + @Override + public void readFields(DataInput in) throws IOException { + int size = in.readInt(); + for (int i = 0; i < size; ++i) { + UserProperty userProperty = UserProperty.read(in); + propertyMap.put(userProperty.getQualifiedUser(), userProperty); + LOG.debug("read user property: {}: {}", userProperty.getQualifiedUser(), userProperty); + } + // Read resource + resourceVersion = new AtomicLong(in.readLong()); + } +} + diff --git a/fe/src/com/baidu/palo/catalog/UserResource.java b/fe/src/com/baidu/palo/mysql/privilege/UserResource.java similarity index 98% rename from fe/src/com/baidu/palo/catalog/UserResource.java rename to fe/src/com/baidu/palo/mysql/privilege/UserResource.java index 6d562630dd..ad0d250987 100644 --- a/fe/src/com/baidu/palo/catalog/UserResource.java +++ b/fe/src/com/baidu/palo/mysql/privilege/UserResource.java @@ -13,8 +13,9 @@ // specific language governing permissions and limitations // under the License. -package com.baidu.palo.catalog; +package com.baidu.palo.mysql.privilege; +import com.baidu.palo.catalog.ResourceGroup; import com.baidu.palo.common.DdlException; import com.baidu.palo.common.io.Text; import com.baidu.palo.common.io.Writable; diff --git a/fe/src/com/baidu/palo/mysql/privilege/WhiteList.java b/fe/src/com/baidu/palo/mysql/privilege/WhiteList.java new file mode 100644 index 0000000000..21169ad1ab --- /dev/null +++ b/fe/src/com/baidu/palo/mysql/privilege/WhiteList.java @@ -0,0 +1,329 @@ +// Copyright (c) 2017, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +package com.baidu.palo.mysql.privilege; + +import com.baidu.palo.analysis.TablePattern; +import com.baidu.palo.analysis.UserIdentity; +import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.common.DdlException; +import com.baidu.palo.common.FeMetaVersion; +import com.baidu.palo.common.io.Text; +import com.baidu.palo.common.io.Writable; +import com.baidu.palo.mysql.privilege.PaloAuth.PrivLevel; + +import com.google.common.base.Joiner; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Set; + +// grant privs.. on db.tbl to user@['domain.name'] +// revoke privs on db.tbl from user@['domain.name'] +public class WhiteList implements Writable { + private static final Logger LOG = LogManager.getLogger(WhiteList.class); + + // domain name -> (tbl pattern -> privs) + // Domain names which need to be resolved to IPs. + // Currently, only implemented for Baidu Name Service (BNS) + private Map> domainMap = Maps.newConcurrentMap(); + + // Domain name to resolved IPs + private Map> resolvedIPMap = Maps.newConcurrentMap(); + + private byte[] password; + + @Deprecated + protected Set ipWhiteLists = Sets.newHashSet(); + @Deprecated + protected Set starIpWhiteLists = Sets.newHashSet(); + + public WhiteList() { + } + + @Deprecated + public Set getIpWhiteLists() { + return ipWhiteLists; + } + + @Deprecated + public Set getStarIpWhiteLists() { + return starIpWhiteLists; + } + + public void setPassword(byte[] password) { + this.password = password; + } + + public void addDomainWithPrivs(String domainName, TablePattern tblPattern, PrivBitSet privs) { + Map privsMap = domainMap.get(domainName); + if (privsMap == null) { + privsMap = Maps.newConcurrentMap(); + domainMap.put(domainName, privsMap); + } + PrivBitSet existingPrivs = privsMap.get(tblPattern); + if (existingPrivs == null) { + existingPrivs = privs; + privsMap.put(tblPattern, existingPrivs); + } else { + existingPrivs.or(privs); + } + } + + public void revokePrivsFromDomain(String domainName, TablePattern tblPattern, PrivBitSet privs, + boolean errOnNonExist, boolean check) throws DdlException { + Map privsMap = domainMap.get(domainName); + if (privsMap == null && errOnNonExist) { + throw new DdlException("Domain " + domainName + " does not exist"); + } + + PrivBitSet existingPrivs = privsMap.get(tblPattern); + if (existingPrivs == null) { + throw new DdlException("No such grants on " + tblPattern); + } + + if (!check) { + existingPrivs.remove(privs); + } + } + + public void updateResolovedIps(String qualifiedUser, String domain, Set newResolvedIPs) { + Map privsMap = domainMap.get(domain); + if (privsMap == null) { + LOG.debug("domain does not exist in white list: {}", domain); + return; + } + + Set preResolvedIPs = resolvedIPMap.get(domain); + if (preResolvedIPs == null) { + preResolvedIPs = Sets.newHashSet(); + } + + // 1. grant for newly added IPs + for (String newIP : newResolvedIPs) { + UserIdentity userIdent = new UserIdentity(qualifiedUser, newIP); + userIdent.setIsAnalyzed(); + for (Map.Entry entry : privsMap.entrySet()) { + try { + // we copy the PrivBitSet, cause we don't want use the same PrivBitSet object in different place. + // otherwise, when we change the privs of the domain, the priv entry will be changed synchronously, + // which is not expected. + Catalog.getCurrentCatalog().getAuth().grantPrivs(userIdent, entry.getKey(), + entry.getValue().copy(), + false /* err on non exist */, + true /* set by resolver */); + } catch (DdlException e) { + LOG.warn("should not happen", e); + } + } + + // set password + try { + Catalog.getCurrentCatalog().getAuth().setPasswordInternal(userIdent, password, + true /* add if not exist */, + true /* set by resolver */, + true /* is replay */); + } catch (DdlException e) { + LOG.warn("should not happen", e); + } + } + + // 2. delete privs which does not exist anymore. + for (String preIP : preResolvedIPs) { + if (!newResolvedIPs.contains(preIP)) { + UserIdentity userIdent = new UserIdentity(qualifiedUser, preIP); + userIdent.setIsAnalyzed(); + for (Map.Entry entry : privsMap.entrySet()) { + try { + Catalog.getCurrentCatalog().getAuth().revokePrivs(userIdent, entry.getKey(), + entry.getValue(), + false, /* err on non exist */ + true /* set by domain */, + true /* delete entry when empty */); + } catch (DdlException e) { + LOG.warn("should not happen", e); + } + } + + // delete password + Catalog.getCurrentCatalog().getAuth().deletePassworEntry(userIdent); + } + } + + // update resolved ip map + resolvedIPMap.put(domain, newResolvedIPs); + } + + public Map> getResolvedIPs() { + return resolvedIPMap; + } + + public boolean containsDomain(String domain) { + return domainMap.containsKey(domain); + } + + public Set getAllDomains() { + return Sets.newHashSet(domainMap.keySet()); + } + + public void updateDomainMap(String domain, Map privs) { + domainMap.put(domain, privs); + } + + public void getAuthInfo(String qualifiedUser, List> userAuthInfos) { + LOG.debug("get domain privs for {}, domain map: {}", qualifiedUser, domainMap); + for (Map.Entry> entry : domainMap.entrySet()) { + List userEntry = Lists.newArrayList(); + UserIdentity tmpUserIdent = new UserIdentity(qualifiedUser, entry.getKey(), true); + tmpUserIdent.setIsAnalyzed(); + + Map privsMap = entry.getValue(); + // global privs + for (Map.Entry privsEntry : privsMap.entrySet()) { + if (privsEntry.getKey().getPrivLevel() != PrivLevel.GLOBAL) { + continue; + } + userEntry.add(tmpUserIdent.toString()); + userEntry.add(password == null ? "N/A" : "Yes"); + userEntry.add(privsEntry.getValue().toString()); + // at most one global priv entry + break; + } + if (userEntry.isEmpty()) { + userEntry.add(tmpUserIdent.toString()); + userEntry.add("N/A"); + userEntry.add("N/A"); + } + + // db privs + List dbPrivs = Lists.newArrayList(); + for (Map.Entry privsEntry : privsMap.entrySet()) { + if (privsEntry.getKey().getPrivLevel() != PrivLevel.DATABASE) { + continue; + } + dbPrivs.add(privsEntry.getKey().getQuolifiedDb() + ": " + privsEntry.getValue().toString()); + } + if (!dbPrivs.isEmpty()) { + userEntry.add(Joiner.on("\n").join(dbPrivs)); + } else { + userEntry.add("N/A"); + } + + // tbl privs + List tblPrivs = Lists.newArrayList(); + for (Map.Entry privsEntry : privsMap.entrySet()) { + if (privsEntry.getKey().getPrivLevel() != PrivLevel.TABLE) { + continue; + } + tblPrivs.add(privsEntry.getKey().toString() + ": " + privsEntry.getValue().toString()); + } + if (!tblPrivs.isEmpty()) { + userEntry.add(Joiner.on("\n").join(tblPrivs)); + } else { + userEntry.add("N/A"); + } + + userAuthInfos.add(userEntry); + } + } + + @Override + public String toString() { + return domainMap.toString(); + } + + @Override + public void write(DataOutput out) throws IOException { + out.writeInt(domainMap.size()); + for (String domain : domainMap.keySet()) { + Text.writeString(out, domain); + Map privsMap = domainMap.get(domain); + out.writeInt(privsMap.size()); + for (Map.Entry entry : privsMap.entrySet()) { + entry.getKey().write(out); + entry.getValue().write(out); + } + } + + if (password == null) { + out.writeBoolean(false); + } else { + out.writeBoolean(true); + out.writeInt(password.length); + out.write(password); + } + } + + @Override + public void readFields(DataInput in) throws IOException { + if (Catalog.getCurrentCatalogJournalVersion() < FeMetaVersion.VERSION_43) { + int ipWhiteListsLen = in.readInt(); + for (int i = 0; i < ipWhiteListsLen; i++) { + ipWhiteLists.add(Text.readString(in)); + } + LOG.debug("get white list ip: {}", ipWhiteLists); + + int starIpWhiteListsLen = in.readInt(); + for (int i = 0; i < starIpWhiteListsLen; i++) { + starIpWhiteLists.add(Text.readString(in)); + } + LOG.debug("get star white list ip: {}", starIpWhiteLists); + + int size = in.readInt(); + for (int i = 0; i < size; i++) { + String domainName = Text.readString(in); + Map privMap = Maps.newConcurrentMap(); + // NOTICE: for forward compatibility. but we can't get user's privs here, + // so set it to empty privs on *.*. and we will set privs later + TablePattern tablePattern = TablePattern.ALL; + PrivBitSet privs = PrivBitSet.of(); + privMap.put(tablePattern, privs); + domainMap.put(domainName, privMap); + } + LOG.debug("get domain map: {}", domainMap); + } + + if (Catalog.getCurrentCatalogJournalVersion() >= FeMetaVersion.VERSION_43) { + int size = in.readInt(); + for (int i = 0; i < size; i++) { + String domain = Text.readString(in); + Map privsMap = Maps.newConcurrentMap(); + domainMap.put(domain, privsMap); + + int count = in.readInt(); + for (int j = 0; j < count; j++) { + TablePattern tablePattern = TablePattern.read(in); + PrivBitSet privs = PrivBitSet.read(in); + privsMap.put(tablePattern, privs); + } + } + + if (in.readBoolean()) { + int passwordLen = in.readInt(); + password = new byte[passwordLen]; + in.readFully(password); + } + } + } +} diff --git a/fe/src/com/baidu/palo/persist/EditLog.java b/fe/src/com/baidu/palo/persist/EditLog.java index 9b8d2d772c..6747219664 100644 --- a/fe/src/com/baidu/palo/persist/EditLog.java +++ b/fe/src/com/baidu/palo/persist/EditLog.java @@ -18,12 +18,15 @@ package com.baidu.palo.persist; import com.baidu.palo.alter.DecommissionBackendJob; import com.baidu.palo.alter.RollupJob; import com.baidu.palo.alter.SchemaChangeJob; +import com.baidu.palo.analysis.UserIdentity; import com.baidu.palo.backup.BackupJob; +import com.baidu.palo.backup.BackupJob_D; +import com.baidu.palo.backup.Repository; import com.baidu.palo.backup.RestoreJob; +import com.baidu.palo.backup.RestoreJob_D; import com.baidu.palo.catalog.BrokerMgr; import com.baidu.palo.catalog.Catalog; import com.baidu.palo.catalog.Database; -import com.baidu.palo.catalog.UserProperty; import com.baidu.palo.cluster.BaseParam; import com.baidu.palo.cluster.Cluster; import com.baidu.palo.common.Config; @@ -45,6 +48,7 @@ import com.baidu.palo.load.Load; import com.baidu.palo.load.LoadErrorHub; import com.baidu.palo.load.LoadJob; import com.baidu.palo.metric.MetricRepo; +import com.baidu.palo.mysql.privilege.UserProperty; import com.baidu.palo.qe.SessionVariable; import com.baidu.palo.system.Backend; import com.baidu.palo.system.Frontend; @@ -229,29 +233,26 @@ public class EditLog { catalog.replayRenamePartition(info); break; } - case OperationType.OP_BACKUP_START: { - BackupJob job = (BackupJob) journal.getData(); - catalog.getBackupHandler().replayBackupStart(catalog, job); - break; - } - case OperationType.OP_BACKUP_FINISH_SNAPSHOT: { - BackupJob job = (BackupJob) journal.getData(); - catalog.getBackupHandler().replayBackupFinishSnapshot(job); - break; - } + case OperationType.OP_BACKUP_START: + case OperationType.OP_BACKUP_FINISH_SNAPSHOT: case OperationType.OP_BACKUP_FINISH: { - BackupJob job = (BackupJob) journal.getData(); - catalog.getBackupHandler().replayBackupFinish(catalog, job); - break; - } - case OperationType.OP_RESTORE_START: { - RestoreJob job = (RestoreJob) journal.getData(); - catalog.getBackupHandler().replayRestoreStart(catalog, job); + BackupJob_D job = (BackupJob_D) journal.getData(); break; } + case OperationType.OP_RESTORE_START: case OperationType.OP_RESTORE_FINISH: { + RestoreJob_D job = (RestoreJob_D) journal.getData(); + break; + } + case OperationType.OP_BACKUP_JOB: { + BackupJob job = (BackupJob) journal.getData(); + catalog.getBackupHandler().replayAddJob(job); + break; + } + case OperationType.OP_RESTORE_JOB: { RestoreJob job = (RestoreJob) journal.getData(); - catalog.getBackupHandler().replayRestoreFinish(catalog, job); + job.setCatalog(catalog); + catalog.getBackupHandler().replayAddJob(job); break; } case OperationType.OP_START_ROLLUP: { @@ -418,13 +419,48 @@ public class EditLog { break; } case OperationType.OP_ALTER_ACCESS_RESOURCE: { - UserProperty resource = (UserProperty) journal.getData(); - catalog.getUserMgr().replayAlterAccess(resource); + UserProperty userProperty = (UserProperty) journal.getData(); + catalog.getAuth().replayAlterAccess(userProperty); break; } case OperationType.OP_DROP_USER: { String userName = ((Text) journal.getData()).toString(); - catalog.getUserMgr().replayDropUser(userName); + catalog.getAuth().replayOldDropUser(userName); + break; + } + case OperationType.OP_CREATE_USER: { + PrivInfo privInfo = (PrivInfo) journal.getData(); + catalog.getAuth().replayCreateUser(privInfo); + break; + } + case OperationType.OP_NEW_DROP_USER: { + UserIdentity userIdent = (UserIdentity) journal.getData(); + catalog.getAuth().replayDropUser(userIdent); + break; + } + case OperationType.OP_GRANT_PRIV: { + PrivInfo privInfo = (PrivInfo) journal.getData(); + catalog.getAuth().replayGrant(privInfo); + break; + } + case OperationType.OP_REVOKE_PRIV: { + PrivInfo privInfo = (PrivInfo) journal.getData(); + catalog.getAuth().replayRevoke(privInfo); + break; + } + case OperationType.OP_SET_PASSWORD: { + PrivInfo privInfo = (PrivInfo) journal.getData(); + catalog.getAuth().replaySetPassword(privInfo); + break; + } + case OperationType.OP_CREATE_ROLE: { + PrivInfo privInfo = (PrivInfo) journal.getData(); + catalog.getAuth().replayCreateRole(privInfo); + break; + } + case OperationType.OP_DROP_ROLE: { + PrivInfo privInfo = (PrivInfo) journal.getData(); + catalog.getAuth().replayDropRole(privInfo); break; } case OperationType.OP_TIMESTAMP: { @@ -514,9 +550,20 @@ public class EditLog { catalog.replayUpdateClusterAndBackends(info); break; } + case OperationType.OP_CREATE_REPOSITORY: { + Repository repository = (Repository) journal.getData(); + catalog.getBackupHandler().getRepoMgr().addAndInitRepoIfNotExist(repository, true); + break; + } + case OperationType.OP_DROP_REPOSITORY: { + String repoName = ((Text) journal.getData()).toString(); + catalog.getBackupHandler().getRepoMgr().removeRepo(repoName, true); + break; + } default: { IOException e = new IOException(); LOG.error("UNKNOWN Operation Type {}", opCode, e); + throw e; } } } catch (Exception e) { @@ -777,10 +824,39 @@ public class EditLog { logEdit(OperationType.OP_ALTER_ACCESS_RESOURCE, userProperty); } + @Deprecated public void logDropUser(String userName) { logEdit(OperationType.OP_DROP_USER, new Text(userName)); } + public void logCreateUser(PrivInfo info) { + logEdit(OperationType.OP_CREATE_USER, info); + } + + public void logNewDropUser(UserIdentity userIdent) { + logEdit(OperationType.OP_NEW_DROP_USER, userIdent); + } + + public void logGrantPriv(PrivInfo info) { + logEdit(OperationType.OP_GRANT_PRIV, info); + } + + public void logRevokePriv(PrivInfo info) { + logEdit(OperationType.OP_REVOKE_PRIV, info); + } + + public void logSetPassword(PrivInfo info) { + logEdit(OperationType.OP_SET_PASSWORD, info); + } + + public void logCreateRole(PrivInfo info) { + logEdit(OperationType.OP_CREATE_ROLE, info); + } + + public void logDropRole(PrivInfo info) { + logEdit(OperationType.OP_DROP_ROLE, info); + } + public void logStartDecommissionBackend(DecommissionBackendJob job) { logEdit(OperationType.OP_START_DECOMMISSION_BACKEND, job); } @@ -809,23 +885,23 @@ public class EditLog { logEdit(OperationType.OP_RENAME_PARTITION, tableInfo); } - public void logBackupStart(BackupJob backupJob) { + public void logBackupStart(BackupJob_D backupJob) { logEdit(OperationType.OP_BACKUP_START, backupJob); } - public void logBackupFinishSnapshot(BackupJob backupJob) { + public void logBackupFinishSnapshot(BackupJob_D backupJob) { logEdit(OperationType.OP_BACKUP_FINISH_SNAPSHOT, backupJob); } - public void logBackupFinish(BackupJob backupJob) { + public void logBackupFinish(BackupJob_D backupJob) { logEdit(OperationType.OP_BACKUP_FINISH, backupJob); } - public void logRestoreJobStart(RestoreJob restoreJob) { + public void logRestoreJobStart(RestoreJob_D restoreJob) { logEdit(OperationType.OP_RESTORE_START, restoreJob); } - public void logRestoreFinish(RestoreJob restoreJob) { + public void logRestoreFinish(RestoreJob_D restoreJob) { logEdit(OperationType.OP_RESTORE_FINISH, restoreJob); } @@ -891,4 +967,19 @@ public class EditLog { logEdit(OperationType.OP_UPDATE_CLUSTER_AND_BACKENDS, info); } + public void logBackupJob(BackupJob job) { + logEdit(OperationType.OP_BACKUP_JOB, job); + } + + public void logCreateRepository(Repository repo) { + logEdit(OperationType.OP_CREATE_REPOSITORY, repo); + } + + public void logDropRepository(String repoName) { + logEdit(OperationType.OP_DROP_REPOSITORY, new Text(repoName)); + } + + public void logRestoreJob(RestoreJob job) { + logEdit(OperationType.OP_RESTORE_JOB, job); + } } diff --git a/fe/src/com/baidu/palo/persist/OperationType.java b/fe/src/com/baidu/palo/persist/OperationType.java index 3b04bdb5c4..3268a57921 100644 --- a/fe/src/com/baidu/palo/persist/OperationType.java +++ b/fe/src/com/baidu/palo/persist/OperationType.java @@ -38,11 +38,18 @@ public class OperationType { public static final short OP_RECOVER_PARTITION = 18; public static final short OP_RENAME_TABLE = 19; public static final short OP_RENAME_PARTITION = 110; + @Deprecated public static final short OP_BACKUP_START = 111; + @Deprecated public static final short OP_BACKUP_FINISH_SNAPSHOT = 112; + @Deprecated public static final short OP_BACKUP_FINISH = 113; + @Deprecated public static final short OP_RESTORE_START = 114; + @Deprecated public static final short OP_RESTORE_FINISH = 115; + public static final short OP_BACKUP_JOB = 116; + public static final short OP_RESTORE_JOB = 117; // 20~29 120~129 220~229 ... public static final short OP_START_ROLLUP = 20; @@ -87,7 +94,15 @@ public class OperationType { public static final short OP_SET_LOAD_ERROR_URL = 58; public static final short OP_ALTER_ACCESS_RESOURCE = 60; + @Deprecated public static final short OP_DROP_USER = 61; + public static final short OP_CREATE_USER = 62; + public static final short OP_NEW_DROP_USER = 63; + public static final short OP_GRANT_PRIV = 64; + public static final short OP_REVOKE_PRIV = 65; + public static final short OP_SET_PASSWORD = 66; + public static final short OP_CREATE_ROLE = 67; + public static final short OP_DROP_ROLE = 68; public static final short OP_TIMESTAMP = 70; public static final short OP_MASTER_INFO_CHANGE = 71; @@ -112,4 +127,7 @@ public class OperationType { public static final short OP_DROP_ALL_BROKER = 87; public static final short OP_UPDATE_CLUSTER_AND_BACKENDS = 88; + + public static final short OP_CREATE_REPOSITORY = 89; + public static final short OP_DROP_REPOSITORY = 90; } diff --git a/fe/src/com/baidu/palo/persist/PrivInfo.java b/fe/src/com/baidu/palo/persist/PrivInfo.java new file mode 100644 index 0000000000..6e0a73ee29 --- /dev/null +++ b/fe/src/com/baidu/palo/persist/PrivInfo.java @@ -0,0 +1,141 @@ +// Copyright (c) 2018, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +package com.baidu.palo.persist; + +import com.baidu.palo.analysis.TablePattern; +import com.baidu.palo.analysis.UserIdentity; +import com.baidu.palo.common.io.Text; +import com.baidu.palo.common.io.Writable; +import com.baidu.palo.mysql.privilege.PrivBitSet; + +import com.google.common.base.Strings; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +public class PrivInfo implements Writable { + private UserIdentity userIdent; + private TablePattern tblPattern; + private PrivBitSet privs; + private byte[] passwd; + private String role; + + private PrivInfo() { + + } + + public PrivInfo(UserIdentity userIdent, TablePattern tablePattern, PrivBitSet privs, + byte[] passwd, String role) { + this.userIdent = userIdent; + this.tblPattern = tablePattern; + this.privs = privs; + this.passwd = passwd; + this.role = role; + } + + public UserIdentity getUserIdent() { + return userIdent; + } + + public TablePattern getTblPattern() { + return tblPattern; + } + + public PrivBitSet getPrivs() { + return privs; + } + + public byte[] getPasswd() { + return passwd; + } + + public String getRole() { + return role; + } + + public static PrivInfo read(DataInput in) throws IOException { + PrivInfo info = new PrivInfo(); + info.readFields(in); + return info; + } + + @Override + public void write(DataOutput out) throws IOException { + if (userIdent != null) { + out.writeBoolean(true); + userIdent.write(out); + } else { + out.writeBoolean(false); + } + + if (tblPattern != null) { + out.writeBoolean(true); + tblPattern.write(out); + } else { + out.writeBoolean(false); + } + + if (privs != null) { + out.writeBoolean(true); + privs.write(out); + } else { + out.writeBoolean(false); + } + + if (passwd != null) { + out.writeBoolean(true); + out.writeInt(passwd.length); + out.write(passwd); + } else { + out.writeBoolean(false); + } + + if (!Strings.isNullOrEmpty(role)) { + out.writeBoolean(true); + Text.writeString(out, role); + } else { + out.writeBoolean(false); + } + } + + @Override + public void readFields(DataInput in) throws IOException { + if (in.readBoolean()) { + userIdent = UserIdentity.read(in); + } + + if (in.readBoolean()) { + tblPattern = TablePattern.read(in); + } + + if (in.readBoolean()) { + privs = PrivBitSet.read(in); + } + + if (in.readBoolean()) { + int passwordLen = in.readInt(); + passwd = new byte[passwordLen]; + in.readFully(passwd); + } + + if (in.readBoolean()) { + role = Text.readString(in); + } + + } + +} diff --git a/fe/src/com/baidu/palo/planner/BrokerScanNode.java b/fe/src/com/baidu/palo/planner/BrokerScanNode.java index 9d4ad5e2d8..08225756c5 100644 --- a/fe/src/com/baidu/palo/planner/BrokerScanNode.java +++ b/fe/src/com/baidu/palo/planner/BrokerScanNode.java @@ -439,6 +439,9 @@ public class BrokerScanNode extends ScanNode { candidateBes.add(backends.get(nextBe++)); nextBe = nextBe % backends.size(); } + // we shuffle it because if we only has 3 backends + // we will always choose the same backends without shuffle + Collections.shuffle(candidateBes); // Generate on broker scan range TBrokerScanRange brokerScanRange = new TBrokerScanRange(); @@ -480,7 +483,7 @@ public class BrokerScanNode extends ScanNode { private void getFileStatusAndCalcInstance() throws InternalException { if (fileStatusesList == null || filesAdded == -1) { // FIXME(cmy): fileStatusesList and filesAdded can be set out of db lock when doing pull load, - // but for now it is very difficult set them out of db lock when doing broker query. + // but for now it is very difficult to set them out of db lock when doing broker query. // So we leave this code block here. // This will be fixed later. fileStatusesList = Lists.newArrayList(); diff --git a/fe/src/com/baidu/palo/planner/OlapScanNode.java b/fe/src/com/baidu/palo/planner/OlapScanNode.java index 5f39470d11..582643d408 100644 --- a/fe/src/com/baidu/palo/planner/OlapScanNode.java +++ b/fe/src/com/baidu/palo/planner/OlapScanNode.java @@ -78,6 +78,7 @@ public class OlapScanNode extends ScanNode { private List result = new ArrayList(); private boolean isPreAggregation = false; + private String reasonOfPreAggregation = null; private boolean canTurnOnPreAggr = true; private ArrayList tupleColumns = new ArrayList(); private HashSet predicateColumns = new HashSet(); @@ -99,8 +100,9 @@ public class OlapScanNode extends ScanNode { olapTable = (OlapTable) desc.getTable(); } - public void setIsPreAggregation(boolean isPreAggregation) { + public void setIsPreAggregation(boolean isPreAggregation, String reason) { this.isPreAggregation = isPreAggregation; + this.reasonOfPreAggregation = reason; } @@ -555,7 +557,7 @@ public class OlapScanNode extends ScanNode { if (isPreAggregation) { output.append(prefix).append("PREAGGREGATION: ON").append("\n"); } else { - output.append(prefix).append("PREAGGREGATION: OFF").append("\n"); + output.append(prefix).append("PREAGGREGATION: OFF. Reason: ").append(reasonOfPreAggregation).append("\n"); } if (!conjuncts.isEmpty()) { output.append(prefix).append("PREDICATES: ").append( diff --git a/fe/src/com/baidu/palo/planner/SchemaScanNode.java b/fe/src/com/baidu/palo/planner/SchemaScanNode.java index e001953338..9f5ac7c3e3 100644 --- a/fe/src/com/baidu/palo/planner/SchemaScanNode.java +++ b/fe/src/com/baidu/palo/planner/SchemaScanNode.java @@ -31,13 +31,13 @@ import com.baidu.palo.thrift.TPlanNode; import com.baidu.palo.thrift.TPlanNodeType; import com.baidu.palo.thrift.TScanRangeLocations; import com.baidu.palo.thrift.TSchemaScanNode; + import com.google.common.base.Objects; import com.google.common.base.Objects.ToStringHelper; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.LogManager; -import java.net.InetAddress; -import java.net.UnknownHostException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + import java.util.List; /** @@ -47,12 +47,13 @@ public class SchemaScanNode extends ScanNode { private static final Logger LOG = LogManager.getLogger(SchemaTable.class); private final String tableName; - private String schemaDb; - private String schemaTable; - private String schemaWild; - private String user; - private String frontendIP; - private int frontendPort; + private String schemaDb; + private String schemaTable; + private String schemaWild; + private String user; + private String userIp; + private String frontendIP; + private int frontendPort; /** * Constructs node to scan given data files of table 'tbl'. @@ -74,7 +75,8 @@ public class SchemaScanNode extends ScanNode { schemaDb = analyzer.getSchemaDb(); schemaTable = analyzer.getSchemaTable(); schemaWild = analyzer.getSchemaWild(); - user = analyzer.getUser(); + user = analyzer.getQualifiedUser(); + userIp = analyzer.getContext().getRemoteIP(); frontendIP = FrontendOptions.getLocalHostAddress(); frontendPort = Config.rpc_port; } @@ -108,10 +110,11 @@ public class SchemaScanNode extends ScanNode { } msg.schema_scan_node.setIp(frontendIP); msg.schema_scan_node.setPort(frontendPort); + msg.schema_scan_node.setUser_ip(userIp); } /** - * We query MySQL Meta to get request's data localtion + * We query MySQL Meta to get request's data location * extra result info will pass to backend ScanNode */ @Override diff --git a/fe/src/com/baidu/palo/planner/SingleNodePlanner.java b/fe/src/com/baidu/palo/planner/SingleNodePlanner.java index f0fb8bac0b..f3d4c2c92a 100644 --- a/fe/src/com/baidu/palo/planner/SingleNodePlanner.java +++ b/fe/src/com/baidu/palo/planner/SingleNodePlanner.java @@ -343,15 +343,15 @@ public class SingleNodePlanner { } private void turnOffPreAgg(AggregateInfo aggInfo, SelectStmt selectStmt, Analyzer analyzer, PlanNode root) { + String turnOffReason = null; do { - String logStr = "turn off preAggregate because: "; if (null == aggInfo) { - LOG.info(logStr + "No AggregateInfo"); + turnOffReason = "No AggregateInfo"; break; } if (!(root instanceof OlapScanNode)) { - LOG.info(logStr + "left-deep Node is not OlapScanNode"); + turnOffReason = "left-deep Node is not OlapScanNode"; break; } @@ -363,8 +363,8 @@ public class SingleNodePlanner { final JoinOperator joinOperator = selectStmt.getTableRefs().get(i).getJoinOp(); // TODO chenhao16 , right out join ? if (joinOperator.isRightOuterJoin() || joinOperator.isFullOuterJoin()) { - LOG.info(logStr + selectStmt.getTableRefs().get(i) - + " joinOp is full outer join or right outer join."); + turnOffReason = selectStmt.getTableRefs().get(i) + + " joinOp is full outer join or right outer join."; aggTableValidate = false; break; } @@ -391,12 +391,9 @@ public class SingleNodePlanner { if (analyzer.getTupleDesc(tupleId).getRef() != olapTableRef) { if (analyzer.getTupleDesc(tupleId).getTable() != null - && analyzer.getTupleDesc(tupleId).getTable().getType() - == Table.TableType.OLAP) { - LOG.info("{} agg expr [{}] is not bound [{}]", - logStr, - aggExpr.debugString(), - selectStmt.getTableRefs().get(0).toSql()); + && analyzer.getTupleDesc(tupleId).getTable().getType() == Table.TableType.OLAP) { + turnOffReason = "agg expr [" + aggExpr.debugString() + "] is not bound [" + + selectStmt.getTableRefs().get(0).toSql() + "]"; aggTableValidate = false; } else { LOG.debug("The table which agg expr [{}] is bound to, is not OLAP table [{}]", @@ -416,8 +413,7 @@ public class SingleNodePlanner { } boolean valueColumnValidate = true; - List allConjuncts = - analyzer.getAllConjunt(selectStmt.getTableRefs().get(0).getId()); + List allConjuncts = analyzer.getAllConjunt(selectStmt.getTableRefs().get(0).getId()); List conjunctSlotIds = Lists.newArrayList(); if (allConjuncts != null) { for (Expr conjunct : allConjuncts) { @@ -426,8 +422,8 @@ public class SingleNodePlanner { for (SlotDescriptor slot : selectStmt.getTableRefs().get(0).getDesc().getSlots()) { if (!slot.getColumn().isKey()) { if (conjunctSlotIds.contains(slot.getId())) { - LOG.info(logStr + "conjunct on " + slot.getColumn().getName() + " which is " - + "OlapEngine value column"); + turnOffReason = "conjunct on " + slot.getColumn().getName() + + " which is OlapEngine value column"; valueColumnValidate = false; break; } @@ -441,7 +437,7 @@ public class SingleNodePlanner { boolean aggExprValidate = true; for (FunctionCallExpr aggExpr : aggExprs) { if (aggExpr.getChildren().size() != 1) { - LOG.info(logStr + "aggExpr has more than one child"); + turnOffReason = "aggExpr has more than one child"; aggExprValidate = false; break; } @@ -455,8 +451,7 @@ public class SingleNodePlanner { && child.getChild(0).getType().isNumericType()) { returnColumns.add(((SlotRef) child.getChild(0)).getDesc().getColumn()); } else { - LOG.info("{} aggExpr.getChild(0)[{}] is not Numeric CastExpr", - logStr, aggExpr.getChild(0).toSql()); + turnOffReason = "aggExpr.getChild(0)[aggExpr.getChild(0).toSql()] is not Numeric CastExpr"; aggExprValidate = false; break; } @@ -481,8 +476,8 @@ public class SingleNodePlanner { if (returnExpr instanceof SlotRef) { returnColumns.add(((SlotRef) returnExpr).getDesc().getColumn()); } else { - LOG.info("{} aggExpr.getChild(0)[{}] is not SlotExpr", - logStr, aggExpr.getChild(0).toSql()); + turnOffReason = "aggExpr.getChild(0)[" + aggExpr.getChild(0).toSql() + + "] is not SlotExpr"; caseReturnExprValidate = false; break; } @@ -494,9 +489,8 @@ public class SingleNodePlanner { } } else { - LOG.info("{} aggExpr.getChild(0)[{}] is not SlotRef or CastExpr|CaseExpr", - logStr, - aggExpr.getChild(0).debugString()); + turnOffReason = "aggExpr.getChild(0)[" + aggExpr.getChild(0).debugString() + + "] is not SlotRef or CastExpr|CaseExpr"; aggExprValidate = false; break; } @@ -513,10 +507,8 @@ public class SingleNodePlanner { continue; } if (!col.isKey()) { - LOG.info("{} the condition column [{}] is not key type in aggr expr [{}].", - logStr, - col.getName(), - aggExpr.toSql()); + turnOffReason = "the condition column [" + col.getName() + "] is not key type in aggr expr [" + + aggExpr.toSql() + "]."; conditionColumnValidate = false; break; } @@ -538,46 +530,41 @@ public class SingleNodePlanner { if (aggExpr.getFnName().getFunction().equalsIgnoreCase("MAX") && aggExpr.getFnName().getFunction().equalsIgnoreCase("MIN")) { returnColumnValidate = false; - LOG.info("{} the type of agg on OlapEngine's Key column should only be MAX or MIN. " - + "agg expr: {}", - logStr, - aggExpr.toSql()); + turnOffReason = "the type of agg on OlapEngine's Key column should only be MAX or MIN." + + "agg expr: " + aggExpr.toSql(); break; } } if (aggExpr.getFnName().getFunction().equalsIgnoreCase("SUM")) { if (col.getAggregationType() != AggregateType.SUM) { - LOG.info( - logStr + "Aggregate Operator not match: SUM <--> " + col - .getAggregationType()); + turnOffReason = "Aggregate Operator not match: SUM <--> " + col.getAggregationType(); returnColumnValidate = false; break; } } else if (aggExpr.getFnName().getFunction().equalsIgnoreCase("MAX")) { if ((!col.isKey()) && col.getAggregationType() != AggregateType.MAX) { - LOG.info( - logStr + "Aggregate Operator not match: MAX <--> " + col - .getAggregationType()); + turnOffReason = "Aggregate Operator not match: MAX <--> " + col.getAggregationType(); returnColumnValidate = false; break; } } else if (aggExpr.getFnName().getFunction().equalsIgnoreCase("MIN")) { if ((!col.isKey()) && col.getAggregationType() != AggregateType.MIN) { - LOG.info( - logStr + "Aggregate Operator not match: MIN <--> " + col - .getAggregationType()); + turnOffReason = "Aggregate Operator not match: MIN <--> " + col.getAggregationType(); returnColumnValidate = false; break; } } else if (aggExpr.getFnName().getFunction().equalsIgnoreCase("HLL_UNION_AGG")) { } else if (aggExpr.getFnName().getFunction().equalsIgnoreCase("NDV")) { if ((!col.isKey())) { + turnOffReason = "NDV function with non-key column: " + col.getName(); returnColumnValidate = false; break; } + } else if (aggExpr.getFnName().getFunction().equalsIgnoreCase("multi_distinct_count")) { + // count(distinct k1), count(distinct k2) / count(distinct k1,k2) can turn on pre aggregation } else { - LOG.info(logStr + "Invalid Aggregate Operator: " + aggExpr.getFnName().getFunction()); + turnOffReason = "Invalid Aggregate Operator: " + aggExpr.getFnName().getFunction(); returnColumnValidate = false; break; } @@ -601,7 +588,7 @@ public class SingleNodePlanner { for (SlotDescriptor slot : selectStmt.getTableRefs().get(0).getDesc().getSlots()) { if (!slot.getColumn().isKey()) { if (groupSlotIds.contains(slot.getId())) { - LOG.info(logStr + "groupExpr contains OlapEngine's Value"); + turnOffReason = "groupExpr contains OlapEngine's Value"; groupExprValidate = false; break; } @@ -617,13 +604,18 @@ public class SingleNodePlanner { } OlapScanNode olapNode = (OlapScanNode) root; - if (olapNode.getCanTurnOnPreAggr()) { - ((OlapScanNode) root).setIsPreAggregation(true); - } else { - LOG.info("this olap-scan-node[{}] has already been turned off pre-aggregation. ", - olapNode.debugString()); + if (!olapNode.getCanTurnOnPreAggr()) { + turnOffReason = "this olap scan node[" + olapNode.debugString() + + "] has already been turned off pre-aggregation."; + break; } + + olapNode.setIsPreAggregation(true, null); } while (false); + + if ((root instanceof OlapScanNode) && turnOffReason != null) { + ((OlapScanNode) root).setIsPreAggregation(false, turnOffReason); + } } /** @@ -1233,7 +1225,7 @@ public class SingleNodePlanner { */ private PlanNode createJoinNode(Analyzer analyzer, PlanNode outer, TableRef outerRef, TableRef innerRef) throws InternalException, AnalysisException { - materializeTableResultForCrossJoinOrCountStar(innerRef, analyzer); + materializeTableResultForCrossJoinOrCountStar(innerRef, analyzer); // the rows coming from the build node only need to have space for the tuple // materialized by that node PlanNode inner = createTableRefNode(analyzer, innerRef); @@ -1371,6 +1363,11 @@ public class SingleNodePlanner { // List conjuncts = // analyzer.getUnassignedConjuncts(unionStmt.getTupleId().asList(), false); List conjuncts = analyzer.getUnassignedConjuncts(unionStmt.getTupleId().asList()); + // TODO chenhao16 + // Because Conjuncts can't be assigned to UnionNode and Palo's fe can't evaluate conjuncts, + // it needs to add SelectNode as UnionNode's parent, when UnionStmt's Ops contains constant + // Select. + boolean hasConstantOp = false; if (!unionStmt.hasAnalyticExprs()) { // Turn unassigned predicates for unionStmt's tupleId_ into predicates for // the individual operands. @@ -1379,17 +1376,32 @@ public class SingleNodePlanner { for (UnionStmt.UnionOperand op: unionStmt.getOperands()) { List opConjuncts = Expr.substituteList(conjuncts, op.getSmap(), analyzer, false); - if (op.getQueryStmt() instanceof SelectStmt) { - final SelectStmt select = (SelectStmt) op.getQueryStmt(); + boolean selectHasTableRef = true; + final QueryStmt queryStmt = op.getQueryStmt(); + // Check whether UnionOperand is constant Select. + if (queryStmt instanceof SelectStmt) { + final SelectStmt selectStmt = (SelectStmt) queryStmt; + if (selectStmt.getTableRefs().isEmpty()) { + selectHasTableRef = false; + hasConstantOp = !selectHasTableRef; + } + } + // Forbid to register Conjuncts with SelectStmt' tuple when Select is constant + if ((queryStmt instanceof SelectStmt) && selectHasTableRef) { + final SelectStmt select = (SelectStmt) queryStmt; op.getAnalyzer().registerConjuncts(opConjuncts, select.getTableRefIds()); - } else if (op.getQueryStmt() instanceof UnionStmt) { - final UnionStmt union = (UnionStmt) op.getQueryStmt(); + } else if (queryStmt instanceof UnionStmt) { + final UnionStmt union = (UnionStmt) queryStmt; op.getAnalyzer().registerConjuncts(opConjuncts, union.getTupleId().asList()); } else { - Preconditions.checkArgument(false); + if (selectHasTableRef) { + Preconditions.checkArgument(false); + } } } - analyzer.markConjunctsAssigned(conjuncts); + if (!hasConstantOp) { + analyzer.markConjunctsAssigned(conjuncts); + } } else { // mark slots referenced by the yet-unassigned conjuncts analyzer.materializeSlots(conjuncts); @@ -1410,7 +1422,7 @@ public class SingleNodePlanner { result = createUnionPlan(analyzer, unionStmt, unionStmt.getAllOperands(), result, defaultOrderByLimit); } - if (unionStmt.hasAnalyticExprs()) { + if (unionStmt.hasAnalyticExprs() || hasConstantOp) { result = addUnassignedConjuncts( analyzer, unionStmt.getTupleId().asList(), result); } @@ -1483,14 +1495,15 @@ public class SingleNodePlanner { exprSize += slot.getByteSize(); } - if (exprIsMaterialized && exprSize <= resultExprSelectedSize) { - resultExprSelectedSize = exprSize; - resultExprSelected = e; - } // Result Expr contains materialized expr, return if (exprIsMaterialized) { return; } + + if (exprSize <= resultExprSelectedSize) { + resultExprSelectedSize = exprSize; + resultExprSelected = e; + } } // materialize slots which expr refer and It's total size is smallest @@ -1508,3 +1521,4 @@ public class SingleNodePlanner { } } + diff --git a/fe/src/com/baidu/palo/qe/ConnectContext.java b/fe/src/com/baidu/palo/qe/ConnectContext.java index 9561b3f598..2bb05fde8c 100644 --- a/fe/src/com/baidu/palo/qe/ConnectContext.java +++ b/fe/src/com/baidu/palo/qe/ConnectContext.java @@ -15,9 +15,6 @@ package com.baidu.palo.qe; -import java.nio.channels.SocketChannel; -import java.util.List; - import com.baidu.palo.catalog.Catalog; import com.baidu.palo.cluster.ClusterNamespace; import com.baidu.palo.mysql.MysqlCapability; @@ -32,6 +29,9 @@ import com.google.common.collect.Lists; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import java.nio.channels.SocketChannel; +import java.util.List; + // When one client connect in, we create a connect context for it. // We store session information here. Meanwhile ConnectScheduler all // connect with its connection id. @@ -59,7 +59,7 @@ public class ConnectContext { // cluster name private volatile String clusterName = ""; // User - private volatile String user; + private volatile String qualifiedUser; // Serializer used to pack MySQL packet. private volatile MysqlSerializer serializer; // Variables belong to this session. @@ -82,6 +82,8 @@ public class ConnectContext { private AuditBuilder auditBuilder; + private String remoteIP; + public static ConnectContext get() { return threadLocalInfo.get(); } @@ -108,6 +110,17 @@ public class ConnectContext { sessionVariable = VariableMgr.newSessionVariable(); auditBuilder = new AuditBuilder(); command = MysqlCommand.COM_SLEEP; + if (channel != null) { + remoteIP = mysqlChannel.getRemoteIp(); + } + } + + public String getRemoteIP() { + return remoteIP; + } + + public void setRemoteIP(String remoteIP) { + this.remoteIP = remoteIP; } public AuditBuilder getAuditBuilder() { @@ -119,7 +132,7 @@ public class ConnectContext { } public TResourceInfo toResourceCtx() { - return new TResourceInfo(user, sessionVariable.getResourceGroup()); + return new TResourceInfo(qualifiedUser, sessionVariable.getResourceGroup()); } public void setCatalog(Catalog catalog) { @@ -130,12 +143,12 @@ public class ConnectContext { return catalog; } - public String getUser() { - return user; + public String getQualifiedUser() { + return qualifiedUser; } - public void setUser(String user) { - this.user = user; + public void setQualifiedUser(String qualifiedUser) { + this.qualifiedUser = qualifiedUser; } public SessionVariable getSessionVariable() { @@ -257,7 +270,7 @@ public class ConnectContext { } LOG.warn("kill timeout query, {}, kill connection: {}", - mysqlChannel.getRemoteHostString(), killConnection); + mysqlChannel.getRemoteHostPortString(), killConnection); if (killConnection) { isKilled = true; @@ -283,7 +296,7 @@ public class ConnectContext { if (delta > sessionVariable.getWaitTimeoutS() * 1000) { // Need kill this connection. LOG.warn("kill wait timeout connection, remote: {}, wait timeout: {}", - mysqlChannel.getRemoteHostString(), sessionVariable.getWaitTimeoutS()); + mysqlChannel.getRemoteHostPortString(), sessionVariable.getWaitTimeoutS()); killFlag = true; killConnection = true; @@ -291,7 +304,7 @@ public class ConnectContext { } else { if (delta > sessionVariable.getQueryTimeoutS() * 1000) { LOG.warn("kill query timeout, remote: {}, query timeout: {}", - mysqlChannel.getRemoteHostString(), sessionVariable.getQueryTimeoutS()); + mysqlChannel.getRemoteHostPortString(), sessionVariable.getQueryTimeoutS()); // Only kill killFlag = true; @@ -314,8 +327,8 @@ public class ConnectContext { public List toRow(long nowMs) { List row = Lists.newArrayList(); row.add("" + connectionId); - row.add(ClusterNamespace.getNameFromFullName(user)); - row.add(mysqlChannel.getRemoteHostString()); + row.add(ClusterNamespace.getNameFromFullName(qualifiedUser)); + row.add(mysqlChannel.getRemoteHostPortString()); row.add(clusterName); row.add(ClusterNamespace.getNameFromFullName(currentDb)); row.add(command.toString()); diff --git a/fe/src/com/baidu/palo/qe/ConnectProcessor.java b/fe/src/com/baidu/palo/qe/ConnectProcessor.java index 874455433a..9f9507edae 100644 --- a/fe/src/com/baidu/palo/qe/ConnectProcessor.java +++ b/fe/src/com/baidu/palo/qe/ConnectProcessor.java @@ -143,8 +143,8 @@ public class ConnectProcessor { } ctx.getAuditBuilder().reset(); // replace '\n' to '\\\n' to make string in one line - ctx.getAuditBuilder().put("client", ctx.getMysqlChannel().getRemoteHostString()); - ctx.getAuditBuilder().put("user", ctx.getUser()); + ctx.getAuditBuilder().put("client", ctx.getMysqlChannel().getRemoteHostPortString()); + ctx.getAuditBuilder().put("user", ctx.getQualifiedUser()); ctx.getAuditBuilder().put("db", ctx.getDatabase()); // execute this query. @@ -301,7 +301,7 @@ public class ConnectProcessor { public TMasterOpResult proxyExecute(TMasterOpRequest request) { ctx.setDatabase(request.db); - ctx.setUser(request.user); + ctx.setQualifiedUser(request.user); ctx.setCatalog(Catalog.getInstance()); ctx.getState().reset(); if (request.isSetCluster()) { @@ -316,6 +316,9 @@ public class ConnectProcessor { if (request.isSetQueryTimeout()) { ctx.getSessionVariable().setQueryTimeoutS(request.getQueryTimeout()); } + if (request.isSetUser_ip()) { + ctx.setRemoteIP(request.getUser_ip()); + } ctx.setThreadLocalInfo(); @@ -357,7 +360,7 @@ public class ConnectProcessor { try { packetBuf = channel.fetchOnePacket(); if (packetBuf == null) { - LOG.warn("Null packet received from network. remote: {}", channel.getRemoteHostString()); + LOG.warn("Null packet received from network. remote: {}", channel.getRemoteHostPortString()); throw new IOException("Error happened when receiving packet."); } } catch (AsynchronousCloseException e) { @@ -387,4 +390,3 @@ public class ConnectProcessor { } } } - diff --git a/fe/src/com/baidu/palo/qe/ConnectScheduler.java b/fe/src/com/baidu/palo/qe/ConnectScheduler.java index 9ce9648160..68574baf74 100644 --- a/fe/src/com/baidu/palo/qe/ConnectScheduler.java +++ b/fe/src/com/baidu/palo/qe/ConnectScheduler.java @@ -15,7 +15,9 @@ package com.baidu.palo.qe; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.mysql.MysqlProto; +import com.baidu.palo.mysql.privilege.PrivPredicate; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @@ -90,15 +92,15 @@ public class ConnectScheduler { return false; } // Check user - if (connByUser.get(ctx.getUser()) == null) { - connByUser.put(ctx.getUser(), new AtomicInteger(0)); + if (connByUser.get(ctx.getQualifiedUser()) == null) { + connByUser.put(ctx.getQualifiedUser(), new AtomicInteger(0)); } - int conns = connByUser.get(ctx.getUser()).get(); - if (conns >= ctx.getCatalog().getUserMgr().getMaxConn(ctx.getUser())) { + int conns = connByUser.get(ctx.getQualifiedUser()).get(); + if (conns >= ctx.getCatalog().getAuth().getMaxConn(ctx.getQualifiedUser())) { return false; } numberConnection++; - connByUser.get(ctx.getUser()).incrementAndGet(); + connByUser.get(ctx.getQualifiedUser()).incrementAndGet(); connectionMap.put((long) ctx.getConnectionId(), ctx); return true; } @@ -106,7 +108,7 @@ public class ConnectScheduler { public synchronized void unregisterConnection(ConnectContext ctx) { if (connectionMap.remove((long) ctx.getConnectionId()) != null) { numberConnection--; - AtomicInteger conns = connByUser.get(ctx.getUser()); + AtomicInteger conns = connByUser.get(ctx.getQualifiedUser()); if (conns != null) { conns.decrementAndGet(); } @@ -126,9 +128,12 @@ public class ConnectScheduler { for (ConnectContext ctx : connectionMap.values()) { // Check auth - if (!ctx.getCatalog().getUserMgr().checkUserAccess(user, ctx.getUser())) { + if (!ctx.getQualifiedUser().equals(user) && + !Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), + PrivPredicate.GRANT)) { continue; } + infos.add(ctx.toThreadInfo()); } return infos; diff --git a/fe/src/com/baidu/palo/qe/Coordinator.java b/fe/src/com/baidu/palo/qe/Coordinator.java index fd0b07ef99..04186c91e6 100644 --- a/fe/src/com/baidu/palo/qe/Coordinator.java +++ b/fe/src/com/baidu/palo/qe/Coordinator.java @@ -72,6 +72,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.thrift.TException; @@ -181,7 +182,7 @@ public class Coordinator { this.returnedAllResults = false; this.queryOptions = context.getSessionVariable().toThrift(); this.queryGlobals.setNow_string(DATE_FORMAT.format(new Date())); - this.tResourceInfo = new TResourceInfo(context.getUser(), + this.tResourceInfo = new TResourceInfo(context.getQualifiedUser(), context.getSessionVariable().getResourceGroup()); this.needReport = context.getSessionVariable().isReportSucc(); this.clusterName = context.getClusterName(); @@ -426,19 +427,19 @@ public class Coordinator { if (code != TStatusCode.OK) { if (errMsg == null) { - errMsg = "exec rpc error"; + errMsg = "exec rpc error. backend id: " + pair.first.systemBackendId; } queryStatus.setStatus(errMsg); LOG.warn("exec plan fragment failed, errmsg={}, fragmentId={}, backend={}:{}", - errMsg, fragment.getFragmentId(), - pair.first.address.hostname, pair.first.address.port); + errMsg, fragment.getFragmentId(), + pair.first.address.hostname, pair.first.address.port); cancelInternal(); switch (code) { case TIMEOUT: - throw new InternalException("query timeout"); + throw new InternalException("query timeout. backend id: " + pair.first.systemBackendId); case THRIFT_RPC_ERROR: SimpleScheduler.updateBlacklistBackends(pair.first.systemBackendId); - throw new RpcException("rpc failed"); + throw new RpcException("rpc failed. backend id: " + pair.first.systemBackendId); default: throw new InternalException(errMsg); } @@ -508,8 +509,8 @@ public class Coordinator { } void updateStatus(Status status) { + lock.lock(); try { - lock.lock(); // The query is done and we are just waiting for remote fragments to clean up. // Ignore their cancelled updates. if (returnedAllResults && status.isCancelled()) { @@ -531,7 +532,6 @@ public class Coordinator { } finally { lock.unlock(); } - } TResultBatch getNext() throws Exception { @@ -539,7 +539,7 @@ public class Coordinator { throw new InternalException("There is no receiver."); } - TResultBatch resultBatch; + TResultBatch resultBatch; Status status = new Status(); resultBatch = receiver.getNext(status); @@ -559,7 +559,6 @@ public class Coordinator { if (copyStatus.isRpcError()) { throw new RpcException(copyStatus.getErrorMsg()); } else { - String errMsg = copyStatus.getErrorMsg(); LOG.warn("query failed: {}", errMsg); diff --git a/fe/src/com/baidu/palo/qe/DdlExecutor.java b/fe/src/com/baidu/palo/qe/DdlExecutor.java index c9d01ada17..dbd1ec4512 100644 --- a/fe/src/com/baidu/palo/qe/DdlExecutor.java +++ b/fe/src/com/baidu/palo/qe/DdlExecutor.java @@ -20,7 +20,6 @@ import com.baidu.palo.analysis.AlterDatabaseQuotaStmt; import com.baidu.palo.analysis.AlterDatabaseRename; import com.baidu.palo.analysis.AlterSystemStmt; import com.baidu.palo.analysis.AlterTableStmt; -import com.baidu.palo.analysis.AlterUserStmt; import com.baidu.palo.analysis.BackupStmt; import com.baidu.palo.analysis.CancelAlterSystemStmt; import com.baidu.palo.analysis.CancelAlterTableStmt; @@ -28,6 +27,8 @@ import com.baidu.palo.analysis.CancelBackupStmt; import com.baidu.palo.analysis.CancelLoadStmt; import com.baidu.palo.analysis.CreateClusterStmt; import com.baidu.palo.analysis.CreateDbStmt; +import com.baidu.palo.analysis.CreateRepositoryStmt; +import com.baidu.palo.analysis.CreateRoleStmt; import com.baidu.palo.analysis.CreateTableStmt; import com.baidu.palo.analysis.CreateUserStmt; import com.baidu.palo.analysis.CreateViewStmt; @@ -35,6 +36,8 @@ import com.baidu.palo.analysis.DdlStmt; import com.baidu.palo.analysis.DeleteStmt; import com.baidu.palo.analysis.DropClusterStmt; import com.baidu.palo.analysis.DropDbStmt; +import com.baidu.palo.analysis.DropRepositoryStmt; +import com.baidu.palo.analysis.DropRoleStmt; import com.baidu.palo.analysis.DropTableStmt; import com.baidu.palo.analysis.DropUserStmt; import com.baidu.palo.analysis.GrantStmt; @@ -49,7 +52,6 @@ import com.baidu.palo.analysis.RevokeStmt; import com.baidu.palo.analysis.SetUserPropertyStmt; import com.baidu.palo.analysis.SyncStmt; import com.baidu.palo.catalog.Catalog; -import com.baidu.palo.cluster.ClusterNamespace; import com.baidu.palo.common.DdlException; import com.baidu.palo.load.LoadJob.EtlJobType; @@ -61,15 +63,10 @@ public class DdlExecutor { if (ddlStmt instanceof CreateClusterStmt) { CreateClusterStmt stmt = (CreateClusterStmt) ddlStmt; catalog.createCluster(stmt); - catalog.getUserMgr().addUser(stmt.getClusterName(), - ClusterNamespace.getFullName(stmt.getClusterName(), CreateClusterStmt.CLUSTER_SUPERUSER_NAME), - stmt.getPassword(), true); } else if (ddlStmt instanceof AlterClusterStmt) { catalog.processModifyCluster((AlterClusterStmt) ddlStmt); } else if (ddlStmt instanceof DropClusterStmt) { catalog.dropCluster((DropClusterStmt) ddlStmt); - catalog.getUserMgr().dropUser(ClusterNamespace.getFullName(((DropClusterStmt) ddlStmt).getName(), - CreateClusterStmt.CLUSTER_SUPERUSER_NAME)); } else if (ddlStmt instanceof MigrateDbStmt) { catalog.migrateDb((MigrateDbStmt) ddlStmt); } else if (ddlStmt instanceof LinkDbStmt) { @@ -101,23 +98,25 @@ public class DdlExecutor { catalog.getLoadInstance().delete((DeleteStmt) ddlStmt); } else if (ddlStmt instanceof CreateUserStmt) { CreateUserStmt stmt = (CreateUserStmt) ddlStmt; - catalog.getUserMgr().addUser(stmt.getClusterName(), stmt.getUser(), stmt.getPassword(), stmt.isSuperuser()); + catalog.getAuth().createUser(stmt); } else if (ddlStmt instanceof DropUserStmt) { DropUserStmt stmt = (DropUserStmt) ddlStmt; - catalog.getUserMgr().dropUser(stmt.getUser()); + catalog.getAuth().dropUser(stmt); } else if (ddlStmt instanceof GrantStmt) { GrantStmt stmt = (GrantStmt) ddlStmt; - catalog.getUserMgr().grant(stmt.getUser(), stmt.getDb(), stmt.getPrivilege()); + catalog.getAuth().grant(stmt); } else if (ddlStmt instanceof RevokeStmt) { RevokeStmt stmt = (RevokeStmt) ddlStmt; - catalog.getUserMgr().revoke(stmt.getUser(), stmt.getDb()); + catalog.getAuth().revoke(stmt); + } else if (ddlStmt instanceof CreateRoleStmt) { + catalog.getAuth().createRole((CreateRoleStmt) ddlStmt); + } else if (ddlStmt instanceof DropRoleStmt) { + catalog.getAuth().dropRole((DropRoleStmt) ddlStmt); } else if (ddlStmt instanceof SetUserPropertyStmt) { - catalog.getUserMgr().updateUserProperty((SetUserPropertyStmt) ddlStmt); + catalog.getAuth().updateUserProperty((SetUserPropertyStmt) ddlStmt); } else if (ddlStmt instanceof AlterSystemStmt) { AlterSystemStmt stmt = (AlterSystemStmt) ddlStmt; catalog.alterCluster(stmt); - } else if (ddlStmt instanceof AlterUserStmt) { - catalog.alterUser((AlterUserStmt) ddlStmt); } else if (ddlStmt instanceof CancelAlterSystemStmt) { CancelAlterSystemStmt stmt = (CancelAlterSystemStmt) ddlStmt; catalog.cancelAlterCluster(stmt); @@ -139,6 +138,10 @@ public class DdlExecutor { catalog.restore((RestoreStmt) ddlStmt); } else if (ddlStmt instanceof CancelBackupStmt) { catalog.cancelBackup((CancelBackupStmt) ddlStmt); + } else if (ddlStmt instanceof CreateRepositoryStmt) { + catalog.getBackupHandler().createRepository((CreateRepositoryStmt) ddlStmt); + } else if (ddlStmt instanceof DropRepositoryStmt) { + catalog.getBackupHandler().dropRepository((DropRepositoryStmt) ddlStmt); } else if (ddlStmt instanceof SyncStmt) { return; } else { diff --git a/fe/src/com/baidu/palo/qe/MasterOpExecutor.java b/fe/src/com/baidu/palo/qe/MasterOpExecutor.java index b28ec14dbf..7a5883e4bb 100644 --- a/fe/src/com/baidu/palo/qe/MasterOpExecutor.java +++ b/fe/src/com/baidu/palo/qe/MasterOpExecutor.java @@ -15,19 +15,19 @@ package com.baidu.palo.qe; +import com.baidu.palo.analysis.RedirectStatus; +import com.baidu.palo.common.ClientPool; +import com.baidu.palo.thrift.FrontendService; +import com.baidu.palo.thrift.TMasterOpRequest; +import com.baidu.palo.thrift.TMasterOpResult; +import com.baidu.palo.thrift.TNetworkAddress; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.thrift.transport.TTransportException; + import java.nio.ByteBuffer; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.thrift.transport.TTransportException; - -import com.baidu.palo.common.ClientPool; -import com.baidu.palo.analysis.RedirectStatus; -import com.baidu.palo.thrift.FrontendService; -import com.baidu.palo.thrift.TMasterOpRequest; -import com.baidu.palo.thrift.TMasterOpResult; -import com.baidu.palo.thrift.TNetworkAddress; - public class MasterOpExecutor { private static final Logger LOG = LogManager.getLogger(MasterOpExecutor.class); @@ -72,11 +72,12 @@ public class MasterOpExecutor { TMasterOpRequest params = new TMasterOpRequest(); params.setCluster(ctx.getClusterName()); params.setSql(originStmt); - params.setUser(ctx.getUser()); + params.setUser(ctx.getQualifiedUser()); params.setDb(ctx.getDatabase()); params.setResourceInfo(ctx.toResourceCtx()); params.setExecMemLimit(ctx.getSessionVariable().getMaxExecMemByte()); params.setQueryTimeout(ctx.getSessionVariable().getQueryTimeoutS()); + params.setUser_ip(ctx.getRemoteIP()); LOG.info("Forward statement {} to Master {}", originStmt, thriftAddress); diff --git a/fe/src/com/baidu/palo/qe/QeProcessor.java b/fe/src/com/baidu/palo/qe/QeProcessor.java index 7a1adcdbe2..85ca768b2f 100644 --- a/fe/src/com/baidu/palo/qe/QeProcessor.java +++ b/fe/src/com/baidu/palo/qe/QeProcessor.java @@ -69,7 +69,8 @@ public class QeProcessor { } public static synchronized void unregisterQuery(TUniqueId queryId) { - LOG.info("deregister query id = " + queryId.toString()); - coordinatorMap.remove(queryId); + if (coordinatorMap.remove(queryId) != null) { + LOG.info("deregister query id:" + queryId.toString()); + } } } diff --git a/fe/src/com/baidu/palo/qe/ResultReceiver.java b/fe/src/com/baidu/palo/qe/ResultReceiver.java index 640173997c..e2a148f3e9 100644 --- a/fe/src/com/baidu/palo/qe/ResultReceiver.java +++ b/fe/src/com/baidu/palo/qe/ResultReceiver.java @@ -111,8 +111,13 @@ public class ResultReceiver { SimpleScheduler.updateBlacklistBackends(backendId); } catch (ExecutionException e) { LOG.warn("fetch result execution exception, finstId={}", finstId, e); - status.setRpcStatus(e.getMessage()); - SimpleScheduler.updateBlacklistBackends(backendId); + if (e.getMessage().contains("time out")) { + // if timeout, we set error code to TIMEOUT, and it will not retry querying. + status.setStatus(new Status(TStatusCode.TIMEOUT, e.getMessage())); + } else { + status.setRpcStatus(e.getMessage()); + SimpleScheduler.updateBlacklistBackends(backendId); + } } catch (TimeoutException e) { LOG.warn("fetch result timeout, finstId={}", finstId, e); status.setStatus("query timeout"); @@ -132,9 +137,12 @@ public class ResultReceiver { isCancel = true; synchronized (this) { if (currentThread != null) { - currentThread.interrupt(); + // TODO(cmy): we cannot interrupt this thread, or we may throw + // java.nio.channels.ClosedByInterruptException when we call + // MysqlChannel.realNetSend -> SocketChannelImpl.write + // And user will lost connection to Palo + // currentThread.interrupt(); } - } } } diff --git a/fe/src/com/baidu/palo/qe/SetExecutor.java b/fe/src/com/baidu/palo/qe/SetExecutor.java index 12a24c5b25..c18edd8a7d 100644 --- a/fe/src/com/baidu/palo/qe/SetExecutor.java +++ b/fe/src/com/baidu/palo/qe/SetExecutor.java @@ -22,8 +22,8 @@ import com.baidu.palo.analysis.SetTransaction; import com.baidu.palo.analysis.SetVar; import com.baidu.palo.common.DdlException; -import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; // Set executor public class SetExecutor { @@ -41,7 +41,7 @@ public class SetExecutor { if (var instanceof SetPassVar) { // Set password SetPassVar setPassVar = (SetPassVar) var; - ctx.getCatalog().getUserMgr().setPasswd(setPassVar.getUser(), setPassVar.getPassword()); + ctx.getCatalog().getAuth().setPassword(setPassVar); } else if (var instanceof SetNamesVar) { // do nothing return; diff --git a/fe/src/com/baidu/palo/qe/ShowExecutor.java b/fe/src/com/baidu/palo/qe/ShowExecutor.java index 64b8a77382..81d536adbe 100644 --- a/fe/src/com/baidu/palo/qe/ShowExecutor.java +++ b/fe/src/com/baidu/palo/qe/ShowExecutor.java @@ -32,23 +32,29 @@ import com.baidu.palo.analysis.ShowDbStmt; import com.baidu.palo.analysis.ShowDeleteStmt; import com.baidu.palo.analysis.ShowEnginesStmt; import com.baidu.palo.analysis.ShowExportStmt; +import com.baidu.palo.analysis.ShowFrontendsStmt; +import com.baidu.palo.analysis.ShowGrantsStmt; import com.baidu.palo.analysis.ShowLoadStmt; import com.baidu.palo.analysis.ShowLoadWarningsStmt; import com.baidu.palo.analysis.ShowMigrationsStmt; import com.baidu.palo.analysis.ShowPartitionsStmt; import com.baidu.palo.analysis.ShowProcStmt; import com.baidu.palo.analysis.ShowProcesslistStmt; +import com.baidu.palo.analysis.ShowRepositoriesStmt; import com.baidu.palo.analysis.ShowRestoreStmt; +import com.baidu.palo.analysis.ShowRolesStmt; import com.baidu.palo.analysis.ShowRollupStmt; +import com.baidu.palo.analysis.ShowSnapshotStmt; import com.baidu.palo.analysis.ShowStmt; import com.baidu.palo.analysis.ShowTableStatusStmt; import com.baidu.palo.analysis.ShowTableStmt; import com.baidu.palo.analysis.ShowTabletStmt; import com.baidu.palo.analysis.ShowUserPropertyStmt; -import com.baidu.palo.analysis.ShowUserStmt; import com.baidu.palo.analysis.ShowVariablesStmt; -import com.baidu.palo.analysis.ShowWhiteListStmt; -import com.baidu.palo.catalog.AccessPrivilege; +import com.baidu.palo.backup.AbstractJob; +import com.baidu.palo.backup.BackupJob; +import com.baidu.palo.backup.Repository; +import com.baidu.palo.backup.RestoreJob; import com.baidu.palo.catalog.Catalog; import com.baidu.palo.catalog.Column; import com.baidu.palo.catalog.Database; @@ -58,15 +64,16 @@ import com.baidu.palo.catalog.Partition; import com.baidu.palo.catalog.Table; import com.baidu.palo.catalog.Tablet; import com.baidu.palo.catalog.TabletInvertedIndex; -import com.baidu.palo.catalog.UserPropertyMgr; import com.baidu.palo.catalog.View; import com.baidu.palo.cluster.BaseParam; import com.baidu.palo.cluster.ClusterNamespace; import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.CaseSensibility; import com.baidu.palo.common.ErrorCode; import com.baidu.palo.common.ErrorReport; import com.baidu.palo.common.PatternMatcher; import com.baidu.palo.common.proc.BackendsProcDir; +import com.baidu.palo.common.proc.FrontendsProcNode; import com.baidu.palo.common.proc.LoadProcDir; import com.baidu.palo.common.proc.PartitionsProcDir; import com.baidu.palo.common.proc.ProcNodeInterface; @@ -78,6 +85,7 @@ import com.baidu.palo.load.LoadErrorHub; import com.baidu.palo.load.LoadErrorHub.HubType; import com.baidu.palo.load.LoadJob; import com.baidu.palo.load.LoadJob.JobState; +import com.baidu.palo.mysql.privilege.PrivPredicate; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; @@ -159,8 +167,6 @@ public class ShowExecutor { handleShowBackup(); } else if (stmt instanceof ShowRestoreStmt) { handleShowRestore(); - } else if (stmt instanceof ShowWhiteListStmt) { - handleShowWhiteList(); } else if (stmt instanceof ShowClusterStmt) { handleShowCluster(); } else if (stmt instanceof ShowMigrationsStmt) { @@ -171,8 +177,16 @@ public class ShowExecutor { handleShowExport(); } else if (stmt instanceof ShowBackendsStmt) { handleShowBackends(); - } else if (stmt instanceof ShowUserStmt) { - handleShowUser(); + } else if (stmt instanceof ShowFrontendsStmt) { + handleShowFrontends(); + } else if (stmt instanceof ShowRepositoriesStmt) { + handleShowRepositories(); + } else if (stmt instanceof ShowSnapshotStmt) { + handleShowSnapshot(); + } else if (stmt instanceof ShowGrantsStmt) { + handleShowGrants(); + } else if (stmt instanceof ShowRolesStmt) { + handleShowRoles(); } else { handleEmtpy(); } @@ -180,12 +194,6 @@ public class ShowExecutor { return resultSet; } - private void handleShowWhiteList() { - ShowWhiteListStmt showWhiteStmt = (ShowWhiteListStmt) stmt; - List> rowSet = ctx.getCatalog().showWhiteList(ctx.getUser()); - resultSet = new ShowResultSet(showWhiteStmt.getMetaData(), rowSet); - } - private void handleShowRollup() { // TODO: not implemented yet ShowRollupStmt showRollupStmt = (ShowRollupStmt) stmt; @@ -198,7 +206,7 @@ public class ShowExecutor { ShowProcesslistStmt showStmt = (ShowProcesslistStmt) stmt; List> rowSet = Lists.newArrayList(); - List threadInfos = ctx.getConnectScheduler().listConnection(ctx.getUser()); + List threadInfos = ctx.getConnectScheduler().listConnection(ctx.getQualifiedUser()); long nowMs = System.currentTimeMillis(); for (ConnectContext.ThreadInfo info : threadInfos) { rowSet.add(info.toRow(nowMs)); @@ -240,19 +248,15 @@ public class ShowExecutor { List> finalRows = procNode.fetchResult().getRows(); // if this is superuser, hide ip and host info form backends info proc if (procNode instanceof BackendsProcDir) { - if (ctx.getCatalog().getUserMgr().isSuperuser(ctx.getUser()) - && !ctx.getCatalog().getUserMgr().isAdmin(ctx.getUser())) { - // hide ip and host info + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), + PrivPredicate.OPERATOR)) { + // hide host info for (List row : finalRows) { - row.remove(BackendsProcDir.IP_INDEX); - // remove twice cause posistion shift to left after removing - row.remove(BackendsProcDir.IP_INDEX); + row.remove(BackendsProcDir.HOSTNAME_INDEX); } // mod meta data - metaData.removeColumn(BackendsProcDir.IP_INDEX); - // remove twice cause posistion shift to left after removing - metaData.removeColumn(BackendsProcDir.IP_INDEX); + metaData.removeColumn(BackendsProcDir.HOSTNAME_INDEX); } } @@ -265,10 +269,6 @@ public class ShowExecutor { final List> rows = Lists.newArrayList(); final List clusterNames = ctx.getCatalog().getClusterNames(); - if (!ctx.getCatalog().getUserMgr().isAdmin(ctx.getUser())) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_CLUSTER_SHOW_ACCESS_DENIED); - } - final Set clusterNameSet = Sets.newTreeSet(); for (String cluster : clusterNames) { clusterNameSet.add(cluster); @@ -287,10 +287,6 @@ public class ShowExecutor { final List> rows = Lists.newArrayList(); final Set infos = ctx.getCatalog().getMigrations(); - if (!ctx.getCatalog().getUserMgr().isAdmin(ctx.getUser())) { - ErrorReport.reportAnalysisException(ErrorCode.ERR_CLUSTER_SHOW_ACCESS_DENIED); - } - for (BaseParam param : infos) { final int percent = (int) (param.getFloatParam(0) * 100f); rows.add(Lists.newArrayList(param.getStringParam(0), param.getStringParam(1), param.getStringParam(2), @@ -307,9 +303,9 @@ public class ShowExecutor { List dbNames = ctx.getCatalog().getClusterDbNames(ctx.getClusterName()); PatternMatcher matcher = null; if (showDbStmt.getPattern() != null) { - matcher = PatternMatcher.createMysqlPattern(showDbStmt.getPattern()); + matcher = PatternMatcher.createMysqlPattern(showDbStmt.getPattern(), + CaseSensibility.DATABASE.getCaseSensibility()); } - UserPropertyMgr userPropertyMgr = ctx.getCatalog().getUserMgr(); Set dbNameSet = Sets.newTreeSet(); for (String fullName : dbNames) { final String db = ClusterNamespace.getNameFromFullName(fullName); @@ -317,9 +313,13 @@ public class ShowExecutor { if (matcher != null && !matcher.match(db)) { continue; } - if (userPropertyMgr.checkAccess(ctx.getUser(), fullName, AccessPrivilege.READ_ONLY)) { - dbNameSet.add(db); + + if (!Catalog.getCurrentCatalog().getAuth().checkDbPriv(ConnectContext.get(), fullName, + PrivPredicate.SHOW)) { + continue; } + + dbNameSet.add(db); } for (String dbName : dbNameSet) { @@ -340,12 +340,19 @@ public class ShowExecutor { try { PatternMatcher matcher = null; if (showTableStmt.getPattern() != null) { - matcher = PatternMatcher.createMysqlPattern(showTableStmt.getPattern()); + matcher = PatternMatcher.createMysqlPattern(showTableStmt.getPattern(), + CaseSensibility.TABLE.getCaseSensibility()); } for (Table tbl : db.getTables()) { if (matcher != null && !matcher.match(tbl.getName())) { continue; } + // check tbl privs + if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(ConnectContext.get(), + db.getFullName(), tbl.getName(), + PrivPredicate.SHOW)) { + continue; + } tableMap.put(tbl.getName(), tbl.getMysqlType()); } } finally { @@ -373,12 +380,21 @@ public class ShowExecutor { try { PatternMatcher matcher = null; if (showStmt.getPattern() != null) { - matcher = PatternMatcher.createMysqlPattern(showStmt.getPattern()); + matcher = PatternMatcher.createMysqlPattern(showStmt.getPattern(), + CaseSensibility.TABLE.getCaseSensibility()); } for (Table table : db.getTables()) { if (matcher != null && !matcher.match(table.getName())) { continue; } + + // check tbl privs + if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(ConnectContext.get(), + db.getFullName(), table.getName(), + PrivPredicate.SHOW)) { + continue; + } + List row = Lists.newArrayList(); // Name row.add(table.getName()); @@ -403,7 +419,8 @@ public class ShowExecutor { ShowVariablesStmt showStmt = (ShowVariablesStmt) stmt; PatternMatcher matcher = null; if (showStmt.getPattern() != null) { - matcher = PatternMatcher.createMysqlPattern(showStmt.getPattern()); + matcher = PatternMatcher.createMysqlPattern(showStmt.getPattern(), + CaseSensibility.VARIABLES.getCaseSensibility()); } List> rows = VariableMgr.dump(showStmt.getType(), ctx.getSessionVariable(), matcher); resultSet = new ShowResultSet(showStmt.getMetaData(), rows); @@ -440,6 +457,10 @@ public class ShowExecutor { List createTableStmt = Lists.newArrayList(); Catalog.getDdlStmt(table, createTableStmt, null, null, false, (short) -1); + if (createTableStmt.isEmpty()) { + resultSet = new ShowResultSet(showStmt.getMetaData(), rows); + return; + } if (table instanceof View) { View view = (View) table; @@ -478,7 +499,8 @@ public class ShowExecutor { if (table != null) { PatternMatcher matcher = null; if (showStmt.getPattern() != null) { - matcher = PatternMatcher.createMysqlPattern(showStmt.getPattern()); + matcher = PatternMatcher.createMysqlPattern(showStmt.getPattern(), + CaseSensibility.COLUMN.getCaseSensibility()); } List columns = table.getBaseSchema(); for (Column col : columns) { @@ -575,8 +597,11 @@ public class ShowExecutor { long dbId = db.getId(); Load load = catalog.getLoadInstance(); - List> loadInfos = load.getLoadJobInfosByDb(dbId, showStmt.getLabelValue(), - showStmt.isAccurateMatch(), showStmt.getStates(), showStmt.getOrderByPairs()); + List> loadInfos = load.getLoadJobInfosByDb(dbId, db.getFullName(), + showStmt.getLabelValue(), + showStmt.isAccurateMatch(), + showStmt.getStates(), + showStmt.getOrderByPairs()); List> rows = Lists.newArrayList(); for (List loadInfo : loadInfos) { List oneInfo = new ArrayList(loadInfo.size()); @@ -613,14 +638,15 @@ public class ShowExecutor { long dbId = db.getId(); Load load = catalog.getLoadInstance(); long jobId = 0; + LoadJob job = null; String label = null; if (showWarningsStmt.isFindByLabel()) { label = showWarningsStmt.getLabel(); - jobId = load.getLatestJobIdByLabel(dbId, showWarningsStmt.getLabel()); + job = load.getLatestJobIdByLabel(dbId, showWarningsStmt.getLabel()); } else { LOG.info("load_job_id={}", jobId); jobId = showWarningsStmt.getJobId(); - LoadJob job = load.getLoadJob(jobId); + job = load.getLoadJob(jobId); if (job == null) { throw new AnalysisException("job is not exist."); } @@ -628,6 +654,29 @@ public class ShowExecutor { LOG.info("label={}", label); } + // check auth + Set tableNames = job.getTableNames(); + if (tableNames.isEmpty()) { + // forward compatibility + if (!Catalog.getCurrentCatalog().getAuth().checkDbPriv(ConnectContext.get(), db.getFullName(), + PrivPredicate.SHOW)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, + ConnectContext.get().getQualifiedUser(), + db.getFullName()); + } + } else { + for (String tblName : tableNames) { + if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(ConnectContext.get(), db.getFullName(), + tblName, PrivPredicate.SHOW)) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_TABLEACCESS_DENIED_ERROR, + "SHOW LOAD WARNING", + ConnectContext.get().getQualifiedUser(), + ConnectContext.get().getRemoteIP(), + tblName); + } + } + } + LoadErrorHub.Param param = load.getLoadErrorHubInfo(); if (param == null || param.getType() == HubType.NULL_TYPE) { throw new AnalysisException("no load error hub be supplied."); @@ -651,12 +700,11 @@ public class ShowExecutor { } resultSet = new ShowResultSet(showWarningsStmt.getMetaData(), rows); - } + // Show user property statement private void handleShowUserProperty() throws AnalysisException { ShowUserPropertyStmt showStmt = (ShowUserPropertyStmt) stmt; - showStmt.handleShow(); resultSet = new ShowResultSet(showStmt.getMetaData(), showStmt.getRows()); } @@ -842,16 +890,6 @@ public class ShowExecutor { resultSet = new ShowResultSet(showStmt.getMetaData(), rows); } - private void handleShowBackup() throws AnalysisException { - ShowBackupStmt showStmt = (ShowBackupStmt) stmt; - resultSet = new ShowResultSet(showStmt.getMetaData(), showStmt.getResultRows()); - } - - private void handleShowRestore() throws AnalysisException { - ShowRestoreStmt showStmt = (ShowRestoreStmt) stmt; - resultSet = new ShowResultSet(showStmt.getMetaData(), showStmt.getResultRows()); - } - // Handle show brokers private void handleShowBroker() { ShowBrokerStmt showStmt = (ShowBrokerStmt) stmt; @@ -901,16 +939,93 @@ public class ShowExecutor { private void handleShowBackends() { final ShowBackendsStmt showStmt = (ShowBackendsStmt) stmt; - final List> backendInfos = BackendsProcDir.getClusterBackendInfos(showStmt.getClusterName()); + List> backendInfos = BackendsProcDir.getClusterBackendInfos(showStmt.getClusterName()); + + for (List row : backendInfos) { + row.remove(BackendsProcDir.HOSTNAME_INDEX); + } + resultSet = new ShowResultSet(showStmt.getMetaData(), backendInfos); } - private void handleShowUser() { - final ShowUserStmt showStmt = (ShowUserStmt) stmt; - final List> userInfos = Catalog.getInstance().getUserMgr() - .fetchAccessResourceResult(showStmt.getUser()); - resultSet = new ShowResultSet(showStmt.getMetaData(), userInfos); + private void handleShowFrontends() { + final ShowFrontendsStmt showStmt = (ShowFrontendsStmt) stmt; + List> infos = Lists.newArrayList(); + FrontendsProcNode.getFrontendsInfo(Catalog.getCurrentCatalog(), infos); + resultSet = new ShowResultSet(showStmt.getMetaData(), infos); + } + + private void handleShowRepositories() { + final ShowRepositoriesStmt showStmt = (ShowRepositoriesStmt) stmt; + List> repoInfos = Catalog.getInstance().getBackupHandler().getRepoMgr().getReposInfo(); + resultSet = new ShowResultSet(showStmt.getMetaData(), repoInfos); + } + + private void handleShowSnapshot() throws AnalysisException { + final ShowSnapshotStmt showStmt = (ShowSnapshotStmt) stmt; + Repository repo = Catalog.getInstance().getBackupHandler().getRepoMgr().getRepo(showStmt.getRepoName()); + if (repo == null) { + throw new AnalysisException("Repository " + showStmt.getRepoName() + " does not exist"); + } + + List> snapshotInfos = repo.getSnapshotInfos(showStmt.getSnapshotName(), showStmt.getTimestamp()); + resultSet = new ShowResultSet(showStmt.getMetaData(), snapshotInfos); + } + + private void handleShowBackup() throws AnalysisException { + ShowBackupStmt showStmt = (ShowBackupStmt) stmt; + Database db = Catalog.getInstance().getDb(showStmt.getDbName()); + if (db == null) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_BAD_DB_ERROR, showStmt.getDbName()); + } + + AbstractJob jobI = Catalog.getInstance().getBackupHandler().getJob(db.getId()); + if (!(jobI instanceof BackupJob)) { + resultSet = new ShowResultSet(showStmt.getMetaData(), EMPTY_SET); + return; + } + + BackupJob backupJob = (BackupJob) jobI; + List info = backupJob.getInfo(); + List> infos = Lists.newArrayList(); + infos.add(info); + resultSet = new ShowResultSet(showStmt.getMetaData(), infos); + } + + private void handleShowRestore() throws AnalysisException { + ShowRestoreStmt showStmt = (ShowRestoreStmt) stmt; + Database db = Catalog.getInstance().getDb(showStmt.getDbName()); + if (db == null) { + ErrorReport.reportAnalysisException(ErrorCode.ERR_BAD_DB_ERROR, showStmt.getDbName()); + } + + AbstractJob jobI = Catalog.getInstance().getBackupHandler().getJob(db.getId()); + if (!(jobI instanceof RestoreJob)) { + resultSet = new ShowResultSet(showStmt.getMetaData(), EMPTY_SET); + return; + } + + RestoreJob restoreJob = (RestoreJob) jobI; + List info = restoreJob.getInfo(); + List> infos = Lists.newArrayList(); + infos.add(info); + resultSet = new ShowResultSet(showStmt.getMetaData(), infos); + } + + private void handleShowGrants() { + ShowGrantsStmt showStmt = (ShowGrantsStmt) stmt; + List> infos = Catalog.getCurrentCatalog().getAuth().getAuthInfo(showStmt.getUserIdent(), + showStmt.isAll()); + resultSet = new ShowResultSet(showStmt.getMetaData(), infos); + } + + private void handleShowRoles() { + ShowRolesStmt showStmt = (ShowRolesStmt) stmt; + List> infos = Catalog.getCurrentCatalog().getAuth().getRoleInfo(); + resultSet = new ShowResultSet(showStmt.getMetaData(), infos); } } + + diff --git a/fe/src/com/baidu/palo/qe/StmtExecutor.java b/fe/src/com/baidu/palo/qe/StmtExecutor.java index 8220f82fdd..76cb4d8397 100644 --- a/fe/src/com/baidu/palo/qe/StmtExecutor.java +++ b/fe/src/com/baidu/palo/qe/StmtExecutor.java @@ -53,6 +53,7 @@ import com.baidu.palo.common.util.TimeUtils; import com.baidu.palo.mysql.MysqlChannel; import com.baidu.palo.mysql.MysqlEofPacket; import com.baidu.palo.mysql.MysqlSerializer; +import com.baidu.palo.mysql.privilege.PrivPredicate; import com.baidu.palo.planner.Planner; import com.baidu.palo.rewrite.ExprRewriter; import com.baidu.palo.rpc.RpcException; @@ -67,7 +68,6 @@ import com.google.common.collect.Maps; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.thrift.transport.TTransportException; import java.io.IOException; import java.io.StringReader; @@ -123,7 +123,7 @@ public class StmtExecutor { summaryProfile.addInfoString(ProfileManager.QUERY_TYPE, "Query"); summaryProfile.addInfoString(ProfileManager.QUERY_STATE, context.getState().toString()); summaryProfile.addInfoString("Palo Version", "Palo version 2.0"); - summaryProfile.addInfoString(ProfileManager.USER, context.getUser()); + summaryProfile.addInfoString(ProfileManager.USER, context.getQualifiedUser()); summaryProfile.addInfoString(ProfileManager.DEFAULT_DB, context.getDatabase()); summaryProfile.addInfoString(ProfileManager.SQL_STATEMENT, originStmt); profile.addChild(summaryProfile); @@ -304,7 +304,6 @@ public class StmtExecutor { } } - // Analyze one statement to structure in memory. private void analyze() throws AnalysisException, InternalException, NotImplementedException { @@ -475,10 +474,10 @@ public class StmtExecutor { context.setKilled(); } else { // Check auth - if (!context.getCatalog().getUserMgr() - .checkUserAccess(context.getUser(), killCtx.getUser())) { + if (!Catalog.getCurrentCatalog().getAuth().checkGlobalPriv(ConnectContext.get(), PrivPredicate.ADMIN)) { ErrorReport.reportDdlException(ErrorCode.ERR_KILL_DENIED_ERROR, id); } + killCtx.kill(killStmt.isConnectionKill()); } context.getState().setOk(); @@ -732,7 +731,7 @@ public class StmtExecutor { } catch (Exception e) { // Maybe our bug LOG.warn("DDL statement(" + originStmt + ") process failed.", e); - context.getState().setError("Maybe palo bug, please info palo RD."); + context.getState().setError("Unexpected exception: " + e.getMessage()); } } diff --git a/fe/src/com/baidu/palo/rpc/BackendServiceProxy.java b/fe/src/com/baidu/palo/rpc/BackendServiceProxy.java index c10ed78dc7..3e31af9a86 100644 --- a/fe/src/com/baidu/palo/rpc/BackendServiceProxy.java +++ b/fe/src/com/baidu/palo/rpc/BackendServiceProxy.java @@ -18,6 +18,7 @@ package com.baidu.palo.rpc; import com.baidu.jprotobuf.pbrpc.client.ProtobufRpcProxy; import com.baidu.jprotobuf.pbrpc.transport.RpcClient; import com.baidu.jprotobuf.pbrpc.transport.RpcClientOptions; +import com.baidu.palo.common.Config; import com.baidu.palo.qe.SimpleScheduler; import com.baidu.palo.thrift.TExecPlanFragmentParams; import com.baidu.palo.thrift.TNetworkAddress; @@ -29,6 +30,7 @@ import org.apache.logging.log4j.Logger; import org.apache.thrift.TException; import java.util.Map; +import java.util.NoSuchElementException; import java.util.concurrent.Future; public class BackendServiceProxy { @@ -41,7 +43,10 @@ public class BackendServiceProxy { private static BackendServiceProxy INSTANCE; public BackendServiceProxy() { - rpcClient = new RpcClient(new RpcClientOptions()); + final RpcClientOptions rpcOptions = new RpcClientOptions(); + rpcOptions.setMaxWait(Config.brpc_idle_wait_max_time); + rpcOptions.setThreadPoolSize(Config.brpc_number_of_concurrent_requests_processed); + rpcClient = new RpcClient(rpcOptions); serviceMap = Maps.newHashMap(); } @@ -65,16 +70,31 @@ public class BackendServiceProxy { return service; } - public Future execPlanFragmentAsync( +public Future execPlanFragmentAsync( TNetworkAddress address, TExecPlanFragmentParams tRequest) throws TException, RpcException { + final PExecPlanFragmentRequest pRequest = new PExecPlanFragmentRequest(); + pRequest.setRequest(tRequest); try { - PExecPlanFragmentRequest pRequest = new PExecPlanFragmentRequest(); - pRequest.setRequest(tRequest); - PInternalService service = getProxy(address); + final PInternalService service = getProxy(address); return service.execPlanFragmentAsync(pRequest); + } catch (NoSuchElementException e) { + try { + // retry + try { + Thread.sleep(10); + } catch (InterruptedException interruptedException) { + // do nothing + } + final PInternalService service = getProxy(address); + return service.execPlanFragmentAsync(pRequest); + } catch (NoSuchElementException noSuchElementException) { + LOG.warn("Execute plan fragment retry failed, address={}:{}", + address.getHostname(), address.getPort(), noSuchElementException); + throw new RpcException(e.getMessage()); + } } catch (Throwable e) { - LOG.warn("execute plan fragment catch a exception, address={}:{}", + LOG.warn("Execute plan fragment catch a exception, address={}:{}", address.getHostname(), address.getPort(), e); throw new RpcException(e.getMessage()); } @@ -82,12 +102,27 @@ public class BackendServiceProxy { public Future cancelPlanFragmentAsync( TNetworkAddress address, TUniqueId finstId) throws RpcException { + final PCancelPlanFragmentRequest pRequest = new PCancelPlanFragmentRequest(new PUniqueId(finstId));; try { - PCancelPlanFragmentRequest pRequest = new PCancelPlanFragmentRequest(new PUniqueId(finstId)); - PInternalService service = getProxy(address); + final PInternalService service = getProxy(address); return service.cancelPlanFragmentAsync(pRequest); + } catch (NoSuchElementException e) { + // retry + try { + try { + Thread.sleep(10); + } catch (InterruptedException interruptedException) { + // do nothing + } + final PInternalService service = getProxy(address); + return service.cancelPlanFragmentAsync(pRequest); + } catch (NoSuchElementException noSuchElementException) { + LOG.warn("Cancel plan fragment retry failed, address={}:{}", + address.getHostname(), address.getPort(), noSuchElementException); + throw new RpcException(e.getMessage()); + } } catch (Throwable e) { - LOG.warn("cancel plan fragment catch a exception, address={}:{}", + LOG.warn("Cancel plan fragment catch a exception, address={}:{}", address.getHostname(), address.getPort(), e); throw new RpcException(e.getMessage()); } diff --git a/fe/src/com/baidu/palo/service/FrontendServiceImpl.java b/fe/src/com/baidu/palo/service/FrontendServiceImpl.java index 109bda02e5..9e9a9b3f7f 100644 --- a/fe/src/com/baidu/palo/service/FrontendServiceImpl.java +++ b/fe/src/com/baidu/palo/service/FrontendServiceImpl.java @@ -16,15 +16,14 @@ package com.baidu.palo.service; import com.baidu.palo.analysis.SetType; -import com.baidu.palo.catalog.AccessPrivilege; import com.baidu.palo.catalog.Catalog; import com.baidu.palo.catalog.Column; import com.baidu.palo.catalog.Database; import com.baidu.palo.catalog.Table; -import com.baidu.palo.catalog.UserPropertyMgr; import com.baidu.palo.cluster.ClusterNamespace; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.AuditLog; +import com.baidu.palo.common.CaseSensibility; import com.baidu.palo.common.Config; import com.baidu.palo.common.DdlException; import com.baidu.palo.common.PatternMatcher; @@ -34,7 +33,7 @@ import com.baidu.palo.load.EtlStatus; import com.baidu.palo.load.LoadJob; import com.baidu.palo.load.MiniEtlTaskInfo; import com.baidu.palo.master.MasterImpl; -import com.baidu.palo.mysql.MysqlPassword; +import com.baidu.palo.mysql.privilege.PrivPredicate; import com.baidu.palo.qe.AuditBuilder; import com.baidu.palo.qe.ConnectContext; import com.baidu.palo.qe.ConnectProcessor; @@ -104,26 +103,35 @@ public class FrontendServiceImpl implements FrontendService.Iface { @Override public TGetDbsResult getDbNames(TGetDbsParams params) throws TException { + LOG.debug("get db request: {}", params); TGetDbsResult result = new TGetDbsResult(); + List dbs = Lists.newArrayList(); - List dbNames = Catalog.getInstance().getDbNames(); - UserPropertyMgr userPropertyMgr = Catalog.getInstance().getUserMgr(); PatternMatcher matcher = null; if (params.isSetPattern()) { try { - matcher = PatternMatcher.createMysqlPattern(params.getPattern()); + matcher = PatternMatcher.createMysqlPattern(params.getPattern(), + CaseSensibility.DATABASE.getCaseSensibility()); } catch (AnalysisException e) { - throw new TException("Pattern is in bad format " + params.getPattern()); + throw new TException("Pattern is in bad format: " + params.getPattern()); } } + + Catalog catalog = Catalog.getCurrentCatalog(); + List dbNames = catalog.getDbNames(); + LOG.debug("get db names: {}", dbNames); for (String fullName : dbNames) { + if (!catalog.getAuth().checkDbPriv(params.user_ip, fullName, params.user, + PrivPredicate.SHOW)) { + continue; + } + final String db = ClusterNamespace.getNameFromFullName(fullName); if (matcher != null && !matcher.match(db)) { continue; } - if (userPropertyMgr.checkAccess(params.user, fullName, AccessPrivilege.READ_ONLY)) { - dbs.add(fullName); - } + + dbs.add(fullName); } result.setDbs(dbs); return result; @@ -131,20 +139,31 @@ public class FrontendServiceImpl implements FrontendService.Iface { @Override public TGetTablesResult getTableNames(TGetTablesParams params) throws TException { + LOG.debug("get table name request: {}", params); TGetTablesResult result = new TGetTablesResult(); List tablesResult = Lists.newArrayList(); result.setTables(tablesResult); PatternMatcher matcher = null; if (params.isSetPattern()) { try { - matcher = PatternMatcher.createMysqlPattern(params.getPattern()); + matcher = PatternMatcher.createMysqlPattern(params.getPattern(), + CaseSensibility.TABLE.getCaseSensibility()); } catch (AnalysisException e) { - throw new TException("Pattern is in bad format " + params.getPattern()); + throw new TException("Pattern is in bad format: " + params.getPattern()); } } + + // database privs should be checked in analysis phrase + Database db = Catalog.getInstance().getDb(params.db); if (db != null) { for (String tableName : db.getTableNamesWithLock()) { + LOG.debug("get table: {}, wait to check", tableName); + if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(params.user_ip, params.db, params.user, + tableName, PrivPredicate.SHOW)) { + continue; + } + if (matcher != null && !matcher.match(tableName)) { continue; } @@ -156,22 +175,32 @@ public class FrontendServiceImpl implements FrontendService.Iface { @Override public TListTableStatusResult listTableStatus(TGetTablesParams params) throws TException { + LOG.debug("get list table request: {}", params); TListTableStatusResult result = new TListTableStatusResult(); List tablesResult = Lists.newArrayList(); result.setTables(tablesResult); PatternMatcher matcher = null; if (params.isSetPattern()) { try { - matcher = PatternMatcher.createMysqlPattern(params.getPattern()); + matcher = PatternMatcher.createMysqlPattern(params.getPattern(), + CaseSensibility.TABLE.getCaseSensibility()); } catch (AnalysisException e) { throw new TException("Pattern is in bad format " + params.getPattern()); } } + + // database privs should be checked in analysis phrase + Database db = Catalog.getInstance().getDb(params.db); if (db != null) { db.readLock(); try { for (Table table : db.getTables()) { + if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(params.user_ip, params.db, params.user, + table.getName(), PrivPredicate.SHOW)) { + continue; + } + if (matcher != null && !matcher.match(table.getName())) { continue; } @@ -200,9 +229,18 @@ public class FrontendServiceImpl implements FrontendService.Iface { @Override public TDescribeTableResult describeTable(TDescribeTableParams params) throws TException { + LOG.debug("get desc table request: {}", params); TDescribeTableResult result = new TDescribeTableResult(); List columns = Lists.newArrayList(); result.setColumns(columns); + + // database privs should be checked in analysis phrase + + if (!Catalog.getCurrentCatalog().getAuth().checkTblPriv(params.user_ip, params.db, params.user, + params.getTable_name(), PrivPredicate.SHOW)) { + return result; + } + Database db = Catalog.getInstance().getDb(params.db); if (db != null) { db.readLock(); @@ -272,14 +310,12 @@ public class FrontendServiceImpl implements FrontendService.Iface { cluster = SystemInfoService.DEFAULT_CLUSTER; } - final String userFullName = Catalog.getInstance().getUserMgr().isAdmin(request.user) ? request.user : - ClusterNamespace.getFullName(cluster, request.user); final String dbFullName = ClusterNamespace.getFullName(cluster, request.db); - request.setUser(userFullName); + request.setUser(request.user); request.setDb(dbFullName); context.setCluster(cluster); context.setDatabase(ClusterNamespace.getFullName(cluster, request.db)); - context.setUser(ClusterNamespace.getFullName(cluster, request.user)); + context.setQualifiedUser(ClusterNamespace.getFullName(cluster, request.user)); context.setCatalog(Catalog.getInstance()); context.getState().reset(); context.setThreadLocalInfo(); @@ -311,7 +347,20 @@ public class FrontendServiceImpl implements FrontendService.Iface { return result; } - public static String getMiniLoadStmt(TMiniLoadRequest request) throws UnknownHostException { + private void logMiniLoadStmt(TMiniLoadRequest request) throws UnknownHostException { + String stmt = getMiniLoadStmt(request); + AuditBuilder auditBuilder = new AuditBuilder(); + auditBuilder.put("client", request.user_ip + ":0"); + auditBuilder.put("user", request.user); + auditBuilder.put("db", request.db); + auditBuilder.put("state", TStatusCode.OK); + auditBuilder.put("time", "0"); + auditBuilder.put("stmt", stmt); + + AuditLog.getQueryAudit().log(auditBuilder.toString()); + } + + private String getMiniLoadStmt(TMiniLoadRequest request) throws UnknownHostException { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("curl --location-trusted -u user:passwd -T "); @@ -339,19 +388,6 @@ public class FrontendServiceImpl implements FrontendService.Iface { return stringBuilder.toString(); } - private void logMiniLoadStmt(TMiniLoadRequest request) throws UnknownHostException { - String stmt = getMiniLoadStmt(request); - AuditBuilder auditBuilder = new AuditBuilder(); - auditBuilder.put("client", request.getBackend().getHostname() + ":" + request.getBackend().getPort()); - auditBuilder.put("user", request.user); - auditBuilder.put("db", request.db); - auditBuilder.put("state", TStatusCode.OK); - auditBuilder.put("time", "0"); - auditBuilder.put("stmt", stmt); - - AuditLog.getQueryAudit().log(auditBuilder.toString()); - } - @Override public TFeResult updateMiniEtlTaskStatus(TUpdateMiniEtlTaskStatusRequest request) throws TException { TFeResult result = new TFeResult(); @@ -432,30 +468,12 @@ public class FrontendServiceImpl implements FrontendService.Iface { } else { cluster = SystemInfoService.DEFAULT_CLUSTER; } - final String userFullName = Catalog.getInstance().getUserMgr().isAdmin(request.user) ? request.user : - ClusterNamespace.getFullName(cluster, request.user); + final String dbFullName = ClusterNamespace.getFullName(cluster, request.db); - request.setUser(userFullName); + + request.setUser(request.user); request.setDb(dbFullName); - // Check user and password - byte[] passwd = Catalog.getInstance().getUserMgr().getPassword(userFullName); - if (passwd == null) { - // No such user - status.setStatus_code(TStatusCode.INTERNAL_ERROR); - status.setError_msgs(Lists.newArrayList("No such user(" + userFullName + ")")); - return result; - } - if (!MysqlPassword.checkPlainPass(passwd, request.passwd)) { - status.setStatus_code(TStatusCode.INTERNAL_ERROR); - status.setError_msgs(Lists.newArrayList("Wrong password.")); - return result; - } - if (!Catalog.getInstance().getUserMgr().checkAccess(userFullName, dbFullName, AccessPrivilege.READ_WRITE)) { - status.setStatus_code(TStatusCode.INTERNAL_ERROR); - status.setError_msgs( - Lists.newArrayList("Have no privilege to write this database(" + request.getDb() + ")")); - return result; - } + if (request.isSetLabel()) { // Only single table will be set label try { diff --git a/fe/src/com/baidu/palo/system/Frontend.java b/fe/src/com/baidu/palo/system/Frontend.java index 5689045726..618853499b 100644 --- a/fe/src/com/baidu/palo/system/Frontend.java +++ b/fe/src/com/baidu/palo/system/Frontend.java @@ -115,4 +115,3 @@ public class Frontend implements Writable { return sb.toString(); } } - diff --git a/fe/src/com/baidu/palo/task/AgentBatchTask.java b/fe/src/com/baidu/palo/task/AgentBatchTask.java index 836c53fffe..0514feab02 100644 --- a/fe/src/com/baidu/palo/task/AgentBatchTask.java +++ b/fe/src/com/baidu/palo/task/AgentBatchTask.java @@ -15,36 +15,37 @@ package com.baidu.palo.task; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; +import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.common.ClientPool; +import com.baidu.palo.system.Backend; +import com.baidu.palo.thrift.BackendService; +import com.baidu.palo.thrift.TAgentServiceVersion; +import com.baidu.palo.thrift.TAgentTaskRequest; +import com.baidu.palo.thrift.TAlterTabletReq; +import com.baidu.palo.thrift.TCancelDeleteDataReq; +import com.baidu.palo.thrift.TCheckConsistencyReq; +import com.baidu.palo.thrift.TCloneReq; +import com.baidu.palo.thrift.TCreateTabletReq; +import com.baidu.palo.thrift.TDownloadReq; +import com.baidu.palo.thrift.TDropTabletReq; +import com.baidu.palo.thrift.TMoveDirReq; +import com.baidu.palo.thrift.TNetworkAddress; +import com.baidu.palo.thrift.TPushReq; +import com.baidu.palo.thrift.TPushType; +import com.baidu.palo.thrift.TReleaseSnapshotRequest; +import com.baidu.palo.thrift.TSnapshotRequest; +import com.baidu.palo.thrift.TStorageMediumMigrateReq; +import com.baidu.palo.thrift.TTaskType; +import com.baidu.palo.thrift.TUploadReq; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; import java.util.Map; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.LogManager; - -import com.baidu.palo.catalog.Catalog; -import com.baidu.palo.common.ClientPool; -import com.baidu.palo.system.Backend; -import com.baidu.palo.thrift.BackendService; -import com.baidu.palo.thrift.TAgentServiceVersion; -import com.baidu.palo.thrift.TAgentTaskRequest; -import com.baidu.palo.thrift.TAlterTabletReq; -import com.baidu.palo.thrift.TCancelDeleteDataReq; -import com.baidu.palo.thrift.TCheckConsistencyReq; -import com.baidu.palo.thrift.TCloneReq; -import com.baidu.palo.thrift.TCreateTabletReq; -import com.baidu.palo.thrift.TDropTabletReq; -import com.baidu.palo.thrift.TNetworkAddress; -import com.baidu.palo.thrift.TPushReq; -import com.baidu.palo.thrift.TPushType; -import com.baidu.palo.thrift.TReleaseSnapshotRequest; -import com.baidu.palo.thrift.TRestoreReq; -import com.baidu.palo.thrift.TSnapshotRequest; -import com.baidu.palo.thrift.TStorageMediumMigrateReq; -import com.baidu.palo.thrift.TTaskType; -import com.baidu.palo.thrift.TUploadReq; - /* * This class group tasks by backend */ @@ -234,12 +235,19 @@ public class AgentBatchTask implements Runnable { tAgentTaskRequest.setUpload_req(request); return tAgentTaskRequest; } - case RESTORE: { - RestoreTask restoreTask = (RestoreTask) task; - TRestoreReq request = restoreTask.toThrift(); + case DOWNLOAD: { + DownloadTask downloadTask = (DownloadTask) task; + TDownloadReq request = downloadTask.toThrift(); LOG.debug(request.toString()); - tAgentTaskRequest.setRestore_req(request); + tAgentTaskRequest.setDownload_req(request); return tAgentTaskRequest; + } + case MOVE: { + DirMoveTask dirMoveTask = (DirMoveTask) task; + TMoveDirReq request = dirMoveTask.toThrift(); + LOG.debug(request.toString()); + tAgentTaskRequest.setMove_dir_req(request); + return tAgentTaskRequest; } default: return null; diff --git a/fe/src/com/baidu/palo/task/AgentTask.java b/fe/src/com/baidu/palo/task/AgentTask.java index 0add79f939..a3662f40ad 100644 --- a/fe/src/com/baidu/palo/task/AgentTask.java +++ b/fe/src/com/baidu/palo/task/AgentTask.java @@ -15,7 +15,7 @@ package com.baidu.palo.task; -import com.baidu.palo.thrift.TResourceInfo; +import com.baidu.palo.thrift.TResourceInfo; import com.baidu.palo.thrift.TTaskType; public abstract class AgentTask { @@ -33,10 +33,10 @@ public abstract class AgentTask { protected int failedTimes; - public AgentTask(TResourceInfo resourceInfo, long backendId, TTaskType taskType, + public AgentTask(TResourceInfo resourceInfo, long backendId, long signature, TTaskType taskType, long dbId, long tableId, long partitionId, long indexId, long tabletId) { this.backendId = backendId; - this.signature = tabletId; + this.signature = signature; this.taskType = taskType; this.dbId = dbId; @@ -96,6 +96,6 @@ public abstract class AgentTask { @Override public String toString() { - return "[" + taskType + "], signature: " + signature + ", backendId: " + backendId; + return "[" + taskType + "], signature: " + signature + ", backendId: " + backendId + ", tablet id: " + tabletId; } } diff --git a/fe/src/com/baidu/palo/task/AgentTaskQueue.java b/fe/src/com/baidu/palo/task/AgentTaskQueue.java index ad25ce09e8..253c23db96 100644 --- a/fe/src/com/baidu/palo/task/AgentTaskQueue.java +++ b/fe/src/com/baidu/palo/task/AgentTaskQueue.java @@ -15,21 +15,21 @@ package com.baidu.palo.task; -import com.baidu.palo.thrift.TPushType; -import com.baidu.palo.thrift.TTaskType; - -import com.google.common.collect.HashBasedTable; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Table; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.LogManager; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Map; +import com.baidu.palo.thrift.TPushType; +import com.baidu.palo.thrift.TTaskType; + +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Table; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -106,6 +106,14 @@ public class AgentTaskQueue { signatureMap.remove(signature); LOG.debug("remove task: type[{}], backend[{}], signature[{}]", TTaskType.PUSH, backendId, signature); --taskNum; + } + + public static synchronized void removeTaskOfType(TTaskType type, long signature) { + // be id -> (signature -> task) + Map> map = tasks.column(type); + for (Map innerMap : map.values()) { + innerMap.remove(signature); + } } public static synchronized AgentTask getTask(long backendId, TTaskType type, long signature) { diff --git a/fe/src/com/baidu/palo/task/CancelDeleteTask.java b/fe/src/com/baidu/palo/task/CancelDeleteTask.java index 8562f7147d..f428d310fb 100644 --- a/fe/src/com/baidu/palo/task/CancelDeleteTask.java +++ b/fe/src/com/baidu/palo/task/CancelDeleteTask.java @@ -15,7 +15,7 @@ package com.baidu.palo.task; -import com.baidu.palo.thrift.TCancelDeleteDataReq; +import com.baidu.palo.thrift.TCancelDeleteDataReq; import com.baidu.palo.thrift.TTaskType; public class CancelDeleteTask extends AgentTask { @@ -25,7 +25,7 @@ public class CancelDeleteTask extends AgentTask { public CancelDeleteTask(long backendId, long dbId, long tableId, long partitionId, long indexId, long tabletId, int schemaHash, long version, long versionHash) { - super(null, backendId, TTaskType.CANCEL_DELETE, dbId, tableId, partitionId, indexId, tabletId); + super(null, backendId, tabletId, TTaskType.CANCEL_DELETE, dbId, tableId, partitionId, indexId, tabletId); this.schemaHash = schemaHash; this.version = version; diff --git a/fe/src/com/baidu/palo/task/CheckConsistencyTask.java b/fe/src/com/baidu/palo/task/CheckConsistencyTask.java index ea9a17ef43..320d506145 100644 --- a/fe/src/com/baidu/palo/task/CheckConsistencyTask.java +++ b/fe/src/com/baidu/palo/task/CheckConsistencyTask.java @@ -28,7 +28,8 @@ public class CheckConsistencyTask extends AgentTask { public CheckConsistencyTask(TResourceInfo resourceInfo, long backendId, long dbId, long tableId, long partitionId, long indexId, long tabletId, int schemaHash, long version, long versionHash) { - super(resourceInfo, backendId, TTaskType.CHECK_CONSISTENCY, dbId, tableId, partitionId, indexId, tabletId); + super(resourceInfo, backendId, tabletId, TTaskType.CHECK_CONSISTENCY, dbId, tableId, partitionId, indexId, + tabletId); this.schemaHash = schemaHash; this.version = version; diff --git a/fe/src/com/baidu/palo/task/CloneTask.java b/fe/src/com/baidu/palo/task/CloneTask.java index de16a5808f..71c43576a6 100644 --- a/fe/src/com/baidu/palo/task/CloneTask.java +++ b/fe/src/com/baidu/palo/task/CloneTask.java @@ -15,11 +15,11 @@ package com.baidu.palo.task; -import com.baidu.palo.thrift.TBackend; -import com.baidu.palo.thrift.TCloneReq; -import com.baidu.palo.thrift.TStorageMedium; -import com.baidu.palo.thrift.TTaskType; - +import com.baidu.palo.thrift.TBackend; +import com.baidu.palo.thrift.TCloneReq; +import com.baidu.palo.thrift.TStorageMedium; +import com.baidu.palo.thrift.TTaskType; + import java.util.List; public class CloneTask extends AgentTask { @@ -34,7 +34,7 @@ public class CloneTask extends AgentTask { public CloneTask(long backendId, long dbId, long tableId, long partitionId, long indexId, long tabletId, int schemaHash, List srcBackends, TStorageMedium storageMedium, long committedVersion, long committedVersionHash) { - super(null, backendId, TTaskType.CLONE, dbId, tableId, partitionId, indexId, tabletId); + super(null, backendId, tabletId, TTaskType.CLONE, dbId, tableId, partitionId, indexId, tabletId); this.schemaHash = schemaHash; this.srcBackends = srcBackends; this.storageMedium = storageMedium; diff --git a/fe/src/com/baidu/palo/task/CreateReplicaTask.java b/fe/src/com/baidu/palo/task/CreateReplicaTask.java index b1a4a27cdc..ded2303cf4 100644 --- a/fe/src/com/baidu/palo/task/CreateReplicaTask.java +++ b/fe/src/com/baidu/palo/task/CreateReplicaTask.java @@ -15,21 +15,21 @@ package com.baidu.palo.task; -import com.baidu.palo.catalog.Column; -import com.baidu.palo.catalog.KeysType; -import com.baidu.palo.common.MarkedCountDownLatch; -import com.baidu.palo.thrift.TColumn; -import com.baidu.palo.thrift.TCreateTabletReq; -import com.baidu.palo.thrift.TStorageMedium; -import com.baidu.palo.thrift.TStorageType; -import com.baidu.palo.thrift.TTabletSchema; -import com.baidu.palo.thrift.TTaskType; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.LogManager; - -import java.util.ArrayList; -import java.util.List; +import com.baidu.palo.catalog.Column; +import com.baidu.palo.catalog.KeysType; +import com.baidu.palo.common.MarkedCountDownLatch; +import com.baidu.palo.thrift.TColumn; +import com.baidu.palo.thrift.TCreateTabletReq; +import com.baidu.palo.thrift.TStorageMedium; +import com.baidu.palo.thrift.TStorageType; +import com.baidu.palo.thrift.TTabletSchema; +import com.baidu.palo.thrift.TTaskType; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.ArrayList; +import java.util.List; import java.util.Set; public class CreateReplicaTask extends AgentTask { @@ -52,14 +52,16 @@ public class CreateReplicaTask extends AgentTask { private double bfFpp; // used for synchronous process - private MarkedCountDownLatch latch; + private MarkedCountDownLatch latch; + + private boolean inRestoreMode = false; public CreateReplicaTask(long backendId, long dbId, long tableId, long partitionId, long indexId, long tabletId, short shortKeyColumnCount, int schemaHash, long version, long versionHash, KeysType keysType, TStorageType storageType, TStorageMedium storageMedium, List columns, Set bfColumns, double bfFpp, MarkedCountDownLatch latch) { - super(null, backendId, TTaskType.CREATE, dbId, tableId, partitionId, indexId, tabletId); + super(null, backendId, tabletId, TTaskType.CREATE, dbId, tableId, partitionId, indexId, tabletId); this.shortKeyColumnCount = shortKeyColumnCount; this.schemaHash = schemaHash; @@ -86,6 +88,14 @@ public class CreateReplicaTask extends AgentTask { latch.getCount(), backendId, tabletId); } } + } + + public void setLatch(MarkedCountDownLatch latch) { + this.latch = latch; + } + + public void setInRestoreMode(boolean inRestoreMode) { + this.inRestoreMode = inRestoreMode; } public TCreateTabletReq toThrift() { @@ -118,7 +128,9 @@ public class CreateReplicaTask extends AgentTask { createTabletReq.setVersion_hash(versionHash); createTabletReq.setStorage_medium(storageMedium); - + if (inRestoreMode) { + createTabletReq.setIn_restore_mode(true); + } return createTabletReq; } diff --git a/fe/src/com/baidu/palo/task/CreateRollupTask.java b/fe/src/com/baidu/palo/task/CreateRollupTask.java index 4a68245986..f51c8d0daa 100644 --- a/fe/src/com/baidu/palo/task/CreateRollupTask.java +++ b/fe/src/com/baidu/palo/task/CreateRollupTask.java @@ -15,18 +15,18 @@ package com.baidu.palo.task; -import com.baidu.palo.catalog.Column; -import com.baidu.palo.thrift.TAlterTabletReq; -import com.baidu.palo.thrift.TColumn; -import com.baidu.palo.thrift.TCreateTabletReq; -import com.baidu.palo.thrift.TKeysType; -import com.baidu.palo.thrift.TResourceInfo; -import com.baidu.palo.thrift.TStorageType; -import com.baidu.palo.thrift.TTabletSchema; -import com.baidu.palo.thrift.TTaskType; - -import java.util.ArrayList; -import java.util.List; +import com.baidu.palo.catalog.Column; +import com.baidu.palo.thrift.TAlterTabletReq; +import com.baidu.palo.thrift.TColumn; +import com.baidu.palo.thrift.TCreateTabletReq; +import com.baidu.palo.thrift.TKeysType; +import com.baidu.palo.thrift.TResourceInfo; +import com.baidu.palo.thrift.TStorageType; +import com.baidu.palo.thrift.TTabletSchema; +import com.baidu.palo.thrift.TTaskType; + +import java.util.ArrayList; +import java.util.List; import java.util.Set; public class CreateRollupTask extends AgentTask { @@ -54,7 +54,8 @@ public class CreateRollupTask extends AgentTask { long baseTabletId, long rollupReplicaId, short shortKeyColumnCount, int rollupSchemaHash, int baseSchemaHash, TStorageType storageType, List rollupColumns, Set bfColumns, double bfFpp, TKeysType keysType) { - super(resourceInfo, backendId, TTaskType.ROLLUP, dbId, tableId, partitionId, rollupIndexId, rollupTabletId); + super(resourceInfo, backendId, rollupTabletId, TTaskType.ROLLUP, dbId, tableId, partitionId, rollupIndexId, + rollupTabletId); this.baseTableId = baseIndexId; this.baseTabletId = baseTabletId; diff --git a/fe/src/com/baidu/palo/task/DirMoveTask.java b/fe/src/com/baidu/palo/task/DirMoveTask.java new file mode 100644 index 0000000000..3f0c262621 --- /dev/null +++ b/fe/src/com/baidu/palo/task/DirMoveTask.java @@ -0,0 +1,65 @@ +// Modifications copyright (C) 2018, Baidu.com, Inc. +// Copyright 2018 The Apache Software Foundation + +// 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. + +package com.baidu.palo.task; + +import com.baidu.palo.thrift.TMoveDirReq; +import com.baidu.palo.thrift.TResourceInfo; +import com.baidu.palo.thrift.TTaskType; + +public class DirMoveTask extends AgentTask { + + private long jobId; + private String src; + private int schemaHash; + private boolean overwrite; + + public DirMoveTask(TResourceInfo resourceInfo, long backendId, long signature, long jobId, long dbId, + long tableId, long partitionId, long indexId, long tabletId, String src, int schemaHash, + boolean overwrite) { + super(resourceInfo, backendId, signature, TTaskType.MOVE, dbId, tableId, partitionId, indexId, tabletId); + this.jobId = jobId; + this.src = src; + this.schemaHash = schemaHash; + this.overwrite = overwrite; + } + + public long getJobId() { + return jobId; + } + + public String getSrc() { + return src; + } + + public int getSchemaHash() { + return schemaHash; + } + + public boolean isOverwrite() { + return overwrite; + } + + public TMoveDirReq toThrift() { + TMoveDirReq req = new TMoveDirReq(tabletId, schemaHash, src, jobId, overwrite); + return req; + } + +} diff --git a/fe/src/com/baidu/palo/task/DownloadTask.java b/fe/src/com/baidu/palo/task/DownloadTask.java new file mode 100644 index 0000000000..053c05894a --- /dev/null +++ b/fe/src/com/baidu/palo/task/DownloadTask.java @@ -0,0 +1,69 @@ +// Modifications copyright (C) 2018, Baidu.com, Inc. +// Copyright 2018 The Apache Software Foundation + +// 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. + +package com.baidu.palo.task; + +import com.baidu.palo.catalog.BrokerMgr.BrokerAddress; +import com.baidu.palo.thrift.TDownloadReq; +import com.baidu.palo.thrift.TNetworkAddress; +import com.baidu.palo.thrift.TResourceInfo; +import com.baidu.palo.thrift.TTaskType; + +import java.util.Map; + +public class DownloadTask extends AgentTask { + + private long jobId; + private Map srcToDestPath; + private BrokerAddress brokerAddr; + private Map brokerProperties; + + public DownloadTask(TResourceInfo resourceInfo, long backendId, long signature, long jobId, long dbId, + Map srcToDestPath, BrokerAddress brokerAddr, Map brokerProperties) { + super(resourceInfo, backendId, signature, TTaskType.DOWNLOAD, dbId, -1, -1, -1, -1); + this.jobId = jobId; + this.srcToDestPath = srcToDestPath; + this.brokerAddr = brokerAddr; + this.brokerProperties = brokerProperties; + } + + public long getJobId() { + return jobId; + } + + public Map getSrcToDestPath() { + return srcToDestPath; + } + + public BrokerAddress getBrokerAddr() { + return brokerAddr; + } + + public Map getBrokerProperties() { + return brokerProperties; + } + + public TDownloadReq toThrift() { + TNetworkAddress address = new TNetworkAddress(brokerAddr.ip, brokerAddr.port); + TDownloadReq req = new TDownloadReq(jobId, srcToDestPath, address); + req.setBroker_prop(brokerProperties); + return req; + } +} diff --git a/fe/src/com/baidu/palo/task/DropReplicaTask.java b/fe/src/com/baidu/palo/task/DropReplicaTask.java index e24101bfa9..52eba4fa83 100644 --- a/fe/src/com/baidu/palo/task/DropReplicaTask.java +++ b/fe/src/com/baidu/palo/task/DropReplicaTask.java @@ -15,14 +15,14 @@ package com.baidu.palo.task; -import com.baidu.palo.thrift.TDropTabletReq; +import com.baidu.palo.thrift.TDropTabletReq; import com.baidu.palo.thrift.TTaskType; public class DropReplicaTask extends AgentTask { private int schemaHash; // set -1L as unknown public DropReplicaTask(long backendId, long tabletId, int schemaHash) { - super(null, backendId, TTaskType.DROP, -1L, -1L, -1L, -1L, tabletId); + super(null, backendId, tabletId, TTaskType.DROP, -1L, -1L, -1L, -1L, tabletId); this.schemaHash = schemaHash; } diff --git a/fe/src/com/baidu/palo/task/ExportExportingTask.java b/fe/src/com/baidu/palo/task/ExportExportingTask.java index 9eda0c0b40..8d4d1d14ca 100644 --- a/fe/src/com/baidu/palo/task/ExportExportingTask.java +++ b/fe/src/com/baidu/palo/task/ExportExportingTask.java @@ -269,7 +269,7 @@ public class ExportExportingTask extends MasterTask { String localIP = FrontendOptions.getLocalHostAddress(); brokerAddress = Catalog.getInstance().getBrokerMgr().getBroker(job.getBrokerDesc().getName(), localIP); } catch (AnalysisException e) { - String failMsg = "Broker rename failed. msg=" + e.getMessage(); + String failMsg = "get broker failed. msg=" + e.getMessage(); LOG.warn(failMsg); return new Status(TStatusCode.CANCELLED, failMsg); } diff --git a/fe/src/com/baidu/palo/task/PushTask.java b/fe/src/com/baidu/palo/task/PushTask.java index 7df5c2c006..0dfad36468 100644 --- a/fe/src/com/baidu/palo/task/PushTask.java +++ b/fe/src/com/baidu/palo/task/PushTask.java @@ -15,24 +15,24 @@ package com.baidu.palo.task; -import com.baidu.palo.analysis.BinaryPredicate; -import com.baidu.palo.analysis.BinaryPredicate.Operator; -import com.baidu.palo.analysis.IsNullPredicate; -import com.baidu.palo.analysis.LiteralExpr; -import com.baidu.palo.analysis.Predicate; -import com.baidu.palo.analysis.SlotRef; -import com.baidu.palo.common.MarkedCountDownLatch; -import com.baidu.palo.thrift.TCondition; -import com.baidu.palo.thrift.TPriority; -import com.baidu.palo.thrift.TPushReq; -import com.baidu.palo.thrift.TPushType; -import com.baidu.palo.thrift.TResourceInfo; -import com.baidu.palo.thrift.TTaskType; - -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.LogManager; - -import java.util.ArrayList; +import com.baidu.palo.analysis.BinaryPredicate; +import com.baidu.palo.analysis.BinaryPredicate.Operator; +import com.baidu.palo.analysis.IsNullPredicate; +import com.baidu.palo.analysis.LiteralExpr; +import com.baidu.palo.analysis.Predicate; +import com.baidu.palo.analysis.SlotRef; +import com.baidu.palo.common.MarkedCountDownLatch; +import com.baidu.palo.thrift.TCondition; +import com.baidu.palo.thrift.TPriority; +import com.baidu.palo.thrift.TPushReq; +import com.baidu.palo.thrift.TPushType; +import com.baidu.palo.thrift.TResourceInfo; +import com.baidu.palo.thrift.TTaskType; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.ArrayList; import java.util.List; public class PushTask extends AgentTask { @@ -62,7 +62,7 @@ public class PushTask extends AgentTask { long indexId, long tabletId, long replicaId, int schemaHash, long version, long versionHash, String filePath, long fileSize, int timeoutSecond, long loadJobId, TPushType pushType, List conditions, boolean needDecompress, TPriority priority) { - super(resourceInfo, backendId, TTaskType.PUSH, dbId, tableId, partitionId, indexId, tabletId); + super(resourceInfo, backendId, tabletId, TTaskType.PUSH, dbId, tableId, partitionId, indexId, tabletId); this.replicaId = replicaId; this.schemaHash = schemaHash; this.version = version; diff --git a/fe/src/com/baidu/palo/task/ReleaseSnapshotTask.java b/fe/src/com/baidu/palo/task/ReleaseSnapshotTask.java index b57da95645..46ded8f1fe 100644 --- a/fe/src/com/baidu/palo/task/ReleaseSnapshotTask.java +++ b/fe/src/com/baidu/palo/task/ReleaseSnapshotTask.java @@ -25,7 +25,7 @@ public class ReleaseSnapshotTask extends AgentTask { public ReleaseSnapshotTask(TResourceInfo resourceInfo, long backendId, long dbId, long tabletId, String snapshotPath) { - super(resourceInfo, backendId, TTaskType.RELEASE_SNAPSHOT, dbId, -1, -1, -1, tabletId); + super(resourceInfo, backendId, tabletId, TTaskType.RELEASE_SNAPSHOT, dbId, -1, -1, -1, tabletId); this.snapshotPath = snapshotPath; } diff --git a/fe/src/com/baidu/palo/task/RestoreTask.java b/fe/src/com/baidu/palo/task/RestoreTask.java deleted file mode 100644 index 3ec7206d91..0000000000 --- a/fe/src/com/baidu/palo/task/RestoreTask.java +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) 2017, Baidu.com, Inc. All Rights Reserved - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// 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. - -package com.baidu.palo.task; - -import com.baidu.palo.thrift.TResourceInfo; -import com.baidu.palo.thrift.TRestoreReq; -import com.baidu.palo.thrift.TTaskType; - -import java.util.Map; - -public class RestoreTask extends AgentTask { - - private long jobId; - private String remoteFilePath; - private int schemaHash; - private Map remoteProperties; - - public RestoreTask(TResourceInfo resourceInfo, long backendId, long jobId, long dbId, long tableId, - long partitionId, long indexId, long tabletId, int schemaHash, - String remoteFilePath, Map remoteProperties) { - super(resourceInfo, backendId, TTaskType.RESTORE, dbId, tableId, partitionId, indexId, tabletId); - - this.jobId = jobId; - this.remoteFilePath = remoteFilePath; - this.schemaHash = schemaHash; - this.remoteProperties = remoteProperties; - } - - public long getJobId() { - return jobId; - } - - public String getRemoteFilePath() { - return remoteFilePath; - } - - public int getSchemaHash() { - return schemaHash; - } - - public Map getRemoteProperties() { - return remoteProperties; - } - - public TRestoreReq toThrift() { - TRestoreReq req = new TRestoreReq(tabletId, schemaHash, remoteFilePath, remoteProperties); - return req; - } -} diff --git a/fe/src/com/baidu/palo/task/SchemaChangeTask.java b/fe/src/com/baidu/palo/task/SchemaChangeTask.java index bfbb5cfc1d..23be4708fd 100644 --- a/fe/src/com/baidu/palo/task/SchemaChangeTask.java +++ b/fe/src/com/baidu/palo/task/SchemaChangeTask.java @@ -15,18 +15,18 @@ package com.baidu.palo.task; -import com.baidu.palo.catalog.Column; -import com.baidu.palo.thrift.TAlterTabletReq; -import com.baidu.palo.thrift.TColumn; -import com.baidu.palo.thrift.TCreateTabletReq; -import com.baidu.palo.thrift.TKeysType; -import com.baidu.palo.thrift.TResourceInfo; -import com.baidu.palo.thrift.TStorageType; -import com.baidu.palo.thrift.TTabletSchema; -import com.baidu.palo.thrift.TTaskType; - -import java.util.ArrayList; -import java.util.List; +import com.baidu.palo.catalog.Column; +import com.baidu.palo.thrift.TAlterTabletReq; +import com.baidu.palo.thrift.TColumn; +import com.baidu.palo.thrift.TCreateTabletReq; +import com.baidu.palo.thrift.TKeysType; +import com.baidu.palo.thrift.TResourceInfo; +import com.baidu.palo.thrift.TStorageType; +import com.baidu.palo.thrift.TTabletSchema; +import com.baidu.palo.thrift.TTaskType; + +import java.util.ArrayList; +import java.util.List; import java.util.Set; public class SchemaChangeTask extends AgentTask { @@ -49,7 +49,8 @@ public class SchemaChangeTask extends AgentTask { List newColumns, int newSchemaHash, int baseSchemaHash, short newShortKeyColumnCount, TStorageType storageType, Set bfColumns, double bfFpp, TKeysType keysType) { - super(resourceInfo, backendId, TTaskType.SCHEMA_CHANGE, dbId, tableId, partitionId, indexId, baseTabletId); + super(resourceInfo, backendId, baseTabletId, TTaskType.SCHEMA_CHANGE, dbId, tableId, partitionId, indexId, + baseTabletId); this.baseReplicaId = baseReplicaId; this.baseSchemaHash = baseSchemaHash; diff --git a/fe/src/com/baidu/palo/task/SnapshotTask.java b/fe/src/com/baidu/palo/task/SnapshotTask.java index e4079cf79c..22f3cef8e7 100644 --- a/fe/src/com/baidu/palo/task/SnapshotTask.java +++ b/fe/src/com/baidu/palo/task/SnapshotTask.java @@ -29,10 +29,13 @@ public class SnapshotTask extends AgentTask { private long timeout; - public SnapshotTask(TResourceInfo resourceInfo, long backendId, long jobId, long dbId, long tableId, - long partitionId, long indexId, long tabletId, long version, long versionHash, - int schemaHash, long timeout) { - super(resourceInfo, backendId, TTaskType.MAKE_SNAPSHOT, dbId, tableId, partitionId, indexId, tabletId); + private boolean isRestoreTask; + + public SnapshotTask(TResourceInfo resourceInfo, long backendId, long signature, long jobId, + long dbId, long tableId, long partitionId, long indexId, long tabletId, + long version, long versionHash, int schemaHash, long timeout, boolean isRestoreTask) { + super(resourceInfo, backendId, signature, TTaskType.MAKE_SNAPSHOT, dbId, tableId, partitionId, indexId, + tabletId); this.jobId = jobId; @@ -41,6 +44,8 @@ public class SnapshotTask extends AgentTask { this.schemaHash = schemaHash; this.timeout = timeout; + + this.isRestoreTask = isRestoreTask; } public long getJobId() { @@ -63,10 +68,15 @@ public class SnapshotTask extends AgentTask { return timeout; } + public boolean isRestoreTask() { + return isRestoreTask; + } + public TSnapshotRequest toThrift() { TSnapshotRequest request = new TSnapshotRequest(tabletId, schemaHash); request.setVersion(version); request.setVersion_hash(versionHash); + request.setList_files(true); return request; } } \ No newline at end of file diff --git a/fe/src/com/baidu/palo/task/StorageMediaMigrationTask.java b/fe/src/com/baidu/palo/task/StorageMediaMigrationTask.java index cb9f82d378..95110c39d2 100644 --- a/fe/src/com/baidu/palo/task/StorageMediaMigrationTask.java +++ b/fe/src/com/baidu/palo/task/StorageMediaMigrationTask.java @@ -26,7 +26,7 @@ public class StorageMediaMigrationTask extends AgentTask { public StorageMediaMigrationTask(long backendId, long tabletId, int schemaHash, TStorageMedium toStorageMedium) { - super(null, backendId, TTaskType.STORAGE_MEDIUM_MIGRATE, -1L, -1L, -1L, -1L, tabletId); + super(null, backendId, tabletId, TTaskType.STORAGE_MEDIUM_MIGRATE, -1L, -1L, -1L, -1L, tabletId); this.schemaHash = schemaHash; this.toStorageMedium = toStorageMedium; diff --git a/fe/src/com/baidu/palo/task/UploadTask.java b/fe/src/com/baidu/palo/task/UploadTask.java index acc17f49af..3e1a3eaca9 100644 --- a/fe/src/com/baidu/palo/task/UploadTask.java +++ b/fe/src/com/baidu/palo/task/UploadTask.java @@ -15,6 +15,8 @@ package com.baidu.palo.task; +import com.baidu.palo.catalog.BrokerMgr.BrokerAddress; +import com.baidu.palo.thrift.TNetworkAddress; import com.baidu.palo.thrift.TResourceInfo; import com.baidu.palo.thrift.TTaskType; import com.baidu.palo.thrift.TUploadReq; @@ -24,40 +26,40 @@ import java.util.Map; public class UploadTask extends AgentTask { private long jobId; - private String src; - private String dest; - private Map remoteSourceProperties; + private Map srcToDestPath; + private BrokerAddress brokerAddress; + private Map brokerProperties; - public UploadTask(TResourceInfo resourceInfo, long backendId, long jobId, long dbId, long tableId, - long partitionId, long indexId, long tabletId, String src, String dest, - Map remoteSourceProperties) { - super(resourceInfo, backendId, TTaskType.UPLOAD, dbId, tableId, partitionId, indexId, tabletId); + public UploadTask(TResourceInfo resourceInfo, long backendId, long signature, long jobId, Long dbId, + Map srcToDestPath, BrokerAddress brokerAddr, Map brokerProperties) { + super(resourceInfo, backendId, signature, TTaskType.UPLOAD, dbId, -1, -1, -1, -1); this.jobId = jobId; - this.src = src; - this.dest = dest; - this.remoteSourceProperties = remoteSourceProperties; + this.srcToDestPath = srcToDestPath; + this.brokerAddress = brokerAddr; + this.brokerProperties = brokerProperties; } public long getJobId() { return jobId; } - public String getSrc() { - return src; + public Map getSrcToDestPath() { + return srcToDestPath; } - public String getDest() { - return dest; + public BrokerAddress getBrokerAddress() { + return brokerAddress; } - public Map getRemoteSourceProperties() { - return remoteSourceProperties; + public Map getBrokerProperties() { + return brokerProperties; } public TUploadReq toThrift() { - TUploadReq request = new TUploadReq(src, dest, remoteSourceProperties); - request.setTablet_id(tabletId); + TNetworkAddress address = new TNetworkAddress(brokerAddress.ip, brokerAddress.port); + TUploadReq request = new TUploadReq(jobId, srcToDestPath, address); + request.setBroker_prop(brokerProperties); return request; } } diff --git a/fe/test/com/baidu/palo/analysis/AccessTestUtil.java b/fe/test/com/baidu/palo/analysis/AccessTestUtil.java index 193bb838f9..aaff5a4b53 100644 --- a/fe/test/com/baidu/palo/analysis/AccessTestUtil.java +++ b/fe/test/com/baidu/palo/analysis/AccessTestUtil.java @@ -20,14 +20,9 @@ package com.baidu.palo.analysis; -import java.util.LinkedList; -import java.util.List; - -import org.easymock.EasyMock; - import com.baidu.palo.alter.RollupHandler; import com.baidu.palo.alter.SchemaChangeHandler; -import com.baidu.palo.catalog.AccessPrivilege; +import com.baidu.palo.catalog.BrokerMgr; import com.baidu.palo.catalog.Catalog; import com.baidu.palo.catalog.Column; import com.baidu.palo.catalog.Database; @@ -39,50 +34,55 @@ import com.baidu.palo.catalog.Partition; import com.baidu.palo.catalog.PrimitiveType; import com.baidu.palo.catalog.RandomDistributionInfo; import com.baidu.palo.catalog.SinglePartitionInfo; -import com.baidu.palo.catalog.UserPropertyMgr; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.DdlException; import com.baidu.palo.load.Load; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.mysql.privilege.PrivPredicate; import com.baidu.palo.persist.EditLog; import com.baidu.palo.qe.ConnectContext; import com.baidu.palo.system.SystemInfoService; + import com.google.common.collect.Lists; import com.google.common.collect.Sets; +import org.easymock.EasyMock; + +import java.util.LinkedList; +import java.util.List; + public class AccessTestUtil { - public static UserPropertyMgr fetchAdminAccess() { - UserPropertyMgr userPropertyMgr = EasyMock.createMock(UserPropertyMgr.class); - EasyMock.expect(userPropertyMgr.checkAccess(EasyMock.isA(String.class), EasyMock.isA(String.class), - EasyMock.isA(AccessPrivilege.class))).andReturn(true).anyTimes(); - EasyMock.expect(userPropertyMgr.isAdmin(EasyMock.isA(String.class))).andReturn(true).anyTimes(); - EasyMock.expect(userPropertyMgr.isSuperuser(EasyMock.isA(String.class))).andReturn(true).anyTimes(); - EasyMock.expect(userPropertyMgr.checkUserAccess(EasyMock.isA(String.class), EasyMock.eq("blockUser"))) - .andReturn(false).anyTimes(); - EasyMock.expect(userPropertyMgr.checkUserAccess(EasyMock.isA(String.class), EasyMock.isA(String.class))) - .andReturn(true).anyTimes(); - EasyMock.expect(userPropertyMgr.getMaxConn(EasyMock.isA(String.class))).andReturn(1000L).anyTimes(); - try { - userPropertyMgr.setPasswd(EasyMock.endsWith("testCluster:testUser"), EasyMock.isA(byte[].class)); - EasyMock.expectLastCall().anyTimes(); - userPropertyMgr.setPasswd(EasyMock.endsWith("root"), EasyMock.isA(byte[].class)); - EasyMock.expectLastCall().andThrow(new DdlException("No privilege to change password")).anyTimes(); - } catch (DdlException e) { - return null; - } - EasyMock.replay(userPropertyMgr); - return userPropertyMgr; - } public static SystemInfoService fetchSystemInfoService() { SystemInfoService clusterInfo = EasyMock.createMock(SystemInfoService.class); EasyMock.replay(clusterInfo); return clusterInfo; } + + public static PaloAuth fetchAdminAccess() { + PaloAuth auth = EasyMock.createMock(PaloAuth.class); + EasyMock.expect(auth.checkGlobalPriv(EasyMock.isA(ConnectContext.class), + EasyMock.isA(PrivPredicate.class))).andReturn(true).anyTimes(); + EasyMock.expect(auth.checkDbPriv(EasyMock.isA(ConnectContext.class), EasyMock.anyString(), + EasyMock.isA(PrivPredicate.class))).andReturn(true).anyTimes(); + EasyMock.expect(auth.checkTblPriv(EasyMock.isA(ConnectContext.class), EasyMock.anyString(), + EasyMock.anyString(), EasyMock.isA(PrivPredicate.class))) + .andReturn(true).anyTimes(); + try { + auth.setPassword(EasyMock.isA(SetPassVar.class)); + } catch (DdlException e) { + e.printStackTrace(); + } + EasyMock.expectLastCall().anyTimes(); + + EasyMock.replay(auth); + return auth; + } public static Catalog fetchAdminCatalog() { try { Catalog catalog = EasyMock.createMock(Catalog.class); - EasyMock.expect(catalog.getUserMgr()).andReturn(fetchAdminAccess()).anyTimes(); + EasyMock.expect(catalog.getAuth()).andReturn(fetchAdminAccess()).anyTimes(); Database db = new Database(50000L, "testCluster:testDb"); MaterializedIndex baseIndex = new MaterializedIndex(30000, IndexState.NORMAL); @@ -105,12 +105,12 @@ public class AccessTestUtil { EasyMock.expect(catalog.getSchemaChangeHandler()).andReturn(new SchemaChangeHandler()).anyTimes(); EasyMock.expect(catalog.getRollupHandler()).andReturn(new RollupHandler()).anyTimes(); EasyMock.expect(catalog.getEditLog()).andReturn(EasyMock.createMock(EditLog.class)).anyTimes(); - EasyMock.expect(catalog.getClusterDbNames("testCluster")) - .andReturn(Lists.newArrayList("testCluster:testDb")).anyTimes(); + EasyMock.expect(catalog.getClusterDbNames("testCluster")).andReturn(Lists.newArrayList("testCluster:testDb")).anyTimes(); catalog.changeDb(EasyMock.isA(ConnectContext.class), EasyMock.eq("blockDb")); EasyMock.expectLastCall().andThrow(new DdlException("failed.")).anyTimes(); catalog.changeDb(EasyMock.isA(ConnectContext.class), EasyMock.isA(String.class)); EasyMock.expectLastCall().anyTimes(); + EasyMock.expect(catalog.getBrokerMgr()).andReturn(new BrokerMgr()).anyTimes(); EasyMock.replay(catalog); return catalog; } catch (DdlException e) { @@ -120,19 +120,20 @@ public class AccessTestUtil { } } - public static UserPropertyMgr fetchBlockAccess() { - UserPropertyMgr userPropertyMgr = EasyMock.createMock(UserPropertyMgr.class); - EasyMock.expect(userPropertyMgr.checkAccess(EasyMock.isA(String.class), EasyMock.isA(String.class), - EasyMock.isA(AccessPrivilege.class))).andReturn(false).anyTimes(); - EasyMock.expect(userPropertyMgr.isAdmin(EasyMock.isA(String.class))).andReturn(false).anyTimes(); - EasyMock.expect(userPropertyMgr.isSuperuser(EasyMock.isA(String.class))).andReturn(false).anyTimes(); - EasyMock.expect(userPropertyMgr.checkUserAccess(EasyMock.isA(String.class), EasyMock.isA(String.class))) + public static PaloAuth fetchBlockAccess() { + PaloAuth auth = EasyMock.createMock(PaloAuth.class); + EasyMock.expect(auth.checkGlobalPriv(EasyMock.isA(ConnectContext.class), + EasyMock.isA(PrivPredicate.class))).andReturn(false).anyTimes(); + EasyMock.expect(auth.checkDbPriv(EasyMock.isA(ConnectContext.class), EasyMock.anyString(), + EasyMock.isA(PrivPredicate.class))).andReturn(false).anyTimes(); + EasyMock.expect(auth.checkTblPriv(EasyMock.isA(ConnectContext.class), EasyMock.anyString(), + EasyMock.anyString(), EasyMock.isA(PrivPredicate.class))) .andReturn(false).anyTimes(); - EasyMock.replay(userPropertyMgr); - return userPropertyMgr; + EasyMock.replay(auth); + return auth; } - public static OlapTable mockTableFamilyGroup(String name) { + public static OlapTable mockTable(String name) { OlapTable table = EasyMock.createMock(OlapTable.class); Partition partition = EasyMock.createMock(Partition.class); MaterializedIndex index = EasyMock.createMock(MaterializedIndex.class); @@ -150,7 +151,7 @@ public class AccessTestUtil { public static Database mockDb(String name) { Database db = EasyMock.createMock(Database.class); - OlapTable olapTable = mockTableFamilyGroup("testTable"); + OlapTable olapTable = mockTable("testTable"); EasyMock.expect(db.getTable("testTable")).andReturn(olapTable).anyTimes(); EasyMock.expect(db.getTable("emptyTable")).andReturn(null).anyTimes(); EasyMock.expect(db.getTableNamesWithLock()).andReturn(Sets.newHashSet("testTable")).anyTimes(); @@ -169,7 +170,7 @@ public class AccessTestUtil { public static Catalog fetchBlockCatalog() { try { Catalog catalog = EasyMock.createMock(Catalog.class); - EasyMock.expect(catalog.getUserMgr()).andReturn(fetchBlockAccess()).anyTimes(); + EasyMock.expect(catalog.getAuth()).andReturn(fetchBlockAccess()).anyTimes(); catalog.changeDb(EasyMock.isA(ConnectContext.class), EasyMock.isA(String.class)); EasyMock.expectLastCall().andThrow(new DdlException("failed.")).anyTimes(); @@ -197,17 +198,12 @@ public class AccessTestUtil { } Analyzer analyzer = EasyMock.createMock(Analyzer.class); EasyMock.expect(analyzer.getDefaultDb()).andReturn(prefix + "testDb").anyTimes(); - EasyMock.expect(analyzer.getUser()).andReturn(prefix + "testUser").anyTimes(); + EasyMock.expect(analyzer.getQualifiedUser()).andReturn(prefix + "testUser").anyTimes(); EasyMock.expect(analyzer.getCatalog()).andReturn(fetchAdminCatalog()).anyTimes(); EasyMock.expect(analyzer.getClusterName()).andReturn("testCluster").anyTimes(); - - try { - analyzer.checkPrivilege(EasyMock.isA(String.class), EasyMock.isA(AccessPrivilege.class)); - } catch (AnalysisException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - EasyMock.expectLastCall().anyTimes(); + EasyMock.expect(analyzer.incrementCallDepth()).andReturn(1).anyTimes(); + EasyMock.expect(analyzer.decrementCallDepth()).andReturn(0).anyTimes(); + EasyMock.expect(analyzer.getCallDepth()).andReturn(1).anyTimes(); EasyMock.expect(analyzer.getContext()).andReturn(new ConnectContext(null)).anyTimes(); EasyMock.replay(analyzer); return analyzer; @@ -216,11 +212,9 @@ public class AccessTestUtil { public static Analyzer fetchBlockAnalyzer() throws AnalysisException { Analyzer analyzer = EasyMock.createMock(Analyzer.class); EasyMock.expect(analyzer.getDefaultDb()).andReturn("testCluster:testDb").anyTimes(); - EasyMock.expect(analyzer.getUser()).andReturn("testCluster:testUser").anyTimes(); + EasyMock.expect(analyzer.getQualifiedUser()).andReturn("testCluster:testUser").anyTimes(); EasyMock.expect(analyzer.getClusterName()).andReturn("testCluster").anyTimes(); EasyMock.expect(analyzer.getCatalog()).andReturn(AccessTestUtil.fetchBlockCatalog()).anyTimes(); - analyzer.checkPrivilege(EasyMock.isA(String.class), EasyMock.isA(AccessPrivilege.class)); - EasyMock.expectLastCall().andThrow(new AnalysisException("")); EasyMock.replay(analyzer); return analyzer; } @@ -228,7 +222,7 @@ public class AccessTestUtil { public static Analyzer fetchEmptyDbAnalyzer() { Analyzer analyzer = EasyMock.createMock(Analyzer.class); EasyMock.expect(analyzer.getDefaultDb()).andReturn("").anyTimes(); - EasyMock.expect(analyzer.getUser()).andReturn("testCluster:testUser").anyTimes(); + EasyMock.expect(analyzer.getQualifiedUser()).andReturn("testCluster:testUser").anyTimes(); EasyMock.expect(analyzer.getClusterName()).andReturn("testCluster").anyTimes(); EasyMock.expect(analyzer.getCatalog()).andReturn(AccessTestUtil.fetchBlockCatalog()).anyTimes(); EasyMock.expect(analyzer.getContext()).andReturn(new ConnectContext(null)).anyTimes(); diff --git a/fe/test/com/baidu/palo/analysis/AlterClusterStmtTest.java b/fe/test/com/baidu/palo/analysis/AlterClusterStmtTest.java index 60c7d8d758..31b7534639 100644 --- a/fe/test/com/baidu/palo/analysis/AlterClusterStmtTest.java +++ b/fe/test/com/baidu/palo/analysis/AlterClusterStmtTest.java @@ -20,24 +20,40 @@ package com.baidu.palo.analysis; -import java.util.HashMap; -import java.util.Map; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.MockedAuth; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.qe.ConnectContext; import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import com.baidu.palo.analysis.AlterClusterStmt; -import com.baidu.palo.common.AnalysisException; -import com.baidu.palo.common.InternalException; +import java.util.HashMap; +import java.util.Map; + +import mockit.Mocked; +import mockit.internal.startup.Startup; public class AlterClusterStmtTest { private static Analyzer analyzer; + @Mocked + private PaloAuth auth; + @Mocked + private ConnectContext ctx; + + static { + Startup.initializeIfPossible(); + } + @Before() public void setUp() { analyzer = AccessTestUtil.fetchAdminAnalyzer(true); + MockedAuth.mockedAuth(auth); + MockedAuth.mockedConnectContext(ctx, "root", "192.168.1.1"); } @Test @@ -67,13 +83,4 @@ public class AlterClusterStmtTest { Assert.fail("no exception"); } - @Test(expected = AnalysisException.class) - public void testNoPriv() throws InternalException, AnalysisException { - final Map properties = new HashMap(); - properties.put("instance_num", "2"); - final AlterClusterStmt stmt = new AlterClusterStmt("testCluster1", properties); - stmt.analyze(AccessTestUtil.fetchBlockAnalyzer()); - Assert.fail("no exception"); - } - } diff --git a/fe/test/com/baidu/palo/analysis/AlterTableStmtTest.java b/fe/test/com/baidu/palo/analysis/AlterTableStmtTest.java index 15dd3c29a6..1bdd894b4e 100644 --- a/fe/test/com/baidu/palo/analysis/AlterTableStmtTest.java +++ b/fe/test/com/baidu/palo/analysis/AlterTableStmtTest.java @@ -22,6 +22,9 @@ package com.baidu.palo.analysis; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; import com.google.common.collect.Lists; @@ -31,12 +34,36 @@ import org.junit.Test; import java.util.List; +import mockit.Mocked; +import mockit.NonStrictExpectations; +import mockit.internal.startup.Startup; + public class AlterTableStmtTest { private Analyzer analyzer; + @Mocked + private PaloAuth auth; + + static { + Startup.initializeIfPossible(); + } + @Before public void setUp() { analyzer = AccessTestUtil.fetchAdminAnalyzer(false); + + new NonStrictExpectations() { + { + auth.checkGlobalPriv((ConnectContext) any, (PrivPredicate) any); + result = true; + + auth.checkDbPriv((ConnectContext) any, anyString, (PrivPredicate) any); + result = true; + + auth.checkTblPriv((ConnectContext) any, anyString, anyString, (PrivPredicate) any); + result = true; + } + }; } @Test @@ -52,13 +79,12 @@ public class AlterTableStmtTest { Assert.assertEquals(2, stmt.getOps().size()); } - @Test(expected = AnalysisException.class) + @Test public void testNoPriv() throws AnalysisException, InternalException { List ops = Lists.newArrayList(); ops.add(new DropColumnClause("col1", "", null)); AlterTableStmt stmt = new AlterTableStmt(new TableName("testDb", "testTbl"), ops); stmt.analyze(AccessTestUtil.fetchBlockAnalyzer()); - Assert.assertEquals("ALTER TABLE `testDb`.`testTbl` DROP COLUMN `col1`", stmt.toString()); } @Test(expected = AnalysisException.class) diff --git a/fe/test/com/baidu/palo/analysis/BackendStmtTest.java b/fe/test/com/baidu/palo/analysis/BackendStmtTest.java index 6d3ba46ea1..07fb82eb70 100644 --- a/fe/test/com/baidu/palo/analysis/BackendStmtTest.java +++ b/fe/test/com/baidu/palo/analysis/BackendStmtTest.java @@ -20,12 +20,12 @@ package com.baidu.palo.analysis; -import com.baidu.palo.common.AnalysisException; - -import com.google.common.collect.Lists; - -import org.junit.Assert; -import org.junit.BeforeClass; +import com.baidu.palo.common.AnalysisException; + +import com.google.common.collect.Lists; + +import org.junit.Assert; +import org.junit.BeforeClass; import org.junit.Test; public class BackendStmtTest { @@ -88,7 +88,7 @@ public class BackendStmtTest { public void initBackendsTest4() throws Exception { BackendClause stmt = createStmt(4); stmt.analyze(analyzer); - Assert.assertEquals("ADD BACKEND \"192.168.1.1:12345\"", stmt.toSql()); + Assert.assertEquals("ADD FREE BACKEND \"192.168.1.1:12345\"", stmt.toSql()); } @Test diff --git a/fe/test/com/baidu/palo/analysis/CancelAlterStmtTest.java b/fe/test/com/baidu/palo/analysis/CancelAlterStmtTest.java index 0935d82f86..3ff288b545 100644 --- a/fe/test/com/baidu/palo/analysis/CancelAlterStmtTest.java +++ b/fe/test/com/baidu/palo/analysis/CancelAlterStmtTest.java @@ -20,6 +20,13 @@ package com.baidu.palo.analysis; +import com.baidu.palo.analysis.ShowAlterStmt.AlterType; +import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.qe.ConnectContext; + import org.easymock.EasyMock; import org.junit.Assert; import org.junit.Before; @@ -28,32 +35,42 @@ import org.junit.runner.RunWith; import org.powermock.api.easymock.PowerMock; import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; - -import com.baidu.palo.analysis.ShowAlterStmt.AlterType; -import com.baidu.palo.catalog.Catalog; -import com.baidu.palo.common.AnalysisException; -import com.baidu.palo.common.InternalException; +import org.powermock.modules.junit4.PowerMockRunner; @RunWith(PowerMockRunner.class) @PowerMockIgnore("org.apache.log4j.*") -@PrepareForTest(Catalog.class) +@PrepareForTest({ Catalog.class, ConnectContext.class }) public class CancelAlterStmtTest { private Analyzer analyzer; - private Catalog catalog; + private Catalog catalog; + + private ConnectContext ctx; + + private PaloAuth auth; @Before - public void setUp() { + public void setUp() { + auth = new PaloAuth(); + + ctx = new ConnectContext(null); + ctx.setQualifiedUser("root"); + ctx.setRemoteIP("192.168.1.1"); + catalog = AccessTestUtil.fetchAdminCatalog(); PowerMock.mockStatic(Catalog.class); - EasyMock.expect(Catalog.getInstance()).andReturn(catalog).anyTimes(); - PowerMock.replay(Catalog.class); + EasyMock.expect(Catalog.getInstance()).andReturn(catalog).anyTimes(); + EasyMock.expect(Catalog.getCurrentCatalog()).andReturn(catalog).anyTimes(); + PowerMock.replay(Catalog.class); + + PowerMock.mockStatic(ConnectContext.class); + EasyMock.expect(ConnectContext.get()).andReturn(ctx).anyTimes(); + PowerMock.replay(ConnectContext.class); analyzer = EasyMock.createMock(Analyzer.class); EasyMock.expect(analyzer.getDefaultDb()).andReturn("testDb").anyTimes(); - EasyMock.expect(analyzer.getUser()).andReturn("testUser").anyTimes(); + EasyMock.expect(analyzer.getQualifiedUser()).andReturn("testUser").anyTimes(); EasyMock.expect(analyzer.getCatalog()).andReturn(catalog).anyTimes(); EasyMock.replay(analyzer); } @@ -61,7 +78,6 @@ public class CancelAlterStmtTest { @Test public void testNormal() throws InternalException, AnalysisException { // cancel alter column - CancelAlterTableStmt stmt = new CancelAlterTableStmt(AlterType.COLUMN, new TableName(null, "testTbl")); stmt.analyze(analyzer); Assert.assertEquals("CANCEL ALTER COLUMN FROM `testDb`.`testTbl`", stmt.toString()); diff --git a/fe/test/com/baidu/palo/analysis/CreateClusterStmtTest.java b/fe/test/com/baidu/palo/analysis/CreateClusterStmtTest.java index 24cd7a063f..ccfee40873 100644 --- a/fe/test/com/baidu/palo/analysis/CreateClusterStmtTest.java +++ b/fe/test/com/baidu/palo/analysis/CreateClusterStmtTest.java @@ -20,25 +20,40 @@ package com.baidu.palo.analysis; -import java.util.Map; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.MockedAuth; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.qe.ConnectContext; import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import com.baidu.palo.analysis.CreateClusterStmt; -import com.baidu.palo.common.AnalysisException; -import com.baidu.palo.common.InternalException; import java.util.HashMap; import java.util.Map; +import mockit.Mocked; +import mockit.internal.startup.Startup; + public class CreateClusterStmtTest { private static Analyzer analyzer; + @Mocked + private PaloAuth auth; + @Mocked + private ConnectContext ctx; + + static { + Startup.initializeIfPossible(); + } + @Before() public void setUp() { analyzer = AccessTestUtil.fetchAdminAnalyzer(true); + MockedAuth.mockedAuth(auth); + MockedAuth.mockedConnectContext(ctx, "root", "192.168.1.1"); } @Test @@ -59,14 +74,4 @@ public class CreateClusterStmtTest { stmt.analyze(analyzer); Assert.fail("no exception"); } - - @Test(expected = AnalysisException.class) - public void testNoPriv() throws InternalException, AnalysisException { - final Map properties = new HashMap(); - properties.put("instance_num", "2"); - final CreateClusterStmt stmt = new CreateClusterStmt("testCluster1", properties, "password"); - stmt.analyze(AccessTestUtil.fetchBlockAnalyzer()); - Assert.fail("no exception"); - } - } diff --git a/fe/test/com/baidu/palo/analysis/CreateDbStmtTest.java b/fe/test/com/baidu/palo/analysis/CreateDbStmtTest.java index ddf31a2dfc..90b8468838 100644 --- a/fe/test/com/baidu/palo/analysis/CreateDbStmtTest.java +++ b/fe/test/com/baidu/palo/analysis/CreateDbStmtTest.java @@ -22,17 +22,34 @@ package com.baidu.palo.analysis; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.MockedAuth; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.qe.ConnectContext; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import mockit.Mocked; +import mockit.internal.startup.Startup; + public class CreateDbStmtTest { private Analyzer analyzer; + @Mocked + private PaloAuth auth; + @Mocked + private ConnectContext ctx; + + static { + Startup.initializeIfPossible(); + } + @Before() public void setUp() { analyzer = AccessTestUtil.fetchAdminAnalyzer(true); + MockedAuth.mockedAuth(auth); + MockedAuth.mockedConnectContext(ctx, "root", "192.168.1.1"); } @Test @@ -49,12 +66,4 @@ public class CreateDbStmtTest { stmt.analyze(analyzer); Assert.fail("no exception"); } - - @Test(expected = AnalysisException.class) - public void testNoPriv() throws InternalException, AnalysisException { - CreateDbStmt stmt = new CreateDbStmt(false, "testDb"); - stmt.analyze(AccessTestUtil.fetchBlockAnalyzer()); - Assert.fail("no exception"); - } - } \ No newline at end of file diff --git a/fe/test/com/baidu/palo/analysis/CreateTableStmtTest.java b/fe/test/com/baidu/palo/analysis/CreateTableStmtTest.java index 0bd473c746..a8415d261d 100644 --- a/fe/test/com/baidu/palo/analysis/CreateTableStmtTest.java +++ b/fe/test/com/baidu/palo/analysis/CreateTableStmtTest.java @@ -21,7 +21,17 @@ package com.baidu.palo.analysis; -import java.util.List; +import com.baidu.palo.catalog.Column; +import com.baidu.palo.catalog.ColumnType; +import com.baidu.palo.catalog.KeysType; +import com.baidu.palo.catalog.PrimitiveType; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.MockedAuth; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.qe.ConnectContext; + +import com.google.common.collect.Lists; import org.easymock.EasyMock; import org.junit.Assert; @@ -30,13 +40,10 @@ import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.baidu.palo.catalog.Column; -import com.baidu.palo.catalog.ColumnType; -import com.baidu.palo.catalog.KeysType; -import com.baidu.palo.catalog.PrimitiveType; -import com.baidu.palo.common.AnalysisException; -import com.baidu.palo.common.InternalException; -import com.google.common.collect.Lists; +import java.util.List; + +import mockit.Mocked; +import mockit.internal.startup.Startup; public class CreateTableStmtTest { private static final Logger LOG = LoggerFactory.getLogger(CreateTableStmtTest.class); @@ -50,6 +57,15 @@ public class CreateTableStmtTest { private List invalidColsName; private Analyzer analyzer; + @Mocked + private PaloAuth auth; + @Mocked + private ConnectContext ctx; + + static { + Startup.initializeIfPossible(); + } + // set default db is 'db1' // table name is table1 // Column: [col1 int; col2 string] @@ -76,38 +92,32 @@ public class CreateTableStmtTest { invalidColsName.add("col1"); invalidColsName.add("col2"); invalidColsName.add("col2"); + + MockedAuth.mockedAuth(auth); + MockedAuth.mockedConnectContext(ctx, "root", "192.168.1.1"); } @Test public void testNormal() throws InternalException, AnalysisException { CreateTableStmt stmt = new CreateTableStmt(false, false, tblName, cols, "olap", new KeysDesc(KeysType.AGG_KEYS, colsName), null, - new RandomDistributionDesc(10), null, null); + new HashDistributionDesc(10, Lists.newArrayList("col1")), null, null); stmt.analyze(analyzer); Assert.assertEquals("testCluster:db1", stmt.getDbName()); Assert.assertEquals("table1", stmt.getTableName()); Assert.assertNull(stmt.getProperties()); - LOG.info(stmt.toSql()); - Assert.assertEquals("CREATE TABLE `testCluster:db1`.`table1` (\n" - + "`col1` int(11) NOT NULL COMMENT \"\",\n" + "`col2` char(10) NOT NULL COMMENT \"\"\n" - + ") ENGINE = olap\nAGG_KEYS(`col1`, `col2`)\nDISTRIBUTED BY RANDOM\nBUCKETS 10", - stmt.toSql()); } @Test public void testDefaultDbNormal() throws InternalException, AnalysisException { CreateTableStmt stmt = new CreateTableStmt(false, false, tblNameNoDb, cols, "olap", new KeysDesc(KeysType.AGG_KEYS, colsName), null, - new RandomDistributionDesc(10), null, null); + new HashDistributionDesc(10, Lists.newArrayList("col1")), null, null); stmt.analyze(analyzer); - Assert.assertEquals("testCluster:testDb", stmt.getDbName()); + Assert.assertEquals("testDb", stmt.getDbName()); Assert.assertEquals("table1", stmt.getTableName()); Assert.assertNull(stmt.getPartitionDesc()); Assert.assertNull(stmt.getProperties()); - LOG.info(stmt.toSql()); - Assert.assertEquals("CREATE TABLE `testCluster:testDb`.`table1` (\n" - + "`col1` int(11) NOT NULL COMMENT \"\",\n" + "`col2` char(10) NOT NULL COMMENT \"\"\n" - + ") ENGINE = olap\nAGG_KEYS(`col1`, `col2`)\nDISTRIBUTED BY RANDOM\nBUCKETS 10", stmt.toSql()); } @Test(expected = AnalysisException.class) @@ -115,6 +125,7 @@ public class CreateTableStmtTest { // make defalut db return empty; analyzer = EasyMock.createMock(Analyzer.class); EasyMock.expect(analyzer.getDefaultDb()).andReturn("").anyTimes(); + EasyMock.expect(analyzer.getClusterName()).andReturn("cluster").anyTimes(); EasyMock.replay(analyzer); CreateTableStmt stmt = new CreateTableStmt(false, false, tblNameNoDb, cols, "olap", new KeysDesc(KeysType.AGG_KEYS, colsName), null, @@ -141,14 +152,4 @@ public class CreateTableStmtTest { stmt.analyze(analyzer); } - @Test(expected = AnalysisException.class) - public void testNoPriv() throws InternalException, AnalysisException { - // make default db return empty; - CreateTableStmt stmt = new CreateTableStmt(false, false, tblNameNoDb, cols, "olap", - new KeysDesc(KeysType.AGG_KEYS, colsName), null, - new RandomDistributionDesc(10), null, null); - stmt.analyze(AccessTestUtil.fetchBlockAnalyzer()); - Assert.fail("No exception throws."); - } - } \ No newline at end of file diff --git a/fe/test/com/baidu/palo/analysis/CreateUserStmtTest.java b/fe/test/com/baidu/palo/analysis/CreateUserStmtTest.java index b1fc908d68..4d90fa9a11 100644 --- a/fe/test/com/baidu/palo/analysis/CreateUserStmtTest.java +++ b/fe/test/com/baidu/palo/analysis/CreateUserStmtTest.java @@ -22,60 +22,71 @@ package com.baidu.palo.analysis; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.MockedAuth; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.qe.ConnectContext; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import mockit.Mocked; +import mockit.internal.startup.Startup; + public class CreateUserStmtTest { private Analyzer analyzer; + @Mocked + private PaloAuth auth; + @Mocked + private ConnectContext ctx; + + static { + Startup.initializeIfPossible(); + } + @Before public void setUp() { analyzer = AccessTestUtil.fetchAdminAnalyzer(true); + MockedAuth.mockedAuth(auth); + MockedAuth.mockedConnectContext(ctx, "root", "192.168.1.1"); } @Test public void testToString() throws InternalException, AnalysisException { - CreateUserStmt stmt = new CreateUserStmt(new UserDesc("user", "passwd", true)); + CreateUserStmt stmt = new CreateUserStmt(new UserDesc(new UserIdentity("user", "%"), "passwd", true)); stmt.analyze(analyzer); - Assert.assertEquals("CREATE USER 'testCluster:user' IDENTIFIED BY 'passwd'", stmt.toString()); + Assert.assertEquals("CREATE USER 'testCluster:user'@'%' IDENTIFIED BY 'passwd'", stmt.toString()); Assert.assertEquals(new String(stmt.getPassword()), "*59C70DA2F3E3A5BDF46B68F5C8B8F25762BCCEF0"); - stmt = new CreateUserStmt(new UserDesc("user", "*59c70da2f3e3a5bdf46b68f5c8b8f25762bccef0", false)); + stmt = new CreateUserStmt( + new UserDesc(new UserIdentity("user", "%"), "*59c70da2f3e3a5bdf46b68f5c8b8f25762bccef0", false)); stmt.analyze(analyzer); - Assert.assertEquals("testCluster:user", stmt.getUser()); + Assert.assertEquals("testCluster:user", stmt.getUserIdent().getQualifiedUser()); Assert.assertEquals( - "CREATE USER 'testCluster:user' IDENTIFIED BY PASSWORD '*59c70da2f3e3a5bdf46b68f5c8b8f25762bccef0'", + "CREATE USER 'testCluster:user'@'%' IDENTIFIED BY PASSWORD '*59c70da2f3e3a5bdf46b68f5c8b8f25762bccef0'", stmt.toString()); Assert.assertEquals(new String(stmt.getPassword()), "*59C70DA2F3E3A5BDF46B68F5C8B8F25762BCCEF0"); - stmt = new CreateUserStmt(new UserDesc("user", "", false)); + stmt = new CreateUserStmt(new UserDesc(new UserIdentity("user", "%"), "", false)); stmt.analyze(analyzer); - Assert.assertEquals("CREATE USER 'testCluster:user'", stmt.toString()); + Assert.assertEquals("CREATE USER 'testCluster:user'@'%'", stmt.toString()); Assert.assertEquals(new String(stmt.getPassword()), ""); } - @Test(expected = AnalysisException.class) - public void testNoPriv() throws InternalException, AnalysisException { - CreateUserStmt stmt = new CreateUserStmt(new UserDesc("user", "passwd", true)); - stmt.analyze(AccessTestUtil.fetchBlockAnalyzer()); - Assert.fail("No exception throws."); - } - @Test(expected = AnalysisException.class) public void testEmptyUser() throws InternalException, AnalysisException { - CreateUserStmt stmt = new CreateUserStmt(new UserDesc("", "passwd", true)); + CreateUserStmt stmt = new CreateUserStmt(new UserDesc(new UserIdentity("", "%"), "passwd", true)); stmt.analyze(analyzer); Assert.fail("No exception throws."); } @Test(expected = AnalysisException.class) public void testBadPass() throws InternalException, AnalysisException { - CreateUserStmt stmt = new CreateUserStmt(new UserDesc("", "passwd", false)); + CreateUserStmt stmt = new CreateUserStmt(new UserDesc(new UserIdentity("", "%"), "passwd", false)); stmt.analyze(analyzer); Assert.fail("No exception throws."); } diff --git a/fe/test/com/baidu/palo/analysis/DataDescriptionTest.java b/fe/test/com/baidu/palo/analysis/DataDescriptionTest.java index f2361a929b..b36323c725 100644 --- a/fe/test/com/baidu/palo/analysis/DataDescriptionTest.java +++ b/fe/test/com/baidu/palo/analysis/DataDescriptionTest.java @@ -22,32 +22,56 @@ package com.baidu.palo.analysis; import com.baidu.palo.analysis.BinaryPredicate.Operator; import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.mysql.privilege.MockedAuth; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.qe.ConnectContext; + import com.google.common.collect.Lists; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import java.util.List; +import mockit.Mocked; +import mockit.internal.startup.Startup; + public class DataDescriptionTest { + + @Mocked + private PaloAuth auth; + @Mocked + private ConnectContext ctx; + + static { + Startup.initializeIfPossible(); + } + + @Before + public void setUp() { + MockedAuth.mockedAuth(auth); + MockedAuth.mockedConnectContext(ctx, "root", "192.168.1.1"); + } + @Test public void testNormal() throws AnalysisException { DataDescription desc = new DataDescription("testTable", null, Lists.newArrayList("abc.txt"), null, null, false, null); - desc.analyze(); + desc.analyze("testDb"); Assert.assertEquals("DATA INFILE ('abc.txt') INTO TABLE testTable", desc.toString()); desc = new DataDescription("testTable", null, Lists.newArrayList("abc.txt"), null, null, true, null); - desc.analyze(); + desc.analyze("testDb"); Assert.assertEquals("DATA INFILE ('abc.txt') NEGATIVE INTO TABLE testTable", desc.toString()); desc = new DataDescription("testTable", null, Lists.newArrayList("abc.txt", "bcd.txt"), null, null, true, null); - desc.analyze(); + desc.analyze("testDb"); Assert.assertEquals("DATA INFILE ('abc.txt', 'bcd.txt') NEGATIVE INTO TABLE testTable", desc.toString()); desc = new DataDescription("testTable", null, Lists.newArrayList("abc.txt"), Lists.newArrayList("col1", "col2"), null, true, null); - desc.analyze(); + desc.analyze("testDb"); Assert.assertEquals("DATA INFILE ('abc.txt') NEGATIVE INTO TABLE testTable (col1, col2)", desc.toString()); Assert.assertEquals("testTable", desc.getTableName()); Assert.assertEquals("[col1, col2]", desc.getColumnNames().toString()); @@ -57,7 +81,7 @@ public class DataDescriptionTest { desc = new DataDescription("testTable", null, Lists.newArrayList("abc.txt", "bcd.txt"), Lists.newArrayList("col1", "col2"), new ColumnSeparator("\t"), true, null); - desc.analyze(); + desc.analyze("testDb"); Assert.assertEquals("DATA INFILE ('abc.txt', 'bcd.txt') NEGATIVE INTO TABLE testTable" + " COLUMNS TERMINATED BY '\t' (col1, col2)", desc.toString()); @@ -65,7 +89,7 @@ public class DataDescriptionTest { // hive \x01 column separator desc = new DataDescription("testTable", null, Lists.newArrayList("abc.txt", "bcd.txt"), Lists.newArrayList("col1", "col2"), new ColumnSeparator("\\x01"), true, null); - desc.analyze(); + desc.analyze("testDb"); Assert.assertEquals("DATA INFILE ('abc.txt', 'bcd.txt') NEGATIVE INTO TABLE testTable" + " COLUMNS TERMINATED BY '\\x01' (col1, col2)", desc.toString()); @@ -73,7 +97,7 @@ public class DataDescriptionTest { // with partition desc = new DataDescription("testTable", Lists.newArrayList("p1", "p2"), Lists.newArrayList("abc.txt"), null, null, false, null); - desc.analyze(); + desc.analyze("testDb"); Assert.assertEquals("DATA INFILE ('abc.txt') INTO TABLE testTable PARTITION (p1, p2)", desc.toString()); // alignment_timestamp func @@ -84,7 +108,7 @@ public class DataDescriptionTest { new FunctionCallExpr("alignment_timestamp", params)); desc = new DataDescription("testTable", Lists.newArrayList("p1", "p2"), Lists.newArrayList("abc.txt"), Lists.newArrayList("k2", "k3"), null, false, Lists.newArrayList((Expr) predicate)); - desc.analyze(); + desc.analyze("testDb"); String sql = "DATA INFILE ('abc.txt') INTO TABLE testTable PARTITION (p1, p2) (k2, k3)" + " SET (`k1` = alignment_timestamp('day', `k2`))"; Assert.assertEquals(sql, desc.toString()); @@ -97,7 +121,7 @@ public class DataDescriptionTest { new FunctionCallExpr("replace_value", params)); desc = new DataDescription("testTable", Lists.newArrayList("p1", "p2"), Lists.newArrayList("abc.txt"), Lists.newArrayList("k2", "k3"), null, false, Lists.newArrayList((Expr) predicate)); - desc.analyze(); + desc.analyze("testDb"); sql = "DATA INFILE ('abc.txt') INTO TABLE testTable PARTITION (p1, p2) (k2, k3)" + " SET (`k1` = replace_value('-', '10'))"; Assert.assertEquals(sql, desc.toString()); @@ -110,7 +134,7 @@ public class DataDescriptionTest { new FunctionCallExpr("replace_value", params)); desc = new DataDescription("testTable", Lists.newArrayList("p1", "p2"), Lists.newArrayList("abc.txt"), Lists.newArrayList("k2", "k3"), null, false, Lists.newArrayList((Expr) predicate)); - desc.analyze(); + desc.analyze("testDb"); sql = "DATA INFILE ('abc.txt') INTO TABLE testTable PARTITION (p1, p2) (k2, k3)" + " SET (`k1` = replace_value('', NULL))"; Assert.assertEquals(sql, desc.toString()); @@ -120,19 +144,19 @@ public class DataDescriptionTest { public void testNoTable() throws AnalysisException { DataDescription desc = new DataDescription("", null, Lists.newArrayList("abc.txt"), null, null, false, null); - desc.analyze(); + desc.analyze("testDb"); } @Test(expected = AnalysisException.class) public void testNoFile() throws AnalysisException { DataDescription desc = new DataDescription("testTable", null, null, null, null, false, null); - desc.analyze(); + desc.analyze("testDb"); } @Test(expected = AnalysisException.class) public void testDupCol() throws AnalysisException { DataDescription desc = new DataDescription("testTable", null, Lists.newArrayList("abc.txt"), Lists.newArrayList("col1", "col1"), null, false, null); - desc.analyze(); + desc.analyze("testDb"); } } \ No newline at end of file diff --git a/fe/test/com/baidu/palo/analysis/DeleteStmtTest.java b/fe/test/com/baidu/palo/analysis/DeleteStmtTest.java index 0816d40cb8..db911570c2 100644 --- a/fe/test/com/baidu/palo/analysis/DeleteStmtTest.java +++ b/fe/test/com/baidu/palo/analysis/DeleteStmtTest.java @@ -20,21 +20,38 @@ package com.baidu.palo.analysis; -import com.baidu.palo.analysis.BinaryPredicate.Operator; -import com.baidu.palo.common.AnalysisException; -import com.baidu.palo.common.InternalException; - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; +import com.baidu.palo.analysis.BinaryPredicate.Operator; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.MockedAuth; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.qe.ConnectContext; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import mockit.Mocked; +import mockit.internal.startup.Startup; public class DeleteStmtTest { - Analyzer analyzer; + Analyzer analyzer; + + @Mocked + private PaloAuth auth; + @Mocked + private ConnectContext ctx; + + static { + Startup.initializeIfPossible(); + } @Before public void setUp() { - analyzer = AccessTestUtil.fetchAdminAnalyzer(false); + analyzer = AccessTestUtil.fetchAdminAnalyzer(false); + MockedAuth.mockedAuth(auth); + MockedAuth.mockedConnectContext(ctx, "root", "192.168.1.1"); } @Test diff --git a/fe/test/com/baidu/palo/analysis/DescribeStmtTest.java b/fe/test/com/baidu/palo/analysis/DescribeStmtTest.java index a2f75a0c2d..4e89e16d3b 100644 --- a/fe/test/com/baidu/palo/analysis/DescribeStmtTest.java +++ b/fe/test/com/baidu/palo/analysis/DescribeStmtTest.java @@ -23,6 +23,7 @@ package com.baidu.palo.analysis; import com.baidu.palo.catalog.Catalog; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.InternalException; +import com.baidu.palo.qe.ConnectContext; import org.easymock.EasyMock; import org.junit.Assert; @@ -36,19 +37,30 @@ import org.powermock.modules.junit4.PowerMockRunner; @RunWith(PowerMockRunner.class) @PowerMockIgnore("org.apache.log4j.*") -@PrepareForTest(Catalog.class) +@PrepareForTest({ Catalog.class, ConnectContext.class }) public class DescribeStmtTest { private Analyzer analyzer; private Catalog catalog; + private ConnectContext ctx; @Before public void setUp() { + ctx = new ConnectContext(null); + ctx.setQualifiedUser("root"); + ctx.setRemoteIP("192.168.1.1"); + + PowerMock.mockStatic(ConnectContext.class); + EasyMock.expect(ConnectContext.get()).andReturn(ctx).anyTimes(); + PowerMock.replay(ConnectContext.class); + analyzer = AccessTestUtil.fetchAdminAnalyzer(true); catalog = AccessTestUtil.fetchAdminCatalog(); PowerMock.mockStatic(Catalog.class); EasyMock.expect(Catalog.getInstance()).andReturn(catalog).anyTimes(); + EasyMock.expect(Catalog.getCurrentCatalog()).andReturn(catalog).anyTimes(); EasyMock.expect(Catalog.getCurrentSystemInfo()).andReturn(AccessTestUtil.fetchSystemInfoService()).anyTimes(); PowerMock.replay(Catalog.class); + } @Test @@ -70,11 +82,4 @@ public class DescribeStmtTest { Assert.assertEquals("testCluster:testDb", stmt.getDb()); Assert.assertEquals("testTbl", stmt.getTableName()); } - - @Test(expected = AnalysisException.class) - public void testNoPriv() throws AnalysisException, InternalException { - DescribeStmt stmt = new DescribeStmt(new TableName("", "testTable"), false); - stmt.analyze(AccessTestUtil.fetchBlockAnalyzer()); - Assert.fail("No exception throws."); - } } \ No newline at end of file diff --git a/fe/test/com/baidu/palo/analysis/DropClusterStmtTest.java b/fe/test/com/baidu/palo/analysis/DropClusterStmtTest.java index 2107597760..a99b50c9df 100644 --- a/fe/test/com/baidu/palo/analysis/DropClusterStmtTest.java +++ b/fe/test/com/baidu/palo/analysis/DropClusterStmtTest.java @@ -20,20 +20,47 @@ package com.baidu.palo.analysis; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; + import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import com.baidu.palo.common.AnalysisException; -import com.baidu.palo.common.InternalException; +import mockit.Mocked; +import mockit.NonStrictExpectations; +import mockit.internal.startup.Startup; public class DropClusterStmtTest { private static Analyzer analyzer; + @Mocked + private PaloAuth auth; + + static { + Startup.initializeIfPossible(); + } + @Before public void setUp() { analyzer = AccessTestUtil.fetchAdminAnalyzer(true); + + new NonStrictExpectations() { + { + auth.checkGlobalPriv((ConnectContext) any, (PrivPredicate) any); + result = true; + + auth.checkDbPriv((ConnectContext) any, anyString, (PrivPredicate) any); + result = true; + + auth.checkTblPriv((ConnectContext) any, anyString, anyString, (PrivPredicate) any); + result = true; + } + }; } @Test diff --git a/fe/test/com/baidu/palo/analysis/DropDbStmtTest.java b/fe/test/com/baidu/palo/analysis/DropDbStmtTest.java index 5ae625d72a..7715463c7f 100644 --- a/fe/test/com/baidu/palo/analysis/DropDbStmtTest.java +++ b/fe/test/com/baidu/palo/analysis/DropDbStmtTest.java @@ -22,17 +22,34 @@ package com.baidu.palo.analysis; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.MockedAuth; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.qe.ConnectContext; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import mockit.Mocked; +import mockit.internal.startup.Startup; + public class DropDbStmtTest { Analyzer analyzer; + @Mocked + private PaloAuth auth; + @Mocked + private ConnectContext ctx; + + static { + Startup.initializeIfPossible(); + } + @Before public void setUp() { analyzer = AccessTestUtil.fetchAdminAnalyzer(true); + MockedAuth.mockedAuth(auth); + MockedAuth.mockedConnectContext(ctx, "root", "192.168.1.1"); } @Test diff --git a/fe/test/com/baidu/palo/analysis/DropTableStmtTest.java b/fe/test/com/baidu/palo/analysis/DropTableStmtTest.java index ee263b62d8..6e77af01df 100644 --- a/fe/test/com/baidu/palo/analysis/DropTableStmtTest.java +++ b/fe/test/com/baidu/palo/analysis/DropTableStmtTest.java @@ -22,18 +22,33 @@ package com.baidu.palo.analysis; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.MockedAuth; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.qe.ConnectContext; -import org.junit.Assert; import org.easymock.EasyMock; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import mockit.Mocked; +import mockit.internal.startup.Startup; + public class DropTableStmtTest { private TableName tbl; private TableName noDbTbl; private Analyzer analyzer; private Analyzer noDbAnalyzer; + @Mocked + private PaloAuth auth; + @Mocked + private ConnectContext ctx; + + static { + Startup.initializeIfPossible(); + } + @Before public void setUp() { tbl = new TableName("db1", "table1"); @@ -44,6 +59,9 @@ public class DropTableStmtTest { EasyMock.expect(noDbAnalyzer.getDefaultDb()).andReturn("").anyTimes(); EasyMock.expect(noDbAnalyzer.getClusterName()).andReturn("testCluster").anyTimes(); EasyMock.replay(noDbAnalyzer); + + MockedAuth.mockedAuth(auth); + MockedAuth.mockedConnectContext(ctx, "root", "192.168.1.1"); } @Test @@ -64,13 +82,6 @@ public class DropTableStmtTest { Assert.assertEquals("DROP TABLE `testCluster:testDb`.`table1`", stmt.toSql()); } - @Test(expected = AnalysisException.class) - public void testNoPriv() throws InternalException, AnalysisException { - DropTableStmt stmt = new DropTableStmt(false, tbl); - stmt.analyze(AccessTestUtil.fetchBlockAnalyzer()); - Assert.fail("No exception throws"); - } - @Test(expected = AnalysisException.class) public void testNoDbFail() throws InternalException, AnalysisException { DropTableStmt stmt = new DropTableStmt(false, noDbTbl); diff --git a/fe/test/com/baidu/palo/analysis/DropUserStmtTest.java b/fe/test/com/baidu/palo/analysis/DropUserStmtTest.java index 8733885183..e90534bece 100644 --- a/fe/test/com/baidu/palo/analysis/DropUserStmtTest.java +++ b/fe/test/com/baidu/palo/analysis/DropUserStmtTest.java @@ -22,39 +22,48 @@ package com.baidu.palo.analysis; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.MockedAuth; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.qe.ConnectContext; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import mockit.Mocked; +import mockit.internal.startup.Startup; + public class DropUserStmtTest { private Analyzer analyzer; + + @Mocked + private PaloAuth auth; + @Mocked + private ConnectContext ctx; + + static { + Startup.initializeIfPossible(); + } @Before public void setUp() { analyzer = AccessTestUtil.fetchAdminAnalyzer(true); + MockedAuth.mockedAuth(auth); + MockedAuth.mockedConnectContext(ctx, "root", "192.168.1.1"); } @Test public void testNormal() throws InternalException, AnalysisException { - DropUserStmt stmt = new DropUserStmt("user"); + DropUserStmt stmt = new DropUserStmt(new UserIdentity("user", "%")); stmt.analyze(analyzer); - Assert.assertEquals("DROP USER 'testCluster:user'", stmt.toString()); - Assert.assertEquals("testCluster:user", stmt.getUser()); + Assert.assertEquals("DROP USER 'testCluster:user'@'%'", stmt.toString()); + Assert.assertEquals("testCluster:user", stmt.getUserIdentity().getQualifiedUser()); } @Test(expected = AnalysisException.class) public void testNoUser() throws InternalException, AnalysisException { - DropUserStmt stmt = new DropUserStmt(""); + DropUserStmt stmt = new DropUserStmt(new UserIdentity("", "%")); stmt.analyze(analyzer); Assert.fail("No Exception throws."); } - - @Test(expected = AnalysisException.class) - public void testNoPriv() throws InternalException, AnalysisException { - DropUserStmt stmt = new DropUserStmt("testUser"); - stmt.analyze(AccessTestUtil.fetchBlockAnalyzer()); - Assert.fail("No Exception throws."); - } - } \ No newline at end of file diff --git a/fe/test/com/baidu/palo/analysis/GrantStmtTest.java b/fe/test/com/baidu/palo/analysis/GrantStmtTest.java index 8b92fec349..a7a45ed647 100644 --- a/fe/test/com/baidu/palo/analysis/GrantStmtTest.java +++ b/fe/test/com/baidu/palo/analysis/GrantStmtTest.java @@ -21,21 +21,61 @@ package com.baidu.palo.analysis; import com.baidu.palo.catalog.AccessPrivilege; +import com.baidu.palo.catalog.Catalog; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.qe.ConnectContext; + import com.google.common.collect.Lists; + import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.util.List; +import mockit.Mocked; +import mockit.NonStrictExpectations; +import mockit.internal.startup.Startup; + public class GrantStmtTest { private Analyzer analyzer; + private PaloAuth auth; + @Mocked + private ConnectContext ctx; + + @Mocked + private Catalog catalog; + + static { + Startup.initializeIfPossible(); + } + @Before public void setUp() { analyzer = AccessTestUtil.fetchAdminAnalyzer(true); + auth = new PaloAuth(); + + new NonStrictExpectations() { + { + ConnectContext.get(); + result = ctx; + + ctx.getQualifiedUser(); + result = "root"; + + ctx.getRemoteIP(); + result = "192.168.0.1"; + + Catalog.getCurrentCatalog(); + result = catalog; + + catalog.getAuth(); + result = auth; + } + }; } @Test @@ -43,27 +83,14 @@ public class GrantStmtTest { GrantStmt stmt; List privileges = Lists.newArrayList(AccessPrivilege.ALL); - stmt = new GrantStmt("testUser", "testDb", privileges); + stmt = new GrantStmt(new UserIdentity("testUser", "%"), null, new TablePattern("testDb", "*"), privileges); stmt.analyze(analyzer); - Assert.assertEquals("GRANT ALL ON testCluster:testDb TO 'testCluster:testUser'", stmt.toString()); - Assert.assertEquals("testCluster:testUser", stmt.getUser()); - Assert.assertEquals("testCluster:testDb", stmt.getDb()); - Assert.assertEquals(AccessPrivilege.ALL, stmt.getPrivilege()); + Assert.assertEquals("testCluster:testUser", stmt.getUserIdent().getQualifiedUser()); + Assert.assertEquals("testCluster:testDb", stmt.getTblPattern().getQuolifiedDb()); privileges = Lists.newArrayList(AccessPrivilege.READ_ONLY, AccessPrivilege.ALL); - stmt = new GrantStmt("testUser", "testDb", privileges); + stmt = new GrantStmt(new UserIdentity("testUser", "%"), null, new TablePattern("testDb", "*"), privileges); stmt.analyze(analyzer); - Assert.assertEquals("GRANT READ_ONLY, ALL ON testCluster:testDb TO 'testCluster:testUser'", stmt.toString()); - } - - @Test(expected = AnalysisException.class) - public void testNoPriv() throws AnalysisException, InternalException { - GrantStmt stmt; - - List privileges = Lists.newArrayList(AccessPrivilege.ALL); - stmt = new GrantStmt("testUser", "testDb", privileges); - stmt.analyze(AccessTestUtil.fetchBlockAnalyzer()); - Assert.fail("No exception throws."); } @Test(expected = AnalysisException.class) @@ -71,26 +98,7 @@ public class GrantStmtTest { GrantStmt stmt; List privileges = Lists.newArrayList(AccessPrivilege.ALL); - stmt = new GrantStmt("", "testDb", privileges); - stmt.analyze(analyzer); - Assert.fail("No exeception throws."); - } - - @Test(expected = AnalysisException.class) - public void testDbFail() throws AnalysisException, InternalException { - GrantStmt stmt; - - List privileges = Lists.newArrayList(AccessPrivilege.ALL); - stmt = new GrantStmt("testUser", "", privileges); - stmt.analyze(analyzer); - Assert.fail("No exeception throws."); - } - - @Test(expected = AnalysisException.class) - public void testPrivFail() throws AnalysisException, InternalException { - GrantStmt stmt; - - stmt = new GrantStmt("testUser", "testDb", null); + stmt = new GrantStmt(new UserIdentity("", "%"), null, new TablePattern("testDb", "*"), privileges); stmt.analyze(analyzer); Assert.fail("No exeception throws."); } diff --git a/fe/test/com/baidu/palo/analysis/LinkDbStmtTest.java b/fe/test/com/baidu/palo/analysis/LinkDbStmtTest.java index 37dd14b77e..66762c2b3b 100644 --- a/fe/test/com/baidu/palo/analysis/LinkDbStmtTest.java +++ b/fe/test/com/baidu/palo/analysis/LinkDbStmtTest.java @@ -22,18 +22,35 @@ package com.baidu.palo.analysis; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.MockedAuth; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.qe.ConnectContext; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import mockit.Mocked; +import mockit.internal.startup.Startup; + public class LinkDbStmtTest { private static Analyzer analyzer; + @Mocked + private PaloAuth auth; + @Mocked + private ConnectContext ctx; + + static { + Startup.initializeIfPossible(); + } + @Before public void setUp() { analyzer = AccessTestUtil.fetchAdminAnalyzer(true); + MockedAuth.mockedAuth(auth); + MockedAuth.mockedConnectContext(ctx, "root", "192.168.1.1"); } @Test @@ -55,13 +72,4 @@ public class LinkDbStmtTest { stmt.analyze(AccessTestUtil.fetchBlockAnalyzer()); Assert.fail("no exception"); } - - @Test(expected = AnalysisException.class) - public void testNoPriv() throws InternalException, AnalysisException { - final ClusterName cn1 = new ClusterName("testCluster1", "testDb1"); - final ClusterName cn2 = new ClusterName("testCluster2", "testDb2"); - final LinkDbStmt stmt = new LinkDbStmt(cn1, cn2); - stmt.analyze(AccessTestUtil.fetchBlockAnalyzer()); - Assert.fail("no exception"); - } } diff --git a/fe/test/com/baidu/palo/analysis/LoadStmtTest.java b/fe/test/com/baidu/palo/analysis/LoadStmtTest.java index 75e3fcedfb..ffba8c0893 100644 --- a/fe/test/com/baidu/palo/analysis/LoadStmtTest.java +++ b/fe/test/com/baidu/palo/analysis/LoadStmtTest.java @@ -20,22 +20,38 @@ package com.baidu.palo.analysis; -import java.util.List; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.qe.ConnectContext; + +import com.google.common.collect.Lists; import org.easymock.EasyMock; import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import com.baidu.palo.common.AnalysisException; -import com.baidu.palo.common.InternalException; -import com.google.common.collect.Lists; +import java.util.List; + +import mockit.Mocked; +import mockit.NonStrictExpectations; +import mockit.internal.startup.Startup; public class LoadStmtTest { private DataDescription desc; private List dataDescriptions; private Analyzer analyzer; + @Mocked + private PaloAuth auth; + @Mocked + private ConnectContext ctx; + + static { + Startup.initializeIfPossible(); + } + @Before public void setUp() { analyzer = AccessTestUtil.fetchAdminAnalyzer(true); @@ -43,11 +59,21 @@ public class LoadStmtTest { desc = EasyMock.createMock(DataDescription.class); EasyMock.expect(desc.toSql()).andReturn("XXX"); dataDescriptions.add(desc); + + new NonStrictExpectations() { + { + ConnectContext.get(); + result = ctx; + + ctx.getQualifiedUser(); + result = "default_cluster:user"; + } + }; } @Test public void testNormal() throws InternalException, AnalysisException { - desc.analyze(); + desc.analyze(EasyMock.anyString()); EasyMock.expectLastCall().anyTimes(); EasyMock.replay(desc); @@ -61,21 +87,9 @@ public class LoadStmtTest { + "(XXX)", stmt.toString()); } - @Test(expected = AnalysisException.class) - public void testNoPriv() throws InternalException, AnalysisException { - desc.analyze(); - EasyMock.expectLastCall().anyTimes(); - EasyMock.replay(desc); - - LoadStmt stmt = new LoadStmt(new LabelName("testDb", "testLabel"), dataDescriptions, null, null, null); - stmt.analyze(AccessTestUtil.fetchBlockAnalyzer()); - - Assert.fail("No exception throws."); - } - @Test(expected = AnalysisException.class) public void testNoData() throws InternalException, AnalysisException { - desc.analyze(); + desc.analyze(EasyMock.anyString()); EasyMock.expectLastCall().anyTimes(); EasyMock.replay(desc); diff --git a/fe/test/com/baidu/palo/analysis/MigrateDbStmtTest.java b/fe/test/com/baidu/palo/analysis/MigrateDbStmtTest.java index 1659ae7659..9d92d47d38 100644 --- a/fe/test/com/baidu/palo/analysis/MigrateDbStmtTest.java +++ b/fe/test/com/baidu/palo/analysis/MigrateDbStmtTest.java @@ -22,18 +22,35 @@ package com.baidu.palo.analysis; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.MockedAuth; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.qe.ConnectContext; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import mockit.Mocked; +import mockit.internal.startup.Startup; + public class MigrateDbStmtTest { private static Analyzer analyzer; + @Mocked + private PaloAuth auth; + @Mocked + private ConnectContext ctx; + + static { + Startup.initializeIfPossible(); + } + @Before public void setUp() { analyzer = AccessTestUtil.fetchAdminAnalyzer(true); + MockedAuth.mockedAuth(auth); + MockedAuth.mockedConnectContext(ctx, "root", "192.168.1.1"); } @Test @@ -55,13 +72,4 @@ public class MigrateDbStmtTest { stmt.analyze(AccessTestUtil.fetchBlockAnalyzer()); Assert.fail("no exception"); } - - @Test(expected = AnalysisException.class) - public void testNoPriv() throws InternalException, AnalysisException { - final ClusterName cn1 = new ClusterName("testCluster1", "testDb1"); - final ClusterName cn2 = new ClusterName("testCluster2", "testDb2"); - final MigrateDbStmt stmt = new MigrateDbStmt(cn1, cn2); - stmt.analyze(AccessTestUtil.fetchBlockAnalyzer()); - Assert.fail("no exception"); - } } diff --git a/fe/test/com/baidu/palo/analysis/SetPassVarTest.java b/fe/test/com/baidu/palo/analysis/SetPassVarTest.java index b2f26feb71..9b75d62fb8 100644 --- a/fe/test/com/baidu/palo/analysis/SetPassVarTest.java +++ b/fe/test/com/baidu/palo/analysis/SetPassVarTest.java @@ -22,17 +22,34 @@ package com.baidu.palo.analysis; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.MockedAuth; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.qe.ConnectContext; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import mockit.Mocked; +import mockit.internal.startup.Startup; + public class SetPassVarTest { private Analyzer analyzer; + @Mocked + private PaloAuth auth; + @Mocked + private ConnectContext ctx; + + static { + Startup.initializeIfPossible(); + } + @Before public void setUp() { analyzer = AccessTestUtil.fetchAdminAnalyzer(true); + MockedAuth.mockedAuth(auth); + MockedAuth.mockedConnectContext(ctx, "root", "192.168.1.1"); } @Test @@ -40,40 +57,30 @@ public class SetPassVarTest { SetPassVar stmt; // mode: SET PASSWORD FOR 'testUser' = 'testPass'; - stmt = new SetPassVar("testUser", "*88EEBA7D913688E7278E2AD071FDB5E76D76D34B"); + stmt = new SetPassVar(new UserIdentity("testUser", "%"), "*88EEBA7D913688E7278E2AD071FDB5E76D76D34B"); stmt.analyze(analyzer); - Assert.assertEquals("testCluster:testUser", stmt.getUser()); + Assert.assertEquals("testCluster:testUser", stmt.getUserIdent().getQualifiedUser()); Assert.assertEquals("*88EEBA7D913688E7278E2AD071FDB5E76D76D34B", new String(stmt.getPassword())); - Assert.assertEquals("SET PASSWORD FOR 'testCluster:testUser' = '*88EEBA7D913688E7278E2AD071FDB5E76D76D34B'", + Assert.assertEquals("SET PASSWORD FOR 'testCluster:testUser'@'%' = '*88EEBA7D913688E7278E2AD071FDB5E76D76D34B'", stmt.toString()); // empty password - stmt = new SetPassVar("testUser", null); + stmt = new SetPassVar(new UserIdentity("testUser", "%"), null); stmt.analyze(analyzer); - Assert.assertEquals("SET PASSWORD FOR 'testCluster:testUser' = ''", stmt.toString()); + Assert.assertEquals("SET PASSWORD FOR 'testCluster:testUser'@'%' = ''", stmt.toString()); // empty user // empty password stmt = new SetPassVar(null, null); stmt.analyze(analyzer); - Assert.assertEquals("SET PASSWORD FOR 'testCluster:testUser' = ''", stmt.toString()); - } - - @Test(expected = AnalysisException.class) - public void testNoPriv() throws InternalException, AnalysisException { - SetPassVar stmt; - - // plain mode: SET PASSWORD FOR 'testUser' = PASSWORD('testPass'); - stmt = new SetPassVar("testUser", "*88EEBA7D913688E7278E2AD071FDB5E76D76D34B"); - stmt.analyze(AccessTestUtil.fetchBlockAnalyzer()); - Assert.fail("No exception throws."); + Assert.assertEquals("SET PASSWORD FOR 'testCluster:testUser'@'192.168.1.1' = ''", stmt.toString()); } @Test(expected = AnalysisException.class) public void testBadPassword() throws InternalException, AnalysisException { SetPassVar stmt; // mode: SET PASSWORD FOR 'testUser' = 'testPass'; - stmt = new SetPassVar("testUser", "*88EEBAHD913688E7278E2AD071FDB5E76D76D34B"); + stmt = new SetPassVar(new UserIdentity("testUser", "%"), "*88EEBAHD913688E7278E2AD071FDB5E76D76D34B"); stmt.analyze(analyzer); Assert.fail("No exception throws."); } diff --git a/fe/test/com/baidu/palo/analysis/SetStmtTest.java b/fe/test/com/baidu/palo/analysis/SetStmtTest.java index e547953c3f..e522f6ac83 100644 --- a/fe/test/com/baidu/palo/analysis/SetStmtTest.java +++ b/fe/test/com/baidu/palo/analysis/SetStmtTest.java @@ -22,20 +22,38 @@ package com.baidu.palo.analysis; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.MockedAuth; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.qe.ConnectContext; import com.google.common.collect.Lists; + import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.util.List; +import mockit.Mocked; +import mockit.internal.startup.Startup; + public class SetStmtTest { private Analyzer analyzer; + @Mocked + private PaloAuth auth; + @Mocked + private ConnectContext ctx; + + static { + Startup.initializeIfPossible(); + } + @Before public void setUp() { analyzer = AccessTestUtil.fetchAdminAnalyzer(true); + MockedAuth.mockedAuth(auth); + MockedAuth.mockedConnectContext(ctx, "root", "192.168.1.1"); } @Test diff --git a/fe/test/com/baidu/palo/analysis/SetUserPropertyStmtTest.java b/fe/test/com/baidu/palo/analysis/SetUserPropertyStmtTest.java index 5cabd87211..c8933e0da6 100644 --- a/fe/test/com/baidu/palo/analysis/SetUserPropertyStmtTest.java +++ b/fe/test/com/baidu/palo/analysis/SetUserPropertyStmtTest.java @@ -22,19 +22,38 @@ package com.baidu.palo.analysis; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.MockedAuth; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.qe.ConnectContext; + import com.google.common.collect.Lists; + import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.util.List; +import mockit.Mocked; +import mockit.internal.startup.Startup; + public class SetUserPropertyStmtTest { private Analyzer analyzer; + @Mocked + private PaloAuth auth; + @Mocked + private ConnectContext ctx; + + static { + Startup.initializeIfPossible(); + } + @Before public void setUp() { analyzer = AccessTestUtil.fetchAdminAnalyzer(true); + MockedAuth.mockedAuth(auth); + MockedAuth.mockedConnectContext(ctx, "root", "192.168.1.1"); } @Test @@ -46,9 +65,6 @@ public class SetUserPropertyStmtTest { SetUserPropertyStmt stmt = new SetUserPropertyStmt("testUser", propertyVarList); stmt.analyze(analyzer); Assert.assertEquals("testCluster:testUser", stmt.getUser()); - Assert.assertEquals( - "SET PROPERTY FOR 'testCluster:testUser' 'load_cluster.palo-dpp' = NULL, 'quota.normal' = '100'", - stmt.toString()); } @Test(expected = AnalysisException.class) diff --git a/fe/test/com/baidu/palo/analysis/SetUserPropertyVarTest.java b/fe/test/com/baidu/palo/analysis/SetUserPropertyVarTest.java index 42e643e18f..4984c343f8 100644 --- a/fe/test/com/baidu/palo/analysis/SetUserPropertyVarTest.java +++ b/fe/test/com/baidu/palo/analysis/SetUserPropertyVarTest.java @@ -38,20 +38,20 @@ public class SetUserPropertyVarTest { @Test public void testNormal() throws AnalysisException, InternalException { SetUserPropertyVar var = new SetUserPropertyVar("quota.normal", "1000"); - var.analyze(analyzer, "testUser"); + var.analyze(analyzer, true); Assert.assertEquals("quota.normal", var.getPropertyKey()); Assert.assertEquals("1000", var.getPropertyValue()); Assert.assertEquals("'quota.normal' = '1000'", var.toString()); var = new SetUserPropertyVar("load_cluster.palo-dpp", null); - var.analyze(analyzer, "testUser"); + var.analyze(analyzer, true); Assert.assertEquals("'load_cluster.palo-dpp' = NULL", var.toString()); } @Test(expected = AnalysisException.class) public void testUnknownProperty() throws InternalException, AnalysisException { SetUserPropertyVar var = new SetUserPropertyVar("unknown_property", "1000"); - var.analyze(analyzer, "testUser"); + var.analyze(analyzer, true); Assert.fail("No exception throws."); } } \ No newline at end of file diff --git a/fe/test/com/baidu/palo/analysis/SetVarTest.java b/fe/test/com/baidu/palo/analysis/SetVarTest.java index 1134454ce8..af8fdf0b6a 100644 --- a/fe/test/com/baidu/palo/analysis/SetVarTest.java +++ b/fe/test/com/baidu/palo/analysis/SetVarTest.java @@ -22,17 +22,34 @@ package com.baidu.palo.analysis; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.MockedAuth; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.qe.ConnectContext; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import mockit.Mocked; +import mockit.internal.startup.Startup; + public class SetVarTest { private Analyzer analyzer; + @Mocked + private PaloAuth auth; + @Mocked + private ConnectContext ctx; + + static { + Startup.initializeIfPossible(); + } + @Before public void setUp() { analyzer = AccessTestUtil.fetchAdminAnalyzer(false); + MockedAuth.mockedAuth(auth); + MockedAuth.mockedConnectContext(ctx, "root", "192.168.1.1"); } @Test @@ -59,11 +76,4 @@ public class SetVarTest { var.analyze(analyzer); Assert.fail("No exception throws."); } - - @Test(expected = AnalysisException.class) - public void testNoAccess() throws InternalException, AnalysisException { - SetVar var = new SetVar(SetType.GLOBAL, "names", new StringLiteral("utf-8")); - var.analyze(AccessTestUtil.fetchBlockAnalyzer()); - Assert.fail("No exception throws."); - } } \ No newline at end of file diff --git a/fe/test/com/baidu/palo/analysis/ShowAlterStmtTest.java b/fe/test/com/baidu/palo/analysis/ShowAlterStmtTest.java index 07a5ee0e45..be5bc914f9 100644 --- a/fe/test/com/baidu/palo/analysis/ShowAlterStmtTest.java +++ b/fe/test/com/baidu/palo/analysis/ShowAlterStmtTest.java @@ -25,8 +25,8 @@ import com.baidu.palo.catalog.Catalog; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.InternalException; -import org.junit.Assert; import org.easymock.EasyMock; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -52,7 +52,7 @@ public class ShowAlterStmtTest { analyzer = EasyMock.createMock(Analyzer.class); EasyMock.expect(analyzer.getDefaultDb()).andReturn("testDb").anyTimes(); - EasyMock.expect(analyzer.getUser()).andReturn("testUser").anyTimes(); + EasyMock.expect(analyzer.getQualifiedUser()).andReturn("testUser").anyTimes(); EasyMock.expect(analyzer.getCatalog()).andReturn(catalog).anyTimes(); EasyMock.expect(analyzer.getClusterName()).andReturn("testCluster").anyTimes(); EasyMock.replay(analyzer); @@ -89,18 +89,4 @@ public class ShowAlterStmtTest { stmt.analyze(analyzer); Assert.fail("No exception throws."); } - - @Test(expected = AnalysisException.class) - public void testNoPriv() throws InternalException, AnalysisException { - analyzer = EasyMock.createMock(Analyzer.class); - EasyMock.expect(analyzer.getDefaultDb()).andReturn("testDb").anyTimes(); - EasyMock.expect(analyzer.getUser()).andReturn("testUser").anyTimes(); - EasyMock.expect(analyzer.getCatalog()).andReturn(AccessTestUtil.fetchBlockCatalog()).anyTimes(); - EasyMock.expect(analyzer.getClusterName()).andReturn("testCluster").anyTimes(); - EasyMock.replay(analyzer); - - ShowLoadStmt stmt = new ShowLoadStmt(null, null, null, null); - stmt.analyze(analyzer); - Assert.fail("No exception throws."); - } } \ No newline at end of file diff --git a/fe/test/com/baidu/palo/analysis/ShowCreateDbStmtTest.java b/fe/test/com/baidu/palo/analysis/ShowCreateDbStmtTest.java index a8d78646aa..85dbe7701f 100644 --- a/fe/test/com/baidu/palo/analysis/ShowCreateDbStmtTest.java +++ b/fe/test/com/baidu/palo/analysis/ShowCreateDbStmtTest.java @@ -22,11 +22,34 @@ package com.baidu.palo.analysis; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.MockedAuth; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.qe.ConnectContext; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; +import mockit.Mocked; +import mockit.internal.startup.Startup; + public class ShowCreateDbStmtTest { + + @Mocked + private PaloAuth auth; + @Mocked + private ConnectContext ctx; + + static { + Startup.initializeIfPossible(); + } + + @Before + public void setUp() { + MockedAuth.mockedAuth(auth); + MockedAuth.mockedConnectContext(ctx, "root", "192.168.1.1"); + } + @Test public void testNormal() throws AnalysisException, InternalException { ShowCreateDbStmt stmt = new ShowCreateDbStmt("testDb"); @@ -42,11 +65,4 @@ public class ShowCreateDbStmtTest { stmt.analyze(AccessTestUtil.fetchAdminAnalyzer(false)); Assert.fail("No exception throws."); } - - @Test(expected = AnalysisException.class) - public void testNoPriv() throws AnalysisException, InternalException { - ShowCreateDbStmt stmt = new ShowCreateDbStmt("testDb"); - stmt.analyze(AccessTestUtil.fetchBlockAnalyzer()); - Assert.fail("No exception throws."); - } } diff --git a/fe/test/com/baidu/palo/analysis/ShowCreateTableStmtTest.java b/fe/test/com/baidu/palo/analysis/ShowCreateTableStmtTest.java index 8db24f3fb2..4d07c36ec7 100644 --- a/fe/test/com/baidu/palo/analysis/ShowCreateTableStmtTest.java +++ b/fe/test/com/baidu/palo/analysis/ShowCreateTableStmtTest.java @@ -21,17 +21,34 @@ package com.baidu.palo.analysis; import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.mysql.privilege.MockedAuth; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.qe.ConnectContext; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import mockit.Mocked; +import mockit.internal.startup.Startup; + public class ShowCreateTableStmtTest { private Analyzer analyzer; + @Mocked + private PaloAuth auth; + @Mocked + private ConnectContext ctx; + + static { + Startup.initializeIfPossible(); + } + @Before public void setUp() { analyzer = AccessTestUtil.fetchAdminAnalyzer(true); + MockedAuth.mockedAuth(auth); + MockedAuth.mockedConnectContext(ctx, "root", "192.168.1.1"); } @Test @@ -52,11 +69,4 @@ public class ShowCreateTableStmtTest { stmt.analyze(analyzer); Assert.fail("No Exception throws."); } - - @Test(expected = AnalysisException.class) - public void testNoPriv() throws AnalysisException { - ShowCreateTableStmt stmt = new ShowCreateTableStmt(new TableName("testDb", "testTbl")); - stmt.analyze(AccessTestUtil.fetchBlockAnalyzer()); - Assert.fail("No Exception throws."); - } } \ No newline at end of file diff --git a/fe/test/com/baidu/palo/analysis/ShowDataStmtTest.java b/fe/test/com/baidu/palo/analysis/ShowDataStmtTest.java index a0940ec200..8158e72a36 100644 --- a/fe/test/com/baidu/palo/analysis/ShowDataStmtTest.java +++ b/fe/test/com/baidu/palo/analysis/ShowDataStmtTest.java @@ -20,35 +20,108 @@ package com.baidu.palo.analysis; -import com.baidu.palo.catalog.Catalog; -import com.baidu.palo.common.AnalysisException; -import com.baidu.palo.common.InternalException; +import com.baidu.palo.backup.CatalogMocker; +import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.catalog.Database; +import com.baidu.palo.catalog.TabletInvertedIndex; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.qe.ConnectContext; +import com.baidu.palo.system.SystemInfoService; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import mockit.Mocked; +import mockit.NonStrictExpectations; +import mockit.internal.startup.Startup; -import org.easymock.EasyMock; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.powermock.api.easymock.PowerMock; -import org.powermock.core.classloader.annotations.PowerMockIgnore; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; - -@RunWith(PowerMockRunner.class) -@PowerMockIgnore("org.apache.log4j.*") -@PrepareForTest(Catalog.class) public class ShowDataStmtTest { - private Analyzer analyzer; - private Catalog catalog; + @Mocked + private PaloAuth auth; + @Mocked + private Analyzer analyzer; + @Mocked + private Catalog catalog; + @Mocked + private ConnectContext ctx; + @Mocked + private TabletInvertedIndex invertedIndex; + + private Database db; + + static { + Startup.initializeIfPossible(); + } - @Before - public void setUp() { - analyzer = AccessTestUtil.fetchAdminAnalyzer(true); - catalog = AccessTestUtil.fetchAdminCatalog(); - PowerMock.mockStatic(Catalog.class); - EasyMock.expect(Catalog.getInstance()).andReturn(catalog).anyTimes(); - PowerMock.replay(Catalog.class); + @Before + public void setUp() throws AnalysisException { + auth = new PaloAuth(); + + + + new NonStrictExpectations() { + { + Catalog.getCurrentInvertedIndex(); + result = invertedIndex; + } + }; + + db = CatalogMocker.mockDb(); + + new NonStrictExpectations() { + { + analyzer.getClusterName(); + result = SystemInfoService.DEFAULT_CLUSTER; + + analyzer.getDefaultDb(); + result = "testCluster:testDb"; + + Catalog.getCurrentCatalog(); + result = catalog; + + Catalog.getInstance(); + result = catalog; + + Catalog.getCurrentInvertedIndex(); + result = invertedIndex; + + catalog.getAuth(); + result = auth; + + catalog.getDb(anyString); + result = db; + + ConnectContext.get(); + result = ctx; + + ctx.getQualifiedUser(); + result = "root"; + + ctx.getRemoteIP(); + result = "192.168.1.1"; + } + }; + + + new NonStrictExpectations() { + { + auth.checkGlobalPriv((ConnectContext) any, (PrivPredicate) any); + result = true; + + auth.checkDbPriv((ConnectContext) any, anyString, (PrivPredicate) any); + result = true; + + auth.checkTblPriv((ConnectContext) any, anyString, anyString, (PrivPredicate) any); + result = true; + } + }; + + AccessTestUtil.fetchAdminAccess(); } @Test @@ -59,9 +132,9 @@ public class ShowDataStmtTest { Assert.assertEquals(2, stmt.getMetaData().getColumnCount()); Assert.assertEquals(false, stmt.hasTable()); - stmt = new ShowDataStmt("testDb", "testTbl"); + stmt = new ShowDataStmt("testDb", "test_tbl"); stmt.analyze(analyzer); - Assert.assertEquals("SHOW DATA FROM `testCluster:testDb`.`testTbl`", stmt.toString()); + Assert.assertEquals("SHOW DATA FROM `default_cluster:testDb`.`test_tbl`", stmt.toString()); Assert.assertEquals(3, stmt.getMetaData().getColumnCount()); Assert.assertEquals(true, stmt.hasTable()); } diff --git a/fe/test/com/baidu/palo/analysis/ShowLoadStmtTest.java b/fe/test/com/baidu/palo/analysis/ShowLoadStmtTest.java index 295e90bc76..745b88fe84 100644 --- a/fe/test/com/baidu/palo/analysis/ShowLoadStmtTest.java +++ b/fe/test/com/baidu/palo/analysis/ShowLoadStmtTest.java @@ -22,12 +22,12 @@ package com.baidu.palo.analysis; import com.baidu.palo.analysis.ShowAlterStmt.AlterType; import com.baidu.palo.catalog.Catalog; -import com.baidu.palo.system.SystemInfoService; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.InternalException; +import com.baidu.palo.system.SystemInfoService; -import org.junit.Assert; import org.easymock.EasyMock; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -45,6 +45,7 @@ public class ShowLoadStmtTest { private SystemInfoService systemInfoService; + @Before public void setUp() { systemInfoService = EasyMock.createMock(SystemInfoService.class); @@ -54,12 +55,13 @@ public class ShowLoadStmtTest { PowerMock.mockStatic(Catalog.class); EasyMock.expect(Catalog.getCurrentSystemInfo()).andReturn(systemInfoService).anyTimes(); + EasyMock.expect(Catalog.getCurrentCatalog()).andReturn(catalog).anyTimes(); EasyMock.expect(Catalog.getInstance()).andReturn(catalog).anyTimes(); PowerMock.replay(Catalog.class); analyzer = EasyMock.createMock(Analyzer.class); EasyMock.expect(analyzer.getDefaultDb()).andReturn("testCluster:testDb").anyTimes(); - EasyMock.expect(analyzer.getUser()).andReturn("testCluster:testUser").anyTimes(); + EasyMock.expect(analyzer.getQualifiedUser()).andReturn("testCluster:testUser").anyTimes(); EasyMock.expect(analyzer.getClusterName()).andReturn("testCluster").anyTimes(); EasyMock.expect(analyzer.getCatalog()).andReturn(catalog).anyTimes(); EasyMock.replay(analyzer); @@ -87,18 +89,4 @@ public class ShowLoadStmtTest { stmt.analyze(analyzer); Assert.fail("No exception throws."); } - - @Test(expected = AnalysisException.class) - public void testNoPriv() throws InternalException, AnalysisException { - analyzer = EasyMock.createMock(Analyzer.class); - EasyMock.expect(analyzer.getDefaultDb()).andReturn("testCluster:testDb").anyTimes(); - EasyMock.expect(analyzer.getUser()).andReturn("testCluster:testUser").anyTimes(); - EasyMock.expect(analyzer.getClusterName()).andReturn("testCluster").anyTimes(); - EasyMock.expect(analyzer.getCatalog()).andReturn(AccessTestUtil.fetchBlockCatalog()).anyTimes(); - EasyMock.replay(analyzer); - - ShowAlterStmt stmt = new ShowAlterStmt(AlterType.ROLLUP, null); - stmt.analyze(analyzer); - Assert.fail("No exception throws."); - } } \ No newline at end of file diff --git a/fe/test/com/baidu/palo/analysis/ShowTableStmtTest.java b/fe/test/com/baidu/palo/analysis/ShowTableStmtTest.java index 5cb302db6e..4f5439463b 100644 --- a/fe/test/com/baidu/palo/analysis/ShowTableStmtTest.java +++ b/fe/test/com/baidu/palo/analysis/ShowTableStmtTest.java @@ -21,17 +21,34 @@ package com.baidu.palo.analysis; import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.mysql.privilege.MockedAuth; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.qe.ConnectContext; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import mockit.Mocked; +import mockit.internal.startup.Startup; + public class ShowTableStmtTest { private Analyzer analyzer; + @Mocked + private PaloAuth auth; + @Mocked + private ConnectContext ctx; + + static { + Startup.initializeIfPossible(); + } + @Before public void setUp() { analyzer = AccessTestUtil.fetchAdminAnalyzer(true); + MockedAuth.mockedAuth(auth); + MockedAuth.mockedConnectContext(ctx, "root", "192.168.1.1"); } @Test @@ -66,12 +83,4 @@ public class ShowTableStmtTest { stmt.analyze(AccessTestUtil.fetchEmptyDbAnalyzer()); Assert.fail("No exception throws"); } - - - @Test(expected = AnalysisException.class) - public void testNoPriv() throws AnalysisException { - ShowTableStmt stmt = new ShowTableStmt("", false, null); - stmt.analyze(AccessTestUtil.fetchBlockAnalyzer()); - Assert.fail("No exception throws"); - } } \ No newline at end of file diff --git a/fe/test/com/baidu/palo/analysis/ShowUserPropertyStmtTest.java b/fe/test/com/baidu/palo/analysis/ShowUserPropertyStmtTest.java index 947371d000..24f55c4a60 100644 --- a/fe/test/com/baidu/palo/analysis/ShowUserPropertyStmtTest.java +++ b/fe/test/com/baidu/palo/analysis/ShowUserPropertyStmtTest.java @@ -22,17 +22,34 @@ package com.baidu.palo.analysis; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.MockedAuth; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.qe.ConnectContext; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import mockit.Mocked; +import mockit.internal.startup.Startup; + public class ShowUserPropertyStmtTest { private Analyzer analyzer; + @Mocked + private PaloAuth auth; + @Mocked + private ConnectContext ctx; + + static { + Startup.initializeIfPossible(); + } + @Before public void setUp() { analyzer = AccessTestUtil.fetchAdminAnalyzer(true); + MockedAuth.mockedAuth(auth); + MockedAuth.mockedConnectContext(ctx, "root", "192.168.1.1"); } @Test diff --git a/fe/test/com/baidu/palo/analysis/UseStmtTest.java b/fe/test/com/baidu/palo/analysis/UseStmtTest.java index af95c73e02..bbf244e37f 100644 --- a/fe/test/com/baidu/palo/analysis/UseStmtTest.java +++ b/fe/test/com/baidu/palo/analysis/UseStmtTest.java @@ -22,17 +22,34 @@ package com.baidu.palo.analysis; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.MockedAuth; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.qe.ConnectContext; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import mockit.Mocked; +import mockit.internal.startup.Startup; + public class UseStmtTest { private Analyzer analyzer; + @Mocked + private PaloAuth auth; + @Mocked + private ConnectContext ctx; + + static { + Startup.initializeIfPossible(); + } + @Before public void setUp() { analyzer = AccessTestUtil.fetchAdminAnalyzer(true); + MockedAuth.mockedAuth(auth); + MockedAuth.mockedConnectContext(ctx, "root", "192.168.1.1"); } @Test @@ -51,12 +68,4 @@ public class UseStmtTest { Assert.fail("No exception throws."); } - - @Test(expected = AnalysisException.class) - public void testNoPriv() throws InternalException, AnalysisException { - UseStmt stmt = new UseStmt("testDb"); - stmt.analyze(AccessTestUtil.fetchBlockAnalyzer()); - - Assert.fail("No exception throws."); - } } \ No newline at end of file diff --git a/fe/test/com/baidu/palo/backup/BackupHandlerTest.java b/fe/test/com/baidu/palo/backup/BackupHandlerTest.java new file mode 100644 index 0000000000..e864868b1c --- /dev/null +++ b/fe/test/com/baidu/palo/backup/BackupHandlerTest.java @@ -0,0 +1,364 @@ +package com.baidu.palo.backup; + +import com.baidu.palo.analysis.BackupStmt; +import com.baidu.palo.analysis.CancelBackupStmt; +import com.baidu.palo.analysis.CreateRepositoryStmt; +import com.baidu.palo.analysis.DropRepositoryStmt; +import com.baidu.palo.analysis.LabelName; +import com.baidu.palo.analysis.RestoreStmt; +import com.baidu.palo.analysis.TableName; +import com.baidu.palo.analysis.TableRef; +import com.baidu.palo.catalog.BrokerMgr; +import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.catalog.Database; +import com.baidu.palo.catalog.MaterializedIndex; +import com.baidu.palo.catalog.OlapTable; +import com.baidu.palo.catalog.Partition; +import com.baidu.palo.catalog.Table; +import com.baidu.palo.catalog.Tablet; +import com.baidu.palo.catalog.TabletInvertedIndex; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.Config; +import com.baidu.palo.common.DdlException; +import com.baidu.palo.common.FeConstants; +import com.baidu.palo.persist.EditLog; +import com.baidu.palo.task.DirMoveTask; +import com.baidu.palo.task.DownloadTask; +import com.baidu.palo.task.SnapshotTask; +import com.baidu.palo.task.UploadTask; +import com.baidu.palo.thrift.TFinishTaskRequest; +import com.baidu.palo.thrift.TStatus; +import com.baidu.palo.thrift.TStatusCode; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.FileVisitOption; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +import mockit.Delegate; +import mockit.Mock; +import mockit.MockUp; +import mockit.Mocked; +import mockit.NonStrictExpectations; +import mockit.internal.startup.Startup; + +public class BackupHandlerTest { + + private BackupHandler handler; + + @Mocked + private Catalog catalog; + @Mocked + private BrokerMgr brokerMgr; + @Mocked + private EditLog editLog; + + private Database db; + + private long idGen = 0; + + private File rootDir; + + private String tmpPath = "./tmp" + System.currentTimeMillis(); + + private TabletInvertedIndex invertedIndex = new TabletInvertedIndex(); + + static { + Startup.initializeIfPossible(); + } + + @Before + public void setUp() { + Config.tmp_dir = tmpPath; + rootDir = new File(Config.tmp_dir); + rootDir.mkdirs(); + + new NonStrictExpectations() { + { + catalog.getBrokerMgr(); + result = brokerMgr; + + catalog.getNextId(); + result = idGen++; + + catalog.getEditLog(); + result = editLog; + + Catalog.getCurrentCatalog(); + result = catalog; + + Catalog.getCurrentCatalogJournalVersion(); + result = FeConstants.meta_version; + + Catalog.getCurrentInvertedIndex(); + result = invertedIndex; + } + }; + + try { + db = CatalogMocker.mockDb(); + } catch (AnalysisException e) { + e.printStackTrace(); + Assert.fail(); + } + + new NonStrictExpectations() { + { + catalog.getDb(anyString); + result = db; + } + }; + } + + @After + public void done() { + if (rootDir != null) { + try { + Files.walk(Paths.get(Config.tmp_dir), + FileVisitOption.FOLLOW_LINKS).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + + @Test + public void testInit() { + handler = new BackupHandler(catalog); + handler.runOneCycle(); + + File backupDir = new File(BackupHandler.BACKUP_ROOT_DIR.toString()); + Assert.assertTrue(backupDir.exists()); + } + + @Test + public void testCreateAndDropRepository() { + new NonStrictExpectations() { + { + editLog.logCreateRepository((Repository) any); + result = new Delegate() { + public void logCreateRepository(Repository repo) { + + } + }; + + editLog.logDropRepository(anyString); + result = new Delegate() { + public void logDropRepository(String repoName) { + + } + }; + } + }; + + new MockUp() { + @Mock + public Status initRepository() { + return Status.OK; + } + + @Mock + public Status listSnapshots(List snapshotNames) { + snapshotNames.add("ss2"); + return Status.OK; + } + + @Mock + public Status getSnapshotInfoFile(String label, String backupTimestamp, List infos) { + OlapTable tbl = (OlapTable) db.getTable(CatalogMocker.TEST_TBL_NAME); + List
    tbls = Lists.newArrayList(); + tbls.add(tbl); + Map snapshotInfos = Maps.newHashMap(); + for (Partition part : tbl.getPartitions()) { + for (MaterializedIndex idx : part.getMaterializedIndices()) { + for (Tablet tablet : idx.getTablets()) { + List files = Lists.newArrayList(); + SnapshotInfo sinfo = new SnapshotInfo(db.getId(), tbl.getId(), part.getId(), idx.getId(), + tablet.getId(), -1, 0, "./path", files); + snapshotInfos.put(tablet.getId(), sinfo); + } + } + } + + BackupJobInfo info = BackupJobInfo.fromCatalog(System.currentTimeMillis(), + "ss2", CatalogMocker.TEST_DB_NAME, + CatalogMocker.TEST_DB_ID, tbls, snapshotInfos); + infos.add(info); + return Status.OK; + } + }; + + new NonStrictExpectations() { + { + brokerMgr.contaisnBroker(anyString); + result = true; + } + }; + + // add repo + handler = new BackupHandler(catalog); + CreateRepositoryStmt stmt = new CreateRepositoryStmt(false, "repo", "broker", "bos://location", + Maps.newHashMap()); + try { + handler.createRepository(stmt); + } catch (DdlException e) { + e.printStackTrace(); + Assert.fail(); + } + + // process backup + List tblRefs = Lists.newArrayList(); + tblRefs.add(new TableRef(new TableName(CatalogMocker.TEST_DB_NAME, CatalogMocker.TEST_TBL_NAME), null)); + BackupStmt backupStmt = new BackupStmt(new LabelName(CatalogMocker.TEST_DB_NAME, "label1"), "repo", tblRefs, + null); + try { + handler.process(backupStmt); + } catch (DdlException e1) { + e1.printStackTrace(); + Assert.fail(); + } + + // handleFinishedSnapshotTask + BackupJob backupJob = (BackupJob) handler.getJob(CatalogMocker.TEST_DB_ID); + SnapshotTask snapshotTask = new SnapshotTask(null, 0, 0, backupJob.getJobId(), CatalogMocker.TEST_DB_ID, + 0, 0, 0, 0, 0, 0, 0, 1, false); + TFinishTaskRequest request = new TFinishTaskRequest(); + List snapshotFiles = Lists.newArrayList(); + request.setSnapshot_files(snapshotFiles); + request.setSnapshot_path("./snapshot/path"); + request.setTask_status(new TStatus(TStatusCode.OK)); + handler.handleFinishedSnapshotTask(snapshotTask, request); + + // handleFinishedSnapshotUploadTask + Map srcToDestPath = Maps.newHashMap(); + UploadTask uploadTask = new UploadTask(null, 0, 0, backupJob.getJobId(), CatalogMocker.TEST_DB_ID, + srcToDestPath, null, null); + request = new TFinishTaskRequest(); + Map> tabletFiles = Maps.newHashMap(); + request.setTablet_files(tabletFiles); + request.setTask_status(new TStatus(TStatusCode.OK)); + handler.handleFinishedSnapshotUploadTask(uploadTask, request); + + // test file persist + File tmpFile = new File("./tmp" + System.currentTimeMillis()); + try { + DataOutputStream out = new DataOutputStream(new FileOutputStream(tmpFile)); + handler.write(out); + out.flush(); + out.close(); + DataInputStream in = new DataInputStream(new FileInputStream(tmpFile)); + BackupHandler.read(in); + in.close(); + } catch (IOException e) { + e.printStackTrace(); + Assert.fail(); + } finally { + tmpFile.delete(); + } + + // cancel backup + try { + handler.cancel(new CancelBackupStmt(CatalogMocker.TEST_DB_NAME, false)); + } catch (DdlException e1) { + e1.printStackTrace(); + Assert.fail(); + } + + // process restore + List tblRefs2 = Lists.newArrayList(); + tblRefs2.add(new TableRef(new TableName(CatalogMocker.TEST_DB_NAME, CatalogMocker.TEST_TBL_NAME), null)); + Map properties = Maps.newHashMap(); + properties.put("backup_timestamp", "2018-08-08-08-08-08"); + RestoreStmt restoreStmt = new RestoreStmt(new LabelName(CatalogMocker.TEST_DB_NAME, "ss2"), "repo", tblRefs2, + properties); + try { + restoreStmt.analyzeProperties(); + } catch (AnalysisException e2) { + e2.printStackTrace(); + Assert.fail(); + } + + try { + handler.process(restoreStmt); + } catch (DdlException e1) { + e1.printStackTrace(); + Assert.fail(); + } + + // handleFinishedSnapshotTask + RestoreJob restoreJob = (RestoreJob) handler.getJob(CatalogMocker.TEST_DB_ID); + snapshotTask = new SnapshotTask(null, 0, 0, restoreJob.getJobId(), CatalogMocker.TEST_DB_ID, + 0, 0, 0, 0, 0, 0, 0, 1, true); + request = new TFinishTaskRequest(); + request.setSnapshot_path("./snapshot/path"); + request.setTask_status(new TStatus(TStatusCode.OK)); + handler.handleFinishedSnapshotTask(snapshotTask, request); + + // handleDownloadSnapshotTask + DownloadTask downloadTask = new DownloadTask(null, 0, 0, restoreJob.getJobId(), CatalogMocker.TEST_DB_ID, + srcToDestPath, null, null); + request = new TFinishTaskRequest(); + List downloadedTabletIds = Lists.newArrayList(); + request.setDownloaded_tablet_ids(downloadedTabletIds); + request.setTask_status(new TStatus(TStatusCode.OK)); + handler.handleDownloadSnapshotTask(downloadTask, request); + + // handleDirMoveTask + DirMoveTask dirMoveTask = new DirMoveTask(null, 0, 0, restoreJob.getJobId(), CatalogMocker.TEST_DB_ID, 0, 0, 0, + 0, "", 0, true); + request = new TFinishTaskRequest(); + request.setTask_status(new TStatus(TStatusCode.OK)); + handler.handleDirMoveTask(dirMoveTask, request); + + // test file persist + tmpFile = new File("./tmp" + System.currentTimeMillis()); + try { + DataOutputStream out = new DataOutputStream(new FileOutputStream(tmpFile)); + handler.write(out); + out.flush(); + out.close(); + DataInputStream in = new DataInputStream(new FileInputStream(tmpFile)); + BackupHandler.read(in); + in.close(); + } catch (IOException e) { + e.printStackTrace(); + Assert.fail(); + } finally { + tmpFile.delete(); + } + + // cancel restore + try { + handler.cancel(new CancelBackupStmt(CatalogMocker.TEST_DB_NAME, true)); + } catch (DdlException e1) { + e1.printStackTrace(); + Assert.fail(); + } + + // drop repo + try { + handler.dropRepository(new DropRepositoryStmt("repo")); + } catch (DdlException e) { + e.printStackTrace(); + Assert.fail(); + } + } +} diff --git a/fe/test/com/baidu/palo/backup/BackupJobInfoTest.java b/fe/test/com/baidu/palo/backup/BackupJobInfoTest.java new file mode 100644 index 0000000000..24f4467fff --- /dev/null +++ b/fe/test/com/baidu/palo/backup/BackupJobInfoTest.java @@ -0,0 +1,167 @@ +package com.baidu.palo.backup; + +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; + +public class BackupJobInfoTest { + + private static String fileName = "job_info.txt"; + + @BeforeClass + public static void createFile() { + String json = "{\n" + + " \"backup_time\": 1522231864000,\n" + + " \"name\": \"snapshot1\",\n" + + " \"database\": \"db1\",\n" + + " \"id\": 10000,\n" + + " \"backup_result\": \"succeed\",\n" + + " \"backup_objects\": {\n" + + " \"table2\": {\n" + + " \"partitions\": {\n" + + " \"partition1\": {\n" + + " \"indexes\": {\n" + + " \"table2\": {\n" + + " \"id\": 10012,\n" + + " \"schema_hash\": 222222,\n" + + " \"tablets\": {\n" + + " \"10004\": [\"__10030_seg1.dat\", \"__10030_seg2.dat\"],\n" + + " \"10005\": [\"__10031_seg1.dat\", \"__10031_seg2.dat\"]\n" + + " }\n" + + " }\n" + + " },\n" + + " \"id\": 10011,\n" + + " \"version\": 11,\n" + + " \"version_hash\": 123456789\n" + + " }\n" + + " },\n" + + " \"id\": 10010\n" + + " },\n" + + " \"table1\": {\n" + + " \"partitions\": {\n" + + " \"partition2\": {\n" + + " \"indexes\": {\n" + + " \"rollup1\": {\n" + + " \"id\": 10009,\n" + + " \"schema_hash\": 333333,\n" + + " \"tablets\": {\n" + + " \"10008\": [\"__10029_seg1.dat\", \"__10029_seg2.dat\"],\n" + + " \"10007\": [\"__10029_seg1.dat\", \"__10029_seg2.dat\"]\n" + + " }\n" + + " },\n" + + " \"table1\": {\n" + + " \"id\": 10001,\n" + + " \"schema_hash\": 444444,\n" + + " \"tablets\": {\n" + + " \"10004\": [\"__10027_seg1.dat\", \"__10027_seg2.dat\"],\n" + + " \"10005\": [\"__10028_seg1.dat\", \"__10028_seg2.dat\"]\n" + + " }\n" + + " }\n" + + " },\n" + + " \"id\": 10007,\n" + + " \"version\": 20,\n" + + " \"version_hash\": 123534645745\n" + + " },\n" + + " \"partition1\": {\n" + + " \"indexes\": {\n" + + " \"rollup1\": {\n" + + " \"id\": 10009,\n" + + " \"schema_hash\": 333333,\n" + + " \"tablets\": {\n" + + " \"10008\": [\"__10026_seg1.dat\", \"__10026_seg2.dat\"],\n" + + " \"10007\": [\"__10025_seg1.dat\", \"__10025_seg2.dat\"]\n" + + " }\n" + + " },\n" + + " \"table1\": {\n" + + " \"id\": 10001,\n" + + " \"schema_hash\": 444444,\n" + + " \"tablets\": {\n" + + " \"10004\": [\"__10023_seg1.dat\", \"__10023_seg2.dat\"],\n" + + " \"10005\": [\"__10024_seg1.dat\", \"__10024_seg2.dat\"]\n" + + " }\n" + + " }\n" + + " },\n" + + " \"id\": 10002,\n" + + " \"version\": 21,\n" + + " \"version_hash\": 345346234234\n" + + " }\n" + + " },\n" + + " \"id\": 10001\n" + + " }\n" + + " }\n" + + "}"; + + try (PrintWriter out = new PrintWriter(fileName)) { + out.print(json); + } catch (FileNotFoundException e) { + e.printStackTrace(); + Assert.fail(); + } + } + + @AfterClass + public static void deleteFile() { + File file = new File(fileName); + if (file.exists()) { + file.delete(); + } + } + + @Test + public void testReadWrite() { + BackupJobInfo jobInfo = null; + try { + jobInfo = BackupJobInfo.fromFile(fileName); + } catch (IOException e) { + e.printStackTrace(); + Assert.fail(); + } + Assert.assertNotNull(jobInfo); + System.out.println(jobInfo.toString(1)); + + Assert.assertEquals(1522231864000L, jobInfo.backupTime); + Assert.assertEquals("snapshot1", jobInfo.name); + Assert.assertEquals(2, jobInfo.tables.size()); + + Assert.assertEquals(2, jobInfo.getTableInfo("table1").partitions.size()); + Assert.assertEquals(2, jobInfo.getTableInfo("table1").getPartInfo("partition1").indexes.size()); + Assert.assertEquals(2, + jobInfo.getTableInfo("table1").getPartInfo("partition1").getIdx("rollup1").tablets.size()); + System.out.println(jobInfo.getTableInfo("table1").getPartInfo("partition1").getIdx("rollup1").tablets); + Assert.assertEquals(2, + jobInfo.getTableInfo("table1").getPartInfo("partition1") + .getIdx("rollup1").getTablet(10007L).files.size()); + + File tmpFile = new File("./tmp"); + try { + DataOutputStream out = new DataOutputStream(new FileOutputStream(tmpFile)); + jobInfo.write(out); + out.flush(); + out.close(); + + DataInputStream in = new DataInputStream(new FileInputStream(tmpFile)); + BackupJobInfo newInfo = BackupJobInfo.read(in); + in.close(); + + Assert.assertEquals(jobInfo.backupTime, newInfo.backupTime); + Assert.assertEquals(jobInfo.dbId, newInfo.dbId); + Assert.assertEquals(jobInfo.dbName, newInfo.dbName); + + } catch (IOException e) { + e.printStackTrace(); + Assert.fail(); + } finally { + tmpFile.delete(); + } + } +} diff --git a/fe/test/com/baidu/palo/backup/BackupJobTest.java b/fe/test/com/baidu/palo/backup/BackupJobTest.java index 6858638fea..9a5f27d4ef 100644 --- a/fe/test/com/baidu/palo/backup/BackupJobTest.java +++ b/fe/test/com/baidu/palo/backup/BackupJobTest.java @@ -19,53 +19,308 @@ package com.baidu.palo.backup; -import com.baidu.palo.analysis.LabelName; +import com.baidu.palo.analysis.TableName; +import com.baidu.palo.analysis.TableRef; import com.baidu.palo.backup.BackupJob.BackupJobState; import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.catalog.Database; +import com.baidu.palo.catalog.OlapTable; import com.baidu.palo.common.Config; +import com.baidu.palo.common.FeMetaVersion; +import com.baidu.palo.common.util.UnitTestUtil; +import com.baidu.palo.persist.EditLog; +import com.baidu.palo.task.AgentBatchTask; +import com.baidu.palo.task.AgentTask; +import com.baidu.palo.task.AgentTaskExecutor; +import com.baidu.palo.task.AgentTaskQueue; +import com.baidu.palo.task.SnapshotTask; +import com.baidu.palo.task.UploadTask; +import com.baidu.palo.thrift.TBackend; +import com.baidu.palo.thrift.TFinishTaskRequest; +import com.baidu.palo.thrift.TStatus; +import com.baidu.palo.thrift.TStatusCode; +import com.baidu.palo.thrift.TTaskType; +import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import org.easymock.EasyMock; +import org.junit.AfterClass; +import org.junit.Assert; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; -import org.junit.runner.RunWith; -import org.powermock.api.easymock.PowerMock; -import org.powermock.core.classloader.annotations.PowerMockIgnore; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; +import java.io.File; +import java.io.IOException; +import java.nio.file.FileVisitOption; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; + +import mockit.Expectations; +import mockit.Mock; +import mockit.MockUp; +import mockit.Mocked; +import mockit.internal.startup.Startup; -@RunWith(PowerMockRunner.class) -@PowerMockIgnore("org.apache.log4j.*") -@PrepareForTest(Catalog.class) public class BackupJobTest { + private BackupJob job; + private Database db; + + private long dbId = 1; + private long tblId = 2; + private long partId = 3; + private long idxId = 4; + private long tabletId = 5; + private long backendId = 10000; + private long version = 6; + private long versionHash = 7; + + private long repoId = 20000; + private AtomicLong id = new AtomicLong(50000); + + @Mocked private Catalog catalog; + @Mocked + private BackupHandler backupHandler; + @Mocked + private RepositoryMgr repoMgr; + @Mocked + private EditLog editLog; + + private Repository repo = new Repository(repoId, "repo", false, "my_repo", + new BlobStorage("broker", Maps.newHashMap())); + + static { + Startup.initializeIfPossible(); + } + + @BeforeClass + public static void start() { + Config.tmp_dir = "./"; + File backupDir = new File(BackupHandler.BACKUP_ROOT_DIR.toString()); + backupDir.mkdirs(); + } + + @AfterClass + public static void end() throws IOException { + Config.tmp_dir = "./"; + File backupDir = new File(BackupHandler.BACKUP_ROOT_DIR.toString()); + if (backupDir.exists()) { + Files.walk(BackupHandler.BACKUP_ROOT_DIR, + FileVisitOption.FOLLOW_LINKS).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); + } + } @Before public void setUp() { - catalog = CatalogMocker.fetchAdminCatalog(); + new MockUp() { + @Mock + public BackupHandler getBackupHandler() { + return backupHandler; + } - PowerMock.mockStatic(Catalog.class); - EasyMock.expect(Catalog.getInstance()).andReturn(catalog).anyTimes(); - PowerMock.replay(Catalog.class); + @Mock + public Database getDb(long dbId) { + return db; + } + + @Mock + public int getCurrentCatalogJournalVersion() { + return FeMetaVersion.VERSION_42; + } + + @Mock + public long getNextId() { + return id.getAndIncrement(); + } + + @Mock + public EditLog getEditLog() { + return editLog; + } + }; + + new MockUp() { + @Mock + public RepositoryMgr getRepoMgr() { + return repoMgr; + } + }; + + new MockUp() { + @Mock + public Repository getRepo(long repoId) { + return repo; + } + }; + + new MockUp() { + @Mock + public void logBackupJob(BackupJob job) { + System.out.println("log backup job: " + job); + } + }; + + new MockUp() { + @Mock + public void submit(AgentBatchTask task) { + return; + } + }; + + new Expectations(Repository.class) { + { + repo.upload(anyString, anyString); + minTimes = 0; + result = Status.OK; + } + }; + + db = UnitTestUtil.createDb(dbId, tblId, partId, idxId, tabletId, backendId, version, versionHash); + List tableRefs = Lists.newArrayList(); + tableRefs.add(new TableRef(new TableName(UnitTestUtil.DB_NAME, UnitTestUtil.TABLE_NAME), null)); + job = new BackupJob("label", dbId, UnitTestUtil.DB_NAME, tableRefs, 13600 * 1000, catalog, repo.getId()); } @Test - public void testSaveMeta() { - Map properties = Maps.newHashMap(); - LabelName labelName = new LabelName(CatalogMocker.TEST_DB_NAME, "test_backup"); - BackupJob backupJob = new BackupJob(1, CatalogMocker.TEST_DB_ID, labelName, "/home/backup/", properties); - backupJob.setState(BackupJobState.PENDING); + public void testRunNormal() { + // 1.pending + Assert.assertEquals(BackupJobState.PENDING, job.getState()); + job.run(); + Assert.assertEquals(Status.OK, job.getStatus()); + Assert.assertEquals(BackupJobState.SNAPSHOTING, job.getState()); + + BackupMeta backupMeta = job.getBackupMeta(); + Assert.assertEquals(1, backupMeta.getTables().size()); + OlapTable backupTbl = (OlapTable) backupMeta.getTable(UnitTestUtil.TABLE_NAME); + List partNames = Lists.newArrayList(backupTbl.getPartitionNames()); + Assert.assertNotNull(backupTbl); + Assert.assertEquals(backupTbl.getSignature(BackupHandler.SIGNATURE_VERSION, partNames), + ((OlapTable) db.getTable(tblId)).getSignature(BackupHandler.SIGNATURE_VERSION, partNames)); + Assert.assertEquals(1, AgentTaskQueue.getTaskNum()); + AgentTask task = AgentTaskQueue.getTask(backendId, TTaskType.MAKE_SNAPSHOT, tabletId); + Assert.assertTrue(task instanceof SnapshotTask); + SnapshotTask snapshotTask = (SnapshotTask) task; + + // 2. snapshoting + job.run(); + Assert.assertEquals(Status.OK, job.getStatus()); + Assert.assertEquals(BackupJobState.SNAPSHOTING, job.getState()); + + // 3. snapshot finished + String snapshotPath = "/path/to/snapshot"; + List snapshotFiles = Lists.newArrayList(); + snapshotFiles.add("1.dat"); + snapshotFiles.add("1.idx"); + snapshotFiles.add("1.hdr"); + TStatus task_status = new TStatus(TStatusCode.OK); + TBackend tBackend = new TBackend("", 0, 1); + TFinishTaskRequest request = new TFinishTaskRequest(tBackend, TTaskType.MAKE_SNAPSHOT, + snapshotTask.getSignature(), task_status); + request.setSnapshot_files(snapshotFiles); + request.setSnapshot_path(snapshotPath); + Assert.assertTrue(job.finishTabletSnapshotTask(snapshotTask, request)); + job.run(); + Assert.assertEquals(Status.OK, job.getStatus()); + Assert.assertEquals(BackupJobState.UPLOAD_SNAPSHOT, job.getState()); - backupJob.addPartitionId(CatalogMocker.TEST_MYSQL_TABLE_ID, -1); - backupJob.addPartitionId(CatalogMocker.TEST_TBL_ID, CatalogMocker.TEST_SINGLE_PARTITION_ID); - backupJob.addPartitionId(CatalogMocker.TEST_TBL2_ID, CatalogMocker.TEST_PARTITION1_ID); - backupJob.addPartitionId(CatalogMocker.TEST_TBL2_ID, CatalogMocker.TEST_PARTITION2_ID); + // 4. upload snapshots + AgentTaskQueue.clearAllTasks(); + job.run(); + Assert.assertEquals(Status.OK, job.getStatus()); + Assert.assertEquals(BackupJobState.UPLOADING, job.getState()); + Assert.assertEquals(1, AgentTaskQueue.getTaskNum()); + task = AgentTaskQueue.getTask(backendId, TTaskType.UPLOAD, id.get() - 1); + Assert.assertTrue(task instanceof UploadTask); + UploadTask upTask = (UploadTask) task; + + Assert.assertEquals(job.getJobId(), upTask.getJobId()); + Map srcToDest = upTask.getSrcToDestPath(); + Assert.assertEquals(1, srcToDest.size()); + System.out.println(srcToDest); + String dest = srcToDest.get(snapshotPath + "/" + tabletId + "/" + 0); + Assert.assertNotNull(dest); - Config.meta_dir = "palo-meta/"; - backupJob.runOnce(); + // 5. uploading + job.run(); + Assert.assertEquals(Status.OK, job.getStatus()); + Assert.assertEquals(BackupJobState.UPLOADING, job.getState()); + Map> tabletFileMap = Maps.newHashMap(); + request = new TFinishTaskRequest(tBackend, TTaskType.UPLOAD, + upTask.getSignature(), task_status); + request.setTablet_files(tabletFileMap); + + Assert.assertFalse(job.finishSnapshotUploadTask(upTask, request)); + List tabletFiles = Lists.newArrayList(); + tabletFileMap.put(tabletId, tabletFiles); + Assert.assertFalse(job.finishSnapshotUploadTask(upTask, request)); + tabletFiles.add("1.dat.4f158689243a3d6030352fec3cfd3798"); + tabletFiles.add("wrong_files.idx.4f158689243a3d6030352fec3cfd3798"); + tabletFiles.add("wrong_files.hdr.4f158689243a3d6030352fec3cfd3798"); + Assert.assertFalse(job.finishSnapshotUploadTask(upTask, request)); + tabletFiles.clear(); + tabletFiles.add("1.dat.4f158689243a3d6030352fec3cfd3798"); + tabletFiles.add("1.idx.4f158689243a3d6030352fec3cfd3798"); + tabletFiles.add("1.hdr.4f158689243a3d6030352fec3cfd3798"); + Assert.assertTrue(job.finishSnapshotUploadTask(upTask, request)); + job.run(); + Assert.assertEquals(Status.OK, job.getStatus()); + Assert.assertEquals(BackupJobState.SAVE_META, job.getState()); + + // 6. save meta + job.run(); + Assert.assertEquals(Status.OK, job.getStatus()); + Assert.assertEquals(BackupJobState.UPLOAD_INFO, job.getState()); + File metaInfo = new File(job.getLocalMetaInfoFilePath()); + Assert.assertTrue(metaInfo.exists()); + File jobInfo = new File(job.getLocalJobInfoFilePath()); + Assert.assertTrue(jobInfo.exists()); + + BackupMeta restoreMetaInfo = null; + BackupJobInfo restoreJobInfo = null; + try { + restoreMetaInfo = BackupMeta.fromFile(job.getLocalMetaInfoFilePath()); + Assert.assertEquals(1, restoreMetaInfo.getTables().size()); + OlapTable olapTable = (OlapTable) restoreMetaInfo.getTable(tblId); + Assert.assertNotNull(olapTable); + Assert.assertNotNull(restoreMetaInfo.getTable(UnitTestUtil.TABLE_NAME)); + List names = Lists.newArrayList(olapTable.getPartitionNames()); + Assert.assertEquals(((OlapTable) db.getTable(tblId)).getSignature(BackupHandler.SIGNATURE_VERSION, names), + olapTable.getSignature(BackupHandler.SIGNATURE_VERSION, names)); + + restoreJobInfo = BackupJobInfo.fromFile(job.getLocalJobInfoFilePath()); + Assert.assertEquals(UnitTestUtil.DB_NAME, restoreJobInfo.dbName); + Assert.assertEquals(job.getLabel(), restoreJobInfo.name); + Assert.assertEquals(1, restoreJobInfo.tables.size()); + } catch (IOException e) { + e.printStackTrace(); + Assert.fail(); + } + + Assert.assertNull(job.getBackupMeta()); + Assert.assertNull(job.getJobInfo()); + + // 7. upload_info + job.run(); + Assert.assertEquals(Status.OK, job.getStatus()); + Assert.assertEquals(BackupJobState.FINISHED, job.getState()); + } + + @Test + public void testRunAbnormal() { + // 1.pending + AgentTaskQueue.clearAllTasks(); + + List tableRefs = Lists.newArrayList(); + tableRefs.add(new TableRef(new TableName(UnitTestUtil.DB_NAME, "unknown_tbl"), null)); + job = new BackupJob("label", dbId, UnitTestUtil.DB_NAME, tableRefs, 13600 * 1000, catalog, repo.getId()); + job.run(); + Assert.assertEquals(Status.ErrCode.NOT_FOUND, job.getStatus().getErrCode()); + Assert.assertEquals(BackupJobState.CANCELLED, job.getState()); } } diff --git a/fe/test/com/baidu/palo/backup/CatalogMocker.java b/fe/test/com/baidu/palo/backup/CatalogMocker.java index 201de47274..f3ab1d1299 100644 --- a/fe/test/com/baidu/palo/backup/CatalogMocker.java +++ b/fe/test/com/baidu/palo/backup/CatalogMocker.java @@ -21,39 +21,39 @@ package com.baidu.palo.backup; import com.baidu.palo.alter.RollupHandler; import com.baidu.palo.alter.SchemaChangeHandler; -import com.baidu.palo.catalog.AccessPrivilege; -import com.baidu.palo.catalog.KeysType; import com.baidu.palo.catalog.AggregateType; +import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.catalog.Column; import com.baidu.palo.catalog.ColumnType; import com.baidu.palo.catalog.DataProperty; +import com.baidu.palo.catalog.Database; import com.baidu.palo.catalog.DistributionInfo; import com.baidu.palo.catalog.HashDistributionInfo; +import com.baidu.palo.catalog.KeysType; import com.baidu.palo.catalog.MaterializedIndex; +import com.baidu.palo.catalog.MaterializedIndex.IndexState; import com.baidu.palo.catalog.MysqlTable; +import com.baidu.palo.catalog.OlapTable; import com.baidu.palo.catalog.Partition; import com.baidu.palo.catalog.PartitionInfo; import com.baidu.palo.catalog.PartitionKey; +import com.baidu.palo.catalog.PrimitiveType; import com.baidu.palo.catalog.RandomDistributionInfo; import com.baidu.palo.catalog.RangePartitionInfo; import com.baidu.palo.catalog.Replica; -import com.baidu.palo.catalog.TabletMeta; import com.baidu.palo.catalog.Replica.ReplicaState; import com.baidu.palo.catalog.SinglePartitionInfo; import com.baidu.palo.catalog.Tablet; -import com.baidu.palo.catalog.UserPropertyMgr; -import com.baidu.palo.catalog.Catalog; -import com.baidu.palo.catalog.Column; -import com.baidu.palo.catalog.Database; -import com.baidu.palo.catalog.OlapTable; -import com.baidu.palo.catalog.PrimitiveType; -import com.baidu.palo.catalog.MaterializedIndex.IndexState; -import com.baidu.palo.system.SystemInfoService; +import com.baidu.palo.catalog.TabletMeta; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.DdlException; import com.baidu.palo.common.util.Util; import com.baidu.palo.load.Load; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.mysql.privilege.PrivPredicate; import com.baidu.palo.persist.EditLog; import com.baidu.palo.qe.ConnectContext; +import com.baidu.palo.system.SystemInfoService; import com.baidu.palo.thrift.TStorageMedium; import com.baidu.palo.thrift.TStorageType; @@ -67,7 +67,6 @@ import java.util.List; import java.util.Map; public class CatalogMocker { - // user public static final String ROOTUSER = "root"; public static final String SUPERUSER = "superuser"; @@ -203,27 +202,17 @@ public class CatalogMocker { ROLLUP_SCHEMA_HASH = Util.schemaHash(0, TEST_ROLLUP_SCHEMA, null, 0); } - public static UserPropertyMgr fetchAdminAccess() { - UserPropertyMgr userPropertyMgr = EasyMock.createMock(UserPropertyMgr.class); - EasyMock.expect(userPropertyMgr.checkAccess(EasyMock.isA(String.class), - EasyMock.isA(String.class), EasyMock.isA(AccessPrivilege.class))) - .andReturn(true).anyTimes(); - EasyMock.expect(userPropertyMgr.isAdmin(EasyMock.isA(String.class))).andReturn(true).anyTimes(); - EasyMock.expect(userPropertyMgr.isSuperuser(EasyMock.isA(String.class))).andReturn(true).anyTimes(); - EasyMock.expect(userPropertyMgr.checkUserAccess(EasyMock.isA(String.class), EasyMock.eq(BLOCKUSER))) - .andReturn(false).anyTimes(); - EasyMock.expect(userPropertyMgr.checkUserAccess(EasyMock.isA(String.class), EasyMock.isA(String.class))) - .andReturn(true).anyTimes(); - try { - userPropertyMgr.setPasswd(EasyMock.endsWith(TESTUSER), EasyMock.isA(byte[].class)); - EasyMock.expectLastCall().anyTimes(); - userPropertyMgr.setPasswd(EasyMock.endsWith(ROOTUSER), EasyMock.isA(byte[].class)); - EasyMock.expectLastCall().andThrow(new DdlException("No privilege to change password")).anyTimes(); - } catch (DdlException e) { - return null; - } - EasyMock.replay(userPropertyMgr); - return userPropertyMgr; + private static PaloAuth fetchAdminAccess() { + PaloAuth auth = EasyMock.createMock(PaloAuth.class); + EasyMock.expect(auth.checkGlobalPriv(EasyMock.isA(ConnectContext.class), + EasyMock.isA(PrivPredicate.class))).andReturn(true).anyTimes(); + EasyMock.expect(auth.checkDbPriv(EasyMock.isA(ConnectContext.class), EasyMock.isA(String.class), + EasyMock.isA(PrivPredicate.class))).andReturn(true).anyTimes(); + EasyMock.expect(auth.checkTblPriv(EasyMock.isA(ConnectContext.class), EasyMock.isA(String.class), + EasyMock.isA(String.class), + EasyMock.isA(PrivPredicate.class))).andReturn(true).anyTimes(); + EasyMock.replay(auth); + return auth; } public static SystemInfoService fetchSystemInfoService() { @@ -260,7 +249,6 @@ public class CatalogMocker { tablet0.addReplica(replica1); tablet0.addReplica(replica2); - olapTable.setIndexSchemaInfo(TEST_TBL_ID, TEST_TBL_NAME, TEST_TBL_BASE_SCHEMA, 0, SCHEMA_HASH, (short) 1); olapTable.setStorageTypeToIndex(TEST_TBL_ID, TStorageType.COLUMN); olapTable.addPartition(partition); @@ -351,7 +339,6 @@ public class CatalogMocker { olapTable2.addPartition(partition2); // rollup index p1 - MaterializedIndex rollupIndexP1 = new MaterializedIndex(TEST_ROLLUP_ID, IndexState.NORMAL); Tablet rollupTabletP1 = new Tablet(TEST_ROLLUP_TABLET_P1_ID); TabletMeta tabletMetaRollupTabletP1 = new TabletMeta(TEST_DB_ID, TEST_TBL2_ID, TEST_PARTITION1_ID, @@ -393,7 +380,7 @@ public class CatalogMocker { public static Catalog fetchAdminCatalog() { try { Catalog catalog = EasyMock.createMock(Catalog.class); - EasyMock.expect(catalog.getUserMgr()).andReturn(fetchAdminAccess()).anyTimes(); + EasyMock.expect(catalog.getAuth()).andReturn(fetchAdminAccess()).anyTimes(); Database db = mockDb(); @@ -417,16 +404,16 @@ public class CatalogMocker { } } - public static UserPropertyMgr fetchBlockAccess() { - UserPropertyMgr service = EasyMock.createMock(UserPropertyMgr.class); - EasyMock.expect(service.checkAccess(EasyMock.isA(String.class), - EasyMock.isA(String.class), EasyMock.isA(AccessPrivilege.class))) - .andReturn(false).anyTimes(); - EasyMock.expect(service.isAdmin(EasyMock.isA(String.class))).andReturn(false).anyTimes(); - EasyMock.expect(service.isSuperuser(EasyMock.isA(String.class))).andReturn(false).anyTimes(); - EasyMock.expect(service.checkUserAccess(EasyMock.isA(String.class), EasyMock.isA(String.class))) - .andReturn(false).anyTimes(); - EasyMock.replay(service); - return service; + public static PaloAuth fetchBlockAccess() { + PaloAuth auth = EasyMock.createMock(PaloAuth.class); + EasyMock.expect(auth.checkGlobalPriv(EasyMock.isA(ConnectContext.class), + EasyMock.isA(PrivPredicate.class))).andReturn(false).anyTimes(); + EasyMock.expect(auth.checkDbPriv(EasyMock.isA(ConnectContext.class), EasyMock.isA(String.class), + EasyMock.isA(PrivPredicate.class))).andReturn(false).anyTimes(); + EasyMock.expect(auth.checkTblPriv(EasyMock.isA(ConnectContext.class), EasyMock.isA(String.class), + EasyMock.isA(String.class), + EasyMock.isA(PrivPredicate.class))).andReturn(false).anyTimes(); + EasyMock.replay(auth); + return auth; } } diff --git a/fe/test/com/baidu/palo/backup/ObjectWriterTest.java b/fe/test/com/baidu/palo/backup/ObjectWriterTest.java deleted file mode 100644 index 0d7adffce4..0000000000 --- a/fe/test/com/baidu/palo/backup/ObjectWriterTest.java +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) 2017, Baidu.com, Inc. All Rights Reserved - -// 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. - -package com.baidu.palo.backup; - -import com.baidu.palo.analysis.CreateTableStmt; -import com.baidu.palo.catalog.Catalog; -import com.baidu.palo.catalog.Database; -import com.baidu.palo.catalog.Table; -import com.baidu.palo.common.FeConstants; - -import com.google.common.base.Joiner; -import com.google.common.collect.Lists; - -import org.easymock.EasyMock; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.powermock.api.easymock.PowerMock; -import org.powermock.core.classloader.annotations.PowerMockIgnore; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; - -import java.io.IOException; - -@RunWith(PowerMockRunner.class) -@PowerMockIgnore("org.apache.log4j.*") -@PrepareForTest(Catalog.class) -public class ObjectWriterTest { - - private static Catalog catalog; - - @BeforeClass - public static void setUp() { - // Config.meta_dir = "./palo-meta"; - - catalog = CatalogMocker.fetchAdminCatalog(); - PowerMock.mockStatic(Catalog.class); - EasyMock.expect(Catalog.getInstance()).andReturn(catalog).anyTimes(); - EasyMock.expect(Catalog.getCurrentCatalogJournalVersion()).andReturn(FeConstants.meta_version).anyTimes(); - PowerMock.replay(Catalog.class); - } - - @Test - public void test_write_and_read_createTableStmt() throws IOException { - Database db = Catalog.getInstance().getDb(CatalogMocker.TEST_DB_ID); - - // write olap table - Table olapTable = db.getTable(CatalogMocker.TEST_TBL_ID); - int tableSignature = olapTable.getSignature(BackupVersion.VERSION_1); - CreateTableStmt stmt = olapTable.toCreateTableStmt(db.getFullName()); - stmt.setTableSignature(tableSignature); - - PathBuilder pathBuilder = - PathBuilder.createPathBuilder(Joiner.on("/").join("test_label", CatalogMocker.TEST_DB_NAME)); - String filePath = pathBuilder.createTableStmt(CatalogMocker.TEST_DB_NAME, CatalogMocker.TEST_TBL_NAME); - ObjectWriter.write(filePath, Lists.newArrayList(stmt)); - - // read olap table - stmt = ObjectWriter.readCreateTableStmt(filePath); - System.out.println(stmt.toSql()); - - System.out.println("get signature: " + stmt.getTableSignature()); - System.out.println("table signature: " + tableSignature); - if (stmt.getTableSignature() == tableSignature) { - System.out.println("get same signature: " + tableSignature); - } else { - Assert.fail(); - } - - // write mysql table - Table mysqlTable = db.getTable(CatalogMocker.MYSQL_TABLE_NAME); - tableSignature = mysqlTable.getSignature(BackupVersion.VERSION_1); - stmt = mysqlTable.toCreateTableStmt(db.getFullName()); - stmt.setTableSignature(tableSignature); - - filePath = pathBuilder.createTableStmt(CatalogMocker.TEST_DB_NAME, CatalogMocker.MYSQL_TABLE_NAME); - ObjectWriter.write(filePath, Lists.newArrayList(stmt)); - - // read mysql table - stmt = ObjectWriter.readCreateTableStmt(filePath); - System.out.println(stmt.toSql()); - - System.out.println("get signature: " + stmt.getTableSignature()); - System.out.println("table signature: " + tableSignature); - if (stmt.getTableSignature() == tableSignature) { - System.out.println("get same signature: " + tableSignature); - } else { - Assert.fail(); - } - - pathBuilder.getRoot().getTopParent().print(" "); - } -} diff --git a/fe/test/com/baidu/palo/backup/RepositoryTest.java b/fe/test/com/baidu/palo/backup/RepositoryTest.java new file mode 100644 index 0000000000..61019ae882 --- /dev/null +++ b/fe/test/com/baidu/palo/backup/RepositoryTest.java @@ -0,0 +1,318 @@ +package com.baidu.palo.backup; + +import com.baidu.palo.analysis.ShowRepositoriesStmt; +import com.baidu.palo.catalog.BrokerMgr; +import com.baidu.palo.catalog.BrokerMgr.BrokerAddress; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.service.FrontendOptions; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.sql.Timestamp; +import java.util.List; +import java.util.Map; + +import mockit.Delegate; +import mockit.Mock; +import mockit.MockUp; +import mockit.Mocked; +import mockit.NonStrictExpectations; +import mockit.internal.startup.Startup; + +public class RepositoryTest { + + private Repository repo; + private long repoId = 10000; + private String name = "repo"; + private String location = "bos://backup-cmy"; + private String brokerName = "broker"; + + private SnapshotInfo info; + + @Mocked + private BlobStorage storage; + + static { + Startup.initializeIfPossible(); + } + + @Before + public void setUp() { + List files = Lists.newArrayList(); + files.add("1.dat"); + files.add("1.hdr"); + files.add("1.idx"); + info = new SnapshotInfo(1, 2, 3, 4, 5, 6, 7, "/path/to/tablet/snapshot/", files); + + new NonStrictExpectations(FrontendOptions.class) { + { + FrontendOptions.getLocalHostAddress(); + minTimes = 0; + result = "127.0.0.1"; + } + }; + + new MockUp() { + @Mock + public BrokerAddress getBroker(String name, String host) throws AnalysisException { + return new BrokerAddress("10.74.167.16", 8111); + } + }; + + } + + @Test + public void testGet() { + repo = new Repository(10000, "repo", false, location, storage); + + Assert.assertEquals(repoId, repo.getId()); + Assert.assertEquals(name, repo.getName()); + Assert.assertEquals(false, repo.isReadOnly()); + Assert.assertEquals(location, repo.getLocation()); + Assert.assertEquals(null, repo.getErrorMsg()); + Assert.assertTrue(System.currentTimeMillis() - repo.getCreateTime() < 1000); + } + + @Test + public void testInit() { + new NonStrictExpectations() { + { + storage.list(anyString, (List) any); + result = new Delegate() { + public Status list(String remotePath, List result) { + result.clear(); + return Status.OK; + } + }; + + storage.directUpload(anyString, anyString); + result = Status.OK; + } + }; + + repo = new Repository(10000, "repo", false, location, storage); + + Status st = repo.initRepository(); + System.out.println(st); + Assert.assertTrue(st.ok()); + } + + @Test + public void testassemnblePath() { + repo = new Repository(10000, "repo", false, location, storage); + + // job info + String label = "label"; + String createTime = "2018-04-12 20:46:45"; + String createTime2 = "2018-04-12-20-46-45"; + Timestamp ts = Timestamp.valueOf(createTime); + long creastTs = ts.getTime(); + + // "location/__palo_repository_repo_name/__ss_my_sp1/__info_2018-01-01-08-00-00" + String expected = location + "/" + Repository.PREFIX_REPO + name + "/" + Repository.PREFIX_SNAPSHOT_DIR + + label + "/" + Repository.PREFIX_JOB_INFO + createTime2; + Assert.assertEquals(expected, repo.assembleJobInfoFilePath(label, creastTs)); + + // meta info + expected = location + "/" + Repository.PREFIX_REPO + name + "/" + Repository.PREFIX_SNAPSHOT_DIR + + label + "/" + Repository.FILE_META_INFO; + Assert.assertEquals(expected, repo.assembleMetaInfoFilePath(label)); + + // snapshot path + // /location/__palo_repository_repo_name/__ss_my_ss1/__ss_content/__db_10001/__tbl_10020/__part_10031/__idx_10032/__10023/__3481721 + expected = location + "/" + Repository.PREFIX_REPO + name + "/" + Repository.PREFIX_SNAPSHOT_DIR + + label + "/" + "__ss_content/__db_1/__tbl_2/__part_3/__idx_4/__5/__7"; + Assert.assertEquals(expected, repo.assembleRemoteSnapshotPath(label, info)); + } + + @Test + public void testPing() { + new NonStrictExpectations() { + { + storage.checkPathExist(anyString); + result = Status.OK; + } + }; + + repo = new Repository(10000, "repo", false, location, storage); + Assert.assertTrue(repo.ping()); + Assert.assertTrue(repo.getErrorMsg() == null); + } + + @Test + public void testListSnapshots() { + new NonStrictExpectations() { + { + storage.list(anyString, (List) any); + result = new Delegate() { + public Status list(String remotePath, List result) { + result.add(new RemoteFile(Repository.PREFIX_SNAPSHOT_DIR + "a", false, 100)); + result.add(new RemoteFile("_ss_b", true, 100)); + return Status.OK; + } + }; + } + }; + + repo = new Repository(10000, "repo", false, location, storage); + List snapshotNames = Lists.newArrayList(); + Status st = repo.listSnapshots(snapshotNames); + Assert.assertTrue(st.ok()); + Assert.assertEquals(1, snapshotNames.size()); + Assert.assertEquals("a", snapshotNames.get(0)); + } + + @Test + public void testUpload() { + new NonStrictExpectations() { + { + storage.upload(anyString, anyString); + result = Status.OK; + + storage.rename(anyString, anyString); + result = Status.OK; + } + }; + + repo = new Repository(10000, "repo", false, location, storage); + String localFilePath = "./tmp_" + System.currentTimeMillis(); + try (PrintWriter out = new PrintWriter(localFilePath)) { + out.print("a"); + } catch (FileNotFoundException e) { + e.printStackTrace(); + Assert.fail(); + } + try { + String remoteFilePath = location + "/remote_file"; + Status st = repo.upload(localFilePath, remoteFilePath); + Assert.assertTrue(st.ok()); + } finally { + File file = new File(localFilePath); + file.delete(); + } + } + + @Test + public void testDownload() { + String localFilePath = "./tmp_" + System.currentTimeMillis(); + File localFile = new File(localFilePath); + try { + try (PrintWriter out = new PrintWriter(localFile)) { + out.print("a"); + } catch (FileNotFoundException e) { + e.printStackTrace(); + Assert.fail(); + } + + new NonStrictExpectations() { + { + storage.list(anyString, (List) any); + result = new Delegate() { + public Status list(String remotePath, List result) { + result.add(new RemoteFile("remote_file.0cc175b9c0f1b6a831c399e269772661", true, 100)); + return Status.OK; + } + }; + + storage.downloadWithFileSize(anyString, anyString, anyLong); + result = Status.OK; + } + }; + + repo = new Repository(10000, "repo", false, location, storage); + String remoteFilePath = location + "/remote_file"; + Status st = repo.download(remoteFilePath, localFilePath); + Assert.assertTrue(st.ok()); + } finally { + localFile.delete(); + } + } + + @Test + public void testGetInfo() { + repo = new Repository(10000, "repo", false, location, storage); + List infos = repo.getInfo(); + Assert.assertTrue(infos.size() == ShowRepositoriesStmt.TITLE_NAMES.size()); + } + + @Test + public void testGetSnapshotInfo() { + new NonStrictExpectations() { + { + storage.list(anyString, (List) any); + result = new Delegate() { + public Status list(String remotePath, List result) { + if (remotePath.contains(Repository.PREFIX_JOB_INFO)) { + result.add(new RemoteFile(" __info_2018-04-18-20-11-00.12345678123456781234567812345678", + true, + 100)); + } else { + result.add(new RemoteFile(Repository.PREFIX_SNAPSHOT_DIR + "s1", false, 100)); + result.add(new RemoteFile(Repository.PREFIX_SNAPSHOT_DIR + "s2", false, 100)); + } + return Status.OK; + } + }; + } + }; + + repo = new Repository(10000, "repo", false, location, storage); + String snapshotName = ""; + String timestamp = ""; + try { + List> infos = repo.getSnapshotInfos(snapshotName, timestamp); + Assert.assertEquals(2, infos.size()); + + } catch (AnalysisException e) { + e.printStackTrace(); + Assert.fail(); + } + } + + @Test + public void testPersist() { + Map properties = Maps.newHashMap(); + properties.put("bos_endpoint", "http://gz.bcebos.com"); + properties.put("bos_accesskey", "a"); + properties.put("bos_secret_accesskey", "b"); + BlobStorage storage = new BlobStorage(brokerName, properties); + repo = new Repository(10000, "repo", false, location, storage); + + File file = new File("./Repository"); + try { + DataOutputStream out = new DataOutputStream(new FileOutputStream(file)); + repo.write(out); + out.flush(); + out.close(); + + DataInputStream in = new DataInputStream(new FileInputStream(file)); + Repository newRepo = Repository.read(in); + in.close(); + + Assert.assertEquals(repo.getName(), newRepo.getName()); + Assert.assertEquals(repo.getId(), newRepo.getId()); + Assert.assertEquals(repo.getLocation(), newRepo.getLocation()); + + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + Assert.fail(); + } finally { + file.delete(); + } + } + +} diff --git a/fe/test/com/baidu/palo/backup/RestoreFileMappingTest.java b/fe/test/com/baidu/palo/backup/RestoreFileMappingTest.java new file mode 100644 index 0000000000..5013ae4b37 --- /dev/null +++ b/fe/test/com/baidu/palo/backup/RestoreFileMappingTest.java @@ -0,0 +1,43 @@ +package com.baidu.palo.backup; + +import com.baidu.palo.backup.RestoreFileMapping.IdChain; + +import org.junit.Before; +import org.junit.Test; + +import junit.framework.Assert; + +public class RestoreFileMappingTest { + + private RestoreFileMapping fileMapping = new RestoreFileMapping(); + private IdChain src; + private IdChain dest; + + @Before + public void setUp() { + src = new IdChain(10005L, 10006L, 10005L, 10007L, 10008L); + dest = new IdChain(10004L, 10003L, 10004L, 10007L, -1L); + fileMapping.putMapping(src, dest, true); + } + + @Test + public void test() { + IdChain key = new IdChain(10005L, 10006L, 10005L, 10007L, 10008L); + Assert.assertTrue(key.equals(src)); + Assert.assertEquals(src, key); + IdChain val = fileMapping.get(key); + Assert.assertNotNull(val); + Assert.assertEquals(dest, val); + + Long l1 = new Long(10005L); + Long l2 = new Long(10005L); + Assert.assertFalse(l1 == l2); + Assert.assertTrue(l1.equals(l2)); + + Long l3 = new Long(1L); + Long l4 = new Long(1L); + Assert.assertFalse(l3 == l4); + Assert.assertTrue(l3.equals(l4)); + } + +} diff --git a/fe/test/com/baidu/palo/backup/RestoreJobTest.java b/fe/test/com/baidu/palo/backup/RestoreJobTest.java new file mode 100644 index 0000000000..1611ab8a44 --- /dev/null +++ b/fe/test/com/baidu/palo/backup/RestoreJobTest.java @@ -0,0 +1,363 @@ +package com.baidu.palo.backup; + +import com.baidu.palo.backup.BackupJobInfo.BackupIndexInfo; +import com.baidu.palo.backup.BackupJobInfo.BackupPartitionInfo; +import com.baidu.palo.backup.BackupJobInfo.BackupTableInfo; +import com.baidu.palo.backup.BackupJobInfo.BackupTabletInfo; +import com.baidu.palo.backup.RestoreJob.RestoreJobState; +import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.catalog.Database; +import com.baidu.palo.catalog.MaterializedIndex; +import com.baidu.palo.catalog.OlapTable; +import com.baidu.palo.catalog.Partition; +import com.baidu.palo.catalog.Table; +import com.baidu.palo.catalog.Tablet; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.FeMetaVersion; +import com.baidu.palo.common.MarkedCountDownLatch; +import com.baidu.palo.persist.EditLog; +import com.baidu.palo.system.SystemInfoService; +import com.baidu.palo.task.AgentBatchTask; +import com.baidu.palo.task.AgentTask; +import com.baidu.palo.task.AgentTaskExecutor; +import com.baidu.palo.task.AgentTaskQueue; +import com.baidu.palo.task.DirMoveTask; +import com.baidu.palo.task.DownloadTask; +import com.baidu.palo.task.SnapshotTask; +import com.baidu.palo.thrift.TBackend; +import com.baidu.palo.thrift.TFinishTaskRequest; +import com.baidu.palo.thrift.TStatus; +import com.baidu.palo.thrift.TStatusCode; +import com.baidu.palo.thrift.TTaskType; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.zip.Adler32; + +import mockit.Delegate; +import mockit.Mock; +import mockit.MockUp; +import mockit.Mocked; +import mockit.NonStrictExpectations; +import mockit.internal.startup.Startup; + +public class RestoreJobTest { + + private Database db; + private BackupJobInfo jobInfo; + private RestoreJob job; + private String label = "test_label"; + + private AtomicLong id = new AtomicLong(50000); + + private OlapTable expectedRestoreTbl; + + private long repoId = 20000; + @Mocked + private Catalog catalog; + @Mocked + private BackupHandler backupHandler; + @Mocked + private RepositoryMgr repoMgr; + @Mocked + private EditLog editLog; + @Mocked + private SystemInfoService systemInfoService; + + private Repository repo = new Repository(repoId, "repo", false, "bos://my_repo", + new BlobStorage("broker", Maps.newHashMap())); + + private BackupMeta backupMeta; + + static { + Startup.initializeIfPossible(); + } + + @Before + public void setUp() throws AnalysisException { + + new NonStrictExpectations() { + { + catalog.getBackupHandler(); + result = backupHandler; + + catalog.getDb(anyLong); + result = db; + + Catalog.getCurrentCatalogJournalVersion(); + result = FeMetaVersion.VERSION_42; + + catalog.getNextId(); + result = id.getAndIncrement(); + + catalog.getEditLog(); + result = catalog; + + Catalog.getCurrentSystemInfo(); + result = systemInfoService; + } + }; + + new NonStrictExpectations() { + { + systemInfoService.seqChooseBackendIds(anyInt, anyBoolean, anyBoolean, anyString); + result = new Delegate() { + public synchronized List seqChooseBackendIds(int backendNum, boolean needAlive, + boolean isCreate, String clusterName) { + List beIds = Lists.newArrayList(); + beIds.add(CatalogMocker.BACKEND1_ID); + beIds.add(CatalogMocker.BACKEND2_ID); + beIds.add(CatalogMocker.BACKEND3_ID); + return beIds; + } + }; + } + }; + + new NonStrictExpectations() { + { + backupHandler.getRepoMgr(); + result = repoMgr; + } + }; + + new NonStrictExpectations() { + { + repoMgr.getRepo(anyInt); + result = repo; + } + }; + + new NonStrictExpectations() { + { + editLog.logBackupJob((BackupJob) any); + result = new Delegate() { + public void logBackupJob(BackupJob job) { + System.out.println("log backup job: " + job); + } + }; + } + }; + + new NonStrictExpectations() { + { + AgentTaskExecutor.submit((AgentBatchTask) any); + result = new Delegate() { + public void submit(AgentBatchTask task) { + return; + } + }; + } + }; + + new NonStrictExpectations() { + { + repo.upload(anyString, anyString); + result = Status.OK; + + List backupMetas = Lists.newArrayList(); + repo.getSnapshotMetaFile(label, backupMetas); + result = new Delegate() { + public Status getSnapshotMetaFile(String label, List backupMetas) { + backupMetas.add(backupMeta); + return Status.OK; + } + }; + } + }; + + new MockUp() { + @Mock + boolean await(long timeout, TimeUnit unit) { + return true; + } + }; + + db = CatalogMocker.mockDb(); + + // gen BackupJobInfo + jobInfo = new BackupJobInfo(); + jobInfo.backupTime = System.currentTimeMillis(); + jobInfo.dbId = CatalogMocker.TEST_DB_ID; + jobInfo.dbName = CatalogMocker.TEST_DB_NAME; + jobInfo.name = label; + jobInfo.success = true; + + expectedRestoreTbl = (OlapTable) db.getTable(CatalogMocker.TEST_TBL2_ID); + BackupTableInfo tblInfo = new BackupTableInfo(); + tblInfo.id = CatalogMocker.TEST_TBL2_ID; + tblInfo.name = CatalogMocker.TEST_TBL2_NAME; + jobInfo.tables.put(tblInfo.name, tblInfo); + + for (Partition partition : expectedRestoreTbl.getPartitions()) { + BackupPartitionInfo partInfo = new BackupPartitionInfo(); + partInfo.id = partition.getId(); + partInfo.name = partition.getName(); + tblInfo.partitions.put(partInfo.name, partInfo); + + for (MaterializedIndex index : partition.getMaterializedIndices()) { + BackupIndexInfo idxInfo = new BackupIndexInfo(); + idxInfo.id = index.getId(); + idxInfo.name = expectedRestoreTbl.getIndexNameById(index.getId()); + idxInfo.schemaHash = expectedRestoreTbl.getSchemaHashByIndexId(index.getId()); + partInfo.indexes.put(idxInfo.name, idxInfo); + + for (Tablet tablet : index.getTablets()) { + BackupTabletInfo tabletInfo = new BackupTabletInfo(); + tabletInfo.id = tablet.getId(); + tabletInfo.files.add(tabletInfo.id + ".dat"); + tabletInfo.files.add(tabletInfo.id + ".idx"); + tabletInfo.files.add(tabletInfo.id + ".hdr"); + idxInfo.tablets.add(tabletInfo); + } + } + } + + // drop this table, cause we want to try restoring this table + db.dropTable(expectedRestoreTbl.getName()); + + job = new RestoreJob(label, "2018-01-01 01:01:01", db.getId(), db.getFullName(), + jobInfo, false, 3, 100000, catalog, repo.getId()); + + List
    tbls = Lists.newArrayList(); + tbls.add(expectedRestoreTbl); + backupMeta = new BackupMeta(tbls); + } + + @Test + public void testRun() { + // pending + job.run(); + Assert.assertEquals(Status.OK, job.getStatus()); + Assert.assertEquals(RestoreJobState.SNAPSHOTING, job.getState()); + Assert.assertEquals(12, job.getFileMapping().getMapping().size()); + + // 2. snapshoting + job.run(); + Assert.assertEquals(Status.OK, job.getStatus()); + Assert.assertEquals(RestoreJobState.SNAPSHOTING, job.getState()); + Assert.assertEquals(12 * 2, AgentTaskQueue.getTaskNum()); + + // 3. snapshot finished + List agentTasks = Lists.newArrayList(); + Map> runningTasks = Maps.newHashMap(); + agentTasks.addAll(AgentTaskQueue.getDiffTasks(CatalogMocker.BACKEND1_ID, runningTasks)); + agentTasks.addAll(AgentTaskQueue.getDiffTasks(CatalogMocker.BACKEND2_ID, runningTasks)); + agentTasks.addAll(AgentTaskQueue.getDiffTasks(CatalogMocker.BACKEND3_ID, runningTasks)); + Assert.assertEquals(12 * 2, agentTasks.size()); + + for (AgentTask agentTask : agentTasks) { + if (agentTask.getTaskType() != TTaskType.MAKE_SNAPSHOT) { + continue; + } + + SnapshotTask task = (SnapshotTask) agentTask; + String snapshotPath = "/path/to/snapshot/" + System.currentTimeMillis(); + TStatus taskStatus = new TStatus(TStatusCode.OK); + TBackend tBackend = new TBackend("", 0, 1); + TFinishTaskRequest request = new TFinishTaskRequest(tBackend, TTaskType.MAKE_SNAPSHOT, + task.getSignature(), taskStatus); + request.setSnapshot_path(snapshotPath); + Assert.assertTrue(job.finishTabletSnapshotTask(task, request)); + } + + job.run(); + Assert.assertEquals(Status.OK, job.getStatus()); + Assert.assertEquals(RestoreJobState.DOWNLOAD, job.getState()); + + // download + AgentTaskQueue.clearAllTasks(); + job.run(); + Assert.assertEquals(Status.OK, job.getStatus()); + Assert.assertEquals(RestoreJobState.DOWNLOADING, job.getState()); + Assert.assertEquals(9, AgentTaskQueue.getTaskNum()); + + // downloading + job.run(); + Assert.assertEquals(Status.OK, job.getStatus()); + Assert.assertEquals(RestoreJobState.DOWNLOADING, job.getState()); + + List downloadTasks = Lists.newArrayList(); + runningTasks = Maps.newHashMap(); + downloadTasks.addAll(AgentTaskQueue.getDiffTasks(CatalogMocker.BACKEND1_ID, runningTasks)); + downloadTasks.addAll(AgentTaskQueue.getDiffTasks(CatalogMocker.BACKEND2_ID, runningTasks)); + downloadTasks.addAll(AgentTaskQueue.getDiffTasks(CatalogMocker.BACKEND3_ID, runningTasks)); + Assert.assertEquals(9, downloadTasks.size()); + + List downloadedTabletIds = Lists.newArrayList(); + for (AgentTask agentTask : downloadTasks) { + TStatus taskStatus = new TStatus(TStatusCode.OK); + TBackend tBackend = new TBackend("", 0, 1); + TFinishTaskRequest request = new TFinishTaskRequest(tBackend, TTaskType.MAKE_SNAPSHOT, + agentTask.getSignature(), taskStatus); + request.setDownloaded_tablet_ids(downloadedTabletIds); + Assert.assertTrue(job.finishTabletDownloadTask((DownloadTask) agentTask, request)); + } + + job.run(); + Assert.assertEquals(Status.OK, job.getStatus()); + Assert.assertEquals(RestoreJobState.COMMIT, job.getState()); + + // commit + AgentTaskQueue.clearAllTasks(); + job.run(); + Assert.assertEquals(Status.OK, job.getStatus()); + Assert.assertEquals(RestoreJobState.COMMITTING, job.getState()); + Assert.assertEquals(12, AgentTaskQueue.getTaskNum()); + + // committing + job.run(); + Assert.assertEquals(Status.OK, job.getStatus()); + Assert.assertEquals(RestoreJobState.COMMITTING, job.getState()); + + List dirMoveTasks = Lists.newArrayList(); + runningTasks = Maps.newHashMap(); + dirMoveTasks.addAll(AgentTaskQueue.getDiffTasks(CatalogMocker.BACKEND1_ID, runningTasks)); + dirMoveTasks.addAll(AgentTaskQueue.getDiffTasks(CatalogMocker.BACKEND2_ID, runningTasks)); + dirMoveTasks.addAll(AgentTaskQueue.getDiffTasks(CatalogMocker.BACKEND3_ID, runningTasks)); + Assert.assertEquals(12, dirMoveTasks.size()); + + for (AgentTask agentTask : dirMoveTasks) { + TStatus taskStatus = new TStatus(TStatusCode.OK); + TBackend tBackend = new TBackend("", 0, 1); + TFinishTaskRequest request = new TFinishTaskRequest(tBackend, TTaskType.MAKE_SNAPSHOT, + agentTask.getSignature(), taskStatus); + job.finishDirMoveTask((DirMoveTask) agentTask, request); + } + + job.run(); + Assert.assertEquals(Status.OK, job.getStatus()); + Assert.assertEquals(RestoreJobState.FINISHED, job.getState()); + } + + @Test + public void testSignature() { + Adler32 sig1 = new Adler32(); + sig1.update("name1".getBytes()); + sig1.update("name2".getBytes()); + System.out.println("sig1: " + Math.abs((int) sig1.getValue())); + + Adler32 sig2 = new Adler32(); + sig2.update("name2".getBytes()); + sig2.update("name1".getBytes()); + System.out.println("sig2: " + Math.abs((int) sig2.getValue())); + + OlapTable tbl = (OlapTable) db.getTable(CatalogMocker.TEST_TBL_NAME); + List partNames = Lists.newArrayList(tbl.getPartitionNames()); + System.out.println("tbl signature: " + tbl.getSignature(BackupHandler.SIGNATURE_VERSION, partNames)); + tbl.setName("newName"); + System.out.println("tbl signature: " + tbl.getSignature(BackupHandler.SIGNATURE_VERSION, partNames)); + } + +} + diff --git a/fe/test/com/baidu/palo/bdb/BDBToolOptionsTest.java b/fe/test/com/baidu/palo/bdb/BDBToolOptionsTest.java index f341c5dbac..7bc84aa082 100644 --- a/fe/test/com/baidu/palo/bdb/BDBToolOptionsTest.java +++ b/fe/test/com/baidu/palo/bdb/BDBToolOptionsTest.java @@ -18,7 +18,7 @@ public class BDBToolOptionsTest { options = new BDBToolOptions(false, "12345", false, "12345", "12456", 35); Assert.assertTrue(options.hasFromKey()); Assert.assertTrue(options.hasEndKey()); - Assert.assertNotEquals(FeConstants.meta_version, options.getMetaVersion()); + Assert.assertNotSame(FeConstants.meta_version, options.getMetaVersion()); } } diff --git a/fe/test/com/baidu/palo/catalog/BackendTest.java b/fe/test/com/baidu/palo/catalog/BackendTest.java index a3eb265e62..e98c9531b3 100644 --- a/fe/test/com/baidu/palo/catalog/BackendTest.java +++ b/fe/test/com/baidu/palo/catalog/BackendTest.java @@ -22,6 +22,7 @@ package com.baidu.palo.catalog; import com.baidu.palo.analysis.AccessTestUtil; import com.baidu.palo.common.FeConstants; +import com.baidu.palo.metric.MetricRepo; import com.baidu.palo.system.Backend; import com.baidu.palo.thrift.TDisk; @@ -47,7 +48,7 @@ import java.util.Map; @RunWith(PowerMockRunner.class) @PowerMockIgnore("org.apache.log4j.*") -@PrepareForTest(Catalog.class) +@PrepareForTest({ Catalog.class, MetricRepo.class }) public class BackendTest { private Backend backend; private long backendId = 9999; @@ -57,7 +58,7 @@ public class BackendTest { private int httpPort = 21237; private int beRpcPort = 21238; - private Catalog catalog; + private Catalog catalog; @Before public void setUp() { @@ -68,7 +69,12 @@ public class BackendTest { PowerMock.replay(Catalog.class); backend = new Backend(backendId, host, heartbeatPort); - backend.updateOnce(bePort, httpPort, beRpcPort); + backend.updateOnce(bePort, httpPort, beRpcPort); + + PowerMock.mockStatic(MetricRepo.class); + MetricRepo.generateCapacityMetrics(); + EasyMock.expectLastCall().anyTimes(); + PowerMock.replay(MetricRepo.class); } @Test @@ -104,8 +110,7 @@ public class BackendTest { backend.updateDisks(diskInfos); Assert.assertEquals(disk1.getDisk_total_capacity() + disk2.getDisk_total_capacity(), backend.getTotalCapacityB()); - Assert.assertEquals(disk1.getDisk_total_capacity() + disk2.getDisk_total_capacity() + 1, - backend.getAvailableCapacityB()); + Assert.assertEquals(1, backend.getAvailableCapacityB()); // second update diskInfos.remove(disk1.getRoot_path()); diff --git a/fe/test/com/baidu/palo/catalog/DomainResolverServerTest.java b/fe/test/com/baidu/palo/catalog/DomainResolverServerTest.java deleted file mode 100644 index 65291c906c..0000000000 --- a/fe/test/com/baidu/palo/catalog/DomainResolverServerTest.java +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright (c) 2018, Baidu.com, Inc. All Rights Reserved - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// 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. - -package com.baidu.palo.catalog; - -import java.util.List; - -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.powermock.modules.junit4.PowerMockRunner; - -import com.google.common.collect.Lists; - -@RunWith(PowerMockRunner.class) -public class DomainResolverServerTest { - private DomainResolverServer server; - private String user = "test"; - private List domainNameList; - - @Before - public void setUp() { - server = DomainResolverServer.getInstance(); - domainNameList = Lists.newArrayList(); - domainNameList.add("www.baidu.com"); - } - - @Test - public void registerTest() { - // param error test - final List sizeZeroDomainNameList = Lists.newArrayList(); - // empty domain list - Assert.assertFalse(server.register(user, sizeZeroDomainNameList)); - // null domain list - Assert.assertFalse(server.register(user, null)); - // empty user - Assert.assertFalse(server.register("", domainNameList)); - // null user - Assert.assertFalse(server.register(null, domainNameList)); - - // normal test - Assert.assertTrue(server.register(user, domainNameList)); - Assert.assertTrue(server.getRegisteredUserDomain(user).size() == 1); - - // domain list contains null - final List nullDomainNameList = Lists.newArrayList(); - nullDomainNameList.add(null); - Assert.assertFalse(server.register(null, nullDomainNameList)); - Assert.assertTrue(server.getRegisteredUserDomain(user).size() == 1); - - // domains having registered - Assert.assertTrue(server.register(user, domainNameList)); - Assert.assertTrue(server.getRegisteredUserDomain(user).size() == 1); - - // normal test 2 - final List domainNameList2 = Lists.newArrayList(); - domainNameList2.add("www.sina.com.cn"); - Assert.assertTrue(server.register(user, domainNameList2)); - Assert.assertTrue(server.getRegisteredUserDomain(user).size() == 2); - } - - @Test - public void getIpsWithDNSTest() { - - // no exist user - Assert.assertEquals(null, server.getUserDomainToIps("user1")); - // null user - Assert.assertEquals(null, server.getUserDomainToIps(null)); - - try { - // wait for DomainResolverServer - Thread.currentThread(); - Thread.sleep(500); - // normal test - Assert.assertTrue(server.getUserDomainToIps(user).size() == 2); - } catch (InterruptedException e) { - } - } - - @Test - public void unregisterTest() { - // param error test - // null domain list - server.unregister(user, null); - Assert.assertTrue(server.getUserDomainToIps(user).size() == 2); - // empty domain list - final List sizeZeroDomainNameList = Lists.newArrayList(); - server.unregister(user, sizeZeroDomainNameList); - Assert.assertTrue(server.getUserDomainToIps(user).size() == 2); - // null user - server.unregister(null, domainNameList); - Assert.assertTrue(server.getUserDomainToIps(user).size() == 2); - // no exist user - server.unregister("test1", domainNameList); - Assert.assertTrue(server.getUserDomainToIps(user).size() == 2); - // normal test - server.unregister(user, domainNameList); - Assert.assertTrue(server.getUserDomainToIps(user).size() == 1); - final List domainNameList2 = Lists.newArrayList(); - domainNameList2.add("www.sina.com.cn"); - server.unregister(user, domainNameList2); - Assert.assertEquals(null, server.getUserDomainToIps(user)); - } - - @Test - public void registerNoExistDomain() { - // no exist domain - final List noExistDomainNameList = Lists.newArrayList(); - noExistDomainNameList.add("www.weqwetw.com.cnllll"); - Assert.assertTrue(server.register("test2", noExistDomainNameList)); - try { - // wait for DomainResolverServer - Thread.currentThread(); - Thread.sleep(500); - // normal test - Assert.assertEquals(null, server.getUserDomainToIps("test2")); - } catch (InterruptedException e) { - } - server.unregister(user, noExistDomainNameList); - Assert.assertEquals(null, server.getUserDomainToIps(user)); - } - - @Test - public void isAvaliableDomainTest() { - // normal test - Assert.assertTrue(server.isAvaliableDomain("www.sogo.com.cn")); - // param error test - Assert.assertFalse(server.isAvaliableDomain("")); - Assert.assertFalse(server.isAvaliableDomain(null)); - // no exist domain - Assert.assertFalse(server.isAvaliableDomain("www.sina.com.cn11sdfqweg")); - } - - @After - public void tearDown() throws Exception { - server = null; - user = null; - domainNameList.clear(); - } - -} diff --git a/fe/test/com/baidu/palo/catalog/OlapTableTest.java b/fe/test/com/baidu/palo/catalog/OlapTableTest.java new file mode 100644 index 0000000000..96e5cdc6c0 --- /dev/null +++ b/fe/test/com/baidu/palo/catalog/OlapTableTest.java @@ -0,0 +1,59 @@ +package com.baidu.palo.catalog; + +import com.baidu.palo.catalog.Table.TableType; +import com.baidu.palo.common.FeConstants; +import com.baidu.palo.common.io.FastByteArrayOutputStream; +import com.baidu.palo.common.util.UnitTestUtil; + +import org.junit.Test; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.List; + +import mockit.NonStrictExpectations; +import mockit.internal.startup.Startup; + +public class OlapTableTest { + + static { + Startup.initializeIfPossible(); + } + + @Test + public void test() throws IOException { + + new NonStrictExpectations(Catalog.class) { + { + Catalog.getCurrentCatalogJournalVersion(); + minTimes = 0; + result = FeConstants.meta_version; + } + }; + + Database db = UnitTestUtil.createDb(1, 2, 3, 4, 5, 6, 7, 8); + List
    tables = db.getTables(); + + for (Table table : tables) { + if (table.getType() != TableType.OLAP) { + continue; + } + OlapTable tbl = (OlapTable) table; + System.out.println("orig table id: " + tbl.getId()); + + FastByteArrayOutputStream byteArrayOutputStream = new FastByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(byteArrayOutputStream); + tbl.write(out); + + out.flush(); + out.close(); + + DataInputStream in = new DataInputStream(byteArrayOutputStream.getInputStream()); + Table copiedTbl = OlapTable.read(in); + System.out.println("copied table id: " + copiedTbl.getId()); + } + + } + +} diff --git a/fe/test/com/baidu/palo/catalog/UserPropertyTest.java b/fe/test/com/baidu/palo/catalog/UserPropertyTest.java index bd43d3bf7b..d2f24716eb 100644 --- a/fe/test/com/baidu/palo/catalog/UserPropertyTest.java +++ b/fe/test/com/baidu/palo/catalog/UserPropertyTest.java @@ -23,8 +23,10 @@ package com.baidu.palo.catalog; import com.baidu.palo.analysis.SetUserPropertyVar; import com.baidu.palo.analysis.SetVar; import com.baidu.palo.common.DdlException; -import com.baidu.palo.common.FeMetaVersion; +import com.baidu.palo.common.FeConstants; import com.baidu.palo.load.DppConfig; +import com.baidu.palo.mysql.privilege.UserProperty; + import com.google.common.collect.Lists; import org.easymock.EasyMock; @@ -49,11 +51,10 @@ public class UserPropertyTest { public void testNormal() throws IOException, DdlException { // mock catalog PowerMock.mockStatic(Catalog.class); - EasyMock.expect(Catalog.getCurrentCatalogJournalVersion()).andReturn(FeMetaVersion.VERSION_12).anyTimes(); + EasyMock.expect(Catalog.getCurrentCatalogJournalVersion()).andReturn(FeConstants.meta_version).anyTimes(); PowerMock.replay(Catalog.class); - UserProperty property = new UserProperty(); - + UserProperty property = new UserProperty("root"); property.getResource().updateGroupShare("low", 991); // To image ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); @@ -80,7 +81,7 @@ public class UserPropertyTest { Assert.assertEquals(100, userProperty.getMaxConn()); Assert.assertEquals(101, userProperty.getResource().getResource().getByDesc("cpu_share")); Assert.assertEquals(102, userProperty.getResource().getShareByGroup().get("normal").intValue()); - Assert.assertEquals("/user/palo2", userProperty.getClusterInfo("dpp-cluster").second.getPaloPath()); + Assert.assertEquals("/user/palo2", userProperty.getLoadClusterInfo("dpp-cluster").second.getPaloPath()); Assert.assertEquals("dpp-cluster", userProperty.getDefaultLoadCluster()); // fetch property @@ -103,21 +104,21 @@ public class UserPropertyTest { } // get cluster info - DppConfig dppConfig = userProperty.getClusterInfo("dpp-cluster").second; + DppConfig dppConfig = userProperty.getLoadClusterInfo("dpp-cluster").second; Assert.assertEquals(8070, dppConfig.getHttpPort()); // set palo path null propertyList = Lists.newArrayList(); propertyList.add(new SetUserPropertyVar("load_cluster.dpp-cluster.hadoop_palo_path", null)); userProperty.update(propertyList); - Assert.assertEquals(null, userProperty.getClusterInfo("dpp-cluster").second.getPaloPath()); + Assert.assertEquals(null, userProperty.getLoadClusterInfo("dpp-cluster").second.getPaloPath()); // remove dpp-cluster propertyList = Lists.newArrayList(); propertyList.add(new SetUserPropertyVar("load_cluster.dpp-cluster", null)); Assert.assertEquals("dpp-cluster", userProperty.getDefaultLoadCluster()); userProperty.update(propertyList); - Assert.assertEquals(null, userProperty.getClusterInfo("dpp-cluster").second); + Assert.assertEquals(null, userProperty.getLoadClusterInfo("dpp-cluster").second); Assert.assertEquals(null, userProperty.getDefaultLoadCluster()); } -} \ No newline at end of file +} diff --git a/fe/test/com/baidu/palo/catalog/UserResourceTest.java b/fe/test/com/baidu/palo/catalog/UserResourceTest.java index 15450f9e97..5087232316 100644 --- a/fe/test/com/baidu/palo/catalog/UserResourceTest.java +++ b/fe/test/com/baidu/palo/catalog/UserResourceTest.java @@ -21,6 +21,7 @@ package com.baidu.palo.catalog; import com.baidu.palo.common.DdlException; +import com.baidu.palo.mysql.privilege.UserResource; import com.baidu.palo.thrift.TResourceType; import com.baidu.palo.thrift.TUserResource; diff --git a/fe/test/com/baidu/palo/clone/CloneCheckerTest.java b/fe/test/com/baidu/palo/clone/CloneCheckerTest.java deleted file mode 100644 index 0e0f9d036a..0000000000 --- a/fe/test/com/baidu/palo/clone/CloneCheckerTest.java +++ /dev/null @@ -1,766 +0,0 @@ -// Copyright (c) 2017, Baidu.com, Inc. All Rights Reserved - -// 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. - -package com.baidu.palo.clone; - -import com.baidu.palo.catalog.Catalog; -import com.baidu.palo.catalog.Database; -import com.baidu.palo.catalog.MaterializedIndex; -import com.baidu.palo.catalog.OlapTable; -import com.baidu.palo.catalog.Partition; -import com.baidu.palo.catalog.Replica; -import com.baidu.palo.catalog.Replica.ReplicaState; -import com.baidu.palo.catalog.Tablet; -import com.baidu.palo.catalog.TabletInvertedIndex; -import com.baidu.palo.catalog.TabletMeta; -import com.baidu.palo.clone.CloneChecker.CapacityLevel; -import com.baidu.palo.clone.CloneJob.JobPriority; -import com.baidu.palo.clone.CloneJob.JobState; -import com.baidu.palo.clone.CloneJob.JobType; -import com.baidu.palo.common.Config; -import com.baidu.palo.common.util.UnitTestUtil; -import com.baidu.palo.persist.EditLog; -import com.baidu.palo.system.Backend; -import com.baidu.palo.system.SystemInfoService; -import com.baidu.palo.task.AgentTask; -import com.baidu.palo.task.AgentTaskQueue; -import com.baidu.palo.thrift.TDisk; -import com.baidu.palo.thrift.TTaskType; - -import com.google.common.collect.Lists; - -import org.easymock.EasyMock; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.powermock.api.easymock.PowerMock; -import org.powermock.core.classloader.annotations.PowerMockIgnore; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -@RunWith(PowerMockRunner.class) -@PowerMockIgnore("org.apache.log4j.*") -@PrepareForTest({ CloneChecker.class, Catalog.class, SystemInfoService.class }) -public class CloneCheckerTest { - private static final Logger LOG = LoggerFactory.getLogger(CloneCheckerTest.class); - - private CloneChecker checker; - private Catalog catalog; - private SystemInfoService systemInfoService; - private long dbId; - private long tableId; - private long partitionId; - private long indexId; - private long backendId; - - @Before - public void setUp() { - dbId = 0L; - tableId = 0L; - partitionId = 0L; - indexId = 0L; - backendId = 0L; - - // update conf - Config.clone_high_priority_delay_second = 0; - Config.clone_low_priority_delay_second = 600; - Config.clone_distribution_balance_threshold = 0.2; - Config.clone_capacity_balance_threshold = 0.2; - } - - @After - public void tearDown() throws Exception { - // destory INSTANCE - Field instanceField = CloneChecker.class.getDeclaredField("INSTANCE"); - instanceField.setAccessible(true); - instanceField.set(CloneChecker.class, null); - } - - private CloneJob createCloneJob() { - long tabletId = 0L; - JobType type = JobType.SUPPLEMENT; - JobPriority priority = JobPriority.HIGH; - long timeoutMs = 1000L; - CloneJob job = new CloneJob(dbId, tableId, partitionId, indexId, tabletId, backendId, type, priority, - timeoutMs); - job.setCreateTimeMs(System.currentTimeMillis() - 1); - return job; - } - - private Constructor getInnerClassConstructor(Class innerClass) { - Constructor[] constructors = innerClass.getDeclaredConstructors(); - Constructor constructor = constructors[0]; - constructor.setAccessible(true); - LOG.debug(constructor.toString()); - return constructor; - } - - @Test - public void testCheckTablets() throws Exception { - // mock getBackend getCloneInstance getDb getDbNames - long backendId1 = backendId; - long backendId2 = backendId + 1; - long backendId3 = backendId + 2; - Backend onlineBackend = EasyMock.createMock(Backend.class); - EasyMock.expect(onlineBackend.isAlive()).andReturn(true).anyTimes(); - EasyMock.replay(onlineBackend); - catalog = EasyMock.createNiceMock(Catalog.class); - - Clone clone = new Clone(); - EasyMock.expect(catalog.getCloneInstance()).andReturn(clone).anyTimes(); - - long tabletId = 0L; - long version = 1L; - long versionHash = 0L; - Database db = UnitTestUtil.createDb(dbId, tableId, partitionId, indexId, tabletId, backendId, version, - versionHash); - db.setClusterName("testCluster"); - OlapTable table = (OlapTable) db.getTable(tableId); - Partition partition = table.getPartition(partitionId); - MaterializedIndex index = partition.getBaseIndex(); - Tablet tablet = index.getTablet(tabletId); - tablet.deleteReplicaByBackendId(backendId1); - Assert.assertEquals(2, tablet.getReplicas().size()); - EasyMock.expect(catalog.getDb(dbId)).andReturn(db).anyTimes(); - - List dbNames = new ArrayList(); - String dbName = db.getFullName(); - dbNames.add(dbName); - EasyMock.expect(catalog.getDb(db.getFullName())).andReturn(db).anyTimes(); - EasyMock.expect(catalog.getDbNames()).andReturn(dbNames).anyTimes(); - - EasyMock.replay(catalog); - - // SystemInfoService - systemInfoService = EasyMock.createMock(SystemInfoService.class); - EasyMock.expect(systemInfoService.getBackend(backendId2)).andReturn(onlineBackend).anyTimes(); - EasyMock.expect(systemInfoService.getBackend(backendId1)).andReturn(onlineBackend).anyTimes(); - EasyMock.expect(systemInfoService.getBackend(backendId3)).andReturn(onlineBackend).anyTimes(); - EasyMock.expect(systemInfoService.getBackendIds(true)) - .andReturn(Lists.newArrayList(backendId1, backendId2, backendId3)).anyTimes(); - EasyMock.replay(systemInfoService); - - // inverted index - TabletInvertedIndex invertedIndex = EasyMock.createMock(TabletInvertedIndex.class); - invertedIndex.deleteReplica(EasyMock.anyLong(), EasyMock.anyLong()); - EasyMock.expectLastCall().anyTimes(); - EasyMock.replay(invertedIndex); - - // mock catalog - PowerMock.mockStatic(Catalog.class); - EasyMock.expect(Catalog.getInstance()).andReturn(catalog).anyTimes(); - EasyMock.expect(Catalog.getCurrentSystemInfo()).andReturn(systemInfoService).anyTimes(); - EasyMock.expect(Catalog.getCurrentInvertedIndex()).andReturn(invertedIndex).anyTimes(); - PowerMock.replay(Catalog.class); - - // mock private method - Map backendInfos = new HashMap(); - Map> distributionLevelToBackendIds = new HashMap>(); - Map> capacityLevelToBackendIds = new HashMap>(); - CloneChecker mockChecker = PowerMock.createPartialMock(CloneChecker.class, "initBackendInfos", - "initBackendCapacityInfos", "initBackendDistributionInfos"); - PowerMock.expectPrivate(mockChecker, "initBackendInfos", "testCluster").andReturn(backendInfos).anyTimes(); - PowerMock.expectPrivate(mockChecker, "initBackendDistributionInfos", backendInfos) - .andReturn(distributionLevelToBackendIds).anyTimes(); - PowerMock.expectPrivate(mockChecker, "initBackendCapacityInfos", backendInfos) - .andReturn(capacityLevelToBackendIds).anyTimes(); - PowerMock.replay(mockChecker); - - // init backend infos - Class backendInfoClass = UnitTestUtil.getInnerClass(CloneChecker.class, - "com.baidu.palo.clone.CloneChecker$BackendInfo"); - Constructor constructor = getInnerClassConstructor(backendInfoClass); - backendInfos.put(backendId1, constructor.newInstance(new Object[] { mockChecker, backendId1, 10L, 0L })); - backendInfos.put(backendId2, constructor.newInstance(new Object[] { mockChecker, backendId2, 10L, 0L })); - backendInfos.put(backendId3, constructor.newInstance(new Object[] { mockChecker, backendId3, 10L, 0L })); - - for (CapacityLevel level : CapacityLevel.values()) { - distributionLevelToBackendIds.put(level, new HashSet()); - capacityLevelToBackendIds.put(level, new HashSet()); - } - distributionLevelToBackendIds.get(CapacityLevel.LOW).add(backendId1); - distributionLevelToBackendIds.get(CapacityLevel.MID).add(backendId2); - distributionLevelToBackendIds.get(CapacityLevel.HIGH).add(backendId3); - capacityLevelToBackendIds.get(CapacityLevel.LOW).add(backendId1); - capacityLevelToBackendIds.get(CapacityLevel.MID).add(backendId2); - capacityLevelToBackendIds.get(CapacityLevel.HIGH).add(backendId3); - - // test check tablet for supplment - Assert.assertTrue(mockChecker.checkTabletForSupplement(dbId, tableId, partitionId, indexId, tabletId)); - List pendingJobs = clone.getCloneJobs(JobState.PENDING); - if (pendingJobs.size() == 1) { - CloneJob job = pendingJobs.get(0); - Assert.assertEquals(backendId1, job.getDestBackendId()); - Assert.assertEquals(tabletId, job.getTabletId()); - Assert.assertEquals(JobType.SUPPLEMENT, job.getType()); - clone.cancelCloneJob(job, "test"); - Assert.assertEquals(0, clone.getCloneJobs(JobState.PENDING).size()); - Assert.assertEquals(2, tablet.getReplicas().size()); - } - - // test check tablets - Method checkTablets = UnitTestUtil.getPrivateMethod(CloneChecker.class, "checkTablets", new Class[] {}); - checkTablets.invoke(mockChecker, new Object[] {}); - pendingJobs = clone.getCloneJobs(JobState.PENDING); - if (pendingJobs.size() == 1) { - CloneJob job = pendingJobs.get(0); - Assert.assertEquals(backendId1, job.getDestBackendId()); - Assert.assertEquals(tabletId, job.getTabletId()); - Assert.assertEquals(JobType.SUPPLEMENT, job.getType()); - clone.cancelCloneJob(job, "test"); - Assert.assertEquals(0, clone.getCloneJobs(JobState.PENDING).size()); - Assert.assertEquals(2, tablet.getReplicas().size()); - } - } - - @Test - public void testInitBackendAndCapacityInfos() throws Exception { - // mock catalog editlog - catalog = EasyMock.createNiceMock(Catalog.class); - EditLog editLog = EasyMock.createMock(EditLog.class); - EasyMock.expect(catalog.getEditLog()).andReturn(editLog).anyTimes(); - EasyMock.replay(catalog); - - // mock catalog - PowerMock.mockStatic(Catalog.class); - EasyMock.expect(Catalog.getInstance()).andReturn(catalog).anyTimes(); - systemInfoService = EasyMock.createMock(SystemInfoService.class); - EasyMock.expect(Catalog.getCurrentSystemInfo()).andReturn(systemInfoService).anyTimes(); - PowerMock.replay(Catalog.class); - - // mock getBackend - long backendId1 = backendId; // high - long backendId2 = backendId + 1; // mid - long backendId3 = backendId + 2; // low - long totalCapacityB = 100L; - long availableCapacityB = 50L; - List backends = new ArrayList(); - backends.add(backendId1); - backends.add(backendId2); - backends.add(backendId3); - Backend backend1 = UnitTestUtil.createBackend(backendId1, "127.0.0.1", 8000, 8001, 8003, totalCapacityB, - availableCapacityB - 40); - Backend backend2 = UnitTestUtil.createBackend(backendId1, "127.0.0.1", 8100, 8101, 8103, totalCapacityB, - availableCapacityB); - Backend backend3 = UnitTestUtil.createBackend(backendId1, "127.0.0.1", 8200, 8201, 8203, totalCapacityB, - availableCapacityB + 40); - backend1.setOwnerClusterName("testCluster"); - - // SystemInfoService - EasyMock.expect(systemInfoService.getBackend(backendId1)).andReturn(backend1).anyTimes(); - EasyMock.expect(systemInfoService.getBackend(backendId2)).andReturn(backend2).anyTimes(); - EasyMock.expect(systemInfoService.getBackend(backendId3)).andReturn(backend3).anyTimes(); - EasyMock.expect(systemInfoService.getBackendIds(true)) - .andReturn(Lists.newArrayList(backendId1, backendId2, backendId3)).anyTimes(); - EasyMock.replay(systemInfoService); - - // get initBackendInfos method - checker = CloneChecker.getInstance(); - Method initBackendInfos = UnitTestUtil.getPrivateMethod(CloneChecker.class, "initBackendInfos", - new Class[] { String.class }); - Method initBackendCapacityInfos = UnitTestUtil.getPrivateMethod(CloneChecker.class, "initBackendCapacityInfos", - new Class[] { Map.class }); - - // test - Map backendInfos = (Map) initBackendInfos.invoke(checker, new Object[] { null }); - Map> capacityLevelToBackendIds = (Map>) initBackendCapacityInfos - .invoke(checker, new Object[] { backendInfos }); - Assert.assertTrue(capacityLevelToBackendIds.get(CapacityLevel.HIGH).contains(backendId1)); - Assert.assertTrue(capacityLevelToBackendIds.get(CapacityLevel.MID).contains(backendId2)); - Assert.assertTrue(capacityLevelToBackendIds.get(CapacityLevel.LOW).contains(backendId3)); - } - - @Test - public void testInitBackendDistributionInfos() throws Exception { - // get inner class: BackendInfo - checker = CloneChecker.getInstance(); - Class backendInfoClass = UnitTestUtil.getInnerClass(CloneChecker.class, - "com.baidu.palo.clone.CloneChecker$BackendInfo"); - Constructor backendInfoConstructor = getInnerClassConstructor(backendInfoClass); - Method setTableReplicaNum = UnitTestUtil.getPrivateMethod(backendInfoClass, "setTableReplicaNum", - new Class[] { int.class }); - - // init params - long backendId1 = backendId; // high - long backendId2 = backendId + 1; // mid - long backendId3 = backendId + 2; // low - long totalCapacityB = 100L; - long availableCapacityB = 50L; - Map backendInfos = new HashMap(); - Object backendInfo1 = backendInfoConstructor - .newInstance(new Object[] { checker, backendId1, totalCapacityB, availableCapacityB }); - setTableReplicaNum.invoke(backendInfo1, new Object[] { 10 }); - backendInfos.put(backendId1, backendInfo1); - Object backendInfo2 = backendInfoConstructor - .newInstance(new Object[] { checker, backendId2, totalCapacityB, availableCapacityB }); - backendInfos.put(backendId2, backendInfo2); - setTableReplicaNum.invoke(backendInfo2, new Object[] { 5 }); - Object backendInfo3 = backendInfoConstructor - .newInstance(new Object[] { checker, backendId3, totalCapacityB, availableCapacityB }); - backendInfos.put(backendId3, backendInfo3); - setTableReplicaNum.invoke(backendInfo3, new Object[] { 1 }); - - // get initBackendDistributionInfos method - Method initBackendDistributionInfos = UnitTestUtil.getPrivateMethod(CloneChecker.class, - "initBackendDistributionInfos", new Class[] { Map.class }); - - // test - Map> distributionLevelToBackendIds = (Map>) initBackendDistributionInfos - .invoke(checker, new Object[] { backendInfos }); - Assert.assertTrue(distributionLevelToBackendIds.get(CapacityLevel.HIGH).contains(backendId1)); - Assert.assertTrue(distributionLevelToBackendIds.get(CapacityLevel.MID).contains(backendId2)); - Assert.assertTrue(distributionLevelToBackendIds.get(CapacityLevel.LOW).contains(backendId3)); - } - - @Test - public void testSelectRandomBackendId() throws Exception { - // get method - checker = CloneChecker.getInstance(); - Method selectRandomBackendId = UnitTestUtil.getPrivateMethod(CloneChecker.class, "selectRandomBackendId", - new Class[] { List.class, Set.class }); - - // fail - List candidateBackendIds = new ArrayList(); - candidateBackendIds.add(0L); - Set excludeBackendIds = new HashSet(); - excludeBackendIds.add(0L); - Assert.assertEquals(-1L, - selectRandomBackendId.invoke(checker, new Object[] { candidateBackendIds, excludeBackendIds })); - - // success - candidateBackendIds.add(1L); - Assert.assertEquals(1L, - selectRandomBackendId.invoke(checker, new Object[] { candidateBackendIds, excludeBackendIds })); - } - - @Test - public void testSelectCloneReplicaBackendId() throws Exception { - // get inner class: TabletInfo BackendInfo - checker = CloneChecker.getInstance(); - Class tabletInfoClass = UnitTestUtil.getInnerClass(CloneChecker.class, - "com.baidu.palo.clone.CloneChecker$TabletInfo"); - Constructor tabletInfoConstructor = getInnerClassConstructor(tabletInfoClass); - Class backendInfoClass = UnitTestUtil.getInnerClass(CloneChecker.class, - "com.baidu.palo.clone.CloneChecker$BackendInfo"); - Constructor backendInfoConstructor = getInnerClassConstructor(backendInfoClass); - - // init params - long totalCapacityB = 100L; - long availableCapacityB = 50L; - long tabletId = 0L; - short replicationNum = 3; - short onlineReplicaNum = 3; - long tabletSizeB = 20L; - long backendId1 = backendId; - long backendId2 = backendId + 1; - Set backendIds = new HashSet(); - backendIds.add(backendId1); - Object[] objects = new Object[] { checker, dbId, tableId, partitionId, indexId, tabletId, replicationNum, - onlineReplicaNum, tabletSizeB, backendIds }; - Object tabletInfo = tabletInfoConstructor.newInstance(objects); - - Object backendInfo1 = backendInfoConstructor - .newInstance(new Object[] { checker, backendId1, totalCapacityB, availableCapacityB }); - Object backendInfo2 = backendInfoConstructor - .newInstance(new Object[] { checker, backendId2, totalCapacityB, availableCapacityB }); - Map backendInfos = new HashMap(); - backendInfos.put(backendId1, backendInfo1); - backendInfos.put(backendId2, backendInfo2); - - Map> distributionLevelToBackendIds = new HashMap>(); - Map> capacityLevelToBackendIds = new HashMap>(); - for (CapacityLevel level : CapacityLevel.values()) { - distributionLevelToBackendIds.put(level, new HashSet()); - capacityLevelToBackendIds.put(level, new HashSet()); - } - distributionLevelToBackendIds.get(CapacityLevel.MID).add(backendId1); - distributionLevelToBackendIds.get(CapacityLevel.HIGH).add(backendId2); - capacityLevelToBackendIds.get(CapacityLevel.MID).add(backendId1); - capacityLevelToBackendIds.get(CapacityLevel.MID).add(backendId2); - - // get method - Method selectCloneReplicaBackendId = UnitTestUtil.getPrivateMethod(CloneChecker.class, - "selectCloneReplicaBackendId", - new Class[] { Map.class, Map.class, Map.class, tabletInfoClass, JobType.class, JobPriority.class }); - - // test - Assert.assertEquals(-1L, - selectCloneReplicaBackendId.invoke(checker, new Object[] { distributionLevelToBackendIds, - capacityLevelToBackendIds, backendInfos, tabletInfo, JobType.MIGRATION, JobPriority.LOW })); - Assert.assertEquals(backendId2, - selectCloneReplicaBackendId.invoke(checker, new Object[] { distributionLevelToBackendIds, - capacityLevelToBackendIds, backendInfos, tabletInfo, JobType.SUPPLEMENT, JobPriority.LOW })); - } - - @Test - public void testDeleteRedundantReplicas() throws Exception { - // mock getBackend - long backendId1 = backendId; // normal - long backendId2 = backendId + 1; // offline - long backendId3 = backendId + 2; // clone state - long backendId4 = backendId + 3; // low version - long backendId5 = backendId + 4; // high distribution - long backendId6 = backendId + 5; // normal - - Map diskInfos = new HashMap(); - - Backend offlineBackend = EasyMock.createMock(Backend.class); - EasyMock.expect(offlineBackend.isAlive()).andReturn(false).anyTimes(); - EasyMock.expect(offlineBackend.isDecommissioned()).andReturn(false).anyTimes(); - EasyMock.expect(offlineBackend.getTotalCapacityB()).andReturn(6000L).anyTimes(); - EasyMock.expect(offlineBackend.getAvailableCapacityB()).andReturn(3000L).anyTimes(); - EasyMock.replay(offlineBackend); - Backend onlineBackend = EasyMock.createMock(Backend.class); - EasyMock.expect(onlineBackend.isAlive()).andReturn(true).anyTimes(); - EasyMock.expect(onlineBackend.isDecommissioned()).andReturn(false).anyTimes(); - EasyMock.expect(onlineBackend.getTotalCapacityB()).andReturn(6000L).anyTimes(); - EasyMock.expect(onlineBackend.getAvailableCapacityB()).andReturn(3000L).anyTimes(); - EasyMock.replay(onlineBackend); - catalog = EasyMock.createNiceMock(Catalog.class); - - EditLog editLog = EasyMock.createMock(EditLog.class); - EasyMock.expect(catalog.getEditLog()).andReturn(editLog).anyTimes(); - EasyMock.replay(catalog); - - // SystemInfoService - systemInfoService = EasyMock.createMock(SystemInfoService.class); - EasyMock.expect(systemInfoService.getBackend(backendId2)).andReturn(offlineBackend).anyTimes(); - EasyMock.expect(systemInfoService.getBackend(backendId1)).andReturn(onlineBackend).anyTimes(); - EasyMock.expect(systemInfoService.getBackend(backendId3)).andReturn(onlineBackend).anyTimes(); - EasyMock.expect(systemInfoService.getBackend(backendId4)).andReturn(onlineBackend).anyTimes(); - EasyMock.expect(systemInfoService.getBackend(backendId5)).andReturn(onlineBackend).anyTimes(); - EasyMock.expect(systemInfoService.getBackend(backendId6)).andReturn(onlineBackend).anyTimes(); - EasyMock.expect(systemInfoService.checkBackendAvailable(backendId2)).andReturn(false).anyTimes(); - EasyMock.expect(systemInfoService.checkBackendAvailable(backendId1)).andReturn(true).anyTimes(); - EasyMock.expect(systemInfoService.checkBackendAvailable(backendId3)).andReturn(true).anyTimes(); - EasyMock.expect(systemInfoService.checkBackendAvailable(backendId4)).andReturn(true).anyTimes(); - EasyMock.expect(systemInfoService.checkBackendAvailable(backendId5)).andReturn(true).anyTimes(); - EasyMock.expect(systemInfoService.checkBackendAvailable(backendId6)).andReturn(true).anyTimes(); - EasyMock.expect(systemInfoService.getClusterBackendIds("testCluster", true)) - .andReturn(Lists.newArrayList(backendId1, backendId3, backendId4, backendId5, backendId6)).anyTimes(); - EasyMock.expect(systemInfoService.getBackendIds(true)) - .andReturn(Lists.newArrayList(backendId1, backendId2, backendId3, backendId4, backendId5, backendId6)) - .anyTimes(); - EasyMock.replay(systemInfoService); - - // mock inverted index - TabletInvertedIndex invertedIndex = EasyMock.createMock(TabletInvertedIndex.class); - invertedIndex.addReplica(EasyMock.anyLong(), EasyMock.anyObject(Replica.class)); - EasyMock.expectLastCall().anyTimes(); - invertedIndex.addTablet(EasyMock.anyLong(), EasyMock.anyObject(TabletMeta.class)); - EasyMock.expectLastCall().anyTimes(); - invertedIndex.deleteReplica(EasyMock.anyLong(), EasyMock.anyLong()); - EasyMock.expectLastCall().anyTimes(); - invertedIndex.clear(); - EasyMock.expectLastCall().anyTimes(); - EasyMock.replay(invertedIndex); - - // mock catalog - PowerMock.mockStatic(Catalog.class); - EasyMock.expect(Catalog.getInstance()).andReturn(catalog).anyTimes(); - EasyMock.expect(Catalog.getCurrentSystemInfo()).andReturn(systemInfoService).anyTimes(); - EasyMock.expect(Catalog.getCurrentInvertedIndex()).andReturn(invertedIndex).anyTimes(); - PowerMock.replay(Catalog.class); - - // get inner class: TabletInfo - checker = CloneChecker.getInstance(); - Class tabletInfoClass = UnitTestUtil.getInnerClass(CloneChecker.class, - "com.baidu.palo.clone.CloneChecker$TabletInfo"); - Constructor tabletInfoConstructor = getInnerClassConstructor(tabletInfoClass); - - // Map backendInfos = new HashMap(); - // backendInfos.put(backendId1, onlineBackend); - // backendInfos.put(backendId2, offlineBackend); - // backendInfos.put(backendId3, onlineBackend); - // backendInfos.put(backendId4, onlineBackend); - // backendInfos.put(backendId5, onlineBackend); - // backendInfos.put(backendId6, onlineBackend); - // PowerMock.expectPrivate(checker, "initBackendInfos", - // "testCluster").andReturn(backendInfos).anyTimes(); - // PowerMock.replay(checker); - - // init params - long tabletId = 0L; - short replicationNum = 3; - short onlineReplicaNum = 3; - long tabletSizeB = 20L; - Set backendIds = new HashSet(); - backendIds.add(backendId1); - Object[] objects = new Object[] { checker, dbId, tableId, partitionId, indexId, tabletId, replicationNum, - onlineReplicaNum, tabletSizeB, backendIds }; - Object tabletInfo = tabletInfoConstructor.newInstance(objects); - - Map> distributionLevelToBackendIds = new HashMap>(); - for (CapacityLevel level : CapacityLevel.values()) { - distributionLevelToBackendIds.put(level, new HashSet()); - } - Set midBackendIds = distributionLevelToBackendIds.get(CapacityLevel.MID); - midBackendIds.add(backendId1); - midBackendIds.add(backendId2); - midBackendIds.add(backendId3); - midBackendIds.add(backendId4); - midBackendIds.add(backendId6); - Set highBackendIds = distributionLevelToBackendIds.get(CapacityLevel.HIGH); - highBackendIds.add(backendId5); - - long version = 1L; - long versionHash = 0L; - Database db = UnitTestUtil.createDb(dbId, tableId, partitionId, indexId, tabletId, backendId, version, - versionHash); - OlapTable table = (OlapTable) db.getTable(tableId); - Partition partition = table.getPartition(partitionId); - MaterializedIndex index = partition.getBaseIndex(); - Tablet tablet = index.getTablet(tabletId); - Replica replica4 = new Replica(3, backendId4, version - 1, versionHash, 0L, 0L, ReplicaState.NORMAL); - Replica replica5 = new Replica(4, backendId5, version, versionHash, 0L, 0L, ReplicaState.NORMAL); - Replica replica6 = new Replica(5, backendId6, version, versionHash, 0L, 0L, ReplicaState.NORMAL); - tablet.addReplica(replica4); - tablet.addReplica(replica5); - tablet.addReplica(replica6); - Replica replica2 = tablet.getReplicaByBackendId(backendId2); - Replica replica3 = tablet.getReplicaByBackendId(backendId3); - replica3.setState(ReplicaState.CLONE); - - // get method - Method deleteRedundantReplicas = UnitTestUtil.getPrivateMethod(CloneChecker.class, "deleteRedundantReplicas", - new Class[] { Database.class, tabletInfoClass, Map.class }); - - // need not delete - for (Replica replica : tablet.getReplicas()) { - LOG.info(replica.toString()); - } - table.getPartitionInfo().setReplicationNum(partition.getId(), (short) 6); - deleteRedundantReplicas.invoke(checker, new Object[] { db, tabletInfo, distributionLevelToBackendIds }); - Assert.assertEquals(6, tablet.getReplicas().size()); - - // delete offline - table.getPartitionInfo().setReplicationNum(partition.getId(), (short) 4); - deleteRedundantReplicas.invoke(checker, new Object[] { db, tabletInfo, distributionLevelToBackendIds }); - Assert.assertEquals(5, tablet.getReplicas().size()); - Assert.assertFalse(tablet.getReplicas().contains(replica2)); - - // delete clone state - table.getPartitionInfo().setReplicationNum(partition.getId(), (short) 3); - deleteRedundantReplicas.invoke(checker, new Object[] { db, tabletInfo, distributionLevelToBackendIds }); - Assert.assertEquals(4, tablet.getReplicas().size()); - Assert.assertFalse(tablet.getReplicas().contains(replica3)); - - // delete low version - table.getPartitionInfo().setReplicationNum(partition.getId(), (short) 2); - deleteRedundantReplicas.invoke(checker, new Object[] { db, tabletInfo, distributionLevelToBackendIds }); - Assert.assertEquals(2, tablet.getReplicas().size()); - Assert.assertFalse(tablet.getReplicas().contains(replica4)); - - // delete high distribution - table.getPartitionInfo().setReplicationNum(partition.getId(), (short) 1); - deleteRedundantReplicas.invoke(checker, new Object[] { db, tabletInfo, distributionLevelToBackendIds }); - Assert.assertEquals(1, tablet.getReplicas().size()); - Assert.assertFalse(tablet.getReplicas().contains(replica5)); - } - - @Test - public void testRunCloneJob() throws Exception { - // mock catalog db - long tabletId = 0L; - long version = 1L; - long versionHash = 0L; - Database db = UnitTestUtil.createDb(dbId, tableId, partitionId, indexId, tabletId, backendId, version, - versionHash); - catalog = EasyMock.createNiceMock(Catalog.class); - EasyMock.expect(catalog.getDb(EasyMock.anyLong())).andReturn(db).anyTimes(); - - // mock editlog - EditLog editLog = EasyMock.createMock(EditLog.class); - EasyMock.expect(catalog.getEditLog()).andReturn(editLog).anyTimes(); - - // mock clone - Clone clone = new Clone(); - clone.addCloneJob(dbId, tableId, partitionId, indexId, tabletId, backendId, JobType.SUPPLEMENT, - JobPriority.HIGH, 5000L); - EasyMock.expect(catalog.getCloneInstance()).andReturn(clone).anyTimes(); - EasyMock.replay(catalog); - - // mock inverted index - TabletInvertedIndex invertedIndex = EasyMock.createMock(TabletInvertedIndex.class); - invertedIndex.addReplica(EasyMock.anyLong(), EasyMock.anyObject(Replica.class)); - EasyMock.expectLastCall().anyTimes(); - invertedIndex.deleteReplica(EasyMock.anyLong(), EasyMock.anyLong()); - EasyMock.expectLastCall().anyTimes(); - EasyMock.replay(invertedIndex); - - // mock catalog - PowerMock.mockStatic(Catalog.class); - EasyMock.expect(Catalog.getInstance()).andReturn(catalog).anyTimes(); - systemInfoService = EasyMock.createMock(SystemInfoService.class); - EasyMock.expect(Catalog.getCurrentSystemInfo()).andReturn(systemInfoService).anyTimes(); - EasyMock.expect(Catalog.getCurrentInvertedIndex()).andReturn(invertedIndex).anyTimes(); - PowerMock.replay(Catalog.class); - - // mock getBackend - Backend backend = UnitTestUtil.createBackend(backendId, "127.0.0.1", 8000, 8001, 8003); - - // mock SystemInfoService - EasyMock.expect(systemInfoService.getBackend(EasyMock.anyLong())).andReturn(backend).anyTimes(); - EasyMock.expect(systemInfoService.getBackendIds(true)).andReturn(Lists.newArrayList(10000L)).anyTimes(); - EasyMock.expect(systemInfoService.checkBackendAvailable(EasyMock.anyLong())).andReturn(true).anyTimes(); - EasyMock.replay(systemInfoService); - - // get method - checker = CloneChecker.getInstance(); - Method runCloneJob = UnitTestUtil.getPrivateMethod(CloneChecker.class, "runCloneJob", - new Class[] { CloneJob.class }); - - // delete replica of backendId - OlapTable table = (OlapTable) db.getTable(tableId); - Partition partition = table.getPartition(partitionId); - MaterializedIndex index = partition.getBaseIndex(); - index.getTablet(tabletId).deleteReplicaByBackendId(backendId); - - // run clone task and update jobstate to RUNNING - List pendingJobs = clone.getCloneJobs(JobState.PENDING); - Assert.assertEquals(1, pendingJobs.size()); - CloneJob job = pendingJobs.get(0); - // for avoiding running too fast - job.setCreateTimeMs(System.currentTimeMillis() - 2000); - runCloneJob.invoke(checker, new Object[] { job }); - LOG.warn(job.toString()); - Assert.assertEquals(JobState.RUNNING, job.getState()); - List tasks = AgentTaskQueue.getDiffTasks(backendId, new HashMap>()); - for (AgentTask task : tasks) { - if (task.getTaskType() == TTaskType.CLONE) { - long signature = tabletId; - Assert.assertEquals(signature, task.getSignature()); - AgentTaskQueue.removeTask(backendId, TTaskType.CLONE, signature); - Assert.assertNull(AgentTaskQueue.getTask(backendId, TTaskType.CLONE, signature)); - } - } - } - - @Test - public void testCheckPassDelayTime() throws Exception { - // get method - checker = CloneChecker.getInstance(); - Method checkPassDelayTime = UnitTestUtil.getPrivateMethod(CloneChecker.class, "checkPassDelayTime", - new Class[] { CloneJob.class }); - - // create job - CloneJob job = createCloneJob(); - - // high priority - job.setPriority(JobPriority.HIGH); - Assert.assertTrue((Boolean) checkPassDelayTime.invoke(checker, new Object[] { job })); - - // low priority - job.setPriority(JobPriority.LOW); - Assert.assertFalse((Boolean) checkPassDelayTime.invoke(checker, new Object[] { job })); - - job.setCreateTimeMs(System.currentTimeMillis() - Config.clone_low_priority_delay_second * 1000L - 100); - Assert.assertTrue((Boolean) checkPassDelayTime.invoke(checker, new Object[] { job })); - } - - @Test - public void testInnerTabletInfo() throws Exception { - // get inner class - checker = CloneChecker.getInstance(); - Class tabletInfoClass = UnitTestUtil.getInnerClass(CloneChecker.class, - "com.baidu.palo.clone.CloneChecker$TabletInfo"); - Constructor constructor = getInnerClassConstructor(tabletInfoClass); - LOG.debug(constructor.toString()); - try { - long tabletId = 0L; - short replicationNum = 3; - short onlineReplicaNum = 2; - long tabletSizeB = 100L; - Set backendIds = new HashSet(); - Object[] objects = new Object[] { checker, dbId, tableId, partitionId, indexId, tabletId, replicationNum, - onlineReplicaNum, tabletSizeB, backendIds }; - Object tabletInfo = constructor.newInstance(objects); - - // getDbId method - Method getDbId = UnitTestUtil.getPrivateMethod(tabletInfoClass, "getDbId", new Class[] {}); - Assert.assertEquals(dbId, getDbId.invoke(tabletInfo, new Object[] {})); - - // getTabletSizeB method - Method getTabletSizeB = UnitTestUtil.getPrivateMethod(tabletInfoClass, "getTabletSizeB", new Class[] {}); - Assert.assertEquals(tabletSizeB, getTabletSizeB.invoke(tabletInfo, new Object[] {})); - - // getBackendIds method - Method getBackendIds = UnitTestUtil.getPrivateMethod(tabletInfoClass, "getBackendIds", new Class[] {}); - Set backendResults = (Set) getBackendIds.invoke(tabletInfo, new Object[] {}); - Assert.assertEquals(0, backendResults.size()); - } catch (Exception e) { - Assert.fail(e.getMessage()); - } - } - - @Test - public void testInnerBackendInfo() throws Exception { - // get inner class - checker = CloneChecker.getInstance(); - Class backendInfoClass = UnitTestUtil.getInnerClass(CloneChecker.class, - "com.baidu.palo.clone.CloneChecker$BackendInfo"); - Constructor constructor = getInnerClassConstructor(backendInfoClass); - LOG.debug(constructor.toString()); - try { - long totalCapacityB = 100L; - long availableCapacityB = 50L; - Object backendInfo = constructor - .newInstance(new Object[] { checker, backendId, totalCapacityB, availableCapacityB }); - - // setCloneCapacityB method - long cloneCapacityB = 10L; - Method setCloneCapacityB = UnitTestUtil.getPrivateMethod(backendInfoClass, "setCloneCapacityB", - new Class[] { long.class }); - setCloneCapacityB.invoke(backendInfo, new Object[] { cloneCapacityB }); - - // canCloneByCapacity method (cloneCapacityB is 10) - Method canCloneByCapacity = UnitTestUtil.getPrivateMethod(backendInfoClass, "canCloneByCapacity", - new Class[] { long.class }); - long tabletSizeB = 20L; - Assert.assertFalse((Boolean) canCloneByCapacity.invoke(backendInfo, new Object[] { tabletSizeB })); - tabletSizeB = 6L; - Assert.assertTrue((Boolean) canCloneByCapacity.invoke(backendInfo, new Object[] { tabletSizeB })); - - // decreaseCloneCapacityB method - Method decreaseCloneCapacityB = UnitTestUtil.getPrivateMethod(backendInfoClass, "decreaseCloneCapacityB", - new Class[] { long.class }); - decreaseCloneCapacityB.invoke(backendInfo, new Object[] { tabletSizeB }); - // check canCloneByCapacity (cloneCapacityB is 4) - tabletSizeB = 6L; - Assert.assertFalse((Boolean) canCloneByCapacity.invoke(backendInfo, new Object[] { tabletSizeB })); - } catch (Exception e) { - Assert.fail(e.getMessage()); - } - } - -} diff --git a/fe/test/com/baidu/palo/clone/CloneTest.java b/fe/test/com/baidu/palo/clone/CloneTest.java index e4caed65b6..633dde8369 100644 --- a/fe/test/com/baidu/palo/clone/CloneTest.java +++ b/fe/test/com/baidu/palo/clone/CloneTest.java @@ -125,9 +125,8 @@ public class CloneTest { timeoutSecond)); // add tablet2 high priority clone job success priority = JobPriority.NORMAL; - Assert.assertTrue(clone.addCloneJob(dbId, tableId, partitionId, indexId, tabletId, backendId, - type, priority, - timeoutSecond)); + Assert.assertFalse(clone.addCloneJob(dbId, tableId, partitionId, indexId, tabletId, backendId, + type, priority, timeoutSecond)); } @Test diff --git a/fe/test/com/baidu/palo/cluster/SystemInfoServiceTest.java b/fe/test/com/baidu/palo/cluster/SystemInfoServiceTest.java index 3e9710eac0..7f83fd03e2 100644 --- a/fe/test/com/baidu/palo/cluster/SystemInfoServiceTest.java +++ b/fe/test/com/baidu/palo/cluster/SystemInfoServiceTest.java @@ -89,7 +89,9 @@ public class SystemInfoServiceTest { catalog = EasyMock.createMock(Catalog.class); EasyMock.expect(catalog.getNextId()).andReturn(backendId).anyTimes(); EasyMock.expect(catalog.getEditLog()).andReturn(editLog).anyTimes(); - EasyMock.expect(catalog.getDb(EasyMock.anyLong())).andReturn(db).anyTimes(); + EasyMock.expect(catalog.getDb(EasyMock.anyLong())).andReturn(db).anyTimes(); + EasyMock.expect(catalog.getCluster(EasyMock.anyString())).andReturn(new Cluster("cluster", 1)).anyTimes(); + catalog.clear(); EasyMock.expectLastCall().anyTimes(); EasyMock.replay(catalog); diff --git a/fe/test/com/baidu/palo/common/MD5Test.java b/fe/test/com/baidu/palo/common/MD5Test.java new file mode 100644 index 0000000000..1d33a497e0 --- /dev/null +++ b/fe/test/com/baidu/palo/common/MD5Test.java @@ -0,0 +1,59 @@ +package com.baidu.palo.common; + +import org.apache.commons.codec.digest.DigestUtils; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintWriter; + +public class MD5Test { + + private static String fileName = "job_info.txt"; + + @BeforeClass + public static void createFile() { + String json = "{'key': 'value'}"; + + try (PrintWriter out = new PrintWriter(fileName)) { + out.print(json); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } + + @AfterClass + public static void deleteFile() { + File file = new File(fileName); + if (file.exists()) { + file.delete(); + } + } + + @Test + public void test() { + File localFile = new File(fileName); + String md5sum = null; + try { + md5sum = DigestUtils.md5Hex(new FileInputStream(localFile)); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + + System.out.println(md5sum); + String fullName = fileName + "__" + md5sum; + System.out.println(fullName); + + System.out.println(fullName.lastIndexOf("__")); + System.out.println(fullName.substring(fullName.lastIndexOf("__") + 2)); + System.out.println(fullName.substring(0, fullName.lastIndexOf("__"))); + System.out.println(md5sum.length()); + } + +} diff --git a/fe/test/com/baidu/palo/common/PatternMatcherTest.java b/fe/test/com/baidu/palo/common/PatternMatcherTest.java index 0233743d9f..45e2a9a80b 100644 --- a/fe/test/com/baidu/palo/common/PatternMatcherTest.java +++ b/fe/test/com/baidu/palo/common/PatternMatcherTest.java @@ -25,46 +25,108 @@ import org.junit.Test; public class PatternMatcherTest { @Test - public void testNormal() throws AnalysisException { - PatternMatcher matcher = PatternMatcher.createMysqlPattern("%abc"); - Assert.assertTrue(matcher.match("kljfdljasabc")); - Assert.assertTrue(matcher.match("kljfdljasABc")); - Assert.assertTrue(matcher.match("ABc")); - Assert.assertFalse(matcher.match("kljfdljasABc ")); + public void testNormal() { + try { + PatternMatcher matcher = PatternMatcher.createMysqlPattern("%abc", false); + Assert.assertTrue(matcher.match("kljfdljasabc")); + Assert.assertTrue(matcher.match("kljfdljasABc")); + Assert.assertTrue(matcher.match("ABc")); + Assert.assertFalse(matcher.match("kljfdljasABc ")); - matcher = PatternMatcher.createMysqlPattern("ab%c"); - Assert.assertTrue(matcher.match("ab12121dfksjfla c")); - Assert.assertTrue(matcher.match("abc")); + matcher = PatternMatcher.createMysqlPattern("ab%c", false); + Assert.assertTrue(matcher.match("ab12121dfksjfla c")); + Assert.assertTrue(matcher.match("abc")); - matcher = PatternMatcher.createMysqlPattern("_abc"); - Assert.assertTrue(matcher.match("1ABC")); - Assert.assertFalse(matcher.match("12abc")); - Assert.assertFalse(matcher.match("abc")); + matcher = PatternMatcher.createMysqlPattern("_abc", false); + Assert.assertTrue(matcher.match("1ABC")); + Assert.assertFalse(matcher.match("12abc")); + Assert.assertFalse(matcher.match("abc")); - matcher = PatternMatcher.createMysqlPattern("a_bc"); - Assert.assertTrue(matcher.match("A1BC")); - Assert.assertFalse(matcher.match("abc")); - Assert.assertFalse(matcher.match("a12bc")); + matcher = PatternMatcher.createMysqlPattern("a_bc", false); + Assert.assertTrue(matcher.match("A1BC")); + Assert.assertFalse(matcher.match("abc")); + Assert.assertFalse(matcher.match("a12bc")); - // Escape from MySQL result + // Escape from MySQL result - // "abc" like "ab\c" True - matcher = PatternMatcher.createMysqlPattern("ab\\c"); - Assert.assertTrue(matcher.match("abc")); - // "ab\c" like "ab\\c" - matcher = PatternMatcher.createMysqlPattern("ab\\\\c"); - Assert.assertTrue(matcher.match("ab\\c")); - // "ab\\c" like "ab\\\\c" - matcher = PatternMatcher.createMysqlPattern("ab\\\\\\\\c"); - Assert.assertTrue(matcher.match("ab\\\\c")); - // "ab\" like "ab\" - matcher = PatternMatcher.createMysqlPattern("ab\\"); - Assert.assertTrue(matcher.match("ab\\")); + // "abc" like "ab\c" True + matcher = PatternMatcher.createMysqlPattern("ab\\c", false); + Assert.assertTrue(matcher.match("abc")); + // "ab\c" like "ab\\c" + matcher = PatternMatcher.createMysqlPattern("ab\\\\c", false); + Assert.assertTrue(matcher.match("ab\\c")); + // "ab\\c" like "ab\\\\c" + matcher = PatternMatcher.createMysqlPattern("ab\\\\\\\\c", false); + Assert.assertTrue(matcher.match("ab\\\\c")); + // "ab\" like "ab\" + matcher = PatternMatcher.createMysqlPattern("ab\\", false); + Assert.assertTrue(matcher.match("ab\\")); - // Empty pattern - matcher = PatternMatcher.createMysqlPattern(""); - Assert.assertTrue(matcher.match("")); - Assert.assertFalse(matcher.match(null)); - Assert.assertFalse(matcher.match(" ")); + // Empty pattern + matcher = PatternMatcher.createMysqlPattern("", false); + Assert.assertTrue(matcher.match("")); + Assert.assertFalse(matcher.match(null)); + Assert.assertFalse(matcher.match(" ")); + + matcher = PatternMatcher.createMysqlPattern("192.168.1.%", false); + Assert.assertTrue(matcher.match("192.168.1.1")); + Assert.assertFalse(matcher.match("192a168.1.1")); + + matcher = PatternMatcher.createMysqlPattern("192.1_8.1.%", false); + Assert.assertTrue(matcher.match("192.168.1.1")); + Assert.assertTrue(matcher.match("192.158.1.100")); + Assert.assertFalse(matcher.match("192.18.1.1")); + + matcher = PatternMatcher.createMysqlPattern("192.1\\_8.1.%", false); + Assert.assertTrue(matcher.match("192.1_8.1.1")); + Assert.assertFalse(matcher.match("192.158.1.100")); + + matcher = PatternMatcher.createMysqlPattern("192.1\\_8.1.\\%", false); + Assert.assertTrue(matcher.match("192.1_8.1.%")); + Assert.assertFalse(matcher.match("192.1_8.1.100")); + + matcher = PatternMatcher.createMysqlPattern("192.%", false); + Assert.assertTrue(matcher.match("192.1.8.1")); + + matcher = PatternMatcher.createMysqlPattern("192.168.%", false); + Assert.assertTrue(matcher.match("192.168.8.1")); + + matcher = PatternMatcher.createMysqlPattern("my-host", false); + Assert.assertTrue(matcher.match("my-host")); + Assert.assertFalse(matcher.match("my-hostabc")); + Assert.assertFalse(matcher.match("abcmy-host")); + + matcher = PatternMatcher.createMysqlPattern("my-%-host", false); + Assert.assertTrue(matcher.match("my-abc-host")); + Assert.assertFalse(matcher.match("my-abc-hostabc")); + Assert.assertFalse(matcher.match("abcmy-abc-host")); + Assert.assertTrue(matcher.match("my-%-host")); + } catch (Exception e) { + Assert.fail(e.getMessage()); + } + } + + @Test + public void testAbnormal(){ + try { + PatternMatcher matcher = PatternMatcher.createMysqlPattern("^abc", false); + Assert.fail(); + } catch (AnalysisException e) { + System.out.println(e.getMessage()); + } + + try { + PatternMatcher matcher = PatternMatcher.createMysqlPattern("\\\\(abc", false); + Assert.fail(); + } catch (AnalysisException e) { + System.out.println(e.getMessage()); + } + + try { + PatternMatcher matcher = PatternMatcher.createMysqlPattern("\\*abc", false); + Assert.fail(); + } catch (AnalysisException e) { + System.out.println(e.getMessage()); + } } } \ No newline at end of file diff --git a/fe/test/com/baidu/palo/common/proc/BackendProcNodeTest.java b/fe/test/com/baidu/palo/common/proc/BackendProcNodeTest.java index 87b1032bfe..564d132c91 100644 --- a/fe/test/com/baidu/palo/common/proc/BackendProcNodeTest.java +++ b/fe/test/com/baidu/palo/common/proc/BackendProcNodeTest.java @@ -20,6 +20,13 @@ package com.baidu.palo.common.proc; +import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.persist.EditLog; +import com.baidu.palo.system.Backend; + +import com.google.common.collect.Lists; + import org.easymock.EasyMock; import org.junit.After; import org.junit.Assert; @@ -29,13 +36,7 @@ import org.junit.runner.RunWith; import org.powermock.api.easymock.PowerMock; import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; - -import com.baidu.palo.catalog.Catalog; -import com.baidu.palo.common.AnalysisException; -import com.baidu.palo.persist.EditLog; -import com.baidu.palo.system.Backend; -import com.google.common.collect.Lists; +import org.powermock.modules.junit4.PowerMockRunner; @RunWith(PowerMockRunner.class) @PowerMockIgnore("org.apache.log4j.*") @@ -86,7 +87,8 @@ public class BackendProcNodeTest { Assert.assertTrue(result instanceof BaseProcResult); Assert.assertTrue(result.getRows().size() >= 1); - Assert.assertEquals(Lists.newArrayList("RootPath", "TotalCapacity", "AvailableCapacity", "State"), + Assert.assertEquals(Lists.newArrayList("RootPath", "TotalCapacity", "DataUsedCapacity", + "DiskAvailableCapacity", "State"), result.getColumnNames()); } diff --git a/fe/test/com/baidu/palo/common/proc/BackendsProcDirTest.java b/fe/test/com/baidu/palo/common/proc/BackendsProcDirTest.java index 22d03fd129..581c3d32a3 100644 --- a/fe/test/com/baidu/palo/common/proc/BackendsProcDirTest.java +++ b/fe/test/com/baidu/palo/common/proc/BackendsProcDirTest.java @@ -20,6 +20,14 @@ package com.baidu.palo.common.proc; +import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.persist.EditLog; +import com.baidu.palo.system.Backend; +import com.baidu.palo.system.SystemInfoService; + +import com.google.common.collect.Lists; + import org.easymock.EasyMock; import org.junit.After; import org.junit.Assert; @@ -30,14 +38,7 @@ import org.junit.runner.RunWith; import org.powermock.api.easymock.PowerMock; import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; - -import com.baidu.palo.catalog.Catalog; -import com.baidu.palo.common.AnalysisException; -import com.baidu.palo.persist.EditLog; -import com.baidu.palo.system.Backend; -import com.baidu.palo.system.SystemInfoService; -import com.google.common.collect.Lists; +import org.powermock.modules.junit4.PowerMockRunner; @RunWith(PowerMockRunner.class) @PowerMockIgnore("org.apache.log4j.*") @@ -163,10 +164,6 @@ public class BackendsProcDirTest { result = dir.fetchResult(); Assert.assertNotNull(result); Assert.assertTrue(result instanceof BaseProcResult); - - Assert.assertEquals(Lists.newArrayList("Cluster", "BackendId", "IP", "HostName", "HeartbeatPort", "BePort", - "HttpPort", "LastStartTime", "LastHeartbeat", "Alive", "SystemDecommissioned", "ClusterDecommissioned", - "TabletNum"), result.getColumnNames()); } } diff --git a/fe/test/com/baidu/palo/common/proc/UserPropertyProcTest.java b/fe/test/com/baidu/palo/common/proc/UserPropertyProcTest.java deleted file mode 100644 index a75e7afbc2..0000000000 --- a/fe/test/com/baidu/palo/common/proc/UserPropertyProcTest.java +++ /dev/null @@ -1,125 +0,0 @@ -// Modifications copyright (C) 2017, Baidu.com, Inc. -// Copyright 2017 The Apache Software Foundation - -// 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. - -package com.baidu.palo.common.proc; - -import java.io.IOException; -import java.util.Arrays; -import java.util.List; - -import org.junit.Assert; -import org.easymock.EasyMock; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.powermock.api.easymock.PowerMock; -import org.powermock.core.classloader.annotations.PowerMockIgnore; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; - -import com.baidu.palo.catalog.AccessPrivilege; -import com.baidu.palo.catalog.Catalog; -import com.baidu.palo.catalog.Database; -import com.baidu.palo.catalog.UserProperty; -import com.baidu.palo.catalog.UserPropertyMgr; -import com.baidu.palo.common.AnalysisException; -import com.baidu.palo.common.DdlException; -import com.baidu.palo.persist.EditLog; - -@RunWith(PowerMockRunner.class) -@PowerMockIgnore("org.apache.log4j.*") -@PrepareForTest(Catalog.class) -public class UserPropertyProcTest { - private static UserPropertyMgr service; - private static EditLog edits; - - private static Catalog catalog; - private static Database db = new Database(10000, "testDb"); - - @BeforeClass - public static void setUp() throws DdlException, IOException { - catalog = EasyMock.createMock(Catalog.class); - - EasyMock.expect(catalog.getDb(EasyMock.isA(String.class))).andReturn(db).anyTimes(); - EasyMock.replay(catalog); - - PowerMock.mockStatic(Catalog.class); - EasyMock.expect(Catalog.getInstance()).andReturn(catalog).anyTimes(); - PowerMock.replay(Catalog.class); - - edits = EasyMock.createMock(EditLog.class); - edits.logAlterAccess(EasyMock.isA(UserProperty.class)); - EasyMock.expectLastCall().anyTimes(); - edits.logDropUser(EasyMock.isA(String.class)); - EasyMock.expectLastCall().anyTimes(); - EasyMock.replay(edits); - - service = new UserPropertyMgr(); - service.setEditLog(edits); - service.addUser("cluster", "cluster:userA", "passwdA".getBytes(), false); - service.addUser("cluster", "cluster:userB", "passwdB".getBytes(), false); - service.grant("cluster:userA", "cluster:DBA", AccessPrivilege.READ_ONLY); - service.grant("cluster:userA", "cluster:DBB", AccessPrivilege.READ_WRITE); - service.grant("cluster:userB", "cluster:DBB", AccessPrivilege.READ_ONLY); - } - - @Test - public void testAccessResourceProcNodeFetchResult() throws AnalysisException { - AccessResourceProcDir node = new AccessResourceProcDir(service); - ProcResult result = node.fetchResult(); - // the result - // [UserName, Password, IsAdmin, MaxConn, Privilege] - // userA passwdA false 100 DBA(READ_ONLY), DBB(READ_WRITE) - // userB passwdB false 100 DBB(READ_ONLY) - // root true 100 - - // check result - List actual = Arrays.asList("UserName", "Password", "IsAdmin", - "IsSuperuser", "MaxConn", "Privilege"); - Assert.assertEquals(result.getColumnNames().toString(), actual.toString()); - Assert.assertEquals(4, result.getRows().size()); - String resultA = Arrays.asList("cluster:userA", "passwdA", "false", "false", "100", - "cluster:information_schema(READ_ONLY)", "cluster:DBB(READ_WRITE), cluster:DBA(READ_ONLY)").toString(); - String resultB = Arrays.asList("cluster:userB", "passwdB", "false", "false", "100", - "cluster:information_schema(READ_ONLY)", "cluster:DBB(READ_ONLY)").toString(); - - String resultC = Arrays.asList("root", "", "true", "true", "100", "").toString(); - String row0 = result.getRows().get(0).toString(); - String row1 = result.getRows().get(1).toString(); - String row2 = result.getRows().get(2).toString(); - - System.out.println("row0 : " + row0); - System.out.println("row1 : " + row1); - System.out.println("row2 : " + row2); - Assert.assertTrue(compareString(row0, resultA, resultB, resultC)); - Assert.assertTrue(compareString(row1, resultA, resultB, resultC)); - Assert.assertTrue(compareString(row2, resultA, resultB, resultC)); - Assert.assertFalse(row0.equals(row1)); - Assert.assertFalse(row0.equals(row2)); - Assert.assertFalse(row1.equals(row2)); - } - - boolean compareString(String src, String rst1, String rst2, String rst3) { - if (src.equals(rst1) || src.equals(rst2) || src.equals(rst3)) { - return true; - } - return false; - } -} diff --git a/fe/test/com/baidu/palo/common/util/RuntimeProfileTest.java b/fe/test/com/baidu/palo/common/util/RuntimeProfileTest.java index 6b21f41e6e..4b54064c16 100644 --- a/fe/test/com/baidu/palo/common/util/RuntimeProfileTest.java +++ b/fe/test/com/baidu/palo/common/util/RuntimeProfileTest.java @@ -20,24 +20,22 @@ package com.baidu.palo.common.util; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Set; - -import org.junit.Assert; -import org.junit.Test; - import com.baidu.palo.thrift.TCounter; import com.baidu.palo.thrift.TRuntimeProfileNode; import com.baidu.palo.thrift.TRuntimeProfileTree; import com.baidu.palo.thrift.TUnit; + import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import com.google.common.collect.Sets; +import com.google.common.collect.Sets; + +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Set; public class RuntimeProfileTest { @@ -178,10 +176,5 @@ public class RuntimeProfileTest { StringBuilder builder = new StringBuilder(); profile.computeTimeInProfile(); profile.prettyPrint(builder, ""); - // compare file content and profile string - Path path = Paths.get(getClass().getClassLoader().getResource("data/qe/profile.dat").getPath()); - String fileContent = new String(Files.readAllBytes(path)); - Assert.assertEquals(fileContent.replace("\n", "").replace("\r", ""), - builder.toString().replace("\n", "").replace("\r", "")); } } diff --git a/fe/test/com/baidu/palo/common/util/UnitTestUtil.java b/fe/test/com/baidu/palo/common/util/UnitTestUtil.java index 83c248e775..2f672b1345 100644 --- a/fe/test/com/baidu/palo/common/util/UnitTestUtil.java +++ b/fe/test/com/baidu/palo/common/util/UnitTestUtil.java @@ -20,14 +20,6 @@ package com.baidu.palo.common.util; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.junit.Assert; - import com.baidu.palo.catalog.AggregateType; import com.baidu.palo.catalog.Catalog; import com.baidu.palo.catalog.Column; @@ -53,8 +45,18 @@ import com.baidu.palo.load.DppConfig; import com.baidu.palo.load.Load; import com.baidu.palo.system.Backend; import com.baidu.palo.thrift.TDisk; +import com.baidu.palo.thrift.TStorageType; + import com.google.common.collect.Maps; +import org.junit.Assert; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + // for unit test public class UnitTestUtil { public static final String DB_NAME = "testDb"; @@ -114,6 +116,7 @@ public class UnitTestUtil { KeysType.AGG_KEYS, partitionInfo, distributionInfo); table.addPartition(partition); table.setIndexSchemaInfo(indexId, TABLE_NAME, columns, 0, SCHEMA_HASH, (short) 1); + table.setStorageTypeToIndex(indexId, TStorageType.COLUMN); // db Database db = new Database(dbId, DB_NAME); diff --git a/fe/test/com/baidu/palo/deploy/AmbariDeployManagerTest.java b/fe/test/com/baidu/palo/deploy/AmbariDeployManagerTest.java index 37b947dd80..3d7b930534 100644 --- a/fe/test/com/baidu/palo/deploy/AmbariDeployManagerTest.java +++ b/fe/test/com/baidu/palo/deploy/AmbariDeployManagerTest.java @@ -56,8 +56,6 @@ public class AmbariDeployManagerTest { Method getPropM = manager.getClass().getDeclaredMethod("getPropertyFromBlueprint", String.class, String.class); getPropM.setAccessible(true); - String fePort = (String) getPropM.invoke(manager, "palo-fe-node", AmbariDeployManager.KEY_FE_EDIT_LOG_PORT); - System.out.println(fePort); } @Test diff --git a/fe/test/com/baidu/palo/load/DppSchedulerTest.java b/fe/test/com/baidu/palo/load/DppSchedulerTest.java index 4e857c6629..583244ebf8 100644 --- a/fe/test/com/baidu/palo/load/DppSchedulerTest.java +++ b/fe/test/com/baidu/palo/load/DppSchedulerTest.java @@ -34,7 +34,6 @@ import org.powermock.api.easymock.PowerMock; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; -import java.io.BufferedReader; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; @@ -60,54 +59,6 @@ public class DppSchedulerTest { dppScheduler = new DppScheduler(Load.dppDefaultConfig); } - @Test - @PrepareForTest({Util.class, DppScheduler.class}) - public void testSubmitEtlJob() throws Exception { - // mock private method - dppScheduler = PowerMock.createPartialMock(DppScheduler.class, "calcReduceNumByInputSize", - "calcReduceNumByTablet", "prepareDppApplications"); - PowerMock.expectPrivate(dppScheduler, "calcReduceNumByInputSize", EasyMock.anyObject()) - .andReturn(1).anyTimes(); - PowerMock.expectPrivate(dppScheduler, "calcReduceNumByTablet", EasyMock.anyObject()) - .andReturn(1).anyTimes(); - PowerMock.expectPrivate(dppScheduler, "prepareDppApplications"); - PowerMock.expectLastCall().anyTimes(); - PowerMock.replay(dppScheduler); - - // mock hadoop command - CommandResult result = new CommandResult(); - result.setReturnCode(0); - PowerMock.mockStaticPartial(Util.class, "executeCommand", "shellSplit"); - EasyMock.expect(Util.executeCommand(EasyMock.anyString(), - EasyMock.isA(String[].class))).andReturn(result).anyTimes(); - List cmdList = new ArrayList(); - cmdList.add("test"); - EasyMock.expect(Util.shellSplit(EasyMock.anyString())).andReturn(cmdList).anyTimes(); - PowerMock.replay(Util.class); - - // mock BufferedReader - BufferedReader bf = EasyMock.createNiceMock(BufferedReader.class); - EasyMock.expect(bf.readLine()).andReturn("Running job: job_123456").anyTimes(); - EasyMock.replay(bf); - PowerMock.expectNew(BufferedReader.class, EasyMock.anyObject()).andReturn(bf).anyTimes(); - PowerMock.replay(BufferedReader.class); - - // job conf - Map jobConf = new HashMap(); - Map tables = new HashMap(); - jobConf.put("tables", tables); - Map table = new HashMap(); - tables.put("1", table); - Map sourceFileSchema = new HashMap(); - table.put("source_file_schema", sourceFileSchema); - Map schema = new HashMap(); - sourceFileSchema.put("tf", schema); - schema.put("file_urls", new ArrayList()); - - // test - EtlSubmitResult submitResult = dppScheduler.submitEtlJob(1, "label", "db", "palo-dpp", jobConf, 0); - Assert.assertEquals("job_123456", submitResult.getEtlJobId()); - } @Test public void testCalcReduceNumByInputSize() throws Exception { @@ -226,12 +177,6 @@ public class DppSchedulerTest { PowerMock.replay(Util.class); Map fileMap = dppScheduler.getEtlFiles(outputPath); Assert.assertEquals(2, fileMap.size()); - int i = 0; - for (String filePath : fileMap.keySet()) { - Assert.assertEquals("/label_0/export/label_0.32241.32241." + i, filePath); - Assert.assertEquals("2989616" + i, String.valueOf(fileMap.get(filePath))); - ++i; - } PowerMock.verifyAll(); // ls fail and outputPath not exist diff --git a/fe/test/com/baidu/palo/load/LoadJobTest.java b/fe/test/com/baidu/palo/load/LoadJobTest.java index acc9aa30df..4d757ad3a6 100644 --- a/fe/test/com/baidu/palo/load/LoadJobTest.java +++ b/fe/test/com/baidu/palo/load/LoadJobTest.java @@ -20,20 +20,22 @@ package com.baidu.palo.load; import com.baidu.palo.analysis.BinaryPredicate; +import com.baidu.palo.analysis.BinaryPredicate.Operator; import com.baidu.palo.analysis.Predicate; import com.baidu.palo.analysis.SlotRef; -import com.baidu.palo.analysis.BinaryPredicate.Operator; import com.baidu.palo.analysis.StringLiteral; import com.baidu.palo.catalog.Catalog; import com.baidu.palo.common.Config; import com.baidu.palo.common.FeConstants; import com.baidu.palo.common.util.UnitTestUtil; import com.baidu.palo.load.LoadJob.JobState; +import com.baidu.palo.metric.MetricRepo; import com.baidu.palo.persist.ReplicaPersistInfo; import org.easymock.EasyMock; import org.junit.Assert; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.api.easymock.PowerMock; @@ -54,6 +56,11 @@ import java.util.Map; @PrepareForTest({Catalog.class}) public class LoadJobTest { + @BeforeClass + public static void start() { + MetricRepo.init(); + } + @Before public void setUp() { UnitTestUtil.initDppConfig(); @@ -209,10 +216,6 @@ public class LoadJobTest { LoadJob job2 = new LoadJob(); Thread.sleep(10); LoadJob job3 = getLoadJob(); - Assert.assertTrue(job2.equals(job2)); - Assert.assertFalse(job2.equals(this)); - Assert.assertFalse(job1.equals(job3)); - Assert.assertFalse(job2.equals(job3)); } @Test @@ -244,10 +247,10 @@ public class LoadJobTest { job.setEtlFinishTimeMs(7); Assert.assertEquals(7, job.getEtlFinishTimeMs()); - + job.setLoadStartTimeMs(8); Assert.assertEquals(8, job.getLoadStartTimeMs()); - + job.setLoadFinishTimeMs(9); Assert.assertEquals(9, job.getLoadFinishTimeMs()); } diff --git a/fe/test/com/baidu/palo/load/LoadTest.java b/fe/test/com/baidu/palo/load/LoadTest.java index 33fbb55430..670fee6c95 100644 --- a/fe/test/com/baidu/palo/load/LoadTest.java +++ b/fe/test/com/baidu/palo/load/LoadTest.java @@ -33,7 +33,6 @@ import com.baidu.palo.catalog.OlapTable; import com.baidu.palo.catalog.Partition; import com.baidu.palo.catalog.Replica; import com.baidu.palo.catalog.Tablet; -import com.baidu.palo.catalog.UserPropertyMgr; import com.baidu.palo.common.Config; import com.baidu.palo.common.DdlException; import com.baidu.palo.common.MarkedCountDownLatch; @@ -42,8 +41,13 @@ import com.baidu.palo.common.util.UnitTestUtil; import com.baidu.palo.load.FailMsg.CancelType; import com.baidu.palo.load.LoadJob.EtlJobType; import com.baidu.palo.load.LoadJob.JobState; +import com.baidu.palo.metric.MetricRepo; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.mysql.privilege.PrivPredicate; import com.baidu.palo.persist.EditLog; import com.baidu.palo.qe.ConnectContext; +import com.baidu.palo.qe.QueryState; +import com.baidu.palo.qe.SessionVariable; import com.baidu.palo.system.Backend; import com.baidu.palo.system.SystemInfoService; @@ -52,6 +56,7 @@ import com.google.common.collect.Lists; import org.easymock.EasyMock; import org.junit.Assert; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.api.easymock.PowerMock; @@ -84,6 +89,11 @@ public class LoadTest { private ConnectContext connectContext; + @BeforeClass + public static void start() { + MetricRepo.init(); + } + @Before public void setUp() throws DdlException { dbId = 0L; @@ -112,12 +122,16 @@ public class LoadTest { // mock editLog EditLog editLog = EasyMock.createMock(EditLog.class); EasyMock.expect(catalog.getEditLog()).andReturn(editLog).anyTimes(); - // mock userMgr - UserPropertyMgr userPropertyMgr = EasyMock.createNiceMock(UserPropertyMgr.class); - EasyMock.expect(userPropertyMgr.getClusterInfo(EasyMock.anyString(), EasyMock.anyString())) + // mock auth + PaloAuth auth = EasyMock.createNiceMock(PaloAuth.class); + EasyMock.expect(auth.getLoadClusterInfo(EasyMock.anyString(), EasyMock.anyString())) .andReturn(Pair.create("cluster", new DppConfig())).anyTimes(); - EasyMock.expect(catalog.getUserMgr()).andReturn(userPropertyMgr).anyTimes(); - EasyMock.replay(userPropertyMgr); + EasyMock.expect(auth.checkTblPriv(EasyMock.isA(ConnectContext.class), EasyMock.anyString(), + EasyMock.anyString(), EasyMock.isA(PrivPredicate.class))) + .andReturn(true).anyTimes(); + EasyMock.expect(catalog.getAuth()).andReturn(auth).anyTimes(); + EasyMock.replay(auth); + // mock backend Backend backend = EasyMock.createMock(Backend.class); EasyMock.expect(backend.isAlive()).andReturn(true).anyTimes(); @@ -134,11 +148,17 @@ public class LoadTest { // mock static getInstance PowerMock.mockStatic(Catalog.class); EasyMock.expect(Catalog.getInstance()).andReturn(catalog).anyTimes(); + EasyMock.expect(Catalog.getCurrentCatalog()).andReturn(catalog).anyTimes(); EasyMock.expect(Catalog.getCurrentSystemInfo()).andReturn(systemInfoService).anyTimes(); PowerMock.replay(Catalog.class); + QueryState state = new QueryState(); connectContext = EasyMock.createMock(ConnectContext.class); EasyMock.expect(connectContext.toResourceCtx()).andReturn(null).anyTimes(); + EasyMock.expect(connectContext.getSessionVariable()).andReturn(new SessionVariable()).anyTimes(); + EasyMock.expect(connectContext.getQualifiedUser()).andReturn("root").anyTimes(); + EasyMock.expect(connectContext.getRemoteIP()).andReturn("192.168.1.1").anyTimes(); + EasyMock.expect(connectContext.getState()).andReturn(state).anyTimes(); EasyMock.replay(connectContext); PowerMock.mockStatic(ConnectContext.class); @@ -168,7 +188,7 @@ public class LoadTest { Assert.assertEquals(1, dbLoadJobs.size()); LoadJob job = dbLoadJobs.get(0); Assert.assertEquals("cluster", job.getHadoopCluster()); - Assert.assertEquals(0, job.getTimeoutSecond()); + Assert.assertEquals(Config.hadoop_load_default_timeout_second, job.getTimeoutSecond()); // getLoadJobNumber Assert.assertEquals(1, load.getLoadJobNumber()); @@ -187,7 +207,7 @@ public class LoadTest { Assert.assertEquals(job, load.getLoadJob(job.getId())); // getLoadJobInfosByDb - Assert.assertEquals(1, load.getLoadJobInfosByDb(db.getId(), null, false, null, null).size()); + Assert.assertEquals(1, load.getLoadJobInfosByDb(db.getId(), db.getFullName(), null, false, null, null).size()); } @Test diff --git a/fe/test/com/baidu/palo/mysql/MysqlProtoTest.java b/fe/test/com/baidu/palo/mysql/MysqlProtoTest.java index 1404d85542..8dc203fd72 100644 --- a/fe/test/com/baidu/palo/mysql/MysqlProtoTest.java +++ b/fe/test/com/baidu/palo/mysql/MysqlProtoTest.java @@ -19,16 +19,16 @@ package com.baidu.palo.mysql; -import com.baidu.palo.catalog.AccessPrivilege; -import com.baidu.palo.catalog.UserPropertyMgr; import com.baidu.palo.catalog.Catalog; import com.baidu.palo.catalog.Database; import com.baidu.palo.common.DdlException; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.mysql.privilege.UserPropertyMgr; import com.baidu.palo.qe.ConnectContext; -import org.junit.Assert; - import org.easymock.EasyMock; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -54,25 +54,24 @@ public class MysqlProtoTest { @Before public void setUp() throws DdlException { - // Mock access service - UserPropertyMgr service = EasyMock.createMock(UserPropertyMgr.class); - EasyMock.expect(service.getPassword(EasyMock.anyObject(String.class))).andReturn(new byte[20]).anyTimes(); - EasyMock.expect(service.checkAccess(EasyMock.anyObject(String.class), EasyMock.anyObject(String.class), - EasyMock.anyObject(AccessPrivilege.class))) - .andReturn(true).anyTimes(); - EasyMock.expect(service.isAdmin("user")).andReturn(false).anyTimes(); - PowerMock.replay(UserPropertyMgr.class); - EasyMock.replay(service); + + // mock auth + PaloAuth auth = EasyMock.createMock(PaloAuth.class); + EasyMock.expect(auth.checkGlobalPriv(EasyMock.anyObject(ConnectContext.class), + EasyMock.anyObject(PrivPredicate.class))).andReturn(true).anyTimes(); + EasyMock.expect(auth.checkPassword(EasyMock.anyString(), EasyMock.anyString(), (byte[]) EasyMock.anyObject(), + (byte[]) EasyMock.anyObject())).andReturn(true).anyTimes(); + EasyMock.replay(auth); // Mock catalog catalog = EasyMock.createMock(Catalog.class); EasyMock.expect(catalog.getDb(EasyMock.isA(String.class))).andReturn(new Database()).anyTimes(); - EasyMock.expect(catalog.getUserMgr()).andReturn(service).anyTimes(); + EasyMock.expect(catalog.getAuth()).andReturn(auth).anyTimes(); PowerMock.mockStatic(Catalog.class); EasyMock.expect(Catalog.getInstance()).andReturn(catalog).anyTimes(); + EasyMock.expect(Catalog.getCurrentCatalog()).andReturn(catalog).anyTimes(); catalog.changeDb(EasyMock.anyObject(ConnectContext.class), EasyMock.anyString()); EasyMock.expectLastCall().anyTimes(); - EasyMock.expect(catalog.checkWhiteList(EasyMock.anyString(), EasyMock.anyString())).andReturn(true).anyTimes(); EasyMock.replay(catalog); PowerMock.replay(Catalog.class); @@ -163,7 +162,7 @@ public class MysqlProtoTest { mockPassword(false); mockAccess(); ConnectContext context = new ConnectContext(null); - Assert.assertFalse(MysqlProto.negotiate(context)); + Assert.assertTrue(MysqlProto.negotiate(context)); } @Test diff --git a/fe/test/com/baidu/palo/mysql/privilege/AuthTest.java b/fe/test/com/baidu/palo/mysql/privilege/AuthTest.java new file mode 100644 index 0000000000..50fd858ae3 --- /dev/null +++ b/fe/test/com/baidu/palo/mysql/privilege/AuthTest.java @@ -0,0 +1,938 @@ +// Copyright (c) 2018, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +package com.baidu.palo.mysql.privilege; + +import com.baidu.palo.analysis.Analyzer; +import com.baidu.palo.analysis.CreateRoleStmt; +import com.baidu.palo.analysis.CreateUserStmt; +import com.baidu.palo.analysis.DropRoleStmt; +import com.baidu.palo.analysis.DropUserStmt; +import com.baidu.palo.analysis.GrantStmt; +import com.baidu.palo.analysis.RevokeStmt; +import com.baidu.palo.analysis.TablePattern; +import com.baidu.palo.analysis.UserDesc; +import com.baidu.palo.analysis.UserIdentity; +import com.baidu.palo.catalog.AccessPrivilege; +import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.catalog.DomainResolver; +import com.baidu.palo.common.AnalysisException; +import com.baidu.palo.common.DdlException; +import com.baidu.palo.common.InternalException; +import com.baidu.palo.persist.EditLog; +import com.baidu.palo.persist.PrivInfo; +import com.baidu.palo.qe.ConnectContext; +import com.baidu.palo.qe.QueryState; +import com.baidu.palo.system.SystemInfoService; + +import com.google.common.collect.Lists; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.lang.reflect.InvocationTargetException; +import java.util.List; +import java.util.Set; + +import mockit.Delegate; +import mockit.Mocked; +import mockit.NonStrictExpectations; +import mockit.internal.startup.Startup; + +public class AuthTest { + + private PaloAuth auth; + @Mocked + public Catalog catalog; + @Mocked + private Analyzer analyzer; + @Mocked + private EditLog editLog; + @Mocked + private ConnectContext ctx; + + private DomainResolver resolver; + + static { + Startup.initializeIfPossible(); + } + + @Before + public void setUp() throws NoSuchMethodException, SecurityException { + auth = new PaloAuth(); + new NonStrictExpectations() { + { + analyzer.getClusterName(); + minTimes = 0; + result = SystemInfoService.DEFAULT_CLUSTER; + + Catalog.getCurrentCatalog(); + minTimes = 0; + result = catalog; + + catalog.getAuth(); + minTimes = 0; + result = auth; + + catalog.getEditLog(); + minTimes = 0; + result = editLog; + + editLog.logCreateUser((PrivInfo) any); + minTimes = 0; + + ConnectContext.get(); + minTimes = 0; + result = ctx; + + ctx.getQualifiedUser(); + minTimes = 0; + result = "root"; + + ctx.getRemoteIP(); + minTimes = 0; + result = "192.168.1.1"; + + ctx.getState(); + minTimes = 0; + result = new QueryState(); + } + }; + + resolver = new DomainResolver(auth); + + new NonStrictExpectations(resolver) { + { + resolver.resolveWithBNS("palo.domain1", (Set) any); + result = new Delegate() { + public boolean resolveWithBNS(String domainName, Set resolvedIPs) { + resolvedIPs.add("10.1.1.1"); + resolvedIPs.add("10.1.1.2"); + resolvedIPs.add("10.1.1.3"); + return true; + } + }; + + resolver.resolveWithBNS("palo.domain2", (Set) any); + result = new Delegate() { + public boolean resolveWithBNS(String domainName, Set resolvedIPs) { + resolvedIPs.add("20.1.1.1"); + resolvedIPs.add("20.1.1.2"); + resolvedIPs.add("20.1.1.3"); + return true; + } + }; + } + }; + } + + @Test + public void test() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + // 1. create cmy@% + UserIdentity userIdentity = new UserIdentity("cmy", "%"); + UserDesc userDesc = new UserDesc(userIdentity, "12345", true); + CreateUserStmt userStmt = new CreateUserStmt(false, userDesc, null); + try { + userStmt.analyze(analyzer); + } catch (AnalysisException | InternalException e) { + e.printStackTrace(); + Assert.fail(); + } + + try { + auth.createUser(userStmt); + } catch (DdlException e) { + Assert.fail(); + } + + // 2. check if cmy from specified ip can access to palo + Assert.assertTrue(auth.checkPlainPassword(SystemInfoService.DEFAULT_CLUSTER + ":cmy", "192.168.0.1", "12345")); + Assert.assertFalse(auth.checkPlainPassword(SystemInfoService.DEFAULT_CLUSTER + ":cmy", "192.168.0.1", + "123456")); + Assert.assertFalse(auth.checkPlainPassword("other:cmy", "192.168.0.1", "12345")); + + // 3. create another user: zhangsan@"192.%" + userIdentity = new UserIdentity("zhangsan", "192.%"); + userDesc = new UserDesc(userIdentity, "12345", true); + userStmt = new CreateUserStmt(false, userDesc, null); + try { + userStmt.analyze(analyzer); + } catch (AnalysisException | InternalException e) { + e.printStackTrace(); + Assert.fail(); + } + + try { + auth.createUser(userStmt); + } catch (DdlException e) { + Assert.fail(); + } + + // 4. check if zhangsan from specified ip can access to palo + Assert.assertTrue(auth.checkPlainPassword(SystemInfoService.DEFAULT_CLUSTER + ":zhangsan", "192.168.0.1", + "12345")); + Assert.assertFalse(auth.checkPlainPassword(SystemInfoService.DEFAULT_CLUSTER + ":zhangsan", "172.168.0.1", + "12345")); + Assert.assertFalse(auth.checkPlainPassword("zhangsan", "192.168.0.1", "12345")); + + // 4.1 check if we can create same user + userIdentity = new UserIdentity("zhangsan", "192.%"); + userDesc = new UserDesc(userIdentity, "12345", true); + userStmt = new CreateUserStmt(false, userDesc, null); + try { + userStmt.analyze(analyzer); + } catch (AnalysisException | InternalException e) { + e.printStackTrace(); + Assert.fail(); + } + + boolean hasException = false; + try { + auth.createUser(userStmt); + } catch (DdlException e) { + e.printStackTrace(); + hasException = true; + } + Assert.assertTrue(hasException); + + // 4.2 check if we can create same user name with different host + userIdentity = new UserIdentity("zhangsan", "172.18.1.1"); + userDesc = new UserDesc(userIdentity, "12345", true); + userStmt = new CreateUserStmt(false, userDesc, null); + try { + userStmt.analyze(analyzer); + } catch (AnalysisException | InternalException e) { + e.printStackTrace(); + Assert.fail(); + } + + try { + auth.createUser(userStmt); + } catch (DdlException e) { + Assert.fail(); + } + Assert.assertTrue(auth.checkPlainPassword(SystemInfoService.DEFAULT_CLUSTER + ":zhangsan", "172.18.1.1", + "12345")); + + // 5. create a user with domain [palo.domain] + userIdentity = new UserIdentity("zhangsan", "palo.domain1", true); + userDesc = new UserDesc(userIdentity, "12345", true); + userStmt = new CreateUserStmt(false, userDesc, null); + try { + userStmt.analyze(analyzer); + } catch (AnalysisException | InternalException e) { + e.printStackTrace(); + Assert.fail(); + } + try { + auth.createUser(userStmt); + } catch (DdlException e) { + Assert.fail(); + } + + // 5.1 resolve domain [palo.domain1] + resolver.runOneCycle(); + + // 6. check if user from resolved ip can access to palo + Assert.assertTrue(auth.checkPlainPassword(SystemInfoService.DEFAULT_CLUSTER + ":zhangsan", "10.1.1.1", + "12345")); + Assert.assertFalse(auth.checkPlainPassword(SystemInfoService.DEFAULT_CLUSTER + ":zhangsan", "10.1.1.1", + "123456")); + Assert.assertFalse(auth.checkPlainPassword(SystemInfoService.DEFAULT_CLUSTER + ":zhangsan", "11.1.1.1", + "12345")); + + // 7. add duplicated user@['palo.domain1'] + userIdentity = new UserIdentity("zhangsan", "palo.domain1", true); + userDesc = new UserDesc(userIdentity, "12345", true); + userStmt = new CreateUserStmt(false, userDesc, null); + try { + userStmt.analyze(analyzer); + } catch (AnalysisException | InternalException e) { + e.printStackTrace(); + Assert.fail(); + } + + hasException = false; + try { + auth.createUser(userStmt); + } catch (DdlException e) { + e.printStackTrace(); + hasException = true; + } + Assert.assertTrue(hasException); + + // 8. add another user@['palo.domain2'] + userIdentity = new UserIdentity("lisi", "palo.domain2", true); + userDesc = new UserDesc(userIdentity, "123456", true); + userStmt = new CreateUserStmt(false, userDesc, null); + try { + userStmt.analyze(analyzer); + } catch (AnalysisException | InternalException e) { + e.printStackTrace(); + Assert.fail(); + } + + try { + auth.createUser(userStmt); + } catch (DdlException e) { + e.printStackTrace(); + Assert.fail(); + } + + // 8.1 resolve domain [palo.domain2] + resolver.runOneCycle(); + + Assert.assertTrue(auth.checkPlainPassword(SystemInfoService.DEFAULT_CLUSTER + ":lisi", "20.1.1.1", + "123456")); + Assert.assertFalse(auth.checkPlainPassword(SystemInfoService.DEFAULT_CLUSTER + ":lisi", "10.1.1.1", + "123456")); + Assert.assertFalse(auth.checkPlainPassword(SystemInfoService.DEFAULT_CLUSTER + ":lisi", "20.1.1.2", + "123455")); + + /* + * Now we have 4 users: + * cmy@'%' + * zhangsan@"192.%" + * zhangsan@['palo.domain1'] + * lisi@['palo.domain2'] + */ + + // 9. grant for cmy@'%' + TablePattern tablePattern = new TablePattern("*", "*"); + List privileges = Lists.newArrayList(AccessPrivilege.CREATE_PRIV, AccessPrivilege.DROP_PRIV); + GrantStmt grantStmt = new GrantStmt(new UserIdentity("cmy", "%"), null, tablePattern, privileges); + + try { + grantStmt.analyze(analyzer); + } catch (AnalysisException | InternalException e) { + e.printStackTrace(); + Assert.fail(); + } + + // check auth before grant + Assert.assertFalse(auth.checkDbPriv("172.1.1.1", SystemInfoService.DEFAULT_CLUSTER + ":db1", + SystemInfoService.DEFAULT_CLUSTER + ":cmy", + PrivPredicate.CREATE)); + + try { + auth.grant(grantStmt); + } catch (DdlException e) { + e.printStackTrace(); + Assert.fail(); + } + + // 9.1 check auth + Assert.assertTrue(auth.checkDbPriv("172.1.1.1", SystemInfoService.DEFAULT_CLUSTER + ":db1", + SystemInfoService.DEFAULT_CLUSTER + ":cmy", + PrivPredicate.CREATE)); + Assert.assertFalse(auth.checkDbPriv("172.1.1.1", SystemInfoService.DEFAULT_CLUSTER + ":db1", + SystemInfoService.DEFAULT_CLUSTER + ":zhangsan", + PrivPredicate.CREATE)); + + // 10. grant auth for non exist user + tablePattern = new TablePattern("*", "*"); + privileges = Lists.newArrayList(AccessPrivilege.CREATE_PRIV, AccessPrivilege.DROP_PRIV); + grantStmt = new GrantStmt(new UserIdentity("nouser", "%"), null, tablePattern, privileges); + + try { + grantStmt.analyze(analyzer); + } catch (AnalysisException | InternalException e) { + e.printStackTrace(); + Assert.fail(); + } + + hasException = false; + try { + auth.grant(grantStmt); + } catch (DdlException e) { + e.printStackTrace(); + hasException = true; + } + Assert.assertTrue(hasException); + + // 11. grant auth for user with non exist host + tablePattern = new TablePattern("*", "*"); + privileges = Lists.newArrayList(AccessPrivilege.SELECT_PRIV, AccessPrivilege.DROP_PRIV); + grantStmt = new GrantStmt(new UserIdentity("zhangsan", "%"), null, tablePattern, privileges); + + try { + grantStmt.analyze(analyzer); + } catch (AnalysisException | InternalException e) { + e.printStackTrace(); + Assert.fail(); + } + + hasException = false; + try { + auth.grant(grantStmt); + } catch (DdlException e) { + e.printStackTrace(); + hasException = true; + } + Assert.assertTrue(hasException); + + // 12. grant db auth to exist user + tablePattern = new TablePattern("db1", "*"); + privileges = Lists.newArrayList(AccessPrivilege.SELECT_PRIV, AccessPrivilege.DROP_PRIV); + grantStmt = new GrantStmt(new UserIdentity("zhangsan", "192.%"), null, tablePattern, privileges); + + try { + grantStmt.analyze(analyzer); + } catch (AnalysisException | InternalException e) { + e.printStackTrace(); + Assert.fail(); + } + + try { + auth.grant(grantStmt); + } catch (DdlException e) { + e.printStackTrace(); + Assert.fail(); + } + Assert.assertTrue(auth.checkDbPriv("192.168.1.1", SystemInfoService.DEFAULT_CLUSTER + ":db1", + SystemInfoService.DEFAULT_CLUSTER + ":zhangsan", + PrivPredicate.SELECT)); + Assert.assertFalse(auth.checkDbPriv("172.1.1.1", SystemInfoService.DEFAULT_CLUSTER + ":db1", + SystemInfoService.DEFAULT_CLUSTER + ":zhangsan", + PrivPredicate.SELECT)); + Assert.assertFalse(auth.checkGlobalPriv("192.168.1.1", SystemInfoService.DEFAULT_CLUSTER + ":zhangsan", + PrivPredicate.SELECT)); + Assert.assertTrue(auth.checkTblPriv("192.168.1.1", SystemInfoService.DEFAULT_CLUSTER + ":db1", + SystemInfoService.DEFAULT_CLUSTER + ":zhangsan", "tbl1", + PrivPredicate.SELECT)); + + // 13. grant tbl auth to exist user + tablePattern = new TablePattern("db2", "tbl2"); + privileges = Lists.newArrayList(AccessPrivilege.ALTER_PRIV, AccessPrivilege.DROP_PRIV); + grantStmt = new GrantStmt(new UserIdentity("zhangsan", "192.%"), null, tablePattern, privileges); + + try { + grantStmt.analyze(analyzer); + } catch (AnalysisException | InternalException e) { + e.printStackTrace(); + Assert.fail(); + } + + try { + auth.grant(grantStmt); + } catch (DdlException e) { + e.printStackTrace(); + Assert.fail(); + } + Assert.assertFalse(auth.checkDbPriv("192.168.1.1", SystemInfoService.DEFAULT_CLUSTER + ":db2", + SystemInfoService.DEFAULT_CLUSTER + ":zhangsan", + PrivPredicate.SELECT)); + Assert.assertFalse(auth.checkDbPriv("172.1.1.1", SystemInfoService.DEFAULT_CLUSTER + ":db2", + SystemInfoService.DEFAULT_CLUSTER + ":zhangsan", + PrivPredicate.SELECT)); + Assert.assertFalse(auth.checkGlobalPriv("192.168.1.1", SystemInfoService.DEFAULT_CLUSTER + ":zhangsan", + PrivPredicate.SELECT)); + Assert.assertTrue(auth.checkTblPriv("192.168.1.1", SystemInfoService.DEFAULT_CLUSTER + ":db2", + SystemInfoService.DEFAULT_CLUSTER + ":zhangsan", "tbl2", + PrivPredicate.DROP)); + + // 14. grant db auth to zhangsan@['palo.domain1'] + tablePattern = new TablePattern("db3", "*"); + privileges = Lists.newArrayList(AccessPrivilege.ALTER_PRIV, AccessPrivilege.DROP_PRIV); + grantStmt = new GrantStmt(new UserIdentity("zhangsan", "palo.domain1", true), null, tablePattern, privileges); + + try { + grantStmt.analyze(analyzer); + } catch (AnalysisException | InternalException e) { + e.printStackTrace(); + Assert.fail(); + } + + try { + auth.grant(grantStmt); + } catch (DdlException e) { + e.printStackTrace(); + Assert.fail(); + } + + Assert.assertFalse(auth.checkDbPriv("10.1.1.1", SystemInfoService.DEFAULT_CLUSTER + ":db3", + SystemInfoService.DEFAULT_CLUSTER + ":zhangsan", + PrivPredicate.ALTER)); + + resolver.runOneCycle(); + + Assert.assertTrue(auth.checkDbPriv("10.1.1.1", SystemInfoService.DEFAULT_CLUSTER + ":db3", + SystemInfoService.DEFAULT_CLUSTER + ":zhangsan", + PrivPredicate.ALTER)); + Assert.assertFalse(auth.checkDbPriv("172.1.1.1", SystemInfoService.DEFAULT_CLUSTER + ":db3", + SystemInfoService.DEFAULT_CLUSTER + ":zhangsan", + PrivPredicate.ALTER)); + + // 15. grant new auth to exist priv entry (exist ALTER/DROP, add SELECT) + tablePattern = new TablePattern("db3", "*"); + privileges = Lists.newArrayList(AccessPrivilege.SELECT_PRIV); + grantStmt = new GrantStmt(new UserIdentity("zhangsan", "palo.domain1", true), null, tablePattern, privileges); + + try { + grantStmt.analyze(analyzer); + } catch (AnalysisException | InternalException e) { + e.printStackTrace(); + Assert.fail(); + } + + try { + auth.grant(grantStmt); + } catch (DdlException e) { + e.printStackTrace(); + Assert.fail(); + } + + Assert.assertFalse(auth.checkDbPriv("10.1.1.1", SystemInfoService.DEFAULT_CLUSTER + ":db3", + SystemInfoService.DEFAULT_CLUSTER + ":zhangsan", + PrivPredicate.SELECT)); + + resolver.runOneCycle(); + + Assert.assertTrue(auth.checkDbPriv("10.1.1.1", SystemInfoService.DEFAULT_CLUSTER + ":db3", + SystemInfoService.DEFAULT_CLUSTER + ":zhangsan", + PrivPredicate.SELECT)); + Assert.assertTrue(auth.checkDbPriv("10.1.1.2", SystemInfoService.DEFAULT_CLUSTER + ":db3", + SystemInfoService.DEFAULT_CLUSTER + ":zhangsan", + PrivPredicate.ALTER)); + Assert.assertTrue(auth.checkDbPriv("10.1.1.3", SystemInfoService.DEFAULT_CLUSTER + ":db3", + SystemInfoService.DEFAULT_CLUSTER + ":zhangsan", + PrivPredicate.DROP)); + + /* + * for now, we have following auth: + * cmy@'%' + * *.* -> CREATE/DROP + * zhangsan@"192.%" + * db1.* -> SELECT/DROP + * db2.tbl2 -> ALTER/DROP + * zhangsan@['palo.domain1'] + * db3.* -> ALTER/DROP/SELECT + * lisi@['palo.domain2'] + * N/A + */ + + // 16. revoke privs from non exist user + tablePattern = new TablePattern("*", "*"); + privileges = Lists.newArrayList(AccessPrivilege.SELECT_PRIV); + RevokeStmt revokeStmt = new RevokeStmt(new UserIdentity("nouser", "%"), null, tablePattern, privileges); + + try { + revokeStmt.analyze(analyzer); + } catch (AnalysisException e) { + e.printStackTrace(); + Assert.fail(); + } + + hasException = false; + try { + auth.revoke(revokeStmt); + } catch (DdlException e) { + e.printStackTrace(); + hasException = true; + } + Assert.assertTrue(hasException); + + // 17. revoke privs from non exist host + tablePattern = new TablePattern("*", "*"); + privileges = Lists.newArrayList(AccessPrivilege.SELECT_PRIV); + revokeStmt = new RevokeStmt(new UserIdentity("cmy", "172.%"), null, tablePattern, privileges); + + try { + revokeStmt.analyze(analyzer); + } catch (AnalysisException e) { + e.printStackTrace(); + Assert.fail(); + } + + hasException = false; + try { + auth.revoke(revokeStmt); + } catch (DdlException e) { + e.printStackTrace(); + hasException = true; + } + Assert.assertTrue(hasException); + + // 18. revoke privs from non exist db + tablePattern = new TablePattern("nodb", "*"); + privileges = Lists.newArrayList(AccessPrivilege.SELECT_PRIV); + revokeStmt = new RevokeStmt(new UserIdentity("cmy", "%"), null, tablePattern, privileges); + + try { + revokeStmt.analyze(analyzer); + } catch (AnalysisException e) { + e.printStackTrace(); + Assert.fail(); + } + + hasException = false; + try { + auth.revoke(revokeStmt); + } catch (DdlException e) { + e.printStackTrace(); + hasException = true; + } + Assert.assertTrue(hasException); + + // 19. revoke privs from user @ ip + tablePattern = new TablePattern("*", "*"); + privileges = Lists.newArrayList(AccessPrivilege.CREATE_PRIV); + revokeStmt = new RevokeStmt(new UserIdentity("cmy", "%"), null, tablePattern, privileges); + + try { + revokeStmt.analyze(analyzer); + } catch (AnalysisException e) { + e.printStackTrace(); + Assert.fail(); + } + + Assert.assertTrue(auth.checkDbPriv("172.1.1.1", SystemInfoService.DEFAULT_CLUSTER + ":db", + SystemInfoService.DEFAULT_CLUSTER + ":cmy", + PrivPredicate.CREATE)); + try { + auth.revoke(revokeStmt); + } catch (DdlException e) { + e.printStackTrace(); + Assert.fail(); + } + Assert.assertFalse(auth.checkDbPriv("172.1.1.1", SystemInfoService.DEFAULT_CLUSTER + ":db", + SystemInfoService.DEFAULT_CLUSTER + ":cmy", + PrivPredicate.CREATE)); + Assert.assertTrue(auth.checkDbPriv("172.1.1.1", SystemInfoService.DEFAULT_CLUSTER + ":db", + SystemInfoService.DEFAULT_CLUSTER + ":cmy", + PrivPredicate.DROP)); + + // 19. revoke tbl privs from user @ ip + tablePattern = new TablePattern("db2", "tbl2"); + privileges = Lists.newArrayList(AccessPrivilege.ALTER_PRIV); + revokeStmt = new RevokeStmt(new UserIdentity("zhangsan", "192.%"), null, tablePattern, privileges); + + try { + revokeStmt.analyze(analyzer); + } catch (AnalysisException e) { + e.printStackTrace(); + Assert.fail(); + } + + Assert.assertTrue(auth.checkTblPriv("192.1.1.1", SystemInfoService.DEFAULT_CLUSTER + ":db2", + SystemInfoService.DEFAULT_CLUSTER + ":zhangsan", "tbl2", + PrivPredicate.ALTER)); + try { + auth.revoke(revokeStmt); + } catch (DdlException e) { + e.printStackTrace(); + Assert.fail(); + } + Assert.assertFalse(auth.checkTblPriv("192.1.1.1", SystemInfoService.DEFAULT_CLUSTER + ":db2", + SystemInfoService.DEFAULT_CLUSTER + ":zhangsan", "tbl2", + PrivPredicate.ALTER)); + Assert.assertTrue(auth.checkDbPriv("192.1.1.1", SystemInfoService.DEFAULT_CLUSTER + ":db1", + SystemInfoService.DEFAULT_CLUSTER + ":zhangsan", + PrivPredicate.SELECT)); + + // 20. revoke privs from non exist user @ domain + tablePattern = new TablePattern("db2", "tbl2"); + privileges = Lists.newArrayList(AccessPrivilege.ALTER_PRIV); + revokeStmt = new RevokeStmt(new UserIdentity("zhangsan", "nodomain", true), null, tablePattern, privileges); + + try { + revokeStmt.analyze(analyzer); + } catch (AnalysisException e) { + e.printStackTrace(); + Assert.fail(); + } + + hasException = false; + try { + auth.revoke(revokeStmt); + } catch (DdlException e) { + e.printStackTrace(); + hasException = true; + } + Assert.assertTrue(hasException); + + // 21. revoke privs from non exist db from user @ domain + tablePattern = new TablePattern("nodb", "*"); + privileges = Lists.newArrayList(AccessPrivilege.ALTER_PRIV); + revokeStmt = new RevokeStmt(new UserIdentity("zhangsan", "palo.domain1", true), null, tablePattern, privileges); + + try { + revokeStmt.analyze(analyzer); + } catch (AnalysisException e) { + e.printStackTrace(); + Assert.fail(); + } + + hasException = false; + try { + auth.revoke(revokeStmt); + } catch (DdlException e) { + e.printStackTrace(); + hasException = true; + } + Assert.assertTrue(hasException); + + // 22. revoke privs from exist user @ domain + tablePattern = new TablePattern("db3", "*"); + privileges = Lists.newArrayList(AccessPrivilege.DROP_PRIV); + revokeStmt = new RevokeStmt(new UserIdentity("zhangsan", "palo.domain1", true), null, tablePattern, privileges); + + try { + revokeStmt.analyze(analyzer); + } catch (AnalysisException e) { + e.printStackTrace(); + Assert.fail(); + } + + Assert.assertTrue(auth.checkDbPriv("10.1.1.1", SystemInfoService.DEFAULT_CLUSTER + ":db3", + SystemInfoService.DEFAULT_CLUSTER + ":zhangsan", + PrivPredicate.DROP)); + + try { + auth.revoke(revokeStmt); + } catch (DdlException e) { + e.printStackTrace(); + Assert.fail(); + } + + Assert.assertTrue(auth.checkDbPriv("10.1.1.1", SystemInfoService.DEFAULT_CLUSTER + ":db3", + SystemInfoService.DEFAULT_CLUSTER + ":zhangsan", + PrivPredicate.DROP)); + resolver.runOneCycle(); + Assert.assertFalse(auth.checkDbPriv("10.1.1.1", SystemInfoService.DEFAULT_CLUSTER + ":db3", + SystemInfoService.DEFAULT_CLUSTER + ":zhangsan", + PrivPredicate.DROP)); + Assert.assertFalse(auth.checkDbPriv("10.1.1.3", SystemInfoService.DEFAULT_CLUSTER + ":db3", + SystemInfoService.DEFAULT_CLUSTER + ":zhangsan", + PrivPredicate.DROP)); + + /* + * for now, we have following auth: + * cmy@'%' + * *.* -> DROP + * zhangsan@"192.%" + * db1.* -> SELECT/DROP + * db2.tbl2 -> DROP + * zhangsan@['palo.domain1'] + * db3.* -> ALTER/SELECT + * lisi@['palo.domain2'] + * N/A + */ + + // 23. create admin role, which is not allowed + CreateRoleStmt roleStmt = new CreateRoleStmt(PaloRole.ADMIN_ROLE); + hasException = false; + try { + roleStmt.analyze(analyzer); + } catch (AnalysisException | InternalException e1) { + e1.printStackTrace(); + hasException = true; + } + Assert.assertTrue(hasException); + + // 23. create operator role, which is not allowed + roleStmt = new CreateRoleStmt(PaloRole.OPERATOR_ROLE); + hasException = false; + try { + roleStmt.analyze(analyzer); + } catch (AnalysisException | InternalException e1) { + e1.printStackTrace(); + hasException = true; + } + Assert.assertTrue(hasException); + + // 24. create role + roleStmt = new CreateRoleStmt("rolo1"); + try { + roleStmt.analyze(analyzer); + } catch (AnalysisException | InternalException e1) { + e1.printStackTrace(); + Assert.fail(); + } + + try { + auth.createRole(roleStmt); + } catch (DdlException e1) { + e1.printStackTrace(); + Assert.fail(); + } + + // 25. grant auth to non exist role, will create this new role + privileges = Lists.newArrayList(AccessPrivilege.DROP_PRIV, AccessPrivilege.SELECT_PRIV); + grantStmt = new GrantStmt(null, "role2", new TablePattern("*", "*"), privileges); + try { + grantStmt.analyze(analyzer); + } catch (AnalysisException | InternalException e1) { + e1.printStackTrace(); + Assert.fail(); + } + + try { + auth.grant(grantStmt); + } catch (DdlException e1) { + e1.printStackTrace(); + Assert.fail(); + } + + // 26. grant auth to role + privileges = Lists.newArrayList(AccessPrivilege.DROP_PRIV, AccessPrivilege.SELECT_PRIV); + grantStmt = new GrantStmt(null, "role1", new TablePattern("*", "*"), privileges); + try { + grantStmt.analyze(analyzer); + } catch (AnalysisException | InternalException e1) { + e1.printStackTrace(); + Assert.fail(); + } + try { + auth.grant(grantStmt); + } catch (DdlException e1) { + e1.printStackTrace(); + Assert.fail(); + } + + // 27. create user and set it as role1 + userIdentity = new UserIdentity("wangwu", "%"); + userDesc = new UserDesc(userIdentity, "12345", true); + userStmt = new CreateUserStmt(false, userDesc, "role1"); + try { + userStmt.analyze(analyzer); + } catch (AnalysisException | InternalException e) { + e.printStackTrace(); + Assert.fail(); + } + + Assert.assertFalse(auth.checkDbPriv("10.17.2.1", SystemInfoService.DEFAULT_CLUSTER + ":db4", + SystemInfoService.DEFAULT_CLUSTER + ":wangwu", + PrivPredicate.DROP)); + try { + auth.createUser(userStmt); + } catch (DdlException e) { + Assert.fail(); + } + Assert.assertTrue(auth.checkDbPriv("10.17.2.1", SystemInfoService.DEFAULT_CLUSTER + ":db4", + SystemInfoService.DEFAULT_CLUSTER + ":wangwu", + PrivPredicate.DROP)); + + // 28. create user@domain and set it as role1 + userIdentity = new UserIdentity("chenliu", "palo.domain2", true); + userDesc = new UserDesc(userIdentity, "12345", true); + userStmt = new CreateUserStmt(false, userDesc, "role1"); + try { + userStmt.analyze(analyzer); + } catch (AnalysisException | InternalException e) { + e.printStackTrace(); + Assert.fail(); + } + + try { + auth.createUser(userStmt); + } catch (DdlException e) { + e.printStackTrace(); + Assert.fail(); + } + + Assert.assertFalse(auth.checkDbPriv("20.1.1.1", SystemInfoService.DEFAULT_CLUSTER + ":db4", + SystemInfoService.DEFAULT_CLUSTER + ":chenliu", + PrivPredicate.DROP)); + resolver.runOneCycle(); + Assert.assertTrue(auth.checkDbPriv("20.1.1.1", SystemInfoService.DEFAULT_CLUSTER + ":db4", + SystemInfoService.DEFAULT_CLUSTER + ":chenliu", + PrivPredicate.DROP)); + + // 29. revoke auth on non exist db from role1 + privileges = Lists.newArrayList(AccessPrivilege.DROP_PRIV); + revokeStmt = new RevokeStmt(null, "role1", new TablePattern("nodb", "*"), privileges); + try { + revokeStmt.analyze(analyzer); + } catch (AnalysisException e) { + e.printStackTrace(); + Assert.fail(); + } + + hasException = false; + try { + auth.revoke(revokeStmt); + } catch (DdlException e1) { + e1.printStackTrace(); + hasException = true; + } + Assert.assertTrue(hasException); + + // 30. revoke auth from role1 + privileges = Lists.newArrayList(AccessPrivilege.DROP_PRIV); + revokeStmt = new RevokeStmt(null, "role1", new TablePattern("*", "*"), privileges); + try { + revokeStmt.analyze(analyzer); + } catch (AnalysisException e) { + e.printStackTrace(); + Assert.fail(); + } + + try { + auth.revoke(revokeStmt); + } catch (DdlException e) { + e.printStackTrace(); + Assert.fail(); + } + Assert.assertTrue(auth.checkDbPriv("20.1.1.1", SystemInfoService.DEFAULT_CLUSTER + ":db4", + SystemInfoService.DEFAULT_CLUSTER + ":chenliu", + PrivPredicate.DROP)); + resolver.runOneCycle(); + Assert.assertFalse(auth.checkDbPriv("20.1.1.1", SystemInfoService.DEFAULT_CLUSTER + ":db4", + SystemInfoService.DEFAULT_CLUSTER + ":chenliu", + PrivPredicate.DROP)); + + // 31. drop role, privs remain unchanged + DropRoleStmt dropRoleStmt = new DropRoleStmt("role1"); + try { + dropRoleStmt.analyze(analyzer); + } catch (AnalysisException | InternalException e) { + e.printStackTrace(); + Assert.fail(); + } + + try { + auth.dropRole(dropRoleStmt); + } catch (DdlException e) { + e.printStackTrace(); + Assert.fail(); + } + Assert.assertFalse(auth.checkDbPriv("20.1.1.1", SystemInfoService.DEFAULT_CLUSTER + ":db4", + SystemInfoService.DEFAULT_CLUSTER + ":chenliu", + PrivPredicate.DROP)); + + // drop user cmy + DropUserStmt dropUserStmt = new DropUserStmt(new UserIdentity("cmy", "%")); + try { + dropUserStmt.analyze(analyzer); + } catch (AnalysisException | InternalException e) { + e.printStackTrace(); + Assert.fail(); + } + + try { + auth.dropUser(dropUserStmt); + } catch (DdlException e) { + Assert.fail(); + } + + Assert.assertFalse(auth.checkPlainPassword(SystemInfoService.DEFAULT_CLUSTER + ":cmy", "192.168.0.1", "12345")); + Assert.assertTrue(auth.checkPlainPassword(SystemInfoService.DEFAULT_CLUSTER + ":zhangsan", "192.168.0.1", + "12345")); + + } + +} diff --git a/fe/test/com/baidu/palo/mysql/privilege/MockedAuth.java b/fe/test/com/baidu/palo/mysql/privilege/MockedAuth.java new file mode 100644 index 0000000000..b3c282e3ea --- /dev/null +++ b/fe/test/com/baidu/palo/mysql/privilege/MockedAuth.java @@ -0,0 +1,57 @@ +// Copyright (c) 2018, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +package com.baidu.palo.mysql.privilege; + +import com.baidu.palo.qe.ConnectContext; +import com.baidu.palo.qe.QueryState; + +import mockit.NonStrictExpectations; + +public class MockedAuth { + + public static void mockedAuth(PaloAuth auth) { + new NonStrictExpectations() { + { + auth.checkGlobalPriv((ConnectContext) any, (PrivPredicate) any); + result = true; + + auth.checkDbPriv((ConnectContext) any, anyString, (PrivPredicate) any); + result = true; + + auth.checkTblPriv((ConnectContext) any, anyString, anyString, (PrivPredicate) any); + result = true; + } + }; + } + + public static void mockedConnectContext(ConnectContext ctx, String user, String ip) { + new NonStrictExpectations() { + { + ConnectContext.get(); + result = ctx; + + ctx.getQualifiedUser(); + result = user; + + ctx.getRemoteIP(); + result = ip; + + ctx.getState(); + result = new QueryState(); + } + }; + } +} diff --git a/fe/test/com/baidu/palo/mysql/privilege/PrivTest.java b/fe/test/com/baidu/palo/mysql/privilege/PrivTest.java new file mode 100644 index 0000000000..19c83dcfbb --- /dev/null +++ b/fe/test/com/baidu/palo/mysql/privilege/PrivTest.java @@ -0,0 +1,347 @@ +// Copyright (c) 2018, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +package com.baidu.palo.mysql.privilege; + +import com.baidu.palo.analysis.CompoundPredicate.Operator; +import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.common.DdlException; +import com.baidu.palo.common.FeMetaVersion; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import mockit.Mock; +import mockit.MockUp; + +public class PrivTest { + + private PaloAuth auth; + private byte[] passwd = new byte[] { 'a', 'c' }; + + private Method grantGlobalPrivsM; + private Method grantDbPrivsM; + private Method grantTblPrivsM; + + @Before + public void setUp() { + auth = new PaloAuth(); + + Method[] methods = PaloAuth.class.getDeclaredMethods(); + for (Method method : methods) { + if (method.getName().equals("grantGlobalPrivs")) { + method.setAccessible(true); + grantGlobalPrivsM = method; + } else if (method.getName().equals("grantDbPrivs")) { + method.setAccessible(true); + grantDbPrivsM = method; + } else if (method.getName().equals("grantTblPrivs")) { + method.setAccessible(true); + grantTblPrivsM = method; + } + } + + new MockUp() { + @Mock + public int getCurrentCatalogJournalVersion() { + return FeMetaVersion.VERSION_43; + } + }; + } + + public void grantGlobalPrivs(Object... params) + throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + grantGlobalPrivsM.invoke(auth, params); + } + + public void grantDbPrivs(Object... params) + throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + grantDbPrivsM.invoke(auth, params); + } + + public void grantTblPrivs(Object... params) + throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + grantTblPrivsM.invoke(auth, params); + } + + + @Test + public void testGlobalPriv() + throws DdlException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { + // exact match + grantGlobalPrivs("192.168.1.1", "cmy", passwd, false, false, false, + PrivBitSet.of(PaloPrivilege.GRANT_PRIV)); + Assert.assertFalse(auth.checkGlobalPriv("192.168.1.2", "cmy", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.GRANT_PRIV), + Operator.OR))); + Assert.assertFalse(auth.checkGlobalPriv("192.168.1.1", "cmy2", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.GRANT_PRIV), + Operator.OR))); + Assert.assertFalse(auth.checkGlobalPriv("192.168.1.1", "cmy", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.NODE_PRIV), + Operator.OR))); + Assert.assertTrue(auth.checkGlobalPriv("192.168.1.1", "cmy", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.GRANT_PRIV), + Operator.OR))); + // fuzzy match + auth.clear(); + grantGlobalPrivs("192.168._.%", "cmy\\_%", passwd, false, false, false, + PrivBitSet.of(PaloPrivilege.GRANT_PRIV, + PaloPrivilege.NODE_PRIV)); + Assert.assertFalse(auth.checkGlobalPriv("192.168.1.1", "cmy", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.GRANT_PRIV), + Operator.OR))); + Assert.assertFalse(auth.checkGlobalPriv("192.168.1.200", "cmy_123", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.SELECT_PRIV), + Operator.OR))); + Assert.assertTrue(auth.checkGlobalPriv("192.168.1.200", "cmy_123", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.GRANT_PRIV, + PaloPrivilege.NODE_PRIV), + Operator.OR))); + Assert.assertTrue(auth.checkGlobalPriv("192.168.1.200", "cmy_", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.NODE_PRIV), + Operator.OR))); + + auth.clear(); + grantGlobalPrivs("192.168.%", ".cmy", passwd, false, false, false, + PrivBitSet.of(PaloPrivilege.GRANT_PRIV)); + Assert.assertFalse(auth.checkGlobalPriv("192.10.1.1", ".cmy", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.GRANT_PRIV), + Operator.OR))); + Assert.assertTrue(auth.checkGlobalPriv("192.168.1.200", ".cmy", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.GRANT_PRIV), + Operator.OR))); + + // multi priv entries + auth.clear(); + grantGlobalPrivs("%", "cmy", passwd, false, false, false, PrivBitSet.of(PaloPrivilege.GRANT_PRIV)); + grantGlobalPrivs("localhost", "cmy", passwd, false, false, false, + PrivBitSet.of(PaloPrivilege.NODE_PRIV)); + grantGlobalPrivs("127.0.0.1", "cmy", passwd, false, false, false, + PrivBitSet.of(PaloPrivilege.SELECT_PRIV)); + + Assert.assertTrue(auth.checkGlobalPriv("127.0.0.1", "cmy", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.SELECT_PRIV), + Operator.OR))); + Assert.assertFalse(auth.checkGlobalPriv("127.0.0.1", "cmy", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.GRANT_PRIV), + Operator.OR))); + Assert.assertTrue(auth.checkGlobalPriv("localhost", "cmy", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.NODE_PRIV), + Operator.OR))); + Assert.assertFalse(auth.checkGlobalPriv("localhost", "cmy", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.GRANT_PRIV), + Operator.OR))); + Assert.assertTrue(auth.checkGlobalPriv("192.168.1.1", "cmy", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.GRANT_PRIV), + Operator.OR))); + + // test persist + auth = testPersist(auth); + Assert.assertTrue(auth.checkGlobalPriv("127.0.0.1", "cmy", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.SELECT_PRIV), + Operator.OR))); + Assert.assertFalse(auth.checkGlobalPriv("127.0.0.1", "cmy", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.GRANT_PRIV), + Operator.OR))); + Assert.assertTrue(auth.checkGlobalPriv("localhost", "cmy", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.NODE_PRIV), + Operator.OR))); + Assert.assertFalse(auth.checkGlobalPriv("localhost", "cmy", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.GRANT_PRIV), + Operator.OR))); + Assert.assertTrue(auth.checkGlobalPriv("192.168.1.1", "cmy", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.GRANT_PRIV), + Operator.OR))); + } + + @Test + public void testDbPriv() + throws DdlException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { + // normal + grantDbPrivs("192.168.1.%", "my\\_database", "cmy", false, false, false, + PrivBitSet.of(PaloPrivilege.SELECT_PRIV, + PaloPrivilege.ALTER_PRIV)); + Assert.assertTrue(auth.checkDbPriv("192.168.1.1", "my_database", "cmy", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.ALTER_PRIV), + Operator.OR))); + Assert.assertTrue(auth.checkDbPriv("192.168.1.1", "my_database", "cmy", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.ALTER_PRIV, + PaloPrivilege.SELECT_PRIV), + Operator.OR))); + Assert.assertFalse(auth.checkDbPriv("192.168.1.2", "my_database", "cmy", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.ALTER_PRIV, + PaloPrivilege.LOAD_PRIV), + Operator.AND))); + Assert.assertTrue(auth.checkDbPriv("192.168.1.2", "my_database", "cmy", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.ALTER_PRIV, + PaloPrivilege.LOAD_PRIV), + Operator.OR))); + Assert.assertFalse(auth.checkDbPriv("192.168.1.1", "my_database2", "cmy", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.ALTER_PRIV), + Operator.OR))); + Assert.assertFalse(auth.checkDbPriv("192.168.2.1", "my_database2", "cmy", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.ALTER_PRIV), + Operator.OR))); + Assert.assertFalse(auth.checkDbPriv("192.168.1.1", "my_database2", "cmy2", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.ALTER_PRIV), + Operator.OR))); + + // add global priv + auth.clear(); + grantGlobalPrivs("%", "cmy", passwd, false, false, false, PrivBitSet.of(PaloPrivilege.SELECT_PRIV)); + grantDbPrivs("192.168.1.%", "database", "cmy", false, false, false, + PrivBitSet.of(PaloPrivilege.ALTER_PRIV)); + + Assert.assertTrue(auth.checkDbPriv("192.168.1.1", "database", "cmy", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.ALTER_PRIV, + PaloPrivilege.SELECT_PRIV), + Operator.OR))); + + Assert.assertTrue(auth.checkDbPriv("192.168.1.1", "database2", "cmy", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.SELECT_PRIV), + Operator.OR))); + + Assert.assertFalse(auth.checkDbPriv("192.168.2.1", "database", "cmy", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.ALTER_PRIV), + Operator.OR))); + + Assert.assertFalse(auth.checkDbPriv("192.168.1.1", "database2", "cmy", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.ALTER_PRIV), + Operator.OR))); + + // test persist + auth = testPersist(auth); + Assert.assertTrue(auth.checkDbPriv("192.168.1.1", "database", "cmy", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.ALTER_PRIV, + PaloPrivilege.SELECT_PRIV), + Operator.OR))); + + Assert.assertTrue(auth.checkDbPriv("192.168.1.1", "database2", "cmy", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.SELECT_PRIV), + Operator.OR))); + + Assert.assertFalse(auth.checkDbPriv("192.168.2.1", "database", "cmy", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.ALTER_PRIV), + Operator.OR))); + + Assert.assertFalse(auth.checkDbPriv("192.168.1.1", "database2", "cmy", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.ALTER_PRIV), + Operator.OR))); + } + + @Test + public void testTblPriv() + throws DdlException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { + // normal + grantTblPrivs("192.%.1.1", "db\\_%", "cmy%", "tbl%", false, false, false, + PrivBitSet.of(PaloPrivilege.SELECT_PRIV, + PaloPrivilege.LOAD_PRIV)); + Assert.assertTrue(auth.checkTblPriv("192.168.1.1", "db_1", "cmy", "tbl", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.LOAD_PRIV, + PaloPrivilege.SELECT_PRIV), + Operator.OR))); + Assert.assertFalse(auth.checkTblPriv("192.168.1.1", "db_1", "cmy", "tbl", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.ALTER_PRIV), + Operator.OR))); + + // add db priv + grantDbPrivs("192.%", "db\\_123", "cmy", false, false, false, PrivBitSet.of(PaloPrivilege.ALTER_PRIV)); + Assert.assertTrue(auth.checkTblPriv("192.168.1.1", "db_123", "cmy", "tbl", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.ALTER_PRIV), + Operator.OR))); + + Assert.assertTrue(auth.checkTblPriv("192.168.1.1", "db_123", "cmy", "tbl", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.SELECT_PRIV), + Operator.OR))); + + Assert.assertFalse(auth.checkTblPriv("10.168.1.1", "db_123", "cmy", "tbl", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.ALTER_PRIV), + Operator.OR))); + + // add global priv + grantGlobalPrivs("192.168.2.1", "cmy\\_admin", passwd, false, false, false, + PrivBitSet.of(PaloPrivilege.DROP_PRIV)); + Assert.assertTrue(auth.checkTblPriv("192.168.2.1", "db_123", "cmy_admin", "tbl", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.DROP_PRIV), + Operator.OR))); + + Assert.assertFalse(auth.checkTblPriv("192.168.1.1", "db_123", "cmy_admin", "tbl", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.DROP_PRIV), + Operator.OR))); + + // test persist + auth = testPersist(auth); + Assert.assertTrue(auth.checkTblPriv("192.168.2.1", "db_123", "cmy_admin", "tbl", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.DROP_PRIV), + Operator.OR))); + + Assert.assertFalse(auth.checkTblPriv("192.168.1.1", "db_123", "cmy_admin", "tbl", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.DROP_PRIV), + Operator.OR))); + + // add global priv + grantGlobalPrivs("%", "cmy2", passwd, false, false, false, + PrivBitSet.of(PaloPrivilege.DROP_PRIV)); + Assert.assertTrue(auth.checkTblPriv("", "db_123", "cmy2", "tbl", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.DROP_PRIV), + Operator.OR))); + Assert.assertTrue(auth.checkTblPriv(null, "db_123", "cmy2", "tbl", + PrivPredicate.of(PrivBitSet.of(PaloPrivilege.DROP_PRIV), + Operator.OR))); + + } + + private PaloAuth testPersist(PaloAuth auth) { + // 1. Write objects to file + File file = new File("./paloAuth"); + try { + file.createNewFile(); + DataOutputStream dos = new DataOutputStream(new FileOutputStream(file)); + + auth.write(dos); + + dos.flush(); + dos.close(); + + // 2. Read objects from file + DataInputStream dis = new DataInputStream(new FileInputStream(file)); + + PaloAuth replayed = PaloAuth.read(dis); + + System.out.println(replayed.toString()); + + return replayed; + + } catch (IOException e) { + e.printStackTrace(); + Assert.fail(); + } finally { + file.delete(); + } + return null; + } + +} diff --git a/fe/test/com/baidu/palo/mysql/privilege/PrivilegeTest.java b/fe/test/com/baidu/palo/mysql/privilege/PrivilegeTest.java new file mode 100644 index 0000000000..658b43b069 --- /dev/null +++ b/fe/test/com/baidu/palo/mysql/privilege/PrivilegeTest.java @@ -0,0 +1,161 @@ +// Copyright (c) 2018, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +package com.baidu.palo.mysql.privilege; + +import com.baidu.palo.common.DdlException; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Random; + +public class PrivilegeTest { + + private static int userNum = 250; + private static int dbNum = 250; + private static int tblNum = 3000; + + private static String[] dbs = new String[dbNum]; + private static String[] tbls = new String[tblNum]; + private static String[] users = new String[userNum]; + + private static PaloAuth auth = new PaloAuth(); + private static Random random = new Random(System.currentTimeMillis()); + + private static Method grantGlobalPrivsM; + private static Method grantDbPrivsM; + private static Method grantTblPrivsM; + + static { + Method[] methods = PaloAuth.class.getDeclaredMethods(); + for (Method method : methods) { + if (method.getName().equals("grantGlobalPrivs")) { + method.setAccessible(true); + grantGlobalPrivsM = method; + } else if (method.getName().equals("grantDbPrivs")) { + method.setAccessible(true); + grantDbPrivsM = method; + } else if (method.getName().equals("grantTblPrivs")) { + method.setAccessible(true); + grantTblPrivsM = method; + } + } + } + + private static String getRandomString(String prefix, int length) { + String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + Random random = new Random(); + StringBuffer sb = new StringBuffer(prefix); + for (int i = 0; i < length; i++) { + int number = random.nextInt(62); + sb.append(str.charAt(number)); + } + return sb.toString(); + } + + private static void genNames() { + for (int i = 0; i < dbNum; i++) { + dbs[i] = getRandomString("db_", 10); + } + + for (int i = 0; i < tblNum; i++) { + tbls[i] = getRandomString("tbl_", 20); + } + + for (int i = 0; i < userNum; i++) { + users[i] = getRandomString("user_", 20); + } + } + + public static void genAuth() throws DdlException, IllegalAccessException, + IllegalArgumentException, InvocationTargetException { + long start = System.currentTimeMillis(); + + byte[] passwd = new byte[] { 'a', 'b' }; + + genNames(); + + // global privs + Object[] params = { "%", "root", passwd, false, false, false, PrivBitSet.of(PaloPrivilege.NODE_PRIV) }; + grantGlobalPrivsM.invoke(auth, params); + + + Object[] params2 = { "%", "superuser", passwd, false, false, false, + PrivBitSet.of(PaloPrivilege.GRANT_PRIV, PaloPrivilege.SELECT_PRIV, PaloPrivilege.LOAD_PRIV, + PaloPrivilege.ALTER_PRIV, PaloPrivilege.CREATE_PRIV, PaloPrivilege.DROP_PRIV) }; + grantGlobalPrivsM.invoke(auth, params2); + + // db privs + PrivBitSet dbPrivs = PrivBitSet.of(PaloPrivilege.SELECT_PRIV, PaloPrivilege.LOAD_PRIV, + PaloPrivilege.ALTER_PRIV, PaloPrivilege.CREATE_PRIV, + PaloPrivilege.DROP_PRIV); + for (int i = 0; i < dbs.length; i++) { + for (int j = 0; j < 2; j++) { + int idx = Math.abs(random.nextInt()) % userNum; + Object[] params3 = { "%", dbs[i], users[idx], false, false, false, dbPrivs }; + grantDbPrivsM.invoke(auth, params3); + } + } + + // tbl privs + PrivBitSet tblPrivs = PrivBitSet.of(PaloPrivilege.SELECT_PRIV, PaloPrivilege.LOAD_PRIV); + for (int i = 0; i < tbls.length; i++) { + int dbIdx = Math.abs(random.nextInt()) % dbNum; + int userIdx = Math.abs(random.nextInt()) % userNum; + int tblIdx = Math.abs(random.nextInt()) % tblNum; + + Object[] params4 = { "%", dbs[dbIdx], users[userIdx], tbls[tblIdx], false, false, false, tblPrivs }; + grantTblPrivsM.invoke(auth, params4); + } + + // System.out.println("gen auth cost: " + (System.currentTimeMillis() - start)); + } + + private static long randomCheckTablePrivs(PrivPredicate predicate) + throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + int dbIdx = Math.abs(random.nextInt()) % dbNum; + int userIdx = Math.abs(random.nextInt()) % userNum; + int tblIdx = Math.abs(random.nextInt()) % tblNum; + + long start = System.nanoTime(); + boolean res = auth.checkTblPriv("192.168.1.1", users[userIdx], dbs[dbIdx], tbls[tblIdx], + predicate); + + if (res) { + System.out.println(res); + } + return System.nanoTime() - start; + // System.out.println("check auth cost: " + (System.currentTimeMillis() - start)); + } + + public static void main(String[] args) throws DdlException, IllegalAccessException, + IllegalArgumentException, InvocationTargetException { + genAuth(); + + // System.out.println(auth.getUserPrivTable()); + // System.out.println(auth.getDbPrivTable()); + // System.out.println(auth.getTablePrivTable()); + + PrivPredicate predicate = PrivPredicate.ADMIN; + int num = 10000; + long cost = 0; + for (int i = 0; i < num; i++) { + cost += randomCheckTablePrivs(predicate); + } + System.out.println("total auth cost: " + cost); + System.out.println("avg auth cost: " + cost / num); + } + +} diff --git a/fe/test/com/baidu/palo/mysql/privilege/UserIdentityTest.java b/fe/test/com/baidu/palo/mysql/privilege/UserIdentityTest.java new file mode 100644 index 0000000000..424d10e8ab --- /dev/null +++ b/fe/test/com/baidu/palo/mysql/privilege/UserIdentityTest.java @@ -0,0 +1,46 @@ +// Copyright (c) 2018, Baidu.com, Inc. All Rights Reserved + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// 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. + +package com.baidu.palo.mysql.privilege; + +import com.baidu.palo.analysis.UserIdentity; +import com.baidu.palo.system.SystemInfoService; + +import org.junit.Assert; +import org.junit.Test; + + +public class UserIdentityTest { + + @Test + public void test() { + UserIdentity userIdent = new UserIdentity(SystemInfoService.DEFAULT_CLUSTER + ":cmy", "192.%"); + userIdent.setIsAnalyzed(); + + String str = "'" + SystemInfoService.DEFAULT_CLUSTER + ":cmy" + "'@'192.%'"; + Assert.assertEquals(str, userIdent.toString()); + + UserIdentity userIdent2 = UserIdentity.fromString(str); + Assert.assertEquals(userIdent2.toString(), userIdent.toString()); + + String str2 = "'default_cluster:walletdc_write'@['cluster-leida.orp.all']"; + userIdent = UserIdentity.fromString(str2); + Assert.assertNotNull(userIdent); + Assert.assertTrue(userIdent.isDomain()); + userIdent.setIsAnalyzed(); + Assert.assertEquals(str2, userIdent.toString()); + } + +} diff --git a/fe/test/com/baidu/palo/persist/AccessTest.java b/fe/test/com/baidu/palo/persist/AccessTest.java deleted file mode 100644 index c13768f676..0000000000 --- a/fe/test/com/baidu/palo/persist/AccessTest.java +++ /dev/null @@ -1,286 +0,0 @@ -// Copyright (c) 2017, Baidu.com, Inc. All Rights Reserved - -// 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. - -package com.baidu.palo.persist; - -import org.easymock.EasyMock; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.runner.RunWith; -import org.powermock.api.easymock.PowerMock; -import org.powermock.core.classloader.annotations.PowerMockIgnore; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; - -import com.baidu.palo.catalog.AccessPrivilege; -import com.baidu.palo.catalog.UserProperty; -import com.baidu.palo.catalog.UserPropertyMgr; -import com.baidu.palo.catalog.Catalog; -import com.baidu.palo.catalog.Database; -import com.baidu.palo.common.DdlException; -import com.baidu.palo.common.FeConstants; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Arrays; - -@RunWith(PowerMockRunner.class) -@PowerMockIgnore("org.apache.log4j.*") -@PrepareForTest(Catalog.class) -public class AccessTest { - private static File file = new File("./AccessTest"); - private static UserProperty userProperty1 = new UserProperty(); - private static UserProperty userProperty2 = new UserProperty(); - private static byte[] passwd = new byte[0]; - private static EditLog edits; - private static Catalog catalog; - - static UserProperty invokeGetAccessResourceFunction(UserPropertyMgr userPropertyMgr, String userName) - throws IllegalAccessException, - IllegalArgumentException, InvocationTargetException, NoSuchFieldException, - SecurityException, NoSuchMethodException { - Method method = userPropertyMgr.getClass().getDeclaredMethod("getAccessResource", String.class); - method.setAccessible(true); - Object object = method.invoke(userPropertyMgr, userName); - return (UserProperty) object; - } - - @BeforeClass - public static void setUpClass() throws IOException { - file.createNewFile(); - passwd = "passwordIsLong".getBytes(); - // ordinary user - userProperty1.setUser("userName"); - userProperty1.setAccess("db1", AccessPrivilege.READ_ONLY); - userProperty1.setAccess("db2", AccessPrivilege.READ_WRITE); - userProperty1.setPassword(passwd); - userProperty1.setIsAdmin(false); - // adminstrator user - userProperty2.setUser("root"); - userProperty2.setPassword(new byte[0]); - userProperty2.setIsAdmin(true); - - edits = EasyMock.createMock(EditLog.class); - edits.logAlterAccess(EasyMock.isA(UserProperty.class)); - EasyMock.expectLastCall().anyTimes(); - edits.logDropUser(EasyMock.isA(String.class)); - EasyMock.expectLastCall().anyTimes(); - EasyMock.replay(edits); - - } - - @Before - public void setUp() { - catalog = EasyMock.createMock(Catalog.class); - - PowerMock.mockStatic(Catalog.class); - EasyMock.expect(Catalog.getInstance()).andReturn(catalog).anyTimes(); - EasyMock.expect(Catalog.getCurrentCatalogJournalVersion()).andReturn(FeConstants.meta_version).anyTimes(); - PowerMock.replay(Catalog.class); - } - - @AfterClass - public static void tearDownClass() { - file.delete(); - } - - @Test - public void testAccessResource() throws Exception { - // write ordinary user information to snapshot - DataOutputStream dos = new DataOutputStream(new FileOutputStream(file)); - userProperty1.write(dos); - dos.flush(); - dos.close(); - - // read snapshot - UserProperty result = new UserProperty(); - DataInputStream dis = new DataInputStream(new FileInputStream(file)); - result.readFields(dis); - dis.close(); - - // check result - Assert.assertEquals(result.getUser(), "userName"); - Assert.assertTrue(Arrays.equals(result.getPassword(), passwd)); - Assert.assertEquals(result.isAdmin(), false); - Assert.assertEquals(result.getMaxConn(), 100); - Assert.assertTrue(result.checkAccess("db1", AccessPrivilege.READ_ONLY)); - Assert.assertFalse(result.checkAccess("db1", AccessPrivilege.READ_WRITE)); - Assert.assertTrue(result.checkAccess("db2", AccessPrivilege.READ_ONLY)); - Assert.assertTrue(result.checkAccess("db2", AccessPrivilege.READ_WRITE)); - Assert.assertFalse(result.checkAccess("no_exists_db", AccessPrivilege.READ_ONLY)); - Assert.assertFalse(result.checkAccess("no_exists_db", AccessPrivilege.READ_WRITE)); - } - - @Test - public void testAccessService() throws Exception { - file.delete(); - file.createNewFile(); - UserPropertyMgr result = new UserPropertyMgr(); - result.unprotectAlterAccess(userProperty1); - result.unprotectAlterAccess(userProperty2); - - // write snapshot - DataOutputStream dos = new DataOutputStream(new FileOutputStream(file)); - result.write(dos); - dos.flush(); - dos.close(); - // read snapshot - DataInputStream dis = new DataInputStream(new FileInputStream(file)); - result.readFields(dis); - dis.close(); - // check root resource - UserProperty rootResource = invokeGetAccessResourceFunction(result, "root"); - Assert.assertEquals(rootResource.getUser(), "root"); - Assert.assertEquals(rootResource.isAdmin(), true); - Assert.assertEquals(rootResource.getMaxConn(), 100); - Assert.assertTrue(Arrays.equals(rootResource.getPassword(), new byte[0])); - Assert.assertTrue(rootResource.checkAccess("db1", AccessPrivilege.READ_WRITE)); - Assert.assertTrue(rootResource.checkAccess("db2", AccessPrivilege.READ_WRITE)); - - UserProperty ordinaryResource = invokeGetAccessResourceFunction(result, "userName"); - Assert.assertEquals(ordinaryResource.getUser(), "userName"); - Assert.assertTrue(Arrays.equals(ordinaryResource.getPassword(), passwd)); - Assert.assertEquals(ordinaryResource.isAdmin(), false); - Assert.assertEquals(ordinaryResource.getMaxConn(), 100); - Assert.assertTrue(ordinaryResource.checkAccess("db1", AccessPrivilege.READ_ONLY)); - Assert.assertFalse(ordinaryResource.checkAccess("db1", AccessPrivilege.READ_WRITE)); - Assert.assertTrue(ordinaryResource.checkAccess("db2", AccessPrivilege.READ_ONLY)); - Assert.assertTrue(ordinaryResource.checkAccess("db2", AccessPrivilege.READ_WRITE)); - } - - // add user - @Test(expected = DdlException.class) - public void testAddUserExceptionUserIsEmpty() throws Exception { - UserPropertyMgr service = new UserPropertyMgr(); - service.addUser("", "", new byte[0], false); - Assert.fail("No Exception throws."); - } - - @Test(expected = DdlException.class) - public void testAddUserExceptionUserIsNull() throws Exception { - UserPropertyMgr service = new UserPropertyMgr(); - service.addUser(null, null, new byte[0], false); - Assert.fail("No Exception throws."); - } - - @Test - public void testAddUserSuccess() throws Exception { - UserPropertyMgr service = new UserPropertyMgr(); - service.setEditLog(edits); - service.addUser("cluster", "user", new byte[0], false); - Assert.assertNotNull(invokeGetAccessResourceFunction(service, "user")); - - Assert.assertTrue(Arrays.equals(invokeGetAccessResourceFunction(service, "user") - .getPassword(), new byte[0])); - } - - @Test(expected = DdlException.class) - public void testAddUserTwoTimes() throws Exception { - UserPropertyMgr service = new UserPropertyMgr(); - service.setEditLog(edits); - service.addUser("cluster", "user", "pi".getBytes(), false); - service.addUser("cluster", "user", "pi".getBytes(), false); - Assert.fail("No Exception throws."); - } - - // set Passwd - @Test(expected = DdlException.class) - public void testSetPasswdExceptionUserIsEmpty() throws Exception { - UserPropertyMgr service = new UserPropertyMgr(); - service.setPasswd("user", new byte[0]); - Assert.fail("No Exception throws."); - } - - @Test - public void testSetPasswdSuccess() throws Exception { - UserPropertyMgr service = new UserPropertyMgr(); - service.setEditLog(edits); - service.addUser("cluster", "user", new byte[0], false); - Assert.assertTrue(Arrays.equals(service.getPassword("user"), new byte[0])); - - byte[] newPasswd = "*B6BDA741F59FE8066344FE3E118291C5D7DD12AD".getBytes(); - service.setPasswd("user", newPasswd); - Assert.assertTrue(Arrays.equals(service.getPassword("user"), newPasswd)); - } - - @Test(expected = DdlException.class) - public void testGrandExceptionUserIsEmpty() throws Exception { - UserPropertyMgr service = new UserPropertyMgr(); - service.setEditLog(edits); - service.grant("user", "db", AccessPrivilege.READ_ONLY); - Assert.fail("No Exception throws."); - } - - // grant - @Ignore("Not Ready to Run") - @Test(expected = DdlException.class) - public void testGrandExceptionDbIsEmpty() throws Exception { - UserPropertyMgr service = new UserPropertyMgr(); - service.setEditLog(edits); - service.addUser("cluster", "user", new byte[0], false); - service.grant("user", "db_not_exists", AccessPrivilege.READ_ONLY); - Assert.fail("No Exception throws."); - } - - @Test - public void testGrandSuccess() throws Exception { - catalog = EasyMock.createMock(Catalog.class); - EasyMock.expect(catalog.getDb("db_exists")).andReturn(new Database()).anyTimes(); - EasyMock.replay(catalog); - PowerMock.mockStatic(Catalog.class); - EasyMock.expect(Catalog.getInstance()).andReturn(catalog).anyTimes(); - PowerMock.replay(Catalog.class); - - UserPropertyMgr service = new UserPropertyMgr(); - service.setEditLog(edits); - service.addUser("cluster", "user", new byte[0], false); - service.grant("user", "db_exists", AccessPrivilege.READ_ONLY); - Assert.assertFalse(service.checkAccess("user", "db_not_exists", AccessPrivilege.READ_ONLY)); - Assert.assertTrue(service.checkAccess("user", "db_exists", AccessPrivilege.READ_ONLY)); - } - - // drop User - @Test(expected = DdlException.class) - public void testDropUserExceptionUserNotExist() throws Exception { - UserPropertyMgr service = new UserPropertyMgr(); - service.setEditLog(edits); - service.dropUser("user"); - Assert.fail("No Exception throws."); - } - - @Test - public void testDropUserSuccess() throws Exception { - UserPropertyMgr service = new UserPropertyMgr(); - service.setEditLog(edits); - service.addUser("cluster", "user", new byte[0], false); - Assert.assertNotNull(invokeGetAccessResourceFunction(service, "user")); - service.dropUser("user"); - Assert.assertNull(invokeGetAccessResourceFunction(service, "user")); - } -} diff --git a/fe/test/com/baidu/palo/qe/ConnectContextTest.java b/fe/test/com/baidu/palo/qe/ConnectContextTest.java index cba90dc463..ffe0b714fc 100644 --- a/fe/test/com/baidu/palo/qe/ConnectContextTest.java +++ b/fe/test/com/baidu/palo/qe/ConnectContextTest.java @@ -49,13 +49,14 @@ public class ConnectContextTest { @Before public void setUp() throws Exception { channel = EasyMock.createMock(MysqlChannel.class); - EasyMock.expect(channel.getRemoteHostString()).andReturn("127.0.0.1:12345").anyTimes(); + EasyMock.expect(channel.getRemoteHostPortString()).andReturn("127.0.0.1:12345").anyTimes(); channel.close(); EasyMock.expectLastCall().anyTimes(); executor = EasyMock.createMock(StmtExecutor.class); executor.cancel(); EasyMock.expectLastCall().anyTimes(); PowerMock.expectNew(MysqlChannel.class, EasyMock.isA(SocketChannel.class)).andReturn(channel).anyTimes(); + EasyMock.expect(channel.getRemoteIp()).andReturn("192.168.1.1").anyTimes(); EasyMock.replay(channel); EasyMock.replay(executor); PowerMock.replay(MysqlChannel.class); @@ -92,8 +93,8 @@ public class ConnectContextTest { Assert.assertEquals("testCluster:testDb", ctx.getDatabase()); // User - ctx.setUser("testCluster:testUser"); - Assert.assertEquals("testCluster:testUser", ctx.getUser()); + ctx.setQualifiedUser("testCluster:testUser"); + Assert.assertEquals("testCluster:testUser", ctx.getQualifiedUser()); // Serializer Assert.assertNotNull(ctx.getSerializer()); @@ -119,10 +120,10 @@ public class ConnectContextTest { List row = ctx.toThreadInfo().toRow(1000); Assert.assertEquals(9, row.size()); Assert.assertEquals("101", row.get(0)); - Assert.assertEquals("testCluster:testUser", row.get(1)); + Assert.assertEquals("testUser", row.get(1)); Assert.assertEquals("127.0.0.1:12345", row.get(2)); Assert.assertEquals("testCluster", row.get(3)); - Assert.assertEquals("testCluster:testDb", row.get(4)); + Assert.assertEquals("testDb", row.get(4)); Assert.assertEquals("Ping", row.get(5)); Assert.assertEquals("1", row.get(6)); Assert.assertEquals("", row.get(7)); diff --git a/fe/test/com/baidu/palo/qe/ConnectProcessorTest.java b/fe/test/com/baidu/palo/qe/ConnectProcessorTest.java index 1d6f82d521..9fcc9f7044 100644 --- a/fe/test/com/baidu/palo/qe/ConnectProcessorTest.java +++ b/fe/test/com/baidu/palo/qe/ConnectProcessorTest.java @@ -22,6 +22,7 @@ package com.baidu.palo.qe; import com.baidu.palo.analysis.AccessTestUtil; import com.baidu.palo.catalog.Catalog; +import com.baidu.palo.metric.MetricRepo; import com.baidu.palo.mysql.MysqlChannel; import com.baidu.palo.mysql.MysqlCommand; import com.baidu.palo.mysql.MysqlEofPacket; @@ -29,8 +30,8 @@ import com.baidu.palo.mysql.MysqlErrPacket; import com.baidu.palo.mysql.MysqlOkPacket; import com.baidu.palo.mysql.MysqlSerializer; -import org.junit.Assert; import org.easymock.EasyMock; +import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -96,6 +97,8 @@ public class ConnectProcessorTest { serializer.writeEofString(""); fieldListPacket = serializer.toByteBuffer(); } + + MetricRepo.init(); } @Before @@ -108,7 +111,7 @@ public class ConnectProcessorTest { // Mock MysqlChannel channel = EasyMock.createNiceMock(MysqlChannel.class); PowerMock.expectNew(MysqlChannel.class, EasyMock.isA(SocketChannel.class)).andReturn(channel).anyTimes(); - EasyMock.expect(channel.getRemoteHostString()).andReturn("127.0.0.1:12345").anyTimes(); + EasyMock.expect(channel.getRemoteHostPortString()).andReturn("127.0.0.1:12345").anyTimes(); PowerMock.replay(MysqlChannel.class); myContext = new ConnectContext(EasyMock.createMock(SocketChannel.class)); } @@ -126,7 +129,7 @@ public class ConnectProcessorTest { channel.sendAndFlush(EasyMock.isA(ByteBuffer.class)); EasyMock.expectLastCall().anyTimes(); - EasyMock.expect(channel.getRemoteHostString()).andReturn("127.0.0.1:12345").anyTimes(); + EasyMock.expect(channel.getRemoteHostPortString()).andReturn("127.0.0.1:12345").anyTimes(); EasyMock.replay(channel); @@ -144,10 +147,12 @@ public class ConnectProcessorTest { EasyMock.expect(context.getCatalog()).andReturn(catalog).anyTimes(); EasyMock.expect(context.getState()).andReturn(myContext.getState()).anyTimes(); EasyMock.expect(context.getAuditBuilder()).andReturn(auditBuilder).anyTimes(); - EasyMock.expect(context.getUser()).andReturn("testCluster:user").anyTimes(); + EasyMock.expect(context.getQualifiedUser()).andReturn("testCluster:user").anyTimes(); EasyMock.expect(context.getClusterName()).andReturn("testCluster").anyTimes(); EasyMock.expect(context.getStartTime()).andReturn(0L).anyTimes(); EasyMock.expect(context.getSerializer()).andDelegateTo(myContext).anyTimes(); + EasyMock.expect(context.getReturnRows()).andReturn(1L).anyTimes(); + EasyMock.expect(context.isKilled()).andReturn(false).anyTimes(); context.setKilled(); EasyMock.expectLastCall().andDelegateTo(myContext).anyTimes(); context.setCommand(EasyMock.anyObject(MysqlCommand.class)); @@ -278,7 +283,6 @@ public class ConnectProcessorTest { processor.processOnce(); Assert.assertEquals(MysqlCommand.COM_QUERY, myContext.getCommand()); - Assert.assertEquals("Maybe palo bug", myContext.getState().getErrorMessage()); Assert.assertTrue(myContext.getState().toResponsePacket() instanceof MysqlErrPacket); } diff --git a/fe/test/com/baidu/palo/qe/ConnectSchedulerTest.java b/fe/test/com/baidu/palo/qe/ConnectSchedulerTest.java index 5f068460d6..c9fbf93acb 100644 --- a/fe/test/com/baidu/palo/qe/ConnectSchedulerTest.java +++ b/fe/test/com/baidu/palo/qe/ConnectSchedulerTest.java @@ -24,8 +24,8 @@ import com.baidu.palo.analysis.AccessTestUtil; import com.baidu.palo.mysql.MysqlChannel; import com.baidu.palo.mysql.MysqlProto; -import org.junit.Assert; import org.easymock.EasyMock; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -38,7 +38,6 @@ import org.slf4j.LoggerFactory; import java.io.IOException; import java.nio.channels.SocketChannel; -import java.util.List; import java.util.concurrent.atomic.AtomicLong; @RunWith(PowerMockRunner.class) @@ -92,6 +91,8 @@ public class ConnectSchedulerTest { succSubmit = new AtomicLong(0); MysqlChannel channel = EasyMock.createMock(MysqlChannel.class); + EasyMock.expect(channel.getRemoteIp()).andReturn("192.168.1.1").anyTimes(); + EasyMock.replay(channel); PowerMock.expectNew(MysqlChannel.class, EasyMock.isA(SocketChannel.class)).andReturn(channel).anyTimes(); PowerMock.replay(MysqlChannel.class); @@ -120,17 +121,10 @@ public class ConnectSchedulerTest { } else { context.setCatalog(AccessTestUtil.fetchAdminCatalog()); } - context.setUser("root"); + context.setQualifiedUser("root"); Assert.assertTrue(scheduler.submit(context)); Assert.assertEquals(i, context.getConnectionId()); } - - Thread.sleep(1000); - Assert.assertNotNull(scheduler.getContext(0)); - List threads = scheduler.listConnection("root"); - Assert.assertEquals(1, threads.size()); - Assert.assertNotNull(scheduler.getContext(0)); - Assert.assertEquals(1, succSubmit.intValue()); } @Test @@ -142,7 +136,7 @@ public class ConnectSchedulerTest { ConnectContext context = new ConnectContext(EasyMock.createMock(SocketChannel.class)); context.setCatalog(AccessTestUtil.fetchAdminCatalog()); - context.setUser("root"); + context.setQualifiedUser("root"); Assert.assertTrue(scheduler.submit(context)); Assert.assertEquals(0, context.getConnectionId()); diff --git a/fe/test/com/baidu/palo/qe/CoordinatorTest.java b/fe/test/com/baidu/palo/qe/CoordinatorTest.java index 01c0695299..ade98a8d36 100644 --- a/fe/test/com/baidu/palo/qe/CoordinatorTest.java +++ b/fe/test/com/baidu/palo/qe/CoordinatorTest.java @@ -35,21 +35,16 @@ import com.baidu.palo.planner.PlanNode; import com.baidu.palo.planner.PlanNodeId; import com.baidu.palo.planner.Planner; import com.baidu.palo.system.Backend; -import com.baidu.palo.thrift.TExecPlanFragmentParams; import com.baidu.palo.thrift.TNetworkAddress; -import com.baidu.palo.thrift.TQueryOptions; import com.baidu.palo.thrift.TScanRange; import com.baidu.palo.thrift.TScanRangeLocation; import com.baidu.palo.thrift.TScanRangeLocations; -import com.baidu.palo.thrift.TUniqueId; import com.google.common.collect.ImmutableMap; -import org.apache.thrift.TException; import org.easymock.EasyMock; import org.junit.Assert; import org.junit.BeforeClass; -import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.api.easymock.PowerMock; import org.powermock.core.classloader.annotations.PowerMockIgnore; @@ -63,7 +58,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.ConcurrentMap; @RunWith(PowerMockRunner.class) @PowerMockIgnore({"org.apache.log4j.*", "javax.management.*"}) @@ -414,10 +408,6 @@ public class CoordinatorTest extends Coordinator { } } - @Test - public void testNoUsedHosts() { - } - /* public void testNetworkException() throws TException, NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, diff --git a/fe/test/com/baidu/palo/qe/SetExecutorTest.java b/fe/test/com/baidu/palo/qe/SetExecutorTest.java index 727f7a9456..93a94188b3 100644 --- a/fe/test/com/baidu/palo/qe/SetExecutorTest.java +++ b/fe/test/com/baidu/palo/qe/SetExecutorTest.java @@ -27,32 +27,64 @@ import com.baidu.palo.analysis.SetNamesVar; import com.baidu.palo.analysis.SetPassVar; import com.baidu.palo.analysis.SetStmt; import com.baidu.palo.analysis.SetVar; +import com.baidu.palo.analysis.UserIdentity; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.DdlException; import com.baidu.palo.common.InternalException; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.mysql.privilege.PrivPredicate; import com.google.common.collect.Lists; -import org.junit.Assert; + import org.junit.Before; import org.junit.Test; import java.util.List; +import mockit.Mocked; +import mockit.NonStrictExpectations; +import mockit.internal.startup.Startup; + public class SetExecutorTest { private Analyzer analyzer; private ConnectContext ctx; + @Mocked + private PaloAuth auth; + + static { + Startup.initializeIfPossible(); + } + @Before - public void setUp() { + public void setUp() throws DdlException { analyzer = AccessTestUtil.fetchAdminAnalyzer(false); ctx = new ConnectContext(null); ctx.setCatalog(AccessTestUtil.fetchAdminCatalog()); + ctx.setQualifiedUser("root"); + ctx.setRemoteIP("192.168.1.1"); + + new NonStrictExpectations() { + { + auth.checkGlobalPriv((ConnectContext) any, (PrivPredicate) any); + result = true; + + auth.checkDbPriv((ConnectContext) any, anyString, (PrivPredicate) any); + result = true; + + auth.checkTblPriv((ConnectContext) any, anyString, anyString, (PrivPredicate) any); + result = true; + + auth.setPassword((SetPassVar) any); + minTimes = 0; + } + }; } @Test public void testNormal() throws InternalException, AnalysisException, DdlException { List vars = Lists.newArrayList(); - vars.add(new SetPassVar("testUser", "*88EEBA7D913688E7278E2AD071FDB5E76D76D34B")); + vars.add(new SetPassVar(new UserIdentity("testUser", "%"), "*88EEBA7D913688E7278E2AD071FDB5E76D76D34B")); vars.add(new SetNamesVar("utf8")); vars.add(new SetVar("query_timeout", new IntLiteral(10L))); @@ -63,19 +95,6 @@ public class SetExecutorTest { executor.execute(); } - @Test(expected = DdlException.class) - public void testNoPriv() throws InternalException, AnalysisException, DdlException { - List vars = Lists.newArrayList(); - vars.add(new SetPassVar("root", "*88EEBA7D913688E7278E2AD071FDB5E76D76D34B")); - - SetStmt stmt = new SetStmt(vars); - stmt.analyze(analyzer); - SetExecutor executor = new SetExecutor(ctx, stmt); - - executor.execute(); - Assert.fail("No exception throws"); - } - @Test public void testEmpty() { } diff --git a/fe/test/com/baidu/palo/qe/ShowExecutorTest.java b/fe/test/com/baidu/palo/qe/ShowExecutorTest.java index 3f00d7b3ae..1367606f3a 100644 --- a/fe/test/com/baidu/palo/qe/ShowExecutorTest.java +++ b/fe/test/com/baidu/palo/qe/ShowExecutorTest.java @@ -32,7 +32,6 @@ import com.baidu.palo.analysis.ShowCreateTableStmt; import com.baidu.palo.analysis.ShowDbStmt; import com.baidu.palo.analysis.ShowEnginesStmt; import com.baidu.palo.analysis.ShowProcedureStmt; -import com.baidu.palo.analysis.ShowProcesslistStmt; import com.baidu.palo.analysis.ShowTableStmt; import com.baidu.palo.analysis.ShowVariablesStmt; import com.baidu.palo.analysis.TableName; @@ -46,18 +45,21 @@ import com.baidu.palo.catalog.Partition; import com.baidu.palo.catalog.PrimitiveType; import com.baidu.palo.catalog.RandomDistributionInfo; import com.baidu.palo.catalog.SinglePartitionInfo; +import com.baidu.palo.catalog.Table; import com.baidu.palo.catalog.Table.TableType; -import com.baidu.palo.system.SystemInfoService; import com.baidu.palo.common.AnalysisException; import com.baidu.palo.common.InternalException; import com.baidu.palo.common.PatternMatcher; -import com.baidu.palo.thrift.TStorageType; import com.baidu.palo.mysql.MysqlCommand; +import com.baidu.palo.mysql.privilege.PaloAuth; +import com.baidu.palo.mysql.privilege.PrivPredicate; +import com.baidu.palo.system.SystemInfoService; +import com.baidu.palo.thrift.TStorageType; import com.google.common.collect.Lists; -import org.junit.Assert; import org.easymock.EasyMock; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -72,7 +74,7 @@ import java.util.List; @RunWith(PowerMockRunner.class) @PowerMockIgnore({ "org.apache.log4j.*", "javax.management.*" }) -@PrepareForTest({ ShowExecutor.class, Catalog.class, VariableMgr.class, HelpModule.class }) +@PrepareForTest({ ShowExecutor.class, Catalog.class, VariableMgr.class, HelpModule.class, ConnectContext.class }) public class ShowExecutorTest { private ConnectContext ctx; private Catalog catalog; @@ -82,6 +84,7 @@ public class ShowExecutorTest { ctx = new ConnectContext(null); ctx.setCommand(MysqlCommand.COM_SLEEP); + Column column1 = new Column("col1", PrimitiveType.BIGINT); Column column2 = new Column("col2", PrimitiveType.DOUBLE); column1.setIsKey(true); @@ -121,6 +124,17 @@ public class ShowExecutorTest { EasyMock.expectLastCall().anyTimes(); EasyMock.expect(db.getTable(EasyMock.isA(String.class))).andReturn(table).anyTimes(); EasyMock.replay(db); + + // mock auth + PaloAuth auth = EasyMock.createMock(PaloAuth.class); + EasyMock.expect(auth.checkGlobalPriv(EasyMock.isA(ConnectContext.class), + EasyMock.isA(PrivPredicate.class))).andReturn(true).anyTimes(); + EasyMock.expect(auth.checkDbPriv(EasyMock.isA(ConnectContext.class), EasyMock.anyString(), + EasyMock.isA(PrivPredicate.class))).andReturn(true).anyTimes(); + EasyMock.expect(auth.checkTblPriv(EasyMock.isA(ConnectContext.class), EasyMock.anyString(), + EasyMock.anyString(), EasyMock.isA(PrivPredicate.class))) + .andReturn(true).anyTimes(); + EasyMock.replay(auth); // mock catalog. catalog = EasyMock.createMock(Catalog.class); @@ -129,8 +143,20 @@ public class ShowExecutorTest { EasyMock.expect(catalog.getClusterDbNames("testCluster")).andReturn(Lists.newArrayList("testCluster:testDb")) .anyTimes(); EasyMock.expect(catalog.getClusterDbNames("")).andReturn(Lists.newArrayList("")).anyTimes(); + EasyMock.expect(catalog.getAuth()).andReturn(auth).anyTimes(); EasyMock.replay(catalog); - PowerMock.expectNew(Catalog.class).andReturn(catalog).anyTimes(); + + PowerMock.mockStatic(Catalog.class); + EasyMock.expect(Catalog.getInstance()).andReturn(catalog).anyTimes(); + EasyMock.expect(Catalog.getCurrentCatalog()).andReturn(catalog).anyTimes(); + Catalog.getDdlStmt(EasyMock.isA(Table.class), EasyMock.isA(List.class), + EasyMock.isA(List.class), EasyMock.isA(List.class), EasyMock.anyBoolean(), + EasyMock.anyShort()); + EasyMock.expectLastCall().anyTimes(); + Catalog.getDdlStmt(EasyMock.isA(Table.class), EasyMock.isA(List.class), + EasyMock.isNull(List.class), EasyMock.isNull(List.class), EasyMock.anyBoolean(), + EasyMock.anyShort()); + EasyMock.expectLastCall().anyTimes(); PowerMock.replay(Catalog.class); // mock scheduler @@ -140,8 +166,12 @@ public class ShowExecutorTest { EasyMock.replay(scheduler); ctx.setConnectScheduler(scheduler); ctx.setCatalog(AccessTestUtil.fetchAdminCatalog()); - ctx.setUser("testCluster:testUser"); + ctx.setQualifiedUser("testCluster:testUser"); ctx.setCluster("testCluster"); + + PowerMock.mockStatic(ConnectContext.class); + EasyMock.expect(ConnectContext.get()).andReturn(ctx).anyTimes(); + PowerMock.replay(ConnectContext.class); } @Test @@ -169,8 +199,6 @@ public class ShowExecutorTest { ShowExecutor executor = new ShowExecutor(ctx, stmt); ctx.setCatalog(AccessTestUtil.fetchBlockCatalog()); ShowResultSet resultSet = executor.execute(); - - Assert.assertFalse(resultSet.next()); } @Test @@ -211,6 +239,7 @@ public class ShowExecutorTest { Catalog catalog = AccessTestUtil.fetchAdminCatalog(); PowerMock.mockStatic(Catalog.class); EasyMock.expect(Catalog.getInstance()).andReturn(catalog).anyTimes(); + EasyMock.expect(Catalog.getCurrentCatalog()).andReturn(catalog).anyTimes(); EasyMock.expect(Catalog.getCurrentSystemInfo()).andReturn(clusterInfo).anyTimes(); PowerMock.replay(Catalog.class); @@ -282,7 +311,7 @@ public class ShowExecutorTest { @Test public void testShowCreateDb() throws AnalysisException { ctx.setCatalog(catalog); - ctx.setUser("testCluster:testUser"); + ctx.setQualifiedUser("testCluster:testUser"); ShowCreateDbStmt stmt = new ShowCreateDbStmt("testCluster:testDb"); ShowExecutor executor = new ShowExecutor(ctx, stmt); @@ -297,7 +326,7 @@ public class ShowExecutorTest { @Test(expected = AnalysisException.class) public void testShowCreateNoDb() throws AnalysisException { ctx.setCatalog(catalog); - ctx.setUser("testCluster:testUser"); + ctx.setQualifiedUser("testCluster:testUser"); ShowCreateDbStmt stmt = new ShowCreateDbStmt("testCluster:emptyDb"); ShowExecutor executor = new ShowExecutor(ctx, stmt); @@ -306,33 +335,6 @@ public class ShowExecutorTest { Assert.fail("No exception throws."); } - @Test - public void testShowCreateTable() throws AnalysisException { - ctx.setCatalog(catalog); - ctx.setUser("testCluster:testUser"); - - ShowCreateTableStmt stmt = new ShowCreateTableStmt(new TableName("testCluster:testDb", "testTbl")); - ShowExecutor executor = new ShowExecutor(ctx, stmt); - ShowResultSet resultSet = executor.execute(); - - Assert.assertTrue(resultSet.next()); - Assert.assertEquals("testTbl", resultSet.getString(0)); - - // print to help compare - String result = new String(resultSet.getString(1)); - result = result.replace(' ', '*'); - System.out.println("create table stmt:[" + result + "]"); - - Assert.assertEquals("CREATE TABLE `testTbl` (\n `col1` bigint(20) NOT NULL COMMENT \"\",\n" - + " `col2` double NOT NULL COMMENT \"\"\n" - + ") ENGINE=OLAP\n" - + "AGG_KEYS(`col1`, `col2`)\n" - + "DISTRIBUTED BY RANDOM BUCKETS 10\n" - + "PROPERTIES (\n" - + "\"storage_type\" = \"COLUMN\"\n" - + ");", resultSet.getString(1)); - } - @Test(expected = AnalysisException.class) public void testShowCreateTableEmptyDb() throws AnalysisException { ShowCreateTableStmt stmt = new ShowCreateTableStmt(new TableName("testCluster:emptyDb", "testTable")); @@ -354,7 +356,7 @@ public class ShowExecutorTest { @Test public void testShowColumn() throws AnalysisException { ctx.setCatalog(catalog); - ctx.setUser("testCluster:testUser"); + ctx.setQualifiedUser("testCluster:testUser"); ShowColumnStmt stmt = new ShowColumnStmt(new TableName("testCluster:testDb", "testTbl"), null, null, false); stmt.analyze(AccessTestUtil.fetchAdminAnalyzer(false)); ShowExecutor executor = new ShowExecutor(ctx, stmt); @@ -421,8 +423,6 @@ public class ShowExecutorTest { Assert.assertEquals("Name", resultSet.getMetaData().getColumn(0).getName()); Assert.assertEquals("Location", resultSet.getMetaData().getColumn(1).getName()); Assert.assertEquals("Comment", resultSet.getMetaData().getColumn(2).getName()); - - Assert.assertTrue(resultSet.next()); } @Test @@ -444,16 +444,6 @@ public class ShowExecutorTest { Assert.assertFalse(resultSet.next()); } - @Test - public void testShowProcesslist() throws AnalysisException { - ShowProcesslistStmt stmt = new ShowProcesslistStmt(); - ShowExecutor executor = new ShowExecutor(ctx, stmt); - ShowResultSet resultSet = executor.execute(); - - Assert.assertTrue(resultSet.next()); - Assert.assertFalse(resultSet.next()); - } - @Test public void testHelp() throws AnalysisException, IOException, InternalException { HelpModule module = new HelpModule(); diff --git a/fe/test/com/baidu/palo/qe/StmtExecutorTest.java b/fe/test/com/baidu/palo/qe/StmtExecutorTest.java index 8cb1dbe478..ce8e1f7789 100644 --- a/fe/test/com/baidu/palo/qe/StmtExecutorTest.java +++ b/fe/test/com/baidu/palo/qe/StmtExecutorTest.java @@ -36,19 +36,21 @@ import com.baidu.palo.analysis.UseStmt; import com.baidu.palo.catalog.Catalog; import com.baidu.palo.common.DdlException; import com.baidu.palo.common.util.RuntimeProfile; +import com.baidu.palo.metric.MetricRepo; import com.baidu.palo.mysql.MysqlChannel; import com.baidu.palo.mysql.MysqlSerializer; import com.baidu.palo.planner.Planner; +import com.baidu.palo.rewrite.ExprRewriter; +import com.baidu.palo.service.FrontendOptions; import com.baidu.palo.thrift.TQueryOptions; import com.baidu.palo.thrift.TUniqueId; import com.google.common.collect.Lists; -import java_cup.runtime.Symbol; - import org.easymock.EasyMock; import org.junit.Assert; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.api.easymock.PowerMock; @@ -58,10 +60,13 @@ import org.powermock.modules.junit4.PowerMockRunner; import java.io.IOException; import java.lang.reflect.Field; +import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.util.List; import java.util.SortedMap; +import java_cup.runtime.Symbol; + @RunWith(PowerMockRunner.class) @PowerMockIgnore({"org.apache.log4j.*", "javax.management.*"}) @PrepareForTest({StmtExecutor.class, DdlExecutor.class, Catalog.class}) @@ -70,6 +75,17 @@ public class StmtExecutorTest { private QueryState state; private ConnectScheduler scheduler; + @BeforeClass + public static void start() { + MetricRepo.init(); + try { + FrontendOptions.init(); + } catch (UnknownHostException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + @Before public void setUp() throws IOException { state = new QueryState(); @@ -90,9 +106,11 @@ public class StmtExecutorTest { EasyMock.expect(ctx.getState()).andReturn(state).anyTimes(); EasyMock.expect(ctx.getConnectScheduler()).andReturn(scheduler).anyTimes(); EasyMock.expect(ctx.getConnectionId()).andReturn(1).anyTimes(); - EasyMock.expect(ctx.getUser()).andReturn("testUser").anyTimes(); + EasyMock.expect(ctx.getQualifiedUser()).andReturn("testUser").anyTimes(); ctx.setKilled(); EasyMock.expectLastCall().anyTimes(); + ctx.updateReturnRows(EasyMock.anyInt()); + EasyMock.expectLastCall().anyTimes(); ctx.setQueryId(EasyMock.isA(TUniqueId.class)); EasyMock.expectLastCall().anyTimes(); EasyMock.expect(ctx.queryId()).andReturn(new TUniqueId()).anyTimes(); @@ -114,6 +132,8 @@ public class StmtExecutorTest { queryStmt.getDbs(EasyMock.isA(Analyzer.class), EasyMock.isA(SortedMap.class)); EasyMock.expectLastCall().anyTimes(); EasyMock.expect(queryStmt.getRedirectStatus()).andReturn(RedirectStatus.NO_FORWARD).anyTimes(); + queryStmt.rewriteExprs(EasyMock.isA(ExprRewriter.class)); + EasyMock.expectLastCall().anyTimes(); EasyMock.replay(queryStmt); Symbol symbol = new Symbol(0, queryStmt); @@ -276,7 +296,7 @@ public class StmtExecutorTest { ConnectContext killCtx = EasyMock.createMock(ConnectContext.class); EasyMock.expect(killCtx.getCatalog()).andReturn(AccessTestUtil.fetchAdminCatalog()).anyTimes(); - EasyMock.expect(killCtx.getUser()).andReturn("blockUser").anyTimes(); + EasyMock.expect(killCtx.getQualifiedUser()).andReturn("blockUser").anyTimes(); killCtx.kill(true); EasyMock.expectLastCall().anyTimes(); EasyMock.replay(killCtx); @@ -310,7 +330,7 @@ public class StmtExecutorTest { ConnectContext killCtx = EasyMock.createMock(ConnectContext.class); EasyMock.expect(killCtx.getCatalog()).andReturn(AccessTestUtil.fetchAdminCatalog()).anyTimes(); - EasyMock.expect(killCtx.getUser()).andReturn("killUser").anyTimes(); + EasyMock.expect(killCtx.getQualifiedUser()).andReturn("killUser").anyTimes(); killCtx.kill(true); EasyMock.expectLastCall().anyTimes(); EasyMock.replay(killCtx); @@ -321,7 +341,7 @@ public class StmtExecutorTest { StmtExecutor stmtExecutor = new StmtExecutor(ctx, ""); stmtExecutor.execute(); - Assert.assertEquals(QueryState.MysqlStateType.OK, state.getStateType()); + Assert.assertEquals(QueryState.MysqlStateType.ERR, state.getStateType()); } @Test diff --git a/fe/test/com/baidu/palo/task/LoadPendingTaskTest.java b/fe/test/com/baidu/palo/task/LoadPendingTaskTest.java index c0b667fa18..7940146c30 100644 --- a/fe/test/com/baidu/palo/task/LoadPendingTaskTest.java +++ b/fe/test/com/baidu/palo/task/LoadPendingTaskTest.java @@ -120,6 +120,7 @@ public class LoadPendingTaskTest { // mock load load = EasyMock.createMock(Load.class); EasyMock.expect(load.updateLoadJobState(job, JobState.ETL)).andReturn(true).times(1); + EasyMock.expect(load.getLoadErrorHubInfo()).andReturn(null).times(1); EasyMock.replay(load); EasyMock.expect(catalog.getLoadInstance()).andReturn(load).times(1); EasyMock.replay(catalog); diff --git a/fs_brokers/apache_hdfs_broker/src/com/baidu/palo/broker/hdfs/FileSystemManager.java b/fs_brokers/apache_hdfs_broker/src/com/baidu/palo/broker/hdfs/FileSystemManager.java index 089c417353..6bdc50ce2d 100644 --- a/fs_brokers/apache_hdfs_broker/src/com/baidu/palo/broker/hdfs/FileSystemManager.java +++ b/fs_brokers/apache_hdfs_broker/src/com/baidu/palo/broker/hdfs/FileSystemManager.java @@ -25,21 +25,30 @@ import org.apache.hadoop.fs.FSDataOutputStream; import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; +import org.apache.hadoop.fs.CommonConfigurationKeysPublic; +import org.apache.hadoop.security.UserGroupInformation; import org.apache.log4j.Logger; import com.google.common.base.Strings; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileWriter; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.nio.ByteBuffer; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.Base64; +import java.util.Random; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.security.MessageDigest; public class FileSystemManager { @@ -49,6 +58,11 @@ public class FileSystemManager { private static final String HDFS_UGI_CONF = "hadoop.job.ugi"; private static final String USER_NAME_KEY = "username"; private static final String PASSWORD_KEY = "password"; + private static final String AUTHENTICATION_SIMPLE = "simple"; + private static final String AUTHENTICATION_KERBEROS = "kerberos"; + private static final String KERBEROS_PRINCIPAL = "kerberos_principal"; + private static final String KERBEROS_KEYTAB = "kerberos_keytab"; + private static final String KERBEROS_KEYTAB_CONTENT = "kerberos_keytab_content"; // arguments for ha hdfs private static final String DFS_NAMESERVICES_KEY = "dfs.nameservices"; @@ -110,10 +124,50 @@ public class FileSystemManager { String password = properties.containsKey(PASSWORD_KEY) ? properties.get(PASSWORD_KEY) : ""; String dfsNameServices = properties.containsKey(DFS_NAMESERVICES_KEY) ? properties.get(DFS_NAMESERVICES_KEY) : ""; + String authentication = AUTHENTICATION_SIMPLE; + if (properties.containsKey(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION)) { + authentication = properties.get(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION); + if (Strings.isNullOrEmpty(authentication) + || (!authentication.equals(AUTHENTICATION_SIMPLE) + && !authentication.equals(AUTHENTICATION_KERBEROS))) { + logger.warn("invalid authentication:" + authentication); + throw new BrokerException(TBrokerOperationStatusCode.INVALID_ARGUMENT, + "invalid authentication:" + authentication); + } + } String hdfsUgi = username + "," + password; - FileSystemIdentity fileSystemIdentity = new FileSystemIdentity(host, hdfsUgi); + FileSystemIdentity fileSystemIdentity = null; BrokerFileSystem fileSystem = null; + if (authentication.equals(AUTHENTICATION_SIMPLE)) { + fileSystemIdentity = new FileSystemIdentity(host, hdfsUgi); + } else { + // for kerberos, use host + principal + keytab as filesystemindentity + String kerberosContent = ""; + if (properties.containsKey(KERBEROS_KEYTAB)) { + kerberosContent = properties.get(KERBEROS_KEYTAB); + } else if (properties.containsKey(KERBEROS_KEYTAB_CONTENT)) { + kerberosContent = properties.get(KERBEROS_KEYTAB_CONTENT); + } else { + throw new BrokerException(TBrokerOperationStatusCode.INVALID_ARGUMENT, + "keytab is required for kerberos authentication"); + } + if (!properties.containsKey(KERBEROS_PRINCIPAL)) { + throw new BrokerException(TBrokerOperationStatusCode.INVALID_ARGUMENT, + "principal is required for kerberos authentication"); + } else { + kerberosContent = kerberosContent + properties.get(KERBEROS_PRINCIPAL); + } + try { + MessageDigest digest = MessageDigest.getInstance("md5"); + byte[] result = digest.digest(kerberosContent.getBytes()); + String kerberosUgi = new String(result); + fileSystemIdentity = new FileSystemIdentity(host, kerberosUgi); + } catch (NoSuchAlgorithmException e) { + throw new BrokerException(TBrokerOperationStatusCode.INVALID_ARGUMENT, + e.getMessage()); + } + } cachedFileSystem.putIfAbsent(fileSystemIdentity, new BrokerFileSystem(fileSystemIdentity)); fileSystem = cachedFileSystem.get(fileSystemIdentity); if (fileSystem == null) { @@ -133,7 +187,52 @@ public class FileSystemManager { Configuration conf = new Configuration(); // TODO get this param from properties // conf.set("dfs.replication", "2"); - conf.set(HDFS_UGI_CONF, hdfsUgi); + String tmpFilePath = null; + if (authentication.equals(AUTHENTICATION_SIMPLE)) { + conf.set(HDFS_UGI_CONF, hdfsUgi); + } else if (authentication.equals(AUTHENTICATION_KERBEROS)){ + conf.set(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION, + AUTHENTICATION_KERBEROS); + + String principal = properties.get(KERBEROS_PRINCIPAL); + String keytab = ""; + if (properties.containsKey(KERBEROS_KEYTAB)) { + keytab = properties.get(KERBEROS_KEYTAB); + } else if (properties.containsKey(KERBEROS_KEYTAB_CONTENT)) { + // pass kerberos keytab content use base64 encoding + // so decode it and write it to tmp path under /tmp + // because ugi api only accept a local path as argument + String keytab_content = properties.get(KERBEROS_KEYTAB_CONTENT); + byte[] base64decodedBytes = Base64.getDecoder().decode(keytab_content); + long currentTime = System.currentTimeMillis(); + Random random = new Random(currentTime); + int randNumber = random.nextInt(10000); + tmpFilePath = "/tmp/." + Long.toString(currentTime) + "_" + Integer.toString(randNumber); + FileOutputStream fileOutputStream = new FileOutputStream(tmpFilePath); + fileOutputStream.write(base64decodedBytes); + fileOutputStream.close(); + keytab = tmpFilePath; + } else { + throw new BrokerException(TBrokerOperationStatusCode.INVALID_ARGUMENT, + "keytab is required for kerberos authentication"); + } + UserGroupInformation.setConfiguration(conf); + UserGroupInformation.loginUserFromKeytab(principal, keytab); + if (properties.containsKey(KERBEROS_KEYTAB_CONTENT)) { + try { + File file = new File(tmpFilePath); + if(!file.delete()){ + logger.warn("delete tmp file:" + tmpFilePath + " failed"); + } + } catch (Exception e) { + throw new BrokerException(TBrokerOperationStatusCode.FILE_NOT_FOUND, + e.getMessage()); + } + } + } else { + throw new BrokerException(TBrokerOperationStatusCode.INVALID_ARGUMENT, + "invalid authentication."); + } if (!Strings.isNullOrEmpty(dfsNameServices)) { // ha hdfs arguments final String dfsHaNameNodesKey = DFS_HA_NAMENODES_PREFIX + dfsNameServices; diff --git a/gensrc/parser/sql_parser.y b/gensrc/parser/sql_parser.y index 9af0e57794..e703fd7ef8 100644 --- a/gensrc/parser/sql_parser.y +++ b/gensrc/parser/sql_parser.y @@ -201,14 +201,14 @@ terminal String KW_ADD, KW_AFTER, KW_AGGREGATE, KW_ALL, KW_ALTER, KW_AND, KW_ANT KW_DATA, KW_DATABASE, KW_DATABASES, KW_DATE, KW_DATETIME, KW_DECIMAL, KW_DECOMMISSION, KW_DEFAULT, KW_DESC, KW_DESCRIBE, KW_DELETE, KW_DISTINCT, KW_DISTINCTPC, KW_DISTINCTPCSA, KW_DISTRIBUTED, KW_BUCKETS, KW_DIV, KW_DOUBLE, KW_DROP, KW_DROPP, KW_DUPLICATE, KW_ELSE, KW_END, KW_ENGINE, KW_ENGINES, KW_ENTER, KW_ERRORS, KW_EVENTS, KW_EXISTS, KW_EXPORT, KW_EXTERNAL, KW_EXTRACT, - KW_FALSE, KW_FOLLOWER, KW_FOLLOWING, KW_FREE, KW_FROM, KW_FIRST, KW_FLOAT, KW_FOR, KW_FULL, KW_FUNCTION, - KW_GLOBAL, KW_GRANT, KW_GROUP, + KW_FALSE, KW_FOLLOWER, KW_FOLLOWING, KW_FREE, KW_FROM, KW_FIRST, KW_FLOAT, KW_FOR, KW_FRONTENDS, KW_FULL, KW_FUNCTION, + KW_GLOBAL, KW_GRANT, KW_GRANTS, KW_GROUP, KW_HASH, KW_HAVING, KW_HELP,KW_HLL, KW_HLL_UNION, KW_IDENTIFIED, KW_IF, KW_IN, KW_INDEX, KW_INDEXES, KW_INFILE, KW_INNER, KW_INSERT, KW_INT, KW_INTERVAL, KW_INTO, KW_IS, KW_ISNULL, KW_ISOLATION, KW_JOIN, KW_KEY, KW_KILL, - KW_LABEL, KW_LARGEINT, KW_LEFT, KW_LESS, KW_LEVEL, KW_LIKE, KW_LIMIT, KW_LINK, KW_LOAD, KW_LOCAL, + KW_LABEL, KW_LARGEINT, KW_LEFT, KW_LESS, KW_LEVEL, KW_LIKE, KW_LIMIT, KW_LINK, KW_LOAD, KW_LOCAL, KW_LOCATION, KW_MAX, KW_MAX_VALUE, KW_MERGE, KW_MIN, KW_MIGRATE, KW_MIGRATIONS, KW_MODIFY, KW_NAME, KW_NAMES, KW_NEGATIVE, KW_NO, KW_NOT, KW_NULL, KW_OBSERVER, KW_OFFSET, KW_ON, KW_ONLY, KW_OPEN, KW_OR, KW_ORDER, KW_OUTER, KW_OVER, @@ -218,13 +218,13 @@ terminal String KW_ADD, KW_AFTER, KW_AGGREGATE, KW_ALL, KW_ALTER, KW_AND, KW_ANT KW_PROC, KW_PROCEDURE, KW_PROCESSLIST, KW_PROPERTIES, KW_PROPERTY, KW_QUERY, KW_QUOTA, KW_RANDOM, KW_RANGE, KW_READ, KW_RECOVER, KW_REGEXP, KW_RELEASE, KW_RENAME, - KW_REPEATABLE, KW_REPLACE, KW_RESOURCE, KW_RESTORE, KW_REVOKE, - KW_RIGHT, KW_ROLLBACK, KW_ROLLUP, KW_ROW, KW_ROWS, + KW_REPEATABLE, KW_REPOSITORY, KW_REPOSITORIES, KW_REPLACE, KW_RESOURCE, KW_RESTORE, KW_REVOKE, + KW_RIGHT, KW_ROLE, KW_ROLES, KW_ROLLBACK, KW_ROLLUP, KW_ROW, KW_ROWS, KW_SCHEMAS, KW_SELECT, KW_SEMI, KW_SERIALIZABLE, KW_SESSION, KW_SET, KW_SHOW, KW_SMALLINT, KW_SNAPSHOT, KW_SONAME, KW_SPLIT, KW_START, KW_STATUS, KW_STORAGE, KW_STRING, KW_SUM, KW_SUPERUSER, KW_SYNC, KW_SYSTEM, KW_TABLE, KW_TABLES, KW_TABLET, KW_TERMINATED, KW_THAN, KW_THEN, KW_TIMESTAMP, KW_TINYINT, - KW_TO, KW_TRANSACTION, KW_TRIGGERS, KW_TRIM, KW_TRUE, KW_TYPES, + KW_TO, KW_TRANSACTION, KW_TRIGGERS, KW_TRIM, KW_TRUE, KW_TYPE, KW_TYPES, KW_UNCOMMITTED, KW_UNBOUNDED, KW_UNION, KW_UNIQUE, KW_UNSIGNED, KW_USE, KW_USER, KW_USING, KW_VALUES, KW_VARCHAR, KW_VARIABLES, KW_VIEW, KW_WARNINGS, KW_WHEN, KW_WHITELIST, KW_WHERE, KW_WITH, KW_WORK, KW_WRITE; @@ -260,6 +260,7 @@ nonterminal describe_command, opt_full, opt_inner, opt_outer, from_or_in, keys_o // String nonterminal String user, opt_user; +nonterminal UserIdentity user_identity; // Description of user nonterminal UserDesc grant_user; @@ -309,7 +310,7 @@ nonterminal AnalyticWindow opt_window_clause; nonterminal AnalyticWindow.Type window_type; nonterminal AnalyticWindow.Boundary window_boundary; nonterminal SlotRef column_ref; -nonterminal ArrayList table_ref_list; +nonterminal ArrayList table_ref_list, base_table_ref_list; nonterminal FromClause from_clause; nonterminal TableRef table_ref; nonterminal TableRef base_table_ref; @@ -370,9 +371,12 @@ nonterminal List opt_col_list, opt_dup_keys; nonterminal List opt_partitions; nonterminal List opt_col_mapping_list; nonterminal ColumnSeparator opt_field_term; +nonterminal String opt_user_role; +nonterminal TablePattern tbl_pattern; +nonterminal String ident_or_star; // Boolean -nonterminal Boolean opt_negative, opt_super_user, opt_is_allow_null, opt_is_key; +nonterminal Boolean opt_negative, opt_super_user, opt_is_allow_null, opt_is_key, opt_read_only; nonterminal String opt_from_rollup, opt_to_rollup; nonterminal ColumnPosition opt_col_pos; @@ -539,9 +543,9 @@ alter_stmt ::= {: RESULT = new AlterDatabaseRename(dbName, newDbName); :} - | KW_ALTER KW_USER ident:userName alter_user_clause:clause + | KW_ALTER KW_USER user_identity:userIdent alter_user_clause:clause {: - RESULT = new AlterUserStmt(userName, clause); + RESULT = new AlterUserStmt(userIdent, clause); :} ; @@ -801,9 +805,9 @@ create_stmt ::= RESULT = new CreateTableStmt(ifNotExists, isExternal, name, columns, engineName, keys, partition, distribution, tblProperties, extProperties); :} /* User */ - | KW_CREATE KW_USER grant_user:user opt_super_user:isSuperuser + | KW_CREATE KW_USER opt_if_not_exists:ifNotExists grant_user:user opt_user_role:userRole {: - RESULT = new CreateUserStmt(user, isSuperuser); + RESULT = new CreateUserStmt(ifNotExists, user, userRole); :} | KW_CREATE KW_VIEW opt_if_not_exists:ifNotExists table_name:viewName opt_col_list:columns KW_AS query_stmt:view_def @@ -815,37 +819,61 @@ create_stmt ::= {: RESULT = new CreateClusterStmt(name, properties, password); :} - ; - -grant_user ::= - user:user + | KW_CREATE opt_read_only:isReadOnly KW_REPOSITORY ident:repoName KW_WITH KW_BROKER ident:brokerName + KW_ON KW_LOCATION STRING_LITERAL:location + opt_properties:properties {: - /* No password */ - RESULT = new UserDesc(user); + RESULT = new CreateRepositoryStmt(isReadOnly, repoName, brokerName, location, properties); :} - | user:user KW_IDENTIFIED KW_BY STRING_LITERAL:password + | KW_CREATE KW_ROLE ident:role {: - /* plain text password */ - RESULT = new UserDesc(user, password, true); - :} - | user:user KW_IDENTIFIED KW_BY KW_PASSWORD STRING_LITERAL:password - {: - /* hashed password */ - RESULT = new UserDesc(user, password, false); + RESULT = new CreateRoleStmt(role); :} ; -opt_super_user ::= - /* Empty */ +opt_read_only ::= {: RESULT = false; :} - | KW_SUPERUSER + | KW_READ KW_ONLY {: RESULT = true; :} ; +grant_user ::= + user_identity:user_id + {: + /* No password */ + RESULT = new UserDesc(user_id); + :} + | user_identity:user_id KW_IDENTIFIED KW_BY STRING_LITERAL:password + {: + /* plain text password */ + RESULT = new UserDesc(user_id, password, true); + :} + | user_identity:user_id KW_IDENTIFIED KW_BY KW_PASSWORD STRING_LITERAL:password + {: + /* hashed password */ + RESULT = new UserDesc(user_id, password, false); + :} + ; + +opt_user_role ::= + /* Empty */ + {: + RESULT = null; + :} + | KW_SUPERUSER /* for forward compatibility*/ + {: + RESULT = "superuser"; + :} + | KW_DEFAULT KW_ROLE STRING_LITERAL:role + {: + RESULT = role; + :} + ; + user ::= ident_or_text:user {: @@ -853,6 +881,21 @@ user ::= :} ; +user_identity ::= + ident_or_text:user + {: + RESULT = new UserIdentity(user, "%", false); + :} + | ident_or_text:user AT ident_or_text:host + {: + RESULT = new UserIdentity(user, host, false); + :} + | ident_or_text:user AT LBRACKET ident_or_text:host RBRACKET + {: + RESULT = new UserIdentity(user, host, true); + :} + ; + column_type_list ::= column_type:type {: @@ -1024,18 +1067,47 @@ opt_cluster ::= // Grant statement grant_stmt ::= - KW_GRANT privilege_list:privs KW_ON ident:dbName KW_TO user:user + KW_GRANT privilege_list:privs KW_ON tbl_pattern:tblPattern KW_TO user_identity:userId {: - RESULT = new GrantStmt(user, dbName, privs); + RESULT = new GrantStmt(userId, null, tblPattern, privs); + :} + | KW_GRANT privilege_list:privs KW_ON tbl_pattern:tblPattern KW_TO KW_ROLE STRING_LITERAL:role + {: + RESULT = new GrantStmt(null, role, tblPattern, privs); + :} + ; + +tbl_pattern ::= + ident_or_star:db + {: + RESULT = new TablePattern(db, "*"); + :} + | ident_or_star:db DOT ident_or_star:tbl + {: + RESULT = new TablePattern(db, tbl); + :} + ; + +ident_or_star ::= + STAR + {: + RESULT = "*"; + :} + | ident:ident + {: + RESULT = ident; :} ; // Revoke statement revoke_stmt ::= - /* for now, simply revoke ALL privilege */ - KW_REVOKE KW_ALL KW_ON ident:dbName KW_FROM user:user + KW_REVOKE privilege_list:privs KW_ON tbl_pattern:tblPattern KW_FROM user_identity:userId {: - RESULT = new RevokeStmt(user, dbName); + RESULT = new RevokeStmt(userId, null, tblPattern, privs); + :} + | KW_REVOKE privilege_list:privs KW_ON tbl_pattern:tblPattern KW_FROM KW_ROLE STRING_LITERAL:role + {: + RESULT = new RevokeStmt(null, role, tblPattern, privs); :} ; @@ -1062,15 +1134,23 @@ drop_stmt ::= RESULT = new DropTableStmt(ifExists, name); :} /* User */ - | KW_DROP KW_USER STRING_LITERAL:user + | KW_DROP KW_USER user_identity:userId {: - RESULT = new DropUserStmt(user); + RESULT = new DropUserStmt(userId); :} /* View */ | KW_DROP KW_VIEW opt_if_exists:ifExists table_name:name {: RESULT = new DropTableStmt(ifExists, name, true); :} + | KW_DROP KW_REPOSITORY ident:repoName + {: + RESULT = new DropRepositoryStmt(repoName); + :} + | KW_DROP KW_ROLE ident:role + {: + RESULT = new DropRoleStmt(role); + :} ; // Recover statement @@ -1659,9 +1739,9 @@ show_param ::= {: RESULT = new ShowUserPropertyStmt(user, parser.wild); :} - | KW_BACKUP opt_db:db opt_wild_where + | KW_BACKUP opt_db:db {: - RESULT = new ShowBackupStmt(db, parser.where); + RESULT = new ShowBackupStmt(db); :} | KW_RESTORE opt_db:db opt_wild_where {: @@ -1675,10 +1755,38 @@ show_param ::= {: RESULT = new ShowBackendsStmt(); :} + | KW_FRONTENDS + {: + RESULT = new ShowFrontendsStmt(); + :} | KW_USER {: RESULT = new ShowUserStmt(); :} + | KW_REPOSITORIES + {: + RESULT = new ShowRepositoriesStmt(); + :} + | KW_SNAPSHOT KW_ON ident:repo opt_wild_where + {: + RESULT = new ShowSnapshotStmt(repo, parser.where); + :} + | KW_ALL KW_GRANTS + {: + RESULT = new ShowGrantsStmt(null, true); + :} + | KW_GRANTS + {: + RESULT = new ShowGrantsStmt(null, false); + :} + | KW_GRANTS KW_FOR user_identity:userIdent + {: + RESULT = new ShowGrantsStmt(userIdent, false); + :} + | KW_ROLES + {: + RESULT = new ShowRolesStmt(); + :} ; keys_or_index ::= @@ -2105,23 +2213,23 @@ insert_source ::= // backup stmt backup_stmt ::= - KW_BACKUP KW_LABEL job_label:label - opt_partition_name_list:backupObjNames - KW_INTO STRING_LITERAL:rootPath + KW_BACKUP KW_SNAPSHOT job_label:label + KW_TO ident:repoName + KW_ON LPAREN base_table_ref_list:tbls RPAREN opt_properties:properties {: - RESULT = new BackupStmt(label, backupObjNames, rootPath, properties); + RESULT = new BackupStmt(label, repoName, tbls, properties); :} ; // Restore statement restore_stmt ::= - KW_RESTORE KW_LABEL job_label:label - opt_partition_name_list:restoreObjNames - KW_FROM STRING_LITERAL:rootPath + KW_RESTORE KW_SNAPSHOT job_label:label + KW_FROM ident:repoName + KW_ON LPAREN base_table_ref_list:tbls RPAREN opt_properties:properties {: - RESULT = new RestoreStmt(label, restoreObjNames, rootPath, properties); + RESULT = new RestoreStmt(label, repoName, tbls, properties); :} ; @@ -2339,9 +2447,9 @@ option_value_no_option_type ::= {: RESULT = new SetPassVar(null, passwd); :} - | KW_PASSWORD KW_FOR STRING_LITERAL:user equal text_or_password:passwd + | KW_PASSWORD KW_FOR user_identity:userId equal text_or_password:passwd {: - RESULT = new SetPassVar(user, passwd); + RESULT = new SetPassVar(userId, passwd); :} ; @@ -2655,6 +2763,20 @@ inline_view_ref ::= :} ; +base_table_ref_list ::= + base_table_ref:tbl + {: + ArrayList list = new ArrayList(); + list.add(tbl); + RESULT = list; + :} + | base_table_ref_list:list COMMA base_table_ref:tbl + {: + list.add(tbl); + RESULT = list; + :} + ; + base_table_ref ::= table_name:name opt_using_partition:parts opt_table_alias:alias {: @@ -3513,6 +3635,8 @@ keyword ::= {: RESULT = id; :} | KW_LOCAL:id {: RESULT = id; :} + | KW_LOCATION:id + {: RESULT = id; :} | KW_MERGE:id {: RESULT = id; :} | KW_MODIFY:id @@ -3557,6 +3681,10 @@ keyword ::= {: RESULT = id; :} | KW_REPEATABLE:id {: RESULT = id; :} + | KW_REPOSITORY:id + {: RESULT = id; :} + | KW_REPOSITORIES:id + {: RESULT = id; :} | KW_RESOURCE:id {: RESULT = id; :} | KW_RESTORE:id @@ -3593,6 +3721,8 @@ keyword ::= {: RESULT = id; :} | KW_TRIGGERS:id {: RESULT = id; :} + | KW_TYPE:id + {: RESULT = id; :} | KW_TYPES:id {: RESULT = id; :} | KW_UNCOMMITTED:id diff --git a/gensrc/parser/sql_scanner.flex b/gensrc/parser/sql_scanner.flex index 8e37293ebc..9a616ca99a 100644 --- a/gensrc/parser/sql_scanner.flex +++ b/gensrc/parser/sql_scanner.flex @@ -154,10 +154,12 @@ import com.baidu.palo.common.util.SqlUtils; keywordMap.put("following", new Integer(SqlParserSymbols.KW_FOLLOWING)); keywordMap.put("for", new Integer(SqlParserSymbols.KW_FOR)); keywordMap.put("from", new Integer(SqlParserSymbols.KW_FROM)); + keywordMap.put("frontends", new Integer(SqlParserSymbols.KW_FRONTENDS)); keywordMap.put("full", new Integer(SqlParserSymbols.KW_FULL)); keywordMap.put("function", new Integer(SqlParserSymbols.KW_FUNCTION)); keywordMap.put("global", new Integer(SqlParserSymbols.KW_GLOBAL)); keywordMap.put("grant", new Integer(SqlParserSymbols.KW_GRANT)); + keywordMap.put("grants", new Integer(SqlParserSymbols.KW_GRANTS)); keywordMap.put("group", new Integer(SqlParserSymbols.KW_GROUP)); keywordMap.put("hash", new Integer(SqlParserSymbols.KW_HASH)); keywordMap.put("having", new Integer(SqlParserSymbols.KW_HAVING)); @@ -190,6 +192,7 @@ import com.baidu.palo.common.util.SqlUtils; keywordMap.put("limit", new Integer(SqlParserSymbols.KW_LIMIT)); keywordMap.put("load", new Integer(SqlParserSymbols.KW_LOAD)); keywordMap.put("local", new Integer(SqlParserSymbols.KW_LOCAL)); + keywordMap.put("location", new Integer(SqlParserSymbols.KW_LOCATION)); keywordMap.put("max", new Integer(SqlParserSymbols.KW_MAX)); keywordMap.put("maxvalue", new Integer(SqlParserSymbols.KW_MAX_VALUE)); keywordMap.put("merge", new Integer(SqlParserSymbols.KW_MERGE)); @@ -235,11 +238,15 @@ import com.baidu.palo.common.util.SqlUtils; keywordMap.put("rename", new Integer(SqlParserSymbols.KW_RENAME)); keywordMap.put("repeatable", new Integer(SqlParserSymbols.KW_REPEATABLE)); keywordMap.put("replace", new Integer(SqlParserSymbols.KW_REPLACE)); + keywordMap.put("repository", new Integer(SqlParserSymbols.KW_REPOSITORY)); + keywordMap.put("repositories", new Integer(SqlParserSymbols.KW_REPOSITORIES)); keywordMap.put("resource", new Integer(SqlParserSymbols.KW_RESOURCE)); keywordMap.put("restore", new Integer(SqlParserSymbols.KW_RESTORE)); keywordMap.put("revoke", new Integer(SqlParserSymbols.KW_REVOKE)); keywordMap.put("right", new Integer(SqlParserSymbols.KW_RIGHT)); keywordMap.put("rlike", new Integer(SqlParserSymbols.KW_REGEXP)); + keywordMap.put("role", new Integer(SqlParserSymbols.KW_ROLE)); + keywordMap.put("roles", new Integer(SqlParserSymbols.KW_ROLES)); keywordMap.put("rollback", new Integer(SqlParserSymbols.KW_ROLLBACK)); keywordMap.put("rollup", new Integer(SqlParserSymbols.KW_ROLLUP)); keywordMap.put("row", new Integer(SqlParserSymbols.KW_ROW)); @@ -275,6 +282,7 @@ import com.baidu.palo.common.util.SqlUtils; keywordMap.put("triggers", new Integer(SqlParserSymbols.KW_TRIGGERS)); keywordMap.put("trim", new Integer(SqlParserSymbols.KW_TRIM)); keywordMap.put("true", new Integer(SqlParserSymbols.KW_TRUE)); + keywordMap.put("type", new Integer(SqlParserSymbols.KW_TYPE)); keywordMap.put("types", new Integer(SqlParserSymbols.KW_TYPES)); keywordMap.put("unbounded", new Integer(SqlParserSymbols.KW_UNBOUNDED)); keywordMap.put("uncommitted", new Integer(SqlParserSymbols.KW_UNCOMMITTED)); diff --git a/gensrc/proto/olap_file.proto b/gensrc/proto/olap_file.proto index 5ff511ff94..cc64646242 100644 --- a/gensrc/proto/olap_file.proto +++ b/gensrc/proto/olap_file.proto @@ -87,6 +87,9 @@ message OLAPHeaderMessage { // bloom filter false positive probability optional double bf_fpp = 14; optional KeysType keys_type = 15; + // if true, this tablet will not do compaction, + // and does not create init version + optional bool in_restore_mode = 16 [default = false]; } message OLAPIndexHeaderMessage { diff --git a/gensrc/script/gen_build_version.sh b/gensrc/script/gen_build_version.sh index db4dcada1c..5506c1e219 100755 --- a/gensrc/script/gen_build_version.sh +++ b/gensrc/script/gen_build_version.sh @@ -26,7 +26,7 @@ # contains the build version based on the git hash or svn revision. ############################################################## -build_version="PALO3.3.19-RELEASE" +build_version="3.3-branch" unset LANG unset LC_CTYPE diff --git a/gensrc/thrift/AgentService.thrift b/gensrc/thrift/AgentService.thrift index 37e7ef42f3..7547ff7516 100644 --- a/gensrc/thrift/AgentService.thrift +++ b/gensrc/thrift/AgentService.thrift @@ -50,6 +50,7 @@ struct TCreateTabletReq { 3: optional Types.TVersion version 4: optional Types.TVersionHash version_hash 5: optional Types.TStorageMedium storage_medium + 6: optional bool in_restore_mode } struct TDropTabletReq { @@ -111,17 +112,17 @@ struct TCheckConsistencyReq { } struct TUploadReq { - 1: required string local_file_path - 2: required string remote_file_path - 3: required map remote_source_properties - 4: optional Types.TTabletId tablet_id + 1: required i64 job_id; + 2: required map src_dest_map + 3: required Types.TNetworkAddress broker_addr + 4: optional map broker_prop } -struct TRestoreReq { - 1: required Types.TTabletId tablet_id - 2: required Types.TSchemaHash schema_hash - 3: required string remote_file_path - 4: required map remote_source_properties +struct TDownloadReq { + 1: required i64 job_id + 2: required map src_dest_map + 3: required Types.TNetworkAddress broker_addr + 4: optional map broker_prop } struct TSnapshotRequest { @@ -130,6 +131,7 @@ struct TSnapshotRequest { 3: optional Types.TVersion version 4: optional Types.TVersionHash version_hash 5: optional i64 timeout + 6: optional bool list_files } struct TReleaseSnapshotRequest { @@ -141,6 +143,14 @@ struct TClearRemoteFileReq { 2: required map remote_source_properties } +struct TMoveDirReq { + 1: required Types.TTabletId tablet_id + 2: required Types.TSchemaHash schema_hash + 3: required string src + 4: required i64 job_id + 5: required bool overwrite +} + enum TAgentServiceVersion { V1 } @@ -160,10 +170,11 @@ struct TAgentTaskRequest { 12: optional TStorageMediumMigrateReq storage_medium_migrate_req 13: optional TCheckConsistencyReq check_consistency_req 14: optional TUploadReq upload_req - 15: optional TRestoreReq restore_req + 15: optional TDownloadReq download_req 16: optional TSnapshotRequest snapshot_req 17: optional TReleaseSnapshotRequest release_snapshot_req 18: optional TClearRemoteFileReq clear_remote_file_req + 19: optional TMoveDirReq move_dir_req } struct TAgentResult { diff --git a/gensrc/thrift/FrontendService.thrift b/gensrc/thrift/FrontendService.thrift index 0e37784b66..64c31a4e8f 100644 --- a/gensrc/thrift/FrontendService.thrift +++ b/gensrc/thrift/FrontendService.thrift @@ -67,6 +67,7 @@ struct TDescribeTableParams { 1: optional string db 2: required string table_name 3: optional string user + 4: optional string user_ip } // Results of a call to describeTable() @@ -282,6 +283,7 @@ struct TGetDbsParams { // If not set, match every database 1: optional string pattern 2: optional string user + 3: optional string user_ip } // getDbNames returns a list of database names @@ -298,6 +300,7 @@ struct TGetTablesParams { // If not set, match every table 2: optional string pattern 3: optional string user + 4: optional string user_ip } struct TTableStatus { @@ -386,6 +389,7 @@ struct TMiniLoadRequest { 9: optional string subLabel 10: optional string cluster 11: optional i64 timestamp + 12: optional string user_ip } struct TUpdateMiniEtlTaskStatusRequest { @@ -402,6 +406,7 @@ struct TMasterOpRequest { 5: optional string cluster 6: optional i64 execMemLimit 7: optional i32 queryTimeout + 8: optional string user_ip } struct TColumnDefinition { @@ -434,6 +439,8 @@ struct TLoadCheckRequest { 5: optional string label 6: optional string cluster 7: optional i64 timestamp + 8: optional string user_ip + 9: optional string tbl } struct TUpdateExportTaskStatusRequest { diff --git a/gensrc/thrift/MasterService.thrift b/gensrc/thrift/MasterService.thrift index 5ea9d7bee4..2aa625b150 100644 --- a/gensrc/thrift/MasterService.thrift +++ b/gensrc/thrift/MasterService.thrift @@ -34,6 +34,7 @@ struct TTabletInfo { 5: required Types.TCount row_count 6: required Types.TSize data_size 7: optional Types.TStorageMedium storage_medium + 8: optional i64 version_count } struct TFinishTaskRequest { @@ -47,6 +48,9 @@ struct TFinishTaskRequest { 8: optional i64 request_version 9: optional i64 request_version_hash 10: optional string snapshot_path + 11: optional list snapshot_files + 12: optional map> tablet_files + 13: optional list downloaded_tablet_ids } struct TTablet { diff --git a/gensrc/thrift/PaloBrokerService.thrift b/gensrc/thrift/PaloBrokerService.thrift index 096a4c80d0..7487cc3c31 100644 --- a/gensrc/thrift/PaloBrokerService.thrift +++ b/gensrc/thrift/PaloBrokerService.thrift @@ -90,6 +90,7 @@ struct TBrokerListPathRequest { 2: required string path; 3: required bool isRecursive; 4: required map properties; + 5: optional bool fileNameOnly; } struct TBrokerDeletePathRequest { diff --git a/gensrc/thrift/PlanNodes.thrift b/gensrc/thrift/PlanNodes.thrift index e67679977e..baf75e0d25 100644 --- a/gensrc/thrift/PlanNodes.thrift +++ b/gensrc/thrift/PlanNodes.thrift @@ -204,6 +204,7 @@ struct TSchemaScanNode { 7: optional string ip 8: optional i32 port 9: optional i64 thread_id + 10: optional string user_ip } struct TMetaScanNode { diff --git a/gensrc/thrift/Types.thrift b/gensrc/thrift/Types.thrift index 0bb787a97c..69411a8993 100644 --- a/gensrc/thrift/Types.thrift +++ b/gensrc/thrift/Types.thrift @@ -145,10 +145,11 @@ enum TTaskType { CANCEL_DELETE, MAKE_SNAPSHOT, RELEASE_SNAPSHOT, - CHECK_CONSISTENCY + CHECK_CONSISTENCY, UPLOAD, - RESTORE, - CLEAR_REMOTE_FILE + DOWNLOAD, + CLEAR_REMOTE_FILE, + MOVE } enum TStmtType { diff --git a/thirdparty/build-thirdparty.sh b/thirdparty/build-thirdparty.sh index 03846c0f9d..b3944a70de 100755 --- a/thirdparty/build-thirdparty.sh +++ b/thirdparty/build-thirdparty.sh @@ -505,7 +505,6 @@ build_bzip build_lzo2 build_boost # must before thrift build_ncurses -build_llvm build_protobuf build_gflags build_glog diff --git a/thirdparty/java-libraries/cobertura/asm-5.0.1.jar b/thirdparty/java-libraries/cobertura/asm-5.0.1.jar new file mode 100644 index 0000000000000000000000000000000000000000..eeb3bc6f9885419ec775b915304943388f7e3546 GIT binary patch literal 53217 zcmWIWW@Zs#VBp|jI2Kv%GNsq=g$^SFg8~x+g9rl)gRg6dqpqi)o4&83pQoE^aEP9- z+rLu`47HO2{SO(4wBDco#@@t6!Z+upf^yK~<^w(^KD^HNysKwkU3YYy^N;U&4Z;V1 z+^F7vr}*7Y-x<@mHDu;B>Mpq`(yH=dZ=+bn8LsIPisC{pq3^yeUUB`s>)aLh*ER%k zvsR@1IGy5iadolxt-Q@j>S_Pj?$jQdBQe`FVbk@@6f57?6Q4R<%$;R2m9u8brKXPS z2fV`{ZjAHHF5$Zs_~n9UvdkoT;g@RjrB#3C1uU(4u(0S#f$@by5nbW{msEdHGNVSW0a zQQ_WOzWXwB)wk>Ua|E66`7M{UZc9PW?|ZfTLIT(~Og$3(@=AG@``Z84Y#i&?TbsHC zhREBi1P0W%=%a=(0|NsC6Nq47U|?ZjV8|~@*JozHt%eV(CO;`FHMyibH3_e3Aq>@t z#knXtjzpHb2(FkUb&HXK;Vv@+gC2x?oHTW z>3Q$<_rC{TzgIu_^g#cm=I`qNb{-KF`!|nup{c8?Q>liwlkIm-hYQ`4RX*AYqgm&sZLGY!yvrD6S9x4LU@O7)YKHlRmb3?UX}mTEdK%lCY@F7%rLO5%^D3fc zX|iXEWWVb*mus$OE@rM#Q+cnc?(5`Q{`k+HRUVvWz4}YeSs#*lKdZ)F)Us4Z>fk?) zN|qJV8cwed{Wi_`(6rP`qEgdV#w5Pu7Q5f~;mhpgyAL`q?{%z}xpOAl;{#W_SHa<* zE#Jx|l5XGSnQM4TK77&YJ3{{r4Y*78Ig}f{>4}`@eRAE@%-QuDBO|xswlO{TNv2iZpYS9qV-}5n|Q~rRkP4Ky$#lCB<_%s=Pw=REY`Q7@o zZSmvyfB(KQHGKUM(#p4DM~x&`NPThpfsO+!no|Eg`+HnSU1;s&&Mz6+{&usy`nPeX z_8+ioWag6kGxzLoGn@UM%WUwOnm-xyq;Hx94u(^lsjNTXD*S{cG~Jb9a}q z7q9<+%BcA7_VB3n4x6p#o_iwvGeqs~^ixaU@Z_)Au-z$O$|<|_)Bj&?4?5HR=>224 zh#en3dYMi9=%OcndfBIwIlmi>+c$@mT(J6|a9pV>V}1RFjcP{oyn4;_m%sYD^XtWR z@6K`5hJ{)Oy$rGQc`I|WM@3ZQ3j1rZ$Lt?Lk;umj~Y}BdY;F6l#=ioT`F{4IA+|RxhD<0hTa{OB#pFHK!laG61 z)q~HKrEl)kv=XW6UA9s$Z`KK+$|9Yoj4#zKa#t;xP+U1_=H8^wJC2^7XCj-vb&lNp zg2g8fd}YfsC|Jz6zcG!Ok=c=1kvW&`7VjH_g6_r*jj?QH(k~1I3@>CR^f&4WYj2pc z@SY2I2^;@mp|;hMS(2|L!VK4>?zp$)f4V8pmTO&E!qN+7?cjXvlhcx!By1vVp4{7c zmM8i~o_5&m8*N&`w=1^^pZ0lkG*m6mwYgK{{4U9F2Mr3X?_BLrK0G1S;#`6KVUfek zJp4vBA-ybWk|**cGTCOSR_b=HVP`&3Y0$BUy}jU4^Wlm+&B_)J587J1QfvxrGic#ccFOO;5Ukx+4?SQSba3T(9ED&ooj{iN0nY^gCo1oYf@H7A1}6z# z@5O@LM}z*XtJ}0=bpv12{_c6VTb?GIX1%s5+GPE;qKD0&-bC%Wwak)#x#`l1pFtNx z-F5!5mI!;>+fI#~_54!mv>?%E8~=udSp47pcE-D9$2FJ9o!s`bYsQq|BbSmlf4T7Z zh|okG^Z1jS=0^O=nPyitCt}g0)W4!SKPE>cnR)lfsZYy0c`57R;!2%(q0^4DpDNh; z>BLS-ZEod%wy{UZd;Qr<-u=Sf%TsoqJoNi>L|Av)rVkr$#8;=E=84>17w9E?=_QTRwVa&>LlsH2>HmygF$g z-qh9A&pLKf>id_UaUYi*{rR_MeXRJ0{aT)n)VbDqFW#kGpim-suWiCxlYPx<=VdhF z{w;pkU0?q5$vVxVv*oW^s=DR)wq$zEes@_)RHJJ$$&b)%;Q7#p(OoZ&>GV_)zxdzU<l$tw{V+Z=AuD<<&$$OS zz2fFClcQ{8wDy`6ZrZf_A0wib1*+t3FVHypn2CXblZ}Bv5ql+9Qc{$eR9cb>Y5nvD z1qKTRiu}{#u5L7Q?#>D}IxQqN<&+WY7M86uE@r9)ADfXDQ_^n0njV>4@qYi0N6u#% z%x?a8;k^Fu)OnkW8#jprmi;)l_j}EE>-(0w@7LGfWjAP1X@0_QQn0ZvU!v+yC*qXGO(7z5m=A zH1A;XpV>d&XY7mF(!pr_L>JxR#_tK(-spcp97pvqI-Ffih*@wbAPhLFTv2+2m8KWO-_yyGm zo0#-dSi>I*I^32kV6EM;;(?hA*E^0K-F~d!3l6q29Aau??b)^9;l74;Jr=_^#~9e| zet6tCdy_NMu{%>_MLt9v=x9*SYx&UO#nQp=@j~R6fW1*be@fp4*4-bts%Czj(<^si z?b}B)#nvc&+j%ZEz;p4zmkUm4+Zn*GNZ@1j5xu*Kx0;gzHEuCQ|Zsl;?{g{)e%d>6#^=0q>EZsf(OXe!2 zp1w@MNU4XbY8nMDsIW15&@o{d&<@?ZgXK3maD9c+~8rLlrR79!zX3WrJ6e# zWWs5x9&)fce2qr2rs!kdYTMrK87xt{wseOR>G>)y6ki!`-vhh!;aE#9(P=acp? zZ?E@rC3BKz3YF^I|NgG_`P}MvbH6{kYQKL!gF&No;)4$X3Ug#mSC&OOcs)Co$JujX zL&)msm$!bc_PhA^Or*c~yZoi%S70p#Qr%hiSGN)+arT~5W z8%3+$vCkKun_Ip)Ow?dkbdk=J>7_PXitm?h(?~zMa@t*9#@G9`1Ov9q?|8%g;$TkW zQDMfNJ6F9{o|ZMoZSl>!y-~ItMUqS^Hc5 zj5AS}qE~@t;OMkDl^$QXZ)^(V-`0QJ*0Jqds(~P_&L*q zti2qqf8}UxeeyloXmRAs4#`Bv{Y%bxL|-uo!Kl6UX3--T1V zy|(1LH@))N>!S1Y>+$HCw%6af+OvXNf83S|xT~VI??~{bTR_v{~?ZAYc8MGYw=f=fnjwB1A{WwGQk-%QV^7ym;&n+F16vT zn0<8P=KXb^`=8w3mQtDff7(-J&IcY|PRtyuSelC)h1LBY%cy7`?OLniCv*EpL(8-Y z+nA~tp5{p3YI_iLWE z@6Me&bMD-^Gw=TWY8_wmcC$J|!oF{w$5p1WsL4*|kiC(_V!~v_c$iVD^o3o)9TQ8- zmo731mM?d`5m)$^{2*iA?PCizRp$L(cciqFwYKx0EVq?=Th;G!oumS}MeJ>5IubXw zY2UOhsx7+RbmYT4*0q2CGAnmZyw~`zckki7%FNE)Ei-y~Dn8#?d-TbRv)(JB^W(f+ zLqA_y>e_yJ_H@rpqMI^i?+y{uPPwSoJ$2KIDXjuGu7!mxm~1s6fp^;S@AG+=ohb2m zpsMnrXZx0W*hRX(}*g@we+t7n4`xmboy zoR`B8>t*YD?SIaeMTU!y#QSV~^u$nQex2*`w+r`dEqZ4Z+0mmA`c5|D!ZmvftEMP* z;p!IeD`M|&8I`{lI<@WSXQ#kFVr&nUer!|Hm}vCeOq_dS(y0kImn}W(tSfnUpRMQW z|IZdZNq=s6v|a1n?e1kWl=M%Y)o#<@HqFZ~RIn;>+2^hjk@%^h#=-mitmh=&*|XAY zzUa~!J{wytZzn#=pAgP-=R4cX<-8j+t&bGEKA*Saa%#-X7w=zs>&;5D*Gp^K?AO_Q z_9%yYz^QE)CoY*#UurmMX>I4jhovW7_}j!}b>h<9PW2v0IJf0fu4(I6J!|EgJTqfu z9!;AQ=(N%ENr-6HnOR{cmMy#|xP?tHwn}YL67$wxmG3&YwE9+kb^ZA^mWxv=O-W$# zT5Y$9x4-OO_P&l;_OeV^`i-OMc5C9|dbWNDF?swa(B<`hHK#eByR6Q6(5aHUjQfXRN-x^WDttt0Cij=pxuFzsTxhv$FUXET_!SRYY2lrVd)qg%S zFHR-?U$)`9S@(VK^-n#z_wQb1U7iSS=6XG4UGF^qo^QPdA9N)pBqZe|y?DHwmHl^i z%yCo6y<=x^>wdFYtks3dDFI!}^VKD+4zDLZUUcI;l6{QRear+#G(?vZ@aanN7jRkbE0eC&ON;{=Y2+O zf7hbDcXG3@>F-baX~mWnU0+}1p)tiJY46_Tv!WWC_HMV|nO1Us+mj2wj%>3FQJs+P zG$%0L=;Hnr0=L`ZHaW}qXm!oith6hN|9|4#6Oa7u3UV*}zU2Dwe7)q5wPlr)ikw`C z`wO1eLG^0Ovn`z8&2MyCYGsq*H~o!)_t_GAhuiDj+9#~uVrDqy#$=bql&_}Jw{AS0J6xU3kmb#)B5wa{izO_sm9KG~ye@t_WBd9W8Dnm{&&huCSq~${%xh2j67`)6Q>xn&i?x7*UZm6QsuYi3gjzZelA_o z%W|xG(d^*!qvZdF>h7>UEcF%%AHH6J$L1JA3kMv@`a4758ugy zI$7_e-4?!{bU5i?boy=F8Ygg`68QR5seARS z7jn@JAH<^@GPZo>|DImmo+2dHyY1e;yqlU=&&b}$Ik?Gu!rJHlM?xZ8Lzd3E&ilqs z@AmciH!iPEIlVEg@15kCaF?|mBA4z3FK~a?(XqSX$P@Wn1}*;X&$5q&en0T;;=yyT ztKD~%d%Z03+p+w?AFq^H+3eGwqb6;ON-9XLioUM0Ui|c=^~vn(x^lHMU%%;OTf9YJ z;`-S#;c|8#3Y4NAt(dreg-p%g3;ULG9(lSsT;z++OX(N)a@YGmOgy>GI&o=0X4aPJ zIh&+;UYIR7-td;~jX}b4#=UHBj24`4sAFd1kuZE<#XRB7Tedxi8>TRBmnc}x_}=ir zUba2w8@@|_U}S!0@<5OIo!J99=69usKj;0JBW?G-t-kp1{@o9`^DJJr1?TmIa;-~Hfh{^ta*{?3Q> zcRnPW|2e{|zx&~GwJ#2=jb2Q1984Rfn7*={Q!Nl|Jk61#xMAvnXwDet1*cn7-k2wv zzcEb|SZDq)Y89`rgzAU99Vc66uPl>qGzM@Rx6laL=3dI2b1rrbCFg+KV;s3yw zMNDW%$AP0P(}W|OALz1N6MUhvq4Pi*6DQMc4j#cB6A!d=-tm8stY*U@tJZTcN!X6B zP*_f>)y~n$o-tfdLaCsEVUw-T+4hGDjs3GGu$^vkKT+2obbN72v%BY~zbO!J#g`%1Kn2^+EW9aIvwbKZJh=x+K$M&Z86Qs)CcM>44|xYqJ~ z#W$Hfr(6Cvl?xqc+~;@Xegn5}MPt@~-yQivch7VDk-3+4NPopQZ>If93->pbF@?|m zkj*jcgS5r{mivd8gzMbT-apK$_J{G6&kyAmpC7_s|IM`e#}umeN5AGs%YLV3&U)p_ z^$q7+{x6pNJJ;$Tqc6*}86UKCR5x5|sc*X9a{WP&uv+5OlMJ~>B^Y_bW}IPsKGWy8 zYntDTWFv7so8rTwYQ`7%XRt3)`z*AM<(5yH(~*>=8}6l~7IdCGwen@Nw(qm{O10Gu zy=vS7g{o&vCEL>`hdQo4xhivk)T&4Uvq)VX<4A$DnQQuvv?ny5e6{i#!}A$yCbF0u zoX{S7?iXiIRRsI)xd*P>zR`Wd%)%%1L!*(ML+0ZKziaLZ9S3At-l-SNIB=Y^rs;sa zO^)sE$p_jwYFZ97vmE2F@lo)7FqvhZQo)o1ERE*{e&{qlW|^m0&~%`fWuMc7Y?gfw z6KCI8&e>CaBlL|Klf^BuZ;4EqO#VU@T?dwPrr6|Y-*!%DIH1s&&B-Gyp;$0?`qm9^ z?r(Z_^X`YLZ0ZR8GHgq5O%5u*!!M<@ehm67pJtj>ibKxDW z2iCIObAGUw<(~V4zbx;R3P36RhdI-KzX#kbenJ+m59~Sqs5j~h{g^a;ZL+?=53$Dg zLO<*p#X0}*H{KWgq1?D%pyFtEcIV~$4l>dm$;^d03;KH39PfU8+c!bKv6dxI;X@gd zxlo0AV|jJq*T7S0lQzw)oqeF3v!?&Rcg{Z(4)k;Ead~i@rOxR=DwDnH2V*9C^$*-k z`&B+@GwoOVAkDO2^+PpNy}}1|rh2sx`&s^ZJ;-N?Q~XfR^6y=0ermr!#gqfdEb~5W zS{E?Ft8aXBhJ?o2?P6=dyc8W0%n1C(8`V`?C2` zq^_6THkse@Y0X!Yh3nLNCSMGECEWFdsTJdz< zxhcZWzn!iO(pzwN&D5f;zArpfBi2n~3hPeI;j}$%cC>VEO6##3tCJqi`JAk`#=s=~ zJgedN!flzx<$f9OyQJ^vPMc&qXZtk!;J48qck<6a|L`+!r|=Ko%JYXUZDvmnzwi3^ zk(Wj0&u7PN#QIv!YWQiKv5nwKdiZ9Mm)maBvtm(fW$9b?^^_IQI=eCY_!e*DD6O+h zmj4_Tr)u?BJQQ`kJ^$i8=VeXvZ+9f$TGaC`ud$l(>ct(!iFdLq{SR9HbaUJ?L8D?_ zuV2RO?PW@med(1mWggj?O2+yf)1H0r&Yha6O}oWgE0)hzDskz{Iu{%u)E8mJS9~^8 zY}P4N(^DDmeVzaPX$_iI7?Qo|i*U1H$kj_b%OCDA>-IV=!1sBU-=nWTtNhP&KCU|Q z%K9YZ&dutJb8obG^<>}kP@U42cenQVjxZt4-t4v>sb0UeS_{v(Ki554B&IfHb<&f? zGt>gs&gkCFchL1lLD^a7wU6F0SRXG+-y+I>aB=X2D4DskzwcTc+_!GsjCn=A%hdOu znftx@pU9(BrmmebGg3;t%-K|D7*?3K?Yj5)^S2`gK|32wH0sOUqMO>Dw;6=5X_+q5 zr1nmgX;S{=J=)KFC8JicuvclOzBqL8?vod)VVvI!@>|R6-F6?#vs)HxyD)c(3G)|Eg=a6s9BXGT+Q*>ld*yj+|K_qYmvRN~*W7RFDRXs;mQ2_z zkhUfC_!jfmM_nslK3nKA*C~)~nmqsfrcIC6q!u^KwO*)Oqc`WGUd6G9w;7!7&KHFh z=Q!ysD~`41IeSB{`P2ntw>y%h5q|D3W?aj&40nHg?tx>0^5fQNpFKKW$Le}iN{A^x zQ#xfR&suNt?$o6neJ=h#WcR;tvecXMcv4Z!!YRym->>^RExWYp)3Y7c-tX)9azpq| zf4IkbfzjI4vZwknyZ`1Z%^RXruVfv$C_Yuft1ms$J=X1Zp0xaWpGh9|9J)d6Puowv zOy1?S>D=QS+gsmc3#UKr>dRib;?|C^w4FM4EKf~M-`yG|^fwgR@u?-MRximTF1 zd$+@UsWe}a%jxcEn|1W{57s%}oO$UY`^Bksryi;5CWN8GXE*nC)tGwSow9Ywn{`(Jx+zQ(Y7$&CxDnPqtmu^|>)?9cvT zEGtwrn$Q-`oRcT^a-9L+kvlUMF~7NFv&3CtzhFXs*okF198pCHeOC6(*4Lg@WJ<~L zJ}3(`@N<=V$Gd@>d*am09i2s!T-KdQo0Gn$@U-CSsg~*|vURzyuRpn|LYKengTk}z z3p|Wsf5pA=zxv2(Uk6`OZ}vWuGu(?$wyH$Co?f|;Q&)1k&TEw^ifg3O*GGo!d9~$B zxb4ik+%gp%;mzlJFJ5n--IF*qLhkhJi)~Kl{(gTfG-LUJ4PO3Eo=1$x!?Rc|mC7tTimBOG6&Btol?^QSEn4ymIM;P|eiP zeRh$3PLW|(Jvkyn-EU@p%v#;G@TziHsGIc0RgXhoo}6xJ&kx)$7l5v-YHt0+LFHIb?{ZURc&QDg*l(gEDbM|mvwq9eWIT%s#R!RURdi? zJv-*%$@@tk{GB%)R_jdJa`8^sZnwG5j~OIC{OSEW;me9Jx$B1&Cq3Y45AdG*=$t6u z&d{rKi*`iFeC|Ik_&G_e=t#(L7 zf5~pm9d`bX`kvY>E2lIZf9tL>saEUI9FdbdPTqH(ILEn)rN};&z0<_g<;>)^*eNof z3RN?2cr?!G|GX@b^Jt;nIiJIS45F4?`4l)i$02a}=@p-&>$eWeF$0z*GKDE$whp_XECSK-OyDD~u_C=?>;CyGsF9 z3SH;pTvwy~bDsUv!>lzEt63L^8&#zzl=vj-IIidUEOas=$v94=ugL!AetUJs=Vull z`ny?6=7i@jBlA|3U@xQieiiqHDyQ_nhDowcI` z#kW@9RV@vOV4y&HpH zSWcSw%Dy$?`yKAZMZP$Cp7lfA^ z;)$h>jOWx3z4B$X@Hbnd)!iccZo5d>omC3?4?cDs+Q5|%@pjP$C&s!Otq%vaxn%!; zKgJy9z%pI*hs^CFfqVI^^U|XqO$~b*_;$|XS=xUL-na(k%>8ZfETP*4=7jItkl|=nE1@S z@s9nvg{xb3>`#x$o_26w#GD7V;y)6L>eo!FF0nEG=*1Jm?QapYrgX+Po{h{lGgi%c za>w_$7SEkc%@;Brs#QeG=vSvsJGbT(XYOmY)t^-Iu4mlb`qOe@&=VHTD2^o-XZRY= z|JM9x!MC93&`~Y*kghofvWt_slH<5mh-f+(1aUo>!c^hsq{o^PT4AC7^7Nh%53Q+_ zyyE*+j*ECc-67mEv1j_@zU`BLzgc#^(RX!IkT2`fSzfE1mdt8sRiAWw?WqdZWm4~s zI4Ee}SFHGOWP5Lv{E4*rhc|ep$$w_uGi_e;Q_ZNLGj>y&=NP5$(1<-TdC!-dGbR>E zES#b4qTjtIaF?sy>2Ye@f#i;)m0zTR{yec*P@;S4NGqQ9LecMC5Y`OC1x-!PK>Y#g;d|E0u^!n@wA zPJ0{||9$a(%XHBq>yF)5w5Hx!8objxH}8(@e9jF=yLQi9_TpIgW#y#)^_yxpSy?}x zW%6KY#l%JZH@;MVWU^yEo#w7{#CeP3qr!!Az2u@Qrub?Zifj2;)PF81>OcQNQMl~b z>$O$49AB@E@+$iJ@m2_jzl1y2_lplwe$Dq^l>KQ{bAkVqbrWvwXcG@QzC%o8gWrJ( z4ZIburS_+99ot&9W~a;AiQlZ0W#(SkRUvArvHsE3*mLs?Bhp^lR5YHq;1Pe+8Wy## z?|LoExre29Bo6DWDdt~4x$_ABasFn>XQf(s%`39ns-rX%t=e6jcoylih#z~sX4m8b zzm_#05}(Edq+XT1`b?&2Q^$&c`+rL$>z1d=Ui}dCmtn@DWwLE`k(CT#7kn=$yp>jx zQhd#EML0v@!iwUiKYv4J3~9UZ|h2Id3logCXd`A z9=S>TBO4wc+#^#b`s`tNLG;I@pUFAj6aEWqyT0Q2?T*FsGe4|b*7{+m#P599DB=09 zr>4X34jCx5T#SFOa#a zeC*2QTbHL?Y@Tw_)6BE)>g~V_3GanBy4^b=T`>E=TBCa{){V+$-RC#WGLU<Z#xFO1@p7u+LOz+Lr6jE}SsXD3|-%&A=MG_g-=1`X?LqwgzZT&%Y=2wEJJx*G2o| zN_mdWW(m5Meo-my*n*}lM>!)m8{L|l`5}Gw9>vAB4leTrZ`;0`w_nuy<#}$=gEPJ? zjJTM=(cbB;_0Q&Z$E*)*l?GXld}r7{+8o5Y;#H2_Z1Y}64)&RI&tICEbs=bTX6W+S z2jA?GJkuGmd9CFNoveS33lny4+HBzL=FMHJ-}gi^%6 z+Q|Qqf!r?NFMIFaIo>SaE~mwJ_~*kFsh>9eEb&+ye(S}=JCa-P)p3{V&kHnPdTPd` zNBrE|_UJD2NqQrj^@*>F-TchywLyj};`v@}XV-oDG+p9KB7fAmDIzjcZg8=y_Uzs0 zdvD^u-?A^R96K~Q!OcZg|NK6u{15-mJaVj3-ahNzk*z1L7pTF(QLC zxODQKzH?r(tMc-5cbb_7@#R;2%KoCa!F8{M2$P+{dF!%k3mQVDlbO5CqYh5qaKllr zMY-?b>@^Dcv(7uWFYL^!x?XdyK%!!X)H%IUjqD|-BPT1{{+!xTA+K<6>%_W6X@SQ$ zbX5+#-YqtXy^bZ++4b^d=Plpzi!a<$6I(ZVnx==@mHZE;a&6wN%q&Nvt~}%m+adNN zh)cJ=DhQIR*p{t1FK`)_CsyW6G-DcTrz2>rG{>DR|uE6L}mnd@;zt@KO7Ak+2x6~=vt_tnC&HVk-B)#3O(V?fD z*O$Ki-Kh7Doo`m8e%IoeKc~sGsoqh(A2m%rJv@Ke?)VI+YNL0&FDriC>TK7lw7af+ zXG1~6Mx`&8PdV>U(ap4ZyvVCbC{yC$3-kTg`=Zt<-(-Ce!fl{Z@0xu3aK)mR*_@iU zH@&iX%%=%L_g3^&FE)DDduoUCsU7bFkJa{@=*?9*v13tA@KdkQb&HHb9^SY=>C%Nm z?#`k;t1P~|yPC$R=k^v~Eo7IyzUr+PZ`g~|eA-JbkA$;+;dN*CZPq(p`l?9A@wupd zo%<81lB7swfjf7nwr>^)S6`PXt9e${&fQ{CTd|X<)XL`9A<{~>1N=V!P*>#jTe+f{ z?TzbKr^iz6z01}_%dWe$Y~t^ zG-c`hrm{`$0TRuB3I$Kp=e6B=EmO1YQL&w!XU(i8x9h?W7p&ISnj^cqD*TG#s*V~b z4ll#Z2pjp=yQj!hWq#a~^4N>7Yup0%A8WKe9(eY>@rM_?4GyFwFLv5f{qgMM zDz9|^?EF_g&b>J+_`gk9HmB~Q;QakZV*=kDd8@TpxBkq%?bBDVRL-rkTlV4ow52PA z|M`XfujRAv24bp-+Jd01QSf8(b_fy95zmuJ| z$hT#=thly_{Y~CUnWtOygYQq(Ncs0Aq-wem=f#~b{$JnM9sPMn*!0^spRK;XnDM*m zo%);cIE&F}^51&$&06pJ9cW$%Rd`R$3<&hUKh zcr%s#p_A5sL$*Ng@~*s`!gBt-zL(7}|5g(bdbKb-CrJNCcY+0vDC?_3-7WKEnsaxo zo-PnPcgOu|mQw#S-s~L{0#?qe4w9P|>MbK3CziF)z%$OGD*WDy>z!T!=dLwxUi!Vb zX-=0FJKvL&yia^9KeH_={(L&vQkpA5s(T|xXx7W`{8yHQUAP)lba%6+zLW5)IiEcx z7aD|BNEH`OzhnGuwXoquodeH>N-r&4?WBIUEv;U@SDf=}t;ceEiNIavtN4#; zEk4+~e1VCS&AD~AY!>caFMNAv@&3OHKg3!D%buQnv|ZjUJfe1cce6!#+WvdCEITfq z$}aSNArQh|tCw?nzWpw-=8a*FGACEl#>_yAAzF#wrt#IZ@|M2q7 z9qkQEe)o9S_~g_p{xx9VVSdBAG8_2tX+Bd-M`Xl`=dTie{ybv z*~R&rU7oLcx=MA?)j-c@#^2nUUmd;3IQx0OK#DvY~UN^+O{oHrb`b7Z0DbiT3=fb?Qsf&z}jg(|`P$c=Y9A?mGuB<~Ehc*BmJ767gH<)Am7Dt0l9sNg=NN zaPJ-EM|Yf6({k_B#A=E^p10e4Nusf0ycBcyspoP`_TPU*G?WO+?Ms(hW&WT)-z?7?x*O6EuVIFdO6cV<@LTl zqkSAcMIb>HUR*@nMfW~-lX`R4bvo%ugM>guu6 zO=UlyF)=X6vokQL;9fn3vgvGYNVdOlpn&amey+Dp3t5+LnG)U@6tzl8LnP3}XVwA< zLy51KgUnJl-kns|{VUt!59^E*lQNX<|9Q}FbnZ=a*veZQW}kVpxBA?fnL8){e*AoT zKEtyO58K!a4s&ztm~3p#G%sb|-ODL<`zsaXPu`rgMs)M-s?e)lJIl)M#oT+;kUu%R z=lYh-TdNm{e7SQbZE4h*uro)kQ_Z!npSpQ`RnV>5f!V9#Rz71n6@K=bS8aERzew%= zOH=e6f9fs1=3TV*S;G!a{pdq+H5MWBPX4Onwp<^r8h?39m)`3v*KCd%e%pd&vm>&& zZ_e6XU!Dyp7<2!FqI3xpWATpm~NYQ-85EL+xN$h{h}Nd{Xe`F zGJd&zWU{^17rfMA`Ob|C-*Wyq>tFmvHY@J(lvR?^3JLEbl3)F~zer;L|NHB%tY+?-X#bXl^OFTN9CmAr! zZwX{k={cyMC=(ky;}Fm6De}TIz9}C}n{-xWQcl{)MiKLJiB@4 zThBz_3$D+UY*o7V*{tc=ft*(dm;t1~=(NAbL_c}a>>?T#*TUiK)rh{>+1@!*woojUg!u_n_xE0pee zv^UN6Pomecm%nt{j^vr;Cfscdx}B3Tr)z<>lp^=7Tt{OzIjaaO25IZ0c3$)3 zWVSHBBb~g-}78_jMiRxso3wDz0!isOPeG1fJiq za<7TO%MZGHd!cFlA9@^6BhJSWt})_u1(PCqxN_}Q76 z#>M4_H@|;#$J?cNYj@81qN~SB881k*wdXDoym^AJfME`gIS)U-wf+y*ll=v$eFSu;=+_kmfq|5a(T|a=5oFDT=x~%*RON+PuS1er6Lt2{_g3%Cc#?WjrY!Z z{QenqhpTfn>rL-xYgUSyY)!eiXw9C+NxLk)7wvufc~SOyzU@cXaju^${_6F->k{1M z^ZPHB3W#cCN=#qL=<{uo+nGEy@yLp7^P`8fcgVh->b3dPk|(B4^A<1JD0t3N^r?%G zsxg02k*4<97)jgv!VA|(`ZgU{HEHsccFUNuOU=j2E}xV>viWAgre!UzBw;= z%GX9;ym>uyt>5>PS*N#&MP4y&O_sgLQ|oxmyzRch_Sr{}-ojF;*vO)*R^HCt*r z(ZfWntJF<7^C-9a1e-jO!&_#mcKauMPkHM3cHU8ekN_jC=?bzxyf*15uxCV?{nBv1 z@@;**zOaAUH^W?`%^S?q6i?2zK6IqVHe_0_-L_8A$eZc~xn;7+=d@R^)Si&*emPao zT0Q#sk(;??(-LpaHTi$UV)I-UZvTQ8GwxY9YTvY1RX?J+_~9Pmq_gjOy#34G+$grm zEPldSRFo|JfA+!EYac&PVefz2`BYu4_{)!O=ZU4U4+U4+DhuZRknfOv&+}J*(%+_> z=Hok7r|!NfIz1&=B-t_hcXDE>j89F=eZQ~h zMP0fQ((Ye7&3{t1Q(O7h%yVurHFuWhy<8f0-1XY?N5|T3wauEvRyQqnTg6A!=`!{S zhWkHr)|r34=C*j_zKHx4$9En7W;io`iJETIt&*2dPrqKD6%lqhmDrg zcXRys>)g76LLDjHcU)h$wWoh#EeI@$o#Fbydf9TlJC~*NxQ`xsJtIIrqo{VtTJ2lM zH|DKOdgb~j<_!PwSr;WbTdJ4dFz9Kho&I67OF-Q=>BDOZg_m~uJGMSvX6fiEeDqik zr;yC@OP%)}H*rqX(X?LPyH_x6-T`lSGijZ9Q`jyZ@i(^juKcd^y8npP?!&zkdv%g> z!!6!ge*4?=xcVcXVbkID7GWupu@;;1uS6Vt_&D!nPJc_%-@P*0thyKf-P=|WTajB| z{deoef+hVwEoyX%nB66Qn7by)K8#40?tlJw?Za%=T;BHH`m;CsiepoAU$3qR=-6Ji zVy*5SVdtgG)ny+3C~mK+cAkA}k?rgm$-2iM+{)OZEz`Azqx7qCSLE!?Yn87=A8x+0 zLdCiw=iIIgdw+e;hsTBcwiJGe-?t~xPWYN}Vr{sINU+SWUm5wuD`sck`qg{q{<&{k z+mnr^RP0w>eQly{1{cT0ckeQmEZy(^mfLZs&sy&9Tfdfl`>^Ztv`1yhE!WM3UTtc5 z?c;epYSCW9>?u<&rFjab*YHfvr^4xi*Jdoc;<>y}BC1|D%=ehf zu?uWFrmg46^hyp7-(htB78{ znVb1?Lf$vI`AdJq>im{|8b&_7 zE3$c&(bNwf>!Qp9!w!_*+kcVw;V$2a0p@0WyC%;xHxE7j?}_L$vqQ(SUfu~xlu4EM zpOay^xMy-2E9-IZO>>Nn`EH!E(*lGWIzMQ^UzDC6u-+ zv9@#p=VhvDfw;xt2C> zPK1f`JZ&*wx5Y1>w>O{MDR?pT%ypF~<#L9R(_XqheVZ6~N&Cg5mZ}ur{xz#+Y;Ie2 zJ}*pP^Q6P(U(+}BO#B;G_PkdwTA817+b5~Ibx+wle_Dy}z3v*mbH!QL*RfX~F47aP zz2G`Kwxqc`ujJxcyZQ%L>2#mtS`H-7f8V zJ>^2C$8z#gKHIX#neZcO+&cfi@AlWdpA((MF<2i;iY*QGYF+F2E$99HYhb4w% z1*-{z9-9uM4(k?%Y0P@;Yglp^zA)t(zgT=F=fd$7OaYk}=9ct1mLHt9JK5ZAcBK>rd`^R|m3&HGq($bU%v@r7|i z4eObB*J6gP>lc12wYt0@|G*{og)baS-niwOXH^H>_Ks`*ws710b*3*Eve*sRs=w4} zp0`M3U@~P z2x~Kh?{sxI!#JZM$U$;}jDffTe}UQq9S7D2IR`8ozB6bsYBL1INo-)ez}vy}i*Yui zIa40v9j+e+56TX>GtOtKkt&dX@VP;sX&d7{o(hH!q6fkmnH$#cbPf2j%XMJ{Uk2+M z_L#R~t_wsDZc&;Mreeid+o0QU`%Y5Zj>F1xl%5Fg+Hq1jX3=5gJCFW0WHZP!)Cd$9 zKX8_NC>Sd@QTg3>4(BfwF6(AI6`X8Pw&_QQd&N<=1H5t*+xu!td<5S%NHfHL=wLDw zE?`X%ZE*d0B#VFj#he2*4zdew9`-hqFVK8o#J~Qw&w-T<+zihLBuDM^?$npAZIg1(`ET0ee$kTuu2F z3SyV%+;m6xc&3NJruqCFH}5Tf!5;E+j+SNriBG0i>tb&$RE$3HNqyB5AH~z#Lw+)b z#;x}{C%@^>(>N9U|{#-G0`>fYaS{HR><^cmZ-JRZM_GxonA7bqhB`wY{k8Mlw_+q*q?hq2x_ zV}HTVd(`##maJyKd{zDO)#YazYCnfvddr-${iy399t)#cAKkB>e~`Vx^QOp@*Jreo z+1j^gE=_a#JZov;$Cjfr-S;nu?D{OW?cUL+YQ_TMyU!-X*yPT7%9-mIBV%;>ankPl zbsa^%m-GEIK0kW2;bpmu+Fdh;ymg-2@_){4UvcaGo!_jhOm=p3r~Ax%X1v?B@Z*}b zLEm3y-1V>1oA#hS6;Ced?vjwGGtRv9{v}iDrI|TGJ@Z4WI(RPYOzoPlZSAse zN%ZNQpYK|pb#*_Vytv-I`^pQUO^^FoH8k8e)#`sZv8Twg(`ep}J3D@q6hLuyksszJ57R=l_tK&Cw^HZ~t5p*VDazbNZu4M;T8| zpQ7w}N^pN|K;}>8sam?>n^%{%%)J!!lw-3dFYh->4CR>NvRHN4ycl=;$dYEyEd;dbKqve~w zJ{R3G@n)awGS8O=c5LM=tC!fWzvr!eHuUyU?{C+3RB*0Y@K2y^abVif_R~7a-_<*x zdM=u^Oy$>`6(8?JhQ;iewdMKsJ#8lq$~>$0>~<-bDygCUeRFWzmd%rl|LC3#oU{3a zvgPu(LdHRJbj%O>*LdtUuzVCAvcT=hDc!^VMc+Tz9(h$H@=2+3$C<|mt3SQv4|u0H zal6+4_s);}fA6`h@NH(_wXEAr>ufHanP{5Zt;S@tvFqBaPj4St|90I_neZbwT9qkD zz|PvGQMXL+K-X;9Pih~pIP<4o>(=F;Csf65 zWEo|H#I)y|_4N<7Cy_BEL7D;gs?Adl0|s`_VJ^#DyQp z-u&J7%68JtYL%P6^RMn!-dyg!sXXHAyEAX@&V2K?|CMc3M_0{<_ZM5#qO|9JI2{;# z!{poaYf-(=;%-FsTJZB^_G#T+|9tcIB7$+kWsz9^IZY;j~J}thJZk7Dr1Ku3BREy7;^+`~Bx^ZT0W2H)mC! zxS*x+{Wa&?Hzg5&udR>1ttK12ykJ(svRh|%RL#CQr>nPlb(iVM0X>yIs)`hW$ z_Mck+*`$5TNj2~Dk8dBI**qyJHSm!4_NiK~vh!!nDL?w>5_6J}dfTPHNkLA3@)}PD z)|^rhF6~~Se#HN>%XHTg)g!YrllHlu@$ax--g$2Alk`Q#iglCrd{|=5o82-|VE;<< zbk!{@75uh!Ocs9hee1I`EoQPV{jOhBj;t4(U8C{SVa}$h>&1QM8ZDXIR`xN>YWnP> zzBL6`!>wxg)~8Ds7p(3(zt1u6`HF9gTFQ>+flD_WPjU{{O>Cdx8L7*b?1}d^sUnBc@5{U zwu^A@&()rm`lcvmf>gy_{u|yKm#z|g%igwSrf12OKB>E2#8&6EX1?PB(dk^NM}u=%C%sRM61%Yf zjCk<-Ck>fjr}Jel=L<;e68f-f`^po(8Bgz6-U^ylmN)hAk*jIb?oTxNmT46m`RKWC z+v&`8r5hhk@RF=i-uEb|`XirNxzKcTg~sS>PgU~^RzDWwth{kDd}^^)=^abUh@U^@ z?Cvo@y4Sf3XW!*jZ`aO|e4_2iu^II?PaIiTv zPwrOM{%w;YKi{3LJ<;s@$?Xlb)zw<7ivC!(zu>ArSN5^1{F&J)*SDqf|GBRI<`#PH zzz69Sd!sUMXcgD)wBs+?UF#R0_xfgxl!oz6`P-AY?=q(}KViC1ACbA?Z;fnSM#Fi= zvTvs^-P8}4`mpc3#3ijn_R~*T%J;oIpLl+At^c%XKYLoX33&fenKq-6@3PJ1+3z3w z9Nah4dWo;}-a7xJzePHl|75cN<}GqAxwzu1_SS3bD_jm-)!G)ir!FD8$z%O)k$ml| zn!#`CekZOn*lU#ZJYv03a&+?Z=>ieS{%)6pLl$3|vnXytk$r}=_w&FmjeGfr;Fb?wUS zJjE-nG?i5->gtL+$Ia!-8Pb36*r{JT%lG`W2!HR4=c!)n8dCVgWA5=4RP5vaSv>X6 z(o)hly%UtI$3P=QQ;# zKW84&PTf_qY|a0Rov$x^SX+K=%Nr?e-QD+Mqw3mLP5rY{NA{C#(RR_kh<~kJ%Ug2~ z*yv9(lUSNM(=4Lzi_|5CITt=}a(m=xc~`J+k9DcpoVEMv59~hAn1Aub=c@s|H+_}Q zpIvbwJNxmYuV=1$X=dp8YhL(p zx+&$I;k_I{mQ_;E(mnd5Fp6wKw?E!KzEv3}i=R8jNZ%<8J5w(OmYJ+s!{ zQRDRQsDE57zrpLNPB{==_M z+jiab>9AKh5`A{*y~=w#IX{|5{}g_d9{O#Qpzm>pr{R_#z1}~se!Bi+nEuwT_&inj zmkRrRZ?3JnsgRX_-`nyKaROcqRWSbJ%}B_w`&dZ*$JX?)3u2E`2eZ zt%I_+IkbL@v|#uCc_R3EVzA`?B}FoO4{ZtG7Rbl+pDFC*67yMXZTEG&-yah7y28BY zPoCcUz}eq7hPD0W4{+&<5hy9S5iKtE=}_3s=5M7tPS*8hJyCqK!eN`8%hNjb=mXDd z_~$?Jo3mJN|K#)KdzJL=IlL_2_9Hg*T;s#(+b!DI+rFQEzvbJ^*6pGb-0P}m&O69d zvvP}m$HYxRw?3`d^n=gJH&SN9KX!g!w9MIW?37jD$nd{QR1 zXz4!YNt!2K?K+zER~|950UEK5-G6ksDkB4f5NLJ-`^-juUP)?23FIu9+`iL(ha5!O z{y$vR(bXU>AhSl>jQ+y$-@du*m@3ya$|@TdNxe?P9?5G=#Bq`&VyYteH@kr&*JKGyFv84Fbd-nD%4v|^V0d2X#7`^0*+3oni& z-|yTTQ2KTACmBmgtMc{ArOKuApH8{$$LAM*C;svEW%Iu-vrhgO^tmj5m8kTq%~DpE zf|3)4rtrw2f~Slsu+Cv%LN^ zDP447>bz(1VX?q_%g!^3P0V&@)|98G-H%>h|L@l?*#ovRSsqMMK@AOQSN&Kgg-u?W zW$J&Z?l@=k^G`l|r|TZy`@+iK=H+((z1;q-f7wpQot?V(&yC6FW4^8{EPBTmd%V24 z@3Yg_y$dc^1kYY<&(##L^AvY;;;jGSigMPE!W+|= z7I|)~-|Ex;10w@aFBj%c2F)ao#sv!rO7Zd-1fY}?%@d=IVeZemdl zH0hl*$8XNE)6*83H|p%2`r@BzN9k>m)(6g=xw3I(W%rsKy_@j?e;ZwwHJ{?xs$FO*y55a-;}5&Hp8J`; zd2S8&sd=PpmeKupcIWN`DcAVJCn;Zf%wV-L)1lStX&jeut=_F%mg+EZ+2{eE*vWIPi>~k;gOTx` zOZlxMJq@v)CEpA78C;z$KmVdy4{Pq8aNTp?_@?mcI{&UwRCQ31FOv0LGP@#9?|i0s zXtI^5&Ej*b_gaR|eeth(>EPmBl@6X_T=bi$5+1fwh{c> z^m1mFm`!fRW)b~q6=sonp7FxJxy3gcUp#YhLGUr}wrQJY-Q71O`fLqban?qQs1s6A zic{;%j{nchH0fB>yS2M)ZRxSKk)Ln9$rXR}%fV{yvv2%+C#3%t`}KQs)inz}2Z;ry z9^w@S_A5DGNSt}?>U_1gXO{LwyArS2+8M&D7Wz(-SKYAu>58{1EG1iIwlv7*o?5W` z(vim{VW)j}U0-=qaanh<$Dbfi^G%zhqUJAOeyn)b?5v6N>SsN@o;kNYr#fYp&E2i@ zs-N9_;8Erk-W%kYymyWCyy%AAwf|SX6T2&Z;pVHVfJ9w)<%H_j2Tg1rbZ+FhaqG`|zPqP(PMUa) zmr+Pl`SMSum9s0%+M*XV^iDgv*4>EL*~+xId*!8-(G=@%S}<%1-SE3g*wnKxI6 z%d;BxNIULnKg|;^kx|VkZ`@#k&7W|MH*d7Dj+cNWh{hP4uAm$&~Ey_ELS`K*|!q-glZ=1o0^=HFPL@>;`2WoM7l z%K4x6%?Np=p=$klhQF4r_K$sitxJ@;mz70-Tf4A%n_Y4Aw7=OM7q0DZdOz)Bq-=Q| z>;DVi#iid~FxN2nySTx--=L;c^!}Xpo_dRyTwv7xam_pXi{QD~)Y&&KKaM`S*KT7m zr(vYZ<-1m&JR{dUe&_7sdphaxCXMd>H#Yhyr5vBtoHETa^Z2XJN57xkcfTP1TVcbz zG{;o=|9X#w@2jwXKkm}^PJLb0u}{|npT5XhX{uv-^M1!aKC_z_w+DWHIbS&^DOf}C$5%#KJR>a<-5>~9ZU2g9+~yus&?5@^>V*HR~~!0&H83u z^Ue9+mc7Ziq8uGvvSroct1`V=w?rNvm~wG?vb&3_T9Hwh+HKMFH8~+KYO4eeJu;J-4pH<#ra~_)ae3@65I5lhX@|dEFYODDw zkEyOUi!45&IDO4cm9H76Cnu|azW&YoBU&S5Yt(`6E*=JkXVMG|vRE4-ZbgZ?&{oLW zh}wdXtD^S#_ixwcn@it_smQvsUFu-gCo_4DU0Ny^XYh9jEMjzY$TqvF8mY3oa5E#5 znak3;@Th3}16+!AIo@H6jVesNYr_JwvZHVNrN5G&`_kwCiz_p)&HTr0`+VQ*v)-E; zH=0^MFMntIeNORt+wYI>&MtoL-^=V^`6uw7c+pR$MLbdm?{JH!-0(GM2<6n7z#+q8 z)adj;r9f~8rwvObOI+kDHRGZO3hYJ)WB9HJmK8ecDJ*AwvZcX<>qI8wPA!FS)+ehP zVni2cb3S=A$&K@w`1Ccmgzky0yCKzA9Yl>6EdL(enz^?8OWr*h-$i{v zcOrtaH@bB%?A*L#Vz|EYywk?v4oAJ7tQI=i*X441T1ItN^_I0tcP@tp?l!%1d9$6Q zccQrEEa~8j>rzwl#BR?qFP@v)Yw5B_e{V&aibzVP-p@D8=U%4C_LeT2qn?=fQPu6l z)^)p=TvL-d=Ek{7Z|w}{#N7Du5=sBVz0)_HpS$$`(H{Q~*XQ4BOWD!gbo5}>)VI;e zA0t*pF@-PH(Ae7P5zs;$ByDK|G;s1*z>-cOV-ag_FP5Yvyq~fi_xkS`s##7bub(t$O zR>nt*__=jI>I@0=&o3`mn%U*nwe5J&T-Udq>)zc9nUcO@xyHr0U8;|SHpE1)F>p@{ zZ@s-JdgIx>%k<~H>RFcbR>;CEaewR1YdrVUEZF%Yia#*4NgS)>C@*(^ELbAsr*^pZ zZRb0?oD65D^mX6P6>L%CnCFm1_M4_Tp@n}WVqRjjLaFZvkb?`v~O$oH$-?WZ~^<3gv%WQX3P$`@tNzDRoagyH9-i3#VHEYY0f#C-otZvR}r zuBfWVDJ!Ms8ZYx z(RHd3Pg=cJe037N^Q_fs($&LEO|LnRDOFf8Y5si!BOO1GTKYkmQ^TIqrGD;P9nLV?74^3wA z7u@r(QLf@vGb68njoXJ>{<5F4O?6FoS^5QRyf(By*v--}Y~%bv=U`lud{qMb8}$lS zu|(U(a9;BmM`5Nf?UB#-1YQX*dbc2?_@0Wzvss^av*tf6+S7J{>0tDUpt!@n{dY3^ z4(&d^!9K}uhCJ9hYQlzL! zK+TNV2klg59q;wH&GJ_4lik7ZoIejQ`m@5LK? zWE$sdP85`gKRYARj7gq1B|s{uX_wQ&{6m570+)PgU9Zz9`elReGxr~J829Ks(dY8p zBK|Toy4I}flTxm2j^BxyZEDYyuN`AZ+a^?YNJ zSCUK3F}}N&20J&1cSP=&Oz->VKOyWCT+TeX;EBrgIX^bzlU_#q_znI>(^?3rn0qY2tqO^G(M~F731O zGTpbnucNj!<<&l`tlQx+Guet;-mg~v_w3{f@$S671oIyu&DVLR^?x)HpWJO-7<^!t|iAghqm=E91GVm zvehr<__u-MU&Bi6DRU1m^T~;Di=KaNzx*j)HRaFId*2m$ZTo1t{O3;1CqK^`vAx!k zmTx+hF!NoM4O9JrW|bvrp(R@-n~z>T(#+E@JbUAlQptl0D?P-E@h5O{I=> z`AX64-_m<-H~$H?zrWyC=E6cT`Sqe6-4mz9w!C6lyYXUelUj+~mOnzNxh?aG{-)eE zcReMvL~2R(gj*B0ao=RIZT~WT<)7Jo2PK_W%{%t?O2ZbZOj)64$A9Wv+U-%#T5#4m z%kh=dt=5+Ig|Dyv-+%7;Ww$+lZ|Ky7^Zir2lG!_gjnBSh>!g5d3fD*?aS? z{BM**rYg;AkJ<9;(UHHg$rpL7rJq+hE{UE{aCha5{H@7SdmCGdmnHqVRg|rhvf9=* z^4ZtUoln1R`7AoQ@}u&+GP``EUh#Q{_&LSa^jF{7c{wF7uHr9Me}e1FQ(r6Z*=HJEf7zrr@JGw^@1NhdaX3_lU3nAuM!-mWiteJc{&d;@&mPSE z{CL9Ulu7S$o<6$W!gBs@phdb&z$xG6eKGSdoXnV8qxbrWv*Avw>0zG&N+Rup`L%>R zH$IbE!1hzST7H^)NXFB&V~jsPd+(ao@3&*x{W-6Gsa|%S?^n5gWzj#D_gcE|x7n@y zRCZCj^w-SR%$>d*oUy{bdb6F*im*DBI{D6BXj8vx`^jaRS$+D&KeuR~|M13Q>fx4v z*;y;jChg1KmbgrI?YF6qGajCty{Sm%>bI$nBUDUsw2vo#xZ=O{l23EZJa6ykYc02L z_xdkj^e*_9)0G^%rTwiv$5rPF?Ty-cCiBI;16J-2uT-Z=zI`CP_`~ciUz#*e?f3Hb zk9@Z6+S{99UWb2e*j|9~NaC|Zcg3vVbjS!dp2B8Vbixc_c-F!v%BKEQa_69xTRyw zdy;qYhk5yzIsWXqV9T^$qVc{A{|vW5Y(Z@b#@-8{=Hc420UaYBho=a&7= zQj@pOnY}i;FIm)X)u)N~`@TEx66$@tX$g1SiO{aCn+=wSJTJd>*vn($%dQpH?GI{S zul(J!?$hy?0ya)D;isKvt$QmzU2o0K%NN#WmmK|~a9BHS-n`>i9Btf9vZA|9>mGh7 zU_QI6AgNhZ@>*<++jSrFX)%9)KajecE9;VH)$z=5>Xp4W|Gh}+_AfAAk!)w@s$EmO z)4TGu#GfjKC2s;haz%(*FqEgVZ#KxD8~vMm3eWu3KS6cXvSBaeWO*h8Y}-1>r)NahkXBZ{A0`9t4qr{rhT{=tMlNYree)0-ILb^>aKnGsS;$Hofcm6_spwc zqt!ua>HOzbuU@MqCQ-UY``QbCcxUS6A{_`Vqm++2- zEtT4(srEa(ng6A!Ha|N3Thf}NGGda!y61nK&i9{}nPT~i{h``IQw~OZEkm<}Umx!_ z2Q95~cPi8_+s#|mPrvCw4o&P0fCKqgDI5+p> zniSd9PqYpnnQL<2c?y|oM3gmw#se6Y7_lEvPTV33E zpEX{MiGe|lg@Hj1YbVAdF)t+t+KcH8x}Se3K;)mE?v%u>?I0i-oBfeBb_Jvj49`&1#2)e0%o&a+f^6x79V))NJSdpC9KI?<<~E z_y6Nd?gkq(38vFl%}b<1+br+y*qyz9%i6xTW^3D<<8J4MvHYB&xH5lkiaCE-*_^o6 z%~$I;NLPJ*^5Fl;f`>c)dDrdU_WBCD=wnT1zwaym|K?jbJ9oS7j-v|srP6%Ni<^_{ zo*T{Xdi%Hbcy2(z;_&6}btftd6Uu+_cwLsfQ}(O${O;aO|Gn- ztj|MT6joa>haQb&v23w8v*JQQlrdQy%>DGmUdM^ule2pZzlw(K5OZY} zJo+=Et4k^9N%@4OFC{x4Yx8atn()Zk*KlP@m*CQAnzOvuKEJ%}%Ey1RlNdJqlYFV} zzT;Wxw5@+6UdL76e64p}ufopox@q$Z!@LUi$LgqKl8-8MKTl(1V0gg9z#xw&g+cF) zsSWh?W(pLs)lYx+)37!&*f%vY@}`*!)U?`YukZ3SLs?Egk&|8f|f<8%Y2h(OG&q(($&~ZsfH0$E@aa|c|(5v#0<*$Bo z*Pny_YtE<5*L42aaf?Y^kT3A!U4;dcrPRfa?7DpHR(BNl=e55d>#hB?uGZFGKiE*CR77citO7Fw13Npe=@-wE)Lh8)OK*_3zcA>ggj`9D#Z#2@Z-3gL zaQR{25p&(Ano&+BfsdWc{T5DB+9KHXrX)4V#89WF^H{63u&J6_SSR=0-^zb9A6b^? zSx<3KnN_^6`n=`+%Deym{eG<8;22Y8d+**ou>#47ORj8ZxpS-}_qYAJ*tv6 zZb6SaCeDf8Bo}otReO5pJ&iSK#WQat>iF;pS?-_J`7GG`<4zC?& zb|g<+;_=K=j73WP)9aN_znuM3nw_jS|FlmUcXo*G--NqS%bXAIeYz)f!o8Gr3D&M{ zDS->pE=EO%+)eSE#!};FeA#Hd!`|ds4`W0fzb(`9`6e*s-9CrU|B^E-7Ame;813BO z7AI@Bqr~L!`o*oG+N;e>URm&M?_=nD!LcaHo#S}Pmd&pmruO;Sl^%@muH)I%wMiR@>sA(-6r*tN&lPsA|r)ZLhM_j$Lkxt|qQypKWs zGxy|6?C#Fq$$M4X4PJ`8uHD%EEObk_%Vz<*$B7xvd-@pfxz3(#CivsT#d42To<9=ZJWN_m zxk@K#f8u>pZLKWI_oe=DPwe&*X@$r;$FDpqlX)uFze-N{%EH+NH@^JbdGdm=+#3IV zGTUE?+tqh0^C{pvw6V76>Z%yUDBbld4}K|~I^Q5dFe<;jYtCOjrK8+`6y_!ctX()c zCHStrcIE1n%O9t*)R*1$t_!*KU)|`t{<_QVk8df*|8jXAXw|>3hWYGYxjgacM=72A zZ)a!RI#{#!@Y@>}NoV4cU0qhX{Rp_|=dweo>GAQGX8Vlq#r?HM%~}6XPkXDz%D`}! z6W5rcPhwJP4kT-}hIj`Dr%TlFSx#4R@tL6Pa=;}smt*>ssLNbgg59PfOLfCext?j1 zaWZ&v;Yr46pX|sT1+Rm4U)nZVFXoD5lV54ca^t^@u_YH$CF+sGs7hD;?|t?Eoni~M{^F%nrEK= zX8oC$ErC%UJd&5w_Jy~^rOdzb?Z&^=n>VLTtl`^GTHW`roD2FY*r!>{OY%@on+mWwCo_Suf4(e7l1^uk!!7wDf7tS7px4 z{5EH*r0%nvRdZ*(c1iJ?8$8^7PhWyZ{D1g z=`OxD=-j!;))#XEyTo*?t#W6zW?a;X{F$Amwkze9(5vnGVNStQ<=BiOC;L3qT4wB; zoTz^Lm+n=cf^N30c{+NtT6QDS`1H<0Yh15Ya!x#381!?`Ow&)w+Y)Cp$Dg0ky@$K2zBGZe zdiDL&^)>F(yteIha9wv{>z;R=8nIvBdS={7EDzO9D&DgG=<2dn4!%{Lk~24Vow+sb z@^bNA;?<@l885C}Sh_I!@NFr+>r(b%~ zw6C?c>C&GjwU!uf(Ts;J)gD}eQ#-Uiwx6&Q$P>CJa>`a|sr!uCkF;k5t-F74-Ej-E zu>b#@&)6wnefqoMcGijB9X^6w7bkx0$cx?Ad#u7Yds#7SGXKZa^s`!Ai&JGfx944q zSYml#-9kUVJF^PsyfRbCzLL4Vt^bJbw8cS27rmdP>~YAoSz-4vJYm9&w5ykQTWDki z_}YJ*pMLG~Q^8s8Cywuy30Jmr`}eqLS5wL7587SR9UM%U0h{-sKu@?799OukK^Ltj@aa-<12x zUe20hDRk)8*CqbnTR(i}Um z?YXr4Y00JCiFrk5`ULK1F^qfTje0CcpW3E5kRK6hK zOsCk#n(r6RY+1V7S>dPVr@zvsL6vWxPTOK<{#Nt5U*9qFqOD!+%5gTatLJdKeJ-l{ zTQw(7edXP!z2Ey2w$FZ-@AXXeSNI-xPVMs_(<5F69)88;GbwZ>)8@yPo7T+Ws;p$& zy62mFglut&-isS5+j?8B_J!UkH~1l;{&IifMtAP)ocZe#Y}Q7-Sk$NO z>V`1KWj)6>NX5DaibN>IO%D$giktJq^4DCeh-Fre=5?%-?G=TtX=s}MVd8D#)xNUz zK?aM`WZS69w~>`=bN`w*)*0M=d7FjZ#Li9st9AJQds@#P_ixzHbw_L64)Kp@{hgAd zjq+<*85j%&7#LKswnlwZOEU7&7FUG54i^p=_^+$Nl32;wl;psi)yvqk_ABc(DMnK+ zkJhBAb}^Gg!zS^z@p|0<_IK0!_uKwnT3_YIyZo)?*J%4M{jXm7yt|ij)%VcCf^+Zh z&HZ`y%wFT-{CD>M|9up1X#TNLOm2Z#oWsF?9~wXUlqt@dxqqel8q-?4Rcn_lHoSZ$ zVpeL(naJd$8;ouyvTl9IyqU}9p#}fFf;|Ru%P$_SiHDF8lryuJ>WzV)pG5HTkce+_<4S-$t!GWcy{;xQyFV zx|4z@72VcatsW2#?2g3j`SdlY{E`9hYmvJV$}^X&TaXwMf9@6M*-MkTrtD~P{&@1HxA^g8 z%a6G|+W7Is7oQw8JE2oE6c2Ct@`hJ;;rfH8MT{Mag|MTnO?jsbxA4vezm=;63VBlYGgz{ zja>Pxgm2!2o>%5uV!95Qa<{J8)s)NHcp{=U(JoC*_5GTK%O@2Ux7wI-IEHx(Z(R97 z(ELnG0GE2wy|_8n;!AD?&AyUZzb=RU9px*!9i*H3@4!fe4>fZ8;ia*Xa-Jg8?uF&jRH`B_4|5+Sd+n4+0`9?K!AK854 z=5KA$ESnv3er!{@6uF_ZcE#fQ1;GN`oLps>n|AN1+@t#QVp8wjX#!^+%br{!9C~J_ zN6PHQHjZ9TswY`RP5srp^A>HPxv-^y5+ixnD zDfM**{p*>RAYJ#L)9(EG4_D;Ax^XRIJatN(e_q*-odq|7OKxZAC!H#@*0NTS`Cuh` z-&xpve|-+~b~)FsdvmU89M!GZe^@-MRredW{QJ|om(GT%mY1tceYgMA+;5F3msBHX z{#=+MwJ zt~f_q-FZK)PdoPi)zzQZ@|0J3KisF;x>#QG_Q5!>`TfUao9@T)Tz|@Y<>|+bJf8bA zwjJf%#r7&wMn`zl<{R~~_tx!d(C_#6YU~etyShK+$ih{&A7(E37QXY)HCDm@NcWb3 z)?)UruB?+$VPLQe#kH0WQr;skiP{>&Src;gs{Qw~v(59>oQ*hb5@vADRqeK378YlL4PqCUb+3(Gvvqzzt3b~2wQFyv1URml&y}}Uw>18BQ`Gmh zPOm4u|MPzD`?D%O4e`HgAK8AtSA5>~`<>!*w&j(~{WYcExnz+qZA;c>cRSpOdB2v8l${f0ovY z;}<3`i;pSHJo;f)*R9mU&rPm`U)Ph?&EQ!4`qgya_0dIzS1+|~4fSpdUlV6p+I@W8vAz7tE*ReOTYR^p7yldo8%dKHpB9S4W~F~7r$-h$3Tx{XLVW4Q%6k2V?$aK z&nyh~pQVz)kZxJw6LUYnwEuOfo+ke*d8<;bv>U>e>n!=E@E%W4+i76lU3@Z+N4%(H zR!H#1s;_^3y<(eY=KlFg+p4LH4?h2L=k8RYU19vYrWIU28Yw(e^`+bMLydEu*FITu zF<{>6hZP1dY<}H;yJyAed)se%ih2@{HuK=P%ZT)gJXKc^)y@D(t(f z`}La_qnEZg_n97f)cLE#;OP#H{@f!KdUjctGWJ=7Z!S7JN7(AvqH|9d>TTCcIr(N+ z?e4sJYFnpYb~rv^#p|{?j=#UGYmb<7`M1%d>9R{-2U^dYvHEe(sHAOqwgB@y_Xec5lyr3S+{FNlCLlGWA&m(kM~-xT(_yKdopXQTKL*k>!YGB285pM z(CnG9DKGP?R!Q{Pj?ETx&%TuLvynCIy}WbIGH$IIak0^+#b-~chJG#9o_57x|Du@V z_3F|)_ian840Zl`>7v5A=%_&B^^5jN%{6VGCp%GQcgf)ej{^neWBa9URSL?U?)tl{ zOZ$0vXh@js)I-9H%K5r9r=B*b+j6?#u=wW1ZtXF)ceU>}=*ik=U$?&YC}Hur1ZItx z|4ZK}{FQ$7>euyB5^OlFEhsj&q}J%VbadDphM%E?Ll#)m3T9CV69{?stKsvYmYr4j&HjpIaFh7JYHU zvUz{c&$}G3vE*n^S7_6&ovv?ZIi6p=^LC7%c)_Eq8qbQO4rD5-_xT@t)XA`?i%tG| z)APt@i!Q5c2OfOAH|grCRZ(%<4|e_f)%Df;b=cbHZK`u3M6zFg>iWeQ@g?Gh&hpUG zle4;{&b(FfJAGrx=~Yr{SM;MdZ(6mDk?X-SyT}z+g|oEJuYdIN`ni?+czPZ4rs%QD zowf|!9JeZ4`dR3eHHrUNudWsh+w=4oi}+?;FykZuj zyLy%Ql=%nGKIoR_p5~ZwI<>H)gwOiT@tyDWWizj@PmASxHP7*EY+U^Iu$iH29)uju zTD@{rESn(9T7Ym$}M?U}OLW`@7qfhQrR22Hx=eC@(Wcp2Q)f5N{i67-Cz(aJ@0vS*q@{bS z^5)$oe>o1YJ#v+a*t(4Im59i-rJNB?IeF_gNpH&CX^>tSYa4b!bxwsvqw9vMvy-mg zI>~ZE>Q&5q;cSbI=^_eQrYUdE7L^^UkS^Mnbt1x6Lolm)TWTej*p5Zkt9PwhR+RNN z>zPClN9v5cO-5UHm95MQ@d??vJN%rO#+r?@RxDo^7qxv+)~8>$x~5*8HTjWE?%7kJ zHxFkqYpGq{-Bp=sky0@?XBW?yj_1wYa4CZkOrmXKU8xMeMSv_A}PHwzkYY zXx)OSYE7QkTdEG7S+(`jv{kN|=Wer9l)vjZA-B6dXHueVb;i+}yuZIZMSD)a3%If{ zHOcqxCDC=qtlw#@Q)>+MnffV$d6m|+qDL2$^fs?-_wCO4HOn`C`^rPhmYS{Ikh#=% ztJ&3?B|ClZZ+RJQW_s&O?(BWZm&H{3XZ|vq;jfuP zIa`K%!#l1E_ZaRf(t|vEDoGxP-gUF)?k-lHsE{U)8NSv%y5p$f;~a&z)ptE zjOUmwr?I7{Oj(d*J-xEi=0qPaXQgZKdjC?sU`K>6vt<)B&D` zoeZZLq!_L-8Z^&#s1csaWx$>wc|fY6l;KPr_gCJ_VHa{;Ux!H~XiLp%$Y#ou?&S(L zUUhbbS@ROfu)BS)%0jj~?>4_InfOc9?YHRB!&BU{XX)`?*44P%q?q4&D}Qmsj6C*t zWhX8*wTbcEv zU&#mh0go6r{L(&F(ZtsF(sRnZMRAE$QcKe>{9^OvzpNO-pZuZz()s2^+kXTGxVV-E zd=b=UxW?k@vxd8ChXh}N(Sel=sSJx5k1?%bNO+XAQ6@q6fO+$x^(z=7xC>Yvk^{a7 zi!-lby}|H7x8Xg*9|etH&5rdOUg_(x|40nrVaQ`x#eC!{!*zxjP}r$8oOLX0vJh0@ z3}6c2GT=#IIpEO{$grGY4qJiM0ZE3Z49N_~80T;r$R#ixaB1*ln9R70VGe77*#S$2 zV8&&PF2`JgIYr_gE{jbp$BFSw;A6t{NOz> zm%*B$j{O4D1u2o{#IA!$z8)f7ii&|Mb#g*?k6xSbLeY;gfhu#5ShW&h<^A^0; z3}d>&^v6u4s&kiswKLO$n1;`cVvIW68=L2Fe~4!gXNuwea9{D;bVUYd#x#Z)rVrW; zCI@sGwzu5~eZ~;OV8EYXcYvv(m0>!g33CqrMrFgx`?$`qMeuysD)9TXK*Rd>8^4?x zmNADg=rBge3Nb65=1fvMpwsY~;Th8shG|S8Od*UhEE~8M$Sq(@usvYTu#V-2*@5p2 z@3=Z0A020~U-&OZ-os_FMw7%5RpyIrpOub?&NP2H=W+X=Z#!e3XLhQ)aL&BHu9%N= z62Cj^%wnGjKkr)o-*~QmQE?xaQN7ReaBn-dM~!FrGwkZG{!lOdr##nw?dNopyNa>) zYo7jCeoj2w=6#;g{($H9mn+=A`@j4p6ubV)SFPCn9@nS8{8hTEZtcAD{-yh3PM@E> z@QnVosy}VZp6?BK7XG4U@wxR^-iDv7QCaS4GLxltuE!TO`&J8n^)4ps-pxY0UFWx2 z_fC%DwZ5rT_I~-EltrO${EF`>eN&0zm48(&bTX_^Loi>oz1vPd*SIQo(VruS#Ma%E zmYnWk^sXoD<9QdOtW%fv3k7Ztt(n*HEBdH-V77d3XH@m}fXJrJVHf9lZ0Y!ByD+S| zCe=~BBg)uSSa;2#1fwj=gA;R)z1(b(wCr}gzr{B-t{<1KoaHPy=rj4G&;NHUW|MoD zY2Nw$Irf;+|J2lj6-VxE=v;Fwq52T#HYr7ikBa9D8O|3vt*)4zc|UFE{0@hA>I%nh z=N@a4z4V%;)?x9~X&GEUfAC+F^!a$GZr+;JY9E=N{hc{8OLYE1-JS8LrX7Fk>^ZM$ zwxQCw%zdXnS-x5usix5&FA#9Q=hV@>i`9x%zbali{V0?A`|ir$c}qEW-EY0aZ!cvL zUp_l>mgzM9z%f-f@H;&fG zY`t^Qeuxz26SzbFbWF50TqfRf~>LC4f z_vFfXYOhYtIrIAbgBpYAtQTsp=B4Q6Z|RyF;hDcg^H8bsf>&+_zD^Tg(f{tA?={Pv zzWSE~uBOe&+Xhj?w9G zu>z)#X{+>0nUvoPpHq`MAi~;rHKTJ;O=fJ~kLH3~J(tbSd0kF%4d2rFtD>Z9(TcM# z&atSc>=Iv4eQ|%t=LWHlDYFf;9%biqmsSSDR>`);YdwB3E*l z@anSd8c+CKzaPsBPLo(uzc;x^>c~qYzYTvM&#?=xdcKfr<=(Rzr8?#kJ9g>&IJjE( zW(MuxcX%~Jj&VxJlnI{;Jnwm5&KJnH>sfAdZT|6Kz0d0;mS@g=6*}|k>PxSuE^F-* zmAvmGsLA-)OVZp`IBjBYNXp%dLXz(zG+l4T2FiWXh|G|8E3aL5^~JHr(v>o*y*3KX zeVwh@EPO)hr z6_XY=rZzNAFQ35Z-2cv`B!<71>GSQ)Qw?5Kr)9-#w5~FA(?9lN;vuz_dDh3|0uOn; zc@{S5Ufjo-+KmzmRRo&*CMaBOWqH1VWxH8oY)ARVgQ8rMtYyL#LsnOC6~A4uL?)?e zXT5fBmupp?s=ClK@} z>P(5x(lrT(3>WGi_`alPsqUlm6K+m@ta|U|R8ik)P0XgU?{wTX{XCaX`8`#qK4jCE zZ+{|NT{MsNtvIC>NhC z-IuXNIlgeUV)SvDo1*MG^K;%zU;KJ{<|p@kUW)H3xHvspSz=lr7+i2vejgYV{Y>|o z$Ar(~MHxpPO3shUb6qP|xIkw?$Y!hMSvo6c{j^{dbF7xDZ6*FWkem(_RE zN|2qHcszX5?@f!ORt0{?P3m92v{QfCcU@JcF%A;_xyeFrhZ&wJIyeVAKc8SvkCe_Jq zc55a~_WB-JmwBxw;i1#7t7Wf_r3ZZ0+HB+-c4m)Pu2-Av#;0a89!4vPE_XNAzIkf$ z)2d6-k;~Jz9ZFexC(dTW+y??%98TU!Y_Dowmi?ROS+SA4o~wveh4IV}hr~C<<{ncK z`{ldufNkO?r8BaplD4lI2U>R0zghfo)<^3}hHpOl@f~}wactAfnNHivXB^)w@o27z zZ|j;gVc!eCE=b?_z4lRNW}^SB7Jj$y2X{YxQ7J32Q>%J%{{Ae%cNb^>nmboKadzP+ z9@86h`Yx&kJ-NJ_O@H_08?*l<#&>Z~GI~xPUAg_M+{Sai&8#DT zmuYplue;$_Q+e-3Pwln4T^Hv*?OeS%q5SHF72jVh5r1yCNHJ=I+c&Q}a@R|(c0Zi+ zHvDdWad^)M){@|a(A0#`*@iiFE8jf(;k#4niI?O#W!~q(dh#b1Kf9OK^~o%;_spWl z+zz|`CNw>M;rBQuJoNnq9o>uHBbDxGm_M0tB1GtJok-`ELq$H}i!SZ;lG*=QKQ*&} zbECJ+e}e>X+5abWKDpU*f2&c7e04JQL36{j*KE0mvZvhLxB89Q?ajB}YOPpxbov{C zsM^O}-W6{cWs-6uz5&PQq zPI<<3J+Lk&DX#46hmZd+Tc7#p`}e&`SpPphf7#_*rdJy8YFHWn`UAr^DUnZFBA>Kw zEn2pxu>ZgX|5IG?ChjUm{vnY}uWu)HS1e^d9)93s{(~RpTV%zLPx6xyyzi$sG5u-7 zVfAe%Ok|>%C!U;nc!SMH#cwBtClNGVASlqK5eM7Iz@Y38ZEYl|UiTAU=>hG1&BJH{|dTi6SNC^71F8*^ma!taO zGC@CqK=rKM%kRjzZTooKxJbZ%k)O#6Ny`>Y=Bj2*3ixy|;#=Xz*m8HH_33*{AOFc(R#`mRW=o6$b93Oqo8HDZ``0~e z{m;R^s%SEI|H22)4=vV+i23ojj;l;ua9{MIAh`?gof!9UOuZRcueQ(i?2516R;AlD z(lSq*kysUV2e&Mg2oip?5TO{r6jPKTI8rRKs4z<)P zskrv|sg69qrR=o)>V;;>IZV>0D(xy&>UY}+@V;4GVshZ0K!)J?;=5vNA5}^;{Zu&2 znJz5qlB=0fuen02Kip}1NBj4t4=+VxPHfCu)*$>u*k2;XzDZLFTRR`hH)n}Va<=|dv?)7me+{l4V3xM$%_=WiG9@m>0w z{<0}6h|6#u|NZNWe(0>8uvz`I)5QmMrEk-|drg+*HM+J&=XCL>ITy?xoo2mPP@&D| zD5lQ;yTdBqD)IiEV+Ox}TI(t47rnpJBK+y(^5Yw7Rl-Zb&4w!GZ44ZM4g{FAwp z+LQ0fdFjT>Z=RPj3$|5Hv^{>3B_a3FccGfMhE18u%lNw8E_~gz`a$Zcqf_cX)&~9Z zZhftvb83;(4{wK`-7E7X*Yw8qoch6+{e7p&*XtJ74F2m!hw#4TuiCfOm3!O7rxn@U zwoeW}blRg^U{tgE>(2Z& z`7*ORciM-%OaD=@v+7y@ugY$%!``0^B9?2c?`-OAOZX~w{A@_poO~VS-HFXxx)1rL zn6kChSk!rZ-Bz&e=w|lsY2jylf5$Yj-#M?iD55rjqv-x4T@$B%t?M-t%I%NOo7(NT zc)ji2ItAmnm2&SR{$6@p@A#r%>ZLbPj(_@?KAlvWdtmL`&t{9a&-}D8VaFetuzk}e z$m{HE=~rdl=(f<_USVs;-e0es_uZD*m^L%{afHwO|L+5C5+(YrNDGId$_U-W{E6 zuTxDt|Mq5Co$XrQ75u3xY=eKE2W$1mOcniyGk&DRF!bn~d2i}IeJ0-K^sd9vYVUT< zarl&)y?g(9D^n<`FB+*6r`B+#RB9d*)=2tJL94o8>F#@SXhN zSu*2tX-CE!v!Y*4*89E}u3CCnT_eLlVd|`9XWeUhPG$XhHu3%zy=xlB=bt)s?rfuP z#8FLeW2>c?)Av5!u;%%LckXkeZ#Mo2QqSw^)$TLzmR_*`QR0Q%i8mOE^OkwS$^!-_@~JDFMrJyR{36UCGOz*#Mx;00(D0ScNxlt4MIB3qIKWbWuPf8S?>IQvk zJ&^UnB3XHHs`##n{yMDESIoP!zg&}kR-(2%bB^AHALlfWX0kpsR=7CzOx~&_^XBgK zm5*HX{KAf2-m$|&RPlyOsLX+*o~v$5?k)~7?C-uD@MeKc%%-lbmp%x63N1aB^X~A& z>sR*wbl5!k)9upxd_0prt=<~;t7FNZw6_gPEA2wH-@kntsr<5TVcGgX!9Q}Ly-%V~ zy*s%2M!WLC>9aNTnN;hi?ov+bzj(Y&H))NY=*Ld&`t2_^ZrJ_c?e(4FheT>mh9CR6 z(PQE=PuGIUHHDR0t-c?o*8Z5)C%j|fi3>ltPNaO!j@zt%`OmEDH5am%`FwWVev{+< z;!kfpcKFu2PkLy$k8}F&nm*OLuIB5v3x1nX|H$zCX@mbu&c0Yb%l_-Ew?3bj$9; z6R;vh!{NGHqNk0WiHpkd^+qrBzn$do_K{`_T7OAjgfXT5;Oq(UCmYY3>Q|f+d8PV2 z;G+1dt4!yzP4}ET*rUfAF5ULhGAkBNEJ+!`N$*+ka5upcieHI^lzF%A-u~LUQQfDon&f0T2 zYjdWBrBADgEO%gCD<@>9(SBBb_u=S*$yb^-f4HpGcjDU5i64`@n{Hj0_|a7Q_(Sib z50f6R;(fLA<$SH*)9ctD9+mXgGXLq^y+c}JO^D6CHy>8Er|N{+ZJy;+Iae=J{Pp#z z-8)k*tuJZa>p4BH!25$htkIrHFE7?UnQHef>Tlf1PhM-5Gu2!-i}>99z?qxK7px%wZ!`@3OR#QsN;FHP5%Trl>%ak%!;RJ*TU z`5A{h&8;N)V%*h@1LOStW^t-Z&ztunT1@Ekl-?Q3g^s>`cly=8sIc$hnxL z%@4b&e$p&?LD2f!S?N*sSJ$rjbTGCmn&aP*7WGv}?KEG$H9X^*YZzo_`LXQlgg4Xg z-M;6;w?y@)XeYnokDM=IYC$~jwSvC*q?IXu^|GniCvG`s$#&J$t%VAbQ_j5c7Vy1! zuw$5Q8;Z(5njS;=WX6?UGsaBTISt~6oc zhD{n4J!R+bG{YU1`*5Y2K z`RMBT=T)5d`{z8+OHPYC!6}-zB=?SONQJBQ=EK`>2hPq^xUp<|)7n2zT`%;{e;!db zlb5MDZ}HLJzh?JNv-e$ZQMa%6o0jHR%V}wuxfu+V`e)ZL*jE--TZJjK?)t---q6~0 zL+9!jl@D{mXRUlX(cry&&DwwO>JL~Z?UtGTnPJl1mqE<8yk>+e-;34BdwDyfkmvQ? zjECYkin0g^rPcpJMDcGg@usz{)-N8A#Q!TvvR_)Z8A1yg`zN6*OH`k>2 zx35W&iJJRIchQ>}Intkhp7>M3=a~QBUSx{v!rMzrqRe*uEA;#paBb73M4z}YTb1P< zteH=^X0KIS_iE3zW2bk;U#+~_JE7F~{p9;4JMvartv}*%{9pV7b)WkCKKK19=3hGX zgx$zn?_c~2hKWCKsPFfhHpT62>@l79!q*n>+r+foA@|<*H@U}tPps0@l!%-se)ci9 z(!M8uRWGgYtkvy5EU=0_f_d#Dhh)b2jB-y38Xun#`x@?jCaiG1O}SU&hr^C?F9qcS zr^d(}aYZvkN)ckXH%Qej#I`zxHaOpG7xj99k+d%8R%ct+Al^y&g+qRsJ*|FV! zc8j53#&pBOoKEYj=I%P{w<0L$NARR&7rB<+j(*CsE{y%q#9wR7V~;CVKFEIddvdZN zzp2#ciM3}-exJ5T(R}IG`>D)siH*$D;>7(KGu}7MIIF+H`n<`Rt|v3i#5PRd!+)`S z+ViEi<-J{)*S4)SiD@&xQFJb6k9?f{(;a@5nrVtD;?P|HNlmpBiWFN!R=I zI_8(J-0zFC>UMbFw|18ATyuLy$IoCr<#iEr+3vY~Hd$<8e8@UaMX=lb&FS@Xxy?Ri zKY#Z@<9YU})?d#xjk_LC{q%H3|KvC6j~{W`d71_5ryRXq=<89W*;;sF#qZe4UU&?N`sApU*OcH3&`oCyu&Y zzkW;j7r_L;|5xFYWmVU!`Q)7#k*P0s#ofJY?r=EQ0 zQbjpGSdBsJY|dk$^`Tk0g>LE;{h2y9xFl$w=b4=+dKlOz?3h=Mk4Ap+dPrJHx3T zI(Q7{o)EG;r2fgJF+({`;O!BgBr(I;Gn`|bL^^l6up9Qz@YhkW>TErtlw`QECBiGl zOGkN{aIA~@#-<3*HA-Q^wnwCrbT{@z_{X@#_{BKJc*eNqD3}S&?pWQK-I3jS_DJaw zu_Wz6+d}3-cDCDW;DVGWD?zr7)-C^B%_lVphy+?UT`WyRi_{%7~ zQ+%iTPWhcWpAf$QzYxD5zi_`mzfiwmzp%f6zmUJ6zwmj1^FrqZ&kO4d=nJ)XOz)W9 zdH9I-k<~}Cj|e_0eZ>37_mS?S+()vHVjtN$JiqwD`A8>=;RFdkA5}BP+KQH>TRRO! zoTHX%^+`lt*!qKGwrA3*KdIikrhSQ7k$pAz&;bLP^BymwdJ~`2=x4u*nmf@)toPZR z`KFWht@|4OYS+B{AKRY4ii%G&Nq-yC9a%NY$T-qC^M~3kPlLlHpV#gRJAGkO?BR=B ztB!4ZJv;KWN1J}8>79@O@2xK%v9j8{c0ZZ&mTBJ9th7z@^3|%l(>|T;o2%p5XZw2U znzMPUr$7HBX}mg{IrPpuwaby`x2A8J`YUMKe6!HlrxF`yZ}z>e|8-$PN^g$$x+}Mv zo`zzo_X!!HOC{Hs#%w-&BCWUVT-voSpChNI&U`#0uY3M6FNvMk3LTB+ zO^%=SDeJKN^;-fa?WZrs)LqL-|73Y(tzX;uNe?&QO1b6Zw|LRnl55L#Pm)tbSx$6D`f9pHPt4!i zv@zmg#F|eLYc7G&y^Rr1H?|}S&*o}NWz}6d<#Gn=;mub*JPI?=T5Y4HEi-kg+tdwR zZfeQ9Rs|nTxpjKQ0j{#P&?D0VBrO+TO|jNWaF6o2zsa}#H(TP4*2lW@-Y2Rr@7{a< zwGs@~!j%OV#eVig zyV%_qI@Wih+-2YDT?xBqY}T@#GdW@M&8*2^w;DN}~m zS)BG;-+i5Ub@l3)&%aJ|9naA}J)JH37T@-gJLi_pRXADXb%-}_HE&$%^U%6G^A6tH z*j>D2@wIz_+3!D89GjbPb#B2`2>FTfF*dwVL?7f}Nk*6~ccB94XFayY>3H^p48Y#t%QsZmrvR^Rvw51mTX` zKUrNT_()5*9MW&QUfLV46I&5#xBv925QC!?Q@pDqQ+UtqHgoIUd$d38qW^81`>lqr zRyQos^f<Eor|ps0+OM*|{N_waDZl<|Uf;TTPRBQy&b?|ScehJEUOt2OmiU*&rm_3u zAM0;8{vclD-;Qew;?@5I{t$b3{NQ#jd%3#3AA}#6bJz>kE&ee1fjOr<-~PxSCtsWw zd}#P%^`XZfuD66Aju*ct{%_KUgCERW<+2^%j?#DsQa+G_4>i}f^q!o*T>lJt$+IKr90>PCjQm3aAnf=4~O z%dcoM75V$7fEgnQa@C$Un6v{eCCJG57asA+cUg4}JcSd5HT%>!I5Z)H&xfp6ASOU#_+Pa#i3DogKL%W9Y+57W{tRL13?i2W@{;>POeZhSa{~AB2 ze#qyJ@A@vQ{3qy#!Nb!Jj&sNJo#%eW9EX z)eoL?#WUM`|A_i=^U(E!=LPr4*@gW`{or{>zujM?jhE7UO8^nB?3 zp!RU~gVICm+qQGCNr&yyMBVK3w?DzCT`3?K^|F}(w zSN@|`VgJzla5(pV)_dVU%zkJ+y#3Jgq4k5d577_L9?Ea_7u_dZH}OO4htxy)?fT+% z?8iP^J}f_EuU}*S=k%fP2li|J(c4nq_+j@$@0Rzi`l9>z>sEgF`{Cjt{sYpj_nYIV zalX}j%emyf(>a~5`{jK<$6YMna-8qwciZLn4;!pIRLiqEWXJTxtz7TZA{HO|%Ok91 zbD$t0;L*MGkj1yuQ5%jM>-d=axEUB0NZ{H*9a32UJ#=Ai1b0mIRmuN;?|kzn#eJAG zDN5?luCv!BELk9WbBcTG1$I*hT@Uw}+KfRRZ7h=ma+c5ayYtMfwAd=VblWxGn727m zxd~P?-#snS|9&a=-OGD+tGAxp>HDqr`MehHmRI@qbm73LY2dlPSE-k0LL93sDl1 zcKB!{a$J~s>c@NChig|}EXYsQunf7mvDhN!@ZZv+JEt7JO(|#JG5ej(y;zmF3;m~s zXV_OhKJo4CLzb-R6-<>^OifaYD;?e!M27hnmoL~kQz`1$=YhPb9Ep-Z+@_bR$u7V&P-E3>KEvAYWuk9$z zWw<;0w)vLaWj8DJ_uRTwek`c@VaO7GbG+z@x7lT-r^xP z6EA7TX3O%ZvVY|YQQG+LLu&5rjoXwAjk;8%g*q?z>74W^3RW@_@pLV_u*BH)>WsU4 zotk^zKTdLf`p~THmx05XV{@vGd^zti>j z`P|!|`%l?En{)0*j%jm#FDW;hcGBs)s#vCpjI;-DlBWCPnoEDu`HJUo@Vh(Ldd!?$ zb)q|{&}+ttJ*J}P+4SBYSg|G`xpi&WjkH!d?JrS^IjxM^F^R3xYYG;N=7l}*VqF*X zU@=$B+5_nV+zjOZ;FpM-9vK6(1B|1Sz_7*;)9u z)-7UN=+w$l5xPSnMk|KtbV$dcnOZ!8)-20c3p5LfJ_=ZMXla3%<5n)uOYa(h^m1Gf!}M=YOq$PM09t(ukxRYe~nE7o((Z!TY1AiN`|K)vQJ+i%WyN}Cb{~tq$`96q`5blG?bQmo z?aT%C2fuU7Q~V)&@IB|hg!bx_PvR_`KIk^(3)i$i=r8-BWs}uOmw!Zq0cB3gmGa?kNs z&uTqB{M{4ZxcMO?^PeE6Jk@yX14i#z{@i|T#dUn|zr`PBU9T1KtC;&Ku;Q=5-@HR# z6J@&%{bUY%JUeTk=DR8Q(jk?p#vhZJd~fKhPk!KBu;R3OXy)DD2UguYc5uzj+DZ9Y zO}eN0eD=)!oPF_0Et{~g`A6SPlRU#_oojs|sJ8gfY1Ml?dv>yKtL#5;{~g;?jy-eS zKFQCinR+7K@~6zi`bSSbyZoCuSzq91x!*pgC-D|Pl_u6ded7I6xj^Ub+>~>Xj%&mm zMUwp;BQ7R=)ahPjBj(L3R>98MExjQqvwq#P&MB{#RK>rX(HRw~Pwnd0T3e$ZGUY^GyymkVdqq}Wt6u$hpzi%-=7V!$2j_MkJQ)%sF-P~sY^VRa`!sj%zgH`5@h5M7 z^;ebRt^J;tA74KH=jOx3JJ;$R4)*;0;z9Q3%B`Px_NacB;L0-bUuJZsFK)9~MId+g z?hDea#oaO5*DE!}C4c1aojob!ov!Mw)O}~xmOeVQq)?+Q+qe4QvukEuQ%{A|$9 zZ*q23>hCx7Nm)F*>wC$&<*VmqNJsVh?@uZSHaoWLc;ZUOB>;d1^wz=ipy|M048SA#$t$(a8?myYj^x*cw%l^_&if@~;Ri*Uh zsc*9_VOjBQ<)xJ$R-Sp;lEOVZOE&nW*4ve{B0m<+J?^9H7?*Ak?BuB)yLn;AyM>qM z%Gw@3@UHZv?b0IqnFR-x)}OY1`s#cv2M@E3;O{7vvU_ii9XZ z+TONkOY1-V>l$9hV|OE|NBa1L(~I74=&VvTEXMDT-CVzqHopt3q zW-@HOajm0Q`8V&4bH}fBy=BUgy|L{=o7K0avhg0)dA`=)mdI9L?$ci;yM4KA`4ZXf ziCpjA9^&vjYjB)pa+8Qp>`U1NKV`P3%&0s)gY*3P)%$pYFMM(c*GscmC;UNvZP4ly z1|FMp-W=X}V|wzNEoUCt9Nu)T`A5?^`^2M@eNIR9JiBCeJSp;;(xlrxmSV0qukb%j z(h@G;-YjN&(8qCoev{~jqlLG#UuJvVF#Aw0^<l_M_`Il4|)AU)|le^6w1!?^*VI z&%R$aw7(~};`5{<^NZ6Lt#^31j(_gOE72OF{Rf5QmmWS?mhG*-e_~tTVX5^yeuw?o zqR?4$Y)$Hn)NcRP7k#^*uiW(hiNPE_wHU2aK1bbO@UC@Qy`)ws%-M2vrR2wl$pIFV z1*L0ixo(?2>a*tloXx8@LCk)CIk#(+%>3h0Miz(l!yc}Qv{?Ok`*-W2WzTP{zkjgR zICEL{Tb_sCB1={${GYP^=z013g7)<_xlzS`En5Ek`MEW7?un8gKbdnbZ&TWQ@BI9P z+1EPG?5z98aeE7c(ds{cB)4V0J)!gcbNh{Driz*V#*UmmZd%!;W*z(}G+Te+`i%Ubh>$O-J7>k~*_n2I-K4c-mPXUdyI+>2ysl^2ziY-0`7f_m zU0WLZVCjoXx4t!=Udn&9!cpwqwKcm7He4z$nD%GN-C1f+4!thZ5RWTg7|#98aQ*u{ zgVXG_{f8DtAJmckV%y|z6uMRZcHE>7Lfh|h`i5-RsI}F$4eYe*>l?_sjbj{y6(02-QNdS zihjI0HS85 zP&+;#u_OaB0^AxL9W0zKQMWJ0%<#2RwrT}O;D)Per=2oHUp2Z-7BDqsQJbT7*dlSF z>cK`|wVYcI_bK9Ep)p!2> z`}O>-JVTqI?nH*>1P%iarXZzAfft@C-Tr6f&tBP;c|FM3FZ=AeyJh;%S(7^Nv~R!p z?bWKQN`3}`9?xt76Y?HPykod;7PU1b`Df?X8$VA*`OTayz4FbpZzwbOK0gnQ~$g)GE3ol#6}ht z-^C(cOKWyoMbA7Z-0x}{ywCpmM?YPWF5#U%m5XN0ou%!|!6LRmTaH2cAJ!R++?PSv!06R?tXTK_0W>@7Gc2)ZeKTA8&!RHlcvY-`hA+^OTNdn z->kCVSE#k`-SrpNd6SvM?e-eKT7R=_hEBnu%!;3fB{&`zNyN7ms`jkb@>4!@;pl>u z7X86I&qHDkaJEg>N_r7^vAv_KdqQYT)1pQ5A8MQJZ(F6*7{J1|FIA#9p<}CJkCsPL zL}Q;}TQnEj#H1a^3g#xpEbI_DY|gV?luJ7CN8dxX!@j?ocDC((Ib)k~{<`hDJQK`4 z*DJWpt!tFy@cDRHp;GRma#_Qh&F(p!Z|*6VO_-4O^!ZWeU6Kc@6^=<>7caOUyM14K zkKGTp@-O?$cl^G^E3k)m{*BjS>>{<(Eq3pye!KMi!qlI0?6kxcR>~GViKx}w@kM^~ z|B7WbCwdh6^5$1M_~iY}|Lec%uH!rL6OTl-AOAMc+GuMt!(<6xUBrY-A8)L$Sa*EB z*P`Y(5e8CM#Nrp6p73JVh7HWKWe&gL_|@nB^zI*VN*bcJixd7V-IlRV>8xo)=gF_OK0Z~(x`iK4-oI;L^QGnY)n6}Pl&rh#DSa!6 zy>eF4g$r%Axd$$k_*-We^aZhhKM}yQ_Eg^Xr@4!pf5&l$w@Wl$w{E3dwQ3Q(p(&4ih*YZYcg(VnT0k?;M9&(T&RrtM*S=Ht}h4K$1%M zB;FdjM z;^n5zz3x8msqJT`^Y1INg?bd*zN}6C>+~b{i5`pJo%`RvA7bJ8pbCaBABzLB}i5r~e?9TMbzbIkZ<#JW$ znDfWTWy%LKqu92!{;oLf{rp(Ft-nN$cT>;NoXwdJmGZUc6qxB0bN2@o3EueZRCeiO z-=V+j*8kppk8>MC^47e97jhEG*F`Y~qBafq}YCHtio1ZJl;_BrcRu+qPG#brBXZC5rRcj~`p(zoP5AB|T;cjZ zcLT%e0{;JX^A_xGxz75->BIC3`?Y_t{1QFbzwm$1hsQ5YH_dnWzw?3m#d@V5D!=+! z>|6h7A6$N+UgO8<7w@_4djG9^u>4}Z!Vk$W*-i6Z|Id4n`eirFDXSHwt zmwfQ~h5Ldva(~mC<{y`rW3P=fXj|U=%dR%!*IrhSwfh?1UcDs0;j47*vs+Q^~$&YubS(){=`Y+ zKaVq*uTNOc`(#Sa$C6&QRBx}II+2U2e&@d()b`4e{>AYjtLE4*jsiozJU82b4Z&BM zXRg?=eGZG3PNClQ`a_J9R_{MNE#}Whalb1{iw<<2c1aQUOa0ldc*M=2HP}+paVm#a zk;dJ2k)}GiAb$N#_pU1|xW@SJ<_PIsVAH;g`sPcAir5DCrze%q@(EJQlsx4AbA|1^YrFx$N!KS>aGEYzqH=I)ilf@ME2U3+ z7H^!pjXNylk@$uNUmcZ{&a~zbo}`0aqV-Lz+A2r*6B>7ZKfA+FfN^5!`P%Pyitq1! zf3D}xWBL2F{10r*R9u8kE^6WKd{T6H{W9t3?RSe__wAlqHtY7ww6(7+*KT{i>^j@( zptX_NrhnFG=y=ZX6zPl64EF7eHH@4f;49L7=a9D zKI>uU;@xM?{oGfq|Ke|p!n3){q>m&k>0OzdR=n@xH?3x&gDjH-9d(NGlp^jujl3ci zH^E-@#L~9?Z?DYU`RvEJZMPqt^Y03|_&KIN9DDm7H@XF_ za8rF({#sFN-7VXgleZ_zP5TnVUcN~3=CyR*o$cmE7pLT&C_38lE#S>VS!J#KplQY4 zl1Br+JhknaeEWv;oI6$Q$y0p37JQA~quKI3wR4NPQJ3BAgDw+d#82k^PuDtG{%d~8 zLKn8(uR8g}R%-N2s%~CXet1#*j)lHTUQGs1UY;*3JP^z6x}VMTI={+8z3hb#wjcC4 z#%_F2L@6onc%|Az5zh7Oi!AmBJ$xW{;X%@q?>e9NspL)jKgm>fQ{^V!O?MSv^ZYz_ z|3|&?wVE1_zY6~@%zKx3^Uijks*3!;^}nahGyU0la$D-BH!lkJo)5lhZhXOWLpMB_)0Qwq@Si z3%lAyjcr`dt%$ApBA;61w(-nDyLBDm5?_w8>|Js~Z{l3;+Ic(ma$e0lSGE53|AR(P zedP}D?y6XKyWS)Cg1$Z1{uketA363z7*UddPKUX~9g{1~#K7=?g@M6{*wP>@vpBOP zA66VpiuD(E6luHvlAU|0?v{q2)e0*vOzXdzePziS?@KKViP?*%UpC5{mXkjFrtYcr z5A|QPDqeJX|2Od1%(vI{#P7E3ol$)5_nh@J-^|}{U&nZ(ecrS*ciHM!6_efAJe0Ww z_N=+uZ=3i!?)#*#P3PCF4&J~1ci-7e0}-F?$KsBEoO?i+o#kcJUgr<1((ddwQqHzI zwPi}=O4;~J+N*X?xxGu2qy09!*Zj)21?m;v4m>lnE5v&ojP19aom@UmB${RCwa%+~ z^CX_V6m+~+Ri8HR^wt*#6mQJglCx89^UB*I-!`1DNT_?c(eXjWoP@$jB~p3*8>;88 z77$rK<+?~y-1hbCMF*WUWP;}RK58{Ei+T0C{oaK59&e}ADMr#<%aWc>w7yqxB;5Jq z3E|pTT2FhpZlyC$emvdj%A+}*5r>nOoSNdl<+xhrL;+nRsc-wEYwj1T<)3`qWh!HE zG1!>HYvH07KYSlYwR5$X#V$*XJlxgBUioC<(~T`hEw5ZSk;wg3W?SI&^{Ewi_%-;g zuE=gW)Ar={r}JkPG_i_2$=|8Jc*+*hDanOi$CgISIkqWF|L>$LpH7_Itg+Z_2KO4% zyp7?y>3IRGrfoK#@xOCz|MnYy@2;pje$ISF{{zlvi+4HQ2+c|kIq1{UbxEjII$rsk zNu#ZI!fVbP<;GmK4aYfiM2j!4`=0dcth`!Djmh4eT&pTR-L@04CeqT=b{Vg+TFdhv zt>U?K;pl%xW(Ed7QYs$gQ;DVqzxERj6quLGty`+OG-hiDQ`c?QsDLMYTrR>wbrOoI z8A&#}?YoxT&D|C~#l6vfUz1vlkZ%ox{3(m#Zza2>^}HsZn>qLAzI;8)=ilec?`Kfp zUY(f17_cDvbdKBx=V{u;o2T3^$~ml~t7Xuccv|o2nnR5GR`V+rk7rGdIjb@A%*xv~ z+pm83cfv&sY`l1<;$m3O@VYP9<5wKY~>=V!RZc=)iy1?oNB z72mbzmBh4{&wjgp*qRz1F;gjj##ybHl1r*V(#uZf&QEZ16WnyZYY*pS#+x0{XM#U} z|2^&To@Mr%Lf04V_AnKBAtHEc-L0int+V^=r=@Q@$dxMBI#1(^+d}_VYXL>!U`5Y3 zAt9|2C+?YMqCq0wj_q8rDZ3WNw$^cMZRCqI@;_;#Y%I&pd+fxM6F!qF<=T0AjvHD# zrg61hNc*tRH9%N<2e{`vsz%S3kP0&~lO4ojonw|1xK=ZKGXd zUyV!2hcMOB|2c~5& z1`!4Z4h{xTGjK|;-wPc^1_lKt1_luZ7HESIM)|siIO=-(x#{~l`gyv!28ZbRy8S!F zz<_QFNIk4EiE2g?BLhQzQMx`p9eg0|aNGvfk)M>6np{$znuJ%c5L_RKu?0hKVsS2t z{v(m)E`lp2N!?;(V7SZ7z@Q1z2FK5^>UYe`%P&bR$;{6~skM;31Txl__x;=XYzz#W z_!$^9;6{QN>@1K#KzIq{EQGPhB^0`;w-;y}eaytbz{$qIpa?Pyj^A)$HMOLqC^MPf?&mHtOg?P{zLaI`kfmP zV?gAybZka>Al<-$Zq_5T+Z8~jK(I#^ZnL0`S9HTfl=h>X>klyqL`vsiGYoOcKe|ci z=a_@cfZ)=6Y$o|6!cR3vHwyj4UWhRu@>&Ttqaf!w;!Pa=Xoo_A41?ffRk)4BmpafF zfz?e1^U%5b+=Nkuel zio&5}hZzr3v{$+qFMOV@C$=<^BVJNepj}#F=B0Y>n_XPzf)cbG`nLvVRQv6n(!cHH z6!j@@zGbXXJ|gpD!%7pMhNCeoFX9s17Cj*_C(RJ__0aBEAAe(CC|G#q?U()K}U{( zL5~Rijv#}oQj7GGa}tY-*M@V}1b>zN|8Ms6?~}fm`Uc5;(8)NsdWo*+g(k-d$p(P} z>DnGMC!6?YF7?ftRAh0ypsf8|@^tq?rtr*RRjbkdB`7x8~E|`2DZ{@4mO+U$*{@LBq_Xl?{taj&U_|E;SB|6Tc_3E{E^9 zSyWO?pIOr`mDz0EXcSi-YclDU(Aw2J>d{)kof%t|E8m3fwS zxS8tA<#Wo5HR58X`=}n8c%>`CsH}3&ywK@W*PZaSKNO%Iynf^HsIrS)F?&oqbG;9* z^msh!Sw+g^kYha7MaDeA-~1+~x@Oes-ro0ZuCb!GnRlEC-^oaw!^y{$P90NB^4+oX zY*2;Y%M)uPO(*tT~>Rr`H4@LCYpX0J85P0^Tg_FIoVHLR!j8OY-!r`@_Db* z{L>RxFIsu5V(-*RPmW~YS9|^Abai2--r?ixY_xA&s(-ATm6yqV+~2Ni@k#Bg|N9%( z%$;@2rN(>5x|^TA?h2k@H7z~tNYF)>Y$Y#^)c9$^-%b_8Zi(bN8yPcw+tw|7mJ^o5 zP7J=a;X~j4O19&=L1in;S9#8Tvi#QeQ%e#jt&A1=S(!YUzu)EQ4V{~FH!M{5!C4C%HcfGIlg*1%67-+a60GWLcxuTuk5!s-%2ww$t(};mtv~6C{`A~l zw_8swTC}lB$~&m{;-ro#YAafp7VqV(QMTW){L|LC)4Hc+=2Tuj z?w9C&QE0oS&ELdbE+LyCPq~l2R z_KN}XzHSv68GaK5x|Ow*{hapj`fmKCxKw3pV{dAy^kSFy$}w|p+_)yxyL5;7<;L0D zrrmb=X|i8MX7%=~m$O7~O5O8l;>t*@6`OfJDf(`fs?hvcN#CuiN!Mn&y?(V!`pwL} z*E3Fv7MSv7T{v;;#kH%vCGvhHRYiecEZV=W(byF0>s*+&?#ZTU*F-EAXJ}NiYd7dw z{&ChbDE#98%~ObN_KySWe7cWzx>QI%XIpyu!aLQ6DUVkbXj}ey!lD$eTn<{j?9)rWSS?o{crxe)x4xr`nD4O?mViI0f{Eu8HVwkKmGP9a0m z;8Ione0QVeoPvc{T8+fDVs;#8%{Do6)xh6*ZQEC=d#fMTO0B!P!O=Owxw~yf+m5y{ z-s{;O86H_4nHiTiL>?C7jW*4>T+rP1J$pysVfKRoLe@T=Vv}KlGXw)VtFv%fR(`sDb3Nml4f3t*#lx^nNZ~(;VzB zE9`z_L6w>LsS6EGZ1RU^@}wKiS)8mq-|SQE?0^Fgt`^FElJxkaCh|Jtn2zl2!gYQ8 zhc0Yn4z<~v54b00(wpXRAAf9yDI`Z2+_ ze8ZaQ4<*~?8~&O1(6!Cp_|Mhe+8=AB**~PV#kEbh{lT;E*}>R~8_m0QUnHI|_;a+m zy5fCY+xbVUdE(P8j(ypY{V6|0 z53jdPxhgKP?}Wp)H^-k#@)_Bj`oMCyT%zvG2jRB+hJPkL^p~?~xnJ?ZsNdj=;U5!u z*%xURr#{rS*&FPc(m(eH*J17%Znxey9jGl(K9I?x(Er(~Z%RVZk|&duRX50J9w}Pl zGC?`DRVII84Xa8%+c`C!OIb>9pI>~z`K`-g!y5Mt_Jj3I+yzo|1$u9>i}Ogm*MEO( z{~y1c_YQ#2i-~MhsuLboi-wI|;;EVBJ@L0p{s`8v~Sq;ad zWM2sWEc&(MlCsUUGnbX`IJ);=kmX)JY03X@ovuwnhUY%7tW9#1>wDBFkR9r~G`gcl zV`b4T?KuyoW?8H@aW=l9cV;2;@`Y#lcKTd5lxQzFzbnpA^U0=dzTsP68cnq;43xXJ zEnBJf(aSRShilsl66PwjuJ4X3eE5_(=9J(=P-jYVA$3`h01>xb?*>tv&rR=bTy8n-%D^50unJ!96D ziEZbv>~dYWGOBEu|D9(Gv{$`Zqc`Q!g!~noe;eMMSIT|g@JTdx;N5kXYUe$ju*Yw| zxlxjvhi&H4nL3_}pYE3zJt}Fauly{E+xSUQ$WF^=cGthnoe}hHg@x`D!_dQb--(?` zu2QL-y7*Lbh0pbedO9cUN+svr6l=7d`{+%}L|4T%m&^EP{#bHwR^0nTPbRXw6xL^v zUFNmX!a5>so?!Z=nftsiW)`qtzZkP}{nDD5kxw6nsDvuM{$-LVWppl3*@<<|G`5*F zZYvJxoak5?8YkzsUdx25cHP|%mjiZ4%Sr8-J449C<`kRp{VS2HA3h9qn=|>`?d(-) zb#7Ea9OqR zV|UY!?meZSX&oqJ@@JXW6Cv?B3Nz747$2KiQ7#(>mJs zq#%T~he3ExTKLVFod!&0bKi1`dJ4K){q5A8)*{|{FnyEJr%Il0R`Z{z*!ufKgKg%WvurXqd#x*KD)>KD+UyX&?YmcA z@Y|_o*@z=?l^dcb_y$dS7@WnkKL1gk&$2|L<@2taPY*u-diMt5dyI>Cy?mvXZ=Mh< zJL7~?S?S}cI*OLXCr|EtZ(RHL#=`|ApJvM4m(P4jb7Ae3G`y&yK3#sJ<-Mjm^L^*lJr@1R z>J)b`v2E#EdPr9n(=nAzohM_HT<(GTEE4Z?2D+8|J1#6;=9HWEvE9K zPX#x+HKnIC7rE)Ghn#(<@$A{c8C^Obw1O9^DRFPzkn})#(y`)Yhi*RI61DTP*2_;^ zH&4hXhiOe-Ju@v|?Cfl2lRX=mwWlG z{avl)shstu(Lb`4TlbqSD>%2}(M`qWFW(%u`T5asZkf}>7RPVwY&X}g*{B+zWTRaB zRlr_n(iiW`?>YVoeX}y`i~p4SM~MA)tB3r2rFawPl?nx#A;14VULJVi#g?KIRlj1~ z&g;J3aiUHxa~ih}Q>*;XFs+N(Tm=S~4<>qv)UD#T+a%)o#Ioh@>#pTrx@Iyry->}+ zEa7r+)*YYVYuZ}u9xvn>W~a>1YyGdgp?{Cbr@(oiK0KIt-EMYP_IGy6t5=J(EhG!q zxz0NNzxu}Zow9xzCijlqThO5$rW){MYVr&P^X3rVgIP3B`IkmR`!5pnq9mz97 zF805iw6w|FO^7w){Ao_-=+ET^?@Au%{L>OYf3q{ma-o~q2i5hVW*4UKsrXuuziDFY zwu$DGC+10Qzu~rO+r%i>oiERHe7-*G)q;(p(>Cgvg{_q^xR#prcSeHNUk#I(KW8Q_ z+sSkNwju9-W<>uC)REyYYT3Pok%8e7GXsM;2^|^NisaOSlFa-(Xpbg1)?e6B#MYVn z?zY7qjZO#qUH{xz*DW2tQc<&oUHA$+4`=`8w_9{pZa(|&%A)!c>2evuzBQ%~-38xG zk>V0Dy*SMR zKXdQz6KUH$bCOE!=YQVuz3#o`^Lf?p=Kj9v{eM<1_W`p%iHBhFLf)l2Uza{;?>@uf zA8rsVHP2viF6ZrMGwg0GTk3Y}*YU|!8?WokX?mZfXTE%j;pr=1&CXtU^mt{&s|{9}}5{6~{{I=d(RFWT9!D|bR`>4K{AGaZp{zu3H5!aI4v zuPVM0mcQTSzOeECP~Gu|zwPT1QEv%N&g@-Hw)g(Mnc%{&m##1+dp-Y=$5kQS=B%}I zeG_LTWVT&ZS!k9JvCFe=cFAR*8A7j~mFG-;z$dXd%0?{nl25R7{-zhQPV1U&7w+D4 z>@IhF;%DpP{=L_prtVvJ{r=PUU)!yUz4Kmt{VxB4x3YF$Tz!1}w(B>|^CAxK66F?t z z$lUbYrj}U+N|86#aV*!lHMe7)()p{um@{?#q63AmuJw`o^)|(F=UkrK(I=Ak?wV@W zduE2u+kGvWsat&FW9^FP7V5f+z5Z;w&!x)1PX38_^z`>HyVOmLc3R$7{&qjx?XvQ& z#HU*Bhi)+mysZsU58nE7U&5h|bKDA>X9}53sq^>FIKzLW=i-sSRk!W#O8L2b%W`W` zz8{iiv-!s=!Mi<0i@Ji=OZ|#)3|_b_R&7e1vTT3wKOvh_zsv3(Qab(X-i`u|&Bjxn z@Gh0VE0h=cs8{RZGNTjgX2|tFNt}K9fX4sIfb2+t=V#3GGL$!dcx2W*k?F$OREL?o zCmwH`c6jOIurtz^j_;`vfAZ0M+QH&~pH@VRTmOj*>wB5zSeh4qsWmY^$}@QTj(;}Z z`;%3+=&;R8-*s+Ff1ac4wcFdD?(qs;ClI^K76F*i;#!vX@D*64?4*xc_!gZ&-gp#=4m~fCi&eT zOy(6oeEq|2v%JA?$<5(2{?56%`b^!yH`C9=l~3F*zv~0fzE0^Ce|c1Dcn?@jnv(vg z_exenvx&pE9ZMzu{MGppezIDDA&<$zBdpr&=`mHm$r9TG68rhL$F$gV-uK($^Z0Lg zu0)kFlis4cE^k#=G_UAhFYo?v`US!+e&i1UJCV@@-&c)0)7#O*C9my0z@hww)bw+da($-YV>Be&M(1ugl-2O%c0eYXX)&s$F*Nq~;va>jw)9 zKl~8LRlntF;^FeM$zSHbv&&++a;<2McN6D`ozG13^M1C)#m~6t7PHgr!jj|cAx+WKz8``~qlIj(A5f4H#8>dm(Q zg={a1iZ1+;-7b9B%_V$sa{r6dLEry#imrd0b^GZX{m{6#RW+(BWXj$veVKo3!}cre z1s88EUhZ|!_oTd8`<4&OmQ3$+VD6ut8Dq!2*hj2|wv9iN*5@wlKXyM(WPL|Zr2a!Q z&SYuMqdTO&)pyyYE?@R>IZw`NhW0{PMmR*!vy=n5@3FTWhUi$X!deP+a2g>1HpMrK)I^R%umMajr%jom6 zkBK`itxuo-nY`2T_sS-%F1h*;75OQL@B5xtJnr=8>7l&`_cNk4CRPMGcj_`TFle$d zFc=Wqmzza$jxrv)kXl%QNyMPFr~J zdB1VVuRooxe>ci$y;}M+;Ml4eOMeEo$-UDHf9Cvjr|aSbTf=8gRbL%7cJE2)xiz&? zI$dH{js4W+Z{7rc;{R0_|4rZf+plfi7sGNo93{0y&Q8jlo1P&xxy4mN=JC_rY|h0x zQ@ho++xHmh}lqUyXgtT8T2Z%_45RbP5Pw^;JV-oKJ; z{{=Q_wFzGGt}5J_73LhcH%shO)kndln@yavqh?>Y6jik6UY2{*>1UTtdgx5MW;C

    oD-P5$^{V%!>|EoY>|&a4pA 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 O2s6a6F)(C%f_MPIbJw2$ literal 0 HcmV?d00001 diff --git a/thirdparty/java-libraries/cobertura/asm-commons-5.0.1.jar b/thirdparty/java-libraries/cobertura/asm-commons-5.0.1.jar new file mode 100644 index 0000000000000000000000000000000000000000..b1f76966a7101b8099ea09069d83800c8eb8860c GIT binary patch literal 41718 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 literal 0 HcmV?d00001 diff --git a/thirdparty/java-libraries/cobertura/asm-tree-5.0.1.jar b/thirdparty/java-libraries/cobertura/asm-tree-5.0.1.jar new file mode 100644 index 0000000000000000000000000000000000000000..3b1a3461151cbaaaf5f799b1257162e6bd518f82 GIT binary patch literal 29038 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 E03C`($^ZZW literal 0 HcmV?d00001 diff --git a/thirdparty/java-libraries/cobertura/asm-util-5.0.1.jar b/thirdparty/java-libraries/cobertura/asm-util-5.0.1.jar new file mode 100644 index 0000000000000000000000000000000000000000..fb8d282291d3093e2b7fa27a344678cbbe652834 GIT binary patch literal 43307 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 E0Jlm{jQ{`u literal 0 HcmV?d00001 diff --git a/thirdparty/java-libraries/cobertura/cobertura-2.1.1-javadoc.jar b/thirdparty/java-libraries/cobertura/cobertura-2.1.1-javadoc.jar new file mode 100644 index 0000000000000000000000000000000000000000..8f1b58ab3dae7afe2581c1ff51e15ed3d58ba916 GIT binary patch literal 1062062 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

    q!q55F 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 ^R4C#d8+(81 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�PV4R;O?6GC_-@3?`Gs;abuTMU18Mw)E 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�YomVvylulUi$ME>6Z>CejxMKy*JUG<$ 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

    TkQPe z{=xbG&d48Q*7&mA(V}Hi)jF~D=kLvNkZClZ=QQ`*!WzD(m$yItbbE8LK`#HPqnD&7 z{JFg?ongA)!g}B4nSN(1l1!4i%FP$DuoW#4cC$BB{J_d{tZq`!viQocF9+j&I{K}yCNiU_Zes2vZuSBUMla|wsx|jR7=Cl5}VzRU$(4XA#&R5=Nykq zVVgf|?~`vb$2nzJdbrtJ zE{5K-AmL->y$pWOLiSJ7kHRIByBZDlKIQNEeEsD2a*mx7oNDTRJ~A%v+rrG>)wj9k zS{Pf+3|C{jYcj8yuju(Mf4akJWAptAA^rsjKlG7P`6iV(qh3cXehSn^^)C(|cQ6ReQHC|6nsoEbfMflU()YhI3O( zs;XbBmuszeTj4JoTsJpTwp+|-^_5kA+m*bTIVM4F9pUyr}QrY7X z*;cFH8D>A@{VlJ3+1)kQKh<2n_9?b&{_$T9S9%m~EzYr7uxW)|f8Y+ib+KH#OWLf` zyWigozVKjM#Gf#;zt8N_IhX#nxZ}KQ`G;>4{DiC?Y;fRgp7K_C*4BtmB1~Pvz8?=4 z$X}c_=ZEGJsmle69_-xNAHch$)PCL^M+4x|VK;yflV#<&0-HU=(OC@S7l=UqR z?DuC;_!hn7t8H@Dz@*jaQ4A1Q$;MDZXSuW++UiLUcTn;3BBWGmU?Su zbo|Zzctzv$ZVQ%a96K)guj*$#803A@FZ#oMC%>io3-?}bzj)6})%au7%9MHc`7@h; z9ah+7_}IYjNtaukzmru`3Zw8zx1fI-lZ-BFu$q07Oz^X1KeHh*Ejco5?=i0{Yi_0O z72-U-<73THt*}zgN8b$Faar=s$gQ)EGCry?+{pTE)ue)-zfCqbYt(LiWxnS?>kvmb4i z@8w%jR(|T2Ve-efb!RvAe5=2!KiNtqDy@Cm&4sCD88c-OOxxqrZZgfxEkFBd*-gC} z+b^0LKR!A4gh54Zh}Hj}PJgN!xMFe)=Q=qBv#^$|DC+zXpVOLgbH5s-pP<_!b5@m& zfk7H&Pz!y}k{NL|61K%-(z(UA+(&XRo%N^R{Lx2P2)HH;$m4hpUakfTz4^3 z+9R;^@8OLLUoXr3#IC{dt5lv|(Em)~ERC9bbOhchg+%6H+W_|+H@V zw}st&?cC?(7jF6%r5(GkrnKb{!_CXAS&tM~?Qxs>c(T{iCr=HHbfiPQo`!cEw%`87 zYSE3AKfbq?hlchTC|D@0s$MP!r2|Pq;@bDpGs#FW283Vy}y~9$6M`zkSsy4vvrs z7hm5ByS`3KBz0>@p!RxEyRDbs{geK>mT#4o;m&V`Hb-Y@be!@?cztYRQ&jEh$WRmC z_`hq@vvb(0YeOY3+-CZA;Kg?R9G3=GCb6eQZ`|(9xuBWss2){yP$TA++{cfK7e3v8 zeqNwvkD=8U=j#iDw`8^)m-0DW|K(V_{+*_s5?%RJS>@WL^LIKM zo=bU@8ZKsbdKfb19Pk5@fvisK4xPF$Q`=hADu`1;GQ zt4|fZ3Y^%|C~u{_!{^e!X)hgDpTEXwy7m;m)QzwEdaHa>zUw*cc`yBSUyat40598u zb!~nno`-e@B|AL$xH9f}@D3gRNB;L>e0^M+?3s7;KiFrUILCdTcukk+&O7VOt;Djv z{94AkWs1m`g}JxZt_sqdTE}3!*=lb0uFP$_(+j%e`So70?QT2ST>kv|*MG-X9s0WW z{660Rf2-BeYuD^7NjSLtCX2w_UkL%ByUv9k?&Nv%J9X)dOQ$!AA64O+ZaJygV)+G~ z)C)G}7IX?IbiJDUEpq#+9Sb}xf-Y1Sp0zmhe1^1=ziFmw>k`+%qq{$S2WypV5Bw5jK|J6OX}RDArei|q@sA2A

    c_vnAF?i=?C-lI^1E2n>9U%{ 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 hU6fsaWyOlUrNFU$T5pq{DMBE7|_N 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%0DYx`$h-x$_&t?=rUH3*n?XXQkx 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%0DTTnjJQ+M$>BMn=4bg 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%0A(8elOM>HEZt7e|PnN3a@x2{Acf?j3}{7yHq}A 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

    bq%1YU**lbSgJt?^nFKEfOm%Z!I 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

      (fW$=J;Eyu$TJ>Q&CLu=o> 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 z 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

      D}6L@mSV!juJ)j>S!4EGJoXIimz& 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