All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.roboconf.target.docker.internal.DockerMachineConfigurator Maven / Gradle / Ivy

The newest version!
/**
 * Copyright 2015-2017 Linagora, Université Joseph Fourier, Floralis
 *
 * The present code is developed in the scope of the joint LINAGORA -
 * Université Joseph Fourier - Floralis research program and is designated
 * as a "Result" pursuant to the terms and conditions of the LINAGORA
 * - Université Joseph Fourier - Floralis research program. Each copyright
 * holder of Results enumerated here above fully & independently holds complete
 * ownership of the complete Intellectual Property rights applicable to the whole
 * of said Results, and may freely exploit it in any manner which does not infringe
 * the moral rights of the other copyright holders.
 *
 * Licensed 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 net.roboconf.target.docker.internal;

import static net.roboconf.target.docker.internal.DockerHandler.DEFAULT_IMAGE;
import static net.roboconf.target.docker.internal.DockerHandler.GENERATE_IMAGE_FROM;
import static net.roboconf.target.docker.internal.DockerHandler.IMAGE_ID;
import static net.roboconf.target.docker.internal.DockerHandler.OPTION_PREFIX_ENV;
import static net.roboconf.target.docker.internal.DockerHandler.OPTION_PREFIX_RUN;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.CreateContainerCmd;
import com.github.dockerjava.api.command.CreateContainerResponse;
import com.github.dockerjava.api.model.Bind;
import com.github.dockerjava.api.model.Image;
import com.github.dockerjava.api.model.Volume;
import com.github.dockerjava.core.command.BuildImageResultCallback;
import com.google.common.collect.Sets;

import net.roboconf.core.model.beans.Instance;
import net.roboconf.core.userdata.UserDataHelpers;
import net.roboconf.core.utils.ManifestUtils;
import net.roboconf.core.utils.Utils;
import net.roboconf.messaging.api.MessagingConstants;
import net.roboconf.target.api.AbstractThreadedTargetHandler.MachineConfigurator;
import net.roboconf.target.api.TargetException;
import net.roboconf.target.api.TargetHandlerParameters;

/**
 * @author Vincent Zurczak - Linagora
 * @author Pierre Bourret - Université Joseph Fourier
 */
public class DockerMachineConfigurator implements MachineConfigurator {

	DockerClient dockerClient;
	Logger logger = Logger.getLogger( getClass().getName());

	static final String USER_DATA_DIR = "/tmp/user-data/";
	static final String USER_DATA_FILE = "parameters.properties";

	private final TargetHandlerParameters parameters;
	private final String machineId;

	private final File userDataVolume;
	private final Map containerIdToVolume;


	/**
	 * Constructor.
	 * @param parameters the target parameters
	 * @param machineId the ID machine of the machine to configure
	 * @param userDataVolume the directory into which user data volume should be created
	 * @param containerIdToVolume a map to associate container IDs with user data directories
	 */
	public DockerMachineConfigurator(
			TargetHandlerParameters parameters,
			String machineId,
			File userDataVolume,
			Map containerIdToVolume ) {

		this.parameters = parameters;
		this.machineId = machineId;

		this.userDataVolume = userDataVolume;
		this.containerIdToVolume = containerIdToVolume;
	}


	/**
	 * @return the parameters
	 */
	public TargetHandlerParameters getParameters() {
		return this.parameters;
	}


	@Override
	public Instance getScopedInstance() {
		return this.parameters.getScopedInstance();
	}


	@Override
	public void close() throws IOException {
		if( this.dockerClient != null )
			this.dockerClient.close();
	}


	@Override
	public boolean configure() throws TargetException {

		// Creating a container is almost immediate.
		// And building an image with the REST API is blocking the thread until the creation is complete.
		// So, this is not asynchronous configuration.
		// Said differently, this method will be invoked only once!

		Map targetProperties = this.parameters.getTargetProperties();
		this.dockerClient = DockerUtils.createDockerClient( targetProperties );

		String rbcfVersion = DockerUtils.findDefaultImageVersion( ManifestUtils.findBundleVersion());
		String imageId = Utils.getValue( targetProperties, IMAGE_ID, DEFAULT_IMAGE + ":" + rbcfVersion );
		this.logger.fine( "Used image: " + imageId );

		Image img = DockerUtils.findImageByIdOrByTag( imageId, this.dockerClient );
		if( img == null )
			createImage( imageId );

		createContainer( imageId );
		return true;
	}


