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