Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

While we are refactoring CloudStack architecture, to lay out a better component oriented foundation, be open to tools that most developers are familiar with in Java community, we started to experience the usage of Apache Spring Framework, switching to Spring touches a lot of existing CloudStack codebase, however, it does not bring any business logic changes, but it does introduce changes that CloudStack developers need to be aware of.

1.

...

CloudStack components

CloudStack management server contains a collection of various components, components usually come with 4 different flavors, common framework components, manager components, adapter components and DAO components. After deploy time, CloudStack allows experienced customer to compose a customized setup through editing of configuration files. For CloudStack OSS (OpenSource) distribution, developers provides default configuration in With old component container managed by ComponentLocator class, we don't distinguish framework components and regular components that implement varios CloudStack business logic, we started to separate framework components and business logic components in Spring, as the result, there are separated context configuration file, these two files are applicationContext.xml.in and componentContext.xml.in, following is some sample content from Javelin

applicationContext.xml.in

Panel

<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"

  xmlns:tx="http://www.springframework.org/schema/tx" 

  xmlns:aop="http://www.springframework.org/schema/aop"

  xsi:schemaLocation="http://www.springframework.org/schema/beans

_                      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd_

_                      http://www.springframework.org/schema/tx _

_                      http://www.springframework.org/schema/tx/spring-tx-3.0.xsd_

_                      http://www.springframework.org/schema/aop_

_                      http://www.springframework.org/schema/aop/spring-aop-3.0.xsd_

_                      http://www.springframework.org/schema/context_

                      http://www.springframework.org/schema/context/spring-context-3.0.xsd">                     

 

  <context:annotation-config />

 

  <context:component-scan base-package="org.apache.cloudstack, com.cloud" />

 

  <!--

    @DB support

  -->

  <aop:config proxy-target-class="true">

    <aop:aspect id="dbContextBuilder" ref="transactionContextBuilder">

        <aop:pointcut id="captureAnyMethod"

            expression="execution(* *(..))" 

        />

 

        <aop:around pointcut-ref="captureAnyMethod" method="AroundAnyMethod"/> 

    </aop:aspect>

    <aop:aspect id="actionEventInterceptorAspect" ref="actionEventInterceptor">

        <aop:pointcut id="captureEventMethod"

            expression="execution(* *(..)) and @annotation(com.cloud.event.ActionEvent)" 

        />

        <aop:around pointcut-ref="captureEventMethod" method="AroundAnyMethod"/> 

    </aop:aspect>

 

  </aop:config>

 

  <bean id="transactionContextBuilder" />

  <bean id="actionEventInterceptor" />

  

  <!--

    RPC/Async/EventBus

  -->

  <bean id="onwireRegistry"

    init-method="scan" >

    <property name="packages">

      <list>

        <value>org.apache.cloudstack.framework</value>

      </list>

    </property>

  </bean>

 

  <bean id="messageSerializer">

    <property name="onwireClassRegistry" ref="onwireRegistry" />

  </bean>

 

  <bean id="transportProvider"  init-method="initialize">

    <property name="workerPoolSize" value="5" />

    <property name="nodeId" value="Node1" />

    <property name="messageSerializer" ref="messageSerializer" />

  </bean>

  <bean id="rpcProvider" init-method="initialize">

    <constructor-arg ref="transportProvider" />

    <property name="messageSerializer" ref="messageSerializer" />

  </bean>

 

  <bean id="eventBus" class = "org.apache.cloudstack.framework.eventbus.EventBusBase" />

  <bean id="apiServlet" class = "com.cloud.api.ApiServlet" />

</beans>

Components that offer framework level services are collected in applicationContext.xml.in, these framework components usually help define the runtime environment for all other business logic components, for example, middle-ware service components that are for inter-component communication, CloudStack Database, general DB transaction management etc. CloudStack customers may have various combination of activated business logic components in a particular setup, but most of time, a same set of framework components is usually used.

componentContext.xml.in

Panel

<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"

  xmlns:tx="http://www.springframework.org/schema/tx" 

  xmlns:aop="http://www.springframework.org/schema/aop"

  xsi:schemaLocation="http://www.springframework.org/schema/beans

                      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

                      http://www.springframework.org/schema/tx&nbsp;

                      http://www.springframework.org/schema/tx/spring-tx-3.0.xsd

                      http://www.springframework.org/schema/aop

                      http://www.springframework.org/schema/aop/spring-aop-3.0.xsd

                      http://www.springframework.org/schema/context

                      http://www.springframework.org/schema/context/spring-context-3.0.xsd">                     

  <!--

      Compose a CloudStack deployment with selected components here

  -->

  <bean id="databaseUpgradeChecker" />

  <bean id="management-server" class ="com.cloud.server.ManagementServerExtImpl" />

  <bean id="configuration-server" />

  <bean id="clusterManagerImpl" />

  <bean id="clusteredAgentManagerImpl" />

  <bean id="clusteredVirtualMachineManagerImpl" />

  <bean id="highAvailabilityManagerExtImpl" />

  <!-- bean id="bareMetalVmManagerImpl" / -->

  <bean id="userVmManagerImpl" />

  <bean id="consoleProxyManagerImpl" />

  <bean id="securityGroupManagerImpl2" />

  <bean id="premiumSecondaryStorageManagerImpl" />

  <bean id="randomlyIncreasingVMInstanceDaoImpl" /> 

  <!--

      Network Elements

  -->

  <bean id="Ovs">

    <property name="name" value="Ovs"/>

  </bean>

  <bean id="ExternalDhcpServer">

    <property name="name" value="ExternalDhcpServer"/>

  </bean>

  <bean id="BareMetal">

    <property name="name" value="BareMetal"/>

  </bean>

  <bean id="SecurityGroupProvider">

    <property name="name" value="SecurityGroupProvider"/>

  </bean>

  <bean id="VirtualRouter">

    <property name="name" value="VirtualRouter"/>

  </bean>

  <bean id="VpcVirtualRouter">

    <property name="name" value="VpcVirtualRouter"/>

  </bean>

  <bean id="NiciraNvp">

    <property name="name" value="NiciraNvp"/>

  </bean>

  

  

