Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 179 additions & 0 deletions pkg/provisioner/ironic/inspecthardware_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package ironic

import (
"net/http"
"testing"
"time"

"github.com/metal3-io/baremetal-operator/pkg/bmc"
"github.com/metal3-io/baremetal-operator/pkg/provisioner/ironic/clients"
"github.com/metal3-io/baremetal-operator/pkg/provisioner/ironic/testserver"

"github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes"
"github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection"
"github.com/stretchr/testify/assert"
)

func TestInspectHardware(t *testing.T) {

nodeUUID := "33ce8659-7400-4c68-9535-d10766f07a58"

cases := []struct {
name string
ironic *testserver.IronicMock
inspector *testserver.InspectorMock

expectedDirty bool
expectedRequestAfter int
expectedResultError string
expectedDetailsHost string

expectedPublish string
expectedError string
}{
{
name: "introspection-status-start-new-hardware-inspection",
ironic: testserver.NewIronic(t).Ready().WithNode(nodes.Node{
UUID: nodeUUID,
ProvisionState: "active",
}).WithNodeStatesProvision(nodeUUID),
inspector: testserver.NewInspector(t).Ready().WithIntrospectionFailed(nodeUUID, http.StatusNotFound),

expectedDirty: true,
expectedRequestAfter: 10,
expectedPublish: "InspectionStarted Hardware inspection started",
},
{
name: "introspection-data-failed",
ironic: testserver.NewIronic(t).Ready().WithNode(nodes.Node{
UUID: nodeUUID,
}),
inspector: testserver.NewInspector(t).Ready().
WithIntrospection(nodeUUID, introspection.Introspection{
Finished: true,
}).
WithIntrospectionDataFailed(nodeUUID, http.StatusBadRequest),

expectedError: "failed to retrieve hardware introspection data: Bad request with: \\[GET http://127.0.0.1:.*/v1/introspection/33ce8659-7400-4c68-9535-d10766f07a58/data\\], error message: An error\\\n",
},
{
name: "introspection-status-failed-404-retry-on-wait",
ironic: testserver.NewIronic(t).Ready().WithNode(nodes.Node{
UUID: nodeUUID,
ProvisionState: "inspect wait",
}),
inspector: testserver.NewInspector(t).Ready().WithIntrospectionFailed(nodeUUID, http.StatusNotFound),

expectedDirty: true,
expectedRequestAfter: 15,
},
{
name: "introspection-status-failed-extraction",
ironic: testserver.NewIronic(t).Ready().WithNode(nodes.Node{
UUID: nodeUUID,
ProvisionState: "inspecting",
}),
inspector: testserver.NewInspector(t).Ready().WithIntrospectionFailed(nodeUUID, http.StatusBadRequest),

expectedError: "failed to extract hardware inspection status: Bad request with: \\[GET http://127.0.0.1:.*/v1/introspection/33ce8659-7400-4c68-9535-d10766f07a58\\], error message: An error\\\n",
},
{
name: "introspection-status-failed-404-retry",
ironic: testserver.NewIronic(t).Ready().WithNode(nodes.Node{
UUID: nodeUUID,
ProvisionState: "inspecting",
}),
inspector: testserver.NewInspector(t).Ready().WithIntrospectionFailed(nodeUUID, http.StatusNotFound),

expectedDirty: true,
expectedRequestAfter: 15,
},
{
name: "introspection-aborted",
ironic: testserver.NewIronic(t).Ready().WithNode(nodes.Node{
UUID: nodeUUID,
}),
inspector: testserver.NewInspector(t).Ready().WithIntrospection(nodeUUID, introspection.Introspection{
Finished: true,
Error: "Canceled by operator",
}),

expectedResultError: "Canceled by operator",
},
{
name: "inspection-in-progress",
ironic: testserver.NewIronic(t).Ready().WithNode(nodes.Node{
UUID: nodeUUID,
}),
inspector: testserver.NewInspector(t).Ready().WithIntrospection(nodeUUID, introspection.Introspection{
Finished: false,
}),
expectedDirty: true,
expectedRequestAfter: 15,
},
{
name: "inspection-completed",
ironic: testserver.NewIronic(t).Ready().WithNode(nodes.Node{
UUID: nodeUUID,
}),
inspector: testserver.NewInspector(t).Ready().
WithIntrospection(nodeUUID, introspection.Introspection{
Finished: true,
}).
WithIntrospectionData(nodeUUID, introspection.Data{
Inventory: introspection.InventoryType{
Hostname: "node-0",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where does the name come from?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just followed the Ironic Inspector API, ie https://docs.openstack.org/ironic-inspector/5.1.0/http-api.html#get-introspection-data, to have roughly a one-to-one relationship

},
}),

expectedDirty: false,
expectedDetailsHost: "node-0",
expectedPublish: "InspectionComplete Hardware inspection completed",
},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if tc.ironic != nil {
tc.ironic.Start()
defer tc.ironic.Stop()
}

if tc.inspector != nil {
tc.inspector.Start()
defer tc.inspector.Stop()
}

host := makeHost()
publishedMsg := ""
publisher := func(reason, message string) {
publishedMsg = reason + " " + message
}
auth := clients.AuthConfig{Type: clients.NoAuth}
prov, err := newProvisionerWithSettings(host, bmc.Credentials{}, publisher,
tc.ironic.Endpoint(), auth, tc.inspector.Endpoint(), auth,
)
if err != nil {
t.Fatalf("could not create provisioner: %s", err)
}

prov.status.ID = nodeUUID
result, details, err := prov.InspectHardware()

assert.Equal(t, tc.expectedDirty, result.Dirty)
assert.Equal(t, time.Second*time.Duration(tc.expectedRequestAfter), result.RequeueAfter)
assert.Equal(t, tc.expectedResultError, result.ErrorMessage)

if details != nil {
assert.Equal(t, tc.expectedDetailsHost, details.Hostname)
}
assert.Equal(t, tc.expectedPublish, publishedMsg)
if tc.expectedError == "" {
assert.NoError(t, err)
} else {
assert.Error(t, err)
assert.Regexp(t, tc.expectedError, err.Error())
}
})
}
}
1 change: 1 addition & 0 deletions pkg/provisioner/ironic/ironic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func makeHost() *metal3v1alpha1.BareMetalHost {
WWNVendorExtension: "userd_vendor_extension",
Rotational: &rotational,
},
BootMode: metal3v1alpha1.UEFI,
},
HardwareProfile: "libvirt",
},
Expand Down
30 changes: 29 additions & 1 deletion pkg/provisioner/ironic/testserver/inspector.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package testserver

