diff --git a/lombok.config b/lombok.config
new file mode 100644
index 0000000000000000000000000000000000000000..f202d3031e2c69a6aa27c2cda1ad9fe29c690889
--- /dev/null
+++ b/lombok.config
@@ -0,0 +1,4 @@
+# tells Lombok that this is the root directory and that it shouldn’t search parent directories for more configuration files
+config.stopBubbling = true
+# tells Lombok to add @lombok.Generated annotation to all generated methods
+lombok.addLombokGeneratedAnnotation = true
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 9baf244d9b0038a664e3a2e2daeba27b7d4b3eca..5652f0fb061e960d85468c929295018d46978d7f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,10 +3,10 @@
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
 
-	<groupId>org.geant</groupId>
-	<artifactId>looking-glass-service</artifactId>
-	<version>2.0.14-SNAPSHOT</version>
-	<packaging>jar</packaging>
+    <groupId>org.geant</groupId>
+    <artifactId>looking-glass-service</artifactId>
+    <version>2.0.14-SNAPSHOT</version>
+    <packaging>jar</packaging>
 
     <name>looking-glass-service</name>
     <description>Looking Glass Demo</description>
@@ -14,7 +14,7 @@
     <parent>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-parent</artifactId>
-        <version>2.0.5.RELEASE</version>
+        <version>2.2.5.RELEASE</version>
         <relativePath/> <!-- lookup parent from repository -->
     </parent>
 
@@ -55,6 +55,11 @@
             <artifactId>spring-boot-starter-test</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-test</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-log4j2</artifactId>
@@ -69,16 +74,6 @@
             <artifactId>spring-security-saml2-core</artifactId>
             <version>1.0.3.RELEASE</version>
         </dependency>
-        <dependency>
-            <groupId>io.springfox</groupId>
-            <artifactId>springfox-swagger2</artifactId>
-            <version>2.7.0</version>
-        </dependency>
-        <dependency>
-            <groupId>io.springfox</groupId>
-            <artifactId>springfox-swagger-ui</artifactId>
-            <version>2.7.0</version>
-        </dependency>
         <dependency>
             <groupId>io.micrometer</groupId>
             <artifactId>micrometer-registry-prometheus</artifactId>
@@ -87,6 +82,50 @@
             <groupId>io.micrometer</groupId>
             <artifactId>micrometer-registry-statsd</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.squareup.retrofit2</groupId>
+            <artifactId>retrofit</artifactId>
+            <version>2.3.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.squareup.retrofit2</groupId>
+            <artifactId>converter-gson</artifactId>
+            <version>2.3.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.squareup.retrofit2</groupId>
+            <artifactId>converter-jackson</artifactId>
+            <version>2.8.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.tngtech.jgiven</groupId>
+            <artifactId>jgiven-junit</artifactId>
+            <version>0.18.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.tngtech.jgiven</groupId>
+            <artifactId>jgiven-spring</artifactId>
+            <version>0.18.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <version>3.14.0</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.alexlovett</groupId>
+            <artifactId>jgiven-extension</artifactId>
+            <version>0.0.1-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>com.github.tomakehurst</groupId>
+            <artifactId>wiremock-jre8</artifactId>
+            <version>2.26.3</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
@@ -106,7 +145,9 @@
                                 <line>After=syslog.target</line>
                                 <line>[Service]</line>
                                 <line>User=${artifactId}</line>
-                                <line>ExecStart=/opt/${artifactId}/latest/bin/${artifactId}.${project.packaging} --spring.config.location=file:/opt/${artifactId}/${version}/config/application.yml</line>
+                                <line>ExecStart=/opt/${artifactId}/latest/bin/${artifactId}.${project.packaging}
+                                    --spring.config.location=file:/opt/${artifactId}/${version}/config/application.yml
+                                </line>
                                 <line>StandardOutput=syslog</line>
                                 <line>StandardError=syslog</line>
                                 <line>SuccessExitStatus=143</line>
@@ -156,13 +197,15 @@
                 <version>0.8.5</version>
                 <executions>
                     <execution>
+                        <id>pre-integration-test</id>
+                        <phase>test-compile</phase>
                         <goals>
                             <goal>prepare-agent</goal>
                         </goals>
                     </execution>
                     <execution>
                         <id>report</id>
-                        <phase>prepare-package</phase>
+                        <phase>post-integration-test</phase>
                         <goals>
                             <goal>report</goal>
                         </goals>
@@ -245,8 +288,11 @@
                                     <ruleset>default</ruleset>
                                 </entry>
                                 <entry>
-                                    <name>/opt/${artifactId}/${version}/bin/${project.build.finalName}.${project.packaging}</name>
-                                    <file>${project.build.directory}/${project.build.finalName}.${project.packaging}</file>
+                                    <name>
+                                        /opt/${artifactId}/${version}/bin/${project.build.finalName}.${project.packaging}
+                                    </name>
+                                    <file>${project.build.directory}/${project.build.finalName}.${project.packaging}
+                                    </file>
                                     <ruleset>default</ruleset>
                                 </entry>
                                 <entry>
@@ -256,7 +302,9 @@
                                 </entry>
                                 <entry>
                                     <name>/opt/${artifactId}/${version}/bin/${artifactId}.${project.packaging}</name>
-                                    <linkTo>/opt/${artifactId}/${version}/bin/${project.build.finalName}.${project.packaging}</linkTo>
+                                    <linkTo>
+                                        /opt/${artifactId}/${version}/bin/${project.build.finalName}.${project.packaging}
+                                    </linkTo>
                                     <ruleset>default</ruleset>
                                 </entry>
                                 <entry>
@@ -269,6 +317,34 @@
                     </execution>
                 </executions>
             </plugin>
+            <plugin>
+                <artifactId>maven-failsafe-plugin</artifactId>
+                <version>2.18.1</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>integration-test</goal>
+                            <goal>verify</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>com.tngtech.jgiven</groupId>
+                <artifactId>jgiven-maven-plugin</artifactId>
+                <version>0.18.2</version>
+                <executions>
+                    <execution>
+                        <phase>post-integration-test</phase>
+                        <goals>
+                            <goal>report</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <format>html</format>
+                </configuration>
+            </plugin>
         </plugins>
     </build>
 
diff --git a/saml/keystore.jks b/saml/keystore.jks
deleted file mode 100644
index 402e7ab6eaa52384af67825a7d9bf9fd26183237..0000000000000000000000000000000000000000
Binary files a/saml/keystore.jks and /dev/null differ
diff --git a/src/main/java/org/geant/lgservice/LookingGlassServiceApplication.java b/src/main/java/org/geant/lgservice/LookingGlassServiceApplication.java
index 2d37ef6559f361750cfab987561033de78b46497..0e3e273e48f9f2339af39553b651762cd9cebcf5 100644
--- a/src/main/java/org/geant/lgservice/LookingGlassServiceApplication.java
+++ b/src/main/java/org/geant/lgservice/LookingGlassServiceApplication.java
@@ -1,32 +1,19 @@
 package org.geant.lgservice;
 
+import org.geant.lgservice.infrastructure.rest.inventoryprovider.Config;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Import;
 
-import springfox.documentation.builders.ApiInfoBuilder;
-import springfox.documentation.builders.RequestHandlerSelectors;
-import springfox.documentation.service.ApiInfo;
-import springfox.documentation.spi.DocumentationType;
-import springfox.documentation.spring.web.plugins.Docket;
-import springfox.documentation.swagger2.annotations.EnableSwagger2;
-
-@EnableSwagger2
 @SpringBootApplication
+@Import({
+		org.geant.lgservice.infrastructure.rest.inventoryprovider.Config.class,
+		org.geant.lgservice.infrastructure.ssh.Config.class
+})
 public class LookingGlassServiceApplication {
 
 	public static void main(String[] args) {
 		SpringApplication.run(LookingGlassServiceApplication.class, args);
 	}
 
-	@Bean
-	public Docket productApi() {
-		return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()
-				.apis(RequestHandlerSelectors.basePackage("org.geant.lgservice")).build();
-	}
-
-	private ApiInfo apiInfo() {
-		return new ApiInfoBuilder().title("Looking Glass Rest APIs")
-				.description("This page lists all the rest apis for Looking Glass Application.").build();
-	}
 }
diff --git a/src/main/java/org/geant/lgservice/config/CommandsParser.java b/src/main/java/org/geant/lgservice/config/CommandsParser.java
index 47d92ceab8fe70307bdb00b7bc47e6a60613ed47..f3051fa7b626ae16504d368d797dde0c84d80012 100644
--- a/src/main/java/org/geant/lgservice/config/CommandsParser.java
+++ b/src/main/java/org/geant/lgservice/config/CommandsParser.java
@@ -1,17 +1,22 @@
 package org.geant.lgservice.config;
 
-import java.io.InputStream;
-import java.util.List;
-
-import javax.xml.bind.JAXBContext;
-import javax.xml.bind.JAXBException;
-import javax.xml.bind.Unmarshaller;
-
+import com.google.common.collect.ImmutableMap;
+import lombok.Builder;
+import lombok.Getter;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.geant.lgservice.pojos.Group;
 import org.geant.lgservice.security.LGUser;
-import org.geant.lgservice.utils.LGRole;
+import org.springframework.security.core.GrantedAuthority;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Unmarshaller;
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Predicate;
 
 public class CommandsParser {
 
@@ -38,25 +43,35 @@ public class CommandsParser {
 		return commands.getGroups();
 	}
 
+	@Builder
+	@Getter
+	private static class FileName {
+		private final String accessLevel;
+		private final int rank;
+	}
+
+	private static final Map<Predicate<Collection<GrantedAuthority>>, FileName> FILE_NAME_PREDICATES = ImmutableMap
+			.<Predicate<Collection<GrantedAuthority>>, FileName>builder()
+			.put(
+					grantedAuthorities -> grantedAuthorities.contains(LGUser.Group.INTERNAL),
+					FileName.builder().accessLevel("internal").rank(0).build())
+			.put(
+					grantedAuthorities -> grantedAuthorities.contains(LGUser.Group.EXTERNAL),
+					FileName.builder().accessLevel("external").rank(1).build())
+			.build();
+
 	public String resolveFileName(final String fileName, final LGUser user) {
-		String newFileName = fileName + "_" + LGRole.PUBLIC.getName();
+		String accessLevel = "public";
 		if (user != null) {
-			switch (user.getRole()) {
-			case PUBLIC: {
-				newFileName = fileName + "_" + LGRole.PUBLIC.getName();
-				break;
-			}
-			case INTERNAL: {
-				newFileName = fileName + "_" + LGRole.INTERNAL.getName();
-				break;
-			}
-			case EXTERNAL: {
-				newFileName = fileName + "_" + LGRole.EXTERNAL.getName();
-				break;
-			}
-			}
+			accessLevel = FILE_NAME_PREDICATES.entrySet()
+					.stream()
+					.filter(entry -> entry.getKey().test(user.getAuthorities()))
+					.map(Map.Entry::getValue)
+					.reduce((fileName1, fileName2) -> fileName1.getRank() < fileName2.getRank() ? fileName1 : fileName2)
+					.map(FileName::getAccessLevel)
+					.orElse("public");
 		}
-		newFileName = newFileName + ".xml";
-		return newFileName;
+
+		return String.format("%s_%s.xml", fileName, accessLevel);
 	}
 }
