blob: 45be79eb526a1091f821af454afaecf3bdbf41af [file] [log] [blame]
Carmelo Casconee44592f2018-09-12 02:24:47 -07001/*
2 * Copyright 2017-present Open Networking Foundation
3 *
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
17package org.onosproject.drivers.p4runtime;
18
19import com.google.common.collect.ArrayListMultimap;
20import com.google.common.collect.ListMultimap;
21import com.google.common.collect.Lists;
22import com.google.common.collect.Maps;
23import com.google.common.collect.Sets;
24import com.google.common.util.concurrent.Striped;
25import org.onlab.util.SharedExecutors;
26import org.onosproject.drivers.p4runtime.mirror.P4RuntimeActionProfileMemberMirror;
27import org.onosproject.drivers.p4runtime.mirror.P4RuntimeGroupMirror;
28import org.onosproject.drivers.p4runtime.mirror.TimedEntry;
ghj0504520ed7340c2018-10-26 13:06:35 -070029import org.onosproject.net.DefaultAnnotations;
Carmelo Casconee44592f2018-09-12 02:24:47 -070030import org.onosproject.net.DeviceId;
31import org.onosproject.net.group.DefaultGroup;
32import org.onosproject.net.group.DefaultGroupDescription;
33import org.onosproject.net.group.Group;
34import org.onosproject.net.group.GroupDescription;
35import org.onosproject.net.group.GroupOperation;
36import org.onosproject.net.group.GroupOperations;
37import org.onosproject.net.group.GroupProgrammable;
38import org.onosproject.net.group.GroupStore;
39import org.onosproject.net.pi.model.PiActionId;
40import org.onosproject.net.pi.model.PiActionProfileId;
41import org.onosproject.net.pi.model.PiActionProfileModel;
42import org.onosproject.net.pi.runtime.PiAction;
43import org.onosproject.net.pi.runtime.PiActionGroup;
44import org.onosproject.net.pi.runtime.PiActionGroupHandle;
45import org.onosproject.net.pi.runtime.PiActionGroupMember;
46import org.onosproject.net.pi.runtime.PiActionGroupMemberHandle;
47import org.onosproject.net.pi.runtime.PiActionGroupMemberId;
48import org.onosproject.net.pi.service.PiGroupTranslator;
49import org.onosproject.net.pi.service.PiTranslatedEntity;
50import org.onosproject.net.pi.service.PiTranslationException;
51import org.onosproject.p4runtime.api.P4RuntimeClient;
52
53import java.util.Collection;
54import java.util.Collections;
55import java.util.List;
56import java.util.Map;
57import java.util.Objects;
58import java.util.Optional;
59import java.util.Set;
60import java.util.concurrent.locks.Lock;
61import java.util.stream.Collectors;
62import java.util.stream.Stream;
63
64import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.DELETE;
65import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.INSERT;
66import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.MODIFY;
67
68/**
69 * Implementation of GroupProgrammable to handle action profile groups in
70 * P4Runtime.
71 */
72public class P4RuntimeActionGroupProgrammable
73 extends AbstractP4RuntimeHandlerBehaviour
74 implements GroupProgrammable {
75
76 // If true, we avoid querying the device and return what's already known by
77 // the ONOS store.
78 private static final String READ_ACTION_GROUPS_FROM_MIRROR = "actionGroupReadFromMirror";
79 private static final boolean DEFAULT_READ_ACTION_GROUPS_FROM_MIRROR = false;
ghj0504520ed7340c2018-10-26 13:06:35 -070080 private static final String MAX_MEM_SIZE = "maxMemSize";
Carmelo Casconee44592f2018-09-12 02:24:47 -070081
82 protected GroupStore groupStore;
83 private P4RuntimeGroupMirror groupMirror;
84 private P4RuntimeActionProfileMemberMirror memberMirror;
85 private PiGroupTranslator groupTranslator;
86
87 // Needed to synchronize operations over the same group.
88 private static final Striped<Lock> STRIPED_LOCKS = Striped.lock(30);
ghj0504520ed7340c2018-10-26 13:06:35 -070089 private static final int GROUP_MEMBERS_BUFFER_SIZE = 3;
Carmelo Casconee44592f2018-09-12 02:24:47 -070090
91 @Override
92 protected boolean setupBehaviour() {
93 if (!super.setupBehaviour()) {
94 return false;
95 }
96 groupMirror = this.handler().get(P4RuntimeGroupMirror.class);
97 memberMirror = this.handler().get(P4RuntimeActionProfileMemberMirror.class);
98 groupStore = handler().get(GroupStore.class);
99 groupTranslator = piTranslationService.groupTranslator();
100 return true;
101 }
102
103 @Override
104 public void performGroupOperation(DeviceId deviceId,
105 GroupOperations groupOps) {
106 if (!setupBehaviour()) {
107 return;
108 }
109
110 groupOps.operations().stream()
111 .filter(op -> !op.groupType().equals(GroupDescription.Type.ALL))
112 .forEach(op -> {
113 // ONOS-7785 We need app cookie (action profile id) from the group
114 Group groupOnStore = groupStore.getGroup(deviceId, op.groupId());
115 GroupDescription groupDesc = new DefaultGroupDescription(
116 deviceId, op.groupType(), op.buckets(), groupOnStore.appCookie(),
117 op.groupId().id(), groupOnStore.appId());
118 DefaultGroup groupToApply = new DefaultGroup(op.groupId(), groupDesc);
119 processGroupOperation(groupToApply, op.opType());
120 });
121 }
122
123 @Override
124 public Collection<Group> getGroups() {
125 if (!setupBehaviour()) {
126 return Collections.emptyList();
127 }
128 return getActionGroups();
129 }
130
131 private Collection<Group> getActionGroups() {
132
133 if (driverBoolProperty(READ_ACTION_GROUPS_FROM_MIRROR,
134 DEFAULT_READ_ACTION_GROUPS_FROM_MIRROR)) {
135 return getActionGroupsFromMirror();
136 }
137
138 final Collection<PiActionProfileId> actionProfileIds = pipeconf.pipelineModel()
139 .actionProfiles()
140 .stream()
141 .map(PiActionProfileModel::id)
142 .collect(Collectors.toList());
143 final List<PiActionGroup> groupsOnDevice = actionProfileIds.stream()
144 .flatMap(this::streamGroupsFromDevice)
145 .collect(Collectors.toList());
146 final Set<PiActionGroupMemberHandle> membersOnDevice = actionProfileIds
147 .stream()
148 .flatMap(actProfId -> getMembersFromDevice(actProfId)
149 .stream()
150 .map(memberId -> PiActionGroupMemberHandle.of(
151 deviceId, actProfId, memberId)))
152 .collect(Collectors.toSet());
153
154 if (groupsOnDevice.isEmpty()) {
155 return Collections.emptyList();
156 }
157
158 // Sync mirrors.
159 syncGroupMirror(groupsOnDevice);
160 syncMemberMirror(membersOnDevice);
161
162 final List<Group> result = Lists.newArrayList();
163 final List<PiActionGroup> inconsistentGroups = Lists.newArrayList();
164 final List<PiActionGroup> validGroups = Lists.newArrayList();
165
166 for (PiActionGroup piGroup : groupsOnDevice) {
167 final Group pdGroup = forgeGroupEntry(piGroup);
168 if (pdGroup == null) {
169 // Entry is on device but unknown to translation service or
170 // device mirror. Inconsistent. Mark for removal.
171 inconsistentGroups.add(piGroup);
172 } else {
173 validGroups.add(piGroup);
174 result.add(pdGroup);
175 }
176 }
177
178 // Trigger clean up of inconsistent groups and members. This will also
179 // remove all members that are not used by any group, and update the
180 // mirror accordingly.
181 final Set<PiActionGroupMemberHandle> membersToKeep = validGroups.stream()
182 .flatMap(g -> g.members().stream())
183 .map(m -> PiActionGroupMemberHandle.of(deviceId, m))
184 .collect(Collectors.toSet());
185 final Set<PiActionGroupMemberHandle> inconsistentMembers = Sets.difference(
186 membersOnDevice, membersToKeep);
187 SharedExecutors.getSingleThreadExecutor().execute(
188 () -> cleanUpInconsistentGroupsAndMembers(
189 inconsistentGroups, inconsistentMembers));
190
191 return result;
192 }
193
194 private void syncGroupMirror(Collection<PiActionGroup> groups) {
195 Map<PiActionGroupHandle, PiActionGroup> handleMap = Maps.newHashMap();
196 groups.forEach(g -> handleMap.put(PiActionGroupHandle.of(deviceId, g), g));
197 groupMirror.sync(deviceId, handleMap);
198 }
199
200 private void syncMemberMirror(Collection<PiActionGroupMemberHandle> memberHandles) {
201 Map<PiActionGroupMemberHandle, PiActionGroupMember> handleMap = Maps.newHashMap();
202 memberHandles.forEach(handle -> handleMap.put(
203 handle, dummyMember(handle.actionProfileId(), handle.memberId())));
204 memberMirror.sync(deviceId, handleMap);
205 }
206
207 private Collection<Group> getActionGroupsFromMirror() {
208 return groupMirror.getAll(deviceId).stream()
209 .map(TimedEntry::entry)
210 .map(this::forgeGroupEntry)
211 .filter(Objects::nonNull)
212 .collect(Collectors.toList());
213 }
214
215 private void cleanUpInconsistentGroupsAndMembers(Collection<PiActionGroup> groupsToRemove,
216 Collection<PiActionGroupMemberHandle> membersToRemove) {
217 if (!groupsToRemove.isEmpty()) {
218 log.warn("Found {} inconsistent action profile groups on {}, removing them...",
219 groupsToRemove.size(), deviceId);
220 groupsToRemove.forEach(piGroup -> {
221 log.debug(piGroup.toString());
222 processGroup(piGroup, null, Operation.REMOVE);
223 });
224 }
225 if (!membersToRemove.isEmpty()) {
226 log.warn("Found {} inconsistent action profile members on {}, removing them...",
227 membersToRemove.size(), deviceId);
228 // FIXME: implement client call to remove members from multiple
229 // action profiles in one shot.
230 final ListMultimap<PiActionProfileId, PiActionGroupMemberId>
231 membersByActProfId = ArrayListMultimap.create();
232 membersToRemove.forEach(m -> membersByActProfId.put(
233 m.actionProfileId(), m.memberId()));
234 membersByActProfId.keySet().forEach(actProfId -> {
235 List<PiActionGroupMemberId> removedMembers = getFutureWithDeadline(
236 client.removeActionProfileMembers(
237 actProfId, membersByActProfId.get(actProfId), pipeconf),
238 "cleaning up action profile members", Collections.emptyList());
239 // Update member mirror.
240 removedMembers.stream()
241 .map(id -> PiActionGroupMemberHandle.of(deviceId, actProfId, id))
242 .forEach(memberMirror::remove);
243 });
244 }
245 }
246
247 private Stream<PiActionGroup> streamGroupsFromDevice(PiActionProfileId actProfId) {
248 // TODO: implement P4Runtime client call to read all groups with one call
249 // Good if pipeline has multiple action profiles.
250 final Collection<PiActionGroup> groups = getFutureWithDeadline(
251 client.dumpGroups(actProfId, pipeconf),
252 "dumping groups", Collections.emptyList());
253 return groups.stream();
254 }
255
256 private List<PiActionGroupMemberId> getMembersFromDevice(PiActionProfileId actProfId) {
257 // TODO: implement P4Runtime client call to read all members with one call
258 // Good if pipeline has multiple action profiles.
259 return getFutureWithDeadline(
260 client.dumpActionProfileMemberIds(actProfId, pipeconf),
261 "dumping action profile ids", Collections.emptyList());
262 }
263
264 private Group forgeGroupEntry(PiActionGroup piGroup) {
265 final PiActionGroupHandle handle = PiActionGroupHandle.of(deviceId, piGroup);
266 final Optional<PiTranslatedEntity<Group, PiActionGroup>>
267 translatedEntity = groupTranslator.lookup(handle);
268 final TimedEntry<PiActionGroup> timedEntry = groupMirror.get(handle);
269 // Is entry consistent with our state?
270 if (!translatedEntity.isPresent()) {
271 log.warn("Group handle not found in translation store: {}", handle);
272 return null;
273 }
274 if (!translatedEntity.get().translated().equals(piGroup)) {
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800275 log.warn("Group obtained from device {} is different from the one in " +
Carmelo Casconee44592f2018-09-12 02:24:47 -0700276 "translation store: device={}, store={}",
277 deviceId, piGroup, translatedEntity.get().translated());
278 return null;
279 }
280 if (timedEntry == null) {
281 log.warn("Group handle not found in device mirror: {}", handle);
282 return null;
283 }
284 return addedGroup(translatedEntity.get().original(), timedEntry.lifeSec());
285 }
286
287 private Group addedGroup(Group original, long life) {
288 final DefaultGroup forgedGroup = new DefaultGroup(original.id(), original);
289 forgedGroup.setState(Group.GroupState.ADDED);
290 forgedGroup.setLife(life);
291 return forgedGroup;
292 }
293
294 private void processGroupOperation(Group pdGroup, GroupOperation.Type opType) {
295 final PiActionGroup piGroup;
296 try {
297 piGroup = groupTranslator.translate(pdGroup, pipeconf);
298 } catch (PiTranslationException e) {
299 log.warn("Unable to translate group, aborting {} operation: {} [{}]",
300 opType, e.getMessage(), pdGroup);
301 return;
302 }
303 final Operation operation = opType.equals(GroupOperation.Type.DELETE)
304 ? Operation.REMOVE : Operation.APPLY;
305 processGroup(piGroup, pdGroup, operation);
306 }
307
308 private void processGroup(PiActionGroup groupToApply,
309 Group pdGroup,
310 Operation operation) {
311 final PiActionGroupHandle handle = PiActionGroupHandle.of(deviceId, groupToApply);
312 STRIPED_LOCKS.get(handle).lock();
313 try {
314 switch (operation) {
315 case APPLY:
316 if (applyGroupWithMembersOrNothing(groupToApply, handle)) {
317 groupTranslator.learn(handle, new PiTranslatedEntity<>(
318 pdGroup, groupToApply, handle));
319 }
320 return;
321 case REMOVE:
322 if (deleteGroup(groupToApply, handle)) {
323 groupTranslator.forget(handle);
324 }
325 return;
326 default:
327 log.error("Unknwon group operation type {}, cannot process group", operation);
328 break;
329 }
330 } finally {
331 STRIPED_LOCKS.get(handle).unlock();
332 }
333 }
334
335 private boolean applyGroupWithMembersOrNothing(PiActionGroup group, PiActionGroupHandle handle) {
336 // First apply members, then group, if fails, delete members.
337 if (!applyAllMembersOrNothing(group.members())) {
338 return false;
339 }
340 if (!applyGroup(group, handle)) {
341 deleteMembers(group.members());
342 return false;
343 }
344 return true;
345 }
346
347 private boolean applyGroup(PiActionGroup group, PiActionGroupHandle handle) {
ghj0504520ed7340c2018-10-26 13:06:35 -0700348 final int currentMemberSize = group.members().size();
349 if (groupMirror.get(handle) != null) {
350 String maxMemSize = "";
351 if (groupMirror.annotations(handle) != null &&
352 groupMirror.annotations(handle).value(MAX_MEM_SIZE) != null) {
353 maxMemSize = groupMirror.annotations(handle).value(MAX_MEM_SIZE);
354 }
Ray Milkeyfe6afd82018-11-26 14:03:20 -0800355 if (!maxMemSize.equals("") || currentMemberSize > Integer.parseInt(maxMemSize)) {
ghj0504520ed7340c2018-10-26 13:06:35 -0700356 deleteGroup(group, handle);
357 }
358 }
359
360 P4RuntimeClient.WriteOperationType opType =
Carmelo Casconee44592f2018-09-12 02:24:47 -0700361 groupMirror.get(handle) == null ? INSERT : MODIFY;
ghj0504520ed7340c2018-10-26 13:06:35 -0700362 int currentMaxMemberSize = opType == INSERT ? (currentMemberSize + GROUP_MEMBERS_BUFFER_SIZE) : 0;
363
Carmelo Casconee44592f2018-09-12 02:24:47 -0700364 final boolean success = getFutureWithDeadline(
ghj0504520ed7340c2018-10-26 13:06:35 -0700365 client.writeActionGroup(group, opType, pipeconf, currentMaxMemberSize),
Carmelo Casconee44592f2018-09-12 02:24:47 -0700366 "performing action profile group " + opType, false);
367 if (success) {
368 groupMirror.put(handle, group);
ghj0504520ed7340c2018-10-26 13:06:35 -0700369 if (opType == INSERT) {
370 groupMirror.putAnnotations(handle, DefaultAnnotations
371 .builder()
372 .set(MAX_MEM_SIZE, Integer.toString(currentMaxMemberSize))
373 .build());
374 }
Carmelo Casconee44592f2018-09-12 02:24:47 -0700375 }
376 return success;
377 }
378
379 private boolean deleteGroup(PiActionGroup group, PiActionGroupHandle handle) {
380 final boolean success = getFutureWithDeadline(
ghj0504520ed7340c2018-10-26 13:06:35 -0700381 client.writeActionGroup(group, DELETE, pipeconf, 0),
Carmelo Casconee44592f2018-09-12 02:24:47 -0700382 "performing action profile group " + DELETE, false);
383 if (success) {
384 groupMirror.remove(handle);
385 }
386 return success;
387 }
388
389 private boolean applyAllMembersOrNothing(Collection<PiActionGroupMember> members) {
390 Collection<PiActionGroupMember> appliedMembers = applyMembers(members);
391 if (appliedMembers.size() == members.size()) {
392 return true;
393 } else {
394 deleteMembers(appliedMembers);
395 return false;
396 }
397 }
398
399 private Collection<PiActionGroupMember> applyMembers(
400 Collection<PiActionGroupMember> members) {
401 return members.stream()
402 .filter(this::applyMember)
403 .collect(Collectors.toList());
404 }
405
406 private boolean applyMember(PiActionGroupMember member) {
407 // If exists, modify, otherwise insert
408 final PiActionGroupMemberHandle handle = PiActionGroupMemberHandle.of(
409 deviceId, member);
410 final P4RuntimeClient.WriteOperationType opType =
411 memberMirror.get(handle) == null ? INSERT : MODIFY;
412 final boolean success = getFutureWithDeadline(
413 client.writeActionGroupMembers(Collections.singletonList(member),
414 opType, pipeconf),
415 "performing action profile member " + opType, false);
416 if (success) {
417 memberMirror.put(handle, dummyMember(member.actionProfile(), member.id()));
418 }
419 return success;
420 }
421
422 private void deleteMembers(Collection<PiActionGroupMember> members) {
423 members.forEach(this::deleteMember);
424 }
425
426 private void deleteMember(PiActionGroupMember member) {
427 final PiActionGroupMemberHandle handle = PiActionGroupMemberHandle.of(
428 deviceId, member);
429 final boolean success = getFutureWithDeadline(
430 client.writeActionGroupMembers(Collections.singletonList(member),
431 DELETE, pipeconf),
432 "performing action profile member " + DELETE, false);
433 if (success) {
434 memberMirror.remove(handle);
435 }
436 }
437
438 // FIXME: this is nasty, we have to rely on a dummy member of the mirror
439 // because the PiActionGroupMember abstraction is broken, since it includes
440 // attributes that are not part of a P4Runtime member, e.g. weight.
441 // We should remove weight from the class, and have client methods that
442 // return the full PiActionGroupMember, not just the IDs. Also the naming
443 // "ActionGroupMember" is wrong since it makes believe that members can
444 // exists only inside a group, which is not true.
445 private PiActionGroupMember dummyMember(
446 PiActionProfileId actionProfileId, PiActionGroupMemberId memberId) {
447 return PiActionGroupMember.builder()
448 .forActionProfile(actionProfileId)
449 .withId(memberId)
450 .withAction(PiAction.builder()
451 .withId(PiActionId.of("dummy"))
452 .build())
453 .build();
454 }
455
456 enum Operation {
457 APPLY, REMOVE
458 }
459}