blob: 2f12f25d59e626f6ee8df450b0044fa16dea4822 [file] [log] [blame]
Jian Li49109b52019-01-22 00:17:28 +09001/*
2 * Copyright 2019-present Open Networking Foundation
3 *
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 */
16package org.onosproject.k8snode.web;
17
18import com.fasterxml.jackson.databind.JsonNode;
19import com.fasterxml.jackson.databind.node.ArrayNode;
20import com.fasterxml.jackson.databind.node.ObjectNode;
21import com.google.common.collect.Sets;
Jian Li3defa842019-02-12 00:31:35 +090022import org.onosproject.k8snode.api.K8sApiConfig;
23import org.onosproject.k8snode.api.K8sApiConfigAdminService;
Jian Li49109b52019-01-22 00:17:28 +090024import org.onosproject.k8snode.api.K8sNode;
25import org.onosproject.k8snode.api.K8sNodeAdminService;
Jian Li1e03e712019-06-20 18:13:48 +090026import org.onosproject.k8snode.api.K8sNodeState;
Jian Li49109b52019-01-22 00:17:28 +090027import org.onosproject.rest.AbstractWebResource;
28import org.slf4j.Logger;
29import org.slf4j.LoggerFactory;
30
31import javax.ws.rs.Consumes;
Jian Li3defa842019-02-12 00:31:35 +090032import javax.ws.rs.DELETE;
Jian Li1e03e712019-06-20 18:13:48 +090033import javax.ws.rs.GET;
Jian Li49109b52019-01-22 00:17:28 +090034import javax.ws.rs.POST;
35import javax.ws.rs.PUT;
36import javax.ws.rs.Path;
37import javax.ws.rs.PathParam;
38import javax.ws.rs.Produces;
39import javax.ws.rs.core.Context;
40import javax.ws.rs.core.MediaType;
41import javax.ws.rs.core.Response;
42import javax.ws.rs.core.UriBuilder;
43import javax.ws.rs.core.UriInfo;
44import java.io.InputStream;
45import java.util.Set;
46
47import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
48import static javax.ws.rs.core.Response.created;
49import static org.onlab.util.Tools.nullIsIllegal;
50import static org.onlab.util.Tools.readTreeFromStream;
Jian Lid376e062020-01-02 23:57:13 +090051import static org.onosproject.k8snode.api.K8sNodeState.POST_ON_BOARD;
Jian Li3defa842019-02-12 00:31:35 +090052import static org.onosproject.k8snode.util.K8sNodeUtil.endpoint;
Jian Li49109b52019-01-22 00:17:28 +090053
54/**
55 * Handles REST API call of kubernetes node config.
56 */
57
58@Path("configure")
59public class K8sNodeWebResource extends AbstractWebResource {
60
61 private final Logger log = LoggerFactory.getLogger(getClass());
62
63 private static final String MESSAGE_NODE = "Received node %s request";
64 private static final String NODES = "nodes";
Jian Li3defa842019-02-12 00:31:35 +090065 private static final String API_CONFIGS = "apiConfigs";
Jian Li49109b52019-01-22 00:17:28 +090066 private static final String CREATE = "CREATE";
67 private static final String UPDATE = "UPDATE";
68 private static final String NODE_ID = "NODE_ID";
Jian Li3defa842019-02-12 00:31:35 +090069 private static final String REMOVE = "REMOVE";
Jian Li1e03e712019-06-20 18:13:48 +090070 private static final String QUERY = "QUERY";
71 private static final String INIT = "INIT";
72 private static final String NOT_EXIST = "Not exist";
73 private static final String STATE = "State";
Jian Lid376e062020-01-02 23:57:13 +090074 private static final String RESULT = "Result";
Jian Li49109b52019-01-22 00:17:28 +090075
76 private static final String HOST_NAME = "hostname";
Jian Li3defa842019-02-12 00:31:35 +090077 private static final String ENDPOINT = "endpoint";
Jian Li49109b52019-01-22 00:17:28 +090078 private static final String ERROR_MESSAGE = " cannot be null";
79
Jian Li3defa842019-02-12 00:31:35 +090080 private final K8sNodeAdminService nodeAdminService = get(K8sNodeAdminService.class);
81 private final K8sApiConfigAdminService configAdminService = get(K8sApiConfigAdminService.class);
Jian Li49109b52019-01-22 00:17:28 +090082
83 @Context
84 private UriInfo uriInfo;
85
86 /**
87 * Creates a set of kubernetes nodes' config from the JSON input stream.
88 *
89 * @param input kubernetes nodes JSON input stream
90 * @return 201 CREATED if the JSON is correct, 400 BAD_REQUEST if the JSON
91 * is malformed
92 * @onos.rsModel K8sNode
93 */
94 @POST
Jian Li3defa842019-02-12 00:31:35 +090095 @Path("node")
Jian Li49109b52019-01-22 00:17:28 +090096 @Consumes(MediaType.APPLICATION_JSON)
97 @Produces(MediaType.APPLICATION_JSON)
98 public Response createNodes(InputStream input) {
99 log.trace(String.format(MESSAGE_NODE, CREATE));
100
101 readNodeConfiguration(input).forEach(node -> {
Jian Li3defa842019-02-12 00:31:35 +0900102 K8sNode existing = nodeAdminService.node(node.hostname());
Jian Li49109b52019-01-22 00:17:28 +0900103 if (existing == null) {
Jian Li3defa842019-02-12 00:31:35 +0900104 nodeAdminService.createNode(node);
Jian Li49109b52019-01-22 00:17:28 +0900105 }
106 });
107
108 UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
109 .path(NODES)
110 .path(NODE_ID);
111
112 return created(locationBuilder.build()).build();
113 }
114
115 /**
116 * Updates a set of kubernetes nodes' config from the JSON input stream.
117 *
118 * @param input kubernetes nodes JSON input stream
119 * @return 200 OK with the updated kubernetes node's config, 400 BAD_REQUEST
120 * if the JSON is malformed, and 304 NOT_MODIFIED without the updated config
121 * @onos.rsModel K8sNode
122 */
123 @PUT
Jian Li3defa842019-02-12 00:31:35 +0900124 @Path("node")
Jian Li49109b52019-01-22 00:17:28 +0900125 @Consumes(MediaType.APPLICATION_JSON)
126 @Produces(MediaType.APPLICATION_JSON)
127 public Response updateNodes(InputStream input) {
128 log.trace(String.format(MESSAGE_NODE, UPDATE));
129
130 Set<K8sNode> nodes = readNodeConfiguration(input);
131 for (K8sNode node: nodes) {
Jian Li3defa842019-02-12 00:31:35 +0900132 K8sNode existing = nodeAdminService.node(node.hostname());
Jian Li49109b52019-01-22 00:17:28 +0900133 if (existing == null) {
134 log.warn("There is no node configuration to update : {}", node.hostname());
135 return Response.notModified().build();
136 } else if (!existing.equals(node)) {
Jian Li3defa842019-02-12 00:31:35 +0900137 nodeAdminService.updateNode(node);
Jian Li49109b52019-01-22 00:17:28 +0900138 }
139 }
140
141 return Response.ok().build();
142 }
143
144 /**
145 * Removes a set of kubernetes nodes' config from the JSON input stream.
146 *
147 * @param hostname host name contained in kubernetes nodes configuration
148 * @return 204 NO_CONTENT, 400 BAD_REQUEST if the JSON is malformed, and
149 * 304 NOT_MODIFIED without the updated config
Jian Li49109b52019-01-22 00:17:28 +0900150 */
Jian Li3defa842019-02-12 00:31:35 +0900151 @DELETE
152 @Path("node/{hostname}")
Jian Li49109b52019-01-22 00:17:28 +0900153 @Consumes(MediaType.APPLICATION_JSON)
154 @Produces(MediaType.APPLICATION_JSON)
Jian Li49109b52019-01-22 00:17:28 +0900155 public Response deleteNodes(@PathParam("hostname") String hostname) {
Jian Li3defa842019-02-12 00:31:35 +0900156 log.trace(String.format(MESSAGE_NODE, REMOVE));
Jian Li49109b52019-01-22 00:17:28 +0900157
158 K8sNode existing =
Jian Li3defa842019-02-12 00:31:35 +0900159 nodeAdminService.node(nullIsIllegal(hostname, HOST_NAME + ERROR_MESSAGE));
Jian Li49109b52019-01-22 00:17:28 +0900160
161 if (existing == null) {
162 log.warn("There is no node configuration to delete : {}", hostname);
163 return Response.notModified().build();
164 } else {
Jian Li3defa842019-02-12 00:31:35 +0900165 nodeAdminService.removeNode(hostname);
Jian Li49109b52019-01-22 00:17:28 +0900166 }
167
168 return Response.noContent().build();
169 }
170
Jian Li1e03e712019-06-20 18:13:48 +0900171
172 /**
173 * Obtains the state of the kubernetes node.
174 *
175 * @param hostname hostname of the kubernetes
176 * @return the state of the kubernetes node in Json
177 */
178 @GET
179 @Produces(MediaType.APPLICATION_JSON)
180 @Path("state/{hostname}")
181 public Response stateOfNode(@PathParam("hostname") String hostname) {
182 log.trace(String.format(MESSAGE_NODE, QUERY));
183
184 K8sNode k8sNode = nodeAdminService.node(hostname);
185 String nodeState = k8sNode != null ? k8sNode.state().toString() : NOT_EXIST;
186
187 return ok(mapper().createObjectNode().put(STATE, nodeState)).build();
188 }
189
190 /**
191 * Initializes kubernetes node.
192 *
193 * @param hostname hostname of kubernetes node
194 * @return 200 OK with init result, 404 not found, 500 server error
195 */
196 @GET
197 @Produces(MediaType.APPLICATION_JSON)
198 @Path("init/node/{hostname}")
199 public Response initNode(@PathParam("hostname") String hostname) {
200 log.trace(String.format(MESSAGE_NODE, QUERY));
201
202 K8sNode k8sNode = nodeAdminService.node(hostname);
203 if (k8sNode == null) {
204 log.error("Given node {} does not exist", hostname);
205 return Response.serverError().build();
206 }
207 K8sNode updated = k8sNode.updateState(K8sNodeState.INIT);
208 nodeAdminService.updateNode(updated);
209 return ok(mapper().createObjectNode()).build();
210 }
211
212 /**
213 * Initializes all kubernetes nodes.
214 *
215 * @return 200 OK with init result, 500 server error
216 */
217 @GET
218 @Produces(MediaType.APPLICATION_JSON)
219 @Path("init/all")
220 public Response initAllNodes() {
221 log.trace(String.format(MESSAGE_NODE, QUERY));
222
223 nodeAdminService.nodes()
224 .forEach(n -> {
225 K8sNode updated = n.updateState(K8sNodeState.INIT);
226 nodeAdminService.updateNode(updated);
227 });
228
229 return ok(mapper().createObjectNode()).build();
230 }
231
232 /**
233 * Initializes kubernetes nodes which are in the stats other than COMPLETE.
234 *
235 * @return 200 OK with init result, 500 server error
236 */
237 @GET
238 @Produces(MediaType.APPLICATION_JSON)
239 @Path("init/incomplete")
240 public Response initIncompleteNodes() {
241 log.trace(String.format(MESSAGE_NODE, QUERY));
242
243 nodeAdminService.nodes().stream()
244 .filter(n -> n.state() != K8sNodeState.COMPLETE)
245 .forEach(n -> {
246 K8sNode updated = n.updateState(K8sNodeState.INIT);
247 nodeAdminService.updateNode(updated);
248 });
249
250 return ok(mapper().createObjectNode()).build();
251 }
252
Jian Lid376e062020-01-02 23:57:13 +0900253 /**
254 * Updates a kubernetes nodes' state as post-on-board.
255 *
256 * @param hostname kubernetes node name
257 * @return 200 OK with the updated kubernetes node's config, 400 BAD_REQUEST
258 * if the JSON is malformed, and 304 NOT_MODIFIED without the updated config
259 */
260 @PUT
Jian Li83eb2a72020-01-03 02:09:03 +0900261 @Consumes(MediaType.APPLICATION_JSON)
Jian Lid376e062020-01-02 23:57:13 +0900262 @Produces(MediaType.APPLICATION_JSON)
263 @Path("update/postonboard/{hostname}")
264 public Response postOnBoardNode(@PathParam("hostname") String hostname) {
265 nodeAdminService.node(hostname).updateState(POST_ON_BOARD);
266 return Response.ok().build();
267 }
268
269 /**
270 * Indicates whether all kubernetes nodes are in post-on-board state.
271 *
272 * @return 200 OK with True, or 200 OK with False
273 */
274 @GET
275 @Produces(MediaType.APPLICATION_JSON)
276 @Path("get/postonboard/all")
277 public Response postOnBoardNodes() {
278 long numOfAllNodes = nodeAdminService.nodes().size();
279 long numOfReadyNodes = nodeAdminService.nodes().stream()
280 .filter(n -> n.state() == POST_ON_BOARD)
281 .count();
282 boolean result = numOfAllNodes == numOfReadyNodes;
283
284 return ok(mapper().createObjectNode().put(RESULT, result)).build();
285 }
286
Jian Li49109b52019-01-22 00:17:28 +0900287 private Set<K8sNode> readNodeConfiguration(InputStream input) {
288 Set<K8sNode> nodeSet = Sets.newHashSet();
289 try {
290 JsonNode jsonTree = readTreeFromStream(mapper().enable(INDENT_OUTPUT), input);
291 ArrayNode nodes = (ArrayNode) jsonTree.path(NODES);
292 nodes.forEach(node -> {
293 try {
294 ObjectNode objectNode = node.deepCopy();
295 K8sNode k8sNode =
296 codec(K8sNode.class).decode(objectNode, this);
297
298 nodeSet.add(k8sNode);
299 } catch (Exception e) {
300 log.error("Exception occurred due to {}", e);
301 throw new IllegalArgumentException();
302 }
303 });
304 } catch (Exception e) {
305 throw new IllegalArgumentException(e);
306 }
307
308 return nodeSet;
309 }
Jian Li3defa842019-02-12 00:31:35 +0900310
311 /**
312 * Creates a set of kubernetes API config from the JSON input stream.
313 *
314 * @param input kubernetes API configs JSON input stream
315 * @return 201 CREATED if the JSON is correct, 400 BAD_REQUEST if the JSON
316 * is malformed
317 * @onos.rsModel K8sApiConfig
318 */
319 @POST
320 @Path("api")
321 @Consumes(MediaType.APPLICATION_JSON)
322 @Produces(MediaType.APPLICATION_JSON)
323 public Response createApiConfigs(InputStream input) {
324 log.trace(String.format(MESSAGE_NODE, CREATE));
325
326 readApiConfigConfiguration(input).forEach(config -> {
327 K8sApiConfig existing = configAdminService.apiConfig(endpoint(config));
328 if (existing == null) {
329 configAdminService.createApiConfig(config);
330 }
331 });
332
333 UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
334 .path(API_CONFIGS);
335
336 return created(locationBuilder.build()).build();
337 }
338
339 /**
340 * Updates a set of kubernetes API config from the JSON input stream.
341 *
342 * @param input kubernetes API configs JSON input stream
343 * @return 200 OK with the updated kubernetes API config, 400 BAD_REQUEST
344 * if the JSON is malformed, and 304 NOT_MODIFIED without the updated config
345 * @onos.rsModel K8sApiConfig
346 */
347 @PUT
348 @Path("api")
349 @Consumes(MediaType.APPLICATION_JSON)
350 @Produces(MediaType.APPLICATION_JSON)
351 public Response updateApiConfigs(InputStream input) {
352 log.trace(String.format(MESSAGE_NODE, UPDATE));
353
354 Set<K8sApiConfig> configs = readApiConfigConfiguration(input);
355 for (K8sApiConfig config: configs) {
356 K8sApiConfig existing = configAdminService.apiConfig(endpoint(config));
357 if (existing == null) {
358 log.warn("There is no API configuration to update : {}", endpoint(config));
359 return Response.notModified().build();
360 } else if (!existing.equals(config)) {
361 configAdminService.updateApiConfig(config);
362 }
363 }
364
365 return Response.ok().build();
366 }
367
368 /**
369 * Removes a kubernetes API config.
370 *
371 * @param endpoint kubernetes API endpoint
372 * @return 204 NO_CONTENT, 400 BAD_REQUEST if the JSON is malformed
373 */
374 @DELETE
375 @Path("api/{endpoint : .+}")
376 @Consumes(MediaType.APPLICATION_JSON)
377 @Produces(MediaType.APPLICATION_JSON)
378 public Response deleteApiConfig(@PathParam("endpoint") String endpoint) {
379 log.trace(String.format(MESSAGE_NODE, REMOVE));
380
381 K8sApiConfig existing =
382 configAdminService.apiConfig(nullIsIllegal(endpoint, ENDPOINT + ERROR_MESSAGE));
383
384 if (existing == null) {
385 log.warn("There is no API configuration to delete : {}", endpoint);
386 return Response.notModified().build();
387 } else {
388 configAdminService.removeApiConfig(endpoint);
389 }
390
391 return Response.noContent().build();
392 }
393
394 private Set<K8sApiConfig> readApiConfigConfiguration(InputStream input) {
395 Set<K8sApiConfig> configSet = Sets.newHashSet();
396 try {
397 JsonNode jsonTree = readTreeFromStream(mapper().enable(INDENT_OUTPUT), input);
398 ArrayNode configs = (ArrayNode) jsonTree.path(API_CONFIGS);
399 configs.forEach(config -> {
400 try {
401 ObjectNode objectNode = config.deepCopy();
402 K8sApiConfig k8sApiConfig =
403 codec(K8sApiConfig.class).decode(objectNode, this);
404
405 configSet.add(k8sApiConfig);
406 } catch (Exception e) {
407 log.error("Exception occurred due to {}", e);
408 throw new IllegalArgumentException();
409 }
410 });
411 } catch (Exception e) {
412 throw new IllegalArgumentException(e);
413 }
414
415 return configSet;
416 }
Jian Li49109b52019-01-22 00:17:28 +0900417}