blob: 8bb4fc7ad4c2f92d91ef85d49f581f465dc49ea2 [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 Shum8ebb7932021-08-22 19:40:13 +080019import com.google.common.collect.Lists;
Wailok Shumbe900bd2021-08-05 00:55:47 +080020import com.google.common.collect.Sets;
Wailok Shum8ebb7932021-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 Shum8ebb7932021-08-22 19:40:13 +080023import org.onosproject.drivers.p4runtime.mirror.TimedEntry;
Wailok Shumbe900bd2021-08-05 00:55:47 +080024import org.onosproject.net.DeviceId;
Frank Wangd7e3b4b2017-09-24 13:37:54 +090025import org.onosproject.net.meter.Band;
Frank Wangd7e3b4b2017-09-24 13:37:54 +090026import org.onosproject.net.meter.DefaultMeter;
Wailok Shumbe900bd2021-08-05 00:55:47 +080027import org.onosproject.net.meter.DefaultMeterFeatures;
Frank Wangd7e3b4b2017-09-24 13:37:54 +090028import org.onosproject.net.meter.Meter;
Wailok Shumbe900bd2021-08-05 00:55:47 +080029import org.onosproject.net.meter.MeterFeatures;
Frank Wangd7e3b4b2017-09-24 13:37:54 +090030import org.onosproject.net.meter.MeterOperation;
31import org.onosproject.net.meter.MeterProgrammable;
Wailok Shumbe900bd2021-08-05 00:55:47 +080032import org.onosproject.net.meter.MeterScope;
Frank Wangd7e3b4b2017-09-24 13:37:54 +090033import org.onosproject.net.meter.MeterState;
34import org.onosproject.net.pi.model.PiMeterId;
35import org.onosproject.net.pi.model.PiMeterModel;
36import org.onosproject.net.pi.model.PiPipelineModel;
37import org.onosproject.net.pi.runtime.PiMeterCellConfig;
Carmelo Cascone4c289b72019-01-22 15:30:45 -080038import org.onosproject.net.pi.runtime.PiMeterCellHandle;
Wailok Shum8aa21452021-08-06 16:23:36 +080039import org.onosproject.net.pi.runtime.PiMeterCellId;
Frank Wangd7e3b4b2017-09-24 13:37:54 +090040import org.onosproject.net.pi.service.PiMeterTranslator;
Wailok Shum8ebb7932021-08-22 19:40:13 +080041import org.onosproject.net.pi.service.PiTranslatedEntity;
Frank Wangd7e3b4b2017-09-24 13:37:54 +090042import org.onosproject.net.pi.service.PiTranslationException;
Wailok Shum8ebb7932021-08-22 19:40:13 +080043import org.onosproject.p4runtime.api.P4RuntimeWriteClient.WriteRequest;
Frank Wangd7e3b4b2017-09-24 13:37:54 +090044
45import java.util.Collection;
46import java.util.Collections;
Frank Wangd7e3b4b2017-09-24 13:37:54 +090047import java.util.HashSet;
Wailok Shum8ebb7932021-08-22 19:40:13 +080048import java.util.List;
49import java.util.Optional;
Carmelo Casconee5b28722018-06-22 17:28:28 +020050import java.util.Set;
Frank Wangd7e3b4b2017-09-24 13:37:54 +090051import java.util.concurrent.CompletableFuture;
Frank Wangd7e3b4b2017-09-24 13:37:54 +090052import java.util.concurrent.locks.Lock;
pierventrebbfbfe12022-03-04 15:21:09 -080053import java.util.stream.Collectors;
Frank Wangd7e3b4b2017-09-24 13:37:54 +090054
Wailok Shumbe900bd2021-08-05 00:55:47 +080055import static com.google.common.base.Preconditions.checkNotNull;
Wailok Shum8ebb7932021-08-22 19:40:13 +080056import static org.onosproject.p4runtime.api.P4RuntimeWriteClient.UpdateType;
Wailok Shumbe900bd2021-08-05 00:55:47 +080057
Frank Wangd7e3b4b2017-09-24 13:37:54 +090058/**
59 * Implementation of MeterProgrammable behaviour for P4Runtime.
60 */
61public class P4RuntimeMeterProgrammable extends AbstractP4RuntimeHandlerBehaviour implements MeterProgrammable {
62
Wailok Shum8ebb7932021-08-22 19:40:13 +080063 private static final Striped<Lock> WRITE_LOCKS = Striped.lock(30);
Frank Wangd7e3b4b2017-09-24 13:37:54 +090064
65 private PiMeterTranslator translator;
66 private P4RuntimeMeterMirror meterMirror;
67 private PiPipelineModel pipelineModel;
68
69 @Override
Carmelo Casconec32976e2019-04-08 14:50:52 -070070 protected boolean setupBehaviour(String opName) {
71 if (!super.setupBehaviour(opName)) {
Frank Wangd7e3b4b2017-09-24 13:37:54 +090072 return false;
73 }
74
Yi Tsengd7716482018-10-31 15:34:30 -070075 translator = translationService.meterTranslator();
Frank Wangd7e3b4b2017-09-24 13:37:54 +090076 meterMirror = handler().get(P4RuntimeMeterMirror.class);
77 pipelineModel = pipeconf.pipelineModel();
78 return true;
79 }
80
81 @Override
82 public CompletableFuture<Boolean> performMeterOperation(MeterOperation meterOp) {
83
Carmelo Casconec32976e2019-04-08 14:50:52 -070084 if (!setupBehaviour("performMeterOperation()")) {
Kevin Chuangc267df22018-05-02 16:26:04 +080085 return CompletableFuture.completedFuture(false);
86 }
87
Frank Wangd7e3b4b2017-09-24 13:37:54 +090088 return CompletableFuture.completedFuture(processMeterOp(meterOp));
89 }
90
91 private boolean processMeterOp(MeterOperation meterOp) {
Frank Wangd7e3b4b2017-09-24 13:37:54 +090092 PiMeterCellConfig piMeterCellConfig;
Wailok Shum8ebb7932021-08-22 19:40:13 +080093 final PiMeterCellHandle handle = PiMeterCellHandle.of(deviceId,
94 (PiMeterCellId) meterOp.meter().meterCellId());
95 boolean result = true;
96 WRITE_LOCKS.get(deviceId).lock();
97 try {
98 switch (meterOp.type()) {
99 case ADD:
100 case MODIFY:
101 // Create a config for modify operation
102 try {
103 piMeterCellConfig = translator.translate(meterOp.meter(), pipeconf);
104 } catch (PiTranslationException e) {
105 log.warn("Unable translate meter, aborting meter operation {}: {}",
106 meterOp.type(), e.getMessage());
107 log.debug("exception", e);
108 return false;
109 }
110 translator.learn(handle, new PiTranslatedEntity<>(meterOp.meter(), piMeterCellConfig, handle));
111 break;
112 case REMOVE:
113 // Create a empty config for reset operation
114 PiMeterCellId piMeterCellId = (PiMeterCellId) meterOp.meter().meterCellId();
115 piMeterCellConfig = PiMeterCellConfig.reset(piMeterCellId);
116 translator.forget(handle);
117 break;
118 default:
119 log.warn("Meter Operation type {} not supported", meterOp.type());
Wailok Shum8aa21452021-08-06 16:23:36 +0800120 return false;
Wailok Shum8ebb7932021-08-22 19:40:13 +0800121 }
122
123 WriteRequest request = client.write(p4DeviceId, pipeconf);
124 appendEntryToWriteRequestOrSkip(request, handle, piMeterCellConfig);
125 if (!request.pendingUpdates().isEmpty()) {
126 result = request.submitSync().isSuccess();
127 if (result) {
128 meterMirror.applyWriteRequest(request);
Wailok Shum8aa21452021-08-06 16:23:36 +0800129 }
Wailok Shum8ebb7932021-08-22 19:40:13 +0800130 }
131 } finally {
132 WRITE_LOCKS.get(deviceId).unlock();
Frank Wangd7e3b4b2017-09-24 13:37:54 +0900133 }
Frank Wangd7e3b4b2017-09-24 13:37:54 +0900134 return result;
135 }
136
137 @Override
138 public CompletableFuture<Collection<Meter>> getMeters() {
139
Carmelo Casconec32976e2019-04-08 14:50:52 -0700140 if (!setupBehaviour("getMeters()")) {
Frank Wangd7e3b4b2017-09-24 13:37:54 +0900141 return CompletableFuture.completedFuture(Collections.emptyList());
142 }
143
144 Collection<PiMeterCellConfig> piMeterCellConfigs;
145
146 Set<PiMeterId> meterIds = new HashSet<>();
147 for (PiMeterModel mode : pipelineModel.meters()) {
148 meterIds.add(mode.id());
149 }
150
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700151 piMeterCellConfigs = client.read(p4DeviceId, pipeconf)
pierventrebbfbfe12022-03-04 15:21:09 -0800152 .meterCells(meterIds).submitSync().all(PiMeterCellConfig.class)
153 .stream()
154 .filter(piMeterCellConfig -> !piMeterCellConfig.isDefaultConfig())
155 .collect(Collectors.toList());
Frank Wangd7e3b4b2017-09-24 13:37:54 +0900156
Wailok Shum8ebb7932021-08-22 19:40:13 +0800157 meterMirror.sync(deviceId, piMeterCellConfigs);
158
159 if (piMeterCellConfigs.isEmpty()) {
160 return CompletableFuture.completedFuture(Collections.emptyList());
161 }
162
163 List<PiMeterCellId> inconsistentOrDefaultCells = Lists.newArrayList();
164 List<Meter> meters = Lists.newArrayList();
165
166 // Check the consistency of meter config
167 for (PiMeterCellConfig config : piMeterCellConfigs) {
168 PiMeterCellHandle handle = PiMeterCellHandle.of(deviceId, config);
169 DefaultMeter meter = (DefaultMeter) forgeMeter(config, handle);
170 if (meter == null) {
171 // A default config cannot be used to forge meter
172 // because meter has at least 1 band while default config has no band
173 inconsistentOrDefaultCells.add(config.cellId());
174 } else {
175 meters.add(meter);
176 }
177 }
178
pierventre36b9a892021-08-27 15:25:02 +0200179 // Reset all inconsistent meter cells to the default config
Wailok Shum8ebb7932021-08-22 19:40:13 +0800180 if (!inconsistentOrDefaultCells.isEmpty()) {
181 WriteRequest request = client.write(p4DeviceId, pipeconf);
182 for (PiMeterCellId cellId : inconsistentOrDefaultCells) {
183 PiMeterCellHandle handle = PiMeterCellHandle.of(deviceId, cellId);
184 appendEntryToWriteRequestOrSkip(request, handle, PiMeterCellConfig.reset(cellId));
185 }
pierventre36b9a892021-08-27 15:25:02 +0200186
187 request.submit().whenComplete((response, ex) -> {
188 if (ex != null) {
189 log.error("Exception resetting inconsistent meter entries", ex);
190 } else {
191 log.debug("Successfully removed {} out of {} inconsistent meter entries",
192 response.success().size(), response.all().size());
193 }
194 response.success().forEach(entity -> meterMirror.remove((PiMeterCellHandle) entity.handle()));
195 });
Wailok Shum8ebb7932021-08-22 19:40:13 +0800196 }
Frank Wangd7e3b4b2017-09-24 13:37:54 +0900197
198 return CompletableFuture.completedFuture(meters);
199 }
Wailok Shumbe900bd2021-08-05 00:55:47 +0800200
201 @Override
202 public CompletableFuture<Collection<MeterFeatures>> getMeterFeatures() {
203
204 if (!setupBehaviour("getMeterFeatures()")) {
205 return CompletableFuture.completedFuture(Collections.emptyList());
206 }
207
208 Collection<MeterFeatures> meterFeatures = new HashSet<>();
209 pipeconf.pipelineModel().meters().forEach(
210 m -> meterFeatures.add(new P4RuntimeMeterFeaturesBuilder(m, deviceId).build()));
211
212 return CompletableFuture.completedFuture(meterFeatures);
213 }
214
Wailok Shum8ebb7932021-08-22 19:40:13 +0800215 private Meter forgeMeter(PiMeterCellConfig config, PiMeterCellHandle handle) {
216 final Optional<PiTranslatedEntity<Meter, PiMeterCellConfig>>
217 translatedEntity = translator.lookup(handle);
218 final TimedEntry<PiMeterCellConfig> timedEntry = meterMirror.get(handle);
219
220 // A meter cell config might not be present in the translation store if it
221 // is default configuration.
222 if (translatedEntity.isEmpty()) {
223 if (!config.isDefaultConfig()) {
224 log.warn("Meter Cell Config obtained from device {} is different from " +
225 "one in in translation store: device={}, store=Default", deviceId, config);
226 } else {
pierventrea7708792021-09-10 09:37:29 +0200227 log.debug("Configs for {} obtained from device: {} and from the store are default, " +
228 "skipping the forge section", config.cellId(), deviceId);
Wailok Shum8ebb7932021-08-22 19:40:13 +0800229 }
230 return null;
231 }
pierventre36b9a892021-08-27 15:25:02 +0200232
pierventrea7708792021-09-10 09:37:29 +0200233 // The config is not consistent. MeterProgrammable should remember
234 // that config from devices can be default which means no band
pierventre89984eb2021-09-02 19:16:32 +0200235 if (!isSimilar(translatedEntity.get().translated(), config)) {
Wailok Shum8ebb7932021-08-22 19:40:13 +0800236 log.warn("Meter Cell Config obtained from device {} is different from " +
237 "one in in translation store: device={}, store={}",
238 deviceId, config, translatedEntity.get().translated());
239 return null;
240 }
pierventre36b9a892021-08-27 15:25:02 +0200241
242 // Big problems in the mirror ? This could happen in case of issues with
243 // the eventual consistent maps used in the AbstractDistributedP4RuntimeMirror
Wailok Shum8ebb7932021-08-22 19:40:13 +0800244 if (timedEntry == null) {
245 log.warn("Meter entry handle not found in device mirror: {}", handle);
246 return null;
247 }
248
pierventre36b9a892021-08-27 15:25:02 +0200249 Meter original = translatedEntity.get().original();
250 // Forge a meter with MeterCellId, Bands and DeviceId using the original value.
Wailok Shum8ebb7932021-08-22 19:40:13 +0800251 DefaultMeter meter = (DefaultMeter) DefaultMeter.builder()
pierventre89984eb2021-09-02 19:16:32 +0200252 .withBands(original.bands())
253 .withCellId(original.meterCellId())
254 .forDevice(deviceId)
255 .build();
Wailok Shum8ebb7932021-08-22 19:40:13 +0800256 meter.setState(MeterState.ADDED);
257 return meter;
258 }
259
260 private boolean appendEntryToWriteRequestOrSkip(
261 final WriteRequest writeRequest,
262 final PiMeterCellHandle handle,
263 PiMeterCellConfig configToModify) {
264
265 final TimedEntry<PiMeterCellConfig> configOnDevice = meterMirror.get(handle);
266
267 if (configOnDevice != null && configOnDevice.entry().equals(configToModify)) {
268 log.debug("Ignoring re-apply of existing entry: {}", configToModify);
269 return true;
270 }
271
272 writeRequest.entity(configToModify, UpdateType.MODIFY);
273
274 return false;
275 }
276
Wailok Shumbe900bd2021-08-05 00:55:47 +0800277 /**
pierventre89984eb2021-09-02 19:16:32 +0200278 * Returns true if the given PiMeterCellConfigs are similar enough to be deemed equal
279 * for reconciliation purposes. This is required to handle read/write asymmetry in devices
280 * that allow variations in the meter rate/burst. E.g., devices that implement metering
281 * with a rate or burst size that is slightly higher/lower than the configured ones,
282 * so the values written by ONOS will be different than those read from the device.
283 *
284 * @param onosMeter the ONOS meter
285 * @param deviceMeter the meter in the device
286 * @return true if the meters are similar, false otherwise
287 */
288 protected boolean isSimilar(PiMeterCellConfig onosMeter, PiMeterCellConfig deviceMeter) {
289 return onosMeter.equals(deviceMeter);
290 }
291
292 /**
Wailok Shumbe900bd2021-08-05 00:55:47 +0800293 * P4 meter features builder.
294 */
pierventre36b9a892021-08-27 15:25:02 +0200295 public static class P4RuntimeMeterFeaturesBuilder {
Wailok Shumbe900bd2021-08-05 00:55:47 +0800296 private final PiMeterModel piMeterModel;
297 private DeviceId deviceId;
298
299 private static final long PI_METER_START_INDEX = 0L;
300 private static final short PI_METER_MAX_BAND = 2;
301 private static final short PI_METER_MAX_COLOR = 3;
302
303 public P4RuntimeMeterFeaturesBuilder(PiMeterModel piMeterModel, DeviceId deviceId) {
304 this.piMeterModel = checkNotNull(piMeterModel);
305 this.deviceId = deviceId;
306 }
307
308 /**
309 * To build a MeterFeatures using the PiMeterModel object
310 * retrieved from pipeconf.
311 *
312 * @return the meter features object
313 */
314 public MeterFeatures build() {
pierventre36b9a892021-08-27 15:25:02 +0200315 // We set the basic values before to extract the other information.
Wailok Shumbe900bd2021-08-05 00:55:47 +0800316 MeterFeatures.Builder builder = DefaultMeterFeatures.builder()
317 .forDevice(deviceId)
318 // The scope value will be PiMeterId
319 .withScope(MeterScope.of(piMeterModel.id().id()))
320 .withMaxBands(PI_METER_MAX_BAND)
Wailok Shum1d8c5e82021-09-13 17:23:20 +0800321 .withMaxColors(PI_METER_MAX_COLOR);
322
323 // We extract the number of supported meters.
324 if (piMeterModel.size() > 0) {
325 builder.withStartIndex(PI_METER_START_INDEX)
326 .withEndIndex(piMeterModel.size() - 1);
327 }
pierventre36b9a892021-08-27 15:25:02 +0200328
329 // p4rt meters support MARK_YELLOW (committed config) and
330 // MARK_RED (peak config) band types.
Wailok Shumbe900bd2021-08-05 00:55:47 +0800331 Set<Band.Type> bands = Sets.newHashSet();
pierventre36b9a892021-08-27 15:25:02 +0200332 bands.add(Band.Type.MARK_YELLOW);
333 bands.add(Band.Type.MARK_RED);
Wailok Shumbe900bd2021-08-05 00:55:47 +0800334 builder.withBandTypes(bands);
pierventre36b9a892021-08-27 15:25:02 +0200335
336 // We extract the supported units;
Wailok Shumbe900bd2021-08-05 00:55:47 +0800337 Set<Meter.Unit> units = Sets.newHashSet();
338 if (piMeterModel.unit() == PiMeterModel.Unit.BYTES) {
pierventre36b9a892021-08-27 15:25:02 +0200339 units.add(Meter.Unit.BYTES_PER_SEC);
Wailok Shumbe900bd2021-08-05 00:55:47 +0800340 } else if (piMeterModel.unit() == PiMeterModel.Unit.PACKETS) {
341 units.add(Meter.Unit.PKTS_PER_SEC);
342 }
Wailok Shumbe900bd2021-08-05 00:55:47 +0800343
pierventre36b9a892021-08-27 15:25:02 +0200344 return builder.withUnits(units)
345 .hasBurst(true)
346 .hasStats(false)
347 .build();
Wailok Shumbe900bd2021-08-05 00:55:47 +0800348 }
349
350 /**
351 * To build an empty meter features.
pierventre36b9a892021-08-27 15:25:02 +0200352 *
Wailok Shumbe900bd2021-08-05 00:55:47 +0800353 * @param deviceId the device id
354 * @return the meter features
355 */
356 public MeterFeatures noMeterFeatures(DeviceId deviceId) {
357 return DefaultMeterFeatures.noMeterFeatures(deviceId);
358 }
359 }
Carmelo Casconee5b28722018-06-22 17:28:28 +0200360}