blob: 953f1ff1c9f9bcfef9d22a44e5913acbd2e3b24a [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.onosproject.event.AbstractListenerManager;
25import org.onosproject.grpc.api.GrpcChannelId;
26import org.onosproject.grpc.api.GrpcController;
27import org.onosproject.net.DeviceId;
Carmelo Casconee5b28722018-06-22 17:28:28 +020028import org.onosproject.net.device.DeviceAgentEvent;
29import org.onosproject.net.device.DeviceAgentListener;
Carmelo Cascone9e4972c2018-08-30 00:29:16 -070030import org.onosproject.net.provider.ProviderId;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040031import org.onosproject.p4runtime.api.P4RuntimeClient;
32import org.onosproject.p4runtime.api.P4RuntimeController;
33import org.onosproject.p4runtime.api.P4RuntimeEvent;
34import org.onosproject.p4runtime.api.P4RuntimeEventListener;
Yi Tseng3e7f1452017-10-20 10:31:53 -070035import org.onosproject.store.service.StorageService;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070036import org.osgi.service.component.annotations.Activate;
37import org.osgi.service.component.annotations.Component;
38import org.osgi.service.component.annotations.Deactivate;
39import org.osgi.service.component.annotations.Reference;
40import org.osgi.service.component.annotations.ReferenceCardinality;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040041import org.slf4j.Logger;
42
43import java.io.IOException;
Carmelo Casconee5b28722018-06-22 17:28:28 +020044import java.math.BigInteger;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040045import java.util.Map;
Carmelo Casconee5b28722018-06-22 17:28:28 +020046import java.util.concurrent.ConcurrentMap;
Carmelo Cascone158b8c42018-07-04 19:42:37 +020047import java.util.concurrent.locks.Lock;
48import java.util.function.Supplier;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040049
Carmelo Cascone158b8c42018-07-04 19:42:37 +020050import static com.google.common.base.Preconditions.checkArgument;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040051import static com.google.common.base.Preconditions.checkNotNull;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040052import static org.slf4j.LoggerFactory.getLogger;
53
54/**
55 * P4Runtime controller implementation.
56 */
Ray Milkeyd84f89b2018-08-17 14:54:17 -070057@Component(immediate = true, service = P4RuntimeController.class)
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040058public class P4RuntimeControllerImpl
59 extends AbstractListenerManager<P4RuntimeEvent, P4RuntimeEventListener>
60 implements P4RuntimeController {
61
Carmelo Casconedca52ba2018-09-07 19:06:26 -070062 // Getting the pipeline config from the device can take tens of MBs.
63 private static final int MAX_INBOUND_MSG_SIZE = 256; // Megabytes.
64 private static final int MEGABYTES = 1024 * 1024;
65
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040066 private final Logger log = getLogger(getClass());
Carmelo Cascone158b8c42018-07-04 19:42:37 +020067
68 private final Map<DeviceId, ClientKey> clientKeys = Maps.newHashMap();
69 private final Map<ClientKey, P4RuntimeClient> clients = Maps.newHashMap();
Carmelo Cascone59f57de2017-07-11 19:55:09 -040070 private final Map<DeviceId, GrpcChannelId> channelIds = Maps.newHashMap();
Carmelo Cascone158b8c42018-07-04 19:42:37 +020071
Carmelo Cascone9e4972c2018-08-30 00:29:16 -070072 private final ConcurrentMap<DeviceId, ConcurrentMap<ProviderId, DeviceAgentListener>>
73 deviceAgentListeners = Maps.newConcurrentMap();
Carmelo Cascone158b8c42018-07-04 19:42:37 +020074 private final Striped<Lock> stripedLocks = Striped.lock(30);
75
Carmelo Casconee5b28722018-06-22 17:28:28 +020076 private DistributedElectionIdGenerator electionIdGenerator;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040077
Ray Milkeyd84f89b2018-08-17 14:54:17 -070078 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Cascone44448a52018-06-25 23:36:57 +020079 private GrpcController grpcController;
Carmelo Cascone8d99b172017-07-18 17:26:31 -040080
Ray Milkeyd84f89b2018-08-17 14:54:17 -070081 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Cascone44448a52018-06-25 23:36:57 +020082 private StorageService storageService;
Yi Tseng3e7f1452017-10-20 10:31:53 -070083
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040084 @Activate
85 public void activate() {
Carmelo Cascone2cad9ef2017-08-01 21:52:07 +020086 eventDispatcher.addSink(P4RuntimeEvent.class, listenerRegistry);
Carmelo Casconee5b28722018-06-22 17:28:28 +020087 electionIdGenerator = new DistributedElectionIdGenerator(storageService);
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040088 log.info("Started");
89 }
90
91
92 @Deactivate
93 public void deactivate() {
Carmelo Cascone158b8c42018-07-04 19:42:37 +020094 clientKeys.keySet().forEach(this::removeClient);
95 clientKeys.clear();
96 clients.clear();
97 channelIds.clear();
98 deviceAgentListeners.clear();
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040099 grpcController = null;
Carmelo Casconee5b28722018-06-22 17:28:28 +0200100 electionIdGenerator.destroy();
101 electionIdGenerator = null;
Carmelo Cascone2cad9ef2017-08-01 21:52:07 +0200102 eventDispatcher.removeSink(P4RuntimeEvent.class);
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400103 log.info("Stopped");
104 }
105
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400106 @Override
Carmelo Cascone44448a52018-06-25 23:36:57 +0200107 public boolean createClient(DeviceId deviceId, String serverAddr,
108 int serverPort, long p4DeviceId) {
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400109 checkNotNull(deviceId);
Carmelo Cascone44448a52018-06-25 23:36:57 +0200110 checkNotNull(serverAddr);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200111 checkArgument(serverPort > 0, "Invalid server port");
Carmelo Cascone44448a52018-06-25 23:36:57 +0200112
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200113 return withDeviceLock(() -> doCreateClient(
114 deviceId, serverAddr, serverPort, p4DeviceId), deviceId);
115 }
116
117 private boolean doCreateClient(DeviceId deviceId, String serverAddr,
118 int serverPort, long p4DeviceId) {
119
120 ClientKey clientKey = new ClientKey(deviceId, serverAddr, serverPort, p4DeviceId);
121
122 if (clientKeys.containsKey(deviceId)) {
123 final ClientKey existingKey = clientKeys.get(deviceId);
124 if (clientKey.equals(existingKey)) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700125 log.debug("Not creating client for {} as it already exists (server={}:{}, p4DeviceId={})...",
Carmelo Casconedca52ba2018-09-07 19:06:26 -0700126 deviceId, serverAddr, serverPort, p4DeviceId);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200127 return true;
128 } else {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700129 log.info("Requested client for {} with new " +
130 "endpoint, removing old client (server={}:{}, " +
131 "p4DeviceId={})...",
132 deviceId, existingKey.serverAddr(),
133 existingKey.serverPort(), existingKey.p4DeviceId());
134 doRemoveClient(deviceId);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200135 }
136 }
137
138 log.info("Creating client for {} (server={}:{}, p4DeviceId={})...",
139 deviceId, serverAddr, serverPort, p4DeviceId);
140
141 GrpcChannelId channelId = GrpcChannelId.of(
142 clientKey.deviceId(), "p4runtime-" + clientKey);
Carmelo Cascone44448a52018-06-25 23:36:57 +0200143
144 ManagedChannelBuilder channelBuilder = NettyChannelBuilder
145 .forAddress(serverAddr, serverPort)
Carmelo Casconedca52ba2018-09-07 19:06:26 -0700146 .maxInboundMessageSize(MAX_INBOUND_MSG_SIZE * MEGABYTES)
147 .usePlaintext();
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400148
149 ManagedChannel channel;
150 try {
151 channel = grpcController.connectChannel(channelId, channelBuilder);
152 } catch (IOException e) {
Carmelo Cascone44448a52018-06-25 23:36:57 +0200153 log.warn("Unable to connect to gRPC server of {}: {}",
154 clientKey.deviceId(), e.getMessage());
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400155 return false;
156 }
157
Carmelo Cascone44448a52018-06-25 23:36:57 +0200158 P4RuntimeClient client = new P4RuntimeClientImpl(
159 clientKey.deviceId(), clientKey.p4DeviceId(), channel, this);
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400160
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200161 clientKeys.put(clientKey.deviceId(), clientKey);
162 clients.put(clientKey, client);
Carmelo Cascone44448a52018-06-25 23:36:57 +0200163 channelIds.put(clientKey.deviceId(), channelId);
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400164
165 return true;
166 }
167
168 @Override
169 public P4RuntimeClient getClient(DeviceId deviceId) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200170 if (deviceId == null) {
171 return null;
172 }
173 return withDeviceLock(() -> doGetClient(deviceId), deviceId);
174 }
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400175
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200176 private P4RuntimeClient doGetClient(DeviceId deviceId) {
177 if (!clientKeys.containsKey(deviceId)) {
178 return null;
179 } else {
180 return clients.get(clientKeys.get(deviceId));
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400181 }
182 }
183
184 @Override
185 public void removeClient(DeviceId deviceId) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200186 if (deviceId == null) {
187 return;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400188 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200189 withDeviceLock(() -> doRemoveClient(deviceId), deviceId);
190 }
191
192 private Void doRemoveClient(DeviceId deviceId) {
193 if (clientKeys.containsKey(deviceId)) {
194 final ClientKey clientKey = clientKeys.get(deviceId);
195 clients.get(clientKey).shutdown();
196 grpcController.disconnectChannel(channelIds.get(deviceId));
197 clientKeys.remove(deviceId);
198 clients.remove(clientKey);
199 channelIds.remove(deviceId);
200 }
201 return null;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400202 }
203
204 @Override
205 public boolean hasClient(DeviceId deviceId) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200206 return clientKeys.containsKey(deviceId);
Carmelo Cascone59f57de2017-07-11 19:55:09 -0400207 }
208
209 @Override
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200210 public boolean isReachable(DeviceId deviceId) {
211 if (deviceId == null) {
212 return false;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400213 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200214 return withDeviceLock(() -> doIsReacheable(deviceId), deviceId);
215 }
216
217 private boolean doIsReacheable(DeviceId deviceId) {
218 // FIXME: we're not checking for a P4Runtime server, it could be any gRPC service
219 if (!clientKeys.containsKey(deviceId)) {
220 log.debug("No client for {}, can't check for reachability", deviceId);
221 return false;
222 }
223 return grpcController.isChannelOpen(channelIds.get(deviceId));
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400224 }
225
Yi Tseng3e7f1452017-10-20 10:31:53 -0700226 @Override
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700227 public void addDeviceAgentListener(DeviceId deviceId, ProviderId providerId, DeviceAgentListener listener) {
Carmelo Cascone7044efd2018-07-06 13:01:36 +0200228 checkNotNull(deviceId, "deviceId cannot be null");
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700229 checkNotNull(deviceId, "providerId cannot be null");
Carmelo Cascone7044efd2018-07-06 13:01:36 +0200230 checkNotNull(listener, "listener cannot be null");
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700231 deviceAgentListeners.putIfAbsent(deviceId, Maps.newConcurrentMap());
232 deviceAgentListeners.get(deviceId).put(providerId, listener);
Yi Tseng3e7f1452017-10-20 10:31:53 -0700233 }
234
Andrea Campanella1e573442018-05-17 17:07:13 +0200235 @Override
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700236 public void removeDeviceAgentListener(DeviceId deviceId, ProviderId providerId) {
Carmelo Cascone7044efd2018-07-06 13:01:36 +0200237 checkNotNull(deviceId, "deviceId cannot be null");
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700238 checkNotNull(providerId, "listener cannot be null");
Carmelo Casconee5b28722018-06-22 17:28:28 +0200239 deviceAgentListeners.computeIfPresent(deviceId, (did, listeners) -> {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700240 listeners.remove(providerId);
Carmelo Casconee5b28722018-06-22 17:28:28 +0200241 return listeners;
Andrea Campanella1e573442018-05-17 17:07:13 +0200242 });
243 }
244
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200245 private <U> U withDeviceLock(Supplier<U> task, DeviceId deviceId) {
246 final Lock lock = stripedLocks.get(deviceId);
247 lock.lock();
248 try {
249 return task.get();
250 } finally {
251 lock.unlock();
252 }
253 }
254
Carmelo Casconee5b28722018-06-22 17:28:28 +0200255 BigInteger newMasterElectionId(DeviceId deviceId) {
256 return electionIdGenerator.generate(deviceId);
Andrea Campanella1e573442018-05-17 17:07:13 +0200257 }
258
Carmelo Cascone44448a52018-06-25 23:36:57 +0200259 void postEvent(P4RuntimeEvent event) {
Carmelo Casconee5b28722018-06-22 17:28:28 +0200260 switch (event.type()) {
261 case CHANNEL_EVENT:
262 handleChannelEvent(event);
263 break;
264 case ARBITRATION_RESPONSE:
265 handleArbitrationReply(event);
266 break;
Carmelo Casconede3b6842018-09-05 17:45:10 -0700267 case PERMISSION_DENIED:
268 handlePermissionDenied(event);
269 break;
Carmelo Casconee5b28722018-06-22 17:28:28 +0200270 default:
271 post(event);
272 break;
Andrea Campanella1e573442018-05-17 17:07:13 +0200273 }
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400274 }
Carmelo Cascone44448a52018-06-25 23:36:57 +0200275
Carmelo Casconede3b6842018-09-05 17:45:10 -0700276 private void handlePermissionDenied(P4RuntimeEvent event) {
277 postDeviceAgentEvent(event.subject().deviceId(), new DeviceAgentEvent(
278 DeviceAgentEvent.Type.NOT_MASTER, event.subject().deviceId()));
279 }
280
Carmelo Casconee5b28722018-06-22 17:28:28 +0200281 private void handleChannelEvent(P4RuntimeEvent event) {
282 final ChannelEvent channelEvent = (ChannelEvent) event.subject();
283 final DeviceId deviceId = channelEvent.deviceId();
284 final DeviceAgentEvent.Type agentEventType;
285 switch (channelEvent.type()) {
286 case OPEN:
287 agentEventType = DeviceAgentEvent.Type.CHANNEL_OPEN;
288 break;
289 case CLOSED:
290 agentEventType = DeviceAgentEvent.Type.CHANNEL_CLOSED;
291 break;
292 case ERROR:
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200293 agentEventType = !isReachable(deviceId)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200294 ? DeviceAgentEvent.Type.CHANNEL_CLOSED
295 : DeviceAgentEvent.Type.CHANNEL_ERROR;
296 break;
297 default:
298 log.warn("Unrecognized channel event type {}", channelEvent.type());
299 return;
300 }
301 postDeviceAgentEvent(deviceId, new DeviceAgentEvent(agentEventType, deviceId));
302 }
303
304 private void handleArbitrationReply(P4RuntimeEvent event) {
305 final DeviceId deviceId = event.subject().deviceId();
306 final ArbitrationResponse response = (ArbitrationResponse) event.subject();
307 final DeviceAgentEvent.Type roleType = response.isMaster()
308 ? DeviceAgentEvent.Type.ROLE_MASTER
309 : DeviceAgentEvent.Type.ROLE_STANDBY;
310 postDeviceAgentEvent(deviceId, new DeviceAgentEvent(
311 roleType, response.deviceId()));
312 }
313
314 private void postDeviceAgentEvent(DeviceId deviceId, DeviceAgentEvent event) {
315 if (deviceAgentListeners.containsKey(deviceId)) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700316 deviceAgentListeners.get(deviceId).values().forEach(l -> l.event(event));
Carmelo Casconee5b28722018-06-22 17:28:28 +0200317 }
318 }
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400319}