blob: 28c6bf2b68a0ef856bb9a2a51de5b9f00cec3843 [file] [log] [blame]
Jamie Hannafordb6927c12014-11-03 10:31:26 +01001package nodes
Jamie Hannaford3cfa00a2014-11-03 11:16:35 +01002
3import (
Jamie Hannaford0a9a6be2014-11-03 12:55:38 +01004 "errors"
Jamie Hannaford3cfa00a2014-11-03 11:16:35 +01005 "fmt"
6
Jamie Hannaforded8b89a2014-11-03 12:24:19 +01007 "github.com/racker/perigee"
Jamie Hannaford3cfa00a2014-11-03 11:16:35 +01008 "github.com/rackspace/gophercloud"
9 "github.com/rackspace/gophercloud/pagination"
Jamie Hannaford940159d2014-11-03 13:04:08 +010010 "github.com/rackspace/gophercloud/rackspace/lb/v1"
Jamie Hannaford3cfa00a2014-11-03 11:16:35 +010011)
12
Jamie Hannaford8cdaa802014-11-03 15:11:39 +010013// List is the operation responsible for returning a paginated collection of
14// load balancer nodes. It requires the node ID, its parent load balancer ID,
15// and optional limit integer (passed in either as a pointer or a nil poitner).
Jamie Hannaford3cfa00a2014-11-03 11:16:35 +010016func List(client *gophercloud.ServiceClient, loadBalancerID int, limit *int) pagination.Pager {
17 url := rootURL(client, loadBalancerID)
Jamie Hannaford3cfa00a2014-11-03 11:16:35 +010018 if limit != nil {
19 url += fmt.Sprintf("?limit=%d", limit)
20 }
21
Jamie Hannaforded8b89a2014-11-03 12:24:19 +010022 return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
Jamie Hannaford3cfa00a2014-11-03 11:16:35 +010023 return NodePage{pagination.SinglePageBase(r)}
Jamie Hannaforded8b89a2014-11-03 12:24:19 +010024 })
25}
26
Jamie Hannaford8cdaa802014-11-03 15:11:39 +010027// CreateOptsBuilder is the interface responsible for generating the JSON
28// for a Create operation.
Jamie Hannaforded8b89a2014-11-03 12:24:19 +010029type CreateOptsBuilder interface {
30 ToNodeCreateMap() (map[string]interface{}, error)
31}
32
Jamie Hannaford8cdaa802014-11-03 15:11:39 +010033// CreateOpts is a slice of CreateOpt structs, that allow the user to create
34// multiple nodes in a single operation (one node per CreateOpt).
Jamie Hannaforded8b89a2014-11-03 12:24:19 +010035type CreateOpts []CreateOpt
36
Jamie Hannaford8cdaa802014-11-03 15:11:39 +010037// CreateOpt represents the options to create a single node.
Jamie Hannaforded8b89a2014-11-03 12:24:19 +010038type CreateOpt struct {
Jamie Hannaford8cdaa802014-11-03 15:11:39 +010039 // Required - the IP address or CIDR for this back-end node. It can either be
40 // a private IP (ServiceNet) or a public IP.
41 Address string
42
43 // Optional - the port on which traffic is sent and received.
44 Port int
45
46 // Optional - the condition of the node. See the consts in Results.go.
Jamie Hannaforded8b89a2014-11-03 12:24:19 +010047 Condition Condition
Jamie Hannaford8cdaa802014-11-03 15:11:39 +010048
49 // Optional - the type of the node. See the consts in Results.go.
50 Type Type
51
52 // Optional - a pointer to an integer between 0 and 100.
53 Weight *int
Jamie Hannaford00222d72014-11-03 13:58:52 +010054}
55
56func validateWeight(weight *int) error {
57 if weight != nil && (*weight > 100 || *weight < 0) {
58 return errors.New("Weight must be a valid int between 0 and 100")
59 }
60 return nil
Jamie Hannaforded8b89a2014-11-03 12:24:19 +010061}
62
Jamie Hannaford8cdaa802014-11-03 15:11:39 +010063// ToNodeCreateMap converts a slice of options into a map that can be used for
64// the JSON.
Jamie Hannaforded8b89a2014-11-03 12:24:19 +010065func (opts CreateOpts) ToNodeCreateMap() (map[string]interface{}, error) {
66 type nodeMap map[string]interface{}
67 nodes := []nodeMap{}
68
69 for k, v := range opts {
70 if v.Address == "" {
71 return nodeMap{}, fmt.Errorf("ID is a required attribute, none provided for %d CreateOpt element", k)
72 }
Jamie Hannaford00222d72014-11-03 13:58:52 +010073 if weightErr := validateWeight(v.Weight); weightErr != nil {
74 return nodeMap{}, weightErr
75 }
Jamie Hannaforded8b89a2014-11-03 12:24:19 +010076
77 node := make(map[string]interface{})
78 node["address"] = v.Address
79
80 if v.Port > 0 {
81 node["port"] = v.Port
82 }
83 if v.Condition != "" {
84 node["condition"] = v.Condition
85 }
86 if v.Type != "" {
87 node["type"] = v.Type
88 }
Jamie Hannaford00222d72014-11-03 13:58:52 +010089 if v.Weight != nil {
90 node["weight"] = &v.Weight
91 }
Jamie Hannaforded8b89a2014-11-03 12:24:19 +010092
93 nodes = append(nodes, node)
Jamie Hannaford3cfa00a2014-11-03 11:16:35 +010094 }
Jamie Hannaforded8b89a2014-11-03 12:24:19 +010095
96 return nodeMap{"nodes": nodes}, nil
97}
98
Jamie Hannaford8cdaa802014-11-03 15:11:39 +010099// Create is the operation responsible for creating a new node on a load
100// balancer. Since every load balancer exists in both ServiceNet and the public
101// Internet, both private and public IP addresses can be used for nodes.
102//
103// If nodes need time to boot up services before they become operational, you
104// can temporarily prevent traffic from being sent to that node by setting the
105// Condition field to DRAINING. Health checks will still be performed; but once
106// your node is ready, you can update its condition to ENABLED and have it
107// handle traffic.
Jamie Hannaforded8b89a2014-11-03 12:24:19 +0100108func Create(client *gophercloud.ServiceClient, loadBalancerID int, opts CreateOptsBuilder) CreateResult {
109 var res CreateResult
110
111 reqBody, err := opts.ToNodeCreateMap()
112 if err != nil {
113 res.Err = err
114 return res
115 }
116
117 resp, err := perigee.Request("POST", rootURL(client, loadBalancerID), perigee.Options{
118 MoreHeaders: client.AuthenticatedHeaders(),
119 ReqBody: &reqBody,
120 Results: &res.Body,
121 OkCodes: []int{200},
122 })
123 if err != nil {
124 res.Err = err
125 return res
126 }
127
128 pr, err := pagination.PageResultFrom(resp.HttpResponse)
129 if err != nil {
130 res.Err = err
131 return res
132 }
133
134 return CreateResult{pagination.SinglePageBase(pr)}
Jamie Hannaford3cfa00a2014-11-03 11:16:35 +0100135}
Jamie Hannaford16bebfc2014-11-03 12:52:30 +0100136
Jamie Hannaford8cdaa802014-11-03 15:11:39 +0100137// BulkDelete is the operation responsible for batch deleting multiple nodes in
138// a single operation. It accepts a slice of integer IDs and will remove them
139// from the load balancer. The maximum limit is 10 node removals at once.
Jamie Hannaford16bebfc2014-11-03 12:52:30 +0100140func BulkDelete(c *gophercloud.ServiceClient, loadBalancerID int, nodeIDs []int) DeleteResult {
141 var res DeleteResult
142
Jamie Hannaford0a9a6be2014-11-03 12:55:38 +0100143 if len(nodeIDs) > 10 || len(nodeIDs) == 0 {
144 res.Err = errors.New("You must provide a minimum of 1 and a maximum of 10 node IDs")
145 return res
146 }
147
Jamie Hannaford16bebfc2014-11-03 12:52:30 +0100148 url := rootURL(c, loadBalancerID)
Jamie Hannaford940159d2014-11-03 13:04:08 +0100149 url += v1.IDSliceToQueryString("id", nodeIDs)
Jamie Hannaford16bebfc2014-11-03 12:52:30 +0100150
151 _, res.Err = perigee.Request("DELETE", url, perigee.Options{
152 MoreHeaders: c.AuthenticatedHeaders(),
153 OkCodes: []int{202},
154 })
155
156 return res
157}
Jamie Hannaford51175a02014-11-03 13:29:44 +0100158
Jamie Hannaford8cdaa802014-11-03 15:11:39 +0100159// Get is the operation responsible for showing details for a single node.
Jamie Hannaford51175a02014-11-03 13:29:44 +0100160func Get(c *gophercloud.ServiceClient, lbID, nodeID int) GetResult {
161 var res GetResult
162
163 _, res.Err = perigee.Request("GET", resourceURL(c, lbID, nodeID), perigee.Options{
164 MoreHeaders: c.AuthenticatedHeaders(),
165 Results: &res.Body,
166 OkCodes: []int{200},
167 })
168
169 return res
170}
Jamie Hannaford00222d72014-11-03 13:58:52 +0100171
Jamie Hannaford8cdaa802014-11-03 15:11:39 +0100172// IntToPointer is a function for converting integers into integer pointers.
173// This is useful when updating the weight of a node.
Jamie Hannaford00222d72014-11-03 13:58:52 +0100174func IntToPointer(i int) *int {
175 return &i
176}
177
Jamie Hannaford8cdaa802014-11-03 15:11:39 +0100178// UpdateOptsBuilder represents a type that can be converted into a JSON-like
179// map structure.
Jamie Hannaford00222d72014-11-03 13:58:52 +0100180type UpdateOptsBuilder interface {
181 ToNodeUpdateMap() (map[string]interface{}, error)
182}
183
Jamie Hannaford8cdaa802014-11-03 15:11:39 +0100184// UpdateOpts represent the options for updating an existing node.
Jamie Hannaford00222d72014-11-03 13:58:52 +0100185type UpdateOpts struct {
Jamie Hannaford8cdaa802014-11-03 15:11:39 +0100186 // Optional - the IP address or CIDR for this back-end node. It can either be
187 // a private IP (ServiceNet) or a public IP.
188 Address string
189
190 // Optional - the condition of the node. See the consts in Results.go.
Jamie Hannaford00222d72014-11-03 13:58:52 +0100191 Condition Condition
Jamie Hannaford8cdaa802014-11-03 15:11:39 +0100192
193 // Optional - the type of the node. See the consts in Results.go.
194 Type Type
195
196 // Optional - a pointer to an integer between 0 and 100.
197 Weight *int
Jamie Hannaford00222d72014-11-03 13:58:52 +0100198}
199
Jamie Hannaford8cdaa802014-11-03 15:11:39 +0100200// ToNodeUpdateMap converts an options struct into a JSON-like map.
Jamie Hannaford00222d72014-11-03 13:58:52 +0100201func (opts UpdateOpts) ToNodeUpdateMap() (map[string]interface{}, error) {
202 node := make(map[string]interface{})
203
204 if opts.Address != "" {
205 node["address"] = opts.Address
206 }
207 if opts.Condition != "" {
208 node["condition"] = opts.Condition
209 }
210 if opts.Weight != nil {
211 if weightErr := validateWeight(opts.Weight); weightErr != nil {
212 return node, weightErr
213 }
214 node["weight"] = &opts.Weight
215 }
216 if opts.Type != "" {
217 node["type"] = opts.Type
218 }
219
220 return map[string]interface{}{"node": node}, nil
221}
222
Jamie Hannaford8cdaa802014-11-03 15:11:39 +0100223// Update is the operation responsible for updating an existing node. A node's
224// IP, port, and status are immutable attributes and cannot be modified.
Jamie Hannaford00222d72014-11-03 13:58:52 +0100225func Update(c *gophercloud.ServiceClient, lbID, nodeID int, opts UpdateOptsBuilder) UpdateResult {
226 var res UpdateResult
227
228 reqBody, err := opts.ToNodeUpdateMap()
229 if err != nil {
230 res.Err = err
231 return res
232 }
233
234 _, res.Err = perigee.Request("PUT", resourceURL(c, lbID, nodeID), perigee.Options{
235 MoreHeaders: c.AuthenticatedHeaders(),
236 ReqBody: &reqBody,
237 OkCodes: []int{202},
238 })
239
240 return res
241}
Jamie Hannaford9f4870f2014-11-03 14:03:16 +0100242
Jamie Hannaford8cdaa802014-11-03 15:11:39 +0100243// Delete is the operation responsible for permanently deleting a node.
Jamie Hannaford9f4870f2014-11-03 14:03:16 +0100244func Delete(c *gophercloud.ServiceClient, lbID, nodeID int) DeleteResult {
245 var res DeleteResult
Jamie Hannaford9f4870f2014-11-03 14:03:16 +0100246 _, res.Err = perigee.Request("DELETE", resourceURL(c, lbID, nodeID), perigee.Options{
247 MoreHeaders: c.AuthenticatedHeaders(),
248 OkCodes: []int{200},
249 })
Jamie Hannaford9f4870f2014-11-03 14:03:16 +0100250 return res
251}
Jamie Hannaford1fac9dd2014-11-03 14:22:40 +0100252
Jamie Hannaford8cdaa802014-11-03 15:11:39 +0100253// ListEventsOptsBuilder allows extensions to add additional parameters to the
254// List request.
255type ListEventsOptsBuilder interface {
256 ToEventsListQuery() (string, error)
257}
258
259// ListEventsOpts allows the filtering and sorting of paginated collections through
260// the API.
261type ListEventsOpts struct {
262 Marker string `q:"marker"`
263 Limit int `q:"limit"`
264}
265
266// ToEventsListQuery formats a ListOpts into a query string.
267func (opts ListEventsOpts) ToEventsListQuery() (string, error) {
268 q, err := gophercloud.BuildQueryString(opts)
269 if err != nil {
270 return "", err
271 }
272 return q.String(), nil
273}
274
275// ListEvents is the operation responsible for listing all the events
276// associated with the activity between the node and the load balancer. The
277// events report errors found with the node. The detailedMessage provides the
278// detailed reason for the error.
279func ListEvents(client *gophercloud.ServiceClient, loadBalancerID, nodeID int, opts ListEventsOptsBuilder) pagination.Pager {
Jamie Hannaford1fac9dd2014-11-03 14:22:40 +0100280 url := eventsURL(client, loadBalancerID, nodeID)
Jamie Hannaford8cdaa802014-11-03 15:11:39 +0100281
282 if opts != nil {
283 query, err := opts.ToEventsListQuery()
284 if err != nil {
285 return pagination.Pager{Err: err}
286 }
287 url += query
288 }
289
Jamie Hannaford1fac9dd2014-11-03 14:22:40 +0100290 return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
291 return NodeEventPage{pagination.SinglePageBase(r)}
292 })
293}