github.com/sacloud/libsacloud/v2@v2.32.3/helper/builder/database/builder.go (about)

     1  // Copyright 2016-2022 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  
    15  package database
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  
    22  	"github.com/sacloud/libsacloud/v2/helper/builder"
    23  	"github.com/sacloud/libsacloud/v2/helper/power"
    24  	"github.com/sacloud/libsacloud/v2/helper/setup"
    25  	"github.com/sacloud/libsacloud/v2/sacloud"
    26  	"github.com/sacloud/libsacloud/v2/sacloud/accessor"
    27  	"github.com/sacloud/libsacloud/v2/sacloud/types"
    28  )
    29  
    30  // Builder データベースの構築を行う
    31  type Builder struct {
    32  	PlanID             types.ID
    33  	SwitchID           types.ID
    34  	IPAddresses        []string
    35  	NetworkMaskLen     int
    36  	DefaultRoute       string
    37  	Conf               *sacloud.DatabaseRemarkDBConfCommon
    38  	SourceID           types.ID
    39  	CommonSetting      *sacloud.DatabaseSettingCommon
    40  	BackupSetting      *sacloud.DatabaseSettingBackup
    41  	ReplicationSetting *sacloud.DatabaseReplicationSetting
    42  	Name               string
    43  	Description        string
    44  	Tags               types.Tags
    45  	IconID             types.ID
    46  
    47  	// Parameters RDBMS固有のパラメータ設定
    48  	//
    49  	// キーにはsacloud.DatabaseParameterMetaのLabelを指定する
    50  	//   - 例: effective_cache_size: 10
    51  	Parameters map[string]interface{}
    52  
    53  	SettingsHash string
    54  
    55  	NoWait bool
    56  
    57  	SetupOptions *builder.RetryableSetupParameter
    58  	Client       *APIClient
    59  }
    60  
    61  func (b *Builder) init() {
    62  	if b.SetupOptions == nil {
    63  		b.SetupOptions = builder.DefaultSetupOptions()
    64  	}
    65  }
    66  
    67  // Validate 設定値の検証
    68  func (b *Builder) Validate(ctx context.Context, zone string) error {
    69  	requiredValues := map[string]bool{
    70  		"PlanID":         b.PlanID.IsEmpty(),
    71  		"SwitchID":       b.SwitchID.IsEmpty(),
    72  		"IPAddresses":    len(b.IPAddresses) == 0,
    73  		"NetworkMaskLen": b.NetworkMaskLen == 0,
    74  		"Conf":           b.Conf == nil,
    75  		"CommonSetting":  b.CommonSetting == nil,
    76  	}
    77  	for key, empty := range requiredValues {
    78  		if empty {
    79  			return fmt.Errorf("%s is required", key)
    80  		}
    81  	}
    82  	return nil
    83  }
    84  
    85  // Build データベースの作成や設定をまとめて行う
    86  func (b *Builder) Build(ctx context.Context, zone string) (*sacloud.Database, error) {
    87  	b.init()
    88  
    89  	if err := b.Validate(ctx, zone); err != nil {
    90  		return nil, err
    91  	}
    92  
    93  	builder := &setup.RetryableSetup{
    94  		Create: func(ctx context.Context, zone string) (accessor.ID, error) {
    95  			return b.Client.Database.Create(ctx, zone, &sacloud.DatabaseCreateRequest{
    96  				PlanID:             b.PlanID,
    97  				SwitchID:           b.SwitchID,
    98  				IPAddresses:        b.IPAddresses,
    99  				NetworkMaskLen:     b.NetworkMaskLen,
   100  				DefaultRoute:       b.DefaultRoute,
   101  				Conf:               b.Conf,
   102  				SourceID:           b.SourceID,
   103  				CommonSetting:      b.CommonSetting,
   104  				BackupSetting:      b.BackupSetting,
   105  				ReplicationSetting: b.ReplicationSetting,
   106  				Name:               b.Name,
   107  				Description:        b.Description,
   108  				Tags:               b.Tags,
   109  				IconID:             b.IconID,
   110  			})
   111  		},
   112  		Delete: func(ctx context.Context, zone string, id types.ID) error {
   113  			return b.Client.Database.Delete(ctx, zone, id)
   114  		},
   115  		Read: func(ctx context.Context, zone string, id types.ID) (interface{}, error) {
   116  			return b.Client.Database.Read(ctx, zone, id)
   117  		},
   118  		ProvisionBeforeUp: func(ctx context.Context, zone string, id types.ID, _ interface{}) error {
   119  			if b.NoWait {
   120  				return nil
   121  			}
   122  
   123  			// [HACK] データベースアプライアンス場合のみ/appliance/:id/statusも考慮する
   124  			waiter := sacloud.WaiterForUp(func() (interface{}, error) {
   125  				return b.Client.Database.Status(ctx, zone, id)
   126  			})
   127  			waiter.SetPollingInterval(sacloud.DefaultDBStatusPollingInterval) // HACK 現状は決め打ち、ユースケースが出たら修正する
   128  			_, err := waiter.WaitForState(ctx)
   129  			if err != nil {
   130  				return err
   131  			}
   132  
   133  			if err := b.reconcileDatabaseParameters(ctx, zone, id); err != nil {
   134  				return err
   135  			}
   136  
   137  			return b.Client.Database.Config(ctx, zone, id)
   138  		},
   139  		IsWaitForCopy:       !b.NoWait,
   140  		IsWaitForUp:         !b.NoWait,
   141  		RetryCount:          b.SetupOptions.RetryCount,
   142  		DeleteRetryCount:    b.SetupOptions.DeleteRetryCount,
   143  		DeleteRetryInterval: b.SetupOptions.DeleteRetryInterval,
   144  		PollingInterval:     b.SetupOptions.PollingInterval,
   145  	}
   146  
   147  	result, err := builder.Setup(ctx, zone)
   148  	var db *sacloud.Database
   149  	if result != nil {
   150  		db = result.(*sacloud.Database)
   151  	}
   152  	if err != nil {
   153  		return db, err
   154  	}
   155  
   156  	// refresh
   157  	db, err = b.Client.Database.Read(ctx, zone, db.ID)
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  	return db, nil
   162  }
   163  
   164  // Update データベースの更新
   165  func (b *Builder) Update(ctx context.Context, zone string, id types.ID) (*sacloud.Database, error) {
   166  	b.init()
   167  
   168  	if err := b.Validate(ctx, zone); err != nil {
   169  		return nil, err
   170  	}
   171  
   172  	// check Database is exists
   173  	db, err := b.Client.Database.Read(ctx, zone, id)
   174  	if err != nil {
   175  		return nil, err
   176  	}
   177  
   178  	isNeedShutdown, err := b.collectUpdateInfo(db)
   179  	if err != nil {
   180  		return nil, err
   181  	}
   182  
   183  	isNeedRestart := false
   184  	if db.InstanceStatus.IsUp() && isNeedShutdown {
   185  		if b.NoWait {
   186  			return nil, errors.New("NoWait option is not available due to the need to shut down")
   187  		}
   188  
   189  		isNeedRestart = true
   190  		if err := power.ShutdownDatabase(ctx, b.Client.Database, zone, id, false); err != nil {
   191  			return nil, err
   192  		}
   193  	}
   194  
   195  	_, err = b.Client.Database.Update(ctx, zone, id, &sacloud.DatabaseUpdateRequest{
   196  		Name:               b.Name,
   197  		Description:        b.Description,
   198  		Tags:               b.Tags,
   199  		IconID:             b.IconID,
   200  		CommonSetting:      b.CommonSetting,
   201  		BackupSetting:      b.BackupSetting,
   202  		ReplicationSetting: b.ReplicationSetting,
   203  		SettingsHash:       b.SettingsHash,
   204  	})
   205  	if err != nil {
   206  		return nil, err
   207  	}
   208  	if err := b.reconcileDatabaseParameters(ctx, zone, id); err != nil {
   209  		return nil, err
   210  	}
   211  	if err := b.Client.Database.Config(ctx, zone, id); err != nil {
   212  		return nil, err
   213  	}
   214  	if isNeedRestart {
   215  		if err := power.BootDatabase(ctx, b.Client.Database, zone, id); err != nil {
   216  			return nil, err
   217  		}
   218  	}
   219  
   220  	// refresh
   221  	db, err = b.Client.Database.Read(ctx, zone, id)
   222  	if err != nil {
   223  		return nil, err
   224  	}
   225  	return db, err
   226  }
   227  
   228  func (b *Builder) collectUpdateInfo(db *sacloud.Database) (isNeedShutdown bool, err error) {
   229  	isNeedShutdown = b.CommonSetting.ReplicaPassword != db.CommonSetting.ReplicaPassword
   230  	return
   231  }
   232  
   233  func (b *Builder) reconcileDatabaseParameters(ctx context.Context, zone string, id types.ID) error {
   234  	parameters, err := b.Client.Database.GetParameter(ctx, zone, id)
   235  	if err != nil {
   236  		return err
   237  	}
   238  
   239  	newParameters := make(map[string]interface{})
   240  	// 既存のパラメータは一旦nullに
   241  	for k := range parameters.Settings {
   242  		newParameters[k] = nil
   243  	}
   244  
   245  	for k, v := range b.Parameters {
   246  		found := false
   247  		for _, meta := range parameters.MetaInfo {
   248  			if k == meta.Label {
   249  				newParameters[meta.Name] = v
   250  				found = true
   251  				break
   252  			}
   253  		}
   254  		// kvのキーがラベルではなかったらそのままnmへ
   255  		if !found {
   256  			newParameters[k] = v
   257  		}
   258  	}
   259  	if len(newParameters) > 0 {
   260  		// DatabaseAPI.Configはあとで呼ぶ
   261  		return b.Client.Database.SetParameter(ctx, zone, id, newParameters)
   262  	}
   263  
   264  	return nil
   265  }