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