From 6f2d2f769bd3661e6842a89573c0ff2a68da998d Mon Sep 17 00:00:00 2001 From: diaccio Date: Fri, 12 Nov 2021 17:28:03 +0100 Subject: [PATCH] inbandtelemetry REST This patch extends the in-band-telemetry application with a REST API to provide GET/POST/DELETE in-band domains (IntIntents) Change-Id: I50f50edb03d262c42b4daae1c1be440aca1a6605 --- apps/inbandtelemetry/api/BUILD | 8 +- .../inbandtelemetry/api/package-info.java | 7 +- .../inbandtelemetry/rest/IntIntentCodec.java | 84 +++++++++++ .../rest/IntWebApplication.java | 31 ++++ .../inbandtelemetry/rest/IntWebResource.java | 142 ++++++++++++++++++ .../inbandtelemetry/rest/package-info.java | 20 +++ .../src/main/resources/definitions/Int.json | 129 ++++++++++++++++ .../api/src/main/webapp/WEB-INF/web.xml | 59 ++++++++ .../impl/SimpleIntManager.java | 8 +- .../impl/SimpleIntManagerTest.java | 29 +++- 10 files changed, 510 insertions(+), 7 deletions(-) create mode 100644 apps/inbandtelemetry/api/src/main/java/org/onosproject/inbandtelemetry/rest/IntIntentCodec.java create mode 100644 apps/inbandtelemetry/api/src/main/java/org/onosproject/inbandtelemetry/rest/IntWebApplication.java create mode 100644 apps/inbandtelemetry/api/src/main/java/org/onosproject/inbandtelemetry/rest/IntWebResource.java create mode 100644 apps/inbandtelemetry/api/src/main/java/org/onosproject/inbandtelemetry/rest/package-info.java create mode 100644 apps/inbandtelemetry/api/src/main/resources/definitions/Int.json create mode 100644 apps/inbandtelemetry/api/src/main/webapp/WEB-INF/web.xml diff --git a/apps/inbandtelemetry/api/BUILD b/apps/inbandtelemetry/api/BUILD index 93d94d391b..4c20983250 100644 --- a/apps/inbandtelemetry/api/BUILD +++ b/apps/inbandtelemetry/api/BUILD @@ -1,8 +1,14 @@ -COMPILE_DEPS = CORE_DEPS + KRYO + [ +COMPILE_DEPS = CORE_DEPS + JACKSON + REST + KRYO + [ + "@jersey_client//jar", "//core/store/serializers:onos-core-serializers", ] osgi_jar_with_tests( + api_description = "REST API for Int", + api_package = "org.onosproject.inbandtelemetry.rest", + api_title = "INT REST", + api_version = "1.0", test_deps = TEST_ADAPTERS, + web_context = "/onos/int", deps = COMPILE_DEPS, ) diff --git a/apps/inbandtelemetry/api/src/main/java/org/onosproject/inbandtelemetry/api/package-info.java b/apps/inbandtelemetry/api/src/main/java/org/onosproject/inbandtelemetry/api/package-info.java index 9e80ee5d89..69ff95f917 100644 --- a/apps/inbandtelemetry/api/src/main/java/org/onosproject/inbandtelemetry/api/package-info.java +++ b/apps/inbandtelemetry/api/src/main/java/org/onosproject/inbandtelemetry/api/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-present Open Networking Foundation + * Copyright 2021-present Open Networking Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,6 @@ */ /** - * Service to control a network of devices capable of collecting and exporting - * data plane telemetry via in-band mechanism. + * Implementation REST for in-band-telemetry service. */ -package org.onosproject.inbandtelemetry.api; +package org.onosproject.inbandtelemetry.api; \ No newline at end of file diff --git a/apps/inbandtelemetry/api/src/main/java/org/onosproject/inbandtelemetry/rest/IntIntentCodec.java b/apps/inbandtelemetry/api/src/main/java/org/onosproject/inbandtelemetry/rest/IntIntentCodec.java new file mode 100644 index 0000000000..e91e1801db --- /dev/null +++ b/apps/inbandtelemetry/api/src/main/java/org/onosproject/inbandtelemetry/rest/IntIntentCodec.java @@ -0,0 +1,84 @@ +/* + * Copyright 2021-present Open Networking Foundation + * + * 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.inbandtelemetry.rest; + + +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import org.onosproject.inbandtelemetry.api.IntIntent; +import org.onosproject.net.behaviour.inbandtelemetry.IntMetadataType; +import org.onosproject.net.flow.TrafficSelector; + + +import static com.google.common.base.Preconditions.checkNotNull; + + +/** + * IntIntent JSON codec. + */ +public final class IntIntentCodec extends JsonCodec { + + // JSON field names + private static final String METADATA_TYPES = "metadataTypes"; + private static final String HEADER_TYPE = "headerType"; + private static final String TELEMETRY_MODE = "telemetryMode"; + private static final String REPORT_TYPES = "reportTypes"; + private static final String SELECTOR = "selector"; + + + @Override + public ObjectNode encode(IntIntent intIntent, CodecContext context) { + checkNotNull(intIntent, "intIntent cannot be null"); + ObjectNode result = context.mapper().createObjectNode(); + ArrayNode metadataTypes = context.mapper().createArrayNode(); + ArrayNode reportTypes = context.mapper().createArrayNode(); + intIntent.reportTypes().forEach(reportType -> { + reportTypes.add(reportType.toString()); + }); + intIntent.metadataTypes().forEach(intMetadataType -> { + metadataTypes.add(intMetadataType.toString()); + }); + result.put(HEADER_TYPE, intIntent.headerType().toString()) + .put(TELEMETRY_MODE, intIntent.telemetryMode().toString()); + result.set(REPORT_TYPES, reportTypes); + result.set(METADATA_TYPES, metadataTypes); + result.set(SELECTOR, context.codec(TrafficSelector.class).encode(intIntent.selector(), context)); + return result; + } + @Override + public IntIntent decode(ObjectNode json, CodecContext context) { + if (json == null || !json.isObject()) { + return null; + } + IntIntent.Builder resultBuilder = new IntIntent.Builder(); + resultBuilder.withHeaderType(IntIntent.IntHeaderType.valueOf(json.findValue(HEADER_TYPE).asText())); + resultBuilder.withTelemetryMode(IntIntent.TelemetryMode.valueOf(json.findValue(TELEMETRY_MODE).asText())); + + JsonCodec selectorCodec = context.codec(TrafficSelector.class); + resultBuilder.withSelector(selectorCodec.decode((ObjectNode) json.findValue(SELECTOR), context)); + json.findValue(METADATA_TYPES).forEach(metaNode -> { + resultBuilder.withMetadataType(IntMetadataType.valueOf(metaNode.asText())); + }); + json.findValues(REPORT_TYPES).forEach(jsonNode -> { + resultBuilder.withReportType(IntIntent.IntReportType.valueOf(jsonNode.asText())); + }); + return resultBuilder.build(); + } + +} + diff --git a/apps/inbandtelemetry/api/src/main/java/org/onosproject/inbandtelemetry/rest/IntWebApplication.java b/apps/inbandtelemetry/api/src/main/java/org/onosproject/inbandtelemetry/rest/IntWebApplication.java new file mode 100644 index 0000000000..e74388d357 --- /dev/null +++ b/apps/inbandtelemetry/api/src/main/java/org/onosproject/inbandtelemetry/rest/IntWebApplication.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021-present Open Networking Foundation + * + * 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.inbandtelemetry.rest; + +import org.onlab.rest.AbstractWebApplication; + +import java.util.Set; + +/** + * Inbandtelemetry web application. + */ +public class IntWebApplication extends AbstractWebApplication { + @Override + public Set> getClasses() { + return getClasses(IntWebResource.class); + } +} diff --git a/apps/inbandtelemetry/api/src/main/java/org/onosproject/inbandtelemetry/rest/IntWebResource.java b/apps/inbandtelemetry/api/src/main/java/org/onosproject/inbandtelemetry/rest/IntWebResource.java new file mode 100644 index 0000000000..4f5cd1c76d --- /dev/null +++ b/apps/inbandtelemetry/api/src/main/java/org/onosproject/inbandtelemetry/rest/IntWebResource.java @@ -0,0 +1,142 @@ +/* + * Copyright 2021-present Open Networking Foundation + * + * 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.inbandtelemetry.rest; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import org.onosproject.inbandtelemetry.api.IntIntent; +import org.onosproject.inbandtelemetry.api.IntIntentId; +import org.onosproject.inbandtelemetry.api.IntService; +import org.onosproject.rest.AbstractWebResource; + + +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.GET; +import javax.ws.rs.DELETE; +import javax.ws.rs.POST; +import javax.ws.rs.core.Context; +import javax.ws.rs.Produces; +import javax.ws.rs.Consumes; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; +import javax.ws.rs.core.UriBuilder; +import javax.ws.rs.core.MediaType; +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +import static org.onlab.util.Tools.nullIsNotFound; +import static org.onlab.util.Tools.readTreeFromStream; + + +/** + * Query and program intIntents. + */ +@Path("intIntent") +public class IntWebResource extends AbstractWebResource { + @Context + private UriInfo uriInfo; + + private static final String INTS = "IntIntents"; + private static final String INT = "IntIntent"; + static final String ID = "id"; + private static final String INT_NOT_FOUND = "IntIntent is not found for "; + + private final ObjectNode root = mapper().createObjectNode(); + + /** + * Gets all IntIntents. Returns array of all IntIntents in the system. + * @return 200 OK with a collection of IntIntents + */ + @GET + @Produces(MediaType.APPLICATION_JSON) + public Response getIntents() { + ArrayNode intsNode = root.putArray(INTS); + IntService service = get(IntService.class); + Map intIntents = service.getIntIntents(); + if (!intIntents.isEmpty()) { + intIntents.entrySet().forEach(intIntentEntry -> { + intsNode.add(codec(IntIntent.class).encode(intIntentEntry.getValue(), this) + .put(ID, intIntentEntry.getKey().id())); + } + ); + } + return ok(root).build(); + } + + /** + * Get an IntIntent. Returns an IntIntent in the system. + * @param id IntIntentId + * @return 200 OK with a IntIntent + */ + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("{id}") + public Response getIntent(@PathParam("id") String id) { + ArrayNode intsNode = root.putArray(INT); + IntService service = get(IntService.class); + final IntIntent intIntent = nullIsNotFound(service.getIntIntent(IntIntentId.valueOf(Long.parseLong(id))), + INT_NOT_FOUND + id); + intsNode.add(codec(IntIntent.class).encode(intIntent, this)); + return ok(root).build(); + } + + /** + * Creates new IntIntent. Creates and installs a new IntIntent. + * + * @param stream IntIntent JSON + * @return status of the request - CREATED if the JSON is correct, + * BAD_REQUEST if the JSON is invalid + * @onos.rsModel Int + */ + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Response createIntent(InputStream stream) { + IntService service = get(IntService.class); + try { + ObjectNode jsonTree = readTreeFromStream(mapper(), stream); + IntIntent intIntent = codec(IntIntent.class).decode(jsonTree, this); + IntIntentId intIntentId = service.installIntIntent(intIntent); + UriBuilder locationBuilder = uriInfo.getBaseUriBuilder() + .path(INT) + .path(Long.toString(intIntentId.id())); + return Response + .created(locationBuilder.build()) + .build(); + + } catch (IOException e) { + throw new IllegalArgumentException(e.getMessage()); + } + + } + + /** + * Removes the specified IntIntent. + * + * @param id InIntentId + * @return 204 NO CONTENT + */ + @DELETE + @Path("{id}") + public Response deleteIntent(@PathParam("id") String id) { + IntService service = get(IntService.class); + service.removeIntIntent(IntIntentId.valueOf(Long.parseLong(id))); + return Response.noContent().build(); + } + +} diff --git a/apps/inbandtelemetry/api/src/main/java/org/onosproject/inbandtelemetry/rest/package-info.java b/apps/inbandtelemetry/api/src/main/java/org/onosproject/inbandtelemetry/rest/package-info.java new file mode 100644 index 0000000000..8bb00c6822 --- /dev/null +++ b/apps/inbandtelemetry/api/src/main/java/org/onosproject/inbandtelemetry/rest/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2021-present Open Networking Foundation + * + * 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. + */ + +/** + * Implementation REST for INT service. + */ +package org.onosproject.inbandtelemetry.rest; \ No newline at end of file diff --git a/apps/inbandtelemetry/api/src/main/resources/definitions/Int.json b/apps/inbandtelemetry/api/src/main/resources/definitions/Int.json new file mode 100644 index 0000000000..950d3cd125 --- /dev/null +++ b/apps/inbandtelemetry/api/src/main/resources/definitions/Int.json @@ -0,0 +1,129 @@ +{ + "type": "object", + "title": "INT Intents", + + "required": [ + "INT Intents" + ], + "properties": { + "intintent": { + "type": "array", + "xml": { + "name": "Ints", + "wrapped": true + }, + "items": { + "type": "object", + "title": "int", + "required": [ + "selector", + "metadataTypes", + "headerType", + "reportTypes", + "telemetryMode" + ], + "properties": { + "selector": { + "type": "object", + "title": "selector", + "required": [ + "criteria" + ], + "properties": { + "criteria": { + "type": "array", + "xml": { + "name": "criteria", + "wrapped": true + }, + "items": { + "type": "object", + "title": "criteria", + "properties": { + "type": { + "type": "string", + "description": "Ethernet field name", + "example": "ETH_TYPE" + }, + "ip": { + "type": "string", + "example": "10.1.1.0/24", + "description": "IP source address" + }, + "tcpPort": { + "type": "integer", + "format": "uint16", + "example": 1, + "description": "TCP source address" + }, + "udpPort": { + "type": "uint16", + "format": "uint16", + "example": 1, + "description": "UDP source address" + } + } + } + } + } + }, + "headerType": { + "type": "string", + "enum": [ + "HOP_BY_HOP", + "DESTINATION" + ], + "example": "HOP_BY_HOP" + }, + "telemetryMode": { + "type": "string", + "enum": [ + "POSTCARD", + "INBAND_TELEMETRY" + ], + "example": "INBAND_TELEMETRY" + }, + "metadataTypes": { + "type": "array", + "xml": { + "name": "metadataTypes", + "wrapped": true + }, + "items": { + "type": "string", + "enum": [ + "SWITCH_ID", + "L1_PORT_ID", + "HOP_LATENCY", + "QUEUE_OCCUPANCY", + "INGRESS_TIMESTAMP", + "EGRESS_TIMESTAMP", + "L2_PORT_ID", + "EGRESS_TX_UTIL" + ], + "example": "SWITCH_ID" + } + }, + "reportTypes": { + "type": "array", + "xml": { + "name": "reportTypes", + "wrapped": true + }, + "items": { + "type": "string", + "enum": [ + "TRACKED_FLOW", + "DROPPED_PACKET", + "CONGESTED_QUEUE" + ], + "example": "TRACKED_FLOW" + } + } + } + } + } + } +} + + diff --git a/apps/inbandtelemetry/api/src/main/webapp/WEB-INF/web.xml b/apps/inbandtelemetry/api/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..40cc7f0686 --- /dev/null +++ b/apps/inbandtelemetry/api/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,59 @@ + + + + INT REST API v1.0 + + + + Secured + /* + + + admin + viewer + + + + + admin + viewer + + + + BASIC + karaf + + + + JAX-RS Service + org.glassfish.jersey.servlet.ServletContainer + + javax.ws.rs.Application + org.onosproject.inbandtelemetry.rest.IntWebApplication + + 1 + + + + JAX-RS Service + /* + + diff --git a/apps/inbandtelemetry/impl/src/main/java/org/onosproject/inbandtelemetry/impl/SimpleIntManager.java b/apps/inbandtelemetry/impl/src/main/java/org/onosproject/inbandtelemetry/impl/SimpleIntManager.java index bf7dc184bc..6473fa1ee3 100644 --- a/apps/inbandtelemetry/impl/src/main/java/org/onosproject/inbandtelemetry/impl/SimpleIntManager.java +++ b/apps/inbandtelemetry/impl/src/main/java/org/onosproject/inbandtelemetry/impl/SimpleIntManager.java @@ -19,8 +19,10 @@ import com.google.common.collect.Maps; import com.google.common.util.concurrent.Striped; import org.onlab.util.KryoNamespace; import org.onlab.util.SharedScheduledExecutors; +import org.onosproject.codec.CodecService; import org.onosproject.core.ApplicationId; import org.onosproject.core.CoreService; +import org.onosproject.inbandtelemetry.rest.IntIntentCodec; import org.onosproject.net.behaviour.inbandtelemetry.IntReportConfig; import org.onosproject.net.behaviour.inbandtelemetry.IntMetadataType; import org.onosproject.net.behaviour.inbandtelemetry.IntDeviceConfig; @@ -125,6 +127,9 @@ public class SimpleIntManager implements IntService { @Reference(cardinality = ReferenceCardinality.MANDATORY) protected NetworkConfigRegistry netcfgRegistry; + @Reference(cardinality = ReferenceCardinality.MANDATORY) + protected CodecService codecService; + private final Striped deviceLocks = Striped.lock(10); private final ConcurrentMap> scheduledDeviceTasks = Maps.newConcurrentMap(); @@ -173,7 +178,7 @@ public class SimpleIntManager implements IntService { .register(IntIntent.TelemetryMode.class) .register(IntDeviceConfig.class) .register(IntDeviceConfig.TelemetrySpec.class); - + codecService.registerCodec(IntIntent.class, new IntIntentCodec()); devicesToConfigure = storageService.consistentMapBuilder() .withSerializer(Serializer.using(serializer.build())) .withName("onos-int-devices-to-configure") @@ -250,6 +255,7 @@ public class SimpleIntManager implements IntService { devicesToConfigure.removeListener(devicesToConfigureListener); devicesToConfigure.destroy(); devicesToConfigure = null; + codecService.unregisterCodec(IntIntent.class); // Cancel tasks (if any). scheduledDeviceTasks.values().forEach(f -> { f.cancel(true); diff --git a/apps/inbandtelemetry/impl/src/test/java/org/onosproject/inbandtelemetry/impl/SimpleIntManagerTest.java b/apps/inbandtelemetry/impl/src/test/java/org/onosproject/inbandtelemetry/impl/SimpleIntManagerTest.java index 7fddf3cbb4..a44a376647 100644 --- a/apps/inbandtelemetry/impl/src/test/java/org/onosproject/inbandtelemetry/impl/SimpleIntManagerTest.java +++ b/apps/inbandtelemetry/impl/src/test/java/org/onosproject/inbandtelemetry/impl/SimpleIntManagerTest.java @@ -34,6 +34,7 @@ import org.onlab.packet.IpPrefix; import org.onlab.packet.TpPort; import org.onlab.packet.VlanId; import org.onosproject.TestApplicationId; +import org.onosproject.codec.CodecService; import org.onosproject.core.ApplicationId; import org.onosproject.core.CoreService; import org.onosproject.inbandtelemetry.api.IntIntent; @@ -64,6 +65,7 @@ import org.onosproject.net.host.HostService; import org.onosproject.store.service.ConsistentMap; import org.onosproject.store.service.StorageService; import org.onosproject.store.service.TestStorageService; +import org.onosproject.codec.JsonCodec; import java.io.IOException; import java.io.InputStream; @@ -132,6 +134,7 @@ public class SimpleIntManagerTest { private NetworkConfigRegistry networkConfigRegistry; private NetworkConfigService networkConfigService; private NetworkConfigListener networkConfigListener; + private CodecService codecService = new TestCodecService(); @Before @@ -154,7 +157,7 @@ public class SimpleIntManagerTest { manager.netcfgService = networkConfigService; manager.netcfgRegistry = networkConfigRegistry; manager.eventExecutor = MoreExecutors.newDirectExecutorService(); - + manager.codecService = codecService; expect(coreService.registerApplication(APP_NAME)) .andReturn(APP_ID).anyTimes(); networkConfigRegistry.registerConfigFactory(anyObject()); @@ -427,3 +430,27 @@ public class SimpleIntManagerTest { .build(); } } + +/** + * Test Codec service. + */ +class TestCodecService implements CodecService { + + @Override + public Set> getCodecs() { + return null; + } + + @Override + public JsonCodec getCodec(Class entityClass) { + return null; + } + + @Override + public void registerCodec(Class entityClass, JsonCodec codec) { } + + @Override + public void unregisterCodec(Class entityClass) { + + } +}