Jamie Hannaford | b6927c1 | 2014-11-03 10:31:26 +0100 | [diff] [blame] | 1 | package nodes |
Jamie Hannaford | 3cfa00a | 2014-11-03 11:16:35 +0100 | [diff] [blame] | 2 | |
| 3 | import ( |
Jamie Hannaford | 0a9a6be | 2014-11-03 12:55:38 +0100 | [diff] [blame] | 4 | "errors" |
Jamie Hannaford | 3cfa00a | 2014-11-03 11:16:35 +0100 | [diff] [blame] | 5 | "fmt" |
| 6 | |
Jamie Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 7 | "github.com/racker/perigee" |
Jamie Hannaford | 3cfa00a | 2014-11-03 11:16:35 +0100 | [diff] [blame] | 8 | "github.com/rackspace/gophercloud" |
| 9 | "github.com/rackspace/gophercloud/pagination" |
Jamie Hannaford | 940159d | 2014-11-03 13:04:08 +0100 | [diff] [blame] | 10 | "github.com/rackspace/gophercloud/rackspace/lb/v1" |
Jamie Hannaford | 3cfa00a | 2014-11-03 11:16:35 +0100 | [diff] [blame] | 11 | ) |
| 12 | |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame^] | 13 | // 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 Hannaford | 3cfa00a | 2014-11-03 11:16:35 +0100 | [diff] [blame] | 16 | func List(client *gophercloud.ServiceClient, loadBalancerID int, limit *int) pagination.Pager { |
| 17 | url := rootURL(client, loadBalancerID) |
Jamie Hannaford | 3cfa00a | 2014-11-03 11:16:35 +0100 | [diff] [blame] | 18 | if limit != nil { |
| 19 | url += fmt.Sprintf("?limit=%d", limit) |
| 20 | } |
| 21 | |
Jamie Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 22 | return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { |
Jamie Hannaford | 3cfa00a | 2014-11-03 11:16:35 +0100 | [diff] [blame] | 23 | return NodePage{pagination.SinglePageBase(r)} |
Jamie Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 24 | }) |
| 25 | } |
| 26 | |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame^] | 27 | // CreateOptsBuilder is the interface responsible for generating the JSON |
| 28 | // for a Create operation. |
Jamie Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 29 | type CreateOptsBuilder interface { |
| 30 | ToNodeCreateMap() (map[string]interface{}, error) |
| 31 | } |
| 32 | |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame^] | 33 | // 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 Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 35 | type CreateOpts []CreateOpt |
| 36 | |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame^] | 37 | // CreateOpt represents the options to create a single node. |
Jamie Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 38 | type CreateOpt struct { |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame^] | 39 | // 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 Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 47 | Condition Condition |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame^] | 48 | |
| 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 Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 54 | } |
| 55 | |
| 56 | func 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 Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 61 | } |
| 62 | |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame^] | 63 | // ToNodeCreateMap converts a slice of options into a map that can be used for |
| 64 | // the JSON. |
Jamie Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 65 | func (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 Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 73 | if weightErr := validateWeight(v.Weight); weightErr != nil { |
| 74 | return nodeMap{}, weightErr |
| 75 | } |
Jamie Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 76 | |
| 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 Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 89 | if v.Weight != nil { |
| 90 | node["weight"] = &v.Weight |
| 91 | } |
Jamie Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 92 | |
| 93 | nodes = append(nodes, node) |
Jamie Hannaford | 3cfa00a | 2014-11-03 11:16:35 +0100 | [diff] [blame] | 94 | } |
Jamie Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 95 | |
| 96 | return nodeMap{"nodes": nodes}, nil |
| 97 | } |
| 98 | |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame^] | 99 | // 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 Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 108 | func 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 Hannaford | 3cfa00a | 2014-11-03 11:16:35 +0100 | [diff] [blame] | 135 | } |
Jamie Hannaford | 16bebfc | 2014-11-03 12:52:30 +0100 | [diff] [blame] | 136 | |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame^] | 137 | // 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 Hannaford | 16bebfc | 2014-11-03 12:52:30 +0100 | [diff] [blame] | 140 | func BulkDelete(c *gophercloud.ServiceClient, loadBalancerID int, nodeIDs []int) DeleteResult { |
| 141 | var res DeleteResult |
| 142 | |
Jamie Hannaford | 0a9a6be | 2014-11-03 12:55:38 +0100 | [diff] [blame] | 143 | 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 Hannaford | 16bebfc | 2014-11-03 12:52:30 +0100 | [diff] [blame] | 148 | url := rootURL(c, loadBalancerID) |
Jamie Hannaford | 940159d | 2014-11-03 13:04:08 +0100 | [diff] [blame] | 149 | url += v1.IDSliceToQueryString("id", nodeIDs) |
Jamie Hannaford | 16bebfc | 2014-11-03 12:52:30 +0100 | [diff] [blame] | 150 | |
| 151 | _, res.Err = perigee.Request("DELETE", url, perigee.Options{ |
| 152 | MoreHeaders: c.AuthenticatedHeaders(), |
| 153 | OkCodes: []int{202}, |
| 154 | }) |
| 155 | |
| 156 | return res |
| 157 | } |
Jamie Hannaford | 51175a0 | 2014-11-03 13:29:44 +0100 | [diff] [blame] | 158 | |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame^] | 159 | // Get is the operation responsible for showing details for a single node. |
Jamie Hannaford | 51175a0 | 2014-11-03 13:29:44 +0100 | [diff] [blame] | 160 | func 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 Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 171 | |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame^] | 172 | // IntToPointer is a function for converting integers into integer pointers. |
| 173 | // This is useful when updating the weight of a node. |
Jamie Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 174 | func IntToPointer(i int) *int { |
| 175 | return &i |
| 176 | } |
| 177 | |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame^] | 178 | // UpdateOptsBuilder represents a type that can be converted into a JSON-like |
| 179 | // map structure. |
Jamie Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 180 | type UpdateOptsBuilder interface { |
| 181 | ToNodeUpdateMap() (map[string]interface{}, error) |
| 182 | } |
| 183 | |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame^] | 184 | // UpdateOpts represent the options for updating an existing node. |
Jamie Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 185 | type UpdateOpts struct { |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame^] | 186 | // 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 Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 191 | Condition Condition |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame^] | 192 | |
| 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 Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 198 | } |
| 199 | |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame^] | 200 | // ToNodeUpdateMap converts an options struct into a JSON-like map. |
Jamie Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 201 | func (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 Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame^] | 223 | // 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 Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 225 | func 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 Hannaford | 9f4870f | 2014-11-03 14:03:16 +0100 | [diff] [blame] | 242 | |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame^] | 243 | // Delete is the operation responsible for permanently deleting a node. |
Jamie Hannaford | 9f4870f | 2014-11-03 14:03:16 +0100 | [diff] [blame] | 244 | func Delete(c *gophercloud.ServiceClient, lbID, nodeID int) DeleteResult { |
| 245 | var res DeleteResult |
Jamie Hannaford | 9f4870f | 2014-11-03 14:03:16 +0100 | [diff] [blame] | 246 | _, res.Err = perigee.Request("DELETE", resourceURL(c, lbID, nodeID), perigee.Options{ |
| 247 | MoreHeaders: c.AuthenticatedHeaders(), |
| 248 | OkCodes: []int{200}, |
| 249 | }) |
Jamie Hannaford | 9f4870f | 2014-11-03 14:03:16 +0100 | [diff] [blame] | 250 | return res |
| 251 | } |
Jamie Hannaford | 1fac9dd | 2014-11-03 14:22:40 +0100 | [diff] [blame] | 252 | |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame^] | 253 | // ListEventsOptsBuilder allows extensions to add additional parameters to the |
| 254 | // List request. |
| 255 | type ListEventsOptsBuilder interface { |
| 256 | ToEventsListQuery() (string, error) |
| 257 | } |
| 258 | |
| 259 | // ListEventsOpts allows the filtering and sorting of paginated collections through |
| 260 | // the API. |
| 261 | type ListEventsOpts struct { |
| 262 | Marker string `q:"marker"` |
| 263 | Limit int `q:"limit"` |
| 264 | } |
| 265 | |
| 266 | // ToEventsListQuery formats a ListOpts into a query string. |
| 267 | func (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. |
| 279 | func ListEvents(client *gophercloud.ServiceClient, loadBalancerID, nodeID int, opts ListEventsOptsBuilder) pagination.Pager { |
Jamie Hannaford | 1fac9dd | 2014-11-03 14:22:40 +0100 | [diff] [blame] | 280 | url := eventsURL(client, loadBalancerID, nodeID) |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame^] | 281 | |
| 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 Hannaford | 1fac9dd | 2014-11-03 14:22:40 +0100 | [diff] [blame] | 290 | return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { |
| 291 | return NodeEventPage{pagination.SinglePageBase(r)} |
| 292 | }) |
| 293 | } |