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