com.linkedin.pinot.controller.api.restlet.resources.PinotInstanceRestletResource.java Source code

Java tutorial

Introduction

Here is the source code for com.linkedin.pinot.controller.api.restlet.resources.PinotInstanceRestletResource.java

Source

/**
 * Copyright (C) 2014-2015 LinkedIn Corp. (pinot-core@linkedin.com)
 *
 * 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 com.linkedin.pinot.controller.api.restlet.resources;

import java.util.List;

import com.linkedin.pinot.common.metrics.ControllerMeter;
import com.linkedin.pinot.controller.api.ControllerRestApplication;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.helix.model.InstanceConfig;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.restlet.data.MediaType;
import org.restlet.data.Status;
import org.restlet.representation.Representation;
import org.restlet.representation.StringRepresentation;
import org.restlet.representation.Variant;
import org.restlet.resource.Get;
import org.restlet.resource.Post;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.ByteStreams;
import com.linkedin.pinot.controller.api.pojos.Instance;
import com.linkedin.pinot.common.restlet.swagger.HttpVerb;
import com.linkedin.pinot.common.restlet.swagger.Parameter;
import com.linkedin.pinot.common.restlet.swagger.Paths;
import com.linkedin.pinot.common.restlet.swagger.Response;
import com.linkedin.pinot.common.restlet.swagger.Responses;
import com.linkedin.pinot.common.restlet.swagger.Summary;
import com.linkedin.pinot.common.restlet.swagger.Tags;
import com.linkedin.pinot.controller.helix.core.PinotResourceManagerResponse;

/**
 * Restlet to manage Pinot instances.
 */

public class PinotInstanceRestletResource extends BasePinotControllerRestletResource {
    private static final Logger LOGGER = LoggerFactory.getLogger(PinotInstanceRestletResource.class);

    private final ObjectMapper mapper;

    public PinotInstanceRestletResource() {
        getVariants().add(new Variant(MediaType.TEXT_PLAIN));
        getVariants().add(new Variant(MediaType.APPLICATION_JSON));
        setNegotiated(false);
        mapper = new ObjectMapper();
    }

    @Override
    @Post("json")
    public Representation post(Representation entity) {
        StringRepresentation presentation;

        try {
            final String instanceName = (String) getRequest().getAttributes().get(INSTANCE_NAME);

            if (instanceName == null) {
                // This is a request to create an instance
                try {
                    final Instance instance = mapper.readValue(ByteStreams.toByteArray(entity.getStream()),
                            Instance.class);
                    presentation = addInstance(instance);
                } catch (final Exception e) {
                    presentation = new StringRepresentation(
                            e.getMessage() + "\n" + ExceptionUtils.getStackTrace(e));
                    LOGGER.error("Caught exception while processing post request", e);
                    ControllerRestApplication.getControllerMetrics()
                            .addMeteredGlobalValue(ControllerMeter.CONTROLLER_INTERNAL_ERROR, 1L);
                    setStatus(Status.SERVER_ERROR_INTERNAL);
                }
            } else {
                // This is a request to toggle the state of an instance
                if (_pinotHelixResourceManager.instanceExists(instanceName)) {
                    final String state = getRequest().getEntityAsText().trim();

                    if (isValidState(state)) {
                        presentation = toggleInstanceState(instanceName, state);
                    } else {
                        LOGGER.error(INVALID_STATE_ERROR);
                        setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
                        return new StringRepresentation(INVALID_STATE_ERROR);
                    }
                } else {
                    setStatus(Status.CLIENT_ERROR_NOT_FOUND);
                    presentation = new StringRepresentation("Error: Instance " + instanceName + " not found.");
                }
            }
        } catch (final Exception e) {
            presentation = new StringRepresentation(e.getMessage() + "\n" + ExceptionUtils.getStackTrace(e));
            LOGGER.error("Caught exception while processing post request", e);
            ControllerRestApplication.getControllerMetrics()
                    .addMeteredGlobalValue(ControllerMeter.CONTROLLER_INSTANCE_POST_ERROR, 1L);
            setStatus(Status.SERVER_ERROR_INTERNAL);
        }

        return presentation;
    }

    @HttpVerb("post")
    @Summary("Adds an instance")
    @Tags({ "instance" })
    @Paths({ "/instances", "/instances/" })
    @Responses({ @Response(statusCode = "200", description = "The instance was created successfully"),
            @Response(statusCode = "409", description = "The instance already exists and no action was taken"),
            @Response(statusCode = "500", description = "Failed to create the instance") })
    private StringRepresentation addInstance(
            @Parameter(name = "instance", in = "body", description = "The instance to add", required = true) Instance instance)
            throws JSONException {
        StringRepresentation presentation;
        LOGGER.info("Instance creation request received for instance " + instance.toInstanceId());
        final PinotResourceManagerResponse resp = _pinotHelixResourceManager.addInstance(instance);
        if (resp.status == PinotResourceManagerResponse.ResponseStatus.failure) {
            setStatus(Status.CLIENT_ERROR_CONFLICT);
        }
        presentation = new StringRepresentation(resp.toJSON().toString());
        return presentation;
    }