	/**
	 * Creates a container.
	 * @param imageId the image ID
	 * @throws TargetException if something went wrong
	 */
	void createContainer( String imageId ) throws TargetException {

		// Get the command to pass to the container
		this.logger.info( "Creating container " + this.machineId + " from image " + imageId );

		// Extract parameters as local variables
		Map targetProperties = this.parameters.getTargetProperties();
		String applicationName = this.parameters.getApplicationName();
		String scopedInstancePath = this.parameters.getScopedInstancePath();

		// Deal with the Docker run options
		Map options = new HashMap<> ();
		for( Map.Entry entry : targetProperties.entrySet()) {
			if( entry.getKey().toLowerCase().startsWith( OPTION_PREFIX_RUN )) {
				String key = entry.getKey().substring( OPTION_PREFIX_RUN.length());
				options.put( key, entry.getValue());
			}
		}

		// Deal with environment variables (user)
		List env = new ArrayList<> ();
		for( Map.Entry entry : targetProperties.entrySet()) {
			if( entry.getKey().toLowerCase().startsWith( OPTION_PREFIX_ENV )) {

				String key = entry.getKey().substring( OPTION_PREFIX_ENV.length());
				String value = entry.getValue();

				value = value.replace( "", applicationName );
				value = value.replace( "", scopedInstancePath );
				value = value.replace( "", Utils.getValue(
						this.parameters.getMessagingProperties(),
						MessagingConstants.MESSAGING_TYPE_PROPERTY,
						"" ));

				env.add( key + "=" + value );
			}
		}

		// Deal with environment variables (default one: the Roboconf version)
		String rbcfVersion = DockerUtils.findDefaultImageVersion( ManifestUtils.findBundleVersion());
		env.add( "RBCF_VERSION=" + rbcfVersion );

		// Deal with environment variables (default one: the user data)
		env.add( "AGENT_PARAMETERS=file:" + USER_DATA_DIR + USER_DATA_FILE );

		// Execute...
		try {
			String containerName = DockerUtils.buildContainerNameFrom( scopedInstancePath, applicationName );

			// Create a volume
			File dir = new File( this.userDataVolume, containerName );
			Utils.createDirectory( dir );
			this.containerIdToVolume.put( containerName, dir );

			String userData = UserDataHelpers.writeUserDataAsString(
					this.parameters.getMessagingProperties(),
					this.parameters.getDomain(),
					this.parameters.getApplicationName(),
					this.parameters.getScopedInstancePath());

			Utils.writeStringInto( userData, new File( dir, USER_DATA_FILE ));
			Volume volume = new Volume( USER_DATA_DIR );
			Bind volumeBind = new Bind( dir.getAbsolutePath(), volume );

			// Create the container
			CreateContainerCmd cmd = this.dockerClient.createContainerCmd( imageId )
					.withName( containerName )
					.withEnv( env )
					.withVolumes( Arrays.asList( volume ))
					.withBinds( Arrays.asList( volumeBind ));

			DockerUtils.configureOptions( options, cmd );
			CreateContainerResponse container = cmd.exec();

			// Log warnings, if any
			if( container.getWarnings() != null
					&& container.getWarnings().length > 0
					&& this.logger.isLoggable( Level.FINE )) {

				StringBuilder sb = new StringBuilder();
				sb.append( "The following warnings have been found.\n" );
				for( String s : container.getWarnings())
					sb.append( s ).append( '\n' );

				this.logger.fine( sb.toString().trim());
			}

			// And start the container
			this.dockerClient.startContainerCmd( container.getId()).exec();

			// We're done here!
			this.logger.fine( "Container " + this.machineId + " was succesfully created as " + container.getId());

			// We replace the machine ID in the instance.
			// The configurator will be stopped anyway.
			this.parameters.getScopedInstance().data.put( Instance.MACHINE_ID, container.getId());

		} catch( Exception e ) {
			throw new TargetException( e );
		}
	}


	/**
	 * Creates an image.
	 * @param imageId the image ID
	 * @throws TargetException if something went wrong
	 */
	void createImage( String imageId ) throws TargetException {

		// If there is no Dockerfile, this method will do nothing
		File targetDirectory = this.parameters.getTargetPropertiesDirectory();
		String dockerFilePath = this.parameters.getTargetProperties().get( GENERATE_IMAGE_FROM );
		if( ! Utils.isEmptyOrWhitespaces( dockerFilePath ) && targetDirectory != null ) {

			this.logger.fine( "Trying to create image " + imageId + " from a Dockerfile." );
			File dockerFile = new File( targetDirectory, dockerFilePath );
			if( ! dockerFile.exists())
				throw new TargetException( "No Dockerfile was found at " + dockerFile );

			// Start the build.
			// This will block the current thread until the creation is complete.
			String builtImageId;
			this.logger.fine( "Asking Docker to build the image from our Dockerfile." );
			try {
				builtImageId = this.dockerClient
					.buildImageCmd( dockerFile )
					.withTags( Sets.newHashSet( imageId ))
					.withPull( true )
					.exec( new RoboconfBuildImageResultCallback())
					.awaitImageId();

			} catch( Exception e ) {
				Utils.logException( this.logger, e );
				throw new TargetException( e );
			}

			// No need to store the real image ID... Docker has it.
			// Besides, we search images by both IDs and tags.
			// Anyway, we can log the information.
			this.logger.fine( "Image '" + builtImageId + "' was succesfully generated by Roboconf." );
		}
	}


	/**
	 * A call back invoked when a Docker image was built.
	 * 

* No anonymous class in Roboconf. *

* * @author Vincent Zurczak - Linagora */ static class RoboconfBuildImageResultCallback extends BuildImageResultCallback { // nothing } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy