| /** |
| * 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.oozie.servlet; |
| |
| import java.io.IOException; |
| import java.io.Reader; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.TimeZone; |
| import java.util.regex.Pattern; |
| |
| import javax.servlet.ServletException; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.hadoop.fs.Path; |
| import org.apache.oozie.BuildInfo; |
| import org.apache.oozie.ErrorCode; |
| import org.apache.oozie.cli.ValidationUtil; |
| import org.apache.oozie.client.rest.JsonBean; |
| import org.apache.oozie.client.rest.JsonTags; |
| import org.apache.oozie.client.rest.RestConstants; |
| import org.apache.oozie.command.CommandException; |
| import org.apache.oozie.command.PurgeXCommand; |
| import org.apache.oozie.service.AuthorizationException; |
| import org.apache.oozie.service.AuthorizationService; |
| import org.apache.oozie.service.ConfigurationService; |
| import org.apache.oozie.service.InstrumentationService; |
| import org.apache.oozie.service.JobsConcurrencyService; |
| import org.apache.oozie.service.PurgeService; |
| import org.apache.oozie.service.Services; |
| import org.apache.oozie.service.ShareLibService; |
| import org.apache.oozie.util.AuthUrlClient; |
| import org.apache.oozie.util.ConfigUtils; |
| import org.apache.oozie.util.Instrumentation; |
| import org.apache.oozie.util.XLog; |
| import org.json.simple.JSONArray; |
| import org.json.simple.JSONObject; |
| import org.json.simple.JSONValue; |
| |
| public abstract class BaseAdminServlet extends JsonRestServlet { |
| |
| private static final long serialVersionUID = 1L; |
| private static final XLog LOG = XLog.getLog(BaseAdminServlet.class); |
| protected String modeTag; |
| |
| |
| public BaseAdminServlet(String instrumentationName, ResourceInfo[] RESOURCES_INFO) { |
| super(instrumentationName, RESOURCES_INFO); |
| setAllowSafeModeChanges(true); |
| } |
| |
| /** |
| * Oozie admin PUT request for |
| * change oozie system mode |
| * admin purge command |
| * @param request http request |
| * @param response http response |
| * @throws ServletException thrown if any servlet error |
| * @throws IOException thrown if any I/O error |
| */ |
| @Override |
| protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { |
| String resourceName = getResourceName(request); |
| request.setAttribute(AUDIT_OPERATION, resourceName); |
| request.setAttribute(AUDIT_PARAM, request.getParameter(modeTag)); |
| |
| authorizeRequest(request); |
| switch (resourceName) { |
| case RestConstants.ADMIN_STATUS_RESOURCE: |
| setOozieMode(request, response, resourceName); |
| break; |
| case RestConstants.ADMIN_PURGE: |
| String msg = schedulePurgeCommand(request); |
| JSONObject jsonObject = new JSONObject(); |
| jsonObject.put(JsonTags.PURGE, msg); |
| sendJsonResponse(response, HttpServletResponse.SC_OK, jsonObject); |
| break; |
| default: |
| throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, ErrorCode.E0301, resourceName); |
| } |
| } |
| |
| |
| /** |
| * Get JMS connection Info |
| * @param request the request |
| * @param response the response |
| * @return connection information in a form of a JsonBean |
| * @throws XServletException in case of any servlet error |
| * @throws IOException in case of any IO error |
| */ |
| abstract JsonBean getJMSConnectionInfo(HttpServletRequest request, HttpServletResponse response) |
| throws XServletException, IOException; |
| |
| |
| /** |
| * Return safemode state, instrumentation, configuration, osEnv or |
| * javaSysProps |
| */ |
| @Override |
| @SuppressWarnings("unchecked") |
| public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { |
| String resource = getResourceName(request); |
| Instrumentation instr = Services.get().get(InstrumentationService.class).get(); |
| |
| if (resource.equals(RestConstants.ADMIN_STATUS_RESOURCE)) { |
| JSONObject json = new JSONObject(); |
| populateOozieMode(json); |
| // json.put(JsonTags.SYSTEM_SAFE_MODE, getOozeMode()); |
| sendJsonResponse(response, HttpServletResponse.SC_OK, json); |
| } |
| else if (resource.equals(RestConstants.ADMIN_OS_ENV_RESOURCE)) { |
| authorizeForSystemInfo(request); |
| JSONObject json = new JSONObject(); |
| json.putAll(instr.getOSEnv()); |
| sendJsonResponse(response, HttpServletResponse.SC_OK, json); |
| } |
| else if (resource.equals(RestConstants.ADMIN_JAVA_SYS_PROPS_RESOURCE)) { |
| authorizeForSystemInfo(request); |
| JSONObject json = new JSONObject(); |
| json.putAll(instr.getJavaSystemProperties()); |
| sendJsonResponse(response, HttpServletResponse.SC_OK, json); |
| } |
| else if (resource.equals(RestConstants.ADMIN_CONFIG_RESOURCE)) { |
| authorizeForSystemInfo(request); |
| JSONObject json = new JSONObject(); |
| json.putAll(instr.getConfiguration()); |
| sendJsonResponse(response, HttpServletResponse.SC_OK, json); |
| } |
| else if (resource.equals(RestConstants.ADMIN_INSTRUMENTATION_RESOURCE)) { |
| sendInstrumentationResponse(response, instr); |
| } |
| else if (resource.equals(RestConstants.ADMIN_BUILD_VERSION_RESOURCE)) { |
| JSONObject json = new JSONObject(); |
| json.put(JsonTags.BUILD_VERSION, BuildInfo.getBuildInfo().getProperty(BuildInfo.BUILD_VERSION)); |
| json.put(JsonTags.BUILD_INFO, BuildInfo.getBuildInfo()); |
| sendJsonResponse(response, HttpServletResponse.SC_OK, json); |
| } |
| else if (resource.equals(RestConstants.ADMIN_QUEUE_DUMP_RESOURCE)) { |
| JSONObject json = new JSONObject(); |
| getQueueDump(json); |
| sendJsonResponse(response, HttpServletResponse.SC_OK, json); |
| } |
| else if (resource.equals(RestConstants.ADMIN_TIME_ZONES_RESOURCE)) { |
| JSONObject json = new JSONObject(); |
| json.put(JsonTags.AVAILABLE_TIME_ZONES, availableTimeZonesToJsonArray()); |
| sendJsonResponse(response, HttpServletResponse.SC_OK, json); |
| } |
| else if (resource.equals(RestConstants.ADMIN_JMS_INFO)) { |
| String timeZoneId = request.getParameter(RestConstants.TIME_ZONE_PARAM) == null ? "GMT" : request |
| .getParameter(RestConstants.TIME_ZONE_PARAM); |
| JsonBean jmsBean = getJMSConnectionInfo(request, response); |
| sendJsonResponse(response, HttpServletResponse.SC_OK, jmsBean, timeZoneId); |
| } |
| else if (resource.equals(RestConstants.ADMIN_AVAILABLE_OOZIE_SERVERS_RESOURCE)) { |
| JSONObject json = new JSONObject(); |
| json.putAll(getOozieURLs()); |
| sendJsonResponse(response, HttpServletResponse.SC_OK, json); |
| } |
| else if (resource.equals(RestConstants.ADMIN_UPDATE_SHARELIB)) { |
| authorizeRequest(request); |
| updateShareLib(request, response); |
| } |
| else if (resource.equals(RestConstants.ADMIN_LIST_SHARELIB)) { |
| String sharelibKey = request.getParameter(RestConstants.SHARE_LIB_REQUEST_KEY); |
| sendJsonResponse(response, HttpServletResponse.SC_OK, getShareLib(sharelibKey)); |
| } |
| else if (resource.equals(RestConstants.ADMIN_METRICS_RESOURCE)) { |
| sendMetricsResponse(response); |
| } |
| } |
| |
| private String schedulePurgeCommand(HttpServletRequest request) throws XServletException { |
| final String purgeCmdDisabledMsg = "Purge service is not enabled"; |
| if (!ConfigurationService.getBoolean(PurgeService.PURGE_COMMAND_ENABLED)) { |
| LOG.warn(purgeCmdDisabledMsg); |
| throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, ErrorCode.E0307, purgeCmdDisabledMsg); |
| } |
| String wfAgeStr = request.getParameter(RestConstants.PURGE_WF_AGE); |
| String coordAgeStr = request.getParameter(RestConstants.PURGE_COORD_AGE); |
| String bundleAgeStr = request.getParameter(RestConstants.PURGE_BUNDLE_AGE); |
| String purgeLimitStr = request.getParameter(RestConstants.PURGE_LIMIT); |
| String oldCoordActionStr = request.getParameter(RestConstants.PURGE_OLD_COORD_ACTION); |
| |
| int workflowAge = StringUtils.isBlank(wfAgeStr) ? ConfigurationService.getInt(PurgeService.CONF_OLDER_THAN) |
| : ValidationUtil.parsePositiveInteger(wfAgeStr); |
| int coordAge = StringUtils.isBlank(coordAgeStr) ? ConfigurationService.getInt(PurgeService.COORD_CONF_OLDER_THAN) |
| : ValidationUtil.parsePositiveInteger(coordAgeStr); |
| int bundleAge = StringUtils.isBlank(bundleAgeStr) ? ConfigurationService.getInt(PurgeService.BUNDLE_CONF_OLDER_THAN) |
| : ValidationUtil.parsePositiveInteger(bundleAgeStr); |
| int purgeLimit = StringUtils.isBlank(purgeLimitStr) ? ConfigurationService.getInt(PurgeService.PURGE_LIMIT) |
| : ValidationUtil.parsePositiveInteger(purgeLimitStr); |
| boolean purgeOldCoordAction = StringUtils.isBlank(oldCoordActionStr) |
| ? ConfigurationService.getBoolean(PurgeService.PURGE_OLD_COORD_ACTION) |
| : Boolean.parseBoolean(oldCoordActionStr); |
| |
| LOG.info("Executing oozie purge command."); |
| PurgeXCommand purgeXCommand = new PurgeXCommand(workflowAge, coordAge, bundleAge, purgeLimit, purgeOldCoordAction); |
| try { |
| purgeXCommand.call(); |
| return "Purge command executed successfully"; |
| } catch (CommandException e) { |
| throw new XServletException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getErrorCode(), e.getMessage(), |
| e.getCause()); |
| } |
| } |
| |
| /** |
| * Gets the list of share lib. |
| * |
| * @param sharelibKey the sharelib key |
| * @return the list of supported share lib |
| * @throws IOException in case of any servlet error |
| */ |
| @SuppressWarnings("unchecked") |
| private JSONObject getShareLib(String sharelibKey) throws IOException { |
| JSONObject json = new JSONObject(); |
| |
| ShareLibService shareLibService = Services.get().get(ShareLibService.class); |
| |
| // for testcases. |
| if (shareLibService == null) { |
| return json; |
| } |
| JSONArray shareLibList = new JSONArray(); |
| |
| Map<String, List<Path>> shareLibLauncherMap = shareLibService.getShareLib(); |
| if (sharelibKey != null && !sharelibKey.isEmpty()) { |
| Pattern pattern = Pattern.compile(sharelibKey); |
| for (String key : shareLibLauncherMap.keySet()) { |
| if (pattern.matcher(key).matches() == true) { |
| JSONObject object = new JSONObject(); |
| JSONArray fileList = new JSONArray(); |
| List<Path> pathList = shareLibLauncherMap.get(key); |
| |
| for (Path file : pathList) { |
| fileList.add(file.toString()); |
| } |
| object.put(JsonTags.SHARELIB_LIB_NAME, key); |
| object.put(JsonTags.SHARELIB_LIB_FILES, fileList); |
| shareLibList.add(object); |
| |
| } |
| } |
| } |
| else { |
| for (String key : shareLibLauncherMap.keySet()) { |
| JSONObject object = new JSONObject(); |
| object.put(JsonTags.SHARELIB_LIB_NAME, key); |
| shareLibList.add(object); |
| } |
| |
| } |
| json.put(JsonTags.SHARELIB_LIB, shareLibList); |
| |
| return json; |
| } |
| |
| /** |
| * Update share lib. support HA |
| * |
| * @param request the request |
| * @param response the response |
| * @throws IOException Signals that an I/O exception has occurred. |
| */ |
| @SuppressWarnings("unchecked") |
| public void updateShareLib(HttpServletRequest request, HttpServletResponse response) throws IOException { |
| JSONArray jsonArray = new JSONArray(); |
| JobsConcurrencyService jc = Services.get().get(JobsConcurrencyService.class); |
| if (jc.isAllServerRequest(request.getParameterMap())) { |
| Map<String, String> servers = jc.getOtherServerUrls(); |
| for (String otherUrl : servers.values()) { |
| // It's important that we specify ALL_SERVERS_PARAM=false, so that other oozie server should not call other oozie |
| //servers to update sharelib (and creating an infinite recursion) |
| String serverUrl = otherUrl + "/v2/admin/" + RestConstants.ADMIN_UPDATE_SHARELIB + "?" |
| + RestConstants.ALL_SERVER_REQUEST + "=false"; |
| try { |
| Reader reader = AuthUrlClient.callServer(serverUrl); |
| JSONObject json = (JSONObject) JSONValue.parse(reader); |
| jsonArray.add(json); |
| } |
| catch (Exception e) { |
| JSONObject errorJson = new JSONObject(); |
| errorJson.put(JsonTags.SHARELIB_UPDATE_HOST, otherUrl); |
| errorJson.put(JsonTags.SHARELIB_UPDATE_STATUS, e.getMessage()); |
| JSONObject newJson = new JSONObject(); |
| newJson.put(JsonTags.SHARELIB_LIB_UPDATE, errorJson); |
| jsonArray.add(newJson); |
| } |
| } |
| //For current server |
| JSONObject newJson = new JSONObject(); |
| newJson.put(JsonTags.SHARELIB_LIB_UPDATE, updateLocalShareLib(request)); |
| jsonArray.add(newJson); |
| sendJsonResponse(response, HttpServletResponse.SC_OK, jsonArray); |
| } |
| else { |
| JSONObject newJson = new JSONObject(); |
| newJson.put(JsonTags.SHARELIB_LIB_UPDATE, updateLocalShareLib(request)); |
| sendJsonResponse(response, HttpServletResponse.SC_OK, newJson); |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| private JSONObject updateLocalShareLib(HttpServletRequest request) { |
| ShareLibService shareLibService = Services.get().get(ShareLibService.class); |
| JSONObject json = new JSONObject(); |
| json.put(JsonTags.SHARELIB_UPDATE_HOST, ConfigUtils.getOozieEffectiveUrl()); |
| try { |
| json.putAll(shareLibService.updateShareLib()); |
| json.put(JsonTags.SHARELIB_UPDATE_STATUS, "Successful"); |
| } |
| catch (Exception e) { |
| json.put(JsonTags.SHARELIB_UPDATE_STATUS, e.getClass().getName() + ": " + e.getMessage()); |
| } |
| return json; |
| } |
| |
| /** |
| * Authorize request. |
| * |
| * @param request the HttpServletRequest |
| * @throws XServletException the x servlet exception |
| */ |
| private void authorizeRequest(HttpServletRequest request) throws XServletException { |
| try { |
| AuthorizationService auth = Services.get().get(AuthorizationService.class); |
| auth.authorizeForAdmin(getUser(request), true); |
| } |
| catch (AuthorizationException ex) { |
| throw new XServletException(HttpServletResponse.SC_UNAUTHORIZED, ex); |
| } |
| } |
| |
| /** |
| * Authorize request. |
| * |
| * @param request the HttpServletRequest |
| * @throws XServletException the x servlet exception |
| */ |
| private void authorizeForSystemInfo(HttpServletRequest request) throws XServletException { |
| try { |
| AuthorizationService auth = Services.get().get(AuthorizationService.class); |
| if (auth.isAuthorizedSystemInfo()) { |
| auth.authorizeForSystemInfo(getUser(request), getProxyUser(request)); |
| } |
| } |
| catch (AuthorizationException ex) { |
| throw new XServletException(HttpServletResponse.SC_UNAUTHORIZED, ex); |
| } |
| } |
| |
| @Override |
| protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, |
| IOException { |
| } |
| |
| @SuppressWarnings("unchecked") |
| private <T> JSONArray instrElementsToJson(Map<String, Map<String, Instrumentation.Element<T>>> instrElements) { |
| JSONArray array = new JSONArray(); |
| for (Map.Entry<String, Map<String, Instrumentation.Element<T>>> group : instrElements.entrySet()) { |
| JSONObject json = new JSONObject(); |
| String groupName = group.getKey(); |
| json.put(JsonTags.INSTR_GROUP, groupName); |
| JSONArray dataArray = new JSONArray(); |
| for (Map.Entry<String, Instrumentation.Element<T>> elementEntry : group.getValue().entrySet()) { |
| String samplerName = elementEntry.getKey(); |
| JSONObject dataJson = new JSONObject(); |
| dataJson.put(JsonTags.INSTR_NAME, samplerName); |
| Object value = elementEntry.getValue().getValue(); |
| if (value instanceof Instrumentation.Timer) { |
| Instrumentation.Timer timer = (Instrumentation.Timer) value; |
| dataJson.put(JsonTags.INSTR_TIMER_TICKS, timer.getTicks()); |
| dataJson.put(JsonTags.INSTR_TIMER_OWN_TIME_AVG, timer.getOwnAvg()); |
| dataJson.put(JsonTags.INSTR_TIMER_TOTAL_TIME_AVG, timer.getTotalAvg()); |
| dataJson.put(JsonTags.INSTR_TIMER_OWN_STD_DEV, timer.getOwnStdDev()); |
| dataJson.put(JsonTags.INSTR_TIMER_TOTAL_STD_DEV, timer.getTotalStdDev()); |
| dataJson.put(JsonTags.INSTR_TIMER_OWN_MIN_TIME, timer.getOwnMin()); |
| dataJson.put(JsonTags.INSTR_TIMER_OWN_MAX_TIME, timer.getOwnMax()); |
| dataJson.put(JsonTags.INSTR_TIMER_TOTAL_MIN_TIME, timer.getTotalMin()); |
| dataJson.put(JsonTags.INSTR_TIMER_TOTAL_MAX_TIME, timer.getTotalMax()); |
| } |
| else { |
| dataJson.put(JsonTags.INSTR_VARIABLE_VALUE, value); |
| } |
| dataArray.add(dataJson); |
| } |
| json.put(JsonTags.INSTR_DATA, dataArray); |
| array.add(json); |
| } |
| return array; |
| } |
| |
| @SuppressWarnings("unchecked") |
| private JSONObject instrToJson(Instrumentation instr) { |
| JSONObject json = new JSONObject(); |
| json.put(JsonTags.INSTR_VARIABLES, instrElementsToJson(instr.getVariables())); |
| json.put(JsonTags.INSTR_SAMPLERS, instrElementsToJson(instr.getSamplers())); |
| json.put(JsonTags.INSTR_COUNTERS, instrElementsToJson(instr.getCounters())); |
| json.put(JsonTags.INSTR_TIMERS, instrElementsToJson(instr.getTimers())); |
| return json; |
| } |
| |
| protected abstract void populateOozieMode(JSONObject json); |
| |
| protected abstract void setOozieMode(HttpServletRequest request, HttpServletResponse response, String resourceName) |
| throws XServletException; |
| |
| protected abstract void getQueueDump(JSONObject json) throws XServletException; |
| |
| private static final JSONArray GMTOffsetTimeZones = new JSONArray(); |
| static { |
| prepareGMTOffsetTimeZones(); |
| } |
| |
| @SuppressWarnings({"unchecked", "rawtypes"}) |
| private static void prepareGMTOffsetTimeZones() { |
| 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", |
| "GMT-05:00", "GMT-04:00", "GMT-03:00", "GMT-02:00", "GMT-01:00", "GMT+01:00", "GMT+02:00", |
| "GMT+03:00", "GMT+04:00", "GMT+05:00", "GMT+06:00", "GMT+07:00", "GMT+08:00", "GMT+09:00", |
| "GMT+10:00", "GMT+11:00", "GMT+12:00"}) { |
| TimeZone tz = TimeZone.getTimeZone(tzId); |
| JSONObject json = new JSONObject(); |
| json.put(JsonTags.TIME_ZOME_DISPLAY_NAME, tz.getDisplayName(false, TimeZone.SHORT) + " (" + tzId + ")"); |
| json.put(JsonTags.TIME_ZONE_ID, tzId); |
| GMTOffsetTimeZones.add(json); |
| } |
| } |
| |
| @SuppressWarnings({"unchecked", "rawtypes"}) |
| private JSONArray availableTimeZonesToJsonArray() { |
| JSONArray array = new JSONArray(); |
| for (String tzId : TimeZone.getAvailableIDs()) { |
| // skip id's that are like "Etc/GMT+01:00" because their display names are like "GMT-01:00", which is confusing |
| if (!tzId.startsWith("Etc/GMT")) { |
| JSONObject json = new JSONObject(); |
| TimeZone tZone = TimeZone.getTimeZone(tzId); |
| json.put(JsonTags.TIME_ZOME_DISPLAY_NAME, tZone.getDisplayName(false, TimeZone.SHORT) + " (" + tzId + ")"); |
| json.put(JsonTags.TIME_ZONE_ID, tzId); |
| array.add(json); |
| } |
| } |
| |
| // 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 |
| // in some hourly offsets here (though the user will not be able to use other offsets without editing the cookie manually |
| // and they are not in order) |
| array.addAll(GMTOffsetTimeZones); |
| |
| return array; |
| } |
| |
| protected void sendInstrumentationResponse(HttpServletResponse response, Instrumentation instr) |
| throws IOException, XServletException { |
| sendJsonResponse(response, HttpServletResponse.SC_OK, instrToJson(instr)); |
| } |
| |
| protected abstract Map<String, String> getOozieURLs() throws XServletException; |
| |
| protected abstract void sendMetricsResponse(HttpServletResponse response) throws IOException, XServletException; |
| } |