7. Example: Deploying a Standalone Server to a Cluster (JBoss EAP 6)

There is a sample script in the cliRoot/rhq-remoting-cli-3.1.2.GA/samples directory the defines a series of functions that allow a JBoss EAP 6 standalone server to be added to an existing cluster.
JBoss EAP 6 has the idea of domains, which can be subdivided into server groups which share configuration. Server groups allows multiple server instances to have consistent, uniform configuration settings, to share profiles, and to deploy the same applications through a central command point.
However, JBoss EAP 6 still has the idea of a classic standalone server, a single JBoss instance that is unaffiliated with a domain or server group. A standalone server can be joined in a cluster, a loose association of standalone servers that work together to distribute the work load, either for load balancing or high availability. Unlike a domain, a cluster does not manage configuration or content.
JBoss ON provides the centralized management over configuration and content for standalone servers, introducing some of the ease of maintenance that EAP 6 domains offer. The add-as7-standalone-server-to-cluster.js file defines some useful functions that simplify identifying cluster servers, deployed content, and relevant configuration settings.
The bulk of the functions defined are private and are well-documented within the add-as7-standalone-server-to-cluster.js file, so they are out of the scope of this example. The main purpose of this is example is to review the two public functions which perform two distinct cluster management tasks:
  • addToCluster
  • copyDeployments

7.1. Adding a Standalone EAP 6 Server to a Cluster

A cluster is defined by servers which use the same multicast properties on the same network. If the servers are configured with the same settings, they all automatically associate with each other in a cluster.
There are three configuration properties for a standalone server within a cluster:
  • An identifying name for the JBoss instance to use within the cluster (the node-name)
  • Multicast settings, including the multicast port, a UDP port, and multicast address
  • Socket-binding group information used by mod_cluster
A cluster does not directly manage either configuration properties or deployed content. However, if two EAP 6 standalone servers are in the JBoss ON inventory, then JBoss ON can work somewhat as a bridge, comparing the configuration and content deployments between servers and copying between them. That comparison is what the addToCluster function uses to add a standalone server to a cluster. It uses the configuration properties in an existing cluster member and copies them over to the standalone server.
Actually running the script requires the name of the standalone server, the name of an existing cluster member, a node name for the standalone server, and a boolean that sets whether to copy over the deployments from the existing cluster to the new member. In interactive mode, you can run the add-as7-standalone-server-to-cluster.js script to load the function.
Assuming that you already know resource IDs of the standalone server and an existing cluster member:
[root@server bin]# ./rhq-cli.sh -u rhqadmin -p rhqadmin
rhqadmin@localhost:7080$ exec -f cliRoot/rhq-remoting-cli-3.1.2.GA/samples/add-as7-standalone-server-to-cluster.js
rhqadmin@localhost:7080$ var newAs7Resource = ProxyFactory.getResource(10381) 
rhqadmin@localhost:7080$ var existingClusterMemberResource = ProxyFactory.getResource(10577) 
rhqadmin@localhost:7080$ var newNodeName = jbas7-standalone1
rhqadmin@localhost:7080$ addToCluster(newAs7Resource, newNodeName, existingClusterMemberResource, true)
The addToCluster function makes some assumptions that the cache configuration between the two servers is compatible, both for concrete caches and the cache containers for individual subsystems.
The script runs through a few steps to copy the configuration from the cluster to the standalone server:
  1. It checks the plug-in connection properties in the cluster server and compares them to the plug-in connection properties in the standalone server. If necessary, it copies over the plug-in configuration from the cluster server and restarts the standalone server, loading the new configuration.
  2. It checks the given node name for the standalone server. If necessary, it changes the default node name to the one passed with the function.
  3. It then compares the socket-binding settings for the cluster and standalone servers. If necessary, it copies over the socket-binding configuration for the jgroups, messaging, and mod_cluster bindings from the cluster server and restarts the standalone server.
  4. If set, then the script copies the deployments from the cluster server to the standalone server and restarts the standalone server.
