diff --git a/builder/openstack/builder.go b/builder/openstack/builder.go index 3007d0461..f802e2523 100644 --- a/builder/openstack/builder.go +++ b/builder/openstack/builder.go @@ -126,9 +126,10 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack Wait: b.config.RackconnectWait, }, &StepAllocateIp{ - FloatingIPNetwork: b.config.FloatingIPNetwork, - FloatingIP: b.config.FloatingIP, - ReuseIPs: b.config.ReuseIPs, + FloatingIPNetwork: b.config.FloatingIPNetwork, + FloatingIP: b.config.FloatingIP, + ReuseIPs: b.config.ReuseIPs, + InstanceFloatingIPNet: b.config.InstanceFloatingIPNet, }, &communicator.StepConnect{ Config: &b.config.RunConfig.Comm, diff --git a/builder/openstack/networks.go b/builder/openstack/networks.go index 30c435ee8..537d9ce5a 100644 --- a/builder/openstack/networks.go +++ b/builder/openstack/networks.go @@ -2,6 +2,7 @@ package openstack import ( "fmt" + "log" "github.com/google/uuid" "github.com/gophercloud/gophercloud" @@ -66,7 +67,10 @@ func FindFreeFloatingIP(client *gophercloud.ServiceClient) (*floatingips.Floatin // GetInstancePortID returns internal port of the instance that can be used for // the association of a floating IP. // It will return an ID of a first port if there are many. -func GetInstancePortID(client *gophercloud.ServiceClient, id string) (string, error) { +func GetInstancePortID(client *gophercloud.ServiceClient, id string, instance_float_net string) (string, error) { + + selected_interface := 0 + interfacesPage, err := attachinterfaces.List(client, id).AllPages() if err != nil { return "", err @@ -79,7 +83,16 @@ func GetInstancePortID(client *gophercloud.ServiceClient, id string) (string, er return "", fmt.Errorf("instance '%s' has no interfaces", id) } - return interfaces[0].PortID, nil + for i := 0; i < len(interfaces); i++ { + log.Printf("Instance interface: %v: %+v\n", i, interfaces[i]) + if interfaces[i].NetID == instance_float_net { + log.Printf("Found preferred interface: %v\n", i) + selected_interface = i + log.Printf("Using interface value: %v", selected_interface) + } + } + + return interfaces[selected_interface].PortID, nil } // CheckFloatingIPNetwork checks provided network reference and returns a valid diff --git a/builder/openstack/run_config.go b/builder/openstack/run_config.go index c0bac7e6f..0400d2384 100644 --- a/builder/openstack/run_config.go +++ b/builder/openstack/run_config.go @@ -17,23 +17,24 @@ type RunConfig struct { SSHInterface string `mapstructure:"ssh_interface"` SSHIPVersion string `mapstructure:"ssh_ip_version"` - SourceImage string `mapstructure:"source_image"` - SourceImageName string `mapstructure:"source_image_name"` - SourceImageFilters ImageFilter `mapstructure:"source_image_filter"` - Flavor string `mapstructure:"flavor"` - AvailabilityZone string `mapstructure:"availability_zone"` - RackconnectWait bool `mapstructure:"rackconnect_wait"` - FloatingIPNetwork string `mapstructure:"floating_ip_network"` - FloatingIP string `mapstructure:"floating_ip"` - ReuseIPs bool `mapstructure:"reuse_ips"` - SecurityGroups []string `mapstructure:"security_groups"` - Networks []string `mapstructure:"networks"` - Ports []string `mapstructure:"ports"` - UserData string `mapstructure:"user_data"` - UserDataFile string `mapstructure:"user_data_file"` - InstanceName string `mapstructure:"instance_name"` - InstanceMetadata map[string]string `mapstructure:"instance_metadata"` - ForceDelete bool `mapstructure:"force_delete"` + SourceImage string `mapstructure:"source_image"` + SourceImageName string `mapstructure:"source_image_name"` + SourceImageFilters ImageFilter `mapstructure:"source_image_filter"` + Flavor string `mapstructure:"flavor"` + AvailabilityZone string `mapstructure:"availability_zone"` + RackconnectWait bool `mapstructure:"rackconnect_wait"` + FloatingIPNetwork string `mapstructure:"floating_ip_network"` + FloatingIP string `mapstructure:"floating_ip"` + ReuseIPs bool `mapstructure:"reuse_ips"` + SecurityGroups []string `mapstructure:"security_groups"` + Networks []string `mapstructure:"networks"` + InstanceFloatingIPNet string `mapstructure:"instance_floating_ip_net"` + Ports []string `mapstructure:"ports"` + UserData string `mapstructure:"user_data"` + UserDataFile string `mapstructure:"user_data_file"` + InstanceName string `mapstructure:"instance_name"` + InstanceMetadata map[string]string `mapstructure:"instance_metadata"` + ForceDelete bool `mapstructure:"force_delete"` ConfigDrive bool `mapstructure:"config_drive"` diff --git a/builder/openstack/step_allocate_ip.go b/builder/openstack/step_allocate_ip.go index a63faa836..8b0701491 100644 --- a/builder/openstack/step_allocate_ip.go +++ b/builder/openstack/step_allocate_ip.go @@ -11,9 +11,10 @@ import ( ) type StepAllocateIp struct { - FloatingIPNetwork string - FloatingIP string - ReuseIPs bool + FloatingIPNetwork string + FloatingIP string + ReuseIPs bool + InstanceFloatingIPNet string } func (s *StepAllocateIp) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { @@ -114,7 +115,7 @@ func (s *StepAllocateIp) Run(ctx context.Context, state multistep.StateBag) mult ui.Say(fmt.Sprintf("Associating floating IP '%s' (%s) with instance port...", instanceIP.ID, instanceIP.FloatingIP)) - portID, err := GetInstancePortID(computeClient, server.ID) + portID, err := GetInstancePortID(computeClient, server.ID, s.InstanceFloatingIPNet) if err != nil { err := fmt.Errorf("Error getting interfaces of the instance '%s': %s", server.ID, err) state.Put("error", err) diff --git a/website/source/docs/builders/openstack.html.md b/website/source/docs/builders/openstack.html.md index 45d87a7b0..97d9cc9e4 100644 --- a/website/source/docs/builders/openstack.html.md +++ b/website/source/docs/builders/openstack.html.md @@ -155,6 +155,13 @@ builder. - `insecure` (boolean) - Whether or not the connection to OpenStack can be done over an insecure connection. By default this is false. +- `instance_floating_ip_net` (string) - The ID of the network to which the + instance is attached and which should be used to associate with the floating + IP. This provides control over the floating ip association on multi-homed + instances. The association otherwise depends on a first-returned-interface + policy which could fail if the network to which it is connected is + unreachable from the floating IP network. + - `key` (string) - Client private key file path for SSL client authentication. If omitted the `OS_KEY` environment variable can be used.