View Javadoc
1   /*
2    * Licensed to The Apereo Foundation under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional
4    * information regarding copyright ownership.
5    *
6    *
7    * The Apereo Foundation licenses this file to you under the Educational
8    * Community License, Version 2.0 (the "License"); you may not use this file
9    * except in compliance with the License. You may obtain a copy of the License
10   * at:
11   *
12   *   http://opensource.org/licenses/ecl2.txt
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
17   * License for the specific language governing permissions and limitations under
18   * the License.
19   *
20   */
21  package org.opencastproject.terminationstate.endpoint.aws;
22  
23  import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
24  import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
25  import static javax.servlet.http.HttpServletResponse.SC_OK;
26  import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
27  
28  import org.opencastproject.terminationstate.api.TerminationStateService;
29  import org.opencastproject.terminationstate.endpoint.api.TerminationStateRestService;
30  import org.opencastproject.util.doc.rest.RestParameter;
31  import org.opencastproject.util.doc.rest.RestParameter.Type;
32  import org.opencastproject.util.doc.rest.RestQuery;
33  import org.opencastproject.util.doc.rest.RestResponse;
34  import org.opencastproject.util.doc.rest.RestService;
35  
36  import org.json.simple.JSONObject;
37  import org.osgi.service.component.annotations.Component;
38  import org.osgi.service.component.annotations.Reference;
39  import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;
40  import org.slf4j.Logger;
41  import org.slf4j.LoggerFactory;
42  
43  import javax.ws.rs.FormParam;
44  import javax.ws.rs.GET;
45  import javax.ws.rs.PUT;
46  import javax.ws.rs.Path;
47  import javax.ws.rs.Produces;
48  import javax.ws.rs.core.MediaType;
49  import javax.ws.rs.core.Response;
50  
51  @Path("/termination/aws/autoscaling")
52  @RestService(
53      name = "terminationstateservice",
54      title = "Termination State Service: AWS Auto Scaling",
55      abstractText = "This service responds to notifications from an AWS AutoScaling Group that the "
56          + "underlying EC2 instance is terminating. When put into a termination 'wait' state, it "
57          + "stops the node accepting further jobs, and will inform AWS AutoScaling, once any "
58          + "running jobs complete, that the instance can be terminated. NOTE: The service does not "
59          + "actually shut down the node or instance.",
60      notes = {
61          "All paths above are relative to the REST endpoint base (something like "
62              + "http://your.server/termination/aws/autoscaling)",
63          "If the service is down or not working it will return a status 503, this means the the "
64              + "underlying service is not working and is either restarting or has failed",
65          "A status code 500 means a general failure has occurred which is not recoverable and was "
66              + "not anticipated. In other words, there is a bug! You should file an error report "
67              + "with your server logs from the time when the error occurred: "
68              + "<a href=\"https://github.com/opencast/opencast/issues\">Opencast Issue Tracker</a>"
69      }
70  )
71  @Component(
72      immediate = true,
73      service = AutoScalingTerminationStateRestService.class,
74      property = {
75          "service.description=Termination State Rest Service: AWS Auto Scaling",
76          "opencast.service.type=org.opencastproject.terminationstate.aws.autoscaling",
77          "opencast.service.path=/termination/aws/autoscaling"
78      }
79  )
80  @JaxrsResource
81  public class AutoScalingTerminationStateRestService implements TerminationStateRestService {
82  
83    private static final Logger logger = LoggerFactory.getLogger(AutoScalingTerminationStateRestService.class);
84  
85    private TerminationStateService service;
86  
87    @Override
88    @GET
89    @Path("/state")
90    @Produces(MediaType.APPLICATION_JSON)
91    @RestQuery(
92        name = "stateasjson",
93        description = "Returns the Termination State as JSON. Possible termination states are none, wait and ready.",
94        returnDescription = "A JSON representation of the termination state.",
95        responses = {
96            @RestResponse(
97                responseCode = SC_OK,
98                description = "A JSON representation of the termination state."
99            ),
100           @RestResponse(
101               responseCode = SC_SERVICE_UNAVAILABLE,
102               description = "The AWS Autoscaling Termination State Service is disabled or unavailable"
103           )
104       }
105   )
106   public Response getState() {
107     if (service != null) {
108       JSONObject json  = new JSONObject();
109       String state = service.getState().toString();
110       json.put("state", state);
111       return Response.ok(json.toJSONString()).build();
112     } else {
113       logger.error("TerminationStateService is not available");
114       return Response.status(Response.Status.SERVICE_UNAVAILABLE).build();
115     }
116   }
117 
118   @Override
119   @PUT
120   @Path("/state")
121   @RestQuery(
122       name = "setstate",
123       description = "Set the termination state. The only permissable value to write to the state is 'wait'",
124       returnDescription = "Whether the termination state was set successfully",
125       restParameters = {
126           @RestParameter(
127               name = "state",
128               type = Type.STRING,
129               defaultValue = "wait",
130               description = "The termination state, the only valid value is 'wait'",
131               isRequired = false
132           )
133       },
134       responses = {
135           @RestResponse(
136               responseCode = SC_NO_CONTENT,
137               description = "The node is preparing to terminate"
138           ),
139           @RestResponse(
140               responseCode = SC_BAD_REQUEST,
141               description = "The state was not 'wait'"
142           ),
143           @RestResponse(
144               responseCode = SC_SERVICE_UNAVAILABLE,
145               description = "The AWS Autoscaling Termination State Service is disabled or unavailable"
146           ),
147       }
148   )
149   public Response setState(@FormParam("state") String state) {
150     if (service != null) {
151       if (TerminationStateService.TerminationState.WAIT.toString().equalsIgnoreCase(state)) {
152         service.setState(TerminationStateService.TerminationState.WAIT);
153 
154         // check is state has changed (ie service is working)
155         if (service.getState() != TerminationStateService.TerminationState.NONE) {
156           return Response.noContent().build();
157         }
158       } else {
159         logger.error("state must be 'wait'");
160         return Response.status(Response.Status.BAD_REQUEST).build();
161       }
162     }
163 
164     logger.error("AWS Autoscaling Termination State Serice is not available");
165     return Response.status(Response.Status.SERVICE_UNAVAILABLE).build();
166   }
167 
168   /**
169    OSGI injection callback
170    @param service termination state service instance
171   */
172   @Reference(
173       target = "(&(vendor.name=aws)(vendor.service=autoscaling))"
174   )
175   public void setService(TerminationStateService service) {
176     this.service = service;
177   }
178 }