Skip to content

Commit

Permalink
Merge pull request #649 from andfasano/unit-test-inspect-hardware
Browse files Browse the repository at this point in the history
start adding unit tests for Ironic.InspectHardware
  • Loading branch information
metal3-io-bot authored Oct 9, 2020
2 parents 525005d + 2f52643 commit 4f5bdfe
Show file tree
Hide file tree
Showing 5 changed files with 246 additions and 2 deletions.
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",
},
}),

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 {
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

0 comments on commit 4f5bdfe

Please sign in to comment.