diff --git a/src/main/java/org/geant/lgservice/domain/Router.java b/src/main/java/org/geant/lgservice/domain/Router.java
new file mode 100644
index 0000000000000000000000000000000000000000..5421521b49b14cfa705c7821d7a86f2dd6ea4dfc
--- /dev/null
+++ b/src/main/java/org/geant/lgservice/domain/Router.java
@@ -0,0 +1,18 @@
+package org.geant.lgservice.domain;
+
+import lombok.Builder;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@Getter
+@Builder
+@RequiredArgsConstructor
+public class Router {
+    private final String hostName;
+    private final Access access;
+
+    public enum Access {
+        INTERNAL,
+        PUBLIC
+    }
+}
diff --git a/src/main/java/org/geant/lgservice/domain/RouterRepository.java b/src/main/java/org/geant/lgservice/domain/RouterRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..8177f0a98076a6d2e4b3d4d0ddccc4064e4ddb3f
--- /dev/null
+++ b/src/main/java/org/geant/lgservice/domain/RouterRepository.java
@@ -0,0 +1,7 @@
+package org.geant.lgservice.domain;
+
+import java.util.Optional;
+
+public interface RouterRepository {
+    Optional<Router> getByHostName(String hostname);
+}
diff --git a/src/main/java/org/geant/lgservice/ecmr/CallableCommandExecutor.java b/src/main/java/org/geant/lgservice/ecmr/CallableCommandExecutor.java
deleted file mode 100644
index afb29f3678965113917353734b39ee963b3b4929..0000000000000000000000000000000000000000
--- a/src/main/java/org/geant/lgservice/ecmr/CallableCommandExecutor.java
+++ /dev/null
@@ -1,122 +0,0 @@
-package org.geant.lgservice.ecmr;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.concurrent.Callable;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import org.apache.commons.lang.StringUtils;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.geant.lgservice.pojos.Command;
-import org.geant.lgservice.pojos.CommandOutput;
-import org.geant.lgservice.pojos.Credentials;
-import org.geant.lgservice.pojos.Router;
-
-import ch.ethz.ssh2.Connection;
-import ch.ethz.ssh2.Session;
-
-public class CallableCommandExecutor implements Callable<CommandOutput> {
-
-	private static final Logger logger = LogManager.getLogger(CallableCommandExecutor.class);
-
-	private Router router;
-
-	private Command command;
-
-	private String arguments;
-
-	private boolean displayAsXML;
-
-	private Credentials credentials;
-
-	CallableCommandExecutor(final Router router, final Command command, final String arguments,
-			final boolean displayAsXML, final Credentials credentials) {
-		this.router = router;
-		this.command = command;
-		this.arguments = arguments;
-		this.displayAsXML = displayAsXML;
-		this.credentials = credentials;
-	}
-
-	@Override
-	public CommandOutput call() {
-		long start = System.currentTimeMillis();
-		StringBuilder output = new StringBuilder();
-		CommandOutput finalOutput = new CommandOutput();
-
-		Connection conn = new Connection(router.getName());
-		try {
-		  try {
-		  	  conn.connect(null, 30000, 30000); // Should be configurable but this code is due a rewrite / pulled out
-		  } catch (IOException e) {
-			  logger.error("Error while connecting to router " + router.getName(), e);
-			  return new CommandOutput(router.getName(), "Error while connecting to router " + router.getName(), null);
-		  }
-		  boolean isAuthenticated;
-		  try {
-			  isAuthenticated = conn.authenticateWithPublicKey(credentials.getUsername(),
-					  new File(credentials.getKeyFile()), credentials.getPassword());
-		  } catch (IOException e) {
-			  logger.error("Error while authenticating the user for router " + router.getName(), e);
-			  return new CommandOutput(router.getName(),
-					  "Error while authenticating the user for router " + router.getName(), null);
-		  }
-
-		  if (isAuthenticated) {
-			  Session session;
-			  try {
-				  session = conn.openSession();
-			  } catch (IOException e) {
-				  logger.error("Error while opening session to router " + router.getName(), e);
-				  return new CommandOutput(router.getName(), "Error while opening session to router " + router.getName(),
-						  null);
-			  }
-			  InputStream in = session.getStdout();
-			  BufferedReader br = new BufferedReader(new InputStreamReader(in));
-
-			  try {
-				  session.execCommand(getCommandWithArguments());
-			  } catch (IOException e) {
-				  logger.error("Error while executing command " + command.getValue(), e);
-				  return new CommandOutput(router.getName(), "Error while executing command " + command.getValue(), null);
-			  }
-
-			  while (true) {
-				  String line = "";
-				  try {
-					  line = br.readLine();
-					  if (line == null)
-						  break;
-					  output.append(line + "\n");
-				  } catch (IOException e) {
-				    	return new CommandOutput(router.getName(), "Error while reading line", null);
-				  }
-			  }
-			  long finish = System.currentTimeMillis();
-			  finalOutput.setRouterName(router.getName());
-			  finalOutput.setCommandResult(output.toString());
-			  finalOutput.setExecutionTime(finish - start);
-
-			  return finalOutput;
-		  }
-		  return new CommandOutput(router.getName(), "Unable to authenticate user for router " + router.getName(), null);
-		} finally {
-		  conn.close();
-	    }
-	}
-
-	public String getCommandWithArguments() {
-		return Stream.of(
-				this.command.getValue(),
-				this.arguments,
-				this.displayAsXML ? "| display xml" : "")
-				.filter(StringUtils::isNotBlank)
-				.collect(Collectors.joining(" "));
-	}
-
-}
diff --git a/src/main/java/org/geant/lgservice/ecmr/CommandExecutor.java b/src/main/java/org/geant/lgservice/ecmr/CommandExecutor.java
deleted file mode 100644
index 551969153187b00044026905fbd4b717302e3668..0000000000000000000000000000000000000000
--- a/src/main/java/org/geant/lgservice/ecmr/CommandExecutor.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package org.geant.lgservice.ecmr;
-
-import org.geant.lgservice.config.AppConfig;
-import org.geant.lgservice.pojos.Command;
-import org.geant.lgservice.pojos.CommandOutput;
-import org.geant.lgservice.pojos.Credentials;
-import org.geant.lgservice.pojos.Router;
-import org.springframework.stereotype.Component;
-
-@Component
-public class CommandExecutor {
-
-    private final Credentials credentials;
-
-    public CommandExecutor(AppConfig config) {
-        credentials = Credentials.builder()
-                .username(config.getUsername())
-                .password(config.getPassword())
-                .keyFile(config.getKeyFile())
-                .build();
-    }
-
-    public CommandOutput execute(Router router, Command command, String arguments, boolean asXml) {
-        return new CallableCommandExecutor(router, command, arguments, asXml, credentials).call();
-    }
-}
diff --git a/src/main/java/org/geant/lgservice/exceptions/BusinessException.java b/src/main/java/org/geant/lgservice/exceptions/BusinessException.java
new file mode 100644
index 0000000000000000000000000000000000000000..68926f80859f45b5670daff8afb1f0e0b4729897
--- /dev/null
+++ b/src/main/java/org/geant/lgservice/exceptions/BusinessException.java
@@ -0,0 +1,33 @@
+package org.geant.lgservice.exceptions;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import static lombok.AccessLevel.PRIVATE;
+
+@RequiredArgsConstructor(access = PRIVATE)
+public class BusinessException  extends RuntimeException {
+
+    @Getter
+    private final Reason reason;
+
+    @AllArgsConstructor
+    public enum Reason {
+        UNAUTHORIZED("User is not logged in"),
+        FORBIDDEN("User does not have access rights"),
+        NOT_FOUND("Could not find router");
+
+        @Getter
+        private final String description;
+    }
+
+    public static BusinessException withReason(Reason reason) {
+        return new BusinessException(reason);
+    }
+
+    @Override
+    public String getMessage() {
+        return reason.getDescription();
+    }
+}
diff --git a/src/main/java/org/geant/lgservice/exceptions/TechnicalException.java b/src/main/java/org/geant/lgservice/exceptions/TechnicalException.java
new file mode 100644
index 0000000000000000000000000000000000000000..e18b63ae5faa0b9fbbfd2422f72fb3d68042ec93
--- /dev/null
+++ b/src/main/java/org/geant/lgservice/exceptions/TechnicalException.java
@@ -0,0 +1,11 @@
+package org.geant.lgservice.exceptions;
+
+public class TechnicalException extends RuntimeException {
+    public TechnicalException(Throwable t) {
+        super(t);
+    }
+
+    public TechnicalException(String message) {
+        super(message);
+    }
+}
diff --git a/src/main/java/org/geant/lgservice/infrastructure/rest/inventoryprovider/Config.java b/src/main/java/org/geant/lgservice/infrastructure/rest/inventoryprovider/Config.java
new file mode 100644
index 0000000000000000000000000000000000000000..46b88e30f924457baece8e105b0b605135286c7f
--- /dev/null
+++ b/src/main/java/org/geant/lgservice/infrastructure/rest/inventoryprovider/Config.java
@@ -0,0 +1,16 @@
+package org.geant.lgservice.infrastructure.rest.inventoryprovider;
+
+import org.geant.lgservice.domain.RouterRepository;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class Config {
+
+    @Bean
+    public RouterRepository routerRepository(@Value("${inventoryprovider.baseUrl}")String baseUrl) {
+        return InventoryProviderRouterRepository.builder().baseUrl(baseUrl).build();
+    }
+
+}
diff --git a/src/main/java/org/geant/lgservice/infrastructure/rest/inventoryprovider/InventoryProviderRouterRepository.java b/src/main/java/org/geant/lgservice/infrastructure/rest/inventoryprovider/InventoryProviderRouterRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..7eabb49d91d965defbdb4268224a70fa74e37ba0
--- /dev/null
+++ b/src/main/java/org/geant/lgservice/infrastructure/rest/inventoryprovider/InventoryProviderRouterRepository.java
@@ -0,0 +1,107 @@
+package org.geant.lgservice.infrastructure.rest.inventoryprovider;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.collect.ImmutableMap;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.OkHttpClient;
+import org.geant.lgservice.domain.Router;
+import org.geant.lgservice.domain.RouterRepository;
+import org.geant.lgservice.exceptions.TechnicalException;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.HttpServerErrorException;
+import org.springframework.web.client.HttpStatusCodeException;
+import retrofit2.Call;
+import retrofit2.Response;
+import retrofit2.Retrofit;
+import retrofit2.converter.jackson.JacksonConverterFactory;
+import retrofit2.http.GET;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Optional;
+
+import static java.nio.charset.Charset.defaultCharset;
+import static org.geant.lgservice.domain.Router.Access.INTERNAL;
+import static org.geant.lgservice.domain.Router.Access.PUBLIC;
+
+@Slf4j
+public class InventoryProviderRouterRepository implements RouterRepository {
+
+    private final InventoryProvider inventoryProvider;
+
+    @Override
+    public Optional<Router> getByHostName(String hostname) {
+        try {
+            Response<Collection<InventoryProvider.InventoryProviderRouter>> response = inventoryProvider.getRouters()
+                    .execute();
+            if (!response.isSuccessful()) {
+                HttpStatus status = HttpStatus.valueOf(response.code());
+                HttpStatusCodeException rootException = status.is4xxClientError() ?
+                        new HttpClientErrorException(status, response.message(), response.errorBody()
+                                .bytes(), defaultCharset()) :
+                        new HttpServerErrorException(status, response.message(), response.errorBody()
+                                .bytes(), defaultCharset());
+                log.error(response.message(), rootException);
+                throw new TechnicalException(rootException);
+            }
+
+            return response.body()
+                    .stream()
+                    .filter(router -> router.getEquipmentName().equals(hostname))
+                    .map(InventoryProvider.InventoryProviderRouter::toDomain)
+                    .findFirst();
+        } catch (IOException e) {
+            throw new TechnicalException(e);
+        }
+    }
+
+    @Builder
+    public InventoryProviderRouterRepository(String baseUrl, OkHttpClient client) {
+        this.inventoryProvider = new Retrofit.Builder()
+                .baseUrl(baseUrl)
+                .client(client != null ? client : new OkHttpClient())
+                .addConverterFactory(JacksonConverterFactory.create())
+                .build()
+                .create(InventoryProvider.class);
+    }
+
+    interface InventoryProvider {
+        @GET("routers/all")
+        Call<Collection<InventoryProviderRouter>> getRouters();
+
+        @Getter
+        @JsonIgnoreProperties(ignoreUnknown = true)
+        class InventoryProviderRouter {
+            private final String equipmentName;
+            private final String type;
+
+            @JsonCreator
+            public InventoryProviderRouter(
+                    @JsonProperty("equipment name") String equipmentName,
+                    @JsonProperty("type") String type
+            ) {
+                this.equipmentName = equipmentName;
+                this.type = type;
+            }
+
+            private static final Map<String, Router.Access> ACCESS_MAPPINGS =
+                    ImmutableMap.<String, Router.Access>builder()
+                            .put("INTERNAL", INTERNAL)
+                            .build();
+
+            public Router toDomain() {
+                return Router.builder()
+                        .hostName(equipmentName)
+                        .access(ACCESS_MAPPINGS.getOrDefault(type, PUBLIC))
+                        .build();
+            }
+        }
+
+    }
+}
diff --git a/src/main/java/org/geant/lgservice/infrastructure/ssh/Config.java b/src/main/java/org/geant/lgservice/infrastructure/ssh/Config.java
new file mode 100644
index 0000000000000000000000000000000000000000..de7917939699e080eb5804697bf7296ce4318545
--- /dev/null
+++ b/src/main/java/org/geant/lgservice/infrastructure/ssh/Config.java
@@ -0,0 +1,22 @@
+package org.geant.lgservice.infrastructure.ssh;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.io.File;
+
+@Configuration
+public class Config {
+
+    @Bean
+    public ConnectionFactory connectionFactory(
+            @Value("${configuration.publickey.username}") String username,
+            @Value("${configuration.publickey.keyfile}") File keyFile,
+            @Value("${configuration.publickey.password}") String password,
+            @Value("${configuration.ssh.connection.timeout:30000}") int timeout
+    ) {
+        return new ConnectionFactory(username, password, keyFile, timeout);
+    }
+
+}
diff --git a/src/main/java/org/geant/lgservice/infrastructure/ssh/ConnectionFactory.java b/src/main/java/org/geant/lgservice/infrastructure/ssh/ConnectionFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..46fa347a6f95c2d371f47333c158cf0b38a3e1ac
--- /dev/null
+++ b/src/main/java/org/geant/lgservice/infrastructure/ssh/ConnectionFactory.java
@@ -0,0 +1,36 @@
+package org.geant.lgservice.infrastructure.ssh;
+
+import ch.ethz.ssh2.Connection;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.geant.lgservice.exceptions.TechnicalException;
+
+import java.io.File;
+import java.io.IOException;
+
+import static java.lang.String.format;
+
+@RequiredArgsConstructor
+@Slf4j
+public class ConnectionFactory {
+
+    private final String username;
+    private final String password;
+    private final File keyFile;
+    private final int timeout;
+
+    public Connection createConnection(String hostname) {
+        log.info("Creating connection for {}", hostname);
+        Connection connection = new Connection(hostname);
+        try {
+            connection.connect(null, timeout, timeout);
+            if (!connection.authenticateWithPublicKey(username, keyFile, password)){
+                throw new TechnicalException(format("Could not authenticate ssh connection with %s", hostname));
+            }
+        } catch (IOException e) {
+            throw new TechnicalException(e);
+        }
+        log.info("Connection for {} created");
+        return connection;
+    }
+}
diff --git a/src/main/java/org/geant/lgservice/infrastructure/ssh/ConnectionManager.java b/src/main/java/org/geant/lgservice/infrastructure/ssh/ConnectionManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..0f77845bb5a6a7ef01b37b5e2f26adb3f53d54f7
--- /dev/null
+++ b/src/main/java/org/geant/lgservice/infrastructure/ssh/ConnectionManager.java
@@ -0,0 +1,43 @@
+package org.geant.lgservice.infrastructure.ssh;
+
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.geant.lgservice.infrastructure.ssh.SSHHost.ConnectionStateListener.State.DISCONNECTED;
+
+@Slf4j
+@Component
+@AllArgsConstructor
+public class ConnectionManager implements SSHHost.ConnectionStateListener {
+
+    private final ConnectionFactory connectionFactory;
+
+    private final Map<String, SSHHost> hosts = new ConcurrentHashMap<>();
+
+    public SSHHost getHost(String hostname) {
+        log.info("retrieving host {}", hostname);
+        synchronized (this) {
+            return hosts
+                    .computeIfAbsent(
+                            hostname,
+                            host -> {
+                                log.info("Host [{}] connection does not exist", hostname);
+                                return SSHHost.fromConnection(connectionFactory.createConnection(host))
+                                        .withConnectionStateListener(this);
+                            });
+        }
+    }
+
+    @Override
+    public void stateChanged(SSHHost host, State state) {
+        synchronized (this) {
+            if (state.equals(DISCONNECTED)) {
+                hosts.remove(host);
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/geant/lgservice/infrastructure/ssh/SSHCommandExecutor.java b/src/main/java/org/geant/lgservice/infrastructure/ssh/SSHCommandExecutor.java
new file mode 100644
index 0000000000000000000000000000000000000000..35f532db31c73edc1ef157da219d7616114f8095
--- /dev/null
+++ b/src/main/java/org/geant/lgservice/infrastructure/ssh/SSHCommandExecutor.java
@@ -0,0 +1,31 @@
+package org.geant.lgservice.infrastructure.ssh;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.geant.lgservice.pojos.CommandOutput;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StopWatch;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class SSHCommandExecutor {
+
+    private final ConnectionManager connectionManager;
+
+    public CommandOutput execute(String hostname, String command) {
+        log.info("Executing command [{}] on host [{}]", command, hostname);
+        StopWatch timer = new StopWatch();
+        timer.start();
+        String response = connectionManager.getHost(hostname)
+                .execute(command);
+        timer.stop();
+        log.info("Command completed on host [{}] in [{}]ms with result [{}]", hostname, timer.getTotalTimeMillis(), response);
+        return CommandOutput.builder()
+                .routerName(hostname)
+                .commandResult(response)
+                .executionTime(timer.getTotalTimeMillis())
+                .build();
+    }
+
+}
diff --git a/src/main/java/org/geant/lgservice/infrastructure/ssh/SSHHost.java b/src/main/java/org/geant/lgservice/infrastructure/ssh/SSHHost.java
new file mode 100644
index 0000000000000000000000000000000000000000..8eba9473978cc53240b8b5a0b2ff08c198a9c688
--- /dev/null
+++ b/src/main/java/org/geant/lgservice/infrastructure/ssh/SSHHost.java
@@ -0,0 +1,88 @@
+package org.geant.lgservice.infrastructure.ssh;
+
+import ch.ethz.ssh2.Connection;
+import ch.ethz.ssh2.ConnectionMonitor;
+import ch.ethz.ssh2.Session;
+import lombok.extern.slf4j.Slf4j;
+import org.geant.lgservice.exceptions.TechnicalException;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.stream.Collectors;
+
+import static ch.ethz.ssh2.ChannelCondition.EXIT_STATUS;
+import static org.geant.lgservice.infrastructure.ssh.SSHHost.ConnectionStateListener.State.DISCONNECTED;
+
+@Slf4j
+public class SSHHost implements ConnectionMonitor {
+
+    private final Connection connection;
+    private final Collection<ConnectionStateListener> connectionStateListeners = new HashSet<>();
+
+    private SSHHost(Connection connection) {
+        this.connection = connection;
+        connection.addConnectionMonitor(this);
+    }
+
+    public static SSHHost fromConnection(Connection connection) {
+        if (!connection.isAuthenticationComplete()) {
+            throw new TechnicalException("Connection is not authenticated");
+        }
+        return new SSHHost(connection);
+    }
+
+    public String execute(String command) {
+        Session session = null;
+        try {
+            session = connection.openSession();
+
+            log.debug("sending command [{}] to host [{}]", command, hostname());
+            session.execCommand(command);
+
+            session.waitForCondition(EXIT_STATUS, 0);
+            int exitStatus = session.getExitStatus();
+            log.debug("Exit status [{}] recieved", exitStatus);
+
+            InputStream response = exitStatus == 0 ? session.getStdout() : session.getStderr();
+            try (BufferedReader reader = new BufferedReader(new InputStreamReader(response))) {
+                return reader.lines().collect(Collectors.joining("\n"));
+            }
+        } catch (IOException e) {
+            throw new TechnicalException(e);
+        } finally {
+            if (session != null) {
+                session.close();
+            }
+        }
+    }
+
+    public String hostname() {
+        return connection.getHostname();
+    }
+
+    public SSHHost withConnectionStateListener(ConnectionStateListener listener) {
+        connectionStateListeners.add(listener);
+        return this;
+    }
+
+    @Override
+    public void connectionLost(Throwable reason) {
+        log.warn("Host [{}] lost connection: ", connection.getHostname(), reason);
+        connectionStateListeners
+                .forEach(connectionStateListener -> connectionStateListener.stateChanged(this, DISCONNECTED));
+    }
+
+    @FunctionalInterface
+    public interface ConnectionStateListener {
+
+        enum State {
+            DISCONNECTED
+        }
+
+        void stateChanged(SSHHost host, State state);
+    }
+}
diff --git a/src/main/java/org/geant/lgservice/interfaces/rest/BusinessExceptionHandler.java b/src/main/java/org/geant/lgservice/interfaces/rest/BusinessExceptionHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..a67f39942644af7bbedf38e2b830558a1d79af68
--- /dev/null
+++ b/src/main/java/org/geant/lgservice/interfaces/rest/BusinessExceptionHandler.java
@@ -0,0 +1,19 @@
+package org.geant.lgservice.interfaces.rest;
+
+import org.geant.lgservice.exceptions.BusinessException;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
+
+@ControllerAdvice
+public class BusinessExceptionHandler extends ResponseEntityExceptionHandler {
+
+
+
+    @ExceptionHandler(BusinessException.class)
+    protected ResponseEntity<Object> handleBusinessException (BusinessException ex) {
+        return ResponseEntity.status(HttpStatus.valueOf(ex.getReason().name())).build();
+    }
+}
diff --git a/src/main/java/org/geant/lgservice/interfaces/rest/TechnicalExceptionHandler.java b/src/main/java/org/geant/lgservice/interfaces/rest/TechnicalExceptionHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..bf184cc96a76572005d456cd6f4c84dd2fed0925
--- /dev/null
+++ b/src/main/java/org/geant/lgservice/interfaces/rest/TechnicalExceptionHandler.java
@@ -0,0 +1,21 @@
+package org.geant.lgservice.interfaces.rest;
+
+import lombok.extern.slf4j.Slf4j;
+import org.geant.lgservice.exceptions.TechnicalException;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
+
+import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
+
+@ControllerAdvice
+@Slf4j
+public class TechnicalExceptionHandler extends ResponseEntityExceptionHandler {
+
+    @ExceptionHandler(TechnicalException.class)
+    protected ResponseEntity<Object> handleBusinessException(TechnicalException ex) {
+        log.error("Caught Technical Exception: ", ex);
+        return ResponseEntity.status(INTERNAL_SERVER_ERROR).build();
+    }
+}
diff --git a/src/main/java/org/geant/lgservice/pojos/Command.java b/src/main/java/org/geant/lgservice/pojos/Command.java
index 674fdc65cd166998a69fa5b080e03bd89c1702aa..265d84425583960b6155ec275484ea4a2cc02a3e 100644
--- a/src/main/java/org/geant/lgservice/pojos/Command.java
+++ b/src/main/java/org/geant/lgservice/pojos/Command.java
@@ -1,16 +1,16 @@
 package org.geant.lgservice.pojos;
 
-import lombok.AllArgsConstructor;
-import lombok.Data;
+import lombok.Getter;
 import lombok.NoArgsConstructor;
+import lombok.Setter;
 
-@Data
 @NoArgsConstructor
-@AllArgsConstructor
+@Setter
 public class Command {
 
 	private String name;
 
+	@Getter
 	private String value;
 
 	private String description;
diff --git a/src/main/java/org/geant/lgservice/pojos/CommandOutput.java b/src/main/java/org/geant/lgservice/pojos/CommandOutput.java
index 89b252988870a6a51b3384ac0649e1c60fe29352..c4073ba2c1dc43f891e4968721e9cbfd2d215273 100644
--- a/src/main/java/org/geant/lgservice/pojos/CommandOutput.java
+++ b/src/main/java/org/geant/lgservice/pojos/CommandOutput.java
@@ -1,16 +1,14 @@
 package org.geant.lgservice.pojos;
 
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
 
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
+@Getter
+@Builder
 public class CommandOutput {
 
-	private String routerName;
-	private String commandResult;
-	private Long executionTime;
+	private final String routerName;
+	private final String commandResult;
+	private final Long executionTime;
 
 }
diff --git a/src/main/java/org/geant/lgservice/pojos/Coordinates.java b/src/main/java/org/geant/lgservice/pojos/Coordinates.java
deleted file mode 100644
index 622c71d4ad288808db47f9f768b50c39291c4bf4..0000000000000000000000000000000000000000
--- a/src/main/java/org/geant/lgservice/pojos/Coordinates.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.geant.lgservice.pojos;
-
-import javax.xml.bind.annotation.XmlAttribute;
-
-public class Coordinates {
-
-	private String latitude;
-
-	private String longitude;
-
-	public Coordinates() {
-
-	}
-
-	public Coordinates(String latitude, String longitude) {
-		super();
-		this.latitude = latitude;
-		this.longitude = longitude;
-	}
-
-	public String getLatitude() {
-		return latitude;
-	}
-
-	@XmlAttribute(name = "lat")
-	public void setLatitude(String latitude) {
-		this.latitude = latitude;
-	}
-
-	public String getLongitude() {
-		return longitude;
-	}
-
-	@XmlAttribute(name = "long")
-	public void setLongitude(String longitude) {
-		this.longitude = longitude;
-	}
-
-}
diff --git a/src/main/java/org/geant/lgservice/pojos/Credentials.java b/src/main/java/org/geant/lgservice/pojos/Credentials.java
deleted file mode 100644
index 05f6defe925b12f3bff7d4e19a171609e14f2615..0000000000000000000000000000000000000000
--- a/src/main/java/org/geant/lgservice/pojos/Credentials.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package org.geant.lgservice.pojos;
-
-import lombok.Builder;
-import lombok.Getter;
-
-@Getter
-@Builder
-public class Credentials {
-
-	private String username;
-
-	private String password;
-
-	private String keyFile;
-
-}
diff --git a/src/main/java/org/geant/lgservice/pojos/QueryInputRequest.java b/src/main/java/org/geant/lgservice/pojos/QueryInputRequest.java
index 6ff035763a9c62acfc716e5198fd7a7218ccbc50..3f9b2abf1c66052a151a5867a61662568d0c7197 100644
--- a/src/main/java/org/geant/lgservice/pojos/QueryInputRequest.java
+++ b/src/main/java/org/geant/lgservice/pojos/QueryInputRequest.java
@@ -1,13 +1,11 @@
 package org.geant.lgservice.pojos;
 
-import java.util.List;
-
-import lombok.AllArgsConstructor;
-import lombok.Data;
+import lombok.Getter;
 import lombok.NoArgsConstructor;
 
-@Data
-@AllArgsConstructor
+import java.util.List;
+
+@Getter
 @NoArgsConstructor
 public class QueryInputRequest {
 
diff --git a/src/main/java/org/geant/lgservice/pojos/QueryResponse.java b/src/main/java/org/geant/lgservice/pojos/QueryResponse.java
index 9ded0504bae9655840e85312b76ff20c5027090b..4d6584d66232ec7b864d18d7305c8b3fddb5feea 100644
--- a/src/main/java/org/geant/lgservice/pojos/QueryResponse.java
+++ b/src/main/java/org/geant/lgservice/pojos/QueryResponse.java
@@ -1,14 +1,12 @@
 package org.geant.lgservice.pojos;
 
-import java.util.Map;
+import lombok.Builder;
+import lombok.Getter;
 
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
+import java.util.Map;
 
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
+@Getter
+@Builder
 public class QueryResponse {
 
 	Map<String, CommandOutput> output;
diff --git a/src/main/java/org/geant/lgservice/rest/LookingGlassRestController.java b/src/main/java/org/geant/lgservice/rest/LookingGlassRestController.java
index 94973e050b746a00b63e0956d5e8908e7ce8b8eb..39b98d64aeec92340a0d03a9cc779b5dc6a00dbb 100644
--- a/src/main/java/org/geant/lgservice/rest/LookingGlassRestController.java
+++ b/src/main/java/org/geant/lgservice/rest/LookingGlassRestController.java
@@ -1,11 +1,7 @@
 package org.geant.lgservice.rest;
 
-import java.util.List;
-import java.util.Map;
-
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
-import org.geant.lgservice.exceptions.ConnectionException;
 import org.geant.lgservice.pojos.CommandOutput;
 import org.geant.lgservice.pojos.Group;
 import org.geant.lgservice.pojos.QueryInputRequest;
@@ -16,6 +12,7 @@ import org.geant.lgservice.services.LookingGlassService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.info.BuildProperties;
 import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.web.bind.annotation.CrossOrigin;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -24,7 +21,10 @@ import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
-import io.swagger.annotations.ApiOperation;
+import java.util.List;
+import java.util.Map;
+
+import static java.util.stream.Collectors.toMap;
 
 @RestController
 @RequestMapping("/rest")
@@ -63,19 +63,16 @@ public class LookingGlassRestController {
 
 	@PostMapping(value = "/submit")
 	@CrossOrigin
-	public ResponseEntity<QueryResponse> submit(@RequestBody QueryInputRequest input) throws ConnectionException {
-		QueryResponse response = new QueryResponse();
-		Map<String, CommandOutput> result = service.submitQuery(input.getSelectedRouters(), input.getSelectedCommand(),
-				input.getArguments(), input.isDisplayAsXML());
-		if (result != null && !result.isEmpty())
-			response.setOutput(result);
-		else
-			throw new ConnectionException();
-		return ResponseEntity.ok(response);
+	public QueryResponse submit(@RequestBody QueryInputRequest input, @AuthenticationPrincipal LGUser user) {
+
+		Map<String, CommandOutput> result = input.getSelectedRouters().stream()
+				.map(router -> service.submitQuery(router.getName(), input.getSelectedCommand(), input.getArguments(), input.isDisplayAsXML(), user))
+				.collect(toMap(CommandOutput::getRouterName, output -> output));
+
+		return QueryResponse.builder().output(result).build();
 	}
 
 	@GetMapping(value = "/build")
-	@ApiOperation(value = "Get build information", response = String.class)
 	public ResponseEntity<?> getBuildInfo() {
 		BuildProperties buildProperties = buildService.getBuildInfo();
 		return ResponseEntity.ok(buildProperties);
diff --git a/src/main/java/org/geant/lgservice/security/LGUser.java b/src/main/java/org/geant/lgservice/security/LGUser.java
index 54859a3faf55f4e499e8c96e380d26575a5940cd..340631cda86d2750ae7dead3b22ef295621bcee7 100644
--- a/src/main/java/org/geant/lgservice/security/LGUser.java
+++ b/src/main/java/org/geant/lgservice/security/LGUser.java
@@ -1,103 +1,97 @@
 package org.geant.lgservice.security;
 
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-import org.geant.lgservice.utils.LGRole;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
 import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
 
-public class LGUser extends User {
+import java.util.Collection;
+import java.util.Optional;
+import java.util.stream.Stream;
 
-	private static final long serialVersionUID = 1L;
+import static java.util.stream.Collectors.toSet;
 
-	private String federatedUser;
-	private String givenName;
-	private String surname;
-	private String mail;
-	private LGRole role;
-	private Collection<? extends GrantedAuthority> groups;
+@Getter
+public class LGUser implements UserDetails {
 
-	public static final String INTERNAL_GROUP = "GEANT Staff:All";
-	public static final String EXTERNAL_GROUP = "GEANT_CO:GEANT Services:NREN Svc Mgmt:GEANT NRENs:members_GEANT NRENs";
+	private final String federatedUser;
+	private final String givenName;
+	private final String surname;
+	private final String mail;
+	private final Collection<GrantedAuthority> authorities;
 
-	public LGUser(String federatedUser, String givenName, String surname, String mail,
-			Collection<? extends GrantedAuthority> groups) {
+	@RequiredArgsConstructor
+	public enum Group implements GrantedAuthority{
+		INTERNAL(new SimpleGrantedAuthority("GEANT Staff:All")),
+		EXTERNAL(new SimpleGrantedAuthority("GEANT_CO:GEANT Services:NREN Svc Mgmt:GEANT NRENs:members_GEANT NRENs"));
 
-		super(federatedUser, "DUMMY PASSWORD", true, true, true, true, groups);
+		private final GrantedAuthority authority;
 
-		this.federatedUser = federatedUser;
-		this.givenName = givenName;
-		this.surname = surname;
-		this.mail = mail;
-		this.groups = groups;
-
-		List<String> groupValues = new ArrayList<>();
-		for (GrantedAuthority grantedAuthority : groups) {
-			groupValues.add(grantedAuthority.getAuthority());
-		}
-
-		if (!groupValues.contains(INTERNAL_GROUP) && !groupValues.contains(EXTERNAL_GROUP)) {
-			this.role = LGRole.PUBLIC;
+		public static Optional<Group> fromGroup(String group) {
+			return Stream.of(Group.values())
+					.filter(value -> value.authority.getAuthority().equals(group))
+					.findFirst();
 		}
 
-		if (groupValues.contains(INTERNAL_GROUP)) {
-			this.role = LGRole.INTERNAL;
+		public static Optional<Group> fromAuthority(GrantedAuthority authority) {
+			return Stream.of(Group.values())
+					.filter(authority::equals)
+					.findFirst();
 		}
 
-		if (groupValues.contains(EXTERNAL_GROUP)) {
-			this.role = LGRole.EXTERNAL;
+		@Override
+		public String getAuthority() {
+			return name();
 		}
-
 	}
 
-	public String getSurname() {
-		return surname;
-	}
+	@Builder
+	public LGUser(String federatedUser, String givenName, String surname, String mail,
+			String ...groups) {
 
-	public void setSurname(String surname) {
+		this.federatedUser = federatedUser;
+		this.givenName = givenName;
 		this.surname = surname;
-	}
+		this.mail = mail;
+		this.authorities = Stream.of(groups)
+				.map(Group::fromGroup)
+				.filter(Optional::isPresent)
+				.map(Optional::get)
+				.collect(toSet());
 
-	public String getGivenName() {
-		return givenName;
-	}
 
-	public void setGivenName(String givenName) {
-		this.givenName = givenName;
 	}
 
-	public String getMail() {
-		return mail;
+	@Override
+	public String getPassword() {
+		return null;
 	}
 
-	public void setMail(String mail) {
-		this.mail = mail;
-	}
-
-	public String getFederatedUser() {
+	@Override
+	public String getUsername() {
 		return federatedUser;
 	}
 
-	public void setFederatedUser(String federatedUser) {
-		this.federatedUser = federatedUser;
-	}
-
-	public Collection<? extends GrantedAuthority> getGroups() {
-		return groups;
+	@Override
+	public boolean isAccountNonExpired() {
+		return true;
 	}
 
-	public void setGroups(Collection<? extends GrantedAuthority> groups) {
-		this.groups = groups;
+	@Override
+	public boolean isAccountNonLocked() {
+		return true;
 	}
 
-	public LGRole getRole() {
-		return role;
+	@Override
+	public boolean isCredentialsNonExpired() {
+		return true;
 	}
 
-	public void setRole(LGRole role) {
-		this.role = role;
+	@Override
+	public boolean isEnabled() {
+		return true;
 	}
 
 }
diff --git a/src/main/java/org/geant/lgservice/security/LGUserService.java b/src/main/java/org/geant/lgservice/security/LGUserService.java
index 506512f993831a945769920196dac7303b1cfe35..a0ac88e9bc512327909cdaf52c8f52d6fad5358e 100644
--- a/src/main/java/org/geant/lgservice/security/LGUserService.java
+++ b/src/main/java/org/geant/lgservice/security/LGUserService.java
@@ -1,12 +1,7 @@
 package org.geant.lgservice.security;
 
-import java.util.ArrayList;
-import java.util.List;
-
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.core.userdetails.UsernameNotFoundException;
 import org.springframework.security.saml.SAMLCredential;
 import org.springframework.security.saml.userdetails.SAMLUserDetailsService;
@@ -23,17 +18,15 @@ public class LGUserService implements SAMLUserDetailsService {
 		String givenName = credential.getAttributeAsString("givenName");
 		String surname = credential.getAttributeAsString("sn");
 		String mail = credential.getAttributeAsString("mail");
-
-		List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
 		String[] groupNames = credential.getAttributeAsStringArray("isMemberOf");
-		if (groupNames != null) {
-			for (String role : groupNames) {
-				GrantedAuthority authority = new SimpleGrantedAuthority(role);
-				authorities.add(authority);
-			}
-		}
+
 		LOGGER.info("Current user : " + givenName + ", " + surname);
-		LGUser currentUser = new LGUser(federatedUser, givenName, surname, mail, authorities);
-		return currentUser;
+		return LGUser.builder()
+				.federatedUser(federatedUser)
+				.givenName(givenName)
+				.surname(surname)
+				.mail(mail)
+				.groups(groupNames)
+				.build();
 	}
 }
diff --git a/src/main/java/org/geant/lgservice/services/LookingGlassService.java b/src/main/java/org/geant/lgservice/services/LookingGlassService.java
index ee92c9d8d3438672e02b30bf82456a99f36e6390..aa8e955f9aa0a7f5b8a79cd9cbd0bd4c705ceddf 100644
--- a/src/main/java/org/geant/lgservice/services/LookingGlassService.java
+++ b/src/main/java/org/geant/lgservice/services/LookingGlassService.java
@@ -1,18 +1,17 @@
 package org.geant.lgservice.services;
 
-import java.util.List;
-import java.util.Map;
-
 import org.geant.lgservice.pojos.Command;
 import org.geant.lgservice.pojos.CommandOutput;
 import org.geant.lgservice.pojos.Group;
-import org.geant.lgservice.pojos.Router;
 import org.geant.lgservice.security.LGUser;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.List;
 
 public interface LookingGlassService {
 
 	List<Group> getAllCommands(final LGUser user);
 
-	Map<String, CommandOutput> submitQuery(final List<Router> selectedRouters, final Command selectedCommand,
-			final String arguments, final boolean displayAsXML);
+	CommandOutput submitQuery(final String hostname, final Command selectedCommand,
+							  final String arguments, final boolean displayAsXML, UserDetails user);
 }
diff --git a/src/main/java/org/geant/lgservice/services/LookingGlassServiceImpl.java b/src/main/java/org/geant/lgservice/services/LookingGlassServiceImpl.java
index ab6ac1a706b2acd1873ff488e2ae47631a1d4439..be982e9769f993464cd323216760a391eead0540 100644
--- a/src/main/java/org/geant/lgservice/services/LookingGlassServiceImpl.java
+++ b/src/main/java/org/geant/lgservice/services/LookingGlassServiceImpl.java
@@ -1,21 +1,27 @@
 package org.geant.lgservice.services;
 
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-
 import lombok.RequiredArgsConstructor;
-import org.geant.lgservice.ecmr.CommandExecutor;
+import org.apache.commons.lang.StringUtils;
+import org.geant.lgservice.domain.Router;
+import org.geant.lgservice.domain.RouterRepository;
+import org.geant.lgservice.exceptions.BusinessException;
+import org.geant.lgservice.infrastructure.ssh.SSHCommandExecutor;
 import org.geant.lgservice.pojos.Command;
 import org.geant.lgservice.pojos.CommandOutput;
 import org.geant.lgservice.pojos.Group;
-import org.geant.lgservice.pojos.Router;
 import org.geant.lgservice.security.LGUser;
 import org.geant.lgservice.utils.LookingGlassHelper;
-import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.stereotype.Service;
 
-import static java.util.stream.Collectors.toMap;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.geant.lgservice.domain.Router.Access.INTERNAL;
+import static org.geant.lgservice.exceptions.BusinessException.Reason.FORBIDDEN;
+import static org.geant.lgservice.exceptions.BusinessException.Reason.NOT_FOUND;
+import static org.geant.lgservice.exceptions.BusinessException.Reason.UNAUTHORIZED;
 
 @Service
 @RequiredArgsConstructor
@@ -23,7 +29,9 @@ public class LookingGlassServiceImpl implements LookingGlassService {
 
 	private final LookingGlassHelper lookingGlassHelper;
 
-	private final CommandExecutor commandExecutor;
+	private final RouterRepository routerRepository;
+
+	private final SSHCommandExecutor sshCommandExecutor;
 
 	@Override
 	public List<Group> getAllCommands(final LGUser user) {
@@ -31,10 +39,29 @@ public class LookingGlassServiceImpl implements LookingGlassService {
 	}
 
 	@Override
-	public Map<String, CommandOutput> submitQuery(final List<Router> selectedRouters, final Command selectedCommand,
-			final String arguments, final boolean displayAsXML) {
-		return selectedRouters.stream()
-				.map(router -> commandExecutor.execute(router, selectedCommand, arguments, displayAsXML))
-				.collect(toMap(CommandOutput::getRouterName, output -> output));
+	public CommandOutput submitQuery(final String hostname, final Command selectedCommand,
+									 final String arguments, final boolean displayAsXML, UserDetails user) {
+
+		Router router = routerRepository.getByHostName(hostname)
+				.orElseThrow(() -> BusinessException.withReason(NOT_FOUND));
+
+		if (router.getAccess().equals(INTERNAL)) {
+			if (user == null){
+				throw BusinessException.withReason(UNAUTHORIZED);
+			}else if (!user.getAuthorities().contains(LGUser.Group.INTERNAL)){
+				throw BusinessException.withReason(FORBIDDEN);
+			}
+		}
+
+		return sshCommandExecutor.execute(hostname, getCommandWithArguments(selectedCommand, arguments, displayAsXML));
+	}
+
+	private String getCommandWithArguments(Command command, String arguments, boolean displayAsXML) {
+		return Stream.of(
+				command.getValue(),
+				arguments,
+				displayAsXML ? "| display xml" : "")
+				.filter(StringUtils::isNotBlank)
+				.collect(Collectors.joining(" "));
 	}
 }
diff --git a/src/test/java/org/geant/lgservice/ecmr/CallableCommandExecutorTest.java b/src/test/java/org/geant/lgservice/ecmr/CallableCommandExecutorTest.java
deleted file mode 100644
index 34da57adcd13b53426bba0be22694099b5ec5151..0000000000000000000000000000000000000000
--- a/src/test/java/org/geant/lgservice/ecmr/CallableCommandExecutorTest.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package org.geant.lgservice.ecmr;
-
-import static org.junit.Assert.assertEquals;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import lombok.SneakyThrows;
-import org.geant.lgservice.pojos.Command;
-import org.geant.lgservice.pojos.Router;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.junit4.SpringRunner;
-
-@RunWith(SpringRunner.class)
-@SpringBootTest
-public class CallableCommandExecutorTest {
-
-	public static final String TEST_ROUTER = "test-router.geant.net";
-	public static final String TEST_COMMAND = "ping 25";
-	public static final String TEST_ROUTER_JSON = String.format("{\"name\":\"%s\"}", TEST_ROUTER);
-	private static final ObjectMapper MAPPER = new ObjectMapper();
-	private Router router;
-
-	@Before
-	@SneakyThrows
-	public void setup() {
-		router =  MAPPER.readValue(TEST_ROUTER_JSON, Router.class);
-	}
-
-	@Test
-	public void testGetCommandWithArgumentsXML() {
-		Command command = new Command();
-		command.setValue(TEST_COMMAND);
-		command.setRequiresParams(true);
-
-		CallableCommandExecutor callable = new CallableCommandExecutor(router, command, "param1", true, null);
-		assertEquals("ping 25 param1 | display xml", callable.getCommandWithArguments());
-	}
-
-	@Test
-	public void testGetCommandWithoutArgumentsXML() {
-		Command command = new Command();
-		command.setValue(TEST_COMMAND);
-		command.setRequiresParams(false);
-
-		CallableCommandExecutor callable = new CallableCommandExecutor(router, command, null, true, null);
-		assertEquals("ping 25 | display xml", callable.getCommandWithArguments());
-	}
-
-	@Test
-	public void testGetCommandWithoutArguments() {
-		Command command = new Command();
-		command.setValue(TEST_COMMAND);
-		command.setRequiresParams(false);
-
-		CallableCommandExecutor callable = new CallableCommandExecutor(router, command, null, false, null);
-		assertEquals("ping 25", callable.getCommandWithArguments());
-	}
-
-}
diff --git a/src/test/java/org/geant/lgservice/infrastructure/ssh/SSHHostTest.java b/src/test/java/org/geant/lgservice/infrastructure/ssh/SSHHostTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..9adef25bb14684f4c67041e91925ec77ff647c8e
--- /dev/null
+++ b/src/test/java/org/geant/lgservice/infrastructure/ssh/SSHHostTest.java
@@ -0,0 +1,110 @@
+package org.geant.lgservice.infrastructure.ssh;
+
+import ch.ethz.ssh2.Connection;
+import ch.ethz.ssh2.Session;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.SneakyThrows;
+import org.geant.lgservice.exceptions.TechnicalException;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.io.IOException;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
+import static org.geant.lgservice.infrastructure.ssh.SSHHost.ConnectionStateListener.State.DISCONNECTED;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.when;
+
+public class SSHHostTest {
+
+    private static final String HOSTNAME = "localhost";
+    private static final String RESPONSE_STRING = "A Response String";
+
+    private Connection connection = mock(Connection.class);
+    private Session session = mock(Session.class);
+    private SSHHost subject;
+
+    private PipedInputStream response;
+    private PipedOutputStream responseSource;
+
+    @Before
+    @SneakyThrows
+    public void setup() {
+        reset(connection);
+        when(connection.isAuthenticationComplete()).thenReturn(true);
+        when(connection.openSession()).thenReturn(session);
+        when(connection.getHostname()).thenReturn(HOSTNAME);
+
+        responseSource = new PipedOutputStream();
+        response = new PipedInputStream(responseSource);
+        when(session.getStdout()).thenReturn(response);
+        when(session.getStderr()).thenReturn(response);
+
+        responseSource.write(RESPONSE_STRING.getBytes());
+        responseSource.close();
+
+        subject = SSHHost.fromConnection(connection);
+    }
+
+    @Test
+    public void commandExecutedSuccessfully() {
+        when(session.getExitStatus()).thenReturn(0);
+        assertThat(subject.execute("some command")).isEqualTo(RESPONSE_STRING);
+    }
+
+    @Test
+    public void commandExitedWithNonZeroStatus() {
+        when(session.getExitStatus()).thenReturn(1);
+        assertThat(subject.execute("some command")).isEqualTo(RESPONSE_STRING);
+    }
+
+    @Test(expected = TechnicalException.class)
+    public void createWithUnAuthenticatedConnection() {
+        reset(connection);
+        when(connection.isAuthenticationComplete()).thenReturn(false);
+        SSHHost.fromConnection(connection);
+        fail("subject should have thrown an exception");
+    }
+
+    @Test(expected = TechnicalException.class)
+    public void sessionThrowsIOException() {
+        try {
+            Mockito.doThrow(new IOException()).when(session).execCommand(any(String.class));
+        } catch (IOException e) {
+            fail("Mock should not throw IOException", e);
+        }
+        subject.execute("some command");
+        fail("subject should have thrown an exception");
+    }
+
+    @Getter
+    @Setter
+    @NoArgsConstructor
+    private static class StateListenerOutput {
+        private String host;
+        private SSHHost.ConnectionStateListener.State state;
+    }
+
+    @Test
+    public void connectionStateListenersTriggered() {
+        final StateListenerOutput listenerOutput = new StateListenerOutput();
+
+        subject.withConnectionStateListener((host, state) -> {
+            listenerOutput.setHost(host.hostname());
+            listenerOutput.setState(state);
+        });
+
+        subject.connectionLost(new IOException());
+
+        assertThat(listenerOutput.getHost()).isEqualTo(HOSTNAME);
+        assertThat(listenerOutput.getState()).isEqualTo(DISCONNECTED);
+    }
+}
diff --git a/src/test/java/org/geant/lgservice/integration/BaseIT.java b/src/test/java/org/geant/lgservice/integration/BaseIT.java
new file mode 100644
index 0000000000000000000000000000000000000000..a0ab1a60a811290c37d943d90002562dfc2566b8
--- /dev/null
+++ b/src/test/java/org/geant/lgservice/integration/BaseIT.java
@@ -0,0 +1,97 @@
+package org.geant.lgservice.integration;
+
+import ch.ethz.ssh2.Connection;
+import ch.ethz.ssh2.Session;
+import com.alexlovett.jgivenextension.ExtendedScenarioTest;
+import com.github.tomakehurst.wiremock.junit.WireMockClassRule;
+import com.tngtech.jgiven.integration.spring.SpringStageCreator;
+import lombok.SneakyThrows;
+import org.geant.lgservice.LookingGlassServiceApplication;
+import org.geant.lgservice.infrastructure.ssh.ConnectionFactory;
+import org.geant.lgservice.infrastructure.ssh.ConnectionManager;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.web.server.LocalServerPort;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.lang.reflect.Field;
+import java.util.Map;
+
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.reset;
+import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
+import static org.springframework.test.annotation.DirtiesContext.ClassMode.BEFORE_CLASS;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(
+        webEnvironment = RANDOM_PORT,
+        classes = {LookingGlassServiceApplication.class, Config.class}
+)
+@TestPropertySource(properties = {
+        "server.ssl.enabled=false"
+})
+@DirtiesContext(classMode = BEFORE_CLASS)
+public abstract class BaseIT <GIVEN, WHEN, THEN> extends ExtendedScenarioTest<GIVEN, WHEN, THEN> implements BeanFactoryAware {
+    @LocalServerPort
+    protected int port;
+
+    @Autowired
+    private PortSink portSink;
+
+    @Autowired
+    private ConnectionFactory connectionFactory;
+
+    @Autowired
+    private ConnectionManager connectionManager;
+
+    @Autowired
+    private Connection connection;
+
+    @Autowired
+    private Session session;
+
+    interface PortSink {
+        void sink(int port);
+    }
+
+    @ClassRule
+    public static final WireMockClassRule INVENTORY_PROVIDER = new WireMockClassRule(wireMockConfig().dynamicPort());
+
+    @Before
+    @SneakyThrows
+    public void setup() {
+        portSink.sink(port);
+        INVENTORY_PROVIDER.resetAll();
+        reset(connectionFactory);
+        reset(connection);
+        reset(session);
+        Mockito.when(connectionFactory.createConnection(any(String.class))).thenReturn(connection);
+        Mockito.when(connection.openSession()).thenReturn(session);
+        Mockito.when(connection.isAuthenticationComplete()).thenReturn(true);
+
+        Field hostField = ConnectionManager.class.getDeclaredField("hosts");
+        hostField.setAccessible(true);
+        Map hosts = (Map)hostField.get(connectionManager);
+        hosts.clear();
+    }
+
+    @Override
+    public void setBeanFactory( BeanFactory beanFactory ) {
+        getScenario().setStageCreator( beanFactory.getBean( SpringStageCreator.class ) );
+    }
+
+    @BeforeClass
+    public static void setupClass() {
+        System.setProperty("inventoryprovider.baseUrl", String.format("http://localhost:%s/lg/", INVENTORY_PROVIDER.port()));
+    }
+}
diff --git a/src/test/java/org/geant/lgservice/integration/Config.java b/src/test/java/org/geant/lgservice/integration/Config.java
new file mode 100644
index 0000000000000000000000000000000000000000..137c9338bedb4d01bc112ffe364a5bc9bc4b3721
--- /dev/null
+++ b/src/test/java/org/geant/lgservice/integration/Config.java
@@ -0,0 +1,99 @@
+package org.geant.lgservice.integration;
+
+import ch.ethz.ssh2.Connection;
+import ch.ethz.ssh2.Session;
+import com.tngtech.jgiven.integration.spring.EnableJGiven;
+import org.geant.lgservice.infrastructure.ssh.ConnectionFactory;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpRequest;
+import org.springframework.http.client.ClientHttpRequestExecution;
+import org.springframework.http.client.ClientHttpRequestInterceptor;
+import org.springframework.http.client.ClientHttpResponse;
+import org.springframework.http.client.support.HttpRequestWrapper;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import java.io.IOException;
+import java.net.URI;
+
+import static java.util.Optional.of;
+import static org.geant.lgservice.integration.Context.withDefaults;
+
+@Configuration
+@EnableJGiven
+@ComponentScan(basePackages = { "org.geant.lgservice.integration"})
+@MockBean({
+        ConnectionFactory.class,
+        Connection.class,
+        Session.class
+})
+public class Config {
+
+    @Bean
+    public RestTemplate restTemplate(PortInjectingInterceptor interceptor) {
+        return new RestTemplateBuilder()
+                .rootUri("http://localhost")
+                .interceptors(interceptor)
+                .messageConverters(new MappingJackson2HttpMessageConverter())
+                .build();
+    }
+
+    @Bean
+    PortInjectingInterceptor portInjector() {
+        return new PortInjectingInterceptor();
+    }
+
+    static class PortInjectingInterceptor implements ClientHttpRequestInterceptor, BaseIT.PortSink {
+
+        private int port;
+
+        @Override
+        public ClientHttpResponse intercept(
+                HttpRequest httpRequest,
+                byte[] bytes,
+                ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
+
+            HttpRequest req = new HttpRequestWrapper(httpRequest) {
+                @Override
+                public URI getURI() {
+                    return of(port)
+                            .map(port -> UriComponentsBuilder.fromUri(super.getURI())
+                                    .host("localhost")
+                                    .port(port)
+                                    .build()
+                                    .toUri())
+                            .get();
+                }
+            };
+
+            return clientHttpRequestExecution.execute(req, bytes);
+        }
+
+        @Override
+        public void sink(int port) {
+            this.port = port;
+        }
+    }
+
+    @Bean
+    public Context context(){
+        return withDefaults();
+    }
+
+    @Bean
+    public MockMvc mockMvc(WebApplicationContext context) {
+        return MockMvcBuilders.webAppContextSetup(context)
+                .apply(SecurityMockMvcConfigurers.springSecurity())
+                .build();
+    }
+
+}
diff --git a/src/test/java/org/geant/lgservice/integration/Context.java b/src/test/java/org/geant/lgservice/integration/Context.java
new file mode 100644
index 0000000000000000000000000000000000000000..f97e2afd50709426ae60d282815d67645bff758c
--- /dev/null
+++ b/src/test/java/org/geant/lgservice/integration/Context.java
@@ -0,0 +1,151 @@
+package org.geant.lgservice.integration;
+
+import ch.ethz.ssh2.Connection;
+import ch.ethz.ssh2.Session;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.github.tomakehurst.wiremock.client.WireMock;
+import com.github.tomakehurst.wiremock.stubbing.StubMapping;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import org.geant.lgservice.security.LGUser;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.get;
+import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
+import static org.geant.lgservice.integration.Context.Router.INTERNAL;
+import static org.geant.lgservice.integration.Context.Router.PUBLIC;
+import static org.mockito.Mockito.mock;
+
+@Getter
+@Builder
+public class Context {
+
+    @Getter
+    @RequiredArgsConstructor
+    enum Router {
+        PUBLIC("mx2.tal.ee.geant.net"),
+        INTERNAL("mx1.cbg.uk.geant.net");
+
+        private final String host;
+    }
+
+    private Router router;
+    private String command;
+    private LGUser user;
+
+    private StubMapping inventoryProviderMapping;
+
+    private int responseStatus;
+    private SubmitResponseBody submitResponseBody;
+    private Collection<CommandGroup> commandResponseBody;
+
+    public Context withPublicRouter() {
+        this.router = PUBLIC;
+        return this;
+    }
+
+    public Context withInternalRouter() {
+        this.router = INTERNAL;
+        return this;
+    }
+
+    public Context withCommand(String command) {
+        this.command = command;
+        return this;
+    }
+
+    public Context withUser(LGUser user) {
+        this.user = user;
+        return this;
+    }
+
+    public Context withInventoryProviderStub(StubMapping mapping) {
+        this.inventoryProviderMapping = mapping;
+        return this;
+    }
+
+    public Context withResponseStatus(int responseStatus) {
+        this.responseStatus = responseStatus;
+        return this;
+    }
+
+    public Context withSubmitResponseBody(SubmitResponseBody submitResponseBody) {
+        this.submitResponseBody = submitResponseBody;
+        return this;
+    }
+
+    public Context withCommandResponseBody(Collection<CommandGroup> commandResponseBody) {
+        this.commandResponseBody = commandResponseBody;
+        return this;
+    }
+
+    public String getRouterHost() {
+        return router.getHost();
+    }
+
+    @SneakyThrows
+    public static Context withDefaults() {
+        return Context.builder()
+                .inventoryProviderMapping(stubFor(get("/lg/routers/all")
+                        .willReturn(WireMock.aResponse()
+                                .withStatus(200)
+                                .withHeader("Content-Type", "application/json")
+                                .withBodyFile("routers.json"))))
+                .build()
+                .withPublicRouter()
+                .withCommand("show config");
+    }
+
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class SubmitResponseBody {
+
+        private final Map<String, CommandResult> output;
+
+        @JsonCreator
+        public SubmitResponseBody(@JsonProperty("output") Map<String, CommandResult> output) {
+            this.output = output;
+        }
+
+        public CommandResult result() {
+            return output.values()
+                    .stream()
+                    .findFirst()
+                    .orElseThrow(() -> new AssertionError("There should be command result in the response"));
+        }
+
+        @Getter
+        public static class CommandResult {
+
+            private String routerName;
+            @JsonProperty("commandResult")
+            private String response;
+            private Long executionTime;
+        }
+    }
+
+    @Getter
+    public static class CommandGroup {
+
+        private List<Command> commands;
+        private String name;
+
+        @Getter
+        public static class Command {
+            private String name;
+            private String value;
+            private String description;
+            private boolean requiresParams;
+        }
+    }
+
+
+}
diff --git a/src/test/java/org/geant/lgservice/integration/scenario/GetCommandsIT.java b/src/test/java/org/geant/lgservice/integration/scenario/GetCommandsIT.java
new file mode 100644
index 0000000000000000000000000000000000000000..da1b79cd83491c2f084654001b4a04a78c33cac5
--- /dev/null
+++ b/src/test/java/org/geant/lgservice/integration/scenario/GetCommandsIT.java
@@ -0,0 +1,66 @@
+package org.geant.lgservice.integration.scenario;
+
+import org.geant.lgservice.integration.BaseIT;
+import org.junit.Test;
+
+import static org.geant.lgservice.integration.scenario.Then.ThenResponse.ThenCommandResponse.UserType.EXTERNAL;
+import static org.geant.lgservice.integration.scenario.Then.ThenResponse.ThenCommandResponse.UserType.INTERNAL;
+import static org.geant.lgservice.integration.scenario.Then.ThenResponse.ThenCommandResponse.UserType.PUBLIC;
+
+public class GetCommandsIT extends BaseIT<Given, When, Then> {
+
+    @Test
+    public void anonymous_user_requests_commands() {
+        given()
+                .a().user()
+                    .that().is().anonymous();
+
+        when()
+                .a().request_for_available_commands_is_sent();
+
+        then()
+                .the().response()
+                    .is().ok()
+                    .and().has_commands()
+                        .that().are_for_$_users(PUBLIC)
+                        .and().are_not_for_$_users(EXTERNAL)
+                        .and().are_not_for_$_users(INTERNAL);
+    }
+
+    @Test
+    public void external_user_requests_commands() {
+        given()
+                .a().user()
+                    .that().is().logged_in()
+                    .but().does_not_have_internal_access();
+
+        when()
+                .a().request_for_available_commands_is_sent();
+
+        then()
+                .the().response()
+                    .is().ok()
+                    .and().has_commands()
+                        .that().are_for_$_users(PUBLIC)
+                        .and().are_for_$_users(EXTERNAL)
+                        .and().are_not_for_$_users(INTERNAL);
+    }
+
+    @Test
+    public void internal_user_requests_commands() {
+        given()
+                .a().user()
+                    .that().is().logged_in();
+
+        when()
+                .a().request_for_available_commands_is_sent();
+
+        then()
+                .the().response()
+                    .is().ok()
+                    .and().has_commands()
+                        .that().are_for_$_users(PUBLIC)
+                        .and().are_for_$_users(EXTERNAL)
+                        .and().are_for_$_users(INTERNAL);
+    }
+}
diff --git a/src/test/java/org/geant/lgservice/integration/scenario/Given.java b/src/test/java/org/geant/lgservice/integration/scenario/Given.java
new file mode 100644
index 0000000000000000000000000000000000000000..272ee36db013c224e86fb0226cd245fcd9d1cea9
--- /dev/null
+++ b/src/test/java/org/geant/lgservice/integration/scenario/Given.java
@@ -0,0 +1,161 @@
+package org.geant.lgservice.integration.scenario;
+
+import ch.ethz.ssh2.Connection;
+import ch.ethz.ssh2.Session;
+import com.alexlovett.jgivenextension.ExtendedStage;
+import com.github.tomakehurst.wiremock.client.WireMock;
+import com.github.tomakehurst.wiremock.http.ResponseDefinition;
+import com.github.tomakehurst.wiremock.stubbing.StubMapping;
+import com.tngtech.jgiven.annotation.AfterStage;
+import com.tngtech.jgiven.annotation.BeforeScenario;
+import com.tngtech.jgiven.annotation.ExpectedScenarioState;
+import com.tngtech.jgiven.annotation.ProvidedScenarioState;
+import com.tngtech.jgiven.integration.spring.JGivenStage;
+import lombok.SneakyThrows;
+import org.geant.lgservice.infrastructure.ssh.ConnectionFactory;
+import org.geant.lgservice.integration.BaseIT;
+import org.geant.lgservice.integration.Context;
+import org.geant.lgservice.security.LGUser;
+import org.mockito.Mockito;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.get;
+import static org.geant.lgservice.integration.Context.withDefaults;
+import static org.mockito.ArgumentMatchers.any;
+
+@JGivenStage
+public class Given extends ExtendedStage<Given> {
+
+    @ProvidedScenarioState
+    private Context context;
+
+    @Autowired
+    @ProvidedScenarioState
+    private ConnectionFactory connectionFactory;
+
+    @Autowired
+    @ProvidedScenarioState
+    private Session session;
+
+    @BeforeScenario
+    public void setup() {
+        context = withDefaults();
+    }
+
+    public GivenUser user() {
+        return nest(GivenUser.class);
+    }
+
+    public static class GivenUser extends NestedStage<GivenUser, Given> {
+
+        @ExpectedScenarioState
+        private Context context;
+
+        public GivenUser anonymous() {
+            return self();
+        }
+
+        public GivenUser logged_in() {
+            context.withUser(new LGUser(
+                    "1",
+                    "barry",
+                    "scott",
+                    "barry.scott@geant.org",
+                    "GEANT Staff:All"));
+            return self();
+        }
+
+        public GivenUser does_not_have_internal_access() {
+            LGUser user = context.getUser();
+            context.withUser( new LGUser(
+                    user.getFederatedUser(),
+                    user.getGivenName(),
+                    user.getSurname(),
+                    user.getMail(),
+                    "GEANT_CO:GEANT Services:NREN Svc Mgmt:GEANT NRENs:members_GEANT NRENs"));
+            return self();
+        }
+    }
+
+    public GivenRouter router() {
+        return nest(GivenRouter.class);
+    }
+
+    public static class GivenRouter extends NestedStage<GivenRouter, Given> {
+
+        @ExpectedScenarioState
+        private Context context;
+
+        @Autowired
+        @ExpectedScenarioState
+        private ConnectionFactory connectionFactory;
+
+        @ExpectedScenarioState
+        private Session session;
+
+        public GivenRouter publicly_accessible() {
+            context.withPublicRouter();
+            return self();
+        }
+
+        public GivenRouter internal() {
+            context.withInternalRouter();
+            return self();
+        }
+
+        @SneakyThrows
+        public GivenRouter ok() {
+            PipedOutputStream responseSource = new PipedOutputStream();
+            PipedInputStream response = new PipedInputStream(responseSource);
+
+            Mockito.when(session.getStdout()).thenReturn(response);
+
+            Mockito.when(session.getExitStatus())
+                    .thenReturn(0);
+
+            Mockito.doAnswer(invocation -> {
+                responseSource.write(invocation.getArgument(0, String.class).getBytes());
+                responseSource.flush();
+                responseSource.close();
+                return null;
+            }).when(session).execCommand(any(String.class));
+
+            return self();
+        }
+    }
+
+    public GivenInventoryProvider inventory_provider() {
+        return nest(GivenInventoryProvider.class);
+    }
+
+    public static class GivenInventoryProvider extends NestedStage<GivenInventoryProvider, Given> {
+
+        @ExpectedScenarioState
+        private Context context;
+
+        public GivenInventoryProvider returns_(int status) {
+            context.withInventoryProviderStub(get("/lg/routers/all")
+                    .willReturn(WireMock.aResponse()
+                            .withStatus(status))
+                    .build());
+            return self();
+        }
+
+        public GivenInventoryProvider bad_body() {
+            StubMapping mapping = context.getInventoryProviderMapping();
+            ResponseDefinition response = mapping.getResponse();
+            mapping.setResponse(WireMock.aResponse().withStatus(response.getStatus())
+                    .withBody("{\"This is not valid response body\":true").build());
+            return self();
+        }
+    }
+
+    @AfterStage
+    public void setupMocks() {
+        BaseIT.INVENTORY_PROVIDER.resetAll();
+        BaseIT.INVENTORY_PROVIDER.addStubMapping(context.getInventoryProviderMapping());
+    }
+}
diff --git a/src/test/java/org/geant/lgservice/integration/scenario/SubmitCommandRequestIT.java b/src/test/java/org/geant/lgservice/integration/scenario/SubmitCommandRequestIT.java
new file mode 100644
index 0000000000000000000000000000000000000000..156fe3c887fd294ba63f20cd323a34395e6633dd
--- /dev/null
+++ b/src/test/java/org/geant/lgservice/integration/scenario/SubmitCommandRequestIT.java
@@ -0,0 +1,161 @@
+package org.geant.lgservice.integration.scenario;
+
+import org.geant.lgservice.integration.BaseIT;
+import org.junit.Test;
+
+public class SubmitCommandRequestIT extends BaseIT<Given, When, Then> {
+
+    @Test
+    public void anonymous_user_sends_command_to_public_router(){
+        given()
+                .a().user()
+                    .that().is().anonymous().backup()
+                .and().a().router()
+                    .that().is().publicly_accessible()
+                    .and().is().ok();
+
+        when()
+                .a().request_to_the_router_is_sent();
+
+        then()
+                .the().command_is_executed()
+                .and().the().result_is_returned();
+    }
+
+    @Test
+    public void anonymous_user_sends_command_to_internal_router() {
+        given()
+                .a().user()
+                    .that().is().anonymous().backup()
+                .and().a().router()
+                    .that().is().internal();
+
+        when()
+                .a().request_to_the_router_is_sent();
+
+        then()
+            .the().response()
+                .is().an().error()
+                    .with_a().status(401);
+    }
+
+    @Test
+    public void logged_in_user_sends_command_to_public_router() {
+        given()
+                .a().user()
+                    .that().is().logged_in()
+                    .backup()
+                .and().a().router()
+                    .that().is().publicly_accessible()
+                    .and().is().ok();
+
+        when()
+                .a().request_to_the_router_is_sent();
+
+        then()
+                .the().command_is_executed()
+                .and().the().result_is_returned();
+    }
+
+    @Test
+    public void logged_in_user_sends_command_to_internal_router() {
+        given()
+                .a().user()
+                    .that().is().logged_in()
+                    .backup()
+                .and().a().router()
+                    .that().is().internal()
+                    .and().is().ok();
+
+        when()
+                .a().request_to_the_router_is_sent();
+
+        then()
+                .the().command_is_executed()
+                .and().the().result_is_returned();
+    }
+
+    @Test
+    public void logged_in_user_without_internal_access_sends_command_to_internal_router() {
+        given()
+                .a().user()
+                    .that().is().logged_in()
+                    .but().is().does_not_have_internal_access()
+                    .backup()
+                .and().a().router()
+                    .that().is().internal();
+
+        when()
+                .a().request_to_the_router_is_sent();
+
+        then()
+                .the().response()
+                    .is().an().error()
+                        .with_a().status(403);
+    }
+
+    @Test
+    public void inventory_provider_returns_client_error() {
+        given()
+                .a().user()
+                    .that().is().anonymous()
+                    .backup()
+                .and().a().router()
+                    .that().is().publicly_accessible()
+                    .backup()
+                .and().inventory_provider()
+                    .returns_(400);
+
+        when()
+                .a().request_to_the_router_is_sent();
+
+        then()
+                .the().response()
+                    .is().an().error()
+                        .with().status(500);
+    }
+
+    @Test
+    public void inventory_provider_returns_server_error() {
+        given()
+                .a().user()
+                    .that().is().anonymous()
+                    .backup()
+                .and().a().router()
+                    .that().is().publicly_accessible()
+                    .backup()
+                .and().inventory_provider()
+                    .returns_(500);
+
+        when()
+                .a().request_to_the_router_is_sent();
+
+        then()
+                .the().response()
+                    .is().an().error()
+                        .with().status(500);
+    }
+
+    @Test
+    public void inventory_provider_returns_invalid_body() {
+        given()
+                .a().user()
+                    .that().is().anonymous()
+                    .backup()
+                .and().a().router()
+                    .that().is().publicly_accessible()
+                    .backup()
+                .and().inventory_provider()
+                    .returns_(200)
+                    .with().a().bad_body();
+
+        when()
+                .a().request_to_the_router_is_sent();
+
+        then()
+                .the().response()
+                    .is().an().error()
+                        .with().status(500);
+    }
+
+}
diff --git a/src/test/java/org/geant/lgservice/integration/scenario/Then.java b/src/test/java/org/geant/lgservice/integration/scenario/Then.java
new file mode 100644
index 0000000000000000000000000000000000000000..6af304fde81cdec81ed831c30f6fdfc6aab6cc02
--- /dev/null
+++ b/src/test/java/org/geant/lgservice/integration/scenario/Then.java
@@ -0,0 +1,112 @@
+package org.geant.lgservice.integration.scenario;
+
+import ch.ethz.ssh2.Session;
+import com.alexlovett.jgivenextension.ExtendedStage;
+import com.tngtech.jgiven.annotation.ExpectedScenarioState;
+import com.tngtech.jgiven.integration.spring.JGivenStage;
+import lombok.SneakyThrows;
+import org.geant.lgservice.integration.Context;
+import org.mockito.ArgumentCaptor;
+
+import java.util.stream.Collectors;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+@JGivenStage
+public class Then extends ExtendedStage<Then> {
+
+    @ExpectedScenarioState
+    private Context context;
+
+    @ExpectedScenarioState
+    private Session session;
+
+    @SneakyThrows
+    public Then command_is_executed() {
+        ArgumentCaptor<String> command = ArgumentCaptor.forClass(String.class);
+
+        verify(session, times(1))
+                .execCommand(command.capture());
+        assertThat(command.getValue()).isEqualTo(context.getCommand());
+        return self();
+    }
+
+    public Then result_is_returned() {
+        Context.SubmitResponseBody submitResponseBody = context.getSubmitResponseBody();
+        assertThat(submitResponseBody).isNotNull();
+
+        Context.SubmitResponseBody.CommandResult result = submitResponseBody.result();
+        assertThat(result).isNotNull();
+        assertThat(result.getRouterName()).isEqualTo(context.getRouterHost());
+        assertThat(result.getResponse()).isEqualTo(context.getCommand());
+
+        return self();
+    }
+
+    public ThenResponse response() {
+        return nest(ThenResponse.class);
+    }
+
+    public static class ThenResponse extends NestedStage<ThenResponse, Then> {
+
+        @ExpectedScenarioState
+        private Context context;
+
+        public ThenResponseError error() {
+            assertThat(context.getResponseStatus()).isNotEqualTo(200);
+            return nest(ThenResponseError.class);
+        }
+
+        public ThenResponse ok() {
+            assertThat(context.getResponseStatus()).isEqualTo(200);
+            return self();
+        }
+
+        public static class ThenResponseError extends NestedStage<ThenResponseError, ThenResponse> {
+
+            @ExpectedScenarioState
+            private Context context;
+
+            public ThenResponseError status(int status) {
+                assertThat(context.getResponseStatus()).isEqualTo(status);
+                return self();
+            }
+
+            public ThenResponseError with_a() {
+                return self();
+            }
+        }
+
+        public ThenCommandResponse has_commands() {
+            assertThat(context.getCommandResponseBody()).isNotNull();
+            return nest(ThenCommandResponse.class);
+        }
+
+        public static class ThenCommandResponse extends NestedStage<ThenCommandResponse, ThenResponse> {
+            @ExpectedScenarioState
+            private Context context;
+
+            public enum UserType {
+                PUBLIC,
+                EXTERNAL,
+                INTERNAL
+            }
+
+            public ThenCommandResponse are_for_$_users(UserType userType) {
+                assertThat(context.getCommandResponseBody().stream().map(Context.CommandGroup::getName).collect(Collectors.toList()))
+                        .contains(String.format("%s-group", userType.name().toLowerCase()));
+                return self();
+            }
+
+            public ThenCommandResponse are_not_for_$_users(UserType userType) {
+                assertThat(context.getCommandResponseBody().stream().map(Context.CommandGroup::getName).collect(Collectors.toList()))
+                        .doesNotContain(String.format("%s-group", userType.name().toLowerCase()));
+                return self();
+            }
+        }
+
+    }
+
+}
diff --git a/src/test/java/org/geant/lgservice/integration/scenario/When.java b/src/test/java/org/geant/lgservice/integration/scenario/When.java
new file mode 100644
index 0000000000000000000000000000000000000000..7fa83d8f0de4284e5bad4beddfacb70d9c355647
--- /dev/null
+++ b/src/test/java/org/geant/lgservice/integration/scenario/When.java
@@ -0,0 +1,132 @@
+package org.geant.lgservice.integration.scenario;
+
+import com.alexlovett.jgivenextension.ExtendedStage;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.tngtech.jgiven.annotation.ExpectedScenarioState;
+import com.tngtech.jgiven.integration.spring.JGivenStage;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.geant.lgservice.integration.Context;
+import org.geant.lgservice.integration.Context.SubmitResponseBody;
+import org.geant.lgservice.security.LGUser;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpMethod;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import java.lang.reflect.Type;
+import java.util.Collection;
+
+import static java.util.Collections.singletonList;
+import static org.springframework.http.HttpMethod.GET;
+import static org.springframework.http.HttpMethod.POST;
+import static org.springframework.http.MediaType.APPLICATION_JSON;
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
+
+@JGivenStage
+@Slf4j
+public class When extends ExtendedStage<When> {
+
+    @ExpectedScenarioState
+    private Context context;
+
+    @Autowired
+    private RestTemplate restTemplate;
+
+    @Autowired
+    private MockMvc mvc;
+
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
+    @SneakyThrows
+    public When request_to_the_router_is_sent() {
+        MockHttpServletResponse response = send(POST, "rest/submit", MAPPER.writeValueAsString(RequestBody.builder()
+                .command(context.getCommand())
+                .routerHost(context.getRouterHost())
+                .build()));
+
+        context.withResponseStatus(response.getStatus());
+        if (response.getStatus() == 200) {
+            String responseBody = response.getContentAsString();
+            log.info("response body = {}", responseBody);
+            context.withSubmitResponseBody(MAPPER.readValue(responseBody, SubmitResponseBody.class));
+        }
+
+        return self();
+    }
+
+    @SneakyThrows
+    public When request_for_available_commands_is_sent() {
+        MockHttpServletResponse response = send(GET,"rest/commands/all", null);
+        if (response.getStatus() == 200) {
+            String responseBody = response.getContentAsString();
+            log.info("response body = {}", responseBody);
+            context.withCommandResponseBody(MAPPER.readValue(responseBody, new TypeReference<Collection<Context.CommandGroup>>() {}));
+        }
+        return self();
+    }
+
+    @SneakyThrows
+    private MockHttpServletResponse send(HttpMethod method, String path, String body) {
+        MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders
+                .request(method, UriComponentsBuilder.newInstance().scheme("http").path(path).build().toUri())
+                .contentType(APPLICATION_JSON);
+
+        if (StringUtils.isNotBlank(body)) {
+            requestBuilder.content(body);
+        }
+
+        LGUser user = context.getUser();
+        if (user != null) {
+            requestBuilder.with(user(user));
+        }
+
+        MockHttpServletResponse response = mvc.perform(requestBuilder).andReturn().getResponse();
+        context.withResponseStatus(response.getStatus());
+        return response;
+    }
+
+    @Getter
+    public static class RequestBody {
+
+        private final Collection<Router> selectedRouters;
+        private final Command selectedCommand;
+
+        @Builder
+        private RequestBody(String routerHost, String command) {
+            this.selectedRouters = singletonList(Router.withHost(routerHost));
+            this.selectedCommand = Command.withCommand(command);
+        }
+
+        @RequiredArgsConstructor
+        @Getter
+        public static class Router {
+
+            private final String name;
+
+            public static Router withHost(String hostName) {
+                return new Router(hostName);
+            }
+        }
+
+        @RequiredArgsConstructor
+        @Getter
+        public static class Command {
+
+            private final String value;
+
+            public static Command withCommand(String command) {
+                return new Command(command);
+            }
+        }
+    }
+}
diff --git a/src/test/java/org/geant/lgservice/services/LookingGlassServiceImplTest.java b/src/test/java/org/geant/lgservice/services/LookingGlassServiceImplTest.java
deleted file mode 100644
index 31ed37f8968118af8a9da59251b9ed9e8ef10958..0000000000000000000000000000000000000000
--- a/src/test/java/org/geant/lgservice/services/LookingGlassServiceImplTest.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package org.geant.lgservice.services;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import lombok.SneakyThrows;
-import org.geant.lgservice.ecmr.CommandExecutor;
-import org.geant.lgservice.pojos.Command;
-import org.geant.lgservice.pojos.CommandOutput;
-import org.geant.lgservice.pojos.Router;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.Collections;
-import java.util.Map;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.when;
-
-public class LookingGlassServiceImplTest {
-
-    public static final String TEST_ROUTER_JSON = "{\"name\":\"router.geant.org\"}";
-    private static final ObjectMapper MAPPER = new ObjectMapper();
-
-    private CommandExecutor commandExecutor = mock(CommandExecutor.class);
-
-    private LookingGlassServiceImpl subject = new LookingGlassServiceImpl(null, commandExecutor);
-
-    private Router router;
-
-    @Before
-    @SneakyThrows
-    public void setup() {
-        reset(commandExecutor);
-        router = MAPPER.readValue(TEST_ROUTER_JSON, Router.class);
-    }
-
-    @Test
-    public void testQuery() {
-        Command command = new Command();
-        String arguments = "some args";
-
-        CommandOutput output = new CommandOutput();
-        output.setRouterName(router.getName());
-
-        when(commandExecutor.execute(any(Router.class), any(Command.class), any(String.class), anyBoolean())).thenReturn(output);
-
-        Map<String, CommandOutput> result = subject.submitQuery(Collections.singletonList(router), command, arguments, false);
-
-        assertThat(result).hasSize(1);
-        assertThat(result).containsKey(router.getName());
-        assertThat(result.get(router.getName())).isEqualTo(output);
-    }
-}
diff --git a/src/test/java/org/geant/lgservice/services/LookingGlassServiceTest.java b/src/test/java/org/geant/lgservice/services/LookingGlassServiceTest.java
deleted file mode 100644
index 4669aad648d8c1b60982c7caa1a9544737bc45f5..0000000000000000000000000000000000000000
--- a/src/test/java/org/geant/lgservice/services/LookingGlassServiceTest.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package org.geant.lgservice.services;
-
-import static org.junit.Assert.assertEquals;
-
-import java.util.List;
-
-import org.geant.lgservice.pojos.Group;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.junit4.SpringRunner;
-
-@RunWith(SpringRunner.class)
-@SpringBootTest
-public class LookingGlassServiceTest {
-
-	@Autowired
-	LookingGlassService fixture;
-
-	@Test
-	public void test() {
-		List<Group> commandGroups = fixture.getAllCommands(null);
-		assertEquals("number of commands not correct", 1, commandGroups.size());
-		assertEquals("group name not correct", "test-group", commandGroups.get(0).getName());
-		assertEquals("command name not correct", "test-name", commandGroups.get(0).getCommands().get(0).getName());
-	}
-
-}
diff --git a/src/test/resources/__files/routers.json b/src/test/resources/__files/routers.json
new file mode 100644
index 0000000000000000000000000000000000000000..c3e1d17320216e3e591da8824fdf8ecf79a5ffd7
--- /dev/null
+++ b/src/test/resources/__files/routers.json
@@ -0,0 +1,470 @@
+[
+  {
+    "equipment name": "mx1.cbg.uk.geant.net",
+    "pop": {
+      "abbreviation": "lab",
+      "city": "Cambridge",
+      "country": "UK",
+      "country code": "UK",
+      "latitude": 0.0,
+      "longitude": 0.0,
+      "name": "DANTE Lab"
+    },
+    "type": "INTERNAL"
+  },
+  {
+    "equipment name": "mx2.tal.ee.geant.net",
+    "pop": {
+      "abbreviation": "tal",
+      "city": "Tallinn",
+      "country": "Estonia",
+      "country code": "EE",
+      "latitude": 59.434825,
+      "longitude": 24.71385,
+      "name": "Tallinn"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.lon.uk.geant.net",
+    "pop": {
+      "abbreviation": "lon",
+      "city": "London",
+      "country": "England",
+      "country code": "UK",
+      "latitude": 51.498652777778,
+      "longitude": -0.015805555555556,
+      "name": "London"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.pra.cz.geant.net",
+    "pop": {
+      "abbreviation": "pra",
+      "city": "Prague",
+      "country": "Czech Republic",
+      "country code": "CZ",
+      "latitude": 50.101847222222,
+      "longitude": 14.391738888889,
+      "name": "Prague"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.sof.bg.geant.net",
+    "pop": {
+      "abbreviation": "sof",
+      "city": "Sofia",
+      "country": "Bulgaria",
+      "country code": "BG",
+      "latitude": 42.675758333333,
+      "longitude": 23.370986111111,
+      "name": "Sofia"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx2.rig.lv.geant.net",
+    "pop": {
+      "abbreviation": "rig",
+      "city": "Riga",
+      "country": "Latvia",
+      "country code": "LV",
+      "latitude": 56.948344444444,
+      "longitude": 24.118,
+      "name": "Riga"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.lon2.uk.geant.net",
+    "pop": {
+      "abbreviation": "lon2",
+      "city": "Slough",
+      "country": "UK",
+      "country code": "UK",
+      "latitude": 51.521986111111,
+      "longitude": 0.62331666666667,
+      "name": "London 2 (Slough)"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.ham.de.geant.net",
+    "pop": {
+      "abbreviation": "ham",
+      "city": "Hamburg",
+      "country": "Germany",
+      "country code": "DE",
+      "latitude": 53.550902777778,
+      "longitude": 10.046486111111,
+      "name": "Hamburg"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.poz.pl.geant.net",
+    "pop": {
+      "abbreviation": "poz",
+      "city": "Poznan",
+      "country": "Poland",
+      "country code": "PL",
+      "latitude": 52.411775,
+      "longitude": 16.917561111111,
+      "name": "Poznan"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.ath2.gr.geant.net",
+    "pop": {
+      "abbreviation": "ath2",
+      "city": "Attiki",
+      "country": "Greece",
+      "country code": "GR",
+      "latitude": 37.98,
+      "longitude": 23.73,
+      "name": "Athens 2"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.kau.lt.geant.net",
+    "pop": {
+      "abbreviation": "kau",
+      "city": "Kaunas",
+      "country": "Lithuania",
+      "country code": "LT",
+      "latitude": 54.940672222222,
+      "longitude": 24.018013888889,
+      "name": "Kaunas"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.bud.hu.geant.net",
+    "pop": {
+      "abbreviation": "bud",
+      "city": "Budapest",
+      "country": "Hungary",
+      "country code": "HU",
+      "latitude": 47.517902777778,
+      "longitude": 19.055436111111,
+      "name": "Budapest"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx2.bru.be.geant.net",
+    "pop": {
+      "abbreviation": "bru",
+      "city": "Brussels",
+      "country": "Belgium",
+      "country code": "BE",
+      "latitude": 50.857002777778,
+      "longitude": 4.4115694444444,
+      "name": "Brussels"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx2.bra.sk.geant.net",
+    "pop": {
+      "abbreviation": "bra",
+      "city": "Bratislava",
+      "country": "Slovakia",
+      "country code": "SK",
+      "latitude": 48.119027777778,
+      "longitude": 17.0957,
+      "name": "Bratislava"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx2.kau.lt.geant.net",
+    "pop": {
+      "abbreviation": "kau",
+      "city": "Kaunas",
+      "country": "Lithuania",
+      "country code": "LT",
+      "latitude": 54.940672222222,
+      "longitude": 24.018013888889,
+      "name": "Kaunas"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx2.cbg.uk",
+    "pop": {
+      "abbreviation": "oc",
+      "city": "CAMBRIDGE",
+      "country": "UK",
+      "country code": "UK",
+      "latitude": 0.0,
+      "longitude": 0.0,
+      "name": "Cambridge OC"
+    },
+    "type": "INTERNAL"
+  },
+  {
+    "equipment name": "mx1.buc.ro.geant.net",
+    "pop": {
+      "abbreviation": "buc",
+      "city": "Bucharest",
+      "country": "Romania",
+      "country code": "RO",
+      "latitude": 44.444741666667,
+      "longitude": 26.096422222222,
+      "name": "Bucharest"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.vie.at.geant.net",
+    "pop": {
+      "abbreviation": "vie",
+      "city": "Vienna",
+      "country": "Austria",
+      "country code": "AT",
+      "latitude": 48.268925,
+      "longitude": 16.410194444444,
+      "name": "Vienna"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.mil2.it.geant.net",
+    "pop": {
+      "abbreviation": "mil2",
+      "city": "Milan",
+      "country": "Italy",
+      "country code": "IT",
+      "latitude": 45.475522222222,
+      "longitude": 9.1033194444444,
+      "name": "Milan 2 Caldera"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.ams.nl.geant.net",
+    "pop": {
+      "abbreviation": "ams",
+      "city": "Amsterdam",
+      "country": "Netherlands",
+      "country code": "NL",
+      "latitude": 52.356363888889,
+      "longitude": 4.9529555555556,
+      "name": "Amsterdam"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "sw2.am.office.geant.net",
+    "pop": {
+      "abbreviation": "am",
+      "city": "Amsterdam",
+      "country": "Netherlands",
+      "country code": "NL",
+      "latitude": 52.313305555556,
+      "longitude": 4.9491111111111,
+      "name": "Amsterdam GEANT Office"
+    },
+    "type": "INTERNAL"
+  },
+  {
+    "equipment name": "mx1.fra.de.geant.net",
+    "pop": {
+      "abbreviation": "fra",
+      "city": "Frankfurt",
+      "country": "Germany",
+      "country code": "DE",
+      "latitude": 50.120294444444,
+      "longitude": 8.7358444444444,
+      "name": "Frankfurt"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.lis.pt.geant.net",
+    "pop": {
+      "abbreviation": "lis2",
+      "city": "Lisbon",
+      "country": "Portugal",
+      "country code": "PT",
+      "latitude": 38.759097222222,
+      "longitude": -9.1421944444444,
+      "name": "Lisbon 2"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx2.lju.si.geant.net",
+    "pop": {
+      "abbreviation": "lju",
+      "city": "Ljubljana",
+      "country": "Slovenia",
+      "country code": "SI",
+      "latitude": 46.042366666667,
+      "longitude": 14.488719444444,
+      "name": "Ljubljana"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.dub.ie.geant.net",
+    "pop": {
+      "abbreviation": "dub",
+      "city": "Dublin",
+      "country": "Ireland",
+      "country code": "IE",
+      "latitude": 53.291980555556,
+      "longitude": -6.4147333333333,
+      "name": "Dublin (City West)"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx2.lis.pt.geant.net",
+    "pop": {
+      "abbreviation": "lis",
+      "city": "Lisbon",
+      "country": "Portugal",
+      "country code": "PT",
+      "latitude": 38.759097222222,
+      "longitude": -9.1421944444444,
+      "name": "Lisbon"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.mad.es.geant.net",
+    "pop": {
+      "abbreviation": "mad",
+      "city": "Alcobendas  - Madrid",
+      "country": "Spain",
+      "country code": "ES",
+      "latitude": 40.536477777778,
+      "longitude": -3.6488027777778,
+      "name": "Madrid"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.tal.ee.geant.net",
+    "pop": {
+      "abbreviation": "tal",
+      "city": "Tallinn",
+      "country": "Estonia",
+      "country code": "EE",
+      "latitude": 59.434825,
+      "longitude": 24.71385,
+      "name": "Tallinn"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx2.ath.gr.geant.net",
+    "pop": {
+      "abbreviation": "ath",
+      "city": "Athens",
+      "country": "Greece",
+      "country code": "GR",
+      "latitude": 37.973086111111,
+      "longitude": 23.745555555556,
+      "name": "Athens"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx4 - Camb Lab",
+    "pop": {
+      "abbreviation": "lab",
+      "city": "Cambridge",
+      "country": "UK",
+      "country code": "UK",
+      "latitude": 0.0,
+      "longitude": 0.0,
+      "name": "DANTE Lab"
+    },
+    "type": "INTERNAL"
+  },
+  {
+    "equipment name": "mx1.par.fr.geant.net",
+    "pop": {
+      "abbreviation": "par",
+      "city": "Paris",
+      "country": "France",
+      "country code": "FR",
+      "latitude": 48.904630555556,
+      "longitude": 2.3713555555556,
+      "name": "Paris"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.dub2.ie.geant.net",
+    "pop": {
+      "abbreviation": "dub2",
+      "city": "Dublin 12",
+      "country": "Ireland",
+      "country code": "IE",
+      "latitude": 53.33425,
+      "longitude": -6.3655444444444,
+      "name": "Dublin 2 - Parkwest"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "sw3.am.office.geant.net",
+    "pop": {
+      "abbreviation": "am",
+      "city": "Amsterdam",
+      "country": "Netherlands",
+      "country code": "NL",
+      "latitude": 52.313305555556,
+      "longitude": 4.9491111111111,
+      "name": "Amsterdam GEANT Office"
+    },
+    "type": "INTERNAL"
+  },
+  {
+    "equipment name": "mx2.zag.hr.geant.net",
+    "pop": {
+      "abbreviation": "zag",
+      "city": "Zagreb",
+      "country": "Crotia",
+      "country code": "HR",
+      "latitude": 45.792030555556,
+      "longitude": 15.969494444444,
+      "name": "Zagreb"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.mar.fr.geant.net",
+    "pop": {
+      "abbreviation": "mar",
+      "city": "Marseille",
+      "country": "France",
+      "country code": "FR",
+      "latitude": 43.311027777778,
+      "longitude": 5.3738888888889,
+      "name": "Marseille"
+    },
+    "type": "CORE"
+  },
+  {
+    "equipment name": "mx1.gen.ch.geant.net",
+    "pop": {
+      "abbreviation": "gen",
+      "city": "Geneva",
+      "country": "Switzerland",
+      "country code": "CH",
+      "latitude": 46.232194444444,
+      "longitude": 6.0457638888889,
+      "name": "Geneva"
+    },
+    "type": "CORE"
+  }
+]
\ No newline at end of file
diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml
index 5bddf85b14c909a998a8a58bd624eedd5ffafd07..3ceaa22a4b8c1e14e93d328803c1aaf7408735ab 100644
--- a/src/test/resources/application.yml
+++ b/src/test/resources/application.yml
@@ -10,24 +10,6 @@ configuration:
 security: 
   saml2: 
     metadataURL: "https://login.terena.org/wayf/saml2/idp/metadata"
-server: 
-  port: 8009
-  ssl: 
-    enabled: true
-spring: 
-  datasource: 
-    driverClassName: com.mysql.jdbc.Driver
-    password: opsro
-    url: "jdbc:mysql://test-opsdb01.geant.net:3306/opsdb"
-    username: opsro
-  jpa: 
-    generate-ddl: true
-    hibernate.ddl-auto: update
-    properties: 
-      hibernate: 
-        dialect: org.hibernate.dialect.MySQL5Dialect
-        show-sql: false
-    show-sql: false
 saml:
     metadata-url: https://login.terena.org/wayf/saml2/idp/metadata
     entity-name: https://test-lg.geant.net/saml
diff --git a/src/test/resources/commands_external.xml b/src/test/resources/commands_external.xml
new file mode 100644
index 0000000000000000000000000000000000000000..c955898fc7b6c9c0251af4d73723c329f836c7b1
--- /dev/null
+++ b/src/test/resources/commands_external.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- List of Commands, here name element contains the command name that is
+	visible on UI and value contains actual command. -->
+<commands>
+	<group name="public-group">
+		<command>
+			<name>public-command</name>
+			<value>public-command</value>
+			<description>public-command</description>
+			<requiresParams>true</requiresParams>
+		</command>
+	</group>
+	<group name="external-group">
+		<command>
+			<name>external-command</name>
+			<value>external-command</value>
+			<description>external-command</description>
+			<requiresParams>true</requiresParams>
+		</command>
+	</group>
+</commands>
\ No newline at end of file
diff --git a/src/test/resources/commands_internal.xml b/src/test/resources/commands_internal.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f1ef017e54a71a6ffa5e8766bc4abf51650fc32b
--- /dev/null
+++ b/src/test/resources/commands_internal.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- List of Commands, here name element contains the command name that is
+	visible on UI and value contains actual command. -->
+<commands>
+	<group name="public-group">
+		<command>
+			<name>public-command</name>
+			<value>public-command</value>
+			<description>public-command</description>
+			<requiresParams>true</requiresParams>
+		</command>
+	</group>
+	<group name="external-group">
+		<command>
+			<name>external-command</name>
+			<value>external-command</value>
+			<description>external-command</description>
+			<requiresParams>true</requiresParams>
+		</command>
+	</group>
+	<group name="internal-group">
+		<command>
+			<name>internal-command</name>
+			<value>internal-command</value>
+			<description>internal-command</description>
+			<requiresParams>true</requiresParams>
+		</command>
+	</group>
+</commands>
\ No newline at end of file
diff --git a/src/test/resources/commands_public.xml b/src/test/resources/commands_public.xml
index 8b785de5ab5590d7731b24c76472c263060f9888..70b1e69507f4ca895604c968a921ccb81ea9d1d9 100644
--- a/src/test/resources/commands_public.xml
+++ b/src/test/resources/commands_public.xml
@@ -1,13 +1,13 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- List of Commands, here name element contains the command name that is 
-	visible on UI and value contains actual command. -->
-<commands>
-	<group name="test-group">
-		<command>
-			<name>test-name</name>
-			<value>test-value</value>
-			<description>test-description</description>
-			<requiresParams>true</requiresParams>
-		</command>
-	</group>
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- List of Commands, here name element contains the command name that is
+	visible on UI and value contains actual command. -->
+<commands>
+	<group name="public-group">
+		<command>
+			<name>public-command</name>
+			<value>public-command</value>
+			<description>public-command</description>
+			<requiresParams>true</requiresParams>
+		</command>
+	</group>
 </commands>
\ No newline at end of file