/**
* 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.hdfs.server.namenode.bookkeeper.metadata;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.hdfs.server.namenode.bookkeeper.metadata.proto.CurrentInProgressMetadataWritable;
import org.apache.hadoop.hdfs.server.namenode.bookkeeper.metadata.proto.WritableUtil;
import org.apache.hadoop.hdfs.server.namenode.bookkeeper.zk.RecoveringZooKeeper;
import org.apache.hadoop.hdfs.server.namenode.bookkeeper.zk.ZooKeeperIface;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
import java.net.InetAddress;
import java.util.concurrent.atomic.AtomicInteger;
import static org.apache.hadoop.hdfs.server.namenode.bookkeeper.zk.ZkUtil.interruptedException;
import static org.apache.hadoop.hdfs.server.namenode.bookkeeper.zk.ZkUtil.keeperException;
/**
* Used by {@link BookKeeperJournalMetadataManager} to point to the
* ledger metadata for the ledger holding the in-progress edit log
* segment
*/
public class CurrentInProgressMetadata {
private static final Log LOG = LogFactory.getLog(CurrentInProgressMetadata.class);
// Keep a thread local Writable for serialization as to avoid allocating
// a new object each time we write
private static final ThreadLocal<CurrentInProgressMetadataWritable>
localWritable = new ThreadLocal<CurrentInProgressMetadataWritable>() {
@Override
protected CurrentInProgressMetadataWritable initialValue() {
return new CurrentInProgressMetadataWritable();
}
};
private final ZooKeeperIface zooKeeper;
// Full path to znode storing the information about the current in-progress
// ledger
private final String fullyQualifiedZNode;
// The version we've last read from the ZNode to make sure we don't
// update the in-progress ledger without reading the existing one first
private final AtomicInteger expectedZNodeVersion;
// Identifiers the process that last updated the ZNode
private final String hostname;
/**
* Creates and initialized an instance that will point to the metadata about
* ledger containing the current in-progress edit log segment in a specified
* ZNode. If the ZNode does not exist, the ZNode will be created and set to
* null.
* @param zooKeeper The {@link ZooKeeperIface} implementation. Use
* {@link RecoveringZooKeeper} in order to handle
* transient ZooKeeper issues
* @param fullyQualifiedZNode The path to the ZNode that will be used
* by this instance to point to the ledger
* metadata for the ledger containing the
* current in-progress log segment
* @throws IOException
*/
public CurrentInProgressMetadata(ZooKeeperIface zooKeeper,
String fullyQualifiedZNode) throws IOException {
this.zooKeeper = zooKeeper;
this.fullyQualifiedZNode = fullyQualifiedZNode;
this.expectedZNodeVersion = new AtomicInteger(-1);
// Hostname is used as part of the human-readable identifier
// for the process that last updated the in-progress ZNode
hostname = InetAddress.getLocalHost().toString();
}
public void init() throws IOException {
boolean alreadyExists = false;
try {
// Check whether or not the specified ZNode already exists
Stat stat = zooKeeper.exists(this.fullyQualifiedZNode,
false);
if (stat == null) {
try { // If the specified ZNode does not exist, create it
zooKeeper.create(this.fullyQualifiedZNode, null,
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
LOG.info("Created ZNode " + fullyQualifiedZNode);
} catch (KeeperException.NodeExistsException e) {
LOG.warn(fullyQualifiedZNode + " created by another process. " +
"Ignoring " + e);
alreadyExists = true;
}
} else {
alreadyExists = true;
}
} catch (KeeperException e) {
keeperException("Unrecoverable ZooKeeper error creating " +
fullyQualifiedZNode, e);
} catch (InterruptedException e) {
interruptedException("Interrupted while creating " + fullyQualifiedZNode,
e);
}
if (alreadyExists) {
// If the znode already exists, read it, print the owner information and
// update the version number from znode's stat
CurrentInProgressMetadataWritable owner = localWritable.get();
if (readAndUpdateVersion(owner)) {
LOG.info(fullyQualifiedZNode + " held by " + owner.getId() +
" and points to " + owner.getPath());
} else {
LOG.info(fullyQualifiedZNode + " is currently clear.");
}
}
}
/**
* Update the data in the ZNode to point to a the ZNode containing the
* metadata for the ledger containing the current in-progress edit log
* segment.
* @param newPath Path to the metadata for the current ledger
* @throws StaleVersionException If path read is out of date compared to the
* version in ZooKeeper
* @throws IOException If there is an error talking to ZooKeeper
*/
public void update(String newPath) throws IOException {
String id = hostname + Thread.currentThread().getId();
CurrentInProgressMetadataWritable cip = localWritable.get();
cip.set(id, newPath);
byte[] data = WritableUtil.writableToByteArray(cip);
try {
zooKeeper.setData(fullyQualifiedZNode, data, expectedZNodeVersion.get());
if (LOG.isDebugEnabled()) {
LOG.debug("Set " + fullyQualifiedZNode + " to point to " + newPath);
}
} catch (KeeperException.BadVersionException e) {
// Throw an exception if we try to update without having read the
// current version
LOG.error(fullyQualifiedZNode + " has been updated by another process",
e);
throw new StaleVersionException(fullyQualifiedZNode +
"has been updated by another process!");
} catch (KeeperException e) {
keeperException("Unrecoverable ZooKeeper error updating " +
fullyQualifiedZNode, e);
} catch (InterruptedException e) {
interruptedException("Interrupted updating " + fullyQualifiedZNode, e);
}
}
/**
* Read the full path to the ZNode holding the metadata for the ledger
* containing the current in-progress edit log segment or null if no segment
* is currently in-progress
* @return Full path to the ledger's metadata ZNode or null if no
* segment is in progress
* @throws IOException If there is an error talking to ZooKeeper
*/
public String read() throws IOException {
CurrentInProgressMetadataWritable cip = localWritable.get();
if (readAndUpdateVersion(cip)) {
return cip.getPath();
} else {
if (LOG.isDebugEnabled()) {
LOG.debug(fullyQualifiedZNode + " is currently clear.");
}
}
return null;
}
/**
* Reads the ZNode specified in constructor into an existing
* {@link CurrentInProgressMetadataWritable} instance and sets expected
* ZNode version (for next update) the the current version of the
* specified ZNode
* @param target The writable instance to read into
* @return True if there is a log segment is current in-progress, false
* otherwise
* @throws IOException If there is an unrecoverable error talking to
* ZooKeeper
*/
private boolean readAndUpdateVersion(CurrentInProgressMetadataWritable target)
throws IOException {
Stat stat = new Stat();
try {
byte[] data = zooKeeper.getData(fullyQualifiedZNode, false, stat);
expectedZNodeVersion.set(stat.getVersion());
if (data != null) {
WritableUtil.readWritableFromByteArray(data, target);
return true;
}
} catch (KeeperException e) {
keeperException("Unrecoverable ZooKeeper error reading from " +
fullyQualifiedZNode, e);
} catch (InterruptedException e) {
interruptedException("Interrupted reading from " + fullyQualifiedZNode,
e);
}
return false;
}
/**
* Clear out the data in the ZNode specified in the constructor to indicate
* that no segment is currently in progress. This does not delete the
* actual ZNode.
* @throws IOException If there is an unrecoverable error talking to
* ZooKeeper
*/
public void clear() throws IOException {
try {
zooKeeper.setData(fullyQualifiedZNode, null, expectedZNodeVersion.get());
} catch (KeeperException.BadVersionException e) {
LOG.error(fullyQualifiedZNode + " has been updated by another process",
e);
throw new StaleVersionException(fullyQualifiedZNode +
"has been updated by another process!");
} catch (KeeperException e) {
keeperException("Unrecoverable ZooKeeper error clearing " +
fullyQualifiedZNode, e);
} catch (InterruptedException e) {
interruptedException("Interrupted clearing " + fullyQualifiedZNode, e);
}
}
}