Initial implementation for hwvtepsouthbound
[ovsdb.git] / hwvtepsouthbound / hwvtepsouthbound-impl / src / main / java / org / opendaylight / ovsdb / hwvtepsouthbound / HwvtepConnectionManager.java
1 /*
2  * Copyright (c) 2015 Ericsson India Global Services Pvt Ltd. 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
9 package org.opendaylight.ovsdb.hwvtepsouthbound;
10
11 import static org.opendaylight.ovsdb.lib.operations.Operations.op;
12
13 import java.util.ArrayList;
14 import java.util.List;
15 import java.util.Map;
16 import java.util.concurrent.ConcurrentHashMap;
17 import java.util.concurrent.ExecutionException;
18
19 import javax.annotation.Nonnull;
20
21 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
22 import org.opendaylight.controller.md.sal.common.api.clustering.CandidateAlreadyRegisteredException;
23 import org.opendaylight.controller.md.sal.common.api.clustering.Entity;
24 import org.opendaylight.controller.md.sal.common.api.clustering.EntityOwnershipCandidateRegistration;
25 import org.opendaylight.controller.md.sal.common.api.clustering.EntityOwnershipChange;
26 import org.opendaylight.controller.md.sal.common.api.clustering.EntityOwnershipListener;
27 import org.opendaylight.controller.md.sal.common.api.clustering.EntityOwnershipListenerRegistration;
28 import org.opendaylight.controller.md.sal.common.api.clustering.EntityOwnershipService;
29 import org.opendaylight.controller.md.sal.common.api.clustering.EntityOwnershipState;
30 import org.opendaylight.ovsdb.hwvtepsouthbound.transactions.md.TransactionInvoker;
31 import org.opendaylight.ovsdb.lib.OvsdbClient;
32 import org.opendaylight.ovsdb.lib.OvsdbConnectionListener;
33 import org.opendaylight.ovsdb.lib.operations.Operation;
34 import org.opendaylight.ovsdb.lib.operations.OperationResult;
35 import org.opendaylight.ovsdb.lib.operations.Select;
36 import org.opendaylight.ovsdb.lib.schema.DatabaseSchema;
37 import org.opendaylight.ovsdb.lib.schema.GenericTableSchema;
38 import org.opendaylight.ovsdb.lib.schema.typed.TyperUtils;
39 import org.opendaylight.ovsdb.schema.hardwarevtep.Global;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.hwvtep.rev150901.hwvtep.global.attributes.ConnectionInfo;
41 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
42 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
43 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 import com.google.common.base.Optional;
48
49 public class HwvtepConnectionManager implements OvsdbConnectionListener, AutoCloseable{
50     private Map<ConnectionInfo, HwvtepConnectionInstance> clients =
51                     new ConcurrentHashMap<ConnectionInfo,HwvtepConnectionInstance>();
52     private static final Logger LOG = LoggerFactory.getLogger(HwvtepConnectionManager.class);
53     private static final String ENTITY_TYPE = "hwvtep";
54
55     private DataBroker db;
56     private TransactionInvoker txInvoker;
57     private Map<ConnectionInfo,InstanceIdentifier<Node>> instanceIdentifiers =
58                     new ConcurrentHashMap<ConnectionInfo,InstanceIdentifier<Node>>();
59     private Map<Entity, HwvtepConnectionInstance> entityConnectionMap =
60                     new ConcurrentHashMap<>();
61     private EntityOwnershipService entityOwnershipService;
62     private HwvtepDeviceEntityOwnershipListener hwvtepDeviceEntityOwnershipListener;
63
64     public HwvtepConnectionManager(DataBroker db, TransactionInvoker txInvoker,
65                     EntityOwnershipService entityOwnershipService) {
66         this.db = db;
67         this.txInvoker = txInvoker;
68         this.entityOwnershipService = entityOwnershipService;
69         this.hwvtepDeviceEntityOwnershipListener = new HwvtepDeviceEntityOwnershipListener(this,entityOwnershipService);
70     }
71
72     @Override
73     public void close() throws Exception {
74         if (hwvtepDeviceEntityOwnershipListener != null) {
75             hwvtepDeviceEntityOwnershipListener.close();
76         }
77
78         for (OvsdbClient client: clients.values()) {
79             client.disconnect();
80         }
81     }
82
83     @Override
84     public void connected(@Nonnull final OvsdbClient client) {
85         HwvtepConnectionInstance hwClient = connectedButCallBacksNotRegistered(client);
86         registerEntityForOwnership(hwClient);
87         LOG.trace("connected client: {}", hwClient);
88     }
89
90     @Override
91     public void disconnected(OvsdbClient client) {
92         LOG.info("HWVTEP Disconnected from {}:{}. Cleaning up the operational data store"
93                         ,client.getConnectionInfo().getRemoteAddress(),
94                         client.getConnectionInfo().getRemotePort());
95         ConnectionInfo key = HwvtepSouthboundMapper.createConnectionInfo(client);
96         HwvtepConnectionInstance hwvtepConnectionInstance = getConnectionInstance(key);
97         if (hwvtepConnectionInstance != null) {
98             //TODO: txInvoker.invoke(new HwvtepNodeRemoveCommand(hwvtepConnectionInstance, null, null));
99             removeConnectionInstance(key);
100
101             // Unregister Cluster Ownership for ConnectionInfo
102             unregisterEntityForOwnership(hwvtepConnectionInstance);
103         } else {
104             LOG.warn("HWVTEP disconnected event did not find connection instance for {}", key);
105         }
106         LOG.trace("disconnected client: {}", client);
107     }
108
109     public HwvtepConnectionInstance connectedButCallBacksNotRegistered(final OvsdbClient externalClient) {
110         LOG.info("OVSDB Connection from {}:{}",externalClient.getConnectionInfo().getRemoteAddress(),
111                 externalClient.getConnectionInfo().getRemotePort());
112         ConnectionInfo key = HwvtepSouthboundMapper.createConnectionInfo(externalClient);
113         HwvtepConnectionInstance hwvtepConnectionInstance = getConnectionInstance(key);
114
115         // Check if existing hwvtepConnectionInstance for the OvsdbClient present.
116         // In such cases, we will see if the hwvtepConnectionInstance has same externalClient.
117         if (hwvtepConnectionInstance != null) {
118             if (hwvtepConnectionInstance.hasOvsdbClient(externalClient)) {
119                 LOG.warn("HWVTEP Connection Instance {} already exists for client {}", key, externalClient);
120                 return hwvtepConnectionInstance;
121             }
122             LOG.warn("HWVTEP Connection Instance {} being replaced with client {}", key, externalClient);
123             hwvtepConnectionInstance.disconnect();
124
125             // Unregister Cluster Ownership for ConnectionInfo
126             // Because the hwvtepConnectionInstance is about to be completely replaced!
127             unregisterEntityForOwnership(hwvtepConnectionInstance);
128
129             removeConnectionInstance(key);
130         }
131
132         hwvtepConnectionInstance = new HwvtepConnectionInstance(key, externalClient, getInstanceIdentifier(key),
133                 txInvoker);
134         hwvtepConnectionInstance.createTransactInvokers();
135         return hwvtepConnectionInstance;
136     }
137
138     /* TODO:
139     public OvsdbClient connect(InstanceIdentifier<Node> iid, OvsdbNodeAugmentation ovsdbNode)
140                     throws UnknownHostException {
141     }
142
143     public void disconnect(OvsdbNodeAugmentation ovsdbNode) throws UnknownHostException {
144         OvsdbConnectionInstance client = getConnectionInstance(ovsdbNode.getConnectionInfo());
145
146     }
147
148     */
149
150     private void putConnectionInstance(ConnectionInfo key,HwvtepConnectionInstance instance) {
151         ConnectionInfo connectionInfo = HwvtepSouthboundMapper.suppressLocalIpPort(key);
152         clients.put(connectionInfo, instance);
153         LOG.info("Clients after put: {}", clients);
154     }
155
156     public HwvtepConnectionInstance getConnectionInstance(ConnectionInfo key) {
157         ConnectionInfo connectionInfo = HwvtepSouthboundMapper.suppressLocalIpPort(key);
158         return clients.get(connectionInfo);
159     }
160
161     private void removeConnectionInstance(ConnectionInfo key) {
162         ConnectionInfo connectionInfo = HwvtepSouthboundMapper.suppressLocalIpPort(key);
163         clients.remove(connectionInfo);
164         LOG.info("Clients after remove: {}", clients);
165     }
166
167     private void putInstanceIdentifier(ConnectionInfo key,InstanceIdentifier<Node> iid) {
168         ConnectionInfo connectionInfo = HwvtepSouthboundMapper.suppressLocalIpPort(key);
169         instanceIdentifiers.put(connectionInfo, iid);
170     }
171
172     public InstanceIdentifier<Node> getInstanceIdentifier(ConnectionInfo key) {
173         ConnectionInfo connectionInfo = HwvtepSouthboundMapper.suppressLocalIpPort(key);
174         InstanceIdentifier<Node> iid = instanceIdentifiers.get(connectionInfo);
175         return iid;
176     }
177
178     private void removeInstanceIdentifier(ConnectionInfo key) {
179         ConnectionInfo connectionInfo = HwvtepSouthboundMapper.suppressLocalIpPort(key);
180         instanceIdentifiers.remove(connectionInfo);
181     }
182
183     private void registerEntityForOwnership(HwvtepConnectionInstance hwvtepConnectionInstance) {
184
185         Entity candidateEntity = getEntityFromConnectionInstance(hwvtepConnectionInstance);
186         entityConnectionMap.put(candidateEntity, hwvtepConnectionInstance);
187         hwvtepConnectionInstance.setConnectedEntity(candidateEntity);
188
189         try {
190             EntityOwnershipCandidateRegistration registration =
191                     entityOwnershipService.registerCandidate(candidateEntity);
192             hwvtepConnectionInstance.setDeviceOwnershipCandidateRegistration(registration);
193             LOG.info("HWVTEP entity {} is registered for ownership.", candidateEntity);
194
195             //If entity already has owner, it won't get notification from EntityOwnershipService
196             //so cache the connection instances.
197             Optional<EntityOwnershipState> ownershipStateOpt =
198                     entityOwnershipService.getOwnershipState(candidateEntity);
199             if (ownershipStateOpt.isPresent()) {
200                 EntityOwnershipState ownershipState = ownershipStateOpt.get();
201                 if (ownershipState.hasOwner() && !ownershipState.isOwner()) {
202                     if (getConnectionInstance(hwvtepConnectionInstance.getMDConnectionInfo()) != null) {
203                         LOG.info("OVSDB entity {} is already owned by other southbound plugin "
204                                 + "instance, so *this* instance is NOT an OWNER of the device",
205                                 hwvtepConnectionInstance.getConnectionInfo());
206                         putConnectionInstance(hwvtepConnectionInstance.getMDConnectionInfo(),hwvtepConnectionInstance);
207                     }
208                 }
209             }
210         } catch (CandidateAlreadyRegisteredException e) {
211             LOG.warn("OVSDB entity {} was already registered for {} ownership", candidateEntity, e);
212         }
213
214     }
215
216     private Global getHwvtepGlobalTableEntry(HwvtepConnectionInstance connectionInstance) {
217         DatabaseSchema dbSchema = null;
218         Global globalRow = null;
219
220         try {
221             dbSchema = connectionInstance.getSchema(HwvtepSchemaConstants.databaseName).get();
222         } catch (InterruptedException | ExecutionException e) {
223             LOG.warn("Not able to fetch schema for database {} from device {}",
224                     HwvtepSchemaConstants.databaseName,connectionInstance.getConnectionInfo(),e);
225         }
226
227         if (dbSchema != null) {
228             GenericTableSchema hwvtepSchema = TyperUtils.getTableSchema(dbSchema, Global.class);
229
230             List<String> hwvtepTableColumn = new ArrayList<String>();
231             hwvtepTableColumn.addAll(hwvtepSchema.getColumns());
232             Select<GenericTableSchema> selectOperation = op.select(hwvtepSchema);
233             selectOperation.setColumns(hwvtepTableColumn);;
234
235             ArrayList<Operation> operations = new ArrayList<Operation>();
236             operations.add(selectOperation);
237             operations.add(op.comment("Fetching hardware_vtep table rows"));
238
239             List<OperationResult> results = null;
240             try {
241                 results = connectionInstance.transact(dbSchema, operations).get();
242                 if (results != null ) {
243                     OperationResult selectResult = results.get(0);
244                     globalRow = TyperUtils.getTypedRowWrapper(
245                             dbSchema,Global.class,selectResult.getRows().get(0));
246                 }
247             } catch (InterruptedException | ExecutionException e) {
248                 LOG.warn("Not able to fetch hardware_vtep table row from device {}",
249                         connectionInstance.getConnectionInfo(),e);
250             }
251         }
252         LOG.trace("Fetched global {} from hardware_vtep schema",globalRow);
253         return globalRow;
254     }
255
256     private Entity getEntityFromConnectionInstance(@Nonnull HwvtepConnectionInstance hwvtepConnectionInstance) {
257         YangInstanceIdentifier entityId = null;
258         InstanceIdentifier<Node> iid = hwvtepConnectionInstance.getInstanceIdentifier();;
259         if ( iid == null ) {
260             //TODO: Is Global the right one?
261             Global hwvtepGlobalRow = getHwvtepGlobalTableEntry(hwvtepConnectionInstance);
262             iid = HwvtepSouthboundMapper.getInstanceIdentifier(hwvtepGlobalRow);
263             LOG.info("InstanceIdentifier {} generated for device "
264                     + "connection {}",iid, hwvtepConnectionInstance.getConnectionInfo());
265
266         }
267         entityId = HwvtepSouthboundUtil.getInstanceIdentifierCodec().getYangInstanceIdentifier(iid);
268         Entity deviceEntity = new Entity(ENTITY_TYPE, entityId);
269         LOG.debug("Entity {} created for device connection {}",
270                 deviceEntity, hwvtepConnectionInstance.getConnectionInfo());
271         return deviceEntity;
272     }
273     private void unregisterEntityForOwnership(HwvtepConnectionInstance hwvtepConnectionInstance) {
274         hwvtepConnectionInstance.closeDeviceOwnershipCandidateRegistration();
275         entityConnectionMap.remove(hwvtepConnectionInstance.getConnectedEntity());
276     }
277
278     public void handleOwnershipChanged(EntityOwnershipChange ownershipChange) {
279         HwvtepConnectionInstance hwvtepConnectionInstance = getConnectionInstanceFromEntity(ownershipChange.getEntity());
280         LOG.info("handleOwnershipChanged: {} event received for device {}",
281                 ownershipChange, hwvtepConnectionInstance != null ? hwvtepConnectionInstance.getConnectionInfo()
282                         : "THAT'S NOT REGISTERED BY THIS SOUTHBOUND PLUGIN INSTANCE");
283
284         if (hwvtepConnectionInstance == null) {
285             if (ownershipChange.isOwner()) {
286                 LOG.warn("handleOwnershipChanged: found no connection instance for {}", ownershipChange.getEntity());
287             } else {
288                 // EntityOwnershipService sends notification to all the nodes, irrespective of whether
289                 // that instance registered for the device ownership or not. It is to make sure that
290                 // If all the controller instance that was connected to the device are down, so the
291                 // running instance can clear up the operational data store even though it was not
292                 // connected to the device.
293                 LOG.debug("handleOwnershipChanged: found no connection instance for {}", ownershipChange.getEntity());
294             }
295
296             // If entity has no owner, clean up the operational data store (it's possible because owner controller
297             // might went down abruptly and didn't get a chance to clean up the operational data store.
298             if (!ownershipChange.hasOwner()) {
299                 LOG.debug("{} has no onwer, cleaning up the operational data store", ownershipChange.getEntity());
300                 // Below code might look weird but it's required. We want to give first opportunity to the
301                 // previous owner of the device to clean up the operational data store if there is no owner now.
302                 // That way we will avoid lot of nasty md-sal exceptions because of concurrent delete.
303                 if (ownershipChange.wasOwner()) {
304                     cleanEntityOperationalData(ownershipChange.getEntity());
305                 }
306                 // If first cleanEntityOperationalData() was called, this call will be no-op.
307                 cleanEntityOperationalData(ownershipChange.getEntity());
308             }
309             return;
310         }
311         //Connection detail need to be cached, irrespective of ownership result.
312         putConnectionInstance(hwvtepConnectionInstance.getMDConnectionInfo(),hwvtepConnectionInstance);
313
314         if (ownershipChange.isOwner() == hwvtepConnectionInstance.getHasDeviceOwnership()) {
315             LOG.debug("handleOwnershipChanged: no change in ownership for {}. Ownership status is : {}",
316                     hwvtepConnectionInstance.getConnectionInfo(), hwvtepConnectionInstance.getHasDeviceOwnership());
317             return;
318         }
319
320         hwvtepConnectionInstance.setHasDeviceOwnership(ownershipChange.isOwner());
321         // You were not an owner, but now you are
322         if (ownershipChange.isOwner()) {
323             LOG.info("handleOwnershipChanged: *this* southbound plugin instance is owner of device {}",
324                     hwvtepConnectionInstance.getConnectionInfo());
325
326             //*this* instance of southbound plugin is owner of the device,
327             //so register for monitor callbacks
328             hwvtepConnectionInstance.registerCallbacks();
329
330         } else {
331             //You were owner of the device, but now you are not. With the current ownership
332             //grant mechanism, this scenario should not occur. Because this scenario will occur
333             //when this controller went down or switch flap the connection, but in both the case
334             //it will go through the re-registration process. We need to implement this condition
335             //when clustering service implement a ownership grant strategy which can revoke the
336             //device ownership for load balancing the devices across the instances.
337             //Once this condition occur, we should unregister the callback.
338             LOG.error("handleOwnershipChanged: *this* southbound plugin instance is no longer the owner of device {}",
339                     hwvtepConnectionInstance.getNodeId().getValue());
340         }
341     }
342
343     private void cleanEntityOperationalData(Entity entity) {
344         //TODO: remove entity from Operational DataStore
345         LOG.error("cleanEntityOperationalData(): Code incomplete");
346     }
347
348     private HwvtepConnectionInstance getConnectionInstanceFromEntity(Entity entity) {
349         return entityConnectionMap.get(entity);
350     }
351
352     private class HwvtepDeviceEntityOwnershipListener implements EntityOwnershipListener {
353         private HwvtepConnectionManager hcm;
354         private EntityOwnershipListenerRegistration listenerRegistration;
355
356         HwvtepDeviceEntityOwnershipListener(HwvtepConnectionManager hcm, EntityOwnershipService entityOwnershipService) {
357             this.hcm = hcm;
358             listenerRegistration = entityOwnershipService.registerListener(ENTITY_TYPE, this);
359         }
360         public void close() {
361             listenerRegistration.close();
362         }
363         @Override
364         public void ownershipChanged(EntityOwnershipChange ownershipChange) {
365             hcm.handleOwnershipChanged(ownershipChange);
366         }
367     }
368
369 }