| 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 |  | 
 | 7 | 	"github.com/rackspace/gophercloud" | 
 | 8 | 	"github.com/rackspace/gophercloud/pagination" | 
 | 9 | ) | 
 | 10 |  | 
| Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 11 | // List is the operation responsible for returning a paginated collection of | 
 | 12 | // load balancer nodes. It requires the node ID, its parent load balancer ID, | 
 | 13 | // 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] | 14 | func List(client *gophercloud.ServiceClient, loadBalancerID int, limit *int) pagination.Pager { | 
 | 15 | 	url := rootURL(client, loadBalancerID) | 
| Jamie Hannaford | 3cfa00a | 2014-11-03 11:16:35 +0100 | [diff] [blame] | 16 | 	if limit != nil { | 
 | 17 | 		url += fmt.Sprintf("?limit=%d", limit) | 
 | 18 | 	} | 
 | 19 |  | 
| Jamie Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 20 | 	return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { | 
| Jamie Hannaford | 3cfa00a | 2014-11-03 11:16:35 +0100 | [diff] [blame] | 21 | 		return NodePage{pagination.SinglePageBase(r)} | 
| Jamie Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 22 | 	}) | 
 | 23 | } | 
 | 24 |  | 
| Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 25 | // CreateOptsBuilder is the interface responsible for generating the JSON | 
 | 26 | // for a Create operation. | 
| Jamie Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 27 | type CreateOptsBuilder interface { | 
 | 28 | 	ToNodeCreateMap() (map[string]interface{}, error) | 
 | 29 | } | 
 | 30 |  | 
| Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 31 | // CreateOpts is a slice of CreateOpt structs, that allow the user to create | 
 | 32 | // multiple nodes in a single operation (one node per CreateOpt). | 
| Jamie Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 33 | type CreateOpts []CreateOpt | 
 | 34 |  | 
| Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 35 | // CreateOpt represents the options to create a single node. | 
| Jamie Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 36 | type CreateOpt struct { | 
| Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 37 | 	// Required - the IP address or CIDR for this back-end node. It can either be | 
 | 38 | 	// a private IP (ServiceNet) or a public IP. | 
 | 39 | 	Address string | 
 | 40 |  | 
 | 41 | 	// Optional - the port on which traffic is sent and received. | 
 | 42 | 	Port int | 
 | 43 |  | 
 | 44 | 	// Optional - the condition of the node. See the consts in Results.go. | 
| Jamie Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 45 | 	Condition Condition | 
| Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 46 |  | 
 | 47 | 	// Optional - the type of the node. See the consts in Results.go. | 
 | 48 | 	Type Type | 
 | 49 |  | 
 | 50 | 	// Optional - a pointer to an integer between 0 and 100. | 
 | 51 | 	Weight *int | 
| Jamie Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 52 | } | 
 | 53 |  | 
 | 54 | func validateWeight(weight *int) error { | 
 | 55 | 	if weight != nil && (*weight > 100 || *weight < 0) { | 
 | 56 | 		return errors.New("Weight must be a valid int between 0 and 100") | 
 | 57 | 	} | 
 | 58 | 	return nil | 
| Jamie Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 59 | } | 
 | 60 |  | 
| Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 61 | // ToNodeCreateMap converts a slice of options into a map that can be used for | 
 | 62 | // the JSON. | 
