blob: 7a8372499566736b5daa5621093f4c8c7f0f81e6 [file] [log] [blame]
Jordan Halterman980a8c12017-09-22 18:01:19 -07001/*
2 * Copyright 2017-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.onosproject.upgrade.impl;
17
18import java.util.Objects;
Jordan Halterman5ca07932017-10-07 13:28:22 -070019import java.util.Set;
Jordan Halterman980a8c12017-09-22 18:01:19 -070020import java.util.concurrent.atomic.AtomicReference;
Jordan Halterman5ca07932017-10-07 13:28:22 -070021import java.util.stream.Collectors;
Jordan Halterman980a8c12017-09-22 18:01:19 -070022
23import org.apache.felix.scr.annotations.Activate;
24import org.apache.felix.scr.annotations.Component;
25import org.apache.felix.scr.annotations.Deactivate;
26import org.apache.felix.scr.annotations.Reference;
27import org.apache.felix.scr.annotations.ReferenceCardinality;
28import org.apache.felix.scr.annotations.Service;
Jordan Halterman5ca07932017-10-07 13:28:22 -070029import org.onosproject.cluster.ClusterEvent;
30import org.onosproject.cluster.ClusterEventListener;
Jordan Halterman28183ee2017-10-17 17:29:10 -070031import org.onosproject.cluster.ClusterService;
Jordan Halterman980a8c12017-09-22 18:01:19 -070032import org.onosproject.cluster.ControllerNode;
Jordan Halterman28183ee2017-10-17 17:29:10 -070033import org.onosproject.cluster.MembershipService;
Jordan Halterman5ca07932017-10-07 13:28:22 -070034import org.onosproject.cluster.NodeId;
Jordan Halterman980a8c12017-09-22 18:01:19 -070035import org.onosproject.core.Version;
36import org.onosproject.core.VersionService;
37import org.onosproject.event.AbstractListenerManager;
38import org.onosproject.store.serializers.KryoNamespaces;
39import org.onosproject.store.service.AtomicValue;
40import org.onosproject.store.service.AtomicValueEvent;
41import org.onosproject.store.service.AtomicValueEventListener;
42import org.onosproject.store.service.CoordinationService;
43import org.onosproject.store.service.Serializer;
44import org.onosproject.upgrade.Upgrade;
45import org.onosproject.upgrade.UpgradeAdminService;
46import org.onosproject.upgrade.UpgradeEvent;
47import org.onosproject.upgrade.UpgradeEventListener;
48import org.onosproject.upgrade.UpgradeService;
49import org.slf4j.Logger;
50
slowr0a44fde2017-10-09 14:48:53 -070051import static org.onosproject.security.AppGuard.checkPermission;
52import static org.onosproject.security.AppPermission.Type.*;
Jordan Halterman980a8c12017-09-22 18:01:19 -070053import static org.slf4j.LoggerFactory.getLogger;
54
55/**
56 * Upgrade service implementation.
57 * <p>
58 * This implementation uses the {@link CoordinationService} to store upgrade state in a version-agnostic primitive.
59 * Upgrade state can be seen by current and future version nodes.
60 */
61@Component(immediate = true)
62@Service
63public class UpgradeManager
64 extends AbstractListenerManager<UpgradeEvent, UpgradeEventListener>
65 implements UpgradeService, UpgradeAdminService {
66
67 private final Logger log = getLogger(getClass());
68
69 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
70 protected VersionService versionService;
71
72 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
73 protected CoordinationService coordinationService;
74
75 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Jordan Halterman28183ee2017-10-17 17:29:10 -070076 protected ClusterService clusterService;
77
78 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
79 protected MembershipService membershipService;
Jordan Halterman980a8c12017-09-22 18:01:19 -070080
81 private Version localVersion;
82 private AtomicValue<Upgrade> state;
83 private final AtomicReference<Upgrade> currentState = new AtomicReference<>();
slowr0a44fde2017-10-09 14:48:53 -070084 private final AtomicValueEventListener<Upgrade> stateListener = this::handleUpgradeEvent;
85 private final ClusterEventListener clusterListener = this::handleClusterEvent;
Jordan Halterman980a8c12017-09-22 18:01:19 -070086
87 @Activate
88 public void activate() {
89 state = coordinationService.<Upgrade>atomicValueBuilder()
90 .withName("onos-upgrade-state")
91 .withSerializer(Serializer.using(KryoNamespaces.API))
92 .build()
93 .asAtomicValue();
94 localVersion = versionService.version();
95
96 currentState.set(state.get());
slowr0a44fde2017-10-09 14:48:53 -070097 if (getState() == null) {
98 initializeState(new Upgrade(localVersion, localVersion, Upgrade.Status.INACTIVE));
Jordan Halterman980a8c12017-09-22 18:01:19 -070099 }
100
slowr0a44fde2017-10-09 14:48:53 -0700101 Upgrade upgrade = getState();
Jordan Halterman980a8c12017-09-22 18:01:19 -0700102
103 // If the upgrade state is not initialized, ensure this node matches the version of the cluster.
104 if (!upgrade.status().active() && !Objects.equals(upgrade.source(), localVersion)) {
105 log.error("Node version {} inconsistent with cluster version {}", localVersion, upgrade.source());
106 throw new IllegalStateException("Node version " + localVersion +
107 " inconsistent with cluster version " + upgrade.source());
108 }
109
110 // If the upgrade state is initialized then check the node version.
111 if (upgrade.status() == Upgrade.Status.INITIALIZED) {
112 // If the source version equals the target version, attempt to update the target version.
113 if (Objects.equals(upgrade.source(), upgrade.target()) && !Objects.equals(upgrade.target(), localVersion)) {
slowr0a44fde2017-10-09 14:48:53 -0700114 checkPermission(UPGRADE_WRITE);
Jordan Halterman980a8c12017-09-22 18:01:19 -0700115 upgrade = new Upgrade(upgrade.source(), localVersion, upgrade.status());
slowr0a44fde2017-10-09 14:48:53 -0700116 initializeState(upgrade);
Jordan Halterman980a8c12017-09-22 18:01:19 -0700117 }
118 }
119
120 // If the upgrade status is active, verify that the local version matches the upgrade version.
121 if (upgrade.status().active() && !Objects.equals(upgrade.source(), upgrade.target())) {
122 // If the upgrade source/target are not equal, validate that the node's version is consistent
123 // with versions in the upgrade. There are two possibilities: that a not-yet-upgraded node is being
124 // restarted, or that a node has been upgraded, so we need to check that this node is running either
125 // the source or target version.
126 if (!Objects.equals(localVersion, upgrade.source()) && !Objects.equals(localVersion, upgrade.target())) {
127 log.error("Cannot upgrade node to version {}; Upgrade to {} already in progress",
128 localVersion, upgrade.target());
129 throw new IllegalStateException("Cannot upgrade node to version " + localVersion + "; Upgrade to " +
130 upgrade.target() + " already in progress");
131 }
132 }
133
134 state.addListener(stateListener);
Jordan Halterman5ca07932017-10-07 13:28:22 -0700135 clusterService.addListener(clusterListener);
Jordan Halterman980a8c12017-09-22 18:01:19 -0700136 log.info("Started");
137 }
138
139 @Deactivate
140 public void deactivate() {
141 state.removeListener(stateListener);
Jordan Halterman5ca07932017-10-07 13:28:22 -0700142 clusterService.removeListener(clusterListener);
Jordan Halterman980a8c12017-09-22 18:01:19 -0700143 log.info("Stopped");
144 }
145
slowr0a44fde2017-10-09 14:48:53 -0700146 /**
147 * Initializes the state when the cluster starts.
148 * <p>
149 * This method must be called when updating the state in order to check the permissions
150 *
151 * @param newState new state
152 */
153 private void initializeState(Upgrade newState) {
154 checkPermission(UPGRADE_WRITE);
155 currentState.set(newState);
156 state.set(newState);
157 }
158
159 /**
160 * Changes the current state to new one.
161 * <p>
162 * This method must be called when changing between states in order to check the permissions and
163 * to avoid concurrent state modifications
164 *
165 * @param oldState current upgrade state
166 * @param newState new upgrade state
167 *
168 * @throws IllegalStateException if an upgrade is already in progress
169 */
170 private void changeState(Upgrade oldState, Upgrade newState) {
171 checkPermission(UPGRADE_WRITE);
172 if (!state.compareAndSet(oldState, newState)) {
173 throw new IllegalStateException("Concurrent upgrade modification");
174 } else {
175 currentState.set(newState);
176 }
177 }
178
179 @Override
180 public Upgrade getState() {
181 checkPermission(UPGRADE_READ);
182 return currentState.get();
183 }
184
Jordan Halterman980a8c12017-09-22 18:01:19 -0700185 @Override
186 public boolean isUpgrading() {
187 return getState().status().active();
188 }
189
190 @Override
Jordan Halterman980a8c12017-09-22 18:01:19 -0700191 public Version getVersion() {
slowr0a44fde2017-10-09 14:48:53 -0700192 Upgrade upgrade = getState();
Jordan Halterman980a8c12017-09-22 18:01:19 -0700193 return upgrade.status().upgraded()
194 ? upgrade.target()
195 : upgrade.source();
196 }
197
198 @Override
199 public boolean isLocalActive() {
200 return localVersion.equals(getVersion());
201 }
202
203 @Override
204 public boolean isLocalUpgraded() {
slowr0a44fde2017-10-09 14:48:53 -0700205 Upgrade upgrade = getState();
Jordan Halterman980a8c12017-09-22 18:01:19 -0700206 return upgrade.status().active()
207 && !upgrade.source().equals(upgrade.target())
208 && localVersion.equals(upgrade.target());
209 }
210
211 @Override
212 public void initialize() {
slowr0a44fde2017-10-09 14:48:53 -0700213 Upgrade inactive = getState();
Jordan Halterman980a8c12017-09-22 18:01:19 -0700214
215 // If the current upgrade status is active, fail initialization.
216 if (inactive.status().active()) {
217 throw new IllegalStateException("Upgrade already active");
218 }
219
220 // Set the upgrade status to INITIALIZING.
221 Upgrade initializing = new Upgrade(
222 localVersion,
223 localVersion,
224 Upgrade.Status.INITIALIZING);
slowr0a44fde2017-10-09 14:48:53 -0700225 changeState(inactive, initializing);
Jordan Halterman980a8c12017-09-22 18:01:19 -0700226
slowr0a44fde2017-10-09 14:48:53 -0700227 // Set the upgrade status to INITIALIZED.
228 Upgrade initialized = new Upgrade(
229 initializing.source(),
230 initializing.target(),
231 Upgrade.Status.INITIALIZED);
232 changeState(initializing, initialized);
Jordan Halterman980a8c12017-09-22 18:01:19 -0700233 }
234
235 @Override
236 public void upgrade() {
slowr0a44fde2017-10-09 14:48:53 -0700237 Upgrade initialized = getState();
Jordan Halterman980a8c12017-09-22 18:01:19 -0700238
239 // If the current upgrade status is not INITIALIZED, throw an exception.
240 if (initialized.status() != Upgrade.Status.INITIALIZED) {
241 throw new IllegalStateException("Upgrade not initialized");
242 }
243
244 // Set the upgrade status to UPGRADING.
245 Upgrade upgrading = new Upgrade(
246 initialized.source(),
247 initialized.target(),
248 Upgrade.Status.UPGRADING);
slowr0a44fde2017-10-09 14:48:53 -0700249 changeState(initialized, upgrading);
Jordan Halterman980a8c12017-09-22 18:01:19 -0700250
slowr0a44fde2017-10-09 14:48:53 -0700251 // Set the upgrade status to UPGRADED.
252 Upgrade upgraded = new Upgrade(
253 upgrading.source(),
254 upgrading.target(),
255 Upgrade.Status.UPGRADED);
256 changeState(upgrading, upgraded);
Jordan Halterman980a8c12017-09-22 18:01:19 -0700257 }
258
259 @Override
260 public void commit() {
slowr0a44fde2017-10-09 14:48:53 -0700261 Upgrade upgraded = getState();
Jordan Halterman980a8c12017-09-22 18:01:19 -0700262
263 // If the current upgrade status is not UPGRADED, throw an exception.
264 if (upgraded.status() != Upgrade.Status.UPGRADED) {
265 throw new IllegalStateException("Upgrade not performed");
266 }
267
268 // Determine whether any nodes have not been upgraded to the target version.
Jordan Halterman28183ee2017-10-17 17:29:10 -0700269 boolean upgradeComplete = membershipService.getGroups().size() == 1
270 && membershipService.getLocalGroup().version().equals(upgraded.target());
Jordan Halterman980a8c12017-09-22 18:01:19 -0700271
272 // If some nodes have not yet been upgraded, throw an exception.
273 if (!upgradeComplete) {
274 throw new IllegalStateException("Some nodes have not yet been upgraded to version " + upgraded.target());
275 }
276
277 // Set the upgrade status to COMMITTING.
278 Upgrade committing = new Upgrade(
279 upgraded.source(),
280 upgraded.target(),
281 Upgrade.Status.COMMITTING);
slowr0a44fde2017-10-09 14:48:53 -0700282 changeState(upgraded, committing);
Jordan Halterman980a8c12017-09-22 18:01:19 -0700283
slowr0a44fde2017-10-09 14:48:53 -0700284 // Set the upgrade status to COMMITTED.
285 Upgrade committed = new Upgrade(
286 committing.source(),
287 committing.target(),
288 Upgrade.Status.COMMITTED);
289 changeState(committing, committed);
Jordan Halterman980a8c12017-09-22 18:01:19 -0700290
slowr0a44fde2017-10-09 14:48:53 -0700291 // Set the upgrade status to INACTIVE.
292 Upgrade inactive = new Upgrade(
293 localVersion,
294 localVersion,
295 Upgrade.Status.INACTIVE);
296 changeState(committed, inactive);
Jordan Halterman980a8c12017-09-22 18:01:19 -0700297 }
298
299 @Override
300 public void rollback() {
slowr0a44fde2017-10-09 14:48:53 -0700301 Upgrade upgraded = getState();
Jordan Halterman980a8c12017-09-22 18:01:19 -0700302
303 // If the current upgrade status is not UPGRADED, throw an exception.
304 if (upgraded.status() != Upgrade.Status.UPGRADED) {
305 throw new IllegalStateException("Upgrade not performed");
306 }
307
308 // Set the upgrade status to ROLLING_BACK.
309 Upgrade rollingBack = new Upgrade(
310 upgraded.source(),
311 upgraded.target(),
312 Upgrade.Status.ROLLING_BACK);
slowr0a44fde2017-10-09 14:48:53 -0700313 changeState(upgraded, rollingBack);
Jordan Halterman980a8c12017-09-22 18:01:19 -0700314
slowr0a44fde2017-10-09 14:48:53 -0700315 // Set the upgrade status to ROLLED_BACK.
316 Upgrade rolledBack = new Upgrade(
317 rollingBack.source(),
318 rollingBack.target(),
319 Upgrade.Status.ROLLED_BACK);
320 changeState(rollingBack, rolledBack);
Jordan Halterman980a8c12017-09-22 18:01:19 -0700321 }
322
323 @Override
324 public void reset() {
slowr0a44fde2017-10-09 14:48:53 -0700325 Upgrade upgraded = getState();
Jordan Halterman980a8c12017-09-22 18:01:19 -0700326
327 // If the current upgrade status is not INITIALIZED or ROLLED_BACK, throw an exception.
328 if (upgraded.status() != Upgrade.Status.INITIALIZED
329 && upgraded.status() != Upgrade.Status.ROLLED_BACK) {
330 throw new IllegalStateException("Upgrade not rolled back");
331 }
332
333 // Determine whether any nodes are still running the target version.
Jordan Halterman28183ee2017-10-17 17:29:10 -0700334 boolean rollbackComplete = membershipService.getGroups().size() == 1
335 && membershipService.getLocalGroup().version().equals(upgraded.source());
Jordan Halterman980a8c12017-09-22 18:01:19 -0700336
337 // If some nodes have not yet been downgraded, throw an exception.
338 if (!rollbackComplete) {
339 throw new IllegalStateException("Some nodes have not yet been downgraded to version " + upgraded.source());
340 }
341
342 // Set the upgrade status to RESETTING.
343 Upgrade resetting = new Upgrade(
344 upgraded.source(),
345 upgraded.target(),
346 Upgrade.Status.RESETTING);
slowr0a44fde2017-10-09 14:48:53 -0700347 changeState(upgraded, resetting);
Jordan Halterman980a8c12017-09-22 18:01:19 -0700348
slowr0a44fde2017-10-09 14:48:53 -0700349 // Set the upgrade status to RESET.
350 Upgrade reset = new Upgrade(
351 resetting.source(),
352 resetting.target(),
353 Upgrade.Status.RESET);
354 changeState(resetting, reset);
Jordan Halterman980a8c12017-09-22 18:01:19 -0700355
slowr0a44fde2017-10-09 14:48:53 -0700356 // Set the upgrade status to INACTIVE.
357 Upgrade inactive = new Upgrade(
358 localVersion,
359 localVersion,
360 Upgrade.Status.INACTIVE);
361 changeState(reset, inactive);
Jordan Halterman980a8c12017-09-22 18:01:19 -0700362 }
363
Jordan Halterman5ca07932017-10-07 13:28:22 -0700364 /**
365 * Handles a cluster event.
366 *
367 * @param event the cluster event
368 */
369 protected void handleClusterEvent(ClusterEvent event) {
slowr0a44fde2017-10-09 14:48:53 -0700370 checkPermission(CLUSTER_EVENT);
Jordan Halterman5ca07932017-10-07 13:28:22 -0700371 // If an instance was deactivated, check whether we need to roll back the upgrade.
372 if (event.type() == ClusterEvent.Type.INSTANCE_DEACTIVATED) {
slowr0a44fde2017-10-09 14:48:53 -0700373 Upgrade upgrade = getState();
Jordan Halterman5ca07932017-10-07 13:28:22 -0700374 if (upgrade.status().upgraded()) {
375 // Get the upgraded subset of the cluster and check whether the down node is a member
376 // of the upgraded subset. If so, roll back the upgrade to tolerate the failure.
377 Set<NodeId> upgradedNodes = clusterService.getNodes().stream()
378 .map(ControllerNode::id)
379 .filter(id -> clusterService.getVersion(id).equals(upgrade.target()))
380 .collect(Collectors.toSet());
381 if (upgradedNodes.contains(event.subject().id())) {
382 rollback();
383 }
384 }
385 }
386 }
387
388 /**
389 * Handles an upgrade state event.
390 *
391 * @param event the upgrade value event
392 */
393 protected void handleUpgradeEvent(AtomicValueEvent<Upgrade> event) {
slowr0a44fde2017-10-09 14:48:53 -0700394 checkPermission(UPGRADE_EVENT);
Jordan Halterman980a8c12017-09-22 18:01:19 -0700395 currentState.set(event.newValue());
396 switch (event.newValue().status()) {
397 case INITIALIZED:
398 post(new UpgradeEvent(UpgradeEvent.Type.INITIALIZED, event.newValue()));
399 break;
400 case UPGRADED:
401 post(new UpgradeEvent(UpgradeEvent.Type.UPGRADED, event.newValue()));
402 break;
403 case COMMITTED:
404 post(new UpgradeEvent(UpgradeEvent.Type.COMMITTED, event.newValue()));
405 break;
406 case ROLLED_BACK:
407 post(new UpgradeEvent(UpgradeEvent.Type.ROLLED_BACK, event.newValue()));
408 break;
409 case RESET:
410 post(new UpgradeEvent(UpgradeEvent.Type.RESET, event.newValue()));
411 break;
412 default:
413 break;
414 }
415 }
416}