/* * Copyright 2015-present Open Networking Laboratory * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.onosproject.rest.resources; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import org.onosproject.net.config.Config; import org.onosproject.net.config.NetworkConfigService; import org.onosproject.net.config.SubjectFactory; import org.onosproject.rest.AbstractWebResource; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Set; import static org.onlab.util.Tools.emptyIsNotFound; import static org.onlab.util.Tools.nullIsNotFound; /** * Manage network configurations. */ @Path("network/configuration") public class NetworkConfigWebResource extends AbstractWebResource { //FIX ME not found Multi status error code 207 in jaxrs Response Status. private static final int MULTI_STATUS_RESPONE = 207; private String subjectClassNotFoundErrorString(String subjectClassKey) { return "Config for '" + subjectClassKey + "' not found"; } private String subjectNotFoundErrorString(String subjectClassKey, String subjectKey) { return "Config for '" + subjectClassKey + "/" + subjectKey + "' not found"; } private String configKeyNotFoundErrorString(String subjectClassKey, String subjectKey, String configKey) { return "Config for '" + subjectClassKey + "/" + subjectKey + "/" + configKey + "' not found"; } /** * Gets entire network configuration base. * * @return 200 OK with network configuration JSON */ @GET @Produces(MediaType.APPLICATION_JSON) @SuppressWarnings("unchecked") public Response download() { NetworkConfigService service = get(NetworkConfigService.class); ObjectNode root = mapper().createObjectNode(); service.getSubjectClasses().forEach(sc -> { SubjectFactory subjectFactory = service.getSubjectFactory(sc); produceJson(service, newObject(root, subjectFactory.subjectClassKey()), subjectFactory, sc); }); return ok(root).build(); } /** * Gets all network configuration for a subject class. * * @param subjectClassKey subject class key * @return 200 OK with network configuration JSON */ @GET @Path("{subjectClassKey}") @Produces(MediaType.APPLICATION_JSON) @SuppressWarnings("unchecked") public Response download(@PathParam("subjectClassKey") String subjectClassKey) { NetworkConfigService service = get(NetworkConfigService.class); ObjectNode root = mapper().createObjectNode(); SubjectFactory subjectFactory = nullIsNotFound(service.getSubjectFactory(subjectClassKey), subjectClassNotFoundErrorString(subjectClassKey)); produceJson(service, root, subjectFactory, subjectFactory.subjectClass()); return ok(root).build(); } /** * Gets all network configuration for a subjectKey. * * @param subjectClassKey subjectKey class key * @param subjectKey subjectKey key * @return 200 OK with network configuration JSON */ @GET @Path("{subjectClassKey}/{subjectKey}") @Produces(MediaType.APPLICATION_JSON) @SuppressWarnings("unchecked") public Response download(@PathParam("subjectClassKey") String subjectClassKey, @PathParam("subjectKey") String subjectKey) { NetworkConfigService service = get(NetworkConfigService.class); ObjectNode root = mapper().createObjectNode(); SubjectFactory subjectFactory = nullIsNotFound(service.getSubjectFactory(subjectClassKey), subjectClassNotFoundErrorString(subjectClassKey)); produceSubjectJson(service, root, subjectFactory.createSubject(subjectKey), true, subjectNotFoundErrorString(subjectClassKey, subjectKey)); return ok(root).build(); } /** * Gets specific network configuration for a subjectKey. * * @param subjectClassKey subjectKey class key * @param subjectKey subjectKey key * @param configKey configuration class key * @return 200 OK with network configuration JSON */ @GET @Path("{subjectClassKey}/{subjectKey}/{configKey}") @Produces(MediaType.APPLICATION_JSON) @SuppressWarnings("unchecked") public Response download(@PathParam("subjectClassKey") String subjectClassKey, @PathParam("subjectKey") String subjectKey, @PathParam("configKey") String configKey) { NetworkConfigService service = get(NetworkConfigService.class); Object subject = nullIsNotFound(service.getSubjectFactory(subjectClassKey) .createSubject(subjectKey), subjectNotFoundErrorString(subjectClassKey, subjectKey)); Class configClass = nullIsNotFound(service.getConfigClass(subjectClassKey, configKey), configKeyNotFoundErrorString(subjectClassKey, subjectKey, configKey)); Config config = nullIsNotFound((Config) service.getConfig(subject, configClass), configKeyNotFoundErrorString(subjectClassKey, subjectKey, configKey)); return ok(config.node()).build(); } @SuppressWarnings("unchecked") private void produceJson(NetworkConfigService service, ObjectNode node, SubjectFactory subjectFactory, Class subjectClass) { service.getSubjects(subjectClass).forEach(s -> produceSubjectJson(service, newObject(node, subjectFactory.subjectKey(s)), s, false, "")); } private void produceSubjectJson(NetworkConfigService service, ObjectNode node, Object subject, boolean emptyIsError, String emptyErrorMessage) { Set<? extends Config<Object>> configs = service.getConfigs(subject); if (emptyIsError) { // caller wants an empty set to be a 404 configs = emptyIsNotFound(configs, emptyErrorMessage); } configs.forEach(c -> node.set(c.key(), c.node())); } /** * Uploads bulk network configuration. * * @param request network configuration JSON rooted at the top node * @return 200 OK * @throws IOException if unable to parse the request */ @POST @Consumes(MediaType.APPLICATION_JSON) @SuppressWarnings("unchecked") public Response upload(InputStream request) throws IOException { NetworkConfigService service = get(NetworkConfigService.class); ObjectNode root = (ObjectNode) mapper().readTree(request); List<String> errorMsgs = new ArrayList<String>(); root.fieldNames() .forEachRemaining(sk -> { errorMsgs.addAll(consumeJson(service, (ObjectNode) root.path(sk), service.getSubjectFactory(sk))); }); if (!errorMsgs.isEmpty()) { return Response.status(MULTI_STATUS_RESPONE).entity(produceErrorJson(errorMsgs)).build(); } return Response.ok().build(); } /** * Upload multiple network configurations for a subject class. * * @param subjectClassKey subject class key * @param request network configuration JSON rooted at the top node * @return 200 OK * @throws IOException if unable to parse the request */ @POST @Path("{subjectClassKey}") @Consumes(MediaType.APPLICATION_JSON) @SuppressWarnings("unchecked") public Response upload(@PathParam("subjectClassKey") String subjectClassKey, InputStream request) throws IOException { NetworkConfigService service = get(NetworkConfigService.class); ObjectNode root = (ObjectNode) mapper().readTree(request); List<String> errorMsgs = consumeJson(service, root, service.getSubjectFactory(subjectClassKey)); if (!errorMsgs.isEmpty()) { return Response.status(MULTI_STATUS_RESPONE).entity(produceErrorJson(errorMsgs)).build(); } return Response.ok().build(); } /** * Upload mutliple network configurations for a subjectKey. * * @param subjectClassKey subjectKey class key * @param subjectKey subjectKey key * @param request network configuration JSON rooted at the top node * @return 200 OK * @throws IOException if unable to parse the request */ @POST @Path("{subjectClassKey}/{subjectKey}") @Consumes(MediaType.APPLICATION_JSON) @SuppressWarnings("unchecked") public Response upload(@PathParam("subjectClassKey") String subjectClassKey, @PathParam("subjectKey") String subjectKey, InputStream request) throws IOException { NetworkConfigService service = get(NetworkConfigService.class); ObjectNode root = (ObjectNode) mapper().readTree(request); List<String> errorMsgs = consumeSubjectJson(service, root, service.getSubjectFactory(subjectClassKey).createSubject(subjectKey), subjectClassKey); if (!errorMsgs.isEmpty()) { return Response.status(MULTI_STATUS_RESPONE).entity(produceErrorJson(errorMsgs)).build(); } return Response.ok().build(); } /** * Upload specific network configuration for a subjectKey. * * @param subjectClassKey subjectKey class key * @param subjectKey subjectKey key * @param configKey configuration class key * @param request network configuration JSON rooted at the top node * @return 200 OK * @throws IOException if unable to parse the request */ @POST @Path("{subjectClassKey}/{subjectKey}/{configKey}") @Consumes(MediaType.APPLICATION_JSON) @SuppressWarnings("unchecked") public Response upload(@PathParam("subjectClassKey") String subjectClassKey, @PathParam("subjectKey") String subjectKey, @PathParam("configKey") String configKey, InputStream request) throws IOException { NetworkConfigService service = get(NetworkConfigService.class); JsonNode root = mapper().readTree(request); service.applyConfig(subjectClassKey, service.getSubjectFactory(subjectClassKey).createSubject(subjectKey), configKey, root); return Response.ok().build(); } private List<String> consumeJson(NetworkConfigService service, ObjectNode classNode, SubjectFactory subjectFactory) { List<String> errorMsgs = new ArrayList<String>(); classNode.fieldNames().forEachRemaining(s -> { List<String> error = consumeSubjectJson(service, (ObjectNode) classNode.path(s), subjectFactory.createSubject(s), subjectFactory.subjectClassKey()); errorMsgs.addAll(error); }); return errorMsgs; } private List<String> consumeSubjectJson(NetworkConfigService service, ObjectNode subjectNode, Object subject, String subjectClassKey) { List<String> errorMsgs = new ArrayList<String>(); subjectNode.fieldNames().forEachRemaining(configKey -> { try { service.applyConfig(subjectClassKey, subject, configKey, subjectNode.path(configKey)); } catch (IllegalArgumentException e) { errorMsgs.add("Error parsing config " + subjectClassKey + "/" + subject + "/" + configKey); } }); return errorMsgs; } private ObjectNode produceErrorJson(List<String> errorMsgs) { ObjectMapper mapper = new ObjectMapper(); ObjectNode result = mapper.createObjectNode().put("code", 207).putPOJO("message", errorMsgs); return result; } // FIXME: Refactor to allow queued configs to be removed /** * Clear entire network configuration base. * * @return 204 NO CONTENT */ @DELETE @SuppressWarnings("unchecked") public Response delete() { NetworkConfigService service = get(NetworkConfigService.class); service.removeConfig(); return Response.noContent().build(); } /** * Clear all network configurations for a subject class. * * @param subjectClassKey subject class key * @return 204 NO CONTENT */ @DELETE @Path("{subjectClassKey}") @SuppressWarnings("unchecked") public Response delete(@PathParam("subjectClassKey") String subjectClassKey) { NetworkConfigService service = get(NetworkConfigService.class); service.getSubjects(service.getSubjectFactory(subjectClassKey).subjectClass()) .forEach(subject -> service.removeConfig(subject)); return Response.noContent().build(); } /** * Clear all network configurations for a subjectKey. * * @param subjectClassKey subjectKey class key * @param subjectKey subjectKey key * @return 204 NO CONTENT */ @DELETE @Path("{subjectClassKey}/{subjectKey}") @SuppressWarnings("unchecked") public Response delete(@PathParam("subjectClassKey") String subjectClassKey, @PathParam("subjectKey") String subjectKey) { NetworkConfigService service = get(NetworkConfigService.class); service.removeConfig(service.getSubjectFactory(subjectClassKey).createSubject(subjectKey)); return Response.noContent().build(); } /** * Clear specific network configuration for a subjectKey. * * @param subjectClassKey subjectKey class key * @param subjectKey subjectKey key * @param configKey configuration class key * @return 204 NO CONTENT */ @DELETE @Path("{subjectClassKey}/{subjectKey}/{configKey}") @SuppressWarnings("unchecked") public Response delete(@PathParam("subjectClassKey") String subjectClassKey, @PathParam("subjectKey") String subjectKey, @PathParam("configKey") String configKey) { NetworkConfigService service = get(NetworkConfigService.class); service.removeConfig(subjectClassKey, service.getSubjectFactory(subjectClassKey).createSubject(subjectKey), configKey); return Response.noContent().build(); } }