| Jamie Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 63 | func (opts CreateOpts) ToNodeCreateMap() (map[string]interface{}, error) { | 
 | 64 | 	type nodeMap map[string]interface{} | 
 | 65 | 	nodes := []nodeMap{} | 
 | 66 |  | 
 | 67 | 	for k, v := range opts { | 
 | 68 | 		if v.Address == "" { | 
 | 69 | 			return nodeMap{}, fmt.Errorf("ID is a required attribute, none provided for %d CreateOpt element", k) | 
 | 70 | 		} | 
| Jamie Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 71 | 		if weightErr := validateWeight(v.Weight); weightErr != nil { | 
 | 72 | 			return nodeMap{}, weightErr | 
 | 73 | 		} | 
| Jamie Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 74 |  | 
 | 75 | 		node := make(map[string]interface{}) | 
 | 76 | 		node["address"] = v.Address | 
 | 77 |  | 
 | 78 | 		if v.Port > 0 { | 
 | 79 | 			node["port"] = v.Port | 
 | 80 | 		} | 
 | 81 | 		if v.Condition != "" { | 
 | 82 | 			node["condition"] = v.Condition | 
 | 83 | 		} | 
 | 84 | 		if v.Type != "" { | 
 | 85 | 			node["type"] = v.Type | 
 | 86 | 		} | 
| Jamie Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 87 | 		if v.Weight != nil { | 
 | 88 | 			node["weight"] = &v.Weight | 
 | 89 | 		} | 
| Jamie Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 90 |  | 
 | 91 | 		nodes = append(nodes, node) | 
| Jamie Hannaford | 3cfa00a | 2014-11-03 11:16:35 +0100 | [diff] [blame] | 92 | 	} | 
| Jamie Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 93 |  | 
 | 94 | 	return nodeMap{"nodes": nodes}, nil | 
 | 95 | } | 
 | 96 |  | 
| Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 97 | // Create is the operation responsible for creating a new node on a load | 
 | 98 | // balancer. Since every load balancer exists in both ServiceNet and the public | 
 | 99 | // Internet, both private and public IP addresses can be used for nodes. | 
 | 100 | // | 
 | 101 | // If nodes need time to boot up services before they become operational, you | 
 | 102 | // can temporarily prevent traffic from being sent to that node by setting the | 
 | 103 | // Condition field to DRAINING. Health checks will still be performed; but once | 
 | 104 | // your node is ready, you can update its condition to ENABLED and have it | 
 | 105 | // handle traffic. | 
| Jamie Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 106 | func Create(client *gophercloud.ServiceClient, loadBalancerID int, opts CreateOptsBuilder) CreateResult { | 
 | 107 | 	var res CreateResult | 
 | 108 |  | 
 | 109 | 	reqBody, err := opts.ToNodeCreateMap() | 
 | 110 | 	if err != nil { | 
 | 111 | 		res.Err = err | 
 | 112 | 		return res | 
 | 113 | 	} | 
 | 114 |  | 
| Jamie Hannaford | 5497f94 | 2015-03-25 11:55:51 +0100 | [diff] [blame] | 115 | 	resp, err := client.Post(rootURL(client, loadBalancerID), reqBody, &res.Body, nil) | 
 | 116 |  | 
| Jamie Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 117 | 	if err != nil { | 
 | 118 | 		res.Err = err | 
 | 119 | 		return res | 
 | 120 | 	} | 
 | 121 |  | 
| Ash Wilson | 2c749a0 | 2015-06-24 10:16:16 -0400 | [diff] [blame] | 122 | 	pr := pagination.PageResultFromParsed(resp, res.Body) | 
| Jamie Hannaford | ed8b89a | 2014-11-03 12:24:19 +0100 | [diff] [blame] | 123 | 	return CreateResult{pagination.SinglePageBase(pr)} | 
| Jamie Hannaford | 3cfa00a | 2014-11-03 11:16:35 +0100 | [diff] [blame] | 124 | } | 
| Jamie Hannaford | 16bebfc | 2014-11-03 12:52:30 +0100 | [diff] [blame] | 125 |  | 
| Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 126 | // BulkDelete is the operation responsible for batch deleting multiple nodes in | 
 | 127 | // a single operation. It accepts a slice of integer IDs and will remove them | 
 | 128 | // 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] | 129 | func BulkDelete(c *gophercloud.ServiceClient, loadBalancerID int, nodeIDs []int) DeleteResult { | 
 | 130 | 	var res DeleteResult | 
 | 131 |  | 
| Jamie Hannaford | 0a9a6be | 2014-11-03 12:55:38 +0100 | [diff] [blame] | 132 | 	if len(nodeIDs) > 10 || len(nodeIDs) == 0 { | 
 | 133 | 		res.Err = errors.New("You must provide a minimum of 1 and a maximum of 10 node IDs") | 
 | 134 | 		return res | 
 | 135 | 	} | 
 | 136 |  | 
