blob: ae852c3edade0e178dc64fc49f2dcf7d0223cbcf [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;
29import org.onosproject.bmv2.api.runtime.Bmv2PortInfo;
30import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException;
31import org.onosproject.bmv2.api.runtime.Bmv2TableEntry;
32import org.onosproject.bmv2.api.runtime.Bmv2TernaryMatchParam;
33import org.onosproject.bmv2.api.runtime.Bmv2ValidMatchParam;
34import org.onosproject.bmv2.thriftapi.BmAddEntryOptions;
35import org.onosproject.bmv2.thriftapi.BmCounterValue;
36import org.onosproject.bmv2.thriftapi.BmMatchParam;
37import org.onosproject.bmv2.thriftapi.BmMatchParamExact;
38import org.onosproject.bmv2.thriftapi.BmMatchParamLPM;
39import org.onosproject.bmv2.thriftapi.BmMatchParamTernary;
40import org.onosproject.bmv2.thriftapi.BmMatchParamType;
41import org.onosproject.bmv2.thriftapi.BmMatchParamValid;
42import org.onosproject.bmv2.thriftapi.SimpleSwitch;
43import org.onosproject.bmv2.thriftapi.Standard;
44import org.onosproject.net.DeviceId;
45import org.slf4j.Logger;
46import org.slf4j.LoggerFactory;
47
48import java.nio.ByteBuffer;
49import java.util.Collection;
50import java.util.List;
51import java.util.stream.Collectors;
52
53import static org.onosproject.bmv2.ctl.Bmv2TExceptionParser.parseTException;
54
55/**
56 * Implementation of a Thrift client to control a BMv2 device.
57 */
58public final class Bmv2DeviceThriftClient implements Bmv2DeviceAgent {
59
60 private final Logger log = LoggerFactory.getLogger(this.getClass());
61
62 // FIXME: make context_id arbitrary for each call
63 // See: https://github.com/p4lang/behavioral-model/blob/master/modules/bm_sim/include/bm_sim/context.h
64 private static final int CONTEXT_ID = 0;
65
66 private final Standard.Iface standardClient;
67 private final SimpleSwitch.Iface simpleSwitchClient;
68 private final TTransport transport;
69 private final DeviceId deviceId;
70
71 // ban constructor
72 protected Bmv2DeviceThriftClient(DeviceId deviceId, TTransport transport, Standard.Iface standardClient,
73 SimpleSwitch.Iface simpleSwitchClient) {
74 this.deviceId = deviceId;
75 this.transport = transport;
76 this.standardClient = standardClient;
77 this.simpleSwitchClient = simpleSwitchClient;
78 }
79
80 @Override
81 public DeviceId deviceId() {
82 return deviceId;
83 }
84
85 @Override
86 public boolean ping() {
87 try {
88 return this.simpleSwitchClient.ping();
89 } catch (TException e) {
90 return false;
91 }
92 }
93
94 @Override
95 public final long addTableEntry(Bmv2TableEntry entry) throws Bmv2RuntimeException {
96
97 log.debug("Adding table entry... > deviceId={}, entry={}", deviceId, entry);
98
99 long entryId = -1;
100
101 try {
102 BmAddEntryOptions options = new BmAddEntryOptions();
103
104 if (entry.hasPriority()) {
105 options.setPriority(entry.priority());
106 }
107
108 entryId = standardClient.bm_mt_add_entry(
109 CONTEXT_ID,
110 entry.tableName(),
111 buildMatchParamsList(entry.matchKey()),
112 entry.action().name(),
113 buildActionParamsList(entry.action()),
114 options);
115
116 if (entry.hasTimeout()) {
117 /* bmv2 accepts timeouts in milliseconds */
118 int msTimeout = (int) Math.round(entry.timeout() * 1_000);
119 standardClient.bm_mt_set_entry_ttl(
120 CONTEXT_ID, entry.tableName(), entryId, msTimeout);
121 }
122
123 log.debug("Table entry added! > deviceId={}, entryId={}/{}", deviceId, entry.tableName(), entryId);
124
125 return entryId;
126
127 } catch (TException e) {
128 log.debug("Exception while adding table entry: {} > deviceId={}, tableName={}",
129 e, deviceId, entry.tableName());
130 if (entryId != -1) {
131 // entry is in inconsistent state (unable to add timeout), remove it
132 try {
133 deleteTableEntry(entry.tableName(), entryId);
134 } catch (Bmv2RuntimeException e1) {
135 log.debug("Unable to remove failed table entry: {} > deviceId={}, tableName={}",
136 e1, deviceId, entry.tableName());
137 }
138 }
139 throw parseTException(e);
140 }
141 }
142
143 @Override
144 public final void modifyTableEntry(String tableName,
145 long entryId, Bmv2Action action)
146 throws Bmv2RuntimeException {
147
148 log.debug("Modifying table entry... > deviceId={}, entryId={}/{}", deviceId, tableName, entryId);
149
150 try {
151 standardClient.bm_mt_modify_entry(
152 CONTEXT_ID,
153 tableName,
154 entryId,
155 action.name(),
156 buildActionParamsList(action));
157 log.debug("Table entry modified! > deviceId={}, entryId={}/{}", deviceId, tableName, entryId);
158 } catch (TException e) {
159 log.debug("Exception while modifying table entry: {} > deviceId={}, entryId={}/{}",
160 e, deviceId, tableName, entryId);
161 throw parseTException(e);
162 }
163 }
164
165 @Override
166 public final void deleteTableEntry(String tableName,
167 long entryId) throws Bmv2RuntimeException {
168
169 log.debug("Deleting table entry... > deviceId={}, entryId={}/{}", deviceId, tableName, entryId);
170
171 try {
172 standardClient.bm_mt_delete_entry(CONTEXT_ID, tableName, entryId);
173 log.debug("Table entry deleted! > deviceId={}, entryId={}/{}", deviceId, tableName, entryId);
174 } catch (TException e) {
175 log.debug("Exception while deleting table entry: {} > deviceId={}, entryId={}/{}",
176 e, deviceId, tableName, entryId);
177 throw parseTException(e);
178 }
179 }
180
181 @Override
182 public final void setTableDefaultAction(String tableName, Bmv2Action action)
183 throws Bmv2RuntimeException {
184
185 log.debug("Setting table default... > deviceId={}, tableName={}, action={}", deviceId, tableName, action);
186
187 try {
188 standardClient.bm_mt_set_default_action(
189 CONTEXT_ID,
190 tableName,
191 action.name(),
192 buildActionParamsList(action));
193 log.debug("Table default set! > deviceId={}, tableName={}, action={}", deviceId, tableName, action);
194 } catch (TException e) {
195 log.debug("Exception while setting table default : {} > deviceId={}, tableName={}, action={}",
196 e, deviceId, tableName, action);
197 throw parseTException(e);
198 }
199 }
200
201 @Override
202 public Collection<Bmv2PortInfo> getPortsInfo() throws Bmv2RuntimeException {
203
204 log.debug("Retrieving port info... > deviceId={}", deviceId);
205
206 try {
207 return standardClient.bm_dev_mgr_show_ports().stream()
208 .map(p -> new Bmv2PortInfo(p.getIface_name(), p.getPort_num(), p.isIs_up()))
209 .collect(Collectors.toList());
210 } catch (TException e) {
211 log.debug("Exception while retrieving port info: {} > deviceId={}", e, deviceId);
212 throw parseTException(e);
213 }
214 }
215
216 @Override
217 public String dumpTable(String tableName) throws Bmv2RuntimeException {
218
219 log.debug("Retrieving table dump... > deviceId={}, tableName={}", deviceId, tableName);
220
221 try {
222 String dump = standardClient.bm_dump_table(CONTEXT_ID, tableName);
223 log.debug("Table dump retrieved! > deviceId={}, tableName={}", deviceId, tableName);
224 return dump;
225 } catch (TException e) {
226 log.debug("Exception while retrieving table dump: {} > deviceId={}, tableName={}",
227 e, deviceId, tableName);
228 throw parseTException(e);
229 }
230 }
231
232 @Override
233 public void transmitPacket(int portNumber, ImmutableByteSequence packet) throws Bmv2RuntimeException {
234
235 log.debug("Requesting packet transmission... > portNumber={}, packetSize={}", portNumber, packet.size());
236
237 try {
238
239 simpleSwitchClient.push_packet(portNumber, ByteBuffer.wrap(packet.asArray()));
240 log.debug("Packet transmission requested! > portNumber={}, packetSize={}", portNumber, packet.size());
241 } catch (TException e) {
242 log.debug("Exception while requesting packet transmission: {} > portNumber={}, packetSize={}",
243 e, portNumber, packet.size());
244 throw parseTException(e);
245 }
246 }
247
248 @Override
249 public void resetState() throws Bmv2RuntimeException {
250
251 log.debug("Resetting device state... > deviceId={}", deviceId);
252
253 try {
254 standardClient.bm_reset_state();
255 log.debug("Device state reset! > deviceId={}", deviceId);
256 } catch (TException e) {
257 log.debug("Exception while resetting device state: {} > deviceId={}", e, deviceId);
258 throw parseTException(e);
259 }
260 }
261
262 @Override
263 public String dumpJsonConfig() throws Bmv2RuntimeException {
264
265 log.debug("Dumping device config... > deviceId={}", deviceId);
266
267 try {
268 String config = standardClient.bm_get_config();
269 log.debug("Device config dumped! > deviceId={}, configLength={}", deviceId, config.length());
270 return config;
271 } catch (TException e) {
272 log.debug("Exception while dumping device config: {} > deviceId={}", e, deviceId);
273 throw parseTException(e);
274 }
275 }
276
277 @Override
278 public Pair<Long, Long> readTableEntryCounter(String tableName, long entryId) throws Bmv2RuntimeException {
279
280 log.debug("Reading table entry counters... > deviceId={}, tableName={}, entryId={}",
281 deviceId, tableName, entryId);
282
283 try {
284 BmCounterValue counterValue = standardClient.bm_mt_read_counter(CONTEXT_ID, tableName, entryId);
285 log.debug("Table entry counters retrieved! > deviceId={}, tableName={}, entryId={}, bytes={}, packets={}",
286 deviceId, tableName, entryId, counterValue.bytes, counterValue.packets);
287 return Pair.of(counterValue.bytes, counterValue.packets);
288 } catch (TException e) {
289 log.debug("Exception while reading table counters: {} > deviceId={}, tableName={}, entryId={}",
290 e.toString(), deviceId);
291 throw parseTException(e);
292 }
293 }
294
295 @Override
296 public Pair<Long, Long> readCounter(String counterName, int index) throws Bmv2RuntimeException {
297
298 log.debug("Reading table entry counters... > deviceId={}, counterName={}, index={}",
299 deviceId, counterName, index);
300
301 try {
302 BmCounterValue counterValue = standardClient.bm_counter_read(CONTEXT_ID, counterName, index);
303 log.debug("Table entry counters retrieved! >deviceId={}, counterName={}, index={}, bytes={}, packets={}",
304 deviceId, counterName, index, counterValue.bytes, counterValue.packets);
305 return Pair.of(counterValue.bytes, counterValue.packets);
306 } catch (TException e) {
307 log.debug("Exception while reading table counters: {} > deviceId={}, counterName={}, index={}",
308 e.toString(), deviceId);
309 throw parseTException(e);
310 }
311 }
312
313 @Override
314 public int getProcessInstanceId() throws Bmv2RuntimeException {
315 log.debug("Getting process instance ID... > deviceId={}", deviceId);
316 try {
317 int instanceId = simpleSwitchClient.get_process_instance_id();
318 log.debug("TProcess instance ID retrieved! > deviceId={}, instanceId={}",
319 deviceId, instanceId);
320 return instanceId;
321 } catch (TException e) {
322 log.debug("Exception while getting process instance ID: {} > deviceId={}", e.toString(), deviceId);
323 throw parseTException(e);
324 }
325 }
326
327 @Override
328 public String getJsonConfigMd5() throws Bmv2RuntimeException {
329
330 log.debug("Getting device config md5... > deviceId={}", deviceId);
331
332 try {
333 String md5 = standardClient.bm_get_config_md5();
334 log.debug("Device config md5 received! > deviceId={}, configMd5={}", deviceId, md5);
335 return md5;
336 } catch (TException e) {
337 log.debug("Exception while getting device config md5: {} > deviceId={}", e, deviceId);
338 throw parseTException(e);
339 }
340 }
341
342 @Override
343 public void loadNewJsonConfig(String jsonString) throws Bmv2RuntimeException {
344
345 log.debug("Loading new JSON config on device... > deviceId={}, jsonStringLength={}",
346 deviceId, jsonString.length());
347
348 try {
349 standardClient.bm_load_new_config(jsonString);
350 log.debug("JSON config loaded! > deviceId={}", deviceId);
351 } catch (TException e) {
352 log.debug("Exception while loading JSON config: {} > deviceId={}", e, deviceId);
353 throw parseTException(e);
354 }
355 }
356
357 @Override
358 public void swapJsonConfig() throws Bmv2RuntimeException {
359
360 log.debug("Swapping JSON config on device... > deviceId={}", deviceId);
361
362 try {
363 standardClient.bm_swap_configs();
364 log.debug("JSON config swapped! > deviceId={}", deviceId);
365 } catch (TException e) {
366 log.debug("Exception while swapping JSON config: {} > deviceId={}", e, deviceId);
367 throw parseTException(e);
368 }
369 }
370
371 /**
372 * Builds a list of Bmv2/Thrift compatible match parameters.
373 *
374 * @param matchKey a bmv2 matchKey
375 * @return list of thrift-compatible bm match parameters
376 */
377 private static List<BmMatchParam> buildMatchParamsList(Bmv2MatchKey matchKey) {
378 List<BmMatchParam> paramsList = Lists.newArrayList();
379 matchKey.matchParams().forEach(x -> {
380 ByteBuffer value;
381 ByteBuffer mask;
382 switch (x.type()) {
383 case EXACT:
384 value = ByteBuffer.wrap(((Bmv2ExactMatchParam) x).value().asArray());
385 paramsList.add(
386 new BmMatchParam(BmMatchParamType.EXACT)
387 .setExact(new BmMatchParamExact(value)));
388 break;
389 case TERNARY:
390 value = ByteBuffer.wrap(((Bmv2TernaryMatchParam) x).value().asArray());
391 mask = ByteBuffer.wrap(((Bmv2TernaryMatchParam) x).mask().asArray());
392 paramsList.add(
393 new BmMatchParam(BmMatchParamType.TERNARY)
394 .setTernary(new BmMatchParamTernary(value, mask)));
395 break;
396 case LPM:
397 value = ByteBuffer.wrap(((Bmv2LpmMatchParam) x).value().asArray());
398 int prefixLength = ((Bmv2LpmMatchParam) x).prefixLength();
399 paramsList.add(
400 new BmMatchParam(BmMatchParamType.LPM)
401 .setLpm(new BmMatchParamLPM(value, prefixLength)));
402 break;
403 case VALID:
404 boolean flag = ((Bmv2ValidMatchParam) x).flag();
405 paramsList.add(
406 new BmMatchParam(BmMatchParamType.VALID)
407 .setValid(new BmMatchParamValid(flag)));
408 break;
409 default:
410 // should never be here
411 throw new RuntimeException("Unknown match param type " + x.type().name());
412 }
413 });
414 return paramsList;
415 }
416
417 /**
418 * Build a list of Bmv2/Thrift compatible action parameters.
419 *
420 * @param action an action object
421 * @return list of ByteBuffers
422 */
423 private static List<ByteBuffer> buildActionParamsList(Bmv2Action action) {
424 List<ByteBuffer> buffers = Lists.newArrayList();
425 action.parameters().forEach(p -> buffers.add(ByteBuffer.wrap(p.asArray())));
426 return buffers;
427 }
428}