org.opendaylight.sxp.route.core.RouteReactorImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.opendaylight.sxp.route.core.RouteReactorImpl.java

Source

/*
 * Copyright (c) 2016 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.sxp.route.core;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.MapDifference;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.annotation.Nullable;
import org.opendaylight.controller.md.sal.binding.api.DataBroker;
import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
import org.opendaylight.sxp.controller.core.DatastoreAccess;
import org.opendaylight.sxp.core.Configuration;
import org.opendaylight.sxp.core.SxpNode;
import org.opendaylight.sxp.route.api.RouteReactor;
import org.opendaylight.sxp.route.spi.Routing;
import org.opendaylight.sxp.route.util.RouteUtil;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
import org.opendaylight.yang.gen.v1.urn.opendaylight.sxp.cluster.route.rev161212.SxpClusterRoute;
import org.opendaylight.yang.gen.v1.urn.opendaylight.sxp.cluster.route.rev161212.SxpClusterRouteBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.sxp.cluster.route.rev161212.sxp.cluster.route.RoutingDefinition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Purpose: update route change on system level, expect single thread involvement
 */
public class RouteReactorImpl implements RouteReactor {

    private static final Logger LOG = LoggerFactory.getLogger(RouteReactorImpl.class);

    private final ConcurrentMap<IpAddress, Routing> routingServiceMap = new ConcurrentHashMap<>();

    private final DatastoreAccess datastoreAccess;
    private final RoutingServiceFactory routingServiceFactory;

    /**
     * @param dataBroker            service providing access to Datastore
     * @param routingServiceFactory factory providing {@link Routing} instances
     */
    public RouteReactorImpl(final DataBroker dataBroker, final RoutingServiceFactory routingServiceFactory) {
        datastoreAccess = DatastoreAccess.getInstance(Objects.requireNonNull(dataBroker));
        this.routingServiceFactory = Objects.requireNonNull(routingServiceFactory);
    }

    @Override
    public ListenableFuture<Void> updateRouting(@Nullable final SxpClusterRoute oldRoute,
            @Nullable final SxpClusterRoute newRoute) {
        final Map<IpAddress, RoutingDefinition> oldDefinitions = new HashMap<>();
        final Map<IpAddress, RoutingDefinition> newDefinitions = new HashMap<>();
        final List<RoutingDefinition> outcomingRouteDefinitions = new ArrayList<>();

        fillDefinitionsSafely(oldRoute, oldDefinitions);
        fillDefinitionsSafely(newRoute, newDefinitions);

        final MapDifference<IpAddress, RoutingDefinition> routingDifference = Maps.difference(oldDefinitions,
                newDefinitions);

        // ----------------------------
        // ROUTE UPDATE STRATEGY:
        // 1. remove all deleted routes
        // 2. remove all changed routes
        // 3. add all changed routes
        // 4. add all added routes
        // ----------------------------

        // 1
        processDeleted(routingDifference, outcomingRouteDefinitions);
        // 2+3
        processUpdated(routingDifference, outcomingRouteDefinitions);
        // 4
        processAdded(routingDifference, outcomingRouteDefinitions);

        collectUnchanged(routingDifference, outcomingRouteDefinitions);

        // update DS/operational
        final SxpClusterRoute sxpClusterRoute = new SxpClusterRouteBuilder()
                .setRoutingDefinition(outcomingRouteDefinitions).build();

        return datastoreAccess.put(SxpClusterRouteManager.SXP_CLUSTER_ROUTE_CONFIG_PATH, sxpClusterRoute,
                LogicalDatastoreType.OPERATIONAL);
    }

