364 lines
9.6 KiB
Go
364 lines
9.6 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// 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
|
|
|
|
// RaftProtocol is the version of the Raft protocol spoken by this server.
|
|
RaftProtocol string
|
|
}
|
|
|
|
// RaftConfiguration is returned when querying for the current Raft configuration.
|
|
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) {
|
|
r, err := op.c.newRequest("GET", "/v1/operator/raft/configuration")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
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 {
|
|
r, err := op.c.newRequest("DELETE", "/v1/operator/raft/peer")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r.setWriteOptions(q)
|
|
|
|
r.params.Set("address", address)
|
|
|
|
_, resp, err := requireOK(op.c.doRequest(r))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
resp.Body.Close()
|
|
return nil
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// SchedulerConfiguration is the config for controlling scheduler behavior
|
|
type SchedulerConfiguration struct {
|
|
// SchedulerAlgorithm lets you select between available scheduling algorithms.
|
|
SchedulerAlgorithm SchedulerAlgorithm
|
|
|
|
// PreemptionConfig specifies whether to enable eviction of lower
|
|
// priority jobs to place higher priority jobs.
|
|
PreemptionConfig PreemptionConfig
|
|
|
|
// MemoryOversubscriptionEnabled specifies whether memory oversubscription is enabled
|
|
MemoryOversubscriptionEnabled bool
|
|
|
|
// RejectJobRegistration disables new job registrations except with a
|
|
// management ACL token
|
|
RejectJobRegistration bool
|
|
|
|
// PauseEvalBroker stops the leader evaluation broker process from running
|
|
// until the configuration is updated and written to the Nomad servers.
|
|
PauseEvalBroker bool
|
|
|
|
// CreateIndex/ModifyIndex store the create/modify indexes of this configuration.
|
|
CreateIndex uint64
|
|
ModifyIndex uint64
|
|
}
|
|
|
|
// SchedulerConfigurationResponse is the response object that wraps SchedulerConfiguration
|
|
type SchedulerConfigurationResponse struct {
|
|
// SchedulerConfig contains scheduler config options
|
|
SchedulerConfig *SchedulerConfiguration
|
|
|
|
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
|
|
}
|
|
|
|
// SchedulerAlgorithm is an enum string that encapsulates the valid options for a
|
|
// SchedulerConfiguration block's SchedulerAlgorithm. These modes will allow the
|
|
// scheduler to be user-selectable.
|
|
type SchedulerAlgorithm string
|
|
|
|
const (
|
|
SchedulerAlgorithmBinpack SchedulerAlgorithm = "binpack"
|
|
SchedulerAlgorithmSpread SchedulerAlgorithm = "spread"
|
|
)
|
|
|
|
// PreemptionConfig specifies whether preemption is enabled based on scheduler type
|
|
type PreemptionConfig struct {
|
|
SystemSchedulerEnabled bool
|
|
SysBatchSchedulerEnabled bool
|
|
BatchSchedulerEnabled bool
|
|
ServiceSchedulerEnabled bool
|
|
}
|
|
|
|
// SchedulerGetConfiguration is used to query the current Scheduler configuration.
|
|
func (op *Operator) SchedulerGetConfiguration(q *QueryOptions) (*SchedulerConfigurationResponse, *QueryMeta, error) {
|
|
var resp SchedulerConfigurationResponse
|
|
qm, err := op.c.query("/v1/operator/scheduler/configuration", &resp, q)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return &resp, qm, nil
|
|
}
|
|
|
|
// SchedulerSetConfiguration is used to set the current Scheduler configuration.
|
|
func (op *Operator) SchedulerSetConfiguration(conf *SchedulerConfiguration, q *WriteOptions) (*SchedulerSetConfigurationResponse, *WriteMeta, error) {
|
|
var out SchedulerSetConfigurationResponse
|
|
wm, err := op.c.put("/v1/operator/scheduler/configuration", conf, &out, q)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return &out, wm, nil
|
|
}
|
|
|
|
// 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.
|
|
func (op *Operator) SchedulerCASConfiguration(conf *SchedulerConfiguration, q *WriteOptions) (*SchedulerSetConfigurationResponse, *WriteMeta, error) {
|
|
var out SchedulerSetConfigurationResponse
|
|
wm, err := op.c.put("/v1/operator/scheduler/configuration?cas="+strconv.FormatUint(conf.ModifyIndex, 10), conf, &out, q)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return &out, wm, nil
|
|
}
|
|
|
|
// 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(io.Discard, resp.Body)
|
|
resp.Body.Close()
|
|
|
|
return nil, err
|
|
}
|
|
|
|
return cr, nil
|
|
}
|
|
|
|
// 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.put("/v1/operator/snapshot", in, nil, q)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return wm, nil
|
|
}
|
|
|
|
type License struct {
|
|
// The unique identifier of the license
|
|
LicenseID string
|
|
|
|
// The customer ID associated with the license
|
|
CustomerID string
|
|
|
|
// If set, an identifier that should be used to lock the license to a
|
|
// particular site, cluster, etc.
|
|
InstallationID string
|
|
|
|
// The time at which the license was issued
|
|
IssueTime time.Time
|
|
|
|
// The time at which the license starts being valid
|
|
StartTime time.Time
|
|
|
|
// The time after which the license expires
|
|
ExpirationTime time.Time
|
|
|
|
// The time at which the license ceases to function and can
|
|
// no longer be used in any capacity
|
|
TerminationTime time.Time
|
|
|
|
// The product the license is valid for
|
|
Product string
|
|
|
|
// License Specific Flags
|
|
Flags map[string]interface{}
|
|
|
|
// Modules is a list of the licensed enterprise modules
|
|
Modules []string
|
|
|
|
// List of features enabled by the license
|
|
Features []string
|
|
}
|
|
|
|
type LicenseReply struct {
|
|
License *License
|
|
ConfigOutdated bool
|
|
QueryMeta
|
|
}
|
|
|
|
type ApplyLicenseOptions struct {
|
|
Force bool
|
|
}
|
|
|
|
func (op *Operator) LicensePut(license string, q *WriteOptions) (*WriteMeta, error) {
|
|
return op.ApplyLicense(license, nil, q)
|
|
}
|
|
|
|
func (op *Operator) ApplyLicense(license string, opts *ApplyLicenseOptions, q *WriteOptions) (*WriteMeta, error) {
|
|
r, err := op.c.newRequest("PUT", "/v1/operator/license")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if opts != nil && opts.Force {
|
|
r.params.Add("force", "true")
|
|
}
|
|
|
|
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)
|
|
|
|
return wm, nil
|
|
}
|
|
|
|
func (op *Operator) LicenseGet(q *QueryOptions) (*LicenseReply, *QueryMeta, error) {
|
|
req, err := op.c.newRequest("GET", "/v1/operator/license")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
req.setQueryOptions(q)
|
|
|
|
var reply LicenseReply
|
|
rtt, resp, err := op.c.doRequest(req)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode == 204 {
|
|
return nil, nil, errors.New("Nomad Enterprise only endpoint")
|
|
}
|
|
|
|
if resp.StatusCode != 200 {
|
|
body, _ := io.ReadAll(resp.Body)
|
|
return nil, nil, fmt.Errorf("Unexpected response code: %d (%s)", resp.StatusCode, body)
|
|
}
|
|
|
|
err = json.NewDecoder(resp.Body).Decode(&reply)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
qm := &QueryMeta{}
|
|
parseQueryMeta(resp, qm)
|
|
qm.RequestTime = rtt
|
|
|
|
return &reply, qm, nil
|
|
}
|