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