Repository: EnMasseProject/enmasse
Pull request #3279 (2019-10-07 12:19:34)

Align status of IoTConfig with IoTProject
### Type of change

_Select the type of your PR_

- Refactoring

### Description

Align the status section with the project CRD and K8S resources:

* Rename "state" to "phase"
* Add conditions section, with an initial "Ready" condition

### Checklist

_Please go through this checklist and make sure all applicable tasks have been done_

- [ ] Update/write design documentation in `./documentation/design`
- [X] Write tests and make sure they pass
- [ ] Update documentation
- [ ] Check RBAC rights for Kubernetes / OpenShift roles
- [X] Try your changes from Pod inside your Kubernetes and OpenShift cluster, not just locally
- [ ] Reference relevant issue(s) and close them after merging
- [ ] Update CHANGELOG.md


ctron: @enmasse-ci run tests profile=shared-iot
enmasse-ci: :heavy_exclamation_mark: **Build Failed** :heavy_exclamation_mark:
ctron: @enmasse-ci run tests profile=shared-iot
enmasse-ci: ### :x: Test Summary :x:
**TEST_PROFILE**: shared-iot
**TEST_CASE:** 
**TOTAL:** 13
**PASS:** 0
**FAIL:** 13
**SKIP:** 0
### :heavy_exclamation_mark: Test Failures :heavy_exclamation_mark:
- testEventSingle in io.enmasse.systemtest.iot.shared.mqtt.MqttAdapterTest
- testEventBatch5Before in io.enmasse.systemtest.iot.shared.mqtt.MqttAdapterTest
- testTelemetrySingleQos1 in io.enmasse.systemtest.iot.shared.mqtt.MqttAdapterTest
- testTelemetryBatch50Qos0 in io.enmasse.systemtest.iot.shared.mqtt.MqttAdapterTest
- testTelemetryBatch50Qos1 in io.enmasse.systemtest.iot.shared.mqtt.MqttAdapterTest
- testEventBatch5After in io.enmasse.systemtest.iot.shared.mqtt.MqttAdapterTest
- testOneShotCommand in io.enmasse.systemtest.iot.shared.control.CommandAndControlTest
- testRequestResponseCommand in io.enmasse.systemtest.iot.shared.control.CommandAndControlTest
- testEventSingle in io.enmasse.systemtest.iot.shared.http.HttpAdapterTest
- testEventBatch5Before in io.enmasse.systemtest.iot.shared.http.HttpAdapterTest
- testTelemetryBatch50 in io.enmasse.systemtest.iot.shared.http.HttpAdapterTest
- testTelemetrySingle in io.enmasse.systemtest.iot.shared.http.HttpAdapterTest
- testEventBatch5After in io.enmasse.systemtest.iot.shared.http.HttpAdapterTest
ctron: @enmasse-ci run tests profile=iot

Patch:
diff --git a/HACKING.md b/HACKING.md
index 8f0a22102..bfb198dd2 100644
--- a/HACKING.md
+++ b/HACKING.md
@@ -98,17 +98,13 @@ oc apply -f templates/build/enmasse-latest/install/components/example-authservic
 
 ### Running full systemtest suite
 
-#### Install systemtests dependencies
-
-    ansible-playbook systemtests/ansible/playbooks/systemtests-dependencies.yml --tags clients --skip-tags dependencies
-
 #### Running the systemtests
 
     make systemtests
 
 #### Run single system test
 
-    make SYSTEMTEST_ARGS="io.enmasse.systemtest.standard.QueueTest#testCreateDeleteQueue" systemtests
+    make SYSTEMTEST_ARGS="shared.standard.QueueTest#testCreateDeleteQueue" systemtests
 
 ### Adding / Updating go dependencies
 
