diff options
Diffstat (limited to 'standalone-container/src/main/java')
5 files changed, 1135 insertions, 0 deletions
diff --git a/standalone-container/src/main/java/com/yahoo/application/container/impl/ClassLoaderOsgiFramework.java b/standalone-container/src/main/java/com/yahoo/application/container/impl/ClassLoaderOsgiFramework.java new file mode 100644 index 00000000000..8d4126a01e3 --- /dev/null +++ b/standalone-container/src/main/java/com/yahoo/application/container/impl/ClassLoaderOsgiFramework.java @@ -0,0 +1,569 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.application.container.impl; + +import com.google.common.collect.Lists; +import com.yahoo.container.standalone.StandaloneContainerApplication; +import com.yahoo.jdisc.application.OsgiFramework; +import com.yahoo.jdisc.application.OsgiHeader; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleListener; +import org.osgi.framework.Filter; +import org.osgi.framework.FrameworkListener; +import org.osgi.framework.ServiceFactory; +import org.osgi.framework.ServiceListener; +import org.osgi.framework.ServiceObjects; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; +import org.osgi.framework.Version; +import org.osgi.framework.wiring.BundleCapability; +import org.osgi.framework.wiring.BundleRequirement; +import org.osgi.framework.wiring.BundleRevision; +import org.osgi.framework.wiring.BundleWire; +import org.osgi.framework.wiring.BundleWiring; +import org.osgi.resource.Capability; +import org.osgi.resource.Requirement; +import org.osgi.resource.Wire; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.jar.Attributes; +import java.util.jar.JarFile; + +/** + * A (mock) OSGI implementation which loads classes from the system classpath + * + * @author Tony Vaagenes + * @author ollivir + */ +public final class ClassLoaderOsgiFramework implements OsgiFramework { + private BundleContextImpl bundleContextImpl = new BundleContextImpl(); + private SystemBundleImpl systemBundleImpl = new SystemBundleImpl(); + private BundleWiringImpl bundleWiringImpl = new BundleWiringImpl(); + + private List<URL> bundleLocations = new ArrayList<>(); + private List<Bundle> bundleList = Lists.newArrayList(systemBundleImpl); + private ClassLoader classLoader = null; + + private AtomicInteger nextBundleId = new AtomicInteger(1); + + @Override + public List<Bundle> installBundle(String bundleLocation) { + if (bundleLocation != null && bundleLocation.isEmpty() == false) { + try { + URL url = new URL(bundleLocation); + bundleLocations.add(url); + bundleList.add(new JarBundleImpl(url)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + return bundles(); + } + + private ClassLoader getClassLoader() { + if (bundleLocations.isEmpty()) { + return getClass().getClassLoader(); + } else { + if (classLoader == null) { + classLoader = new URLClassLoader(bundleLocations.toArray(new URL[0]), getClass().getClassLoader()); + } + return classLoader; + } + } + + @Override + public void startBundles(List<Bundle> bundles, boolean privileged) { + } + + @Override + public void refreshPackages() { + } + + @Override + public BundleContext bundleContext() { + return bundleContextImpl; + } + + @Override + public List<Bundle> bundles() { + return bundleList; + } + + @Override + public void start() { + } + + @Override + public void stop() { + } + + private abstract class BundleImpl implements Bundle { + @Override + public int getState() { + return Bundle.ACTIVE; + } + + @Override + public void start(int options) { + } + + @Override + public void start() { + } + + @Override + public void stop(int options) { + } + + @Override + public void stop() { + } + + @Override + public void update(InputStream input) { + } + + @Override + public void update() { + } + + @Override + public void uninstall() { + } + + @Override + public Dictionary<String, String> getHeaders(String locale) { + return getHeaders(); + } + + @Override + public String getSymbolicName() { + return ClassLoaderOsgiFramework.this.getClass().getName(); + } + + @Override + public String getLocation() { + return getSymbolicName(); + } + + @Override + public ServiceReference<?>[] getRegisteredServices() { + return new ServiceReference<?>[0]; + } + + @Override + public ServiceReference<?>[] getServicesInUse() { + return getRegisteredServices(); + } + + @Override + public boolean hasPermission(Object permission) { + return true; + } + + @Override + public URL getResource(String name) { + return getClassLoader().getResource(name); + } + + @Override + public Class<?> loadClass(String name) throws ClassNotFoundException { + return getClassLoader().loadClass(name); + } + + @Override + public Enumeration<URL> getResources(String name) throws IOException { + return getClassLoader().getResources(name); + } + + @Override + public Enumeration<String> getEntryPaths(String path) { + throw new UnsupportedOperationException(); + } + + @Override + public URL getEntry(String path) { + throw new UnsupportedOperationException(); + } + + @Override + public Enumeration<URL> findEntries(String path, String filePattern, boolean recurse) { + throw new UnsupportedOperationException(); + } + + @Override + public long getLastModified() { + return 1L; + } + + @Override + public BundleContext getBundleContext() { + throw new UnsupportedOperationException(); + } + + @Override + public Map<X509Certificate, List<X509Certificate>> getSignerCertificates(int signersType) { + return Collections.emptyMap(); + } + + @Override + @SuppressWarnings("unchecked") + public <T> T adapt(Class<T> clazz) { + if (clazz.equals(BundleRevision.class)) { + return (T) new BundleRevisionImpl(); + } else if (clazz.equals(BundleWiring.class)) { + return (T) new BundleWiringImpl(); + } else { + return null; + } + } + + @Override + public File getDataFile(String filename) { + return null; + } + + @Override + public int compareTo(Bundle o) { + return Long.compare(getBundleId(), o.getBundleId()); + } + } + + private class BundleRevisionImpl implements BundleRevision { + @Override + public String getSymbolicName() { + return this.getClass().getName(); + } + + @Override + public List<BundleRequirement> getDeclaredRequirements(String p1) { + throw new UnsupportedOperationException(); + } + + @Override + public Version getVersion() { + return Version.emptyVersion; + } + + @Override + public BundleWiring getWiring() { + return bundleWiringImpl; + } + + @Override + public List<BundleCapability> getDeclaredCapabilities(String p1) { + throw new UnsupportedOperationException(); + } + + @Override + public int getTypes() { + return 0; + } + + @Override + public Bundle getBundle() { + throw new UnsupportedOperationException(); + } + + @Override + public List<Capability> getCapabilities(String p1) { + throw new UnsupportedOperationException(); + } + + @Override + public List<Requirement> getRequirements(String p1) { + throw new UnsupportedOperationException(); + } + } + + private class BundleWiringImpl implements BundleWiring { + @Override + public List<URL> findEntries(String p1, String p2, int p3) { + throw new UnsupportedOperationException(); + } + + @Override + public List<Wire> getRequiredResourceWires(String p1) { + throw new UnsupportedOperationException(); + } + + @Override + public List<Capability> getResourceCapabilities(String p1) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isCurrent() { + throw new UnsupportedOperationException(); + } + + @Override + public List<BundleWire> getRequiredWires(String p1) { + throw new UnsupportedOperationException(); + } + + @Override + public List<BundleCapability> getCapabilities(String p1) { + throw new UnsupportedOperationException(); + } + + @Override + public List<Wire> getProvidedResourceWires(String p1) { + throw new UnsupportedOperationException(); + } + + @Override + public List<BundleWire> getProvidedWires(String p1) { + throw new UnsupportedOperationException(); + } + + @Override + public BundleRevision getRevision() { + throw new UnsupportedOperationException(); + } + + @Override + public List<Requirement> getResourceRequirements(String p1) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isInUse() { + throw new UnsupportedOperationException(); + } + + @Override + public Collection<String> listResources(String p1, String p2, int p3) { + throw new UnsupportedOperationException(); + } + + @Override + public ClassLoader getClassLoader() { + return ClassLoaderOsgiFramework.this.getClassLoader(); + } + + @Override + public List<BundleRequirement> getRequirements(String p1) { + throw new UnsupportedOperationException(); + } + + @Override + public BundleRevision getResource() { + throw new UnsupportedOperationException(); + } + + @Override + public Bundle getBundle() { + throw new UnsupportedOperationException(); + } + } + + private class SystemBundleImpl extends BundleImpl { + @Override + public long getBundleId() { + return 0L; + } + + @Override + public Version getVersion() { + return Version.emptyVersion; + } + + @Override + public Dictionary<String, String> getHeaders() { + Hashtable<String, String> ret = new Hashtable<>(); + ret.put(OsgiHeader.APPLICATION, StandaloneContainerApplication.class.getName()); + return ret; + } + } + + private class JarBundleImpl extends BundleImpl { + private final long bundleId; + private final Dictionary<String, String> headers; + + JarBundleImpl(URL location) throws IOException { + this.bundleId = (long) nextBundleId.getAndIncrement(); + this.headers = retrieveHeaders(location); + } + + @Override + public long getBundleId() { + return bundleId; + } + + @Override + public Dictionary<String, String> getHeaders() { + return headers; + } + + @Override + public String getSymbolicName() { + return headers.get("Bundle-SymbolicName"); + } + + @Override + public Version getVersion() { + return Version.parseVersion(headers.get("Bundle-Version")); + } + + private Dictionary<String, String> retrieveHeaders(URL location) throws IOException { + try (JarFile jarFile = new JarFile(location.getFile())) { + Attributes attributes = jarFile.getManifest().getMainAttributes(); + Hashtable<String, String> ret = new Hashtable<>(); + attributes.forEach((k, v) -> ret.put(k.toString(), v.toString())); + return ret; + } + } + } + + private class BundleContextImpl implements BundleContext { + @Override + public String getProperty(String key) { + return null; + } + + @Override + public Bundle getBundle() { + return systemBundleImpl; + } + + @Override + public Bundle installBundle(String location, InputStream input) { + throw new UnsupportedOperationException(); + } + + @Override + public Bundle installBundle(String location) { + throw new UnsupportedOperationException(); + } + + @Override + public Bundle getBundle(long id) { + return systemBundleImpl; + } + + @Override + public Bundle[] getBundles() { + return new Bundle[] { systemBundleImpl }; + } + + @Override + public Bundle getBundle(String location) { + return systemBundleImpl; + } + + @Override + public void addServiceListener(ServiceListener listener, String filter) { + } + + @Override + public void addServiceListener(ServiceListener listener) { + } + + @Override + public void removeServiceListener(ServiceListener listener) { + } + + @Override + public void addBundleListener(BundleListener listener) { + } + + @Override + public void removeBundleListener(BundleListener listener) { + } + + @Override + public void addFrameworkListener(FrameworkListener listener) { + } + + @Override + public void removeFrameworkListener(FrameworkListener listener) { + } + + @Override + public ServiceRegistration<?> registerService(String[] classes, Object service, Dictionary<String, ?> properties) { + throw new UnsupportedOperationException(); + } + + @Override + public ServiceRegistration<?> registerService(String clazz, Object service, Dictionary<String, ?> properties) { + return null; + } + + @Override + public <S> ServiceRegistration<S> registerService(Class<S> clazz, S service, Dictionary<String, ?> properties) { + throw new UnsupportedOperationException(); + } + + @Override + public ServiceReference<?>[] getServiceReferences(String clazz, String filter) { + throw new UnsupportedOperationException(); + } + + @Override + public ServiceReference<?>[] getAllServiceReferences(String clazz, String filter) { + throw new UnsupportedOperationException(); + } + + @Override + public ServiceReference<?> getServiceReference(String clazz) { + throw new UnsupportedOperationException(); + } + + @Override + public <S> ServiceReference<S> getServiceReference(Class<S> clazz) { + throw new UnsupportedOperationException(); + } + + @Override + public <S> Collection<ServiceReference<S>> getServiceReferences(Class<S> clazz, String filter) { + return new ArrayList<>(); + } + + @Override + public <S> S getService(ServiceReference<S> reference) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean ungetService(ServiceReference<?> reference) { + throw new UnsupportedOperationException(); + } + + @Override + public File getDataFile(String filename) { + throw new UnsupportedOperationException(); + } + + @Override + public Filter createFilter(String filter) { + throw new UnsupportedOperationException(); + } + + @Override + public <S> ServiceRegistration<S> registerService(Class<S> aClass, ServiceFactory<S> serviceFactory, + Dictionary<String, ?> dictionary) { + throw new UnsupportedOperationException(); + } + + @Override + public <S> ServiceObjects<S> getServiceObjects(ServiceReference<S> serviceReference) { + throw new UnsupportedOperationException(); + } + } +} diff --git a/standalone-container/src/main/java/com/yahoo/application/container/impl/StandaloneContainerRunner.java b/standalone-container/src/main/java/com/yahoo/application/container/impl/StandaloneContainerRunner.java new file mode 100644 index 00000000000..a0fee3265df --- /dev/null +++ b/standalone-container/src/main/java/com/yahoo/application/container/impl/StandaloneContainerRunner.java @@ -0,0 +1,34 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.application.container.impl; + +import com.yahoo.text.Utf8; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * @author Tony Vaagenes + * @author ollivir + */ +public class StandaloneContainerRunner { + public static Path createApplicationPackage(String servicesXml) { + try { + return createApplicationDirectory(servicesXml); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static Path createApplicationDirectory(String servicesXml) throws IOException { + Path applicationDir = Files.createTempDirectory("application"); + Path servicesXmlFile = applicationDir.resolve("services.xml"); + String content = servicesXml; + + if (!servicesXml.startsWith("<?xml")) { + content = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" + servicesXml; + } + Files.write(servicesXmlFile, Utf8.toBytes(content)); + return applicationDir; + } +} diff --git a/standalone-container/src/main/java/com/yahoo/container/standalone/LocalFileDb.java b/standalone-container/src/main/java/com/yahoo/container/standalone/LocalFileDb.java new file mode 100644 index 00000000000..4bbe9986d90 --- /dev/null +++ b/standalone-container/src/main/java/com/yahoo/container/standalone/LocalFileDb.java @@ -0,0 +1,97 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.standalone; + +import com.yahoo.config.FileReference; +import com.yahoo.config.application.api.FileRegistry; +import com.yahoo.filedistribution.fileacquirer.FileAcquirer; +import com.yahoo.net.HostName; + +import java.io.File; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * FileAcquirer and FileRegistry working on a local directory. + * + * @author Tony Vaagenes + * @author ollivir + */ +public class LocalFileDb implements FileAcquirer, FileRegistry { + private static final Constructor<FileReference> fileReferenceConstructor = createFileReferenceConstructor(); + + private final Map<FileReference, File> fileReferenceToFile = new HashMap<>(); + private final Path appPath; + + public LocalFileDb(Path appPath) { + this.appPath = appPath; + } + + /* FileAcquirer overrides */ + @Override + public File waitFor(FileReference reference, long l, TimeUnit timeUnit) { + synchronized (this) { + File file = fileReferenceToFile.get(reference); + if (file == null) { + throw new RuntimeException("Invalid file reference " + reference); + } + return file; + } + } + + @Override + public void shutdown() { + } + + /* FileRegistry overrides */ + public FileReference addFile(String relativePath) { + File file = appPath.resolve(relativePath).toFile(); + if (!file.exists()) { + throw new RuntimeException("The file does not exist: " + file.getPath()); + } + + FileReference fileReference = null; + try { + fileReference = fileReferenceConstructor.newInstance("LocalFileDb:" + relativePath); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException("Unable to create new FileReference", e); + } + fileReferenceToFile.put(fileReference, file); + return fileReference; + } + + @Override + public List<Entry> export() { + return fileReferenceToFile.entrySet().stream().map(entry -> new Entry(entry.getValue().getPath(), entry.getKey())) + .collect(Collectors.toList()); + } + + @Override + public FileReference addUri(String uri) { + throw new RuntimeException("addUri(String uri) is not implemented here."); + } + + public String fileSourceHost() { + return HostName.getLocalhost(); + } + + public Set<String> allRelativePaths() { + return fileReferenceToFile.values().stream().map(File::getPath).collect(Collectors.toSet()); + } + + private static Constructor<FileReference> createFileReferenceConstructor() { + try { + Constructor<FileReference> method = FileReference.class.getDeclaredConstructor(String.class); + method.setAccessible(true); + return method; + } catch (NoSuchMethodException ex) { + throw new IllegalStateException(ex); + } + } +} diff --git a/standalone-container/src/main/java/com/yahoo/container/standalone/StandaloneContainerApplication.java b/standalone-container/src/main/java/com/yahoo/container/standalone/StandaloneContainerApplication.java new file mode 100644 index 00000000000..72937301954 --- /dev/null +++ b/standalone-container/src/main/java/com/yahoo/container/standalone/StandaloneContainerApplication.java @@ -0,0 +1,304 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.standalone; + +import com.google.inject.AbstractModule; +import com.google.inject.ConfigurationException; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.ProvisionException; +import com.google.inject.name.Named; +import com.google.inject.name.Names; +import com.yahoo.collections.Pair; +import com.yahoo.config.application.api.ApplicationPackage; +import com.yahoo.config.application.api.DeployLogger; +import com.yahoo.config.application.api.FileRegistry; +import com.yahoo.config.model.ApplicationConfigProducerRoot; +import com.yahoo.config.model.ConfigModelRepo; +import com.yahoo.config.model.application.provider.BaseDeployLogger; +import com.yahoo.config.model.application.provider.FilesApplicationPackage; +import com.yahoo.config.model.application.provider.StaticConfigDefinitionRepo; +import com.yahoo.config.model.builder.xml.ConfigModelId; +import com.yahoo.config.model.builder.xml.XmlHelper; +import com.yahoo.config.model.deploy.DeployState; +import com.yahoo.config.provision.Zone; +import com.yahoo.container.di.config.SubscriberFactory; +import com.yahoo.container.jdisc.ConfiguredApplication; +import com.yahoo.io.IOUtils; +import com.yahoo.jdisc.application.Application; +import com.yahoo.text.XML; +import com.yahoo.vespa.defaults.Defaults; +import com.yahoo.vespa.model.HostResource; +import com.yahoo.vespa.model.VespaModel; +import com.yahoo.vespa.model.builder.xml.dom.VespaDomBuilder; +import com.yahoo.vespa.model.container.Container; +import com.yahoo.vespa.model.container.ContainerModel; +import com.yahoo.vespa.model.container.xml.ConfigServerContainerModelBuilder; +import com.yahoo.vespa.model.container.xml.ContainerModelBuilder; +import com.yahoo.vespa.model.container.xml.ContainerModelBuilder.Networking; +import org.w3c.dom.Element; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import static com.yahoo.collections.CollectionUtil.first; + +/** + * @author Tony Vaagenes + * @author gjoranv + * @author ollivir + */ +public class StandaloneContainerApplication implements Application { + public static final String PACKAGE_NAME = "standalone_jdisc_container"; + public static final String APPLICATION_LOCATION_INSTALL_VARIABLE = PACKAGE_NAME + ".app_location"; + public static final String DEPLOYMENT_PROFILE_INSTALL_VARIABLE = PACKAGE_NAME + ".deployment_profile"; + public static final String DISABLE_NETWORKING_ANNOTATION = "JDisc.disableNetworking"; + public static final Named APPLICATION_PATH_NAME = Names.named(APPLICATION_LOCATION_INSTALL_VARIABLE); + public static final Named CONFIG_MODEL_REPO_NAME = Names.named("ConfigModelRepo"); + + private static final String DEFAULT_TMP_BASE_DIR = Defaults.getDefaults().underVespaHome("tmp"); + private static final String TMP_DIR_NAME = "standalone_container"; + + private static final StaticConfigDefinitionRepo configDefinitionRepo = new StaticConfigDefinitionRepo(); + + private final Injector injector; + private final Path applicationPath; + private final LocalFileDb distributedFiles; + private final ConfigModelRepo configModelRepo; + private final Networking networkingOption; + private final VespaModel modelRoot; + private final Application configuredApplication; + private final Container container; + + @Inject + public StandaloneContainerApplication(Injector injector) { + this.injector = injector; + ConfiguredApplication.ensureVespaLoggingInitialized(); + this.applicationPath = injectedApplicationPath().orElseGet(this::installApplicationPath); + this.distributedFiles = new LocalFileDb(applicationPath); + this.configModelRepo = resolveConfigModelRepo(); + this.networkingOption = resolveNetworkingOption(); + + try { + Pair<VespaModel, Container> tpl = withTempDir(preprocessedApplicationDir -> createContainerModel(applicationPath, + distributedFiles, preprocessedApplicationDir, networkingOption, configModelRepo)); + this.modelRoot = tpl.getFirst(); + this.container = tpl.getSecond(); + } catch (RuntimeException r) { + throw r; + } catch (Exception e) { + throw new RuntimeException("Failed to create ContainerModel", e); + } + this.configuredApplication = createConfiguredApplication(container); + } + + private ConfigModelRepo resolveConfigModelRepo() { + try { + return injector.getInstance(Key.get(ConfigModelRepo.class, CONFIG_MODEL_REPO_NAME)); + } catch (Exception e) { + return new ConfigModelRepo(); + } + } + + private Networking resolveNetworkingOption() { + try { + Boolean networkingDisable = injector.getInstance(Key.get(Boolean.class, Names.named(DISABLE_NETWORKING_ANNOTATION))); + if (networkingDisable != null) { + return networkingDisable ? Networking.disable : Networking.enable; + } + } catch (Exception ignored) { + } + return Networking.enable; + } + + private Application createConfiguredApplication(Container container) { + Injector augmentedInjector = injector.createChildInjector(new AbstractModule() { + @Override + public void configure() { + bind(SubscriberFactory.class).toInstance(new StandaloneSubscriberFactory(modelRoot)); + } + }); + + System.setProperty("config.id", container.getConfigId()); + return augmentedInjector.getInstance(ConfiguredApplication.class); + } + + private Optional<Path> injectedApplicationPath() { + try { + return Optional.ofNullable(injector.getInstance(Key.get(Path.class, APPLICATION_PATH_NAME))); + } catch (ConfigurationException | ProvisionException ignored) { + } + return Optional.empty(); + } + + private Path installApplicationPath() { + Optional<String> variable = optionalInstallVariable(APPLICATION_LOCATION_INSTALL_VARIABLE); + + return variable.map(Paths::get) + .orElseThrow(() -> new IllegalStateException("Environment variable not set: " + APPLICATION_LOCATION_INSTALL_VARIABLE)); + } + + @Override + public void start() { + try { + com.yahoo.container.Container.get().setCustomFileAcquirer(distributedFiles); + configuredApplication.start(); + } catch (Exception e) { + com.yahoo.container.Container.resetInstance(); + throw e; + } + } + + @Override + public void stop() { + configuredApplication.stop(); + } + + @Override + public void destroy() { + com.yahoo.container.Container.resetInstance(); + configuredApplication.destroy(); + } + + public Container container() { + return container; + } + + private interface ThrowingFunction<T, U> { + U apply(T input) throws Exception; + } + + private static <T> T withTempDir(ThrowingFunction<File, T> f) throws Exception { + File tmpDir = createTempDir(); + try { + return f.apply(tmpDir); + } finally { + IOUtils.recursiveDeleteDir(tmpDir); + } + } + + private static File createTempDir() { + Path basePath; + if (new File(DEFAULT_TMP_BASE_DIR).exists()) { + basePath = Paths.get(DEFAULT_TMP_BASE_DIR); + } else { + basePath = Paths.get(System.getProperty("java.io.tmpdir")); + } + + try { + Path tmpDir = Files.createTempDirectory(basePath, TMP_DIR_NAME); + return tmpDir.toFile(); + } catch (IOException e) { + throw new RuntimeException("Cannot create temp directory", e); + } + } + + private static void validateApplication(ApplicationPackage applicationPackage) { + try { + applicationPackage.validateXML(); + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } + + private static ContainerModelBuilder newContainerModelBuilder(Networking networkingOption) { + Optional<String> profile = optionalInstallVariable(DEPLOYMENT_PROFILE_INSTALL_VARIABLE); + if (profile.isPresent()) { + String profileName = profile.get(); + if ("configserver".equals(profileName)) { + return new ConfigServerContainerModelBuilder(new CloudConfigInstallVariables()); + } else { + throw new RuntimeException("Invalid deployment profile '" + profileName + "'"); + } + } else { + return new ContainerModelBuilder(true, networkingOption); + } + } + + static Pair<VespaModel, Container> createContainerModel(Path applicationPath, FileRegistry fileRegistry, + File preprocessedApplicationDir, Networking networkingOption, ConfigModelRepo configModelRepo) throws Exception { + DeployLogger logger = new BaseDeployLogger(); + FilesApplicationPackage rawApplicationPackage = new FilesApplicationPackage.Builder(applicationPath.toFile()) + .includeSourceFiles(true).preprocessedDir(preprocessedApplicationDir).build(); + ApplicationPackage applicationPackage = rawApplicationPackage.preprocess(Zone.defaultZone(), logger); + validateApplication(applicationPackage); + DeployState deployState = new DeployState.Builder().applicationPackage(applicationPackage).fileRegistry(fileRegistry) + .deployLogger(logger).configDefinitionRepo(configDefinitionRepo).build(true); + + VespaModel root = VespaModel.createIncomplete(deployState); + ApplicationConfigProducerRoot vespaRoot = new ApplicationConfigProducerRoot(root, "vespa", deployState.getDocumentModel(), + deployState.getProperties().vespaVersion(), deployState.getProperties().applicationId()); + + Element spec = containerRootElement(applicationPackage); + ContainerModel containerModel = newContainerModelBuilder(networkingOption).build(deployState, configModelRepo, vespaRoot, spec); + containerModel.getCluster().prepare(); + initializeContainerModel(containerModel, configModelRepo); + Container container = first(containerModel.getCluster().getContainers()); + + // TODO: Separate out model finalization from the VespaModel constructor, + // such that the above and below code to finalize the container can be + // replaced by root.finalize(); + + initializeContainer(container, spec); + + root.freezeModelTopology(); + return new Pair<>(root, container); + } + + private static void initializeContainer(Container container, Element spec) { + HostResource host = container.getRoot().getHostSystem().getHost(Container.SINGLENODE_CONTAINER_SERVICESPEC); + + container.setBasePort(VespaDomBuilder.getXmlWantedPort(spec)); + container.setHostResource(host); + container.initService(); + } + + private static Element getJDiscInServices(Element element) { + List<Element> jDiscElements = new ArrayList<>(); + for (ConfigModelId cid : ContainerModelBuilder.configModelIds) { + List<Element> children = XML.getChildren(element, cid.getName()); + jDiscElements.addAll(children); + } + + if (jDiscElements.size() == 1) { + return jDiscElements.get(0); + } else if (jDiscElements.isEmpty()) { + throw new RuntimeException("No jdisc element found under services."); + } else { + List<String> nameAndId = jDiscElements.stream().map(e -> e.getNodeName() + " id='" + e.getAttribute("id") + "'") + .collect(Collectors.toList()); + throw new RuntimeException("Found multiple JDisc elements: " + String.join(", ", nameAndId)); + } + } + + private static Element containerRootElement(ApplicationPackage applicationPackage) { + Element element = XmlHelper.getDocument(applicationPackage.getServices()).getDocumentElement(); + String nodeName = element.getNodeName(); + + if (ContainerModelBuilder.configModelIds.stream().anyMatch(id -> id.getName().equals(nodeName))) { + return element; + } else { + return getJDiscInServices(element); + } + } + + @SuppressWarnings("deprecation") // TODO: what is the not-deprecated way? + private static void initializeContainerModel(ContainerModel containerModel, ConfigModelRepo configModelRepo) { + containerModel.initialize(configModelRepo); + } + + private static Optional<String> optionalInstallVariable(String name) { + Optional<String> fromEnv = Optional.ofNullable(System.getenv((name.replace(".", "__")))); + if (fromEnv.isPresent()) { + return fromEnv; + } + return Optional.ofNullable(System.getProperty(name)); // for unit testing + } +} diff --git a/standalone-container/src/main/java/com/yahoo/container/standalone/StandaloneSubscriberFactory.java b/standalone-container/src/main/java/com/yahoo/container/standalone/StandaloneSubscriberFactory.java new file mode 100644 index 00000000000..6ea2671b05b --- /dev/null +++ b/standalone-container/src/main/java/com/yahoo/container/standalone/StandaloneSubscriberFactory.java @@ -0,0 +1,131 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.standalone; + +import com.yahoo.config.ConfigBuilder; +import com.yahoo.config.ConfigInstance; +import com.yahoo.config.subscription.ConfigInterruptedException; +import com.yahoo.container.di.config.Subscriber; +import com.yahoo.container.di.config.SubscriberFactory; +import com.yahoo.vespa.config.ConfigKey; +import com.yahoo.vespa.model.VespaModel; + +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * @author Tony Vaagenes + * @author gjoranv + * @author ollivir + */ +public class StandaloneSubscriberFactory implements SubscriberFactory { + private final VespaModel root; + + public StandaloneSubscriberFactory(VespaModel root) { + this.root = root; + } + + private class StandaloneSubscriber implements Subscriber { + + private final Set<ConfigKey<ConfigInstance>> configKeys; + private long generation = -1L; + + StandaloneSubscriber(Set<ConfigKey<ConfigInstance>> configKeys) { + this.configKeys = configKeys; + } + + @Override + public boolean internalRedeploy() { return false; } + + @Override + public boolean configChanged() { + return generation == 0; + } + + @Override + public void close() { + } + + @Override + public Map<ConfigKey<ConfigInstance>, ConfigInstance> config() { + Map<ConfigKey<ConfigInstance>, ConfigInstance> ret = new HashMap<>(); + for (ConfigKey<ConfigInstance> key : configKeys) { + ConfigInstance.Builder builder = root.getConfig(newBuilderInstance(key), key.getConfigId()); + if (builder == null) { + throw new RuntimeException("Invalid config id " + key.getConfigId()); + } + ret.put(key, newConfigInstance(builder)); + } + return ret; + } + + @Override + public long waitNextGeneration() { + generation++; + + if (generation != 0) { + try { + while (!Thread.interrupted()) { + Thread.sleep(10000); + } + } catch (InterruptedException e) { + throw new ConfigInterruptedException(e); + } + } + + return generation; + } + + // if waitNextGeneration has not yet been called, -1 should be returned + @Override + public long generation() { + return generation; + } + } + + @Override + @SuppressWarnings("unchecked") + public Subscriber getSubscriber(Set<? extends ConfigKey<?>> configKeys) { + return new StandaloneSubscriber((Set<ConfigKey<ConfigInstance>>) configKeys); + } + + public void reloadActiveSubscribers(long generation) { + throw new RuntimeException("unsupported"); + } + + private static ConfigInstance.Builder newBuilderInstance(ConfigKey<ConfigInstance> key) { + try { + return builderClass(key).getDeclaredConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new RuntimeException("ConfigInstance builder cannot be instantiated", e); + } + } + + @SuppressWarnings("unchecked") + private static Class<ConfigInstance.Builder> builderClass(ConfigKey<ConfigInstance> key) { + Class<?> configClass = key.getConfigClass(); + if (configClass != null) { + Class<?>[] nestedClasses = configClass.getClasses(); + for (Class<?> clazz : nestedClasses) { + if (clazz.getName().equals(key.getConfigClass().getName() + "$Builder")) { + return (Class<ConfigInstance.Builder>) clazz; + } + } + } + throw new RuntimeException("Builder class for " + (configClass == null ? null : configClass.getName()) + " could not be located"); + } + + private static ConfigInstance newConfigInstance(ConfigBuilder builder) { + try { + return configClass(builder).getConstructor(builder.getClass()).newInstance(builder); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new RuntimeException("ConfigInstance cannot be instantiated", e); + } + } + + @SuppressWarnings("unchecked") + private static Class<ConfigInstance> configClass(ConfigBuilder builder) { + return (Class<ConfigInstance>) builder.getClass().getEnclosingClass(); + } +} |