blob: e6f0a0477da37de2d15dda44135e482fb26306f6 [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
Carmelo Cascone58136812018-07-19 03:40:16 +020019import com.google.common.collect.ImmutableList;
Carmelo Cascone26600972018-09-10 00:23:20 -070020import com.google.common.collect.Lists;
Yi Tseng8d355132018-04-13 01:40:48 +080021import com.google.common.collect.Sets;
Carmelo Cascone58136812018-07-19 03:40:16 +020022import com.google.common.util.concurrent.Striped;
Carmelo Cascone26600972018-09-10 00:23:20 -070023import org.onlab.util.SharedExecutors;
Carmelo Casconee75b7942017-11-21 17:14:49 -080024import org.onosproject.drivers.p4runtime.mirror.P4RuntimeGroupMirror;
Carmelo Cascone58136812018-07-19 03:40:16 +020025import org.onosproject.drivers.p4runtime.mirror.P4RuntimeMulticastGroupMirror;
Yi Tseng76737cf2018-01-31 17:13:21 -080026import org.onosproject.drivers.p4runtime.mirror.TimedEntry;
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040027import org.onosproject.net.DeviceId;
Carmelo Casconee75b7942017-11-21 17:14:49 -080028import org.onosproject.net.group.DefaultGroup;
Yi Tsengfe13f3e2018-08-19 03:09:54 +080029import org.onosproject.net.group.DefaultGroupDescription;
Yi Tseng82512da2017-08-16 19:46:36 -070030import org.onosproject.net.group.Group;
Carmelo Cascone58136812018-07-19 03:40:16 +020031import org.onosproject.net.group.GroupDescription;
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040032import org.onosproject.net.group.GroupOperation;
33import org.onosproject.net.group.GroupOperations;
34import org.onosproject.net.group.GroupProgrammable;
Yi Tseng82512da2017-08-16 19:46:36 -070035import org.onosproject.net.group.GroupStore;
Carmelo Cascone87892e22017-11-13 16:01:29 -080036import org.onosproject.net.pi.model.PiActionProfileId;
Carmelo Casconee75b7942017-11-21 17:14:49 -080037import org.onosproject.net.pi.model.PiActionProfileModel;
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040038import org.onosproject.net.pi.runtime.PiActionGroup;
Carmelo Casconee75b7942017-11-21 17:14:49 -080039import org.onosproject.net.pi.runtime.PiActionGroupHandle;
Yi Tseng8d355132018-04-13 01:40:48 +080040import org.onosproject.net.pi.runtime.PiActionGroupMember;
Carmelo Cascone58136812018-07-19 03:40:16 +020041import org.onosproject.net.pi.runtime.PiMulticastGroupEntry;
42import org.onosproject.net.pi.runtime.PiMulticastGroupEntryHandle;
Carmelo Casconee75b7942017-11-21 17:14:49 -080043import org.onosproject.net.pi.service.PiGroupTranslator;
Carmelo Cascone58136812018-07-19 03:40:16 +020044import org.onosproject.net.pi.service.PiMulticastGroupTranslator;
Carmelo Casconee75b7942017-11-21 17:14:49 -080045import org.onosproject.net.pi.service.PiTranslatedEntity;
Carmelo Cascone326ad2d2017-11-28 18:09:13 -080046import org.onosproject.net.pi.service.PiTranslationException;
Carmelo Cascone58136812018-07-19 03:40:16 +020047import org.onosproject.p4runtime.api.P4RuntimeClient;
Yi Tseng82512da2017-08-16 19:46:36 -070048import org.slf4j.Logger;
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040049
Yi Tseng82512da2017-08-16 19:46:36 -070050import java.util.Collection;
51import java.util.Collections;
Carmelo Cascone26600972018-09-10 00:23:20 -070052import java.util.List;
Carmelo Casconee75b7942017-11-21 17:14:49 -080053import java.util.Objects;
Carmelo Cascone26600972018-09-10 00:23:20 -070054import java.util.Optional;
Yi Tseng82512da2017-08-16 19:46:36 -070055import java.util.concurrent.CompletableFuture;
Yi Tseng82512da2017-08-16 19:46:36 -070056import java.util.concurrent.locks.Lock;
Carmelo Casconee75b7942017-11-21 17:14:49 -080057import java.util.stream.Collectors;
58import java.util.stream.Stream;
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040059
Carmelo Cascone26600972018-09-10 00:23:20 -070060import static com.google.common.base.Preconditions.checkArgument;
Carmelo Casconee5b28722018-06-22 17:28:28 +020061import static java.lang.String.format;
Carmelo Casconee75b7942017-11-21 17:14:49 -080062import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.DELETE;
63import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.INSERT;
Yi Tseng8d355132018-04-13 01:40:48 +080064import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.MODIFY;
Yi Tseng82512da2017-08-16 19:46:36 -070065import static org.slf4j.LoggerFactory.getLogger;
66
67/**
68 * Implementation of the group programmable behaviour for P4Runtime.
Carmelo Cascone58136812018-07-19 03:40:16 +020069 * <p>
70 * This implementation distinguishes between ALL groups, and other types. ALL
71 * groups are handled via PRE multicast group programming, while other types are
72 * handled via action profile group programming.
Yi Tseng82512da2017-08-16 19:46:36 -070073 */
Carmelo Casconee75b7942017-11-21 17:14:49 -080074public class P4RuntimeGroupProgrammable
75 extends AbstractP4RuntimeHandlerBehaviour
76 implements GroupProgrammable {
77
Carmelo Casconee75b7942017-11-21 17:14:49 -080078 private static final String ACT_GRP_MEMS_STR = "action group members";
79 private static final String DELETE_STR = "delete";
80 private static final String ACT_GRP_STR = "action group";
81 private static final String INSERT_STR = "insert";
Yi Tseng8d355132018-04-13 01:40:48 +080082 private static final String MODIFY_STR = "modify";
Carmelo Casconee75b7942017-11-21 17:14:49 -080083
Yi Tseng82512da2017-08-16 19:46:36 -070084 private static final Logger log = getLogger(P4RuntimeGroupProgrammable.class);
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040085
Carmelo Casconee75b7942017-11-21 17:14:49 -080086 // If true, we ignore re-installing groups that are already known in the
87 // device mirror.
Yi Tsengf325a602018-06-27 18:26:33 +080088 private static final String CHECK_MIRROR_BEFORE_UPDATE = "checkMirrorBeforeUpdate";
89 private static final boolean DEFAULT_CHECK_MIRROR_BEFORE_UPDATE = true;
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -040090
Yi Tseng76737cf2018-01-31 17:13:21 -080091 // If true, we avoid querying the device and return what's already known by
92 // the ONOS store.
Carmelo Cascone26600972018-09-10 00:23:20 -070093 private static final String READ_ACTION_GROUPS_FROM_MIRROR = "actionGroupReadFromMirror";
94 private static final boolean DEFAULT_READ_ACTION_GROUPS_FROM_MIRROR = false;
Yi Tseng76737cf2018-01-31 17:13:21 -080095
Esin Karaman971fb7f2017-12-28 13:44:52 +000096 protected GroupStore groupStore;
Carmelo Casconee75b7942017-11-21 17:14:49 -080097 private P4RuntimeGroupMirror groupMirror;
Carmelo Cascone58136812018-07-19 03:40:16 +020098 private PiGroupTranslator groupTranslator;
99 private P4RuntimeMulticastGroupMirror mcGroupMirror;
100 private PiMulticastGroupTranslator mcGroupTranslator;
Yi Tseng82512da2017-08-16 19:46:36 -0700101
102 // Needed to synchronize operations over the same group.
Carmelo Cascone58136812018-07-19 03:40:16 +0200103 private static final Striped<Lock> STRIPED_LOCKS = Striped.lock(30);
Yi Tseng82512da2017-08-16 19:46:36 -0700104
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -0400105 @Override
Carmelo Casconee75b7942017-11-21 17:14:49 -0800106 protected boolean setupBehaviour() {
107 if (!super.setupBehaviour()) {
108 return false;
109 }
110 groupMirror = this.handler().get(P4RuntimeGroupMirror.class);
Carmelo Cascone58136812018-07-19 03:40:16 +0200111 mcGroupMirror = this.handler().get(P4RuntimeMulticastGroupMirror.class);
Carmelo Casconee75b7942017-11-21 17:14:49 -0800112 groupStore = handler().get(GroupStore.class);
Carmelo Cascone58136812018-07-19 03:40:16 +0200113 groupTranslator = piTranslationService.groupTranslator();
114 mcGroupTranslator = piTranslationService.multicastGroupTranslator();
Carmelo Casconee75b7942017-11-21 17:14:49 -0800115 return true;
116 }
117
118 @Override
119 public void performGroupOperation(DeviceId deviceId,
120 GroupOperations groupOps) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200121 if (!setupBehaviour()) {
Yi Tseng82512da2017-08-16 19:46:36 -0700122 return;
123 }
Carmelo Cascone26600972018-09-10 00:23:20 -0700124
125 // TODO: fix GroupProgrammable API, passing the device ID is ambiguous
126 checkArgument(deviceId.equals(this.deviceId),
127 "passed deviceId must be the same assigned to this behavior");
128
Yi Tsengfe13f3e2018-08-19 03:09:54 +0800129 groupOps.operations().forEach(op -> {
130 // ONOS-7785 We need app cookie (action profile id) from the group
131 Group groupOnStore = groupStore.getGroup(deviceId, op.groupId());
132 GroupDescription groupDesc = new DefaultGroupDescription(deviceId,
133 op.groupType(),
134 op.buckets(),
135 groupOnStore.appCookie(),
136 op.groupId().id(),
137 groupOnStore.appId());
138 DefaultGroup groupToApply = new DefaultGroup(op.groupId(), groupDesc);
139 if (op.groupType().equals(GroupDescription.Type.ALL)) {
Carmelo Cascone26600972018-09-10 00:23:20 -0700140 processMcGroupOp(groupToApply, op.opType());
Yi Tsengfe13f3e2018-08-19 03:09:54 +0800141 } else {
142
Carmelo Cascone26600972018-09-10 00:23:20 -0700143 processGroupOp(groupToApply, op.opType());
Yi Tsengfe13f3e2018-08-19 03:09:54 +0800144 }
145 });
Yi Tseng82512da2017-08-16 19:46:36 -0700146 }
147
Yi Tseng82512da2017-08-16 19:46:36 -0700148 @Override
149 public Collection<Group> getGroups() {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200150 if (!setupBehaviour()) {
151 return Collections.emptyList();
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -0400152 }
Carmelo Cascone26600972018-09-10 00:23:20 -0700153 return new ImmutableList.Builder<Group>()
154 .addAll(getActionGroups())
155 .addAll(getMcGroups()).build();
Carmelo Casconee75b7942017-11-21 17:14:49 -0800156 }
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -0400157
Carmelo Cascone26600972018-09-10 00:23:20 -0700158 private Collection<Group> getActionGroups() {
159
160 if (driverBoolProperty(READ_ACTION_GROUPS_FROM_MIRROR,
161 DEFAULT_READ_ACTION_GROUPS_FROM_MIRROR)) {
162 return getActionGroupsFromMirror();
163 }
164
165 final Collection<PiActionGroup> piGroups = pipeconf.pipelineModel()
166 .actionProfiles()
167 .stream()
168 .map(PiActionProfileModel::id)
169 .flatMap(this::streamPiGroupsFromDevice)
170 .collect(Collectors.toList());
171
172 if (piGroups.isEmpty()) {
173 return Collections.emptyList();
174 }
175
176 final List<Group> result = Lists.newArrayList();
177 final List<PiActionGroup> inconsistentGroups = Lists.newArrayList();
178
179 for (PiActionGroup piGroupOnDevice : piGroups) {
180 final Group group = forgeGroupEntry(piGroupOnDevice);
181 if (group == null) {
182 // Entry is on device but unknown to translation service or
183 // device mirror. Inconsistent. Mark for removal.
184 inconsistentGroups.add(piGroupOnDevice);
185 } else {
186 result.add(group);
187 }
188 }
189 // Trigger clean up of inconsistent entries (is any).
190 // TODO: make this behaviour configurable, in some cases it's fine for
191 // the device to have groups that were not installed by us.
192 if (!inconsistentGroups.isEmpty()) {
193 SharedExecutors.getSingleThreadExecutor().execute(
194 () -> cleanUpInconsistentGroups(inconsistentGroups));
195 }
196 return result;
197 }
198
199 private Collection<Group> getActionGroupsFromMirror() {
200 return groupMirror.getAll(deviceId).stream()
201 .map(TimedEntry::entry)
202 .map(this::forgeGroupEntry)
203 .filter(Objects::nonNull)
204 .collect(Collectors.toList());
205 }
206
207 private void cleanUpInconsistentGroups(Collection<PiActionGroup> piGroups) {
208 log.warn("Found {} inconsistent groups on {}, removing them...",
209 piGroups.size(), deviceId);
210 piGroups.forEach(piGroup -> {
211 log.debug(piGroup.toString());
212 // Per-piGroup lock.
213 final PiActionGroupHandle handle = PiActionGroupHandle.of(deviceId, piGroup);
214 STRIPED_LOCKS.get(handle).lock();
215 try {
216 processPiGroup(handle, piGroup, piGroup, null,
217 GroupOperation.Type.DELETE);
218 } finally {
219 STRIPED_LOCKS.get(handle).unlock();
220 }
221 });
222 }
223
224 private Collection<Group> getMcGroups() {
225 // TODO: missing support for reading multicast groups is ready in PI/Stratum.
226 return getMcGroupsFromMirror();
227 }
228
229 private Collection<Group> getMcGroupsFromMirror() {
230 return mcGroupMirror.getAll(deviceId).stream()
231 .map(TimedEntry::entry)
232 .map(this::forgeMcGroupEntry)
233 .filter(Objects::nonNull)
234 .collect(Collectors.toList());
235 }
236
237 private void processGroupOp(Group pdGroup, GroupOperation.Type opType) {
Carmelo Casconee75b7942017-11-21 17:14:49 -0800238 final PiActionGroup piGroup;
239 try {
Carmelo Cascone58136812018-07-19 03:40:16 +0200240 piGroup = groupTranslator.translate(pdGroup, pipeconf);
Carmelo Casconee75b7942017-11-21 17:14:49 -0800241 } catch (PiTranslationException e) {
Carmelo Cascone58136812018-07-19 03:40:16 +0200242 log.warn("Unable to translate group, aborting {} operation: {} [{}]",
243 opType, e.getMessage(), pdGroup);
Carmelo Casconee75b7942017-11-21 17:14:49 -0800244 return;
245 }
Carmelo Casconee75b7942017-11-21 17:14:49 -0800246 final PiActionGroupHandle handle = PiActionGroupHandle.of(deviceId, piGroup);
Carmelo Casconee75b7942017-11-21 17:14:49 -0800247 final PiActionGroup groupOnDevice = groupMirror.get(handle) == null
Carmelo Cascone26600972018-09-10 00:23:20 -0700248 ? null : groupMirror.get(handle).entry();
249 // Per-piGroup lock.
Carmelo Cascone58136812018-07-19 03:40:16 +0200250 final Lock lock = STRIPED_LOCKS.get(handle);
Carmelo Casconee75b7942017-11-21 17:14:49 -0800251 lock.lock();
252 try {
Carmelo Casconee75b7942017-11-21 17:14:49 -0800253 processPiGroup(handle, piGroup,
Carmelo Cascone58136812018-07-19 03:40:16 +0200254 groupOnDevice, pdGroup, opType);
255 } finally {
256 lock.unlock();
257 }
258 }
259
Carmelo Cascone26600972018-09-10 00:23:20 -0700260 private void processMcGroupOp(Group pdGroup, GroupOperation.Type opType) {
Carmelo Cascone58136812018-07-19 03:40:16 +0200261 final PiMulticastGroupEntry mcGroup;
262 try {
263 mcGroup = mcGroupTranslator.translate(pdGroup, pipeconf);
264 } catch (PiTranslationException e) {
265 log.warn("Unable to translate multicast group, aborting {} operation: {} [{}]",
266 opType, e.getMessage(), pdGroup);
267 return;
268 }
269 final PiMulticastGroupEntryHandle handle = PiMulticastGroupEntryHandle.of(
270 deviceId, mcGroup);
271 final PiMulticastGroupEntry groupOnDevice = mcGroupMirror.get(handle) == null
272 ? null
273 : mcGroupMirror.get(handle).entry();
274 final Lock lock = STRIPED_LOCKS.get(handle);
275 lock.lock();
276 try {
277 processMcGroup(handle, mcGroup,
278 groupOnDevice, pdGroup, opType);
Carmelo Casconee75b7942017-11-21 17:14:49 -0800279 } finally {
280 lock.unlock();
Yi Tseng82512da2017-08-16 19:46:36 -0700281 }
282 }
283
Carmelo Casconee75b7942017-11-21 17:14:49 -0800284 private void processPiGroup(PiActionGroupHandle handle,
285 PiActionGroup groupToApply,
286 PiActionGroup groupOnDevice,
Yi Tseng8d355132018-04-13 01:40:48 +0800287 Group pdGroup, GroupOperation.Type operationType) {
288 if (operationType == GroupOperation.Type.ADD) {
Carmelo Casconee75b7942017-11-21 17:14:49 -0800289 if (groupOnDevice != null) {
Yi Tseng8d355132018-04-13 01:40:48 +0800290 log.warn("Unable to add group {} since group already on device {}",
291 groupToApply.id(), deviceId);
292 log.debug("To apply: {}", groupToApply);
293 log.debug("On device: {}", groupOnDevice);
294 return;
Carmelo Casconee75b7942017-11-21 17:14:49 -0800295 }
Yi Tseng8d355132018-04-13 01:40:48 +0800296
Carmelo Casconee75b7942017-11-21 17:14:49 -0800297 if (writeGroupToDevice(groupToApply)) {
298 groupMirror.put(handle, groupToApply);
Carmelo Cascone58136812018-07-19 03:40:16 +0200299 groupTranslator.learn(handle, new PiTranslatedEntity<>(
Carmelo Casconee75b7942017-11-21 17:14:49 -0800300 pdGroup, groupToApply, handle));
301 }
Yi Tseng8d355132018-04-13 01:40:48 +0800302 } else if (operationType == GroupOperation.Type.MODIFY) {
303 if (groupOnDevice == null) {
304 log.warn("Group {} does not exists on device {}, can not modify it",
305 groupToApply.id(), deviceId);
306 return;
307 }
Yi Tsengf325a602018-06-27 18:26:33 +0800308 if (driverBoolProperty(CHECK_MIRROR_BEFORE_UPDATE, DEFAULT_CHECK_MIRROR_BEFORE_UPDATE)
Yi Tseng8d355132018-04-13 01:40:48 +0800309 && groupOnDevice.equals(groupToApply)) {
310 // Group on device has the same members, ignore operation.
311 return;
312 }
313 if (modifyGroupFromDevice(groupToApply, groupOnDevice)) {
314 groupMirror.put(handle, groupToApply);
Carmelo Cascone58136812018-07-19 03:40:16 +0200315 groupTranslator.learn(handle,
316 new PiTranslatedEntity<>(pdGroup, groupToApply, handle));
Yi Tseng8d355132018-04-13 01:40:48 +0800317 }
Carmelo Casconee75b7942017-11-21 17:14:49 -0800318 } else {
Yi Tseng8d355132018-04-13 01:40:48 +0800319 if (groupOnDevice == null) {
320 log.warn("Unable to remove group {} from device {} since it does" +
321 "not exists on device.", groupToApply.id(), deviceId);
322 return;
323 }
324 if (deleteGroupFromDevice(groupOnDevice)) {
Carmelo Casconee75b7942017-11-21 17:14:49 -0800325 groupMirror.remove(handle);
Carmelo Cascone58136812018-07-19 03:40:16 +0200326 groupTranslator.forget(handle);
Carmelo Casconee75b7942017-11-21 17:14:49 -0800327 }
328 }
329 }
330
Carmelo Cascone58136812018-07-19 03:40:16 +0200331 private void processMcGroup(PiMulticastGroupEntryHandle handle,
332 PiMulticastGroupEntry groupToApply,
333 PiMulticastGroupEntry groupOnDevice,
334 Group pdGroup, GroupOperation.Type opType) {
Carmelo Cascone055e9b22018-09-10 01:59:15 -0700335 switch (opType) {
336 case ADD:
337 robustMcGroupAdd(handle, groupToApply, pdGroup);
338 return;
339 case MODIFY:
340 // Since reading multicast groups is not supported yet on
341 // PI/Stratum, we cannot trust groupOnDevic) as we don't have a
342 // mechanism to enforce consistency of the mirror with the
343 // device state.
344 // if (driverBoolProperty(CHECK_MIRROR_BEFORE_UPDATE,
345 // DEFAULT_CHECK_MIRROR_BEFORE_UPDATE)
346 // && p4OpType == MODIFY
347 // && groupOnDevice != null
348 // && groupOnDevice.equals(groupToApply)) {
349 // // Ignore.
350 // return;
351 // }
352 robustMcGroupModify(handle, groupToApply, pdGroup);
353 return;
354 case DELETE:
355 mcGroupApply(handle, groupToApply, pdGroup, DELETE);
356 return;
357 default:
358 log.error("Unknown group operation type {}, " +
359 "cannot process multicast group", opType);
Carmelo Cascone58136812018-07-19 03:40:16 +0200360 }
361 }
362
363 private boolean writeMcGroupOnDevice(PiMulticastGroupEntry group, P4RuntimeClient.WriteOperationType opType) {
364 return getFutureWithDeadline(
365 client.writePreMulticastGroupEntries(
366 Collections.singleton(group), opType),
367 "performing multicast group " + opType, false);
368 }
369
Carmelo Cascone055e9b22018-09-10 01:59:15 -0700370 private boolean mcGroupApply(PiMulticastGroupEntryHandle handle,
371 PiMulticastGroupEntry piGroup,
372 Group pdGroup,
373 P4RuntimeClient.WriteOperationType opType) {
374 switch (opType) {
375 case DELETE:
376 if (writeMcGroupOnDevice(piGroup, DELETE)) {
377 mcGroupMirror.remove(handle);
378 mcGroupTranslator.forget(handle);
379 return true;
380 } else {
381 return false;
382 }
383 case INSERT:
384 case MODIFY:
385 if (writeMcGroupOnDevice(piGroup, opType)) {
386 mcGroupMirror.put(handle, piGroup);
387 mcGroupTranslator.learn(handle, new PiTranslatedEntity<>(
388 pdGroup, piGroup, handle));
389 return true;
390 } else {
391 return false;
392 }
393 default:
394 log.warn("Unknown operation type {}, cannot apply group", opType);
395 return false;
396 }
397 }
398
399 private void robustMcGroupAdd(PiMulticastGroupEntryHandle handle,
400 PiMulticastGroupEntry piGroup,
401 Group pdGroup) {
402 if (mcGroupApply(handle, piGroup, pdGroup, INSERT)) {
403 return;
404 }
405 // Try to delete (perhaps it already exists) and re-add...
406 mcGroupApply(handle, piGroup, pdGroup, DELETE);
407 mcGroupApply(handle, piGroup, pdGroup, INSERT);
408 }
409
410 private void robustMcGroupModify(PiMulticastGroupEntryHandle handle,
411 PiMulticastGroupEntry piGroup,
412 Group pdGroup) {
413 if (mcGroupApply(handle, piGroup, pdGroup, MODIFY)) {
414 return;
415 }
416 // Not sure for which reason it cannot be modified, so try to delete and insert instead...
417 mcGroupApply(handle, piGroup, pdGroup, DELETE);
418 mcGroupApply(handle, piGroup, pdGroup, INSERT);
419 }
420
Yi Tseng8d355132018-04-13 01:40:48 +0800421 private boolean modifyGroupFromDevice(PiActionGroup groupToApply, PiActionGroup groupOnDevice) {
422 PiActionProfileId groupProfileId = groupToApply.actionProfileId();
423 Collection<PiActionGroupMember> membersToRemove = Sets.newHashSet(groupOnDevice.members());
424 membersToRemove.removeAll(groupToApply.members());
425 Collection<PiActionGroupMember> membersToAdd = Sets.newHashSet(groupToApply.members());
426 membersToAdd.removeAll(groupOnDevice.members());
427
428 if (!membersToAdd.isEmpty() &&
429 !completeFuture(client.writeActionGroupMembers(groupProfileId, membersToAdd, INSERT, pipeconf),
430 ACT_GRP_MEMS_STR, INSERT_STR)) {
431 // remove what we added
432 completeFuture(client.writeActionGroupMembers(groupProfileId, membersToAdd, DELETE, pipeconf),
433 ACT_GRP_MEMS_STR, INSERT_STR);
434 return false;
435 }
436
437 if (!completeFuture(client.writeActionGroup(groupToApply, MODIFY, pipeconf),
438 ACT_GRP_STR, MODIFY_STR)) {
439 // recover group information
440 completeFuture(client.writeActionGroup(groupOnDevice, MODIFY, pipeconf),
441 ACT_GRP_STR, MODIFY_STR);
442 // remove what we added
443 completeFuture(client.writeActionGroupMembers(groupProfileId, membersToAdd, DELETE, pipeconf),
444 ACT_GRP_MEMS_STR, INSERT_STR);
445 return false;
446 }
447
448 if (!membersToRemove.isEmpty() &&
449 !completeFuture(client.writeActionGroupMembers(groupProfileId, membersToRemove, DELETE, pipeconf),
Carmelo Casconee5b28722018-06-22 17:28:28 +0200450 ACT_GRP_MEMS_STR, DELETE_STR)) {
Yi Tseng8d355132018-04-13 01:40:48 +0800451 // add what we removed
452 completeFuture(client.writeActionGroupMembers(groupProfileId, membersToRemove, INSERT, pipeconf),
453 ACT_GRP_MEMS_STR, DELETE_STR);
454 // recover group information
455 completeFuture(client.writeActionGroup(groupOnDevice, MODIFY, pipeconf),
456 ACT_GRP_STR, MODIFY_STR);
457 // remove what we added
458 completeFuture(client.writeActionGroupMembers(groupProfileId, membersToAdd, DELETE, pipeconf),
459 ACT_GRP_MEMS_STR, INSERT_STR);
460 return false;
461 }
462
463 return true;
464 }
465
Carmelo Casconee75b7942017-11-21 17:14:49 -0800466 private boolean writeGroupToDevice(PiActionGroup groupToApply) {
467 // First insert members, then group.
468 // The operation is deemed successful if both operations are successful.
469 // FIXME: add transactional semantics, i.e. remove members if group fails.
470 final boolean membersSuccess = completeFuture(
Yi Tseng8d355132018-04-13 01:40:48 +0800471 client.writeActionGroupMembers(groupToApply.actionProfileId(),
472 groupToApply.members(),
473 INSERT, pipeconf),
Carmelo Casconee75b7942017-11-21 17:14:49 -0800474 ACT_GRP_MEMS_STR, INSERT_STR);
475 return membersSuccess && completeFuture(
476 client.writeActionGroup(groupToApply, INSERT, pipeconf),
477 ACT_GRP_STR, INSERT_STR);
478 }
479
480 private boolean deleteGroupFromDevice(PiActionGroup piActionGroup) {
481 // First delete group, then members.
482 // The operation is deemed successful if both operations are successful.
483 final boolean groupSuccess = completeFuture(
484 client.writeActionGroup(piActionGroup, DELETE, pipeconf),
485 ACT_GRP_STR, DELETE_STR);
486 return groupSuccess && completeFuture(
Yi Tseng8d355132018-04-13 01:40:48 +0800487 client.writeActionGroupMembers(piActionGroup.actionProfileId(),
488 piActionGroup.members(),
489 DELETE, pipeconf),
Carmelo Casconee75b7942017-11-21 17:14:49 -0800490 ACT_GRP_MEMS_STR, DELETE_STR);
491 }
492
493 private boolean completeFuture(CompletableFuture<Boolean> completableFuture,
494 String topic, String action) {
Carmelo Casconee5b28722018-06-22 17:28:28 +0200495 return getFutureWithDeadline(
496 completableFuture, format("performing %s %s", action, topic), false);
Carmelo Casconee75b7942017-11-21 17:14:49 -0800497 }
498
Carmelo Cascone26600972018-09-10 00:23:20 -0700499 private Stream<PiActionGroup> streamPiGroupsFromDevice(PiActionProfileId actProfId) {
Carmelo Casconee5b28722018-06-22 17:28:28 +0200500 // Read PI groups and return original PD one.
Carmelo Cascone26600972018-09-10 00:23:20 -0700501 // TODO: implement P4Runtime client call to read all groups with one call
502 // Good is pipeline has multiple action profiles.
503 final Collection<PiActionGroup> groups = getFutureWithDeadline(
Carmelo Casconee5b28722018-06-22 17:28:28 +0200504 client.dumpGroups(actProfId, pipeconf),
505 "dumping groups", Collections.emptyList());
Carmelo Cascone26600972018-09-10 00:23:20 -0700506 return groups.stream();
Carmelo Cascone58136812018-07-19 03:40:16 +0200507 }
508
Carmelo Casconee75b7942017-11-21 17:14:49 -0800509 private Group forgeGroupEntry(PiActionGroup piGroup) {
510 final PiActionGroupHandle handle = PiActionGroupHandle.of(deviceId, piGroup);
Carmelo Cascone26600972018-09-10 00:23:20 -0700511 final Optional<PiTranslatedEntity<Group, PiActionGroup>>
512 translatedEntity = groupTranslator.lookup(handle);
513 final TimedEntry<PiActionGroup> timedEntry = groupMirror.get(handle);
514 // Is entry consistent with our state?
515 if (!translatedEntity.isPresent()) {
516 log.warn("Group handle not found in translation store: {}", handle);
Carmelo Casconee75b7942017-11-21 17:14:49 -0800517 return null;
518 }
Carmelo Cascone26600972018-09-10 00:23:20 -0700519 if (timedEntry == null) {
520 // Don't bother logging more than debug, most probably it's the EC
521 // map backing the store that has not received all the updates yet.
522 log.debug("Group handle not found in device mirror: {}", handle);
523 return null;
524 }
525 return addedGroup(translatedEntity.get().original(), timedEntry.lifeSec());
Carmelo Cascone58136812018-07-19 03:40:16 +0200526 }
527
528 private Group forgeMcGroupEntry(PiMulticastGroupEntry mcGroup) {
529 final PiMulticastGroupEntryHandle handle = PiMulticastGroupEntryHandle.of(
530 deviceId, mcGroup);
Carmelo Cascone26600972018-09-10 00:23:20 -0700531 final Optional<PiTranslatedEntity<Group, PiMulticastGroupEntry>>
532 translatedEntity = mcGroupTranslator.lookup(handle);
533 final TimedEntry<PiMulticastGroupEntry> timedEntry = mcGroupMirror.get(handle);
534 // Is entry consistent with our state?
535 if (!translatedEntity.isPresent()) {
536 log.warn("Multicast group handle not found in translation store: {}", handle);
Carmelo Cascone58136812018-07-19 03:40:16 +0200537 return null;
538 }
Carmelo Cascone26600972018-09-10 00:23:20 -0700539 if (timedEntry == null) {
540 log.warn("Multicast group handle not found in device mirror: {}", handle);
541 return null;
542 }
543 return addedGroup(translatedEntity.get().original(), timedEntry.lifeSec());
Carmelo Cascone58136812018-07-19 03:40:16 +0200544 }
545
546 private Group addedGroup(Group original, long life) {
Carmelo Casconee75b7942017-11-21 17:14:49 -0800547 final DefaultGroup forgedGroup = new DefaultGroup(original.id(), original);
548 forgedGroup.setState(Group.GroupState.ADDED);
549 forgedGroup.setLife(life);
550 return forgedGroup;
551 }
Carmelo Casconeb2e3dba2017-07-27 12:07:09 -0400552}