blob: 01af5eb8c9558e5a62fa9c6abb786a1b8d05de2d [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
Carmelo Casconee44592f2018-09-12 02:24:47 -070019import com.google.common.collect.Lists;
Carmelo Casconee44592f2018-09-12 02:24:47 -070020import com.google.common.collect.Sets;
Carmelo Cascone61469462019-03-05 23:59:11 -080021import com.google.common.util.concurrent.Futures;
22import com.google.common.util.concurrent.Striped;
Carmelo Casconecb4327a2018-09-11 15:17:23 -070023import org.onosproject.drivers.p4runtime.mirror.P4RuntimeActionProfileGroupMirror;
Carmelo Casconee44592f2018-09-12 02:24:47 -070024import org.onosproject.drivers.p4runtime.mirror.P4RuntimeActionProfileMemberMirror;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080025import org.onosproject.drivers.p4runtime.mirror.P4RuntimeMirror;
Carmelo Casconee44592f2018-09-12 02:24:47 -070026import org.onosproject.drivers.p4runtime.mirror.TimedEntry;
27import org.onosproject.net.DeviceId;
28import org.onosproject.net.group.DefaultGroup;
29import org.onosproject.net.group.DefaultGroupDescription;
30import org.onosproject.net.group.Group;
31import org.onosproject.net.group.GroupDescription;
32import org.onosproject.net.group.GroupOperation;
33import org.onosproject.net.group.GroupOperations;
34import org.onosproject.net.group.GroupProgrammable;
35import org.onosproject.net.group.GroupStore;
Carmelo Casconee44592f2018-09-12 02:24:47 -070036import org.onosproject.net.pi.model.PiActionProfileModel;
Carmelo Casconecb4327a2018-09-11 15:17:23 -070037import org.onosproject.net.pi.runtime.PiActionProfileGroup;
38import org.onosproject.net.pi.runtime.PiActionProfileGroupHandle;
39import org.onosproject.net.pi.runtime.PiActionProfileMember;
40import org.onosproject.net.pi.runtime.PiActionProfileMemberHandle;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080041import org.onosproject.net.pi.runtime.PiEntity;
42import org.onosproject.net.pi.runtime.PiHandle;
Carmelo Casconee44592f2018-09-12 02:24:47 -070043import org.onosproject.net.pi.service.PiGroupTranslator;
44import org.onosproject.net.pi.service.PiTranslatedEntity;
45import org.onosproject.net.pi.service.PiTranslationException;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080046import org.onosproject.p4runtime.api.P4RuntimeReadClient;
Carmelo Cascone61469462019-03-05 23:59:11 -080047import org.onosproject.p4runtime.api.P4RuntimeWriteClient.WriteRequest;
48import org.onosproject.p4runtime.api.P4RuntimeWriteClient.WriteResponse;
Carmelo Casconee44592f2018-09-12 02:24:47 -070049
50import java.util.Collection;
51import java.util.Collections;
52import java.util.List;
53import java.util.Map;
54import java.util.Objects;
55import java.util.Optional;
56import java.util.Set;
Carmelo Cascone61469462019-03-05 23:59:11 -080057import java.util.concurrent.CompletableFuture;
58import java.util.concurrent.locks.Lock;
Carmelo Casconee44592f2018-09-12 02:24:47 -070059import java.util.stream.Collectors;
Carmelo Casconee44592f2018-09-12 02:24:47 -070060
Carmelo Cascone99c59db2019-01-17 15:39:35 -080061import static java.util.stream.Collectors.toMap;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080062import static java.util.stream.Collectors.toSet;
Carmelo Casconee44592f2018-09-12 02:24:47 -070063
64/**
65 * Implementation of GroupProgrammable to handle action profile groups in
66 * P4Runtime.
67 */
68public class P4RuntimeActionGroupProgrammable
69 extends AbstractP4RuntimeHandlerBehaviour
70 implements GroupProgrammable {
71
72 // If true, we avoid querying the device and return what's already known by
73 // the ONOS store.
74 private static final String READ_ACTION_GROUPS_FROM_MIRROR = "actionGroupReadFromMirror";
75 private static final boolean DEFAULT_READ_ACTION_GROUPS_FROM_MIRROR = false;
76
Carmelo Cascone61469462019-03-05 23:59:11 -080077 // Used to make sure concurrent calls to write groups are serialized so
78 // that each request gets consistent access to mirror state.
79 private static final Striped<Lock> WRITE_LOCKS = Striped.lock(30);
80
Carmelo Casconee44592f2018-09-12 02:24:47 -070081 protected GroupStore groupStore;
Carmelo Casconecb4327a2018-09-11 15:17:23 -070082 private P4RuntimeActionProfileGroupMirror groupMirror;
Carmelo Casconee44592f2018-09-12 02:24:47 -070083 private P4RuntimeActionProfileMemberMirror memberMirror;
84 private PiGroupTranslator groupTranslator;
85
Carmelo Casconee44592f2018-09-12 02:24:47 -070086 @Override
87 protected boolean setupBehaviour() {
88 if (!super.setupBehaviour()) {
89 return false;
90 }
Carmelo Casconecb4327a2018-09-11 15:17:23 -070091 groupMirror = this.handler().get(P4RuntimeActionProfileGroupMirror.class);
Carmelo Casconee44592f2018-09-12 02:24:47 -070092 memberMirror = this.handler().get(P4RuntimeActionProfileMemberMirror.class);
93 groupStore = handler().get(GroupStore.class);
Yi Tsengd7716482018-10-31 15:34:30 -070094 groupTranslator = translationService.groupTranslator();
Carmelo Casconee44592f2018-09-12 02:24:47 -070095 return true;
96 }
97
98 @Override
99 public void performGroupOperation(DeviceId deviceId,
100 GroupOperations groupOps) {
101 if (!setupBehaviour()) {
102 return;
103 }
104
105 groupOps.operations().stream()
106 .filter(op -> !op.groupType().equals(GroupDescription.Type.ALL))
107 .forEach(op -> {
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800108 // ONOS-7785 We need the group app cookie (which includes
109 // the action profile ID) but this is not part of the
110 // GroupDescription.
Carmelo Casconee44592f2018-09-12 02:24:47 -0700111 Group groupOnStore = groupStore.getGroup(deviceId, op.groupId());
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800112 if (groupOnStore == null) {
113 log.warn("Unable to find group {} in store, aborting {} operation",
114 op.groupId(), op.opType());
115 return;
116 }
Carmelo Casconee44592f2018-09-12 02:24:47 -0700117 GroupDescription groupDesc = new DefaultGroupDescription(
118 deviceId, op.groupType(), op.buckets(), groupOnStore.appCookie(),
119 op.groupId().id(), groupOnStore.appId());
120 DefaultGroup groupToApply = new DefaultGroup(op.groupId(), groupDesc);
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800121 processPdGroup(groupToApply, op.opType());
Carmelo Casconee44592f2018-09-12 02:24:47 -0700122 });
123 }
124
125 @Override
126 public Collection<Group> getGroups() {
127 if (!setupBehaviour()) {
128 return Collections.emptyList();
129 }
Carmelo Casconee44592f2018-09-12 02:24:47 -0700130
131 if (driverBoolProperty(READ_ACTION_GROUPS_FROM_MIRROR,
132 DEFAULT_READ_ACTION_GROUPS_FROM_MIRROR)) {
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800133 return getGroupsFromMirror();
Carmelo Casconee44592f2018-09-12 02:24:47 -0700134 }
135
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800136 // Dump groups and members from device for all action profiles.
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800137 final P4RuntimeReadClient.ReadRequest request = client.read(pipeconf);
138 pipeconf.pipelineModel().actionProfiles()
139 .stream().map(PiActionProfileModel::id)
140 .forEach(id -> request.actionProfileGroups(id)
141 .actionProfileMembers(id));
142 final P4RuntimeReadClient.ReadResponse response = request.submitSync();
143
144 if (!response.isSuccess()) {
145 // Error at client level.
146 return Collections.emptyList();
147 }
148
149 final Collection<PiActionProfileGroup> groupsOnDevice = response.all(
150 PiActionProfileGroup.class);
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800151 final Map<PiActionProfileMemberHandle, PiActionProfileMember> membersOnDevice =
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800152 response.all(PiActionProfileMember.class).stream()
153 .collect(toMap(m -> m.handle(deviceId), m -> m));
Carmelo Casconee44592f2018-09-12 02:24:47 -0700154
155 // Sync mirrors.
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800156 groupMirror.sync(deviceId, groupsOnDevice);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800157 memberMirror.sync(deviceId, membersOnDevice.values());
Carmelo Casconee44592f2018-09-12 02:24:47 -0700158
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800159 // Retrieve the original PD group before translation.
Carmelo Casconee44592f2018-09-12 02:24:47 -0700160 final List<Group> result = Lists.newArrayList();
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800161 final List<PiActionProfileGroup> groupsToRemove = Lists.newArrayList();
162 final Set<PiActionProfileMemberHandle> memberHandlesToKeep = Sets.newHashSet();
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800163 for (PiActionProfileGroup piGroup : groupsOnDevice) {
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800164 final Group pdGroup = checkAndForgeGroupEntry(piGroup, membersOnDevice);
Carmelo Casconee44592f2018-09-12 02:24:47 -0700165 if (pdGroup == null) {
166 // Entry is on device but unknown to translation service or
167 // device mirror. Inconsistent. Mark for removal.
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800168 groupsToRemove.add(piGroup);
Carmelo Casconee44592f2018-09-12 02:24:47 -0700169 } else {
Carmelo Casconee44592f2018-09-12 02:24:47 -0700170 result.add(pdGroup);
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800171 // Keep track of member handles used in groups.
172 piGroup.members().stream()
173 .map(m -> PiActionProfileMemberHandle.of(
174 deviceId, piGroup.actionProfile(), m.id()))
175 .forEach(memberHandlesToKeep::add);
Carmelo Casconee44592f2018-09-12 02:24:47 -0700176 }
177 }
178
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800179 // Trigger clean up of inconsistent groups and members (if any). This
180 // process takes care of removing any orphan member, e.g. from a
181 // partial/unsuccessful group insertion.
182 // This will update the mirror accordingly.
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800183 final Set<PiActionProfileMemberHandle> memberHandlesToRemove = Sets.difference(
184 membersOnDevice.keySet(), memberHandlesToKeep);
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800185 final Set<PiActionProfileGroupHandle> groupHandlesToRemove = groupsToRemove
186 .stream().map(g -> g.handle(deviceId)).collect(toSet());
187 if (groupHandlesToRemove.size() + memberHandlesToRemove.size() > 0) {
188 log.warn("Cleaning up {} action profile groups and " +
189 "{} members on {}...",
190 groupHandlesToRemove.size(), memberHandlesToRemove.size(), deviceId);
Carmelo Cascone61469462019-03-05 23:59:11 -0800191 final WriteRequest deleteRequest = client.write(pipeconf)
192 .delete(groupHandlesToRemove)
193 .delete(memberHandlesToRemove);
194 WRITE_LOCKS.get(deviceId).lock();
195 try {
196 updateMirrorAndSubmit(deleteRequest).whenComplete((r, ex) -> {
197 if (ex != null) {
198 log.error("Exception removing inconsistent group/members", ex);
199 } else {
200 log.debug("Completed removal of inconsistent " +
201 "groups/members ({} of {} updates succeeded)",
202 r.success(), r.all());
203 }
204 });
205 } finally {
206 WRITE_LOCKS.get(deviceId).unlock();
207 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800208 }
Carmelo Casconee44592f2018-09-12 02:24:47 -0700209
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800210 // Done.
Carmelo Casconee44592f2018-09-12 02:24:47 -0700211 return result;
212 }
213
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800214 private Collection<Group> getGroupsFromMirror() {
215 final Map<PiActionProfileMemberHandle, PiActionProfileMember> members =
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800216 memberMirror.getAll(deviceId).stream()
217 .map(TimedEntry::entry)
218 .collect(toMap(e -> e.handle(deviceId), e -> e));
Carmelo Casconee44592f2018-09-12 02:24:47 -0700219 return groupMirror.getAll(deviceId).stream()
220 .map(TimedEntry::entry)
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800221 .map(g -> checkAndForgeGroupEntry(
222 g, members))
Carmelo Casconee44592f2018-09-12 02:24:47 -0700223 .filter(Objects::nonNull)
224 .collect(Collectors.toList());
225 }
226
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800227 private Group checkAndForgeGroupEntry(
228 PiActionProfileGroup piGroupOnDevice,
229 Map<PiActionProfileMemberHandle, PiActionProfileMember> membersOnDevice) {
230 final PiActionProfileGroupHandle handle = PiActionProfileGroupHandle.of(
231 deviceId, piGroupOnDevice);
Carmelo Casconecb4327a2018-09-11 15:17:23 -0700232 final Optional<PiTranslatedEntity<Group, PiActionProfileGroup>>
Carmelo Casconee44592f2018-09-12 02:24:47 -0700233 translatedEntity = groupTranslator.lookup(handle);
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800234 final TimedEntry<PiActionProfileGroup> mirrorEntry = groupMirror.get(handle);
235 // Check that entry obtained from device is consistent with what is known
236 // by the translation store.
Carmelo Casconee44592f2018-09-12 02:24:47 -0700237 if (!translatedEntity.isPresent()) {
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800238 log.warn("Group not found in translation store: {}", handle);
Carmelo Casconee44592f2018-09-12 02:24:47 -0700239 return null;
240 }
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800241 final PiActionProfileGroup piGroupFromStore = translatedEntity.get().translated();
242 if (!piGroupFromStore.equals(piGroupOnDevice)) {
243 log.warn("Group on device {} is different from the one in " +
244 "translation store: {} [device={}, store={}]",
245 deviceId, handle, piGroupOnDevice, piGroupFromStore);
Carmelo Casconee44592f2018-09-12 02:24:47 -0700246 return null;
247 }
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800248 // Groups in P4Runtime contains only a reference to members. Check that
249 // the actual member instances in the translation store are the same
250 // found on the device.
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800251 if (!validateGroupMembers(piGroupFromStore, membersOnDevice)) {
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800252 log.warn("Group on device {} refers to members that are different " +
253 "than those found in translation store: {}", handle);
254 return null;
255 }
256 if (mirrorEntry == null) {
Carmelo Casconee44592f2018-09-12 02:24:47 -0700257 log.warn("Group handle not found in device mirror: {}", handle);
258 return null;
259 }
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800260 // Check that members from device are the same as in the translated group.
261 return addedGroup(translatedEntity.get().original(), mirrorEntry.lifeSec());
262 }
263
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800264 private boolean validateGroupMembers(
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800265 PiActionProfileGroup piGroupFromStore,
266 Map<PiActionProfileMemberHandle, PiActionProfileMember> membersOnDevice) {
267 final Collection<PiActionProfileMember> groupMembers =
268 extractAllMemberInstancesOrNull(piGroupFromStore);
269 if (groupMembers == null) {
270 return false;
271 }
272 return groupMembers.stream().allMatch(
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800273 memberFromStore -> memberFromStore.equals(membersOnDevice.get(
274 memberFromStore.handle(deviceId))));
Carmelo Casconee44592f2018-09-12 02:24:47 -0700275 }
276
277 private Group addedGroup(Group original, long life) {
278 final DefaultGroup forgedGroup = new DefaultGroup(original.id(), original);
279 forgedGroup.setState(Group.GroupState.ADDED);
280 forgedGroup.setLife(life);
281 return forgedGroup;
282 }
283
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800284 private void processPdGroup(Group pdGroup, GroupOperation.Type opType) {
Carmelo Casconecb4327a2018-09-11 15:17:23 -0700285 final PiActionProfileGroup piGroup;
Carmelo Casconee44592f2018-09-12 02:24:47 -0700286 try {
287 piGroup = groupTranslator.translate(pdGroup, pipeconf);
288 } catch (PiTranslationException e) {
289 log.warn("Unable to translate group, aborting {} operation: {} [{}]",
290 opType, e.getMessage(), pdGroup);
291 return;
292 }
293 final Operation operation = opType.equals(GroupOperation.Type.DELETE)
294 ? Operation.REMOVE : Operation.APPLY;
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800295 final PiActionProfileGroupHandle handle = piGroup.handle(deviceId);
296 if (writePiGroupOnDevice(piGroup, handle, operation)) {
297 if (operation.equals(Operation.APPLY)) {
298 groupTranslator.learn(handle, new PiTranslatedEntity<>(
299 pdGroup, piGroup, handle));
300 } else {
301 groupTranslator.forget(handle);
Carmelo Casconee44592f2018-09-12 02:24:47 -0700302 }
Carmelo Casconee44592f2018-09-12 02:24:47 -0700303 }
304 }
305
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800306 private boolean writePiGroupOnDevice(
307 PiActionProfileGroup group,
308 PiActionProfileGroupHandle groupHandle,
309 Operation operation) {
310 // Generate a write request to write both members and groups. Return
311 // true if request is successful or if there's no need to write on
312 // device (according to mirror state), otherwise, return false.
313 final Collection<PiActionProfileMember> members = extractAllMemberInstancesOrNull(group);
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800314 if (members == null) {
315 return false;
316 }
Carmelo Cascone61469462019-03-05 23:59:11 -0800317 final WriteRequest request = client.write(pipeconf);
318 final CompletableFuture<WriteResponse> futureResponse;
319 WRITE_LOCKS.get(deviceId).lock();
320 try {
321 // FIXME: when operation is remove, should we remove members first? Same
322 // thing when modifying a group, should we first modify the group then
323 // remove the member?
324 final boolean allMembersSkipped = members.stream()
325 .allMatch(m -> appendEntityToWriteRequestOrSkip(
326 request, m.handle(deviceId), m, memberMirror, operation));
327 final boolean groupSkipped = appendEntityToWriteRequestOrSkip(
328 request, groupHandle, group, groupMirror, operation);
329 if (allMembersSkipped && groupSkipped) {
330 return true;
331 } else {
332 futureResponse = updateMirrorAndSubmit(request);
333 }
334 } finally {
335 WRITE_LOCKS.get(deviceId).unlock();
Carmelo Casconee44592f2018-09-12 02:24:47 -0700336 }
Carmelo Cascone61469462019-03-05 23:59:11 -0800337 // Wait response from device. Returns true if all entities in the
338 // request (groups and members) were written successfully.
339 return Futures.getUnchecked(futureResponse).isSuccess();
Carmelo Casconee44592f2018-09-12 02:24:47 -0700340 }
341
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800342 private <H extends PiHandle, E extends PiEntity> boolean appendEntityToWriteRequestOrSkip(
Carmelo Cascone61469462019-03-05 23:59:11 -0800343 WriteRequest writeRequest, H handle, E entityToApply,
344 P4RuntimeMirror<H, E> mirror, Operation operation) {
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800345 // Should return true if there's no need to write entity on device,
346 // false if the write request is modified or an error occurs.
347 final TimedEntry<E> entityOnDevice = mirror.get(handle);
348 switch (operation) {
349 case APPLY:
350 if (entityOnDevice == null) {
351 writeRequest.insert(entityToApply);
352 } else if (entityToApply.equals(entityOnDevice.entry())) {
353 // Skip writing if group is unchanged.
354 return true;
355 } else {
356 writeRequest.modify(entityToApply);
357 }
358 break;
359 case REMOVE:
360 if (entityOnDevice == null) {
361 // Skip deleting if group does not exist on device.
362 return true;
363 } else {
364 writeRequest.delete(handle);
365 }
366 break;
367 default:
368 log.error("Unrecognized operation {}", operation);
369 break;
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800370 }
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800371 return false;
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800372 }
373
374 private Collection<PiActionProfileMember> extractAllMemberInstancesOrNull(
375 PiActionProfileGroup group) {
376 final Collection<PiActionProfileMember> instances = group.members().stream()
377 .map(PiActionProfileGroup.WeightedMember::instance)
378 .filter(Objects::nonNull)
379 .collect(Collectors.toList());
380 if (instances.size() != group.members().size()) {
381 log.error("PiActionProfileGroup has {} member references, " +
382 "but only {} instances were found",
383 group.members().size(), instances.size());
384 return null;
385 }
386 return instances;
Carmelo Casconee44592f2018-09-12 02:24:47 -0700387 }
388
Carmelo Cascone61469462019-03-05 23:59:11 -0800389 private CompletableFuture<WriteResponse> updateMirrorAndSubmit(WriteRequest request) {
390 groupMirror.applyWriteRequest(request);
391 memberMirror.applyWriteRequest(request);
392 return request.submit();
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800393 }
394
Carmelo Casconee44592f2018-09-12 02:24:47 -0700395 enum Operation {
396 APPLY, REMOVE
397 }
398}