blob: 5d7fed39f5d50fc8d245e96764b78f1880923672 [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 Casconef7aa3f92017-07-06 23:56:50 -040062 private final Logger log = getLogger(getClass());
Carmelo Cascone158b8c42018-07-04 19:42:37 +020063
64 private final Map<DeviceId, ClientKey> clientKeys = Maps.newHashMap();
65 private final Map<ClientKey, P4RuntimeClient> clients = Maps.newHashMap();
Carmelo Cascone59f57de2017-07-11 19:55:09 -040066 private final Map<DeviceId, GrpcChannelId> channelIds = Maps.newHashMap();
Carmelo Cascone158b8c42018-07-04 19:42:37 +020067
Carmelo Cascone9e4972c2018-08-30 00:29:16 -070068 private final ConcurrentMap<DeviceId, ConcurrentMap<ProviderId, DeviceAgentListener>>
69 deviceAgentListeners = Maps.newConcurrentMap();
Carmelo Cascone158b8c42018-07-04 19:42:37 +020070 private final Striped<Lock> stripedLocks = Striped.lock(30);
71
Carmelo Casconee5b28722018-06-22 17:28:28 +020072 private DistributedElectionIdGenerator electionIdGenerator;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040073
Ray Milkeyd84f89b2018-08-17 14:54:17 -070074 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Cascone44448a52018-06-25 23:36:57 +020075 private GrpcController grpcController;
Carmelo Cascone8d99b172017-07-18 17:26:31 -040076
Ray Milkeyd84f89b2018-08-17 14:54:17 -070077 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Carmelo Cascone44448a52018-06-25 23:36:57 +020078 private StorageService storageService;
Yi Tseng3e7f1452017-10-20 10:31:53 -070079
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040080 @Activate
81 public void activate() {
Carmelo Cascone2cad9ef2017-08-01 21:52:07 +020082 eventDispatcher.addSink(P4RuntimeEvent.class, listenerRegistry);
Carmelo Casconee5b28722018-06-22 17:28:28 +020083 electionIdGenerator = new DistributedElectionIdGenerator(storageService);
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040084 log.info("Started");
85 }
86
87
88 @Deactivate
89 public void deactivate() {
Carmelo Cascone158b8c42018-07-04 19:42:37 +020090 clientKeys.keySet().forEach(this::removeClient);
91 clientKeys.clear();
92 clients.clear();
93 channelIds.clear();
94 deviceAgentListeners.clear();
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040095 grpcController = null;
Carmelo Casconee5b28722018-06-22 17:28:28 +020096 electionIdGenerator.destroy();
97 electionIdGenerator = null;
Carmelo Cascone2cad9ef2017-08-01 21:52:07 +020098 eventDispatcher.removeSink(P4RuntimeEvent.class);
Carmelo Casconef7aa3f92017-07-06 23:56:50 -040099 log.info("Stopped");
100 }
101
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400102 @Override
Carmelo Cascone44448a52018-06-25 23:36:57 +0200103 public boolean createClient(DeviceId deviceId, String serverAddr,
104 int serverPort, long p4DeviceId) {
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400105 checkNotNull(deviceId);
Carmelo Cascone44448a52018-06-25 23:36:57 +0200106 checkNotNull(serverAddr);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200107 checkArgument(serverPort > 0, "Invalid server port");
Carmelo Cascone44448a52018-06-25 23:36:57 +0200108
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200109 return withDeviceLock(() -> doCreateClient(
110 deviceId, serverAddr, serverPort, p4DeviceId), deviceId);
111 }
112
113 private boolean doCreateClient(DeviceId deviceId, String serverAddr,
114 int serverPort, long p4DeviceId) {
115
116 ClientKey clientKey = new ClientKey(deviceId, serverAddr, serverPort, p4DeviceId);
117
118 if (clientKeys.containsKey(deviceId)) {
119 final ClientKey existingKey = clientKeys.get(deviceId);
120 if (clientKey.equals(existingKey)) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700121 log.debug("Not creating client for {} as it already exists (server={}:{}, p4DeviceId={})...",
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200122 deviceId, serverAddr, serverPort, p4DeviceId);
123 return true;
124 } else {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700125 log.info("Requested client for {} with new " +
126 "endpoint, removing old client (server={}:{}, " +
127 "p4DeviceId={})...",
128 deviceId, existingKey.serverAddr(),
129 existingKey.serverPort(), existingKey.p4DeviceId());
130 doRemoveClient(deviceId);
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200131 }
132 }
133
134 log.info("Creating client for {} (server={}:{}, p4DeviceId={})...",
135 deviceId, serverAddr, serverPort, p4DeviceId);
136
137 GrpcChannelId channelId = GrpcChannelId.of(
138 clientKey.deviceId(), "p4runtime-" + clientKey);
Carmelo Cascone44448a52018-06-25 23:36:57 +0200139
140 ManagedChannelBuilder channelBuilder = NettyChannelBuilder
141 .forAddress(serverAddr, serverPort)
Carmelo Cascone72893b72018-08-09 00:59:06 -0700142 .usePlaintext(true);
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400143
144 ManagedChannel channel;
145 try {
146 channel = grpcController.connectChannel(channelId, channelBuilder);
147 } catch (IOException e) {
Carmelo Cascone44448a52018-06-25 23:36:57 +0200148 log.warn("Unable to connect to gRPC server of {}: {}",
149 clientKey.deviceId(), e.getMessage());
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400150 return false;
151 }
152
Carmelo Cascone44448a52018-06-25 23:36:57 +0200153 P4RuntimeClient client = new P4RuntimeClientImpl(
154 clientKey.deviceId(), clientKey.p4DeviceId(), channel, this);
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400155
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200156 clientKeys.put(clientKey.deviceId(), clientKey);
157 clients.put(clientKey, client);
Carmelo Cascone44448a52018-06-25 23:36:57 +0200158 channelIds.put(clientKey.deviceId(), channelId);
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400159
160 return true;
161 }
162
163 @Override
164 public P4RuntimeClient getClient(DeviceId deviceId) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200165 if (deviceId == null) {
166 return null;
167 }
168 return withDeviceLock(() -> doGetClient(deviceId), deviceId);
169 }
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400170
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200171 private P4RuntimeClient doGetClient(DeviceId deviceId) {
172 if (!clientKeys.containsKey(deviceId)) {
173 return null;
174 } else {
175 return clients.get(clientKeys.get(deviceId));
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400176 }
177 }
178
179 @Override
180 public void removeClient(DeviceId deviceId) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200181 if (deviceId == null) {
182 return;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400183 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200184 withDeviceLock(() -> doRemoveClient(deviceId), deviceId);
185 }
186
187 private Void doRemoveClient(DeviceId deviceId) {
188 if (clientKeys.containsKey(deviceId)) {
189 final ClientKey clientKey = clientKeys.get(deviceId);
190 clients.get(clientKey).shutdown();
191 grpcController.disconnectChannel(channelIds.get(deviceId));
192 clientKeys.remove(deviceId);
193 clients.remove(clientKey);
194 channelIds.remove(deviceId);
195 }
196 return null;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400197 }
198
199 @Override
200 public boolean hasClient(DeviceId deviceId) {
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200201 return clientKeys.containsKey(deviceId);
Carmelo Cascone59f57de2017-07-11 19:55:09 -0400202 }
203
204 @Override
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200205 public boolean isReachable(DeviceId deviceId) {
206 if (deviceId == null) {
207 return false;
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400208 }
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200209 return withDeviceLock(() -> doIsReacheable(deviceId), deviceId);
210 }
211
212 private boolean doIsReacheable(DeviceId deviceId) {
213 // FIXME: we're not checking for a P4Runtime server, it could be any gRPC service
214 if (!clientKeys.containsKey(deviceId)) {
215 log.debug("No client for {}, can't check for reachability", deviceId);
216 return false;
217 }
218 return grpcController.isChannelOpen(channelIds.get(deviceId));
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400219 }
220
Yi Tseng3e7f1452017-10-20 10:31:53 -0700221 @Override
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700222 public void addDeviceAgentListener(DeviceId deviceId, ProviderId providerId, DeviceAgentListener listener) {
Carmelo Cascone7044efd2018-07-06 13:01:36 +0200223 checkNotNull(deviceId, "deviceId cannot be null");
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700224 checkNotNull(deviceId, "providerId cannot be null");
Carmelo Cascone7044efd2018-07-06 13:01:36 +0200225 checkNotNull(listener, "listener cannot be null");
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700226 deviceAgentListeners.putIfAbsent(deviceId, Maps.newConcurrentMap());
227 deviceAgentListeners.get(deviceId).put(providerId, listener);
Yi Tseng3e7f1452017-10-20 10:31:53 -0700228 }
229
Andrea Campanella1e573442018-05-17 17:07:13 +0200230 @Override
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700231 public void removeDeviceAgentListener(DeviceId deviceId, ProviderId providerId) {
Carmelo Cascone7044efd2018-07-06 13:01:36 +0200232 checkNotNull(deviceId, "deviceId cannot be null");
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700233 checkNotNull(providerId, "listener cannot be null");
Carmelo Casconee5b28722018-06-22 17:28:28 +0200234 deviceAgentListeners.computeIfPresent(deviceId, (did, listeners) -> {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700235 listeners.remove(providerId);
Carmelo Casconee5b28722018-06-22 17:28:28 +0200236 return listeners;
Andrea Campanella1e573442018-05-17 17:07:13 +0200237 });
238 }
239
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200240 private <U> U withDeviceLock(Supplier<U> task, DeviceId deviceId) {
241 final Lock lock = stripedLocks.get(deviceId);
242 lock.lock();
243 try {
244 return task.get();
245 } finally {
246 lock.unlock();
247 }
248 }
249
Carmelo Casconee5b28722018-06-22 17:28:28 +0200250 BigInteger newMasterElectionId(DeviceId deviceId) {
251 return electionIdGenerator.generate(deviceId);
Andrea Campanella1e573442018-05-17 17:07:13 +0200252 }
253
Carmelo Cascone44448a52018-06-25 23:36:57 +0200254 void postEvent(P4RuntimeEvent event) {
Carmelo Casconee5b28722018-06-22 17:28:28 +0200255 switch (event.type()) {
256 case CHANNEL_EVENT:
257 handleChannelEvent(event);
258 break;
259 case ARBITRATION_RESPONSE:
260 handleArbitrationReply(event);
261 break;
Carmelo Casconede3b6842018-09-05 17:45:10 -0700262 case PERMISSION_DENIED:
263 handlePermissionDenied(event);
264 break;
Carmelo Casconee5b28722018-06-22 17:28:28 +0200265 default:
266 post(event);
267 break;
Andrea Campanella1e573442018-05-17 17:07:13 +0200268 }
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400269 }
Carmelo Cascone44448a52018-06-25 23:36:57 +0200270
Carmelo Casconede3b6842018-09-05 17:45:10 -0700271 private void handlePermissionDenied(P4RuntimeEvent event) {
272 postDeviceAgentEvent(event.subject().deviceId(), new DeviceAgentEvent(
273 DeviceAgentEvent.Type.NOT_MASTER, event.subject().deviceId()));
274 }
275
Carmelo Casconee5b28722018-06-22 17:28:28 +0200276 private void handleChannelEvent(P4RuntimeEvent event) {
277 final ChannelEvent channelEvent = (ChannelEvent) event.subject();
278 final DeviceId deviceId = channelEvent.deviceId();
279 final DeviceAgentEvent.Type agentEventType;
280 switch (channelEvent.type()) {
281 case OPEN:
282 agentEventType = DeviceAgentEvent.Type.CHANNEL_OPEN;
283 break;
284 case CLOSED:
285 agentEventType = DeviceAgentEvent.Type.CHANNEL_CLOSED;
286 break;
287 case ERROR:
Carmelo Cascone158b8c42018-07-04 19:42:37 +0200288 agentEventType = !isReachable(deviceId)
Carmelo Casconee5b28722018-06-22 17:28:28 +0200289 ? DeviceAgentEvent.Type.CHANNEL_CLOSED
290 : DeviceAgentEvent.Type.CHANNEL_ERROR;
291 break;
292 default:
293 log.warn("Unrecognized channel event type {}", channelEvent.type());
294 return;
295 }
296 postDeviceAgentEvent(deviceId, new DeviceAgentEvent(agentEventType, deviceId));
297 }
298
299 private void handleArbitrationReply(P4RuntimeEvent event) {
300 final DeviceId deviceId = event.subject().deviceId();
301 final ArbitrationResponse response = (ArbitrationResponse) event.subject();
302 final DeviceAgentEvent.Type roleType = response.isMaster()
303 ? DeviceAgentEvent.Type.ROLE_MASTER
304 : DeviceAgentEvent.Type.ROLE_STANDBY;
305 postDeviceAgentEvent(deviceId, new DeviceAgentEvent(
306 roleType, response.deviceId()));
307 }
308
309 private void postDeviceAgentEvent(DeviceId deviceId, DeviceAgentEvent event) {
310 if (deviceAgentListeners.containsKey(deviceId)) {
Carmelo Cascone9e4972c2018-08-30 00:29:16 -0700311 deviceAgentListeners.get(deviceId).values().forEach(l -> l.event(event));
Carmelo Casconee5b28722018-06-22 17:28:28 +0200312 }
313 }
Carmelo Casconef7aa3f92017-07-06 23:56:50 -0400314}