From ad9f80e0e766393e2431da5c0b1149a327fe8e75 Mon Sep 17 00:00:00 2001 From: Andrew Purtell Date: Fri, 10 Jul 2015 17:32:07 -0700 Subject: [PATCH] HBASE-13743 Backport HBASE-13709 (Updates to meta table server columns may be eclipsed) to 0.98 --- .../hadoop/hbase/protobuf/RequestConverter.java | 3 + .../hbase/protobuf/generated/AdminProtos.java | 269 +++++++++++++++------ hbase-protocol/src/main/protobuf/Admin.proto | 2 + .../apache/hadoop/hbase/catalog/MetaEditor.java | 39 +-- .../hadoop/hbase/regionserver/HRegionServer.java | 32 ++- .../hbase/regionserver/RegionServerServices.java | 75 +++++- .../regionserver/handler/OpenMetaHandler.java | 8 +- .../regionserver/handler/OpenRegionHandler.java | 27 ++- .../hadoop/hbase/MockRegionServerServices.java | 10 + .../hadoop/hbase/catalog/TestMetaReaderEditor.java | 39 +++ .../hadoop/hbase/master/MockRegionServer.java | 11 + .../regionserver/TestRegionServerNoMaster.java | 4 +- .../handler/TestOpenRegionHandler.java | 4 +- 13 files changed, 407 insertions(+), 116 deletions(-) diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/RequestConverter.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/RequestConverter.java index 6681ca5..f244a50 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/RequestConverter.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/RequestConverter.java @@ -767,6 +767,8 @@ public final class RequestConverter { if (server != null) { builder.setServerStartCode(server.getStartcode()); } + // send the master's wall clock time as well, so that the RS can refer to it + builder.setMasterSystemTime(EnvironmentEdgeManager.currentTimeMillis()); return builder.build(); } @@ -789,6 +791,7 @@ public final class RequestConverter { if (server != null) { builder.setServerStartCode(server.getStartcode()); } + builder.setMasterSystemTime(EnvironmentEdgeManager.currentTimeMillis()); return builder.build(); } diff --git a/hbase-protocol/src/main/java/org/apache/hadoop/hbase/protobuf/generated/AdminProtos.java b/hbase-protocol/src/main/java/org/apache/hadoop/hbase/protobuf/generated/AdminProtos.java index 36d6080..a44c859 100644 --- a/hbase-protocol/src/main/java/org/apache/hadoop/hbase/protobuf/generated/AdminProtos.java +++ b/hbase-protocol/src/main/java/org/apache/hadoop/hbase/protobuf/generated/AdminProtos.java @@ -3874,6 +3874,24 @@ public final class AdminProtos { * */ long getServerStartCode(); + + // optional uint64 master_system_time = 5; + /** + * optional uint64 master_system_time = 5; + * + *
+     * wall clock time from master
+     * 
+ */ + boolean hasMasterSystemTime(); + /** + * optional uint64 master_system_time = 5; + * + *
+     * wall clock time from master
+     * 
+ */ + long getMasterSystemTime(); } /** * Protobuf type {@code OpenRegionRequest} @@ -3939,6 +3957,11 @@ public final class AdminProtos { serverStartCode_ = input.readUInt64(); break; } + case 40: { + bitField0_ |= 0x00000002; + masterSystemTime_ = input.readUInt64(); + break; + } } } } catch (com.google.protobuf.InvalidProtocolBufferException e) { @@ -5195,9 +5218,34 @@ public final class AdminProtos { return serverStartCode_; } + // optional uint64 master_system_time = 5; + public static final int MASTER_SYSTEM_TIME_FIELD_NUMBER = 5; + private long masterSystemTime_; + /** + * optional uint64 master_system_time = 5; + * + *
+     * wall clock time from master
+     * 
+ */ + public boolean hasMasterSystemTime() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional uint64 master_system_time = 5; + * + *
+     * wall clock time from master
+     * 
+ */ + public long getMasterSystemTime() { + return masterSystemTime_; + } + private void initFields() { openInfo_ = java.util.Collections.emptyList(); serverStartCode_ = 0L; + masterSystemTime_ = 0L; } private byte memoizedIsInitialized = -1; public final boolean isInitialized() { @@ -5223,6 +5271,9 @@ public final class AdminProtos { if (((bitField0_ & 0x00000001) == 0x00000001)) { output.writeUInt64(2, serverStartCode_); } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeUInt64(5, masterSystemTime_); + } getUnknownFields().writeTo(output); } @@ -5240,6 +5291,10 @@ public final class AdminProtos { size += com.google.protobuf.CodedOutputStream .computeUInt64Size(2, serverStartCode_); } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt64Size(5, masterSystemTime_); + } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; return size; @@ -5270,6 +5325,11 @@ public final class AdminProtos { result = result && (getServerStartCode() == other.getServerStartCode()); } + result = result && (hasMasterSystemTime() == other.hasMasterSystemTime()); + if (hasMasterSystemTime()) { + result = result && (getMasterSystemTime() + == other.getMasterSystemTime()); + } result = result && getUnknownFields().equals(other.getUnknownFields()); return result; @@ -5291,6 +5351,10 @@ public final class AdminProtos { hash = (37 * hash) + SERVERSTARTCODE_FIELD_NUMBER; hash = (53 * hash) + hashLong(getServerStartCode()); } + if (hasMasterSystemTime()) { + hash = (37 * hash) + MASTER_SYSTEM_TIME_FIELD_NUMBER; + hash = (53 * hash) + hashLong(getMasterSystemTime()); + } hash = (29 * hash) + getUnknownFields().hashCode(); memoizedHashCode = hash; return hash; @@ -5409,6 +5473,8 @@ public final class AdminProtos { } serverStartCode_ = 0L; bitField0_ = (bitField0_ & ~0x00000002); + masterSystemTime_ = 0L; + bitField0_ = (bitField0_ & ~0x00000004); return this; } @@ -5450,6 +5516,10 @@ public final class AdminProtos { to_bitField0_ |= 0x00000001; } result.serverStartCode_ = serverStartCode_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000002; + } + result.masterSystemTime_ = masterSystemTime_; result.bitField0_ = to_bitField0_; onBuilt(); return result; @@ -5495,6 +5565,9 @@ public final class AdminProtos { if (other.hasServerStartCode()) { setServerStartCode(other.getServerStartCode()); } + if (other.hasMasterSystemTime()) { + setMasterSystemTime(other.getMasterSystemTime()); + } this.mergeUnknownFields(other.getUnknownFields()); return this; } @@ -5817,6 +5890,55 @@ public final class AdminProtos { return this; } + // optional uint64 master_system_time = 5; + private long masterSystemTime_ ; + /** + * optional uint64 master_system_time = 5; + * + *
+       * wall clock time from master
+       * 
+ */ + public boolean hasMasterSystemTime() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional uint64 master_system_time = 5; + * + *
+       * wall clock time from master
+       * 
+ */ + public long getMasterSystemTime() { + return masterSystemTime_; + } + /** + * optional uint64 master_system_time = 5; + * + *
+       * wall clock time from master
+       * 
+ */ + public Builder setMasterSystemTime(long value) { + bitField0_ |= 0x00000004; + masterSystemTime_ = value; + onChanged(); + return this; + } + /** + * optional uint64 master_system_time = 5; + * + *
+       * wall clock time from master
+       * 
+ */ + public Builder clearMasterSystemTime() { + bitField0_ = (bitField0_ & ~0x00000004); + masterSystemTime_ = 0L; + onChanged(); + return this; + } + // @@protoc_insertion_point(builder_scope:OpenRegionRequest) } @@ -21410,79 +21532,80 @@ public final class AdminProtos { "FileResponse\022\022\n\nstore_file\030\001 \003(\t\"\030\n\026GetO" + "nlineRegionRequest\";\n\027GetOnlineRegionRes" + "ponse\022 \n\013region_info\030\001 \003(\0132\013.RegionInfo\"" + - "\374\001\n\021OpenRegionRequest\0224\n\topen_info\030\001 \003(\013" + + "\230\002\n\021OpenRegionRequest\0224\n\topen_info\030\001 \003(\013" + "2!.OpenRegionRequest.RegionOpenInfo\022\027\n\017s" + - "erverStartCode\030\002 \001(\004\032\227\001\n\016RegionOpenInfo\022" + - "\033\n\006region\030\001 \002(\0132\013.RegionInfo\022\037\n\027version_" + - "of_offline_node\030\002 \001(\r\022\"\n\rfavored_nodes\030\003" + - " \003(\0132\013.ServerName\022#\n\033openForDistributedL", - "ogReplay\030\004 \001(\010\"\235\001\n\022OpenRegionResponse\022=\n" + - "\ropening_state\030\001 \003(\0162&.OpenRegionRespons" + - "e.RegionOpeningState\"H\n\022RegionOpeningSta" + - "te\022\n\n\006OPENED\020\000\022\022\n\016ALREADY_OPENED\020\001\022\022\n\016FA" + - "ILED_OPENING\020\002\"\271\001\n\022CloseRegionRequest\022 \n" + - "\006region\030\001 \002(\0132\020.RegionSpecifier\022\037\n\027versi" + - "on_of_closing_node\030\002 \001(\r\022\036\n\020transition_i" + - "n_ZK\030\003 \001(\010:\004true\022\'\n\022destination_server\030\004" + - " \001(\0132\013.ServerName\022\027\n\017serverStartCode\030\005 \001" + - "(\004\"%\n\023CloseRegionResponse\022\016\n\006closed\030\001 \002(", - "\010\"P\n\022FlushRegionRequest\022 \n\006region\030\001 \002(\0132" + - "\020.RegionSpecifier\022\030\n\020if_older_than_ts\030\002 " + - "\001(\004\"?\n\023FlushRegionResponse\022\027\n\017last_flush" + - "_time\030\001 \002(\004\022\017\n\007flushed\030\002 \001(\010\"K\n\022SplitReg" + - "ionRequest\022 \n\006region\030\001 \002(\0132\020.RegionSpeci" + - "fier\022\023\n\013split_point\030\002 \001(\014\"\025\n\023SplitRegion" + - "Response\"W\n\024CompactRegionRequest\022 \n\006regi" + - "on\030\001 \002(\0132\020.RegionSpecifier\022\r\n\005major\030\002 \001(" + - "\010\022\016\n\006family\030\003 \001(\014\"\027\n\025CompactRegionRespon" + - "se\"\262\001\n\031UpdateFavoredNodesRequest\022@\n\013upda", - "te_info\030\001 \003(\0132+.UpdateFavoredNodesReques" + - "t.RegionUpdateInfo\032S\n\020RegionUpdateInfo\022\033" + - "\n\006region\030\001 \002(\0132\013.RegionInfo\022\"\n\rfavored_n" + - "odes\030\002 \003(\0132\013.ServerName\".\n\032UpdateFavored" + - "NodesResponse\022\020\n\010response\030\001 \001(\r\"\222\001\n\023Merg" + - "eRegionsRequest\022\"\n\010region_a\030\001 \002(\0132\020.Regi" + - "onSpecifier\022\"\n\010region_b\030\002 \002(\0132\020.RegionSp" + - "ecifier\022\027\n\010forcible\030\003 \001(\010:\005false\022\032\n\022mast" + - "er_system_time\030\004 \001(\004\"\026\n\024MergeRegionsResp" + - "onse\"X\n\010WALEntry\022\024\n\003key\030\001 \002(\0132\007.WALKey\022\027", - "\n\017key_value_bytes\030\002 \003(\014\022\035\n\025associated_ce" + - "ll_count\030\003 \001(\005\"4\n\030ReplicateWALEntryReque" + - "st\022\030\n\005entry\030\001 \003(\0132\t.WALEntry\"\033\n\031Replicat" + - "eWALEntryResponse\"\026\n\024RollWALWriterReques" + - "t\"0\n\025RollWALWriterResponse\022\027\n\017region_to_" + - "flush\030\001 \003(\014\"#\n\021StopServerRequest\022\016\n\006reas" + - "on\030\001 \002(\t\"\024\n\022StopServerResponse\"\026\n\024GetSer" + - "verInfoRequest\"B\n\nServerInfo\022 \n\013server_n" + - "ame\030\001 \002(\0132\013.ServerName\022\022\n\nwebui_port\030\002 \001" + - "(\r\"9\n\025GetServerInfoResponse\022 \n\013server_in", - "fo\030\001 \002(\0132\013.ServerInfo2\306\007\n\014AdminService\022>" + - "\n\rGetRegionInfo\022\025.GetRegionInfoRequest\032\026" + - ".GetRegionInfoResponse\022;\n\014GetStoreFile\022\024" + - ".GetStoreFileRequest\032\025.GetStoreFileRespo" + - "nse\022D\n\017GetOnlineRegion\022\027.GetOnlineRegion" + - "Request\032\030.GetOnlineRegionResponse\0225\n\nOpe" + - "nRegion\022\022.OpenRegionRequest\032\023.OpenRegion" + - "Response\0228\n\013CloseRegion\022\023.CloseRegionReq" + - "uest\032\024.CloseRegionResponse\0228\n\013FlushRegio" + - "n\022\023.FlushRegionRequest\032\024.FlushRegionResp", - "onse\0228\n\013SplitRegion\022\023.SplitRegionRequest" + - "\032\024.SplitRegionResponse\022>\n\rCompactRegion\022" + - "\025.CompactRegionRequest\032\026.CompactRegionRe" + - "sponse\022;\n\014MergeRegions\022\024.MergeRegionsReq" + - "uest\032\025.MergeRegionsResponse\022J\n\021Replicate" + - "WALEntry\022\031.ReplicateWALEntryRequest\032\032.Re" + - "plicateWALEntryResponse\022?\n\006Replay\022\031.Repl" + - "icateWALEntryRequest\032\032.ReplicateWALEntry" + - "Response\022>\n\rRollWALWriter\022\025.RollWALWrite" + - "rRequest\032\026.RollWALWriterResponse\022>\n\rGetS", - "erverInfo\022\025.GetServerInfoRequest\032\026.GetSe" + - "rverInfoResponse\0225\n\nStopServer\022\022.StopSer" + - "verRequest\032\023.StopServerResponse\022M\n\022Updat" + - "eFavoredNodes\022\032.UpdateFavoredNodesReques" + - "t\032\033.UpdateFavoredNodesResponseBA\n*org.ap" + - "ache.hadoop.hbase.protobuf.generatedB\013Ad" + - "minProtosH\001\210\001\001\240\001\001" + "erverStartCode\030\002 \001(\004\022\032\n\022master_system_ti" + + "me\030\005 \001(\004\032\227\001\n\016RegionOpenInfo\022\033\n\006region\030\001 " + + "\002(\0132\013.RegionInfo\022\037\n\027version_of_offline_n" + + "ode\030\002 \001(\r\022\"\n\rfavored_nodes\030\003 \003(\0132\013.Serve", + "rName\022#\n\033openForDistributedLogReplay\030\004 \001" + + "(\010\"\235\001\n\022OpenRegionResponse\022=\n\ropening_sta" + + "te\030\001 \003(\0162&.OpenRegionResponse.RegionOpen" + + "ingState\"H\n\022RegionOpeningState\022\n\n\006OPENED" + + "\020\000\022\022\n\016ALREADY_OPENED\020\001\022\022\n\016FAILED_OPENING" + + "\020\002\"\271\001\n\022CloseRegionRequest\022 \n\006region\030\001 \002(" + + "\0132\020.RegionSpecifier\022\037\n\027version_of_closin" + + "g_node\030\002 \001(\r\022\036\n\020transition_in_ZK\030\003 \001(\010:\004" + + "true\022\'\n\022destination_server\030\004 \001(\0132\013.Serve" + + "rName\022\027\n\017serverStartCode\030\005 \001(\004\"%\n\023CloseR", + "egionResponse\022\016\n\006closed\030\001 \002(\010\"P\n\022FlushRe" + + "gionRequest\022 \n\006region\030\001 \002(\0132\020.RegionSpec" + + "ifier\022\030\n\020if_older_than_ts\030\002 \001(\004\"?\n\023Flush" + + "RegionResponse\022\027\n\017last_flush_time\030\001 \002(\004\022" + + "\017\n\007flushed\030\002 \001(\010\"K\n\022SplitRegionRequest\022 " + + "\n\006region\030\001 \002(\0132\020.RegionSpecifier\022\023\n\013spli" + + "t_point\030\002 \001(\014\"\025\n\023SplitRegionResponse\"W\n\024" + + "CompactRegionRequest\022 \n\006region\030\001 \002(\0132\020.R" + + "egionSpecifier\022\r\n\005major\030\002 \001(\010\022\016\n\006family\030" + + "\003 \001(\014\"\027\n\025CompactRegionResponse\"\262\001\n\031Updat", + "eFavoredNodesRequest\022@\n\013update_info\030\001 \003(" + + "\0132+.UpdateFavoredNodesRequest.RegionUpda" + + "teInfo\032S\n\020RegionUpdateInfo\022\033\n\006region\030\001 \002" + + "(\0132\013.RegionInfo\022\"\n\rfavored_nodes\030\002 \003(\0132\013" + + ".ServerName\".\n\032UpdateFavoredNodesRespons" + + "e\022\020\n\010response\030\001 \001(\r\"\222\001\n\023MergeRegionsRequ" + + "est\022\"\n\010region_a\030\001 \002(\0132\020.RegionSpecifier\022" + + "\"\n\010region_b\030\002 \002(\0132\020.RegionSpecifier\022\027\n\010f" + + "orcible\030\003 \001(\010:\005false\022\032\n\022master_system_ti" + + "me\030\004 \001(\004\"\026\n\024MergeRegionsResponse\"X\n\010WALE", + "ntry\022\024\n\003key\030\001 \002(\0132\007.WALKey\022\027\n\017key_value_" + + "bytes\030\002 \003(\014\022\035\n\025associated_cell_count\030\003 \001" + + "(\005\"4\n\030ReplicateWALEntryRequest\022\030\n\005entry\030" + + "\001 \003(\0132\t.WALEntry\"\033\n\031ReplicateWALEntryRes" + + "ponse\"\026\n\024RollWALWriterRequest\"0\n\025RollWAL" + + "WriterResponse\022\027\n\017region_to_flush\030\001 \003(\014\"" + + "#\n\021StopServerRequest\022\016\n\006reason\030\001 \002(\t\"\024\n\022" + + "StopServerResponse\"\026\n\024GetServerInfoReque" + + "st\"B\n\nServerInfo\022 \n\013server_name\030\001 \002(\0132\013." + + "ServerName\022\022\n\nwebui_port\030\002 \001(\r\"9\n\025GetSer", + "verInfoResponse\022 \n\013server_info\030\001 \002(\0132\013.S" + + "erverInfo2\306\007\n\014AdminService\022>\n\rGetRegionI" + + "nfo\022\025.GetRegionInfoRequest\032\026.GetRegionIn" + + "foResponse\022;\n\014GetStoreFile\022\024.GetStoreFil" + + "eRequest\032\025.GetStoreFileResponse\022D\n\017GetOn" + + "lineRegion\022\027.GetOnlineRegionRequest\032\030.Ge" + + "tOnlineRegionResponse\0225\n\nOpenRegion\022\022.Op" + + "enRegionRequest\032\023.OpenRegionResponse\0228\n\013" + + "CloseRegion\022\023.CloseRegionRequest\032\024.Close" + + "RegionResponse\0228\n\013FlushRegion\022\023.FlushReg", + "ionRequest\032\024.FlushRegionResponse\0228\n\013Spli" + + "tRegion\022\023.SplitRegionRequest\032\024.SplitRegi" + + "onResponse\022>\n\rCompactRegion\022\025.CompactReg" + + "ionRequest\032\026.CompactRegionResponse\022;\n\014Me" + + "rgeRegions\022\024.MergeRegionsRequest\032\025.Merge" + + "RegionsResponse\022J\n\021ReplicateWALEntry\022\031.R" + + "eplicateWALEntryRequest\032\032.ReplicateWALEn" + + "tryResponse\022?\n\006Replay\022\031.ReplicateWALEntr" + + "yRequest\032\032.ReplicateWALEntryResponse\022>\n\r" + + "RollWALWriter\022\025.RollWALWriterRequest\032\026.R", + "ollWALWriterResponse\022>\n\rGetServerInfo\022\025." + + "GetServerInfoRequest\032\026.GetServerInfoResp" + + "onse\0225\n\nStopServer\022\022.StopServerRequest\032\023" + + ".StopServerResponse\022M\n\022UpdateFavoredNode" + + "s\022\032.UpdateFavoredNodesRequest\032\033.UpdateFa" + + "voredNodesResponseBA\n*org.apache.hadoop." + + "hbase.protobuf.generatedB\013AdminProtosH\001\210" + + "\001\001\240\001\001" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -21530,7 +21653,7 @@ public final class AdminProtos { internal_static_OpenRegionRequest_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_OpenRegionRequest_descriptor, - new java.lang.String[] { "OpenInfo", "ServerStartCode", }); + new java.lang.String[] { "OpenInfo", "ServerStartCode", "MasterSystemTime", }); internal_static_OpenRegionRequest_RegionOpenInfo_descriptor = internal_static_OpenRegionRequest_descriptor.getNestedTypes().get(0); internal_static_OpenRegionRequest_RegionOpenInfo_fieldAccessorTable = new diff --git a/hbase-protocol/src/main/protobuf/Admin.proto b/hbase-protocol/src/main/protobuf/Admin.proto index b1c0d64..c687dee 100644 --- a/hbase-protocol/src/main/protobuf/Admin.proto +++ b/hbase-protocol/src/main/protobuf/Admin.proto @@ -70,6 +70,8 @@ message OpenRegionRequest { repeated RegionOpenInfo open_info = 1; // the intended server for this RPC. optional uint64 serverStartCode = 2; + // wall clock time from master + optional uint64 master_system_time = 5; message RegionOpenInfo { required RegionInfo region = 1; diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/catalog/MetaEditor.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/catalog/MetaEditor.java index 94b3641..2b41490 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/catalog/MetaEditor.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/catalog/MetaEditor.java @@ -319,7 +319,7 @@ public class MetaEditor { Put put = new Put(regionInfo.getRegionName()); addRegionInfo(put, regionInfo); if (sn != null) { - addLocation(put, sn, openSeqNum); + addLocation(put, sn, openSeqNum, -1); } putToMetaTable(catalogTracker, put); LOG.info("Added daughter " + regionInfo.getEncodedName() + @@ -335,7 +335,7 @@ public class MetaEditor { * @param regionA * @param regionB * @param sn the location of the region - * @param masterSystemTime + * @param masterSystemTime wall clock time from master if passed in the open region RPC or -1 * @throws IOException */ public static void mergeRegions(final CatalogTracker catalogTracker, @@ -360,7 +360,7 @@ public class MetaEditor { Delete deleteB = makeDeleteFromRegionInfo(regionB, time); // The merged is a new region, openSeqNum = 1 is fine. - addLocation(putOfMerged, sn, 1); + addLocation(putOfMerged, sn, 1, time); byte[] tableRow = Bytes.toBytes(mergedRegion.getRegionNameAsString() + HConstants.DELIMITER); @@ -398,8 +398,8 @@ public class MetaEditor { Put putA = makePutFromRegionInfo(splitA); Put putB = makePutFromRegionInfo(splitB); - addLocation(putA, sn, 1); //these are new regions, openSeqNum = 1 is fine. - addLocation(putB, sn, 1); + addLocation(putA, sn, 1, -1); //these are new regions, openSeqNum = 1 is fine. + addLocation(putB, sn, 1, -1); byte[] tableRow = Bytes.toBytes(parent.getRegionNameAsString() + HConstants.DELIMITER); multiMutate(meta, tableRow, putParent, putA, putB); @@ -454,7 +454,7 @@ public class MetaEditor { public static void updateMetaLocation(CatalogTracker catalogTracker, HRegionInfo regionInfo, ServerName sn, long openSeqNum) throws IOException, ConnectException { - updateLocation(catalogTracker, regionInfo, sn, openSeqNum, HConstants.LATEST_TIMESTAMP); + updateLocation(catalogTracker, regionInfo, sn, openSeqNum, -1); } /** @@ -467,12 +467,13 @@ public class MetaEditor { * @param catalogTracker catalog tracker * @param regionInfo region to update location of * @param sn Server name + * @param openSeqNum the latest sequence number obtained when the region was open * @throws IOException */ public static void updateRegionLocation(CatalogTracker catalogTracker, - HRegionInfo regionInfo, ServerName sn, long updateSeqNum) + HRegionInfo regionInfo, ServerName sn, long openSeqNum) throws IOException { - updateLocation(catalogTracker, regionInfo, sn, updateSeqNum, HConstants.LATEST_TIMESTAMP); + updateLocation(catalogTracker, regionInfo, sn, openSeqNum, -1); } /** @@ -485,13 +486,14 @@ public class MetaEditor { * @param catalogTracker catalog tracker * @param regionInfo region to update location of * @param sn Server name - * @param masterSystemTime + * @param openSeqNum the latest sequence number obtained when the region was open + * @param masterSystemTime wall clock time from master if passed in the open region RPC or -1 * @throws IOException */ public static void updateRegionLocation(CatalogTracker catalogTracker, - HRegionInfo regionInfo, ServerName sn, long updateSeqNum, long masterSystemTime) + HRegionInfo regionInfo, ServerName sn, long openSeqNum, long masterSystemTime) throws IOException { - updateLocation(catalogTracker, regionInfo, sn, updateSeqNum, masterSystemTime); + updateLocation(catalogTracker, regionInfo, sn, openSeqNum, masterSystemTime); } /** @@ -504,7 +506,7 @@ public class MetaEditor { * @param regionInfo region to update location of * @param sn Server name * @param openSeqNum the latest sequence number obtained when the region was open - * @param masterSystemTime + * @param masterSystemTime wall clock time from master if passed in the open region RPC or -1 * @throws IOException In particular could throw {@link java.net.ConnectException} * if the server is down on other end. */ @@ -516,7 +518,7 @@ public class MetaEditor { long time = Math.max(EnvironmentEdgeManager.currentTimeMillis(), masterSystemTime); Put put = new Put(regionInfo.getRegionName(), time); - addLocation(put, sn, openSeqNum); + addLocation(put, sn, openSeqNum, time); putToCatalogTable(catalogTracker, put); LOG.info("Updated row " + regionInfo.getRegionNameAsString() + " with server=" + sn); @@ -640,12 +642,15 @@ public class MetaEditor { return p; } - private static Put addLocation(final Put p, final ServerName sn, long openSeqNum) { - p.addImmutable(HConstants.CATALOG_FAMILY, HConstants.SERVER_QUALIFIER, + private static Put addLocation(final Put p, final ServerName sn, long openSeqNum, long time) { + if (time <= 0) { + time = EnvironmentEdgeManager.currentTimeMillis(); + } + p.addImmutable(HConstants.CATALOG_FAMILY, HConstants.SERVER_QUALIFIER, time, Bytes.toBytes(sn.getHostAndPort())); - p.addImmutable(HConstants.CATALOG_FAMILY, HConstants.STARTCODE_QUALIFIER, + p.addImmutable(HConstants.CATALOG_FAMILY, HConstants.STARTCODE_QUALIFIER, time, Bytes.toBytes(sn.getStartcode())); - p.addImmutable(HConstants.CATALOG_FAMILY, HConstants.SEQNUM_QUALIFIER, + p.addImmutable(HConstants.CATALOG_FAMILY, HConstants.SEQNUM_QUALIFIER, time, Bytes.toBytes(openSeqNum)); return p; } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java index 1608f12..1ca93ea 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java @@ -1871,7 +1871,15 @@ public class HRegionServer implements ClientProtos.ClientService.BlockingInterfa @Override public void postOpenDeployTasks(final HRegion r, final CatalogTracker ct) - throws KeeperException, IOException { + throws KeeperException, IOException { + postOpenDeployTasks(new PostOpenDeployContext(r, -1), ct); + } + + @Override + public void postOpenDeployTasks(final PostOpenDeployContext context, final CatalogTracker ct) + throws KeeperException, IOException { + HRegion r = context.getRegion(); + long masterSystemTime = context.getMasterSystemTime(); checkOpen(); LOG.info("Post open deploy tasks for region=" + r.getRegionNameAsString()); // Do checks to see if we need to compact (references or too many files) @@ -1898,11 +1906,12 @@ public class HRegionServer implements ClientProtos.ClientService.BlockingInterfa MetaRegionTracker.setMetaLocation(getZooKeeper(), this.serverNameFromMasterPOV, State.OPEN); } else { MetaEditor.updateRegionLocation(ct, r.getRegionInfo(), this.serverNameFromMasterPOV, - openSeqNum); + openSeqNum, masterSystemTime); } } if (!useZKForAssignment - && !reportRegionStateTransition(TransitionCode.OPENED, openSeqNum, r.getRegionInfo())) { + && !reportRegionStateTransition(new RegionStateTransitionContext( + TransitionCode.OPENED, openSeqNum, masterSystemTime, r.getRegionInfo()))) { throw new IOException("Failed to report opened region to master: " + r.getRegionNameAsString()); } @@ -2036,6 +2045,16 @@ public class HRegionServer implements ClientProtos.ClientService.BlockingInterfa @Override public boolean reportRegionStateTransition(TransitionCode code, long openSeqNum, HRegionInfo... hris) { + return reportRegionStateTransition( + new RegionStateTransitionContext(code, HConstants.NO_SEQNUM, -1, hris)); + } + + @Override + public boolean reportRegionStateTransition(RegionStateTransitionContext context) { + TransitionCode code = context.getCode(); + long openSeqNum = context.getOpenSeqNum(); + long masterSystemTime = context.getMasterSystemTime(); + HRegionInfo[] hris = context.getHris(); ReportRegionStateTransitionRequest.Builder builder = ReportRegionStateTransitionRequest.newBuilder(); builder.setServer(ProtobufUtil.toServerName(serverName)); RegionStateTransition.Builder transition = builder.addTransitionBuilder(); @@ -3868,6 +3887,9 @@ public class HRegionServer implements ClientProtos.ClientService.BlockingInterfa final Map htds = new HashMap(regionCount); final boolean isBulkAssign = regionCount > 1; + + long masterSystemTime = request.hasMasterSystemTime() ? request.getMasterSystemTime() : -1; + for (RegionOpenInfo regionOpenInfo : request.getOpenInfoList()) { final HRegionInfo region = HRegionInfo.convert(regionOpenInfo.getRegion()); @@ -3958,12 +3980,12 @@ public class HRegionServer implements ClientProtos.ClientService.BlockingInterfa // Need to pass the expected version in the constructor. if (region.isMetaRegion()) { this.service.submit(new OpenMetaHandler(this, this, region, htd, - versionOfOfflineNode)); + versionOfOfflineNode, masterSystemTime)); } else { updateRegionFavoredNodesMapping(region.getEncodedName(), regionOpenInfo.getFavoredNodesList()); this.service.submit(new OpenRegionHandler(this, this, region, htd, - versionOfOfflineNode)); + versionOfOfflineNode, masterSystemTime)); } } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RegionServerServices.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RegionServerServices.java index d331840..d5b908d 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RegionServerServices.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/RegionServerServices.java @@ -71,29 +71,98 @@ public interface RegionServerServices */ TableLockManager getTableLockManager(); + /** + * Context for postOpenDeployTasks(). + */ + class PostOpenDeployContext { + private final HRegion region; + private final long masterSystemTime; + + @InterfaceAudience.Private + public PostOpenDeployContext(HRegion region, long masterSystemTime) { + this.region = region; + this.masterSystemTime = masterSystemTime; + } + public HRegion getRegion() { + return region; + } + public long getMasterSystemTime() { + return masterSystemTime; + } + } + + /** + * Tasks to perform after region open to complete deploy of region on + * regionserver + * + * @param context the context + * @param ct catalog tracker + * @throws KeeperException + * @throws IOException + */ + void postOpenDeployTasks(final PostOpenDeployContext context, final CatalogTracker ct) + throws KeeperException, IOException; + /** * Tasks to perform after region open to complete deploy of region on * regionserver * * @param r Region to open. - * @param ct Instance of {@link CatalogTracker} + * @param ct catalog tracker * @throws KeeperException * @throws IOException + * @deprecated use {@link #postOpenDeployTasks(PostOpenDeployContext)} */ + @Deprecated void postOpenDeployTasks(final HRegion r, final CatalogTracker ct) - throws KeeperException, IOException; + throws KeeperException, IOException; + + class RegionStateTransitionContext { + private final TransitionCode code; + private final long openSeqNum; + private final long masterSystemTime; + private final HRegionInfo[] hris; + + @InterfaceAudience.Private + public RegionStateTransitionContext(TransitionCode code, long openSeqNum, long masterSystemTime, + HRegionInfo... hris) { + this.code = code; + this.openSeqNum = openSeqNum; + this.masterSystemTime = masterSystemTime; + this.hris = hris; + } + public TransitionCode getCode() { + return code; + } + public long getOpenSeqNum() { + return openSeqNum; + } + public long getMasterSystemTime() { + return masterSystemTime; + } + public HRegionInfo[] getHris() { + return hris; + } + } /** * Notify master that a handler requests to change a region state */ - boolean reportRegionStateTransition(TransitionCode code, long openSeqNum, HRegionInfo... hris); + boolean reportRegionStateTransition(final RegionStateTransitionContext context); /** * Notify master that a handler requests to change a region state */ + @Deprecated boolean reportRegionStateTransition(TransitionCode code, HRegionInfo... hris); /** + * Notify master that a handler requests to change a region state + */ + @Deprecated + boolean reportRegionStateTransition(TransitionCode code, long openSeqNum, HRegionInfo... hris); + + /** * Returns a reference to the region server's RPC server */ RpcServerInterface getRpcServer(); diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/handler/OpenMetaHandler.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/handler/OpenMetaHandler.java index ab91daa..46764b8 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/handler/OpenMetaHandler.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/handler/OpenMetaHandler.java @@ -34,13 +34,13 @@ import org.apache.hadoop.hbase.regionserver.RegionServerServices; public class OpenMetaHandler extends OpenRegionHandler { public OpenMetaHandler(final Server server, final RegionServerServices rsServices, HRegionInfo regionInfo, - final HTableDescriptor htd) { - this(server, rsServices, regionInfo, htd, -1); + final HTableDescriptor htd, long masterSystemTime) { + this(server, rsServices, regionInfo, htd, -1, masterSystemTime); } public OpenMetaHandler(final Server server, final RegionServerServices rsServices, HRegionInfo regionInfo, - final HTableDescriptor htd, int versionOfOfflineNode) { - super(server, rsServices, regionInfo, htd, EventType.M_RS_OPEN_META, + final HTableDescriptor htd, int versionOfOfflineNode, long masterSystemTime) { + super(server, rsServices, regionInfo, htd, masterSystemTime, EventType.M_RS_OPEN_META, versionOfOfflineNode); } } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/handler/OpenRegionHandler.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/handler/OpenRegionHandler.java index 7728c05..7f619e5 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/handler/OpenRegionHandler.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/handler/OpenRegionHandler.java @@ -34,6 +34,7 @@ import org.apache.hadoop.hbase.protobuf.generated.RegionServerStatusProtos.Regio import org.apache.hadoop.hbase.regionserver.HRegion; import org.apache.hadoop.hbase.regionserver.RegionServerAccounting; import org.apache.hadoop.hbase.regionserver.RegionServerServices; +import org.apache.hadoop.hbase.regionserver.RegionServerServices.PostOpenDeployContext; import org.apache.hadoop.hbase.util.CancelableProgressable; import org.apache.hadoop.hbase.util.ConfigUtil; import org.apache.hadoop.hbase.zookeeper.ZKAssign; @@ -52,6 +53,7 @@ public class OpenRegionHandler extends EventHandler { private final HRegionInfo regionInfo; private final HTableDescriptor htd; + private final long masterSystemTime; private boolean tomActivated; private int assignmentTimeout; @@ -68,23 +70,25 @@ public class OpenRegionHandler extends EventHandler { public OpenRegionHandler(final Server server, final RegionServerServices rsServices, HRegionInfo regionInfo, HTableDescriptor htd) { - this(server, rsServices, regionInfo, htd, EventType.M_RS_OPEN_REGION, -1); + this(server, rsServices, regionInfo, htd, -1, EventType.M_RS_OPEN_REGION, -1); } + public OpenRegionHandler(final Server server, final RegionServerServices rsServices, HRegionInfo regionInfo, - HTableDescriptor htd, int versionOfOfflineNode) { - this(server, rsServices, regionInfo, htd, EventType.M_RS_OPEN_REGION, + HTableDescriptor htd, int versionOfOfflineNode, long masterSystemTime) { + this(server, rsServices, regionInfo, htd, masterSystemTime, EventType.M_RS_OPEN_REGION, versionOfOfflineNode); } protected OpenRegionHandler(final Server server, final RegionServerServices rsServices, final HRegionInfo regionInfo, - final HTableDescriptor htd, EventType eventType, + final HTableDescriptor htd, long masterSystemTime, EventType eventType, final int versionOfOfflineNode) { super(server, eventType); this.rsServices = rsServices; this.regionInfo = regionInfo; this.htd = htd; + this.masterSystemTime = masterSystemTime; this.versionOfOfflineNode = versionOfOfflineNode; tomActivated = this.server.getConfiguration(). getBoolean(AssignmentManager.ASSIGNMENT_TIMEOUT_MANAGEMENT, @@ -149,7 +153,7 @@ public class OpenRegionHandler extends EventHandler { boolean failed = true; if (isRegionStillOpening() && (!useZKForAssignment || tickleOpening("post_region_open"))) { - if (updateMeta(region)) { + if (updateMeta(region, masterSystemTime)) { failed = false; } } @@ -248,7 +252,7 @@ public class OpenRegionHandler extends EventHandler { * state meantime so master doesn't timeout our region-in-transition. * Caller must cleanup region if this fails. */ - boolean updateMeta(final HRegion r) { + boolean updateMeta(final HRegion r, final long masterSystemTime) { if (this.server.isStopped() || this.rsServices.isStopping()) { return false; } @@ -256,7 +260,7 @@ public class OpenRegionHandler extends EventHandler { // Else, wait. final AtomicBoolean signaller = new AtomicBoolean(false); PostOpenDeployTasksThread t = new PostOpenDeployTasksThread(r, - this.server, this.rsServices, signaller); + this.server, this.rsServices, signaller, masterSystemTime); t.start(); // Total timeout for meta edit. If we fail adding the edit then close out // the region and let it be assigned elsewhere. @@ -323,20 +327,23 @@ public class OpenRegionHandler extends EventHandler { private final RegionServerServices services; private final HRegion region; private final AtomicBoolean signaller; + private final long masterSystemTime; PostOpenDeployTasksThread(final HRegion region, final Server server, - final RegionServerServices services, final AtomicBoolean signaller) { + final RegionServerServices services, final AtomicBoolean signaller, + final long masterSystemTime) { super("PostOpenDeployTasks:" + region.getRegionInfo().getEncodedName()); this.setDaemon(true); this.server = server; this.services = services; this.region = region; - this.signaller = signaller; + this.signaller = signaller; + this.masterSystemTime = masterSystemTime; } public void run() { try { - this.services.postOpenDeployTasks(this.region, + this.services.postOpenDeployTasks(new PostOpenDeployContext(region, masterSystemTime), this.server.getCatalogTracker()); } catch (Throwable e) { String msg = diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/MockRegionServerServices.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/MockRegionServerServices.java index f90f51e..05a8257 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/MockRegionServerServices.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/MockRegionServerServices.java @@ -108,6 +108,11 @@ class MockRegionServerServices implements RegionServerServices { } @Override + public void postOpenDeployTasks(PostOpenDeployContext context, CatalogTracker ct) + throws KeeperException, IOException { + } + + @Override public RpcServerInterface getRpcServer() { return rpcServer; } @@ -245,6 +250,11 @@ class MockRegionServerServices implements RegionServerServices { } @Override + public boolean reportRegionStateTransition(RegionStateTransitionContext context) { + return false; + } + + @Override public boolean registerService(Service service) { // TODO Auto-generated method stub return false; diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/catalog/TestMetaReaderEditor.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/catalog/TestMetaReaderEditor.java index 5e911d3..c696ea4 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/catalog/TestMetaReaderEditor.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/catalog/TestMetaReaderEditor.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import com.google.common.collect.Lists; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; @@ -347,5 +348,43 @@ public class TestMetaReaderEditor { meta.close(); } } + + @Test + public void testMastersSystemTimeIsUsedInUpdateLocations() throws IOException { + long regionId = System.currentTimeMillis(); + HRegionInfo regionInfo = new HRegionInfo(TableName.valueOf("table_foo"), + HConstants.EMPTY_START_ROW, HConstants.EMPTY_END_ROW, false, regionId); + + ServerName sn = ServerName.valueOf("bar", 0, 0); + HTable meta = MetaReader.getMetaHTable(CT); + try { + List regionInfos = Lists.newArrayList(regionInfo); + MetaEditor.addRegionsToMeta(CT, regionInfos, 1); + + long masterSystemTime = EnvironmentEdgeManager.currentTimeMillis() + 123456789; + MetaEditor.updateRegionLocation(CT, regionInfo, sn, 1, masterSystemTime); + + Get get = new Get(regionInfo.getRegionName()); + Result result = meta.get(get); + Cell serverCell = result.getColumnLatestCell(HConstants.CATALOG_FAMILY, + HConstants.SERVER_QUALIFIER); + Cell startCodeCell = result.getColumnLatestCell(HConstants.CATALOG_FAMILY, + HConstants.STARTCODE_QUALIFIER); + Cell seqNumCell = result.getColumnLatestCell(HConstants.CATALOG_FAMILY, + HConstants.SEQNUM_QUALIFIER); + + assertNotNull(serverCell); + assertNotNull(startCodeCell); + assertNotNull(seqNumCell); + assertTrue(serverCell.getValueLength() > 0); + assertTrue(startCodeCell.getValueLength() > 0); + assertTrue(seqNumCell.getValueLength() > 0); + assertEquals(masterSystemTime, serverCell.getTimestamp()); + assertEquals(masterSystemTime, startCodeCell.getTimestamp()); + assertEquals(masterSystemTime, seqNumCell.getTimestamp()); + } finally { + meta.close(); + } + } } diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/MockRegionServer.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/MockRegionServer.java index c09a82d..532544d 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/MockRegionServer.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/MockRegionServer.java @@ -286,6 +286,12 @@ ClientProtos.ClientService.BlockingInterface, RegionServerServices { } @Override + public void postOpenDeployTasks(PostOpenDeployContext context, CatalogTracker ct) + throws KeeperException, IOException { + addToOnlineRegions(context.getRegion()); + } + + @Override public boolean isStopping() { return false; } @@ -578,6 +584,11 @@ ClientProtos.ClientService.BlockingInterface, RegionServerServices { } @Override + public boolean reportRegionStateTransition(RegionStateTransitionContext context) { + return false; + } + + @Override public boolean registerService(Service service) { // TODO Auto-generated method stub return false; diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestRegionServerNoMaster.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestRegionServerNoMaster.java index 5dc107a..32ecaf1 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestRegionServerNoMaster.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestRegionServerNoMaster.java @@ -319,7 +319,7 @@ public class TestRegionServerNoMaster { // Let's start the open handler HTableDescriptor htd = getRS().tableDescriptors.get(hri.getTable()); - getRS().service.submit(new OpenRegionHandler(getRS(), getRS(), hri, htd, 0)); + getRS().service.submit(new OpenRegionHandler(getRS(), getRS(), hri, htd, 0, -1)); // The open handler should have removed the region from RIT but kept the region closed checkRegionIsClosed(); @@ -373,7 +373,7 @@ public class TestRegionServerNoMaster { // 2) The region in RIT was changed. // The order is more or less implementation dependant. HTableDescriptor htd = getRS().tableDescriptors.get(hri.getTable()); - getRS().service.submit(new OpenRegionHandler(getRS(), getRS(), hri, htd, 0)); + getRS().service.submit(new OpenRegionHandler(getRS(), getRS(), hri, htd, 0, -1)); // The open handler should have removed the region from RIT but kept the region closed checkRegionIsClosed(); diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/handler/TestOpenRegionHandler.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/handler/TestOpenRegionHandler.java index 35a3ab2..22e55e9 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/handler/TestOpenRegionHandler.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/handler/TestOpenRegionHandler.java @@ -226,7 +226,7 @@ public class TestOpenRegionHandler { OpenRegionHandler handler = new OpenRegionHandler(server, rsServices, TEST_HRI, TEST_HTD) { @Override - boolean updateMeta(final HRegion r) { + boolean updateMeta(final HRegion r, long masterSystemTime) { // Fake failure of updating META return false; } @@ -250,7 +250,7 @@ public class TestOpenRegionHandler { // Create the handler OpenRegionHandler handler = new OpenRegionHandler(server, rsServices, TEST_HRI, TEST_HTD) { @Override - boolean updateMeta(HRegion r) { + boolean updateMeta(HRegion r, long masterSystemTime) { return false; }; -- 2.2.2