-
Notifications
You must be signed in to change notification settings - Fork 25.6k
Fix handling of mandatory meta plugins #28710
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
30de3f2
54c61e0
3f9215e
bb22f28
8fadee8
7524337
4ba7cd3
c819e2d
368f5f7
ba4d0a5
8545d2a
ed07d55
42008ff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -56,8 +56,8 @@ | |
| import java.util.HashMap; | ||
| import java.util.HashSet; | ||
| import java.util.Iterator; | ||
| import java.util.LinkedHashMap; | ||
| import java.util.LinkedHashSet; | ||
| import java.util.LinkedList; | ||
| import java.util.List; | ||
| import java.util.Locale; | ||
| import java.util.Map; | ||
|
|
@@ -102,6 +102,8 @@ public PluginsService(Settings settings, Path configPath, Path modulesDirectory, | |
|
|
||
| List<Tuple<PluginInfo, Plugin>> pluginsLoaded = new ArrayList<>(); | ||
| List<PluginInfo> pluginsList = new ArrayList<>(); | ||
| // we need to build a List of plugins for checking mandatory plugins | ||
| final List<String> pluginsNames = new ArrayList<>(); | ||
| // first we load plugins that are on the classpath. this is for tests and transport clients | ||
| for (Class<? extends Plugin> pluginClass : classpathPlugins) { | ||
| Plugin plugin = loadPlugin(pluginClass, settings, configPath); | ||
|
|
@@ -112,6 +114,7 @@ public PluginsService(Settings settings, Path configPath, Path modulesDirectory, | |
| } | ||
| pluginsLoaded.add(new Tuple<>(pluginInfo, plugin)); | ||
| pluginsList.add(pluginInfo); | ||
| pluginsNames.add(pluginInfo.getName()); | ||
| } | ||
|
|
||
| Set<Bundle> seenBundles = new LinkedHashSet<>(); | ||
|
|
@@ -135,11 +138,15 @@ public PluginsService(Settings settings, Path configPath, Path modulesDirectory, | |
| // TODO: remove this leniency, but tests bogusly rely on it | ||
| if (isAccessibleDirectory(pluginsDirectory, logger)) { | ||
| checkForFailedPluginRemovals(pluginsDirectory); | ||
| Set<Bundle> plugins = getPluginBundles(pluginsDirectory); | ||
| for (Bundle bundle : plugins) { | ||
| pluginsList.add(bundle.plugin); | ||
| List<BundleCollection> plugins = getPluginBundleCollections(pluginsDirectory); | ||
| for (final BundleCollection plugin : plugins) { | ||
| final Collection<Bundle> bundles = plugin.bundles(); | ||
| for (final Bundle bundle : bundles) { | ||
| pluginsList.add(bundle.plugin); | ||
| } | ||
| seenBundles.addAll(bundles); | ||
| pluginsNames.add(plugin.name()); | ||
| } | ||
| seenBundles.addAll(plugins); | ||
| } | ||
| } catch (IOException ex) { | ||
| throw new IllegalStateException("Unable to initialize plugins", ex); | ||
|
|
@@ -152,12 +159,6 @@ public PluginsService(Settings settings, Path configPath, Path modulesDirectory, | |
| this.info = new PluginsAndModules(pluginsList, modulesList); | ||
| this.plugins = Collections.unmodifiableList(pluginsLoaded); | ||
|
|
||
| // We need to build a List of plugins for checking mandatory plugins | ||
| Set<String> pluginsNames = new HashSet<>(); | ||
| for (Tuple<PluginInfo, Plugin> tuple : this.plugins) { | ||
| pluginsNames.add(tuple.v1().getName()); | ||
| } | ||
|
|
||
| // Checking expected plugins | ||
| List<String> mandatoryPlugins = MANDATORY_SETTING.get(settings); | ||
| if (mandatoryPlugins.isEmpty() == false) { | ||
|
|
@@ -168,7 +169,11 @@ public PluginsService(Settings settings, Path configPath, Path modulesDirectory, | |
| } | ||
| } | ||
| if (!missingPlugins.isEmpty()) { | ||
| throw new ElasticsearchException("Missing mandatory plugins [" + Strings.collectionToDelimitedString(missingPlugins, ", ") + "]"); | ||
| final String message = String.format( | ||
| Locale.ROOT, | ||
| "missing mandatory plugins [%s]", | ||
| Strings.collectionToDelimitedString(missingPlugins, ", ")); | ||
| throw new IllegalStateException(message); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -244,9 +249,17 @@ public PluginsAndModules info() { | |
| return info; | ||
| } | ||
|
|
||
| /** | ||
| * An abstraction over a single plugin and meta-plugins. | ||
| */ | ||
| interface BundleCollection { | ||
| String name(); | ||
| Collection<Bundle> bundles(); | ||
| } | ||
|
|
||
| // a "bundle" is a group of plugins in a single classloader | ||
| // really should be 1-1, but we are not so fortunate | ||
| static class Bundle { | ||
| static class Bundle implements BundleCollection { | ||
| final PluginInfo plugin; | ||
| final Set<URL> urls; | ||
|
|
||
|
|
@@ -266,6 +279,16 @@ static class Bundle { | |
| this.urls = Objects.requireNonNull(urls); | ||
| } | ||
|
|
||
| @Override | ||
| public String name() { | ||
| return plugin.getName(); | ||
| } | ||
|
|
||
| @Override | ||
| public Collection<Bundle> bundles() { | ||
| return Collections.singletonList(this); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean equals(Object o) { | ||
| if (this == o) return true; | ||
|
|
@@ -281,35 +304,75 @@ public int hashCode() { | |
| } | ||
|
|
||
| /** | ||
| * Extracts all installed plugin directories from the provided {@code rootPath} expanding meta plugins if needed. | ||
| * Represents a meta-plugin and the {@link Bundle}s corresponding to its constituents. | ||
| */ | ||
| static class MetaBundle implements BundleCollection{ | ||
|
||
| private final String name; | ||
| private final List<Bundle> bundles; | ||
|
|
||
| MetaBundle(final String name, final List<Bundle> bundles) { | ||
| this.name = name; | ||
| this.bundles = bundles; | ||
| } | ||
|
|
||
| @Override | ||
| public String name() { | ||
| return name; | ||
| } | ||
|
|
||
| @Override | ||
| public Collection<Bundle> bundles() { | ||
| return bundles; | ||
| } | ||
|
|
||
| } | ||
|
|
||
| /** | ||
| * Extracts all installed plugin directories from the provided {@code rootPath} expanding meta-plugins if needed. | ||
| * | ||
| * @param rootPath the path where the plugins are installed | ||
| * @return A list of all plugin paths installed in the {@code rootPath} | ||
| * @return a list of all plugin paths installed in the {@code rootPath} | ||
| * @throws IOException if an I/O exception occurred reading the directories | ||
| */ | ||
| public static List<Path> findPluginDirs(final Path rootPath) throws IOException { | ||
| final Tuple<List<Path>, Map<String, List<Path>>> groupedPluginDirs = findGroupedPluginDirs(rootPath); | ||
| return groupedPluginDirs.v2().values().stream().flatMap(Collection::stream).collect(Collectors.toList()); | ||
| } | ||
|
|
||
| /** | ||
| * Extracts all installed plugin directories from the provided {@code rootPath} expanding meta-plugins if needed. The plugins are | ||
| * grouped into plugins and meta-plugins. The meta-plugins are keyed by the meta-plugin name. | ||
| * | ||
| * @param rootPath the path where the plugins are installed | ||
| * @return a tuple of plugins as the first component and meta-plugins keyed by meta-plugin name as the second component | ||
| * @throws IOException if an I/O exception occurred reading the directories | ||
| */ | ||
| private static Tuple<List<Path>, Map<String, List<Path>>> findGroupedPluginDirs(final Path rootPath) throws IOException { | ||
| final List<Path> plugins = new ArrayList<>(); | ||
| final Map<String, List<Path>> metaPlugins = new LinkedHashMap<>(); | ||
| final Set<String> seen = new HashSet<>(); | ||
| if (Files.exists(rootPath)) { | ||
| try (DirectoryStream<Path> stream = Files.newDirectoryStream(rootPath)) { | ||
| for (Path plugin : stream) { | ||
| if (FileSystemUtils.isDesktopServicesStore(plugin) || | ||
| plugin.getFileName().toString().startsWith(".removing-")) { | ||
| plugin.getFileName().toString().startsWith(".removing-")) { | ||
| continue; | ||
| } | ||
| if (seen.add(plugin.getFileName().toString()) == false) { | ||
| throw new IllegalStateException("duplicate plugin: " + plugin); | ||
| } | ||
| if (MetaPluginInfo.isMetaPlugin(plugin)) { | ||
| final String name = plugin.getFileName().toString(); | ||
| try (DirectoryStream<Path> subStream = Files.newDirectoryStream(plugin)) { | ||
| for (Path subPlugin : subStream) { | ||
| if (MetaPluginInfo.isPropertiesFile(subPlugin) || | ||
| FileSystemUtils.isDesktopServicesStore(subPlugin)) { | ||
| FileSystemUtils.isDesktopServicesStore(subPlugin)) { | ||
| continue; | ||
| } | ||
| if (seen.add(subPlugin.getFileName().toString()) == false) { | ||
| throw new IllegalStateException("duplicate plugin: " + subPlugin); | ||
| } | ||
| plugins.add(subPlugin); | ||
| metaPlugins.computeIfAbsent(name, n -> new ArrayList<>()).add(subPlugin); | ||
| } | ||
| } | ||
| } else { | ||
|
|
@@ -318,7 +381,7 @@ public static List<Path> findPluginDirs(final Path rootPath) throws IOException | |
| } | ||
| } | ||
| } | ||
| return plugins; | ||
| return Tuple.tuple(plugins, metaPlugins); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -380,26 +443,46 @@ static void checkForFailedPluginRemovals(final Path pluginsDirectory) throws IOE | |
| * @throws IOException if an I/O exception occurs reading the plugin bundles | ||
| */ | ||
| static Set<Bundle> getPluginBundles(final Path pluginsDirectory) throws IOException { | ||
| Logger logger = Loggers.getLogger(PluginsService.class); | ||
| Set<Bundle> bundles = new LinkedHashSet<>(); | ||
| return getPluginBundleCollections(pluginsDirectory).stream().flatMap(b -> b.bundles().stream()).collect(Collectors.toSet()); | ||
| } | ||
|
|
||
| List<Path> infos = findPluginDirs(pluginsDirectory); | ||
| for (Path plugin : infos) { | ||
| logger.trace("--- adding plugin [{}]", plugin.toAbsolutePath()); | ||
| final PluginInfo info; | ||
| try { | ||
| info = PluginInfo.readFromProperties(plugin); | ||
| } catch (IOException e) { | ||
| throw new IllegalStateException("Could not load plugin descriptor for existing plugin [" | ||
| + plugin.getFileName() + "]. Was the plugin built before 2.0?", e); | ||
| } | ||
| if (bundles.add(new Bundle(info, plugin)) == false) { | ||
| throw new IllegalStateException("duplicate plugin: " + info); | ||
| private static List<BundleCollection> getPluginBundleCollections(final Path pluginsDirectory) throws IOException { | ||
| final List<BundleCollection> bundles = new ArrayList<>(); | ||
| final Set<Bundle> seenBundles = new HashSet<>(); | ||
| final Tuple<List<Path>, Map<String, List<Path>>> groupedPluginDirs = findGroupedPluginDirs(pluginsDirectory); | ||
| for (final Path plugin : groupedPluginDirs.v1()) { | ||
| final Bundle bundle = bundle(seenBundles, plugin); | ||
| bundles.add(bundle); | ||
| } | ||
| for (final Map.Entry<String, List<Path>> metaPlugin : groupedPluginDirs.v2().entrySet()) { | ||
| final List<Bundle> metaPluginBundles = new ArrayList<>(); | ||
| for (final Path metaPluginPlugin : metaPlugin.getValue()) { | ||
| final Bundle bundle = bundle(seenBundles, metaPluginPlugin); | ||
| metaPluginBundles.add(bundle); | ||
| } | ||
| final MetaBundle metaBundle = new MetaBundle(metaPlugin.getKey(), metaPluginBundles); | ||
| bundles.add(metaBundle); | ||
| } | ||
|
|
||
| return bundles; | ||
| } | ||
|
|
||
| private static Bundle bundle(final Set<Bundle> bundles, final Path plugin) throws IOException { | ||
| Loggers.getLogger(PluginsService.class).trace("--- adding plugin [{}]", plugin.toAbsolutePath()); | ||
| final PluginInfo info; | ||
| try { | ||
| info = PluginInfo.readFromProperties(plugin); | ||
| } catch (final IOException e) { | ||
| throw new IllegalStateException("Could not load plugin descriptor for existing plugin [" | ||
| + plugin.getFileName() + "]. Was the plugin built before 2.0?", e); | ||
| } | ||
| final Bundle bundle = new Bundle(info, plugin); | ||
| if (bundles.add(bundle) == false) { | ||
| throw new IllegalStateException("duplicate plugin: " + info); | ||
| } | ||
| return bundle; | ||
| } | ||
|
|
||
| /** | ||
| * Return the given bundles, sorted in dependency loading order. | ||
| * | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This means that a plugin inside a
metaplugin cannot be set as mandatory ? I don't know if this is a problem or not though.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are correct. I think this is okay though, it’s limited to the user-facing plugins (what they can install and uninstall). Do you agree with that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agreed, thanks for asking.