Java tutorial
/* * 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.accumulo.server.logger; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.nio.channels.FileLock; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.UUID; import org.apache.accumulo.cloudtrace.instrument.thrift.TraceWrap; import org.apache.accumulo.cloudtrace.thrift.TInfo; import org.apache.accumulo.core.Constants; import org.apache.accumulo.core.client.AccumuloSecurityException; import org.apache.accumulo.core.conf.AccumuloConfiguration; import org.apache.accumulo.core.conf.Property; import org.apache.accumulo.core.data.thrift.TKeyExtent; import org.apache.accumulo.core.data.thrift.TMutation; import org.apache.accumulo.core.file.FileUtil; import org.apache.accumulo.core.security.SystemPermission; import org.apache.accumulo.core.security.thrift.AuthInfo; import org.apache.accumulo.core.security.thrift.SecurityErrorCode; import org.apache.accumulo.core.security.thrift.ThriftSecurityException; import org.apache.accumulo.core.tabletserver.thrift.LogCopyInfo; import org.apache.accumulo.core.tabletserver.thrift.LogFile; import org.apache.accumulo.core.tabletserver.thrift.LoggerClosedException; import org.apache.accumulo.core.tabletserver.thrift.MutationLogger; import org.apache.accumulo.core.tabletserver.thrift.NoSuchLogIDException; import org.apache.accumulo.core.tabletserver.thrift.TabletMutations; import org.apache.accumulo.core.util.AddressUtil; import org.apache.accumulo.core.util.CachedConfiguration; import org.apache.accumulo.core.util.UtilWaitThread; import org.apache.accumulo.core.zookeeper.ZooUtil; import org.apache.accumulo.server.Accumulo; import org.apache.accumulo.server.ServerConstants; import org.apache.accumulo.server.client.HdfsZooInstance; import org.apache.accumulo.server.conf.ServerConfiguration; import org.apache.accumulo.server.logger.LogWriter.LogWriteException; import org.apache.accumulo.server.security.Authenticator; import org.apache.accumulo.server.security.SecurityUtil; import org.apache.accumulo.server.security.ZKAuthenticator; import org.apache.accumulo.server.trace.TraceFileSystem; import org.apache.accumulo.server.util.FileSystemMonitor; import org.apache.accumulo.server.util.Halt; import org.apache.accumulo.server.util.TServerUtils; import org.apache.accumulo.server.util.TServerUtils.ServerPort; import org.apache.accumulo.server.zookeeper.IZooReaderWriter; import org.apache.accumulo.server.zookeeper.ZooReaderWriter; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.thrift.TBase; import org.apache.thrift.TException; import org.apache.thrift.server.TServer; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.Watcher.Event.EventType; import org.apache.zookeeper.Watcher.Event.KeeperState; /** * A Mutation logging service. * * This class will register the logging service in ZooKeeper, log updates from tservers, and provided logs to HDFS for recovery. Wraps the LogWriter, but * provides configuration, authentication and ZooKeeper registration. This makes the LogWriter easier to test. The service will stop if it loses its ephemeral * registration in ZooKeeper. */ public class LogService implements MutationLogger.Iface, Watcher { static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(LogService.class); private Configuration conf; private Authenticator authenticator; private TServer service; private LogWriter writer_; private MutationLogger.Iface writer; private String ephemeralNode; enum ShutdownState { STARTED, REGISTERED, WAITING_FOR_HALT, HALT }; private ShutdownState shutdownState = ShutdownState.STARTED; synchronized void switchState(ShutdownState state) { LOG.info("Switching from " + shutdownState + " to " + state); shutdownState = state; } synchronized private void closedCheck() throws LoggerClosedException { if (!shutdownState.equals(ShutdownState.REGISTERED)) throw new LoggerClosedException(); } private List<FileLock> fileLocks = new ArrayList<FileLock>(); private final String addressString; public static void main(String[] args) throws Exception { LogService logService; SecurityUtil.serverLogin(); try { logService = new LogService(args); } catch (Exception e) { LOG.fatal("Failed to initialize log service args=" + Arrays.asList(args), e); throw e; } try { logService.run(); } catch (Exception ex) { LOG.error("Unexpected exception, exiting.", ex); } } public LogService(String[] args) throws UnknownHostException, KeeperException, InterruptedException, IOException { try { Accumulo.init("logger"); } catch (UnknownHostException e1) { LOG.error("Error reading logging configuration"); } FileSystemMonitor.start(Property.LOGGER_MONITOR_FS); conf = CachedConfiguration.getInstance(); FileSystem fs = null; try { fs = TraceFileSystem.wrap(FileUtil.getFileSystem(conf, ServerConfiguration.getSiteConfiguration())); } catch (IOException e) { String msg = "Exception connecting to FileSystem"; LOG.error(msg, e); throw new RuntimeException(msg); } final Set<String> rootDirs = new HashSet<String>(); for (String root : ServerConfiguration.getSystemConfiguration().get(Property.LOGGER_DIR).split(",")) { if (!root.startsWith("/")) root = System.getenv("ACCUMULO_HOME") + "/" + root; else if (root.equals("")) root = System.getProperty("org.apache.accumulo.core.dir.log"); else if (root == null || root.isEmpty()) { String msg = "Write-ahead log directory not set!"; LOG.fatal(msg); throw new RuntimeException(msg); } rootDirs.add(root); } for (String root : rootDirs) { File rootFile = new File(root); rootFile.mkdirs(); FileOutputStream lockOutputStream = new FileOutputStream(root + "/.lock"); FileLock fileLock = lockOutputStream.getChannel().tryLock(); if (fileLock == null) throw new IOException("Failed to acquire lock file"); fileLocks.add(fileLock); try { File test = new File(root, "test_writable"); if (!test.mkdir()) throw new RuntimeException("Unable to write to write-ahead log directory " + root); test.delete(); } catch (Throwable t) { LOG.fatal("Unable to write to write-ahead log directory", t); throw new RuntimeException(t); } LOG.info("Storing recovery logs at " + root); } authenticator = ZKAuthenticator.getInstance(); int poolSize = ServerConfiguration.getSystemConfiguration().getCount(Property.LOGGER_COPY_THREADPOOL_SIZE); boolean archive = ServerConfiguration.getSystemConfiguration().getBoolean(Property.LOGGER_ARCHIVE); AccumuloConfiguration acuConf = ServerConfiguration.getSystemConfiguration(); writer_ = new LogWriter(acuConf, fs, rootDirs, HdfsZooInstance.getInstance().getInstanceID(), poolSize, archive); // call before putting this service online removeIncompleteCopies(acuConf, fs, rootDirs); InvocationHandler h = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Throwable error; try { return method.invoke(writer_, args); } catch (InvocationTargetException ex) { if (ex.getCause() instanceof TException) { throw ex.getCause(); } if (ex.getCause() instanceof TBase) { throw ex.getCause(); } if (ex.getCause() instanceof LogWriteException) { for (String root : rootDirs) { final File rootFile = new File(root); if ((rootFile.getUsableSpace() / (float) rootFile.getTotalSpace()) < 0.95) { LOG.fatal("Logger appears to be running out of space, quitting."); service.stop(); } } } error = ex; } catch (Throwable t) { error = t; } LOG.error("Error invoking log writer: ", error); throw error; } }; writer = (MutationLogger.Iface) Proxy.newProxyInstance(MutationLogger.Iface.class.getClassLoader(), new Class[] { MutationLogger.Iface.class }, h); // Create the thrift-based logging service MutationLogger.Processor processor = new MutationLogger.Processor(TraceWrap.service(this)); ServerPort sp = TServerUtils.startServer(Property.LOGGER_PORT, processor, this.getClass().getSimpleName(), "Logger Client Service Handler", Property.LOGGER_PORTSEARCH, Property.LOGGER_MINTHREADS, Property.LOGGER_THREADCHECK); service = sp.server; InetSocketAddress address = new InetSocketAddress(Accumulo.getLocalAddress(args), sp.port); addressString = AddressUtil.toString(address); registerInZooKeeper(Constants.ZLOGGERS); this.switchState(ShutdownState.REGISTERED); Accumulo.enableTracing(address.getHostName(), "logger"); } /** * @param acuConf * @param fs * @param rootDirs * @throws IOException */ private void removeIncompleteCopies(AccumuloConfiguration acuConf, FileSystem fs, Set<String> rootDirs) throws IOException { if (acuConf.getBoolean(Property.MASTER_RECOVERY_SORT_MAPREDUCE)) { return; } Set<String> walogs = new HashSet<String>(); for (String root : rootDirs) { File rootFile = new File(root); for (File walog : rootFile.listFiles()) { try { UUID.fromString(walog.getName()); walogs.add(walog.getName()); } catch (IllegalArgumentException iea) { LOG.debug("Ignoring " + walog.getName()); } } } // look for .recovered that are not finished for (String walog : walogs) { Path path = new Path(ServerConstants.getRecoveryDir() + "/" + walog + ".recovered"); if (fs.exists(path) && !fs.exists(new Path(path, "finished"))) { LOG.debug("Incomplete copy/sort in dfs, deleting " + path); fs.delete(path, true); } } } public void run() { try { while (!service.isServing()) { UtilWaitThread.sleep(500); } while (service.isServing()) { UtilWaitThread.sleep(500); } LOG.info("Logger shutting down"); writer_.shutdown(); } catch (Throwable t) { LOG.fatal("Error in LogService", t); throw new RuntimeException(t); } Runtime.getRuntime().halt(0); } void registerInZooKeeper(String zooDir) { try { IZooReaderWriter zoo = ZooReaderWriter.getInstance(); String path = ZooUtil.getRoot(HdfsZooInstance.getInstance()) + zooDir; path += "/logger-"; path = zoo.putEphemeralSequential(path, addressString.getBytes()); ephemeralNode = path; zoo.exists(path, this); } catch (Exception ex) { throw new RuntimeException("Unexpected error creating zookeeper entry " + zooDir); } } private void checkForSystemPrivs(String request, AuthInfo credentials) throws ThriftSecurityException { try { if (!authenticator.hasSystemPermission(credentials, credentials.user, SystemPermission.SYSTEM)) { LOG.warn("Got " + request + " from user: " + credentials.user); throw new ThriftSecurityException(credentials.user, SecurityErrorCode.PERMISSION_DENIED); } } catch (AccumuloSecurityException e) { LOG.warn("Got " + request + " from unauthenticatable user: " + e.getUser()); throw e.asThriftException(); } } @Override public void close(TInfo info, long id) throws NoSuchLogIDException, LoggerClosedException, TException { closedCheck(); writer.close(info, id); } @Override public LogCopyInfo startCopy(TInfo info, AuthInfo credentials, String localLog, String fullyQualifiedFileName, boolean sort) throws ThriftSecurityException, TException { checkForSystemPrivs("copy", credentials); LogCopyInfo lci = writer.startCopy(null, credentials, localLog, fullyQualifiedFileName, sort); lci.loggerZNode = ephemeralNode; return lci; } @Override public LogFile create(TInfo info, AuthInfo credentials, String tserverSession) throws ThriftSecurityException, LoggerClosedException, TException { checkForSystemPrivs("create", credentials); closedCheck(); return writer.create(info, credentials, tserverSession); } @Override public void log(TInfo info, long id, long seq, int tid, TMutation mutation) throws NoSuchLogIDException, LoggerClosedException, TException { closedCheck(); writer.log(info, id, seq, tid, mutation); } @Override public void logManyTablets(TInfo info, long id, List<TabletMutations> mutations) throws NoSuchLogIDException, LoggerClosedException, TException { closedCheck(); writer.logManyTablets(info, id, mutations); } @Override public void minorCompactionFinished(TInfo info, long id, long seq, int tid, String fqfn) throws NoSuchLogIDException, LoggerClosedException, TException { closedCheck(); writer.minorCompactionFinished(info, id, seq, tid, fqfn); } @Override public void minorCompactionStarted(TInfo info, long id, long seq, int tid, String fqfn) throws NoSuchLogIDException, LoggerClosedException, TException { closedCheck(); writer.minorCompactionStarted(info, id, seq, tid, fqfn); } @Override public void defineTablet(TInfo info, long id, long seq, int tid, TKeyExtent tablet) throws NoSuchLogIDException, LoggerClosedException, TException { closedCheck(); writer.defineTablet(info, id, seq, tid, tablet); } @Override public void process(WatchedEvent event) { LOG.debug("event " + event.getPath() + " " + event.getType() + " " + event.getState()); if (event.getState() == KeeperState.Expired) { LOG.warn("Logger lost zookeeper registration at " + event.getPath()); service.stop(); } else if (event.getType() == EventType.NodeDeleted) { LOG.info("Logger zookeeper entry lost " + event.getPath()); String[] path = event.getPath().split("/"); if (path[path.length - 1].equals(Constants.ZLOGGERS) && this.shutdownState == ShutdownState.REGISTERED) { LOG.fatal("Stopping server, zookeeper entry lost " + event.getPath()); service.stop(); } } } @Override public List<String> getClosedLogs(TInfo info, AuthInfo credentials) throws ThriftSecurityException, TException { checkForSystemPrivs("getClosedLogs", credentials); return writer.getClosedLogs(info, credentials); } @Override public void remove(TInfo info, AuthInfo credentials, List<String> files) throws TException { try { checkForSystemPrivs("remove", credentials); writer.remove(info, credentials, files); } catch (ThriftSecurityException ex) { LOG.error(ex, ex); } } @Override public void beginShutdown(TInfo tinfo, AuthInfo credentials) throws TException { try { checkForSystemPrivs("beginShutdown", credentials); writer.beginShutdown(tinfo, credentials); switchState(ShutdownState.WAITING_FOR_HALT); } catch (ThriftSecurityException ex) { LOG.error(ex, ex); } } @Override public void halt(TInfo tinfo, AuthInfo credentials) throws TException { try { checkForSystemPrivs("halt", credentials); Halt.halt(0, new Runnable() { @Override public void run() { LOG.info("Halting by request"); } }); } catch (ThriftSecurityException ex) { LOG.error(ex, ex); } } }