github.com/sacloud/libsacloud/v2@v2.32.3/helper/builder/vpcrouter/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 vpcrouter 16 17 import ( 18 "context" 19 "errors" 20 "fmt" 21 "time" 22 23 "github.com/sacloud/libsacloud/v2/helper/builder" 24 "github.com/sacloud/libsacloud/v2/helper/power" 25 "github.com/sacloud/libsacloud/v2/helper/setup" 26 "github.com/sacloud/libsacloud/v2/sacloud" 27 "github.com/sacloud/libsacloud/v2/sacloud/accessor" 28 "github.com/sacloud/libsacloud/v2/sacloud/types" 29 ) 30 31 // Builder VPCルータの構築を行う 32 type Builder struct { 33 Name string 34 Description string 35 Tags types.Tags 36 IconID types.ID 37 PlanID types.ID 38 Version int 39 NICSetting NICSettingHolder 40 AdditionalNICSettings []AdditionalNICSettingHolder 41 RouterSetting *RouterSetting 42 43 SetupOptions *builder.RetryableSetupParameter 44 Client sacloud.VPCRouterAPI 45 NoWait bool 46 } 47 48 // RouterSetting VPCルータの設定 49 type RouterSetting struct { 50 VRID int 51 InternetConnectionEnabled types.StringFlag 52 StaticNAT []*sacloud.VPCRouterStaticNAT 53 PortForwarding []*sacloud.VPCRouterPortForwarding 54 Firewall []*sacloud.VPCRouterFirewall 55 DHCPServer []*sacloud.VPCRouterDHCPServer 56 DHCPStaticMapping []*sacloud.VPCRouterDHCPStaticMapping 57 DNSForwarding *sacloud.VPCRouterDNSForwarding 58 PPTPServer *sacloud.VPCRouterPPTPServer 59 L2TPIPsecServer *sacloud.VPCRouterL2TPIPsecServer 60 WireGuard *sacloud.VPCRouterWireGuard 61 RemoteAccessUsers []*sacloud.VPCRouterRemoteAccessUser 62 SiteToSiteIPsecVPN []*sacloud.VPCRouterSiteToSiteIPsecVPN 63 StaticRoute []*sacloud.VPCRouterStaticRoute 64 SyslogHost string 65 } 66 67 func (b *Builder) init() { 68 if b.SetupOptions == nil { 69 b.SetupOptions = builder.DefaultSetupOptions() 70 } 71 if b.RouterSetting == nil { 72 b.RouterSetting = &RouterSetting{ 73 InternetConnectionEnabled: true, 74 } 75 } 76 } 77 78 func (b *Builder) getInitInterfaceSettings() []*sacloud.VPCRouterInterfaceSetting { 79 s := b.NICSetting.getInterfaceSetting() 80 if s != nil { 81 return []*sacloud.VPCRouterInterfaceSetting{s} 82 } 83 return nil 84 } 85 86 func (b *Builder) getInterfaceSettings() []*sacloud.VPCRouterInterfaceSetting { 87 var settings []*sacloud.VPCRouterInterfaceSetting 88 if s := b.NICSetting.getInterfaceSetting(); s != nil { 89 settings = append(settings, s) 90 } 91 for _, additionalNIC := range b.AdditionalNICSettings { 92 settings = append(settings, additionalNIC.getInterfaceSetting()) 93 } 94 return settings 95 } 96 97 // Validate 設定値の検証 98 func (b *Builder) Validate(ctx context.Context, zone string) error { 99 if err := b.validateCommon(ctx, zone); err != nil { 100 return err 101 } 102 103 if b.NoWait { 104 if len(b.AdditionalNICSettings) > 0 || b.RouterSetting != nil { 105 return errors.New("NoWait=true is not supported with AdditionalNICSettings and RouterSetting") 106 } 107 if b.SetupOptions != nil && b.SetupOptions.BootAfterBuild { 108 return errors.New("NoWait=true is not supported with SetupOptions.BootAfterBuild") 109 } 110 } 111 112 switch b.PlanID { 113 case types.VPCRouterPlans.Standard: 114 return b.validateForStandard(ctx, zone) 115 default: 116 return b.validateForPremium(ctx, zone) 117 } 118 } 119 120 func (b *Builder) validateCommon(ctx context.Context, zone string) error { 121 if b.NICSetting == nil { 122 return errors.New("required field is missing: NICSetting") 123 } 124 switch b.PlanID { 125 case types.VPCRouterPlans.Standard, types.VPCRouterPlans.Premium, types.VPCRouterPlans.HighSpec, types.VPCRouterPlans.HighSpec4000: 126 // noop 127 default: 128 return fmt.Errorf("invalid plan: PlanID: %s", b.PlanID.String()) 129 } 130 131 for i, nic := range b.AdditionalNICSettings { 132 switchID, index := nic.getSwitchInfo() 133 if switchID.IsEmpty() { 134 return fmt.Errorf("invalid SwitchID is specified: AdditionalNICSettings[%d].SwitchID is empty", i) 135 } 136 if index == 0 { 137 return fmt.Errorf("invalid SwitchID is specified: AdditionalNICSettings[%d].Index is Zero", i) 138 } 139 } 140 141 return nil 142 } 143 144 func (b *Builder) validateForStandard(ctx context.Context, zone string) error { 145 if _, ok := b.NICSetting.(*StandardNICSetting); !ok { 146 return fmt.Errorf("invalid NICSetting is specified: %v", b.NICSetting) 147 } 148 for i, nic := range b.AdditionalNICSettings { 149 if _, ok := nic.(*AdditionalStandardNICSetting); !ok { 150 return fmt.Errorf("invalid AdditionalNICSettings is specified: AdditionalNICSettings[%d]:%v", i, nic) 151 } 152 } 153 154 // Static NAT is only for Premium+ 155 if b.RouterSetting.StaticNAT != nil { 156 return errors.New("invalid RouterSetting is specified: StaticNAT is only for Premium+ plan") 157 } 158 return nil 159 } 160 161 func (b *Builder) validateForPremium(ctx context.Context, zone string) error { 162 if _, ok := b.NICSetting.(*PremiumNICSetting); !ok { 163 return fmt.Errorf("invalid NICSetting is specified: %v", b.NICSetting) 164 } 165 for i, nic := range b.AdditionalNICSettings { 166 if _, ok := nic.(*AdditionalPremiumNICSetting); !ok { 167 return fmt.Errorf("invalid AdditionalNICSettings is specified: AdditionalNICSettings[%d]:%v", i, nic) 168 } 169 } 170 return nil 171 } 172 173 // Build VPCルータの作成、スイッチの接続をまとめて行う 174 func (b *Builder) Build(ctx context.Context, zone string) (*sacloud.VPCRouter, error) { 175 b.init() 176 177 if err := b.Validate(ctx, zone); err != nil { 178 return nil, err 179 } 180 181 builder := &setup.RetryableSetup{ 182 Create: func(ctx context.Context, zone string) (accessor.ID, error) { 183 return b.Client.Create(ctx, zone, &sacloud.VPCRouterCreateRequest{ 184 Name: b.Name, 185 Description: b.Description, 186 Tags: b.Tags, 187 IconID: b.IconID, 188 PlanID: b.PlanID, 189 Switch: b.NICSetting.getConnectedSwitch(), 190 IPAddresses: b.NICSetting.getIPAddresses(), 191 Version: b.Version, 192 Settings: &sacloud.VPCRouterSetting{ 193 VRID: b.RouterSetting.VRID, 194 InternetConnectionEnabled: b.RouterSetting.InternetConnectionEnabled, 195 Interfaces: b.getInitInterfaceSettings(), 196 SyslogHost: b.RouterSetting.SyslogHost, 197 }, 198 }) 199 }, 200 ProvisionBeforeUp: func(ctx context.Context, zone string, id types.ID, target interface{}) error { 201 if b.NoWait { 202 return nil 203 } 204 vpcRouter := target.(*sacloud.VPCRouter) 205 206 // スイッチの接続 207 for _, additionalNIC := range b.AdditionalNICSettings { 208 switchID, index := additionalNIC.getSwitchInfo() 209 if err := b.Client.ConnectToSwitch(ctx, zone, id, index, switchID); err != nil { 210 return err 211 } 212 } 213 214 // [HACK] スイッチ接続直後だとエラーになることがあるため数秒待つ 215 time.Sleep(b.SetupOptions.NICUpdateWaitDuration) 216 217 // 残りの設定の投入 218 _, err := b.Client.UpdateSettings(ctx, zone, id, &sacloud.VPCRouterUpdateSettingsRequest{ 219 Settings: &sacloud.VPCRouterSetting{ 220 VRID: b.RouterSetting.VRID, 221 InternetConnectionEnabled: b.RouterSetting.InternetConnectionEnabled, 222 Interfaces: b.getInterfaceSettings(), 223 StaticNAT: b.RouterSetting.StaticNAT, 224 PortForwarding: b.RouterSetting.PortForwarding, 225 Firewall: b.RouterSetting.Firewall, 226 DHCPServer: b.RouterSetting.DHCPServer, 227 DHCPStaticMapping: b.RouterSetting.DHCPStaticMapping, 228 DNSForwarding: b.RouterSetting.DNSForwarding, 229 PPTPServer: b.RouterSetting.PPTPServer, 230 PPTPServerEnabled: b.RouterSetting.PPTPServer != nil, 231 L2TPIPsecServer: b.RouterSetting.L2TPIPsecServer, 232 L2TPIPsecServerEnabled: b.RouterSetting.L2TPIPsecServer != nil, 233 WireGuard: b.RouterSetting.WireGuard, 234 WireGuardEnabled: b.RouterSetting.WireGuard != nil, 235 RemoteAccessUsers: b.RouterSetting.RemoteAccessUsers, 236 SiteToSiteIPsecVPN: b.RouterSetting.SiteToSiteIPsecVPN, 237 StaticRoute: b.RouterSetting.StaticRoute, 238 SyslogHost: b.RouterSetting.SyslogHost, 239 }, 240 SettingsHash: vpcRouter.SettingsHash, 241 }) 242 if err != nil { 243 return err 244 } 245 if err := b.Client.Config(ctx, zone, id); err != nil { 246 return err 247 } 248 249 if b.SetupOptions.BootAfterBuild { 250 return power.BootVPCRouter(ctx, b.Client, zone, id) 251 } 252 return nil 253 }, 254 Delete: func(ctx context.Context, zone string, id types.ID) error { 255 return b.Client.Delete(ctx, zone, id) 256 }, 257 Read: func(ctx context.Context, zone string, id types.ID) (interface{}, error) { 258 return b.Client.Read(ctx, zone, id) 259 }, 260 IsWaitForCopy: !b.NoWait, 261 IsWaitForUp: !b.NoWait && b.SetupOptions.BootAfterBuild, 262 RetryCount: b.SetupOptions.RetryCount, 263 ProvisioningRetryCount: 1, 264 ProvisioningRetryInterval: b.SetupOptions.ProvisioningRetryInterval, 265 DeleteRetryCount: b.SetupOptions.DeleteRetryCount, 266 DeleteRetryInterval: b.SetupOptions.DeleteRetryInterval, 267 PollingInterval: b.SetupOptions.PollingInterval, 268 } 269 270 result, err := builder.Setup(ctx, zone) 271 var vpcRouter *sacloud.VPCRouter 272 if result != nil { 273 vpcRouter = result.(*sacloud.VPCRouter) 274 } 275 if err != nil { 276 return vpcRouter, err 277 } 278 279 // refresh 280 refreshed, err := b.Client.Read(ctx, zone, vpcRouter.ID) 281 if err != nil { 282 return vpcRouter, err 283 } 284 return refreshed, nil 285 } 286 287 // Update VPCルータの更新(再起動を伴う場合あり) 288 // 289 // 接続先スイッチが変更されていた場合、VPCルータの再起動が行われます。 290 func (b *Builder) Update(ctx context.Context, zone string, id types.ID) (*sacloud.VPCRouter, error) { 291 b.init() 292 293 if err := b.Validate(ctx, zone); err != nil { 294 return nil, err 295 } 296 297 // check VPCRouter is exists 298 vpcRouter, err := b.Client.Read(ctx, zone, id) 299 if err != nil { 300 return nil, err 301 } 302 303 isNeedShutdown, err := b.collectUpdateInfo(vpcRouter) 304 if err != nil { 305 return nil, err 306 } 307 308 isNeedRestart := false 309 if vpcRouter.InstanceStatus.IsUp() && isNeedShutdown { 310 if b.NoWait { 311 return nil, errors.New("NoWait option is not available due to the need to shut down") 312 } 313 314 isNeedRestart = true 315 if err := power.ShutdownVPCRouter(ctx, b.Client, zone, id, false); err != nil { 316 return nil, err 317 } 318 } 319 320 // NICの切断/変更(変更分のみ) 321 for _, iface := range vpcRouter.Interfaces { 322 if iface.Index == 0 { 323 continue 324 } 325 326 newSwitchID := b.findAdditionalSwitchSettingByIndex(iface.Index) // 削除されていた場合types.ID(0)が返る 327 if iface.SwitchID != newSwitchID { 328 // disconnect 329 if err := b.Client.DisconnectFromSwitch(ctx, zone, id, iface.Index); err != nil { 330 return nil, err 331 } 332 // connect 333 if !newSwitchID.IsEmpty() { 334 if err := b.Client.ConnectToSwitch(ctx, zone, id, iface.Index, newSwitchID); err != nil { 335 return nil, err 336 } 337 } 338 } 339 } 340 341 // 追加されたNICの接続 342 for _, nicSetting := range b.AdditionalNICSettings { 343 switchID, index := nicSetting.getSwitchInfo() 344 iface := b.findInterfaceByIndex(vpcRouter, index) 345 if iface == nil { 346 if err := b.Client.ConnectToSwitch(ctx, zone, id, index, switchID); err != nil { 347 return nil, err 348 } 349 } 350 } 351 // [HACK] スイッチ接続直後だとエラーになることがあるため数秒待つ 352 time.Sleep(b.SetupOptions.NICUpdateWaitDuration) 353 354 _, err = b.Client.Update(ctx, zone, id, &sacloud.VPCRouterUpdateRequest{ 355 Name: b.Name, 356 Description: b.Description, 357 Tags: b.Tags, 358 IconID: b.IconID, 359 Settings: &sacloud.VPCRouterSetting{ 360 VRID: b.RouterSetting.VRID, 361 InternetConnectionEnabled: b.RouterSetting.InternetConnectionEnabled, 362 Interfaces: b.getInterfaceSettings(), 363 StaticNAT: b.RouterSetting.StaticNAT, 364 PortForwarding: b.RouterSetting.PortForwarding, 365 Firewall: b.RouterSetting.Firewall, 366 DHCPServer: b.RouterSetting.DHCPServer, 367 DHCPStaticMapping: b.RouterSetting.DHCPStaticMapping, 368 DNSForwarding: b.RouterSetting.DNSForwarding, 369 PPTPServer: b.RouterSetting.PPTPServer, 370 PPTPServerEnabled: b.RouterSetting.PPTPServer != nil, 371 L2TPIPsecServer: b.RouterSetting.L2TPIPsecServer, 372 L2TPIPsecServerEnabled: b.RouterSetting.L2TPIPsecServer != nil, 373 WireGuard: b.RouterSetting.WireGuard, 374 WireGuardEnabled: b.RouterSetting.WireGuard != nil, 375 RemoteAccessUsers: b.RouterSetting.RemoteAccessUsers, 376 SiteToSiteIPsecVPN: b.RouterSetting.SiteToSiteIPsecVPN, 377 StaticRoute: b.RouterSetting.StaticRoute, 378 SyslogHost: b.RouterSetting.SyslogHost, 379 }, 380 SettingsHash: vpcRouter.SettingsHash, 381 }) 382 if err != nil { 383 return nil, err 384 } 385 386 if err := b.Client.Config(ctx, zone, id); err != nil { 387 return nil, err 388 } 389 390 if isNeedRestart { 391 if err := power.BootVPCRouter(ctx, b.Client, zone, id); err != nil { 392 return nil, err 393 } 394 } 395 // refresh 396 vpcRouter, err = b.Client.Read(ctx, zone, id) 397 if err != nil { 398 return nil, err 399 } 400 return vpcRouter, err 401 } 402 403 func (b *Builder) collectUpdateInfo(vpcRouter *sacloud.VPCRouter) (isNeedShutdown bool, err error) { 404 // プランの変更はエラーとする 405 if vpcRouter.PlanID != b.PlanID { 406 err = fmt.Errorf("unsupported operation: VPCRouter is not allowd changing Plan: currentPlan: %s", vpcRouter.PlanID.String()) 407 return 408 } 409 410 // スイッチの変更/削除は再起動が必要 411 for _, iface := range vpcRouter.Interfaces { 412 if iface.Index == 0 { 413 continue 414 } 415 newSwitchID := b.findAdditionalSwitchSettingByIndex(iface.Index) // 削除された場合はtypes.ID(0)が返る 416 isNeedShutdown = iface.SwitchID != newSwitchID 417 } 418 if isNeedShutdown { 419 return 420 } 421 422 // スイッチの増設は再起動が必要 423 if len(vpcRouter.Interfaces)-1 != len(b.AdditionalNICSettings) { 424 isNeedShutdown = true 425 } 426 return 427 } 428 429 func (b *Builder) findInterfaceByIndex(vpcRouter *sacloud.VPCRouter, ifIndex int) *sacloud.VPCRouterInterface { 430 for _, iface := range vpcRouter.Interfaces { 431 if iface.Index == ifIndex { 432 return iface 433 } 434 } 435 return nil 436 } 437 438 func (b *Builder) findAdditionalSwitchSettingByIndex(ifIndex int) types.ID { 439 for _, nic := range b.AdditionalNICSettings { 440 switchID, index := nic.getSwitchInfo() 441 if index == ifIndex { 442 return switchID 443 } 444 } 445 return types.ID(0) 446 }