blob: 3407cf29bbc0d07f0d1db3c10e021ba832bd346c [file] [log] [blame]
Carmelo Cascone58136812018-07-19 03:40:16 +02001/*
2 * Copyright 2018-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.net.pi.impl;
18
Yi Tseng29b4f222021-07-28 17:00:46 -070019import com.google.common.collect.Lists;
Carmelo Cascone58136812018-07-19 03:40:16 +020020import com.google.common.collect.Maps;
21import com.google.common.collect.Sets;
Carmelo Cascone1e8843f2018-07-19 19:01:12 +020022import org.onosproject.net.Device;
Carmelo Cascone58136812018-07-19 03:40:16 +020023import org.onosproject.net.PortNumber;
24import org.onosproject.net.flow.instructions.Instruction;
25import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
Yi Tseng29b4f222021-07-28 17:00:46 -070026import org.onosproject.net.flow.instructions.Instructions.TruncateInstruction;
Carmelo Cascone58136812018-07-19 03:40:16 +020027import org.onosproject.net.group.Group;
Yi Tseng29b4f222021-07-28 17:00:46 -070028import org.onosproject.net.group.GroupBucket;
Carmelo Cascone58136812018-07-19 03:40:16 +020029import org.onosproject.net.group.GroupDescription;
Carmelo Cascone1e8843f2018-07-19 19:01:12 +020030import org.onosproject.net.pi.model.PiPipeconf;
31import org.onosproject.net.pi.model.PiPipelineInterpreter;
Carmelo Cascone9db4d5c2019-04-16 17:36:33 -070032import org.onosproject.net.pi.runtime.PiCloneSessionEntry;
Carmelo Cascone58136812018-07-19 03:40:16 +020033import org.onosproject.net.pi.runtime.PiMulticastGroupEntry;
Carmelo Cascone9db4d5c2019-04-16 17:36:33 -070034import org.onosproject.net.pi.runtime.PiPreEntry;
Carmelo Cascone58136812018-07-19 03:40:16 +020035import org.onosproject.net.pi.runtime.PiPreReplica;
36import org.onosproject.net.pi.service.PiTranslationException;
37
38import java.util.Collection;
39import java.util.List;
40import java.util.Map;
Carmelo Cascone1e8843f2018-07-19 19:01:12 +020041import java.util.Optional;
Carmelo Cascone58136812018-07-19 03:40:16 +020042import java.util.Set;
43import java.util.stream.Collectors;
44
45import static com.google.common.base.Preconditions.checkNotNull;
46import static java.lang.String.format;
47
48/**
Carmelo Cascone9db4d5c2019-04-16 17:36:33 -070049 * Implementation of replication group translation logic.
Carmelo Cascone58136812018-07-19 03:40:16 +020050 */
Carmelo Cascone9db4d5c2019-04-16 17:36:33 -070051final class PiReplicationGroupTranslatorImpl {
Carmelo Cascone58136812018-07-19 03:40:16 +020052
Carmelo Cascone9db4d5c2019-04-16 17:36:33 -070053 private PiReplicationGroupTranslatorImpl() {
Carmelo Cascone58136812018-07-19 03:40:16 +020054 // Hides constructor.
55 }
56
57 /**
Carmelo Cascone9db4d5c2019-04-16 17:36:33 -070058 * Returns a PI PRE entry equivalent to the given group, for the given
59 * pipeconf and device.
Carmelo Cascone58136812018-07-19 03:40:16 +020060 * <p>
Carmelo Cascone9db4d5c2019-04-16 17:36:33 -070061 * The passed group is expected to have type {@link GroupDescription.Type#ALL}
62 * or {@link GroupDescription.Type#CLONE}.
Carmelo Cascone58136812018-07-19 03:40:16 +020063 *
64 * @param group group
Carmelo Cascone1e8843f2018-07-19 19:01:12 +020065 * @param pipeconf pipeconf
66 * @param device device
Carmelo Cascone58136812018-07-19 03:40:16 +020067 * @return PI PRE entry
68 * @throws PiTranslationException if the group cannot be translated
69 */
Carmelo Cascone9db4d5c2019-04-16 17:36:33 -070070 static PiPreEntry translate(Group group, PiPipeconf pipeconf, Device device)
Carmelo Cascone58136812018-07-19 03:40:16 +020071 throws PiTranslationException {
72
73 checkNotNull(group);
74
Yi Tseng29b4f222021-07-28 17:00:46 -070075 final List<OutputInstruction> outInstructions = Lists.newArrayList();
76 int truncateMaxLen = PiCloneSessionEntry.DO_NOT_TRUNCATE;
77 for (GroupBucket bucket : group.buckets().buckets()) {
78 int numInstructionsInBucket = bucket.treatment().allInstructions().size();
79 List<OutputInstruction> outputs =
80 getInstructions(bucket, Instruction.Type.OUTPUT, OutputInstruction.class);
81 List<TruncateInstruction> truncates =
82 getInstructions(bucket, Instruction.Type.TRUNCATE, TruncateInstruction.class);
83 if (outputs.size() != 1) {
84 throw new PiTranslationException(
85 "support only groups with just one OUTPUT instruction per bucket");
86 }
87 outInstructions.add(outputs.get(0));
88 if (truncates.size() != 0) {
89 if (group.type() != GroupDescription.Type.CLONE) {
90 throw new PiTranslationException("only CLONE group support truncate instruction");
91 }
92 if (truncates.size() != 1) {
93 throw new PiTranslationException(
94 "support only groups with just one TRUNCATE instruction per bucket");
95 }
96 int truncateInstMaxLen = truncates.get(0).maxLen();
97 if (truncateMaxLen != PiCloneSessionEntry.DO_NOT_TRUNCATE &&
98 truncateMaxLen != truncateInstMaxLen) {
99 throw new PiTranslationException("all TRUNCATE instruction must be the same in a CLONE group");
100 }
101 truncateMaxLen = truncateInstMaxLen;
102 } else if (truncateMaxLen != PiCloneSessionEntry.DO_NOT_TRUNCATE) {
103 // No truncate instruction found in this bucket, but previous bucket contains one.
104 throw new PiTranslationException("all TRUNCATE instruction must be the same in a CLONE group");
105 }
106 if (numInstructionsInBucket != outputs.size() + truncates.size()) {
107 throw new PiTranslationException("bucket contains unsupported instruction(s)");
108 }
Carmelo Cascone58136812018-07-19 03:40:16 +0200109 }
110
Carmelo Cascone9db4d5c2019-04-16 17:36:33 -0700111 switch (group.type()) {
112 case ALL:
113 return PiMulticastGroupEntry.builder()
114 .withGroupId(group.id().id())
115 .addReplicas(getReplicas(outInstructions, device))
116 .build();
117 case CLONE:
118 return PiCloneSessionEntry.builder()
119 .withSessionId(group.id().id())
120 .addReplicas(getReplicas(outInstructions, device))
Yi Tseng29b4f222021-07-28 17:00:46 -0700121 .withMaxPacketLengthBytes(truncateMaxLen)
Carmelo Cascone9db4d5c2019-04-16 17:36:33 -0700122 .build();
123 default:
124 throw new PiTranslationException(format(
125 "group type %s not supported", group.type()));
126 }
Carmelo Cascone58136812018-07-19 03:40:16 +0200127 }
128
Carmelo Cascone1e8843f2018-07-19 19:01:12 +0200129 private static Set<PiPreReplica> getReplicas(
130 Collection<OutputInstruction> instructions, Device device)
131 throws PiTranslationException {
Carmelo Cascone58136812018-07-19 03:40:16 +0200132 // Account for multiple replicas for the same port.
133 final Map<PortNumber, Set<PiPreReplica>> replicaMap = Maps.newHashMap();
134 final List<PortNumber> ports = instructions.stream()
135 .map(OutputInstruction::port)
136 .collect(Collectors.toList());
137 for (PortNumber port : ports) {
Carmelo Cascone1e8843f2018-07-19 19:01:12 +0200138 if (port.isLogical()) {
139 port = logicalToPipelineSpecific(port, device);
140 }
Carmelo Cascone58136812018-07-19 03:40:16 +0200141 // Use incremental instance IDs for replicas of the same port.
Carmelo Cascone1e8843f2018-07-19 19:01:12 +0200142 replicaMap.putIfAbsent(port, Sets.newHashSet());
Carmelo Cascone58136812018-07-19 03:40:16 +0200143 replicaMap.get(port).add(
Carmelo Casconedcd56ba2020-08-04 17:19:40 -0700144 new PiPreReplica(port, replicaMap.get(port).size()));
Carmelo Cascone58136812018-07-19 03:40:16 +0200145 }
146 return replicaMap.values().stream()
147 .flatMap(Collection::stream)
148 .collect(Collectors.toSet());
149 }
Carmelo Cascone1e8843f2018-07-19 19:01:12 +0200150
151 private static PortNumber logicalToPipelineSpecific(
152 PortNumber logicalPort, Device device)
153 throws PiTranslationException {
154 if (!device.is(PiPipelineInterpreter.class)) {
155 throw new PiTranslationException(
156 "missing interpreter, cannot map logical port " + logicalPort.toString());
157 }
158 final PiPipelineInterpreter interpreter = device.as(PiPipelineInterpreter.class);
159 Optional<Integer> mappedPort = interpreter.mapLogicalPortNumber(logicalPort);
160 if (!mappedPort.isPresent()) {
161 throw new PiTranslationException(
162 "interpreter cannot map logical port " + logicalPort.toString());
163 }
164 return PortNumber.portNumber(mappedPort.get());
165 }
Yi Tseng29b4f222021-07-28 17:00:46 -0700166
167 private static <I extends Instruction> List<I> getInstructions(GroupBucket bucket,
168 Instruction.Type type,
169 Class<I> instructionClass) {
170 return bucket.treatment().allInstructions().stream()
171 .filter(i -> i.type() == type)
172 .map(instructionClass::cast)
173 .collect(Collectors.toList());
174 }
Carmelo Cascone58136812018-07-19 03:40:16 +0200175}