blob: 042e932fd862b2da7fb43e20a5776705111d226b [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) {
Jian Li9bb98412020-01-03 03:27:06 +0900265 K8sNode node = nodeAdminService.node(hostname);
266 if (node != null && node.state() != POST_ON_BOARD) {
267 K8sNode updated = node.updateState(POST_ON_BOARD);
268 nodeAdminService.updateNode(updated);
269 }
Jian Lid376e062020-01-02 23:57:13 +0900270 return Response.ok().build();
271 }
272
273 /**
274 * Indicates whether all kubernetes nodes are in post-on-board state.
275 *
276 * @return 200 OK with True, or 200 OK with False
277 */
278 @GET
279 @Produces(MediaType.APPLICATION_JSON)
280 @Path("get/postonboard/all")
281 public Response postOnBoardNodes() {
282 long numOfAllNodes = nodeAdminService.nodes().size();
283 long numOfReadyNodes = nodeAdminService.nodes().stream()
284 .filter(n -> n.state() == POST_ON_BOARD)
285 .count();
286 boolean result = numOfAllNodes == numOfReadyNodes;
287
288 return ok(mapper().createObjectNode().put(RESULT, result)).build();
289 }
290
Jian Li49109b52019-01-22 00:17:28 +0900291 private Set<K8sNode> readNodeConfiguration(InputStream input) {
292 Set<K8sNode> nodeSet = Sets.newHashSet();
293 try {
294 JsonNode jsonTree = readTreeFromStream(mapper().enable(INDENT_OUTPUT), input);
295 ArrayNode nodes = (ArrayNode) jsonTree.path(NODES);
296 nodes.forEach(node -> {
297 try {
298 ObjectNode objectNode = node.deepCopy();
299 K8sNode k8sNode =
300 codec(K8sNode.class).decode(objectNode, this);
301
302 nodeSet.add(k8sNode);
303 } catch (Exception e) {
304 log.error("Exception occurred due to {}", e);
305 throw new IllegalArgumentException();
306 }
307 });
308 } catch (Exception e) {
309 throw new IllegalArgumentException(e);
310 }
311
312 return nodeSet;
313 }
Jian Li3defa842019-02-12 00:31:35 +0900314
315 /**
316 * Creates a set of kubernetes API config from the JSON input stream.
317 *
318 * @param input kubernetes API configs JSON input stream
319 * @return 201 CREATED if the JSON is correct, 400 BAD_REQUEST if the JSON
320 * is malformed
321 * @onos.rsModel K8sApiConfig
322 */
323 @POST
324 @Path("api")
325 @Consumes(MediaType.APPLICATION_JSON)
326 @Produces(MediaType.APPLICATION_JSON)
327 public Response createApiConfigs(InputStream input) {
328 log.trace(String.format(MESSAGE_NODE, CREATE));
329
330 readApiConfigConfiguration(input).forEach(config -> {
331 K8sApiConfig existing = configAdminService.apiConfig(endpoint(config));
332 if (existing == null) {
333 configAdminService.createApiConfig(config);
334 }
335 });
336
337 UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
338 .path(API_CONFIGS);
339
340 return created(locationBuilder.build()).build();
341 }
342
343 /**
344 * Updates a set of kubernetes API config from the JSON input stream.
345 *
346 * @param input kubernetes API configs JSON input stream
347 * @return 200 OK with the updated kubernetes API config, 400 BAD_REQUEST
348 * if the JSON is malformed, and 304 NOT_MODIFIED without the updated config
349 * @onos.rsModel K8sApiConfig
350 */
351 @PUT
352 @Path("api")
353 @Consumes(MediaType.APPLICATION_JSON)
354 @Produces(MediaType.APPLICATION_JSON)
355 public Response updateApiConfigs(InputStream input) {
356 log.trace(String.format(MESSAGE_NODE, UPDATE));
357
358 Set<K8sApiConfig> configs = readApiConfigConfiguration(input);
359 for (K8sApiConfig config: configs) {
360 K8sApiConfig existing = configAdminService.apiConfig(endpoint(config));
361 if (existing == null) {
362 log.warn("There is no API configuration to update : {}", endpoint(config));
363 return Response.notModified().build();
364 } else if (!existing.equals(config)) {
365 configAdminService.updateApiConfig(config);
366 }
367 }
368
369 return Response.ok().build();
370 }
371
372 /**
373 * Removes a kubernetes API config.
374 *
375 * @param endpoint kubernetes API endpoint
376 * @return 204 NO_CONTENT, 400 BAD_REQUEST if the JSON is malformed
377 */
378 @DELETE
379 @Path("api/{endpoint : .+}")
380 @Consumes(MediaType.APPLICATION_JSON)
381 @Produces(MediaType.APPLICATION_JSON)
382 public Response deleteApiConfig(@PathParam("endpoint") String endpoint) {
383 log.trace(String.format(MESSAGE_NODE, REMOVE));
384
385 K8sApiConfig existing =
386 configAdminService.apiConfig(nullIsIllegal(endpoint, ENDPOINT + ERROR_MESSAGE));
387
388 if (existing == null) {
389 log.warn("There is no API configuration to delete : {}", endpoint);
390 return Response.notModified().build();
391 } else {
392 configAdminService.removeApiConfig(endpoint);
393 }
394
395 return Response.noContent().build();
396 }
397
398 private Set<K8sApiConfig> readApiConfigConfiguration(InputStream input) {
399 Set<K8sApiConfig> configSet = Sets.newHashSet();
400 try {
401 JsonNode jsonTree = readTreeFromStream(mapper().enable(INDENT_OUTPUT), input);
402 ArrayNode configs = (ArrayNode) jsonTree.path(API_CONFIGS);
403 configs.forEach(config -> {
404 try {
405 ObjectNode objectNode = config.deepCopy();
406 K8sApiConfig k8sApiConfig =
407 codec(K8sApiConfig.class).decode(objectNode, this);
408
409 configSet.add(k8sApiConfig);
410 } catch (Exception e) {
411 log.error("Exception occurred due to {}", e);
412 throw new IllegalArgumentException();
413 }
414 });
415 } catch (Exception e) {
416 throw new IllegalArgumentException(e);
417 }
418
419 return configSet;
420 }
Jian Li49109b52019-01-22 00:17:28 +0900421}