    /**
     * URI mapping:
     * "/instances", "/instances/" : Lists all the instances
     * "/instances/{instanceName}" : Gets information about an instance
     *
     * {@inheritDoc}
     * @see org.restlet.resource.ServerResource#get()
     */
    @Override
    @Get
    public Representation get() {
        Representation presentation;
        try {
            final String instanceName = (String) getRequest().getAttributes().get(INSTANCE_NAME);

            if (instanceName == null) {
                presentation = getAllInstances();
            } else {
                presentation = getInstanceInformation(instanceName);
            }

        } catch (final Exception e) {
            presentation = new StringRepresentation(e.getMessage() + "\n" + ExceptionUtils.getStackTrace(e));
            LOGGER.error("Caught exception while processing post request", e);
            ControllerRestApplication.getControllerMetrics()
                    .addMeteredGlobalValue(ControllerMeter.CONTROLLER_INSTANCE_GET_ERROR, 1L);
            setStatus(Status.SERVER_ERROR_INTERNAL);
        }
        return presentation;
    }

    /**
     * Gets the information for an instance.
     *
     * @param instanceName The instance name
     */
    @HttpVerb("get")
    @Summary("Gets information for an instance")
    @Tags({ "instance" })
    @Paths({ "/instances/{instanceName}", "/instances/{instanceName}/" })
    @Responses({ @Response(statusCode = "200", description = "Information about the specified instance"),
            @Response(statusCode = "404", description = "The specified instance does not exist"),
            @Response(statusCode = "500", description = "There was an error while fetching information for the given instance") })
    private Representation getInstanceInformation(
            @Parameter(name = "instanceName", description = "The name of the instance (eg. Server_1.2.3.4_1234 or Broker_someHost.example.com_2345)", in = "path", required = true) String instanceName) {
        try {
            if (!_pinotHelixResourceManager.instanceExists(instanceName)) {
                setStatus(Status.CLIENT_ERROR_NOT_FOUND);
                return new StringRepresentation("Error: Instance " + instanceName + " not found.");
            }

            InstanceConfig instanceConfig = _pinotHelixResourceManager.getHelixInstanceConfig(instanceName);

            JSONObject response = new JSONObject();
            response.put("instanceName", instanceConfig.getInstanceName());
            response.put("hostName", instanceConfig.getHostName());
            response.put("enabled", instanceConfig.getInstanceEnabled());
            response.put("port", instanceConfig.getPort());
            response.put("tags", new JSONArray(instanceConfig.getTags()));

            return new StringRepresentation(response.toString());
        } catch (Exception e) {
            LOGGER.warn("Caught exception while fetching information for instance {}", instanceName, e);
            setStatus(Status.SERVER_ERROR_INTERNAL);
            return new StringRepresentation("{}");
        }
    }

    /**
     * Get all instances in the cluster
     * @return List of all instances in the cluster.
     * @throws JSONException
     */
    @HttpVerb("get")
    @Summary("Views all instances")
    @Tags({ "instance" })
    @Paths({ "/instances", "/instances/" })
    @Responses({ @Response(statusCode = "200", description = "A list of instances") })
    private Representation getAllInstances() throws JSONException {
        JSONObject object = new JSONObject();
        JSONArray instanceArray = new JSONArray();

        List<String> instanceNames = _pinotHelixResourceManager.getAllInstanceNames();
        for (String instanceName : instanceNames) {
            instanceArray.put(instanceName);
        }

        object.put("instances", instanceArray);
        return new StringRepresentation(object.toString());
    }

    /**
     *
     * @param instanceName: Name of the instance to enable/disable/drop
     * @param state: One of '{enable|disable|drop}'
     * @return StringRepresentation of state after trying to enable/disable/drop instance.
     * @throws JSONException
     */
    @HttpVerb("post")
    @Summary("Enable, disable or drop an instance")
    @Tags({ "instance" })
    @Paths({ "/instances/{instanceName}/state", "/instances/{instanceName}/state" })
    @Responses({ @Response(statusCode = "200", description = "The instance state was changed successfully"),
            @Response(statusCode = "400", description = "The state given was not enable, disable or drop"),
            @Response(statusCode = "404", description = "The instance was not found") })
    private StringRepresentation toggleInstanceState(
            @Parameter(name = "instanceName", in = "path", description = "The name of the instance for which to toggle its state", required = true) String instanceName,
            @Parameter(name = "state", in = "body", description = "The desired instance state, either enable, disable or drop", required = true) String state)
            throws JSONException {
        if (StateType.ENABLE.name().equalsIgnoreCase(state)) {
            return new StringRepresentation(
                    _pinotHelixResourceManager.enableInstance(instanceName).toJSON().toString());

        } else if (StateType.DISABLE.name().equalsIgnoreCase(state)) {
            return new StringRepresentation(
                    _pinotHelixResourceManager.disableInstance(instanceName).toJSON().toString());

        } else if (StateType.DROP.name().equalsIgnoreCase(state)) {
            return new StringRepresentation(
                    _pinotHelixResourceManager.dropInstance(instanceName).toJSON().toString());

        } else {
            LOGGER.error(INVALID_INSTANCE_URI_ERROR);
            setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
            return new StringRepresentation(INVALID_INSTANCE_URI_ERROR);
        }
    }
}