The first part of the function pulls the plug-in configuration (defined by the private function _getClusterSignificantConfig) for the cluster and then for the standalone server.
function addToCluster(newAs7Resource, newNodeName, existingClusterMemberResource, copyDeployments) {
    println("Reading config of the existing cluster member");
    var clusterConfig = _getClusterSignificantConfig(existingClusterMemberResource);

    println("Reading config of the new member");
    var memberConfig = _getClusterSignificantConfig(newAs7Resource);

    var memberResourceConfiguration = newAs7Resource.resourceConfiguration;
If the configuration properties are different, then the script copies over the new plug-in configuration and restarts the standalone server to load the new connection settings.
    if (memberConfig['config'] != clusterConfig['config']) {
        println("The configurations of the servers differ.\n" +
            "The new cluster member's configuration will be changed to match the configuration of the existing member.");

        //switch to the same configuration
        var pluginConfig = newAs7Resource.pluginConfiguration;
        pluginConfig.getSimple('config').setValue(clusterConfig['config']);
        newAs7Resource.updatePluginConfiguration(pluginConfig);

        //we need to restart straight away so that we see the changes to the
        //rest of the configuration caused by the change of current config.
        println("Restarting the new cluster member to switch it to the new configuration.");
        newAs7Resource.restart();

        //refresh the resource
        newAs7Resource = ProxyFactory.getResource(newAs7Resource.id);

        //refresh the cluster specific config after the restart with the new
        //config
        memberConfig = _getClusterSignificantConfig(newAs7Resource);
        memberResourceConfiguration = newAs7Resource.resourceConfiguration;
    }
It then applies the node name that was given with the script, if it is different than the one set by default.
    //now check what's the node name we see
    if (memberConfig['node-name'] != newNodeName) {
        println("Updating the node name of the new cluster member from '" + memberConfig['node-name'] + "' to '" + newNodeName + "'");
        _updateNodeName(memberResourceConfiguration, newNodeName);
        newAs7Resource.updateResourceConfiguration(memberResourceConfiguration);
     }
The next configuration area for the cluster is the socket-binding settings for important subsystems, jgroups, messaging, and mod_cluster.
    //now apply the socket binding changes for jgroups and other cluster
    //significant subsystems
    //first find the socket binding group config in the new member
    for(i in newAs7Resource.children) {
        var child = newAs7Resource.children[i];
        if (child.resourceType.name == 'SocketBindingGroup' &&
                child.resourceType.plugin == 'jboss-as-7') {
			

            println("Updating socket bindings of jgroups, messaging and modcluster subsystems");

            var portOffset = javascriptString(child.resourceConfiguration.getSimpleValue('port-offset', '0'));
            var clusterMemberPortOffset = clusterConfig['port-offset'];

            var newConfig = child.resourceConfiguration.deepCopy(false);

            _updateSocketBindings(newConfig, portOffset, clusterMemberPortOffset, clusterConfig['jgroups']);
            _updateSocketBindings(newConfig, portOffset, clusterMemberPortOffset, clusterConfig['messaging']);
            _updateSocketBindings(newConfig, portOffset, clusterMemberPortOffset, clusterConfig['modcluster']);

            child.updateResourceConfiguration(newConfig);
        }
    }

    println("Restarting the new member for the new socket bindings to take effect.");
    newAs7Resource.restart();
Although not strictly part of the cluster configuration, part of what JBoss ON can do is compare other parts of the resource setup, like deployed applications. Synchronizing the deployed applications between one server and another, even standalone instances, helps maintain consistency, and this can be done conveniently at the time that a server is added to a cluster simply by syncing the given cluster server's deployments.
    if (copyDeployments) {
        println("Copying the deployments to the new cluster member...");
        copyDeployments(existingClusterMemberResource, newAs7Resource);

        println("Restarting the new cluster member.");
        newAs7Resource.restart();
    }
}

7.2. Copying Deployed Applications Between Standalone Servers

