blob: bfd0aed79f2757d6ebe103118a720dd78c1787aa [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"
10)
11
Jamie Hannaford8cdaa802014-11-03 15:11:39 +010012// List is the operation responsible for returning a paginated collection of
13// load balancer nodes. It requires the node ID, its parent load balancer ID,
14// and optional limit integer (passed in either as a pointer or a nil poitner).
Jamie Hannaford3cfa00a2014-11-03 11:16:35 +010015func List(client *gophercloud.ServiceClient, loadBalancerID int, limit *int) pagination.Pager {
16 url := rootURL(client, loadBalancerID)
Jamie Hannaford3cfa00a2014-11-03 11:16:35 +010017 if limit != nil {
18 url += fmt.Sprintf("?limit=%d", limit)
19 }
20
Jamie Hannaforded8b89a2014-11-03 12:24:19 +010021 return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
Jamie Hannaford3cfa00a2014-11-03 11:16:35 +010022 return NodePage{pagination.SinglePageBase(r)}
Jamie Hannaforded8b89a2014-11-03 12:24:19 +010023 })
24}
25
Jamie Hannaford8cdaa802014-11-03 15:11:39 +010026// CreateOptsBuilder is the interface responsible for generating the JSON
27// for a Create operation.
Jamie Hannaforded8b89a2014-11-03 12:24:19 +010028type CreateOptsBuilder interface {
29 ToNodeCreateMap() (map[string]interface{}, error)
30}
31
Jamie Hannaford8cdaa802014-11-03 15:11:39 +010032// CreateOpts is a slice of CreateOpt structs, that allow the user to create
33// multiple nodes in a single operation (one node per CreateOpt).
Jamie Hannaforded8b89a2014-11-03 12:24:19 +010034type CreateOpts []CreateOpt
35
Jamie Hannaford8cdaa802014-11-03 15:11:39 +010036// CreateOpt represents the options to create a single node.
Jamie Hannaforded8b89a2014-11-03 12:24:19 +010037type CreateOpt struct {
Jamie Hannaford8cdaa802014-11-03 15:11:39 +010038 // Required - the IP address or CIDR for this back-end node. It can either be
39 // a private IP (ServiceNet) or a public IP.
40 Address string
41
42 // Optional - the port on which traffic is sent and received.
43 Port int
44
45 // Optional - the condition of the node. See the consts in Results.go.
Jamie Hannaforded8b89a2014-11-03 12:24:19 +010046 Condition Condition
Jamie Hannaford8cdaa802014-11-03 15:11:39 +010047
48 // Optional - the type of the node. See the consts in Results.go.
49 Type Type
50
51 // Optional - a pointer to an integer between 0 and 100.
52 Weight *int
Jamie Hannaford00222d72014-11-03 13:58:52 +010053}
54
55func validateWeight(weight *int) error {
56 if weight != nil && (*weight > 100 || *weight < 0) {
57 return errors.New("Weight must be a valid int between 0 and 100")
58 }
59 return nil
Jamie Hannaforded8b89a2014-11-03 12:24:19 +010060}
61
Jamie Hannaford8cdaa802014-11-03 15:11:39 +010062// ToNodeCreateMap converts a slice of options into a map that can be used for
63// the JSON.
Jamie Hannaforded8b89a2014-11-03 12:24:19 +010064func (opts CreateOpts) ToNodeCreateMap() (map[string]interface{}, error) {
65 type nodeMap map[string]interface{}
66 nodes := []nodeMap{}
67
68 for k, v := range opts {
69 if v.Address == "" {
70 return nodeMap{}, fmt.Errorf("ID is a required attribute, none provided for %d CreateOpt element", k)
71 }
Jamie Hannaford00222d72014-11-03 13:58:52 +010072 if weightErr := validateWeight(v.Weight); weightErr != nil {
73 return nodeMap{}, weightErr
74 }
Jamie Hannaforded8b89a2014-11-03 12:24:19 +010075
76 node := make(map[string]interface{})
77 node["address"] = v.Address
78
79 if v.Port > 0 {
80 node["port"] = v.Port
81 }
82 if v.Condition != "" {
83 node["condition"] = v.Condition
84 }
85 if v.Type != "" {
86 node["type"] = v.Type
87 }
Jamie Hannaford00222d72014-11-03 13:58:52 +010088 if v.Weight != nil {
89 node["weight"] = &v.Weight
90 }
Jamie Hannaforded8b89a2014-11-03 12:24:19 +010091
92 nodes = append(nodes, node)
Jamie Hannaford3cfa00a2014-11-03 11:16:35 +010093 }
Jamie Hannaforded8b89a2014-11-03 12:24:19 +010094
95 return nodeMap{"nodes": nodes}, nil
96}
97
Jamie Hannaford8cdaa802014-11-03 15:11:39 +010098// Create is the operation responsible for creating a new node on a load
99// balancer. Since every load balancer exists in both ServiceNet and the public
100// Internet, both private and public IP addresses can be used for nodes.
101//
102// If nodes need time to boot up services before they become operational, you
103// can temporarily prevent traffic from being sent to that node by setting the
104// Condition field to DRAINING. Health checks will still be performed; but once
105// your node is ready, you can update its condition to ENABLED and have it
106// handle traffic.
Jamie Hannaforded8b89a2014-11-03 12:24:19 +0100107func Create(client *gophercloud.ServiceClient, loadBalancerID int, opts CreateOptsBuilder) CreateResult {
108 var res CreateResult
109
110 reqBody, err := opts.ToNodeCreateMap()
111 if err != nil {
112 res.Err = err
113 return res
114 }
115
116 resp, err := perigee.Request("POST", rootURL(client, loadBalancerID), perigee.Options{
117 MoreHeaders: client.AuthenticatedHeaders(),
118 ReqBody: &reqBody,
119 Results: &res.Body,
Jamie Hannaford703527e2014-11-05 12:38:15 +0100120 OkCodes: []int{202},
Jamie Hannaforded8b89a2014-11-03 12:24:19 +0100121 })
122 if err != nil {
123 res.Err = err
124 return res
125 }
126
127 pr, err := pagination.PageResultFrom(resp.HttpResponse)
128 if err != nil {
129 res.Err = err
130 return res
131 }
132
133 return CreateResult{pagination.SinglePageBase(pr)}
Jamie Hannaford3cfa00a2014-11-03 11:16:35 +0100134}
Jamie Hannaford16bebfc2014-11-03 12:52:30 +0100135
Jamie Hannaford8cdaa802014-11-03 15:11:39 +0100136// BulkDelete is the operation responsible for batch deleting multiple nodes in
137// a single operation. It accepts a slice of integer IDs and will remove them
138// from the load balancer. The maximum limit is 10 node removals at once.
Jamie Hannaford16bebfc2014-11-03 12:52:30 +0100139func BulkDelete(c *gophercloud.ServiceClient, loadBalancerID int, nodeIDs []int) DeleteResult {
140 var res DeleteResult
141
Jamie Hannaford0a9a6be2014-11-03 12:55:38 +0100142 if len(nodeIDs) > 10 || len(nodeIDs) == 0 {
143 res.Err = errors.New("You must provide a minimum of 1 and a maximum of 10 node IDs")
144 return res
145 }
146
Jamie Hannaford16bebfc2014-11-03 12:52:30 +0100147 url := rootURL(c, loadBalancerID)
Jamie Hannaford950561c2014-11-12 11:12:20 +0100148 url += gophercloud.IDSliceToQueryString("id", nodeIDs)
Jamie Hannaford16bebfc2014-11-03 12:52:30 +0100149
150 _, res.Err = perigee.Request("DELETE", url, perigee.Options{
151 MoreHeaders: c.AuthenticatedHeaders(),
152 OkCodes: []int{202},
153 })
154
155 return res
156}
Jamie Hannaford51175a02014-11-03 13:29:44 +0100157
Jamie Hannaford8cdaa802014-11-03 15:11:39 +0100158// Get is the operation responsible for showing details for a single node.
Jamie Hannaford51175a02014-11-03 13:29:44 +0100159func Get(c *gophercloud.ServiceClient, lbID, nodeID int) GetResult {
160 var res GetResult
161
162 _, res.Err = perigee.Request("GET", resourceURL(c, lbID, nodeID), perigee.Options{
163 MoreHeaders: c.AuthenticatedHeaders(),
164 Results: &res.Body,
165 OkCodes: []int{200},
166 })
167
168 return res
169}
Jamie Hannaford00222d72014-11-03 13:58:52 +0100170
Jamie Hannaford8cdaa802014-11-03 15:11:39 +0100171// UpdateOptsBuilder represents a type that can be converted into a JSON-like
172// map structure.
Jamie Hannaford00222d72014-11-03 13:58:52 +0100173type UpdateOptsBuilder interface {
174 ToNodeUpdateMap() (map[string]interface{}, error)
175}
176
Jamie Hannaford8cdaa802014-11-03 15:11:39 +0100177// UpdateOpts represent the options for updating an existing node.
Jamie Hannaford00222d72014-11-03 13:58:52 +0100178type UpdateOpts struct {
Jamie Hannaford8cdaa802014-11-03 15:11:39 +0100179 // Optional - the IP address or CIDR for this back-end node. It can either be
180 // a private IP (ServiceNet) or a public IP.
181 Address string
182
183 // Optional - the condition of the node. See the consts in Results.go.
Jamie Hannaford00222d72014-11-03 13:58:52 +0100184 Condition Condition
Jamie Hannaford8cdaa802014-11-03 15:11:39 +0100185
186 // Optional - the type of the node. See the consts in Results.go.
187 Type Type
188
189 // Optional - a pointer to an integer between 0 and 100.
190 Weight *int
Jamie Hannaford00222d72014-11-03 13:58:52 +0100191}
192
Jamie Hannaford8cdaa802014-11-03 15:11:39 +0100193// ToNodeUpdateMap converts an options struct into a JSON-like map.
Jamie Hannaford00222d72014-11-03 13:58:52 +0100194func (opts UpdateOpts) ToNodeUpdateMap() (map[string]interface{}, error) {
195 node := make(map[string]interface{})
196
197 if opts.Address != "" {
198 node["address"] = opts.Address
199 }
200 if opts.Condition != "" {
201 node["condition"] = opts.Condition
202 }
203 if opts.Weight != nil {
204 if weightErr := validateWeight(opts.Weight); weightErr != nil {
205 return node, weightErr
206 }
207 node["weight"] = &opts.Weight
208 }
209 if opts.Type != "" {
210 node["type"] = opts.Type
211 }
212
213 return map[string]interface{}{"node": node}, nil
214}
215
Jamie Hannaford8cdaa802014-11-03 15:11:39 +0100216// Update is the operation responsible for updating an existing node. A node's
217// IP, port, and status are immutable attributes and cannot be modified.
Jamie Hannaford00222d72014-11-03 13:58:52 +0100218func Update(c *gophercloud.ServiceClient, lbID, nodeID int, opts UpdateOptsBuilder) UpdateResult {
219 var res UpdateResult
220
221 reqBody, err := opts.ToNodeUpdateMap()
222 if err != nil {
223 res.Err = err
224 return res
225 }
226
227 _, res.Err = perigee.Request("PUT", resourceURL(c, lbID, nodeID), perigee.Options{
228 MoreHeaders: c.AuthenticatedHeaders(),
229 ReqBody: &reqBody,
230 OkCodes: []int{202},
231 })
232
233 return res
234}
Jamie Hannaford9f4870f2014-11-03 14:03:16 +0100235
Jamie Hannaford8cdaa802014-11-03 15:11:39 +0100236// Delete is the operation responsible for permanently deleting a node.
Jamie Hannaford9f4870f2014-11-03 14:03:16 +0100237func Delete(c *gophercloud.ServiceClient, lbID, nodeID int) DeleteResult {
238 var res DeleteResult
Jamie Hannaford9f4870f2014-11-03 14:03:16 +0100239 _, res.Err = perigee.Request("DELETE", resourceURL(c, lbID, nodeID), perigee.Options{
240 MoreHeaders: c.AuthenticatedHeaders(),
Jamie Hannaford703527e2014-11-05 12:38:15 +0100241 OkCodes: []int{202},
Jamie Hannaford9f4870f2014-11-03 14:03:16 +0100242 })
Jamie Hannaford9f4870f2014-11-03 14:03:16 +0100243 return res
244}
Jamie Hannaford1fac9dd2014-11-03 14:22:40 +0100245
Jamie Hannaford8cdaa802014-11-03 15:11:39 +0100246// ListEventsOptsBuilder allows extensions to add additional parameters to the
247// List request.
248type ListEventsOptsBuilder interface {
249 ToEventsListQuery() (string, error)
250}
251
252// ListEventsOpts allows the filtering and sorting of paginated collections through
253// the API.
254type ListEventsOpts struct {
255 Marker string `q:"marker"`
256 Limit int `q:"limit"`
257}
258
259// ToEventsListQuery formats a ListOpts into a query string.
260func (opts ListEventsOpts) ToEventsListQuery() (string, error) {
261 q, err := gophercloud.BuildQueryString(opts)
262 if err != nil {
263 return "", err
264 }
265 return q.String(), nil
266}
267
268// ListEvents is the operation responsible for listing all the events
269// associated with the activity between the node and the load balancer. The
270// events report errors found with the node. The detailedMessage provides the
271// detailed reason for the error.
Jamie Hannaford703527e2014-11-05 12:38:15 +0100272func ListEvents(client *gophercloud.ServiceClient, loadBalancerID int, opts ListEventsOptsBuilder) pagination.Pager {
273 url := eventsURL(client, loadBalancerID)
Jamie Hannaford8cdaa802014-11-03 15:11:39 +0100274
275 if opts != nil {
276 query, err := opts.ToEventsListQuery()
277 if err != nil {
278 return pagination.Pager{Err: err}
279 }
280 url += query
281 }
282
Jamie Hannaford1fac9dd2014-11-03 14:22:40 +0100283 return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
284 return NodeEventPage{pagination.SinglePageBase(r)}
285 })
286}