Skip to content

Commit e634f9c

Browse files
committed
Merge branch 'master' into f-ui/alloc-fs
2 parents d2ba531 + 3b04afe commit e634f9c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+2716
-583
lines changed

api/ioutil.go

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package api
2+
3+
import (
4+
"crypto/md5"
5+
"crypto/sha256"
6+
"crypto/sha512"
7+
"encoding/base64"
8+
"fmt"
9+
"hash"
10+
"io"
11+
"strings"
12+
)
13+
14+
var errMismatchChecksum = fmt.Errorf("mismatch checksum")
15+
16+
// checksumValidatingReader is a wrapper reader that validates
17+
// the checksum of the underlying reader.
18+
type checksumValidatingReader struct {
19+
r io.ReadCloser
20+
21+
// algo is the hash algorithm (e.g. `sha-256`)
22+
algo string
23+
24+
// checksum is the base64 component of checksum
25+
checksum string
26+
27+
// hash is the hashing function used to compute the checksum
28+
hash hash.Hash
29+
}
30+
31+
// newChecksumValidatingReader returns a checksum-validating wrapper reader, according
32+
// to a digest received in HTTP header
33+
//
34+
// The digest must be in the format "<algo>=<base64 of hash>" (e.g. "sha-256=gPelGB7...").
35+
//
36+
// When the reader is fully consumed (i.e. EOT is encountered), if the checksum don't match,
37+
// `Read` returns a checksum mismatch error.
38+
func newChecksumValidatingReader(r io.ReadCloser, digest string) (io.ReadCloser, error) {
39+
parts := strings.SplitN(digest, "=", 2)
40+
if len(parts) != 2 {
41+
return nil, fmt.Errorf("invalid digest format")
42+
}
43+
44+
algo := parts[0]
45+
var hash hash.Hash
46+
switch algo {
47+
case "sha-256":
48+
hash = sha256.New()
49+
case "sha-512":
50+
hash = sha512.New()
51+
case "md5":
52+
hash = md5.New()
53+
}
54+
55+
return &checksumValidatingReader{
56+
r: r,
57+
algo: algo,
58+
checksum: parts[1],
59+
hash: hash,
60+
}, nil
61+
}
62+
63+
func (r *checksumValidatingReader) Read(b []byte) (int, error) {
64+
n, err := r.r.Read(b)
65+
if n != 0 {
66+
r.hash.Write(b[:n])
67+
}
68+
69+
if err == io.EOF || err == io.ErrClosedPipe {
70+
found := base64.StdEncoding.EncodeToString(r.hash.Sum(nil))
71+
if found != r.checksum {
72+
return n, errMismatchChecksum
73+
}
74+
}
75+
76+
return n, err
77+
}
78+
79+
func (r *checksumValidatingReader) Close() error {
80+
return r.r.Close()
81+
}

