/* * Copyright 2010 NCHOVY * * Licensed 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.krakenapps.sentry.impl; import java.io.File; import java.io.IOException; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.felix.ipojo.annotations.Component; import org.apache.felix.ipojo.annotations.Invalidate; import org.apache.felix.ipojo.annotations.Provides; import org.apache.felix.ipojo.annotations.Requires; import org.apache.felix.ipojo.annotations.Validate; import org.krakenapps.log.api.Log; import org.krakenapps.log.api.LogPipe; import org.krakenapps.log.api.Logger; import org.krakenapps.log.api.LoggerEventListener; import org.krakenapps.log.api.LoggerFactory; import org.krakenapps.log.api.LoggerFactoryRegistry; import org.krakenapps.log.api.LoggerRegistry; import org.krakenapps.log.api.LoggerRegistryEventListener; import org.krakenapps.rpc.RpcContext; import org.krakenapps.rpc.RpcException; import org.krakenapps.rpc.RpcSession; import org.krakenapps.sentry.Base; import org.krakenapps.sentry.Sentry; import org.krakenapps.sentry.SentryCommandHandler; import org.krakenapps.sentry.SentryMethod; @Component(name = "sentry-basic-command-handler") @Provides(specifications = { SentryCommandHandler.class }) public class BasicCommandHandler implements SentryCommandHandler, LoggerRegistryEventListener, LogPipe, LoggerEventListener { private final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(BasicCommandHandler.class.getName()); @Requires private Sentry sentry; @Requires private LoggerRegistry loggerRegistry; @Requires private LoggerFactoryRegistry loggerFactoryRegistry; // one connected logger to multiple bases private ConcurrentMap<String, Set<String>> connectedLoggers; public BasicCommandHandler() { connectedLoggers = new ConcurrentHashMap<String, Set<String>>(); } @Validate public void start() { connectedLoggers.clear(); loggerRegistry.addListener(this); } @Invalidate public void stop() { if (loggerRegistry != null) { loggerRegistry.removeListener(this); for (String loggerName : connectedLoggers.keySet()) { Logger logger = loggerRegistry.getLogger("local", loggerName); if (logger != null) { logger.removeLogPipe(this); logger.removeEventListener(this); } } } connectedLoggers.clear(); } @Override public Collection<String> getFeatures() { return Arrays.asList("logger-control", "disk-info", "nic-info"); } @SentryMethod public List<Object> getLoggerFactories() { List<Object> l = new ArrayList<Object>(); // send only local factories for (LoggerFactory factory : loggerFactoryRegistry.getLoggerFactories()) if (factory.getNamespace().equals("local")) l.add(LoggerFactorySerializer.toMap(factory)); return l; } @SentryMethod public Object getLoggerFactory(String name) { log.trace("kraken sentry: querying get logger factory [{}]", name); return LoggerFactorySerializer.toMap(loggerFactoryRegistry.getLoggerFactory(name)); } @SentryMethod public List<Object> getLoggers() { List<Object> l = new ArrayList<Object>(); // send only loggers in local namespace for (Logger logger : loggerRegistry.getLoggers()) if (logger.getNamespace().equals("local")) l.add(LoggerFactorySerializer.toMap(logger)); return l; } @SentryMethod public void createLogger(String factoryName, String name, String description, Map<String, Object> props) { LoggerFactory factory = loggerFactoryRegistry.getLoggerFactory(factoryName); if (factory == null) throw new RpcException("logger factory not found: " + factoryName); Properties config = convert(props); log.trace("kraken sentry: create logger [factory={}, name={}, desc={}] with config [{}]", new Object[] { factory, name, description, buildConfigLog(config) }); factory.newLogger(name, description, config); } private String buildConfigLog(Properties config) { StringBuilder sb = new StringBuilder(); int i = 0; for (Object key : config.keySet()) { if (i != 0) sb.append(", "); sb.append(key.toString()); sb.append("="); sb.append(config.get(key)); i++; } return sb.toString(); } private Properties convert(Map<String, Object> props) { Properties p = new Properties(); for (String key : props.keySet()) { p.put(key, props.get(key)); } return p; } /** * */ @SentryMethod public void removeLogger(String name) { Logger logger = loggerRegistry.getLogger("local", name); if (logger == null) throw new RpcException("logger not found: " + name); if (logger.isRunning()) throw new RpcException("logger is running: " + name); LoggerFactory factory = loggerFactoryRegistry.getLoggerFactory(logger.getFactoryNamespace(), logger.getFactoryName()); factory.deleteLogger(name); } @SentryMethod public void startLogger(String name, int interval) { Logger logger = loggerRegistry.getLogger("local", name); if (logger == null) throw new RpcException("logger not found: " + name); try { logger.start(interval); } catch (Exception e) { throw new RpcException(e.getMessage()); } } @SentryMethod public void stopLogger(String name, int timeout) { Logger logger = loggerRegistry.getLogger("local", name); if (logger == null) throw new RpcException("logger not found: " + name); try { logger.stop(timeout); } catch (Exception e) { throw new RpcException(e.getMessage()); } } @SentryMethod public Map<String, Object> connectLogger(String name) { Logger logger = loggerRegistry.getLogger("local", name); if (logger == null) throw new RpcException("logger not found: " + name); String baseName = sentry.getBaseName(RpcContext.getConnection()); if (baseName == null) throw new RpcException("base not found: " + baseName); try { // add log pipe logger.addLogPipe(this); // add to connected logger list Set<String> baseNames = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>()); Set<String> oldBaseNames = connectedLoggers.putIfAbsent(name, baseNames); if (oldBaseNames != null) baseNames = oldBaseNames; baseNames.add(baseName); log.info("kraken sentry: logger [{}] connected", name); return LoggerFactorySerializer.toMap(logger); } catch (Exception e) { log.error("kraken sentry: connect logger failed", e); throw new RpcException("connect logger failed", e); } } @SentryMethod public void disconnectLogger(String name) { Logger logger = loggerRegistry.getLogger("local", name); if (logger == null) throw new RpcException("logger not found: " + name); String baseName = sentry.getBaseName(RpcContext.getConnection()); if (baseName == null) throw new RpcException("base not found: " + name); try { Set<String> baseNames = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>()); Set<String> oldBaseNames = connectedLoggers.putIfAbsent(name, baseNames); if (oldBaseNames != null) baseNames = oldBaseNames; baseNames.remove(baseName); // remove pipe only if reference count reaches 0 if (baseNames.size() == 0) { logger.removeLogPipe(this); connectedLoggers.remove(name); } } catch (Exception e) { throw new RpcException("disconnect logger failed", e); } } @SentryMethod public void connectLogChannel(String nonce) { try { String baseName = sentry.getBaseName(RpcContext.getConnection()); if (baseName == null) throw new RpcException("base name not found"); sentry.connectLogChannel(baseName, nonce); } catch (Exception e) { throw new RpcException(e.getMessage(), e); } } @SentryMethod public void disconnectLogChannel() { String baseName = sentry.getBaseName(RpcContext.getConnection()); sentry.disconnectLogChannel(baseName); } @SentryMethod public Date getDate() { return new Date(); } @SentryMethod public List<Object> listFiles() { return listFiles(null); } @SentryMethod public List<Object> listFiles(String path) { if (path == null) return toList(File.listRoots()); return toList(new File(path).listFiles()); } private List<Object> toList(File[] files) { List<Object> l = new ArrayList<Object>(files.length); for (File file : files) l.add(toMap(file)); return l; } private Map<String, Object> toMap(File file) { Map<String, Object> m = new HashMap<String, Object>(); m.put("name", file.getName()); m.put("size", file.length()); m.put("is_dir", file.isDirectory()); m.put("modified_at", new Date(file.lastModified())); return m; } @SentryMethod public String getHostName() { try { return InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException e) { throw new RpcException("hostname not found"); } } @SentryMethod public Map<String, String> getSystemInfo() { String[] keys = new String[] { "java.vendor", "java.version", "java.vm.name", "java.vm.vendor", "java.vm.version", "os.name", "os.arch", "os.version", "user.dir", "user.home" }; Map<String, String> m = new HashMap<String, String>(); for (String key : keys) { m.put(key, System.getProperty(key)); } m.put("host.name", getHostName()); return m; } @SentryMethod public Map<String, Object> getDiskPartitions() { Map<String, Object> partitions = new HashMap<String, Object>(); for (File root : File.listRoots()) { Map<String, Object> info = new HashMap<String, Object>(); info.put("total", root.getTotalSpace()); info.put("free", root.getFreeSpace()); try { partitions.put(root.getCanonicalPath(), info); } catch (IOException e) { } } return partitions; } @SentryMethod public List<Object> getNetworkInterfaces() { List<Object> infs = new ArrayList<Object>(); try { Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); while (en.hasMoreElements()) { NetworkInterface ni = en.nextElement(); if (ni.getHardwareAddress() == null || ni.getHardwareAddress().length != 6) continue; infs.add(toMap(ni)); } return infs; } catch (SocketException e) { throw new RpcException(e.getMessage()); } } private Map<String, Object> toMap(NetworkInterface ni) throws SocketException { Map<String, Object> m = new HashMap<String, Object>(); InetAddress ip4addr = getIpAddress(ni, false); InetAddress ip6addr = getIpAddress(ni, true); m.put("display_name", ni.getDisplayName()); m.put("name", ni.getName()); m.put("mac", toMacAddress(ni.getHardwareAddress())); m.put("ip", ip4addr == null ? null : ip4addr.getHostAddress()); m.put("ip6", ip6addr == null ? null : ip6addr.getHostAddress()); m.put("mtu", ni.getMTU()); return m; } private InetAddress getIpAddress(NetworkInterface ni, boolean findIp6) { Enumeration<InetAddress> inetAddresses = ni.getInetAddresses(); while (inetAddresses.hasMoreElements()) { InetAddress addr = inetAddresses.nextElement(); if (findIp6 && addr instanceof Inet6Address) return addr; else if (addr instanceof Inet4Address) return addr; } return null; } private String toMacAddress(byte[] b) { if (b == null || b.length != 6) return null; return String.format("%02x:%02x:%02x:%02x:%02x:%02x", b[0], b[1], b[2], b[3], b[4], b[5]); } @Override public void onLog(Logger logger, Log log) { String loggerName = logger.getName(); Set<String> baseNames = connectedLoggers.get(loggerName); if (baseNames == null) { this.log.warn("kraken-sentry: no base connection with logger {}", loggerName); return; } Map<String, Object> logData = convert(log); for (String baseName : baseNames) { RpcSession logSession = sentry.getLogSession(baseName); if (logSession == null) { this.log.trace("kraken-sentry: log session not found for base [{}]", baseName); continue; } try { logSession.post("onLog", new Object[] { sentry.getGuid(), loggerName, logData }); } catch (Exception e) { this.log.error("kraken-sentry: log callback failed, base=" + baseName, e); } } } private Map<String, Object> convert(Log log) { Map<String, Object> m = new HashMap<String, Object>(); m.put("date", log.getDate()); m.put("msg", log.getMessage()); m.put("params", log.getParams()); return m; } @Override public void loggerAdded(Logger logger) { if (!logger.getNamespace().equals("local")) return; logger.addEventListener(this); // NOTE: connect log pipe when logger is connected } @Override public void loggerRemoved(Logger logger) { if (!logger.getNamespace().equals("local")) return; connectedLoggers.remove(logger.getName()); logger.removeLogPipe(this); logger.removeEventListener(this); } public void sessionClosed(RpcSession session) { String baseName = sentry.getBaseName(session.getConnection()); log.info("kraken sentry: base [{}] disconnected, clean up states", baseName); for (String loggerName : new ArrayList<String>(connectedLoggers.keySet())) { Set<String> baseNames = connectedLoggers.get(loggerName); baseNames.remove(baseName); } } // // LoggerEventListener callback // @Override public void onStart(Logger logger) { if (!logger.getNamespace().equals("local")) return; log.trace("kraken sentry: starting logger {}", logger.getFullName()); // notify logger started event for (Base base : sentry.getBases()) { RpcSession commandSession = sentry.getCommandSession(base.getName()); if (commandSession == null) { log.error("kraken sentry: logger started notification failed, session not found for {}", base.getName()); continue; } try { commandSession.post("loggerStarted", new Object[] { sentry.getGuid(), logger.getName(), logger.getInterval() }); } catch (Exception e) { log.error("kraken sentry: cannot post logger started event", e); } } } @Override public void onStop(Logger logger) { if (!logger.getNamespace().equals("local")) return; log.trace("kraken sentry: stopping logger {}", logger.getFullName()); if (sentry == null) return; // notify logger stopped event for (Base base : sentry.getBases()) { RpcSession commandSession = sentry.getCommandSession(base.getName()); if (commandSession == null) { log.error("kraken sentry: logger stopped notification failed, session not found for {}", base.getName()); continue; } try { commandSession.post("loggerStopped", new Object[] { sentry.getGuid(), logger.getName() }); } catch (Exception e) { log.error("kraken sentry: cannot post logger stopped event", e); } } } @Override public void onUpdated(Logger logger, Properties config) { // ignored. remote propagation not needed } }