2 * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.openflowplugin.impl.role;
10 import javax.annotation.CheckForNull;
11 import javax.annotation.Nullable;
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;
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;
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;
56 * Gets invoked from RpcManagerInitial, registers a candidate with EntityOwnershipService.
57 * On receipt of the ownership notification, makes an rpc call to SalRoleSevice.
59 * Hands over to StatisticsManager at the end.
61 public class RoleManagerImpl implements RoleManager, EntityOwnershipListener {
62 private static final Logger LOG = LoggerFactory.getLogger(RoleManagerImpl.class);
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;
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");
80 public void setDeviceInitializationPhaseHandler(final DeviceInitializationPhaseHandler handler) {
81 deviceInitializationPhaseHandler = handler;
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);
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());
100 final ListenableFuture<OfpRole> roleChangeFuture = roleContext.initialization();
101 final ListenableFuture<Void> initDeviceFuture = Futures.transform(roleChangeFuture, new AsyncFunction<OfpRole, Void>() {
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);
109 LOG.debug("Node {} we are not Master so we are going to finish.", deviceContext.getDeviceState().getNodeId());
110 nextFuture = Futures.immediateFuture(null);
116 Futures.addCallback(initDeviceFuture, new FutureCallback<Void>() {
118 public void onSuccess(final Void result) {
119 LOG.debug("Initialization Node {} is done.", deviceContext.getDeviceState().getNodeId());
120 getRoleContextLevelUp(deviceContext);
124 public void onFailure(final Throwable t) {
125 LOG.warn("Unexpected error for Node {} initialization", deviceContext.getDeviceState().getNodeId(), t);
126 deviceContext.close();
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);
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());
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);
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());
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);
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);
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);
183 private static Entity makeEntity(final NodeId nodeId) {
184 return new Entity(RoleManager.ENTITY_TYPE, nodeId.getValue());
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>() {
196 public void onSuccess(final Void result) {
197 LOG.debug("Delete Node {} was successful", deviceState.getNodeId());
201 public void onFailure(final Throwable t) {
202 LOG.warn("Delete Node {} fail.", deviceState.getNodeId(), t);
209 public void ownershipChanged(final EntityOwnershipChange ownershipChange) {
210 Preconditions.checkArgument(ownershipChange != null);
211 final RoleChangeListener roleChangeListener = contexts.get(ownershipChange.getEntity());
213 LOG.info("Received EntityOwnershipChange:{}, roleChangeListener-present={}", ownershipChange, (roleChangeListener != null));
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>() {
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);
229 public void onFailure(final Throwable throwable) {
230 LOG.warn("NOT freeing roleContext slot for device: {}, {}",
231 roleChangeListener.getDeviceState().getNodeId(), throwable.getMessage());
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);