builder/googlecompute: get instance info

This commit is contained in:
Mitchell Hashimoto 2013-12-13 13:01:28 -08:00
parent 57f707dfcc
commit a72d31fb5b
7 changed files with 233 additions and 44 deletions

View File

@ -66,18 +66,6 @@ func New(projectId string, zone string, c *clientSecrets, pemKey []byte) (*Googl
return googleComputeClient, nil return googleComputeClient, nil
} }
// InstanceStatus returns a string representing the status of the named instance.
// Status will be one of: "PROVISIONING", "STAGING", "RUNNING", "STOPPING",
// "STOPPED", "TERMINATED".
func (g *GoogleComputeClient) InstanceStatus(zone, name string) (string, error) {
instanceGetCall := g.Service.Instances.Get(g.ProjectId, zone, name)
instance, err := instanceGetCall.Do()
if err != nil {
return "", err
}
return instance.Status, nil
}
// CreateImage registers a GCE Image with a project. // CreateImage registers a GCE Image with a project.
func (g *GoogleComputeClient) CreateImage(name, description, sourceURL string) (*compute.Operation, error) { func (g *GoogleComputeClient) CreateImage(name, description, sourceURL string) (*compute.Operation, error) {
imageRawDisk := &compute.ImageRawDisk{ imageRawDisk := &compute.ImageRawDisk{
@ -98,26 +86,6 @@ func (g *GoogleComputeClient) CreateImage(name, description, sourceURL string) (
return operation, nil return operation, nil
} }
// GetNatIp returns the public IPv4 address for named GCE instance.
func (g *GoogleComputeClient) GetNatIP(zone, name string) (string, error) {
instanceGetCall := g.Service.Instances.Get(g.ProjectId, zone, name)
instance, err := instanceGetCall.Do()
if err != nil {
return "", err
}
for _, ni := range instance.NetworkInterfaces {
if ni.AccessConfigs == nil {
continue
}
for _, ac := range ni.AccessConfigs {
if ac.NatIP != "" {
return ac.NatIP, nil
}
}
}
return "", nil
}
// ZoneOperationStatus returns the status for the named zone operation. // ZoneOperationStatus returns the status for the named zone operation.
func (g *GoogleComputeClient) ZoneOperationStatus(zone, name string) (string, error) { func (g *GoogleComputeClient) ZoneOperationStatus(zone, name string) (string, error) {
zoneOperationsGetCall := g.Service.ZoneOperations.Get(g.ProjectId, zone, name) zoneOperationsGetCall := g.Service.ZoneOperations.Get(g.ProjectId, zone, name)

View File

@ -50,9 +50,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
steps := []multistep.Step{ steps := []multistep.Step{
new(StepCreateSSHKey), new(StepCreateSSHKey),
new(StepCreateInstance), new(StepCreateInstance),
new(StepInstanceInfo),
} }
/* /*
new(stepInstanceInfo),
&common.StepConnectSSH{ &common.StepConnectSSH{
SSHAddress: sshAddress, SSHAddress: sshAddress,
SSHConfig: sshConfig, SSHConfig: sshConfig,

View File

@ -7,8 +7,14 @@ type Driver interface {
// DeleteInstance deletes the given instance. // DeleteInstance deletes the given instance.
DeleteInstance(zone, name string) (<-chan error, error) DeleteInstance(zone, name string) (<-chan error, error)
// GetNatIP gets the NAT IP address for the instance.
GetNatIP(zone, name string) (string, error)
// RunInstance takes the given config and launches an instance. // RunInstance takes the given config and launches an instance.
RunInstance(*InstanceConfig) (<-chan error, error) RunInstance(*InstanceConfig) (<-chan error, error)
// WaitForInstance waits for an instance to reach the given state.
WaitForInstance(state, zone, name string) <-chan error
} }
type InstanceConfig struct { type InstanceConfig struct {

View File

@ -70,6 +70,27 @@ func (d *driverGCE) DeleteInstance(zone, name string) (<-chan error, error) {
return errCh, nil return errCh, nil
} }
func (d *driverGCE) GetNatIP(zone, name string) (string, error) {
instance, err := d.service.Instances.Get(d.projectId, zone, name).Do()
if err != nil {
return "", err
}
for _, ni := range instance.NetworkInterfaces {
if ni.AccessConfigs == nil {
continue
}
for _, ac := range ni.AccessConfigs {
if ac.NatIP != "" {
return ac.NatIP, nil
}
}
}
return "", nil
}
func (d *driverGCE) RunInstance(c *InstanceConfig) (<-chan error, error) { func (d *driverGCE) RunInstance(c *InstanceConfig) (<-chan error, error) {
// Get the zone // Get the zone
d.ui.Message(fmt.Sprintf("Loading zone: %s", c.Zone)) d.ui.Message(fmt.Sprintf("Loading zone: %s", c.Zone))
@ -156,6 +177,12 @@ func (d *driverGCE) RunInstance(c *InstanceConfig) (<-chan error, error) {
return errCh, nil return errCh, nil
} }
func (d *driverGCE) WaitForInstance(state, zone, name string) <-chan error {
errCh := make(chan error, 1)
go waitForState(errCh, state, d.refreshInstanceState(zone, name))
return errCh
}
func (d *driverGCE) getImage(name string) (image *compute.Image, err error) { func (d *driverGCE) getImage(name string) (image *compute.Image, err error) {
projects := []string{d.projectId, "debian-cloud", "centos-cloud"} projects := []string{d.projectId, "debian-cloud", "centos-cloud"}
for _, project := range projects { for _, project := range projects {
@ -173,6 +200,16 @@ func (d *driverGCE) getImage(name string) (image *compute.Image, err error) {
return return
} }
func (d *driverGCE) refreshInstanceState(zone, name string) stateRefreshFunc {
return func() (string, error) {
instance, err := d.service.Instances.Get(d.projectId, zone, name).Do()
if err != nil {
return "", err
}
return instance.Status, nil
}
}
func (d *driverGCE) refreshZoneOp(zone string, op *compute.Operation) stateRefreshFunc { func (d *driverGCE) refreshZoneOp(zone string, op *compute.Operation) stateRefreshFunc {
return func() (string, error) { return func() (string, error) {
newOp, err := d.service.ZoneOperations.Get(d.projectId, zone, op.Name).Do() newOp, err := d.service.ZoneOperations.Get(d.projectId, zone, op.Name).Do()

View File

@ -8,9 +8,19 @@ type DriverMock struct {
DeleteInstanceErrCh <-chan error DeleteInstanceErrCh <-chan error
DeleteInstanceErr error DeleteInstanceErr error
GetNatIPZone string
GetNatIPName string
GetNatIPResult string
GetNatIPErr error
RunInstanceConfig *InstanceConfig RunInstanceConfig *InstanceConfig
RunInstanceErrCh <-chan error RunInstanceErrCh <-chan error
RunInstanceErr error RunInstanceErr error
WaitForInstanceState string
WaitForInstanceZone string
WaitForInstanceName string
WaitForInstanceErrCh <-chan error
} }
func (d *DriverMock) DeleteInstance(zone, name string) (<-chan error, error) { func (d *DriverMock) DeleteInstance(zone, name string) (<-chan error, error) {
@ -27,6 +37,12 @@ func (d *DriverMock) DeleteInstance(zone, name string) (<-chan error, error) {
return resultCh, d.DeleteInstanceErr return resultCh, d.DeleteInstanceErr
} }
func (d *DriverMock) GetNatIP(zone, name string) (string, error) {
d.GetNatIPZone = zone
d.GetNatIPName = name
return d.GetNatIPResult, d.GetNatIPErr
}
func (d *DriverMock) RunInstance(c *InstanceConfig) (<-chan error, error) { func (d *DriverMock) RunInstance(c *InstanceConfig) (<-chan error, error) {
d.RunInstanceConfig = c d.RunInstanceConfig = c
@ -39,3 +55,18 @@ func (d *DriverMock) RunInstance(c *InstanceConfig) (<-chan error, error) {
return resultCh, d.RunInstanceErr return resultCh, d.RunInstanceErr
} }
func (d *DriverMock) WaitForInstance(state, zone, name string) <-chan error {
d.WaitForInstanceState = state
d.WaitForInstanceZone = zone
d.WaitForInstanceName = name
resultCh := d.WaitForInstanceErrCh
if resultCh == nil {
ch := make(chan error)
close(ch)
resultCh = ch
}
return resultCh
}

View File

@ -1,40 +1,53 @@
package googlecompute package googlecompute
import ( import (
"errors"
"fmt" "fmt"
"time"
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
) )
// stepInstanceInfo represents a Packer build step that gathers GCE instance info. // stepInstanceInfo represents a Packer build step that gathers GCE instance info.
type stepInstanceInfo int type StepInstanceInfo int
// Run executes the Packer build step that gathers GCE instance info. // Run executes the Packer build step that gathers GCE instance info.
func (s *stepInstanceInfo) Run(state multistep.StateBag) multistep.StepAction { func (s *StepInstanceInfo) Run(state multistep.StateBag) multistep.StepAction {
var ( config := state.Get("config").(*Config)
client = state.Get("client").(*GoogleComputeClient) driver := state.Get("driver").(Driver)
config = state.Get("config").(*Config) ui := state.Get("ui").(packer.Ui)
ui = state.Get("ui").(packer.Ui)
)
instanceName := state.Get("instance_name").(string) instanceName := state.Get("instance_name").(string)
err := waitForInstanceState("RUNNING", config.Zone, instanceName, client, config.stateTimeout)
ui.Say("Waiting for the instance to become running...")
errCh := driver.WaitForInstance("RUNNING", config.Zone, instanceName)
var err error
select {
case err = <-errCh:
case <-time.After(config.stateTimeout):
err = errors.New("time out while waiting for instance to become running")
}
if err != nil { if err != nil {
err := fmt.Errorf("Error creating instance: %s", err) err := fmt.Errorf("Error waiting for instance: %s", err)
state.Put("error", err) state.Put("error", err)
ui.Error(err.Error()) ui.Error(err.Error())
return multistep.ActionHalt return multistep.ActionHalt
} }
ip, err := client.GetNatIP(config.Zone, instanceName)
ip, err := driver.GetNatIP(config.Zone, instanceName)
if err != nil { if err != nil {
err := fmt.Errorf("Error retrieving instance nat ip address: %s", err) err := fmt.Errorf("Error retrieving instance nat ip address: %s", err)
state.Put("error", err) state.Put("error", err)
ui.Error(err.Error()) ui.Error(err.Error())
return multistep.ActionHalt return multistep.ActionHalt
} }
ui.Message(fmt.Sprintf("IP: %s", ip))
state.Put("instance_ip", ip) state.Put("instance_ip", ip)
return multistep.ActionContinue return multistep.ActionContinue
} }
// Cleanup. // Cleanup.
func (s *stepInstanceInfo) Cleanup(state multistep.StateBag) {} func (s *StepInstanceInfo) Cleanup(state multistep.StateBag) {}

View File

@ -0,0 +1,134 @@
package googlecompute
import (
"errors"
"github.com/mitchellh/multistep"
"testing"
"time"
)
func TestStepInstanceInfo_impl(t *testing.T) {
var _ multistep.Step = new(StepInstanceInfo)
}
func TestStepInstanceInfo(t *testing.T) {
state := testState(t)
step := new(StepInstanceInfo)
defer step.Cleanup(state)
state.Put("instance_name", "foo")
config := state.Get("config").(*Config)
driver := state.Get("driver").(*DriverMock)
driver.GetNatIPResult = "1.2.3.4"
// run the step
if action := step.Run(state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
// Verify state
if driver.WaitForInstanceState != "RUNNING" {
t.Fatalf("bad: %#v", driver.WaitForInstanceState)
}
if driver.WaitForInstanceZone != config.Zone {
t.Fatalf("bad: %#v", driver.WaitForInstanceZone)
}
if driver.WaitForInstanceName != "foo" {
t.Fatalf("bad: %#v", driver.WaitForInstanceName)
}
ipRaw, ok := state.GetOk("instance_ip")
if !ok {
t.Fatal("should have ip")
}
if ip, ok := ipRaw.(string); !ok {
t.Fatal("ip is not a string")
} else if ip != "1.2.3.4" {
t.Fatalf("bad ip: %s", ip)
}
}
func TestStepInstanceInfo_getNatIPError(t *testing.T) {
state := testState(t)
step := new(StepInstanceInfo)
defer step.Cleanup(state)
state.Put("instance_name", "foo")
driver := state.Get("driver").(*DriverMock)
driver.GetNatIPErr = errors.New("error")
// run the step
if action := step.Run(state); action != multistep.ActionHalt {
t.Fatalf("bad action: %#v", action)
}
// Verify state
if _, ok := state.GetOk("error"); !ok {
t.Fatal("should have error")
}
if _, ok := state.GetOk("instance_ip"); ok {
t.Fatal("should NOT have instance IP")
}
}
func TestStepInstanceInfo_waitError(t *testing.T) {
state := testState(t)
step := new(StepInstanceInfo)
defer step.Cleanup(state)
state.Put("instance_name", "foo")
errCh := make(chan error, 1)
errCh <- errors.New("error")
driver := state.Get("driver").(*DriverMock)
driver.WaitForInstanceErrCh = errCh
// run the step
if action := step.Run(state); action != multistep.ActionHalt {
t.Fatalf("bad action: %#v", action)
}
// Verify state
if _, ok := state.GetOk("error"); !ok {
t.Fatal("should have error")
}
if _, ok := state.GetOk("instance_ip"); ok {
t.Fatal("should NOT have instance IP")
}
}
func TestStepInstanceInfo_errorTimeout(t *testing.T) {
state := testState(t)
step := new(StepInstanceInfo)
defer step.Cleanup(state)
errCh := make(chan error, 1)
go func() {
<-time.After(10 * time.Millisecond)
errCh <- nil
}()
state.Put("instance_name", "foo")
config := state.Get("config").(*Config)
config.stateTimeout = 1 * time.Microsecond
driver := state.Get("driver").(*DriverMock)
driver.WaitForInstanceErrCh = errCh
// run the step
if action := step.Run(state); action != multistep.ActionHalt {
t.Fatalf("bad action: %#v", action)
}
// Verify state
if _, ok := state.GetOk("error"); !ok {
t.Fatal("should have error")
}
if _, ok := state.GetOk("instance_ip"); ok {
t.Fatal("should NOT have instance IP")
}
}