1 /*
2 * Copyright 2024 LINE Corporation
3 *
4 * LINE Corporation licenses this file to you under the Apache License,
5 * version 2.0 (the "License"); you may not use this file except in compliance
6 * with the License. You may obtain a copy of the License at:
7 *
8 * https://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, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16
17 package com.linecorp.centraldogma.server.command;
18
19 import org.slf4j.Logger;
20 import org.slf4j.LoggerFactory;
21
22 import com.google.common.base.MoreObjects;
23
24 import com.linecorp.centraldogma.server.management.ServerStatus;
25 import com.linecorp.centraldogma.server.management.ServerStatusManager;
26
27 /**
28 * Manages the status of a {@link CommandExecutor}.
29 */
30 public final class CommandExecutorStatusManager {
31
32 private static final Logger logger = LoggerFactory.getLogger(ServerStatusManager.class);
33
34 private final CommandExecutor executor;
35
36 /**
37 * Creates a new instance.
38 */
39 public CommandExecutorStatusManager(CommandExecutor executor) {
40 this.executor = executor;
41 }
42
43 /**
44 * Returns the executor that this {@link CommandExecutorStatusManager} manages.
45 */
46 public CommandExecutor executor() {
47 return executor;
48 }
49
50 /**
51 * Returns whether the {@link #executor()} is writable.
52 */
53 public boolean writable() {
54 return executor.isWritable();
55 }
56
57 /**
58 * Returns whether the {@link #executor()} is replicating.
59 */
60 public boolean replicating() {
61 return executor.isStarted();
62 }
63
64 /**
65 * Updates the status of the executor with the specified {@link UpdateServerStatusCommand}.
66 *
67 * <p>This method could take a long time if the executor is not in the desired state yet.
68 * So it should be not called from an event loop thread.
69 */
70 public synchronized void updateStatus(UpdateServerStatusCommand command) {
71 final ServerStatus serverStatus = command.serverStatus();
72 updateStatus(serverStatus);
73 }
74
75 /**
76 * Updates the status of the executor with the specified {@link ServerStatus}.
77 *
78 * <p>This method could take a long time if the executor is not in the desired state yet.
79 * So it should be not called from an event loop thread.
80 */
81 public synchronized void updateStatus(ServerStatus serverStatus) {
82 if (serverStatus.replicating()) {
83 // Replicating mode is enabled first to write data to the cluster.
84 setReplicating(serverStatus.replicating());
85 setWritable(serverStatus.writable());
86 } else {
87 // For graceful transition, writable mode is disabled first.
88 setWritable(serverStatus.writable());
89 setReplicating(serverStatus.replicating());
90 }
91 }
92
93 /**
94 * Sets the executor to read/write mode or read-only mode.
95 * @return {@code true} if the executor is already in the specified mode, or the mode has been updated
96 * successfully.
97 */
98 public synchronized boolean setWritable(boolean newWritable) {
99 final boolean writable = writable();
100 if (writable == newWritable) {
101 return true;
102 }
103 executor.setWritable(newWritable);
104 if (newWritable) {
105 logger.warn("Left read-only mode.");
106 } else {
107 logger.warn("Entered read-only mode. replication: {}", replicating());
108 }
109 return true;
110 }
111
112 /**
113 * Sets the executor to replicating mode or non-replicating mode.
114 *
115 * <p>This method could take a long time if the executor is not in the desired state yet.
116 * So it should be not called from an event loop thread.
117 *
118 * @return {@code true} if the executor is already in the specified mode, or the mode has been updated
119 * successfully.
120 */
121 public synchronized boolean setReplicating(boolean newReplicating) {
122 if (newReplicating) {
123 if (replicating()) {
124 return true;
125 }
126 try {
127 logger.info("Enabling replication ...");
128 executor.start().get();
129 logger.info("Enabled replication. read-only: {}", !writable());
130 return true;
131 } catch (Exception cause) {
132 logger.warn("Failed to start the command executor:", cause);
133 return false;
134 }
135 } else {
136 if (!replicating()) {
137 return true;
138 }
139 try {
140 logger.info("Disabling replication ...");
141 executor.stop().get();
142 logger.info("Disabled replication");
143 return true;
144 } catch (Exception cause) {
145 logger.warn("Failed to stop the command executor:", cause);
146 return false;
147 }
148 }
149 }
150
151 @Override
152 public String toString() {
153 return MoreObjects.toStringHelper(this)
154 .add("isWritable", writable())
155 .add("replicating", replicating())
156 .toString();
157 }
158 }