ops_disk.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 *DiskOp) Find(ctx context.Context, zone string, conditions *iaas.FindCondition) (*iaas.DiskFindResult, error) {
  28  	results, _ := find(o.key, zone, conditions)
  29  	var values []*iaas.Disk
  30  	for _, res := range results {
  31  		dest := &iaas.Disk{}
  32  		copySameNameField(res, dest)
  33  		values = append(values, dest)
  34  	}
  35  	return &iaas.DiskFindResult{
  36  		Total: len(results),
  37  		Count: len(results),
  38  		From:  0,
  39  		Disks: values,
  40  	}, nil
  41  }
  42  
  43  // Create is fake implementation
  44  func (o *DiskOp) Create(ctx context.Context, zone string, param *iaas.DiskCreateRequest, distantFrom []types.ID, kmsKeyID types.ID) (*iaas.Disk, error) {
  45  	result := &iaas.Disk{}
  46  	copySameNameField(param, result)
  47  	fill(result, fillID, fillCreatedAt, fillDiskPlan)
  48  	result.Availability = types.Availabilities.Migrating
  49  	result.KMSKeyID = kmsKeyID
  50  
  51  	result.Storage = &iaas.Storage{
  52  		ID:   types.ID(123456789012),
  53  		Name: "dummy",
  54  	}
  55  
  56  	if result.Connection == types.EDiskConnection("") {
  57  		result.Connection = types.DiskConnections.VirtIO
  58  	}
  59  	if !param.SourceArchiveID.IsEmpty() {
  60  		archiveOp := NewArchiveOp()
  61  		source, err := archiveOp.Read(ctx, zone, param.SourceArchiveID)
  62  		if err != nil {
  63  			return nil, newErrorBadRequest(o.key, types.ID(0), "SourceArchive is not found")
  64  		}
  65  		result.SourceArchiveAvailability = source.Availability
  66  	}
  67  	if !param.SourceDiskID.IsEmpty() {
  68  		source, err := o.Read(ctx, zone, param.SourceDiskID)
  69  		if err != nil {
  70  			return nil, newErrorBadRequest(o.key, types.ID(0), "SourceDisk is not found")
  71  		}
  72  		result.SourceDiskAvailability = source.Availability
  73  	}
  74  	if !param.ServerID.IsEmpty() {
  75  		server, err := NewServerOp().Read(ctx, zone, param.ServerID)
  76  		if err != nil {
  77  			return nil, newErrorConflict(o.key, types.ID(0), "Server is not found")
  78  		}
  79  		server.Disks = append(server.Disks, &iaas.ServerConnectedDisk{
  80  			ID:              result.ID,
  81  			Name:            result.Name,
  82  			Availability:    result.Availability,
  83  			Connection:      result.Connection,
  84  			ConnectionOrder: result.ConnectionOrder,
  85  			ReinstallCount:  result.ReinstallCount,
  86  			SizeMB:          result.SizeMB,
  87  			DiskPlanID:      result.DiskPlanID,
  88  			Storage:         result.Storage,
  89  		})
  90  		putServer(zone, server)
  91  	}
  92  
  93  	putDisk(zone, result)
  94  
  95  	id := result.ID
  96  	startDiskCopy(o.key, zone, func() (interface{}, error) {
  97  		disk, err := o.Read(context.Background(), zone, id)
  98  		if err != nil {
  99  			return nil, err
 100  		}
 101  		return disk, nil
 102  	})
 103  
 104  	return result, nil
 105  }
 106  
 107  // Config is fake implementation
 108  func (o *DiskOp) Config(ctx context.Context, zone string, id types.ID, edit *iaas.DiskEditRequest) error {
 109  	disk, err := o.Read(ctx, zone, id)
 110  	if err != nil {
 111  		return err
 112  	}
 113  	if disk.ServerID.IsEmpty() {
 114  		return nil
 115  	}
 116  
 117  	serverOp := NewServerOp()
 118  	server, err := serverOp.Read(ctx, zone, disk.ServerID)
 119  	if err != nil {
 120  		return err
 121  	}
 122  
 123  	if edit.HostName != "" {
 124  		server.HostName = edit.HostName
 125  		putServer(zone, server)
 126  	}
 127  
 128  	if len(server.Interfaces) > 0 {
 129  		nic := server.Interfaces[0]
 130  		if nic.SwitchScope == types.Scopes.Shared {
 131  			nic.IPAddress = pool().nextSharedIP().String()
 132  		} else {
 133  			nic.UserIPAddress = edit.UserIPAddress
 134  		}
 135  
 136  		swOp := NewSwitchOp()
 137  		sw, err := swOp.Read(ctx, zone, nic.SwitchID)
 138  		if err != nil {
 139  			return err
 140  		}
 141  
 142  		if len(sw.Subnets) == 0 {
 143  			nic.UserSubnetDefaultRoute = edit.UserSubnet.DefaultRoute
 144  			nic.UserSubnetNetworkMaskLen = edit.UserSubnet.NetworkMaskLen
 145  		} else {
 146  			nic.UserSubnetDefaultRoute = sw.Subnets[0].DefaultRoute
 147  			nic.UserSubnetNetworkMaskLen = sw.Subnets[0].NetworkMaskLen
 148  			nic.SubnetDefaultRoute = sw.Subnets[0].DefaultRoute
 149  			nic.SubnetNetworkAddress = sw.Subnets[0].NetworkAddress
 150  		}
 151  
 152  		putServer(zone, server)
 153  	}
 154  
 155  	return nil
 156  }
 157  
 158  // CreateWithConfig is fake implementation
 159  func (o *DiskOp) CreateWithConfig(ctx context.Context, zone string, createParam *iaas.DiskCreateRequest, editParam *iaas.DiskEditRequest, bootAtAvailable bool, distantFrom []types.ID, kmsKeyID types.ID) (*iaas.Disk, error) {
 160  	// check
 161  	if !createParam.ServerID.IsEmpty() {
 162  		serverOp := NewServerOp()
 163  		_, err := serverOp.Read(ctx, zone, createParam.ServerID)
 164  		if err != nil {
 165  			return nil, newErrorBadRequest(o.key, types.ID(0), fmt.Sprintf("Server %s is not found", createParam.ServerID))
 166  		}
 167  	}
 168  
 169  	result, err := o.Create(ctx, zone, createParam, distantFrom, kmsKeyID)
 170  	if err != nil {
 171  		return nil, err
 172  	}
 173  
 174  	if err := o.Config(ctx, zone, result.ID, editParam); err != nil {
 175  		return nil, err
 176  	}
 177  
 178  	if !createParam.ServerID.IsEmpty() && bootAtAvailable {
 179  		waiter := iaas.WaiterForReady(func() (interface{}, error) {
 180  			disk, err := o.Read(ctx, zone, result.ID)
 181  			if err != nil {
 182  				return nil, err
 183  			}
 184  			return disk, nil
 185  		})
 186  		res, err := waiter.WaitForState(ctx)
 187  		if err != nil {
 188  			return nil, err
 189  		}
 190  		result = res.(*iaas.Disk)
 191  
 192  		// boot server
 193  		serverOp := NewServerOp()
 194  		if err := serverOp.Boot(ctx, zone, createParam.ServerID); err != nil {
 195  			return nil, err
 196  		}
 197  	}
 198  	return result, nil
 199  }
 200  
 201  // ResizePartition is fake implementation
 202  func (o *DiskOp) ResizePartition(ctx context.Context, zone string, id types.ID, param *iaas.DiskResizePartitionRequest) error {
 203  	_, err := o.Read(ctx, zone, id)
 204  	if err != nil {
 205  		return err
 206  	}
 207  	return nil
 208  }
 209  
 210  // ConnectToServer is fake implementation
 211  func (o *DiskOp) ConnectToServer(ctx context.Context, zone string, id types.ID, serverID types.ID) error {
 212  	value, err := o.Read(ctx, zone, id)
 213  	if err != nil {
 214  		return err
 215  	}
 216  
 217  	serverOp := NewServerOp()
 218  	server, err := serverOp.Read(ctx, zone, serverID)
 219  	if err != nil {
 220  		return newErrorBadRequest(o.key, id, fmt.Sprintf("Server[%d] is not exists", serverID))
 221  	}
 222  
 223  	for _, connected := range server.Disks {
 224  		if connected.ID == value.ID {
 225  			return newErrorBadRequest(o.key, id, fmt.Sprintf("Disk[%d] is already connected to Server[%d]", id, serverID))
 226  		}
 227  	}
 228  
 229  	// TODO とりあえず同時実行制御は考慮しない。更新対象リソースが増えるようであれば実装方法を考える
 230  
 231  	connectedDisk := &iaas.ServerConnectedDisk{}
 232  	copySameNameField(value, connectedDisk)
 233  	server.Disks = append(server.Disks, connectedDisk)
 234  	putServer(zone, server)
 235  	value.ServerID = serverID
 236  	value.ServerName = server.Name
 237  	putDisk(zone, value)
 238  
 239  	return nil
 240  }
 241  
 242  // DisconnectFromServer is fake implementation
 243  func (o *DiskOp) DisconnectFromServer(ctx context.Context, zone string, id types.ID) error {
 244  	value, err := o.Read(ctx, zone, id)
 245  	if err != nil {
 246  		return err
 247  	}
 248  
 249  	if value.ServerID.IsEmpty() {
 250  		return newErrorBadRequest(o.key, id, fmt.Sprintf("Disk[%d] is not connected to Server", id))
 251  	}
 252  
 253  	serverOp := NewServerOp()
 254  	server, err := serverOp.Read(ctx, zone, value.ServerID)
 255  	if err != nil {
 256  		return newErrorBadRequest(o.key, id, fmt.Sprintf("Server[%d] is not exists", value.ServerID))
 257  	}
 258  
 259  	var disks []*iaas.ServerConnectedDisk
 260  	for _, connected := range server.Disks {
 261  		if connected.ID != value.ID {
 262  			connectedDisk := &iaas.ServerConnectedDisk{}
 263  			copySameNameField(value, connectedDisk)
 264  			server.Disks = append(server.Disks, connectedDisk)
 265  			disks = append(disks, connected)
 266  		}
 267  	}
 268  	if len(disks) == len(server.Disks) {
 269  		return newInternalServerError(o.key, id, fmt.Sprintf("Disk[%d] is not found on server's connected disks", id))
 270  	}
 271  
 272  	server.Disks = disks
 273  	putServer(zone, server)
 274  	value.ServerID = types.ID(0)
 275  	value.ServerName = ""
 276  	putDisk(zone, value)
 277  
 278  	return nil
 279  }
 280  
 281  // Read is fake implementation
 282  func (o *DiskOp) Read(ctx context.Context, zone string, id types.ID) (*iaas.Disk, error) {
 283  	value := getDiskByID(zone, id)
 284  	if value == nil {
 285  		return nil, newErrorNotFound(o.key, id)
 286  	}
 287  
 288  	dest := &iaas.Disk{}
 289  	copySameNameField(value, dest)
 290  	return dest, nil
 291  }
 292  
 293  // Update is fake implementation
 294  func (o *DiskOp) Update(ctx context.Context, zone string, id types.ID, param *iaas.DiskUpdateRequest) (*iaas.Disk, error) {
 295  	value, err := o.Read(ctx, zone, id)
 296  	if err != nil {
 297  		return nil, err
 298  	}
 299  	copySameNameField(param, value)
 300  	fill(value, fillModifiedAt)
 301  
 302  	putDisk(zone, value)
 303  	return value, nil
 304  }
 305  
 306  // Delete is fake implementation
 307  func (o *DiskOp) Delete(ctx context.Context, zone string, id types.ID) error {
 308  	_, err := o.Read(ctx, zone, id)
 309  	if err != nil {
 310  		return err
 311  	}
 312  	ds().Delete(o.key, zone, id)
 313  	return nil
 314  }
 315  
 316  // Monitor is fake implementation
 317  func (o *DiskOp) Monitor(ctx context.Context, zone string, id types.ID, condition *iaas.MonitorCondition) (*iaas.DiskActivity, error) {
 318  	_, err := o.Read(ctx, zone, id)
 319  	if err != nil {
 320  		return nil, err
 321  	}
 322  	now := time.Now().Truncate(time.Second)
 323  	m := now.Minute() % 5
 324  	if m != 0 {
 325  		now.Add(time.Duration(m) * time.Minute)
 326  	}
 327  
 328  	res := &iaas.DiskActivity{}
 329  	for i := 0; i < 5; i++ {
 330  		res.Values = append(res.Values, &iaas.MonitorDiskValue{
 331  			Time:  now.Add(time.Duration(i*-5) * time.Minute),
 332  			Read:  float64(random(1000)),
 333  			Write: float64(random(1000)),
 334  		})
 335  	}
 336  
 337  	return res, nil
 338  }
 339  
 340  // MonitorDisk is fake implementation
 341  func (o *DiskOp) MonitorDisk(ctx context.Context, zone string, id types.ID, condition *iaas.MonitorCondition) (*iaas.DiskActivity, error) {
 342  	return o.Monitor(ctx, zone, id, condition)
 343  }
 344