blob: e1081238fd1b9bb2bc86f1d475686e3008d81d53 [file] [log] [blame]
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -04001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2017-present Open Networking Foundation
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -04003 *
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
Andrea Campanella0288c872017-08-07 18:32:51 +020017package org.onosproject.drivers.p4runtime;
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040018
Yi Tseng82512da2017-08-16 19:46:36 -070019import com.google.common.collect.ImmutableList;
20import com.google.common.collect.Maps;
21import com.google.common.collect.Sets;
22import org.onlab.util.KryoNamespace;
23import org.onosproject.core.GroupId;
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040024import org.onosproject.net.Device;
25import org.onosproject.net.DeviceId;
Yi Tseng82512da2017-08-16 19:46:36 -070026import org.onosproject.net.device.DeviceService;
27import org.onosproject.net.flow.TrafficTreatment;
28import org.onosproject.net.flow.instructions.Instruction;
29import org.onosproject.net.flow.instructions.PiInstruction;
30import org.onosproject.net.group.Group;
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040031import org.onosproject.net.group.GroupBucket;
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040032import org.onosproject.net.group.GroupOperation;
33import org.onosproject.net.group.GroupOperations;
34import org.onosproject.net.group.GroupProgrammable;
Yi Tseng82512da2017-08-16 19:46:36 -070035import org.onosproject.net.group.GroupStore;
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040036import org.onosproject.net.pi.model.PiPipelineInterpreter;
Yi Tseng82512da2017-08-16 19:46:36 -070037import org.onosproject.net.pi.runtime.PiActionProfileId;
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040038import org.onosproject.net.pi.runtime.PiAction;
39import org.onosproject.net.pi.runtime.PiActionGroup;
40import org.onosproject.net.pi.runtime.PiActionGroupId;
41import org.onosproject.net.pi.runtime.PiActionGroupMember;
42import org.onosproject.net.pi.runtime.PiActionGroupMemberId;
Yi Tseng82512da2017-08-16 19:46:36 -070043import org.onosproject.net.pi.runtime.PiTableAction;
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040044import org.onosproject.net.pi.runtime.PiTableId;
Yi Tseng82512da2017-08-16 19:46:36 -070045import org.onosproject.p4runtime.api.P4RuntimeClient;
46import org.onosproject.p4runtime.api.P4RuntimeGroupReference;
47import org.onosproject.p4runtime.api.P4RuntimeGroupWrapper;
48import org.onosproject.store.serializers.KryoNamespaces;
49import org.slf4j.Logger;
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040050
51import java.nio.ByteBuffer;
Yi Tseng82512da2017-08-16 19:46:36 -070052import java.util.Collection;
53import java.util.Collections;
54import java.util.List;
55import java.util.Map;
56import java.util.concurrent.CompletableFuture;
57import java.util.concurrent.ExecutionException;
58import java.util.concurrent.atomic.AtomicBoolean;
59import java.util.concurrent.locks.Lock;
60import java.util.concurrent.locks.ReentrantLock;
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040061
Yi Tseng82512da2017-08-16 19:46:36 -070062import static org.slf4j.LoggerFactory.getLogger;
63
64/**
65 * Implementation of the group programmable behaviour for P4Runtime.
66 */
67public class P4RuntimeGroupProgrammable extends AbstractP4RuntimeHandlerBehaviour implements GroupProgrammable {
68 private static final String ACT_GRP_MEMS = "action group members";
69 private static final String DELETE = "delete";
70 private static final String ACT_GRP = "action group";
71 private static final String INSERT = "insert";
72 private static final Logger log = getLogger(P4RuntimeGroupProgrammable.class);
73 private static final int GROUP_ID_MASK = 0xffff;
74 public static final KryoNamespace KRYO = new KryoNamespace.Builder()
75 .register(KryoNamespaces.API)
76 .register(DefaultP4RuntimeGroupCookie.class)
77 .build("P4RuntimeGroupProgrammable");
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040078
79 /*
Yi Tseng82512da2017-08-16 19:46:36 -070080 * About action groups in P4runtime:
81 * The type field is a place holder in p4runtime.proto right now, and we haven't defined it yet. You can assume all
82 * the groups are "select" as per the OF spec. As a remainder, in the P4 terminology a member corresponds to an OF
83 * bucket. Each member can also be used directly in the match table (kind of like an OF indirect group).
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040084 */
85
Yi Tseng82512da2017-08-16 19:46:36 -070086 // TODO: make this attribute configurable by child drivers (e.g. BMv2 or Tofino)
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040087 /*
Yi Tseng82512da2017-08-16 19:46:36 -070088 When updating an existing rule, if true, we issue a DELETE operation before inserting the new one, otherwise we
89 issue a MODIFY operation. This is useful fore devices that do not support MODIFY operations for table entries.
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040090 */
Yi Tseng82512da2017-08-16 19:46:36 -070091 private boolean deleteBeforeUpdate = true;
92
93 // TODO: can remove this check as soon as the multi-apply-per-same-flow rule bug is fixed.
94 /*
95 If true, we ignore re-installing rules that are already known in the ENTRY_STORE, i.e. same match key and action.
96 */
97 private boolean checkStoreBeforeUpdate = true;
98
99 // Needed to synchronize operations over the same group.
100 private static final Map<P4RuntimeGroupReference, Lock> GROUP_LOCKS = Maps.newConcurrentMap();
101
102 // TODO: replace with distribute store
103 private static final Map<P4RuntimeGroupReference, P4RuntimeGroupWrapper> GROUP_STORE = Maps.newConcurrentMap();
104
105 private PiPipelineInterpreter interpreter;
106
107 protected boolean init() {
108 if (!setupBehaviour()) {
109 return false;
110 }
111 Device device = deviceService.getDevice(deviceId);
112 // Need an interpreter to map the bucket treatment to a PI action
113 if (!device.is(PiPipelineInterpreter.class)) {
114 log.warn("Can't find interpreter for device {}", device.id());
115 } else {
116 interpreter = device.as(PiPipelineInterpreter.class);
117 }
118 return true;
119 }
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -0400120
121 @Override
122 public void performGroupOperation(DeviceId deviceId, GroupOperations groupOps) {
Yi Tseng82512da2017-08-16 19:46:36 -0700123 if (!init()) {
124 // Ignore group operation of not initialized.
125 return;
126 }
127 Device device = handler().get(DeviceService.class).getDevice(deviceId);
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -0400128
129 for (GroupOperation groupOp : groupOps.operations()) {
Yi Tseng82512da2017-08-16 19:46:36 -0700130 processGroupOp(device, groupOp);
131 }
132 }
133
134 private void processGroupOp(Device device, GroupOperation groupOp) {
135 GroupId groupId = groupOp.groupId();
136 GroupStore groupStore = handler().get(GroupStore.class);
137 Group group = groupStore.getGroup(device.id(), groupId);
138
139 // Most of this logic can go in a core service, e.g. PiGroupTranslationService
140 // From a P4Runtime perspective, we need first to insert members, then the group.
141 PiActionGroupId piActionGroupId = PiActionGroupId.of(groupOp.groupId().id());
142
143 PiActionGroup.Builder piActionGroupBuilder = PiActionGroup.builder()
144 .withId(piActionGroupId);
145
146 switch (group.type()) {
147 case SELECT:
148 piActionGroupBuilder.withType(PiActionGroup.Type.SELECT);
149 break;
150 default:
151 log.warn("Group type {} not supported, ignore group {}.", group.type(), groupId);
152 return;
153 }
154 /*
155 Problem:
156 In P4Runtime, action profiles (i.e. group tables) are specific to one or more tables.
157 Mapping of treatments depends on the target table. How do we derive the target table from here?
158
159 Solution:
160 - Add table information into app cookie and put into group description
161 */
162 // TODO: notify group service if we get deserialize error
163 DefaultP4RuntimeGroupCookie defaultP4RuntimeGroupCookie = KRYO.deserialize(group.appCookie().key());
164 PiTableId piTableId = defaultP4RuntimeGroupCookie.tableId();
165 PiActionProfileId piActionProfileId = defaultP4RuntimeGroupCookie.actionProfileId();
166 piActionGroupBuilder.withActionProfileId(piActionProfileId);
167
168 List<PiActionGroupMember> members = buildMembers(group, piActionGroupId, piTableId);
169 if (members == null) {
170 log.warn("Can't build members for group {} on {}", group, device.id());
171 return;
172 }
173
174 piActionGroupBuilder.addMembers(members);
175 PiActionGroup piActionGroup = piActionGroupBuilder.build();
176
177 P4RuntimeGroupReference groupRef =
178 new P4RuntimeGroupReference(deviceId, piActionProfileId, piActionGroupId);
179 Lock lock = GROUP_LOCKS.computeIfAbsent(groupRef, k -> new ReentrantLock());
180 lock.lock();
181
182
183 try {
184 P4RuntimeGroupWrapper oldGroupWrapper = GROUP_STORE.get(groupRef);
185 P4RuntimeGroupWrapper newGroupWrapper =
186 new P4RuntimeGroupWrapper(piActionGroup, group, System.currentTimeMillis());
187 boolean success;
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -0400188 switch (groupOp.opType()) {
189 case ADD:
Yi Tseng82512da2017-08-16 19:46:36 -0700190 case MODIFY:
191 success = writeGroupToDevice(oldGroupWrapper, piActionGroup, members);
192 if (success) {
193 GROUP_STORE.put(groupRef, newGroupWrapper);
194 }
195 break;
196 case DELETE:
197 success = deleteGroupFromDevice(piActionGroup, members);
198 if (success) {
199 GROUP_STORE.remove(groupRef);
200 }
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -0400201 break;
202 default:
203 throw new UnsupportedOperationException();
204 }
Yi Tseng82512da2017-08-16 19:46:36 -0700205 } finally {
206 lock.unlock();
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -0400207 }
208 }
209
Yi Tseng82512da2017-08-16 19:46:36 -0700210 /**
211 * Installs action group and members to device via client interface.
212 *
213 * @param oldGroupWrapper old group wrapper for the group; null if not exists
214 * @param piActionGroup the action group to be installed
215 * @param members members of the action group
216 * @return true if install success; false otherwise
217 */
218 private boolean writeGroupToDevice(P4RuntimeGroupWrapper oldGroupWrapper,
219 PiActionGroup piActionGroup,
220 Collection<PiActionGroupMember> members) {
221 boolean success = true;
222 CompletableFuture<Boolean> writeSuccess;
223 if (checkStoreBeforeUpdate && oldGroupWrapper != null &&
224 oldGroupWrapper.piActionGroup().equals(piActionGroup)) {
225 // Action group already exists, ignore it
226 return true;
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -0400227 }
Yi Tseng82512da2017-08-16 19:46:36 -0700228 if (deleteBeforeUpdate && oldGroupWrapper != null) {
229 success = deleteGroupFromDevice(oldGroupWrapper.piActionGroup(),
230 oldGroupWrapper.piActionGroup().members());
231 }
232 writeSuccess = client.writeActionGroupMembers(piActionGroup,
233 members,
234 P4RuntimeClient.WriteOperationType.INSERT,
235 pipeconf);
236 success = success && completeSuccess(writeSuccess, ACT_GRP_MEMS, INSERT);
237
238 writeSuccess = client.writeActionGroup(piActionGroup,
239 P4RuntimeClient.WriteOperationType.INSERT,
240 pipeconf);
241 success = success && completeSuccess(writeSuccess, ACT_GRP, INSERT);
242 return success;
243 }
244
245 private boolean deleteGroupFromDevice(PiActionGroup piActionGroup,
246 Collection<PiActionGroupMember> members) {
247 boolean success;
248 CompletableFuture<Boolean> writeSuccess;
249 writeSuccess = client.writeActionGroup(piActionGroup,
250 P4RuntimeClient.WriteOperationType.DELETE,
251 pipeconf);
252 success = completeSuccess(writeSuccess, ACT_GRP, DELETE);
253 writeSuccess = client.writeActionGroupMembers(piActionGroup,
254 members,
255 P4RuntimeClient.WriteOperationType.DELETE,
256 pipeconf);
257 success = success && completeSuccess(writeSuccess, ACT_GRP_MEMS, DELETE);
258 return success;
259 }
260
261 private boolean completeSuccess(CompletableFuture<Boolean> completableFuture,
262 String topic, String action) {
263 try {
264 return completableFuture.get();
265 } catch (InterruptedException | ExecutionException e) {
266 log.warn("Can't {} {} due to {}", action, topic, e.getMessage());
267 return false;
268 }
269 }
270
271 /**
272 * Build pi action group members from group.
273 *
274 * @param group the group
275 * @param piActionGroupId the PI action group id of the group
276 * @param piTableId the PI table related to the group
277 * @return list of PI action group members; null if can't build member list
278 */
279 private List<PiActionGroupMember> buildMembers(Group group, PiActionGroupId piActionGroupId, PiTableId piTableId) {
280 GroupId groupId = group.id();
281 ImmutableList.Builder<PiActionGroupMember> membersBuilder = ImmutableList.builder();
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -0400282
283 int bucketIdx = 0;
Yi Tseng82512da2017-08-16 19:46:36 -0700284 for (GroupBucket bucket : group.buckets().buckets()) {
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -0400285 /*
286 Problem:
287 In P4Runtime action group members, i.e. action buckets, are associated to a numeric ID chosen
288 at member insertion time. This ID must be unique for the whole action profile (i.e. the group table in
289 OpenFlow). In ONOS, GroupBucket doesn't specify any ID.
290
291 Solutions:
292 - Change GroupBucket API to force application wanting to perform group operations to specify a member id.
293 - Maintain state to dynamically allocate/deallocate member IDs, e.g. in a dedicated service, or in a
294 P4Runtime Group Provider.
295
296 Hack:
297 Statically derive member ID by combining groupId and position of the bucket in the list.
298 */
Yi Tseng82512da2017-08-16 19:46:36 -0700299 ByteBuffer bb = ByteBuffer.allocate(4)
300 .putShort((short) (piActionGroupId.id() & GROUP_ID_MASK))
301 .putShort((short) bucketIdx);
302 bb.rewind();
303 int memberId = bb.getInt();
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -0400304
Yi Tseng82512da2017-08-16 19:46:36 -0700305 bucketIdx++;
306 PiAction action;
307 if (interpreter != null) {
308 // if we have interpreter, use interpreter
309 try {
310 action = interpreter.mapTreatment(bucket.treatment(), piTableId);
311 } catch (PiPipelineInterpreter.PiInterpreterException e) {
312 log.warn("Can't map treatment {} to action due to {}, ignore group {}",
313 bucket.treatment(), e.getMessage(), groupId);
314 return null;
315 }
316 } else {
317 // if we don't have interpreter, accept PiInstruction only
318 TrafficTreatment treatment = bucket.treatment();
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -0400319
Yi Tseng82512da2017-08-16 19:46:36 -0700320 if (treatment.allInstructions().size() > 1) {
321 log.warn("Treatment {} has multiple instructions, ignore group {}",
322 treatment, groupId);
323 return null;
324 }
325 Instruction instruction = treatment.allInstructions().get(0);
326 if (instruction.type() != Instruction.Type.PROTOCOL_INDEPENDENT) {
327 log.warn("Instruction {} is not a PROTOCOL_INDEPENDENT type, ignore group {}",
328 instruction, groupId);
329 return null;
330 }
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -0400331
Yi Tseng82512da2017-08-16 19:46:36 -0700332 PiInstruction piInstruction = (PiInstruction) instruction;
333 if (piInstruction.action().type() != PiTableAction.Type.ACTION) {
334 log.warn("Action {} is not an ACTION type, ignore group {}",
335 piInstruction.action(), groupId);
336 return null;
337 }
338 action = (PiAction) piInstruction.action();
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -0400339 }
340
341 PiActionGroupMember member = PiActionGroupMember.builder()
342 .withId(PiActionGroupMemberId.of(memberId))
343 .withAction(action)
344 .withWeight(bucket.weight())
345 .build();
346
Yi Tseng82512da2017-08-16 19:46:36 -0700347 membersBuilder.add(member);
348 }
349 return membersBuilder.build();
350 }
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -0400351
Yi Tseng82512da2017-08-16 19:46:36 -0700352 @Override
353 public Collection<Group> getGroups() {
354 if (!init()) {
355 return Collections.emptySet();
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -0400356 }
357
Yi Tseng82512da2017-08-16 19:46:36 -0700358 Collection<Group> result = Sets.newHashSet();
359 Collection<PiActionProfileId> piActionProfileIds = Sets.newHashSet();
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -0400360
Yi Tseng82512da2017-08-16 19:46:36 -0700361 // Collection action profile Ids
362 // TODO: find better way to get all action profile ids....
363 GROUP_STORE.forEach((groupRef, wrapper) -> piActionProfileIds.add(groupRef.actionProfileId()));
364
365 AtomicBoolean success = new AtomicBoolean(true);
366 piActionProfileIds.forEach(actionProfileId -> {
367 Collection<PiActionGroup> piActionGroups = Sets.newHashSet();
368 try {
369 Collection<PiActionGroup> groupsFromDevice =
370 client.dumpGroups(actionProfileId, pipeconf).get();
371 if (groupsFromDevice == null) {
372 // Got error
373 success.set(false);
374 } else {
375 piActionGroups.addAll(groupsFromDevice);
376 }
377 } catch (ExecutionException | InterruptedException e) {
378 log.error("Exception while dumping groups for action profile {}: {}",
379 actionProfileId.id(), deviceId, e);
380 success.set(false);
381 }
382
383 piActionGroups.forEach(piActionGroup -> {
384 PiActionGroupId actionGroupId = piActionGroup.id();
385 P4RuntimeGroupReference groupRef =
386 new P4RuntimeGroupReference(deviceId, actionProfileId, actionGroupId);
387 P4RuntimeGroupWrapper wrapper = GROUP_STORE.get(groupRef);
388
389 if (wrapper == null) {
390 // group exists in client, but can't find in ONOS
391 log.warn("Can't find action profile group {} from local store.",
392 groupRef);
393 return;
394 }
395 if (!wrapper.piActionGroup().equals(piActionGroup)) {
396 log.warn("Group from device is different to group from local store.");
397 return;
398 }
399 result.add(wrapper.group());
400
401 });
402 });
403
404 if (!success.get()) {
405 // Got error while dump groups from device.
406 return Collections.emptySet();
407 } else {
408 return result;
409 }
410 }
411
412 /**
413 * P4Runtime app cookie for group.
414 */
415 public static class DefaultP4RuntimeGroupCookie {
416 private PiTableId tableId;
417 private PiActionProfileId piActionProfileId;
418 private Integer groupId;
419
420 public DefaultP4RuntimeGroupCookie(PiTableId tableId,
421 PiActionProfileId piActionProfileId,
422 Integer groupId) {
423 this.tableId = tableId;
424 this.piActionProfileId = piActionProfileId;
425 this.groupId = groupId;
426 }
427
428 public PiTableId tableId() {
429 return tableId;
430 }
431
432 public PiActionProfileId actionProfileId() {
433 return piActionProfileId;
434 }
435
436 public Integer groupId() {
437 return groupId;
438 }
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -0400439 }
440}