blob: 30da74dedd88e8e7c1048911a35275213b141e5f [file] [log] [blame]
alshabib7bb05012015-08-05 10:15:09 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2015-present Open Networking Foundation
alshabib7bb05012015-08-05 10:15:09 -07003 *
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 */
Thomas Vachuska52f2cd12018-11-08 21:20:04 -080016package org.onosproject.store.meter.impl;
alshabib7bb05012015-08-05 10:15:09 -070017
alshabibeadfc8e2015-08-18 15:40:46 -070018import com.google.common.collect.Collections2;
pierventre44220052020-09-22 12:51:06 +020019import com.google.common.collect.ImmutableSet;
Pier Luigif094c612017-10-14 12:15:02 +020020import com.google.common.collect.Iterables;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070021import com.google.common.collect.Lists;
alshabibeadfc8e2015-08-18 15:40:46 -070022import com.google.common.collect.Maps;
Pier Luigif094c612017-10-14 12:15:02 +020023import org.apache.commons.lang.math.RandomUtils;
Charles Chan593acf92017-11-22 13:55:41 -080024import org.onlab.util.KryoNamespace;
Daniele Moro43ac2892021-07-15 17:02:59 +020025import org.onosproject.core.ApplicationId;
Jordi Ortizaa8de492016-12-01 00:21:36 +010026import org.onosproject.net.DeviceId;
Pier Luigif094c612017-10-14 12:15:02 +020027import org.onosproject.net.behaviour.MeterQuery;
28import org.onosproject.net.driver.DriverHandler;
29import org.onosproject.net.driver.DriverService;
alshabib58fe6dc2015-08-19 17:16:13 -070030import org.onosproject.net.meter.Band;
31import org.onosproject.net.meter.DefaultBand;
alshabib10c810b2015-08-18 16:59:04 -070032import org.onosproject.net.meter.DefaultMeter;
Jordi Ortiz6c847762017-01-30 17:13:05 +010033import org.onosproject.net.meter.DefaultMeterFeatures;
alshabib10c810b2015-08-18 16:59:04 -070034import org.onosproject.net.meter.Meter;
Wailok Shumf013a782021-07-26 16:51:01 +080035import org.onosproject.net.meter.MeterCellId;
alshabib10c810b2015-08-18 16:59:04 -070036import org.onosproject.net.meter.MeterEvent;
37import org.onosproject.net.meter.MeterFailReason;
Jordi Ortizaa8de492016-12-01 00:21:36 +010038import org.onosproject.net.meter.MeterFeatures;
cansu.toprak409289d2017-10-27 10:04:05 +030039import org.onosproject.net.meter.MeterFeaturesFlag;
Jordi Ortiz6c847762017-01-30 17:13:05 +010040import org.onosproject.net.meter.MeterId;
alshabib70aaa1b2015-09-25 14:30:59 -070041import org.onosproject.net.meter.MeterKey;
alshabib10c810b2015-08-18 16:59:04 -070042import org.onosproject.net.meter.MeterOperation;
Wailok Shumf013a782021-07-26 16:51:01 +080043import org.onosproject.net.meter.MeterScope;
alshabib10c810b2015-08-18 16:59:04 -070044import org.onosproject.net.meter.MeterState;
45import org.onosproject.net.meter.MeterStore;
46import org.onosproject.net.meter.MeterStoreDelegate;
47import org.onosproject.net.meter.MeterStoreResult;
Wailok Shumf013a782021-07-26 16:51:01 +080048import org.onosproject.net.meter.MeterTableKey;
49import org.onosproject.net.pi.model.PiMeterId;
50import org.onosproject.net.pi.runtime.PiMeterCellId;
alshabib7bb05012015-08-05 10:15:09 -070051import org.onosproject.store.AbstractStore;
Pier Luigif094c612017-10-14 12:15:02 +020052import org.onosproject.store.primitives.DefaultDistributedSet;
alshabibeadfc8e2015-08-18 15:40:46 -070053import org.onosproject.store.serializers.KryoNamespaces;
Pier Luigif094c612017-10-14 12:15:02 +020054import org.onosproject.store.service.AtomicCounterMap;
alshabib7bb05012015-08-05 10:15:09 -070055import org.onosproject.store.service.ConsistentMap;
Pier Luigif094c612017-10-14 12:15:02 +020056import org.onosproject.store.service.DistributedPrimitive;
57import org.onosproject.store.service.DistributedSet;
Wailok Shumf013a782021-07-26 16:51:01 +080058import org.onosproject.store.service.EventuallyConsistentMap;
59import org.onosproject.store.service.EventuallyConsistentMapEvent;
60import org.onosproject.store.service.EventuallyConsistentMapListener;
alshabibeadfc8e2015-08-18 15:40:46 -070061import org.onosproject.store.service.MapEvent;
62import org.onosproject.store.service.MapEventListener;
alshabib7bb05012015-08-05 10:15:09 -070063import org.onosproject.store.service.Serializer;
alshabibeadfc8e2015-08-18 15:40:46 -070064import org.onosproject.store.service.StorageException;
alshabib7bb05012015-08-05 10:15:09 -070065import org.onosproject.store.service.StorageService;
alshabibeadfc8e2015-08-18 15:40:46 -070066import org.onosproject.store.service.Versioned;
Wailok Shumf013a782021-07-26 16:51:01 +080067import org.onosproject.store.service.WallClockTimestamp;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070068import org.osgi.service.component.annotations.Activate;
69import org.osgi.service.component.annotations.Component;
70import org.osgi.service.component.annotations.Deactivate;
71import org.osgi.service.component.annotations.Reference;
72import org.osgi.service.component.annotations.ReferenceCardinality;
alshabib7bb05012015-08-05 10:15:09 -070073import org.slf4j.Logger;
74
75import java.util.Collection;
Wailok Shumf013a782021-07-26 16:51:01 +080076import java.util.concurrent.ConcurrentMap;
77import java.util.concurrent.ConcurrentHashMap;
Gamze Abakaf57ef602019-03-11 06:52:48 +000078import java.util.List;
alshabibeadfc8e2015-08-18 15:40:46 -070079import java.util.Map;
Gamze Abakaf57ef602019-03-11 06:52:48 +000080import java.util.Objects;
Pier Luigif094c612017-10-14 12:15:02 +020081import java.util.Set;
alshabibeadfc8e2015-08-18 15:40:46 -070082import java.util.concurrent.CompletableFuture;
Pier Luigif094c612017-10-14 12:15:02 +020083import java.util.stream.Collectors;
alshabib7bb05012015-08-05 10:15:09 -070084
pierventre3b39bd82021-08-18 09:40:14 +020085import static com.google.common.base.Preconditions.checkArgument;
Thomas Vachuska52f2cd12018-11-08 21:20:04 -080086import static org.onosproject.store.meter.impl.DistributedMeterStore.ReuseStrategy.FIRST_FIT;
Jordi Ortizaa8de492016-12-01 00:21:36 +010087import static org.onosproject.net.meter.MeterFailReason.TIMEOUT;
Wailok Shumf013a782021-07-26 16:51:01 +080088import static org.onosproject.net.meter.MeterCellId.MeterCellType.INDEX;
89import static org.onosproject.net.meter.MeterCellId.MeterCellType.PIPELINE_INDEPENDENT;
Wailok Shum6a249352021-07-29 00:02:56 +080090import static org.onosproject.net.meter.MeterStoreResult.Type.FAIL;
alshabib7bb05012015-08-05 10:15:09 -070091import static org.slf4j.LoggerFactory.getLogger;
92
93/**
94 * A distributed meter store implementation. Meters are stored consistently
95 * across the cluster.
96 */
Ray Milkeyd84f89b2018-08-17 14:54:17 -070097@Component(immediate = true, service = MeterStore.class)
alshabib7bb05012015-08-05 10:15:09 -070098public class DistributedMeterStore extends AbstractStore<MeterEvent, MeterStoreDelegate>
99 implements MeterStore {
100
101 private Logger log = getLogger(getClass());
102
pierventre1b8afbc2020-07-13 14:07:05 +0200103 // Meters map related objects
alshabib7bb05012015-08-05 10:15:09 -0700104 private static final String METERSTORE = "onos-meter-store";
pierventre1b8afbc2020-07-13 14:07:05 +0200105 private ConsistentMap<MeterKey, MeterData> meters;
Wailok Shumf013a782021-07-26 16:51:01 +0800106 private MapEventListener<MeterKey, MeterData> metersMapListener = new InternalMetersMapEventListener();
pierventre44220052020-09-22 12:51:06 +0200107 private Map<MeterKey, MeterData> metersMap;
alshabib7bb05012015-08-05 10:15:09 -0700108
pierventre1b8afbc2020-07-13 14:07:05 +0200109 // Meters features related objects
110 private static final String METERFEATURESSTORE = "onos-meter-features-store";
Wailok Shumf013a782021-07-26 16:51:01 +0800111 private EventuallyConsistentMap<MeterTableKey, MeterFeatures> metersFeatures;
112 private EventuallyConsistentMapListener<MeterTableKey, MeterFeatures> featuresMapListener =
113 new InternalFeaturesMapEventListener();
pierventre1b8afbc2020-07-13 14:07:05 +0200114
115 // Meters id related objects
116 private static final String AVAILABLEMETERIDSTORE = "onos-meters-available-store";
pierventreb53d6262021-09-22 11:24:38 +0200117 protected final ConcurrentMap<MeterTableKey, DistributedSet<MeterKey>> availableMeterIds =
118 new ConcurrentHashMap<>();
pierventre1b8afbc2020-07-13 14:07:05 +0200119 private static final String METERIDSTORE = "onos-meters-id-store";
Wailok Shumf013a782021-07-26 16:51:01 +0800120 private AtomicCounterMap<MeterTableKey> meterIdGenerators;
pierventre1b8afbc2020-07-13 14:07:05 +0200121
Charles Chan593acf92017-11-22 13:55:41 -0800122 private static final KryoNamespace.Builder APP_KRYO_BUILDER = KryoNamespace.newBuilder()
123 .register(KryoNamespaces.API)
124 .register(MeterKey.class)
125 .register(MeterData.class)
126 .register(DefaultMeter.class)
127 .register(DefaultBand.class)
128 .register(Band.Type.class)
129 .register(MeterState.class)
debmaiti1bea2892019-06-04 12:36:38 +0530130 .register(Meter.Unit.class)
pierventre3b39bd82021-08-18 09:40:14 +0200131 .register(MeterFailReason.class)
132 .register(MeterTableKey.class)
133 .register(MeterFeatures.class)
134 .register(DefaultMeterFeatures.class)
135 .register(MeterFeaturesFlag.class)
136 .register(MeterScope.class);
Charles Chan593acf92017-11-22 13:55:41 -0800137 private Serializer serializer = Serializer.using(Lists.newArrayList(APP_KRYO_BUILDER.build()));
138
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700139 @Reference(cardinality = ReferenceCardinality.MANDATORY)
alshabib7bb05012015-08-05 10:15:09 -0700140 private StorageService storageService;
141
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700142 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Pier Luigif094c612017-10-14 12:15:02 +0200143 protected DriverService driverService;
144
pierventre1b8afbc2020-07-13 14:07:05 +0200145 // Local cache to handle async ops through futures.
alshabib70aaa1b2015-09-25 14:30:59 -0700146 private Map<MeterKey, CompletableFuture<MeterStoreResult>> futures =
alshabibeadfc8e2015-08-18 15:40:46 -0700147 Maps.newConcurrentMap();
alshabib7bb05012015-08-05 10:15:09 -0700148
pierventre3b39bd82021-08-18 09:40:14 +0200149 // Control the user defined index mode for the store.
150 protected boolean userDefinedIndexMode = false;
151
Pier Luigif094c612017-10-14 12:15:02 +0200152 /**
153 * Defines possible selection strategies to reuse meter ids.
154 */
155 enum ReuseStrategy {
156 /**
157 * Select randomly an available id.
158 */
159 RANDOM,
160 /**
161 * Select the first one.
162 */
163 FIRST_FIT
164 }
Pier Luigif094c612017-10-14 12:15:02 +0200165 private ReuseStrategy reuseStrategy = FIRST_FIT;
Jordi Ortiz6c847762017-01-30 17:13:05 +0100166
alshabib7bb05012015-08-05 10:15:09 -0700167 @Activate
168 public void activate() {
alshabib70aaa1b2015-09-25 14:30:59 -0700169 meters = storageService.<MeterKey, MeterData>consistentMapBuilder()
alshabib7bb05012015-08-05 10:15:09 -0700170 .withName(METERSTORE)
Charles Chan593acf92017-11-22 13:55:41 -0800171 .withSerializer(serializer).build();
Wailok Shumf013a782021-07-26 16:51:01 +0800172 meters.addListener(metersMapListener);
pierventre44220052020-09-22 12:51:06 +0200173 metersMap = meters.asJavaMap();
pierventre26ac1512021-09-10 09:37:29 +0200174
Wailok Shumf013a782021-07-26 16:51:01 +0800175 metersFeatures = storageService.<MeterTableKey, MeterFeatures>eventuallyConsistentMapBuilder()
176 .withName(METERFEATURESSTORE)
177 .withTimestampProvider((key, features) -> new WallClockTimestamp())
pierventre3b39bd82021-08-18 09:40:14 +0200178 .withSerializer(APP_KRYO_BUILDER).build();
Wailok Shumf013a782021-07-26 16:51:01 +0800179 metersFeatures.addListener(featuresMapListener);
pierventre26ac1512021-09-10 09:37:29 +0200180
Wailok Shumf013a782021-07-26 16:51:01 +0800181 meterIdGenerators = storageService.<MeterTableKey>atomicCounterMapBuilder()
Pier Luigif094c612017-10-14 12:15:02 +0200182 .withName(METERIDSTORE)
Wailok Shumf013a782021-07-26 16:51:01 +0800183 .withSerializer(Serializer.using(KryoNamespaces.API,
184 MeterTableKey.class,
185 MeterScope.class)).build();
pierventre3b39bd82021-08-18 09:40:14 +0200186
alshabib7bb05012015-08-05 10:15:09 -0700187 log.info("Started");
188 }
189
190 @Deactivate
191 public void deactivate() {
Wailok Shumf013a782021-07-26 16:51:01 +0800192 meters.removeListener(metersMapListener);
193 metersFeatures.removeListener(featuresMapListener);
194 meters.destroy();
195 metersFeatures.destroy();
pierventre3b39bd82021-08-18 09:40:14 +0200196 availableMeterIds.forEach((key, set) -> set.destroy());
197
alshabib7bb05012015-08-05 10:15:09 -0700198 log.info("Stopped");
199 }
200
alshabib7bb05012015-08-05 10:15:09 -0700201 @Override
Wailok Shum6a249352021-07-29 00:02:56 +0800202 public CompletableFuture<MeterStoreResult> addOrUpdateMeter(Meter meter) {
pierventre3b39bd82021-08-18 09:40:14 +0200203 checkArgument(validIndex(meter), "Meter index is not valid");
Wailok Shum6a249352021-07-29 00:02:56 +0800204 CompletableFuture<MeterStoreResult> future = new CompletableFuture<>();
205 MeterKey key = MeterKey.key(meter.deviceId(), meter.meterCellId());
206 MeterData data = new MeterData(meter, null);
Wailok Shum6a249352021-07-29 00:02:56 +0800207 futures.put(key, future);
Wailok Shum6a249352021-07-29 00:02:56 +0800208 try {
209 meters.compute(key, (k, v) -> data);
210 } catch (StorageException e) {
211 log.error("{} thrown a storage exception: {}", e.getStackTrace()[0].getMethodName(),
212 e.getMessage(), e);
213 futures.remove(key);
214 future.completeExceptionally(e);
215 }
216 return future;
217 }
218
219 @Override
alshabibeadfc8e2015-08-18 15:40:46 -0700220 public CompletableFuture<MeterStoreResult> deleteMeter(Meter meter) {
221 CompletableFuture<MeterStoreResult> future = new CompletableFuture<>();
Wailok Shum6a249352021-07-29 00:02:56 +0800222 MeterKey key = MeterKey.key(meter.deviceId(), meter.meterCellId());
alshabib70aaa1b2015-09-25 14:30:59 -0700223 futures.put(key, future);
Pier Luigif094c612017-10-14 12:15:02 +0200224 // Update the state of the meter. It will be pruned by observing
alshabib7bb05012015-08-05 10:15:09 -0700225 // that it has been removed from the dataplane.
alshabibeadfc8e2015-08-18 15:40:46 -0700226 try {
pierventre26ac1512021-09-10 09:37:29 +0200227 Versioned<MeterData> versionedData = meters.computeIfPresent(key, (k, v) -> {
228 DefaultMeter m = (DefaultMeter) v.meter();
229 MeterState meterState = m.state();
230 if (meterState == MeterState.PENDING_REMOVE) {
231 return v;
232 }
233 m.setState(meter.state());
234 return new MeterData(m, v.reason().isPresent() ? v.reason().get() : null);
235 });
236 // If it does not exist in the system, completes immediately
237 if (versionedData == null) {
238 futures.remove(key);
alshabibe1248b62015-08-20 17:21:55 -0700239 future.complete(MeterStoreResult.success());
240 }
alshabibeadfc8e2015-08-18 15:40:46 -0700241 } catch (StorageException e) {
pierventre1b8afbc2020-07-13 14:07:05 +0200242 log.error("{} thrown a storage exception: {}", e.getStackTrace()[0].getMethodName(),
243 e.getMessage(), e);
Hwanwook Lee8206ad92018-01-02 18:03:50 +0900244 futures.remove(key);
alshabibeadfc8e2015-08-18 15:40:46 -0700245 future.completeExceptionally(e);
alshabib7bb05012015-08-05 10:15:09 -0700246 }
alshabibeadfc8e2015-08-18 15:40:46 -0700247 return future;
alshabib7bb05012015-08-05 10:15:09 -0700248 }
249
250 @Override
Jordi Ortizaa8de492016-12-01 00:21:36 +0100251 public MeterStoreResult storeMeterFeatures(MeterFeatures meterfeatures) {
Wailok Shumf013a782021-07-26 16:51:01 +0800252 // Store meter features, this is done once for each features of every device
Jordi Ortizaa8de492016-12-01 00:21:36 +0100253 MeterStoreResult result = MeterStoreResult.success();
Wailok Shumf013a782021-07-26 16:51:01 +0800254 MeterTableKey key = MeterTableKey.key(meterfeatures.deviceId(), meterfeatures.scope());
Jordi Ortizaa8de492016-12-01 00:21:36 +0100255 try {
Wailok Shumf013a782021-07-26 16:51:01 +0800256 metersFeatures.put(key, meterfeatures);
Jordi Ortizaa8de492016-12-01 00:21:36 +0100257 } catch (StorageException e) {
pierventre1b8afbc2020-07-13 14:07:05 +0200258 log.error("{} thrown a storage exception: {}", e.getStackTrace()[0].getMethodName(),
259 e.getMessage(), e);
Jordi Ortizaa8de492016-12-01 00:21:36 +0100260 result = MeterStoreResult.fail(TIMEOUT);
261 }
262 return result;
263 }
264
265 @Override
Wailok Shum6a249352021-07-29 00:02:56 +0800266 public MeterStoreResult storeMeterFeatures(Collection<MeterFeatures> meterfeatures) {
267 // These store operations is treated as one single operation
268 // If one of them is failed, Fail is returned
269 // But the failed operation will not block the rest.
270 MeterStoreResult result = MeterStoreResult.success();
271 for (MeterFeatures mf : meterfeatures) {
272 if (storeMeterFeatures(mf).type() == FAIL) {
273 result = MeterStoreResult.fail(TIMEOUT);
274 }
275 }
276 return result;
277 }
278
279 @Override
Jordi Ortizaa8de492016-12-01 00:21:36 +0100280 public MeterStoreResult deleteMeterFeatures(DeviceId deviceId) {
281 MeterStoreResult result = MeterStoreResult.success();
Jordi Ortizaa8de492016-12-01 00:21:36 +0100282 try {
Wailok Shumf013a782021-07-26 16:51:01 +0800283 Set<MeterTableKey> keys = metersFeatures.keySet().stream()
284 .filter(key -> key.deviceId().equals(deviceId))
285 .collect(Collectors.toUnmodifiableSet());
pierventre3b39bd82021-08-18 09:40:14 +0200286 keys.forEach(k -> metersFeatures.remove(k));
Jordi Ortizaa8de492016-12-01 00:21:36 +0100287 } catch (StorageException e) {
pierventre1b8afbc2020-07-13 14:07:05 +0200288 log.error("{} thrown a storage exception: {}", e.getStackTrace()[0].getMethodName(),
Wailok Shumf013a782021-07-26 16:51:01 +0800289 e.getMessage(), e);
Jordi Ortizaa8de492016-12-01 00:21:36 +0100290 result = MeterStoreResult.fail(TIMEOUT);
291 }
292 return result;
293 }
294
295 @Override
Wailok Shum6a249352021-07-29 00:02:56 +0800296 public MeterStoreResult deleteMeterFeatures(Collection<MeterFeatures> meterfeatures) {
pierventre26ac1512021-09-10 09:37:29 +0200297 // Same logic of storeMeterFeatures
Wailok Shum6a249352021-07-29 00:02:56 +0800298 MeterStoreResult result = MeterStoreResult.success();
299 for (MeterFeatures mf : meterfeatures) {
300 try {
301 MeterTableKey key = MeterTableKey.key(mf.deviceId(), mf.scope());
302 metersFeatures.remove(key);
303 } catch (StorageException e) {
304 log.error("{} thrown a storage exception: {}", e.getStackTrace()[0].getMethodName(),
305 e.getMessage(), e);
306 result = MeterStoreResult.fail(TIMEOUT);
307 }
308 }
309
310 return result;
311 }
312
313 @Override
pierventre44220052020-09-22 12:51:06 +0200314 public Meter updateMeterState(Meter meter) {
pierventre1b8afbc2020-07-13 14:07:05 +0200315 // Update meter if present (stats workflow)
Wailok Shum6a249352021-07-29 00:02:56 +0800316 MeterKey key = MeterKey.key(meter.deviceId(), meter.meterCellId());
pierventre44220052020-09-22 12:51:06 +0200317 Versioned<MeterData> value = meters.computeIfPresent(key, (k, v) -> {
alshabibeadfc8e2015-08-18 15:40:46 -0700318 DefaultMeter m = (DefaultMeter) v.meter();
pier59721bf2020-01-08 08:57:46 +0100319 MeterState meterState = m.state();
320 if (meterState == MeterState.PENDING_ADD) {
321 m.setState(meter.state());
322 }
alshabib7bb05012015-08-05 10:15:09 -0700323 m.setProcessedPackets(meter.packetsSeen());
324 m.setProcessedBytes(meter.bytesSeen());
325 m.setLife(meter.life());
alshabibeadfc8e2015-08-18 15:40:46 -0700326 // TODO: Prune if drops to zero.
alshabib7bb05012015-08-05 10:15:09 -0700327 m.setReferenceCount(meter.referenceCount());
pierventre1b8afbc2020-07-13 14:07:05 +0200328 return new MeterData(m, null);
alshabib7bb05012015-08-05 10:15:09 -0700329 });
pierventre44220052020-09-22 12:51:06 +0200330 return value != null ? value.value().meter() : null;
alshabib7bb05012015-08-05 10:15:09 -0700331 }
332
333 @Override
alshabib70aaa1b2015-09-25 14:30:59 -0700334 public Meter getMeter(MeterKey key) {
335 MeterData data = Versioned.valueOrElse(meters.get(key), null);
alshabibeadfc8e2015-08-18 15:40:46 -0700336 return data == null ? null : data.meter();
alshabib7bb05012015-08-05 10:15:09 -0700337 }
338
339 @Override
340 public Collection<Meter> getAllMeters() {
pierventre44220052020-09-22 12:51:06 +0200341 return Collections2.transform(ImmutableSet.copyOf(metersMap.values()),
alshabibeadfc8e2015-08-18 15:40:46 -0700342 MeterData::meter);
alshabib7bb05012015-08-05 10:15:09 -0700343 }
344
345 @Override
Jordi Ortiz9287b632017-06-22 11:01:37 +0200346 public Collection<Meter> getAllMeters(DeviceId deviceId) {
347 return Collections2.transform(
pierventre44220052020-09-22 12:51:06 +0200348 Collections2.filter(ImmutableSet.copyOf(metersMap.values()),
Jordi Ortiz9287b632017-06-22 11:01:37 +0200349 (MeterData m) -> m.meter().deviceId().equals(deviceId)),
350 MeterData::meter);
351 }
352
353 @Override
pierventrec0914ec2021-08-27 15:25:02 +0200354 public Collection<Meter> getAllMeters(DeviceId deviceId, MeterScope scope) {
355 if (scope.equals(MeterScope.globalScope())) {
356 return Collections2.transform(
357 Collections2.filter(ImmutableSet.copyOf(metersMap.values()),
358 (MeterData m) -> m.meter().meterCellId().type() == INDEX),
359 MeterData::meter);
360 }
361 return Collections2.transform(
362 Collections2.filter(ImmutableSet.copyOf(metersMap.values()),
363 (MeterData m) -> m.meter().meterCellId().type() == PIPELINE_INDEPENDENT &&
364 ((PiMeterCellId) m.meter().meterCellId()).meterId().id().equals(scope.id())),
365 MeterData::meter);
366 }
367
368 @Override
alshabib7bb05012015-08-05 10:15:09 -0700369 public void failedMeter(MeterOperation op, MeterFailReason reason) {
pierventre1b8afbc2020-07-13 14:07:05 +0200370 // Meter ops failed (got notification from the sb)
pierventre3b39bd82021-08-18 09:40:14 +0200371 MeterKey key = MeterKey.key(op.meter().deviceId(), op.meter().meterCellId());
pierventre1b8afbc2020-07-13 14:07:05 +0200372 meters.computeIfPresent(key, (k, v) -> new MeterData(v.meter(), reason));
alshabib7bb05012015-08-05 10:15:09 -0700373 }
374
alshabib5eb79392015-08-19 18:09:55 -0700375 @Override
Wailok Shumf013a782021-07-26 16:51:01 +0800376 public void purgeMeter(Meter m) {
pierventre26ac1512021-09-10 09:37:29 +0200377 // Once we receive the ack from the sb, create the key
378 // remove definitely the meter and free the id
pierventre3b39bd82021-08-18 09:40:14 +0200379 MeterKey key = MeterKey.key(m.deviceId(), m.meterCellId());
pierventre1b8afbc2020-07-13 14:07:05 +0200380 try {
381 if (Versioned.valueOrNull(meters.remove(key)) != null) {
Wailok Shumf013a782021-07-26 16:51:01 +0800382 MeterScope scope;
383 if (m.meterCellId().type() == PIPELINE_INDEPENDENT) {
384 PiMeterCellId piMeterCellId = (PiMeterCellId) m.meterCellId();
385 scope = MeterScope.of(piMeterCellId.meterId().id());
386 } else {
387 scope = MeterScope.globalScope();
388 }
389 MeterTableKey meterTableKey = MeterTableKey.key(m.deviceId(), scope);
390 freeMeterId(meterTableKey, m.meterCellId());
pierventre1b8afbc2020-07-13 14:07:05 +0200391 }
392 } catch (StorageException e) {
393 log.error("{} thrown a storage exception: {}", e.getStackTrace()[0].getMethodName(),
394 e.getMessage(), e);
pier59721bf2020-01-08 08:57:46 +0100395 }
alshabib5eb79392015-08-19 18:09:55 -0700396 }
397
Jordi Ortizaa8de492016-12-01 00:21:36 +0100398 @Override
pierventre3b39bd82021-08-18 09:40:14 +0200399 public void purgeMeters(DeviceId deviceId) {
Gamze Abakaf57ef602019-03-11 06:52:48 +0000400 List<Versioned<MeterData>> metersPendingRemove = meters.stream()
401 .filter(e -> Objects.equals(e.getKey().deviceId(), deviceId))
402 .map(Map.Entry::getValue)
403 .collect(Collectors.toList());
Gamze Abakaf57ef602019-03-11 06:52:48 +0000404 metersPendingRemove.forEach(versionedMeterKey
Wailok Shumf013a782021-07-26 16:51:01 +0800405 -> purgeMeter(versionedMeterKey.value().meter()));
Gamze Abakaf57ef602019-03-11 06:52:48 +0000406 }
407
408 @Override
Daniele Moro43ac2892021-07-15 17:02:59 +0200409 public void purgeMeters(DeviceId deviceId, ApplicationId appId) {
410 List<Versioned<MeterData>> metersPendingRemove = meters.stream()
411 .filter(e -> Objects.equals(e.getKey().deviceId(), deviceId) &&
412 e.getValue().value().meter().appId().equals(appId))
413 .map(Map.Entry::getValue)
414 .collect(Collectors.toList());
pierventre3b39bd82021-08-18 09:40:14 +0200415 metersPendingRemove.forEach(versionedMeterKey
416 -> purgeMeter(versionedMeterKey.value().meter()));
417 }
418
419 @Override
420 public boolean userDefinedIndexMode(boolean enable) {
421 if (meters.isEmpty() && meterIdGenerators.isEmpty()) {
422 userDefinedIndexMode = enable;
423 } else {
424 log.warn("Unable to {} user defined index mode as store did" +
425 "already some allocations", enable ? "activate" : "deactivate");
426 }
427 return userDefinedIndexMode;
Daniele Moro43ac2892021-07-15 17:02:59 +0200428 }
429
pierventre26ac1512021-09-10 09:37:29 +0200430 protected long getMaxMeters(MeterTableKey key) {
Wailok Shumf013a782021-07-26 16:51:01 +0800431 MeterFeatures features = metersFeatures.get(key);
Jordi Ortizaa8de492016-12-01 00:21:36 +0100432 return features == null ? 0L : features.maxMeter();
433 }
434
pierventre26ac1512021-09-10 09:37:29 +0200435 // Validate index using the meter features, useful mainly
436 // when user defined index mode is enabled
pierventre3b39bd82021-08-18 09:40:14 +0200437 private boolean validIndex(Meter meter) {
438 long index;
439 MeterTableKey key;
440
441 if (meter.meterCellId().type() == PIPELINE_INDEPENDENT) {
442 PiMeterCellId piMeterCellId = (PiMeterCellId) meter.meterCellId();
443 index = piMeterCellId.index();
444 key = MeterTableKey.key(meter.deviceId(), MeterScope.of(piMeterCellId.meterId().id()));
445 } else if (meter.meterCellId().type() == INDEX) {
446 MeterId meterId = (MeterId) meter.meterCellId();
447 index = meterId.id();
448 key = MeterTableKey.key(meter.deviceId(), MeterScope.globalScope());
449 } else {
pierventre26ac1512021-09-10 09:37:29 +0200450 log.warn("Unable to validate index unsupported cell type {}", meter.meterCellId().type());
pierventre3b39bd82021-08-18 09:40:14 +0200451 return false;
452 }
453
454 MeterFeatures features = metersFeatures.get(key);
455 long startIndex = features == null ? -1L : features.startIndex();
456 long endIndex = features == null ? -1L : features.endIndex();
457 return index >= startIndex && index <= endIndex;
458 }
459
Wailok Shumf013a782021-07-26 16:51:01 +0800460 private long getStartIndex(MeterTableKey key) {
Wailok Shumf013a782021-07-26 16:51:01 +0800461 MeterFeatures features = metersFeatures.get(key);
462 return features == null ? -1L : features.startIndex();
463 }
464
465 private long getEndIndex(MeterTableKey key) {
Wailok Shumf013a782021-07-26 16:51:01 +0800466 MeterFeatures features = metersFeatures.get(key);
467 return features == null ? -1L : features.endIndex();
468 }
469
pierventre26ac1512021-09-10 09:37:29 +0200470 // queryMaxMeters is implemented in MeterQuery behaviour implementations.
Pier Luigif094c612017-10-14 12:15:02 +0200471 private long queryMaxMeters(DeviceId device) {
Pier Luigif094c612017-10-14 12:15:02 +0200472 DriverHandler handler = driverService.createHandler(device);
Pier Luigif094c612017-10-14 12:15:02 +0200473 if (handler == null || !handler.hasBehaviour(MeterQuery.class)) {
Pier Luigif094c612017-10-14 12:15:02 +0200474 return 0L;
475 }
pierventre26ac1512021-09-10 09:37:29 +0200476
477 // FIXME architecturally this is not right, we should fallback to this
478 // behavior in the providers. Once we do that we can remove this code.
Pier Luigif094c612017-10-14 12:15:02 +0200479 MeterQuery query = handler.behaviour(MeterQuery.class);
pierventre26ac1512021-09-10 09:37:29 +0200480 // This results to be necessary because the available ids sets are created
481 // in the meter features map listener if the device does not provide the meter
482 // feature this is the only chance to create this set.
Wailok Shumf013a782021-07-26 16:51:01 +0800483 String setName = AVAILABLEMETERIDSTORE + "-" + device + "global";
484 MeterTableKey meterTableKey = MeterTableKey.key(device, MeterScope.globalScope());
485 insertAvailableKeySet(meterTableKey, setName);
pierventre26ac1512021-09-10 09:37:29 +0200486
Pier Luigif094c612017-10-14 12:15:02 +0200487 return query.getMaxMeters();
488 }
489
Wailok Shumf013a782021-07-26 16:51:01 +0800490 private boolean updateMeterIdAvailability(MeterTableKey meterTableKey, MeterCellId id,
Pier Luigif094c612017-10-14 12:15:02 +0200491 boolean available) {
Wailok Shumf013a782021-07-26 16:51:01 +0800492 DistributedSet<MeterKey> keySet = availableMeterIds.get(meterTableKey);
493 if (keySet == null) {
Wailok Shumf013a782021-07-26 16:51:01 +0800494 log.warn("Reusable Key set for device: {} scope: {} not found",
495 meterTableKey.deviceId(), meterTableKey.scope());
496 return false;
497 }
pierventre26ac1512021-09-10 09:37:29 +0200498
Pier Luigif094c612017-10-14 12:15:02 +0200499 // According to available, make available or unavailable a meter key
Wailok Shumf013a782021-07-26 16:51:01 +0800500 DeviceId deviceId = meterTableKey.deviceId();
501 return available ? keySet.add(MeterKey.key(deviceId, id)) :
502 keySet.remove(MeterKey.key(deviceId, id));
Pier Luigif094c612017-10-14 12:15:02 +0200503 }
504
Wailok Shumf013a782021-07-26 16:51:01 +0800505 private MeterCellId getNextAvailableId(Set<MeterCellId> availableIds) {
Pier Luigif094c612017-10-14 12:15:02 +0200506 if (availableIds.isEmpty()) {
Pier Luigif094c612017-10-14 12:15:02 +0200507 return null;
508 }
pierventre26ac1512021-09-10 09:37:29 +0200509
Pier Luigif094c612017-10-14 12:15:02 +0200510 if (reuseStrategy == FIRST_FIT || availableIds.size() == 1) {
511 return availableIds.iterator().next();
512 }
pierventre26ac1512021-09-10 09:37:29 +0200513
514 // If it is random, get the size and return a random element
Pier Luigif094c612017-10-14 12:15:02 +0200515 int size = availableIds.size();
Pier Luigif094c612017-10-14 12:15:02 +0200516 return Iterables.get(availableIds, RandomUtils.nextInt(size));
517 }
518
pierventre26ac1512021-09-10 09:37:29 +0200519 // Implements reuse strategy of the meter cell ids
Wailok Shumf013a782021-07-26 16:51:01 +0800520 private MeterCellId firstReusableMeterId(MeterTableKey meterTableKey) {
Wailok Shumf013a782021-07-26 16:51:01 +0800521 DistributedSet<MeterKey> keySet = availableMeterIds.get(meterTableKey);
522 if (keySet == null) {
Wailok Shumf013a782021-07-26 16:51:01 +0800523 log.warn("Reusable Key set for device: {} scope: {} not found",
524 meterTableKey.deviceId(), meterTableKey.scope());
525 return null;
526 }
pierventre26ac1512021-09-10 09:37:29 +0200527
Wailok Shumf013a782021-07-26 16:51:01 +0800528 Set<MeterCellId> localAvailableMeterIds = keySet.stream()
529 .filter(meterKey ->
530 meterKey.deviceId().equals(meterTableKey.deviceId()))
pierventre3b39bd82021-08-18 09:40:14 +0200531 .map(MeterKey::meterCellId)
Pier Luigif094c612017-10-14 12:15:02 +0200532 .collect(Collectors.toSet());
Wailok Shumf013a782021-07-26 16:51:01 +0800533 MeterCellId meterId = getNextAvailableId(localAvailableMeterIds);
Pier Luigif094c612017-10-14 12:15:02 +0200534 while (meterId != null) {
Wailok Shumf013a782021-07-26 16:51:01 +0800535 if (updateMeterIdAvailability(meterTableKey, meterId, false)) {
Pier Luigif094c612017-10-14 12:15:02 +0200536 return meterId;
537 }
Pier Luigif094c612017-10-14 12:15:02 +0200538 localAvailableMeterIds.remove(meterId);
Pier Luigif094c612017-10-14 12:15:02 +0200539 meterId = getNextAvailableId(localAvailableMeterIds);
540 }
pierventre26ac1512021-09-10 09:37:29 +0200541 // there are no available ids that can be reused
Pier Luigif094c612017-10-14 12:15:02 +0200542 return null;
543 }
544
545 @Override
Wailok Shumf013a782021-07-26 16:51:01 +0800546 public MeterCellId allocateMeterId(DeviceId deviceId, MeterScope meterScope) {
pierventre3b39bd82021-08-18 09:40:14 +0200547 if (userDefinedIndexMode) {
548 log.warn("Unable to allocate meter id when user defined index mode is enabled");
549 return null;
550 }
Wailok Shumf013a782021-07-26 16:51:01 +0800551 MeterTableKey meterTableKey = MeterTableKey.key(deviceId, meterScope);
552 MeterCellId meterCellId;
Pier Luigif094c612017-10-14 12:15:02 +0200553 long id;
Wailok Shumf013a782021-07-26 16:51:01 +0800554 // First, search for reusable key
555 meterCellId = firstReusableMeterId(meterTableKey);
556 if (meterCellId != null) {
Wailok Shumf013a782021-07-26 16:51:01 +0800557 return meterCellId;
Pier Luigif094c612017-10-14 12:15:02 +0200558 }
Wailok Shumf013a782021-07-26 16:51:01 +0800559 // If there was no reusable meter id we have to generate a new value
560 // using start and end index as lower and upper bound respectively.
561 long startIndex = getStartIndex(meterTableKey);
562 long endIndex = getEndIndex(meterTableKey);
pierventre26ac1512021-09-10 09:37:29 +0200563 // If the device does not give us MeterFeatures fallback to queryMeters
Wailok Shumf013a782021-07-26 16:51:01 +0800564 if (startIndex == -1L || endIndex == -1L) {
pierventre26ac1512021-09-10 09:37:29 +0200565 // Only meaningful for OpenFlow today
Wailok Shumf013a782021-07-26 16:51:01 +0800566 long maxMeters = queryMaxMeters(deviceId);
Wailok Shumf013a782021-07-26 16:51:01 +0800567 if (maxMeters == 0L) {
568 return null;
569 } else {
Wailok Shum90b988a2021-09-13 17:23:20 +0800570 // OpenFlow meter index starts from 1, ends with max
Wailok Shumf013a782021-07-26 16:51:01 +0800571 startIndex = 1L;
Wailok Shum90b988a2021-09-13 17:23:20 +0800572 endIndex = maxMeters;
Wailok Shumf013a782021-07-26 16:51:01 +0800573 }
Pier Luigif094c612017-10-14 12:15:02 +0200574 }
pierventre26ac1512021-09-10 09:37:29 +0200575
Wailok Shumf013a782021-07-26 16:51:01 +0800576 do {
Wailok Shum79919522021-08-22 19:35:34 +0800577 id = meterIdGenerators.getAndIncrement(meterTableKey);
Wailok Shumf013a782021-07-26 16:51:01 +0800578 } while (id < startIndex);
Wailok Shumf013a782021-07-26 16:51:01 +0800579 if (id > endIndex) {
Pier Luigif094c612017-10-14 12:15:02 +0200580 return null;
581 }
pierventre26ac1512021-09-10 09:37:29 +0200582
583 // For backward compatibility if we are using global scope,
584 // return a MeterId, otherwise we create a PiMeterCellId
Wailok Shumf013a782021-07-26 16:51:01 +0800585 if (meterScope.isGlobal()) {
586 return MeterId.meterId(id);
587 } else {
588 return PiMeterCellId.ofIndirect(PiMeterId.of(meterScope.id()), id);
589 }
590
Pier Luigif094c612017-10-14 12:15:02 +0200591 }
592
593 @Override
594 public void freeMeterId(DeviceId deviceId, MeterId meterId) {
Wailok Shumf013a782021-07-26 16:51:01 +0800595 MeterTableKey meterTableKey = MeterTableKey.key(deviceId, MeterScope.globalScope());
596 freeMeterId(meterTableKey, meterId);
597 }
598
pierventre26ac1512021-09-10 09:37:29 +0200599 protected void freeMeterId(DeviceId deviceId, MeterCellId meterCellId) {
600 MeterTableKey meterTableKey;
601 if (meterCellId.type() == PIPELINE_INDEPENDENT) {
602 meterTableKey = MeterTableKey.key(deviceId,
603 MeterScope.of(((PiMeterCellId) meterCellId).meterId().id()));
604 } else if (meterCellId.type() == INDEX) {
605 meterTableKey = MeterTableKey.key(deviceId, MeterScope.globalScope());
606 } else {
607 log.warn("Unable to free meter id unsupported cell type {}", meterCellId.type());
608 return;
609 }
610 freeMeterId(meterTableKey, meterCellId);
611 }
612
613 protected void freeMeterId(MeterTableKey meterTableKey, MeterCellId meterCellId) {
pierventre3b39bd82021-08-18 09:40:14 +0200614 if (userDefinedIndexMode) {
pierventre26ac1512021-09-10 09:37:29 +0200615 log.debug("Unable to free meter id when user defined index mode is enabled");
pierventre3b39bd82021-08-18 09:40:14 +0200616 return;
617 }
Wailok Shumf013a782021-07-26 16:51:01 +0800618 long index;
619 if (meterCellId.type() == PIPELINE_INDEPENDENT) {
620 PiMeterCellId piMeterCellId = (PiMeterCellId) meterCellId;
621 index = piMeterCellId.index();
622 } else if (meterCellId.type() == INDEX) {
623 MeterId meterId = (MeterId) meterCellId;
624 index = meterId.id();
625 } else {
pierventre26ac1512021-09-10 09:37:29 +0200626 log.warn("Unable to free meter id unsupported cell type {}", meterCellId.type());
Wailok Shumf013a782021-07-26 16:51:01 +0800627 return;
628 }
Pier Luigibdcd9672017-10-13 13:54:48 +0200629 // Avoid to free meter not allocated
Wailok Shum79919522021-08-22 19:35:34 +0800630 if (meterIdGenerators.get(meterTableKey) <= index) {
Pier Luigibdcd9672017-10-13 13:54:48 +0200631 return;
632 }
Wailok Shumf013a782021-07-26 16:51:01 +0800633 updateMeterIdAvailability(meterTableKey, meterCellId, true);
Pier Luigif094c612017-10-14 12:15:02 +0200634 }
635
pierventre1b8afbc2020-07-13 14:07:05 +0200636 // Enabling the events distribution across the cluster
Wailok Shumf013a782021-07-26 16:51:01 +0800637 private class InternalMetersMapEventListener implements MapEventListener<MeterKey, MeterData> {
alshabibeadfc8e2015-08-18 15:40:46 -0700638 @Override
HIGUCHI Yuta0574a552015-09-29 14:38:25 -0700639 public void event(MapEvent<MeterKey, MeterData> event) {
640 MeterKey key = event.key();
Ray Milkeyd0f017f2018-09-21 12:52:34 -0700641 Versioned<MeterData> value = event.type() == MapEvent.Type.REMOVE ? event.oldValue() : event.newValue();
642 MeterData data = value.value();
pierventre1b8afbc2020-07-13 14:07:05 +0200643 MeterData oldData = Versioned.valueOrNull(event.oldValue());
alshabibeadfc8e2015-08-18 15:40:46 -0700644 switch (event.type()) {
645 case INSERT:
646 case UPDATE:
647 switch (data.meter().state()) {
648 case PENDING_ADD:
649 case PENDING_REMOVE:
pierventre1b8afbc2020-07-13 14:07:05 +0200650 // Two cases. If there is a reason, the meter operation failed.
651 // Otherwise, we are ready to install/remove through the delegate.
652 if (data.reason().isEmpty()) {
653 notifyDelegate(new MeterEvent(data.meter().state() == MeterState.PENDING_ADD ?
654 MeterEvent.Type.METER_ADD_REQ : MeterEvent.Type.METER_REM_REQ, data.meter()));
655 } else {
Jordi Ortizdf28ecd2017-03-25 19:22:36 +0100656 futures.computeIfPresent(key, (k, v) -> {
pierventre1b8afbc2020-07-13 14:07:05 +0200657 v.complete(MeterStoreResult.fail(data.reason().get()));
Jordi Ortizdf28ecd2017-03-25 19:22:36 +0100658 return null;
659 });
alshabibe1248b62015-08-20 17:21:55 -0700660 }
661 break;
pierventre1b8afbc2020-07-13 14:07:05 +0200662 case ADDED:
663 // Transition from pending to installed
664 if (data.meter().state() == MeterState.ADDED &&
665 (oldData != null && oldData.meter().state() == MeterState.PENDING_ADD)) {
666 futures.computeIfPresent(key, (k, v) -> {
667 v.complete(MeterStoreResult.success());
668 return null;
669 });
670 notifyDelegate(new MeterEvent(MeterEvent.Type.METER_ADDED, data.meter()));
pierventrec0914ec2021-08-27 15:25:02 +0200671 // Update stats case - we report reference count zero only for INDEX based meters
672 } else if (data.meter().referenceCount() == 0 &&
673 data.meter().meterCellId().type() == INDEX) {
pierventre1b8afbc2020-07-13 14:07:05 +0200674 notifyDelegate(new MeterEvent(MeterEvent.Type.METER_REFERENCE_COUNT_ZERO,
675 data.meter()));
alshabibeadfc8e2015-08-18 15:40:46 -0700676 }
677 break;
678 default:
679 log.warn("Unknown meter state type {}", data.meter().state());
680 }
681 break;
682 case REMOVE:
pierventre1b8afbc2020-07-13 14:07:05 +0200683 futures.computeIfPresent(key, (k, v) -> {
684 v.complete(MeterStoreResult.success());
685 return null;
686 });
pierventre1b8afbc2020-07-13 14:07:05 +0200687 notifyDelegate(new MeterEvent(MeterEvent.Type.METER_REMOVED, data.meter()));
alshabibeadfc8e2015-08-18 15:40:46 -0700688 break;
689 default:
690 log.warn("Unknown Map event type {}", event.type());
691 }
alshabibeadfc8e2015-08-18 15:40:46 -0700692 }
693 }
694
Wailok Shumf013a782021-07-26 16:51:01 +0800695 private class InternalFeaturesMapEventListener implements
696 EventuallyConsistentMapListener<MeterTableKey, MeterFeatures> {
697 @Override
698 public void event(EventuallyConsistentMapEvent<MeterTableKey, MeterFeatures> event) {
699 MeterTableKey meterTableKey = event.key();
700 MeterFeatures meterFeatures = event.value();
701 switch (event.type()) {
702 case PUT:
Wailok Shumf013a782021-07-26 16:51:01 +0800703 String setName = AVAILABLEMETERIDSTORE + "-" +
704 meterFeatures.deviceId() + meterFeatures.scope().id();
705 insertAvailableKeySet(meterTableKey, setName);
706 break;
707 case REMOVE:
Wailok Shumf013a782021-07-26 16:51:01 +0800708 DistributedSet<MeterKey> set = availableMeterIds.remove(meterTableKey);
709 if (set != null) {
710 set.destroy();
711 }
712 break;
713 default:
714 break;
715 }
716 }
717 }
718
719 private void insertAvailableKeySet(MeterTableKey meterTableKey, String setName) {
720 DistributedSet<MeterKey> availableMeterIdSet =
721 new DefaultDistributedSet<>(storageService.<MeterKey>setBuilder()
722 .withName(setName)
723 .withSerializer(Serializer.using(KryoNamespaces.API,
724 MeterKey.class)).build(),
725 DistributedPrimitive.DEFAULT_OPERATION_TIMEOUT_MILLIS);
726 availableMeterIds.put(meterTableKey, availableMeterIdSet);
727 }
alshabib7bb05012015-08-05 10:15:09 -0700728}