blob: ab603b143890b2dbd969c71caedd35ac2d0bc813 [file] [log] [blame]
Andrea Campanellacbbb7952019-11-25 06:38:41 +00001/*
2 * Copyright 2016-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 */
16package org.opencord.olt.impl;
17
Saurav Dasf62cea82020-08-26 17:43:04 -070018import static java.util.stream.Collectors.collectingAndThen;
19import static java.util.stream.Collectors.groupingBy;
20import static java.util.stream.Collectors.mapping;
21import static java.util.stream.Collectors.toSet;
22import static org.onlab.util.Tools.groupedThreads;
23import static org.opencord.olt.impl.OsgiPropertyConstants.DELETE_METERS;
24import static org.opencord.olt.impl.OsgiPropertyConstants.DELETE_METERS_DEFAULT;
25import static org.slf4j.LoggerFactory.getLogger;
26
27import java.util.ArrayList;
28import java.util.Collection;
29import java.util.Dictionary;
30import java.util.HashMap;
31import java.util.HashSet;
32import java.util.List;
33import java.util.Map;
34import java.util.Optional;
35import java.util.Properties;
36import java.util.Set;
37import java.util.concurrent.CompletableFuture;
38import java.util.concurrent.ExecutorService;
39import java.util.concurrent.Executors;
40import java.util.concurrent.atomic.AtomicInteger;
41import java.util.concurrent.atomic.AtomicReference;
42import java.util.stream.Collectors;
43
Jonathan Hart4f178fa2020-02-03 10:46:01 -080044import org.onlab.util.KryoNamespace;
Andrea Campanellacbbb7952019-11-25 06:38:41 +000045import org.onlab.util.Tools;
46import org.onosproject.cfg.ComponentConfigService;
47import org.onosproject.core.ApplicationId;
48import org.onosproject.core.CoreService;
49import org.onosproject.net.DeviceId;
50import org.onosproject.net.flowobjective.ObjectiveError;
51import org.onosproject.net.meter.Band;
52import org.onosproject.net.meter.DefaultBand;
53import org.onosproject.net.meter.DefaultMeterRequest;
54import org.onosproject.net.meter.Meter;
55import org.onosproject.net.meter.MeterContext;
56import org.onosproject.net.meter.MeterEvent;
57import org.onosproject.net.meter.MeterFailReason;
58import org.onosproject.net.meter.MeterId;
59import org.onosproject.net.meter.MeterKey;
60import org.onosproject.net.meter.MeterListener;
61import org.onosproject.net.meter.MeterRequest;
62import org.onosproject.net.meter.MeterService;
Jonathan Hart4f178fa2020-02-03 10:46:01 -080063import org.onosproject.store.serializers.KryoNamespaces;
64import org.onosproject.store.service.ConsistentMultimap;
65import org.onosproject.store.service.Serializer;
66import org.onosproject.store.service.StorageService;
Andrea Campanellacbbb7952019-11-25 06:38:41 +000067import org.opencord.olt.internalapi.AccessDeviceMeterService;
68import org.opencord.sadis.BandwidthProfileInformation;
69import org.osgi.service.component.ComponentContext;
70import org.osgi.service.component.annotations.Activate;
71import org.osgi.service.component.annotations.Component;
72import org.osgi.service.component.annotations.Deactivate;
73import org.osgi.service.component.annotations.Modified;
74import org.osgi.service.component.annotations.Reference;
75import org.osgi.service.component.annotations.ReferenceCardinality;
76import org.slf4j.Logger;
77
Saurav Dasf62cea82020-08-26 17:43:04 -070078import com.google.common.collect.ImmutableMap;
79import com.google.common.collect.ImmutableSet;
Andrea Campanellacbbb7952019-11-25 06:38:41 +000080
81/**
82 * Provisions Meters on access devices.
83 */
84@Component(immediate = true, property = {
85 DELETE_METERS + ":Boolean=" + DELETE_METERS_DEFAULT,
86 })
87public class OltMeterService implements AccessDeviceMeterService {
88
89 @Reference(cardinality = ReferenceCardinality.MANDATORY)
90 protected MeterService meterService;
91
92 @Reference(cardinality = ReferenceCardinality.MANDATORY)
93 protected CoreService coreService;
94
95 @Reference(cardinality = ReferenceCardinality.MANDATORY)
96 protected ComponentConfigService componentConfigService;
97
Jonathan Hart4f178fa2020-02-03 10:46:01 -080098 @Reference(cardinality = ReferenceCardinality.MANDATORY)
99 protected StorageService storageService;
100
Saurav Dasf62cea82020-08-26 17:43:04 -0700101 /**
102 * Delete meters when reference count drops to zero.
103 */
104 protected boolean deleteMeters = DELETE_METERS_DEFAULT;
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000105
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000106 private ApplicationId appId;
107 private static final String APP_NAME = "org.opencord.olt";
108
109 private final MeterListener meterListener = new InternalMeterListener();
110
111 private final Logger log = getLogger(getClass());
112
113 protected ExecutorService eventExecutor;
114
Ilayda Ozdemir90a93622021-02-25 09:40:58 +0000115 protected Map<DeviceId, Set<BandwidthProfileInformation>> pendingMeters;
116 protected Map<DeviceId, Map<MeterKey, AtomicInteger>> pendingRemoveMeters;
117 protected ConsistentMultimap<String, MeterKey> bpInfoToMeter;
Andrea Campanella3ce4d282020-06-09 13:46:58 +0200118
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000119 @Activate
120 public void activate(ComponentContext context) {
121 eventExecutor = Executors.newFixedThreadPool(5, groupedThreads("onos/olt",
122 "events-%d", log));
123 appId = coreService.registerApplication(APP_NAME);
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800124 modified(context);
125
126 KryoNamespace serializer = KryoNamespace.newBuilder()
127 .register(KryoNamespaces.API)
128 .register(MeterKey.class)
Ilayda Ozdemir90a93622021-02-25 09:40:58 +0000129 .register(BandwidthProfileInformation.class)
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800130 .build();
131
132 bpInfoToMeter = storageService.<String, MeterKey>consistentMultimapBuilder()
133 .withName("volt-bp-info-to-meter")
134 .withSerializer(Serializer.using(serializer))
135 .withApplicationId(appId)
136 .build();
137
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000138 meterService.addListener(meterListener);
139 componentConfigService.registerProperties(getClass());
Ilayda Ozdemir90a93622021-02-25 09:40:58 +0000140 pendingMeters = storageService.<DeviceId, Set<BandwidthProfileInformation>>consistentMapBuilder()
141 .withName("volt-pending-meters")
142 .withSerializer(Serializer.using(serializer))
143 .withApplicationId(appId)
144 .build().asJavaMap();
145 pendingRemoveMeters = storageService.<DeviceId, Map<MeterKey, AtomicInteger>>consistentMapBuilder()
146 .withName("volt-pending-remove-meters")
147 .withSerializer(Serializer.using(serializer))
148 .withApplicationId(appId)
149 .build().asJavaMap();
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000150 log.info("Olt Meter service started");
151 }
152
153 @Deactivate
154 public void deactivate() {
155 meterService.removeListener(meterListener);
156 }
157
158
159 @Modified
160 public void modified(ComponentContext context) {
161 Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties();
162
163 Boolean d = Tools.isPropertyEnabled(properties, "deleteMeters");
164 if (d != null) {
165 deleteMeters = d;
166 }
167 }
168
169 @Override
170 public ImmutableMap<String, Collection<MeterKey>> getBpMeterMappings() {
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800171 return bpInfoToMeter.stream()
172 .collect(collectingAndThen(
173 groupingBy(Map.Entry::getKey, mapping(Map.Entry::getValue, toSet())),
174 ImmutableMap::copyOf));
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000175 }
176
Matteo Scandolo19b56f62020-10-29 13:29:21 -0700177 boolean addMeterIdToBpMapping(DeviceId deviceId, MeterId meterId, String bandwidthProfile) {
Andrea Campanella0c3309d2020-05-29 01:51:18 -0700178 log.debug("adding bp {} to meter {} mapping for device {}",
179 bandwidthProfile, meterId, deviceId);
Matteo Scandolo19b56f62020-10-29 13:29:21 -0700180 return bpInfoToMeter.put(bandwidthProfile, MeterKey.key(deviceId, meterId));
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000181 }
182
183 @Override
184 public MeterId getMeterIdFromBpMapping(DeviceId deviceId, String bandwidthProfile) {
yasin saplib4b8ee12021-06-13 18:25:20 +0000185 if (bandwidthProfile == null) {
186 log.warn("Bandwidth Profile requested is null");
187 return null;
188 }
189 if (bpInfoToMeter.get(bandwidthProfile) == null) {
190 log.warn("Bandwidth Profile '{}' is not present in the map",
191 bandwidthProfile);
192 return null;
193 }
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800194 if (bpInfoToMeter.get(bandwidthProfile).value().isEmpty()) {
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000195 log.warn("Bandwidth Profile '{}' is not currently mapped to a meter",
196 bandwidthProfile);
197 return null;
198 }
199
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800200 Optional<? extends MeterKey> meterKeyForDevice = bpInfoToMeter.get(bandwidthProfile).value()
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000201 .stream()
202 .filter(meterKey -> meterKey.deviceId().equals(deviceId))
203 .findFirst();
204 if (meterKeyForDevice.isPresent()) {
Matteo Scandolo19b56f62020-10-29 13:29:21 -0700205 log.debug("Found meter {} for bandwidth profile {} on {}",
206 meterKeyForDevice.get().meterId(), bandwidthProfile, deviceId);
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000207 return meterKeyForDevice.get().meterId();
208 } else {
Matteo Scandolo19b56f62020-10-29 13:29:21 -0700209 log.warn("Bandwidth Profile '{}' is not currently mapped to a meter on {} , {}",
210 bandwidthProfile, deviceId, bpInfoToMeter.get(bandwidthProfile).value());
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000211 return null;
212 }
213 }
214
215 @Override
216 public ImmutableSet<MeterKey> getProgMeters() {
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800217 return bpInfoToMeter.stream()
218 .map(Map.Entry::getValue)
219 .collect(ImmutableSet.toImmutableSet());
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000220 }
221
222 @Override
223 public MeterId createMeter(DeviceId deviceId, BandwidthProfileInformation bpInfo,
224 CompletableFuture<Object> meterFuture) {
Saurav Dasf62cea82020-08-26 17:43:04 -0700225 log.debug("Creating meter on {} for {}", deviceId, bpInfo);
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000226 if (bpInfo == null) {
Matteo Scandolo19b56f62020-10-29 13:29:21 -0700227 log.warn("Requested bandwidth profile on {} information is NULL", deviceId);
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000228 meterFuture.complete(ObjectiveError.BADPARAMS);
229 return null;
230 }
231
232 MeterId meterId = getMeterIdFromBpMapping(deviceId, bpInfo.id());
233 if (meterId != null) {
234 log.debug("Meter {} was previously created for bp {}", meterId, bpInfo.id());
235 meterFuture.complete(null);
236 return meterId;
237 }
238
239 List<Band> meterBands = createMeterBands(bpInfo);
240
241 final AtomicReference<MeterId> meterIdRef = new AtomicReference<>();
242 MeterRequest meterRequest = DefaultMeterRequest.builder()
243 .withBands(meterBands)
244 .withUnit(Meter.Unit.KB_PER_SEC)
245 .withContext(new MeterContext() {
246 @Override
247 public void onSuccess(MeterRequest op) {
Matteo Scandolo19b56f62020-10-29 13:29:21 -0700248 log.debug("Meter {} for {} is installed on the device {}",
249 meterIdRef.get(), bpInfo.id(), deviceId);
250 boolean added = addMeterIdToBpMapping(deviceId, meterIdRef.get(), bpInfo.id());
251 if (added) {
252 meterFuture.complete(null);
253 } else {
254 log.error("Failed to add Meter {} for {} on {} to the meter-bandwidth mapping",
255 meterIdRef.get(), bpInfo.id(), deviceId);
256 meterFuture.complete(ObjectiveError.UNKNOWN);
257 }
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000258 }
259
260 @Override
261 public void onError(MeterRequest op, MeterFailReason reason) {
Andrea Campanellac727a372020-06-09 17:34:38 +0200262 log.error("Failed installing meter {} on {} for {}",
263 meterIdRef.get(), deviceId, bpInfo.id());
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000264 bpInfoToMeter.remove(bpInfo.id(),
265 MeterKey.key(deviceId, meterIdRef.get()));
266 meterFuture.complete(reason);
267 }
268 })
269 .forDevice(deviceId)
270 .fromApp(appId)
271 .burst()
272 .add();
273
274 Meter meter = meterService.submit(meterRequest);
275 meterIdRef.set(meter.id());
Saurav Dasf62cea82020-08-26 17:43:04 -0700276 log.info("Meter {} created and sent for installation on {} for {}",
277 meter.id(), deviceId, bpInfo);
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000278 return meter.id();
279 }
280
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800281 @Override
Andrea Campanella600d2e22020-06-22 11:00:31 +0200282 public void removeFromPendingMeters(DeviceId deviceId, BandwidthProfileInformation bwpInfo) {
283 if (deviceId == null) {
284 return;
285 }
286 pendingMeters.computeIfPresent(deviceId, (id, bwps) -> {
287 bwps.remove(bwpInfo);
288 return bwps;
289 });
Andrea Campanella3ce4d282020-06-09 13:46:58 +0200290 }
291
292 @Override
Andrea Campanellad1e26642020-10-23 12:08:32 +0200293 public synchronized boolean checkAndAddPendingMeter(DeviceId deviceId, BandwidthProfileInformation bwpInfo) {
yasin saplib4b8ee12021-06-13 18:25:20 +0000294 if (bwpInfo == null) {
295 log.debug("Bandwidth profile is null for device: {}", deviceId);
296 return false;
297 }
Andrea Campanellad1e26642020-10-23 12:08:32 +0200298 if (pendingMeters.containsKey(deviceId)
299 && pendingMeters.get(deviceId).contains(bwpInfo)) {
Matteo Scandolo19b56f62020-10-29 13:29:21 -0700300 log.debug("Meter is already pending on {} with bp {}",
Andrea Campanellad1e26642020-10-23 12:08:32 +0200301 deviceId, bwpInfo);
Andrea Campanella600d2e22020-06-22 11:00:31 +0200302 return false;
303 }
Andrea Campanellad1e26642020-10-23 12:08:32 +0200304 log.debug("Adding bandwidth profile {} to pending on {}",
305 bwpInfo, deviceId);
306 pendingMeters.compute(deviceId, (id, bwps) -> {
307 if (bwps == null) {
308 bwps = new HashSet<>();
309 }
310 bwps.add(bwpInfo);
311 return bwps;
312 });
313
314 return true;
Andrea Campanella3ce4d282020-06-09 13:46:58 +0200315 }
316
317 @Override
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800318 public void clearMeters(DeviceId deviceId) {
Andrea Campanella65487ba2020-06-17 11:31:30 +0200319 log.debug("Removing all meters for device {}", deviceId);
Andrea Campanella600d2e22020-06-22 11:00:31 +0200320 clearDeviceState(deviceId);
Andrea Campanella65487ba2020-06-17 11:31:30 +0200321 meterService.purgeMeters(deviceId);
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800322 }
323
Andrea Campanella600d2e22020-06-22 11:00:31 +0200324 @Override
325 public void clearDeviceState(DeviceId deviceId) {
326 log.info("Clearing local device state for {}", deviceId);
327 pendingRemoveMeters.remove(deviceId);
328 removeMetersFromBpMapping(deviceId);
329 //Following call handles cornercase of OLT delete during meter provisioning
330 pendingMeters.remove(deviceId);
331 }
332
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000333 private List<Band> createMeterBands(BandwidthProfileInformation bpInfo) {
334 List<Band> meterBands = new ArrayList<>();
335
Gamze Abakaf46ab432021-03-03 10:51:17 +0000336 // add cir
337 if (bpInfo.committedInformationRate() != 0) {
338 meterBands.add(createMeterBand(bpInfo.committedInformationRate(), bpInfo.committedBurstSize()));
339 }
340
341 // check if both air and gir are set together in sadis
342 // if they are, set air to 0
343 if (bpInfo.assuredInformationRate() != 0 && bpInfo.guaranteedInformationRate() != 0) {
344 bpInfo.setAssuredInformationRate(0);
345 }
346
347 // add pir
348 long pir = bpInfo.peakInformationRate() != 0 ? bpInfo.peakInformationRate() : (bpInfo.exceededInformationRate()
349 + bpInfo.committedInformationRate() + bpInfo.guaranteedInformationRate()
350 + bpInfo.assuredInformationRate());
351
352 Long pbs = bpInfo.peakBurstSize() != null ? bpInfo.peakBurstSize() :
353 (bpInfo.exceededBurstSize() != null ? bpInfo.exceededBurstSize() : 0) +
354 (bpInfo.committedBurstSize() != null ? bpInfo.committedBurstSize() : 0);
355
356 meterBands.add(createMeterBand(pir, pbs));
357
358 // add gir
359 if (bpInfo.guaranteedInformationRate() != 0) {
360 meterBands.add(createMeterBand(bpInfo.guaranteedInformationRate(), 0L));
361 }
362
363 // add air
364 // air is used in place of gir only if gir is
365 // not present and air is not 0, see line 330.
366 // Included for backwards compatibility, will be removed in VOLTHA 2.9.
367 if (bpInfo.assuredInformationRate() != 0) {
368 meterBands.add(createMeterBand(bpInfo.assuredInformationRate(), 0L));
369 }
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000370
371 return meterBands;
372 }
373
374 private Band createMeterBand(long rate, Long burst) {
375 return DefaultBand.builder()
376 .withRate(rate) //already Kbps
377 .burstSize(burst) // already Kbits
378 .ofType(Band.Type.DROP) // no matter
379 .build();
380 }
381
Andrea Campanella600d2e22020-06-22 11:00:31 +0200382 private void removeMeterFromBpMapping(MeterKey meterKey) {
383 List<Map.Entry<String, MeterKey>> meters = bpInfoToMeter.stream()
384 .filter(e -> e.getValue().equals(meterKey))
385 .collect(Collectors.toList());
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000386
Andrea Campanella600d2e22020-06-22 11:00:31 +0200387 meters.forEach(e -> bpInfoToMeter.remove(e.getKey(), e.getValue()));
388 }
389
390 private void removeMetersFromBpMapping(DeviceId deviceId) {
391 List<Map.Entry<String, MeterKey>> meters = bpInfoToMeter.stream()
392 .filter(e -> e.getValue().deviceId().equals(deviceId))
393 .collect(Collectors.toList());
394
395 meters.forEach(e -> bpInfoToMeter.remove(e.getKey(), e.getValue()));
396 }
397
398 private class InternalMeterListener implements MeterListener {
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000399
400 @Override
401 public void event(MeterEvent meterEvent) {
402 eventExecutor.execute(() -> {
403 Meter meter = meterEvent.subject();
404 if (meter == null) {
405 log.error("Meter in event {} is null", meterEvent);
406 return;
407 }
408 MeterKey key = MeterKey.key(meter.deviceId(), meter.id());
409 if (deleteMeters && MeterEvent.Type.METER_REFERENCE_COUNT_ZERO.equals(meterEvent.type())) {
Andrea Campanella600d2e22020-06-22 11:00:31 +0200410 log.info("Zero Count Meter Event is received. Meter is {} on {}",
411 meter.id(), meter.deviceId());
412 incrementMeterCount(meter.deviceId(), key);
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000413
Andrea Campanella600d2e22020-06-22 11:00:31 +0200414 if (appId.equals(meter.appId()) && pendingRemoveMeters.get(meter.deviceId())
415 .get(key).get() == 3) {
416 log.info("Deleting unreferenced, no longer programmed Meter {} on {}",
417 meter.id(), meter.deviceId());
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000418 deleteMeter(meter.deviceId(), meter.id());
419 }
420 }
421 if (MeterEvent.Type.METER_REMOVED.equals(meterEvent.type())) {
Andrea Campanella600d2e22020-06-22 11:00:31 +0200422 log.info("Meter Removed Event is received for {} on {}",
423 meter.id(), meter.deviceId());
424 pendingRemoveMeters.computeIfPresent(meter.deviceId(),
425 (id, meters) -> {
426 if (meters.get(key) == null) {
427 log.info("Meters is not pending " +
428 "{} on {}", key, id);
429 return meters;
430 }
431 meters.remove(key);
432 return meters;
433 });
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000434 removeMeterFromBpMapping(key);
435 }
436 });
437 }
438
Andrea Campanella600d2e22020-06-22 11:00:31 +0200439 private void incrementMeterCount(DeviceId deviceId, MeterKey key) {
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000440 if (key == null) {
441 return;
442 }
Andrea Campanella600d2e22020-06-22 11:00:31 +0200443 pendingRemoveMeters.compute(deviceId,
444 (id, meters) -> {
445 if (meters == null) {
446 meters = new HashMap<>();
447
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000448 }
Andrea Campanella600d2e22020-06-22 11:00:31 +0200449 if (meters.get(key) == null) {
450 meters.put(key, new AtomicInteger(1));
451 }
452 meters.get(key).addAndGet(1);
453 return meters;
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000454 });
455 }
456
457 private void deleteMeter(DeviceId deviceId, MeterId meterId) {
458 Meter meter = meterService.getMeter(deviceId, meterId);
459 if (meter != null) {
460 MeterRequest meterRequest = DefaultMeterRequest.builder()
461 .withBands(meter.bands())
462 .withUnit(meter.unit())
463 .forDevice(deviceId)
464 .fromApp(appId)
465 .burst()
466 .remove();
467
468 meterService.withdraw(meterRequest, meterId);
469 }
470 }
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000471 }
472}