blob: 3fff30cdff57bc5c552a30d0b4ee383f732be8b3 [file] [log] [blame]
Gamze Abaka1b7816e2019-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 Campanellad936c8e2020-01-30 11:04:34 +010018import com.google.common.collect.HashMultimap;
Gamze Abaka1b7816e2019-11-25 06:38:41 +000019import com.google.common.collect.ImmutableMap;
20import com.google.common.collect.ImmutableSet;
21import com.google.common.collect.Maps;
Andrea Campanellad936c8e2020-01-30 11:04:34 +010022import com.google.common.collect.Multimaps;
23import com.google.common.collect.SetMultimap;
Gamze Abaka1b7816e2019-11-25 06:38:41 +000024import com.google.common.collect.Sets;
25import org.apache.felix.scr.annotations.Activate;
26import org.apache.felix.scr.annotations.Component;
27import org.apache.felix.scr.annotations.Deactivate;
28import org.apache.felix.scr.annotations.Modified;
29import org.apache.felix.scr.annotations.Property;
30import org.apache.felix.scr.annotations.Reference;
31import org.apache.felix.scr.annotations.ReferenceCardinality;
32import org.apache.felix.scr.annotations.Service;
33import org.onlab.util.Tools;
34import org.onosproject.cfg.ComponentConfigService;
35import org.onosproject.core.ApplicationId;
36import org.onosproject.core.CoreService;
37import org.onosproject.net.DeviceId;
38import org.onosproject.net.flowobjective.ObjectiveError;
39import org.onosproject.net.meter.Band;
40import org.onosproject.net.meter.DefaultBand;
41import org.onosproject.net.meter.DefaultMeterRequest;
42import org.onosproject.net.meter.Meter;
43import org.onosproject.net.meter.MeterContext;
44import org.onosproject.net.meter.MeterEvent;
45import org.onosproject.net.meter.MeterFailReason;
46import org.onosproject.net.meter.MeterId;
47import org.onosproject.net.meter.MeterKey;
48import org.onosproject.net.meter.MeterListener;
49import org.onosproject.net.meter.MeterRequest;
50import org.onosproject.net.meter.MeterService;
51import org.opencord.olt.internalapi.AccessDeviceMeterService;
52import org.opencord.sadis.BandwidthProfileInformation;
53import org.osgi.service.component.ComponentContext;
54import org.slf4j.Logger;
55
56import java.util.ArrayList;
Andrea Campanellad936c8e2020-01-30 11:04:34 +010057import java.util.Collection;
Gamze Abaka1b7816e2019-11-25 06:38:41 +000058import java.util.Dictionary;
59import java.util.Iterator;
60import java.util.List;
61import java.util.Map;
62import java.util.Optional;
63import java.util.Properties;
64import java.util.Set;
65import 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;
70
71import static org.onlab.util.Tools.groupedThreads;
72import static org.slf4j.LoggerFactory.getLogger;
73
Andrea Campanellad936c8e2020-01-30 11:04:34 +010074/**
75 * Provisions Meters on access devices.
76 */
Gamze Abaka1b7816e2019-11-25 06:38:41 +000077@Service
78@Component(immediate = true)
79public class OltMeterService implements AccessDeviceMeterService {
80
81 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
82 protected MeterService meterService;
83
84 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
85 protected CoreService coreService;
86
87 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
88 protected ComponentConfigService componentConfigService;
89
90 @Property(name = "deleteMeters", boolValue = true,
91 label = "Deleting Meters based on flow count statistics")
92 protected boolean deleteMeters = true;
93
Andrea Campanellad936c8e2020-01-30 11:04:34 +010094 protected SetMultimap<String, MeterKey> bpInfoToMeter =
95 Multimaps.synchronizedSetMultimap(HashMultimap.create());
Gamze Abaka1b7816e2019-11-25 06:38:41 +000096 protected Set<MeterKey> programmedMeters;
97 private ApplicationId appId;
98 private static final String APP_NAME = "org.opencord.olt";
99
100 private final MeterListener meterListener = new InternalMeterListener();
101
102 private final Logger log = getLogger(getClass());
103
104 protected ExecutorService eventExecutor;
105
106 @Activate
107 public void activate(ComponentContext context) {
108 eventExecutor = Executors.newFixedThreadPool(5, groupedThreads("onos/olt",
109 "events-%d", log));
110 appId = coreService.registerApplication(APP_NAME);
Gamze Abaka1b7816e2019-11-25 06:38:41 +0000111 programmedMeters = Sets.newConcurrentHashSet();
112 meterService.addListener(meterListener);
113 componentConfigService.registerProperties(getClass());
114 log.info("Olt Meter service started");
115 }
116
117 @Deactivate
118 public void deactivate() {
119 meterService.removeListener(meterListener);
120 }
121
122
123 @Modified
124 public void modified(ComponentContext context) {
125 Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties();
126
127 Boolean d = Tools.isPropertyEnabled(properties, "deleteMeters");
128 if (d != null) {
129 deleteMeters = d;
130 }
131 }
132
133 @Override
Andrea Campanellad936c8e2020-01-30 11:04:34 +0100134 public ImmutableMap<String, Collection<MeterKey>> getBpMeterMappings() {
135 return ImmutableMap.copyOf(bpInfoToMeter.asMap());
Gamze Abaka1b7816e2019-11-25 06:38:41 +0000136 }
137
138 @Override
139 public void addMeterIdToBpMapping(DeviceId deviceId, MeterId meterId, String bandwidthProfile) {
Andrea Campanellad936c8e2020-01-30 11:04:34 +0100140 bpInfoToMeter.put(bandwidthProfile, MeterKey.key(deviceId, meterId));
Gamze Abaka1b7816e2019-11-25 06:38:41 +0000141 }
142
143 @Override
144 public MeterId getMeterIdFromBpMapping(DeviceId deviceId, String bandwidthProfile) {
145 if (bpInfoToMeter.get(bandwidthProfile) == null) {
146 log.warn("Bandwidth Profile '{}' is not currently mapped to a meter",
147 bandwidthProfile);
148 return null;
149 }
150
151 Optional<MeterKey> meterKeyForDevice = bpInfoToMeter.get(bandwidthProfile)
152 .stream()
153 .filter(meterKey -> meterKey.deviceId().equals(deviceId))
154 .findFirst();
155 if (meterKeyForDevice.isPresent()) {
156 log.debug("Found meter {} for bandwidth profile {}",
157 meterKeyForDevice.get().meterId(), bandwidthProfile);
158 return meterKeyForDevice.get().meterId();
159 } else {
160 log.warn("Bandwidth profile '{}' is not currently mapped to a meter",
161 bandwidthProfile);
162 return null;
163 }
164 }
165
166 @Override
167 public ImmutableSet<MeterKey> getProgMeters() {
168 return ImmutableSet.copyOf(programmedMeters);
169 }
170
171 @Override
172 public MeterId createMeter(DeviceId deviceId, BandwidthProfileInformation bpInfo,
173 CompletableFuture<Object> meterFuture) {
174 if (bpInfo == null) {
175 log.warn("Requested bandwidth profile information is NULL");
176 meterFuture.complete(ObjectiveError.BADPARAMS);
177 return null;
178 }
179
180 MeterId meterId = getMeterIdFromBpMapping(deviceId, bpInfo.id());
181 if (meterId != null) {
182 log.debug("Meter {} was previously created for bp {}", meterId, bpInfo.id());
183 meterFuture.complete(null);
184 return meterId;
185 }
186
187 List<Band> meterBands = createMeterBands(bpInfo);
188
189 final AtomicReference<MeterId> meterIdRef = new AtomicReference<>();
190 MeterRequest meterRequest = DefaultMeterRequest.builder()
191 .withBands(meterBands)
192 .withUnit(Meter.Unit.KB_PER_SEC)
193 .withContext(new MeterContext() {
194 @Override
195 public void onSuccess(MeterRequest op) {
196 meterFuture.complete(null);
197 }
198
199 @Override
200 public void onError(MeterRequest op, MeterFailReason reason) {
Andrea Campanellad936c8e2020-01-30 11:04:34 +0100201 bpInfoToMeter.remove(bpInfo.id(),
202 MeterKey.key(deviceId, meterIdRef.get()));
Gamze Abaka1b7816e2019-11-25 06:38:41 +0000203 meterFuture.complete(reason);
204 }
205 })
206 .forDevice(deviceId)
207 .fromApp(appId)
208 .burst()
209 .add();
210
211 Meter meter = meterService.submit(meterRequest);
212 meterIdRef.set(meter.id());
213 addMeterIdToBpMapping(deviceId, meterIdRef.get(), bpInfo.id());
214 programmedMeters.add(MeterKey.key(deviceId, meter.id()));
215 log.info("Meter is created. Meter Id {}", meter.id());
216 return meter.id();
217 }
218
219 private List<Band> createMeterBands(BandwidthProfileInformation bpInfo) {
220 List<Band> meterBands = new ArrayList<>();
221
222 meterBands.add(createMeterBand(bpInfo.committedInformationRate(), bpInfo.committedBurstSize()));
223 meterBands.add(createMeterBand(bpInfo.exceededInformationRate(), bpInfo.exceededBurstSize()));
224 meterBands.add(createMeterBand(bpInfo.assuredInformationRate(), 0L));
225
226 return meterBands;
227 }
228
229 private Band createMeterBand(long rate, Long burst) {
230 return DefaultBand.builder()
231 .withRate(rate) //already Kbps
232 .burstSize(burst) // already Kbits
233 .ofType(Band.Type.DROP) // no matter
234 .build();
235 }
236
237 private class InternalMeterListener implements MeterListener {
238
239 Map<MeterKey, AtomicInteger> pendingRemoveMeters = Maps.newConcurrentMap();
240
241 @Override
242 public void event(MeterEvent meterEvent) {
243 eventExecutor.execute(() -> {
244 Meter meter = meterEvent.subject();
Andrea Campanellad936c8e2020-01-30 11:04:34 +0100245 if (meter == null) {
246 log.error("Meter in event {} is null", meterEvent);
247 return;
248 }
Gamze Abaka1b7816e2019-11-25 06:38:41 +0000249 MeterKey key = MeterKey.key(meter.deviceId(), meter.id());
250 if (deleteMeters && MeterEvent.Type.METER_REFERENCE_COUNT_ZERO.equals(meterEvent.type())) {
251 log.info("Zero Count Meter Event is received. Meter is {}", meter.id());
252 incrementMeterCount(key);
253
Andrea Campanellad936c8e2020-01-30 11:04:34 +0100254 if (appId.equals(meter.appId()) && pendingRemoveMeters.get(key).get() == 3) {
Gamze Abaka1b7816e2019-11-25 06:38:41 +0000255 log.info("Deleting unreferenced, no longer programmed Meter {}", meter.id());
256 deleteMeter(meter.deviceId(), meter.id());
257 }
258 }
259 if (MeterEvent.Type.METER_REMOVED.equals(meterEvent.type())) {
260 log.info("Meter Removed Event is received for {}", meter.id());
261 programmedMeters.remove(key);
262 pendingRemoveMeters.remove(key);
Andrea Campanellad936c8e2020-01-30 11:04:34 +0100263 removeMeterFromBpMapping(key);
Gamze Abaka1b7816e2019-11-25 06:38:41 +0000264 }
265 });
266 }
267
268 private void incrementMeterCount(MeterKey key) {
269 if (key == null) {
270 return;
271 }
272 pendingRemoveMeters.compute(key,
273 (k, v) -> {
274 if (v == null) {
275 return new AtomicInteger(1);
276 }
277 v.addAndGet(1);
278 return v;
279 });
280 }
281
282 private void deleteMeter(DeviceId deviceId, MeterId meterId) {
283 Meter meter = meterService.getMeter(deviceId, meterId);
284 if (meter != null) {
285 MeterRequest meterRequest = DefaultMeterRequest.builder()
286 .withBands(meter.bands())
287 .withUnit(meter.unit())
288 .forDevice(deviceId)
289 .fromApp(appId)
290 .burst()
291 .remove();
292
293 meterService.withdraw(meterRequest, meterId);
294 }
295 }
296
Andrea Campanellad936c8e2020-01-30 11:04:34 +0100297 private void removeMeterFromBpMapping(MeterKey meterKey) {
298 Iterator<Map.Entry<String, MeterKey>> iterator = bpInfoToMeter.entries().iterator();
Gamze Abaka1b7816e2019-11-25 06:38:41 +0000299 while (iterator.hasNext()) {
Andrea Campanellad936c8e2020-01-30 11:04:34 +0100300 Map.Entry<String, MeterKey> entry = iterator.next();
301 if (entry.getValue().equals(meterKey)) {
Gamze Abaka1b7816e2019-11-25 06:38:41 +0000302 iterator.remove();
303 log.info("Deleted meter for MeterKey {} - Last prog meters {}", meterKey, programmedMeters);
304 break;
305 }
306 }
307 }
308 }
309}