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