Skip to content

Commit 8081337

Browse files
Allow disabling modules and extensions from module.properties
Discovery of modules is based on classpath scanning. In some situations it may not be possible or desirable to change the classpath. To force a module to not load you can create a file called modules.properties on the classpath that can exclude specific modules from loading. Additionally this same file can be used to exclude a specific extension. Extension loading is typically done through global configuration. If you want to set up an environment and you don't even want the extension/module loaded on the first start, then using the config file is appropriate. Example: modules.properties modules.exclude=storage-image-s3,storage-volume-solidfire extensions.exclude=ClusterScopeStoragePoolAllocator,ZoneWideStoragePoolAllocator Typically you would want to place this file in /etc/cloudstack/management
1 parent 0ef6135 commit 8081337

15 files changed

Lines changed: 396 additions & 18 deletions

File tree

framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/registry/ExtensionRegistry.java

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
import org.slf4j.LoggerFactory;
3535
import org.springframework.beans.factory.BeanNameAware;
3636

37-
import com.cloud.utils.component.Named;
3837
import com.cloud.utils.component.Registry;
3938

4039
public class ExtensionRegistry implements Registry<Object>, Configurable, BeanNameAware {
@@ -81,7 +80,7 @@ public boolean register(Object item) {
8180
}
8281
}
8382

84-
String name = getName(item);
83+
String name = RegistryUtils.getName(item);
8584

