1
|
/**
|
2
|
* Copyright (C) 2013 EDIT
|
3
|
* European Distributed Institute of Taxonomy
|
4
|
* http://www.e-taxonomy.eu
|
5
|
*
|
6
|
* The contents of this file are subject to the Mozilla Public License Version 1.1
|
7
|
* See LICENSE.TXT at the top of this package for the full license terms.
|
8
|
*/
|
9
|
package eu.etaxonomy.cdm.server.instance;
|
10
|
|
11
|
import static eu.etaxonomy.cdm.server.AssumedMemoryRequirements.HEAP_CDMSERVER;
|
12
|
import static eu.etaxonomy.cdm.server.AssumedMemoryRequirements.HEAP_PER_INSTANCE;
|
13
|
import static eu.etaxonomy.cdm.server.AssumedMemoryRequirements.MB;
|
14
|
|
15
|
import java.io.File;
|
16
|
import java.io.IOException;
|
17
|
import java.util.List;
|
18
|
|
19
|
import org.apache.commons.collections.map.ListOrderedMap;
|
20
|
import org.apache.log4j.Logger;
|
21
|
import org.eclipse.jetty.util.component.LifeCycle;
|
22
|
|
23
|
import eu.etaxonomy.cdm.server.Bootloader;
|
24
|
import eu.etaxonomy.cdm.server.JvmManager;
|
25
|
|
26
|
/**
|
27
|
* Manager to load / reload list of instances the instance manager holds the
|
28
|
* list of {@link CdmInstance}, the list can be updated, the instances are
|
29
|
* identified by the bean id map<String,CdmInstance>
|
30
|
*
|
31
|
* The instance list is initially empty, all instances are usually started after
|
32
|
* loading the list, see {@#loadDataSources()}
|
33
|
*
|
34
|
* @author a.kohlbecker
|
35
|
* @date May 10, 2013
|
36
|
*/
|
37
|
public class InstanceManager implements LifeCycle.Listener {
|
38
|
|
39
|
private static final Logger logger = Logger.getLogger(InstanceManager.class);
|
40
|
|
41
|
private ListOrderedMap instances = new ListOrderedMap();
|
42
|
|
43
|
private final StartupQueue queue = new StartupQueue();
|
44
|
|
45
|
private final boolean austostart = true;
|
46
|
|
47
|
boolean serverIsRunning = false;
|
48
|
|
49
|
private File datasourcesFile;
|
50
|
|
51
|
/**
|
52
|
* @return the datasourcesFile
|
53
|
*/
|
54
|
public File getDatasourcesFile() {
|
55
|
return datasourcesFile;
|
56
|
}
|
57
|
|
58
|
/**
|
59
|
* @param datasourcesFile
|
60
|
* the datasourcesFile to set
|
61
|
*/
|
62
|
public void setDatasourcesFile(File datasourcesFile) {
|
63
|
this.datasourcesFile = datasourcesFile;
|
64
|
}
|
65
|
|
66
|
public InstanceManager(File configurationFile) {
|
67
|
this.datasourcesFile = configurationFile;
|
68
|
queue.setParallelStartUps(JvmManager.availableProcessors());
|
69
|
}
|
70
|
|
71
|
/**
|
72
|
* @return the {@link Bootloader} singelton instance
|
73
|
*/
|
74
|
private Bootloader bootloader() {
|
75
|
return Bootloader.getBootloader();
|
76
|
}
|
77
|
|
78
|
/**
|
79
|
* this list of instances may contain removed instances.
|
80
|
* {@link #numOfConfiguredInstances()}
|
81
|
*
|
82
|
* @return the instances
|
83
|
*/
|
84
|
@SuppressWarnings("unchecked")
|
85
|
public List<CdmInstance> getInstances() {
|
86
|
return instances.valueList();
|
87
|
}
|
88
|
|
89
|
public CdmInstance getInstance(String instanceName) {
|
90
|
return (CdmInstance) instances.get(instanceName);
|
91
|
}
|
92
|
|
93
|
/**
|
94
|
* Starts the instance
|
95
|
*
|
96
|
* Rebinds the JndiDataSource and starts the given instance. The method
|
97
|
* returns once the instance is fully started up.
|
98
|
*
|
99
|
* @param instance
|
100
|
* @throws Exception
|
101
|
*/
|
102
|
public void start(CdmInstance instance) {
|
103
|
instance.clearProblems();
|
104
|
if (instance.getWebAppContext() != null) {
|
105
|
// instance.unbindJndiDataSource();
|
106
|
// instance.bindJndiDataSource();
|
107
|
if (!instance.bindJndiDataSource()) {
|
108
|
// a problem with the datasource occurred skip this webapp
|
109
|
logger.error("a problem with the datasource occurred -> aborting startup of /" + instance.getName());
|
110
|
instance.setStatus(Status.error);
|
111
|
} else {
|
112
|
// ready for startup add to queue
|
113
|
if (logger.isDebugEnabled()) {
|
114
|
logger.debug("starting " + instance.getName());
|
115
|
}
|
116
|
queue.add(instance);
|
117
|
}
|
118
|
}
|
119
|
}
|
120
|
|
121
|
public void stop(CdmInstance instance) throws Exception {
|
122
|
// TODO do we need to
|
123
|
// bootloader().removeCdmInstanceContext(existingInstance); always?
|
124
|
// see reLoadInstanceConfigurations()
|
125
|
if (instance.getWebAppContext() != null) {
|
126
|
instance.getWebAppContext().stop();
|
127
|
}
|
128
|
instance.unbindJndiDataSource();
|
129
|
instance.getProblems().clear();
|
130
|
// explicitly set status stopped here to clear up prior error states
|
131
|
instance.setStatus(Status.stopped);
|
132
|
}
|
133
|
|
134
|
/**
|
135
|
* @return the number of existing instances, former instances which have
|
136
|
* been removed are not counted
|
137
|
*/
|
138
|
public int numOfConfiguredInstances() {
|
139
|
int cnt = 0;
|
140
|
for (CdmInstance instance : getInstances()) {
|
141
|
if (instance.getStatus().equals(Status.removed)) {
|
142
|
continue;
|
143
|
}
|
144
|
cnt++;
|
145
|
}
|
146
|
return cnt;
|
147
|
}
|
148
|
|
149
|
/**
|
150
|
* loads and reloads the list of instances. After loading the configuration
|
151
|
* the required memory is checked
|
152
|
* <p>
|
153
|
* reload behavior:
|
154
|
* <ol>
|
155
|
* <li>newly added instances are created but are not started automatically</li>
|
156
|
* <li>removed instances are stopped, configuration and context are removed,
|
157
|
* state is set to Status.removed to indicate removal, removed instances can
|
158
|
* be re-added.</li>
|
159
|
* <li>the order of instances as defined in the config file is always
|
160
|
* retained
|
161
|
* </ol>
|
162
|
*
|
163
|
*/
|
164
|
synchronized public void reLoadInstanceConfigurations() {
|
165
|
|
166
|
ListOrderedMap currentInstances = instances;
|
167
|
ListOrderedMap updatedInstances = new ListOrderedMap();
|
168
|
|
169
|
List<Configuration> configList = DataSourcePropertyParser.parseDataSourceConfigs(datasourcesFile);
|
170
|
logger.info("cdm server instance names loaded: " + configList.toString());
|
171
|
|
172
|
for (Configuration config : configList) {
|
173
|
String key = config.getInstanceName();
|
174
|
if (currentInstances.containsKey(key)) {
|
175
|
CdmInstance existingInstance = (CdmInstance) currentInstances.get(key);
|
176
|
if (!(existingInstance.getStatus().equals(Status.removed) && existingInstance.getWebAppContext() == null)) {
|
177
|
// re-added instance if not already removed (removed
|
178
|
// instances will not be re-added if they have been stopped
|
179
|
// successfully)
|
180
|
updatedInstances.put(key, existingInstance);
|
181
|
if (!(existingInstance).getConfiguration().equals(config)) {
|
182
|
// instance has changed: stop it, clear error states,
|
183
|
// set new configuration
|
184
|
try {
|
185
|
// TODO change problems into messages + severity
|
186
|
// (notice, error)
|
187
|
stop(existingInstance);
|
188
|
bootloader().removeCdmInstanceContext(existingInstance);
|
189
|
existingInstance.setConfiguration(config);
|
190
|
existingInstance.getProblems().add("Reloaded with modified configuration");
|
191
|
} catch (Exception e) {
|
192
|
existingInstance.getProblems().add(
|
193
|
"Error while stopping modified instance: " + e.getMessage());
|
194
|
logger.error(e, e);
|
195
|
}
|
196
|
}
|
197
|
}
|
198
|
} else {
|
199
|
// create and add a new instance
|
200
|
updatedInstances.put(key, new CdmInstance(config));
|
201
|
}
|
202
|
}
|
203
|
|
204
|
// find removed instances
|
205
|
for (Object keyOfExisting : currentInstances.keyList()) {
|
206
|
if (!updatedInstances.containsKey(keyOfExisting)) {
|
207
|
CdmInstance removedInstance = (CdmInstance) currentInstances.get(keyOfExisting);
|
208
|
|
209
|
if (removedInstance.getStatus().equals(Status.removed)) {
|
210
|
// instance already is removed, forget it now
|
211
|
continue;
|
212
|
}
|
213
|
|
214
|
// remove the instance but remember it until next config reload
|
215
|
updatedInstances.put(keyOfExisting, removedInstance);
|
216
|
removedInstance.setStatus(Status.removed);
|
217
|
removedInstance.getProblems().add("Removed from configuration and thus stopped");
|
218
|
try {
|
219
|
bootloader().removeCdmInstanceContext(removedInstance);
|
220
|
} catch (Exception e) {
|
221
|
logger.error(e.getMessage(), e);
|
222
|
}
|
223
|
|
224
|
}
|
225
|
}
|
226
|
|
227
|
instances = updatedInstances;
|
228
|
|
229
|
verifyMemoryRequirements();
|
230
|
|
231
|
if (serverIsRunning) {
|
232
|
addNewInstancesToServer(false);
|
233
|
}
|
234
|
}
|
235
|
|
236
|
private void addNewInstancesToServer(boolean austostart) {
|
237
|
for (CdmInstance instance : getInstances()) {
|
238
|
if (instance.getStatus().equals(Status.uninitialized)) {
|
239
|
try {
|
240
|
if (bootloader().addCdmInstanceContext(instance) != null && austostart) {
|
241
|
try {
|
242
|
start(instance);
|
243
|
} catch (Exception e) {
|
244
|
logger.error("Could not start " + instance.getWebAppContext().getContextPath(), e);
|
245
|
instance.getProblems().add(e.getMessage());
|
246
|
instance.setStatus(Status.error);
|
247
|
}
|
248
|
}
|
249
|
} catch (IOException e) {
|
250
|
logger.error(e, e); // TODO better throw?
|
251
|
}
|
252
|
}
|
253
|
}
|
254
|
}
|
255
|
|
256
|
@Override
|
257
|
public void lifeCycleFailure(LifeCycle event, Throwable cause) {
|
258
|
logger.error("Jetty LifeCycleFailure", cause);
|
259
|
}
|
260
|
|
261
|
@Override
|
262
|
public void lifeCycleStarted(LifeCycle event) {
|
263
|
serverIsRunning = true;
|
264
|
logger.info("cdmserver has started, now adding CDM server contexts");
|
265
|
addNewInstancesToServer(austostart);
|
266
|
|
267
|
}
|
268
|
|
269
|
@Override
|
270
|
public void lifeCycleStarting(LifeCycle event) {
|
271
|
}
|
272
|
|
273
|
@Override
|
274
|
public void lifeCycleStopped(LifeCycle event) {
|
275
|
serverIsRunning = false;
|
276
|
}
|
277
|
|
278
|
@Override
|
279
|
public void lifeCycleStopping(LifeCycle event) {
|
280
|
serverIsRunning = false;
|
281
|
}
|
282
|
|
283
|
private void verifyMemoryRequirements() {
|
284
|
|
285
|
verifyMemoryRequirement("HeapSpace", HEAP_CDMSERVER, HEAP_PER_INSTANCE, JvmManager.getHeapMemoryUsage()
|
286
|
.getMax());
|
287
|
}
|
288
|
|
289
|
private void verifyMemoryRequirement(String memoryName, long requiredSpaceServer, long requiredSpacePerInstance,
|
290
|
long availableSpace) {
|
291
|
|
292
|
long recommendedMinimumSpace = recommendedMinimumSpace(requiredSpaceServer, requiredSpacePerInstance, null);
|
293
|
|
294
|
if (recommendedMinimumSpace > availableSpace) {
|
295
|
|
296
|
String message = memoryName + " (" + (availableSpace / MB) + "MB) insufficient for "
|
297
|
+ numOfConfiguredInstances() + " instances. Increase " + memoryName + " to "
|
298
|
+ (recommendedMinimumSpace / MB) + "MB";
|
299
|
|
300
|
logger.error(message + " => disabling some instances!!!");
|
301
|
|
302
|
// disabling some instances
|
303
|
int i = 0;
|
304
|
for (CdmInstance instance : getInstances()) {
|
305
|
i++;
|
306
|
if (recommendedMinimumSpace(requiredSpaceServer, requiredSpacePerInstance, i) > availableSpace) {
|
307
|
instance.setStatus(Status.disabled);
|
308
|
instance.getProblems().add("Disabled due to: " + message);
|
309
|
}
|
310
|
}
|
311
|
}
|
312
|
}
|
313
|
|
314
|
/**
|
315
|
* @param requiredServerSpace
|
316
|
* @param requiredSpacePerIntance
|
317
|
* @param numOfInstances
|
318
|
* may be null, the total number of instances found in the
|
319
|
* current configuration is used in this case.
|
320
|
* @return
|
321
|
*/
|
322
|
public long recommendedMinimumSpace(long requiredServerSpace, long requiredSpacePerIntance, Integer numOfInstances) {
|
323
|
if (numOfInstances == null) {
|
324
|
numOfInstances = numOfConfiguredInstances();
|
325
|
}
|
326
|
return (numOfInstances * requiredSpacePerIntance) + requiredServerSpace;
|
327
|
}
|
328
|
|
329
|
}
|