blob: d57f3c1d3168cf78bc6acf3264ee2eb9b62c1beb [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;
80import com.google.common.collect.Maps;
Andrea Campanellacbbb7952019-11-25 06:38:41 +000081
82/**
83 * Provisions Meters on access devices.
84 */
85@Component(immediate = true, property = {
86 DELETE_METERS + ":Boolean=" + DELETE_METERS_DEFAULT,
87 })
88public class OltMeterService implements AccessDeviceMeterService {
89
90 @Reference(cardinality = ReferenceCardinality.MANDATORY)
91 protected MeterService meterService;
92
93 @Reference(cardinality = ReferenceCardinality.MANDATORY)
94 protected CoreService coreService;
95
96 @Reference(cardinality = ReferenceCardinality.MANDATORY)
97 protected ComponentConfigService componentConfigService;
98
Jonathan Hart4f178fa2020-02-03 10:46:01 -080099 @Reference(cardinality = ReferenceCardinality.MANDATORY)
100 protected StorageService storageService;
101
Saurav Dasf62cea82020-08-26 17:43:04 -0700102 /**
103 * Delete meters when reference count drops to zero.
104 */
105 protected boolean deleteMeters = DELETE_METERS_DEFAULT;
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000106
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000107 private ApplicationId appId;
108 private static final String APP_NAME = "org.opencord.olt";
109
110 private final MeterListener meterListener = new InternalMeterListener();
111
112 private final Logger log = getLogger(getClass());
113
114 protected ExecutorService eventExecutor;
115
Andrea Campanella600d2e22020-06-22 11:00:31 +0200116 private Map<DeviceId, Set<BandwidthProfileInformation>> pendingMeters;
117 private Map<DeviceId, Map<MeterKey, AtomicInteger>> pendingRemoveMeters;
118 ConsistentMultimap<String, MeterKey> bpInfoToMeter;
Andrea Campanella3ce4d282020-06-09 13:46:58 +0200119
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000120 @Activate
121 public void activate(ComponentContext context) {
122 eventExecutor = Executors.newFixedThreadPool(5, groupedThreads("onos/olt",
123 "events-%d", log));
124 appId = coreService.registerApplication(APP_NAME);
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800125 modified(context);
126
127 KryoNamespace serializer = KryoNamespace.newBuilder()
128 .register(KryoNamespaces.API)
129 .register(MeterKey.class)
130 .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());
Andrea Campanella600d2e22020-06-22 11:00:31 +0200140 pendingMeters = Maps.newConcurrentMap();
141 pendingRemoveMeters = Maps.newConcurrentMap();
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000142 log.info("Olt Meter service started");
143 }
144
145 @Deactivate
146 public void deactivate() {
147 meterService.removeListener(meterListener);
148 }
149
150
151 @Modified
152 public void modified(ComponentContext context) {
153 Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties();
154
155 Boolean d = Tools.isPropertyEnabled(properties, "deleteMeters");
156 if (d != null) {
157 deleteMeters = d;
158 }
159 }
160
161 @Override
162 public ImmutableMap<String, Collection<MeterKey>> getBpMeterMappings() {
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800163 return bpInfoToMeter.stream()
164 .collect(collectingAndThen(
165 groupingBy(Map.Entry::getKey, mapping(Map.Entry::getValue, toSet())),
166 ImmutableMap::copyOf));
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000167 }
168
Matteo Scandolo19b56f62020-10-29 13:29:21 -0700169 boolean addMeterIdToBpMapping(DeviceId deviceId, MeterId meterId, String bandwidthProfile) {
Andrea Campanella0c3309d2020-05-29 01:51:18 -0700170 log.debug("adding bp {} to meter {} mapping for device {}",
171 bandwidthProfile, meterId, deviceId);
Matteo Scandolo19b56f62020-10-29 13:29:21 -0700172 return bpInfoToMeter.put(bandwidthProfile, MeterKey.key(deviceId, meterId));
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000173 }
174
175 @Override
176 public MeterId getMeterIdFromBpMapping(DeviceId deviceId, String bandwidthProfile) {
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800177 if (bpInfoToMeter.get(bandwidthProfile).value().isEmpty()) {
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000178 log.warn("Bandwidth Profile '{}' is not currently mapped to a meter",
179 bandwidthProfile);
180 return null;
181 }
182
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800183 Optional<? extends MeterKey> meterKeyForDevice = bpInfoToMeter.get(bandwidthProfile).value()
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000184 .stream()
185 .filter(meterKey -> meterKey.deviceId().equals(deviceId))
186 .findFirst();
187 if (meterKeyForDevice.isPresent()) {
Matteo Scandolo19b56f62020-10-29 13:29:21 -0700188 log.debug("Found meter {} for bandwidth profile {} on {}",
189 meterKeyForDevice.get().meterId(), bandwidthProfile, deviceId);
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000190 return meterKeyForDevice.get().meterId();
191 } else {
Matteo Scandolo19b56f62020-10-29 13:29:21 -0700192 log.warn("Bandwidth Profile '{}' is not currently mapped to a meter on {} , {}",
193 bandwidthProfile, deviceId, bpInfoToMeter.get(bandwidthProfile).value());
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000194 return null;
195 }
196 }
197
198 @Override
199 public ImmutableSet<MeterKey> getProgMeters() {
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800200 return bpInfoToMeter.stream()
201 .map(Map.Entry::getValue)
202 .collect(ImmutableSet.toImmutableSet());
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000203 }
204
205 @Override
206 public MeterId createMeter(DeviceId deviceId, BandwidthProfileInformation bpInfo,
207 CompletableFuture<Object> meterFuture) {
Saurav Dasf62cea82020-08-26 17:43:04 -0700208 log.debug("Creating meter on {} for {}", deviceId, bpInfo);
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000209 if (bpInfo == null) {
Matteo Scandolo19b56f62020-10-29 13:29:21 -0700210 log.warn("Requested bandwidth profile on {} information is NULL", deviceId);
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000211 meterFuture.complete(ObjectiveError.BADPARAMS);
212 return null;
213 }
214
215 MeterId meterId = getMeterIdFromBpMapping(deviceId, bpInfo.id());
216 if (meterId != null) {
217 log.debug("Meter {} was previously created for bp {}", meterId, bpInfo.id());
218 meterFuture.complete(null);
219 return meterId;
220 }
221
222 List<Band> meterBands = createMeterBands(bpInfo);
223
224 final AtomicReference<MeterId> meterIdRef = new AtomicReference<>();
225 MeterRequest meterRequest = DefaultMeterRequest.builder()
226 .withBands(meterBands)
227 .withUnit(Meter.Unit.KB_PER_SEC)
228 .withContext(new MeterContext() {
229 @Override
230 public void onSuccess(MeterRequest op) {
Matteo Scandolo19b56f62020-10-29 13:29:21 -0700231 log.debug("Meter {} for {} is installed on the device {}",
232 meterIdRef.get(), bpInfo.id(), deviceId);
233 boolean added = addMeterIdToBpMapping(deviceId, meterIdRef.get(), bpInfo.id());
234 if (added) {
235 meterFuture.complete(null);
236 } else {
237 log.error("Failed to add Meter {} for {} on {} to the meter-bandwidth mapping",
238 meterIdRef.get(), bpInfo.id(), deviceId);
239 meterFuture.complete(ObjectiveError.UNKNOWN);
240 }
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000241 }
242
243 @Override
244 public void onError(MeterRequest op, MeterFailReason reason) {
Andrea Campanellac727a372020-06-09 17:34:38 +0200245 log.error("Failed installing meter {} on {} for {}",
246 meterIdRef.get(), deviceId, bpInfo.id());
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000247 bpInfoToMeter.remove(bpInfo.id(),
248 MeterKey.key(deviceId, meterIdRef.get()));
249 meterFuture.complete(reason);
250 }
251 })
252 .forDevice(deviceId)
253 .fromApp(appId)
254 .burst()
255 .add();
256
257 Meter meter = meterService.submit(meterRequest);
258 meterIdRef.set(meter.id());
Saurav Dasf62cea82020-08-26 17:43:04 -0700259 log.info("Meter {} created and sent for installation on {} for {}",
260 meter.id(), deviceId, bpInfo);
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000261 return meter.id();
262 }
263
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800264 @Override
Andrea Campanella600d2e22020-06-22 11:00:31 +0200265 public void removeFromPendingMeters(DeviceId deviceId, BandwidthProfileInformation bwpInfo) {
266 if (deviceId == null) {
267 return;
268 }
269 pendingMeters.computeIfPresent(deviceId, (id, bwps) -> {
270 bwps.remove(bwpInfo);
271 return bwps;
272 });
Andrea Campanella3ce4d282020-06-09 13:46:58 +0200273 }
274
275 @Override
Andrea Campanellad1e26642020-10-23 12:08:32 +0200276 public synchronized boolean checkAndAddPendingMeter(DeviceId deviceId, BandwidthProfileInformation bwpInfo) {
277 if (pendingMeters.containsKey(deviceId)
278 && pendingMeters.get(deviceId).contains(bwpInfo)) {
Matteo Scandolo19b56f62020-10-29 13:29:21 -0700279 log.debug("Meter is already pending on {} with bp {}",
Andrea Campanellad1e26642020-10-23 12:08:32 +0200280 deviceId, bwpInfo);
Andrea Campanella600d2e22020-06-22 11:00:31 +0200281 return false;
282 }
Andrea Campanellad1e26642020-10-23 12:08:32 +0200283 log.debug("Adding bandwidth profile {} to pending on {}",
284 bwpInfo, deviceId);
285 pendingMeters.compute(deviceId, (id, bwps) -> {
286 if (bwps == null) {
287 bwps = new HashSet<>();
288 }
289 bwps.add(bwpInfo);
290 return bwps;
291 });
292
293 return true;
Andrea Campanella3ce4d282020-06-09 13:46:58 +0200294 }
295
296 @Override
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800297 public void clearMeters(DeviceId deviceId) {
Andrea Campanella65487ba2020-06-17 11:31:30 +0200298 log.debug("Removing all meters for device {}", deviceId);
Andrea Campanella600d2e22020-06-22 11:00:31 +0200299 clearDeviceState(deviceId);
Andrea Campanella65487ba2020-06-17 11:31:30 +0200300 meterService.purgeMeters(deviceId);
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800301 }
302
Andrea Campanella600d2e22020-06-22 11:00:31 +0200303 @Override
304 public void clearDeviceState(DeviceId deviceId) {
305 log.info("Clearing local device state for {}", deviceId);
306 pendingRemoveMeters.remove(deviceId);
307 removeMetersFromBpMapping(deviceId);
308 //Following call handles cornercase of OLT delete during meter provisioning
309 pendingMeters.remove(deviceId);
310 }
311
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000312 private List<Band> createMeterBands(BandwidthProfileInformation bpInfo) {
313 List<Band> meterBands = new ArrayList<>();
314
315 meterBands.add(createMeterBand(bpInfo.committedInformationRate(), bpInfo.committedBurstSize()));
316 meterBands.add(createMeterBand(bpInfo.exceededInformationRate(), bpInfo.exceededBurstSize()));
317 meterBands.add(createMeterBand(bpInfo.assuredInformationRate(), 0L));
318
319 return meterBands;
320 }
321
322 private Band createMeterBand(long rate, Long burst) {
323 return DefaultBand.builder()
324 .withRate(rate) //already Kbps
325 .burstSize(burst) // already Kbits
326 .ofType(Band.Type.DROP) // no matter
327 .build();
328 }
329
Andrea Campanella600d2e22020-06-22 11:00:31 +0200330 private void removeMeterFromBpMapping(MeterKey meterKey) {
331 List<Map.Entry<String, MeterKey>> meters = bpInfoToMeter.stream()
332 .filter(e -> e.getValue().equals(meterKey))
333 .collect(Collectors.toList());
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000334
Andrea Campanella600d2e22020-06-22 11:00:31 +0200335 meters.forEach(e -> bpInfoToMeter.remove(e.getKey(), e.getValue()));
336 }
337
338 private void removeMetersFromBpMapping(DeviceId deviceId) {
339 List<Map.Entry<String, MeterKey>> meters = bpInfoToMeter.stream()
340 .filter(e -> e.getValue().deviceId().equals(deviceId))
341 .collect(Collectors.toList());
342
343 meters.forEach(e -> bpInfoToMeter.remove(e.getKey(), e.getValue()));
344 }
345
346 private class InternalMeterListener implements MeterListener {
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000347
348 @Override
349 public void event(MeterEvent meterEvent) {
350 eventExecutor.execute(() -> {
351 Meter meter = meterEvent.subject();
352 if (meter == null) {
353 log.error("Meter in event {} is null", meterEvent);
354 return;
355 }
356 MeterKey key = MeterKey.key(meter.deviceId(), meter.id());
357 if (deleteMeters && MeterEvent.Type.METER_REFERENCE_COUNT_ZERO.equals(meterEvent.type())) {
Andrea Campanella600d2e22020-06-22 11:00:31 +0200358 log.info("Zero Count Meter Event is received. Meter is {} on {}",
359 meter.id(), meter.deviceId());
360 incrementMeterCount(meter.deviceId(), key);
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000361
Andrea Campanella600d2e22020-06-22 11:00:31 +0200362 if (appId.equals(meter.appId()) && pendingRemoveMeters.get(meter.deviceId())
363 .get(key).get() == 3) {
364 log.info("Deleting unreferenced, no longer programmed Meter {} on {}",
365 meter.id(), meter.deviceId());
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000366 deleteMeter(meter.deviceId(), meter.id());
367 }
368 }
369 if (MeterEvent.Type.METER_REMOVED.equals(meterEvent.type())) {
Andrea Campanella600d2e22020-06-22 11:00:31 +0200370 log.info("Meter Removed Event is received for {} on {}",
371 meter.id(), meter.deviceId());
372 pendingRemoveMeters.computeIfPresent(meter.deviceId(),
373 (id, meters) -> {
374 if (meters.get(key) == null) {
375 log.info("Meters is not pending " +
376 "{} on {}", key, id);
377 return meters;
378 }
379 meters.remove(key);
380 return meters;
381 });
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000382 removeMeterFromBpMapping(key);
383 }
384 });
385 }
386
Andrea Campanella600d2e22020-06-22 11:00:31 +0200387 private void incrementMeterCount(DeviceId deviceId, MeterKey key) {
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000388 if (key == null) {
389 return;
390 }
Andrea Campanella600d2e22020-06-22 11:00:31 +0200391 pendingRemoveMeters.compute(deviceId,
392 (id, meters) -> {
393 if (meters == null) {
394 meters = new HashMap<>();
395
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000396 }
Andrea Campanella600d2e22020-06-22 11:00:31 +0200397 if (meters.get(key) == null) {
398 meters.put(key, new AtomicInteger(1));
399 }
400 meters.get(key).addAndGet(1);
401 return meters;
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000402 });
403 }
404
405 private void deleteMeter(DeviceId deviceId, MeterId meterId) {
406 Meter meter = meterService.getMeter(deviceId, meterId);
407 if (meter != null) {
408 MeterRequest meterRequest = DefaultMeterRequest.builder()
409 .withBands(meter.bands())
410 .withUnit(meter.unit())
411 .forDevice(deviceId)
412 .fromApp(appId)
413 .burst()
414 .remove();
415
416 meterService.withdraw(meterRequest, meterId);
417 }
418 }
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000419 }
420}