blob: df434eb2d2d02c405de219fcafe9d4c96bb486be [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
18import com.google.common.collect.ImmutableMap;
19import com.google.common.collect.ImmutableSet;
20import com.google.common.collect.Maps;
21import com.google.common.collect.Sets;
22import org.apache.felix.scr.annotations.Activate;
23import org.apache.felix.scr.annotations.Component;
24import org.apache.felix.scr.annotations.Deactivate;
25import org.apache.felix.scr.annotations.Modified;
26import org.apache.felix.scr.annotations.Property;
27import org.apache.felix.scr.annotations.Reference;
28import org.apache.felix.scr.annotations.ReferenceCardinality;
29import org.apache.felix.scr.annotations.Service;
30import org.onlab.util.Tools;
31import org.onosproject.cfg.ComponentConfigService;
32import org.onosproject.core.ApplicationId;
33import org.onosproject.core.CoreService;
34import org.onosproject.net.DeviceId;
35import org.onosproject.net.flowobjective.ObjectiveError;
36import org.onosproject.net.meter.Band;
37import org.onosproject.net.meter.DefaultBand;
38import org.onosproject.net.meter.DefaultMeterRequest;
39import org.onosproject.net.meter.Meter;
40import org.onosproject.net.meter.MeterContext;
41import org.onosproject.net.meter.MeterEvent;
42import org.onosproject.net.meter.MeterFailReason;
43import org.onosproject.net.meter.MeterId;
44import org.onosproject.net.meter.MeterKey;
45import org.onosproject.net.meter.MeterListener;
46import org.onosproject.net.meter.MeterRequest;
47import org.onosproject.net.meter.MeterService;
48import org.opencord.olt.internalapi.AccessDeviceMeterService;
49import org.opencord.sadis.BandwidthProfileInformation;
50import org.osgi.service.component.ComponentContext;
51import org.slf4j.Logger;
52
53import java.util.ArrayList;
54
55import java.util.Dictionary;
56import java.util.Iterator;
57import java.util.List;
58import java.util.Map;
59import java.util.Optional;
60import java.util.Properties;
61import java.util.Set;
62import 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;
67
68import static org.onlab.util.Tools.groupedThreads;
69import static org.slf4j.LoggerFactory.getLogger;
70
71@Service
72@Component(immediate = true)
73public class OltMeterService implements AccessDeviceMeterService {
74
75 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
76 protected MeterService meterService;
77
78 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
79 protected CoreService coreService;
80
81 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
82 protected ComponentConfigService componentConfigService;
83
84 @Property(name = "deleteMeters", boolValue = true,
85 label = "Deleting Meters based on flow count statistics")
86 protected boolean deleteMeters = true;
87
88 protected Map<String, Set<MeterKey>> bpInfoToMeter;
89 protected Set<MeterKey> programmedMeters;
90 private ApplicationId appId;
91 private static final String APP_NAME = "org.opencord.olt";
92
93 private final MeterListener meterListener = new InternalMeterListener();
94
95 private final Logger log = getLogger(getClass());
96
97 protected ExecutorService eventExecutor;
98
99 @Activate
100 public void activate(ComponentContext context) {
101 eventExecutor = Executors.newFixedThreadPool(5, groupedThreads("onos/olt",
102 "events-%d", log));
103 appId = coreService.registerApplication(APP_NAME);
104 bpInfoToMeter = Maps.newConcurrentMap();
105 programmedMeters = Sets.newConcurrentHashSet();
106 meterService.addListener(meterListener);
107 componentConfigService.registerProperties(getClass());
108 log.info("Olt Meter service started");
109 }
110
111 @Deactivate
112 public void deactivate() {
113 meterService.removeListener(meterListener);
114 }
115
116
117 @Modified
118 public void modified(ComponentContext context) {
119 Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties();
120
121 Boolean d = Tools.isPropertyEnabled(properties, "deleteMeters");
122 if (d != null) {
123 deleteMeters = d;
124 }
125 }
126
127 @Override
128 public ImmutableMap<String, Set<MeterKey>> getBpMeterMappings() {
129 return ImmutableMap.copyOf(bpInfoToMeter);
130 }
131
132 @Override
133 public void addMeterIdToBpMapping(DeviceId deviceId, MeterId meterId, String bandwidthProfile) {
134 bpInfoToMeter.compute(bandwidthProfile, (k, v) -> {
135 if (v == null) {
136 return Sets.newHashSet(MeterKey.key(deviceId, meterId));
137 } else {
138 v.add(MeterKey.key(deviceId, meterId));
139 return v;
140 }
141 });
142 }
143
144 @Override
145 public MeterId getMeterIdFromBpMapping(DeviceId deviceId, String bandwidthProfile) {
146 if (bpInfoToMeter.get(bandwidthProfile) == null) {
147 log.warn("Bandwidth Profile '{}' is not currently mapped to a meter",
148 bandwidthProfile);
149 return null;
150 }
151
152 Optional<MeterKey> meterKeyForDevice = bpInfoToMeter.get(bandwidthProfile)
153 .stream()
154 .filter(meterKey -> meterKey.deviceId().equals(deviceId))
155 .findFirst();
156 if (meterKeyForDevice.isPresent()) {
157 log.debug("Found meter {} for bandwidth profile {}",
158 meterKeyForDevice.get().meterId(), bandwidthProfile);
159 return meterKeyForDevice.get().meterId();
160 } else {
161 log.warn("Bandwidth profile '{}' is not currently mapped to a meter",
162 bandwidthProfile);
163 return null;
164 }
165 }
166
167 @Override
168 public ImmutableSet<MeterKey> getProgMeters() {
169 return ImmutableSet.copyOf(programmedMeters);
170 }
171
172 @Override
173 public MeterId createMeter(DeviceId deviceId, BandwidthProfileInformation bpInfo,
174 CompletableFuture<Object> meterFuture) {
175 if (bpInfo == null) {
176 log.warn("Requested bandwidth profile information is NULL");
177 meterFuture.complete(ObjectiveError.BADPARAMS);
178 return null;
179 }
180
181 MeterId meterId = getMeterIdFromBpMapping(deviceId, bpInfo.id());
182 if (meterId != null) {
183 log.debug("Meter {} was previously created for bp {}", meterId, bpInfo.id());
184 meterFuture.complete(null);
185 return meterId;
186 }
187
188 List<Band> meterBands = createMeterBands(bpInfo);
189
190 final AtomicReference<MeterId> meterIdRef = new AtomicReference<>();
191 MeterRequest meterRequest = DefaultMeterRequest.builder()
192 .withBands(meterBands)
193 .withUnit(Meter.Unit.KB_PER_SEC)
194 .withContext(new MeterContext() {
195 @Override
196 public void onSuccess(MeterRequest op) {
197 meterFuture.complete(null);
198 }
199
200 @Override
201 public void onError(MeterRequest op, MeterFailReason reason) {
202 bpInfoToMeter.remove(MeterKey.key(deviceId, meterIdRef.get()));
203 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();
245 MeterKey key = MeterKey.key(meter.deviceId(), meter.id());
246 if (deleteMeters && MeterEvent.Type.METER_REFERENCE_COUNT_ZERO.equals(meterEvent.type())) {
247 log.info("Zero Count Meter Event is received. Meter is {}", meter.id());
248 incrementMeterCount(key);
249
250 if (meter != null && appId.equals(meter.appId()) && pendingRemoveMeters.get(key).get() == 3) {
251 log.info("Deleting unreferenced, no longer programmed Meter {}", meter.id());
252 deleteMeter(meter.deviceId(), meter.id());
253 }
254 }
255 if (MeterEvent.Type.METER_REMOVED.equals(meterEvent.type())) {
256 log.info("Meter Removed Event is received for {}", meter.id());
257 programmedMeters.remove(key);
258 pendingRemoveMeters.remove(key);
259 removeMeterFromBpMapping(meter);
260 }
261 });
262 }
263
264 private void incrementMeterCount(MeterKey key) {
265 if (key == null) {
266 return;
267 }
268 pendingRemoveMeters.compute(key,
269 (k, v) -> {
270 if (v == null) {
271 return new AtomicInteger(1);
272 }
273 v.addAndGet(1);
274 return v;
275 });
276 }
277
278 private void deleteMeter(DeviceId deviceId, MeterId meterId) {
279 Meter meter = meterService.getMeter(deviceId, meterId);
280 if (meter != null) {
281 MeterRequest meterRequest = DefaultMeterRequest.builder()
282 .withBands(meter.bands())
283 .withUnit(meter.unit())
284 .forDevice(deviceId)
285 .fromApp(appId)
286 .burst()
287 .remove();
288
289 meterService.withdraw(meterRequest, meterId);
290 }
291 }
292
293 private void removeMeterFromBpMapping(Meter meter) {
294 MeterKey meterKey = MeterKey.key(meter.deviceId(), meter.id());
295 Iterator<Map.Entry<String, Set<MeterKey>>> iterator = bpInfoToMeter.entrySet().iterator();
296 while (iterator.hasNext()) {
297 Map.Entry<String, Set<MeterKey>> entry = iterator.next();
298 if (entry.getValue().contains(meterKey)) {
299 iterator.remove();
300 log.info("Deleted meter for MeterKey {} - Last prog meters {}", meterKey, programmedMeters);
301 break;
302 }
303 }
304 }
305 }
306}