org.opendaylight.groupbasedpolicy.renderer.ofoverlay.PolicyManager.java Source code

Java tutorial

Introduction

Here is the source code for org.opendaylight.groupbasedpolicy.renderer.ofoverlay.PolicyManager.java

Source

/*
 * Copyright (c) 2014 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.groupbasedpolicy.renderer.ofoverlay;

import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.opendaylight.controller.md.sal.binding.api.DataBroker;
import org.opendaylight.controller.md.sal.binding.api.DataTreeChangeListener;
import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction;
import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
import org.opendaylight.groupbasedpolicy.dto.EgKey;
import org.opendaylight.groupbasedpolicy.dto.EpKey;
import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.endpoint.EndpointManager;
import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.DestinationMapper;
import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.EgressNatMapper;
import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.ExternalMapper;
import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.FlowUtils;
import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.GroupTable;
import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.IngressNatMapper;
import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.OfTable;
import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.PolicyEnforcer;
import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.PortSecurity;
import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.SourceMapper;
import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.node.SwitchListener;
import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.node.SwitchManager;
import org.opendaylight.groupbasedpolicy.util.DataStoreHelper;
import org.opendaylight.groupbasedpolicy.util.IidFactory;
import org.opendaylight.groupbasedpolicy.util.SingletonTask;
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.groupbasedpolicy.ofoverlay.rev140528.OfOverlayConfig.LearningMode;
import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.interests.followed.tenants.followed.tenant.FollowedEndpointGroup;
import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.interests.followed.tenants.followed.tenant.FollowedEndpointGroupBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.resolved.policy.rev150828.ResolvedPolicies;
import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.resolved.policy.rev150828.resolved.policies.ResolvedPolicy;
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
import org.opendaylight.yang.gen.v1.urn.opendaylight.table.types.rev131026.TableId;
import org.opendaylight.yangtools.concepts.ListenerRegistration;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;

/**
 * Manage policies on switches by subscribing to updates from the
 * policy resolver and information about endpoints from the endpoint
 * registry
 */