    /**
     * Add all unchanged {@link RoutingDefinition} into provided {@link List}
     *
     * @param routingDifference         contains configuration changes
     * @param outcomingRouteDefinitions where result will be stored
     */
    private void collectUnchanged(final MapDifference<IpAddress, RoutingDefinition> routingDifference,
            final List<RoutingDefinition> outcomingRouteDefinitions) {
        final SxpClusterRoute sxpClusterRouteOper = datastoreAccess.readSynchronous(
                SxpClusterRouteManager.SXP_CLUSTER_ROUTE_CONFIG_PATH, LogicalDatastoreType.OPERATIONAL);

        final ImmutableMap<IpAddress, RoutingDefinition> routingDefinitionMap = Optional
                .ofNullable(sxpClusterRouteOper).map(SxpClusterRoute::getRoutingDefinition)
                .map((routingDefinitions -> Maps.uniqueIndex(routingDefinitions, RoutingDefinition::getIpAddress)))
                .orElse(ImmutableMap.of());

        routingDifference.entriesInCommon().forEach((virtualIface, routingDefinition) -> {
            final RoutingDefinition routingDef = routingDefinitionMap.get(virtualIface);
            if (routingDef != null) {
                outcomingRouteDefinitions.add(routingDef);
            } else {
                LOG.debug("Missing unchainged routing definition in current DS/operational: {}", routingDefinition);
            }
        });
    }

    /**
     * Process all newly added {@link RoutingDefinition} and create new interface and virtual ip-address for them,
     * adds them into into provided {@link List}
     *
     * @param routingDifference         contains configuration changes
     * @param outcomingRouteDefinitions where result will be stored
     */
    @VisibleForTesting
    void processAdded(final MapDifference<IpAddress, RoutingDefinition> routingDifference,
            final List<RoutingDefinition> outcomingRouteDefinitions) {
        routingDifference.entriesOnlyOnRight().forEach((vIface, routingDef) -> {
            final boolean readyToAdd;
            // clean old unexpected state if any
            final Routing existingRouting = routingServiceMap.get(vIface);
            if (existingRouting != null) {
                LOG.info("Found unexpected route -> closing it: {}", existingRouting);
                findSxpNodesOnVirtualIp(vIface).forEach(SxpNode::shutdown);
                final boolean removalSucceded = existingRouting.removeRouteForCurrentService();
                if (!removalSucceded) {
                    LOG.warn("Route cannot be closed (cleaning before A): {}", existingRouting);
                    RoutingDefinition oldDefinition = RouteUtil.extractRoutingDefinition(existingRouting);
                    outcomingRouteDefinitions.add(RouteUtil.createOperationalRouteDefinition(oldDefinition, false,
                            "route can not be closed (by cleaning before add)"));
                    findSxpNodesOnVirtualIp(vIface).forEach(SxpNode::start);
                    readyToAdd = false;
                } else {
                    readyToAdd = true;
                }
            } else {
                readyToAdd = true;
            }

            if (readyToAdd) {
                final Routing routeService = routingServiceFactory.instantiateRoutingService(routingDef);
                routingServiceMap.put(vIface, routeService);
                final boolean succeeded = routeService.addRouteForCurrentService();
                if (succeeded) {
                    routeService.updateArpTableForCurrentService();
                    findSxpNodesOnVirtualIp(vIface).forEach(SxpNode::start);
                    outcomingRouteDefinitions
                            .add(RouteUtil.createOperationalRouteDefinition(routingDef, true, "added"));
                } else {
                    LOG.warn("Route can not be created (by add): {}", routeService);
                    outcomingRouteDefinitions.add(RouteUtil.createOperationalRouteDefinition(routingDef, false,
                            "route can not be created (by add)"));
                }
            }
        });
    }

