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}