blob: c3c86c1217295fe3faf5adddf3d90a1eb3424fa0 [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;
34import org.onosproject.bmv2.api.Bmv2Action;
35import org.onosproject.bmv2.api.Bmv2Exception;
36import org.onosproject.bmv2.api.Bmv2PortInfo;
37import org.onosproject.bmv2.api.Bmv2TableEntry;
38import org.onosproject.net.DeviceId;
39import org.p4.bmv2.thrift.BmAddEntryOptions;
40import org.p4.bmv2.thrift.DevMgrPortInfo;
41import org.p4.bmv2.thrift.Standard;
42
43import java.util.Collection;
44import java.util.List;
45import java.util.concurrent.ExecutionException;
46import java.util.concurrent.TimeUnit;
47import java.util.stream.Collectors;
48
49import static com.google.common.base.Preconditions.checkNotNull;
50
51/**
52 * Implementation of a Thrift client to control the Bmv2 switch.
53 */
54public final class Bmv2ThriftClient {
55 /*
56 FIXME: derive context_id from device id
57 Using different context id values should serve to control different
58 switches responding to the same IP address and port
59 */
60 private static final int CONTEXT_ID = 0;
61 /*
62 Static transport/client cache:
63 - avoids opening a new transport session when there's one already open
64 - close the connection after a predefined timeout of 5 seconds
65 */
66 private static LoadingCache<DeviceId, Pair<TTransport, Standard.Iface>>
67 clientCache = CacheBuilder.newBuilder()
68 .expireAfterAccess(5, TimeUnit.SECONDS)
69 .removalListener(new ClientRemovalListener())
70 .build(new ClientLoader());
71 private final Standard.Iface stdClient;
72
73 // ban constructor
74 private Bmv2ThriftClient(Standard.Iface stdClient) {
75 this.stdClient = stdClient;
76 }
77
78 /**
79 * Returns a client object to control the passed device.
80 *
81 * @param deviceId device id
82 * @return bmv2 client object
83 * @throws Bmv2Exception if a connection to the device cannot be established
84 */
85 public static Bmv2ThriftClient of(DeviceId deviceId) throws Bmv2Exception {
86 try {
87 checkNotNull(deviceId, "deviceId cannot be null");
88 return new Bmv2ThriftClient(clientCache.get(deviceId).getValue());
89 } catch (ExecutionException e) {
90 throw new Bmv2Exception(e.getMessage(), e.getCause());
91 }
92 }
93
94 /**
95 * Pings the device. Returns true if the device is reachable,
96 * false otherwise.
97 *
98 * @param deviceId device id
99 * @return true if reachable, false otherwise
100 */
101 public static boolean ping(DeviceId deviceId) {
102 // poll ports status as workaround to assess device reachability
103 try {
104 of(deviceId).stdClient.bm_dev_mgr_show_ports();
105 return true;
106 } catch (TException | Bmv2Exception e) {
107 return false;
108 }
109 }
110
111 /**
112 * Parse device ID into host and port.
113 *
114 * @param did device ID
115 * @return a pair of host and port
116 */
117 private static Pair<String, Integer> parseDeviceId(DeviceId did) {
118 String[] info = did.toString().split(":");
119 if (info.length == 3) {
120 String host = info[1];
121 int port = Integer.parseInt(info[2]);
122 return ImmutablePair.of(host, port);
123 } else {
124 throw new IllegalArgumentException(
125 "Unable to parse BMv2 device ID "
126 + did.toString()
127 + ", expected format is scheme:host:port");
128 }
129 }
130
131 /**
132 * Adds a new table entry.
133 *
134 * @param entry a table entry value
135 * @return table-specific entry ID
136 * @throws Bmv2Exception if any error occurs
137 */
138 public final long addTableEntry(Bmv2TableEntry entry) throws Bmv2Exception {
139
140 long entryId = -1;
141
142 try {
143 BmAddEntryOptions options = new BmAddEntryOptions();
144
145 if (entry.hasPriority()) {
146 options.setPriority(entry.priority());
147 }
148
149 entryId = stdClient.bm_mt_add_entry(
150 CONTEXT_ID,
151 entry.tableName(),
152 entry.matchKey().bmMatchParams(),
153 entry.action().name(),
154 entry.action().parameters(),
155 options);
156
157 if (entry.hasTimeout()) {
158 /* bmv2 accepts timeouts in milliseconds */
159 int msTimeout = (int) Math.round(entry.timeout() * 1_000);
160 stdClient.bm_mt_set_entry_ttl(
161 CONTEXT_ID, entry.tableName(), entryId, msTimeout);
162 }
163
164 return entryId;
165
166 } catch (TException e) {
167 if (entryId != -1) {
168 try {
169 stdClient.bm_mt_delete_entry(
170 CONTEXT_ID, entry.tableName(), entryId);
171 } catch (TException e1) {
172 // this should never happen as we know the entry is there
173 throw new Bmv2Exception(e1.getMessage(), e1);
174 }
175 }
176 throw new Bmv2Exception(e.getMessage(), e);
177 }
178 }
179
180 /**
181 * Modifies a currently installed entry by updating its action.
182 *
183 * @param tableName string value of table name
184 * @param entryId long value of entry ID
185 * @param action an action value
186 * @throws Bmv2Exception if any error occurs
187 */
188 public final void modifyTableEntry(String tableName,
189 long entryId, Bmv2Action action)
190 throws Bmv2Exception {
191
192 try {
193 stdClient.bm_mt_modify_entry(
194 CONTEXT_ID,
195 tableName,
196 entryId,
197 action.name(),
198 action.parameters()
199 );
200 } catch (TException e) {
201 throw new Bmv2Exception(e.getMessage(), e);
202 }
203 }
204
205 /**
206 * Deletes currently installed entry.
207 *
208 * @param tableName string value of table name
209 * @param entryId long value of entry ID
210 * @throws Bmv2Exception if any error occurs
211 */
212 public final void deleteTableEntry(String tableName,
213 long entryId) throws Bmv2Exception {
214
215 try {
216 stdClient.bm_mt_delete_entry(CONTEXT_ID, tableName, entryId);
217 } catch (TException e) {
218 throw new Bmv2Exception(e.getMessage(), e);
219 }
220 }
221
222 /**
223 * Sets table default action.
224 *
225 * @param tableName string value of table name
226 * @param action an action value
227 * @throws Bmv2Exception if any error occurs
228 */
229 public final void setTableDefaultAction(String tableName, Bmv2Action action)
230 throws Bmv2Exception {
231
232 try {
233 stdClient.bm_mt_set_default_action(
234 CONTEXT_ID,
235 tableName,
236 action.name(),
237 action.parameters());
238 } catch (TException e) {
239 throw new Bmv2Exception(e.getMessage(), e);
240 }
241 }
242
243 /**
244 * Returns information of the ports currently configured in the switch.
245 *
246 * @return collection of port information
247 * @throws Bmv2Exception if any error occurs
248 */
249 public Collection<Bmv2PortInfo> getPortsInfo() throws Bmv2Exception {
250
251 try {
252 List<DevMgrPortInfo> portInfos = stdClient.bm_dev_mgr_show_ports();
253
254 Collection<Bmv2PortInfo> bmv2PortInfos = Lists.newArrayList();
255
256 bmv2PortInfos.addAll(
257 portInfos.stream()
258 .map(Bmv2PortInfo::new)
259 .collect(Collectors.toList()));
260
261 return bmv2PortInfos;
262
263 } catch (TException e) {
264 throw new Bmv2Exception(e.getMessage(), e);
265 }
266 }
267
268 /**
269 * Return a string representation of a table content.
270 *
271 * @param tableName string value of table name
272 * @return table string dump
273 * @throws Bmv2Exception if any error occurs
274 */
275 public String dumpTable(String tableName) throws Bmv2Exception {
276
277 try {
278 return stdClient.bm_dump_table(CONTEXT_ID, tableName);
279 } catch (TException e) {
280 throw new Bmv2Exception(e.getMessage(), e);
281 }
282 }
283
284 /**
285 * Reset the state of the switch (e.g. delete all entries, etc.).
286 *
287 * @throws Bmv2Exception if any error occurs
288 */
289 public void resetState() throws Bmv2Exception {
290
291 try {
292 stdClient.bm_reset_state();
293 } catch (TException e) {
294 throw new Bmv2Exception(e.getMessage(), e);
295 }
296 }
297
298 /**
299 * Transport/client cache loader.
300 */
301 private static class ClientLoader
302 extends CacheLoader<DeviceId, Pair<TTransport, Standard.Iface>> {
303
304 @Override
305 public Pair<TTransport, Standard.Iface> load(DeviceId deviceId)
306 throws TTransportException {
307 Pair<String, Integer> info = parseDeviceId(deviceId);
308 //make the expensive call
309 TTransport transport = new TSocket(
310 info.getLeft(), info.getRight());
311 TProtocol protocol = new TBinaryProtocol(transport);
312 Standard.Iface stdClient = new Standard.Client(
313 new TMultiplexedProtocol(protocol, "standard"));
314
315 transport.open();
316
317 return ImmutablePair.of(transport, stdClient);
318 }
319 }
320
321 /**
322 * Client cache removal listener. Close the connection on cache removal.
323 */
324 private static class ClientRemovalListener implements
325 RemovalListener<DeviceId, Pair<TTransport, Standard.Iface>> {
326
327 @Override
328 public void onRemoval(
329 RemovalNotification<DeviceId, Pair<TTransport, Standard.Iface>>
330 notification) {
331 // close the transport connection
332 notification.getValue().getKey().close();
333 }
334 }
335}