blob: 3cbdbd710693fbaba154c17ebafff5b244b1660b [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;
Andrea Campanella3ce4d282020-06-09 13:46:58 +020045import org.opencord.olt.internalapi.DeviceBandwidthProfile;
Andrea Campanellacbbb7952019-11-25 06:38:41 +000046import org.opencord.sadis.BandwidthProfileInformation;
47import org.osgi.service.component.ComponentContext;
48import org.osgi.service.component.annotations.Activate;
49import org.osgi.service.component.annotations.Component;
50import org.osgi.service.component.annotations.Deactivate;
51import org.osgi.service.component.annotations.Modified;
52import org.osgi.service.component.annotations.Reference;
53import org.osgi.service.component.annotations.ReferenceCardinality;
54import org.slf4j.Logger;
55
56import java.util.ArrayList;
57import java.util.Collection;
58import java.util.Dictionary;
Andrea Campanellacbbb7952019-11-25 06:38:41 +000059import java.util.List;
60import java.util.Map;
61import java.util.Optional;
62import java.util.Properties;
Andrea Campanella3ce4d282020-06-09 13:46:58 +020063import java.util.Set;
Andrea Campanellacbbb7952019-11-25 06:38:41 +000064import java.util.concurrent.CompletableFuture;
Andrea Campanella3ce4d282020-06-09 13:46:58 +020065import java.util.concurrent.ConcurrentHashMap;
Andrea Campanellacbbb7952019-11-25 06:38:41 +000066import 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
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800103 ConsistentMultimap<String, MeterKey> bpInfoToMeter;
104
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000105 private ApplicationId appId;
106 private static final String APP_NAME = "org.opencord.olt";
107
108 private final MeterListener meterListener = new InternalMeterListener();
109
110 private final Logger log = getLogger(getClass());
111
112 protected ExecutorService eventExecutor;
113
Andrea Campanella3ce4d282020-06-09 13:46:58 +0200114 private Set<DeviceBandwidthProfile> pendingMeters;
115
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 Campanella3ce4d282020-06-09 13:46:58 +0200136 pendingMeters = ConcurrentHashMap.newKeySet();
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000137 log.info("Olt Meter service started");
138 }
139
140 @Deactivate
141 public void deactivate() {
142 meterService.removeListener(meterListener);
143 }
144
145
146 @Modified
147 public void modified(ComponentContext context) {
148 Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties();
149
150 Boolean d = Tools.isPropertyEnabled(properties, "deleteMeters");
151 if (d != null) {
152 deleteMeters = d;
153 }
154 }
155
156 @Override
157 public ImmutableMap<String, Collection<MeterKey>> getBpMeterMappings() {
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800158 return bpInfoToMeter.stream()
159 .collect(collectingAndThen(
160 groupingBy(Map.Entry::getKey, mapping(Map.Entry::getValue, toSet())),
161 ImmutableMap::copyOf));
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000162 }
163
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800164 void addMeterIdToBpMapping(DeviceId deviceId, MeterId meterId, String bandwidthProfile) {
Andrea Campanella0c3309d2020-05-29 01:51:18 -0700165 log.debug("adding bp {} to meter {} mapping for device {}",
166 bandwidthProfile, meterId, deviceId);
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000167 bpInfoToMeter.put(bandwidthProfile, MeterKey.key(deviceId, meterId));
168 }
169
170 @Override
171 public MeterId getMeterIdFromBpMapping(DeviceId deviceId, String bandwidthProfile) {
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800172 if (bpInfoToMeter.get(bandwidthProfile).value().isEmpty()) {
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000173 log.warn("Bandwidth Profile '{}' is not currently mapped to a meter",
174 bandwidthProfile);
175 return null;
176 }
177
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800178 Optional<? extends MeterKey> meterKeyForDevice = bpInfoToMeter.get(bandwidthProfile).value()
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000179 .stream()
180 .filter(meterKey -> meterKey.deviceId().equals(deviceId))
181 .findFirst();
182 if (meterKeyForDevice.isPresent()) {
183 log.debug("Found meter {} for bandwidth profile {}",
184 meterKeyForDevice.get().meterId(), bandwidthProfile);
185 return meterKeyForDevice.get().meterId();
186 } else {
Andrea Campanella0c3309d2020-05-29 01:51:18 -0700187 log.warn("Bandwidth Profile '{}' is not currently mapped to a meter in {}",
188 bandwidthProfile, bpInfoToMeter.get(bandwidthProfile).value());
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000189 return null;
190 }
191 }
192
193 @Override
194 public ImmutableSet<MeterKey> getProgMeters() {
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800195 return bpInfoToMeter.stream()
196 .map(Map.Entry::getValue)
197 .collect(ImmutableSet.toImmutableSet());
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000198 }
199
200 @Override
201 public MeterId createMeter(DeviceId deviceId, BandwidthProfileInformation bpInfo,
202 CompletableFuture<Object> meterFuture) {
Andrea Campanella3ce4d282020-06-09 13:46:58 +0200203 log.debug("Installing meter on {} for {}", deviceId, bpInfo);
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000204 if (bpInfo == null) {
205 log.warn("Requested bandwidth profile information is NULL");
206 meterFuture.complete(ObjectiveError.BADPARAMS);
207 return null;
208 }
209
210 MeterId meterId = getMeterIdFromBpMapping(deviceId, bpInfo.id());
211 if (meterId != null) {
212 log.debug("Meter {} was previously created for bp {}", meterId, bpInfo.id());
213 meterFuture.complete(null);
214 return meterId;
215 }
216
217 List<Band> meterBands = createMeterBands(bpInfo);
218
219 final AtomicReference<MeterId> meterIdRef = new AtomicReference<>();
220 MeterRequest meterRequest = DefaultMeterRequest.builder()
221 .withBands(meterBands)
222 .withUnit(Meter.Unit.KB_PER_SEC)
223 .withContext(new MeterContext() {
224 @Override
225 public void onSuccess(MeterRequest op) {
Andrea Campanella0c3309d2020-05-29 01:51:18 -0700226 log.debug("Meter {} is installed on the device {}",
Andrea Campanella3ce4d282020-06-09 13:46:58 +0200227 meterIdRef.get(), deviceId);
Andrea Campanella0c3309d2020-05-29 01:51:18 -0700228 addMeterIdToBpMapping(deviceId, meterIdRef.get(), bpInfo.id());
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000229 meterFuture.complete(null);
230 }
231
232 @Override
233 public void onError(MeterRequest op, MeterFailReason reason) {
Andrea Campanellac727a372020-06-09 17:34:38 +0200234 log.error("Failed installing meter {} on {} for {}",
235 meterIdRef.get(), deviceId, bpInfo.id());
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000236 bpInfoToMeter.remove(bpInfo.id(),
237 MeterKey.key(deviceId, meterIdRef.get()));
238 meterFuture.complete(reason);
239 }
240 })
241 .forDevice(deviceId)
242 .fromApp(appId)
243 .burst()
244 .add();
245
246 Meter meter = meterService.submit(meterRequest);
247 meterIdRef.set(meter.id());
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000248 log.info("Meter is created. Meter Id {}", meter.id());
249 return meter.id();
250 }
251
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800252 @Override
Andrea Campanella3ce4d282020-06-09 13:46:58 +0200253 public void addToPendingMeters(DeviceBandwidthProfile deviceBandwidthProfile) {
254 pendingMeters.add(deviceBandwidthProfile);
255 }
256
257 @Override
258 public void removeFromPendingMeters(DeviceBandwidthProfile deviceBandwidthProfile) {
259 pendingMeters.remove(deviceBandwidthProfile);
260 }
261
262 @Override
263 public boolean isMeterPending(DeviceBandwidthProfile deviceBandwidthProfile) {
264 return pendingMeters.contains(deviceBandwidthProfile);
265 }
266
267 @Override
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800268 public void clearMeters(DeviceId deviceId) {
Andrea Campanella65487ba2020-06-17 11:31:30 +0200269 log.debug("Removing all meters for device {}", deviceId);
270 meterService.purgeMeters(deviceId);
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800271 }
272
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000273 private List<Band> createMeterBands(BandwidthProfileInformation bpInfo) {
274 List<Band> meterBands = new ArrayList<>();
275
276 meterBands.add(createMeterBand(bpInfo.committedInformationRate(), bpInfo.committedBurstSize()));
277 meterBands.add(createMeterBand(bpInfo.exceededInformationRate(), bpInfo.exceededBurstSize()));
278 meterBands.add(createMeterBand(bpInfo.assuredInformationRate(), 0L));
279
280 return meterBands;
281 }
282
283 private Band createMeterBand(long rate, Long burst) {
284 return DefaultBand.builder()
285 .withRate(rate) //already Kbps
286 .burstSize(burst) // already Kbits
287 .ofType(Band.Type.DROP) // no matter
288 .build();
289 }
290
291 private class InternalMeterListener implements MeterListener {
292
293 Map<MeterKey, AtomicInteger> pendingRemoveMeters = Maps.newConcurrentMap();
294
295 @Override
296 public void event(MeterEvent meterEvent) {
297 eventExecutor.execute(() -> {
298 Meter meter = meterEvent.subject();
299 if (meter == null) {
300 log.error("Meter in event {} is null", meterEvent);
301 return;
302 }
303 MeterKey key = MeterKey.key(meter.deviceId(), meter.id());
304 if (deleteMeters && MeterEvent.Type.METER_REFERENCE_COUNT_ZERO.equals(meterEvent.type())) {
305 log.info("Zero Count Meter Event is received. Meter is {}", meter.id());
306 incrementMeterCount(key);
307
308 if (appId.equals(meter.appId()) && pendingRemoveMeters.get(key).get() == 3) {
309 log.info("Deleting unreferenced, no longer programmed Meter {}", meter.id());
310 deleteMeter(meter.deviceId(), meter.id());
311 }
312 }
313 if (MeterEvent.Type.METER_REMOVED.equals(meterEvent.type())) {
314 log.info("Meter Removed Event is received for {}", meter.id());
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000315 pendingRemoveMeters.remove(key);
316 removeMeterFromBpMapping(key);
317 }
318 });
319 }
320
321 private void incrementMeterCount(MeterKey key) {
322 if (key == null) {
323 return;
324 }
325 pendingRemoveMeters.compute(key,
326 (k, v) -> {
327 if (v == null) {
328 return new AtomicInteger(1);
329 }
330 v.addAndGet(1);
331 return v;
332 });
333 }
334
335 private void deleteMeter(DeviceId deviceId, MeterId meterId) {
336 Meter meter = meterService.getMeter(deviceId, meterId);
337 if (meter != null) {
338 MeterRequest meterRequest = DefaultMeterRequest.builder()
339 .withBands(meter.bands())
340 .withUnit(meter.unit())
341 .forDevice(deviceId)
342 .fromApp(appId)
343 .burst()
344 .remove();
345
346 meterService.withdraw(meterRequest, meterId);
347 }
348 }
349
350 private void removeMeterFromBpMapping(MeterKey meterKey) {
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800351 List<Map.Entry<String, MeterKey>> meters = bpInfoToMeter.stream()
352 .filter(e -> e.getValue().equals(meterKey))
353 .collect(Collectors.toList());
354
355 meters.forEach(e -> bpInfoToMeter.remove(e.getKey(), e.getValue()));
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000356 }
357 }
358}