View Javadoc
1   package emissary.command;
2   
3   import emissary.client.EmissaryResponse;
4   import emissary.command.converter.ModeConverter;
5   import emissary.command.converter.ProjectBaseConverter;
6   import emissary.directory.EmissaryNode;
7   import emissary.server.EmissaryServer;
8   import emissary.server.api.Pause;
9   
10  import org.slf4j.Logger;
11  import org.slf4j.LoggerFactory;
12  import picocli.CommandLine.Command;
13  import picocli.CommandLine.Option;
14  
15  import java.nio.file.Path;
16  import java.util.LinkedHashSet;
17  import java.util.Locale;
18  import java.util.Set;
19  import java.util.concurrent.TimeUnit;
20  
21  import static emissary.directory.EmissaryNode.STRICT_STARTUP_MODE;
22  
23  @Command(description = "Start an Emissary jetty server", subcommands = {HelpCommand.class})
24  public class ServerCommand extends ServiceCommand {
25      private static final Logger LOG = LoggerFactory.getLogger(ServerCommand.class);
26  
27      public static final String COMMAND_NAME = "server";
28  
29      public static final int DEFAULT_PORT = 8001;
30  
31      @Option(names = {"-m", "--mode"}, description = "mode: standalone or cluster\nDefault: ${DEFAULT-VALUE}", converter = ModeConverter.class,
32              defaultValue = "standalone")
33      private EmissaryNode.Mode mode;
34  
35      @Option(names = "--staticDir", description = "path to static assets, loaded from classpath otherwise", converter = ProjectBaseConverter.class)
36      private Path staticDir;
37  
38      @Option(names = {"-a", "--agents"}, description = "number of mobile agents (default is based on memory)\nDefault: ${DEFAULT-VALUE}")
39      private int agents;
40  
41      @Option(names = {"-t", "--timeout"}, description = "max amount of time to attempt a server refresh (in minutes) \nDefault: ${DEFAULT-VALUE}")
42      private int timeout;
43  
44      @Option(names = {"--dumpJettyBeans"}, description = "dump all the jetty beans that loaded\nDefault: ${DEFAULT-VALUE}")
45      private boolean dumpJettyBeans = false;
46  
47      @Option(names = {"--strict"}, description = "If one Place fails to start, shut down the entire server\nDefault: ${DEFAULT-VALUE}")
48      private boolean strictMode = false;
49  
50      @Override
51      public String getCommandName() {
52          return COMMAND_NAME;
53      }
54  
55      @Override
56      public int getDefaultPort() {
57          return DEFAULT_PORT;
58      }
59  
60      public EmissaryNode.Mode getMode() {
61          return mode;
62      }
63  
64      public Path getStaticDir() {
65          return staticDir;
66      }
67  
68      public int getAgents() {
69          return agents;
70      }
71  
72      public long getTimeout() {
73          return timeout;
74      }
75  
76      public boolean shouldDumpJettyBeans() {
77          return dumpJettyBeans;
78      }
79  
80      /**
81       * If strictMode is set to true, the server will shut down if a Place fails to start
82       * 
83       * @return strictMode
84       */
85      public boolean shouldStrictMode() {
86          return strictMode;
87      }
88  
89      @Override
90      public void setupCommand() {
91          setupHttp();
92          if (getTimeout() > 0) {
93              System.setProperty(EmissaryNode.NODE_REFRESH_TIMEOUT_PROPERTY, String.valueOf(TimeUnit.MINUTES.toMillis(getTimeout())));
94          }
95  
96          reinitLogback();
97          setupServer();
98      }
99  
100     public void setupServer() {
101         String flavorMode;
102         if (getFlavor() == null) {
103             flavorMode = getMode().toString();
104         } else {
105             flavorMode = getMode().toString() + "," + getFlavor();
106         }
107 
108         if (shouldStrictMode()) {
109             System.setProperty(STRICT_STARTUP_MODE, "true");
110         }
111 
112         // Must maintain insertion order
113         Set<String> flavorSet = new LinkedHashSet<>();
114         for (String f : flavorMode.split(",")) {
115             flavorSet.add(f.toUpperCase(Locale.getDefault()));
116         }
117 
118         if (flavorSet.contains("STANDALONE") && flavorSet.contains("CLUSTER")) {
119             throw new IllegalArgumentException("Can not run a server in both STANDALONE and CLUSTER");
120         } else {
121             overrideFlavor(String.join(",", flavorSet));
122         }
123     }
124 
125     @Override
126     protected void startService() {
127         LOG.info("Running Emissary Server");
128         EmissaryServer.init(this).startServer();
129     }
130 
131     @Override
132     protected void refreshService() {
133         EmissaryResponse response = performPost(getServiceRefreshEndpoint());
134         if (response.getStatus() != 200) {
135             LOG.error("Failed to {} Emissary services: {}", isInvalidate() ? "invalidate" : "refresh", response.getContentString());
136         } else {
137             LOG.info("{} Emissary services", isInvalidate() ? "Invalidating" : "Refreshing");
138         }
139     }
140 
141     @Override
142     protected void pauseService() {
143         setServerState(Pause.PAUSE);
144     }
145 
146     @Override
147     protected void unpauseService() {
148         setServerState(Pause.UNPAUSE);
149     }
150 
151     protected void setServerState(String state) {
152         LOG.debug("Setting state to {} for EmissaryServer", state);
153         EmissaryResponse response = performPost("/api/" + state);
154         if (response.getStatus() != 200) {
155             LOG.error("Setting Emissary server state to {} failed -- {}", state, response.getContentString());
156         } else {
157             LOG.info("Setting Emissary server state to {} successful", state);
158         }
159     }
160 }