blob: 23b51935044931120e1cfe0bdf5e3751928e0391 [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. Bainbridgeb5415042016-05-13 17:06:10 -070046// ProcessingOptions used to determine on what hosts to operate
47type ProcessingOptions struct {
48 Filter struct {
49 Zones struct {
50 Include []string
51 Exclude []string
52 }
53 Hosts struct {
54 Include []string
55 Exclude []string
56 }
57 }
David K. Bainbridge6ea57c12016-06-06 23:29:12 -070058 Mappings map[string]interface{}
David K. Bainbridge6ea57c12016-06-06 23:29:12 -070059 Preview bool
60 AlwaysRename bool
David K. Bainbridge068e87d2016-06-30 13:53:19 -070061 Provisioner Provisioner
David K. Bainbridge6ea57c12016-06-06 23:29:12 -070062 ProvisionURL string
63 ProvisionTTL time.Duration
64 PowerHelper string
65 PowerHelperUser string
66 PowerHelperHost string
David K. Bainbridgeb5415042016-05-13 17:06:10 -070067}
68
69// Transitions the actual map
70//
71// Currently this is a hand compiled / optimized "next step" table. This should
72// really be generated from the state machine chart input. Once this has been
73// accomplished you should be able to determine the action to take given your
74// target state and your current state.
David K. Bainbridgeefa951d2016-05-26 10:54:25 -070075var Transitions = map[string]map[string][]Action{
David K. Bainbridgeb5415042016-05-13 17:06:10 -070076 "Deployed": {
David K. Bainbridgeefa951d2016-05-26 10:54:25 -070077 "New": []Action{Reset, Commission},
78 "Deployed": []Action{Provision, Done},
79 "Ready": []Action{Reset, Aquire},
80 "Allocated": []Action{Reset, Deploy},
81 "Retired": []Action{Reset, AdminState},
82 "Reserved": []Action{Reset, AdminState},
83 "Releasing": []Action{Reset, Wait},
84 "DiskErasing": []Action{Reset, Wait},
85 "Deploying": []Action{Reset, Wait},
86 "Commissioning": []Action{Reset, Wait},
87 "Missing": []Action{Reset, Fail},
88 "FailedReleasing": []Action{Reset, Fail},
89 "FailedDiskErasing": []Action{Reset, Fail},
90 "FailedDeployment": []Action{Reset, Fail},
91 "Broken": []Action{Reset, Fail},
92 "FailedCommissioning": []Action{Reset, Fail},
David K. Bainbridgeb5415042016-05-13 17:06:10 -070093 },
94}
95
96const (
97 // defaultStateMachine Would be nice to drive from a graph language
98 defaultStateMachine string = `
David K. Bainbridged9b966f2016-05-31 13:30:05 -070099 (New)->(Commissioning)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700100 (Commissioning)->(FailedCommissioning)
101 (FailedCommissioning)->(New)
102 (Commissioning)->(Ready)
103 (Ready)->(Deploying)
104 (Ready)->(Allocated)
105 (Allocated)->(Deploying)
106 (Deploying)->(Deployed)
107 (Deploying)->(FailedDeployment)
108 (FailedDeployment)->(Broken)
109 (Deployed)->(Releasing)
110 (Releasing)->(FailedReleasing)
111 (FailedReleasing)->(Broken)
112 (Releasing)->(DiskErasing)
113 (DiskErasing)->(FailedEraseDisk)
114 (FailedEraseDisk)->(Broken)
115 (Releasing)->(Ready)
116 (DiskErasing)->(Ready)
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700117 (Broken)->(Ready)
David K. Bainbridged9b966f2016-05-31 13:30:05 -0700118 (Deployed)->(Provisioning)
119 (Provisioning)->|a|
120 |a|->(Execute Script)->|b|
121 |a|->(HTTP PUT)
122 (HTTP PUT)->(HTTP GET)
123 (HTTP GET)->(HTTP GET)
124 (HTTP GET)->|b|
125 |b|->(Provisioned)
126 |b|->(ProvisionError)
127 (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 {
140 if entry, ok := options.Mappings[mac]; ok {
141 if name, ok := entry.(map[string]interface{})["hostname"]; ok && current != name.(string) {
142 nodesObj := client.GetSubObject("nodes")
143 nodeObj := nodesObj.GetSubObject(node.ID())
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700144 log.Infof("RENAME '%s' to '%s'\n", node.Hostname(), name.(string))
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700145
146 if !options.Preview {
147 nodeObj.Update(url.Values{"hostname": []string{name.(string)}})
148 }
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. Bainbridge068e87d2016-06-30 13:53:19 -0700163 options.Provisioner.Clear(node.ID())
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700164
165 return nil
166}
167
168// Provision we are at the target state, nothing to do
169var Provision = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700170 log.Debugf("CHECK PROVISION: %s", node.Hostname())
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700171
172 if options.AlwaysRename {
173 updateNodeName(client, node, options)
174 }
175
David K. Bainbridge068e87d2016-06-30 13:53:19 -0700176 record, err := options.Provisioner.Get(node.ID())
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700177 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700178 log.Warningf("unable to retrieve provisioning state of node '%s' : %s", node.Hostname(), err)
David K. Bainbridge068e87d2016-06-30 13:53:19 -0700179 } else if record == nil || record.Status == Failed {
180 var label string
181 if record == nil {
182 label = "NotFound"
183 } else {
184 label = record.Status.String()
185 }
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700186 log.Debugf("Current state of node '%s' is '%s'", node.Hostname(), label)
David K. Bainbridge068e87d2016-06-30 13:53:19 -0700187 ips := node.IPs()
188 ip := ""
189 if len(ips) > 0 {
190 ip = ips[0]
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700191 }
David K. Bainbridge068e87d2016-06-30 13:53:19 -0700192 macs := node.MACs()
193 mac := ""
194 if len(macs) > 0 {
195 mac = macs[0]
196 }
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700197 log.Debugf("POSTing '%s' (%s) to '%s'", node.Hostname(), node.ID(), options.ProvisionURL)
David K. Bainbridge068e87d2016-06-30 13:53:19 -0700198 err = options.Provisioner.Provision(&ProvisionRequest{
199 Id: node.ID(),
200 Name: node.Hostname(),
201 Ip: ip,
202 Mac: mac,
203 })
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700204
205 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700206 log.Errorf("unable to provision '%s' (%s) : %s", node.ID, node.Hostname(), err)
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700207 }
David K. Bainbridge068e87d2016-06-30 13:53:19 -0700208
209 } else if options.ProvisionTTL > 0 &&
210 record.Status == Running && time.Since(time.Unix(record.Timestamp, 0)) > options.ProvisionTTL {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700211 log.Errorf("Provisioning of node '%s' has passed provisioning TTL of '%v'",
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700212 node.Hostname(), options.ProvisionTTL)
David K. Bainbridge068e87d2016-06-30 13:53:19 -0700213 options.Provisioner.Clear(node.ID())
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700214 } else {
215 log.Debugf("Not invoking provisioning for '%s', current state is '%s'", node.Hostname(),
David K. Bainbridge068e87d2016-06-30 13:53:19 -0700216 record.Status.String())
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700217 }
218
219 return nil
220}
221
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700222// Done we are at the target state, nothing to do
223var Done = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
224 // As devices are normally in the "COMPLETED" state we don't want to
225 // log this fact unless we are in verbose mode. I suspect it would be
226 // nice to log it once when the device transitions from a non COMPLETE
227 // state to a complete state, but that would require keeping state.
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700228 log.Debugf("COMPLETE: %s", node.Hostname())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700229
230 if options.AlwaysRename {
231 updateNodeName(client, node, options)
232 }
233
234 return nil
235}
236
237// Deploy cause a node to deploy
238var Deploy = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700239 log.Infof("DEPLOY: %s", node.Hostname())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700240
241 if options.AlwaysRename {
242 updateNodeName(client, node, options)
243 }
244
245 if !options.Preview {
246 nodesObj := client.GetSubObject("nodes")
247 myNode := nodesObj.GetSubObject(node.ID())
248 // Start the node with the trusty distro. This should really be looked up or
249 // a parameter default
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700250 _, err := myNode.CallPost("start", url.Values{"distro_series": []string{"trusty"}})
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700251 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700252 log.Errorf("DEPLOY '%s' : '%s'", node.Hostname(), err)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700253 return err
254 }
255 }
256 return nil
257}
258
259// Aquire aquire a machine to a specific operator
260var Aquire = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700261 log.Infof("AQUIRE: %s", node.Hostname())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700262 nodesObj := client.GetSubObject("nodes")
263
264 if options.AlwaysRename {
265 updateNodeName(client, node, options)
266 }
267
268 if !options.Preview {
269 // With a new version of MAAS we have to make sure the node is linked
270 // to the subnet vid DHCP before we move to the Aquire state. To do this
271 // We need to unlink the interface to the subnet and then relink it.
272 //
273 // Iterate through all the interfaces on the node, searching for ones
274 // that are valid and not DHCP and move them to DHCP
275 ifcsObj := client.GetSubObject("nodes").GetSubObject(node.ID()).GetSubObject("interfaces")
276 ifcsListObj, err := ifcsObj.CallGet("", url.Values{})
277 if err != nil {
278 return err
279 }
280
281 ifcsArray, err := ifcsListObj.GetArray()
282 if err != nil {
283 return err
284 }
285
286 for _, ifc := range ifcsArray {
287 ifcMap, err := ifc.GetMap()
288 if err != nil {
289 return err
290 }
291
292 // Iterate over the links assocated with the interface, looking for
293 // links with a subnect as well as a mode of "auto"
294 links, ok := ifcMap["links"]
295 if ok {
296 linkArray, err := links.GetArray()
297 if err != nil {
298 return err
299 }
300
301 for _, link := range linkArray {
302 linkMap, err := link.GetMap()
303 if err != nil {
304 return err
305 }
306 subnet, ok := linkMap["subnet"]
307 if ok {
308 subnetMap, err := subnet.GetMap()
309 if err != nil {
310 return err
311 }
312
313 val, err := linkMap["mode"].GetString()
314 if err != nil {
315 return err
316 }
317
318 if val == "auto" {
319 // Found one we like, so grab the subnet from the data and
320 // then relink this as DHCP
321 cidr, err := subnetMap["cidr"].GetString()
322 if err != nil {
323 return err
324 }
325
326 fifcID, err := ifcMap["id"].GetFloat64()
327 if err != nil {
328 return err
329 }
330 ifcID := strconv.Itoa(int(fifcID))
331
332 flID, err := linkMap["id"].GetFloat64()
333 if err != nil {
334 return err
335 }
336 lID := strconv.Itoa(int(flID))
337
338 ifcObj := ifcsObj.GetSubObject(ifcID)
339 _, err = ifcObj.CallPost("unlink_subnet", url.Values{"id": []string{lID}})
340 if err != nil {
341 return err
342 }
343 _, err = ifcObj.CallPost("link_subnet", url.Values{"mode": []string{"DHCP"}, "subnet": []string{cidr}})
344 if err != nil {
345 return err
346 }
347 }
348 }
349 }
350 }
351 }
352 _, err = nodesObj.CallPost("acquire",
353 url.Values{"name": []string{node.Hostname()}})
354 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700355 log.Errorf("AQUIRE '%s' : '%s'", node.Hostname(), err)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700356 return err
357 }
358 }
359 return nil
360}
361
362// Commission cause a node to be commissioned
363var Commission = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
364 updateNodeName(client, node, options)
365
366 // Need to understand the power state of the node. We only want to move to "Commissioning" if the node
367 // power is off. If the node power is not off, then turn it off.
368 state := node.PowerState()
369 switch state {
370 case "on":
371 // Attempt to turn the node off
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700372 log.Infof("POWER DOWN: %s", node.Hostname())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700373 if !options.Preview {
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700374 //POST /api/1.0/nodes/{system_id}/ op=stop
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700375 nodesObj := client.GetSubObject("nodes")
376 nodeObj := nodesObj.GetSubObject(node.ID())
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700377 _, err := nodeObj.CallPost("stop", url.Values{"stop_mode": []string{"soft"}})
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700378 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700379 log.Errorf("Commission '%s' : changing power start to off : '%s'", node.Hostname(), err)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700380 }
381 return err
382 }
383 break
384 case "off":
385 // We are off so move to commissioning
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700386 log.Infof("COMISSION: %s", node.Hostname())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700387 if !options.Preview {
388 nodesObj := client.GetSubObject("nodes")
389 nodeObj := nodesObj.GetSubObject(node.ID())
390
391 updateNodeName(client, node, options)
392
393 _, err := nodeObj.CallPost("commission", url.Values{})
394 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700395 log.Errorf("Commission '%s' : '%s'", node.Hostname(), err)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700396 }
397 return err
398 }
399 break
400 default:
401 // We are in a state from which we can't move forward.
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700402 log.Warningf("%s has invalid power state '%s'", node.Hostname(), state)
David K. Bainbridge6ea57c12016-06-06 23:29:12 -0700403
404 // If a power helper script is set, we have an unknown power state, and
405 // we have not power type then attempt to use the helper script to discover
406 // and set the power settings
407 if options.PowerHelper != "" && node.PowerType() == "" {
408 cmd := exec.Command(options.PowerHelper,
409 append([]string{options.PowerHelperUser, options.PowerHelperHost},
410 node.MACs()...)...)
411 stdout, err := cmd.Output()
412 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700413 log.Errorf("Failed while executing power helper script '%s' : %s",
David K. Bainbridge6ea57c12016-06-06 23:29:12 -0700414 options.PowerHelper, err)
415 return err
416 }
417 power := Power{}
418 err = json.Unmarshal(stdout, &power)
419 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700420 log.Errorf("Failed to parse output of power helper script '%s' : %s",
David K. Bainbridge6ea57c12016-06-06 23:29:12 -0700421 options.PowerHelper, err)
422 return err
423 }
424 switch power.Name {
425 case "amt":
426 params := map[string]string{
427 "mac_address": power.MacAddress,
428 "power_pass": power.PowerPassword,
429 "power_address": power.PowerAddress,
430 }
431 node.UpdatePowerParameters(power.Name, params)
432 default:
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700433 log.Warningf("Unsupported power type discovered '%s'", power.Name)
David K. Bainbridge6ea57c12016-06-06 23:29:12 -0700434 }
435 }
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700436 break
437 }
438 return nil
439}
440
441// Wait a do nothing state, while work is being done
442var Wait = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700443 log.Infof("WAIT: %s", node.Hostname())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700444 return nil
445}
446
447// Fail a state from which we cannot, currently, automatically recover
448var Fail = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700449 log.Infof("FAIL: %s", node.Hostname())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700450 return nil
451}
452
453// AdminState an administrative state from which we should make no automatic transition
454var AdminState = func(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700455 log.Infof("ADMIN: %s", node.Hostname())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700456 return nil
457}
458
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700459func findActions(target string, current string) ([]Action, error) {
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700460 targets, ok := Transitions[target]
461 if !ok {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700462 log.Warningf("unable to find transitions to target state '%s'", target)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700463 return nil, fmt.Errorf("Could not find transition to target state '%s'", target)
464 }
465
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700466 actions, ok := targets[current]
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700467 if !ok {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700468 log.Warningf("unable to find transition from current state '%s' to target state '%s'",
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700469 current, target)
470 return nil, fmt.Errorf("Could not find transition from current state '%s' to target state '%s'",
471 current, target)
472 }
473
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700474 return actions, nil
475}
476
477// ProcessActions
478func ProcessActions(actions []Action, client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
479 var err error
480 for _, action := range actions {
481 if err = action(client, node, options); err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700482 log.Errorf("Error while processing action for node '%s' : %s",
David K. Bainbridge6ea57c12016-06-06 23:29:12 -0700483 node.Hostname(), err)
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700484 break
485 }
486 }
487 return err
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700488}
489
490// ProcessNode something
491func ProcessNode(client *maas.MAASObject, node MaasNode, options ProcessingOptions) error {
492 substatus, err := node.GetInteger("substatus")
493 if err != nil {
494 return err
495 }
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700496 actions, err := findActions("Deployed", MaasNodeStatus(substatus).String())
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700497 if err != nil {
498 return err
499 }
500
501 if options.Preview {
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700502 ProcessActions(actions, client, node, options)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700503 } else {
David K. Bainbridgeefa951d2016-05-26 10:54:25 -0700504 go ProcessActions(actions, client, node, options)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700505 }
506 return nil
507}
508
509func buildFilter(filter []string) ([]*regexp.Regexp, error) {
510
511 results := make([]*regexp.Regexp, len(filter))
512 for i, v := range filter {
513 r, err := regexp.Compile(v)
514 if err != nil {
515 return nil, err
516 }
517 results[i] = r
518 }
519 return results, nil
520}
521
522func matchedFilter(include []*regexp.Regexp, target string) bool {
523 for _, e := range include {
524 if e.MatchString(target) {
525 return true
526 }
527 }
528 return false
529}
530
531// ProcessAll something
532func ProcessAll(client *maas.MAASObject, nodes []MaasNode, options ProcessingOptions) []error {
533 errors := make([]error, len(nodes))
534 includeHosts, err := buildFilter(options.Filter.Hosts.Include)
535 if err != nil {
536 log.Fatalf("[error] invalid regular expression for include filter '%s' : %s", options.Filter.Hosts.Include, err)
537 }
538
539 includeZones, err := buildFilter(options.Filter.Zones.Include)
540 if err != nil {
541 log.Fatalf("[error] invalid regular expression for include filter '%v' : %s", options.Filter.Zones.Include, err)
542 }
543
544 for i, node := range nodes {
545 // For hostnames we always match on an empty filter
546 if len(includeHosts) >= 0 && matchedFilter(includeHosts, node.Hostname()) {
547
548 // For zones we don't match on an empty filter
549 if len(includeZones) >= 0 && matchedFilter(includeZones, node.Zone()) {
550 err := ProcessNode(client, node, options)
551 if err != nil {
552 errors[i] = err
553 } else {
554 errors[i] = nil
555 }
556 } else {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700557 log.Debugf("ignoring node '%s' as its zone '%s' didn't match include zone name filter '%v'",
558 node.Hostname(), node.Zone(), options.Filter.Zones.Include)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700559 }
560 } else {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700561 log.Debugf("ignoring node '%s' as it didn't match include hostname filter '%v'",
562 node.Hostname(), options.Filter.Hosts.Include)
David K. Bainbridgeb5415042016-05-13 17:06:10 -0700563 }
564 }
565 return errors
566}