/* * Copyright © 2014, 2017 EBay Software Foundation * * 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.ovsdb.integrationtest.ovsdbclient; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.opendaylight.ovsdb.lib.operations.Operations.op; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.common.util.concurrent.ListenableFuture; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutionException; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.opendaylight.ovsdb.lib.MonitorCallBack; import org.opendaylight.ovsdb.lib.OvsdbClient; import org.opendaylight.ovsdb.lib.it.LibraryIntegrationTestBase; import org.opendaylight.ovsdb.lib.it.LibraryIntegrationTestUtils; import org.opendaylight.ovsdb.lib.message.MonitorRequest; import org.opendaylight.ovsdb.lib.message.MonitorRequestBuilder; import org.opendaylight.ovsdb.lib.message.MonitorSelect; import org.opendaylight.ovsdb.lib.message.TableUpdate; import org.opendaylight.ovsdb.lib.message.TableUpdates; import org.opendaylight.ovsdb.lib.notation.Column; import org.opendaylight.ovsdb.lib.notation.Mutator; import org.opendaylight.ovsdb.lib.notation.Row; import org.opendaylight.ovsdb.lib.notation.UUID; import org.opendaylight.ovsdb.lib.operations.OperationResult; import org.opendaylight.ovsdb.lib.operations.TransactionBuilder; import org.opendaylight.ovsdb.lib.schema.ColumnSchema; import org.opendaylight.ovsdb.lib.schema.DatabaseSchema; import org.opendaylight.ovsdb.lib.schema.GenericTableSchema; import org.opendaylight.ovsdb.lib.schema.TableSchema; 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.slf4j.Logger; import org.slf4j.LoggerFactory; @RunWith(PaxExam.class) @ExamReactorStrategy(PerClass.class) public class OvsdbClientTestIT extends LibraryIntegrationTestBase { private static final Logger LOG = LoggerFactory.getLogger(OvsdbClientTestIT.class); OvsdbClient ovs; DatabaseSchema dbSchema = null; private static final String TEST_BRIDGE_NAME = "br-test"; private static UUID testBridgeUuid = null; /** * Test general OVSDB transactions (viz., insert, select, update, * mutate, comment, delete, where, commit) as well as the special * transactions (viz., abort and assert) */ @Test public void testTransact() throws IOException, InterruptedException, ExecutionException { assertNotNull(dbSchema); TableSchema<GenericTableSchema> bridge = dbSchema.table("Bridge", GenericTableSchema.class); bridge.column("name", String.class); createBridgeTransaction(); abortTransaction(); assertTransaction(); } /** * Test OVS monitor request and reply, with and without specific column filters, * for the Bridge table in the OVSDB. The setup involves creating a test bridge with 5 * flood_vlans and 2 key-value pairs, and monitoring the DB update. */ @Test public void testMonitorRequest() throws ExecutionException, InterruptedException, IOException { assertNotNull(dbSchema); // Create Test Bridge before testing the Monitor operation createBridgeTransaction(); sendBridgeMonitorRequest(true); // Test monitor request with Column filters sendBridgeMonitorRequest(false); // Test monitor request without filters } public void sendBridgeMonitorRequest(boolean filter) throws ExecutionException, InterruptedException, IOException { assertNotNull(dbSchema); GenericTableSchema bridge = dbSchema.table("Bridge", GenericTableSchema.class); List<MonitorRequest> monitorRequests = new ArrayList<>(); ColumnSchema<GenericTableSchema, Set<Integer>> flood_vlans = bridge.multiValuedColumn("flood_vlans", Integer.class); ColumnSchema<GenericTableSchema, Map<String, String>> externalIds = bridge.multiValuedColumn("external_ids", String.class, String.class); ColumnSchema<GenericTableSchema, String> name = bridge.column("name", String.class); MonitorRequestBuilder<GenericTableSchema> builder = new MonitorRequestBuilder<>(bridge); if (filter) { builder.addColumn(bridge.column("name")) .addColumn(bridge.column("fail_mode", String.class)) .addColumn(flood_vlans) .addColumn(externalIds); } monitorRequests.add(builder.with(new MonitorSelect(true, true, true, true)) .build()); final List<Object> results = new ArrayList<>(); TableUpdates updates = ovs.monitor(dbSchema, monitorRequests, new MonitorCallBack() { @Override public void update(TableUpdates result, DatabaseSchema dbSchema) { results.add(result); LOG.info("result = {}", result); } @Override public void exception(Throwable t) { results.add(t); LOG.warn("t = ", t); } }); if (updates != null) { results.add(updates); } for (int i = 0; i < 3 ; i++) { //wait 3 seconds to get a result LOG.info("waiting on monitor response for Bridge Table..."); if (!results.isEmpty()) { break; } Thread.sleep(1000); } assertTrue(!results.isEmpty()); Object result = results.get(0); assertTrue(result instanceof TableUpdates); updates = (TableUpdates) result; TableUpdate<GenericTableSchema> update = updates.getUpdate(bridge); assertTrue(update.getRows().size() > 0); for (UUID uuid : update.getRows().keySet()) { Row<GenericTableSchema> aNew = update.getNew(uuid); if (!aNew.getColumn(name).getData().equals(TEST_BRIDGE_NAME)) { continue; } if (filter) { assertEquals(builder.getColumns().size(), aNew.getColumns().size()); } else { // As per RFC7047, Section 4.1.5 : If "columns" is omitted, all columns in the table, except for "_uuid", are monitored. assertEquals(bridge.getColumns().size() - 1, aNew.getColumns().size()); } for (Column<GenericTableSchema, ?> column: aNew.getColumns()) { if (column.getSchema().equals(flood_vlans)) { // Test for the 5 flood_vlans inserted in Bridge br-test in createBridgeTransaction Set<Integer> data = column.getData(flood_vlans); assertNotNull(data); assertTrue(!data.isEmpty()); assertEquals(5, data.size()); } else if (column.getSchema().equals(externalIds)) { // Test for the {"key", "value"} external_ids inserted in Bridge br-test in createBridgeTransaction Map<String, String> data = column.getData(externalIds); assertNotNull(data); assertNotNull(data.get("key")); assertEquals("value", data.get("key")); // Test for {"key2", "value2"} external_ids mutation-inserted in Bridge br-test in createBridgeTransaction assertNotNull(data.get("key2")); assertEquals("value2", data.get("key2")); } } return; } fail("Bridge being monitored :"+ TEST_BRIDGE_NAME +" Not found"); } /* * TODO : selectOpenVSwitchTableUuid method isn't working as expected due to the Jackson * parsing challenges on the Row object returned by the Select operation. */ private UUID selectOpenVSwitchTableUuid() throws ExecutionException, InterruptedException { assertNotNull(dbSchema); GenericTableSchema ovsTable = dbSchema.table("Open_vSwitch", GenericTableSchema.class); ColumnSchema<GenericTableSchema, UUID> _uuid = ovsTable.column("_uuid", UUID.class); List<OperationResult> results = ovs.transactBuilder(dbSchema) .add(op.select(ovsTable) .column(_uuid)) .execute() .get(); assertTrue(!results.isEmpty()); OperationResult result = results.get(0); List<Row<GenericTableSchema>> rows = result.getRows(); Row<GenericTableSchema> ovsTableRow = rows.get(0); return ovsTableRow.getColumn(_uuid).getData(); } private void createBridgeTransaction() throws IOException, InterruptedException, ExecutionException { assertNotNull(dbSchema); TableSchema<GenericTableSchema> bridge = dbSchema.table("Bridge", GenericTableSchema.class); GenericTableSchema ovsTable = dbSchema.table("Open_vSwitch", GenericTableSchema.class); ColumnSchema<GenericTableSchema, String> name = bridge.column("name", String.class); ColumnSchema<GenericTableSchema, String> fail_mode = bridge.column("fail_mode", String.class); ColumnSchema<GenericTableSchema, Set<Integer>> flood_vlans = bridge.multiValuedColumn("flood_vlans", Integer.class); ColumnSchema<GenericTableSchema, Map<String, String>> externalIds = bridge.multiValuedColumn("external_ids", String.class, String.class); ColumnSchema<GenericTableSchema, Set<UUID>> bridges = ovsTable.multiValuedColumn("bridges", UUID.class); ColumnSchema<GenericTableSchema, UUID> _uuid = ovsTable.column("_uuid", UUID.class); String namedUuid = "br_test"; int insertOperationIndex = 0; UUID parentTable = selectOpenVSwitchTableUuid(); TransactionBuilder transactionBuilder = ovs.transactBuilder(dbSchema) /* * Make sure that the position of insert operation matches the insertOperationIndex. * This will be used later when the Results are processed. */ .add(op.insert(bridge) .withId(namedUuid) .value(name, TEST_BRIDGE_NAME) .value(flood_vlans, Sets.newHashSet(100, 101, 4001)) .value(externalIds, ImmutableMap.of("key","value"))) .add(op.comment("Inserting Bridge br-int")) .add(op.update(bridge) .set(fail_mode, "secure") .where(name.opEqual(TEST_BRIDGE_NAME)) .build()) .add(op.select(bridge) .column(name) .column(_uuid) .where(name.opEqual(TEST_BRIDGE_NAME)) .build()) .add(op.mutate(bridge) .addMutation(flood_vlans, Mutator.INSERT, Sets.newHashSet(200,400)) .where(name.opEqual(TEST_BRIDGE_NAME)) .build()) .add(op.mutate(bridge) .addMutation(externalIds, Mutator.INSERT, ImmutableMap.of("key2","value2")) .where(name.opEqual(TEST_BRIDGE_NAME)) .build()) .add(op.mutate(ovsTable) .addMutation(bridges, Mutator.INSERT, Collections.singleton(new UUID(namedUuid))) .where(_uuid.opEqual(parentTable)) .build()) .add(op.commit(true)); ListenableFuture<List<OperationResult>> results = transactionBuilder.execute(); List<OperationResult> operationResults = results.get(); assertFalse(operationResults.isEmpty()); // Check if Results matches the number of operations in transaction assertEquals(transactionBuilder.getOperations().size(), operationResults.size()); LOG.info("Insert & Update operation results = {}", operationResults); for (OperationResult result : operationResults) { assertNull(result.getError()); } testBridgeUuid = operationResults.get(insertOperationIndex).getUuid(); } private void assertTransaction() throws InterruptedException, ExecutionException { assertNotNull(dbSchema); TableSchema<GenericTableSchema> bridge = dbSchema.table("Bridge", GenericTableSchema.class); ColumnSchema<GenericTableSchema, String> name = bridge.column("name", String.class); /* * Adding a separate Assert operation in a transaction. Lets not mix this with other * valid transactions as above. */ ListenableFuture<List<OperationResult>> results = ovs.transactBuilder(dbSchema) .add(op.delete(bridge) .where(name.opEqual(TEST_BRIDGE_NAME)) .build()) .add(op.assertion("Assert12345")) // Failing intentionally .execute(); List<OperationResult> operationResults = results.get(); assertFalse(operationResults.isEmpty()); /* Testing for an Assertion Error */ assertFalse(operationResults.get(1).getError() == null); LOG.info("Assert operation results = {}", operationResults); } private void abortTransaction() throws InterruptedException, ExecutionException { assertNotNull(dbSchema); TableSchema<GenericTableSchema> bridge = dbSchema.table("Bridge", GenericTableSchema.class); ColumnSchema<GenericTableSchema, String> name = bridge.column("name", String.class); /* * Adding a separate Abort operation in a transaction. Lets not mix this with other * valid transactions as above. */ ListenableFuture<List<OperationResult>> results = ovs.transactBuilder(dbSchema) .add(op.delete(bridge) .where(name.opEqual(TEST_BRIDGE_NAME)) .build()) .add(op.abort()) .execute(); List<OperationResult> operationResults = results.get(); assertFalse(operationResults.isEmpty()); /* Testing for Abort Error */ assertFalse(operationResults.get(1).getError() == null); LOG.info("Abort operation results = {}", operationResults); } public void testGetDBs() throws ExecutionException, InterruptedException { ListenableFuture<List<String>> databases = ovs.getDatabases(); List<String> dbNames = databases.get(); assertNotNull(dbNames); boolean hasOpenVswitchSchema = false; for(String dbName : dbNames) { if (dbName.equals(LibraryIntegrationTestUtils.OPEN_VSWITCH)) { hasOpenVswitchSchema = true; break; } } assertTrue(LibraryIntegrationTestUtils.OPEN_VSWITCH + " schema is not supported by the switch", hasOpenVswitchSchema); } @Before public void setup() throws Exception { schema = LibraryIntegrationTestUtils.OPEN_VSWITCH; super.setup2(); if (ovs != null) { return; } ovs = LibraryIntegrationTestUtils.getTestConnection(this); assertNotNull("Failed to get connection to ovsdb node", ovs); LOG.info("Connection Info: {}", ovs.getConnectionInfo().toString()); testGetDBs(); dbSchema = ovs.getSchema(LibraryIntegrationTestUtils.OPEN_VSWITCH).get(); } @After public void tearDown() throws InterruptedException, ExecutionException { if (dbSchema == null) { return; } TableSchema<GenericTableSchema> bridge = dbSchema.table("Bridge", GenericTableSchema.class); ColumnSchema<GenericTableSchema, String> name = bridge.column("name", String.class); GenericTableSchema ovsTable = dbSchema.table("Open_vSwitch", GenericTableSchema.class); ColumnSchema<GenericTableSchema, Set<UUID>> bridges = ovsTable.multiValuedColumn("bridges", UUID.class); ColumnSchema<GenericTableSchema, UUID> _uuid = ovsTable.column("_uuid", UUID.class); UUID parentTable = selectOpenVSwitchTableUuid(); ListenableFuture<List<OperationResult>> results = ovs.transactBuilder(dbSchema) .add(op.delete(bridge) .where(name.opEqual(TEST_BRIDGE_NAME)) .build()) .add(op.mutate(ovsTable) .addMutation(bridges, Mutator.DELETE, Collections.singleton(testBridgeUuid)) .where(_uuid.opEqual(parentTable)) .build()) .add(op.commit(true)) .execute(); List<OperationResult> operationResults = results.get(); LOG.info("Delete operation results = {}", operationResults); ovs.disconnect(); } }