

package org.onosproject.net.intent.impl.installer;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.onlab.packet.VlanId;
import org.onosproject.cfg.ComponentConfigService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.NetworkResource;
import org.onosproject.net.flow.DefaultFlowRule;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.FlowRuleOperations;
import org.onosproject.net.flow.FlowRuleServiceAdapter;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.intent.FlowRuleIntent;
import org.onosproject.net.intent.Intent;
import org.onosproject.net.intent.IntentData;
import org.onosproject.net.intent.IntentInstallationContext;
import org.onosproject.net.intent.IntentOperationContext;
import org.onosproject.net.intent.IntentState;
import org.onosproject.net.intent.PathIntent;
import org.onosproject.store.service.WallClockTimestamp;
import org.onosproject.store.trivial.SimpleIntentStore;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static org.easymock.EasyMock.mock;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

/**
 * Tests for flow rule Intent installer.
 */
public class FlowRuleIntentInstallerTest extends AbstractIntentInstallerTest {

    private TestFlowRuleService flowRuleService;
    private TestFlowRuleServiceNonDisruptive flowRuleServiceNonDisruptive;
    private FlowRuleIntentInstaller installer;

    @Before
    public void setup() {
        super.setup();
        flowRuleService = new TestFlowRuleService();
        flowRuleServiceNonDisruptive = new TestFlowRuleServiceNonDisruptive();
        installer = new FlowRuleIntentInstaller();
        installer.flowRuleService = flowRuleService;
        installer.store = new SimpleIntentStore();
        installer.intentExtensionService = intentExtensionService;
        installer.intentInstallCoordinator = intentInstallCoordinator;
        installer.trackerService = trackerService;
        installer.configService = mock(ComponentConfigService.class);

        installer.activate();
    }

    @After
    public void tearDown() {
        super.tearDown();
        installer.deactivated();
    }

    /**
     * Installs Intents only, no Intents to be uninstall.
     */
    @Test
    public void testInstallOnly() {
        List<Intent> intentsToUninstall = Lists.newArrayList();
        List<Intent> intentsToInstall = createFlowRuleIntents();

        IntentData toUninstall = null;
        IntentData toInstall = new IntentData(createP2PIntent(),
                                              IntentState.INSTALLING,
                                              new WallClockTimestamp());
        toInstall = new IntentData(toInstall, intentsToInstall);


        IntentOperationContext<FlowRuleIntent> operationContext;
        IntentInstallationContext context = new IntentInstallationContext(toUninstall, toInstall);
        operationContext = new IntentOperationContext(intentsToUninstall, intentsToInstall, context);

        installer.apply(operationContext);

        IntentOperationContext successContext = intentInstallCoordinator.successContext;
        assertEquals(successContext, operationContext);

        Set<FlowRule> expectedFlowRules = intentsToInstall.stream()
                .map(intent -> (FlowRuleIntent) intent)
                .map(FlowRuleIntent::flowRules)
                .flatMap(Collection::stream)
                .collect(Collectors.toSet());

        assertEquals(expectedFlowRules, flowRuleService.flowRulesAdd);
    }

    /**
     * Uninstalls Intents only, no Intents to be install.
     */
    @Test
    public void testUninstallOnly() {
        List<Intent> intentsToInstall = Lists.newArrayList();
        List<Intent> intentsToUninstall = createFlowRuleIntents();

        IntentData toInstall = null;
        IntentData toUninstall = new IntentData(createP2PIntent(),
                                              IntentState.WITHDRAWING,
                                              new WallClockTimestamp());
        toUninstall = new IntentData(toUninstall, intentsToUninstall);


        IntentOperationContext<FlowRuleIntent> operationContext;
        IntentInstallationContext context = new IntentInstallationContext(toUninstall, toInstall);
        operationContext = new IntentOperationContext(intentsToUninstall, intentsToInstall, context);

        installer.apply(operationContext);

        IntentOperationContext successContext = intentInstallCoordinator.successContext;
        assertEquals(successContext, operationContext);

        Set<FlowRule> expectedFlowRules = intentsToUninstall.stream()
                .map(intent -> (FlowRuleIntent) intent)
                .map(FlowRuleIntent::flowRules)
                .flatMap(Collection::stream)
                .collect(Collectors.toSet());

        assertEquals(expectedFlowRules, flowRuleService.flowRulesRemove);
    }

