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