| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 org.apache.geode.perftest.jvms; |
| |
| |
| import static java.util.concurrent.TimeUnit.DAYS; |
| |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.security.InvalidKeyException; |
| import java.security.KeyStore; |
| import java.security.KeyStoreException; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.NoSuchProviderException; |
| import java.security.PrivateKey; |
| import java.security.SignatureException; |
| import java.security.cert.CertificateException; |
| import java.security.cert.X509Certificate; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.CompletableFuture; |
| import java.util.concurrent.TimeUnit; |
| |
| import org.apache.commons.io.FileUtils; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| import sun.security.tools.keytool.CertAndKeyGen; |
| import sun.security.x509.X500Name; |
| |
| import org.apache.geode.perftest.infrastructure.Infrastructure; |
| import org.apache.geode.perftest.infrastructure.InfrastructureFactory; |
| import org.apache.geode.perftest.jvms.classpath.ClassPathCopier; |
| import org.apache.geode.perftest.jvms.rmi.Controller; |
| import org.apache.geode.perftest.jvms.rmi.ControllerFactory; |
| import org.apache.geode.perftest.runner.SharedContext; |
| |
| /** |
| * Factory for launching JVMs and a given infrastructure and setting up RMI |
| * access to all JVMs. |
| */ |
| public class RemoteJVMFactory { |
| private static final Logger logger = LoggerFactory.getLogger(RemoteJVMFactory.class); |
| |
| public static final String RMI_HOST = "RMI_HOST"; |
| public static final String RMI_PORT_PROPERTY = "RMI_PORT"; |
| public static final String CONTROLLER = "CONTROLLER"; |
| public static final String OUTPUT_DIR = "OUTPUT_DIR"; |
| public static final String ROLE = "ROLE"; |
| public static final String JVM_ID = "JVM_ID"; |
| public static final int RMI_PORT = 33333; |
| private static final String CLASSPATH = System.getProperty("java.class.path"); |
| private static final String JAVA_HOME = System.getProperty("java.home"); |
| private final JVMLauncher jvmLauncher; |
| private final ClassPathCopier classPathCopier; |
| private final ControllerFactory controllerFactory; |
| private final InfrastructureFactory infrastructureFactory; |
| |
| public RemoteJVMFactory(InfrastructureFactory infrastructureFactory, |
| JVMLauncher jvmLauncher, |
| ClassPathCopier classPathCopier, |
| ControllerFactory controllerFactory) { |
| this.infrastructureFactory = infrastructureFactory; |
| this.jvmLauncher = jvmLauncher; |
| this.classPathCopier = classPathCopier; |
| this.controllerFactory = controllerFactory; |
| } |
| |
| public RemoteJVMFactory(InfrastructureFactory infrastructureFactory) { |
| this(infrastructureFactory, new JVMLauncher(), new ClassPathCopier(CLASSPATH, JAVA_HOME), |
| new ControllerFactory()); |
| } |
| |
| /** |
| * Start all requested JVMs on the infrastructure |
| * |
| * @param roles The JVMs to start. Keys a roles and values are the number |
| * of JVMs in that role. |
| * |
| * @return a {@link RemoteJVMs} object used to access the JVMs through RMI |
| */ |
| public RemoteJVMs launch(Map<String, Integer> roles, |
| Map<String, List<String>> jvmArgs) throws Exception { |
| int numWorkers = roles.values().stream().mapToInt(Integer::intValue).sum(); |
| |
| Infrastructure infra = infrastructureFactory.create(numWorkers); |
| |
| Set<Infrastructure.Node> nodes = infra.getNodes(); |
| |
| if (nodes.size() < numWorkers) { |
| throw new IllegalStateException( |
| "Too few nodes for test. Need " + numWorkers + ", have " + nodes.size()); |
| } |
| |
| List<JVMMapping> mapping = mapRolesToNodes(roles, nodes, jvmArgs); |
| |
| Controller controller = |
| controllerFactory.createController(new SharedContext(mapping), numWorkers); |
| |
| classPathCopier.copyToNodes(infra, node -> getLibDir(mapping, node)); |
| |
| final List<File> files = new ArrayList<>(); |
| files.add(createKeystore()); |
| |
| final File securityJsonFile = new File("security.json"); |
| FileUtils.copyURLToFile(getClass().getClassLoader().getResource("security.json"), |
| securityJsonFile); |
| files.add(securityJsonFile); |
| |
| if (JavaVersion.current().atLeast(JavaVersion.v17)) { |
| final File javaArgsFile = new File("java.args"); |
| FileUtils.copyURLToFile( |
| getClass().getClassLoader().getResource("open-all-jdk-packages-linux-openjdk-17"), |
| javaArgsFile); |
| files.add(javaArgsFile); |
| } |
| |
| infra.copyToNodes(files, node -> getLibDir(mapping, node), false); |
| |
| CompletableFuture<Void> processesExited = jvmLauncher.launchProcesses(infra, RMI_PORT, mapping); |
| |
| if (!controller.waitForWorkers(5, TimeUnit.MINUTES)) { |
| throw new IllegalStateException("Workers failed to start in 5 minute"); |
| } |
| |
| return new RemoteJVMs(infra, mapping, controller, processesExited); |
| } |
| |
| private JVMMapping getJvmMapping(List<JVMMapping> mapping, Infrastructure.Node node) { |
| return mapping.stream() |
| .filter(entry -> entry.getNode().equals(node)) |
| .findFirst() |
| .orElseThrow(() -> new IllegalStateException("Could not find node dir " + node)); |
| } |
| |
| private String getLibDir(List<JVMMapping> mapping, Infrastructure.Node node) { |
| return getJvmMapping(mapping, node) |
| .getLibDir(); |
| } |
| |
| private File createKeystore() |
| throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, |
| NoSuchProviderException, InvalidKeyException, SignatureException { |
| |
| CertAndKeyGen keyGen = new CertAndKeyGen("RSA", "SHA1WithRSA", null); |
| keyGen.generate(1024); |
| |
| char[] password = "123456".toCharArray(); |
| PrivateKey privateKey = keyGen.getPrivateKey(); |
| |
| // Generate self signed certificate |
| X509Certificate[] chain = new X509Certificate[1]; |
| chain[0] = keyGen.getSelfCertificate(new X500Name("CN=ROOT"), DAYS.toSeconds(365)); |
| |
| logger.debug("Certificate : {}", chain[0]); |
| |
| KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); |
| ks.load(null, null); |
| ks.setKeyEntry("default", privateKey, password, chain); |
| |
| File jksFile = new File("temp-self-signed.jks"); |
| FileOutputStream fos = new FileOutputStream(jksFile); |
| ks.store(fos, password); |
| fos.close(); |
| |
| return jksFile; |
| } |
| |
| public InfrastructureFactory getInfrastructureFactory() { |
| return infrastructureFactory; |
| } |
| |
| private List<JVMMapping> mapRolesToNodes(Map<String, Integer> roles, |
| Set<Infrastructure.Node> nodes, |
| Map<String, List<String>> jvmArgs) { |
| List<JVMMapping> mapping = new ArrayList<>(); |
| Iterator<Infrastructure.Node> nodeItr = nodes.iterator(); |
| |
| int id = 0; |
| for (Map.Entry<String, Integer> roleEntry : roles.entrySet()) { |
| for (int i = 0; i < roleEntry.getValue(); i++) { |
| Infrastructure.Node node = nodeItr.next(); |
| String role = roleEntry.getKey(); |
| List<String> roleArgs = jvmArgs.getOrDefault(role, Collections.emptyList()); |
| mapping.add(new JVMMapping(node, role, id++, roleArgs)); |
| } |
| |
| } |
| return mapping; |
| } |
| } |