blob: eb6687ad0c25b4e43b5b2d2ee5104344a15432ed [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 Casconeaa8b6292016-04-13 14:27:06 -070034import org.onlab.util.ImmutableByteSequence;
35import org.onosproject.bmv2.api.runtime.Bmv2Action;
36import org.onosproject.bmv2.api.runtime.Bmv2ExactMatchParam;
37import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException;
38import org.onosproject.bmv2.api.runtime.Bmv2LpmMatchParam;
39import org.onosproject.bmv2.api.runtime.Bmv2MatchKey;
40import org.onosproject.bmv2.api.runtime.Bmv2PortInfo;
41import org.onosproject.bmv2.api.runtime.Bmv2TableEntry;
42import org.onosproject.bmv2.api.runtime.Bmv2TernaryMatchParam;
43import org.onosproject.bmv2.api.runtime.Bmv2ValidMatchParam;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080044import org.onosproject.net.DeviceId;
45import org.p4.bmv2.thrift.BmAddEntryOptions;
Carmelo Casconeaa8b6292016-04-13 14:27:06 -070046import org.p4.bmv2.thrift.BmMatchParam;
47import org.p4.bmv2.thrift.BmMatchParamExact;
48import org.p4.bmv2.thrift.BmMatchParamLPM;
49import org.p4.bmv2.thrift.BmMatchParamTernary;
50import org.p4.bmv2.thrift.BmMatchParamType;
51import org.p4.bmv2.thrift.BmMatchParamValid;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080052import org.p4.bmv2.thrift.DevMgrPortInfo;
53import org.p4.bmv2.thrift.Standard;
54
Carmelo Casconeaa8b6292016-04-13 14:27:06 -070055import java.nio.ByteBuffer;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080056import java.util.Collection;
57import java.util.List;
58import java.util.concurrent.ExecutionException;
59import java.util.concurrent.TimeUnit;
60import java.util.stream.Collectors;
61
62import static com.google.common.base.Preconditions.checkNotNull;
63
64/**
65 * Implementation of a Thrift client to control the Bmv2 switch.
66 */
67public final class Bmv2ThriftClient {
68 /*
69 FIXME: derive context_id from device id
70 Using different context id values should serve to control different
71 switches responding to the same IP address and port
72 */
73 private static final int CONTEXT_ID = 0;
74 /*
75 Static transport/client cache:
76 - avoids opening a new transport session when there's one already open
77 - close the connection after a predefined timeout of 5 seconds
78 */
Carmelo Casconeaa8b6292016-04-13 14:27:06 -070079 private static LoadingCache<DeviceId, Bmv2ThriftClient>
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080080 clientCache = CacheBuilder.newBuilder()
81 .expireAfterAccess(5, TimeUnit.SECONDS)
82 .removalListener(new ClientRemovalListener())
83 .build(new ClientLoader());
84 private final Standard.Iface stdClient;
Carmelo Casconeaa8b6292016-04-13 14:27:06 -070085 private final TTransport transport;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080086
87 // ban constructor
Carmelo Casconeaa8b6292016-04-13 14:27:06 -070088 private Bmv2ThriftClient(TTransport transport, Standard.Iface stdClient) {
89 this.transport = transport;
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080090 this.stdClient = stdClient;
91 }
92
Carmelo Casconeaa8b6292016-04-13 14:27:06 -070093 private void closeTransport() {
94 this.transport.close();
95 }
96
Carmelo Cascone2ea177b2016-02-25 18:38:42 -080097 /**
98 * Returns a client object to control the passed device.
99 *
100 * @param deviceId device id
101 * @return bmv2 client object
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700102 * @throws Bmv2RuntimeException if a connection to the device cannot be established
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800103 */
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700104 public static Bmv2ThriftClient of(DeviceId deviceId) throws Bmv2RuntimeException {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800105 try {
106 checkNotNull(deviceId, "deviceId cannot be null");
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700107 return clientCache.get(deviceId);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800108 } catch (ExecutionException e) {
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700109 throw new Bmv2RuntimeException(e.getMessage(), e.getCause());
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800110 }
111 }
112
113 /**
114 * Pings the device. Returns true if the device is reachable,
115 * false otherwise.
116 *
117 * @param deviceId device id
118 * @return true if reachable, false otherwise
119 */
120 public static boolean ping(DeviceId deviceId) {
121 // poll ports status as workaround to assess device reachability
122 try {
123 of(deviceId).stdClient.bm_dev_mgr_show_ports();
124 return true;
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700125 } catch (TException | Bmv2RuntimeException e) {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800126 return false;
127 }
128 }
129
130 /**
131 * Parse device ID into host and port.
132 *
133 * @param did device ID
134 * @return a pair of host and port
135 */
136 private static Pair<String, Integer> parseDeviceId(DeviceId did) {
137 String[] info = did.toString().split(":");
138 if (info.length == 3) {
139 String host = info[1];
140 int port = Integer.parseInt(info[2]);
141 return ImmutablePair.of(host, port);
142 } else {
143 throw new IllegalArgumentException(
144 "Unable to parse BMv2 device ID "
145 + did.toString()
146 + ", expected format is scheme:host:port");
147 }
148 }
149
150 /**
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700151 * Builds a list of Bmv2/Thrift compatible match parameters.
152 *
153 * @param matchKey a bmv2 matchKey
154 * @return list of thrift-compatible bm match parameters
155 */
156 private static List<BmMatchParam> buildMatchParamsList(Bmv2MatchKey matchKey) {
157 List<BmMatchParam> paramsList = Lists.newArrayList();
158 matchKey.matchParams().forEach(x -> {
159 switch (x.type()) {
160 case EXACT:
161 paramsList.add(
162 new BmMatchParam(BmMatchParamType.EXACT)
163 .setExact(new BmMatchParamExact(
164 ((Bmv2ExactMatchParam) x).value().asReadOnlyBuffer())));
165 break;
166 case TERNARY:
167 paramsList.add(
168 new BmMatchParam(BmMatchParamType.TERNARY)
169 .setTernary(new BmMatchParamTernary(
170 ((Bmv2TernaryMatchParam) x).value().asReadOnlyBuffer(),
171 ((Bmv2TernaryMatchParam) x).mask().asReadOnlyBuffer())));
172 break;
173 case LPM:
174 paramsList.add(
175 new BmMatchParam(BmMatchParamType.LPM)
176 .setLpm(new BmMatchParamLPM(
177 ((Bmv2LpmMatchParam) x).value().asReadOnlyBuffer(),
178 ((Bmv2LpmMatchParam) x).prefixLength())));
179 break;
180 case VALID:
181 paramsList.add(
182 new BmMatchParam(BmMatchParamType.VALID)
183 .setValid(new BmMatchParamValid(
184 ((Bmv2ValidMatchParam) x).flag())));
185 break;
186 default:
187 // should never be here
188 throw new RuntimeException("Unknown match param type " + x.type().name());
189 }
190 });
191 return paramsList;
192 }
193
194 /**
195 * Build a list of Bmv2/Thrift compatible action parameters.
196 *
197 * @param action an action object
198 * @return list of ByteBuffers
199 */
200 private static List<ByteBuffer> buildActionParamsList(Bmv2Action action) {
201 return action.parameters()
202 .stream()
203 .map(ImmutableByteSequence::asReadOnlyBuffer)
204 .collect(Collectors.toList());
205 }
206
207 /**
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800208 * Adds a new table entry.
209 *
210 * @param entry a table entry value
211 * @return table-specific entry ID
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700212 * @throws Bmv2RuntimeException if any error occurs
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800213 */
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700214 public final long addTableEntry(Bmv2TableEntry entry) throws Bmv2RuntimeException {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800215
216 long entryId = -1;
217
218 try {
219 BmAddEntryOptions options = new BmAddEntryOptions();
220
221 if (entry.hasPriority()) {
222 options.setPriority(entry.priority());
223 }
224
225 entryId = stdClient.bm_mt_add_entry(
226 CONTEXT_ID,
227 entry.tableName(),
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700228 buildMatchParamsList(entry.matchKey()),
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800229 entry.action().name(),
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700230 buildActionParamsList(entry.action()),
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800231 options);
232
233 if (entry.hasTimeout()) {
234 /* bmv2 accepts timeouts in milliseconds */
235 int msTimeout = (int) Math.round(entry.timeout() * 1_000);
236 stdClient.bm_mt_set_entry_ttl(
237 CONTEXT_ID, entry.tableName(), entryId, msTimeout);
238 }
239
240 return entryId;
241
242 } catch (TException e) {
243 if (entryId != -1) {
244 try {
245 stdClient.bm_mt_delete_entry(
246 CONTEXT_ID, entry.tableName(), entryId);
247 } catch (TException e1) {
248 // this should never happen as we know the entry is there
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700249 throw new Bmv2RuntimeException(e1.getMessage(), e1);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800250 }
251 }
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700252 throw new Bmv2RuntimeException(e.getMessage(), e);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800253 }
254 }
255
256 /**
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
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700262 * @throws Bmv2RuntimeException if any error occurs
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800263 */
264 public final void modifyTableEntry(String tableName,
265 long entryId, Bmv2Action action)
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700266 throws Bmv2RuntimeException {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800267
268 try {
269 stdClient.bm_mt_modify_entry(
270 CONTEXT_ID,
271 tableName,
272 entryId,
273 action.name(),
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700274 buildActionParamsList(action));
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800275 } catch (TException e) {
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700276 throw new Bmv2RuntimeException(e.getMessage(), e);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800277 }
278 }
279
280 /**
281 * Deletes currently installed entry.
282 *
283 * @param tableName string value of table name
284 * @param entryId long value of entry ID
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700285 * @throws Bmv2RuntimeException if any error occurs
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800286 */
287 public final void deleteTableEntry(String tableName,
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700288 long entryId) throws Bmv2RuntimeException {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800289
290 try {
291 stdClient.bm_mt_delete_entry(CONTEXT_ID, tableName, entryId);
292 } catch (TException e) {
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700293 throw new Bmv2RuntimeException(e.getMessage(), e);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800294 }
295 }
296
297 /**
298 * Sets table default action.
299 *
300 * @param tableName string value of table name
301 * @param action an action value
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700302 * @throws Bmv2RuntimeException if any error occurs
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800303 */
304 public final void setTableDefaultAction(String tableName, Bmv2Action action)
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700305 throws Bmv2RuntimeException {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800306
307 try {
308 stdClient.bm_mt_set_default_action(
309 CONTEXT_ID,
310 tableName,
311 action.name(),
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700312 buildActionParamsList(action));
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800313 } catch (TException e) {
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700314 throw new Bmv2RuntimeException(e.getMessage(), e);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800315 }
316 }
317
318 /**
319 * Returns information of the ports currently configured in the switch.
320 *
321 * @return collection of port information
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700322 * @throws Bmv2RuntimeException if any error occurs
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800323 */
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700324 public Collection<Bmv2PortInfo> getPortsInfo() throws Bmv2RuntimeException {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800325
326 try {
327 List<DevMgrPortInfo> portInfos = stdClient.bm_dev_mgr_show_ports();
328
329 Collection<Bmv2PortInfo> bmv2PortInfos = Lists.newArrayList();
330
331 bmv2PortInfos.addAll(
332 portInfos.stream()
333 .map(Bmv2PortInfo::new)
334 .collect(Collectors.toList()));
335
336 return bmv2PortInfos;
337
338 } catch (TException e) {
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700339 throw new Bmv2RuntimeException(e.getMessage(), e);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800340 }
341 }
342
343 /**
344 * Return a string representation of a table content.
345 *
346 * @param tableName string value of table name
347 * @return table string dump
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700348 * @throws Bmv2RuntimeException if any error occurs
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800349 */
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700350 public String dumpTable(String tableName) throws Bmv2RuntimeException {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800351
352 try {
353 return stdClient.bm_dump_table(CONTEXT_ID, tableName);
354 } catch (TException e) {
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700355 throw new Bmv2RuntimeException(e.getMessage(), e);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800356 }
357 }
358
359 /**
360 * Reset the state of the switch (e.g. delete all entries, etc.).
361 *
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700362 * @throws Bmv2RuntimeException if any error occurs
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800363 */
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700364 public void resetState() throws Bmv2RuntimeException {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800365
366 try {
367 stdClient.bm_reset_state();
368 } catch (TException e) {
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700369 throw new Bmv2RuntimeException(e.getMessage(), e);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800370 }
371 }
372
373 /**
374 * Transport/client cache loader.
375 */
376 private static class ClientLoader
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700377 extends CacheLoader<DeviceId, Bmv2ThriftClient> {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800378
379 @Override
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700380 public Bmv2ThriftClient load(DeviceId deviceId)
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800381 throws TTransportException {
382 Pair<String, Integer> info = parseDeviceId(deviceId);
383 //make the expensive call
384 TTransport transport = new TSocket(
385 info.getLeft(), info.getRight());
386 TProtocol protocol = new TBinaryProtocol(transport);
387 Standard.Iface stdClient = new Standard.Client(
388 new TMultiplexedProtocol(protocol, "standard"));
389
390 transport.open();
391
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700392 return new Bmv2ThriftClient(transport, stdClient);
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800393 }
394 }
395
396 /**
397 * Client cache removal listener. Close the connection on cache removal.
398 */
399 private static class ClientRemovalListener implements
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700400 RemovalListener<DeviceId, Bmv2ThriftClient> {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800401
402 @Override
403 public void onRemoval(
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700404 RemovalNotification<DeviceId, Bmv2ThriftClient> notification) {
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800405 // close the transport connection
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700406 notification.getValue().closeTransport();
Carmelo Cascone2ea177b2016-02-25 18:38:42 -0800407 }
408 }
409}