</beans> 

...

while for non-OSS distribution, the configuration will be provided in applicationContext.xml.in and nonossComponentContext.xml.in.

For all components that are mandatory and shared among both OSS distribution and non-OSS distribution, they should be declared in applicationContext.xml.in. Optional components, depends on whether or not they are specific to OSS or non-OSS distribution, they can appear in applicationContext.xml.in *and *componentContext.xml.in,* *either in one of the two files or the both.

If a certain component has different configuration for OSS and non-OSS distribution, it should go both to applicationContext.xml.in *and *componentContext.xml.in.

Use following flow to determine where your newly developed component should go

Code Block
if (component is mandatory to OSS/non-OSS ) {


    if( component shares common configuration in OSS/non-OSS ) {
        put it in applicationContext.xml.in
    } else {
        put it in both componentContext.xml and nonossComponentContext.xml.in
    }


} else {

    if(component is for OSS only)
        put it in componentContext.xml
    else
        put it in nonossComponentContext.xml

}

2. How to declare to be a CloudStack component

Spring component is nothing else more than a Java POJO object, once you've determined which file the component declaration should go using the flow at above. declaring it is fairly simple. Following gives an example

Code Block

  <bean id="configurationDaoImpl" /  <bean id="configurationDaoImpl" class="com.cloud.configuration.dao.ConfigurationDaoImpl" />

ID of the component should be unique, as original configuration file are generated automatically from legacy code, it is usually named after the component class name.

2. How to declare to be a CloudStack component

In Spring term, CloudStack uses both singleton components and prototype components, Spring component is nothing else more than a Java POJO object, you can either declare it with @Component annotation or in <bean> declaration in applicationContext.xml or componentContext.xml. CloudStack uses a lot of singleton components, for example, managers, DAOs, following is an example of such components

Code Block

 @Component
@Local(value=ClusterDao.class)
public class ClusterDaoImpl extends GenericDaoBase<ClusterVO, Long> implements ClusterDao {


    protected final SearchBuilder<ClusterVO> PodSearch;
    protected final SearchBuilder<ClusterVO> HyTypeWithoutGuidSearch;
    protected final SearchBuilder<ClusterVO> AvailHyperSearch;
    protected final SearchBuilder<ClusterVO> ZoneSearch;
    protected final SearchBuilder<ClusterVO> ZoneHyTypeSearch;
    private static final String GET_POD_CLUSTER_MAP_PREFIX = "SELECT pod_id, id FROM cloud.cluster WHERE cluster.id IN( ";
    private static final String GET_POD_CLUSTER_MAP_SUFFIX = " )";
    @Inject
    protected HostPodDao _hostPodDao;
    public ClusterDaoImpl() {
    }


    // more code ...
}

For most of low-level built-in components like DAOs, in Javelin practice, we use @Component annotation, since it saves us a lot of typing and can take advantage of Eclipse IDE's powerful refactoring and type-infer feature to help us coding. However, if you are developing high level components and you want to give flexibility for customer to choose to compose a particular CloudStack deployment, declare in componentContext.xml would be a better choice.

3. Auto-wiring

One of the biggest advantage of switching to Spring is that Auto-wiring in Component has now become consistent. With previous ComponentLocator, there are a lot of places that have to use run-time wiring, for example, using ComponentLocator.getManager() to wire a reference to a manager component. The reason for developers to do so is that ComponentLocator does not fully resolved dependent injection for components, so when inter-component relationship becomes complex in a large system like CloudStack, lots of hacking ways rise up inside CloudStack codebase. Although it solves the immediate needs, but it also creates a lot of confusion for developers, the more components being involved into injection business, the more the system is tightly coupled with ComponentLocator itself.

...

It is a good practice to separate component construction and initialization, always try to make your component be self-dependable in initialization, self-dependable initialization means the component is able to initialize itself with the very minimal dependency to other component's initialization status. Use @PostConstruct to mark such initialization method for Spring to automatically call for you. Following is an example.

Code Block

 @Component

public class ConfigurationDaoImpl extends GenericDaoBase<ConfigurationVO, String> implements ConfigurationDao {

	@PostConstruct 	void initComponent() {		// more code...	} 
}

...

4

...

One side effect of using Spring is that component object created by Spring are not real object instantiated from you java class, but a proxy object in front of it. If your code heavily relies on runtime reflection info, your should be aware of such impact. During Javelin refactoring period, we've corrected a few cases that falls into this category. Examples can be found in ApiServer/ApiDispatcher. 

...

.

...

4

...

Avoid creating run-time relationship with component container

Although we've tried the best to cut the relationship to a component container inside component code, we still have very few places in framework components that need to be aware of existence of the component container, to avoid any strong binding to a particular container like Spring, we introduced a class called ComponentContext, it is responsible to bridge CloudStack component with a chosen component container (for now, it is Spring). 

As a business logic component developer, you should avoid using any of the functions provided by ComponentContext, this can make the business component neutral to component container, since we now switched to standard javax @Inject auto-wiring annotation, it is even possible for your component to run in containers that are other than CloudStack

5. Component

...

lifecycle

In CloudStack, some components are lifecyle sensitive, examples are those manager objects, adapter objects, to make lifecyle management more general and flexible, there are some TODO works to make component life-cycle management easier. We'll keep this topic updated in the community.