Java tutorial
/* * LICENCE : CloudUnit is available under the GNU Affero General Public License : https://gnu.org/licenses/agpl.html * but CloudUnit is licensed too under a standard commercial license. * Please contact our sales team if you would like to discuss the specifics of our Enterprise license. * If you are not sure whether the AGPL is right for you, * you can always test our software under the AGPL and inspect the source code before you contact us * about purchasing a commercial license. * * LEGAL TERMS : "CloudUnit" is a registered trademark of Treeptik and can't be used to endorse * or promote products derived from this project without prior written permission from Treeptik. * Products or services derived from this software may not be called "CloudUnit" * nor may "Treeptik" or similar confusing terms appear in their names without prior written permission. * For any questions, contact us : contact@treeptik.fr */ package cn.org.once.cstack.service.impl; import java.util.*; import java.util.stream.Collectors; import javax.annotation.PostConstruct; import javax.inject.Inject; import javax.persistence.PersistenceException; import cn.org.once.cstack.config.events.ServerStopEvent; import cn.org.once.cstack.dao.ServerDAO; import cn.org.once.cstack.enums.RemoteExecAction; import cn.org.once.cstack.model.User; import cn.org.once.cstack.service.EnvironmentService; import cn.org.once.cstack.service.VolumeAssociationService; import cn.org.once.cstack.utils.NamingUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.annotation.CacheEvict; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.spotify.docker.client.DockerClient; import com.spotify.docker.client.exceptions.DockerException; import com.spotify.docker.client.messages.VolumeList; import cn.org.once.cstack.config.events.ApplicationPendingEvent; import cn.org.once.cstack.config.events.ApplicationStartEvent; import cn.org.once.cstack.config.events.ServerStartEvent; import cn.org.once.cstack.dao.ApplicationDAO; import cn.org.once.cstack.dto.VolumeAssociationDTO; import cn.org.once.cstack.exception.CheckException; import cn.org.once.cstack.exception.DockerJSONException; import cn.org.once.cstack.exception.FatalDockerJSONException; import cn.org.once.cstack.exception.ServiceException; import cn.org.once.cstack.model.Application; import cn.org.once.cstack.model.Server; import cn.org.once.cstack.model.Status; import cn.org.once.cstack.model.Volume; import cn.org.once.cstack.model.VolumeAssociation; import cn.org.once.cstack.model.VolumeAssociationId; import cn.org.once.cstack.service.DockerService; import cn.org.once.cstack.service.ServerService; import cn.org.once.cstack.service.VolumeService; @Service public class ServerServiceImpl implements ServerService { private Logger logger = LoggerFactory.getLogger(ServerServiceImpl.class); @Inject private ServerDAO serverDAO; @Inject private ApplicationDAO applicationDAO; @Value("${cloudunit.instance.name}") private String cuInstanceName; @Value("#{environment.CU_DOMAIN}") private String domainSuffix; protected String domain; @Inject private DockerService dockerService; @Inject private ApplicationEventPublisher applicationEventPublisher; @Inject private VolumeService volumeService; @Inject private VolumeAssociationService volumeAssociationService; @Inject private EnvironmentService environmentService; @Inject private DockerClient dockerClient; public ServerDAO getServerDAO() { return this.serverDAO; } @PostConstruct public void init() throws ServiceException { domain = NamingUtils.getCloudUnitDomain(domainSuffix); } /** * Save app in just in DB, not create container use principally to charge * status.PENDING of entity until it's really functionnal */ @Override @Transactional public Server saveInDB(Server server) throws ServiceException { server = serverDAO.save(server); return server; } /** * Create a server with or without a tag. Tag parameter is needed for * restore processus after cloning The idea is to use the same logic for a * new server or another one coming from registry. * * @param server * @return * @throws ServiceException * @throws CheckException */ @Override @Transactional public Server create(Server server) throws ServiceException, CheckException { logger.debug("create : Methods parameters : " + server); logger.info("ServerService : Starting creating Server " + server.getName()); // General information Application application = server.getApplication(); User user = server.getApplication().getUser(); // Build a custom container String containerName = NamingUtils.getContainerName(server.getApplication().getName(), null, server.getApplication().getUser().getLogin()); String imagePath = server.getImage().getPath(); String imageSubType = server.getImage().getImageSubType().toString(); try { dockerService.createServer(containerName, server, imagePath, imageSubType, user, null, true, null); server = dockerService.startServer(containerName, server); server = serverDAO.saveAndFlush(server); // Update server with all its information server.setManagerLocation(String.format("http://%s/%s", containerName + domain, dockerService.getEnv(server.getName(), "CU_SERVER_MANAGER_PATH"))); server.setStatus(Status.START); server = this.update(server); } catch (PersistenceException e) { logger.error("ServerService Error : Create Server " + e); throw new ServiceException(e.getLocalizedMessage(), e); } catch (DockerJSONException e) { StringBuilder msgError = new StringBuilder(512); msgError.append("server=").append(server); logger.error("" + msgError, e); throw new ServiceException(msgError.toString(), e); } logger.info("ServerService : Server " + server.getName() + " successfully created."); return server; } /** * check if the status passed in parameter is the same as in db if it's case * a checkException is throws * * @throws ServiceException * CheckException */ @Override public void checkStatus(Server server, String status) throws CheckException { if (server.getStatus().name().equalsIgnoreCase(status)) { throw new CheckException("Error : Server " + server.getName() + " is already " + status + "ED"); } } /** * check if the status is PENDING return TRUE else return false * * @throws ServiceException * CheckException */ @Override public boolean checkStatusPENDING(Server server) throws ServiceException { logger.info("--CHECK SERVER STATUS PENDING--"); if (server.getStatus().name().equalsIgnoreCase("PENDING")) { return true; } else { return false; } } @Transactional public Server update(Server server) throws ServiceException { logger.info("ServerService : Starting updating Server " + server.getName()); try { serverDAO.save(server); } catch (PersistenceException | FatalDockerJSONException e) { logger.error("ServerService Error : update Server" + e); e.printStackTrace(); throw new ServiceException("Error database : " + e.getLocalizedMessage(), e); } logger.info("ServerService : Server " + server.getName() + " successfully updated."); return server; } @Override @Transactional public Server remove(String serverName) throws ServiceException { Server server = null; try { server = this.findByName(serverName); Application application = server.getApplication(); cleanServerDependencies(server.getName(), application.getUser(), application.getName()); try { dockerService.removeContainer(server.getName(), true); } catch (Exception e) { logger.error("Cannot delete the container [" + serverName + "]. Maybe already destroyed"); } serverDAO.delete(server); logger.info("ServerService : Server successfully removed "); } catch (PersistenceException e) { logger.error("Error database : " + server.getName() + " : " + e); throw new ServiceException("Error database : " + e.getLocalizedMessage(), e); } catch (DockerJSONException e) { logger.error(serverName, e); } return server; } @Override public Server findById(Integer id) throws ServiceException { try { logger.debug("findById : Methods parameters : " + id); Server server = serverDAO.findOne(id); if (server != null) { logger.info("Server with id " + id + " found!"); logger.info("" + server); } return server; } catch (PersistenceException e) { logger.error("Error ServerService : error findById Method : " + e); throw new ServiceException("Error database : " + e.getLocalizedMessage(), e); } } @Override public List<Server> findAll() throws ServiceException { try { logger.debug("start findAll"); List<Server> servers = serverDAO.findAll(); logger.info("ServerService : All Servers found "); return servers; } catch (PersistenceException e) { logger.error("Error ServerService : error findAll Method : " + e); throw new ServiceException("Error database : " + e.getLocalizedMessage(), e); } } @Override @Transactional public Server startServer(Server server) throws ServiceException { logger.info("ServerService : Starting Server " + server.getName()); try { Application application = server.getApplication(); // Call the hook for pre start server.setStartDate(new Date()); applicationDAO.saveAndFlush(application); server = this.update(server); server = dockerService.startServer(server.getName(), server); applicationEventPublisher.publishEvent(new ServerStartEvent(server)); } catch (DockerJSONException e) { e.printStackTrace(); throw new ServiceException(server.toString(), e); } catch (PersistenceException e) { throw new ServiceException(server.toString(), e); } return server; } @Override @Transactional public Server stopServer(Server server) throws ServiceException { try { dockerService.execCommand(server.getName(), RemoteExecAction.CLEAN_LOGS.getCommand()); dockerService.stopContainer(server.getName()); applicationEventPublisher.publishEvent(new ServerStopEvent(server)); } catch (PersistenceException e) { throw new ServiceException(server.toString(), e); } catch (DockerJSONException e) { throw new ServiceException(server.toString(), e); } return server; } @Override @Transactional public Server restartServer(Server server) throws ServiceException { server = this.stopServer(server); server = this.startServer(server); return server; } @Override public Server findByName(String serverName) throws ServiceException { try { return serverDAO.findByName(serverName); } catch (PersistenceException e) { throw new ServiceException("Error database : " + e.getLocalizedMessage(), e); } } @Override public Server findByApp(Application application) throws ServiceException { try { return serverDAO.findByApp(application.getId()); } catch (PersistenceException e) { throw new ServiceException("Error database : " + e.getLocalizedMessage(), e); } } @Override public Server findByContainerID(String id) throws ServiceException { try { return serverDAO.findByContainerID(id); } catch (PersistenceException e) { throw new ServiceException("Error database : " + e.getLocalizedMessage(), e); } } @CacheEvict(value = "env", allEntries = true) @Transactional public Server update(Server server, String jvmMemory, String options, boolean restorePreviousEnv) throws ServiceException { String previousJvmMemory = server.getJvmMemory().toString(); String previousJvmOptions = server.getJvmOptions(); options = options == null ? "" : options; final String jvmOptions = options.replaceAll("//", "\\\\/\\\\/"); try { List<String> envs = environmentService.loadEnvironnmentsByContainer(server.getName()).stream() .map(e -> e.getKeyEnv() + "=" + e.getValueEnv()).collect(Collectors.toList()); String currentJvmMemory = dockerService.getEnv(server.getName(), "JAVA_OPTS"); currentJvmMemory = currentJvmMemory.replaceAll(previousJvmMemory, jvmMemory); currentJvmMemory = currentJvmMemory.substring(currentJvmMemory.lastIndexOf("-Xms")); currentJvmMemory = jvmOptions + " " + currentJvmMemory; envs.add("JAVA_OPTS=" + currentJvmMemory); dockerService.stopContainer(server.getName()); dockerService.removeContainer(server.getName(), false); List<String> volumes = volumeService.loadAllByContainerName(server.getName()).stream() .map(v -> v.getName() + ":" + v.getVolumeAssociations().stream().findFirst().get().getPath() + ":" + v.getVolumeAssociations().stream().findFirst().get().getMode()) .collect(Collectors.toList()); dockerService.createServer(server.getName(), server, server.getImage().getPath(), server.getImage().getImageSubType().toString(), server.getApplication().getUser(), envs, false, volumes); server = startServer(server); server.setJvmMemory(Long.valueOf(jvmMemory)); server.setJvmOptions(jvmOptions); server = saveInDB(server); } catch (Exception e) { if (!restorePreviousEnv) { StringBuilder msgError = new StringBuilder(); msgError.append("jvmMemory:").append(jvmMemory).append(","); msgError.append("jvmOptions:").append(jvmOptions).append(","); throw new ServiceException(msgError.toString(), e); } else { // Rollback to previous configuration logger.warn("Restore the previous environment for jvm configuration. Maybe a syntax error"); update(server, previousJvmMemory, previousJvmOptions, false); } } return server; } /** * Change the version of the jvm * * @param application * @param javaVersion * @throws CheckException * @throws ServiceException */ @Override public void changeJavaVersion(Application application, String javaVersion) throws CheckException, ServiceException { logger.info( "Starting changing to java version " + javaVersion + ", the application " + application.getName()); try { // todo } catch (Exception e) { throw new ServiceException(application + ", javaVersion:" + javaVersion, e); } } @Override @Transactional @CacheEvict(value = "env", allEntries = true) public void addVolume(Application application, VolumeAssociationDTO volumeAssociationDTO) throws ServiceException, CheckException { checkVolumeFormat(volumeAssociationDTO); Volume volume = null; if (!volumeService.loadAllVolumes().stream() .filter(v -> v.getName().equals(volumeAssociationDTO.getVolumeName())).findAny().isPresent()) { VolumeList volumeList = null; try { volumeList = dockerClient.listVolumes(); } catch (InterruptedException | DockerException e) { throw new ServiceException("Action failed"); } if (volumeList.volumes().stream().filter(v -> v.name().equals(volumeAssociationDTO.getVolumeName())) .findFirst().orElse(null) == null) { throw new CheckException("This volume does not exist"); } else { volume = volumeService.registerNewVolume(volumeAssociationDTO.getVolumeName()); } } else { volume = volumeService.findByName(volumeAssociationDTO.getVolumeName()); } if (volumeAssociationService.checkVolumeAssociationPathAlreadyPresent(volumeAssociationDTO.getPath(), application.getServer().getId()) > 0) { throw new CheckException("This path is already use !"); } VolumeAssociation volumeAssociation = new VolumeAssociation( new VolumeAssociationId(application.getServer(), volume), volumeAssociationDTO.getPath(), volumeAssociationDTO.getMode()); volumeService.saveAssociation(volumeAssociation); volume.getVolumeAssociations().add(volumeAssociation); application.getServer().getVolumeAssociations().add(volumeAssociation); stopAndRemoveServer(application.getServer(), application); recreateAndMountVolumes(application.getServer(), application); } @Override @Transactional @CacheEvict(value = "env", allEntries = true) public void removeVolume(String containerName, String volumeName) throws ServiceException { Server server = null; try { server = findByName(containerName); Volume volume = volumeService.findByName(volumeName); volumeService .removeAssociation(new VolumeAssociation(new VolumeAssociationId(server, volume), null, null)); stopAndRemoveServer(server, server.getApplication()); recreateAndMountVolumes(server, server.getApplication()); } catch (CheckException e) { e.printStackTrace(); throw new CheckException(e.getMessage()); } catch (Exception e) { e.printStackTrace(); throw new ServiceException(e.getMessage()); } finally { applicationEventPublisher.publishEvent(new ServerStartEvent(server)); applicationEventPublisher.publishEvent(new ApplicationStartEvent(server.getApplication())); } } private void stopAndRemoveServer(Server server, Application application) throws ServiceException { applicationEventPublisher.publishEvent(new ApplicationPendingEvent(application)); applicationEventPublisher.publishEvent(new ServerStopEvent(server)); dockerService.removeContainer(server.getName(), false); } @Transactional private void recreateAndMountVolumes(Server server, Application application) throws ServiceException { List<String> volumes = volumeService.loadAllByContainerName(server.getName()).stream() .map(v -> v.getName() + ":" + v.getVolumeAssociations().stream().findFirst().get().getPath() + ":" + v.getVolumeAssociations().stream().findFirst().get().getMode()) .collect(Collectors.toList()); List<String> envs = environmentService.loadEnvironnmentsByContainer(server.getName()).stream() .map(e -> e.getKeyEnv() + "=" + e.getValueEnv()).collect(Collectors.toList()); dockerService.createServer(server.getName(), server, server.getImage().getPath(), server.getImage().getImageSubType().toString(), server.getApplication().getUser(), envs, false, volumes); server = startServer(server); //addCredentialsForServerManagement(server, server.getApplication().getUser()); } private void checkVolumeFormat(VolumeAssociationDTO volume) throws ServiceException { if (volume.getVolumeName() == null || volume.getVolumeName().isEmpty()) throw new CheckException("This name is not consistent !"); if (volume.getApplicationName() == null || volume.getApplicationName().isEmpty()) throw new CheckException("Application name is not consistent !"); if (volume.getContainerName() == null || volume.getContainerName().isEmpty()) throw new CheckException("Application name is not consistent !"); if (volume.getPath() == null || volume.getPath().isEmpty() || !volume.getPath().startsWith("/") || !volume.getPath().replaceAll("/", "").matches("^[-a-zA-Z0-9_]*$")) throw new CheckException("This path is not consistent !"); if (!volume.getVolumeName().matches("^[-a-zA-Z0-9_]*$")) throw new CheckException("This name is not consistent : " + volume.getVolumeName()); if (!(volume.getMode().equalsIgnoreCase("ro") || volume.getMode().equalsIgnoreCase("rw"))) { throw new CheckException("Authorized mode value : ro (readOnly) or rw (read-write)"); } } private void cleanServerDependencies(String name, User user, String applicationName) throws ServiceException { volumeService.loadAllByContainerName(name).stream().forEach( v -> volumeService.removeAssociation(v.getVolumeAssociations().stream().findFirst().get())); } }