func TestACLEndpoint_List(t *testing.T) { dir1, s1 := testServerWithConfig(t, func(c *Config) { c.ACLDatacenter = "dc1" c.ACLMasterToken = "root" }) defer os.RemoveAll(dir1) defer s1.Shutdown() codec := rpcClient(t, s1) defer codec.Close() testutil.WaitForLeader(t, s1.RPC, "dc1") ids := []string{} for i := 0; i < 5; i++ { arg := structs.ACLRequest{ Datacenter: "dc1", Op: structs.ACLSet, ACL: structs.ACL{ Name: "User token", Type: structs.ACLTypeClient, }, WriteRequest: structs.WriteRequest{Token: "root"}, } var out string if err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out); err != nil { t.Fatalf("err: %v", err) } ids = append(ids, out) } getR := structs.DCSpecificRequest{ Datacenter: "dc1", QueryOptions: structs.QueryOptions{Token: "root"}, } var acls structs.IndexedACLs if err := msgpackrpc.CallWithCodec(codec, "ACL.List", &getR, &acls); err != nil { t.Fatalf("err: %v", err) } if acls.Index == 0 { t.Fatalf("Bad: %v", acls) } // 5 + anonymous + master if len(acls.ACLs) != 7 { t.Fatalf("Bad: %v", acls.ACLs) } for i := 0; i < len(acls.ACLs); i++ { s := acls.ACLs[i] if s.ID == anonymousToken || s.ID == "root" { continue } if !lib.StrContains(ids, s.ID) { t.Fatalf("bad: %v", s) } if s.Name != "User token" { t.Fatalf("bad: %v", s) } } }
func TestSession_NodeSessions(t *testing.T) { dir1, s1 := testServer(t) defer os.RemoveAll(dir1) defer s1.Shutdown() codec := rpcClient(t, s1) defer codec.Close() testutil.WaitForLeader(t, s1.RPC, "dc1") s1.fsm.State().EnsureNode(1, &structs.Node{Node: "foo", Address: "127.0.0.1"}) s1.fsm.State().EnsureNode(1, &structs.Node{Node: "bar", Address: "127.0.0.1"}) ids := []string{} for i := 0; i < 10; i++ { arg := structs.SessionRequest{ Datacenter: "dc1", Op: structs.SessionCreate, Session: structs.Session{ Node: "bar", }, } if i < 5 { arg.Session.Node = "foo" } var out string if err := msgpackrpc.CallWithCodec(codec, "Session.Apply", &arg, &out); err != nil { t.Fatalf("err: %v", err) } if i < 5 { ids = append(ids, out) } } getR := structs.NodeSpecificRequest{ Datacenter: "dc1", Node: "foo", } var sessions structs.IndexedSessions if err := msgpackrpc.CallWithCodec(codec, "Session.NodeSessions", &getR, &sessions); err != nil { t.Fatalf("err: %v", err) } if sessions.Index == 0 { t.Fatalf("Bad: %v", sessions) } if len(sessions.Sessions) != 5 { t.Fatalf("Bad: %v", sessions.Sessions) } for i := 0; i < len(sessions.Sessions); i++ { s := sessions.Sessions[i] if !lib.StrContains(ids, s.ID) { t.Fatalf("bad: %v", s) } if s.Node != "foo" { t.Fatalf("bad: %v", s) } } }
func TestInternal_NodeInfo(t *testing.T) { dir1, s1 := testServer(t) defer os.RemoveAll(dir1) defer s1.Shutdown() codec := rpcClient(t, s1) defer codec.Close() testutil.WaitForLeader(t, s1.RPC, "dc1") arg := structs.RegisterRequest{ Datacenter: "dc1", Node: "foo", Address: "127.0.0.1", Service: &structs.NodeService{ ID: "db", Service: "db", Tags: []string{"master"}, }, Check: &structs.HealthCheck{ Name: "db connect", Status: structs.HealthPassing, ServiceID: "db", }, } var out struct{} if err := msgpackrpc.CallWithCodec(codec, "Catalog.Register", &arg, &out); err != nil { t.Fatalf("err: %v", err) } var out2 structs.IndexedNodeDump req := structs.NodeSpecificRequest{ Datacenter: "dc1", Node: "foo", } if err := msgpackrpc.CallWithCodec(codec, "Internal.NodeInfo", &req, &out2); err != nil { t.Fatalf("err: %v", err) } nodes := out2.Dump if len(nodes) != 1 { t.Fatalf("Bad: %v", nodes) } if nodes[0].Node != "foo" { t.Fatalf("Bad: %v", nodes[0]) } if !lib.StrContains(nodes[0].Services[0].Tags, "master") { t.Fatalf("Bad: %v", nodes[0]) } if nodes[0].Checks[0].Status != structs.HealthPassing { t.Fatalf("Bad: %v", nodes[0]) } }
func TestCatalog_NodeServices(t *testing.T) { dir1, s1 := testServer(t) defer os.RemoveAll(dir1) defer s1.Shutdown() codec := rpcClient(t, s1) defer codec.Close() args := structs.NodeSpecificRequest{ Datacenter: "dc1", Node: "foo", } var out structs.IndexedNodeServices err := msgpackrpc.CallWithCodec(codec, "Catalog.NodeServices", &args, &out) if err != nil { t.Fatalf("err: %v", err) } testutil.WaitForLeader(t, s1.RPC, "dc1") // Just add a node if err := s1.fsm.State().EnsureNode(1, &structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil { t.Fatalf("err: %v", err) } if err := s1.fsm.State().EnsureService(2, "foo", &structs.NodeService{ID: "db", Service: "db", Tags: []string{"primary"}, Address: "127.0.0.1", Port: 5000}); err != nil { t.Fatalf("err: %v", err) } if err := s1.fsm.State().EnsureService(3, "foo", &structs.NodeService{ID: "web", Service: "web", Tags: nil, Address: "127.0.0.1", Port: 80}); err != nil { t.Fatalf("err: %v", err) } if err := msgpackrpc.CallWithCodec(codec, "Catalog.NodeServices", &args, &out); err != nil { t.Fatalf("err: %v", err) } if out.NodeServices.Node.Address != "127.0.0.1" { t.Fatalf("bad: %v", out) } if len(out.NodeServices.Services) != 2 { t.Fatalf("bad: %v", out) } services := out.NodeServices.Services if !lib.StrContains(services["db"].Tags, "primary") || services["db"].Port != 5000 { t.Fatalf("bad: %v", out) } if len(services["web"].Tags) != 0 || services["web"].Port != 80 { t.Fatalf("bad: %v", out) } }
func TestDecodeConfig_Service(t *testing.T) { // Basics input := `{"service": {"id": "red1", "name": "redis", "tags": ["master"], "port":8000, "check": {"script": "/bin/check_redis", "interval": "10s", "ttl": "15s", "DeregisterCriticalServiceAfter": "90m" }}}` config, err := DecodeConfig(bytes.NewReader([]byte(input))) if err != nil { t.Fatalf("err: %s", err) } if len(config.Services) != 1 { t.Fatalf("missing service") } serv := config.Services[0] if serv.ID != "red1" { t.Fatalf("bad: %v", serv) } if serv.Name != "redis" { t.Fatalf("bad: %v", serv) } if !lib.StrContains(serv.Tags, "master") { t.Fatalf("bad: %v", serv) } if serv.Port != 8000 { t.Fatalf("bad: %v", serv) } if serv.Check.Script != "/bin/check_redis" { t.Fatalf("bad: %v", serv) } if serv.Check.Interval != 10*time.Second { t.Fatalf("bad: %v", serv) } if serv.Check.TTL != 15*time.Second { t.Fatalf("bad: %v", serv) } if serv.Check.DeregisterCriticalServiceAfter != 90*time.Minute { t.Fatalf("bad: %v", serv) } }
func TestInternal_NodeDump(t *testing.T) { dir1, s1 := testServer(t) defer os.RemoveAll(dir1) defer s1.Shutdown() codec := rpcClient(t, s1) defer codec.Close() testutil.WaitForLeader(t, s1.RPC, "dc1") arg := structs.RegisterRequest{ Datacenter: "dc1", Node: "foo", Address: "127.0.0.1", Service: &structs.NodeService{ ID: "db", Service: "db", Tags: []string{"master"}, }, Check: &structs.HealthCheck{ Name: "db connect", Status: structs.HealthPassing, ServiceID: "db", }, } var out struct{} if err := msgpackrpc.CallWithCodec(codec, "Catalog.Register", &arg, &out); err != nil { t.Fatalf("err: %v", err) } arg = structs.RegisterRequest{ Datacenter: "dc1", Node: "bar", Address: "127.0.0.2", Service: &structs.NodeService{ ID: "db", Service: "db", Tags: []string{"slave"}, }, Check: &structs.HealthCheck{ Name: "db connect", Status: structs.HealthWarning, ServiceID: "db", }, } if err := msgpackrpc.CallWithCodec(codec, "Catalog.Register", &arg, &out); err != nil { t.Fatalf("err: %v", err) } var out2 structs.IndexedNodeDump req := structs.DCSpecificRequest{ Datacenter: "dc1", } if err := msgpackrpc.CallWithCodec(codec, "Internal.NodeDump", &req, &out2); err != nil { t.Fatalf("err: %v", err) } nodes := out2.Dump if len(nodes) != 3 { t.Fatalf("Bad: %v", nodes) } var foundFoo, foundBar bool for _, node := range nodes { switch node.Node { case "foo": foundFoo = true if !lib.StrContains(node.Services[0].Tags, "master") { t.Fatalf("Bad: %v", nodes[0]) } if node.Checks[0].Status != structs.HealthPassing { t.Fatalf("Bad: %v", nodes[0]) } case "bar": foundBar = true if !lib.StrContains(node.Services[0].Tags, "slave") { t.Fatalf("Bad: %v", nodes[1]) } if node.Checks[0].Status != structs.HealthWarning { t.Fatalf("Bad: %v", nodes[1]) } default: continue } } if !foundFoo || !foundBar { t.Fatalf("missing foo or bar") } }
func TestSession_Renew(t *testing.T) { dir1, s1 := testServer(t) defer os.RemoveAll(dir1) defer s1.Shutdown() codec := rpcClient(t, s1) defer codec.Close() testutil.WaitForLeader(t, s1.RPC, "dc1") TTL := "10s" // the minimum allowed ttl ttl := 10 * time.Second s1.fsm.State().EnsureNode(1, &structs.Node{Node: "foo", Address: "127.0.0.1"}) ids := []string{} for i := 0; i < 5; i++ { arg := structs.SessionRequest{ Datacenter: "dc1", Op: structs.SessionCreate, Session: structs.Session{ Node: "foo", TTL: TTL, }, } var out string if err := msgpackrpc.CallWithCodec(codec, "Session.Apply", &arg, &out); err != nil { t.Fatalf("err: %v", err) } ids = append(ids, out) } // Verify the timer map is setup if len(s1.sessionTimers) != 5 { t.Fatalf("missing session timers") } getR := structs.DCSpecificRequest{ Datacenter: "dc1", } var sessions structs.IndexedSessions if err := msgpackrpc.CallWithCodec(codec, "Session.List", &getR, &sessions); err != nil { t.Fatalf("err: %v", err) } if sessions.Index == 0 { t.Fatalf("Bad: %v", sessions) } if len(sessions.Sessions) != 5 { t.Fatalf("Bad: %v", sessions.Sessions) } for i := 0; i < len(sessions.Sessions); i++ { s := sessions.Sessions[i] if !lib.StrContains(ids, s.ID) { t.Fatalf("bad: %v", s) } if s.Node != "foo" { t.Fatalf("bad: %v", s) } if s.TTL != TTL { t.Fatalf("bad session TTL: %s %v", s.TTL, s) } t.Logf("Created session '%s'", s.ID) } // Sleep for time shorter than internal destroy ttl time.Sleep(ttl * structs.SessionTTLMultiplier / 2) // renew 3 out of 5 sessions for i := 0; i < 3; i++ { renewR := structs.SessionSpecificRequest{ Datacenter: "dc1", Session: ids[i], } var session structs.IndexedSessions if err := msgpackrpc.CallWithCodec(codec, "Session.Renew", &renewR, &session); err != nil { t.Fatalf("err: %v", err) } if session.Index == 0 { t.Fatalf("Bad: %v", session) } if len(session.Sessions) != 1 { t.Fatalf("Bad: %v", session.Sessions) } s := session.Sessions[0] if !lib.StrContains(ids, s.ID) { t.Fatalf("bad: %v", s) } if s.Node != "foo" { t.Fatalf("bad: %v", s) } t.Logf("Renewed session '%s'", s.ID) } // now sleep for 2/3 the internal destroy TTL time for renewed sessions // which is more than the internal destroy TTL time for the non-renewed sessions time.Sleep((ttl * structs.SessionTTLMultiplier) * 2.0 / 3.0) var sessionsL1 structs.IndexedSessions if err := msgpackrpc.CallWithCodec(codec, "Session.List", &getR, &sessionsL1); err != nil { t.Fatalf("err: %v", err) } if sessionsL1.Index == 0 { t.Fatalf("Bad: %v", sessionsL1) } t.Logf("Expect 2 sessions to be destroyed") for i := 0; i < len(sessionsL1.Sessions); i++ { s := sessionsL1.Sessions[i] if !lib.StrContains(ids, s.ID) { t.Fatalf("bad: %v", s) } if s.Node != "foo" { t.Fatalf("bad: %v", s) } if s.TTL != TTL { t.Fatalf("bad: %v", s) } if i > 2 { t.Errorf("session '%s' should be destroyed", s.ID) } } if len(sessionsL1.Sessions) != 3 { t.Fatalf("Bad: %v", sessionsL1.Sessions) } // now sleep again for ttl*2 - no sessions should still be alive time.Sleep(ttl * structs.SessionTTLMultiplier) var sessionsL2 structs.IndexedSessions if err := msgpackrpc.CallWithCodec(codec, "Session.List", &getR, &sessionsL2); err != nil { t.Fatalf("err: %v", err) } if sessionsL2.Index == 0 { t.Fatalf("Bad: %v", sessionsL2) } if len(sessionsL2.Sessions) != 0 { for i := 0; i < len(sessionsL2.Sessions); i++ { s := sessionsL2.Sessions[i] if !lib.StrContains(ids, s.ID) { t.Fatalf("bad: %v", s) } if s.Node != "foo" { t.Fatalf("bad: %v", s) } if s.TTL != TTL { t.Fatalf("bad: %v", s) } t.Errorf("session '%s' should be destroyed", s.ID) } t.Fatalf("Bad: %v", sessionsL2.Sessions) } }
// DecodeConfig reads the configuration from the given reader in JSON // format and decodes it into a proper Config structure. func DecodeConfig(r io.Reader) (*Config, error) { var raw interface{} var result Config dec := json.NewDecoder(r) if err := dec.Decode(&raw); err != nil { return nil, err } // Check the result type if obj, ok := raw.(map[string]interface{}); ok { // Check for a "services", "service" or "check" key, meaning // this is actually a definition entry if sub, ok := obj["services"]; ok { if list, ok := sub.([]interface{}); ok { for _, srv := range list { service, err := DecodeServiceDefinition(srv) if err != nil { return nil, err } result.Services = append(result.Services, service) } } } if sub, ok := obj["service"]; ok { service, err := DecodeServiceDefinition(sub) if err != nil { return nil, err } result.Services = append(result.Services, service) } if sub, ok := obj["checks"]; ok { if list, ok := sub.([]interface{}); ok { for _, chk := range list { check, err := DecodeCheckDefinition(chk) if err != nil { return nil, err } result.Checks = append(result.Checks, check) } } } if sub, ok := obj["check"]; ok { check, err := DecodeCheckDefinition(sub) if err != nil { return nil, err } result.Checks = append(result.Checks, check) } // A little hacky but upgrades the old stats config directives to the new way if sub, ok := obj["statsd_addr"]; ok && result.Telemetry.StatsdAddr == "" { result.Telemetry.StatsdAddr = sub.(string) } if sub, ok := obj["statsite_addr"]; ok && result.Telemetry.StatsiteAddr == "" { result.Telemetry.StatsiteAddr = sub.(string) } if sub, ok := obj["statsite_prefix"]; ok && result.Telemetry.StatsitePrefix == "" { result.Telemetry.StatsitePrefix = sub.(string) } if sub, ok := obj["dogstatsd_addr"]; ok && result.Telemetry.DogStatsdAddr == "" { result.Telemetry.DogStatsdAddr = sub.(string) } if sub, ok := obj["dogstatsd_tags"].([]interface{}); ok && len(result.Telemetry.DogStatsdTags) == 0 { result.Telemetry.DogStatsdTags = make([]string, len(sub)) for i := range sub { result.Telemetry.DogStatsdTags[i] = sub[i].(string) } } } // Decode var md mapstructure.Metadata msdec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ Metadata: &md, Result: &result, }) if err != nil { return nil, err } if err := msdec.Decode(raw); err != nil { return nil, err } // Check unused fields and verify that no bad configuration options were // passed to Consul. There are a few additional fields which don't directly // use mapstructure decoding, so we need to account for those as well. allowedKeys := []string{ "service", "services", "check", "checks", "statsd_addr", "statsite_addr", "statsite_prefix", "dogstatsd_addr", "dogstatsd_tags", } var unused []string for _, field := range md.Unused { if !lib.StrContains(allowedKeys, field) { unused = append(unused, field) } } if len(unused) > 0 { return nil, fmt.Errorf("Config has invalid keys: %s", strings.Join(unused, ",")) } // Handle time conversions if raw := result.DNSConfig.NodeTTLRaw; raw != "" { dur, err := time.ParseDuration(raw) if err != nil { return nil, fmt.Errorf("NodeTTL invalid: %v", err) } result.DNSConfig.NodeTTL = dur } if raw := result.DNSConfig.MaxStaleRaw; raw != "" { dur, err := time.ParseDuration(raw) if err != nil { return nil, fmt.Errorf("MaxStale invalid: %v", err) } result.DNSConfig.MaxStale = dur } if len(result.DNSConfig.ServiceTTLRaw) != 0 { if result.DNSConfig.ServiceTTL == nil { result.DNSConfig.ServiceTTL = make(map[string]time.Duration) } for service, raw := range result.DNSConfig.ServiceTTLRaw { dur, err := time.ParseDuration(raw) if err != nil { return nil, fmt.Errorf("ServiceTTL %s invalid: %v", service, err) } result.DNSConfig.ServiceTTL[service] = dur } } if raw := result.CheckUpdateIntervalRaw; raw != "" { dur, err := time.ParseDuration(raw) if err != nil { return nil, fmt.Errorf("CheckUpdateInterval invalid: %v", err) } result.CheckUpdateInterval = dur } if raw := result.ACLTTLRaw; raw != "" { dur, err := time.ParseDuration(raw) if err != nil { return nil, fmt.Errorf("ACL TTL invalid: %v", err) } result.ACLTTL = dur } if raw := result.RetryIntervalRaw; raw != "" { dur, err := time.ParseDuration(raw) if err != nil { return nil, fmt.Errorf("RetryInterval invalid: %v", err) } result.RetryInterval = dur } if raw := result.RetryIntervalWanRaw; raw != "" { dur, err := time.ParseDuration(raw) if err != nil { return nil, fmt.Errorf("RetryIntervalWan invalid: %v", err) } result.RetryIntervalWan = dur } // Merge the single recursor if result.DNSRecursor != "" { result.DNSRecursors = append(result.DNSRecursors, result.DNSRecursor) } if raw := result.SessionTTLMinRaw; raw != "" { dur, err := time.ParseDuration(raw) if err != nil { return nil, fmt.Errorf("Session TTL Min invalid: %v", err) } result.SessionTTLMin = dur } if result.AdvertiseAddrs.SerfLanRaw != "" { addr, err := net.ResolveTCPAddr("tcp", result.AdvertiseAddrs.SerfLanRaw) if err != nil { return nil, fmt.Errorf("AdvertiseAddrs.SerfLan is invalid: %v", err) } result.AdvertiseAddrs.SerfLan = addr } if result.AdvertiseAddrs.SerfWanRaw != "" { addr, err := net.ResolveTCPAddr("tcp", result.AdvertiseAddrs.SerfWanRaw) if err != nil { return nil, fmt.Errorf("AdvertiseAddrs.SerfWan is invalid: %v", err) } result.AdvertiseAddrs.SerfWan = addr } if result.AdvertiseAddrs.RPCRaw != "" { addr, err := net.ResolveTCPAddr("tcp", result.AdvertiseAddrs.RPCRaw) if err != nil { return nil, fmt.Errorf("AdvertiseAddrs.RPC is invalid: %v", err) } result.AdvertiseAddrs.RPC = addr } return &result, nil }
func TestFSM_SnapshotRestore(t *testing.T) { fsm, err := NewFSM(nil, os.Stderr) if err != nil { t.Fatalf("err: %v", err) } // Add some state fsm.state.EnsureNode(1, &structs.Node{Node: "foo", Address: "127.0.0.1"}) fsm.state.EnsureNode(2, &structs.Node{Node: "baz", Address: "127.0.0.2", TaggedAddresses: map[string]string{"hello": "1.2.3.4"}}) fsm.state.EnsureService(3, "foo", &structs.NodeService{ID: "web", Service: "web", Tags: nil, Address: "127.0.0.1", Port: 80}) fsm.state.EnsureService(4, "foo", &structs.NodeService{ID: "db", Service: "db", Tags: []string{"primary"}, Address: "127.0.0.1", Port: 5000}) fsm.state.EnsureService(5, "baz", &structs.NodeService{ID: "web", Service: "web", Tags: nil, Address: "127.0.0.2", Port: 80}) fsm.state.EnsureService(6, "baz", &structs.NodeService{ID: "db", Service: "db", Tags: []string{"secondary"}, Address: "127.0.0.2", Port: 5000}) fsm.state.EnsureCheck(7, &structs.HealthCheck{ Node: "foo", CheckID: "web", Name: "web connectivity", Status: structs.HealthPassing, ServiceID: "web", }) fsm.state.KVSSet(8, &structs.DirEntry{ Key: "/test", Value: []byte("foo"), }) session := &structs.Session{ID: generateUUID(), Node: "foo"} fsm.state.SessionCreate(9, session) acl := &structs.ACL{ID: generateUUID(), Name: "User Token"} fsm.state.ACLSet(10, acl) fsm.state.KVSSet(11, &structs.DirEntry{ Key: "/remove", Value: []byte("foo"), }) fsm.state.KVSDelete(12, "/remove") idx, _, err := fsm.state.KVSList("/remove") if err != nil { t.Fatalf("err: %s", err) } if idx != 12 { t.Fatalf("bad index: %d", idx) } updates := structs.Coordinates{ &structs.Coordinate{ Node: "baz", Coord: generateRandomCoordinate(), }, &structs.Coordinate{ Node: "foo", Coord: generateRandomCoordinate(), }, } if err := fsm.state.CoordinateBatchUpdate(13, updates); err != nil { t.Fatalf("err: %s", err) } query := structs.PreparedQuery{ ID: generateUUID(), Service: structs.ServiceQuery{ Service: "web", }, RaftIndex: structs.RaftIndex{ CreateIndex: 14, ModifyIndex: 14, }, } if err := fsm.state.PreparedQuerySet(14, &query); err != nil { t.Fatalf("err: %s", err) } // Snapshot snap, err := fsm.Snapshot() if err != nil { t.Fatalf("err: %v", err) } defer snap.Release() // Persist buf := bytes.NewBuffer(nil) sink := &MockSink{buf, false} if err := snap.Persist(sink); err != nil { t.Fatalf("err: %v", err) } // Try to restore on a new FSM fsm2, err := NewFSM(nil, os.Stderr) if err != nil { t.Fatalf("err: %v", err) } // Do a restore if err := fsm2.Restore(sink); err != nil { t.Fatalf("err: %v", err) } // Verify the contents _, nodes, err := fsm2.state.Nodes() if err != nil { t.Fatalf("err: %s", err) } if len(nodes) != 2 { t.Fatalf("bad: %v", nodes) } if nodes[0].Node != "baz" || nodes[0].Address != "127.0.0.2" || len(nodes[0].TaggedAddresses) != 1 || nodes[0].TaggedAddresses["hello"] != "1.2.3.4" { t.Fatalf("bad: %v", nodes[0]) } if nodes[1].Node != "foo" || nodes[1].Address != "127.0.0.1" || len(nodes[1].TaggedAddresses) != 0 { t.Fatalf("bad: %v", nodes[1]) } _, fooSrv, err := fsm2.state.NodeServices("foo") if err != nil { t.Fatalf("err: %s", err) } if len(fooSrv.Services) != 2 { t.Fatalf("Bad: %v", fooSrv) } if !lib.StrContains(fooSrv.Services["db"].Tags, "primary") { t.Fatalf("Bad: %v", fooSrv) } if fooSrv.Services["db"].Port != 5000 { t.Fatalf("Bad: %v", fooSrv) } _, checks, err := fsm2.state.NodeChecks("foo") if err != nil { t.Fatalf("err: %s", err) } if len(checks) != 1 { t.Fatalf("Bad: %v", checks) } // Verify key is set _, d, err := fsm2.state.KVSGet("/test") if err != nil { t.Fatalf("err: %v", err) } if string(d.Value) != "foo" { t.Fatalf("bad: %v", d) } // Verify session is restored idx, s, err := fsm2.state.SessionGet(session.ID) if err != nil { t.Fatalf("err: %v", err) } if s.Node != "foo" { t.Fatalf("bad: %v", s) } if idx <= 1 { t.Fatalf("bad index: %d", idx) } // Verify ACL is restored _, a, err := fsm2.state.ACLGet(acl.ID) if err != nil { t.Fatalf("err: %v", err) } if a.Name != "User Token" { t.Fatalf("bad: %v", a) } if a.ModifyIndex <= 1 { t.Fatalf("bad index: %d", idx) } // Verify tombstones are restored func() { snap := fsm2.state.Snapshot() defer snap.Close() stones, err := snap.Tombstones() if err != nil { t.Fatalf("err: %s", err) } stone := stones.Next().(*state.Tombstone) if stone == nil { t.Fatalf("missing tombstone") } if stone.Key != "/remove" || stone.Index != 12 { t.Fatalf("bad: %v", stone) } if stones.Next() != nil { t.Fatalf("unexpected extra tombstones") } }() // Verify coordinates are restored _, coords, err := fsm2.state.Coordinates() if err != nil { t.Fatalf("err: %s", err) } if !reflect.DeepEqual(coords, updates) { t.Fatalf("bad: %#v", coords) } // Verify queries are restored. _, queries, err := fsm2.state.PreparedQueryList() if err != nil { t.Fatalf("err: %s", err) } if len(queries) != 1 { t.Fatalf("bad: %#v", queries) } if !reflect.DeepEqual(queries[0], &query) { t.Fatalf("bad: %#v", queries[0]) } }
// Register is used to upsert a job for scheduling func (j *Job) Register(args *structs.JobRegisterRequest, reply *structs.JobRegisterResponse) error { if done, err := j.srv.forward("Job.Register", args, args, reply); done { return err } defer metrics.MeasureSince([]string{"nomad", "job", "register"}, time.Now()) // Validate the arguments if args.Job == nil { return fmt.Errorf("missing job for registration") } // Initialize the job fields (sets defaults and any necessary init work). args.Job.Canonicalize() // Validate the job. if err := validateJob(args.Job); err != nil { return err } if args.EnforceIndex { // Lookup the job snap, err := j.srv.fsm.State().Snapshot() if err != nil { return err } job, err := snap.JobByID(args.Job.ID) if err != nil { return err } jmi := args.JobModifyIndex if job != nil { if jmi == 0 { return fmt.Errorf("%s 0: job already exists", RegisterEnforceIndexErrPrefix) } else if jmi != job.JobModifyIndex { return fmt.Errorf("%s %d: job exists with conflicting job modify index: %d", RegisterEnforceIndexErrPrefix, jmi, job.JobModifyIndex) } } else if jmi != 0 { return fmt.Errorf("%s %d: job does not exist", RegisterEnforceIndexErrPrefix, jmi) } } // Ensure that the job has permissions for the requested Vault tokens policies := args.Job.VaultPolicies() if len(policies) != 0 { vconf := j.srv.config.VaultConfig if !vconf.IsEnabled() { return fmt.Errorf("Vault not enabled and Vault policies requested") } // Have to check if the user has permissions if !vconf.AllowsUnauthenticated() { if args.Job.VaultToken == "" { return fmt.Errorf("Vault policies requested but missing Vault Token") } vault := j.srv.vault s, err := vault.LookupToken(context.Background(), args.Job.VaultToken) if err != nil { return err } allowedPolicies, err := PoliciesFrom(s) if err != nil { return err } // If we are given a root token it can access all policies if !lib.StrContains(allowedPolicies, "root") { flatPolicies := structs.VaultPoliciesSet(policies) subset, offending := structs.SliceStringIsSubset(allowedPolicies, flatPolicies) if !subset { return fmt.Errorf("Passed Vault Token doesn't allow access to the following policies: %s", strings.Join(offending, ", ")) } } } // Add implicit constraints that the task groups are run on a Node with // Vault for _, tg := range args.Job.TaskGroups { _, ok := policies[tg.Name] if !ok { // Not requesting Vault continue } found := false for _, c := range tg.Constraints { if c.Equal(vaultConstraint) { found = true break } } if !found { tg.Constraints = append(tg.Constraints, vaultConstraint) } } } // Clear the Vault token args.Job.VaultToken = "" // Commit this update via Raft _, index, err := j.srv.raftApply(structs.JobRegisterRequestType, args) if err != nil { j.srv.logger.Printf("[ERR] nomad.job: Register failed: %v", err) return err } // Populate the reply with job information reply.JobModifyIndex = index // If the job is periodic, we don't create an eval. if args.Job.IsPeriodic() { return nil } // Create a new evaluation eval := &structs.Evaluation{ ID: structs.GenerateUUID(), Priority: args.Job.Priority, Type: args.Job.Type, TriggeredBy: structs.EvalTriggerJobRegister, JobID: args.Job.ID, JobModifyIndex: index, Status: structs.EvalStatusPending, } update := &structs.EvalUpdateRequest{ Evals: []*structs.Evaluation{eval}, WriteRequest: structs.WriteRequest{Region: args.Region}, } // Commit this evaluation via Raft // XXX: There is a risk of partial failure where the JobRegister succeeds // but that the EvalUpdate does not. _, evalIndex, err := j.srv.raftApply(structs.EvalUpdateRequestType, update) if err != nil { j.srv.logger.Printf("[ERR] nomad.job: Eval create failed: %v", err) return err } // Populate the reply with eval information reply.EvalID = eval.ID reply.EvalCreateIndex = evalIndex reply.Index = evalIndex return nil }