1package gsclient 2 3import ( 4 "context" 5 "errors" 6 "net/http" 7 "path" 8) 9 10// ServerOperator provides an interface for operations on servers. 11type ServerOperator interface { 12 GetServer(ctx context.Context, id string) (Server, error) 13 GetServerList(ctx context.Context) ([]Server, error) 14 GetServersByLocation(ctx context.Context, id string) ([]Server, error) 15 CreateServer(ctx context.Context, body ServerCreateRequest) (ServerCreateResponse, error) 16 UpdateServer(ctx context.Context, id string, body ServerUpdateRequest) error 17 DeleteServer(ctx context.Context, id string) error 18 StartServer(ctx context.Context, id string) error 19 StopServer(ctx context.Context, id string) error 20 ShutdownServer(ctx context.Context, id string) error 21 IsServerOn(ctx context.Context, id string) (bool, error) 22 GetServerMetricList(ctx context.Context, id string) ([]ServerMetric, error) 23 GetServerEventList(ctx context.Context, id string) ([]Event, error) 24 GetDeletedServers(ctx context.Context) ([]Server, error) 25} 26 27// ServerList holds a list of servers. 28type ServerList struct { 29 // Array of servers. 30 List map[string]ServerProperties `json:"servers"` 31} 32 33// DeletedServerList holds a list of deleted servers. 34type DeletedServerList struct { 35 // Array of deleted servers. 36 List map[string]ServerProperties `json:"deleted_servers"` 37} 38 39// Server represents a single server. 40type Server struct { 41 // Properties of a server. 42 Properties ServerProperties `json:"server"` 43} 44 45// ServerProperties holds properties of a server. 46type ServerProperties struct { 47 // The UUID of an object is always unique, and refers to a specific object. 48 ObjectUUID string `json:"object_uuid"` 49 50 // The human-readable name of the object. It supports the full UTF-8 character set, with a maximum of 64 characters. 51 Name string `json:"name"` 52 53 // Indicates the amount of memory in GB. 54 Memory int `json:"memory"` 55 56 // Number of server cores. 57 Cores int `json:"cores"` 58 59 // Specifies the hardware settings for the virtual machine. 60 HardwareProfile string `json:"hardware_profile"` 61 62 // Status indicates the status of the object. it could be in-provisioning or active 63 Status string `json:"status"` 64 65 // Helps to identify which data center an object belongs to. 66 LocationUUID string `json:"location_uuid"` 67 68 // The power status of the server. 69 Power bool `json:"power"` 70 71 // **DEPRECATED** The price for the current period since the last bill. 72 CurrentPrice float64 `json:"current_price"` 73 74 // Which Availability-Zone the Server is placed. 75 AvailabilityZone string `json:"availability_zone"` 76 77 // If the server should be auto-started in case of a failure (default=true). 78 AutoRecovery bool `json:"auto_recovery"` 79 80 // Legacy-Hardware emulation instead of virtio hardware. 81 // If enabled, hot-plugging cores, memory, storage, network, etc. will not work, 82 // but the server will most likely run every x86 compatible operating system. 83 // This mode comes with a performance penalty, as emulated hardware does not benefit from the virtio driver infrastructure. 84 Legacy bool `json:"legacy"` 85 86 // The token used by the panel to open the websocket VNC connection to the server console. 87 ConsoleToken string `json:"console_token"` 88 89 // Total minutes of memory used. 90 UsageInMinutesMemory int `json:"usage_in_minutes_memory"` 91 92 // Total minutes of cores used. 93 UsageInMinutesCores int `json:"usage_in_minutes_cores"` 94 95 // List of labels. 96 Labels []string `json:"labels"` 97 98 // Information about other objects which are related to this server. Object could be IPs, storages, networks, and ISO images. 99 Relations ServerRelations `json:"relations"` 100 101 // Defines the date and time the object was initially created. 102 CreateTime GSTime `json:"create_time"` 103 104 // Defines the date and time of the last object change. 105 ChangeTime GSTime `json:"change_time"` 106} 107 108// ServerRelations holds a list of server relations. 109// It shows the relations between a server and ISO images/Networks/IP addresses/Storages. 110type ServerRelations struct { 111 // Array of object (ServerIsoImageRelationProperties). 112 IsoImages []ServerIsoImageRelationProperties `json:"isoimages"` 113 114 // Array of object (ServerNetworkRelationProperties). 115 Networks []ServerNetworkRelationProperties `json:"networks"` 116 117 // Array of object (ServerIPRelationProperties). 118 PublicIPs []ServerIPRelationProperties `json:"public_ips"` 119 120 // Array of object (ServerStorageRelationProperties). 121 Storages []ServerStorageRelationProperties `json:"storages"` 122} 123 124// ServerCreateRequest represents a request for creating a server. 125type ServerCreateRequest struct { 126 // The human-readable name of the object. It supports the full UTF-8 character set, with a maximum of 64 characters. 127 Name string `json:"name"` 128 129 // The amount of server memory in GB. 130 Memory int `json:"memory"` 131 132 // The number of server cores. 133 Cores int `json:"cores"` 134 135 // Specifies the hardware settings for the virtual machine. 136 // Allowed values: DefaultServerHardware, NestedServerHardware, LegacyServerHardware, CiscoCSRServerHardware, 137 // SophosUTMServerHardware, F5BigipServerHardware, Q35ServerHardware, Q35NestedServerHardware. 138 HardwareProfile ServerHardwareProfile `json:"hardware_profile,omitempty"` 139 140 // Defines which Availability-Zone the Server is placed. Can be empty. 141 AvailablityZone string `json:"availability_zone,omitempty"` 142 143 // List of labels. Can be empty. 144 Labels []string `json:"labels,omitempty"` 145 146 // Status indicates the status of the object. Can be empty. 147 Status string `json:"status,omitempty"` 148 149 // If the server should be auto-started in case of a failure (default=true when AutoRecovery=nil). 150 AutoRecovery *bool `json:"auto_recovery,omitempty"` 151 152 // The information about other object which are related to this server. the object could be ip, storage, network, and isoimage. 153 // **Caution**: This field is deprecated. 154 Relations *ServerCreateRequestRelations `json:"relations,omitempty"` 155} 156 157// ServerCreateRequestRelations holds a list of a server's relations. 158type ServerCreateRequestRelations struct { 159 // Array of objects (ServerCreateRequestIsoimage). 160 IsoImages []ServerCreateRequestIsoimage `json:"isoimages"` 161 162 // Array of objects (ServerCreateRequestNetwork). 163 Networks []ServerCreateRequestNetwork `json:"networks"` 164 165 // Array of objects (ServerCreateRequestIP). 166 PublicIPs []ServerCreateRequestIP `json:"public_ips"` 167 168 // Array of objects (ServerCreateRequestStorage). 169 Storages []ServerCreateRequestStorage `json:"storages"` 170} 171 172// ServerCreateResponse represents a response for creating a server. 173type ServerCreateResponse struct { 174 // UUID of object being created. Same as ServerUUID. 175 ObjectUUID string `json:"object_uuid"` 176 177 // UUID of the request. 178 RequestUUID string `json:"request_uuid"` 179 180 // UUID of server being created. Same as ObjectUUID. 181 ServerUUID string `json:"server_uuid"` 182 183 // UUIDs of attached networks. 184 NetworkUUIDs []string `json:"network_uuids"` 185 186 // UUIDs of attached storages. 187 StorageUUIDs []string `json:"storage_uuids"` 188 189 // UUIDs of attached IP addresses. 190 IPaddrUUIDs []string `json:"ipaddr_uuids"` 191} 192 193// ServerPowerUpdateRequest reresents a request for updating server's power state. 194type ServerPowerUpdateRequest struct { 195 // Power=true => server is on. 196 // Power=false => server if off. 197 Power bool `json:"power"` 198} 199 200// ServerCreateRequestStorage represents a relation between a server and a storage. 201type ServerCreateRequestStorage struct { 202 // UUID of the storage being attached to the server. 203 StorageUUID string `json:"storage_uuid"` 204 205 // Is the storage a boot device? 206 BootDevice bool `json:"bootdevice,omitempty"` 207} 208 209// ServerCreateRequestNetwork represents a relation between a server and a network. 210type ServerCreateRequestNetwork struct { 211 // UUID of the networks being attached to the server. 212 NetworkUUID string `json:"network_uuid"` 213 214 // Is the network a boot device? 215 BootDevice bool `json:"bootdevice,omitempty"` 216} 217 218// ServerCreateRequestIP represents a relation between a server and an IP address. 219type ServerCreateRequestIP struct { 220 // UUID of the IP address being attached to the server. 221 IPaddrUUID string `json:"ipaddr_uuid"` 222} 223 224// ServerCreateRequestIsoimage represents a relation between a server and an ISO image. 225type ServerCreateRequestIsoimage struct { 226 // UUID of the ISO-image being attached to the server. 227 IsoimageUUID string `json:"isoimage_uuid"` 228} 229 230// ServerUpdateRequest represents a request for updating a server. 231type ServerUpdateRequest struct { 232 // The human-readable name of the object. It supports the full UTF-8 character set, with a maximum of 64 characters. 233 // Leave it if you do not want to update the name. 234 Name string `json:"name,omitempty"` 235 236 // Defines which Availability-Zone the Server is placed. Leave it if you do not want to update the zone. 237 AvailablityZone string `json:"availability_zone,omitempty"` 238 239 // The amount of server memory in GB. Leave it if you do not want to update the memory. 240 Memory int `json:"memory,omitempty"` 241 242 // The number of server cores. Leave it if you do not want to update the number of the cpu cores. 243 Cores int `json:"cores,omitempty"` 244 245 // List of labels. Leave it if you do not want to update the list of labels. 246 Labels *[]string `json:"labels,omitempty"` 247 248 // If the server should be auto-started in case of a failure (default=true). 249 // Leave it if you do not want to update this feature of the server. 250 AutoRecovery *bool `json:"auto_recovery,omitempty"` 251} 252 253// ServerMetricList holds a list of a server's metrics. 254type ServerMetricList struct { 255 // Array of a server's metrics 256 List []ServerMetricProperties `json:"server_metrics"` 257} 258 259// ServerMetric represents a single metric of a server. 260type ServerMetric struct { 261 // Properties of a server metric. 262 Properties ServerMetricProperties `json:"server_metric"` 263} 264 265// ServerMetricProperties holds properties of a server metric. 266type ServerMetricProperties struct { 267 // Defines the begin of the time range. 268 BeginTime GSTime `json:"begin_time"` 269 270 // Defines the end of the time range. 271 EndTime GSTime `json:"end_time"` 272 273 // The UUID of an object is always unique, and refers to a specific object. 274 PaaSServiceUUID string `json:"paas_service_uuid"` 275 276 // Core usage. 277 CoreUsage struct { 278 // Value. 279 Value float64 `json:"value"` 280 281 // Unit of value. 282 Unit string `json:"unit"` 283 } `json:"core_usage"` 284 285 // Storage usage. 286 StorageSize struct { 287 // Value. 288 Value float64 `json:"value"` 289 290 // Unit of value. 291 Unit string `json:"unit"` 292 } `json:"storage_size"` 293} 294 295// ServerHardwareProfile represents the type of server. 296type ServerHardwareProfile string 297 298// All available server's hardware types. 299const ( 300 DefaultServerHardware ServerHardwareProfile = "default" 301 NestedServerHardware ServerHardwareProfile = "nested" 302 LegacyServerHardware ServerHardwareProfile = "legacy" 303 CiscoCSRServerHardware ServerHardwareProfile = "cisco_csr" 304 SophosUTMServerHardware ServerHardwareProfile = "sophos_utm" 305 F5BigipServerHardware ServerHardwareProfile = "f5_bigip" 306 Q35ServerHardware ServerHardwareProfile = "q35" 307 Q35NestedServerHardware ServerHardwareProfile = "q35_nested" 308) 309 310// GetServer gets a specific server based on given list. 311// 312// See: https://gridscale.io/en//api-documentation/index.html#operation/getServer 313func (c *Client) GetServer(ctx context.Context, id string) (Server, error) { 314 if !isValidUUID(id) { 315 return Server{}, errors.New("'id' is invalid") 316 } 317 r := gsRequest{ 318 uri: path.Join(apiServerBase, id), 319 method: http.MethodGet, 320 skipCheckingRequest: true, 321 } 322 var response Server 323 err := r.execute(ctx, *c, &response) 324 return response, err 325} 326 327// GetServerList gets a list of available servers. 328// 329// See: https://gridscale.io/en//api-documentation/index.html#operation/getServers 330func (c *Client) GetServerList(ctx context.Context) ([]Server, error) { 331 r := gsRequest{ 332 uri: apiServerBase, 333 method: http.MethodGet, 334 skipCheckingRequest: true, 335 } 336 var response ServerList 337 var servers []Server 338 err := r.execute(ctx, *c, &response) 339 for _, properties := range response.List { 340 servers = append(servers, Server{ 341 Properties: properties, 342 }) 343 } 344 return servers, err 345} 346 347// CreateServer creates a new server in a project. Normally you want to use 348// `Q35ServerHardware` as hardware profile. 349// 350// See: https://gridscale.io/en//api-documentation/index.html#operation/createServer 351func (c *Client) CreateServer(ctx context.Context, body ServerCreateRequest) (ServerCreateResponse, error) { 352 // check if these slices are nil 353 // make them be empty slice instead of nil 354 // so that JSON structure will be valid 355 if body.Relations != nil && body.Relations.PublicIPs == nil { 356 body.Relations.PublicIPs = make([]ServerCreateRequestIP, 0) 357 } 358 if body.Relations != nil && body.Relations.Networks == nil { 359 body.Relations.Networks = make([]ServerCreateRequestNetwork, 0) 360 } 361 if body.Relations != nil && body.Relations.IsoImages == nil { 362 body.Relations.IsoImages = make([]ServerCreateRequestIsoimage, 0) 363 } 364 if body.Relations != nil && body.Relations.Storages == nil { 365 body.Relations.Storages = make([]ServerCreateRequestStorage, 0) 366 } 367 r := gsRequest{ 368 uri: apiServerBase, 369 method: http.MethodPost, 370 body: body, 371 } 372 var response ServerCreateResponse 373 err := r.execute(ctx, *c, &response) 374 // this fixed the endpoint's bug temporarily when creating server with/without 375 //'relations' field. 376 if response.ServerUUID == "" && response.ObjectUUID != "" { 377 response.ServerUUID = response.ObjectUUID 378 } else if response.ObjectUUID == "" && response.ServerUUID != "" { 379 response.ObjectUUID = response.ServerUUID 380 } 381 return response, err 382} 383 384// DeleteServer removes a specific server. 385// 386// See: https://gridscale.io/en//api-documentation/index.html#operation/deleteServer 387func (c *Client) DeleteServer(ctx context.Context, id string) error { 388 if !isValidUUID(id) { 389 return errors.New("'id' is invalid") 390 } 391 r := gsRequest{ 392 uri: path.Join(apiServerBase, id), 393 method: http.MethodDelete, 394 } 395 return r.execute(ctx, *c, nil) 396} 397 398// UpdateServer updates a specific server. 399// 400// See: https://gridscale.io/en//api-documentation/index.html#operation/updateServer 401func (c *Client) UpdateServer(ctx context.Context, id string, body ServerUpdateRequest) error { 402 if !isValidUUID(id) { 403 return errors.New("'id' is invalid") 404 } 405 r := gsRequest{ 406 uri: path.Join(apiServerBase, id), 407 method: http.MethodPatch, 408 body: body, 409 } 410 return r.execute(ctx, *c, nil) 411} 412 413// GetServerEventList gets a list of a specific server's events. 414// 415// See: https://gridscale.io/en//api-documentation/index.html#operation/getServerEvents 416func (c *Client) GetServerEventList(ctx context.Context, id string) ([]Event, error) { 417 if !isValidUUID(id) { 418 return nil, errors.New("'id' is invalid") 419 } 420 r := gsRequest{ 421 uri: path.Join(apiServerBase, id, "events"), 422 method: http.MethodGet, 423 skipCheckingRequest: true, 424 } 425 var response EventList 426 var serverEvents []Event 427 err := r.execute(ctx, *c, &response) 428 for _, properties := range response.List { 429 serverEvents = append(serverEvents, Event{Properties: properties}) 430 } 431 return serverEvents, err 432} 433 434// GetServerMetricList gets a list of a specific server's metrics. 435// 436// See: https://gridscale.io/en//api-documentation/index.html#operation/getServerMetrics 437func (c *Client) GetServerMetricList(ctx context.Context, id string) ([]ServerMetric, error) { 438 if !isValidUUID(id) { 439 return nil, errors.New("'id' is invalid") 440 } 441 r := gsRequest{ 442 uri: path.Join(apiServerBase, id, "metrics"), 443 method: http.MethodGet, 444 skipCheckingRequest: true, 445 } 446 var response ServerMetricList 447 var serverMetrics []ServerMetric 448 err := r.execute(ctx, *c, &response) 449 for _, properties := range response.List { 450 serverMetrics = append(serverMetrics, ServerMetric{Properties: properties}) 451 } 452 return serverMetrics, err 453} 454 455// IsServerOn returns true if the server's power is on, otherwise returns false. 456func (c *Client) IsServerOn(ctx context.Context, id string) (bool, error) { 457 server, err := c.GetServer(ctx, id) 458 if err != nil { 459 return false, err 460 } 461 return server.Properties.Power, nil 462} 463 464// setServerPowerState turn on/off a specific server. 465// turnOn=true to turn on, turnOn=false to turn off. 466func (c *Client) setServerPowerState(ctx context.Context, id string, powerState bool) error { 467 isOn, err := c.IsServerOn(ctx, id) 468 if err != nil { 469 return err 470 } 471 if isOn == powerState { 472 return nil 473 } 474 r := gsRequest{ 475 uri: path.Join(apiServerBase, id, "power"), 476 method: http.MethodPatch, 477 body: ServerPowerUpdateRequest{ 478 Power: powerState, 479 }, 480 } 481 err = r.execute(ctx, *c, nil) 482 if err != nil { 483 return err 484 } 485 if c.Synchronous() { 486 return c.waitForServerPowerStatus(ctx, id, powerState) 487 } 488 return nil 489} 490 491// StartServer starts a server. 492func (c *Client) StartServer(ctx context.Context, id string) error { 493 return c.setServerPowerState(ctx, id, true) 494} 495 496// StopServer stops a server. 497func (c *Client) StopServer(ctx context.Context, id string) error { 498 return c.setServerPowerState(ctx, id, false) 499} 500 501// ShutdownServer shutdowns a specific server. 502func (c *Client) ShutdownServer(ctx context.Context, id string) error { 503 // Make sure the server exists and that it isn't already in the state we need it to be 504 server, err := c.GetServer(ctx, id) 505 if err != nil { 506 return err 507 } 508 if !server.Properties.Power { 509 return nil 510 } 511 r := gsRequest{ 512 uri: path.Join(apiServerBase, id, "shutdown"), 513 method: http.MethodPatch, 514 body: map[string]string{}, 515 } 516 517 err = r.execute(ctx, *c, nil) 518 if err != nil { 519 return err 520 } 521 522 if c.Synchronous() { 523 // If we get an error, which includes a timeout, power off the server instead. 524 err = c.waitForServerPowerStatus(ctx, id, false) 525 if err != nil { 526 return err 527 } 528 } 529 return nil 530} 531 532// GetServersByLocation gets a list of servers by location. 533// 534// See: https://gridscale.io/en//api-documentation/index.html#operation/getLocationServers 535func (c *Client) GetServersByLocation(ctx context.Context, id string) ([]Server, error) { 536 if !isValidUUID(id) { 537 return nil, errors.New("'id' is invalid") 538 } 539 r := gsRequest{ 540 uri: path.Join(apiLocationBase, id, "servers"), 541 method: http.MethodGet, 542 skipCheckingRequest: true, 543 } 544 var response ServerList 545 var servers []Server 546 err := r.execute(ctx, *c, &response) 547 for _, properties := range response.List { 548 servers = append(servers, Server{Properties: properties}) 549 } 550 return servers, err 551} 552 553// GetDeletedServers gets a list of deleted servers. 554// 555// See: https://gridscale.io/en//api-documentation/index.html#operation/getDeletedServers 556func (c *Client) GetDeletedServers(ctx context.Context) ([]Server, error) { 557 r := gsRequest{ 558 uri: path.Join(apiDeletedBase, "servers"), 559 method: http.MethodGet, 560 skipCheckingRequest: true, 561 } 562 var response DeletedServerList 563 var servers []Server 564 err := r.execute(ctx, *c, &response) 565 for _, properties := range response.List { 566 servers = append(servers, Server{Properties: properties}) 567 } 568 return servers, err 569} 570 571// waitForServerPowerStatus allows to wait for a server changing its power status. 572func (c *Client) waitForServerPowerStatus(ctx context.Context, id string, status bool) error { 573 return retryWithContext(ctx, func() (bool, error) { 574 server, err := c.GetServer(ctx, id) 575 return server.Properties.Power != status, err 576 }, c.DelayInterval()) 577} 578