Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 1 | /* |
| 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 | |
| 17 | package org.onosproject.p4runtime.ctl; |
| 18 | |
Carmelo Cascone | 81929aa | 2018-04-07 01:38:55 -0700 | [diff] [blame] | 19 | import org.onosproject.net.pi.model.PiMeterId; |
Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 20 | import org.onosproject.net.pi.model.PiMeterType; |
| 21 | import org.onosproject.net.pi.model.PiPipeconf; |
Carmelo Cascone | 81929aa | 2018-04-07 01:38:55 -0700 | [diff] [blame] | 22 | import org.onosproject.net.pi.model.PiTableId; |
Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 23 | import org.onosproject.net.pi.runtime.PiMeterBand; |
| 24 | import org.onosproject.net.pi.runtime.PiMeterCellConfig; |
| 25 | import org.onosproject.net.pi.runtime.PiMeterCellId; |
Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 26 | import org.onosproject.net.pi.runtime.PiTableEntry; |
| 27 | import org.slf4j.Logger; |
Carmelo Cascone | 6af4e17 | 2018-06-15 16:01:30 +0200 | [diff] [blame] | 28 | import p4.v1.P4RuntimeOuterClass; |
| 29 | import p4.v1.P4RuntimeOuterClass.DirectMeterEntry; |
| 30 | import p4.v1.P4RuntimeOuterClass.Entity; |
| 31 | import p4.v1.P4RuntimeOuterClass.MeterConfig; |
| 32 | import p4.v1.P4RuntimeOuterClass.MeterEntry; |
Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 33 | |
Carmelo Cascone | 81929aa | 2018-04-07 01:38:55 -0700 | [diff] [blame] | 34 | import java.util.Collections; |
Carmelo Cascone | e44592f | 2018-09-12 02:24:47 -0700 | [diff] [blame] | 35 | import java.util.List; |
Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 36 | import java.util.Objects; |
| 37 | import java.util.stream.Collectors; |
| 38 | |
| 39 | import static java.lang.String.format; |
Carmelo Cascone | 81929aa | 2018-04-07 01:38:55 -0700 | [diff] [blame] | 40 | import static org.onosproject.p4runtime.ctl.P4RuntimeUtils.indexMsg; |
Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 41 | import static org.slf4j.LoggerFactory.getLogger; |
Carmelo Cascone | 6af4e17 | 2018-06-15 16:01:30 +0200 | [diff] [blame] | 42 | import static p4.v1.P4RuntimeOuterClass.Entity.EntityCase.DIRECT_METER_ENTRY; |
| 43 | import static p4.v1.P4RuntimeOuterClass.Entity.EntityCase.METER_ENTRY; |
Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 44 | |
| 45 | /** |
Carmelo Cascone | 81929aa | 2018-04-07 01:38:55 -0700 | [diff] [blame] | 46 | * Encoder/decoder of PI meter cell configurations to meter entry protobuf |
| 47 | * messages, and vice versa. |
Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 48 | */ |
| 49 | final class MeterEntryCodec { |
| 50 | |
| 51 | private static final Logger log = getLogger(MeterEntryCodec.class); |
| 52 | |
| 53 | private MeterEntryCodec() { |
| 54 | // Hides constructor. |
| 55 | } |
| 56 | |
| 57 | /** |
Carmelo Cascone | 81929aa | 2018-04-07 01:38:55 -0700 | [diff] [blame] | 58 | * Returns a collection of P4Runtime entity protobuf messages describing |
| 59 | * both meter or direct meter entries, encoded from the given collection of |
| 60 | * PI meter cell configurations, for the given pipeconf. If a PI meter cell |
| 61 | * configurations cannot be encoded, it is skipped, hence the returned |
| 62 | * collection might have different size than the input one. |
Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 63 | * |
Carmelo Cascone | 81929aa | 2018-04-07 01:38:55 -0700 | [diff] [blame] | 64 | * @param cellConfigs meter cell configurations |
| 65 | * @param pipeconf pipeconf |
| 66 | * @return collection of entity messages describing both meter or direct |
| 67 | * meter entries |
Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 68 | */ |
Carmelo Cascone | e44592f | 2018-09-12 02:24:47 -0700 | [diff] [blame] | 69 | static List<Entity> encodePiMeterCellConfigs(List<PiMeterCellConfig> cellConfigs, |
Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 70 | PiPipeconf pipeconf) { |
Carmelo Cascone | 81929aa | 2018-04-07 01:38:55 -0700 | [diff] [blame] | 71 | final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf); |
| 72 | |
| 73 | if (browser == null) { |
| 74 | log.error("Unable to get a P4Info browser for pipeconf {}", pipeconf.id()); |
| 75 | return Collections.emptyList(); |
| 76 | } |
| 77 | |
Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 78 | return cellConfigs |
| 79 | .stream() |
| 80 | .map(cellConfig -> { |
| 81 | try { |
Carmelo Cascone | 81929aa | 2018-04-07 01:38:55 -0700 | [diff] [blame] | 82 | return encodePiMeterCellConfig(cellConfig, pipeconf, browser); |
Carmelo Cascone | 99c59db | 2019-01-17 15:39:35 -0800 | [diff] [blame] | 83 | } catch (P4InfoBrowser.NotFoundException | CodecException e) { |
Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 84 | log.warn("Unable to encode PI meter cell id: {}", e.getMessage()); |
| 85 | log.debug("exception", e); |
| 86 | return null; |
| 87 | } |
| 88 | }) |
| 89 | .filter(Objects::nonNull) |
| 90 | .collect(Collectors.toList()); |
| 91 | } |
| 92 | |
| 93 | /** |
Carmelo Cascone | 81929aa | 2018-04-07 01:38:55 -0700 | [diff] [blame] | 94 | * Returns a collection of P4Runtime entity protobuf messages to be used in |
| 95 | * requests to read all cells from the given meter identifiers. Works for |
| 96 | * both indirect or direct meters. If a PI meter identifier cannot be |
| 97 | * encoded, it is skipped, hence the returned collection might have |
| 98 | * different size than the input one. |
Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 99 | * |
Carmelo Cascone | 81929aa | 2018-04-07 01:38:55 -0700 | [diff] [blame] | 100 | * @param meterIds meter identifiers |
| 101 | * @param pipeconf pipeconf |
| 102 | * @return collection of entity messages |
| 103 | */ |
Carmelo Cascone | e44592f | 2018-09-12 02:24:47 -0700 | [diff] [blame] | 104 | static List<Entity> readAllCellsEntities(List<PiMeterId> meterIds, |
Carmelo Cascone | 81929aa | 2018-04-07 01:38:55 -0700 | [diff] [blame] | 105 | PiPipeconf pipeconf) { |
| 106 | final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf); |
| 107 | |
| 108 | if (browser == null) { |
| 109 | log.error("Unable to get a P4Info browser for pipeconf {}", pipeconf.id()); |
| 110 | return Collections.emptyList(); |
| 111 | } |
| 112 | |
| 113 | return meterIds |
| 114 | .stream() |
| 115 | .map(meterId -> { |
| 116 | try { |
| 117 | return readAllCellsEntity(meterId, pipeconf, browser); |
Carmelo Cascone | 99c59db | 2019-01-17 15:39:35 -0800 | [diff] [blame] | 118 | } catch (P4InfoBrowser.NotFoundException | CodecException e) { |
Carmelo Cascone | 81929aa | 2018-04-07 01:38:55 -0700 | [diff] [blame] | 119 | log.warn("Unable to encode meter ID to read-all-cells entity: {}", |
| 120 | e.getMessage()); |
| 121 | return null; |
| 122 | } |
| 123 | }) |
| 124 | .filter(Objects::nonNull) |
| 125 | .collect(Collectors.toList()); |
| 126 | } |
| 127 | |
| 128 | /** |
| 129 | * Returns a collection of PI meter cell configurations, decoded from the |
| 130 | * given P4Runtime entity protobuf messages describing both meter or direct |
| 131 | * meter entries, and pipeconf. If an entity message cannot be encoded, it |
| 132 | * is skipped, hence the returned collection might have different size than |
| 133 | * the input one. |
| 134 | * |
| 135 | * @param entities P4Runtime entity messages |
| 136 | * @param pipeconf pipeconf |
Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 137 | * @return collection of PI meter cell data |
| 138 | */ |
Carmelo Cascone | e44592f | 2018-09-12 02:24:47 -0700 | [diff] [blame] | 139 | static List<PiMeterCellConfig> decodeMeterEntities(List<Entity> entities, |
| 140 | PiPipeconf pipeconf) { |
Carmelo Cascone | 81929aa | 2018-04-07 01:38:55 -0700 | [diff] [blame] | 141 | final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf); |
| 142 | |
| 143 | if (browser == null) { |
| 144 | log.error("Unable to get a P4Info browser for pipeconf {}", pipeconf.id()); |
| 145 | return Collections.emptyList(); |
| 146 | } |
| 147 | |
Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 148 | return entities |
| 149 | .stream() |
| 150 | .filter(entity -> entity.getEntityCase() == METER_ENTRY || |
| 151 | entity.getEntityCase() == DIRECT_METER_ENTRY) |
| 152 | .map(entity -> { |
| 153 | try { |
Carmelo Cascone | 81929aa | 2018-04-07 01:38:55 -0700 | [diff] [blame] | 154 | return decodeMeterEntity(entity, pipeconf, browser); |
Carmelo Cascone | 99c59db | 2019-01-17 15:39:35 -0800 | [diff] [blame] | 155 | } catch (CodecException | P4InfoBrowser.NotFoundException e) { |
Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 156 | log.warn("Unable to decode meter entity message: {}", e.getMessage()); |
| 157 | return null; |
| 158 | } |
| 159 | }) |
| 160 | .filter(Objects::nonNull) |
| 161 | .collect(Collectors.toList()); |
| 162 | } |
| 163 | |
Carmelo Cascone | 81929aa | 2018-04-07 01:38:55 -0700 | [diff] [blame] | 164 | private static Entity encodePiMeterCellConfig(PiMeterCellConfig config, |
| 165 | PiPipeconf pipeconf, |
| 166 | P4InfoBrowser browser) |
Carmelo Cascone | 99c59db | 2019-01-17 15:39:35 -0800 | [diff] [blame] | 167 | throws P4InfoBrowser.NotFoundException, CodecException { |
Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 168 | |
Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 169 | int meterId; |
| 170 | Entity entity; |
Carmelo Cascone | 81929aa | 2018-04-07 01:38:55 -0700 | [diff] [blame] | 171 | MeterConfig meterConfig; |
| 172 | |
| 173 | PiMeterBand[] bands = config.meterBands() |
| 174 | .toArray(new PiMeterBand[config.meterBands().size()]); |
Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 175 | if (bands.length == 2) { |
Carmelo Cascone | 81929aa | 2018-04-07 01:38:55 -0700 | [diff] [blame] | 176 | long cir, cburst, pir, pburst; |
| 177 | // The band with bigger burst is peak if rate of them is equal. |
| 178 | if (bands[0].rate() > bands[1].rate() || |
| 179 | (bands[0].rate() == bands[1].rate() && |
| 180 | bands[0].burst() >= bands[1].burst())) { |
Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 181 | cir = bands[1].rate(); |
| 182 | cburst = bands[1].burst(); |
| 183 | pir = bands[0].rate(); |
| 184 | pburst = bands[0].burst(); |
| 185 | } else { |
| 186 | cir = bands[0].rate(); |
| 187 | cburst = bands[0].burst(); |
| 188 | pir = bands[1].rate(); |
| 189 | pburst = bands[1].burst(); |
| 190 | } |
Carmelo Cascone | 81929aa | 2018-04-07 01:38:55 -0700 | [diff] [blame] | 191 | meterConfig = MeterConfig.newBuilder() |
| 192 | .setCir(cir) |
| 193 | .setCburst(cburst) |
| 194 | .setPir(pir) |
| 195 | .setPburst(pburst) |
| 196 | .build(); |
| 197 | } else if (bands.length == 0) { |
| 198 | // When reading meter cells. |
| 199 | meterConfig = null; |
| 200 | } else { |
Carmelo Cascone | 99c59db | 2019-01-17 15:39:35 -0800 | [diff] [blame] | 201 | throw new CodecException("number of meter bands should be either 2 or 0"); |
Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 202 | } |
| 203 | |
Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 204 | switch (config.cellId().meterType()) { |
| 205 | case INDIRECT: |
Carmelo Cascone | 81929aa | 2018-04-07 01:38:55 -0700 | [diff] [blame] | 206 | meterId = browser.meters() |
| 207 | .getByName(config.cellId().meterId().id()) |
| 208 | .getPreamble().getId(); |
| 209 | MeterEntry.Builder indEntryBuilder = MeterEntry.newBuilder() |
| 210 | .setMeterId(meterId) |
| 211 | .setIndex(indexMsg(config.cellId().index())); |
| 212 | if (meterConfig != null) { |
| 213 | indEntryBuilder.setConfig(meterConfig); |
| 214 | } |
| 215 | entity = Entity.newBuilder() |
| 216 | .setMeterEntry(indEntryBuilder.build()).build(); |
Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 217 | break; |
| 218 | case DIRECT: |
Carmelo Cascone | 81929aa | 2018-04-07 01:38:55 -0700 | [diff] [blame] | 219 | DirectMeterEntry.Builder dirEntryBuilder = DirectMeterEntry.newBuilder() |
| 220 | .setTableEntry(TableEntryEncoder.encode( |
| 221 | config.cellId().tableEntry(), pipeconf)); |
| 222 | if (meterConfig != null) { |
| 223 | dirEntryBuilder.setConfig(meterConfig); |
Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 224 | } |
Carmelo Cascone | 81929aa | 2018-04-07 01:38:55 -0700 | [diff] [blame] | 225 | entity = Entity.newBuilder() |
| 226 | .setDirectMeterEntry(dirEntryBuilder.build()).build(); |
Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 227 | break; |
| 228 | default: |
Carmelo Cascone | 99c59db | 2019-01-17 15:39:35 -0800 | [diff] [blame] | 229 | throw new CodecException(format("unrecognized PI meter type '%s'", |
| 230 | config.cellId().meterType())); |
Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 231 | } |
Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 232 | |
| 233 | return entity; |
| 234 | } |
| 235 | |
Carmelo Cascone | 81929aa | 2018-04-07 01:38:55 -0700 | [diff] [blame] | 236 | private static Entity readAllCellsEntity(PiMeterId meterId, |
| 237 | PiPipeconf pipeconf, |
| 238 | P4InfoBrowser browser) |
Carmelo Cascone | 99c59db | 2019-01-17 15:39:35 -0800 | [diff] [blame] | 239 | throws P4InfoBrowser.NotFoundException, CodecException { |
Carmelo Cascone | 81929aa | 2018-04-07 01:38:55 -0700 | [diff] [blame] | 240 | |
| 241 | if (!pipeconf.pipelineModel().meter(meterId).isPresent()) { |
Carmelo Cascone | 99c59db | 2019-01-17 15:39:35 -0800 | [diff] [blame] | 242 | throw new CodecException(format( |
Carmelo Cascone | 81929aa | 2018-04-07 01:38:55 -0700 | [diff] [blame] | 243 | "not such meter '%s' in pipeline model", meterId)); |
| 244 | } |
| 245 | final PiMeterType meterType = pipeconf.pipelineModel() |
| 246 | .meter(meterId).get().meterType(); |
| 247 | |
| 248 | switch (meterType) { |
| 249 | case INDIRECT: |
| 250 | final int p4InfoMeterId = browser.meters() |
| 251 | .getByName(meterId.id()) |
| 252 | .getPreamble().getId(); |
| 253 | return Entity.newBuilder().setMeterEntry( |
| 254 | P4RuntimeOuterClass.MeterEntry.newBuilder() |
| 255 | // Index unset to read all cells |
| 256 | .setMeterId(p4InfoMeterId) |
| 257 | .build()) |
| 258 | .build(); |
| 259 | case DIRECT: |
| 260 | final PiTableId tableId = pipeconf.pipelineModel() |
| 261 | .meter(meterId).get().table(); |
| 262 | if (tableId == null) { |
Carmelo Cascone | 99c59db | 2019-01-17 15:39:35 -0800 | [diff] [blame] | 263 | throw new CodecException(format( |
Carmelo Cascone | 81929aa | 2018-04-07 01:38:55 -0700 | [diff] [blame] | 264 | "null table for direct meter '%s'", meterId)); |
| 265 | } |
| 266 | final int p4TableId = browser.tables().getByName(tableId.id()) |
| 267 | .getPreamble().getId(); |
| 268 | return Entity.newBuilder().setDirectMeterEntry( |
| 269 | P4RuntimeOuterClass.DirectMeterEntry.newBuilder() |
| 270 | .setTableEntry( |
| 271 | // Match unset to read all cells |
| 272 | P4RuntimeOuterClass.TableEntry.newBuilder() |
| 273 | .setTableId(p4TableId) |
| 274 | .build()) |
| 275 | .build()) |
| 276 | .build(); |
| 277 | default: |
Carmelo Cascone | 99c59db | 2019-01-17 15:39:35 -0800 | [diff] [blame] | 278 | throw new CodecException(format( |
Carmelo Cascone | 81929aa | 2018-04-07 01:38:55 -0700 | [diff] [blame] | 279 | "unrecognized PI meter type '%s'", meterType)); |
| 280 | } |
| 281 | } |
| 282 | |
| 283 | private static PiMeterCellConfig decodeMeterEntity(Entity entity, |
| 284 | PiPipeconf pipeconf, |
| 285 | P4InfoBrowser browser) |
Carmelo Cascone | 99c59db | 2019-01-17 15:39:35 -0800 | [diff] [blame] | 286 | throws CodecException, P4InfoBrowser.NotFoundException { |
Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 287 | |
Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 288 | MeterConfig meterConfig; |
Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 289 | PiMeterCellId piCellId; |
| 290 | |
Carmelo Cascone | 81929aa | 2018-04-07 01:38:55 -0700 | [diff] [blame] | 291 | if (entity.getEntityCase() == METER_ENTRY) { |
| 292 | String meterName = browser.meters() |
FrankWang | 9ea7276 | 2018-04-17 15:36:49 +0800 | [diff] [blame] | 293 | .getById(entity.getMeterEntry().getMeterId()) |
Carmelo Cascone | 81929aa | 2018-04-07 01:38:55 -0700 | [diff] [blame] | 294 | .getPreamble() |
| 295 | .getName(); |
| 296 | piCellId = PiMeterCellId.ofIndirect( |
| 297 | PiMeterId.of(meterName), |
| 298 | entity.getMeterEntry().getIndex().getIndex()); |
| 299 | meterConfig = entity.getMeterEntry().getConfig(); |
| 300 | } else if (entity.getEntityCase() == DIRECT_METER_ENTRY) { |
| 301 | PiTableEntry piTableEntry = TableEntryEncoder.decode( |
| 302 | entity.getDirectMeterEntry().getTableEntry(), |
| 303 | pipeconf); |
| 304 | piCellId = PiMeterCellId.ofDirect(piTableEntry); |
| 305 | meterConfig = entity.getDirectMeterEntry().getConfig(); |
| 306 | } else { |
Carmelo Cascone | 99c59db | 2019-01-17 15:39:35 -0800 | [diff] [blame] | 307 | throw new CodecException(format( |
Carmelo Cascone | 81929aa | 2018-04-07 01:38:55 -0700 | [diff] [blame] | 308 | "unrecognized entity type '%s' in P4Runtime message", |
| 309 | entity.getEntityCase().name())); |
Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 310 | } |
| 311 | |
Carmelo Cascone | 81929aa | 2018-04-07 01:38:55 -0700 | [diff] [blame] | 312 | return PiMeterCellConfig.builder() |
| 313 | .withMeterCellId(piCellId) |
| 314 | .withMeterBand(new PiMeterBand(meterConfig.getCir(), |
| 315 | meterConfig.getCburst())) |
| 316 | .withMeterBand(new PiMeterBand(meterConfig.getPir(), |
| 317 | meterConfig.getPburst())) |
| 318 | .build(); |
Frank Wang | d7e3b4b | 2017-09-24 13:37:54 +0900 | [diff] [blame] | 319 | } |
Carmelo Cascone | 81929aa | 2018-04-07 01:38:55 -0700 | [diff] [blame] | 320 | } |