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" |
| 10 | ) |
| 11 | |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 12 | // 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 Hannaford | 3cfa00a | 2014-11-03 11:16:35 +0100 | [diff] [blame] | 15 | func List(client *gophercloud.ServiceClient, loadBalancerID int, limit *int) pagination.Pager { |
| 16 | url := rootURL(client, loadBalancerID) |
Jamie Hannaford | 3cfa00a | 2014-11-03 11:16:35 +0100 | [diff] [blame] | 17 | if limit != nil { |
| 18 | url += fmt.Sprintf("?limit=%d", limit) |
| 19 | } |
| 20 | |
Jamie Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 21 | return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { |
Jamie Hannaford | 3cfa00a | 2014-11-03 11:16:35 +0100 | [diff] [blame] | 22 | return NodePage{pagination.SinglePageBase(r)} |
Jamie Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 23 | }) |
| 24 | } |
| 25 | |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 26 | // CreateOptsBuilder is the interface responsible for generating the JSON |
| 27 | // for a Create operation. |
Jamie Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 28 | type CreateOptsBuilder interface { |
| 29 | ToNodeCreateMap() (map[string]interface{}, error) |
| 30 | } |
| 31 | |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 32 | // 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 Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 34 | type CreateOpts []CreateOpt |
| 35 | |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 36 | // CreateOpt represents the options to create a single node. |
Jamie Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 37 | type CreateOpt struct { |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 38 | // 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 Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 46 | Condition Condition |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 47 | |
| 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 Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 53 | } |
| 54 | |
| 55 | func 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 Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 60 | } |
| 61 | |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 62 | // ToNodeCreateMap converts a slice of options into a map that can be used for |
| 63 | // the JSON. |
Jamie Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 64 | func (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 Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 72 | if weightErr := validateWeight(v.Weight); weightErr != nil { |
| 73 | return nodeMap{}, weightErr |
| 74 | } |
Jamie Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 75 | |
| 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 Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 88 | if v.Weight != nil { |
| 89 | node["weight"] = &v.Weight |
| 90 | } |
Jamie Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 91 | |
| 92 | nodes = append(nodes, node) |
Jamie Hannaford | 3cfa00a | 2014-11-03 11:16:35 +0100 | [diff] [blame] | 93 | } |
Jamie Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 94 | |
| 95 | return nodeMap{"nodes": nodes}, nil |
| 96 | } |
| 97 | |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 98 | // 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 Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 107 | func 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 Hannaford | 703527e | 2014-11-05 12:38:15 +0100 | [diff] [blame] | 120 | OkCodes: []int{202}, |
Jamie Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 121 | }) |
| 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 Hannaford | 3cfa00a | 2014-11-03 11:16:35 +0100 | [diff] [blame] | 134 | } |
Jamie Hannaford | 16bebfc | 2014-11-03 12:52:30 +0100 | [diff] [blame] | 135 | |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 136 | // 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 Hannaford | 16bebfc | 2014-11-03 12:52:30 +0100 | [diff] [blame] | 139 | func BulkDelete(c *gophercloud.ServiceClient, loadBalancerID int, nodeIDs []int) DeleteResult { |
| 140 | var res DeleteResult |
| 141 | |
Jamie Hannaford | 0a9a6be | 2014-11-03 12:55:38 +0100 | [diff] [blame] | 142 | 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 Hannaford | 16bebfc | 2014-11-03 12:52:30 +0100 | [diff] [blame] | 147 | url := rootURL(c, loadBalancerID) |
Jamie Hannaford | 950561c | 2014-11-12 11:12:20 +0100 | [diff] [blame] | 148 | url += gophercloud.IDSliceToQueryString("id", nodeIDs) |
Jamie Hannaford | 16bebfc | 2014-11-03 12:52:30 +0100 | [diff] [blame] | 149 | |
| 150 | _, res.Err = perigee.Request("DELETE", url, perigee.Options{ |
| 151 | MoreHeaders: c.AuthenticatedHeaders(), |
| 152 | OkCodes: []int{202}, |
| 153 | }) |
| 154 | |
| 155 | return res |
| 156 | } |
Jamie Hannaford | 51175a0 | 2014-11-03 13:29:44 +0100 | [diff] [blame] | 157 | |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 158 | // Get is the operation responsible for showing details for a single node. |
Jamie Hannaford | 51175a0 | 2014-11-03 13:29:44 +0100 | [diff] [blame] | 159 | func 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 Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 170 | |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 171 | // UpdateOptsBuilder represents a type that can be converted into a JSON-like |
| 172 | // map structure. |
Jamie Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 173 | type UpdateOptsBuilder interface { |
| 174 | ToNodeUpdateMap() (map[string]interface{}, error) |
| 175 | } |
| 176 | |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 177 | // UpdateOpts represent the options for updating an existing node. |
Jamie Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 178 | type UpdateOpts struct { |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 179 | // Optional - the condition of the node. See the consts in Results.go. |
Jamie Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 180 | Condition Condition |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 181 | |
| 182 | // Optional - the type of the node. See the consts in Results.go. |
| 183 | Type Type |
| 184 | |
| 185 | // Optional - a pointer to an integer between 0 and 100. |
| 186 | Weight *int |
Jamie Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 187 | } |
| 188 | |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 189 | // ToNodeUpdateMap converts an options struct into a JSON-like map. |
Jamie Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 190 | func (opts UpdateOpts) ToNodeUpdateMap() (map[string]interface{}, error) { |
| 191 | node := make(map[string]interface{}) |
| 192 | |
Jamie Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 193 | if opts.Condition != "" { |
| 194 | node["condition"] = opts.Condition |
| 195 | } |
| 196 | if opts.Weight != nil { |
| 197 | if weightErr := validateWeight(opts.Weight); weightErr != nil { |
| 198 | return node, weightErr |
| 199 | } |
| 200 | node["weight"] = &opts.Weight |
| 201 | } |
| 202 | if opts.Type != "" { |
| 203 | node["type"] = opts.Type |
| 204 | } |
| 205 | |
| 206 | return map[string]interface{}{"node": node}, nil |
| 207 | } |
| 208 | |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 209 | // Update is the operation responsible for updating an existing node. A node's |
| 210 | // IP, port, and status are immutable attributes and cannot be modified. |
Jamie Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 211 | func Update(c *gophercloud.ServiceClient, lbID, nodeID int, opts UpdateOptsBuilder) UpdateResult { |
| 212 | var res UpdateResult |
| 213 | |
| 214 | reqBody, err := opts.ToNodeUpdateMap() |
| 215 | if err != nil { |
| 216 | res.Err = err |
| 217 | return res |
| 218 | } |
| 219 | |
| 220 | _, res.Err = perigee.Request("PUT", resourceURL(c, lbID, nodeID), perigee.Options{ |
| 221 | MoreHeaders: c.AuthenticatedHeaders(), |
| 222 | ReqBody: &reqBody, |
| 223 | OkCodes: []int{202}, |
| 224 | }) |
| 225 | |
| 226 | return res |
| 227 | } |
Jamie Hannaford | 9f4870f | 2014-11-03 14:03:16 +0100 | [diff] [blame] | 228 | |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 229 | // Delete is the operation responsible for permanently deleting a node. |
Jamie Hannaford | 9f4870f | 2014-11-03 14:03:16 +0100 | [diff] [blame] | 230 | func Delete(c *gophercloud.ServiceClient, lbID, nodeID int) DeleteResult { |
| 231 | var res DeleteResult |
Jamie Hannaford | 9f4870f | 2014-11-03 14:03:16 +0100 | [diff] [blame] | 232 | _, res.Err = perigee.Request("DELETE", resourceURL(c, lbID, nodeID), perigee.Options{ |
| 233 | MoreHeaders: c.AuthenticatedHeaders(), |
Jamie Hannaford | 703527e | 2014-11-05 12:38:15 +0100 | [diff] [blame] | 234 | OkCodes: []int{202}, |
Jamie Hannaford | 9f4870f | 2014-11-03 14:03:16 +0100 | [diff] [blame] | 235 | }) |
Jamie Hannaford | 9f4870f | 2014-11-03 14:03:16 +0100 | [diff] [blame] | 236 | return res |
| 237 | } |
Jamie Hannaford | 1fac9dd | 2014-11-03 14:22:40 +0100 | [diff] [blame] | 238 | |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 239 | // ListEventsOptsBuilder allows extensions to add additional parameters to the |
| 240 | // List request. |
| 241 | type ListEventsOptsBuilder interface { |
| 242 | ToEventsListQuery() (string, error) |
| 243 | } |
| 244 | |
| 245 | // ListEventsOpts allows the filtering and sorting of paginated collections through |
| 246 | // the API. |
| 247 | type ListEventsOpts struct { |
| 248 | Marker string `q:"marker"` |
| 249 | Limit int `q:"limit"` |
| 250 | } |
| 251 | |
| 252 | // ToEventsListQuery formats a ListOpts into a query string. |
| 253 | func (opts ListEventsOpts) ToEventsListQuery() (string, error) { |
| 254 | q, err := gophercloud.BuildQueryString(opts) |
| 255 | if err != nil { |
| 256 | return "", err |
| 257 | } |
| 258 | return q.String(), nil |
| 259 | } |
| 260 | |
| 261 | // ListEvents is the operation responsible for listing all the events |
| 262 | // associated with the activity between the node and the load balancer. The |
| 263 | // events report errors found with the node. The detailedMessage provides the |
| 264 | // detailed reason for the error. |
Jamie Hannaford | 703527e | 2014-11-05 12:38:15 +0100 | [diff] [blame] | 265 | func ListEvents(client *gophercloud.ServiceClient, loadBalancerID int, opts ListEventsOptsBuilder) pagination.Pager { |
| 266 | url := eventsURL(client, loadBalancerID) |
Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 267 | |
| 268 | if opts != nil { |
| 269 | query, err := opts.ToEventsListQuery() |
| 270 | if err != nil { |
| 271 | return pagination.Pager{Err: err} |
| 272 | } |
| 273 | url += query |
| 274 | } |
| 275 | |
Jamie Hannaford | 1fac9dd | 2014-11-03 14:22:40 +0100 | [diff] [blame] | 276 | return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { |
| 277 | return NodeEventPage{pagination.SinglePageBase(r)} |
| 278 | }) |
| 279 | } |