blob: 5165783f09ed0381be2b2c810a1e05bd1651aba0 [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;
Frank Wangd7e3b4b2017-09-24 13:37:54 +090026import org.onosproject.net.meter.DefaultMeter;
Wailok Shumf013a782021-07-26 16:51:01 +080027import org.onosproject.net.meter.DefaultMeterFeatures;
Frank Wangd7e3b4b2017-09-24 13:37:54 +090028import org.onosproject.net.meter.Meter;
Wailok Shumf013a782021-07-26 16:51:01 +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 Shumf013a782021-07-26 16:51:01 +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 Shum96642092021-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 Shum6d42cff2021-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 Shum6d42cff2021-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 Shum6d42cff2021-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;
Frank Wangd7e3b4b2017-09-24 13:37:54 +090053
Wailok Shumf013a782021-07-26 16:51:01 +080054import static com.google.common.base.Preconditions.checkNotNull;
Wailok Shum6d42cff2021-08-22 19:40:13 +080055import static org.onosproject.p4runtime.api.P4RuntimeWriteClient.UpdateType;
Wailok Shumf013a782021-07-26 16:51:01 +080056
Frank Wangd7e3b4b2017-09-24 13:37:54 +090057/**
58 * Implementation of MeterProgrammable behaviour for P4Runtime.
59 */
60public class P4RuntimeMeterProgrammable extends AbstractP4RuntimeHandlerBehaviour implements MeterProgrammable {
61
Wailok Shum6d42cff2021-08-22 19:40:13 +080062 private static final Striped<Lock> WRITE_LOCKS = Striped.lock(30);
Frank Wangd7e3b4b2017-09-24 13:37:54 +090063
64 private PiMeterTranslator translator;
65 private P4RuntimeMeterMirror meterMirror;
66 private PiPipelineModel pipelineModel;
67
68 @Override
Carmelo Casconec32976e2019-04-08 14:50:52 -070069 protected boolean setupBehaviour(String opName) {
70 if (!super.setupBehaviour(opName)) {
Frank Wangd7e3b4b2017-09-24 13:37:54 +090071 return false;
72 }
73
Yi Tsengd7716482018-10-31 15:34:30 -070074 translator = translationService.meterTranslator();
Frank Wangd7e3b4b2017-09-24 13:37:54 +090075 meterMirror = handler().get(P4RuntimeMeterMirror.class);
76 pipelineModel = pipeconf.pipelineModel();
77 return true;
78 }
79
80 @Override
81 public CompletableFuture<Boolean> performMeterOperation(MeterOperation meterOp) {
82
Carmelo Casconec32976e2019-04-08 14:50:52 -070083 if (!setupBehaviour("performMeterOperation()")) {
Kevin Chuangc267df22018-05-02 16:26:04 +080084 return CompletableFuture.completedFuture(false);
85 }
86
Frank Wangd7e3b4b2017-09-24 13:37:54 +090087 return CompletableFuture.completedFuture(processMeterOp(meterOp));
88 }
89
90 private boolean processMeterOp(MeterOperation meterOp) {
Frank Wangd7e3b4b2017-09-24 13:37:54 +090091 PiMeterCellConfig piMeterCellConfig;
Wailok Shum6d42cff2021-08-22 19:40:13 +080092 final PiMeterCellHandle handle = PiMeterCellHandle.of(deviceId,
93 (PiMeterCellId) meterOp.meter().meterCellId());
94 boolean result = true;
95 WRITE_LOCKS.get(deviceId).lock();
96 try {
97 switch (meterOp.type()) {
98 case ADD:
99 case MODIFY:
100 // Create a config for modify operation
101 try {
102 piMeterCellConfig = translator.translate(meterOp.meter(), pipeconf);
103 } catch (PiTranslationException e) {
104 log.warn("Unable translate meter, aborting meter operation {}: {}",
105 meterOp.type(), e.getMessage());
106 log.debug("exception", e);
107 return false;
108 }
109 translator.learn(handle, new PiTranslatedEntity<>(meterOp.meter(), piMeterCellConfig, handle));
110 break;
111 case REMOVE:
112 // Create a empty config for reset operation
113 PiMeterCellId piMeterCellId = (PiMeterCellId) meterOp.meter().meterCellId();
114 piMeterCellConfig = PiMeterCellConfig.reset(piMeterCellId);
115 translator.forget(handle);
116 break;
117 default:
118 log.warn("Meter Operation type {} not supported", meterOp.type());
Wailok Shum96642092021-08-06 16:23:36 +0800119 return false;
Wailok Shum6d42cff2021-08-22 19:40:13 +0800120 }
121
122 WriteRequest request = client.write(p4DeviceId, pipeconf);
123 appendEntryToWriteRequestOrSkip(request, handle, piMeterCellConfig);
124 if (!request.pendingUpdates().isEmpty()) {
125 result = request.submitSync().isSuccess();
126 if (result) {
127 meterMirror.applyWriteRequest(request);
Wailok Shum96642092021-08-06 16:23:36 +0800128 }
Wailok Shum6d42cff2021-08-22 19:40:13 +0800129 }
130 } finally {
131 WRITE_LOCKS.get(deviceId).unlock();
Frank Wangd7e3b4b2017-09-24 13:37:54 +0900132 }
Frank Wangd7e3b4b2017-09-24 13:37:54 +0900133 return result;
134 }
135
136 @Override
137 public CompletableFuture<Collection<Meter>> getMeters() {
138
Carmelo Casconec32976e2019-04-08 14:50:52 -0700139 if (!setupBehaviour("getMeters()")) {
Frank Wangd7e3b4b2017-09-24 13:37:54 +0900140 return CompletableFuture.completedFuture(Collections.emptyList());
141 }
142
143 Collection<PiMeterCellConfig> piMeterCellConfigs;
144
145 Set<PiMeterId> meterIds = new HashSet<>();
146 for (PiMeterModel mode : pipelineModel.meters()) {
147 meterIds.add(mode.id());
148 }
149
Carmelo Casconec2be50a2019-04-10 00:15:39 -0700150 piMeterCellConfigs = client.read(p4DeviceId, pipeconf)
Carmelo Cascone4c289b72019-01-22 15:30:45 -0800151 .meterCells(meterIds).submitSync().all(PiMeterCellConfig.class);
Frank Wangd7e3b4b2017-09-24 13:37:54 +0900152
Wailok Shum6d42cff2021-08-22 19:40:13 +0800153 meterMirror.sync(deviceId, piMeterCellConfigs);
154
155 if (piMeterCellConfigs.isEmpty()) {
156 return CompletableFuture.completedFuture(Collections.emptyList());
157 }
158
159 List<PiMeterCellId> inconsistentOrDefaultCells = Lists.newArrayList();
160 List<Meter> meters = Lists.newArrayList();
161
162 // Check the consistency of meter config
163 for (PiMeterCellConfig config : piMeterCellConfigs) {
164 PiMeterCellHandle handle = PiMeterCellHandle.of(deviceId, config);
165 DefaultMeter meter = (DefaultMeter) forgeMeter(config, handle);
166 if (meter == null) {
167 // A default config cannot be used to forge meter
168 // because meter has at least 1 band while default config has no band
169 inconsistentOrDefaultCells.add(config.cellId());
170 } else {
171 meters.add(meter);
172 }
173 }
174
pierventrec0914ec2021-08-27 15:25:02 +0200175 // Reset all inconsistent meter cells to the default config
Wailok Shum6d42cff2021-08-22 19:40:13 +0800176 if (!inconsistentOrDefaultCells.isEmpty()) {
177 WriteRequest request = client.write(p4DeviceId, pipeconf);
178 for (PiMeterCellId cellId : inconsistentOrDefaultCells) {
179 PiMeterCellHandle handle = PiMeterCellHandle.of(deviceId, cellId);
180 appendEntryToWriteRequestOrSkip(request, handle, PiMeterCellConfig.reset(cellId));
181 }
pierventrec0914ec2021-08-27 15:25:02 +0200182
183 request.submit().whenComplete((response, ex) -> {
184 if (ex != null) {
185 log.error("Exception resetting inconsistent meter entries", ex);
186 } else {
187 log.debug("Successfully removed {} out of {} inconsistent meter entries",
188 response.success().size(), response.all().size());
189 }
190 response.success().forEach(entity -> meterMirror.remove((PiMeterCellHandle) entity.handle()));
191 });
Wailok Shum6d42cff2021-08-22 19:40:13 +0800192 }
Frank Wangd7e3b4b2017-09-24 13:37:54 +0900193
194 return CompletableFuture.completedFuture(meters);
195 }
Wailok Shumf013a782021-07-26 16:51:01 +0800196
197 @Override
198 public CompletableFuture<Collection<MeterFeatures>> getMeterFeatures() {
199
200 if (!setupBehaviour("getMeterFeatures()")) {
201 return CompletableFuture.completedFuture(Collections.emptyList());
202 }
203
204 Collection<MeterFeatures> meterFeatures = new HashSet<>();
205 pipeconf.pipelineModel().meters().forEach(
206 m -> meterFeatures.add(new P4RuntimeMeterFeaturesBuilder(m, deviceId).build()));
207
208 return CompletableFuture.completedFuture(meterFeatures);
209 }
210
Wailok Shum6d42cff2021-08-22 19:40:13 +0800211 private Meter forgeMeter(PiMeterCellConfig config, PiMeterCellHandle handle) {
212 final Optional<PiTranslatedEntity<Meter, PiMeterCellConfig>>
213 translatedEntity = translator.lookup(handle);
214 final TimedEntry<PiMeterCellConfig> timedEntry = meterMirror.get(handle);
215
216 // A meter cell config might not be present in the translation store if it
217 // is default configuration.
218 if (translatedEntity.isEmpty()) {
219 if (!config.isDefaultConfig()) {
220 log.warn("Meter Cell Config obtained from device {} is different from " +
221 "one in in translation store: device={}, store=Default", deviceId, config);
222 } else {
223 log.debug("Configs obtained from device: {} and present in the store are default, " +
pierventrec0914ec2021-08-27 15:25:02 +0200224 "skipping the forge section", deviceId);
Wailok Shum6d42cff2021-08-22 19:40:13 +0800225 }
226 return null;
227 }
pierventrec0914ec2021-08-27 15:25:02 +0200228
Wailok Shum6d42cff2021-08-22 19:40:13 +0800229 // The config is not consistent
pierventre85208182021-09-02 19:16:32 +0200230 if (!isSimilar(translatedEntity.get().translated(), config)) {
Wailok Shum6d42cff2021-08-22 19:40:13 +0800231 log.warn("Meter Cell Config obtained from device {} is different from " +
232 "one in in translation store: device={}, store={}",
233 deviceId, config, translatedEntity.get().translated());
234 return null;
235 }
pierventrec0914ec2021-08-27 15:25:02 +0200236
237 // Big problems in the mirror ? This could happen in case of issues with
238 // the eventual consistent maps used in the AbstractDistributedP4RuntimeMirror
Wailok Shum6d42cff2021-08-22 19:40:13 +0800239 if (timedEntry == null) {
240 log.warn("Meter entry handle not found in device mirror: {}", handle);
241 return null;
242 }
243
pierventrec0914ec2021-08-27 15:25:02 +0200244 Meter original = translatedEntity.get().original();
245 // Forge a meter with MeterCellId, Bands and DeviceId using the original value.
Wailok Shum6d42cff2021-08-22 19:40:13 +0800246 // Other values are not required because we cannot retrieve them from the south
247 DefaultMeter meter = (DefaultMeter) DefaultMeter.builder()
pierventre85208182021-09-02 19:16:32 +0200248 .withBands(original.bands())
249 .withCellId(original.meterCellId())
250 .forDevice(deviceId)
251 .build();
Wailok Shum6d42cff2021-08-22 19:40:13 +0800252 meter.setState(MeterState.ADDED);
253 return meter;
254 }
255
256 private boolean appendEntryToWriteRequestOrSkip(
257 final WriteRequest writeRequest,
258 final PiMeterCellHandle handle,
259 PiMeterCellConfig configToModify) {
260
261 final TimedEntry<PiMeterCellConfig> configOnDevice = meterMirror.get(handle);
262
263 if (configOnDevice != null && configOnDevice.entry().equals(configToModify)) {
264 log.debug("Ignoring re-apply of existing entry: {}", configToModify);
265 return true;
266 }
267
268 writeRequest.entity(configToModify, UpdateType.MODIFY);
269
270 return false;
271 }
272
Wailok Shumf013a782021-07-26 16:51:01 +0800273 /**
pierventre85208182021-09-02 19:16:32 +0200274 * Returns true if the given PiMeterCellConfigs are similar enough to be deemed equal
275 * for reconciliation purposes. This is required to handle read/write asymmetry in devices
276 * that allow variations in the meter rate/burst. E.g., devices that implement metering
277 * with a rate or burst size that is slightly higher/lower than the configured ones,
278 * so the values written by ONOS will be different than those read from the device.
279 *
280 * @param onosMeter the ONOS meter
281 * @param deviceMeter the meter in the device
282 * @return true if the meters are similar, false otherwise
283 */
284 protected boolean isSimilar(PiMeterCellConfig onosMeter, PiMeterCellConfig deviceMeter) {
285 return onosMeter.equals(deviceMeter);
286 }
287
288 /**
Wailok Shumf013a782021-07-26 16:51:01 +0800289 * P4 meter features builder.
290 */
pierventrec0914ec2021-08-27 15:25:02 +0200291 public static class P4RuntimeMeterFeaturesBuilder {
Wailok Shumf013a782021-07-26 16:51:01 +0800292 private final PiMeterModel piMeterModel;
293 private DeviceId deviceId;
294
295 private static final long PI_METER_START_INDEX = 0L;
296 private static final short PI_METER_MAX_BAND = 2;
297 private static final short PI_METER_MAX_COLOR = 3;
298
299 public P4RuntimeMeterFeaturesBuilder(PiMeterModel piMeterModel, DeviceId deviceId) {
300 this.piMeterModel = checkNotNull(piMeterModel);
301 this.deviceId = deviceId;
302 }
303
304 /**
305 * To build a MeterFeatures using the PiMeterModel object
306 * retrieved from pipeconf.
307 *
308 * @return the meter features object
309 */
310 public MeterFeatures build() {
pierventrec0914ec2021-08-27 15:25:02 +0200311 // We set the basic values before to extract the other information.
Wailok Shumf013a782021-07-26 16:51:01 +0800312 MeterFeatures.Builder builder = DefaultMeterFeatures.builder()
313 .forDevice(deviceId)
314 // The scope value will be PiMeterId
315 .withScope(MeterScope.of(piMeterModel.id().id()))
316 .withMaxBands(PI_METER_MAX_BAND)
317 .withMaxColors(PI_METER_MAX_COLOR)
318 .withStartIndex(PI_METER_START_INDEX)
319 .withEndIndex(piMeterModel.size() - 1);
pierventrec0914ec2021-08-27 15:25:02 +0200320
321 // p4rt meters support MARK_YELLOW (committed config) and
322 // MARK_RED (peak config) band types.
Wailok Shumf013a782021-07-26 16:51:01 +0800323 Set<Band.Type> bands = Sets.newHashSet();
pierventrec0914ec2021-08-27 15:25:02 +0200324 bands.add(Band.Type.MARK_YELLOW);
325 bands.add(Band.Type.MARK_RED);
Wailok Shumf013a782021-07-26 16:51:01 +0800326 builder.withBandTypes(bands);
pierventrec0914ec2021-08-27 15:25:02 +0200327
328 // We extract the supported units;
Wailok Shumf013a782021-07-26 16:51:01 +0800329 Set<Meter.Unit> units = Sets.newHashSet();
330 if (piMeterModel.unit() == PiMeterModel.Unit.BYTES) {
pierventrec0914ec2021-08-27 15:25:02 +0200331 units.add(Meter.Unit.BYTES_PER_SEC);
Wailok Shumf013a782021-07-26 16:51:01 +0800332 } else if (piMeterModel.unit() == PiMeterModel.Unit.PACKETS) {
333 units.add(Meter.Unit.PKTS_PER_SEC);
334 }
Wailok Shumf013a782021-07-26 16:51:01 +0800335
pierventrec0914ec2021-08-27 15:25:02 +0200336 return builder.withUnits(units)
337 .hasBurst(true)
338 .hasStats(false)
339 .build();
Wailok Shumf013a782021-07-26 16:51:01 +0800340 }
341
342 /**
343 * To build an empty meter features.
pierventrec0914ec2021-08-27 15:25:02 +0200344 *
Wailok Shumf013a782021-07-26 16:51:01 +0800345 * @param deviceId the device id
346 * @return the meter features
347 */
348 public MeterFeatures noMeterFeatures(DeviceId deviceId) {
349 return DefaultMeterFeatures.noMeterFeatures(deviceId);
350 }
351 }
Carmelo Casconee5b28722018-06-22 17:28:28 +0200352}