| /* |
| * 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 org.onosproject.drivers.p4runtime.mirror.P4RuntimeGroupMirror; |
| import org.onosproject.net.DeviceId; |
| import org.onosproject.net.group.DefaultGroup; |
| 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.model.PiActionProfileModel; |
| import org.onosproject.net.pi.runtime.PiActionGroup; |
| import org.onosproject.net.pi.runtime.PiActionGroupHandle; |
| import org.onosproject.net.pi.service.PiGroupTranslator; |
| import org.onosproject.net.pi.service.PiTranslatedEntity; |
| import org.onosproject.net.pi.service.PiTranslationException; |
| import org.slf4j.Logger; |
| |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.concurrent.CompletableFuture; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.locks.Lock; |
| import java.util.concurrent.locks.ReentrantLock; |
| import java.util.stream.Collectors; |
| import java.util.stream.Stream; |
| |
| import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.DELETE; |
| import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.INSERT; |
| import static org.slf4j.LoggerFactory.getLogger; |
| |
| /** |
| * Implementation of the group programmable behaviour for P4Runtime. |
| */ |
| public class P4RuntimeGroupProgrammable |
| extends AbstractP4RuntimeHandlerBehaviour |
| implements GroupProgrammable { |
| |
| private enum Operation { |
| APPLY, REMOVE |
| } |
| |
| private static final String ACT_GRP_MEMS_STR = "action group members"; |
| private static final String DELETE_STR = "delete"; |
| private static final String ACT_GRP_STR = "action group"; |
| private static final String INSERT_STR = "insert"; |
| |
| private static final Logger log = getLogger(P4RuntimeGroupProgrammable.class); |
| |
| // If true, we ignore re-installing groups that are already known in the |
| // device mirror. |
| private boolean checkMirrorBeforeUpdate = true; |
| |
| private GroupStore groupStore; |
| private P4RuntimeGroupMirror groupMirror; |
| private PiGroupTranslator translator; |
| |
| // Needed to synchronize operations over the same group. |
| private static final Map<PiActionGroupHandle, Lock> GROUP_LOCKS = |
| Maps.newConcurrentMap(); |
| |
| @Override |
| protected boolean setupBehaviour() { |
| if (!super.setupBehaviour()) { |
| return false; |
| } |
| groupMirror = this.handler().get(P4RuntimeGroupMirror.class); |
| groupStore = handler().get(GroupStore.class); |
| translator = piTranslationService.groupTranslator(); |
| return true; |
| } |
| |
| @Override |
| public void performGroupOperation(DeviceId deviceId, |
| GroupOperations groupOps) { |
| if (!setupBehaviour()) { |
| return; |
| } |
| groupOps.operations().forEach(op -> processGroupOp(deviceId, op)); |
| } |
| |
| @Override |
| public Collection<Group> getGroups() { |
| if (!setupBehaviour()) { |
| return Collections.emptyList(); |
| } |
| return pipeconf.pipelineModel().actionProfiles().stream() |
| .map(PiActionProfileModel::id) |
| .flatMap(this::streamGroupsFromDevice) |
| .collect(Collectors.toList()); |
| } |
| |
| private void processGroupOp(DeviceId deviceId, GroupOperation groupOp) { |
| final Group pdGroup = groupStore.getGroup(deviceId, groupOp.groupId()); |
| |
| final PiActionGroup piGroup; |
| try { |
| piGroup = translator.translate(pdGroup, pipeconf); |
| } catch (PiTranslationException e) { |
| log.warn("Unable translate group, aborting {} operation: {}", |
| groupOp.opType(), e.getMessage()); |
| return; |
| } |
| |
| final PiActionGroupHandle handle = PiActionGroupHandle.of(deviceId, piGroup); |
| |
| final PiActionGroup groupOnDevice = groupMirror.get(handle) == null |
| ? null |
| : groupMirror.get(handle).entry(); |
| |
| final Lock lock = GROUP_LOCKS.computeIfAbsent(handle, k -> new ReentrantLock()); |
| lock.lock(); |
| try { |
| final Operation operation; |
| switch (groupOp.opType()) { |
| case ADD: |
| case MODIFY: |
| operation = Operation.APPLY; |
| break; |
| case DELETE: |
| operation = Operation.REMOVE; |
| break; |
| default: |
| log.warn("Group operation {} not supported", groupOp.opType()); |
| return; |
| } |
| processPiGroup(handle, piGroup, |
| groupOnDevice, pdGroup, operation); |
| } finally { |
| lock.unlock(); |
| } |
| } |
| |
| private void processPiGroup(PiActionGroupHandle handle, |
| PiActionGroup groupToApply, |
| PiActionGroup groupOnDevice, |
| Group pdGroup, Operation operation) { |
| if (operation == Operation.APPLY) { |
| if (groupOnDevice != null) { |
| if (checkMirrorBeforeUpdate |
| && groupOnDevice.equals(groupToApply)) { |
| // Group on device has the same members, ignore operation. |
| return; |
| } |
| // Remove before adding it. |
| processPiGroup(handle, groupToApply, groupOnDevice, |
| pdGroup, Operation.REMOVE); |
| } |
| if (writeGroupToDevice(groupToApply)) { |
| groupMirror.put(handle, groupToApply); |
| translator.learn(handle, new PiTranslatedEntity<>( |
| pdGroup, groupToApply, handle)); |
| } |
| } else { |
| if (deleteGroupFromDevice(groupToApply)) { |
| groupMirror.remove(handle); |
| translator.forget(handle); |
| } |
| } |
| } |
| |
| private boolean writeGroupToDevice(PiActionGroup groupToApply) { |
| // First insert members, then group. |
| // The operation is deemed successful if both operations are successful. |
| // FIXME: add transactional semantics, i.e. remove members if group fails. |
| final boolean membersSuccess = completeFuture( |
| client.writeActionGroupMembers(groupToApply, INSERT, pipeconf), |
| ACT_GRP_MEMS_STR, INSERT_STR); |
| return membersSuccess && completeFuture( |
| client.writeActionGroup(groupToApply, INSERT, pipeconf), |
| ACT_GRP_STR, INSERT_STR); |
| } |
| |
| private boolean deleteGroupFromDevice(PiActionGroup piActionGroup) { |
| // First delete group, then members. |
| // The operation is deemed successful if both operations are successful. |
| final boolean groupSuccess = completeFuture( |
| client.writeActionGroup(piActionGroup, DELETE, pipeconf), |
| ACT_GRP_STR, DELETE_STR); |
| return groupSuccess && completeFuture( |
| client.writeActionGroupMembers(piActionGroup, DELETE, pipeconf), |
| ACT_GRP_MEMS_STR, DELETE_STR); |
| } |
| |
| private boolean completeFuture(CompletableFuture<Boolean> completableFuture, |
| String topic, String action) { |
| try { |
| if (completableFuture.get()) { |
| return true; |
| } else { |
| log.warn("Unable to {} {}", action, topic); |
| return false; |
| } |
| } catch (InterruptedException | ExecutionException e) { |
| log.warn("Exception while performing {} {}: {}", action, topic, e.getMessage()); |
| log.debug("Exception", e); |
| return false; |
| } |
| } |
| |
| private Stream<Group> streamGroupsFromDevice(PiActionProfileId actProfId) { |
| try { |
| // Read PI groups and return original PD one. |
| return client.dumpGroups(actProfId, pipeconf).get().stream() |
| .map(this::forgeGroupEntry) |
| .filter(Objects::nonNull); |
| } catch (ExecutionException | InterruptedException e) { |
| log.error("Exception while dumping groups from action profile '{}' on {}: {}", |
| actProfId.id(), deviceId, e); |
| return Stream.empty(); |
| } |
| } |
| |
| private Group forgeGroupEntry(PiActionGroup piGroup) { |
| final PiActionGroupHandle handle = PiActionGroupHandle.of(deviceId, piGroup); |
| if (!translator.lookup(handle).isPresent()) { |
| log.warn("Missing PI group from translation store: {} - {}:{}", |
| pipeconf.id(), piGroup.actionProfileId(), |
| piGroup.id()); |
| return null; |
| } |
| final long life = groupMirror.get(handle) != null |
| ? groupMirror.get(handle).lifeSec() : 0; |
| final Group original = translator.lookup(handle).get().original(); |
| final DefaultGroup forgedGroup = new DefaultGroup(original.id(), original); |
| forgedGroup.setState(Group.GroupState.ADDED); |
| forgedGroup.setLife(life); |
| return forgedGroup; |
| } |
| } |