    /**
     * Do both install and uninstall Intents with different flow rules.
     */
    @Test
    public void testUninstallAndInstall() {
        List<Intent> intentsToInstall = createAnotherFlowRuleIntents();
        List<Intent> intentsToUninstall = createFlowRuleIntents();

        IntentData toInstall = new IntentData(createP2PIntent(),
                                              IntentState.INSTALLING,
                                              new WallClockTimestamp());
        toInstall = new IntentData(toInstall, intentsToInstall);
        IntentData toUninstall = new IntentData(createP2PIntent(),
                                                IntentState.INSTALLED,
                                                new WallClockTimestamp());
        toUninstall = new IntentData(toUninstall, intentsToUninstall);

        IntentOperationContext<FlowRuleIntent> operationContext;
        IntentInstallationContext context = new IntentInstallationContext(toUninstall, toInstall);
        operationContext = new IntentOperationContext(intentsToUninstall, intentsToInstall, context);

        installer.apply(operationContext);

        IntentOperationContext successContext = intentInstallCoordinator.successContext;
        assertEquals(successContext, operationContext);

        Set<FlowRule> expectedFlowRules = intentsToUninstall.stream()
                .map(intent -> (FlowRuleIntent) intent)
                .map(FlowRuleIntent::flowRules)
                .flatMap(Collection::stream)
                .collect(Collectors.toSet());

        assertEquals(expectedFlowRules, flowRuleService.flowRulesRemove);

        expectedFlowRules = intentsToInstall.stream()
                .map(intent -> (FlowRuleIntent) intent)
                .map(FlowRuleIntent::flowRules)
                .flatMap(Collection::stream)
                .collect(Collectors.toSet());

        assertEquals(expectedFlowRules, flowRuleService.flowRulesAdd);
    }

    /**
     * Do both install and uninstall Intents with same flow rules.
     */
    @Test
    public void testUninstallAndInstallUnchanged() {
        List<Intent> intentsToInstall = createFlowRuleIntents();
        List<Intent> intentsToUninstall = createFlowRuleIntents();

        IntentData toInstall = new IntentData(createP2PIntent(),
                                              IntentState.INSTALLING,
                                              new WallClockTimestamp());
        toInstall = new IntentData(toInstall, intentsToInstall);
        IntentData toUninstall = new IntentData(createP2PIntent(),
                                                IntentState.INSTALLED,
                                                new WallClockTimestamp());
        toUninstall = new IntentData(toUninstall, intentsToUninstall);

        IntentOperationContext<FlowRuleIntent> operationContext;
        IntentInstallationContext context = new IntentInstallationContext(toUninstall, toInstall);
        operationContext = new IntentOperationContext(intentsToUninstall, intentsToInstall, context);

        installer.apply(operationContext);

        IntentOperationContext successContext = intentInstallCoordinator.successContext;
        assertEquals(successContext, operationContext);

        assertEquals(0, flowRuleService.flowRulesRemove.size());
        assertEquals(0, flowRuleService.flowRulesAdd.size());
        assertEquals(0, flowRuleService.flowRulesModify.size());
    }

    /**
     * Do both install and uninstall Intents with same flow rule Intent.
     */
    @Test
    public void testUninstallAndInstallSame() {
        List<Intent> intentsToInstall = createFlowRuleIntents();
        List<Intent> intentsToUninstall = intentsToInstall;

        IntentData toInstall = new IntentData(createP2PIntent(),
                                              IntentState.INSTALLING,
                                              new WallClockTimestamp());
        toInstall = new IntentData(toInstall, intentsToInstall);
        IntentData toUninstall = new IntentData(createP2PIntent(),
                                                IntentState.INSTALLED,
                                                new WallClockTimestamp());
        toUninstall = new IntentData(toUninstall, intentsToUninstall);

        IntentOperationContext<FlowRuleIntent> operationContext;
        IntentInstallationContext context = new IntentInstallationContext(toUninstall, toInstall);
        operationContext = new IntentOperationContext(intentsToUninstall, intentsToInstall, context);

        installer.apply(operationContext);

        IntentOperationContext successContext = intentInstallCoordinator.successContext;
        assertEquals(successContext, operationContext);

        assertEquals(0, flowRuleService.flowRulesRemove.size());
        assertEquals(0, flowRuleService.flowRulesAdd.size());
        assertEquals(0, flowRuleService.flowRulesModify.size());
    }

