HIGUCHI Yuta
Committed by Gerrit Code Review

ONOS-3323 gRPC implementation of Remote Service

- Start modelling Device related service (ONOS-3306)
- exclude machine generated code from doc

Change-Id: Idffbcd883f813de79c6f05fedc9475f308efcc31
......@@ -46,11 +46,13 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.1</version>
<version>2.10.3</version>
<configuration>
<show>package</show>
<excludePackageNames>@external-excludes
</excludePackageNames>
<excludePackageNames>@external-excludes</excludePackageNames>
<sourceFileExcludes>
<sourceFileExclude>**/generated-sources/**</sourceFileExclude>
</sourceFileExcludes>
<docfilessubdirs>true</docfilessubdirs>
<doctitle>ONOS Java API (1.4.0-SNAPSHOT)</doctitle>
<groups>
......
......@@ -51,8 +51,11 @@
<show>package</show>
<docfilessubdirs>true</docfilessubdirs>
<doctitle>ONOS Java API (1.4.0-SNAPSHOT)</doctitle>
<excludePackageNames>@internal-excludes
</excludePackageNames>
<excludePackageNames>@internal-excludes</excludePackageNames>
<sourceFileExcludes>
<sourceFileExclude>**/generated-sources/**</sourceFileExclude>
</sourceFileExcludes>
<groups>
<group>
<title>Network Model &amp; Services</title>
......
......@@ -36,6 +36,7 @@
<module>net</module>
<module>store</module>
<module>rpc</module>
<module>rpc-grpc</module>
</modules>
<dependencies>
......
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!--
~ Copyright 2015 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.
-->
<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="${project.artifactId}-${project.version}">
<feature name="${project.artifactId}" version="${project.version}"
description="${project.description}">
<feature>onos-api</feature>
<bundle>mvn:com.google.protobuf/protobuf-java/3.0.0-beta-1</bundle>
<bundle>mvn:io.netty/netty-common/4.1.0.Beta6</bundle>
<bundle>mvn:io.netty/netty-buffer/4.1.0.Beta6</bundle>
<bundle>mvn:io.netty/netty-transport/4.1.0.Beta6</bundle>
<bundle>mvn:io.netty/netty-handler/4.1.0.Beta6</bundle>
<bundle>mvn:io.netty/netty-codec/4.1.0.Beta6</bundle>
<bundle>mvn:io.netty/netty-codec-http/4.1.0.Beta6</bundle>
<bundle>mvn:io.netty/netty-codec-http2/4.1.0.Beta6</bundle>
<bundle>mvn:io.netty/netty-resolver/4.1.0.Beta6</bundle>
<bundle>mvn:com.twitter/hpack/0.11.0</bundle>
<!-- TODO: Create shaded jar for these. -->
<bundle>wrap:mvn:com.google.auth/google-auth-library-credentials/0.3.0$Bundle-SymbolicName=com.google.auth.google-auth-library-credentials&amp;Bundle-Version=0.3.0</bundle>
<bundle>wrap:mvn:com.google.auth/google-auth-library-oauth2-http/0.3.0$Bundle-SymbolicName=com.google.auth.google-auth-library-oauth2-http&amp;Bundle-Version=0.3.0</bundle>
<bundle>wrap:mvn:io.grpc/grpc-all/0.9.0$Bundle-SymbolicName=io.grpc.grpc-all&amp;Bundle-Version=0.9.0&amp;Import-Package=io.netty.*;version=4.1.0.Beta6,javax.net.ssl,com.google.protobuf.nano;resolution:=optional,okio;resolution:=optional,*</bundle>
<bundle>mvn:${project.groupId}/${project.artifactId}/${project.version}</bundle>
</feature>
</features>
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2015 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>onos-incubator</artifactId>
<groupId>org.onosproject</groupId>
<version>1.4.0-SNAPSHOT</version>
</parent>
<artifactId>onos-incubator-rpc-grpc</artifactId>
<packaging>bundle</packaging>
<description>ONOS inter-cluster RPC based on gRPC</description>
<url>http://onosproject.org</url>
<properties>
<onos.app.name>org.onosproject.incubator.rpc.grpc</onos.app.name>
<onos.app.requires>org.onosproject.incubator.rpc</onos.app.requires>
<!-- Note: update feature.xml when updating -->
<grpc.version>0.9.0</grpc.version>
<grpc.netty.version>4.1.0.Beta6</grpc.netty.version>
</properties>
<pluginRepositories>
<pluginRepository>
<id>protoc-plugin</id>
<url>https://dl.bintray.com/sergei-ivanov/maven/</url>
</pluginRepository>
</pluginRepositories>
<dependencies>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-api</artifactId>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-incubator-api</artifactId>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onlab-osgi</artifactId>
</dependency>
<!--
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-all</artifactId>
<version>${grpc.version}</version>
</dependency>
-->
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-core</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-auth</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-api</artifactId>
<scope>test</scope>
<classifier>tests</classifier>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.scr.annotations</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.4.0.Final</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-scr-plugin</artifactId>
<executions>
<execution>
<id>generate-scr-srcdescriptor</id>
<goals>
<goal>scr</goal>
</goals>
</execution>
</executions>
<configuration>
<!-- avoid searching into wrong source path -->
<scanClasses>true</scanClasses>
<supportedProjectTypes>
<supportedProjectType>bundle</supportedProjectType>
<supportedProjectType>war</supportedProjectType>
</supportedProjectTypes>
</configuration>
</plugin>
<plugin>
<groupId>org.onosproject</groupId>
<artifactId>onos-maven-plugin</artifactId>
<executions>
<execution>
<id>cfg</id>
<phase>generate-resources</phase>
<goals>
<goal>cfg</goal>
</goals>
</execution>
<execution>
<id>swagger</id>
<phase>generate-sources</phase>
<goals>
<goal>swagger</goal>
</goals>
</execution>
<execution>
<id>app</id>
<phase>package</phase>
<goals>
<goal>app</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.google.protobuf.tools</groupId>
<artifactId>maven-protoc-plugin</artifactId>
<version>0.4.2</version>
<configuration>
<!-- The version of protoc must match protobuf-java. If you don't
depend on protobuf-java directly, you will be transitively depending on the
protobuf-java version that grpc depends on. -->
<protocArtifact>com.google.protobuf:protoc:3.0.0-beta-1:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>1.9.1</version>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>${project.build.directory}/generated-sources/protobuf/java</source>
<source>${project.build.directory}/generated-sources/protobuf/grpc-java</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<!-- gRPC requires more recent version of netty -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec</artifactId>
<version>${grpc.netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport</artifactId>
<version>${grpc.netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-handler</artifactId>
<version>${grpc.netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-buffer</artifactId>
<version>${grpc.netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-common</artifactId>
<version>${grpc.netty.version}</version>
</dependency>
<dependency>
<groupId>com.twitter</groupId>
<artifactId>hpack</artifactId>
<!-- 0.11.0 and later are published as a bundle -->
<version>0.11.0</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
/*
* Copyright 2015 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.incubator.rpc.grpc;
import java.util.Map;
import org.onosproject.net.device.DeviceProvider;
import org.onosproject.net.device.DeviceProviderRegistry;
import org.onosproject.net.device.DeviceProviderService;
import org.onosproject.net.provider.AbstractProviderRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Maps;
import io.grpc.Channel;
import io.grpc.ManagedChannel;
// gRPC Client side
/**
* Proxy object to handle DeviceProviderRegistry calls.
*
* RPC wise, this will start/stop bidirectional streaming service sessions.
*/
final class DeviceProviderRegistryClientProxy
extends AbstractProviderRegistry<DeviceProvider, DeviceProviderService>
implements DeviceProviderRegistry {
private final Logger log = LoggerFactory.getLogger(getClass());
private final Channel channel;
private final Map<DeviceProvider, DeviceProviderServiceClientProxy> pServices;
DeviceProviderRegistryClientProxy(ManagedChannel channel) {
this.channel = channel;
pServices = Maps.newIdentityHashMap();
}
@Override
protected synchronized DeviceProviderService createProviderService(DeviceProvider provider) {
// Create session
DeviceProviderServiceClientProxy pService = new DeviceProviderServiceClientProxy(provider, channel);
log.debug("Created DeviceProviderServiceClientProxy", pService);
DeviceProviderServiceClientProxy old = pServices.put(provider, pService);
if (old != null) {
// sanity check, can go away
log.warn("Duplicate registration detected for {}", provider.id());
}
return pService;
}
@Override
public synchronized void unregister(DeviceProvider provider) {
DeviceProviderServiceClientProxy pService = pServices.remove(provider);
log.debug("Unregistering DeviceProviderServiceClientProxy", pService);
super.unregister(provider);
if (pService != null) {
pService.shutdown();
}
}
}
/*
* Copyright 2015 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.incubator.rpc.grpc;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentHashMap;
import org.onosproject.incubator.rpc.RemoteServiceContext;
import org.onosproject.net.device.DeviceProviderRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.MoreObjects;
import io.grpc.ManagedChannel;
// gRPC Client side
// Probably there should be plug-in mechanism in the future.
/**
* RemoteServiceContext based on gRPC.
*
* <p>
* Currently it supports {@link DeviceProviderRegistry}.
*/
public class GrpcRemoteServiceContext implements RemoteServiceContext {
private final Logger log = LoggerFactory.getLogger(getClass());
private final Map<Class<? extends Object>, Object> services = new ConcurrentHashMap<>();
private final ManagedChannel channel;
public GrpcRemoteServiceContext(ManagedChannel channel) {
this.channel = checkNotNull(channel);
services.put(DeviceProviderRegistry.class, new DeviceProviderRegistryClientProxy(channel));
}
@Override
public <T> T get(Class<T> serviceClass) {
@SuppressWarnings("unchecked")
T service = (T) services.get(serviceClass);
if (service != null) {
return service;
}
log.error("{} not supported", serviceClass);
throw new NoSuchElementException(serviceClass.getTypeName() + " not supported");
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("services", services.keySet())
.add("channel", channel.authority())
.toString();
}
}
/*
* Copyright 2015 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.incubator.rpc.grpc;
import static com.google.common.base.Preconditions.checkArgument;
import java.net.URI;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.onosproject.incubator.rpc.RemoteServiceContext;
import org.onosproject.incubator.rpc.RemoteServiceContextProvider;
import org.onosproject.incubator.rpc.RemoteServiceContextProviderService;
import org.onosproject.incubator.rpc.RemoteServiceProviderRegistry;
import org.onosproject.net.provider.ProviderId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.grpc.ManagedChannel;
import io.grpc.netty.NegotiationType;
import io.grpc.netty.NettyChannelBuilder;
// gRPC Client side
/**
* RemoteServiceContextProvider based on gRPC.
*/
@Component(immediate = true)
public class GrpcRemoteServiceProvider implements RemoteServiceContextProvider {
public static final String GRPC_SCHEME = "grpc";
public static final String RPC_PROVIDER_NAME = "org.onosproject.rpc.provider.grpc";
private static final ProviderId PID = new ProviderId(GRPC_SCHEME, RPC_PROVIDER_NAME);
private final Logger log = LoggerFactory.getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected RemoteServiceProviderRegistry rpcRegistry;
private Map<URI, ManagedChannel> channels = new ConcurrentHashMap<>();
private RemoteServiceContextProviderService providerService;
@Activate
protected void activate() {
providerService = rpcRegistry.register(this);
// FIXME remove me. test code to see if gRPC loads in karaf
//getChannel(URI.create("grpc://localhost:8080"));
log.info("Started");
}
@Deactivate
protected void deactivate() {
rpcRegistry.unregister(this);
// shutdown all channels
channels.values().stream()
.forEach(ManagedChannel::shutdown);
// Should we wait for shutdown? How?
channels.clear();
log.info("Stopped");
}
@Override
public ProviderId id() {
return PID;
}
@Override
public RemoteServiceContext get(URI uri) {
// Create gRPC client
return new GrpcRemoteServiceContext(getChannel(uri));
}
private ManagedChannel getChannel(URI uri) {
checkArgument(Objects.equals(GRPC_SCHEME, uri.getScheme()),
"Invalid URI scheme: %s", uri.getScheme());
return channels.compute(uri, (u, ch) -> {
if (ch != null && !ch.isShutdown()) {
return ch;
} else {
return createChannel(u);
}
});
}
private ManagedChannel createChannel(URI uri) {
log.debug("Creating channel for {}", uri);
return NettyChannelBuilder.forAddress(uri.getHost(), uri.getPort())
.negotiationType(NegotiationType.PLAINTEXT)
.build();
}
}
/*
* Copyright 2015 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.
*/
/**
* gRPC based RemoteServiceProvider implementation.
*/
package org.onosproject.incubator.rpc.grpc;
syntax = "proto3";
option java_package = "org.onosproject.grpc";
import "Port.proto";
package Device;
enum DeviceType {
OTHER = 0;
SWITCH = 1;
ROUTER = 2;
ROADM = 3;
OTN = 4;
ROADM_OTN = 5;
FIREWALL = 6;
BALANCER = 7;
IPS = 8;
IDS = 9;
CONTROLLER = 10;
VIRTUAL = 11;
FIBER_SWITCH = 12;
MICROWAVE = 13;
}
message DeviceDescription {
string device_Uri = 1;
DeviceType type = 2;
string manufacturer = 3;
string hw_version = 4;
string sw_version = 5;
string serial_number = 6;
string chassis_id = 7;
map<string, string> annotations = 8;
}
enum MastershipRole {
NONE = 0;
MASTER = 1;
STANDBY = 2;
}
message DeviceConnected {
// DeviceID as String DeviceId#toString
string device_id = 1;
DeviceDescription device_description = 2;
}
message DeviceDisconnected {
// DeviceID as String DeviceId#toString
string device_id = 1;
}
message UpdatePorts {
// DeviceID as String DeviceId#toString
string device_id = 1;
repeated Port.PortDescription port_descriptions= 2;
}
message PortStatusChanged {
// DeviceID as String DeviceId#toString
string device_id = 1;
Port.PortDescription port_description= 2;
}
message ReceivedRoleReply {
// DeviceID as String DeviceId#toString
string device_id = 1;
MastershipRole requested = 2;
MastershipRole response = 3;
}
message UpdatePortStatistics {
// DeviceID as String DeviceId#toString
string device_id = 1;
repeated Port.PortStatistics port_statistics = 2;
}
message RegisterProvider {
// DeviceProvider's ProviderId scheme
string provider_scheme = 1;
}
message DeviceProviderServiceMsg {
oneof method {
DeviceConnected device_connected= 1;
DeviceDisconnected device_disconnected = 2;
UpdatePorts update_ports= 3;
PortStatusChanged port_status_changed = 4;
ReceivedRoleReply received_role_reply = 5;
UpdatePortStatistics update_port_statistics = 6;
// This message is for return value of DeviceProvider#isReachable
IsReachableResponse is_reachable_response = 7;
// This MUST be the 1st message over the stream
RegisterProvider register_provider = 8;
}
}
message TriggerProbe {
// DeviceID as String DeviceId#toString
string device_id = 1;
}
message RoleChanged {
// DeviceID as String DeviceId#toString
string device_id = 1;
MastershipRole new_role = 2;
}
message IsReachableRequest {
int32 xid = 1;
// DeviceID as String DeviceId#toString
string device_id = 2;
}
message IsReachableResponse {
int32 xid = 1;
bool is_reachable = 2;
}
message DeviceProviderMsg {
oneof method {
TriggerProbe trigger_probe = 1;
RoleChanged role_changed = 2;
IsReachableRequest is_reachable_request= 3;
}
}
service DeviceProviderRegistryRpc {
rpc Register(stream DeviceProviderServiceMsg) returns (stream DeviceProviderMsg);
}
syntax = "proto3";
option java_package = "org.onosproject.grpc";
package Port;
enum PortType {
// Signifies copper-based connectivity.
COPPER = 0;
// Signifies optical fiber-based connectivity.
FIBER = 1;
// Signifies optical fiber-based packet port.
PACKET = 2;
// Signifies optical fiber-based optical tributary port (called T-port).
//The signal from the client side will be formed into a ITU G.709 (OTN) frame.
ODUCLT = 3;
// Signifies optical fiber-based Line-side port (called L-port).
OCH = 4;
// Signifies optical fiber-based WDM port (called W-port).
//Optical Multiplexing Section (See ITU G.709).
OMS = 5;
// Signifies virtual port.
VIRTUAL = 6;
}
// TODO What are we going to do with more specific PortDescription ...
message PortDescription {
// PortNumber as String PortNumber#toString
string port_number = 1;
bool is_enabled = 2;
PortType type = 3;
int64 port_speed = 4;
map<string, string> annotations = 8;
}
message PortStatistics {
int32 port = 1;
int64 packets_received = 2;
int64 packets_sent = 3;
// TODO add all other fields
}