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 }