8685
if ( name != null && exclude.size() > 0 && exclude.contains(name) ) {
8786
return false;
@@ -103,7 +102,7 @@ public boolean register(Object item) {
103102
break;
104103
}
105104

106-
if ( getName(registered.get(i)).equals(orderTest) ) {
105+
if ( RegistryUtils.getName(registered.get(i)).equals(orderTest) ) {
107106
i++;
108107
}
109108
}
@@ -116,16 +115,6 @@ public boolean register(Object item) {
116115

117116
return true;
118117
}
119-
120-
protected String getName(Object object) {
121-
if ( object instanceof Named ) {
122-
String name = ((Named)object).getName();
123-
if ( name != null )
124-
return name;
125-
}
126-
127-
return object == null ? null : object.getClass().getSimpleName();
128-
}
129118

130119
@Override
131120
public void unregister(Object type) {

framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/registry/RegistryLifecycle.java

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import java.util.HashSet;
2222
import java.util.Iterator;
23+
import java.util.Properties;
2324
import java.util.Set;
2425

2526
import org.slf4j.Logger;
@@ -29,13 +30,17 @@
2930
import org.springframework.context.ApplicationContext;
3031
import org.springframework.context.ApplicationContextAware;
3132
import org.springframework.context.SmartLifecycle;
33+
import org.springframework.util.StringUtils;
3234

3335
import com.cloud.utils.component.Registry;
3436

3537
public class RegistryLifecycle implements BeanPostProcessor, SmartLifecycle, ApplicationContextAware {
3638

3739
private static final Logger log = LoggerFactory.getLogger(RegistryLifecycle.class);
3840

41+
public static final String EXTENSION_EXCLUDE = "extensions.exclude";
42+
public static final String EXTENSION_INCLUDE_PREFIX = "extensions.include.";
43+
3944
Registry<Object> registry;
4045

4146
/* The bean name works around circular dependency issues in Spring. This shouldn't be
@@ -46,15 +51,52 @@ public class RegistryLifecycle implements BeanPostProcessor, SmartLifecycle, App
4651
Set<Object> beans = new HashSet<Object>();
4752
Class<?> typeClass;
4853
ApplicationContext applicationContext;
54+
Set<String> excludes = null;
4955

5056
@Override
5157
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
52-
if ( typeClass.isAssignableFrom(bean.getClass()) )
58+
if ( typeClass.isAssignableFrom(bean.getClass()) && ! isExcluded(bean) ) {
5359
beans.add(bean);
54-
60+
}
61+
5562
return bean;
5663
}
5764

65+
protected synchronized boolean isExcluded(Object bean) {
66+
String name = RegistryUtils.getName(bean);
67+
68+
if ( excludes == null ) {
69+
loadExcluded();
70+
}
71+
72+
boolean result = excludes.contains(name);
73+
if ( result ) {
74+
log.info("Excluding extension [{}] based on configuration", name);
75+
}
76+
77+
return result;
78+
}
79+
80+
protected synchronized void loadExcluded() {
81+
Properties props = applicationContext.getBean("DefaultConfigProperties", Properties.class);
82+
excludes = new HashSet<String>();
83+
for ( String exclude : props.getProperty(EXTENSION_EXCLUDE, "").trim().split("\\s*,\\s*") ) {
84+
if ( StringUtils.hasText(exclude) ) {
85+
excludes.add(exclude);
86+
}
87+
}
88+
89+
for ( String key : props.stringPropertyNames() ) {
90+
if ( key.startsWith(EXTENSION_INCLUDE_PREFIX) ) {
91+
String module = key.substring(EXTENSION_INCLUDE_PREFIX.length());
92+
boolean include = props.getProperty(key).equalsIgnoreCase("true");
93+
if ( ! include ) {
94+
excludes.add(module);
95+
}
96+
}
97+
}
98+
}
99+
58100
@Override
59101
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
60102
return bean;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.cloudstack.spring.lifecycle.registry;
20+
21+
import com.cloud.utils.component.Named;
22+
23+
public class RegistryUtils {
24+
25+
public static String getName(Object object) {
26+
if ( object instanceof Named ) {
27+
String name = ((Named)object).getName();
28+
if ( name != null )
29+
return name;
30+
}
31+
32+
return object == null ? null : object.getClass().getSimpleName();
33+
}
34+
35+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.cloudstack.spring.module.factory;
20+
21+
import java.util.ArrayList;
22+
import java.util.List;
23+
24+
import org.springframework.beans.factory.FactoryBean;
25+
import org.springframework.core.io.Resource;
26+
27+
public class QuietLoaderFactory implements FactoryBean<Resource[]> {
28+
29+
Resource[] resources;
30+
31+
@Override
32+
public Resource[] getObject() throws Exception {
33+
List<Resource> existing = new ArrayList<Resource>();
34+
35+
for ( Resource resource : resources ) {
36+
if ( resource.exists() ) {
37+
existing.add(resource);
38+
}
39+
}
40+
41+
return existing.toArray(new Resource[existing.size()]);
42+
}
43+
44+
@Override
45+
public Class<?> getObjectType() {
46+
return Resource[].class;
47+
}
48+
49+
@Override
50+
public boolean isSingleton() {
51+
return false;
52+
}
53+
54+
public Resource[] getResources() {
55+
return resources;
56+
}
57+
58+
public void setResources(Resource[] resources) {
59+
this.resources = resources;
60+
}
61+
62+
}

framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/model/impl/DefaultModuleDefinitionSet.java

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,23 @@
1919
package org.apache.cloudstack.spring.module.model.impl;
2020

2121
import java.io.IOException;
22+
import java.io.InputStream;
2223
import java.net.URL;
2324
import java.util.ArrayList;
2425
import java.util.EmptyStackException;
2526
import java.util.HashMap;
27+
import java.util.HashSet;
2628
import java.util.LinkedHashSet;
2729
import java.util.List;
2830
import java.util.Map;
31+
import java.util.Properties;
2932
import java.util.Set;
3033
import java.util.Stack;
3134

3235
import org.apache.cloudstack.spring.module.context.ResourceApplicationContext;
3336
import org.apache.cloudstack.spring.module.model.ModuleDefinition;
3437
import org.apache.cloudstack.spring.module.model.ModuleDefinitionSet;
38+
import org.apache.commons.io.IOUtils;
3539
import org.slf4j.Logger;
3640
import org.slf4j.LoggerFactory;
3741
import org.springframework.beans.BeansException;
@@ -40,18 +44,25 @@
4044
import org.springframework.context.annotation.Configuration;
4145
import org.springframework.core.io.Resource;
4246
import org.springframework.core.io.UrlResource;
47+
import org.springframework.util.StringUtils;
4348

4449
public class DefaultModuleDefinitionSet implements ModuleDefinitionSet {
4550

4651
private static final Logger log = LoggerFactory.getLogger(DefaultModuleDefinitionSet.class);
4752

4853
public static final String DEFAULT_CONFIG_RESOURCES = "DefaultConfigResources";
54+
public static final String DEFAULT_CONFIG_PROPERTIES = "DefaultConfigProperties";
55+
public static final String MODULES_EXCLUDE = "modules.exclude";
56+
public static final String MODULES_INCLUDE_PREFIX = "modules.include.";
57+
public static final String MODULE_PROPERITES = "ModuleProperties";
4958
public static final String DEFAULT_CONFIG_XML = "defaults-context.xml";
5059

5160
String root;
5261
Map<String, ModuleDefinition> modules;
5362
Map<String, ApplicationContext> contexts = new HashMap<String, ApplicationContext>();
5463
ApplicationContext rootContext = null;
64+
Set<String> excludes = new HashSet<String>();
65+
Properties configProperties = null;
5566

5667
public DefaultModuleDefinitionSet(Map<String, ModuleDefinition> modules, String root) {
5768
super();
@@ -136,7 +147,7 @@ protected ApplicationContext loadContext(ModuleDefinition def, ApplicationContex
136147
}
137148

138149
protected boolean shouldLoad(ModuleDefinition def) {
139-
return true;
150+
return ! excludes.contains(def.getName());
140151
}
141152

142153
protected ApplicationContext getDefaultsContext() {
@@ -156,9 +167,52 @@ public void with(ModuleDefinition def, Stack<ModuleDefinition> parents) {
156167
}
157168
}
158169
});
159-
170+
171+
configProperties = (Properties) context.getBean(DEFAULT_CONFIG_PROPERTIES);
172+
for ( Resource resource : resources ) {
173+
load(resource, configProperties);
174+
}
175+
176+
for ( Resource resource : (Resource[])context.getBean(MODULE_PROPERITES) ) {
177+
load(resource, configProperties);
178+
}
179+
180+
parseExcludes();
181+
160182
return context;
161183
}
184+
185+
protected void parseExcludes() {
186+
for ( String exclude : configProperties.getProperty(MODULES_EXCLUDE, "").trim().split("\\s*,\\s*") ) {
187+
if ( StringUtils.hasText(exclude) ) {
188+
excludes.add(exclude);
189+
}
190+
}
191+
192+
for ( String key : configProperties.stringPropertyNames() ) {
193+
if ( key.startsWith(MODULES_INCLUDE_PREFIX) ) {
194+
String module = key.substring(MODULES_INCLUDE_PREFIX.length());
195+
boolean include = configProperties.getProperty(key).equalsIgnoreCase("true");
196+
if ( ! include ) {
197+
excludes.add(module);
198+
}
199+
}
200+
}
201+
}
202+
203+
protected void load(Resource resource, Properties props) {
204+
InputStream is = null;
205+
try {
206+
if ( resource.exists() ) {
207+
is = resource.getInputStream();
208+
props.load(is);
209+
}
210+
} catch (IOException e) {
211+
throw new IllegalStateException("Failed to load resource [" + resource + "]", e);
212+
} finally {
213+
IOUtils.closeQuietly(is);
214+
}
215+
}
162216

163217
protected void printHierarchy() {
164218
withModule(new WithModule() {
@@ -178,6 +232,7 @@ protected void withModule(ModuleDefinition def, Stack<ModuleDefinition> parents,
178232
return;
179233

180234
if ( ! shouldLoad(def) ) {
235+
log.info("Excluding context [{}] based on configuration", def.getName());
181236
return;
182237
}
183238

framework/spring/module/src/main/resources/org/apache/cloudstack/spring/module/model/impl/defaults-context.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,14 @@
2424
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
2525

2626
<bean id="DefaultConfigResources" class="java.util.ArrayList" />
27+
<bean id="DefaultConfigProperties" class="java.util.Properties" />
28+
29+
<bean id="ModuleProperties" class="org.apache.cloudstack.spring.module.factory.QuietLoaderFactory" >
30+
<property name="resources">
31+
<list>
32+
<value>classpath:modules.properties</value>
33+
</list>
34+
</property>
35+
</bean>
2736

2837
</beans>

framework/spring/module/src/test/java/org/apache/cloudstack/spring/module/factory/ModuleBasedContextFactoryTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,16 @@ public void testOverride() throws IOException {
6666
assertEquals("a string", set.getApplicationContext("child1").getBean("override", String.class));
6767
}
6868

69+
@Test
70+
public void testExcluded() throws IOException {
71+
ModuleBasedContextFactory factory = new ModuleBasedContextFactory();
72+
ModuleDefinitionSet set = factory.loadModules(defs, "base");
73+
74+
assertNull(set.getApplicationContext("excluded"));
75+
assertNull(set.getApplicationContext("excluded2"));
76+
assertNull(set.getApplicationContext("orphan-of-excluded"));
77+
}
78+
6979
@Test
7080
public void testBeans() throws IOException {
7181
ModuleBasedContextFactory factory = new ModuleBasedContextFactory();

framework/spring/module/src/test/java/org/apache/cloudstack/spring/module/locator/impl/ClasspathModuleDefinitionSetLocatorTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public void testDiscover() throws IOException {
3434

3535
Collection<ModuleDefinition> modules = factory.locateModules("testhierarchy");
3636

37-
assertEquals(5, modules.size());
37+
assertEquals(8, modules.size());
3838
}
3939

4040
}

0 commit comments

Comments
 (0)