Merge "Fixing sonar issues on ConfigurationServiceImpl.java"
[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.equals(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.equals(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, 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, 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)) {
185             return null;
186         }
187         String[] parentColumn = OvsVswitchdSchemaConstants.getParentColumnToMutate(childTableName);
188         if (parentColumn != null && parentColumn[0].equals(OvsVswitchdSchemaConstants.DATABASE_NAME)) {
189             Connection connection = connectionService.getConnection(node);
190             OpenVSwitch openVSwitch = connection.getClient().getTypedRowWrapper(OpenVSwitch.class, null);
191             ConcurrentMap<String, Row> row = this.getRows(node, openVSwitch.getSchema().getName());
192             if (row == null || row.size() == 0) {
193                 return null;
194             }
195             return (String)row.keySet().toArray()[0];
196         }
197         return null;
198     }
199
200     /*
201      * Though this is a New API that takes in Row object, this still is considered a
202      * Deprecated call because of the assumption with a Single Row insertion.
203      * An ideal insertRow must be able to take in multiple Rows, which includes the
204      * Row being inserted in one Table and other Rows that needs mutate in other Tables.
205      */
206     @Override
207     @Deprecated
208     public StatusWithUuid insertRow(Node node, String tableName, String parentUuid, Row<GenericTableSchema> row) {
209         String[] parentColumn = OvsVswitchdSchemaConstants.getParentColumnToMutate(tableName);
210         if (parentColumn == null) {
211             parentColumn = new String[]{null, null};
212         }
213
214         Connection connection = connectionService.getConnection(node);
215         OvsdbClient client = connection.getClient();
216
217         String myParentUuid = parentUuid;
218         if (myParentUuid == null) {
219             myParentUuid = this.getSpecialCaseParentUUID(node, OvsVswitchdSchemaConstants.DATABASE_NAME, tableName);
220         }
221         LOGGER.debug("insertRow Connection : {} Table : {} ParentTable : {} Parent Column: {} Parent UUID : {} Row : {}",
222                 client.getConnectionInfo(), tableName, parentColumn[0], parentColumn[1], myParentUuid, row);
223
224         DatabaseSchema dbSchema = client.getDatabaseSchema(OvsVswitchdSchemaConstants.DATABASE_NAME);
225         TransactionBuilder transactionBuilder = client.transactBuilder(dbSchema);
226
227         String namedUuid = "Transaction_"+ tableName;
228         this.processTypedInsertTransaction(client, OvsVswitchdSchemaConstants.DATABASE_NAME, tableName,
229                                 parentColumn[0], myParentUuid, parentColumn[1], namedUuid,
230                                 row, transactionBuilder);
231
232         ListenableFuture<List<OperationResult>> results = transactionBuilder.execute();
233         List<OperationResult> operationResults;
234         try {
235             operationResults = results.get();
236             if (operationResults.isEmpty() || (transactionBuilder.getOperations().size() != operationResults.size())) {
237                 return new StatusWithUuid(StatusCode.INTERNALERROR);
238             }
239             for (OperationResult result : operationResults) {
240                 if (result.getError() != null) {
241                     return new StatusWithUuid(StatusCode.BADREQUEST, result.getError());
242                 }
243             }
244             UUID uuid = operationResults.get(0).getUuid();
245             return new StatusWithUuid(StatusCode.SUCCESS, uuid);
246         } catch (InterruptedException | ExecutionException e) {
247             // TODO Auto-generated catch block
248             return new StatusWithUuid(StatusCode.INTERNALERROR, e.getLocalizedMessage());
249         }
250
251     }
252
253     @Override
254     @Deprecated
255     public Status updateRow (Node node, String tableName, String parentUUID, String rowUUID, Row row) {
256         String databaseName = OvsVswitchdSchemaConstants.DATABASE_NAME;
257         Row<GenericTableSchema> updatedRow = this.updateRow(node, databaseName, tableName, new UUID(rowUUID), row, true);
258         return new StatusWithUuid(StatusCode.SUCCESS);
259     }
260
261     private void processDeleteTransaction(OvsdbClient client, String databaseName, String childTable,
262                                     String parentTable, String parentColumn, String uuid, TransactionBuilder transactionBuilder) {
263         DatabaseSchema dbSchema = client.getDatabaseSchema(databaseName);
264         TableSchema<GenericTableSchema> childTableSchema = dbSchema.table(childTable, GenericTableSchema.class);
265
266         if (parentColumn != null) {
267             TableSchema<GenericTableSchema> parentTableSchema = dbSchema.table(parentTable, GenericTableSchema.class);
268             ColumnSchema<GenericTableSchema, UUID> parentColumnSchema = parentTableSchema.column(parentColumn, UUID.class);
269             transactionBuilder
270                 .add(op.mutate(parentTableSchema)
271                         .addMutation(parentColumnSchema, Mutator.DELETE, new UUID(uuid))
272                         .where(parentColumnSchema.opIncludes(new UUID(uuid)))
273                         .build());
274         }
275
276         ColumnSchema<GenericTableSchema, UUID> _uuid = childTableSchema.column("_uuid", UUID.class);
277         transactionBuilder.add(op.delete(childTableSchema)
278                 .where(_uuid.opEqual(new UUID(uuid)))
279                 .build());
280     }
281
282     @Override
283     @Deprecated
284     public Status deleteRow(Node node, String tableName, String uuid) {
285         String databaseName = OvsVswitchdSchemaConstants.DATABASE_NAME;
286         Connection connection = connectionService.getConnection(node);
287         OvsdbClient client = connection.getClient();
288
289         String[] parentColumn = OvsVswitchdSchemaConstants.getParentColumnToMutate(tableName);
290         if (parentColumn == null) {
291             parentColumn = new String[]{null, null};
292         }
293
294         LOGGER.debug("deleteRow : Connection : {} databaseName : {} tableName : {} Uuid : {} ParentTable : {} ParentColumn : {}",
295                 client.getConnectionInfo(), databaseName, tableName, uuid, parentColumn[0], parentColumn[1]);
296
297         DatabaseSchema dbSchema = client.getDatabaseSchema(databaseName);
298         TransactionBuilder transactionBuilder = client.transactBuilder(dbSchema);
299         this.processDeleteTransaction(client, OvsVswitchdSchemaConstants.DATABASE_NAME, tableName,
300                                       parentColumn[0], parentColumn[1], uuid, transactionBuilder);
301
302         ListenableFuture<List<OperationResult>> results = transactionBuilder.execute();
303         List<OperationResult> operationResults;
304         try {
305             operationResults = results.get();
306             if (operationResults.isEmpty() || (transactionBuilder.getOperations().size() != operationResults.size())) {
307                 return new StatusWithUuid(StatusCode.INTERNALERROR);
308             }
309             for (OperationResult result : operationResults) {
310                 if (result.getError() != null) {
311                     return new StatusWithUuid(StatusCode.BADREQUEST, result.getError());
312                 }
313             }
314         } catch (InterruptedException | ExecutionException e) {
315             LOGGER.error("Error in deleteRow() {} {}", node, tableName, e);
316         }
317
318         return new Status(StatusCode.SUCCESS);
319     }
320
321     @Override
322     @Deprecated
323     public ConcurrentMap<String, Row> getRows(Node node, String tableName) {
324         ConcurrentMap<String, Row> ovsTable = ovsdbInventoryService.getTableCache(node, OvsVswitchdSchemaConstants.DATABASE_NAME,  tableName);
325         return ovsTable;
326     }
327
328     @Override
329     @Deprecated
330     public Row getRow(Node node, String tableName, String uuid) {
331         Map<String, Row> ovsTable = ovsdbInventoryService.getTableCache(node, OvsVswitchdSchemaConstants.DATABASE_NAME,  tableName);
332         if (ovsTable == null) {
333             return null;
334         }
335         return ovsTable.get(uuid);
336     }
337
338     @Override
339     @Deprecated
340     public List<String> getTables(Node node) {
341         return this.getTables(node, OvsVswitchdSchemaConstants.DATABASE_NAME);
342     }
343
344     private InetAddress getControllerIPAddress(Connection connection) {
345         InetAddress controllerIP = null;
346
347         String addressString = ConfigProperties.getProperty(this.getClass(), "ovsdb.controller.address");
348
349         if (addressString != null) {
350             try {
351                 controllerIP = InetAddress.getByName(addressString);
352                 if (controllerIP != null) {
353                     return controllerIP;
354                 }
355             } catch (UnknownHostException e) {
356                 LOGGER.error("Host {} is invalid", addressString);
357             }
358         }
359
360         addressString = ConfigProperties.getProperty(this.getClass(), "of.address");
361
362         if (addressString != null) {
363             try {
364                 controllerIP = InetAddress.getByName(addressString);
365                 if (controllerIP != null) {
366                     return controllerIP;
367                 }
368             } catch (UnknownHostException e) {
369                 LOGGER.error("Host {} is invalid", addressString);
370             }
371         }
372
373         try {
374             controllerIP = connection.getClient().getConnectionInfo().getLocalAddress();
375             return controllerIP;
376         } catch (Exception e) {
377             LOGGER.debug("Invalid connection provided to getControllerIPAddresses", e);
378         }
379         return controllerIP;
380     }
381
382     private short getControllerOFPort() {
383         Short defaultOpenFlowPort = 6633;
384         Short openFlowPort = defaultOpenFlowPort;
385         String portString = ConfigProperties.getProperty(this.getClass(), "of.listenPort");
386         if (portString != null) {
387             try {
388                 openFlowPort = Short.decode(portString).shortValue();
389             } catch (NumberFormatException e) {
390                 LOGGER.warn("Invalid port:{}, use default({})", portString,
391                         openFlowPort);
392             }
393         }
394         return openFlowPort;
395     }
396
397     private UUID getCurrentControllerUuid(Node node, final String controllerTableName, final String target) {
398         ConcurrentMap<String, Row> rows = this.getRows(node, controllerTableName);
399
400         if (rows != null) {
401             for (Map.Entry<String, Row> entry : rows.entrySet()) {
402                 Controller currController = this.getTypedRow(node, Controller.class, entry.getValue());
403                 Column<GenericTableSchema, String> column = currController.getTargetColumn();
404                 String currTarget = column.getData();
405                 if (currTarget != null && currTarget.equalsIgnoreCase(target)) {
406                     return currController.getUuid();
407                 }
408             }
409         }
410         return null;
411     }
412
413     @Override
414     public Boolean setOFController(Node node, String bridgeUUID) throws InterruptedException, ExecutionException {
415         Connection connection = this.getConnection(node);
416         if (connection == null) {
417             return false;
418         }
419
420         Bridge bridge = connection.getClient().createTypedRowWrapper(Bridge.class);
421
422         Status updateOperationStatus = null;
423         try {
424             OvsdbSet<String> protocols = new OvsdbSet<String>();
425
426             String ofVersion = System.getProperty("ovsdb.of.version", OPENFLOW_13);
427             switch (ofVersion) {
428                 case OPENFLOW_13:
429                     //fall through
430                 default:
431                     protocols.add("OpenFlow13");
432                     break;
433             }
434             bridge.setProtocols(protocols);
435             updateOperationStatus = this.updateRow(node, bridge.getSchema().getName(),
436                                                    null, bridgeUUID, bridge.getRow());
437             LOGGER.debug("Bridge {} updated to {} with Status {}", bridgeUUID,
438                     protocols.toArray()[0], updateOperationStatus);
439
440         } catch (SchemaVersionMismatchException e){
441             LOGGER.debug(e.toString());
442         }
443
444         // If we fail to update the protocols
445         if (updateOperationStatus != null && !updateOperationStatus.isSuccess()) {
446             return updateOperationStatus.isSuccess();
447         }
448
449         Status status = null;
450         UUID currControllerUuid = null;
451         InetAddress ofControllerAddr = this.getControllerIPAddress(connection);
452         short ofControllerPort = getControllerOFPort();
453         String newControllerTarget = "tcp:"+ofControllerAddr.getHostAddress()+":"+ofControllerPort;
454         Controller newController = connection.getClient().createTypedRowWrapper(Controller.class);
455         newController.setTarget(newControllerTarget);
456         final String controllerTableName = newController.getSchema().getName();
457
458         currControllerUuid = getCurrentControllerUuid(node, controllerTableName, newControllerTarget);
459
460         if (currControllerUuid != null) {
461             bridge = connection.getClient().createTypedRowWrapper(Bridge.class);
462             bridge.setController(Sets.newHashSet(currControllerUuid));
463             status = this.updateRow(node, bridge.getSchema().getName(), null, bridgeUUID, bridge.getRow());
464         } else {
465             status = this.insertRow(node, controllerTableName, bridgeUUID, newController.getRow());
466         }
467
468         if (status != null) {
469             return status.isSuccess();
470         }
471
472         return false;
473     }
474
475
476     public Boolean setBridgeOFController(Node node, String bridgeIdentifier) {
477         if (connectionService == null) {
478             LOGGER.error("Couldn't refer to the ConnectionService");
479             return false;
480         }
481
482         try{
483             Connection connection = connectionService.getConnection(node);
484             Bridge bridge = connection.getClient().getTypedRowWrapper(Bridge.class, null);
485
486             Map<String, Row> brTableCache = ovsdbInventoryService.getTableCache(node, OvsVswitchdSchemaConstants.DATABASE_NAME, bridge.getSchema().getName());
487             for (String uuid : brTableCache.keySet()) {
488                 bridge = connection.getClient().getTypedRowWrapper(Bridge.class, brTableCache.get(uuid));
489                 if (bridge.getName().contains(bridgeIdentifier)) {
490                     return setOFController(node, uuid);
491                 }
492             }
493         } catch(Exception e) {
494             LOGGER.error("Error in setBridgeOFController()", e);
495         }
496         return false;
497     }
498
499     @Override
500     public <T extends TypedBaseTable<?>> String getTableName(Node node, Class<T> typedClass) {
501         Connection connection = connectionService.getConnection(node);
502         if (connection == null) return null;
503         OvsdbClient client = connection.getClient();
504         TypedBaseTable<?> typedTable = client.getTypedRowWrapper(typedClass, null);
505         if (typedTable == null) return null;
506         return typedTable.getSchema().getName();
507     }
508
509     @Override
510     public <T extends TypedBaseTable<?>> T getTypedRow(Node node, Class<T> typedClass, Row row) {
511         Connection connection = connectionService.getConnection(node);
512         if (connection == null) return null;
513         OvsdbClient client = connection.getClient();
514         return (T)client.getTypedRowWrapper(typedClass, row);
515     }
516
517     @Override
518     public <T extends TypedBaseTable<?>> T createTypedRow(Node node, Class<T> typedClass) {
519         Connection connection = connectionService.getConnection(node);
520         if (connection == null) return null;
521         OvsdbClient client = connection.getClient();
522         return client.createTypedRowWrapper(typedClass);
523     }
524
525     // SCHEMA-INDEPENDENT Configuration Service APIs
526
527     private String getTableNameForRowUuid(Node node, String databaseName, UUID rowUuid) {
528         ConcurrentMap<String, ConcurrentMap<String, Row>> cache  = ovsdbInventoryService.getCache(node, databaseName);
529         if (cache == null) return null;
530         for (String tableName : cache.keySet()) {
531             ConcurrentMap<String, Row> rows = cache.get(tableName);
532             if (rows.get(rowUuid.toString()) != null) {
533                 return tableName;
534             }
535         }
536         return null;
537     }
538
539     private String getReferencingColumn (TableSchema<?> parentTableSchema, String childTableName) throws OvsdbPluginException {
540         Map<String, ColumnSchema> columnSchemas = parentTableSchema.getColumnSchemas();
541         String refColumn = null;
542         for (String columnName : columnSchemas.keySet()) {
543             ColumnSchema columnSchema = columnSchemas.get(columnName);
544             if (columnSchema.getType().getBaseType().getClass().equals(UuidBaseType.class)) {
545                 UuidBaseType refType = (UuidBaseType)columnSchema.getType().getBaseType();
546                 if (refType.getRefTable() != null && refType.getRefTable().equalsIgnoreCase(childTableName)) {
547                     if (refColumn == null) {
548                         refColumn = columnName;
549                     } else {
550                         throw new OvsdbPluginException("Multiple Referencing Columns for "+ childTableName +" on "+ parentTableSchema.getName());
551                     }
552                 }
553             }
554         }
555         if (refColumn != null) {
556             return refColumn;
557         }
558         throw new OvsdbPluginException("No Referencing Column for "+childTableName+" on "+parentTableSchema.getName());
559     }
560     /*
561      * A common Insert Transaction convenience method that populates the TransactionBuilder with insert operation
562      * for a Child Row and also mutates the parent row with the UUID of the inserted Child.
563      */
564     private void processInsertTransaction(OvsdbClient client, String databaseName, String childTable,
565                                     String parentTable, UUID parentUuid, String parentColumn, String namedUuid,
566                                     Row<GenericTableSchema> row,
567                                     TransactionBuilder transactionBuilder) {
568         // Insert the row as the first transaction entry
569         DatabaseSchema dbSchema = client.getDatabaseSchema(databaseName);
570         TableSchema<GenericTableSchema> childTableSchema = dbSchema.table(childTable, GenericTableSchema.class);
571         transactionBuilder.add(op.insert(childTableSchema, row)
572                         .withId(namedUuid));
573
574         // Followed by the Mutation
575         if (parentColumn != null) {
576             TableSchema<GenericTableSchema> parentTableSchema = dbSchema.table(parentTable, GenericTableSchema.class);
577             ColumnSchema<GenericTableSchema, UUID> parentColumnSchema = parentTableSchema.column(parentColumn, UUID.class);
578             ColumnSchema<GenericTableSchema, UUID> _uuid = parentTableSchema.column("_uuid", UUID.class);
579
580             transactionBuilder
581                 .add(op.mutate(parentTableSchema)
582                         .addMutation(parentColumnSchema, Mutator.INSERT, new UUID(namedUuid))
583                         .where(_uuid.opEqual(parentUuid))
584                         .build());
585         }
586     }
587
588     /**
589      * insert a Row in a Table of a specified Database Schema.
590      *
591      * This method can insert just a single Row specified in the row parameter.
592      * But {@link #insertTree(Node, String, String, UUID, Row) insertTree}
593      * can insert a hierarchy of rows with parent-child relationship.
594      *
595      * @param node OVSDB Node
596      * @param databaseName Database Name that represents the Schema supported by the node.
597      * @param tableName Table on which the row is inserted
598      * @param parentTable Name of the Parent Table to which this operation will result in attaching/mutating.
599      * @param parentUuid UUID of a Row in parent table to which this operation will result in attaching/mutating.
600      * @param parentColumn Name of the Column in the Parent Table to be mutated with the UUID that results from the insert operation.
601      * @param row Row of table Content to be inserted
602      * @throws OvsdbPluginException Any failure during the insert transaction will result in a specific exception.
603      * @return UUID of the inserted Row
604      */
605     @Override
606     public UUID insertRow(Node node, String databaseName, String tableName, String parentTable, UUID parentUuid,
607                           String parentColumn, Row<GenericTableSchema> row) throws OvsdbPluginException {
608         Connection connection = connectionService.getConnection(node);
609         OvsdbClient client = connection.getClient();
610         DatabaseSchema dbSchema = client.getDatabaseSchema(databaseName);
611         TableSchema<GenericTableSchema> tableSchema = dbSchema.table(tableName, GenericTableSchema.class);
612
613         Row<GenericTableSchema> processedRow = this.insertTree(node, databaseName, tableName, parentTable, parentUuid, parentColumn, row);
614
615         ColumnSchema<GenericTableSchema, UUID> _uuid = tableSchema.column("_uuid", UUID.class);
616         Column<GenericTableSchema, UUID> uuid = processedRow.getColumn(_uuid);
617         return uuid.getData();
618     }
619
620     /**
621      * insert a Row in a Table of a specified Database Schema. This is a convenience method on top of
622      * {@link insertRow(Node, String, String, String, UUID, String, Row) insertRow}
623      * which assumes that OVSDB schema implementation that corresponds to the databaseName will provide
624      * the necessary service to populate the Parent Table Name and Parent Column Name.
625      *
626      * This method can insert just a single Row specified in the row parameter.
627      * But {@link #insertTree(Node, String, String, UUID, Row) insertTree}
628      * can insert a hierarchy of rows with parent-child relationship.
629      *
630      * @param node OVSDB Node
631      * @param databaseName Database Name that represents the Schema supported by the node.
632      * @param tableName Table on which the row is inserted
633      * @param parentRowUuid UUID of the parent table to which this operation will result in attaching/mutating.
634      * @param row Row of table Content to be inserted
635      * @throws OvsdbPluginException Any failure during the insert transaction will result in a specific exception.
636      * @return UUID of the inserted Row
637      */
638     @Override
639     public UUID insertRow(Node node, String databaseName, String tableName,
640             UUID parentRowUuid, Row<GenericTableSchema> row)
641             throws OvsdbPluginException {
642         return this.insertRow(node, databaseName, tableName, null, parentRowUuid, null, row);
643     }
644
645     /**
646      * inserts a Tree of Rows in multiple Tables that has parent-child relationships referenced through the OVSDB schema's refTable construct
647      *
648      * @param node OVSDB Node
649      * @param databaseName Database Name that represents the Schema supported by the node.
650      * @param tableName Table on which the row is inserted
651      * @param parentTable Name of the Parent Table to which this operation will result in attaching/mutating.
652      * @param parentUuid UUID of a Row in parent table to which this operation will result in attaching/mutating.
653      * @param parentColumn Name of the Column in the Parent Table to be mutated with the UUID that results from the insert operation.
654      * @param row Row Tree with parent-child relationships via column of type refTable.
655      * @throws OvsdbPluginException Any failure during the insert transaction will result in a specific exception.
656      * @return Returns the row tree with the UUID of every inserted Row populated in the _uuid column of every row in the tree
657      */
658     @Override
659     public Row<GenericTableSchema> insertTree(Node node, String databaseName, String tableName, String parentTable, UUID parentUuid,
660                                               String parentColumn, Row<GenericTableSchema> row) throws OvsdbPluginException {
661         Connection connection = connectionService.getConnection(node);
662         OvsdbClient client = connection.getClient();
663
664         if (databaseName == null || tableName == null) {
665             throw new OvsdbPluginException("databaseName, tableName and parentUuid are Mandatory Parameters");
666         }
667
668         if (parentTable == null && parentUuid != null) {
669             parentTable = this.getTableNameForRowUuid(node, databaseName, parentUuid);
670         }
671
672         if (parentColumn == null && parentTable != null) {
673             DatabaseSchema dbSchema = client.getDatabaseSchema(databaseName);
674             TableSchema<GenericTableSchema> parentTableSchema = dbSchema.table(parentTable, GenericTableSchema.class);
675             parentColumn = this.getReferencingColumn(parentTableSchema, tableName);
676         }
677
678         LOGGER.debug("insertTree Connection : {} Table : {} ParentTable : {} Parent Column: {} Parent UUID : {} Row : {}",
679                 client.getConnectionInfo(), tableName, parentTable, parentColumn, parentUuid, row);
680
681         Map<UUID, Map.Entry<String, Row<GenericTableSchema>>> referencedRows = Maps.newConcurrentMap();
682         extractReferencedRows(node, databaseName, row, referencedRows, 0);
683         DatabaseSchema dbSchema = client.getDatabaseSchema(OvsVswitchdSchemaConstants.DATABASE_NAME);
684         TransactionBuilder transactionBuilder = client.transactBuilder(dbSchema);
685
686         String namedUuid = "Transaction_"+ tableName;
687         this.processInsertTransaction(client, databaseName, tableName, parentTable, parentUuid,
688                                       parentColumn, namedUuid, row, transactionBuilder);
689
690         int referencedRowsInsertIndex = transactionBuilder.getOperations().size();
691         // Insert Referenced Rows
692         if (referencedRows != null) {
693             for (UUID refUuid : referencedRows.keySet()) {
694                 Map.Entry<String, Row<GenericTableSchema>> referencedRow = referencedRows.get(refUuid);
695                 TableSchema<GenericTableSchema> refTableSchema = dbSchema.table(referencedRow.getKey(), GenericTableSchema.class);
696                 transactionBuilder.add(op.insert(refTableSchema, referencedRow.getValue())
697                                 .withId(refUuid.toString()));
698             }
699         }
700
701         ListenableFuture<List<OperationResult>> results = transactionBuilder.execute();
702         List<OperationResult> operationResults;
703         try {
704             operationResults = results.get();
705             if (operationResults.isEmpty() || (transactionBuilder.getOperations().size() != operationResults.size())) {
706                 throw new OvsdbPluginException("Insert Operation Failed");
707             }
708             for (OperationResult result : operationResults) {
709                 if (result.getError() != null) {
710                     throw new OvsdbPluginException("Insert Operation Failed with Error : "+result.getError().toString());
711                 }
712             }
713             return getNormalizedRow(dbSchema, tableName, row, referencedRows, operationResults, referencedRowsInsertIndex);
714         } catch (InterruptedException | ExecutionException e) {
715             throw new OvsdbPluginException("Exception : "+e.getLocalizedMessage());
716         }
717     }
718
719     /**
720      * inserts a Tree of Rows in multiple Tables that has parent-child relationships referenced through the OVSDB schema's refTable construct.
721      * This is a convenience method on top of {@link #insertTree(Node, String, String, String, UUID, String, Row) insertTree}
722      *
723      * @param node OVSDB Node
724      * @param databaseName Database Name that represents the Schema supported by the node.
725      * @param tableName Table on which the row is inserted
726      * @param parentRowUuid UUID of a Row in parent table to which this operation will result in attaching/mutating.
727      * @param row Row Tree with parent-child relationships via column of type refTable.
728      * @throws OvsdbPluginException Any failure during the insert transaction will result in a specific exception.
729      * @return Returns the row tree with the UUID of every inserted Row populated in the _uuid column of every row in the tree
730      */
731     @Override
732     public Row<GenericTableSchema> insertTree(Node node, String databaseName,
733             String tableName, UUID parentRowUuid, Row<GenericTableSchema> row)
734             throws OvsdbPluginException {
735         return this.insertTree(node, databaseName, tableName, null, parentRowUuid, null, row);
736     }
737
738     /**
739      * Convenience method that helps insertTree to extract Rows that are referenced directly from within a primary row
740      * to be inserted. These referenced rows are *NOT* defined in the OVSDB specification. But, we felt that from a northbound
741      * application standpoint, having such an option is useful and our implementation supports it for applications to make use of.
742      * In short, whichever ColumnSchema is based on an UUID (refered by RefTable in schema), applications can directly insert an
743      * entire row and this method will help navigate it through and identify such cases.
744      * After identifying these Referenced Rows, it will modify the primary row with Named UUIDs and fill out the referencedRows
745      * Map structure so that insertTree can insert all the Rows defined in this Tree of rows in a single transaction with automatic
746      * Mutation on the parent rows.
747      *
748      * @param node OVSDB Node
749      * @param dbName Database Name that represents the Schema supported by the node.
750      * @param row Row Tree with parent-child relationships via column of type refTable.
751      * @param referencedRows Map of Named-UUID to the actual referenced row (with RefTable)
752      * @param namedUuidSuffix Named UUID must be unique for every new Row insert within a given transaction.
753      *        This index will help to retain the uniqueness.
754      */
755     private void extractReferencedRows(Node node, String dbName, Row<GenericTableSchema> row,
756                                        Map<UUID, Map.Entry<String, Row<GenericTableSchema>>> referencedRows,
757                                        int namedUuidSuffix) {
758         OvsdbClient client = connectionService.getConnection(node).getClient();
759         Collection<Column<GenericTableSchema, ?>> columns = row.getColumns();
760         for (Column column : columns) {
761             if (column.getData() != null) {
762                 if (column.getData() instanceof ReferencedRow) {
763                     ReferencedRow refRowObject = (ReferencedRow)column.getData();
764                     UUID refUuid = new UUID("NamedUuid"+namedUuidSuffix++);
765                     column.setData(refUuid);
766                     try {
767                         DatabaseSchema dbSchema = client.getSchema(dbName).get();
768                         GenericTableSchema schema = dbSchema.table(refRowObject.getRefTable(), GenericTableSchema.class);
769                         Row<GenericTableSchema> refRow = schema.createRow((ObjectNode)refRowObject.getJsonNode());
770                         referencedRows.put(refUuid, new AbstractMap.SimpleEntry<String, Row<GenericTableSchema>>(refRowObject.getRefTable(), refRow));
771                         extractReferencedRows(node, dbName, refRow, referencedRows, namedUuidSuffix);
772                     } catch (InterruptedException | ExecutionException e) {
773                         LOGGER.error("Exception while extracting multi-level Row references " + e.getLocalizedMessage());
774                     }
775                 } else if (column.getData() instanceof OvsdbSet) {
776                     OvsdbSet<Object> setObject = (OvsdbSet<Object>)column.getData();
777                     OvsdbSet<Object> modifiedSet = new OvsdbSet<Object>();
778                     for (Object obj : setObject) {
779                         if (obj instanceof ReferencedRow) {
780                             ReferencedRow refRowObject = (ReferencedRow)obj;
781                             UUID refUuid = new UUID("NamedUuid"+namedUuidSuffix++);
782                             modifiedSet.add(refUuid);
783                             try {
784                                 DatabaseSchema dbSchema = client.getSchema(dbName).get();
785                                 GenericTableSchema schema = dbSchema.table(refRowObject.getRefTable(), GenericTableSchema.class);
786                                 Row<GenericTableSchema> refRow = schema.createRow((ObjectNode)refRowObject.getJsonNode());
787                                 referencedRows.put(refUuid, new AbstractMap.SimpleEntry<String, Row<GenericTableSchema>>(refRowObject.getRefTable(), refRow));
788                                 extractReferencedRows(node, dbName, refRow, referencedRows, namedUuidSuffix);
789                             } catch (InterruptedException | ExecutionException e) {
790                                 LOGGER.error("Exception while extracting multi-level Row references " + e.getLocalizedMessage());
791                             }
792                         } else {
793                             modifiedSet.add(obj);
794                         }
795                     }
796                     column.setData(modifiedSet);
797                 }
798             }
799         }
800     }
801
802     /**
803      * getNormalizedRow normalizes the Row from a namedUuid Space as defined in extractReferencedRows to the actual Uuid as created
804      * by the Ovsdb-server. In order to perform this normalization, it processes the operation results for a corresponding Transaction
805      * where the referenced rows are inserted along with the Primary row. It changes the named-Uuid to the actual Uuid before returning
806      * the Row to the application.
807      *
808      * @param dbSchema Database Schema supported by the node.
809      * @param row Row Tree with parent-child relationships via column of type refTable.
810      * @param tableName Table on which the row is inserted
811      * @param referencedRows Map of Named-UUID to the actual referenced row (with RefTable)
812      * @param operationResults Operation Results returned by ovsdb-server for the insertTree transaction
813      * @param referencedRowsInsertIndex Starting index in OperationResults from which the ReferencedRow insert results begin.
814      * @return
815      */
816     private Row<GenericTableSchema> getNormalizedRow(DatabaseSchema dbSchema, String tableName, Row<GenericTableSchema> row,
817                                                      Map<UUID, Map.Entry<String, Row<GenericTableSchema>>> referencedRows,
818                                                      List<OperationResult> operationResults, int referencedRowsInsertIndex) {
819         UUID primaryRowUuid = operationResults.get(0).getUuid();
820         TableSchema<GenericTableSchema> primaryRowTableSchema = dbSchema.table(tableName, GenericTableSchema.class);
821         ColumnSchema<GenericTableSchema, UUID> uuid = primaryRowTableSchema.column("_uuid", UUID.class);
822         if (uuid != null) {
823             Column<GenericTableSchema, UUID> uuidColumn = new Column<GenericTableSchema, UUID>(uuid, primaryRowUuid);
824             row.addColumn("_uuid", uuidColumn);
825         }
826
827         if (referencedRows != null) {
828             Collection<Column<GenericTableSchema, ?>> columns = row.getColumns();
829             if (referencedRows != null) {
830                 for (int idx=0; idx < referencedRows.keySet().size(); idx++) {
831                     UUID refUuid = (UUID) referencedRows.keySet().toArray()[idx];
832                     for (Column column : columns) {
833                         if (column.getData() != null) {
834                             if ((column.getData() instanceof UUID) && column.getData().equals(refUuid)) {
835                                 column.setData(operationResults.get(referencedRowsInsertIndex + idx).getUuid());
836                             } else if ((column.getData() instanceof OvsdbSet) && ((OvsdbSet)column.getData()).contains(refUuid)) {
837                                 OvsdbSet<UUID> refSet = (OvsdbSet<UUID>)column.getData();
838                                 refSet.remove(refUuid);
839                                 refSet.add(operationResults.get(referencedRowsInsertIndex + idx).getUuid());
840                             }
841                         }
842                     }
843                 }
844             }
845         }
846         return row;
847     }
848
849     @Override
850     public Row<GenericTableSchema> updateRow(Node node, String databaseName,
851             String tableName, UUID rowUuid, Row<GenericTableSchema> row,
852             boolean overwrite) {
853         Connection connection = connectionService.getConnection(node);
854         OvsdbClient client = connection.getClient();
855
856         LOGGER.debug("updateRow : Connection : {} databaseName : {} tableName : {} rowUUID : {} row : {}",
857                 client.getConnectionInfo(), databaseName, tableName, rowUuid, row.toString());
858         try{
859             DatabaseSchema dbSchema = client.getDatabaseSchema(databaseName);
860             TransactionBuilder transactionBuilder = client.transactBuilder(dbSchema);
861             TableSchema<GenericTableSchema> tableSchema = dbSchema.table(tableName, GenericTableSchema.class);
862             ColumnSchema<GenericTableSchema, UUID> uuid = tableSchema.column("_uuid", UUID.class);
863             transactionBuilder.add(op.update(tableSchema, row)
864                     .where(uuid.opEqual(rowUuid))
865                     .build());
866
867             ListenableFuture<List<OperationResult>> results = transactionBuilder.execute();
868             List<OperationResult> operationResults = results.get();
869             for (OperationResult result : operationResults) {
870                 if (result.getError() != null) {
871                     throw new OvsdbPluginException("Error updating row : " + result.getError() +
872                                                    " Details: " + result.getDetails());
873                 }
874             }
875             if (operationResults.isEmpty() || (transactionBuilder.getOperations().size() != operationResults.size())) {
876                 throw new OvsdbPluginException("Failed to update row. Please check OVS logs for more info.");
877             }
878
879             return this.getRow(node, databaseName, tableName, rowUuid);
880         } catch(Exception e){
881             throw new OvsdbPluginException("Error updating row due to an exception "+ e.getMessage());
882         }
883     }
884
885     @Override
886     public void deleteRow(Node node, String databaseName, String tableName, String parentTable, UUID parentRowUuid,
887             String parentColumn, UUID rowUuid) {
888         Connection connection = connectionService.getConnection(node);
889         OvsdbClient client = connection.getClient();
890
891         if (parentTable == null && parentRowUuid != null) {
892             parentTable = this.getTableNameForRowUuid(node, databaseName, parentRowUuid);
893         }
894
895         String myParentColumn = parentColumn;
896         if (myParentColumn == null && parentTable != null) {
897             DatabaseSchema dbSchema = client.getDatabaseSchema(databaseName);
898             TableSchema<GenericTableSchema> parentTableSchema = dbSchema.table(parentTable, GenericTableSchema.class);
899             myParentColumn = this.getReferencingColumn(parentTableSchema, tableName);
900         }
901
902         LOGGER.debug("deleteRow : Connection : {} databaseName : {} tableName : {} Uuid : {} ParentTable : {} ParentColumn : {}",
903                 client.getConnectionInfo(), databaseName, tableName, rowUuid, parentTable, myParentColumn);
904
905         DatabaseSchema dbSchema = client.getDatabaseSchema(databaseName);
906         TransactionBuilder transactionBuilder = client.transactBuilder(dbSchema);
907         this.processDeleteTransaction(client, databaseName, tableName,
908                                       parentTable, myParentColumn, rowUuid.toString(), transactionBuilder);
909
910         ListenableFuture<List<OperationResult>> results = transactionBuilder.execute();
911         List<OperationResult> operationResults;
912         try {
913             operationResults = results.get();
914             if (operationResults.isEmpty() || (transactionBuilder.getOperations().size() != operationResults.size())) {
915                 throw new OvsdbPluginException("Delete Operation Failed");
916             }
917             for (OperationResult result : operationResults) {
918                 if (result.getError() != null) {
919                     throw new OvsdbPluginException("Delete Operation Failed with Error : "+result.getError().toString());
920                 }
921             }
922         } catch (InterruptedException | ExecutionException e) {
923             LOGGER.error("Error in deleteRow() {} {} {} {}", node, databaseName, tableName, parentTable, e);
924         }
925     }
926
927     @Override
928     public void deleteRow(Node node, String databaseName, String tableName, UUID rowUuid) {
929         this.deleteRow(node, databaseName, tableName, null, null, null, rowUuid);
930     }
931
932     @Override
933     public Row<GenericTableSchema> getRow(Node node, String databaseName,
934             String tableName, UUID uuid) {
935         ConcurrentMap<UUID, Row<GenericTableSchema>> rows = this.getRows(node, databaseName, tableName);
936         if (rows != null) {
937             return rows.get(uuid);
938         }
939         return null;
940     }
941
942     @Override
943     public ConcurrentMap<UUID, Row<GenericTableSchema>> getRows(Node node,
944             String databaseName, String tableName) throws OvsdbPluginException {
945         ConcurrentMap<String, Row> ovsTable = ovsdbInventoryService.getTableCache(node, databaseName, tableName);
946         if (ovsTable == null) {
947             return null;
948         }
949         ConcurrentMap<UUID, Row<GenericTableSchema>> tableDB = Maps.newConcurrentMap();
950         for (String uuidStr : ovsTable.keySet()) {
951             tableDB.put(new UUID(uuidStr), ovsTable.get(uuidStr));
952         }
953         return tableDB;
954     }
955
956     @Override
957     public ConcurrentMap<UUID, Row<GenericTableSchema>> getRows(Node node,
958             String databaseName, String tableName, String fiqlQuery) {
959         return this.getRows(node, databaseName, tableName);
960     }
961
962     @Override
963     public List<String> getTables(Node node, String databaseName) {
964         ConcurrentMap<String, ConcurrentMap<String, Row>> cache  = ovsdbInventoryService.getCache(node, databaseName);
965         if (cache == null) {
966             return null;
967         } else {
968             return new ArrayList<String>(cache.keySet());
969         }
970     }
971 }