blob: a7dbadde549f9dc6f524339e459c5993b15f43eb [file] [log] [blame]
Frank Wangd7e3b4b2017-09-24 13:37:54 +09001/*
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
Wailok Shum6d42cff2021-08-22 19:40:13 +080019import com.google.common.collect.Lists;
Wailok Shumf013a782021-07-26 16:51:01 +080020import com.google.common.collect.Sets;
Wailok Shum6d42cff2021-08-22 19:40:13 +080021import com.google.common.util.concurrent.Striped;
Frank Wangd7e3b4b2017-09-24 13:37:54 +090022import org.onosproject.drivers.p4runtime.mirror.P4RuntimeMeterMirror;
Wailok Shum6d42cff2021-08-22 19:40:13 +080023import org.onosproject.drivers.p4runtime.mirror.TimedEntry;
Wailok Shumf013a782021-07-26 16:51:01 +080024import org.onosproject.net.DeviceId;
Frank Wangd7e3b4b2017-09-24 13:37:54 +090025import org.onosproject.net.meter.Band;
26import org.onosproject.net.meter.DefaultBand;
27import org.onosproject.net.meter.DefaultMeter;
Wailok Shumf013a782021-07-26 16:51:01 +080028import org.onosproject.net.meter.DefaultMeterFeatures;
Frank Wangd7e3b4b2017-09-24 13:37:54 +090029import org.onosproject.net.meter.Meter;
Wailok Shumf013a782021-07-26 16:51:01 +080030import org.onosproject.net.meter.MeterFeatures;
Frank Wangd7e3b4b2017-09-24 13:37:54 +090031import org.onosproject.net.meter.MeterOperation;
32import org.onosproject.net.meter.MeterProgrammable;
Wailok Shumf013a782021-07-26 16:51:01 +080033import org.onosproject.net.meter.MeterScope;
Frank Wangd7e3b4b2017-09-24 13:37:54 +090034import org.onosproject.net.meter.MeterState;
35import org.onosproject.net.pi.model.PiMeterId;
36import org.onosproject.net.pi.model.PiMeterModel;
37import org.onosproject.net.pi.model.PiPipelineModel;
38import org.onosproject.net.pi.runtime.PiMeterCellConfig;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080039import org.onosproject.net.pi.runtime.PiMeterCellHandle;
Wailok Shum96642092021-08-06 16:23:36 +080040import org.onosproject.net.pi.runtime.PiMeterCellId;
Frank Wangd7e3b4b2017-09-24 13:37:54 +090041import org.onosproject.net.pi.service.PiMeterTranslator;
Wailok Shum6d42cff2021-08-22 19:40:13 +080042import org.onosproject.net.pi.service.PiTranslatedEntity;
Frank Wangd7e3b4b2017-09-24 13:37:54 +090043import org.onosproject.net.pi.service.PiTranslationException;
Wailok Shum6d42cff2021-08-22 19:40:13 +080044import org.onosproject.p4runtime.api.P4RuntimeWriteClient.WriteRequest;
45import org.onosproject.p4runtime.api.P4RuntimeWriteClient.WriteResponse;
Frank Wangd7e3b4b2017-09-24 13:37:54 +090046
47import java.util.Collection;
48import java.util.Collections;
Frank Wangd7e3b4b2017-09-24 13:37:54 +090049import java.util.HashSet;
Wailok Shum6d42cff2021-08-22 19:40:13 +080050import java.util.List;
51import java.util.Optional;
Carmelo Casconee5b28722018-06-22 17:28:28 +020052import java.util.Set;
Frank Wangd7e3b4b2017-09-24 13:37:54 +090053import java.util.concurrent.CompletableFuture;
Frank Wangd7e3b4b2017-09-24 13:37:54 +090054import java.util.concurrent.locks.Lock;
Frank Wangd7e3b4b2017-09-24 13:37:54 +090055import java.util.stream.Collectors;
56
Wailok Shumf013a782021-07-26 16:51:01 +080057import static com.google.common.base.Preconditions.checkNotNull;
Wailok Shum96642092021-08-06 16:23:36 +080058import static org.onosproject.net.meter.MeterOperation.Type.ADD;
59import static org.onosproject.net.meter.MeterOperation.Type.MODIFY;
60import static org.onosproject.net.meter.MeterOperation.Type.REMOVE;
Wailok Shum6d42cff2021-08-22 19:40:13 +080061import static org.onosproject.p4runtime.api.P4RuntimeWriteClient.UpdateType;
Wailok Shumf013a782021-07-26 16:51:01 +080062
Frank Wangd7e3b4b2017-09-24 13:37:54 +090063/**
64 * Implementation of MeterProgrammable behaviour for P4Runtime.
65 */
66public class P4RuntimeMeterProgrammable extends AbstractP4RuntimeHandlerBehaviour implements MeterProgrammable {
67
Wailok Shum6d42cff2021-08-22 19:40:13 +080068 private static final Striped<Lock> WRITE_LOCKS = Striped.lock(30);
Frank Wangd7e3b4b2017-09-24 13:37:54 +090069
70 private PiMeterTranslator translator;
71 private P4RuntimeMeterMirror meterMirror;
72 private PiPipelineModel pipelineModel;
73
74 @Override
Carmelo Casconec32976e2019-04-08 14:50:52 -070075 protected boolean setupBehaviour(String opName) {
76 if (!super.setupBehaviour(opName)) {
Frank Wangd7e3b4b2017-09-24 13:37:54 +090077 return false;
78 }
79
Yi Tsengd7716482018-10-31 15:34:30 -070080 translator = translationService.meterTranslator();
Frank Wangd7e3b4b2017-09-24 13:37:54 +090081 meterMirror = handler().get(P4RuntimeMeterMirror.class);
82 pipelineModel = pipeconf.pipelineModel();
83 return true;
84 }
85
86 @Override
87 public CompletableFuture<Boolean> performMeterOperation(MeterOperation meterOp) {
88
Carmelo Casconec32976e2019-04-08 14:50:52 -070089 if (!setupBehaviour("performMeterOperation()")) {
Kevin Chuangc267df22018-05-02 16:26:04 +080090 return CompletableFuture.completedFuture(false);
91 }
92
Frank Wangd7e3b4b2017-09-24 13:37:54 +090093 return CompletableFuture.completedFuture(processMeterOp(meterOp));
94 }
95
96 private boolean processMeterOp(MeterOperation meterOp) {
Frank Wangd7e3b4b2017-09-24 13:37:54 +090097 PiMeterCellConfig piMeterCellConfig;
Wailok Shum6d42cff2021-08-22 19:40:13 +080098 final PiMeterCellHandle handle = PiMeterCellHandle.of(deviceId,
99 (PiMeterCellId) meterOp.meter().meterCellId());
100 boolean result = true;
101 WRITE_LOCKS.get(deviceId).lock();
102 try {
103 switch (meterOp.type()) {
104 case ADD:
105 case MODIFY:
106 // Create a config for modify operation
107 try {
108 piMeterCellConfig = translator.translate(meterOp.meter(), pipeconf);
109 } catch (PiTranslationException e) {
110 log.warn("Unable translate meter, aborting meter operation {}: {}",
111 meterOp.type(), e.getMessage());
112 log.debug("exception", e);
113 return false;
114 }
115 translator.learn(handle, new PiTranslatedEntity<>(meterOp.meter(), piMeterCellConfig, handle));
116 break;
117 case REMOVE:
118 // Create a empty config for reset operation
119 PiMeterCellId piMeterCellId = (PiMeterCellId) meterOp.meter().meterCellId();
120 piMeterCellConfig = PiMeterCellConfig.reset(piMeterCellId);
121 translator.forget(handle);
122 break;
123 default:
124 log.warn("Meter Operation type {} not supported", meterOp.type());
Wailok Shum96642092021-08-06 16:23:36 +0800125 return false;
Wailok Shum6d42cff2021-08-22 19:40:13 +0800126 }
127
128 WriteRequest request = client.write(p4DeviceId, pipeconf);
129 appendEntryToWriteRequestOrSkip(request, handle, piMeterCellConfig);
130 if (!request.pendingUpdates().isEmpty()) {
131 result = request.submitSync().isSuccess();
132 if (result) {
133 meterMirror.applyWriteRequest(request);
Wailok Shum96642092021-08-06 16:23:36 +0800134 }
Wailok Shum6d42cff2021-08-22 19:40:13 +0800135 }
136 } finally {
137 WRITE_LOCKS.get(deviceId).unlock();
Frank Wangd7e3b4b2017-09-24 13:37:54 +0900138 }
Frank Wangd7e3b4b2017-09-24 13:37:54 +0900139 return result;
140 }
141
142 @Override
143 public CompletableFuture<Collection<Meter>> getMeters() {
144
Carmelo Casconec32976e2019-04-08 14:50:52 -0700145 if (!setupBehaviour("getMeters()")) {
Frank Wangd7e3b4b2017-09-24 13:37:54 +0900146 return CompletableFuture.completedFuture(Collections.emptyList());
147 }
148
149 Collection<PiMeterCellConfig> piMeterCellConfigs;
150
151 Set<PiMeterId> meterIds = new HashSet<>();
152 for (PiMeterModel mode : pipelineModel.meters()) {
153 meterIds.add(mode.id());
154 }
155
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700156 piMeterCellConfigs = client.read(p4DeviceId, pipeconf)
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800157 .meterCells(meterIds).submitSync().all(PiMeterCellConfig.class);
Frank Wangd7e3b4b2017-09-24 13:37:54 +0900158
Wailok Shum6d42cff2021-08-22 19:40:13 +0800159 meterMirror.sync(deviceId, piMeterCellConfigs);
160
161 if (piMeterCellConfigs.isEmpty()) {
162 return CompletableFuture.completedFuture(Collections.emptyList());
163 }
164
165 List<PiMeterCellId> inconsistentOrDefaultCells = Lists.newArrayList();
166 List<Meter> meters = Lists.newArrayList();
167
168 // Check the consistency of meter config
169 for (PiMeterCellConfig config : piMeterCellConfigs) {
170 PiMeterCellHandle handle = PiMeterCellHandle.of(deviceId, config);
171 DefaultMeter meter = (DefaultMeter) forgeMeter(config, handle);
172 if (meter == null) {
173 // A default config cannot be used to forge meter
174 // because meter has at least 1 band while default config has no band
175 inconsistentOrDefaultCells.add(config.cellId());
176 } else {
177 meters.add(meter);
178 }
179 }
180
181 // Reset all inconsistent meter cells to default state
182 if (!inconsistentOrDefaultCells.isEmpty()) {
183 WriteRequest request = client.write(p4DeviceId, pipeconf);
184 for (PiMeterCellId cellId : inconsistentOrDefaultCells) {
185 PiMeterCellHandle handle = PiMeterCellHandle.of(deviceId, cellId);
186 appendEntryToWriteRequestOrSkip(request, handle, PiMeterCellConfig.reset(cellId));
187 }
188 WriteResponse response = request.submitSync();
189 meterMirror.applyWriteResponse(response);
190 }
Frank Wangd7e3b4b2017-09-24 13:37:54 +0900191
192 return CompletableFuture.completedFuture(meters);
193 }
Wailok Shumf013a782021-07-26 16:51:01 +0800194
195 @Override
196 public CompletableFuture<Collection<MeterFeatures>> getMeterFeatures() {
197
198 if (!setupBehaviour("getMeterFeatures()")) {
199 return CompletableFuture.completedFuture(Collections.emptyList());
200 }
201
202 Collection<MeterFeatures> meterFeatures = new HashSet<>();
203 pipeconf.pipelineModel().meters().forEach(
204 m -> meterFeatures.add(new P4RuntimeMeterFeaturesBuilder(m, deviceId).build()));
205
206 return CompletableFuture.completedFuture(meterFeatures);
207 }
208
Wailok Shum6d42cff2021-08-22 19:40:13 +0800209 private Meter forgeMeter(PiMeterCellConfig config, PiMeterCellHandle handle) {
210 final Optional<PiTranslatedEntity<Meter, PiMeterCellConfig>>
211 translatedEntity = translator.lookup(handle);
212 final TimedEntry<PiMeterCellConfig> timedEntry = meterMirror.get(handle);
213
214 // A meter cell config might not be present in the translation store if it
215 // is default configuration.
216 if (translatedEntity.isEmpty()) {
217 if (!config.isDefaultConfig()) {
218 log.warn("Meter Cell Config obtained from device {} is different from " +
219 "one in in translation store: device={}, store=Default", deviceId, config);
220 } else {
221 log.debug("Configs obtained from device: {} and present in the store are default, " +
222 "skipping the forge section");
223 }
224 return null;
225 }
226 // The config is not consistent
227 if (!translatedEntity.get().translated().equals(config)) {
228 log.warn("Meter Cell Config obtained from device {} is different from " +
229 "one in in translation store: device={}, store={}",
230 deviceId, config, translatedEntity.get().translated());
231 return null;
232 }
233 if (timedEntry == null) {
234 log.warn("Meter entry handle not found in device mirror: {}", handle);
235 return null;
236 }
237
238 // Forge a meter with MeterCellId, Bands and DeviceId
239 // Other values are not required because we cannot retrieve them from the south
240 DefaultMeter meter = (DefaultMeter) DefaultMeter.builder()
241 .withBands(config.meterBands().stream().map(b -> DefaultBand.builder()
242 .withRate(b.rate())
243 .burstSize(b.burst())
244 .ofType(Band.Type.NONE)
245 .build()).collect(Collectors.toList()))
246 .withCellId(config.cellId())
247 .forDevice(deviceId)
248 .build();
249 meter.setState(MeterState.ADDED);
250 return meter;
251 }
252
253 private boolean appendEntryToWriteRequestOrSkip(
254 final WriteRequest writeRequest,
255 final PiMeterCellHandle handle,
256 PiMeterCellConfig configToModify) {
257
258 final TimedEntry<PiMeterCellConfig> configOnDevice = meterMirror.get(handle);
259
260 if (configOnDevice != null && configOnDevice.entry().equals(configToModify)) {
261 log.debug("Ignoring re-apply of existing entry: {}", configToModify);
262 return true;
263 }
264
265 writeRequest.entity(configToModify, UpdateType.MODIFY);
266
267 return false;
268 }
269
Wailok Shumf013a782021-07-26 16:51:01 +0800270 /**
271 * P4 meter features builder.
272 */
273 public class P4RuntimeMeterFeaturesBuilder {
274 private final PiMeterModel piMeterModel;
275 private DeviceId deviceId;
276
277 private static final long PI_METER_START_INDEX = 0L;
278 private static final short PI_METER_MAX_BAND = 2;
279 private static final short PI_METER_MAX_COLOR = 3;
280
281 public P4RuntimeMeterFeaturesBuilder(PiMeterModel piMeterModel, DeviceId deviceId) {
282 this.piMeterModel = checkNotNull(piMeterModel);
283 this.deviceId = deviceId;
284 }
285
286 /**
287 * To build a MeterFeatures using the PiMeterModel object
288 * retrieved from pipeconf.
289 *
290 * @return the meter features object
291 */
292 public MeterFeatures build() {
293 /*
294 * We set the basic values before to extract the other information.
295 */
296 MeterFeatures.Builder builder = DefaultMeterFeatures.builder()
297 .forDevice(deviceId)
298 // The scope value will be PiMeterId
299 .withScope(MeterScope.of(piMeterModel.id().id()))
300 .withMaxBands(PI_METER_MAX_BAND)
301 .withMaxColors(PI_METER_MAX_COLOR)
302 .withStartIndex(PI_METER_START_INDEX)
303 .withEndIndex(piMeterModel.size() - 1);
304 /*
305 * Pi meter only support NONE type
306 */
307 Set<Band.Type> bands = Sets.newHashSet();
308 bands.add(Band.Type.NONE);
309 builder.withBandTypes(bands);
310 /*
311 * We extract the supported units;
312 */
313 Set<Meter.Unit> units = Sets.newHashSet();
314 if (piMeterModel.unit() == PiMeterModel.Unit.BYTES) {
315 units.add(Meter.Unit.KB_PER_SEC);
316 } else if (piMeterModel.unit() == PiMeterModel.Unit.PACKETS) {
317 units.add(Meter.Unit.PKTS_PER_SEC);
318 }
319 builder.withUnits(units);
320 /*
321 * Burst is supported ?
322 */
323 builder.hasBurst(true);
324 /*
325 * Stats are supported ?
326 */
327 builder.hasStats(false);
328
329 return builder.build();
330 }
331
332 /**
333 * To build an empty meter features.
334 * @param deviceId the device id
335 * @return the meter features
336 */
337 public MeterFeatures noMeterFeatures(DeviceId deviceId) {
338 return DefaultMeterFeatures.noMeterFeatures(deviceId);
339 }
340 }
Carmelo Casconee5b28722018-06-22 17:28:28 +0200341}