GridGain Developers Hub

Code Deployment with UriDeploymentSpi

Senior Software Engineer

What is Code Deployment

“Deploying code” means putting your application code in a remote environment where your app can run and serve your users. In Java, you usually deploy your app code by yourself, by updating the classpath and restarting the application to apply the changes.

Some web servers, such as TomCat, can deploy web applications using JAR and WAR files. When these files are updated, they deploy a new version of the web application without restarting the entire web server.

We can use a similar concept in Java. The Java classloaders feature enables you to load classes from different sources. By default, all the classes are loaded from the system classloader, which usually gets classes from the application classpath. However, it is possible to load classes that are not included in the JAVA classpath from another location.

With UriDeploymentSpi, you can deploy new versions of Java classes used by GridGain. This lets you update your Java classes without having to restart the Java application that runs the GridGain server.

What Can be Deployed via UriDeploymentSpi

Unfortunately, not everything can be deployed via UriDeploymentSpi. Here is the list of supported items:

  • GridGain compute tasks

  • GridGain services

  • Java POJO that aren’t included in the classpath

Let’s review all the relevant cases.

GridGain Configuration

Every GridGain node should have UriDeploymentSpi configured as follows:

<property name="deploymentSpi">
    <bean class="org.apache.ignite.spi.deployment.uri.UriDeploymentSpi">
        <property name="temporaryDirectoryPath" value="C:\\Users\\alexs\\UriDeploymentTest\\tmp_folder"/>
        <property name="uriList">
            <list>
                <value>file://freq=2000@192.168.0.18/Users/alexs/UriDeploymentTest/deployment_folder</value>
            </list>
        </property>
    </bean>
</property>

You need to prepare two folders:

  • temporaryDirectoryPath - GridGain will unzip the new classes here for further loading

  • uriList - set of URI addresses to be scanned for JARs

We advise you to use a network folder which all nodes can access inside uriList. It will help avoid the situation where different nodes have different versions of the classes.

Note the freq=2000 parameter. Its value, in milliseconds, defines how often the location will be checked for new classes. You can set this parameter to a low value to avoid delay in class loading. The default is UriDeploymentFileScanner.DFLT_SCAN_FREQ.

Out of the box, this SPI supports the following protocols:

  • file: - file protocol

  • http: - HTTP protocol

  • https: - secure HTTP protocol

UriCodeDeployment Limitations

UriDeploymentSpi has the following limitations:

  • You must prepare JAR files that contain all the related classes together (constructor parameters, classes used in compute tasks and services, etc.).

  • Each JAR file must contain the ignite.xml file that lists the names of all the deployed classes. Otherwise, only compute tasks will be deployed.

  • All nodes should use the same URI location to avoid delays when updating the scannable files.

  • To enable redeployment, you need to set -DIGNITE_EVENT_DRIVEN_SERVICE_PROCESSOR_ENABLED=true.

  • You cannot have the same class in the JVM classpath and in UriDeploymentSPI. In such a case, the JVM one will be used because Java does not allow multiple versions of the same class.

Prepare the JAR File

Your JAR must have the following structure:

META-INF/
META-INF/MANIFEST.MF
com/
com/gridgain/
com/gridgain/compute/
com/gridgain/models/
com/gridgain/service/
com/gridgain/compute/CacheSizeJob.class
com/gridgain/compute/TestComputeTask.class
com/gridgain/models/ModelClass.class
com/gridgain/service/RedeployServiceComputeTask$RedeployService.class
com/gridgain/service/RedeployServiceComputeTask.class
com/gridgain/service/TestService.class
META-INF/ignite.xml
META-INF/maven/
META-INF/maven/org.gridgain/
META-INF/maven/org.gridgain/UriDeploymentTest/
META-INF/maven/org.gridgain/UriDeploymentTest/pom.xml
META-INF/maven/org.gridgain/UriDeploymentTest/pom.properties

To build a JAR with the above structure:

  1. Create a Maven project with the following pom.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>org.gridgain</groupId>
        <artifactId>UriDeploymentTest</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <repositories>
            <repository>
                <id>GridGain External Repository</id>
                <url>http://www.gridgainsystems.com/nexus/content/repositories/external</url>
            </repository>
        </repositories>
    
        <properties>
            <maven.compiler.source>8</maven.compiler.source>
            <maven.compiler.target>8</maven.compiler.target>
            <ignite.version>8.8.30</ignite.version>
        </properties>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-jar-plugin</artifactId>
                    <configuration>
                        <archive>
                            <manifest>
                                <addClasspath>true</addClasspath>
                            </manifest>
                            <manifestEntries>
                                <Class-Path>lib/</Class-Path>
                            </manifestEntries>
                        </archive>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
        <dependencies>
            <dependency>
                <groupId>org.gridgain</groupId>
                <artifactId>ignite-core</artifactId>
                <version>${ignite.version}</version>
            </dependency>
            <dependency>
                <groupId>org.gridgain</groupId>
                <artifactId>gridgain-core</artifactId>
                <version>${ignite.version}</version>
            </dependency>
            <dependency>
                <groupId>org.gridgain</groupId>
                <artifactId>ignite-spring</artifactId>
                <version>${ignite.version}</version>
            </dependency>
        </dependencies>
    </project>
  2. Inside the Maven resources folder, create the META_INF/ignite.xml file with the following content:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:util="http://www.springframework.org/schema/util"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd">
    
    
        <util:list id="testComputeTask" value-type="java.lang.String">
            <value>com.gridgain.compute.TestComputeTask</value>
        </util:list>
    
        <util:list id="testComputeTaskJob" value-type="java.lang.String">
            <value>com.gridgain.compute.CacheSizeJob</value>
        </util:list>
    
        <util:list id="testModelClass" value-type="java.lang.String">
            <value>com.gridgain.models.ModelClass</value>
        </util:list>
    
        <util:list id="testServiceDeployCompute" value-type="java.lang.String">
            <value>com.gridgain.service.RedeployServiceComputeTask</value>
        </util:list>
    
        <util:list id="testServiceDeployComputeJob" value-type="java.lang.String">
            <value>com.gridgain.service.RedeployServiceComputeTask$RedeployService</value>
        </util:list>
    
        <util:list id="testService" value-type="java.lang.String">
            <value>com.gridgain.service.TestService</value>
        </util:list>
    </beans>
  3. Create a network folder (common for all hosts) named deployment_folder.

  4. On every host, create a tmp folder named tmp_folder.

  5. Build your project with the mvn clean package command.

  6. Put the generated JAR into deployment_folder.

