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