blob: 04e37e5be1b7e5641486bb549b27e496267d8141 [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.cache.CacheBuilder;
20import com.google.common.cache.CacheLoader;
21import com.google.common.cache.CacheStats;
22import com.google.common.cache.LoadingCache;
23import com.google.common.collect.Lists;
24import org.apache.commons.lang3.tuple.Pair;
25import org.onlab.util.HexString;
26import org.onlab.util.ImmutableByteSequence;
27import org.onlab.util.SharedScheduledExecutors;
28import org.onosproject.bmv2.api.context.Bmv2ActionModel;
29import org.onosproject.bmv2.api.context.Bmv2Configuration;
30import org.onosproject.bmv2.api.runtime.Bmv2Action;
31import org.onosproject.bmv2.api.runtime.Bmv2ExactMatchParam;
32import org.onosproject.bmv2.api.runtime.Bmv2LpmMatchParam;
33import org.onosproject.bmv2.api.runtime.Bmv2MatchKey;
34import org.onosproject.bmv2.api.runtime.Bmv2ParsedTableEntry;
35import org.onosproject.bmv2.api.runtime.Bmv2TernaryMatchParam;
36import org.slf4j.Logger;
37import org.slf4j.LoggerFactory;
38
39import java.util.Arrays;
40import java.util.Collections;
41import java.util.List;
42import java.util.Optional;
43import java.util.concurrent.ExecutionException;
44import java.util.concurrent.TimeUnit;
45import java.util.regex.Matcher;
46import java.util.regex.Pattern;
47import java.util.stream.Collectors;
48
49import static com.google.common.base.Preconditions.checkNotNull;
50import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.fitByteSequence;
51import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.ByteSequenceFitException;
52
53/**
54 * BMv2 table dump parser.
55 */
56public final class Bmv2TableDumpParser {
57
58 // Examples of a BMv2 table dump can be found in Bmv2TableDumpParserTest
59
60 // 1: entry id, 2: match string, 3: action string
61 private static final String ENTRY_PATTERN_REGEX = "(\\d+): (.*) => (.*)";
62 // 1: match values, 2: masks
63 private static final String MATCH_TERNARY_PATTERN_REGEX = "([0-9a-fA-F ]+) &&& ([0-9a-fA-F ]+)";
64 // 1: match values, 2: masks
65 private static final String MATCH_LPM_PATTERN_REGEX = "([0-9a-fA-F ]+) / ([0-9a-fA-F ]+)";
66 // 1: match values
67 private static final String MATCH_EXACT_PATTERN_REGEX = "([0-9a-fA-F ]+)";
68 // 1: action name, 2: action params
69 private static final String ACTION_PATTERN_REGEX = "(.+) - ?([0-9a-fA-F ,]*)";
70
71 private static final Pattern ENTRY_PATTERN = Pattern.compile(ENTRY_PATTERN_REGEX);
72 private static final Pattern MATCH_TERNARY_PATTERN = Pattern.compile(MATCH_TERNARY_PATTERN_REGEX);
73 private static final Pattern MATCH_LPM_PATTERN = Pattern.compile(MATCH_LPM_PATTERN_REGEX);
74 private static final Pattern MATCH_EXACT_PATTERN = Pattern.compile(MATCH_EXACT_PATTERN_REGEX);
75 private static final Pattern ACTION_PATTERN = Pattern.compile(ACTION_PATTERN_REGEX);
76
77 // Cache to avoid re-parsing known lines.
78 // The assumption here is that entries are not updated too frequently, so that the entry id doesn't change often.
79 // Otherwise, we should cache only the match and action strings...
80 private static final LoadingCache<Pair<String, Bmv2Configuration>, Optional<Bmv2ParsedTableEntry>> ENTRY_CACHE =
81 CacheBuilder.newBuilder()
82 .expireAfterAccess(60, TimeUnit.SECONDS)
83 .recordStats()
84 .build(new CacheLoader<Pair<String, Bmv2Configuration>, Optional<Bmv2ParsedTableEntry>>() {
85 @Override
86 public Optional<Bmv2ParsedTableEntry> load(Pair<String, Bmv2Configuration> key) throws Exception {
87 // Very expensive call.
88 return Optional.ofNullable(parseLine(key.getLeft(), key.getRight()));
89 }
90 });
91
92 private static final Logger log = LoggerFactory.getLogger(Bmv2TableDumpParser.class);
93
94 private static final long STATS_LOG_FREQUENCY = 3; // minutes
95
96 static {
97 SharedScheduledExecutors.getSingleThreadExecutor().scheduleAtFixedRate(
98 () -> reportStats(), 0, STATS_LOG_FREQUENCY, TimeUnit.MINUTES);
99 }
100
101 private Bmv2TableDumpParser() {
102 // Ban constructor.
103 }
104
105 /**
106 * Parse the given BMv2 table dump.
107 *
108 * @param tableDump a string value
109 * @return a list of {@link Bmv2ParsedTableEntry}
110 */
111 public static List<Bmv2ParsedTableEntry> parse(String tableDump, Bmv2Configuration configuration) {
112 checkNotNull(tableDump, "tableDump cannot be null");
113 // Parse all lines
114 List<Bmv2ParsedTableEntry> result = Arrays.stream(tableDump.split("\n"))
115 .map(line -> Pair.of(line, configuration))
116 .map(Bmv2TableDumpParser::loadFromCache)
117 .filter(Optional::isPresent)
118 .map(Optional::get)
119 .collect(Collectors.toList());
120 return result;
121 }
122
123 private static Optional<Bmv2ParsedTableEntry> loadFromCache(Pair<String, Bmv2Configuration> key) {
124 try {
125 return ENTRY_CACHE.get(key);
126 } catch (ExecutionException e) {
127 Throwable t = e.getCause();
128 if (t instanceof Bmv2TableDumpParserException) {
129 Bmv2TableDumpParserException parserException = (Bmv2TableDumpParserException) t;
130 log.warn("{}", parserException.getMessage());
131 } else {
132 log.error("Exception while parsing table dump line", e);
133 }
134 return Optional.empty();
135 }
136 }
137
138 private static void reportStats() {
139 CacheStats stats = ENTRY_CACHE.stats();
140 log.info("Cache stats: requestCount={}, hitRate={}, exceptionsCount={}, avgLoadPenalty={}",
141 stats.requestCount(), stats.hitRate(), stats.loadExceptionCount(), stats.averageLoadPenalty());
142 }
143
144 private static Bmv2ParsedTableEntry parseLine(String line, Bmv2Configuration configuration)
145 throws Bmv2TableDumpParserException {
146 Matcher matcher = ENTRY_PATTERN.matcher(line);
147 if (matcher.find()) {
148 long entryId = parseEntryId(matcher, 1);
149 String matchString = parseMatchString(matcher, 2);
150 String actionString = parseActionString(matcher, 3);
151 Bmv2MatchKey matchKey = parseMatchKey(matchString);
152 Bmv2Action action = parseAction(actionString, configuration);
153 return new Bmv2ParsedTableEntry(entryId, matchKey, action);
154 } else {
155 // Not a table entry
156 return null;
157 }
158 }
159
160 private static Long parseEntryId(Matcher matcher, int groupIdx) throws Bmv2TableDumpParserException {
161 String str = matcher.group(groupIdx);
162 if (str == null) {
163 throw new Bmv2TableDumpParserException("Unable to find entry ID: " + matcher.group());
164 }
165 long entryId;
166 try {
167 entryId = Long.valueOf(str.trim());
168 } catch (NumberFormatException e) {
169 throw new Bmv2TableDumpParserException("Unable to parse entry id for string: " + matcher.group());
170 }
171 return entryId;
172 }
173
174 private static String parseMatchString(Matcher matcher, int groupIdx) throws Bmv2TableDumpParserException {
175 String str = matcher.group(groupIdx);
176 if (str == null) {
177 throw new Bmv2TableDumpParserException("Unable to find match string: " + matcher.group());
178 }
179 return str.trim();
180 }
181
182 private static String parseActionString(Matcher matcher, int groupIdx) throws Bmv2TableDumpParserException {
183 String str = matcher.group(groupIdx);
184 if (str == null) {
185 throw new Bmv2TableDumpParserException("Unable to find action string: " + matcher.group());
186 }
187 return str.trim();
188 }
189
190 private static Bmv2MatchKey parseMatchKey(String str) throws Bmv2TableDumpParserException {
191
192 Bmv2MatchKey.Builder builder = Bmv2MatchKey.builder();
193
194 // Try with ternary...
195 Matcher matcher = MATCH_TERNARY_PATTERN.matcher(str);
196 if (matcher.find()) {
197 // Ternary Match.
198 List<ImmutableByteSequence> values = parseMatchValues(matcher, 1);
199 List<ImmutableByteSequence> masks = parseMatchMasks(matcher, 2, values);
200 for (int i = 0; i < values.size(); i++) {
201 builder.add(new Bmv2TernaryMatchParam(values.get(i), masks.get(i)));
202 }
203 return builder.build();
204 }
205
206 // FIXME: LPM match parsing broken if table key contains also a ternary match
207 // Also it assumes the lpm parameter is the last one, which is wrong.
208 // Try with LPM...
209 matcher = MATCH_LPM_PATTERN.matcher(str);
210 if (matcher.find()) {
211 // Lpm Match.
212 List<ImmutableByteSequence> values = parseMatchValues(matcher, 1);
213 int prefixLength = parseLpmPrefix(matcher, 2);
214 for (int i = 0; i < values.size() - 1; i++) {
215 builder.add(new Bmv2ExactMatchParam(values.get(i)));
216 }
217 builder.add(new Bmv2LpmMatchParam(values.get(values.size() - 1), prefixLength));
218 return builder.build();
219 }
220
221 // Try with exact...
222 matcher = MATCH_EXACT_PATTERN.matcher(str);
223 if (matcher.find()) {
224 // Exact match.
225 parseMatchValues(matcher, 1)
226 .stream()
227 .map(Bmv2ExactMatchParam::new)
228 .forEach(builder::add);
229 return builder.build();
230 }
231
232 throw new Bmv2TableDumpParserException("Unable to parse match string: " + str);
233 }
234
235 private static List<ImmutableByteSequence> parseMatchValues(Matcher matcher, int groupIdx)
236 throws Bmv2TableDumpParserException {
237 String matchString = matcher.group(groupIdx);
238 if (matchString == null) {
239 throw new Bmv2TableDumpParserException("Unable to find match params for string: " + matcher.group());
240 }
241 List<ImmutableByteSequence> result = Lists.newArrayList();
242 for (String paramString : matchString.split(" ")) {
243 byte[] bytes = HexString.fromHexString(paramString, null);
244 result.add(ImmutableByteSequence.copyFrom(bytes));
245 }
246 return result;
247 }
248
249 private static List<ImmutableByteSequence> parseMatchMasks(Matcher matcher, int groupIdx,
250 List<ImmutableByteSequence> matchParams)
251 throws Bmv2TableDumpParserException {
252 String maskString = matcher.group(groupIdx);
253 if (maskString == null) {
254 throw new Bmv2TableDumpParserException("Unable to find mask for string: " + matcher.group());
255 }
256 List<ImmutableByteSequence> result = Lists.newArrayList();
257 /*
258 Mask here is a hex string with no spaces, hence individual mask params can be derived according
259 to given matchParam sizes.
260 */
261 byte[] maskBytes = HexString.fromHexString(maskString, null);
262 int startPosition = 0;
263 for (ImmutableByteSequence bs : matchParams) {
264 if (startPosition + bs.size() > maskBytes.length) {
265 throw new Bmv2TableDumpParserException("Invalid length for mask in string: " + matcher.group());
266 }
267 ImmutableByteSequence maskParam = ImmutableByteSequence.copyFrom(maskBytes,
268 startPosition,
269 startPosition + bs.size() - 1);
270 result.add(maskParam);
271 startPosition += bs.size();
272 }
273 return result;
274 }
275
276 private static int parseLpmPrefix(Matcher matcher, int groupIdx)
277 throws Bmv2TableDumpParserException {
278 String str = matcher.group(groupIdx);
279 if (str == null) {
280 throw new Bmv2TableDumpParserException("Unable to find LPM prefix for string: " + matcher.group());
281 }
282 // For some reason the dumped prefix has 16 bits more than the one programmed
283 try {
284 return Integer.valueOf(str.trim()) - 16;
285 } catch (NumberFormatException e) {
286 throw new Bmv2TableDumpParserException("Unable to parse LPM prefix from string: " + matcher.group());
287 }
288 }
289
290 private static Bmv2Action parseAction(String str, Bmv2Configuration configuration)
291 throws Bmv2TableDumpParserException {
292 Matcher matcher = ACTION_PATTERN.matcher(str);
293 if (matcher.find()) {
294 String actionName = parseActionName(matcher, 1);
295 Bmv2ActionModel actionModel = configuration.action(actionName);
296 if (actionModel == null) {
297 throw new Bmv2TableDumpParserException("Not such an action in configuration: " + actionName);
298 }
299 Bmv2Action.Builder builder = Bmv2Action.builder().withName(actionName);
300 List<ImmutableByteSequence> actionParams = parseActionParams(matcher, 2);
301 if (actionParams.size() != actionModel.runtimeDatas().size()) {
302 throw new Bmv2TableDumpParserException("Invalid number of parameters for action: " + actionName);
303 }
304 for (int i = 0; i < actionModel.runtimeDatas().size(); i++) {
305 try {
306 // fit param byte-width according to configuration.
307 builder.addParameter(fitByteSequence(actionParams.get(i),
308 actionModel.runtimeDatas().get(i).bitWidth()));
309 } catch (ByteSequenceFitException e) {
310 throw new Bmv2TableDumpParserException("Unable to parse action param: " + e.toString());
311 }
312 }
313 return builder.build();
314 }
315 throw new Bmv2TableDumpParserException("Unable to parse action string: " + str.trim());
316 }
317
318 private static String parseActionName(Matcher matcher, int groupIdx) throws Bmv2TableDumpParserException {
319 String actionName = matcher.group(groupIdx);
320 if (actionName == null) {
321 throw new Bmv2TableDumpParserException("Unable to find action name for string: " + matcher.group());
322 }
323 return actionName.trim();
324 }
325
326 private static List<ImmutableByteSequence> parseActionParams(Matcher matcher, int groupIdx)
327 throws Bmv2TableDumpParserException {
328 String paramsString = matcher.group(groupIdx);
329 if (paramsString == null) {
330 throw new Bmv2TableDumpParserException("Unable to find action params for string: " + matcher.group());
331 }
332 if (paramsString.length() == 0) {
333 return Collections.emptyList();
334 }
335 return Arrays.stream(paramsString.split(","))
336 .map(String::trim)
337 .map(s -> HexString.fromHexString(s, null))
338 .map(ImmutableByteSequence::copyFrom)
339 .collect(Collectors.toList());
340 }
341
342 public static class Bmv2TableDumpParserException extends Exception {
343 public Bmv2TableDumpParserException(String msg) {
344 super(msg);
345 }
346 }
347}