blob: 1c4d5dcdef8ed9deabbb5169d097c87012c65226 [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.Maps;
20import com.google.common.collect.Sets;
Yi Tseng82512da2017-08-16 19:46:36 -070021import org.onosproject.core.GroupId;
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040022import org.onosproject.net.Device;
23import org.onosproject.net.DeviceId;
Yi Tseng82512da2017-08-16 19:46:36 -070024import org.onosproject.net.device.DeviceService;
Yi Tseng82512da2017-08-16 19:46:36 -070025import org.onosproject.net.group.Group;
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040026import org.onosproject.net.group.GroupOperation;
27import org.onosproject.net.group.GroupOperations;
28import org.onosproject.net.group.GroupProgrammable;
Yi Tseng82512da2017-08-16 19:46:36 -070029import org.onosproject.net.group.GroupStore;
Carmelo Cascone87892e22017-11-13 16:01:29 -080030import org.onosproject.net.pi.model.PiActionProfileId;
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040031import org.onosproject.net.pi.runtime.PiActionGroup;
32import org.onosproject.net.pi.runtime.PiActionGroupId;
Carmelo Cascone87b9b392017-10-02 18:33:20 +020033import org.onosproject.net.pi.runtime.PiTranslationService;
Yi Tseng82512da2017-08-16 19:46:36 -070034import org.onosproject.p4runtime.api.P4RuntimeClient;
35import org.onosproject.p4runtime.api.P4RuntimeGroupReference;
36import org.onosproject.p4runtime.api.P4RuntimeGroupWrapper;
Yi Tseng82512da2017-08-16 19:46:36 -070037import org.slf4j.Logger;
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040038
Yi Tseng82512da2017-08-16 19:46:36 -070039import java.util.Collection;
40import java.util.Collections;
Yi Tseng82512da2017-08-16 19:46:36 -070041import java.util.Map;
42import java.util.concurrent.CompletableFuture;
43import java.util.concurrent.ExecutionException;
44import java.util.concurrent.atomic.AtomicBoolean;
45import java.util.concurrent.locks.Lock;
46import java.util.concurrent.locks.ReentrantLock;
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040047
Yi Tseng82512da2017-08-16 19:46:36 -070048import static org.slf4j.LoggerFactory.getLogger;
49
50/**
51 * Implementation of the group programmable behaviour for P4Runtime.
52 */
53public class P4RuntimeGroupProgrammable extends AbstractP4RuntimeHandlerBehaviour implements GroupProgrammable {
54 private static final String ACT_GRP_MEMS = "action group members";
55 private static final String DELETE = "delete";
56 private static final String ACT_GRP = "action group";
57 private static final String INSERT = "insert";
58 private static final Logger log = getLogger(P4RuntimeGroupProgrammable.class);
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040059
60 /*
Yi Tseng82512da2017-08-16 19:46:36 -070061 * About action groups in P4runtime:
62 * The type field is a place holder in p4runtime.proto right now, and we haven't defined it yet. You can assume all
63 * the groups are "select" as per the OF spec. As a remainder, in the P4 terminology a member corresponds to an OF
64 * 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 -040065 */
66
Yi Tseng82512da2017-08-16 19:46:36 -070067 // TODO: make this attribute configurable by child drivers (e.g. BMv2 or Tofino)
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040068 /*
Yi Tseng82512da2017-08-16 19:46:36 -070069 When updating an existing rule, if true, we issue a DELETE operation before inserting the new one, otherwise we
70 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 -040071 */
Yi Tseng82512da2017-08-16 19:46:36 -070072 private boolean deleteBeforeUpdate = true;
73
74 // TODO: can remove this check as soon as the multi-apply-per-same-flow rule bug is fixed.
75 /*
76 If true, we ignore re-installing rules that are already known in the ENTRY_STORE, i.e. same match key and action.
77 */
78 private boolean checkStoreBeforeUpdate = true;
79
80 // Needed to synchronize operations over the same group.
81 private static final Map<P4RuntimeGroupReference, Lock> GROUP_LOCKS = Maps.newConcurrentMap();
82
83 // TODO: replace with distribute store
84 private static final Map<P4RuntimeGroupReference, P4RuntimeGroupWrapper> GROUP_STORE = Maps.newConcurrentMap();
85
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040086 @Override
87 public void performGroupOperation(DeviceId deviceId, GroupOperations groupOps) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +020088 if (!setupBehaviour()) {
Yi Tseng82512da2017-08-16 19:46:36 -070089 return;
90 }
Carmelo Cascone87b9b392017-10-02 18:33:20 +020091
Yi Tseng82512da2017-08-16 19:46:36 -070092 Device device = handler().get(DeviceService.class).getDevice(deviceId);
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040093
94 for (GroupOperation groupOp : groupOps.operations()) {
Yi Tseng82512da2017-08-16 19:46:36 -070095 processGroupOp(device, groupOp);
96 }
97 }
98
99 private void processGroupOp(Device device, GroupOperation groupOp) {
100 GroupId groupId = groupOp.groupId();
101 GroupStore groupStore = handler().get(GroupStore.class);
102 Group group = groupStore.getGroup(device.id(), groupId);
103
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200104 PiActionGroup piActionGroup;
105 try {
106 piActionGroup = piTranslationService.translateGroup(group, pipeconf);
107 } catch (PiTranslationService.PiTranslationException e) {
108 log.warn("Unable translate group, aborting group operation {}: {}", groupOp.opType(), e.getMessage());
Yi Tseng82512da2017-08-16 19:46:36 -0700109 return;
110 }
111
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200112 P4RuntimeGroupReference groupRef = new P4RuntimeGroupReference(deviceId, piActionGroup.actionProfileId(),
113 piActionGroup.id());
Yi Tseng82512da2017-08-16 19:46:36 -0700114
Yi Tseng82512da2017-08-16 19:46:36 -0700115 Lock lock = GROUP_LOCKS.computeIfAbsent(groupRef, k -> new ReentrantLock());
116 lock.lock();
117
Yi Tseng82512da2017-08-16 19:46:36 -0700118 try {
119 P4RuntimeGroupWrapper oldGroupWrapper = GROUP_STORE.get(groupRef);
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200120 P4RuntimeGroupWrapper newGroupWrapper = new P4RuntimeGroupWrapper(piActionGroup, group,
121 System.currentTimeMillis());
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -0400122 switch (groupOp.opType()) {
123 case ADD:
Yi Tseng82512da2017-08-16 19:46:36 -0700124 case MODIFY:
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200125 if (writeGroupToDevice(oldGroupWrapper, piActionGroup)) {
Yi Tseng82512da2017-08-16 19:46:36 -0700126 GROUP_STORE.put(groupRef, newGroupWrapper);
127 }
128 break;
129 case DELETE:
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200130 if (deleteGroupFromDevice(piActionGroup)) {
Yi Tseng82512da2017-08-16 19:46:36 -0700131 GROUP_STORE.remove(groupRef);
132 }
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -0400133 break;
134 default:
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200135 log.warn("Group operation {} not supported", groupOp.opType());
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -0400136 }
Yi Tseng82512da2017-08-16 19:46:36 -0700137 } finally {
138 lock.unlock();
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -0400139 }
140 }
141
Yi Tseng82512da2017-08-16 19:46:36 -0700142 /**
143 * Installs action group and members to device via client interface.
144 *
145 * @param oldGroupWrapper old group wrapper for the group; null if not exists
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200146 * @param piActionGroup the action group to be installed
Yi Tseng82512da2017-08-16 19:46:36 -0700147 * @return true if install success; false otherwise
148 */
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200149 private boolean writeGroupToDevice(P4RuntimeGroupWrapper oldGroupWrapper, PiActionGroup piActionGroup) {
Yi Tseng82512da2017-08-16 19:46:36 -0700150 boolean success = true;
151 CompletableFuture<Boolean> writeSuccess;
152 if (checkStoreBeforeUpdate && oldGroupWrapper != null &&
153 oldGroupWrapper.piActionGroup().equals(piActionGroup)) {
154 // Action group already exists, ignore it
155 return true;
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -0400156 }
Yi Tseng82512da2017-08-16 19:46:36 -0700157 if (deleteBeforeUpdate && oldGroupWrapper != null) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200158 success = deleteGroupFromDevice(oldGroupWrapper.piActionGroup());
Yi Tseng82512da2017-08-16 19:46:36 -0700159 }
160 writeSuccess = client.writeActionGroupMembers(piActionGroup,
Yi Tseng82512da2017-08-16 19:46:36 -0700161 P4RuntimeClient.WriteOperationType.INSERT,
162 pipeconf);
163 success = success && completeSuccess(writeSuccess, ACT_GRP_MEMS, INSERT);
164
165 writeSuccess = client.writeActionGroup(piActionGroup,
166 P4RuntimeClient.WriteOperationType.INSERT,
167 pipeconf);
168 success = success && completeSuccess(writeSuccess, ACT_GRP, INSERT);
169 return success;
170 }
171
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200172 private boolean deleteGroupFromDevice(PiActionGroup piActionGroup) {
Yi Tseng82512da2017-08-16 19:46:36 -0700173 boolean success;
174 CompletableFuture<Boolean> writeSuccess;
175 writeSuccess = client.writeActionGroup(piActionGroup,
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200176 P4RuntimeClient.WriteOperationType.DELETE,
177 pipeconf);
Yi Tseng82512da2017-08-16 19:46:36 -0700178 success = completeSuccess(writeSuccess, ACT_GRP, DELETE);
179 writeSuccess = client.writeActionGroupMembers(piActionGroup,
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200180 P4RuntimeClient.WriteOperationType.DELETE,
181 pipeconf);
Yi Tseng82512da2017-08-16 19:46:36 -0700182 success = success && completeSuccess(writeSuccess, ACT_GRP_MEMS, DELETE);
183 return success;
184 }
185
186 private boolean completeSuccess(CompletableFuture<Boolean> completableFuture,
187 String topic, String action) {
188 try {
189 return completableFuture.get();
190 } catch (InterruptedException | ExecutionException e) {
191 log.warn("Can't {} {} due to {}", action, topic, e.getMessage());
192 return false;
193 }
194 }
195
Yi Tseng82512da2017-08-16 19:46:36 -0700196 @Override
197 public Collection<Group> getGroups() {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200198 if (!setupBehaviour()) {
199 return Collections.emptyList();
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -0400200 }
201
Yi Tseng82512da2017-08-16 19:46:36 -0700202 Collection<Group> result = Sets.newHashSet();
203 Collection<PiActionProfileId> piActionProfileIds = Sets.newHashSet();
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -0400204
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200205 // TODO: find better way to get all action profile ids. e.g. by providing them in the interpreter
Yi Tseng82512da2017-08-16 19:46:36 -0700206 GROUP_STORE.forEach((groupRef, wrapper) -> piActionProfileIds.add(groupRef.actionProfileId()));
207
208 AtomicBoolean success = new AtomicBoolean(true);
209 piActionProfileIds.forEach(actionProfileId -> {
210 Collection<PiActionGroup> piActionGroups = Sets.newHashSet();
211 try {
212 Collection<PiActionGroup> groupsFromDevice =
213 client.dumpGroups(actionProfileId, pipeconf).get();
214 if (groupsFromDevice == null) {
215 // Got error
216 success.set(false);
217 } else {
218 piActionGroups.addAll(groupsFromDevice);
219 }
220 } catch (ExecutionException | InterruptedException e) {
221 log.error("Exception while dumping groups for action profile {}: {}",
222 actionProfileId.id(), deviceId, e);
223 success.set(false);
224 }
225
226 piActionGroups.forEach(piActionGroup -> {
227 PiActionGroupId actionGroupId = piActionGroup.id();
228 P4RuntimeGroupReference groupRef =
229 new P4RuntimeGroupReference(deviceId, actionProfileId, actionGroupId);
230 P4RuntimeGroupWrapper wrapper = GROUP_STORE.get(groupRef);
231
232 if (wrapper == null) {
233 // group exists in client, but can't find in ONOS
234 log.warn("Can't find action profile group {} from local store.",
235 groupRef);
236 return;
237 }
238 if (!wrapper.piActionGroup().equals(piActionGroup)) {
239 log.warn("Group from device is different to group from local store.");
240 return;
241 }
242 result.add(wrapper.group());
243
244 });
245 });
246
247 if (!success.get()) {
248 // Got error while dump groups from device.
249 return Collections.emptySet();
250 } else {
251 return result;
252 }
253 }
254
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -0400255}