Enable adding interpreter dependency via GUI

This commit is contained in:
Mina Lee 2016-01-23 13:01:17 -08:00
parent d5c931b919
commit fe9cb92f57
21 changed files with 455 additions and 158 deletions

View file

@ -165,7 +165,7 @@ public class SparkDependencyContext {
collectRequest.addRepository(repo);
}
for (Repository repo : repositories) {
RemoteRepository rr = new RemoteRepository(repo.getName(), "default", repo.getUrl());
RemoteRepository rr = new RemoteRepository(repo.getId(), "default", repo.getUrl());
rr.setPolicy(repo.isSnapshot(), null);
Authentication auth = repo.getAuthentication();
if (auth != null) {

View file

@ -24,6 +24,7 @@ import java.util.List;
import org.sonatype.aether.RepositorySystem;
import org.sonatype.aether.RepositorySystemSession;
import org.sonatype.aether.repository.Authentication;
import org.sonatype.aether.repository.RemoteRepository;
import org.sonatype.aether.resolution.ArtifactResult;
@ -51,6 +52,16 @@ public abstract class AbstractDependencyResolver {
}
}
public void addRepo(String id, String url, boolean snapshot, Authentication auth) {
synchronized (repos) {
delRepo(id);
RemoteRepository rr = new RemoteRepository(id, "default", url);
rr.setPolicy(snapshot, null);
rr.setAuthentication(auth);
repos.add(rr);
}
}
public RemoteRepository delRepo(String id) {
synchronized (repos) {
Iterator<RemoteRepository> it = repos.iterator();

View file

@ -127,7 +127,7 @@ public class DependencyContext {
collectRequest.addRepository(mavenCentral);
collectRequest.addRepository(mavenLocal);
for (Repository repo : repositories) {
RemoteRepository rr = new RemoteRepository(repo.getName(), "default", repo.getUrl());
RemoteRepository rr = new RemoteRepository(repo.getId(), "default", repo.getUrl());
rr.setPolicy(repo.isSnapshot(), null);
collectRequest.addRepository(rr);
}

View file

@ -18,6 +18,7 @@
package org.apache.zeppelin.dep;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
@ -28,6 +29,7 @@ import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonatype.aether.RepositoryException;
import org.sonatype.aether.artifact.Artifact;
import org.sonatype.aether.collection.CollectRequest;
import org.sonatype.aether.graph.Dependency;
@ -56,16 +58,18 @@ public class DependencyResolver extends AbstractDependencyResolver {
super(localRepoPath);
}
public List<File> load(String artifact) throws Exception {
public List<File> load(String artifact)
throws RepositoryException, IOException {
return load(artifact, new LinkedList<String>());
}
public List<File> load(String artifact, String destPath) throws Exception {
public List<File> load(String artifact, String destPath)
throws RepositoryException, IOException {
return load(artifact, new LinkedList<String>(), destPath);
}
public synchronized List<File> load(String artifact, Collection<String> excludes)
throws Exception {
throws RepositoryException, IOException {
if (StringUtils.isBlank(artifact)) {
// Should throw here
throw new RuntimeException("Invalid artifact to load");
@ -83,7 +87,7 @@ public class DependencyResolver extends AbstractDependencyResolver {
}
public List<File> load(String artifact, Collection<String> excludes, String destPath)
throws Exception {
throws RepositoryException, IOException {
List<File> libs = load(artifact, excludes);
// find home dir
@ -105,7 +109,8 @@ public class DependencyResolver extends AbstractDependencyResolver {
return libs;
}
private List<File> loadFromMvn(String artifact, Collection<String> excludes) throws Exception {
private List<File> loadFromMvn(String artifact, Collection<String> excludes)
throws RepositoryException {
Collection<String> allExclusions = new LinkedList<String>();
allExclusions.addAll(excludes);
allExclusions.addAll(Arrays.asList(exclusions));
@ -142,7 +147,7 @@ public class DependencyResolver extends AbstractDependencyResolver {
*/
@Override
public List<ArtifactResult> getArtifactsWithDep(String dependency,
Collection<String> excludes) throws Exception {
Collection<String> excludes) throws RepositoryException {
Artifact artifact = new DefaultArtifact(dependency);
DependencyFilter classpathFilter = DependencyFilterUtils.classpathFilter(JavaScopes.COMPILE);
PatternExclusionsDependencyFilter exclusionFilter =

View file

@ -23,13 +23,13 @@ import org.sonatype.aether.repository.Authentication;
*/
public class Repository {
private boolean snapshot = false;
private String name;
private String id;
private String url;
private String username = null;
private String password = null;
public Repository(String name){
this.name = name;
public Repository(String id){
this.id = id;
}
public Repository url(String url) {
@ -46,8 +46,8 @@ public class Repository {
return snapshot;
}
public String getName() {
return name;
public String getId() {
return id;
}
public String getUrl() {

View file

@ -127,10 +127,6 @@ public abstract class Interpreter {
}
}
public static Logger logger = LoggerFactory.getLogger(Interpreter.class);
private InterpreterGroup interpreterGroup;
private URL [] classloaderUrls;

View file

@ -30,9 +30,7 @@ import org.apache.zeppelin.interpreter.InterpreterContextRunner;
import org.apache.zeppelin.interpreter.InterpreterException;
import org.apache.zeppelin.interpreter.InterpreterGroup;
import org.apache.zeppelin.interpreter.InterpreterResult;
import org.apache.zeppelin.interpreter.InterpreterResult.Code;
import org.apache.zeppelin.interpreter.InterpreterResult.Type;
import org.apache.zeppelin.interpreter.WrappedInterpreter;
import org.apache.zeppelin.interpreter.thrift.RemoteInterpreterContext;
import org.apache.zeppelin.interpreter.thrift.RemoteInterpreterResult;
import org.apache.zeppelin.interpreter.thrift.RemoteInterpreterService.Client;

View file

@ -42,6 +42,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import org.sonatype.aether.RepositoryException;
/**
* Interpreter Rest API
@ -85,17 +86,40 @@ public class InterpreterRestApi {
*/
@POST
@Path("setting")
public Response newSettings(String message) throws InterpreterException, IOException {
NewInterpreterSettingRequest request = gson.fromJson(message,
NewInterpreterSettingRequest.class);
Properties p = new Properties();
p.putAll(request.getProperties());
// Option is deprecated from API, always use remote = true
InterpreterGroup interpreterGroup = interpreterFactory.add(request.getName(),
request.getGroup(), new InterpreterOption(true), p);
InterpreterSetting setting = interpreterFactory.get(interpreterGroup.getId());
logger.info("new setting created with " + setting.id());
return new JsonResponse(Status.CREATED, "", setting ).build();
public Response newSettings(String message) {
try {
NewInterpreterSettingRequest request = gson.fromJson(message,
NewInterpreterSettingRequest.class);
Properties p = new Properties();
p.putAll(request.getProperties());
// Option is deprecated from API, always use remote = true
InterpreterGroup interpreterGroup = interpreterFactory.add(request.getName(),
request.getGroup(),
request.getDependencies(),
new InterpreterOption(true),
p);
InterpreterSetting setting = interpreterFactory.get(interpreterGroup.getId());
logger.info("new setting created with " + setting.id());
return new JsonResponse(Status.CREATED, "", setting).build();
} catch (InterpreterException e) {
logger.error("Exception in InterpreterRestApi while creating ", e);
return new JsonResponse(
Status.NOT_FOUND,
e.getMessage(),
ExceptionUtils.getStackTrace(e)).build();
} catch (IOException e) {
logger.error("Exception in InterpreterRestApi while creating ", e);
return new JsonResponse(
Status.INTERNAL_SERVER_ERROR,
e.getMessage(),
ExceptionUtils.getStackTrace(e)).build();
} catch (RepositoryException e) {
logger.error("Exception in InterpreterRestApi while creating ", e);
return new JsonResponse(
Status.INTERNAL_SERVER_ERROR,
e.getMessage(),
ExceptionUtils.getStackTrace(e)).build();
}
}
@PUT
@ -104,11 +128,13 @@ public class InterpreterRestApi {
logger.info("Update interpreterSetting {}", settingId);
try {
UpdateInterpreterSettingRequest p = gson.fromJson(message,
UpdateInterpreterSettingRequest request = gson.fromJson(message,
UpdateInterpreterSettingRequest.class);
// Option is deprecated from API, always use remote = true
interpreterFactory.setPropertyAndRestart(settingId,
new InterpreterOption(true), p.getProperties());
new InterpreterOption(true),
request.getProperties(),
request.getDependencies());
} catch (InterpreterException e) {
logger.error("Exception in InterpreterRestApi while updateSetting ", e);
return new JsonResponse(
@ -117,6 +143,10 @@ public class InterpreterRestApi {
logger.error("Exception in InterpreterRestApi while updateSetting ", e);
return new JsonResponse(
Status.INTERNAL_SERVER_ERROR, e.getMessage(), ExceptionUtils.getStackTrace(e)).build();
} catch (RepositoryException e) {
logger.error("Exception in InterpreterRestApi while updateSetting ", e);
return new JsonResponse(
Status.INTERNAL_SERVER_ERROR, e.getMessage(), ExceptionUtils.getStackTrace(e)).build();
}
InterpreterSetting setting = interpreterFactory.get(settingId);
if (setting == null) {

View file

@ -17,9 +17,10 @@
package org.apache.zeppelin.rest.message;
import java.util.List;
import java.util.Map;
import org.apache.zeppelin.interpreter.InterpreterOption;
import org.apache.zeppelin.dep.Dependency;
/**
* NewInterpreterSetting rest api request message
@ -30,6 +31,7 @@ public class NewInterpreterSettingRequest {
String group;
// option was deprecated
Map<String, String> properties;
List<Dependency> dependencies;
public NewInterpreterSettingRequest() {
@ -47,4 +49,7 @@ public class NewInterpreterSettingRequest {
return properties;
}
public List<Dependency> getDependencies() {
return dependencies;
}
}

View file

@ -17,27 +17,30 @@
package org.apache.zeppelin.rest.message;
import java.util.List;
import java.util.Properties;
import org.apache.zeppelin.interpreter.InterpreterOption;
import org.apache.zeppelin.dep.Dependency;
/**
*
* UpdateInterpreterSetting rest api request message
*/
public class UpdateInterpreterSettingRequest {
// option was deprecated
Properties properties;
List<Dependency> dependencies;
public UpdateInterpreterSettingRequest(InterpreterOption option,
Properties properties) {
super();
public UpdateInterpreterSettingRequest(Properties properties,
List<Dependency> dependencies) {
this.properties = properties;
this.dependencies = dependencies;
}
public Properties getProperties() {
return properties;
}
public List<Dependency> getDependencies() {
return dependencies;
}
}

View file

@ -22,6 +22,7 @@ import java.io.IOException;
import java.lang.ref.WeakReference;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
@ -35,6 +36,7 @@ import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.zeppelin.dep.Dependency;
import org.apache.zeppelin.interpreter.InterpreterGroup;
import org.apache.zeppelin.interpreter.InterpreterOption;
import org.apache.zeppelin.interpreter.InterpreterSetting;
@ -48,6 +50,7 @@ import org.slf4j.LoggerFactory;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import org.sonatype.aether.RepositoryException;
public abstract class AbstractTestRestApi {
@ -363,10 +366,14 @@ public abstract class AbstractTestRestApi {
}
//Create new Setting and return Setting ID
protected String createTempSetting(String tempName) throws IOException {
InterpreterGroup interpreterGroup = ZeppelinServer.notebook.getInterpreterFactory().add(tempName,"newGroup",
new InterpreterOption(false),new Properties());
protected String createTempSetting(String tempName)
throws IOException, RepositoryException {
InterpreterGroup interpreterGroup = ZeppelinServer.notebook.getInterpreterFactory()
.add(tempName,
"newGroup",
new LinkedList<Dependency>(),
new InterpreterOption(false),
new Properties());
return interpreterGroup.getId();
}

View file

@ -81,7 +81,8 @@
ngToastProvider.configure({
dismissButton: true,
dismissOnClick: false,
timeout: 6000
timeout: 6000,
verticalPosition: 'bottom'
});
});

View file

@ -275,31 +275,6 @@ kbd {
border-radius: 2px;
}
/*
ngToast Style
*/
.ng-toast .alert {
color: white !important;
border: none !important;
}
.ng-toast .alert-danger {
background: #A94442 !important;
}
.ng-toast .alert-warning {
background: #CE9532 !important;
}
.ng-toast .alert-info {
background: #589EC1 !important;
}
.ng-toast .alert-success {
background: #428443 !important;
}
/*
temporary fix for bootstrap issue (https://github.com/twbs/bootstrap/issues/5865)
This part should be removed when new version of bootstrap handles this issue.

View file

@ -12,7 +12,7 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<div>
<div class="row">
<div class="row interpreter">
<div class="col-md-12">
<div class="interpreterSettingAdd" ng-show="showAddNewSetting">
<hr />
@ -67,6 +67,50 @@ limitations under the License.
</tr>
</table>
<b>Dependencies</b>
<table class="table table-striped properties">
<tr>
<th>artifact</th>
<th>exclude</th>
<th>action</th>
</tr>
<tr ng-repeat="dep in newInterpreterSetting.dependencies">
<td>
<input ng-model="dep.groupArtifactVersion" style="width:100%"/>
</td>
<td>
<textarea msd-elastic ng-model="dep.exclusions"
ng-list
placeholder="(Optional) comma separated groupId:artifactId list">
</textarea>
</td>
<td>
<div class="btn btn-default btn-sm fa fa-remove"
ng-click="removeInterpreterDependency(dep.groupArtifactVersion)">
</div>
</td>
</tr>
<tr>
<td>
<input ng-model="newInterpreterSetting.depArtifact"
placeholder="groupId:artifactId:version or local file path"
style="width: 100%"/>
</td>
<td>
<textarea msd-elastic ng-model="newInterpreterSetting.depExclude"
ng-list
placeholder="(Optional) comma separated groupId:artifactId list">
</textarea>
</td>
<td>
<div class="btn btn-default btn-sm fa fa-plus" ng-click="addNewInterpreterDependency()">
</div>
</td>
</tr>
</table>
<span class="btn btn-primary" ng-click="addNewInterpreterSetting()">
Save
</span>

View file

@ -15,7 +15,7 @@
'use strict';
angular.module('zeppelinWebApp').controller('InterpreterCtrl', function($scope, $route, $routeParams, $location, $rootScope,
$http, baseUrlSrv) {
$http, baseUrlSrv, ngToast) {
var interpreterSettingsTmp = [];
$scope.interpreterSettings = [];
$scope.availableInterpreters = {};
@ -24,28 +24,32 @@ angular.module('zeppelinWebApp').controller('InterpreterCtrl', function($scope,
var getInterpreterSettings = function() {
$http.get(baseUrlSrv.getRestApiBase()+'/interpreter/setting').
success(function(data, status, headers, config) {
$scope.interpreterSettings = data.body;
}).
error(function(data, status, headers, config) {
console.log('Error %o %o', status, data.message);
});
success(function(data, status, headers, config) {
$scope.interpreterSettings = data.body;
}).
error(function(data, status, headers, config) {
console.log('Error %o %o', status, data.message);
});
};
var getAvailableInterpreters = function() {
$http.get(baseUrlSrv.getRestApiBase()+'/interpreter').
success(function(data, status, headers, config) {
$scope.availableInterpreters = data.body;
}).
error(function(data, status, headers, config) {
console.log('Error %o %o', status, data.message);
});
success(function(data, status, headers, config) {
$scope.availableInterpreters = data.body;
}).
error(function(data, status, headers, config) {
console.log('Error %o %o', status, data.message);
});
};
var emptyNewProperty = function(object) {
angular.extend(object, {propertyValue: '', propertyKey: ''});
};
var emptyNewDependency = function(object) {
angular.extend(object, {depArtifact: '', depExclude: ''});
};
var removeTMPSettings = function(index) {
interpreterSettingsTmp.splice(index, 1);
};
@ -55,29 +59,34 @@ angular.module('zeppelinWebApp').controller('InterpreterCtrl', function($scope,
interpreterSettingsTmp[index] = angular.copy($scope.interpreterSettings[index]);
};
$scope.updateInterpreterSetting = function(settingId) {
BootstrapDialog.confirm({
closable: true,
title: '',
message: 'Do you want to update this interpreter and restart with new settings?',
callback: function(result) {
if (result) {
var index = _.findIndex($scope.interpreterSettings, {'id': settingId});
var request = {
properties: angular.copy($scope.interpreterSettings[index].properties),
};
$http.put(baseUrlSrv.getRestApiBase() + '/interpreter/setting/' + settingId, request).
success(function (data, status, headers, config) {
$scope.interpreterSettings[index] = data.body;
removeTMPSettings(index);
}).
error(function (data, status, headers, config) {
console.log('Error %o %o', status, data.message);
});
}
$scope.updateInterpreterSetting = function(form, settingId) {
var result = confirm('Do you want to update this interpreter and restart with new settings?');
if (result) {
var index = _.findIndex($scope.interpreterSettings, {'id': settingId});
var setting = $scope.interpreterSettings[index];
if (setting.propertyKey !== '' || setting.propertyKey) {
$scope.addNewInterpreterProperty(settingId);
}
});
if (setting.depArtifact !== '' || setting.depArtifact) {
$scope.addNewInterpreterDependency(settingId);
}
var request = {
properties: angular.copy(setting.properties),
dependencies: angular.copy(setting.dependencies)
};
$http.put(baseUrlSrv.getRestApiBase() + '/interpreter/setting/' + settingId, request).
success(function (data, status, headers, config) {
$scope.interpreterSettings[index] = data.body;
removeTMPSettings(index);
}).
error(function (data, status, headers, config) {
console.log('Error %o %o', status, data.message);
ngToast.danger(data.message);
form.$show();
});
}
};
$scope.resetInterpreterSetting = function(settingId){
@ -117,8 +126,8 @@ angular.module('zeppelinWebApp').controller('InterpreterCtrl', function($scope,
var intpInfo = el[i];
for (var key in intpInfo) {
properties[key] = {
value : intpInfo[key].defaultValue,
description : intpInfo[key].description
value: intpInfo[key].defaultValue,
description: intpInfo[key].description
};
}
}
@ -165,32 +174,46 @@ angular.module('zeppelinWebApp').controller('InterpreterCtrl', function($scope,
return;
}
var newSetting = angular.copy($scope.newInterpreterSetting);
for (var p in $scope.newInterpreterSetting.properties) {
newSetting.properties[p] = $scope.newInterpreterSetting.properties[p].value;
var newSetting = $scope.newInterpreterSetting;
if (newSetting.propertyKey !== '' || newSetting.propertyKey) {
$scope.addNewInterpreterProperty();
}
if (newSetting.depArtifact !== '' || newSetting.depArtifact) {
$scope.addNewInterpreterDependency();
}
$http.post(baseUrlSrv.getRestApiBase()+'/interpreter/setting', newSetting).
success(function(data, status, headers, config) {
$scope.resetNewInterpreterSetting();
getInterpreterSettings();
$scope.showAddNewSetting = false;
}).
error(function(data, status, headers, config) {
console.log('Error %o %o', status, data.message);
});
var request = angular.copy($scope.newInterpreterSetting);
// Change properties to proper request format
var newProperties = {};
for (var p in newSetting.properties) {
newProperties[p] = newSetting.properties[p].value;
}
request.properties = newProperties;
$http.post(baseUrlSrv.getRestApiBase() + '/interpreter/setting', request).
success(function(data, status, headers, config) {
$scope.resetNewInterpreterSetting();
getInterpreterSettings();
$scope.showAddNewSetting = false;
}).
error(function(data, status, headers, config) {
console.log('Error %o %o', status, data.message);
ngToast.danger(data.message);
});
};
$scope.cancelInterpreterSetting = function() {
$scope.showAddNewSetting = false;
$scope.resetNewInterpreterSetting();
};
$scope.resetNewInterpreterSetting = function() {
$scope.newInterpreterSetting = {
name : undefined,
group : undefined,
properties : {}
name: undefined,
group: undefined,
properties: {},
dependencies: []
};
emptyNewProperty($scope.newInterpreterSetting);
};
@ -205,6 +228,21 @@ angular.module('zeppelinWebApp').controller('InterpreterCtrl', function($scope,
}
};
$scope.removeInterpreterDependency = function(artifact, settingId) {
if (settingId === undefined) {
$scope.newInterpreterSetting.dependencies = _.reject($scope.newInterpreterSetting.dependencies,
function(el) {
return el.groupArtifactVersion === artifact;
});
} else {
var index = _.findIndex($scope.interpreterSettings, {'id': settingId});
$scope.interpreterSettings[index].dependencies = _.reject($scope.interpreterSettings[index].dependencies,
function(el) {
return el.groupArtifactVersion === artifact;
});
}
};
$scope.addNewInterpreterProperty = function(settingId) {
if(settingId === undefined) {
// Add new property from create form
@ -230,6 +268,58 @@ angular.module('zeppelinWebApp').controller('InterpreterCtrl', function($scope,
}
};
$scope.addNewInterpreterDependency = function(settingId) {
if(settingId === undefined) {
// Add new dependency from create form
if (!$scope.newInterpreterSetting.depArtifact || $scope.newInterpreterSetting.depArtifact === '') {
return;
}
// overwrite if artifact already exists
var newSetting = $scope.newInterpreterSetting;
for(var d in newSetting.dependencies) {
if (newSetting.dependencies[d].groupArtifactVersion === newSetting.depArtifact) {
newSetting.dependencies[d] = {
'groupArtifactVersion': newSetting.depArtifact,
'exclusions': newSetting.depExclude
};
newSetting.dependencies.splice(d, 1);
}
}
newSetting.dependencies.push({
'groupArtifactVersion': newSetting.depArtifact,
'exclusions': (newSetting.depExclude === '')? []: newSetting.depExclude
});
emptyNewDependency(newSetting);
}
else {
// Add new dependency from edit form
var index = _.findIndex($scope.interpreterSettings, { 'id': settingId });
var setting = $scope.interpreterSettings[index];
if (!setting.depArtifact || setting.depArtifact === '') {
return;
}
// overwrite if artifact already exists
for(var dep in setting.dependencies) {
if (setting.dependencies[dep].groupArtifactVersion === setting.depArtifact) {
setting.dependencies[dep] = {
'groupArtifactVersion': setting.depArtifact,
'exclusions': setting.depExclude
};
setting.dependencies.splice(dep, 1);
}
}
setting.dependencies.push({
'groupArtifactVersion': setting.depArtifact,
'exclusions': (setting.depExclude === '')? []: setting.depExclude
});
emptyNewDependency(setting);
}
};
var init = function() {
$scope.resetNewInterpreterSetting();
getInterpreterSettings();

View file

@ -34,6 +34,13 @@
font-size: 12px;
}
.interpreter input {
width: 100%;
display: block;
height: 23px;
border: 1px solid #CCCCCC;
}
.interpreter .interpreter-title {
font-size: 20px;
font-weight: bold;

View file

@ -109,18 +109,74 @@ limitations under the License.
</td>
</tr>
</table>
<form editable-form name="valueform" onaftersave="updateInterpreterSetting(setting.id)" ng-show="valueform.$visible">
<button type="submit" class="btn btn-primary"
ng-disabled="valueform.$waiting">
Save
</button>
<button type="button" class="btn btn-default"
ng-disabled="valueform.$waiting"
ng-click="valueform.$cancel(); resetInterpreterSetting(setting.id)">
Cancel
</button>
</form>
</div>
<div class="col-md-12" ng-show="!_.isEmpty(setting.dependencies) || valueform.$visible">
<h5>Dependencies</h5>
<table class="table table-striped">
<thead>
<tr>
<th style="width:40%">artifact</th>
<th>exclude</th>
<th ng-if="valueform.$visible">action</th>
</tr>
</thead>
<tr ng-repeat="dep in setting.dependencies">
<td>
<span editable-text="dep.groupArtifactVersion" e-placeholder="groupId:artifactId:version or local file path"
e-form="valueform" e-msd-elastic e-style="width:100%">
{{dep.groupArtifactVersion | breakFilter}}
</span>
</td>
<td>
<textarea ng-if="valueform.$visible" ng-model="dep.exclusions"
placeholder="(Optional) comma separated groupId:artifactId list"
form="valueform"
e-msd-elastic
ng-list="">
</textarea>
<div ng-if="!valueform.$visible">{{dep.exclusions.join()}}</div>
</td>
<td ng-if="valueform.$visible">
<div class="btn btn-default btn-sm fa fa-remove"
ng-click="removeInterpreterDependency(dep.groupArtifactVersion, setting.id)">
</div>
</td>
</tr>
<tr ng-if="valueform.$visible">
<td>
<input ng-model="setting.depArtifact"
placeholder="groupId:artifactId:version or local file path"
style="width: 100%">
</input>
</td>
<td>
<textarea ng-model="setting.depExclude"
placeholder="(Optional) comma separated groupId:artifactId list"
msd-elastic
ng-list="">
</textarea>
</td>
<td>
<div class="btn btn-default btn-sm fa fa-plus"
ng-click="addNewInterpreterDependency(setting.id)">
</div>
</td>
</tr>
</table>
</div>
<form editable-form name="valueform"
onaftersave="updateInterpreterSetting(valueform, setting.id)"
ng-show="valueform.$visible">
<button type="submit" class="btn btn-primary">
Save
</button>
<button type="button" class="btn btn-default"
ng-disabled="valueform.$waiting"
ng-click="valueform.$cancel(); resetInterpreterSetting(setting.id)">
Cancel
</button>
</form>
</div>
</div>
</div>

View file

@ -19,10 +19,13 @@ package org.apache.zeppelin.interpreter;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.NullArgumentException;
import org.apache.zeppelin.conf.ZeppelinConfiguration;
import org.apache.zeppelin.conf.ZeppelinConfiguration.ConfVars;
import org.apache.zeppelin.dep.Dependency;
import org.apache.zeppelin.dep.DependencyResolver;
import org.apache.zeppelin.display.AngularObjectRegistry;
import org.apache.zeppelin.display.AngularObjectRegistryListener;
@ -34,6 +37,7 @@ import org.apache.zeppelin.scheduler.Job;
import org.apache.zeppelin.scheduler.Job.Status;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonatype.aether.RepositoryException;
import java.io.*;
import java.lang.reflect.Constructor;
@ -68,13 +72,13 @@ public class InterpreterFactory {
AngularObjectRegistryListener angularObjectRegistryListener;
private final RemoteInterpreterProcessListener remoteInterpreterProcessListener;
DependencyResolver depResolver;
private DependencyResolver depResolver;
public InterpreterFactory(ZeppelinConfiguration conf,
AngularObjectRegistryListener angularObjectRegistryListener,
RemoteInterpreterProcessListener remoteInterpreterProcessListener,
DependencyResolver depResolver)
throws InterpreterException, IOException {
throws InterpreterException, IOException, RepositoryException {
this(conf, new InterpreterOption(true), angularObjectRegistryListener,
remoteInterpreterProcessListener, depResolver);
}
@ -84,7 +88,7 @@ public class InterpreterFactory {
AngularObjectRegistryListener angularObjectRegistryListener,
RemoteInterpreterProcessListener remoteInterpreterProcessListener,
DependencyResolver depResolver)
throws InterpreterException, IOException {
throws InterpreterException, IOException, RepositoryException {
this.conf = conf;
this.defaultOption = defaultOption;
this.angularObjectRegistryListener = angularObjectRegistryListener;
@ -101,7 +105,7 @@ public class InterpreterFactory {
init();
}
private void init() throws InterpreterException, IOException {
private void init() throws InterpreterException, IOException, RepositoryException {
ClassLoader oldcl = Thread.currentThread().getContextClassLoader();
// Load classes
@ -172,7 +176,11 @@ public class InterpreterFactory {
if (found) {
// add all interpreters in group
add(groupName, groupName, defaultOption, p);
add(groupName,
groupName,
new LinkedList<Dependency>(),
defaultOption,
p);
groupClassNameMap.remove(groupName);
break;
}
@ -225,12 +233,11 @@ public class InterpreterFactory {
// previously created setting should turn this feature on here.
setting.getOption().setRemote(true);
InterpreterSetting intpSetting = new InterpreterSetting(
setting.id(),
setting.getName(),
setting.getGroup(),
setting.getDependencies(),
setting.getOption());
InterpreterGroup interpreterGroup = createInterpreterGroup(
@ -246,6 +253,31 @@ public class InterpreterFactory {
this.interpreterBindings = info.interpreterBindings;
}
private void loadInterpreterDependencies(InterpreterSetting intSetting)
throws IOException, RepositoryException {
// dependencies to prevent library conflict
File localRepoDir = new File(conf.getInterpreterLocalRepoPath() + "/" + intSetting.id());
if (localRepoDir.exists()) {
FileUtils.cleanDirectory(localRepoDir);
}
// load dependencies
List<Dependency> deps = intSetting.getDependencies();
if (deps != null) {
for (Dependency d: deps) {
if (d.getExclusions() != null) {
depResolver.load(
d.getGroupArtifactVersion(),
d.getExclusions(),
conf.getString(ConfVars.ZEPPELIN_DEP_LOCALREPO) + "/" + intSetting.id());
} else {
depResolver.load(
d.getGroupArtifactVersion(),
conf.getString(ConfVars.ZEPPELIN_DEP_LOCALREPO) + "/" + intSetting.id());
}
}
}
}
private void saveToFile() throws IOException {
String jsonString;
@ -330,15 +362,21 @@ public class InterpreterFactory {
* @throws IOException
*/
public InterpreterGroup add(String name, String groupName,
List<Dependency> dependencies,
InterpreterOption option, Properties properties)
throws InterpreterException, IOException {
throws InterpreterException, IOException, RepositoryException {
synchronized (interpreterSettings) {
InterpreterSetting intpSetting = new InterpreterSetting(
name,
groupName,
dependencies,
option);
if (dependencies.size() > 0) {
loadInterpreterDependencies(intpSetting);
}
InterpreterGroup interpreterGroup = createInterpreterGroup(
intpSetting.id(), groupName, option, properties);
@ -354,13 +392,13 @@ public class InterpreterFactory {
String groupName,
InterpreterOption option,
Properties properties)
throws InterpreterException , NullArgumentException {
throws InterpreterException, NullArgumentException {
//When called from REST API without option we receive NPE
if (option == null )
if (option == null)
throw new NullArgumentException("option");
//When called from REST API without option we receive NPE
if (properties == null )
if (properties == null)
throw new NullArgumentException("properties");
AngularObjectRegistry angularObjectRegistry;
@ -429,6 +467,9 @@ public class InterpreterFactory {
saveToFile();
}
}
File localRepoDir = new File(conf.getInterpreterLocalRepoPath() + "/" + id);
FileUtils.deleteDirectory(localRepoDir);
}
/**
@ -512,8 +553,10 @@ public class InterpreterFactory {
* @param properties
* @throws IOException
*/
public void setPropertyAndRestart(String id, InterpreterOption option,
Properties properties) throws IOException {
public void setPropertyAndRestart(String id,
InterpreterOption option,
Properties properties,
List<Dependency> dependencies) throws IOException, RepositoryException {
synchronized (interpreterSettings) {
InterpreterSetting intpsetting = interpreterSettings.get(id);
if (intpsetting != null) {
@ -524,11 +567,14 @@ public class InterpreterFactory {
intpsetting.getInterpreterGroup().destroy();
intpsetting.setOption(option);
intpsetting.setDependencies(dependencies);
InterpreterGroup interpreterGroup = createInterpreterGroup(
intpsetting.id(),
intpsetting.getGroup(), option, properties);
intpsetting.setInterpreterGroup(interpreterGroup);
loadInterpreterDependencies(intpsetting);
saveToFile();
} else {
throw new InterpreterException("Interpreter setting id " + id

View file

@ -17,9 +17,12 @@
package org.apache.zeppelin.interpreter;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.Random;
import org.apache.zeppelin.dep.Dependency;
import org.apache.zeppelin.notebook.utility.IdHashes;
/**
@ -32,21 +35,26 @@ public class InterpreterSetting {
private String description;
private Properties properties;
private InterpreterGroup interpreterGroup;
private List<Dependency> dependencies;
private InterpreterOption option;
public InterpreterSetting(String id, String name,
public InterpreterSetting(String id,
String name,
String group,
List<Dependency> dependencies,
InterpreterOption option) {
this.id = id;
this.name = name;
this.group = group;
this.dependencies = dependencies;
this.option = option;
}
public InterpreterSetting(String name,
String group,
List<Dependency> dependencies,
InterpreterOption option) {
this(generateId(), name, group, option);
this(generateId(), name, group, dependencies, option);
}
public String id() {
@ -90,6 +98,17 @@ public class InterpreterSetting {
return properties;
}
public List<Dependency> getDependencies() {
if (dependencies == null) {
return new LinkedList<Dependency>();
}
return dependencies;
}
public void setDependencies(List<Dependency> dependencies) {
this.dependencies = dependencies;
}
public InterpreterOption getOption() {
if (option == null) {
option = new InterpreterOption();

View file

@ -24,17 +24,20 @@ import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import org.apache.commons.lang.NullArgumentException;
import org.apache.zeppelin.conf.ZeppelinConfiguration;
import org.apache.zeppelin.conf.ZeppelinConfiguration.ConfVars;
import org.apache.zeppelin.dep.Dependency;
import org.apache.zeppelin.interpreter.mock.MockInterpreter1;
import org.apache.zeppelin.interpreter.mock.MockInterpreter2;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.sonatype.aether.RepositoryException;
public class InterpreterFactoryTest {
@ -98,14 +101,14 @@ public class InterpreterFactoryTest {
}
@Test
public void testFactoryDefaultList() throws IOException {
public void testFactoryDefaultList() throws IOException, RepositoryException {
// get default list from default setting
List<String> all = factory.getDefaultInterpreterSettingList();
assertEquals(2, all.size());
assertEquals(factory.get(all.get(0)).getInterpreterGroup().getFirst().getClassName(), "org.apache.zeppelin.interpreter.mock.MockInterpreter1");
// add setting
factory.add("a mock", "mock2", new InterpreterOption(false), new Properties());
factory.add("a mock", "mock2", new LinkedList<Dependency>(), new InterpreterOption(false), new Properties());
all = factory.getDefaultInterpreterSettingList();
assertEquals(2, all.size());
assertEquals("mock1", factory.get(all.get(0)).getName());
@ -113,16 +116,16 @@ public class InterpreterFactoryTest {
}
@Test
public void testExceptions() throws IOException {
public void testExceptions() throws InterpreterException, IOException, RepositoryException {
List<String> all = factory.getDefaultInterpreterSettingList();
// add setting with null option & properties expected nullArgumentException.class
try {
factory.add("a mock", "mock2", null, new Properties());
factory.add("a mock", "mock2", new LinkedList<Dependency>(), null, new Properties());
} catch(NullArgumentException e) {
assertEquals("Test null option" , e.getMessage(),new NullArgumentException("option").getMessage());
}
try {
factory.add("a mock" , "mock2" , new InterpreterOption(false),null);
factory.add("a mock", "mock2", new LinkedList<Dependency>(), new InterpreterOption(false), null);
} catch (NullArgumentException e){
assertEquals("Test null properties" , e.getMessage(),new NullArgumentException("properties").getMessage());
}
@ -130,14 +133,14 @@ public class InterpreterFactoryTest {
@Test
public void testSaveLoad() throws InterpreterException, IOException {
public void testSaveLoad() throws IOException, RepositoryException {
// interpreter settings
assertEquals(2, factory.get().size());
// check if file saved
assertTrue(new File(conf.getInterpreterSettingPath()).exists());
factory.add("newsetting", "mock1", new InterpreterOption(false), new Properties());
factory.add("newsetting", "mock1", new LinkedList<Dependency>(), new InterpreterOption(false), new Properties());
assertEquals(3, factory.get().size());
InterpreterFactory factory2 = new InterpreterFactory(conf, null, null, null);

View file

@ -55,6 +55,7 @@ import org.junit.Test;
import org.quartz.SchedulerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonatype.aether.RepositoryException;
public class NotebookTest implements JobListenerFactory{
private static final Logger logger = LoggerFactory.getLogger(NotebookTest.class);
@ -161,7 +162,7 @@ public class NotebookTest implements JobListenerFactory{
}
@Test
public void testPersist() throws IOException, SchedulerException{
public void testPersist() throws IOException, SchedulerException, RepositoryException {
Note note = notebook.createNote();
// run with default repl