    /**
     * Nothing to uninstall or install.
     */
    @Test
    public void testNoAnyIntentToApply() {
        IntentData toInstall = null;
        IntentData toUninstall = null;
        IntentOperationContext<FlowRuleIntent> operationContext;
        IntentInstallationContext context = new IntentInstallationContext(toUninstall, toInstall);
        operationContext = new IntentOperationContext<>(ImmutableList.of(), ImmutableList.of(), context);
        installer.apply(operationContext);

        IntentOperationContext successContext = intentInstallCoordinator.successContext;
        assertEquals(successContext, operationContext);

        assertEquals(0, flowRuleService.flowRulesRemove.size());
        assertEquals(0, flowRuleService.flowRulesAdd.size());
        assertEquals(0, flowRuleService.flowRulesModify.size());
    }

    /**
     * Test if the flow installation failed.
     */
    @Test
    public void testFailed() {
        installer.flowRuleService = new TestFailedFlowRuleService();
        List<Intent> intentsToUninstall = Lists.newArrayList();
        List<Intent> intentsToInstall = createFlowRuleIntents();

        IntentData toUninstall = null;
        IntentData toInstall = new IntentData(createP2PIntent(),
                                              IntentState.INSTALLING,
                                              new WallClockTimestamp());
        toInstall = new IntentData(toInstall, intentsToInstall);


        IntentOperationContext<FlowRuleIntent> operationContext;
        IntentInstallationContext context = new IntentInstallationContext(toUninstall, toInstall);
        operationContext = new IntentOperationContext(intentsToUninstall, intentsToInstall, context);

        installer.apply(operationContext);

        IntentOperationContext failedContext = intentInstallCoordinator.failedContext;
        assertEquals(failedContext, operationContext);
    }

    /**
     * Test intents with same match rules, should do modify instead of add.
     */
    @Test
    public void testRuleModify() {
        List<Intent> intentsToInstall = createFlowRuleIntents();
        List<Intent> intentsToUninstall = createFlowRuleIntentsWithSameMatch();

        IntentData toInstall = new IntentData(createP2PIntent(),
                                              IntentState.INSTALLING,
                                              new WallClockTimestamp());
        toInstall = new IntentData(toInstall, intentsToInstall);
        IntentData toUninstall = new IntentData(createP2PIntent(),
                                                IntentState.INSTALLED,
                                                new WallClockTimestamp());
        toUninstall = new IntentData(toUninstall, intentsToUninstall);

        IntentOperationContext<FlowRuleIntent> operationContext;
        IntentInstallationContext context = new IntentInstallationContext(toUninstall, toInstall);
        operationContext = new IntentOperationContext(intentsToUninstall, intentsToInstall, context);

        installer.apply(operationContext);

        IntentOperationContext successContext = intentInstallCoordinator.successContext;
        assertEquals(successContext, operationContext);

        assertEquals(0, flowRuleService.flowRulesRemove.size());
        assertEquals(0, flowRuleService.flowRulesAdd.size());
        assertEquals(1, flowRuleService.flowRulesModify.size());

        FlowRuleIntent installedIntent = (FlowRuleIntent) intentsToInstall.get(0);
        assertEquals(flowRuleService.flowRulesModify.size(), installedIntent.flowRules().size());
        assertTrue(flowRuleService.flowRulesModify.containsAll(installedIntent.flowRules()));
    }

