blob: 28d88e97ba8605b8f47805e04c2be30ded5f8958 [file] [log] [blame]
Carmelo Cascone2ea177b2016-02-25 18:38:42 -08001/*
2 * Copyright 2014-2016 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
17package org.onosproject.bmv2.ctl;
18
19import com.google.common.cache.CacheBuilder;
20import com.google.common.cache.CacheLoader;
21import com.google.common.cache.LoadingCache;
22import com.google.common.cache.RemovalListener;
23import com.google.common.cache.RemovalNotification;
24import com.google.common.collect.Lists;
25import org.apache.commons.lang3.tuple.ImmutablePair;
26import org.apache.commons.lang3.tuple.Pair;
27import org.apache.thrift.TException;
28import org.apache.thrift.protocol.TBinaryProtocol;
29import org.apache.thrift.protocol.TMultiplexedProtocol;
30import org.apache.thrift.protocol.TProtocol;
31import org.apache.thrift.transport.TSocket;
32import org.apache.thrift.transport.TTransport;
33import org.apache.thrift.transport.TTransportException;
Carmelo Casconee4da9092016-04-26 12:14:08 -070034import org.onlab.util.ImmutableByteSequence;
Carmelo Casconeaa8b6292016-04-13 14:27:06 -070035import org.onosproject.bmv2.api.runtime.Bmv2Action;
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -070036import org.onosproject.bmv2.api.runtime.Bmv2Client;
Carmelo Casconeaa8b6292016-04-13 14:27:06 -070037import org.onosproject.bmv2.api.runtime.Bmv2ExactMatchParam;
Carmelo Casconeaa8b6292016-04-13 14:27:06 -070038import org.onosproject.bmv2.api.runtime.Bmv2LpmMatchParam;
39import org.onosproject.bmv2.api.runtime.Bmv2MatchKey;
40import org.onosproject.bmv2.api.runtime.Bmv2PortInfo;
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -070041import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException;
Carmelo Casconeaa8b6292016-04-13 14:27:06 -070042import org.onosproject.bmv2.api.runtime.Bmv2TableEntry;
43import org.onosproject.bmv2.api.runtime.Bmv2TernaryMatchParam;
44import org.onosproject.bmv2.api.runtime.Bmv2ValidMatchParam;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080045import org.onosproject.net.DeviceId;
46import org.p4.bmv2.thrift.BmAddEntryOptions;
Carmelo Casconef8cf2882016-05-04 14:06:17 -070047import org.p4.bmv2.thrift.BmCounterValue;
Carmelo Casconeaa8b6292016-04-13 14:27:06 -070048import org.p4.bmv2.thrift.BmMatchParam;
49import org.p4.bmv2.thrift.BmMatchParamExact;
50import org.p4.bmv2.thrift.BmMatchParamLPM;
51import org.p4.bmv2.thrift.BmMatchParamTernary;
52import org.p4.bmv2.thrift.BmMatchParamType;
53import org.p4.bmv2.thrift.BmMatchParamValid;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080054import org.p4.bmv2.thrift.DevMgrPortInfo;
Carmelo Casconee4da9092016-04-26 12:14:08 -070055import org.p4.bmv2.thrift.SimpleSwitch;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080056import org.p4.bmv2.thrift.Standard;
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -070057import org.slf4j.Logger;
58import org.slf4j.LoggerFactory;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080059
Carmelo Casconeaa8b6292016-04-13 14:27:06 -070060import java.nio.ByteBuffer;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080061import java.util.Collection;
62import java.util.List;
63import java.util.concurrent.ExecutionException;
64import java.util.concurrent.TimeUnit;
65import java.util.stream.Collectors;
66
67import static com.google.common.base.Preconditions.checkNotNull;
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -070068import static org.onosproject.bmv2.ctl.SafeThriftClient.Options;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080069
70/**
71 * Implementation of a Thrift client to control the Bmv2 switch.
72 */
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -070073public final class Bmv2ThriftClient implements Bmv2Client {
74
75 private static final Logger LOG =
76 LoggerFactory.getLogger(Bmv2ThriftClient.class);
77
78 // FIXME: make context_id arbitrary for each call
79 // See: https://github.com/p4lang/behavioral-model/blob/master/modules/bm_sim/include/bm_sim/context.h
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080080 private static final int CONTEXT_ID = 0;
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -070081 // Seconds after a client is expired (and connection closed) in the cache.
82 private static final int CLIENT_CACHE_TIMEOUT = 60;
83 // Number of connection retries after a network error.
Carmelo Cascone5fa651e2016-04-27 17:35:57 -070084 private static final int NUM_CONNECTION_RETRIES = 3;
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -070085 // Time between retries in milliseconds.
Carmelo Cascone5fa651e2016-04-27 17:35:57 -070086 private static final int TIME_BETWEEN_RETRIES = 300;
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -070087
88 // Static client cache where clients are removed after a predefined timeout.
89 private static final LoadingCache<DeviceId, Bmv2ThriftClient>
90 CLIENT_CACHE = CacheBuilder.newBuilder()
91 .expireAfterAccess(CLIENT_CACHE_TIMEOUT, TimeUnit.SECONDS)
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080092 .removalListener(new ClientRemovalListener())
93 .build(new ClientLoader());
Carmelo Cascone2bf44a42016-05-03 18:03:56 -070094
95 private static final Bmv2TableDumpParser TABLE_DUMP_PARSER = new Bmv2TableDumpParser();
96
Carmelo Casconee4da9092016-04-26 12:14:08 -070097 private final Standard.Iface standardClient;
98 private final SimpleSwitch.Iface simpleSwitchClient;
Carmelo Casconeaa8b6292016-04-13 14:27:06 -070099 private final TTransport transport;
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700100 private final DeviceId deviceId;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800101
102 // ban constructor
Carmelo Casconee4da9092016-04-26 12:14:08 -0700103 private Bmv2ThriftClient(DeviceId deviceId, TTransport transport, Standard.Iface standardClient,
104 SimpleSwitch.Iface simpleSwitchClient) {
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700105 this.deviceId = deviceId;
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700106 this.transport = transport;
Carmelo Casconee4da9092016-04-26 12:14:08 -0700107 this.standardClient = standardClient;
108 this.simpleSwitchClient = simpleSwitchClient;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800109
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700110 LOG.debug("New client created! > deviceId={}", deviceId);
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700111 }
112
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800113 /**
114 * Returns a client object to control the passed device.
115 *
116 * @param deviceId device id
117 * @return bmv2 client object
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700118 * @throws Bmv2RuntimeException if a connection to the device cannot be established
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800119 */
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700120 public static Bmv2ThriftClient of(DeviceId deviceId) throws Bmv2RuntimeException {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800121 try {
122 checkNotNull(deviceId, "deviceId cannot be null");
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700123 LOG.debug("Getting a client from cache... > deviceId{}", deviceId);
124 return CLIENT_CACHE.get(deviceId);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800125 } catch (ExecutionException e) {
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700126 LOG.debug("Exception while getting a client from cache: {} > ", e, deviceId);
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700127 throw new Bmv2RuntimeException(e.getMessage(), e.getCause());
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800128 }
129 }
130
131 /**
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700132 * Force a close of the transport session (if one is open) with the given device.
133 *
134 * @param deviceId device id
135 */
136 public static void forceDisconnectOf(DeviceId deviceId) {
137 CLIENT_CACHE.invalidate(deviceId);
138 }
139
140 /**
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800141 * Pings the device. Returns true if the device is reachable,
142 * false otherwise.
143 *
144 * @param deviceId device id
145 * @return true if reachable, false otherwise
146 */
147 public static boolean ping(DeviceId deviceId) {
148 // poll ports status as workaround to assess device reachability
149 try {
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700150 LOG.debug("Pinging device... > deviceId={}", deviceId);
151 Bmv2ThriftClient client = of(deviceId);
Carmelo Casconee4da9092016-04-26 12:14:08 -0700152 boolean result = client.simpleSwitchClient.ping();
153 LOG.debug("Device pinged! > deviceId={}, state={}", deviceId, result);
154 return result;
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700155 } catch (TException | Bmv2RuntimeException e) {
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700156 LOG.debug("Device NOT reachable! > deviceId={}", deviceId);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800157 return false;
158 }
159 }
160
161 /**
162 * Parse device ID into host and port.
163 *
164 * @param did device ID
165 * @return a pair of host and port
166 */
167 private static Pair<String, Integer> parseDeviceId(DeviceId did) {
168 String[] info = did.toString().split(":");
169 if (info.length == 3) {
170 String host = info[1];
171 int port = Integer.parseInt(info[2]);
172 return ImmutablePair.of(host, port);
173 } else {
174 throw new IllegalArgumentException(
175 "Unable to parse BMv2 device ID "
176 + did.toString()
177 + ", expected format is scheme:host:port");
178 }
179 }
180
181 /**
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700182 * Builds a list of Bmv2/Thrift compatible match parameters.
183 *
184 * @param matchKey a bmv2 matchKey
185 * @return list of thrift-compatible bm match parameters
186 */
187 private static List<BmMatchParam> buildMatchParamsList(Bmv2MatchKey matchKey) {
188 List<BmMatchParam> paramsList = Lists.newArrayList();
189 matchKey.matchParams().forEach(x -> {
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700190 ByteBuffer value;
191 ByteBuffer mask;
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700192 switch (x.type()) {
193 case EXACT:
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700194 value = ByteBuffer.wrap(((Bmv2ExactMatchParam) x).value().asArray());
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700195 paramsList.add(
196 new BmMatchParam(BmMatchParamType.EXACT)
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700197 .setExact(new BmMatchParamExact(value)));
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700198 break;
199 case TERNARY:
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700200 value = ByteBuffer.wrap(((Bmv2TernaryMatchParam) x).value().asArray());
201 mask = ByteBuffer.wrap(((Bmv2TernaryMatchParam) x).mask().asArray());
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700202 paramsList.add(
203 new BmMatchParam(BmMatchParamType.TERNARY)
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700204 .setTernary(new BmMatchParamTernary(value, mask)));
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700205 break;
206 case LPM:
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700207 value = ByteBuffer.wrap(((Bmv2LpmMatchParam) x).value().asArray());
208 int prefixLength = ((Bmv2LpmMatchParam) x).prefixLength();
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700209 paramsList.add(
210 new BmMatchParam(BmMatchParamType.LPM)
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700211 .setLpm(new BmMatchParamLPM(value, prefixLength)));
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700212 break;
213 case VALID:
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700214 boolean flag = ((Bmv2ValidMatchParam) x).flag();
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700215 paramsList.add(
216 new BmMatchParam(BmMatchParamType.VALID)
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700217 .setValid(new BmMatchParamValid(flag)));
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700218 break;
219 default:
220 // should never be here
221 throw new RuntimeException("Unknown match param type " + x.type().name());
222 }
223 });
224 return paramsList;
225 }
226
227 /**
228 * Build a list of Bmv2/Thrift compatible action parameters.
229 *
230 * @param action an action object
231 * @return list of ByteBuffers
232 */
233 private static List<ByteBuffer> buildActionParamsList(Bmv2Action action) {
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700234 List<ByteBuffer> buffers = Lists.newArrayList();
235 action.parameters().forEach(p -> buffers.add(ByteBuffer.wrap(p.asArray())));
236 return buffers;
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700237 }
238
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700239 @Override
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700240 public final long addTableEntry(Bmv2TableEntry entry) throws Bmv2RuntimeException {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800241
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700242 LOG.debug("Adding table entry... > deviceId={}, entry={}", deviceId, entry);
243
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800244 long entryId = -1;
245
246 try {
247 BmAddEntryOptions options = new BmAddEntryOptions();
248
249 if (entry.hasPriority()) {
250 options.setPriority(entry.priority());
251 }
252
Carmelo Casconee4da9092016-04-26 12:14:08 -0700253 entryId = standardClient.bm_mt_add_entry(
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800254 CONTEXT_ID,
255 entry.tableName(),
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700256 buildMatchParamsList(entry.matchKey()),
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800257 entry.action().name(),
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700258 buildActionParamsList(entry.action()),
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800259 options);
260
261 if (entry.hasTimeout()) {
262 /* bmv2 accepts timeouts in milliseconds */
263 int msTimeout = (int) Math.round(entry.timeout() * 1_000);
Carmelo Casconee4da9092016-04-26 12:14:08 -0700264 standardClient.bm_mt_set_entry_ttl(
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800265 CONTEXT_ID, entry.tableName(), entryId, msTimeout);
266 }
267
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700268 LOG.debug("Table entry added! > deviceId={}, entryId={}/{}", deviceId, entry.tableName(), entryId);
269
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800270 return entryId;
271
272 } catch (TException e) {
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700273 LOG.debug("Exception while adding table entry: {} > deviceId={}, tableName={}",
274 e, deviceId, entry.tableName());
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800275 if (entryId != -1) {
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700276 // entry is in inconsistent state (unable to add timeout), remove it
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800277 try {
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700278 deleteTableEntry(entry.tableName(), entryId);
279 } catch (Bmv2RuntimeException e1) {
280 LOG.debug("Unable to remove failed table entry: {} > deviceId={}, tableName={}",
281 e1, deviceId, entry.tableName());
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800282 }
283 }
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700284 throw new Bmv2RuntimeException(e.getMessage(), e);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800285 }
286 }
287
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700288 @Override
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800289 public final void modifyTableEntry(String tableName,
290 long entryId, Bmv2Action action)
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700291 throws Bmv2RuntimeException {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800292
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700293 LOG.debug("Modifying table entry... > deviceId={}, entryId={}/{}", deviceId, tableName, entryId);
294
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800295 try {
Carmelo Casconee4da9092016-04-26 12:14:08 -0700296 standardClient.bm_mt_modify_entry(
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800297 CONTEXT_ID,
298 tableName,
299 entryId,
300 action.name(),
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700301 buildActionParamsList(action));
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700302 LOG.debug("Table entry modified! > deviceId={}, entryId={}/{}", deviceId, tableName, entryId);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800303 } catch (TException e) {
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700304 LOG.debug("Exception while modifying table entry: {} > deviceId={}, entryId={}/{}",
305 e, deviceId, tableName, entryId);
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700306 throw new Bmv2RuntimeException(e.getMessage(), e);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800307 }
308 }
309
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700310 @Override
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800311 public final void deleteTableEntry(String tableName,
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700312 long entryId) throws Bmv2RuntimeException {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800313
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700314 LOG.debug("Deleting table entry... > deviceId={}, entryId={}/{}", deviceId, tableName, entryId);
315
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800316 try {
Carmelo Casconee4da9092016-04-26 12:14:08 -0700317 standardClient.bm_mt_delete_entry(CONTEXT_ID, tableName, entryId);
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700318 LOG.debug("Table entry deleted! > deviceId={}, entryId={}/{}", deviceId, tableName, entryId);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800319 } catch (TException e) {
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700320 LOG.debug("Exception while deleting table entry: {} > deviceId={}, entryId={}/{}",
321 e, deviceId, tableName, entryId);
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700322 throw new Bmv2RuntimeException(e.getMessage(), e);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800323 }
324 }
325
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700326 @Override
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800327 public final void setTableDefaultAction(String tableName, Bmv2Action action)
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700328 throws Bmv2RuntimeException {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800329
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700330 LOG.debug("Setting table default... > deviceId={}, tableName={}, action={}", deviceId, tableName, action);
331
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800332 try {
Carmelo Casconee4da9092016-04-26 12:14:08 -0700333 standardClient.bm_mt_set_default_action(
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800334 CONTEXT_ID,
335 tableName,
336 action.name(),
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700337 buildActionParamsList(action));
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700338 LOG.debug("Table default set! > deviceId={}, tableName={}, action={}", deviceId, tableName, action);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800339 } catch (TException e) {
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700340 LOG.debug("Exception while setting table default : {} > deviceId={}, tableName={}, action={}",
341 e, deviceId, tableName, action);
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700342 throw new Bmv2RuntimeException(e.getMessage(), e);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800343 }
344 }
345
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700346 @Override
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700347 public Collection<Bmv2PortInfo> getPortsInfo() throws Bmv2RuntimeException {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800348
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700349 LOG.debug("Retrieving port info... > deviceId={}", deviceId);
350
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800351 try {
Carmelo Casconee4da9092016-04-26 12:14:08 -0700352 List<DevMgrPortInfo> portInfos = standardClient.bm_dev_mgr_show_ports();
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800353
354 Collection<Bmv2PortInfo> bmv2PortInfos = Lists.newArrayList();
355
356 bmv2PortInfos.addAll(
357 portInfos.stream()
358 .map(Bmv2PortInfo::new)
359 .collect(Collectors.toList()));
360
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700361 LOG.debug("Port info retrieved! > deviceId={}, portInfos={}", deviceId, bmv2PortInfos);
362
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800363 return bmv2PortInfos;
364
365 } catch (TException e) {
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700366 LOG.debug("Exception while retrieving port info: {} > deviceId={}", e, deviceId);
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700367 throw new Bmv2RuntimeException(e.getMessage(), e);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800368 }
369 }
370
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700371 @Override
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700372 public String dumpTable(String tableName) throws Bmv2RuntimeException {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800373
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700374 LOG.debug("Retrieving table dump... > deviceId={}, tableName={}", deviceId, tableName);
375
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800376 try {
Carmelo Casconee4da9092016-04-26 12:14:08 -0700377 String dump = standardClient.bm_dump_table(CONTEXT_ID, tableName);
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700378 LOG.debug("Table dump retrieved! > deviceId={}, tableName={}", deviceId, tableName);
379 return dump;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800380 } catch (TException e) {
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700381 LOG.debug("Exception while retrieving table dump: {} > deviceId={}, tableName={}",
382 e, deviceId, tableName);
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700383 throw new Bmv2RuntimeException(e.getMessage(), e);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800384 }
385 }
386
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700387 @Override
Carmelo Cascone2bf44a42016-05-03 18:03:56 -0700388 public List<Long> getInstalledEntryIds(String tableName) throws Bmv2RuntimeException {
389
390 LOG.debug("Getting entry ids... > deviceId={}, tableName={}", deviceId, tableName);
391
392 try {
393 List<Long> entryIds = TABLE_DUMP_PARSER.getEntryIds(dumpTable(tableName));
394 LOG.debug("Entry ids retrieved! > deviceId={}, tableName={}, entryIdsCount={}",
395 deviceId, tableName, entryIds.size());
396 return entryIds;
397 } catch (Bmv2TableDumpParser.Bmv2TableDumpParserException e) {
398 LOG.debug("Exception while retrieving entry ids: {} > deviceId={}, tableName={}",
399 e, deviceId, tableName);
400 throw new Bmv2RuntimeException(e.getMessage(), e);
401 }
402 }
403
404 @Override
405 public int cleanupTable(String tableName) throws Bmv2RuntimeException {
406
407 LOG.debug("Starting table cleanup... > deviceId={}, tableName={}", deviceId, tableName);
408
409 List<Long> entryIds = getInstalledEntryIds(tableName);
410
411 int count = 0;
412 for (Long entryId : entryIds) {
413 try {
414 standardClient.bm_mt_delete_entry(CONTEXT_ID, tableName, entryId);
415 count++;
416 } catch (TException e) {
417 LOG.warn("Exception while deleting entry: {} > deviceId={}, tableName={}, entryId={}",
418 e.toString(), deviceId, tableName, entryId);
419 }
420 }
421
422 return count;
423 }
424
425 @Override
Carmelo Casconee4da9092016-04-26 12:14:08 -0700426 public void transmitPacket(int portNumber, ImmutableByteSequence packet) throws Bmv2RuntimeException {
427
428 LOG.debug("Requesting packet transmission... > portNumber={}, packet={}", portNumber, packet);
429
430 try {
431
432 simpleSwitchClient.push_packet(portNumber, ByteBuffer.wrap(packet.asArray()));
433 LOG.debug("Packet transmission requested! > portNumber={}, packet={}", portNumber, packet);
434 } catch (TException e) {
435 LOG.debug("Exception while requesting packet transmission: {} > portNumber={}, packet={}",
Carmelo Cascone5fa651e2016-04-27 17:35:57 -0700436 e, portNumber, packet);
Carmelo Casconee4da9092016-04-26 12:14:08 -0700437 throw new Bmv2RuntimeException(e.getMessage(), e);
438 }
439 }
440
441 @Override
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700442 public void resetState() throws Bmv2RuntimeException {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800443
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700444 LOG.debug("Resetting device state... > deviceId={}", deviceId);
445
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800446 try {
Carmelo Casconee4da9092016-04-26 12:14:08 -0700447 standardClient.bm_reset_state();
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700448 LOG.debug("Device state reset! > deviceId={}", deviceId);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800449 } catch (TException e) {
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700450 LOG.debug("Exception while resetting device state: {} > deviceId={}", e, deviceId);
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700451 throw new Bmv2RuntimeException(e.getMessage(), e);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800452 }
453 }
454
Carmelo Casconeb57a1342016-05-03 09:43:06 -0700455 @Override
456 public String dumpJsonConfig() throws Bmv2RuntimeException {
457
458 LOG.debug("Dumping device config... > deviceId={}", deviceId);
459
460 try {
461 String config = standardClient.bm_get_config();
462 LOG.debug("Device config dumped! > deviceId={}, configLength={}", deviceId, config.length());
463 return config;
464 } catch (TException e) {
465 LOG.debug("Exception while dumping device config: {} > deviceId={}", e, deviceId);
466 throw new Bmv2RuntimeException(e.getMessage(), e);
467 }
468 }
469
470 @Override
Carmelo Casconef8cf2882016-05-04 14:06:17 -0700471 public Pair<Long, Long> readTableEntryCounter(String tableName, long entryId) throws Bmv2RuntimeException {
472
473 LOG.debug("Reading table entry counters... > deviceId={}, tableName={}, entryId={}",
474 deviceId, tableName, entryId);
475
476 try {
477 BmCounterValue counterValue = standardClient.bm_mt_read_counter(CONTEXT_ID, tableName, entryId);
478 LOG.debug("Table entry counters retrieved! > deviceId={}, tableName={}, entryId={}, bytes={}, packets={}",
479 deviceId, tableName, entryId, counterValue.bytes, counterValue.packets);
480 return Pair.of(counterValue.bytes, counterValue.packets);
481 } catch (TException e) {
482 LOG.debug("Exception while reading table counters: {} > deviceId={}, tableName={}, entryId={}",
483 e.toString(), deviceId);
484 throw new Bmv2RuntimeException(e.getMessage(), e);
485 }
486 }
487
488 @Override
Carmelo Casconeb57a1342016-05-03 09:43:06 -0700489 public String getJsonConfigMd5() throws Bmv2RuntimeException {
490
491 LOG.debug("Getting device config md5... > deviceId={}", deviceId);
492
493 try {
494 String md5 = standardClient.bm_get_config_md5();
495 LOG.debug("Device config md5 received! > deviceId={}, configMd5={}", deviceId, md5);
496 return md5;
497 } catch (TException e) {
498 LOG.debug("Exception while getting device config md5: {} > deviceId={}", e, deviceId);
499 throw new Bmv2RuntimeException(e.getMessage(), e);
500 }
501 }
502
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800503 /**
504 * Transport/client cache loader.
505 */
506 private static class ClientLoader
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700507 extends CacheLoader<DeviceId, Bmv2ThriftClient> {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800508
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700509 private static final Options RECONN_OPTIONS = new Options(NUM_CONNECTION_RETRIES, TIME_BETWEEN_RETRIES);
510
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800511 @Override
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700512 public Bmv2ThriftClient load(DeviceId deviceId)
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800513 throws TTransportException {
Carmelo Cascone37d5dbf2016-04-18 15:15:48 -0700514 LOG.debug("Creating new client in cache... > deviceId={}", deviceId);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800515 Pair<String, Integer> info = parseDeviceId(deviceId);
516 //make the expensive call
517 TTransport transport = new TSocket(
518 info.getLeft(), info.getRight());
519 TProtocol protocol = new TBinaryProtocol(transport);
Carmelo Cascone2c72ac72016-05-04 21:52:37 -0700520 // Our BMv2 device implements multiple Thrift services, create a client for each one on the same transport.
Carmelo Casconee4da9092016-04-26 12:14:08 -0700521 Standard.Client standardClient = new Standard.Client(
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800522 new TMultiplexedProtocol(protocol, "standard"));
Carmelo Casconee4da9092016-04-26 12:14:08 -0700523 SimpleSwitch.Client simpleSwitch = new SimpleSwitch.Client(
524 new TMultiplexedProtocol(protocol, "simple_switch"));
525 // Wrap clients so to automatically have synchronization and resiliency to connectivity errors
526 Standard.Iface safeStandardClient = SafeThriftClient.wrap(standardClient,
527 Standard.Iface.class,
528 RECONN_OPTIONS);
529 SimpleSwitch.Iface safeSimpleSwitchClient = SafeThriftClient.wrap(simpleSwitch,
530 SimpleSwitch.Iface.class,
531 RECONN_OPTIONS);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800532
Carmelo Casconee4da9092016-04-26 12:14:08 -0700533 return new Bmv2ThriftClient(deviceId, transport, safeStandardClient, safeSimpleSwitchClient);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800534 }
535 }
536
537 /**
538 * Client cache removal listener. Close the connection on cache removal.
539 */
540 private static class ClientRemovalListener implements
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700541 RemovalListener<DeviceId, Bmv2ThriftClient> {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800542
543 @Override
Carmelo Cascone2c72ac72016-05-04 21:52:37 -0700544 public void onRemoval(RemovalNotification<DeviceId, Bmv2ThriftClient> notification) {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800545 // close the transport connection
Carmelo Cascone2c72ac72016-05-04 21:52:37 -0700546 Bmv2ThriftClient client = notification.getValue();
547 // Locking here is ugly, but needed (see SafeThriftClient).
548 synchronized (client.transport) {
549 LOG.debug("Closing transport session... > deviceId={}", client.deviceId);
550 if (client.transport.isOpen()) {
551 client.transport.close();
552 LOG.debug("Transport session closed! > deviceId={}", client.deviceId);
553 } else {
554 LOG.debug("Transport session was already closed! deviceId={}", client.deviceId);
555 }
556 }
557 LOG.debug("Removing client from cache... > deviceId={}", client.deviceId);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800558 }
559 }
560}