Merge ~cgrabowski/maas:embed_run_scripts_in_hardware_sync into maas:master
- Git
- lp:~cgrabowski/maas
- embed_run_scripts_in_hardware_sync
- Merge into master
Status: | Rejected |
---|---|
Rejected by: | Christian Grabowski |
Proposed branch: | ~cgrabowski/maas:embed_run_scripts_in_hardware_sync |
Merge into: | maas:master |
Diff against target: |
2163 lines (+1552/-104) 44 files modified
.gitignore (+1/-0) src/host-info/Makefile (+12/-1) src/host-info/cmd/hardware-sync/main.go (+160/-58) src/host-info/go.mod (+1/-0) src/host-info/go.sum (+2/-0) src/host-info/pkg/scriptrunner/runner.go (+494/-0) src/host-info/pkg/scriptrunner/runner_test.go (+164/-0) src/host-info/scripts/50-maas-01-commissioning.py (+1/-0) src/host-info/scripts/badblocks.py (+1/-0) src/host-info/scripts/base-connectivity.sh (+1/-0) src/host-info/scripts/bmc_config.py (+1/-0) src/host-info/scripts/capture_lldpd.py (+1/-0) src/host-info/scripts/dhcp_unconfigured_ifaces.py (+1/-0) src/host-info/scripts/fio.py (+1/-0) src/host-info/scripts/gateway-connectivity.sh (+115/-0) src/host-info/scripts/install_lldpd.py (+1/-0) src/host-info/scripts/internet-connectivity.sh (+98/-0) src/host-info/scripts/maas-get-fruid-api-data.sh (+1/-0) src/host-info/scripts/maas-kernel-cmdline.sh (+1/-0) src/host-info/scripts/maas-list-modaliases.sh (+1/-0) src/host-info/scripts/maas-lshw.sh (+1/-0) src/host-info/scripts/maas-serial-ports.sh (+1/-0) src/host-info/scripts/maas-support-info.sh (+1/-0) src/host-info/scripts/maas_api_helper.py (+1/-0) src/host-info/scripts/maas_run_scripts.py (+1/-0) src/host-info/scripts/memtester.sh (+1/-0) src/host-info/scripts/ntp.sh (+1/-0) src/host-info/scripts/rack-controller-connectivity.sh (+99/-0) src/host-info/scripts/seven_z.py (+1/-0) src/host-info/scripts/smartctl.py (+1/-0) src/host-info/scripts/stress-ng-cpu-long.sh (+1/-0) src/host-info/scripts/stress-ng-cpu-short.sh (+1/-0) src/host-info/scripts/stress-ng-memory-long.sh (+1/-0) src/host-info/scripts/stress-ng-memory-short.sh (+1/-0) src/metadataserver/api.py (+74/-0) src/metadataserver/builtin_scripts/testing_scripts/badblocks.py (+3/-1) src/metadataserver/builtin_scripts/testing_scripts/gateway-connectivity.sh (+1/-1) src/metadataserver/templates/hardware_sync_service.template (+6/-1) src/metadataserver/tests/test_api.py (+121/-0) src/metadataserver/tests/test_vendor_data.py (+15/-3) src/metadataserver/urls.py (+10/-1) src/metadataserver/user_data/templates/snippets/maas_run_scripts.py (+90/-37) src/metadataserver/user_data/templates/snippets/tests/test_maas_run_scripts.py (+44/-0) src/metadataserver/vendor_data.py (+18/-1) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Alberto Donato (community) | Needs Information | ||
MAAS Lander | Approve | ||
Review via email: mp+415278@code.launchpad.net |
Commit message
add local scripts test
add runner tests
add script metadata endpoint
update script runner for local scripts
provide file extensions
add commissioning scripts
template in hardware-sync executable in systemd config
embed scripts as subcommands
Description of the change
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b embed_run_
STATUS: FAILED
LOG: http://
COMMIT: f685a1175007bdb
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b embed_run_
STATUS: FAILED
LOG: http://
COMMIT: 18a2ec8c7c3bcb3
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b embed_run_
STATUS: SUCCESS
COMMIT: c007b066d7f8d3a
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b embed_run_
STATUS: SUCCESS
COMMIT: cbfdde8a2055195
Alberto Donato (ack) wrote : | # |
While I think it's useful to move to a go binary to run maas scripts, I think we should avoid having two different ways to serve scripts that get run on a machine.
As a first step, it'd be useful to have something that works end-to-end using maas-run-scripts from the systemd timer, i.e. generating the config file and just downloading running it periodically.
Later we can replace the script with a go binary, which does the same thing but has no external dependencies, and make optimizations to reduce data transfer.
For instance, the binary could check a url on maas for the exact MAAS version/revision and if it doesn't match, download and re-execute itself.
We could also investigate pre-packaging builtin scripts as a tarball on release so that we serve it statically and only need to download custom scripts.
Whatever we do, we should try to use the same mechanisms and paths on commissioning, brownfield and hardware updates.
WDYT?
Christian Grabowski (cgrabowski) wrote : | # |
> While I think it's useful to move to a go binary to run maas scripts, I think
> we should avoid having two different ways to serve scripts that get run on a
> machine.
>
> As a first step, it'd be useful to have something that works end-to-end using
> maas-run-scripts from the systemd timer, i.e. generating the config file and
> just downloading running it periodically.
>
> Later we can replace the script with a go binary, which does the same thing
> but has no external dependencies, and make optimizations to reduce data
> transfer.
> For instance, the binary could check a url on maas for the exact MAAS
> version/revision and if it doesn't match, download and re-execute itself.
> We could also investigate pre-packaging builtin scripts as a tarball on
> release so that we serve it statically and only need to download custom
> scripts.
>
> Whatever we do, we should try to use the same mechanisms and paths on
> commissioning, brownfield and hardware updates.
>
> WDYT?
Well the original plan was to use maas-run-scripts directly, so I'm not opposed to that. But it would require backing out a fair bit of work at this point.
As discussed on the spec, this already is supposed to replace brownfield (the redirect handler that was added will serve this instead), and it should replace commissioning and testing, but we decided that was out of scope for now, but we can certainly move towards this replacing those as well. So I think the concern of having two ways to run scripts on a machine is valid, but also one that is temporary.
I do like the idea of having a go binary directly replace maas-run-scripts rather than embedding it and executing it as a child proc, but I did think this would be a good first step, and then have maas-run-scripts call out to this binary for each subsequent script.
If having commissioning's way of running scripts being different for the current scope is a concern, I'm fine backing out the existing changes and closing this MP in favor of one that uses maas-run-scripts directly.
Alberto Donato (ack) wrote : | # |
I do get the point about reducing dependencies on the running system and having a simpler way to deliver binaries, and I think we should eventually replace commissioning/
This, though, is a much broader scope (it's a whole new feature), so I think that for now we should go for minimal change and just extend the current brownfield method to be run periodically.
Making the main binary a go binary that still uses maas-run-scripts doesn't really reduce our dependency on binaries from the system.
Christian Grabowski (cgrabowski) wrote : | # |
closing in favor of https:/
Unmerged commits
- cbfdde8... by Christian Grabowski
-
add local scripts test
- 9bcc406... by Christian Grabowski
-
add runner tests
- d35a1dd... by Christian Grabowski
-
add script metadata endpoint
- 0609567... by Christian Grabowski
-
update script runner for local scripts
- b8cce3f... by Christian Grabowski
-
provide file extensions
- ad80ec4... by Christian Grabowski
-
add commissioning scripts
- 696c205... by Christian Grabowski
-
template in hardware-sync executable in systemd config
- a90cce8... by Christian Grabowski
-
embed scripts as subcommands
Preview Diff
1 | diff --git a/.gitignore b/.gitignore |
2 | index dd00eca..45e89ae 100644 |
3 | --- a/.gitignore |
4 | +++ b/.gitignore |
5 | @@ -47,6 +47,7 @@ |
6 | /src/maasui/nodejs |
7 | /src/host-info/bin |
8 | /src/host-info/vendor |
9 | +/src/host-info/pkg/scriptrunner/statik |
10 | /stage |
11 | /TAGS |
12 | /tags |
13 | diff --git a/src/host-info/Makefile b/src/host-info/Makefile |
14 | index d3a2b66..ba682cc 100644 |
15 | --- a/src/host-info/Makefile |
16 | +++ b/src/host-info/Makefile |
17 | @@ -21,13 +21,19 @@ HARDWARE_SYNC_DIR := $(CMD_DIR)/hardware-sync |
18 | VENDOR_DIR := $(PACKAGE_DIR)/vendor |
19 | GO_CACHE_DIR := $(shell [ -d $(HOME)/.cache ] && echo $(HOME)/.cache/go-cache || mktemp --tmpdir -d tmp.go-cacheXXX) |
20 | |
21 | +SCRIPTS_SRC_DIR := ./scripts/ |
22 | +SCRIPTS_OUT_DIR := ./pkg/scriptrunner/ |
23 | + |
24 | .DEFAULT_GOAL := build |
25 | |
26 | +$(SCRIPTS_OUT_DIR)statik/statik.go: |
27 | + statik -src=$(SCRIPTS_SRC_DIR) -dest=$(SCRIPTS_OUT_DIR) -f |
28 | + |
29 | # XXX: Explicitly set GOCACHE to avoid situations where we can't mkdir $HOME/.cache (autopkgtest VM) |
30 | $(MACHINE_RESOURCES_BINARIES): vendor |
31 | GOCACHE=$(GO_CACHE_DIR) GOARCH=$(DEB_GO_ARCH_$(notdir $@)) go build -mod vendor -ldflags '-s -w' -o $@ $(PACKAGE_DIR) |
32 | |
33 | -$(HARDWARE_SYNC_BINARIES): vendor |
34 | +$(HARDWARE_SYNC_BINARIES): vendor $(SCRIPTS_OUT_DIR)statik/statik.go |
35 | GOCACHE=$(GO_CACHE_DIR) GOARCH=$(DEB_GO_ARCH_$(notdir $@)) go build -mod vendor -ldflags '-s -w' -o $@ $(HARDWARE_SYNC_DIR) |
36 | |
37 | build: $(MACHINE_RESOURCES_BINARIES) $(HARDWARE_SYNC_BINARIES) |
38 | @@ -35,6 +41,7 @@ build: $(MACHINE_RESOURCES_BINARIES) $(HARDWARE_SYNC_BINARIES) |
39 | |
40 | clean: |
41 | rm -rf $(BINDIR) $(VENDOR_DIR) |
42 | + rm -rf $(SCRIPTS_OUT_DIR)statik/ |
43 | .PHONY: clean |
44 | |
45 | format: |
46 | @@ -53,3 +60,7 @@ update-deps: |
47 | go get -u all |
48 | go mod tidy |
49 | .PHONY: update-deps |
50 | + |
51 | +test: |
52 | + go test -v ./... |
53 | +.PHONY: test |
54 | diff --git a/src/host-info/cmd/hardware-sync/main.go b/src/host-info/cmd/hardware-sync/main.go |
55 | index 9c5960e..97aa250 100644 |
56 | --- a/src/host-info/cmd/hardware-sync/main.go |
57 | +++ b/src/host-info/cmd/hardware-sync/main.go |
58 | @@ -4,86 +4,188 @@ |
59 | package main |
60 | |
61 | import ( |
62 | + "context" |
63 | "encoding/json" |
64 | - "flag" |
65 | "fmt" |
66 | "os" |
67 | + "os/signal" |
68 | + "syscall" |
69 | |
70 | "host-info/pkg/info" |
71 | + "host-info/pkg/scriptrunner" |
72 | ) |
73 | |
74 | // MAASVersion corresponds with the running MAAS version hardware-sync reports to |
75 | -var MAASVersion = "" // set at compile time |
76 | +var ( |
77 | + MAASVersion = "" // set at compile time |
78 | |
79 | -const TokenFormat = "'consumer-key:token-key:token-secret[:consumer_secret]'" |
80 | + HelpText = `maas-hardware-sync [-h | -v | --list | COMMAND] [COMMAND_ARGS] |
81 | +maas-hardware-sync runs all commands necessary for reporting hardware info to MAAS. |
82 | |
83 | -type getMachineTokenOptions struct { |
84 | - SystemId string |
85 | - TokenFile string |
86 | -} |
87 | - |
88 | -type reportResultsOptions struct { |
89 | - Config string |
90 | - MachineToken string |
91 | - MetadataUrl string |
92 | -} |
93 | - |
94 | -type cmdOptions struct { |
95 | - Debug bool |
96 | - MachineResourceRun bool |
97 | - GetMachineToken getMachineTokenOptions |
98 | - ReportResults reportResultsOptions |
99 | -} |
100 | +maas-hardware-sync -h: print help message |
101 | +maas-hardware-sync -v: print MAAS version this binary is part of |
102 | +maas-hardware-sync --list: list available commands |
103 | +maas-hardware-sync COMMAND [COMMAND_ARGS]: run a command and pass it COMMAND_ARGS |
104 | +` |
105 | +) |
106 | |
107 | -func parseCmdLine() (opts cmdOptions) { |
108 | - flag.BoolVar(&opts.Debug, "debub", false, "print verbose debug messages") |
109 | - flag.BoolVar(&opts.MachineResourceRun, "machine-resources", false, "when set, will read machine resources and print the info to stdout") |
110 | - flag.StringVar(&opts.GetMachineToken.SystemId, "system-id", "", "system ID for the machine to get credentials for") |
111 | - flag.StringVar(&opts.GetMachineToken.TokenFile, "token-file", "", "path for the file to write the token to") |
112 | - flag.StringVar( |
113 | - &opts.ReportResults.Config, |
114 | - "config", |
115 | - "", |
116 | - "cloud-init config with MAAS credentials and endpoint, (e.g. /etc/cloud/cloud.cfg.d/90_dpkg_local_cloud_config.cfg)", |
117 | - ) |
118 | - flag.StringVar( |
119 | - &opts.ReportResults.MachineToken, |
120 | - "machine-token", |
121 | - "", |
122 | - fmt.Sprintf("Machine OAuth token, in the %s form", TokenFormat), |
123 | - ) |
124 | - flag.StringVar( |
125 | - &opts.ReportResults.MetadataUrl, |
126 | - "metadata-url", |
127 | - "", |
128 | - "MAAS metadata URL", |
129 | - ) |
130 | - flag.Parse() |
131 | - return opts |
132 | +func parseCmdLine() (cmd string, args []string) { |
133 | + if len(os.Args) < 2 { |
134 | + return "help", args |
135 | + } |
136 | + switch os.Args[1] { |
137 | + case "-h": |
138 | + return "help", args |
139 | + case "-v": |
140 | + return "version", args |
141 | + case "--list": |
142 | + return "list_cmds", args |
143 | + default: |
144 | + if len(os.Args) > 2 { |
145 | + args = os.Args[2:] |
146 | + } |
147 | + return os.Args[1], args |
148 | + } |
149 | } |
150 | |
151 | -func getMachineResources() error { |
152 | +func getMachineResources() ([]byte, error) { |
153 | machineResources, err := info.GetInfo() |
154 | if err != nil { |
155 | - return err |
156 | + return nil, err |
157 | } |
158 | - encoder := json.NewEncoder(os.Stdout) |
159 | - return encoder.Encode(machineResources) |
160 | + return json.Marshal(machineResources) |
161 | +} |
162 | + |
163 | +func handleSignal(cancel context.CancelFunc) { |
164 | + sigC := make(chan os.Signal, 2) |
165 | + signal.Notify(sigC, syscall.SIGTERM, syscall.SIGINT) |
166 | + <-sigC |
167 | + cancel() |
168 | } |
169 | |
170 | func run() int { |
171 | - opts := parseCmdLine() |
172 | + bkg := context.Background() |
173 | + ctx, cancel := context.WithCancel(bkg) |
174 | |
175 | - if opts.MachineResourceRun { |
176 | - err := getMachineResources() |
177 | - if err != nil { |
178 | - fmt.Printf("an error occurred while fetching machine resources: %s\n", err) |
179 | - return 1 |
180 | - } |
181 | - return 0 |
182 | + go handleSignal(cancel) |
183 | + |
184 | + var out, errOut []byte |
185 | + |
186 | + cmd, args := parseCmdLine() |
187 | + |
188 | + runner, err := scriptrunner.New() |
189 | + if err != nil { |
190 | + fmt.Printf("failed to initialize script runner: %s\n", err) |
191 | + return 1 |
192 | + } |
193 | + |
194 | + switch cmd { |
195 | + case "help": |
196 | + out = []byte(HelpText) |
197 | + case "version": |
198 | + out = []byte(MAASVersion) |
199 | + case "list_cmds": |
200 | + out = []byte(scriptrunner.ListCMDs()) |
201 | + case scriptrunner.CMDGetMachineToken: |
202 | + out, errOut, err = runner.GetMachineToken(ctx, args) |
203 | + case scriptrunner.CMDRegisterMachine: |
204 | + out, errOut, err = runner.RegisterMachine(ctx, args) |
205 | + case scriptrunner.CMDReportResults: |
206 | + out, errOut, err = runner.ReportResults(ctx, args) |
207 | + case scriptrunner.CMDBMCConfig: |
208 | + out, errOut, err = runner.BMCConfig(ctx, args) |
209 | + case scriptrunner.CMDCaptureLLDPD: |
210 | + out, errOut, err = runner.CaptureLLDPD(ctx, args) |
211 | + case scriptrunner.CMDDHCPUnconfiguredIfaces: |
212 | + out, errOut, err = runner.DHCPUnconfiguredIfaces(ctx, args) |
213 | + case scriptrunner.CMDInstallLLDPD: |
214 | + out, errOut, err = runner.InstallLLDPD(ctx, args) |
215 | + case scriptrunner.CMDMAASGetFruidAPIData: |
216 | + out, errOut, err = runner.MAASGetFruidAPIData(ctx, args) |
217 | + case scriptrunner.CMDMAASKernelCMDLine: |
218 | + out, errOut, err = runner.MAASKernelCMDLine(ctx, args) |
219 | + case scriptrunner.CMDMAASListModaliases: |
220 | + out, errOut, err = runner.MAASListModaliases(ctx, args) |
221 | + case scriptrunner.CMDMAASLSHW: |
222 | + out, errOut, err = runner.MAASLshw(ctx, args) |
223 | + case scriptrunner.CMDMAASSerialPorts: |
224 | + out, errOut, err = runner.MAASSerialPorts(ctx, args) |
225 | + case scriptrunner.CMDMAASSupportInfo: |
226 | + out, errOut, err = runner.MAASSupportInfo(ctx, args) |
227 | + case scriptrunner.CMDMAASCommissioning: |
228 | + out, errOut, err = runner.MAASCommissioning(ctx, args) |
229 | + case scriptrunner.CMDBadBlocks: |
230 | + out, errOut, err = runner.BadBlocks(ctx, args) |
231 | + case scriptrunner.CMDBadBlocksDestructive: |
232 | + args = append([]string{ |
233 | + "destructive", |
234 | + }, args...) |
235 | + out, errOut, err = runner.BadBlocks(ctx, args) |
236 | + case scriptrunner.CMDFio: |
237 | + out, errOut, err = runner.Fio(ctx, args) |
238 | + case scriptrunner.CMDGatewayConnectivity: |
239 | + out, errOut, err = runner.GatewayConnectivity(ctx, args) |
240 | + case scriptrunner.CMDInternetConnectivity: |
241 | + out, errOut, err = runner.InternetConnectivity(ctx, args) |
242 | + case scriptrunner.CMDMemTester: |
243 | + out, errOut, err = runner.MemTester(ctx, args) |
244 | + case scriptrunner.CMDNtp: |
245 | + out, errOut, err = runner.Ntp(ctx, args) |
246 | + case scriptrunner.CMDSevenZ: |
247 | + out, errOut, err = runner.SevenZ(ctx, args) |
248 | + case scriptrunner.CMDRackControllerConnectivity: |
249 | + out, errOut, err = runner.RackControllerConnectivity(ctx, args) |
250 | + case scriptrunner.CMDSmartctlConveyance: |
251 | + out, errOut, err = runner.Smartctl(ctx, append([]string{ |
252 | + "--test", |
253 | + "conveyance", |
254 | + }, args...)) |
255 | + case scriptrunner.CMDSmartctlLong: |
256 | + out, errOut, err = runner.Smartctl(ctx, append([]string{ |
257 | + "--test", |
258 | + "long", |
259 | + }, args...)) |
260 | + case scriptrunner.CMDSmartctlShort: |
261 | + out, errOut, err = runner.Smartctl(ctx, append([]string{ |
262 | + "--test", |
263 | + "short", |
264 | + }, args...)) |
265 | + case scriptrunner.CMDSmartctlValidate: |
266 | + out, errOut, err = runner.Smartctl(ctx, append([]string{ |
267 | + "--test", |
268 | + "validate", |
269 | + }, args...)) |
270 | + case scriptrunner.CMDStressNGCpuLong: |
271 | + out, errOut, err = runner.StressNGCpuLong(ctx, args) |
272 | + case scriptrunner.CMDStressNGCpuShort: |
273 | + out, errOut, err = runner.StressNGCpuShort(ctx, args) |
274 | + case scriptrunner.CMDStressNGMemLong: |
275 | + out, errOut, err = runner.StressNGMemLong(ctx, args) |
276 | + case scriptrunner.CMDStressNGMemShort: |
277 | + out, errOut, err = runner.StressNGMemShort(ctx, args) |
278 | + case "machine-resources": |
279 | + out, err = getMachineResources() |
280 | + case "40-maas-01-machine-resources": |
281 | + out, err = getMachineResources() |
282 | + default: |
283 | + // TODO handle custom scripts for commissioning |
284 | + err = fmt.Errorf("%s is an unknown command", cmd) |
285 | + } |
286 | + if err != nil { |
287 | + fmt.Printf("an error occured while executing '%s': %s\n", os.Args[1], err) |
288 | + return 1 |
289 | + } |
290 | + _, err = os.Stdout.Write(out) |
291 | + if err != nil { |
292 | + fmt.Printf("error writing output: %s\n", err) |
293 | + return 1 |
294 | + } |
295 | + _, err = os.Stderr.Write(errOut) |
296 | + if err != nil { |
297 | + fmt.Printf("error writing stderr: %s\n", err) |
298 | + return 1 |
299 | } |
300 | |
301 | - // TODO embed scripts and call out to maas_run_scripts |
302 | return 0 |
303 | } |
304 | |
305 | diff --git a/src/host-info/go.mod b/src/host-info/go.mod |
306 | index 4b414a4..544b842 100644 |
307 | --- a/src/host-info/go.mod |
308 | +++ b/src/host-info/go.mod |
309 | @@ -12,6 +12,7 @@ require ( |
310 | github.com/lxc/lxd v0.0.0-20210317085550-5d82899488db |
311 | github.com/pborman/uuid v1.2.1 // indirect |
312 | github.com/pkg/errors v0.9.1 // indirect |
313 | + github.com/rakyll/statik v0.1.7 |
314 | github.com/stretchr/testify v1.7.0 // indirect |
315 | golang.org/x/sys v0.0.0-20210317091845-390168757d9c // indirect |
316 | gopkg.in/robfig/cron.v2 v2.0.0-20150107220207-be2e0b0deed5 // indirect |
317 | diff --git a/src/host-info/go.sum b/src/host-info/go.sum |
318 | index 3b1758d..564b524 100644 |
319 | --- a/src/host-info/go.sum |
320 | +++ b/src/host-info/go.sum |
321 | @@ -29,6 +29,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= |
322 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= |
323 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
324 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
325 | +github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= |
326 | +github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= |
327 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
328 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= |
329 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= |
330 | diff --git a/src/host-info/pkg/scriptrunner/runner.go b/src/host-info/pkg/scriptrunner/runner.go |
331 | new file mode 100644 |
332 | index 0000000..040141d |
333 | --- /dev/null |
334 | +++ b/src/host-info/pkg/scriptrunner/runner.go |
335 | @@ -0,0 +1,494 @@ |
336 | +package scriptrunner |
337 | + |
338 | +import ( |
339 | + "bytes" |
340 | + "context" |
341 | + "fmt" |
342 | + "html/template" |
343 | + "io" |
344 | + "io/ioutil" |
345 | + "net/http" |
346 | + "os/exec" |
347 | + "strings" |
348 | + |
349 | + "github.com/rakyll/statik/fs" |
350 | + |
351 | + _ "host-info/pkg/scriptrunner/statik" |
352 | +) |
353 | + |
354 | +const ( |
355 | + EnvBin = "/usr/bin/env" |
356 | + BashBin = "/bin/bash" |
357 | + Python3BinName = "python3" |
358 | + |
359 | + MAASScriptsRunner = "/maas_run_scripts.py" |
360 | + MAASAPIHelper = "/maas_api_helper.py" |
361 | + BMCConfig = "/bmc_config.py" |
362 | + CaptureLLDPD = "/capture_lldpd.py" |
363 | + DHCPUnconfiguredIfaces = "/dhcp_unconfigured_ifaces.py" |
364 | + InstallLLDPD = "/install_lldpd.py" |
365 | + MAASGetFruidApiData = "/maas-get-fruid-api-data.sh" |
366 | + MAASKernelCMDLine = "/maas-kernel-cmdline.sh" |
367 | + MAASListModaliases = "/maas-list-modaliases.sh" |
368 | + MAASLSHW = "/maas-lshw.sh" |
369 | + MAASSerialPorts = "/maas-serial-ports.sh" |
370 | + MAASSupportInfo = "/maas-support-info.sh" |
371 | + MAASCommissioning = "/50-maas-01-commissioning.py" |
372 | + BadBlocks = "/badblocks.py" |
373 | + BaseConnectivity = "/base-connectivity.sh" |
374 | + Fio = "/fio.py" |
375 | + GatewayConnectivity = "/gateway-connectivity.sh" |
376 | + InternetConnectivity = "/internet-connectivity.sh" |
377 | + MemTester = "/memtester.sh" |
378 | + Ntp = "/ntp.sh" |
379 | + RackControllerConnectivity = "/rack-controller-connectivity.sh" |
380 | + SevenZ = "/seven_z.py" |
381 | + Smartctl = "/smartctl.py" |
382 | + StressNGCpuLong = "/stress-ng-cpu-long.sh" |
383 | + StressNGCpuShort = "/stress-ng-cpu-short.sh" |
384 | + StressNGMemLong = "/stress-ng-memory-long.sh" |
385 | + StressNGMemShort = "/stress-ng-memory-short.sh" |
386 | + |
387 | + CMDGetMachineToken = "get-machine-token" |
388 | + CMDRegisterMachine = "register-machine" |
389 | + CMDReportResults = "report-results" |
390 | + CMDBMCConfig = "30-maas-01-bmc-config" |
391 | + CMDCaptureLLDPD = "maas-capture-lldpd" |
392 | + CMDDHCPUnconfiguredIfaces = "20-maas-02-dhcp-unconfigured-ifaces" |
393 | + CMDInstallLLDPD = "20-maas-01-install-lldpd" |
394 | + CMDMAASGetFruidAPIData = "maas-get-fruid-api-data" |
395 | + CMDMAASKernelCMDLine = "maas-kernel-cmdline" |
396 | + CMDMAASListModaliases = "maas-list-modaliases" |
397 | + CMDMAASLSHW = "maas-lshw" |
398 | + CMDMAASSerialPorts = "maas-serial-ports" |
399 | + CMDMAASSupportInfo = "maas-support-info" |
400 | + CMDMAASCommissioning = "50-maas-01-commissioning" |
401 | + CMDBadBlocks = "badblocks" |
402 | + CMDBadBlocksDestructive = "badblocks-destructive" |
403 | + CMDFio = "fio" |
404 | + CMDGatewayConnectivity = "gateway-connectivity" |
405 | + CMDInternetConnectivity = "internet-connectivity" |
406 | + CMDMemTester = "memtester" |
407 | + CMDNtp = "ntp" |
408 | + CMDSevenZ = "7z" |
409 | + CMDRackControllerConnectivity = "rack-controller-connectivity" |
410 | + CMDSmartctlConveyance = "smartctl-conveyance" |
411 | + CMDSmartctlLong = "smartctl-long" |
412 | + CMDSmartctlShort = "smartctl-short" |
413 | + CMDSmartctlValidate = "smartctl-validate" |
414 | + CMDStressNGCpuLong = "stress-ng-cpu-long" |
415 | + CMDStressNGCpuShort = "stress-ng-cpu-short" |
416 | + CMDStressNGMemLong = "stress-ng-memory-long" |
417 | + CMDStressNGMemShort = "stress-ng-memory-short" |
418 | +) |
419 | + |
420 | +func ListCMDs() string { |
421 | + cmdList := []string{ |
422 | + CMDGetMachineToken, |
423 | + CMDRegisterMachine, |
424 | + CMDReportResults, |
425 | + CMDBMCConfig, |
426 | + CMDCaptureLLDPD, |
427 | + CMDDHCPUnconfiguredIfaces, |
428 | + CMDInstallLLDPD, |
429 | + CMDMAASGetFruidAPIData, |
430 | + CMDMAASKernelCMDLine, |
431 | + CMDMAASListModaliases, |
432 | + CMDMAASLSHW, |
433 | + CMDMAASSerialPorts, |
434 | + CMDMAASSupportInfo, |
435 | + CMDMAASCommissioning, |
436 | + CMDBadBlocks, |
437 | + CMDBadBlocksDestructive, |
438 | + CMDFio, |
439 | + CMDGatewayConnectivity, |
440 | + CMDInternetConnectivity, |
441 | + CMDMemTester, |
442 | + CMDNtp, |
443 | + CMDSevenZ, |
444 | + CMDRackControllerConnectivity, |
445 | + CMDSmartctlConveyance, |
446 | + CMDSmartctlLong, |
447 | + CMDSmartctlShort, |
448 | + CMDSmartctlValidate, |
449 | + CMDStressNGCpuLong, |
450 | + CMDStressNGCpuShort, |
451 | + CMDStressNGMemLong, |
452 | + CMDStressNGMemShort, |
453 | + } |
454 | + return strings.Join(cmdList, "\n") + "\n" |
455 | +} |
456 | + |
457 | +type ScriptRunner interface { |
458 | + Execute(ctx context.Context, args []string) ([]byte, []byte, error) |
459 | +} |
460 | + |
461 | +func pipeScriptToProcess(script io.Reader, stdin io.WriteCloser) error { |
462 | + defer func() { |
463 | + stdin.Close() |
464 | + if scriptCloser, ok := script.(io.ReadCloser); ok { |
465 | + scriptCloser.Close() |
466 | + } |
467 | + }() |
468 | + |
469 | + _, err := io.Copy(stdin, script) |
470 | + if err != nil { |
471 | + return fmt.Errorf("failed piping script to process: %w", err) |
472 | + } |
473 | + return nil |
474 | +} |
475 | + |
476 | +func pipeStd(std io.Reader, stdC chan []byte) error { |
477 | + defer close(stdC) |
478 | + |
479 | + var buf bytes.Buffer |
480 | + _, err := io.Copy(&buf, std) |
481 | + if err != nil { |
482 | + return fmt.Errorf("failed piping output to parent: %w", err) |
483 | + } |
484 | + stdC <- buf.Bytes() |
485 | + return nil |
486 | +} |
487 | + |
488 | +type BashRunner struct { |
489 | + Env []string |
490 | + Script io.Reader |
491 | +} |
492 | + |
493 | +func (b *BashRunner) Execute(ctx context.Context, args []string) ([]byte, []byte, error) { |
494 | + args = append([]string{"-s", "-"}, args...) |
495 | + cmd := exec.CommandContext(ctx, BashBin, args...) |
496 | + |
497 | + var key string |
498 | + for i, e := range b.Env { |
499 | + if i%2 == 0 { |
500 | + key = e |
501 | + } else { |
502 | + cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", key, e)) |
503 | + } |
504 | + } |
505 | + |
506 | + stdin, err := cmd.StdinPipe() |
507 | + if err != nil { |
508 | + return nil, nil, fmt.Errorf("failed to fetch stdin pipe: %w", err) |
509 | + } |
510 | + stdout, err := cmd.StdoutPipe() |
511 | + if err != nil { |
512 | + return nil, nil, fmt.Errorf("failed to fetch stdout pipe: %w", err) |
513 | + } |
514 | + stderr, err := cmd.StderrPipe() |
515 | + if err != nil { |
516 | + return nil, nil, fmt.Errorf("failed to fetch stderr pipe: %w", err) |
517 | + } |
518 | + |
519 | + go pipeScriptToProcess(b.Script, stdin) |
520 | + |
521 | + stdoutC := make(chan []byte) |
522 | + stderrC := make(chan []byte) |
523 | + go pipeStd(stdout, stdoutC) |
524 | + go pipeStd(stderr, stderrC) |
525 | + |
526 | + err = cmd.Run() |
527 | + out := <-stdoutC |
528 | + errOut := <-stderrC |
529 | + if err != nil { |
530 | + return nil, nil, fmt.Errorf( |
531 | + "failed to execute bash script: %w: %s\n----------------\n%s", |
532 | + err, |
533 | + string(out), |
534 | + string(errOut), |
535 | + ) |
536 | + } |
537 | + return out, errOut, nil |
538 | +} |
539 | + |
540 | +type Python3ScriptRunner struct { |
541 | + SubCommand string |
542 | + APIHelper io.ReadCloser |
543 | + Script io.Reader |
544 | +} |
545 | + |
546 | +func (p *Python3ScriptRunner) Execute(ctx context.Context, args []string) ([]byte, []byte, error) { |
547 | + var cmd *exec.Cmd |
548 | + if len(p.SubCommand) > 0 { |
549 | + cmdArgs := append([]string{Python3BinName, "-", p.SubCommand}, args...) |
550 | + cmd = exec.CommandContext(ctx, EnvBin, cmdArgs...) |
551 | + } else { |
552 | + cmdArgs := append([]string{Python3BinName, "-"}, args...) |
553 | + cmd = exec.CommandContext(ctx, EnvBin, cmdArgs...) |
554 | + } |
555 | + |
556 | + stdin, err := cmd.StdinPipe() |
557 | + if err != nil { |
558 | + return nil, nil, fmt.Errorf("failed to fetch stdin pipe: %w", err) |
559 | + } |
560 | + stdout, err := cmd.StdoutPipe() |
561 | + if err != nil { |
562 | + return nil, nil, fmt.Errorf("failed to fetch stdout pipe: %w", err) |
563 | + } |
564 | + stderr, err := cmd.StderrPipe() |
565 | + if err != nil { |
566 | + return nil, nil, fmt.Errorf("failed to fetch stderr pipe: %w", err) |
567 | + } |
568 | + |
569 | + go func() { |
570 | + defer func() { |
571 | + stdin.Close() |
572 | + if p.APIHelper != nil { |
573 | + p.APIHelper.Close() |
574 | + } |
575 | + if script, ok := p.Script.(io.ReadCloser); ok { |
576 | + script.Close() |
577 | + } |
578 | + }() |
579 | + |
580 | + if p.APIHelper != nil { |
581 | + io.Copy(stdin, p.APIHelper) |
582 | + } |
583 | + io.Copy(stdin, p.Script) |
584 | + }() |
585 | + |
586 | + stdoutC := make(chan []byte) |
587 | + stderrC := make(chan []byte) |
588 | + go pipeStd(stdout, stdoutC) |
589 | + go pipeStd(stderr, stderrC) |
590 | + |
591 | + err = cmd.Run() |
592 | + out := <-stdoutC |
593 | + errOut := <-stderrC |
594 | + if err != nil { |
595 | + return nil, nil, fmt.Errorf( |
596 | + "failed to execute python script: %w: %s\n----------------\n%s", |
597 | + err, |
598 | + string(out), |
599 | + string(errOut), |
600 | + ) |
601 | + } |
602 | + |
603 | + return out, errOut, nil |
604 | +} |
605 | + |
606 | +type runScriptTemplateOptions struct { |
607 | + ImportAPIHelper bool |
608 | +} |
609 | + |
610 | +type Runner struct { |
611 | + scripts http.FileSystem |
612 | +} |
613 | + |
614 | +func New() (*Runner, error) { |
615 | + content, err := fs.New() |
616 | + if err != nil { |
617 | + return nil, err |
618 | + } |
619 | + |
620 | + return &Runner{ |
621 | + scripts: content, |
622 | + }, nil |
623 | +} |
624 | + |
625 | +func (r *Runner) execMAASRunScripts(ctx context.Context, subcommand string, args []string) ([]byte, []byte, error) { |
626 | + script, err := r.scripts.Open(MAASScriptsRunner) |
627 | + if err != nil { |
628 | + return nil, nil, fmt.Errorf("failed to load script for '%s': %w", subcommand, err) |
629 | + } |
630 | + defer script.Close() |
631 | + tmpl := template.New("maas_run_scripts") |
632 | + scriptContents, err := ioutil.ReadAll(script) |
633 | + if err != nil { |
634 | + return nil, nil, err |
635 | + } |
636 | + tmpl, err = tmpl.Parse(string(scriptContents)) |
637 | + if err != nil { |
638 | + return nil, nil, err |
639 | + } |
640 | + var runScript bytes.Buffer |
641 | + err = tmpl.Execute(&runScript, &runScriptTemplateOptions{ImportAPIHelper: false}) |
642 | + if err != nil { |
643 | + return nil, nil, err |
644 | + } |
645 | + apiHelper, err := r.scripts.Open(MAASAPIHelper) |
646 | + if err != nil { |
647 | + return nil, nil, fmt.Errorf("failed to load maas_api_helper: %w", err) |
648 | + } |
649 | + |
650 | + py := &Python3ScriptRunner{ |
651 | + SubCommand: subcommand, |
652 | + APIHelper: apiHelper, |
653 | + Script: &runScript, |
654 | + } |
655 | + return py.Execute(ctx, append([]string{"--local-scripts"}, args...)) |
656 | +} |
657 | + |
658 | +func (r *Runner) basicPython3Script(ctx context.Context, script string, args []string) ([]byte, []byte, error) { |
659 | + content, err := r.scripts.Open(script) |
660 | + if err != nil { |
661 | + return nil, nil, fmt.Errorf("failed loading %s: %w", script, err) |
662 | + } |
663 | + |
664 | + py := &Python3ScriptRunner{ |
665 | + Script: content, |
666 | + } |
667 | + return py.Execute(ctx, args) |
668 | +} |
669 | + |
670 | +func (r *Runner) basicBashScript(ctx context.Context, script string, args []string) ([]byte, []byte, error) { |
671 | + content, err := r.scripts.Open(script) |
672 | + if err != nil { |
673 | + return nil, nil, fmt.Errorf("failed loading %s: %w", script, err) |
674 | + } |
675 | + |
676 | + sh := &BashRunner{ |
677 | + Script: content, |
678 | + } |
679 | + return sh.Execute(ctx, args) |
680 | +} |
681 | + |
682 | +type connectivityHelper struct { |
683 | + UseGoTemplate bool |
684 | + Template string |
685 | +} |
686 | + |
687 | +func (r *Runner) connectivityTest(ctx context.Context, script string, args []string) ([]byte, []byte, error) { |
688 | + baseScript, err := r.scripts.Open(BaseConnectivity) |
689 | + if err != nil { |
690 | + return nil, nil, err |
691 | + } |
692 | + baseContent, err := ioutil.ReadAll(baseScript) |
693 | + if err != nil { |
694 | + return nil, nil, err |
695 | + } |
696 | + connScript, err := r.scripts.Open(script) |
697 | + if err != nil { |
698 | + return nil, nil, err |
699 | + } |
700 | + connContent, err := ioutil.ReadAll(connScript) |
701 | + tmpl := template.New("maas_connectivity") |
702 | + tmpl, err = tmpl.Parse(string(connContent)) |
703 | + if err != nil { |
704 | + return nil, nil, err |
705 | + } |
706 | + var connTest bytes.Buffer |
707 | + err = tmpl.Execute( |
708 | + &connTest, |
709 | + connectivityHelper{ |
710 | + UseGoTemplate: true, |
711 | + Template: string(baseContent), |
712 | + }, |
713 | + ) |
714 | + if err != nil { |
715 | + return nil, nil, err |
716 | + } |
717 | + sh := &BashRunner{ |
718 | + Script: &connTest, |
719 | + } |
720 | + return sh.Execute(ctx, nil) |
721 | +} |
722 | + |
723 | +func (r *Runner) GetMachineToken(ctx context.Context, args []string) ([]byte, []byte, error) { |
724 | + return r.execMAASRunScripts(ctx, CMDGetMachineToken, args) |
725 | +} |
726 | + |
727 | +func (r *Runner) RegisterMachine(ctx context.Context, args []string) ([]byte, []byte, error) { |
728 | + return r.execMAASRunScripts(ctx, CMDRegisterMachine, args) |
729 | +} |
730 | + |
731 | +func (r *Runner) ReportResults(ctx context.Context, args []string) ([]byte, []byte, error) { |
732 | + return r.execMAASRunScripts(ctx, CMDReportResults, args) |
733 | +} |
734 | + |
735 | +func (r *Runner) BMCConfig(ctx context.Context, args []string) ([]byte, []byte, error) { |
736 | + return r.basicPython3Script(ctx, BMCConfig, args) |
737 | +} |
738 | + |
739 | +func (r *Runner) CaptureLLDPD(ctx context.Context, args []string) ([]byte, []byte, error) { |
740 | + return r.basicPython3Script(ctx, CaptureLLDPD, args) |
741 | +} |
742 | + |
743 | +func (r *Runner) DHCPUnconfiguredIfaces(ctx context.Context, args []string) ([]byte, []byte, error) { |
744 | + return r.basicPython3Script(ctx, DHCPUnconfiguredIfaces, args) |
745 | +} |
746 | + |
747 | +func (r *Runner) InstallLLDPD(ctx context.Context, args []string) ([]byte, []byte, error) { |
748 | + return r.basicPython3Script(ctx, InstallLLDPD, args) |
749 | +} |
750 | + |
751 | +func (r *Runner) MAASGetFruidAPIData(ctx context.Context, args []string) ([]byte, []byte, error) { |
752 | + return r.basicBashScript(ctx, MAASGetFruidApiData, args) |
753 | +} |
754 | + |
755 | +func (r *Runner) MAASKernelCMDLine(ctx context.Context, args []string) ([]byte, []byte, error) { |
756 | + return r.basicBashScript(ctx, MAASKernelCMDLine, args) |
757 | +} |
758 | + |
759 | +func (r *Runner) MAASListModaliases(ctx context.Context, args []string) ([]byte, []byte, error) { |
760 | + return r.basicBashScript(ctx, MAASListModaliases, args) |
761 | +} |
762 | + |
763 | +func (r *Runner) MAASLshw(ctx context.Context, args []string) ([]byte, []byte, error) { |
764 | + return r.basicBashScript(ctx, MAASLSHW, args) |
765 | +} |
766 | + |
767 | +func (r *Runner) MAASSerialPorts(ctx context.Context, args []string) ([]byte, []byte, error) { |
768 | + return r.basicBashScript(ctx, MAASSerialPorts, args) |
769 | +} |
770 | + |
771 | +func (r *Runner) MAASSupportInfo(ctx context.Context, args []string) ([]byte, []byte, error) { |
772 | + return r.basicBashScript(ctx, MAASSupportInfo, args) |
773 | +} |
774 | + |
775 | +func (r *Runner) MAASCommissioning(ctx context.Context, args []string) ([]byte, []byte, error) { |
776 | + return r.basicPython3Script(ctx, MAASCommissioning, args) |
777 | +} |
778 | + |
779 | +func (r *Runner) BadBlocks(ctx context.Context, args []string) ([]byte, []byte, error) { |
780 | + return r.basicPython3Script(ctx, BadBlocks, args) |
781 | +} |
782 | + |
783 | +func (r *Runner) Fio(ctx context.Context, args []string) ([]byte, []byte, error) { |
784 | + return r.basicPython3Script(ctx, Fio, args) |
785 | +} |
786 | + |
787 | +func (r *Runner) GatewayConnectivity(ctx context.Context, args []string) ([]byte, []byte, error) { |
788 | + return r.connectivityTest(ctx, GatewayConnectivity, args) |
789 | +} |
790 | + |
791 | +func (r *Runner) InternetConnectivity(ctx context.Context, args []string) ([]byte, []byte, error) { |
792 | + return r.connectivityTest(ctx, InternetConnectivity, args) |
793 | +} |
794 | + |
795 | +func (r *Runner) MemTester(ctx context.Context, args []string) ([]byte, []byte, error) { |
796 | + return r.basicBashScript(ctx, MemTester, args) |
797 | +} |
798 | + |
799 | +func (r *Runner) Ntp(ctx context.Context, args []string) ([]byte, []byte, error) { |
800 | + return r.basicBashScript(ctx, Ntp, args) |
801 | +} |
802 | + |
803 | +func (r *Runner) RackControllerConnectivity(ctx context.Context, args []string) ([]byte, []byte, error) { |
804 | + return r.connectivityTest(ctx, RackControllerConnectivity, args) |
805 | +} |
806 | + |
807 | +func (r *Runner) SevenZ(ctx context.Context, args []string) ([]byte, []byte, error) { |
808 | + return r.basicPython3Script(ctx, SevenZ, args) |
809 | +} |
810 | + |
811 | +func (r *Runner) Smartctl(ctx context.Context, args []string) ([]byte, []byte, error) { |
812 | + return r.basicPython3Script(ctx, Smartctl, args) |
813 | +} |
814 | + |
815 | +func (r *Runner) StressNGCpuLong(ctx context.Context, args []string) ([]byte, []byte, error) { |
816 | + return r.basicBashScript(ctx, StressNGCpuLong, args) |
817 | +} |
818 | + |
819 | +func (r *Runner) StressNGCpuShort(ctx context.Context, args []string) ([]byte, []byte, error) { |
820 | + return r.basicBashScript(ctx, StressNGCpuShort, args) |
821 | +} |
822 | + |
823 | +func (r *Runner) StressNGMemLong(ctx context.Context, args []string) ([]byte, []byte, error) { |
824 | + return r.basicBashScript(ctx, StressNGMemLong, args) |
825 | +} |
826 | + |
827 | +func (r *Runner) StressNGMemShort(ctx context.Context, args []string) ([]byte, []byte, error) { |
828 | + return r.basicBashScript(ctx, StressNGMemShort, args) |
829 | +} |
830 | diff --git a/src/host-info/pkg/scriptrunner/runner_test.go b/src/host-info/pkg/scriptrunner/runner_test.go |
831 | new file mode 100644 |
832 | index 0000000..f582817 |
833 | --- /dev/null |
834 | +++ b/src/host-info/pkg/scriptrunner/runner_test.go |
835 | @@ -0,0 +1,164 @@ |
836 | +package scriptrunner |
837 | + |
838 | +import ( |
839 | + "bytes" |
840 | + "context" |
841 | + "os" |
842 | + "strings" |
843 | + "testing" |
844 | +) |
845 | + |
846 | +const bashPrintInput = `#!/bin/bash |
847 | +env |
848 | +echo $@ |
849 | +` |
850 | + |
851 | +const bashExitOne = `#!/bin/bash |
852 | +echo "error message" && exit 1 |
853 | +` |
854 | + |
855 | +const python3PrintInput = `import os |
856 | +import sys |
857 | + |
858 | +print(os.environ.items()) |
859 | +print("\n".join(sys.argv)) |
860 | +` |
861 | + |
862 | +const python3ExitOne = `import os |
863 | + |
864 | +print("error message") |
865 | +os.exit(1) |
866 | +` |
867 | + |
868 | +func TestBashRunner(t *testing.T) { |
869 | + table := []struct { |
870 | + Name string |
871 | + Env []string |
872 | + Args []string |
873 | + }{ |
874 | + { |
875 | + Name: "just_command_cleanly_executes", |
876 | + }, |
877 | + { |
878 | + Name: "command_with_args", |
879 | + Args: []string{"--arg", "val"}, |
880 | + }, |
881 | + { |
882 | + Name: "command_with_env", |
883 | + Env: []string{"ENV_VAR", "val"}, |
884 | + }, |
885 | + { |
886 | + Name: "command_with_args_and_env", |
887 | + Env: []string{"ENV_VAR", "val"}, |
888 | + Args: []string{"--args", "val"}, |
889 | + }, |
890 | + } |
891 | + |
892 | + for _, tcase := range table { |
893 | + t.Run(tcase.Name, func(tt *testing.T) { |
894 | + runner := &BashRunner{ |
895 | + Env: tcase.Env, |
896 | + Script: bytes.NewBuffer([]byte(bashPrintInput)), |
897 | + } |
898 | + stdout, _, err := runner.Execute(context.Background(), tcase.Args) |
899 | + if err != nil { |
900 | + tt.Fatal(err) |
901 | + } |
902 | + for _, envvar := range tcase.Env { |
903 | + if !strings.Contains(string(stdout), envvar) { |
904 | + tt.Errorf("%s does not contain %s", stdout, envvar) |
905 | + } |
906 | + } |
907 | + for _, arg := range tcase.Args { |
908 | + if !strings.Contains(string(stdout), arg) { |
909 | + tt.Errorf("%s does not contain %s", stdout, arg) |
910 | + } |
911 | + } |
912 | + }) |
913 | + } |
914 | +} |
915 | + |
916 | +func TestBashRunnerInheritsParentEnv(t *testing.T) { |
917 | + os.Setenv("TEST_VAR", "test_val") |
918 | + defer os.Setenv("TEST_VAR", "") |
919 | + runner := &BashRunner{ |
920 | + Script: bytes.NewBuffer([]byte(bashPrintInput)), |
921 | + } |
922 | + stdout, _, err := runner.Execute(context.Background(), nil) |
923 | + if err != nil { |
924 | + t.Fatal(err) |
925 | + } |
926 | + if !strings.Contains(string(stdout), "TEST_VAR=test_val") { |
927 | + t.Fatalf("%s does not contain TEST_VAR=test_val", stdout) |
928 | + } |
929 | +} |
930 | + |
931 | +func TestBashRunnerReturnsErrorWhenScriptExitsNon0(t *testing.T) { |
932 | + runner := &BashRunner{ |
933 | + Script: bytes.NewBuffer([]byte(bashExitOne)), |
934 | + } |
935 | + _, _, err := runner.Execute(context.Background(), nil) |
936 | + if err == nil { |
937 | + t.Fatal("expected an error to be returned") |
938 | + } |
939 | +} |
940 | + |
941 | +func TestPython3ScriptRunner(t *testing.T) { |
942 | + table := []struct { |
943 | + Name string |
944 | + Args []string |
945 | + }{ |
946 | + { |
947 | + Name: "just_command_cleanly_execute", |
948 | + }, |
949 | + { |
950 | + Name: "command_with_args", |
951 | + Args: []string{"--args", "val"}, |
952 | + }, |
953 | + } |
954 | + for _, tcase := range table { |
955 | + t.Run(tcase.Name, func(tt *testing.T) { |
956 | + runner := &Python3ScriptRunner{ |
957 | + Script: bytes.NewBuffer([]byte(python3PrintInput)), |
958 | + } |
959 | + stdout, _, err := runner.Execute(context.Background(), tcase.Args) |
960 | + if err != nil { |
961 | + tt.Fatal(err) |
962 | + } |
963 | + for _, arg := range tcase.Args { |
964 | + if !strings.Contains(string(stdout), arg) { |
965 | + tt.Errorf("%s does not contain %s", stdout, arg) |
966 | + } |
967 | + } |
968 | + }) |
969 | + } |
970 | + |
971 | +} |
972 | + |
973 | +func TestPython3RunnerInheritsParentEnv(t *testing.T) { |
974 | + os.Setenv("TEST_VAR", "test_val") |
975 | + defer os.Setenv("TEST_VAR", "") |
976 | + runner := &Python3ScriptRunner{ |
977 | + Script: bytes.NewBuffer([]byte(python3PrintInput)), |
978 | + } |
979 | + stdout, _, err := runner.Execute(context.Background(), nil) |
980 | + if err != nil { |
981 | + t.Fatal(err) |
982 | + } |
983 | + if !strings.Contains(string(stdout), "TEST_VAR") { |
984 | + t.Fatalf("%s does not contain TEST_VAR", stdout) |
985 | + } |
986 | + if !strings.Contains(string(stdout), "test_val") { |
987 | + t.Fatalf("%s does not contain test_val", stdout) |
988 | + } |
989 | +} |
990 | + |
991 | +func TestPython3RunnerReturnsErrorWhenScriptExitsNon0(t *testing.T) { |
992 | + runner := &Python3ScriptRunner{ |
993 | + Script: bytes.NewBuffer([]byte(python3ExitOne)), |
994 | + } |
995 | + _, _, err := runner.Execute(context.Background(), nil) |
996 | + if err == nil { |
997 | + t.Fatal("expected an error to be returned") |
998 | + } |
999 | +} |
1000 | diff --git a/src/host-info/scripts/50-maas-01-commissioning.py b/src/host-info/scripts/50-maas-01-commissioning.py |
1001 | new file mode 120000 |
1002 | index 0000000..aa47949 |
1003 | --- /dev/null |
1004 | +++ b/src/host-info/scripts/50-maas-01-commissioning.py |
1005 | @@ -0,0 +1 @@ |
1006 | +/home/christian/maas/src/provisioningserver/refresh/50-maas-01-commissioning |
1007 | \ No newline at end of file |
1008 | diff --git a/src/host-info/scripts/badblocks.py b/src/host-info/scripts/badblocks.py |
1009 | new file mode 120000 |
1010 | index 0000000..1386716 |
1011 | --- /dev/null |
1012 | +++ b/src/host-info/scripts/badblocks.py |
1013 | @@ -0,0 +1 @@ |
1014 | +/home/christian/maas/src/metadataserver/builtin_scripts/testing_scripts/badblocks.py |
1015 | \ No newline at end of file |
1016 | diff --git a/src/host-info/scripts/base-connectivity.sh b/src/host-info/scripts/base-connectivity.sh |
1017 | new file mode 120000 |
1018 | index 0000000..ef91a26 |
1019 | --- /dev/null |
1020 | +++ b/src/host-info/scripts/base-connectivity.sh |
1021 | @@ -0,0 +1 @@ |
1022 | +/home/christian/maas/src/metadataserver/builtin_scripts/testing_scripts/base-connectivity.sh |
1023 | \ No newline at end of file |
1024 | diff --git a/src/host-info/scripts/bmc_config.py b/src/host-info/scripts/bmc_config.py |
1025 | new file mode 120000 |
1026 | index 0000000..eb6352e |
1027 | --- /dev/null |
1028 | +++ b/src/host-info/scripts/bmc_config.py |
1029 | @@ -0,0 +1 @@ |
1030 | +/home/christian/maas/src/metadataserver/builtin_scripts/commissioning_scripts/bmc_config.py |
1031 | \ No newline at end of file |
1032 | diff --git a/src/host-info/scripts/capture_lldpd.py b/src/host-info/scripts/capture_lldpd.py |
1033 | new file mode 120000 |
1034 | index 0000000..44218a5 |
1035 | --- /dev/null |
1036 | +++ b/src/host-info/scripts/capture_lldpd.py |
1037 | @@ -0,0 +1 @@ |
1038 | +/home/christian/maas/src/metadataserver/builtin_scripts/commissioning_scripts/capture_lldpd.py |
1039 | \ No newline at end of file |
1040 | diff --git a/src/host-info/scripts/dhcp_unconfigured_ifaces.py b/src/host-info/scripts/dhcp_unconfigured_ifaces.py |
1041 | new file mode 120000 |
1042 | index 0000000..7510086 |
1043 | --- /dev/null |
1044 | +++ b/src/host-info/scripts/dhcp_unconfigured_ifaces.py |
1045 | @@ -0,0 +1 @@ |
1046 | +/home/christian/maas/src/metadataserver/builtin_scripts/commissioning_scripts/dhcp_unconfigured_ifaces.py |
1047 | \ No newline at end of file |
1048 | diff --git a/src/host-info/scripts/fio.py b/src/host-info/scripts/fio.py |
1049 | new file mode 120000 |
1050 | index 0000000..e1ad126 |
1051 | --- /dev/null |
1052 | +++ b/src/host-info/scripts/fio.py |
1053 | @@ -0,0 +1 @@ |
1054 | +/home/christian/maas/src/metadataserver/builtin_scripts/testing_scripts/fio.py |
1055 | \ No newline at end of file |
1056 | diff --git a/src/host-info/scripts/gateway-connectivity.sh b/src/host-info/scripts/gateway-connectivity.sh |
1057 | new file mode 100644 |
1058 | index 0000000..25cd583 |
1059 | --- /dev/null |
1060 | +++ b/src/host-info/scripts/gateway-connectivity.sh |
1061 | @@ -0,0 +1,115 @@ |
1062 | +#!/bin/bash |
1063 | +# |
1064 | +# gateway-connectivity - Check if an interface has access to the configured gateway. |
1065 | +# |
1066 | +# Author: Lee Trager <lee.trager@canonical.com> |
1067 | +# |
1068 | +# Copyright (C) 2019-2022 Canonical |
1069 | +# |
1070 | +# This program is free software: you can redistribute it and/or modify |
1071 | +# it under the terms of the GNU Affero General Public License as |
1072 | +# published by the Free Software Foundation, either version 3 of the |
1073 | +# License, or (at your option) any later version. |
1074 | +# |
1075 | +# This program is distributed in the hope that it will be useful, |
1076 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1077 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1078 | +# GNU Affero General Public License for more details. |
1079 | +# |
1080 | +# You should have received a copy of the GNU Affero General Public License |
1081 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1082 | +# |
1083 | +# --- Start MAAS 1.0 script metadata --- |
1084 | +# name: gateway-connectivity |
1085 | +# title: Gateway Connectivity Validation |
1086 | +# description: Check if an interface has access to the configured gateway. |
1087 | +# tags: |
1088 | +# - network-validation |
1089 | +# script_type: test |
1090 | +# hardware_type: network |
1091 | +# parallel: instance |
1092 | +# parameters: |
1093 | +# interface: |
1094 | +# type: interface |
1095 | +# apply_configured_networking: True |
1096 | +# timeout: 00:05:00 |
1097 | +# --- End MAAS 1.0 script metadata --- |
1098 | + |
1099 | +{{.InjectFile}} |
1100 | + |
1101 | +OPTS=$(getopt -o 'i:h' --long 'interface:,help' -n 'gateway-connectivity' -- "$@") |
1102 | + |
1103 | +if [ $? -ne 0 ]; then |
1104 | + exit 1 |
1105 | +fi |
1106 | + |
1107 | +eval set -- "$OPTS" |
1108 | +unset OPTS |
1109 | + |
1110 | +while true; do |
1111 | + case "$1" in |
1112 | + '-i'|'--interface') |
1113 | + INTERFACE=$2 |
1114 | + shift 2 |
1115 | + continue |
1116 | + ;; |
1117 | + '-h'|'--help') |
1118 | + echo "usage: gateway-connectivity [--interface INTERFACE]" |
1119 | + echo |
1120 | + echo "Check if an interface has access to the configured gateway." |
1121 | + echo |
1122 | + echo "optional arguments:" |
1123 | + echo " -h, --help Show this message" |
1124 | + echo " -i, --interface The interface to test gateway connectivity with." |
1125 | + echo " Default: Any interface" |
1126 | + exit 0 |
1127 | + ;; |
1128 | + '--') |
1129 | + shift |
1130 | + break |
1131 | + ;; |
1132 | + *) |
1133 | + echo "Unknown argument $1!" >&2 |
1134 | + exit 1 |
1135 | + esac |
1136 | +done |
1137 | + |
1138 | + |
1139 | +if [ -n "$INTERFACE" ]; then |
1140 | + IP_ARGS="dev $INTERFACE" |
1141 | +fi |
1142 | + |
1143 | +AWK_SCRIPT=' |
1144 | +{ |
1145 | + for(i = 0; i < NF; i++) { |
1146 | + if($i == "via") { |
1147 | + print $(i + 1); |
1148 | + break; |
1149 | + } |
1150 | + } |
1151 | +} |
1152 | +' |
1153 | + |
1154 | +for gateway in $(ip route show $IP_ARGS | awk "$AWK_SCRIPT"); do |
1155 | + if [ -n "$gateways" ]; then |
1156 | + gateways="$gateways,$gateway" |
1157 | + else |
1158 | + gateways="$gateway" |
1159 | + fi |
1160 | +done |
1161 | + |
1162 | +for gateway in $(ip -6 route show $IP_ARGS | awk "$AWK_SCRIPT"); do |
1163 | + if [ -n "$gateways" ]; then |
1164 | + gateways="$gateways,$gateway" |
1165 | + else |
1166 | + gateways="$gateway" |
1167 | + fi |
1168 | +done |
1169 | + |
1170 | +if [ -z "$gateways" ]; then |
1171 | + echo "WARNING: No gateways configured, skipping test" |
1172 | + [ -n "$RESULT_PATH" ] && echo "{status: skipped}" >> $RESULT_PATH |
1173 | + exit 0 |
1174 | +fi |
1175 | + |
1176 | +test_interface "$INTERFACE" "$gateways" |
1177 | diff --git a/src/host-info/scripts/install_lldpd.py b/src/host-info/scripts/install_lldpd.py |
1178 | new file mode 120000 |
1179 | index 0000000..9d76c0b |
1180 | --- /dev/null |
1181 | +++ b/src/host-info/scripts/install_lldpd.py |
1182 | @@ -0,0 +1 @@ |
1183 | +/home/christian/maas/src/metadataserver/builtin_scripts/commissioning_scripts/install_lldpd.py |
1184 | \ No newline at end of file |
1185 | diff --git a/src/host-info/scripts/internet-connectivity.sh b/src/host-info/scripts/internet-connectivity.sh |
1186 | new file mode 100644 |
1187 | index 0000000..bcfd95b |
1188 | --- /dev/null |
1189 | +++ b/src/host-info/scripts/internet-connectivity.sh |
1190 | @@ -0,0 +1,98 @@ |
1191 | +#!/bin/bash |
1192 | +# |
1193 | +# internet-connectivity - Check if an interface can access the specified URL(s). |
1194 | +# |
1195 | +# Author: Lee Trager <lee.trager@canonical.com> |
1196 | +# |
1197 | +# Copyright (C) 2017-2019 Canonical |
1198 | +# |
1199 | +# This program is free software: you can redistribute it and/or modify |
1200 | +# it under the terms of the GNU Affero General Public License as |
1201 | +# published by the Free Software Foundation, either version 3 of the |
1202 | +# License, or (at your option) any later version. |
1203 | +# |
1204 | +# This program is distributed in the hope that it will be useful, |
1205 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1206 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1207 | +# GNU Affero General Public License for more details. |
1208 | +# |
1209 | +# You should have received a copy of the GNU Affero General Public License |
1210 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1211 | +# |
1212 | +# --- Start MAAS 1.0 script metadata --- |
1213 | +# name: internet-connectivity |
1214 | +# title: Internet Connectivity Validation |
1215 | +# description: Check if an interface can access the specified URL(s). |
1216 | +# tags: |
1217 | +# - internet |
1218 | +# - network-validation |
1219 | +# script_type: test |
1220 | +# hardware_type: network |
1221 | +# parallel: instance |
1222 | +# parameters: |
1223 | +# interface: |
1224 | +# type: interface |
1225 | +# url: |
1226 | +# type: url |
1227 | +# description: A comma separated list of URLs, IPs, or domains to test if |
1228 | +# all the interfaces have access to. Any protocol supported |
1229 | +# by curl is supported. If no protocol or icmp is given the |
1230 | +# URL will be pinged. |
1231 | +# default: https://connectivity-check.ubuntu.com |
1232 | +# required: True |
1233 | +# allow_list: True |
1234 | +# apply_configured_networking: True |
1235 | +# timeout: 00:05:00 |
1236 | +# --- End MAAS 1.0 script metadata --- |
1237 | + |
1238 | +{{.InjectFile}} |
1239 | + |
1240 | +OPTS=$(getopt -o 'i:u:h' --long 'interface:,url:,help' -n 'internet-connectivity' -- "$@") |
1241 | + |
1242 | +if [ $? -ne 0 ]; then |
1243 | + exit 1 |
1244 | +fi |
1245 | + |
1246 | +eval set -- "$OPTS" |
1247 | +unset OPTS |
1248 | + |
1249 | +while true; do |
1250 | + case "$1" in |
1251 | + '-i'|'--interface') |
1252 | + INTERFACE=$2 |
1253 | + shift 2 |
1254 | + continue |
1255 | + ;; |
1256 | + '-u'|'--url') |
1257 | + URL=$2 |
1258 | + shift 2 |
1259 | + continue |
1260 | + ;; |
1261 | + '-h'|'--help') |
1262 | + echo "usage: internet-connectivity [--interface INTERFACE] [--url URL]" |
1263 | + echo |
1264 | + echo "Check if an interface can access the specified URL(s)." |
1265 | + echo |
1266 | + echo "optional arguments:" |
1267 | + echo " -h, --help Show this message" |
1268 | + echo " -i, --interface The interface to test internet connectivity with." |
1269 | + echo " Default: Any interface" |
1270 | + echo " -u, --url A URL or comma seperated list of URLs that should be accessible." |
1271 | + echo " Default: https://connectivity-check.ubuntu.com" |
1272 | + exit 0 |
1273 | + ;; |
1274 | + '--') |
1275 | + shift |
1276 | + break |
1277 | + ;; |
1278 | + *) |
1279 | + echo "Unknown argument $1!" >&2 |
1280 | + exit 1 |
1281 | + esac |
1282 | +done |
1283 | + |
1284 | +if [ -z "$URL" ]; then |
1285 | + URL="https://connectivity-check.ubuntu.com" |
1286 | +fi |
1287 | + |
1288 | +test_interface "$INTERFACE" "$URL" |
1289 | diff --git a/src/host-info/scripts/maas-get-fruid-api-data.sh b/src/host-info/scripts/maas-get-fruid-api-data.sh |
1290 | new file mode 120000 |
1291 | index 0000000..a142ee1 |
1292 | --- /dev/null |
1293 | +++ b/src/host-info/scripts/maas-get-fruid-api-data.sh |
1294 | @@ -0,0 +1 @@ |
1295 | +/home/christian/maas/src/metadataserver/builtin_scripts/commissioning_scripts/maas-get-fruid-api-data |
1296 | \ No newline at end of file |
1297 | diff --git a/src/host-info/scripts/maas-kernel-cmdline.sh b/src/host-info/scripts/maas-kernel-cmdline.sh |
1298 | new file mode 120000 |
1299 | index 0000000..8660214 |
1300 | --- /dev/null |
1301 | +++ b/src/host-info/scripts/maas-kernel-cmdline.sh |
1302 | @@ -0,0 +1 @@ |
1303 | +/home/christian/maas/src/metadataserver/builtin_scripts/commissioning_scripts/maas-kernel-cmdline |
1304 | \ No newline at end of file |
1305 | diff --git a/src/host-info/scripts/maas-list-modaliases.sh b/src/host-info/scripts/maas-list-modaliases.sh |
1306 | new file mode 120000 |
1307 | index 0000000..c11273f |
1308 | --- /dev/null |
1309 | +++ b/src/host-info/scripts/maas-list-modaliases.sh |
1310 | @@ -0,0 +1 @@ |
1311 | +/home/christian/maas/src/provisioningserver/refresh/maas-list-modaliases |
1312 | \ No newline at end of file |
1313 | diff --git a/src/host-info/scripts/maas-lshw.sh b/src/host-info/scripts/maas-lshw.sh |
1314 | new file mode 120000 |
1315 | index 0000000..6d7fd41 |
1316 | --- /dev/null |
1317 | +++ b/src/host-info/scripts/maas-lshw.sh |
1318 | @@ -0,0 +1 @@ |
1319 | +/home/christian/maas/src/provisioningserver/refresh/maas-lshw |
1320 | \ No newline at end of file |
1321 | diff --git a/src/host-info/scripts/maas-serial-ports.sh b/src/host-info/scripts/maas-serial-ports.sh |
1322 | new file mode 120000 |
1323 | index 0000000..593de7b |
1324 | --- /dev/null |
1325 | +++ b/src/host-info/scripts/maas-serial-ports.sh |
1326 | @@ -0,0 +1 @@ |
1327 | +/home/christian/maas/src/provisioningserver/refresh/maas-serial-ports |
1328 | \ No newline at end of file |
1329 | diff --git a/src/host-info/scripts/maas-support-info.sh b/src/host-info/scripts/maas-support-info.sh |
1330 | new file mode 120000 |
1331 | index 0000000..435a4b7 |
1332 | --- /dev/null |
1333 | +++ b/src/host-info/scripts/maas-support-info.sh |
1334 | @@ -0,0 +1 @@ |
1335 | +/home/christian/maas/src/provisioningserver/refresh/maas-support-info |
1336 | \ No newline at end of file |
1337 | diff --git a/src/host-info/scripts/maas_api_helper.py b/src/host-info/scripts/maas_api_helper.py |
1338 | new file mode 120000 |
1339 | index 0000000..7763d27 |
1340 | --- /dev/null |
1341 | +++ b/src/host-info/scripts/maas_api_helper.py |
1342 | @@ -0,0 +1 @@ |
1343 | +/home/christian/maas/src/provisioningserver/refresh/maas_api_helper.py |
1344 | \ No newline at end of file |
1345 | diff --git a/src/host-info/scripts/maas_run_scripts.py b/src/host-info/scripts/maas_run_scripts.py |
1346 | new file mode 120000 |
1347 | index 0000000..699c33e |
1348 | --- /dev/null |
1349 | +++ b/src/host-info/scripts/maas_run_scripts.py |
1350 | @@ -0,0 +1 @@ |
1351 | +/home/christian/maas/src/metadataserver/user_data/templates/snippets/maas_run_scripts.py |
1352 | \ No newline at end of file |
1353 | diff --git a/src/host-info/scripts/memtester.sh b/src/host-info/scripts/memtester.sh |
1354 | new file mode 120000 |
1355 | index 0000000..df3fe25 |
1356 | --- /dev/null |
1357 | +++ b/src/host-info/scripts/memtester.sh |
1358 | @@ -0,0 +1 @@ |
1359 | +/home/christian/maas/src/metadataserver/builtin_scripts/testing_scripts/memtester.sh |
1360 | \ No newline at end of file |
1361 | diff --git a/src/host-info/scripts/ntp.sh b/src/host-info/scripts/ntp.sh |
1362 | new file mode 120000 |
1363 | index 0000000..5378686 |
1364 | --- /dev/null |
1365 | +++ b/src/host-info/scripts/ntp.sh |
1366 | @@ -0,0 +1 @@ |
1367 | +/home/christian/maas/src/metadataserver/builtin_scripts/testing_scripts/ntp.sh |
1368 | \ No newline at end of file |
1369 | diff --git a/src/host-info/scripts/rack-controller-connectivity.sh b/src/host-info/scripts/rack-controller-connectivity.sh |
1370 | new file mode 100644 |
1371 | index 0000000..b9ab9b3 |
1372 | --- /dev/null |
1373 | +++ b/src/host-info/scripts/rack-controller-connectivity.sh |
1374 | @@ -0,0 +1,99 @@ |
1375 | +#!/bin/bash |
1376 | +# |
1377 | +# rack-controller-connectivity - Check if an interface has access to the booted rack controller. |
1378 | +# |
1379 | +# Author: Lee Trager <lee.trager@canonical.com> |
1380 | +# |
1381 | +# Copyright (C) 2019 Canonical |
1382 | +# |
1383 | +# This program is free software: you can redistribute it and/or modify |
1384 | +# it under the terms of the GNU Affero General Public License as |
1385 | +# published by the Free Software Foundation, either version 3 of the |
1386 | +# License, or (at your option) any later version. |
1387 | +# |
1388 | +# This program is distributed in the hope that it will be useful, |
1389 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1390 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1391 | +# GNU Affero General Public License for more details. |
1392 | +# |
1393 | +# You should have received a copy of the GNU Affero General Public License |
1394 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1395 | +# |
1396 | +# --- Start MAAS 1.0 script metadata --- |
1397 | +# name: rack-controller-connectivity |
1398 | +# title: Gateway Connectivity Validation |
1399 | +# description: Check if an interface has access to the booted rack controller. |
1400 | +# tags: |
1401 | +# - network-validation |
1402 | +# script_type: test |
1403 | +# hardware_type: network |
1404 | +# parallel: instance |
1405 | +# parameters: |
1406 | +# interface: |
1407 | +# type: interface |
1408 | +# apply_configured_networking: True |
1409 | +# timeout: 00:05:00 |
1410 | +# --- End MAAS 1.0 script metadata --- |
1411 | + |
1412 | +{{.InjectFile}} |
1413 | + |
1414 | +OPTS=$(getopt -o 'i:h' --long 'interface:,help' -n 'rack-controller-connectivity' -- "$@") |
1415 | + |
1416 | +if [ $? -ne 0 ]; then |
1417 | + exit 1 |
1418 | +fi |
1419 | + |
1420 | +eval set -- "$OPTS" |
1421 | +unset OPTS |
1422 | + |
1423 | +while true; do |
1424 | + case "$1" in |
1425 | + '-i'|'--interface') |
1426 | + INTERFACE=$2 |
1427 | + shift 2 |
1428 | + continue |
1429 | + ;; |
1430 | + '-h'|'--help') |
1431 | + echo "usage: rack-controller-connectivity [--interface INTERFACE]" |
1432 | + echo |
1433 | + echo "Check if an interface has access to the booted rack controller." |
1434 | + echo |
1435 | + echo "optional arguments:" |
1436 | + echo " -h, --help Show this message" |
1437 | + echo " -i, --interface The interface to test rack controller connectivity with." |
1438 | + echo " Default: Any interface" |
1439 | + exit 0 |
1440 | + ;; |
1441 | + '--') |
1442 | + shift |
1443 | + break |
1444 | + ;; |
1445 | + *) |
1446 | + echo "Unknown argument $1!" >&2 |
1447 | + exit 1 |
1448 | + esac |
1449 | +done |
1450 | + |
1451 | +# When booting into the ephemeral environment root is the retrieved from |
1452 | +# the rack controller. |
1453 | +for i in $(cat /proc/cmdline); do |
1454 | + arg=$(echo $i | cut -d '=' -f1) |
1455 | + if [ "$arg" == "root" ]; then |
1456 | + value=$(echo $i | cut -d '=' -f2-) |
1457 | + # MAAS normally specifies the file has "filetype:url" |
1458 | + filetype=$(echo $value | cut -d ':' -f1) |
1459 | + if [ "$filetype" == "squash" ]; then |
1460 | + url=$(echo $value | cut -d ':' -f2-) |
1461 | + else |
1462 | + url=$filetype |
1463 | + fi |
1464 | + break |
1465 | + fi |
1466 | +done |
1467 | + |
1468 | +if [ -z "$url" ] || $(echo "$url" | grep -vq "://"); then |
1469 | + echo "ERROR: Unable to find rack controller URL!" |
1470 | + exit 1 |
1471 | +fi |
1472 | + |
1473 | +test_interface "$INTERFACE" "$url" |
1474 | diff --git a/src/host-info/scripts/seven_z.py b/src/host-info/scripts/seven_z.py |
1475 | new file mode 120000 |
1476 | index 0000000..a769a0e |
1477 | --- /dev/null |
1478 | +++ b/src/host-info/scripts/seven_z.py |
1479 | @@ -0,0 +1 @@ |
1480 | +/home/christian/maas/src/metadataserver/builtin_scripts/testing_scripts/seven_z.py |
1481 | \ No newline at end of file |
1482 | diff --git a/src/host-info/scripts/smartctl.py b/src/host-info/scripts/smartctl.py |
1483 | new file mode 120000 |
1484 | index 0000000..918166a |
1485 | --- /dev/null |
1486 | +++ b/src/host-info/scripts/smartctl.py |
1487 | @@ -0,0 +1 @@ |
1488 | +/home/christian/maas/src/metadataserver/builtin_scripts/testing_scripts/smartctl.py |
1489 | \ No newline at end of file |
1490 | diff --git a/src/host-info/scripts/stress-ng-cpu-long.sh b/src/host-info/scripts/stress-ng-cpu-long.sh |
1491 | new file mode 120000 |
1492 | index 0000000..bdb03ae |
1493 | --- /dev/null |
1494 | +++ b/src/host-info/scripts/stress-ng-cpu-long.sh |
1495 | @@ -0,0 +1 @@ |
1496 | +/home/christian/maas/src/metadataserver/builtin_scripts/testing_scripts/stress-ng-cpu-long.sh |
1497 | \ No newline at end of file |
1498 | diff --git a/src/host-info/scripts/stress-ng-cpu-short.sh b/src/host-info/scripts/stress-ng-cpu-short.sh |
1499 | new file mode 120000 |
1500 | index 0000000..1626d40 |
1501 | --- /dev/null |
1502 | +++ b/src/host-info/scripts/stress-ng-cpu-short.sh |
1503 | @@ -0,0 +1 @@ |
1504 | +/home/christian/maas/src/metadataserver/builtin_scripts/testing_scripts/stress-ng-cpu-short.sh |
1505 | \ No newline at end of file |
1506 | diff --git a/src/host-info/scripts/stress-ng-memory-long.sh b/src/host-info/scripts/stress-ng-memory-long.sh |
1507 | new file mode 120000 |
1508 | index 0000000..911a85f |
1509 | --- /dev/null |
1510 | +++ b/src/host-info/scripts/stress-ng-memory-long.sh |
1511 | @@ -0,0 +1 @@ |
1512 | +/home/christian/maas/src/metadataserver/builtin_scripts/testing_scripts/stress-ng-memory-long.sh |
1513 | \ No newline at end of file |
1514 | diff --git a/src/host-info/scripts/stress-ng-memory-short.sh b/src/host-info/scripts/stress-ng-memory-short.sh |
1515 | new file mode 120000 |
1516 | index 0000000..2cc8c96 |
1517 | --- /dev/null |
1518 | +++ b/src/host-info/scripts/stress-ng-memory-short.sh |
1519 | @@ -0,0 +1 @@ |
1520 | +/home/christian/maas/src/metadataserver/builtin_scripts/testing_scripts/stress-ng-memory-short.sh |
1521 | \ No newline at end of file |
1522 | diff --git a/src/metadataserver/api.py b/src/metadataserver/api.py |
1523 | index 787e628..add0920 100644 |
1524 | --- a/src/metadataserver/api.py |
1525 | +++ b/src/metadataserver/api.py |
1526 | @@ -1442,6 +1442,80 @@ class MAASScriptsHandler(OperationsHandler): |
1527 | ) |
1528 | |
1529 | |
1530 | +class MAASScriptsMetadataHandler(OperationsHandler): |
1531 | + def _get_script_meta_data(self, script_set, mtime, include_finshed=False): |
1532 | + if script_set is None: |
1533 | + return [] |
1534 | + |
1535 | + meta_data = [] |
1536 | + for script_result in script_set: |
1537 | + # Don't rerun Scripts which have already run. |
1538 | + if ( |
1539 | + not include_finshed |
1540 | + and script_result.status |
1541 | + not in SCRIPT_STATUS_RUNNING_OR_PENDING |
1542 | + ): |
1543 | + continue |
1544 | + |
1545 | + md_item = {} |
1546 | + if script_result.script is None: |
1547 | + # Script was deleted by the user and it is not a builtin |
1548 | + # commissioning script. Don't expect a result. |
1549 | + script_result.delete() |
1550 | + continue |
1551 | + |
1552 | + md_item = get_script_result_properties(script_result) |
1553 | + meta_data.append(md_item) |
1554 | + return meta_data |
1555 | + |
1556 | + def read(self, request, version, mac=None): |
1557 | + node = get_queried_node(request) |
1558 | + mtime = time.time() |
1559 | + meta_data = {} |
1560 | + |
1561 | + if ( |
1562 | + node.status |
1563 | + in ( |
1564 | + NODE_STATUS.COMMISSIONING, |
1565 | + NODE_STATUS.DEPLOYED, |
1566 | + NODE_STATUS.ENTERING_RESCUE_MODE, |
1567 | + NODE_STATUS.RESCUE_MODE, |
1568 | + ) |
1569 | + and node.current_commissioning_script_set is not None |
1570 | + ): |
1571 | + # Prefetch all the data we need. |
1572 | + qs = node.current_commissioning_script_set.scriptresult_set |
1573 | + qs = qs.select_related("script", "script__script") |
1574 | + # After the script runner finishes sending all commissioning |
1575 | + # results it redownloads the script tar. It does this in-case |
1576 | + # a commissioning script discovers hardware associated with |
1577 | + # hardware identified in the for_hardware field of a script. |
1578 | + # select_for_hardware_scripts() processes the output of the |
1579 | + # builtin commissioning scripts and adds any associated script. |
1580 | + # This does not need to happen the first time the script runner |
1581 | + # downloads the tar as the region has not yet received new |
1582 | + # data. |
1583 | + for script_result in qs: |
1584 | + if script_result.status != SCRIPT_STATUS.PENDING: |
1585 | + script_set = node.current_commissioning_script_set |
1586 | + script_set.select_for_hardware_scripts() |
1587 | + break |
1588 | + meta_data["commissioning_scripts"] = self._get_script_meta_data( |
1589 | + node.current_commissioning_script_set, mtime |
1590 | + ) |
1591 | + |
1592 | + if node.current_testing_script_set is not None: |
1593 | + qs = node.current_testing_script_set.scriptresult_set |
1594 | + qs = qs.select_related("script", "script__script") |
1595 | + meta_data["testing_scripts"] = self._get_script_meta_data( |
1596 | + node.current_testing_script_set, mtime |
1597 | + ) |
1598 | + |
1599 | + return HttpResponse( |
1600 | + json.dumps({"1.0": meta_data}), content_type="application/json" |
1601 | + ) |
1602 | + |
1603 | + |
1604 | class AnonMetaDataHandler(VersionIndexHandler): |
1605 | """Anonymous metadata.""" |
1606 | |
1607 | diff --git a/src/metadataserver/builtin_scripts/testing_scripts/badblocks.py b/src/metadataserver/builtin_scripts/testing_scripts/badblocks.py |
1608 | index f52f544..5df0f13 100755 |
1609 | --- a/src/metadataserver/builtin_scripts/testing_scripts/badblocks.py |
1610 | +++ b/src/metadataserver/builtin_scripts/testing_scripts/badblocks.py |
1611 | @@ -203,7 +203,9 @@ def run_badblocks(storage, destructive=False): |
1612 | |
1613 | if __name__ == "__main__": |
1614 | # Determine if badblocks should run destructively from the script name. |
1615 | - if "destructive" in sys.argv[0]: |
1616 | + if "destructive" in sys.argv[0] or ( |
1617 | + len(sys.argv) >= 2 and sys.argv[1] == "destructive" |
1618 | + ): |
1619 | destructive = True |
1620 | else: |
1621 | destructive = False |
1622 | diff --git a/src/metadataserver/builtin_scripts/testing_scripts/gateway-connectivity.sh b/src/metadataserver/builtin_scripts/testing_scripts/gateway-connectivity.sh |
1623 | index 543d6a0..f6e8fd8 100644 |
1624 | --- a/src/metadataserver/builtin_scripts/testing_scripts/gateway-connectivity.sh |
1625 | +++ b/src/metadataserver/builtin_scripts/testing_scripts/gateway-connectivity.sh |
1626 | @@ -4,7 +4,7 @@ |
1627 | # |
1628 | # Author: Lee Trager <lee.trager@canonical.com> |
1629 | # |
1630 | -# Copyright (C) 2019 Canonical |
1631 | +# Copyright (C) 2019-2022 Canonical |
1632 | # |
1633 | # This program is free software: you can redistribute it and/or modify |
1634 | # it under the terms of the GNU Affero General Public License as |
1635 | diff --git a/src/metadataserver/templates/hardware_sync_service.template b/src/metadataserver/templates/hardware_sync_service.template |
1636 | index 806bd42..bfc9e68 100644 |
1637 | --- a/src/metadataserver/templates/hardware_sync_service.template |
1638 | +++ b/src/metadataserver/templates/hardware_sync_service.template |
1639 | @@ -8,4 +8,9 @@ After=network.target |
1640 | type=oneshot |
1641 | ExecStartPre=/usr/bin/wget -O /usr/bin/maas-hardware-sync {{ maas_url }}/hardware-sync/{{ architecture }} |
1642 | ExecStartPre=/bin/chmod 0755 /usr/bin/maas-hardware-sync |
1643 | -ExecStart=/bin/echo "TODO run hardware-sync commands" |
1644 | +ExecStartPre=/usr/bin/maas-hardware-sync get-machine-token\ |
1645 | + --maas-url '{{ maas_url }}'\ |
1646 | + --admin_token '{{ admin_token }}'\ |
1647 | + --system-id '{{ system_id }}'\ |
1648 | + --token-file '{{ token_file_path }}' |
1649 | +ExecStart=/usr/bin/maas-hardware-sync report-results --config '{{ token_file_path }}' |
1650 | diff --git a/src/metadataserver/tests/test_api.py b/src/metadataserver/tests/test_api.py |
1651 | index 8b8b5bd..e6a4fbb 100644 |
1652 | --- a/src/metadataserver/tests/test_api.py |
1653 | +++ b/src/metadataserver/tests/test_api.py |
1654 | @@ -4029,3 +4029,124 @@ class TestEnlistViews(MAASServerTestCase): |
1655 | response.content.decode(settings.DEFAULT_CHARSET).splitlines(), |
1656 | ContainsAll(("user-data", "meta-data")), |
1657 | ) |
1658 | + |
1659 | + |
1660 | +class TestMAASScriptsMetadataHandler(MAASServerTestCase): |
1661 | + """Tests for the script metadata""" |
1662 | + |
1663 | + def setUp(self): |
1664 | + super().setUp() |
1665 | + load_builtin_scripts() |
1666 | + |
1667 | + def test_node_with_builtin_script_set(self): |
1668 | + node = factory.make_Node( |
1669 | + status=NODE_STATUS.DEPLOYED, with_empty_script_sets=True |
1670 | + ) |
1671 | + response = make_node_client(node=node).get( |
1672 | + reverse("maas-scripts-metadata", args=["latest"]) |
1673 | + ) |
1674 | + self.assertEqual( |
1675 | + (http.client.OK, "application/json"), |
1676 | + (response.status_code, response["Content-Type"]), |
1677 | + ) |
1678 | + body = json.loads(response.content) |
1679 | + expected_commissioning_metadata = [ |
1680 | + get_script_result_properties(script_result) |
1681 | + for script_result in node.current_commissioning_script_set |
1682 | + ] |
1683 | + self.assertCountEqual( |
1684 | + expected_commissioning_metadata, |
1685 | + body["1.0"]["commissioning_scripts"], |
1686 | + ) |
1687 | + |
1688 | + def test_node_with_custom_commissioning_script_set(self): |
1689 | + script_name = factory.make_name("script") |
1690 | + script = factory.make_Script( |
1691 | + name=script_name, script_type=SCRIPT_TYPE.COMMISSIONING |
1692 | + ) |
1693 | + node = factory.make_Node(status=NODE_STATUS.COMMISSIONING) |
1694 | + node.current_commissioning_script_set = ( |
1695 | + ScriptSet.objects.create_commissioning_script_set(node) |
1696 | + ) |
1697 | + node.save() |
1698 | + response = make_node_client(node=node).get( |
1699 | + reverse("maas-scripts-metadata", args=["latest"]) |
1700 | + ) |
1701 | + self.assertEqual( |
1702 | + (http.client.OK, "application/json"), |
1703 | + (response.status_code, response["Content-Type"]), |
1704 | + ) |
1705 | + body = json.loads(response.content) |
1706 | + expected_commissioning_metadata = [ |
1707 | + get_script_result_properties(script_result) |
1708 | + for script_result in node.current_commissioning_script_set |
1709 | + ] |
1710 | + self.assertCountEqual( |
1711 | + expected_commissioning_metadata, |
1712 | + body["1.0"]["commissioning_scripts"], |
1713 | + ) |
1714 | + self.assertIn( |
1715 | + script.name, |
1716 | + [s["name"] for s in body["1.0"]["commissioning_scripts"]], |
1717 | + ) |
1718 | + |
1719 | + def test_node_with_custom_testing_script_set(self): |
1720 | + script_name = factory.make_name("script") |
1721 | + script = factory.make_Script( |
1722 | + name=script_name, script_type=SCRIPT_TYPE.TESTING |
1723 | + ) |
1724 | + node = factory.make_Node(status=NODE_STATUS.COMMISSIONING) |
1725 | + node.current_testing_script_set = ( |
1726 | + ScriptSet.objects.create_testing_script_set(node, scripts=[script]) |
1727 | + ) |
1728 | + node.save() |
1729 | + response = make_node_client(node=node).get( |
1730 | + reverse("maas-scripts-metadata", args=["latest"]) |
1731 | + ) |
1732 | + self.assertEqual( |
1733 | + (http.client.OK, "application/json"), |
1734 | + (response.status_code, response["Content-Type"]), |
1735 | + ) |
1736 | + body = json.loads(response.content) |
1737 | + expected_testing_metadata = [ |
1738 | + get_script_result_properties(script_result) |
1739 | + for script_result in node.current_testing_script_set |
1740 | + ] |
1741 | + self.assertCountEqual( |
1742 | + expected_testing_metadata, body["1.0"]["testing_scripts"] |
1743 | + ) |
1744 | + self.assertIn( |
1745 | + script.name, |
1746 | + [s["name"] for s in body["1.0"]["testing_scripts"]], |
1747 | + ) |
1748 | + |
1749 | + def test_deployed_machine_script_set(self): |
1750 | + script_name = factory.make_name("script") |
1751 | + script = factory.make_Script( |
1752 | + name=script_name, script_type=SCRIPT_TYPE.COMMISSIONING |
1753 | + ) |
1754 | + machine = factory.make_Machine(status=NODE_STATUS.DEPLOYED) |
1755 | + machine.current_commissioning_script_set = ( |
1756 | + ScriptSet.objects.create_deployed_machine_script_set(machine) |
1757 | + ) |
1758 | + machine.save() |
1759 | + response = make_node_client(node=machine).get( |
1760 | + reverse("maas-scripts-metadata", args=["latest"]) |
1761 | + ) |
1762 | + self.assertEqual( |
1763 | + (http.client.OK, "application/json"), |
1764 | + (response.status_code, response["Content-Type"]), |
1765 | + ) |
1766 | + body = json.loads(response.content) |
1767 | + expected_commissioning_metadata = [ |
1768 | + get_script_result_properties(script_result) |
1769 | + for script_result in machine.current_commissioning_script_set |
1770 | + ] |
1771 | + self.assertCountEqual( |
1772 | + expected_commissioning_metadata, |
1773 | + body["1.0"]["commissioning_scripts"], |
1774 | + ) |
1775 | + self.assertNotIn( |
1776 | + script.name, |
1777 | + [s["name"] for s in body["1.0"]["commissioning_scripts"]], |
1778 | + ) |
1779 | diff --git a/src/metadataserver/tests/test_vendor_data.py b/src/metadataserver/tests/test_vendor_data.py |
1780 | index 6c4000a..7c32692 100644 |
1781 | --- a/src/metadataserver/tests/test_vendor_data.py |
1782 | +++ b/src/metadataserver/tests/test_vendor_data.py |
1783 | @@ -20,6 +20,7 @@ import yaml |
1784 | |
1785 | from maasserver.enum import NODE_STATUS |
1786 | from maasserver.models import Config, ControllerInfo, NodeMetadata |
1787 | +from maasserver.models.user import get_auth_tokens |
1788 | from maasserver.node_status import COMMISSIONING_LIKE_STATUSES |
1789 | from maasserver.server_address import get_maas_facing_server_host |
1790 | from maasserver.testing.factory import factory |
1791 | @@ -39,6 +40,7 @@ from metadataserver.vendor_data import ( |
1792 | generate_system_info, |
1793 | get_node_maas_url, |
1794 | get_vendor_data, |
1795 | + HARDWARE_SYNC_MACHINE_TOKEN_PATH, |
1796 | HARDWARE_SYNC_SERVICE_TEMPLATE, |
1797 | HARDWARE_SYNC_TIMER_TEMPLATE, |
1798 | ) |
1799 | @@ -710,16 +712,22 @@ class TestGenerateHardwareSyncSystemdConfiguration(MAASServerTestCase): |
1800 | self.assertRaises(StopIteration, next, config) |
1801 | |
1802 | def test_returns_timer_and_service_when_node_enable_hw_sync_is_True(self): |
1803 | + self.maxDiff = None |
1804 | + user = factory.make_admin() |
1805 | + token = get_auth_tokens(user)[0] |
1806 | node = factory.make_Node( |
1807 | status=NODE_STATUS.DEPLOYING, |
1808 | + owner=user, |
1809 | enable_hw_sync=True, |
1810 | ) |
1811 | config = generate_hardware_sync_systemd_configuration(node) |
1812 | expected_interval = Config.objects.get_configs( |
1813 | ["hardware_sync_interval"] |
1814 | ) |
1815 | - |
1816 | - maas_url = get_node_maas_url(node) |
1817 | + expected_token = f"{token.consumer.key}:{token.key}:{token.secret}" |
1818 | + if token.consumer.secret: |
1819 | + expected_token += f":{token.consumer.secret}" |
1820 | + expected_maas_url = get_node_maas_url(node) |
1821 | |
1822 | expected = ( |
1823 | "write_files", |
1824 | @@ -732,7 +740,11 @@ class TestGenerateHardwareSyncSystemdConfiguration(MAASServerTestCase): |
1825 | }, |
1826 | { |
1827 | "content": self._get_service_template().substitute( |
1828 | - maas_url=maas_url, architecture=node.architecture |
1829 | + architecture=node.architecture, |
1830 | + admin_token=expected_token, |
1831 | + maas_url=expected_maas_url, |
1832 | + system_id=node.system_id, |
1833 | + token_file_path=HARDWARE_SYNC_MACHINE_TOKEN_PATH, |
1834 | ), |
1835 | "path": "/lib/systemd/system/maas_hardware_sync.service", |
1836 | }, |
1837 | diff --git a/src/metadataserver/urls.py b/src/metadataserver/urls.py |
1838 | index 0bd1f08..2e4d21d 100644 |
1839 | --- a/src/metadataserver/urls.py |
1840 | +++ b/src/metadataserver/urls.py |
1841 | @@ -17,6 +17,7 @@ from metadataserver.api import ( |
1842 | EnlistVersionIndexHandler, |
1843 | IndexHandler, |
1844 | MAASScriptsHandler, |
1845 | + MAASScriptsMetadataHandler, |
1846 | MetaDataHandler, |
1847 | StatusHandler, |
1848 | UserDataHandler, |
1849 | @@ -40,6 +41,9 @@ index_handler = OperationsResource(IndexHandler, authentication=api_auth) |
1850 | maas_scripts_handler = OperationsResource( |
1851 | MAASScriptsHandler, authentication=api_auth |
1852 | ) |
1853 | +maas_scripts_metadata_handler = OperationsResource( |
1854 | + MAASScriptsMetadataHandler, authentication=api_auth |
1855 | +) |
1856 | commissioning_scripts_handler = OperationsResource( |
1857 | CommissioningScriptsHandler, authentication=api_auth |
1858 | ) |
1859 | @@ -90,11 +94,16 @@ node_patterns = [ |
1860 | # definitive. maas-scripts is xz compressed while |
1861 | # maas-commissioning-scripts is not. |
1862 | url( |
1863 | - r"^[/]*(?P<version>[^/]+)/maas-scripts", |
1864 | + r"^[/]*(?P<version>[^/]+)/maas-scripts(?!-metadata)", |
1865 | maas_scripts_handler, |
1866 | name="maas-scripts", |
1867 | ), |
1868 | url( |
1869 | + r"^[/]*(?P<version>[^/]+)/maas-scripts-metadata", |
1870 | + maas_scripts_metadata_handler, |
1871 | + name="maas-scripts-metadata", |
1872 | + ), |
1873 | + url( |
1874 | r"^[/]*(?P<version>[^/]+)/maas-commissioning-scripts", |
1875 | commissioning_scripts_handler, |
1876 | name="commissioning-scripts", |
1877 | diff --git a/src/metadataserver/user_data/templates/snippets/maas_run_scripts.py b/src/metadataserver/user_data/templates/snippets/maas_run_scripts.py |
1878 | index 09e8855..cfce8e9 100644 |
1879 | --- a/src/metadataserver/user_data/templates/snippets/maas_run_scripts.py |
1880 | +++ b/src/metadataserver/user_data/templates/snippets/maas_run_scripts.py |
1881 | @@ -26,6 +26,7 @@ import yaml |
1882 | |
1883 | # imports from maas_api_helpers (only used in tests) |
1884 | # {% comment %} |
1885 | +# {{if .ImportAPIHelper }} |
1886 | from snippets.maas_api_helper import ( |
1887 | capture_script_output, |
1888 | Config, |
1889 | @@ -38,6 +39,7 @@ from snippets.maas_api_helper import ( |
1890 | signal, |
1891 | ) |
1892 | |
1893 | +# {{end}} |
1894 | # {% endcomment %} |
1895 | |
1896 | |
1897 | @@ -186,6 +188,15 @@ class Script: |
1898 | } |
1899 | |
1900 | |
1901 | +LOCAL_SCRIPTS_RUNNER = "/usr/bin/maas-hardware-sync" |
1902 | + |
1903 | + |
1904 | +class LocalScript(Script): |
1905 | + @property |
1906 | + def command(self): |
1907 | + return [LOCAL_SCRIPTS_RUNNER, self.name] |
1908 | + |
1909 | + |
1910 | def oauth_token(string): |
1911 | """Helper to use as type for OAuth token commandline args.""" |
1912 | try: |
1913 | @@ -222,6 +233,12 @@ def parse_args(args): |
1914 | action="store_true", |
1915 | default=False, |
1916 | ) |
1917 | + parser.add_argument( |
1918 | + "--local-scripts", |
1919 | + type=bool, |
1920 | + default=False, |
1921 | + help="Path to locally installed scripts, if one exists", |
1922 | + ) |
1923 | subparsers = parser.add_subparsers( |
1924 | metavar="ACTION", |
1925 | dest="action", |
1926 | @@ -332,6 +349,23 @@ def fetch_scripts(maas_url, metadata_url, paths, credentials): |
1927 | ] |
1928 | |
1929 | |
1930 | +def load_local_scripts(maas_url, metadata_url, paths, credentials): |
1931 | + res = geturl( |
1932 | + metadata_url + "maas-scripts-metadata", |
1933 | + credentials=credentials, |
1934 | + retry=False, |
1935 | + ) |
1936 | + if res.status == http.client.NO_CONTENT: |
1937 | + raise ExitError("No script returned") |
1938 | + |
1939 | + data = json.load(res) |
1940 | + |
1941 | + return [ |
1942 | + LocalScript(script_info, maas_url, paths) |
1943 | + for script_info in data["1.0"]["commissioning_scripts"] |
1944 | + ] |
1945 | + |
1946 | + |
1947 | def get_machine_token(maas_url, admin_token, system_id): |
1948 | """Return a dict with machine token and MAAS URL.""" |
1949 | try: |
1950 | @@ -370,6 +404,41 @@ def write_token(credentials, path=None): |
1951 | print(content) |
1952 | |
1953 | |
1954 | +def _action_report_results_scripts( |
1955 | + ns, maas_url, metadata_url, paths, config, script |
1956 | +): |
1957 | + print( |
1958 | + f"* Running '{script.name}'...", |
1959 | + end="\n" if ns.debug else " ", |
1960 | + ) |
1961 | + result = script.run(console_output=ns.debug) |
1962 | + if ns.debug: |
1963 | + print( |
1964 | + f"* Finished running '{script.name}': ", |
1965 | + end=" ", |
1966 | + ) |
1967 | + if result.exit_status == 0: |
1968 | + print("success") |
1969 | + else: |
1970 | + print( |
1971 | + "FAILED (status {result.exit_status}): {result.error}".format( |
1972 | + result=result |
1973 | + ) |
1974 | + ) |
1975 | + signal( |
1976 | + metadata_url, |
1977 | + config.credentials, |
1978 | + result.status, |
1979 | + error=result.error, |
1980 | + script_name=script.name, |
1981 | + script_result_id=script.info.get("script_result_id"), |
1982 | + files=result.result_files, |
1983 | + runtime=result.runtime, |
1984 | + exit_status=result.exit_status, |
1985 | + script_version_id=script.info.get("script_version_id"), |
1986 | + ) |
1987 | + |
1988 | + |
1989 | def action_report_results(ns): |
1990 | config = get_config(ns) |
1991 | if not config.metadata_url: |
1992 | @@ -381,46 +450,30 @@ def action_report_results(ns): |
1993 | maas_url = get_base_url(config.metadata_url) |
1994 | metadata_url = maas_url + "/MAAS/metadata/" + MD_VERSION + "/" |
1995 | |
1996 | - print( |
1997 | - "* Fetching scripts from {url} to {dir}".format( |
1998 | - url=metadata_url, dir=paths.scripts |
1999 | - ) |
2000 | - ) |
2001 | - for script in fetch_scripts( |
2002 | - maas_url, metadata_url, paths, config.credentials |
2003 | - ): |
2004 | - if not script.should_run(): |
2005 | - continue |
2006 | - print( |
2007 | - f"* Running '{script.name}'...", |
2008 | - end="\n" if ns.debug else " ", |
2009 | - ) |
2010 | - result = script.run(console_output=ns.debug) |
2011 | - if ns.debug: |
2012 | - print( |
2013 | - f"* Finished running '{script.name}': ", |
2014 | - end=" ", |
2015 | + if ns.local_scripts: |
2016 | + print("* Fetching script metadata from {url}".format(url=metadata_url)) |
2017 | + for script in load_local_scripts( |
2018 | + maas_url, metadata_url, paths, config.credentials |
2019 | + ): |
2020 | + if not script.should_run(): |
2021 | + continue |
2022 | + _action_report_results_scripts( |
2023 | + ns, maas_url, metadata_url, paths, config, script |
2024 | ) |
2025 | - if result.exit_status == 0: |
2026 | - print("success") |
2027 | - else: |
2028 | - print( |
2029 | - "FAILED (status {result.exit_status}): {result.error}".format( |
2030 | - result=result |
2031 | - ) |
2032 | + else: |
2033 | + print( |
2034 | + "* Fetching scripts from {url} to {dir}".format( |
2035 | + url=metadata_url, dir=paths.scripts |
2036 | ) |
2037 | - signal( |
2038 | - metadata_url, |
2039 | - config.credentials, |
2040 | - result.status, |
2041 | - error=result.error, |
2042 | - script_name=script.name, |
2043 | - script_result_id=script.info.get("script_result_id"), |
2044 | - files=result.result_files, |
2045 | - runtime=result.runtime, |
2046 | - exit_status=result.exit_status, |
2047 | - script_version_id=script.info.get("script_version_id"), |
2048 | ) |
2049 | + for script in fetch_scripts( |
2050 | + maas_url, metadata_url, paths, config.credentials |
2051 | + ): |
2052 | + if not script.should_run(): |
2053 | + continue |
2054 | + _action_report_results_scripts( |
2055 | + ns, maas_url, metadata_url, paths, config, script |
2056 | + ) |
2057 | |
2058 | |
2059 | def action_register_machine(ns): |
2060 | diff --git a/src/metadataserver/user_data/templates/snippets/tests/test_maas_run_scripts.py b/src/metadataserver/user_data/templates/snippets/tests/test_maas_run_scripts.py |
2061 | index ef0b3be..3823aa9 100644 |
2062 | --- a/src/metadataserver/user_data/templates/snippets/tests/test_maas_run_scripts.py |
2063 | +++ b/src/metadataserver/user_data/templates/snippets/tests/test_maas_run_scripts.py |
2064 | @@ -13,6 +13,8 @@ from provisioningserver.refresh.maas_api_helper import Credentials |
2065 | from snippets import maas_run_scripts |
2066 | from snippets.maas_run_scripts import ( |
2067 | get_config, |
2068 | + LOCAL_SCRIPTS_RUNNER, |
2069 | + LocalScript, |
2070 | main, |
2071 | parse_args, |
2072 | Script, |
2073 | @@ -517,3 +519,45 @@ class TestWriteToken(MAASTestCase): |
2074 | yaml.safe_load(path), |
2075 | {"reporting": {"maas": token_info}}, |
2076 | ) |
2077 | + |
2078 | + |
2079 | +class TestLocalScript(MAASTestCase): |
2080 | + def test_command(self): |
2081 | + info = { |
2082 | + "name": "myscript", |
2083 | + "path": "commissioning-scripts/myscript", |
2084 | + "timeout_seconds": 100, |
2085 | + } |
2086 | + paths = ScriptsPaths(base_path=Path("/base")) |
2087 | + script = LocalScript(info, "http://maas.example.com", paths) |
2088 | + self.assertCountEqual( |
2089 | + [LOCAL_SCRIPTS_RUNNER, script.name], script.command |
2090 | + ) |
2091 | + |
2092 | + def test_run(self): |
2093 | + fake_process = self.patch( |
2094 | + maas_run_scripts.subprocess, "Popen" |
2095 | + ).return_value |
2096 | + mock_capture_script_output = self.patch( |
2097 | + maas_run_scripts, "capture_script_output" |
2098 | + ) |
2099 | + info = { |
2100 | + "name": "myscript", |
2101 | + "path": "commissioning-scripts/myscript", |
2102 | + "timeout_seconds": 100, |
2103 | + } |
2104 | + paths = ScriptsPaths(base_path=Path("/base")) |
2105 | + script = LocalScript(info, "http://maas.example.com", paths) |
2106 | + result = script.run() |
2107 | + self.assertEqual(result.exit_status, 0) |
2108 | + self.assertEqual(result.status, "WORKING") |
2109 | + self.assertIsNone(result.error) |
2110 | + self.assertGreater(result.runtime, 0.0) |
2111 | + mock_capture_script_output.assert_called_once_with( |
2112 | + fake_process, |
2113 | + script.combined_path, |
2114 | + script.stdout_path, |
2115 | + script.stderr_path, |
2116 | + timeout_seconds=100, |
2117 | + console_output=False, |
2118 | + ) |
2119 | diff --git a/src/metadataserver/vendor_data.py b/src/metadataserver/vendor_data.py |
2120 | index 2d17e6a..39d93c0 100644 |
2121 | --- a/src/metadataserver/vendor_data.py |
2122 | +++ b/src/metadataserver/vendor_data.py |
2123 | @@ -18,6 +18,7 @@ import yaml |
2124 | from maasserver import ntp |
2125 | from maasserver.models import Config, NodeMetadata |
2126 | from maasserver.models.controllerinfo import get_target_version |
2127 | +from maasserver.models.user import get_auth_tokens |
2128 | from maasserver.node_status import COMMISSIONING_LIKE_STATUSES |
2129 | from maasserver.permissions import NodePermission |
2130 | from maasserver.preseed import get_network_yaml_settings |
2131 | @@ -33,6 +34,7 @@ VIRSH_PASSWORD_METADATA_KEY = "virsh_password" |
2132 | |
2133 | HARDWARE_SYNC_TIMER_TEMPLATE = "hardware_sync_timer.template" |
2134 | HARDWARE_SYNC_SERVICE_TEMPLATE = "hardware_sync_service.template" |
2135 | +HARDWARE_SYNC_MACHINE_TOKEN_PATH = "/tmp/mymachine-creds.yaml" |
2136 | |
2137 | |
2138 | def get_vendor_data(node, proxy): |
2139 | @@ -389,8 +391,23 @@ def generate_hardware_sync_systemd_configuration(node): |
2140 | hardware_sync_timer = hardware_sync_timer_tmpl.substitute( |
2141 | hardware_sync_interval=hardware_sync_interval |
2142 | ) |
2143 | + |
2144 | + admin_token = "" |
2145 | + if node.owner is not None: |
2146 | + tokens = get_auth_tokens(node.owner) |
2147 | + if tokens: |
2148 | + admin_token = ( |
2149 | + f"{tokens[0].consumer.key}:{tokens[0].key}:{tokens[0].secret}" |
2150 | + ) |
2151 | + if tokens[0].consumer.secret: |
2152 | + admin_token += f":{tokens[0].consumer.secret}" |
2153 | + |
2154 | hardware_sync_service = hardware_sync_service_tmpl.substitute( |
2155 | - maas_url=maas_url, architecture=node.architecture |
2156 | + architecture=node.architecture, |
2157 | + admin_token=admin_token, |
2158 | + maas_url=maas_url, |
2159 | + system_id=node.system_id, |
2160 | + token_file_path=HARDWARE_SYNC_MACHINE_TOKEN_PATH, |
2161 | ) |
2162 | |
2163 | yield "write_files", [ |
UNIT TESTS scripts_ in_hardware_ sync lp:~cgrabowski/maas/+git/maas into -b master lp:~maas-committers/maas
-b embed_run_
STATUS: FAILED maas-ci. internal: 8080/job/ maas/job/ branch- tester/ 11846/console f26583ccb74898f 24cc0b8f21
LOG: http://
COMMIT: 634b95be2d5c07b