blob: 80aae4ac0372dafdc06d92e27607e2400812d30f [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 Campanellaaf39b4c2020-05-13 14:07:44 +020018import com.google.common.collect.ImmutableList;
Andrea Campanellacbbb7952019-11-25 06:38:41 +000019import com.google.common.collect.ImmutableMap;
20import com.google.common.collect.ImmutableSet;
21import com.google.common.collect.Maps;
Jonathan Hart4f178fa2020-02-03 10:46:01 -080022import org.onlab.util.KryoNamespace;
Andrea Campanellacbbb7952019-11-25 06:38:41 +000023import org.onlab.util.Tools;
24import org.onosproject.cfg.ComponentConfigService;
25import org.onosproject.core.ApplicationId;
26import org.onosproject.core.CoreService;
27import org.onosproject.net.DeviceId;
28import org.onosproject.net.flowobjective.ObjectiveError;
29import org.onosproject.net.meter.Band;
30import org.onosproject.net.meter.DefaultBand;
31import org.onosproject.net.meter.DefaultMeterRequest;
32import org.onosproject.net.meter.Meter;
33import org.onosproject.net.meter.MeterContext;
34import org.onosproject.net.meter.MeterEvent;
35import org.onosproject.net.meter.MeterFailReason;
36import org.onosproject.net.meter.MeterId;
37import org.onosproject.net.meter.MeterKey;
38import org.onosproject.net.meter.MeterListener;
39import org.onosproject.net.meter.MeterRequest;
40import org.onosproject.net.meter.MeterService;
Jonathan Hart4f178fa2020-02-03 10:46:01 -080041import org.onosproject.store.serializers.KryoNamespaces;
42import org.onosproject.store.service.ConsistentMultimap;
43import org.onosproject.store.service.Serializer;
44import org.onosproject.store.service.StorageService;
Andrea Campanellacbbb7952019-11-25 06:38:41 +000045import org.opencord.olt.internalapi.AccessDeviceMeterService;
46import 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 Campanellacbbb7952019-11-25 06:38:41 +000063import java.util.concurrent.CompletableFuture;
64import java.util.concurrent.ExecutorService;
65import java.util.concurrent.Executors;
66import java.util.concurrent.atomic.AtomicInteger;
67import java.util.concurrent.atomic.AtomicReference;
Jonathan Hart4f178fa2020-02-03 10:46:01 -080068import java.util.stream.Collectors;
Andrea Campanellacbbb7952019-11-25 06:38:41 +000069
Jonathan Hart4f178fa2020-02-03 10:46:01 -080070import static java.util.stream.Collectors.collectingAndThen;
71import static java.util.stream.Collectors.groupingBy;
72import static java.util.stream.Collectors.mapping;
73import static java.util.stream.Collectors.toSet;
Andrea Campanellacbbb7952019-11-25 06:38:41 +000074import static org.onlab.util.Tools.groupedThreads;
75import static org.opencord.olt.impl.OsgiPropertyConstants.DELETE_METERS;
76import static org.opencord.olt.impl.OsgiPropertyConstants.DELETE_METERS_DEFAULT;
77import static org.slf4j.LoggerFactory.getLogger;
78
79/**
80 * Provisions Meters on access devices.
81 */
82@Component(immediate = true, property = {
83 DELETE_METERS + ":Boolean=" + DELETE_METERS_DEFAULT,
84 })
85public class OltMeterService implements AccessDeviceMeterService {
86
87 @Reference(cardinality = ReferenceCardinality.MANDATORY)
88 protected MeterService meterService;
89
90 @Reference(cardinality = ReferenceCardinality.MANDATORY)
91 protected CoreService coreService;
92
93 @Reference(cardinality = ReferenceCardinality.MANDATORY)
94 protected ComponentConfigService componentConfigService;
95
Jonathan Hart4f178fa2020-02-03 10:46:01 -080096 @Reference(cardinality = ReferenceCardinality.MANDATORY)
97 protected StorageService storageService;
98
Andrea Campanellacbbb7952019-11-25 06:38:41 +000099 protected boolean deleteMeters = true;
100
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800101 ConsistentMultimap<String, MeterKey> bpInfoToMeter;
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
112 @Activate
113 public void activate(ComponentContext context) {
114 eventExecutor = Executors.newFixedThreadPool(5, groupedThreads("onos/olt",
115 "events-%d", log));
116 appId = coreService.registerApplication(APP_NAME);
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800117 modified(context);
118
119 KryoNamespace serializer = KryoNamespace.newBuilder()
120 .register(KryoNamespaces.API)
121 .register(MeterKey.class)
122 .build();
123
124 bpInfoToMeter = storageService.<String, MeterKey>consistentMultimapBuilder()
125 .withName("volt-bp-info-to-meter")
126 .withSerializer(Serializer.using(serializer))
127 .withApplicationId(appId)
128 .build();
129
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000130 meterService.addListener(meterListener);
131 componentConfigService.registerProperties(getClass());
132 log.info("Olt Meter service started");
133 }
134
135 @Deactivate
136 public void deactivate() {
137 meterService.removeListener(meterListener);
138 }
139
140
141 @Modified
142 public void modified(ComponentContext context) {
143 Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties();
144
145 Boolean d = Tools.isPropertyEnabled(properties, "deleteMeters");
146 if (d != null) {
147 deleteMeters = d;
148 }
149 }
150
151 @Override
152 public ImmutableMap<String, Collection<MeterKey>> getBpMeterMappings() {
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800153 return bpInfoToMeter.stream()
154 .collect(collectingAndThen(
155 groupingBy(Map.Entry::getKey, mapping(Map.Entry::getValue, toSet())),
156 ImmutableMap::copyOf));
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000157 }
158
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800159 void addMeterIdToBpMapping(DeviceId deviceId, MeterId meterId, String bandwidthProfile) {
Andrea Campanella0c3309d2020-05-29 01:51:18 -0700160 log.debug("adding bp {} to meter {} mapping for device {}",
161 bandwidthProfile, meterId, deviceId);
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000162 bpInfoToMeter.put(bandwidthProfile, MeterKey.key(deviceId, meterId));
163 }
164
165 @Override
166 public MeterId getMeterIdFromBpMapping(DeviceId deviceId, String bandwidthProfile) {
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800167 if (bpInfoToMeter.get(bandwidthProfile).value().isEmpty()) {
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000168 log.warn("Bandwidth Profile '{}' is not currently mapped to a meter",
169 bandwidthProfile);
170 return null;
171 }
172
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800173 Optional<? extends MeterKey> meterKeyForDevice = bpInfoToMeter.get(bandwidthProfile).value()
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000174 .stream()
175 .filter(meterKey -> meterKey.deviceId().equals(deviceId))
176 .findFirst();
177 if (meterKeyForDevice.isPresent()) {
178 log.debug("Found meter {} for bandwidth profile {}",
179 meterKeyForDevice.get().meterId(), bandwidthProfile);
180 return meterKeyForDevice.get().meterId();
181 } else {
Andrea Campanella0c3309d2020-05-29 01:51:18 -0700182 log.warn("Bandwidth Profile '{}' is not currently mapped to a meter in {}",
183 bandwidthProfile, bpInfoToMeter.get(bandwidthProfile).value());
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000184 return null;
185 }
186 }
187
188 @Override
189 public ImmutableSet<MeterKey> getProgMeters() {
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800190 return bpInfoToMeter.stream()
191 .map(Map.Entry::getValue)
192 .collect(ImmutableSet.toImmutableSet());
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000193 }
194
195 @Override
196 public MeterId createMeter(DeviceId deviceId, BandwidthProfileInformation bpInfo,
197 CompletableFuture<Object> meterFuture) {
198 if (bpInfo == null) {
199 log.warn("Requested bandwidth profile information is NULL");
200 meterFuture.complete(ObjectiveError.BADPARAMS);
201 return null;
202 }
203
204 MeterId meterId = getMeterIdFromBpMapping(deviceId, bpInfo.id());
205 if (meterId != null) {
206 log.debug("Meter {} was previously created for bp {}", meterId, bpInfo.id());
207 meterFuture.complete(null);
208 return meterId;
209 }
210
211 List<Band> meterBands = createMeterBands(bpInfo);
212
213 final AtomicReference<MeterId> meterIdRef = new AtomicReference<>();
214 MeterRequest meterRequest = DefaultMeterRequest.builder()
215 .withBands(meterBands)
216 .withUnit(Meter.Unit.KB_PER_SEC)
217 .withContext(new MeterContext() {
218 @Override
219 public void onSuccess(MeterRequest op) {
Andrea Campanella0c3309d2020-05-29 01:51:18 -0700220 log.debug("Meter {} is installed on the device {}",
221 meterId, deviceId);
222 addMeterIdToBpMapping(deviceId, meterIdRef.get(), bpInfo.id());
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000223 meterFuture.complete(null);
224 }
225
226 @Override
227 public void onError(MeterRequest op, MeterFailReason reason) {
228 bpInfoToMeter.remove(bpInfo.id(),
229 MeterKey.key(deviceId, meterIdRef.get()));
230 meterFuture.complete(reason);
231 }
232 })
233 .forDevice(deviceId)
234 .fromApp(appId)
235 .burst()
236 .add();
237
238 Meter meter = meterService.submit(meterRequest);
239 meterIdRef.set(meter.id());
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000240 log.info("Meter is created. Meter Id {}", meter.id());
241 return meter.id();
242 }
243
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800244 @Override
245 public void clearMeters(DeviceId deviceId) {
246 List<Map.Entry<String, MeterKey>> meters = bpInfoToMeter.stream()
247 .filter(e -> e.getValue().deviceId().equals(deviceId))
248 .collect(Collectors.toList());
249
250 meters.forEach(e -> bpInfoToMeter.remove(e.getKey(), e.getValue()));
Andrea Campanellaaf39b4c2020-05-13 14:07:44 +0200251 List<Meter> metersToRemove = ImmutableList.copyOf(meterService.getMeters(deviceId));
252 metersToRemove.forEach(meter -> {
253 MeterRequest mq = DefaultMeterRequest.builder().fromApp(appId)
254 .forDevice(deviceId).withBands(meter.bands())
255 .withUnit(meter.unit()).remove();
256 meterService.withdraw(mq, meter.id());
257 });
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800258 }
259
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000260 private List<Band> createMeterBands(BandwidthProfileInformation bpInfo) {
261 List<Band> meterBands = new ArrayList<>();
262
263 meterBands.add(createMeterBand(bpInfo.committedInformationRate(), bpInfo.committedBurstSize()));
264 meterBands.add(createMeterBand(bpInfo.exceededInformationRate(), bpInfo.exceededBurstSize()));
265 meterBands.add(createMeterBand(bpInfo.assuredInformationRate(), 0L));
266
267 return meterBands;
268 }
269
270 private Band createMeterBand(long rate, Long burst) {
271 return DefaultBand.builder()
272 .withRate(rate) //already Kbps
273 .burstSize(burst) // already Kbits
274 .ofType(Band.Type.DROP) // no matter
275 .build();
276 }
277
278 private class InternalMeterListener implements MeterListener {
279
280 Map<MeterKey, AtomicInteger> pendingRemoveMeters = Maps.newConcurrentMap();
281
282 @Override
283 public void event(MeterEvent meterEvent) {
284 eventExecutor.execute(() -> {
285 Meter meter = meterEvent.subject();
286 if (meter == null) {
287 log.error("Meter in event {} is null", meterEvent);
288 return;
289 }
290 MeterKey key = MeterKey.key(meter.deviceId(), meter.id());
291 if (deleteMeters && MeterEvent.Type.METER_REFERENCE_COUNT_ZERO.equals(meterEvent.type())) {
292 log.info("Zero Count Meter Event is received. Meter is {}", meter.id());
293 incrementMeterCount(key);
294
295 if (appId.equals(meter.appId()) && pendingRemoveMeters.get(key).get() == 3) {
296 log.info("Deleting unreferenced, no longer programmed Meter {}", meter.id());
297 deleteMeter(meter.deviceId(), meter.id());
298 }
299 }
300 if (MeterEvent.Type.METER_REMOVED.equals(meterEvent.type())) {
301 log.info("Meter Removed Event is received for {}", meter.id());
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000302 pendingRemoveMeters.remove(key);
303 removeMeterFromBpMapping(key);
304 }
305 });
306 }
307
308 private void incrementMeterCount(MeterKey key) {
309 if (key == null) {
310 return;
311 }
312 pendingRemoveMeters.compute(key,
313 (k, v) -> {
314 if (v == null) {
315 return new AtomicInteger(1);
316 }
317 v.addAndGet(1);
318 return v;
319 });
320 }
321
322 private void deleteMeter(DeviceId deviceId, MeterId meterId) {
323 Meter meter = meterService.getMeter(deviceId, meterId);
324 if (meter != null) {
325 MeterRequest meterRequest = DefaultMeterRequest.builder()
326 .withBands(meter.bands())
327 .withUnit(meter.unit())
328 .forDevice(deviceId)
329 .fromApp(appId)
330 .burst()
331 .remove();
332
333 meterService.withdraw(meterRequest, meterId);
334 }
335 }
336
337 private void removeMeterFromBpMapping(MeterKey meterKey) {
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800338 List<Map.Entry<String, MeterKey>> meters = bpInfoToMeter.stream()
339 .filter(e -> e.getValue().equals(meterKey))
340 .collect(Collectors.toList());
341
342 meters.forEach(e -> bpInfoToMeter.remove(e.getKey(), e.getValue()));
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000343 }
344 }
345}