/**
* 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 org.apache.hadoop.hbase.catalog;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.HRegionInfo;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.catalog.MetaReader.Visitor;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.master.MasterServices;
import org.apache.hadoop.hbase.migration.HRegionInfo090x;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.Writables;
/**
* Tools to help with migration of meta tables so they no longer host
* instances of HTableDescriptor.
* @deprecated Used migration from 0.90 to 0.92 so will be going away in next
* release
*/
public class MetaMigrationRemovingHTD {
private static final Log LOG = LogFactory.getLog(MetaMigrationRemovingHTD.class);
/**
* Update legacy META rows, removing HTD from HRI.
* @param masterServices
* @return List of table descriptors.
* @throws IOException
*/
public static Set<HTableDescriptor> updateMetaWithNewRegionInfo(
final MasterServices masterServices)
throws IOException {
MigratingVisitor v = new MigratingVisitor(masterServices);
MetaReader.fullScan(masterServices.getCatalogTracker(), v);
updateRootWithMetaMigrationStatus(masterServices.getCatalogTracker());
return v.htds;
}
/**
* Update the ROOT with new HRI. (HRI with no HTD)
* @param masterServices
* @return List of table descriptors
* @throws IOException
*/
static Set<HTableDescriptor> updateRootWithNewRegionInfo(
final MasterServices masterServices)
throws IOException {
MigratingVisitor v = new MigratingVisitor(masterServices);
MetaReader.fullScan(masterServices.getCatalogTracker(), v, null, true);
return v.htds;
}
/**
* Meta visitor that migrates the info:regioninfo as it visits.
*/
static class MigratingVisitor implements Visitor {
private final MasterServices services;
final Set<HTableDescriptor> htds = new HashSet<HTableDescriptor>();
MigratingVisitor(final MasterServices services) {
this.services = services;
}
@Override
public boolean visit(Result r) throws IOException {
if (r == null || r.isEmpty()) return true;
// Check info:regioninfo, info:splitA, and info:splitB. Make sure all
// have migrated HRegionInfos... that there are no leftover 090 version
// HRegionInfos.
byte [] hriBytes = getBytes(r, HConstants.REGIONINFO_QUALIFIER);
// Presumes that an edit updating all three cells either succeeds or
// doesn't -- that we don't have case of info:regioninfo migrated but not
// info:splitA.
if (isMigrated(hriBytes)) return true;
// OK. Need to migrate this row in meta.
HRegionInfo090x hri090 = getHRegionInfo090x(hriBytes);
HTableDescriptor htd = hri090.getTableDesc();
if (htd == null) {
LOG.warn("A 090 HRI has null HTD? Continuing; " + hri090.toString());
return true;
}
if (!this.htds.contains(htd)) {
// If first time we are adding a table, then write it out to fs.
// Presumes that first region in table has THE table's schema which
// might not be too bad of a presumption since it'll be first region
// 'altered'
this.services.getMasterFileSystem().createTableDescriptor(htd);
this.htds.add(htd);
}
// This will 'migrate' the hregioninfo from 090 version to 092.
HRegionInfo hri = new HRegionInfo(hri090);
// Now make a put to write back to meta.
Put p = new Put(hri.getRegionName());
p.add(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER,
Writables.getBytes(hri));
// Now check info:splitA and info:splitB if present. Migrate these too.
checkSplit(r, p, HConstants.SPLITA_QUALIFIER);
checkSplit(r, p, HConstants.SPLITB_QUALIFIER);
// Below we fake out putToCatalogTable
MetaEditor.putToCatalogTable(this.services.getCatalogTracker(), p);
LOG.info("Migrated " + Bytes.toString(p.getRow()));
return true;
}
}
static void checkSplit(final Result r, final Put p, final byte [] which)
throws IOException {
byte [] hriSplitBytes = getBytes(r, which);
if (!isMigrated(hriSplitBytes)) {
// This will convert the HRI from 090 to 092 HRI.
HRegionInfo hri = Writables.getHRegionInfo(hriSplitBytes);
p.add(HConstants.CATALOG_FAMILY, which, Writables.getBytes(hri));
}
}
/**
* @param r Result to dig in.
* @param qualifier Qualifier to look at in the passed <code>r</code>.
* @return Bytes for an HRegionInfo or null if no bytes or empty bytes found.
*/
static byte [] getBytes(final Result r, final byte [] qualifier) {
byte [] hriBytes = r.getValue(HConstants.CATALOG_FAMILY, qualifier);
if (hriBytes == null || hriBytes.length <= 0) return null;
return hriBytes;
}
/**
* @param r Result to look in.
* @param qualifier What to look at in the passed result.
* @return Either a 090 vintage HRegionInfo OR null if no HRegionInfo or
* the HRegionInfo is up to date and not in need of migration.
* @throws IOException
*/
static HRegionInfo090x get090HRI(final Result r, final byte [] qualifier)
throws IOException {
byte [] hriBytes = r.getValue(HConstants.CATALOG_FAMILY, qualifier);
if (hriBytes == null || hriBytes.length <= 0) return null;
if (isMigrated(hriBytes)) return null;
return getHRegionInfo090x(hriBytes);
}
static boolean isMigrated(final byte [] hriBytes) {
if (hriBytes == null || hriBytes.length <= 0) return true;
// Else, what version this HRegionInfo instance is at. The first byte
// is the version byte in a serialized HRegionInfo. If its same as our
// current HRI, then nothing to do.
if (hriBytes[0] == HRegionInfo.VERSION) return true;
if (hriBytes[0] == HRegionInfo.VERSION_PRE_092) return false;
// Unknown version. Return true that its 'migrated' but log warning.
// Should 'never' happen.
assert false: "Unexpected version; bytes=" + Bytes.toStringBinary(hriBytes);
return true;
}
/**
* Migrate root and meta to newer version. This updates the META and ROOT
* and removes the HTD from HRI.
* @param masterServices
* @throws IOException
*/
public static void migrateRootAndMeta(final MasterServices masterServices)
throws IOException {
updateRootWithNewRegionInfo(masterServices);
updateMetaWithNewRegionInfo(masterServices);
}
/**
* Update the version flag in -ROOT-.
* @param catalogTracker
* @throws IOException
*/
public static void updateRootWithMetaMigrationStatus(final CatalogTracker catalogTracker)
throws IOException {
Put p = new Put(HRegionInfo.FIRST_META_REGIONINFO.getRegionName());
MetaEditor.putToRootTable(catalogTracker, setMetaVersion(p));
LOG.info("Updated -ROOT- meta version=" + HConstants.META_VERSION);
}
static Put setMetaVersion(final Put p) {
p.add(HConstants.CATALOG_FAMILY, HConstants.META_VERSION_QUALIFIER,
Bytes.toBytes(HConstants.META_VERSION));
return p;
}
/**
* @return True if the meta table has been migrated.
* @throws IOException
*/
// Public because used in tests
public static boolean isMetaHRIUpdated(final MasterServices services)
throws IOException {
List<Result> results = MetaReader.fullScanOfRoot(services.getCatalogTracker());
if (results == null || results.isEmpty()) {
LOG.info("Not migrated");
return false;
}
// Presume only the one result because we only support on meta region.
Result r = results.get(0);
short version = getMetaVersion(r);
boolean migrated = version >= HConstants.META_VERSION;
LOG.info("Meta version=" + version + "; migrated=" + migrated);
return migrated;
}
/**
* @param r Result to look at
* @return Current meta table version or -1 if no version found.
*/
static short getMetaVersion(final Result r) {
byte [] value = r.getValue(HConstants.CATALOG_FAMILY,
HConstants.META_VERSION_QUALIFIER);
return value == null || value.length <= 0? -1: Bytes.toShort(value);
}
/**
* @return True if migrated.
* @throws IOException
*/
public static boolean updateMetaWithNewHRI(final MasterServices services)
throws IOException {
if (isMetaHRIUpdated(services)) {
LOG.info("ROOT/Meta already up-to date with new HRI.");
return true;
}
LOG.info("Meta has HRI with HTDs. Updating meta now.");
try {
migrateRootAndMeta(services);
LOG.info("ROOT and Meta updated with new HRI.");
return true;
} catch (IOException e) {
throw new RuntimeException("Update ROOT/Meta with new HRI failed." +
"Master startup aborted.");
}
}
/**
* Get HREgionInfoForMigration serialized from bytes.
* @param bytes serialized bytes
* @return An instance of a 090 HRI or null if we failed deserialize
*/
public static HRegionInfo090x getHRegionInfo090x(final byte [] bytes) {
if (bytes == null || bytes.length == 0) return null;
HRegionInfo090x hri = null;
try {
hri = (HRegionInfo090x)Writables.getWritable(bytes, new HRegionInfo090x());
} catch (IOException ioe) {
LOG.warn("Failed deserialize as a 090 HRegionInfo); bytes=" +
Bytes.toStringBinary(bytes), ioe);
}
return hri;
}
}