github.com/vnpaycloud-console/gophercloud/v2@v2.0.5/openstack/compute/v2/servers/requests.go (about)

     1  package servers
     2  
     3  import (
     4  	"context"
     5  	"encoding/base64"
     6  	"encoding/json"
     7  	"fmt"
     8  	"maps"
     9  	"net"
    10  	"regexp"
    11  	"strings"
    12  
    13  	"github.com/vnpaycloud-console/gophercloud/v2"
    14  	"github.com/vnpaycloud-console/gophercloud/v2/pagination"
    15  )
    16  
    17  // ListOptsBuilder allows extensions to add additional parameters to the
    18  // List request.
    19  type ListOptsBuilder interface {
    20  	ToServerListQuery() (string, error)
    21  }
    22  
    23  // ListOpts allows the filtering and sorting of paginated collections through
    24  // the API. Filtering is achieved by passing in struct field values that map to
    25  // the server attributes you want to see returned. Marker and Limit are used
    26  // for pagination.
    27  type ListOpts struct {
    28  	// ChangesSince is a time/date stamp for when the server last changed status.
    29  	ChangesSince string `q:"changes-since"`
    30  
    31  	// Image is the name of the image in URL format.
    32  	Image string `q:"image"`
    33  
    34  	// Flavor is the name of the flavor in URL format.
    35  	Flavor string `q:"flavor"`
    36  
    37  	// IP is a regular expression to match the IPv4 address of the server.
    38  	IP string `q:"ip"`
    39  
    40  	// This requires the client to be set to microversion 2.5 or later, unless
    41  	// the user is an admin.
    42  	// IP is a regular expression to match the IPv6 address of the server.
    43  	IP6 string `q:"ip6"`
    44  
    45  	// Name of the server as a string; can be queried with regular expressions.
    46  	// Realize that ?name=bob returns both bob and bobb. If you need to match bob
    47  	// only, you can use a regular expression matching the syntax of the
    48  	// underlying database server implemented for Compute.
    49  	Name string `q:"name"`
    50  
    51  	// Status is the value of the status of the server so that you can filter on
    52  	// "ACTIVE" for example.
    53  	Status string `q:"status"`
    54  
    55  	// Host is the name of the host as a string.
    56  	Host string `q:"host"`
    57  
    58  	// Marker is a UUID of the server at which you want to set a marker.
    59  	Marker string `q:"marker"`
    60  
    61  	// Limit is an integer value for the limit of values to return.
    62  	Limit int `q:"limit"`
    63  
    64  	// AllTenants is a bool to show all tenants.
    65  	AllTenants bool `q:"all_tenants"`
    66  
    67  	// TenantID lists servers for a particular tenant.
    68  	// Setting "AllTenants = true" is required.
    69  	TenantID string `q:"tenant_id"`
    70  
    71  	// This requires the client to be set to microversion 2.83 or later, unless
    72  	// the user is an admin.
    73  	// UserID lists servers for a particular user.
    74  	UserID string `q:"user_id"`
    75  
    76  	// This requires the client to be set to microversion 2.26 or later.
    77  	// Tags filters on specific server tags. All tags must be present for the server.
    78  	Tags string `q:"tags"`
    79  
    80  	// This requires the client to be set to microversion 2.26 or later.
    81  	// TagsAny filters on specific server tags. At least one of the tags must be present for the server.
    82  	TagsAny string `q:"tags-any"`
    83  
    84  	// This requires the client to be set to microversion 2.26 or later.
    85  	// NotTags filters on specific server tags. All tags must be absent for the server.
    86  	NotTags string `q:"not-tags"`
    87  
    88  	// This requires the client to be set to microversion 2.26 or later.
    89  	// NotTagsAny filters on specific server tags. At least one of the tags must be absent for the server.
    90  	NotTagsAny string `q:"not-tags-any"`
    91  
    92  	// Display servers based on their availability zone (Admin only until microversion 2.82).
    93  	AvailabilityZone string `q:"availability_zone"`
    94  }
    95  
    96  // ToServerListQuery formats a ListOpts into a query string.
    97  func (opts ListOpts) ToServerListQuery() (string, error) {
    98  	q, err := gophercloud.BuildQueryString(opts)
    99  	return q.String(), err
   100  }
   101  
   102  // ListSimple makes a request against the API to list servers accessible to you.
   103  func ListSimple(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
   104  	url := listURL(client)
   105  	if opts != nil {
   106  		query, err := opts.ToServerListQuery()
   107  		if err != nil {
   108  			return pagination.Pager{Err: err}
   109  		}
   110  		url += query
   111  	}
   112  	return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
   113  		return ServerPage{pagination.LinkedPageBase{PageResult: r}}
   114  	})
   115  }
   116  
   117  // List makes a request against the API to list servers details accessible to you.
   118  func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
   119  	url := listDetailURL(client)
   120  	if opts != nil {
   121  		query, err := opts.ToServerListQuery()
   122  		if err != nil {
   123  			return pagination.Pager{Err: err}
   124  		}
   125  		url += query
   126  	}
   127  	return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
   128  		return ServerPage{pagination.LinkedPageBase{PageResult: r}}
   129  	})
   130  }
   131  
   132  // SchedulerHintOptsBuilder builds the scheduler hints into a serializable format.
   133  type SchedulerHintOptsBuilder interface {
   134  	ToSchedulerHintsMap() (map[string]any, error)
   135  }
   136  
   137  // SchedulerHintOpts represents a set of scheduling hints that are passed to the
   138  // OpenStack scheduler.
   139  type SchedulerHintOpts struct {
   140  	// Group specifies a Server Group to place the instance in.
   141  	Group string
   142  
   143  	// DifferentHost will place the instance on a compute node that does not
   144  	// host the given instances.
   145  	DifferentHost []string
   146  
   147  	// SameHost will place the instance on a compute node that hosts the given
   148  	// instances.
   149  	SameHost []string
   150  
   151  	// Query is a conditional statement that results in compute nodes able to
   152  	// host the instance.
   153  	Query []any
   154  
   155  	// TargetCell specifies a cell name where the instance will be placed.
   156  	TargetCell string `json:"target_cell,omitempty"`
   157  
   158  	// DifferentCell specifies cells names where an instance should not be placed.
   159  	DifferentCell []string `json:"different_cell,omitempty"`
   160  
   161  	// BuildNearHostIP specifies a subnet of compute nodes to host the instance.
   162  	BuildNearHostIP string
   163  
   164  	// AdditionalProperies are arbitrary key/values that are not validated by nova.
   165  	AdditionalProperties map[string]any
   166  }
   167  
   168  // ToSchedulerHintsMap assembles a request body for scheduler hints.
   169  func (opts SchedulerHintOpts) ToSchedulerHintsMap() (map[string]any, error) {
   170  	sh := make(map[string]any)
   171  
   172  	uuidRegex, _ := regexp.Compile("^[a-z0-9]{8}-[a-z0-9]{4}-[1-5][a-z0-9]{3}-[a-z0-9]{4}-[a-z0-9]{12}$")
   173  
   174  	if opts.Group != "" {
   175  		if !uuidRegex.MatchString(opts.Group) {
   176  			err := gophercloud.ErrInvalidInput{}
   177  			err.Argument = "servers.schedulerhints.SchedulerHintOpts.Group"
   178  			err.Value = opts.Group
   179  			err.Info = "Group must be a UUID"
   180  			return nil, err
   181  		}
   182  		sh["group"] = opts.Group
   183  	}
   184  
   185  	if len(opts.DifferentHost) > 0 {
   186  		for _, diffHost := range opts.DifferentHost {
   187  			if !uuidRegex.MatchString(diffHost) {
   188  				err := gophercloud.ErrInvalidInput{}
   189  				err.Argument = "servers.schedulerhints.SchedulerHintOpts.DifferentHost"
   190  				err.Value = opts.DifferentHost
   191  				err.Info = "The hosts must be in UUID format."
   192  				return nil, err
   193  			}
   194  		}
   195  		sh["different_host"] = opts.DifferentHost
   196  	}
   197  
   198  	if len(opts.SameHost) > 0 {
   199  		for _, sameHost := range opts.SameHost {
   200  			if !uuidRegex.MatchString(sameHost) {
   201  				err := gophercloud.ErrInvalidInput{}
   202  				err.Argument = "servers.schedulerhints.SchedulerHintOpts.SameHost"
   203  				err.Value = opts.SameHost
   204  				err.Info = "The hosts must be in UUID format."
   205  				return nil, err
   206  			}
   207  		}
   208  		sh["same_host"] = opts.SameHost
   209  	}
   210  
   211  	/*
   212  		Query can be something simple like:
   213  			 [">=", "$free_ram_mb", 1024]
   214  
   215  			Or more complex like:
   216  				['and',
   217  					['>=', '$free_ram_mb', 1024],
   218  					['>=', '$free_disk_mb', 200 * 1024]
   219  				]
   220  
   221  		Because of the possible complexity, just make sure the length is a minimum of 3.
   222  	*/
   223  	if len(opts.Query) > 0 {
   224  		if len(opts.Query) < 3 {
   225  			err := gophercloud.ErrInvalidInput{}
   226  			err.Argument = "servers.schedulerhints.SchedulerHintOpts.Query"
   227  			err.Value = opts.Query
   228  			err.Info = "Must be a conditional statement in the format of [op,variable,value]"
   229  			return nil, err
   230  		}
   231  
   232  		// The query needs to be sent as a marshalled string.
   233  		b, err := json.Marshal(opts.Query)
   234  		if err != nil {
   235  			err := gophercloud.ErrInvalidInput{}
   236  			err.Argument = "servers.schedulerhints.SchedulerHintOpts.Query"
   237  			err.Value = opts.Query
   238  			err.Info = "Must be a conditional statement in the format of [op,variable,value]"
   239  			return nil, err
   240  		}
   241  
   242  		sh["query"] = string(b)
   243  	}
   244  
   245  	if opts.TargetCell != "" {
   246  		sh["target_cell"] = opts.TargetCell
   247  	}
   248  
   249  	if len(opts.DifferentCell) > 0 {
   250  		sh["different_cell"] = opts.DifferentCell
   251  	}
   252  
   253  	if opts.BuildNearHostIP != "" {
   254  		if _, _, err := net.ParseCIDR(opts.BuildNearHostIP); err != nil {
   255  			err := gophercloud.ErrInvalidInput{}
   256  			err.Argument = "servers.schedulerhints.SchedulerHintOpts.BuildNearHostIP"
   257  			err.Value = opts.BuildNearHostIP
   258  			err.Info = "Must be a valid subnet in the form 192.168.1.1/24"
   259  			return nil, err
   260  		}
   261  		ipParts := strings.Split(opts.BuildNearHostIP, "/")
   262  		sh["build_near_host_ip"] = ipParts[0]
   263  		sh["cidr"] = "/" + ipParts[1]
   264  	}
   265  
   266  	if opts.AdditionalProperties != nil {
   267  		for k, v := range opts.AdditionalProperties {
   268  			sh[k] = v
   269  		}
   270  	}
   271  
   272  	if len(sh) == 0 {
   273  		return sh, nil
   274  	}
   275  
   276  	return map[string]any{"os:scheduler_hints": sh}, nil
   277  }
   278  
   279  // Network is used within CreateOpts to control a new server's network
   280  // attachments.
   281  type Network struct {
   282  	// UUID of a network to attach to the newly provisioned server.
   283  	// Required unless Port is provided.
   284  	UUID string
   285  
   286  	// Port of a neutron network to attach to the newly provisioned server.
   287  	// Required unless UUID is provided.
   288  	Port string
   289  
   290  	// FixedIP specifies a fixed IPv4 address to be used on this network.
   291  	FixedIP string
   292  
   293  	// Tag may contain an optional device role tag for the server's virtual
   294  	// network interface. This can be used to identify network interfaces when
   295  	// multiple networks are connected to one server.
   296  	//
   297  	// Requires microversion 2.32 through 2.36 or 2.42 or later.
   298  	Tag string
   299  }
   300  
   301  type (
   302  	// DestinationType represents the type of medium being used as the
   303  	// destination of the bootable device.
   304  	DestinationType string
   305  
   306  	// SourceType represents the type of medium being used as the source of the
   307  	// bootable device.
   308  	SourceType string
   309  )
   310  
   311  const (
   312  	// DestinationLocal DestinationType is for using an ephemeral disk as the
   313  	// destination.
   314  	DestinationLocal DestinationType = "local"
   315  
   316  	// DestinationVolume DestinationType is for using a volume as the destination.
   317  	DestinationVolume DestinationType = "volume"
   318  
   319  	// SourceBlank SourceType is for a "blank" or empty source.
   320  	SourceBlank SourceType = "blank"
   321  
   322  	// SourceImage SourceType is for using images as the source of a block device.
   323  	SourceImage SourceType = "image"
   324  
   325  	// SourceSnapshot SourceType is for using a volume snapshot as the source of
   326  	// a block device.
   327  	SourceSnapshot SourceType = "snapshot"
   328  
   329  	// SourceVolume SourceType is for using a volume as the source of block
   330  	// device.
   331  	SourceVolume SourceType = "volume"
   332  )
   333  
   334  // BlockDevice is a structure with options for creating block devices in a
   335  // server. The block device may be created from an image, snapshot, new volume,
   336  // or existing volume. The destination may be a new volume, existing volume
   337  // which will be attached to the instance, ephemeral disk, or boot device.
   338  type BlockDevice struct {
   339  	// SourceType must be one of: "volume", "snapshot", "image", or "blank".
   340  	SourceType SourceType `json:"source_type" required:"true"`
   341  
   342  	// UUID is the unique identifier for the existing volume, snapshot, or
   343  	// image (see above).
   344  	UUID string `json:"uuid,omitempty"`
   345  
   346  	// BootIndex is the boot index. It defaults to 0.
   347  	BootIndex int `json:"boot_index"`
   348  
   349  	// DeleteOnTermination specifies whether or not to delete the attached volume
   350  	// when the server is deleted. Defaults to `false`.
   351  	DeleteOnTermination bool `json:"delete_on_termination"`
   352  
   353  	// DestinationType is the type that gets created. Possible values are "volume"
   354  	// and "local".
   355  	DestinationType DestinationType `json:"destination_type,omitempty"`
   356  
   357  	// GuestFormat specifies the format of the block device.
   358  	// Not specifying this will cause the device to be formatted to the default in Nova
   359  	// which is currently vfat.
   360  	// https://opendev.org/openstack/nova/src/commit/d0b459423dd81644e8d9382b6c87fabaa4f03ad4/nova/privsep/fs.py#L257
   361  	GuestFormat string `json:"guest_format,omitempty"`
   362  
   363  	// VolumeSize is the size of the volume to create (in gigabytes). This can be
   364  	// omitted for existing volumes.
   365  	VolumeSize int `json:"volume_size,omitempty"`
   366  
   367  	// DeviceType specifies the device type of the block devices.
   368  	// Examples of this are disk, cdrom, floppy, lun, etc.
   369  	DeviceType string `json:"device_type,omitempty"`
   370  
   371  	// DiskBus is the bus type of the block devices.
   372  	// Examples of this are ide, usb, virtio, scsi, etc.
   373  	DiskBus string `json:"disk_bus,omitempty"`
   374  
   375  	// VolumeType is the volume type of the block device.
   376  	// This requires Compute API microversion 2.67 or later.
   377  	VolumeType string `json:"volume_type,omitempty"`
   378  
   379  	// Tag is an arbitrary string that can be applied to a block device.
   380  	// Information about the device tags can be obtained from the metadata API
   381  	// and the config drive, allowing devices to be easily identified.
   382  	// This requires Compute API microversion 2.42 or later.
   383  	Tag string `json:"tag,omitempty"`
   384  }
   385  
   386  // Personality is an array of files that are injected into the server at launch.
   387  type Personality []*File
   388  
   389  // File is used within CreateOpts and RebuildOpts to inject a file into the
   390  // server at launch.
   391  // File implements the json.Marshaler interface, so when a Create or Rebuild
   392  // operation is requested, json.Marshal will call File's MarshalJSON method.
   393  type File struct {
   394  	// Path of the file.
   395  	Path string
   396  
   397  	// Contents of the file. Maximum content size is 255 bytes.
   398  	Contents []byte
   399  }
   400  
   401  // MarshalJSON marshals the escaped file, base64 encoding the contents.
   402  func (f *File) MarshalJSON() ([]byte, error) {
   403  	file := struct {
   404  		Path     string `json:"path"`
   405  		Contents string `json:"contents"`
   406  	}{
   407  		Path:     f.Path,
   408  		Contents: base64.StdEncoding.EncodeToString(f.Contents),
   409  	}
   410  	return json.Marshal(file)
   411  }
   412  
   413  // DiskConfig represents one of the two possible settings for the DiskConfig
   414  // option when creating, rebuilding, or resizing servers: Auto or Manual.
   415  type DiskConfig string
   416  
   417  const (
   418  	// Auto builds a server with a single partition the size of the target flavor
   419  	// disk and automatically adjusts the filesystem to fit the entire partition.
   420  	// Auto may only be used with images and servers that use a single EXT3
   421  	// partition.
   422  	Auto DiskConfig = "AUTO"
   423  
   424  	// Manual builds a server using whatever partition scheme and filesystem are
   425  	// present in the source image. If the target flavor disk is larger, the
   426  	// remaining space is left unpartitioned. This enables images to have non-EXT3
   427  	// filesystems, multiple partitions, and so on, and enables you to manage the
   428  	// disk configuration. It also results in slightly shorter boot times.
   429  	Manual DiskConfig = "MANUAL"
   430  )
   431  
   432  // CreateOptsBuilder allows extensions to add additional parameters to the
   433  // Create request.
   434  type CreateOptsBuilder interface {
   435  	ToServerCreateMap() (map[string]any, error)
   436  }
   437  
   438  // CreateOpts specifies server creation parameters.
   439  type CreateOpts struct {
   440  	// Name is the name to assign to the newly launched server.
   441  	Name string `json:"name" required:"true"`
   442  
   443  	// ImageRef is the ID or full URL to the image that contains the
   444  	// server's OS and initial state.
   445  	// Also optional if using the boot-from-volume extension.
   446  	ImageRef string `json:"imageRef"`
   447  
   448  	// FlavorRef is the ID or full URL to the flavor that describes the server's specs.
   449  	FlavorRef string `json:"flavorRef"`
   450  
   451  	// SecurityGroups lists the names of the security groups to which this server
   452  	// should belong.
   453  	SecurityGroups []string `json:"-"`
   454  
   455  	// UserData contains configuration information or scripts to use upon launch.
   456  	// Create will base64-encode it for you, if it isn't already.
   457  	UserData []byte `json:"-"`
   458  
   459  	// AvailabilityZone in which to launch the server.
   460  	AvailabilityZone string `json:"availability_zone,omitempty"`
   461  
   462  	// Networks dictates how this server will be attached to available networks.
   463  	// By default, the server will be attached to all isolated networks for the
   464  	// tenant.
   465  	// Starting with microversion 2.37 networks can also be an "auto" or "none"
   466  	// string.
   467  	Networks any `json:"-"`
   468  
   469  	// Metadata contains key-value pairs (up to 255 bytes each) to attach to the
   470  	// server.
   471  	Metadata map[string]string `json:"metadata,omitempty"`
   472  
   473  	// Personality includes files to inject into the server at launch.
   474  	// Create will base64-encode file contents for you.
   475  	Personality Personality `json:"personality,omitempty"`
   476  
   477  	// ConfigDrive enables metadata injection through a configuration drive.
   478  	ConfigDrive *bool `json:"config_drive,omitempty"`
   479  
   480  	// AdminPass sets the root user password. If not set, a randomly-generated
   481  	// password will be created and returned in the response.
   482  	AdminPass string `json:"adminPass,omitempty"`
   483  
   484  	// AccessIPv4 specifies an IPv4 address for the instance.
   485  	AccessIPv4 string `json:"accessIPv4,omitempty"`
   486  
   487  	// AccessIPv6 specifies an IPv6 address for the instance.
   488  	AccessIPv6 string `json:"accessIPv6,omitempty"`
   489  
   490  	// Min specifies Minimum number of servers to launch.
   491  	Min int `json:"min_count,omitempty"`
   492  
   493  	// Max specifies Maximum number of servers to launch.
   494  	Max int `json:"max_count,omitempty"`
   495  
   496  	// Tags allows a server to be tagged with single-word metadata.
   497  	// Requires microversion 2.52 or later.
   498  	Tags []string `json:"tags,omitempty"`
   499  
   500  	// (Available from 2.90) Hostname specifies the hostname to configure for the
   501  	// instance in the metadata service. Starting with microversion 2.94, this can
   502  	// be a Fully Qualified Domain Name (FQDN) of up to 255 characters in length.
   503  	// If not set, OpenStack will derive the server's hostname from the Name field.
   504  	Hostname string `json:"hostname,omitempty"`
   505  
   506  	// BlockDevice describes the mapping of various block devices.
   507  	BlockDevice []BlockDevice `json:"block_device_mapping_v2,omitempty"`
   508  
   509  	// DiskConfig [optional] controls how the created server's disk is partitioned.
   510  	DiskConfig DiskConfig `json:"OS-DCF:diskConfig,omitempty"`
   511  
   512  	// HypervisorHostname is the name of the hypervisor to which the server is scheduled.
   513  	HypervisorHostname string `json:"hypervisor_hostname,omitempty"`
   514  }
   515  
   516  // ToServerCreateMap assembles a request body based on the contents of a
   517  // CreateOpts.
   518  func (opts CreateOpts) ToServerCreateMap() (map[string]any, error) {
   519  	// We intentionally don't envelope the body here since we want to strip
   520  	// some fields out and modify others
   521  	b, err := gophercloud.BuildRequestBody(opts, "")
   522  	if err != nil {
   523  		return nil, err
   524  	}
   525  
   526  	if opts.UserData != nil {
   527  		var userData string
   528  		if _, err := base64.StdEncoding.DecodeString(string(opts.UserData)); err != nil {
   529  			userData = base64.StdEncoding.EncodeToString(opts.UserData)
   530  		} else {
   531  			userData = string(opts.UserData)
   532  		}
   533  		b["user_data"] = &userData
   534  	}
   535  
   536  	if len(opts.SecurityGroups) > 0 {
   537  		securityGroups := make([]map[string]any, len(opts.SecurityGroups))
   538  		for i, groupName := range opts.SecurityGroups {
   539  			securityGroups[i] = map[string]any{"name": groupName}
   540  		}
   541  		b["security_groups"] = securityGroups
   542  	}
   543  
   544  	switch v := opts.Networks.(type) {
   545  	case []Network:
   546  		if len(v) > 0 {
   547  			networks := make([]map[string]any, len(v))
   548  			for i, net := range v {
   549  				networks[i] = make(map[string]any)
   550  				if net.UUID != "" {
   551  					networks[i]["uuid"] = net.UUID
   552  				}
   553  				if net.Port != "" {
   554  					networks[i]["port"] = net.Port
   555  				}
   556  				if net.FixedIP != "" {
   557  					networks[i]["fixed_ip"] = net.FixedIP
   558  				}
   559  				if net.Tag != "" {
   560  					networks[i]["tag"] = net.Tag
   561  				}
   562  			}
   563  			b["networks"] = networks
   564  		}
   565  	case string:
   566  		if v == "auto" || v == "none" {
   567  			b["networks"] = v
   568  		} else {
   569  			return nil, fmt.Errorf(`networks must be a slice of Network struct or a string with "auto" or "none" values, current value is %q`, v)
   570  		}
   571  	}
   572  
   573  	if opts.Min != 0 {
   574  		b["min_count"] = opts.Min
   575  	}
   576  
   577  	if opts.Max != 0 {
   578  		b["max_count"] = opts.Max
   579  	}
   580  
   581  	// Now we do our enveloping
   582  	b = map[string]any{"server": b}
   583  
   584  	return b, nil
   585  }
   586  
   587  // Create requests a server to be provisioned to the user in the current tenant.
   588  func Create(ctx context.Context, client *gophercloud.ServiceClient, opts CreateOptsBuilder, hintOpts SchedulerHintOptsBuilder) (r CreateResult) {
   589  	b, err := opts.ToServerCreateMap()
   590  	if err != nil {
   591  		r.Err = err
   592  		return
   593  	}
   594  
   595  	if hintOpts != nil {
   596  		sh, err := hintOpts.ToSchedulerHintsMap()
   597  		if err != nil {
   598  			r.Err = err
   599  			return
   600  		}
   601  		maps.Copy(b, sh)
   602  	}
   603  
   604  	resp, err := client.Post(ctx, createURL(client), b, &r.Body, &gophercloud.RequestOpts{
   605  		OkCodes: []int{200, 202},
   606  	})
   607  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
   608  	return
   609  }
   610  
   611  // Delete requests that a server previously provisioned be removed from your
   612  // account.
   613  func Delete(ctx context.Context, client *gophercloud.ServiceClient, id string) (r DeleteResult) {
   614  	resp, err := client.Delete(ctx, deleteURL(client, id), nil)
   615  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
   616  	return
   617  }
   618  
   619  // ForceDelete forces the deletion of a server.
   620  func ForceDelete(ctx context.Context, client *gophercloud.ServiceClient, id string) (r ActionResult) {
   621  	resp, err := client.Post(ctx, actionURL(client, id), map[string]any{"forceDelete": ""}, nil, nil)
   622  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
   623  	return
   624  }
   625  
   626  // Get requests details on a single server, by ID.
   627  func Get(ctx context.Context, client *gophercloud.ServiceClient, id string) (r GetResult) {
   628  	resp, err := client.Get(ctx, getURL(client, id), &r.Body, &gophercloud.RequestOpts{
   629  		OkCodes: []int{200, 203},
   630  	})
   631  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
   632  	return
   633  }
   634  
   635  // UpdateOptsBuilder allows extensions to add additional attributes to the
   636  // Update request.
   637  type UpdateOptsBuilder interface {
   638  	ToServerUpdateMap() (map[string]any, error)
   639  }
   640  
   641  // UpdateOpts specifies the base attributes that may be updated on an existing
   642  // server.
   643  type UpdateOpts struct {
   644  	// Name changes the displayed name of the server.
   645  	// The server host name will *not* change.
   646  	// Server names are not constrained to be unique, even within the same tenant.
   647  	Name string `json:"name,omitempty"`
   648  
   649  	// AccessIPv4 provides a new IPv4 address for the instance.
   650  	AccessIPv4 string `json:"accessIPv4,omitempty"`
   651  
   652  	// AccessIPv6 provides a new IPv6 address for the instance.
   653  	AccessIPv6 string `json:"accessIPv6,omitempty"`
   654  }
   655  
   656  // ToServerUpdateMap formats an UpdateOpts structure into a request body.
   657  func (opts UpdateOpts) ToServerUpdateMap() (map[string]any, error) {
   658  	return gophercloud.BuildRequestBody(opts, "server")
   659  }
   660  
   661  // Update requests that various attributes of the indicated server be changed.
   662  func Update(ctx context.Context, client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
   663  	b, err := opts.ToServerUpdateMap()
   664  	if err != nil {
   665  		r.Err = err
   666  		return
   667  	}
   668  	resp, err := client.Put(ctx, updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
   669  		OkCodes: []int{200},
   670  	})
   671  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
   672  	return
   673  }
   674  
   675  // ChangeAdminPassword alters the administrator or root password for a specified
   676  // server.
   677  func ChangeAdminPassword(ctx context.Context, client *gophercloud.ServiceClient, id, newPassword string) (r ActionResult) {
   678  	b := map[string]any{
   679  		"changePassword": map[string]string{
   680  			"adminPass": newPassword,
   681  		},
   682  	}
   683  	resp, err := client.Post(ctx, actionURL(client, id), b, nil, nil)
   684  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
   685  	return
   686  }
   687  
   688  // RebootMethod describes the mechanisms by which a server reboot can be requested.
   689  type RebootMethod string
   690  
   691  // These constants determine how a server should be rebooted.
   692  // See the Reboot() function for further details.
   693  const (
   694  	SoftReboot RebootMethod = "SOFT"
   695  	HardReboot RebootMethod = "HARD"
   696  	OSReboot                = SoftReboot
   697  	PowerCycle              = HardReboot
   698  )
   699  
   700  // RebootOptsBuilder allows extensions to add additional parameters to the
   701  // reboot request.
   702  type RebootOptsBuilder interface {
   703  	ToServerRebootMap() (map[string]any, error)
   704  }
   705  
   706  // RebootOpts provides options to the reboot request.
   707  type RebootOpts struct {
   708  	// Type is the type of reboot to perform on the server.
   709  	Type RebootMethod `json:"type" required:"true"`
   710  }
   711  
   712  // ToServerRebootMap builds a body for the reboot request.
   713  func (opts RebootOpts) ToServerRebootMap() (map[string]any, error) {
   714  	return gophercloud.BuildRequestBody(opts, "reboot")
   715  }
   716  
   717  /*
   718  Reboot requests that a given server reboot.
   719  
   720  Two methods exist for rebooting a server:
   721  
   722  HardReboot (aka PowerCycle) starts the server instance by physically cutting
   723  power to the machine, or if a VM, terminating it at the hypervisor level.
   724  It's done. Caput. Full stop.
   725  Then, after a brief while, power is restored or the VM instance restarted.
   726  
   727  SoftReboot (aka OSReboot) simply tells the OS to restart under its own
   728  procedure.
   729  E.g., in Linux, asking it to enter runlevel 6, or executing
   730  "sudo shutdown -r now", or by asking Windows to rtart the machine.
   731  */
   732  func Reboot(ctx context.Context, client *gophercloud.ServiceClient, id string, opts RebootOptsBuilder) (r ActionResult) {
   733  	b, err := opts.ToServerRebootMap()
   734  	if err != nil {
   735  		r.Err = err
   736  		return
   737  	}
   738  	resp, err := client.Post(ctx, actionURL(client, id), b, nil, nil)
   739  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
   740  	return
   741  }
   742  
   743  // RebuildOptsBuilder allows extensions to provide additional parameters to the
   744  // rebuild request.
   745  type RebuildOptsBuilder interface {
   746  	ToServerRebuildMap() (map[string]any, error)
   747  }
   748  
   749  // RebuildOpts represents the configuration options used in a server rebuild
   750  // operation.
   751  type RebuildOpts struct {
   752  	// AdminPass is the server's admin password
   753  	AdminPass string `json:"adminPass,omitempty"`
   754  
   755  	// ImageRef is the ID of the image you want your server to be provisioned on.
   756  	ImageRef string `json:"imageRef"`
   757  
   758  	// Name to set the server to
   759  	Name string `json:"name,omitempty"`
   760  
   761  	// AccessIPv4 [optional] provides a new IPv4 address for the instance.
   762  	AccessIPv4 string `json:"accessIPv4,omitempty"`
   763  
   764  	// AccessIPv6 [optional] provides a new IPv6 address for the instance.
   765  	AccessIPv6 string `json:"accessIPv6,omitempty"`
   766  
   767  	// Metadata [optional] contains key-value pairs (up to 255 bytes each)
   768  	// to attach to the server.
   769  	Metadata map[string]string `json:"metadata,omitempty"`
   770  
   771  	// Personality [optional] includes files to inject into the server at launch.
   772  	// Rebuild will base64-encode file contents for you.
   773  	Personality Personality `json:"personality,omitempty"`
   774  
   775  	// DiskConfig controls how the rebuilt server's disk is partitioned.
   776  	DiskConfig DiskConfig `json:"OS-DCF:diskConfig,omitempty"`
   777  }
   778  
   779  // ToServerRebuildMap formats a RebuildOpts struct into a map for use in JSON
   780  func (opts RebuildOpts) ToServerRebuildMap() (map[string]any, error) {
   781  	if opts.DiskConfig != "" && opts.DiskConfig != Auto && opts.DiskConfig != Manual {
   782  		err := gophercloud.ErrInvalidInput{}
   783  		err.Argument = "servers.RebuildOpts.DiskConfig"
   784  		err.Info = "Must be either diskconfig.Auto or diskconfig.Manual"
   785  		return nil, err
   786  	}
   787  
   788  	b, err := gophercloud.BuildRequestBody(opts, "")
   789  	if err != nil {
   790  		return nil, err
   791  	}
   792  
   793  	return map[string]any{"rebuild": b}, nil
   794  }
   795  
   796  // Rebuild will reprovision the server according to the configuration options
   797  // provided in the RebuildOpts struct.
   798  func Rebuild(ctx context.Context, client *gophercloud.ServiceClient, id string, opts RebuildOptsBuilder) (r RebuildResult) {
   799  	b, err := opts.ToServerRebuildMap()
   800  	if err != nil {
   801  		r.Err = err
   802  		return
   803  	}
   804  	resp, err := client.Post(ctx, actionURL(client, id), b, &r.Body, nil)
   805  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
   806  	return
   807  }
   808  
   809  // ResizeOptsBuilder allows extensions to add additional parameters to the
   810  // resize request.
   811  type ResizeOptsBuilder interface {
   812  	ToServerResizeMap() (map[string]any, error)
   813  }
   814  
   815  // ResizeOpts represents the configuration options used to control a Resize
   816  // operation.
   817  type ResizeOpts struct {
   818  	// FlavorRef is the ID of the flavor you wish your server to become.
   819  	FlavorRef string `json:"flavorRef" required:"true"`
   820  
   821  	// DiskConfig [optional] controls how the resized server's disk is partitioned.
   822  	DiskConfig DiskConfig `json:"OS-DCF:diskConfig,omitempty"`
   823  }
   824  
   825  // ToServerResizeMap formats a ResizeOpts as a map that can be used as a JSON
   826  // request body for the Resize request.
   827  func (opts ResizeOpts) ToServerResizeMap() (map[string]any, error) {
   828  	if opts.DiskConfig != "" && opts.DiskConfig != Auto && opts.DiskConfig != Manual {
   829  		err := gophercloud.ErrInvalidInput{}
   830  		err.Argument = "servers.ResizeOpts.DiskConfig"
   831  		err.Info = "Must be either diskconfig.Auto or diskconfig.Manual"
   832  		return nil, err
   833  	}
   834  
   835  	return gophercloud.BuildRequestBody(opts, "resize")
   836  }
   837  
   838  // Resize instructs the provider to change the flavor of the server.
   839  //
   840  // Note that this implies rebuilding it.
   841  //
   842  // Unfortunately, one cannot pass rebuild parameters to the resize function.
   843  // When the resize completes, the server will be in VERIFY_RESIZE state.
   844  // While in this state, you can explore the use of the new server's
   845  // configuration. If you like it, call ConfirmResize() to commit the resize
   846  // permanently. Otherwise, call RevertResize() to restore the old configuration.
   847  func Resize(ctx context.Context, client *gophercloud.ServiceClient, id string, opts ResizeOptsBuilder) (r ActionResult) {
   848  	b, err := opts.ToServerResizeMap()
   849  	if err != nil {
   850  		r.Err = err
   851  		return
   852  	}
   853  	resp, err := client.Post(ctx, actionURL(client, id), b, nil, nil)
   854  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
   855  	return
   856  }
   857  
   858  // ConfirmResize confirms a previous resize operation on a server.
   859  // See Resize() for more details.
   860  func ConfirmResize(ctx context.Context, client *gophercloud.ServiceClient, id string) (r ActionResult) {
   861  	resp, err := client.Post(ctx, actionURL(client, id), map[string]any{"confirmResize": nil}, nil, &gophercloud.RequestOpts{
   862  		OkCodes: []int{201, 202, 204},
   863  	})
   864  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
   865  	return
   866  }
   867  
   868  // RevertResize cancels a previous resize operation on a server.
   869  // See Resize() for more details.
   870  func RevertResize(ctx context.Context, client *gophercloud.ServiceClient, id string) (r ActionResult) {
   871  	resp, err := client.Post(ctx, actionURL(client, id), map[string]any{"revertResize": nil}, nil, nil)
   872  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
   873  	return
   874  }
   875  
   876  // ResetMetadataOptsBuilder allows extensions to add additional parameters to
   877  // the Reset request.
   878  type ResetMetadataOptsBuilder interface {
   879  	ToMetadataResetMap() (map[string]any, error)
   880  }
   881  
   882  // MetadataOpts is a map that contains key-value pairs.
   883  type MetadataOpts map[string]string
   884  
   885  // ToMetadataResetMap assembles a body for a Reset request based on the contents
   886  // of a MetadataOpts.
   887  func (opts MetadataOpts) ToMetadataResetMap() (map[string]any, error) {
   888  	return map[string]any{"metadata": opts}, nil
   889  }
   890  
   891  // ToMetadataUpdateMap assembles a body for an Update request based on the
   892  // contents of a MetadataOpts.
   893  func (opts MetadataOpts) ToMetadataUpdateMap() (map[string]any, error) {
   894  	return map[string]any{"metadata": opts}, nil
   895  }
   896  
   897  // ResetMetadata will create multiple new key-value pairs for the given server
   898  // ID.
   899  // Note: Using this operation will erase any already-existing metadata and
   900  // create the new metadata provided. To keep any already-existing metadata,
   901  // use the UpdateMetadatas or UpdateMetadata function.
   902  func ResetMetadata(ctx context.Context, client *gophercloud.ServiceClient, id string, opts ResetMetadataOptsBuilder) (r ResetMetadataResult) {
   903  	b, err := opts.ToMetadataResetMap()
   904  	if err != nil {
   905  		r.Err = err
   906  		return
   907  	}
   908  	resp, err := client.Put(ctx, metadataURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
   909  		OkCodes: []int{200},
   910  	})
   911  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
   912  	return
   913  }
   914  
   915  // Metadata requests all the metadata for the given server ID.
   916  func Metadata(ctx context.Context, client *gophercloud.ServiceClient, id string) (r GetMetadataResult) {
   917  	resp, err := client.Get(ctx, metadataURL(client, id), &r.Body, nil)
   918  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
   919  	return
   920  }
   921  
   922  // UpdateMetadataOptsBuilder allows extensions to add additional parameters to
   923  // the Create request.
   924  type UpdateMetadataOptsBuilder interface {
   925  	ToMetadataUpdateMap() (map[string]any, error)
   926  }
   927  
   928  // UpdateMetadata updates (or creates) all the metadata specified by opts for
   929  // the given server ID. This operation does not affect already-existing metadata
   930  // that is not specified by opts.
   931  func UpdateMetadata(ctx context.Context, client *gophercloud.ServiceClient, id string, opts UpdateMetadataOptsBuilder) (r UpdateMetadataResult) {
   932  	b, err := opts.ToMetadataUpdateMap()
   933  	if err != nil {
   934  		r.Err = err
   935  		return
   936  	}
   937  	resp, err := client.Post(ctx, metadataURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
   938  		OkCodes: []int{200},
   939  	})
   940  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
   941  	return
   942  }
   943  
   944  // MetadatumOptsBuilder allows extensions to add additional parameters to the
   945  // Create request.
   946  type MetadatumOptsBuilder interface {
   947  	ToMetadatumCreateMap() (map[string]any, string, error)
   948  }
   949  
   950  // MetadatumOpts is a map of length one that contains a key-value pair.
   951  type MetadatumOpts map[string]string
   952  
   953  // ToMetadatumCreateMap assembles a body for a Create request based on the
   954  // contents of a MetadataumOpts.
   955  func (opts MetadatumOpts) ToMetadatumCreateMap() (map[string]any, string, error) {
   956  	if len(opts) != 1 {
   957  		err := gophercloud.ErrInvalidInput{}
   958  		err.Argument = "servers.MetadatumOpts"
   959  		err.Info = "Must have 1 and only 1 key-value pair"
   960  		return nil, "", err
   961  	}
   962  	metadatum := map[string]any{"meta": opts}
   963  	var key string
   964  	for k := range metadatum["meta"].(MetadatumOpts) {
   965  		key = k
   966  	}
   967  	return metadatum, key, nil
   968  }
   969  
   970  // CreateMetadatum will create or update the key-value pair with the given key
   971  // for the given server ID.
   972  func CreateMetadatum(ctx context.Context, client *gophercloud.ServiceClient, id string, opts MetadatumOptsBuilder) (r CreateMetadatumResult) {
   973  	b, key, err := opts.ToMetadatumCreateMap()
   974  	if err != nil {
   975  		r.Err = err
   976  		return
   977  	}
   978  	resp, err := client.Put(ctx, metadatumURL(client, id, key), b, &r.Body, &gophercloud.RequestOpts{
   979  		OkCodes: []int{200},
   980  	})
   981  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
   982  	return
   983  }
   984  
   985  // Metadatum requests the key-value pair with the given key for the given
   986  // server ID.
   987  func Metadatum(ctx context.Context, client *gophercloud.ServiceClient, id, key string) (r GetMetadatumResult) {
   988  	resp, err := client.Get(ctx, metadatumURL(client, id, key), &r.Body, nil)
   989  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
   990  	return
   991  }
   992  
   993  // DeleteMetadatum will delete the key-value pair with the given key for the
   994  // given server ID.
   995  func DeleteMetadatum(ctx context.Context, client *gophercloud.ServiceClient, id, key string) (r DeleteMetadatumResult) {
   996  	resp, err := client.Delete(ctx, metadatumURL(client, id, key), nil)
   997  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
   998  	return
   999  }
  1000  
  1001  // ListAddresses makes a request against the API to list the servers IP
  1002  // addresses.
  1003  func ListAddresses(client *gophercloud.ServiceClient, id string) pagination.Pager {
  1004  	return pagination.NewPager(client, listAddressesURL(client, id), func(r pagination.PageResult) pagination.Page {
  1005  		return AddressPage{pagination.SinglePageBase(r)}
  1006  	})
  1007  }
  1008  
  1009  // ListAddressesByNetwork makes a request against the API to list the servers IP
  1010  // addresses for the given network.
  1011  func ListAddressesByNetwork(client *gophercloud.ServiceClient, id, network string) pagination.Pager {
  1012  	return pagination.NewPager(client, listAddressesByNetworkURL(client, id, network), func(r pagination.PageResult) pagination.Page {
  1013  		return NetworkAddressPage{pagination.SinglePageBase(r)}
  1014  	})
  1015  }
  1016  
  1017  // CreateImageOptsBuilder allows extensions to add additional parameters to the
  1018  // CreateImage request.
  1019  type CreateImageOptsBuilder interface {
  1020  	ToServerCreateImageMap() (map[string]any, error)
  1021  }
  1022  
  1023  // CreateImageOpts provides options to pass to the CreateImage request.
  1024  type CreateImageOpts struct {
  1025  	// Name of the image/snapshot.
  1026  	Name string `json:"name" required:"true"`
  1027  
  1028  	// Metadata contains key-value pairs (up to 255 bytes each) to attach to
  1029  	// the created image.
  1030  	Metadata map[string]string `json:"metadata,omitempty"`
  1031  }
  1032  
  1033  // ToServerCreateImageMap formats a CreateImageOpts structure into a request
  1034  // body.
  1035  func (opts CreateImageOpts) ToServerCreateImageMap() (map[string]any, error) {
  1036  	return gophercloud.BuildRequestBody(opts, "createImage")
  1037  }
  1038  
  1039  // CreateImage makes a request against the nova API to schedule an image to be
  1040  // created of the server
  1041  func CreateImage(ctx context.Context, client *gophercloud.ServiceClient, id string, opts CreateImageOptsBuilder) (r CreateImageResult) {
  1042  	b, err := opts.ToServerCreateImageMap()
  1043  	if err != nil {
  1044  		r.Err = err
  1045  		return
  1046  	}
  1047  	resp, err := client.Post(ctx, actionURL(client, id), b, nil, &gophercloud.RequestOpts{
  1048  		OkCodes: []int{202},
  1049  	})
  1050  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
  1051  	return
  1052  }
  1053  
  1054  // GetPassword makes a request against the nova API to get the encrypted
  1055  // administrative password.
  1056  func GetPassword(ctx context.Context, client *gophercloud.ServiceClient, serverId string) (r GetPasswordResult) {
  1057  	resp, err := client.Get(ctx, passwordURL(client, serverId), &r.Body, nil)
  1058  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
  1059  	return
  1060  }
  1061  
  1062  // ShowConsoleOutputOptsBuilder is the interface types must satisfy in order to be
  1063  // used as ShowConsoleOutput options
  1064  type ShowConsoleOutputOptsBuilder interface {
  1065  	ToServerShowConsoleOutputMap() (map[string]any, error)
  1066  }
  1067  
  1068  // ShowConsoleOutputOpts satisfies the ShowConsoleOutputOptsBuilder
  1069  type ShowConsoleOutputOpts struct {
  1070  	// The number of lines to fetch from the end of console log.
  1071  	// All lines will be returned if this is not specified.
  1072  	Length int `json:"length,omitempty"`
  1073  }
  1074  
  1075  // ToServerShowConsoleOutputMap formats a ShowConsoleOutputOpts structure into a request body.
  1076  func (opts ShowConsoleOutputOpts) ToServerShowConsoleOutputMap() (map[string]any, error) {
  1077  	return gophercloud.BuildRequestBody(opts, "os-getConsoleOutput")
  1078  }
  1079  
  1080  // ShowConsoleOutput makes a request against the nova API to get console log from the server
  1081  func ShowConsoleOutput(ctx context.Context, client *gophercloud.ServiceClient, id string, opts ShowConsoleOutputOptsBuilder) (r ShowConsoleOutputResult) {
  1082  	b, err := opts.ToServerShowConsoleOutputMap()
  1083  	if err != nil {
  1084  		r.Err = err
  1085  		return
  1086  	}
  1087  	resp, err := client.Post(ctx, actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
  1088  		OkCodes: []int{200},
  1089  	})
  1090  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
  1091  	return
  1092  }
  1093  
  1094  // EvacuateOptsBuilder allows extensions to add additional parameters to the
  1095  // the Evacuate request.
  1096  type EvacuateOptsBuilder interface {
  1097  	ToEvacuateMap() (map[string]any, error)
  1098  }
  1099  
  1100  // EvacuateOpts specifies Evacuate action parameters.
  1101  type EvacuateOpts struct {
  1102  	// The name of the host to which the server is evacuated
  1103  	Host string `json:"host,omitempty"`
  1104  
  1105  	// Indicates whether server is on shared storage
  1106  	OnSharedStorage bool `json:"onSharedStorage"`
  1107  
  1108  	// An administrative password to access the evacuated server
  1109  	AdminPass string `json:"adminPass,omitempty"`
  1110  }
  1111  
  1112  // ToServerGroupCreateMap constructs a request body from CreateOpts.
  1113  func (opts EvacuateOpts) ToEvacuateMap() (map[string]any, error) {
  1114  	return gophercloud.BuildRequestBody(opts, "evacuate")
  1115  }
  1116  
  1117  // Evacuate will Evacuate a failed instance to another host.
  1118  func Evacuate(ctx context.Context, client *gophercloud.ServiceClient, id string, opts EvacuateOptsBuilder) (r EvacuateResult) {
  1119  	b, err := opts.ToEvacuateMap()
  1120  	if err != nil {
  1121  		r.Err = err
  1122  		return
  1123  	}
  1124  	resp, err := client.Post(ctx, actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
  1125  		OkCodes: []int{200},
  1126  	})
  1127  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
  1128  	return
  1129  }
  1130  
  1131  // InjectNetworkInfo will inject the network info into a server
  1132  func InjectNetworkInfo(ctx context.Context, client *gophercloud.ServiceClient, id string) (r InjectNetworkResult) {
  1133  	b := map[string]any{
  1134  		"injectNetworkInfo": nil,
  1135  	}
  1136  	resp, err := client.Post(ctx, actionURL(client, id), b, nil, nil)
  1137  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
  1138  	return
  1139  }
  1140  
  1141  // Lock is the operation responsible for locking a Compute server.
  1142  func Lock(ctx context.Context, client *gophercloud.ServiceClient, id string) (r LockResult) {
  1143  	resp, err := client.Post(ctx, actionURL(client, id), map[string]any{"lock": nil}, nil, nil)
  1144  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
  1145  	return
  1146  }
  1147  
  1148  // Unlock is the operation responsible for unlocking a Compute server.
  1149  func Unlock(ctx context.Context, client *gophercloud.ServiceClient, id string) (r UnlockResult) {
  1150  	resp, err := client.Post(ctx, actionURL(client, id), map[string]any{"unlock": nil}, nil, nil)
  1151  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
  1152  	return
  1153  }
  1154  
  1155  // Migrate will initiate a migration of the instance to another host.
  1156  func Migrate(ctx context.Context, client *gophercloud.ServiceClient, id string) (r MigrateResult) {
  1157  	resp, err := client.Post(ctx, actionURL(client, id), map[string]any{"migrate": nil}, nil, nil)
  1158  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
  1159  	return
  1160  }
  1161  
  1162  // LiveMigrateOptsBuilder allows extensions to add additional parameters to the
  1163  // LiveMigrate request.
  1164  type LiveMigrateOptsBuilder interface {
  1165  	ToLiveMigrateMap() (map[string]any, error)
  1166  }
  1167  
  1168  // LiveMigrateOpts specifies parameters of live migrate action.
  1169  type LiveMigrateOpts struct {
  1170  	// The host to which to migrate the server.
  1171  	// If this parameter is None, the scheduler chooses a host.
  1172  	Host *string `json:"host"`
  1173  
  1174  	// Set to True to migrate local disks by using block migration.
  1175  	// If the source or destination host uses shared storage and you set
  1176  	// this value to True, the live migration fails.
  1177  	BlockMigration *bool `json:"block_migration,omitempty"`
  1178  
  1179  	// Set to True to enable over commit when the destination host is checked
  1180  	// for available disk space. Set to False to disable over commit. This setting
  1181  	// affects only the libvirt virt driver.
  1182  	DiskOverCommit *bool `json:"disk_over_commit,omitempty"`
  1183  }
  1184  
  1185  // ToLiveMigrateMap constructs a request body from LiveMigrateOpts.
  1186  func (opts LiveMigrateOpts) ToLiveMigrateMap() (map[string]any, error) {
  1187  	return gophercloud.BuildRequestBody(opts, "os-migrateLive")
  1188  }
  1189  
  1190  // LiveMigrate will initiate a live-migration (without rebooting) of the instance to another host.
  1191  func LiveMigrate(ctx context.Context, client *gophercloud.ServiceClient, id string, opts LiveMigrateOptsBuilder) (r MigrateResult) {
  1192  	b, err := opts.ToLiveMigrateMap()
  1193  	if err != nil {
  1194  		r.Err = err
  1195  		return
  1196  	}
  1197  	resp, err := client.Post(ctx, actionURL(client, id), b, nil, nil)
  1198  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
  1199  	return
  1200  }
  1201  
  1202  // Pause is the operation responsible for pausing a Compute server.
  1203  func Pause(ctx context.Context, client *gophercloud.ServiceClient, id string) (r PauseResult) {
  1204  	resp, err := client.Post(ctx, actionURL(client, id), map[string]any{"pause": nil}, nil, nil)
  1205  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
  1206  	return
  1207  }
  1208  
  1209  // Unpause is the operation responsible for unpausing a Compute server.
  1210  func Unpause(ctx context.Context, client *gophercloud.ServiceClient, id string) (r UnpauseResult) {
  1211  	resp, err := client.Post(ctx, actionURL(client, id), map[string]any{"unpause": nil}, nil, nil)
  1212  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
  1213  	return
  1214  }
  1215  
  1216  // RescueOptsBuilder is an interface that allows extensions to override the
  1217  // default structure of a Rescue request.
  1218  type RescueOptsBuilder interface {
  1219  	ToServerRescueMap() (map[string]any, error)
  1220  }
  1221  
  1222  // RescueOpts represents the configuration options used to control a Rescue
  1223  // option.
  1224  type RescueOpts struct {
  1225  	// AdminPass is the desired administrative password for the instance in
  1226  	// RESCUE mode.
  1227  	// If it's left blank, the server will generate a password.
  1228  	AdminPass string `json:"adminPass,omitempty"`
  1229  
  1230  	// RescueImageRef contains reference on an image that needs to be used as
  1231  	// rescue image.
  1232  	// If it's left blank, the server will be rescued with the default image.
  1233  	RescueImageRef string `json:"rescue_image_ref,omitempty"`
  1234  }
  1235  
  1236  // ToServerRescueMap formats a RescueOpts as a map that can be used as a JSON
  1237  // request body for the Rescue request.
  1238  func (opts RescueOpts) ToServerRescueMap() (map[string]any, error) {
  1239  	return gophercloud.BuildRequestBody(opts, "rescue")
  1240  }
  1241  
  1242  // Rescue instructs the provider to place the server into RESCUE mode.
  1243  func Rescue(ctx context.Context, client *gophercloud.ServiceClient, id string, opts RescueOptsBuilder) (r RescueResult) {
  1244  	b, err := opts.ToServerRescueMap()
  1245  	if err != nil {
  1246  		r.Err = err
  1247  		return
  1248  	}
  1249  	resp, err := client.Post(ctx, actionURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
  1250  		OkCodes: []int{200},
  1251  	})
  1252  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
  1253  	return
  1254  }
  1255  
  1256  // Unrescue instructs the provider to return the server from RESCUE mode.
  1257  func Unrescue(ctx context.Context, client *gophercloud.ServiceClient, id string) (r UnrescueResult) {
  1258  	resp, err := client.Post(ctx, actionURL(client, id), map[string]any{"unrescue": nil}, nil, nil)
  1259  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
  1260  	return
  1261  }
  1262  
  1263  // ResetNetwork will reset the network of a server
  1264  func ResetNetwork(ctx context.Context, client *gophercloud.ServiceClient, id string) (r ResetNetworkResult) {
  1265  	b := map[string]any{
  1266  		"resetNetwork": nil,
  1267  	}
  1268  	resp, err := client.Post(ctx, actionURL(client, id), b, nil, nil)
  1269  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
  1270  	return
  1271  }
  1272  
  1273  // ServerState refers to the states usable in ResetState Action
  1274  type ServerState string
  1275  
  1276  const (
  1277  	// StateActive returns the state of the server as active
  1278  	StateActive ServerState = "active"
  1279  
  1280  	// StateError returns the state of the server as error
  1281  	StateError ServerState = "error"
  1282  )
  1283  
  1284  // ResetState will reset the state of a server
  1285  func ResetState(ctx context.Context, client *gophercloud.ServiceClient, id string, state ServerState) (r ResetStateResult) {
  1286  	stateMap := map[string]any{"state": state}
  1287  	resp, err := client.Post(ctx, actionURL(client, id), map[string]any{"os-resetState": stateMap}, nil, nil)
  1288  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
  1289  	return
  1290  }
  1291  
  1292  // Shelve is the operation responsible for shelving a Compute server.
  1293  func Shelve(ctx context.Context, client *gophercloud.ServiceClient, id string) (r ShelveResult) {
  1294  	resp, err := client.Post(ctx, actionURL(client, id), map[string]any{"shelve": nil}, nil, nil)
  1295  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
  1296  	return
  1297  }
  1298  
  1299  // ShelveOffload is the operation responsible for Shelve-Offload a Compute server.
  1300  func ShelveOffload(ctx context.Context, client *gophercloud.ServiceClient, id string) (r ShelveOffloadResult) {
  1301  	resp, err := client.Post(ctx, actionURL(client, id), map[string]any{"shelveOffload": nil}, nil, nil)
  1302  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
  1303  	return
  1304  }
  1305  
  1306  // UnshelveOptsBuilder allows extensions to add additional parameters to the
  1307  // Unshelve request.
  1308  type UnshelveOptsBuilder interface {
  1309  	ToUnshelveMap() (map[string]any, error)
  1310  }
  1311  
  1312  // UnshelveOpts specifies parameters of shelve-offload action.
  1313  type UnshelveOpts struct {
  1314  	// Sets the availability zone to unshelve a server
  1315  	// Available only after nova 2.77
  1316  	AvailabilityZone string `json:"availability_zone,omitempty"`
  1317  }
  1318  
  1319  func (opts UnshelveOpts) ToUnshelveMap() (map[string]any, error) {
  1320  	// Key 'availabilty_zone' is required if the unshelve action is an object
  1321  	// i.e {"unshelve": {}} will be rejected
  1322  	b, err := gophercloud.BuildRequestBody(opts, "unshelve")
  1323  	if err != nil {
  1324  		return nil, err
  1325  	}
  1326  
  1327  	if _, ok := b["unshelve"].(map[string]any)["availability_zone"]; !ok {
  1328  		b["unshelve"] = nil
  1329  	}
  1330  
  1331  	return b, err
  1332  }
  1333  
  1334  // Unshelve is the operation responsible for unshelve a Compute server.
  1335  func Unshelve(ctx context.Context, client *gophercloud.ServiceClient, id string, opts UnshelveOptsBuilder) (r UnshelveResult) {
  1336  	b, err := opts.ToUnshelveMap()
  1337  	if err != nil {
  1338  		r.Err = err
  1339  		return
  1340  	}
  1341  	resp, err := client.Post(ctx, actionURL(client, id), b, nil, nil)
  1342  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
  1343  	return
  1344  }
  1345  
  1346  // Start is the operation responsible for starting a Compute server.
  1347  func Start(ctx context.Context, client *gophercloud.ServiceClient, id string) (r StartResult) {
  1348  	resp, err := client.Post(ctx, actionURL(client, id), map[string]any{"os-start": nil}, nil, nil)
  1349  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
  1350  	return
  1351  }
  1352  
  1353  // Stop is the operation responsible for stopping a Compute server.
  1354  func Stop(ctx context.Context, client *gophercloud.ServiceClient, id string) (r StopResult) {
  1355  	resp, err := client.Post(ctx, actionURL(client, id), map[string]any{"os-stop": nil}, nil, nil)
  1356  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
  1357  	return
  1358  }
  1359  
  1360  // Suspend is the operation responsible for suspending a Compute server.
  1361  func Suspend(ctx context.Context, client *gophercloud.ServiceClient, id string) (r SuspendResult) {
  1362  	resp, err := client.Post(ctx, actionURL(client, id), map[string]any{"suspend": nil}, nil, nil)
  1363  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
  1364  	return
  1365  }
  1366  
  1367  // Resume is the operation responsible for resuming a Compute server.
  1368  func Resume(ctx context.Context, client *gophercloud.ServiceClient, id string) (r ResumeResult) {
  1369  	resp, err := client.Post(ctx, actionURL(client, id), map[string]any{"resume": nil}, nil, nil)
  1370  	_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
  1371  	return
  1372  }