blob: 5a16571532774bb75291ea60004ec6d93f0a8aa2 [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. Bainbridge7b1f18c2016-11-17 09:27:07 -0800101 (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)
David K. Bainbridge7b1f18c2016-11-17 09:27:07 -0800121 (Provisioning)->(HTTP PUT)
122 (HTTP PUT)->(HTTP GET)
123 (HTTP GET)->(HTTP GET)
124 (HTTP GET)->|b|
125 |b|->(Provisioned)
126 |b|->(ProvisionError)
David K. Bainbridged9b966f2016-05-31 13:30:05 -0700127 (ProvisionError)->(Provisioning)`
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700128)
129
130// updateName - changes the name of the MAAS node based on the configuration file
131func updateNodeName(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
132 macs := node.MACs()
133
134 // Get current node name and strip off domain name
135 current := node.Hostname()
136 if i := strings.IndexRune(current, '.'); i != -1 {
137 current = current[:i]
138 }
139 for _, mac := range macs {
David K. Bainbridgee9d7af72016-10-14 08:42:55 -0700140 if name, ok := options.Mappings[mac]; ok {
141 if current != name {
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700142 nodesObj := client.GetSubObject("nodes")
143 nodeObj := nodesObj.GetSubObject(node.ID())
David K. Bainbridgee9d7af72016-10-14 08:42:55 -0700144 log.Infof("RENAME '%s' to '%s'\n", node.Hostname(), name)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700145
146 if !options.Preview {
David K. Bainbridgee9d7af72016-10-14 08:42:55 -0700147 nodeObj.Update(url.Values{"hostname": []string{name}})
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700148 }
149 }
150 }
151 }
152 return nil
153}
154
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700155// Reset we are at the target state, nothing to do
156var Reset = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700157 log.Debugf("RESET: %s", node.Hostname())
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700158
159 if options.AlwaysRename {
160 updateNodeName(client, node, options)
161 }
162
David K. Bainbridge84918ec2016-07-08 09:12:35 -0700163 err := options.Provisioner.Clear(node.ID())
164 if err != nil {
165 log.Errorf("Attempting to clear provisioning state of node '%s' : %s", node.ID(), err)
166 }
167 return err
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700168}
169
170// Provision we are at the target state, nothing to do
171var Provision = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700172 log.Debugf("CHECK PROVISION: %s", node.Hostname())
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700173
174 if options.AlwaysRename {
175 updateNodeName(client, node, options)
176 }
177
David K. Bainbridge068e87d2016-06-30 13:53:19 -0700178 record, err := options.Provisioner.Get(node.ID())
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700179 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700180 log.Warningf("unable to retrieve provisioning state of node '%s' : %s", node.Hostname(), err)
David K. Bainbridge068e87d2016-06-30 13:53:19 -0700181 } else if record == nil || record.Status == Failed {
182 var label string
183 if record == nil {
184 label = "NotFound"
185 } else {
186 label = record.Status.String()
187 }
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700188 log.Debugf("Current state of node '%s' is '%s'", node.Hostname(), label)
David K. Bainbridge068e87d2016-06-30 13:53:19 -0700189 ips := node.IPs()
190 ip := ""
191 if len(ips) > 0 {
192 ip = ips[0]
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700193 }
David K. Bainbridge068e87d2016-06-30 13:53:19 -0700194 macs := node.MACs()
195 mac := ""
196 if len(macs) > 0 {
197 mac = macs[0]
198 }
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700199 log.Debugf("POSTing '%s' (%s) to '%s'", node.Hostname(), node.ID(), options.ProvisionURL)
David K. Bainbridge068e87d2016-06-30 13:53:19 -0700200 err = options.Provisioner.Provision(&ProvisionRequest{
201 Id: node.ID(),
202 Name: node.Hostname(),
203 Ip: ip,
204 Mac: mac,
205 })
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700206
207 if err != nil {
David K. Bainbridge11850cb2016-10-28 14:05:59 -0700208 log.Errorf("unable to provision '%s' (%s) : %s", node.ID(), node.Hostname(), err)
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700209 }
David K. Bainbridge068e87d2016-06-30 13:53:19 -0700210
211 } else if options.ProvisionTTL > 0 &&
212 record.Status == Running && time.Since(time.Unix(record.Timestamp, 0)) > options.ProvisionTTL {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700213 log.Errorf("Provisioning of node '%s' has passed provisioning TTL of '%v'",
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700214 node.Hostname(), options.ProvisionTTL)
David K. Bainbridge068e87d2016-06-30 13:53:19 -0700215 options.Provisioner.Clear(node.ID())
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700216 } else {
217 log.Debugf("Not invoking provisioning for '%s', current state is '%s'", node.Hostname(),
David K. Bainbridge068e87d2016-06-30 13:53:19 -0700218 record.Status.String())
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700219 }
220
221 return nil
222}
223
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700224// Done we are at the target state, nothing to do
225var Done = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
226 // As devices are normally in the "COMPLETED" state we don't want to
227 // log this fact unless we are in verbose mode. I suspect it would be
228 // nice to log it once when the device transitions from a non COMPLETE
229 // state to a complete state, but that would require keeping state.
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700230 log.Debugf("COMPLETE: %s", node.Hostname())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700231
232 if options.AlwaysRename {
233 updateNodeName(client, node, options)
234 }
235
236 return nil
237}
238
239// Deploy cause a node to deploy
240var Deploy = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700241 log.Infof("DEPLOY: %s", node.Hostname())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700242
243 if options.AlwaysRename {
244 updateNodeName(client, node, options)
245 }
246
247 if !options.Preview {
248 nodesObj := client.GetSubObject("nodes")
249 myNode := nodesObj.GetSubObject(node.ID())
250 // Start the node with the trusty distro. This should really be looked up or
251 // a parameter default
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700252 _, err := myNode.CallPost("start", url.Values{"distro_series": []string{"trusty"}})
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700253 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700254 log.Errorf("DEPLOY '%s' : '%s'", node.Hostname(), err)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700255 return err
256 }
257 }
258 return nil
259}
260
261// Aquire aquire a machine to a specific operator
262var Aquire = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700263 log.Infof("AQUIRE: %s", node.Hostname())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700264 nodesObj := client.GetSubObject("nodes")
265
266 if options.AlwaysRename {
267 updateNodeName(client, node, options)
268 }
269
270 if !options.Preview {
271 // With a new version of MAAS we have to make sure the node is linked
272 // to the subnet vid DHCP before we move to the Aquire state. To do this
273 // We need to unlink the interface to the subnet and then relink it.
274 //
275 // Iterate through all the interfaces on the node, searching for ones
276 // that are valid and not DHCP and move them to DHCP
277 ifcsObj := client.GetSubObject("nodes").GetSubObject(node.ID()).GetSubObject("interfaces")
278 ifcsListObj, err := ifcsObj.CallGet("", url.Values{})
279 if err != nil {
280 return err
281 }
282
283 ifcsArray, err := ifcsListObj.GetArray()
284 if err != nil {
285 return err
286 }
287
288 for _, ifc := range ifcsArray {
289 ifcMap, err := ifc.GetMap()
290 if err != nil {
291 return err
292 }
293
294 // Iterate over the links assocated with the interface, looking for
295 // links with a subnect as well as a mode of "auto"
296 links, ok := ifcMap["links"]
297 if ok {
298 linkArray, err := links.GetArray()
299 if err != nil {
300 return err
301 }
302
303 for _, link := range linkArray {
304 linkMap, err := link.GetMap()
305 if err != nil {
306 return err
307 }
308 subnet, ok := linkMap["subnet"]
309 if ok {
310 subnetMap, err := subnet.GetMap()
311 if err != nil {
312 return err
313 }
314
315 val, err := linkMap["mode"].GetString()
316 if err != nil {
317 return err
318 }
319
320 if val == "auto" {
321 // Found one we like, so grab the subnet from the data and
322 // then relink this as DHCP
323 cidr, err := subnetMap["cidr"].GetString()
324 if err != nil {
325 return err
326 }
327
328 fifcID, err := ifcMap["id"].GetFloat64()
329 if err != nil {
330 return err
331 }
332 ifcID := strconv.Itoa(int(fifcID))
333
334 flID, err := linkMap["id"].GetFloat64()
335 if err != nil {
336 return err
337 }
338 lID := strconv.Itoa(int(flID))
339
340 ifcObj := ifcsObj.GetSubObject(ifcID)
341 _, err = ifcObj.CallPost("unlink_subnet", url.Values{"id": []string{lID}})
342 if err != nil {
343 return err
344 }
345 _, err = ifcObj.CallPost("link_subnet", url.Values{"mode": []string{"DHCP"}, "subnet": []string{cidr}})
346 if err != nil {
347 return err
348 }
349 }
350 }
351 }
352 }
353 }
354 _, err = nodesObj.CallPost("acquire",
355 url.Values{"name": []string{node.Hostname()}})
356 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700357 log.Errorf("AQUIRE '%s' : '%s'", node.Hostname(), err)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700358 return err
359 }
360 }
361 return nil
362}
363
364// Commission cause a node to be commissioned
365var Commission = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
366 updateNodeName(client, node, options)
367
368 // Need to understand the power state of the node. We only want to move to "Commissioning" if the node
369 // power is off. If the node power is not off, then turn it off.
370 state := node.PowerState()
371 switch state {
372 case "on":
373 // Attempt to turn the node off
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700374 log.Infof("POWER DOWN: %s", node.Hostname())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700375 if !options.Preview {
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700376 //POST /api/1.0/nodes/{system_id}/ op=stop
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700377 nodesObj := client.GetSubObject("nodes")
378 nodeObj := nodesObj.GetSubObject(node.ID())
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700379 _, err := nodeObj.CallPost("stop", url.Values{"stop_mode": []string{"soft"}})
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700380 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700381 log.Errorf("Commission '%s' : changing power start to off : '%s'", node.Hostname(), err)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700382 }
383 return err
384 }
385 break
386 case "off":
387 // We are off so move to commissioning
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700388 log.Infof("COMISSION: %s", node.Hostname())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700389 if !options.Preview {
390 nodesObj := client.GetSubObject("nodes")
391 nodeObj := nodesObj.GetSubObject(node.ID())
392
393 updateNodeName(client, node, options)
394
395 _, err := nodeObj.CallPost("commission", url.Values{})
396 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700397 log.Errorf("Commission '%s' : '%s'", node.Hostname(), err)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700398 }
399 return err
400 }
401 break
402 default:
403 // We are in a state from which we can't move forward.
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700404 log.Warningf("%s has invalid power state '%s'", node.Hostname(), state)
David K. Bainbridge6ea57c12016-06-06 23:29:12 -0700405
406 // If a power helper script is set, we have an unknown power state, and
407 // we have not power type then attempt to use the helper script to discover
408 // and set the power settings
409 if options.PowerHelper != "" && node.PowerType() == "" {
410 cmd := exec.Command(options.PowerHelper,
411 append([]string{options.PowerHelperUser, options.PowerHelperHost},
412 node.MACs()...)...)
413 stdout, err := cmd.Output()
414 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700415 log.Errorf("Failed while executing power helper script '%s' : %s",
David K. Bainbridge6ea57c12016-06-06 23:29:12 -0700416 options.PowerHelper, err)
417 return err
418 }
419 power := Power{}
420 err = json.Unmarshal(stdout, &power)
421 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700422 log.Errorf("Failed to parse output of power helper script '%s' : %s",
David K. Bainbridge6ea57c12016-06-06 23:29:12 -0700423 options.PowerHelper, err)
424 return err
425 }
426 switch power.Name {
427 case "amt":
428 params := map[string]string{
429 "mac_address": power.MacAddress,
430 "power_pass": power.PowerPassword,
431 "power_address": power.PowerAddress,
432 }
433 node.UpdatePowerParameters(power.Name, params)
434 default:
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700435 log.Warningf("Unsupported power type discovered '%s'", power.Name)
David K. Bainbridge6ea57c12016-06-06 23:29:12 -0700436 }
437 }
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700438 break
439 }
440 return nil
441}
442
443// Wait a do nothing state, while work is being done
444var Wait = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700445 log.Infof("WAIT: %s", node.Hostname())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700446 return nil
447}
448
449// Fail a state from which we cannot, currently, automatically recover
450var Fail = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700451 log.Infof("FAIL: %s", node.Hostname())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700452 return nil
453}
454
455// AdminState an administrative state from which we should make no automatic transition
456var AdminState = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700457 log.Infof("ADMIN: %s", node.Hostname())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700458 return nil
459}
460
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700461func findActions(target string, current string) ([]Action, error) {
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700462 targets, ok := Transitions[target]
463 if !ok {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700464 log.Warningf("unable to find transitions to target state '%s'", target)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700465 return nil, fmt.Errorf("Could not find transition to target state '%s'", target)
466 }
467
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700468 actions, ok := targets[current]
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700469 if !ok {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700470 log.Warningf("unable to find transition from current state '%s' to target state '%s'",
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700471 current, target)
472 return nil, fmt.Errorf("Could not find transition from current state '%s' to target state '%s'",
473 current, target)
474 }
475
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700476 return actions, nil
477}
478
479// ProcessActions
480func ProcessActions(actions []Action, client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
481 var err error
482 for _, action := range actions {
483 if err = action(client, node, options); err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700484 log.Errorf("Error while processing action for node '%s' : %s",
David K. Bainbridge6ea57c12016-06-06 23:29:12 -0700485 node.Hostname(), err)
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700486 break
487 }
488 }
489 return err
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700490}
491
492// ProcessNode something
493func ProcessNode(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
494 substatus, err := node.GetInteger("substatus")
495 if err != nil {
496 return err
497 }
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700498 actions, err := findActions("Deployed", MaasNodeStatus(substatus).String())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700499 if err != nil {
500 return err
501 }
502
503 if options.Preview {
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700504 ProcessActions(actions, client, node, options)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700505 } else {
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700506 go ProcessActions(actions, client, node, options)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700507 }
508 return nil
509}
510
511func buildFilter(filter []string) ([]*regexp.Regexp, error) {
512
513 results := make([]*regexp.Regexp, len(filter))
514 for i, v := range filter {
515 r, err := regexp.Compile(v)
516 if err != nil {
517 return nil, err
518 }
519 results[i] = r
520 }
521 return results, nil
522}
523
524func matchedFilter(include []*regexp.Regexp, target string) bool {
525 for _, e := range include {
526 if e.MatchString(target) {
527 return true
528 }
529 }
530 return false
531}
532
533// ProcessAll something
534func ProcessAll(client *maas.MAASObject, nodes []MaasNode, options ProcessingOptions) []error {
535 errors := make([]error, len(nodes))
536 includeHosts, err := buildFilter(options.Filter.Hosts.Include)
537 if err != nil {
538 log.Fatalf("[error] invalid regular expression for include filter '%s' : %s", options.Filter.Hosts.Include, err)
539 }
540
541 includeZones, err := buildFilter(options.Filter.Zones.Include)
542 if err != nil {
543 log.Fatalf("[error] invalid regular expression for include filter '%v' : %s", options.Filter.Zones.Include, err)
544 }
545
546 for i, node := range nodes {
547 // For hostnames we always match on an empty filter
548 if len(includeHosts) >= 0 && matchedFilter(includeHosts, node.Hostname()) {
549
550 // For zones we don't match on an empty filter
551 if len(includeZones) >= 0 && matchedFilter(includeZones, node.Zone()) {
552 err := ProcessNode(client, node, options)
553 if err != nil {
554 errors[i] = err
555 } else {
556 errors[i] = nil
557 }
558 } else {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700559 log.Debugf("ignoring node '%s' as its zone '%s' didn't match include zone name filter '%v'",
560 node.Hostname(), node.Zone(), options.Filter.Zones.Include)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700561 }
562 } else {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700563 log.Debugf("ignoring node '%s' as it didn't match include hostname filter '%v'",
564 node.Hostname(), options.Filter.Hosts.Include)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700565 }
566 }
567 return errors
568}