Skip to content

Commit 0e5c597

Browse files
Marc Paquettegeofffranks
authored andcommitted
add ASGPoller from policy-server that queries capi for security groups
Signed-off-by: Geoff Franks <[email protected]>
1 parent 60fb4c6 commit 0e5c597

File tree

4 files changed

+304
-40
lines changed

4 files changed

+304
-40
lines changed
Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package asg_syncer
22

33
import (
4-
"time"
4+
"encoding/json"
5+
"fmt"
56

67
"code.cloudfoundry.org/lager"
78
"code.cloudfoundry.org/policy-server/cc_client"
@@ -10,19 +11,63 @@ import (
1011
)
1112

1213
type ASGSyncer struct {
13-
Logger lager.Logger
14-
Store store.SecurityGroupsStore
15-
UAAClient uaa_client.UAAClient
16-
CCClient cc_client.CCClient
17-
RequestTimeout time.Duration
14+
Logger lager.Logger
15+
Store store.SecurityGroupsStore
16+
UAAClient uaa_client.UAAClient
17+
CCClient cc_client.CCClient
1818
}
1919

20-
func NewASGSyncer(logger lager.Logger, store store.SecurityGroupsStore, uaaClient uaa_client.UAAClient, ccClient cc_client.CCClient, requestTimeout time.Duration) *ASGSyncer {
20+
func NewASGSyncer(logger lager.Logger, store store.SecurityGroupsStore, uaaClient uaa_client.UAAClient, ccClient cc_client.CCClient) *ASGSyncer {
2121
return &ASGSyncer{
22-
Logger: logger,
23-
Store: store,
24-
UAAClient: uaaClient,
25-
CCClient: ccClient,
26-
RequestTimeout: requestTimeout,
22+
Logger: logger,
23+
Store: store,
24+
UAAClient: uaaClient,
25+
CCClient: ccClient,
2726
}
2827
}
28+
29+
func (a *ASGSyncer) Poll() error {
30+
token, err := a.UAAClient.GetToken()
31+
if err != nil {
32+
return err
33+
}
34+
ccSGs, err := a.CCClient.GetSecurityGroups(token)
35+
if err != nil {
36+
return err
37+
}
38+
39+
sgs := []store.SecurityGroup{}
40+
for _, ccSG := range ccSGs {
41+
stagingSpaces := []string{}
42+
for _, data := range ccSG.Relationships.StagingSpaces.Data {
43+
if guid, ok := data["guid"]; ok {
44+
stagingSpaces = append(stagingSpaces, guid)
45+
} else {
46+
return fmt.Errorf("no 'guid' found for staging-space-relationship on asg '%s'", ccSG.GUID)
47+
}
48+
}
49+
runningSpaces := []string{}
50+
for _, data := range ccSG.Relationships.RunningSpaces.Data {
51+
if guid, ok := data["guid"]; ok {
52+
runningSpaces = append(runningSpaces, guid)
53+
} else {
54+
return fmt.Errorf("no 'guid' found for running-space-relationship on asg '%s'", ccSG.GUID)
55+
}
56+
}
57+
rules, err := json.Marshal(ccSG.Rules)
58+
if err != nil {
59+
return fmt.Errorf("error converting rules to json for ASG '%s': %s", ccSG.GUID, err)
60+
}
61+
sgs = append(sgs, store.SecurityGroup{
62+
Guid: ccSG.GUID,
63+
Name: ccSG.Name,
64+
Rules: string(rules),
65+
StagingDefault: ccSG.GloballyEnabled.Staging,
66+
RunningDefault: ccSG.GloballyEnabled.Running,
67+
StagingSpaceGuids: stagingSpaces,
68+
RunningSpaceGuids: runningSpaces,
69+
})
70+
}
71+
72+
return a.Store.Replace(sgs)
73+
}

src/code.cloudfoundry.org/policy-server/asg_syncer/asg_syncer_test.go

Lines changed: 202 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package asg_syncer_test
22

33
import (
4+
"fmt"
45
"time"
56

67
"code.cloudfoundry.org/lager/lagertest"
78
"code.cloudfoundry.org/policy-server/asg_syncer"
9+
"code.cloudfoundry.org/policy-server/cc_client"
810
ccfakes "code.cloudfoundry.org/policy-server/cc_client/fakes"
11+
"code.cloudfoundry.org/policy-server/store"
912
dbfakes "code.cloudfoundry.org/policy-server/store/fakes"
1013
uaafakes "code.cloudfoundry.org/policy-server/uaa_client/fakes"
1114
. "github.com/onsi/ginkgo"
@@ -27,12 +30,206 @@ var _ = Describe("ASGSyncer", func() {
2730
fakeCCClient = &ccfakes.CCClient{}
2831
logger = lagertest.NewTestLogger("test")
2932
})
30-
It("pulls ASG data from CAPI and stores it in the SecurityGroupStore", func() {
31-
syncer := asg_syncer.NewASGSyncer(logger, fakeStore, fakeUAAClient, fakeCCClient, requestTimeout)
32-
Expect(syncer).ToNot(BeNil())
33+
Describe("NewASGSyncer()", func() {
34+
asgSyncer := asg_syncer.NewASGSyncer(logger, fakeStore, fakeUAAClient, fakeCCClient, requestTimeout)
3335

34-
By("querying CAPI for /v3/security_groups, handling pagination", func() {
35-
Expect(syncer).ToNot(BeNil())
36+
Expect(asgSyncer).To(Equal(&asg_syncer.ASGSyncer{
37+
Logger: logger,
38+
Store: fakeStore,
39+
UAAClient: fakeUAAClient,
40+
CCClient: fakeCCClient,
41+
RequestTimeout: requestTimeout,
42+
}))
43+
})
44+
Describe("asgSyncer.Sync()", func() {
45+
var asgSyncer *asg_syncer.ASGSyncer
46+
BeforeEach(func() {
47+
asgSyncer = asg_syncer.NewASGSyncer(logger, fakeStore, fakeUAAClient, fakeCCClient, requestTimeout)
48+
fakeUAAClient.GetTokenReturns("fake-token", nil)
49+
fakeCCClient.GetSecurityGroupsReturns([]cc_client.SecurityGroupResource{{
50+
GUID: "first-guid",
51+
Name: "asg-1",
52+
GloballyEnabled: cc_client.SecurityGroupGloballyEnabled{Running: true, Staging: true},
53+
Rules: []cc_client.SecurityGroupRule{{
54+
Protocol: "ICMP",
55+
Destination: "10.10.10.10/32",
56+
Code: 4,
57+
Type: 1,
58+
Description: "fake icmp rule",
59+
Log: false,
60+
}, {
61+
Protocol: "TCP",
62+
Destination: "20.20.20.20/32",
63+
Ports: "80-1024",
64+
Description: "fake tcp rule",
65+
Log: true,
66+
}},
67+
Relationships: cc_client.SecurityGroupRelationships{},
68+
}, {
69+
GUID: "second-guid",
70+
Name: "asg-2",
71+
Rules: []cc_client.SecurityGroupRule{{
72+
Protocol: "UDP",
73+
Destination: "0.0.0/0",
74+
Ports: "53",
75+
Description: "fake dns rule",
76+
Log: true,
77+
}},
78+
Relationships: cc_client.SecurityGroupRelationships{
79+
RunningSpaces: cc_client.SecurityGroupSpaceRelationship{
80+
Data: []map[string]string{{
81+
"guid": "space-1-guid",
82+
}, {
83+
"guid": "space-2-guid",
84+
}},
85+
},
86+
StagingSpaces: cc_client.SecurityGroupSpaceRelationship{
87+
Data: []map[string]string{{
88+
"guid": "space-3-guid",
89+
}},
90+
},
91+
},
92+
}}, nil)
93+
})
94+
It("pulls properly", func() {
95+
err := asgSyncer.Poll()
96+
Expect(err).To(BeNil())
97+
98+
By("Getting a UAA token", func() {
99+
Expect(fakeUAAClient.GetTokenCallCount()).To(Equal(1))
100+
})
101+
By("Requesting data from CAPI", func() {
102+
Expect(fakeCCClient.GetSecurityGroupsCallCount()).To(Equal(1))
103+
Expect(fakeCCClient.GetSecurityGroupsArgsForCall(0)).To(Equal("fake-token"))
104+
})
105+
By("calling Replace() on the store", func() {
106+
Expect(fakeStore.ReplaceCallCount()).To(Equal(1))
107+
})
108+
By("Translating cc_client security group resources into store security groups", func() {
109+
Expect(fakeStore.ReplaceArgsForCall(0)).To(Equal([]store.SecurityGroup{{
110+
Guid: "first-guid",
111+
Name: "asg-1",
112+
StagingDefault: true,
113+
RunningDefault: true,
114+
Rules: `[{"protocol":"ICMP","destination":"10.10.10.10/32","ports":"","type":1,"code":4,"description":"fake icmp rule","log":false},{"protocol":"TCP","destination":"20.20.20.20/32","ports":"80-1024","type":0,"code":0,"description":"fake tcp rule","log":true}]`,
115+
RunningSpaceGuids: []string{},
116+
StagingSpaceGuids: []string{},
117+
}, {
118+
Guid: "second-guid",
119+
Name: "asg-2",
120+
Rules: `[{"protocol":"UDP","destination":"0.0.0/0","ports":"53","type":0,"code":0,"description":"fake dns rule","log":true}]`,
121+
RunningSpaceGuids: []string{"space-1-guid", "space-2-guid"},
122+
StagingSpaceGuids: []string{"space-3-guid"},
123+
}}))
124+
})
125+
})
126+
Context("when acquiring a lock", func() {
127+
Context("and the lock is already taken", func() {
128+
It("doesn't poll capi or update the database", func() {
129+
err := asgSyncer.Poll()
130+
Expect(err).ToNot(HaveOccurred())
131+
Expect(fakeCCClient.GetSecurityGroupsCallCount()).To(Equal(0))
132+
Expect(fakeStore.ReplaceCallCount()).To(Equal(0))
133+
})
134+
It("debug logs that another policy-server has the lock", func() {
135+
Expect(logger.Logs()).To(Equal("something"))
136+
})
137+
})
138+
Context("and the lock is obtained successfully", func() {
139+
It("polls CAPI and updates the database", func() {
140+
err := asgSyncer.Poll()
141+
Expect(err).ToNot(HaveOccurred())
142+
Expect(fakeCCClient.GetSecurityGroupsCallCount()).To(Equal(1))
143+
Expect(fakeStore.ReplaceCallCount()).To(Equal(1))
144+
})
145+
It("info logs that it is the leader now", func() {
146+
Expect(logger.Logs()).To(Equal("something"))
147+
})
148+
})
149+
Context("and an error occurs obtaining the lock", func() {
150+
It("returns a relevant error", func() {
151+
err := asgSyncer.Poll()
152+
Expect(err).To(HaveOccurred())
153+
Expect(err.Error()).To(Equal("uaa error"))
154+
155+
})
156+
It("doesn't poll capi or update the database", func() {
157+
asgSyncer.Poll()
158+
Expect(fakeCCClient.GetSecurityGroupsCallCount()).To(Equal(0))
159+
Expect(fakeStore.ReplaceCallCount()).To(Equal(0))
160+
})
161+
})
162+
163+
})
164+
165+
Context("when errors occur", func() {
166+
167+
Context("getting a UAA token", func() {
168+
BeforeEach(func() {
169+
fakeUAAClient.GetTokenReturns("", fmt.Errorf("uaa error"))
170+
})
171+
It("returns a relevant error", func() {
172+
err := asgSyncer.Poll()
173+
Expect(err).To(HaveOccurred())
174+
Expect(err.Error()).To(Equal("uaa error"))
175+
176+
})
177+
It("doesn't poll capi or update the database", func() {
178+
asgSyncer.Poll()
179+
Expect(fakeCCClient.GetSecurityGroupsCallCount()).To(Equal(0))
180+
Expect(fakeStore.ReplaceCallCount()).To(Equal(0))
181+
})
182+
})
183+
184+
Context("polling CAPI", func() {
185+
BeforeEach(func() {
186+
fakeCCClient.GetSecurityGroupsReturns([]cc_client.SecurityGroupResource{}, fmt.Errorf("capi error"))
187+
})
188+
It("returns a relevant error", func() {
189+
err := asgSyncer.Poll()
190+
Expect(err).To(HaveOccurred())
191+
Expect(err.Error()).To(Equal("capi error"))
192+
})
193+
It("doesn't update the database", func() {
194+
asgSyncer.Poll()
195+
Expect(fakeStore.ReplaceCallCount()).To(Equal(0))
196+
})
197+
})
198+
199+
Context("when CAPI returns bad relationship data", func() {
200+
BeforeEach(func() {
201+
fakeCCClient.GetSecurityGroupsReturns([]cc_client.SecurityGroupResource{{
202+
Name: "bad-asg",
203+
GUID: "bad-asg-guid",
204+
Relationships: cc_client.SecurityGroupRelationships{
205+
RunningSpaces: cc_client.SecurityGroupSpaceRelationship{
206+
Data: []map[string]string{{"blarg": "blargh"}},
207+
},
208+
},
209+
}}, nil)
210+
})
211+
It("returns a relevant error", func() {
212+
err := asgSyncer.Poll()
213+
Expect(err).To(HaveOccurred())
214+
Expect(err.Error()).To(Equal("no 'guid' found for running-space-relationship on asg 'bad-asg-guid'"))
215+
216+
})
217+
It("doesn't update the database", func() {
218+
asgSyncer.Poll()
219+
Expect(fakeStore.ReplaceCallCount()).To(Equal(0))
220+
})
221+
})
222+
223+
Context("replacing data in the store", func() {
224+
BeforeEach(func() {
225+
fakeStore.ReplaceReturns(fmt.Errorf("db error"))
226+
})
227+
It("returns a relevant error", func() {
228+
err := asgSyncer.Poll()
229+
Expect(err).To(HaveOccurred())
230+
Expect(err.Error()).To(Equal("db error"))
231+
})
232+
})
36233
})
37234
})
38235
})

src/code.cloudfoundry.org/policy-server/cc_client/client.go

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -46,30 +46,32 @@ type GetSecurityGroupsResponse struct {
4646
Resources []SecurityGroupResource `json:"resources"`
4747
}
4848

49+
type SecurityGroupGloballyEnabled struct {
50+
Running bool `json:"running"`
51+
Staging bool `json:"staging"`
52+
}
53+
type SecurityGroupRule struct {
54+
Protocol string `json:"protocol"`
55+
Destination string `json:"destination"`
56+
Ports string `json:"ports"`
57+
Type int `json:"type"`
58+
Code int `json:"code"`
59+
Description string `json:"description"`
60+
Log bool `json:"log"`
61+
}
62+
type SecurityGroupRelationships struct {
63+
StagingSpaces SecurityGroupSpaceRelationship `json:"staging_spaces"`
64+
RunningSpaces SecurityGroupSpaceRelationship `json:"running_spacres"`
65+
}
66+
type SecurityGroupSpaceRelationship struct {
67+
Data []map[string]string `json:"data"`
68+
}
4969
type SecurityGroupResource struct {
50-
GUID string `json:"guid"`
51-
Name string `json:"name"`
52-
GloballyEnabled struct {
53-
Running bool `json:"running"`
54-
Staging bool `json:"staging"`
55-
} `json:"globally_enabled"`
56-
Rules []struct {
57-
Protocol string `json:"protocol"`
58-
Destination string `json:"destination"`
59-
Ports string `json:"ports"`
60-
Type int `json:"type"`
61-
Code int `json:"code"`
62-
Description string `json:"description"`
63-
Log bool `json:"log"`
64-
} `json:"rules"`
65-
Relationships struct {
66-
StagingSpaces struct {
67-
Data []map[string]string `json:"data"`
68-
} `json:"staging_spaces"`
69-
RunningSpaces struct {
70-
Data []map[string]string `json:"data"`
71-
} `json:"running_spaces"`
72-
} `json:"relationships"`
70+
GUID string `json:"guid"`
71+
Name string `json:"name"`
72+
GloballyEnabled SecurityGroupGloballyEnabled `json:"globally_enabled"`
73+
Rules []SecurityGroupRule `json:"rules"`
74+
Relationships SecurityGroupRelationships `json:"relationships"`
7375
}
7476

7577
type AppsV3Response struct {

0 commit comments

Comments
 (0)