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
This commit is contained in:
Darren Shepherd 2013-11-05 11:02:41 -07:00
parent 0ef6135198
commit 80813375ae
15 changed files with 396 additions and 18 deletions

View File

@ -34,7 +34,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanNameAware;
import com.cloud.utils.component.Named;
import com.cloud.utils.component.Registry;
public class ExtensionRegistry implements Registry<Object>, Configurable, BeanNameAware {
@ -81,7 +80,7 @@ public class ExtensionRegistry implements Registry<Object>, Configurable, BeanNa
}
}
String name = getName(item);
String name = RegistryUtils.getName(item);
if ( name != null && exclude.size() > 0 && exclude.contains(name) ) {
return false;
@ -103,7 +102,7 @@ public class ExtensionRegistry implements Registry<Object>, Configurable, BeanNa
break;
}
if ( getName(registered.get(i)).equals(orderTest) ) {
if ( RegistryUtils.getName(registered.get(i)).equals(orderTest) ) {
i++;
}
}
@ -116,16 +115,6 @@ public class ExtensionRegistry implements Registry<Object>, Configurable, BeanNa
return true;
}
protected String getName(Object object) {
if ( object instanceof Named ) {
String name = ((Named)object).getName();
if ( name != null )
return name;
}
return object == null ? null : object.getClass().getSimpleName();
}
@Override
public void unregister(Object type) {

View File

@ -20,6 +20,7 @@ package org.apache.cloudstack.spring.lifecycle.registry;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
import org.slf4j.Logger;
@ -29,6 +30,7 @@ import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.SmartLifecycle;
import org.springframework.util.StringUtils;
import com.cloud.utils.component.Registry;
@ -36,6 +38,9 @@ public class RegistryLifecycle implements BeanPostProcessor, SmartLifecycle, App
private static final Logger log = LoggerFactory.getLogger(RegistryLifecycle.class);
public static final String EXTENSION_EXCLUDE = "extensions.exclude";
public static final String EXTENSION_INCLUDE_PREFIX = "extensions.include.";
Registry<Object> registry;
/* 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
Set<Object> beans = new HashSet<Object>();
Class<?> typeClass;
ApplicationContext applicationContext;
Set<String> excludes = null;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if ( typeClass.isAssignableFrom(bean.getClass()) )
if ( typeClass.isAssignableFrom(bean.getClass()) && ! isExcluded(bean) ) {
beans.add(bean);
}
return bean;
}
protected synchronized boolean isExcluded(Object bean) {
String name = RegistryUtils.getName(bean);
if ( excludes == null ) {
loadExcluded();
}
boolean result = excludes.contains(name);
if ( result ) {
log.info("Excluding extension [{}] based on configuration", name);
}
return result;
}
protected synchronized void loadExcluded() {
Properties props = applicationContext.getBean("DefaultConfigProperties", Properties.class);
excludes = new HashSet<String>();
for ( String exclude : props.getProperty(EXTENSION_EXCLUDE, "").trim().split("\\s*,\\s*") ) {
if ( StringUtils.hasText(exclude) ) {
excludes.add(exclude);
}
}
for ( String key : props.stringPropertyNames() ) {
if ( key.startsWith(EXTENSION_INCLUDE_PREFIX) ) {
String module = key.substring(EXTENSION_INCLUDE_PREFIX.length());
boolean include = props.getProperty(key).equalsIgnoreCase("true");
if ( ! include ) {
excludes.add(module);
}
}
}
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;

View File

@ -0,0 +1,35 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.cloudstack.spring.lifecycle.registry;
import com.cloud.utils.component.Named;
public class RegistryUtils {
public static String getName(Object object) {
if ( object instanceof Named ) {
String name = ((Named)object).getName();
if ( name != null )
return name;
}
return object == null ? null : object.getClass().getSimpleName();
}
}

View File

@ -0,0 +1,62 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.cloudstack.spring.module.factory;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.core.io.Resource;
public class QuietLoaderFactory implements FactoryBean<Resource[]> {
Resource[] resources;
@Override
public Resource[] getObject() throws Exception {
List<Resource> existing = new ArrayList<Resource>();
for ( Resource resource : resources ) {
if ( resource.exists() ) {
existing.add(resource);
}
}
return existing.toArray(new Resource[existing.size()]);
}
@Override
public Class<?> getObjectType() {
return Resource[].class;
}
@Override
public boolean isSingleton() {
return false;
}
public Resource[] getResources() {
return resources;
}
public void setResources(Resource[] resources) {
this.resources = resources;
}
}

View File

@ -19,19 +19,23 @@
package org.apache.cloudstack.spring.module.model.impl;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.EmptyStackException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Stack;
import org.apache.cloudstack.spring.module.context.ResourceApplicationContext;
import org.apache.cloudstack.spring.module.model.ModuleDefinition;
import org.apache.cloudstack.spring.module.model.ModuleDefinitionSet;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
@ -40,18 +44,25 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.util.StringUtils;
public class DefaultModuleDefinitionSet implements ModuleDefinitionSet {
private static final Logger log = LoggerFactory.getLogger(DefaultModuleDefinitionSet.class);
public static final String DEFAULT_CONFIG_RESOURCES = "DefaultConfigResources";
public static final String DEFAULT_CONFIG_PROPERTIES = "DefaultConfigProperties";
public static final String MODULES_EXCLUDE = "modules.exclude";
public static final String MODULES_INCLUDE_PREFIX = "modules.include.";
public static final String MODULE_PROPERITES = "ModuleProperties";
public static final String DEFAULT_CONFIG_XML = "defaults-context.xml";
String root;
Map<String, ModuleDefinition> modules;
Map<String, ApplicationContext> contexts = new HashMap<String, ApplicationContext>();
ApplicationContext rootContext = null;
Set<String> excludes = new HashSet<String>();
Properties configProperties = null;
public DefaultModuleDefinitionSet(Map<String, ModuleDefinition> modules, String root) {
super();
@ -136,7 +147,7 @@ public class DefaultModuleDefinitionSet implements ModuleDefinitionSet {
}
protected boolean shouldLoad(ModuleDefinition def) {
return true;
return ! excludes.contains(def.getName());
}
protected ApplicationContext getDefaultsContext() {
@ -156,9 +167,52 @@ public class DefaultModuleDefinitionSet implements ModuleDefinitionSet {
}
}
});
configProperties = (Properties) context.getBean(DEFAULT_CONFIG_PROPERTIES);
for ( Resource resource : resources ) {
load(resource, configProperties);
}
for ( Resource resource : (Resource[])context.getBean(MODULE_PROPERITES) ) {
load(resource, configProperties);
}
parseExcludes();
return context;
}
protected void parseExcludes() {
for ( String exclude : configProperties.getProperty(MODULES_EXCLUDE, "").trim().split("\\s*,\\s*") ) {
if ( StringUtils.hasText(exclude) ) {
excludes.add(exclude);
}
}
for ( String key : configProperties.stringPropertyNames() ) {
if ( key.startsWith(MODULES_INCLUDE_PREFIX) ) {
String module = key.substring(MODULES_INCLUDE_PREFIX.length());
boolean include = configProperties.getProperty(key).equalsIgnoreCase("true");
if ( ! include ) {
excludes.add(module);
}
}
}
}
protected void load(Resource resource, Properties props) {
InputStream is = null;
try {
if ( resource.exists() ) {
is = resource.getInputStream();
props.load(is);
}
} catch (IOException e) {
throw new IllegalStateException("Failed to load resource [" + resource + "]", e);
} finally {
IOUtils.closeQuietly(is);
}
}
protected void printHierarchy() {
withModule(new WithModule() {
@ -178,6 +232,7 @@ public class DefaultModuleDefinitionSet implements ModuleDefinitionSet {
return;
if ( ! shouldLoad(def) ) {
log.info("Excluding context [{}] based on configuration", def.getName());
return;
}

View File

@ -24,5 +24,14 @@
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<bean id="DefaultConfigResources" class="java.util.ArrayList" />
<bean id="DefaultConfigProperties" class="java.util.Properties" />
<bean id="ModuleProperties" class="org.apache.cloudstack.spring.module.factory.QuietLoaderFactory" >
<property name="resources">
<list>
<value>classpath:modules.properties</value>
</list>
</property>
</bean>
</beans>

View File

@ -66,6 +66,16 @@ public class ModuleBasedContextFactoryTest {
assertEquals("a string", set.getApplicationContext("child1").getBean("override", String.class));
}
@Test
public void testExcluded() throws IOException {
ModuleBasedContextFactory factory = new ModuleBasedContextFactory();
ModuleDefinitionSet set = factory.loadModules(defs, "base");
assertNull(set.getApplicationContext("excluded"));
assertNull(set.getApplicationContext("excluded2"));
assertNull(set.getApplicationContext("orphan-of-excluded"));
}
@Test
public void testBeans() throws IOException {
ModuleBasedContextFactory factory = new ModuleBasedContextFactory();

View File

@ -34,7 +34,7 @@ public class ClasspathModuleDefinitionSetLocatorTest {
Collection<ModuleDefinition> modules = factory.locateModules("testhierarchy");
assertEquals(5, modules.size());
assertEquals(8, modules.size());
}
}

View File

@ -0,0 +1,19 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
name=excluded
parent=base

View File

@ -0,0 +1,33 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<bean id="name" class="java.lang.String" >
<constructor-arg value="excluded" />
</bean>
<bean id="excluded" class="java.lang.String" >
<constructor-arg value="excluded" />
</bean>
</beans>

View File

@ -0,0 +1,19 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
name=excluded2
parent=base

View File

@ -0,0 +1,33 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<bean id="name" class="java.lang.String" >
<constructor-arg value="excluded2" />
</bean>
<bean id="excluded" class="java.lang.String" >
<constructor-arg value="excluded2" />
</bean>
</beans>

View File

@ -0,0 +1,20 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
modules.include.excluded=false
modules.include.base=True
modules.exclude=excluded2,excluded2

View File

@ -0,0 +1,19 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
name=orphan-of-excluded
parent=excluded

View File

@ -0,0 +1,33 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<bean id="name" class="java.lang.String" >
<constructor-arg value="orphan-of-excluded" />
</bean>
<bean id="excluded" class="java.lang.String" >
<constructor-arg value="orphan-of-excluded" />
</bean>
</beans>