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 }