forked from metal3-io/baremetal-operator
/
ironic.go
256 lines (214 loc) · 8.01 KB
/
ironic.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
package testserver
import (
"encoding/json"
"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
type IronicMock struct {
*MockServer
CreatedNodes int
}
// NewIronic builds an ironic mock server
func NewIronic(t *testing.T) *IronicMock {
return &IronicMock{
MockServer: New(t, "ironic"),
CreatedNodes: 0,
}
}
// WithDefaultResponses sets a valid answer for all the API calls
func (m *IronicMock) WithDefaultResponses() *IronicMock {
m.AddDefaultResponseJSON("/v1/nodes/{id}", "", http.StatusOK, nodes.Node{
UUID: "{id}",
})
m.AddDefaultResponse("/v1/nodes/{id}/states/provision", "", http.StatusAccepted, "{}")
m.AddDefaultResponse("/v1/nodes/{id}/states/power", "", http.StatusAccepted, "{}")
m.AddDefaultResponse("/v1/nodes/{id}/validate", "", http.StatusOK, "{}")
m.Ready()
return m
}
// Endpoint returns the URL for accessing the server
func (m *IronicMock) Endpoint() string {
if m == nil {
return "https://ironic.test/v1/"
}
return m.MockServer.Endpoint()
}
// Ready configures the server with a valid response for /v1
func (m *IronicMock) Ready() *IronicMock {
m.ResponseWithCode("/v1", "{}", http.StatusOK)
return m
}
// NotReady configures the server with an error response for /v1
func (m *IronicMock) NotReady(errorCode int) *IronicMock {
m.ErrorResponse("/v1", errorCode)
return m
}
// WithDrivers configures the server so /v1/drivers returns a valid value
func (m *IronicMock) WithDrivers() *IronicMock {
m.ResponseWithCode("/v1/drivers", `
{
"drivers": [{
"hosts": [
"master-2.ostest.test.metalkube.org"
],
"links": [
{
"href": "http://[fd00:1101::3]:6385/v1/drivers/fake-hardware",
"rel": "self"
},
{
"href": "http://[fd00:1101::3]:6385/drivers/fake-hardware",
"rel": "bookmark"
}
],
"name": "fake-hardware"
}]
}
`, http.StatusOK)
return m
}
func (m *IronicMock) buildURL(url string, method string) string {
return fmt.Sprintf("%s:%s", url, method)
}
// Delete configures the server with a valid response for [DELETE] /v1/nodes/ on the
// specific node id
func (m *IronicMock) Delete(id string) *IronicMock {
m.ResponseWithCode(m.buildURL("/v1/nodes/"+id, http.MethodDelete), "", http.StatusAccepted)
return m
}
// DeleteError configures the server with an error response for [DELETE] /v1/nodes/
func (m *IronicMock) DeleteError(id string, errorCode int) *IronicMock {
m.ResponseWithCode(m.buildURL("/v1/nodes/"+id, http.MethodDelete), "", errorCode)
return m
}
// Node configures the server with a valid response for /v1/nodes/{name,uuid}
func (m *IronicMock) Node(node nodes.Node) *IronicMock {
if node.UUID != "" {
m.ResponseJSON(m.buildURL("/v1/nodes/"+node.UUID, http.MethodGet), node)
}
if node.Name != "" {
m.ResponseJSON(m.buildURL("/v1/nodes/"+node.Name, http.MethodGet), node)
}
return m
}
// NodeUpdateError configures configures the server with an error response for [PATCH] /v1/nodes/{id}
func (m *IronicMock) NodeUpdateError(id string, errorCode int) *IronicMock {
m.ResponseWithCode(m.buildURL("/v1/nodes/"+id, http.MethodPatch), "", errorCode)
return m
}
// NodeUpdate configures the server with a valid response for PATCH
// for /v1/nodes/{name,uuid}
func (m *IronicMock) NodeUpdate(node nodes.Node) *IronicMock {
if node.UUID != "" {
m.ResponseJSON(m.buildURL("/v1/nodes/"+node.UUID, http.MethodPatch), node)
}
if node.Name != "" {
m.ResponseJSON(m.buildURL("/v1/nodes/"+node.Name, http.MethodPatch), node)
}
return m
}
//GetLastNodeUpdateRequestFor returns the content of the last update request for the specified node
func (m *IronicMock) GetLastNodeUpdateRequestFor(id string) (updates []nodes.UpdateOperation) {
if bodyRaw, ok := m.GetLastRequestFor("/v1/nodes/"+id, http.MethodPatch); ok {
json.Unmarshal([]byte(bodyRaw), &updates)
}
return
}
func (m *IronicMock) withNodeStatesProvision(nodeUUID string, method string) *IronicMock {
m.ResponseWithCode(m.buildURL("/v1/nodes/"+nodeUUID+"/states/provision", method), "{}", http.StatusAccepted)
return m
}
// WithNodeStatesProvision configures the server with a valid response for [GET] /v1/nodes/<node>/states/provision
func (m *IronicMock) WithNodeStatesProvision(nodeUUID string) *IronicMock {
return m.withNodeStatesProvision(nodeUUID, http.MethodGet)
}
// WithNodeStatesProvision configures the server with a valid response for [PATCH] /v1/nodes/<node>/states/provision
func (m *IronicMock) WithNodeStatesProvisionUpdate(nodeUUID string) *IronicMock {
return m.withNodeStatesProvision(nodeUUID, http.MethodPut)
}
// NoNode configures the server so /v1/nodes/name returns a 404
func (m *IronicMock) NoNode(name string) *IronicMock {
return m.NodeError(name, http.StatusNotFound)
}
// NodeError configures the server to return the specified error code for /v1/nodes/name
func (m *IronicMock) NodeError(name string, errorCode int) *IronicMock {
m.ErrorResponse(fmt.Sprintf("/v1/nodes/%s", name), errorCode)
return m
}
type NodeCreateCallback func(node nodes.Node)
// CreateNodes configures the server so POSTing to /v1/nodes saves the data
func (m *IronicMock) CreateNodes(callback NodeCreateCallback) *IronicMock {
m.Handler("/v1/nodes", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, fmt.Sprintf("%s not handled for %s", r.Method, r.URL),
http.StatusNotImplemented)
}
bodyRaw, err := ioutil.ReadAll(r.Body)
if err != nil {
m.logRequest(r, fmt.Sprintf("ERROR: %s", err))
http.Error(w, fmt.Sprintf("%s", err), http.StatusInternalServerError)
return
}
body := string(bodyRaw)
m.t.Logf("%s: create nodes request %v", m.name, body)
// Unpack the input so we can update it
node := nodes.Node{}
err = json.Unmarshal(bodyRaw, &node)
if err != nil {
m.logRequest(r, fmt.Sprintf("ERROR: %s", err))
http.Error(w, fmt.Sprintf("%s", err), http.StatusInternalServerError)
return
}
// The UUID value doesn't actually have to be a UUID, so we
// just make a new string based on the count of nodes already
// created.
node.UUID = fmt.Sprintf("node-%d", m.CreatedNodes)
m.t.Logf("%s: uuid %s", m.name, node.UUID)
m.CreatedNodes++
// Pass the data to the test via the callback
callback(node)
// Handle the response to this request
m.SendJSONResponse(node, http.StatusCreated, w, r)
})
return m
}
func (m *IronicMock) withNodeStatesPower(nodeUUID string, code int, method string) *IronicMock {
m.ResponseWithCode(m.buildURL("/v1/nodes/"+nodeUUID+"/states/power", method), "{}", code)
return m
}
// WithNodeStatesPower configures the server with a valid response for [GET] /v1/nodes/<node>/states/power
func (m *IronicMock) WithNodeStatesPower(nodeUUID string, code int) *IronicMock {
return m.withNodeStatesPower(nodeUUID, code, http.MethodGet)
}
// WithNodeStatesPowerUpdate configures the server with a valid response for [PUT] /v1/nodes/<node>/states/power
func (m *IronicMock) WithNodeStatesPowerUpdate(nodeUUID string, code int) *IronicMock {
return m.withNodeStatesPower(nodeUUID, code, http.MethodPut)
}
// WithNodeValidate configures the server with a valid response for /v1/nodes/<node>/validate
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
}