All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opendaylight.daexim.impl.DataExportImportAppProvider Maven / Gradle / Ivy

/*
 * Copyright (C) 2016 AT&T Intellectual Property. All rights reserved.
 * Copyright (c) 2016 Brocade Communications Systems, Inc. All rights reserved.
 * Copyright (c) 2017 Red Hat, 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.daexim.impl;

import static java.util.Objects.requireNonNull;
import static org.opendaylight.mdsal.common.api.LogicalDatastoreType.CONFIGURATION;
import static org.opendaylight.mdsal.common.api.LogicalDatastoreType.OPERATIONAL;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.daexim.DataImportBootReady;
import org.opendaylight.daexim.DataImportBootService;
import org.opendaylight.daexim.spi.NodeNameProvider;
import org.opendaylight.infrautils.ready.SystemReadyMonitor;
import org.opendaylight.infrautils.utils.concurrent.ThreadFactoryProvider;
import org.opendaylight.mdsal.binding.api.DataBroker;
import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
import org.opendaylight.mdsal.binding.api.DataTreeModification;
import org.opendaylight.mdsal.binding.api.ReadTransaction;
import org.opendaylight.mdsal.binding.api.RpcProviderService;
import org.opendaylight.mdsal.binding.api.WriteTransaction;
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
import org.opendaylight.mdsal.dom.api.DOMDataBroker;
import org.opendaylight.mdsal.dom.api.DOMSchemaService;
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.daexim.internal.rev160921.Daexim;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.internal.rev160921.ImportOperationResult;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.internal.rev160921.IpcType;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.internal.rev160921.daexim.DaeximControl;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.internal.rev160921.daexim.DaeximControlBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.internal.rev160921.daexim.DaeximStatus;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.internal.rev160921.daexim.DaeximStatusBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.internal.rev160921.daexim.daexim.status.NodeStatus;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.internal.rev160921.daexim.daexim.status.NodeStatusBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.internal.rev160921.daexim.daexim.status.NodeStatusKey;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.rev160921.AbsoluteTime;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.rev160921.CancelExport;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.rev160921.CancelExportInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.rev160921.CancelExportOutput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.rev160921.CancelExportOutputBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.rev160921.DataStoreScope;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.rev160921.ImmediateImport;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.rev160921.ImmediateImportInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.rev160921.ImmediateImportInputBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.rev160921.ImmediateImportOutput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.rev160921.ImmediateImportOutputBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.rev160921.OperationStatus;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.rev160921.ScheduleExport;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.rev160921.ScheduleExportInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.rev160921.ScheduleExportInput.RunAt;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.rev160921.ScheduleExportOutput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.rev160921.ScheduleExportOutputBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.rev160921.Status;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.rev160921.StatusExport;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.rev160921.StatusExportInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.rev160921.StatusExportOutput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.rev160921.StatusExportOutputBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.rev160921.StatusImport;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.rev160921.StatusImportInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.rev160921.StatusImportOutput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.rev160921.StatusImportOutputBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.rev160921.status.export.output.Nodes;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.rev160921.status.export.output.NodesBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.rev160921.status.export.output.NodesKey;
import org.opendaylight.yangtools.binding.util.BindingMap;
import org.opendaylight.yangtools.concepts.Registration;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
import org.opendaylight.yangtools.yang.common.ErrorType;
import org.opendaylight.yangtools.yang.common.RpcResult;
import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
import org.osgi.service.component.ComponentFactory;
import org.osgi.service.component.ComponentInstance;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.RequireServiceComponentRuntime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Singleton
@Component(immediate = true, service = DataImportBootService.class)
@RequireServiceComponentRuntime
public final class DataExportImportAppProvider implements DataImportBootService, AutoCloseable {
    private static final Logger LOG = LoggerFactory.getLogger(DataExportImportAppProvider.class);
    private static final InstanceIdentifier TOP_IID = InstanceIdentifier.create(Daexim.class);
    private static final InstanceIdentifier GLOBAL_STATUS_II = TOP_IID.child(DaeximStatus.class);
    private static final InstanceIdentifier IPC_II = TOP_IID.child(DaeximControl.class);
    private static final DataTreeIdentifier IPC_DTC = DataTreeIdentifier.of(OPERATIONAL, IPC_II);

    private final AtomicBoolean skipIpcDCN = new AtomicBoolean(false);
    private final DataBroker dataBroker;
    private final DOMDataBroker domDataBroker;
    private final DOMSchemaService schemaService;
    private final NodeNameProvider nodeNameProvider;

    private final ListeningScheduledExecutorService scheduledExecutorService;
    private final InstanceIdentifier nodeStatusII;
    private final Registration rpcReg;

    private final ComponentFactory factory;
    private ComponentInstance componentReg;

    private ListenableFuture exportSchedule;
    private volatile Status exportStatus = Status.Initial;
    private volatile Status importStatus = Status.Initial;
    private volatile String exportFailure = null;
    private volatile String importFailure = null;
    private volatile long lastImportTimestamp = -1;
    private volatile long lastImportChanged = -1;
    private volatile long lastExportChanged = -1;

    @Inject
    @Activate
    public DataExportImportAppProvider(@Reference DataBroker dataBroker, @Reference DOMDataBroker domDataBroker,
            @Reference DOMSchemaService schemaService, @Reference NodeNameProvider nodeNameProvider,
            @Reference RpcProviderService rpcProvider, @Reference SystemReadyMonitor systemReadyService,
            @Reference(target = "(component.factory=" + OSGiDataImportBootReady.FACTORY_NAME + ")")
            ComponentFactory factory) {
        this.dataBroker = requireNonNull(dataBroker);
        this.domDataBroker = requireNonNull(domDataBroker);
        this.schemaService = requireNonNull(schemaService);
        this.nodeNameProvider = requireNonNull(nodeNameProvider);
        this.factory = requireNonNull(factory);

        nodeStatusII = GLOBAL_STATUS_II.child(NodeStatus.class, new NodeStatusKey(nodeNameProvider.getNodeName()));
        if (readDaeximControl() != null) {
            skipIpcDCN.set(true);
        }
        dataBroker.registerTreeChangeListener(IPC_DTC, this::ipcHandler);
        updateNodeStatus();
        scheduledExecutorService = MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(10,
                ThreadFactoryProvider.builder().namePrefix("daexim-scheduler").logger(LOG).build().get()));
        LOG.info("Daexim Session Initiated, running on node '{}'", nodeNameProvider.getNodeName());

        final var bootImportConfigurationDataFile = Util.getDaeximFilePath(true, CONFIGURATION);
        final var bootImportOperationalDataFile = Util.getDaeximFilePath(true, OPERATIONAL);
        LOG.info("Checking for presence of boot import data files ({}, {})",
                bootImportConfigurationDataFile, bootImportOperationalDataFile);
        if (Files.exists(bootImportOperationalDataFile) || Files.exists(bootImportConfigurationDataFile)) {
            LOG.info("Daexim found files to import on boot, and will import them once the system is fully ready...");
            updateImportStatus(Status.BootImportScheduled);
            systemReadyService.registerListener(() -> {
                updateImportStatus(Status.BootImportInProgress);
                LOG.info("Daexim found files to import on boot; importing them now that the system is fully ready...");

                // FIXME: this is wrong, as highlighted by testImportOnBootWithBrokenJSON() failing sometimes:
                //        - immediateImport() fails and sets the status
                //        - then the test sees import as completed, checks the file
                //        - then this callback runs, renaming the file
                Futures.addCallback(immediateImport(new ImmediateImportInputBuilder()
                        .setCheckModels(Util.isModelFilePresent(true))
                        .setClearStores(DataStoreScope.None)
                        .setStrictDataConsistency(true)
                    .build(),
                    true), new FutureCallback<>() {
                        @Override
                        public void onSuccess(RpcResult result) {
                            if (!result.isSuccessful()
                                    || !result.getErrors().isEmpty()
                                    || !result.getResult().getResult()) {
                                failed(null);
                            } else {
                                renameBootImportFiles();
                                registerDataImportBootReady();
                            }
                        }

                        @Override
                        public void onFailure(Throwable throwable) {
                            failed(throwable);
                        }

                        private void failed(Throwable throwable) {
                            renameBootImportFiles();
                            if (throwable != null) {
                                LOG.warn("Daexim import on boot failed :(", throwable);
                            } else {
                                LOG.warn("Daexim import on boot failed :(");
                            }
                        }
                    },
                    MoreExecutors.directExecutor());
            });
        } else {
            registerDataImportBootReady();
        }

        rpcReg = rpcProvider.registerRpcImplementations(
            (ScheduleExport) this::scheduleExport,
            (StatusExport) this::statusExport,
            (CancelExport) this::cancelExport,
            (ImmediateImport) this::immediateImport,
            (StatusImport) this::statusImport);
    }

    void registerDataImportBootReady() {
        // publish an instance of DataImportBootReady into the OSGi service registry
        componentReg = factory.newInstance(null);
    }

    private static void renameBootImportFiles() {
        boolean renamedAtLeastOneFile = false;
        renamedAtLeastOneFile = renameFile(Util.getModelsFilePath(true));
        renamedAtLeastOneFile |= renameFile(Util.getDaeximFilePath(true, CONFIGURATION));
        renamedAtLeastOneFile |= renameFile(Util.getDaeximFilePath(true, OPERATIONAL));
        if (renamedAtLeastOneFile) {
            // LOG level warn instead of info just so that this message is logged in production where info may disabled
            LOG.warn("Daexim import on boot succesfully completed; renamed files to prevent re-import on next boot");
        }
    }

    private static boolean renameFile(Path file) {
        try {
            if (file.toFile().exists()) {
                final Path renamedFile = file.resolveSibling(file.getFileName().toString() + ".imported");
                Files.move(file, renamedFile, StandardCopyOption.ATOMIC_MOVE);
                // There was failure on CI because original file still exists despite using ATOMIC_MOVE
                int counter = 10;
                while (counter-- > 0) {
                    if (!file.toFile().exists()) {
                        break;
                    } else {
                        TimeUnit.MILLISECONDS.sleep(200);
                    }
                }
                if (file.toFile().exists()) {
                    throw new IllegalStateException();
                }
                LOG.info("Renamed {} to {}", file, renamedFile);
            }
            return true;
        } catch (IOException e) {
            LOG.error("Failed to rename file: {}", file.toString(), e);
            return false;
        } catch (IllegalStateException | InterruptedException e) {
            LOG.error("Failed to wait for original file to vanish: {}", file, e);
            return false;
        }
    }

    /*
     * Invoked when IPC has been posted to control data structure
     */
    private void ipcHandler(final Collection> changes) {
        if (skipIpcDCN.compareAndSet(true, false)) {
            return;
        }
        final DaeximControl newTask = changes.iterator().next().getRootNode().dataAfter();
        if (newTask != null) {
            LOG.info("IPC received : {}", newTask);
            if (newTask.getRunOnNode() != null
                    && !Objects.equals(newTask.getRunOnNode(), nodeNameProvider.getNodeName())) {
                exportFailure = null;
                updateExportStatus(Status.Skipped);
                updateNodeStatus();
                LOG.info("Export task skipped");
                return;
            }
            switch (newTask.getTaskType()) {
                case Cancel:
                    processCancel();
                    break;
                case Schedule:
                    processSchedule(newTask);
                    break;
                default:
                    throw new IllegalArgumentException("Invalid IPC : " + newTask.getTaskType());
            }
        }
    }

    private void processSchedule(DaeximControl newTask) {
        updateExportStatus(Status.Scheduled);
        long scheduleAtTimestamp = Util.parseDate(newTask.getRunAt().getValue()).getTime();
        exportSchedule = scheduledExecutorService.schedule(new ExportTask(newTask.getIncludedModules(),
            newTask.getExcludedModules(), newTask.getStrictDataConsistency(), newTask.getSplitByModule(),
            domDataBroker, schemaService, notUsed -> updateExportStatus(Status.InProgress)),
            scheduleAtTimestamp - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
        Futures.addCallback(exportSchedule, new FutureCallback<>() {
            @Override
            public void onSuccess(Void result) {
                exportFailure = null;
                updateExportStatus(Status.Complete);
                LOG.info("Export task success");
            }

            @Override
            public void onFailure(Throwable throwable) {
                if (throwable instanceof CancellationException) {
                    LOG.info("Previous export has been cancelled");
                } else {
                    LOG.error("Export failed", throwable);
                    exportFailure = throwable.getMessage();
                    updateExportStatus(Status.Failed);
                }
            }
        }, MoreExecutors.directExecutor());
    }

    private void processCancel() {
        Status newStatus = exportStatus;
        if (Status.InProgress.equals(newStatus) || Status.Scheduled.equals(newStatus)) {
            newStatus = Status.Initial;
        }
        // Cancel/Unschedule
        cancelScheduleInternal();
        updateExportStatus(newStatus);
    }

    /*
     * Invoke IPC
     */
    private void invokeIPC(DaeximControl ctl) throws InterruptedException, ExecutionException {
        final WriteTransaction wTrx = dataBroker.newWriteOnlyTransaction();
        wTrx.put(OPERATIONAL, IPC_II, ctl);
        wTrx.commit().get();
    }

    /*
     * Update status of local node
     */
    private synchronized void updateNodeStatus() {
        final WriteTransaction wTrx = dataBroker.newWriteOnlyTransaction();
        wTrx.put(LogicalDatastoreType.OPERATIONAL, nodeStatusII, createNodeStatusData());
        try {
            wTrx.commit().get();
        } catch (InterruptedException | ExecutionException e) {
            LOG.error("Failed to update local node status", e);
        }
    }

    private NodeStatus createNodeStatusData() {
        return new NodeStatusBuilder()
            .setExportStatus(exportStatus)
            .setExportResult(exportFailure)
            .setImportStatus(importStatus)
            .setImportResult(importFailure)
            .setDataFiles(Util.collectDataFiles(false).values().stream()
                .map(File::getAbsolutePath)
                .collect(ImmutableSet.toImmutableSet()))
            .setNodeName(nodeNameProvider.getNodeName())
            .setModelFile(Util.isModelFilePresent(false) ? Util.getModelsFilePath(false).toString() : null)
            .setLastExportChange(
                lastExportChanged != -1 ? new AbsoluteTime(Util.toDateAndTime(new Date(lastExportChanged))) : null)
            .setLastImportChange(
                lastImportChanged != -1 ? new AbsoluteTime(Util.toDateAndTime(new Date(lastImportChanged))) : null)
            .setImportedAt(
                lastImportTimestamp != -1 ? new AbsoluteTime(Util.toDateAndTime(new Date(lastImportTimestamp))) : null)
            .build();
    }

    @Nullable
    private DaeximControl readDaeximControl() {
        final ListenableFuture> future;
        try (ReadTransaction roTrx = dataBroker.newReadOnlyTransaction()) {
            future = roTrx.read(LogicalDatastoreType.OPERATIONAL, IPC_II);
        }

        try {
            return future.get().orElse(null);
        } catch (InterruptedException | ExecutionException e) {
            LOG.warn("Failed to read IPC", e);
            return null;
        }
    }

    /*
     * Read global status
     */
    private DaeximStatus readGlobalStatus() throws InterruptedException, ExecutionException {
        final ListenableFuture> future;
        try (ReadTransaction roTrx = dataBroker.newReadOnlyTransaction()) {
            future = roTrx.read(LogicalDatastoreType.OPERATIONAL, GLOBAL_STATUS_II);
        }

        // After restore, our top level elements are gone
        return future.get().orElseGet(this::rebuildGlobalStatus);
    }

    /*
     * Initializes status of local node and return global status with local node's
     * status included in it
     */
    private DaeximStatus rebuildGlobalStatus() {
        exportStatus = Status.Initial;
        importStatus = Status.Initial;
        LOG.info("Global status is not yet created");
        updateNodeStatus();
        return new DaeximStatusBuilder().setNodeStatus(BindingMap.of(createNodeStatusData())).build();
    }

    /**
     * This function calculate runtime global status.
* Here are rules: *

*

    *
  1. If all nodes are {@link Status#Complete} then then return * {@link Status#Complete}
  2. *
  3. If at least one node is {@link Status#InProgress} then return * {@link Status#InProgress}
  4. *
  5. If at least one node is {@link Status#Scheduled} then return * {@link Status#Scheduled}
  6. *
  7. If at least one node is {@link Status#Failed} then return * {@link Status#Failed}
  8. *
  9. If all nodes are {@link Status#Initial} then then return * {@link Status#Initial}
  10. *
  11. If none of previous are true, then return * {@value Status#Inconsistent} *
*/ private static Status calculateStatus(final List nodes) { boolean inProgress = false; boolean isComplete = true; boolean isFailed = false; boolean isInitial = true; boolean isScheduled = false; for (final Nodes t : nodes) { switch (t.getStatus()) { // at least one export is in progress case InProgress: isInitial = false; isComplete = false; inProgress = true; break; // all nodes completed their job, we are done case Complete: isComplete &= true; break; // any node failed case Failed: isInitial = false; isComplete = false; isFailed = true; break; // all nodes are in initial status case Initial: isComplete = false; isInitial &= true; break; case Scheduled: isInitial = false; isComplete = false; isScheduled = true; break; default: break; } } if (isComplete) { return Status.Complete; } if (inProgress) { return Status.InProgress; } if (isScheduled) { return Status.Scheduled; } if (isFailed) { return Status.Failed; } if (isInitial) { return Status.Initial; } return Status.Inconsistent; } private void cancelScheduleInternal() { if (exportSchedule != null) { exportSchedule.cancel(true); exportSchedule = null; } updateExportStatus(Status.Initial); } /** * Method called when provider is about to close. */ @Override @PreDestroy @Deactivate public void close() { rpcReg.close(); if (componentReg != null) { componentReg.dispose(); componentReg = null; } if (scheduledExecutorService != null) { scheduledExecutorService.shutdownNow(); } LOG.info("{} closed", getClass().getSimpleName()); } // RPC Methods /** * Cancels any pending or active export tasks. */ @VisibleForTesting ListenableFuture> cancelExport(CancelExportInput input) { try { // FIXME: this should be asynchronous! invokeIPC(new DaeximControlBuilder().setTaskType(IpcType.Cancel).build()); return RpcResultBuilder.success(new CancelExportOutputBuilder() .setResult(true) .build()).buildFuture(); } catch (ExecutionException | InterruptedException e) { return RpcResultBuilder.failed() .withResult(new CancelExportOutputBuilder() .setResult(false) .setReason(e.getMessage()) .build()) .withError(ErrorType.APPLICATION, e.getMessage(), e) .buildFuture(); } } /** * Schedule export. */ @VisibleForTesting ListenableFuture> scheduleExport(ScheduleExportInput input) { Objects.requireNonNull(input, "input"); awaitBootImport("DataExportImport.scheduleExport()"); long scheduleAtTimestamp; final ScheduleExportOutputBuilder outputBuilder = new ScheduleExportOutputBuilder(); final RunAt runAt = input.getRunAt(); if (runAt == null) { return RpcResultBuilder.failed() .withError(ErrorType.PROTOCOL, "No schedule info present in request (run-at)") .buildFuture(); } final String scheduledString; if (runAt.getRelativeTime() != null) { scheduleAtTimestamp = System.currentTimeMillis() + runAt.getRelativeTime().getValue().toJava() * 10; scheduledString = Util.dateToUtcString(new Date(scheduleAtTimestamp)); } else { scheduledString = runAt.getAbsoluteTime().getValue(); scheduleAtTimestamp = Util.parseDate(scheduledString).getTime(); } // verify that we are not trying to schedule in the past if (scheduleAtTimestamp < System.currentTimeMillis()) { return RpcResultBuilder.failed() .withError(ErrorType.PROTOCOL, "Attempt to schedule export in past") .buildFuture(); } cancelScheduleInternal(); LOG.info("Scheduling export at {}, which is {} seconds in the future", scheduledString, (scheduleAtTimestamp - System.currentTimeMillis()) / 1000); try { final DaeximControlBuilder builder = new DaeximControlBuilder() .setTaskType(IpcType.Schedule) .setStrictDataConsistency(input.getStrictDataConsistency()) .setIncludedModules(input.getIncludedModules()) .setExcludedModules(input.getExcludedModules()) .setRunAt(new AbsoluteTime(new DateAndTime(Util.toDateAndTime(new Date(scheduleAtTimestamp))))) .setSplitByModule(input.getSplitByModule()); if (Boolean.TRUE.equals(input.getLocalNodeOnly())) { builder.setRunOnNode(nodeNameProvider.getNodeName()); } // FIXME: this should be asynchronous! invokeIPC(builder.build()); outputBuilder.setResult(true); return RpcResultBuilder.success(outputBuilder.build()).buildFuture(); } catch (ExecutionException | InterruptedException e) { LOG.error("scheduleExport() failed", e); outputBuilder.setResult(false); return RpcResultBuilder.failed() .withError(ErrorType.APPLICATION, e.getMessage(), e).withResult(outputBuilder.build()) .buildFuture(); } } /** * Pending export status. */ ListenableFuture> statusExport(StatusExportInput input) { final StatusExportOutputBuilder builder = new StatusExportOutputBuilder(); try { final DaeximStatus gs = readGlobalStatus(); final List tasks = gs.nonnullNodeStatus().values().stream() .map(nodeStatus -> { final var nb = new NodesBuilder() .setReason(nodeStatus.getExportResult()) .withKey(new NodesKey(nodeStatus.getNodeName())) .setStatus(nodeStatus.getExportStatus()); if (Status.Complete.equals(nodeStatus.getExportStatus())) { nb.setModelFile(nodeStatus.getModelFile()).setDataFiles(nodeStatus.getDataFiles()); } return nb .setLastChange(nodeStatus.getLastExportChange()) .build(); }) .collect(Collectors.toList()); final Status s = calculateStatus(tasks); builder.setStatus(s); if (Status.Scheduled.equals(s)) { final DaeximControl ctrl = readDaeximControl(); if (ctrl == null) { return RpcResultBuilder.failed() .withError(ErrorType.APPLICATION, "Missing control data") .buildFuture(); } builder.setRunAt(ctrl.getRunAt()); } builder.setNodes(BindingMap.of(tasks)); return RpcResultBuilder.success(builder.build()).buildFuture(); } catch (ExecutionException | InterruptedException e) { LOG.error("statusExport() failed", e); return RpcResultBuilder.failed() .withError(ErrorType.APPLICATION, e.getMessage(), e) .buildFuture(); } } /** * Immediate restore operation. */ @VisibleForTesting ListenableFuture> immediateImport(ImmediateImportInput input) { Objects.requireNonNull(input, "input"); awaitBootImport("DataExportImport.immediateImport()"); return immediateImport(input, false); } private ListenableFuture> immediateImport(ImmediateImportInput input, boolean isBooting) { final var importFuture = scheduledExecutorService.submit( new ImportTask(input, domDataBroker, schemaService, isBooting, () -> { if (!isBooting) { // if isBooting then we've set another status before calling this // (it's important that happens immediately, without waiting for the Executor) updateImportStatus(Status.InProgress); } })); Futures.addCallback(importFuture, new FutureCallback<>() { @Override public void onSuccess(ImportOperationResult result) { LOG.info("Restore operation finished : {}", result); if (!result.getResult()) { importFailure = result.getReason(); if (isBooting) { updateImportStatus(Status.BootImportFailed); } else { updateImportStatus(Status.Failed); } } else { lastImportTimestamp = System.currentTimeMillis(); updateImportStatus(Status.Complete); } } @Override public void onFailure(Throwable throwable) { LOG.info("Restore operation failed", throwable); lastImportTimestamp = -1; importFailure = throwable.getMessage(); updateImportStatus(Status.Failed); } }, MoreExecutors.directExecutor()); return Futures.transform(importFuture, inputResult -> { final ImmediateImportOutputBuilder output = new ImmediateImportOutputBuilder(); output.setResult(inputResult.getResult()); if (!inputResult.getResult()) { output.setReason(inputResult.getReason()); return RpcResultBuilder.success().withResult(output.build()).build(); } return RpcResultBuilder.success(output.build()).build(); }, MoreExecutors.directExecutor()); } @Override public OperationStatus statusImportOnLocalNode() { return new StatusImportOutputBuilder().setStatus(importStatus).setReason(importFailure).build(); } @Override @Deprecated public void awaitBootImport(String blockingWhat) { if (Status.BootImportFailed.equals(importStatus)) { throw new IllegalStateException(importFailure); } else if (Status.BootImportInProgress.equals(importStatus)) { while (Status.BootImportInProgress.equals(importStatus)) { LOG.warn("awaitBootImport() blocking {}, waiting 5s more...", blockingWhat); try { Thread.sleep(5000); } catch (InterruptedException e) { LOG.error("awaitBootImport() boot-import-in-progress InterruptedException " + "- import not finished, returning anyway", e); return; } } // recursive self invocation just to avoid copy/paste of BootImportFailed handling awaitBootImport(blockingWhat); } } /** * Import status RPC. */ @VisibleForTesting ListenableFuture> statusImport(StatusImportInput input) { final StatusImportOutputBuilder builder = new StatusImportOutputBuilder(); try { final DaeximStatus gs = readGlobalStatus(); final var nodes = gs.nonnullNodeStatus().values().stream().map(nodeStatus -> { final org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.rev160921.status._import.output .NodesBuilder nb = new org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.rev160921 .status._import.output.NodesBuilder(); if (Status.Complete.equals(nodeStatus.getImportStatus())) { nb.setImportedAt(nodeStatus.getImportedAt()); } nb.setReason(nodeStatus.getImportResult()); nb.setModelFile(nodeStatus.getModelFile()); nb.setDataFiles(nodeStatus.getDataFiles()); nb.setStatus(nodeStatus.getImportStatus()); if (nodeStatus.getLastImportChange() != null) { nb.setLastChange(nodeStatus.getLastImportChange()); } nb.withKey(new org.opendaylight.yang.gen.v1.urn.opendaylight.daexim.rev160921 .status._import.output.NodesKey(nodeStatus.getNodeName())); return nb.build(); }).collect(BindingMap.toMap()); builder.setStatus(importStatus); builder.setNodes(nodes); return RpcResultBuilder.success(builder.build()).buildFuture(); } catch (ExecutionException | InterruptedException e) { LOG.error("statusImport() failed", e); return RpcResultBuilder.failed() .withError(ErrorType.APPLICATION, e.getMessage(), e) .buildFuture(); } } private void updateExportStatus(Status newStatus) { if (!exportStatus.equals(newStatus)) { lastExportChanged = System.currentTimeMillis(); LOG.debug("Export status transition from {} to {} at {}", exportStatus, newStatus, lastExportChanged); exportStatus = newStatus; updateNodeStatus(); } } private void updateImportStatus(Status newStatus) { if (!importStatus.equals(newStatus)) { lastImportChanged = System.currentTimeMillis(); LOG.debug("Import status transition from {} to {} at {}", importStatus, newStatus, lastImportChanged); importStatus = newStatus; updateNodeStatus(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy