ops_server.go raw

   1  // Copyright 2022-2025 The sacloud/iaas-api-go 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 fake
  16  
  17  import (
  18  	"context"
  19  	"fmt"
  20  	"time"
  21  
  22  	"github.com/sacloud/iaas-api-go"
  23  	"github.com/sacloud/iaas-api-go/types"
  24  )
  25  
  26  // Find is fake implementation
  27  func (o *ServerOp) Find(ctx context.Context, zone string, conditions *iaas.FindCondition) (*iaas.ServerFindResult, error) {
  28  	results, _ := find(o.key, zone, conditions)
  29  	var values []*iaas.Server
  30  	for _, res := range results {
  31  		dest := &iaas.Server{}
  32  		copySameNameField(res, dest)
  33  		values = append(values, dest)
  34  	}
  35  	return &iaas.ServerFindResult{
  36  		Total:   len(results),
  37  		Count:   len(results),
  38  		From:    0,
  39  		Servers: values,
  40  	}, nil
  41  }
  42  
  43  // Create is fake implementation
  44  func (o *ServerOp) Create(ctx context.Context, zone string, param *iaas.ServerCreateRequest) (*iaas.Server, error) {
  45  	result := &iaas.Server{}
  46  	copySameNameField(param, result)
  47  	fill(result, fillID, fillCreatedAt)
  48  
  49  	result.Availability = types.Availabilities.Migrating
  50  	if param.Generation == types.PlanGenerations.Default {
  51  		switch zone {
  52  		case "is1a":
  53  			result.Generation = types.PlanGenerations.G200
  54  		default:
  55  			result.Generation = types.PlanGenerations.G100
  56  		}
  57  	}
  58  	// TODO プランAPIを実装したら修正する
  59  	result.ServerPlanID = types.StringID(fmt.Sprintf("%03d%03d%03d", result.Generation, result.GetMemoryGB(), result.CPU))
  60  	result.ServerPlanName = fmt.Sprintf("世代:%03d メモリ:%03d CPU:%03d", result.Generation, result.GetMemoryGB(), result.CPU)
  61  
  62  	// NIC操作のためにあらかじめ登録しておく
  63  	putServer(zone, result)
  64  
  65  	for _, cs := range param.ConnectedSwitches {
  66  		ifOp := NewInterfaceOp()
  67  		swOp := NewSwitchOp()
  68  
  69  		ifCreateParam := &iaas.InterfaceCreateRequest{}
  70  		if cs != nil {
  71  			if cs.Scope != types.Scopes.Shared {
  72  				_, err := swOp.Read(ctx, zone, cs.ID)
  73  				if err != nil {
  74  					return nil, newErrorConflict(o.key, types.ID(0), err.Error())
  75  				}
  76  			}
  77  			ifCreateParam.ServerID = result.ID
  78  		}
  79  
  80  		iface, err := ifOp.Create(ctx, zone, ifCreateParam)
  81  		if err != nil {
  82  			return nil, newErrorConflict(o.key, types.ID(0), err.Error())
  83  		}
  84  
  85  		if cs != nil {
  86  			if cs.Scope == types.Scopes.Shared {
  87  				if err := ifOp.ConnectToSharedSegment(ctx, zone, iface.ID); err != nil {
  88  					return nil, newErrorConflict(o.key, types.ID(0), err.Error())
  89  				}
  90  			} else {
  91  				if err := ifOp.ConnectToSwitch(ctx, zone, iface.ID, cs.ID); err != nil {
  92  					return nil, newErrorConflict(o.key, types.ID(0), err.Error())
  93  				}
  94  			}
  95  		}
  96  
  97  		iface, err = ifOp.Read(ctx, zone, iface.ID)
  98  		if err != nil {
  99  			return nil, newErrorConflict(o.key, types.ID(0), err.Error())
 100  		}
 101  		ifaceView := &iaas.InterfaceView{}
 102  		copySameNameField(iface, ifaceView)
 103  
 104  		// note: UserIPAddressとIPAddressはディスクの修正にて設定されるためここでは空となる。
 105  		if cs != nil {
 106  			if cs.Scope == types.Scopes.Shared {
 107  				ifaceView.SwitchScope = sharedSegmentSwitch.Scope
 108  				ifaceView.SwitchID = sharedSegmentSwitch.ID
 109  				ifaceView.SwitchName = sharedSegmentSwitch.Name
 110  
 111  				if len(sharedSegmentSwitch.Subnets) > 0 {
 112  					ifaceView.UserSubnetDefaultRoute = sharedSegmentSwitch.Subnets[0].DefaultRoute
 113  					ifaceView.UserSubnetNetworkMaskLen = sharedSegmentSwitch.Subnets[0].NetworkMaskLen
 114  					ifaceView.SubnetDefaultRoute = sharedSegmentSwitch.Subnets[0].DefaultRoute
 115  					ifaceView.SubnetNetworkAddress = sharedSegmentSwitch.Subnets[0].NetworkAddress
 116  				}
 117  			} else {
 118  				ifaceView.SwitchScope = types.Scopes.User
 119  				ifaceView.SwitchID = cs.ID
 120  
 121  				sw, err := swOp.Read(ctx, zone, cs.ID)
 122  				if err != nil {
 123  					return nil, err
 124  				}
 125  				if len(sw.Subnets) > 0 {
 126  					ifaceView.UserSubnetDefaultRoute = sw.Subnets[0].DefaultRoute
 127  					ifaceView.UserSubnetNetworkMaskLen = sw.Subnets[0].NetworkMaskLen
 128  					ifaceView.SubnetDefaultRoute = sw.Subnets[0].DefaultRoute
 129  					ifaceView.SubnetNetworkAddress = sw.Subnets[0].NetworkAddress
 130  				}
 131  			}
 132  		}
 133  
 134  		result.Interfaces = append(result.Interfaces, ifaceView)
 135  	}
 136  	zoneOp := NewZoneOp()
 137  	zones, _ := zoneOp.Find(ctx, nil)
 138  	for _, z := range zones.Zones {
 139  		if zone == z.Name {
 140  			zoneInfo := &iaas.ZoneInfo{}
 141  			copySameNameField(z, zoneInfo)
 142  			result.Zone = zoneInfo
 143  		}
 144  	}
 145  
 146  	result.Availability = types.Availabilities.Available
 147  	putServer(zone, result)
 148  	return result, nil
 149  }
 150  
 151  // Read is fake implementation
 152  func (o *ServerOp) Read(ctx context.Context, zone string, id types.ID) (*iaas.Server, error) {
 153  	value := getServerByID(zone, id)
 154  	if value == nil {
 155  		return nil, newErrorNotFound(o.key, id)
 156  	}
 157  
 158  	dest := &iaas.Server{}
 159  	copySameNameField(value, dest)
 160  	return dest, nil
 161  }
 162  
 163  // Update is fake implementation
 164  func (o *ServerOp) Update(ctx context.Context, zone string, id types.ID, param *iaas.ServerUpdateRequest) (*iaas.Server, error) {
 165  	value, err := o.Read(ctx, zone, id)
 166  	if err != nil {
 167  		return nil, err
 168  	}
 169  
 170  	copySameNameField(param, value)
 171  	fill(value, fillModifiedAt)
 172  
 173  	putServer(zone, value)
 174  	return value, nil
 175  }
 176  
 177  // Delete is fake implementation
 178  func (o *ServerOp) Delete(ctx context.Context, zone string, id types.ID) error {
 179  	value, err := o.Read(ctx, zone, id)
 180  	if err != nil {
 181  		return err
 182  	}
 183  
 184  	if value.InstanceStatus.IsUp() {
 185  		return newErrorConflict(o.key, id, fmt.Sprintf("Server[%s] is still running", id))
 186  	}
 187  
 188  	ifOp := NewInterfaceOp()
 189  	for _, iface := range value.Interfaces {
 190  		if err := ifOp.Delete(ctx, zone, iface.ID); err != nil {
 191  			return err
 192  		}
 193  	}
 194  
 195  	diskOp := NewDiskOp()
 196  	for _, disk := range value.Disks {
 197  		if err := diskOp.DisconnectFromServer(ctx, zone, disk.ID); err != nil {
 198  			return err
 199  		}
 200  	}
 201  
 202  	ds().Delete(o.key, zone, id)
 203  	return nil
 204  }
 205  
 206  // DeleteWithDisks is fake implementation
 207  func (o *ServerOp) DeleteWithDisks(ctx context.Context, zone string, id types.ID, disks *iaas.ServerDeleteWithDisksRequest) error {
 208  	if err := o.Delete(ctx, zone, id); err != nil {
 209  		return err
 210  	}
 211  	diskOp := NewDiskOp()
 212  	for _, diskID := range disks.IDs {
 213  		if err := diskOp.Delete(ctx, zone, diskID); err != nil {
 214  			return err
 215  		}
 216  	}
 217  	return nil
 218  }
 219  
 220  // ChangePlan is fake implementation
 221  func (o *ServerOp) ChangePlan(ctx context.Context, zone string, id types.ID, plan *iaas.ServerChangePlanRequest) (*iaas.Server, error) {
 222  	value, err := o.Read(ctx, zone, id)
 223  	if err != nil {
 224  		return nil, err
 225  	}
 226  
 227  	if value.InstanceStatus.IsUp() {
 228  		return nil, newErrorConflict(o.key, id, fmt.Sprintf("Server[%d] is running", value.ID))
 229  	}
 230  
 231  	value.MemoryMB = plan.MemoryMB
 232  	value.CPU = plan.CPU
 233  	value.CPUModel = plan.CPUModel
 234  	if value.CPUModel == "" {
 235  		value.CPUModel = "uncategorized"
 236  	}
 237  	value.Commitment = plan.Commitment
 238  	value.Generation = plan.Generation
 239  	value.GPU = plan.GPU
 240  	value.GPUModel = plan.GPUModel
 241  	value.ServerPlanID = types.StringID(fmt.Sprintf("%03d%03d%03d", value.Generation, value.GetMemoryGB(), value.CPU))
 242  	value.ServerPlanName = fmt.Sprintf("世代:%03d メモリ:%03d CPU:%03d", value.Generation, value.GetMemoryGB(), value.CPU)
 243  
 244  	// ID変更
 245  	ds().Delete(o.key, zone, value.ID)
 246  	newServer := &iaas.Server{}
 247  	copySameNameField(value, newServer)
 248  	newServer.ID = pool().generateID()
 249  	putServer(zone, newServer)
 250  
 251  	// DiskのServerIDも変更
 252  	searched, _ := NewDiskOp().Find(ctx, zone, nil)
 253  	for _, disk := range searched.Disks {
 254  		if disk.ServerID == value.ID {
 255  			disk.ServerID = newServer.ID
 256  			putDisk(zone, disk)
 257  		}
 258  	}
 259  	for _, nic := range newServer.Interfaces {
 260  		iface, err := NewInterfaceOp().Read(ctx, zone, nic.ID)
 261  		if err == nil {
 262  			iface.ServerID = newServer.ID
 263  			putInterface(zone, iface)
 264  		}
 265  	}
 266  
 267  	return newServer, nil
 268  }
 269  
 270  // InsertCDROM is fake implementation
 271  func (o *ServerOp) InsertCDROM(ctx context.Context, zone string, id types.ID, insertParam *iaas.InsertCDROMRequest) error {
 272  	value, err := o.Read(ctx, zone, id)
 273  	if err != nil {
 274  		return err
 275  	}
 276  
 277  	cdromOp := NewCDROMOp()
 278  	if _, err = cdromOp.Read(ctx, zone, insertParam.ID); err != nil {
 279  		return newErrorBadRequest(o.key, id, fmt.Sprintf("CDROM[%d] is not exists", insertParam.ID))
 280  	}
 281  
 282  	value.CDROMID = insertParam.ID
 283  	putServer(zone, value)
 284  	return nil
 285  }
 286  
 287  // EjectCDROM is fake implementation
 288  func (o *ServerOp) EjectCDROM(ctx context.Context, zone string, id types.ID, insertParam *iaas.EjectCDROMRequest) error {
 289  	value, err := o.Read(ctx, zone, id)
 290  	if err != nil {
 291  		return err
 292  	}
 293  
 294  	cdromOp := NewCDROMOp()
 295  	if _, err = cdromOp.Read(ctx, zone, insertParam.ID); err != nil {
 296  		return newErrorBadRequest(o.key, id, fmt.Sprintf("CDROM[%d] is not exists", insertParam.ID))
 297  	}
 298  
 299  	value.CDROMID = types.ID(0)
 300  	putServer(zone, value)
 301  	return nil
 302  }
 303  
 304  // Boot is fake implementation
 305  func (o *ServerOp) Boot(ctx context.Context, zone string, id types.ID) error {
 306  	value, err := o.Read(ctx, zone, id)
 307  	if err != nil {
 308  		return err
 309  	}
 310  	if value.InstanceStatus.IsUp() {
 311  		return newErrorConflict(o.key, id, "Boot is failed")
 312  	}
 313  
 314  	startPowerOn(o.key, zone, func() (interface{}, error) {
 315  		return o.Read(context.Background(), zone, id)
 316  	})
 317  
 318  	return err
 319  }
 320  
 321  // BootWithVariables is fake implementation
 322  func (o *ServerOp) BootWithVariables(ctx context.Context, zone string, id types.ID, param *iaas.ServerBootVariables) error {
 323  	return o.Boot(ctx, zone, id) // paramは非対応
 324  }
 325  
 326  // Shutdown is fake implementation
 327  func (o *ServerOp) Shutdown(ctx context.Context, zone string, id types.ID, shutdownOption *iaas.ShutdownOption) error {
 328  	value, err := o.Read(ctx, zone, id)
 329  	if err != nil {
 330  		return err
 331  	}
 332  	if !value.InstanceStatus.IsUp() {
 333  		return newErrorConflict(o.key, id, "Shutdown is failed")
 334  	}
 335  
 336  	startPowerOff(o.key, zone, func() (interface{}, error) {
 337  		return o.Read(context.Background(), zone, id)
 338  	})
 339  
 340  	return err
 341  }
 342  
 343  // Reset is fake implementation
 344  func (o *ServerOp) Reset(ctx context.Context, zone string, id types.ID) error {
 345  	value, err := o.Read(ctx, zone, id)
 346  	if err != nil {
 347  		return err
 348  	}
 349  	if !value.InstanceStatus.IsUp() {
 350  		return newErrorConflict(o.key, id, "Reset is failed")
 351  	}
 352  
 353  	startPowerOn(o.key, zone, func() (interface{}, error) {
 354  		return o.Read(context.Background(), zone, id)
 355  	})
 356  
 357  	return nil
 358  }
 359  
 360  // SendKey is fake implementation
 361  func (o *ServerOp) SendKey(ctx context.Context, zone string, id types.ID, keyboardParam *iaas.SendKeyRequest) error {
 362  	_, err := o.Read(ctx, zone, id)
 363  	if err != nil {
 364  		return err
 365  	}
 366  	return nil
 367  }
 368  
 369  // SendNMI is fake implementation
 370  func (o *ServerOp) SendNMI(ctx context.Context, zone string, id types.ID) error {
 371  	_, err := o.Read(ctx, zone, id)
 372  	if err != nil {
 373  		return err
 374  	}
 375  	return nil
 376  }
 377  
 378  // GetVNCProxy is fake implementation
 379  func (o *ServerOp) GetVNCProxy(ctx context.Context, zone string, id types.ID) (*iaas.VNCProxyInfo, error) {
 380  	_, err := o.Read(ctx, zone, id)
 381  	if err != nil {
 382  		return nil, err
 383  	}
 384  	vncFileTemplate := `[connection]
 385  host=sac-%s-vnc.cloud.sakura.ad.jp
 386  port=51234
 387  password=aaabababababa`
 388  
 389  	return &iaas.VNCProxyInfo{
 390  		Status:       "OK",
 391  		Host:         "localhost",
 392  		IOServerHost: fmt.Sprintf("sac-%s-vnc.cloud.sakura.ad.jp", zone),
 393  		Port:         51234,
 394  		Password:     "dummy",
 395  		VNCFile:      fmt.Sprintf(vncFileTemplate, zone),
 396  	}, nil
 397  }
 398  
 399  // Monitor is fake implementation
 400  func (o *ServerOp) Monitor(ctx context.Context, zone string, id types.ID, condition *iaas.MonitorCondition) (*iaas.CPUTimeActivity, error) {
 401  	value, err := o.Read(ctx, zone, id)
 402  	if err != nil {
 403  		return nil, err
 404  	}
 405  
 406  	now := time.Now().Truncate(time.Second)
 407  	m := now.Minute() % 5
 408  	if m != 0 {
 409  		now.Add(time.Duration(m) * time.Minute)
 410  	}
 411  
 412  	res := &iaas.CPUTimeActivity{}
 413  	for i := 0; i < 5; i++ {
 414  		res.Values = append(res.Values, &iaas.MonitorCPUTimeValue{
 415  			Time:    now.Add(time.Duration(i*-5) * time.Minute),
 416  			CPUTime: float64(random(value.CPU * 1000)),
 417  		})
 418  	}
 419  
 420  	return res, nil
 421  }
 422  
 423  // MonitorCPU is fake implementation
 424  func (o *ServerOp) MonitorCPU(ctx context.Context, zone string, id types.ID, condition *iaas.MonitorCondition) (*iaas.CPUTimeActivity, error) {
 425  	return o.Monitor(ctx, zone, id, condition)
 426  }
 427