Add pax-exam integration-tests for hardware_vtep
[netvirt.git] / integrationtest / src / test / java / org / opendaylight / ovsdb / integrationtest / schema / hardwarevtep / HardwareVTEPIT.java
1 /*
2  *  Copyright (C) 2014 Red Hat, Inc.
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  *  Authors : Sam Hague, Matt Oswalt
9  */
10 package org.opendaylight.ovsdb.integrationtest.schema.hardwarevtep;
11
12 import static org.junit.Assert.assertEquals;
13 import static org.junit.Assert.assertNotNull;
14 import static org.junit.Assert.assertNull;
15 import static org.junit.Assert.assertTrue;
16 import static org.junit.Assert.fail;
17 import static org.opendaylight.ovsdb.lib.operations.Operations.op;
18 import static org.ops4j.pax.exam.CoreOptions.junitBundles;
19 import static org.ops4j.pax.exam.CoreOptions.options;
20 import static org.ops4j.pax.exam.CoreOptions.propagateSystemProperty;
21 import static org.ops4j.pax.exam.CoreOptions.systemProperty;
22
23 import com.google.common.collect.Lists;
24 import com.google.common.collect.Sets;
25 import com.google.common.util.concurrent.ListenableFuture;
26 import java.io.IOException;
27 import java.util.HashMap;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Set;
31 import java.util.concurrent.ExecutionException;
32 import javax.inject.Inject;
33 import junit.framework.Assert;
34 import org.junit.Assume;
35 import org.junit.Before;
36 import org.junit.Rule;
37 import org.junit.Test;
38 import org.junit.rules.TestRule;
39 import org.junit.rules.TestWatcher;
40 import org.junit.runner.Description;
41 import org.junit.runner.RunWith;
42 import org.opendaylight.ovsdb.integrationtest.ConfigurationBundles;
43 import org.opendaylight.ovsdb.integrationtest.OvsdbIntegrationTestBase;
44 import org.opendaylight.ovsdb.lib.MonitorCallBack;
45 import org.opendaylight.ovsdb.lib.OvsdbClient;
46 import org.opendaylight.ovsdb.lib.message.MonitorRequest;
47 import org.opendaylight.ovsdb.lib.message.MonitorRequestBuilder;
48 import org.opendaylight.ovsdb.lib.message.MonitorSelect;
49 import org.opendaylight.ovsdb.lib.message.TableUpdate;
50 import org.opendaylight.ovsdb.lib.message.TableUpdates;
51 import org.opendaylight.ovsdb.lib.notation.Mutator;
52 import org.opendaylight.ovsdb.lib.notation.Row;
53 import org.opendaylight.ovsdb.lib.notation.UUID;
54 import org.opendaylight.ovsdb.lib.operations.OperationResult;
55 import org.opendaylight.ovsdb.lib.operations.TransactionBuilder;
56 import org.opendaylight.ovsdb.lib.schema.DatabaseSchema;
57 import org.opendaylight.ovsdb.lib.schema.GenericTableSchema;
58 import org.opendaylight.ovsdb.lib.schema.TableSchema;
59 import org.opendaylight.ovsdb.lib.schema.typed.TypedBaseTable;
60 import org.opendaylight.ovsdb.schema.hardwarevtep.Global;
61 import org.opendaylight.ovsdb.schema.hardwarevtep.Manager;
62 import org.opendaylight.ovsdb.schema.openvswitch.OpenVSwitch;
63 import org.ops4j.pax.exam.Configuration;
64 import org.ops4j.pax.exam.Option;
65 import org.ops4j.pax.exam.junit.PaxExam;
66 import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
67 import org.ops4j.pax.exam.spi.reactors.PerSuite;
68 import org.ops4j.pax.exam.util.PathUtils;
69 import org.osgi.framework.BundleContext;
70 import org.slf4j.Logger;
71 import org.slf4j.LoggerFactory;
72
73 @RunWith(PaxExam.class)
74 @ExamReactorStrategy(PerSuite.class)
75 public class HardwareVTEPIT  extends OvsdbIntegrationTestBase {
76     private static final Logger LOG = LoggerFactory.getLogger(HardwareVTEPIT.class);
77     private static boolean monitorReady = false;
78     private static boolean schemaSupported = false;
79     private static final String ASSERT_TRANS_ERROR = "Transaction should not have errors";
80     private static final String ASSERT_TRANS_RESULT_EMPTY = "Transaction should not be empty";
81     private static final String ASSERT_TRANS_OPERATION_COUNT = "Transaction should match number of operations";
82     private static final String ASSERT_TRANS_UUID = "Transaction UUID should not be null";
83     private UUID testManagerUuid = null;
84
85     private static Map<String, Map<UUID, Row>> tableCache = new HashMap<>();
86     private static Map<String, Map<UUID, Row>> getTableCache () {
87         return tableCache;
88     }
89
90     private static OvsdbClient ovsdbClient;
91     private OvsdbClient getClient () {
92         return ovsdbClient;
93     }
94
95     private static DatabaseSchema dbSchema;
96     private DatabaseSchema getDbSchema () {
97         return dbSchema;
98     }
99
100     @Inject
101     private BundleContext bc;
102
103     @Configuration
104     public Option[] config() throws Exception {
105         return options(
106                 systemProperty("logback.configurationFile").value(
107                         "file:" + PathUtils.getBaseDir()
108                                 + "/src/test/resources/logback.xml"
109                 ),
110                 // To start OSGi console for inspection remotely
111                 systemProperty("osgi.console").value("2401"),
112
113                 propagateSystemProperty("ovsdbserver.ipaddress"),
114                 propagateSystemProperty("ovsdbserver.port"),
115
116                 ConfigurationBundles.controllerBundles(),
117                 ConfigurationBundles.ovsdbLibraryBundles(),
118                 ConfigurationBundles.ovsdbDefaultSchemaBundles(),
119                 junitBundles()
120         );
121     }
122
123     /*
124      * Method adds a log as each test method starts and finishes. This is useful when
125      * the test suite is used because the suites only print a final summary.
126      */
127     @Rule
128     public TestRule watcher = new TestWatcher() {
129         @Override
130         protected void starting(Description description) {
131             LOG.info("TestWatcher: Starting test: {}",
132                     description.getDisplayName());
133         }
134
135         @Override
136         protected void finished(Description description) {
137             LOG.info("TestWatcher: Finished test: {}", description.getDisplayName());
138         }
139     };
140
141     @Before
142     public void setUp () throws ExecutionException, InterruptedException, IOException {
143         areWeReady(bc);
144         assertTrue(HARDWARE_VTEP + " is required.", checkSchema(HARDWARE_VTEP));
145         assertTrue("Failed to monitor tables", monitorTables());
146     }
147
148     public boolean checkSchema (String schema) {
149         if (schemaSupported) {
150             LOG.info("Schema ({}) is supported", schema);
151             return true;
152         }
153         try {
154             ovsdbClient = getTestConnection();
155             assertNotNull("Invalid Client. Check connection params", ovsdbClient);
156             //Thread.sleep(3000); // Wait for a few seconds to get the Schema exchange done
157             if (isSchemaSupported(ovsdbClient, schema)) {
158                 dbSchema = ovsdbClient.getSchema(schema).get();
159                 assertNotNull(dbSchema);
160                 LOG.info("{} schema in {} with tables: {}",
161                         schema, ovsdbClient.getConnectionInfo(), dbSchema.getTables());
162                 schemaSupported = true;
163                 return true;
164             }
165         } catch (Exception e) {
166             fail("Exception : "+e.getMessage());
167         }
168
169         LOG.info("Schema ({}) is not supported", schema);
170         return false;
171     }
172
173     public UUID getOpenVSwitchTableUuid (OvsdbClient ovs, Map<String, Map<UUID, Row>> tableCache) {
174         OpenVSwitch openVSwitch = getClient().getTypedRowWrapper(OpenVSwitch.class, null);
175         Map<UUID, Row> ovsTable = tableCache.get(openVSwitch.getSchema().getName());
176         if (ovsTable != null) {
177             if (ovsTable.keySet().size() >= 1) {
178                 return (UUID)ovsTable.keySet().toArray()[0];
179             }
180         }
181         return null;
182     }
183
184     public UUID getGlobalTableUuid(OvsdbClient ovs, Map<String, Map<UUID, Row>> tableCache) {
185         Global glbl = getClient().getTypedRowWrapper(Global.class, null);
186         Map<UUID, Row> glblTbl = tableCache.get(glbl.getSchema().getName());
187         if (glblTbl != null) {
188             if (glblTbl.keySet().size() >= 1) {
189                 return (UUID)glblTbl.keySet().toArray()[0];
190             }
191         }
192         return null;
193     }
194
195     public boolean isSchemaSupported (OvsdbClient client, String schema) throws ExecutionException, InterruptedException {
196         ListenableFuture<List<String>> databases = client.getDatabases();
197         List<String> dbNames = databases.get();
198         assertNotNull(dbNames);
199         if (dbNames.contains(schema)) {
200             return true;
201         } else {
202             return false;
203         }
204     }
205
206     /**
207      * As per RFC 7047, section 4.1.5, if a Monitor request is sent without any columns, the update response will not include
208      * the _uuid column.
209      * ----------------------------------------------------------------------------------------------------------------------------------
210      * Each <monitor-request> specifies one or more columns and the manner in which the columns (or the entire table) are to be monitored.
211      * The "columns" member specifies the columns whose values are monitored. It MUST NOT contain duplicates.
212      * If "columns" is omitted, all columns in the table, except for "_uuid", are monitored.
213      * ----------------------------------------------------------------------------------------------------------------------------------
214      * In order to overcome this limitation, this method
215      *
216      * @return MonitorRequest that includes all the Bridge Columns including _uuid
217      */
218     public <T extends TypedBaseTable<GenericTableSchema>> MonitorRequest<GenericTableSchema> getAllColumnsMonitorRequest (Class <T> klazz) {
219         TypedBaseTable<GenericTableSchema> table = getClient().createTypedRowWrapper(klazz);
220         GenericTableSchema tableSchema = table.getSchema();
221         Set<String> columns = tableSchema.getColumns();
222         MonitorRequestBuilder<GenericTableSchema> bridgeBuilder = MonitorRequestBuilder.builder(table.getSchema());
223         for (String column : columns) {
224             bridgeBuilder.addColumn(column);
225         }
226         return bridgeBuilder.with(new MonitorSelect(true, true, true, true)).build();
227     }
228
229     public <T extends TableSchema<T>> MonitorRequest<T> getAllColumnsMonitorRequest (T tableSchema) {
230         Set<String> columns = tableSchema.getColumns();
231         MonitorRequestBuilder<T> monitorBuilder = MonitorRequestBuilder.builder(tableSchema);
232         for (String column : columns) {
233             monitorBuilder.addColumn(column);
234         }
235         return monitorBuilder.with(new MonitorSelect(true, true, true, true)).build();
236     }
237
238     public boolean monitorTables () throws ExecutionException, InterruptedException, IOException {
239         if (monitorReady) {
240             LOG.info("Monitoring is already initialized.");
241             return monitorReady;
242         }
243
244         assertNotNull(getDbSchema());
245
246         List<MonitorRequest<GenericTableSchema>> monitorRequests = Lists.newArrayList();
247         Set<String> tables = getDbSchema().getTables();
248         assertNotNull("ovsdb tables should not be null", tables);
249
250         for (String tableName : tables) {
251             GenericTableSchema tableSchema = getDbSchema().table(tableName, GenericTableSchema.class);
252             monitorRequests.add(this.getAllColumnsMonitorRequest(tableSchema));
253         }
254         TableUpdates updates = getClient().monitor(getDbSchema(), monitorRequests, new UpdateMonitor());
255         assertNotNull(updates);
256         this.updateTableCache(updates);
257
258         monitorReady = true;
259         LOG.info("Monitoring is initialized.");
260         return monitorReady;
261     }
262
263     private void updateTableCache (TableUpdates updates) {
264         for (String tableName : updates.getUpdates().keySet()) {
265             Map<UUID, Row> tUpdate = getTableCache().get(tableName);
266             TableUpdate update = updates.getUpdates().get(tableName);
267             for (UUID uuid : (Set<UUID>)update.getRows().keySet()) {
268                 if (update.getNew(uuid) != null) {
269                     if (tUpdate == null) {
270                         tUpdate = new HashMap<>();
271                         getTableCache().put(tableName, tUpdate);
272                     }
273                     tUpdate.put(uuid, update.getNew(uuid));
274                 } else {
275                     tUpdate.remove(uuid);
276                 }
277             }
278         }
279     }
280
281     private class UpdateMonitor implements MonitorCallBack {
282         @Override
283         public void update(TableUpdates result, DatabaseSchema dbSchema) {
284             updateTableCache(result);
285         }
286
287         @Override
288         public void exception(Throwable t) {
289             LOG.error("Exception t = " + t);
290         }
291     }
292
293     public List<OperationResult> executeTransaction (TransactionBuilder transactionBuilder, String text)
294             throws ExecutionException, InterruptedException {
295         ListenableFuture<List<OperationResult>> results = transactionBuilder.execute();
296         List<OperationResult> operationResults = results.get();
297         LOG.info("{}: {}", text, operationResults);
298         org.junit.Assert.assertFalse(ASSERT_TRANS_RESULT_EMPTY, operationResults.isEmpty());
299         assertEquals(ASSERT_TRANS_OPERATION_COUNT, transactionBuilder.getOperations().size(), operationResults.size());
300         for (OperationResult result : operationResults) {
301             assertNull(ASSERT_TRANS_ERROR, result.getError());
302         }
303         //Thread.sleep(500); // Wait for a few seconds to ensure the cache updates
304         return operationResults;
305     }
306
307     /**
308      * Create a new manager string in addition to whatever is already there
309      * Will modify the Global table to include the UUID to the new Manager row
310      */
311     public void managerInsert () throws ExecutionException, InterruptedException {
312         //Ensure test only proceeds if HW VTEP is supported
313         Assume.assumeTrue(isSchemaSupported(getClient(), HARDWARE_VTEP));
314
315         //proceed only if schema was already retrieved successfully
316         Assert.assertNotNull(getDbSchema());
317
318         //create new manager and set target string
319         Manager manager = getClient().createTypedRowWrapper(Manager.class);
320         manager.setTarget("ptcp:6641");
321
322         String transactionUuidStr = "foobar";
323
324         Global glbl = this.getClient().createTypedRowWrapper(Global.class);
325         glbl.setManagers(Sets.newHashSet(new UUID(transactionUuidStr)));
326
327         TransactionBuilder transactionBuilder = getClient().transactBuilder(getDbSchema())
328                 .add(op.insert(manager.getSchema())
329                         .withId(transactionUuidStr)
330                         .value(manager.getTargetColumn()))
331                 .add(op.comment("Manager: Inserting " + transactionUuidStr))
332                 .add(op.mutate(glbl.getSchema())
333                         .addMutation(glbl.getManagersColumn().getSchema(), Mutator.INSERT,
334                                 glbl.getManagersColumn().getData()))
335                 .add(op.comment("Global: Mutating " + transactionUuidStr));
336
337         int insertOperationIndex = 0;
338         List<OperationResult> operationResults = executeTransaction(transactionBuilder,
339                 "Manager: Insert and Mutate results");
340         testManagerUuid = operationResults.get(insertOperationIndex).getUuid();
341         assertNotNull(ASSERT_TRANS_UUID, testManagerUuid);
342
343         // Verify that the local cache was updated with the remote changes
344         Row managerRow = getTableCache().get(manager.getSchema().getName()).get(testManagerUuid);
345         Manager monitoredManager = getClient().getTypedRowWrapper(Manager.class, managerRow);
346         assertEquals(manager.getTargetColumn().getData(), monitoredManager.getTargetColumn().getData());
347         assertNotNull(monitoredManager.getUuid());
348         assertNotNull(monitoredManager.getVersion());
349         assertNotNull(getGlobalTableUuid(getClient(), getTableCache()));
350     }
351
352     public void managerDelete () throws ExecutionException, InterruptedException {
353         Assume.assumeTrue(isSchemaSupported(getClient(), HARDWARE_VTEP));
354
355         Manager manager = getClient().getTypedRowWrapper(Manager.class, null);
356         Global global = getClient().getTypedRowWrapper(Global.class, null);
357
358         TransactionBuilder transactionBuilder = getClient().transactBuilder(getDbSchema())
359                 .add(op.delete(manager.getSchema())
360                         .where(manager.getUuidColumn().getSchema().opEqual(testManagerUuid))
361                         .build())
362                 .add(op.comment("Manager: Deleting " + testManagerUuid))
363                 .add(op.mutate(global.getSchema())
364                         .addMutation(global.getManagersColumn().getSchema(), Mutator.DELETE,
365                                 Sets.newHashSet(testManagerUuid)))
366                 .add(op.comment("Global: Mutating " + testManagerUuid))
367                 .add(op.commit(true));
368
369         executeTransaction(transactionBuilder, "Manager delete operation results");
370     }
371
372     @Test
373     public void testManager () throws ExecutionException, InterruptedException {
374         managerInsert();
375         managerDelete();
376     }
377 }