public class PolicyManager
        implements SwitchListener, EndpointListener, DataTreeChangeListener<ResolvedPolicy>, Closeable {
    private static final Logger LOG = LoggerFactory.getLogger(PolicyManager.class);

    private Map<InstanceIdentifier<Table>, TableBuilder> actualGbpFlows = new HashMap<>();
    private Map<InstanceIdentifier<Table>, TableBuilder> previousGbpFlows = new HashMap<>();

    private short tableOffset;
    private static final short TABLEID_PORTSECURITY = 0;
    private static final short TABLEID_INGRESS_NAT = 1;
    private static final short TABLEID_SOURCE_MAPPER = 2;
    private static final short TABLEID_DESTINATION_MAPPER = 3;
    private static final short TABLEID_POLICY_ENFORCER = 4;
    private static final short TABLEID_EGRESS_NAT = 5;
    private static final short TABLEID_EXTERNAL_MAPPER = 6;

    private final SwitchManager switchManager;
    private final EndpointManager endpointManager;

    private final ListenerRegistration<PolicyManager> registerDataTreeChangeListener;

    private final ScheduledExecutorService executor;
    private final SingletonTask flowUpdateTask;
    private final DataBroker dataBroker;

    /**
     * The delay before triggering the flow update task in response to an
     * event in milliseconds.
     */
    private final static int FLOW_UPDATE_DELAY = 250;

    public PolicyManager(DataBroker dataBroker, SwitchManager switchManager, EndpointManager endpointManager,
            ScheduledExecutorService executor, short tableOffset) {
        super();
        this.switchManager = switchManager;
        this.executor = executor;
        this.dataBroker = dataBroker;
        this.tableOffset = tableOffset;
        try {
            // to validate against model
            verifyMaxTableId(tableOffset);
        } catch (IllegalArgumentException e) {
            throw new IllegalArgumentException("Failed to start OF-Overlay renderer\n."
                    + "Max. table ID would be out of range. Check config-subsystem.\n{}", e);
        }

        if (dataBroker != null) {
            registerDataTreeChangeListener = dataBroker.registerDataTreeChangeListener(
                    new DataTreeIdentifier<>(LogicalDatastoreType.OPERATIONAL,
                            InstanceIdentifier.builder(ResolvedPolicies.class).child(ResolvedPolicy.class).build()),
                    this);
        } else {
            registerDataTreeChangeListener = null;
            LOG.error("DataBroker is null. Listener for {} was not registered.",
                    ResolvedPolicy.class.getCanonicalName());
        }
        if (switchManager != null)
            switchManager.registerListener(this);
        this.endpointManager = endpointManager;
        endpointManager.registerListener(this);

        flowUpdateTask = new SingletonTask(executor, new FlowUpdateTask());
        scheduleUpdate();

        LOG.debug("Initialized OFOverlay policy manager");
    }

    private List<? extends OfTable> createFlowPipeline(OfContext ofCtx) {
        // TODO - PORTSECURITY is kept in table 0.
        // According to openflow spec,processing on vSwitch always starts from table 0.
        // Packets will be droped if table 0 is empty.
        // Alternative workaround - table-miss flow entries in table 0.
        return ImmutableList.of(new PortSecurity(ofCtx, (short) 0), new GroupTable(ofCtx),
                new IngressNatMapper(ofCtx, getTABLEID_INGRESS_NAT()),
                new SourceMapper(ofCtx, getTABLEID_SOURCE_MAPPER()),
                new DestinationMapper(ofCtx, getTABLEID_DESTINATION_MAPPER()),
                new PolicyEnforcer(ofCtx, getTABLEID_POLICY_ENFORCER()),
                new EgressNatMapper(ofCtx, getTABLEID_EGRESS_NAT()),
                new ExternalMapper(ofCtx, getTABLEID_EXTERNAL_MAPPER()));
    }

    /**
     * @param tableOffset the new offset value
     * @return {@link ListenableFuture} to indicate that tables have been synced
     */
    public ListenableFuture<Void> changeOpenFlowTableOffset(final short tableOffset) {
        try {
            verifyMaxTableId(tableOffset);
        } catch (IllegalArgumentException e) {
            LOG.error("Cannot update table offset. Max. table ID would be out of range.\n{}", e);
            // TODO - invalid offset value remains in conf DS
            // It's not possible to validate offset value by using constrains in model,
            // because number of tables in pipeline varies.
            return Futures.immediateFuture(null);
        }
        List<Short> tableIDs = getTableIDs();
        this.tableOffset = tableOffset;
        return Futures.transform(removeUnusedTables(tableIDs), new Function<Void, Void>() {

            @Override
            public Void apply(Void tablesRemoved) {
                scheduleUpdate();
                return null;
            }
        });
    }

    /**
     * @param  tableIDs - IDs of tables to delete
     * @return ListenableFuture<Void> - which will be filled when clearing is done
     */
    private ListenableFuture<Void> removeUnusedTables(final List<Short> tableIDs) {
        List<ListenableFuture<Void>> checkList = new ArrayList<>();
        final ReadWriteTransaction rwTx = dataBroker.newReadWriteTransaction();
        for (Short tableId : tableIDs) {
            for (NodeId nodeId : switchManager.getReadySwitches()) {
                final InstanceIdentifier<Table> tablePath = FlowUtils.createTablePath(nodeId, tableId);
                checkList.add(deleteTableIfExists(rwTx, tablePath));
            }
        }
        ListenableFuture<List<Void>> allAsListFuture = Futures.allAsList(checkList);
        return Futures.transform(allAsListFuture, new AsyncFunction<List<Void>, Void>() {

            @Override
            public ListenableFuture<Void> apply(List<Void> readyToSubmit) {
                return rwTx.submit();
            }
        });
    }

    private List<Short> getTableIDs() {
        List<Short> tableIds = new ArrayList<>();
        tableIds.add(getTABLEID_PORTSECURITY());
        tableIds.add(getTABLEID_INGRESS_NAT());
        tableIds.add(getTABLEID_SOURCE_MAPPER());
        tableIds.add(getTABLEID_DESTINATION_MAPPER());
        tableIds.add(getTABLEID_POLICY_ENFORCER());
        tableIds.add(getTABLEID_EGRESS_NAT());
        tableIds.add(getTABLEID_EXTERNAL_MAPPER());
        return tableIds;
    }

    private ListenableFuture<Void> deleteTableIfExists(final ReadWriteTransaction rwTx,
            final InstanceIdentifier<Table> tablePath) {
        return Futures.transform(rwTx.read(LogicalDatastoreType.CONFIGURATION, tablePath),
                new Function<Optional<Table>, Void>() {

                    @Override
                    public Void apply(Optional<Table> optTable) {
                        if (optTable.isPresent()) {
                            rwTx.delete(LogicalDatastoreType.CONFIGURATION, tablePath);
                        }
                        return null;
                    }
                });
    }

    // **************
    // SwitchListener
    // **************

    public short getTABLEID_PORTSECURITY() {
        return (short) (tableOffset + TABLEID_PORTSECURITY);
    }

    public short getTABLEID_INGRESS_NAT() {
        return (short) (tableOffset + TABLEID_INGRESS_NAT);
    }

    public short getTABLEID_SOURCE_MAPPER() {
        return (short) (tableOffset + TABLEID_SOURCE_MAPPER);
    }

    public short getTABLEID_DESTINATION_MAPPER() {
        return (short) (tableOffset + TABLEID_DESTINATION_MAPPER);
    }

    public short getTABLEID_POLICY_ENFORCER() {
        return (short) (tableOffset + TABLEID_POLICY_ENFORCER);
    }

    public short getTABLEID_EGRESS_NAT() {
        return (short) (tableOffset + TABLEID_EGRESS_NAT);
    }

    public short getTABLEID_EXTERNAL_MAPPER() {
        return (short) (tableOffset + TABLEID_EXTERNAL_MAPPER);
    }

    public TableId verifyMaxTableId(short tableOffset) {
        return new TableId((short) (tableOffset + TABLEID_EXTERNAL_MAPPER));
    }

    @Override
    public void switchReady(final NodeId nodeId) {
        scheduleUpdate();
    }

    @Override
    public void switchRemoved(NodeId sw) {
        // XXX TODO purge switch flows
        scheduleUpdate();
    }

    @Override
    public void switchUpdated(NodeId sw) {
        scheduleUpdate();
    }

    // ****************
    // EndpointListener
    // ****************

    @Override
    public void endpointUpdated(EpKey epKey) {
        scheduleUpdate();
    }

    @Override
    public void nodeEndpointUpdated(NodeId nodeId, EpKey epKey) {
        scheduleUpdate();
    }

    @Override
    public void groupEndpointUpdated(EgKey egKey, EpKey epKey) {
        // TODO a renderer should remove followed-EPG and followed-tenant at some point
        if (dataBroker == null) {
            LOG.error("DataBroker is null. Cannot write followed-epg {}", epKey);
            return;
        }
        WriteTransaction wTx = dataBroker.newWriteOnlyTransaction();
        FollowedEndpointGroup followedEpg = new FollowedEndpointGroupBuilder().setId(egKey.getEgId()).build();
        wTx.put(LogicalDatastoreType.OPERATIONAL, IidFactory.followedEndpointgroupIid(
                OFOverlayRenderer.RENDERER_NAME, egKey.getTenantId(), egKey.getEgId()), followedEpg, true);
        DataStoreHelper.submitToDs(wTx);
        scheduleUpdate();
    }

    // **************
    // DataTreeChangeListener<ResolvedPolicy>
    // **************

    @Override
    public void onDataTreeChanged(Collection<DataTreeModification<ResolvedPolicy>> changes) {
        scheduleUpdate();
    }

    // *************
    // PolicyManager
    // *************

    /**
     * Set the learning mode to the specified value
     * @param learningMode the learning mode to set
     */
    public void setLearningMode(LearningMode learningMode) {
        // No-op for now
    }

    // **************
    // Implementation
    // **************

    private void scheduleUpdate() {
        if (switchManager != null) {
            LOG.trace("Scheduling flow update task");

            // Mark all existing flows as previous - will be compared with new ones
            previousGbpFlows = actualGbpFlows;
            actualGbpFlows = new HashMap<>();

            flowUpdateTask.reschedule(FLOW_UPDATE_DELAY, TimeUnit.MILLISECONDS);
        }
    }

    /**
     * Update the flows on a particular switch
     */
    private class SwitchFlowUpdateTask implements Callable<Void> {
        private final OfWriter ofWriter;

        public SwitchFlowUpdateTask(OfWriter ofWriter) {
            this.ofWriter = ofWriter;
        }

        @Override
        public Void call() throws Exception {
            OfContext ofCtx = new OfContext(dataBroker, PolicyManager.this, switchManager, endpointManager,
                    executor);
            if (ofCtx.getCurrentPolicy() == null)
                return null;
            List<? extends OfTable> flowPipeline = createFlowPipeline(ofCtx);
            for (NodeId node : switchManager.getReadySwitches()) {
                for (OfTable table : flowPipeline) {
                    try {
                        table.sync(node, ofWriter);
                    } catch (Exception e) {
                        LOG.error("Failed to write Openflow table {}", table.getClass().getSimpleName(), e);
                    }
                }
            }
            return null;
        }
    }

    /**
     * Update all flows on all switches as needed.  Note that this will block
     * one of the threads on the executor.
     */
    private class FlowUpdateTask implements Runnable {
        @Override
        public void run() {
            LOG.debug("Beginning flow update task");

            CompletionService<Void> ecs = new ExecutorCompletionService<>(executor);

            OfWriter ofWriter = new OfWriter();

            SwitchFlowUpdateTask swut = new SwitchFlowUpdateTask(ofWriter);
            ecs.submit(swut);

            try {
                ecs.take().get();
                // Current gbp flow must be independent, find out where this run() ends,
                // set flows to one field and reset another
                actualGbpFlows.putAll(ofWriter.commitToDataStore(dataBroker, previousGbpFlows));
            } catch (InterruptedException | ExecutionException e) {
                LOG.error("Failed to update flow tables", e);
            }
            LOG.debug("Flow update completed");
        }
    }

    @Override
    public void close() throws IOException {
        if (registerDataTreeChangeListener != null)
            registerDataTreeChangeListener.close();
        // TODO unregister classifier and action instance validators
    }

}