From 5ceac6896106f91a7a1aa40d71c791418af3984d Mon Sep 17 00:00:00 2001
From: Sinisa Veseli <sveseli@aps.anl.gov>
Date: Mon, 13 Apr 2015 13:30:54 +0000
Subject: [PATCH] add first functional DS web service with user, experiment and
 auth controllers that allow retrieving user and experiment information from
 the db, start/stop experiment, and principal authorization; added
 corresponding API and CLI classes; added storage manager skeleton

---
 src/python/dm/ds_web_service/__init__.py      |  0
 src/python/dm/ds_web_service/api/__init__.py  |  0
 .../dm/ds_web_service/api/authRestApi.py      | 35 ++++++++
 src/python/dm/ds_web_service/api/dsRestApi.py | 21 +++++
 .../dm/ds_web_service/api/dsRestApiFactory.py | 57 ++++++++++++
 .../ds_web_service/api/experimentRestApi.py   | 83 +++++++++++++++++
 .../dm/ds_web_service/api/userRestApi.py      | 46 ++++++++++
 src/python/dm/ds_web_service/cli/__init__.py  |  0
 .../dm/ds_web_service/cli/addExperimentCli.py | 48 ++++++++++
 .../dm/ds_web_service/cli/dsWebServiceCli.py  | 17 ++++
 .../cli/dsWebServiceSessionCli.py             | 21 +++++
 .../dm/ds_web_service/cli/getExperimentCli.py | 43 +++++++++
 .../cli/getExperimentTypesCli.py              | 27 ++++++
 .../ds_web_service/cli/getExperimentsCli.py   | 27 ++++++
 .../dm/ds_web_service/cli/getUserCli.py       | 42 +++++++++
 .../dm/ds_web_service/cli/getUsersCli.py      | 27 ++++++
 .../ds_web_service/cli/startExperimentCli.py  | 36 ++++++++
 .../ds_web_service/cli/stopExperimentCli.py   | 36 ++++++++
 .../dm/ds_web_service/service/__init__.py     |  0
 .../ds_web_service/service/auth/__init__.py   |  0
 .../service/auth/dsAuthPrincipalRetriever.py  | 24 +++++
 .../service/authRouteDescriptor.py            | 32 +++++++
 .../service/authSessionController.py          | 22 +++++
 .../dm/ds_web_service/service/dsWebService.py | 42 +++++++++
 .../service/dsWebServiceRouteMapper.py        | 40 +++++++++
 .../service/experimentRouteDescriptor.py      | 89 +++++++++++++++++++
 .../service/experimentSessionController.py    | 87 ++++++++++++++++++
 .../ds_web_service/service/impl/__init__.py   |  0
 .../service/impl/authSessionControllerImpl.py | 29 ++++++
 .../impl/experimentSessionControllerImpl.py   | 49 ++++++++++
 .../service/impl/fileSystemControllerImpl.py  | 32 +++++++
 .../service/impl/storageManager.py            | 73 +++++++++++++++
 .../impl/userInfoSessionControllerImpl.py     | 26 ++++++
 .../service/userInfoSessionController.py      | 38 ++++++++
 .../service/userRouteDescriptor.py            | 53 +++++++++++
 35 files changed, 1202 insertions(+)
 create mode 100644 src/python/dm/ds_web_service/__init__.py
 create mode 100644 src/python/dm/ds_web_service/api/__init__.py
 create mode 100755 src/python/dm/ds_web_service/api/authRestApi.py
 create mode 100755 src/python/dm/ds_web_service/api/dsRestApi.py
 create mode 100755 src/python/dm/ds_web_service/api/dsRestApiFactory.py
 create mode 100755 src/python/dm/ds_web_service/api/experimentRestApi.py
 create mode 100755 src/python/dm/ds_web_service/api/userRestApi.py
 create mode 100644 src/python/dm/ds_web_service/cli/__init__.py
 create mode 100755 src/python/dm/ds_web_service/cli/addExperimentCli.py
 create mode 100755 src/python/dm/ds_web_service/cli/dsWebServiceCli.py
 create mode 100755 src/python/dm/ds_web_service/cli/dsWebServiceSessionCli.py
 create mode 100755 src/python/dm/ds_web_service/cli/getExperimentCli.py
 create mode 100755 src/python/dm/ds_web_service/cli/getExperimentTypesCli.py
 create mode 100755 src/python/dm/ds_web_service/cli/getExperimentsCli.py
 create mode 100755 src/python/dm/ds_web_service/cli/getUserCli.py
 create mode 100755 src/python/dm/ds_web_service/cli/getUsersCli.py
 create mode 100755 src/python/dm/ds_web_service/cli/startExperimentCli.py
 create mode 100755 src/python/dm/ds_web_service/cli/stopExperimentCli.py
 create mode 100644 src/python/dm/ds_web_service/service/__init__.py
 create mode 100644 src/python/dm/ds_web_service/service/auth/__init__.py
 create mode 100755 src/python/dm/ds_web_service/service/auth/dsAuthPrincipalRetriever.py
 create mode 100755 src/python/dm/ds_web_service/service/authRouteDescriptor.py
 create mode 100755 src/python/dm/ds_web_service/service/authSessionController.py
 create mode 100755 src/python/dm/ds_web_service/service/dsWebService.py
 create mode 100755 src/python/dm/ds_web_service/service/dsWebServiceRouteMapper.py
 create mode 100755 src/python/dm/ds_web_service/service/experimentRouteDescriptor.py
 create mode 100755 src/python/dm/ds_web_service/service/experimentSessionController.py
 create mode 100644 src/python/dm/ds_web_service/service/impl/__init__.py
 create mode 100755 src/python/dm/ds_web_service/service/impl/authSessionControllerImpl.py
 create mode 100755 src/python/dm/ds_web_service/service/impl/experimentSessionControllerImpl.py
 create mode 100755 src/python/dm/ds_web_service/service/impl/fileSystemControllerImpl.py
 create mode 100755 src/python/dm/ds_web_service/service/impl/storageManager.py
 create mode 100755 src/python/dm/ds_web_service/service/impl/userInfoSessionControllerImpl.py
 create mode 100755 src/python/dm/ds_web_service/service/userInfoSessionController.py
 create mode 100755 src/python/dm/ds_web_service/service/userRouteDescriptor.py

