Carmelo Cascone | b2e3dba | 2017-07-27 12:07:09 -0400 | [diff] [blame] | 1 | /* |
Brian O'Connor | a09fe5b | 2017-08-03 21:12:30 -0700 | [diff] [blame] | 2 | * Copyright 2017-present Open Networking Foundation |
Carmelo Cascone | b2e3dba | 2017-07-27 12:07:09 -0400 | [diff] [blame] | 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 | |
Andrea Campanella | 0288c87 | 2017-08-07 18:32:51 +0200 | [diff] [blame] | 17 | package org.onosproject.drivers.p4runtime; |
Carmelo Cascone | b2e3dba | 2017-07-27 12:07:09 -0400 | [diff] [blame] | 18 | |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 19 | import com.google.common.collect.Maps; |
| 20 | import com.google.common.collect.Sets; |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 21 | import org.onosproject.core.GroupId; |
Carmelo Cascone | b2e3dba | 2017-07-27 12:07:09 -0400 | [diff] [blame] | 22 | import org.onosproject.net.Device; |
| 23 | import org.onosproject.net.DeviceId; |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 24 | import org.onosproject.net.device.DeviceService; |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 25 | import org.onosproject.net.group.Group; |
Carmelo Cascone | b2e3dba | 2017-07-27 12:07:09 -0400 | [diff] [blame] | 26 | import org.onosproject.net.group.GroupOperation; |
| 27 | import org.onosproject.net.group.GroupOperations; |
| 28 | import org.onosproject.net.group.GroupProgrammable; |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 29 | import org.onosproject.net.group.GroupStore; |
Carmelo Cascone | 87892e2 | 2017-11-13 16:01:29 -0800 | [diff] [blame^] | 30 | import org.onosproject.net.pi.model.PiActionProfileId; |
Carmelo Cascone | b2e3dba | 2017-07-27 12:07:09 -0400 | [diff] [blame] | 31 | import org.onosproject.net.pi.runtime.PiActionGroup; |
| 32 | import org.onosproject.net.pi.runtime.PiActionGroupId; |
Carmelo Cascone | 87b9b39 | 2017-10-02 18:33:20 +0200 | [diff] [blame] | 33 | import org.onosproject.net.pi.runtime.PiTranslationService; |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 34 | import org.onosproject.p4runtime.api.P4RuntimeClient; |
| 35 | import org.onosproject.p4runtime.api.P4RuntimeGroupReference; |
| 36 | import org.onosproject.p4runtime.api.P4RuntimeGroupWrapper; |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 37 | import org.slf4j.Logger; |
Carmelo Cascone | b2e3dba | 2017-07-27 12:07:09 -0400 | [diff] [blame] | 38 | |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 39 | import java.util.Collection; |
| 40 | import java.util.Collections; |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 41 | import java.util.Map; |
| 42 | import java.util.concurrent.CompletableFuture; |
| 43 | import java.util.concurrent.ExecutionException; |
| 44 | import java.util.concurrent.atomic.AtomicBoolean; |
| 45 | import java.util.concurrent.locks.Lock; |
| 46 | import java.util.concurrent.locks.ReentrantLock; |
Carmelo Cascone | b2e3dba | 2017-07-27 12:07:09 -0400 | [diff] [blame] | 47 | |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 48 | import static org.slf4j.LoggerFactory.getLogger; |
| 49 | |
| 50 | /** |
| 51 | * Implementation of the group programmable behaviour for P4Runtime. |
| 52 | */ |
| 53 | public 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 Cascone | b2e3dba | 2017-07-27 12:07:09 -0400 | [diff] [blame] | 59 | |
| 60 | /* |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 61 | * 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 Cascone | b2e3dba | 2017-07-27 12:07:09 -0400 | [diff] [blame] | 65 | */ |
| 66 | |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 67 | // TODO: make this attribute configurable by child drivers (e.g. BMv2 or Tofino) |
Carmelo Cascone | b2e3dba | 2017-07-27 12:07:09 -0400 | [diff] [blame] | 68 | /* |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 69 | 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 Cascone | b2e3dba | 2017-07-27 12:07:09 -0400 | [diff] [blame] | 71 | */ |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 72 | 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 Cascone | b2e3dba | 2017-07-27 12:07:09 -0400 | [diff] [blame] | 86 | @Override |
| 87 | public void performGroupOperation(DeviceId deviceId, GroupOperations groupOps) { |
Carmelo Cascone | 87b9b39 | 2017-10-02 18:33:20 +0200 | [diff] [blame] | 88 | if (!setupBehaviour()) { |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 89 | return; |
| 90 | } |
Carmelo Cascone | 87b9b39 | 2017-10-02 18:33:20 +0200 | [diff] [blame] | 91 | |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 92 | Device device = handler().get(DeviceService.class).getDevice(deviceId); |
Carmelo Cascone | b2e3dba | 2017-07-27 12:07:09 -0400 | [diff] [blame] | 93 | |
| 94 | for (GroupOperation groupOp : groupOps.operations()) { |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 95 | 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 Cascone | 87b9b39 | 2017-10-02 18:33:20 +0200 | [diff] [blame] | 104 | 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 Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 109 | return; |
| 110 | } |
| 111 | |
Carmelo Cascone | 87b9b39 | 2017-10-02 18:33:20 +0200 | [diff] [blame] | 112 | P4RuntimeGroupReference groupRef = new P4RuntimeGroupReference(deviceId, piActionGroup.actionProfileId(), |
| 113 | piActionGroup.id()); |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 114 | |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 115 | Lock lock = GROUP_LOCKS.computeIfAbsent(groupRef, k -> new ReentrantLock()); |
| 116 | lock.lock(); |
| 117 | |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 118 | try { |
| 119 | P4RuntimeGroupWrapper oldGroupWrapper = GROUP_STORE.get(groupRef); |
Carmelo Cascone | 87b9b39 | 2017-10-02 18:33:20 +0200 | [diff] [blame] | 120 | P4RuntimeGroupWrapper newGroupWrapper = new P4RuntimeGroupWrapper(piActionGroup, group, |
| 121 | System.currentTimeMillis()); |
Carmelo Cascone | b2e3dba | 2017-07-27 12:07:09 -0400 | [diff] [blame] | 122 | switch (groupOp.opType()) { |
| 123 | case ADD: |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 124 | case MODIFY: |
Carmelo Cascone | 87b9b39 | 2017-10-02 18:33:20 +0200 | [diff] [blame] | 125 | if (writeGroupToDevice(oldGroupWrapper, piActionGroup)) { |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 126 | GROUP_STORE.put(groupRef, newGroupWrapper); |
| 127 | } |
| 128 | break; |
| 129 | case DELETE: |
Carmelo Cascone | 87b9b39 | 2017-10-02 18:33:20 +0200 | [diff] [blame] | 130 | if (deleteGroupFromDevice(piActionGroup)) { |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 131 | GROUP_STORE.remove(groupRef); |
| 132 | } |
Carmelo Cascone | b2e3dba | 2017-07-27 12:07:09 -0400 | [diff] [blame] | 133 | break; |
| 134 | default: |
Carmelo Cascone | 87b9b39 | 2017-10-02 18:33:20 +0200 | [diff] [blame] | 135 | log.warn("Group operation {} not supported", groupOp.opType()); |
Carmelo Cascone | b2e3dba | 2017-07-27 12:07:09 -0400 | [diff] [blame] | 136 | } |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 137 | } finally { |
| 138 | lock.unlock(); |
Carmelo Cascone | b2e3dba | 2017-07-27 12:07:09 -0400 | [diff] [blame] | 139 | } |
| 140 | } |
| 141 | |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 142 | /** |
| 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 Cascone | 87b9b39 | 2017-10-02 18:33:20 +0200 | [diff] [blame] | 146 | * @param piActionGroup the action group to be installed |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 147 | * @return true if install success; false otherwise |
| 148 | */ |
Carmelo Cascone | 87b9b39 | 2017-10-02 18:33:20 +0200 | [diff] [blame] | 149 | private boolean writeGroupToDevice(P4RuntimeGroupWrapper oldGroupWrapper, PiActionGroup piActionGroup) { |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 150 | 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 Cascone | b2e3dba | 2017-07-27 12:07:09 -0400 | [diff] [blame] | 156 | } |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 157 | if (deleteBeforeUpdate && oldGroupWrapper != null) { |
Carmelo Cascone | 87b9b39 | 2017-10-02 18:33:20 +0200 | [diff] [blame] | 158 | success = deleteGroupFromDevice(oldGroupWrapper.piActionGroup()); |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 159 | } |
| 160 | writeSuccess = client.writeActionGroupMembers(piActionGroup, |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 161 | 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 Cascone | 87b9b39 | 2017-10-02 18:33:20 +0200 | [diff] [blame] | 172 | private boolean deleteGroupFromDevice(PiActionGroup piActionGroup) { |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 173 | boolean success; |
| 174 | CompletableFuture<Boolean> writeSuccess; |
| 175 | writeSuccess = client.writeActionGroup(piActionGroup, |
Carmelo Cascone | 87b9b39 | 2017-10-02 18:33:20 +0200 | [diff] [blame] | 176 | P4RuntimeClient.WriteOperationType.DELETE, |
| 177 | pipeconf); |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 178 | success = completeSuccess(writeSuccess, ACT_GRP, DELETE); |
| 179 | writeSuccess = client.writeActionGroupMembers(piActionGroup, |
Carmelo Cascone | 87b9b39 | 2017-10-02 18:33:20 +0200 | [diff] [blame] | 180 | P4RuntimeClient.WriteOperationType.DELETE, |
| 181 | pipeconf); |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 182 | 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 Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 196 | @Override |
| 197 | public Collection<Group> getGroups() { |
Carmelo Cascone | 87b9b39 | 2017-10-02 18:33:20 +0200 | [diff] [blame] | 198 | if (!setupBehaviour()) { |
| 199 | return Collections.emptyList(); |
Carmelo Cascone | b2e3dba | 2017-07-27 12:07:09 -0400 | [diff] [blame] | 200 | } |
| 201 | |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 202 | Collection<Group> result = Sets.newHashSet(); |
| 203 | Collection<PiActionProfileId> piActionProfileIds = Sets.newHashSet(); |
Carmelo Cascone | b2e3dba | 2017-07-27 12:07:09 -0400 | [diff] [blame] | 204 | |
Carmelo Cascone | 87b9b39 | 2017-10-02 18:33:20 +0200 | [diff] [blame] | 205 | // TODO: find better way to get all action profile ids. e.g. by providing them in the interpreter |
Yi Tseng | 82512da | 2017-08-16 19:46:36 -0700 | [diff] [blame] | 206 | 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 Cascone | b2e3dba | 2017-07-27 12:07:09 -0400 | [diff] [blame] | 255 | } |