blob: a14049182f02e54eb737f5f2bf4684c0b2d16998 [file] [log] [blame]
Carmelo Casconef7aa3f92017-07-06 23:56:50 -04001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2017-present Open Networking Foundation
Carmelo Casconef7aa3f92017-07-06 23:56:50 -04003 *
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 */
16
17package org.onosproject.p4runtime.ctl;
18
19import com.google.common.collect.Maps;
Carmelo Cascone158b8c42018-07-04 19:42:37 +020020import com.google.common.util.concurrent.Striped;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040021import io.grpc.ManagedChannel;
22import io.grpc.ManagedChannelBuilder;
Carmelo Cascone44448a52018-06-25 23:36:57 +020023import io.grpc.netty.NettyChannelBuilder;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040024import org.apache.felix.scr.annotations.Activate;
25import org.apache.felix.scr.annotations.Component;
26import org.apache.felix.scr.annotations.Deactivate;
27import org.apache.felix.scr.annotations.Reference;
28import org.apache.felix.scr.annotations.ReferenceCardinality;
29import org.apache.felix.scr.annotations.Service;
30import org.onosproject.event.AbstractListenerManager;
31import org.onosproject.grpc.api.GrpcChannelId;
32import org.onosproject.grpc.api.GrpcController;
33import org.onosproject.net.DeviceId;
Carmelo Casconee5b28722018-06-22 17:28:28 +020034import org.onosproject.net.device.DeviceAgentEvent;
35import org.onosproject.net.device.DeviceAgentListener;
Carmelo Cascone9e4972c2018-08-30 00:29:16 -070036import org.onosproject.net.provider.ProviderId;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040037import org.onosproject.p4runtime.api.P4RuntimeClient;
38import org.onosproject.p4runtime.api.P4RuntimeController;
39import org.onosproject.p4runtime.api.P4RuntimeEvent;
40import org.onosproject.p4runtime.api.P4RuntimeEventListener;
Yi Tseng3e7f1452017-10-20 10:31:53 -070041import org.onosproject.store.service.StorageService;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040042import org.slf4j.Logger;
43
44import java.io.IOException;
Carmelo Casconee5b28722018-06-22 17:28:28 +020045import java.math.BigInteger;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040046import java.util.Map;
Carmelo Casconee5b28722018-06-22 17:28:28 +020047import java.util.concurrent.ConcurrentMap;
Carmelo Cascone158b8c42018-07-04 19:42:37 +020048import java.util.concurrent.locks.Lock;
49import java.util.function.Supplier;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040050
Carmelo Cascone158b8c42018-07-04 19:42:37 +020051import static com.google.common.base.Preconditions.checkArgument;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040052import static com.google.common.base.Preconditions.checkNotNull;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040053import static org.slf4j.LoggerFactory.getLogger;
54
55/**
56 * P4Runtime controller implementation.
57 */
58@Component(immediate = true)
59@Service
60public class P4RuntimeControllerImpl
61 extends AbstractListenerManager<P4RuntimeEvent, P4RuntimeEventListener>
62 implements P4RuntimeController {
63
Carmelo Casconedca52ba2018-09-07 19:06:26 -070064 // Getting the pipeline config from the device can take tens of MBs.
65 private static final int MAX_INBOUND_MSG_SIZE = 256; // Megabytes.
66 private static final int MEGABYTES = 1024 * 1024;
67
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040068 private final Logger log = getLogger(getClass());
Carmelo Cascone158b8c42018-07-04 19:42:37 +020069
70 private final Map<DeviceId, ClientKey> clientKeys = Maps.newHashMap();
71 private final Map<ClientKey, P4RuntimeClient> clients = Maps.newHashMap();
Carmelo Cascone59f57de2017-07-11 19:55:09 -040072 private final Map<DeviceId, GrpcChannelId> channelIds = Maps.newHashMap();
Carmelo Cascone158b8c42018-07-04 19:42:37 +020073
Carmelo Cascone9e4972c2018-08-30 00:29:16 -070074 private final ConcurrentMap<DeviceId, ConcurrentMap<ProviderId, DeviceAgentListener>>
75 deviceAgentListeners = Maps.newConcurrentMap();
Carmelo Cascone158b8c42018-07-04 19:42:37 +020076 private final Striped<Lock> stripedLocks = Striped.lock(30);
77
Carmelo Casconee5b28722018-06-22 17:28:28 +020078 private DistributedElectionIdGenerator electionIdGenerator;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040079
Carmelo Cascone8d99b172017-07-18 17:26:31 -040080 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Carmelo Cascone44448a52018-06-25 23:36:57 +020081 private GrpcController grpcController;
Carmelo Cascone8d99b172017-07-18 17:26:31 -040082
Yi Tseng3e7f1452017-10-20 10:31:53 -070083 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Carmelo Cascone44448a52018-06-25 23:36:57 +020084 private StorageService storageService;
Yi Tseng3e7f1452017-10-20 10:31:53 -070085
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040086 @Activate
87 public void activate() {
Carmelo Cascone2cad9ef2017-08-01 21:52:07 +020088 eventDispatcher.addSink(P4RuntimeEvent.class, listenerRegistry);
Carmelo Casconee5b28722018-06-22 17:28:28 +020089 electionIdGenerator = new DistributedElectionIdGenerator(storageService);
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040090 log.info("Started");
91 }
92
93
94 @Deactivate
95 public void deactivate() {
Carmelo Cascone158b8c42018-07-04 19:42:37 +020096 clientKeys.keySet().forEach(this::removeClient);
97 clientKeys.clear();
98 clients.clear();
99 channelIds.clear();
100 deviceAgentListeners.clear();
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400101 grpcController = null;
Carmelo Casconee5b28722018-06-22 17:28:28 +0200102 electionIdGenerator.destroy();
103 electionIdGenerator = null;
Carmelo Cascone2cad9ef2017-08-01 21:52:07 +0200104 eventDispatcher.removeSink(P4RuntimeEvent.class);
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400105 log.info("Stopped");
106 }
107
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400108 @Override
Carmelo Cascone44448a52018-06-25 23:36:57 +0200109 public boolean createClient(DeviceId deviceId, String serverAddr,
110 int serverPort, long p4DeviceId) {
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400111 checkNotNull(deviceId);
Carmelo Cascone44448a52018-06-25 23:36:57 +0200112 checkNotNull(serverAddr);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200113 checkArgument(serverPort > 0, "Invalid server port");
Carmelo Cascone44448a52018-06-25 23:36:57 +0200114
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200115 return withDeviceLock(() -> doCreateClient(
116 deviceId, serverAddr, serverPort, p4DeviceId), deviceId);
117 }
118
119 private boolean doCreateClient(DeviceId deviceId, String serverAddr,
120 int serverPort, long p4DeviceId) {
121
122 ClientKey clientKey = new ClientKey(deviceId, serverAddr, serverPort, p4DeviceId);
123
124 if (clientKeys.containsKey(deviceId)) {
125 final ClientKey existingKey = clientKeys.get(deviceId);
126 if (clientKey.equals(existingKey)) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700127 log.debug("Not creating client for {} as it already exists (server={}:{}, p4DeviceId={})...",
Carmelo Casconedca52ba2018-09-07 19:06:26 -0700128 deviceId, serverAddr, serverPort, p4DeviceId);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200129 return true;
130 } else {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700131 log.info("Requested client for {} with new " +
132 "endpoint, removing old client (server={}:{}, " +
133 "p4DeviceId={})...",
134 deviceId, existingKey.serverAddr(),
135 existingKey.serverPort(), existingKey.p4DeviceId());
136 doRemoveClient(deviceId);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200137 }
138 }
139
140 log.info("Creating client for {} (server={}:{}, p4DeviceId={})...",
141 deviceId, serverAddr, serverPort, p4DeviceId);
142
143 GrpcChannelId channelId = GrpcChannelId.of(
144 clientKey.deviceId(), "p4runtime-" + clientKey);
Carmelo Cascone44448a52018-06-25 23:36:57 +0200145
146 ManagedChannelBuilder channelBuilder = NettyChannelBuilder
147 .forAddress(serverAddr, serverPort)
Carmelo Casconedca52ba2018-09-07 19:06:26 -0700148 .maxInboundMessageSize(MAX_INBOUND_MSG_SIZE * MEGABYTES)
149 .usePlaintext();
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400150
151 ManagedChannel channel;
152 try {
153 channel = grpcController.connectChannel(channelId, channelBuilder);
154 } catch (IOException e) {
Carmelo Cascone44448a52018-06-25 23:36:57 +0200155 log.warn("Unable to connect to gRPC server of {}: {}",
156 clientKey.deviceId(), e.getMessage());
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400157 return false;
158 }
159
Carmelo Cascone44448a52018-06-25 23:36:57 +0200160 P4RuntimeClient client = new P4RuntimeClientImpl(
161 clientKey.deviceId(), clientKey.p4DeviceId(), channel, this);
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400162
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200163 clientKeys.put(clientKey.deviceId(), clientKey);
164 clients.put(clientKey, client);
Carmelo Cascone44448a52018-06-25 23:36:57 +0200165 channelIds.put(clientKey.deviceId(), channelId);
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400166
167 return true;
168 }
169
170 @Override
171 public P4RuntimeClient getClient(DeviceId deviceId) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200172 if (deviceId == null) {
173 return null;
174 }
175 return withDeviceLock(() -> doGetClient(deviceId), deviceId);
176 }
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400177
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200178 private P4RuntimeClient doGetClient(DeviceId deviceId) {
179 if (!clientKeys.containsKey(deviceId)) {
180 return null;
181 } else {
182 return clients.get(clientKeys.get(deviceId));
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400183 }
184 }
185
186 @Override
187 public void removeClient(DeviceId deviceId) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200188 if (deviceId == null) {
189 return;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400190 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200191 withDeviceLock(() -> doRemoveClient(deviceId), deviceId);
192 }
193
194 private Void doRemoveClient(DeviceId deviceId) {
195 if (clientKeys.containsKey(deviceId)) {
196 final ClientKey clientKey = clientKeys.get(deviceId);
197 clients.get(clientKey).shutdown();
198 grpcController.disconnectChannel(channelIds.get(deviceId));
199 clientKeys.remove(deviceId);
200 clients.remove(clientKey);
201 channelIds.remove(deviceId);
202 }
203 return null;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400204 }
205
206 @Override
207 public boolean hasClient(DeviceId deviceId) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200208 return clientKeys.containsKey(deviceId);
Carmelo Cascone59f57de2017-07-11 19:55:09 -0400209 }
210
211 @Override
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200212 public boolean isReachable(DeviceId deviceId) {
213 if (deviceId == null) {
214 return false;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400215 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200216 return withDeviceLock(() -> doIsReacheable(deviceId), deviceId);
217 }
218
219 private boolean doIsReacheable(DeviceId deviceId) {
220 // FIXME: we're not checking for a P4Runtime server, it could be any gRPC service
221 if (!clientKeys.containsKey(deviceId)) {
222 log.debug("No client for {}, can't check for reachability", deviceId);
223 return false;
224 }
225 return grpcController.isChannelOpen(channelIds.get(deviceId));
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400226 }
227
Yi Tseng3e7f1452017-10-20 10:31:53 -0700228 @Override
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700229 public void addDeviceAgentListener(DeviceId deviceId, ProviderId providerId, DeviceAgentListener listener) {
Carmelo Cascone7044efd2018-07-06 13:01:36 +0200230 checkNotNull(deviceId, "deviceId cannot be null");
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700231 checkNotNull(deviceId, "providerId cannot be null");
Carmelo Cascone7044efd2018-07-06 13:01:36 +0200232 checkNotNull(listener, "listener cannot be null");
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700233 deviceAgentListeners.putIfAbsent(deviceId, Maps.newConcurrentMap());
234 deviceAgentListeners.get(deviceId).put(providerId, listener);
Yi Tseng3e7f1452017-10-20 10:31:53 -0700235 }
236
Andrea Campanella1e573442018-05-17 17:07:13 +0200237 @Override
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700238 public void removeDeviceAgentListener(DeviceId deviceId, ProviderId providerId) {
Carmelo Cascone7044efd2018-07-06 13:01:36 +0200239 checkNotNull(deviceId, "deviceId cannot be null");
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700240 checkNotNull(providerId, "listener cannot be null");
Carmelo Casconee5b28722018-06-22 17:28:28 +0200241 deviceAgentListeners.computeIfPresent(deviceId, (did, listeners) -> {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700242 listeners.remove(providerId);
Carmelo Casconee5b28722018-06-22 17:28:28 +0200243 return listeners;
Andrea Campanella1e573442018-05-17 17:07:13 +0200244 });
245 }
246
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200247 private <U> U withDeviceLock(Supplier<U> task, DeviceId deviceId) {
248 final Lock lock = stripedLocks.get(deviceId);
249 lock.lock();
250 try {
251 return task.get();
252 } finally {
253 lock.unlock();
254 }
255 }
256
Carmelo Casconee5b28722018-06-22 17:28:28 +0200257 BigInteger newMasterElectionId(DeviceId deviceId) {
258 return electionIdGenerator.generate(deviceId);
Andrea Campanella1e573442018-05-17 17:07:13 +0200259 }
260
Carmelo Cascone44448a52018-06-25 23:36:57 +0200261 void postEvent(P4RuntimeEvent event) {
Carmelo Casconee5b28722018-06-22 17:28:28 +0200262 switch (event.type()) {
263 case CHANNEL_EVENT:
264 handleChannelEvent(event);
265 break;
266 case ARBITRATION_RESPONSE:
267 handleArbitrationReply(event);
268 break;
Carmelo Casconede3b6842018-09-05 17:45:10 -0700269 case PERMISSION_DENIED:
270 handlePermissionDenied(event);
271 break;
Carmelo Casconee5b28722018-06-22 17:28:28 +0200272 default:
273 post(event);
274 break;
Andrea Campanella1e573442018-05-17 17:07:13 +0200275 }
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400276 }
Carmelo Cascone44448a52018-06-25 23:36:57 +0200277
Carmelo Casconede3b6842018-09-05 17:45:10 -0700278 private void handlePermissionDenied(P4RuntimeEvent event) {
279 postDeviceAgentEvent(event.subject().deviceId(), new DeviceAgentEvent(
280 DeviceAgentEvent.Type.NOT_MASTER, event.subject().deviceId()));
281 }
282
Carmelo Casconee5b28722018-06-22 17:28:28 +0200283 private void handleChannelEvent(P4RuntimeEvent event) {
284 final ChannelEvent channelEvent = (ChannelEvent) event.subject();
285 final DeviceId deviceId = channelEvent.deviceId();
286 final DeviceAgentEvent.Type agentEventType;
287 switch (channelEvent.type()) {
288 case OPEN:
289 agentEventType = DeviceAgentEvent.Type.CHANNEL_OPEN;
290 break;
291 case CLOSED:
292 agentEventType = DeviceAgentEvent.Type.CHANNEL_CLOSED;
293 break;
294 case ERROR:
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200295 agentEventType = !isReachable(deviceId)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200296 ? DeviceAgentEvent.Type.CHANNEL_CLOSED
297 : DeviceAgentEvent.Type.CHANNEL_ERROR;
298 break;
299 default:
300 log.warn("Unrecognized channel event type {}", channelEvent.type());
301 return;
302 }
303 postDeviceAgentEvent(deviceId, new DeviceAgentEvent(agentEventType, deviceId));
304 }
305
306 private void handleArbitrationReply(P4RuntimeEvent event) {
307 final DeviceId deviceId = event.subject().deviceId();
308 final ArbitrationResponse response = (ArbitrationResponse) event.subject();
309 final DeviceAgentEvent.Type roleType = response.isMaster()
310 ? DeviceAgentEvent.Type.ROLE_MASTER
311 : DeviceAgentEvent.Type.ROLE_STANDBY;
312 postDeviceAgentEvent(deviceId, new DeviceAgentEvent(
313 roleType, response.deviceId()));
314 }
315
316 private void postDeviceAgentEvent(DeviceId deviceId, DeviceAgentEvent event) {
317 if (deviceAgentListeners.containsKey(deviceId)) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700318 deviceAgentListeners.get(deviceId).values().forEach(l -> l.event(event));
Carmelo Casconee5b28722018-06-22 17:28:28 +0200319 }
320 }
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400321}