/** * 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.notifier.server; import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hdfs.AvatarZooKeeperClient; import org.apache.hadoop.hdfs.CachingAvatarZooKeeperClient; import org.apache.hadoop.hdfs.qjournal.client.QuorumJournalManager; import org.apache.hadoop.hdfs.server.common.Storage.StorageDirectory; import org.apache.hadoop.hdfs.server.common.StorageInfo; import org.apache.hadoop.hdfs.server.common.Util; import org.apache.hadoop.hdfs.server.namenode.AvatarNode; import org.apache.hadoop.hdfs.server.namenode.FileJournalManagerReadOnly; import org.apache.hadoop.hdfs.server.namenode.JournalManager; import org.apache.hadoop.hdfs.server.namenode.NNStorage; import org.apache.hadoop.hdfs.server.namenode.NameNode; import org.apache.hadoop.hdfs.server.protocol.NamespaceInfo; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.data.Stat; /** * This class supports reading the edit logs from HDFS Avatar nodes, so it is * failover-aware, and the HDFS namenode failover would be transparent for * this log reader. * */ public class ServerLogReaderAvatar extends ServerLogReaderTransactional { public static final Log LOG = LogFactory .getLog(ServerLogReaderAvatar.class); // remote journal from which the reader is consuming transactions private final JournalManager remoteJournalManagerZero; private final JournalManager remoteJournalManagerOne; /** * The location of the edits logs for Avatar zero and one node. */ private final URI editsUriZero; private final URI editsUriOne; /* We will look up the rpc address in zookeeper entity to determine * which node is the current primary node */ // rpc address of the avatar zero node private URI avatarZeroURI; // rpc address of the avatar one node private URI avatarOneURI; // the rpc address we read from zookeeper entity. private URI primaryURI = null; // the name of the zookeeper entity. private URI logicalName; private CachingAvatarZooKeeperClient zk; private static final int FAILOVER_RETRY_SLEEP = 1000; static { Configuration.addDefaultResource("avatar-default.xml"); Configuration.addDefaultResource("avatar-site.xml"); } static void throwIOException(String msg, Throwable e) throws IOException { LOG.error(msg, e); throw new IOException(msg, e); } public ServerLogReaderAvatar(IServerCore core) throws IOException { super(core, null); // append the service name to each avatar meta directory name String serviceName = core.getServiceName(); if (serviceName != null && !serviceName.equals("")) { AvatarNode.adjustMetaDirectoryNames(conf, serviceName); } else { serviceName = ""; } // edits directory uri String editsStringZero = core.getConfiguration().get( AvatarNode.DFS_SHARED_EDITS_DIR0_KEY); String editsStringOne = core.getConfiguration().get( AvatarNode.DFS_SHARED_EDITS_DIR1_KEY); editsUriZero = Util.stringAsURI(editsStringZero); editsUriOne = Util.stringAsURI(editsStringOne); remoteJournalManagerZero = constructJournalManager(editsUriZero); remoteJournalManagerOne = constructJournalManager(editsUriOne); try { this.logicalName = new URI("hdfs://" + conf.get(NameNode.DFS_NAMENODE_RPC_ADDRESS_KEY + (serviceName.isEmpty() ? "" : "." + serviceName), "")); this.avatarZeroURI = addrToURI(conf.get( AvatarNode.DFS_NAMENODE_RPC_ADDRESS0_KEY + (serviceName.isEmpty() ? "" : "." + serviceName), "")); this.avatarOneURI = addrToURI(conf.get( AvatarNode.DFS_NAMENODE_RPC_ADDRESS1_KEY + (serviceName.isEmpty() ? "" : "." + serviceName), "")); zk = new CachingAvatarZooKeeperClient(conf, null); LOG.info("Initializing input stream"); initialize(); LOG.info("Initialization completed"); } catch (URISyntaxException e) { throwIOException(e.getMessage(), e); } } private JournalManager constructJournalManager(URI editsUri) throws IOException { if (editsUri.getScheme().equals(NNStorage.LOCAL_URI_SCHEME)) { StorageDirectory sd = new NNStorage(new StorageInfo()).new StorageDirectory( new File(editsUri.getPath())); return new FileJournalManagerReadOnly(sd); } else if (editsUri.getScheme().equals(QuorumJournalManager.QJM_URI_SCHEME)) { return new QuorumJournalManager(conf, editsUri, new NamespaceInfo(new StorageInfo()), null, false); } else { throwIOException("Other journals not supported yet.", null); } return null; } /** * Construct URI from address string. */ private URI addrToURI(String addrString) throws URISyntaxException { if (addrString.startsWith(logicalName.getScheme())) { // if the scheme is correct, we just return the new URI(...) return new URI(addrString); } else { // otherwise, we will extract the host and port from the address // string, and construct the URI using logicalName's scheme (hdfs://). if (addrString.indexOf(":") == -1) { // This is not a valid addr string return null; } String fsHost = addrString.substring(0, addrString.indexOf(":")); int port = Integer.parseInt(addrString .substring(addrString.indexOf(":") + 1)); return new URI(logicalName.getScheme(), logicalName.getUserInfo(), fsHost, port, logicalName.getPath(), logicalName.getQuery(), logicalName.getFragment()); } } /** * Detect the primary node and the current Journal Manager; * @throws IOException */ protected void detectJournalManager() throws IOException { int failures = 0; do { try { Stat stat = new Stat(); String primaryAddr = zk.getPrimaryAvatarAddress(logicalName, stat, true, true); if (primaryAddr == null || primaryAddr.trim().isEmpty()) { primaryURI = null; remoteJournalManager = null; LOG.warn("Failover detected, wait for it to finish..."); failures = 0; sleep(FAILOVER_RETRY_SLEEP); continue; } primaryURI = addrToURI(primaryAddr); LOG.info("Read primary URI from zk: " + primaryURI); if (primaryURI.equals(avatarZeroURI)) { remoteJournalManager = remoteJournalManagerZero; } else if (primaryURI.equals(avatarOneURI)) { remoteJournalManager = remoteJournalManagerOne; } else { LOG.warn("Invalid primaryURI: " + primaryURI); primaryURI = null; remoteJournalManager = null; failures = 0; sleep(FAILOVER_RETRY_SLEEP); } } catch (KeeperException kex) { if (KeeperException.Code.CONNECTIONLOSS == kex.code() && failures < AvatarZooKeeperClient.ZK_CONNECTION_RETRIES) { failures++; // This means there was a failure connecting to zookeeper // we should retry since some nodes might be down. sleep(FAILOVER_RETRY_SLEEP); continue; } throwIOException(kex.getMessage(), kex); } catch (InterruptedException e) { throwIOException(e.getMessage(), e); } catch (URISyntaxException e) { throwIOException(e.getMessage(), e); } } while (remoteJournalManager == null); } }