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