SOLR-14604: Add the ability to uninstall a package from with the Package CLI (#1710)

Co-authored-by: Marcus <marcuseagan@gmail.com>
This commit is contained in:
Ishan Chattopadhyaya 2020-08-04 14:53:25 +05:30
parent a6c058a260
commit bd21da6eca
5 changed files with 118 additions and 13 deletions

View File

@ -116,6 +116,8 @@ New Features
* SOLR-14681: Introduce ability to delete .jar stored in the Package Store. (MarcusSorealheis , Mike Drob) * SOLR-14681: Introduce ability to delete .jar stored in the Package Store. (MarcusSorealheis , Mike Drob)
* SOLR-14604: Add the ability to uninstall a package from with the Package CLI. (MarcusSorealheis)
Improvements Improvements
--------------------- ---------------------

View File

@ -28,6 +28,7 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Scanner; import java.util.Scanner;
import java.util.Set; import java.util.Set;
@ -35,8 +36,14 @@ import java.util.stream.Collectors;
import org.apache.commons.collections4.MultiValuedMap; import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.collections4.multimap.HashSetValuedHashMap; import org.apache.commons.collections4.multimap.HashSetValuedHashMap;
import org.apache.http.client.methods.HttpDelete;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrClient; import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.request.V2Request;
import org.apache.solr.client.solrj.request.beans.Package;
import org.apache.solr.client.solrj.request.beans.PluginMeta; import org.apache.solr.client.solrj.request.beans.PluginMeta;
import org.apache.solr.client.solrj.response.V2Response;
import org.apache.solr.common.NavigableObject; import org.apache.solr.common.NavigableObject;
import org.apache.solr.common.SolrException; import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode; import org.apache.solr.common.SolrException.ErrorCode;
@ -44,6 +51,7 @@ import org.apache.solr.common.cloud.SolrZkClient;
import org.apache.solr.common.cloud.ZkStateReader; import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.util.Pair; import org.apache.solr.common.util.Pair;
import org.apache.solr.common.util.Utils; import org.apache.solr.common.util.Utils;
import org.apache.solr.filestore.DistribPackageStore;
import org.apache.solr.packagemanager.SolrPackage.Command; import org.apache.solr.packagemanager.SolrPackage.Command;
import org.apache.solr.packagemanager.SolrPackage.Manifest; import org.apache.solr.packagemanager.SolrPackage.Manifest;
import org.apache.solr.packagemanager.SolrPackage.Plugin; import org.apache.solr.packagemanager.SolrPackage.Plugin;
@ -85,6 +93,67 @@ public class PackageManager implements Closeable {
} }
} }
public void uninstall(String packageName, String version) {
SolrPackageInstance packageInstance = getPackageInstance(packageName, version);
if (packageInstance == null) {
PackageUtils.printRed("Package " + packageName + ":" + version + " doesn't exist. Use the install command to install this package version first.");
System.exit(1);
}
// Make sure that this package instance is not deployed on any collection
Map<String, String> collectionsDeployedOn = getDeployedCollections(packageName);
for (String collection: collectionsDeployedOn.keySet()) {
if (version.equals(collectionsDeployedOn.get(collection))) {
PackageUtils.printRed("Package " + packageName + " is currently deployed on collection: " + collection + ". Undeploy the package with undeploy <package-name> -collections <collection1>[,<collection2>,...] before attempting to uninstall the package.");
System.exit(1);
}
}
// Make sure that no plugin from this package instance has been deployed as cluster level plugins
Map<String, SolrPackageInstance> clusterPackages = getPackagesDeployedAsClusterLevelPlugins();
for (String clusterPackageName: clusterPackages.keySet()) {
SolrPackageInstance clusterPackageInstance = clusterPackages.get(clusterPackageName);
if (packageName.equals(clusterPackageName) && version.equals(clusterPackageInstance.version)) {
PackageUtils.printRed("Package " + packageName + "is currently deployed as a cluster-level plugin (" + clusterPackageInstance.getCustomData() + "). Undeploy the package with undeploy <package-name> -collections <collection1>[,<collection2>,...] before uninstalling the package.");
System.exit(1);
}
}
// Delete the package by calling the Package API and remove the Jar
PackageUtils.printGreen("Executing Package API to remove this package...");
Package.DelVersion del = new Package.DelVersion();
del.version = version;
del.pkg = packageName;
V2Request req = new V2Request.Builder(PackageUtils.PACKAGE_PATH)
.forceV2(true)
.withMethod(SolrRequest.METHOD.POST)
.withPayload(Collections.singletonMap("delete", del))
.build();
try {
V2Response resp = req.process(solrClient);
PackageUtils.printGreen("Response: " + resp.jsonStr());
} catch (SolrServerException | IOException e) {
throw new SolrException(ErrorCode.BAD_REQUEST, e);
}
PackageUtils.printGreen("Executing Package Store API to remove the " + packageName + " package...");
List<String> filesToDelete = new ArrayList<>(packageInstance.files);
filesToDelete.add(String.format(Locale.ROOT, "/package/%s/%s/%s", packageName, version, "manifest.json"));
for (String filePath: filesToDelete) {
DistribPackageStore.deleteZKFileEntry(zkClient, filePath);
String path = solrClient.getBaseURL() + "/api/cluster/files" + filePath;
PackageUtils.printGreen("Deleting " + path);
HttpDelete httpDel = new HttpDelete(path);
Utils.executeHttpMethod(solrClient.getHttpClient(), path, Utils.JSONCONSUMER, httpDel);
}
PackageUtils.printGreen("Package uninstalled: " + packageName + ":" + version + ":-)");
}
@SuppressWarnings({"unchecked", "rawtypes"}) @SuppressWarnings({"unchecked", "rawtypes"})
public List<SolrPackageInstance> fetchInstalledPackageInstances() throws SolrException { public List<SolrPackageInstance> fetchInstalledPackageInstances() throws SolrException {
log.info("Getting packages from packages.json..."); log.info("Getting packages from packages.json...");
@ -100,10 +169,13 @@ public class PackageManager implements Closeable {
List pkg = (List)packagesZnodeMap.get(packageName); List pkg = (List)packagesZnodeMap.get(packageName);
for (Map pkgVersion: (List<Map>)pkg) { for (Map pkgVersion: (List<Map>)pkg) {
Manifest manifest = PackageUtils.fetchManifest(solrClient, solrBaseUrl, pkgVersion.get("manifest").toString(), pkgVersion.get("manifestSHA512").toString()); Manifest manifest = PackageUtils.fetchManifest(solrClient, solrBaseUrl, pkgVersion.get("manifest").toString(), pkgVersion.get("manifestSHA512").toString());
List<Plugin> solrplugins = manifest.plugins; List<Plugin> solrPlugins = manifest.plugins;
SolrPackageInstance pkgInstance = new SolrPackageInstance(packageName.toString(), null, SolrPackageInstance pkgInstance = new SolrPackageInstance(packageName.toString(), null,
pkgVersion.get("version").toString(), manifest, solrplugins, manifest.parameterDefaults); pkgVersion.get("version").toString(), manifest, solrPlugins, manifest.parameterDefaults);
List<SolrPackageInstance> list = packages.containsKey(packageName)? packages.get(packageName): new ArrayList<SolrPackageInstance>(); if (pkgVersion.containsKey("files")) {
pkgInstance.files = (List) pkgVersion.get("files");
}
List<SolrPackageInstance> list = packages.containsKey(packageName) ? packages.get(packageName) : new ArrayList<SolrPackageInstance>();
list.add(pkgInstance); list.add(pkgInstance);
packages.put(packageName.toString(), list); packages.put(packageName.toString(), list);
ret.add(pkgInstance); ret.add(pkgInstance);
@ -139,18 +211,28 @@ public class PackageManager implements Closeable {
} }
/** /**
* Get a list of packages that have their plugins deployed as cluster level plugins. * Get a map of packages (key: package name, value: package instance) that have their plugins deployed as cluster level plugins.
* The returned packages also contain the "pluginMeta" from "clusterprops.json" as custom data. * The returned packages also contain the "pluginMeta" from "clusterprops.json" as custom data.
*/ */
@SuppressWarnings({"unchecked"})
public Map<String, SolrPackageInstance> getPackagesDeployedAsClusterLevelPlugins() { public Map<String, SolrPackageInstance> getPackagesDeployedAsClusterLevelPlugins() {
Map<String, String> packageVersions = new HashMap<>(); Map<String, String> packageVersions = new HashMap<>();
MultiValuedMap<String, PluginMeta> packagePlugins = new HashSetValuedHashMap<>(); // map of package name to multiple values of pluginMeta (Map<String, String>) MultiValuedMap<String, PluginMeta> packagePlugins = new HashSetValuedHashMap<>(); // map of package name to multiple values of pluginMeta (Map<String, String>)
@SuppressWarnings({"unchecked"}) @SuppressWarnings({"unchecked"})
Map<String, Object> result = (Map<String, Object>)Utils.executeGET(solrClient.getHttpClient(), Map<String, Object> result;
solrBaseUrl + PackageUtils.CLUSTERPROPS_PATH, Utils.JSONCONSUMER); try {
result = (Map<String, Object>) Utils.executeGET(solrClient.getHttpClient(),
solrBaseUrl + PackageUtils.CLUSTERPROPS_PATH, Utils.JSONCONSUMER);
} catch (SolrException ex) {
if (ex.code() == ErrorCode.NOT_FOUND.code) {
result = Collections.emptyMap(); // Cluster props doesn't exist, that means there are no cluster level plugins installed.
} else {
throw ex;
}
}
@SuppressWarnings({"unchecked"}) @SuppressWarnings({"unchecked"})
Map<String, Object> clusterPlugins = (Map<String, Object>) result.getOrDefault("plugin", Collections.emptyMap()); Map<String, Object> clusterPlugins = (Map<String, Object>) result.getOrDefault("plugin", Collections.emptyMap());
for (String key: clusterPlugins.keySet()) { for (String key : clusterPlugins.keySet()) {
// Map<String, String> pluginMeta = (Map<String, String>) clusterPlugins.get(key); // Map<String, String> pluginMeta = (Map<String, String>) clusterPlugins.get(key);
PluginMeta pluginMeta; PluginMeta pluginMeta;
try { try {
@ -485,7 +567,7 @@ public class PackageManager implements Closeable {
} }
if (actualValue != null) { if (actualValue != null) {
String expectedValue = PackageUtils.resolve(cmd.expected, pkg.parameterDefaults, overridesMap, systemParams); String expectedValue = PackageUtils.resolve(cmd.expected, pkg.parameterDefaults, overridesMap, systemParams);
PackageUtils.printGreen("Actual: " + actualValue+", expected: " + expectedValue); PackageUtils.printGreen("Actual: " + actualValue + ", expected: " + expectedValue);
if (!expectedValue.equals(actualValue)) { if (!expectedValue.equals(actualValue)) {
PackageUtils.printRed("Failed to deploy plugin: " + plugin.name); PackageUtils.printRed("Failed to deploy plugin: " + plugin.name);
success = false; success = false;
@ -516,7 +598,7 @@ public class PackageManager implements Closeable {
} }
if (actualValue != null) { if (actualValue != null) {
String expectedValue = PackageUtils.resolve(cmd.expected, pkg.parameterDefaults, collectionParameterOverrides, systemParams); String expectedValue = PackageUtils.resolve(cmd.expected, pkg.parameterDefaults, collectionParameterOverrides, systemParams);
PackageUtils.printGreen("Actual: "+actualValue+", expected: "+expectedValue); PackageUtils.printGreen("Actual: " + actualValue + ", expected: "+expectedValue);
if (!expectedValue.equals(actualValue)) { if (!expectedValue.equals(actualValue)) {
PackageUtils.printRed("Failed to deploy plugin: " + plugin.name); PackageUtils.printRed("Failed to deploy plugin: " + plugin.name);
success = false; success = false;
@ -542,7 +624,7 @@ public class PackageManager implements Closeable {
SolrPackageInstance latest = null; SolrPackageInstance latest = null;
if (versions != null && !versions.isEmpty()) { if (versions != null && !versions.isEmpty()) {
latest = versions.get(0); latest = versions.get(0);
for (int i=0; i<versions.size(); i++) { for (int i=0; i < versions.size(); i++) {
SolrPackageInstance pkg = versions.get(i); SolrPackageInstance pkg = versions.get(i);
if (pkg.version.equals(version)) { if (pkg.version.equals(version)) {
return pkg; return pkg;

View File

@ -46,7 +46,9 @@ public class SolrPackageInstance implements ReflectMapWriter {
final public Map<String, String> parameterDefaults; final public Map<String, String> parameterDefaults;
@JsonIgnore public List<String> files;
@JsonIgnore
private Object customData; private Object customData;
@JsonIgnore @JsonIgnore

View File

@ -176,6 +176,16 @@ public class PackageTool extends SolrCLI.ToolBase {
} }
break; break;
} }
case "uninstall": {
Pair<String, String> parsedVersion = parsePackageVersion(cli.getArgList().get(1).toString());
if (parsedVersion.second() == null) {
throw new SolrException(ErrorCode.BAD_REQUEST, "Package name and version are both required. Actual: " + cli.getArgList().get(1));
}
String packageName = parsedVersion.first();
String version = parsedVersion.second();
packageManager.uninstall(packageName, version);
break;
}
case "help": case "help":
case "usage": case "usage":
print("Package Manager\n---------------"); print("Package Manager\n---------------");

View File

@ -176,6 +176,15 @@ If a package supports undeploying the plugins it contains (check package author'
$ bin/solr package undeploy <package-name> -collections <collection1>[,<collection2>,...] $ bin/solr package undeploy <package-name> -collections <collection1>[,<collection2>,...]
---- ----
=== Uninstall a Package
If a package has been undeployed or was never deployed, then it can be uninstalled as follows:
[source,bash]
----
$ bin/solr package uninstall <package-name>:<package-version>
----
or or
[source,bash] [source,bash]