diff --git a/objectstore_middletier/CMakeLists.txt b/objectstore_middletier/CMakeLists.txt
index ebb62593ce2a9c55c8711d98906c252f2a1bbcc5..a84ae67f3e215379f6b4dc29b1fcc0c697d918f2 100644
--- a/objectstore_middletier/CMakeLists.txt
+++ b/objectstore_middletier/CMakeLists.txt
@@ -5,5 +5,15 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../middletier)
 include_directories(${CMAKE_BINARY_DIR})
 
 add_library (CTAObjectStoreMiddleTier
-  ObjectStoreMiddleTier.cpp
+  ObjectStoreMiddleTierAdmin.cpp
 )
+
+set(MiddleTierUnitTests
+  MiddleTierAdminAbstractTest.cpp
+  MiddleTierTest.cpp
+)
+
+add_executable(ObjectStoreMiddleTierUnitTests unit_tests.cpp ${MiddleTierUnitTests})
+target_link_libraries(ObjectStoreMiddleTierUnitTests
+  CTAObjectStoreMiddleTier
+  protobuf rados CTAObjectStore gtest gmock ctamiddletier)
diff --git a/objectstore_middletier/MiddleTierAdminAbstractTest.cpp b/objectstore_middletier/MiddleTierAdminAbstractTest.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9ff2a596226c4a82f597ccfbc2d5f9749a50af60
--- /dev/null
+++ b/objectstore_middletier/MiddleTierAdminAbstractTest.cpp
@@ -0,0 +1,715 @@
+/*
+ * The CERN Tape Archive (CTA) project
+ * Copyright (C) 2015  CERN
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "MiddleTierAdminAbstractTest.hpp"
+#include "cta/SqliteMiddleTierAdmin.hpp"
+#include "cta/SqliteMiddleTierUser.hpp"
+
+#include <gtest/gtest.h>
+namespace unitTests {
+
+TEST_P(MiddleTierAdminAbstractTest, createStorageClass_new) {
+  using namespace cta;
+
+  const SecurityIdentity requester;
+
+  {
+    std::list<StorageClass> storageClasses;
+    ASSERT_NO_THROW(storageClasses = m_middleTier.admin->getStorageClasses(requester));
+    ASSERT_TRUE(storageClasses.empty());
+  }
+
+  const std::string name = "TestStorageClass";
+  const uint16_t nbCopies = 2;
+  const std::string comment = "Comment";
+  ASSERT_NO_THROW(m_middleTier.admin->createStorageClass(requester, name, nbCopies,
+    comment));
+
+  {
+    std::list<StorageClass> storageClasses;
+    ASSERT_NO_THROW(storageClasses = m_middleTier.admin->getStorageClasses(requester));
+    ASSERT_EQ(1, storageClasses.size());
+
+    StorageClass storageClass;
+    ASSERT_NO_THROW(storageClass = storageClasses.front());
+    ASSERT_EQ(name, storageClass.getName());
+    ASSERT_EQ(nbCopies, storageClass.getNbCopies());
+  }
+}
+
+TEST_P(MiddleTierAdminAbstractTest,
+  createStorageClass_already_existing) {
+  using namespace cta;
+
+  const SecurityIdentity requester;
+
+  {
+    std::list<StorageClass> storageClasses;
+    ASSERT_NO_THROW(storageClasses = m_middleTier.admin->getStorageClasses(requester));
+    ASSERT_TRUE(storageClasses.empty());
+  }
+
+  const std::string name = "TestStorageClass";
+  const uint16_t nbCopies = 2;
+  const std::string comment = "Comment";
+  ASSERT_NO_THROW(m_middleTier.admin->createStorageClass(requester, name, nbCopies, comment));
+
+  {
+    std::list<StorageClass> storageClasses;
+    ASSERT_NO_THROW(storageClasses = m_middleTier.admin->getStorageClasses(requester));
+    ASSERT_EQ(1, storageClasses.size());
+
+    StorageClass storageClass;
+    ASSERT_NO_THROW(storageClass = storageClasses.front());
+    ASSERT_EQ(name, storageClass.getName());
+    ASSERT_EQ(nbCopies, storageClass.getNbCopies());
+  }
+  
+  ASSERT_THROW(m_middleTier.admin->createStorageClass(requester, name, nbCopies, comment),
+    std::exception);
+}
+
+TEST_P(MiddleTierAdminAbstractTest,
+  createStorageClass_lexicographical_order) {
+  using namespace cta;
+
+  const SecurityIdentity requester;
+
+  {
+    std::list<StorageClass> storageClasses;
+    ASSERT_NO_THROW(storageClasses = m_middleTier.admin->getStorageClasses(requester));
+    ASSERT_TRUE(storageClasses.empty());
+  }
+
+  ASSERT_NO_THROW(m_middleTier.admin->createStorageClass(requester, "d", 1, "Comment d"));
+  ASSERT_NO_THROW(m_middleTier.admin->createStorageClass(requester, "b", 1, "Comment b"));
+  ASSERT_NO_THROW(m_middleTier.admin->createStorageClass(requester, "a", 1, "Comment a"));
+  ASSERT_NO_THROW(m_middleTier.admin->createStorageClass(requester, "c", 1, "Comment c"));
+  
+  {
+    std::list<StorageClass> storageClasses;
+    ASSERT_NO_THROW(storageClasses = m_middleTier.admin->getStorageClasses(requester));
+    ASSERT_EQ(4, storageClasses.size());
+
+    ASSERT_EQ(std::string("a"), storageClasses.front().getName());
+    storageClasses.pop_front();
+    ASSERT_EQ(std::string("b"), storageClasses.front().getName());
+    storageClasses.pop_front();
+    ASSERT_EQ(std::string("c"), storageClasses.front().getName());
+    storageClasses.pop_front();
+    ASSERT_EQ(std::string("d"), storageClasses.front().getName());
+  }
+}
+
+TEST_P(MiddleTierAdminAbstractTest, deleteStorageClass_existing) {
+  using namespace cta;
+
+  const SecurityIdentity requester;
+
+  {
+    std::list<StorageClass> storageClasses;
+    ASSERT_NO_THROW(storageClasses = m_middleTier.admin->getStorageClasses(requester));
+    ASSERT_TRUE(storageClasses.empty());
+  }
+
+  const std::string name = "TestStorageClass";
+  const uint16_t nbCopies = 2;
+  const std::string comment = "Comment";
+  ASSERT_NO_THROW(m_middleTier.admin->createStorageClass(requester, name, nbCopies, comment));
+
+  {
+    std::list<StorageClass> storageClasses;
+    ASSERT_NO_THROW(storageClasses = m_middleTier.admin->getStorageClasses(requester));
+    ASSERT_EQ(1, storageClasses.size());
+  
+    StorageClass storageClass;
+    ASSERT_NO_THROW(storageClass = storageClasses.front());
+    ASSERT_EQ(name, storageClass.getName());
+    ASSERT_EQ(nbCopies, storageClass.getNbCopies());
+
+    ASSERT_NO_THROW(m_middleTier.admin->deleteStorageClass(requester, name));
+  }
+
+  {
+    std::list<StorageClass> storageClasses;
+    ASSERT_NO_THROW(storageClasses = m_middleTier.admin->getStorageClasses(requester));
+    ASSERT_TRUE(storageClasses.empty());
+  }
+}
+
+TEST_P(MiddleTierAdminAbstractTest,
+  deleteStorageClass_in_use_by_directory) {
+  using namespace cta;
+
+  const SecurityIdentity requester;
+
+  {
+    std::list<StorageClass> storageClasses;
+    ASSERT_NO_THROW(storageClasses = m_middleTier.admin->getStorageClasses(requester));
+    ASSERT_TRUE(storageClasses.empty());
+  }
+
+  const std::string storageClassName = "TestStorageClass";
+  const uint16_t nbCopies = 2;
+  const std::string comment = "Comment";
+  ASSERT_NO_THROW(m_middleTier.admin->createStorageClass(requester, storageClassName, nbCopies,
+    comment));
+
+  {
+    std::list<StorageClass> storageClasses;
+    ASSERT_NO_THROW(storageClasses = m_middleTier.admin->getStorageClasses(requester));
+    ASSERT_EQ(1, storageClasses.size());
+
+    StorageClass storageClass;
+    ASSERT_NO_THROW(storageClass = storageClasses.front());
+    ASSERT_EQ(storageClassName, storageClass.getName());
+    ASSERT_EQ(nbCopies, storageClass.getNbCopies());
+  }
+
+  ASSERT_NO_THROW(m_middleTier.user->setDirStorageClass(requester, "/",
+    storageClassName));
+
+  ASSERT_THROW(m_middleTier.admin->deleteStorageClass(requester, storageClassName),
+    std::exception);
+
+  {
+    std::list<StorageClass> storageClasses;
+    ASSERT_NO_THROW(storageClasses = m_middleTier.admin->getStorageClasses(requester));
+    ASSERT_EQ(1, storageClasses.size());
+
+    StorageClass storageClass;
+    ASSERT_NO_THROW(storageClass = storageClasses.front());
+    ASSERT_EQ(storageClassName, storageClass.getName());
+    ASSERT_EQ(nbCopies, storageClass.getNbCopies());
+  }
+
+  ASSERT_NO_THROW(m_middleTier.user->clearDirStorageClass(requester, "/"));
+
+  ASSERT_NO_THROW(m_middleTier.admin->deleteStorageClass(requester, storageClassName));
+
+  {
+    std::list<StorageClass> storageClasses;
+    ASSERT_NO_THROW(storageClasses = m_middleTier.admin->getStorageClasses(requester));
+    ASSERT_TRUE(storageClasses.empty());
+  }
+}
+
+TEST_P(MiddleTierAdminAbstractTest, deleteStorageClass_in_use_by_route) {
+  using namespace cta;
+
+  const SecurityIdentity requester;
+
+  {
+    std::list<StorageClass> storageClasses;
+    ASSERT_NO_THROW(storageClasses = m_middleTier.admin->getStorageClasses(requester));
+    ASSERT_TRUE(storageClasses.empty());
+  }
+
+  const std::string storageClassName = "TestStorageClass";
+  const uint16_t nbCopies = 2;
+  const std::string comment = "Comment";
+  ASSERT_NO_THROW(m_middleTier.admin->createStorageClass(requester, storageClassName, nbCopies,
+    comment));
+
+  {
+    std::list<StorageClass> storageClasses;
+    ASSERT_NO_THROW(storageClasses = m_middleTier.admin->getStorageClasses(requester));
+    ASSERT_EQ(1, storageClasses.size());
+
+    StorageClass storageClass;
+    ASSERT_NO_THROW(storageClass = storageClasses.front());
+    ASSERT_EQ(storageClassName, storageClass.getName());
+    ASSERT_EQ(nbCopies, storageClass.getNbCopies());
+  }
+
+  const std::string tapePoolName = "TestTapePool";
+  const uint16_t nbPartialTapes = 1;
+  ASSERT_NO_THROW(m_middleTier.admin->createTapePool(requester, tapePoolName,
+    nbPartialTapes, comment));
+
+  {
+    std::list<TapePool> tapePools;
+    ASSERT_NO_THROW(tapePools = m_middleTier.admin->getTapePools(requester));
+    ASSERT_EQ(1, tapePools.size());
+
+    TapePool tapePool;
+    ASSERT_NO_THROW(tapePool = tapePools.front());
+    ASSERT_EQ(tapePoolName, tapePool.getName());
+  }
+
+  const uint16_t copyNb = 1;
+  ASSERT_NO_THROW(m_middleTier.admin->createArchivalRoute(requester, storageClassName,
+    copyNb, tapePoolName, comment));
+
+  {
+    std::list<ArchivalRoute> archivalRoutes;
+    ASSERT_NO_THROW(archivalRoutes = m_middleTier.admin->getArchivalRoutes(requester));
+    ASSERT_EQ(1, archivalRoutes.size());
+
+    ArchivalRoute archivalRoute;
+    ASSERT_NO_THROW(archivalRoute = archivalRoutes.front());
+    ASSERT_EQ(storageClassName, archivalRoute.getStorageClassName());
+    ASSERT_EQ(copyNb, archivalRoute.getCopyNb());
+    ASSERT_EQ(tapePoolName, archivalRoute.getTapePoolName());
+  }
+
+  ASSERT_THROW(m_middleTier.admin->deleteStorageClass(requester, storageClassName),
+    std::exception);
+
+  {
+    std::list<StorageClass> storageClasses;
+    ASSERT_NO_THROW(storageClasses = m_middleTier.admin->getStorageClasses(requester));
+    ASSERT_EQ(1, storageClasses.size());
+
+    StorageClass storageClass;
+    ASSERT_NO_THROW(storageClass = storageClasses.front());
+    ASSERT_EQ(storageClassName, storageClass.getName());
+    ASSERT_EQ(nbCopies, storageClass.getNbCopies());
+  }
+
+  ASSERT_NO_THROW(m_middleTier.admin->deleteArchivalRoute(requester, storageClassName,
+    copyNb));
+
+  {
+    std::list<ArchivalRoute> archivalRoutes;
+    ASSERT_NO_THROW(archivalRoutes = m_middleTier.admin->getArchivalRoutes(requester));
+    ASSERT_TRUE(archivalRoutes.empty());
+  }
+
+  ASSERT_NO_THROW(m_middleTier.admin->deleteStorageClass(requester, storageClassName));
+
+  {
+    std::list<StorageClass> storageClasses;
+    ASSERT_NO_THROW(storageClasses = m_middleTier.admin->getStorageClasses(requester));
+    ASSERT_TRUE(storageClasses.empty());
+  }
+}
+
+TEST_P(MiddleTierAdminAbstractTest, deleteStorageClass_non_existing) {
+  using namespace cta;
+
+  const SecurityIdentity requester;
+
+  {
+    std::list<StorageClass> storageClasses;
+    ASSERT_NO_THROW(storageClasses = m_middleTier.admin->getStorageClasses(requester));
+    ASSERT_TRUE(storageClasses.empty());
+  }
+
+  const std::string name = "TestStorageClass";
+  ASSERT_THROW(m_middleTier.admin->deleteStorageClass(requester, name), std::exception);
+
+  {
+    std::list<StorageClass> storageClasses;
+    ASSERT_NO_THROW(storageClasses = m_middleTier.admin->getStorageClasses(requester));
+    ASSERT_TRUE(storageClasses.empty());
+  }
+}
+
+TEST_P(MiddleTierAdminAbstractTest, deleteTapePool_in_use) {
+  using namespace cta;
+
+  const SecurityIdentity requester;
+
+  {
+    std::list<ArchivalRoute> archivalRoutes;
+    ASSERT_NO_THROW(archivalRoutes = m_middleTier.admin->getArchivalRoutes(requester));
+    ASSERT_TRUE(archivalRoutes.empty());
+  }
+
+  const std::string storageClassName = "TestStorageClass";
+  const std::string comment = "Comment";
+  {
+    const uint16_t nbCopies = 2;
+    ASSERT_NO_THROW(m_middleTier.admin->createStorageClass(requester, storageClassName,
+      nbCopies, comment));
+  }
+
+  const std::string tapePoolName = "TestTapePool";
+  const uint16_t nbPartialTapes = 1;
+  ASSERT_NO_THROW(m_middleTier.admin->createTapePool(requester, tapePoolName,
+    nbPartialTapes, comment));
+
+  const uint16_t copyNb = 1;
+  ASSERT_NO_THROW(m_middleTier.admin->createArchivalRoute(requester, storageClassName,
+    copyNb, tapePoolName, comment));
+
+  {
+    std::list<ArchivalRoute> archivalRoutes;
+    ASSERT_NO_THROW(archivalRoutes = m_middleTier.admin->getArchivalRoutes(requester));
+    ASSERT_EQ(1, archivalRoutes.size());
+
+    ArchivalRoute archivalRoute;
+    ASSERT_NO_THROW(archivalRoute = archivalRoutes.front());
+    ASSERT_EQ(storageClassName, archivalRoute.getStorageClassName());
+    ASSERT_EQ(copyNb, archivalRoute.getCopyNb());
+    ASSERT_EQ(tapePoolName, archivalRoute.getTapePoolName());
+  }
+
+  ASSERT_THROW(m_middleTier.admin->deleteTapePool(requester, tapePoolName), std::exception);
+
+  {
+    std::list<ArchivalRoute> archivalRoutes;
+    ASSERT_NO_THROW(archivalRoutes = m_middleTier.admin->getArchivalRoutes(requester));
+    ASSERT_EQ(1, archivalRoutes.size());
+
+    ArchivalRoute archivalRoute;
+    ASSERT_NO_THROW(archivalRoute = archivalRoutes.front());
+    ASSERT_EQ(storageClassName, archivalRoute.getStorageClassName());
+    ASSERT_EQ(copyNb, archivalRoute.getCopyNb());
+    ASSERT_EQ(tapePoolName, archivalRoute.getTapePoolName());
+  }
+}
+
+TEST_P(MiddleTierAdminAbstractTest, createArchivalRoute_new) {
+  using namespace cta;
+
+  const SecurityIdentity requester;
+
+  {
+    std::list<ArchivalRoute> archivalRoutes;
+    ASSERT_NO_THROW(archivalRoutes = m_middleTier.admin->getArchivalRoutes(requester));
+    ASSERT_TRUE(archivalRoutes.empty());
+  }
+
+  const std::string storageClassName = "TestStorageClass";
+  const std::string comment = "Comment";
+  {
+    const uint16_t nbCopies = 2;
+    ASSERT_NO_THROW(m_middleTier.admin->createStorageClass(requester, storageClassName,
+      nbCopies, comment));
+  }
+
+  const std::string tapePoolName = "TestTapePool";
+  const uint16_t nbPartialTapes = 1;
+  ASSERT_NO_THROW(m_middleTier.admin->createTapePool(requester, tapePoolName,
+    nbPartialTapes, comment));
+
+  const uint16_t copyNb = 1;
+  ASSERT_NO_THROW(m_middleTier.admin->createArchivalRoute(requester, storageClassName,
+    copyNb, tapePoolName, comment));
+
+  {
+    std::list<ArchivalRoute> archivalRoutes;
+    ASSERT_NO_THROW(archivalRoutes = m_middleTier.admin->getArchivalRoutes(requester));
+    ASSERT_EQ(1, archivalRoutes.size());
+
+    ArchivalRoute archivalRoute;
+    ASSERT_NO_THROW(archivalRoute = archivalRoutes.front());
+    ASSERT_EQ(storageClassName, archivalRoute.getStorageClassName());
+    ASSERT_EQ(copyNb, archivalRoute.getCopyNb());
+    ASSERT_EQ(tapePoolName, archivalRoute.getTapePoolName());
+  }
+}
+
+TEST_P(MiddleTierAdminAbstractTest,
+  createArchivalRoute_already_existing) {
+  using namespace cta;
+
+  const SecurityIdentity requester;
+
+  {
+    std::list<ArchivalRoute> archivalRoutes;
+    ASSERT_NO_THROW(archivalRoutes = m_middleTier.admin->getArchivalRoutes(requester));
+    ASSERT_TRUE(archivalRoutes.empty());
+  }
+
+  const std::string storageClassName = "TestStorageClass";
+  const std::string comment = "Comment";
+  {
+    const uint16_t nbCopies = 2;
+    ASSERT_NO_THROW(m_middleTier.admin->createStorageClass(requester, storageClassName,
+      nbCopies, comment));
+  }
+
+  const std::string tapePoolName = "TestTapePool";
+  const uint16_t nbPartialTapes = 1;
+  ASSERT_NO_THROW(m_middleTier.admin->createTapePool(requester, tapePoolName,
+    nbPartialTapes, comment));
+
+  const uint16_t copyNb = 1;
+  ASSERT_NO_THROW(m_middleTier.admin->createArchivalRoute(requester, storageClassName,
+    copyNb, tapePoolName, comment));
+
+  {
+    std::list<ArchivalRoute> archivalRoutes;
+    ASSERT_NO_THROW(archivalRoutes = m_middleTier.admin->getArchivalRoutes(requester));
+    ASSERT_EQ(1, archivalRoutes.size());
+
+    ArchivalRoute archivalRoute;
+    ASSERT_NO_THROW(archivalRoute = archivalRoutes.front());
+    ASSERT_EQ(storageClassName, archivalRoute.getStorageClassName());
+    ASSERT_EQ(copyNb, archivalRoute.getCopyNb());
+    ASSERT_EQ(tapePoolName, archivalRoute.getTapePoolName());
+  }
+
+  ASSERT_THROW(m_middleTier.admin->createArchivalRoute(requester, storageClassName,
+    copyNb, tapePoolName, comment), std::exception);
+}
+
+TEST_P(MiddleTierAdminAbstractTest, deleteArchivalRoute_existing) {
+  using namespace cta;
+
+  const SecurityIdentity requester;
+
+  {
+    std::list<ArchivalRoute> archivalRoutes;
+    ASSERT_NO_THROW(archivalRoutes = m_middleTier.admin->getArchivalRoutes(requester));
+    ASSERT_TRUE(archivalRoutes.empty());
+  }
+
+  const std::string storageClassName = "TestStorageClass";
+  const std::string comment = "Comment";
+  {
+    const uint16_t nbCopies = 2;
+    ASSERT_NO_THROW(m_middleTier.admin->createStorageClass(requester, storageClassName,
+      nbCopies, comment));
+  }
+
+  const std::string tapePoolName = "TestTapePool";
+  const uint16_t nbPartialTapes = 1;
+  ASSERT_NO_THROW(m_middleTier.admin->createTapePool(requester, tapePoolName,
+    nbPartialTapes, comment));
+
+  const uint16_t copyNb = 1;
+  ASSERT_NO_THROW(m_middleTier.admin->createArchivalRoute(requester, storageClassName,
+    copyNb, tapePoolName, comment));
+
+  {
+    std::list<ArchivalRoute> archivalRoutes;
+    ASSERT_NO_THROW(archivalRoutes = m_middleTier.admin->getArchivalRoutes(requester));
+    ASSERT_EQ(1, archivalRoutes.size());
+
+    ArchivalRoute archivalRoute;
+    ASSERT_NO_THROW(archivalRoute = archivalRoutes.front());
+    ASSERT_EQ(storageClassName, archivalRoute.getStorageClassName());
+    ASSERT_EQ(copyNb, archivalRoute.getCopyNb());
+    ASSERT_EQ(tapePoolName, archivalRoute.getTapePoolName());
+  }
+
+  ASSERT_NO_THROW(m_middleTier.admin->deleteArchivalRoute(requester, storageClassName,
+    copyNb));
+
+  {
+    std::list<ArchivalRoute> archivalRoutes;
+    ASSERT_NO_THROW(archivalRoutes = m_middleTier.admin->getArchivalRoutes(requester));
+    ASSERT_TRUE(archivalRoutes.empty());
+  }
+}
+
+TEST_P(MiddleTierAdminAbstractTest, deleteArchivalRoute_non_existing) {
+  using namespace cta;
+
+  const SecurityIdentity requester;
+
+  {
+    std::list<ArchivalRoute> archivalRoutes;
+    ASSERT_NO_THROW(archivalRoutes = m_middleTier.admin->getArchivalRoutes(requester));
+    ASSERT_TRUE(archivalRoutes.empty());
+  }
+
+  const std::string storageClassName = "TestStorageClass";
+  const std::string comment = "Comment";
+  {
+    const uint16_t nbCopies = 2;
+    ASSERT_NO_THROW(m_middleTier.admin->createStorageClass(requester, storageClassName,
+      nbCopies, comment));
+  }
+
+  const std::string tapePoolName = "TestTapePool";
+  const uint16_t nbPartialTapes = 1;
+  ASSERT_NO_THROW(m_middleTier.admin->createTapePool(requester, tapePoolName,
+    nbPartialTapes, comment));
+
+  const uint16_t copyNb = 1;
+  ASSERT_THROW(m_middleTier.admin->deleteArchivalRoute(requester, tapePoolName, copyNb),
+    std::exception);
+}
+
+TEST_P(MiddleTierAdminAbstractTest, createTape_new) {
+  using namespace cta;
+
+  const SecurityIdentity requester;
+
+  {
+    std::list<LogicalLibrary> libraries;
+    ASSERT_NO_THROW(libraries = m_middleTier.admin->getLogicalLibraries(requester));
+    ASSERT_TRUE(libraries.empty());
+  }
+
+  {
+    std::list<TapePool> pools;
+    ASSERT_NO_THROW(pools = m_middleTier.admin->getTapePools(requester));
+    ASSERT_TRUE(pools.empty());
+  }
+
+  {
+    std::list<Tape> tapes;
+    ASSERT_NO_THROW(tapes = m_middleTier.admin->getTapes(requester));
+    ASSERT_TRUE(tapes.empty());
+  }
+
+  const std::string libraryName = "TestLogicalLibrary";
+  const std::string libraryComment = "Library comment";
+  ASSERT_NO_THROW(m_middleTier.admin->createLogicalLibrary(requester, libraryName,
+    libraryComment));
+  {
+    std::list<LogicalLibrary> libraries;
+    ASSERT_NO_THROW(libraries = m_middleTier.admin->getLogicalLibraries(requester));
+    ASSERT_EQ(1, libraries.size());
+  
+    LogicalLibrary logicalLibrary;
+    ASSERT_NO_THROW(logicalLibrary = libraries.front());
+    ASSERT_EQ(libraryName, logicalLibrary.getName());
+    ASSERT_EQ(libraryComment, logicalLibrary.getComment());
+  }
+
+  const std::string tapePoolName = "TestTapePool";
+  const uint16_t nbPartialTapes = 1;
+  const std::string comment = "Tape pool omment";
+  ASSERT_NO_THROW(m_middleTier.admin->createTapePool(requester, tapePoolName,
+    nbPartialTapes, comment));
+  {
+    std::list<TapePool> tapePools;
+    ASSERT_NO_THROW(tapePools = m_middleTier.admin->getTapePools(requester));
+    ASSERT_EQ(1, tapePools.size());
+    
+    TapePool tapePool;
+    ASSERT_NO_THROW(tapePool = tapePools.front());
+    ASSERT_EQ(tapePoolName, tapePool.getName());
+    ASSERT_EQ(comment, tapePool.getComment());
+  } 
+
+  const std::string vid = "TestVid";
+  const uint64_t capacityInBytes = 12345678;
+  const std::string tapeComment = "Tape comment";
+  ASSERT_NO_THROW(m_middleTier.admin->createTape(requester, vid, libraryName, tapePoolName,
+    capacityInBytes, tapeComment));
+  {
+    std::list<Tape> tapes;
+    ASSERT_NO_THROW(tapes = m_middleTier.admin->getTapes(requester));
+    ASSERT_EQ(1, tapes.size()); 
+  
+    Tape tape;
+    ASSERT_NO_THROW(tape = tapes.front());
+    ASSERT_EQ(vid, tape.getVid());
+    ASSERT_EQ(libraryName, tape.getLogicalLibraryName());
+    ASSERT_EQ(tapePoolName, tape.getTapePoolName());
+    ASSERT_EQ(capacityInBytes, tape.getCapacityInBytes());
+    ASSERT_EQ(0, tape.getDataOnTapeInBytes());
+    ASSERT_EQ(tapeComment, tape.getComment());
+  } 
+}
+
+TEST_P(MiddleTierAdminAbstractTest,
+  createTape_new_non_existing_library) {
+  using namespace cta;
+
+  const SecurityIdentity requester;
+
+  {
+    std::list<LogicalLibrary> libraries;
+    ASSERT_NO_THROW(libraries = m_middleTier.admin->getLogicalLibraries(requester));
+    ASSERT_TRUE(libraries.empty());
+  }
+
+  {
+    std::list<TapePool> pools;
+    ASSERT_NO_THROW(pools = m_middleTier.admin->getTapePools(requester));
+    ASSERT_TRUE(pools.empty());
+  }
+
+  {
+    std::list<Tape> tapes;
+    ASSERT_NO_THROW(tapes = m_middleTier.admin->getTapes(requester));
+    ASSERT_TRUE(tapes.empty());
+  }
+
+  const std::string libraryName = "TestLogicalLibrary";
+
+  const std::string tapePoolName = "TestTapePool";
+  const uint16_t nbPartialTapes = 1;
+  const std::string comment = "Tape pool omment";
+  ASSERT_NO_THROW(m_middleTier.admin->createTapePool(requester, tapePoolName,
+    nbPartialTapes, comment));
+  {
+    std::list<TapePool> tapePools;
+    ASSERT_NO_THROW(tapePools = m_middleTier.admin->getTapePools(requester));
+    ASSERT_EQ(1, tapePools.size());
+    
+    TapePool tapePool;
+    ASSERT_NO_THROW(tapePool = tapePools.front());
+    ASSERT_EQ(tapePoolName, tapePool.getName());
+    ASSERT_EQ(comment, tapePool.getComment());
+  } 
+
+  const std::string vid = "TestVid";
+  const uint64_t capacityInBytes = 12345678;
+  const std::string tapeComment = "Tape comment";
+  ASSERT_THROW(m_middleTier.admin->createTape(requester, vid, libraryName, tapePoolName,
+    capacityInBytes, tapeComment), std::exception);
+}
+
+TEST_P(MiddleTierAdminAbstractTest, createTape_new_non_existing_pool) {
+  using namespace cta;
+
+  const SecurityIdentity requester;
+
+  {
+    std::list<LogicalLibrary> libraries;
+    ASSERT_NO_THROW(libraries = m_middleTier.admin->getLogicalLibraries(requester));
+    ASSERT_TRUE(libraries.empty());
+  }
+
+  {
+    std::list<TapePool> pools;
+    ASSERT_NO_THROW(pools = m_middleTier.admin->getTapePools(requester));
+    ASSERT_TRUE(pools.empty());
+  }
+
+  {
+    std::list<Tape> tapes;
+    ASSERT_NO_THROW(tapes = m_middleTier.admin->getTapes(requester));
+    ASSERT_TRUE(tapes.empty());
+  }
+
+  const std::string libraryName = "TestLogicalLibrary";
+  const std::string libraryComment = "Library comment";
+  ASSERT_NO_THROW(m_middleTier.admin->createLogicalLibrary(requester, libraryName,
+    libraryComment));
+  {
+    std::list<LogicalLibrary> libraries;
+    ASSERT_NO_THROW(libraries = m_middleTier.admin->getLogicalLibraries(requester));
+    ASSERT_EQ(1, libraries.size());
+  
+    LogicalLibrary logicalLibrary;
+    ASSERT_NO_THROW(logicalLibrary = libraries.front());
+    ASSERT_EQ(libraryName, logicalLibrary.getName());
+    ASSERT_EQ(libraryComment, logicalLibrary.getComment());
+  }
+
+  const std::string tapePoolName = "TestTapePool";
+
+  const std::string vid = "TestVid";
+  const uint64_t capacityInBytes = 12345678;
+  const std::string tapeComment = "Tape comment";
+  ASSERT_THROW(m_middleTier.admin->createTape(requester, vid, libraryName, tapePoolName,
+    capacityInBytes, tapeComment), std::exception);
+}
+
+} // namespace unitTests
diff --git a/objectstore_middletier/MiddleTierAdminAbstractTest.hpp b/objectstore_middletier/MiddleTierAdminAbstractTest.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..3097dba548244ab1a21634087944164523383b67
--- /dev/null
+++ b/objectstore_middletier/MiddleTierAdminAbstractTest.hpp
@@ -0,0 +1,28 @@
+#pragma once
+
+#include <gtest/gtest.h>
+#include "cta/MiddleTierAdmin.hpp"
+#include "cta/MiddleTierUser.hpp"
+
+namespace unitTests {
+  
+  class MiddleTierFull {
+  public:
+    cta::MiddleTierAdmin * admin;
+    cta::MiddleTierUser * user;
+    MiddleTierFull(cta::MiddleTierAdmin * a, cta::MiddleTierUser * u):
+      admin(a), user(u) {}
+    MiddleTierFull(): admin(NULL), user(NULL) {}
+  };
+
+class MiddleTierAdminAbstractTest: public ::testing::TestWithParam<MiddleTierFull> {
+protected:
+  MiddleTierAdminAbstractTest() {}
+  virtual void SetUp() {
+    m_middleTier = GetParam();
+  }
+  MiddleTierFull m_middleTier;
+};
+
+}
+
diff --git a/objectstore_middletier/MiddleTierTest.cpp b/objectstore_middletier/MiddleTierTest.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0970ca342f4c2cda72f07937350e6f8a13220b85
--- /dev/null
+++ b/objectstore_middletier/MiddleTierTest.cpp
@@ -0,0 +1,55 @@
+/*
+ * The CERN Tape Archive (CTA) project
+ * Copyright (C) 2015  CERN
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "MiddleTierAdminAbstractTest.hpp"
+#include "ObjectStoreMiddleTierAdmin.hpp"
+#include "objectstore/BackendRados.hpp"
+#include "objectstore/BackendVFS.hpp"
+#include "middletier/cta/SqliteMiddleTierAdmin.hpp"
+
+
+#define TEST_RADOS 0
+#define TEST_VFS 0
+#define TEST_SQL 1
+
+namespace unitTests {
+
+#if TEST_RADOS
+cta::objectstore::BackendRados osRados("tapetest", "tapetest");
+cta::OStoreMiddleTierAdmin mtaRados(osRados);
+unitTests::MiddleTierFull middleTierRados;
+middleTierRados.admin = &mtaRados;
+middleTierRados.user = NULL;
+
+INSTANTIATE_TEST_CASE_P(MiddleTierRados, MiddleTierAdminAbstractTest , ::testing::Values(middleTierRados));
+#endif
+
+#if TEST_VFS
+cta::objectstore::BackendVFS osVFS;
+
+#endif
+
+#if TEST_SQL
+cta::SqliteDatabase sqlite;
+cta::Vfs vfs;
+cta::SqliteMiddleTierAdmin mtaSQL(vfs, sqlite);
+MiddleTierFull mtfSQL(&mtaSQL, NULL);
+INSTANTIATE_TEST_CASE_P(MiddleTierSQL, MiddleTierAdminAbstractTest, ::testing::Values(mtfSQL));
+#endif
+
+}
\ No newline at end of file
diff --git a/objectstore_middletier/ObjectStoreMiddleTierAdmin.cpp b/objectstore_middletier/ObjectStoreMiddleTierAdmin.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..47eb1317f6bfe4848536faad409227decc2e3d5a
--- /dev/null
+++ b/objectstore_middletier/ObjectStoreMiddleTierAdmin.cpp
@@ -0,0 +1,65 @@
+/*
+ * The CERN Tape Archive (CTA) project
+ * Copyright (C) 2015  CERN
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "cta/Exception.hpp"
+#include "ObjectStoreMiddleTierAdmin.hpp"
+#include "objectstore/Backend.hpp"
+#include "objectstore/RootEntry.hpp"
+#include "objectstore/AdminUsersList.hpp"
+
+namespace cta {
+
+//------------------------------------------------------------------------------
+// constructor
+//------------------------------------------------------------------------------
+OStoreMiddleTierAdmin::OStoreMiddleTierAdmin(objectstore::Backend& backend):
+  m_backend(backend) {
+  // check that we can at least access the root entry
+  objectstore::RootEntry re(m_backend);
+  objectstore::ScopedSharedLock reLock(re);
+  re.fetch();
+}
+
+//------------------------------------------------------------------------------
+// destructor
+//------------------------------------------------------------------------------
+OStoreMiddleTierAdmin::~OStoreMiddleTierAdmin() throw() {
+}
+
+//------------------------------------------------------------------------------
+// createAdminUser
+//------------------------------------------------------------------------------
+void OStoreMiddleTierAdmin::createAdminUser(
+  const SecurityIdentity &requester,
+  const UserIdentity &user,
+  const std::string &comment) {
+  // Authz is not handled in this layer. We hence store the new admin user
+  // without checks.
+  objectstore::RootEntry re(m_backend);
+  objectstore::ScopedSharedLock reLock(re);
+  re.fetch();
+  objectstore::AdminUsersList aul(re.getAdminUsersList(), m_backend);
+  reLock.release();
+  objectstore::ScopedExclusiveLock aulLock(aul);
+  aul.fetch();
+  AdminUser au(user, requester.user, comment);
+  aul.add(au);
+  aul.commit();
+}
+
+}
diff --git a/objectstore_middletier/ObjectStoreMiddleTierAdmin.hpp b/objectstore_middletier/ObjectStoreMiddleTierAdmin.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..80f4a7a11078f360c74b0bafc23603944407bc2e
--- /dev/null
+++ b/objectstore_middletier/ObjectStoreMiddleTierAdmin.hpp
@@ -0,0 +1,311 @@
+/*
+ * The CERN Tape Archive (CTA) project
+ * Copyright (C) 2015  CERN
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "cta/MiddleTierAdmin.hpp"
+
+namespace cta {
+
+namespace objectstore {
+  class Backend;
+}
+  
+/**
+ * The administration API of the the middle-tier.
+ * ObjectStore based implementation
+ */
+class OStoreMiddleTierAdmin: public MiddleTierAdmin {
+public:
+  /**
+   * Contructor
+   */
+  OStoreMiddleTierAdmin(objectstore::Backend & backend);
+  
+  /**
+   * Destructor
+   */
+  virtual ~OStoreMiddleTierAdmin() throw();
+
+  /**
+   * Creates the specified administrator.
+   *
+   * @param requester The identity of the user requesting the creation of the
+   * administrator.
+   * @param user The identity of the administrator.
+   * @param comment The comment describing the sministrator.
+   */
+  virtual void createAdminUser(
+    const SecurityIdentity &requester,
+    const UserIdentity &user,
+    const std::string &comment);
+
+  /**
+   * Deletes the specified administrator.
+   *
+   * @param requester The identity of the user requesting the deletion of the
+   * administrator.
+   * @param user The identity of the administrator.
+   */
+  virtual void deleteAdminUser(
+    const SecurityIdentity &requester,
+    const UserIdentity &user);
+
+  /**
+   * Returns the current list of administrators in lexicographical order.
+   *
+   * @param requester The identity of the user requesting the list.
+   * @return The current list of administrators in lexicographical order.
+   */
+  virtual std::list<AdminUser> getAdminUsers(const SecurityIdentity &requester)
+   const;
+
+  /**
+   * Creates the specified administration host.
+   *
+   * @param requester The identity of the user requesting the creation of the
+   * administration host.
+   * @param hostName The network name of the administration host.
+   * @param comment The comment describing the administration host.
+   */
+  virtual void createAdminHost(
+    const SecurityIdentity &requester,
+    const std::string &hostName,
+    const std::string &comment);
+
+  /**
+   * Deletes the specified administration host.
+   *
+   * @param requester The identity of the user requesting the deletion of the
+   * administration host.
+   * @param hostName The network name of the administration host.
+   * @param comment The comment describing the administration host.
+   */
+  virtual void deleteAdminHost(
+    const SecurityIdentity &requester,
+    const std::string &hostName);
+
+  /**
+   * Returns the current list of administration hosts in lexicographical order.
+   *
+   * @param requester The identity of the user requesting the list.
+   * @return The current list of administration hosts in lexicographical order.
+   */
+  virtual std::list<AdminHost> getAdminHosts(const SecurityIdentity &requester)
+   const;
+
+  /**
+   * Creates the specified storage class.
+   *
+   * @param requester The identity of the user requesting the creation of the
+   * storage class.
+   * @param name The name of the storage class.
+   * @param nbCopies The number of copies a file associated with this storage
+   * class should have on tape.
+   * @param comment The comment describing the storage class.
+   */
+  virtual void createStorageClass(
+    const SecurityIdentity &requester,
+    const std::string &name,
+    const uint8_t nbCopies,
+    const std::string &comment);
+
+  /**
+   * Deletes the specified storage class.
+   *
+   * @param requester The identity of the user requesting the deletion of the
+   * storage class.
+   * @param name The name of the storage class.
+   */
+  virtual void deleteStorageClass(
+    const SecurityIdentity &requester,
+    const std::string &name);
+
+  /**
+   * Gets the current list of storage classes in lexicographical order.
+   *
+   * @param requester The identity of the user requesting the list.
+   * @return The current list of storage classes in lexicographical order.
+   */
+  virtual std::list<StorageClass> getStorageClasses(
+    const SecurityIdentity &requester) const;
+
+  /**
+   * Creates a tape pool with the specifed name.
+   *
+   * @param requester The identity of the user requesting the creation of the
+   * tape pool.
+   * @param name The name of the tape pool.
+   * @param nbDrives The maximum number of drives that can be concurrently
+   * assigned to this pool independent of whether they are archiving or
+   * retrieving files.
+   * @param nbPartialTapes The maximum number of tapes that can be partially
+   * full at any moment in time.
+   * @param comment The comment describing the tape pool.
+   */
+  virtual void createTapePool(
+    const SecurityIdentity &requester,
+    const std::string &name,
+    const uint16_t nbDrives,
+    const uint32_t nbPartialTapes,
+    const std::string &comment);
+
+  /**
+   * Delete the tape pool with the specifed name.
+   *
+   * @param requester The identity of the user requesting the deletion of the
+   * tape pool.
+   * @param name The name of the tape pool.
+   */
+  virtual void deleteTapePool(
+    const SecurityIdentity &requester,
+    const std::string &name);
+
+  /**
+   * Gets the current list of tape pools in lexicographical order.
+   *
+   * @param requester The identity of the user requesting the list.
+   * @return The current list of tape pools in lexicographical order.
+   */
+  virtual std::list<TapePool> getTapePools(
+    const SecurityIdentity &requester) const;
+
+  /**
+   * Creates the specified archive route.
+   *
+   * @param requester The identity of the user requesting the creation of the
+   * archive route.
+   * @param storageClassName The name of the storage class that identifies the
+   * source disk files.
+   * @param copyNb The tape copy number.
+   * @param tapePoolName The name of the destination tape pool.
+   * @param comment The comment describing the archive route.
+   */
+  virtual void createArchivalRoute(
+    const SecurityIdentity &requester,
+    const std::string &storageClassName,
+    const uint8_t copyNb,
+    const std::string &tapePoolName,
+    const std::string &comment);
+
+  /**
+   * Deletes the specified archive route.
+   *
+   * @param requester The identity of the user requesting the deletion of the
+   * archive route.
+   * @param storageClassName The name of the storage class that identifies the
+   * source disk files.
+   * @param copyNb The tape copy number.
+   */
+  virtual void deleteArchivalRoute(
+    const SecurityIdentity &requester,
+    const std::string &storageClassName,
+    const uint8_t copyNb);
+
+  /**
+   * Gets the current list of archive routes.
+   *
+   * @param requester The identity of the user requesting the list.
+   */
+  virtual std::list<ArchivalRoute> getArchivalRoutes(
+    const SecurityIdentity &requester) const;
+
+  /**
+   * Creates a logical library with the specified name.
+   *
+   * @param requester The identity of the user requesting the creation of the
+   * logical library.
+   * @param name The name of the logical library.
+   * @param comment The comment describing the logical library.
+   */
+  virtual void createLogicalLibrary(
+    const SecurityIdentity &requester,
+    const std::string &name,
+    const std::string &comment);
+
+  /**
+   * Deletes the logical library with the specified name.
+   *
+   * @param requester The identity of the user requesting the deletion of the
+   * logical library.
+   * @param name The name of the logical library.
+   */
+  virtual void deleteLogicalLibrary(
+    const SecurityIdentity &requester,
+    const std::string &name);
+
+  /**
+   * Returns the current list of libraries in lexicographical order.
+   *
+   * @param requester The identity of the user requesting the list.
+   * @return The current list of libraries in lexicographical order.
+   */
+  virtual std::list<LogicalLibrary> getLogicalLibraries(
+    const SecurityIdentity &requester) const;
+
+  /**
+   * Creates a tape.
+   *
+   * @param requester The identity of the user requesting the creation of the
+   * tape.
+   * @param vid The volume identifier of the tape.
+   * @param logicalLibrary The name of the logical library to which the tape
+   * belongs.
+   * @param tapePoolName The name of the tape pool to which the tape belongs.
+   * @param capacityInBytes The capacity of the tape.
+   * @param comment The comment describing the logical library.
+   */
+  virtual void createTape(
+    const SecurityIdentity &requester,
+    const std::string &vid,
+    const std::string &logicalLibraryName,
+    const std::string &tapePoolName,
+    const uint64_t capacityInBytes,
+    const std::string &comment);
+
+  /**
+   * Deletes the tape with the specified volume identifier.
+   *
+   * @param requester The identity of the user requesting the deletion of the
+   * tape.
+   * @param vid The volume identifier of the tape.
+   */
+  virtual void deleteTape(
+    const SecurityIdentity &requester,
+    const std::string &vid);
+
+  /**
+   * Returns the current list of tapes in the lexicographical order of their
+   * volume identifiers.
+   *
+   * @param requester The identity of the user requesting the list.
+   * @return The current list of tapes in the lexicographical order of their
+   * volume identifiers.
+   */
+  virtual std::list<Tape> getTapes(
+    const SecurityIdentity &requester) const;
+  
+private:
+  /**
+   * Reference to the backend used for storing objects
+   */
+  objectstore::Backend & m_backend;
+};
+
+}
+
diff --git a/objectstore_middletier/unit_tests.cpp b/objectstore_middletier/unit_tests.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..602c1b51ec664c2e41a52303d28dc91e1a06f0c2
--- /dev/null
+++ b/objectstore_middletier/unit_tests.cpp
@@ -0,0 +1,37 @@
+/*
+ * The CERN Tape Archive (CTA) project
+ * Copyright (C) 2015  CERN
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+
+int main(int argc, char** argv) {
+  // The following line must be executed to initialize Google Mock
+  // (and Google Test) before running the tests.
+  ::testing::InitGoogleMock(&argc, argv);
+  int ret = RUN_ALL_TESTS();
+
+  // Close standard in, out and error so that valgrind can be used with the
+  // following command-line to track open file-descriptors:
+  //
+  //     valgrind --track-fds=yes
+  close(0);
+  close(1);
+  close(2);
+
+  return ret;
+}