    /**
     * Updates routes for all changed {@link RoutingDefinition} and recreate new {@link Routing} for them,
     * adds updated definitions into provided {@link List}
     *
     * @param routingDifference         contains configuration changes
     * @param outcomingRouteDefinitions where result will be stored
     */
    @VisibleForTesting
    void processUpdated(final MapDifference<IpAddress, RoutingDefinition> routingDifference,
            final List<RoutingDefinition> outcomingRouteDefinitions) {
        routingDifference.entriesDiffering().forEach((vIface, routingDiff) -> {
            final Routing routing = routingServiceMap.get(vIface);
            if (routing != null) {
                findSxpNodesOnVirtualIp(vIface).forEach(SxpNode::shutdown);
                final RoutingDefinition outcomingRouteDef;
                final boolean succeededRemoval = routing.removeRouteForCurrentService();
                if (succeededRemoval) {
                    final RoutingDefinition newRoutingDefinition = routingDiff.rightValue();
                    routing.setNetmask(newRoutingDefinition.getNetmask())
                            .setInterface(newRoutingDefinition.getInterface());
                    final boolean succeededAddition = routing.addRouteForCurrentService();
                    if (succeededAddition) {
                        routing.updateArpTableForCurrentService();
                        outcomingRouteDef = RouteUtil.createOperationalRouteDefinition(routingDiff.rightValue(),
                                true, "updated");
                    } else {
                        outcomingRouteDef = RouteUtil.createOperationalRouteDefinition(routingDiff.rightValue(),
                                false, "route can not be created (by update)");
                    }
                } else {
                    LOG.warn("Route cannot be closed (U): {}", routing);
                    outcomingRouteDef = RouteUtil.createOperationalRouteDefinition(routingDiff.leftValue(), false,
                            "route can not be closed (by update)");
                }
                findSxpNodesOnVirtualIp(vIface).forEach(SxpNode::start);
                outcomingRouteDefinitions.add(outcomingRouteDef);
            } else {
                outcomingRouteDefinitions.add(RouteUtil.createOperationalRouteDefinition(routingDiff.rightValue(),
                        false, "route can not be updated - missing routingService"));
            }
        });
    }

    /**
     * Removes routes for deleted {@link RoutingDefinition},
     * if {@link Routing} was unsuccessfully removed adds them into provided {@link List}
     *
     * @param routingDifference         contains configuration changes
     * @param outcomingRouteDefinitions where result will be stored
     */
    @VisibleForTesting
    void processDeleted(final MapDifference<IpAddress, RoutingDefinition> routingDifference,
            final List<RoutingDefinition> outcomingRouteDefinitions) {
        routingDifference.entriesOnlyOnLeft().forEach((vIpAddress, routingDef) -> {
            final Routing routingService = routingServiceMap.remove(vIpAddress);
            if (routingService != null) {
                findSxpNodesOnVirtualIp(vIpAddress).forEach(SxpNode::shutdown);
                final boolean succeeded = routingService.removeRouteForCurrentService();
                if (!succeeded) {
                    LOG.warn("Route cannot be closed (D): {}", routingService);
                    outcomingRouteDefinitions.add(RouteUtil.createOperationalRouteDefinition(routingDef, false,
                            "route can not be closed (by remove)"));
                }
            }
        });
    }

    /**
     * @param vIpAddress virtual address
     * @return Nodes related to specified virtual ip-address
     */
    private static Collection<SxpNode> findSxpNodesOnVirtualIp(final IpAddress vIpAddress) {
        return RouteUtil.findSxpNodesOnVirtualIp(vIpAddress, Configuration.getNodes());
    }

    /**
     * @param route       configuration of {@link SxpClusterRoute} that will be transformed
     * @param definitions configuration that will receive data
     */
    @VisibleForTesting
    void fillDefinitionsSafely(final @Nullable SxpClusterRoute route,
            final Map<IpAddress, RoutingDefinition> definitions) {
        Optional.ofNullable(route).map(SxpClusterRoute::getRoutingDefinition).map((routingDefs) -> routingDefs
                .stream().map((routingDef) -> definitions.put(routingDef.getIpAddress(), routingDef)).count());
    }

    @Override
    public ListenableFuture<Void> wipeRouting() {
        routingServiceMap.forEach((vIpAddress, routingService) -> {
            findSxpNodesOnVirtualIp(vIpAddress).forEach(SxpNode::shutdown);
            final boolean succeeded = routingService.removeRouteForCurrentService();
            if (succeeded) {
                LOG.debug("wiped out route: {}", routingService);
            } else {
                LOG.warn("failed to wipe out route: {}", routingService);
            }
        });
        routingServiceMap.clear();
        return Futures.immediateFuture(null);
    }

    /**
     * @return currently defined routings
     */
    @VisibleForTesting
    ConcurrentMap<IpAddress, Routing> getRoutingServiceMap() {
        return routingServiceMap;
    }
}