import "testing"
import (
"testing"

"github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection"
)

// InspectorMock is a test server that implements Ironic Inspector's semantics
type InspectorMock struct {
Expand Down Expand Up @@ -33,3 +37,27 @@ func (m *InspectorMock) NotReady(errorCode int) *InspectorMock {
m.ErrorResponse("/v1", errorCode)
return m
}

// WithIntrospection configures the server with a valid response for /v1/introspection/<node>
func (m *InspectorMock) WithIntrospection(nodeUUID string, status introspection.Introspection) *InspectorMock {
m.ResponseJSON("/v1/introspection/"+nodeUUID, status)
return m
}

// WithIntrospectionFailed configures the server with an error response for /v1/introspection/<node>
func (m *InspectorMock) WithIntrospectionFailed(nodeUUID string, errorCode int) *InspectorMock {
m.ErrorResponse("/v1/introspection/"+nodeUUID, errorCode)
return m
}

// WithIntrospectionData configures the server with a valid response for /v1/introspection/<node>/data
func (m *InspectorMock) WithIntrospectionData(nodeUUID string, data introspection.Data) *InspectorMock {
m.ResponseJSON("/v1/introspection/"+nodeUUID+"/data", data)
return m
}

// WithIntrospectionDataFailed configures the server with an error response for /v1/introspection/<node>/data
func (m *InspectorMock) WithIntrospectionDataFailed(nodeUUID string, errorCode int) *InspectorMock {
m.ErrorResponse("/v1/introspection/"+nodeUUID+"/data", errorCode)
return m
}
19 changes: 18 additions & 1 deletion pkg/provisioner/ironic/testserver/ironic.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package testserver

import "testing"
import (
"net/http"
"testing"

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

// IronicMock is a test server that implements Ironic's semantics
type IronicMock struct {
Expand Down Expand Up @@ -58,3 +63,15 @@ func (m *IronicMock) WithDrivers() *IronicMock {
`)
return m
}

// WithNode configures the server with a valid response for /v1/nodes
func (m *IronicMock) WithNode(node nodes.Node) *IronicMock {
m.ResponseJSON("/v1/nodes/"+node.UUID, node)
return m
}

// WithNodeStatesProvision configures the server with a valid response for /v1/nodes/<node>/states/provision
func (m *IronicMock) WithNodeStatesProvision(nodeUUID string) *IronicMock {
m.ResponseWithCode("/v1/nodes/"+nodeUUID+"/states/provision", "{}", http.StatusAccepted)
return m
}
19 changes: 19 additions & 0 deletions pkg/provisioner/ironic/testserver/server.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package testserver

import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
Expand Down Expand Up @@ -72,15 +73,33 @@ func (m *MockServer) NotFound(pattern string) *MockServer {
// Response attaches a handler function that returns the given payload
// from requests to the URL pattern
func (m *MockServer) Response(pattern string, payload string) *MockServer {
return m.ResponseWithCode(pattern, payload, http.StatusOK)
}

// ResponseWithCode attaches a handler function that returns the given payload
// from requests to the URL pattern along with the specified code
func (m *MockServer) ResponseWithCode(pattern string, payload string, code int) *MockServer {
m.t.Logf("%s: adding response handler for %s", m.name, pattern)
m.mux.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
m.logRequest(r, payload)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
fmt.Fprint(w, payload)
})
return m
}

// ResponseJSON marshals the JSON object as payload returned by the response
// handler
func (m *MockServer) ResponseJSON(pattern string, payload interface{}) *MockServer {
Comment thread
andfasano marked this conversation as resolved.
content, err := json.Marshal(payload)
if err != nil {
m.t.Error(err)
}
m.Response(pattern, string(content))
return m
}

// ErrorResponse attaches a handler function that returns the given
// error code from requests to the URL pattern
func (m *MockServer) ErrorResponse(pattern string, errorCode int) *MockServer {
Expand Down