github.com/hashicorp/packer@v1.14.3/provisioner/windows-restart/provisioner_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package restart 5 6 import ( 7 "bytes" 8 "context" 9 "fmt" 10 "testing" 11 "time" 12 13 packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 14 ) 15 16 func testConfig() map[string]interface{} { 17 return map[string]interface{}{} 18 } 19 20 func TestProvisioner_Impl(t *testing.T) { 21 var raw interface{} 22 raw = &Provisioner{} 23 if _, ok := raw.(packersdk.Provisioner); !ok { 24 t.Fatalf("must be a Provisioner") 25 } 26 } 27 28 func TestProvisionerPrepare_Defaults(t *testing.T) { 29 var p Provisioner 30 config := testConfig() 31 32 err := p.Prepare(config) 33 if err != nil { 34 t.Fatalf("err: %s", err) 35 } 36 37 if p.config.RestartTimeout != 5*time.Minute { 38 t.Errorf("unexpected restart timeout: %s", p.config.RestartTimeout) 39 } 40 41 if p.config.RestartCommand != "shutdown /r /f /t 0 /c \"packer restart\"" { 42 t.Errorf("unexpected restart command: %s", p.config.RestartCommand) 43 } 44 } 45 46 func TestProvisionerPrepare_ConfigRetryTimeout(t *testing.T) { 47 var p Provisioner 48 config := testConfig() 49 config["restart_timeout"] = "1m" 50 51 err := p.Prepare(config) 52 if err != nil { 53 t.Fatalf("err: %s", err) 54 } 55 56 if p.config.RestartTimeout != 1*time.Minute { 57 t.Errorf("unexpected restart timeout: %s", p.config.RestartTimeout) 58 } 59 } 60 61 func TestProvisionerPrepare_ConfigErrors(t *testing.T) { 62 var p Provisioner 63 config := testConfig() 64 config["restart_timeout"] = "m" 65 66 err := p.Prepare(config) 67 if err == nil { 68 t.Fatal("Expected error parsing restart_timeout but did not receive one.") 69 } 70 } 71 72 func TestProvisionerPrepare_InvalidKey(t *testing.T) { 73 var p Provisioner 74 config := testConfig() 75 76 // Add a random key 77 config["i_should_not_be_valid"] = true 78 err := p.Prepare(config) 79 if err == nil { 80 t.Fatal("should have error") 81 } 82 } 83 84 func testUi() *packersdk.BasicUi { 85 return &packersdk.BasicUi{ 86 Reader: new(bytes.Buffer), 87 Writer: new(bytes.Buffer), 88 ErrorWriter: new(bytes.Buffer), 89 } 90 } 91 92 func TestProvisionerProvision_Success(t *testing.T) { 93 config := testConfig() 94 95 // Defaults provided by Packer 96 ui := testUi() 97 p := new(Provisioner) 98 99 // Defaults provided by Packer 100 comm := new(packersdk.MockCommunicator) 101 p.Prepare(config) 102 waitForCommunicatorOld := waitForCommunicator 103 waitForCommunicator = func(context.Context, *Provisioner) error { 104 return nil 105 } 106 waitForRestartOld := waitForRestart 107 waitForRestart = func(context.Context, *Provisioner, packersdk.Communicator) error { 108 return nil 109 } 110 err := p.Provision(context.Background(), ui, comm, make(map[string]interface{})) 111 if err != nil { 112 t.Fatal("should not have error") 113 } 114 115 expectedCommand := DefaultRestartCommand 116 117 // Should run the command without alteration 118 if comm.StartCmd.Command != expectedCommand { 119 t.Fatalf("Expect command to be: %s, got %s", expectedCommand, comm.StartCmd.Command) 120 } 121 // Set this back! 122 waitForCommunicator = waitForCommunicatorOld 123 waitForRestart = waitForRestartOld 124 } 125 126 func TestProvisionerProvision_CustomCommand(t *testing.T) { 127 config := testConfig() 128 129 // Defaults provided by Packer 130 ui := testUi() 131 p := new(Provisioner) 132 expectedCommand := "specialrestart.exe -NOW" 133 config["restart_command"] = expectedCommand 134 135 // Defaults provided by Packer 136 comm := new(packersdk.MockCommunicator) 137 p.Prepare(config) 138 waitForCommunicatorOld := waitForCommunicator 139 waitForCommunicator = func(context.Context, *Provisioner) error { 140 return nil 141 } 142 waitForRestartOld := waitForRestart 143 waitForRestart = func(context.Context, *Provisioner, packersdk.Communicator) error { 144 return nil 145 } 146 err := p.Provision(context.Background(), ui, comm, make(map[string]interface{})) 147 if err != nil { 148 t.Fatal("should not have error") 149 } 150 151 // Should run the command without alteration 152 if comm.StartCmd.Command != expectedCommand { 153 t.Fatalf("Expect command to be: %s, got %s", expectedCommand, comm.StartCmd.Command) 154 } 155 // Set this back! 156 waitForCommunicator = waitForCommunicatorOld 157 waitForRestart = waitForRestartOld 158 } 159 160 func TestProvisionerProvision_RestartCommandFail(t *testing.T) { 161 config := testConfig() 162 ui := testUi() 163 p := new(Provisioner) 164 comm := new(packersdk.MockCommunicator) 165 comm.StartStderr = "WinRM terminated" 166 comm.StartExitStatus = 1 167 168 p.Prepare(config) 169 err := p.Provision(context.Background(), ui, comm, make(map[string]interface{})) 170 if err == nil { 171 t.Fatal("should have error") 172 } 173 } 174 func TestProvisionerProvision_WaitForRestartFail(t *testing.T) { 175 config := testConfig() 176 177 // Defaults provided by Packer 178 ui := testUi() 179 p := new(Provisioner) 180 181 // Defaults provided by Packer 182 comm := new(packersdk.MockCommunicator) 183 p.Prepare(config) 184 waitForCommunicatorOld := waitForCommunicator 185 waitForCommunicator = func(context.Context, *Provisioner) error { 186 return fmt.Errorf("Machine did not restart properly") 187 } 188 err := p.Provision(context.Background(), ui, comm, make(map[string]interface{})) 189 if err == nil { 190 t.Fatal("should have error") 191 } 192 193 // Set this back! 194 waitForCommunicator = waitForCommunicatorOld 195 } 196 197 func TestProvision_waitForRestartTimeout(t *testing.T) { 198 retryableSleep = 10 * time.Millisecond 199 config := testConfig() 200 config["restart_timeout"] = "1ms" 201 ui := testUi() 202 p := new(Provisioner) 203 comm := new(packersdk.MockCommunicator) 204 var err error 205 206 p.Prepare(config) 207 waitForCommunicatorOld := waitForCommunicator 208 waitDone := make(chan bool) 209 waitContinue := make(chan bool) 210 211 // Block until cancel comes through 212 waitForCommunicator = func(context.Context, *Provisioner) error { 213 for range waitDone { 214 } 215 waitContinue <- true 216 return nil 217 } 218 219 go func() { 220 err = p.Provision(context.Background(), ui, comm, make(map[string]interface{})) 221 close(waitDone) 222 }() 223 <-waitContinue 224 225 if err == nil { 226 t.Fatal("should not have error") 227 } 228 229 // Set this back! 230 waitForCommunicator = waitForCommunicatorOld 231 232 } 233 234 func TestProvision_waitForCommunicator(t *testing.T) { 235 config := testConfig() 236 237 // Defaults provided by Packer 238 ui := testUi() 239 p := new(Provisioner) 240 241 // Defaults provided by Packer 242 comm := new(packersdk.MockCommunicator) 243 p.comm = comm 244 p.ui = ui 245 comm.StartStderr = "WinRM terminated" 246 comm.StartStdout = "WIN-V4CEJ7MC5SN restarted." 247 comm.StartExitStatus = 1 248 p.Prepare(config) 249 err := waitForCommunicator(context.Background(), p) 250 251 if err != nil { 252 t.Fatalf("should not have error, got: %s", err.Error()) 253 } 254 255 expectedCommand := DefaultRestartCheckCommand 256 257 // Should run the command without alteration 258 if comm.StartCmd.Command != expectedCommand { 259 t.Fatalf("Expect command to be: %s, got %s", expectedCommand, comm.StartCmd.Command) 260 } 261 } 262 263 func TestProvision_waitForCommunicatorWithCancel(t *testing.T) { 264 config := testConfig() 265 266 // Defaults provided by Packer 267 ui := testUi() 268 p := new(Provisioner) 269 270 // Defaults provided by Packer 271 comm := new(packersdk.MockCommunicator) 272 p.comm = comm 273 p.ui = ui 274 retryableSleep = 5 * time.Second 275 p.cancel = make(chan struct{}) 276 var err error 277 278 ctx, cancel := context.WithCancel(context.Background()) 279 280 comm.StartStderr = "WinRM terminated" 281 comm.StartExitStatus = 1 // Always fail 282 p.Prepare(config) 283 284 // Run 2 goroutines; 285 // 1st to call waitForCommunicator (that will always fail) 286 // 2nd to cancel the operation 287 waitStart := make(chan bool) 288 waitDone := make(chan bool) 289 go func() { 290 waitStart <- true 291 err = waitForCommunicator(ctx, p) 292 waitDone <- true 293 }() 294 295 go func() { 296 time.Sleep(10 * time.Millisecond) 297 <-waitStart 298 cancel() 299 }() 300 <-waitDone 301 302 // Expect a Cancel error 303 if err == nil { 304 t.Fatalf("Should have err") 305 } 306 } 307 308 func TestProvision_Cancel(t *testing.T) { 309 config := testConfig() 310 311 // Defaults provided by Packer 312 ui := testUi() 313 p := new(Provisioner) 314 315 comm := new(packersdk.MockCommunicator) 316 p.Prepare(config) 317 done := make(chan error) 318 319 topCtx, cancelTopCtx := context.WithCancel(context.Background()) 320 321 // Block until cancel comes through 322 waitForCommunicator = func(ctx context.Context, p *Provisioner) error { 323 cancelTopCtx() 324 <-ctx.Done() 325 return ctx.Err() 326 } 327 328 // Create two go routines to provision and cancel in parallel 329 // Provision will block until cancel happens 330 go func() { 331 done <- p.Provision(topCtx, ui, comm, make(map[string]interface{})) 332 }() 333 334 // Expect interrupt error 335 if err := <-done; err == nil { 336 t.Fatal("should have error") 337 } 338 }