blob: 5b3153cb95db5f097944201598993d10d8905374 [file] [log] [blame]
Yi Tseng2a340f72018-11-02 16:52:47 -07001/*
2 * Copyright 2018-present Open Networking Foundation
3 *
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.grpc.ctl;
18
19import com.google.common.collect.Maps;
20import com.google.common.util.concurrent.Striped;
21import io.grpc.ManagedChannel;
Yi Tseng2a340f72018-11-02 16:52:47 -070022import org.onosproject.event.AbstractListenerManager;
23import org.onosproject.event.Event;
24import org.onosproject.event.EventListener;
Yi Tseng2a340f72018-11-02 16:52:47 -070025import org.onosproject.grpc.api.GrpcClient;
26import org.onosproject.grpc.api.GrpcClientController;
Yi Tseng2a340f72018-11-02 16:52:47 -070027import org.onosproject.net.DeviceId;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080028import org.onosproject.net.device.DeviceAgentEvent;
29import org.onosproject.net.device.DeviceAgentListener;
30import org.onosproject.net.provider.ProviderId;
Ray Milkey5739b2c2018-11-06 14:04:51 -080031import org.osgi.service.component.annotations.Activate;
Ray Milkey5739b2c2018-11-06 14:04:51 -080032import org.osgi.service.component.annotations.Deactivate;
Yi Tseng2a340f72018-11-02 16:52:47 -070033import org.slf4j.Logger;
34
Yi Tseng2a340f72018-11-02 16:52:47 -070035import java.util.Map;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080036import java.util.concurrent.ConcurrentMap;
Yi Tseng2a340f72018-11-02 16:52:47 -070037import java.util.concurrent.locks.Lock;
38import java.util.function.Supplier;
39
40import static com.google.common.base.Preconditions.checkNotNull;
Carmelo Casconea71b8492018-12-17 17:47:50 -080041import static java.lang.String.format;
Yi Tseng2a340f72018-11-02 16:52:47 -070042import static org.slf4j.LoggerFactory.getLogger;
43
44/**
Carmelo Casconec2be50a2019-04-10 00:15:39 -070045 * Abstract class of a controller for gRPC clients which provides means to
46 * create clients, associate device agent listeners to them and register other
47 * event listeners.
Yi Tseng2a340f72018-11-02 16:52:47 -070048 *
49 * @param <C> the gRPC client type
Yi Tseng2a340f72018-11-02 16:52:47 -070050 * @param <E> the event type of the gRPC client
51 * @param <L> the event listener of event {@link E}
52 */
Yi Tseng2a340f72018-11-02 16:52:47 -070053public abstract class AbstractGrpcClientController
Carmelo Casconec2be50a2019-04-10 00:15:39 -070054 <C extends GrpcClient, E extends Event, L extends EventListener<E>>
Yi Tseng2a340f72018-11-02 16:52:47 -070055 extends AbstractListenerManager<E, L>
Carmelo Casconec2be50a2019-04-10 00:15:39 -070056 implements GrpcClientController<C> {
Yi Tseng2a340f72018-11-02 16:52:47 -070057
58 /**
59 * The default max inbound message size (MB).
60 */
Yi Tseng2a340f72018-11-02 16:52:47 -070061 private static final int DEFAULT_DEVICE_LOCK_SIZE = 30;
62
63 private final Logger log = getLogger(getClass());
Carmelo Casconec2be50a2019-04-10 00:15:39 -070064 private final Map<DeviceId, C> clients = Maps.newHashMap();
Carmelo Cascone3977ea42019-02-28 13:43:42 -080065 private final ConcurrentMap<DeviceId, ConcurrentMap<ProviderId, DeviceAgentListener>>
66 deviceAgentListeners = Maps.newConcurrentMap();
67 private final Class<E> eventClass;
Carmelo Casconec2be50a2019-04-10 00:15:39 -070068 private final String serviceName;
Yi Tseng2a340f72018-11-02 16:52:47 -070069 private final Striped<Lock> stripedLocks = Striped.lock(DEFAULT_DEVICE_LOCK_SIZE);
70
Carmelo Casconec2be50a2019-04-10 00:15:39 -070071 public AbstractGrpcClientController(Class<E> eventClass, String serviceName) {
Carmelo Cascone3977ea42019-02-28 13:43:42 -080072 this.eventClass = eventClass;
Carmelo Casconec2be50a2019-04-10 00:15:39 -070073 this.serviceName = serviceName;
Carmelo Cascone3977ea42019-02-28 13:43:42 -080074 }
75
Yi Tseng2a340f72018-11-02 16:52:47 -070076 @Activate
77 public void activate() {
Carmelo Cascone3977ea42019-02-28 13:43:42 -080078 eventDispatcher.addSink(eventClass, listenerRegistry);
Yi Tseng2a340f72018-11-02 16:52:47 -070079 log.info("Started");
80 }
81
82 @Deactivate
83 public void deactivate() {
Carmelo Cascone3977ea42019-02-28 13:43:42 -080084 eventDispatcher.removeSink(eventClass);
Yi Tseng2a340f72018-11-02 16:52:47 -070085 clients.clear();
Carmelo Cascone3977ea42019-02-28 13:43:42 -080086 deviceAgentListeners.clear();
Yi Tseng2a340f72018-11-02 16:52:47 -070087 log.info("Stopped");
88 }
89
90 @Override
Carmelo Casconec2be50a2019-04-10 00:15:39 -070091 public boolean create(DeviceId deviceId, ManagedChannel channel) {
92 checkNotNull(deviceId);
93 checkNotNull(channel);
94 return withDeviceLock(() -> doCreateClient(deviceId, channel), deviceId);
Yi Tseng2a340f72018-11-02 16:52:47 -070095 }
96
Carmelo Casconec2be50a2019-04-10 00:15:39 -070097 private boolean doCreateClient(DeviceId deviceId, ManagedChannel channel) {
Yi Tseng2a340f72018-11-02 16:52:47 -070098
Carmelo Casconec2be50a2019-04-10 00:15:39 -070099 if (clients.containsKey(deviceId)) {
100 throw new IllegalArgumentException(format(
101 "A %s client already exists for %s", serviceName, deviceId));
Brian O'Connorc6943832018-12-12 17:27:11 -0800102 }
Yi Tseng2a340f72018-11-02 16:52:47 -0700103
pierventref92de512021-05-18 18:06:40 +0200104 log.info("Creating {}...", clientName(deviceId));
Carmelo Casconed51a5552019-04-13 01:22:25 -0700105
Carmelo Casconea71b8492018-12-17 17:47:50 -0800106 final C client;
107 try {
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700108 client = createClientInstance(deviceId, channel);
Carmelo Casconea71b8492018-12-17 17:47:50 -0800109 } catch (Throwable e) {
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700110 log.error("Exception while creating {}", clientName(deviceId), e);
Yi Tseng2a340f72018-11-02 16:52:47 -0700111 return false;
112 }
Carmelo Casconea71b8492018-12-17 17:47:50 -0800113
114 if (client == null) {
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700115 log.error("Unable to create {}, implementation returned null...",
116 clientName(deviceId));
Carmelo Casconea71b8492018-12-17 17:47:50 -0800117 return false;
118 }
119
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700120 clients.put(deviceId, client);
Yi Tseng2a340f72018-11-02 16:52:47 -0700121 return true;
122 }
123
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700124 protected abstract C createClientInstance(DeviceId deviceId, ManagedChannel channel);
Yi Tseng2a340f72018-11-02 16:52:47 -0700125
126 @Override
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700127 public C get(DeviceId deviceId) {
Yi Tseng2a340f72018-11-02 16:52:47 -0700128 checkNotNull(deviceId);
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700129 return withDeviceLock(() -> clients.get(deviceId), deviceId);
Yi Tseng2a340f72018-11-02 16:52:47 -0700130 }
131
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700132 @Override
133 public void remove(DeviceId deviceId) {
134 checkNotNull(deviceId);
135 withDeviceLock(() -> {
136 final C client = clients.remove(deviceId);
137 if (client != null) {
pierventref92de512021-05-18 18:06:40 +0200138 log.info("Removing {}...", clientName(deviceId));
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700139 client.shutdown();
140 }
Yi Tseng2a340f72018-11-02 16:52:47 -0700141 return null;
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700142 }, deviceId);
Yi Tseng2a340f72018-11-02 16:52:47 -0700143 }
144
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800145 @Override
146 public void addDeviceAgentListener(DeviceId deviceId, ProviderId providerId, DeviceAgentListener listener) {
147 checkNotNull(deviceId, "deviceId cannot be null");
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700148 checkNotNull(providerId, "providerId cannot be null");
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800149 checkNotNull(listener, "listener cannot be null");
150 deviceAgentListeners.putIfAbsent(deviceId, Maps.newConcurrentMap());
151 deviceAgentListeners.get(deviceId).put(providerId, listener);
152 }
153
154 @Override
155 public void removeDeviceAgentListener(DeviceId deviceId, ProviderId providerId) {
156 checkNotNull(deviceId, "deviceId cannot be null");
157 checkNotNull(providerId, "listener cannot be null");
158 deviceAgentListeners.computeIfPresent(deviceId, (did, listeners) -> {
159 listeners.remove(providerId);
160 return listeners.isEmpty() ? null : listeners;
161 });
162 }
163
164 public void postEvent(E event) {
165 checkNotNull(event);
166 post(event);
167 }
168
169 public void postEvent(DeviceAgentEvent event) {
170 // We should have only one event delivery mechanism. We have two now
171 // because we have two different types of events, DeviceAgentEvent and
172 // controller/protocol specific ones (e.g. P4Runtime or gNMI).
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700173 // TODO: extend device agent event to allow delivery of protocol-specific
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800174 // events, e.g. packet-in
175 checkNotNull(event);
176 if (deviceAgentListeners.containsKey(event.subject())) {
177 deviceAgentListeners.get(event.subject()).values()
178 .forEach(l -> l.event(event));
Yi Tseng2a340f72018-11-02 16:52:47 -0700179 }
Yi Tseng2a340f72018-11-02 16:52:47 -0700180 }
181
182 private <U> U withDeviceLock(Supplier<U> task, DeviceId deviceId) {
183 final Lock lock = stripedLocks.get(deviceId);
184 lock.lock();
185 try {
186 return task.get();
187 } finally {
188 lock.unlock();
189 }
190 }
Carmelo Casconea71b8492018-12-17 17:47:50 -0800191
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700192 private String clientName(DeviceId deviceId) {
193 return format("%s client for %s", serviceName, deviceId);
Carmelo Casconea71b8492018-12-17 17:47:50 -0800194 }
Yi Tseng2a340f72018-11-02 16:52:47 -0700195}