diff --git a/address-space-controller/src/main/java/io/enmasse/controller/CreateController.java b/address-space-controller/src/main/java/io/enmasse/controller/CreateController.java
index fe535c248..b7deb26c8 100644
--- a/address-space-controller/src/main/java/io/enmasse/controller/CreateController.java
+++ b/address-space-controller/src/main/java/io/enmasse/controller/CreateController.java
@@ -5,7 +5,6 @@
 package io.enmasse.controller;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import io.enmasse.address.model.*;
 import io.enmasse.admin.model.AddressPlan;
 import io.enmasse.admin.model.AddressSpacePlan;
@@ -36,7 +35,6 @@ import static io.enmasse.k8s.api.EventLogger.Type.Normal;
 public class CreateController implements Controller {
     private static final Logger log = LoggerFactory.getLogger(CreateController.class.getName());
 
-    private static final ObjectMapper mapper = new ObjectMapper();
     private final Kubernetes kubernetes;
     private final SchemaProvider schemaProvider;
     private final InfraResourceFactory infraResourceFactory;
diff --git a/api-model/src/main/java/io/enmasse/iot/model/v1/CommonCondition.java b/api-model/src/main/java/io/enmasse/iot/model/v1/CommonCondition.java
new file mode 100644
index 000000000..8b1021bd8
--- /dev/null
+++ b/api-model/src/main/java/io/enmasse/iot/model/v1/CommonCondition.java
@@ -0,0 +1,86 @@
+/*
+ * 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.JsonInclude;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.MoreObjects.ToStringHelper;
+
+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"))
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class CommonCondition<T extends Enum<T>> {
+
+    private T type;
+    private String status;
+    private String lastTransitionTime;
+    private String reason;
+    private String message;
+
+    public void setType(T type) {
+        this.type = type;
+    }
+
+    public T getType() {
+        return type;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+    public String getLastTransitionTime() {
+        return lastTransitionTime;
+    }
+
+    public void setLastTransitionTime(String lastTransitionTime) {
+        this.lastTransitionTime = lastTransitionTime;
+    }
+
+    public String getReason() {
+        return reason;
+    }
+
+    public void setReason(String reason) {
+        this.reason = reason;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    protected ToStringHelper toStringHelper() {
+        return MoreObjects.toStringHelper(this)
+                .add("type", this.type)
+                .add("status", this.status)
+                .add("lastTransitionTime", this.lastTransitionTime)
+                .add("reason", this.reason)
+                .add("message", this.message);
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper().toString();
+    }
+}
diff --git a/api-model/src/main/java/io/enmasse/iot/model/v1/ConfigCondition.java b/api-model/src/main/java/io/enmasse/iot/model/v1/ConfigCondition.java
new file mode 100644
index 000000000..7f8cd6f8a
--- /dev/null
+++ b/api-model/src/main/java/io/enmasse/iot/model/v1/ConfigCondition.java
@@ -0,0 +1,25 @@
+/*
+ * 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.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"))
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class ConfigCondition extends CommonCondition<ProjectConditionType> {
+
+}
diff --git a/api-model/src/main/java/io/enmasse/iot/model/v1/ConfigConditionType.java b/api-model/src/main/java/io/enmasse/iot/model/v1/ConfigConditionType.java
new file mode 100644
index 000000000..5fec1dd24
--- /dev/null
+++ b/api-model/src/main/java/io/enmasse/iot/model/v1/ConfigConditionType.java
@@ -0,0 +1,23 @@
+/*
+ * 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.JsonValue;
+
+public enum ConfigConditionType {
+    READY("Ready");
+
+    @JsonValue
+    final private String value;
+
+    private ConfigConditionType(final String value) {
+        this.value = value;
+    }
+
+    public String getValue() {
+        return value;
+    }
+}
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
index dee492648..a4e3b0ad9 100644
--- 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
@@ -4,6 +4,7 @@
  */
 package io.enmasse.iot.model.v1;
 
+import java.util.List;
 import java.util.Map;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
@@ -26,10 +27,12 @@ import io.sundr.builder.annotations.Inline;
 public class IoTConfigStatus {
 
     private boolean initialized = false;
-    private String state;
+    private String phase;
+    private String phaseReason;
     private String authenticationServicePSK;
     private Map<String, AdapterStatus> adapters;
     private Map<String, ServiceStatus> services;
+    private List<ConfigCondition> conditions;
 
     public boolean isInitialized() {
         return initialized;
@@ -39,12 +42,20 @@ public class IoTConfigStatus {
         this.initialized = initialized;
     }
 
-    public String getState() {
-        return state;
+    public String getPhase() {
+        return phase;
     }
 
-    public void setState(String state) {
-        this.state = state;
+    public void setPhase(String phase) {
+        this.phase = phase;
+    }
+
+    public String getPhaseReason() {
+        return phaseReason;
+    }
+
+    public void setPhaseReason(String phaseReason) {
+        this.phaseReason = phaseReason;
     }
 
     public String getAuthenticationServicePSK() {
@@ -71,4 +82,12 @@ public class IoTConfigStatus {
         this.services = services;
     }
 
+    public List<ConfigCondition> getConditions() {
+        return conditions;
+    }
+
+    public void setConditions(List<ConfigCondition> conditions) {
+        this.conditions = conditions;
+    }
+
 }
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 d24f912c2..e3a434150 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
@@ -4,6 +4,8 @@
  */
 package io.enmasse.iot.model.v1;
 
+import java.util.List;
+
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonProperty;
@@ -31,6 +33,7 @@ public class IoTProjectStatus {
     private boolean ready = false;
     private String tenantName;
     private ExternalDownstreamStrategy downstreamEndpoint;
+    private List<ProjectCondition> conditions;
 
     public boolean isReady() {
         return ready;
@@ -56,4 +59,12 @@ public class IoTProjectStatus {
         this.downstreamEndpoint = downstreamEndpoint;
     }
 
+    public List<ProjectCondition> getConditions() {
+        return conditions;
+    }
+
+    public void setConditions(List<ProjectCondition> conditions) {
+        this.conditions = conditions;
+    }
+
 }
diff --git a/api-model/src/main/java/io/enmasse/iot/model/v1/ProjectCondition.java b/api-model/src/main/java/io/enmasse/iot/model/v1/ProjectCondition.java
new file mode 100644
index 000000000..2385d49b8
--- /dev/null
+++ b/api-model/src/main/java/io/enmasse/iot/model/v1/ProjectCondition.java
@@ -0,0 +1,25 @@
+/*
+ * 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.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"))
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class ProjectCondition extends CommonCondition<ProjectConditionType> {
+
+}
diff --git a/api-model/src/main/java/io/enmasse/iot/model/v1/ProjectConditionType.java b/api-model/src/main/java/io/enmasse/iot/model/v1/ProjectConditionType.java
new file mode 100644
index 000000000..0fbe22919
--- /dev/null
+++ b/api-model/src/main/java/io/enmasse/iot/model/v1/ProjectConditionType.java
@@ -0,0 +1,26 @@
+/*
+ * 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.JsonValue;
+
+public enum ProjectConditionType {
+    READY("Ready"),
+    RESOURCES_CREATED("ResourcesCreated"),
+    RESOURCES_READY("ResourcesReady"),
+    ;
+
+    @JsonValue
+    private final String value;
+
+    private ProjectConditionType(final String value) {
+        this.value = value;
+    }
+
+    public String getValue() {
+        return value;
+    }
+}
diff --git a/documentation/modules/con-standard-queue.adoc b/documentation/modules/con-standard-queue.adoc
index 2ce9f8f48..5bf4ad484 100644
--- a/documentation/modules/con-standard-queue.adoc
+++ b/documentation/modules/con-standard-queue.adoc
@@ -6,6 +6,8 @@
 = Queue
 // !standard.address.queue.shortDescription:A store-and-forward queue
 // !standard.address.queue.longDescription:start
-The queue address type is a store-and-forward queue. This address type is appropriate for implementing a distributed work queue, handling traffic bursts, and other use cases where you want to decouple the producer and consumer. A queue can be sharded across multiple storage units. Message ordering might be lost for queues in the standard address space.
+The queue address type is a store-and-forward queue. This address type is appropriate for implementing a distributed work queue, handling traffic bursts, and other use cases when you want to decouple the producer and consumer. A queue can be sharded across multiple storage units. Message ordering might be lost for queues in the standard address space.
 // !standard.address.queue.longDescription:stop
 
+Regarding dead-letter queue (DLQ) storing, you can determine if any stored DLQs exist by logging in to the console and link:{BookUrlBase}{BaseProductVersion}{BookNameUrl}#ref-view-message-conn-stats-table-messaging[viewing the Addresses page]. To resolve this situation, you must connect with a client and consume from a DLQ address.
+
diff --git a/documentation/modules/ref-view-message-conn-stats-table.adoc b/documentation/modules/ref-view-message-conn-stats-table.adoc
index 8db090aee..238e1af79 100644
--- a/documentation/modules/ref-view-message-conn-stats-table.adoc
+++ b/documentation/modules/ref-view-message-conn-stats-table.adoc
@@ -21,6 +21,7 @@
 |Number of receivers attached |*Receivers*
 |Queue and topic address types only: Number of stored messages on the broker or brokers |*Stored*
 |Standard address space only: Message deliveries per second |For the desired address, expand the twisty on the left to show the *Senders* table; see the *Delivery Rate* column.
+|Standard address space and queue address type only: Number of dead letter queues (DLQs) |*Stored DLQ*
 |===
 
 
diff --git a/pkg/apis/iot/v1alpha1/funcs.go b/pkg/apis/iot/v1alpha1/funcs.go
index a3b14f4bd..89b862f0b 100644
--- a/pkg/apis/iot/v1alpha1/funcs.go
+++ b/pkg/apis/iot/v1alpha1/funcs.go
@@ -120,6 +120,26 @@ func (p *IoTProjectStatus) GetProjectCondition(t ProjectConditionType) *ProjectC
 	return &p.Conditions[len(p.Conditions)-1]
 }
 
+func (config *IoTConfigStatus) GetConfigCondition(t ConfigConditionType) *ConfigCondition {
+	for i, c := range config.Conditions {
+		if c.Type == t {
+			return &config.Conditions[i]
+		}
+	}
+
+	nc := ConfigCondition{
+		Type: t,
+		CommonCondition: CommonCondition{
+			Status:             corev1.ConditionUnknown,
+			LastTransitionTime: metav1.Now(),
+		},
+	}
+
+	config.Conditions = append(config.Conditions, nc)
+
+	return &config.Conditions[len(config.Conditions)-1]
+}
+
 func (c *CommonCondition) SetStatus(status corev1.ConditionStatus, reason string, message string) {
 
 	if c.Status != status {
diff --git a/pkg/apis/iot/v1alpha1/types_config.go b/pkg/apis/iot/v1alpha1/types_config.go
index 842c95473..49dce785b 100644
--- a/pkg/apis/iot/v1alpha1/types_config.go
+++ b/pkg/apis/iot/v1alpha1/types_config.go
@@ -199,11 +199,25 @@ type MqttAdapterConfig struct {
 
 type IoTConfigStatus struct {
 	Initialized bool   `json:"initialized"`
-	State       string `json:"state"`
+	Phase       string `json:"phase"`
+	PhaseReason string `json:"phaseReason,omitempty"`
 
 	AuthenticationServicePSK *string                  `json:"authenticationServicePSK"`
 	Adapters                 map[string]AdapterStatus `json:"adapters,omitempty"`
 	Services                 map[string]ServiceStatus `json:"services,omitempty"`
+
+	Conditions []ConfigCondition `json:"conditions"`
+}
+
+type ConfigConditionType string
+
+const (
+	ConfigConditionTypeReady ConfigConditionType = "Ready"
+)
+
+type ConfigCondition struct {
+	Type            ConfigConditionType `json:"type"`
+	CommonCondition `json:",inline"`
 }
 
 type CommonStatus struct {
@@ -227,7 +241,7 @@ type EndpointStatus struct {
 
 const (
 	ConfigStateWrongName = "WrongName"
-	ConfigStateRunning   = "Running"
+	ConfigStateReady     = "Ready"
 	ConfigStateFailed    = "Failed"
 )
 
diff --git a/pkg/apis/iot/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/iot/v1alpha1/zz_generated.deepcopy.go
index c1d087dbd..2c547d47e 100644
--- a/pkg/apis/iot/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/iot/v1alpha1/zz_generated.deepcopy.go
@@ -316,6 +316,23 @@ func (in *CommonStatus) DeepCopy() *CommonStatus {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ConfigCondition) DeepCopyInto(out *ConfigCondition) {
+	*out = *in
+	in.CommonCondition.DeepCopyInto(&out.CommonCondition)
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigCondition.
+func (in *ConfigCondition) DeepCopy() *ConfigCondition {
+	if in == nil {
+		return nil
+	}
+	out := new(ConfigCondition)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ConnectionInformation) DeepCopyInto(out *ConnectionInformation) {
 	*out = *in
@@ -727,6 +744,13 @@ func (in *IoTConfigStatus) DeepCopyInto(out *IoTConfigStatus) {
 			(*out)[key] = val
 		}
 	}
+	if in.Conditions != nil {
+		in, out := &in.Conditions, &out.Conditions
+		*out = make([]ConfigCondition, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
 	return
 }
 
diff --git a/pkg/controller/iotconfig/iotconfig_controller.go b/pkg/controller/iotconfig/iotconfig_controller.go
index 6a91368d6..7f833d75e 100644
--- a/pkg/controller/iotconfig/iotconfig_controller.go
+++ b/pkg/controller/iotconfig/iotconfig_controller.go
@@ -216,6 +216,11 @@ func (r *ReconcileIoTConfig) Reconcile(request reconcile.Request) (reconcile.Res
 	return r.updateFinalStatus(ctx, config, rc)
 }
 
+func syncConfigCondition(status *iotv1alpha1.IoTConfigStatus) {
+	ready := status.GetConfigCondition(iotv1alpha1.ConfigConditionTypeReady)
+	ready.SetStatusOkOrElse(status.Initialized, "NotReady", "infrastructure is not ready yet")
+}
+
 func (r *ReconcileIoTConfig) updateStatus(ctx context.Context, config *iotv1alpha1.IoTConfig, err error) error {
 
 	// we are initialized when there is no error
@@ -223,11 +228,13 @@ func (r *ReconcileIoTConfig) updateStatus(ctx context.Context, config *iotv1alph
 	config.Status.Initialized = err == nil
 
 	if config.Status.Initialized {
-		config.Status.State = iotv1alpha1.ConfigStateRunning
+		config.Status.Phase = iotv1alpha1.ConfigStateReady
 	} else {
-		config.Status.State = iotv1alpha1.ConfigStateFailed
+		config.Status.Phase = iotv1alpha1.ConfigStateFailed
 	}
 
+	syncConfigCondition(&config.Status)
+
 	return r.client.Status().Update(ctx, config)
 }
 
@@ -247,9 +254,11 @@ func (r *ReconcileIoTConfig) updateFinalStatus(ctx context.Context, config *iotv
 func (r *ReconcileIoTConfig) failWrongConfigName(ctx context.Context, config *iotv1alpha1.IoTConfig) (reconcile.Result, error) {
 
 	config.Status.Initialized = false
-	config.Status.State = iotv1alpha1.ConfigStateWrongName
+	config.Status.Phase = iotv1alpha1.ConfigStateWrongName
+
+	syncConfigCondition(&config.Status)
 
-	if err := r.client.Update(ctx, config); err != nil {
+	if err := r.client.Status().Update(ctx, config); err != nil {
 		return reconcile.Result{}, err
 	}
 
diff --git a/pkg/util/password.go b/pkg/util/password.go
index e3cb8362b..51305a3c4 100644
--- a/pkg/util/password.go
+++ b/pkg/util/password.go
@@ -11,7 +11,7 @@ import (
 )
 
 var (
-	PossibleCharacters = []rune("abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789" + ";:_,.-#+*=?()/&%$!")
+	PossibleCharacters = []rune("abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789" + ";:,.-_=")
 	MaxLen             = big.NewInt(int64(len(PossibleCharacters)))
 )
 
diff --git a/templates/crds/010-iotconfigs.crd.yaml b/templates/crds/010-iotconfigs.crd.yaml
index 34339fc2f..f1689c25f 100644
--- a/templates/crds/010-iotconfigs.crd.yaml
+++ b/templates/crds/010-iotconfigs.crd.yaml
@@ -14,5 +14,10 @@ spec:
     singular: iotconfig
     shortNames:
     - icfg
+  additionalPrinterColumns:
+  - name: Phase
+    type: string
+    description: Phase of the IoT config
+    JSONPath: .status.phase
   subresources:
     status: {}
diff --git a/templates/iot/examples/iot-config-dev.yaml b/templates/iot/examples/iot-config-dev.yaml
index 4a38de13e..138978a83 100644
--- a/templates/iot/examples/iot-config-dev.yaml
+++ b/templates/iot/examples/iot-config-dev.yaml
@@ -8,7 +8,7 @@ spec:
       infinispan:
         server:
           external:
-            host: infinispan-server
+            host: infinispan
             port: 11222
             username: app
             password: test12
diff --git a/templates/iot/examples/iot-config-k8s.yaml b/templates/iot/examples/iot-config-k8s.yaml
index 402ca5c33..c65e83608 100644
--- a/templates/iot/examples/iot-config-k8s.yaml
+++ b/templates/iot/examples/iot-config-k8s.yaml
@@ -15,7 +15,7 @@ spec:
       infinispan:
         server:
           external:
-            host: infinispan-server
+            host: infinispan
             port: 11222
             username: app
             password: test12
diff --git a/templates/iot/examples/iot-config.yaml b/templates/iot/examples/iot-config.yaml
index 050e6ce2c..dded373b1 100644
--- a/templates/iot/examples/iot-config.yaml
+++ b/templates/iot/examples/iot-config.yaml
@@ -8,7 +8,7 @@ spec:
       infinispan:
         server:
           external:
-            host: infinispan-server
+            host: infinispan
             port: 11222
             username: app
             password: test12


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
index af9faebe0..ef6e88af3 100644
--- 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
@@ -89,7 +89,7 @@ public class IoTConfigTest {
 
         assertThat(value.getStatus(), notNullValue());
         assertThat(value.getStatus().isInitialized(), is(true));
-        assertThat(value.getStatus().getState(), is("ok"));
+        assertThat(value.getStatus().getPhase(), is("Ready"));
         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
index 529a90189..77dd44ef9 100644
--- 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
@@ -37,7 +37,14 @@
 	},
 	"status": {
 		"initialized": true,
-		"state": "ok",
-		"authenticationServicePSK": "psk"
+		"phase": "Ready",
+		"authenticationServicePSK": "psk",
+		"conditions": [
+			{
+				"lastTransitionTime": "2019-10-07T11:37:57Z",
+				"status": "True",
+				"type": "Ready"
+			}
+		]
 	}
 }
\ No newline at end of file
diff --git a/systemtests/README.md b/systemtests/README.md
index 8dec76fd5..3a9f69e32 100644
--- a/systemtests/README.md
+++ b/systemtests/README.md
@@ -25,5 +25,23 @@ the [documentation](http://enmasse.io/documentation/master/openshift/#quickstart
 
 Where $PROFILE can be:
 * systemtests
-* systemtests-marathon
-* systemtests-upgrade
+* marathon
+* iot
+* shared
+* isolated
+* shared-iot
+* isolated-iot
+* smoke
+* smoke-iot
+* upgrade
+
+## Running upgrade test
+
+    mkdir templates/build -pv
+    export START_VERSION=0.28.2
+    wget https://github.com/EnMasseProject/enmasse/releases/download/${START_VERSION}/enmasse-${START_VERSION}.tgz -O templates/build/enmasse-${START_VERSION}.tgz
+    tar zxvf templates/build/enmasse-${version}.tgz -C templates/build
+    make templates
+    export START_TEMPLATES=${pwd}/templates/build/enmasse-${START_VERSION}
+    export UPGRADE_TEMPLATES=${pwd}/templates/build/enmasse-latest
+    make SYSTEMTEST_PROFILE=upgrade
diff --git a/systemtests/src/main/java/io/enmasse/systemtest/utils/IoTUtils.java b/systemtests/src/main/java/io/enmasse/systemtest/utils/IoTUtils.java
index e09aa3dfd..09252da43 100644
--- a/systemtests/src/main/java/io/enmasse/systemtest/utils/IoTUtils.java
+++ b/systemtests/src/main/java/io/enmasse/systemtest/utils/IoTUtils.java
@@ -74,7 +74,7 @@ public class IoTUtils {
             }
         }
         if (!isReady) {
-            String jsonStatus = config != null && config.getStatus() != null ? config.getStatus().getState() : "";
+            String jsonStatus = config != null && config.getStatus() != null ? config.getStatus().getPhase() : "";
             throw new IllegalStateException("IoTConfig " + Objects.requireNonNull(config).getMetadata().getName() + " is not in Ready state within timeout: " + jsonStatus);
         }
 
diff --git a/systemtests/src/test/java/io/enmasse/systemtest/iot/isolated/SimpleK8sDeployTest.java b/systemtests/src/test/java/io/enmasse/systemtest/iot/isolated/SimpleK8sDeployTest.java
index fcb4c46b5..c0190115f 100644
--- a/systemtests/src/test/java/io/enmasse/systemtest/iot/isolated/SimpleK8sDeployTest.java
+++ b/systemtests/src/test/java/io/enmasse/systemtest/iot/isolated/SimpleK8sDeployTest.java
@@ -13,7 +13,6 @@ import io.enmasse.iot.model.v1.IoTConfigBuilder;
 import io.enmasse.systemtest.Environment;
 import io.enmasse.systemtest.bases.TestBase;
 import io.enmasse.systemtest.bases.iot.ITestIoTIsolated;
-import io.enmasse.systemtest.bases.iot.ITestIoTShared;
 import io.enmasse.systemtest.cmdclients.KubeCMDClient;
 import io.enmasse.systemtest.platform.Kubernetes;
 import io.enmasse.systemtest.utils.IoTUtils;
@@ -21,7 +20,6 @@ import io.enmasse.systemtest.utils.TestUtils;
 import io.fabric8.kubernetes.api.model.Quantity;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
diff --git a/systemtests/src/test/java/io/enmasse/systemtest/isolated/upgrade/UpgradeTest.java b/systemtests/src/test/java/io/enmasse/systemtest/isolated/upgrade/UpgradeTest.java
index 9a5cbbaaf..e9e2edda0 100644
--- a/systemtests/src/test/java/io/enmasse/systemtest/isolated/upgrade/UpgradeTest.java
+++ b/systemtests/src/test/java/io/enmasse/systemtest/isolated/upgrade/UpgradeTest.java
@@ -252,9 +252,6 @@ class UpgradeTest extends TestBase implements ITestIsolatedStandard {
         String images = Files.readString(imageEnvDir);
         log.info("Expected images: {}", images);
 
-        kubernetes.listPods().stream().filter(pod -> pod.getMetadata().getName().matches("^qdrouterd-.*-0"))
-                .forEach(pod -> kubernetes.deletePod(kubernetes.getInfraNamespace(), pod.getMetadata().getName()));
-
         TestUtils.waitUntilCondition("Images are updated", (phase) -> {
             AtomicBoolean ready = new AtomicBoolean(true);
             log.info("=======================================================");