    /**
     * Testing the non-disruptive reallocation.
     */
    @Test
    public void testUninstallAndInstallNonDisruptive() throws InterruptedException {

        installer.flowRuleService = flowRuleServiceNonDisruptive;

        List<Intent> intentsToInstall = createAnotherFlowRuleIntentsNonDisruptive();
        List<Intent> intentsToUninstall = createFlowRuleIntentsNonDisruptive();

        IntentData toInstall = new IntentData(createP2PIntentNonDisruptive(),
                                              IntentState.INSTALLING,
                                              new WallClockTimestamp());
        toInstall = new IntentData(toInstall, intentsToInstall);
        IntentData toUninstall = new IntentData(createP2PIntentNonDisruptive(),
                                                IntentState.INSTALLED,
                                                new WallClockTimestamp());
        toUninstall = new IntentData(toUninstall, intentsToUninstall);

        IntentOperationContext<FlowRuleIntent> operationContext;
        IntentInstallationContext context = new IntentInstallationContext(toUninstall, toInstall);
        operationContext = new IntentOperationContext(intentsToUninstall, intentsToInstall, context);

        installer.apply(operationContext);

        //A single FlowRule is evaluated for every non-disruptive stage
        TrafficSelector selector = DefaultTrafficSelector.builder()
                .matchInPhyPort(CP1.port())
                .build();
        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
                .setOutput(CP3.port())
                .build();

        FlowRule firstStageInstalledRule = DefaultFlowRule.builder()
                .forDevice(CP1.deviceId())
                .withSelector(selector)
                .withTreatment(treatment)
                .fromApp(APP_ID)
                .withPriority(DEFAULT_PRIORITY - 1)
                .makePermanent()
                .build();

        assertTrue(flowRuleServiceNonDisruptive.flowRulesAdd.contains(firstStageInstalledRule));

        selector = DefaultTrafficSelector.builder()
                .matchInPhyPort(CP4_2.port())
                .build();
        treatment = DefaultTrafficTreatment.builder()
                .setOutput(CP4_1.port())
                .build();

        FlowRule secondStageUninstalledRule = DefaultFlowRule.builder()
                .forDevice(CP4_1.deviceId())
                .withSelector(selector)
                .withTreatment(treatment)
                .fromApp(APP_ID)
                .withPriority(DEFAULT_PRIORITY)
                .makePermanent()
                .build();

        assertTrue(flowRuleServiceNonDisruptive.flowRulesRemove.contains(secondStageUninstalledRule));

        selector = DefaultTrafficSelector.builder()
                .matchInPhyPort(CP4_3.port())
                .build();
        treatment = DefaultTrafficTreatment.builder()
                .setOutput(CP4_1.port())
                .build();

        FlowRule thirdStageInstalledRule = DefaultFlowRule.builder()
                .forDevice(CP4_1.deviceId())
                .withSelector(selector)
                .withTreatment(treatment)
                .fromApp(APP_ID)
                .withPriority(DEFAULT_PRIORITY)
                .makePermanent()
                .build();

        assertTrue(flowRuleServiceNonDisruptive.flowRulesAdd.contains(thirdStageInstalledRule));

        selector = DefaultTrafficSelector.builder()
                .matchInPhyPort(CP2_1.port())
                .build();
        treatment = DefaultTrafficTreatment.builder()
                .setOutput(CP2_2.port())
                .build();

        FlowRule lastStageUninstalledRule = DefaultFlowRule.builder()
                .forDevice(CP2_1.deviceId())
                .withSelector(selector)
                .withTreatment(treatment)
                .fromApp(APP_ID)
                .withPriority(DEFAULT_PRIORITY)
                .makePermanent()
                .build();

        assertTrue(flowRuleServiceNonDisruptive.flowRulesRemove.contains(lastStageUninstalledRule));

        IntentOperationContext successContext = intentInstallCoordinator.successContext;
        assertEquals(successContext, operationContext);
    }

    /**
     * Generates FlowRuleIntents for test.
     *
     * @return the FlowRuleIntents for test
     */
    public List<Intent> createFlowRuleIntents() {
        TrafficSelector selector = DefaultTrafficSelector.builder()
                .matchInPhyPort(CP1.port())
                .build();
        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
                .setOutput(CP2.port())
                .build();

        FlowRule flowRule = DefaultFlowRule.builder()
                .forDevice(CP1.deviceId())
                .withSelector(selector)
                .withTreatment(treatment)
                .fromApp(APP_ID)
                .withPriority(DEFAULT_PRIORITY)
                .makePermanent()
                .build();

        List<NetworkResource> resources = ImmutableList.of(CP1.deviceId());

        FlowRuleIntent intent = new FlowRuleIntent(APP_ID,
                                                   KEY1,
                                                   ImmutableList.of(flowRule),
                                                   resources,
                                                   PathIntent.ProtectionType.PRIMARY,
                                                   RG1);

        List<Intent> flowRuleIntents = Lists.newArrayList();
        flowRuleIntents.add(intent);

        return flowRuleIntents;
    }

