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 sacloud
16
17import (
18	"encoding/json"
19	"fmt"
20	"strings"
21)
22
23// AllowDatabaseBackupWeekdays データベースバックアップ実行曜日リスト
24func AllowDatabaseBackupWeekdays() []string {
25	return []string{"mon", "tue", "wed", "thu", "fri", "sat", "sun"}
26}
27
28// Database データベース(appliance)
29type Database struct {
30	*Appliance // アプライアンス共通属性
31
32	Remark   *DatabaseRemark   `json:",omitempty"` // リマーク
33	Settings *DatabaseSettings `json:",omitempty"` // データベース設定
34}
35
36// DatabaseRemark データベースリマーク
37type DatabaseRemark struct {
38	*ApplianceRemarkBase
39	propPlanID                               // プランID
40	DBConf          *DatabaseCommonRemarks   // コンフィグ
41	Network         *DatabaseRemarkNetwork   // ネットワーク
42	SourceAppliance *DatabaseSourceAppliance `json:",omitempty"` // クローン元DB
43	Zone            struct {                 // ゾーン
44		ID ID `json:",omitempty"` // ゾーンID
45	}
46}
47
48// DatabaseSourceAppliance ソースアプライアンス(クローン元DB)
49type DatabaseSourceAppliance struct {
50	ID ID `json:",omitempty"`
51}
52
53// UnmarshalJSON 配列/オブジェクトが混在することへの対応
54//
55// v2からのバックポート
56func (s *DatabaseSourceAppliance) UnmarshalJSON(b []byte) error {
57	if string(b) == "[]" {
58		return nil
59	}
60	type alias DatabaseSourceAppliance
61
62	var a alias
63	if err := json.Unmarshal(b, &a); err != nil {
64		return err
65	}
66	*s = DatabaseSourceAppliance(a)
67	return nil
68}
69
70// DatabaseRemarkNetwork ネットワーク
71type DatabaseRemarkNetwork struct {
72	NetworkMaskLen int    `json:",omitempty"` // ネットワークマスク長
73	DefaultRoute   string `json:",omitempty"` // デフォルトルート
74}
75
76// UnmarshalJSON JSONアンマーシャル(配列、オブジェクトが混在するためここで対応)
77func (s *DatabaseRemarkNetwork) UnmarshalJSON(data []byte) error {
78	targetData := strings.Replace(strings.Replace(string(data), " ", "", -1), "\n", "", -1)
79	if targetData == `[]` {
80		return nil
81	}
82
83	tmp := &struct {
84		// NetworkMaskLen
85		NetworkMaskLen int `json:",omitempty"`
86		// DefaultRoute
87		DefaultRoute string `json:",omitempty"`
88	}{}
89	if err := json.Unmarshal(data, &tmp); err != nil {
90		return err
91	}
92
93	s.NetworkMaskLen = tmp.NetworkMaskLen
94	s.DefaultRoute = tmp.DefaultRoute
95	return nil
96}
97
98// DatabaseCommonRemarks リマークリスト
99type DatabaseCommonRemarks struct {
100	Common *DatabaseCommonRemark // Common
101}
102
103// DatabaseCommonRemark リマーク
104type DatabaseCommonRemark struct {
105	DatabaseName     string `json:",omitempty"` // 名称
106	DatabaseRevision string `json:",omitempty"` // リビジョン
107	DatabaseTitle    string `json:",omitempty"` // タイトル
108	DatabaseVersion  string `json:",omitempty"` // バージョン
109}
110
111// DatabaseSettings データベース設定リスト
112type DatabaseSettings struct {
113	DBConf *DatabaseSetting `json:",omitempty"` // コンフィグ
114}
115
116// DatabaseSetting データベース設定
117type DatabaseSetting struct {
118	Backup      *DatabaseBackupSetting      `json:",omitempty"` // バックアップ設定
119	Common      *DatabaseCommonSetting      `json:",oitempty"`  // 共通設定
120	Replication *DatabaseReplicationSetting `json:",omitempty"` // レプリケーション設定
121}
122
123// DatabaseServer データベースサーバー情報
124type DatabaseServer struct {
125	IPAddress  string `json:",omitempty"` // IPアドレス
126	Port       string `json:",omitempty"` // ポート
127	Enabled    string `json:",omitempty"` // 有効/無効
128	Status     string `json:",omitempty"` // ステータス
129	ActiveConn string `json:",omitempty"` // アクティブコネクション
130}
131
132// DatabasePlan プラン
133type DatabasePlan int
134
135var (
136	// DatabasePlanMini ミニプラン(後方互換用)
137	DatabasePlanMini = DatabasePlan(10)
138	// DatabasePlan10G 10Gプラン
139	DatabasePlan10G = DatabasePlan(10)
140	// DatabasePlan30G 30Gプラン
141	DatabasePlan30G = DatabasePlan(30)
142	// DatabasePlan90G 90Gプラン
143	DatabasePlan90G = DatabasePlan(90)
144	// DatabasePlan240G 240Gプラン
145	DatabasePlan240G = DatabasePlan(240)
146	// DatabasePlan500G 500Gプラン
147	DatabasePlan500G = DatabasePlan(500)
148	// DatabasePlan1T 1Tプラン
149	DatabasePlan1T = DatabasePlan(1000)
150)
151
152// AllowDatabasePlans 指定可能なデータベースプラン
153func AllowDatabasePlans() []int {
154	return []int{
155		int(DatabasePlan10G),
156		int(DatabasePlan30G),
157		int(DatabasePlan90G),
158		int(DatabasePlan240G),
159		int(DatabasePlan500G),
160		int(DatabasePlan1T),
161	}
162}
163
164// DatabaseBackupSetting バックアップ設定
165type DatabaseBackupSetting struct {
166	Rotate    int      `json:",omitempty"` // ローテーション世代数
167	Time      string   `json:",omitempty"` // 開始時刻
168	DayOfWeek []string `json:",omitempty"` // 取得曜日
169}
170
171// DatabaseCommonSetting 共通設定
172type DatabaseCommonSetting struct {
173	DefaultUser     string        `json:",omitempty"` // ユーザー名
174	UserPassword    string        `json:",omitempty"` // ユーザーパスワード
175	WebUI           interface{}   `json:",omitempty"` // WebUIのIPアドレス or FQDN
176	ReplicaPassword string        `json:",omitempty"` // レプリケーションパスワード
177	ReplicaUser     string        `json:",omitempty"` // レプリケーションユーザー
178	ServicePort     json.Number   `json:",omitempty"` // ポート番号
179	SourceNetwork   SourceNetwork // 接続許可ネットワーク
180}
181
182// SourceNetwork 接続許可ネットワーク
183type SourceNetwork []string
184
185// UnmarshalJSON JSONアンマーシャル(配列と文字列が混在するためここで対応)
186func (s *SourceNetwork) UnmarshalJSON(data []byte) error {
187	// SourceNetworkが未設定の場合、APIレスポンスが""となるため回避する
188	if string(data) == `""` {
189		return nil
190	}
191
192	tmp := []string{}
193	if err := json.Unmarshal(data, &tmp); err != nil {
194		return err
195	}
196	source := SourceNetwork(tmp)
197	*s = source
198	return nil
199}
200
201// MarshalJSON JSONマーシャル(配列と文字列が混在するためここで対応)
202func (s *SourceNetwork) MarshalJSON() ([]byte, error) {
203	if s == nil {
204		return []byte(""), nil
205	}
206
207	list := []string(*s)
208	if len(list) == 0 || (len(list) == 1 && list[0] == "") {
209		return []byte(`""`), nil
210	}
211
212	return json.Marshal(list)
213}
214
215// DatabaseReplicationSetting レプリケーション設定
216type DatabaseReplicationSetting struct {
217	// Model レプリケーションモデル
218	Model DatabaseReplicationModels `json:",omitempty"`
219	// Appliance マスター側アプライアンス
220	Appliance *DatabaseSourceAppliance `json:",omitempty"`
221	// IPAddress IPアドレス
222	IPAddress string `json:",omitempty"`
223	// Port ポート
224	Port int `json:",omitempty"`
225	// User ユーザー
226	User string `json:",omitempty"`
227	// Password パスワード
228	Password string `json:",omitempty"`
229}
230
231// DatabaseReplicationModels データベースのレプリケーションモデル
232type DatabaseReplicationModels string
233
234const (
235	// DatabaseReplicationModelMasterSlave レプリケーションモデル: Master-Slave(マスター側)
236	DatabaseReplicationModelMasterSlave = "Master-Slave"
237	// DatabaseReplicationModelAsyncReplica レプリケーションモデル: Async-Replica(スレーブ側)
238	DatabaseReplicationModelAsyncReplica = "Async-Replica"
239)
240
241// CreateDatabaseValue データベース作成用パラメータ
242type CreateDatabaseValue struct {
243	Plan             DatabasePlan // プラン
244	AdminPassword    string       // 管理者パスワード
245	DefaultUser      string       // ユーザー名
246	UserPassword     string       // パスワード
247	SourceNetwork    []string     // 接続許可ネットワーク
248	ServicePort      int          // ポート
249	EnableBackup     bool         // バックアップ有効化
250	BackupRotate     int          // バックアップ世代数
251	BackupTime       string       // バックアップ開始時間
252	BackupDayOfWeek  []string     // バックアップ取得曜日
253	SwitchID         ID           // 接続先スイッチ
254	IPAddress1       string       // IPアドレス1
255	MaskLen          int          // ネットワークマスク長
256	DefaultRoute     string       // デフォルトルート
257	Name             string       // 名称
258	Description      string       // 説明
259	Tags             []string     // タグ
260	Icon             *Resource    // アイコン
261	WebUI            bool         // WebUI有効
262	DatabaseName     string       // データベース名
263	DatabaseRevision string       // リビジョン
264	DatabaseTitle    string       // データベースタイトル
265	DatabaseVersion  string       // データベースバージョン
266	// ReplicaUser      string    // レプリケーションユーザー 現在はreplica固定
267	ReplicaPassword string    // レプリケーションパスワード
268	SourceAppliance *Resource // クローン元DB
269}
270
271// SlaveDatabaseValue スレーブデータベース作成用パラメータ
272type SlaveDatabaseValue struct {
273	Plan            DatabasePlan // プラン
274	DefaultUser     string       // ユーザー名
275	UserPassword    string       // パスワード
276	SwitchID        ID           // 接続先スイッチ
277	IPAddress1      string       // IPアドレス1
278	MaskLen         int          // ネットワークマスク長
279	DefaultRoute    string       // デフォルトルート
280	Name            string       // 名称
281	Description     string       // 説明
282	Tags            []string     // タグ
283	Icon            *Resource    // アイコン
284	DatabaseName    string       // データベース名
285	DatabaseVersion string       // データベースバージョン
286	// ReplicaUser      string    // レプリケーションユーザー 現在はreplica固定
287	ReplicaPassword   string // レプリケーションパスワード
288	MasterApplianceID ID     // クローン元DB
289	MasterIPAddress   string // マスターIPアドレス
290	MasterPort        int    // マスターポート
291}
292
293// NewCreatePostgreSQLDatabaseValue PostgreSQL作成用パラメーター
294func NewCreatePostgreSQLDatabaseValue() *CreateDatabaseValue {
295	return &CreateDatabaseValue{
296		DatabaseName:    "postgres",
297		DatabaseVersion: "12",
298	}
299}
300
301// NewCreateMariaDBDatabaseValue MariaDB作成用パラメーター
302func NewCreateMariaDBDatabaseValue() *CreateDatabaseValue {
303	return &CreateDatabaseValue{
304		DatabaseName:    "MariaDB",
305		DatabaseVersion: "10.4",
306	}
307}
308
309// NewCloneDatabaseValue クローンDB作成用パラメータ
310func NewCloneDatabaseValue(db *Database) *CreateDatabaseValue {
311	return &CreateDatabaseValue{
312		DatabaseName:    db.Remark.DBConf.Common.DatabaseName,
313		DatabaseVersion: db.Remark.DBConf.Common.DatabaseVersion,
314		SourceAppliance: NewResource(db.ID),
315	}
316}
317
318// CreateNewDatabase データベース作成
319func CreateNewDatabase(values *CreateDatabaseValue) *Database {
320
321	db := &Database{
322		// Appliance
323		Appliance: &Appliance{
324			// Class
325			Class: "database",
326			// Name
327			propName: propName{Name: values.Name},
328			// Description
329			propDescription: propDescription{Description: values.Description},
330			// TagsType
331			propTags: propTags{
332				// Tags
333				Tags: values.Tags,
334			},
335			// Icon
336			propIcon: propIcon{
337				&Icon{
338					// Resource
339					Resource: values.Icon,
340				},
341			},
342			// Plan
343			//propPlanID: propPlanID{Plan: &Resource{ID: int64(values.Plan)}},
344		},
345		// Remark
346		Remark: &DatabaseRemark{
347			// ApplianceRemarkBase
348			ApplianceRemarkBase: &ApplianceRemarkBase{
349				// Servers
350				Servers: []interface{}{""},
351			},
352			// DBConf
353			DBConf: &DatabaseCommonRemarks{
354				// Common
355				Common: &DatabaseCommonRemark{
356					// DatabaseName
357					DatabaseName: values.DatabaseName,
358					// DatabaseRevision
359					DatabaseRevision: values.DatabaseRevision,
360					// DatabaseTitle
361					DatabaseTitle: values.DatabaseTitle,
362					// DatabaseVersion
363					DatabaseVersion: values.DatabaseVersion,
364				},
365			},
366			// Plan
367			propPlanID: propPlanID{Plan: &Resource{ID: ID(values.Plan)}},
368		},
369		// Settings
370		Settings: &DatabaseSettings{
371			// DBConf
372			DBConf: &DatabaseSetting{
373				// Backup
374				Backup: &DatabaseBackupSetting{
375					// Rotate
376					// Rotate: values.BackupRotate,
377					Rotate: 8,
378					// Time
379					Time: values.BackupTime,
380					// DayOfWeek
381					DayOfWeek: values.BackupDayOfWeek,
382				},
383				// Common
384				Common: &DatabaseCommonSetting{
385					// DefaultUser
386					DefaultUser: values.DefaultUser,
387					// UserPassword
388					UserPassword: values.UserPassword,
389					// SourceNetwork
390					SourceNetwork: SourceNetwork(values.SourceNetwork),
391				},
392			},
393		},
394	}
395
396	if !values.SourceAppliance.ID.IsEmpty() {
397		db.Remark.SourceAppliance = &DatabaseSourceAppliance{ID: values.SourceAppliance.ID}
398	}
399
400	if values.ServicePort > 0 {
401		db.Settings.DBConf.Common.ServicePort = json.Number(fmt.Sprintf("%d", values.ServicePort))
402	}
403
404	if !values.EnableBackup {
405		db.Settings.DBConf.Backup = nil
406	}
407
408	db.Remark.Switch = &ApplianceRemarkSwitch{
409		// ID
410		ID: values.SwitchID,
411	}
412	db.Remark.Network = &DatabaseRemarkNetwork{
413		// NetworkMaskLen
414		NetworkMaskLen: values.MaskLen,
415		// DefaultRoute
416		DefaultRoute: values.DefaultRoute,
417	}
418
419	db.Remark.Servers = []interface{}{
420		map[string]interface{}{"IPAddress": values.IPAddress1},
421	}
422
423	if values.WebUI {
424		db.Settings.DBConf.Common.WebUI = values.WebUI
425	}
426
427	if values.ReplicaPassword != "" {
428		db.Settings.DBConf.Common.ReplicaUser = "replica"
429		db.Settings.DBConf.Common.ReplicaPassword = values.ReplicaPassword
430		db.Settings.DBConf.Replication = &DatabaseReplicationSetting{
431			Model: DatabaseReplicationModelMasterSlave,
432		}
433	}
434
435	return db
436}
437
438// NewSlaveDatabaseValue スレーブ向けパラメータ作成
439func NewSlaveDatabaseValue(values *SlaveDatabaseValue) *Database {
440	db := &Database{
441		// Appliance
442		Appliance: &Appliance{
443			// Class
444			Class: "database",
445			// Name
446			propName: propName{Name: values.Name},
447			// Description
448			propDescription: propDescription{Description: values.Description},
449			// TagsType
450			propTags: propTags{
451				// Tags
452				Tags: values.Tags,
453			},
454			// Icon
455			propIcon: propIcon{
456				&Icon{
457					// Resource
458					Resource: values.Icon,
459				},
460			},
461			// Plan
462			//propPlanID: propPlanID{Plan: &Resource{ID: int64(values.Plan)}},
463		},
464		// Remark
465		Remark: &DatabaseRemark{
466			// ApplianceRemarkBase
467			ApplianceRemarkBase: &ApplianceRemarkBase{
468				// Servers
469				Servers: []interface{}{""},
470			},
471			// DBConf
472			DBConf: &DatabaseCommonRemarks{
473				// Common
474				Common: &DatabaseCommonRemark{
475					// DatabaseName
476					DatabaseName: values.DatabaseName,
477					// DatabaseVersion
478					DatabaseVersion: values.DatabaseVersion,
479				},
480			},
481			// Plan
482			propPlanID: propPlanID{Plan: &Resource{ID: ID(int64(values.Plan) + 1)}},
483		},
484		// Settings
485		Settings: &DatabaseSettings{
486			// DBConf
487			DBConf: &DatabaseSetting{
488				// Common
489				Common: &DatabaseCommonSetting{
490					// DefaultUser
491					DefaultUser: values.DefaultUser,
492					// UserPassword
493					UserPassword: values.UserPassword,
494				},
495				// Replication
496				Replication: &DatabaseReplicationSetting{
497					Model:     DatabaseReplicationModelAsyncReplica,
498					Appliance: &DatabaseSourceAppliance{ID: values.MasterApplianceID},
499					IPAddress: values.MasterIPAddress,
500					Port:      values.MasterPort,
501					User:      "replica",
502					Password:  values.ReplicaPassword,
503				},
504			},
505		},
506	}
507
508	db.Remark.Switch = &ApplianceRemarkSwitch{
509		// ID
510		ID: values.SwitchID,
511	}
512	db.Remark.Network = &DatabaseRemarkNetwork{
513		// NetworkMaskLen
514		NetworkMaskLen: values.MaskLen,
515		// DefaultRoute
516		DefaultRoute: values.DefaultRoute,
517	}
518
519	db.Remark.Servers = []interface{}{
520		map[string]interface{}{"IPAddress": values.IPAddress1},
521	}
522
523	return db
524}
525
526// AddSourceNetwork 接続許可ネットワーク 追加
527func (s *Database) AddSourceNetwork(nw string) {
528	res := []string(s.Settings.DBConf.Common.SourceNetwork)
529	res = append(res, nw)
530	s.Settings.DBConf.Common.SourceNetwork = SourceNetwork(res)
531}
532
533// DeleteSourceNetwork 接続許可ネットワーク 削除
534func (s *Database) DeleteSourceNetwork(nw string) {
535	res := []string{}
536	for _, s := range s.Settings.DBConf.Common.SourceNetwork {
537		if s != nw {
538			res = append(res, s)
539		}
540	}
541	s.Settings.DBConf.Common.SourceNetwork = SourceNetwork(res)
542}
543
544// IsReplicationMaster レプリケーションが有効かつマスターとして構成されているか
545func (s *Database) IsReplicationMaster() bool {
546	return s.IsReplicationEnabled() && s.Settings.DBConf.Replication.Model == DatabaseReplicationModelMasterSlave
547}
548
549// IsReplicationEnabled レプリケーションが有効な場合はTrueを返す
550func (s *Database) IsReplicationEnabled() bool {
551	return s.Settings.DBConf.Replication != nil
552}
553
554// DatabaseName MariaDB or PostgreSQLの何れかを返す
555func (s *Database) DatabaseName() string {
556	return s.Remark.DBConf.Common.DatabaseName
557}
558
559// DatabaseRevision データベースのリビジョンを返す
560//
561// 例: MariaDBの場合 => 10.2.15 / PostgreSQLの場合 => 10.3
562func (s *Database) DatabaseRevision() string {
563	return s.Remark.DBConf.Common.DatabaseRevision
564}
565
566// DatabaseVersion データベースのバージョンを返す
567//
568// 例: MariaDBの場合 => 10.2 / PostgreSQLの場合 => 10
569func (s *Database) DatabaseVersion() string {
570	return s.Remark.DBConf.Common.DatabaseVersion
571}
572
573// WebUIAddress WebUIが有効な場合、IPアドレス or FQDNを返す、無効な場合は空文字を返す
574func (s *Database) WebUIAddress() string {
575	webUI := s.Settings.DBConf.Common.WebUI
576	if webUI != nil {
577		if v, ok := webUI.(string); ok {
578			return v
579		}
580	}
581	return ""
582}
583
584// IPAddress IPアドレスを取得
585func (s *Database) IPAddress() string {
586	if len(s.Remark.Servers) < 1 {
587		return ""
588	}
589	v, ok := s.Remark.Servers[0].(map[string]string)
590	if !ok {
591		return ""
592	}
593	return v["IPAddress"]
594}
595
596// NetworkMaskLen ネットワークマスク長を取得
597func (s *Database) NetworkMaskLen() int {
598	if s.Remark.Network == nil {
599		return -1
600	}
601	return s.Remark.Network.NetworkMaskLen
602}
603
604// DefaultRoute デフォルトゲートウェイアドレスを取得
605func (s *Database) DefaultRoute() string {
606	if s.Remark.Network == nil {
607		return ""
608	}
609	return s.Remark.Network.DefaultRoute
610}
611