blob: 1c4d5dcdef8ed9deabbb5169d097c87012c65226 [file] [log] [blame]
/*
* 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.model.PiActionProfileId;
import org.onosproject.net.pi.runtime.PiActionGroup;
import org.onosproject.net.pi.runtime.PiActionGroupId;
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;
}
}
}