blob: 9fb3625ad0efddeb8f7fe6f25f5783a8919b19d9 [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
Carmelo Casconea71b8492018-12-17 17:47:50 -0800104 final C client;
105 try {
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700106 client = createClientInstance(deviceId, channel);
Carmelo Casconea71b8492018-12-17 17:47:50 -0800107 } catch (Throwable e) {
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700108 log.error("Exception while creating {}", clientName(deviceId), e);
Yi Tseng2a340f72018-11-02 16:52:47 -0700109 return false;
110 }
Carmelo Casconea71b8492018-12-17 17:47:50 -0800111
112 if (client == null) {
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700113 log.error("Unable to create {}, implementation returned null...",
114 clientName(deviceId));
Carmelo Casconea71b8492018-12-17 17:47:50 -0800115 return false;
116 }
117
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700118 clients.put(deviceId, client);
Yi Tseng2a340f72018-11-02 16:52:47 -0700119 return true;
120 }
121
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700122 protected abstract C createClientInstance(DeviceId deviceId, ManagedChannel channel);
Yi Tseng2a340f72018-11-02 16:52:47 -0700123
124 @Override
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700125 public C get(DeviceId deviceId) {
Yi Tseng2a340f72018-11-02 16:52:47 -0700126 checkNotNull(deviceId);
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700127 return withDeviceLock(() -> clients.get(deviceId), deviceId);
Yi Tseng2a340f72018-11-02 16:52:47 -0700128 }
129
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700130 @Override
131 public void remove(DeviceId deviceId) {
132 checkNotNull(deviceId);
133 withDeviceLock(() -> {
134 final C client = clients.remove(deviceId);
135 if (client != null) {
136 client.shutdown();
137 }
Yi Tseng2a340f72018-11-02 16:52:47 -0700138 return null;
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700139 }, deviceId);
Yi Tseng2a340f72018-11-02 16:52:47 -0700140 }
141
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800142 @Override
143 public void addDeviceAgentListener(DeviceId deviceId, ProviderId providerId, DeviceAgentListener listener) {
144 checkNotNull(deviceId, "deviceId cannot be null");
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700145 checkNotNull(providerId, "providerId cannot be null");
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800146 checkNotNull(listener, "listener cannot be null");
147 deviceAgentListeners.putIfAbsent(deviceId, Maps.newConcurrentMap());
148 deviceAgentListeners.get(deviceId).put(providerId, listener);
149 }
150
151 @Override
152 public void removeDeviceAgentListener(DeviceId deviceId, ProviderId providerId) {
153 checkNotNull(deviceId, "deviceId cannot be null");
154 checkNotNull(providerId, "listener cannot be null");
155 deviceAgentListeners.computeIfPresent(deviceId, (did, listeners) -> {
156 listeners.remove(providerId);
157 return listeners.isEmpty() ? null : listeners;
158 });
159 }
160
161 public void postEvent(E event) {
162 checkNotNull(event);
163 post(event);
164 }
165
166 public void postEvent(DeviceAgentEvent event) {
167 // We should have only one event delivery mechanism. We have two now
168 // because we have two different types of events, DeviceAgentEvent and
169 // controller/protocol specific ones (e.g. P4Runtime or gNMI).
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700170 // TODO: extend device agent event to allow delivery of protocol-specific
Carmelo Cascone3977ea42019-02-28 13:43:42 -0800171 // events, e.g. packet-in
172 checkNotNull(event);
173 if (deviceAgentListeners.containsKey(event.subject())) {
174 deviceAgentListeners.get(event.subject()).values()
175 .forEach(l -> l.event(event));
Yi Tseng2a340f72018-11-02 16:52:47 -0700176 }
Yi Tseng2a340f72018-11-02 16:52:47 -0700177 }
178
179 private <U> U withDeviceLock(Supplier<U> task, DeviceId deviceId) {
180 final Lock lock = stripedLocks.get(deviceId);
181 lock.lock();
182 try {
183 return task.get();
184 } finally {
185 lock.unlock();
186 }
187 }
Carmelo Casconea71b8492018-12-17 17:47:50 -0800188
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700189 private String clientName(DeviceId deviceId) {
190 return format("%s client for %s", serviceName, deviceId);
Carmelo Casconea71b8492018-12-17 17:47:50 -0800191 }
Yi Tseng2a340f72018-11-02 16:52:47 -0700192}