| Jamie Hannaford | 16bebfc | 2014-11-03 12:52:30 +0100 | [diff] [blame] | 137 | 	url := rootURL(c, loadBalancerID) | 
| Jamie Hannaford | 950561c | 2014-11-12 11:12:20 +0100 | [diff] [blame] | 138 | 	url += gophercloud.IDSliceToQueryString("id", nodeIDs) | 
| Jamie Hannaford | 16bebfc | 2014-11-03 12:52:30 +0100 | [diff] [blame] | 139 |  | 
| Jamie Hannaford | 5497f94 | 2015-03-25 11:55:51 +0100 | [diff] [blame] | 140 | 	_, res.Err = c.Delete(url, nil) | 
| Jamie Hannaford | 16bebfc | 2014-11-03 12:52:30 +0100 | [diff] [blame] | 141 | 	return res | 
 | 142 | } | 
| Jamie Hannaford | 51175a0 | 2014-11-03 13:29:44 +0100 | [diff] [blame] | 143 |  | 
| Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 144 | // Get is the operation responsible for showing details for a single node. | 
| Jamie Hannaford | 51175a0 | 2014-11-03 13:29:44 +0100 | [diff] [blame] | 145 | func Get(c *gophercloud.ServiceClient, lbID, nodeID int) GetResult { | 
 | 146 | 	var res GetResult | 
| Jamie Hannaford | 5497f94 | 2015-03-25 11:55:51 +0100 | [diff] [blame] | 147 | 	_, res.Err = c.Get(resourceURL(c, lbID, nodeID), &res.Body, nil) | 
| Jamie Hannaford | 51175a0 | 2014-11-03 13:29:44 +0100 | [diff] [blame] | 148 | 	return res | 
 | 149 | } | 
| Jamie Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 150 |  | 
| Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 151 | // UpdateOptsBuilder represents a type that can be converted into a JSON-like | 
 | 152 | // map structure. | 
| Jamie Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 153 | type UpdateOptsBuilder interface { | 
 | 154 | 	ToNodeUpdateMap() (map[string]interface{}, error) | 
 | 155 | } | 
 | 156 |  | 
| Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 157 | // UpdateOpts represent the options for updating an existing node. | 
| Jamie Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 158 | type UpdateOpts struct { | 
| Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 159 | 	// Optional - the condition of the node. See the consts in Results.go. | 
| Jamie Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 160 | 	Condition Condition | 
| Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 161 |  | 
 | 162 | 	// Optional - the type of the node. See the consts in Results.go. | 
 | 163 | 	Type Type | 
 | 164 |  | 
 | 165 | 	// Optional - a pointer to an integer between 0 and 100. | 
 | 166 | 	Weight *int | 
| Jamie Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 167 | } | 
 | 168 |  | 
| Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 169 | // ToNodeUpdateMap converts an options struct into a JSON-like map. | 
| Jamie Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 170 | func (opts UpdateOpts) ToNodeUpdateMap() (map[string]interface{}, error) { | 
 | 171 | 	node := make(map[string]interface{}) | 
 | 172 |  | 
| Jamie Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 173 | 	if opts.Condition != "" { | 
 | 174 | 		node["condition"] = opts.Condition | 
 | 175 | 	} | 
 | 176 | 	if opts.Weight != nil { | 
 | 177 | 		if weightErr := validateWeight(opts.Weight); weightErr != nil { | 
 | 178 | 			return node, weightErr | 
 | 179 | 		} | 
 | 180 | 		node["weight"] = &opts.Weight | 
 | 181 | 	} | 
 | 182 | 	if opts.Type != "" { | 
 | 183 | 		node["type"] = opts.Type | 
 | 184 | 	} | 
 | 185 |  | 
 | 186 | 	return map[string]interface{}{"node": node}, nil | 
 | 187 | } | 
 | 188 |  | 
| Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 189 | // Update is the operation responsible for updating an existing node. A node's | 
 | 190 | // IP, port, and status are immutable attributes and cannot be modified. | 
