/** * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.openflowplugin.openflow.md.it; import static org.ops4j.pax.exam.CoreOptions.options; import static org.ops4j.pax.exam.CoreOptions.systemProperty; import com.google.common.base.Optional; import com.google.common.util.concurrent.CheckedFuture; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import java.math.BigInteger; import java.util.ArrayList; import java.util.Collection; import java.util.Deque; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import javax.annotation.Nonnull; import javax.inject.Inject; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.opendaylight.controller.md.sal.binding.api.DataBroker; import org.opendaylight.controller.md.sal.binding.api.DataObjectModification.ModificationType; 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.ReadTransaction; import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction; import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException; import org.opendaylight.controller.sal.binding.api.NotificationProviderService; import org.opendaylight.openflowjava.protocol.impl.clients.ClientEvent; import org.opendaylight.openflowjava.protocol.impl.clients.ScenarioHandler; import org.opendaylight.openflowjava.protocol.impl.clients.SimpleClient; import org.opendaylight.openflowjava.protocol.impl.clients.SleepEvent; import org.opendaylight.openflowjava.protocol.impl.clients.WaitForMessageEvent; import org.opendaylight.openflowjava.util.ByteBufUtils; import org.opendaylight.openflowplugin.openflow.md.core.ThreadPoolLoggingExecutor; import org.opendaylight.openflowplugin.openflow.md.core.sal.OpenflowPluginProvider; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4Prefix; import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.DecNwTtlCaseBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.dec.nw.ttl._case.DecNwTtl; import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.dec.nw.ttl._case.DecNwTtlBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.Action; import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.ActionBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.ActionKey; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowId; 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.TableKey; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.Flow; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowKey; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowCookie; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowModFlags; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.InstructionsBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.MatchBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.instruction.ApplyActionsCaseBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.instruction.apply.actions._case.ApplyActionsBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.Instruction; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.InstructionBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.InstructionKey; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; import org.opendaylight.yang.gen.v1.urn.opendaylight.l2.types.rev130827.EtherType; import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.ethernet.match.fields.EthernetTypeBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.match.EthernetMatchBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.match.layer._3.match.Ipv4Match; import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.match.layer._3.match.Ipv4MatchBuilder; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.ops4j.pax.exam.Configuration; import org.ops4j.pax.exam.Option; import org.ops4j.pax.exam.junit.PaxExam; import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy; import org.ops4j.pax.exam.spi.reactors.PerClass; import org.ops4j.pax.exam.util.Filter; import org.osgi.framework.BundleContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * covers basic handshake scenarios */ @RunWith(PaxExam.class) @ExamReactorStrategy(PerClass.class) public class OFPluginFlowTest { static final Logger LOG = LoggerFactory .getLogger(OFPluginFlowTest.class); private static final ArrayBlockingQueue<Runnable> SCENARIO_POOL_QUEUE = new ArrayBlockingQueue<>(1); @Inject @Filter(timeout=60000) OpenflowPluginProvider openflowPluginProvider; @Inject @Filter(timeout=60000) BundleContext ctx; @Inject @Filter(timeout=60000) static DataBroker dataBroker; @Inject @Filter(timeout=60000) NotificationProviderService notificationService; private SimpleClient switchSim; private ThreadPoolLoggingExecutor scenarioPool; /** * test setup * @throws InterruptedException */ @Before public void setUp() throws InterruptedException { LOG.debug("openflowPluginProvider: "+openflowPluginProvider); scenarioPool = new ThreadPoolLoggingExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, SCENARIO_POOL_QUEUE, "scenario"); //FIXME: plugin should provide service exposing startup result via future Thread.sleep(5000L); } /** * test tear down */ @After public void tearDown() { try { LOG.debug("tearing down simulator"); switchSim.getScenarioDone().get(getFailSafeTimeout(), TimeUnit.MILLISECONDS); } catch (Exception e) { String msg = "waiting for scenario to finish failed: "+e.getMessage(); LOG.error(msg, e); Assert.fail(msg); } finally { scenarioPool.shutdownNow(); SCENARIO_POOL_QUEUE.clear(); } try { LOG.debug("checking if simulator succeeded to connect to controller"); boolean simulatorWasOnline = switchSim.getIsOnlineFuture().get(100, TimeUnit.MILLISECONDS); Assert.assertTrue("simulator failed to connect to controller", simulatorWasOnline); } catch (Exception e) { String message = "simulator probably failed to connect to controller"; LOG.error(message, e); Assert.fail(message); } } final class TriggerTestListener implements DataTreeChangeListener<FlowCapableNode> { public TriggerTestListener() { // NOOP } @Override public void onDataTreeChanged(@Nonnull Collection<DataTreeModification<FlowCapableNode>> modifications) { for (DataTreeModification modification : modifications) { if (modification.getRootNode().getModificationType() == ModificationType.WRITE) { InstanceIdentifier<FlowCapableNode> ii = modification.getRootPath().getRootIdentifier(); if (ii != null) { LOG.info("Node was added (brm) {}", ii); writeFlow(createTestFlow(), ii); break; } } } } } /** * test basic integration with OFLib running the handshake * @throws Exception */ @Test public void testFlowMod() throws Exception { LOG.debug("testFlowMod integration test"); TriggerTestListener brmListener = new TriggerTestListener(); final DataTreeIdentifier<FlowCapableNode> dataTreeIdentifier = new DataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, getWildcardPath()); dataBroker.registerDataTreeChangeListener(dataTreeIdentifier, brmListener); switchSim = createSimpleClient(); switchSim.setSecuredClient(false); Deque<ClientEvent> handshakeScenario = ScenarioFactory.createHandshakeScenarioVBM( ScenarioFactory.VERSION_BITMAP_13, (short) 0, ScenarioFactory.VERSION_BITMAP_10_13, false); handshakeScenario.addFirst(new SleepEvent(6000L)); ScenarioFactory.appendPostHandshakeScenario(handshakeScenario, true); WaitForMessageEvent flowModEvent = new WaitForMessageEvent(ByteBufUtils .hexStringToBytes( "04 0e 00 58 00 00 00 03 00 00 00 00 00 00 00 0a " + "00 00 00 00 00 00 00 0a 00 00 00 00 00 00 80 00 " + "ff ff ff ff ff ff ff ff ff ff ff ff 00 01 00 00 " + "00 01 00 16 80 00 0a 02 08 00 80 00 19 08 0a 00 " + "00 01 ff ff ff 00 00 00 00 04 00 10 00 00 00 00 " + "00 18 00 08 00 00 00 00")); handshakeScenario.addFirst(flowModEvent); ScenarioHandler scenario = new ScenarioHandler(handshakeScenario); switchSim.setScenarioHandler(scenario); scenarioPool.execute(switchSim); LOG.info("finishing testFlowMod"); } private static InstanceIdentifier<?> getWildcardPath() { return InstanceIdentifier.create(Nodes.class).child(Node.class).augmentation(FlowCapableNode.class); } /** * @return */ private static SimpleClient createSimpleClient() { return new SimpleClient("localhost", 6653); } /** * @return timeout for case of failure */ private static long getFailSafeTimeout() { return 20000; } /** * @return bundle options */ @Configuration public Option[] config() { LOG.info("configuring..."); return options( systemProperty("osgi.console").value("2401"), systemProperty("osgi.bundles.defaultStartLevel").value("4"), systemProperty("pax.exam.osgi.unresolved.fail").value("true"), OFPaxOptionsAssistant.osgiConsoleBundles(), OFPaxOptionsAssistant.loggingBudles(), OFPaxOptionsAssistant.ofPluginBundles()); } static FlowBuilder createTestFlow() { short tableId = 0; FlowBuilder flow = new FlowBuilder(); flow.setMatch(createMatch1().build()); flow.setInstructions(createDecNwTtlInstructions().build()); FlowId flowId = new FlowId("127"); FlowKey key = new FlowKey(flowId); if (null == flow.isBarrier()) { flow.setBarrier(Boolean.FALSE); } BigInteger value = BigInteger.TEN; flow.setCookie(new FlowCookie(value)); flow.setCookieMask(new FlowCookie(value)); flow.setHardTimeout(0); flow.setIdleTimeout(0); flow.setInstallHw(false); flow.setStrict(false); flow.setContainerName(null); flow.setFlags(new FlowModFlags(false, false, false, false, true)); flow.setId(flowId); flow.setTableId(tableId); flow.setKey(key); flow.setFlowName("Foo" + "X" + "f1"); return flow; } private static MatchBuilder createMatch1() { MatchBuilder match = new MatchBuilder(); Ipv4MatchBuilder ipv4Match = new Ipv4MatchBuilder(); Ipv4Prefix prefix = new Ipv4Prefix("10.0.0.1/24"); ipv4Match.setIpv4Destination(prefix); Ipv4Match i4m = ipv4Match.build(); match.setLayer3Match(i4m); EthernetMatchBuilder eth = new EthernetMatchBuilder(); EthernetTypeBuilder ethTypeBuilder = new EthernetTypeBuilder(); ethTypeBuilder.setType(new EtherType(0x0800L)); eth.setEthernetType(ethTypeBuilder.build()); match.setEthernetMatch(eth.build()); return match; } private static InstructionsBuilder createDecNwTtlInstructions() { DecNwTtlBuilder ta = new DecNwTtlBuilder(); DecNwTtl decNwTtl = ta.build(); ActionBuilder ab = new ActionBuilder(); ab.setAction(new DecNwTtlCaseBuilder().setDecNwTtl(decNwTtl).build()); ab.setKey(new ActionKey(0)); // Add our drop action to a list List<Action> actionList = new ArrayList<Action>(); actionList.add(ab.build()); // Create an Apply Action ApplyActionsBuilder aab = new ApplyActionsBuilder(); aab.setAction(actionList); // Wrap our Apply Action in an Instruction InstructionBuilder ib = new InstructionBuilder(); ib.setInstruction(new ApplyActionsCaseBuilder().setApplyActions(aab.build()).build()); ib.setKey(new InstructionKey(0)); ib.setOrder(0); // Put our Instruction in a list of Instructions InstructionsBuilder isb = new InstructionsBuilder(); List<Instruction> instructions = new ArrayList<Instruction>(); instructions.add(ib.build()); ib.setKey(new InstructionKey(0)); isb.setInstruction(instructions); return isb; } static void writeFlow(FlowBuilder flow, InstanceIdentifier<FlowCapableNode> flowNodeIdent) { ReadWriteTransaction modification = dataBroker.newReadWriteTransaction(); final InstanceIdentifier<Flow> path1 = flowNodeIdent.child(Table.class, new TableKey(flow.getTableId())) .child(Flow.class, flow.getKey()); modification.merge(LogicalDatastoreType.CONFIGURATION, path1, flow.build(), true); CheckedFuture<Void, TransactionCommitFailedException> commitFuture = modification.submit(); Futures.addCallback(commitFuture, new FutureCallback<Void>() { @Override public void onSuccess(Void aVoid) { LOG.debug("Write of flow on device succeeded."); } @Override public void onFailure(Throwable throwable) { LOG.error("Write of flow on device failed.", throwable); } }); } //TODO move to separate test util class private final static Flow readFlow(InstanceIdentifier<Flow> flow) { Flow searchedFlow = null; ReadTransaction rt = dataBroker.newReadOnlyTransaction(); CheckedFuture<Optional<Flow>, ReadFailedException> flowFuture = rt.read(LogicalDatastoreType.CONFIGURATION, flow); try { Optional<Flow> maybeFlow = flowFuture.checkedGet(500, TimeUnit.SECONDS); if(maybeFlow.isPresent()) { searchedFlow = maybeFlow.get(); } } catch (TimeoutException e) { LOG.error("Future timed out. Getting FLOW from DataStore failed.", e); } catch (ReadFailedException e) { LOG.error("Something wrong happened in DataStore. Getting FLOW for userId {} failed.", e); } return searchedFlow; } }