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