api/ioutil_test.go

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package api
2+
3+
import (
4+
"bytes"
5+
"crypto/sha256"
6+
"crypto/sha512"
7+
"encoding/base64"
8+
"fmt"
9+
"hash"
10+
"io"
11+
"io/ioutil"
12+
"math/rand"
13+
"testing"
14+
"testing/iotest"
15+
16+
"github.com/stretchr/testify/require"
17+
)
18+
19+
func TestChecksumValidatingReader(t *testing.T) {
20+
data := make([]byte, 4096)
21+
_, err := rand.Read(data)
22+
require.NoError(t, err)
23+
24+
cases := []struct {
25+
algo string
26+
hash hash.Hash
27+
}{
28+
{"sha-256", sha256.New()},
29+
{"sha-512", sha512.New()},
30+
}
31+
32+
for _, c := range cases {
33+
t.Run("valid: "+c.algo, func(t *testing.T) {
34+
_, err := c.hash.Write(data)
35+
require.NoError(t, err)
36+
37+
checksum := c.hash.Sum(nil)
38+
digest := c.algo + "=" + base64.StdEncoding.EncodeToString(checksum)
39+
40+
r := iotest.HalfReader(bytes.NewReader(data))
41+
cr, err := newChecksumValidatingReader(ioutil.NopCloser(r), digest)
42+
require.NoError(t, err)
43+
44+
_, err = io.Copy(ioutil.Discard, cr)
45+
require.NoError(t, err)
46+
})
47+
48+
t.Run("invalid: "+c.algo, func(t *testing.T) {
49+
_, err := c.hash.Write(data)
50+
require.NoError(t, err)
51+
52+
checksum := c.hash.Sum(nil)
53+
// mess up checksum
54+
checksum[0]++
55+
digest := c.algo + "=" + base64.StdEncoding.EncodeToString(checksum)
56+
57+
r := iotest.HalfReader(bytes.NewReader(data))
58+
cr, err := newChecksumValidatingReader(ioutil.NopCloser(r), digest)
59+
require.NoError(t, err)
60+
61+
_, err = io.Copy(ioutil.Discard, cr)
62+
require.Error(t, err)
63+
require.Equal(t, errMismatchChecksum, err)
64+
})
65+
}
66+
}
67+
68+
func TestChecksumValidatingReader_PropagatesError(t *testing.T) {
69+
70+
pr, pw := io.Pipe()
71+
defer pr.Close()
72+
defer pw.Close()
73+
74+
expectedErr := fmt.Errorf("some error")
75+
76+
go func() {
77+
pw.Write([]byte("some input"))
78+
pw.CloseWithError(expectedErr)
79+
}()
80+
81+
cr, err := newChecksumValidatingReader(pr, "sha-256=aaaa")
82+
require.NoError(t, err)
83+
84+
_, err = io.Copy(ioutil.Discard, cr)
85+
require.Error(t, err)
86+
require.Equal(t, expectedErr, err)
87+
}

api/jobs.go

+8
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,13 @@ func (j *Jobs) PrefixList(prefix string) ([]*JobListStub, *QueryMeta, error) {
142142
return j.List(&QueryOptions{Prefix: prefix})
143143
}
144144