diff --git a/src/python/dm/ds_web_service/__init__.py b/src/python/dm/ds_web_service/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/python/dm/ds_web_service/api/__init__.py b/src/python/dm/ds_web_service/api/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/python/dm/ds_web_service/api/authRestApi.py b/src/python/dm/ds_web_service/api/authRestApi.py
new file mode 100755
index 00000000..571f9f31
--- /dev/null
+++ b/src/python/dm/ds_web_service/api/authRestApi.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+
+import os
+import urllib
+
+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.authorizationPrincipal import AuthorizationPrincipal
+from dsRestApi import DsRestApi
+
+class AuthRestApi(DsRestApi):
+   
+    def __init__(self, username=None, password=None, host=None, port=None, protocol=None):
+        DsRestApi.__init__(self, username, password, host, port, protocol)
+
+    @DsRestApi.execute
+    def getAuthorizationPrincipal(self, username):
+        if username is None:
+            raise InvalidRequest('Username must be provided.')
+        url = '%s/authorizationPrincipals/%s' % (self.getContextRoot(), username)
+        self.logger.debug('Retrieving principal for user %s' % (username))
+        responseData = self.sendSessionRequest(url=url, method='GET')
+        return AuthorizationPrincipal(responseData)
+
+#######################################################################
+# Testing.
+
+if __name__ == '__main__':
+    api = AuthRestApi('dm', 'dm', 'zagreb.svdev.net', 22236, 'http')
+    print api.authenticateUser('sveseli', 'sv')
+        
+
+
+
diff --git a/src/python/dm/ds_web_service/api/dsRestApi.py b/src/python/dm/ds_web_service/api/dsRestApi.py
new file mode 100755
index 00000000..0465538a
--- /dev/null
+++ b/src/python/dm/ds_web_service/api/dsRestApi.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+
+from dm.common.api.dmRestApi import DmRestApi
+
+class DsRestApi(DmRestApi):
+    """ Base DS DM REST api class. """
+
+    def __init__(self, username=None, password=None, host=None, port=None, protocol=None):
+        if host == None:
+            host = self.configurationManager.getDsWebServiceHost()
+        if port == None:
+            port = self.configurationManager.getDsWebServicePort()
+        DmRestApi.__init__(self, username, password, host, port, protocol)
+
+#######################################################################
+# Testing.
+
+if __name__ == '__main__':
+    pass
+
+
diff --git a/src/python/dm/ds_web_service/api/dsRestApiFactory.py b/src/python/dm/ds_web_service/api/dsRestApiFactory.py
new file mode 100755
index 00000000..07bc253d
--- /dev/null
+++ b/src/python/dm/ds_web_service/api/dsRestApiFactory.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python
+
+from dm.common.utility.loggingManager import LoggingManager
+from dm.common.utility.configurationManager import ConfigurationManager
+
+class DsRestApiFactory:
+
+    CONFIG_SECTION_NAME = 'DsRestApiFactory'
+    USERNAME_KEY = 'username'
+    PASSWORD_FILE_KEY = 'passwordfile'
+    HOST_KEY = 'host'
+    PORT_KEY = 'port'
+    PROTOCOL_KEY = 'protocol'
+
+    __logger = None
+    __username = None
+    __password = None
+    __host = None
+    __port = None
+    __protocol = None
+
+    @classmethod
+    def getLogger(cls):
+        if cls.__logger is None:
+            cls.__logger = LoggingManager.getInstance().getLogger(cls.__name__)
+        return cls.__logger
+
+    @classmethod
+    def __getConfiguration(cls):
+        if cls.__username is None:
+            cls.__username = ConfigurationManager.getInstance().getConfigOption(cls.CONFIG_SECTION_NAME, cls.USERNAME_KEY)
+            cls.__password = open(ConfigurationManager.getInstance().getConfigOption(cls.CONFIG_SECTION_NAME, cls.PASSWORD_FILE_KEY)).read().strip()
+            cls.__host = ConfigurationManager.getInstance().getConfigOption(cls.CONFIG_SECTION_NAME, cls.HOST_KEY)
+            cls.__port = ConfigurationManager.getInstance().getConfigOption(cls.CONFIG_SECTION_NAME, cls.PORT_KEY)
+            cls.__protocol = ConfigurationManager.getInstance().getConfigOption(cls.CONFIG_SECTION_NAME, cls.PROTOCOL_KEY)
+        return (cls.__username, cls.__password, cls.__host, cls.__port, cls.__protocol)
+
+    @classmethod
+    def getUserRestApi(cls):
+        from userRestApi import UserRestApi
+        (username, password, host, port, protocol) = cls.__getConfiguration()
+        api = UserRestApi(username, password, host, port, protocol)
+        return api
+
+    @classmethod
+    def getAuthRestApi(cls):
+        from authRestApi import AuthRestApi
+        (username, password, host, port, protocol) = cls.__getConfiguration()
+        api = AuthRestApi(username, password, host, port, protocol)
+        return api
+
+####################################################################
+# Testing
+
+if __name__ == '__main__':
+    pass
+
diff --git a/src/python/dm/ds_web_service/api/experimentRestApi.py b/src/python/dm/ds_web_service/api/experimentRestApi.py
new file mode 100755
index 00000000..b2438352
--- /dev/null
+++ b/src/python/dm/ds_web_service/api/experimentRestApi.py
@@ -0,0 +1,83 @@
+#!/usr/bin/env python
+
+import os
+import urllib
+
+from dm.common.utility.encoder import Encoder
+from dm.common.exceptions.dmException import DmException
+from dm.common.objects.experiment import Experiment
+from dm.common.objects.experimentType import ExperimentType
+from dsRestApi import DsRestApi
+
+class ExperimentRestApi(DsRestApi):
+    
+    def __init__(self, username=None, password=None, host=None, port=None, protocol=None):
+        DsRestApi.__init__(self, username, password, host, port, protocol)
+
+    @DsRestApi.execute
+    def getExperimentTypes(self):
+        url = '%s/experimentTypes' % (self.getContextRoot())
+        responseData = self.sendSessionRequest(url=url, method='GET')
+        return self.toDmObjectList(responseData, ExperimentType)
+
+    @DsRestApi.execute
+    def getExperiments(self):
+        url = '%s/experiments' % (self.getContextRoot())
+        responseData = self.sendSessionRequest(url=url, method='GET')
+        return self.toDmObjectList(responseData, Experiment)
+
+    @DsRestApi.execute
+    def getExperimentByName(self, name):
+        url = '%s/experimentsByName/%s' % (self.getContextRoot(), name)
+        if name is None or not len(name):
+            raise InvalidRequest('Experiment name must be provided.')
+        responseDict = self.sendSessionRequest(url=url, method='GET')
+        return Experiment(responseDict)
+
+    @DsRestApi.execute
+    def getExperimentById(self, id):
+        url = '%s/experiments/%s' % (self.getContextRoot(), id)
+        if id is None:
+            raise InvalidRequest('Experiment id must be provided.')
+        responseDict = self.sendSessionRequest(url=url, method='GET')
+        return Experiment(responseDict)
+
+    @DsRestApi.execute
+    def startExperiment(self, name):
+        url = '%s/experiments/start' % (self.getContextRoot())
+        if name is None or not len(name):
+            raise InvalidRequest('Experiment name must be provided.')
+        url += '?name=%s' % Encoder.encode(name)
+        responseDict = self.sendSessionRequest(url=url, method='PUT')
+        return Experiment(responseDict)
+
+    @DsRestApi.execute
+    def stopExperiment(self, name):
+        url = '%s/experiments/stop' % (self.getContextRoot())
+        if name is None or not len(name):
+            raise InvalidRequest('Experiment name must be provided.')
+        url += '?name=%s' % Encoder.encode(name)
+        responseDict = self.sendSessionRequest(url=url, method='PUT')
+        return Experiment(responseDict)
+
+    @DsRestApi.execute
+    def addExperiment(self, name, experimentTypeId, description):
+        url = '%s/experiments' % (self.getContextRoot())
+        if name is None or not len(name):
+            raise InvalidRequest('Experiment name must be provided.')
+        url += '?name=%s' % Encoder.encode(name)
+        if experimentTypeId is None:
+            raise InvalidRequest('Experiment type id must be provided.')
+        url += '&experimentTypeId=%s' % experimentTypeId
+        if description is not None:
+            url += '&description=%s' % Encoder.encode(description)
+        responseDict = self.sendSessionRequest(url=url, method='POST')
+        return Experiment(responseDict)
+
+#######################################################################
+# Testing.
+
+if __name__ == '__main__':
+    api = ExperimentRestApi('sveseli', 'sveseli', 'zagreb.svdev.net', 33336, 'http')
+    print api.startExperiment('experiment1')
+
diff --git a/src/python/dm/ds_web_service/api/userRestApi.py b/src/python/dm/ds_web_service/api/userRestApi.py
new file mode 100755
index 00000000..ee43ab21
--- /dev/null
+++ b/src/python/dm/ds_web_service/api/userRestApi.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+
+import os
+import urllib
+
+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.userInfo import UserInfo
+from dsRestApi import DsRestApi
+
+class UserRestApi(DsRestApi):
+    
+    def __init__(self, username=None, password=None, host=None, port=None, protocol=None):
+        DsRestApi.__init__(self, username, password, host, port, protocol)
+
+    @DsRestApi.execute
+    def getUsers(self):
+        url = '%s/users' % (self.getContextRoot())
+        responseData = self.sendSessionRequest(url=url, method='GET')
+        return self.toDmObjectList(responseData, UserInfo)
+
+    @DsRestApi.execute
+    def getUserById(self, id):
+        if id is None:
+            raise InvalidRequest('User id must be provided.')
+        url = '%s/users/%s' % (self.getContextRoot(), id)
+        responseData = self.sendSessionRequest(url=url, method='GET')
+        return UserInfo(responseData)
+
+    @DsRestApi.execute
+    def getUserByUsername(self, username):
+        if username is None:
+            raise InvalidRequest('Username must be provided.')
+        url = '%s/usersByUsername/%s' % (self.getContextRoot(), username)
+        responseData = self.sendSessionRequest(url=url, method='GET')
+        return UserInfo(responseData)
+
+#######################################################################
+# Testing.
+
+if __name__ == '__main__':
+    pass
+
+
+
diff --git a/src/python/dm/ds_web_service/cli/__init__.py b/src/python/dm/ds_web_service/cli/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/python/dm/ds_web_service/cli/addExperimentCli.py b/src/python/dm/ds_web_service/cli/addExperimentCli.py
new file mode 100755
index 00000000..aecd43cd
--- /dev/null
+++ b/src/python/dm/ds_web_service/cli/addExperimentCli.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+
+from dm.ds_web_service.api.experimentRestApi import ExperimentRestApi
+from dm.common.exceptions.invalidRequest import InvalidRequest
+from dsWebServiceSessionCli import DsWebServiceSessionCli
+
+class AddExperimentCli(DsWebServiceSessionCli):
+    def __init__(self):
+        DsWebServiceSessionCli.__init__(self)
+        self.addOption('', '--name', dest='name', help='Experiment name.')
+        self.addOption('', '--type-id', dest='typeId', help='Experiment type id.')
+        self.addOption('', '--description', dest='description', help='Experiment description.')
+
+    def checkArgs(self):
+        if self.options.name is None:
+            raise InvalidRequest('Experiment name must be provided.')
+        if self.options.typeId is None:
+            raise InvalidRequest('Experiment type id must be provided.')
+
+
+    def getName(self):
+        return self.options.name
+
+    def getTypeId(self):
+        return self.options.typeId
+
+    def getDescription(self):
+        return self.options.description
+
+    def runCommand(self):
+        self.parseArgs(usage="""
+    dm-get-experiment --name=NAME --type-id=TYPEID
+        [--description=DESCRIPTION]
+
+Description:
+    Add new experiment to the DM database. 
+        """)
+        self.checkArgs()
+        api = ExperimentRestApi(self.getLoginUsername(), self.getLoginPassword(), self.getServiceHost(), self.getServicePort(), self.getServiceProtocol())
+        experiment = api.addExperiment(self.getName(), self.getTypeId(), self.getDescription())
+        print experiment.getDisplayString(self.getDisplayKeys(), self.getDisplayFormat())
+
+#######################################################################
+# Run command.
+if __name__ == '__main__':
+    cli = AddExperimentCli()
+    cli.run()
+
diff --git a/src/python/dm/ds_web_service/cli/dsWebServiceCli.py b/src/python/dm/ds_web_service/cli/dsWebServiceCli.py
new file mode 100755
index 00000000..84bb15b6
--- /dev/null
+++ b/src/python/dm/ds_web_service/cli/dsWebServiceCli.py
@@ -0,0 +1,17 @@
+#!/usr/bin/env python
+
+from dm.common.cli.dmRestCli import DmRestCli
+from dm.common.utility.configurationManager import ConfigurationManager
+
+class DsWebServiceCli(DmRestCli):
+    """ DM DS web service cli class. """
+
+    def __init__(self, validArgCount=0):
+        DmRestCli.__init__(self, validArgCount)
+
+    def getDefaultServiceHost(self):
+        return ConfigurationManager.getInstance().getDsWebServiceHost()
+
+    def getDefaultServicePort(self):
+        return ConfigurationManager.getInstance().getDsWebServicePort()
+                        `
diff --git a/src/python/dm/ds_web_service/cli/dsWebServiceSessionCli.py b/src/python/dm/ds_web_service/cli/dsWebServiceSessionCli.py
new file mode 100755
index 00000000..c0770110
--- /dev/null
+++ b/src/python/dm/ds_web_service/cli/dsWebServiceSessionCli.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+
+from dm.common.cli.dmRestSessionCli import DmRestSessionCli
+from dm.common.utility.osUtility import OsUtility
+from dm.common.utility.configurationManager import ConfigurationManager
+
+class DsWebServiceSessionCli(DmRestSessionCli):
+    """ DM DS web service session cli class. """
+
+    DEFAULT_SESSION_CACHE_FILE = OsUtility.getUserHomeDir() + '/.dm/.ds.session.cache'
+
+    def __init__(self, validArgCount=0):
+        DmRestSessionCli.__init__(self, validArgCount)
+        ConfigurationManager.getInstance().setSessionCacheFile(DsWebServiceSessionCli.DEFAULT_SESSION_CACHE_FILE)
+
+    def getDefaultServiceHost(self):
+        return ConfigurationManager.getInstance().getDsWebServiceHost()
+
+    def getDefaultServicePort(self):
+        return ConfigurationManager.getInstance().getDsWebServicePort()
+
diff --git a/src/python/dm/ds_web_service/cli/getExperimentCli.py b/src/python/dm/ds_web_service/cli/getExperimentCli.py
new file mode 100755
index 00000000..40a80722
--- /dev/null
+++ b/src/python/dm/ds_web_service/cli/getExperimentCli.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+
+from dm.common.exceptions.invalidRequest import InvalidRequest
+from dm.ds_web_service.api.userRestApi import ExperimentRestApi
+from dsWebServiceSessionCli import DsWebServiceSessionCli
+
+class GetExperimentCli(DsWebServiceSessionCli):
+    def __init__(self):
+        DsWebServiceSessionCli.__init__(self)
+        self.addOption('', '--id', dest='id', help='Experiment id. Either id or name must be provided. If both are provided, id takes precedence.')
+        self.addOption('', '--name', dest='name', help='Experiment name. Either id or name must be provided. If both are provided, id takes precedence.')
+
+    def checkArgs(self):
+        if self.options.id is None and self.options.username is None:
+            raise InvalidRequest('Either user id or username must be provided.')
+
+    def getId(self):
+        return self.options.id
+
+    def getName(self):
+        return self.options.name
+
+    def runCommand(self):
+        self.parseArgs(usage="""
+    dm-get-user --id=ID|--name=NAME
+
+Description:
+    Retrieves experiment information.
+        """)
+        self.checkArgs()
+        api = ExperimentRestApi(self.getLoginUsername(), self.getLoginPassword(), self.getServiceHost(), self.getServicePort(), self.getServiceProtocol())
+        if self.getId() is not None:
+            experiment = api.getExperimentById(self.getId())
+        else:
+            experiment = api.getExperimentByName(self.getName())
+        print experiment.getDisplayString(self.getDisplayKeys(), self.getDisplayFormat())
+
+#######################################################################
+# Run command.
+if __name__ == '__main__':
+    cli = GetExperimentCli()
+    cli.run()
+
diff --git a/src/python/dm/ds_web_service/cli/getExperimentTypesCli.py b/src/python/dm/ds_web_service/cli/getExperimentTypesCli.py
new file mode 100755
index 00000000..55313950
--- /dev/null
+++ b/src/python/dm/ds_web_service/cli/getExperimentTypesCli.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+
+from dm.ds_web_service.api.experimentRestApi import ExperimentRestApi
+from dsWebServiceSessionCli import DsWebServiceSessionCli
+
+class GetExperimentTypesCli(DsWebServiceSessionCli):
+    def __init__(self):
+        DsWebServiceSessionCli.__init__(self)
+
+    def runCommand(self):
+        self.parseArgs(usage="""
+    dm-get-experiment-types 
+
+Description:
+    Retrieves list of known experiment types.
+        """)
+        api = ExperimentRestApi(self.getLoginUsername(), self.getLoginPassword(), self.getServiceHost(), self.getServicePort(), self.getServiceProtocol())
+        experimentTypes = api.getExperimentTypes()
+        for experimentType in experimentTypes:
+            print experimentType.getDisplayString(self.getDisplayKeys(), self.getDisplayFormat())
+
+
+#######################################################################
+# Run command.
+if __name__ == '__main__':
+    cli = GetExperimentTypesCli()
+    cli.run()
diff --git a/src/python/dm/ds_web_service/cli/getExperimentsCli.py b/src/python/dm/ds_web_service/cli/getExperimentsCli.py
new file mode 100755
index 00000000..d46f5b73
--- /dev/null
+++ b/src/python/dm/ds_web_service/cli/getExperimentsCli.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+
+from dm.ds_web_service.api.experimentRestApi import ExperimentRestApi
+from dsWebServiceSessionCli import DsWebServiceSessionCli
+
+class GetExperimentsCli(DsWebServiceSessionCli):
+    def __init__(self):
+        DsWebServiceSessionCli.__init__(self)
+
+    def runCommand(self):
+        self.parseArgs(usage="""
+    dm-get-experiments 
+
+Description:
+    Retrieves list of known experiments.
+        """)
+        api = ExperimentRestApi(self.getLoginUsername(), self.getLoginPassword(), self.getServiceHost(), self.getServicePort(), self.getServiceProtocol())
+        experiments = api.getExperiments()
+        for experiment in experiments:
+            print experiment.getDisplayString(self.getDisplayKeys(), self.getDisplayFormat())
+
+
+#######################################################################
+# Run command.
+if __name__ == '__main__':
+    cli = GetExperimentsCli()
+    cli.run()
diff --git a/src/python/dm/ds_web_service/cli/getUserCli.py b/src/python/dm/ds_web_service/cli/getUserCli.py
new file mode 100755
index 00000000..b45c34b5
--- /dev/null
+++ b/src/python/dm/ds_web_service/cli/getUserCli.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+
+from dm.common.exceptions.invalidRequest import InvalidRequest
+from dm.ds_web_service.api.userRestApi import UserRestApi
+from dsWebServiceSessionCli import DsWebServiceSessionCli
+
+class GetUserCli(DsWebServiceSessionCli):
+    def __init__(self):
+        DsWebServiceSessionCli.__init__(self)
+        self.addOption('', '--id', dest='id', help='User id. Either id or username must be provided. If both are provided, id takes precedence.')
+        self.addOption('', '--username', dest='username', help='User username. Either id or username must be provided. If both are provided, id takes precedence.')
+
+    def checkArgs(self):
+        if self.options.id is None and self.options.username is None:
+            raise InvalidRequest('Either user id or username must be provided.')
+
+    def getId(self):
+        return self.options.id
+
+    def getUsername(self):
+        return self.options.username
+
+    def runCommand(self):
+        self.parseArgs(usage="""
+    dm-get-user --id=ID|--username=USERNAME
+
+Description:
+    Retrieves user information.
+        """)
+        self.checkArgs()
+        api = UserRestApi(self.getLoginUsername(), self.getLoginPassword(), self.getServiceHost(), self.getServicePort(), self.getServiceProtocol())
+        if self.getId() is not None:
+            userInfo = api.getUserById(self.getId())
+        else:
+            userInfo = api.getUserByUsername(self.getUsername())
+        print userInfo.getDisplayString(self.getDisplayKeys(), self.getDisplayFormat())
+
+#######################################################################
+# Run command.
+if __name__ == '__main__':
+    cli = GetUserCli()
+    cli.run()
diff --git a/src/python/dm/ds_web_service/cli/getUsersCli.py b/src/python/dm/ds_web_service/cli/getUsersCli.py
new file mode 100755
index 00000000..8119fe0e
--- /dev/null
+++ b/src/python/dm/ds_web_service/cli/getUsersCli.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+
+from dm.ds_web_service.api.userRestApi import UserRestApi
+from dsWebServiceSessionCli import DsWebServiceSessionCli
+
+class GetUsersCli(DsWebServiceSessionCli):
+    def __init__(self):
+        DsWebServiceSessionCli.__init__(self)
+
+    def runCommand(self):
+        self.parseArgs(usage="""
+    dm-get-users 
+
+Description:
+    Retrieves list of registered users.
+        """)
+        api = UserRestApi(self.getLoginUsername(), self.getLoginPassword(), self.getServiceHost(), self.getServicePort(), self.getServiceProtocol())
+        users = api.getUsers()
+        for user in users:
+            print user.getDisplayString(self.getDisplayKeys(), self.getDisplayFormat())
+
+
+#######################################################################
+# Run command.
+if __name__ == '__main__':
+    cli = GetUsersCli()
+    cli.run()
diff --git a/src/python/dm/ds_web_service/cli/startExperimentCli.py b/src/python/dm/ds_web_service/cli/startExperimentCli.py
new file mode 100755
index 00000000..ed307d58
--- /dev/null
+++ b/src/python/dm/ds_web_service/cli/startExperimentCli.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python
+
+from dm.ds_web_service.api.experimentRestApi import ExperimentRestApi
+from dm.common.exceptions.invalidRequest import InvalidRequest
+from dsWebServiceSessionCli import DsWebServiceSessionCli
+
+class StartExperimentCli(DsWebServiceSessionCli):
+    def __init__(self):
+        DsWebServiceSessionCli.__init__(self)
+        self.addOption('', '--name', dest='name', help='Experiment name.')
+
+    def checkArgs(self):
+        if self.options.name is None:
+            raise InvalidRequest('Experiment name must be provided.')
+
+    def getName(self):
+        return self.options.name
+
+    def runCommand(self):
+        self.parseArgs(usage="""
+    dm-start-experiment --name=NAME
+
+Description:
+    Updates experiment start time in the DM database and prepares experiment data directory.
+        """)
+        self.checkArgs()
+        api = ExperimentRestApi(self.getLoginUsername(), self.getLoginPassword(), self.getServiceHost(), self.getServicePort(), self.getServiceProtocol())
+        experiment = api.startExperiment(self.getName())
+        print experiment.getDisplayString(self.getDisplayKeys(), self.getDisplayFormat())
+
+#######################################################################
+# Run command.
+if __name__ == '__main__':
+    cli = StartExperimentCli()
+    cli.run()
+
diff --git a/src/python/dm/ds_web_service/cli/stopExperimentCli.py b/src/python/dm/ds_web_service/cli/stopExperimentCli.py
new file mode 100755
index 00000000..7715a01d
--- /dev/null
+++ b/src/python/dm/ds_web_service/cli/stopExperimentCli.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python
+
+from dm.ds_web_service.api.experimentRestApi import ExperimentRestApi
+from dm.common.exceptions.invalidRequest import InvalidRequest
+from dsWebServiceSessionCli import DsWebServiceSessionCli
+
+class StopExperimentCli(DsWebServiceSessionCli):
+    def __init__(self):
+        DsWebServiceSessionCli.__init__(self)
+        self.addOption('', '--name', dest='name', help='Experiment name.')
+
+    def checkArgs(self):
+        if self.options.name is None:
+            raise InvalidRequest('Experiment name must be provided.')
+
+    def getName(self):
+        return self.options.name
+
+    def runCommand(self):
+        self.parseArgs(usage="""
+    dm-stop-experiment --name=NAME
+
+Description:
+    Updates experiment end date in the DM database and checks experiment data permissions.
+        """)
+        self.checkArgs()
+        api = ExperimentRestApi(self.getLoginUsername(), self.getLoginPassword(), self.getServiceHost(), self.getServicePort(), self.getServiceProtocol())
+        experiment = api.stopExperiment(self.getName())
+        print experiment.getDisplayString(self.getDisplayKeys(), self.getDisplayFormat())
+
+#######################################################################
+# Run command.
+if __name__ == '__main__':
+    cli = StopExperimentCli()
+    cli.run()
+
diff --git a/src/python/dm/ds_web_service/service/__init__.py b/src/python/dm/ds_web_service/service/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/python/dm/ds_web_service/service/auth/__init__.py b/src/python/dm/ds_web_service/service/auth/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/python/dm/ds_web_service/service/auth/dsAuthPrincipalRetriever.py b/src/python/dm/ds_web_service/service/auth/dsAuthPrincipalRetriever.py
new file mode 100755
index 00000000..9be90da8
--- /dev/null
+++ b/src/python/dm/ds_web_service/service/auth/dsAuthPrincipalRetriever.py
@@ -0,0 +1,24 @@
+#!/usr/bin/env python
+
+from dm.common.constants import dmRole
+from dm.common.objects.authorizationPrincipal import AuthorizationPrincipal
+from dm.common.db.api.userDbApi import UserDbApi
+from dm.common.service.auth.authorizationPrincipalRetriever import AuthorizationPrincipalRetriever 
+from dm.ds_web_service.api.dsRestApiFactory import DsRestApiFactory
+
+
+class DsAuthPrincipalRetriever(AuthorizationPrincipalRetriever):
+
+    def __init__(self):
+        AuthorizationPrincipalRetriever.__init__(self, self.__class__.__name__)
+        self.authApi = DsRestApiFactory.getAuthRestApi()
+
+    def getAuthorizationPrincipal(self, username):
+        principal = self.authApi.getAuthorizationPrincipal(username)
+        return principal
+
+#######################################################################
+# Testing.
+if __name__ == '__main__':
+    pass
+
diff --git a/src/python/dm/ds_web_service/service/authRouteDescriptor.py b/src/python/dm/ds_web_service/service/authRouteDescriptor.py
new file mode 100755
index 00000000..9cc34dc9
--- /dev/null
+++ b/src/python/dm/ds_web_service/service/authRouteDescriptor.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+
+#
+# Auth route descriptor.
+#
+
+from dm.common.utility.configurationManager import ConfigurationManager
+from authSessionController import AuthSessionController
+
+class AuthRouteDescriptor:
+
+    @classmethod
+    def getRoutes(cls):
+        contextRoot = ConfigurationManager.getInstance().getContextRoot()
+        authSessionController = AuthSessionController()
+
+        routes = [
+
+            # Get authorization principal
+            {
+                'name'          : 'getAuthorizationPrincipal',
+                'path'          : '%s/authorizationPrincipals/:(username)' % contextRoot,
+                'controller'    : authSessionController,
+                'action'        : 'getAuthorizationPrincipal',
+                'method'        : [ 'GET' ]
+            },
+
+        ]
+       
+        return routes
+
+
diff --git a/src/python/dm/ds_web_service/service/authSessionController.py b/src/python/dm/ds_web_service/service/authSessionController.py
new file mode 100755
index 00000000..6582f742
--- /dev/null
+++ b/src/python/dm/ds_web_service/service/authSessionController.py
@@ -0,0 +1,22 @@
+#authenticateUser!/usr/bin/env python
+
+import cherrypy
+from dm.common.service.dmSessionController import DmSessionController
+from dm.ds_web_service.service.impl.authSessionControllerImpl import AuthSessionControllerImpl
+
+class AuthSessionController(DmSessionController):
+
+    def __init__(self):
+        DmSessionController.__init__(self)
+        self.authSessionControllerImpl = AuthSessionControllerImpl()
+
+    @cherrypy.expose
+    @DmSessionController.require(DmSessionController.isLoggedIn())
+    @DmSessionController.execute
+    def getAuthorizationPrincipal(self, username, **kwargs):
+        if not len(username):
+            raise InvalidRequest('Invalid username provided.')
+        response = self.authSessionControllerImpl.getAuthorizationPrincipal(username).getFullJsonRep()
+        self.logger.debug('Returning authorization principal for %s' % (username))
+        return response
+
diff --git a/src/python/dm/ds_web_service/service/dsWebService.py b/src/python/dm/ds_web_service/service/dsWebService.py
new file mode 100755
index 00000000..acf8654b
--- /dev/null
+++ b/src/python/dm/ds_web_service/service/dsWebService.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+
+#
+# DM DS Web Service
+#
+
+####################################################################
+
+from dm.common.service.dmRestWebServiceBase import DmRestWebServiceBase
+from dm.common.utility.dmModuleManager import DmModuleManager
+from dm.common.utility.configurationManager import ConfigurationManager
+from dm.ds_web_service.service.impl.storageManager import StorageManager
+from dsWebServiceRouteMapper import DsWebServiceRouteMapper
+
+####################################################################
+
+class DsWebService(DmRestWebServiceBase):
+ 
+    def __init__(self):
+        DmRestWebServiceBase.__init__(self, DsWebServiceRouteMapper)
+
+    def initDmModules(self):
+        self.logger.debug('Initializing dm modules')
+
+        # Add modules that will be started.
+        moduleManager = DmModuleManager.getInstance()
+        moduleManager.addModule(StorageManager.getInstance())
+        self.logger.debug('Initialized dm modules')
+
+    def getDefaultServerHost(self):
+        return ConfigurationManager.getInstance().getServiceHost()
+
+    def getDefaultServerPort(self):
+        return ConfigurationManager.getInstance().getServicePort()
+
+####################################################################
+# Run service
+
+if __name__ == '__main__':
+    ConfigurationManager.getInstance().setServiceName('ds-web-service')
+    service = DsWebService();
+    service.run()
diff --git a/src/python/dm/ds_web_service/service/dsWebServiceRouteMapper.py b/src/python/dm/ds_web_service/service/dsWebServiceRouteMapper.py
new file mode 100755
index 00000000..ba377b8b
--- /dev/null
+++ b/src/python/dm/ds_web_service/service/dsWebServiceRouteMapper.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python
+
+#
+# Route mapper for DM DS web service.
+#
+
+import sys
+import os
+
+import cherrypy
+from dm.common.utility.loggingManager import LoggingManager
+from dm.common.utility.configurationManager import ConfigurationManager
+from dm.common.service.loginRouteDescriptor import LoginRouteDescriptor
+from userRouteDescriptor import UserRouteDescriptor
+from experimentRouteDescriptor import ExperimentRouteDescriptor
+from authRouteDescriptor import AuthRouteDescriptor
+
+class DsWebServiceRouteMapper:
+
+    @classmethod
+    def setupRoutes(cls):
+        """ Setup RESTFul routes. """
+        logger = LoggingManager.getInstance().getLogger(cls.__name__)
+        contextRoot = ConfigurationManager.getInstance().getContextRoot()
+        logger.debug('Using context root: %s' % contextRoot)
+
+        # Get routes.
+        routes = LoginRouteDescriptor.getRoutes() 
+        routes += AuthRouteDescriptor.getRoutes()
+        routes += UserRouteDescriptor.getRoutes()
+        routes += ExperimentRouteDescriptor.getRoutes()
+
+        # Add routes to dispatcher. 
+        d = cherrypy.dispatch.RoutesDispatcher()
+        for route in routes:
+            logger.debug('Connecting route: %s' % route)
+            d.connect(route['name'], route['path'], action=route['action'], controller=route['controller'], conditions=dict(method=route['method']))
+        return d
+
+
diff --git a/src/python/dm/ds_web_service/service/experimentRouteDescriptor.py b/src/python/dm/ds_web_service/service/experimentRouteDescriptor.py
new file mode 100755
index 00000000..736795fb
--- /dev/null
+++ b/src/python/dm/ds_web_service/service/experimentRouteDescriptor.py
@@ -0,0 +1,89 @@
+#!/usr/bin/env python
+
+#
+# User route descriptor.
+#
+
+from dm.common.utility.configurationManager import ConfigurationManager
+from experimentSessionController import ExperimentSessionController
+
+class ExperimentRouteDescriptor:
+
+    @classmethod
+    def getRoutes(cls):
+        contextRoot = ConfigurationManager.getInstance().getContextRoot()
+
+        # Static instances shared between different routes
+        experimentSessionController = ExperimentSessionController()
+
+        # Define routes.
+        routes = [
+
+            # Get experiment types
+            {
+                'name' : 'getExperimentTypes',
+                'path' : '%s/experimentTypes' % contextRoot,
+                'controller' : experimentSessionController,
+                'action' : 'getExperimentTypes',
+                'method' : ['GET']
+            },
+
+            # Get experiments
+            {
+                'name' : 'getExperiments',
+                'path' : '%s/experiments' % contextRoot,
+                'controller' : experimentSessionController,
+                'action' : 'getExperiments',
+                'method' : ['GET']
+            },
+
+            # Get experiment
+            {
+                'name' : 'getExperimentById',
+                'path' : '%s/experiments/:(id)' % contextRoot,
+                'controller' : experimentSessionController,
+                'action' : 'getExperimentById',
+                'method' : ['GET']
+            },
+
+            # Get experiment
+            {
+                'name' : 'getExperimentByName',
+                'path' : '%s/experimentsByName/:(name)' % contextRoot,
+                'controller' : experimentSessionController,
+                'action' : 'getExperimentByName',
+                'method' : ['GET']
+            },
+
+            # Add experiment
+            {
+                'name' : 'addExperiment',
+                'path' : '%s/experiments' % contextRoot,
+                'controller' : experimentSessionController,
+                'action' : 'addExperiment',
+                'method' : ['POST']
+            },
+
+            # Start experiment
+            {
+                'name' : 'startExperiment',
+                'path' : '%s/experiments/start' % contextRoot,
+                'controller' : experimentSessionController,
+                'action' : 'startExperiment',
+                'method' : ['PUT']
+            },
+
+            # Stop experiment
+            {
+                'name' : 'stopExperiment',
+                'path' : '%s/experiments/stop' % contextRoot,
+                'controller' : experimentSessionController,
+                'action' : 'stopExperiment',
+                'method' : ['PUT']
+            },
+
+        ]
+       
+        return routes
+
+
diff --git a/src/python/dm/ds_web_service/service/experimentSessionController.py b/src/python/dm/ds_web_service/service/experimentSessionController.py
new file mode 100755
index 00000000..c9c583b2
--- /dev/null
+++ b/src/python/dm/ds_web_service/service/experimentSessionController.py
@@ -0,0 +1,87 @@
+#!/usr/bin/env python
+
+import cherrypy
+
+from dm.common.service.dmSessionController import DmSessionController
+from dm.common.exceptions.invalidRequest import InvalidRequest
+from dm.common.utility.encoder import Encoder
+
+from dm.ds_web_service.service.impl.experimentSessionControllerImpl import ExperimentSessionControllerImpl
+
+
+class ExperimentSessionController(DmSessionController):
+
+    def __init__(self):
+        DmSessionController.__init__(self)
+        self.experimentSessionControllerImpl = ExperimentSessionControllerImpl()
+
+    @cherrypy.expose
+    @DmSessionController.require(DmSessionController.isLoggedIn())
+    @DmSessionController.execute
+    def getExperimentTypes(self, **kwargs):
+        return self.listToJson(self.experimentSessionControllerImpl.getExperimentTypes())
+
+    @cherrypy.expose
+    @DmSessionController.require(DmSessionController.isLoggedIn())
+    @DmSessionController.execute
+    def getExperiments(self, **kwargs):
+        return self.listToJson(self.experimentSessionControllerImpl.getExperiments())
+
+    @cherrypy.expose
+    @DmSessionController.require(DmSessionController.isLoggedIn())
+    @DmSessionController.execute
+    def getExperimentByName(self, name, **kwargs):
+        response = self.experimentSessionControllerImpl.getExperimentByName(name).getFullJsonRep()
+        self.logger.debug('Returning: %s' % response)
+        return response
+
+    @cherrypy.expose
+    @DmSessionController.require(DmSessionController.isLoggedIn())
+    @DmSessionController.execute
+    def getExperimentById(self, id, **kwargs):
+        response = self.experimentSessionControllerImpl.getExperimentByid(id).getFullJsonRep()
+        self.logger.debug('Returning: %s' % response)
+        return response
+
+    @cherrypy.expose
+    @DmSessionController.require(DmSessionController.isLoggedIn())
+    @DmSessionController.execute
+    def addExperiment(self, **kwargs):
+        name = kwargs.get('name')
+        if name is None or not len(name):
+            raise InvalidRequest('Missing experiment name.')
+        name = Encoder.decode(name)
+        experimentTypeId = kwargs.get('experimentTypeId')
+        if experimentTypeId is None:
+            raise InvalidRequest('Missing experiment type id.')
+        description = kwargs.get('description')
+        if description is not None:
+            description = Encoder.decode(description)
+        response = self.experimentSessionControllerImpl.addExperiment(name, experimentTypeId, description).getFullJsonRep()
+        self.logger.debug('Returning: %s' % response)
+        return response
+
+    @cherrypy.expose
+    @DmSessionController.require(DmSessionController.isLoggedIn())
+    @DmSessionController.execute
+    def startExperiment(self, **kwargs):
+        name = kwargs.get('name')
+        if name is None or not len(name):
+            raise InvalidRequest('Missing experiment name.')
+        name = Encoder.decode(name)
+        response = self.experimentSessionControllerImpl.startExperiment(name).getFullJsonRep()
+        self.logger.debug('Returning: %s' % response)
+        return response
+
+    @cherrypy.expose
+    @DmSessionController.require(DmSessionController.isLoggedIn())
+    @DmSessionController.execute
+    def stopExperiment(self, **kwargs):
+        name = kwargs.get('name')
+        if name is None or not len(name):
+            raise InvalidRequest('Missing experiment name.')
+        name = Encoder.decode(name)
+        response = self.experimentSessionControllerImpl.stopExperiment(name).getFullJsonRep()
+        self.logger.debug('Returning: %s' % response)
+        return response
+
diff --git a/src/python/dm/ds_web_service/service/impl/__init__.py b/src/python/dm/ds_web_service/service/impl/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/python/dm/ds_web_service/service/impl/authSessionControllerImpl.py b/src/python/dm/ds_web_service/service/impl/authSessionControllerImpl.py
new file mode 100755
index 00000000..12ac9cd0
--- /dev/null
+++ b/src/python/dm/ds_web_service/service/impl/authSessionControllerImpl.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+
+#
+# Implementation for user info controller.
+#
+
+from dm.common.constants import dmRole
+from dm.common.objects.authorizationPrincipal import AuthorizationPrincipal 
+from dm.common.objects.dmObjectManager import DmObjectManager
+from dm.common.db.api.userDbApi import UserDbApi
+
+class AuthSessionControllerImpl(DmObjectManager):
+    """ User info controller implementation class. """
+
+    def __init__(self):
+        DmObjectManager.__init__(self)
+        self.userDbApi = UserDbApi()
+
+    def getAuthorizationPrincipal(self, username):
+        user = self.userDbApi.getUserWithPasswordByUsername(username)
+        principal = AuthorizationPrincipal(name=username, token=user.get('password'))
+        principal.setRole(dmRole.DM_USER_ROLE)
+        principal.setUserInfo(user)
+        for userSystemRoleName in user.get('userSystemRoleNameList', []):
+            if userSystemRoleName == dmRole.DM_ADMIN_ROLE:
+                principal.setRole(dmRole.DM_ADMIN_ROLE)
+        return principal
+
+
diff --git a/src/python/dm/ds_web_service/service/impl/experimentSessionControllerImpl.py b/src/python/dm/ds_web_service/service/impl/experimentSessionControllerImpl.py
new file mode 100755
index 00000000..f41021dc
--- /dev/null
+++ b/src/python/dm/ds_web_service/service/impl/experimentSessionControllerImpl.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+#
+# Implementation for experiment session controller.
+#
+
+import time
+
+from dm.common.objects.experiment import Experiment
+from dm.common.objects.dmObjectManager import DmObjectManager
+from dm.common.exceptions.invalidRequest import InvalidRequest
+from dm.common.db.api.experimentDbApi import ExperimentDbApi
+from dm.ds_web_service.service.impl.storageManager import StorageManager
+
+class ExperimentSessionControllerImpl(DmObjectManager):
+    """ Experiment session controller implementation class. """
+
+    def __init__(self):
+        DmObjectManager.__init__(self)
+        self.experimentDbApi = ExperimentDbApi()
+
+    def getExperimentTypes(self):
+        experimentTypeList = self.experimentDbApi.getExperimentTypes()
+        return experimentTypeList
+
+    def getExperiments(self):
+        experimentList = self.experimentDbApi.getExperiments()
+        return experimentList
+
+    def getExperimentByName(self, name):
+        experiment = self.experimentDbApi.getExperimentByName(name)
+        return experiment
+
+    def getExperimentById(self, id):
+        experiment = self.experimentDbApi.getExperimentById(id)
+        return experiment
+
+    def addExperiment(self, name, experimentTypeId, description):
+        experiment = self.experimentDbApi.addExperiment(name, experimentTypeId, description)
+        return experiment
+
+    def startExperiment(self, name):
+        experiment = self.experimentDbApi.setExperimentStartDateToNow(name)
+        StorageManager.getInstance().createExperimentDataDirectory(experiment)
+        return experiment
+
+    def stopExperiment(self, name):
+        experiment = self.experimentDbApi.setExperimentEndDateToNow(name)
+        return experiment
diff --git a/src/python/dm/ds_web_service/service/impl/fileSystemControllerImpl.py b/src/python/dm/ds_web_service/service/impl/fileSystemControllerImpl.py
new file mode 100755
index 00000000..b3e27a94
--- /dev/null
+++ b/src/python/dm/ds_web_service/service/impl/fileSystemControllerImpl.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+
+#
+# Implementation for file system controller.
+#
+
+#######################################################################
+
+import threading
+
+from dm.common.objects.dmObject import DmObject
+from dm.common.objects.dmObjectManager import DmObjectManager
+from dm.common.utility.dmSubprocess import DmSubprocess
+
+#######################################################################
+
+class FileSystemControllerImpl(DmObjectManager):
+    """ FS controller implementation class. """
+
+    def __init__(self):
+        DmObjectManager.__init__(self)
+
+    def getDirectoryList(self, path):
+        p = DmSubprocess('ls -l %s' % path)
+        p.run()
+        return DmObject({'path' : path, 'directoryList' : p.getStdOut()})
+
+    def writeFile(self, path, content):
+        f = open(path, 'w')
+        f.write('%s\n' % content)
+        f.close()
+        return DmObject({'path' : path})
diff --git a/src/python/dm/ds_web_service/service/impl/storageManager.py b/src/python/dm/ds_web_service/service/impl/storageManager.py
new file mode 100755
index 00000000..e12565f3
--- /dev/null
+++ b/src/python/dm/ds_web_service/service/impl/storageManager.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python
+
+import threading
+import time
+
+from dm.common.utility.loggingManager import LoggingManager
+from dm.common.utility.configurationManager import ConfigurationManager
+from dm.common.utility.singleton import Singleton
+
+
+class StorageManager(Singleton):
+
+    CONFIG_SECTION_NAME = 'StorageManager'
+    STORAGE_DIRECTORY_KEY = 'storagedirectory'
+
+    # Singleton.
+    __instanceLock = threading.RLock()
+    __instance = None
+
+    def __init__(self):
+        StorageManager.__instanceLock.acquire()
+        try:
+            if StorageManager.__instance:
+                return
+            StorageManager.__instance = self
+            self.logger = LoggingManager.getInstance().getLogger(self.__class__.__name__)
+
+            self.logger.debug('Initializing')
+            self.lock = threading.RLock()
+            self.__configure()
+            self.logger.debug('Initialization complete')
+        finally:
+            StorageManager.__instanceLock.release()
+
+    def __configure(self):
+        cm = ConfigurationManager.getInstance()
+        configItems = cm.getConfigItems(StorageManager.CONFIG_SECTION_NAME)
+        self.logger.debug('Got config items: %s' % configItems)
+        self.storageDirectory = cm.getConfigOption(StorageManager.CONFIG_SECTION_NAME, StorageManager.STORAGE_DIRECTORY_KEY)
+
+    def createExperimentDataDirectory(self, experiment):
+        self.lock.acquire()
+        try:
+            experimentTypeName = experiment.get('experimentType').get('rootDataPath')
+            experimentName = experiment.get('name')
+            dataDirectory = '%s/%s/%s' % (self.storageDirectory, experimentTypeName, experimentName)
+            dataDirectory = dataDirectory.replace('//', '/')
+            self.logger.debug('Creating data directory for experiment %s: %s' % (experimentName, dataDirectory))
+            experiment['dataDirectory'] = dataDirectory
+        finally:
+            self.lock.release()
+      
+    def start(self):
+        self.lock.acquire()
+        try:
+           self.logger.debug('Started storage manager')
+        finally:
+           self.lock.release()
+
+    def stop(self):
+        self.lock.acquire()
+        try:
+           self.logger.debug('Stopped storage manager')
+        finally:
+           self.lock.release()
+
+####################################################################
+# Testing
+
+if __name__ == '__main__':
+    sm = StorageManager.getInstance()
+    print sm
+
diff --git a/src/python/dm/ds_web_service/service/impl/userInfoSessionControllerImpl.py b/src/python/dm/ds_web_service/service/impl/userInfoSessionControllerImpl.py
new file mode 100755
index 00000000..2fbeb83a
--- /dev/null
+++ b/src/python/dm/ds_web_service/service/impl/userInfoSessionControllerImpl.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python
+
+#
+# Implementation for user info controller.
+#
+
+from dm.common.objects.dmObject import DmObject
+from dm.common.objects.dmObjectManager import DmObjectManager
+from dm.common.db.api.userDbApi import UserDbApi
+
+class UserInfoSessionControllerImpl(DmObjectManager):
+    """ User info controller implementation class. """
+
+    def __init__(self):
+        DmObjectManager.__init__(self)
+        self.userDbApi = UserDbApi()
+
+    def getUsers(self):
+        return self.userDbApi.getUsers()
+
+    def getUserById(self, id):
+        return self.userDbApi.getUserById(id)
+
+    def getUserByUsername(self, username):
+        return self.userDbApi.getUserByUsername(username)
+
diff --git a/src/python/dm/ds_web_service/service/userInfoSessionController.py b/src/python/dm/ds_web_service/service/userInfoSessionController.py
new file mode 100755
index 00000000..984e5ba7
--- /dev/null
+++ b/src/python/dm/ds_web_service/service/userInfoSessionController.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+
+import cherrypy
+from dm.common.service.dmSessionController import DmSessionController
+from dm.ds_web_service.service.impl.userInfoSessionControllerImpl import UserInfoSessionControllerImpl
+
+class UserInfoSessionController(DmSessionController):
+
+    def __init__(self):
+        DmSessionController.__init__(self)
+        self.userInfoSessionControllerImpl = UserInfoSessionControllerImpl()
+
+    @cherrypy.expose
+    @DmSessionController.require(DmSessionController.isLoggedIn())
+    @DmSessionController.execute
+    def getUsers(self, **kwargs):
+        return self.listToJson(self.userInfoSessionControllerImpl.getUsers())
+
+    @cherrypy.expose
+    @DmSessionController.require(DmSessionController.isLoggedIn())
+    @DmSessionController.execute
+    def getUserById(self, id, **kwargs):
+        if not id:
+            raise InvalidRequest('Invalid id provided.')
+        response = self.userInfoSessionControllerImpl.getUserById(id).getFullJsonRep()
+        self.logger.debug('Returning user info for %s: %s' % (id,response))
+        return response
+
+    @cherrypy.expose
+    @DmSessionController.require(DmSessionController.isLoggedIn())
+    @DmSessionController.execute
+    def getUserByUsername(self, username, **kwargs):
+        if not len(username):
+            raise InvalidRequest('Invalid username provided.')
+        response = self.userInfoSessionControllerImpl.getUserByUsername(username).getFullJsonRep()
+        self.logger.debug('Returning user info for %s: %s' % (username,response))
+        return response
+
diff --git a/src/python/dm/ds_web_service/service/userRouteDescriptor.py b/src/python/dm/ds_web_service/service/userRouteDescriptor.py
new file mode 100755
index 00000000..651727f1
--- /dev/null
+++ b/src/python/dm/ds_web_service/service/userRouteDescriptor.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+
+#
+# User route descriptor.
+#
+
+from dm.common.utility.configurationManager import ConfigurationManager
+from userInfoSessionController import UserInfoSessionController
+
+class UserRouteDescriptor:
+
+    @classmethod
+    def getRoutes(cls):
+        contextRoot = ConfigurationManager.getInstance().getContextRoot()
+
+        # Static instances shared between different routes
+        userInfoSessionController = UserInfoSessionController()
+
+        # Define routes.
+        routes = [
+
+            # Get user info list
+            {
+                'name' : 'getUsers',
+                'path' : '%s/users' % contextRoot,
+                'controller' : userInfoSessionController,
+                'action' : 'getUsers', 
+                'method' : ['GET']
+            },
+
+            # Get user by id
+            {
+                'name' : 'getUserById',
+                'path' : '%s/users/:(id)' % contextRoot,
+                'controller' : userInfoSessionController,
+                'action' : 'getUserById', 
+                'method' : ['GET']
+            },
+
+            # Get user by username
+            {
+                'name' : 'getUserByUsername',
+                'path' : '%s/usersByUsername/:(username)' % contextRoot,
+                'controller' : userInfoSessionController,
+                'action' : 'getUserByUsername', 
+                'method' : ['GET']
+            },
+
+        ]
+       
+        return routes
+
+
-- 
GitLab