blob: 94506b9706103a280d62326de75edbcb2141dbdf [file] [log] [blame]
Carmelo Cascone17fc9e42016-05-31 11:29:21 -07001/*
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
17package org.onosproject.bmv2.ctl;
18
19import com.google.common.collect.Lists;
20import org.apache.commons.lang3.tuple.Pair;
21import org.apache.thrift.TException;
22import org.apache.thrift.transport.TTransport;
23import org.onlab.util.ImmutableByteSequence;
24import org.onosproject.bmv2.api.runtime.Bmv2Action;
25import org.onosproject.bmv2.api.runtime.Bmv2DeviceAgent;
26import org.onosproject.bmv2.api.runtime.Bmv2ExactMatchParam;
27import org.onosproject.bmv2.api.runtime.Bmv2LpmMatchParam;
28import org.onosproject.bmv2.api.runtime.Bmv2MatchKey;
Carmelo Cascone25f18882016-06-14 19:16:50 -070029import org.onosproject.bmv2.api.runtime.Bmv2MatchParam;
30import org.onosproject.bmv2.api.runtime.Bmv2ParsedTableEntry;
Carmelo Cascone17fc9e42016-05-31 11:29:21 -070031import org.onosproject.bmv2.api.runtime.Bmv2PortInfo;
32import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException;
33import org.onosproject.bmv2.api.runtime.Bmv2TableEntry;
34import org.onosproject.bmv2.api.runtime.Bmv2TernaryMatchParam;
35import org.onosproject.bmv2.api.runtime.Bmv2ValidMatchParam;
Carmelo Cascone25f18882016-06-14 19:16:50 -070036import org.onosproject.bmv2.thriftapi.BmActionEntry;
Carmelo Cascone17fc9e42016-05-31 11:29:21 -070037import org.onosproject.bmv2.thriftapi.BmAddEntryOptions;
38import org.onosproject.bmv2.thriftapi.BmCounterValue;
39import org.onosproject.bmv2.thriftapi.BmMatchParam;
40import org.onosproject.bmv2.thriftapi.BmMatchParamExact;
41import org.onosproject.bmv2.thriftapi.BmMatchParamLPM;
42import org.onosproject.bmv2.thriftapi.BmMatchParamTernary;
43import org.onosproject.bmv2.thriftapi.BmMatchParamType;
44import org.onosproject.bmv2.thriftapi.BmMatchParamValid;
Carmelo Cascone25f18882016-06-14 19:16:50 -070045import org.onosproject.bmv2.thriftapi.BmMtEntry;
Carmelo Cascone17fc9e42016-05-31 11:29:21 -070046import org.onosproject.bmv2.thriftapi.SimpleSwitch;
47import org.onosproject.bmv2.thriftapi.Standard;
48import org.onosproject.net.DeviceId;
49import org.slf4j.Logger;
50import org.slf4j.LoggerFactory;
51
52import java.nio.ByteBuffer;
53import java.util.Collection;
54import java.util.List;
55import java.util.stream.Collectors;
56
Carmelo Cascone25f18882016-06-14 19:16:50 -070057import static org.onlab.util.ImmutableByteSequence.copyFrom;
Carmelo Cascone17fc9e42016-05-31 11:29:21 -070058import static org.onosproject.bmv2.ctl.Bmv2TExceptionParser.parseTException;
59
60/**
61 * Implementation of a Thrift client to control a BMv2 device.
62 */
63public final class Bmv2DeviceThriftClient implements Bmv2DeviceAgent {
64
65 private final Logger log = LoggerFactory.getLogger(this.getClass());
66
67 // FIXME: make context_id arbitrary for each call
68 // See: https://github.com/p4lang/behavioral-model/blob/master/modules/bm_sim/include/bm_sim/context.h
69 private static final int CONTEXT_ID = 0;
70
71 private final Standard.Iface standardClient;
72 private final SimpleSwitch.Iface simpleSwitchClient;
73 private final TTransport transport;
74 private final DeviceId deviceId;
75
76 // ban constructor
77 protected Bmv2DeviceThriftClient(DeviceId deviceId, TTransport transport, Standard.Iface standardClient,
78 SimpleSwitch.Iface simpleSwitchClient) {
79 this.deviceId = deviceId;
80 this.transport = transport;
81 this.standardClient = standardClient;
82 this.simpleSwitchClient = simpleSwitchClient;
83 }
84
85 @Override
86 public DeviceId deviceId() {
87 return deviceId;
88 }
89
90 @Override
91 public boolean ping() {
92 try {
93 return this.simpleSwitchClient.ping();
94 } catch (TException e) {
95 return false;
96 }
97 }
98
99 @Override
100 public final long addTableEntry(Bmv2TableEntry entry) throws Bmv2RuntimeException {
101
102 log.debug("Adding table entry... > deviceId={}, entry={}", deviceId, entry);
103
104 long entryId = -1;
105
106 try {
107 BmAddEntryOptions options = new BmAddEntryOptions();
108
109 if (entry.hasPriority()) {
110 options.setPriority(entry.priority());
111 }
112
113 entryId = standardClient.bm_mt_add_entry(
114 CONTEXT_ID,
115 entry.tableName(),
116 buildMatchParamsList(entry.matchKey()),
117 entry.action().name(),
118 buildActionParamsList(entry.action()),
119 options);
120
121 if (entry.hasTimeout()) {
122 /* bmv2 accepts timeouts in milliseconds */
123 int msTimeout = (int) Math.round(entry.timeout() * 1_000);
124 standardClient.bm_mt_set_entry_ttl(
125 CONTEXT_ID, entry.tableName(), entryId, msTimeout);
126 }
127
128 log.debug("Table entry added! > deviceId={}, entryId={}/{}", deviceId, entry.tableName(), entryId);
129
130 return entryId;
131
132 } catch (TException e) {
133 log.debug("Exception while adding table entry: {} > deviceId={}, tableName={}",
134 e, deviceId, entry.tableName());
135 if (entryId != -1) {
136 // entry is in inconsistent state (unable to add timeout), remove it
137 try {
138 deleteTableEntry(entry.tableName(), entryId);
139 } catch (Bmv2RuntimeException e1) {
140 log.debug("Unable to remove failed table entry: {} > deviceId={}, tableName={}",
141 e1, deviceId, entry.tableName());
142 }
143 }
144 throw parseTException(e);
145 }
146 }
147
148 @Override
149 public final void modifyTableEntry(String tableName,
150 long entryId, Bmv2Action action)
151 throws Bmv2RuntimeException {
152
153 log.debug("Modifying table entry... > deviceId={}, entryId={}/{}", deviceId, tableName, entryId);
154
155 try {
156 standardClient.bm_mt_modify_entry(
157 CONTEXT_ID,
158 tableName,
159 entryId,
160 action.name(),
161 buildActionParamsList(action));
162 log.debug("Table entry modified! > deviceId={}, entryId={}/{}", deviceId, tableName, entryId);
163 } catch (TException e) {
164 log.debug("Exception while modifying table entry: {} > deviceId={}, entryId={}/{}",
165 e, deviceId, tableName, entryId);
166 throw parseTException(e);
167 }
168 }
169
170 @Override
171 public final void deleteTableEntry(String tableName,
172 long entryId) throws Bmv2RuntimeException {
173
174 log.debug("Deleting table entry... > deviceId={}, entryId={}/{}", deviceId, tableName, entryId);
175
176 try {
177 standardClient.bm_mt_delete_entry(CONTEXT_ID, tableName, entryId);
178 log.debug("Table entry deleted! > deviceId={}, entryId={}/{}", deviceId, tableName, entryId);
179 } catch (TException e) {
180 log.debug("Exception while deleting table entry: {} > deviceId={}, entryId={}/{}",
181 e, deviceId, tableName, entryId);
182 throw parseTException(e);
183 }
184 }
185
186 @Override
187 public final void setTableDefaultAction(String tableName, Bmv2Action action)
188 throws Bmv2RuntimeException {
189
190 log.debug("Setting table default... > deviceId={}, tableName={}, action={}", deviceId, tableName, action);
191
192 try {
193 standardClient.bm_mt_set_default_action(
194 CONTEXT_ID,
195 tableName,
196 action.name(),
197 buildActionParamsList(action));
198 log.debug("Table default set! > deviceId={}, tableName={}, action={}", deviceId, tableName, action);
199 } catch (TException e) {
200 log.debug("Exception while setting table default : {} > deviceId={}, tableName={}, action={}",
201 e, deviceId, tableName, action);
202 throw parseTException(e);
203 }
204 }
205
206 @Override
207 public Collection<Bmv2PortInfo> getPortsInfo() throws Bmv2RuntimeException {
208
209 log.debug("Retrieving port info... > deviceId={}", deviceId);
210
211 try {
212 return standardClient.bm_dev_mgr_show_ports().stream()
213 .map(p -> new Bmv2PortInfo(p.getIface_name(), p.getPort_num(), p.isIs_up()))
214 .collect(Collectors.toList());
215 } catch (TException e) {
216 log.debug("Exception while retrieving port info: {} > deviceId={}", e, deviceId);
217 throw parseTException(e);
218 }
219 }
220
221 @Override
Carmelo Cascone25f18882016-06-14 19:16:50 -0700222 public List<Bmv2ParsedTableEntry> getTableEntries(String tableName) throws Bmv2RuntimeException {
Carmelo Cascone17fc9e42016-05-31 11:29:21 -0700223
Carmelo Cascone25f18882016-06-14 19:16:50 -0700224 log.debug("Retrieving table entries... > deviceId={}, tableName={}", deviceId, tableName);
Carmelo Cascone17fc9e42016-05-31 11:29:21 -0700225
Carmelo Cascone25f18882016-06-14 19:16:50 -0700226 List<BmMtEntry> bmEntries;
Carmelo Cascone17fc9e42016-05-31 11:29:21 -0700227 try {
Carmelo Cascone25f18882016-06-14 19:16:50 -0700228 bmEntries = standardClient.bm_mt_get_entries(CONTEXT_ID, tableName);
Carmelo Cascone17fc9e42016-05-31 11:29:21 -0700229 } catch (TException e) {
Carmelo Cascone25f18882016-06-14 19:16:50 -0700230 log.debug("Exception while retrieving table entries: {} > deviceId={}, tableName={}",
Carmelo Cascone17fc9e42016-05-31 11:29:21 -0700231 e, deviceId, tableName);
232 throw parseTException(e);
233 }
Carmelo Cascone25f18882016-06-14 19:16:50 -0700234
235 List<Bmv2ParsedTableEntry> parsedEntries = Lists.newArrayList();
236
237 entryLoop:
238 for (BmMtEntry bmEntry : bmEntries) {
239
240 Bmv2MatchKey.Builder matchKeyBuilder = Bmv2MatchKey.builder();
241 for (BmMatchParam bmParam : bmEntry.getMatch_key()) {
242 Bmv2MatchParam param;
243 switch (bmParam.getType()) {
244 case EXACT:
245 param = new Bmv2ExactMatchParam(copyFrom(bmParam.getExact().getKey()));
246 break;
247 case LPM:
248 param = new Bmv2LpmMatchParam(copyFrom(bmParam.getLpm().getKey()),
249 bmParam.getLpm().getPrefix_length());
250 break;
251 case TERNARY:
252 param = new Bmv2TernaryMatchParam(copyFrom(bmParam.getTernary().getKey()),
253 copyFrom(bmParam.getTernary().getMask()));
254 break;
255 case VALID:
256 param = new Bmv2ValidMatchParam(bmParam.getValid().isKey());
257 break;
258 default:
259 log.warn("Parsing of match type {} unsupported, skipping table entry.",
260 bmParam.getType().name());
261 continue entryLoop;
262 }
263 matchKeyBuilder.add(param);
264 }
265
266 Bmv2Action.Builder actionBuilder = Bmv2Action.builder();
267 BmActionEntry bmActionEntry = bmEntry.getAction_entry();
268 switch (bmActionEntry.getAction_type()) {
269 case ACTION_DATA:
270 actionBuilder.withName(bmActionEntry.getAction_name());
271 bmActionEntry.getAction_data()
272 .stream()
273 .map(ImmutableByteSequence::copyFrom)
274 .forEach(actionBuilder::addParameter);
275 break;
276 default:
277 log.warn("Parsing of action action type {} unsupported, skipping table entry.",
278 bmActionEntry.getAction_type().name());
279 continue entryLoop;
280 }
281
282 parsedEntries.add(new Bmv2ParsedTableEntry(bmEntry.getEntry_handle(), matchKeyBuilder.build(),
283 actionBuilder.build(), bmEntry.getOptions().getPriority()));
284 }
285
286 return parsedEntries;
Carmelo Cascone17fc9e42016-05-31 11:29:21 -0700287 }
288
289 @Override
290 public void transmitPacket(int portNumber, ImmutableByteSequence packet) throws Bmv2RuntimeException {
291
292 log.debug("Requesting packet transmission... > portNumber={}, packetSize={}", portNumber, packet.size());
293
294 try {
295
Carmelo Cascone25f18882016-06-14 19:16:50 -0700296 simpleSwitchClient.packet_out(portNumber, ByteBuffer.wrap(packet.asArray()));
Carmelo Cascone17fc9e42016-05-31 11:29:21 -0700297 log.debug("Packet transmission requested! > portNumber={}, packetSize={}", portNumber, packet.size());
298 } catch (TException e) {
299 log.debug("Exception while requesting packet transmission: {} > portNumber={}, packetSize={}",
300 e, portNumber, packet.size());
301 throw parseTException(e);
302 }
303 }
304
305 @Override
306 public void resetState() throws Bmv2RuntimeException {
307
308 log.debug("Resetting device state... > deviceId={}", deviceId);
309
310 try {
311 standardClient.bm_reset_state();
312 log.debug("Device state reset! > deviceId={}", deviceId);
313 } catch (TException e) {
314 log.debug("Exception while resetting device state: {} > deviceId={}", e, deviceId);
315 throw parseTException(e);
316 }
317 }
318
319 @Override
320 public String dumpJsonConfig() throws Bmv2RuntimeException {
321
322 log.debug("Dumping device config... > deviceId={}", deviceId);
323
324 try {
325 String config = standardClient.bm_get_config();
326 log.debug("Device config dumped! > deviceId={}, configLength={}", deviceId, config.length());
327 return config;
328 } catch (TException e) {
329 log.debug("Exception while dumping device config: {} > deviceId={}", e, deviceId);
330 throw parseTException(e);
331 }
332 }
333
334 @Override
335 public Pair<Long, Long> readTableEntryCounter(String tableName, long entryId) throws Bmv2RuntimeException {
336
337 log.debug("Reading table entry counters... > deviceId={}, tableName={}, entryId={}",
338 deviceId, tableName, entryId);
339
340 try {
341 BmCounterValue counterValue = standardClient.bm_mt_read_counter(CONTEXT_ID, tableName, entryId);
342 log.debug("Table entry counters retrieved! > deviceId={}, tableName={}, entryId={}, bytes={}, packets={}",
343 deviceId, tableName, entryId, counterValue.bytes, counterValue.packets);
344 return Pair.of(counterValue.bytes, counterValue.packets);
345 } catch (TException e) {
346 log.debug("Exception while reading table counters: {} > deviceId={}, tableName={}, entryId={}",
347 e.toString(), deviceId);
348 throw parseTException(e);
349 }
350 }
351
352 @Override
353 public Pair<Long, Long> readCounter(String counterName, int index) throws Bmv2RuntimeException {
354
355 log.debug("Reading table entry counters... > deviceId={}, counterName={}, index={}",
356 deviceId, counterName, index);
357
358 try {
359 BmCounterValue counterValue = standardClient.bm_counter_read(CONTEXT_ID, counterName, index);
360 log.debug("Table entry counters retrieved! >deviceId={}, counterName={}, index={}, bytes={}, packets={}",
361 deviceId, counterName, index, counterValue.bytes, counterValue.packets);
362 return Pair.of(counterValue.bytes, counterValue.packets);
363 } catch (TException e) {
364 log.debug("Exception while reading table counters: {} > deviceId={}, counterName={}, index={}",
365 e.toString(), deviceId);
366 throw parseTException(e);
367 }
368 }
369
370 @Override
371 public int getProcessInstanceId() throws Bmv2RuntimeException {
372 log.debug("Getting process instance ID... > deviceId={}", deviceId);
373 try {
374 int instanceId = simpleSwitchClient.get_process_instance_id();
375 log.debug("TProcess instance ID retrieved! > deviceId={}, instanceId={}",
376 deviceId, instanceId);
377 return instanceId;
378 } catch (TException e) {
379 log.debug("Exception while getting process instance ID: {} > deviceId={}", e.toString(), deviceId);
380 throw parseTException(e);
381 }
382 }
383
384 @Override
385 public String getJsonConfigMd5() throws Bmv2RuntimeException {
386
387 log.debug("Getting device config md5... > deviceId={}", deviceId);
388
389 try {
390 String md5 = standardClient.bm_get_config_md5();
391 log.debug("Device config md5 received! > deviceId={}, configMd5={}", deviceId, md5);
392 return md5;
393 } catch (TException e) {
394 log.debug("Exception while getting device config md5: {} > deviceId={}", e, deviceId);
395 throw parseTException(e);
396 }
397 }
398
399 @Override
400 public void loadNewJsonConfig(String jsonString) throws Bmv2RuntimeException {
401
402 log.debug("Loading new JSON config on device... > deviceId={}, jsonStringLength={}",
403 deviceId, jsonString.length());
404
405 try {
406 standardClient.bm_load_new_config(jsonString);
407 log.debug("JSON config loaded! > deviceId={}", deviceId);
408 } catch (TException e) {
409 log.debug("Exception while loading JSON config: {} > deviceId={}", e, deviceId);
410 throw parseTException(e);
411 }
412 }
413
414 @Override
415 public void swapJsonConfig() throws Bmv2RuntimeException {
416
417 log.debug("Swapping JSON config on device... > deviceId={}", deviceId);
418
419 try {
420 standardClient.bm_swap_configs();
421 log.debug("JSON config swapped! > deviceId={}", deviceId);
422 } catch (TException e) {
423 log.debug("Exception while swapping JSON config: {} > deviceId={}", e, deviceId);
424 throw parseTException(e);
425 }
426 }
427
428 /**
429 * Builds a list of Bmv2/Thrift compatible match parameters.
430 *
431 * @param matchKey a bmv2 matchKey
432 * @return list of thrift-compatible bm match parameters
433 */
434 private static List<BmMatchParam> buildMatchParamsList(Bmv2MatchKey matchKey) {
435 List<BmMatchParam> paramsList = Lists.newArrayList();
436 matchKey.matchParams().forEach(x -> {
437 ByteBuffer value;
438 ByteBuffer mask;
439 switch (x.type()) {
440 case EXACT:
441 value = ByteBuffer.wrap(((Bmv2ExactMatchParam) x).value().asArray());
442 paramsList.add(
443 new BmMatchParam(BmMatchParamType.EXACT)
444 .setExact(new BmMatchParamExact(value)));
445 break;
446 case TERNARY:
447 value = ByteBuffer.wrap(((Bmv2TernaryMatchParam) x).value().asArray());
448 mask = ByteBuffer.wrap(((Bmv2TernaryMatchParam) x).mask().asArray());
449 paramsList.add(
450 new BmMatchParam(BmMatchParamType.TERNARY)
451 .setTernary(new BmMatchParamTernary(value, mask)));
452 break;
453 case LPM:
454 value = ByteBuffer.wrap(((Bmv2LpmMatchParam) x).value().asArray());
455 int prefixLength = ((Bmv2LpmMatchParam) x).prefixLength();
456 paramsList.add(
457 new BmMatchParam(BmMatchParamType.LPM)
458 .setLpm(new BmMatchParamLPM(value, prefixLength)));
459 break;
460 case VALID:
461 boolean flag = ((Bmv2ValidMatchParam) x).flag();
462 paramsList.add(
463 new BmMatchParam(BmMatchParamType.VALID)
464 .setValid(new BmMatchParamValid(flag)));
465 break;
466 default:
467 // should never be here
468 throw new RuntimeException("Unknown match param type " + x.type().name());
469 }
470 });
471 return paramsList;
472 }
473
474 /**
475 * Build a list of Bmv2/Thrift compatible action parameters.
476 *
477 * @param action an action object
478 * @return list of ByteBuffers
479 */
480 private static List<ByteBuffer> buildActionParamsList(Bmv2Action action) {
481 List<ByteBuffer> buffers = Lists.newArrayList();
482 action.parameters().forEach(p -> buffers.add(ByteBuffer.wrap(p.asArray())));
483 return buffers;
484 }
485}