Merge "Though shall not use org.eclipse.tycho's osgi.. you don't even need it!"
[openflowplugin.git] / openflowplugin-impl / src / main / java / org / opendaylight / openflowplugin / impl / lifecycle / ContextChainHolderImpl.java
1 /*
2  * Copyright (c) 2016 Pantheon Technologies s.r.o. 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.lifecycle;
9
10 import com.google.common.annotations.VisibleForTesting;
11 import com.google.common.base.Function;
12 import com.google.common.base.Verify;
13 import com.google.common.util.concurrent.Futures;
14 import com.google.common.util.concurrent.ListenableFuture;
15 import io.netty.util.HashedWheelTimer;
16 import io.netty.util.Timeout;
17 import io.netty.util.TimerTask;
18 import io.netty.util.internal.ConcurrentSet;
19 import java.util.ArrayList;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Objects;
23 import java.util.Set;
24 import java.util.concurrent.ConcurrentHashMap;
25 import java.util.concurrent.TimeUnit;
26 import java.util.concurrent.TimeoutException;
27 import javax.annotation.Nonnull;
28 import javax.annotation.Nullable;
29 import org.opendaylight.controller.md.sal.common.api.clustering.EntityOwnershipChange;
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.data.TransactionCommitFailedException;
33 import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonServiceProvider;
34 import org.opendaylight.openflowplugin.api.openflow.OFPManager;
35 import org.opendaylight.openflowplugin.api.openflow.connection.ConnectionContext;
36 import org.opendaylight.openflowplugin.api.openflow.connection.ConnectionStatus;
37 import org.opendaylight.openflowplugin.api.openflow.device.DeviceContext;
38 import org.opendaylight.openflowplugin.api.openflow.device.DeviceInfo;
39 import org.opendaylight.openflowplugin.api.openflow.device.DeviceManager;
40 import org.opendaylight.openflowplugin.api.openflow.lifecycle.ContextChain;
41 import org.opendaylight.openflowplugin.api.openflow.lifecycle.ContextChainHolder;
42 import org.opendaylight.openflowplugin.api.openflow.lifecycle.ContextChainMastershipState;
43 import org.opendaylight.openflowplugin.api.openflow.lifecycle.LifecycleService;
44 import org.opendaylight.openflowplugin.api.openflow.rpc.RpcContext;
45 import org.opendaylight.openflowplugin.api.openflow.rpc.RpcManager;
46 import org.opendaylight.openflowplugin.api.openflow.statistics.StatisticsContext;
47 import org.opendaylight.openflowplugin.api.openflow.statistics.StatisticsManager;
48 import org.opendaylight.openflowplugin.impl.util.DeviceStateUtil;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
50 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
53
54 public class ContextChainHolderImpl implements ContextChainHolder {
55
56     private static final Logger LOG = LoggerFactory.getLogger(ContextChainHolderImpl.class);
57
58     private static final String CONTEXT_CREATED_FOR_CONNECTION = " context created for connection: {}";
59     private static final long DEFAULT_CHECK_ROLE_MASTER = 10000L;
60     private static final String SERVICE_ENTITY_TYPE = "org.opendaylight.mdsal.ServiceEntityType";
61     private static final String ASYNC_SERVICE_ENTITY_TYPE = "org.opendaylight.mdsal.AsyncServiceCloseEntityType";
62
63     private final ConcurrentHashMap<DeviceInfo, ContextChain> contextChainMap = new ConcurrentHashMap<>();
64     private final ConcurrentHashMap<DeviceInfo, ContextChain> withoutRoleChains = new ConcurrentHashMap<>();
65     private final List<DeviceInfo> markToBeRemoved = new ArrayList<>();
66     private final HashedWheelTimer timer;
67     private final Long checkRoleMaster;
68
69     private DeviceManager deviceManager;
70     private RpcManager rpcManager;
71     private StatisticsManager statisticsManager;
72     private EntityOwnershipListenerRegistration eosListenerRegistration;
73     private ClusterSingletonServiceProvider singletonServicesProvider;
74     private boolean timerIsRunningRole;
75
76     public ContextChainHolderImpl(final HashedWheelTimer timer) {
77         this.timerIsRunningRole = false;
78         this.timer = timer;
79         this.checkRoleMaster = DEFAULT_CHECK_ROLE_MASTER;
80     }
81
82     @Override
83     public <T extends OFPManager> void addManager(final T manager) {
84         if (Objects.isNull(deviceManager) && manager instanceof DeviceManager) {
85             LOG.trace("Context chain holder: Device manager OK.");
86             deviceManager = (DeviceManager) manager;
87         } else if (Objects.isNull(rpcManager) && manager instanceof RpcManager) {
88             LOG.trace("Context chain holder: RPC manager OK.");
89             rpcManager = (RpcManager) manager;
90         } else if (Objects.isNull(statisticsManager) && manager instanceof StatisticsManager) {
91             LOG.trace("Context chain holder: Statistics manager OK.");
92             statisticsManager = (StatisticsManager) manager;
93         }
94     }
95
96     @Override
97     public ContextChain createContextChain(final ConnectionContext connectionContext) {
98
99         final DeviceInfo deviceInfo = connectionContext.getDeviceInfo();
100         final String deviceInfoLOGValue = deviceInfo.getLOGValue();
101
102         if (LOG.isDebugEnabled()) {
103             LOG.debug("Creating a new chain" + CONTEXT_CREATED_FOR_CONNECTION, deviceInfoLOGValue);
104         }
105
106         final ContextChain contextChain = new ContextChainImpl(connectionContext);
107         final LifecycleService lifecycleService = new LifecycleServiceImpl(this);
108         lifecycleService.registerDeviceRemovedHandler(deviceManager);
109         lifecycleService.registerDeviceRemovedHandler(rpcManager);
110         lifecycleService.registerDeviceRemovedHandler(statisticsManager);
111
112         if (LOG.isDebugEnabled()) {
113             LOG.debug("Lifecycle services" + CONTEXT_CREATED_FOR_CONNECTION, deviceInfoLOGValue);
114         }
115
116         final DeviceContext deviceContext = deviceManager.createContext(connectionContext);
117
118         if (LOG.isDebugEnabled()) {
119             LOG.debug("Device" + CONTEXT_CREATED_FOR_CONNECTION, deviceInfoLOGValue);
120         }
121
122         final RpcContext rpcContext = rpcManager.createContext(connectionContext.getDeviceInfo(), deviceContext);
123
124         if (LOG.isDebugEnabled()) {
125             LOG.debug("RPC" + CONTEXT_CREATED_FOR_CONNECTION, deviceInfoLOGValue);
126         }
127
128         final StatisticsContext statisticsContext
129                 = statisticsManager.createContext(deviceContext);
130
131         if (LOG.isDebugEnabled()) {
132             LOG.debug("Statistics" + CONTEXT_CREATED_FOR_CONNECTION, deviceInfoLOGValue);
133         }
134
135         deviceContext.setLifecycleInitializationPhaseHandler(statisticsContext);
136         statisticsContext.setLifecycleInitializationPhaseHandler(rpcContext);
137         statisticsContext.setInitialSubmitHandler(deviceContext);
138
139         contextChain.addLifecycleService(lifecycleService);
140         contextChain.addContext(deviceContext);
141         contextChain.addContext(rpcContext);
142         contextChain.addContext(statisticsContext);
143         this.withoutRoleChains.put(deviceInfo, contextChain);
144         if (!this.timerIsRunningRole) {
145             this.startTimerRole();
146         }
147         deviceContext.onPublished();
148         contextChain.registerServices(this.singletonServicesProvider);
149
150         return contextChain;
151     }
152
153     @Override
154     public ListenableFuture<Void> destroyContextChain(final DeviceInfo deviceInfo) {
155         ContextChain chain = contextChainMap.remove(deviceInfo);
156         if (chain != null) {
157             chain.close();
158         }
159         if (markToBeRemoved.contains(deviceInfo)) {
160             markToBeRemoved.remove(deviceInfo);
161             LOG.info("Removing device: {} from DS", deviceInfo.getLOGValue());
162             return deviceManager.removeDeviceFromOperationalDS(deviceInfo);
163         } else {
164             return Futures.immediateFuture(null);
165         }
166     }
167
168     @Override
169     public ConnectionStatus deviceConnected(final ConnectionContext connectionContext) throws Exception {
170
171         DeviceInfo deviceInfo = connectionContext.getDeviceInfo();
172         LOG.info("Device {} connected.", deviceInfo.getLOGValue());
173         ContextChain contextChain = contextChainMap.get(deviceInfo);
174         if (contextChain != null) {
175             if (contextChain.addAuxiliaryConnection(connectionContext)) {
176                 LOG.info("An auxiliary connection was added to device: {}", deviceInfo.getLOGValue());
177                 return ConnectionStatus.MAY_CONTINUE;
178             } else {
179                 LOG.warn("Device {} already connected. Closing all connection to the device.", deviceInfo.getLOGValue());
180                 destroyContextChain(deviceInfo);
181                 return ConnectionStatus.ALREADY_CONNECTED;
182             }
183         } else {
184             if (LOG.isDebugEnabled()) {
185                 LOG.debug("No context chain found for device: {}, creating new.", deviceInfo.getLOGValue());
186             }
187             contextChainMap.put(deviceInfo, createContextChain(connectionContext));
188         }
189
190         return ConnectionStatus.MAY_CONTINUE;
191     }
192
193     @Override
194     public void addSingletonServicesProvider(final ClusterSingletonServiceProvider singletonServicesProvider) {
195         this.singletonServicesProvider = singletonServicesProvider;
196     }
197
198     @Override
199     public void onNotAbleToStartMastership(final DeviceInfo deviceInfo, @Nonnull final String reason, final boolean mandatory) {
200         this.withoutRoleChains.remove(deviceInfo);
201         LOG.warn("Not able to set MASTER role on device {}, reason: {}", deviceInfo.getLOGValue(), reason);
202         if (mandatory && contextChainMap.containsKey(deviceInfo)) {
203             LOG.warn("This mastering is mandatory, destroying context chain and closing connection.");
204             Futures.transform(contextChainMap.get(deviceInfo).stopChain(), new Function<Void, Object>() {
205                         @Nullable
206                         @Override
207                         public Object apply(@Nullable Void aVoid) {
208                             destroyContextChain(deviceInfo);
209                             return null;
210                         }
211                     });
212         }
213     }
214
215     @Override
216     public void onMasterRoleAcquired(final DeviceInfo deviceInfo,
217                                      @Nonnull final ContextChainMastershipState mastershipState) {
218         this.withoutRoleChains.remove(deviceInfo);
219         ContextChain contextChain = contextChainMap.get(deviceInfo);
220         if (contextChain != null) {
221             if (contextChain.isMastered(mastershipState)) {
222                 LOG.info("Role MASTER was granted to device {}", deviceInfo.getLOGValue());
223                 this.sendNotificationNodeAdded(deviceInfo);
224             }
225         }
226     }
227
228     @Override
229     public void onSlaveRoleAcquired(final DeviceInfo deviceInfo) {
230         this.withoutRoleChains.remove(deviceInfo);
231         ContextChain contextChain = contextChainMap.get(deviceInfo);
232         if (contextChain != null) {
233             contextChain.makeContextChainStateSlave();
234         }
235     }
236
237     @Override
238     public void onSlaveRoleNotAcquired(final DeviceInfo deviceInfo) {
239         this.withoutRoleChains.remove(deviceInfo);
240         ContextChain contextChain = contextChainMap.get(deviceInfo);
241         if (contextChain != null) {
242             destroyContextChain(deviceInfo);
243         }
244     }
245
246     @Override
247     public void onDeviceDisconnected(final ConnectionContext connectionContext) {
248
249         final DeviceInfo deviceInfo = connectionContext.getDeviceInfo();
250         if (deviceInfo != null) {
251             ContextChain chain = contextChainMap.get(deviceInfo);
252             if (chain != null) {
253                 if (chain.auxiliaryConnectionDropped(connectionContext)) {
254                     LOG.info("Auxiliary connection from device {} disconnected.", deviceInfo.getLOGValue());
255                 } else {
256                     LOG.info("Device {} disconnected.", deviceInfo.getLOGValue());
257                     Futures.transform(chain.connectionDropped(), new Function<Void, Object>() {
258                         @Nullable
259                         @Override
260                         public Object apply(@Nullable Void aVoid) {
261                             destroyContextChain(deviceInfo);
262                             return null;
263                         }
264                     });
265                 }
266             }
267         }
268     }
269
270     @Override
271     public void changeEntityOwnershipService(final EntityOwnershipService entityOwnershipService) {
272         if (Objects.nonNull(this.eosListenerRegistration)) {
273             LOG.warn("EOS Listener already registered.");
274         } else {
275             this.eosListenerRegistration = Verify.verifyNotNull(entityOwnershipService.registerListener
276                     (ASYNC_SERVICE_ENTITY_TYPE, this));
277         }
278     }
279
280     private void startTimerRole() {
281         this.timerIsRunningRole = true;
282         if (LOG.isDebugEnabled()) {
283             LOG.debug("There is a context chain without role, starting timer.");
284         }
285         timer.newTimeout(new RoleTimerTask(), this.checkRoleMaster, TimeUnit.MILLISECONDS);
286     }
287
288     private void stopTimerRole() {
289         this.timerIsRunningRole = false;
290         if (LOG.isDebugEnabled()) {
291             LOG.debug("There are no context chains, stopping timer.");
292         }
293     }
294
295     private void timerTickRole() {
296         if (!withoutRoleChains.isEmpty()) {
297             this.withoutRoleChains.forEach((deviceInfo, contextChain) -> contextChain.makeDeviceSlave());
298             timer.newTimeout(new RoleTimerTask(), this.checkRoleMaster, TimeUnit.MILLISECONDS);
299         } else {
300             this.stopTimerRole();
301         }
302     }
303
304     @VisibleForTesting
305     boolean checkAllManagers() {
306         return Objects.nonNull(deviceManager) && Objects.nonNull(rpcManager) && Objects.nonNull(statisticsManager);
307     }
308
309     @Override
310     public void close() throws Exception {
311         this.contextChainMap.forEach((deviceInfo, contextChain) -> {
312             if (contextChain.isMastered(ContextChainMastershipState.CHECK)) {
313                 contextChain.stopChain();
314             }
315             contextChain.close();
316         });
317         if (Objects.nonNull(eosListenerRegistration)) {
318             eosListenerRegistration.close();
319         }
320     }
321
322     @Override
323     public void ownershipChanged(EntityOwnershipChange entityOwnershipChange) {
324         if (!entityOwnershipChange.hasOwner() && !entityOwnershipChange.isOwner() && entityOwnershipChange.wasOwner()) {
325             final YangInstanceIdentifier yii = entityOwnershipChange.getEntity().getId();
326             final YangInstanceIdentifier.NodeIdentifierWithPredicates niiwp =
327                     (YangInstanceIdentifier.NodeIdentifierWithPredicates) yii.getLastPathArgument();
328             String entityName =  niiwp.getKeyValues().values().iterator().next().toString();
329             if (LOG.isDebugEnabled()) {
330                 LOG.debug("Last master for entity : {}", entityName);
331             }
332
333             if (entityName != null ){
334                 final NodeId nodeId = new NodeId(entityName);
335                 DeviceInfo inMap = null;
336                 for (Map.Entry<DeviceInfo, ContextChain> entry : contextChainMap.entrySet()) {
337                     if (entry.getKey().getNodeId().equals(nodeId)) {
338                         inMap = entry.getKey();
339                         break;
340                     }                    
341                 }
342                 if (Objects.nonNull(inMap)) {
343                     markToBeRemoved.add(inMap);
344                 } else {
345                     try {
346                         LOG.info("Removing device: {} from DS", nodeId);
347                         deviceManager
348                                 .removeDeviceFromOperationalDS(DeviceStateUtil.createNodeInstanceIdentifier(nodeId))
349                                 .checkedGet(5L, TimeUnit.SECONDS);
350                     } catch (TimeoutException | TransactionCommitFailedException e) {
351                         LOG.warn("Not able to remove device {} from DS", nodeId);
352                     }
353                 }
354             }                
355         }        
356     }
357
358     private void sendNotificationNodeAdded(final DeviceInfo deviceInfo) {
359         this.deviceManager.sendNodeAddedNotification(deviceInfo);
360     }
361
362     private class RoleTimerTask implements TimerTask {
363
364         @Override
365         public void run(Timeout timeout) throws Exception {
366             timerTickRole();
367         }
368
369     }
370 }
371