Skip to content

Commit 290741f

Browse files
Merge pull request #84 from tboerger/openstack-fixes
Detect openstack ip without floating ips, add image and flavor by id
2 parents 0f593cc + a6327b9 commit 290741f

12 files changed

+313
-100
lines changed

cmd/drone-autoscaler/main.go

+1
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@ func setupProvider(c config.Config) (autoscaler.Provider, error) {
315315
openstack.WithImage(c.OpenStack.Image),
316316
openstack.WithRegion(c.OpenStack.Region),
317317
openstack.WithFlavor(c.OpenStack.Flavor),
318+
openstack.WithNetwork(c.OpenStack.Network),
318319
openstack.WithFloatingIpPool(c.OpenStack.Pool),
319320
openstack.WithSSHKey(c.OpenStack.SSHKey),
320321
openstack.WithSecurityGroup(c.OpenStack.SecurityGroup...),

config/config.go

+1
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ type (
192192
Region string `envconfig:"OS_REGION_NAME"`
193193
Image string
194194
Flavor string
195+
Network string
195196
Pool string `envconfig:"DRONE_OPENSTACK_IP_POOL"`
196197
SecurityGroup []string `split_words:"true"`
197198
SSHKey string

config/load_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ func TestLoad(t *testing.T) {
143143
"DRONE_PACKET_USERDATA_FILE": "/path/to/cloud/init.yml",
144144
"DRONE_PACKET_HOSTNAME": "agent",
145145
"DRONE_PACKET_TAGS": "drone,agent,prod",
146+
"DRONE_OPENSTACK_NETWORK": "my-subnet-1",
146147
"DRONE_OPENSTACK_IP_POOL": "ext-ips-1",
147148
"DRONE_OPENSTACK_SSHKEY": "drone-ci",
148149
"DRONE_OPENSTACK_SECURITY_GROUP": "secgrp-feedface",
@@ -321,6 +322,7 @@ var jsonConfig = []byte(`{
321322
"Region": "sto-01",
322323
"Image": "ubuntu-16.04-server-latest",
323324
"Flavor": "t1.medium",
325+
"Network": "my-subnet-1",
324326
"Pool": "ext-ips-1",
325327
"SecurityGroup": [
326328
"secgrp-feedface"

drivers/openstack/create.go

+86-21
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ import (
1010

1111
"github.com/drone/autoscaler"
1212
"github.com/drone/autoscaler/logger"
13-
1413
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips"
1514
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs"
1615
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
16+
"github.com/gophercloud/gophercloud/openstack/networking/v2/networks"
17+
"github.com/gophercloud/gophercloud/pagination"
1718
)
1819

1920
// Create creates an OpenStack instance
@@ -27,18 +28,37 @@ func (p *provider) Create(ctx context.Context, opts autoscaler.InstanceCreateOpt
2728
if err != nil {
2829
return nil, err
2930
}
30-
// Make a floating ip to attach.
31-
ip, err := floatingips.Create(p.computeClient, floatingips.CreateOpts{
32-
Pool: p.pool,
33-
}).Extract()
34-
if err != nil {
35-
return nil, err
31+
32+
logger := logger.FromContext(ctx).
33+
WithField("region", p.region).
34+
WithField("image", p.image).
35+
WithField("flavor", p.flavor).
36+
WithField("network", p.network).
37+
WithField("pool", p.pool).
38+
WithField("name", opts.Name)
39+
40+
logger.Debugln("instance create")
41+
42+
nets := make([]servers.Network, 0)
43+
44+
if p.network != "" {
45+
network, err := networks.Get(p.networkClient, p.network).Extract()
46+
if err != nil {
47+
logger.WithError(err).
48+
Debugln("failed to find network")
49+
return nil, err
50+
}
51+
52+
nets = append(nets, servers.Network{
53+
UUID: network.ID,
54+
})
3655
}
3756

3857
serverCreateOpts := servers.CreateOpts{
3958
Name: opts.Name,
40-
ImageName: p.image,
41-
FlavorName: p.flavor,
59+
ImageRef: p.image,
60+
FlavorRef: p.flavor,
61+
Networks: nets,
4262
UserData: buf.Bytes(),
4363
ServiceClient: p.computeClient,
4464
Metadata: p.metadata,
@@ -50,35 +70,80 @@ func (p *provider) Create(ctx context.Context, opts autoscaler.InstanceCreateOpt
5070
}
5171
server, err := servers.Create(p.computeClient, createOpts).Extract()
5272
if err != nil {
53-
floatingips.Delete(p.computeClient, ip.ID)
73+
logger.WithError(err).
74+
Debugln("failed to create server")
5475
return nil, err
5576
}
56-
logger := logger.FromContext(ctx).
57-
WithField("region", p.region).
58-
WithField("image", p.image).
59-
WithField("sizes", p.flavor).
60-
WithField("name", opts.Name)
6177

6278
err = servers.WaitForStatus(p.computeClient, server.ID, "ACTIVE", 300)
6379
if err != nil {
80+
logger.WithError(err).
81+
Debugln("failed waiting for server")
6482
return nil, err
6583
}
66-
floatingips.AssociateInstance(p.computeClient, server.ID, floatingips.AssociateOpts{
67-
FloatingIP: ip.IP,
68-
})
69-
70-
logger.Debugln("instance create")
7184

7285
instance := &autoscaler.Instance{
7386
Provider: autoscaler.ProviderOpenStack,
7487
ID: server.ID,
7588
Name: server.Name,
7689
Region: p.region,
77-
Address: ip.IP,
7890
Image: p.image,
7991
Size: p.flavor,
8092
}
8193

94+
if p.network != "" {
95+
network, err := networks.Get(p.networkClient, p.network).Extract()
96+
if err != nil {
97+
logger.WithError(err).
98+
Debugln("failed to find network")
99+
return nil, err
100+
}
101+
102+
if err := servers.ListAddresses(p.computeClient, server.ID).EachPage(func(page pagination.Page) (bool, error) {
103+
result, err := servers.ExtractAddresses(page)
104+
if err != nil {
105+
return false, err
106+
}
107+
108+
for name, addresses := range result {
109+
if name == network.Name {
110+
for _, address := range addresses {
111+
instance.Address = address.Address
112+
return true, nil
113+
}
114+
}
115+
116+
}
117+
118+
return false, nil
119+
}); err != nil {
120+
logger.WithError(err).
121+
Debugln("failed to fetch address")
122+
return nil, err
123+
}
124+
}
125+
126+
if p.pool != "" {
127+
ip, err := floatingips.Create(p.computeClient, floatingips.CreateOpts{
128+
Pool: p.pool,
129+
}).Extract()
130+
if err != nil {
131+
logger.WithError(err).
132+
Debugln("failed to create floating ip")
133+
return nil, err
134+
}
135+
136+
if err := floatingips.AssociateInstance(p.computeClient, server.ID, floatingips.AssociateOpts{
137+
FloatingIP: ip.IP,
138+
}).ExtractErr(); err != nil {
139+
logger.WithError(err).
140+
Debugln("failed to associate floating ip")
141+
return nil, err
142+
}
143+
144+
instance.Address = ip.IP
145+
}
146+
82147
logger.
83148
WithField("name", instance.Name).
84149
WithField("ip", instance.Address).

drivers/openstack/create_test.go

+33-9
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ package openstack
66

77
import (
88
"context"
9-
"github.com/drone/autoscaler"
10-
"github.com/h2non/gock"
119
"os"
1210
"testing"
11+
12+
"github.com/drone/autoscaler"
13+
"github.com/h2non/gock"
1314
)
1415

1516
func TestCreate(t *testing.T) {
@@ -31,6 +32,21 @@ func TestCreate(t *testing.T) {
3132
SetHeader("X-Subject-Token", authToken).
3233
BodyString(string(tokenResp1))
3334

35+
authResp2 := helperLoad(t, "authresp1.json")
36+
gock.New("http://ops.my.cloud").
37+
Get("/identity").
38+
Reply(300).
39+
SetHeader("Content-Type", "application/json").
40+
BodyString(string(authResp2))
41+
42+
tokenResp2 := helperLoad(t, "tokenresp1.json")
43+
gock.New("http://ops.my.cloud").
44+
Post("/identity/v3/auth/tokens").
45+
Reply(201).
46+
SetHeader("Content-Type", "application/json").
47+
SetHeader("X-Subject-Token", authToken).
48+
BodyString(string(tokenResp2))
49+
3450
fipResp1 := helperLoad(t, "fipresp1.json")
3551
gock.New("http://ops.my.cloud").
3652
Post("/compute/v2.1/os-floating-ips").
@@ -113,6 +129,7 @@ func TestAuthFail(t *testing.T) {
113129
if err != nil {
114130
t.Error("Unable to set OS_PASSWORD")
115131
}
132+
116133
authResp1 := helperLoad(t, "authresp1.json")
117134
gock.New("http://ops.my.cloud").
118135
Get("/identity").
@@ -160,13 +177,20 @@ func TestCreateFail(t *testing.T) {
160177
SetHeader("X-Subject-Token", authToken).
161178
BodyString(string(tokenResp1))
162179

163-
fipResp1 := helperLoad(t, "fipresp1.json")
180+
authResp2 := helperLoad(t, "authresp1.json")
164181
gock.New("http://ops.my.cloud").
165-
Post("/compute/v2.1/os-floating-ips").
166-
MatchHeader("X-Auth-Token", authToken).
167-
Reply(200).
182+
Get("/identity").
183+
Reply(300).
168184
SetHeader("Content-Type", "application/json").
169-
BodyString(string(fipResp1))
185+
BodyString(string(authResp2))
186+
187+
tokenResp2 := helperLoad(t, "tokenresp1.json")
188+
gock.New("http://ops.my.cloud").
189+
Post("/identity/v3/auth/tokens").
190+
Reply(201).
191+
SetHeader("Content-Type", "application/json").
192+
SetHeader("X-Subject-Token", authToken).
193+
BodyString(string(tokenResp2))
170194

171195
imageListResp := helperLoad(t, "imagelistresp1.json")
172196
gock.New("http://ops.my.cloud").
@@ -240,7 +264,7 @@ func testInstance(instance *autoscaler.Instance) func(t *testing.T) {
240264
if want, got := instance.Address, "172.24.4.5"; got != want {
241265
t.Errorf("Want instance IP %q, got %q", want, got)
242266
}
243-
if want, got := instance.Image, "ubuntu-16.04-server-latest"; got != want {
267+
if want, got := instance.Image, "4ef19958-ee2d-44a7-a100-de0b8afdbc8e"; got != want {
244268
t.Errorf("Want instance ID %q, got %q", want, got)
245269
}
246270
if want, got := instance.ID, "56046f6d-3184-495b-938b-baa450db970d"; got != want {
@@ -255,7 +279,7 @@ func testInstance(instance *autoscaler.Instance) func(t *testing.T) {
255279
if want, got := instance.Region, "RegionOne"; got != want {
256280
t.Errorf("Want instance Region %q, got %q", want, got)
257281
}
258-
if want, got := instance.Size, "m1.small"; got != want {
282+
if want, got := instance.Size, "29e3cce3-d771-4220-80fe-3edf0e8dd466"; got != want {
259283
t.Errorf("Want instance Size %q, got %q", want, got)
260284
}
261285
}

drivers/openstack/destroy.go

+44-19
Original file line numberDiff line numberDiff line change
@@ -6,62 +6,87 @@ package openstack
66

77
import (
88
"context"
9+
"fmt"
910

1011
"github.com/drone/autoscaler"
1112
"github.com/drone/autoscaler/logger"
1213

1314
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips"
1415
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
16+
"github.com/gophercloud/gophercloud/pagination"
1517
)
1618

1719
func (p *provider) Destroy(ctx context.Context, instance *autoscaler.Instance) error {
1820
logger := logger.FromContext(ctx).
1921
WithField("region", instance.Region).
2022
WithField("image", instance.Image).
21-
WithField("size", instance.Size).
23+
WithField("flavor", instance.Size).
2224
WithField("name", instance.Name)
2325

2426
logger.Debugln("deleting instance")
2527

26-
_ = p.deleteFloatingIps(instance)
28+
err := p.deleteFloatingIps(instance)
29+
if err != nil {
30+
logger.WithError(err).
31+
Debugln("failed to delete floating ips")
2732

28-
err := servers.Delete(p.computeClient, instance.ID).ExtractErr()
33+
return err
34+
}
35+
36+
err = servers.Delete(p.computeClient, instance.ID).ExtractErr()
2937
if err == nil {
3038
logger.Debugln("instance deleted")
3139
return nil
3240
}
3341

42+
if err.Error() == "Resource not found" {
43+
logger.WithError(err).
44+
Debugln("instance does not exist")
45+
return autoscaler.ErrInstanceNotFound
46+
}
47+
3448
logger.WithError(err).
35-
Errorln("deleting instance failed, attempting to force")
49+
Errorln("attempting to force delete")
3650

3751
err = servers.ForceDelete(p.computeClient, instance.ID).ExtractErr()
38-
3952
if err == nil {
4053
logger.Debugln("instance deleted")
4154
return nil
4255
}
4356

57+
if err.Error() == "Resource not found" {
58+
logger.WithError(err).
59+
Debugln("instance does not exist")
60+
return autoscaler.ErrInstanceNotFound
61+
}
62+
4463
logger.WithError(err).
4564
Errorln("force-deleting instance failed")
4665

4766
return err
4867
}
4968

5069
func (p *provider) deleteFloatingIps(instance *autoscaler.Instance) error {
51-
floatingips.DisassociateInstance(p.computeClient, instance.ID, floatingips.DisassociateOpts{
52-
FloatingIP: instance.Address,
53-
})
54-
// Remove our allocated ip from the pool.
55-
allPages, err := floatingips.List(p.computeClient).AllPages()
56-
ips, err := floatingips.ExtractFloatingIPs(allPages)
57-
if err != nil {
58-
return err
59-
}
60-
for _, fip := range ips {
61-
if fip.InstanceID == instance.ID {
62-
floatingips.Delete(p.computeClient, fip.ID)
70+
return floatingips.List(p.computeClient).EachPage(func(page pagination.Page) (bool, error) {
71+
ips, err := floatingips.ExtractFloatingIPs(page)
72+
if err != nil {
73+
return false, err
6374
}
64-
}
6575

66-
return nil
76+
for _, ip := range ips {
77+
if ip.InstanceID == instance.ID {
78+
if err := floatingips.DisassociateInstance(p.computeClient, instance.ID, floatingips.DisassociateOpts{
79+
FloatingIP: ip.IP,
80+
}).ExtractErr(); err != nil {
81+
return false, fmt.Errorf("failed to disassociate floating ip: %s", err)
82+
}
83+
84+
if err := floatingips.Delete(p.computeClient, ip.ID).ExtractErr(); err != nil {
85+
return false, fmt.Errorf("failed to delete floating ip: %s", err)
86+
}
87+
}
88+
}
89+
90+
return true, nil
91+
})
6792
}

0 commit comments

Comments
 (0)