BUG-5001: clean up state transitions
[openflowplugin.git] / openflowplugin-impl / src / main / java / org / opendaylight / openflowplugin / impl / services / SalRoleServiceImpl.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.services;
9
10 import com.google.common.base.Function;
11 import com.google.common.base.Preconditions;
12 import com.google.common.util.concurrent.CheckedFuture;
13 import com.google.common.util.concurrent.Futures;
14 import com.google.common.util.concurrent.ListenableFuture;
15 import com.google.common.util.concurrent.ListeningExecutorService;
16 import com.google.common.util.concurrent.MoreExecutors;
17 import java.math.BigInteger;
18 import java.util.concurrent.Callable;
19 import java.util.concurrent.ExecutionException;
20 import java.util.concurrent.Executors;
21 import java.util.concurrent.Future;
22 import java.util.concurrent.TimeUnit;
23 import java.util.concurrent.TimeoutException;
24 import java.util.concurrent.atomic.AtomicReference;
25 import org.opendaylight.openflowplugin.api.openflow.connection.ConnectionContext.CONNECTION_STATE;
26 import org.opendaylight.openflowplugin.api.openflow.device.DeviceContext;
27 import org.opendaylight.openflowplugin.api.openflow.device.RequestContextStack;
28 import org.opendaylight.openflowplugin.api.openflow.device.Xid;
29 import org.opendaylight.openflowplugin.impl.role.RoleChangeException;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.OfHeader;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.RoleRequestOutput;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.role.service.rev150727.OfpRole;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.role.service.rev150727.SalRoleService;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.role.service.rev150727.SetRoleInput;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.role.service.rev150727.SetRoleOutput;
37 import org.opendaylight.yangtools.yang.common.RpcResult;
38 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42
43 public final class SalRoleServiceImpl extends AbstractSimpleService<SetRoleInput, SetRoleOutput> implements SalRoleService  {
44
45     private static final Logger LOG = LoggerFactory.getLogger(SalRoleServiceImpl.class);
46
47     private static final BigInteger MAX_GENERATION_ID = new BigInteger("ffffffffffffffff", 16);
48
49     private static final int MAX_RETRIES = 42;
50
51     private static final Function<Exception, RoleChangeException> EXCEPTION_FUNCTION = new Function<Exception, RoleChangeException>() {
52         @Override
53         public RoleChangeException apply(final Exception input) {
54             if (input instanceof ExecutionException) {
55                 final Throwable cause = input.getCause();
56                 if (cause instanceof RoleChangeException) {
57                     return (RoleChangeException) cause;
58                 }
59             } else if (input instanceof RoleChangeException) {
60                 return (RoleChangeException) input;
61             }
62
63             return new RoleChangeException(input.getMessage(), input);
64         }
65     };
66
67     private final DeviceContext deviceContext;
68     private final RoleService roleService;
69     private final AtomicReference<OfpRole> lastKnownRoleRef = new AtomicReference<>(OfpRole.NOCHANGE);
70     private final ListeningExecutorService listeningExecutorService;
71     private final NodeId nodeId;
72     private final Short version;
73
74     public SalRoleServiceImpl(final RequestContextStack requestContextStack, final DeviceContext deviceContext) {
75         super(requestContextStack, deviceContext, SetRoleOutput.class);
76         this.deviceContext = Preconditions.checkNotNull(deviceContext);
77         this.roleService =  new RoleService(requestContextStack, deviceContext, RoleRequestOutput.class);
78         nodeId = deviceContext.getPrimaryConnectionContext().getNodeId();
79         version = deviceContext.getPrimaryConnectionContext().getFeatures().getVersion();
80         listeningExecutorService = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());
81     }
82
83     @Override
84     protected OfHeader buildRequest(final Xid xid, final SetRoleInput input) {
85         return null;
86     }
87
88     @Override
89     public Future<RpcResult<SetRoleOutput>> setRole(final SetRoleInput input) {
90         LOG.info("SetRole called with input:{}", input);
91
92         // compare with last known role and set if different. If they are same, then return.
93         if (lastKnownRoleRef.compareAndSet(input.getControllerRole(), input.getControllerRole())) {
94             LOG.info("Role to be set is same as the last known role for the device:{}. Hence ignoring.", input.getControllerRole());
95             return Futures.immediateFuture(RpcResultBuilder.<SetRoleOutput>success().build());
96         }
97
98         RoleChangeTask roleChangeTask = new RoleChangeTask(input.getControllerRole());
99
100         do {
101             // Check current connection state
102             final CONNECTION_STATE state = deviceContext.getPrimaryConnectionContext().getConnectionState();
103             switch (state) {
104                 case RIP:
105                     LOG.info("Device {} has been disconnected", input.getNode());
106                     return Futures.immediateFailedFuture(new Exception(String.format(
107                         "Device connection doesn't exist anymore. Primary connection status : %s", state)));
108                 case WORKING:
109                     // We can proceed
110                     break;
111                 default:
112                     LOG.info("Device {} is in state {}, role change is not allowed", input.getNode(), state);
113                     return Futures.immediateCheckedFuture(RpcResultBuilder.<SetRoleOutput>failed().build());
114             }
115
116             ListenableFuture<SetRoleOutput> taskFuture = listeningExecutorService.submit(roleChangeTask);
117             LOG.info("RoleChangeTask submitted for execution");
118             CheckedFuture<SetRoleOutput, RoleChangeException> taskFutureChecked = Futures.makeChecked(taskFuture, EXCEPTION_FUNCTION);
119             try {
120                 SetRoleOutput setRoleOutput = taskFutureChecked.checkedGet(10, TimeUnit.SECONDS);
121                 LOG.info("setRoleOutput received after roleChangeTask execution:{}", setRoleOutput);
122                 lastKnownRoleRef.set(input.getControllerRole());
123                 return Futures.immediateFuture(RpcResultBuilder.<SetRoleOutput>success().withResult(setRoleOutput).build());
124
125             } catch (TimeoutException | RoleChangeException e) {
126                 roleChangeTask.incrementRetryCounter();
127                 LOG.info("Exception in setRole(), will retry: {} times.",
128                     MAX_RETRIES - roleChangeTask.getRetryCounter(), e);
129             }
130
131         } while (roleChangeTask.getRetryCounter() < MAX_RETRIES);
132
133         return Futures.immediateFailedFuture(new RoleChangeException(
134             "Set Role failed after " + MAX_RETRIES + "tries on device " + input.getNode().getValue()));
135     }
136
137     private static BigInteger getNextGenerationId(final BigInteger generationId) {
138         if (generationId.compareTo(MAX_GENERATION_ID) < 0) {
139             return generationId.add(BigInteger.ONE);
140         } else {
141             return BigInteger.ZERO;
142         }
143     }
144
145     private final class RoleChangeTask implements Callable<SetRoleOutput> {
146
147         private final OfpRole ofpRole;
148         private int retryCounter = 0;
149
150         RoleChangeTask(final OfpRole ofpRole) {
151             this.ofpRole = Preconditions.checkNotNull(ofpRole);
152         }
153
154         @Override
155         public SetRoleOutput call() throws RoleChangeException {
156             LOG.info("RoleChangeTask called on device:{} OFPRole:{}", nodeId.getValue(), ofpRole);
157
158             // we cannot move ahead without having the generation id, so block the thread till we get it.
159             final BigInteger generationId;
160             try {
161                 generationId = roleService.getGenerationIdFromDevice(version).get(10, TimeUnit.SECONDS);
162                 LOG.info("RoleChangeTask, GenerationIdFromDevice from device is {}", generationId);
163             } catch (Exception e ) {
164                 LOG.info("Exception in getting generationId for device:{}", nodeId.getValue(), e);
165                 throw new RoleChangeException("Exception in getting generationId for device:"+ nodeId.getValue(), e);
166             }
167
168             LOG.info("GenerationId received from device:{} is {}", nodeId.getValue(), generationId);
169             final BigInteger nextGenerationId = getNextGenerationId(generationId);
170             LOG.info("nextGenerationId received from device:{} is {}", nodeId.getValue(), nextGenerationId);
171
172             final SetRoleOutput setRoleOutput;
173             try {
174                 setRoleOutput = roleService.submitRoleChange(ofpRole, version, nextGenerationId).get(10 , TimeUnit.SECONDS);
175                 LOG.info("setRoleOutput after submitRoleChange:{}", setRoleOutput);
176
177             }  catch (InterruptedException | ExecutionException |  TimeoutException e) {
178                 LOG.error("Exception in making role change for device", e);
179                 throw new RoleChangeException("Exception in making role change for device:" + nodeId.getValue());
180             }
181
182             return setRoleOutput;
183         }
184
185         public void incrementRetryCounter() {
186             this.retryCounter++;
187         }
188
189         public int getRetryCounter() {
190             return retryCounter;
191         }
192     }
193 }