X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=southbound%2Fsouthbound-impl%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fovsdb%2Fsouthbound%2FOvsdbConnectionManager.java;h=a3ee6cd37ab5ae91d695ad4d23f941670e1a9161;hb=82997ed5ede5fad4508fa2f2fcbaaf673b983699;hp=b5958ad3bef1c6712f058c23a3f353c557f1b67a;hpb=f0afee265efb4e414a31617f2fc1335d80e1326b;p=ovsdb.git diff --git a/southbound/southbound-impl/src/main/java/org/opendaylight/ovsdb/southbound/OvsdbConnectionManager.java b/southbound/southbound-impl/src/main/java/org/opendaylight/ovsdb/southbound/OvsdbConnectionManager.java index b5958ad3b..a3ee6cd37 100644 --- a/southbound/southbound-impl/src/main/java/org/opendaylight/ovsdb/southbound/OvsdbConnectionManager.java +++ b/southbound/southbound-impl/src/main/java/org/opendaylight/ovsdb/southbound/OvsdbConnectionManager.java @@ -18,7 +18,10 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; import org.opendaylight.controller.md.sal.binding.api.DataBroker; import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction; import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction; @@ -42,12 +45,17 @@ import org.opendaylight.ovsdb.lib.schema.DatabaseSchema; import org.opendaylight.ovsdb.lib.schema.GenericTableSchema; import org.opendaylight.ovsdb.lib.schema.typed.TyperUtils; import org.opendaylight.ovsdb.schema.openvswitch.OpenVSwitch; +import org.opendaylight.ovsdb.southbound.reconciliation.ReconciliationManager; +import org.opendaylight.ovsdb.southbound.reconciliation.ReconciliationTask; +import org.opendaylight.ovsdb.southbound.reconciliation.connection.ConnectionReconciliationTask; import org.opendaylight.ovsdb.southbound.transactions.md.OvsdbNodeRemoveCommand; +import org.opendaylight.ovsdb.southbound.transactions.md.TransactionCommand; import org.opendaylight.ovsdb.southbound.transactions.md.TransactionInvoker; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.OvsdbBridgeAttributes; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.OvsdbBridgeAugmentation; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.OvsdbNodeAugmentation; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.ovsdb.node.attributes.ConnectionInfo; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.ovsdb.node.attributes.ManagedNodeEntry; import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; @@ -73,6 +81,7 @@ public class OvsdbConnectionManager implements OvsdbConnectionListener, AutoClos private EntityOwnershipService entityOwnershipService; private OvsdbDeviceEntityOwnershipListener ovsdbDeviceEntityOwnershipListener; private OvsdbConnection ovsdbConnection; + private final ReconciliationManager reconciliationManager; public OvsdbConnectionManager(DataBroker db,TransactionInvoker txInvoker, EntityOwnershipService entityOwnershipService, @@ -82,15 +91,29 @@ public class OvsdbConnectionManager implements OvsdbConnectionListener, AutoClos this.entityOwnershipService = entityOwnershipService; this.ovsdbDeviceEntityOwnershipListener = new OvsdbDeviceEntityOwnershipListener(this, entityOwnershipService); this.ovsdbConnection = ovsdbConnection; + this.reconciliationManager = new ReconciliationManager(db); } @Override public void connected(@Nonnull final OvsdbClient externalClient) { + LOG.info("Library connected {} from {}:{} to {}:{}", + externalClient.getConnectionInfo().getType(), + externalClient.getConnectionInfo().getRemoteAddress(), + externalClient.getConnectionInfo().getRemotePort(), + externalClient.getConnectionInfo().getLocalAddress(), + externalClient.getConnectionInfo().getLocalPort()); + List databases = new ArrayList<>(); + try { + databases = externalClient.getDatabases().get(); + } catch (InterruptedException | ExecutionException e) { + LOG.warn("Unable to fetch database list"); + } - OvsdbConnectionInstance client = connectedButCallBacksNotRegistered(externalClient); - - // Register Cluster Ownership for ConnectionInfo - registerEntityForOwnership(client); + if(databases.contains(SouthboundConstants.OPEN_V_SWITCH)) { + OvsdbConnectionInstance client = connectedButCallBacksNotRegistered(externalClient); + // Register Cluster Ownership for ConnectionInfo + registerEntityForOwnership(client); + } } public OvsdbConnectionInstance connectedButCallBacksNotRegistered(final OvsdbClient externalClient) { @@ -107,12 +130,13 @@ public class OvsdbConnectionManager implements OvsdbConnectionListener, AutoClos return ovsdbConnectionInstance; } LOG.warn("OVSDB Connection Instance {} being replaced with client {}", key, externalClient); - ovsdbConnectionInstance.disconnect(); // Unregister Cluster Ownership for ConnectionInfo // Because the ovsdbConnectionInstance is about to be completely replaced! unregisterEntityForOwnership(ovsdbConnectionInstance); + ovsdbConnectionInstance.disconnect(); + removeConnectionInstance(key); } @@ -124,25 +148,42 @@ public class OvsdbConnectionManager implements OvsdbConnectionListener, AutoClos @Override public void disconnected(OvsdbClient client) { - LOG.info("OVSDB Disconnected from {}:{}. Cleaning up the operational data store" - ,client.getConnectionInfo().getRemoteAddress(), - client.getConnectionInfo().getRemotePort()); + LOG.info("Library disconnected {} from {}:{} to {}:{}. Cleaning up the operational data store", + client.getConnectionInfo().getType(), + client.getConnectionInfo().getRemoteAddress(), + client.getConnectionInfo().getRemotePort(), + client.getConnectionInfo().getLocalAddress(), + client.getConnectionInfo().getLocalPort()); ConnectionInfo key = SouthboundMapper.createConnectionInfo(client); OvsdbConnectionInstance ovsdbConnectionInstance = getConnectionInstance(key); if (ovsdbConnectionInstance != null) { + // Unregister Entity ownership as soon as possible ,so this instance should + // not be used as a candidate in Entity election (given that this instance is + // about to disconnect as well), if current owner get disconnected from + // OVSDB device. + unregisterEntityForOwnership(ovsdbConnectionInstance); + txInvoker.invoke(new OvsdbNodeRemoveCommand(ovsdbConnectionInstance, null, null)); + removeConnectionInstance(key); - // Unregister Cluster Onwership for ConnectionInfo - unregisterEntityForOwnership(ovsdbConnectionInstance); + //Controller initiated connection can be terminated from switch side. + //So cleanup the instance identifier cache. + removeInstanceIdentifier(key); + retryConnection(ovsdbConnectionInstance.getInstanceIdentifier(), + ovsdbConnectionInstance.getOvsdbNodeAugmentation(), + ConnectionReconciliationTriggers.ON_DISCONNECT); } else { - LOG.warn("OVSDB disconnected event did not find connection instance for {}", key); + LOG.warn("disconnected : Connection instance not found for OVSDB Node {} ", key); } - LOG.trace("OvsdbConnectionManager: disconnected exit"); + LOG.trace("OvsdbConnectionManager: exit disconnected client: {}", client); + } public OvsdbClient connect(InstanceIdentifier iid, OvsdbNodeAugmentation ovsdbNode) throws UnknownHostException { + LOG.info("Connecting to {}", SouthboundUtil.connectionInfoToString(ovsdbNode.getConnectionInfo())); + // TODO handle case where we already have a connection // TODO use transaction chains to handle ordering issues between disconnected // TODO and connected when writing to the operational store @@ -159,20 +200,23 @@ public class OvsdbConnectionManager implements OvsdbConnectionListener, AutoClos // Register Cluster Ownership for ConnectionInfo registerEntityForOwnership(ovsdbConnectionInstance); } else { - LOG.warn("Failed to connect to Ovsdb Node {}", ovsdbNode.getConnectionInfo()); + LOG.warn("Failed to connect to OVSDB Node {}", ovsdbNode.getConnectionInfo()); } return client; } public void disconnect(OvsdbNodeAugmentation ovsdbNode) throws UnknownHostException { + LOG.info("Disconnecting from {}", SouthboundUtil.connectionInfoToString(ovsdbNode.getConnectionInfo())); OvsdbConnectionInstance client = getConnectionInstance(ovsdbNode.getConnectionInfo()); if (client != null) { - client.disconnect(); - // Unregister Cluster Onwership for ConnectionInfo unregisterEntityForOwnership(client); + client.disconnect(); + removeInstanceIdentifier(ovsdbNode.getConnectionInfo()); + } else { + LOG.debug("disconnect : connection instance not found for {}",ovsdbNode.getConnectionInfo()); } } @@ -191,7 +235,7 @@ public class OvsdbConnectionManager implements OvsdbConnectionListener, AutoClos } */ @Override - public void close() throws Exception { + public void close() { if (ovsdbDeviceEntityOwnershipListener != null) { ovsdbDeviceEntityOwnershipListener.close(); } @@ -228,8 +272,7 @@ public class OvsdbConnectionManager implements OvsdbConnectionListener, AutoClos public InstanceIdentifier getInstanceIdentifier(ConnectionInfo key) { ConnectionInfo connectionInfo = SouthboundMapper.suppressLocalIpPort(key); - InstanceIdentifier iid = instanceIdentifiers.get(connectionInfo); - return iid; + return instanceIdentifiers.get(connectionInfo); } public OvsdbConnectionInstance getConnectionInstance(OvsdbBridgeAttributes mn) { @@ -262,7 +305,7 @@ public class OvsdbConnectionManager implements OvsdbConnectionListener, AutoClos LogicalDatastoreType.OPERATIONAL, nodePath); transaction.close(); Optional optional = nodeFuture.get(); - if (optional != null && optional.isPresent() && optional.get() instanceof Node) { + if (optional != null && optional.isPresent() && optional.get() != null) { return this.getConnectionInstance(optional.get()); } else { LOG.warn("Found non-topological node {} on path {}",optional); @@ -294,42 +337,43 @@ public class OvsdbConnectionManager implements OvsdbConnectionListener, AutoClos return ovsdbConnectionInstance.getHasDeviceOwnership(); } - public void setHasDeviceOwnership(ConnectionInfo connectionInfo, Boolean hasDeviceOwnership) { - OvsdbConnectionInstance ovsdbConnectionInstance = getConnectionInstance(connectionInfo); - if (ovsdbConnectionInstance != null) { - ovsdbConnectionInstance.setHasDeviceOwnership(hasDeviceOwnership); - } + public void reconcileConnection(InstanceIdentifier iid, OvsdbNodeAugmentation ovsdbNode){ + this.retryConnection(iid, ovsdbNode, + ConnectionReconciliationTriggers.ON_CONTROLLER_INITIATED_CONNECTION_FAILURE); + } + public void stopConnectionReconciliationIfActive(InstanceIdentifier iid, OvsdbNodeAugmentation ovsdbNode) { + final ReconciliationTask task = new ConnectionReconciliationTask( + reconciliationManager, + this, + iid, + ovsdbNode); + reconciliationManager.dequeue(task); + } private void handleOwnershipChanged(EntityOwnershipChange ownershipChange) { OvsdbConnectionInstance ovsdbConnectionInstance = getConnectionInstanceFromEntity(ownershipChange.getEntity()); - LOG.info("handleOwnershipChanged: {} event received for device {}", + LOG.debug("handleOwnershipChanged: {} event received for device {}", ownershipChange, ovsdbConnectionInstance != null ? ovsdbConnectionInstance.getConnectionInfo() - : "THAT'S NOT REGISTERED BY THIS SOUTHBOUND PLUGIN INSTANCE"); + : "that's currently NOT registered by *this* southbound plugin instance"); if (ovsdbConnectionInstance == null) { if (ownershipChange.isOwner()) { - LOG.warn("handleOwnershipChanged: found no connection instance for {}", ownershipChange.getEntity()); + LOG.warn("handleOwnershipChanged: *this* instance is elected as an owner of the device {} but it " + + "is NOT registered for ownership", ownershipChange.getEntity()); } else { // EntityOwnershipService sends notification to all the nodes, irrespective of whether // that instance registered for the device ownership or not. It is to make sure that // If all the controller instance that was connected to the device are down, so the // running instance can clear up the operational data store even though it was not // connected to the device. - LOG.debug("handleOwnershipChanged: found no connection instance for {}", ownershipChange.getEntity()); + LOG.debug("handleOwnershipChanged: No connection instance found for {}", ownershipChange.getEntity()); } // If entity has no owner, clean up the operational data store (it's possible because owner controller // might went down abruptly and didn't get a chance to clean up the operational data store. if (!ownershipChange.hasOwner()) { - LOG.debug("{} has no onwer, cleaning up the operational data store", ownershipChange.getEntity()); - // Below code might look weird but it's required. We want to give first opportunity to the - // previous owner of the device to clean up the operational data store if there is no owner now. - // That way we will avoid lot of nasty md-sal exceptions because of concurrent delete. - if (ownershipChange.wasOwner()) { - cleanEntityOperationalData(ownershipChange.getEntity()); - } - // If first cleanEntityOperationalData() was called, this call will be no-op. + LOG.info("{} has no owner, cleaning up the operational data store", ownershipChange.getEntity()); cleanEntityOperationalData(ownershipChange.getEntity()); } return; @@ -338,15 +382,17 @@ public class OvsdbConnectionManager implements OvsdbConnectionListener, AutoClos putConnectionInstance(ovsdbConnectionInstance.getMDConnectionInfo(),ovsdbConnectionInstance); if (ownershipChange.isOwner() == ovsdbConnectionInstance.getHasDeviceOwnership()) { - LOG.debug("handleOwnershipChanged: no change in ownership for {}. Ownership status is : {}", - ovsdbConnectionInstance.getConnectionInfo(), ovsdbConnectionInstance.getHasDeviceOwnership()); + LOG.info("handleOwnershipChanged: no change in ownership for {}. Ownership status is : {}", + ovsdbConnectionInstance.getConnectionInfo(), ovsdbConnectionInstance.getHasDeviceOwnership() + ? SouthboundConstants.OWNERSHIPSTATES.OWNER.getState() + : SouthboundConstants.OWNERSHIPSTATES.NONOWNER.getState()); return; } ovsdbConnectionInstance.setHasDeviceOwnership(ownershipChange.isOwner()); // You were not an owner, but now you are if (ownershipChange.isOwner()) { - LOG.info("handleOwnershipChanged: *this* southbound plugin instance is owner of device {}", + LOG.info("handleOwnershipChanged: *this* southbound plugin instance is an OWNER of the device {}", ovsdbConnectionInstance.getConnectionInfo()); //*this* instance of southbound plugin is owner of the device, @@ -361,21 +407,44 @@ public class OvsdbConnectionManager implements OvsdbConnectionListener, AutoClos //when clustering service implement a ownership grant strategy which can revoke the //device ownership for load balancing the devices across the instances. //Once this condition occur, we should unregister the callback. - LOG.error("handleOwnershipChanged: *this* southbound plugin instance is no longer the owner of device {}", + LOG.error("handleOwnershipChanged: *this* southbound plugin instance is no longer the owner of device {}." + + "This should NOT happen.", ovsdbConnectionInstance.getNodeId().getValue()); } } private void cleanEntityOperationalData(Entity entity) { - InstanceIdentifier nodeIid = (InstanceIdentifier) SouthboundUtil - .getInstanceIdentifierCodec().bindingDeserializer(entity.getId()); + //Do explicit cleanup rather than using OvsdbNodeRemoveCommand, because there + // are chances that other controller instance went down abruptly and it does + // not clear manager entry, which OvsdbNodeRemoveCommand look for before cleanup. + + @SuppressWarnings("unchecked") final InstanceIdentifier nodeIid = + (InstanceIdentifier) SouthboundUtil + .getInstanceIdentifierCodec().bindingDeserializer(entity.getId()); + + txInvoker.invoke(new TransactionCommand() { + @Override + public void execute(ReadWriteTransaction transaction) { + Optional ovsdbNodeOpt = SouthboundUtil.readNode(transaction, nodeIid); + if (ovsdbNodeOpt.isPresent()) { + Node ovsdbNode = ovsdbNodeOpt.get(); + OvsdbNodeAugmentation nodeAugmentation = ovsdbNode.getAugmentation(OvsdbNodeAugmentation.class); + if (nodeAugmentation != null) { + if (nodeAugmentation.getManagedNodeEntry() != null) { + for (ManagedNodeEntry managedNode : nodeAugmentation.getManagedNodeEntry()) { + transaction.delete( + LogicalDatastoreType.OPERATIONAL, managedNode.getBridgeRef().getValue()); + } + } else { + LOG.debug("{} had no managed nodes", ovsdbNode.getNodeId().getValue()); + } + } + transaction.delete(LogicalDatastoreType.OPERATIONAL, nodeIid); + } + } + }); - final ReadWriteTransaction transaction = db.newReadWriteTransaction(); - Optional node = SouthboundUtil.readNode(transaction, nodeIid); - if (node.isPresent()) { - SouthboundUtil.deleteNode(transaction, nodeIid); - } } private OpenVSwitch getOpenVswitchTableEntry(OvsdbConnectionInstance connectionInstance) { @@ -414,7 +483,7 @@ public class OvsdbConnectionManager implements OvsdbConnectionListener, AutoClos return openVSwitchRow; } private Entity getEntityFromConnectionInstance(@Nonnull OvsdbConnectionInstance ovsdbConnectionInstance) { - InstanceIdentifier iid = ovsdbConnectionInstance.getInstanceIdentifier();; + InstanceIdentifier iid = ovsdbConnectionInstance.getInstanceIdentifier(); if ( iid == null ) { /* Switch initiated connection won't have iid, till it gets OpenVSwitch * table update but update callback is always registered after ownership @@ -424,7 +493,7 @@ public class OvsdbConnectionManager implements OvsdbConnectionListener, AutoClos iid = SouthboundMapper.getInstanceIdentifier(openvswitchRow); LOG.info("InstanceIdentifier {} generated for device " + "connection {}",iid,ovsdbConnectionInstance.getConnectionInfo()); - + ovsdbConnectionInstance.setInstanceIdentifier(iid); } YangInstanceIdentifier entityId = SouthboundUtil.getInstanceIdentifierCodec().getYangInstanceIdentifier(iid); Entity deviceEntity = new Entity(ENTITY_TYPE, entityId); @@ -446,7 +515,7 @@ public class OvsdbConnectionManager implements OvsdbConnectionListener, AutoClos EntityOwnershipCandidateRegistration registration = entityOwnershipService.registerCandidate(candidateEntity); ovsdbConnectionInstance.setDeviceOwnershipCandidateRegistration(registration); - LOG.info("OVSDB entity {} is registred for ownership.", candidateEntity); + LOG.info("OVSDB entity {} is registered for ownership.", candidateEntity); //If entity already has owner, it won't get notification from EntityOwnershipService //so cache the connection instances. @@ -455,16 +524,14 @@ public class OvsdbConnectionManager implements OvsdbConnectionListener, AutoClos if (ownershipStateOpt.isPresent()) { EntityOwnershipState ownershipState = ownershipStateOpt.get(); if (ownershipState.hasOwner() && !ownershipState.isOwner()) { - if (getConnectionInstance(ovsdbConnectionInstance.getMDConnectionInfo()) != null) { - LOG.info("OVSDB entity {} is already owned by other southbound plugin " - + "instance, so *this* instance is NOT an OWNER of the device", - ovsdbConnectionInstance.getConnectionInfo()); - putConnectionInstance(ovsdbConnectionInstance.getMDConnectionInfo(),ovsdbConnectionInstance); - } + LOG.info("OVSDB entity {} is already owned by other southbound plugin " + + "instance, so *this* instance is NOT an OWNER of the device", + ovsdbConnectionInstance.getConnectionInfo()); + putConnectionInstance(ovsdbConnectionInstance.getMDConnectionInfo(),ovsdbConnectionInstance); } } } catch (CandidateAlreadyRegisteredException e) { - LOG.warn("OVSDB entity {} was already registered for {} ownership", candidateEntity, e); + LOG.warn("OVSDB entity {} was already registered for ownership", candidateEntity, e); } } @@ -474,6 +541,54 @@ public class OvsdbConnectionManager implements OvsdbConnectionListener, AutoClos entityConnectionMap.remove(ovsdbConnectionInstance.getConnectedEntity()); } + private void retryConnection(final InstanceIdentifier iid, final OvsdbNodeAugmentation ovsdbNode, + ConnectionReconciliationTriggers trigger) { + final ReconciliationTask task = new ConnectionReconciliationTask( + reconciliationManager, + this, + iid, + ovsdbNode); + + if(reconciliationManager.isEnqueued(task)){ + return; + } + switch(trigger){ + case ON_CONTROLLER_INITIATED_CONNECTION_FAILURE: + reconciliationManager.enqueueForRetry(task); + break; + case ON_DISCONNECT: + { + ReadOnlyTransaction tx = db.newReadOnlyTransaction(); + CheckedFuture, ReadFailedException> readNodeFuture = + tx.read(LogicalDatastoreType.CONFIGURATION, iid); + + final OvsdbConnectionManager connectionManager = this; + Futures.addCallback(readNodeFuture, new FutureCallback>() { + @Override + public void onSuccess(@Nullable Optional node) { + if (node.isPresent()) { + LOG.info("Disconnected/Failed connection {} was controller initiated, attempting " + + "reconnection", ovsdbNode.getConnectionInfo()); + reconciliationManager.enqueue(task); + + } else { + LOG.debug("Connection {} was switch initiated, no reconciliation is required" + , ovsdbNode.getConnectionInfo()); + } + } + + @Override + public void onFailure(Throwable t) { + LOG.warn("Read Config/DS for Node failed! {}", iid, t); + } + }); + break; + } + default: + break; + } + } + private class OvsdbDeviceEntityOwnershipListener implements EntityOwnershipListener { private OvsdbConnectionManager cm; private EntityOwnershipListenerRegistration listenerRegistration; @@ -490,4 +605,18 @@ public class OvsdbConnectionManager implements OvsdbConnectionListener, AutoClos cm.handleOwnershipChanged(ownershipChange); } } + + private enum ConnectionReconciliationTriggers { + /* + Reconciliation trigger for scenario where controller's attempt + to connect to switch fails on config data store notification + */ + ON_CONTROLLER_INITIATED_CONNECTION_FAILURE, + + /* + Reconciliation trigger for the scenario where controller + initiated connection disconnects. + */ + ON_DISCONNECT + } }