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