Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
P
packer
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Kristopher Ruzic
packer
Commits
3bc0c4aa
Commit
3bc0c4aa
authored
Nov 05, 2013
by
Mitchell Hashimoto
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
builder/qemu: simplify driver, make things more Go-like
parent
d78787e1
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
194 additions
and
245 deletions
+194
-245
builder/qemu/builder.go
builder/qemu/builder.go
+13
-1
builder/qemu/driver.go
builder/qemu/driver.go
+74
-129
builder/qemu/step_boot_wait.go
builder/qemu/step_boot_wait.go
+25
-0
builder/qemu/step_run.go
builder/qemu/step_run.go
+26
-95
builder/qemu/step_shutdown.go
builder/qemu/step_shutdown.go
+13
-20
builder/qemu/step_wait_for_shutdown.go
builder/qemu/step_wait_for_shutdown.go
+43
-0
No files found.
builder/qemu/builder.go
View file @
3bc0c4aa
...
...
@@ -372,7 +372,19 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
new
(
stepHTTPServer
),
new
(
stepForwardSSH
),
new
(
stepConfigureVNC
),
new
(
stepRun
),
&
stepRun
{
BootDrive
:
"d"
,
Message
:
"Starting VM, booting from CD-ROM"
,
},
&
stepBootWait
{},
&
stepTypeBootCommand
{},
&
stepWaitForShutdown
{
Message
:
"Waiting for initial VM boot to shut down"
,
},
&
stepRun
{
BootDrive
:
"c"
,
Message
:
"Starting VM, booting from hard disk"
,
},
&
common
.
StepConnectSSH
{
SSHAddress
:
sshAddress
,
SSHConfig
:
sshConfig
,
...
...
builder/qemu/driver.go
View file @
3bc0c4aa
...
...
@@ -3,7 +3,6 @@ package qemu
import
(
"bufio"
"bytes"
"errors"
"fmt"
"github.com/mitchellh/multistep"
"io"
...
...
@@ -11,7 +10,8 @@ import (
"os/exec"
"regexp"
"strings"
"time"
"sync"
"syscall"
"unicode"
)
...
...
@@ -25,21 +25,14 @@ type Driver interface {
// qemuImgPath - string value for the qemu-img executable
Initialize
(
string
,
string
)
// Checks if the VM with the given name is running.
IsRunning
(
string
)
(
bool
,
error
)
// Stop stops a running machine, forcefully.
Stop
(
string
)
error
Stop
()
error
// Qemu executes the given command via qemu-system-x86_64
Qemu
(
vmName
string
,
qemuArgs
...
string
)
error
Qemu
(
qemuArgs
...
string
)
error
// wait on shutdown of the VM with option to cancel
WaitForShutdown
(
vmName
string
,
block
bool
,
state
multistep
.
StateBag
,
cancellCallback
DriverCancelCallback
)
error
WaitForShutdown
(
<-
chan
struct
{})
bool
// Qemu executes the given command via qemu-img
QemuImg
(
...
string
)
error
...
...
@@ -53,156 +46,108 @@ type Driver interface {
Version
()
(
string
,
error
)
}
type
driverState
struct
{
cmd
*
exec
.
Cmd
cancelChan
chan
struct
{}
waitDone
chan
error
}
type
QemuDriver
struct
{
qemuPath
string
qemuImgPath
string
state
map
[
string
]
*
driverState
}
func
(
d
*
QemuDriver
)
getDriverState
(
name
string
)
*
driverState
{
if
_
,
ok
:=
d
.
state
[
name
];
!
ok
{
d
.
state
[
name
]
=
&
driverState
{}
}
return
d
.
state
[
name
]
vmCmd
*
exec
.
Cmd
vmEndCh
<-
chan
int
lock
sync
.
Mutex
}
func
(
d
*
QemuDriver
)
Initialize
(
qemuPath
string
,
qemuImgPath
string
)
{
d
.
qemuPath
=
qemuPath
d
.
qemuImgPath
=
qemuImgPath
d
.
state
=
make
(
map
[
string
]
*
driverState
)
}
func
(
d
*
QemuDriver
)
IsRunning
(
name
string
)
(
bool
,
error
)
{
ds
:=
d
.
getDriverState
(
name
)
return
ds
.
cancelChan
!=
nil
,
nil
}
func
(
d
*
QemuDriver
)
Stop
(
name
string
)
error
{
ds
:=
d
.
getDriverState
(
name
)
func
(
d
*
QemuDriver
)
Stop
()
error
{
d
.
lock
.
Lock
()
defer
d
.
lock
.
Unlock
()
// signal to the command 'wait' to kill the process
if
ds
.
cancelChan
!=
nil
{
close
(
ds
.
cancelChan
)
ds
.
cancelChan
=
nil
if
d
.
vmCmd
!=
nil
{
if
err
:=
d
.
vmCmd
.
Process
.
Kill
();
err
!=
nil
{
return
err
}
}
return
nil
}
func
(
d
*
QemuDriver
)
Qemu
(
vmName
string
,
qemuArgs
...
string
)
error
{
func
(
d
*
QemuDriver
)
Qemu
(
qemuArgs
...
string
)
error
{
d
.
lock
.
Lock
()
defer
d
.
lock
.
Unlock
()
if
d
.
vmCmd
!=
nil
{
panic
(
"Existing VM state found"
)
}
stdout_r
,
stdout_w
:=
io
.
Pipe
()
stderr_r
,
stderr_w
:=
io
.
Pipe
()
log
.
Printf
(
"Executing %s: %#v"
,
d
.
qemuPath
,
qemuArgs
)
ds
:=
d
.
getDriverState
(
vmName
)
ds
.
cmd
=
exec
.
Command
(
d
.
qemuPath
,
qemuArgs
...
)
ds
.
cmd
.
Stdout
=
stdout_w
ds
.
cmd
.
Stderr
=
stderr_w
cmd
:=
exec
.
Command
(
d
.
qemuPath
,
qemuArgs
...
)
cmd
.
Stdout
=
stdout_w
cmd
.
Stderr
=
stderr_w
err
:=
cmd
.
Start
()
if
err
!=
nil
{
err
=
fmt
.
Errorf
(
"Error starting VM: %s"
,
err
)
return
err
}
go
logReader
(
"Qemu stdout"
,
stdout_r
)
go
logReader
(
"Qemu stderr"
,
stderr_r
)
err
:=
ds
.
cmd
.
Start
()
if
err
!=
nil
{
err
=
fmt
.
Errorf
(
"Error starting VM: %s"
,
err
)
}
else
{
log
.
Printf
(
"---- Started Qemu ------- PID = %d"
,
ds
.
cmd
.
Process
.
Pid
)
log
.
Printf
(
"Started Qemu. Pid: %d"
,
cmd
.
Process
.
Pid
)
// Wait for Qemu to complete in the background, and mark when its done
endCh
:=
make
(
chan
int
,
1
)
go
func
()
{
defer
stderr_w
.
Close
()
defer
stdout_w
.
Close
()
var
exitCode
int
=
0
if
err
:=
cmd
.
Wait
();
err
!=
nil
{
if
exiterr
,
ok
:=
err
.
(
*
exec
.
ExitError
);
ok
{
// The program has exited with an exit code != 0
if
status
,
ok
:=
exiterr
.
Sys
()
.
(
syscall
.
WaitStatus
);
ok
{
exitCode
=
status
.
ExitStatus
()
}
else
{
exitCode
=
254
}
}
}
ds
.
cancelChan
=
make
(
chan
struct
{})
endCh
<-
exitCode
// make the channel to watch the process
ds
.
waitDone
=
make
(
chan
error
)
d
.
lock
.
Lock
()
defer
d
.
lock
.
Unlock
()
d
.
vmCmd
=
nil
d
.
vmEndCh
=
nil
}()
// start the virtual machine in the background
go
func
()
{
defer
stderr_w
.
Close
()
defer
stdout_w
.
Close
()
ds
.
waitDone
<-
ds
.
cmd
.
Wait
()
}()
}
// Setup our state so we know we are running
d
.
vmCmd
=
cmd
d
.
vmEndCh
=
endCh
return
err
return
nil
}
func
(
d
*
QemuDriver
)
WaitForShutdown
(
vmName
string
,
block
bool
,
state
multistep
.
StateBag
,
cancelCallback
DriverCancelCallback
)
error
{
var
err
error
ds
:=
d
.
getDriverState
(
vmName
)
if
block
{
// wait in the background for completion or caller cancel
for
{
select
{
case
<-
ds
.
cancelChan
:
log
.
Println
(
"Qemu process request to cancel -- killing Qemu process."
)
if
err
=
ds
.
cmd
.
Process
.
Kill
();
err
!=
nil
{
log
.
Printf
(
"Failed to kill qemu: %v"
,
err
)
}
func
(
d
*
QemuDriver
)
WaitForShutdown
(
cancelCh
<-
chan
struct
{})
bool
{
d
.
lock
.
Lock
()
endCh
:=
d
.
vmEndCh
d
.
lock
.
Unlock
()
// clear out the error channel since it's just a cancel
// and therefore the reason for failure is clear
log
.
Println
(
"Empytying waitDone channel."
)
<-
ds
.
waitDone
// this gig is over -- assure calls to IsRunning see the nil
log
.
Println
(
"'Nil'ing out cancelChan."
)
ds
.
cancelChan
=
nil
return
errors
.
New
(
"WaitForShutdown cancelled"
)
case
err
=
<-
ds
.
waitDone
:
log
.
Printf
(
"Qemu Process done with output = %v"
,
err
)
// assure calls to IsRunning see the nil
log
.
Println
(
"'Nil'ing out cancelChan."
)
ds
.
cancelChan
=
nil
return
nil
case
<-
time
.
After
(
1
*
time
.
Second
)
:
cancel
:=
cancelCallback
(
state
)
if
cancel
{
log
.
Println
(
"Qemu process request to cancel -- killing Qemu process."
)
// The step sequence was cancelled, so cancel waiting for SSH
// and just start the halting process.
close
(
ds
.
cancelChan
)
log
.
Println
(
"Cancel request made, quitting waiting for Qemu."
)
return
errors
.
New
(
"WaitForShutdown cancelled by interrupt."
)
}
}
}
}
else
{
go
func
()
{
select
{
case
<-
ds
.
cancelChan
:
log
.
Println
(
"Qemu process request to cancel -- killing Qemu process."
)
if
err
=
ds
.
cmd
.
Process
.
Kill
();
err
!=
nil
{
log
.
Printf
(
"Failed to kill qemu: %v"
,
err
)
}
// clear out the error channel since it's just a cancel
// and therefore the reason for failure is clear
log
.
Println
(
"Empytying waitDone channel."
)
<-
ds
.
waitDone
log
.
Println
(
"'Nil'ing out cancelChan."
)
ds
.
cancelChan
=
nil
case
err
=
<-
ds
.
waitDone
:
log
.
Printf
(
"Qemu Process done with output = %v"
,
err
)
log
.
Println
(
"'Nil'ing out cancelChan."
)
ds
.
cancelChan
=
nil
}
}()
if
endCh
==
nil
{
return
true
}
ds
.
cancelChan
=
nil
return
err
select
{
case
<-
endCh
:
return
true
case
<-
cancelCh
:
return
false
}
}
func
(
d
*
QemuDriver
)
QemuImg
(
args
...
string
)
error
{
...
...
builder/qemu/step_boot_wait.go
0 → 100644
View file @
3bc0c4aa
package
qemu
import
(
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"time"
)
// stepBootWait waits the configured time period.
type
stepBootWait
struct
{}
func
(
s
*
stepBootWait
)
Run
(
state
multistep
.
StateBag
)
multistep
.
StepAction
{
config
:=
state
.
Get
(
"config"
)
.
(
*
config
)
ui
:=
state
.
Get
(
"ui"
)
.
(
packer
.
Ui
)
if
int64
(
config
.
bootWait
)
>
0
{
ui
.
Say
(
fmt
.
Sprintf
(
"Waiting %s for boot..."
,
config
.
bootWait
))
time
.
Sleep
(
config
.
bootWait
)
}
return
multistep
.
ActionContinue
}
func
(
s
*
stepBootWait
)
Cleanup
(
state
multistep
.
StateBag
)
{}
builder/qemu/step_run.go
View file @
3bc0c4aa
...
...
@@ -6,49 +6,51 @@ import (
"github.com/mitchellh/packer/packer"
"path/filepath"
"strings"
"time"
)
// stepRun runs the virtual machine
type
stepRun
struct
{
vmName
string
BootDrive
string
Message
string
}
func
runBootCommand
(
state
multistep
.
StateBag
,
actionChannel
chan
multistep
.
StepAction
)
{
config
:=
state
.
Get
(
"config"
)
.
(
*
config
)
func
(
s
*
stepRun
)
Run
(
state
multistep
.
StateBag
)
multistep
.
StepAction
{
driver
:=
state
.
Get
(
"driver"
)
.
(
Driver
)
ui
:=
state
.
Get
(
"ui"
)
.
(
packer
.
Ui
)
bootCmd
:=
stepTypeBootCommand
{}
if
int64
(
config
.
bootWait
)
>
0
{
ui
.
Say
(
fmt
.
Sprintf
(
"Waiting %s for boot..."
,
config
.
bootWait
))
time
.
Sleep
(
config
.
bootWait
)
ui
.
Say
(
s
.
Message
)
command
:=
getCommandArgs
(
s
.
BootDrive
,
state
)
if
err
:=
driver
.
Qemu
(
command
...
);
err
!=
nil
{
err
:=
fmt
.
Errorf
(
"Error launching VM: %s"
,
err
)
ui
.
Error
(
err
.
Error
())
return
multistep
.
ActionHalt
}
actionChannel
<-
bootCmd
.
Run
(
state
)
return
multistep
.
ActionContinue
}
func
cancelCallback
(
state
multistep
.
StateBag
)
bool
{
cancel
:=
false
if
_
,
ok
:=
state
.
GetOk
(
multistep
.
StateCancelled
);
ok
{
cancel
=
true
func
(
s
*
stepRun
)
Cleanup
(
state
multistep
.
StateBag
)
{
driver
:=
state
.
Get
(
"driver"
)
.
(
Driver
)
ui
:=
state
.
Get
(
"ui"
)
.
(
packer
.
Ui
)
if
err
:=
driver
.
Stop
();
err
!=
nil
{
ui
.
Error
(
fmt
.
Sprintf
(
"Error shutting down VM: %s"
,
err
))
}
return
cancel
}
func
(
s
*
stepRun
)
getCommandArgs
(
bootDrive
string
,
state
multistep
.
StateBag
)
[]
string
{
ui
:=
state
.
Get
(
"ui"
)
.
(
packer
.
Ui
)
func
getCommandArgs
(
bootDrive
string
,
state
multistep
.
StateBag
)
[]
string
{
config
:=
state
.
Get
(
"config"
)
.
(
*
config
)
vmName
:=
config
.
VMName
imgPath
:=
filepath
.
Join
(
config
.
OutputDir
,
fmt
.
Sprintf
(
"%s.%s"
,
vmName
,
strings
.
ToLower
(
config
.
Format
)))
isoPath
:=
state
.
Get
(
"iso_path"
)
.
(
string
)
vncPort
:=
state
.
Get
(
"vnc_port"
)
.
(
uint
)
guiArgument
:=
"sdl"
sshHostPort
:=
state
.
Get
(
"sshHostPort"
)
.
(
uint
)
ui
:=
state
.
Get
(
"ui"
)
.
(
packer
.
Ui
)
guiArgument
:=
"sdl"
vnc
:=
fmt
.
Sprintf
(
"0.0.0.0:%d"
,
vncPort
-
5900
)
vmName
:=
config
.
VMName
imgPath
:=
filepath
.
Join
(
config
.
OutputDir
,
fmt
.
Sprintf
(
"%s.%s"
,
vmName
,
strings
.
ToLower
(
config
.
Format
)))
if
config
.
Headless
==
true
{
ui
.
Message
(
"WARNING: The VM will be started in headless mode, as configured.
\n
"
+
...
...
@@ -112,74 +114,3 @@ func (s *stepRun) getCommandArgs(
return
outArgs
}
func
(
s
*
stepRun
)
runVM
(
sendBootCommands
bool
,
bootDrive
string
,
state
multistep
.
StateBag
)
multistep
.
StepAction
{
config
:=
state
.
Get
(
"config"
)
.
(
*
config
)
driver
:=
state
.
Get
(
"driver"
)
.
(
Driver
)
ui
:=
state
.
Get
(
"ui"
)
.
(
packer
.
Ui
)
vmName
:=
config
.
VMName
ui
.
Say
(
"Starting the virtual machine for OS Install..."
)
command
:=
s
.
getCommandArgs
(
bootDrive
,
state
)
if
err
:=
driver
.
Qemu
(
vmName
,
command
...
);
err
!=
nil
{
err
:=
fmt
.
Errorf
(
"Error launching VM: %s"
,
err
)
ui
.
Error
(
err
.
Error
())
return
multistep
.
ActionHalt
}
s
.
vmName
=
vmName
// run the boot command after its own timeout
if
sendBootCommands
{
waitDone
:=
make
(
chan
multistep
.
StepAction
,
1
)
go
runBootCommand
(
state
,
waitDone
)
select
{
case
action
:=
<-
waitDone
:
if
action
!=
multistep
.
ActionContinue
{
// stop the VM in its tracks
driver
.
Stop
(
vmName
)
return
multistep
.
ActionHalt
}
}
}
ui
.
Say
(
"Waiting for VM to shutdown..."
)
if
err
:=
driver
.
WaitForShutdown
(
vmName
,
sendBootCommands
,
state
,
cancelCallback
);
err
!=
nil
{
err
:=
fmt
.
Errorf
(
"Error waiting for initial VM install to shutdown: %s"
,
err
)
ui
.
Error
(
err
.
Error
())
return
multistep
.
ActionHalt
}
return
multistep
.
ActionContinue
}
func
(
s
*
stepRun
)
Run
(
state
multistep
.
StateBag
)
multistep
.
StepAction
{
// First, the OS install boot
action
:=
s
.
runVM
(
true
,
"d"
,
state
)
if
action
==
multistep
.
ActionContinue
{
// Then the provisioning install
action
=
s
.
runVM
(
false
,
"c"
,
state
)
}
return
action
}
func
(
s
*
stepRun
)
Cleanup
(
state
multistep
.
StateBag
)
{
if
s
.
vmName
==
""
{
return
}
driver
:=
state
.
Get
(
"driver"
)
.
(
Driver
)
ui
:=
state
.
Get
(
"ui"
)
.
(
packer
.
Ui
)
if
running
,
_
:=
driver
.
IsRunning
(
s
.
vmName
);
running
{
if
err
:=
driver
.
Stop
(
s
.
vmName
);
err
!=
nil
{
ui
.
Error
(
fmt
.
Sprintf
(
"Error shutting down VM: %s"
,
err
))
}
}
}
builder/qemu/step_shutdown.go
View file @
3bc0c4aa
...
...
@@ -17,7 +17,6 @@ import (
// config *config
// driver Driver
// ui packer.Ui
// vmName string
//
// Produces:
// <nothing>
...
...
@@ -28,7 +27,6 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction {
config
:=
state
.
Get
(
"config"
)
.
(
*
config
)
driver
:=
state
.
Get
(
"driver"
)
.
(
Driver
)
ui
:=
state
.
Get
(
"ui"
)
.
(
packer
.
Ui
)
vmName
:=
config
.
VMName
if
config
.
ShutdownCommand
!=
""
{
ui
.
Say
(
"Gracefully halting virtual machine..."
)
...
...
@@ -41,28 +39,23 @@ func (s *stepShutdown) Run(state multistep.StateBag) multistep.StepAction {
return
multistep
.
ActionHalt
}
// Wait for the machine to actually shut down
log
.
Printf
(
"Waiting max %s for shutdown to complete"
,
config
.
shutdownTimeout
)
shutdownTimer
:=
time
.
After
(
config
.
shutdownTimeout
)
for
{
running
,
_
:=
driver
.
IsRunning
(
vmName
)
if
!
running
{
break
}
// Start the goroutine that will time out our graceful attempt
cancelCh
:=
make
(
chan
struct
{},
1
)
go
func
()
{
defer
close
(
cancelCh
)
<-
time
.
After
(
config
.
shutdownTimeout
)
}()
select
{
case
<-
shutdownTimer
:
err
:=
errors
.
New
(
"Timeout while waiting for machine to shut down."
)
state
.
Put
(
"error"
,
err
)
ui
.
Error
(
err
.
Error
())
return
multistep
.
ActionHalt
default
:
time
.
Sleep
(
1
*
time
.
Second
)
}
log
.
Printf
(
"Waiting max %s for shutdown to complete"
,
config
.
shutdownTimeout
)
if
ok
:=
driver
.
WaitForShutdown
(
cancelCh
);
!
ok
{
err
:=
errors
.
New
(
"Timeout while waiting for machine to shut down."
)
state
.
Put
(
"error"
,
err
)
ui
.
Error
(
err
.
Error
())
return
multistep
.
ActionHalt
}
}
else
{
ui
.
Say
(
"Halting the virtual machine..."
)
if
err
:=
driver
.
Stop
(
vmName
);
err
!=
nil
{
if
err
:=
driver
.
Stop
();
err
!=
nil
{
err
:=
fmt
.
Errorf
(
"Error stopping VM: %s"
,
err
)
state
.
Put
(
"error"
,
err
)
ui
.
Error
(
err
.
Error
())
...
...
builder/qemu/step_wait_for_shutdown.go
0 → 100644
View file @
3bc0c4aa
package
qemu
import
(
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"time"
)
// stepWaitForShutdown waits for the shutdown of the currently running
// qemu VM.
type
stepWaitForShutdown
struct
{
Message
string
}
func
(
s
*
stepWaitForShutdown
)
Run
(
state
multistep
.
StateBag
)
multistep
.
StepAction
{
driver
:=
state
.
Get
(
"driver"
)
.
(
Driver
)
ui
:=
state
.
Get
(
"ui"
)
.
(
packer
.
Ui
)
stopCh
:=
make
(
chan
struct
{})
defer
close
(
stopCh
)
cancelCh
:=
make
(
chan
struct
{})
go
func
()
{
for
{
if
_
,
ok
:=
state
.
GetOk
(
multistep
.
StateCancelled
);
ok
{
close
(
cancelCh
)
return
}
select
{
case
<-
stopCh
:
return
case
<-
time
.
After
(
100
*
time
.
Millisecond
)
:
}
}
}()
ui
.
Say
(
s
.
Message
)
driver
.
WaitForShutdown
(
cancelCh
)
return
multistep
.
ActionContinue
}
func
(
s
*
stepWaitForShutdown
)
Cleanup
(
state
multistep
.
StateBag
)
{}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment