blob: 3769d2da0dc245eb40a52f15d3c6eabc84ba58b6 [file] [log] [blame]
David K. Bainbridgedf9df632016-07-07 18:47:46 -07001// Copyright 2016 Open Networking Laboratory
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
David K. Bainbridgeb5415042016-05-13 17:06:10 -070014package main
15
16import (
David K. Bainbridgeefa951d2016-05-26 10:54:25 -070017 "encoding/json"
David K. Bainbridgeb5415042016-05-13 17:06:10 -070018 "fmt"
David K. Bainbridgeb5415042016-05-13 17:06:10 -070019 "net/url"
David K. Bainbridgeefa951d2016-05-26 10:54:25 -070020 "os/exec"
David K. Bainbridgeb5415042016-05-13 17:06:10 -070021 "regexp"
22 "strconv"
23 "strings"
David K. Bainbridgeefa951d2016-05-26 10:54:25 -070024 "time"
David K. Bainbridgeb5415042016-05-13 17:06:10 -070025
26 maas "github.com/juju/gomaasapi"
27)
28
29// Action how to get from there to here
30type Action func(*maas.MAASObject, MaasNode, ProcessingOptions) error
31
32// Transition the map from where i want to be from where i might be
33type Transition struct {
34 Target string
35 Current string
36 Using Action
37}
38
David K. Bainbridge6ea57c12016-06-06 23:29:12 -070039type Power struct {
40 Name string `json:"name"`
41 MacAddress string `json:"mac_address"`
42 PowerPassword string `json:"power_password"`
43 PowerAddress string `json:"power_address"`
44}
45
David K. Bainbridgee9d7af72016-10-14 08:42:55 -070046type HostFilter struct {
47 Zones struct {
48 Include []string `json:"include,omitempty"`
49 Exclude []string `json:"exclude,omitempty"`
50 } `json:"zones,omitempty"`
51 Hosts struct {
52 Include []string `json:"include,omitempty"`
53 Exclude []string `json:"exclude,omitempty"`
54 } `json:"hosts,omitempty"`
55}
56
David K. Bainbridgeb5415042016-05-13 17:06:10 -070057// ProcessingOptions used to determine on what hosts to operate
58type ProcessingOptions struct {
David K. Bainbridgee9d7af72016-10-14 08:42:55 -070059 Filter HostFilter
60 Mappings map[string]string
David K. Bainbridge6ea57c12016-06-06 23:29:12 -070061 Preview bool
62 AlwaysRename bool
David K. Bainbridge068e87d2016-06-30 13:53:19 -070063 Provisioner Provisioner
David K. Bainbridge6ea57c12016-06-06 23:29:12 -070064 ProvisionURL string
65 ProvisionTTL time.Duration
66 PowerHelper string
67 PowerHelperUser string
68 PowerHelperHost string
David K. Bainbridgeb5415042016-05-13 17:06:10 -070069}
70
71// Transitions the actual map
72//
73// Currently this is a hand compiled / optimized "next step" table. This should
74// really be generated from the state machine chart input. Once this has been
75// accomplished you should be able to determine the action to take given your
76// target state and your current state.
David K. Bainbridgeefa951d2016-05-26 10:54:25 -070077var Transitions = map[string]map[string][]Action{
David K. Bainbridgeb5415042016-05-13 17:06:10 -070078 "Deployed": {
David K. Bainbridgeefa951d2016-05-26 10:54:25 -070079 "New": []Action{Reset, Commission},
80 "Deployed": []Action{Provision, Done},
81 "Ready": []Action{Reset, Aquire},
82 "Allocated": []Action{Reset, Deploy},
83 "Retired": []Action{Reset, AdminState},
84 "Reserved": []Action{Reset, AdminState},
85 "Releasing": []Action{Reset, Wait},
86 "DiskErasing": []Action{Reset, Wait},
87 "Deploying": []Action{Reset, Wait},
88 "Commissioning": []Action{Reset, Wait},
89 "Missing": []Action{Reset, Fail},
90 "FailedReleasing": []Action{Reset, Fail},
91 "FailedDiskErasing": []Action{Reset, Fail},
92 "FailedDeployment": []Action{Reset, Fail},
93 "Broken": []Action{Reset, Fail},
94 "FailedCommissioning": []Action{Reset, Fail},
David K. Bainbridgeb5415042016-05-13 17:06:10 -070095 },
96}
97
98const (
99 // defaultStateMachine Would be nice to drive from a graph language
100 defaultStateMachine string = `
David K. Bainbridged9b966f2016-05-31 13:30:05 -0700101 (New)->(Commissioning)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700102 (Commissioning)->(FailedCommissioning)
103 (FailedCommissioning)->(New)
104 (Commissioning)->(Ready)
105 (Ready)->(Deploying)
106 (Ready)->(Allocated)
107 (Allocated)->(Deploying)
108 (Deploying)->(Deployed)
109 (Deploying)->(FailedDeployment)
110 (FailedDeployment)->(Broken)
111 (Deployed)->(Releasing)
112 (Releasing)->(FailedReleasing)
113 (FailedReleasing)->(Broken)
114 (Releasing)->(DiskErasing)
115 (DiskErasing)->(FailedEraseDisk)
116 (FailedEraseDisk)->(Broken)
117 (Releasing)->(Ready)
118 (DiskErasing)->(Ready)
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700119 (Broken)->(Ready)
David K. Bainbridged9b966f2016-05-31 13:30:05 -0700120 (Deployed)->(Provisioning)
121 (Provisioning)->|a|
122 |a|->(Execute Script)->|b|
123 |a|->(HTTP PUT)
124 (HTTP PUT)->(HTTP GET)
125 (HTTP GET)->(HTTP GET)
126 (HTTP GET)->|b|
127 |b|->(Provisioned)
128 |b|->(ProvisionError)
129 (ProvisionError)->(Provisioning)`
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700130)
131
132// updateName - changes the name of the MAAS node based on the configuration file
133func updateNodeName(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
134 macs := node.MACs()
135
136 // Get current node name and strip off domain name
137 current := node.Hostname()
138 if i := strings.IndexRune(current, '.'); i != -1 {
139 current = current[:i]
140 }
141 for _, mac := range macs {
David K. Bainbridgee9d7af72016-10-14 08:42:55 -0700142 if name, ok := options.Mappings[mac]; ok {
143 if current != name {
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700144 nodesObj := client.GetSubObject("nodes")
145 nodeObj := nodesObj.GetSubObject(node.ID())
David K. Bainbridgee9d7af72016-10-14 08:42:55 -0700146 log.Infof("RENAME '%s' to '%s'\n", node.Hostname(), name)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700147
148 if !options.Preview {
David K. Bainbridgee9d7af72016-10-14 08:42:55 -0700149 nodeObj.Update(url.Values{"hostname": []string{name}})
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700150 }
151 }
152 }
153 }
154 return nil
155}
156
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700157// Reset we are at the target state, nothing to do
158var Reset = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700159 log.Debugf("RESET: %s", node.Hostname())
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700160
161 if options.AlwaysRename {
162 updateNodeName(client, node, options)
163 }
164
David K. Bainbridge84918ec2016-07-08 09:12:35 -0700165 err := options.Provisioner.Clear(node.ID())
166 if err != nil {
167 log.Errorf("Attempting to clear provisioning state of node '%s' : %s", node.ID(), err)
168 }
169 return err
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700170}
171
172// Provision we are at the target state, nothing to do
173var Provision = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700174 log.Debugf("CHECK PROVISION: %s", node.Hostname())
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700175
176 if options.AlwaysRename {
177 updateNodeName(client, node, options)
178 }
179
David K. Bainbridge068e87d2016-06-30 13:53:19 -0700180 record, err := options.Provisioner.Get(node.ID())
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700181 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700182 log.Warningf("unable to retrieve provisioning state of node '%s' : %s", node.Hostname(), err)
David K. Bainbridge068e87d2016-06-30 13:53:19 -0700183 } else if record == nil || record.Status == Failed {
184 var label string
185 if record == nil {
186 label = "NotFound"
187 } else {
188 label = record.Status.String()
189 }
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700190 log.Debugf("Current state of node '%s' is '%s'", node.Hostname(), label)
David K. Bainbridge068e87d2016-06-30 13:53:19 -0700191 ips := node.IPs()
192 ip := ""
193 if len(ips) > 0 {
194 ip = ips[0]
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700195 }
David K. Bainbridge068e87d2016-06-30 13:53:19 -0700196 macs := node.MACs()
197 mac := ""
198 if len(macs) > 0 {
199 mac = macs[0]
200 }
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700201 log.Debugf("POSTing '%s' (%s) to '%s'", node.Hostname(), node.ID(), options.ProvisionURL)
David K. Bainbridge068e87d2016-06-30 13:53:19 -0700202 err = options.Provisioner.Provision(&ProvisionRequest{
203 Id: node.ID(),
204 Name: node.Hostname(),
205 Ip: ip,
206 Mac: mac,
207 })
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700208
209 if err != nil {
David K. Bainbridge11850cb2016-10-28 14:05:59 -0700210 log.Errorf("unable to provision '%s' (%s) : %s", node.ID(), node.Hostname(), err)
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700211 }
David K. Bainbridge068e87d2016-06-30 13:53:19 -0700212
213 } else if options.ProvisionTTL > 0 &&
214 record.Status == Running && time.Since(time.Unix(record.Timestamp, 0)) > options.ProvisionTTL {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700215 log.Errorf("Provisioning of node '%s' has passed provisioning TTL of '%v'",
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700216 node.Hostname(), options.ProvisionTTL)
David K. Bainbridge068e87d2016-06-30 13:53:19 -0700217 options.Provisioner.Clear(node.ID())
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700218 } else {
219 log.Debugf("Not invoking provisioning for '%s', current state is '%s'", node.Hostname(),
David K. Bainbridge068e87d2016-06-30 13:53:19 -0700220 record.Status.String())
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700221 }
222
223 return nil
224}
225
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700226// Done we are at the target state, nothing to do
227var Done = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
228 // As devices are normally in the "COMPLETED" state we don't want to
229 // log this fact unless we are in verbose mode. I suspect it would be
230 // nice to log it once when the device transitions from a non COMPLETE
231 // state to a complete state, but that would require keeping state.
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700232 log.Debugf("COMPLETE: %s", node.Hostname())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700233
234 if options.AlwaysRename {
235 updateNodeName(client, node, options)
236 }
237
238 return nil
239}
240
241// Deploy cause a node to deploy
242var Deploy = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700243 log.Infof("DEPLOY: %s", node.Hostname())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700244
245 if options.AlwaysRename {
246 updateNodeName(client, node, options)
247 }
248
249 if !options.Preview {
250 nodesObj := client.GetSubObject("nodes")
251 myNode := nodesObj.GetSubObject(node.ID())
252 // Start the node with the trusty distro. This should really be looked up or
253 // a parameter default
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700254 _, err := myNode.CallPost("start", url.Values{"distro_series": []string{"trusty"}})
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700255 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700256 log.Errorf("DEPLOY '%s' : '%s'", node.Hostname(), err)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700257 return err
258 }
259 }
260 return nil
261}
262
263// Aquire aquire a machine to a specific operator
264var Aquire = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700265 log.Infof("AQUIRE: %s", node.Hostname())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700266 nodesObj := client.GetSubObject("nodes")
267
268 if options.AlwaysRename {
269 updateNodeName(client, node, options)
270 }
271
272 if !options.Preview {
273 // With a new version of MAAS we have to make sure the node is linked
274 // to the subnet vid DHCP before we move to the Aquire state. To do this
275 // We need to unlink the interface to the subnet and then relink it.
276 //
277 // Iterate through all the interfaces on the node, searching for ones
278 // that are valid and not DHCP and move them to DHCP
279 ifcsObj := client.GetSubObject("nodes").GetSubObject(node.ID()).GetSubObject("interfaces")
280 ifcsListObj, err := ifcsObj.CallGet("", url.Values{})
281 if err != nil {
282 return err
283 }
284
285 ifcsArray, err := ifcsListObj.GetArray()
286 if err != nil {
287 return err
288 }
289
290 for _, ifc := range ifcsArray {
291 ifcMap, err := ifc.GetMap()
292 if err != nil {
293 return err
294 }
295
296 // Iterate over the links assocated with the interface, looking for
297 // links with a subnect as well as a mode of "auto"
298 links, ok := ifcMap["links"]
299 if ok {
300 linkArray, err := links.GetArray()
301 if err != nil {
302 return err
303 }
304
305 for _, link := range linkArray {
306 linkMap, err := link.GetMap()
307 if err != nil {
308 return err
309 }
310 subnet, ok := linkMap["subnet"]
311 if ok {
312 subnetMap, err := subnet.GetMap()
313 if err != nil {
314 return err
315 }
316
317 val, err := linkMap["mode"].GetString()
318 if err != nil {
319 return err
320 }
321
322 if val == "auto" {
323 // Found one we like, so grab the subnet from the data and
324 // then relink this as DHCP
325 cidr, err := subnetMap["cidr"].GetString()
326 if err != nil {
327 return err
328 }
329
330 fifcID, err := ifcMap["id"].GetFloat64()
331 if err != nil {
332 return err
333 }
334 ifcID := strconv.Itoa(int(fifcID))
335
336 flID, err := linkMap["id"].GetFloat64()
337 if err != nil {
338 return err
339 }
340 lID := strconv.Itoa(int(flID))
341
342 ifcObj := ifcsObj.GetSubObject(ifcID)
343 _, err = ifcObj.CallPost("unlink_subnet", url.Values{"id": []string{lID}})
344 if err != nil {
345 return err
346 }
347 _, err = ifcObj.CallPost("link_subnet", url.Values{"mode": []string{"DHCP"}, "subnet": []string{cidr}})
348 if err != nil {
349 return err
350 }
351 }
352 }
353 }
354 }
355 }
356 _, err = nodesObj.CallPost("acquire",
357 url.Values{"name": []string{node.Hostname()}})
358 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700359 log.Errorf("AQUIRE '%s' : '%s'", node.Hostname(), err)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700360 return err
361 }
362 }
363 return nil
364}
365
366// Commission cause a node to be commissioned
367var Commission = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
368 updateNodeName(client, node, options)
369
370 // Need to understand the power state of the node. We only want to move to "Commissioning" if the node
371 // power is off. If the node power is not off, then turn it off.
372 state := node.PowerState()
373 switch state {
374 case "on":
375 // Attempt to turn the node off
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700376 log.Infof("POWER DOWN: %s", node.Hostname())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700377 if !options.Preview {
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700378 //POST /api/1.0/nodes/{system_id}/ op=stop
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700379 nodesObj := client.GetSubObject("nodes")
380 nodeObj := nodesObj.GetSubObject(node.ID())
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700381 _, err := nodeObj.CallPost("stop", url.Values{"stop_mode": []string{"soft"}})
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700382 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700383 log.Errorf("Commission '%s' : changing power start to off : '%s'", node.Hostname(), err)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700384 }
385 return err
386 }
387 break
388 case "off":
389 // We are off so move to commissioning
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700390 log.Infof("COMISSION: %s", node.Hostname())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700391 if !options.Preview {
392 nodesObj := client.GetSubObject("nodes")
393 nodeObj := nodesObj.GetSubObject(node.ID())
394
395 updateNodeName(client, node, options)
396
397 _, err := nodeObj.CallPost("commission", url.Values{})
398 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700399 log.Errorf("Commission '%s' : '%s'", node.Hostname(), err)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700400 }
401 return err
402 }
403 break
404 default:
405 // We are in a state from which we can't move forward.
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700406 log.Warningf("%s has invalid power state '%s'", node.Hostname(), state)
David K. Bainbridge6ea57c12016-06-06 23:29:12 -0700407
408 // If a power helper script is set, we have an unknown power state, and
409 // we have not power type then attempt to use the helper script to discover
410 // and set the power settings
411 if options.PowerHelper != "" && node.PowerType() == "" {
412 cmd := exec.Command(options.PowerHelper,
413 append([]string{options.PowerHelperUser, options.PowerHelperHost},
414 node.MACs()...)...)
415 stdout, err := cmd.Output()
416 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700417 log.Errorf("Failed while executing power helper script '%s' : %s",
David K. Bainbridge6ea57c12016-06-06 23:29:12 -0700418 options.PowerHelper, err)
419 return err
420 }
421 power := Power{}
422 err = json.Unmarshal(stdout, &power)
423 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700424 log.Errorf("Failed to parse output of power helper script '%s' : %s",
David K. Bainbridge6ea57c12016-06-06 23:29:12 -0700425 options.PowerHelper, err)
426 return err
427 }
428 switch power.Name {
429 case "amt":
430 params := map[string]string{
431 "mac_address": power.MacAddress,
432 "power_pass": power.PowerPassword,
433 "power_address": power.PowerAddress,
434 }
435 node.UpdatePowerParameters(power.Name, params)
436 default:
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700437 log.Warningf("Unsupported power type discovered '%s'", power.Name)
David K. Bainbridge6ea57c12016-06-06 23:29:12 -0700438 }
439 }
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700440 break
441 }
442 return nil
443}
444
445// Wait a do nothing state, while work is being done
446var Wait = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700447 log.Infof("WAIT: %s", node.Hostname())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700448 return nil
449}
450
451// Fail a state from which we cannot, currently, automatically recover
452var Fail = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700453 log.Infof("FAIL: %s", node.Hostname())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700454 return nil
455}
456
457// AdminState an administrative state from which we should make no automatic transition
458var AdminState = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700459 log.Infof("ADMIN: %s", node.Hostname())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700460 return nil
461}
462
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700463func findActions(target string, current string) ([]Action, error) {
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700464 targets, ok := Transitions[target]
465 if !ok {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700466 log.Warningf("unable to find transitions to target state '%s'", target)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700467 return nil, fmt.Errorf("Could not find transition to target state '%s'", target)
468 }
469
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700470 actions, ok := targets[current]
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700471 if !ok {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700472 log.Warningf("unable to find transition from current state '%s' to target state '%s'",
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700473 current, target)
474 return nil, fmt.Errorf("Could not find transition from current state '%s' to target state '%s'",
475 current, target)
476 }
477
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700478 return actions, nil
479}
480
481// ProcessActions
482func ProcessActions(actions []Action, client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
483 var err error
484 for _, action := range actions {
485 if err = action(client, node, options); err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700486 log.Errorf("Error while processing action for node '%s' : %s",
David K. Bainbridge6ea57c12016-06-06 23:29:12 -0700487 node.Hostname(), err)
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700488 break
489 }
490 }
491 return err
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700492}
493
494// ProcessNode something
495func ProcessNode(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
496 substatus, err := node.GetInteger("substatus")
497 if err != nil {
498 return err
499 }
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700500 actions, err := findActions("Deployed", MaasNodeStatus(substatus).String())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700501 if err != nil {
502 return err
503 }
504
505 if options.Preview {
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700506 ProcessActions(actions, client, node, options)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700507 } else {
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700508 go ProcessActions(actions, client, node, options)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700509 }
510 return nil
511}
512
513func buildFilter(filter []string) ([]*regexp.Regexp, error) {
514
515 results := make([]*regexp.Regexp, len(filter))
516 for i, v := range filter {
517 r, err := regexp.Compile(v)
518 if err != nil {
519 return nil, err
520 }
521 results[i] = r
522 }
523 return results, nil
524}
525
526func matchedFilter(include []*regexp.Regexp, target string) bool {
527 for _, e := range include {
528 if e.MatchString(target) {
529 return true
530 }
531 }
532 return false
533}
534
535// ProcessAll something
536func ProcessAll(client *maas.MAASObject, nodes []MaasNode, options ProcessingOptions) []error {
537 errors := make([]error, len(nodes))
538 includeHosts, err := buildFilter(options.Filter.Hosts.Include)
539 if err != nil {
540 log.Fatalf("[error] invalid regular expression for include filter '%s' : %s", options.Filter.Hosts.Include, err)
541 }
542
543 includeZones, err := buildFilter(options.Filter.Zones.Include)
544 if err != nil {
545 log.Fatalf("[error] invalid regular expression for include filter '%v' : %s", options.Filter.Zones.Include, err)
546 }
547
548 for i, node := range nodes {
549 // For hostnames we always match on an empty filter
550 if len(includeHosts) >= 0 && matchedFilter(includeHosts, node.Hostname()) {
551
552 // For zones we don't match on an empty filter
553 if len(includeZones) >= 0 && matchedFilter(includeZones, node.Zone()) {
554 err := ProcessNode(client, node, options)
555 if err != nil {
556 errors[i] = err
557 } else {
558 errors[i] = nil
559 }
560 } else {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700561 log.Debugf("ignoring node '%s' as its zone '%s' didn't match include zone name filter '%v'",
562 node.Hostname(), node.Zone(), options.Filter.Zones.Include)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700563 }
564 } else {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700565 log.Debugf("ignoring node '%s' as it didn't match include hostname filter '%v'",
566 node.Hostname(), options.Filter.Hosts.Include)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700567 }
568 }
569 return errors
570}