use of co.cask.cdap.common.InvalidArtifactException in project cdap by caskdata.
the class AppLifecycleHttpHandler method updateApp.
/**
* Updates an existing application.
*/
@POST
@Path("/apps/{app-id}/update")
@AuditPolicy(AuditDetail.REQUEST_BODY)
public void updateApp(HttpRequest request, HttpResponder responder, @PathParam("namespace-id") final String namespaceId, @PathParam("app-id") final String appName) throws NotFoundException, BadRequestException, UnauthorizedException, IOException {
ApplicationId appId = validateApplicationId(namespaceId, appName);
AppRequest appRequest;
try (Reader reader = new InputStreamReader(new ChannelBufferInputStream(request.getContent()), Charsets.UTF_8)) {
appRequest = GSON.fromJson(reader, AppRequest.class);
} catch (IOException e) {
LOG.error("Error reading request to update app {} in namespace {}.", appName, namespaceId, e);
throw new IOException("Error reading request body.");
} catch (JsonSyntaxException e) {
throw new BadRequestException("Request body is invalid json: " + e.getMessage());
}
try {
applicationLifecycleService.updateApp(appId, appRequest, createProgramTerminator());
responder.sendString(HttpResponseStatus.OK, "Update complete.");
} catch (InvalidArtifactException e) {
throw new BadRequestException(e.getMessage());
} catch (ConflictException e) {
responder.sendString(HttpResponseStatus.CONFLICT, e.getMessage());
} catch (NotFoundException | UnauthorizedException e) {
throw e;
} catch (Exception e) {
// this is the same behavior as deploy app pipeline, but this is bad behavior. Error handling needs improvement.
LOG.error("Deploy failure", e);
responder.sendString(HttpResponseStatus.BAD_REQUEST, e.getMessage());
}
}
use of co.cask.cdap.common.InvalidArtifactException in project cdap by caskdata.
the class ArtifactInspector method inspectArtifact.
/**
* Inspect the given artifact to determine the classes contained in the artifact.
*
* @param artifactId the id of the artifact to inspect
* @param artifactFile the artifact file
* @param parentClassLoader the parent classloader to use when inspecting plugins contained in the artifact.
* For example, a ProgramClassLoader created from the artifact the input artifact extends
* @return metadata about the classes contained in the artifact
* @throws IOException if there was an exception opening the jar file
* @throws InvalidArtifactException if the artifact is invalid. For example, if the application main class is not
* actually an Application.
*/
ArtifactClasses inspectArtifact(Id.Artifact artifactId, File artifactFile, @Nullable ClassLoader parentClassLoader) throws IOException, InvalidArtifactException {
Path tmpDir = Paths.get(cConf.get(Constants.CFG_LOCAL_DATA_DIR), cConf.get(Constants.AppFabric.TEMP_DIR)).toAbsolutePath();
Files.createDirectories(tmpDir);
Location artifactLocation = Locations.toLocation(artifactFile);
Path stageDir = Files.createTempDirectory(tmpDir, artifactFile.getName());
try {
File unpackedDir = BundleJarUtil.unJar(artifactLocation, Files.createTempDirectory(stageDir, "unpacked-").toFile());
try (CloseableClassLoader artifactClassLoader = artifactClassLoaderFactory.createClassLoader(unpackedDir)) {
ArtifactClasses.Builder builder = inspectApplications(artifactId, ArtifactClasses.builder(), artifactLocation, artifactClassLoader);
try (PluginInstantiator pluginInstantiator = new PluginInstantiator(cConf, parentClassLoader == null ? artifactClassLoader : parentClassLoader, Files.createTempDirectory(stageDir, "plugins-").toFile())) {
pluginInstantiator.addArtifact(artifactLocation, artifactId.toArtifactId());
inspectPlugins(builder, artifactFile, artifactId.toArtifactId(), pluginInstantiator);
}
return builder.build();
}
} catch (EOFException | ZipException e) {
throw new InvalidArtifactException("Artifact " + artifactId + " is not a valid zip file.", e);
} finally {
try {
DirUtils.deleteDirectoryContents(stageDir.toFile());
} catch (IOException e) {
LOG.warn("Exception raised while deleting directory {}", stageDir, e);
}
}
}
use of co.cask.cdap.common.InvalidArtifactException in project cdap by caskdata.
the class ArtifactRepository method createParentClassLoader.
/**
* Create a parent classloader using an artifact from one of the artifacts in the specified parents.
*
* @param artifactId the id of the artifact to create the parent classloader for
* @param parentArtifacts the ranges of parents to create the classloader from
* @return a classloader based off a parent artifact
* @throws ArtifactRangeNotFoundException if none of the parents could be found
* @throws InvalidArtifactException if one of the parents also has parents
* @throws IOException if there was some error reading from the store
*/
private CloseableClassLoader createParentClassLoader(Id.Artifact artifactId, Set<ArtifactRange> parentArtifacts, EntityImpersonator entityImpersonator) throws ArtifactRangeNotFoundException, IOException, InvalidArtifactException {
List<ArtifactDetail> parents = new ArrayList<>();
for (ArtifactRange parentRange : parentArtifacts) {
parents.addAll(artifactStore.getArtifacts(parentRange, Integer.MAX_VALUE, ArtifactSortOrder.UNORDERED));
}
if (parents.isEmpty()) {
throw new ArtifactRangeNotFoundException(String.format("Artifact %s extends artifacts '%s' that do not exist", artifactId, Joiner.on('/').join(parentArtifacts)));
}
// check if any of the parents also have parents, which is not allowed. This is to simplify things
// so that we don't have to chain a bunch of classloaders, and also to keep it simple for users to avoid
// complicated dependency trees that are hard to manage.
boolean isInvalid = false;
StringBuilder errMsg = new StringBuilder("Invalid artifact '").append(artifactId).append("'.").append(" Artifact parents cannot have parents.");
for (ArtifactDetail parent : parents) {
Set<ArtifactRange> grandparents = parent.getMeta().getUsableBy();
if (!grandparents.isEmpty()) {
isInvalid = true;
errMsg.append(" Parent '").append(parent.getDescriptor().getArtifactId().getName()).append("-").append(parent.getDescriptor().getArtifactId().getVersion().getVersion()).append("' has parents.");
}
}
if (isInvalid) {
throw new InvalidArtifactException(errMsg.toString());
}
// assumes any of the parents will do
Location parentLocation = parents.get(0).getDescriptor().getLocation();
return createArtifactClassLoader(parentLocation, entityImpersonator);
}
use of co.cask.cdap.common.InvalidArtifactException in project cdap by caskdata.
the class InMemoryConfigurator method getSpecJson.
private <T extends Config> String getSpecJson(Application<T> app) throws Exception {
// This Gson cannot be static since it is used to deserialize user class.
// Gson will keep a static map to class, hence will leak the classloader
Gson gson = new GsonBuilder().registerTypeAdapterFactory(new CaseInsensitiveEnumTypeAdapterFactory()).create();
// Now, we call configure, which returns application specification.
DefaultAppConfigurer configurer;
File tempDir = DirUtils.createTempDir(baseUnpackDir);
try (PluginInstantiator pluginInstantiator = new PluginInstantiator(cConf, app.getClass().getClassLoader(), tempDir)) {
configurer = new DefaultAppConfigurer(appNamespace, artifactId, app, configString, artifactRepository, pluginInstantiator);
T appConfig;
Type configType = Artifacts.getConfigType(app.getClass());
if (configString.isEmpty()) {
//noinspection unchecked
appConfig = ((Class<T>) configType).newInstance();
} else {
try {
appConfig = gson.fromJson(configString, configType);
} catch (JsonSyntaxException e) {
throw new IllegalArgumentException("Invalid JSON configuration was provided. Please check the syntax.", e);
}
}
try {
ClassLoader oldClassLoader = ClassLoaders.setContextClassLoader(new CombineClassLoader(null, Arrays.asList(app.getClass().getClassLoader(), getClass().getClassLoader())));
try {
app.configure(configurer, new DefaultApplicationContext<>(appConfig));
} finally {
ClassLoaders.setContextClassLoader(oldClassLoader);
}
} catch (Throwable t) {
Throwable rootCause = Throwables.getRootCause(t);
if (rootCause instanceof ClassNotFoundException) {
// Heuristic to provide better error message
String missingClass = rootCause.getMessage();
// If the missing class has "spark" in the name, try to see if Spark is available
if (missingClass.startsWith("org.apache.spark.") || missingClass.startsWith("co.cask.cdap.api.spark.")) {
// Try to load the SparkContext class, which should be available if Spark is available in the platform
try {
artifactClassLoader.loadClass("org.apache.spark.SparkContext");
} catch (ClassNotFoundException e) {
// Spark is not available, it is most likely caused by missing Spark in the platform
throw new IllegalStateException("Missing Spark related class " + missingClass + ". It may be caused by unavailability of Spark. " + "Please verify environment variable " + Constants.SPARK_HOME + " is set correctly", t);
}
// Spark is available, can be caused by incompatible Spark version
throw new InvalidArtifactException("Missing Spark related class " + missingClass + ". Configured to use Spark located at " + System.getenv(Constants.SPARK_HOME) + ", which may be incompatible with the one required by the application", t);
}
// then the missing class is most likely due to some missing library in the artifact jar
throw new InvalidArtifactException("Missing class " + missingClass + ". It may be caused by missing dependency jar(s) in the artifact jar.", t);
}
throw t;
}
} finally {
try {
DirUtils.deleteDirectoryContents(tempDir);
} catch (IOException e) {
LOG.warn("Exception raised when deleting directory {}", tempDir, e);
}
}
ApplicationSpecification specification = configurer.createSpecification(applicationName, applicationVersion);
// TODO: The SchemaGenerator should be injected
return ApplicationSpecificationAdapter.create(new ReflectionSchemaGenerator()).toJson(specification);
}
use of co.cask.cdap.common.InvalidArtifactException in project cdap by caskdata.
the class ApplicationLifecycleService method deployAppAndArtifact.
/**
* Deploy an application by first adding the application jar to the artifact repository, then creating an application
* using that newly added artifact.
*
* @param namespace the namespace to deploy the application and artifact in
* @param appName the name of the app. If null, the name will be set based on the application spec
* @param artifactId the id of the artifact to add and create the application from
* @param jarFile the application jar to add as an artifact and create the application from
* @param configStr the configuration to send to the application when generating the application specification
* @param programTerminator a program terminator that will stop programs that are removed when updating an app.
* For example, if an update removes a flow, the terminator defines how to stop that flow.
* @return information about the deployed application
* @throws InvalidArtifactException the the artifact is invalid. For example, if it does not contain any app classes
* @throws ArtifactAlreadyExistsException if the specified artifact already exists
* @throws IOException if there was an IO error writing the artifact
*/
public ApplicationWithPrograms deployAppAndArtifact(NamespaceId namespace, @Nullable String appName, Id.Artifact artifactId, File jarFile, @Nullable String configStr, @Nullable KerberosPrincipalId ownerPrincipal, ProgramTerminator programTerminator, boolean updateSchedules) throws Exception {
ArtifactDetail artifactDetail = artifactRepository.addArtifact(artifactId, jarFile);
try {
return deployApp(namespace, appName, null, configStr, programTerminator, artifactDetail, ownerPrincipal, updateSchedules);
} catch (Exception e) {
// to the state we were in before this call.
try {
artifactRepository.deleteArtifact(artifactId);
} catch (IOException e2) {
// if the delete fails, nothing we can do, just log it and continue on
LOG.warn("Failed to delete artifact {} after deployment of artifact and application failed.", artifactId, e2);
e.addSuppressed(e2);
}
throw e;
}
}
Aggregations