github.com/sacloud/libsacloud/v2@v2.32.3/helper/builder/mobilegateway/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 mobilegateway
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"reflect"
    22  	"time"
    23  
    24  	"github.com/sacloud/libsacloud/v2/helper/builder"
    25  	"github.com/sacloud/libsacloud/v2/helper/power"
    26  	"github.com/sacloud/libsacloud/v2/helper/setup"
    27  	"github.com/sacloud/libsacloud/v2/sacloud"
    28  	"github.com/sacloud/libsacloud/v2/sacloud/accessor"
    29  	"github.com/sacloud/libsacloud/v2/sacloud/types"
    30  )
    31  
    32  // Builder モバイルゲートウェイの構築を行う
    33  type Builder struct {
    34  	Name                            string
    35  	Description                     string
    36  	Tags                            types.Tags
    37  	IconID                          types.ID
    38  	PrivateInterface                *PrivateInterfaceSetting
    39  	StaticRoutes                    []*sacloud.MobileGatewayStaticRoute
    40  	SIMRoutes                       []*SIMRouteSetting
    41  	InternetConnectionEnabled       bool
    42  	InterDeviceCommunicationEnabled bool
    43  	DNS                             *sacloud.MobileGatewayDNSSetting
    44  	SIMs                            []*SIMSetting
    45  	TrafficConfig                   *sacloud.MobileGatewayTrafficControl
    46  
    47  	SettingsHash string
    48  	NoWait       bool
    49  
    50  	SetupOptions *builder.RetryableSetupParameter
    51  	Client       *APIClient
    52  }
    53  
    54  // PrivateInterfaceSetting モバイルゲートウェイのプライベート側インターフェース設定
    55  type PrivateInterfaceSetting struct {
    56  	SwitchID       types.ID
    57  	IPAddress      string
    58  	NetworkMaskLen int
    59  }
    60  
    61  // SIMSetting モバイルゲートウェイに接続するSIM設定
    62  type SIMSetting struct {
    63  	SIMID     types.ID
    64  	IPAddress string
    65  }
    66  
    67  // SIMRouteSetting SIMルート設定
    68  type SIMRouteSetting struct {
    69  	SIMID  types.ID
    70  	Prefix string
    71  }
    72  
    73  // BuilderFromResource 既存のMobileGatewayからBuilderを組み立てて返す
    74  func BuilderFromResource(ctx context.Context, caller sacloud.APICaller, zone string, id types.ID) (*Builder, error) {
    75  	mgwOp := sacloud.NewMobileGatewayOp(caller)
    76  	current, err := mgwOp.Read(ctx, zone, id)
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  
    81  	var privateInterface *PrivateInterfaceSetting
    82  	for i, nic := range current.InterfaceSettings {
    83  		if nic.Index == 1 {
    84  			privateInterface = &PrivateInterfaceSetting{
    85  				SwitchID:       current.Interfaces[i].SwitchID,
    86  				IPAddress:      nic.IPAddress[0],
    87  				NetworkMaskLen: nic.NetworkMaskLen,
    88  			}
    89  		}
    90  	}
    91  
    92  	simRoutes, err := mgwOp.GetSIMRoutes(ctx, zone, id)
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  	var simRouteSettings []*SIMRouteSetting
    97  	for _, r := range simRoutes {
    98  		simRouteSettings = append(simRouteSettings, &SIMRouteSetting{
    99  			SIMID:  types.StringID(r.ResourceID),
   100  			Prefix: r.Prefix,
   101  		})
   102  	}
   103  
   104  	dns, err := mgwOp.GetDNS(ctx, zone, id)
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  
   109  	sims, err := mgwOp.ListSIM(ctx, zone, id)
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  	var simSettings []*SIMSetting
   114  	for _, s := range sims {
   115  		simSettings = append(simSettings, &SIMSetting{
   116  			SIMID:     types.StringID(s.ResourceID),
   117  			IPAddress: s.IP,
   118  		})
   119  	}
   120  
   121  	trafficConfig, err := mgwOp.GetTrafficConfig(ctx, zone, id)
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  
   126  	return &Builder{
   127  		Name:                            current.Name,
   128  		Description:                     current.Description,
   129  		Tags:                            current.Tags,
   130  		IconID:                          current.IconID,
   131  		PrivateInterface:                privateInterface,
   132  		StaticRoutes:                    current.StaticRoutes,
   133  		SIMRoutes:                       simRouteSettings,
   134  		InternetConnectionEnabled:       current.InternetConnectionEnabled.Bool(),
   135  		InterDeviceCommunicationEnabled: current.InterDeviceCommunicationEnabled.Bool(),
   136  		DNS:                             dns,
   137  		SIMs:                            simSettings,
   138  		TrafficConfig:                   trafficConfig,
   139  		SettingsHash:                    current.SettingsHash,
   140  		NoWait:                          false,
   141  		Client:                          NewAPIClient(caller),
   142  	}, nil
   143  }
   144  
   145  func (b *Builder) init() {
   146  	if b.SetupOptions == nil {
   147  		b.SetupOptions = builder.DefaultSetupOptions()
   148  	}
   149  }
   150  
   151  // Validate 設定値の検証
   152  func (b *Builder) Validate(ctx context.Context, zone string) error {
   153  	if b.PrivateInterface != nil {
   154  		if b.PrivateInterface.SwitchID.IsEmpty() {
   155  			return fmt.Errorf("switch id is required when specified private interface")
   156  		}
   157  		if b.PrivateInterface.IPAddress == "" {
   158  			return fmt.Errorf("ip address is required when specified private interface")
   159  		}
   160  		if b.PrivateInterface.NetworkMaskLen == 0 {
   161  			return fmt.Errorf("ip address is required when specified private interface")
   162  		}
   163  	}
   164  	if len(b.SIMRoutes) > 0 && len(b.SIMs) == 0 {
   165  		return fmt.Errorf("sim settings are required when specified sim routes")
   166  	}
   167  
   168  	if b.NoWait {
   169  		if b.PrivateInterface != nil || len(b.StaticRoutes) > 0 || len(b.SIMRoutes) > 0 || b.DNS != nil || len(b.SIMs) > 0 || b.TrafficConfig != nil {
   170  			return errors.New("NoWait=true is not supported with PrivateInterface/StaticRoutes/SIMRoutes/DNS/SIMs/TrafficConfig")
   171  		}
   172  	}
   173  	return nil
   174  }
   175  
   176  // Build モバイルゲートウェイの作成や設定をまとめて行う
   177  func (b *Builder) Build(ctx context.Context, zone string) (*sacloud.MobileGateway, error) {
   178  	b.init()
   179  
   180  	if err := b.Validate(ctx, zone); err != nil {
   181  		return nil, err
   182  	}
   183  
   184  	builder := &setup.RetryableSetup{
   185  		Create: func(ctx context.Context, zone string) (accessor.ID, error) {
   186  			return b.Client.MobileGateway.Create(ctx, zone, &sacloud.MobileGatewayCreateRequest{
   187  				Name:                            b.Name,
   188  				Description:                     b.Description,
   189  				Tags:                            b.Tags,
   190  				IconID:                          b.IconID,
   191  				InternetConnectionEnabled:       types.StringFlag(b.InternetConnectionEnabled),
   192  				InterDeviceCommunicationEnabled: types.StringFlag(b.InterDeviceCommunicationEnabled),
   193  			})
   194  		},
   195  		ProvisionBeforeUp: func(ctx context.Context, zone string, id types.ID, target interface{}) error {
   196  			if b.NoWait {
   197  				return nil
   198  			}
   199  			mgw := target.(*sacloud.MobileGateway)
   200  
   201  			// スイッチの接続
   202  			if b.PrivateInterface != nil {
   203  				if err := b.Client.MobileGateway.ConnectToSwitch(ctx, zone, id, b.PrivateInterface.SwitchID); err != nil {
   204  					return err
   205  				}
   206  			}
   207  
   208  			// [HACK] スイッチ接続直後だとエラーになることがあるため数秒待つ
   209  			time.Sleep(b.SetupOptions.NICUpdateWaitDuration)
   210  
   211  			// Interface設定
   212  			updated, err := b.Client.MobileGateway.UpdateSettings(ctx, zone, id, &sacloud.MobileGatewayUpdateSettingsRequest{
   213  				InterfaceSettings:               b.getInterfaceSettings(),
   214  				InternetConnectionEnabled:       types.StringFlag(b.InternetConnectionEnabled),
   215  				InterDeviceCommunicationEnabled: types.StringFlag(b.InterDeviceCommunicationEnabled),
   216  				SettingsHash:                    mgw.SettingsHash,
   217  			})
   218  			if err != nil {
   219  				return err
   220  			}
   221  			// [HACK] インターフェースの設定をConfigで反映させておかないとエラーになることへの対応
   222  			// see: https://github.com/sacloud/libsacloud/issues/589
   223  			if err := b.Client.MobileGateway.Config(ctx, zone, id); err != nil {
   224  				return err
   225  			}
   226  			mgw = updated
   227  
   228  			// traffic config
   229  			if b.TrafficConfig != nil {
   230  				if err := b.Client.MobileGateway.SetTrafficConfig(ctx, zone, id, b.TrafficConfig); err != nil {
   231  					return err
   232  				}
   233  			}
   234  
   235  			// dns
   236  			if b.DNS != nil {
   237  				if err := b.Client.MobileGateway.SetDNS(ctx, zone, id, b.DNS); err != nil {
   238  					return err
   239  				}
   240  			}
   241  
   242  			// static route
   243  			if len(b.StaticRoutes) > 0 {
   244  				_, err := b.Client.MobileGateway.UpdateSettings(ctx, zone, id, &sacloud.MobileGatewayUpdateSettingsRequest{
   245  					InterfaceSettings:               b.getInterfaceSettings(),
   246  					StaticRoutes:                    b.StaticRoutes,
   247  					InternetConnectionEnabled:       types.StringFlag(b.InternetConnectionEnabled),
   248  					InterDeviceCommunicationEnabled: types.StringFlag(b.InterDeviceCommunicationEnabled),
   249  					SettingsHash:                    mgw.SettingsHash,
   250  				})
   251  				if err != nil {
   252  					return err
   253  				}
   254  			}
   255  
   256  			// SIMs
   257  			if len(b.SIMs) > 0 {
   258  				for _, sim := range b.SIMs {
   259  					if err := b.Client.MobileGateway.AddSIM(ctx, zone, id, &sacloud.MobileGatewayAddSIMRequest{SIMID: sim.SIMID.String()}); err != nil {
   260  						return err
   261  					}
   262  					if err := b.Client.SIM.AssignIP(ctx, sim.SIMID, &sacloud.SIMAssignIPRequest{IP: sim.IPAddress}); err != nil {
   263  						return err
   264  					}
   265  				}
   266  			}
   267  
   268  			// SIM routes
   269  			if len(b.SIMRoutes) > 0 {
   270  				if err := b.Client.MobileGateway.SetSIMRoutes(ctx, zone, id, b.getSIMRouteSettings()); err != nil {
   271  					return err
   272  				}
   273  			}
   274  
   275  			if err := b.Client.MobileGateway.Config(ctx, zone, id); err != nil {
   276  				return err
   277  			}
   278  
   279  			if b.SetupOptions.BootAfterBuild {
   280  				return power.BootMobileGateway(ctx, b.Client.MobileGateway, zone, id)
   281  			}
   282  			return nil
   283  		},
   284  		Delete: func(ctx context.Context, zone string, id types.ID) error {
   285  			return b.Client.MobileGateway.Delete(ctx, zone, id)
   286  		},
   287  		Read: func(ctx context.Context, zone string, id types.ID) (interface{}, error) {
   288  			return b.Client.MobileGateway.Read(ctx, zone, id)
   289  		},
   290  		IsWaitForCopy:             !b.NoWait,
   291  		IsWaitForUp:               !b.NoWait && b.SetupOptions.BootAfterBuild,
   292  		RetryCount:                b.SetupOptions.RetryCount,
   293  		ProvisioningRetryCount:    1,
   294  		ProvisioningRetryInterval: b.SetupOptions.ProvisioningRetryInterval,
   295  		DeleteRetryCount:          b.SetupOptions.DeleteRetryCount,
   296  		DeleteRetryInterval:       b.SetupOptions.DeleteRetryInterval,
   297  		PollingInterval:           b.SetupOptions.PollingInterval,
   298  	}
   299  
   300  	result, err := builder.Setup(ctx, zone)
   301  	var mgw *sacloud.MobileGateway
   302  	if result != nil {
   303  		mgw = result.(*sacloud.MobileGateway)
   304  	}
   305  	if err != nil {
   306  		return mgw, err
   307  	}
   308  
   309  	// refresh
   310  	refreshed, err := b.Client.MobileGateway.Read(ctx, zone, mgw.ID)
   311  	if err != nil {
   312  		return mgw, err
   313  	}
   314  	return refreshed, nil
   315  }
   316  
   317  // Update モバイルゲートウェイの更新
   318  //
   319  // 更新中、SIMルートが一時的にクリアされます。また、接続先スイッチが変更されていた場合は再起動されます。
   320  func (b *Builder) Update(ctx context.Context, zone string, id types.ID) (*sacloud.MobileGateway, error) {
   321  	b.init()
   322  
   323  	if err := b.Validate(ctx, zone); err != nil {
   324  		return nil, err
   325  	}
   326  
   327  	// check MobileGateway is exists
   328  	mgw, err := b.Client.MobileGateway.Read(ctx, zone, id)
   329  	if err != nil {
   330  		return nil, err
   331  	}
   332  	mgw.SettingsHash = b.SettingsHash // 更新ルートが複数あるためここに設定しておく
   333  
   334  	isNeedShutdown, err := b.collectUpdateInfo(mgw)
   335  	if err != nil {
   336  		return nil, err
   337  	}
   338  
   339  	isNeedRestart := false
   340  	if mgw.InstanceStatus.IsUp() && isNeedShutdown {
   341  		if b.NoWait {
   342  			return nil, errors.New("NoWait option is not available due to the need to shut down")
   343  		}
   344  		isNeedRestart = true
   345  		if err := power.ShutdownMobileGateway(ctx, b.Client.MobileGateway, zone, id, false); err != nil {
   346  			return nil, err
   347  		}
   348  	}
   349  
   350  	// NICの切断/変更
   351  	if b.isPrivateInterfaceChanged(mgw) {
   352  		if len(mgw.Interfaces) > 1 && !mgw.Interfaces[1].SwitchID.IsEmpty() {
   353  			if b.PrivateInterface != nil && mgw.Interfaces[1].SwitchID != b.PrivateInterface.SwitchID {
   354  				// 切断
   355  				if err := b.Client.MobileGateway.DisconnectFromSwitch(ctx, zone, id); err != nil {
   356  					return nil, err
   357  				}
   358  				// [HACK] スイッチ接続直後だとエラーになることがあるため数秒待つ
   359  				time.Sleep(b.SetupOptions.NICUpdateWaitDuration)
   360  
   361  				updated, err := b.Client.MobileGateway.UpdateSettings(ctx, zone, id, &sacloud.MobileGatewayUpdateSettingsRequest{
   362  					InternetConnectionEnabled:       types.StringFlag(b.InternetConnectionEnabled),
   363  					InterDeviceCommunicationEnabled: types.StringFlag(b.InterDeviceCommunicationEnabled),
   364  					SettingsHash:                    mgw.SettingsHash,
   365  				})
   366  				if err != nil {
   367  					return nil, err
   368  				}
   369  				// [HACK] インターフェースの設定をConfigで反映させておかないとエラーになることへの対応
   370  				// see: https://github.com/sacloud/libsacloud/issues/589
   371  				if err := b.Client.MobileGateway.Config(ctx, zone, id); err != nil {
   372  					return nil, err
   373  				}
   374  				mgw = updated
   375  			}
   376  		}
   377  
   378  		// 接続
   379  		if b.PrivateInterface != nil {
   380  			if len(mgw.Interfaces) == 1 {
   381  				// スイッチの接続
   382  				if err := b.Client.MobileGateway.ConnectToSwitch(ctx, zone, id, b.PrivateInterface.SwitchID); err != nil {
   383  					return nil, err
   384  				}
   385  
   386  				// [HACK] スイッチ接続直後だとエラーになることがあるため数秒待つ
   387  				time.Sleep(b.SetupOptions.NICUpdateWaitDuration)
   388  			}
   389  
   390  			// Interface設定
   391  			updated, err := b.Client.MobileGateway.UpdateSettings(ctx, zone, id, &sacloud.MobileGatewayUpdateSettingsRequest{
   392  				InterfaceSettings:               b.getInterfaceSettings(),
   393  				InternetConnectionEnabled:       types.StringFlag(b.InternetConnectionEnabled),
   394  				InterDeviceCommunicationEnabled: types.StringFlag(b.InterDeviceCommunicationEnabled),
   395  				SettingsHash:                    mgw.SettingsHash,
   396  			})
   397  			if err != nil {
   398  				return nil, err
   399  			}
   400  			// [HACK] インターフェースの設定をConfigで反映させておかないとエラーになることへの対応
   401  			// see: https://github.com/sacloud/libsacloud/issues/589
   402  			if err := b.Client.MobileGateway.Config(ctx, zone, id); err != nil {
   403  				return nil, err
   404  			}
   405  			mgw = updated
   406  		}
   407  	}
   408  
   409  	mgw, err = b.Client.MobileGateway.Update(ctx, zone, id, &sacloud.MobileGatewayUpdateRequest{
   410  		Name:                            b.Name,
   411  		Description:                     b.Description,
   412  		Tags:                            b.Tags,
   413  		IconID:                          b.IconID,
   414  		InterfaceSettings:               b.getInterfaceSettings(),
   415  		InternetConnectionEnabled:       types.StringFlag(b.InternetConnectionEnabled),
   416  		InterDeviceCommunicationEnabled: types.StringFlag(b.InterDeviceCommunicationEnabled),
   417  		SettingsHash:                    mgw.SettingsHash,
   418  	})
   419  	if err != nil {
   420  		return nil, err
   421  	}
   422  
   423  	// traffic config
   424  	trafficConfig, err := b.Client.MobileGateway.GetTrafficConfig(ctx, zone, id)
   425  	if err != nil {
   426  		if !sacloud.IsNotFoundError(err) {
   427  			return nil, err
   428  		}
   429  	}
   430  	if !reflect.DeepEqual(trafficConfig, b.TrafficConfig) {
   431  		if trafficConfig != nil && b.TrafficConfig == nil {
   432  			if err := b.Client.MobileGateway.DeleteTrafficConfig(ctx, zone, id); err != nil {
   433  				return nil, err
   434  			}
   435  		} else {
   436  			if err := b.Client.MobileGateway.SetTrafficConfig(ctx, zone, id, b.TrafficConfig); err != nil {
   437  				return nil, err
   438  			}
   439  		}
   440  	}
   441  
   442  	// dns
   443  	dns, err := b.Client.MobileGateway.GetDNS(ctx, zone, id)
   444  	if err != nil {
   445  		if !sacloud.IsNotFoundError(err) {
   446  			return nil, err
   447  		}
   448  	}
   449  	if !reflect.DeepEqual(dns, b.DNS) {
   450  		if dns == nil {
   451  			zone, err := b.Client.Zone.Read(ctx, mgw.ZoneID)
   452  			if err != nil {
   453  				return nil, err
   454  			}
   455  			b.DNS = &sacloud.MobileGatewayDNSSetting{
   456  				DNS1: zone.Region.NameServers[0],
   457  				DNS2: zone.Region.NameServers[1],
   458  			}
   459  		}
   460  		if err := b.Client.MobileGateway.SetDNS(ctx, zone, id, b.DNS); err != nil {
   461  			return nil, err
   462  		}
   463  	}
   464  
   465  	// static route(
   466  	if len(b.StaticRoutes) > 0 {
   467  		_, err := b.Client.MobileGateway.UpdateSettings(ctx, zone, id, &sacloud.MobileGatewayUpdateSettingsRequest{
   468  			InterfaceSettings:               b.getInterfaceSettings(),
   469  			StaticRoutes:                    b.StaticRoutes,
   470  			InternetConnectionEnabled:       types.StringFlag(b.InternetConnectionEnabled),
   471  			InterDeviceCommunicationEnabled: types.StringFlag(b.InterDeviceCommunicationEnabled),
   472  			SettingsHash:                    mgw.SettingsHash,
   473  		})
   474  		if err != nil {
   475  			return nil, err
   476  		}
   477  	}
   478  
   479  	// SIMs and SIMRoutes
   480  	currentSIMs, err := b.currentConnectedSIMs(ctx, zone, id)
   481  	if err != nil {
   482  		return nil, err
   483  	}
   484  	currentSIMRoutes, err := b.currentSIMRoutes(ctx, zone, id)
   485  	if err != nil {
   486  		return nil, err
   487  	}
   488  
   489  	if !reflect.DeepEqual(currentSIMs, b.SIMs) || !reflect.DeepEqual(currentSIMRoutes, b.SIMRoutes) {
   490  		if len(currentSIMRoutes) > 0 {
   491  			// SIMルートクリア
   492  			if err := b.Client.MobileGateway.SetSIMRoutes(ctx, zone, id, []*sacloud.MobileGatewaySIMRouteParam{}); err != nil {
   493  				return nil, err
   494  			}
   495  		}
   496  		// SIM変更
   497  		added, updated, deleted := b.changedSIMs(currentSIMs, b.SIMs)
   498  		for _, sim := range deleted {
   499  			if err := b.Client.SIM.ClearIP(ctx, sim.SIMID); err != nil {
   500  				return nil, err
   501  			}
   502  			if err := b.Client.MobileGateway.DeleteSIM(ctx, zone, id, sim.SIMID); err != nil {
   503  				return nil, err
   504  			}
   505  		}
   506  		for _, sim := range updated {
   507  			if err := b.Client.SIM.ClearIP(ctx, sim.SIMID); err != nil {
   508  				return nil, err
   509  			}
   510  			if err := b.Client.SIM.AssignIP(ctx, sim.SIMID, &sacloud.SIMAssignIPRequest{IP: sim.IPAddress}); err != nil {
   511  				return nil, err
   512  			}
   513  		}
   514  		for _, sim := range added {
   515  			if err := b.Client.MobileGateway.AddSIM(ctx, zone, id, &sacloud.MobileGatewayAddSIMRequest{SIMID: sim.SIMID.String()}); err != nil {
   516  				return nil, err
   517  			}
   518  			if err := b.Client.SIM.AssignIP(ctx, sim.SIMID, &sacloud.SIMAssignIPRequest{IP: sim.IPAddress}); err != nil {
   519  				return nil, err
   520  			}
   521  		}
   522  		if len(b.SIMRoutes) > 0 {
   523  			if err := b.Client.MobileGateway.SetSIMRoutes(ctx, zone, id, b.getSIMRouteSettings()); err != nil {
   524  				return nil, err
   525  			}
   526  		}
   527  	}
   528  
   529  	if err := b.Client.MobileGateway.Config(ctx, zone, id); err != nil {
   530  		return nil, err
   531  	}
   532  
   533  	if isNeedRestart {
   534  		if err := power.BootMobileGateway(ctx, b.Client.MobileGateway, zone, id); err != nil {
   535  			return nil, err
   536  		}
   537  	}
   538  
   539  	// refresh
   540  	mgw, err = b.Client.MobileGateway.Read(ctx, zone, id)
   541  	if err != nil {
   542  		return nil, err
   543  	}
   544  	return mgw, err
   545  }
   546  
   547  func (b *Builder) getInterfaceSettings() []*sacloud.MobileGatewayInterfaceSetting {
   548  	if b.PrivateInterface == nil {
   549  		return nil
   550  	}
   551  	return []*sacloud.MobileGatewayInterfaceSetting{
   552  		{
   553  			Index:          1,
   554  			NetworkMaskLen: b.PrivateInterface.NetworkMaskLen,
   555  			IPAddress:      []string{b.PrivateInterface.IPAddress},
   556  		},
   557  	}
   558  }
   559  
   560  func (b *Builder) getSIMRouteSettings() []*sacloud.MobileGatewaySIMRouteParam {
   561  	var results []*sacloud.MobileGatewaySIMRouteParam
   562  	for _, route := range b.SIMRoutes {
   563  		results = append(results, &sacloud.MobileGatewaySIMRouteParam{
   564  			ResourceID: route.SIMID.String(),
   565  			Prefix:     route.Prefix,
   566  		})
   567  	}
   568  	return results
   569  }
   570  
   571  func (b *Builder) collectUpdateInfo(mgw *sacloud.MobileGateway) (isNeedShutdown bool, err error) {
   572  	// スイッチの変更/削除は再起動が必要
   573  	isNeedShutdown = b.isPrivateInterfaceChanged(mgw)
   574  	return
   575  }
   576  
   577  func (b *Builder) isPrivateInterfaceChanged(mgw *sacloud.MobileGateway) bool {
   578  	current := b.currentPrivateInterfaceState(mgw)
   579  	return !reflect.DeepEqual(current, b.PrivateInterface)
   580  }
   581  
   582  func (b *Builder) currentPrivateInterfaceState(mgw *sacloud.MobileGateway) *PrivateInterfaceSetting {
   583  	if len(mgw.Interfaces) > 1 {
   584  		switchID := mgw.Interfaces[1].SwitchID
   585  		var setting *sacloud.MobileGatewayInterfaceSetting
   586  		for _, s := range mgw.InterfaceSettings {
   587  			if s.Index == 1 {
   588  				setting = s
   589  			}
   590  		}
   591  		if setting != nil {
   592  			var ip string
   593  			if len(setting.IPAddress) > 0 {
   594  				ip = setting.IPAddress[0]
   595  			}
   596  			return &PrivateInterfaceSetting{
   597  				SwitchID:       switchID,
   598  				IPAddress:      ip,
   599  				NetworkMaskLen: setting.NetworkMaskLen,
   600  			}
   601  		}
   602  	}
   603  	return nil
   604  }
   605  
   606  func (b *Builder) currentConnectedSIMs(ctx context.Context, zone string, id types.ID) ([]*SIMSetting, error) {
   607  	var results []*SIMSetting
   608  
   609  	sims, err := b.Client.MobileGateway.ListSIM(ctx, zone, id)
   610  	if err != nil && !sacloud.IsNotFoundError(err) {
   611  		return results, err
   612  	}
   613  	for _, sim := range sims {
   614  		results = append(results, &SIMSetting{
   615  			SIMID:     types.StringID(sim.ResourceID),
   616  			IPAddress: sim.IP,
   617  		})
   618  	}
   619  	return results, nil
   620  }
   621  
   622  func (b *Builder) currentSIMRoutes(ctx context.Context, zone string, id types.ID) ([]*sacloud.MobileGatewaySIMRoute, error) {
   623  	return b.Client.MobileGateway.GetSIMRoutes(ctx, zone, id)
   624  }
   625  
   626  func (b *Builder) changedSIMs(current []*SIMSetting, desired []*SIMSetting) (added, updated, deleted []*SIMSetting) {
   627  	for _, c := range current {
   628  		isExists := false
   629  		for _, d := range desired {
   630  			if c.SIMID == d.SIMID {
   631  				isExists = true
   632  				if c.IPAddress != d.IPAddress {
   633  					updated = append(updated, d)
   634  				}
   635  			}
   636  		}
   637  		if !isExists {
   638  			deleted = append(deleted, c)
   639  		}
   640  	}
   641  	for _, d := range desired {
   642  		isExists := false
   643  		for _, c := range current {
   644  			if c.SIMID == d.SIMID {
   645  				isExists = true
   646  				continue
   647  			}
   648  		}
   649  		if !isExists {
   650  			added = append(added, d)
   651  		}
   652  	}
   653  	return
   654  }