001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019package org.apache.oozie.servlet; 020 021import java.io.IOException; 022import java.io.Reader; 023import java.util.List; 024import java.util.Map; 025import java.util.TimeZone; 026import java.util.regex.Pattern; 027 028import javax.servlet.ServletException; 029import javax.servlet.http.HttpServletRequest; 030import javax.servlet.http.HttpServletResponse; 031 032import org.apache.commons.lang.StringUtils; 033import org.apache.hadoop.fs.Path; 034import org.apache.oozie.BuildInfo; 035import org.apache.oozie.ErrorCode; 036import org.apache.oozie.cli.ValidationUtil; 037import org.apache.oozie.client.rest.JsonBean; 038import org.apache.oozie.client.rest.JsonTags; 039import org.apache.oozie.client.rest.RestConstants; 040import org.apache.oozie.command.CommandException; 041import org.apache.oozie.command.PurgeXCommand; 042import org.apache.oozie.service.AuthorizationException; 043import org.apache.oozie.service.AuthorizationService; 044import org.apache.oozie.service.ConfigurationService; 045import org.apache.oozie.service.InstrumentationService; 046import org.apache.oozie.service.JobsConcurrencyService; 047import org.apache.oozie.service.PurgeService; 048import org.apache.oozie.service.Services; 049import org.apache.oozie.service.ShareLibService; 050import org.apache.oozie.util.AuthUrlClient; 051import org.apache.oozie.util.ConfigUtils; 052import org.apache.oozie.util.Instrumentation; 053import org.apache.oozie.util.XLog; 054import org.json.simple.JSONArray; 055import org.json.simple.JSONObject; 056import org.json.simple.JSONValue; 057 058public abstract class BaseAdminServlet extends JsonRestServlet { 059 060 private static final long serialVersionUID = 1L; 061 private static final XLog LOG = XLog.getLog(BaseAdminServlet.class); 062 protected String modeTag; 063 064 065 public BaseAdminServlet(String instrumentationName, ResourceInfo[] RESOURCES_INFO) { 066 super(instrumentationName, RESOURCES_INFO); 067 setAllowSafeModeChanges(true); 068 } 069 070 /** 071 * Oozie admin PUT request for 072 * change oozie system mode 073 * admin purge command 074 * @param request http request 075 * @param response http response 076 * @throws ServletException thrown if any servlet error 077 * @throws IOException thrown if any I/O error 078 */ 079 @Override 080 protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 081 String resourceName = getResourceName(request); 082 request.setAttribute(AUDIT_OPERATION, resourceName); 083 request.setAttribute(AUDIT_PARAM, request.getParameter(modeTag)); 084 085 authorizeRequest(request); 086 switch (resourceName) { 087 case RestConstants.ADMIN_STATUS_RESOURCE: 088 setOozieMode(request, response, resourceName); 089 break; 090 case RestConstants.ADMIN_PURGE: 091 String msg = schedulePurgeCommand(request); 092 JSONObject jsonObject = new JSONObject(); 093 jsonObject.put(JsonTags.PURGE, msg); 094 sendJsonResponse(response, HttpServletResponse.SC_OK, jsonObject); 095 break; 096 default: 097 throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, ErrorCode.E0301, resourceName); 098 } 099 } 100 101 102 /** 103 * Get JMS connection Info 104 * @param request the request 105 * @param response the response 106 * @throws XServletException in case of any servlet error 107 * @throws IOException in case of any IO error 108 */ 109 abstract JsonBean getJMSConnectionInfo(HttpServletRequest request, HttpServletResponse response) 110 throws XServletException, IOException; 111 112 113 /** 114 * Return safemode state, instrumentation, configuration, osEnv or 115 * javaSysProps 116 */ 117 @Override 118 @SuppressWarnings("unchecked") 119 public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 120 String resource = getResourceName(request); 121 Instrumentation instr = Services.get().get(InstrumentationService.class).get(); 122 123 if (resource.equals(RestConstants.ADMIN_STATUS_RESOURCE)) { 124 JSONObject json = new JSONObject(); 125 populateOozieMode(json); 126 // json.put(JsonTags.SYSTEM_SAFE_MODE, getOozeMode()); 127 sendJsonResponse(response, HttpServletResponse.SC_OK, json); 128 } 129 else if (resource.equals(RestConstants.ADMIN_OS_ENV_RESOURCE)) { 130 authorizeForSystemInfo(request); 131 JSONObject json = new JSONObject(); 132 json.putAll(instr.getOSEnv()); 133 sendJsonResponse(response, HttpServletResponse.SC_OK, json); 134 } 135 else if (resource.equals(RestConstants.ADMIN_JAVA_SYS_PROPS_RESOURCE)) { 136 authorizeForSystemInfo(request); 137 JSONObject json = new JSONObject(); 138 json.putAll(instr.getJavaSystemProperties()); 139 sendJsonResponse(response, HttpServletResponse.SC_OK, json); 140 } 141 else if (resource.equals(RestConstants.ADMIN_CONFIG_RESOURCE)) { 142 authorizeForSystemInfo(request); 143 JSONObject json = new JSONObject(); 144 json.putAll(instr.getConfiguration()); 145 sendJsonResponse(response, HttpServletResponse.SC_OK, json); 146 } 147 else if (resource.equals(RestConstants.ADMIN_INSTRUMENTATION_RESOURCE)) { 148 sendInstrumentationResponse(response, instr); 149 } 150 else if (resource.equals(RestConstants.ADMIN_BUILD_VERSION_RESOURCE)) { 151 JSONObject json = new JSONObject(); 152 json.put(JsonTags.BUILD_VERSION, BuildInfo.getBuildInfo().getProperty(BuildInfo.BUILD_VERSION)); 153 json.put(JsonTags.BUILD_INFO, BuildInfo.getBuildInfo()); 154 sendJsonResponse(response, HttpServletResponse.SC_OK, json); 155 } 156 else if (resource.equals(RestConstants.ADMIN_QUEUE_DUMP_RESOURCE)) { 157 JSONObject json = new JSONObject(); 158 getQueueDump(json); 159 sendJsonResponse(response, HttpServletResponse.SC_OK, json); 160 } 161 else if (resource.equals(RestConstants.ADMIN_TIME_ZONES_RESOURCE)) { 162 JSONObject json = new JSONObject(); 163 json.put(JsonTags.AVAILABLE_TIME_ZONES, availableTimeZonesToJsonArray()); 164 sendJsonResponse(response, HttpServletResponse.SC_OK, json); 165 } 166 else if (resource.equals(RestConstants.ADMIN_JMS_INFO)) { 167 String timeZoneId = request.getParameter(RestConstants.TIME_ZONE_PARAM) == null ? "GMT" : request 168 .getParameter(RestConstants.TIME_ZONE_PARAM); 169 JsonBean jmsBean = getJMSConnectionInfo(request, response); 170 sendJsonResponse(response, HttpServletResponse.SC_OK, jmsBean, timeZoneId); 171 } 172 else if (resource.equals(RestConstants.ADMIN_AVAILABLE_OOZIE_SERVERS_RESOURCE)) { 173 JSONObject json = new JSONObject(); 174 json.putAll(getOozieURLs()); 175 sendJsonResponse(response, HttpServletResponse.SC_OK, json); 176 } 177 else if (resource.equals(RestConstants.ADMIN_UPDATE_SHARELIB)) { 178 authorizeRequest(request); 179 updateShareLib(request, response); 180 } 181 else if (resource.equals(RestConstants.ADMIN_LIST_SHARELIB)) { 182 String sharelibKey = request.getParameter(RestConstants.SHARE_LIB_REQUEST_KEY); 183 sendJsonResponse(response, HttpServletResponse.SC_OK, getShareLib(sharelibKey)); 184 } 185 else if (resource.equals(RestConstants.ADMIN_METRICS_RESOURCE)) { 186 sendMetricsResponse(response); 187 } 188 } 189 190 private String schedulePurgeCommand(HttpServletRequest request) throws XServletException { 191 final String purgeCmdDisabledMsg = "Purge service is not enabled"; 192 if (!ConfigurationService.getBoolean(PurgeService.PURGE_COMMAND_ENABLED)) { 193 LOG.warn(purgeCmdDisabledMsg); 194 throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, ErrorCode.E0307, purgeCmdDisabledMsg); 195 } 196 String wfAgeStr = request.getParameter(RestConstants.PURGE_WF_AGE); 197 String coordAgeStr = request.getParameter(RestConstants.PURGE_COORD_AGE); 198 String bundleAgeStr = request.getParameter(RestConstants.PURGE_BUNDLE_AGE); 199 String purgeLimitStr = request.getParameter(RestConstants.PURGE_LIMIT); 200 String oldCoordActionStr = request.getParameter(RestConstants.PURGE_OLD_COORD_ACTION); 201 202 int workflowAge = StringUtils.isBlank(wfAgeStr) ? ConfigurationService.getInt(PurgeService.CONF_OLDER_THAN) 203 : ValidationUtil.parsePositiveInteger(wfAgeStr); 204 int coordAge = StringUtils.isBlank(coordAgeStr) ? ConfigurationService.getInt(PurgeService.COORD_CONF_OLDER_THAN) 205 : ValidationUtil.parsePositiveInteger(coordAgeStr); 206 int bundleAge = StringUtils.isBlank(bundleAgeStr) ? ConfigurationService.getInt(PurgeService.BUNDLE_CONF_OLDER_THAN) 207 : ValidationUtil.parsePositiveInteger(bundleAgeStr); 208 int purgeLimit = StringUtils.isBlank(purgeLimitStr) ? ConfigurationService.getInt(PurgeService.PURGE_LIMIT) 209 : ValidationUtil.parsePositiveInteger(purgeLimitStr); 210 boolean purgeOldCoordAction = StringUtils.isBlank(oldCoordActionStr) 211 ? ConfigurationService.getBoolean(PurgeService.PURGE_OLD_COORD_ACTION) 212 : Boolean.parseBoolean(oldCoordActionStr); 213 214 LOG.info("Executing oozie purge command."); 215 PurgeXCommand purgeXCommand = new PurgeXCommand(workflowAge, coordAge, bundleAge, purgeLimit, purgeOldCoordAction); 216 try { 217 purgeXCommand.call(); 218 return "Purge command executed successfully"; 219 } catch (CommandException e) { 220 throw new XServletException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getErrorCode(), e.getMessage(), 221 e.getCause()); 222 } 223 } 224 225 /** 226 * Gets the list of share lib. 227 * 228 * @param sharelibKey the sharelib key 229 * @return the list of supported share lib 230 * @throws IOException in case of any servlet error 231 */ 232 @SuppressWarnings("unchecked") 233 private JSONObject getShareLib(String sharelibKey) throws IOException { 234 JSONObject json = new JSONObject(); 235 236 ShareLibService shareLibService = Services.get().get(ShareLibService.class); 237 238 // for testcases. 239 if (shareLibService == null) { 240 return json; 241 } 242 JSONArray shareLibList = new JSONArray(); 243 244 Map<String, List<Path>> shareLibLauncherMap = shareLibService.getShareLib(); 245 if (sharelibKey != null && !sharelibKey.isEmpty()) { 246 Pattern pattern = Pattern.compile(sharelibKey); 247 for (String key : shareLibLauncherMap.keySet()) { 248 if (pattern.matcher(key).matches() == true) { 249 JSONObject object = new JSONObject(); 250 JSONArray fileList = new JSONArray(); 251 List<Path> pathList = shareLibLauncherMap.get(key); 252 253 for (Path file : pathList) { 254 fileList.add(file.toString()); 255 } 256 object.put(JsonTags.SHARELIB_LIB_NAME, key); 257 object.put(JsonTags.SHARELIB_LIB_FILES, fileList); 258 shareLibList.add(object); 259 260 } 261 } 262 } 263 else { 264 for (String key : shareLibLauncherMap.keySet()) { 265 JSONObject object = new JSONObject(); 266 object.put(JsonTags.SHARELIB_LIB_NAME, key); 267 shareLibList.add(object); 268 } 269 270 } 271 json.put(JsonTags.SHARELIB_LIB, shareLibList); 272 273 return json; 274 } 275 276 /** 277 * Update share lib. support HA 278 * 279 * @param request the request 280 * @param response the response 281 * @throws IOException Signals that an I/O exception has occurred. 282 */ 283 @SuppressWarnings("unchecked") 284 public void updateShareLib(HttpServletRequest request, HttpServletResponse response) throws IOException { 285 JSONArray jsonArray = new JSONArray(); 286 JobsConcurrencyService jc = Services.get().get(JobsConcurrencyService.class); 287 if (jc.isAllServerRequest(request.getParameterMap())) { 288 Map<String, String> servers = jc.getOtherServerUrls(); 289 for (String otherUrl : servers.values()) { 290 // It's important that we specify ALL_SERVERS_PARAM=false, so that other oozie server should not call other oozie 291 //servers to update sharelib (and creating an infinite recursion) 292 String serverUrl = otherUrl + "/v2/admin/" + RestConstants.ADMIN_UPDATE_SHARELIB + "?" 293 + RestConstants.ALL_SERVER_REQUEST + "=false"; 294 try { 295 Reader reader = AuthUrlClient.callServer(serverUrl); 296 JSONObject json = (JSONObject) JSONValue.parse(reader); 297 jsonArray.add(json); 298 } 299 catch (Exception e) { 300 JSONObject errorJson = new JSONObject(); 301 errorJson.put(JsonTags.SHARELIB_UPDATE_HOST, otherUrl); 302 errorJson.put(JsonTags.SHARELIB_UPDATE_STATUS, e.getMessage()); 303 JSONObject newJson = new JSONObject(); 304 newJson.put(JsonTags.SHARELIB_LIB_UPDATE, errorJson); 305 jsonArray.add(newJson); 306 } 307 } 308 //For current server 309 JSONObject newJson = new JSONObject(); 310 newJson.put(JsonTags.SHARELIB_LIB_UPDATE, updateLocalShareLib(request)); 311 jsonArray.add(newJson); 312 sendJsonResponse(response, HttpServletResponse.SC_OK, jsonArray); 313 } 314 else { 315 JSONObject newJson = new JSONObject(); 316 newJson.put(JsonTags.SHARELIB_LIB_UPDATE, updateLocalShareLib(request)); 317 sendJsonResponse(response, HttpServletResponse.SC_OK, newJson); 318 } 319 } 320 321 @SuppressWarnings("unchecked") 322 private JSONObject updateLocalShareLib(HttpServletRequest request) { 323 ShareLibService shareLibService = Services.get().get(ShareLibService.class); 324 JSONObject json = new JSONObject(); 325 json.put(JsonTags.SHARELIB_UPDATE_HOST, ConfigUtils.getOozieEffectiveUrl()); 326 try { 327 json.putAll(shareLibService.updateShareLib()); 328 json.put(JsonTags.SHARELIB_UPDATE_STATUS, "Successful"); 329 } 330 catch (Exception e) { 331 json.put(JsonTags.SHARELIB_UPDATE_STATUS, e.getClass().getName() + ": " + e.getMessage()); 332 } 333 return json; 334 } 335 336 /** 337 * Authorize request. 338 * 339 * @param request the HttpServletRequest 340 * @throws XServletException the x servlet exception 341 */ 342 private void authorizeRequest(HttpServletRequest request) throws XServletException { 343 try { 344 AuthorizationService auth = Services.get().get(AuthorizationService.class); 345 auth.authorizeForAdmin(getUser(request), true); 346 } 347 catch (AuthorizationException ex) { 348 throw new XServletException(HttpServletResponse.SC_UNAUTHORIZED, ex); 349 } 350 } 351 352 /** 353 * Authorize request. 354 * 355 * @param request the HttpServletRequest 356 * @throws XServletException the x servlet exception 357 */ 358 private void authorizeForSystemInfo(HttpServletRequest request) throws XServletException { 359 try { 360 AuthorizationService auth = Services.get().get(AuthorizationService.class); 361 if (auth.isAuthorizedSystemInfo()) { 362 auth.authorizeForSystemInfo(getUser(request), getProxyUser(request)); 363 } 364 } 365 catch (AuthorizationException ex) { 366 throw new XServletException(HttpServletResponse.SC_UNAUTHORIZED, ex); 367 } 368 } 369 370 @Override 371 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, 372 IOException { 373 } 374 375 @SuppressWarnings("unchecked") 376 private <T> JSONArray instrElementsToJson(Map<String, Map<String, Instrumentation.Element<T>>> instrElements) { 377 JSONArray array = new JSONArray(); 378 for (Map.Entry<String, Map<String, Instrumentation.Element<T>>> group : instrElements.entrySet()) { 379 JSONObject json = new JSONObject(); 380 String groupName = group.getKey(); 381 json.put(JsonTags.INSTR_GROUP, groupName); 382 JSONArray dataArray = new JSONArray(); 383 for (Map.Entry<String, Instrumentation.Element<T>> elementEntry : group.getValue().entrySet()) { 384 String samplerName = elementEntry.getKey(); 385 JSONObject dataJson = new JSONObject(); 386 dataJson.put(JsonTags.INSTR_NAME, samplerName); 387 Object value = elementEntry.getValue().getValue(); 388 if (value instanceof Instrumentation.Timer) { 389 Instrumentation.Timer timer = (Instrumentation.Timer) value; 390 dataJson.put(JsonTags.INSTR_TIMER_TICKS, timer.getTicks()); 391 dataJson.put(JsonTags.INSTR_TIMER_OWN_TIME_AVG, timer.getOwnAvg()); 392 dataJson.put(JsonTags.INSTR_TIMER_TOTAL_TIME_AVG, timer.getTotalAvg()); 393 dataJson.put(JsonTags.INSTR_TIMER_OWN_STD_DEV, timer.getOwnStdDev()); 394 dataJson.put(JsonTags.INSTR_TIMER_TOTAL_STD_DEV, timer.getTotalStdDev()); 395 dataJson.put(JsonTags.INSTR_TIMER_OWN_MIN_TIME, timer.getOwnMin()); 396 dataJson.put(JsonTags.INSTR_TIMER_OWN_MAX_TIME, timer.getOwnMax()); 397 dataJson.put(JsonTags.INSTR_TIMER_TOTAL_MIN_TIME, timer.getTotalMin()); 398 dataJson.put(JsonTags.INSTR_TIMER_TOTAL_MAX_TIME, timer.getTotalMax()); 399 } 400 else { 401 dataJson.put(JsonTags.INSTR_VARIABLE_VALUE, value); 402 } 403 dataArray.add(dataJson); 404 } 405 json.put(JsonTags.INSTR_DATA, dataArray); 406 array.add(json); 407 } 408 return array; 409 } 410 411 @SuppressWarnings("unchecked") 412 private JSONObject instrToJson(Instrumentation instr) { 413 JSONObject json = new JSONObject(); 414 json.put(JsonTags.INSTR_VARIABLES, instrElementsToJson(instr.getVariables())); 415 json.put(JsonTags.INSTR_SAMPLERS, instrElementsToJson(instr.getSamplers())); 416 json.put(JsonTags.INSTR_COUNTERS, instrElementsToJson(instr.getCounters())); 417 json.put(JsonTags.INSTR_TIMERS, instrElementsToJson(instr.getTimers())); 418 return json; 419 } 420 421 protected abstract void populateOozieMode(JSONObject json); 422 423 protected abstract void setOozieMode(HttpServletRequest request, HttpServletResponse response, String resourceName) 424 throws XServletException; 425 426 protected abstract void getQueueDump(JSONObject json) throws XServletException; 427 428 private static final JSONArray GMTOffsetTimeZones = new JSONArray(); 429 static { 430 prepareGMTOffsetTimeZones(); 431 } 432 433 @SuppressWarnings({"unchecked", "rawtypes"}) 434 private static void prepareGMTOffsetTimeZones() { 435 for (String tzId : new String[]{"GMT-12:00", "GMT-11:00", "GMT-10:00", "GMT-09:00", "GMT-08:00", "GMT-07:00", "GMT-06:00", 436 "GMT-05:00", "GMT-04:00", "GMT-03:00", "GMT-02:00", "GMT-01:00", "GMT+01:00", "GMT+02:00", 437 "GMT+03:00", "GMT+04:00", "GMT+05:00", "GMT+06:00", "GMT+07:00", "GMT+08:00", "GMT+09:00", 438 "GMT+10:00", "GMT+11:00", "GMT+12:00"}) { 439 TimeZone tz = TimeZone.getTimeZone(tzId); 440 JSONObject json = new JSONObject(); 441 json.put(JsonTags.TIME_ZOME_DISPLAY_NAME, tz.getDisplayName(false, TimeZone.SHORT) + " (" + tzId + ")"); 442 json.put(JsonTags.TIME_ZONE_ID, tzId); 443 GMTOffsetTimeZones.add(json); 444 } 445 } 446 447 @SuppressWarnings({"unchecked", "rawtypes"}) 448 private JSONArray availableTimeZonesToJsonArray() { 449 JSONArray array = new JSONArray(); 450 for (String tzId : TimeZone.getAvailableIDs()) { 451 // skip id's that are like "Etc/GMT+01:00" because their display names are like "GMT-01:00", which is confusing 452 if (!tzId.startsWith("Etc/GMT")) { 453 JSONObject json = new JSONObject(); 454 TimeZone tZone = TimeZone.getTimeZone(tzId); 455 json.put(JsonTags.TIME_ZOME_DISPLAY_NAME, tZone.getDisplayName(false, TimeZone.SHORT) + " (" + tzId + ")"); 456 json.put(JsonTags.TIME_ZONE_ID, tzId); 457 array.add(json); 458 } 459 } 460 461 // The combo box this populates cannot be edited, so the user can't type in GMT offsets (like in the CLI), so we'll add 462 // in some hourly offsets here (though the user will not be able to use other offsets without editing the cookie manually 463 // and they are not in order) 464 array.addAll(GMTOffsetTimeZones); 465 466 return array; 467 } 468 469 protected void sendInstrumentationResponse(HttpServletResponse response, Instrumentation instr) 470 throws IOException, XServletException { 471 sendJsonResponse(response, HttpServletResponse.SC_OK, instrToJson(instr)); 472 } 473 474 protected abstract Map<String, String> getOozieURLs() throws XServletException; 475 476 protected abstract void sendMetricsResponse(HttpServletResponse response) throws IOException, XServletException; 477}