For controller initiated connection, there is race condition
[ovsdb.git] / southbound / southbound-impl / src / main / java / org / opendaylight / ovsdb / southbound / OvsdbConnectionManager.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
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 package org.opendaylight.ovsdb.southbound;
9
10 import static org.opendaylight.ovsdb.lib.operations.Operations.op;
11
12 import java.net.InetAddress;
13 import java.net.UnknownHostException;
14 import java.util.ArrayList;
15 import java.util.List;
16 import java.util.Map;
17 import java.util.concurrent.ConcurrentHashMap;
18 import java.util.concurrent.ExecutionException;
19
20 import javax.annotation.Nonnull;
21
22 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
23 import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
24 import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction;
25 import org.opendaylight.controller.md.sal.common.api.clustering.CandidateAlreadyRegisteredException;
26 import org.opendaylight.controller.md.sal.common.api.clustering.Entity;
27 import org.opendaylight.controller.md.sal.common.api.clustering.EntityOwnershipCandidateRegistration;
28 import org.opendaylight.controller.md.sal.common.api.clustering.EntityOwnershipChange;
29 import org.opendaylight.controller.md.sal.common.api.clustering.EntityOwnershipListener;
30 import org.opendaylight.controller.md.sal.common.api.clustering.EntityOwnershipListenerRegistration;
31 import org.opendaylight.controller.md.sal.common.api.clustering.EntityOwnershipService;
32 import org.opendaylight.controller.md.sal.common.api.clustering.EntityOwnershipState;
33 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
34 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
35 import org.opendaylight.ovsdb.lib.OvsdbClient;
36 import org.opendaylight.ovsdb.lib.OvsdbConnection;
37 import org.opendaylight.ovsdb.lib.OvsdbConnectionListener;
38 import org.opendaylight.ovsdb.lib.operations.Operation;
39 import org.opendaylight.ovsdb.lib.operations.OperationResult;
40 import org.opendaylight.ovsdb.lib.operations.Select;
41 import org.opendaylight.ovsdb.lib.schema.DatabaseSchema;
42 import org.opendaylight.ovsdb.lib.schema.GenericTableSchema;
43 import org.opendaylight.ovsdb.lib.schema.typed.TyperUtils;
44 import org.opendaylight.ovsdb.schema.openvswitch.OpenVSwitch;
45 import org.opendaylight.ovsdb.southbound.transactions.md.OvsdbNodeRemoveCommand;
46 import org.opendaylight.ovsdb.southbound.transactions.md.TransactionInvoker;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.OvsdbBridgeAttributes;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.OvsdbBridgeAugmentation;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.OvsdbNodeAugmentation;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.ovsdb.node.attributes.ConnectionInfo;
51 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
52 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
53 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
56
57 import com.google.common.base.Optional;
58 import com.google.common.base.Preconditions;
59 import com.google.common.util.concurrent.CheckedFuture;
60
61 public class OvsdbConnectionManager implements OvsdbConnectionListener, AutoCloseable {
62     private Map<ConnectionInfo, OvsdbConnectionInstance> clients =
63             new ConcurrentHashMap<>();
64     private static final Logger LOG = LoggerFactory.getLogger(OvsdbConnectionManager.class);
65     private static final String ENTITY_TYPE = "ovsdb";
66
67     private DataBroker db;
68     private TransactionInvoker txInvoker;
69     private Map<ConnectionInfo,InstanceIdentifier<Node>> instanceIdentifiers =
70             new ConcurrentHashMap<>();
71     private Map<Entity, OvsdbConnectionInstance> entityConnectionMap =
72             new ConcurrentHashMap<>();
73     private EntityOwnershipService entityOwnershipService;
74     private OvsdbDeviceEntityOwnershipListener ovsdbDeviceEntityOwnershipListener;
75     private OvsdbConnection ovsdbConnection;
76
77     public OvsdbConnectionManager(DataBroker db,TransactionInvoker txInvoker,
78                                   EntityOwnershipService entityOwnershipService,
79                                   OvsdbConnection ovsdbConnection) {
80         this.db = db;
81         this.txInvoker = txInvoker;
82         this.entityOwnershipService = entityOwnershipService;
83         this.ovsdbDeviceEntityOwnershipListener = new OvsdbDeviceEntityOwnershipListener(this, entityOwnershipService);
84         this.ovsdbConnection = ovsdbConnection;
85     }
86
87     @Override
88     public void connected(@Nonnull final OvsdbClient externalClient) {
89
90         OvsdbConnectionInstance client = connectedButCallBacksNotRegistered(externalClient);
91
92         // Register Cluster Ownership for ConnectionInfo
93         registerEntityForOwnership(client);
94     }
95
96     public OvsdbConnectionInstance connectedButCallBacksNotRegistered(final OvsdbClient externalClient) {
97         LOG.info("OVSDB Connection from {}:{}",externalClient.getConnectionInfo().getRemoteAddress(),
98                 externalClient.getConnectionInfo().getRemotePort());
99         ConnectionInfo key = SouthboundMapper.createConnectionInfo(externalClient);
100         OvsdbConnectionInstance ovsdbConnectionInstance = getConnectionInstance(key);
101
102         // Check if existing ovsdbConnectionInstance for the OvsdbClient present.
103         // In such cases, we will see if the ovsdbConnectionInstance has same externalClient.
104         if (ovsdbConnectionInstance != null) {
105             if (ovsdbConnectionInstance.hasOvsdbClient(externalClient)) {
106                 LOG.warn("OVSDB Connection Instance {} already exists for client {}", key, externalClient);
107                 return ovsdbConnectionInstance;
108             }
109             LOG.warn("OVSDB Connection Instance {} being replaced with client {}", key, externalClient);
110
111             // Unregister Cluster Ownership for ConnectionInfo
112             // Because the ovsdbConnectionInstance is about to be completely replaced!
113             unregisterEntityForOwnership(ovsdbConnectionInstance);
114
115             ovsdbConnectionInstance.disconnect();
116
117             removeConnectionInstance(key);
118         }
119
120         ovsdbConnectionInstance = new OvsdbConnectionInstance(key, externalClient, txInvoker,
121                 getInstanceIdentifier(key));
122         ovsdbConnectionInstance.createTransactInvokers();
123         return ovsdbConnectionInstance;
124     }
125
126     @Override
127     public void disconnected(OvsdbClient client) {
128         LOG.info("OVSDB Disconnected from {}:{}. Cleaning up the operational data store"
129                 ,client.getConnectionInfo().getRemoteAddress(),
130                 client.getConnectionInfo().getRemotePort());
131         ConnectionInfo key = SouthboundMapper.createConnectionInfo(client);
132         OvsdbConnectionInstance ovsdbConnectionInstance = getConnectionInstance(key);
133         if (ovsdbConnectionInstance != null) {
134             // Unregister Entity ownership as soon as possible ,so this instance should
135             // not be used as a candidate in Entity election (given that this instance is
136             // about to disconnect as well), if current owner get disconnected from
137             // OVSDB device.
138             unregisterEntityForOwnership(ovsdbConnectionInstance);
139
140             txInvoker.invoke(new OvsdbNodeRemoveCommand(ovsdbConnectionInstance, null, null));
141
142             removeConnectionInstance(key);
143         } else {
144             LOG.warn("disconnected : Connection instance not found for OVSDB Node {} ", key);
145         }
146         LOG.trace("OvsdbConnectionManager: disconnected exit");
147     }
148
149     public OvsdbClient connect(InstanceIdentifier<Node> iid,
150             OvsdbNodeAugmentation ovsdbNode) throws UnknownHostException {
151         // TODO handle case where we already have a connection
152         // TODO use transaction chains to handle ordering issues between disconnected
153         // TODO and connected when writing to the operational store
154         InetAddress ip = SouthboundMapper.createInetAddress(ovsdbNode.getConnectionInfo().getRemoteIp());
155         OvsdbClient client = ovsdbConnection.connect(ip,
156                 ovsdbNode.getConnectionInfo().getRemotePort().getValue());
157         // For connections from the controller to the ovs instance, the library doesn't call
158         // this method for us
159         if (client != null) {
160             putInstanceIdentifier(ovsdbNode.getConnectionInfo(), iid.firstIdentifierOf(Node.class));
161             OvsdbConnectionInstance ovsdbConnectionInstance = connectedButCallBacksNotRegistered(client);
162             ovsdbConnectionInstance.setOvsdbNodeAugmentation(ovsdbNode);
163
164             // Register Cluster Ownership for ConnectionInfo
165             registerEntityForOwnership(ovsdbConnectionInstance);
166         } else {
167             LOG.warn("Failed to connect to OVSDB Node {}", ovsdbNode.getConnectionInfo());
168         }
169         return client;
170     }
171
172     public void disconnect(OvsdbNodeAugmentation ovsdbNode) throws UnknownHostException {
173         OvsdbConnectionInstance client = getConnectionInstance(ovsdbNode.getConnectionInfo());
174         if (client != null) {
175             // Unregister Cluster Onwership for ConnectionInfo
176             unregisterEntityForOwnership(client);
177
178             client.disconnect();
179
180             removeInstanceIdentifier(ovsdbNode.getConnectionInfo());
181         } else {
182             LOG.debug("disconnect : connection instance not found for {}",ovsdbNode.getConnectionInfo());
183         }
184     }
185
186 /*    public void init(ConnectionInfo key) {
187         OvsdbConnectionInstance client = getConnectionInstance(key);
188
189         // TODO (FF): make sure that this cluster instance is the 'entity owner' fo the given OvsdbConnectionInstance ?
190
191         if (client != null) {
192
193              *  Note: registerCallbacks() is idemPotent... so if you call it repeatedly all is safe,
194              *  it only registersCallbacks on the *first* call.
195
196             client.registerCallbacks();
197         }
198     }
199 */
200     @Override
201     public void close() throws Exception {
202         if (ovsdbDeviceEntityOwnershipListener != null) {
203             ovsdbDeviceEntityOwnershipListener.close();
204         }
205
206         for (OvsdbClient client: clients.values()) {
207             client.disconnect();
208         }
209     }
210
211     private void putConnectionInstance(ConnectionInfo key,OvsdbConnectionInstance instance) {
212         ConnectionInfo connectionInfo = SouthboundMapper.suppressLocalIpPort(key);
213         clients.put(connectionInfo, instance);
214     }
215
216     private void removeConnectionInstance(ConnectionInfo key) {
217         ConnectionInfo connectionInfo = SouthboundMapper.suppressLocalIpPort(key);
218         clients.remove(connectionInfo);
219     }
220
221     private void putInstanceIdentifier(ConnectionInfo key,InstanceIdentifier<Node> iid) {
222         ConnectionInfo connectionInfo = SouthboundMapper.suppressLocalIpPort(key);
223         instanceIdentifiers.put(connectionInfo, iid);
224     }
225
226     private void removeInstanceIdentifier(ConnectionInfo key) {
227         ConnectionInfo connectionInfo = SouthboundMapper.suppressLocalIpPort(key);
228         instanceIdentifiers.remove(connectionInfo);
229     }
230
231     public OvsdbConnectionInstance getConnectionInstance(ConnectionInfo key) {
232         ConnectionInfo connectionInfo = SouthboundMapper.suppressLocalIpPort(key);
233         return clients.get(connectionInfo);
234     }
235
236     public InstanceIdentifier<Node> getInstanceIdentifier(ConnectionInfo key) {
237         ConnectionInfo connectionInfo = SouthboundMapper.suppressLocalIpPort(key);
238         return instanceIdentifiers.get(connectionInfo);
239     }
240
241     public OvsdbConnectionInstance getConnectionInstance(OvsdbBridgeAttributes mn) {
242         Optional<OvsdbNodeAugmentation> optional = SouthboundUtil.getManagingNode(db, mn);
243         if (optional.isPresent()) {
244             return getConnectionInstance(optional.get().getConnectionInfo());
245         } else {
246             return null;
247         }
248     }
249
250     public OvsdbConnectionInstance getConnectionInstance(Node node) {
251         Preconditions.checkNotNull(node);
252         OvsdbNodeAugmentation ovsdbNode = node.getAugmentation(OvsdbNodeAugmentation.class);
253         OvsdbBridgeAugmentation ovsdbManagedNode = node.getAugmentation(OvsdbBridgeAugmentation.class);
254         if (ovsdbNode != null) {
255             return getConnectionInstance(ovsdbNode.getConnectionInfo());
256         } else if (ovsdbManagedNode != null) {
257             return getConnectionInstance(ovsdbManagedNode);
258         } else {
259             LOG.warn("This is not a node that gives any hint how to find its OVSDB Manager: {}",node);
260             return null;
261         }
262     }
263
264     public OvsdbConnectionInstance getConnectionInstance(InstanceIdentifier<Node> nodePath) {
265         try {
266             ReadOnlyTransaction transaction = db.newReadOnlyTransaction();
267             CheckedFuture<Optional<Node>, ReadFailedException> nodeFuture = transaction.read(
268                     LogicalDatastoreType.OPERATIONAL, nodePath);
269             transaction.close();
270             Optional<Node> optional = nodeFuture.get();
271             if (optional != null && optional.isPresent() && optional.get() instanceof Node) {
272                 return this.getConnectionInstance(optional.get());
273             } else {
274                 LOG.warn("Found non-topological node {} on path {}",optional);
275                 return null;
276             }
277         } catch (Exception e) {
278             LOG.warn("Failed to get Ovsdb Node {}",nodePath, e);
279             return null;
280         }
281     }
282
283     public OvsdbClient getClient(ConnectionInfo connectionInfo) {
284         return getConnectionInstance(connectionInfo);
285     }
286
287     public OvsdbClient getClient(OvsdbBridgeAttributes mn) {
288         return getConnectionInstance(mn);
289     }
290
291     public OvsdbClient getClient(Node node) {
292         return getConnectionInstance(node);
293     }
294
295     public Boolean getHasDeviceOwnership(ConnectionInfo connectionInfo) {
296         OvsdbConnectionInstance ovsdbConnectionInstance = getConnectionInstance(connectionInfo);
297         if (ovsdbConnectionInstance == null) {
298             return Boolean.FALSE;
299         }
300         return ovsdbConnectionInstance.getHasDeviceOwnership();
301     }
302
303     public void setHasDeviceOwnership(ConnectionInfo connectionInfo, Boolean hasDeviceOwnership) {
304         OvsdbConnectionInstance ovsdbConnectionInstance = getConnectionInstance(connectionInfo);
305         if (ovsdbConnectionInstance != null) {
306             ovsdbConnectionInstance.setHasDeviceOwnership(hasDeviceOwnership);
307         }
308     }
309
310     private void handleOwnershipChanged(EntityOwnershipChange ownershipChange) {
311         OvsdbConnectionInstance ovsdbConnectionInstance = getConnectionInstanceFromEntity(ownershipChange.getEntity());
312         LOG.debug("handleOwnershipChanged: {} event received for device {}",
313                 ownershipChange, ovsdbConnectionInstance != null ? ovsdbConnectionInstance.getConnectionInfo()
314                         : "that's currently NOT registered by *this* southbound plugin instance");
315
316         if (ovsdbConnectionInstance == null) {
317             if (ownershipChange.isOwner()) {
318                 LOG.warn("handleOwnershipChanged: *this* instance is elected as an owner of the device {} but it "
319                         + "is NOT registered for ownership", ownershipChange.getEntity());
320             } else {
321                 // EntityOwnershipService sends notification to all the nodes, irrespective of whether
322                 // that instance registered for the device ownership or not. It is to make sure that
323                 // If all the controller instance that was connected to the device are down, so the
324                 // running instance can clear up the operational data store even though it was not
325                 // connected to the device.
326                 LOG.debug("handleOwnershipChanged: No connection instance found for {}", ownershipChange.getEntity());
327             }
328
329             // If entity has no owner, clean up the operational data store (it's possible because owner controller
330             // might went down abruptly and didn't get a chance to clean up the operational data store.
331             if (!ownershipChange.hasOwner()) {
332                 LOG.info("{} has no owner, cleaning up the operational data store", ownershipChange.getEntity());
333                 cleanEntityOperationalData(ownershipChange.getEntity());
334             }
335             return;
336         }
337         //Connection detail need to be cached, irrespective of ownership result.
338         putConnectionInstance(ovsdbConnectionInstance.getMDConnectionInfo(),ovsdbConnectionInstance);
339
340         if (ownershipChange.isOwner() == ovsdbConnectionInstance.getHasDeviceOwnership()) {
341             LOG.info("handleOwnershipChanged: no change in ownership for {}. Ownership status is : {}",
342                     ovsdbConnectionInstance.getConnectionInfo(), ovsdbConnectionInstance.getHasDeviceOwnership()
343                             ? SouthboundConstants.OWNERSHIPSTATES.OWNER.getState()
344                             : SouthboundConstants.OWNERSHIPSTATES.NONOWNER.getState());
345             return;
346         }
347
348         ovsdbConnectionInstance.setHasDeviceOwnership(ownershipChange.isOwner());
349         // You were not an owner, but now you are
350         if (ownershipChange.isOwner()) {
351             LOG.info("handleOwnershipChanged: *this* southbound plugin instance is an OWNER of the device {}",
352                     ovsdbConnectionInstance.getConnectionInfo());
353
354             //*this* instance of southbound plugin is owner of the device,
355             //so register for monitor callbacks
356             ovsdbConnectionInstance.registerCallbacks();
357
358         } else {
359             //You were owner of the device, but now you are not. With the current ownership
360             //grant mechanism, this scenario should not occur. Because this scenario will occur
361             //when this controller went down or switch flap the connection, but in both the case
362             //it will go through the re-registration process. We need to implement this condition
363             //when clustering service implement a ownership grant strategy which can revoke the
364             //device ownership for load balancing the devices across the instances.
365             //Once this condition occur, we should unregister the callback.
366             LOG.error("handleOwnershipChanged: *this* southbound plugin instance is no longer the owner of device {}."
367                     + "This should NOT happen.",
368                     ovsdbConnectionInstance.getNodeId().getValue());
369         }
370     }
371
372     private void cleanEntityOperationalData(Entity entity) {
373
374         InstanceIdentifier<Node> nodeIid = (InstanceIdentifier<Node>) SouthboundUtil
375                 .getInstanceIdentifierCodec().bindingDeserializer(entity.getId());
376
377         final ReadWriteTransaction transaction = db.newReadWriteTransaction();
378         Optional<Node> node = SouthboundUtil.readNode(transaction, nodeIid);
379         if (node.isPresent()) {
380             SouthboundUtil.deleteNode(transaction, nodeIid);
381         }
382     }
383
384     private OpenVSwitch getOpenVswitchTableEntry(OvsdbConnectionInstance connectionInstance) {
385         DatabaseSchema dbSchema = null;
386         OpenVSwitch openVSwitchRow = null;
387         try {
388             dbSchema = connectionInstance.getSchema(OvsdbSchemaContants.databaseName).get();
389         } catch (InterruptedException | ExecutionException e) {
390             LOG.warn("Not able to fetch schema for database {} from device {}",
391                     OvsdbSchemaContants.databaseName,connectionInstance.getConnectionInfo(),e);
392         }
393         if (dbSchema != null) {
394             GenericTableSchema openVSwitchSchema = TyperUtils.getTableSchema(dbSchema, OpenVSwitch.class);
395
396             List<String> openVSwitchTableColumn = new ArrayList<>();
397             openVSwitchTableColumn.addAll(openVSwitchSchema.getColumns());
398             Select<GenericTableSchema> selectOperation = op.select(openVSwitchSchema);
399             selectOperation.setColumns(openVSwitchTableColumn);
400
401             List<Operation> operations = new ArrayList<>();
402             operations.add(selectOperation);
403             operations.add(op.comment("Fetching Open_VSwitch table rows"));
404             try {
405                 List<OperationResult> results = connectionInstance.transact(dbSchema, operations).get();
406                 if (results != null ) {
407                     OperationResult selectResult = results.get(0);
408                     openVSwitchRow = TyperUtils.getTypedRowWrapper(
409                             dbSchema,OpenVSwitch.class,selectResult.getRows().get(0));
410
411                 }
412             } catch (InterruptedException | ExecutionException e) {
413                 LOG.warn("Not able to fetch OpenVswitch table row from device {}",
414                         connectionInstance.getConnectionInfo(),e);
415             }
416         }
417         return openVSwitchRow;
418     }
419     private Entity getEntityFromConnectionInstance(@Nonnull OvsdbConnectionInstance ovsdbConnectionInstance) {
420         InstanceIdentifier<Node> iid = ovsdbConnectionInstance.getInstanceIdentifier();
421         if ( iid == null ) {
422             /* Switch initiated connection won't have iid, till it gets OpenVSwitch
423              * table update but update callback is always registered after ownership
424              * is granted. So we are explicitly fetch the row here to get the iid.
425              */
426             OpenVSwitch openvswitchRow = getOpenVswitchTableEntry(ovsdbConnectionInstance);
427             iid = SouthboundMapper.getInstanceIdentifier(openvswitchRow);
428             LOG.info("InstanceIdentifier {} generated for device "
429                     + "connection {}",iid,ovsdbConnectionInstance.getConnectionInfo());
430             ovsdbConnectionInstance.setInstanceIdentifier(iid);
431         }
432         YangInstanceIdentifier entityId = SouthboundUtil.getInstanceIdentifierCodec().getYangInstanceIdentifier(iid);
433         Entity deviceEntity = new Entity(ENTITY_TYPE, entityId);
434         LOG.debug("Entity {} created for device connection {}",
435                 deviceEntity, ovsdbConnectionInstance.getConnectionInfo());
436         return deviceEntity;
437     }
438
439     private OvsdbConnectionInstance getConnectionInstanceFromEntity(Entity entity) {
440         return entityConnectionMap.get(entity);
441     }
442
443     private void registerEntityForOwnership(OvsdbConnectionInstance ovsdbConnectionInstance) {
444
445         Entity candidateEntity = getEntityFromConnectionInstance(ovsdbConnectionInstance);
446         entityConnectionMap.put(candidateEntity, ovsdbConnectionInstance);
447         ovsdbConnectionInstance.setConnectedEntity(candidateEntity);
448         try {
449             EntityOwnershipCandidateRegistration registration =
450                     entityOwnershipService.registerCandidate(candidateEntity);
451             ovsdbConnectionInstance.setDeviceOwnershipCandidateRegistration(registration);
452             LOG.info("OVSDB entity {} is registered for ownership.", candidateEntity);
453
454             //If entity already has owner, it won't get notification from EntityOwnershipService
455             //so cache the connection instances.
456             Optional<EntityOwnershipState> ownershipStateOpt =
457                     entityOwnershipService.getOwnershipState(candidateEntity);
458             if (ownershipStateOpt.isPresent()) {
459                 EntityOwnershipState ownershipState = ownershipStateOpt.get();
460                 if (ownershipState.hasOwner() && !ownershipState.isOwner()) {
461                     LOG.info("OVSDB entity {} is already owned by other southbound plugin "
462                                     + "instance, so *this* instance is NOT an OWNER of the device",
463                             ovsdbConnectionInstance.getConnectionInfo());
464                     putConnectionInstance(ovsdbConnectionInstance.getMDConnectionInfo(),ovsdbConnectionInstance);
465                 }
466             }
467         } catch (CandidateAlreadyRegisteredException e) {
468             LOG.warn("OVSDB entity {} was already registered for ownership", candidateEntity, e);
469         }
470
471     }
472
473     private void unregisterEntityForOwnership(OvsdbConnectionInstance ovsdbConnectionInstance) {
474         ovsdbConnectionInstance.closeDeviceOwnershipCandidateRegistration();
475         entityConnectionMap.remove(ovsdbConnectionInstance.getConnectedEntity());
476     }
477
478     private class OvsdbDeviceEntityOwnershipListener implements EntityOwnershipListener {
479         private OvsdbConnectionManager cm;
480         private EntityOwnershipListenerRegistration listenerRegistration;
481
482         OvsdbDeviceEntityOwnershipListener(OvsdbConnectionManager cm, EntityOwnershipService entityOwnershipService) {
483             this.cm = cm;
484             listenerRegistration = entityOwnershipService.registerListener(ENTITY_TYPE, this);
485         }
486         public void close() {
487             listenerRegistration.close();
488         }
489         @Override
490         public void ownershipChanged(EntityOwnershipChange ownershipChange) {
491             cm.handleOwnershipChanged(ownershipChange);
492         }
493     }
494 }