blob: 7a3a3a3df35a8be7151b2cd3a9ef3d4f1a1915bd [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 Campanellacbbb7952019-11-25 06:38:41 +000058import java.util.List;
59import java.util.Map;
60import java.util.Optional;
61import java.util.Properties;
Andrea Campanellacbbb7952019-11-25 06:38:41 +000062import java.util.concurrent.CompletableFuture;
63import java.util.concurrent.ExecutorService;
64import java.util.concurrent.Executors;
65import java.util.concurrent.atomic.AtomicInteger;
66import java.util.concurrent.atomic.AtomicReference;
Jonathan Hart4f178fa2020-02-03 10:46:01 -080067import java.util.stream.Collectors;
Andrea Campanellacbbb7952019-11-25 06:38:41 +000068
Jonathan Hart4f178fa2020-02-03 10:46:01 -080069import static java.util.stream.Collectors.collectingAndThen;
70import static java.util.stream.Collectors.groupingBy;
71import static java.util.stream.Collectors.mapping;
72import static java.util.stream.Collectors.toSet;
Andrea Campanellacbbb7952019-11-25 06:38:41 +000073import static org.onlab.util.Tools.groupedThreads;
74import static org.opencord.olt.impl.OsgiPropertyConstants.DELETE_METERS;
75import static org.opencord.olt.impl.OsgiPropertyConstants.DELETE_METERS_DEFAULT;
76import static org.slf4j.LoggerFactory.getLogger;
77
78/**
79 * Provisions Meters on access devices.
80 */
81@Component(immediate = true, property = {
82 DELETE_METERS + ":Boolean=" + DELETE_METERS_DEFAULT,
83 })
84public class OltMeterService implements AccessDeviceMeterService {
85
86 @Reference(cardinality = ReferenceCardinality.MANDATORY)
87 protected MeterService meterService;
88
89 @Reference(cardinality = ReferenceCardinality.MANDATORY)
90 protected CoreService coreService;
91
92 @Reference(cardinality = ReferenceCardinality.MANDATORY)
93 protected ComponentConfigService componentConfigService;
94
Jonathan Hart4f178fa2020-02-03 10:46:01 -080095 @Reference(cardinality = ReferenceCardinality.MANDATORY)
96 protected StorageService storageService;
97
Andrea Campanellacbbb7952019-11-25 06:38:41 +000098 protected boolean deleteMeters = true;
99
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800100 ConsistentMultimap<String, MeterKey> bpInfoToMeter;
101
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000102 private ApplicationId appId;
103 private static final String APP_NAME = "org.opencord.olt";
104
105 private final MeterListener meterListener = new InternalMeterListener();
106
107 private final Logger log = getLogger(getClass());
108
109 protected ExecutorService eventExecutor;
110
111 @Activate
112 public void activate(ComponentContext context) {
113 eventExecutor = Executors.newFixedThreadPool(5, groupedThreads("onos/olt",
114 "events-%d", log));
115 appId = coreService.registerApplication(APP_NAME);
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800116 modified(context);
117
118 KryoNamespace serializer = KryoNamespace.newBuilder()
119 .register(KryoNamespaces.API)
120 .register(MeterKey.class)
121 .build();
122
123 bpInfoToMeter = storageService.<String, MeterKey>consistentMultimapBuilder()
124 .withName("volt-bp-info-to-meter")
125 .withSerializer(Serializer.using(serializer))
126 .withApplicationId(appId)
127 .build();
128
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000129 meterService.addListener(meterListener);
130 componentConfigService.registerProperties(getClass());
131 log.info("Olt Meter service started");
132 }
133
134 @Deactivate
135 public void deactivate() {
136 meterService.removeListener(meterListener);
137 }
138
139
140 @Modified
141 public void modified(ComponentContext context) {
142 Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties();
143
144 Boolean d = Tools.isPropertyEnabled(properties, "deleteMeters");
145 if (d != null) {
146 deleteMeters = d;
147 }
148 }
149
150 @Override
151 public ImmutableMap<String, Collection<MeterKey>> getBpMeterMappings() {
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800152 return bpInfoToMeter.stream()
153 .collect(collectingAndThen(
154 groupingBy(Map.Entry::getKey, mapping(Map.Entry::getValue, toSet())),
155 ImmutableMap::copyOf));
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000156 }
157
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800158 void addMeterIdToBpMapping(DeviceId deviceId, MeterId meterId, String bandwidthProfile) {
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000159 bpInfoToMeter.put(bandwidthProfile, MeterKey.key(deviceId, meterId));
160 }
161
162 @Override
163 public MeterId getMeterIdFromBpMapping(DeviceId deviceId, String bandwidthProfile) {
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800164 if (bpInfoToMeter.get(bandwidthProfile).value().isEmpty()) {
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000165 log.warn("Bandwidth Profile '{}' is not currently mapped to a meter",
166 bandwidthProfile);
167 return null;
168 }
169
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800170 Optional<? extends MeterKey> meterKeyForDevice = bpInfoToMeter.get(bandwidthProfile).value()
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000171 .stream()
172 .filter(meterKey -> meterKey.deviceId().equals(deviceId))
173 .findFirst();
174 if (meterKeyForDevice.isPresent()) {
175 log.debug("Found meter {} for bandwidth profile {}",
176 meterKeyForDevice.get().meterId(), bandwidthProfile);
177 return meterKeyForDevice.get().meterId();
178 } else {
179 log.warn("Bandwidth profile '{}' is not currently mapped to a meter",
180 bandwidthProfile);
181 return null;
182 }
183 }
184
185 @Override
186 public ImmutableSet<MeterKey> getProgMeters() {
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800187 return bpInfoToMeter.stream()
188 .map(Map.Entry::getValue)
189 .collect(ImmutableSet.toImmutableSet());
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000190 }
191
192 @Override
193 public MeterId createMeter(DeviceId deviceId, BandwidthProfileInformation bpInfo,
194 CompletableFuture<Object> meterFuture) {
195 if (bpInfo == null) {
196 log.warn("Requested bandwidth profile information is NULL");
197 meterFuture.complete(ObjectiveError.BADPARAMS);
198 return null;
199 }
200
201 MeterId meterId = getMeterIdFromBpMapping(deviceId, bpInfo.id());
202 if (meterId != null) {
203 log.debug("Meter {} was previously created for bp {}", meterId, bpInfo.id());
204 meterFuture.complete(null);
205 return meterId;
206 }
207
208 List<Band> meterBands = createMeterBands(bpInfo);
209
210 final AtomicReference<MeterId> meterIdRef = new AtomicReference<>();
211 MeterRequest meterRequest = DefaultMeterRequest.builder()
212 .withBands(meterBands)
213 .withUnit(Meter.Unit.KB_PER_SEC)
214 .withContext(new MeterContext() {
215 @Override
216 public void onSuccess(MeterRequest op) {
217 meterFuture.complete(null);
218 }
219
220 @Override
221 public void onError(MeterRequest op, MeterFailReason reason) {
222 bpInfoToMeter.remove(bpInfo.id(),
223 MeterKey.key(deviceId, meterIdRef.get()));
224 meterFuture.complete(reason);
225 }
226 })
227 .forDevice(deviceId)
228 .fromApp(appId)
229 .burst()
230 .add();
231
232 Meter meter = meterService.submit(meterRequest);
233 meterIdRef.set(meter.id());
234 addMeterIdToBpMapping(deviceId, meterIdRef.get(), bpInfo.id());
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000235 log.info("Meter is created. Meter Id {}", meter.id());
236 return meter.id();
237 }
238
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800239 @Override
240 public void clearMeters(DeviceId deviceId) {
241 List<Map.Entry<String, MeterKey>> meters = bpInfoToMeter.stream()
242 .filter(e -> e.getValue().deviceId().equals(deviceId))
243 .collect(Collectors.toList());
244
245 meters.forEach(e -> bpInfoToMeter.remove(e.getKey(), e.getValue()));
246 }
247
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000248 private List<Band> createMeterBands(BandwidthProfileInformation bpInfo) {
249 List<Band> meterBands = new ArrayList<>();
250
251 meterBands.add(createMeterBand(bpInfo.committedInformationRate(), bpInfo.committedBurstSize()));
252 meterBands.add(createMeterBand(bpInfo.exceededInformationRate(), bpInfo.exceededBurstSize()));
253 meterBands.add(createMeterBand(bpInfo.assuredInformationRate(), 0L));
254
255 return meterBands;
256 }
257
258 private Band createMeterBand(long rate, Long burst) {
259 return DefaultBand.builder()
260 .withRate(rate) //already Kbps
261 .burstSize(burst) // already Kbits
262 .ofType(Band.Type.DROP) // no matter
263 .build();
264 }
265
266 private class InternalMeterListener implements MeterListener {
267
268 Map<MeterKey, AtomicInteger> pendingRemoveMeters = Maps.newConcurrentMap();
269
270 @Override
271 public void event(MeterEvent meterEvent) {
272 eventExecutor.execute(() -> {
273 Meter meter = meterEvent.subject();
274 if (meter == null) {
275 log.error("Meter in event {} is null", meterEvent);
276 return;
277 }
278 MeterKey key = MeterKey.key(meter.deviceId(), meter.id());
279 if (deleteMeters && MeterEvent.Type.METER_REFERENCE_COUNT_ZERO.equals(meterEvent.type())) {
280 log.info("Zero Count Meter Event is received. Meter is {}", meter.id());
281 incrementMeterCount(key);
282
283 if (appId.equals(meter.appId()) && pendingRemoveMeters.get(key).get() == 3) {
284 log.info("Deleting unreferenced, no longer programmed Meter {}", meter.id());
285 deleteMeter(meter.deviceId(), meter.id());
286 }
287 }
288 if (MeterEvent.Type.METER_REMOVED.equals(meterEvent.type())) {
289 log.info("Meter Removed Event is received for {}", meter.id());
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000290 pendingRemoveMeters.remove(key);
291 removeMeterFromBpMapping(key);
292 }
293 });
294 }
295
296 private void incrementMeterCount(MeterKey key) {
297 if (key == null) {
298 return;
299 }
300 pendingRemoveMeters.compute(key,
301 (k, v) -> {
302 if (v == null) {
303 return new AtomicInteger(1);
304 }
305 v.addAndGet(1);
306 return v;
307 });
308 }
309
310 private void deleteMeter(DeviceId deviceId, MeterId meterId) {
311 Meter meter = meterService.getMeter(deviceId, meterId);
312 if (meter != null) {
313 MeterRequest meterRequest = DefaultMeterRequest.builder()
314 .withBands(meter.bands())
315 .withUnit(meter.unit())
316 .forDevice(deviceId)
317 .fromApp(appId)
318 .burst()
319 .remove();
320
321 meterService.withdraw(meterRequest, meterId);
322 }
323 }
324
325 private void removeMeterFromBpMapping(MeterKey meterKey) {
Jonathan Hart4f178fa2020-02-03 10:46:01 -0800326 List<Map.Entry<String, MeterKey>> meters = bpInfoToMeter.stream()
327 .filter(e -> e.getValue().equals(meterKey))
328 .collect(Collectors.toList());
329
330 meters.forEach(e -> bpInfoToMeter.remove(e.getKey(), e.getValue()));
Andrea Campanellacbbb7952019-11-25 06:38:41 +0000331 }
332 }
333}