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
This commit is contained in:
diaccio 2021-11-12 17:28:03 +01:00 committed by Pier Luigi Ventre
parent 844bc4e061
commit 6f2d2f769b
10 changed files with 510 additions and 7 deletions

View File

@ -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,
)

View File

@ -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;

View File

@ -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<IntIntent> {
// 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<TrafficSelector> 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();
}
}

View File

@ -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<Class<?>> getClasses() {
return getClasses(IntWebResource.class);
}
}

View File

@ -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<IntIntentId, IntIntent> 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();
}
}

View File

@ -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;

View File

@ -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"
}
}
}
}
}
}
}

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2020-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.
-->
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="ONOS" version="2.5">
<display-name> INT REST API v1.0</display-name>
<security-constraint>
<web-resource-collection>
<web-resource-name>Secured</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
<role-name>viewer</role-name>
</auth-constraint>
</security-constraint>
<security-role>
<role-name>admin</role-name>
<role-name>viewer</role-name>
</security-role>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>karaf</realm-name>
</login-config>
<servlet>
<servlet-name>JAX-RS Service</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>org.onosproject.inbandtelemetry.rest.IntWebApplication</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>JAX-RS Service</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>

View File

@ -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<Lock> deviceLocks = Striped.lock(10);
private final ConcurrentMap<DeviceId, ScheduledFuture<?>> 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.<DeviceId, Long>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);

View File

@ -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<Class<?>> getCodecs() {
return null;
}
@Override
public <T> JsonCodec<T> getCodec(Class<T> entityClass) {
return null;
}
@Override
public <T> void registerCodec(Class<T> entityClass, JsonCodec<T> codec) { }
@Override
public void unregisterCodec(Class<?> entityClass) {
}
}