blob: 3b1048fc4f2702d1bf6134891760a2bb08774b4f [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 Li3defa842019-02-12 00:31:35 +090051import static org.onosproject.k8snode.util.K8sNodeUtil.endpoint;
Jian Li49109b52019-01-22 00:17:28 +090052
53/**
54 * Handles REST API call of kubernetes node config.
55 */
56
57@Path("configure")
58public class K8sNodeWebResource extends AbstractWebResource {
59
60 private final Logger log = LoggerFactory.getLogger(getClass());
61
62 private static final String MESSAGE_NODE = "Received node %s request";
63 private static final String NODES = "nodes";
Jian Li3defa842019-02-12 00:31:35 +090064 private static final String API_CONFIGS = "apiConfigs";
Jian Li49109b52019-01-22 00:17:28 +090065 private static final String CREATE = "CREATE";
66 private static final String UPDATE = "UPDATE";
67 private static final String NODE_ID = "NODE_ID";
Jian Li3defa842019-02-12 00:31:35 +090068 private static final String REMOVE = "REMOVE";
Jian Li1e03e712019-06-20 18:13:48 +090069 private static final String QUERY = "QUERY";
70 private static final String INIT = "INIT";
71 private static final String NOT_EXIST = "Not exist";
72 private static final String STATE = "State";
Jian Li49109b52019-01-22 00:17:28 +090073
74 private static final String HOST_NAME = "hostname";
Jian Li3defa842019-02-12 00:31:35 +090075 private static final String ENDPOINT = "endpoint";
Jian Li49109b52019-01-22 00:17:28 +090076 private static final String ERROR_MESSAGE = " cannot be null";
77
Jian Li3defa842019-02-12 00:31:35 +090078 private final K8sNodeAdminService nodeAdminService = get(K8sNodeAdminService.class);
79 private final K8sApiConfigAdminService configAdminService = get(K8sApiConfigAdminService.class);
Jian Li49109b52019-01-22 00:17:28 +090080
81 @Context
82 private UriInfo uriInfo;
83
84 /**
85 * Creates a set of kubernetes nodes' config from the JSON input stream.
86 *
87 * @param input kubernetes nodes JSON input stream
88 * @return 201 CREATED if the JSON is correct, 400 BAD_REQUEST if the JSON
89 * is malformed
90 * @onos.rsModel K8sNode
91 */
92 @POST
Jian Li3defa842019-02-12 00:31:35 +090093 @Path("node")
Jian Li49109b52019-01-22 00:17:28 +090094 @Consumes(MediaType.APPLICATION_JSON)
95 @Produces(MediaType.APPLICATION_JSON)
96 public Response createNodes(InputStream input) {
97 log.trace(String.format(MESSAGE_NODE, CREATE));
98
99 readNodeConfiguration(input).forEach(node -> {
Jian Li3defa842019-02-12 00:31:35 +0900100 K8sNode existing = nodeAdminService.node(node.hostname());
Jian Li49109b52019-01-22 00:17:28 +0900101 if (existing == null) {
Jian Li3defa842019-02-12 00:31:35 +0900102 nodeAdminService.createNode(node);
Jian Li49109b52019-01-22 00:17:28 +0900103 }
104 });
105
106 UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
107 .path(NODES)
108 .path(NODE_ID);
109
110 return created(locationBuilder.build()).build();
111 }
112
113 /**
114 * Updates a set of kubernetes nodes' config from the JSON input stream.
115 *
116 * @param input kubernetes nodes JSON input stream
117 * @return 200 OK with the updated kubernetes node's config, 400 BAD_REQUEST
118 * if the JSON is malformed, and 304 NOT_MODIFIED without the updated config
119 * @onos.rsModel K8sNode
120 */
121 @PUT
Jian Li3defa842019-02-12 00:31:35 +0900122 @Path("node")
Jian Li49109b52019-01-22 00:17:28 +0900123 @Consumes(MediaType.APPLICATION_JSON)
124 @Produces(MediaType.APPLICATION_JSON)
125 public Response updateNodes(InputStream input) {
126 log.trace(String.format(MESSAGE_NODE, UPDATE));
127
128 Set<K8sNode> nodes = readNodeConfiguration(input);
129 for (K8sNode node: nodes) {
Jian Li3defa842019-02-12 00:31:35 +0900130 K8sNode existing = nodeAdminService.node(node.hostname());
Jian Li49109b52019-01-22 00:17:28 +0900131 if (existing == null) {
132 log.warn("There is no node configuration to update : {}", node.hostname());
133 return Response.notModified().build();
134 } else if (!existing.equals(node)) {
Jian Li3defa842019-02-12 00:31:35 +0900135 nodeAdminService.updateNode(node);
Jian Li49109b52019-01-22 00:17:28 +0900136 }
137 }
138
139 return Response.ok().build();
140 }
141
142 /**
143 * Removes a set of kubernetes nodes' config from the JSON input stream.
144 *
145 * @param hostname host name contained in kubernetes nodes configuration
146 * @return 204 NO_CONTENT, 400 BAD_REQUEST if the JSON is malformed, and
147 * 304 NOT_MODIFIED without the updated config
Jian Li49109b52019-01-22 00:17:28 +0900148 */
Jian Li3defa842019-02-12 00:31:35 +0900149 @DELETE
150 @Path("node/{hostname}")
Jian Li49109b52019-01-22 00:17:28 +0900151 @Consumes(MediaType.APPLICATION_JSON)
152 @Produces(MediaType.APPLICATION_JSON)
Jian Li49109b52019-01-22 00:17:28 +0900153 public Response deleteNodes(@PathParam("hostname") String hostname) {
Jian Li3defa842019-02-12 00:31:35 +0900154 log.trace(String.format(MESSAGE_NODE, REMOVE));
Jian Li49109b52019-01-22 00:17:28 +0900155
156 K8sNode existing =
Jian Li3defa842019-02-12 00:31:35 +0900157 nodeAdminService.node(nullIsIllegal(hostname, HOST_NAME + ERROR_MESSAGE));
Jian Li49109b52019-01-22 00:17:28 +0900158
159 if (existing == null) {
160 log.warn("There is no node configuration to delete : {}", hostname);
161 return Response.notModified().build();
162 } else {
Jian Li3defa842019-02-12 00:31:35 +0900163 nodeAdminService.removeNode(hostname);
Jian Li49109b52019-01-22 00:17:28 +0900164 }
165
166 return Response.noContent().build();
167 }
168
Jian Li1e03e712019-06-20 18:13:48 +0900169
170 /**
171 * Obtains the state of the kubernetes node.
172 *
173 * @param hostname hostname of the kubernetes
174 * @return the state of the kubernetes node in Json
175 */
176 @GET
177 @Produces(MediaType.APPLICATION_JSON)
178 @Path("state/{hostname}")
179 public Response stateOfNode(@PathParam("hostname") String hostname) {
180 log.trace(String.format(MESSAGE_NODE, QUERY));
181
182 K8sNode k8sNode = nodeAdminService.node(hostname);
183 String nodeState = k8sNode != null ? k8sNode.state().toString() : NOT_EXIST;
184
185 return ok(mapper().createObjectNode().put(STATE, nodeState)).build();
186 }
187
188 /**
189 * Initializes kubernetes node.
190 *
191 * @param hostname hostname of kubernetes node
192 * @return 200 OK with init result, 404 not found, 500 server error
193 */
194 @GET
195 @Produces(MediaType.APPLICATION_JSON)
196 @Path("init/node/{hostname}")
197 public Response initNode(@PathParam("hostname") String hostname) {
198 log.trace(String.format(MESSAGE_NODE, QUERY));
199
200 K8sNode k8sNode = nodeAdminService.node(hostname);
201 if (k8sNode == null) {
202 log.error("Given node {} does not exist", hostname);
203 return Response.serverError().build();
204 }
205 K8sNode updated = k8sNode.updateState(K8sNodeState.INIT);
206 nodeAdminService.updateNode(updated);
207 return ok(mapper().createObjectNode()).build();
208 }
209
210 /**
211 * Initializes all kubernetes nodes.
212 *
213 * @return 200 OK with init result, 500 server error
214 */
215 @GET
216 @Produces(MediaType.APPLICATION_JSON)
217 @Path("init/all")
218 public Response initAllNodes() {
219 log.trace(String.format(MESSAGE_NODE, QUERY));
220
221 nodeAdminService.nodes()
222 .forEach(n -> {
223 K8sNode updated = n.updateState(K8sNodeState.INIT);
224 nodeAdminService.updateNode(updated);
225 });
226
227 return ok(mapper().createObjectNode()).build();
228 }
229
230 /**
231 * Initializes kubernetes nodes which are in the stats other than COMPLETE.
232 *
233 * @return 200 OK with init result, 500 server error
234 */
235 @GET
236 @Produces(MediaType.APPLICATION_JSON)
237 @Path("init/incomplete")
238 public Response initIncompleteNodes() {
239 log.trace(String.format(MESSAGE_NODE, QUERY));
240
241 nodeAdminService.nodes().stream()
242 .filter(n -> n.state() != K8sNodeState.COMPLETE)
243 .forEach(n -> {
244 K8sNode updated = n.updateState(K8sNodeState.INIT);
245 nodeAdminService.updateNode(updated);
246 });
247
248 return ok(mapper().createObjectNode()).build();
249 }
250
Jian Li49109b52019-01-22 00:17:28 +0900251 private Set<K8sNode> readNodeConfiguration(InputStream input) {
252 Set<K8sNode> nodeSet = Sets.newHashSet();
253 try {
254 JsonNode jsonTree = readTreeFromStream(mapper().enable(INDENT_OUTPUT), input);
255 ArrayNode nodes = (ArrayNode) jsonTree.path(NODES);
256 nodes.forEach(node -> {
257 try {
258 ObjectNode objectNode = node.deepCopy();
259 K8sNode k8sNode =
260 codec(K8sNode.class).decode(objectNode, this);
261
262 nodeSet.add(k8sNode);
263 } catch (Exception e) {
264 log.error("Exception occurred due to {}", e);
265 throw new IllegalArgumentException();
266 }
267 });
268 } catch (Exception e) {
269 throw new IllegalArgumentException(e);
270 }
271
272 return nodeSet;
273 }
Jian Li3defa842019-02-12 00:31:35 +0900274
275 /**
276 * Creates a set of kubernetes API config from the JSON input stream.
277 *
278 * @param input kubernetes API configs JSON input stream
279 * @return 201 CREATED if the JSON is correct, 400 BAD_REQUEST if the JSON
280 * is malformed
281 * @onos.rsModel K8sApiConfig
282 */
283 @POST
284 @Path("api")
285 @Consumes(MediaType.APPLICATION_JSON)
286 @Produces(MediaType.APPLICATION_JSON)
287 public Response createApiConfigs(InputStream input) {
288 log.trace(String.format(MESSAGE_NODE, CREATE));
289
290 readApiConfigConfiguration(input).forEach(config -> {
291 K8sApiConfig existing = configAdminService.apiConfig(endpoint(config));
292 if (existing == null) {
293 configAdminService.createApiConfig(config);
294 }
295 });
296
297 UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
298 .path(API_CONFIGS);
299
300 return created(locationBuilder.build()).build();
301 }
302
303 /**
304 * Updates a set of kubernetes API config from the JSON input stream.
305 *
306 * @param input kubernetes API configs JSON input stream
307 * @return 200 OK with the updated kubernetes API config, 400 BAD_REQUEST
308 * if the JSON is malformed, and 304 NOT_MODIFIED without the updated config
309 * @onos.rsModel K8sApiConfig
310 */
311 @PUT
312 @Path("api")
313 @Consumes(MediaType.APPLICATION_JSON)
314 @Produces(MediaType.APPLICATION_JSON)
315 public Response updateApiConfigs(InputStream input) {
316 log.trace(String.format(MESSAGE_NODE, UPDATE));
317
318 Set<K8sApiConfig> configs = readApiConfigConfiguration(input);
319 for (K8sApiConfig config: configs) {
320 K8sApiConfig existing = configAdminService.apiConfig(endpoint(config));
321 if (existing == null) {
322 log.warn("There is no API configuration to update : {}", endpoint(config));
323 return Response.notModified().build();
324 } else if (!existing.equals(config)) {
325 configAdminService.updateApiConfig(config);
326 }
327 }
328
329 return Response.ok().build();
330 }
331
332 /**
333 * Removes a kubernetes API config.
334 *
335 * @param endpoint kubernetes API endpoint
336 * @return 204 NO_CONTENT, 400 BAD_REQUEST if the JSON is malformed
337 */
338 @DELETE
339 @Path("api/{endpoint : .+}")
340 @Consumes(MediaType.APPLICATION_JSON)
341 @Produces(MediaType.APPLICATION_JSON)
342 public Response deleteApiConfig(@PathParam("endpoint") String endpoint) {
343 log.trace(String.format(MESSAGE_NODE, REMOVE));
344
345 K8sApiConfig existing =
346 configAdminService.apiConfig(nullIsIllegal(endpoint, ENDPOINT + ERROR_MESSAGE));
347
348 if (existing == null) {
349 log.warn("There is no API configuration to delete : {}", endpoint);
350 return Response.notModified().build();
351 } else {
352 configAdminService.removeApiConfig(endpoint);
353 }
354
355 return Response.noContent().build();
356 }
357
358 private Set<K8sApiConfig> readApiConfigConfiguration(InputStream input) {
359 Set<K8sApiConfig> configSet = Sets.newHashSet();
360 try {
361 JsonNode jsonTree = readTreeFromStream(mapper().enable(INDENT_OUTPUT), input);
362 ArrayNode configs = (ArrayNode) jsonTree.path(API_CONFIGS);
363 configs.forEach(config -> {
364 try {
365 ObjectNode objectNode = config.deepCopy();
366 K8sApiConfig k8sApiConfig =
367 codec(K8sApiConfig.class).decode(objectNode, this);
368
369 configSet.add(k8sApiConfig);
370 } catch (Exception e) {
371 log.error("Exception occurred due to {}", e);
372 throw new IllegalArgumentException();
373 }
374 });
375 } catch (Exception e) {
376 throw new IllegalArgumentException(e);
377 }
378
379 return configSet;
380 }
Jian Li49109b52019-01-22 00:17:28 +0900381}