Index: src/main/java/org/apache/hadoop/hbase/ClusterStatus.java =================================================================== --- src/main/java/org/apache/hadoop/hbase/ClusterStatus.java (revision 942318) +++ src/main/java/org/apache/hadoop/hbase/ClusterStatus.java (revision ) @@ -37,6 +37,7 @@ *

* ClusterStatus provides clients with information such as: *

*/ public class ClusterStatus extends VersionedWritable { - private static final byte VERSION = 0; + private static final byte VERSION = 1; private String hbaseVersion; + private String activeMaster; private Collection liveServerInfo; private Collection deadServers; private NavigableMap intransition; @@ -140,7 +142,8 @@ if (!(o instanceof ClusterStatus)) { return false; } - return (getVersion() == ((ClusterStatus)o).getVersion()) && + return getVersion() == ((ClusterStatus)o).getVersion() && + getActiveMaster().equals(((ClusterStatus)o).getActiveMaster()) && getHBaseVersion().equals(((ClusterStatus)o).getHBaseVersion()) && liveServerInfo.equals(((ClusterStatus)o).liveServerInfo) && deadServers.equals(((ClusterStatus)o).deadServers); @@ -164,6 +167,16 @@ // /** + * Returns the IP address of the currently active master as reported + * by ZooKeeper. + * + * @return The IP address of the currently active master. + */ + public String getActiveMaster() { + return activeMaster; + } + + /** * Returns detailed region server information: A list of * {@link HServerInfo}, containing server load and resource usage * statistics as {@link HServerLoad}, containing per-region @@ -178,6 +191,15 @@ // Setters // + /** + * Sets the currently active master's IP address. + * + * @param activeMaster The new IP address to set. + */ + public void setActiveMaster(String activeMaster) { + this.activeMaster = activeMaster; + } + public void setServerInfo(Collection serverInfo) { this.liveServerInfo = serverInfo; } @@ -201,6 +223,7 @@ public void write(DataOutput out) throws IOException { super.write(out); out.writeUTF(hbaseVersion); + out.writeUTF(activeMaster); out.writeInt(liveServerInfo.size()); for (HServerInfo server: liveServerInfo) { server.write(out); @@ -219,6 +242,7 @@ public void readFields(DataInput in) throws IOException { super.readFields(in); hbaseVersion = in.readUTF(); + activeMaster = in.readUTF(); int count = in.readInt(); liveServerInfo = new ArrayList(count); for (int i = 0; i < count; i++) { Index: src/main/java/org/apache/hadoop/hbase/util/InfoServer.java =================================================================== --- src/main/java/org/apache/hadoop/hbase/util/InfoServer.java (revision 948298) +++ src/main/java/org/apache/hadoop/hbase/util/InfoServer.java (revision ) @@ -20,6 +20,7 @@ package org.apache.hadoop.hbase.util; +import org.apache.hadoop.hbase.master.HMaster; import org.apache.hadoop.http.HttpServer; import org.mortbay.jetty.handler.ContextHandlerCollection; import org.mortbay.jetty.servlet.Context; @@ -34,13 +35,13 @@ * is to serve up status information for the server. * There are three contexts: * "/stacks/" -> points to stack trace - * "/static/" -> points to common static files (src/hbase-webapps/static) - * "/" -> the jsp server code from (src/hbase-webapps/) + * "/static/" -> points to common static files (src/webapps/static) + * "/" -> the jsp server code from (src/webapps/) */ public class InfoServer extends HttpServer { /** * Create a status server on the given port. - * The jsp scripts are taken from src/hbase-webapps/name. + * The jsp scripts are taken from src/webapps/name. * @param name The name of the server * @param bindAddress address to bind to * @param port The port to use on the server @@ -52,7 +53,10 @@ throws IOException { super(name, bindAddress, port, findPort); webServer.addHandler(new ContextHandlerCollection()); + if (HMaster.MASTER.equals(name)) { + addFilter("master-locator", MasterLocatorFilter.class.getName(), null); - } + } + } protected void addDefaultApps(ContextHandlerCollection parent, String appDir) throws IOException { @@ -103,7 +107,7 @@ throws IOException { URL url = InfoServer.class.getClassLoader().getResource(path); if (url == null) - throw new IOException("hbase-webapps not found in CLASSPATH: " + path); + throw new IOException("webapps not found in CLASSPATH: " + path); return url.toString(); } @@ -116,7 +120,7 @@ public static String getWebAppDir(final String webappName) throws IOException { String webappDir; - webappDir = getWebAppsPath("hbase-webapps/" + webappName); + webappDir = getWebAppsPath("webapps/" + webappName); return webappDir; } } Index: src/main/java/org/apache/hadoop/hbase/master/HMaster.java =================================================================== --- src/main/java/org/apache/hadoop/hbase/master/HMaster.java (revision 951652) +++ src/main/java/org/apache/hadoop/hbase/master/HMaster.java (revision ) @@ -747,7 +747,7 @@ // table would sit should it exist. Open scanner on it. If a region // for the table we want to create already exists, then table already // created. Throw already-exists exception. - MetaRegion m = regionManager.getFirstMetaRegionForRegion(newRegions[0]); + MetaRegion m = regionManager.getFirstMetaRegionForRegion(newRegions[0]); byte [] metaRegionName = m.getRegionName(); HRegionInterface srvr = this.connection.getHRegionConnection(m.getServer()); byte[] firstRowInTable = Bytes.toBytes(tableName + ",,"); @@ -1001,7 +1001,7 @@ HRegionInfo hri = getHRegionInfo(rr.getRow(), rr); if (hostnameAndPort == null) { // Get server from the .META. if it wasn't passed as argument - hostnameAndPort = + hostnameAndPort = Bytes.toString(rr.getValue(CATALOG_FAMILY, SERVER_QUALIFIER)); } // Take region out of the intransistions in case it got stuck there doing @@ -1032,6 +1032,10 @@ public ClusterStatus getClusterStatus() { ClusterStatus status = new ClusterStatus(); status.setHBaseVersion(VersionInfo.getVersion()); + HServerAddress hsa = zooKeeperWrapper.readMasterAddress(null); + if (hsa != null) { + status.setActiveMaster(hsa.getBindAddress()); + } status.setServerInfo(serverManager.getServersToServerInfo().values()); status.setDeadServers(serverManager.getDeadServers()); status.setRegionsInTransition(this.regionManager.getRegionsInTransition()); Index: src/main/java/org/apache/hadoop/hbase/util/MasterLocatorFilter.java =================================================================== --- src/main/java/org/apache/hadoop/hbase/util/MasterLocatorFilter.java (revision ) +++ src/main/java/org/apache/hadoop/hbase/util/MasterLocatorFilter.java (revision ) @@ -0,0 +1,97 @@ +/** + * Copyright 2010 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 org.apache.hadoop.hbase.util; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HServerAddress; +import org.apache.hadoop.hbase.client.HBaseAdmin; +import org.apache.hadoop.hbase.client.HConnection; +import org.apache.hadoop.hbase.master.HMaster; +import org.apache.hadoop.hbase.zookeeper.ZooKeeperWrapper; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * A filter that finds the currently active master and redirects to it from + * a non-active master. + */ +public class MasterLocatorFilter implements Filter { + + private FilterConfig filterConfig = null; + private InetAddress address = null; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + this.filterConfig = filterConfig; + try { + this.address = InetAddress.getLocalHost(); + } catch (UnknownHostException e) { + throw new ServletException("Could not get own host name.", e); + } + } + + @Override + public void destroy() { + this.filterConfig = null; + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, + FilterChain chain) throws IOException, ServletException { + HMaster master = (HMaster) filterConfig.getServletContext(). + getAttribute(HMaster.MASTER); + Configuration conf = master.getConfiguration(); + HBaseAdmin hbadmin = new HBaseAdmin(conf); + HConnection connection = hbadmin.getConnection(); + ZooKeeperWrapper wrapper = connection.getZooKeeperWrapper(); + HServerAddress hsa = wrapper.readMasterAddressOrThrow(); + String ipAddr = hsa.getHostname(); + // compare local IP with current master IP address + if (ipAddr != null && ipAddr.equals(address.getHostAddress())) { + HttpServletRequest hreq = ((HttpServletRequest) request); + HttpServletResponse hres = ((HttpServletResponse) response); + // reconstruct URL with new address but same port and params + StringBuffer url = new StringBuffer(); + url.append(hreq.getScheme()); + url.append("://"); + url.append(ipAddr); + url.append(':'); + url.append(hreq.getServerPort()); + url.append(hreq.getRequestURI()); + String qry = hreq.getQueryString(); + if (qry != null) { + url.append('?'); + url.append(qry); + } + // redirect to active master + hres.sendRedirect(url.toString()); + } else { + // or move on locally, i.e. let it pass + chain.doFilter(request, response); + } + } + +}