OpenStack builder: floating IP refactoring
Remove usage of the deprecated OpenStack Compute service floating IP management and add methods to work with the OpenStack Networking service floating IPs API. Remove usage of the deprecated OpenStack Compute service floating IP pools and add methods to work with the OpenStack Networking service external networks API. Move reusable logic of working with the OpenStack Networking service API to a separate methods in the networking.go file. Pass error messages from the API services to the ui messages in the allocate IP step.
This commit is contained in:
parent
68afd3d8da
commit
0eef9b4292
|
@ -201,6 +201,13 @@ func (c *AccessConfig) blockStorageV3Client() (*gophercloud.ServiceClient, error
|
|||
})
|
||||
}
|
||||
|
||||
func (c *AccessConfig) networkV2Client() (*gophercloud.ServiceClient, error) {
|
||||
return openstack.NewNetworkV2(c.osClient, gophercloud.EndpointOpts{
|
||||
Region: c.Region,
|
||||
Availability: c.getEndpointType(),
|
||||
})
|
||||
}
|
||||
|
||||
func (c *AccessConfig) getEndpointType() gophercloud.Availability {
|
||||
if c.EndpointType == "internal" || c.EndpointType == "internalURL" {
|
||||
return gophercloud.AvailabilityInternal
|
||||
|
|
|
@ -115,9 +115,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
|
|||
Wait: b.config.RackconnectWait,
|
||||
},
|
||||
&StepAllocateIp{
|
||||
FloatingIpPool: b.config.FloatingIpPool,
|
||||
FloatingIp: b.config.FloatingIp,
|
||||
ReuseIps: b.config.ReuseIps,
|
||||
FloatingNetwork: b.config.FloatingNetwork,
|
||||
FloatingIP: b.config.FloatingIP,
|
||||
ReuseIPs: b.config.ReuseIPs,
|
||||
},
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.RunConfig.Comm,
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
package openstack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces"
|
||||
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/external"
|
||||
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips"
|
||||
"github.com/gophercloud/gophercloud/openstack/networking/v2/networks"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
)
|
||||
|
||||
// ExternalNetwork is a network with external router.
|
||||
type ExternalNetwork struct {
|
||||
networks.Network
|
||||
external.NetworkExternalExt
|
||||
}
|
||||
|
||||
// FindExternalNetwork returns existing network with external router.
|
||||
// It will return first network if there are many.
|
||||
func FindExternalNetwork(client *gophercloud.ServiceClient) (*ExternalNetwork, error) {
|
||||
var externalNetworks []ExternalNetwork
|
||||
|
||||
allPages, err := networks.List(client, networks.ListOpts{
|
||||
Status: "ACTIVE",
|
||||
}).AllPages()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Extract external networks from found networks.
|
||||
err = networks.ExtractNetworksInto(allPages, &externalNetworks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(externalNetworks) == 0 {
|
||||
return nil, fmt.Errorf("no external networks found")
|
||||
}
|
||||
|
||||
// Return the first external network.
|
||||
return &externalNetworks[0], nil
|
||||
}
|
||||
|
||||
// CheckFloatingIP gets a floating IP by its ID and checks if it is already
|
||||
// associated with any internal interface.
|
||||
// It returns floating IP if it can be used.
|
||||
func CheckFloatingIP(client *gophercloud.ServiceClient, id string) (*floatingips.FloatingIP, error) {
|
||||
floatingIP, err := floatingips.Get(client, id).Extract()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if floatingIP.PortID != "" {
|
||||
return nil, fmt.Errorf("provided floating IP '%s' is already associated with port '%s'",
|
||||
id, floatingIP.PortID)
|
||||
}
|
||||
|
||||
return floatingIP, nil
|
||||
}
|
||||
|
||||
// FindFreeFloatingIP returns free unassociated floating IP.
|
||||
// It will return first floating IP if there are many.
|
||||
func FindFreeFloatingIP(client *gophercloud.ServiceClient) (*floatingips.FloatingIP, error) {
|
||||
var freeFloatingIP *floatingips.FloatingIP
|
||||
|
||||
pager := floatingips.List(client, floatingips.ListOpts{
|
||||
Status: "DOWN",
|
||||
})
|
||||
err := pager.EachPage(func(page pagination.Page) (bool, error) {
|
||||
candidates, err := floatingips.ExtractFloatingIPs(page)
|
||||
if err != nil {
|
||||
return false, err // stop and throw error out
|
||||
}
|
||||
|
||||
for _, candidate := range candidates {
|
||||
if candidate.PortID != "" {
|
||||
continue // this floating IP is associated with port, move to next in list
|
||||
}
|
||||
|
||||
// Floating IP is able to be allocated.
|
||||
freeFloatingIP = &candidate
|
||||
return false, nil // stop iterating over pages
|
||||
}
|
||||
return true, nil // try the next page
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if freeFloatingIP == nil {
|
||||
return nil, fmt.Errorf("no free floating IPs found")
|
||||
}
|
||||
|
||||
return freeFloatingIP, nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
interfacesPage, err := attachinterfaces.List(client, id).AllPages()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
interfaces, err := attachinterfaces.ExtractInterfaces(interfacesPage)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(interfaces) == 0 {
|
||||
return "", fmt.Errorf("instance '%s' has no interfaces", id)
|
||||
}
|
||||
|
||||
return interfaces[0].PortID, nil
|
||||
}
|
|
@ -23,9 +23,9 @@ type RunConfig struct {
|
|||
Flavor string `mapstructure:"flavor"`
|
||||
AvailabilityZone string `mapstructure:"availability_zone"`
|
||||
RackconnectWait bool `mapstructure:"rackconnect_wait"`
|
||||
FloatingIpPool string `mapstructure:"floating_ip_pool"`
|
||||
FloatingIp string `mapstructure:"floating_ip"`
|
||||
ReuseIps bool `mapstructure:"reuse_ips"`
|
||||
FloatingNetwork string `mapstructure:"floating_network"`
|
||||
FloatingIP string `mapstructure:"floating_ip"`
|
||||
ReuseIPs bool `mapstructure:"reuse_ips"`
|
||||
SecurityGroups []string `mapstructure:"security_groups"`
|
||||
Networks []string `mapstructure:"networks"`
|
||||
Ports []string `mapstructure:"ports"`
|
||||
|
@ -57,10 +57,6 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
|
|||
c.TemporaryKeyPairName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
|
||||
}
|
||||
|
||||
if c.UseFloatingIp && c.FloatingIpPool == "" {
|
||||
c.FloatingIpPool = "public"
|
||||
}
|
||||
|
||||
// Validation
|
||||
errs := c.Comm.Prepare(ctx)
|
||||
|
||||
|
|
|
@ -9,8 +9,8 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
|
||||
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips"
|
||||
packerssh "github.com/hashicorp/packer/communicator/ssh"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
@ -35,9 +35,9 @@ func CommHost(
|
|||
|
||||
// If we have a floating IP, use that
|
||||
ip := state.Get("access_ip").(*floatingips.FloatingIP)
|
||||
if ip != nil && ip.IP != "" {
|
||||
log.Printf("[DEBUG] Using floating IP %s to connect", ip.IP)
|
||||
return ip.IP, nil
|
||||
if ip != nil && ip.FloatingIP != "" {
|
||||
log.Printf("[DEBUG] Using floating IP %s to connect", ip.FloatingIP)
|
||||
return ip.FloatingIP, nil
|
||||
}
|
||||
|
||||
if s.AccessIPv4 != "" {
|
||||
|
|
|
@ -4,17 +4,16 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/floatingips"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type StepAllocateIp struct {
|
||||
FloatingIpPool string
|
||||
FloatingIp string
|
||||
ReuseIps bool
|
||||
FloatingNetwork string
|
||||
FloatingIP string
|
||||
ReuseIPs bool
|
||||
}
|
||||
|
||||
func (s *StepAllocateIp) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
|
@ -23,123 +22,149 @@ func (s *StepAllocateIp) Run(_ context.Context, state multistep.StateBag) multis
|
|||
server := state.Get("server").(*servers.Server)
|
||||
|
||||
// We need the v2 compute client
|
||||
client, err := config.computeV2Client()
|
||||
computeClient, err := config.computeV2Client()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error initializing compute client: %s", err)
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
var instanceIp floatingips.FloatingIP
|
||||
// We need the v2 network client
|
||||
networkClient, err := config.networkV2Client()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error initializing network client: %s", err)
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
var instanceIP floatingips.FloatingIP
|
||||
|
||||
// This is here in case we error out before putting instanceIp into the
|
||||
// statebag below, because it is requested by Cleanup()
|
||||
state.Put("access_ip", &instanceIp)
|
||||
state.Put("access_ip", &instanceIP)
|
||||
|
||||
if s.FloatingIp != "" {
|
||||
instanceIp.IP = s.FloatingIp
|
||||
} else if s.FloatingIpPool != "" {
|
||||
// If ReuseIps is set to true and we have a free floating IP in
|
||||
// the pool, use it first rather than creating one
|
||||
if s.ReuseIps {
|
||||
ui.Say(fmt.Sprintf("Searching for unassociated floating IP in pool %s", s.FloatingIpPool))
|
||||
pager := floatingips.List(client)
|
||||
err := pager.EachPage(func(page pagination.Page) (bool, error) {
|
||||
candidates, err := floatingips.ExtractFloatingIPs(page)
|
||||
|
||||
if err != nil {
|
||||
return false, err // stop and throw error out
|
||||
}
|
||||
|
||||
for _, candidate := range candidates {
|
||||
if candidate.Pool != s.FloatingIpPool || candidate.InstanceID != "" {
|
||||
continue // move to next in list
|
||||
}
|
||||
|
||||
// In correct pool and able to be allocated
|
||||
instanceIp.IP = candidate.IP
|
||||
ui.Message(fmt.Sprintf("Selected floating IP: %s", instanceIp.IP))
|
||||
state.Put("floatingip_istemp", false)
|
||||
return false, nil // stop iterating over pages
|
||||
}
|
||||
return true, nil // try the next page
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error searching for floating ip from pool '%s'", s.FloatingIpPool)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
// Try to use floating IP provided by the user or find a free floating IP.
|
||||
if s.FloatingIP != "" {
|
||||
freeFloatingIP, err := CheckFloatingIP(networkClient, s.FloatingIP)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error using provided floating IP '%s': %s", s.FloatingIP, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if instanceIp.IP == "" {
|
||||
ui.Say(fmt.Sprintf("Creating floating IP..."))
|
||||
ui.Message(fmt.Sprintf("Pool: %s", s.FloatingIpPool))
|
||||
newIp, err := floatingips.Create(client, floatingips.CreateOpts{
|
||||
Pool: s.FloatingIpPool,
|
||||
}).Extract()
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating floating ip from pool '%s'", s.FloatingIpPool)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
instanceIp = *newIp
|
||||
ui.Message(fmt.Sprintf("Created floating IP: %s", instanceIp.IP))
|
||||
state.Put("floatingip_istemp", true)
|
||||
instanceIP = *freeFloatingIP
|
||||
ui.Message(fmt.Sprintf("Selected floating IP: '%s' (%s)", instanceIP.ID, instanceIP.FloatingIP))
|
||||
state.Put("floatingip_istemp", false)
|
||||
} else if s.ReuseIPs {
|
||||
// If ReuseIPs is set to true and we have a free floating IP, use it rather
|
||||
// than creating one.
|
||||
ui.Say(fmt.Sprint("Searching for unassociated floating IP"))
|
||||
freeFloatingIP, err := FindFreeFloatingIP(networkClient)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error searching for floating IP: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
instanceIP = *freeFloatingIP
|
||||
ui.Message(fmt.Sprintf("Selected floating IP: '%s' (%s)", instanceIP.ID, instanceIP.FloatingIP))
|
||||
state.Put("floatingip_istemp", false)
|
||||
}
|
||||
|
||||
if instanceIp.IP != "" {
|
||||
ui.Say(fmt.Sprintf("Associating floating IP with server..."))
|
||||
ui.Message(fmt.Sprintf("IP: %s", instanceIp.IP))
|
||||
err := floatingips.AssociateInstance(client, server.ID, floatingips.AssociateOpts{
|
||||
FloatingIP: instanceIp.IP,
|
||||
}).ExtractErr()
|
||||
// Create a new floating IP if it wasn't obtained in the previous step.
|
||||
if instanceIP.ID == "" {
|
||||
// Search for the external network that can be used for the floating IPs if
|
||||
// user hasn't provided any.
|
||||
floatingNetwork := s.FloatingNetwork
|
||||
if floatingNetwork == "" {
|
||||
ui.Say(fmt.Sprintf("Searching for the external network..."))
|
||||
externalNetwork, err := FindExternalNetwork(networkClient)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error searching the external network: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
floatingNetwork = externalNetwork.ID
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Creating floating IP..."))
|
||||
newIP, err := floatingips.Create(networkClient, floatingips.CreateOpts{
|
||||
FloatingNetworkID: floatingNetwork,
|
||||
}).Extract()
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error creating floating IP from floating network '%s': %s", floatingNetwork, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
instanceIP = *newIP
|
||||
ui.Message(fmt.Sprintf("Created floating IP: '%s' (%s)", instanceIP.ID, instanceIP.FloatingIP))
|
||||
state.Put("floatingip_istemp", true)
|
||||
}
|
||||
|
||||
// Assoctate a floating IP that was obtained in the previous steps.
|
||||
if instanceIP.ID != "" {
|
||||
ui.Say(fmt.Sprintf("Associating floating IP '%s' (%s) with instance port...",
|
||||
instanceIP.ID, instanceIP.FloatingIP))
|
||||
|
||||
portID, err := GetInstancePortID(computeClient, server.ID)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error getting interfaces of the instance '%s': %s", server.ID, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
_, err = floatingips.Update(networkClient, instanceIP.ID, floatingips.UpdateOpts{
|
||||
PortID: &portID,
|
||||
}).Extract()
|
||||
if err != nil {
|
||||
err := fmt.Errorf(
|
||||
"Error associating floating IP %s with instance: %s",
|
||||
instanceIp.IP, err)
|
||||
"Error associating floating IP '%s' (%s) with instance port '%s': %s",
|
||||
instanceIP.ID, instanceIP.FloatingIP, portID, err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Message(fmt.Sprintf(
|
||||
"Added floating IP %s to instance!", instanceIp.IP))
|
||||
"Added floating IP '%s' (%s) to instance!", instanceIP.ID, instanceIP.FloatingIP))
|
||||
}
|
||||
|
||||
state.Put("access_ip", &instanceIp)
|
||||
state.Put("access_ip", &instanceIP)
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepAllocateIp) Cleanup(state multistep.StateBag) {
|
||||
config := state.Get("config").(Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
instanceIp := state.Get("access_ip").(*floatingips.FloatingIP)
|
||||
instanceIP := state.Get("access_ip").(*floatingips.FloatingIP)
|
||||
|
||||
// Don't delete pool addresses we didn't allocate
|
||||
if state.Get("floatingip_istemp") == false {
|
||||
return
|
||||
}
|
||||
|
||||
// We need the v2 compute client
|
||||
client, err := config.computeV2Client()
|
||||
// We need the v2 network client
|
||||
client, err := config.networkV2Client()
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf(
|
||||
"Error deleting temporary floating IP %s", instanceIp.IP))
|
||||
"Error deleting temporary floating IP '%s' (%s)", instanceIP.ID, instanceIP.FloatingIP))
|
||||
return
|
||||
}
|
||||
|
||||
if s.FloatingIpPool != "" && instanceIp.ID != "" {
|
||||
if err := floatingips.Delete(client, instanceIp.ID).ExtractErr(); err != nil {
|
||||
if instanceIP.ID != "" {
|
||||
if err := floatingips.Delete(client, instanceIP.ID).ExtractErr(); err != nil {
|
||||
ui.Error(fmt.Sprintf(
|
||||
"Error deleting temporary floating IP %s", instanceIp.IP))
|
||||
"Error deleting temporary floating IP '%s' (%s)", instanceIP.ID, instanceIP.FloatingIP))
|
||||
return
|
||||
}
|
||||
|
||||
ui.Say(fmt.Sprintf("Deleted temporary floating IP %s", instanceIp.IP))
|
||||
ui.Say(fmt.Sprintf("Deleted temporary floating IP '%s' (%s)", instanceIP.ID, instanceIP.FloatingIP))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue