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