blob: da75fab04bd9daf482afe1dcdeb4a8f3afdbab2e [file] [log] [blame]
Henry Yue20926e2016-08-25 22:58:02 -04001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2016-present Open Networking Foundation
Henry Yue20926e2016-08-25 22:58:02 -04003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.protocol.restconf.server.rpp;
18
19import com.fasterxml.jackson.core.JsonProcessingException;
20import com.fasterxml.jackson.databind.node.ObjectNode;
21import org.glassfish.jersey.server.ChunkedOutput;
Henry Yue20926e2016-08-25 22:58:02 -040022import org.onosproject.rest.AbstractWebResource;
Henry Yu3a91b132017-04-26 16:00:55 -040023import org.onosproject.restconf.api.Patch;
24import org.onosproject.restconf.api.RestconfException;
Henry Yuc10f7fc2017-07-26 13:42:08 -040025import org.onosproject.restconf.api.RestconfRpcOutput;
Henry Yu3a91b132017-04-26 16:00:55 -040026import org.onosproject.restconf.api.RestconfService;
Henry Yue20926e2016-08-25 22:58:02 -040027import org.slf4j.Logger;
28
Henry Yuc10f7fc2017-07-26 13:42:08 -040029import javax.servlet.http.HttpServletRequest;
Henry Yue20926e2016-08-25 22:58:02 -040030import javax.ws.rs.Consumes;
31import javax.ws.rs.DELETE;
32import javax.ws.rs.GET;
33import javax.ws.rs.POST;
34import javax.ws.rs.PUT;
35import javax.ws.rs.Path;
36import javax.ws.rs.PathParam;
37import javax.ws.rs.Produces;
38import javax.ws.rs.core.Context;
39import javax.ws.rs.core.MediaType;
40import javax.ws.rs.core.Response;
41import javax.ws.rs.core.UriInfo;
42import java.io.IOException;
43import java.io.InputStream;
Henry Yuc10f7fc2017-07-26 13:42:08 -040044import java.net.URI;
45import java.util.concurrent.CompletableFuture;
Henry Yue20926e2016-08-25 22:58:02 -040046
chengfanc58d4be2016-09-20 10:33:12 +080047import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
48import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
Henry Yuc10f7fc2017-07-26 13:42:08 -040049import static javax.ws.rs.core.Response.Status.NOT_FOUND;
50import static javax.ws.rs.core.Response.Status.OK;
Ray Milkeyb784adb2018-04-02 15:33:07 -070051import static org.onlab.util.Tools.readTreeFromStream;
Henry Yue20926e2016-08-25 22:58:02 -040052import static org.slf4j.LoggerFactory.getLogger;
53
chengfanc58d4be2016-09-20 10:33:12 +080054
Henry Yue20926e2016-08-25 22:58:02 -040055/*
56 * This class is the main implementation of the RESTCONF Protocol
57 * Proxy module. Currently it only handles some basic operations
58 * on data resource nodes. However, the design intention is to
59 * create a code structure that allows new methods/functionality
60 * to be easily added in future releases.
61 */
62
63/**
64 * Implementation of the RESTCONF Protocol Proxy module.
65 */
66@Path("/")
67public class RestconfWebResource extends AbstractWebResource {
68
69 @Context
70 UriInfo uriInfo;
71
Henry Yuc10f7fc2017-07-26 13:42:08 -040072 private static final String NOT_EXIST = "Requested data resource does not exist";
73
Henry Yu3a91b132017-04-26 16:00:55 -040074 private final RestconfService service = get(RestconfService.class);
Henry Yue20926e2016-08-25 22:58:02 -040075 private final Logger log = getLogger(getClass());
76
77 /**
78 * Handles a RESTCONF GET operation against a target data resource. If the
79 * operation is successful, the JSON presentation of the resource plus HTTP
80 * status code "200 OK" is returned. Otherwise, HTTP error status code
81 * "400 Bad Request" is returned.
82 *
83 * @param uriString URI of the data resource.
84 * @return HTTP response
85 */
86 @GET
Henry Yuc10f7fc2017-07-26 13:42:08 -040087 @Consumes(MediaType.APPLICATION_JSON)
Henry Yue20926e2016-08-25 22:58:02 -040088 @Produces(MediaType.APPLICATION_JSON)
89 @Path("data/{identifier : .+}")
90 public Response handleGetRequest(@PathParam("identifier") String uriString) {
Henry Yue20926e2016-08-25 22:58:02 -040091 log.debug("handleGetRequest: {}", uriString);
92
Henry Yuc10f7fc2017-07-26 13:42:08 -040093 URI uri = uriInfo.getRequestUri();
94
Henry Yue20926e2016-08-25 22:58:02 -040095 try {
Henry Yuc10f7fc2017-07-26 13:42:08 -040096 ObjectNode node = service.runGetOperationOnDataResource(uri);
97 if (node == null) {
98 return Response.status(NOT_FOUND).entity(NOT_EXIST).build();
99 }
Henry Yue20926e2016-08-25 22:58:02 -0400100 return ok(node).build();
101 } catch (RestconfException e) {
102 log.error("ERROR: handleGetRequest: {}", e.getMessage());
103 log.debug("Exception in handleGetRequest:", e);
104 return e.getResponse();
105 }
106 }
107
108 /**
109 * Handles the RESTCONF Event Notification Subscription request. If the
110 * subscription is successful, a ChunkedOutput stream is created and returned
111 * to the caller.
Yuta HIGUCHId1ce4bc2017-06-03 01:05:33 -0700112 * <p>
Henry Yue20926e2016-08-25 22:58:02 -0400113 * This function is not blocked on streaming the data (so that it can handle
114 * other incoming requests). Instead, a worker thread running in the background
115 * does the data streaming. If errors occur during streaming, the worker thread
116 * calls ChunkedOutput.close() to disconnect the session and terminates itself.
117 *
118 * @param streamId Event stream ID
Henry Yuc10f7fc2017-07-26 13:42:08 -0400119 * @param request RESTCONF client information from which the client IP
120 * address is retrieved
Henry Yue20926e2016-08-25 22:58:02 -0400121 * @return A string data stream over HTTP keep-alive session
122 */
123 @GET
124 @Produces(MediaType.APPLICATION_JSON)
125 @Path("streams/{streamId}")
Henry Yuc10f7fc2017-07-26 13:42:08 -0400126 public ChunkedOutput<String> handleNotificationRegistration(@PathParam("streamId") String streamId,
127 @Context HttpServletRequest request) {
Yuta HIGUCHId1ce4bc2017-06-03 01:05:33 -0700128 final ChunkedOutput<String> output = new ChunkedOutput<>(String.class);
Henry Yue20926e2016-08-25 22:58:02 -0400129 try {
Henry Yuc10f7fc2017-07-26 13:42:08 -0400130 service.subscribeEventStream(streamId, request.getRemoteAddr(), output);
Henry Yue20926e2016-08-25 22:58:02 -0400131 } catch (RestconfException e) {
132 log.error("ERROR: handleNotificationRegistration: {}", e.getMessage());
133 log.debug("Exception in handleNotificationRegistration:", e);
134 try {
135 output.close();
136 } catch (IOException ex) {
137 log.error("ERROR: handleNotificationRegistration:", ex);
138 }
139 }
140
141 return output;
142 }
143
144 /**
Henry Yuc10f7fc2017-07-26 13:42:08 -0400145 * Handles a RESTCONF POST operation against the entire data store. If the
146 * operation is successful, HTTP status code "201 Created" is returned
147 * and there is no response message-body. If the data resource already
148 * exists, then the HTTP status code "409 Conflict" is returned.
149 *
150 * @param stream Input JSON object
151 * @return HTTP response
152 */
153 @POST
154 @Consumes(MediaType.APPLICATION_JSON)
155 @Produces(MediaType.APPLICATION_JSON)
156 @Path("data")
157 public Response handlePostDatastore(InputStream stream) {
158
159 log.debug("handlePostDatastore");
160 return handlePostRequest(null, stream);
161 }
162
163 /**
Henry Yue20926e2016-08-25 22:58:02 -0400164 * Handles a RESTCONF POST operation against a data resource. If the
165 * operation is successful, HTTP status code "201 Created" is returned
166 * and there is no response message-body. If the data resource already
167 * exists, then the HTTP status code "409 Conflict" is returned.
168 *
Henry Yuc10f7fc2017-07-26 13:42:08 -0400169 * @param uriString URI of the parent data resource
Henry Yue20926e2016-08-25 22:58:02 -0400170 * @param stream Input JSON object
171 * @return HTTP response
172 */
173 @POST
174 @Consumes(MediaType.APPLICATION_JSON)
175 @Produces(MediaType.APPLICATION_JSON)
176 @Path("data/{identifier : .+}")
chengfanc58d4be2016-09-20 10:33:12 +0800177 public Response handlePostRequest(@PathParam("identifier") String uriString,
178 InputStream stream) {
Henry Yue20926e2016-08-25 22:58:02 -0400179 log.debug("handlePostRequest: {}", uriString);
180
Henry Yuc10f7fc2017-07-26 13:42:08 -0400181 URI uri = uriInfo.getRequestUri();
182
Henry Yue20926e2016-08-25 22:58:02 -0400183 try {
Ray Milkeyb784adb2018-04-02 15:33:07 -0700184 ObjectNode rootNode = readTreeFromStream(mapper(), stream);
Henry Yue20926e2016-08-25 22:58:02 -0400185
Henry Yuc10f7fc2017-07-26 13:42:08 -0400186 service.runPostOperationOnDataResource(uri, rootNode);
Henry Yue20926e2016-08-25 22:58:02 -0400187 return Response.created(uriInfo.getRequestUri()).build();
188 } catch (JsonProcessingException e) {
189 log.error("ERROR: handlePostRequest ", e);
chengfanc58d4be2016-09-20 10:33:12 +0800190 return Response.status(BAD_REQUEST).build();
Henry Yue20926e2016-08-25 22:58:02 -0400191 } catch (RestconfException e) {
192 log.error("ERROR: handlePostRequest: {}", e.getMessage());
193 log.debug("Exception in handlePostRequest:", e);
194 return e.getResponse();
195 } catch (IOException ex) {
196 log.error("ERROR: handlePostRequest ", ex);
chengfanc58d4be2016-09-20 10:33:12 +0800197 return Response.status(INTERNAL_SERVER_ERROR).build();
Henry Yue20926e2016-08-25 22:58:02 -0400198 }
199 }
200
201 /**
Henry Yuc10f7fc2017-07-26 13:42:08 -0400202 * Handles a RESTCONF RPC request. This function executes the RPC in
203 * the target application's context and returns the results as a Future.
204 *
205 * @param rpcName Name of the RPC
206 * @param rpcInput Input parameters
207 * @param request RESTCONF client information from which the client IP
208 * address is retrieved
209 * @return RPC output
210 */
211 @POST
212 @Consumes(MediaType.APPLICATION_JSON)
213 @Produces(MediaType.APPLICATION_JSON)
214 @Path("operations/{rpc : .+}")
215 public Response handleRpcRequest(@PathParam("rpc") String rpcName,
216 InputStream rpcInput,
217 @Context HttpServletRequest request) {
218 URI uri = uriInfo.getRequestUri();
219 try {
Ray Milkeyb784adb2018-04-02 15:33:07 -0700220 ObjectNode inputNode = readTreeFromStream(mapper(), rpcInput);
Henry Yuc10f7fc2017-07-26 13:42:08 -0400221 CompletableFuture<RestconfRpcOutput> rpcFuture = service.runRpc(uri,
222 inputNode,
223 request.getRemoteAddr());
224 RestconfRpcOutput restconfRpcOutput;
225 restconfRpcOutput = rpcFuture.get();
226 if (restconfRpcOutput.status() != OK) {
227 return Response.status(restconfRpcOutput.status())
228 .entity(restconfRpcOutput.reason()).build();
229 }
230 ObjectNode node = restconfRpcOutput.output();
231 return ok(node).build();
232 } catch (JsonProcessingException e) {
233 log.error("ERROR: handleRpcRequest", e);
234 return Response.status(BAD_REQUEST).build();
235 } catch (RestconfException e) {
236 log.error("ERROR: handleRpcRequest: {}", e.getMessage());
237 log.debug("Exception in handleRpcRequest:", e);
238 return e.getResponse();
239 } catch (Exception e) {
240 log.error("ERROR: handleRpcRequest ", e);
241 return Response.status(INTERNAL_SERVER_ERROR).build();
242 }
243 }
244
245 /**
Henry Yue20926e2016-08-25 22:58:02 -0400246 * Handles a RESTCONF PUT operation against a data resource. If a new
247 * resource is successfully created, then the HTTP status code "201 Created"
248 * is returned. If an existing resource is modified, then the HTTP
249 * status code "204 No Content" is returned. If the input JSON payload
250 * contains errors, then "400 Bad Request" is returned. If an exception
251 * occurs during the operation, the status code enclosed in
252 * the RestconfException object, such as "500 Internal Server Error",
253 * is returned.
254 *
255 * @param uriString URI of the data resource.
256 * @param stream Input JSON object
257 * @return HTTP response
258 */
259 @PUT
260 @Consumes(MediaType.APPLICATION_JSON)
261 @Produces(MediaType.APPLICATION_JSON)
262 @Path("data/{identifier : .+}")
chengfanc58d4be2016-09-20 10:33:12 +0800263 public Response handlePutRequest(@PathParam("identifier") String uriString,
264 InputStream stream) {
Henry Yue20926e2016-08-25 22:58:02 -0400265 log.debug("handlePutRequest: {}", uriString);
266
Henry Yuc10f7fc2017-07-26 13:42:08 -0400267 URI uri = uriInfo.getRequestUri();
268
Henry Yue20926e2016-08-25 22:58:02 -0400269 try {
Ray Milkeyb784adb2018-04-02 15:33:07 -0700270 ObjectNode rootNode = readTreeFromStream(mapper(), stream);
Henry Yue20926e2016-08-25 22:58:02 -0400271
Henry Yuc10f7fc2017-07-26 13:42:08 -0400272 service.runPutOperationOnDataResource(uri, rootNode);
Henry Yue20926e2016-08-25 22:58:02 -0400273 return Response.created(uriInfo.getRequestUri()).build();
274 } catch (JsonProcessingException e) {
275 log.error("ERROR: handlePutRequest ", e);
chengfanc58d4be2016-09-20 10:33:12 +0800276 return Response.status(BAD_REQUEST).build();
Henry Yue20926e2016-08-25 22:58:02 -0400277 } catch (RestconfException e) {
278 log.error("ERROR: handlePutRequest: {}", e.getMessage());
279 log.debug("Exception in handlePutRequest:", e);
280 return e.getResponse();
281 } catch (IOException ex) {
282 log.error("ERROR: handlePutRequest ", ex);
chengfanc58d4be2016-09-20 10:33:12 +0800283 return Response.status(INTERNAL_SERVER_ERROR).build();
Henry Yue20926e2016-08-25 22:58:02 -0400284 }
285 }
286
287 /**
288 * Handles the RESTCONF DELETION Operation against a data resource. If the
289 * resource is successfully deleted, the HTTP status code "204 No Content"
290 * is returned in the response. If an exception occurs, then the
291 * HTTP status code enclosed in the RestconfException object is
292 * returned.
293 *
294 * @param uriString URI of the data resource to be deleted.
295 * @return HTTP response
296 */
297 @DELETE
Henry Yuc10f7fc2017-07-26 13:42:08 -0400298 @Consumes(MediaType.APPLICATION_JSON)
Henry Yue20926e2016-08-25 22:58:02 -0400299 @Produces(MediaType.APPLICATION_JSON)
300 @Path("data/{identifier : .+}")
301 public Response handleDeleteRequest(@PathParam("identifier") String uriString) {
Henry Yue20926e2016-08-25 22:58:02 -0400302 log.debug("handleDeleteRequest: {}", uriString);
303
Henry Yuc10f7fc2017-07-26 13:42:08 -0400304 URI uri = uriInfo.getRequestUri();
305
Henry Yue20926e2016-08-25 22:58:02 -0400306 try {
Henry Yuc10f7fc2017-07-26 13:42:08 -0400307 service.runDeleteOperationOnDataResource(uri);
Henry Yue20926e2016-08-25 22:58:02 -0400308 return Response.ok().build();
309 } catch (RestconfException e) {
310 log.error("ERROR: handleDeleteRequest: {}", e.getMessage());
311 log.debug("Exception in handleDeleteRequest:", e);
312 return e.getResponse();
313 }
314 }
315
chengfanc58d4be2016-09-20 10:33:12 +0800316 /**
317 * Handles a RESTCONF PATCH operation against a data resource.
318 * If the PATCH request succeeds, a "200 OK" status-line is returned if
319 * there is a message-body, and "204 No Content" is returned if no
320 * response message-body is sent.
321 *
Henry Yuc10f7fc2017-07-26 13:42:08 -0400322 * @param uriString URI of the parent data resource
chengfanc58d4be2016-09-20 10:33:12 +0800323 * @param stream Input JSON object
324 * @return HTTP response
325 */
326 @Patch
327 @Consumes(MediaType.APPLICATION_JSON)
328 @Produces(MediaType.APPLICATION_JSON)
329 @Path("data/{identifier : .+}")
330 public Response handlePatchRequest(@PathParam("identifier") String uriString,
331 InputStream stream) {
332
333 log.debug("handlePatchRequest: {}", uriString);
334
Henry Yuc10f7fc2017-07-26 13:42:08 -0400335 URI uri = uriInfo.getRequestUri();
336
chengfanc58d4be2016-09-20 10:33:12 +0800337 try {
Ray Milkeyb784adb2018-04-02 15:33:07 -0700338 ObjectNode rootNode = readTreeFromStream(mapper(), stream);
chengfanc58d4be2016-09-20 10:33:12 +0800339
Henry Yuc10f7fc2017-07-26 13:42:08 -0400340 service.runPatchOperationOnDataResource(uri, rootNode);
chengfanc58d4be2016-09-20 10:33:12 +0800341 return Response.ok().build();
342 } catch (JsonProcessingException e) {
343 log.error("ERROR: handlePatchRequest ", e);
344 return Response.status(BAD_REQUEST).build();
345 } catch (RestconfException e) {
346 log.error("ERROR: handlePatchRequest: {}", e.getMessage());
347 log.debug("Exception in handlePatchRequest:", e);
348 return e.getResponse();
349 } catch (IOException ex) {
350 log.error("ERROR: handlePatchRequest ", ex);
351 return Response.status(INTERNAL_SERVER_ERROR).build();
352 }
353 }
354
Henry Yuc10f7fc2017-07-26 13:42:08 -0400355
356 /**
357 * Handles a RESTCONF PATCH operation against the entire data store.
358 * If the PATCH request succeeds, a "200 OK" status-line is returned if
359 * there is a message-body, and "204 No Content" is returned if no
360 * response message-body is sent.
361 *
362 * @param stream Input JSON object
363 * @return HTTP response
364 */
365 @Patch
366 @Consumes(MediaType.APPLICATION_JSON)
367 @Produces(MediaType.APPLICATION_JSON)
368 @Path("data")
369 public Response handlePatchDatastore(InputStream stream) {
370 log.debug("handlePatchDatastore");
371 return handlePatchRequest(null, stream);
372 }
Henry Yue20926e2016-08-25 22:58:02 -0400373}