diff --git a/bin/dm-download b/bin/dm-download new file mode 100755 index 0000000000000000000000000000000000000000..b0d57dfc8687484c5c3789abe942c74401a08f7e --- /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 e19e6eca263a3f45279644f6c1b488f1cf3288fa..90d7d0d70c6593e1805b0056da2079981b518efe 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 ad7f67d906b04175e34f6306244ffa6403d40b89..afc042be96a25874f2808d914800812065a58d29 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 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/src/python/dm/common/plugins/fileProcessorInterface.py b/src/python/dm/common/plugins/fileProcessorInterface.py deleted file mode 100755 index f9aef1cedf77655088cdfe436fc0d0a2538e5b92..0000000000000000000000000000000000000000 --- 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 1b369025fd72713d282e96cfd036912c0186bff0..0000000000000000000000000000000000000000 --- 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 3550d0cf22b5ec2cca5671f56e488182d0cb05c5..0000000000000000000000000000000000000000 --- 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 55cf8e3c7181eb27062923d76c9cc44d883745ba..075e9c9e56677ba3d1e8eda1a9de6e54488df0ab 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 354a6b7097aa901be264097c13a8f38649dfeb9e..4db4845577f4b25d4d44ae666ed51701a4b01f98 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 afe5c2a1714eb1cc86f5c9364a4fb728626e994b..97acf654df2bcce81bfd05918ff09bb95c7bd357 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 0000000000000000000000000000000000000000..c553aa5f9ded636c38c288db5df09de2e30d61f7 --- /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 597d886b61f19bf02c6c5a5468d9dc927a1cb8bc..6d365118a4bfdeaa719b8f86bae6292da0685c03 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 566a41d007ada37b1782854e95b8e24d6b6a2af7..91a0dab5a9cd27d51ee224aad9c4392bf9e9e061 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 0000000000000000000000000000000000000000..fe9650e6b9d4ed2dc60b8c3a7de025ba3f2234e6 --- /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 6e0565a33db06362121ec5d1a1130f7e80754a2c..f56798dfcf7c0837535b7f00a0569ac268cef46e 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 b599c4a6db76533d8da394eab3d96dd371e798c4..f68d0f595118591e651808de392fc0a91f83f434 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 5192b3f7dda0444f22e1de0301270a11444f9491..64d983ec79b184295f1f8a24b77fd0a2b226493e 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 c44acf938e30fc45a1ac9f295267122d8196808c..108b62ff5d01959dafffc3962d75504dbd4ed626 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 05af7916b994e7192afd89fa2551ed05103f73b2..24b69ee995b25bffca10a2e44c66e8d196aebb34 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 cb1e7595fc925ed5c97d41787ffebc90f9188152..004b4739d4fbc7c8453f0ff665a0c11a10845867 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