blob: 8676aeefcc5ebb9677f523b2a3177b13ab99260c [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;
Yi Tseng8d355132018-04-13 01:40:48 +080020import com.google.common.collect.Sets;
Carmelo Casconee75b7942017-11-21 17:14:49 -080021import org.onosproject.drivers.p4runtime.mirror.P4RuntimeGroupMirror;
Yi Tseng76737cf2018-01-31 17:13:21 -080022import org.onosproject.drivers.p4runtime.mirror.TimedEntry;
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040023import org.onosproject.net.DeviceId;
Carmelo Casconee75b7942017-11-21 17:14:49 -080024import org.onosproject.net.group.DefaultGroup;
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 Casconee75b7942017-11-21 17:14:49 -080031import org.onosproject.net.pi.model.PiActionProfileModel;
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040032import org.onosproject.net.pi.runtime.PiActionGroup;
Carmelo Casconee75b7942017-11-21 17:14:49 -080033import org.onosproject.net.pi.runtime.PiActionGroupHandle;
Yi Tseng8d355132018-04-13 01:40:48 +080034import org.onosproject.net.pi.runtime.PiActionGroupMember;
Carmelo Casconee75b7942017-11-21 17:14:49 -080035import org.onosproject.net.pi.service.PiGroupTranslator;
36import org.onosproject.net.pi.service.PiTranslatedEntity;
Carmelo Cascone326ad2d2017-11-28 18:09:13 -080037import org.onosproject.net.pi.service.PiTranslationException;
Yi Tseng82512da2017-08-16 19:46:36 -070038import org.slf4j.Logger;
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040039
Yi Tseng82512da2017-08-16 19:46:36 -070040import java.util.Collection;
41import java.util.Collections;
Yi Tseng82512da2017-08-16 19:46:36 -070042import java.util.Map;
Carmelo Casconee75b7942017-11-21 17:14:49 -080043import java.util.Objects;
Yi Tseng82512da2017-08-16 19:46:36 -070044import java.util.concurrent.CompletableFuture;
Yi Tseng82512da2017-08-16 19:46:36 -070045import java.util.concurrent.locks.Lock;
46import java.util.concurrent.locks.ReentrantLock;
Carmelo Casconee75b7942017-11-21 17:14:49 -080047import java.util.stream.Collectors;
48import java.util.stream.Stream;
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040049
Carmelo Casconee5b28722018-06-22 17:28:28 +020050import static java.lang.String.format;
Carmelo Casconee75b7942017-11-21 17:14:49 -080051import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.DELETE;
52import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.INSERT;
Yi Tseng8d355132018-04-13 01:40:48 +080053import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.MODIFY;
Yi Tseng82512da2017-08-16 19:46:36 -070054import static org.slf4j.LoggerFactory.getLogger;
55
56/**
57 * Implementation of the group programmable behaviour for P4Runtime.
58 */
Carmelo Casconee75b7942017-11-21 17:14:49 -080059public class P4RuntimeGroupProgrammable
60 extends AbstractP4RuntimeHandlerBehaviour
61 implements GroupProgrammable {
62
Carmelo Casconee75b7942017-11-21 17:14:49 -080063 private static final String ACT_GRP_MEMS_STR = "action group members";
64 private static final String DELETE_STR = "delete";
65 private static final String ACT_GRP_STR = "action group";
66 private static final String INSERT_STR = "insert";
Yi Tseng8d355132018-04-13 01:40:48 +080067 private static final String MODIFY_STR = "modify";
Carmelo Casconee75b7942017-11-21 17:14:49 -080068
Yi Tseng82512da2017-08-16 19:46:36 -070069 private static final Logger log = getLogger(P4RuntimeGroupProgrammable.class);
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040070
Carmelo Casconee75b7942017-11-21 17:14:49 -080071 // If true, we ignore re-installing groups that are already known in the
72 // device mirror.
Yi Tsengf325a602018-06-27 18:26:33 +080073 private static final String CHECK_MIRROR_BEFORE_UPDATE = "checkMirrorBeforeUpdate";
74 private static final boolean DEFAULT_CHECK_MIRROR_BEFORE_UPDATE = true;
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040075
Yi Tseng76737cf2018-01-31 17:13:21 -080076 // If true, we avoid querying the device and return what's already known by
77 // the ONOS store.
Yi Tsengf325a602018-06-27 18:26:33 +080078 private static final String IGNORE_DEVICE_WHEN_GET = "ignoreDeviceWhenGet";
79 private static final boolean DEFAULT_IGNORE_DEVICE_WHEN_GET = false;
Yi Tseng76737cf2018-01-31 17:13:21 -080080
Esin Karaman971fb7f2017-12-28 13:44:52 +000081 protected GroupStore groupStore;
Carmelo Casconee75b7942017-11-21 17:14:49 -080082 private P4RuntimeGroupMirror groupMirror;
83 private PiGroupTranslator translator;
Yi Tseng82512da2017-08-16 19:46:36 -070084
85 // Needed to synchronize operations over the same group.
Carmelo Casconee75b7942017-11-21 17:14:49 -080086 private static final Map<PiActionGroupHandle, Lock> GROUP_LOCKS =
87 Maps.newConcurrentMap();
Yi Tseng82512da2017-08-16 19:46:36 -070088
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040089 @Override
Carmelo Casconee75b7942017-11-21 17:14:49 -080090 protected boolean setupBehaviour() {
91 if (!super.setupBehaviour()) {
92 return false;
93 }
94 groupMirror = this.handler().get(P4RuntimeGroupMirror.class);
95 groupStore = handler().get(GroupStore.class);
96 translator = piTranslationService.groupTranslator();
97 return true;
98 }
99
100 @Override
101 public void performGroupOperation(DeviceId deviceId,
102 GroupOperations groupOps) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200103 if (!setupBehaviour()) {
Yi Tseng82512da2017-08-16 19:46:36 -0700104 return;
105 }
Carmelo Casconee75b7942017-11-21 17:14:49 -0800106 groupOps.operations().forEach(op -> processGroupOp(deviceId, op));
Yi Tseng82512da2017-08-16 19:46:36 -0700107 }
108
Yi Tseng82512da2017-08-16 19:46:36 -0700109 @Override
110 public Collection<Group> getGroups() {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200111 if (!setupBehaviour()) {
112 return Collections.emptyList();
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -0400113 }
Yi Tsengf325a602018-06-27 18:26:33 +0800114 if (!driverBoolProperty(IGNORE_DEVICE_WHEN_GET, DEFAULT_IGNORE_DEVICE_WHEN_GET)) {
Yi Tseng76737cf2018-01-31 17:13:21 -0800115 return pipeconf.pipelineModel().actionProfiles().stream()
116 .map(PiActionProfileModel::id)
117 .flatMap(this::streamGroupsFromDevice)
118 .collect(Collectors.toList());
119 } else {
120 return groupMirror.getAll(deviceId).stream()
121 .map(TimedEntry::entry)
122 .map(this::forgeGroupEntry)
123 .collect(Collectors.toList());
124 }
Carmelo Casconee75b7942017-11-21 17:14:49 -0800125 }
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -0400126
Carmelo Casconee75b7942017-11-21 17:14:49 -0800127 private void processGroupOp(DeviceId deviceId, GroupOperation groupOp) {
128 final Group pdGroup = groupStore.getGroup(deviceId, groupOp.groupId());
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -0400129
Carmelo Casconee75b7942017-11-21 17:14:49 -0800130 final PiActionGroup piGroup;
131 try {
132 piGroup = translator.translate(pdGroup, pipeconf);
133 } catch (PiTranslationException e) {
134 log.warn("Unable translate group, aborting {} operation: {}",
135 groupOp.opType(), e.getMessage());
136 return;
137 }
Yi Tseng82512da2017-08-16 19:46:36 -0700138
Carmelo Casconee75b7942017-11-21 17:14:49 -0800139 final PiActionGroupHandle handle = PiActionGroupHandle.of(deviceId, piGroup);
140
141 final PiActionGroup groupOnDevice = groupMirror.get(handle) == null
142 ? null
143 : groupMirror.get(handle).entry();
144
145 final Lock lock = GROUP_LOCKS.computeIfAbsent(handle, k -> new ReentrantLock());
146 lock.lock();
147 try {
Carmelo Casconee75b7942017-11-21 17:14:49 -0800148 processPiGroup(handle, piGroup,
Yi Tseng8d355132018-04-13 01:40:48 +0800149 groupOnDevice, pdGroup, groupOp.opType());
Carmelo Casconee75b7942017-11-21 17:14:49 -0800150 } finally {
151 lock.unlock();
Yi Tseng82512da2017-08-16 19:46:36 -0700152 }
153 }
154
Carmelo Casconee75b7942017-11-21 17:14:49 -0800155 private void processPiGroup(PiActionGroupHandle handle,
156 PiActionGroup groupToApply,
157 PiActionGroup groupOnDevice,
Yi Tseng8d355132018-04-13 01:40:48 +0800158 Group pdGroup, GroupOperation.Type operationType) {
159 if (operationType == GroupOperation.Type.ADD) {
Carmelo Casconee75b7942017-11-21 17:14:49 -0800160 if (groupOnDevice != null) {
Yi Tseng8d355132018-04-13 01:40:48 +0800161 log.warn("Unable to add group {} since group already on device {}",
162 groupToApply.id(), deviceId);
163 log.debug("To apply: {}", groupToApply);
164 log.debug("On device: {}", groupOnDevice);
165 return;
Carmelo Casconee75b7942017-11-21 17:14:49 -0800166 }
Yi Tseng8d355132018-04-13 01:40:48 +0800167
Carmelo Casconee75b7942017-11-21 17:14:49 -0800168 if (writeGroupToDevice(groupToApply)) {
169 groupMirror.put(handle, groupToApply);
170 translator.learn(handle, new PiTranslatedEntity<>(
171 pdGroup, groupToApply, handle));
172 }
Yi Tseng8d355132018-04-13 01:40:48 +0800173 } else if (operationType == GroupOperation.Type.MODIFY) {
174 if (groupOnDevice == null) {
175 log.warn("Group {} does not exists on device {}, can not modify it",
176 groupToApply.id(), deviceId);
177 return;
178 }
Yi Tsengf325a602018-06-27 18:26:33 +0800179 if (driverBoolProperty(CHECK_MIRROR_BEFORE_UPDATE, DEFAULT_CHECK_MIRROR_BEFORE_UPDATE)
Yi Tseng8d355132018-04-13 01:40:48 +0800180 && groupOnDevice.equals(groupToApply)) {
181 // Group on device has the same members, ignore operation.
182 return;
183 }
184 if (modifyGroupFromDevice(groupToApply, groupOnDevice)) {
185 groupMirror.put(handle, groupToApply);
186 translator.learn(handle,
187 new PiTranslatedEntity<>(pdGroup, groupToApply, handle));
188 }
Carmelo Casconee75b7942017-11-21 17:14:49 -0800189 } else {
Yi Tseng8d355132018-04-13 01:40:48 +0800190 if (groupOnDevice == null) {
191 log.warn("Unable to remove group {} from device {} since it does" +
192 "not exists on device.", groupToApply.id(), deviceId);
193 return;
194 }
195 if (deleteGroupFromDevice(groupOnDevice)) {
Carmelo Casconee75b7942017-11-21 17:14:49 -0800196 groupMirror.remove(handle);
197 translator.forget(handle);
198 }
199 }
200 }
201
Yi Tseng8d355132018-04-13 01:40:48 +0800202 private boolean modifyGroupFromDevice(PiActionGroup groupToApply, PiActionGroup groupOnDevice) {
203 PiActionProfileId groupProfileId = groupToApply.actionProfileId();
204 Collection<PiActionGroupMember> membersToRemove = Sets.newHashSet(groupOnDevice.members());
205 membersToRemove.removeAll(groupToApply.members());
206 Collection<PiActionGroupMember> membersToAdd = Sets.newHashSet(groupToApply.members());
207 membersToAdd.removeAll(groupOnDevice.members());
208
209 if (!membersToAdd.isEmpty() &&
210 !completeFuture(client.writeActionGroupMembers(groupProfileId, membersToAdd, INSERT, pipeconf),
211 ACT_GRP_MEMS_STR, INSERT_STR)) {
212 // remove what we added
213 completeFuture(client.writeActionGroupMembers(groupProfileId, membersToAdd, DELETE, pipeconf),
214 ACT_GRP_MEMS_STR, INSERT_STR);
215 return false;
216 }
217
218 if (!completeFuture(client.writeActionGroup(groupToApply, MODIFY, pipeconf),
219 ACT_GRP_STR, MODIFY_STR)) {
220 // recover group information
221 completeFuture(client.writeActionGroup(groupOnDevice, MODIFY, pipeconf),
222 ACT_GRP_STR, MODIFY_STR);
223 // remove what we added
224 completeFuture(client.writeActionGroupMembers(groupProfileId, membersToAdd, DELETE, pipeconf),
225 ACT_GRP_MEMS_STR, INSERT_STR);
226 return false;
227 }
228
229 if (!membersToRemove.isEmpty() &&
230 !completeFuture(client.writeActionGroupMembers(groupProfileId, membersToRemove, DELETE, pipeconf),
Carmelo Casconee5b28722018-06-22 17:28:28 +0200231 ACT_GRP_MEMS_STR, DELETE_STR)) {
Yi Tseng8d355132018-04-13 01:40:48 +0800232 // add what we removed
233 completeFuture(client.writeActionGroupMembers(groupProfileId, membersToRemove, INSERT, pipeconf),
234 ACT_GRP_MEMS_STR, DELETE_STR);
235 // recover group information
236 completeFuture(client.writeActionGroup(groupOnDevice, MODIFY, pipeconf),
237 ACT_GRP_STR, MODIFY_STR);
238 // remove what we added
239 completeFuture(client.writeActionGroupMembers(groupProfileId, membersToAdd, DELETE, pipeconf),
240 ACT_GRP_MEMS_STR, INSERT_STR);
241 return false;
242 }
243
244 return true;
245 }
246
Carmelo Casconee75b7942017-11-21 17:14:49 -0800247 private boolean writeGroupToDevice(PiActionGroup groupToApply) {
248 // First insert members, then group.
249 // The operation is deemed successful if both operations are successful.
250 // FIXME: add transactional semantics, i.e. remove members if group fails.
251 final boolean membersSuccess = completeFuture(
Yi Tseng8d355132018-04-13 01:40:48 +0800252 client.writeActionGroupMembers(groupToApply.actionProfileId(),
253 groupToApply.members(),
254 INSERT, pipeconf),
Carmelo Casconee75b7942017-11-21 17:14:49 -0800255 ACT_GRP_MEMS_STR, INSERT_STR);
256 return membersSuccess && completeFuture(
257 client.writeActionGroup(groupToApply, INSERT, pipeconf),
258 ACT_GRP_STR, INSERT_STR);
259 }
260
261 private boolean deleteGroupFromDevice(PiActionGroup piActionGroup) {
262 // First delete group, then members.
263 // The operation is deemed successful if both operations are successful.
264 final boolean groupSuccess = completeFuture(
265 client.writeActionGroup(piActionGroup, DELETE, pipeconf),
266 ACT_GRP_STR, DELETE_STR);
267 return groupSuccess && completeFuture(
Yi Tseng8d355132018-04-13 01:40:48 +0800268 client.writeActionGroupMembers(piActionGroup.actionProfileId(),
269 piActionGroup.members(),
270 DELETE, pipeconf),
Carmelo Casconee75b7942017-11-21 17:14:49 -0800271 ACT_GRP_MEMS_STR, DELETE_STR);
272 }
273
274 private boolean completeFuture(CompletableFuture<Boolean> completableFuture,
275 String topic, String action) {
Carmelo Casconee5b28722018-06-22 17:28:28 +0200276 return getFutureWithDeadline(
277 completableFuture, format("performing %s %s", action, topic), false);
Carmelo Casconee75b7942017-11-21 17:14:49 -0800278 }
279
280 private Stream<Group> streamGroupsFromDevice(PiActionProfileId actProfId) {
Carmelo Casconee5b28722018-06-22 17:28:28 +0200281 // Read PI groups and return original PD one.
282 Collection<PiActionGroup> groups = getFutureWithDeadline(
283 client.dumpGroups(actProfId, pipeconf),
284 "dumping groups", Collections.emptyList());
285 return groups.stream()
286 .map(this::forgeGroupEntry)
287 .filter(Objects::nonNull);
Carmelo Casconee75b7942017-11-21 17:14:49 -0800288 }
289
290 private Group forgeGroupEntry(PiActionGroup piGroup) {
291 final PiActionGroupHandle handle = PiActionGroupHandle.of(deviceId, piGroup);
292 if (!translator.lookup(handle).isPresent()) {
293 log.warn("Missing PI group from translation store: {} - {}:{}",
294 pipeconf.id(), piGroup.actionProfileId(),
295 piGroup.id());
296 return null;
297 }
298 final long life = groupMirror.get(handle) != null
299 ? groupMirror.get(handle).lifeSec() : 0;
300 final Group original = translator.lookup(handle).get().original();
301 final DefaultGroup forgedGroup = new DefaultGroup(original.id(), original);
302 forgedGroup.setState(Group.GroupState.ADDED);
303 forgedGroup.setLife(life);
304 return forgedGroup;
305 }
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -0400306}