1package clusters
2
3import (
4	"fmt"
5	"net/http"
6
7	"github.com/gophercloud/gophercloud"
8	"github.com/gophercloud/gophercloud/pagination"
9)
10
11// AdjustmentType represents valid values for resizing a cluster.
12type AdjustmentType string
13
14const (
15	ExactCapacityAdjustment      AdjustmentType = "EXACT_CAPACITY"
16	ChangeInCapacityAdjustment   AdjustmentType = "CHANGE_IN_CAPACITY"
17	ChangeInPercentageAdjustment AdjustmentType = "CHANGE_IN_PERCENTAGE"
18)
19
20// RecoveryAction represents valid values for recovering a cluster.
21type RecoveryAction string
22
23const (
24	RebootRecovery   RecoveryAction = "REBOOT"
25	RebuildRecovery  RecoveryAction = "REBUILD"
26	RecreateRecovery RecoveryAction = "RECREATE"
27)
28
29// CreateOptsBuilder allows extensions to add additional parameters
30// to the Create request.
31type CreateOptsBuilder interface {
32	ToClusterCreateMap() (map[string]interface{}, error)
33}
34
35// CreateOpts represents options used to create a cluster.
36type CreateOpts struct {
37	Name            string                 `json:"name" required:"true"`
38	DesiredCapacity int                    `json:"desired_capacity"`
39	ProfileID       string                 `json:"profile_id" required:"true"`
40	MinSize         *int                   `json:"min_size,omitempty"`
41	Timeout         int                    `json:"timeout,omitempty"`
42	MaxSize         int                    `json:"max_size,omitempty"`
43	Metadata        map[string]interface{} `json:"metadata,omitempty"`
44	Config          map[string]interface{} `json:"config,omitempty"`
45}
46
47// ToClusterCreateMap constructs a request body from CreateOpts.
48func (opts CreateOpts) ToClusterCreateMap() (map[string]interface{}, error) {
49	return gophercloud.BuildRequestBody(opts, "cluster")
50}
51
52// Create requests the creation of a new cluster.
53func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
54	b, err := opts.ToClusterCreateMap()
55	if err != nil {
56		r.Err = err
57		return
58	}
59	var result *http.Response
60	result, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{
61		OkCodes: []int{200, 201, 202},
62	})
63
64	if r.Err == nil {
65		r.Header = result.Header
66	}
67
68	return
69}
70
71// Get retrieves details of a single cluster.
72func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
73	var result *http.Response
74	result, r.Err = client.Get(getURL(client, id), &r.Body, &gophercloud.RequestOpts{
75		OkCodes: []int{200},
76	})
77
78	if r.Err == nil {
79		r.Header = result.Header
80	}
81
82	return
83}
84
85// ListOptsBuilder allows extensions to add additional parameters to
86// the List request.
87type ListOptsBuilder interface {
88	ToClusterListQuery() (string, error)
89}
90
91// ListOpts represents options to list clusters.
92type ListOpts struct {
93	Limit         int    `q:"limit"`
94	Marker        string `q:"marker"`
95	Sort          string `q:"sort"`
96	GlobalProject *bool  `q:"global_project"`
97	Name          string `q:"name,omitempty"`
98	Status        string `q:"status,omitempty"`
99}
100
101// ToClusterListQuery formats a ListOpts into a query string.
102func (opts ListOpts) ToClusterListQuery() (string, error) {
103	q, err := gophercloud.BuildQueryString(opts)
104	return q.String(), err
105}
106
107// List instructs OpenStack to provide a list of clusters.
108func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
109	url := listURL(client)
110	if opts != nil {
111		query, err := opts.ToClusterListQuery()
112		if err != nil {
113			return pagination.Pager{Err: err}
114		}
115		url += query
116	}
117
118	return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
119		return ClusterPage{pagination.LinkedPageBase{PageResult: r}}
120	})
121}
122
123// UpdateOptsBuilder allows extensions to add additional parameters to the
124// Update request.
125type UpdateOptsBuilder interface {
126	ToClusterUpdateMap() (map[string]interface{}, error)
127}
128
129// UpdateOpts represents options to update a cluster.
130type UpdateOpts struct {
131	Config      string                 `json:"config,omitempty"`
132	Name        string                 `json:"name,omitempty"`
133	ProfileID   string                 `json:"profile_id,omitempty"`
134	Timeout     *int                   `json:"timeout,omitempty"`
135	Metadata    map[string]interface{} `json:"metadata,omitempty"`
136	ProfileOnly *bool                  `json:"profile_only,omitempty"`
137}
138
139// ToClusterUpdateMap assembles a request body based on the contents of
140// UpdateOpts.
141func (opts UpdateOpts) ToClusterUpdateMap() (map[string]interface{}, error) {
142	b, err := gophercloud.BuildRequestBody(opts, "cluster")
143	if err != nil {
144		return nil, err
145	}
146	return b, nil
147}
148
149// Update will update an existing cluster.
150func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
151	b, err := opts.ToClusterUpdateMap()
152	if err != nil {
153		r.Err = err
154		return r
155	}
156
157	var result *http.Response
158	result, r.Err = client.Patch(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
159		OkCodes: []int{200, 202},
160	})
161
162	if r.Err == nil {
163		r.Header = result.Header
164	}
165	return
166}
167
168// Delete deletes the specified cluster ID.
169func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
170	var result *http.Response
171	result, r.Err = client.Delete(deleteURL(client, id), nil)
172	if r.Err == nil {
173		r.Header = result.Header
174	}
175	return
176}
177
178// ResizeOptsBuilder allows extensions to add additional parameters to the
179// resize request.
180type ResizeOptsBuilder interface {
181	ToClusterResizeMap() (map[string]interface{}, error)
182}
183
184// ResizeOpts represents options for resizing a cluster.
185type ResizeOpts struct {
186	AdjustmentType AdjustmentType `json:"adjustment_type,omitempty"`
187	Number         interface{}    `json:"number,omitempty"`
188	MinSize        *int           `json:"min_size,omitempty"`
189	MaxSize        *int           `json:"max_size,omitempty"`
190	MinStep        *int           `json:"min_step,omitempty"`
191	Strict         *bool          `json:"strict,omitempty"`
192}
193
194// ToClusterResizeMap constructs a request body from ResizeOpts.
195func (opts ResizeOpts) ToClusterResizeMap() (map[string]interface{}, error) {
196	if opts.AdjustmentType != "" && opts.Number == nil {
197		return nil, fmt.Errorf("Number field MUST NOT be empty when AdjustmentType field used")
198	}
199
200	switch opts.Number.(type) {
201	case nil, int, int32, int64:
202		// Valid type. Always allow
203	case float32, float64:
204		if opts.AdjustmentType != ChangeInPercentageAdjustment {
205			return nil, fmt.Errorf("Only ChangeInPercentageAdjustment allows float value for Number field")
206		}
207	default:
208		return nil, fmt.Errorf("Number field must be either int, float, or omitted")
209	}
210
211	return gophercloud.BuildRequestBody(opts, "resize")
212}
213
214func Resize(client *gophercloud.ServiceClient, id string, opts ResizeOptsBuilder) (r ActionResult) {
215	b, err := opts.ToClusterResizeMap()
216	if err != nil {
217		r.Err = err
218		return
219	}
220
221	var result *http.Response
222	result, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
223		OkCodes: []int{200, 201, 202},
224	})
225	if r.Err == nil {
226		r.Header = result.Header
227	}
228
229	return
230}
231
232// ScaleInOptsBuilder allows extensions to add additional parameters to the
233// ScaleIn request.
234type ScaleInOptsBuilder interface {
235	ToClusterScaleInMap() (map[string]interface{}, error)
236}
237
238// ScaleInOpts represents options used to scale-in a cluster.
239type ScaleInOpts struct {
240	Count *int `json:"count,omitempty"`
241}
242
243// ToClusterScaleInMap constructs a request body from ScaleInOpts.
244func (opts ScaleInOpts) ToClusterScaleInMap() (map[string]interface{}, error) {
245	return gophercloud.BuildRequestBody(opts, "scale_in")
246}
247
248// ScaleIn will reduce the capacity of a cluster.
249func ScaleIn(client *gophercloud.ServiceClient, id string, opts ScaleInOptsBuilder) (r ActionResult) {
250	b, err := opts.ToClusterScaleInMap()
251	if err != nil {
252		r.Err = err
253		return
254	}
255	var result *http.Response
256	result, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
257		OkCodes: []int{200, 201, 202},
258	})
259	if r.Err == nil {
260		r.Header = result.Header
261	}
262
263	return
264}
265
266// ScaleOutOptsBuilder allows extensions to add additional parameters to the
267// ScaleOut request.
268type ScaleOutOptsBuilder interface {
269	ToClusterScaleOutMap() (map[string]interface{}, error)
270}
271
272// ScaleOutOpts represents options used to scale-out a cluster.
273type ScaleOutOpts struct {
274	Count int `json:"count,omitempty"`
275}
276
277// ToClusterScaleOutMap constructs a request body from ScaleOutOpts.
278func (opts ScaleOutOpts) ToClusterScaleOutMap() (map[string]interface{}, error) {
279	return gophercloud.BuildRequestBody(opts, "scale_out")
280}
281
282// ScaleOut will increase the capacity of a cluster.
283func ScaleOut(client *gophercloud.ServiceClient, id string, opts ScaleOutOptsBuilder) (r ActionResult) {
284	b, err := opts.ToClusterScaleOutMap()
285	if err != nil {
286		r.Err = err
287		return
288	}
289	var result *http.Response
290	result, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
291		OkCodes: []int{200, 201, 202},
292	})
293	if r.Err == nil {
294		r.Header = result.Header
295	}
296
297	return
298}
299
300// AttachPolicyOptsBuilder allows extensions to add additional parameters to the
301// AttachPolicy request.
302type AttachPolicyOptsBuilder interface {
303	ToClusterAttachPolicyMap() (map[string]interface{}, error)
304}
305
306// PolicyOpts params
307type AttachPolicyOpts struct {
308	PolicyID string `json:"policy_id" required:"true"`
309	Enabled  *bool  `json:"enabled,omitempty"`
310}
311
312// ToClusterAttachPolicyMap constructs a request body from AttachPolicyOpts.
313func (opts AttachPolicyOpts) ToClusterAttachPolicyMap() (map[string]interface{}, error) {
314	return gophercloud.BuildRequestBody(opts, "policy_attach")
315}
316
317// Attach Policy will attach a policy to a cluster.
318func AttachPolicy(client *gophercloud.ServiceClient, id string, opts AttachPolicyOptsBuilder) (r ActionResult) {
319	b, err := opts.ToClusterAttachPolicyMap()
320	if err != nil {
321		r.Err = err
322		return
323	}
324	var result *http.Response
325	result, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
326		OkCodes: []int{200, 201, 202},
327	})
328	if r.Err == nil {
329		r.Header = result.Header
330	}
331
332	return
333}
334
335// UpdatePolicyOptsBuilder allows extensions to add additional parameters to the
336// UpdatePolicy request.
337type UpdatePolicyOptsBuilder interface {
338	ToClusterUpdatePolicyMap() (map[string]interface{}, error)
339}
340
341// UpdatePolicyOpts represents options used to update a cluster policy.
342type UpdatePolicyOpts struct {
343	PolicyID string `json:"policy_id" required:"true"`
344	Enabled  *bool  `json:"enabled,omitempty" required:"true"`
345}
346
347// ToClusterUpdatePolicyMap constructs a request body from UpdatePolicyOpts.
348func (opts UpdatePolicyOpts) ToClusterUpdatePolicyMap() (map[string]interface{}, error) {
349	return gophercloud.BuildRequestBody(opts, "policy_update")
350}
351
352// UpdatePolicy will update a cluster's policy.
353func UpdatePolicy(client *gophercloud.ServiceClient, id string, opts UpdatePolicyOptsBuilder) (r ActionResult) {
354	b, err := opts.ToClusterUpdatePolicyMap()
355	if err != nil {
356		r.Err = err
357		return
358	}
359	var result *http.Response
360	result, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
361		OkCodes: []int{200, 201, 202},
362	})
363	if r.Err == nil {
364		r.Header = result.Header
365	}
366
367	return
368}
369
370// DetachPolicyOptsBuilder allows extensions to add additional parameters to the
371// DetachPolicy request.
372type DetachPolicyOptsBuilder interface {
373	ToClusterDetachPolicyMap() (map[string]interface{}, error)
374}
375
376// DetachPolicyOpts represents options used to detach a policy from a cluster.
377type DetachPolicyOpts struct {
378	PolicyID string `json:"policy_id" required:"true"`
379}
380
381// ToClusterDetachPolicyMap constructs a request body from DetachPolicyOpts.
382func (opts DetachPolicyOpts) ToClusterDetachPolicyMap() (map[string]interface{}, error) {
383	return gophercloud.BuildRequestBody(opts, "policy_detach")
384}
385
386// DetachPolicy will detach a policy from a cluster.
387func DetachPolicy(client *gophercloud.ServiceClient, id string, opts DetachPolicyOptsBuilder) (r ActionResult) {
388	b, err := opts.ToClusterDetachPolicyMap()
389	if err != nil {
390		r.Err = err
391		return
392	}
393
394	var result *http.Response
395	result, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
396		OkCodes: []int{200, 201, 202},
397	})
398	if r.Err == nil {
399		r.Header = result.Header
400	}
401
402	return
403}
404
405// ListPolicyOptsBuilder allows extensions to add additional parameters to the
406// ListPolicies request.
407type ListPoliciesOptsBuilder interface {
408	ToClusterPoliciesListQuery() (string, error)
409}
410
411// ListPoliciesOpts represents options to list a cluster's policies.
412type ListPoliciesOpts struct {
413	Enabled *bool  `q:"enabled"`
414	Name    string `q:"policy_name"`
415	Type    string `q:"policy_type"`
416	Sort    string `q:"sort"`
417}
418
419// ToClusterPoliciesListQuery formats a ListOpts into a query string.
420func (opts ListPoliciesOpts) ToClusterPoliciesListQuery() (string, error) {
421	q, err := gophercloud.BuildQueryString(opts)
422	return q.String(), err
423}
424
425// ListPolicies instructs OpenStack to provide a list of policies for a cluster.
426func ListPolicies(client *gophercloud.ServiceClient, clusterID string, opts ListPoliciesOptsBuilder) pagination.Pager {
427	url := listPoliciesURL(client, clusterID)
428	if opts != nil {
429		query, err := opts.ToClusterPoliciesListQuery()
430		if err != nil {
431			return pagination.Pager{Err: err}
432		}
433		url += query
434	}
435
436	return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
437		return ClusterPolicyPage{pagination.SinglePageBase(r)}
438	})
439}
440
441// GetPolicy retrieves details of a cluster policy.
442func GetPolicy(client *gophercloud.ServiceClient, clusterID string, policyID string) (r GetPolicyResult) {
443	_, r.Err = client.Get(getPolicyURL(client, clusterID, policyID), &r.Body, nil)
444	return
445}
446
447// RecoverOptsBuilder allows extensions to add additional parameters to the
448// Recover request.
449type RecoverOptsBuilder interface {
450	ToClusterRecoverMap() (map[string]interface{}, error)
451}
452
453// RecoverOpts represents options used to recover a cluster.
454type RecoverOpts struct {
455	Operation     RecoveryAction `json:"operation,omitempty"`
456	Check         *bool          `json:"check,omitempty"`
457	CheckCapacity *bool          `json:"check_capacity,omitempty"`
458}
459
460// ToClusterRecovermap constructs a request body from RecoverOpts.
461func (opts RecoverOpts) ToClusterRecoverMap() (map[string]interface{}, error) {
462	return gophercloud.BuildRequestBody(opts, "recover")
463}
464
465// Recover implements cluster recover request.
466func Recover(client *gophercloud.ServiceClient, id string, opts RecoverOptsBuilder) (r ActionResult) {
467	b, err := opts.ToClusterRecoverMap()
468	if err != nil {
469		r.Err = err
470		return
471	}
472	var result *http.Response
473	result, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
474		OkCodes: []int{200, 201, 202},
475	})
476	if r.Err == nil {
477		r.Header = result.Header
478	}
479
480	return
481}
482
483// Check will perform a health check on a cluster.
484func Check(client *gophercloud.ServiceClient, id string) (r ActionResult) {
485	b := map[string]interface{}{
486		"check": map[string]interface{}{},
487	}
488
489	var result *http.Response
490	result, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
491		OkCodes: []int{200, 201, 202},
492	})
493	if r.Err == nil {
494		r.Header = result.Header
495	}
496
497	return
498}
499
500// ToClusterCompleteLifecycleMap constructs a request body from CompleteLifecycleOpts.
501func (opts CompleteLifecycleOpts) ToClusterCompleteLifecycleMap() (map[string]interface{}, error) {
502	return gophercloud.BuildRequestBody(opts, "complete_lifecycle")
503}
504
505type CompleteLifecycleOpts struct {
506	LifecycleActionTokenID string `json:"lifecycle_action_token" required:"true"`
507}
508
509func CompleteLifecycle(client *gophercloud.ServiceClient, id string, opts CompleteLifecycleOpts) (r ActionResult) {
510	b, err := opts.ToClusterCompleteLifecycleMap()
511	if err != nil {
512		r.Err = err
513		return
514	}
515
516	var result *http.Response
517	result, r.Err = client.Post(actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
518		OkCodes: []int{202},
519	})
520	if r.Err == nil {
521		r.Header = result.Header
522	}
523
524	return
525}
526
527func (opts AddNodesOpts) ToClusterAddNodeMap() (map[string]interface{}, error) {
528	return gophercloud.BuildRequestBody(opts, "add_nodes")
529}
530
531type AddNodesOpts struct {
532	Nodes []string `json:"nodes" required:"true"`
533}
534
535func AddNodes(client *gophercloud.ServiceClient, id string, opts AddNodesOpts) (r ActionResult) {
536	b, err := opts.ToClusterAddNodeMap()
537	if err != nil {
538		r.Err = err
539		return
540	}
541	var result *http.Response
542	result, r.Err = client.Post(nodeURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
543		OkCodes: []int{202},
544	})
545	r.Header = result.Header
546	return
547}
548
549func (opts RemoveNodesOpts) ToClusterRemoveNodeMap() (map[string]interface{}, error) {
550	return gophercloud.BuildRequestBody(opts, "del_nodes")
551}
552
553type RemoveNodesOpts struct {
554	Nodes []string `json:"nodes" required:"true"`
555}
556
557func RemoveNodes(client *gophercloud.ServiceClient, clusterID string, opts RemoveNodesOpts) (r DeleteResult) {
558	b, err := opts.ToClusterRemoveNodeMap()
559	if err != nil {
560		r.Err = err
561		return
562	}
563	var result *http.Response
564	result, r.Err = client.Post(nodeURL(client, clusterID), b, &r.Body, &gophercloud.RequestOpts{
565		OkCodes: []int{202},
566	})
567	r.Header = result.Header
568	return
569}
570
571func (opts ReplaceNodesOpts) ToClusterReplaceNodeMap() (map[string]interface{}, error) {
572	return gophercloud.BuildRequestBody(opts, "replace_nodes")
573}
574
575type ReplaceNodesOpts struct {
576	Nodes map[string]string `json:"nodes" required:"true"`
577}
578
579func ReplaceNodes(client *gophercloud.ServiceClient, id string, opts ReplaceNodesOpts) (r ActionResult) {
580	b, err := opts.ToClusterReplaceNodeMap()
581	if err != nil {
582		r.Err = err
583		return
584	}
585	var result *http.Response
586	result, r.Err = client.Post(nodeURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
587		OkCodes: []int{202},
588	})
589	r.Header = result.Header
590	return
591}
592
593type CollectOptsBuilder interface {
594	ToClusterCollectMap() (string, error)
595}
596
597// CollectOpts represents options to collect attribute values across a cluster
598type CollectOpts struct {
599	Path string `q:"path" required:"true"`
600}
601
602func (opts CollectOpts) ToClusterCollectMap() (string, error) {
603	return opts.Path, nil
604}
605
606// Collect instructs OpenStack to aggregate attribute values across a cluster
607func Collect(client *gophercloud.ServiceClient, id string, opts CollectOptsBuilder) (r CollectResult) {
608	query, err := opts.ToClusterCollectMap()
609	if err != nil {
610		r.Err = err
611		return
612	}
613
614	var result *http.Response
615	result, r.Err = client.Get(collectURL(client, id, query), &r.Body, &gophercloud.RequestOpts{
616		OkCodes: []int{200},
617	})
618
619	if r.Err == nil {
620		r.Header = result.Header
621	}
622
623	return
624}
625
626// OperationName represents valid values for cluster operation
627type OperationName string
628
629const (
630	// Nova Profile Op Names
631	RebootOperation         OperationName = "reboot"
632	RebuildOperation        OperationName = "rebuild"
633	ChangePasswordOperation OperationName = "change_password"
634	PauseOperation          OperationName = "pause"
635	UnpauseOperation        OperationName = "unpause"
636	SuspendOperation        OperationName = "suspend"
637	ResumeOperation         OperationName = "resume"
638	LockOperation           OperationName = "lock"
639	UnlockOperation         OperationName = "unlock"
640	StartOperation          OperationName = "start"
641	StopOperation           OperationName = "stop"
642	RescueOperation         OperationName = "rescue"
643	UnrescueOperation       OperationName = "unrescue"
644	EvacuateOperation       OperationName = "evacuate"
645
646	// Heat Pofile Op Names
647	AbandonOperation OperationName = "abandon"
648)
649
650// ToClusterOperationMap constructs a request body from OperationOpts.
651func (opts OperationOpts) ToClusterOperationMap() (map[string]interface{}, error) {
652	operationArg := struct {
653		Filters OperationFilters `json:"filters,omitempty"`
654		Params  OperationParams  `json:"params,omitempty"`
655	}{
656		Filters: opts.Filters,
657		Params:  opts.Params,
658	}
659
660	return gophercloud.BuildRequestBody(operationArg, string(opts.Operation))
661}
662
663// OperationOptsBuilder allows extensions to add additional parameters to the
664// Op request.
665type OperationOptsBuilder interface {
666	ToClusterOperationMap() (map[string]interface{}, error)
667}
668type OperationFilters map[string]interface{}
669type OperationParams map[string]interface{}
670
671// OperationOpts represents options used to perform an operation on a cluster
672type OperationOpts struct {
673	Operation OperationName    `json:"operation" required:"true"`
674	Filters   OperationFilters `json:"filters,omitempty"`
675	Params    OperationParams  `json:"params,omitempty"`
676}
677
678func Ops(client *gophercloud.ServiceClient, id string, opts OperationOptsBuilder) (r ActionResult) {
679	b, err := opts.ToClusterOperationMap()
680	if err != nil {
681		r.Err = err
682		return
683	}
684
685	var result *http.Response
686	result, r.Err = client.Post(opsURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
687		OkCodes: []int{202},
688	})
689	if r.Err == nil {
690		r.Header = result.Header
691	}
692
693	return
694}
695