001 /** 002 * Copyright (c) 2010 Yahoo! Inc. All rights reserved. 003 * Licensed under the Apache License, Version 2.0 (the "License"); 004 * you may not use this file except in compliance with the License. 005 * You may obtain a copy of the License at 006 * 007 * http://www.apache.org/licenses/LICENSE-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, software 010 * distributed under the License is distributed on an "AS IS" BASIS, 011 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012 * See the License for the specific language governing permissions and 013 * limitations under the License. See accompanying LICENSE file. 014 */ 015 package org.apache.oozie.service; 016 017 import org.apache.commons.logging.LogFactory; 018 import org.apache.hadoop.conf.Configuration; 019 import org.apache.hadoop.util.ReflectionUtils; 020 import org.apache.hadoop.util.VersionInfo; 021 import org.apache.oozie.client.OozieClient.SYSTEM_MODE; 022 import org.apache.oozie.util.XLog; 023 import org.apache.oozie.util.Instrumentable; 024 import org.apache.oozie.util.IOUtils; 025 import org.apache.oozie.ErrorCode; 026 027 import java.util.ArrayList; 028 import java.util.Collections; 029 import java.util.LinkedHashMap; 030 import java.util.List; 031 import java.util.Map; 032 import java.io.IOException; 033 import java.io.File; 034 035 /** 036 * Services is a singleton that manages the lifecycle of all registered {@link Services}. <p/> It has 2 built in 037 * services: {@link XLogService} and {@link ConfigurationService}. <p/> The rest of the services are loaded from the 038 * {@link #CONF_SERVICE_CLASSES} configuration property. The services class names must be separated by commas (spaces 039 * and enters are allowed). <p/> The {@link #CONF_SYSTEM_MODE} configuration property is any of 040 * NORMAL/SAFEMODE/NOWEBSERVICE. <p/> Services are loaded and initialized in the order they are defined in the in 041 * configuration property. <p/> After all services are initialized, if the Instrumentation service is present, all 042 * services that implement the {@link Instrumentable} are instrumented. <p/> Services are destroyed in reverse order. 043 * <p/> If services initialization fail, initialized services are immediatly destroyed. 044 */ 045 public class Services { 046 private static final int MAX_SYSTEM_ID_LEN = 10; 047 048 /** 049 * Environment variable that indicates the location of the Oozie home directory. 050 * The Oozie home directory is used to pick up the conf/ directory from 051 */ 052 public static final String OOZIE_HOME_DIR = "oozie.home.dir"; 053 054 public static final String CONF_SYSTEM_ID = "oozie.system.id"; 055 056 public static final String CONF_SERVICE_CLASSES = "oozie.services"; 057 058 public static final String CONF_SERVICE_EXT_CLASSES = "oozie.services.ext"; 059 060 public static final String CONF_SYSTEM_MODE = "oozie.systemmode"; 061 062 public static final String CONF_DELETE_RUNTIME_DIR = "oozie.delete.runtime.dir.on.shutdown"; 063 064 private static Services SERVICES; 065 066 private SYSTEM_MODE systemMode; 067 private String runtimeDir; 068 private Configuration conf; 069 private Map<Class<? extends Service>, Service> services = new LinkedHashMap<Class<? extends Service>, Service>(); 070 private String systemId; 071 private static String oozieHome; 072 073 public static void setOozieHome() throws ServiceException { 074 oozieHome = System.getProperty(OOZIE_HOME_DIR); 075 if (oozieHome == null) { 076 throw new ServiceException(ErrorCode.E0000); 077 } 078 if (!oozieHome.startsWith("/")) { 079 throw new ServiceException(ErrorCode.E0003, oozieHome); 080 } 081 File file = new File(oozieHome); 082 if (!file.exists()) { 083 throw new ServiceException(ErrorCode.E0004, oozieHome); 084 } 085 } 086 087 public static String getOozieHome() throws ServiceException { 088 return oozieHome; 089 } 090 091 /** 092 * Create a services. <p/> The built in services are initialized. 093 * 094 * @throws ServiceException thrown if any of the built in services could not initialize. 095 */ 096 public Services() throws ServiceException { 097 setOozieHome(); 098 if (SERVICES != null) { 099 XLog log = XLog.getLog(getClass()); 100 log.warn(XLog.OPS, "Previous services singleton active, destroying it"); 101 SERVICES.destroy(); 102 SERVICES = null; 103 } 104 setServiceInternal(XLogService.class, false); 105 setServiceInternal(ConfigurationService.class, true); 106 conf = get(ConfigurationService.class).getConf(); 107 systemId = conf.get(CONF_SYSTEM_ID, ("oozie-" + System.getProperty("user.name"))); 108 if (systemId.length() > MAX_SYSTEM_ID_LEN) { 109 systemId = systemId.substring(0, MAX_SYSTEM_ID_LEN); 110 XLog.getLog(getClass()).warn("System ID [{0}] exceeds maximum length [{1}], trimming", systemId, 111 MAX_SYSTEM_ID_LEN); 112 } 113 setSystemMode(SYSTEM_MODE.valueOf(conf.get(CONF_SYSTEM_MODE, SYSTEM_MODE.NORMAL.toString()))); 114 runtimeDir = createRuntimeDir(); 115 } 116 117 private String createRuntimeDir() throws ServiceException { 118 try { 119 File file = File.createTempFile(getSystemId(), ".dir"); 120 file.delete(); 121 if (!file.mkdir()) { 122 ServiceException ex = new ServiceException(ErrorCode.E0001, file.getAbsolutePath()); 123 XLog.getLog(getClass()).fatal(ex); 124 throw ex; 125 } 126 XLog.getLog(getClass()).info("Initialized runtime directory [{0}]", file.getAbsolutePath()); 127 return file.getAbsolutePath(); 128 } 129 catch (IOException ex) { 130 ServiceException sex = new ServiceException(ErrorCode.E0001, ex); 131 XLog.getLog(getClass()).fatal(ex); 132 throw sex; 133 } 134 } 135 136 /** 137 * Return active system mode. <p/> . 138 * 139 * @return 140 */ 141 142 public SYSTEM_MODE getSystemMode() { 143 return systemMode; 144 } 145 146 /** 147 * Return the runtime directory of the Oozie instance. <p/> The directory is created under TMP and it is always a 148 * new directory per Services initialization. 149 * 150 * @return the runtime directory of the Oozie instance. 151 */ 152 public String getRuntimeDir() { 153 return runtimeDir; 154 } 155 156 /** 157 * Return the system ID, the value defined in the {@link #CONF_SYSTEM_ID} configuration property. 158 * 159 * @return the system ID, the value defined in the {@link #CONF_SYSTEM_ID} configuration property. 160 */ 161 public String getSystemId() { 162 return systemId; 163 } 164 165 /** 166 * Set and set system mode. 167 * 168 * @param sysMode system mode 169 */ 170 171 public synchronized void setSystemMode(SYSTEM_MODE sysMode) { 172 if (this.systemMode != sysMode) { 173 XLog log = XLog.getLog(getClass()); 174 log.info(XLog.OPS, "Exiting " + this.systemMode + " Entering " + sysMode); 175 } 176 this.systemMode = sysMode; 177 } 178 179 /** 180 * Return the services configuration. 181 * 182 * @return services configuraiton. 183 */ 184 public Configuration getConf() { 185 return conf; 186 } 187 188 /** 189 * Initialize all services define in the {@link #CONF_SERVICE_CLASSES} configuration property. 190 * 191 * @throws ServiceException thrown if any of the services could not initialize. 192 */ 193 @SuppressWarnings("unchecked") 194 public void init() throws ServiceException { 195 XLog log = new XLog(LogFactory.getLog(getClass())); 196 log.trace("Initializing"); 197 SERVICES = this; 198 try { 199 Class<? extends Service>[] serviceClasses = (Class<? extends Service>[]) conf.getClasses( 200 CONF_SERVICE_CLASSES); 201 if (serviceClasses != null) { 202 for (Class<? extends Service> serviceClass : serviceClasses) { 203 setService(serviceClass); 204 } 205 } 206 serviceClasses = (Class<? extends Service>[]) conf.getClasses(CONF_SERVICE_EXT_CLASSES); 207 if (serviceClasses != null) { 208 for (Class<? extends Service> serviceClass : serviceClasses) { 209 setService(serviceClass); 210 } 211 } 212 } 213 catch (RuntimeException ex) { 214 XLog.getLog(getClass()).fatal(ex.getMessage(), ex); 215 throw ex; 216 } 217 catch (ServiceException ex) { 218 SERVICES = null; 219 throw ex; 220 } 221 InstrumentationService instrService = get(InstrumentationService.class); 222 if (instrService != null) { 223 for (Service service : services.values()) { 224 if (service instanceof Instrumentable) { 225 ((Instrumentable) service).instrument(instrService.get()); 226 } 227 } 228 } 229 log.info("Initialized"); 230 log.info("Running with JARs for Hadoop version [{0}]", VersionInfo.getVersion()); 231 log.info("Oozie System ID [{0}] started!", getSystemId()); 232 } 233 234 /** 235 * Destroy all services. 236 */ 237 public void destroy() { 238 XLog log = new XLog(LogFactory.getLog(getClass())); 239 log.trace("Shutting down"); 240 boolean deleteRuntimeDir = false; 241 if (conf != null) { 242 deleteRuntimeDir = conf.getBoolean(CONF_DELETE_RUNTIME_DIR, false); 243 } 244 if (services != null) { 245 List<Service> list = new ArrayList<Service>(services.values()); 246 Collections.reverse(list); 247 for (Service service : list) { 248 try { 249 log.trace("Destroying service[{0}]", service.getInterface()); 250 if (service.getInterface() == XLogService.class) { 251 log.info("Shutdown"); 252 } 253 service.destroy(); 254 } 255 catch (Throwable ex) { 256 log.error("Error destroying service[{0}], {1}", service.getInterface(), ex.getMessage(), ex); 257 } 258 } 259 } 260 if (deleteRuntimeDir) { 261 try { 262 IOUtils.delete(new File(runtimeDir)); 263 } 264 catch (IOException ex) { 265 log.error("Error deleting runtime directory [{0}], {1}", runtimeDir, ex.getMessage(), ex); 266 } 267 } 268 services = null; 269 conf = null; 270 SERVICES = null; 271 } 272 273 /** 274 * Return a service by its public interface. 275 * 276 * @param serviceKlass service public interface. 277 * @return the associated service, or <code>null</code> if not define. 278 */ 279 @SuppressWarnings("unchecked") 280 public <T extends Service> T get(Class<T> serviceKlass) { 281 return (T) services.get(serviceKlass); 282 } 283 284 /** 285 * Set a service programmatically. <p/> The service will be initialized by the services. <p/> If a service is 286 * already defined with the same public interface it will be destroyed. 287 * 288 * @param klass service klass 289 * @throws ServiceException if the service could not be initialized, at this point all services have been 290 * destroyed. 291 */ 292 public void setService(Class<? extends Service> klass) throws ServiceException { 293 setServiceInternal(klass, true); 294 } 295 296 private void setServiceInternal(Class<? extends Service> klass, boolean logging) throws ServiceException { 297 try { 298 Service newService = (Service) ReflectionUtils.newInstance(klass, null); 299 Service oldService = services.get(newService.getInterface()); 300 if (oldService != null) { 301 oldService.destroy(); 302 } 303 if (logging) { 304 XLog log = new XLog(LogFactory.getLog(getClass())); 305 log.trace("Initializing service[{0}] class[{1}]", newService.getInterface(), newService.getClass()); 306 } 307 newService.init(this); 308 services.put(newService.getInterface(), newService); 309 } 310 catch (ServiceException ex) { 311 XLog.getLog(getClass()).fatal(ex.getMessage(), ex); 312 destroy(); 313 throw ex; 314 } 315 } 316 317 /** 318 * Return the services singleton. 319 * 320 * @return services singleton, <code>null</code> if not initialized. 321 */ 322 public static Services get() { 323 return SERVICES; 324 } 325 326 }