| /* |
| * Copyright 2017-present Open Networking Foundation |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.onosproject.drivers.p4runtime; |
| |
| import com.google.common.collect.Maps; |
| import com.google.common.collect.Sets; |
| import org.onosproject.core.GroupId; |
| import org.onosproject.net.Device; |
| import org.onosproject.net.DeviceId; |
| import org.onosproject.net.device.DeviceService; |
| import org.onosproject.net.group.Group; |
| import org.onosproject.net.group.GroupOperation; |
| import org.onosproject.net.group.GroupOperations; |
| import org.onosproject.net.group.GroupProgrammable; |
| import org.onosproject.net.group.GroupStore; |
| import org.onosproject.net.pi.runtime.PiActionGroup; |
| import org.onosproject.net.pi.runtime.PiActionGroupId; |
| import org.onosproject.net.pi.runtime.PiActionProfileId; |
| import org.onosproject.net.pi.runtime.PiTranslationService; |
| import org.onosproject.p4runtime.api.P4RuntimeClient; |
| import org.onosproject.p4runtime.api.P4RuntimeGroupReference; |
| import org.onosproject.p4runtime.api.P4RuntimeGroupWrapper; |
| import org.slf4j.Logger; |
| |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Map; |
| import java.util.concurrent.CompletableFuture; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.concurrent.locks.Lock; |
| import java.util.concurrent.locks.ReentrantLock; |
| |
| import static org.slf4j.LoggerFactory.getLogger; |
| |
| /** |
| * Implementation of the group programmable behaviour for P4Runtime. |
| */ |
| public class P4RuntimeGroupProgrammable extends AbstractP4RuntimeHandlerBehaviour implements GroupProgrammable { |
| private static final String ACT_GRP_MEMS = "action group members"; |
| private static final String DELETE = "delete"; |
| private static final String ACT_GRP = "action group"; |
| private static final String INSERT = "insert"; |
| private static final Logger log = getLogger(P4RuntimeGroupProgrammable.class); |
| |
| /* |
| * About action groups in P4runtime: |
| * The type field is a place holder in p4runtime.proto right now, and we haven't defined it yet. You can assume all |
| * the groups are "select" as per the OF spec. As a remainder, in the P4 terminology a member corresponds to an OF |
| * bucket. Each member can also be used directly in the match table (kind of like an OF indirect group). |
| */ |
| |
| // TODO: make this attribute configurable by child drivers (e.g. BMv2 or Tofino) |
| /* |
| When updating an existing rule, if true, we issue a DELETE operation before inserting the new one, otherwise we |
| issue a MODIFY operation. This is useful fore devices that do not support MODIFY operations for table entries. |
| */ |
| private boolean deleteBeforeUpdate = true; |
| |
| // TODO: can remove this check as soon as the multi-apply-per-same-flow rule bug is fixed. |
| /* |
| If true, we ignore re-installing rules that are already known in the ENTRY_STORE, i.e. same match key and action. |
| */ |
| private boolean checkStoreBeforeUpdate = true; |
| |
| // Needed to synchronize operations over the same group. |
| private static final Map<P4RuntimeGroupReference, Lock> GROUP_LOCKS = Maps.newConcurrentMap(); |
| |
| // TODO: replace with distribute store |
| private static final Map<P4RuntimeGroupReference, P4RuntimeGroupWrapper> GROUP_STORE = Maps.newConcurrentMap(); |
| |
| @Override |
| public void performGroupOperation(DeviceId deviceId, GroupOperations groupOps) { |
| if (!setupBehaviour()) { |
| return; |
| } |
| |
| Device device = handler().get(DeviceService.class).getDevice(deviceId); |
| |
| for (GroupOperation groupOp : groupOps.operations()) { |
| processGroupOp(device, groupOp); |
| } |
| } |
| |
| private void processGroupOp(Device device, GroupOperation groupOp) { |
| GroupId groupId = groupOp.groupId(); |
| GroupStore groupStore = handler().get(GroupStore.class); |
| Group group = groupStore.getGroup(device.id(), groupId); |
| |
| PiActionGroup piActionGroup; |
| try { |
| piActionGroup = piTranslationService.translateGroup(group, pipeconf); |
| } catch (PiTranslationService.PiTranslationException e) { |
| log.warn("Unable translate group, aborting group operation {}: {}", groupOp.opType(), e.getMessage()); |
| return; |
| } |
| |
| P4RuntimeGroupReference groupRef = new P4RuntimeGroupReference(deviceId, piActionGroup.actionProfileId(), |
| piActionGroup.id()); |
| |
| Lock lock = GROUP_LOCKS.computeIfAbsent(groupRef, k -> new ReentrantLock()); |
| lock.lock(); |
| |
| try { |
| P4RuntimeGroupWrapper oldGroupWrapper = GROUP_STORE.get(groupRef); |
| P4RuntimeGroupWrapper newGroupWrapper = new P4RuntimeGroupWrapper(piActionGroup, group, |
| System.currentTimeMillis()); |
| switch (groupOp.opType()) { |
| case ADD: |
| case MODIFY: |
| if (writeGroupToDevice(oldGroupWrapper, piActionGroup)) { |
| GROUP_STORE.put(groupRef, newGroupWrapper); |
| } |
| break; |
| case DELETE: |
| if (deleteGroupFromDevice(piActionGroup)) { |
| GROUP_STORE.remove(groupRef); |
| } |
| break; |
| default: |
| log.warn("Group operation {} not supported", groupOp.opType()); |
| } |
| } finally { |
| lock.unlock(); |
| } |
| } |
| |
| /** |
| * Installs action group and members to device via client interface. |
| * |
| * @param oldGroupWrapper old group wrapper for the group; null if not exists |
| * @param piActionGroup the action group to be installed |
| * @return true if install success; false otherwise |
| */ |
| private boolean writeGroupToDevice(P4RuntimeGroupWrapper oldGroupWrapper, PiActionGroup piActionGroup) { |
| boolean success = true; |
| CompletableFuture<Boolean> writeSuccess; |
| if (checkStoreBeforeUpdate && oldGroupWrapper != null && |
| oldGroupWrapper.piActionGroup().equals(piActionGroup)) { |
| // Action group already exists, ignore it |
| return true; |
| } |
| if (deleteBeforeUpdate && oldGroupWrapper != null) { |
| success = deleteGroupFromDevice(oldGroupWrapper.piActionGroup()); |
| } |
| writeSuccess = client.writeActionGroupMembers(piActionGroup, |
| P4RuntimeClient.WriteOperationType.INSERT, |
| pipeconf); |
| success = success && completeSuccess(writeSuccess, ACT_GRP_MEMS, INSERT); |
| |
| writeSuccess = client.writeActionGroup(piActionGroup, |
| P4RuntimeClient.WriteOperationType.INSERT, |
| pipeconf); |
| success = success && completeSuccess(writeSuccess, ACT_GRP, INSERT); |
| return success; |
| } |
| |
| private boolean deleteGroupFromDevice(PiActionGroup piActionGroup) { |
| boolean success; |
| CompletableFuture<Boolean> writeSuccess; |
| writeSuccess = client.writeActionGroup(piActionGroup, |
| P4RuntimeClient.WriteOperationType.DELETE, |
| pipeconf); |
| success = completeSuccess(writeSuccess, ACT_GRP, DELETE); |
| writeSuccess = client.writeActionGroupMembers(piActionGroup, |
| P4RuntimeClient.WriteOperationType.DELETE, |
| pipeconf); |
| success = success && completeSuccess(writeSuccess, ACT_GRP_MEMS, DELETE); |
| return success; |
| } |
| |
| private boolean completeSuccess(CompletableFuture<Boolean> completableFuture, |
| String topic, String action) { |
| try { |
| return completableFuture.get(); |
| } catch (InterruptedException | ExecutionException e) { |
| log.warn("Can't {} {} due to {}", action, topic, e.getMessage()); |
| return false; |
| } |
| } |
| |
| @Override |
| public Collection<Group> getGroups() { |
| if (!setupBehaviour()) { |
| return Collections.emptyList(); |
| } |
| |
| Collection<Group> result = Sets.newHashSet(); |
| Collection<PiActionProfileId> piActionProfileIds = Sets.newHashSet(); |
| |
| // TODO: find better way to get all action profile ids. e.g. by providing them in the interpreter |
| GROUP_STORE.forEach((groupRef, wrapper) -> piActionProfileIds.add(groupRef.actionProfileId())); |
| |
| AtomicBoolean success = new AtomicBoolean(true); |
| piActionProfileIds.forEach(actionProfileId -> { |
| Collection<PiActionGroup> piActionGroups = Sets.newHashSet(); |
| try { |
| Collection<PiActionGroup> groupsFromDevice = |
| client.dumpGroups(actionProfileId, pipeconf).get(); |
| if (groupsFromDevice == null) { |
| // Got error |
| success.set(false); |
| } else { |
| piActionGroups.addAll(groupsFromDevice); |
| } |
| } catch (ExecutionException | InterruptedException e) { |
| log.error("Exception while dumping groups for action profile {}: {}", |
| actionProfileId.id(), deviceId, e); |
| success.set(false); |
| } |
| |
| piActionGroups.forEach(piActionGroup -> { |
| PiActionGroupId actionGroupId = piActionGroup.id(); |
| P4RuntimeGroupReference groupRef = |
| new P4RuntimeGroupReference(deviceId, actionProfileId, actionGroupId); |
| P4RuntimeGroupWrapper wrapper = GROUP_STORE.get(groupRef); |
| |
| if (wrapper == null) { |
| // group exists in client, but can't find in ONOS |
| log.warn("Can't find action profile group {} from local store.", |
| groupRef); |
| return; |
| } |
| if (!wrapper.piActionGroup().equals(piActionGroup)) { |
| log.warn("Group from device is different to group from local store."); |
| return; |
| } |
| result.add(wrapper.group()); |
| |
| }); |
| }); |
| |
| if (!success.get()) { |
| // Got error while dump groups from device. |
| return Collections.emptySet(); |
| } else { |
| return result; |
| } |
| } |
| |
| } |