blob: c674753383f7e37117b60f8729d070138bf23835 [file] [log] [blame]
Yi Tsengf33c0772017-06-06 14:56:18 -07001/*
2 * Copyright 2017-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 */
16package org.onosproject.bmv2.model;
17
18import com.eclipsesource.json.JsonArray;
19import com.eclipsesource.json.JsonObject;
20import com.eclipsesource.json.JsonValue;
21import com.google.common.annotations.Beta;
22import com.google.common.collect.Lists;
23import com.google.common.collect.Maps;
24import com.google.common.collect.Sets;
25import org.onosproject.net.pi.model.PiMatchType;
26import org.slf4j.Logger;
27
28import java.util.List;
29import java.util.Map;
30import java.util.Set;
31
32import static org.slf4j.LoggerFactory.getLogger;
33
34/**
35 * BMv2 pipeline model parser.
36 *
37 * @see <a href="https://github.com/p4lang/behavioral-model/blob/master/docs/JSON_format.md">
38 * BMv2 JSON specification</a>
39 */
40@Beta
41public final class Bmv2PipelineModelParser {
42 private static final Logger log = getLogger(Bmv2PipelineModelParser.class);
43
44 // General fields and values
45 private static final String NAME = "name";
46 private static final String ID = "id";
47 private static final int NO_ID = Integer.MIN_VALUE;
48
49 // Hide default parser
50 private Bmv2PipelineModelParser() {
51 }
52
53 /**
54 * Translate BMv2 json config to Bmv2PipelineModel object.
55 *
56 * @param jsonObject the BMv2 json config
57 * @return Bmv2PipelineModel object for the json config
58 */
59 public static Bmv2PipelineModel parse(JsonObject jsonObject) {
60 List<Bmv2HeaderTypeModel> headerTypeModels = HeaderTypesParser.parse(jsonObject);
61 Map<Integer, Integer> headerIdToIndex = HeaderStackParser.parse(jsonObject);
62 List<Bmv2HeaderModel> headerModels = HeadersParser.parse(jsonObject, headerTypeModels, headerIdToIndex);
63 List<Bmv2ActionModel> actionModels = ActionsParser.parse(jsonObject);
64 List<Bmv2TableModel> tableModels = TablesParser.parse(jsonObject, headerModels, actionModels);
65
66 return new Bmv2PipelineModel(headerTypeModels, headerModels,
67 actionModels, tableModels);
68 }
69
70 /**
71 * Parser for BMv2 header types.
72 */
73 private static class HeaderTypesParser {
74 private static final String HEADER_TYPES = "header_types";
75 private static final String FIELDS = "fields";
76 private static final int FIELD_NAME_INDEX = 0;
77 private static final int FIELD_BIT_WIDTH_INDEX = 1;
78 private static final int FIELD_SIGNED_INDEX = 2;
79 private static final int SIZE_WITH_SIGNED_FLAG = 3;
80
81 private static List<Bmv2HeaderTypeModel> parse(JsonObject jsonObject) {
82 List<Bmv2HeaderTypeModel> headerTypeModels = Lists.newArrayList();
83 jsonObject.get(HEADER_TYPES).asArray().forEach(jsonValue -> {
84 JsonObject headerFieldType = jsonValue.asObject();
85 String name = headerFieldType.getString(NAME, null);
86 int id = headerFieldType.getInt(ID, NO_ID);
87 if (id == NO_ID) {
88 log.warn("Can't get id from header type field {}", jsonValue);
89 return;
90 }
91 if (name == null) {
92 log.warn("Can't get name from header type field {}", jsonValue);
93 return;
94 }
95 List<Bmv2HeaderFieldTypeModel> fields = Lists.newArrayList();
96 headerFieldType.get(FIELDS).asArray().forEach(fieldValue -> {
97 JsonArray fieldInfo = fieldValue.asArray();
98 boolean signed = false;
99 if (fieldInfo.size() == SIZE_WITH_SIGNED_FLAG) {
100 // 3-tuple value, third value is a boolean value
101 // true if the field is signed; otherwise false
102 signed = fieldInfo.get(FIELD_SIGNED_INDEX).asBoolean();
103 }
104 fields.add(new Bmv2HeaderFieldTypeModel(fieldInfo.get(FIELD_NAME_INDEX).asString(),
105 fieldInfo.get(FIELD_BIT_WIDTH_INDEX).asInt(),
106 signed));
107 });
108 headerTypeModels.add(new Bmv2HeaderTypeModel(name, id, fields));
109 });
110 return headerTypeModels;
111 }
112 }
113
114 /**
115 * Parser for BMv2 header stacks.
116 */
117 private static class HeaderStackParser {
118 private static final String HEADER_STACK = "header_stacks";
119 private static final String HEADER_IDS = "header_ids";
120
121 /**
122 * Parser header stacks, return header-id to stack index mapping.
123 *
124 * @param jsonObject BMv2 json config
125 * @return header-id to stack index mapping
126 */
127 private static Map<Integer, Integer> parse(JsonObject jsonObject) {
128 Map<Integer, Integer> headerIdToIndex = Maps.newHashMap();
129 jsonObject.get(HEADER_STACK).asArray().forEach(jsonValue -> {
130 JsonArray headerIds = jsonValue.asObject().get(HEADER_IDS).asArray();
131 int index = 0;
132 for (JsonValue id : headerIds.values()) {
133 headerIdToIndex.put(id.asInt(), index);
134 index++;
135 }
136 });
137 return headerIdToIndex;
138 }
139 }
140
141 /**
142 * Parser for BMv2 headers.
143 */
144 private static class HeadersParser {
145 private static final String HEADERS = "headers";
146 private static final String HEADER_TYPE = "header_type";
147 private static final String METADATA = "metadata";
148 private static final String DEFAULT_HEADER_TYPE = "";
149 private static final Integer DEFAULT_HEADER_INDEX = 0;
150
151 private static List<Bmv2HeaderModel> parse(JsonObject jsonObject,
152 List<Bmv2HeaderTypeModel> headerTypeModels,
153 Map<Integer, Integer> headerIdToIndex) {
154 List<Bmv2HeaderModel> headerModels = Lists.newArrayList();
155
156 jsonObject.get(HEADERS).asArray().forEach(jsonValue -> {
157 JsonObject header = jsonValue.asObject();
158 String name = header.getString(NAME, null);
159 int id = header.getInt(ID, NO_ID);
160 String headerTypeName = header.getString(HEADER_TYPE, DEFAULT_HEADER_TYPE);
161 boolean isMetadata = header.getBoolean(METADATA, false);
162
163 if (name == null || id == -1) {
164 log.warn("Can't get name or id from header {}", header);
165 return;
166 }
167 Bmv2HeaderTypeModel headerTypeModel = headerTypeModels.stream()
168 .filter(model -> model.name().equals(headerTypeName))
169 .findFirst()
170 .orElse(null);
171
172 if (headerTypeModel == null) {
173 log.warn("Can't get header type model {} from header {}", headerTypeName, header);
174 return;
175 }
176
177 Integer index = headerIdToIndex.get(id);
178
179 // No index for this header, set to default
180 if (index == null) {
181 index = DEFAULT_HEADER_INDEX;
182 }
183 headerModels.add(new Bmv2HeaderModel(name, id, index, headerTypeModel, isMetadata));
184 });
185
186 return headerModels;
187 }
188 }
189
190 /**
191 * Parser for BMv2 actions.
192 */
193 private static class ActionsParser {
194 private static final String ACTIONS = "actions";
195 private static final String RUNTIME_DATA = "runtime_data";
196 private static final String BITWIDTH = "bitwidth";
197
198 private static List<Bmv2ActionModel> parse(JsonObject jsonObject) {
199 List<Bmv2ActionModel> actionModels = Lists.newArrayList();
200
201 jsonObject.get(ACTIONS).asArray().forEach(jsonValue -> {
202 JsonObject action = jsonValue.asObject();
203 String name = action.getString(NAME, null);
204 int id = action.getInt(ID, NO_ID);
205 List<Bmv2ActionParamModel> paramModels = Lists.newArrayList();
206 action.get(RUNTIME_DATA).asArray().forEach(paramValue -> {
207 JsonObject paramInfo = paramValue.asObject();
208 String paramName = paramInfo.getString(NAME, null);
209 int bitWidth = paramInfo.getInt(BITWIDTH, -1);
210
211 if (paramName == null || bitWidth == -1) {
212 log.warn("Can't get name or bit width from runtime data {}", paramInfo);
213 return;
214 }
215 paramModels.add(new Bmv2ActionParamModel(paramName, bitWidth));
216 });
217
218 actionModels.add(new Bmv2ActionModel(name, id, paramModels));
219 });
220
221 return actionModels;
222 }
223 }
224
225 /**
226 * Parser for BMv2 tables.
227 */
228 private static class TablesParser {
229 private static final String PIPELINES = "pipelines";
230 private static final String TABLES = "tables";
231 private static final String KEY = "key";
232 private static final String MATCH_TYPE = "match_type";
233 private static final String TARGET = "target";
234 private static final int TARGET_HEADER_INDEX = 0;
235 private static final int TARGET_FIELD_INDEX = 1;
236 private static final String ACTIONS = "actions";
237 private static final String MAX_SIZE = "max_size";
238 private static final int DEFAULT_MAX_SIZE = 0;
239 private static final String WITH_COUNTERS = "with_counters";
240 private static final String SUPPORT_TIMEOUT = "support_timeout";
241
242 private static List<Bmv2TableModel> parse(JsonObject jsonObject,
243 List<Bmv2HeaderModel> headerModels,
244 List<Bmv2ActionModel> actionModels) {
245 List<Bmv2TableModel> tableModels = Lists.newArrayList();
246 jsonObject.get(PIPELINES).asArray().forEach(pipelineVal -> {
247 JsonObject pipeline = pipelineVal.asObject();
248 pipeline.get(TABLES).asArray().forEach(tableVal -> {
249 JsonObject table = tableVal.asObject();
250 String tableName = table.getString(NAME, null);
251 int tableId = table.getInt(ID, NO_ID);
252 int maxSize = table.getInt(MAX_SIZE, DEFAULT_MAX_SIZE);
253 boolean hasCounters = table.getBoolean(WITH_COUNTERS, false);
254 boolean suppportAging = table.getBoolean(SUPPORT_TIMEOUT, false);
255
256 // Match field
257 Set<Bmv2TableMatchFieldModel> matchFieldModels =
258 Sets.newHashSet();
259 table.get(KEY).asArray().forEach(keyVal -> {
260 JsonObject key = keyVal.asObject();
261 String matchTypeName = key.getString(MATCH_TYPE, null);
262
263 if (matchTypeName == null) {
264 log.warn("Can't find match type from key {}", key);
265 return;
266 }
267 PiMatchType matchType = PiMatchType.valueOf(matchTypeName.toUpperCase());
268
269 // convert target array to Bmv2HeaderFieldTypeModel
270 // e.g. ["ethernet", "dst"]
271 JsonArray targetArray = key.get(TARGET).asArray();
272 Bmv2HeaderFieldModel matchField;
273
274 String headerName = targetArray.get(TARGET_HEADER_INDEX).asString();
275 String fieldName = targetArray.get(TARGET_FIELD_INDEX).asString();
276
277 Bmv2HeaderModel headerModel = headerModels.stream()
278 .filter(hm -> hm.name().equals(headerName))
279 .findAny()
280 .orElse(null);
281
282 if (headerModel == null) {
283 log.warn("Can't find header {} for table {}", headerName, tableName);
284 return;
285 }
286 Bmv2HeaderFieldTypeModel fieldModel =
287 (Bmv2HeaderFieldTypeModel) headerModel.type()
288 .field(fieldName)
289 .orElse(null);
290
291 if (fieldModel == null) {
292 log.warn("Can't find field {} from header {}", fieldName, headerName);
293 return;
294 }
295 matchField = new Bmv2HeaderFieldModel(headerModel, fieldModel);
296 matchFieldModels.add(new Bmv2TableMatchFieldModel(matchType, matchField));
297 });
298
299 // Actions
300 Set<Bmv2ActionModel> actions = Sets.newHashSet();
301 table.get(ACTIONS).asArray().forEach(actionVal -> {
302 String actionName = actionVal.asString();
303 Bmv2ActionModel action = actionModels.stream()
304 .filter(am -> am.name().equals(actionName))
305 .findAny()
306 .orElse(null);
307 if (action == null) {
308 log.warn("Can't find action {}", actionName);
309 return;
310 }
311 actions.add(action);
312 });
313
314 tableModels.add(new Bmv2TableModel(tableName, tableId,
315 maxSize, hasCounters,
316 suppportAging,
317 matchFieldModels, actions));
318 });
319 });
320
321 return tableModels;
322 }
323 }
324}