Repository: EnMasseProject/enmasse
Pull request #2465 (2019-03-18 14:10:49)

Replace javascript authservice with a java one
This removes some special handling of none-authservice in the makefiles,
and allowing us to build the whole project only with maven and go. The
implementation is supposed to be a drop-in replacement of the existing.

lulf: @enmasse-ci run tests testcase=standard.auth.AuthenticationTest
lulf: @enmasse-ci run tests testcase=standard.auth.AuthenticationTest

Patch:
diff --git a/.checkstyle/checkstyle.xml b/.checkstyle/checkstyle.xml
index 129ff4e03..12ac97a11 100644
--- a/.checkstyle/checkstyle.xml
+++ b/.checkstyle/checkstyle.xml
@@ -10,7 +10,13 @@
         <property name="fileExtensions" value="java"/>
     </module>
     <module name="SuppressionFilter">
-      <property name="file" value="${checkstyle.suppressions.file}" />
+        <property name="file" value="${checkstyle.suppressions.file}" />
+    </module>
+    <module name="TreeWalker">
+        <module name="FallThrough">
+            <property name="reliefPattern" value="FALL-THROUGH"/>
+        </module>
+        <module name="StringLiteralEquality"/>
     </module>
 </module>
 
diff --git a/.gitignore b/.gitignore
index 281213241..a576adb84 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,6 +14,7 @@
 **/.project
 **/.classpath
 .factorypath
+**/.checkstyle
 
 # VS Code
 .vscode
@@ -31,4 +32,4 @@ go/
 
 # Documentation specific
 documentation/html
-scripts/swagger2markup.jar
\ No newline at end of file
+scripts/swagger2markup.jar
diff --git a/.jenkins/Jenkinsfile-pr b/.jenkins/Jenkinsfile-pr
index ca244c171..ef1377f16 100644
--- a/.jenkins/Jenkinsfile-pr
+++ b/.jenkins/Jenkinsfile-pr
@@ -9,6 +9,8 @@ pipeline {
     parameters {
         //execute all tests as default for each new Pull request
         string(name: 'TEST_CASE', defaultValue: '', description: 'maven parameter for executing specific tests')
+        string(name: 'TEST_PROFILE', defaultValue: 'systemtests-pr', description: 'maven profile for tests')
+        string(name: 'UPGRADE_FROM', defaultValue: '0.26.2', description: 'base version which is upgraded')
     }
     options {
         timeout(time: 10, unit: 'HOURS')
@@ -49,8 +51,22 @@ pipeline {
                 script {
                     if (env.ghprbCommentBody.contains('testcase=')) {
                         env.TEST_CASE = env.ghprbCommentBody.split('testcase=')[1].split(/\s/)[0]
+                    } else {
+                        env.TEST_CASE = params.TEST_CASE
+                    }
+                    if (env.ghprbCommentBody.contains('profile=')) {
+                        env.TEST_PROFILE = env.ghprbCommentBody.split('profile=')[1].split(/\s/)[0]
+                    } else {
+                        env.TEST_PROFILE = params.TEST_PROFILE
+                    }
+                    if (env.ghprbCommentBody.contains('upgrade_from=')) {
+                        env.UPGRADE_FROM = env.ghprbCommentBody.split('upgrade_from=')[1].split(/\s/)[0]
+                    } else {
+                        env.UPGRADE_FROM = params.UPGRADE_FROM
                     }
                     echo "TEST_CASE: ${env.TEST_CASE}"
+                    echo "TEST_PROFILE: ${env.TEST_PROFILE}"
+                    echo "UPGRADE_FROM: ${env.UPGRADE_FROM}"
                 }
             }
         }
@@ -78,7 +94,129 @@ pipeline {
                 sh 'make docker_push'
             }
         }
