From e42794d220377f9bf51ff6b7e92454b1c8b0e17b Mon Sep 17 00:00:00 2001 From: Sinisa Veseli <sveseli@aps.anl.gov> Date: Fri, 4 Dec 2015 22:04:09 +0000 Subject: [PATCH] added user interfaces and utilities that enable experiment data download from machines that have ssh access to the storage host --- bin/dm-download | 18 ++++ doc/RELEASE_NOTES.txt | 2 + etc/dm.sudo-rules.template | 2 +- src/python/dm/common/plugins/__init__.py | 0 .../common/plugins/fileProcessorInterface.py | 9 -- .../dm/common/plugins/fileTransferPlugin.py | 91 ------------------- .../common/plugins/rsyncFileTransferPlugin.py | 18 ---- .../dm/common/utility/configurationManager.py | 1 + .../utility/ldapLinuxPlatformUtility.py | 12 ++- src/python/dm/common/utility/linuxUtility.py | 13 ++- .../dm/common/utility/rsyncFileTransfer.py | 35 +++++++ .../dm/ds_web_service/api/fileRestApi.py | 31 +++++++ .../dm/ds_web_service/cli/addExperimentCli.py | 5 +- .../dm/ds_web_service/cli/downloadCli.py | 46 ++++++++++ .../cli/statExperimentFileCli.py | 2 +- .../service/experimentRouteDescriptor.py | 18 ++++ .../service/experimentSessionController.py | 22 +++++ .../service/impl/experimentManager.py | 21 ++++- .../impl/experimentSessionControllerImpl.py | 5 + .../service/userRouteDescriptor.py | 1 + 20 files changed, 226 insertions(+), 126 deletions(-) create mode 100755 bin/dm-download delete mode 100644 src/python/dm/common/plugins/__init__.py delete mode 100755 src/python/dm/common/plugins/fileProcessorInterface.py delete mode 100755 src/python/dm/common/plugins/fileTransferPlugin.py delete mode 100755 src/python/dm/common/plugins/rsyncFileTransferPlugin.py create mode 100755 src/python/dm/common/utility/rsyncFileTransfer.py create mode 100755 src/python/dm/ds_web_service/cli/downloadCli.py diff --git a/bin/dm-download b/bin/dm-download new file mode 100755 index 00000000..b0d57dfc --- /dev/null +++ b/bin/dm-download @@ -0,0 +1,18 @@ +#!/bin/sh + +# Run command + +if [ -z $DM_ROOT_DIR ]; then + cd `dirname $0` && myDir=`pwd` + setupFile=$myDir/../setup.sh + if [ ! -f $setupFile ]; then + echo "Cannot find setup file: $setupFile" + exit 1 + fi + source $setupFile > /dev/null +fi +source dm_command_setup.sh + +eval "$DM_ROOT_DIR/src/python/dm/ds_web_service/cli/downloadCli.py $DM_COMMAND_ARGS" + + diff --git a/doc/RELEASE_NOTES.txt b/doc/RELEASE_NOTES.txt index e19e6eca..90d7d0d7 100644 --- a/doc/RELEASE_NOTES.txt +++ b/doc/RELEASE_NOTES.txt @@ -7,6 +7,8 @@ Release 0.7 () simultaneously (required changes to DAQ service REST interfaces) - Enhanced start/stop DAQ and upload commands to use DM_FILE_SERVER_URL environment variable +- Added user interfaces and utilities that enable experiment data download + from machines that have SSH access to the storage host Release 0.6 (11/6/2015) ============================= diff --git a/etc/dm.sudo-rules.template b/etc/dm.sudo-rules.template index ad7f67d9..afc042be 100644 --- a/etc/dm.sudo-rules.template +++ b/etc/dm.sudo-rules.template @@ -6,7 +6,7 @@ Cmnd_Alias SETFACL=/usr/bin/setfacl -m group\:*\:rx DM_STORAGE_DIR/* Cmnd_Alias USERMOD=/usr/sbin/usermod -a -G * * Cmnd_Alias GROUPADD=/usr/sbin/groupadd * Cmnd_Alias CHOWN=/bin/chown -R \:* * -Cmnd_Alias GPASSWD=/usr/bin/gpasswd -M * * +Cmnd_Alias GPASSWD=/usr/bin/gpasswd * * * USER HOST = (root) NOPASSWD: SETFACL,USERMOD,GROUPADD,CHOWN,GPASSWD diff --git a/src/python/dm/common/plugins/__init__.py b/src/python/dm/common/plugins/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/python/dm/common/plugins/fileProcessorInterface.py b/src/python/dm/common/plugins/fileProcessorInterface.py deleted file mode 100755 index f9aef1ce..00000000 --- a/src/python/dm/common/plugins/fileProcessorInterface.py +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env python - -import abc -class FileProcessorInterface: - - @abc.abstractmethod - def processFile(self, filePath, daqPath, experiment): - return NotImplemented - diff --git a/src/python/dm/common/plugins/fileTransferPlugin.py b/src/python/dm/common/plugins/fileTransferPlugin.py deleted file mode 100755 index 1b369025..00000000 --- a/src/python/dm/common/plugins/fileTransferPlugin.py +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env python - -import os -from dm.common.utility.loggingManager import LoggingManager -from dm.common.utility.dmSubprocess import DmSubprocess -from dm.common.exceptions.invalidArgument import InvalidArgument -from dm.common.exceptions.invalidRequest import InvalidRequest -from fileProcessorInterface import FileProcessorInterface - -class FileTransferPlugin(FileProcessorInterface): - - def __init__(self, command, src=None, dest=None): - self.src = src - self.dest = dest - self.logger = LoggingManager.getInstance().getLogger(self.__class__.__name__) - if command is None or not len(command): - raise InvalidArgument('File transfer command must be non-empty string.') - self.command = command - self.subprocess = None - - def processFile(self, filePath, daqPath, experiment): - storageHost = experiment.get('storageHost') - storageDirectory = experiment.get('storageDirectory') - dest = '%s:%s' % (storageHost, storageDirectory) - # Use relative path with respect to daq directory as a source - os.chdir(daqPath) - src = os.path.relpath(filePath, daqPath) - self.start(src, dest) - - def getFullCommand(self, src, dest): - return '%s %s %s' % (self.command, src, dest) - - def setSrc(self, src): - self.src = src - - def setDest(self, dest): - self.dest = dest - - def start(self, src=None, dest=None): - # Use preconfigured source if provided source is None - fileSrc = src - if src is None: - fileSrc = self.src - # Use provided destination only if preconfigured destination is None - # Plugins may have desired destination preconfigured for all files - fileDest = self.dest - if self.dest is None: - fileDest = dest - - if not fileSrc or not fileDest: - raise InvalidRequest('Both source and destination must be non-empty strings.') - self.subprocess = DmSubprocess.getSubprocess(self.getFullCommand(fileSrc, fileDest)) - return self.subprocess.run() - - def wait(self): - if self.subprocess: - return self.subprocess.wait() - return None - - def poll(self): - if self.subprocess: - return self.subprocess.poll() - return None - - def getStdOut(self): - if self.subprocess: - return self.subprocess.getStdOut() - return None - - - def getStdErr(self): - if self.subprocess: - return self.subprocess.getStdErr() - return None - - def getExitStatus(self): - if self.subprocess: - return self.subprocess.getExitStatus() - return None - - def reset(self): - self.subprocess = None - -####################################################################### -# Testing. -if __name__ == '__main__': - ft = FileTransfer('rsync -arv', '/tmp/xyz', '/tmp/xyz2') - ft.start() - print 'StdOut: ', ft.getStdOut() - print 'StdErr: ', ft.getStdErr() - print 'Exit Status: ', ft.getExitStatus() diff --git a/src/python/dm/common/plugins/rsyncFileTransferPlugin.py b/src/python/dm/common/plugins/rsyncFileTransferPlugin.py deleted file mode 100755 index 3550d0cf..00000000 --- a/src/python/dm/common/plugins/rsyncFileTransferPlugin.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env python - -from fileTransferPlugin import FileTransferPlugin -class RsyncFileTransferPlugin(FileTransferPlugin): - - COMMAND = 'rsync -arvlPR' - - def __init__(self, src=None, dest=None): - FileTransferPlugin.__init__(self, self.COMMAND, src, dest) - -####################################################################### -# Testing. -if __name__ == '__main__': - ft = RsyncFileTransferPlugin('/tmp/xyz', '/tmp/xyz2') - ft.start() - print 'StdOut: ', ft.getStdOut() - print 'StdErr: ', ft.getStdErr() - print 'Exit Status: ', ft.getExitStatus() diff --git a/src/python/dm/common/utility/configurationManager.py b/src/python/dm/common/utility/configurationManager.py index 55cf8e3c..075e9c9e 100755 --- a/src/python/dm/common/utility/configurationManager.py +++ b/src/python/dm/common/utility/configurationManager.py @@ -816,3 +816,4 @@ class ConfigurationManager(UserDict.UserDict): if __name__ == '__main__': cm = ConfigurationManager.getInstance() print cm + print cm.getUsername() diff --git a/src/python/dm/common/utility/ldapLinuxPlatformUtility.py b/src/python/dm/common/utility/ldapLinuxPlatformUtility.py index 354a6b70..4db48455 100755 --- a/src/python/dm/common/utility/ldapLinuxPlatformUtility.py +++ b/src/python/dm/common/utility/ldapLinuxPlatformUtility.py @@ -18,6 +18,7 @@ class LdapLinuxPlatformUtility: USERMOD_CMD = '/usr/sbin/usermod' SETFACL_CMD = '/usr/bin/setfacl' CHOWN_CMD = '/bin/chown' + GPASSWD_CMD = '/usr/bin/gpasswd' def __init__(self, serverUrl, adminDn, adminPasswordFile, groupDnFormat, minGidNumber=None): self.serverUrl = serverUrl @@ -164,12 +165,21 @@ class LdapLinuxPlatformUtility: logger.error('Could not add user %s to group %s: %s' % (username, groupName, ex)) raise InternalError(exception=ex) + @classmethod def addLocalUserToGroup(cls, username, groupName): """ Add local user to group. """ logger = cls.getLogger() logger.debug('Adding local user %s to group %s' % (username, groupName)) - cmd = '%s -a -G %s %s' % (cls.USERMOD_CMD, groupName, username) + cmd = '%s -a %s %s' % (cls.GPASSWD_CMD, username, groupName) + cls.executeSudoCommand(cmd) + + @classmethod + def deleteLocalUserFromGroup(cls, username, groupName): + """ Remove local user from group. """ + logger = cls.getLogger() + logger.debug('Removing local user %s from group %s' % (username, groupName)) + cmd = '%s -d %s %s' % (cls.GPASSWD_CMD, username, groupName) cls.executeSudoCommand(cmd) def getGroupInfo(self, groupName): diff --git a/src/python/dm/common/utility/linuxUtility.py b/src/python/dm/common/utility/linuxUtility.py index afe5c2a1..97acf654 100755 --- a/src/python/dm/common/utility/linuxUtility.py +++ b/src/python/dm/common/utility/linuxUtility.py @@ -49,7 +49,18 @@ class LinuxUtility: @classmethod def addLocalUserToGroup(cls, username, groupName): """ Add local user to group. """ - cls.addUserToGroup(username, groupName) + logger = cls.getLogger() + logger.debug('Adding local user %s to group %s' % (username, groupName)) + cmd = '%s -a %s %s' % (cls.GPASSWD_CMD, username, groupName) + cls.executeSudoCommand(cmd) + + @classmethod + def deleteLocalUserFromGroup(cls, username, groupName): + """ Remove local user from group. """ + logger = cls.getLogger() + logger.debug('Removing local user %s from group %s' % (username, groupName)) + cmd = '%s -d %s %s' % (cls.GPASSWD_CMD, username, groupName) + cls.executeSudoCommand(cmd) @classmethod def setGroupUsers(cls, groupName, usernameList): diff --git a/src/python/dm/common/utility/rsyncFileTransfer.py b/src/python/dm/common/utility/rsyncFileTransfer.py new file mode 100755 index 00000000..c553aa5f --- /dev/null +++ b/src/python/dm/common/utility/rsyncFileTransfer.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python + +from dm.common.utility.dmSubprocess import DmSubprocess + +class RsyncFileTransfer: + + COMMAND = 'rsync' + + def __init__(self, src, dest, flags='-arvlP'): + self.src = src + self.dest = dest + self.flags = flags + self.command = '%s %s %s %s' % (self.COMMAND, self.flags, self.src, self.dest) + self.subprocess = DmSubprocess.getSubprocess(self.command) + + def execute(self): + return self.subprocess.run() + + def getStdOut(self): + return self.subprocess.getStdOut() + + def getStdErr(self): + return self.subprocess.getStdErr() + + def getExitStatus(self): + return self.subprocess.getExitStatus() + +####################################################################### +# Testing. +if __name__ == '__main__': + ft = RsyncFileTransfer('/tmp/abc', '/tmp/abc2') + ft.execute() + print 'StdOut: ', ft.getStdOut() + print 'StdErr: ', ft.getStdErr() + print 'Exit Status: ', ft.getExitStatus() diff --git a/src/python/dm/ds_web_service/api/fileRestApi.py b/src/python/dm/ds_web_service/api/fileRestApi.py index 597d886b..6d365118 100755 --- a/src/python/dm/ds_web_service/api/fileRestApi.py +++ b/src/python/dm/ds_web_service/api/fileRestApi.py @@ -3,10 +3,14 @@ import os import urllib import json +import getpass from dm.common.utility.encoder import Encoder from dm.common.exceptions.dmException import DmException +from dm.common.exceptions.invalidRequest import InvalidRequest from dm.common.objects.fileMetadata import FileMetadata +from dm.common.objects.experiment import Experiment +from dm.common.utility.rsyncFileTransfer import RsyncFileTransfer from dsRestApi import DsRestApi class FileRestApi(DsRestApi): @@ -40,6 +44,33 @@ class FileRestApi(DsRestApi): responseDict = self.sendSessionRequest(url=url, method='POST') return FileMetadata(responseDict) + @DsRestApi.execute + def download(self, experimentName, experimentFilePath='', destDirectory='.'): + username = getpass.getuser() + + # Initialize download + url = '%s/downloadAuthorizations/%s/%s' % (self.getContextRoot(), username, experimentName) + if not experimentName: + raise InvalidRequest('Experiment name must be provided.') + self.logger.info('Authorizing download for user %s (experiment: %s)' % (username, experimentName)) + responseDict = self.sendSessionRequest(url=url, method='POST') + experiment = Experiment(responseDict) + storageDirectory = experiment.get('storageDirectory') + storageHost = experiment.get('storageHost') + src = '%s@%s:%s' % (username, storageHost, storageDirectory) + if experimentFilePath: + src = '%s/%s' % (src, experimentFilePath) + dest = destDirectory + + # Download + fileTransfer = RsyncFileTransfer(src=src, dest=dest) + self.logger.info('Executing file download on behalf of %s (experiment: %s)' % (username, experimentName)) + fileTransfer.execute() + + # Finalize download + self.logger.info('Deleting download authorization for user %s (experiment: %s)' % (username, experimentName)) + self.sendSessionRequest(url=url, method='DELETE') + ####################################################################### # Testing. diff --git a/src/python/dm/ds_web_service/cli/addExperimentCli.py b/src/python/dm/ds_web_service/cli/addExperimentCli.py index 566a41d0..91a0dab5 100755 --- a/src/python/dm/ds_web_service/cli/addExperimentCli.py +++ b/src/python/dm/ds_web_service/cli/addExperimentCli.py @@ -10,8 +10,8 @@ class AddExperimentCli(DsWebServiceSessionCli): self.addOption('', '--experiment', dest='experimentName', help='Experiment name.') self.addOption('', '--type-id', dest='typeId', help='Experiment type id.') self.addOption('', '--description', dest='description', help='Experiment description.') - self.addOption('', '--start-date', dest='startDate', help='Experiment start date in format 31-AUG-15.') - self.addOption('', '--end-date', dest='endDate', help='Experiment end date in format 31-AUG-15.') + self.addOption('', '--start-date', dest='startDate', help='Experiment start date in format DD-MMM-YY.') + self.addOption('', '--end-date', dest='endDate', help='Experiment end date in format DD-MMM-YY.') def checkArgs(self): if self.options.experimentName is None: @@ -19,7 +19,6 @@ class AddExperimentCli(DsWebServiceSessionCli): if self.options.typeId is None: raise InvalidRequest('Experiment type id must be provided.') - def getExperimentName(self): return self.options.experimentName diff --git a/src/python/dm/ds_web_service/cli/downloadCli.py b/src/python/dm/ds_web_service/cli/downloadCli.py new file mode 100755 index 00000000..fe9650e6 --- /dev/null +++ b/src/python/dm/ds_web_service/cli/downloadCli.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python + +from dm.common.exceptions.invalidRequest import InvalidRequest +from dm.ds_web_service.api.fileRestApi import FileRestApi +from dsWebServiceSessionCli import DsWebServiceSessionCli + +class DownloadCli(DsWebServiceSessionCli): + def __init__(self): + DsWebServiceSessionCli.__init__(self) + self.addOption('', '--experiment', dest='experimentName', help='Experiment name.') + self.addOption('', '--relative-path', dest='experimentFilePath', default='', help='Experiment (relative) file path. If omitted, all experiment data will be downloaded.') + self.addOption('', '--destination-directory', dest='destinationDirectory', default='.', help='Destination directory. If omitted, files will be downloaded to current directory.') + + def checkArgs(self): + if self.options.experimentName is None: + raise InvalidRequest('Experiment name must be provided.') + + def getExperimentName(self): + return self.options.experimentName + + def getExperimentFilePath(self): + return self.options.experimentFilePath + + def getDestinationDirectory(self): + return self.options.destinationDirectory + + def runCommand(self): + self.parseArgs(usage=""" + dm-download --experiment=EXPERIMENTNAME + [--relative-path=EXPERIMENTFILEPATH] + [--destination-directory=DESTINATIONDIRECTORY] + +Description: + Downloads experiment files. + """) + self.checkArgs() + api = FileRestApi(self.getLoginUsername(), self.getLoginPassword(), self.getServiceHost(), self.getServicePort(), self.getServiceProtocol()) + api.download(self.getExperimentName(), self.getExperimentFilePath(), self.getDestinationDirectory()) + #print fileMetadata.getDisplayString(self.getDisplayKeys(), self.getDisplayFormat()) + +####################################################################### +# Run command. +if __name__ == '__main__': + cli = DownloadCli() + cli.run() + diff --git a/src/python/dm/ds_web_service/cli/statExperimentFileCli.py b/src/python/dm/ds_web_service/cli/statExperimentFileCli.py index 6e0565a3..f56798df 100755 --- a/src/python/dm/ds_web_service/cli/statExperimentFileCli.py +++ b/src/python/dm/ds_web_service/cli/statExperimentFileCli.py @@ -24,7 +24,7 @@ class StatExperimentFileCli(DsWebServiceSessionCli): def runCommand(self): self.parseArgs(usage=""" - dm-get-experiment --experiment=EXPERIMENTNAME --relative-path=EXPERIMENTFILEPATH + dm-stat-file --experiment=EXPERIMENTNAME --relative-path=EXPERIMENTFILEPATH Description: Retrieves stat information for a given file. diff --git a/src/python/dm/ds_web_service/service/experimentRouteDescriptor.py b/src/python/dm/ds_web_service/service/experimentRouteDescriptor.py index b599c4a6..f68d0f59 100755 --- a/src/python/dm/ds_web_service/service/experimentRouteDescriptor.py +++ b/src/python/dm/ds_web_service/service/experimentRouteDescriptor.py @@ -91,6 +91,24 @@ class ExperimentRouteDescriptor: 'method' : ['PUT'] }, + # Authorize download for a given UNIX username + { + 'name' : 'authorizeDownload', + 'path' : '%s/downloadAuthorizations/:(username)/:(experimentName)' % contextRoot, + 'controller' : experimentSessionController, + 'action' : 'authorizeDownload', + 'method' : ['POST'] + }, + + # Revoke authorization download for a given UNIX username + { + 'name' : 'deauthorizeDownload', + 'path' : '%s/downloadAuthorizations/:(username)/:(experimentName)' % contextRoot, + 'controller' : experimentSessionController, + 'action' : 'deauthorizeDownload', + 'method' : ['DELETE'] + }, + ] return routes diff --git a/src/python/dm/ds_web_service/service/experimentSessionController.py b/src/python/dm/ds_web_service/service/experimentSessionController.py index 5192b3f7..64d983ec 100755 --- a/src/python/dm/ds_web_service/service/experimentSessionController.py +++ b/src/python/dm/ds_web_service/service/experimentSessionController.py @@ -103,3 +103,25 @@ class ExperimentSessionController(DmSessionController): self.logger.debug('Returning: %s' % response) return response + @cherrypy.expose + @DmSessionController.require(DmSessionController.isAdministrator()) + @DmSessionController.execute + def authorizeDownload(self, username, experimentName, **kwargs): + if not username: + raise InvalidRequest('Invalid username provided.') + if not experimentName: + raise InvalidRequest('Invalid experiment name provided.') + response = self.experimentSessionControllerImpl.authorizeDownload(username, experimentName).getFullJsonRep() + return response + + @cherrypy.expose + @DmSessionController.require(DmSessionController.isAdministrator()) + @DmSessionController.execute + def deauthorizeDownload(self, username, experimentName, **kwargs): + if not username: + raise InvalidRequest('Invalid username provided.') + if not experimentName: + raise InvalidRequest('Invalid experiment name provided.') + response = self.experimentSessionControllerImpl.deauthorizeDownload(username, experimentName).getFullJsonRep() + return response + diff --git a/src/python/dm/ds_web_service/service/impl/experimentManager.py b/src/python/dm/ds_web_service/service/impl/experimentManager.py index c44acf93..108b62ff 100755 --- a/src/python/dm/ds_web_service/service/impl/experimentManager.py +++ b/src/python/dm/ds_web_service/service/impl/experimentManager.py @@ -15,6 +15,7 @@ from dm.common.utility.fileUtility import FileUtility from dm.common.db.api.experimentDbApi import ExperimentDbApi from dm.common.processing.fileProcessingManager import FileProcessingManager from dm.common.exceptions.objectNotFound import ObjectNotFound +from dm.common.exceptions.invalidRequest import InvalidRequest class ExperimentManager(Singleton): @@ -52,7 +53,7 @@ class ExperimentManager(Singleton): cm = ConfigurationManager.getInstance() configItems = cm.getConfigItems(ExperimentManager.CONFIG_SECTION_NAME) self.logger.debug('Got config items: %s' % configItems) - self.storageDirectory =cm.getConfigOption(ExperimentManager.CONFIG_SECTION_NAME, ExperimentManager.STORAGE_DIRECTORY_KEY) + self.storageDirectory = cm.getConfigOption(ExperimentManager.CONFIG_SECTION_NAME, ExperimentManager.STORAGE_DIRECTORY_KEY) self.manageStoragePermissions = ValueUtility.toBoolean(cm.getConfigOption(ExperimentManager.CONFIG_SECTION_NAME, ExperimentManager.MANAGE_STORAGE_PERMISSIONS_KEY)) platformUtility = cm.getConfigOption(ExperimentManager.CONFIG_SECTION_NAME, ExperimentManager.PLATFORM_UTILITY_KEY) if platformUtility: @@ -90,6 +91,24 @@ class ExperimentManager(Singleton): experimentUsers = experiment.get('experimentUsernameList', []) self.platformUtility.setGroupUsers(experimentName, experimentUsers) + def authorizeDownload(self, username, experimentName): + experiment = self.experimentDbApi.getExperimentByName(experimentName) + storageDirectory = self.updateExperimentWithStorageDataDirectory(experiment) + if os.path.exists(storageDirectory): + self.platformUtility.addLocalUserToGroup(username, experimentName) + else: + raise InvalidRequest('Experiment %s has not been started.' % experimentName) + return experiment + + def deauthorizeDownload(self, username, experimentName): + experiment = self.experimentDbApi.getExperimentByName(experimentName) + storageDirectory = self.updateExperimentWithStorageDataDirectory(experiment) + if os.path.exists(storageDirectory): + self.platformUtility.deleteLocalUserFromGroup(username, experimentName) + else: + raise InvalidRequest('Experiment %s has not been started.' % experimentName) + return experiment + def createExperimentGroup(self, experiment): experimentName = experiment.get('name') storageDirectory = experiment.get('storageDirectory') diff --git a/src/python/dm/ds_web_service/service/impl/experimentSessionControllerImpl.py b/src/python/dm/ds_web_service/service/impl/experimentSessionControllerImpl.py index 05af7916..24b69ee9 100755 --- a/src/python/dm/ds_web_service/service/impl/experimentSessionControllerImpl.py +++ b/src/python/dm/ds_web_service/service/impl/experimentSessionControllerImpl.py @@ -62,3 +62,8 @@ class ExperimentSessionControllerImpl(DmObjectManager): ExperimentManager.getInstance().updateExperimentWithStorageDataDirectory(experiment) return experiment + def authorizeDownload(self, username, experimentName): + return ExperimentManager.getInstance().authorizeDownload(username, experimentName) + + def deauthorizeDownload(self, username, experimentName): + return ExperimentManager.getInstance().deauthorizeDownload(username, experimentName) diff --git a/src/python/dm/ds_web_service/service/userRouteDescriptor.py b/src/python/dm/ds_web_service/service/userRouteDescriptor.py index cb1e7595..004b4739 100755 --- a/src/python/dm/ds_web_service/service/userRouteDescriptor.py +++ b/src/python/dm/ds_web_service/service/userRouteDescriptor.py @@ -63,6 +63,7 @@ class UserRouteDescriptor: 'action' : 'deleteUserExperimentRole', 'method' : ['DELETE'] }, + ] return routes -- GitLab