From 612475fe3aaea22f0dc2615ff50d6ac16cbdde9e Mon Sep 17 00:00:00 2001 From: Madhu Venugopal Date: Tue, 26 Aug 2014 18:20:46 -0700 Subject: [PATCH] Schema independent plugin insert operation for a traditional single Row & more advanced insertTree for multi level Row insertion in a single operation. This commit brings in the plugin support which will be used by northboundv3 APIs & more advanced applications that make use of the plugin. Change-Id: If57d82da9234cc1d5a40ffd9b789ec3a81092383 Signed-off-by: Madhu Venugopal --- .../ovsdb/lib/notation/ReferencedRow.java | 30 ++ .../ovsdb/lib/schema/BaseType.java | 9 + .../plugin/impl/ConfigurationServiceImpl.java | 300 ++++++++++++++++-- 3 files changed, 315 insertions(+), 24 deletions(-) create mode 100644 library/src/main/java/org/opendaylight/ovsdb/lib/notation/ReferencedRow.java diff --git a/library/src/main/java/org/opendaylight/ovsdb/lib/notation/ReferencedRow.java b/library/src/main/java/org/opendaylight/ovsdb/lib/notation/ReferencedRow.java new file mode 100644 index 000000000..f8d09d591 --- /dev/null +++ b/library/src/main/java/org/opendaylight/ovsdb/lib/notation/ReferencedRow.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 Red Hat, Inc. and others + * + * 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 + * + * Authors : Madhu Venugopal + */ + +package org.opendaylight.ovsdb.lib.notation; + +import com.fasterxml.jackson.databind.JsonNode; + +public class ReferencedRow { + String refTable; + JsonNode jsonNode; + public ReferencedRow(String refTable, JsonNode jsonNode) { + this.refTable = refTable; + this.jsonNode = jsonNode; + } + + public JsonNode getJsonNode() { + return jsonNode; + } + + public String getRefTable() { + return refTable; + } +} diff --git a/library/src/main/java/org/opendaylight/ovsdb/lib/schema/BaseType.java b/library/src/main/java/org/opendaylight/ovsdb/lib/schema/BaseType.java index be9149f3a..070de8324 100644 --- a/library/src/main/java/org/opendaylight/ovsdb/lib/schema/BaseType.java +++ b/library/src/main/java/org/opendaylight/ovsdb/lib/schema/BaseType.java @@ -12,6 +12,7 @@ package org.opendaylight.ovsdb.lib.schema; import java.util.Set; import org.opendaylight.ovsdb.lib.error.TyperException; +import org.opendaylight.ovsdb.lib.notation.ReferencedRow; import org.opendaylight.ovsdb.lib.notation.UUID; import com.fasterxml.jackson.databind.JsonNode; @@ -478,6 +479,14 @@ public abstract class BaseType> { return new UUID(value.get(1).asText()); } } + } else { + /* + * UUIDBaseType used by RefTable from SouthBound will always be an Array of ["uuid", ]. + * But there are some cases from northbound where the RefTable type can be expanded to a Row + * with contents. In those scenarios, just retain the content and return a ReferencedRow for + * the upper layer functions to process it. + */ + return new ReferencedRow(refTable, value); } return null; } diff --git a/plugin/src/main/java/org/opendaylight/ovsdb/plugin/impl/ConfigurationServiceImpl.java b/plugin/src/main/java/org/opendaylight/ovsdb/plugin/impl/ConfigurationServiceImpl.java index 56aea7e9f..90e02948f 100644 --- a/plugin/src/main/java/org/opendaylight/ovsdb/plugin/impl/ConfigurationServiceImpl.java +++ b/plugin/src/main/java/org/opendaylight/ovsdb/plugin/impl/ConfigurationServiceImpl.java @@ -13,7 +13,9 @@ import static org.opendaylight.ovsdb.lib.operations.Operations.op; import java.net.InetAddress; import java.net.UnknownHostException; +import java.util.AbstractMap; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -37,6 +39,7 @@ import org.opendaylight.ovsdb.lib.error.SchemaVersionMismatchException; import org.opendaylight.ovsdb.lib.notation.Column; import org.opendaylight.ovsdb.lib.notation.Mutator; import org.opendaylight.ovsdb.lib.notation.OvsdbSet; +import org.opendaylight.ovsdb.lib.notation.ReferencedRow; import org.opendaylight.ovsdb.lib.notation.Row; import org.opendaylight.ovsdb.lib.notation.UUID; import org.opendaylight.ovsdb.lib.operations.Insert; @@ -67,7 +70,9 @@ import org.osgi.framework.FrameworkUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; import com.google.common.util.concurrent.ListenableFuture; public class ConfigurationServiceImpl implements IPluginInBridgeDomainConfigService, @@ -183,7 +188,7 @@ public class ConfigurationServiceImpl implements IPluginInBridgeDomainConfigServ List> columns = new ArrayList>(); columns.add(nameColumn); Row intfRow = new Row(tableSchema, columns); - this.processInsertTransaction(client, databaseName, "Interface", null, null, null, namedUuid, intfRow, transactionBuilder); + this.processTypedInsertTransaction(client, databaseName, "Interface", null, null, null, namedUuid, intfRow, transactionBuilder); } } @@ -192,25 +197,11 @@ public class ConfigurationServiceImpl implements IPluginInBridgeDomainConfigServ * the parent table for the newly inserted Child. * Due to some additional special case(s), the Transaction is further amended by handleSpecialInsertCase */ - private void processInsertTransaction(OvsdbClient client, String databaseName, String childTable, + private void processTypedInsertTransaction(OvsdbClient client, String databaseName, String childTable, String parentTable, String parentUuid, String parentColumn, String namedUuid, Row row, TransactionBuilder transactionBuilder) { - DatabaseSchema dbSchema = client.getDatabaseSchema(databaseName); - TableSchema childTableSchema = dbSchema.table(childTable, GenericTableSchema.class); - transactionBuilder.add(op.insert(childTableSchema, row) - .withId(namedUuid)); - - if (parentColumn != null) { - TableSchema parentTableSchema = dbSchema.table(parentTable, GenericTableSchema.class); - ColumnSchema parentColumnSchema = parentTableSchema.column(parentColumn, UUID.class); - ColumnSchema _uuid = parentTableSchema.column("_uuid", UUID.class); - - transactionBuilder - .add(op.mutate(parentTableSchema) - .addMutation(parentColumnSchema, Mutator.INSERT, new UUID(namedUuid)) - .where(_uuid.opEqual(new UUID(parentUuid))) - .build()); - } + this.processInsertTransaction(client, databaseName, childTable, parentTable, new UUID(parentUuid), parentColumn, + namedUuid, row, transactionBuilder); /* * There are a few Open_vSwitch schema specific special case handling to be done for * the older API (such as by inserting a mandatory Interface row automatically upon inserting @@ -245,6 +236,7 @@ public class ConfigurationServiceImpl implements IPluginInBridgeDomainConfigServ * Row being inserted in one Table and other Rows that needs mutate in other Tables. */ @Override + @Deprecated public StatusWithUuid insertRow(Node node, String tableName, String parentUuid, Row row) { String[] parentColumn = OvsVswitchdSchemaConstants.getParentColumnToMutate(tableName); if (parentColumn == null) { @@ -264,7 +256,7 @@ public class ConfigurationServiceImpl implements IPluginInBridgeDomainConfigServ TransactionBuilder transactionBuilder = client.transactBuilder(dbSchema); String namedUuid = "Transaction_"+ tableName; - this.processInsertTransaction(client, OvsVswitchdSchemaConstants.DATABASE_NAME, tableName, + this.processTypedInsertTransaction(client, OvsVswitchdSchemaConstants.DATABASE_NAME, tableName, parentColumn[0], parentUuid, parentColumn[1], namedUuid, row, transactionBuilder); @@ -290,6 +282,7 @@ public class ConfigurationServiceImpl implements IPluginInBridgeDomainConfigServ } @Override + @Deprecated public Status updateRow (Node node, String tableName, String parentUUID, String rowUUID, Row row) { String databaseName = OvsVswitchdSchemaConstants.DATABASE_NAME; Connection connection = connectionService.getConnection(node); @@ -345,6 +338,7 @@ public class ConfigurationServiceImpl implements IPluginInBridgeDomainConfigServ } @Override + @Deprecated public Status deleteRow(Node node, String tableName, String uuid) { String databaseName = OvsVswitchdSchemaConstants.DATABASE_NAME; Connection connection = connectionService.getConnection(node); @@ -384,12 +378,14 @@ public class ConfigurationServiceImpl implements IPluginInBridgeDomainConfigServ } @Override + @Deprecated public ConcurrentMap getRows(Node node, String tableName) { ConcurrentMap ovsTable = ovsdbInventoryService.getTableCache(node, OvsVswitchdSchemaConstants.DATABASE_NAME, tableName); return ovsTable; } @Override + @Deprecated public Row getRow(Node node, String tableName, String uuid) { Map ovsTable = ovsdbInventoryService.getTableCache(node, OvsVswitchdSchemaConstants.DATABASE_NAME, tableName); if (ovsTable == null) return null; @@ -397,6 +393,7 @@ public class ConfigurationServiceImpl implements IPluginInBridgeDomainConfigServ } @Override + @Deprecated public List getTables(Node node) { ConcurrentMap> cache = ovsdbInventoryService.getCache(node, OvsVswitchdSchemaConstants.DATABASE_NAME); if (cache == null) return null; @@ -1175,30 +1172,285 @@ public class ConfigurationServiceImpl implements IPluginInBridgeDomainConfigServ return null; } + + // SCHEMA-INDEPENDENT Configuration Service APIs + + /* + * A common Insert Transaction convenience method that populates the TransactionBuilder with insert operation + * for a Child Row and also mutates the parent row with the UUID of the inserted Child. + */ + private void processInsertTransaction(OvsdbClient client, String databaseName, String childTable, + String parentTable, UUID parentUuid, String parentColumn, String namedUuid, + Row row, + TransactionBuilder transactionBuilder) { + // Insert the row as the first transaction entry + DatabaseSchema dbSchema = client.getDatabaseSchema(databaseName); + TableSchema childTableSchema = dbSchema.table(childTable, GenericTableSchema.class); + transactionBuilder.add(op.insert(childTableSchema, row) + .withId(namedUuid)); + + // Followed by the Mutation + if (parentColumn != null) { + TableSchema parentTableSchema = dbSchema.table(parentTable, GenericTableSchema.class); + ColumnSchema parentColumnSchema = parentTableSchema.column(parentColumn, UUID.class); + ColumnSchema _uuid = parentTableSchema.column("_uuid", UUID.class); + + transactionBuilder + .add(op.mutate(parentTableSchema) + .addMutation(parentColumnSchema, Mutator.INSERT, new UUID(namedUuid)) + .where(_uuid.opEqual(parentUuid)) + .build()); + } + } + + /** + * insert a Row in a Table of a specified Database Schema. + * + * This method can insert just a single Row specified in the row parameter. + * But {@link #insertTree(Node, String, String, UUID, Row) insertTree} + * can insert a hierarchy of rows with parent-child relationship. + * + * @param node OVSDB Node + * @param databaseName Database Name that represents the Schema supported by the node. + * @param tableName Table on which the row is inserted + * @param parentTable Name of the Parent Table to which this operation will result in attaching/mutating. + * @param parentUuid UUID of a Row in parent table to which this operation will result in attaching/mutating. + * @param parentColumn Name of the Column in the Parent Table to be mutated with the UUID that results from the insert operation. + * @param row Row of table Content to be inserted + * @throws OvsdbPluginException Any failure during the insert transaction will result in a specific exception. + * @return UUID of the inserted Row + */ @Override public UUID insertRow(Node node, String databaseName, String tableName, String parentTable, UUID parentUuid, String parentColumn, Row row) throws OvsdbPluginException { - throw new OvsdbPluginException("Not implemented Yet"); + Connection connection = connectionService.getConnection(node); + OvsdbClient client = connection.getClient(); + DatabaseSchema dbSchema = client.getDatabaseSchema(databaseName); + TableSchema tableSchema = dbSchema.table(tableName, GenericTableSchema.class); + + Row processedRow = this.insertTree(node, databaseName, tableName, parentTable, parentUuid, parentColumn, row); + + ColumnSchema _uuid = tableSchema.column("_uuid", UUID.class); + Column uuid = processedRow.getColumn(_uuid); + return uuid.getData(); } + /** + * insert a Row in a Table of a specified Database Schema. This is a convenience method on top of + * {@link insertRow(Node, String, String, String, UUID, String, Row) insertRow} + * which assumes that OVSDB schema implementation that corresponds to the databaseName will provide + * the necessary service to populate the Parent Table Name and Parent Column Name. + * + * This method can insert just a single Row specified in the row parameter. + * But {@link #insertTree(Node, String, String, UUID, Row) insertTree} + * can insert a hierarchy of rows with parent-child relationship. + * + * @param node OVSDB Node + * @param databaseName Database Name that represents the Schema supported by the node. + * @param tableName Table on which the row is inserted + * @param parentUuid UUID of the parent table to which this operation will result in attaching/mutating. + * @param row Row of table Content to be inserted + * @throws OvsdbPluginException Any failure during the insert transaction will result in a specific exception. + * @return UUID of the inserted Row + */ @Override public UUID insertRow(Node node, String databaseName, String tableName, UUID parentRowUuid, Row row) throws OvsdbPluginException { - throw new OvsdbPluginException("Not implemented Yet"); + return this.insertRow(node, databaseName, tableName, null, parentRowUuid, null, row); } + /** + * inserts a Tree of Rows in multiple Tables that has parent-child relationships referenced through the OVSDB schema's refTable construct + * + * @param node OVSDB Node + * @param databaseName Database Name that represents the Schema supported by the node. + * @param tableName Table on which the row is inserted + * @param parentTable Name of the Parent Table to which this operation will result in attaching/mutating. + * @param parentUuid UUID of a Row in parent table to which this operation will result in attaching/mutating. + * @param parentColumn Name of the Column in the Parent Table to be mutated with the UUID that results from the insert operation. + * @param row Row Tree with parent-child relationships via column of type refTable. + * @throws OvsdbPluginException Any failure during the insert transaction will result in a specific exception. + * @return Returns the row tree with the UUID of every inserted Row populated in the _uuid column of every row in the tree + */ @Override - public Row insertTree(Node node, String databaseName, String tableName, String parentTable, UUID parentRowUuid, + public Row insertTree(Node node, String databaseName, String tableName, String parentTable, UUID parentUuid, String parentColumn, Row row) throws OvsdbPluginException { - throw new OvsdbPluginException("Not implemented Yet"); + Connection connection = connectionService.getConnection(node); + OvsdbClient client = connection.getClient(); + + if (databaseName == null || tableName == null) { + throw new OvsdbPluginException("databaseName, tableName and parentUuid are Mandatory Parameters"); + } + logger.debug("insertTree Connection : {} Table : {} ParentTable : {} Parent Column: {} Parent UUID : {} Row : {}", + client.getConnectionInfo(), tableName, parentTable, parentColumn, parentUuid, row); + + Map>> referencedRows = Maps.newConcurrentMap(); + extractReferencedRows(node, databaseName, row, referencedRows, 0); + DatabaseSchema dbSchema = client.getDatabaseSchema(OvsVswitchdSchemaConstants.DATABASE_NAME); + TransactionBuilder transactionBuilder = client.transactBuilder(dbSchema); + + String namedUuid = "Transaction_"+ tableName; + this.processInsertTransaction(client, databaseName, tableName, parentTable, parentUuid, + parentColumn, namedUuid, row, transactionBuilder); + + int referencedRowsInsertIndex = transactionBuilder.getOperations().size(); + // Insert Referenced Rows + if (referencedRows != null) { + for (UUID refUuid : referencedRows.keySet()) { + Map.Entry> referencedRow = referencedRows.get(refUuid); + TableSchema refTableSchema = dbSchema.table(referencedRow.getKey(), GenericTableSchema.class); + transactionBuilder.add(op.insert(refTableSchema, referencedRow.getValue()) + .withId(refUuid.toString())); + } + } + + ListenableFuture> results = transactionBuilder.execute(); + List operationResults; + try { + operationResults = results.get(); + if (operationResults.isEmpty() || (transactionBuilder.getOperations().size() != operationResults.size())) { + throw new OvsdbPluginException("Insert Operation Failed"); + } + for (OperationResult result : operationResults) { + if (result.getError() != null) { + throw new OvsdbPluginException("Insert Operation Failed with Error : "+result.getError().toString()); + } + } + return getNormalizedRow(dbSchema, tableName, row, referencedRows, operationResults, referencedRowsInsertIndex); + } catch (InterruptedException | ExecutionException e) { + throw new OvsdbPluginException("Exception : "+e.getLocalizedMessage()); + } } + /** + * inserts a Tree of Rows in multiple Tables that has parent-child relationships referenced through the OVSDB schema's refTable construct. + * This is a convenience method on top of {@link #insertTree(Node, String, String, String, UUID, String, Row) insertTree} + * + * @param node OVSDB Node + * @param databaseName Database Name that represents the Schema supported by the node. + * @param tableName Table on which the row is inserted + * @param parentUuid UUID of a Row in parent table to which this operation will result in attaching/mutating. + * @param row Row Tree with parent-child relationships via column of type refTable. + * @throws OvsdbPluginException Any failure during the insert transaction will result in a specific exception. + * @return Returns the row tree with the UUID of every inserted Row populated in the _uuid column of every row in the tree + */ @Override public Row insertTree(Node node, String databaseName, String tableName, UUID parentRowUuid, Row row) throws OvsdbPluginException { - throw new OvsdbPluginException("Not implemented Yet"); + return this.insertTree(node, databaseName, tableName, null, parentRowUuid, null, row); + } + + /** + * Convenience method that helps insertTree to extract Rows that are referenced directly from within a primary row + * to be inserted. These referenced rows are *NOT* defined in the OVSDB specification. But, we felt that from a northbound + * application standpoint, having such an option is useful and our implementation supports it for applications to make use of. + * In short, whichever ColumnSchema is based on an UUID (refered by RefTable in schema), applications can directly insert an + * entire row and this method will help navigate it through and identify such cases. + * After identifying these Referenced Rows, it will modify the primary row with Named UUIDs and fill out the referencedRows + * Map structure so that insertTree can insert all the Rows defined in this Tree of rows in a single transaction with automatic + * Mutation on the parent rows. + * + * @param node OVSDB Node + * @param dbName Database Name that represents the Schema supported by the node. + * @param row Row Tree with parent-child relationships via column of type refTable. + * @param referencedRows Map of Named-UUID to the actual referenced row (with RefTable) + * @param namedUuidSuffix Named UUID must be unique for every new Row insert within a given transaction. + * This index will help to retain the uniqueness. + */ + private void extractReferencedRows(Node node, String dbName, Row row, + Map>> referencedRows, + int namedUuidSuffix) { + OvsdbClient client = connectionService.getConnection(node).getClient(); + Collection> columns = row.getColumns(); + for (Column column : columns) { + if (column.getData() != null) { + if (column.getData() instanceof ReferencedRow) { + ReferencedRow refRowObject = (ReferencedRow)column.getData(); + UUID refUuid = new UUID("NamedUuid"+namedUuidSuffix++); + column.setData(refUuid); + try { + DatabaseSchema dbSchema = client.getSchema(dbName).get(); + GenericTableSchema schema = dbSchema.table(refRowObject.getRefTable(), GenericTableSchema.class); + Row refRow = schema.createRow((ObjectNode)refRowObject.getJsonNode()); + referencedRows.put(refUuid, new AbstractMap.SimpleEntry>(refRowObject.getRefTable(), refRow)); + extractReferencedRows(node, dbName, refRow, referencedRows, namedUuidSuffix); + } catch (InterruptedException | ExecutionException e) { + logger.error("Exception while extracting multi-level Row references " + e.getLocalizedMessage()); + } + } else if (column.getData() instanceof OvsdbSet) { + OvsdbSet setObject = (OvsdbSet)column.getData(); + OvsdbSet modifiedSet = new OvsdbSet(); + for (Object obj : setObject) { + if (obj instanceof ReferencedRow) { + ReferencedRow refRowObject = (ReferencedRow)obj; + UUID refUuid = new UUID("NamedUuid"+namedUuidSuffix++); + modifiedSet.add(refUuid); + try { + DatabaseSchema dbSchema = client.getSchema(dbName).get(); + GenericTableSchema schema = dbSchema.table(refRowObject.getRefTable(), GenericTableSchema.class); + Row refRow = schema.createRow((ObjectNode)refRowObject.getJsonNode()); + referencedRows.put(refUuid, new AbstractMap.SimpleEntry>(refRowObject.getRefTable(), refRow)); + extractReferencedRows(node, dbName, refRow, referencedRows, namedUuidSuffix); + } catch (InterruptedException | ExecutionException e) { + logger.error("Exception while extracting multi-level Row references " + e.getLocalizedMessage()); + } + } else { + modifiedSet.add(obj); + } + } + column.setData(modifiedSet); + } + } + } + } + + /** + * getNormalizedRow normalizes the Row from a namedUuid Space as defined in extractReferencedRows to the actual Uuid as created + * by the Ovsdb-server. In order to perform this normalization, it processes the operation results for a corresponding Transaction + * where the referenced rows are inserted along with the Primary row. It changes the named-Uuid to the actual Uuid before returning + * the Row to the application. + * + * @param dbSchema Database Schema supported by the node. + * @param row Row Tree with parent-child relationships via column of type refTable. + * @param tableName Table on which the row is inserted + * @param referencedRows Map of Named-UUID to the actual referenced row (with RefTable) + * @param operationResults Operation Results returned by ovsdb-server for the insertTree transaction + * @param referencedRowsInsertIndex Starting index in OperationResults from which the ReferencedRow insert results begin. + * @return + */ + private Row getNormalizedRow(DatabaseSchema dbSchema, String tableName, Row row, + Map>> referencedRows, + List operationResults, int referencedRowsInsertIndex) { + UUID primaryRowUuid = operationResults.get(0).getUuid(); + TableSchema primaryRowTableSchema = dbSchema.table(tableName, GenericTableSchema.class); + ColumnSchema _uuid = primaryRowTableSchema.column("_uuid", UUID.class); + if (_uuid != null) { + Column _uuidColumn = new Column(_uuid, primaryRowUuid); + row.addColumn("_uuid", _uuidColumn); + } + + if (referencedRows != null) { + Collection> columns = row.getColumns(); + if (referencedRows != null) { + for (int idx=0; idx < referencedRows.keySet().size(); idx++) { + UUID refUuid = (UUID) referencedRows.keySet().toArray()[idx]; + for (Column column : columns) { + if (column.getData() != null) { + if ((column.getData() instanceof UUID) && column.getData().equals(refUuid)) { + column.setData(operationResults.get(referencedRowsInsertIndex + idx).getUuid()); + } else if ((column.getData() instanceof OvsdbSet) && ((OvsdbSet)column.getData()).contains(refUuid)) { + OvsdbSet refSet = (OvsdbSet)column.getData(); + refSet.remove(refUuid); + refSet.add(operationResults.get(referencedRowsInsertIndex + idx).getUuid()); + } + } + } + } + } + } + return row; } @Override -- 2.36.6