    /**
     * Generates FlowRuleIntents for test. Flow rules in Intent should have same
     * match as we created by createFlowRuleIntents method, but action will be
     * different.
     *
     * @return the FlowRuleIntents for test
     */
    public List<Intent> createFlowRuleIntentsWithSameMatch() {
        TrafficSelector selector = DefaultTrafficSelector.builder()
                .matchInPhyPort(CP1.port())
                .build();
        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
                .punt()
                .build();

        FlowRule flowRule = DefaultFlowRule.builder()
                .forDevice(CP1.deviceId())
                .withSelector(selector)
                .withTreatment(treatment)
                .fromApp(APP_ID)
                .withPriority(DEFAULT_PRIORITY)
                .makePermanent()
                .build();

        List<NetworkResource> resources = ImmutableList.of(CP1.deviceId());

        FlowRuleIntent intent = new FlowRuleIntent(APP_ID,
                                                   KEY1,
                                                   ImmutableList.of(flowRule),
                                                   resources,
                                                   PathIntent.ProtectionType.PRIMARY,
                                                   RG1);

        List<Intent> flowRuleIntents = Lists.newArrayList();
        flowRuleIntents.add(intent);

        return flowRuleIntents;
    }

    /**
     * Generates another different FlowRuleIntents for test.
     *
     * @return the FlowRuleIntents for test
     */
    public List<Intent> createAnotherFlowRuleIntents() {
        TrafficSelector selector = DefaultTrafficSelector.builder()
                .matchVlanId(VlanId.vlanId("100"))
                .matchInPhyPort(CP1.port())
                .build();
        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
                .setOutput(CP2.port())
                .build();

        FlowRule flowRule = DefaultFlowRule.builder()
                .forDevice(CP1.deviceId())
                .withSelector(selector)
                .withTreatment(treatment)
                .fromApp(APP_ID)
                .withPriority(DEFAULT_PRIORITY)
                .makePermanent()
                .build();

        List<NetworkResource> resources = ImmutableList.of(CP1.deviceId());

        FlowRuleIntent intent = new FlowRuleIntent(APP_ID,
                                                   KEY1,
                                                   ImmutableList.of(flowRule),
                                                   resources,
                                                   PathIntent.ProtectionType.PRIMARY,
                                                   RG1);

        List<Intent> flowRuleIntents = Lists.newArrayList();
        flowRuleIntents.add(intent);

        return flowRuleIntents;
    }

    /**
     * Generates FlowRuleIntents for testing non-disruptive reallocation.
     *
     * @return the FlowRuleIntents for test
     */
    public List<Intent> createFlowRuleIntentsNonDisruptive() {

        Map<ConnectPoint, ConnectPoint> portsAssociation = Maps.newHashMap();
        portsAssociation.put(CP1, CP2);
        portsAssociation.put(CP2_1, CP2_2);
        portsAssociation.put(CP4_2, CP4_1);

        List<FlowRule> flowRules = Lists.newArrayList();

        for (ConnectPoint srcPoint : portsAssociation.keySet()) {

            TrafficSelector selector = DefaultTrafficSelector.builder()
                    .matchInPhyPort(srcPoint.port())
                    .build();
            TrafficTreatment treatment = DefaultTrafficTreatment.builder()
                    .setOutput(portsAssociation.get(srcPoint).port())
                    .build();

            FlowRule flowRule = DefaultFlowRule.builder()
                    .forDevice(srcPoint.deviceId())
                    .withSelector(selector)
                    .withTreatment(treatment)
                    .fromApp(APP_ID)
                    .withPriority(DEFAULT_PRIORITY)
                    .makePermanent()
                    .build();
            flowRules.add(flowRule);
        }



        List<NetworkResource> resources = ImmutableList.of(S1_S2, S2_S4);

        FlowRuleIntent intent = new FlowRuleIntent(APP_ID,
                                                   KEY1,
                                                   flowRules,
                                                   resources,
                                                   PathIntent.ProtectionType.PRIMARY,
                                                   RG1);

        List<Intent> flowRuleIntents = Lists.newArrayList();
        flowRuleIntents.add(intent);

        return flowRuleIntents;
    }

