Merge "Bug 4957 Fix blocking call to Init RoleGet"
[openflowplugin.git] / openflowplugin-impl / src / main / java / org / opendaylight / openflowplugin / impl / role / RoleManagerImpl.java
1 /**
2  * Copyright (c) 2015 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.openflowplugin.impl.role;
9
10 import javax.annotation.CheckForNull;
11 import javax.annotation.Nullable;
12 import java.util.Map;
13 import java.util.concurrent.ConcurrentHashMap;
14 import java.util.concurrent.ConcurrentMap;
15 import java.util.concurrent.ExecutionException;
16 import java.util.concurrent.TimeUnit;
17 import java.util.concurrent.TimeoutException;
18
19 import com.google.common.base.Optional;
20 import com.google.common.base.Preconditions;
21 import com.google.common.base.Verify;
22 import com.google.common.util.concurrent.AsyncFunction;
23 import com.google.common.util.concurrent.CheckedFuture;
24 import com.google.common.util.concurrent.FutureCallback;
25 import com.google.common.util.concurrent.Futures;
26 import com.google.common.util.concurrent.ListenableFuture;
27 import java.util.Map;
28 import java.util.concurrent.ConcurrentHashMap;
29 import java.util.concurrent.ConcurrentMap;
30 import javax.annotation.CheckForNull;
31 import javax.annotation.Nullable;
32 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
33 import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
34 import org.opendaylight.controller.md.sal.common.api.clustering.Entity;
35 import org.opendaylight.controller.md.sal.common.api.clustering.EntityOwnershipChange;
36 import org.opendaylight.controller.md.sal.common.api.clustering.EntityOwnershipListener;
37 import org.opendaylight.controller.md.sal.common.api.clustering.EntityOwnershipListenerRegistration;
38 import org.opendaylight.controller.md.sal.common.api.clustering.EntityOwnershipService;
39 import org.opendaylight.controller.md.sal.common.api.clustering.EntityOwnershipState;
40 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
41 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
42 import org.opendaylight.openflowplugin.api.OFConstants;
43 import org.opendaylight.openflowplugin.api.openflow.device.DeviceContext;
44 import org.opendaylight.openflowplugin.api.openflow.device.DeviceState;
45 import org.opendaylight.openflowplugin.api.openflow.device.handlers.DeviceInitializationPhaseHandler;
46 import org.opendaylight.openflowplugin.api.openflow.role.RoleChangeListener;
47 import org.opendaylight.openflowplugin.api.openflow.role.RoleContext;
48 import org.opendaylight.openflowplugin.api.openflow.role.RoleManager;
49 import org.opendaylight.openflowplugin.impl.util.DeviceInitializationUtils;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.role.service.rev150727.OfpRole;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54
55 /**
56  * Gets invoked from RpcManagerInitial, registers a candidate with EntityOwnershipService.
57  * On receipt of the ownership notification, makes an rpc call to SalRoleSevice.
58  *
59  * Hands over to StatisticsManager at the end.
60  */
61 public class RoleManagerImpl implements RoleManager, EntityOwnershipListener {
62     private static final Logger LOG = LoggerFactory.getLogger(RoleManagerImpl.class);
63
64     private DeviceInitializationPhaseHandler deviceInitializationPhaseHandler;
65     private final DataBroker dataBroker;
66     private final EntityOwnershipService entityOwnershipService;
67     private final ConcurrentMap<Entity, RoleContext> contexts = new ConcurrentHashMap<>();
68     private final EntityOwnershipListenerRegistration entityOwnershipListenerRegistration;
69     private final boolean switchFeaturesMandatory;
70
71     public RoleManagerImpl(final EntityOwnershipService entityOwnershipService, final DataBroker dataBroker, final boolean switchFeaturesMandatory) {
72         this.entityOwnershipService = Preconditions.checkNotNull(entityOwnershipService);
73         this.dataBroker = Preconditions.checkNotNull(dataBroker);
74         this.switchFeaturesMandatory = switchFeaturesMandatory;
75         this.entityOwnershipListenerRegistration = Preconditions.checkNotNull(entityOwnershipService.registerListener(RoleManager.ENTITY_TYPE, this));
76         LOG.debug("Registering OpenflowOwnershipListener listening to all entity ownership changes");
77     }
78
79     @Override
80     public void setDeviceInitializationPhaseHandler(final DeviceInitializationPhaseHandler handler) {
81         deviceInitializationPhaseHandler = handler;
82     }
83
84     @Override
85     public void onDeviceContextLevelUp(@CheckForNull final DeviceContext deviceContext) {
86         LOG.debug("RoleManager called for device:{}", deviceContext.getPrimaryConnectionContext().getNodeId());
87         if (deviceContext.getDeviceState().getFeatures().getVersion() < OFConstants.OFP_VERSION_1_3) {
88             // Roles are not supported before OF1.3, so move forward.
89             deviceInitializationPhaseHandler.onDeviceContextLevelUp(deviceContext);
90             return;
91         }
92
93         final RoleContext roleContext = new RoleContextImpl(deviceContext, entityOwnershipService,
94                 makeEntity(deviceContext.getDeviceState().getNodeId()));
95         // if the device context gets closed (mostly on connection close), we would need to cleanup
96         deviceContext.addDeviceContextClosedHandler(this);
97         Verify.verify(contexts.putIfAbsent(roleContext.getEntity(), roleContext) == null,
98                 "RoleCtx for master Node {} is still not close.", deviceContext.getDeviceState().getNodeId());
99
100         final ListenableFuture<OfpRole> roleChangeFuture = roleContext.initialization();
101         final ListenableFuture<Void> initDeviceFuture = Futures.transform(roleChangeFuture, new AsyncFunction<OfpRole, Void>() {
102             @Override
103             public ListenableFuture<Void> apply(final OfpRole input) throws Exception {
104                 final ListenableFuture<Void> nextFuture;
105                 if (OfpRole.BECOMEMASTER.equals(input)) {
106                     LOG.debug("Node {} was initialized", deviceContext.getDeviceState().getNodeId());
107                     nextFuture = DeviceInitializationUtils.initializeNodeInformation(deviceContext, switchFeaturesMandatory);
108                 } else {
109                     LOG.debug("Node {} we are not Master so we are going to finish.", deviceContext.getDeviceState().getNodeId());
110                     nextFuture = Futures.immediateFuture(null);
111                 }
112                 return nextFuture;
113             }
114         });
115
116         Futures.addCallback(initDeviceFuture, new FutureCallback<Void>() {
117             @Override
118             public void onSuccess(final Void result) {
119                 LOG.debug("Initialization Node {} is done.", deviceContext.getDeviceState().getNodeId());
120                 getRoleContextLevelUp(deviceContext);
121             }
122
123             @Override
124             public void onFailure(final Throwable t) {
125                 LOG.warn("Unexpected error for Node {} initialization", deviceContext.getDeviceState().getNodeId(), t);
126                 deviceContext.close();
127             }
128         });
129     }
130
131     void getRoleContextLevelUp(final DeviceContext deviceContext) {
132         LOG.debug("Created role context for node {}", deviceContext.getDeviceState().getNodeId());
133         LOG.debug("roleChangeFuture success for device:{}. Moving to StatisticsManager", deviceContext.getDeviceState().getNodeId());
134         deviceInitializationPhaseHandler.onDeviceContextLevelUp(deviceContext);
135     }
136
137     @Override
138     public void close() throws Exception {
139         entityOwnershipListenerRegistration.close();
140         for (final Map.Entry<Entity, RoleContext> roleContextEntry : contexts.entrySet()) {
141             // got here because last known role is LEADER and DS might need clearing up
142             final Entity entity = roleContextEntry.getKey();
143             final Optional<EntityOwnershipState> ownershipState = entityOwnershipService.getOwnershipState(entity);
144             final NodeId nodeId = roleContextEntry.getValue().getDeviceState().getNodeId();
145             if (ownershipState.isPresent())
146                 if ((!ownershipState.get().hasOwner())) {
147                     LOG.trace("Last role is LEADER and ownershipService returned hasOwner=false for node: {}; " +
148                             "cleaning DS as being probably the last owner", nodeId);
149                     removeDeviceFromOperDS(roleContextEntry.getValue());
150                 } else {
151                     // NOOP - there is another owner
152                     LOG.trace("Last role is LEADER and ownershipService returned hasOwner=true for node: {}; " +
153                             "leaving DS untouched", nodeId);
154                 }
155             else {
156                 // TODO: is this safe? When could this happen?
157                 LOG.warn("Last role is LEADER but ownershipService returned empty ownership info for node: {}; " +
158                         "cleaning DS ANYWAY!", nodeId);
159                 removeDeviceFromOperDS(roleContextEntry.getValue());
160             }
161         }
162         contexts.clear();
163     }
164
165     @Override
166     public void onDeviceContextClosed(final DeviceContext deviceContext) {
167         final NodeId nodeId = deviceContext.getDeviceState().getNodeId();
168         final OfpRole role = deviceContext.getDeviceState().getRole();
169         LOG.debug("onDeviceContextClosed for node {}", nodeId);
170
171         final Entity entity = makeEntity(nodeId);
172         final RoleContext roleContext = contexts.get(entity);
173         if (roleContext != null) {
174             LOG.debug("Found roleContext associated to deviceContext: {}, now closing the roleContext", nodeId);
175             roleContext.close();
176             if (role == null || OfpRole.BECOMESLAVE.equals(role)) {
177                 LOG.debug("No DS commitment for device {} - LEADER is somewhere else", nodeId);
178                 contexts.remove(entity, roleContext);
179             }
180         }
181     }
182
183     private static Entity makeEntity(final NodeId nodeId) {
184         return new Entity(RoleManager.ENTITY_TYPE, nodeId.getValue());
185     }
186
187     private CheckedFuture<Void, TransactionCommitFailedException> removeDeviceFromOperDS(final RoleChangeListener roleChangeListener) {
188         Preconditions.checkArgument(roleChangeListener != null);
189         final DeviceState deviceState = roleChangeListener.getDeviceState();
190         final WriteTransaction delWtx = dataBroker.newWriteOnlyTransaction();
191         delWtx.delete(LogicalDatastoreType.OPERATIONAL, deviceState.getNodeInstanceIdentifier());
192         final CheckedFuture<Void, TransactionCommitFailedException> delFuture = delWtx.submit();
193         Futures.addCallback(delFuture, new FutureCallback<Void>() {
194
195             @Override
196             public void onSuccess(final Void result) {
197                 LOG.debug("Delete Node {} was successful", deviceState.getNodeId());
198             }
199
200             @Override
201             public void onFailure(final Throwable t) {
202                 LOG.warn("Delete Node {} fail.", deviceState.getNodeId(), t);
203             }
204         });
205         return delFuture;
206     }
207
208     @Override
209     public void ownershipChanged(final EntityOwnershipChange ownershipChange) {
210         Preconditions.checkArgument(ownershipChange != null);
211         final RoleChangeListener roleChangeListener = contexts.get(ownershipChange.getEntity());
212
213         LOG.info("Received EntityOwnershipChange:{}, roleChangeListener-present={}", ownershipChange, (roleChangeListener != null));
214
215         if (roleChangeListener != null) {
216             LOG.debug("Found roleChangeListener for local entity:{}", ownershipChange.getEntity());
217             // if this was the master and entity does not have a master
218             if (ownershipChange.wasOwner() && !ownershipChange.isOwner() && !ownershipChange.hasOwner()) {
219                 LOG.info("Initiate removal from operational. Possibly the last node to be disconnected for :{}. ", ownershipChange);
220                 Futures.addCallback(removeDeviceFromOperDS(roleChangeListener),
221                         new FutureCallback<Void>() {
222                             @Override
223                             public void onSuccess(@Nullable final Void aVoid) {
224                                 LOG.debug("Freeing roleContext slot for device: {}", roleChangeListener.getDeviceState().getNodeId());
225                                 contexts.remove(ownershipChange.getEntity(), roleChangeListener);
226                             }
227
228                             @Override
229                             public void onFailure(final Throwable throwable) {
230                                 LOG.warn("NOT freeing roleContext slot for device: {}, {}",
231                                         roleChangeListener.getDeviceState().getNodeId(), throwable.getMessage());
232                             }
233                         });
234             } else {
235                 final OfpRole newRole = ownershipChange.isOwner() ? OfpRole.BECOMEMASTER : OfpRole.BECOMESLAVE;
236                 final OfpRole oldRole = ownershipChange.wasOwner() ? OfpRole.BECOMEMASTER : OfpRole.BECOMESLAVE;
237                 // send even if they are same. we do the check for duplicates in SalRoleService and maintain a lastKnownRole
238                 roleChangeListener.onRoleChanged(oldRole, newRole);
239             }
240         }
241     }
242 }