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