1// Copyright 2016-2020 The Libsacloud Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package api
16
17import (
18	"encoding/json"
19	"fmt"
20	"time"
21
22	"github.com/sacloud/libsacloud/sacloud"
23)
24
25//HACK: さくらのAPI側仕様: Applianceの内容によってJSONフォーマットが異なるため
26//      ロードバランサ/VPCルータそれぞれでリクエスト/レスポンスデータ型を定義する。
27
28// SearchDatabaseResponse データベース検索レスポンス
29type SearchDatabaseResponse struct {
30	// Total 総件数
31	Total int `json:",omitempty"`
32	// From ページング開始位置
33	From int `json:",omitempty"`
34	// Count 件数
35	Count int `json:",omitempty"`
36	// Databases データベースリスト
37	Databases []sacloud.Database `json:"Appliances,omitempty"`
38}
39
40type databaseRequest struct {
41	Database *sacloud.Database      `json:"Appliance,omitempty"`
42	From     int                    `json:",omitempty"`
43	Count    int                    `json:",omitempty"`
44	Sort     []string               `json:",omitempty"`
45	Filter   map[string]interface{} `json:",omitempty"`
46	Exclude  []string               `json:",omitempty"`
47	Include  []string               `json:",omitempty"`
48}
49
50type databaseResponse struct {
51	*sacloud.ResultFlagValue
52	*sacloud.Database `json:"Appliance,omitempty"`
53	// Success
54	Success interface{} `json:",omitempty"` //HACK: さくらのAPI側仕様: 戻り値:Successがbool値へ変換できないためinterface{}で受ける
55}
56
57type databaseStatusResponse struct {
58	*sacloud.ResultFlagValue
59	Success   interface{} `json:",omitempty"` //HACK: さくらのAPI側仕様: 戻り値:Successがbool値へ変換できないためinterface{}
60	Appliance *struct {
61		SettingsResponse *sacloud.DatabaseStatus
62	}
63}
64
65type databaseBackupResponse struct {
66	Log  string `json:",omitempty"`
67	IsOk bool   `json:"is_ok,omitempty"`
68}
69
70// DatabaseAPI データベースAPI
71type DatabaseAPI struct {
72	*baseAPI
73}
74
75// NewDatabaseAPI データベースAPI作成
76func NewDatabaseAPI(client *Client) *DatabaseAPI {
77	return &DatabaseAPI{
78		&baseAPI{
79			client: client,
80			FuncGetResourceURL: func() string {
81				return "appliance"
82			},
83			FuncBaseSearchCondition: func() *sacloud.Request {
84				res := &sacloud.Request{}
85				res.AddFilter("Class", "database")
86				return res
87			},
88		},
89	}
90}
91
92// Find 検索
93func (api *DatabaseAPI) Find() (*SearchDatabaseResponse, error) {
94	data, err := api.client.newRequest("GET", api.getResourceURL(), api.getSearchState())
95	if err != nil {
96		return nil, err
97	}
98	var res SearchDatabaseResponse
99	if err := json.Unmarshal(data, &res); err != nil {
100		return nil, err
101	}
102	return &res, nil
103}
104
105func (api *DatabaseAPI) request(f func(*databaseResponse) error) (*sacloud.Database, error) {
106	res := &databaseResponse{}
107	err := f(res)
108	if err != nil {
109		return nil, err
110	}
111	return res.Database, nil
112}
113
114func (api *DatabaseAPI) createRequest(value *sacloud.Database) *databaseResponse {
115	return &databaseResponse{Database: value}
116}
117
118// New 新規作成用パラメーター作成
119func (api *DatabaseAPI) New(values *sacloud.CreateDatabaseValue) *sacloud.Database {
120	return sacloud.CreateNewDatabase(values)
121}
122
123// Create 新規作成
124func (api *DatabaseAPI) Create(value *sacloud.Database) (*sacloud.Database, error) {
125	return api.request(func(res *databaseResponse) error {
126		return api.create(api.createRequest(value), res)
127	})
128}
129
130// Read 読み取り
131func (api *DatabaseAPI) Read(id sacloud.ID) (*sacloud.Database, error) {
132	return api.request(func(res *databaseResponse) error {
133		return api.read(id, nil, res)
134	})
135}
136
137// Status DBの設定/起動状態の取得
138func (api *DatabaseAPI) Status(id sacloud.ID) (*sacloud.DatabaseStatus, error) {
139	var (
140		method = "GET"
141		uri    = fmt.Sprintf("%s/%d/status", api.getResourceURL(), id)
142	)
143
144	res := &databaseStatusResponse{}
145	err := api.baseAPI.request(method, uri, nil, res)
146	if err != nil {
147		return nil, err
148	}
149	return res.Appliance.SettingsResponse, nil
150}
151
152// Backup バックアップ取得
153func (api *DatabaseAPI) Backup(id sacloud.ID) (string, error) {
154	var (
155		method = "POST"
156		uri    = fmt.Sprintf("%s/%d/action/history", api.getResourceURL(), id)
157	)
158
159	body := map[string]interface{}{
160		"Appliance": map[string]interface{}{
161			"Settings": map[string]interface{}{
162				"DBConf": map[string]interface{}{
163					"backup": map[string]string{
164						"availability": "discontinued",
165					},
166				},
167			},
168		},
169	}
170
171	res := &databaseBackupResponse{}
172	err := api.baseAPI.request(method, uri, body, res)
173	if err != nil {
174		return "", err
175	}
176	return res.Log, nil
177}
178
179// DownloadLog ログ取得
180func (api *DatabaseAPI) DownloadLog(id sacloud.ID, logID string) (string, error) {
181	var (
182		method = "GET"
183		uri    = fmt.Sprintf("%s/%d/download/log/%s", api.getResourceURL(), id, logID)
184	)
185
186	res := &databaseBackupResponse{}
187	err := api.baseAPI.request(method, uri, nil, res)
188	if err != nil {
189		return "", err
190	}
191	return res.Log, nil
192}
193
194// Restore バックアップからの復元
195func (api *DatabaseAPI) Restore(id sacloud.ID, backupID string) (string, error) {
196	var (
197		method = "POST"
198		uri    = fmt.Sprintf("%s/%d/action/history/%s", api.getResourceURL(), id, backupID)
199	)
200
201	body := map[string]interface{}{
202		"Appliance": map[string]interface{}{},
203	}
204
205	res := &databaseBackupResponse{}
206	err := api.baseAPI.request(method, uri, body, res)
207	if err != nil {
208		return "", err
209	}
210	return res.Log, nil
211}
212
213// DeleteBackup バックアップの削除
214func (api *DatabaseAPI) DeleteBackup(id sacloud.ID, backupID string) (string, error) {
215	var (
216		method = "DELETE"
217		uri    = fmt.Sprintf("%s/%d/action/history/%s", api.getResourceURL(), id, backupID)
218	)
219
220	body := map[string]interface{}{
221		"Appliance": map[string]interface{}{},
222	}
223
224	res := &databaseBackupResponse{}
225	err := api.baseAPI.request(method, uri, body, res)
226	if err != nil {
227		return "", err
228	}
229	return res.Log, nil
230}
231
232// HistoryLock バックアップ削除ロック
233func (api *DatabaseAPI) HistoryLock(id sacloud.ID, backupID string) (string, error) {
234	var (
235		method = "PUT"
236		uri    = fmt.Sprintf("%s/%d/action/history-lock/%s", api.getResourceURL(), id, backupID)
237	)
238
239	body := map[string]interface{}{
240		"Appliance": map[string]interface{}{},
241	}
242
243	res := &databaseBackupResponse{}
244	err := api.baseAPI.request(method, uri, body, res)
245	if err != nil {
246		return "", err
247	}
248	return res.Log, nil
249}
250
251// HistoryUnlock バックアップ削除アンロック
252func (api *DatabaseAPI) HistoryUnlock(id sacloud.ID, backupID string) (string, error) {
253	var (
254		method = "DELETE"
255		uri    = fmt.Sprintf("%s/%d/action/history-lock/%s", api.getResourceURL(), id, backupID)
256	)
257
258	body := map[string]interface{}{
259		"Appliance": map[string]interface{}{},
260	}
261
262	res := &databaseBackupResponse{}
263	err := api.baseAPI.request(method, uri, body, res)
264	if err != nil {
265		return "", err
266	}
267	return res.Log, nil
268}
269
270// Update 更新
271func (api *DatabaseAPI) Update(id sacloud.ID, value *sacloud.Database) (*sacloud.Database, error) {
272	return api.request(func(res *databaseResponse) error {
273		return api.update(id, api.createRequest(value), res)
274	})
275}
276
277// UpdateSetting 設定更新
278func (api *DatabaseAPI) UpdateSetting(id sacloud.ID, value *sacloud.Database) (*sacloud.Database, error) {
279	req := &sacloud.Database{
280		// Settings
281		Settings: value.Settings,
282	}
283	return api.request(func(res *databaseResponse) error {
284		return api.update(id, api.createRequest(req), res)
285	})
286}
287
288// Delete 削除
289func (api *DatabaseAPI) Delete(id sacloud.ID) (*sacloud.Database, error) {
290	return api.request(func(res *databaseResponse) error {
291		return api.delete(id, nil, res)
292	})
293}
294
295// Config 設定変更の反映
296func (api *DatabaseAPI) Config(id sacloud.ID) (bool, error) {
297	var (
298		method = "PUT"
299		uri    = fmt.Sprintf("%s/%d/config", api.getResourceURL(), id)
300	)
301	return api.modify(method, uri, nil)
302}
303
304// IsUp 起動しているか判定
305func (api *DatabaseAPI) IsUp(id sacloud.ID) (bool, error) {
306	lb, err := api.Read(id)
307	if err != nil {
308		return false, err
309	}
310	return lb.Instance.IsUp(), nil
311}
312
313// IsDown ダウンしているか判定
314func (api *DatabaseAPI) IsDown(id sacloud.ID) (bool, error) {
315	lb, err := api.Read(id)
316	if err != nil {
317		return false, err
318	}
319	return lb.Instance.IsDown(), nil
320}
321
322// IsDatabaseRunning データベースプロセスが起動しているか判定
323func (api *DatabaseAPI) IsDatabaseRunning(id sacloud.ID) (bool, error) {
324	db, err := api.Status(id)
325	if err != nil {
326		return false, err
327	}
328	return db.IsUp(), nil
329
330}
331
332// Boot 起動
333func (api *DatabaseAPI) Boot(id sacloud.ID) (bool, error) {
334	var (
335		method = "PUT"
336		uri    = fmt.Sprintf("%s/%d/power", api.getResourceURL(), id)
337	)
338	return api.modify(method, uri, nil)
339}
340
341// Shutdown シャットダウン(graceful)
342func (api *DatabaseAPI) Shutdown(id sacloud.ID) (bool, error) {
343	var (
344		method = "DELETE"
345		uri    = fmt.Sprintf("%s/%d/power", api.getResourceURL(), id)
346	)
347
348	return api.modify(method, uri, nil)
349}
350
351// Stop シャットダウン(force)
352func (api *DatabaseAPI) Stop(id sacloud.ID) (bool, error) {
353	var (
354		method = "DELETE"
355		uri    = fmt.Sprintf("%s/%d/power", api.getResourceURL(), id)
356	)
357
358	return api.modify(method, uri, map[string]bool{"Force": true})
359}
360
361// RebootForce 再起動
362func (api *DatabaseAPI) RebootForce(id sacloud.ID) (bool, error) {
363	var (
364		method = "PUT"
365		uri    = fmt.Sprintf("%s/%d/reset", api.getResourceURL(), id)
366	)
367
368	return api.modify(method, uri, nil)
369}
370
371// ResetForce リセット
372func (api *DatabaseAPI) ResetForce(id sacloud.ID, recycleProcess bool) (bool, error) {
373	var (
374		method = "PUT"
375		uri    = fmt.Sprintf("%s/%d/reset", api.getResourceURL(), id)
376	)
377
378	return api.modify(method, uri, map[string]bool{"RecycleProcess": recycleProcess})
379}
380
381// SleepUntilUp 起動するまで待機
382func (api *DatabaseAPI) SleepUntilUp(id sacloud.ID, timeout time.Duration) error {
383	handler := waitingForUpFunc(func() (hasUpDown, error) {
384		return api.Read(id)
385	}, 0)
386	return blockingPoll(handler, timeout)
387}
388
389// SleepUntilDatabaseRunning 起動するまで待機
390func (api *DatabaseAPI) SleepUntilDatabaseRunning(id sacloud.ID, timeout time.Duration, maxRetry int) error {
391	handler := waitingForUpFunc(func() (hasUpDown, error) {
392		return api.Read(id)
393	}, maxRetry)
394	return blockingPoll(handler, timeout)
395}
396
397// SleepUntilDown ダウンするまで待機
398func (api *DatabaseAPI) SleepUntilDown(id sacloud.ID, timeout time.Duration) error {
399	handler := waitingForDownFunc(func() (hasUpDown, error) {
400		return api.Read(id)
401	}, 0)
402	return blockingPoll(handler, timeout)
403}
404
405// SleepWhileCopying コピー終了まで待機
406func (api *DatabaseAPI) SleepWhileCopying(id sacloud.ID, timeout time.Duration, maxRetry int) error {
407	handler := waitingForAvailableFunc(func() (hasAvailable, error) {
408		return api.Read(id)
409	}, maxRetry)
410	return blockingPoll(handler, timeout)
411}
412
413// AsyncSleepWhileCopying コピー終了まで待機(非同期)
414func (api *DatabaseAPI) AsyncSleepWhileCopying(id sacloud.ID, timeout time.Duration, maxRetry int) (chan (interface{}), chan (interface{}), chan (error)) {
415	handler := waitingForAvailableFunc(func() (hasAvailable, error) {
416		return api.Read(id)
417	}, maxRetry)
418	return poll(handler, timeout)
419}
420
421// MonitorCPU CPUアクティビティーモニター取得
422func (api *DatabaseAPI) MonitorCPU(id sacloud.ID, body *sacloud.ResourceMonitorRequest) (*sacloud.MonitorValues, error) {
423	return api.baseAPI.applianceMonitorBy(id, "cpu", 0, body)
424}
425
426// MonitorDatabase データーベース固有項目アクティビティモニター取得
427func (api *DatabaseAPI) MonitorDatabase(id sacloud.ID, body *sacloud.ResourceMonitorRequest) (*sacloud.MonitorValues, error) {
428	return api.baseAPI.applianceMonitorBy(id, "database", 0, body)
429}
430
431// MonitorInterface NICアクティビティーモニター取得
432func (api *DatabaseAPI) MonitorInterface(id sacloud.ID, body *sacloud.ResourceMonitorRequest) (*sacloud.MonitorValues, error) {
433	return api.baseAPI.applianceMonitorBy(id, "interface", 0, body)
434}
435
436// MonitorSystemDisk システムディスクアクティビティーモニター取得
437func (api *DatabaseAPI) MonitorSystemDisk(id sacloud.ID, body *sacloud.ResourceMonitorRequest) (*sacloud.MonitorValues, error) {
438	return api.baseAPI.applianceMonitorBy(id, "disk", 1, body)
439}
440
441// MonitorBackupDisk バックアップディスクアクティビティーモニター取得
442func (api *DatabaseAPI) MonitorBackupDisk(id sacloud.ID, body *sacloud.ResourceMonitorRequest) (*sacloud.MonitorValues, error) {
443	return api.baseAPI.applianceMonitorBy(id, "disk", 2, body)
444}
445