Repository: EnMasseProject/enmasse
Pull request #3192 (2019-09-16 08:22:05)

Add validation of model
* Rename field names for statuses
* Validate connector names and address patterns
* Validate forwarder remoteAddress connector reference
* Validate forwarder address type

Issue #3150

famartinrh: @lulf sorry you need to rebase because a reference to `getForwarderStatuses` was added to `o.enmasse.systemtest.utils.AddressUtils` apart from that LGTM

Patch:
diff --git a/address-space-controller/src/main/java/io/enmasse/controller/MetricsReporterController.java b/address-space-controller/src/main/java/io/enmasse/controller/MetricsReporterController.java
index fe643140a..78d223b93 100644
--- a/address-space-controller/src/main/java/io/enmasse/controller/MetricsReporterController.java
+++ b/address-space-controller/src/main/java/io/enmasse/controller/MetricsReporterController.java
@@ -37,9 +37,9 @@ public class MetricsReporterController implements Controller {
             MetricLabel[] labels = new MetricLabel[]{new MetricLabel("name", addressSpace.getMetadata().getName()), new MetricLabel("namespace", addressSpace.getMetadata().getNamespace())};
             readyValues.add(new MetricValue(addressSpace.getStatus().isReady() ? 1 : 0, now, labels));
             notReadyValues.add(new MetricValue(addressSpace.getStatus().isReady() ? 0 : 1, now, labels));
-            numConnectors.add(new MetricValue(addressSpace.getStatus().getConnectorStatuses().size(), now, labels));
+            numConnectors.add(new MetricValue(addressSpace.getStatus().getConnectors().size(), now, labels));
 
-            for (AddressSpaceStatusConnector connectorStatus : addressSpace.getStatus().getConnectorStatuses()) {
+            for (AddressSpaceStatusConnector connectorStatus : addressSpace.getStatus().getConnectors()) {
 
                 MetricLabel[] connectorLabels = new MetricLabel[]{new MetricLabel("name", addressSpace.getMetadata().getName()), new MetricLabel("namespace", addressSpace.getMetadata().getNamespace())};
                 readyConnectorValues.add(new MetricValue(connectorStatus.isReady() ? 1 : 0, now, connectorLabels));
diff --git a/address-space-controller/src/main/java/io/enmasse/controller/RouterConfigController.java b/address-space-controller/src/main/java/io/enmasse/controller/RouterConfigController.java
index e34eb3d18..3e2dad511 100644
--- a/address-space-controller/src/main/java/io/enmasse/controller/RouterConfigController.java
+++ b/address-space-controller/src/main/java/io/enmasse/controller/RouterConfigController.java
@@ -70,7 +70,7 @@ public class RouterConfigController implements Controller {
                 .withReady(true)
                 .build())
         .collect(Collectors.toList());
-        addressSpace.getStatus().setConnectorStatuses(connectorStatuses);
+        addressSpace.getStatus().setConnectors(connectorStatuses);
     }
 
     private void reconcileRouterConfig(AddressSpace addressSpace, StandardInfraConfig infraConfig) throws IOException {
@@ -191,7 +191,7 @@ public class RouterConfigController implements Controller {
             if (connector.getTls().getCaCert() != null) {
                 String data = getSelectorValue(addressSpace.getMetadata().getNamespace(), connector.getTls().getCaCert(), "ca.crt").orElse(null);
                 if (data == null) {
-                    updateConnectorStatus(connector.getName(), false, "Unable to locate value or secret for caCert", addressSpace.getStatus().getConnectorStatuses());
+                    updateConnectorStatus(connector.getName(), false, "Unable to locate value or secret for caCert", addressSpace.getStatus().getConnectors());
                 } else if (!data.equals(connectorSecret.getData().get("ca.crt"))) {
                     connectorSecret.getData().put("ca.crt", data);
                     needsUpdate = true;
@@ -201,7 +201,7 @@ public class RouterConfigController implements Controller {
             if (connector.getTls().getClientCert() != null) {
                 String data = getSelectorValue(addressSpace.getMetadata().getNamespace(), connector.getTls().getClientCert(), "tls.crt").orElse(null);
                 if (data == null) {
-                    updateConnectorStatus(connector.getName(), false, "Unable to locate value or secret for clientCert", addressSpace.getStatus().getConnectorStatuses());
+                    updateConnectorStatus(connector.getName(), false, "Unable to locate value or secret for clientCert", addressSpace.getStatus().getConnectors());
                 } else if (!data.equals(connectorSecret.getData().get("tls.crt"))) {
                     connectorSecret.getData().put("tls.crt", data);
                     needsUpdate = true;
@@ -211,7 +211,7 @@ public class RouterConfigController implements Controller {
             if (connector.getTls().getClientKey() != null) {
                 String data = getSelectorValue(addressSpace.getMetadata().getNamespace(), connector.getTls().getClientKey(), "tls.key").orElse(null);
                 if (data == null) {
-                    updateConnectorStatus(connector.getName(), false, "Unable to locate value or secret for clientKey", addressSpace.getStatus().getConnectorStatuses());
+                    updateConnectorStatus(connector.getName(), false, "Unable to locate value or secret for clientKey", addressSpace.getStatus().getConnectors());
                 } else if (!data.equals(connectorSecret.getData().get("tls.key"))) {
                     connectorSecret.getData().put("tls.key", data);
                     needsUpdate = true;
@@ -475,7 +475,7 @@ public class RouterConfigController implements Controller {
         for (AddressSpaceSpecConnector connector : addressSpace.getSpec().getConnectors()) {
             Connector remoteConnector = new Connector();
             remoteConnector.setName(connector.getName());
-            AddressSpaceStatusConnector connectorStatus = addressSpace.getStatus().getConnectorStatuses().stream()
+            AddressSpaceStatusConnector connectorStatus = addressSpace.getStatus().getConnectors().stream()
                     .filter(status -> status.getName().equals(connector.getName()))
                     .findFirst().orElse(null);
 
@@ -487,7 +487,7 @@ public class RouterConfigController implements Controller {
             Iterator<AddressSpaceSpecConnectorEndpoint> endpointIt = connector.getEndpointHosts().iterator();
             // If connector is missing initial host:
             if (!endpointIt.hasNext()) {
-                updateConnectorStatus(connector.getName(), false, "Missing at least one endpoint on connector", addressSpace.getStatus().getConnectorStatuses());
+                updateConnectorStatus(connector.getName(), false, "Missing at least one endpoint on connector", addressSpace.getStatus().getConnectors());
                 continue;
             }
 
@@ -522,12 +522,12 @@ public class RouterConfigController implements Controller {
 
                 if (tls.getClientCert() != null || tls.getClientKey() != null) {
                     if (tls.getClientCert() == null) {
-                        updateConnectorStatus(connector.getName(), false, "Both clientCert and clientKey must be specified (only clientKey is specified)", addressSpace.getStatus().getConnectorStatuses());
+                        updateConnectorStatus(connector.getName(), false, "Both clientCert and clientKey must be specified (only clientKey is specified)", addressSpace.getStatus().getConnectors());
                         continue;
                     }
 
                     if (tls.getClientKey() == null) {
-                        updateConnectorStatus(connector.getName(), false, "Both clientCert and clientKey must be specified (only clientCert is specified)", addressSpace.getStatus().getConnectorStatuses());
+                        updateConnectorStatus(connector.getName(), false, "Both clientCert and clientKey must be specified (only clientCert is specified)", addressSpace.getStatus().getConnectors());
                         continue;
                     }
 
@@ -544,7 +544,7 @@ public class RouterConfigController implements Controller {
                 if (connector.getCredentials().getUsername() != null) {
                     String data = getSelectorValue(addressSpace.getMetadata().getNamespace(), connector.getCredentials().getUsername(), "username").orElse(null);
                     if (data == null) {
-                        updateConnectorStatus(connector.getName(), false, "Unable to locate value or secret for username", addressSpace.getStatus().getConnectorStatuses());
+                        updateConnectorStatus(connector.getName(), false, "Unable to locate value or secret for username", addressSpace.getStatus().getConnectors());
                         continue;
                     }
                     remoteConnector.setSaslUsername(data);
@@ -553,7 +553,7 @@ public class RouterConfigController implements Controller {
                 if (connector.getCredentials().getPassword() != null) {
                     String data = getSelectorValue(addressSpace.getMetadata().getNamespace(), connector.getCredentials().getPassword(), "password").orElse(null);
                     if (data == null) {
-                        updateConnectorStatus(connector.getName(), false, "Unable to locate value or secret for password", addressSpace.getStatus().getConnectorStatuses());
+                        updateConnectorStatus(connector.getName(), false, "Unable to locate value or secret for password", addressSpace.getStatus().getConnectors());
                         continue;
                     }
                     remoteConnector.setSaslPassword(data);
diff --git a/address-space-controller/src/main/java/io/enmasse/controller/RouterStatusController.java b/address-space-controller/src/main/java/io/enmasse/controller/RouterStatusController.java
index 154a7a2d7..fae04daca 100644
--- a/address-space-controller/src/main/java/io/enmasse/controller/RouterStatusController.java
+++ b/address-space-controller/src/main/java/io/enmasse/controller/RouterStatusController.java
@@ -46,7 +46,7 @@ public class RouterStatusController implements Controller {
         InfraConfig infraConfig = InfraConfigs.parseCurrentInfraConfig(addressSpace);
 
         if (infraConfig instanceof StandardInfraConfig) {
-            if (!addressSpace.getStatus().getConnectorStatuses().isEmpty()) {
+            if (!addressSpace.getStatus().getConnectors().isEmpty()) {
                 checkConnectorStatuses(addressSpace);
             }
         }
@@ -113,7 +113,7 @@ public class RouterStatusController implements Controller {
             connectorMap.put(connector.getName(), connector);
         }
 
-        for (AddressSpaceStatusConnector connector : addressSpace.getStatus().getConnectorStatuses()) {
+        for (AddressSpaceStatusConnector connector : addressSpace.getStatus().getConnectors()) {
             checkConnectorStatus(connector, connectorMap.get(connector.getName()), results);
         }
     }
diff --git a/api-model/src/main/java/io/enmasse/address/model/AddressSpaceSpecConnector.java b/api-model/src/main/java/io/enmasse/address/model/AddressSpaceSpecConnector.java
index adca220ef..c9646c965 100644
--- a/api-model/src/main/java/io/enmasse/address/model/AddressSpaceSpecConnector.java
+++ b/api-model/src/main/java/io/enmasse/address/model/AddressSpaceSpecConnector.java
@@ -9,6 +9,7 @@ import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonSetter;
 import com.fasterxml.jackson.annotation.Nulls;
 import io.enmasse.admin.model.v1.AbstractWithAdditionalProperties;
+import io.enmasse.model.validation.AddressSpaceConnectorName;
 import io.fabric8.kubernetes.api.model.Doneable;
 import io.sundr.builder.annotations.Buildable;
 import io.sundr.builder.annotations.BuildableReference;
@@ -32,16 +33,19 @@ import java.util.List;
         )
 )
 @JsonInclude(JsonInclude.Include.NON_NULL)
+@AddressSpaceConnectorName
 public class AddressSpaceSpecConnector extends AbstractWithAdditionalProperties {
     @NotNull
     private String name;
 
+    @NotNull
     @NotEmpty
     private List<@Valid  AddressSpaceSpecConnectorEndpoint> endpointHosts = Collections.emptyList();
 
     private AddressSpaceSpecConnectorCredentials credentials;
     private AddressSpaceSpecConnectorTls tls;
-    private List<AddressSpaceSpecConnectorAddressRule> addresses = Collections.emptyList();
+
+    private List<@Valid AddressSpaceSpecConnectorAddressRule> addresses = Collections.emptyList();
 
     public String getName() {
         return name;
diff --git a/api-model/src/main/java/io/enmasse/address/model/AddressSpaceSpecConnectorAddressRule.java b/api-model/src/main/java/io/enmasse/address/model/AddressSpaceSpecConnectorAddressRule.java
index a8cf53c57..70776cc2c 100644
--- a/api-model/src/main/java/io/enmasse/address/model/AddressSpaceSpecConnectorAddressRule.java
+++ b/api-model/src/main/java/io/enmasse/address/model/AddressSpaceSpecConnectorAddressRule.java
@@ -6,6 +6,8 @@ package io.enmasse.address.model;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
 import io.enmasse.admin.model.v1.AbstractWithAdditionalProperties;
+import io.enmasse.model.validation.AddressSpaceConnectorAddressRuleName;
+import io.enmasse.model.validation.AddressSpaceConnectorAddressRulePattern;
 import io.fabric8.kubernetes.api.model.Doneable;
 import io.sundr.builder.annotations.Buildable;
 import io.sundr.builder.annotations.BuildableReference;
@@ -25,6 +27,8 @@ import javax.validation.constraints.NotNull;
         )
 )
 @JsonInclude(JsonInclude.Include.NON_NULL)
+@AddressSpaceConnectorAddressRuleName
+@AddressSpaceConnectorAddressRulePattern
 public class AddressSpaceSpecConnectorAddressRule {
     @NotNull
     private String name;
diff --git a/api-model/src/main/java/io/enmasse/address/model/AddressSpaceSpecConnectorCredentials.java b/api-model/src/main/java/io/enmasse/address/model/AddressSpaceSpecConnectorCredentials.java
index b209a791b..9d44a1c75 100644
--- a/api-model/src/main/java/io/enmasse/address/model/AddressSpaceSpecConnectorCredentials.java
+++ b/api-model/src/main/java/io/enmasse/address/model/AddressSpaceSpecConnectorCredentials.java
@@ -11,6 +11,8 @@ import io.sundr.builder.annotations.Buildable;
 import io.sundr.builder.annotations.BuildableReference;
 import io.sundr.builder.annotations.Inline;
 
+import javax.validation.constraints.NotNull;
+
 @Buildable(
         editableEnabled = false,
         generateBuilderPackage = false,
@@ -24,7 +26,10 @@ import io.sundr.builder.annotations.Inline;
 )
 @JsonInclude(JsonInclude.Include.NON_NULL)
 public class AddressSpaceSpecConnectorCredentials extends AbstractWithAdditionalProperties {
+    @NotNull
     private StringOrSecretSelector username;
+
+    @NotNull
     private StringOrSecretSelector password;
 
     public StringOrSecretSelector getUsername() {
diff --git a/api-model/src/main/java/io/enmasse/address/model/AddressSpaceStatus.java b/api-model/src/main/java/io/enmasse/address/model/AddressSpaceStatus.java
index c88f48664..e01fe8d18 100644
--- a/api-model/src/main/java/io/enmasse/address/model/AddressSpaceStatus.java
+++ b/api-model/src/main/java/io/enmasse/address/model/AddressSpaceStatus.java
@@ -54,7 +54,7 @@ public class AddressSpaceStatus extends AbstractWithAdditionalProperties {
     private String caCert;
 
     @JsonSetter(nulls = Nulls.AS_EMPTY)
-    private List<AddressSpaceStatusConnector> connectorStatuses = new ArrayList<>();
+    private List<AddressSpaceStatusConnector> connectors = new ArrayList<>();
 
     public AddressSpaceStatus() {
     }
@@ -66,7 +66,7 @@ public class AddressSpaceStatus extends AbstractWithAdditionalProperties {
     public AddressSpaceStatus(AddressSpaceStatus other) {
         this.ready = other.isReady();
         this.endpointStatuses = new ArrayList<>(other.getEndpointStatuses());
-        this.connectorStatuses = new ArrayList<>(other.getConnectorStatuses());
+        this.connectors = new ArrayList<>(other.getConnectors());
         this.messages.addAll(other.getMessages());
         this.phase = other.getPhase();
     }
@@ -128,12 +128,12 @@ public class AddressSpaceStatus extends AbstractWithAdditionalProperties {
                 phase == status.phase &&
                 Objects.equals(endpointStatuses, status.endpointStatuses) &&
                 Objects.equals(messages, status.messages) &&
-                Objects.equals(connectorStatuses, status.connectorStatuses);
+                Objects.equals(connectors, status.connectors);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(ready, phase, endpointStatuses, messages, connectorStatuses);
+        return Objects.hash(ready, phase, endpointStatuses, messages, connectors);
     }
 
     @Override
@@ -143,7 +143,7 @@ public class AddressSpaceStatus extends AbstractWithAdditionalProperties {
                 .append(",").append("phase=").append(phase)
                 .append(",").append("endpointStatuses=").append(endpointStatuses)
                 .append(",").append("messages=").append(messages)
-                .append(",").append("connectorStatuses=").append(connectorStatuses)
+                .append(",").append("connectors=").append(connectors)
                 .append("}")
                 .toString();
     }
@@ -159,16 +159,16 @@ public class AddressSpaceStatus extends AbstractWithAdditionalProperties {
     }
 
     public AddressSpaceStatus appendConnectorStatus(AddressSpaceStatusConnector connectorStatus) {
-        connectorStatuses.add(connectorStatus);
+        connectors.add(connectorStatus);
         return this;
     }
 
-    public List<AddressSpaceStatusConnector> getConnectorStatuses() {
-        return connectorStatuses;
+    public List<AddressSpaceStatusConnector> getConnectors() {
+        return connectors;
     }
 
-    public void setConnectorStatuses(List<AddressSpaceStatusConnector> connectorStatuses) {
-        this.connectorStatuses = connectorStatuses;
+    public void setConnectors(List<AddressSpaceStatusConnector> connectors) {
+        this.connectors = connectors;
     }
 
 }
diff --git a/api-model/src/main/java/io/enmasse/address/model/AddressSpecForwarder.java b/api-model/src/main/java/io/enmasse/address/model/AddressSpecForwarder.java
index 2ebcfd01e..fb65d4de6 100644
--- a/api-model/src/main/java/io/enmasse/address/model/AddressSpecForwarder.java
+++ b/api-model/src/main/java/io/enmasse/address/model/AddressSpecForwarder.java
@@ -6,11 +6,13 @@ package io.enmasse.address.model;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
 import io.enmasse.admin.model.v1.AbstractWithAdditionalProperties;
+import io.enmasse.model.validation.AddressForwarderName;
 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;
 
+import javax.validation.constraints.NotEmpty;
 import javax.validation.constraints.NotNull;
 
 @Buildable(
@@ -25,10 +27,12 @@ import javax.validation.constraints.NotNull;
         )
 )
 @JsonInclude(JsonInclude.Include.NON_NULL)
+@AddressForwarderName
 public class AddressSpecForwarder extends AbstractWithAdditionalProperties {
     @NotNull
     private String name;
     @NotNull
+    @NotEmpty
     private String remoteAddress;
     @NotNull
     private AddressSpecForwarderDirection direction;
diff --git a/api-model/src/main/java/io/enmasse/address/model/AddressStatus.java b/api-model/src/main/java/io/enmasse/address/model/AddressStatus.java
index f4cc9fbd7..ef8cbc58d 100644
--- a/api-model/src/main/java/io/enmasse/address/model/AddressStatus.java
+++ b/api-model/src/main/java/io/enmasse/address/model/AddressStatus.java
@@ -10,7 +10,6 @@ import java.util.List;
 import java.util.Objects;
 
 import javax.validation.Valid;
-import javax.validation.constraints.NotNull;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonProperty;
@@ -45,7 +44,7 @@ public class AddressStatus extends AbstractWithAdditionalProperties {
     private List<@Valid BrokerStatus> brokerStatuses = new ArrayList<>();
     private AddressPlanStatus planStatus;
 
-    private List<@Valid AddressStatusForwarder> forwarderStatuses;
+    private List<@Valid AddressStatusForwarder> forwarders;
 
     public AddressStatus() {
     }
@@ -62,10 +61,10 @@ public class AddressStatus extends AbstractWithAdditionalProperties {
         for (BrokerStatus brokerStatus : other.getBrokerStatuses()) {
             brokerStatuses.add(new BrokerStatus(brokerStatus.getClusterId(), brokerStatus.getContainerId(), brokerStatus.getState()));
         }
-        if (other.getForwarderStatuses() != null) {
-            this.forwarderStatuses = new ArrayList<>();
-            for (AddressStatusForwarder forwarderStatus : other.getForwarderStatuses()) {
-                forwarderStatuses.add(new AddressStatusForwarderBuilder()
+        if (other.getForwarders() != null) {
+            this.forwarders = new ArrayList<>();
+            for (AddressStatusForwarder forwarderStatus : other.getForwarders()) {
+                forwarders.add(new AddressStatusForwarderBuilder()
                         .withName(forwarderStatus.getName())
                         .withReady(forwarderStatus.isReady())
                         .withMessages(new ArrayList<>(forwarderStatus.getMessages()))
@@ -137,12 +136,12 @@ public class AddressStatus extends AbstractWithAdditionalProperties {
                 Objects.equals(messages, status.messages) &&
                 Objects.equals(brokerStatuses, status.brokerStatuses) &&
                 Objects.equals(planStatus, status.planStatus) &&
-                Objects.equals(forwarderStatuses, status.forwarderStatuses);
+                Objects.equals(forwarders, status.forwarders);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(ready, phase, messages, brokerStatuses, planStatus, forwarderStatuses);
+        return Objects.hash(ready, phase, messages, brokerStatuses, planStatus, forwarders);
     }
 
 
@@ -154,7 +153,7 @@ public class AddressStatus extends AbstractWithAdditionalProperties {
                 .append(",").append("messages=").append(messages)
                 .append(",").append("brokerStatuses=").append(brokerStatuses)
                 .append(",").append("planStatus=").append(planStatus)
-                .append(",").append("forwarderStatuses=").append(forwarderStatuses)
+                .append(",").append("forwarders=").append(forwarders)
                 .append("}")
                 .toString();
     }
@@ -171,11 +170,11 @@ public class AddressStatus extends AbstractWithAdditionalProperties {
         this.planStatus = planStatus;
     }
 
-    public List<AddressStatusForwarder> getForwarderStatuses() {
-        return forwarderStatuses;
+    public List<AddressStatusForwarder> getForwarders() {
+        return forwarders;
     }
 
-    public void setForwarderStatuses(List<AddressStatusForwarder> forwarderStatuses) {
-        this.forwarderStatuses = new ArrayList<>(forwarderStatuses);
+    public void setForwarders(List<AddressStatusForwarder> forwarders) {
+        this.forwarders = new ArrayList<>(forwarders);
     }
 }
diff --git a/api-model/src/main/java/io/enmasse/model/validation/AddressForwarderName.java b/api-model/src/main/java/io/enmasse/model/validation/AddressForwarderName.java
new file mode 100644
index 000000000..c62f98622
--- /dev/null
+++ b/api-model/src/main/java/io/enmasse/model/validation/AddressForwarderName.java
@@ -0,0 +1,33 @@
+/*
+ * 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.model.validation;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@Target({TYPE, ANNOTATION_TYPE})
+@Retention(RUNTIME)
+@Constraint(validatedBy = {AddressForwarderNameValidator.class})
+@Documented
+/**
+ * Validate the {@code .name} field conform to address space connector naming rules.
+ */
+public @interface AddressForwarderName {
+
+    String message() default "Invalid address forwarder name";
+
+    Class<?>[] groups() default {};
+
+    Class<? extends Payload>[] payload() default {};
+
+}
diff --git a/api-model/src/main/java/io/enmasse/model/validation/AddressForwarderNameValidator.java b/api-model/src/main/java/io/enmasse/model/validation/AddressForwarderNameValidator.java
new file mode 100644
index 000000000..ee1b48ef2
--- /dev/null
+++ b/api-model/src/main/java/io/enmasse/model/validation/AddressForwarderNameValidator.java
@@ -0,0 +1,32 @@
+/*
+ * 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.model.validation;
+
+import io.enmasse.address.model.AddressSpecForwarder;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+import java.util.regex.Pattern;
+
+public class AddressForwarderNameValidator implements ConstraintValidator<AddressForwarderName, AddressSpecForwarder> {
+
+    private static final Pattern NAME_PATTERN = Pattern.compile("[a-zA-Z0-9]+");
+
+    @Override
+    public boolean isValid(AddressSpecForwarder forwarder, ConstraintValidatorContext context) {
+
+        if (forwarder == null) {
+            return true;
+        }
+
+        if (forwarder.getName() == null) {
+            return false;
+        }
+
+        return NAME_PATTERN.matcher(forwarder.getName()).matches();
+    }
+}
+
diff --git a/api-model/src/main/java/io/enmasse/model/validation/AddressSpaceConnectorAddressRuleName.java b/api-model/src/main/java/io/enmasse/model/validation/AddressSpaceConnectorAddressRuleName.java
new file mode 100644
index 000000000..0605489b3
--- /dev/null
+++ b/api-model/src/main/java/io/enmasse/model/validation/AddressSpaceConnectorAddressRuleName.java
@@ -0,0 +1,33 @@
+/*
+ * 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.model.validation;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@Target({TYPE, ANNOTATION_TYPE})
+@Retention(RUNTIME)
+@Constraint(validatedBy = {AddressSpaceConnectorAddressRuleNameValidator.class})
+@Documented
+/**
+ * Validate the name field of an address space connector.
+ */
+public @interface AddressSpaceConnectorAddressRuleName {
+
+    String message() default "Invalid address space connector address rule name";
+
+    Class<?>[] groups() default {};
+
+    Class<? extends Payload>[] payload() default {};
+
+}
diff --git a/api-model/src/main/java/io/enmasse/model/validation/AddressSpaceConnectorAddressRuleNameValidator.java b/api-model/src/main/java/io/enmasse/model/validation/AddressSpaceConnectorAddressRuleNameValidator.java
new file mode 100644
index 000000000..c04633b2c
--- /dev/null
+++ b/api-model/src/main/java/io/enmasse/model/validation/AddressSpaceConnectorAddressRuleNameValidator.java
@@ -0,0 +1,32 @@
+/*
+ * 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.model.validation;
+
+import io.enmasse.address.model.AddressSpaceSpecConnectorAddressRule;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+import java.util.regex.Pattern;
+
+public class AddressSpaceConnectorAddressRuleNameValidator implements ConstraintValidator<AddressSpaceConnectorAddressRuleName, AddressSpaceSpecConnectorAddressRule> {
+
+    private static final Pattern NAME_PATTERN = Pattern.compile("[a-zA-Z0-9]+");
+
+    @Override
+    public boolean isValid(AddressSpaceSpecConnectorAddressRule rule, ConstraintValidatorContext context) {
+
+        if (rule == null) {
+            return true;
+        }
+
+        if (rule.getName() == null) {
+            return false;
+        }
+
+        return NAME_PATTERN.matcher(rule.getName()).matches();
+    }
+}
+
diff --git a/api-model/src/main/java/io/enmasse/model/validation/AddressSpaceConnectorAddressRulePattern.java b/api-model/src/main/java/io/enmasse/model/validation/AddressSpaceConnectorAddressRulePattern.java
new file mode 100644
index 000000000..405a6c9c2
--- /dev/null
+++ b/api-model/src/main/java/io/enmasse/model/validation/AddressSpaceConnectorAddressRulePattern.java
@@ -0,0 +1,33 @@
+/*
+ * 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.model.validation;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@Target({TYPE, ANNOTATION_TYPE})
+@Retention(RUNTIME)
+@Constraint(validatedBy = {AddressSpaceConnectorAddressRulePatternValidator.class})
+@Documented
+/**
+ * Validate the pattern field of an address space connector.
+ */
+public @interface AddressSpaceConnectorAddressRulePattern {
+
+    String message() default "Invalid address space connector address rule pattern";
+
+    Class<?>[] groups() default {};
+
+    Class<? extends Payload>[] payload() default {};
+
+}
diff --git a/api-model/src/main/java/io/enmasse/model/validation/AddressSpaceConnectorAddressRulePatternValidator.java b/api-model/src/main/java/io/enmasse/model/validation/AddressSpaceConnectorAddressRulePatternValidator.java
new file mode 100644
index 000000000..ab33d2258
--- /dev/null
+++ b/api-model/src/main/java/io/enmasse/model/validation/AddressSpaceConnectorAddressRulePatternValidator.java
@@ -0,0 +1,32 @@
+/*
+ * 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.model.validation;
+
+import io.enmasse.address.model.AddressSpaceSpecConnectorAddressRule;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+import java.util.regex.Pattern;
+
+public class AddressSpaceConnectorAddressRulePatternValidator implements ConstraintValidator<AddressSpaceConnectorAddressRulePattern, AddressSpaceSpecConnectorAddressRule> {
+
+    private static final Pattern PATTERN = Pattern.compile("([^/]+(/?[^/]+)*)*");
+
+    @Override
+    public boolean isValid(AddressSpaceSpecConnectorAddressRule rule, ConstraintValidatorContext context) {
+
+        if (rule == null) {
+            return true;
+        }
+
+        if (rule.getPattern() == null) {
+            return false;
+        }
+
+        return PATTERN.matcher(rule.getPattern()).matches();
+    }
+}
+
diff --git a/api-model/src/main/java/io/enmasse/model/validation/AddressSpaceConnectorName.java b/api-model/src/main/java/io/enmasse/model/validation/AddressSpaceConnectorName.java
new file mode 100644
index 000000000..d6f6362c3
--- /dev/null
+++ b/api-model/src/main/java/io/enmasse/model/validation/AddressSpaceConnectorName.java
@@ -0,0 +1,33 @@
+/*
+ * 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.model.validation;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@Target({TYPE, ANNOTATION_TYPE})
+@Retention(RUNTIME)
+@Constraint(validatedBy = {AddressSpaceConnectorNameValidator.class})
+@Documented
+/**
+ * Validate the {@code .name} field conform to address space connector naming rules.
+ */
+public @interface AddressSpaceConnectorName {
+
+    String message() default "Invalid address space connector name";
+
+    Class<?>[] groups() default {};
+
+    Class<? extends Payload>[] payload() default {};
+
+}
diff --git a/api-model/src/main/java/io/enmasse/model/validation/AddressSpaceConnectorNameValidator.java b/api-model/src/main/java/io/enmasse/model/validation/AddressSpaceConnectorNameValidator.java
new file mode 100644
index 000000000..acf9e5980
--- /dev/null
+++ b/api-model/src/main/java/io/enmasse/model/validation/AddressSpaceConnectorNameValidator.java
@@ -0,0 +1,34 @@
+/*
+ * 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.model.validation;
+
+import io.enmasse.address.model.AddressSpaceSpecConnector;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+import java.util.regex.Pattern;
+
+public class AddressSpaceConnectorNameValidator implements ConstraintValidator<AddressSpaceConnectorName, AddressSpaceSpecConnector> {
+
+    private static final Pattern NAME_PATTERN = Pattern.compile("[a-zA-Z0-9]+");
+
+    @Override
+    public boolean isValid(AddressSpaceSpecConnector connector, ConstraintValidatorContext context) {
+
+        if (connector == null) {
+            // if the object to check is null, we ignore it
+            return true;
+        }
+
+        if (connector.getName() == null) {
+            // we do require a name
+            return false;
+        }
+
+        return NAME_PATTERN.matcher(connector.getName()).matches();
+    }
+}
+
diff --git a/api-model/src/main/java/io/enmasse/model/validation/StringOrSecretSelectorSpec.java b/api-model/src/main/java/io/enmasse/model/validation/StringOrSecretSelectorSpec.java
new file mode 100644
index 000000000..e0ce078f3
--- /dev/null
+++ b/api-model/src/main/java/io/enmasse/model/validation/StringOrSecretSelectorSpec.java
@@ -0,0 +1,33 @@
+/*
+ * 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.model.validation;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@Target({TYPE, ANNOTATION_TYPE})
+@Retention(RUNTIME)
+@Constraint(validatedBy = {StringOrSecretSelectorValidator.class})
+@Documented
+/**
+ * Validate that either value or valueFromSecret is set
+ */
+public @interface StringOrSecretSelectorSpec {
+
+    String message() default "Invalid selector";
+
+    Class<?>[] groups() default {};
+
+    Class<? extends Payload>[] payload() default {};
+
+}
diff --git a/api-model/src/main/java/io/enmasse/model/validation/StringOrSecretSelectorValidator.java b/api-model/src/main/java/io/enmasse/model/validation/StringOrSecretSelectorValidator.java
new file mode 100644
index 000000000..7706cdaa9
--- /dev/null
+++ b/api-model/src/main/java/io/enmasse/model/validation/StringOrSecretSelectorValidator.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.model.validation;
+
+import io.enmasse.address.model.StringOrSecretSelector;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+
+public class StringOrSecretSelectorValidator implements ConstraintValidator<StringOrSecretSelectorSpec, StringOrSecretSelector> {
+
+    @Override
+    public boolean isValid(StringOrSecretSelector selector, ConstraintValidatorContext context) {
+
+        if (selector == null) {
+            return true;
+        }
+
+        return (selector.getValue() == null ^ selector.getValueFromSecret() == null);
+    }
+}
+
diff --git a/api-server/src/main/java/io/enmasse/api/v1/AddressApiHelper.java b/api-server/src/main/java/io/enmasse/api/v1/AddressApiHelper.java
index 5939a2d46..47ad06e2e 100644
--- a/api-server/src/main/java/io/enmasse/api/v1/AddressApiHelper.java
+++ b/api-server/src/main/java/io/enmasse/api/v1/AddressApiHelper.java
@@ -91,14 +91,18 @@ public class AddressApiHelper {
         return new AddressResolver(type);
     }
 
-    private AddressSpace getAddressSpace(String namespace, String addressSpaceId) throws Exception {
+    public AddressSpace getAddressSpace(String namespace, String addressSpaceId) throws Exception {
         return addressSpaceApi.getAddressSpaceWithName(namespace, addressSpaceId)
                 .orElseThrow(() -> new NotFoundException("Address space " + addressSpaceId + " not found"));
     }
 
+    public Optional<Address> getAddress(String namespace, AddressSpace addressSpace, String address) throws Exception {
+        return addressSpaceApi.withAddressSpace(addressSpace).getAddressWithName(namespace, address);
+    }
+
     public Optional<Address> getAddress(String namespace, String addressSpaceId, String address) throws Exception {
         AddressSpace addressSpace = getAddressSpace(namespace, addressSpaceId);
-        return addressSpaceApi.withAddressSpace(addressSpace).getAddressWithName(namespace, address);
+        return getAddress(namespace, addressSpace, address);
     }
 
     public Address deleteAddress(String namespace, String addressSpaceId, String name) throws Exception {
@@ -109,8 +113,7 @@ public class AddressApiHelper {
         return addressOptional.filter(addressApi::deleteAddress).orElse(null);
     }
 
-    public Address createAddress(String addressSpaceId, Address address) throws Exception {
-        AddressSpace addressSpace = getAddressSpace(address.getMetadata().getNamespace(), addressSpaceId);
+    public Address createAddress(AddressSpace addressSpace, Address address) throws Exception {
         validateAddress(addressSpace, address);
         AddressApi addressApi = addressSpaceApi.withAddressSpace(addressSpace);
         addressApi.createAddress(address);
@@ -158,8 +161,7 @@ public class AddressApiHelper {
         apiMap.forEach((addr, api) -> api.createAddress(addr));
     }
 
-    public Address replaceAddress(String addressSpaceId, Address address) throws Exception {
-        AddressSpace addressSpace = getAddressSpace(address.getMetadata().getNamespace(), addressSpaceId);
+    public Address replaceAddress(AddressSpace addressSpace, Address address) throws Exception {
         validateAddress(addressSpace, address);
         AddressApi addressApi = addressSpaceApi.withAddressSpace(addressSpace);
         if (!addressApi.replaceAddress(address)) {
diff --git a/api-server/src/main/java/io/enmasse/api/v1/http/HttpAddressServiceBase.java b/api-server/src/main/java/io/enmasse/api/v1/http/HttpAddressServiceBase.java
index 2eeaa7399..bd9f71748 100644
--- a/api-server/src/main/java/io/enmasse/api/v1/http/HttpAddressServiceBase.java
+++ b/api-server/src/main/java/io/enmasse/api/v1/http/HttpAddressServiceBase.java
@@ -106,9 +106,10 @@ public class HttpAddressServiceBase {
         });
     }
 
-    Response internalCreateAddress(SecurityContext securityContext, UriInfo uriInfo, String namespace, String addressSpace, Address address) throws Exception {
+    Response internalCreateAddress(SecurityContext securityContext, UriInfo uriInfo, String namespace, String addressSpaceName, Address address) throws Exception {
         checkRequestBodyNotNull(address);
         DefaultValidator.validate(address);
+        AddressSpace addressSpace = apiHelper.getAddressSpace(namespace, addressSpaceName);
         Address finalAddress = setAddressDefaults(namespace, addressSpace, address, null);
         return doRequest("Error creating address", () -> {
             verifyAuthorized(securityContext, namespace, ResourceVerb.create);
@@ -119,21 +120,22 @@ public class HttpAddressServiceBase {
         });
     }
 
-    Response internalCreateAddresses(SecurityContext securityContext, UriInfo uriInfo, String namespace, String addressSpace, AddressList addressList) throws Exception {
+    Response internalCreateAddresses(SecurityContext securityContext, UriInfo uriInfo, String namespace, String addressSpaceName, AddressList addressList) throws Exception {
         checkRequestBodyNotNull(addressList);
         DefaultValidator.validate(addressList);
+        AddressSpace addressSpace = apiHelper.getAddressSpace(namespace, addressSpaceName);
         Set<Address> finalAddresses = addressList.getItems().stream()
                 .map(a -> setAddressDefaults(namespace, addressSpace, a, null))
                 .collect(Collectors.toSet());
         return doRequest("Error creating address", () -> {
             verifyAuthorized(securityContext, namespace, ResourceVerb.create);
-            apiHelper.createAddresses(addressSpace, finalAddresses);
+            apiHelper.createAddresses(addressSpaceName, finalAddresses);
             return Response.created(uriInfo.getAbsolutePathBuilder().build()).build();
         });
     }
 
     @SuppressWarnings("deprecation")
-    static Address setAddressDefaults(String namespace, String addressSpace, Address address, Address existing) {
+    static Address setAddressDefaults(String namespace, AddressSpace addressSpace, Address address, Address existing) {
         if (existing == null) {
             if (address.getMetadata().getNamespace() == null || address.getMetadata().getName() == null) {
                 AddressBuilder builder = new AddressBuilder(address);
@@ -142,7 +144,7 @@ public class HttpAddressServiceBase {
                 }
 
                 if (address.getMetadata().getName() == null) {
-                    builder.editOrNewMetadata().withName(Address.generateName(addressSpace, address.getSpec().getAddress())).endMetadata();
+                    builder.editOrNewMetadata().withName(Address.generateName(addressSpace.getMetadata().getName(), address.getSpec().getAddress())).endMetadata();
                 }
 
                 address = builder.build();
@@ -162,6 +164,7 @@ public class HttpAddressServiceBase {
 
                     .editOrNewSpec()
                     .withPlan(address.getSpec().getPlan())
+                    .withForwarders(address.getSpec().getForwarders())
                     .endSpec()
 
                     .build();
@@ -170,11 +173,43 @@ public class HttpAddressServiceBase {
         // validate the addressspace
 
         if ( address.getSpec().getAddressSpace() != null ) {
-            if ( !addressSpace.equals(address.getSpec().getAddressSpace())) {
+            if ( !addressSpace.getMetadata().getName().equals(address.getSpec().getAddressSpace())) {
                 throw new BadRequestException(String.format("Address space in spec section does not match address space in request URI"));
             }
         }
 
+        // validate forwarders
+        if (address.getSpec().getForwarders() != null && !address.getSpec().getForwarders().isEmpty()) {
+            if (addressSpace.getSpec().getConnectors() == null || addressSpace.getSpec().getConnectors().isEmpty()) {
+                throw new BadRequestException(String.format("Unable to create forwarders: There are no connectors configured for address space '%s'", addressSpace.getMetadata().getName()));
+            }
+
+            if (!"standard".equals(addressSpace.getSpec().getType())) {
+                throw new BadRequestException(String.format("Unable to create forwarders for addresses in address space type '%s': Forwarders can only be created for addresses in the 'standard' address space type", addressSpace.getSpec().getType()));
+            }
+
+            if (!Arrays.asList("queue", "subscription").contains(address.getSpec().getType())) {
+                throw new BadRequestException(String.format("Unable to create forwarders for address type '%s': Forwarders can only be created for address types 'queue' and 'subscription'", address.getSpec().getType()));
+            }
+
+            for (AddressSpecForwarder forwarder : address.getSpec().getForwarders()) {
+                boolean found = false;
+                for (AddressSpaceSpecConnector connector : addressSpace.getSpec().getConnectors()) {
+                    if (forwarder.getRemoteAddress().startsWith(connector.getName())) {
+                        found = true;
+                        break;
+                    }
+                }
+                if (!found) {
+                    throw new BadRequestException(String.format("Unable to create forwarder '%s': remoteAddress '%s' is not prefixed with any connector in address space '%s'", forwarder.getName(), forwarder.getRemoteAddress(), address.getMetadata().getName()));
+                }
+
+                if ("subscription".equals(address.getSpec().getType()) && AddressSpecForwarderDirection.in.equals(forwarder.getDirection())) {
+                    throw new BadRequestException(String.format("Unable to create forwarder '%s': direction 'in' is not allowed on 'subscription' address type", forwarder.getName()));
+                }
+            }
+        }
+
         return address;
     }
 
@@ -235,13 +270,14 @@ public class HttpAddressServiceBase {
         }
     }
 
-    Response internalReplaceAddress(SecurityContext securityContext, String namespace, String addressSpace, String addressNameFromURL, Address payload) throws Exception {
+    Response internalReplaceAddress(SecurityContext securityContext, String namespace, String addressSpaceName, String addressNameFromURL, Address payload) throws Exception {
         checkRequestBodyNotNull(payload);
         checkAddressObjectNameNotNull(payload, addressNameFromURL);
         checkMatchingAddressName(addressNameFromURL, payload);
         DefaultValidator.validate(payload);
         return doRequest("Error updating address", () -> {
             verifyAuthorized(securityContext, namespace, ResourceVerb.update);
+            AddressSpace addressSpace = apiHelper.getAddressSpace(namespace, addressSpaceName);
             Address existing = apiHelper.getAddress(namespace, addressSpace, addressNameFromURL).orElse(null);
             if (existing == null) {
                 return Response.status(404).entity(Status.notFound("Address", addressNameFromURL)).build();
@@ -283,11 +319,12 @@ public class HttpAddressServiceBase {
         });
     }
 
-    protected Response patchInternal(@Context SecurityContext securityContext, String namespace, String addressSpace, String addressName,
+    protected Response patchInternal(@Context SecurityContext securityContext, String namespace, String addressSpaceName, String addressName,
                                      CheckedFunction<JsonNode, JsonNode, JsonPatchException> patcher) throws Exception {
         return doRequest("Error patching address space " + addressName, () -> {
             verifyAuthorized(securityContext, namespace, ResourceVerb.patch);
 
+            AddressSpace addressSpace = apiHelper.getAddressSpace(namespace, addressSpaceName);
             Optional<Address> existing = apiHelper.getAddress(namespace, addressSpace, addressName);
 
             if (!existing.isPresent()) {
diff --git a/documentation/design/proposals/bridging_external.adoc b/documentation/design/proposals/bridging_external.adoc
index f7cf82d9a..26e9c13a3 100644
--- a/documentation/design/proposals/bridging_external.adoc
+++ b/documentation/design/proposals/bridging_external.adoc
@@ -130,6 +130,21 @@ For each forwarder on an address, the agent will create a connector from the bro
 * Set up an AMQP broker (i.e. Artemis) on a remote host
 * Configure an address space with connectors and create addresses with forwarders for the 4 different use cases listed initially
 
+List of functionalities/behaviours to test:
+  - [tested] Sending messages to a remote AMQP endpoint via a local address space - by creating a connector and using prefixing
+  - [tested] Receiving messages from a remote AMQP endpoint via a local address space - by creating a connector and using prefixing
+  - [tested] config a connector to refer to a host that does not exist. addressspace should report ready true and the connector's status should report the failure.
+  - invalid connector names (ie: using / in connector name)
+  - invalid patterns in address rule in connector (ie: queue*)
+  - Restart broker to ensure router reattached to broker and you can send/recv messages
+  - use TLS
+  - Using mutual TLS (SASL EXTERNAL) instead of credentials
+  - [tested] Forwarding messages from a local queue in a local address space to a destination on a remote AMQP endpoint
+  - [tested] Forwarding messages to a local queue in a local address space from a destination on a remote AMQP endpoint
+  - forward to FULL remote queue
+  - forward to FULL local queue
+  - try to forward messages when the remote broker is unavailable , and check how the messages are automatically forwarded when the remote broker comes back up
+
 == Documentation
 
 The addifional fields should be added to the address space reference, and the tenant guide should be extended to cover the use cases as a realistic scenario with examples.
diff --git a/standard-controller/src/main/java/io/enmasse/controller/standard/AddressController.java b/standard-controller/src/main/java/io/enmasse/controller/standard/AddressController.java
index a52c40a85..cac1899c2 100644
--- a/standard-controller/src/main/java/io/enmasse/controller/standard/AddressController.java
+++ b/standard-controller/src/main/java/io/enmasse/controller/standard/AddressController.java
@@ -29,7 +29,6 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.util.*;
-import java.util.function.BinaryOperator;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
@@ -132,7 +131,7 @@ public class AddressController implements Watcher<Address> {
                             .withReady(true)
                             .build());
                 }
-                address.getStatus().setForwarderStatuses(forwarderStatuses);
+                address.getStatus().setForwarders(forwarderStatuses);
             }
 
             Address existing = validAddresses.get(address.getSpec().getAddress());
@@ -260,7 +259,7 @@ public class AddressController implements Watcher<Address> {
 
         }
 
-        Set<Address> addressesWithForwarders = filterBy(addressSet, address -> (address.getStatus().getForwarderStatuses() != null && !address.getStatus().getForwarderStatuses().isEmpty()));
+        Set<Address> addressesWithForwarders = filterBy(addressSet, address -> (address.getStatus().getForwarders() != null && !address.getStatus().getForwarders().isEmpty()));
         long readyForwarders = countForwardersReady(addressesWithForwarders, true);
         long notReadyForwarders = countForwardersReady(addressesWithForwarders, false);
 
@@ -288,7 +287,7 @@ public class AddressController implements Watcher<Address> {
     private long countForwardersReady(Set<Address> addressesWithForwarders, boolean desired) {
         long total = 0;
         for (Address address : addressesWithForwarders) {
-            for (AddressStatusForwarder forwarder : address.getStatus().getForwarderStatuses()) {
+            for (AddressStatusForwarder forwarder : address.getStatus().getForwarders()) {
                 if (forwarder.isReady() == desired) {
                     total++;
                 }
diff --git a/standard-controller/src/main/java/io/enmasse/controller/standard/RouterStatus.java b/standard-controller/src/main/java/io/enmasse/controller/standard/RouterStatus.java
index b82ab4f46..796ca8782 100644
--- a/standard-controller/src/main/java/io/enmasse/controller/standard/RouterStatus.java
+++ b/standard-controller/src/main/java/io/enmasse/controller/standard/RouterStatus.java
@@ -186,9 +186,9 @@ class RouterStatus {
             }
 
             if (!found) {
-                updateForwarderStatus(forwarder.getName(), false, "Unable to find link for forwarder '" + forwarder.getName() + "'", address.getStatus().getForwarderStatuses());
+                updateForwarderStatus(forwarder.getName(), false, "Unable to find link for forwarder '" + forwarder.getName() + "'", address.getStatus().getForwarders());
             } else if (!isUp) {
-                updateForwarderStatus(forwarder.getName(), false, "Unable to find link in the up state for forwarder '" + forwarder.getName() + "'", address.getStatus().getForwarderStatuses());
+                updateForwarderStatus(forwarder.getName(), false, "Unable to find link in the up state for forwarder '" + forwarder.getName() + "'", address.getStatus().getForwarders());
             } else {
                 ok++;
             }


Test patch:
diff --git a/address-space-controller/src/test/java/io/enmasse/controller/RouterConfigControllerTest.java b/address-space-controller/src/test/java/io/enmasse/controller/RouterConfigControllerTest.java
index 4afdef75f..99e62e002 100644
--- a/address-space-controller/src/test/java/io/enmasse/controller/RouterConfigControllerTest.java
+++ b/address-space-controller/src/test/java/io/enmasse/controller/RouterConfigControllerTest.java
@@ -265,7 +265,7 @@ public class RouterConfigControllerTest {
 
         configController.reconcile(addressSpace);
 
-        AddressSpaceStatusConnector status = addressSpace.getStatus().getConnectorStatuses().get(0);
+        AddressSpaceStatusConnector status = addressSpace.getStatus().getConnectors().get(0);
         assertNotNull(status);
         assertEquals("remote1", status.getName());
         assertFalse(status.isReady());
@@ -334,7 +334,7 @@ public class RouterConfigControllerTest {
         assertEquals("remote1", lrOut.getConnection());
 
 
-        status = addressSpace.getStatus().getConnectorStatuses().get(0);
+        status = addressSpace.getStatus().getConnectors().get(0);
         assertNotNull(status);
         assertEquals("remote1", status.getName());
         assertTrue(status.isReady());
diff --git a/api-model/src/test/java/io/enmasse/address/model/AddressSpaceSpecConnectorAddressRuleTest.java b/api-model/src/test/java/io/enmasse/address/model/AddressSpaceSpecConnectorAddressRuleTest.java
new file mode 100644
index 000000000..e84a12758
--- /dev/null
+++ b/api-model/src/test/java/io/enmasse/address/model/AddressSpaceSpecConnectorAddressRuleTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.address.model;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+
+import static io.enmasse.address.model.validation.ValidationMatchers.isNotValid;
+import static io.enmasse.address.model.validation.ValidationMatchers.isValid;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class AddressSpaceSpecConnectorAddressRuleTest {
+    @Test
+    public void testAddressRuleValidation() {
+        assertTrue(validRule("a", "a"));
+        assertTrue(validRule("a", "b"));
+        assertTrue(validRule("a", "*"));
+        assertTrue(validRule("a", "a/b"));
+        assertTrue(validRule("a", "a/#"));
+        assertTrue(validRule("a", "#/a"));
+        assertTrue(validRule("a", "a/*"));
+        assertTrue(validRule("a", "*/a"));
+        assertTrue(validRule("a", "a/*/b"));
+        assertTrue(validRule("a", "*/a/#"));
+        assertTrue(validRule("a", "*/a/*/b"));
+
+        assertFalse(validRule(null, "a"));
+        assertFalse(validRule("", "a"));
+        assertFalse(validRule("a/", "a"));
+        assertFalse(validRule("a.", "a"));
+        assertFalse(validRule("..", "a"));
+        assertFalse(validRule("a", "/"));
+        assertFalse(validRule("a", "/a"));
+        assertFalse(validRule("a", "/*"));
+        assertFalse(validRule("a", "*/"));
+        assertFalse(validRule("a", "*//"));
+        assertFalse(validRule("a", "//"));
+    }
+
+    private boolean validRule(String name, String pattern) {
+        AddressSpaceSpecConnectorAddressRule c = new AddressSpaceSpecConnectorAddressRuleBuilder()
+                .withName(name)
+                .withPattern(pattern)
+                .build();
+        return isValid().matches(c);
+    }
+}
diff --git a/api-model/src/test/java/io/enmasse/address/model/AddressSpaceSpecConnectorTest.java b/api-model/src/test/java/io/enmasse/address/model/AddressSpaceSpecConnectorTest.java
new file mode 100644
index 000000000..af83f255e
--- /dev/null
+++ b/api-model/src/test/java/io/enmasse/address/model/AddressSpaceSpecConnectorTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.address.model;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+
+import static io.enmasse.address.model.validation.ValidationMatchers.isNotValid;
+import static io.enmasse.address.model.validation.ValidationMatchers.isValid;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class AddressSpaceSpecConnectorTest {
+    @Test
+    public void testConnectorValidation() {
+        AddressSpaceSpecConnector connector = new AddressSpaceSpecConnectorBuilder()
+                .withName("c1")
+                .build();
+        assertThat(connector, isNotValid());
+
+        connector = new AddressSpaceSpecConnectorBuilder()
+                .withName("c1")
+                .withEndpointHosts(new ArrayList<>())
+                .build();
+        assertThat(connector, isNotValid());
+
+
+        connector = new AddressSpaceSpecConnectorBuilder()
+                .withName("c1")
+                .addNewEndpointHost()
+                    .withHost("example.com")
+                    .endEndpointHost()
+                .build();
+        assertThat(connector, isValid());
+
+        assertTrue(validConnectorName("a"));
+        assertTrue(validConnectorName("1"));
+        assertTrue(validConnectorName("a1"));
+        assertFalse(validConnectorName(null));
+        assertFalse(validConnectorName(""));
+        assertFalse(validConnectorName("a1."));
+        assertFalse(validConnectorName("a1.."));
+        assertFalse(validConnectorName("a1/.."));
+    }
+
+    private boolean validConnectorName(String name) {
+        AddressSpaceSpecConnector c = new AddressSpaceSpecConnectorBuilder()
+                .withName(name)
+                .addNewEndpointHost()
+                .withHost("example.com")
+                .endEndpointHost()
+                .build();
+        return isValid().matches(c);
+    }
+}
diff --git a/api-model/src/test/java/io/enmasse/address/model/v1/address/AddressSpecForwarderTest.java b/api-model/src/test/java/io/enmasse/address/model/v1/address/AddressSpecForwarderTest.java
new file mode 100644
index 000000000..a88315610
--- /dev/null
+++ b/api-model/src/test/java/io/enmasse/address/model/v1/address/AddressSpecForwarderTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.address.model.v1.address;
+
+import io.enmasse.address.model.AddressSpecForwarder;
+import io.enmasse.address.model.AddressSpecForwarderBuilder;
+import io.enmasse.address.model.AddressSpecForwarderDirection;
+import org.junit.jupiter.api.Test;
+
+import static io.enmasse.address.model.validation.ValidationMatchers.isNotValid;
+import static io.enmasse.address.model.validation.ValidationMatchers.isValid;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class AddressSpecForwarderTest {
+    @Test
+    public void testForwarderValidation() {
+        AddressSpecForwarder forwarder = new AddressSpecForwarderBuilder()
+                .withName("c1")
+                .build();
+        assertThat(forwarder, isNotValid());
+
+        assertFalse(validForwarder("c", null, null));
+        assertFalse(validForwarder("c", AddressSpecForwarderDirection.in, null));
+        assertTrue(validForwarder("c", AddressSpecForwarderDirection.in, "r1"));
+        assertTrue(validForwarder("c1", AddressSpecForwarderDirection.in, "r1"));
+
+        assertFalse(validForwarder("", AddressSpecForwarderDirection.in, "r1"));
+        assertFalse(validForwarder(".c", AddressSpecForwarderDirection.in, "r1"));
+        assertFalse(validForwarder("c.", AddressSpecForwarderDirection.in, "r1"));
+        assertFalse(validForwarder("c/", AddressSpecForwarderDirection.in, "r1"));
+        assertFalse(validForwarder("/c", AddressSpecForwarderDirection.in, "r1"));
+        assertFalse(validForwarder("c*", AddressSpecForwarderDirection.in, "r1"));
+        assertFalse(validForwarder("c", AddressSpecForwarderDirection.in, ""));
+    }
+
+    private boolean validForwarder(String name, AddressSpecForwarderDirection dir, String remoteAddress) {
+        AddressSpecForwarder c = new AddressSpecForwarderBuilder()
+                .withName(name)
+                .withDirection(dir)
+                .withRemoteAddress(remoteAddress)
+                .build();
+        return isValid().matches(c);
+    }
+}
diff --git a/api-server/src/test/java/io/enmasse/api/v1/AddressApiHelperTest.java b/api-server/src/test/java/io/enmasse/api/v1/AddressApiHelperTest.java
index d0ad80fe8..1e9f1517b 100644
--- a/api-server/src/test/java/io/enmasse/api/v1/AddressApiHelperTest.java
+++ b/api-server/src/test/java/io/enmasse/api/v1/AddressApiHelperTest.java
@@ -5,37 +5,6 @@
 
 package io.enmasse.api.v1;
 
-import static org.hamcrest.CoreMatchers.is;
-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 static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.fail;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.same;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-
-import javax.ws.rs.BadRequestException;
-import javax.ws.rs.NotFoundException;
-import javax.ws.rs.core.SecurityContext;
-
-import org.apache.http.auth.BasicUserPrincipal;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.mockito.internal.util.collections.Sets;
-
 import io.enmasse.address.model.Address;
 import io.enmasse.address.model.AddressBuilder;
 import io.enmasse.address.model.AddressSpace;
@@ -43,19 +12,36 @@ import io.enmasse.address.model.AddressSpaceSpec;
 import io.enmasse.api.server.TestSchemaProvider;
 import io.enmasse.k8s.api.AddressApi;
 import io.enmasse.k8s.api.AddressSpaceApi;
-import io.fabric8.kubernetes.api.model.ObjectMeta;
+import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
+import org.apache.http.auth.BasicUserPrincipal;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import javax.ws.rs.BadRequestException;
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.core.SecurityContext;
+import java.util.*;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
 
 public class AddressApiHelperTest {
 
     private AddressApiHelper helper;
     private AddressApi addressApi;
     private SecurityContext securityContext;
+    private AddressSpace addressSpace;
 
     @BeforeEach
     public void setup() {
-        AddressSpace addressSpace = mock(AddressSpace.class);
+        addressSpace = mock(AddressSpace.class);
         when(addressSpace.getSpec()).thenReturn(mock(AddressSpaceSpec.class));
-        when(addressSpace.getMetadata()).thenReturn(mock(ObjectMeta.class));
+        when(addressSpace.getMetadata()).thenReturn(new ObjectMetaBuilder()
+                .withName("test")
+                .build());
         when(addressSpace.getSpec().getType()).thenReturn("brokered");
         AddressSpaceApi addressSpaceApi = mock(AddressSpaceApi.class);
         addressApi = mock(AddressApi.class);
@@ -75,7 +61,7 @@ public class AddressApiHelperTest {
         when(addressApi.listAddresses(any())).thenReturn(addresses);
         when(addressApi.replaceAddress(any())).thenReturn(true);
         Address invalidAddress = createAddress("q1", "q2");
-        Throwable exception = assertThrows(BadRequestException.class, () -> helper.replaceAddress("test", invalidAddress));
+        Throwable exception = assertThrows(BadRequestException.class, () -> helper.replaceAddress(addressSpace, invalidAddress));
         assertEquals("Address 'q2' already exists with resource name 'q2'", exception.getMessage());
         verify(addressApi, never()).replaceAddress(any(Address.class));
     }
@@ -86,7 +72,7 @@ public class AddressApiHelperTest {
         when(addressApi.listAddresses(any())).thenReturn(Collections.emptySet());
 
         Address addr = createAddress("someOtherName", "q1");
-        helper.createAddress("test", addr);
+        helper.createAddress(addressSpace, addr);
         verify(addressApi).createAddress(eq(addr));
     }
 
@@ -138,14 +124,14 @@ public class AddressApiHelperTest {
     public void testReplaceAddress() throws Exception {
         when(addressApi.replaceAddress(any())).thenReturn(true);
 
-        helper.replaceAddress("test", createAddress("q1"));
+        helper.replaceAddress(addressSpace, createAddress("q1"));
         verify(addressApi).replaceAddress(eq(createAddress("q1")));
     }
 
     @Test
     public void testReplaceAddressNotFound() throws Exception {
         when(addressApi.replaceAddress(any())).thenReturn(false);
-        Throwable exception = assertThrows(NotFoundException.class, () -> helper.replaceAddress("test", createAddress("q1")));
+        Throwable exception = assertThrows(NotFoundException.class, () -> helper.replaceAddress(addressSpace, createAddress("q1")));
         assertEquals("Address q1 not found", exception.getMessage());
     }
 
diff --git a/api-server/src/test/java/io/enmasse/api/v1/http/DefaultsTest.java b/api-server/src/test/java/io/enmasse/api/v1/http/DefaultsTest.java
index d2ae102ad..9f2fa8b4b 100644
--- a/api-server/src/test/java/io/enmasse/api/v1/http/DefaultsTest.java
+++ b/api-server/src/test/java/io/enmasse/api/v1/http/DefaultsTest.java
@@ -12,6 +12,8 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
 import java.util.HashMap;
 import java.util.Map;
 
+import io.enmasse.address.model.AddressSpace;
+import io.enmasse.address.model.AddressSpaceBuilder;
 import org.junit.jupiter.api.Test;
 
 import io.enmasse.address.model.Address;
@@ -33,7 +35,7 @@ public class DefaultsTest {
 
                 .build();
 
-        final Address resultAddress = HttpAddressServiceBase.setAddressDefaults("ns", "address-space", inputAddress, null);
+        final Address resultAddress = HttpAddressServiceBase.setAddressDefaults("ns", createAddressSpace("address-space"), inputAddress, null);
 
         assertNotNull(resultAddress.getMetadata());
         assertEquals("ns", resultAddress.getMetadata().getNamespace());
@@ -56,7 +58,7 @@ public class DefaultsTest {
                 .endMetadata()
                 .build();
 
-        final Address resultAddress = HttpAddressServiceBase.setAddressDefaults("ns", "address-space", inputAddress, existing);
+        final Address resultAddress = HttpAddressServiceBase.setAddressDefaults("ns", createAddressSpace("address-space"), inputAddress, existing);
 
         assertNotNull(resultAddress.getMetadata());
         assertEquals("ns", resultAddress.getMetadata().getNamespace());
@@ -80,7 +82,7 @@ public class DefaultsTest {
         inputAddress.getMetadata().setAnnotations(null);
         inputAddress.getMetadata().setLabels(null);
 
-        final Address resultAddress = HttpAddressServiceBase.setAddressDefaults("ns", "address-space", inputAddress, existing);
+        final Address resultAddress = HttpAddressServiceBase.setAddressDefaults("ns", createAddressSpace("address-space"), inputAddress, existing);
 
         assertNotNull(resultAddress.getMetadata());
         assertEquals("ns", resultAddress.getMetadata().getNamespace());
@@ -105,7 +107,7 @@ public class DefaultsTest {
                 .endMetadata()
                 .build();
 
-        final Address resultAddress = HttpAddressServiceBase.setAddressDefaults("ns", "address-space", inputAddress, existing);
+        final Address resultAddress = HttpAddressServiceBase.setAddressDefaults("ns", createAddressSpace("address-space"), inputAddress, existing);
 
         assertNotNull(resultAddress.getMetadata());
         assertEquals("ns", resultAddress.getMetadata().getNamespace());
@@ -121,4 +123,15 @@ public class DefaultsTest {
         assertNotNull(resultAddress.getSpec());
     }
 
+    private static AddressSpace createAddressSpace(String name) {
+        return new AddressSpaceBuilder()
+                .editOrNewMetadata()
+                .withName(name)
+                .endMetadata()
+                .editOrNewSpec()
+                .withType("standard")
+                .withPlan("small")
+                .endSpec()
+                .build();
+    }
 }
diff --git a/api-server/src/test/java/io/enmasse/api/v1/http/HttpAddressServiceTest.java b/api-server/src/test/java/io/enmasse/api/v1/http/HttpAddressServiceTest.java
index 3c37e56a0..cba5fe29e 100644
--- a/api-server/src/test/java/io/enmasse/api/v1/http/HttpAddressServiceTest.java
+++ b/api-server/src/test/java/io/enmasse/api/v1/http/HttpAddressServiceTest.java
@@ -137,7 +137,7 @@ public class HttpAddressServiceTest {
         addressApi.createAddress(address1);
 
         final JsonPatch patch = mapper.readValue("[" +
-                "{\"op\":\"replace\",\"path\":\"/metadata/name\",\"value\":\"newname\"}," +
+                "{\"op\":\"replace\",\"path\":\"/metadata/name\",\"value\":\"myspace.newname\"}," +
                 "{\"op\":\"replace\",\"path\":\"/metadata/namespace\",\"value\":\"newnamespace\"}," +
                 "{\"op\":\"add\",\"path\":\"/metadata/labels/mylabel\",\"value\":\"myvalue\"}" +
                 "]", JsonPatch.class);
diff --git a/standard-controller/src/test/java/io/enmasse/controller/standard/AddressControllerTest.java b/standard-controller/src/test/java/io/enmasse/controller/standard/AddressControllerTest.java
index 055dea7ce..4360e4ece 100644
--- a/standard-controller/src/test/java/io/enmasse/controller/standard/AddressControllerTest.java
+++ b/standard-controller/src/test/java/io/enmasse/controller/standard/AddressControllerTest.java
@@ -475,7 +475,7 @@ public class AddressControllerTest {
         ArgumentCaptor<Address> captor = ArgumentCaptor.forClass(Address.class);
         verify(mockApi).replaceAddress(captor.capture());
         Address captured = captor.getValue();
-        assertEquals(captured.getStatus().getForwarderStatuses().size(), a.getSpec().getForwarders().size());
-        assertFalse(captured.getStatus().getForwarderStatuses().get(0).isReady());
+        assertEquals(captured.getStatus().getForwarders().size(), a.getSpec().getForwarders().size());
+        assertFalse(captured.getStatus().getForwarders().get(0).isReady());
     }
 }
diff --git a/systemtests/src/main/java/io/enmasse/systemtest/utils/AddressSpaceUtils.java b/systemtests/src/main/java/io/enmasse/systemtest/utils/AddressSpaceUtils.java
index b72dd7ff9..c3b658b08 100644
--- a/systemtests/src/main/java/io/enmasse/systemtest/utils/AddressSpaceUtils.java
+++ b/systemtests/src/main/java/io/enmasse/systemtest/utils/AddressSpaceUtils.java
@@ -266,7 +266,7 @@ public class AddressSpaceUtils {
     public static boolean areAddressSpaceConnectorsReady(AddressSpace addressSpace) {
         return Optional.ofNullable(addressSpace)
             .map(AddressSpace::getStatus)
-            .map(AddressSpaceStatus::getConnectorStatuses)
+            .map(AddressSpaceStatus::getConnectors)
             .map(Stream::of)
             .orElseGet(Stream::empty)
             .flatMap(Collection::stream)
@@ -277,7 +277,7 @@ public class AddressSpaceUtils {
     public static String getConnectorStatuses(AddressSpace addressSpace) {
         return Optional.ofNullable(addressSpace)
             .map(AddressSpace::getStatus)
-            .map(AddressSpaceStatus::getConnectorStatuses)
+            .map(AddressSpaceStatus::getConnectors)
             .map(Stream::of)
             .orElseGet(Stream::empty)
             .flatMap(Collection::stream)
diff --git a/systemtests/src/main/java/io/enmasse/systemtest/utils/AddressUtils.java b/systemtests/src/main/java/io/enmasse/systemtest/utils/AddressUtils.java
index 4905baf77..ded30fe49 100644
--- a/systemtests/src/main/java/io/enmasse/systemtest/utils/AddressUtils.java
+++ b/systemtests/src/main/java/io/enmasse/systemtest/utils/AddressUtils.java
@@ -168,7 +168,7 @@ public class AddressUtils {
     public static boolean areForwardersReady(Address address) {
         return Optional.ofNullable(address)
                 .map(Address::getStatus)
-                .map(AddressStatus::getForwarderStatuses)
+                .map(AddressStatus::getForwarders)
                 .map(Stream::of)
                 .orElseGet(Stream::empty)
                 .flatMap(Collection::stream)