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  }