Repository: metal3-io/baremetal-operator
Pull request #797 (2021-02-16 00:18:44)

Prepend the namespace to ironic nodenames to avoid conflicts
Modify the hostname for ironic to prepend the namespace to the name to avoid naming conflicts. 


metal3-io-bot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/metal3-io/baremetal-operator/pull/797#" title="Author self-approved">asalkeld</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **maelk**
You can assign the PR to them by writing `/assign @maelk` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=metal3-io%2Fbaremetal-operator).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/metal3-io/baremetal-operator/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["maelk"]} -->
metal3-io-bot: Hi @asalkeld. Thanks for your PR.

I'm waiting for a [metal3-io](https://github.com/orgs/metal3-io/people) member to verify that this patch is reasonable to test. If it is, they should reply with `/ok-to-test` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should [join the org](https://github.com/orgs/metal3-io/people) to skip this step.

Once the patch is verified, the new status will be reflected by the `ok-to-test` label.

I understand the commands that are listed [here](https://go.k8s.io/bot-commands?repo=metal3-io%2Fbaremetal-operator).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>

asalkeld: /ok-to-test
metal3-io-bot: @asalkeld: Cannot trigger testing until a trusted user reviews the PR and leaves an `/ok-to-test` message.

<details>

In response to [this](https://github.com/metal3-io/baremetal-operator/pull/797#issuecomment-779582921):

>/ok-to-test


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
hardys: /cc @maelk PTAL - this changes the naming convention for hosts in Ironic, to avoid potential conflict in a multi-namespace deployment
metal3-io-bot: @sadasu: adding LGTM is restricted to approvers and reviewers in OWNERS files.

<details>

In response to [this](https://github.com/metal3-io/baremetal-operator/pull/797#pullrequestreview-602052869):

>/lgtm


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
sadasu: /test-integration
imain: lgtm!
metal3-io-bot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/metal3-io/baremetal-operator/pull/797#" title="Author self-approved">asalkeld</a>*, *<a href="https://github.com/metal3-io/baremetal-operator/pull/797#pullrequestreview-606620175" title="Approved">imain</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **maelk**
You can assign the PR to them by writing `/assign @maelk` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=metal3-io%2Fbaremetal-operator).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/metal3-io/baremetal-operator/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["maelk"]} -->
stbenjam: /ok-to-test
metal3-io-bot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/metal3-io/baremetal-operator/pull/797#" title="Author self-approved">asalkeld</a>*, *<a href="https://github.com/metal3-io/baremetal-operator/pull/797#pullrequestreview-606620175" title="Approved">imain</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **hardys**
You can assign the PR to them by writing `/assign @hardys` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=metal3-io%2Fbaremetal-operator).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/metal3-io/baremetal-operator/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["hardys"]} -->
asalkeld: /test-integration
asalkeld: /test-integration
kirankt: /test-integration
furkatgofurov7: There was [an issue](https://kubernetes.slack.com/archives/CHD49TLE7/p1615895171009200) with our CI recently, so tests did not run properly in the previous runs. Hopefully should be fine now.

/test-integration
metal3-io-bot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/metal3-io/baremetal-operator/pull/797#" title="Author self-approved">asalkeld</a>*, *<a href="https://github.com/metal3-io/baremetal-operator/pull/797#pullrequestreview-606620175" title="Approved">imain</a>*, *<a href="https://github.com/metal3-io/baremetal-operator/pull/797#pullrequestreview-619001403" title="Approved">zaneb</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=metal3-io%2Fbaremetal-operator).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/metal3-io/baremetal-operator/blob/master/OWNERS)~~ [zaneb]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
hardys: /test-integration
zaneb: /test-integration
hardys: /test-integration
zaneb: This has been around for a while and has had quite a few eyes on it.
/lgtm
metal3-io-bot: @asalkeld: The following test **failed**, say `/retest` to rerun them all:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
unit | 10c5a25da4ce228c463765dc827f9e86d2395663 | [link](https://prow.apps.ci.metal3.io/view/gcs/metal3-prow-artifacts/pr-logs/pull/metal3-io_baremetal-operator/797/unit/1375656710798053378) | `/test unit`



<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
metal3-io-bot: New changes are detected. LGTM label has been removed.
asalkeld: needed a unit test fix after @honza's PR got merged.
metal3-io-bot: @asalkeld: The following test **failed**, say `/retest` to rerun them all:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
govet | 3a0c8e2fbbae86e7c2c5549bb1d31585175cee98 | [link](https://prow.apps.ci.metal3.io/view/gcs/metal3-prow-artifacts/pr-logs/pull/metal3-io_baremetal-operator/797/govet/1376310651378470912) | `/test govet`



<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
asalkeld: /retest

Patch:
diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml
index 8cc788c9..b7f9601f 100644
--- a/config/manager/manager.yaml
+++ b/config/manager/manager.yaml
@@ -27,6 +27,10 @@ spec:
             valueFrom:
               fieldRef:
                 fieldPath: metadata.name
+          - name: POD_NAMESPACE
+            valueFrom:
+              fieldRef:
+                fieldPath: metadata.namespace
         envFrom:
           - configMapRef:
               name: ironic
@@ -37,4 +41,4 @@ spec:
             port: 9440
           initialDelaySeconds: 3
           periodSeconds: 3
-      terminationGracePeriodSeconds: 10
\ No newline at end of file
+      terminationGracePeriodSeconds: 10
diff --git a/config/render/capm3.yaml b/config/render/capm3.yaml
index 95779965..c36b761f 100644
--- a/config/render/capm3.yaml
+++ b/config/render/capm3.yaml
@@ -1039,6 +1039,10 @@ spec:
           valueFrom:
             fieldRef:
               fieldPath: metadata.name
+        - name: POD_NAMESPACE
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.namespace
         envFrom:
         - configMapRef:
             name: baremetal-operator-ironic
diff --git a/main.go b/main.go
index 6958e5f0..5fea6ea6 100644
--- a/main.go
+++ b/main.go
@@ -103,13 +103,18 @@ func main() {
 
 	printVersion()
 
+	leaderElectionNamespace := os.Getenv("POD_NAMESPACE")
+	if leaderElectionNamespace == "" {
+		leaderElectionNamespace = watchNamespace
+	}
+
 	mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
 		Scheme:                  scheme,
 		MetricsBindAddress:      metricsAddr,
 		Port:                    0, // Add flag with default of 9443 when adding webhooks
 		LeaderElection:          enableLeaderElection,
 		LeaderElectionID:        "baremetal-operator",
-		LeaderElectionNamespace: watchNamespace,
+		LeaderElectionNamespace: leaderElectionNamespace,
 		Namespace:               watchNamespace,
 		HealthProbeBindAddress:  healthAddr,
 	})
diff --git a/pkg/provisioner/ironic/ironic.go b/pkg/provisioner/ironic/ironic.go
index f45506e2..0570fb18 100644
--- a/pkg/provisioner/ironic/ironic.go
+++ b/pkg/provisioner/ironic/ironic.go
@@ -51,10 +51,11 @@ var (
 
 const (
 	// See nodes.Node.PowerState for details
-	powerOn      = "power on"
-	powerOff     = "power off"
-	softPowerOff = "soft power off"
-	powerNone    = "None"
+	powerOn       = "power on"
+	powerOff      = "power off"
+	softPowerOff  = "soft power off"
+	powerNone     = "None"
+	nameSeparator = "~"
 )
 
 var bootModeCapabilities = map[metal3v1alpha1.BootMode]string{
@@ -183,8 +184,6 @@ func newProvisionerWithSettings(host metal3v1alpha1.BareMetalHost, bmcCreds bmc.
 }
 
 func newProvisionerWithIronicClients(host metal3v1alpha1.BareMetalHost, bmcCreds bmc.Credentials, publisher provisioner.EventPublisher, clientIronic *gophercloud.ServiceClient, clientInspector *gophercloud.ServiceClient) (*ironicProvisioner, error) {
-	provisionerLogger := log.WithValues("host", host.Name)
-
 	// Ensure we have a microversion high enough to get the features
 	// we need.
 	clientIronic.Microversion = "1.56"
@@ -194,10 +193,10 @@ func newProvisionerWithIronicClients(host metal3v1alpha1.BareMetalHost, bmcCreds
 		bmcCreds:  bmcCreds,
 		client:    clientIronic,
 		inspector: clientInspector,
-		log:       provisionerLogger,
-		debugLog:  provisionerLogger.V(1),
 		publisher: publisher,
 	}
+	p.log = log.WithValues("host", p.ironicNodeNameFromHost())
+	p.debugLog = p.log.V(1)
 
 	return p, nil
 }
@@ -308,18 +307,25 @@ func (p *ironicProvisioner) findExistingHost() (ironicNode *nodes.Node, err erro
 	}
 
 	// Try to load the node by name
-	p.log.Info("looking for existing node by name", "name", p.host.Name)
-	ironicNode, err = nodes.Get(p.client, p.host.Name).Extract()
-	switch err.(type) {
-	case nil:
-		p.debugLog.Info("found existing node by name")
-		return ironicNode, nil
-	case gophercloud.ErrDefault404:
-		p.log.Info(
-			fmt.Sprintf("node with name %s doesn't exist", p.host.Name))
-	default:
-		return nil, errors.Wrap(err,
-			fmt.Sprintf("failed to find node by name %s", p.host.Name))
+	nodeSearchList := []string{p.ironicNodeNameFromHost()}
+	if !strings.Contains(p.host.Name, nameSeparator) {
+		nodeSearchList = append(nodeSearchList, p.host.Name)
+	}
+
+	for _, nodeName := range nodeSearchList {
+		p.debugLog.Info("looking for existing node by name", "name", nodeName)
+		ironicNode, err = nodes.Get(p.client, nodeName).Extract()
+		switch err.(type) {
+		case nil:
+			p.debugLog.Info("found existing node by name")
+			return ironicNode, nil
+		case gophercloud.ErrDefault404:
+			p.log.Info(
+				fmt.Sprintf("node with name %s doesn't exist", nodeName))
+		default:
+			return nil, errors.Wrap(err,
+				fmt.Sprintf("failed to find node by name %s", nodeName))
+		}
 	}
 
 	// Try to load the node by port address
@@ -501,12 +507,12 @@ func (p *ironicProvisioner) ValidateManagementAccess(credentialsChanged, force b
 		// if there are differences.
 		provID = ironicNode.UUID
 
-		if ironicNode.Name == "" {
+		if ironicNode.Name != p.ironicNodeNameFromHost() {
 			updates := nodes.UpdateOpts{
 				nodes.UpdateOperation{
 					Op:    nodes.ReplaceOp,
 					Path:  "/name",
-					Value: p.host.Name,
+					Value: p.ironicNodeNameFromHost(),
 				},
 			}
 			ironicNode, err = nodes.Update(p.client, ironicNode.UUID, updates).Extract()
@@ -1840,6 +1846,10 @@ func (p *ironicProvisioner) softPowerOff() (result provisioner.Result, err error
 	return result, nil
 }
 
+func (p *ironicProvisioner) ironicNodeNameFromHost() string {
+	return p.host.Namespace + nameSeparator + p.host.Name
+}
+
 // IsReady checks if the provisioning backend is available
 func (p *ironicProvisioner) IsReady() (result bool, err error) {
 	p.debugLog.Info("verifying ironic provisioner dependencies")
@@ -1857,7 +1867,7 @@ func (p *ironicProvisioner) HasProvisioningCapacity() (result bool, err error) {
 	}
 
 	// If the current host is already under processing then let's skip the test
-	if _, ok := hosts[p.host.Name]; ok {
+	if _, ok := hosts[p.ironicNodeNameFromHost()]; ok {
 		return true, nil
 	}
 


Test patch:
diff --git a/pkg/provisioner/ironic/delete_test.go b/pkg/provisioner/ironic/delete_test.go
index 00a1eb9a..6230964f 100644
--- a/pkg/provisioner/ironic/delete_test.go
+++ b/pkg/provisioner/ironic/delete_test.go
@@ -86,7 +86,7 @@ func TestDelete(t *testing.T) {
 		},
 		{
 			name:   "not-ironic-node",
-			ironic: testserver.NewIronic(t).Ready().NoNode(nodeUUID).NoNode("myhost"),
+			ironic: testserver.NewIronic(t).Ready().NoNode(nodeUUID).NoNode("myns" + nameSeparator + "myhost").NoNode("myhost"),
 		},
 		{
 			name: "available-node",
diff --git a/pkg/provisioner/ironic/findhost_test.go b/pkg/provisioner/ironic/findhost_test.go
index 79f95d07..e8b21af6 100644
--- a/pkg/provisioner/ironic/findhost_test.go
+++ b/pkg/provisioner/ironic/findhost_test.go
@@ -23,7 +23,7 @@ func TestFindExistingHost(t *testing.T) {
 			name:           "no-node",
 			hostName:       "name",
 			provisioningID: "uuid",
-			ironic:         testserver.NewIronic(t).NoNode("name").NoNode("uuid"),
+			ironic:         testserver.NewIronic(t).NoNode("myns" + nameSeparator + "name").NoNode("name").NoNode("uuid"),
 		},
 		{
 			name:           "by-name",
@@ -31,21 +31,21 @@ func TestFindExistingHost(t *testing.T) {
 			provisioningID: "uuid",
 			ironic: testserver.NewIronic(t).NoNode("uuid").
 				Node(nodes.Node{
-					Name: "name",
+					Name: "myns" + nameSeparator + "name",
 					UUID: "different-uuid",
 				}),
-			nodeName: "name",
+			nodeName: "myns" + nameSeparator + "name",
 		},
 		{
 			name:           "by-uuid",
 			hostName:       "name",
 			provisioningID: "uuid",
-			ironic: testserver.NewIronic(t).NoNode("name").
+			ironic: testserver.NewIronic(t).NoNode("myns" + nameSeparator + "name").NoNode("name").
 				Node(nodes.Node{
-					Name: "different-name",
+					Name: "myns" + nameSeparator + "different-name",
 					UUID: "uuid",
 				}),
-			nodeName: "different-name",
+			nodeName: "myns" + nameSeparator + "different-name",
 		},
 	}
 
diff --git a/pkg/provisioner/ironic/provisioncapacity_test.go b/pkg/provisioner/ironic/provisioncapacity_test.go
index f37f0c27..1b54c124 100644
--- a/pkg/provisioner/ironic/provisioncapacity_test.go
+++ b/pkg/provisioner/ironic/provisioncapacity_test.go
@@ -62,7 +62,7 @@ func TestHasProvisioningCapacity(t *testing.T) {
 			allNodes := []nodes.Node{}
 			for n, state := range tc.nodeStates {
 				allNodes = append(allNodes, nodes.Node{
-					Name:           fmt.Sprintf("node-%d", n),
+					Name:           fmt.Sprintf("myns%snode-%d", nameSeparator, n),
 					ProvisionState: string(state),
 				})
 			}
diff --git a/pkg/provisioner/ironic/updatehardwarestate_test.go b/pkg/provisioner/ironic/updatehardwarestate_test.go
index e4e89b0b..691a2f0a 100644
--- a/pkg/provisioner/ironic/updatehardwarestate_test.go
+++ b/pkg/provisioner/ironic/updatehardwarestate_test.go
@@ -90,15 +90,15 @@ func TestUpdateHardwareState(t *testing.T) {
 			name: "node-not-found-by-name",
 
 			hostName: "worker-0",
-			ironic:   testserver.NewIronic(t).Ready().NoNode(nodeUUID).NodeError("myhost", http.StatusGatewayTimeout),
+			ironic:   testserver.NewIronic(t).Ready().NoNode(nodeUUID).NodeError("myns.myhost", http.StatusGatewayTimeout),
 
-			expectedError: "failed to find existing host: failed to find node by name worker-0: EOF",
+			expectedError: "failed to find existing host: failed to find node by name myns.worker-0: EOF",
 
 			expectUnreadablePower: true,
 		},
 		{
 			name:   "not-ironic-node",
-			ironic: testserver.NewIronic(t).Ready().NoNode(nodeUUID).NoNode("myhost"),
+			ironic: testserver.NewIronic(t).Ready().NoNode(nodeUUID).NoNode("myns" + nameSeparator + "myhost").NoNode("myhost"),
 
 			expectedError: "Host not registered",
 
diff --git a/pkg/provisioner/ironic/validatemanagementaccess_test.go b/pkg/provisioner/ironic/validatemanagementaccess_test.go
index 7369fba7..3f8b13ae 100644
--- a/pkg/provisioner/ironic/validatemanagementaccess_test.go
+++ b/pkg/provisioner/ironic/validatemanagementaccess_test.go
@@ -22,7 +22,7 @@ func TestValidateManagementAccessNoMAC(t *testing.T) {
 	host.Spec.BootMACAddress = ""
 	host.Status.Provisioning.ID = "" // so we don't lookup by uuid
 
-	ironic := testserver.NewIronic(t).Ready().NoNode(host.Name)
+	ironic := testserver.NewIronic(t).Ready().NoNode(host.Namespace + nameSeparator + host.Name).NoNode(host.Name)
 	ironic.Start()
 	defer ironic.Stop()
 
@@ -50,7 +50,7 @@ func TestValidateManagementAccessMACOptional(t *testing.T) {
 	// Set up ironic server to return the node
 	ironic := testserver.NewIronic(t).Ready().
 		Node(nodes.Node{
-			Name: host.Name,
+			Name: host.Namespace + nameSeparator + host.Name,
 			UUID: host.Status.Provisioning.ID,
 		})
 	ironic.Start()
@@ -85,7 +85,7 @@ func TestValidateManagementAccessCreateNodeNoImage(t *testing.T) {
 		createdNode = &node
 	}
 
-	ironic := testserver.NewIronic(t).Ready().CreateNodes(createCallback).NoNode(host.Name)
+	ironic := testserver.NewIronic(t).Ready().CreateNodes(createCallback).NoNode(host.Namespace + nameSeparator + host.Name).NoNode(host.Name)
 	ironic.Start()
 	defer ironic.Stop()
 
@@ -120,7 +120,7 @@ func TestValidateManagementAccessCreateWithImage(t *testing.T) {
 		createdNode = &node
 	}
 
-	ironic := testserver.NewIronic(t).Ready().CreateNodes(createCallback).NoNode(host.Name)
+	ironic := testserver.NewIronic(t).Ready().CreateNodes(createCallback).NoNode(host.Namespace + nameSeparator + host.Name).NoNode(host.Name)
 	ironic.AddDefaultResponse("/v1/nodes/node-0", "PATCH", http.StatusOK, "{}")
 	ironic.Start()
 	defer ironic.Stop()
@@ -158,7 +158,7 @@ func TestValidateManagementAccessCreateWithLiveIso(t *testing.T) {
 		createdNode = &node
 	}
 
-	ironic := testserver.NewIronic(t).Ready().CreateNodes(createCallback).NoNode(host.Name)
+	ironic := testserver.NewIronic(t).Ready().CreateNodes(createCallback).NoNode(host.Namespace + nameSeparator + host.Name).NoNode(host.Name)
 	ironic.AddDefaultResponse("/v1/nodes/node-0", "PATCH", http.StatusOK, "{}")
 	ironic.Start()
 	defer ironic.Stop()
@@ -236,7 +236,7 @@ func TestValidateManagementAccessCreateNodeImageSpecOrStatus(t *testing.T) {
 				createdNode = &node
 			}
 
-			ironic := testserver.NewIronic(t).Ready().CreateNodes(createCallback).NoNode(host.Name)
+			ironic := testserver.NewIronic(t).Ready().CreateNodes(createCallback).NoNode(host.Namespace + nameSeparator + host.Name).NoNode(host.Name)
 			ironic.NodeUpdate(nodes.Node{UUID: "node-0"})
 			ironic.Start()
 			defer ironic.Stop()
@@ -278,7 +278,7 @@ func TestValidateManagementAccessExistingNode(t *testing.T) {
 	}
 
 	ironic := testserver.NewIronic(t).Ready().CreateNodes(createCallback).Node(nodes.Node{
-		Name: host.Name,
+		Name: host.Namespace + nameSeparator + host.Name,
 		UUID: "uuid",
 	})
 	ironic.Start()
@@ -300,6 +300,43 @@ func TestValidateManagementAccessExistingNode(t *testing.T) {
 	assert.Equal(t, "uuid", provID)
 }
 
+func TestValidateManagementAccessExistingNodeNameUpdate(t *testing.T) {
+	// Create a host without a bootMACAddress and with a BMC that
+	// does not require one.
+	host := makeHost()
+	host.Spec.BootMACAddress = ""
+	host.Status.Provisioning.ID = "uuid"
+
+	ironic := testserver.NewIronic(t).
+		Node(
+			nodes.Node{
+				Name: host.Name,
+				UUID: "uuid",
+			}).
+		NodeUpdate(
+			nodes.Node{
+				Name: host.Namespace + nameSeparator + host.Name,
+				UUID: "uuid",
+			})
+	ironic.Start()
+	defer ironic.Stop()
+
+	auth := clients.AuthConfig{Type: clients.NoAuth}
+	prov, err := newProvisionerWithSettings(host, bmc.Credentials{}, nullEventPublisher,
+		ironic.Endpoint(), auth, testserver.NewInspector(t).Endpoint(), auth,
+	)
+	if err != nil {
+		t.Fatalf("could not create provisioner: %s", err)
+	}
+
+	result, _, err := prov.ValidateManagementAccess(false, false)
+	if err != nil {
+		t.Fatalf("error from ValidateManagementAccess: %s", err)
+	}
+	assert.Equal(t, "", result.ErrorMessage)
+	assert.Equal(t, false, result.Dirty)
+}
+
 func TestValidateManagementAccessExistingNodeContinue(t *testing.T) {
 	statuses := []nodes.ProvisionState{
 		nodes.Manageable,
@@ -340,7 +377,7 @@ func TestValidateManagementAccessExistingNodeContinue(t *testing.T) {
 			}
 
 			ironic := testserver.NewIronic(t).Ready().CreateNodes(createCallback).Node(nodes.Node{
-				Name:           host.Name,
+				Name:           host.Namespace + nameSeparator + host.Name,
 				UUID:           "", // to match status in host
 				ProvisionState: string(status),
 			})
@@ -384,7 +421,7 @@ func TestValidateManagementAccessExistingNodeWaiting(t *testing.T) {
 			}
 
 			node := nodes.Node{
-				Name:           host.Name,
+				Name:           host.Namespace + nameSeparator + host.Name,
 				UUID:           "uuid", // to match status in host
 				ProvisionState: string(status),
 			}
@@ -420,12 +457,12 @@ func TestValidateManagementAccessNewCredentials(t *testing.T) {
 	ironic := testserver.NewIronic(t).
 		Node(
 			nodes.Node{
-				Name: host.Name,
+				Name: host.Namespace + nameSeparator + host.Name,
 				UUID: "uuid",
 			}).
 		NodeUpdate(
 			nodes.Node{
-				Name: host.Name,
+				Name: host.Namespace + nameSeparator + host.Name,
 				UUID: "uuid",
 				DriverInfo: map[string]interface{}{
 					"test_address": "test.bmc",
@@ -472,6 +509,7 @@ func TestValidateManagementAccessLinkExistingIronicNodeByMAC(t *testing.T) {
 	}
 
 	ironic := testserver.NewIronic(t).Ready().CreateNodes(createCallback).Node(existingNode).Port(existingNodePort)
+	ironic.AddDefaultResponse("/v1/nodes/myns"+nameSeparator+"myhost", "GET", http.StatusNotFound, "")
 	ironic.AddDefaultResponse("/v1/nodes/myhost", "GET", http.StatusNotFound, "")
 	ironic.AddDefaultResponse("/v1/nodes/"+existingNode.UUID, "PATCH", http.StatusOK, "{}")
 	ironic.Start()
@@ -515,6 +553,7 @@ func TestValidateManagementAccessExistingPortWithWrongUUID(t *testing.T) {
 	}
 
 	ironic := testserver.NewIronic(t).Ready().CreateNodes(createCallback).Node(existingNode).Port(existingNodePort)
+	ironic.AddDefaultResponse("/v1/nodes/myns"+nameSeparator+"myhost", "GET", http.StatusNotFound, "")
 	ironic.AddDefaultResponse("/v1/nodes/myhost", "GET", http.StatusNotFound, "")
 	ironic.AddDefaultResponse("/v1/nodes/random-wrong-id", "GET", http.StatusNotFound, "")
 	ironic.Start()
@@ -558,6 +597,7 @@ func TestValidateManagementAccessExistingPortButHasName(t *testing.T) {
 	}
 
 	ironic := testserver.NewIronic(t).Ready().CreateNodes(createCallback).Node(existingNode).Port(existingNodePort)
+	ironic.AddDefaultResponse("/v1/nodes/myns"+nameSeparator+"myhost", "GET", http.StatusNotFound, "")
 	ironic.AddDefaultResponse("/v1/nodes/myhost", "GET", http.StatusNotFound, "")
 	ironic.Start()
 	defer ironic.Stop()
@@ -583,7 +623,7 @@ func TestValidateManagementAccessAddTwoHostsWithSameMAC(t *testing.T) {
 
 	existingNode := nodes.Node{
 		UUID: "33ce8659-7400-4c68-9535-d10766f07a58",
-		Name: "myhost",
+		Name: "myns" + nameSeparator + "myhost",
 	}
 
 	existingNodePort := ports.Port{
@@ -628,7 +668,7 @@ func TestValidateManagementAccessUnsupportedSecureBoot(t *testing.T) {
 	host.Spec.BootMode = metal3v1alpha1.UEFISecureBoot
 	host.Status.Provisioning.ID = "" // so we don't lookup by uuid
 
-	ironic := testserver.NewIronic(t).Ready().NoNode(host.Name)
+	ironic := testserver.NewIronic(t).Ready().NoNode("myns" + nameSeparator + host.Name).NoNode(host.Name)
 	ironic.Start()
 	defer ironic.Stop()