-        stage('install enmasse') {
+
+        //----------------------------------------------------------------------------------------------------
+        // upgrade PR build
+        //----------------------------------------------------------------------------------------------------
+        stage('[upgrade] download released version') {
+            when {
+                expression { env.TEST_PROFILE == 'systemtests-upgrade' }
+            }
+            steps {
+                sh "git checkout -- agent && git checkout tags/${env.UPGRADE_FROM}"
+                sh "sh ./systemtests/scripts/download_released_enmasse.sh ${env.UPGRADE_FROM}"
+            }
+        }
+        stage('[upgrade] deploy enmasse') {
+            when {
+                expression { env.TEST_PROFILE == 'systemtests-upgrade' }
+            }
+            steps {
+                withCredentials([string(credentialsId: 'openshift-host', variable: 'OPENSHIFT_URL'), usernamePassword(credentialsId: 'openshift-credentials', passwordVariable: 'OPENSHIFT_PASSWD', usernameVariable: 'OPENSHIFT_USER')]) {
+                    script {
+                        lib.installEnmasse(env.UPGRADE_FROM, false, false, false)
+                    }
+                }
+            }
+        }
+        stage('[upgrade] run systemtests before upgrade') {
+            when {
+                expression { env.TEST_PROFILE == 'systemtests-upgrade' }
+            }
+            environment {
+                USE_DUMMY_ADDRESS = true
+                SKIP_CLEANUP = true
+                SYSTEMTESTS_UPGRADED = false
+            }
+            steps {
+                withCredentials([string(credentialsId: 'openshift-host', variable: 'OPENSHIFT_URL'), usernamePassword(credentialsId: 'openshift-credentials', passwordVariable: 'OPENSHIFT_PASSWD', usernameVariable: 'OPENSHIFT_USER')]) {
+                    script {
+                        lib.runSystemtests(env.CORES_DIR, 'systemtests-upgrade', 'upgrade.**')
+                    }
+                }
+            }
+        }
+        stage('[upgrade] upgrade enmasse') {
+            when {
+                expression { env.TEST_PROFILE == 'systemtests-upgrade' }
+            }
+            environment {
+                TAG = "${BUILD_TAG}"
+            }
+            steps {
+                withCredentials([string(credentialsId: 'openshift-host', variable: 'OPENSHIFT_URL'), usernamePassword(credentialsId: 'openshift-credentials', passwordVariable: 'OPENSHIFT_PASSWD', usernameVariable: 'OPENSHIFT_USER')]) {
+                    script {
+                        sh "git checkout ${sha1}"
+                        lib.installEnmasse(env.TAG, true, true, true)
+                    }
+                }
+            }
+        }
+        stage('[upgrade] run systemtests after upgrade') {
+            when {
+                expression { env.TEST_PROFILE == 'systemtests-upgrade' }
+            }
+            environment {
+                USE_DUMMY_ADDRESS = true
+                SKIP_CLEANUP = true
+                SYSTEMTESTS_UPGRADED = true
+            }
+            steps {
+                withCredentials([string(credentialsId: 'openshift-host', variable: 'OPENSHIFT_URL'), usernamePassword(credentialsId: 'openshift-credentials', passwordVariable: 'OPENSHIFT_PASSWD', usernameVariable: 'OPENSHIFT_USER')]) {
+                    script {
+                        lib.runSystemtests(env.CORES_DIR, 'systemtests-upgrade', 'upgrade.**')
+                    }
+                }
+            }
+        }
+        stage('[upgrade] download released version again') {
+            when {
+                expression { env.TEST_PROFILE == 'systemtests-upgrade' }
+            }
+            steps {
+                sh "sh ./systemtests/scripts/download_released_enmasse.sh ${env.UPGRADE_FROM}"
+            }
+        }
+        stage('[upgrade] downgrade enmasse') {
+            when {
+                expression { env.TEST_PROFILE == 'systemtests-upgrade' }
+            }
+            environment {
+                TAG = "${env.UPGRADE_FROM}"
+            }
+            steps {
+                withCredentials([string(credentialsId: 'openshift-host', variable: 'OPENSHIFT_URL'), usernamePassword(credentialsId: 'openshift-credentials', passwordVariable: 'OPENSHIFT_PASSWD', usernameVariable: 'OPENSHIFT_USER')]) {
+                    script {
+                        sh "git checkout tags/${env.UPGRADE_FROM}"
+                        lib.installEnmasse(env.UPGRADE_FROM, true, true, false)
+                    }
+                }
+            }
+        }
+        stage('[upgrade] run systemtests after downgrade') {
+            when {
+                expression { env.TEST_PROFILE == 'systemtests-upgrade' }
+            }
+            environment {
+                USE_DUMMY_ADDRESS = true
+                SKIP_CLEANUP = false
+                SYSTEMTESTS_UPGRADED = true
+            }
+            steps {
+                withCredentials([string(credentialsId: 'openshift-host', variable: 'OPENSHIFT_URL'), usernamePassword(credentialsId: 'openshift-credentials', passwordVariable: 'OPENSHIFT_PASSWD', usernameVariable: 'OPENSHIFT_USER')]) {
+                    script {
+                        lib.runSystemtests(env.CORES_DIR, 'systemtests-upgrade', 'upgrade.**')
+                    }
+                }
+            }
+        }
+        //----------------------------------------------------------------------------------------------------
+        // standard PR build
+        //----------------------------------------------------------------------------------------------------
+        stage('[PR] install enmasse') {
+            when {
+                expression { env.TEST_PROFILE != 'systemtests-upgrade' }
+            }
             steps {
                 withCredentials([string(credentialsId: 'openshift-host', variable: 'OPENSHIFT_URL'), usernamePassword(credentialsId: 'openshift-credentials', passwordVariable: 'OPENSHIFT_PASSWD', usernameVariable: 'OPENSHIFT_USER')]) {
                     script {
@@ -87,11 +225,14 @@ pipeline {
                 }
             }
         }
-        stage('system tests') {
+        stage('[PR] system tests') {
+            when {
+                expression { env.TEST_PROFILE != 'systemtests-upgrade' }
+            }
             steps {
                 withCredentials([string(credentialsId: 'openshift-host', variable: 'OPENSHIFT_URL'), usernamePassword(credentialsId: 'openshift-credentials', passwordVariable: 'OPENSHIFT_PASSWD', usernameVariable: 'OPENSHIFT_USER')]) {
                     script {
-                        lib.runSystemtests(env.CORES_DIR, 'systemtests-pr', env.TEST_CASE)
+                        lib.runSystemtests(env.CORES_DIR, env.TEST_PROFILE, env.TEST_CASE)
                     }
                 }
             }
diff --git a/Makefile b/Makefile
index 2f8b0d142..72dbe6dd5 100644
--- a/Makefile
+++ b/Makefile
@@ -1,8 +1,7 @@
 TOPDIR          := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
 include $(TOPDIR)/Makefile.env.mk
 
-BUILD_DIRS       = none-authservice
-DOCKER_DIRS      = agent topic-forwarder artemis broker-plugin api-server address-space-controller standard-controller keycloak-plugin router router-metrics mqtt-gateway mqtt-lwt service-broker iot/iot-tenant-service iot/iot-auth-service iot/iot-device-registry iot/iot-http-adapter iot/iot-mqtt-adapter
+DOCKER_DIRS      = agent topic-forwarder artemis broker-plugin api-server address-space-controller none-authservice standard-controller keycloak-plugin router router-metrics mqtt-gateway mqtt-lwt service-broker iot/iot-tenant-service iot/iot-auth-service iot/iot-device-registry iot/iot-http-adapter iot/iot-mqtt-adapter
 FULL_BUILD       = true
 
 GO_DIRS          = \
@@ -11,7 +10,6 @@ GO_DIRS          = \
 	iot/iot-gc \
 
 DOCKER_TARGETS   = docker_build docker_tag docker_push clean
-BUILD_TARGETS    = init build test package $(DOCKER_TARGETS) coverage
 INSTALLDIR       = $(CURDIR)/templates/install
 SKIP_TESTS      ?= false
 
@@ -25,7 +23,7 @@ ifneq ($(strip $(PROJECT_DISPLAY_NAME)),)
 	MAVEN_ARGS+="-Dapplication.display.name=$(PROJECT_DISPLAY_NAME)"
 endif
 
-all: init build_java docker_build templates
+all: build_java docker_build templates
 
 test: test_go
 
@@ -93,10 +91,6 @@ java_coverage:
 	mvn test -Pcoverage -B $(MAVEN_ARGS)
 	mvn jacoco:report-aggregate
 
-$(BUILD_TARGETS): $(BUILD_DIRS)
-$(BUILD_DIRS):
-	$(MAKE) FULL_BUILD=$(FULL_BUILD) -C $@ $(MAKECMDGOALS)
-
 $(DOCKER_TARGETS): $(DOCKER_DIRS) $(GO_DIRS)
 $(DOCKER_DIRS):
 	$(MAKE) FULL_BUILD=$(FULL_BUILD) -C $@ $(MAKECMDGOALS)
@@ -133,4 +127,4 @@ docu_clean: docu_htmlclean
 	rm scripts/swagger2markup.jar
 
 .PHONY: test_go_vet test_go_plain
-.PHONY: all $(BUILD_TARGETS) $(GO_DIRS) $(DOCKER_TARGETS) $(BUILD_DIRS) $(DOCKER_DIRS) build_java test_go systemtests clean_java docu_html docu_swagger docu_htmlclean docu_check
+.PHONY: all $(GO_DIRS) $(DOCKER_TARGETS) $(DOCKER_DIRS) build_java test_go systemtests clean_java docu_html docu_swagger docu_htmlclean docu_check
diff --git a/api-model/src/main/java/io/enmasse/iot/model/v1/DownstreamStrategy.java b/api-model/src/main/java/io/enmasse/iot/model/v1/DownstreamStrategy.java
index 3f20716ae..49282ca4e 100644
--- a/api-model/src/main/java/io/enmasse/iot/model/v1/DownstreamStrategy.java
+++ b/api-model/src/main/java/io/enmasse/iot/model/v1/DownstreamStrategy.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018, EnMasse authors.
+ * Copyright 2019, EnMasse authors.
  * License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
  */
 package io.enmasse.iot.model.v1;
diff --git a/api-model/src/main/java/io/enmasse/iot/model/v1/ExternalDownstreamStrategy.java b/api-model/src/main/java/io/enmasse/iot/model/v1/ExternalDownstreamStrategy.java
index 3a3535623..f5261bcbe 100644
--- a/api-model/src/main/java/io/enmasse/iot/model/v1/ExternalDownstreamStrategy.java
+++ b/api-model/src/main/java/io/enmasse/iot/model/v1/ExternalDownstreamStrategy.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018, EnMasse authors.
+ * Copyright 2019, EnMasse authors.
  * License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
  */
 
@@ -26,7 +26,6 @@ import io.sundr.builder.annotations.Inline;
                 value = "done"
                 )
         )
-//FIXME: implement missing fields and remove ignore annotation
 @JsonIgnoreProperties(ignoreUnknown = true)
 @JsonInclude(NON_NULL)
 public class ExternalDownstreamStrategy {
diff --git a/api-model/src/main/java/io/enmasse/iot/model/v1/ImageOverride.java b/api-model/src/main/java/io/enmasse/iot/model/v1/ImageOverride.java
new file mode 100644
index 000000000..e06620b9f
--- /dev/null
+++ b/api-model/src/main/java/io/enmasse/iot/model/v1/ImageOverride.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2019, EnMasse authors.
+ * License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
+ */
+package io.enmasse.iot.model.v1;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+import io.fabric8.kubernetes.api.model.Doneable;
+import io.sundr.builder.annotations.Buildable;
+import io.sundr.builder.annotations.Inline;
+
+@Buildable(
+        editableEnabled = false,
+        generateBuilderPackage = false,
+        builderPackage = "io.fabric8.kubernetes.api.builder",
+        inline = @Inline(
+                type = Doneable.class,
+                prefix = "Doneable",
+                value = "done"
+                )
+        )
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class ImageOverride {
+
+    private String name;
+    private String imagePullPolicy;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getImagePullPolicy() {
+        return imagePullPolicy;
+    }
+
+    public void setImagePullPolicy(String imagePullPolicy) {
+        this.imagePullPolicy = imagePullPolicy;
+    }
+
+}
diff --git a/api-model/src/main/java/io/enmasse/iot/model/v1/InterServiceCertificates.java b/api-model/src/main/java/io/enmasse/iot/model/v1/InterServiceCertificates.java
new file mode 100644
index 000000000..f49a04e32
--- /dev/null
+++ b/api-model/src/main/java/io/enmasse/iot/model/v1/InterServiceCertificates.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2019, EnMasse authors.
+ * License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
+ */
+package io.enmasse.iot.model.v1;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+import io.fabric8.kubernetes.api.model.Doneable;
+import io.sundr.builder.annotations.Buildable;
+import io.sundr.builder.annotations.Inline;
+
+@Buildable(
+        editableEnabled = false,
+        generateBuilderPackage = false,
+        builderPackage = "io.fabric8.kubernetes.api.builder",
+        inline = @Inline(
+                type = Doneable.class,
+                prefix = "Doneable",
+                value = "done"
+                )
+        )
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class InterServiceCertificates {
+
+    private SecretCertificatesStrategy secretCertificatesStrategy;
+    private ServiceCAStrategy serviceCAStrategy;
+
+    public SecretCertificatesStrategy getSecretCertificatesStrategy() {
+        return secretCertificatesStrategy;
+    }
+
+    public void setSecretCertificatesStrategy(SecretCertificatesStrategy secretCertificatesStrategy) {
+        this.secretCertificatesStrategy = secretCertificatesStrategy;
+    }
+
+    public ServiceCAStrategy getServiceCAStrategy() {
+        return serviceCAStrategy;
+    }
+
+    public void setServiceCAStrategy(ServiceCAStrategy serviceCAStrategy) {
+        this.serviceCAStrategy = serviceCAStrategy;
+    }
+
+}
diff --git a/api-model/src/main/java/io/enmasse/iot/model/v1/IoTConfig.java b/api-model/src/main/java/io/enmasse/iot/model/v1/IoTConfig.java
new file mode 100644
index 000000000..d7eada49a
--- /dev/null
+++ b/api-model/src/main/java/io/enmasse/iot/model/v1/IoTConfig.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2019, EnMasse authors.
+ * License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
+ */
+package io.enmasse.iot.model.v1;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+import io.enmasse.common.model.AbstractHasMetadata;
+import io.enmasse.common.model.DefaultCustomResource;
+import io.fabric8.kubernetes.api.model.Doneable;
+import io.sundr.builder.annotations.Buildable;
+import io.sundr.builder.annotations.BuildableReference;
+import io.sundr.builder.annotations.Inline;
+
+@Buildable(
+        editableEnabled = false,
+        generateBuilderPackage = false,
+        builderPackage = "io.fabric8.kubernetes.api.builder",
+        refs = {@BuildableReference(AbstractHasMetadata.class)},
+        inline = @Inline(
+                        type = Doneable.class,
+                        prefix = "Doneable",
+                        value = "done"))
+@DefaultCustomResource
+@SuppressWarnings("serial")
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class IoTConfig extends AbstractHasMetadata<IoTConfig> {
+
+    public static final String KIND = "IoTConfig";
+
+    private IoTConfigSpec spec;
+    private IoTConfigStatus status;
+
+    public IoTConfig() {
+        super(KIND, IoTCrd.API_VERSION);
+    }
+
+    public IoTConfigSpec getSpec() {
+        return spec;
+    }
+
+    public void setSpec(IoTConfigSpec spec) {
+        this.spec = spec;
+    }
+
+    public IoTConfigStatus getStatus() {
+        return status;
+    }
+
+    public void setStatus(IoTConfigStatus status) {
+        this.status = status;
+    }
+
+}
diff --git a/api-model/src/main/java/io/enmasse/iot/model/v1/IoTConfigSpec.java b/api-model/src/main/java/io/enmasse/iot/model/v1/IoTConfigSpec.java
new file mode 100644
index 000000000..7e9c0b3cc
--- /dev/null
+++ b/api-model/src/main/java/io/enmasse/iot/model/v1/IoTConfigSpec.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2019, EnMasse authors.
+ * License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
+ */
+package io.enmasse.iot.model.v1;
+
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+import io.fabric8.kubernetes.api.model.Doneable;
+import io.sundr.builder.annotations.Buildable;
+import io.sundr.builder.annotations.Inline;
+
+@Buildable(
+        editableEnabled = false,
+        generateBuilderPackage = false,
+        builderPackage = "io.fabric8.kubernetes.api.builder",
+        inline = @Inline(
+                type = Doneable.class,
+                prefix = "Doneable",
+                value = "done"
+                )
+        )
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class IoTConfigSpec {
+
+    private Boolean enableDefaultRoutes;
+    private Map<String, ImageOverride> imageOverrides;
+    private InterServiceCertificates interServiceCertificates;
+
+    public Boolean getEnableDefaultRoutes() {
+        return enableDefaultRoutes;
+    }
+
+    public void setEnableDefaultRoutes(Boolean enableDefaultRoutes) {
+        this.enableDefaultRoutes = enableDefaultRoutes;
+    }
+
+    public Map<String, ImageOverride> getImageOverrides() {
+        return imageOverrides;
+    }
+
+    public void setImageOverrides(Map<String, ImageOverride> imageOverrides) {
+        this.imageOverrides = imageOverrides;
+    }
+
+    public InterServiceCertificates getInterServiceCertificates() {
+        return interServiceCertificates;
+    }
+
+    public void setInterServiceCertificates(InterServiceCertificates interServiceCertificates) {
+        this.interServiceCertificates = interServiceCertificates;
+    }
+
+}
diff --git a/api-model/src/main/java/io/enmasse/iot/model/v1/IoTConfigStatus.java b/api-model/src/main/java/io/enmasse/iot/model/v1/IoTConfigStatus.java
new file mode 100644
index 000000000..70dec25ea
--- /dev/null
+++ b/api-model/src/main/java/io/enmasse/iot/model/v1/IoTConfigStatus.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2019, EnMasse authors.
+ * License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
+ */
+package io.enmasse.iot.model.v1;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+import io.fabric8.kubernetes.api.model.Doneable;
+import io.sundr.builder.annotations.Buildable;
+import io.sundr.builder.annotations.Inline;
+
+@Buildable(
+        editableEnabled = false,
+        generateBuilderPackage = false,
+        builderPackage = "io.fabric8.kubernetes.api.builder",
+        inline = @Inline(
+                type = Doneable.class,
+                prefix = "Doneable",
+                value = "done"
+                )
+        )
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class IoTConfigStatus {
+
+    private boolean initialized = false;
+    private String state;
+    private String authenticationServicePSK;
+
+    public boolean isInitialized() {
+        return initialized;
+    }
+
+    public void setInitialized(boolean initialized) {
+        this.initialized = initialized;
+    }
+
+    public String getState() {
+        return state;
+    }
+
+    public void setState(String state) {
+        this.state = state;
+    }
+
+    public String getAuthenticationServicePSK() {
+        return authenticationServicePSK;
+    }
+
+    public void setAuthenticationServicePSK(String authenticationServicePSK) {
+        this.authenticationServicePSK = authenticationServicePSK;
+    }
+
+}
diff --git a/api-model/src/main/java/io/enmasse/iot/model/v1/IoTCrd.java b/api-model/src/main/java/io/enmasse/iot/model/v1/IoTCrd.java
index 98def8f64..4f4c50d2e 100644
--- a/api-model/src/main/java/io/enmasse/iot/model/v1/IoTCrd.java
+++ b/api-model/src/main/java/io/enmasse/iot/model/v1/IoTCrd.java
@@ -23,6 +23,7 @@ public class IoTCrd {
     public static void registerCustomCrds() {
         KubernetesDeserializer.registerCustomKind(API_VERSION, IoTProject.KIND, IoTProject.class);
         KubernetesDeserializer.registerCustomKind(API_VERSION, IoTProjectList.KIND, IoTProjectList.class);
+        KubernetesDeserializer.registerCustomKind(API_VERSION, IoTConfig.KIND, IoTConfig.class);
     }
 
     public static CustomResourceDefinition project() {
diff --git a/api-model/src/main/java/io/enmasse/iot/model/v1/IoTProject.java b/api-model/src/main/java/io/enmasse/iot/model/v1/IoTProject.java
index 337806c7a..9503a358c 100644
--- a/api-model/src/main/java/io/enmasse/iot/model/v1/IoTProject.java
+++ b/api-model/src/main/java/io/enmasse/iot/model/v1/IoTProject.java
@@ -24,7 +24,6 @@ import io.sundr.builder.annotations.Inline;
                                 value = "done"))
 @DefaultCustomResource
 @SuppressWarnings("serial")
-//FIXME: implement missing fields and remove ignore annotation
 @JsonIgnoreProperties(ignoreUnknown = true)
 public class IoTProject extends AbstractHasMetadata<IoTProject> {
 
diff --git a/api-model/src/main/java/io/enmasse/iot/model/v1/IoTProjectSpec.java b/api-model/src/main/java/io/enmasse/iot/model/v1/IoTProjectSpec.java
index 7f9821708..f0d0d9435 100644
--- a/api-model/src/main/java/io/enmasse/iot/model/v1/IoTProjectSpec.java
+++ b/api-model/src/main/java/io/enmasse/iot/model/v1/IoTProjectSpec.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018, EnMasse authors.
+ * Copyright 2019, EnMasse authors.
  * License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
  */
 package io.enmasse.iot.model.v1;
diff --git a/api-model/src/main/java/io/enmasse/iot/model/v1/IoTProjectStatus.java b/api-model/src/main/java/io/enmasse/iot/model/v1/IoTProjectStatus.java
index 9b24f8007..f09ea206a 100644
--- a/api-model/src/main/java/io/enmasse/iot/model/v1/IoTProjectStatus.java
+++ b/api-model/src/main/java/io/enmasse/iot/model/v1/IoTProjectStatus.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018, EnMasse authors.
+ * Copyright 2019, EnMasse authors.
  * License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
  */
 package io.enmasse.iot.model.v1;
diff --git a/api-model/src/main/java/io/enmasse/iot/model/v1/ManagedDownstreamStrategy.java b/api-model/src/main/java/io/enmasse/iot/model/v1/ManagedDownstreamStrategy.java
index ec418abce..d67bf9a75 100644
--- a/api-model/src/main/java/io/enmasse/iot/model/v1/ManagedDownstreamStrategy.java
+++ b/api-model/src/main/java/io/enmasse/iot/model/v1/ManagedDownstreamStrategy.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018, EnMasse authors.
+ * Copyright 2019, EnMasse authors.
  * License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
  */
 package io.enmasse.iot.model.v1;
@@ -23,7 +23,6 @@ import io.sundr.builder.annotations.Inline;
                 value = "done"
                 )
         )
-//FIXME: implement missing fields and remove ignore annotation
 @JsonIgnoreProperties(ignoreUnknown = true)
 @JsonInclude(NON_NULL)
 public class ManagedDownstreamStrategy {
diff --git a/api-model/src/main/java/io/enmasse/iot/model/v1/SecretCertificatesStrategy.java b/api-model/src/main/java/io/enmasse/iot/model/v1/SecretCertificatesStrategy.java
new file mode 100644
index 000000000..685f93d18
--- /dev/null
+++ b/api-model/src/main/java/io/enmasse/iot/model/v1/SecretCertificatesStrategy.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2019, EnMasse authors.
+ * License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
+ */
+package io.enmasse.iot.model.v1;
+
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+import io.fabric8.kubernetes.api.model.Doneable;
+import io.sundr.builder.annotations.Buildable;
+import io.sundr.builder.annotations.Inline;
+
+@Buildable(
+        editableEnabled = false,
+        generateBuilderPackage = false,
+        builderPackage = "io.fabric8.kubernetes.api.builder",
+        inline = @Inline(
+                type = Doneable.class,
+                prefix = "Doneable",
+                value = "done"
+                )
+        )
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class SecretCertificatesStrategy {
+
+    private String caSecretName;
+    private Map<String, String> serviceSecretNames;
+
+    public String getCaSecretName() {
+        return caSecretName;
+    }
+
+    public void setCaSecretName(String caSecretName) {
+        this.caSecretName = caSecretName;
+    }
+
+    public Map<String, String> getServiceSecretNames() {
+        return serviceSecretNames;
+    }
+
+    public void setServiceSecretNames(Map<String, String> serviceSecretNames) {
+        this.serviceSecretNames = serviceSecretNames;
+    }
+
+}
diff --git a/api-model/src/main/java/io/enmasse/iot/model/v1/ServiceCAStrategy.java b/api-model/src/main/java/io/enmasse/iot/model/v1/ServiceCAStrategy.java
new file mode 100644
index 000000000..063026e6e
--- /dev/null
+++ b/api-model/src/main/java/io/enmasse/iot/model/v1/ServiceCAStrategy.java
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2019, EnMasse authors.
+ * License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
+ */
+package io.enmasse.iot.model.v1;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ServiceCAStrategy {
+
+}
diff --git a/documentation/modules/con-brokered-topic.adoc b/documentation/modules/con-brokered-topic.adoc
index 1ceccd809..6a3ee88ae 100644
--- a/documentation/modules/con-brokered-topic.adoc
+++ b/documentation/modules/con-brokered-topic.adoc
@@ -24,4 +24,7 @@ So, for example:
 
 * `a/+/b` matches `a/foo/b` and `a/bar/b`, but would not match `a/foo/bar`
 
+== Known issue with creating a subscriber on a hierarchical topic
+
+A known issue exists where creating a subscriber on a hierarchical topic in {ProductName} causes the broker to instead create it as a competing consumer (handling the address like a queue rather than a topic). For more information about the specific workaround for your client, see the applicable client example section in link:{BookUrlBase}{BaseProductVersion}{BookNameUrl}#assembly-connecting-applications-messaging[Connecting applications to {ProductName}].
 
diff --git a/documentation/modules/con-standard-topic.adoc b/documentation/modules/con-standard-topic.adoc
index 5362803fb..82e740bf3 100644
--- a/documentation/modules/con-standard-topic.adoc
+++ b/documentation/modules/con-standard-topic.adoc
@@ -27,3 +27,7 @@ So, for example:
 
 In the standard address space, the first level must always be a defined topic address; that is, `#` and `+` are not valid as the first characters of a subscribing address.
 
+== Known issue with creating a subscriber on a hierarchical topic
+
+A known issue exists where creating a subscriber on a hierarchical topic in {ProductName} causes the broker to instead create it as a competing consumer (handling the address like a queue rather than a topic). For more information about the specific workaround for your client, see the applicable client example section in link:{BookUrlBase}{BaseProductVersion}{BookNameUrl}#assembly-connecting-applications-messaging[Connecting applications to {ProductName}].
+
diff --git a/documentation/modules/ref-cpp-example.adoc b/documentation/modules/ref-cpp-example.adoc
index 771873a0a..7002f3a0d 100644
--- a/documentation/modules/ref-cpp-example.adoc
+++ b/documentation/modules/ref-cpp-example.adoc
@@ -71,3 +71,28 @@ int main(int argc, char **argv) {
 }
 ----
 
+== Known issue with creating a subscriber on a hierarchical topic
+
+A known issue exists where creating a subscriber on a hierarchical topic in {ProductName} causes the broker to instead create it as a competing consumer (handling the address like a queue rather than a topic).
+
+The workaround involves setting the capability `"topic"` in the source.
+
+.Procedure
+
+* In the `topic_receive.cpp` file, edit the code so that it is similar to what is shown in this example:
+
+[source,cpp,options="nowrap",subs="+quotes,attributes"]
+----
+void on_container_start(proton::container& cont) override {
+        proton::connection conn = cont.connect(conn_url_);
+        proton::receiver_options opts {};
+        proton::source_options sopts {};
+
+        sopts.capabilities(std::vector<proton::symbol> { "topic" });
+        opts.source(sopts);
+
+        conn.open_receiver(address_, opts);
+    }
+----
+
+
diff --git a/documentation/modules/ref-python-example.adoc b/documentation/modules/ref-python-example.adoc
index 7d319c3e4..bccda5bdc 100644
--- a/documentation/modules/ref-python-example.adoc
+++ b/documentation/modules/ref-python-example.adoc
@@ -37,3 +37,28 @@ class HelloWorld(MessagingHandler):
 Container(HelloWorld("amqps://_messaging-route-hostname_:443", "myqueue")).run()
 ----
 
+== Known issue with creating a subscriber on a hierarchical topic
+
+A known issue exists where creating a subscriber on a hierarchical topic in {ProductName} causes the broker to instead create it as a competing consumer (handling the address like a queue rather than a topic).
+
+The workaround for this issue involves setting the capability `"topic"` in the source.
+
+.Procedure
+
+. In the `simple_recv.py` file, modify the `from proton.reactor import Container` to add the `ReceiverOption`:
+
+[source,python,options="nowrap",subs="+quotes,attributes"]
+----
+class CapabilityOptions(ReceiverOption):
+    def apply(self, receiver):
+        receiver.source.capabilities.put_object(symbol("topic"))
+----
+
+. Modify the following line to add `options=CapabilityOptions()`:
+
+[source,python,options="nowrap",subs="+quotes,attributes"]
+----
+def on_start(self, event):
+        event.container.create_receiver(conn, self.address, options=CapabilityOptions())
+----
+
diff --git a/iot/examples/README.md b/iot/examples/README.md
index 10b462505..ffeb0df72 100644
--- a/iot/examples/README.md
+++ b/iot/examples/README.md
@@ -18,6 +18,7 @@
   oc apply -f templates/build/enmasse-latest/install/components/enmasse-controller-manager
   
   oc apply -f templates/build/enmasse-latest/install/preview-bundles/iot
+  oc apply -f iot/examples/iot-config.yaml
   ```
 
 * Wait for the EnMasse installation to succeed. The following command should show all pods to be ready:
diff --git a/keycloak-plugin/sasl-plugin/src/main/java/io/enmasse/keycloak/spi/ScramSaslAuthenticator.java b/keycloak-plugin/sasl-plugin/src/main/java/io/enmasse/keycloak/spi/ScramSaslAuthenticator.java
index d08c40f9a..90df22fef 100644
--- a/keycloak-plugin/sasl-plugin/src/main/java/io/enmasse/keycloak/spi/ScramSaslAuthenticator.java
+++ b/keycloak-plugin/sasl-plugin/src/main/java/io/enmasse/keycloak/spi/ScramSaslAuthenticator.java
@@ -385,6 +385,7 @@ class ScramSaslAuthenticator implements SaslServerMechanism.Instance
                     challenge = new byte[0];
                     break;
                 }
+              //$FALL-THROUGH$
             default:
                 throw new IllegalArgumentException("No response expected in state " + state);
 
diff --git a/none-authservice/Dockerfile b/none-authservice/Dockerfile
index cab7b963d..d1e629b5c 100644
--- a/none-authservice/Dockerfile
+++ b/none-authservice/Dockerfile
@@ -1,14 +1,10 @@
-FROM enmasseproject/nodejs-base:6-1
+FROM enmasseproject/java-base:11-0
 
-RUN mkdir -p /opt/app-root/src/
-RUN cd /opt/app-root/src/
-RUN ["/bin/bash", "-c", "npm install debug && npm install rhea"]
 ARG version
 ARG maven_version
 ARG commit
 ENV VERSION=${version} COMMIT=${commit} MAVEN_VERSION=${maven_version}
 
-ADD build/none-authservice-${MAVEN_VERSION}.tgz /opt/app-root/src/
-EXPOSE 5671
+ADD target/none-authservice-${MAVEN_VERSION}.jar /none-authservice.jar
 
-CMD ["node", "/opt/app-root/src/none-authservice.js"]
+CMD ["/opt/run-java/launch_java.sh", "/none-authservice.jar"]
diff --git a/none-authservice/Makefile b/none-authservice/Makefile
index 3e933513a..27e8bca79 100644
--- a/none-authservice/Makefile
+++ b/none-authservice/Makefile
@@ -1,11 +1,1 @@
-include ../Makefile.common
-
-build:
-	@echo "Nothing to build"
-
-package:
-	tar -czf build/none-authservice-$(MAVEN_VERSION).tgz *.js
-
-test:
-
-coverage:
+include ../Makefile.java.mk
diff --git a/none-authservice/none-authservice.js b/none-authservice/none-authservice.js
deleted file mode 100644
index e2ef17385..000000000
--- a/none-authservice/none-authservice.js
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2017 Red Hat Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-var container = require('rhea');
-var fs = require('fs');
-var path = require('path');
-var cert_dir = (process.env.CERT_DIR !== undefined) ? process.env.CERT_DIR : "/opt/none-authservice/cert";
-
-function authenticate(username, password) {
-    console.log('Authenticating as ' + username);
-    return true;
-}
-container.sasl_server_mechanisms.enable_plain(authenticate);
-container.sasl_server_mechanisms.enable_anonymous();
-var server = container.listen({
-    port: process.env.LISTENPORT,
-    require_sasl: true,
-    transport: 'tls',
-    key: fs.readFileSync(path.resolve(cert_dir, 'tls.key')),
-    cert: fs.readFileSync(path.resolve(cert_dir, 'tls.crt'))
-});
-
-console.log('Listening on port ' + process.env.LISTENPORT);
-container.on('connection_open', function (context) {
-    var authenticatedIdentity = { 'sub' : context.connection.sasl_transport.username || 'anonymous' };
-    var groups = [ "manage" ];
-    var properties = context.connection.local.open.properties || {};
-    properties["authenticated-identity"] = authenticatedIdentity;
-    properties["groups"] = groups;
-    context.connection.local.open.properties = properties;
-    context.connection.close();
-});
-container.on('disconnected', function (context) {
-});
diff --git a/none-authservice/pom.xml b/none-authservice/pom.xml
new file mode 100644
index 000000000..4ba277d08
--- /dev/null
+++ b/none-authservice/pom.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <parent>
+    <groupId>io.enmasse</groupId>
+    <artifactId>enmasse</artifactId>
+    <version>0.27-SNAPSHOT</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>none-authservice</artifactId>
+  <dependencies>
+    <dependency>
+      <groupId>io.vertx</groupId>
+      <artifactId>vertx-proton</artifactId>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>ch.qos.logback</groupId>
+      <artifactId>logback-classic</artifactId>
+      <scope>runtime</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-shade-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>shade</goal>
+            </goals>
+            <configuration>
+              <transformers>
+                <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
+                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+                  <mainClass>io.enmasse.authservice.none.NoneAuthService</mainClass>
+                </transformer>
+              </transformers>
+              <filters>
+                <filter>
+                  <artifact>*:*</artifact>
+                  <excludes>
+                    <exclude>META-INF/*.SF</exclude>
+                    <exclude>META-INF/*.DSA</exclude>
+                    <exclude>META-INF/*.RSA</exclude>
+                  </excludes>
+                </filter>
+              </filters>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/none-authservice/src/main/java/io/enmasse/authservice/none/NoneAuthService.java b/none-authservice/src/main/java/io/enmasse/authservice/none/NoneAuthService.java
new file mode 100644
index 000000000..910e0464c
--- /dev/null
+++ b/none-authservice/src/main/java/io/enmasse/authservice/none/NoneAuthService.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2019, EnMasse authors.
+ * License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
+ */
+package io.enmasse.authservice.none;
+
+import io.vertx.core.Vertx;
+import io.vertx.core.net.PemKeyCertOptions;
+import io.vertx.proton.ProtonConnection;
+import io.vertx.proton.ProtonServer;
+import io.vertx.proton.ProtonServerOptions;
+import org.apache.qpid.proton.amqp.Symbol;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+public class NoneAuthService {
+    private static final Logger log = LoggerFactory.getLogger(NoneAuthService.class);
+    private static final Symbol AUTHENTICATED_IDENTITY = Symbol.getSymbol("authenticated-identity");
+    private static final Symbol GROUPS = Symbol.getSymbol("groups");
+
+    public static void main(String[] args) {
+        Map<String, String> env = System.getenv();
+        String certDir = env.getOrDefault("CERT_DIR", "/opt/none-authservice/cert");
+        int listenPort = Integer.parseInt(env.getOrDefault("LISTENPORT", "5671"));
+
+        Vertx vertx = Vertx.vertx();
+
+        ProtonServerOptions options = new ProtonServerOptions();
+        options.setSsl(true);
+        options.setPemKeyCertOptions(new PemKeyCertOptions()
+                .setCertPath(new File(certDir, "tls.crt").getAbsolutePath())
+                .setKeyPath(new File(certDir, "tls.key").getAbsolutePath()));
+
+        ProtonServer server = ProtonServer.create(vertx, options);
+        server.connectHandler(NoneAuthService::connectHandler);
+        server.saslAuthenticatorFactory(NoneAuthServiceAuthenticator::new);
+        server.listen(listenPort, "0.0.0.0", result -> {
+            if (result.succeeded()) {
+                log.info("Listening on {}", listenPort);
+            } else {
+                log.error("Error listening on {}", listenPort, result.cause());
+            }
+        });
+    }
+
+    private static void connectHandler(ProtonConnection connection) {
+        connection.setContainer("none-authservice");
+        connection.openHandler(conn -> {
+            Map<Symbol, Object> properties = new HashMap<>();
+
+            properties.put(AUTHENTICATED_IDENTITY, Collections.singletonMap("sub", "anonymous"));
+            properties.put(GROUPS, Collections.singletonList("manage"));
+
+            connection.setProperties(properties);
+            connection.open();
+            connection.close();
+
+        }).closeHandler(conn -> {
+            connection.close();
+            connection.disconnect();
+        }).disconnectHandler(protonConnection -> {
+            connection.disconnect();
+        });
+    }
+}
diff --git a/none-authservice/src/main/java/io/enmasse/authservice/none/NoneAuthServiceAuthenticator.java b/none-authservice/src/main/java/io/enmasse/authservice/none/NoneAuthServiceAuthenticator.java
new file mode 100644
index 000000000..08d7efbb4
--- /dev/null
+++ b/none-authservice/src/main/java/io/enmasse/authservice/none/NoneAuthServiceAuthenticator.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2019, EnMasse authors.
+ * License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
+ */
+package io.enmasse.authservice.none;
+
+import io.vertx.core.Handler;
+import io.vertx.core.net.NetSocket;
+import io.vertx.proton.ProtonConnection;
+import io.vertx.proton.sasl.ProtonSaslAuthenticator;
+import org.apache.qpid.proton.engine.Sasl;
+import org.apache.qpid.proton.engine.Transport;
+
+public class NoneAuthServiceAuthenticator implements ProtonSaslAuthenticator {
+    private static final String MECH_ANONYMOUS = "ANONYMOUS";
+    private static final String MECH_PLAIN = "PLAIN";
+
+    private Sasl sasl;
+
+    @Override
+    public void init(NetSocket netSocket, ProtonConnection protonConnection, Transport transport) {
+
+        transport.setInitialRemoteMaxFrameSize(1024*1024);
+        this.sasl = transport.sasl();
+        sasl.server();
+        sasl.allowSkip(false);
+        sasl.setMechanisms(MECH_ANONYMOUS, MECH_PLAIN);
+    }
+
+    @Override
+    public void process(Handler<Boolean> handler) {
+        String[] remoteMechanisms = sasl.getRemoteMechanisms();
+        boolean done = false;
+        if (!done && remoteMechanisms.length > 0) {
+            String chosen = remoteMechanisms[0];
+            byte[] response;
+            if (sasl.pending() > 0) {
+                response = new byte[sasl.pending()];
+                sasl.recv(response, 0, response.length);
+            }
+            done = true;
+            if (MECH_ANONYMOUS.equals(chosen) || MECH_PLAIN.equals(chosen)) {
+                sasl.done(Sasl.SaslOutcome.PN_SASL_OK);
+            } else {
+                sasl.done(Sasl.SaslOutcome.PN_SASL_SYS);
+            }
+        }
+        handler.handle(done);
+    }
+
+    @Override
+    public boolean succeeded() {
+        return true;
+    }
+}
diff --git a/none-authservice/src/main/resources/logback.xml b/none-authservice/src/main/resources/logback.xml
new file mode 100644
index 000000000..50d086292
--- /dev/null
+++ b/none-authservice/src/main/resources/logback.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE xml>
+<configuration>
+
+  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+    <encoder>
+      <pattern>%d{yyyy-MM-dd'T'HH:mm:ss.SSS'Z',GMT} %-5p [%c{0}] %m%n</pattern>
+    </encoder>
+  </appender>
+
+  <root level="${LOG_LEVEL:-info}">
+    <appender-ref ref="STDOUT" />
+  </root>
+
+  <logger name="io.vertx.core.net.impl.NetServerImpl" level="OFF"/>
+
+</configuration>
diff --git a/pom.xml b/pom.xml
index ff069120d..c148292fd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -115,6 +115,7 @@
         <module>discovery-lib</module>
         <module>topic-forwarder</module>
         <module>mqtt-gateway</module>
+        <module>none-authservice</module>
         <module>mqtt-lwt</module>
         <module>standard-controller</module>
         <module>address-space-controller</module>
diff --git a/standard-controller/src/main/java/io/enmasse/controller/standard/AddressProvisioner.java b/standard-controller/src/main/java/io/enmasse/controller/standard/AddressProvisioner.java
index 3ea9ac7c1..60f0c6613 100644
--- a/standard-controller/src/main/java/io/enmasse/controller/standard/AddressProvisioner.java
+++ b/standard-controller/src/main/java/io/enmasse/controller/standard/AddressProvisioner.java
@@ -330,7 +330,7 @@ public class AddressProvisioner {
 
                     updateBrokerStatus(address, Collections.singletonList(brokerStatus));
 
-                    brokers.sort(Comparator.comparingDouble(BrokerInfo::getCredit));
+                    brokers.sort(Comparator.comparing(BrokerInfo::getBrokerId));
 
                     for (BrokerInfo brokerInfo : brokers) {
                         if (brokerInfo.getCredit() + resourceRequest.getValue() < 1) {
@@ -447,7 +447,11 @@ public class AddressProvisioner {
             String resourceName = entry.getKey();
             if ("router".equals(resourceName)) {
                 int totalNeeded = sumNeeded(entry.getValue());
-                router.setNewReplicas(Math.max(totalNeeded, router.getInfraConfig() != null ? router.getInfraConfig().getSpec().getRouter().getMinReplicas() : 0));
+                int infraConfigMin = Optional.ofNullable(router.getInfraConfig())
+                        .flatMap(infraConfig -> Optional.ofNullable(router.getInfraConfig().getSpec().getRouter()))
+                        .flatMap(routerSpec -> Optional.ofNullable(routerSpec.getMinReplicas()))
+                        .orElse(1);
+                router.setNewReplicas(Math.max(totalNeeded, infraConfigMin));
             } else if ("broker".equals(resourceName)) {
                 // Provision pooled broker
                 int needPooled = sumNeededMatching(entry.getValue(), pooledPattern);
@@ -504,7 +508,7 @@ public class AddressProvisioner {
             }
         }
 
-        brokers.sort(Comparator.comparingDouble(BrokerInfo::getCredit));
+        brokers.sort(Comparator.comparing(BrokerInfo::getBrokerId));
 
         for (BrokerInfo brokerInfo : brokers) {
             if (brokerInfo.getCredit() + credit < 1) {
diff --git a/templates/csv/enmasse.clusterserviceversion.yaml b/templates/csv/enmasse.clusterserviceversion.yaml
index 2fd4af40a..90558f8ea 100644
--- a/templates/csv/enmasse.clusterserviceversion.yaml
+++ b/templates/csv/enmasse.clusterserviceversion.yaml
@@ -9,10 +9,10 @@ metadata:
     categories: "Streaming & Messaging"
     certified: "false"
     description: EnMasse provides a self-service messaging platform with a uniform interface to manage different messaging infrastructure.
-    containerImage: ${ADDRESS_SPACE_CONTROLLER_IMAGE}
+    containerImage: ${ENMASSE_CONTROLLER_MANAGER_IMAGE}
     createdAt: 2019-02-19T00:00:00Z
     support: EnMasse
-    alm-examples: '[{"apiVersion":"admin.enmasse.io/v1beta1","kind":"StandardInfraConfig","metadata":{"name":"my-standard-infra"},"spec":{"version":"${ENMASSE_VERSION}","broker":{"resources":{"storage":"5Gi"},"addressFullPolicy":"DROP"},"router":{"linkCapacity":50}}},{"apiVersion":"admin.enmasse.io/v1beta1","kind":"BrokeredInfraConfig","metadata":{"name":"my-brokered-infra"},"spec":{"version":"${ENMASSE_VERSION}","broker":{"resources":{"memory":"4Gi"}}}},{"apiVersion":"admin.enmasse.io/v1beta2","kind":"AddressPlan","metadata":{"name":"small-standard-queue"},"spec":{"addressType":"queue","shortDescription":"Small Queue","resources":{"router":0.01,"broker":0.1}}},{"apiVersion":"admin.enmasse.io/v1beta2","kind":"AddressSpacePlan","metadata":{"name":"standard-small"},"spec":{"addressSpaceType":"standard","infraConfigRef":"my-standard-infra","shortDescription":"Small Address Space Plan","resourceLimits":{"router":1,"broker":2,"aggregate":2},"addressPlans":["small-standard-queue"]}]'
+    alm-examples: '[{"apiVersion":"admin.enmasse.io/v1beta1","kind":"StandardInfraConfig","metadata":{"name":"my-standard-infra"},"spec":{"version":"${ENMASSE_VERSION}","broker":{"resources":{"memory":"1Gi","storage":"5Gi"},"addressFullPolicy":"BLOCK"},"router":{"linkCapacity":50,"resources":{"memory":"512Mi"}}}},{"apiVersion":"admin.enmasse.io/v1beta1","kind":"BrokeredInfraConfig","metadata":{"name":"my-brokered-infra"},"spec":{"version":"${ENMASSE_VERSION}","broker":{"resources":{"memory":"4Gi"}}}},{"apiVersion":"admin.enmasse.io/v1beta2","kind":"AddressPlan","metadata":{"name":"small-standard-queue"},"spec":{"addressType":"queue","shortDescription":"Small Queue","resources":{"router":0.01,"broker":0.1}}},{"apiVersion":"admin.enmasse.io/v1beta2","kind":"AddressSpacePlan","metadata":{"name":"standard-small"},"spec":{"addressSpaceType":"standard","infraConfigRef":"my-standard-infra","shortDescription":"Small Address Space Plan","resourceLimits":{"router":1,"broker":2,"aggregate":2},"addressPlans":["small-standard-queue"]}},{"apiVersion":"admin.enmasse.io/v1beta1","kind":"AuthenticationService","metadata":{"name":"none-authservice"},"spec":{"type":"none"}}]'
 spec:
   maturity: beta
   displayName: EnMasse
@@ -22,11 +22,11 @@ spec:
 
     ### Supported features
 
-    * ** Built-in authentication and authorization ** - Use the built-in or plug in your own authentication service for authentication and authorization of messaging clients.
+    * **Built-in authentication and authorization** - Use the built-in or plug in your own authentication service for authentication and authorization of messaging clients.
 
-    * ** Self-service messaging for applications ** - The service admin deploys and manages the messaging infrastructure, while applications can request messaging resources without caring about the messaging infrastructure.
+    * **Self-service messaging for applications** - The service admin deploys and manages the messaging infrastructure, while applications can request messaging resources without caring about the messaging infrastructure.
 
-    * ** Supports a wide variety of messaging patterns ** - Choose between JMS-style messaging with strict guarantees, or messaging that supports a larger number of connections and higher throughput.
+    * **Supports a wide variety of messaging patterns** - Choose between JMS-style messaging with strict guarantees, or messaging that supports a larger number of connections and higher throughput.
 
     ### Documentation
     
@@ -35,6 +35,7 @@ spec:
     ### Getting help
 
     * [EnMasse mailing list](https://www.redhat.com/mailman/listinfo/enmasse)
+
     * [#enmasse IRC channel on Freenode](https://webchat.freenode.net/?randomnick=1&channels=enmasse&uio=d4)
 
     ### Contributing
@@ -42,8 +43,10 @@ spec:
     If you've got some great ideas and use cases for EnMasse, we would love to hear them!
 
     * Raise issues on [GitHub](https://github.com/EnMasseProject/enmasse/issues).
+
     * Read the [Hacking guide](https://github.com/EnMasseProject/enmasse/blob/master/HACKING.md) for details on how to build EnMasse.
-    * Fix issues by opening Pull Requests
+
+    * Fix issues and open Pull Requests
 
     ### License
 
@@ -65,7 +68,7 @@ spec:
       app: enmasse
   links:
   - name: Documentation
-    url: http://enmasse.io/documentation
+    url: https://enmasse.io/documentation
   - name: GitHub
     url: https://github.com/EnMasseProject/enmasse
 
@@ -83,6 +86,20 @@ spec:
     strategy: deployment
     spec:
       permissions:
+      - serviceAccountName: enmasse-operator
+        rules:
+        - apiGroups: [ "apps" ]
+          resources: [ "deployments" ]
+          verbs: [ "get", "list", "watch", "update", "create", "patch", "delete" ]
+        - apiGroups: [ "" ]
+          resources: [ "configmaps", "secrets", "persistentvolumeclaims", "services" ]
+          verbs: [ "get", "list", "watch", "update", "create", "patch", "delete" ]
+        - apiGroups: [ "", "route.openshift.io" ]
+          resources: [ "routes", "routes/custom-host", "routes/status"]
+          verbs: [ "create", "update", "patch", "get", "list", "watch", "delete" ]
+        - apiGroups: [ "admin.enmasse.io" ]
+          resources: [ "authenticationservices", "authenticationservices/finalizers" ]
+          verbs: [ "get", "list", "watch", "update", "create", "patch" ]
       - serviceAccountName: address-space-controller
         rules:
         - apiGroups: [ "admin.enmasse.io" ]
@@ -141,6 +158,21 @@ spec:
           resources: [ "addressspaceplans", "addressplans", "standardinfraconfigs", "brokeredinfraconfigs", "authenticationservices" ]
           verbs: [ "get", "list", "watch" ]
       clusterPermissions:
+      - serviceAccountName: standard-authservice
+        rules:
+        - apiGroups: [ "authentication.k8s.io" ]
+          resources: [ "tokenreviews" ]
+          verbs: [ "create" ]
+      - serviceAccountName: address-space-controller
+        rules:
+        - apiGroups: [ "", "user.openshift.io" ]
+          resources: [ "users" ]
+          verbs: [ "get" ]
+      - serviceAccountName: enmasse-operator
+        rules:
+        - apiGroups: [ "oauth.openshift.io" ]
+          resources: [ "oauthclients" ]
+          verbs: [ "create", "get", "list", "watch" ]
       - serviceAccountName: api-server
         rules:
         - apiGroups: [ "authentication.k8s.io" ]
@@ -180,7 +212,7 @@ spec:
               containers:
               - env:
                 - name: JAVA_OPTS
-                  value: -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps
+                  value: -verbose:gc -Xlog:gc*
                 - name: CERT_DIR
                   value: /api-server-cert
                 - name: ENABLE_RBAC
@@ -252,7 +284,7 @@ spec:
               containers:
               - env:
                 - name: JAVA_OPTS
-                  value: -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps
+                  value: -verbose:gc -Xlog:gc*
                 - name: CERT_DIR
                   value: /api-server-cert
                 - name: ENABLE_RBAC
@@ -298,6 +330,54 @@ spec:
                   name: apiservice-cert
                   readOnly: true
               serviceAccountName: api-server
+      - name: enmasse-operator
+        spec:
+          replicas: 1
+          selector:
+            matchLabels:
+              app: enmasse
+              name: enmasse-operator
+          template:
+            metadata:
+              labels:
+                app: enmasse
+                name: enmasse-operator
+            spec:
+              affinity:
+                nodeAffinity:
+                  preferredDuringSchedulingIgnoredDuringExecution:
+                    - weight: 1
+                      preference:
+                        matchExpressions:
+                          - key: node-role.enmasse.io/operator-infra
+                            operator: In
+                            values:
+                              - "true"
+              serviceAccountName: enmasse-operator
+              containers:
+              - name: controller
+                image: ${ENMASSE_CONTROLLER_MANAGER_IMAGE}
+                imagePullPolicy: ${IMAGE_PULL_POLICY}
+                env:
+                - name: POD_NAME
+                  valueFrom:
+                    fieldRef:
+                      fieldPath: metadata.name
+                - name: NAMESPACE
+                  valueFrom:
+                    fieldRef:
+                      fieldPath: metadata.namespace
+                - name: OPERATOR_NAME
+                  value: "enmasse-operator"
+                - name: ENMASSE_DEFAULT_PULL_POLICY
+                  value: "${IMAGE_PULL_POLICY}"
+                - name: CONTROLLER_DISABLE_ALL
+                  value: "true"
+                - name: CONTROLLER_ENABLE_AUTHENTICATION_SERVICE
+                  value: "true"
+                resources:
+                  limits:
+                    memory: 128Mi
       - name: address-space-controller
         spec:
           replicas: 1
@@ -475,35 +555,15 @@ spec:
   customresourcedefinitions:
     owned:
       - group: admin.enmasse.io
-        version: v1alpha1
+        version: v1beta1
         kind: AuthenticationService
         name: authenticationservices.admin.enmasse.io
         displayName: Authentication Service
         description: Authentication service configuration available to address spaces.
         specDescriptors:
-          - description: The hostname of the authentication service.
-            displayName: Hostname
-            path: host
-            x-descriptors:
-              - 'urn:alm:descriptor:com.tectonic.ui:label'
-          - description: The port of the authentication service
-            displayName: Port
-            path: port
-            x-descriptors:
-              - 'urn:alm:descriptor:com.tectonic.ui:label'
-          - description: The realm to use for the authentication service.
-            displayName: Realm
-            path: realm
-            x-descriptors:
-              - 'urn:alm:descriptor:com.tectonic.ui:label'
-          - description: The secret containing the CA certificate of the authentication service.
-            displayName: CA Secret Name
-            path: caCertSecretName
-            x-descriptors:
-              - 'urn:alm:descriptor:com.tectonic.ui:label'
-          - description: The secret containing the client certificate to use for authenticating to the authentication service.
-            displayName: Client Secret Name
-            path: clientCertSecretName
+          - description: The type of authentication service
+            displayName: Type
+            path: type
             x-descriptors:
               - 'urn:alm:descriptor:com.tectonic.ui:label'
       - group: admin.enmasse.io
diff --git a/templates/enmasse-operator/050-Deployment-enmasse-operator.yaml b/templates/enmasse-operator/050-Deployment-enmasse-operator.yaml
index af38a24a9..6507637b0 100644
--- a/templates/enmasse-operator/050-Deployment-enmasse-operator.yaml
+++ b/templates/enmasse-operator/050-Deployment-enmasse-operator.yaml
@@ -17,6 +17,16 @@ spec:
         app: enmasse
         name: enmasse-operator
     spec:
+      affinity:
+        nodeAffinity:
+          preferredDuringSchedulingIgnoredDuringExecution:
+            - weight: 1
+              preference:
+                matchExpressions:
+                  - key: node-role.enmasse.io/operator-infra
+                    operator: In
+                    values:
+                      - "true"
       serviceAccountName: enmasse-operator
       containers:
       - name: controller


Test patch:
diff --git a/api-model/src/test/java/io/enmasse/iot/model/v1/IoTConfigTest.java b/api-model/src/test/java/io/enmasse/iot/model/v1/IoTConfigTest.java
new file mode 100644
index 000000000..ddc33167c
--- /dev/null
+++ b/api-model/src/test/java/io/enmasse/iot/model/v1/IoTConfigTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2018, EnMasse authors.
+ * License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
+ */
+package io.enmasse.iot.model.v1;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Collections;
+
+import org.junit.jupiter.api.Test;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class IoTConfigTest {
+
+    @Test
+    void testCreateConfig() {
+        IoTConfig config = new IoTConfigBuilder()
+                .withNewMetadata()
+                .withName("config")
+                .endMetadata()
+                .withNewSpec()
+                .withNewInterServiceCertificates()
+                .withNewSecretCertificatesStrategy()
+                .withCaSecretName("ca-secret-name")
+                .withServiceSecretNames(Collections.singletonMap("iot", "value"))
+                .endSecretCertificatesStrategy()
+                .endInterServiceCertificates()
+                .withImageOverrides(Collections.singletonMap("image1",
+                        new ImageOverrideBuilder()
+                            .withName("img")
+                            .withImagePullPolicy("Always")
+                            .build()))
+                .endSpec()
+                .build();
+
+        assertEquals("config", config.getMetadata().getName());
+        assertNotNull(config.getSpec().getInterServiceCertificates());
+        assertNotNull(config.getSpec().getInterServiceCertificates().getSecretCertificatesStrategy());
+        assertEquals("ca-secret-name",
+                config.getSpec().getInterServiceCertificates().getSecretCertificatesStrategy().getCaSecretName());
+        assertEquals(Collections.singletonMap("iot", "value"),
+                config.getSpec().getInterServiceCertificates().getSecretCertificatesStrategy().getServiceSecretNames());
+        assertNotNull(config.getSpec().getImageOverrides());
+        ImageOverride image = config.getSpec().getImageOverrides().get("image1");
+        assertNotNull(image);
+        assertEquals("img", image.getName());
+        assertEquals("Always", image.getImagePullPolicy());
+        assertNull(config.getSpec().getEnableDefaultRoutes());
+    }
+
+    @Test
+    public void testParse() throws IOException {
+        final ObjectMapper mapper = new ObjectMapper();
+
+        final URL url = IoTProjectTest.class.getResource("resources/iot-config.json");
+        final IoTConfig value = mapper.readValue(url, IoTConfig.class);
+
+        assertThat(value, notNullValue());
+
+        assertThat(value.getMetadata(), notNullValue());
+        assertThat(value.getMetadata().getName(), is("default"));
+
+        assertThat(value.getSpec(), notNullValue());
+
+        assertThat(value.getSpec().getEnableDefaultRoutes(), notNullValue());
+        assertThat(value.getSpec().getEnableDefaultRoutes(), is(true));
+
+        assertThat(value.getSpec().getImageOverrides(), notNullValue());
+        assertThat(value.getSpec().getImageOverrides().get("image1"), notNullValue());
+        assertThat(value.getSpec().getImageOverrides().get("image2"), notNullValue());
+        assertThat(value.getSpec().getImageOverrides().get("image1").getName(), is("image1-name"));
+        assertThat(value.getSpec().getImageOverrides().get("image1").getImagePullPolicy(), is("Always"));
+
+        assertThat(value.getSpec().getInterServiceCertificates(), notNullValue());
+        assertThat(value.getSpec().getInterServiceCertificates().getSecretCertificatesStrategy(), notNullValue());
+        assertThat(value.getSpec().getInterServiceCertificates().getSecretCertificatesStrategy().getCaSecretName(), is("ca-name"));
+        assertThat(value.getSpec().getInterServiceCertificates().getSecretCertificatesStrategy().getServiceSecretNames(), notNullValue());
+        assertThat(value.getSpec().getInterServiceCertificates().getSecretCertificatesStrategy().getServiceSecretNames().get("secret1"), is("value1"));
+        assertThat(value.getSpec().getInterServiceCertificates().getSecretCertificatesStrategy().getServiceSecretNames().get("secret2"), is("value2"));
+
+        assertThat(value.getStatus(), notNullValue());
+        assertThat(value.getStatus().isInitialized(), is(true));
+        assertThat(value.getStatus().getState(), is("ok"));
+        assertThat(value.getStatus().getAuthenticationServicePSK(), is("psk"));
+
+
+    }
+
+}
diff --git a/api-model/src/test/resources/io/enmasse/iot/model/v1/resources/iot-config.json b/api-model/src/test/resources/io/enmasse/iot/model/v1/resources/iot-config.json
new file mode 100644
index 000000000..6d89f2fac
--- /dev/null
+++ b/api-model/src/test/resources/io/enmasse/iot/model/v1/resources/iot-config.json
@@ -0,0 +1,34 @@
+{
+	"apiVersion": "iot.enmasse.io/v1alpha1",
+	"kind": "IoTConfig",
+	"metadata": {
+		"name": "default"
+	},
+	"spec": {
+		"enableDefaultRoutes": true,
+		"imageOverrides": {
+			"image1": {
+				"name": "image1-name",
+				"imagePullPolicy": "Always"
+			},
+			"image2": {
+				"name": "image2-name",
+				"imagePullPolicy": "Always"
+			}
+		},
+		"interServiceCertificates": {
+			"secretCertificatesStrategy": {
+				"caSecretName": "ca-name",
+				"serviceSecretNames": {
+					"secret1": "value1",
+					"secret2": "value2"
+				}
+			}
+		}
+	},
+	"status": {
+		"initialized": true,
+		"state": "ok",
+		"authenticationServicePSK": "psk"
+	}
+}
\ No newline at end of file
diff --git a/systemtests/scripts/test_func.sh b/systemtests/scripts/test_func.sh
index 824d1bb5c..d30fde95a 100755
--- a/systemtests/scripts/test_func.sh
+++ b/systemtests/scripts/test_func.sh
@@ -386,18 +386,23 @@ function get_oc_args() {
 }
 
 function is_upgraded() {
-    IMAGE=$1
+    IMAGE=${1}
     TAG=${TAG:-"latest"}
 
-    IMAGE_TAG=$(echo $IMAGE | cut -f 2 -d ':')
+    IMAGE_TAG=${IMAGE##*:}
     TEMPLATES=$(find ${CURDIR}/../../templates/build/enmasse-${TAG} -name '*.yaml' -exec cat {} \; | grep "image")
     if [[ "${TEMPLATES}" == *"${IMAGE}"* ]]; then
         echo "true"
     else
-        if [[ "${IMAGE_TAG}" == "${TAG}" ]]; then
+        OPERATOR_TEMPLATES=$(cat ${CURDIR}/../../enmasse-controller-manager/build/operatorImageMap.yaml)
+        if [[ "${OPERATOR_TEMPLATES}" == *"${IMAGE}"* ]]; then
             echo "true"
         else
-            echo "false"
+            if [[ "${IMAGE_TAG}" == "${TAG}" ]]; then
+                echo "true"
+            else
+                echo "false"
+            fi
         fi
     fi
 }
diff --git a/systemtests/scripts/wait_until_up.sh b/systemtests/scripts/wait_until_up.sh
index 914e8d17b..631be772b 100755
--- a/systemtests/scripts/wait_until_up.sh
+++ b/systemtests/scripts/wait_until_up.sh
@@ -27,12 +27,15 @@ function waitingContainersReady {
     pods_id=$($CMD get pods -n ${NAMESPACE} | awk 'NR >1 {print $1}')
     for pod_id in ${pods_id}
     do
-        ready=$($CMD get -o json pod -n ${NAMESPACE}  ${pod_id} -o jsonpath={.status.containerStatuses[0].ready})
+        ready=$($CMD get -o json pod -n ${NAMESPACE} ${pod_id} -o jsonpath={.status.containerStatuses[0].ready})
         if [[ "${UPGRADED}" == "true" ]]; then
             image=$($CMD get pod ${pod_id} -o jsonpath={.spec.containers[*].image})
             initImage=$($CMD get pod ${pod_id} -o jsonpath={.spec.initContainers[*].image})
             imageUpgraded=$(is_upgraded ${image})
             initImageUpgraded=$(is_upgraded ${initImage})
+            echo "-----------------------------------------------------"
+            echo "image upgraded: ${imageUpgraded}"
+            echo "init image upgraded: ${initImageUpgraded}"
             if [[ "${imageUpgraded}" == "true" ]] && [[ "${initImageUpgraded}" == "true" ]]; then
                 info "Pod ${pod_id} is upgraded to ${image}, initImage: ${initImage}"
             else
@@ -67,6 +70,7 @@ do
     fi
     num_running=`${CMD} get pods -n ${NAMESPACE}| grep -v deploy | grep -c Running`
     if [[ "$num_running" -eq "$EXPECTED_PODS" ]]; then
+        echo "==================================================================================="
         waitingContainersReady ${NAMESPACE}
         if [[ $? -gt 0 ]]
         then
diff --git a/systemtests/src/main/java/io/enmasse/systemtest/GlobalLogCollector.java b/systemtests/src/main/java/io/enmasse/systemtest/GlobalLogCollector.java
index 315c38830..516ee0744 100644
--- a/systemtests/src/main/java/io/enmasse/systemtest/GlobalLogCollector.java
+++ b/systemtests/src/main/java/io/enmasse/systemtest/GlobalLogCollector.java
@@ -126,7 +126,11 @@ public class GlobalLogCollector {
         allArgs.add("127.0.0.1:55671");
         allArgs.addAll(Arrays.asList(args));
 
-        String output = kubernetes.runOnPod(pod, "router", allArgs.toArray(new String[0]));
+        String output = KubeCMDClient.runOnPod(
+                pod.getMetadata().getNamespace(),
+                pod.getMetadata().getName(),
+                Optional.of("router"),
+                allArgs.toArray(new String[0])).getStdOut();
         try {
             Path routerAutoLinks = resolveLogFile(pod.getMetadata().getName() + filesuffix);
             log.info("router info '{}' pod will be archived with path: '{}'", pod.getMetadata().getName(), routerAutoLinks);
@@ -146,7 +150,11 @@ public class GlobalLogCollector {
     }
 
     private void collectJmap(Pod pod) {
-        kubernetes.runOnPod(pod, "api-server", "jmap", "-dump:live,format=b,file=/tmp/dump.bin", "1");
+        KubeCMDClient.runOnPod(
+                pod.getMetadata().getNamespace(),
+                pod.getMetadata().getName(),
+                Optional.empty(),
+                "api-server", "jmap", "-dump:live,format=b,file=/tmp/dump.bin", "1");
         try {
             Path jmapLog = resolveLogFile(pod.getMetadata().getName() + ".dump." + Instant.now().toString().replace(":", "_") + ".bin");
             KubeCMDClient.copyPodContent(pod.getMetadata().getName(), "/tmp/dump.bin", jmapLog.toAbsolutePath().toString());
diff --git a/systemtests/src/main/java/io/enmasse/systemtest/Kubernetes.java b/systemtests/src/main/java/io/enmasse/systemtest/Kubernetes.java
index bfeee32f4..3e5f4319c 100644
--- a/systemtests/src/main/java/io/enmasse/systemtest/Kubernetes.java
+++ b/systemtests/src/main/java/io/enmasse/systemtest/Kubernetes.java
@@ -539,17 +539,15 @@ public abstract class Kubernetes {
                     @Override
                     public void onFailure(Throwable throwable, Response response) {
                         data.completeExceptionally(throwable);
-
                     }
-
                     @Override
                     public void onClose(int i, String s) {
                         data.complete(baos.toString());
                     }
                 }).exec(command)) {
-            return data.get(1, TimeUnit.MINUTES);
+            return data.get(10, TimeUnit.SECONDS);
         } catch (Exception e) {
-            log.warn("Exception running command {} on pod: {}", command, e.getMessage());
+            log.warn("Exception running command {} on pod: {} with exception {}", command, pod.getMetadata().getName(), e);
             return "";
         }
     }
diff --git a/systemtests/src/main/java/io/enmasse/systemtest/cmdclients/KubeCMDClient.java b/systemtests/src/main/java/io/enmasse/systemtest/cmdclients/KubeCMDClient.java
index 2495e2d4d..241d95bbf 100644
--- a/systemtests/src/main/java/io/enmasse/systemtest/cmdclients/KubeCMDClient.java
+++ b/systemtests/src/main/java/io/enmasse/systemtest/cmdclients/KubeCMDClient.java
@@ -217,6 +217,15 @@ public class KubeCMDClient extends CmdClient {
         return execute(deleteCmd, DEFAULT_SYNC_TIMEOUT, true);
     }
 
+    public static ExecutionResultData runOnPod(String namespace, String podName, Optional<String> container, String... args) {
+        List<String> runCmd = new ArrayList<>();
+        String[] base = container.map(s -> new String[]{CMD, "exec", podName, "-n", namespace, "--container", s, "--"})
+                .orElseGet(() -> new String[]{CMD, "exec", podName, "-n", namespace, "--"});
+        Collections.addAll(runCmd, base);
+        Collections.addAll(runCmd, args);
+        return execute(runCmd, DEFAULT_SYNC_TIMEOUT, false);
+    }
+
     public static ExecutionResultData runQDstat(String podName, String... args) {
         List<String> runCmd = new ArrayList<>();
         String[] base = new String[]{CMD, "exec", podName, "--", "qdstat"};
diff --git a/systemtests/src/test/java/io/enmasse/systemtest/iot/SimpleK8sDeployTest.java b/systemtests/src/test/java/io/enmasse/systemtest/iot/SimpleK8sDeployTest.java
index 18b37c6ca..1dda952de 100644
--- a/systemtests/src/test/java/io/enmasse/systemtest/iot/SimpleK8sDeployTest.java
+++ b/systemtests/src/test/java/io/enmasse/systemtest/iot/SimpleK8sDeployTest.java
@@ -8,22 +8,27 @@ package io.enmasse.systemtest.iot;
 import static io.enmasse.systemtest.TestTag.sharedIot;
 import static io.enmasse.systemtest.TestTag.smoke;
 
+import java.io.File;
 import java.nio.file.Paths;
 import java.time.Duration;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 
-import io.enmasse.systemtest.utils.TestUtils;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import io.enmasse.iot.model.v1.IoTConfig;
+import io.enmasse.iot.model.v1.IoTConfigBuilder;
 import io.enmasse.systemtest.Environment;
 import io.enmasse.systemtest.Kubernetes;
 import io.enmasse.systemtest.TimeoutBudget;
 import io.enmasse.systemtest.cmdclients.KubeCMDClient;
+import io.enmasse.systemtest.utils.TestUtils;
 
 @Tag(sharedIot)
 @Tag(smoke)
@@ -47,15 +52,43 @@ public class SimpleK8sDeployTest {
         IOT_LABELS.put("component", "iot");
     }
 
+    private static File configTempFile;
     private Kubernetes client = Kubernetes.getInstance();
 
     @BeforeAll
     protected static void setup () throws Exception {
-        KubeCMDClient.createFromFile(NAMESPACE, Paths.get("templates/iot/k8s-systemtests-config.json"));
+        Map<String, String> secrets = new HashMap<>();
+        secrets.put("iot-auth-service-tls", "systemtests-iot-auth-service-tls");
+        secrets.put("iot-tenant-service-tls", "systemtests-iot-tenant-service-tls");
+        secrets.put("iot-device-registry-tls", "systemtests-iot-device-registry-tls");
+        secrets.put("iot-http-adapter-tls", "systemtests-iot-http-adapter-tls");
+        secrets.put("iot-mqtt-adapter-tls", "systemtests-iot-mqtt-adapter-tls");
+
+        IoTConfig config = new IoTConfigBuilder()
+                .withNewMetadata()
+                .withName("default")
+                .endMetadata()
+                .withNewSpec()
+                .withNewInterServiceCertificates()
+                .withNewSecretCertificatesStrategy()
+                .withCaSecretName("systemtests-iot-service-ca")
+                .withServiceSecretNames(secrets)
+                .endSecretCertificatesStrategy()
+                .endInterServiceCertificates()
+                .endSpec()
+                .build();
+
+        configTempFile = File.createTempFile("iot-config", "json");
+        new ObjectMapper().writeValue(configTempFile, config);
+
+        KubeCMDClient.createFromFile(NAMESPACE, Paths.get(configTempFile.toURI()));
     }
 
     @AfterAll
     protected static void cleanup() throws Exception {
+        if(configTempFile!=null && configTempFile.exists()) {
+            configTempFile.delete();
+        }
         KubeCMDClient.deleteIoTConfig(NAMESPACE, "default");
     }
 
diff --git a/systemtests/src/test/java/io/enmasse/systemtest/standard/infra/InfraTest.java b/systemtests/src/test/java/io/enmasse/systemtest/standard/infra/InfraTest.java
index a85caf34e..dcf4dd2f5 100644
--- a/systemtests/src/test/java/io/enmasse/systemtest/standard/infra/InfraTest.java
+++ b/systemtests/src/test/java/io/enmasse/systemtest/standard/infra/InfraTest.java
@@ -39,7 +39,7 @@ class InfraTest extends InfraTestBase implements ITestBaseStandard {
         testInfra = PlanUtils.createStandardInfraConfigObject("test-infra-1",
                 PlanUtils.createStandardBrokerResourceObject("512Mi", "1Gi"),
                 PlanUtils.createStandardAdminResourceObject("512Mi"),
-                PlanUtils.createStandardRouterResourceObject("512Mi"),
+                PlanUtils.createStandardRouterResourceObject("256Mi"),
                 environment.enmasseVersion());
         plansProvider.createInfraConfig(testInfra);
 
@@ -56,7 +56,7 @@ class InfraTest extends InfraTestBase implements ITestBaseStandard {
                         new ResourceAllowance("broker", 3.0),
                         new ResourceAllowance("router", 3.0),
                         new ResourceAllowance("aggregate", 5.0)),
-                Arrays.asList(exampleAddressPlan));
+                Collections.singletonList(exampleAddressPlan));
 
         plansProvider.createAddressSpacePlan(exampleSpacePlan);
 
@@ -66,7 +66,7 @@ class InfraTest extends InfraTestBase implements ITestBaseStandard {
 
         setAddresses(exampleAddressSpace, AddressUtils.createTopicAddressObject("example-queue", exampleAddressPlan.getMetadata().getName()));
 
-        assertInfra("512Mi", Optional.of("1Gi"), 2, "256Mi", "512Mi");
+        assertInfra("512Mi", Optional.of("1Gi"), 1, "256Mi", "512Mi");
 
     }
 
diff --git a/systemtests/templates/iot/k8s-systemtests-config.json b/systemtests/templates/iot/k8s-systemtests-config.json
deleted file mode 100644
index 10bcbee87..000000000
--- a/systemtests/templates/iot/k8s-systemtests-config.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
-  "apiVersion": "iot.enmasse.io/v1alpha1",
-  "kind": "IoTConfig",
-  "metadata": {
-    "name": "default"
-  },
-  "spec": {
-    "interServiceCertificates": {
-      "secretCertificatesStrategy": {
-        "caSecretName": "systemtests-iot-service-ca",
-        "serviceSecretNames": {
-          "iot-auth-service-tls": "systemtests-iot-auth-service-tls",
-          "iot-tenant-service-tls": "systemtests-iot-tenant-service-tls",
-          "iot-device-registry-tls": "systemtests-iot-device-registry-tls",
-          "iot-http-adapter-tls": "systemtests-iot-http-adapter-tls",
-          "iot-mqtt-adapter-tls": "systemtests-iot-mqtt-adapter-tls"
-        }
-      }
-    }
-  }
-}
\ No newline at end of file