Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge upstream 2020-12-04 #114

Merged
merged 9 commits into from Dec 5, 2020
4 changes: 4 additions & 0 deletions Makefile
Expand Up @@ -104,6 +104,10 @@ lint: $(GOLANGCI_LINT)
$(GOLANGCI_LINT):
./hack/install-golangci-lint.sh

.PHONY: manifest-lint
manifest-lint: ## Run manifest validation
./hack/manifestlint.sh

## --------------------------------------
## Build/Run Targets
## --------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion docs/testing.md
Expand Up @@ -18,7 +18,7 @@ The tests can be run via `make`
make test
```

Run linters test before pushing your commit
Run linters test before pushing your commit.

```bash
make lint
Expand Down
3 changes: 3 additions & 0 deletions hack/manifestlint.sh
Expand Up @@ -20,8 +20,11 @@ CONTAINER_RUNTIME="${CONTAINER_RUNTIME:-podman}"
# matches our regexp pattern (i.e. kustom, patch).

if [ "${IS_CONTAINER}" != "false" ]; then
{ set +x; } 2>/dev/null
echo "<-------------------------STARTING MANIFESTS VALIDATION CHECKS------------------------->"
kubeval --strict --ignore-missing-schemas \
-d config,examples -i kustom,patch -o tap
echo "<-------------------------COMPLETED MANIFESTS VALIDATION CHECKS------------------------>"
else
"${CONTAINER_RUNTIME}" run --rm \
--env IS_CONTAINER=TRUE \
Expand Down
100 changes: 94 additions & 6 deletions pkg/provisioner/ironic/ironic.go
Expand Up @@ -230,6 +230,31 @@ func (p *ironicProvisioner) validateNode(ironicNode *nodes.Node) (errorMessage s
return "", nil
}

func (p *ironicProvisioner) listAllPorts(address string) ([]ports.Port, error) {
var allPorts []ports.Port

opts := ports.ListOpts{}

if address != "" {
opts.Address = address
}

pager := ports.List(p.client, opts)

if pager.Err != nil {
return allPorts, pager.Err
}

allPages, err := pager.AllPages()

if err != nil {
return allPorts, err
}

return ports.ExtractPorts(allPages)

}

// Look for an existing registration for the host in Ironic.
func (p *ironicProvisioner) findExistingHost() (ironicNode *nodes.Node, err error) {
// Try to load the node by UUID
Expand Down Expand Up @@ -258,11 +283,48 @@ func (p *ironicProvisioner) findExistingHost() (ironicNode *nodes.Node, err erro
p.log.Info("found existing node by name")
return ironicNode, nil
case gophercloud.ErrDefault404:
return nil, nil
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))
}

// Try to load the node by port address
p.log.Info("looking for existing node by MAC", "MAC", p.host.Spec.BootMACAddress)
allPorts, err := p.listAllPorts(p.host.Spec.BootMACAddress)

if err != nil {
p.log.Info("failed to find an existing port with address", "MAC", p.host.Spec.BootMACAddress)
return nil, nil
}

if len(allPorts) > 0 {
nodeUUID := allPorts[0].NodeUUID
ironicNode, err = nodes.Get(p.client, nodeUUID).Extract()
switch err.(type) {
case nil:
p.log.Info("found existing node by ID")

// If the node has a name, this means we didn't find it above.
if ironicNode.Name != "" {
return nil, errors.New(fmt.Sprint("node found by MAC but has a name: ", ironicNode.Name))
}

return ironicNode, nil
case gophercloud.ErrDefault404:
return nil, errors.Wrap(err,
fmt.Sprintf("port exists but linked node doesn't %s", nodeUUID))
default:
return nil, errors.Wrap(err,
fmt.Sprintf("port exists but failed to find linked node by ID %s", nodeUUID))
}
} else {
p.log.Info("port with address doesn't exist", "MAC", p.host.Spec.BootMACAddress)
}

return nil, nil

}

// ValidateManagementAccess registers the host with the provisioning
Expand Down Expand Up @@ -442,6 +504,29 @@ func (p *ironicProvisioner) ValidateManagementAccess(credentialsChanged bool) (r
p.log.Info("setting provisioning id", "ID", p.status.ID)
}

if ironicNode.Name == "" {
updates := nodes.UpdateOpts{
nodes.UpdateOperation{
Op: nodes.ReplaceOp,
Path: "/name",
Value: p.host.Name,
},
}
ironicNode, err = nodes.Update(p.client, ironicNode.UUID, updates).Extract()
switch err.(type) {
case nil:
case gophercloud.ErrDefault409:
p.log.Info("could not update ironic node name, busy")
result.Dirty = true
result.RequeueAfter = provisionRequeueDelay
return result, nil
default:
return result, errors.Wrap(err, "failed to update ironc node name")
}
p.log.Info("updated ironic node name")

}

// Look for the case where we previously enrolled this node
// and now the credentials have changed.
if credentialsChanged {
Expand Down Expand Up @@ -505,6 +590,14 @@ func (p *ironicProvisioner) ValidateManagementAccess(credentialsChanged bool) (r
nodes.ProvisionStateOpts{Target: nodes.TargetManage},
)

case nodes.Verifying:
// If we're still waiting for the state to change in Ironic,
// return true to indicate that we're dirty and need to be
// reconciled again.
result.RequeueAfter = provisionRequeueDelay
result.Dirty = true
return result, nil

case nodes.Manageable:
p.log.Info("have manageable host")
return result, nil
Expand All @@ -521,11 +614,6 @@ func (p *ironicProvisioner) ValidateManagementAccess(credentialsChanged bool) (r
return result, nil

default:
// If we're still waiting for the state to change in Ironic,
// return true to indicate that we're dirty and need to be
// reconciled again.
result.RequeueAfter = provisionRequeueDelay
result.Dirty = true
return result, nil
}
}
Expand Down
24 changes: 24 additions & 0 deletions pkg/provisioner/ironic/testserver/ironic.go
Expand Up @@ -5,9 +5,11 @@ import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"testing"

"github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes"
"github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports"
)

// IronicMock is a test server that implements Ironic's semantics
Expand Down Expand Up @@ -230,3 +232,25 @@ func (m *IronicMock) WithNodeValidate(nodeUUID string) *IronicMock {
m.ResponseWithCode("/v1/nodes/"+nodeUUID+"/validate", "{}", http.StatusOK)
return m
}

// Port configures the server with a valid response for
// [GET] /v1/nodes/<node uuid>/ports
// [GET] /v1/ports
// [GET] /v1/ports?=address=<node uuid>
func (m *IronicMock) Port(port ports.Port) *IronicMock {
if port.NodeUUID == "" {
m.MockServer.t.Error("When using withPort(), the port must include a NodeUUID.")
}

resp := map[string][]ports.Port{
"ports": {port},
}

address := url.QueryEscape(port.Address)

m.ResponseJSON(m.buildURL("/v1/nodes/"+port.NodeUUID+"/ports", http.MethodGet), resp)
m.ResponseJSON(m.buildURL("/v1/ports", http.MethodGet), resp)
m.ResponseJSON(m.buildURL("/v1/ports?address="+address, http.MethodGet), resp)

return m
}