Carmelo Cascone
Committed by Gerrit Code Review

ONOS-4118 Added synchronization and resiliency to Bmv2ThriftClient

Due to the multi-threaded nature of drivers, calls to a Bmv2ThriftClient
could result in a race condition if not properly synchronized. Also,
once open, transport session might close due to several reasons. Now the
client calls are synchronized and automatically wrapped in a try/catch
that tries to re-open the session for fixed number of times before
giving up.

Change-Id: I5dcdd5a6304406dc6d9d3a0ccf7f16cdbf3b9573
...@@ -20,6 +20,7 @@ import com.google.common.collect.Lists; ...@@ -20,6 +20,7 @@ import com.google.common.collect.Lists;
20 import com.google.common.collect.Maps; 20 import com.google.common.collect.Maps;
21 import org.apache.commons.lang3.tuple.Pair; 21 import org.apache.commons.lang3.tuple.Pair;
22 import org.apache.commons.lang3.tuple.Triple; 22 import org.apache.commons.lang3.tuple.Triple;
23 +import org.onosproject.bmv2.api.runtime.Bmv2Client;
23 import org.onosproject.bmv2.api.runtime.Bmv2MatchKey; 24 import org.onosproject.bmv2.api.runtime.Bmv2MatchKey;
24 import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException; 25 import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException;
25 import org.onosproject.bmv2.api.runtime.Bmv2TableEntry; 26 import org.onosproject.bmv2.api.runtime.Bmv2TableEntry;
...@@ -84,7 +85,7 @@ public class Bmv2FlowRuleDriver extends AbstractHandlerBehaviour ...@@ -84,7 +85,7 @@ public class Bmv2FlowRuleDriver extends AbstractHandlerBehaviour
84 85
85 DeviceId deviceId = handler().data().deviceId(); 86 DeviceId deviceId = handler().data().deviceId();
86 87
87 - Bmv2ThriftClient deviceClient; 88 + Bmv2Client deviceClient;
88 try { 89 try {
89 deviceClient = Bmv2ThriftClient.of(deviceId); 90 deviceClient = Bmv2ThriftClient.of(deviceId);
90 } catch (Bmv2RuntimeException e) { 91 } catch (Bmv2RuntimeException e) {
......
...@@ -18,6 +18,7 @@ package org.onosproject.drivers.bmv2; ...@@ -18,6 +18,7 @@ package org.onosproject.drivers.bmv2;
18 18
19 import com.google.common.collect.ImmutableList; 19 import com.google.common.collect.ImmutableList;
20 import com.google.common.collect.Lists; 20 import com.google.common.collect.Lists;
21 +import org.onosproject.bmv2.api.runtime.Bmv2Client;
21 import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException; 22 import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException;
22 import org.onosproject.bmv2.ctl.Bmv2ThriftClient; 23 import org.onosproject.bmv2.ctl.Bmv2ThriftClient;
23 import org.onosproject.net.DefaultAnnotations; 24 import org.onosproject.net.DefaultAnnotations;
...@@ -41,7 +42,7 @@ public class Bmv2PortGetterDriver extends AbstractHandlerBehaviour ...@@ -41,7 +42,7 @@ public class Bmv2PortGetterDriver extends AbstractHandlerBehaviour
41 42
42 @Override 43 @Override
43 public List<PortDescription> getPorts() { 44 public List<PortDescription> getPorts() {
44 - Bmv2ThriftClient deviceClient; 45 + Bmv2Client deviceClient;
45 try { 46 try {
46 deviceClient = Bmv2ThriftClient.of(handler().data().deviceId()); 47 deviceClient = Bmv2ThriftClient.of(handler().data().deviceId());
47 } catch (Bmv2RuntimeException e) { 48 } catch (Bmv2RuntimeException e) {
......
1 +/*
2 + * Copyright 2016-present Open Networking Laboratory
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 +
17 +package org.onosproject.bmv2.api.runtime;
18 +
19 +import java.util.Collection;
20 +
21 +/**
22 + * RPC client to control a BMv2 device.
23 + */
24 +public interface Bmv2Client {
25 + /**
26 + * Adds a new table entry.
27 + *
28 + * @param entry a table entry value
29 + * @return table-specific entry ID
30 + * @throws Bmv2RuntimeException if any error occurs
31 + */
32 + long addTableEntry(Bmv2TableEntry entry) throws Bmv2RuntimeException;
33 +
34 + /**
35 + * Modifies a currently installed entry by updating its action.
36 + *
37 + * @param tableName string value of table name
38 + * @param entryId long value of entry ID
39 + * @param action an action value
40 + * @throws Bmv2RuntimeException if any error occurs
41 + */
42 + void modifyTableEntry(String tableName,
43 + long entryId, Bmv2Action action)
44 + throws Bmv2RuntimeException;
45 +
46 + /**
47 + * Deletes currently installed entry.
48 + *
49 + * @param tableName string value of table name
50 + * @param entryId long value of entry ID
51 + * @throws Bmv2RuntimeException if any error occurs
52 + */
53 + void deleteTableEntry(String tableName,
54 + long entryId) throws Bmv2RuntimeException;
55 +
56 + /**
57 + * Sets table default action.
58 + *
59 + * @param tableName string value of table name
60 + * @param action an action value
61 + * @throws Bmv2RuntimeException if any error occurs
62 + */
63 + void setTableDefaultAction(String tableName, Bmv2Action action)
64 + throws Bmv2RuntimeException;
65 +
66 + /**
67 + * Returns information of the ports currently configured in the switch.
68 + *
69 + * @return collection of port information
70 + * @throws Bmv2RuntimeException if any error occurs
71 + */
72 + Collection<Bmv2PortInfo> getPortsInfo() throws Bmv2RuntimeException;
73 +
74 + /**
75 + * Return a string representation of a table content.
76 + *
77 + * @param tableName string value of table name
78 + * @return table string dump
79 + * @throws Bmv2RuntimeException if any error occurs
80 + */
81 + String dumpTable(String tableName) throws Bmv2RuntimeException;
82 +
83 + /**
84 + * Reset the state of the switch (e.g. delete all entries, etc.).
85 + *
86 + * @throws Bmv2RuntimeException if any error occurs
87 + */
88 + void resetState() throws Bmv2RuntimeException;
89 +}
...@@ -31,13 +31,13 @@ import org.apache.thrift.protocol.TProtocol; ...@@ -31,13 +31,13 @@ import org.apache.thrift.protocol.TProtocol;
31 import org.apache.thrift.transport.TSocket; 31 import org.apache.thrift.transport.TSocket;
32 import org.apache.thrift.transport.TTransport; 32 import org.apache.thrift.transport.TTransport;
33 import org.apache.thrift.transport.TTransportException; 33 import org.apache.thrift.transport.TTransportException;
34 -import org.onlab.util.ImmutableByteSequence;
35 import org.onosproject.bmv2.api.runtime.Bmv2Action; 34 import org.onosproject.bmv2.api.runtime.Bmv2Action;
35 +import org.onosproject.bmv2.api.runtime.Bmv2Client;
36 import org.onosproject.bmv2.api.runtime.Bmv2ExactMatchParam; 36 import org.onosproject.bmv2.api.runtime.Bmv2ExactMatchParam;
37 -import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException;
38 import org.onosproject.bmv2.api.runtime.Bmv2LpmMatchParam; 37 import org.onosproject.bmv2.api.runtime.Bmv2LpmMatchParam;
39 import org.onosproject.bmv2.api.runtime.Bmv2MatchKey; 38 import org.onosproject.bmv2.api.runtime.Bmv2MatchKey;
40 import org.onosproject.bmv2.api.runtime.Bmv2PortInfo; 39 import org.onosproject.bmv2.api.runtime.Bmv2PortInfo;
40 +import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException;
41 import org.onosproject.bmv2.api.runtime.Bmv2TableEntry; 41 import org.onosproject.bmv2.api.runtime.Bmv2TableEntry;
42 import org.onosproject.bmv2.api.runtime.Bmv2TernaryMatchParam; 42 import org.onosproject.bmv2.api.runtime.Bmv2TernaryMatchParam;
43 import org.onosproject.bmv2.api.runtime.Bmv2ValidMatchParam; 43 import org.onosproject.bmv2.api.runtime.Bmv2ValidMatchParam;
...@@ -51,6 +51,8 @@ import org.p4.bmv2.thrift.BmMatchParamType; ...@@ -51,6 +51,8 @@ import org.p4.bmv2.thrift.BmMatchParamType;
51 import org.p4.bmv2.thrift.BmMatchParamValid; 51 import org.p4.bmv2.thrift.BmMatchParamValid;
52 import org.p4.bmv2.thrift.DevMgrPortInfo; 52 import org.p4.bmv2.thrift.DevMgrPortInfo;
53 import org.p4.bmv2.thrift.Standard; 53 import org.p4.bmv2.thrift.Standard;
54 +import org.slf4j.Logger;
55 +import org.slf4j.LoggerFactory;
54 56
55 import java.nio.ByteBuffer; 57 import java.nio.ByteBuffer;
56 import java.util.Collection; 58 import java.util.Collection;
...@@ -60,38 +62,43 @@ import java.util.concurrent.TimeUnit; ...@@ -60,38 +62,43 @@ import java.util.concurrent.TimeUnit;
60 import java.util.stream.Collectors; 62 import java.util.stream.Collectors;
61 63
62 import static com.google.common.base.Preconditions.checkNotNull; 64 import static com.google.common.base.Preconditions.checkNotNull;
65 +import static org.onosproject.bmv2.ctl.SafeThriftClient.Options;
63 66
64 /** 67 /**
65 * Implementation of a Thrift client to control the Bmv2 switch. 68 * Implementation of a Thrift client to control the Bmv2 switch.
66 */ 69 */
67 -public final class Bmv2ThriftClient { 70 +public final class Bmv2ThriftClient implements Bmv2Client {
68 - /* 71 +
69 - FIXME: derive context_id from device id 72 + private static final Logger LOG =
70 - Using different context id values should serve to control different 73 + LoggerFactory.getLogger(Bmv2ThriftClient.class);
71 - switches responding to the same IP address and port 74 +
72 - */ 75 + // FIXME: make context_id arbitrary for each call
76 + // See: https://github.com/p4lang/behavioral-model/blob/master/modules/bm_sim/include/bm_sim/context.h
73 private static final int CONTEXT_ID = 0; 77 private static final int CONTEXT_ID = 0;
74 - /* 78 + // Seconds after a client is expired (and connection closed) in the cache.
75 - Static transport/client cache: 79 + private static final int CLIENT_CACHE_TIMEOUT = 60;
76 - - avoids opening a new transport session when there's one already open 80 + // Number of connection retries after a network error.
77 - - close the connection after a predefined timeout of 5 seconds 81 + private static final int NUM_CONNECTION_RETRIES = 10;
78 - */ 82 + // Time between retries in milliseconds.
79 - private static LoadingCache<DeviceId, Bmv2ThriftClient> 83 + private static final int TIME_BETWEEN_RETRIES = 200;
80 - clientCache = CacheBuilder.newBuilder() 84 +
81 - .expireAfterAccess(5, TimeUnit.SECONDS) 85 + // Static client cache where clients are removed after a predefined timeout.
86 + private static final LoadingCache<DeviceId, Bmv2ThriftClient>
87 + CLIENT_CACHE = CacheBuilder.newBuilder()
88 + .expireAfterAccess(CLIENT_CACHE_TIMEOUT, TimeUnit.SECONDS)
82 .removalListener(new ClientRemovalListener()) 89 .removalListener(new ClientRemovalListener())
83 .build(new ClientLoader()); 90 .build(new ClientLoader());
84 private final Standard.Iface stdClient; 91 private final Standard.Iface stdClient;
85 private final TTransport transport; 92 private final TTransport transport;
93 + private final DeviceId deviceId;
86 94
87 // ban constructor 95 // ban constructor
88 - private Bmv2ThriftClient(TTransport transport, Standard.Iface stdClient) { 96 + private Bmv2ThriftClient(DeviceId deviceId, TTransport transport, Standard.Iface stdClient) {
97 + this.deviceId = deviceId;
89 this.transport = transport; 98 this.transport = transport;
90 this.stdClient = stdClient; 99 this.stdClient = stdClient;
91 - }
92 100
93 - private void closeTransport() { 101 + LOG.debug("New client created! > deviceId={}", deviceId);
94 - this.transport.close();
95 } 102 }
96 103
97 /** 104 /**
...@@ -104,8 +111,10 @@ public final class Bmv2ThriftClient { ...@@ -104,8 +111,10 @@ public final class Bmv2ThriftClient {
104 public static Bmv2ThriftClient of(DeviceId deviceId) throws Bmv2RuntimeException { 111 public static Bmv2ThriftClient of(DeviceId deviceId) throws Bmv2RuntimeException {
105 try { 112 try {
106 checkNotNull(deviceId, "deviceId cannot be null"); 113 checkNotNull(deviceId, "deviceId cannot be null");
107 - return clientCache.get(deviceId); 114 + LOG.debug("Getting a client from cache... > deviceId{}", deviceId);
115 + return CLIENT_CACHE.get(deviceId);
108 } catch (ExecutionException e) { 116 } catch (ExecutionException e) {
117 + LOG.debug("Exception while getting a client from cache: {} > ", e, deviceId);
109 throw new Bmv2RuntimeException(e.getMessage(), e.getCause()); 118 throw new Bmv2RuntimeException(e.getMessage(), e.getCause());
110 } 119 }
111 } 120 }
...@@ -120,9 +129,13 @@ public final class Bmv2ThriftClient { ...@@ -120,9 +129,13 @@ public final class Bmv2ThriftClient {
120 public static boolean ping(DeviceId deviceId) { 129 public static boolean ping(DeviceId deviceId) {
121 // poll ports status as workaround to assess device reachability 130 // poll ports status as workaround to assess device reachability
122 try { 131 try {
123 - of(deviceId).stdClient.bm_dev_mgr_show_ports(); 132 + LOG.debug("Pinging device... > deviceId={}", deviceId);
133 + Bmv2ThriftClient client = of(deviceId);
134 + client.stdClient.bm_dev_mgr_show_ports();
135 + LOG.debug("Device reachable! > deviceId={}", deviceId);
124 return true; 136 return true;
125 } catch (TException | Bmv2RuntimeException e) { 137 } catch (TException | Bmv2RuntimeException e) {
138 + LOG.debug("Device NOT reachable! > deviceId={}", deviceId);
126 return false; 139 return false;
127 } 140 }
128 } 141 }
...@@ -156,32 +169,34 @@ public final class Bmv2ThriftClient { ...@@ -156,32 +169,34 @@ public final class Bmv2ThriftClient {
156 private static List<BmMatchParam> buildMatchParamsList(Bmv2MatchKey matchKey) { 169 private static List<BmMatchParam> buildMatchParamsList(Bmv2MatchKey matchKey) {
157 List<BmMatchParam> paramsList = Lists.newArrayList(); 170 List<BmMatchParam> paramsList = Lists.newArrayList();
158 matchKey.matchParams().forEach(x -> { 171 matchKey.matchParams().forEach(x -> {
172 + ByteBuffer value;
173 + ByteBuffer mask;
159 switch (x.type()) { 174 switch (x.type()) {
160 case EXACT: 175 case EXACT:
176 + value = ByteBuffer.wrap(((Bmv2ExactMatchParam) x).value().asArray());
161 paramsList.add( 177 paramsList.add(
162 new BmMatchParam(BmMatchParamType.EXACT) 178 new BmMatchParam(BmMatchParamType.EXACT)
163 - .setExact(new BmMatchParamExact( 179 + .setExact(new BmMatchParamExact(value)));
164 - ((Bmv2ExactMatchParam) x).value().asReadOnlyBuffer())));
165 break; 180 break;
166 case TERNARY: 181 case TERNARY:
182 + value = ByteBuffer.wrap(((Bmv2TernaryMatchParam) x).value().asArray());
183 + mask = ByteBuffer.wrap(((Bmv2TernaryMatchParam) x).mask().asArray());
167 paramsList.add( 184 paramsList.add(
168 new BmMatchParam(BmMatchParamType.TERNARY) 185 new BmMatchParam(BmMatchParamType.TERNARY)
169 - .setTernary(new BmMatchParamTernary( 186 + .setTernary(new BmMatchParamTernary(value, mask)));
170 - ((Bmv2TernaryMatchParam) x).value().asReadOnlyBuffer(),
171 - ((Bmv2TernaryMatchParam) x).mask().asReadOnlyBuffer())));
172 break; 187 break;
173 case LPM: 188 case LPM:
189 + value = ByteBuffer.wrap(((Bmv2LpmMatchParam) x).value().asArray());
190 + int prefixLength = ((Bmv2LpmMatchParam) x).prefixLength();
174 paramsList.add( 191 paramsList.add(
175 new BmMatchParam(BmMatchParamType.LPM) 192 new BmMatchParam(BmMatchParamType.LPM)
176 - .setLpm(new BmMatchParamLPM( 193 + .setLpm(new BmMatchParamLPM(value, prefixLength)));
177 - ((Bmv2LpmMatchParam) x).value().asReadOnlyBuffer(),
178 - ((Bmv2LpmMatchParam) x).prefixLength())));
179 break; 194 break;
180 case VALID: 195 case VALID:
196 + boolean flag = ((Bmv2ValidMatchParam) x).flag();
181 paramsList.add( 197 paramsList.add(
182 new BmMatchParam(BmMatchParamType.VALID) 198 new BmMatchParam(BmMatchParamType.VALID)
183 - .setValid(new BmMatchParamValid( 199 + .setValid(new BmMatchParamValid(flag)));
184 - ((Bmv2ValidMatchParam) x).flag())));
185 break; 200 break;
186 default: 201 default:
187 // should never be here 202 // should never be here
...@@ -198,21 +213,26 @@ public final class Bmv2ThriftClient { ...@@ -198,21 +213,26 @@ public final class Bmv2ThriftClient {
198 * @return list of ByteBuffers 213 * @return list of ByteBuffers
199 */ 214 */
200 private static List<ByteBuffer> buildActionParamsList(Bmv2Action action) { 215 private static List<ByteBuffer> buildActionParamsList(Bmv2Action action) {
201 - return action.parameters() 216 + List<ByteBuffer> buffers = Lists.newArrayList();
202 - .stream() 217 + action.parameters().forEach(p -> buffers.add(ByteBuffer.wrap(p.asArray())));
203 - .map(ImmutableByteSequence::asReadOnlyBuffer) 218 + return buffers;
204 - .collect(Collectors.toList());
205 } 219 }
206 220
207 - /** 221 + private void closeTransport() {
208 - * Adds a new table entry. 222 + LOG.debug("Closing transport session... > deviceId={}", deviceId);
209 - * 223 + if (this.transport.isOpen()) {
210 - * @param entry a table entry value 224 + this.transport.close();
211 - * @return table-specific entry ID 225 + LOG.debug("Transport session closed! > deviceId={}", deviceId);
212 - * @throws Bmv2RuntimeException if any error occurs 226 + } else {
213 - */ 227 + LOG.debug("Transport session was already closed! deviceId={}", deviceId);
228 + }
229 + }
230 +
231 + @Override
214 public final long addTableEntry(Bmv2TableEntry entry) throws Bmv2RuntimeException { 232 public final long addTableEntry(Bmv2TableEntry entry) throws Bmv2RuntimeException {
215 233
234 + LOG.debug("Adding table entry... > deviceId={}, entry={}", deviceId, entry);
235 +
216 long entryId = -1; 236 long entryId = -1;
217 237
218 try { 238 try {
...@@ -237,34 +257,33 @@ public final class Bmv2ThriftClient { ...@@ -237,34 +257,33 @@ public final class Bmv2ThriftClient {
237 CONTEXT_ID, entry.tableName(), entryId, msTimeout); 257 CONTEXT_ID, entry.tableName(), entryId, msTimeout);
238 } 258 }
239 259
260 + LOG.debug("Table entry added! > deviceId={}, entryId={}/{}", deviceId, entry.tableName(), entryId);
261 +
240 return entryId; 262 return entryId;
241 263
242 } catch (TException e) { 264 } catch (TException e) {
265 + LOG.debug("Exception while adding table entry: {} > deviceId={}, tableName={}",
266 + e, deviceId, entry.tableName());
243 if (entryId != -1) { 267 if (entryId != -1) {
268 + // entry is in inconsistent state (unable to add timeout), remove it
244 try { 269 try {
245 - stdClient.bm_mt_delete_entry( 270 + deleteTableEntry(entry.tableName(), entryId);
246 - CONTEXT_ID, entry.tableName(), entryId); 271 + } catch (Bmv2RuntimeException e1) {
247 - } catch (TException e1) { 272 + LOG.debug("Unable to remove failed table entry: {} > deviceId={}, tableName={}",
248 - // this should never happen as we know the entry is there 273 + e1, deviceId, entry.tableName());
249 - throw new Bmv2RuntimeException(e1.getMessage(), e1);
250 } 274 }
251 } 275 }
252 throw new Bmv2RuntimeException(e.getMessage(), e); 276 throw new Bmv2RuntimeException(e.getMessage(), e);
253 } 277 }
254 } 278 }
255 279
256 - /** 280 + @Override
257 - * Modifies a currently installed entry by updating its action.
258 - *
259 - * @param tableName string value of table name
260 - * @param entryId long value of entry ID
261 - * @param action an action value
262 - * @throws Bmv2RuntimeException if any error occurs
263 - */
264 public final void modifyTableEntry(String tableName, 281 public final void modifyTableEntry(String tableName,
265 long entryId, Bmv2Action action) 282 long entryId, Bmv2Action action)
266 throws Bmv2RuntimeException { 283 throws Bmv2RuntimeException {
267 284
285 + LOG.debug("Modifying table entry... > deviceId={}, entryId={}/{}", deviceId, tableName, entryId);
286 +
268 try { 287 try {
269 stdClient.bm_mt_modify_entry( 288 stdClient.bm_mt_modify_entry(
270 CONTEXT_ID, 289 CONTEXT_ID,
...@@ -272,57 +291,55 @@ public final class Bmv2ThriftClient { ...@@ -272,57 +291,55 @@ public final class Bmv2ThriftClient {
272 entryId, 291 entryId,
273 action.name(), 292 action.name(),
274 buildActionParamsList(action)); 293 buildActionParamsList(action));
294 + LOG.debug("Table entry modified! > deviceId={}, entryId={}/{}", deviceId, tableName, entryId);
275 } catch (TException e) { 295 } catch (TException e) {
296 + LOG.debug("Exception while modifying table entry: {} > deviceId={}, entryId={}/{}",
297 + e, deviceId, tableName, entryId);
276 throw new Bmv2RuntimeException(e.getMessage(), e); 298 throw new Bmv2RuntimeException(e.getMessage(), e);
277 } 299 }
278 } 300 }
279 301
280 - /** 302 + @Override
281 - * Deletes currently installed entry.
282 - *
283 - * @param tableName string value of table name
284 - * @param entryId long value of entry ID
285 - * @throws Bmv2RuntimeException if any error occurs
286 - */
287 public final void deleteTableEntry(String tableName, 303 public final void deleteTableEntry(String tableName,
288 long entryId) throws Bmv2RuntimeException { 304 long entryId) throws Bmv2RuntimeException {
289 305
306 + LOG.debug("Deleting table entry... > deviceId={}, entryId={}/{}", deviceId, tableName, entryId);
307 +
290 try { 308 try {
291 stdClient.bm_mt_delete_entry(CONTEXT_ID, tableName, entryId); 309 stdClient.bm_mt_delete_entry(CONTEXT_ID, tableName, entryId);
310 + LOG.debug("Table entry deleted! > deviceId={}, entryId={}/{}", deviceId, tableName, entryId);
292 } catch (TException e) { 311 } catch (TException e) {
312 + LOG.debug("Exception while deleting table entry: {} > deviceId={}, entryId={}/{}",
313 + e, deviceId, tableName, entryId);
293 throw new Bmv2RuntimeException(e.getMessage(), e); 314 throw new Bmv2RuntimeException(e.getMessage(), e);
294 } 315 }
295 } 316 }
296 317
297 - /** 318 + @Override
298 - * Sets table default action.
299 - *
300 - * @param tableName string value of table name
301 - * @param action an action value
302 - * @throws Bmv2RuntimeException if any error occurs
303 - */
304 public final void setTableDefaultAction(String tableName, Bmv2Action action) 319 public final void setTableDefaultAction(String tableName, Bmv2Action action)
305 throws Bmv2RuntimeException { 320 throws Bmv2RuntimeException {
306 321
322 + LOG.debug("Setting table default... > deviceId={}, tableName={}, action={}", deviceId, tableName, action);
323 +
307 try { 324 try {
308 stdClient.bm_mt_set_default_action( 325 stdClient.bm_mt_set_default_action(
309 CONTEXT_ID, 326 CONTEXT_ID,
310 tableName, 327 tableName,
311 action.name(), 328 action.name(),
312 buildActionParamsList(action)); 329 buildActionParamsList(action));
330 + LOG.debug("Table default set! > deviceId={}, tableName={}, action={}", deviceId, tableName, action);
313 } catch (TException e) { 331 } catch (TException e) {
332 + LOG.debug("Exception while setting table default : {} > deviceId={}, tableName={}, action={}",
333 + e, deviceId, tableName, action);
314 throw new Bmv2RuntimeException(e.getMessage(), e); 334 throw new Bmv2RuntimeException(e.getMessage(), e);
315 } 335 }
316 } 336 }
317 337
318 - /** 338 + @Override
319 - * Returns information of the ports currently configured in the switch.
320 - *
321 - * @return collection of port information
322 - * @throws Bmv2RuntimeException if any error occurs
323 - */
324 public Collection<Bmv2PortInfo> getPortsInfo() throws Bmv2RuntimeException { 339 public Collection<Bmv2PortInfo> getPortsInfo() throws Bmv2RuntimeException {
325 340
341 + LOG.debug("Retrieving port info... > deviceId={}", deviceId);
342 +
326 try { 343 try {
327 List<DevMgrPortInfo> portInfos = stdClient.bm_dev_mgr_show_ports(); 344 List<DevMgrPortInfo> portInfos = stdClient.bm_dev_mgr_show_ports();
328 345
...@@ -333,39 +350,42 @@ public final class Bmv2ThriftClient { ...@@ -333,39 +350,42 @@ public final class Bmv2ThriftClient {
333 .map(Bmv2PortInfo::new) 350 .map(Bmv2PortInfo::new)
334 .collect(Collectors.toList())); 351 .collect(Collectors.toList()));
335 352
353 + LOG.debug("Port info retrieved! > deviceId={}, portInfos={}", deviceId, bmv2PortInfos);
354 +
336 return bmv2PortInfos; 355 return bmv2PortInfos;
337 356
338 } catch (TException e) { 357 } catch (TException e) {
358 + LOG.debug("Exception while retrieving port info: {} > deviceId={}", e, deviceId);
339 throw new Bmv2RuntimeException(e.getMessage(), e); 359 throw new Bmv2RuntimeException(e.getMessage(), e);
340 } 360 }
341 } 361 }
342 362
343 - /** 363 + @Override
344 - * Return a string representation of a table content.
345 - *
346 - * @param tableName string value of table name
347 - * @return table string dump
348 - * @throws Bmv2RuntimeException if any error occurs
349 - */
350 public String dumpTable(String tableName) throws Bmv2RuntimeException { 364 public String dumpTable(String tableName) throws Bmv2RuntimeException {
351 365
366 + LOG.debug("Retrieving table dump... > deviceId={}, tableName={}", deviceId, tableName);
367 +
352 try { 368 try {
353 - return stdClient.bm_dump_table(CONTEXT_ID, tableName); 369 + String dump = stdClient.bm_dump_table(CONTEXT_ID, tableName);
370 + LOG.debug("Table dump retrieved! > deviceId={}, tableName={}", deviceId, tableName);
371 + return dump;
354 } catch (TException e) { 372 } catch (TException e) {
373 + LOG.debug("Exception while retrieving table dump: {} > deviceId={}, tableName={}",
374 + e, deviceId, tableName);
355 throw new Bmv2RuntimeException(e.getMessage(), e); 375 throw new Bmv2RuntimeException(e.getMessage(), e);
356 } 376 }
357 } 377 }
358 378
359 - /** 379 + @Override
360 - * Reset the state of the switch (e.g. delete all entries, etc.).
361 - *
362 - * @throws Bmv2RuntimeException if any error occurs
363 - */
364 public void resetState() throws Bmv2RuntimeException { 380 public void resetState() throws Bmv2RuntimeException {
365 381
382 + LOG.debug("Resetting device state... > deviceId={}", deviceId);
383 +
366 try { 384 try {
367 stdClient.bm_reset_state(); 385 stdClient.bm_reset_state();
386 + LOG.debug("Device state reset! > deviceId={}", deviceId);
368 } catch (TException e) { 387 } catch (TException e) {
388 + LOG.debug("Exception while resetting device state: {} > deviceId={}", e, deviceId);
369 throw new Bmv2RuntimeException(e.getMessage(), e); 389 throw new Bmv2RuntimeException(e.getMessage(), e);
370 } 390 }
371 } 391 }
...@@ -376,20 +396,26 @@ public final class Bmv2ThriftClient { ...@@ -376,20 +396,26 @@ public final class Bmv2ThriftClient {
376 private static class ClientLoader 396 private static class ClientLoader
377 extends CacheLoader<DeviceId, Bmv2ThriftClient> { 397 extends CacheLoader<DeviceId, Bmv2ThriftClient> {
378 398
399 + // Connection retries options: max 10 retries each 200 ms
400 + private static final Options RECONN_OPTIONS = new Options(NUM_CONNECTION_RETRIES, TIME_BETWEEN_RETRIES);
401 +
379 @Override 402 @Override
380 public Bmv2ThriftClient load(DeviceId deviceId) 403 public Bmv2ThriftClient load(DeviceId deviceId)
381 throws TTransportException { 404 throws TTransportException {
405 + LOG.debug("Creating new client in cache... > deviceId={}", deviceId);
382 Pair<String, Integer> info = parseDeviceId(deviceId); 406 Pair<String, Integer> info = parseDeviceId(deviceId);
383 //make the expensive call 407 //make the expensive call
384 TTransport transport = new TSocket( 408 TTransport transport = new TSocket(
385 info.getLeft(), info.getRight()); 409 info.getLeft(), info.getRight());
386 TProtocol protocol = new TBinaryProtocol(transport); 410 TProtocol protocol = new TBinaryProtocol(transport);
387 - Standard.Iface stdClient = new Standard.Client( 411 + Standard.Client stdClient = new Standard.Client(
388 new TMultiplexedProtocol(protocol, "standard")); 412 new TMultiplexedProtocol(protocol, "standard"));
413 + // Wrap the client so to automatically have synchronization and resiliency to connectivity problems
414 + Standard.Iface reconnStdIface = SafeThriftClient.wrap(stdClient,
415 + Standard.Iface.class,
416 + RECONN_OPTIONS);
389 417
390 - transport.open(); 418 + return new Bmv2ThriftClient(deviceId, transport, reconnStdIface);
391 -
392 - return new Bmv2ThriftClient(transport, stdClient);
393 } 419 }
394 } 420 }
395 421
...@@ -403,6 +429,7 @@ public final class Bmv2ThriftClient { ...@@ -403,6 +429,7 @@ public final class Bmv2ThriftClient {
403 public void onRemoval( 429 public void onRemoval(
404 RemovalNotification<DeviceId, Bmv2ThriftClient> notification) { 430 RemovalNotification<DeviceId, Bmv2ThriftClient> notification) {
405 // close the transport connection 431 // close the transport connection
432 + LOG.debug("Removing client from cache... > deviceId={}", notification.getKey());
406 notification.getValue().closeTransport(); 433 notification.getValue().closeTransport();
407 } 434 }
408 } 435 }
......
1 +/*
2 + * Copyright 2016-present Open Networking Laboratory
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 +
17 +/*
18 + * Most of the code of this class was copied from:
19 + * http://liveramp.com/engineering/reconnecting-thrift-client/
20 + */
21 +
22 +package org.onosproject.bmv2.ctl;
23 +
24 +import com.google.common.collect.ImmutableSet;
25 +import org.apache.thrift.TServiceClient;
26 +import org.apache.thrift.transport.TTransport;
27 +import org.apache.thrift.transport.TTransportException;
28 +import org.slf4j.Logger;
29 +import org.slf4j.LoggerFactory;
30 +
31 +import java.lang.reflect.InvocationHandler;
32 +import java.lang.reflect.InvocationTargetException;
33 +import java.lang.reflect.Method;
34 +import java.lang.reflect.Proxy;
35 +import java.util.Set;
36 +
37 +/**
38 + * Thrift client wrapper that attempts a few reconnects before giving up a method call execution. It al provides
39 + * synchronization between calls (automatically serialize multiple calls to the same client from different threads).
40 + */
41 +public final class SafeThriftClient {
42 +
43 + private static final Logger LOG = LoggerFactory.getLogger(SafeThriftClient.class);
44 +
45 + /**
46 + * List of causes which suggest a restart might fix things (defined as constants in {@link TTransportException}).
47 + */
48 + private static final Set<Integer> RESTARTABLE_CAUSES = ImmutableSet.of(TTransportException.NOT_OPEN,
49 + TTransportException.END_OF_FILE,
50 + TTransportException.TIMED_OUT,
51 + TTransportException.UNKNOWN);
52 +
53 + private SafeThriftClient() {
54 + // ban constructor.
55 + }
56 +
57 + /**
58 + * Reflectively wraps an already existing Thrift client.
59 + *
60 + * @param baseClient the client to wrap
61 + * @param clientInterface the interface that the client implements
62 + * @param options options that control behavior of the reconnecting client
63 + * @param <T>
64 + * @param <C>
65 + * @return
66 + */
67 + public static <T extends TServiceClient, C> C wrap(T baseClient, Class<C> clientInterface, Options options) {
68 + Object proxyObject = Proxy.newProxyInstance(clientInterface.getClassLoader(),
69 + new Class<?>[]{clientInterface},
70 + new ReconnectingClientProxy<T>(baseClient,
71 + options.getNumRetries(),
72 + options.getTimeBetweenRetries()));
73 +
74 + return (C) proxyObject;
75 + }
76 +
77 + /**
78 + * Reflectively wraps an already existing Thrift client.
79 + *
80 + * @param baseClient the client to wrap
81 + * @param options options that control behavior of the reconnecting client
82 + * @param <T>
83 + * @param <C>
84 + * @return
85 + */
86 + public static <T extends TServiceClient, C> C wrap(T baseClient, Options options) {
87 + Class<?>[] interfaces = baseClient.getClass().getInterfaces();
88 +
89 + for (Class<?> iface : interfaces) {
90 + if (iface.getSimpleName().equals("Iface")
91 + && iface.getEnclosingClass().equals(baseClient.getClass().getEnclosingClass())) {
92 + return (C) wrap(baseClient, iface, options);
93 + }
94 + }
95 +
96 + throw new RuntimeException("Class needs to implement Iface directly. Use wrap(TServiceClient, Class) instead.");
97 + }
98 +
99 + /**
100 + * Reflectively wraps an already existing Thrift client.
101 + *
102 + * @param baseClient the client to wrap
103 + * @param clientInterface the interface that the client implements
104 + * @param <T>
105 + * @param <C>
106 + * @return
107 + */
108 + public static <T extends TServiceClient, C> C wrap(T baseClient, Class<C> clientInterface) {
109 + return wrap(baseClient, clientInterface, Options.defaults());
110 + }
111 +
112 + /**
113 + * Reflectively wraps an already existing Thrift client.
114 + *
115 + * @param baseClient the client to wrap
116 + * @param <T>
117 + * @param <C>
118 + * @return
119 + */
120 + public static <T extends TServiceClient, C> C wrap(T baseClient) {
121 + return wrap(baseClient, Options.defaults());
122 + }
123 +
124 + /**
125 + * Reconnection options for {@link SafeThriftClient}.
126 + */
127 + public static class Options {
128 + private int numRetries;
129 + private long timeBetweenRetries;
130 +
131 + /**
132 + * Creates new options with the given parameters.
133 + *
134 + * @param numRetries the maximum number of times to try reconnecting before giving up and throwing an
135 + * exception
136 + * @param timeBetweenRetries the number of milliseconds to wait in between reconnection attempts.
137 + */
138 + public Options(int numRetries, long timeBetweenRetries) {
139 + this.numRetries = numRetries;
140 + this.timeBetweenRetries = timeBetweenRetries;
141 + }
142 +
143 + private static Options defaults() {
144 + return new Options(5, 10000L);
145 + }
146 +
147 + private int getNumRetries() {
148 + return numRetries;
149 + }
150 +
151 + private long getTimeBetweenRetries() {
152 + return timeBetweenRetries;
153 + }
154 + }
155 +
156 + /**
157 + * Helper proxy class. Attempts to call method on proxy object wrapped in try/catch. If it fails, it attempts a
158 + * reconnect and tries the method again.
159 + *
160 + * @param <T>
161 + */
162 + private static class ReconnectingClientProxy<T extends TServiceClient> implements InvocationHandler {
163 + private final T baseClient;
164 + private final int maxRetries;
165 + private final long timeBetweenRetries;
166 +
167 + public ReconnectingClientProxy(T baseClient, int maxRetries, long timeBetweenRetries) {
168 + this.baseClient = baseClient;
169 + this.maxRetries = maxRetries;
170 + this.timeBetweenRetries = timeBetweenRetries;
171 + }
172 +
173 + private static void reconnectOrThrowException(TTransport transport, int maxRetries, long timeBetweenRetries)
174 + throws TTransportException {
175 + int errors = 0;
176 + transport.close();
177 +
178 + while (errors < maxRetries) {
179 + try {
180 + LOG.debug("Attempting to reconnect...");
181 + transport.open();
182 + LOG.debug("Reconnection successful");
183 + break;
184 + } catch (TTransportException e) {
185 + LOG.error("Error while reconnecting:", e);
186 + errors++;
187 +
188 + if (errors < maxRetries) {
189 + try {
190 + LOG.debug("Sleeping for {} milliseconds before retrying", timeBetweenRetries);
191 + Thread.sleep(timeBetweenRetries);
192 + } catch (InterruptedException e2) {
193 + Thread.currentThread().interrupt();
194 + throw new RuntimeException(e);
195 + }
196 + }
197 + }
198 + }
199 +
200 + if (errors >= maxRetries) {
201 + throw new TTransportException("Failed to reconnect");
202 + }
203 + }
204 +
205 + @Override
206 + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
207 +
208 + // With Thrift clients must be instantiated for each different transport session, i.e. server instance.
209 + // Hence, using baseClient as lock, only calls towards the same server will be synchronized.
210 +
211 + synchronized (baseClient) {
212 +
213 + LOG.debug("Invoking client method... > method={}, fromThread={}",
214 + method.getName(), Thread.currentThread().getId());
215 +
216 + Object result = null;
217 +
218 + try {
219 + result = method.invoke(baseClient, args);
220 +
221 + } catch (InvocationTargetException e) {
222 + if (e.getTargetException() instanceof TTransportException) {
223 + TTransportException cause = (TTransportException) e.getTargetException();
224 +
225 + if (RESTARTABLE_CAUSES.contains(cause.getType())) {
226 + reconnectOrThrowException(baseClient.getInputProtocol().getTransport(),
227 + maxRetries,
228 + timeBetweenRetries);
229 + result = method.invoke(baseClient, args);
230 + }
231 + }
232 +
233 + if (result == null) {
234 + LOG.debug("Exception while invoking client method: {} > method={}, fromThread={}",
235 + e, method.getName(), Thread.currentThread().getId());
236 + throw e.getTargetException();
237 + }
238 + }
239 +
240 + LOG.debug("Method invoke complete! > method={}, fromThread={}",
241 + method.getName(), Thread.currentThread().getId());
242 +
243 + return result;
244 + }
245 + }
246 + }
247 +}