While EAP 6 server groups manage content centrally for all group members, standalone servers are on their own. JBoss ON can help as an intermediary to sync application content between separate server instances. The addToCluster function has this as an option when joining a standalone server to a cluster. The copyDeployments function can copy deployments between any two standalone instances.
Invoking the function requires only the name of the source EAP 6 server (the one to copy the deployments from) and then the name of the target EAP 6 server (the one to copy the deployments to).
Assuming that you already know resource IDs of the two EAP 6 server resources, load the add-as7-standalone-server-to-cluster.js script and then set the source and target resources. For example, in interactive mode:
[root@server bin]# ./rhq-cli.sh -u rhqadmin -p rhqadmin
rhqadmin@localhost:7080$ exec -f cliRoot/rhq-remoting-cli-3.1.2.GA/samples/add-as7-standalone-server-to-cluster.js
rhqadmin@localhost:7080$ var source = ProxyFactory.getResource(10381) 
rhqadmin@localhost:7080$ var target = ProxyFactory.getResource(10577) 
rhqadmin@localhost:7080$ copyDeployments(source, target)
The first part of the function gets the server resource IDs.
function copyDeployments(sourceAS7, targetAS7) {
    if (typeof sourceAS7 == 'object') {
        sourceAS7 = sourceAS7.id;
    }

    if (typeof targetAS7 == 'object') {
        targetAS7 = targetAS7.id;
    }
All of the deployed applications are listed as children of the source JBoss EAP 6 server. The copyDeployments function retrieves each deployment by searching for all of the children of the server that are of a deployment resource type.
    var deploymentResourceType = ResourceTypeManager.getResourceTypeByNameAndPlugin('Deployment', 'jboss-as-7');

    var deploymentsCrit = new ResourceCriteria;
    deploymentsCrit.addFilterParentResourceId(sourceAS7);
    deploymentsCrit.addFilterResourceTypeId(deploymentResourceType.id);

    var unlimitedPageControl = PageControl.unlimitedInstance;

    var sourceDeployments = ResourceManager.findResourcesByCriteria(deploymentsCrit);
    var iterator = sourceDeployments.iterator();
    while (iterator.hasNext()) {
        var deploymentResource = iterator.next();
        //get a resource proxy for easy access to configurations, etc.
        deploymentResource = ProxyFactory.getResource(deploymentResource.id);

	println("Copying deployment " + deploymentResource.name);
Each discovered deployment is then copied over as a new child resource to the target server. These are content-backed resources, so they are exported and uploaded as content. The function also searches for and pulls in the content metadata and the content history, so that any important historical information about the deployment is also copied over.
        var installedPackage = ContentManager.getBackingPackageForResource(deploymentResource.id);
        var content = ContentManager.getPackageBytes(deploymentResource.id, installedPackage.id);

        var runtimeName = deploymentResource.resourceConfiguration.getSimpleValue('runtime-name', deploymentResource.name);

        var deploymentConfiguration = new Configuration;
        deploymentConfiguration.put(new PropertySimple('runtimeName', runtimeName));

        //so now we have both metadata and the data of the deployment, let's
        //push a copy of it to the target server
        var history = ResourceFactoryManager.createPackageBackedResource(targetAS7,
            deploymentResourceType.id, deploymentResource.name,
            deploymentResource.pluginConfiguration,
            installedPackage.packageVersion.generalPackage.name,
            installedPackage.packageVersion.version,
            installedPackage.packageVersion.architecture.id,
            deploymentConfiguration, content, null);

        while (history.status.name() == 'IN_PROGRESS') {
            java.lang.Thread.sleep(1000);
            //the API for checking the create histories is kinda weird..
            var histories = ResourceFactoryManager.findCreateChildResourceHistory(targetAS7, null, null, unlimitedPageControl);
            var hit = histories.iterator();
            var found = false;
            while(hit.hasNext()) {
                var h = hit.next();

                if (h.id == history.id) {
                    history = h;
                    found = true;
                    break;
                                           
                }
            }

            if (!found) {
                throw "The history object for the deployment seems to have disappeared, this is very strange.";
            }
        }

        println("Deployment finished with status: " + history.status.toString() +
            (history.status.name() == 'SUCCESS' ? "." : (", error message: " + history.errorMessage + ".")));
    }
}