/*
* Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
package org.opendaylight.openflowplugin.impl.statistics;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
import org.opendaylight.controller.md.sal.common.api.data.TransactionChainClosedException;
import org.opendaylight.openflowplugin.api.openflow.device.DeviceContext;
import org.opendaylight.openflowplugin.api.openflow.device.DeviceInfo;
import org.opendaylight.openflowplugin.api.openflow.device.DeviceRegistry;
import org.opendaylight.openflowplugin.api.openflow.device.TxFacade;
import org.opendaylight.openflowplugin.api.openflow.registry.group.DeviceGroupRegistry;
import org.opendaylight.openflowplugin.api.openflow.registry.meter.DeviceMeterRegistry;
import org.opendaylight.openflowplugin.api.openflow.statistics.ofpspecific.EventIdentifier;
import org.opendaylight.openflowplugin.api.openflow.statistics.ofpspecific.StatisticsGatherer;
import org.opendaylight.openflowplugin.impl.common.MultipartReplyTranslatorUtil;
import org.opendaylight.openflowplugin.impl.datastore.MultipartWriterProvider;
import org.opendaylight.openflowplugin.impl.statistics.ofpspecific.EventsTimeCounter;
import org.opendaylight.openflowplugin.openflow.md.core.sal.convertor.ConvertorExecutor;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.DateAndTime;
import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableStatisticsGatheringStatus;
import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableStatisticsGatheringStatusBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.meters.Meter;
import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.meters.MeterKey;
import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.snapshot.gathering.status.grouping.SnapshotGatheringStatusEnd;
import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.snapshot.gathering.status.grouping.SnapshotGatheringStatusEndBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.snapshot.gathering.status.grouping.SnapshotGatheringStatusStartBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.Table;
import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.TableBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.groups.Group;
import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.groups.GroupKey;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.common.types.rev130731.MultipartType;
import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.OfHeader;
import org.opendaylight.yangtools.yang.binding.DataContainer;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
import org.opendaylight.yangtools.yang.common.RpcResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Utils for gathering statistics
*/
public final class StatisticsGatheringUtils {
private static final String DATE_AND_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
private static final Logger LOG = LoggerFactory.getLogger(StatisticsGatheringUtils.class);
private static final String QUEUE2_REQCTX = "QUEUE2REQCTX-";
private StatisticsGatheringUtils() {
throw new IllegalStateException("This class should not be instantiated.");
}
static <T extends OfHeader>ListenableFuture<Boolean> gatherStatistics(final StatisticsGatherer<T> statisticsGatheringService,
final DeviceInfo deviceInfo,
final MultipartType type,
final TxFacade txFacade,
final DeviceRegistry registry,
final Boolean initial,
final ConvertorExecutor convertorExecutor,
final MultipartWriterProvider statisticsWriterProvider) {
final EventIdentifier eventIdentifier;
if (MultipartType.OFPMPFLOW.equals(type)) {
eventIdentifier = new EventIdentifier(type.toString(), deviceInfo.getNodeId().getValue());
EventsTimeCounter.markStart(eventIdentifier);
} else {
eventIdentifier = null;
}
return Futures.transform(
statisticsGatheringService.getStatisticsOfType(
new EventIdentifier(QUEUE2_REQCTX + type.toString(), deviceInfo.getNodeId().toString()),
type),
new AsyncFunction<RpcResult<List<T>>, Boolean>() {
@Nullable
@Override
public ListenableFuture<Boolean> apply(@Nonnull final RpcResult<List<T>> rpcResult) {
boolean isMultipartProcessed = Boolean.TRUE;
if (rpcResult.isSuccessful()) {
LOG.debug("Stats reply successfully received for node {} of type {}", deviceInfo.getNodeId(), type);
// TODO: in case the result value is null then multipart data probably got processed on the fly -
// TODO: this contract should by clearly stated and enforced - now simple true value is returned
if (Objects.nonNull(rpcResult.getResult()) && !rpcResult.getResult().isEmpty()) {
final List<DataContainer> allMultipartData;
try {
allMultipartData = rpcResult
.getResult()
.stream()
.map(reply -> MultipartReplyTranslatorUtil
.translate(reply, deviceInfo, convertorExecutor, null))
.filter(java.util.Optional::isPresent)
.map(java.util.Optional::get)
.collect(Collectors.toList());
} catch (final Exception e) {
LOG.warn("Stats processing of type {} for node {} failed during transformation step",
type, deviceInfo.getLOGValue(), e);
return Futures.immediateFailedFuture(e);
}
try {
return processStatistics(type, allMultipartData, txFacade, registry, deviceInfo,
statisticsWriterProvider,
eventIdentifier, initial);
} catch (final Exception e) {
LOG.warn("Stats processing of type {} for node {} failed during processing step",
type, deviceInfo.getNodeId(), e);
return Futures.immediateFailedFuture(e);
}
} else {
LOG.debug("Stats reply was empty for node {} of type {}", deviceInfo.getNodeId(), type);
}
} else {
LOG.warn("Stats reply FAILED for node {} of type {}: {}", deviceInfo.getNodeId(), type,
rpcResult.getErrors());
isMultipartProcessed = Boolean.FALSE;
}
return Futures.immediateFuture(isMultipartProcessed);
}
});
}
private static ListenableFuture<Boolean> processStatistics(final MultipartType type,
final List<? extends DataContainer> statistics,
final TxFacade txFacade,
final DeviceRegistry deviceRegistry,
final DeviceInfo deviceInfo,
final MultipartWriterProvider statisticsWriterProvider,
final EventIdentifier eventIdentifier,
final boolean initial) {
ListenableFuture<Void> future = Futures.immediateFuture(null);
final InstanceIdentifier<FlowCapableNode> instanceIdentifier = deviceInfo
.getNodeInstanceIdentifier()
.augmentation(FlowCapableNode.class);
switch (type) {
case OFPMPFLOW:
future = deleteAllKnownFlows(txFacade, instanceIdentifier, initial);
break;
case OFPMPMETERCONFIG:
deleteAllKnownMeters(txFacade, instanceIdentifier, deviceRegistry.getDeviceMeterRegistry());
break;
case OFPMPGROUPDESC:
deleteAllKnownGroups(txFacade, instanceIdentifier, deviceRegistry.getDeviceGroupRegistry());
break;
}
return Futures.transform(future, (Function<Void, Boolean>) input -> {
if (writeStatistics(type, statistics, deviceInfo, statisticsWriterProvider)) {
txFacade.submitTransaction();
if (MultipartType.OFPMPFLOW.equals(type)) {
EventsTimeCounter.markEnd(eventIdentifier);
deviceRegistry.getDeviceFlowRegistry().processMarks();
}
LOG.debug("Stats reply added to transaction for node {} of type {}", deviceInfo.getNodeId(), type);
return Boolean.TRUE;
}
LOG.warn("Stats processing of type {} for node {} failed during write-to-tx step", type, deviceInfo.getLOGValue());
return Boolean.FALSE;
});
}
private static boolean writeStatistics(final MultipartType type,
final List<? extends DataContainer> statistics,
final DeviceInfo deviceInfo,
final MultipartWriterProvider statisticsWriterProvider) {
final AtomicBoolean result = new AtomicBoolean(false);
try {
statistics.forEach(stat -> statisticsWriterProvider.lookup(type).ifPresent(p -> {
final boolean write = p.write(stat, false);
if (!result.get()) {
result.set(write);
}
}));
} catch (final Exception ex) {
LOG.warn("Stats processing of type {} for node {} failed during write-to-tx step", type, deviceInfo.getLOGValue(), ex);
}
return result.get();
}
public static ListenableFuture<Void> deleteAllKnownFlows(final TxFacade txFacade,
final InstanceIdentifier<FlowCapableNode> instanceIdentifier,
final boolean initial) {
if (initial) {
return Futures.immediateFuture(null);
}
final ReadOnlyTransaction readTx = txFacade.getReadTransaction();
return Futures.transform(Futures
.withFallback(readTx.read(LogicalDatastoreType.OPERATIONAL, instanceIdentifier), t -> {
// we wish to close readTx for fallBack
readTx.close();
return Futures.immediateFailedFuture(t);
}), (Function<Optional<FlowCapableNode>, Void>)
flowCapNodeOpt -> {
// we have to read actual tables with all information before we set empty Flow list, merge is expensive and
// not applicable for lists
if (flowCapNodeOpt != null && flowCapNodeOpt.isPresent()) {
for (final Table tableData : flowCapNodeOpt.get().getTable()) {
final Table table = new TableBuilder(tableData).setFlow(Collections.emptyList()).build();
final InstanceIdentifier<Table> iiToTable = instanceIdentifier.child(Table.class, tableData.getKey());
txFacade.writeToTransaction(LogicalDatastoreType.OPERATIONAL, iiToTable, table);
}
}
readTx.close();
return null;
});
}
private static void deleteAllKnownMeters(final TxFacade txFacade,
final InstanceIdentifier<FlowCapableNode> instanceIdentifier,
final DeviceMeterRegistry meterRegistry) {
meterRegistry.forEach(meterId -> txFacade
.addDeleteToTxChain(
LogicalDatastoreType.OPERATIONAL,
instanceIdentifier.child(Meter.class, new MeterKey(meterId))));
meterRegistry.processMarks();
}
private static void deleteAllKnownGroups(final TxFacade txFacade,
final InstanceIdentifier<FlowCapableNode> instanceIdentifier,
final DeviceGroupRegistry groupRegistry) {
groupRegistry.forEach(groupId -> txFacade
.addDeleteToTxChain(
LogicalDatastoreType.OPERATIONAL,
instanceIdentifier.child(Group.class, new GroupKey(groupId))));
groupRegistry.processMarks();
}
/**
* Writes snapshot gathering start timestamp + cleans end mark
*
* @param deviceContext txManager + node path keeper
*/
static void markDeviceStateSnapshotStart(final DeviceContext deviceContext) {
final InstanceIdentifier<FlowCapableStatisticsGatheringStatus> statusPath = deviceContext.getDeviceInfo()
.getNodeInstanceIdentifier().augmentation(FlowCapableStatisticsGatheringStatus.class);
final SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DATE_AND_TIME_FORMAT);
final FlowCapableStatisticsGatheringStatus gatheringStatus = new FlowCapableStatisticsGatheringStatusBuilder()
.setSnapshotGatheringStatusStart(new SnapshotGatheringStatusStartBuilder()
.setBegin(new DateAndTime(simpleDateFormat.format(new Date())))
.build())
.setSnapshotGatheringStatusEnd(null) // TODO: reconsider if really need to clean end mark here
.build();
try {
deviceContext.writeToTransaction(LogicalDatastoreType.OPERATIONAL, statusPath, gatheringStatus);
} catch (final TransactionChainClosedException e) {
LOG.warn("Can't write to transaction, transaction chain probably closed.");
LOG.trace("Write to transaction exception: ", e);
}
deviceContext.submitTransaction();
}
/**
* Writes snapshot gathering end timestamp + outcome
*
* @param deviceContext txManager + node path keeper
* @param succeeded outcome of currently finished gathering
*/
static void markDeviceStateSnapshotEnd(final DeviceContext deviceContext, final boolean succeeded) {
final InstanceIdentifier<SnapshotGatheringStatusEnd> statusEndPath = deviceContext.getDeviceInfo()
.getNodeInstanceIdentifier().augmentation(FlowCapableStatisticsGatheringStatus.class)
.child(SnapshotGatheringStatusEnd.class);
final SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DATE_AND_TIME_FORMAT);
final SnapshotGatheringStatusEnd gatheringStatus = new SnapshotGatheringStatusEndBuilder()
.setEnd(new DateAndTime(simpleDateFormat.format(new Date())))
.setSucceeded(succeeded)
.build();
try {
deviceContext.writeToTransaction(LogicalDatastoreType.OPERATIONAL, statusEndPath, gatheringStatus);
} catch (TransactionChainClosedException e) {
LOG.warn("Can't write to transaction, transaction chain probably closed.");
LOG.trace("Write to transaction exception: ", e);
}
deviceContext.submitTransaction();
}
}