    /**
     * Generates another FlowRuleIntent, going through a different path, for testing non-disruptive reallocation.
     *
     * @return the FlowRuleIntents for test
     */
    public List<Intent> createAnotherFlowRuleIntentsNonDisruptive() {
        Map<ConnectPoint, ConnectPoint> portsAssociation = Maps.newHashMap();
        portsAssociation.put(CP1, CP3);
        portsAssociation.put(CP3_1, CP3_2);
        portsAssociation.put(CP4_3, CP4_1);

        List<FlowRule> flowRules = Lists.newArrayList();

        for (ConnectPoint srcPoint : portsAssociation.keySet()) {

            TrafficSelector selector = DefaultTrafficSelector.builder()
                    .matchInPhyPort(srcPoint.port())
                    .build();
            TrafficTreatment treatment = DefaultTrafficTreatment.builder()
                    .setOutput(portsAssociation.get(srcPoint).port())
                    .build();

            FlowRule flowRule = DefaultFlowRule.builder()
                    .forDevice(srcPoint.deviceId())
                    .withSelector(selector)
                    .withTreatment(treatment)
                    .fromApp(APP_ID)
                    .withPriority(DEFAULT_PRIORITY)
                    .makePermanent()
                    .build();
            flowRules.add(flowRule);
        }



        List<NetworkResource> resources = ImmutableList.of(S1_S3, S3_S4);

        FlowRuleIntent intent = new FlowRuleIntent(APP_ID,
                                                   KEY1,
                                                   flowRules,
                                                   resources,
                                                   PathIntent.ProtectionType.PRIMARY,
                                                   RG1);

        List<Intent> flowRuleIntents = Lists.newArrayList();
        flowRuleIntents.add(intent);

        return flowRuleIntents;
    }

    /**
     * The FlowRuleService for test; always success for any flow rule operations.
     */
    class TestFlowRuleService extends FlowRuleServiceAdapter {

        Set<FlowRule> flowRulesAdd = Sets.newHashSet();
        Set<FlowRule> flowRulesModify = Sets.newHashSet();
        Set<FlowRule> flowRulesRemove = Sets.newHashSet();

        public void record(FlowRuleOperations ops) {
            flowRulesAdd.clear();
            flowRulesRemove.clear();
            ops.stages().forEach(stage -> {
                stage.forEach(op -> {
                    switch (op.type()) {
                        case ADD:
                            flowRulesAdd.add(op.rule());
                            break;
                        case REMOVE:
                            flowRulesRemove.add(op.rule());
                            break;
                        case MODIFY:
                            flowRulesModify.add(op.rule());
                        default:
                            break;
                    }
                });
            });
        }

        @Override
        public void apply(FlowRuleOperations ops) {
            record(ops);
            ops.callback().onSuccess(ops);
        }
    }

    /**
     * The FlowRuleService for test; always failed for any flow rule operations.
     */
    class TestFailedFlowRuleService extends TestFlowRuleService {
        @Override
        public void apply(FlowRuleOperations ops) {
            record(ops);
            ops.callback().onError(ops);
        }
    }

    /**
     * The FlowRuleService for testing non-disruptive reallocation.
     * It keeps all the FlowRules installed/uninstalled.
     */
    class TestFlowRuleServiceNonDisruptive extends FlowRuleServiceAdapter {

        Set<FlowRule> flowRulesAdd = Sets.newHashSet();
        Set<FlowRule> flowRulesRemove = Sets.newHashSet();

        public void record(FlowRuleOperations ops) {
            ops.stages().forEach(stage -> {
                stage.forEach(op -> {
                    switch (op.type()) {
                        case ADD:
                            flowRulesAdd.add(op.rule());
                            break;
                        case REMOVE:
                            flowRulesRemove.add(op.rule());
                            break;
                        default:
                            break;
                    }
                });
            });
        }

        @Override
        public void apply(FlowRuleOperations ops) {
            record(ops);
            ops.callback().onSuccess(ops);
        }
    }

}