| Jamie Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 191 | func Update(c *gophercloud.ServiceClient, lbID, nodeID int, opts UpdateOptsBuilder) UpdateResult { | 
 | 192 | 	var res UpdateResult | 
 | 193 |  | 
 | 194 | 	reqBody, err := opts.ToNodeUpdateMap() | 
 | 195 | 	if err != nil { | 
 | 196 | 		res.Err = err | 
 | 197 | 		return res | 
 | 198 | 	} | 
 | 199 |  | 
| Jamie Hannaford | 5497f94 | 2015-03-25 11:55:51 +0100 | [diff] [blame] | 200 | 	_, res.Err = c.Put(resourceURL(c, lbID, nodeID), reqBody, nil, nil) | 
| Jamie Hannaford | 00222d7 | 2014-11-03 13:58:52 +0100 | [diff] [blame] | 201 | 	return res | 
 | 202 | } | 
| Jamie Hannaford | 9f4870f | 2014-11-03 14:03:16 +0100 | [diff] [blame] | 203 |  | 
| Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 204 | // Delete is the operation responsible for permanently deleting a node. | 
| Jamie Hannaford | 9f4870f | 2014-11-03 14:03:16 +0100 | [diff] [blame] | 205 | func Delete(c *gophercloud.ServiceClient, lbID, nodeID int) DeleteResult { | 
 | 206 | 	var res DeleteResult | 
| Jamie Hannaford | 5497f94 | 2015-03-25 11:55:51 +0100 | [diff] [blame] | 207 | 	_, res.Err = c.Delete(resourceURL(c, lbID, nodeID), nil) | 
| Jamie Hannaford | 9f4870f | 2014-11-03 14:03:16 +0100 | [diff] [blame] | 208 | 	return res | 
 | 209 | } | 
| Jamie Hannaford | 1fac9dd | 2014-11-03 14:22:40 +0100 | [diff] [blame] | 210 |  | 
| Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 211 | // ListEventsOptsBuilder allows extensions to add additional parameters to the | 
 | 212 | // List request. | 
 | 213 | type ListEventsOptsBuilder interface { | 
 | 214 | 	ToEventsListQuery() (string, error) | 
 | 215 | } | 
 | 216 |  | 
 | 217 | // ListEventsOpts allows the filtering and sorting of paginated collections through | 
 | 218 | // the API. | 
 | 219 | type ListEventsOpts struct { | 
 | 220 | 	Marker string `q:"marker"` | 
 | 221 | 	Limit  int    `q:"limit"` | 
 | 222 | } | 
 | 223 |  | 
 | 224 | // ToEventsListQuery formats a ListOpts into a query string. | 
 | 225 | func (opts ListEventsOpts) ToEventsListQuery() (string, error) { | 
 | 226 | 	q, err := gophercloud.BuildQueryString(opts) | 
 | 227 | 	if err != nil { | 
 | 228 | 		return "", err | 
 | 229 | 	} | 
 | 230 | 	return q.String(), nil | 
 | 231 | } | 
 | 232 |  | 
 | 233 | // ListEvents is the operation responsible for listing all the events | 
 | 234 | // associated with the activity between the node and the load balancer. The | 
 | 235 | // events report errors found with the node. The detailedMessage provides the | 
 | 236 | // detailed reason for the error. | 
| Jamie Hannaford | 703527e | 2014-11-05 12:38:15 +0100 | [diff] [blame] | 237 | func ListEvents(client *gophercloud.ServiceClient, loadBalancerID int, opts ListEventsOptsBuilder) pagination.Pager { | 
 | 238 | 	url := eventsURL(client, loadBalancerID) | 
| Jamie Hannaford | 8cdaa80 | 2014-11-03 15:11:39 +0100 | [diff] [blame] | 239 |  | 
 | 240 | 	if opts != nil { | 
 | 241 | 		query, err := opts.ToEventsListQuery() | 
 | 242 | 		if err != nil { | 
 | 243 | 			return pagination.Pager{Err: err} | 
 | 244 | 		} | 
 | 245 | 		url += query | 
 | 246 | 	} | 
 | 247 |  | 
| Jamie Hannaford | 1fac9dd | 2014-11-03 14:22:40 +0100 | [diff] [blame] | 248 | 	return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page { | 
 | 249 | 		return NodeEventPage{pagination.SinglePageBase(r)} | 
 | 250 | 	}) | 
 | 251 | } |