blob: fc56066b508d8c9f10d821c7029f8e20913ee4f1 [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
Andrea Campanellacbbb7952019-11-25 06:38:41 +000018import com.google.common.collect.ImmutableMap;
19import com.google.common.collect.ImmutableSet;
20import com.google.common.collect.Maps;
Jonathan Hart4f178fa2020-02-03 10:46:01 -080021import org.onlab.util.KryoNamespace;
Andrea Campanellacbbb7952019-11-25 06:38:41 +000022import org.onlab.util.Tools;
23import org.onosproject.cfg.ComponentConfigService;
24import org.onosproject.core.ApplicationId;
25import org.onosproject.core.CoreService;
26import org.onosproject.net.DeviceId;
27import org.onosproject.net.flowobjective.ObjectiveError;
28import org.onosproject.net.meter.Band;
29import org.onosproject.net.meter.DefaultBand;
30import org.onosproject.net.meter.DefaultMeterRequest;
31import org.onosproject.net.meter.Meter;
32import org.onosproject.net.meter.MeterContext;
33import org.onosproject.net.meter.MeterEvent;
34import org.onosproject.net.meter.MeterFailReason;
35import org.onosproject.net.meter.MeterId;
36import org.onosproject.net.meter.MeterKey;
37import org.onosproject.net.meter.MeterListener;
38import org.onosproject.net.meter.MeterRequest;
39import org.onosproject.net.meter.MeterService;
Jonathan Hart4f178fa2020-02-03 10:46:01 -080040import org.onosproject.store.serializers.KryoNamespaces;
41import org.onosproject.store.service.ConsistentMultimap;
42import org.onosproject.store.service.Serializer;
43import org.onosproject.store.service.StorageService;
Andrea Campanellacbbb7952019-11-25 06:38:41 +000044import org.opencord.olt.internalapi.AccessDeviceMeterService;
45import org.opencord.sadis.BandwidthProfileInformation;
46import org.osgi.service.component.ComponentContext;
47import org.osgi.service.component.annotations.Activate;
48import org.osgi.service.component.annotations.Component;
49import org.osgi.service.component.annotations.Deactivate;
50import org.osgi.service.component.annotations.Modified;
51import org.osgi.service.component.annotations.Reference;
52import org.osgi.service.component.annotations.ReferenceCardinality;
53import org.slf4j.Logger;
54
55import java.util.ArrayList;
56import java.util.Collection;
57import java.util.Dictionary;
Andrea Campanella600d2e22020-06-22 11:00:31 +020058import java.util.HashMap;
59import java.util.HashSet;
Andrea Campanellacbbb7952019-11-25 06:38:41 +000060import java.util.List;
61import java.util.Map;
62import java.util.Optional;
63import java.util.Properties;
Andrea Campanella3ce4d282020-06-09 13:46:58 +020064import java.util.Set;
Andrea Campanellacbbb7952019-11-25 06:38:41 +000065import java.util.concurrent.CompletableFuture;
66import java.util.concurrent.ExecutorService;
67import java.util.concurrent.Executors;
68import java.util.concurrent.atomic.AtomicInteger;
69import java.util.concurrent.atomic.AtomicReference;
Jonathan Hart4f178fa2020-02-03 10:46:01 -080070import java.util.stream.Collectors;
Andrea Campanellacbbb7952019-11-25 06:38:41 +000071
Jonathan Hart4f178fa2020-02-03 10:46:01 -080072import static java.util.stream.Collectors.collectingAndThen;
73import static java.util.stream.Collectors.groupingBy;
74import static java.util.stream.Collectors.mapping;
75import static java.util.stream.Collectors.toSet;
Andrea Campanellacbbb7952019-11-25 06:38:41 +000076import static org.onlab.util.Tools.groupedThreads;
77import static org.opencord.olt.impl.OsgiPropertyConstants.DELETE_METERS;
78import static org.opencord.olt.impl.OsgiPropertyConstants.DELETE_METERS_DEFAULT;
79import static org.slf4j.LoggerFactory.getLogger;
80
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
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000101 protected boolean deleteMeters = true;
102
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000103 private ApplicationId appId;
104 private static final String APP_NAME = "org.opencord.olt";
105
106 private final MeterListener meterListener = new InternalMeterListener();
107
108 private final Logger log = getLogger(getClass());
109
110 protected ExecutorService eventExecutor;
111
Andrea Campanella600d2e22020-06-22 11:00:31 +0200112 private Map<DeviceId, Set<BandwidthProfileInformation>> pendingMeters;
113 private Map<DeviceId, Map<MeterKey, AtomicInteger>> pendingRemoveMeters;
114 ConsistentMultimap<String, MeterKey> bpInfoToMeter;
Andrea Campanella3ce4d282020-06-09 13:46:58 +0200115
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000116 @Activate
117 public void activate(ComponentContext context) {
118 eventExecutor = Executors.newFixedThreadPool(5, groupedThreads("onos/olt",
119 "events-%d", log));
120 appId = coreService.registerApplication(APP_NAME);
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800121 modified(context);
122
123 KryoNamespace serializer = KryoNamespace.newBuilder()
124 .register(KryoNamespaces.API)
125 .register(MeterKey.class)
126 .build();
127
128 bpInfoToMeter = storageService.<String, MeterKey>consistentMultimapBuilder()
129 .withName("volt-bp-info-to-meter")
130 .withSerializer(Serializer.using(serializer))
131 .withApplicationId(appId)
132 .build();
133
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000134 meterService.addListener(meterListener);
135 componentConfigService.registerProperties(getClass());
Andrea Campanella600d2e22020-06-22 11:00:31 +0200136 pendingMeters = Maps.newConcurrentMap();
137 pendingRemoveMeters = Maps.newConcurrentMap();
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000138 log.info("Olt Meter service started");
139 }
140
141 @Deactivate
142 public void deactivate() {
143 meterService.removeListener(meterListener);
144 }
145
146
147 @Modified
148 public void modified(ComponentContext context) {
149 Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties();
150
151 Boolean d = Tools.isPropertyEnabled(properties, "deleteMeters");
152 if (d != null) {
153 deleteMeters = d;
154 }
155 }
156
157 @Override
158 public ImmutableMap<String, Collection<MeterKey>> getBpMeterMappings() {
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800159 return bpInfoToMeter.stream()
160 .collect(collectingAndThen(
161 groupingBy(Map.Entry::getKey, mapping(Map.Entry::getValue, toSet())),
162 ImmutableMap::copyOf));
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000163 }
164
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800165 void addMeterIdToBpMapping(DeviceId deviceId, MeterId meterId, String bandwidthProfile) {
Andrea Campanella0c3309d2020-05-29 01:51:18 -0700166 log.debug("adding bp {} to meter {} mapping for device {}",
167 bandwidthProfile, meterId, deviceId);
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000168 bpInfoToMeter.put(bandwidthProfile, MeterKey.key(deviceId, meterId));
169 }
170
171 @Override
172 public MeterId getMeterIdFromBpMapping(DeviceId deviceId, String bandwidthProfile) {
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800173 if (bpInfoToMeter.get(bandwidthProfile).value().isEmpty()) {
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000174 log.warn("Bandwidth Profile '{}' is not currently mapped to a meter",
175 bandwidthProfile);
176 return null;
177 }
178
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800179 Optional<? extends MeterKey> meterKeyForDevice = bpInfoToMeter.get(bandwidthProfile).value()
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000180 .stream()
181 .filter(meterKey -> meterKey.deviceId().equals(deviceId))
182 .findFirst();
183 if (meterKeyForDevice.isPresent()) {
184 log.debug("Found meter {} for bandwidth profile {}",
185 meterKeyForDevice.get().meterId(), bandwidthProfile);
186 return meterKeyForDevice.get().meterId();
187 } else {
Andrea Campanella0c3309d2020-05-29 01:51:18 -0700188 log.warn("Bandwidth Profile '{}' is not currently mapped to a meter in {}",
189 bandwidthProfile, bpInfoToMeter.get(bandwidthProfile).value());
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000190 return null;
191 }
192 }
193
194 @Override
195 public ImmutableSet<MeterKey> getProgMeters() {
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800196 return bpInfoToMeter.stream()
197 .map(Map.Entry::getValue)
198 .collect(ImmutableSet.toImmutableSet());
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000199 }
200
201 @Override
202 public MeterId createMeter(DeviceId deviceId, BandwidthProfileInformation bpInfo,
203 CompletableFuture<Object> meterFuture) {
Andrea Campanella3ce4d282020-06-09 13:46:58 +0200204 log.debug("Installing meter on {} for {}", deviceId, bpInfo);
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000205 if (bpInfo == null) {
206 log.warn("Requested bandwidth profile information is NULL");
207 meterFuture.complete(ObjectiveError.BADPARAMS);
208 return null;
209 }
210
211 MeterId meterId = getMeterIdFromBpMapping(deviceId, bpInfo.id());
212 if (meterId != null) {
213 log.debug("Meter {} was previously created for bp {}", meterId, bpInfo.id());
214 meterFuture.complete(null);
215 return meterId;
216 }
217
218 List<Band> meterBands = createMeterBands(bpInfo);
219
220 final AtomicReference<MeterId> meterIdRef = new AtomicReference<>();
221 MeterRequest meterRequest = DefaultMeterRequest.builder()
222 .withBands(meterBands)
223 .withUnit(Meter.Unit.KB_PER_SEC)
224 .withContext(new MeterContext() {
225 @Override
226 public void onSuccess(MeterRequest op) {
Andrea Campanella0c3309d2020-05-29 01:51:18 -0700227 log.debug("Meter {} is installed on the device {}",
Andrea Campanella3ce4d282020-06-09 13:46:58 +0200228 meterIdRef.get(), deviceId);
Andrea Campanella0c3309d2020-05-29 01:51:18 -0700229 addMeterIdToBpMapping(deviceId, meterIdRef.get(), bpInfo.id());
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000230 meterFuture.complete(null);
231 }
232
233 @Override
234 public void onError(MeterRequest op, MeterFailReason reason) {
Andrea Campanellac727a372020-06-09 17:34:38 +0200235 log.error("Failed installing meter {} on {} for {}",
236 meterIdRef.get(), deviceId, bpInfo.id());
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000237 bpInfoToMeter.remove(bpInfo.id(),
238 MeterKey.key(deviceId, meterIdRef.get()));
239 meterFuture.complete(reason);
240 }
241 })
242 .forDevice(deviceId)
243 .fromApp(appId)
244 .burst()
245 .add();
246
247 Meter meter = meterService.submit(meterRequest);
248 meterIdRef.set(meter.id());
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000249 log.info("Meter is created. Meter Id {}", meter.id());
250 return meter.id();
251 }
252
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800253 @Override
Andrea Campanella600d2e22020-06-22 11:00:31 +0200254 public void addToPendingMeters(DeviceId deviceId, BandwidthProfileInformation bwpInfo) {
255 if (deviceId == null) {
256 return;
257 }
258 pendingMeters.compute(deviceId, (id, bwps) -> {
259 if (bwps == null) {
260 bwps = new HashSet<>();
261 }
262 bwps.add(bwpInfo);
263 return bwps;
264 });
Andrea Campanella3ce4d282020-06-09 13:46:58 +0200265 }
266
267 @Override
Andrea Campanella600d2e22020-06-22 11:00:31 +0200268 public void removeFromPendingMeters(DeviceId deviceId, BandwidthProfileInformation bwpInfo) {
269 if (deviceId == null) {
270 return;
271 }
272 pendingMeters.computeIfPresent(deviceId, (id, bwps) -> {
273 bwps.remove(bwpInfo);
274 return bwps;
275 });
Andrea Campanella3ce4d282020-06-09 13:46:58 +0200276 }
277
278 @Override
Andrea Campanella600d2e22020-06-22 11:00:31 +0200279 public boolean isMeterPending(DeviceId deviceId, BandwidthProfileInformation bwpInfo) {
280 if (!pendingMeters.containsKey(deviceId)) {
281 return false;
282 }
283 return pendingMeters.get(deviceId).contains(bwpInfo);
Andrea Campanella3ce4d282020-06-09 13:46:58 +0200284 }
285
286 @Override
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800287 public void clearMeters(DeviceId deviceId) {
Andrea Campanella65487ba2020-06-17 11:31:30 +0200288 log.debug("Removing all meters for device {}", deviceId);
Andrea Campanella600d2e22020-06-22 11:00:31 +0200289 clearDeviceState(deviceId);
Andrea Campanella65487ba2020-06-17 11:31:30 +0200290 meterService.purgeMeters(deviceId);
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800291 }
292
Andrea Campanella600d2e22020-06-22 11:00:31 +0200293 @Override
294 public void clearDeviceState(DeviceId deviceId) {
295 log.info("Clearing local device state for {}", deviceId);
296 pendingRemoveMeters.remove(deviceId);
297 removeMetersFromBpMapping(deviceId);
298 //Following call handles cornercase of OLT delete during meter provisioning
299 pendingMeters.remove(deviceId);
300 }
301
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000302 private List<Band> createMeterBands(BandwidthProfileInformation bpInfo) {
303 List<Band> meterBands = new ArrayList<>();
304
305 meterBands.add(createMeterBand(bpInfo.committedInformationRate(), bpInfo.committedBurstSize()));
306 meterBands.add(createMeterBand(bpInfo.exceededInformationRate(), bpInfo.exceededBurstSize()));
307 meterBands.add(createMeterBand(bpInfo.assuredInformationRate(), 0L));
308
309 return meterBands;
310 }
311
312 private Band createMeterBand(long rate, Long burst) {
313 return DefaultBand.builder()
314 .withRate(rate) //already Kbps
315 .burstSize(burst) // already Kbits
316 .ofType(Band.Type.DROP) // no matter
317 .build();
318 }
319
Andrea Campanella600d2e22020-06-22 11:00:31 +0200320 private void removeMeterFromBpMapping(MeterKey meterKey) {
321 List<Map.Entry<String, MeterKey>> meters = bpInfoToMeter.stream()
322 .filter(e -> e.getValue().equals(meterKey))
323 .collect(Collectors.toList());
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000324
Andrea Campanella600d2e22020-06-22 11:00:31 +0200325 meters.forEach(e -> bpInfoToMeter.remove(e.getKey(), e.getValue()));
326 }
327
328 private void removeMetersFromBpMapping(DeviceId deviceId) {
329 List<Map.Entry<String, MeterKey>> meters = bpInfoToMeter.stream()
330 .filter(e -> e.getValue().deviceId().equals(deviceId))
331 .collect(Collectors.toList());
332
333 meters.forEach(e -> bpInfoToMeter.remove(e.getKey(), e.getValue()));
334 }
335
336 private class InternalMeterListener implements MeterListener {
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000337
338 @Override
339 public void event(MeterEvent meterEvent) {
340 eventExecutor.execute(() -> {
341 Meter meter = meterEvent.subject();
342 if (meter == null) {
343 log.error("Meter in event {} is null", meterEvent);
344 return;
345 }
346 MeterKey key = MeterKey.key(meter.deviceId(), meter.id());
347 if (deleteMeters && MeterEvent.Type.METER_REFERENCE_COUNT_ZERO.equals(meterEvent.type())) {
Andrea Campanella600d2e22020-06-22 11:00:31 +0200348 log.info("Zero Count Meter Event is received. Meter is {} on {}",
349 meter.id(), meter.deviceId());
350 incrementMeterCount(meter.deviceId(), key);
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000351
Andrea Campanella600d2e22020-06-22 11:00:31 +0200352 if (appId.equals(meter.appId()) && pendingRemoveMeters.get(meter.deviceId())
353 .get(key).get() == 3) {
354 log.info("Deleting unreferenced, no longer programmed Meter {} on {}",
355 meter.id(), meter.deviceId());
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000356 deleteMeter(meter.deviceId(), meter.id());
357 }
358 }
359 if (MeterEvent.Type.METER_REMOVED.equals(meterEvent.type())) {
Andrea Campanella600d2e22020-06-22 11:00:31 +0200360 log.info("Meter Removed Event is received for {} on {}",
361 meter.id(), meter.deviceId());
362 pendingRemoveMeters.computeIfPresent(meter.deviceId(),
363 (id, meters) -> {
364 if (meters.get(key) == null) {
365 log.info("Meters is not pending " +
366 "{} on {}", key, id);
367 return meters;
368 }
369 meters.remove(key);
370 return meters;
371 });
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000372 removeMeterFromBpMapping(key);
373 }
374 });
375 }
376
Andrea Campanella600d2e22020-06-22 11:00:31 +0200377 private void incrementMeterCount(DeviceId deviceId, MeterKey key) {
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000378 if (key == null) {
379 return;
380 }
Andrea Campanella600d2e22020-06-22 11:00:31 +0200381 pendingRemoveMeters.compute(deviceId,
382 (id, meters) -> {
383 if (meters == null) {
384 meters = new HashMap<>();
385
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000386 }
Andrea Campanella600d2e22020-06-22 11:00:31 +0200387 if (meters.get(key) == null) {
388 meters.put(key, new AtomicInteger(1));
389 }
390 meters.get(key).addAndGet(1);
391 return meters;
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000392 });
393 }
394
395 private void deleteMeter(DeviceId deviceId, MeterId meterId) {
396 Meter meter = meterService.getMeter(deviceId, meterId);
397 if (meter != null) {
398 MeterRequest meterRequest = DefaultMeterRequest.builder()
399 .withBands(meter.bands())
400 .withUnit(meter.unit())
401 .forDevice(deviceId)
402 .fromApp(appId)
403 .burst()
404 .remove();
405
406 meterService.withdraw(meterRequest, meterId);
407 }
408 }
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000409 }
410}