e4ff8c1b4110e877720c0f7a22d8a314b1052dcc
[openflowplugin.git] / openflowplugin-it / src / test / java / org / opendaylight / openflowplugin / openflow / md / it / OFPluginFlowTest.java
1 /**
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.openflowplugin.openflow.md.it;
9
10 import static org.ops4j.pax.exam.CoreOptions.options;
11 import static org.ops4j.pax.exam.CoreOptions.systemProperty;
12
13 import com.google.common.base.Optional;
14 import com.google.common.util.concurrent.CheckedFuture;
15 import com.google.common.util.concurrent.FutureCallback;
16 import com.google.common.util.concurrent.Futures;
17 import com.google.common.util.concurrent.MoreExecutors;
18 import java.math.BigInteger;
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.Deque;
22 import java.util.List;
23 import java.util.concurrent.ArrayBlockingQueue;
24 import java.util.concurrent.TimeUnit;
25 import java.util.concurrent.TimeoutException;
26 import javax.annotation.Nonnull;
27 import javax.inject.Inject;
28 import org.junit.After;
29 import org.junit.Assert;
30 import org.junit.Before;
31 import org.junit.Test;
32 import org.junit.runner.RunWith;
33 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
34 import org.opendaylight.controller.md.sal.binding.api.DataObjectModification.ModificationType;
35 import org.opendaylight.controller.md.sal.binding.api.DataTreeChangeListener;
36 import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
37 import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
38 import org.opendaylight.controller.md.sal.binding.api.ReadTransaction;
39 import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction;
40 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
41 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
42 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
43 import org.opendaylight.controller.sal.binding.api.NotificationProviderService;
44 import org.opendaylight.openflowjava.protocol.impl.clients.ClientEvent;
45 import org.opendaylight.openflowjava.protocol.impl.clients.ScenarioHandler;
46 import org.opendaylight.openflowjava.protocol.impl.clients.SimpleClient;
47 import org.opendaylight.openflowjava.protocol.impl.clients.SleepEvent;
48 import org.opendaylight.openflowjava.protocol.impl.clients.WaitForMessageEvent;
49 import org.opendaylight.openflowjava.util.ByteBufUtils;
50 import org.opendaylight.openflowplugin.openflow.md.core.ThreadPoolLoggingExecutor;
51 import org.opendaylight.openflowplugin.openflow.md.core.sal.OpenflowPluginProvider;
52 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4Prefix;
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.DecNwTtlCaseBuilder;
54 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.dec.nw.ttl._case.DecNwTtl;
55 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.dec.nw.ttl._case.DecNwTtlBuilder;
56 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.Action;
57 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.ActionBuilder;
58 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.ActionKey;
59 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
60 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowId;
61 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.Table;
62 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.TableKey;
63 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.Flow;
64 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowBuilder;
65 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowKey;
66 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowCookie;
67 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowModFlags;
68 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.InstructionsBuilder;
69 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.MatchBuilder;
70 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.instruction.ApplyActionsCaseBuilder;
71 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.instruction.apply.actions._case.ApplyActionsBuilder;
72 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.Instruction;
73 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.InstructionBuilder;
74 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.InstructionKey;
75 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes;
76 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
77 import org.opendaylight.yang.gen.v1.urn.opendaylight.l2.types.rev130827.EtherType;
78 import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.ethernet.match.fields.EthernetTypeBuilder;
79 import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.match.EthernetMatchBuilder;
80 import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.match.layer._3.match.Ipv4Match;
81 import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.match.layer._3.match.Ipv4MatchBuilder;
82 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
83 import org.ops4j.pax.exam.Configuration;
84 import org.ops4j.pax.exam.Option;
85 import org.ops4j.pax.exam.junit.PaxExam;
86 import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
87 import org.ops4j.pax.exam.spi.reactors.PerClass;
88 import org.ops4j.pax.exam.util.Filter;
89 import org.osgi.framework.BundleContext;
90 import org.slf4j.Logger;
91 import org.slf4j.LoggerFactory;
92
93 /**
94  * covers basic handshake scenarios
95  */
96 @RunWith(PaxExam.class)
97 @ExamReactorStrategy(PerClass.class)
98 public class OFPluginFlowTest {
99
100     static final Logger LOG = LoggerFactory
101             .getLogger(OFPluginFlowTest.class);
102
103     private static final ArrayBlockingQueue<Runnable> SCENARIO_POOL_QUEUE = new ArrayBlockingQueue<>(1);
104
105     @Inject @Filter(timeout=60000)
106     OpenflowPluginProvider openflowPluginProvider;
107
108     @Inject @Filter(timeout=60000)
109     BundleContext ctx;
110
111     @Inject @Filter(timeout=60000)
112     static DataBroker dataBroker;
113
114     @Inject @Filter(timeout=60000)
115     NotificationProviderService notificationService;
116
117     private SimpleClient switchSim;
118     private ThreadPoolLoggingExecutor scenarioPool;
119
120     /**
121      * test setup
122      * @throws InterruptedException
123      */
124     @Before
125     public void setUp() throws InterruptedException {
126         LOG.debug("openflowPluginProvider: "+openflowPluginProvider);
127         scenarioPool = new ThreadPoolLoggingExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, SCENARIO_POOL_QUEUE, "scenario");
128         //FIXME: plugin should provide service exposing startup result via future
129         Thread.sleep(5000L);
130     }
131
132     /**
133      * test tear down
134      */
135     @After
136     public void tearDown() {
137         try {
138             LOG.debug("tearing down simulator");
139             switchSim.getScenarioDone().get(getFailSafeTimeout(), TimeUnit.MILLISECONDS);
140         } catch (Exception e) {
141             String msg = "waiting for scenario to finish failed: "+e.getMessage();
142             LOG.error(msg, e);
143             Assert.fail(msg);
144         } finally {
145             scenarioPool.shutdownNow();
146             SCENARIO_POOL_QUEUE.clear();
147         }
148
149         try {
150             LOG.debug("checking if simulator succeeded to connect to controller");
151             boolean simulatorWasOnline = switchSim.getIsOnlineFuture().get(100, TimeUnit.MILLISECONDS);
152             Assert.assertTrue("simulator failed to connect to controller", simulatorWasOnline);
153         } catch (Exception e) {
154             String message = "simulator probably failed to connect to controller";
155             LOG.error(message, e);
156             Assert.fail(message);
157         }
158     }
159
160     final class TriggerTestListener implements DataTreeChangeListener<FlowCapableNode> {
161
162         public TriggerTestListener() {
163             // NOOP
164         }
165
166         @Override
167         public void onDataTreeChanged(@Nonnull Collection<DataTreeModification<FlowCapableNode>> modifications) {
168
169             for (DataTreeModification modification : modifications) {
170                 if (modification.getRootNode().getModificationType() == ModificationType.WRITE) {
171                     InstanceIdentifier<FlowCapableNode> ii = modification.getRootPath().getRootIdentifier();
172                     if (ii != null) {
173                         LOG.info("Node was added (brm) {}", ii);
174                         writeFlow(createTestFlow(), ii);
175                         break;
176                     }
177                 }
178             }
179         }
180     }
181
182     /**
183      * test basic integration with OFLib running the handshake
184      * @throws Exception
185      */
186     @Test
187     public void testFlowMod() throws Exception {
188         LOG.debug("testFlowMod integration test");
189         TriggerTestListener brmListener = new TriggerTestListener();
190
191         final DataTreeIdentifier<FlowCapableNode> dataTreeIdentifier = new DataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, getWildcardPath());
192         dataBroker.registerDataTreeChangeListener(dataTreeIdentifier, brmListener);
193
194         switchSim = createSimpleClient();
195         switchSim.setSecuredClient(false);
196         Deque<ClientEvent> handshakeScenario = ScenarioFactory.createHandshakeScenarioVBM(
197                 ScenarioFactory.VERSION_BITMAP_13, (short) 0, ScenarioFactory.VERSION_BITMAP_10_13, false);
198         handshakeScenario.addFirst(new SleepEvent(6000L));
199         ScenarioFactory.appendPostHandshakeScenario(handshakeScenario, true);
200         WaitForMessageEvent flowModEvent = new WaitForMessageEvent(ByteBufUtils
201                 .hexStringToBytes(
202                         "04 0e 00 58 00 00 00 03 00 00 00 00 00 00 00 0a "
203                         + "00 00 00 00 00 00 00 0a 00 00 00 00 00 00 80 00 "
204                         + "ff ff ff ff ff ff ff ff ff ff ff ff 00 01 00 00 "
205                         + "00 01 00 16 80 00 0a 02 08 00 80 00 19 08 0a 00 "
206                         + "00 01 ff ff ff 00 00 00 00 04 00 10 00 00 00 00 "
207                         + "00 18 00 08 00 00 00 00"));
208         handshakeScenario.addFirst(flowModEvent);
209         ScenarioHandler scenario = new ScenarioHandler(handshakeScenario);
210         switchSim.setScenarioHandler(scenario);
211         scenarioPool.execute(switchSim);
212         LOG.info("finishing testFlowMod");
213     }
214
215     private static InstanceIdentifier<?> getWildcardPath() {
216         return InstanceIdentifier.create(Nodes.class).child(Node.class).augmentation(FlowCapableNode.class);
217     }
218
219     /**
220      * @return
221      */
222     private static SimpleClient createSimpleClient() {
223         return new SimpleClient("localhost", 6653);
224     }
225
226     /**
227      * @return timeout for case of failure
228      */
229     private static long getFailSafeTimeout() {
230         return 20000;
231     }
232
233
234     /**
235      * @return bundle options
236      */
237     @Configuration
238     public Option[] config() {
239         LOG.info("configuring...");
240         return options(
241                 systemProperty("osgi.console").value("2401"),
242                 systemProperty("osgi.bundles.defaultStartLevel").value("4"),
243                 systemProperty("pax.exam.osgi.unresolved.fail").value("true"),
244
245                 OFPaxOptionsAssistant.osgiConsoleBundles(),
246                 OFPaxOptionsAssistant.loggingBudles(),
247                 OFPaxOptionsAssistant.ofPluginBundles());
248     }
249
250     static FlowBuilder createTestFlow() {
251         short tableId = 0;
252         FlowBuilder flow = new FlowBuilder();
253         flow.setMatch(createMatch1().build());
254         flow.setInstructions(createDecNwTtlInstructions().build());
255
256         FlowId flowId = new FlowId("127");
257         FlowKey key = new FlowKey(flowId);
258         if (null == flow.isBarrier()) {
259             flow.setBarrier(Boolean.FALSE);
260         }
261         BigInteger value = BigInteger.TEN;
262         flow.setCookie(new FlowCookie(value));
263         flow.setCookieMask(new FlowCookie(value));
264         flow.setHardTimeout(0);
265         flow.setIdleTimeout(0);
266         flow.setInstallHw(false);
267         flow.setStrict(false);
268         flow.setContainerName(null);
269         flow.setFlags(new FlowModFlags(false, false, false, false, true));
270         flow.setId(flowId);
271         flow.setTableId(tableId);
272
273         flow.setKey(key);
274         flow.setFlowName("Foo" + "X" + "f1");
275
276         return flow;
277     }
278
279     private static MatchBuilder createMatch1() {
280         MatchBuilder match = new MatchBuilder();
281         Ipv4MatchBuilder ipv4Match = new Ipv4MatchBuilder();
282         Ipv4Prefix prefix = new Ipv4Prefix("10.0.0.1/24");
283         ipv4Match.setIpv4Destination(prefix);
284         Ipv4Match i4m = ipv4Match.build();
285         match.setLayer3Match(i4m);
286
287         EthernetMatchBuilder eth = new EthernetMatchBuilder();
288         EthernetTypeBuilder ethTypeBuilder = new EthernetTypeBuilder();
289         ethTypeBuilder.setType(new EtherType(0x0800L));
290         eth.setEthernetType(ethTypeBuilder.build());
291         match.setEthernetMatch(eth.build());
292         return match;
293     }
294
295     private static InstructionsBuilder createDecNwTtlInstructions() {
296         DecNwTtlBuilder ta = new DecNwTtlBuilder();
297         DecNwTtl decNwTtl = ta.build();
298         ActionBuilder ab = new ActionBuilder();
299         ab.setAction(new DecNwTtlCaseBuilder().setDecNwTtl(decNwTtl).build());
300         ab.setKey(new ActionKey(0));
301         // Add our drop action to a list
302         List<Action> actionList = new ArrayList<Action>();
303         actionList.add(ab.build());
304
305         // Create an Apply Action
306         ApplyActionsBuilder aab = new ApplyActionsBuilder();
307         aab.setAction(actionList);
308
309         // Wrap our Apply Action in an Instruction
310         InstructionBuilder ib = new InstructionBuilder();
311         ib.setInstruction(new ApplyActionsCaseBuilder().setApplyActions(aab.build()).build());
312         ib.setKey(new InstructionKey(0));
313         ib.setOrder(0);
314
315         // Put our Instruction in a list of Instructions
316         InstructionsBuilder isb = new InstructionsBuilder();
317         List<Instruction> instructions = new ArrayList<Instruction>();
318         instructions.add(ib.build());
319         ib.setKey(new InstructionKey(0));
320         isb.setInstruction(instructions);
321         return isb;
322     }
323
324     static void writeFlow(FlowBuilder flow, InstanceIdentifier<FlowCapableNode> flowNodeIdent) {
325         ReadWriteTransaction modification = dataBroker.newReadWriteTransaction();
326         final InstanceIdentifier<Flow> path1 = flowNodeIdent.child(Table.class, new TableKey(flow.getTableId()))
327                 .child(Flow.class, flow.getKey());
328         modification.merge(LogicalDatastoreType.CONFIGURATION, path1, flow.build(), true);
329         CheckedFuture<Void, TransactionCommitFailedException> commitFuture = modification.submit();
330         Futures.addCallback(commitFuture, new FutureCallback<Void>() {
331             @Override
332             public void onSuccess(Void aVoid) {
333                 LOG.debug("Write of flow on device succeeded.");
334             }
335
336             @Override
337             public void onFailure(Throwable throwable) {
338                 LOG.error("Write of flow on device failed.", throwable);
339             }
340         }, MoreExecutors.directExecutor());
341     }
342
343     //TODO move to separate test util class
344     private final static Flow readFlow(InstanceIdentifier<Flow> flow) {
345         Flow searchedFlow = null;
346         ReadTransaction rt = dataBroker.newReadOnlyTransaction();
347         CheckedFuture<Optional<Flow>, ReadFailedException> flowFuture =
348             rt.read(LogicalDatastoreType.CONFIGURATION, flow);
349
350         try {
351           Optional<Flow> maybeFlow = flowFuture.checkedGet(500, TimeUnit.SECONDS);
352           if(maybeFlow.isPresent()) {
353               searchedFlow = maybeFlow.get();
354           }
355         } catch (TimeoutException e) {
356           LOG.error("Future timed out. Getting FLOW from DataStore failed.", e);
357         } catch (ReadFailedException e) {
358           LOG.error("Something wrong happened in DataStore. Getting FLOW for userId {} failed.", e);
359         }
360
361         return searchedFlow;
362     }
363 }