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