2017-02-10 01:58:20 +00:00
|
|
|
package api
|
|
|
|
|
2020-04-21 18:55:31 +00:00
|
|
|
import (
|
2021-02-08 18:53:06 +00:00
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
2021-12-15 00:47:09 +00:00
|
|
|
"fmt"
|
2020-05-21 22:52:19 +00:00
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
2020-04-21 18:55:31 +00:00
|
|
|
"strconv"
|
2020-06-19 14:57:09 +00:00
|
|
|
"strings"
|
2020-04-21 18:55:31 +00:00
|
|
|
"time"
|
|
|
|
)
|
2018-09-28 04:27:38 +00:00
|
|
|
|
2017-02-10 01:58:20 +00:00
|
|
|
// Operator can be used to perform low-level operator tasks for Nomad.
|
|
|
|
type Operator struct {
|
|
|
|
c *Client
|
|
|
|
}
|
|
|
|
|
|
|
|
// Operator returns a handle to the operator endpoints.
|
|
|
|
func (c *Client) Operator() *Operator {
|
|
|
|
return &Operator{c}
|
|
|
|
}
|
|
|
|
|
|
|
|
// RaftServer has information about a server in the Raft configuration.
|
|
|
|
type RaftServer struct {
|
|
|
|
// ID is the unique ID for the server. These are currently the same
|
|
|
|
// as the address, but they will be changed to a real GUID in a future
|
|
|
|
// release of Nomad.
|
|
|
|
ID string
|
|
|
|
|
|
|
|
// Node is the node name of the server, as known by Nomad, or this
|
|
|
|
// will be set to "(unknown)" otherwise.
|
|
|
|
Node string
|
|
|
|
|
|
|
|
// Address is the IP:port of the server, used for Raft communications.
|
|
|
|
Address string
|
|
|
|
|
|
|
|
// Leader is true if this server is the current cluster leader.
|
|
|
|
Leader bool
|
|
|
|
|
|
|
|
// Voter is true if this server has a vote in the cluster. This might
|
|
|
|
// be false if the server is staging and still coming online, or if
|
|
|
|
// it's a non-voting server, which will be added in a future release of
|
|
|
|
// Nomad.
|
|
|
|
Voter bool
|
2017-11-22 00:29:11 +00:00
|
|
|
|
|
|
|
// RaftProtocol is the version of the Raft protocol spoken by this server.
|
|
|
|
RaftProtocol string
|
2017-02-10 01:58:20 +00:00
|
|
|
}
|
|
|
|
|
2018-03-11 17:48:04 +00:00
|
|
|
// RaftConfiguration is returned when querying for the current Raft configuration.
|
2017-02-10 01:58:20 +00:00
|
|
|
type RaftConfiguration struct {
|
|
|
|
// Servers has the list of servers in the Raft configuration.
|
|
|
|
Servers []*RaftServer
|
|
|
|
|
|
|
|
// Index has the Raft index of this configuration.
|
|
|
|
Index uint64
|
|
|
|
}
|
|
|
|
|
|
|
|
// RaftGetConfiguration is used to query the current Raft peer set.
|
|
|
|
func (op *Operator) RaftGetConfiguration(q *QueryOptions) (*RaftConfiguration, error) {
|
2017-02-20 19:12:34 +00:00
|
|
|
r, err := op.c.newRequest("GET", "/v1/operator/raft/configuration")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-02-10 01:58:20 +00:00
|
|
|
r.setQueryOptions(q)
|
|
|
|
_, resp, err := requireOK(op.c.doRequest(r))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
var out RaftConfiguration
|
|
|
|
if err := decodeBody(resp, &out); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &out, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// RaftRemovePeerByAddress is used to kick a stale peer (one that it in the Raft
|
|
|
|
// quorum but no longer known to Serf or the catalog) by address in the form of
|
|
|
|
// "IP:port".
|
|
|
|
func (op *Operator) RaftRemovePeerByAddress(address string, q *WriteOptions) error {
|
2017-02-20 19:12:34 +00:00
|
|
|
r, err := op.c.newRequest("DELETE", "/v1/operator/raft/peer")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-02-10 01:58:20 +00:00
|
|
|
r.setWriteOptions(q)
|
|
|
|
|
2017-09-26 22:26:33 +00:00
|
|
|
r.params.Set("address", address)
|
2017-02-10 01:58:20 +00:00
|
|
|
|
|
|
|
_, resp, err := requireOK(op.c.doRequest(r))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
resp.Body.Close()
|
|
|
|
return nil
|
|
|
|
}
|
2018-01-16 21:35:32 +00:00
|
|
|
|
|
|
|
// RaftRemovePeerByID is used to kick a stale peer (one that is in the Raft
|
|
|
|
// quorum but no longer known to Serf or the catalog) by ID.
|
|
|
|
func (op *Operator) RaftRemovePeerByID(id string, q *WriteOptions) error {
|
|
|
|
r, err := op.c.newRequest("DELETE", "/v1/operator/raft/peer")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
r.setWriteOptions(q)
|
|
|
|
|
|
|
|
r.params.Set("id", id)
|
|
|
|
|
|
|
|
_, resp, err := requireOK(op.c.doRequest(r))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
resp.Body.Close()
|
|
|
|
return nil
|
|
|
|
}
|
2018-09-28 04:27:38 +00:00
|
|
|
|
2020-04-03 23:03:14 +00:00
|
|
|
// SchedulerConfiguration is the config for controlling scheduler behavior
|
2018-09-28 04:27:38 +00:00
|
|
|
type SchedulerConfiguration struct {
|
2020-04-03 23:03:14 +00:00
|
|
|
// SchedulerAlgorithm lets you select between available scheduling algorithms.
|
2020-04-24 14:47:43 +00:00
|
|
|
SchedulerAlgorithm SchedulerAlgorithm
|
2020-04-03 23:03:14 +00:00
|
|
|
|
2018-10-01 14:26:52 +00:00
|
|
|
// PreemptionConfig specifies whether to enable eviction of lower
|
2018-09-28 04:27:38 +00:00
|
|
|
// priority jobs to place higher priority jobs.
|
2018-10-01 14:26:52 +00:00
|
|
|
PreemptionConfig PreemptionConfig
|
2018-09-28 04:27:38 +00:00
|
|
|
|
2021-05-03 02:53:53 +00:00
|
|
|
// MemoryOversubscriptionEnabled specifies whether memory oversubscription is enabled
|
|
|
|
MemoryOversubscriptionEnabled bool
|
|
|
|
|
2021-12-06 20:20:34 +00:00
|
|
|
// RejectJobRegistration disables new job registrations except with a
|
|
|
|
// management ACL token
|
|
|
|
RejectJobRegistration bool
|
|
|
|
|
2018-09-28 04:27:38 +00:00
|
|
|
// CreateIndex/ModifyIndex store the create/modify indexes of this configuration.
|
|
|
|
CreateIndex uint64
|
|
|
|
ModifyIndex uint64
|
|
|
|
}
|
|
|
|
|
2018-10-29 18:10:43 +00:00
|
|
|
// SchedulerConfigurationResponse is the response object that wraps SchedulerConfiguration
|
|
|
|
type SchedulerConfigurationResponse struct {
|
|
|
|
// SchedulerConfig contains scheduler config options
|
2018-11-10 16:31:10 +00:00
|
|
|
SchedulerConfig *SchedulerConfiguration
|
2018-10-29 18:10:43 +00:00
|
|
|
|
2018-11-10 16:31:10 +00:00
|
|
|
QueryMeta
|
|
|
|
}
|
|
|
|
|
|
|
|
// SchedulerSetConfigurationResponse is the response object used
|
|
|
|
// when updating scheduler configuration
|
|
|
|
type SchedulerSetConfigurationResponse struct {
|
|
|
|
// Updated returns whether the config was actually updated
|
|
|
|
// Only set when the request uses CAS
|
|
|
|
Updated bool
|
|
|
|
|
|
|
|
WriteMeta
|
2018-10-29 18:10:43 +00:00
|
|
|
}
|
|
|
|
|
2020-04-03 23:03:14 +00:00
|
|
|
// SchedulerAlgorithm is an enum string that encapsulates the valid options for a
|
|
|
|
// SchedulerConfiguration stanza's SchedulerAlgorithm. These modes will allow the
|
|
|
|
// scheduler to be user-selectable.
|
|
|
|
type SchedulerAlgorithm string
|
|
|
|
|
2020-04-24 14:47:43 +00:00
|
|
|
const (
|
|
|
|
SchedulerAlgorithmBinpack SchedulerAlgorithm = "binpack"
|
|
|
|
SchedulerAlgorithmSpread SchedulerAlgorithm = "spread"
|
|
|
|
)
|
|
|
|
|
2018-10-01 14:26:52 +00:00
|
|
|
// PreemptionConfig specifies whether preemption is enabled based on scheduler type
|
|
|
|
type PreemptionConfig struct {
|
2020-10-09 21:31:38 +00:00
|
|
|
SystemSchedulerEnabled bool
|
|
|
|
SysBatchSchedulerEnabled bool
|
|
|
|
BatchSchedulerEnabled bool
|
|
|
|
ServiceSchedulerEnabled bool
|
2018-10-01 14:26:52 +00:00
|
|
|
}
|
|
|
|
|
2018-09-28 04:27:38 +00:00
|
|
|
// SchedulerGetConfiguration is used to query the current Scheduler configuration.
|
2021-11-02 21:42:52 +00:00
|
|
|
func (op *Operator) SchedulerGetConfiguration(q *QueryOptions) (*SchedulerConfigurationResponse, *QueryMeta, error) {
|
|
|
|
var resp SchedulerConfigurationResponse
|
2018-11-12 21:57:45 +00:00
|
|
|
qm, err := op.c.query("/v1/operator/scheduler/configuration", &resp, q)
|
2018-09-28 04:27:38 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
return &resp, qm, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// SchedulerSetConfiguration is used to set the current Scheduler configuration.
|
2018-11-10 16:31:10 +00:00
|
|
|
func (op *Operator) SchedulerSetConfiguration(conf *SchedulerConfiguration, q *WriteOptions) (*SchedulerSetConfigurationResponse, *WriteMeta, error) {
|
|
|
|
var out SchedulerSetConfigurationResponse
|
2018-11-12 21:57:45 +00:00
|
|
|
wm, err := op.c.write("/v1/operator/scheduler/configuration", conf, &out, q)
|
2018-09-28 04:27:38 +00:00
|
|
|
if err != nil {
|
2018-11-10 16:31:10 +00:00
|
|
|
return nil, nil, err
|
2018-09-28 04:27:38 +00:00
|
|
|
}
|
2018-11-10 16:31:10 +00:00
|
|
|
return &out, wm, nil
|
2018-09-28 04:27:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// SchedulerCASConfiguration is used to perform a Check-And-Set update on the
|
|
|
|
// Scheduler configuration. The ModifyIndex value will be respected. Returns
|
|
|
|
// true on success or false on failures.
|
2018-11-10 16:31:10 +00:00
|
|
|
func (op *Operator) SchedulerCASConfiguration(conf *SchedulerConfiguration, q *WriteOptions) (*SchedulerSetConfigurationResponse, *WriteMeta, error) {
|
|
|
|
var out SchedulerSetConfigurationResponse
|
2018-11-12 21:57:45 +00:00
|
|
|
wm, err := op.c.write("/v1/operator/scheduler/configuration?cas="+strconv.FormatUint(conf.ModifyIndex, 10), conf, &out, q)
|
2018-09-28 04:27:38 +00:00
|
|
|
if err != nil {
|
2018-11-10 16:31:10 +00:00
|
|
|
return nil, nil, err
|
2018-09-28 04:27:38 +00:00
|
|
|
}
|
|
|
|
|
2018-11-10 16:31:10 +00:00
|
|
|
return &out, wm, nil
|
2018-09-28 04:27:38 +00:00
|
|
|
}
|
2020-04-21 18:55:31 +00:00
|
|
|
|
2020-05-21 22:52:19 +00:00
|
|
|
// Snapshot is used to capture a snapshot state of a running cluster.
|
|
|
|
// The returned reader that must be consumed fully
|
|
|
|
func (op *Operator) Snapshot(q *QueryOptions) (io.ReadCloser, error) {
|
|
|
|
r, err := op.c.newRequest("GET", "/v1/operator/snapshot")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
r.setQueryOptions(q)
|
|
|
|
_, resp, err := requireOK(op.c.doRequest(r))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
digest := resp.Header.Get("Digest")
|
|
|
|
|
|
|
|
cr, err := newChecksumValidatingReader(resp.Body, digest)
|
|
|
|
if err != nil {
|
|
|
|
io.Copy(ioutil.Discard, resp.Body)
|
|
|
|
resp.Body.Close()
|
|
|
|
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return cr, nil
|
|
|
|
}
|
|
|
|
|
2020-06-06 12:19:10 +00:00
|
|
|
// SnapshotRestore is used to restore a running nomad cluster to an original
|
|
|
|
// state.
|
|
|
|
func (op *Operator) SnapshotRestore(in io.Reader, q *WriteOptions) (*WriteMeta, error) {
|
|
|
|
wm, err := op.c.write("/v1/operator/snapshot", in, nil, q)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return wm, nil
|
|
|
|
}
|
|
|
|
|
2020-04-21 18:55:31 +00:00
|
|
|
type License struct {
|
|
|
|
// The unique identifier of the license
|
2020-05-11 20:38:35 +00:00
|
|
|
LicenseID string
|
2020-04-21 18:55:31 +00:00
|
|
|
|
|
|
|
// The customer ID associated with the license
|
2020-05-11 20:38:35 +00:00
|
|
|
CustomerID string
|
2020-04-21 18:55:31 +00:00
|
|
|
|
|
|
|
// If set, an identifier that should be used to lock the license to a
|
|
|
|
// particular site, cluster, etc.
|
2020-05-11 20:38:35 +00:00
|
|
|
InstallationID string
|
2020-04-21 18:55:31 +00:00
|
|
|
|
|
|
|
// The time at which the license was issued
|
2020-05-11 20:38:35 +00:00
|
|
|
IssueTime time.Time
|
2020-04-21 18:55:31 +00:00
|
|
|
|
|
|
|
// The time at which the license starts being valid
|
2020-05-11 20:38:35 +00:00
|
|
|
StartTime time.Time
|
2020-04-21 18:55:31 +00:00
|
|
|
|
|
|
|
// The time after which the license expires
|
2020-05-11 20:38:35 +00:00
|
|
|
ExpirationTime time.Time
|
2020-04-21 18:55:31 +00:00
|
|
|
|
|
|
|
// The time at which the license ceases to function and can
|
|
|
|
// no longer be used in any capacity
|
2020-05-11 20:38:35 +00:00
|
|
|
TerminationTime time.Time
|
2020-04-21 18:55:31 +00:00
|
|
|
|
|
|
|
// The product the license is valid for
|
2020-05-11 20:38:35 +00:00
|
|
|
Product string
|
2020-04-21 18:55:31 +00:00
|
|
|
|
|
|
|
// License Specific Flags
|
2020-05-11 20:38:35 +00:00
|
|
|
Flags map[string]interface{}
|
2020-04-21 18:55:31 +00:00
|
|
|
|
|
|
|
// Modules is a list of the licensed enterprise modules
|
2020-05-11 20:38:35 +00:00
|
|
|
Modules []string
|
2020-04-21 18:55:31 +00:00
|
|
|
|
|
|
|
// List of features enabled by the license
|
2020-05-11 20:38:35 +00:00
|
|
|
Features []string
|
2020-04-21 18:55:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type LicenseReply struct {
|
2021-03-23 13:08:14 +00:00
|
|
|
License *License
|
|
|
|
ConfigOutdated bool
|
2020-04-21 18:55:31 +00:00
|
|
|
QueryMeta
|
|
|
|
}
|
|
|
|
|
2021-03-23 13:08:14 +00:00
|
|
|
type ApplyLicenseOptions struct {
|
|
|
|
Force bool
|
|
|
|
}
|
|
|
|
|
2020-05-05 14:28:58 +00:00
|
|
|
func (op *Operator) LicensePut(license string, q *WriteOptions) (*WriteMeta, error) {
|
2021-03-23 13:08:14 +00:00
|
|
|
return op.ApplyLicense(license, nil, q)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (op *Operator) ApplyLicense(license string, opts *ApplyLicenseOptions, q *WriteOptions) (*WriteMeta, error) {
|
2020-06-19 14:57:09 +00:00
|
|
|
r, err := op.c.newRequest("PUT", "/v1/operator/license")
|
2020-04-21 18:55:31 +00:00
|
|
|
if err != nil {
|
2020-05-05 14:28:58 +00:00
|
|
|
return nil, err
|
2020-04-21 18:55:31 +00:00
|
|
|
}
|
2021-03-23 13:08:14 +00:00
|
|
|
|
|
|
|
if opts != nil && opts.Force {
|
|
|
|
r.params.Add("force", "true")
|
|
|
|
}
|
|
|
|
|
2020-06-19 14:57:09 +00:00
|
|
|
r.setWriteOptions(q)
|
|
|
|
r.body = strings.NewReader(license)
|
|
|
|
|
|
|
|
rtt, resp, err := requireOK(op.c.doRequest(r))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
wm := &WriteMeta{RequestTime: rtt}
|
|
|
|
parseWriteMeta(resp, wm)
|
|
|
|
|
2020-05-05 14:28:58 +00:00
|
|
|
return wm, nil
|
2020-04-21 18:55:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (op *Operator) LicenseGet(q *QueryOptions) (*LicenseReply, *QueryMeta, error) {
|
2021-02-08 18:53:06 +00:00
|
|
|
req, err := op.c.newRequest("GET", "/v1/operator/license")
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
2021-04-05 19:35:14 +00:00
|
|
|
req.setQueryOptions(q)
|
2021-02-08 18:53:06 +00:00
|
|
|
|
2020-04-21 18:55:31 +00:00
|
|
|
var reply LicenseReply
|
2021-03-31 19:40:42 +00:00
|
|
|
rtt, resp, err := op.c.doRequest(req)
|
2020-04-21 18:55:31 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
2021-02-08 18:53:06 +00:00
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
if resp.StatusCode == 204 {
|
|
|
|
return nil, nil, errors.New("Nomad Enterprise only endpoint")
|
|
|
|
}
|
|
|
|
|
2021-12-15 00:47:09 +00:00
|
|
|
if resp.StatusCode != 200 {
|
|
|
|
body, _ := io.ReadAll(resp.Body)
|
|
|
|
return nil, nil, fmt.Errorf("Unexpected response code: %d (%s)", resp.StatusCode, body)
|
|
|
|
}
|
|
|
|
|
2021-02-08 18:53:06 +00:00
|
|
|
err = json.NewDecoder(resp.Body).Decode(&reply)
|
2021-03-31 19:40:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
2021-02-08 18:53:06 +00:00
|
|
|
}
|
|
|
|
|
2021-03-31 19:40:42 +00:00
|
|
|
qm := &QueryMeta{}
|
|
|
|
parseQueryMeta(resp, qm)
|
|
|
|
qm.RequestTime = rtt
|
|
|
|
|
|
|
|
return &reply, qm, nil
|
2020-04-21 18:55:31 +00:00
|
|
|
}
|