Add the ignite-urideploy Dependency

The ignite-urideploy dependency is optional. This is why you need to add it explicitly, in one of the following ways:

  1. If you run the node from the binaries, copy the ignite-urideploy dependency from optional libraries:

    cp -R $IGNITE-HOME/libs/optional/ignite-urideploy $IGNITE-HOME/libs/ignite-urideploy
  2. If you use Maven, add the ignite-urideploy dependency to pom.xml:

    <dependency>
        <groupId>org.gridgain</groupId>
        <artifactId>ignite-urideploy</artifactId>
        <version>${ignite.version}</version>
    </dependency>

Now you can start your server node and use the classes from deployment_folder as described below.

How to Use the Deployed Classes

POJO Classes

In Java, to use classes that are not included in the classpath, you have to load them from a special classloader. UriDeploymentSPI has its own classloader that you can get like this:

ignite.configuration()
	.getDeploymentSpi()
	.findResource("com.gridgain.models.ModelClass")
	.getClassLoader());

The limitation of the above approach is that you need to use the Java reflection to create objects and work with them. This can be a little tricky. For example, here is the code where we create a ModelClass object and call its method:

Class<?> modelClassFromURI = Class.forName("com.gridgain.models.ModelClass", true, ignite.configuration()
	.getDeploymentSpi()
	.findResource("com.gridgain.models.ModelClass")
	.getClassLoader());

Constructor<?> ctor = modelClassFromURI.getConstructor(long.class);

Method sumInstanceMethod = modelClassFromURI.getMethod("getValue");

Object object = ctor.newInstance(10L);

long result = (long) sumInstanceMethod.invoke(object);

System.out.println("Results from Model Class - " + result);

However, typically you only need to use this approach for POJO classes and services if you intend to deploy them from your code. Compute tasks can be deployed by name. Services can also be deployed using compute tasks.

Compute Tasks

Here is how you can deploy a compute task:

System.out.println("Results from compute - " +
                    ignite.compute().execute("com.gridgain.compute.TestComputeTask", "myCache"));

Compute tasks can be loaded by name. See example of this compute task in the sample project.

Services

Having set -DIGNITE_EVENT_DRIVEN_SERVICE_PROCESSOR_ENABLED=true, you can deploy services in two ways:

  1. From the compute task called by name:

    package com.gridgain.service;
    
    import org.apache.ignite.Ignite;
    import org.apache.ignite.IgniteException;
    import org.apache.ignite.IgniteServices;
    import org.apache.ignite.cluster.ClusterNode;
    import org.apache.ignite.compute.ComputeJob;
    import org.apache.ignite.compute.ComputeJobResult;
    import org.apache.ignite.compute.ComputeJobResultPolicy;
    import org.apache.ignite.compute.ComputeTask;
    import org.apache.ignite.resources.IgniteInstanceResource;
    
    import java.util.Collections;
    import java.util.List;
    import java.util.Map;
    
    public class RedeployServiceComputeTask implements ComputeTask<String, Long> {
        @Override
        public Map<? extends ComputeJob, ClusterNode> map(List<ClusterNode> list, String arg) throws IgniteException {
            return Collections.singletonMap(new RedeployService(arg), list.iterator().next());
        }
    
        @Override
        public ComputeJobResultPolicy result(ComputeJobResult computeJobResult, List<ComputeJobResult> list) throws IgniteException {
            return ComputeJobResultPolicy.REDUCE;
        }
    
        @Override
        public Long reduce(List<ComputeJobResult> results) throws IgniteException {
            return results.iterator().next().getData();
        }
    
        private class RedeployService implements ComputeJob {
            /** */
            private String cacheName;
    
            /** */
            @IgniteInstanceResource
            private Ignite ignite;
    
            /** */
            public RedeployService(String arg) {
                this.cacheName = arg;
            }
    
            /** */
            @Override public void cancel() {
                // No-op.
            }
    
            /** */
            @Override public Long execute() {
                IgniteServices svcs = ignite.services();
    
                svcs.cancel("mySingleton");
    
                svcs.deployClusterSingleton("mySingleton", new TestService("myCache"));
    
                return 0L;
            }
        }
    }
  2. Using a reflection:

    //service loaded from the URI
    IgniteServices svcs = ignite.services();
    
    Class<?> serviceClassFromURI = Class.forName("com.gridgain.service.TestService", true, ignite.configuration()
    		.getDeploymentSpi()
    		.findResource("com.gridgain.service.TestService")
    		.getClassLoader());
    
    Constructor<?> serCtor = serviceClassFromURI.getConstructor(String.class);
    
    svcs.cancel("mySingleton");
    svcs.deployClusterSingleton("mySingleton", (Service) serCtor.newInstance("myCache"));

Learn More

For more information, see the sample project.