Fix javadoc JDK8 compatibility
[ovsdb.git] / plugin / src / main / java / org / opendaylight / ovsdb / plugin / impl / ConfigurationServiceImpl.java
1 /*
2  * Copyright (C) 2013 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 : Madhu Venugopal, Brent Salisbury, Keith Burns
9  */
10 package org.opendaylight.ovsdb.plugin.impl;
11
12 import static org.opendaylight.ovsdb.lib.operations.Operations.op;
13
14 import java.net.InetAddress;
15 import java.net.UnknownHostException;
16 import java.util.AbstractMap;
17 import java.util.ArrayList;
18 import java.util.Collection;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Set;
22 import java.util.concurrent.ConcurrentMap;
23 import java.util.concurrent.ExecutionException;
24
25 import org.opendaylight.ovsdb.plugin.api.Status;
26 import org.opendaylight.ovsdb.plugin.api.StatusCode;
27 import org.opendaylight.ovsdb.lib.OvsdbClient;
28 import org.opendaylight.ovsdb.lib.error.SchemaVersionMismatchException;
29 import org.opendaylight.ovsdb.lib.notation.Column;
30 import org.opendaylight.ovsdb.lib.notation.Mutator;
31 import org.opendaylight.ovsdb.lib.notation.OvsdbSet;
32 import org.opendaylight.ovsdb.lib.notation.ReferencedRow;
33 import org.opendaylight.ovsdb.lib.notation.Row;
34 import org.opendaylight.ovsdb.lib.notation.UUID;
35 import org.opendaylight.ovsdb.lib.operations.Insert;
36 import org.opendaylight.ovsdb.lib.operations.Operation;
37 import org.opendaylight.ovsdb.lib.operations.OperationResult;
38 import org.opendaylight.ovsdb.lib.operations.TransactionBuilder;
39 import org.opendaylight.ovsdb.lib.schema.BaseType.UuidBaseType;
40 import org.opendaylight.ovsdb.lib.schema.ColumnSchema;
41 import org.opendaylight.ovsdb.lib.schema.DatabaseSchema;
42 import org.opendaylight.ovsdb.lib.schema.GenericTableSchema;
43 import org.opendaylight.ovsdb.lib.schema.TableSchema;
44 import org.opendaylight.ovsdb.lib.schema.typed.TypedBaseTable;
45 import org.opendaylight.ovsdb.plugin.api.Connection;
46 import org.opendaylight.ovsdb.plugin.api.OvsVswitchdSchemaConstants;
47 import org.opendaylight.ovsdb.plugin.api.OvsdbConfigurationService;
48 import org.opendaylight.ovsdb.plugin.api.OvsdbConnectionService;
49 import org.opendaylight.ovsdb.plugin.api.OvsdbInventoryService;
50 import org.opendaylight.ovsdb.plugin.api.StatusWithUuid;
51 import org.opendaylight.ovsdb.plugin.error.OvsdbPluginException;
52 import org.opendaylight.ovsdb.schema.openvswitch.Bridge;
53 import org.opendaylight.ovsdb.schema.openvswitch.Controller;
54 import org.opendaylight.ovsdb.schema.openvswitch.OpenVSwitch;
55 import org.opendaylight.ovsdb.schema.openvswitch.Port;
56 import org.opendaylight.ovsdb.utils.config.ConfigProperties;
57 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
58
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61
62 import com.fasterxml.jackson.databind.node.ObjectNode;
63 import com.google.common.collect.Maps;
64 import com.google.common.collect.Sets;
65 import com.google.common.util.concurrent.ListenableFuture;
66
67 public class ConfigurationServiceImpl implements OvsdbConfigurationService
68 {
69     private static final Logger logger = LoggerFactory
70             .getLogger(ConfigurationServiceImpl.class);
71
72     OvsdbConnectionService connectionService;
73     OvsdbInventoryService ovsdbInventoryService;
74     protected static final String OPENFLOW_13 = "1.3";
75
76     void init() {
77     }
78
79     /**
80      * Function called by the dependency manager when at least one dependency
81      * become unsatisfied or when the component is shutting down because for
82      * example bundle is being stopped.
83      *
84      */
85     void destroy() {
86     }
87
88     /**
89      * Function called by dependency manager after "init ()" is called and after
90      * the services provided by the class are registered in the service registry
91      *
92      */
93     void start() {
94     }
95
96     /**
97      * Function called by the dependency manager before the services exported by
98      * the component are unregistered, this will be followed by a "destroy ()"
99      * calls
100      *
101      */
102     void stop() {
103     }
104
105     public void setConnectionServiceInternal(OvsdbConnectionService connectionService) {
106         this.connectionService = connectionService;
107     }
108
109     public void unsetConnectionServiceInternal(OvsdbConnectionService connectionService) {
110         if (this.connectionService == connectionService) {
111             this.connectionService = null;
112         }
113     }
114
115     public void setOvsdbInventoryService(OvsdbInventoryService ovsdbInventoryService) {
116         this.ovsdbInventoryService = ovsdbInventoryService;
117     }
118
119     public void unsetInventoryServiceInternal(OvsdbInventoryService ovsdbInventoryService) {
120         if (this.ovsdbInventoryService == ovsdbInventoryService) {
121             this.ovsdbInventoryService = null;
122         }
123     }
124
125     private Connection getConnection (Node node) {
126         Connection connection = connectionService.getConnection(node);
127         if (connection == null || !connection.getClient().isActive()) {
128             return null;
129         }
130
131         return connection;
132     }
133     /*
134      * There are a few Open_vSwitch schema specific special case handling to be done for
135      * the older API (such as by inserting a mandatory Interface row automatically upon inserting
136      * a Port row.
137      */
138     private void handleSpecialInsertCase(OvsdbClient client, String databaseName,
139             String tableName, String uuid, Row<GenericTableSchema> row, TransactionBuilder transactionBuilder) {
140         Port port = client.getTypedRowWrapper(Port.class, null);
141         if (databaseName.equals(OvsVswitchdSchemaConstants.DATABASE_NAME) && tableName.equals(port.getSchema().getName())) {
142             port = client.getTypedRowWrapper(Port.class, row);
143             DatabaseSchema dbSchema = client.getDatabaseSchema(databaseName);
144             TableSchema<GenericTableSchema> tableSchema = dbSchema.table(tableName, GenericTableSchema.class);
145             ColumnSchema<GenericTableSchema, Set<UUID>> columnSchema = tableSchema.multiValuedColumn("interfaces", UUID.class);
146             String namedUuid = "Special_"+tableName;
147             List<Operation> priorOperations = transactionBuilder.getOperations();
148             Insert portOperation = (Insert)priorOperations.get(0);
149             portOperation.value(columnSchema, new UUID(namedUuid));
150
151             Column<GenericTableSchema, ?> nameColumn = port.getNameColumn();
152             List<Column<GenericTableSchema, ?>> columns = new ArrayList<Column<GenericTableSchema, ?>>();
153             columns.add(nameColumn);
154             Row<GenericTableSchema> intfRow = new Row<GenericTableSchema>(tableSchema, columns);
155             this.processTypedInsertTransaction(client, databaseName, "Interface", null, null, null, namedUuid, intfRow, transactionBuilder);
156         }
157     }
158
159     /*
160      * A common Transaction that takes in old API style Parent_uuid and inserts a mutation on
161      * the parent table for the newly inserted Child.
162      * Due to some additional special case(s), the Transaction is further amended by handleSpecialInsertCase
163      */
164     private void processTypedInsertTransaction(OvsdbClient client, String databaseName, String childTable,
165                                     String parentTable, String parentUuid, String parentColumn, String namedUuid,
166                                     Row<GenericTableSchema> row, TransactionBuilder transactionBuilder) {
167         this.processInsertTransaction(client, databaseName, childTable, parentTable, new UUID(parentUuid), parentColumn,
168                                       namedUuid, row, transactionBuilder);
169         /*
170          * There are a few Open_vSwitch schema specific special case handling to be done for
171          * the older API (such as by inserting a mandatory Interface row automatically upon inserting
172          * a Port row.
173          */
174         handleSpecialInsertCase(client, databaseName, childTable, namedUuid, row, transactionBuilder);
175     }
176
177     /*
178      * TODO : Move all the Special Cases out of ConfigurationService and into the Schema specific bundles.
179      * But that makes plugin more reliant on the Typed Bundles more than just API wrapper.
180      * Keeping these Special Handling locally till we introduce the full schema independent APIs in the
181      * plugin layer.
182      */
183     public String getSpecialCaseParentUUID(Node node, String databaseName, String childTableName) {
184         if (!databaseName.equals(OvsVswitchdSchemaConstants.DATABASE_NAME)) return null;
185         String[] parentColumn = OvsVswitchdSchemaConstants.getParentColumnToMutate(childTableName);
186         if (parentColumn != null && parentColumn[0].equals(OvsVswitchdSchemaConstants.DATABASE_NAME)) {
187             Connection connection = connectionService.getConnection(node);
188             OpenVSwitch openVSwitch = connection.getClient().getTypedRowWrapper(OpenVSwitch.class, null);
189             ConcurrentMap<String, Row> row = this.getRows(node, openVSwitch.getSchema().getName());
190             if (row == null || row.size() == 0) return null;
191             return (String)row.keySet().toArray()[0];
192         }
193         return null;
194     }
195
196     /*
197      * Though this is a New API that takes in Row object, this still is considered a
198      * Deprecated call because of the assumption with a Single Row insertion.
199      * An ideal insertRow must be able to take in multiple Rows, which includes the
200      * Row being inserted in one Table and other Rows that needs mutate in other Tables.
201      */
202     @Override
203     @Deprecated
204     public StatusWithUuid insertRow(Node node, String tableName, String parentUuid, Row<GenericTableSchema> row) {
205         String[] parentColumn = OvsVswitchdSchemaConstants.getParentColumnToMutate(tableName);
206         if (parentColumn == null) {
207             parentColumn = new String[]{null, null};
208         }
209
210         Connection connection = connectionService.getConnection(node);
211         OvsdbClient client = connection.getClient();
212
213         if (parentUuid == null) {
214             parentUuid = this.getSpecialCaseParentUUID(node, OvsVswitchdSchemaConstants.DATABASE_NAME, tableName);
215         }
216         logger.debug("insertRow Connection : {} Table : {} ParentTable : {} Parent Column: {} Parent UUID : {} Row : {}",
217                      client.getConnectionInfo(), tableName, parentColumn[0], parentColumn[1], parentUuid, row);
218
219         DatabaseSchema dbSchema = client.getDatabaseSchema(OvsVswitchdSchemaConstants.DATABASE_NAME);
220         TransactionBuilder transactionBuilder = client.transactBuilder(dbSchema);
221
222         String namedUuid = "Transaction_"+ tableName;
223         this.processTypedInsertTransaction(client, OvsVswitchdSchemaConstants.DATABASE_NAME, tableName,
224                                 parentColumn[0], parentUuid, parentColumn[1], namedUuid,
225                                 row, transactionBuilder);
226
227         ListenableFuture<List<OperationResult>> results = transactionBuilder.execute();
228         List<OperationResult> operationResults;
229         try {
230             operationResults = results.get();
231             if (operationResults.isEmpty() || (transactionBuilder.getOperations().size() != operationResults.size())) {
232                 return new StatusWithUuid(StatusCode.INTERNALERROR);
233             }
234             for (OperationResult result : operationResults) {
235                 if (result.getError() != null) {
236                     return new StatusWithUuid(StatusCode.BADREQUEST, result.getError());
237                 }
238             }
239             UUID uuid = operationResults.get(0).getUuid();
240             return new StatusWithUuid(StatusCode.SUCCESS, uuid);
241         } catch (InterruptedException | ExecutionException e) {
242             // TODO Auto-generated catch block
243             return new StatusWithUuid(StatusCode.INTERNALERROR, e.getLocalizedMessage());
244         }
245
246     }
247
248     @Override
249     @Deprecated
250     public Status updateRow (Node node, String tableName, String parentUUID, String rowUUID, Row row) {
251         String databaseName = OvsVswitchdSchemaConstants.DATABASE_NAME;
252         Row<GenericTableSchema> updatedRow = this.updateRow(node, databaseName, tableName, new UUID(rowUUID), row, true);
253         return new StatusWithUuid(StatusCode.SUCCESS);
254     }
255
256     private void processDeleteTransaction(OvsdbClient client, String databaseName, String childTable,
257                                     String parentTable, String parentColumn, String uuid, TransactionBuilder transactionBuilder) {
258         DatabaseSchema dbSchema = client.getDatabaseSchema(databaseName);
259         TableSchema<GenericTableSchema> childTableSchema = dbSchema.table(childTable, GenericTableSchema.class);
260
261         if (parentColumn != null) {
262             TableSchema<GenericTableSchema> parentTableSchema = dbSchema.table(parentTable, GenericTableSchema.class);
263             ColumnSchema<GenericTableSchema, UUID> parentColumnSchema = parentTableSchema.column(parentColumn, UUID.class);
264             transactionBuilder
265                 .add(op.mutate(parentTableSchema)
266                         .addMutation(parentColumnSchema, Mutator.DELETE, new UUID(uuid))
267                         .where(parentColumnSchema.opIncludes(new UUID(uuid)))
268                         .build());
269         }
270
271         ColumnSchema<GenericTableSchema, UUID> _uuid = childTableSchema.column("_uuid", UUID.class);
272         transactionBuilder.add(op.delete(childTableSchema)
273                 .where(_uuid.opEqual(new UUID(uuid)))
274                 .build());
275     }
276
277     @Override
278     @Deprecated
279     public Status deleteRow(Node node, String tableName, String uuid) {
280         String databaseName = OvsVswitchdSchemaConstants.DATABASE_NAME;
281         Connection connection = connectionService.getConnection(node);
282         OvsdbClient client = connection.getClient();
283
284         String[] parentColumn = OvsVswitchdSchemaConstants.getParentColumnToMutate(tableName);
285         if (parentColumn == null) {
286             parentColumn = new String[]{null, null};
287         }
288
289         logger.debug("deleteRow : Connection : {} databaseName : {} tableName : {} Uuid : {} ParentTable : {} ParentColumn : {}",
290                 client.getConnectionInfo(), databaseName, tableName, uuid, parentColumn[0], parentColumn[1]);
291
292         DatabaseSchema dbSchema = client.getDatabaseSchema(databaseName);
293         TransactionBuilder transactionBuilder = client.transactBuilder(dbSchema);
294         this.processDeleteTransaction(client, OvsVswitchdSchemaConstants.DATABASE_NAME, tableName,
295                                       parentColumn[0], parentColumn[1], uuid, transactionBuilder);
296
297         ListenableFuture<List<OperationResult>> results = transactionBuilder.execute();
298         List<OperationResult> operationResults;
299         try {
300             operationResults = results.get();
301             if (operationResults.isEmpty() || (transactionBuilder.getOperations().size() != operationResults.size())) {
302                 return new StatusWithUuid(StatusCode.INTERNALERROR);
303             }
304             for (OperationResult result : operationResults) {
305                 if (result.getError() != null) {
306                     return new StatusWithUuid(StatusCode.BADREQUEST, result.getError());
307                 }
308             }
309         } catch (InterruptedException | ExecutionException e) {
310             logger.error("Error in deleteRow() {} {}", node, tableName, e);
311         }
312
313         return new Status(StatusCode.SUCCESS);
314     }
315
316     @Override
317     @Deprecated
318     public ConcurrentMap<String, Row> getRows(Node node, String tableName) {
319         ConcurrentMap<String, Row> ovsTable = ovsdbInventoryService.getTableCache(node, OvsVswitchdSchemaConstants.DATABASE_NAME,  tableName);
320         return ovsTable;
321     }
322
323     @Override
324     @Deprecated
325     public Row getRow(Node node, String tableName, String uuid) {
326         Map<String, Row> ovsTable = ovsdbInventoryService.getTableCache(node, OvsVswitchdSchemaConstants.DATABASE_NAME,  tableName);
327         if (ovsTable == null) return null;
328         return ovsTable.get(uuid);
329     }
330
331     @Override
332     @Deprecated
333     public List<String> getTables(Node node) {
334         return this.getTables(node, OvsVswitchdSchemaConstants.DATABASE_NAME);
335     }
336
337     private InetAddress getControllerIPAddress(Connection connection) {
338         InetAddress controllerIP = null;
339
340         String addressString = ConfigProperties.getProperty(this.getClass(), "ovsdb.controller.address");
341
342         if (addressString != null) {
343             try {
344                 controllerIP = InetAddress.getByName(addressString);
345                 if (controllerIP != null) {
346                     return controllerIP;
347                 }
348             } catch (UnknownHostException e) {
349                 logger.error("Host {} is invalid", addressString);
350             }
351         }
352
353         addressString = ConfigProperties.getProperty(this.getClass(), "of.address");
354
355         if (addressString != null) {
356             try {
357                 controllerIP = InetAddress.getByName(addressString);
358                 if (controllerIP != null) {
359                     return controllerIP;
360                 }
361             } catch (UnknownHostException e) {
362                 logger.error("Host {} is invalid", addressString);
363             }
364         }
365
366         try {
367             controllerIP = connection.getClient().getConnectionInfo().getLocalAddress();
368             return controllerIP;
369         } catch (Exception e) {
370             logger.debug("Invalid connection provided to getControllerIPAddresses", e);
371         }
372         return controllerIP;
373     }
374
375     private short getControllerOFPort() {
376         Short defaultOpenFlowPort = 6633;
377         Short openFlowPort = defaultOpenFlowPort;
378         String portString = ConfigProperties.getProperty(this.getClass(), "of.listenPort");
379         if (portString != null) {
380             try {
381                 openFlowPort = Short.decode(portString).shortValue();
382             } catch (NumberFormatException e) {
383                 logger.warn("Invalid port:{}, use default({})", portString,
384                         openFlowPort);
385             }
386         }
387         return openFlowPort;
388     }
389
390     private UUID getCurrentControllerUuid(Node node, final String controllerTableName, final String target) {
391         ConcurrentMap<String, Row> rows = this.getRows(node, controllerTableName);
392
393         if (rows != null) {
394             for (Map.Entry<String, Row> entry : rows.entrySet()) {
395                 Controller currController = this.getTypedRow(node, Controller.class, entry.getValue());
396                 Column<GenericTableSchema, String> column = currController.getTargetColumn();
397                 String currTarget = column.getData();
398                 if (currTarget != null && currTarget.equalsIgnoreCase(target)) {
399                     return currController.getUuid();
400                 }
401             }
402         }
403         return null;
404     }
405
406     @Override
407     public Boolean setOFController(Node node, String bridgeUUID) throws InterruptedException, ExecutionException {
408         Connection connection = this.getConnection(node);
409         if (connection == null) {
410             return false;
411         }
412
413         Bridge bridge = connection.getClient().createTypedRowWrapper(Bridge.class);
414
415         Status updateOperationStatus = null;
416         try {
417             OvsdbSet<String> protocols = new OvsdbSet<String>();
418
419             String ofVersion = System.getProperty("ovsdb.of.version", OPENFLOW_13);
420             switch (ofVersion) {
421                 case OPENFLOW_13:
422                     //fall through
423                 default:
424                     protocols.add("OpenFlow13");
425                     break;
426             }
427             bridge.setProtocols(protocols);
428             updateOperationStatus = this.updateRow(node, bridge.getSchema().getName(),
429                                                    null, bridgeUUID, bridge.getRow());
430             logger.debug("Bridge {} updated to {} with Status {}", bridgeUUID,
431                          protocols.toArray()[0],updateOperationStatus);
432
433         } catch (SchemaVersionMismatchException e){
434             logger.debug(e.toString());
435         }
436
437         // If we fail to update the protocols
438         if (updateOperationStatus != null && !updateOperationStatus.isSuccess()) {
439             return updateOperationStatus.isSuccess();
440         }
441
442         Status status = null;
443         UUID currControllerUuid = null;
444         InetAddress ofControllerAddr = this.getControllerIPAddress(connection);
445         short ofControllerPort = getControllerOFPort();
446         String newControllerTarget = "tcp:"+ofControllerAddr.getHostAddress()+":"+ofControllerPort;
447         Controller newController = connection.getClient().createTypedRowWrapper(Controller.class);
448         newController.setTarget(newControllerTarget);
449         final String controllerTableName = newController.getSchema().getName();
450
451         currControllerUuid = getCurrentControllerUuid(node, controllerTableName, newControllerTarget);
452
453         if (currControllerUuid != null) {
454             bridge = connection.getClient().createTypedRowWrapper(Bridge.class);
455             bridge.setController(Sets.newHashSet(currControllerUuid));
456             status = this.updateRow(node, bridge.getSchema().getName(), null, bridgeUUID, bridge.getRow());
457         } else {
458             status = this.insertRow(node, controllerTableName, bridgeUUID, newController.getRow());
459         }
460
461         if (status != null) {
462             return status.isSuccess();
463         }
464
465         return false;
466     }
467
468
469     public Boolean setBridgeOFController(Node node, String bridgeIdentifier) {
470         if (connectionService == null) {
471             logger.error("Couldn't refer to the ConnectionService");
472             return false;
473         }
474
475         try{
476             Connection connection = connectionService.getConnection(node);
477             Bridge bridge = connection.getClient().getTypedRowWrapper(Bridge.class, null);
478
479             Map<String, Row> brTableCache = ovsdbInventoryService.getTableCache(node, OvsVswitchdSchemaConstants.DATABASE_NAME, bridge.getSchema().getName());
480             for (String uuid : brTableCache.keySet()) {
481                 bridge = connection.getClient().getTypedRowWrapper(Bridge.class, brTableCache.get(uuid));
482                 if (bridge.getName().contains(bridgeIdentifier)) {
483                     return setOFController(node, uuid);
484                 }
485             }
486         } catch(Exception e) {
487             logger.error("Error in setBridgeOFController()",e);
488         }
489         return false;
490     }
491
492     @Override
493     public <T extends TypedBaseTable<?>> String getTableName(Node node, Class<T> typedClass) {
494         Connection connection = connectionService.getConnection(node);
495         if (connection == null) return null;
496         OvsdbClient client = connection.getClient();
497         TypedBaseTable<?> typedTable = client.getTypedRowWrapper(typedClass, null);
498         if (typedTable == null) return null;
499         return typedTable.getSchema().getName();
500     }
501
502     @Override
503     public <T extends TypedBaseTable<?>> T getTypedRow(Node node, Class<T> typedClass, Row row) {
504         Connection connection = connectionService.getConnection(node);
505         if (connection == null) return null;
506         OvsdbClient client = connection.getClient();
507         return (T)client.getTypedRowWrapper(typedClass, row);
508     }
509
510     @Override
511     public <T extends TypedBaseTable<?>> T createTypedRow(Node node, Class<T> typedClass) {
512         Connection connection = connectionService.getConnection(node);
513         if (connection == null) return null;
514         OvsdbClient client = connection.getClient();
515         return client.createTypedRowWrapper(typedClass);
516     }
517
518     // SCHEMA-INDEPENDENT Configuration Service APIs
519
520     private String getTableNameForRowUuid(Node node, String databaseName, UUID rowUuid) {
521         ConcurrentMap<String, ConcurrentMap<String, Row>> cache  = ovsdbInventoryService.getCache(node, databaseName);
522         if (cache == null) return null;
523         for (String tableName : cache.keySet()) {
524             ConcurrentMap<String, Row> rows = cache.get(tableName);
525             if (rows.get(rowUuid.toString()) != null) {
526                 return tableName;
527             }
528         }
529         return null;
530     }
531
532     private String getReferencingColumn (TableSchema<?> parentTableSchema, String childTableName) throws OvsdbPluginException {
533         Map<String, ColumnSchema> columnSchemas = parentTableSchema.getColumnSchemas();
534         String refColumn = null;
535         for (String columnName : columnSchemas.keySet()) {
536             ColumnSchema columnSchema = columnSchemas.get(columnName);
537             if (columnSchema.getType().getBaseType().getClass().equals(UuidBaseType.class)) {
538                 UuidBaseType refType = (UuidBaseType)columnSchema.getType().getBaseType();
539                 if (refType.getRefTable() != null && refType.getRefTable().equalsIgnoreCase(childTableName)) {
540                     if (refColumn == null) {
541                         refColumn = columnName;
542                     } else {
543                         throw new OvsdbPluginException("Multiple Referencing Columns for "+ childTableName +" on "+ parentTableSchema.getName());
544                     }
545                 }
546             }
547         }
548         if (refColumn != null) {
549             return refColumn;
550         }
551         throw new OvsdbPluginException("No Referencing Column for "+childTableName+" on "+parentTableSchema.getName());
552     }
553     /*
554      * A common Insert Transaction convenience method that populates the TransactionBuilder with insert operation
555      * for a Child Row and also mutates the parent row with the UUID of the inserted Child.
556      */
557     private void processInsertTransaction(OvsdbClient client, String databaseName, String childTable,
558                                     String parentTable, UUID parentUuid, String parentColumn, String namedUuid,
559                                     Row<GenericTableSchema> row,
560                                     TransactionBuilder transactionBuilder) {
561         // Insert the row as the first transaction entry
562         DatabaseSchema dbSchema = client.getDatabaseSchema(databaseName);
563         TableSchema<GenericTableSchema> childTableSchema = dbSchema.table(childTable, GenericTableSchema.class);
564         transactionBuilder.add(op.insert(childTableSchema, row)
565                         .withId(namedUuid));
566
567         // Followed by the Mutation
568         if (parentColumn != null) {
569             TableSchema<GenericTableSchema> parentTableSchema = dbSchema.table(parentTable, GenericTableSchema.class);
570             ColumnSchema<GenericTableSchema, UUID> parentColumnSchema = parentTableSchema.column(parentColumn, UUID.class);
571             ColumnSchema<GenericTableSchema, UUID> _uuid = parentTableSchema.column("_uuid", UUID.class);
572
573             transactionBuilder
574                 .add(op.mutate(parentTableSchema)
575                         .addMutation(parentColumnSchema, Mutator.INSERT, new UUID(namedUuid))
576                         .where(_uuid.opEqual(parentUuid))
577                         .build());
578         }
579     }
580
581     /**
582      * insert a Row in a Table of a specified Database Schema.
583      *
584      * This method can insert just a single Row specified in the row parameter.
585      * But {@link #insertTree(Node, String, String, UUID, Row) insertTree}
586      * can insert a hierarchy of rows with parent-child relationship.
587      *
588      * @param node OVSDB Node
589      * @param databaseName Database Name that represents the Schema supported by the node.
590      * @param tableName Table on which the row is inserted
591      * @param parentTable Name of the Parent Table to which this operation will result in attaching/mutating.
592      * @param parentUuid UUID of a Row in parent table to which this operation will result in attaching/mutating.
593      * @param parentColumn Name of the Column in the Parent Table to be mutated with the UUID that results from the insert operation.
594      * @param row Row of table Content to be inserted
595      * @throws OvsdbPluginException Any failure during the insert transaction will result in a specific exception.
596      * @return UUID of the inserted Row
597      */
598     @Override
599     public UUID insertRow(Node node, String databaseName, String tableName, String parentTable, UUID parentUuid,
600                           String parentColumn, Row<GenericTableSchema> row) throws OvsdbPluginException {
601         Connection connection = connectionService.getConnection(node);
602         OvsdbClient client = connection.getClient();
603         DatabaseSchema dbSchema = client.getDatabaseSchema(databaseName);
604         TableSchema<GenericTableSchema> tableSchema = dbSchema.table(tableName, GenericTableSchema.class);
605
606         Row<GenericTableSchema> processedRow = this.insertTree(node, databaseName, tableName, parentTable, parentUuid, parentColumn, row);
607
608         ColumnSchema<GenericTableSchema, UUID> _uuid = tableSchema.column("_uuid", UUID.class);
609         Column<GenericTableSchema, UUID> uuid = processedRow.getColumn(_uuid);
610         return uuid.getData();
611     }
612
613     /**
614      * insert a Row in a Table of a specified Database Schema. This is a convenience method on top of
615      * {@link insertRow(Node, String, String, String, UUID, String, Row) insertRow}
616      * which assumes that OVSDB schema implementation that corresponds to the databaseName will provide
617      * the necessary service to populate the Parent Table Name and Parent Column Name.
618      *
619      * This method can insert just a single Row specified in the row parameter.
620      * But {@link #insertTree(Node, String, String, UUID, Row) insertTree}
621      * can insert a hierarchy of rows with parent-child relationship.
622      *
623      * @param node OVSDB Node
624      * @param databaseName Database Name that represents the Schema supported by the node.
625      * @param tableName Table on which the row is inserted
626      * @param parentRowUuid UUID of the parent table to which this operation will result in attaching/mutating.
627      * @param row Row of table Content to be inserted
628      * @throws OvsdbPluginException Any failure during the insert transaction will result in a specific exception.
629      * @return UUID of the inserted Row
630      */
631     @Override
632     public UUID insertRow(Node node, String databaseName, String tableName,
633             UUID parentRowUuid, Row<GenericTableSchema> row)
634             throws OvsdbPluginException {
635         return this.insertRow(node, databaseName, tableName, null, parentRowUuid, null, row);
636     }
637
638     /**
639      * inserts a Tree of Rows in multiple Tables that has parent-child relationships referenced through the OVSDB schema's refTable construct
640      *
641      * @param node OVSDB Node
642      * @param databaseName Database Name that represents the Schema supported by the node.
643      * @param tableName Table on which the row is inserted
644      * @param parentTable Name of the Parent Table to which this operation will result in attaching/mutating.
645      * @param parentUuid UUID of a Row in parent table to which this operation will result in attaching/mutating.
646      * @param parentColumn Name of the Column in the Parent Table to be mutated with the UUID that results from the insert operation.
647      * @param row Row Tree with parent-child relationships via column of type refTable.
648      * @throws OvsdbPluginException Any failure during the insert transaction will result in a specific exception.
649      * @return Returns the row tree with the UUID of every inserted Row populated in the _uuid column of every row in the tree
650      */
651     @Override
652     public Row<GenericTableSchema> insertTree(Node node, String databaseName, String tableName, String parentTable, UUID parentUuid,
653                                               String parentColumn, Row<GenericTableSchema> row) throws OvsdbPluginException {
654         Connection connection = connectionService.getConnection(node);
655         OvsdbClient client = connection.getClient();
656
657         if (databaseName == null || tableName == null) {
658             throw new OvsdbPluginException("databaseName, tableName and parentUuid are Mandatory Parameters");
659         }
660
661         if (parentTable == null && parentUuid != null) {
662             parentTable = this.getTableNameForRowUuid(node, databaseName, parentUuid);
663         }
664
665         if (parentColumn == null && parentTable != null) {
666             DatabaseSchema dbSchema = client.getDatabaseSchema(databaseName);
667             TableSchema<GenericTableSchema> parentTableSchema = dbSchema.table(parentTable, GenericTableSchema.class);
668             parentColumn = this.getReferencingColumn(parentTableSchema, tableName);
669         }
670
671         logger.debug("insertTree Connection : {} Table : {} ParentTable : {} Parent Column: {} Parent UUID : {} Row : {}",
672                      client.getConnectionInfo(), tableName, parentTable, parentColumn, parentUuid, row);
673
674         Map<UUID, Map.Entry<String, Row<GenericTableSchema>>> referencedRows = Maps.newConcurrentMap();
675         extractReferencedRows(node, databaseName, row, referencedRows, 0);
676         DatabaseSchema dbSchema = client.getDatabaseSchema(OvsVswitchdSchemaConstants.DATABASE_NAME);
677         TransactionBuilder transactionBuilder = client.transactBuilder(dbSchema);
678
679         String namedUuid = "Transaction_"+ tableName;
680         this.processInsertTransaction(client, databaseName, tableName, parentTable, parentUuid,
681                                       parentColumn, namedUuid, row, transactionBuilder);
682
683         int referencedRowsInsertIndex = transactionBuilder.getOperations().size();
684         // Insert Referenced Rows
685         if (referencedRows != null) {
686             for (UUID refUuid : referencedRows.keySet()) {
687                 Map.Entry<String, Row<GenericTableSchema>> referencedRow = referencedRows.get(refUuid);
688                 TableSchema<GenericTableSchema> refTableSchema = dbSchema.table(referencedRow.getKey(), GenericTableSchema.class);
689                 transactionBuilder.add(op.insert(refTableSchema, referencedRow.getValue())
690                                 .withId(refUuid.toString()));
691             }
692         }
693
694         ListenableFuture<List<OperationResult>> results = transactionBuilder.execute();
695         List<OperationResult> operationResults;
696         try {
697             operationResults = results.get();
698             if (operationResults.isEmpty() || (transactionBuilder.getOperations().size() != operationResults.size())) {
699                 throw new OvsdbPluginException("Insert Operation Failed");
700             }
701             for (OperationResult result : operationResults) {
702                 if (result.getError() != null) {
703                     throw new OvsdbPluginException("Insert Operation Failed with Error : "+result.getError().toString());
704                 }
705             }
706             return getNormalizedRow(dbSchema, tableName, row, referencedRows, operationResults, referencedRowsInsertIndex);
707         } catch (InterruptedException | ExecutionException e) {
708             throw new OvsdbPluginException("Exception : "+e.getLocalizedMessage());
709         }
710     }
711
712     /**
713      * inserts a Tree of Rows in multiple Tables that has parent-child relationships referenced through the OVSDB schema's refTable construct.
714      * This is a convenience method on top of {@link #insertTree(Node, String, String, String, UUID, String, Row) insertTree}
715      *
716      * @param node OVSDB Node
717      * @param databaseName Database Name that represents the Schema supported by the node.
718      * @param tableName Table on which the row is inserted
719      * @param parentRowUuid UUID of a Row in parent table to which this operation will result in attaching/mutating.
720      * @param row Row Tree with parent-child relationships via column of type refTable.
721      * @throws OvsdbPluginException Any failure during the insert transaction will result in a specific exception.
722      * @return Returns the row tree with the UUID of every inserted Row populated in the _uuid column of every row in the tree
723      */
724     @Override
725     public Row<GenericTableSchema> insertTree(Node node, String databaseName,
726             String tableName, UUID parentRowUuid, Row<GenericTableSchema> row)
727             throws OvsdbPluginException {
728         return this.insertTree(node, databaseName, tableName, null, parentRowUuid, null, row);
729     }
730
731     /**
732      * Convenience method that helps insertTree to extract Rows that are referenced directly from within a primary row
733      * to be inserted. These referenced rows are *NOT* defined in the OVSDB specification. But, we felt that from a northbound
734      * application standpoint, having such an option is useful and our implementation supports it for applications to make use of.
735      * In short, whichever ColumnSchema is based on an UUID (refered by RefTable in schema), applications can directly insert an
736      * entire row and this method will help navigate it through and identify such cases.
737      * After identifying these Referenced Rows, it will modify the primary row with Named UUIDs and fill out the referencedRows
738      * Map structure so that insertTree can insert all the Rows defined in this Tree of rows in a single transaction with automatic
739      * Mutation on the parent rows.
740      *
741      * @param node OVSDB Node
742      * @param dbName Database Name that represents the Schema supported by the node.
743      * @param row Row Tree with parent-child relationships via column of type refTable.
744      * @param referencedRows Map of Named-UUID to the actual referenced row (with RefTable)
745      * @param namedUuidSuffix Named UUID must be unique for every new Row insert within a given transaction.
746      *        This index will help to retain the uniqueness.
747      */
748     private void extractReferencedRows(Node node, String dbName, Row<GenericTableSchema> row,
749                                        Map<UUID, Map.Entry<String, Row<GenericTableSchema>>> referencedRows,
750                                        int namedUuidSuffix) {
751         OvsdbClient client = connectionService.getConnection(node).getClient();
752         Collection<Column<GenericTableSchema, ?>> columns = row.getColumns();
753         for (Column column : columns) {
754             if (column.getData() != null) {
755                 if (column.getData() instanceof ReferencedRow) {
756                     ReferencedRow refRowObject = (ReferencedRow)column.getData();
757                     UUID refUuid = new UUID("NamedUuid"+namedUuidSuffix++);
758                     column.setData(refUuid);
759                     try {
760                         DatabaseSchema dbSchema = client.getSchema(dbName).get();
761                         GenericTableSchema schema = dbSchema.table(refRowObject.getRefTable(), GenericTableSchema.class);
762                         Row<GenericTableSchema> refRow = schema.createRow((ObjectNode)refRowObject.getJsonNode());
763                         referencedRows.put(refUuid, new AbstractMap.SimpleEntry<String, Row<GenericTableSchema>>(refRowObject.getRefTable(), refRow));
764                         extractReferencedRows(node, dbName, refRow, referencedRows, namedUuidSuffix);
765                     } catch (InterruptedException | ExecutionException e) {
766                         logger.error("Exception while extracting multi-level Row references " + e.getLocalizedMessage());
767                     }
768                 } else if (column.getData() instanceof OvsdbSet) {
769                     OvsdbSet<Object> setObject = (OvsdbSet<Object>)column.getData();
770                     OvsdbSet<Object> modifiedSet = new OvsdbSet<Object>();
771                     for (Object obj : setObject) {
772                         if (obj instanceof ReferencedRow) {
773                             ReferencedRow refRowObject = (ReferencedRow)obj;
774                             UUID refUuid = new UUID("NamedUuid"+namedUuidSuffix++);
775                             modifiedSet.add(refUuid);
776                             try {
777                                 DatabaseSchema dbSchema = client.getSchema(dbName).get();
778                                 GenericTableSchema schema = dbSchema.table(refRowObject.getRefTable(), GenericTableSchema.class);
779                                 Row<GenericTableSchema> refRow = schema.createRow((ObjectNode)refRowObject.getJsonNode());
780                                 referencedRows.put(refUuid, new AbstractMap.SimpleEntry<String, Row<GenericTableSchema>>(refRowObject.getRefTable(), refRow));
781                                 extractReferencedRows(node, dbName, refRow, referencedRows, namedUuidSuffix);
782                             } catch (InterruptedException | ExecutionException e) {
783                                 logger.error("Exception while extracting multi-level Row references " + e.getLocalizedMessage());
784                             }
785                         } else {
786                             modifiedSet.add(obj);
787                         }
788                     }
789                     column.setData(modifiedSet);
790                 }
791             }
792         }
793     }
794
795     /**
796      * getNormalizedRow normalizes the Row from a namedUuid Space as defined in extractReferencedRows to the actual Uuid as created
797      * by the Ovsdb-server. In order to perform this normalization, it processes the operation results for a corresponding Transaction
798      * where the referenced rows are inserted along with the Primary row. It changes the named-Uuid to the actual Uuid before returning
799      * the Row to the application.
800      *
801      * @param dbSchema Database Schema supported by the node.
802      * @param row Row Tree with parent-child relationships via column of type refTable.
803      * @param tableName Table on which the row is inserted
804      * @param referencedRows Map of Named-UUID to the actual referenced row (with RefTable)
805      * @param operationResults Operation Results returned by ovsdb-server for the insertTree transaction
806      * @param referencedRowsInsertIndex Starting index in OperationResults from which the ReferencedRow insert results begin.
807      * @return
808      */
809     private Row<GenericTableSchema> getNormalizedRow(DatabaseSchema dbSchema, String tableName, Row<GenericTableSchema> row,
810                                                      Map<UUID, Map.Entry<String, Row<GenericTableSchema>>> referencedRows,
811                                                      List<OperationResult> operationResults, int referencedRowsInsertIndex) {
812         UUID primaryRowUuid = operationResults.get(0).getUuid();
813         TableSchema<GenericTableSchema> primaryRowTableSchema = dbSchema.table(tableName, GenericTableSchema.class);
814         ColumnSchema<GenericTableSchema, UUID> _uuid = primaryRowTableSchema.column("_uuid", UUID.class);
815         if (_uuid != null) {
816             Column<GenericTableSchema, UUID> _uuidColumn = new Column<GenericTableSchema, UUID>(_uuid, primaryRowUuid);
817             row.addColumn("_uuid", _uuidColumn);
818         }
819
820         if (referencedRows != null) {
821             Collection<Column<GenericTableSchema, ?>> columns = row.getColumns();
822             if (referencedRows != null) {
823                 for (int idx=0; idx < referencedRows.keySet().size(); idx++) {
824                     UUID refUuid = (UUID) referencedRows.keySet().toArray()[idx];
825                     for (Column column : columns) {
826                         if (column.getData() != null) {
827                             if ((column.getData() instanceof UUID) && column.getData().equals(refUuid)) {
828                                 column.setData(operationResults.get(referencedRowsInsertIndex + idx).getUuid());
829                             } else if ((column.getData() instanceof OvsdbSet) && ((OvsdbSet)column.getData()).contains(refUuid)) {
830                                 OvsdbSet<UUID> refSet = (OvsdbSet<UUID>)column.getData();
831                                 refSet.remove(refUuid);
832                                 refSet.add(operationResults.get(referencedRowsInsertIndex + idx).getUuid());
833                             }
834                         }
835                     }
836                 }
837             }
838         }
839         return row;
840     }
841
842     @Override
843     public Row<GenericTableSchema> updateRow(Node node, String databaseName,
844             String tableName, UUID rowUuid, Row<GenericTableSchema> row,
845             boolean overwrite) throws OvsdbPluginException {
846         Connection connection = connectionService.getConnection(node);
847         OvsdbClient client = connection.getClient();
848
849         logger.debug("updateRow : Connection : {} databaseName : {} tableName : {} rowUUID : {} row : {}",
850                       client.getConnectionInfo(), databaseName, tableName, rowUuid, row.toString());
851         try{
852             DatabaseSchema dbSchema = client.getDatabaseSchema(databaseName);
853             TransactionBuilder transactionBuilder = client.transactBuilder(dbSchema);
854             TableSchema<GenericTableSchema> tableSchema = dbSchema.table(tableName, GenericTableSchema.class);
855             ColumnSchema<GenericTableSchema, UUID> _uuid = tableSchema.column("_uuid", UUID.class);
856             transactionBuilder.add(op.update(tableSchema, row)
857                                      .where(_uuid.opEqual(rowUuid))
858                                      .build());
859
860             ListenableFuture<List<OperationResult>> results = transactionBuilder.execute();
861             List<OperationResult> operationResults = results.get();
862             for (OperationResult result : operationResults) {
863                 if (result.getError() != null) {
864                     throw new OvsdbPluginException("Error updating row : " + result.getError() +
865                                                    " Details: " + result.getDetails());
866                 }
867             }
868             if (operationResults.isEmpty() || (transactionBuilder.getOperations().size() != operationResults.size())) {
869                 throw new OvsdbPluginException("Failed to update row. Please check OVS logs for more info.");
870             }
871
872             return this.getRow(node, databaseName, tableName, rowUuid);
873         } catch(Exception e){
874             throw new OvsdbPluginException("Error updating row due to an exception "+ e.getMessage());
875         }
876     }
877
878     @Override
879     public void deleteRow(Node node, String databaseName, String tableName, String parentTable, UUID parentRowUuid,
880             String parentColumn, UUID rowUuid) throws OvsdbPluginException {
881         Connection connection = connectionService.getConnection(node);
882         OvsdbClient client = connection.getClient();
883
884         if (parentTable == null && parentRowUuid != null) {
885             parentTable = this.getTableNameForRowUuid(node, databaseName, parentRowUuid);
886         }
887
888         if (parentColumn == null && parentTable != null) {
889             DatabaseSchema dbSchema = client.getDatabaseSchema(databaseName);
890             TableSchema<GenericTableSchema> parentTableSchema = dbSchema.table(parentTable, GenericTableSchema.class);
891             parentColumn = this.getReferencingColumn(parentTableSchema, tableName);
892         }
893
894         logger.debug("deleteRow : Connection : {} databaseName : {} tableName : {} Uuid : {} ParentTable : {} ParentColumn : {}",
895                 client.getConnectionInfo(), databaseName, tableName, rowUuid, parentTable, parentColumn);
896
897         DatabaseSchema dbSchema = client.getDatabaseSchema(databaseName);
898         TransactionBuilder transactionBuilder = client.transactBuilder(dbSchema);
899         this.processDeleteTransaction(client, databaseName, tableName,
900                                       parentTable, parentColumn, rowUuid.toString(), transactionBuilder);
901
902         ListenableFuture<List<OperationResult>> results = transactionBuilder.execute();
903         List<OperationResult> operationResults;
904         try {
905             operationResults = results.get();
906             if (operationResults.isEmpty() || (transactionBuilder.getOperations().size() != operationResults.size())) {
907                 throw new OvsdbPluginException("Delete Operation Failed");
908             }
909             for (OperationResult result : operationResults) {
910                 if (result.getError() != null) {
911                     throw new OvsdbPluginException("Delete Operation Failed with Error : "+result.getError().toString());
912                 }
913             }
914         } catch (InterruptedException | ExecutionException e) {
915             // TODO Auto-generated catch block
916             e.printStackTrace();
917         }
918     }
919
920     @Override
921     public void deleteRow(Node node, String databaseName, String tableName, UUID rowUuid) throws OvsdbPluginException {
922         this.deleteRow(node, databaseName, tableName, null, null, null, rowUuid);
923     }
924
925     @Override
926     public Row<GenericTableSchema> getRow(Node node, String databaseName,
927             String tableName, UUID uuid) throws OvsdbPluginException {
928         ConcurrentMap<UUID, Row<GenericTableSchema>> rows = this.getRows(node, databaseName, tableName);
929         if (rows != null) {
930             return rows.get(uuid);
931         }
932         return null;
933     }
934
935     @Override
936     public ConcurrentMap<UUID, Row<GenericTableSchema>> getRows(Node node,
937             String databaseName, String tableName) throws OvsdbPluginException {
938         ConcurrentMap<String, Row> ovsTable = ovsdbInventoryService.getTableCache(node, databaseName, tableName);
939         if (ovsTable == null) return null;
940         ConcurrentMap<UUID, Row<GenericTableSchema>> tableDB = Maps.newConcurrentMap();
941         for (String uuidStr : ovsTable.keySet()) {
942             tableDB.put(new UUID(uuidStr), ovsTable.get(uuidStr));
943         }
944         return tableDB;
945     }
946
947     @Override
948     public ConcurrentMap<UUID, Row<GenericTableSchema>> getRows(Node node,
949             String databaseName, String tableName, String fiqlQuery)
950             throws OvsdbPluginException {
951         return this.getRows(node, databaseName, tableName);
952     }
953
954     @Override
955     public List<String> getTables(Node node, String databaseName) throws OvsdbPluginException {
956         ConcurrentMap<String, ConcurrentMap<String, Row>> cache  = ovsdbInventoryService.getCache(node, databaseName);
957         if (cache == null) return null;
958         return new ArrayList<String>(cache.keySet());
959     }
960 }