145+
// ListAll is used to list all of the existing jobs in all namespaces.
146+
func (j *Jobs) ListAll() ([]*JobListStub, *QueryMeta, error) {
147+
return j.List(&QueryOptions{
148+
Params: map[string]string{"all_namespaces": "true"},
149+
})
150+
}
151+
145152
// Info is used to retrieve information about a particular
146153
// job given its unique ID.
147154
func (j *Jobs) Info(jobID string, q *QueryOptions) (*Job, *QueryMeta, error) {
@@ -867,6 +874,7 @@ type JobListStub struct {
867874
ID string
868875
ParentID string
869876
Name string
877+
Namespace string `json:",omitempty"`
870878
Datacenters []string
871879
Type string
872880
Priority int

api/operator.go

+28
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package api
22

33
import (
4+
"io"
5+
"io/ioutil"
46
"strconv"
57
"time"
68
)
@@ -194,6 +196,32 @@ func (op *Operator) SchedulerCASConfiguration(conf *SchedulerConfiguration, q *W
194196
return &out, wm, nil
195197
}
196198

199+
// Snapshot is used to capture a snapshot state of a running cluster.
200+
// The returned reader that must be consumed fully
201+
func (op *Operator) Snapshot(q *QueryOptions) (io.ReadCloser, error) {
202+
r, err := op.c.newRequest("GET", "/v1/operator/snapshot")
203+
if err != nil {
204+
return nil, err
205+
}
206+
r.setQueryOptions(q)
207+
_, resp, err := requireOK(op.c.doRequest(r))
208+
if err != nil {
209+
return nil, err
210+
}
211+
212+
digest := resp.Header.Get("Digest")
213+
214+
cr, err := newChecksumValidatingReader(resp.Body, digest)
215+
if err != nil {
216+
io.Copy(ioutil.Discard, resp.Body)
217+
resp.Body.Close()
218+
219+
return nil, err
220+
}
221+
222+
return cr, nil
223+
}
224+
197225
type License struct {
198226
// The unique identifier of the license
199227
LicenseID string

client/client.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ func NewClient(cfg *config.Config, consulCatalog consul.CatalogAPI, consulServic
344344
invalidAllocs: make(map[string]struct{}),
345345
serversContactedCh: make(chan struct{}),
346346
serversContactedOnce: sync.Once{},
347-
EnterpriseClient: newEnterpriseClient(),
347+
EnterpriseClient: newEnterpriseClient(logger),
348348
}
349349

350350
c.batchNodeUpdates = newBatchNodeUpdates(

client/enterprise_client_oss.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
package client
44

5+
import hclog "github.com/hashicorp/go-hclog"
6+
57
// EnterpriseClient holds information and methods for enterprise functionality
68
type EnterpriseClient struct{}
79

8-
func newEnterpriseClient() *EnterpriseClient {
10+
func newEnterpriseClient(logger hclog.Logger) *EnterpriseClient {
911
return &EnterpriseClient{}
1012
}
1113

command/agent/config_oss.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// +build !pro,!ent
1+
// +build !ent
22

33
package agent
44

command/agent/http.go

+1
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ func (s *HTTPServer) registerHandlers(enableDebug bool) {
318318
s.mux.HandleFunc("/v1/operator/raft/", s.wrap(s.OperatorRequest))
319319
s.mux.HandleFunc("/v1/operator/autopilot/configuration", s.wrap(s.OperatorAutopilotConfiguration))
320320
s.mux.HandleFunc("/v1/operator/autopilot/health", s.wrap(s.OperatorServerHealth))
321+
s.mux.HandleFunc("/v1/operator/snapshot", s.wrap(s.SnapshotRequest))
321322

322323
s.mux.HandleFunc("/v1/system/gc", s.wrap(s.GarbageCollectRequest))
323324
s.mux.HandleFunc("/v1/system/reconcile/summaries", s.wrap(s.ReconcileJobSummaries))

command/agent/job_endpoint.go

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ func (s *HTTPServer) jobListRequest(resp http.ResponseWriter, req *http.Request)
3131
return nil, nil
3232
}
3333

34+
args.AllNamespaces, _ = strconv.ParseBool(req.URL.Query().Get("all_namespaces"))
3435
var out structs.JobListResponse
3536
if err := s.agent.RPC("Job.List", &args, &out); err != nil {
3637
return nil, err

command/agent/job_endpoint_test.go

+40
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,46 @@ func TestHTTP_PrefixJobsList(t *testing.T) {
127127
})
128128
}
129129

130+
func TestHTTP_JobsList_AllNamespaces_OSS(t *testing.T) {
131+
t.Parallel()
132+
httpTest(t, nil, func(s *TestAgent) {
133+
for i := 0; i < 3; i++ {
134+
// Create the job
135+
job := mock.Job()
136+
args := structs.JobRegisterRequest{
137+
Job: job,
138+
WriteRequest: structs.WriteRequest{
139+
Region: "global",
140+
Namespace: structs.DefaultNamespace,
141+
},
142+
}
143+
var resp structs.JobRegisterResponse
144+
err := s.Agent.RPC("Job.Register", &args, &resp)
145+
require.NoError(t, err)
146+
}
147+
148+
// Make the HTTP request
149+
req, err := http.NewRequest("GET", "/v1/jobs?all_namespaces=true", nil)
150+
require.NoError(t, err)
151+
respW := httptest.NewRecorder()
152+
153+
// Make the request
154+
obj, err := s.Server.JobsRequest(respW, req)
155+
require.NoError(t, err)
156+
157+
// Check for the index
158+
require.NotEmpty(t, respW.HeaderMap.Get("X-Nomad-Index"), "missing index")
159+
require.Equal(t, "true", respW.HeaderMap.Get("X-Nomad-KnownLeader"), "missing known leader")
160+
require.NotEmpty(t, respW.HeaderMap.Get("X-Nomad-LastContact"), "missing last contact")
161+
162+
// Check the job
163+
j := obj.([]*structs.JobListStub)
164+
require.Len(t, j, 3)
165+
166+
require.Equal(t, "default", j[0].Namespace)
167+
})
168+
}
169+
130170
func TestHTTP_JobsRegister(t *testing.T) {
131171
t.Parallel()
132172
httpTest(t, nil, func(s *TestAgent) {

0 commit comments

Comments
 (0)