blob: cc06d9e84711dc1f0747e3eafb5aacc4d842df68 [file] [log] [blame]
Carmelo Casconec2be50a2019-04-10 00:15:39 -07001/*
2 * Copyright 2019-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.utils;
18
19import com.google.common.util.concurrent.Striped;
Carmelo Cascone4b616312019-04-17 14:15:45 -070020import io.grpc.ConnectivityState;
Carmelo Casconec2be50a2019-04-10 00:15:39 -070021import io.grpc.ManagedChannel;
22import org.onosproject.grpc.api.GrpcChannelController;
23import org.onosproject.grpc.api.GrpcClient;
24import org.onosproject.grpc.api.GrpcClientController;
25import org.onosproject.net.DeviceId;
26import org.onosproject.net.device.DeviceAgentListener;
27import org.onosproject.net.device.DeviceHandshaker;
28import org.onosproject.net.provider.ProviderId;
29
30import java.net.URI;
31import java.util.concurrent.CompletableFuture;
32import java.util.concurrent.locks.Lock;
33
34import static java.util.concurrent.CompletableFuture.completedFuture;
35
36/**
37 * Abstract implementation of DeviceHandshaker that uses gRPC to establish a
38 * connection to the device.
39 *
40 * @param <CLIENT> gRPC client class
41 * @param <CTRL> gRPC controller class
42 */
43public abstract class AbstractGrpcHandshaker
44 <CLIENT extends GrpcClient, CTRL extends GrpcClientController<CLIENT>>
45 extends AbstractGrpcHandlerBehaviour<CLIENT, CTRL>
46 implements DeviceHandshaker {
47
48 /**
49 * Creates a new instance of this behaviour for the given gRPC controller
50 * class.
51 *
52 * @param controllerClass gRPC controller class
53 */
54 public AbstractGrpcHandshaker(Class<CTRL> controllerClass) {
55 super(controllerClass);
56 }
57
58 private static final Striped<Lock> DEVICE_LOCKS = Striped.lock(10);
59
60 @Override
61 public boolean connect() {
62 final GrpcChannelController channelController = handler().get(
63 GrpcChannelController.class);
64 final CTRL clientController = handler().get(controllerClass);
65 final DeviceId deviceId = data().deviceId();
66
67 final URI netcfgUri = mgmtUriFromNetcfg();
68 if (netcfgUri == null) {
69 return false;
70 }
71
72 DEVICE_LOCKS.get(deviceId).lock();
73 try {
74 if (clientController.get(deviceId) != null) {
75 throw new IllegalStateException(
76 "A client for this device already exists");
77 }
78
79 // Create or get an existing channel. We support sharing the same
80 // channel by different drivers for the same device.
81 final ManagedChannel channel;
82 final URI existingChannelUri = CHANNEL_URIS.get(deviceId);
83 if (existingChannelUri != null) {
84 if (!existingChannelUri.equals(netcfgUri)) {
85 throw new IllegalStateException(
86 "A gRPC channel with different URI already " +
87 "exists for this device");
88 }
89 channel = channelController.get(existingChannelUri)
90 .orElseThrow(() -> new IllegalStateException(
91 "Missing gRPC channel in controller"));
92 } else {
Carmelo Casconeb9536692019-05-28 18:15:23 -070093 channel = channelController.create(netcfgUri);
Carmelo Casconec2be50a2019-04-10 00:15:39 -070094 // Store channel URI for future use.
95 CHANNEL_URIS.put(deviceId, netcfgUri);
96 // Trigger connection.
97 channel.getState(true);
98 }
99
100 return clientController.create(deviceId, channel);
101 } finally {
102 DEVICE_LOCKS.get(deviceId).unlock();
103 }
104 }
105
106 @Override
107 public boolean hasConnection() {
108 final DeviceId deviceId = data().deviceId();
109 final URI netcfgUri = mgmtUriFromNetcfg();
110 // If a client already exists for this device, but the netcfg with the
111 // server endpoints has changed, this will return false.
112 DEVICE_LOCKS.get(deviceId).lock();
113 try {
114 final URI existingChannelUri = CHANNEL_URIS.get(deviceId);
115 return existingChannelUri != null &&
116 existingChannelUri.equals(netcfgUri) &&
117 handler().get(GrpcChannelController.class)
118 .get(existingChannelUri).isPresent() &&
119 handler().get(controllerClass)
120 .get(deviceId) != null;
121 } finally {
122 DEVICE_LOCKS.get(deviceId).unlock();
123 }
124 }
125
126 @Override
127 public void disconnect() {
128 final DeviceId deviceId = data().deviceId();
129 final URI netcfgUri = mgmtUriFromNetcfg();
130 // This removes any clients and channels associated with this device ID.
131 DEVICE_LOCKS.get(deviceId).lock();
132 try {
133 final URI existingChannelUri = CHANNEL_URIS.remove(deviceId);
134 handler().get(controllerClass).remove(deviceId);
135 if (existingChannelUri != null) {
136 handler().get(GrpcChannelController.class).destroy(existingChannelUri);
137 }
138 if (netcfgUri != null) {
139 // This should not be needed if we are sure there can never be
140 // two channels for the same device.
141 handler().get(GrpcChannelController.class).destroy(netcfgUri);
142 }
143 } finally {
144 DEVICE_LOCKS.get(deviceId).unlock();
145 }
146 }
147
148 @Override
149 public boolean isReachable() {
150 return setupBehaviour("isReachable()") && client.isServerReachable();
151 }
152
153 @Override
154 public CompletableFuture<Boolean> probeReachability() {
155 if (!setupBehaviour("probeReachability()")) {
156 return completedFuture(false);
157 }
Carmelo Cascone4b616312019-04-17 14:15:45 -0700158 resetChannelConnectBackoffIfNeeded();
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700159 return client.probeService();
160 }
161
162 @Override
163 public void addDeviceAgentListener(ProviderId providerId, DeviceAgentListener listener) {
164 // Don't use controller/deviceId class variables as they might be uninitialized.
165 handler().get(controllerClass)
166 .addDeviceAgentListener(data().deviceId(), providerId, listener);
167 }
168
169 @Override
170 public void removeDeviceAgentListener(ProviderId providerId) {
171 // Don't use controller/deviceId class variable as they might be uninitialized.
172 handler().get(controllerClass)
173 .removeDeviceAgentListener(data().deviceId(), providerId);
174 }
Carmelo Cascone4b616312019-04-17 14:15:45 -0700175
Carmelo Casconeb9536692019-05-28 18:15:23 -0700176 private void resetChannelConnectBackoffIfNeeded() {
Carmelo Cascone4b616312019-04-17 14:15:45 -0700177 // Stimulate channel reconnect if in failure state.
178 final ManagedChannel channel = getExistingChannel();
179 if (channel == null) {
180 // Where did the channel go?
181 return;
182 }
183 if (channel.getState(false)
Carmelo Casconeb9536692019-05-28 18:15:23 -0700184 .equals(ConnectivityState.TRANSIENT_FAILURE)) {
Carmelo Cascone4b616312019-04-17 14:15:45 -0700185 channel.resetConnectBackoff();
186 }
187 }
188
189 private ManagedChannel getExistingChannel() {
190 final DeviceId deviceId = data().deviceId();
191 if (CHANNEL_URIS.containsKey(deviceId)) {
192 return handler().get(GrpcChannelController.class)
193 .get(CHANNEL_URIS.get(deviceId)).orElse(null);
194 }
195 return null;
196 }
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700197}