func newFakeRegistryForSsh() registry.Registry { machines := []machine.MachineState{ {"c31e44e1-f858-436e-933e-59c642517860", "1.2.3.4", map[string]string{"ping": "pong"}, "", resource.ResourceTuple{}}, {"595989bb-cbb7-49ce-8726-722d6e157b4e", "5.6.7.8", map[string]string{"foo": "bar"}, "", resource.ResourceTuple{}}, {"hello.service", "8.7.6.5", map[string]string{"foo": "bar"}, "", resource.ResourceTuple{}}, } jobs := []job.Job{ *job.NewJob("j1.service", unit.Unit{}), *job.NewJob("j2.service", unit.Unit{}), *job.NewJob("hello.service", unit.Unit{}), } states := map[string]*unit.UnitState{ "j1.service": unit.NewUnitState("loaded", "active", "listening", &machines[0]), "j2.service": unit.NewUnitState("loaded", "inactive", "dead", &machines[1]), "hello.service": unit.NewUnitState("loaded", "inactive", "dead", &machines[2]), } reg := registry.NewFakeRegistry() reg.SetMachines(machines) reg.SetUnitStates(states) reg.SetJobs(jobs) return reg }
func newFakeRegistryForSsh() registry.Registry { // clear machineStates for every invocation machineStates = nil machines := []machine.MachineState{ newMachineState("c31e44e1-f858-436e-933e-59c642517860", "1.2.3.4", map[string]string{"ping": "pong"}), newMachineState("595989bb-cbb7-49ce-8726-722d6e157b4e", "5.6.7.8", map[string]string{"foo": "bar"}), newMachineState("hello.service", "8.7.6.5", map[string]string{"foo": "bar"}), } jobs := []job.Job{ *job.NewJob("j1.service", unit.Unit{}), *job.NewJob("j2.service", unit.Unit{}), *job.NewJob("hello.service", unit.Unit{}), } states := map[string]*unit.UnitState{ "j1.service": unit.NewUnitState("loaded", "active", "listening", machines[0].ID), "j2.service": unit.NewUnitState("loaded", "inactive", "dead", machines[1].ID), "hello.service": unit.NewUnitState("loaded", "inactive", "dead", machines[2].ID), } reg := registry.NewFakeRegistry() reg.SetMachines(machines) reg.SetUnitStates(states) reg.SetJobs(jobs) return reg }
// Assert that jobs and their peers are properly indexed func TestGetJobsByPeer(t *testing.T) { state := NewState() u1 := unit.NewUnit(`[X-Fleet] X-ConditionMachineOf=b X-ConditionMachineOf=c `) j1 := job.NewJob("a", *u1) state.TrackJob(j1) u2 := unit.NewUnit(`[X-Fleet] X-ConditionMachineOf=c `) j2 := job.NewJob("d", *u2) state.TrackJob(j2) peers := state.GetJobsByPeer("b") if len(peers) != 1 || peers[0] != "a" { t.Fatalf("Unexpected index of job peers %v", peers) } peers = state.GetJobsByPeer("c") if len(peers) != 2 || peers[0] != "a" || peers[1] != "d" { t.Fatalf("Unexpected index of job peers %v", peers) } }
func (r *EtcdRegistry) getJobFromModel(jm jobModel) *job.Job { var err error var unit *unit.Unit // New-style Jobs should have a populated UnitHash, and the contents of the Unit are stored separately in the Registry if !jm.UnitHash.Empty() { unit = r.getUnitByHash(jm.UnitHash) if unit == nil { log.Warningf("No Unit found in Registry for Job(%s)", jm.Name) return nil } if unit.Hash() != jm.UnitHash { log.Errorf("Unit Hash %s does not match expected %s for Job(%s)!", unit.Hash(), jm.UnitHash, jm.Name) return nil } } else { // Old-style Jobs had "Payloads" instead of Units, also stored separately in the Registry unit, err = r.getUnitFromLegacyPayload(jm.Name) if err != nil { log.Errorf("Error retrieving legacy payload for Job(%s)", jm.Name) return nil } else if unit == nil { log.Warningf("No Payload found in Registry for Job(%s)", jm.Name) return nil } log.Infof("Migrating legacy Payload(%s)", jm.Name) if err := r.storeOrGetUnit(*unit); err != nil { log.Warningf("Unable to migrate legacy Payload: %v", err) } } return job.NewJob(jm.Name, *unit) }
func TestSignJob(t *testing.T) { c, _ := initSign(t) u := unit.NewUnit("Echo") j := job.NewJob("echo.service", *u) data, err := marshal(u) if err != nil { t.Fatal("marshal error:", err) } expectedSig, err := c.keyring.Sign(testPublicKeys["rsa"], data) if err != nil { t.Fatal("sign error:", err) } s, err := c.SignJob(j) if err != nil { t.Fatal("sign payload error:", err) } if s.Tag != TagForJob("echo.service") { t.Fatal("sign tag error:", err) } if len(s.Signatures) != 1 { t.Fatal("expect 1 signature instead of", len(s.Signatures)) } if bytes.Compare(s.Signatures[0].Blob, expectedSig.Blob) != 0 { t.Fatal("wrong signature") } }
func newTestJobWithMachineMetadata(metadata string) *job.Job { contents := fmt.Sprintf(` [X-Fleet] %s `, metadata) return job.NewJob("pong.service", *unit.NewUnit(contents)) }
func TestFakeRegistryJobLifecycle(t *testing.T) { reg := NewFakeRegistry() jobs, err := reg.Jobs() if err != nil { t.Fatalf("Received error while calling Jobs: %v", err) } if !reflect.DeepEqual([]job.Job{}, jobs) { t.Fatalf("Expected no jobs, got %v", jobs) } u, _ := unit.NewUnit("") j1 := job.NewJob("job1.service", *u) err = reg.CreateJob(j1) if err != nil { t.Fatalf("Received error while calling CreateJob: %v", err) } jobs, err = reg.Jobs() if err != nil { t.Fatalf("Received error while calling Jobs: %v", err) } if len(jobs) != 1 { t.Fatalf("Expected 1 Job, got %v", jobs) } if jobs[0].Name != "job1.service" { t.Fatalf("Expected Job with name \"job1.service\", got %q", jobs[0].Name) } err = reg.ScheduleJob("job1.service", "XXX") if err != nil { t.Fatalf("Received error while calling ScheduleJob: %v", err) } j, err := reg.Job("job1.service") if err != nil { t.Fatalf("Received error while calling JobTarget: %v", err) } if j.TargetMachineID != "XXX" { t.Fatalf("Job should be scheduled to XXX, got %v", j.TargetMachineID) } err = reg.DestroyJob("job1.service") if err != nil { t.Fatalf("Received error while calling DestroyJob: %v", err) } jobs, err = reg.Jobs() if err != nil { t.Fatalf("Received error while calling Jobs: %v", err) } if !reflect.DeepEqual([]job.Job{}, jobs) { t.Fatalf("Expected no jobs, got %v", jobs) } }
func TestListUnitsFieldsToStrings(t *testing.T) { j := job.NewJob("test", *unit.NewUnit("")) for _, tt := range []string{"state", "load", "active", "sub", "desc", "machine"} { f := listUnitsFields[tt](j, false) assertEqual(t, tt, "-", f) } f := listUnitsFields["unit"](j, false) assertEqual(t, "unit", "test", f) j = job.NewJob("test", *unit.NewUnit(`[Unit] Description=some description`)) d := listUnitsFields["desc"](j, false) assertEqual(t, "desc", "some description", d) for _, state := range []job.JobState{job.JobStateLoaded, job.JobStateInactive, job.JobStateLaunched} { j.State = &state f := listUnitsFields["state"](j, false) assertEqual(t, "state", string(state), f) } j.UnitState = unit.NewUnitState("foo", "bar", "baz", nil) for k, want := range map[string]string{ "load": "foo", "active": "bar", "sub": "baz", "machine": "-", } { got := listUnitsFields[k](j, false) assertEqual(t, k, want, got) } j.UnitState.MachineState = &machine.MachineState{"some-id", "1.2.3.4", nil, "", resource.ResourceTuple{}} ms := listUnitsFields["machine"](j, true) assertEqual(t, "machine", "some-id/1.2.3.4", ms) uh := "f035b2f14edc4d23572e5f3d3d4cb4f78d0e53c3" fuh := listUnitsFields["hash"](j, true) suh := listUnitsFields["hash"](j, false) assertEqual(t, "hash", uh, fuh) assertEqual(t, "hash", uh[:7], suh) }
func createJob(jobName string, unit *unit.Unit) (*job.Job, error) { j := job.NewJob(jobName, *unit) if err := registryCtl.CreateJob(j); err != nil { return nil, fmt.Errorf("failed creating job %s: %v", j.Name, err) } log.V(1).Infof("Created Job(%s) in Registry", j.Name) return j, nil }
func newTestJobFromUnitContents(t *testing.T, name, contents string) *job.Job { u, err := unit.NewUnitFile(contents) if err != nil { t.Fatalf("error creating Unit from %q: %v", contents, err) } j := job.NewJob(name, *u) if j == nil { t.Fatalf("error creating Job %q from %q", name, u) } return j }
func newTestRegistryForListUnits(jobs []job.Job) registry.Registry { j := []job.Job{*job.NewJob("pong.service", *unit.NewUnit("Echo"))} if jobs != nil { for _, job := range jobs { j = append(j, job) } } return TestRegistry{jobs: j} }
func newTestRegistryForSsh() registry.Registry { machines := []machine.MachineState{ machine.MachineState{"c31e44e1-f858-436e-933e-59c642517860", "1.2.3.4", map[string]string{"ping": "pong"}, ""}, machine.MachineState{"595989bb-cbb7-49ce-8726-722d6e157b4e", "5.6.7.8", map[string]string{"foo": "bar"}, ""}, machine.MachineState{"hello.service", "8.7.6.5", map[string]string{"foo": "bar"}, ""}, } jobs := []job.Job{ *job.NewJob("j1.service", unit.Unit{}), *job.NewJob("j2.service", unit.Unit{}), *job.NewJob("hello.service", unit.Unit{}), } states := map[string]*unit.UnitState{ "j1.service": unit.NewUnitState("loaded", "active", "listening", &machines[0]), "j2.service": unit.NewUnitState("loaded", "inactive", "dead", &machines[1]), "hello.service": unit.NewUnitState("loaded", "inactive", "dead", &machines[2]), } return TestRegistry{machines: machines, jobStates: states, jobs: jobs} }
func TestAbleToRunConditionMachineIDMatch(t *testing.T) { u := unit.NewUnit(`[X-Fleet] X-ConditionMachineID=XYZ `) job := job.NewJob("example.service", *u) mach := &machine.FakeMachine{machine.MachineState{ID: "XYZ"}} agent := Agent{Machine: mach, state: NewState()} if !agent.ableToRun(job) { t.Fatalf("Agent should be able to run job") } }
func TestAbleToRunConditionMachineIDMismatch(t *testing.T) { u := unit.NewUnit(`[X-Fleet] X-ConditionMachineID=XYZ `) job := job.NewJob("example.service", *u) mach := machine.New(machine.MachineState{ID: "123"}) agent := Agent{machine: mach, state: NewState()} if agent.AbleToRun(job) { t.Fatalf("Agent should not be able to run job") } }
func TestAbleToRunConditionMachineBootIDMismatch(t *testing.T) { uf := unit.NewSystemdUnitFile(`[X-Fleet] X-ConditionMachineBootID=XYZ `) payload := job.NewJobPayload("example.service", *uf) job := job.NewJob("example.service", make(map[string][]string, 0), payload, nil) mach := machine.New("123", "", make(map[string]string, 0)) agent := Agent{machine: mach, state: NewState()} if agent.AbleToRun(job) { t.Fatalf("Agent should not be able to run job") } }
// Assert that no jobs are returned for unknown peers func TestGetJobsByPeerUnknown(t *testing.T) { u := unit.NewUnit(`[X-Fleet] X-ConditionMachineOf=b `) j := job.NewJob("a", *u) state := NewState() state.TrackJob(j) peers := state.GetJobsByPeer("c") if len(peers) != 0 { t.Fatalf("Unexpected index of job peers %v", peers) } }
func newFakeRegistryForListUnits(jobs []job.Job) registry.Registry { j := []job.Job{*job.NewJob("pong.service", *unit.NewUnit("Echo"))} if jobs != nil { for _, job := range jobs { j = append(j, job) } } reg := registry.NewFakeRegistry() reg.SetJobs(j) return reg }
// Assert that existing jobs and potential jobs that do not conflict do not // trigger a match func TestHasConflictNoMatch(t *testing.T) { state := NewState() u := unit.NewUnit(`[X-Fleet]`) j := job.NewJob("example.service", *u) state.TrackJob(j) state.SetTargetState(j.Name, job.JobStateLoaded) agent := Agent{state: state} matched, name := agent.HasConflict("other.service", []string{}) if matched { t.Errorf("Expected no match, but got conflict with %s", name) } }
func (ur *unitsResource) create(rw http.ResponseWriter, item string, ds job.JobState, u *unit.Unit) { j := job.NewJob(item, *u) if err := ur.reg.CreateJob(j); err != nil { log.Errorf("Failed creating Job(%s) in Registry: %v", j.Name, err) sendError(rw, http.StatusInternalServerError, nil) return } if err := ur.reg.SetJobTargetState(j.Name, ds); err != nil { log.Errorf("Failed setting target state of Job(%s): %v", j.Name, err) sendError(rw, http.StatusInternalServerError, nil) return } rw.WriteHeader(http.StatusNoContent) }
func TestJobDescription(t *testing.T) { contents := `[Unit] Description=PING ` jp := job.NewJobPayload("ping.service", *unit.NewSystemdUnitFile(contents)) j := []job.Job{*job.NewJob("ping.service", map[string][]string{}, jp, nil)} registryCtl = newTestRegistryForListUnits(nil, j) names, _ := findAllUnits() if len(names) != 3 { t.Errorf("Expected to find three units: %v\n", names) } if names["ping.service"] != "PING" { t.Errorf("Expected to have `PING` as a description, but it was %s\n", names["ping.service"]) } }
// Assert that a potential conflict is triggered against the existing job name func TestHasConflictPotentialMatch(t *testing.T) { state := NewState() u := unit.NewUnit(`[X-Fleet]`) j := job.NewJob("example.service", *u) state.TrackJob(j) state.SetTargetState(j.Name, job.JobStateLoaded) agent := Agent{state: state} matched, name := agent.HasConflict("other.service", []string{"example.service"}) if !matched { t.Errorf("Expected conflict with 'example.service', no conflict reported") } else if name != "example.service" { t.Errorf("Expected conflict with 'example.service', but conflict found with %s", name) } }
func TestHasConflictIgnoresBids(t *testing.T) { state := NewState() u := unit.NewUnit(`[X-Fleet] X-Conflicts=other.service `) j := job.NewJob("example.service", *u) state.TrackJob(j) state.TrackBid(j.Name) agent := Agent{state: state} matched, name := agent.HasConflict("other.service", []string{}) if matched { t.Errorf("Expected no conflict, but got conflict with %s", name) } }
func (r *EtcdRegistry) getJobFromObjectNode(node *etcd.Node) (*job.Job, error) { var err error var jm jobModel if err = unmarshal(node.Value, &jm); err != nil { return nil, err } var unit *unit.Unit // New-style Jobs should have a populated UnitHash, and the contents of the Unit are stored separately in the Registry if !jm.UnitHash.Empty() { unit = r.getUnitByHash(jm.UnitHash) if unit == nil { log.Warningf("No Unit found in Registry for Job(%s)", jm.Name) return nil, nil } if unit.Hash() != jm.UnitHash { log.Errorf("Unit Hash %s does not match expected %s for Job(%s)!", unit.Hash(), jm.UnitHash, jm.Name) return nil, nil } } else { // Old-style Jobs had "Payloads" instead of Units, also stored separately in the Registry unit, err = r.getUnitFromLegacyPayload(jm.Name) if err != nil { log.Errorf("Error retrieving legacy payload for Job(%s)", jm.Name) return nil, nil } else if unit == nil { log.Warningf("No Payload found in Registry for Job(%s)", jm.Name) return nil, nil } log.Infof("Migrating legacy Payload(%s)", jm.Name) if err := r.storeOrGetUnit(*unit); err != nil { log.Warningf("Unable to migrate legacy Payload: %v", err) } jm.UnitHash = unit.Hash() log.Infof("Updating Job(%s) with legacy payload Hash(%s)", jm.Name, jm.UnitHash) if err := r.updateJobObjectNode(&jm, node.ModifiedIndex); err != nil { log.Warningf("Unable to update Job(%s) with legacy payload Hash(%s): %v", jm.Name, jm.UnitHash, err) } } return job.NewJob(jm.Name, *unit), nil }
func TestJobDescription(t *testing.T) { contents := `[Unit] Description=PING ` j := []job.Job{*job.NewJob("ping.service", *unit.NewUnit(contents))} registryCtl = newFakeRegistryForListUnits(j) jobs, _, _ := findAllUnits() if len(jobs) != 2 { t.Errorf("Expected to find two units: %v\n", jobs) } ping := jobs["ping.service"] desc := ping.Unit.Description() if desc != "PING" { t.Errorf("Expected to have `PING` as a description, but it was %s\n", desc) } }
func newTestRegistryForListUnits(payloads []job.JobPayload, jobs []job.Job) Registry { j := []job.Job{*job.NewJob("pong.service", map[string][]string{}, nil, nil)} p := []job.JobPayload{*job.NewJobPayload("echo.service", *unit.NewSystemdUnitFile("Echo"))} if payloads != nil { for _, jp := range payloads { p = append(p, jp) } } if jobs != nil { for _, job := range jobs { j = append(j, job) } } return TestRegistry{jobs: j, payloads: p} }
func TestFakeRegistryJobLifecycle(t *testing.T) { reg := NewFakeRegistry() jobs, err := reg.GetAllJobs() if err != nil { t.Fatalf("Received error while calling GetAllJobs: %v", err) } if !reflect.DeepEqual([]job.Job{}, jobs) { t.Fatalf("Expected no jobs, got %v", jobs) } j1 := job.NewJob("job1.service", *unit.NewUnit("")) err = reg.CreateJob(j1) if err != nil { t.Fatalf("Received error while calling CreateJob: %v", err) } jobs, err = reg.GetAllJobs() if err != nil { t.Fatalf("Received error while calling GetAllJobs: %v", err) } if len(jobs) != 1 { t.Fatalf("Expected 1 Job, got %v", jobs) } if jobs[0].Name != "job1.service" { t.Fatalf("Expected Job with name \"job1.service\", got %q", jobs[0].Name) } err = reg.DestroyJob("job1.service") if err != nil { t.Fatalf("Received error while calling DestroyJob: %v", err) } jobs, err = reg.GetAllJobs() if err != nil { t.Fatalf("Received error while calling GetAllJobs: %v", err) } if !reflect.DeepEqual([]job.Job{}, jobs) { t.Fatalf("Expected no jobs, got %v", jobs) } }
func TestVerifyJob(t *testing.T) { c, v := initSign(t) u, err := unit.NewUnit("Echo") if err != nil { t.Fatalf("unexpected error creating new unit: %v", err) } j := job.NewJob("echo.service", *u) data, err := marshal(u) if err != nil { t.Fatal("marshal error:", err) } v.pubkeys = append(v.pubkeys, testPublicKeys["rsa"]) signature, err := c.keyring.Sign(testPublicKeys["rsa"], data) if err != nil { t.Fatal("sign error:", err) } ss := &SignatureSet{TagForJob("echo.service"), []*gossh.Signature{signature}} ok, err := v.VerifyJob(j, ss) if err != nil { t.Fatal("error verifying job:", err) } if !ok { t.Fatal("job verification failed") } ss.Tag = "" ok, err = v.VerifyJob(j, ss) if err == nil || ok == true { t.Fatal("should fail on job verification") } ok, err = v.VerifyJob(j, nil) if err == nil || ok == true { t.Fatal("should fail on job verification") } }
func startUnitAction(c *cli.Context) { var err error r := getRegistry() payloads := make([]job.JobPayload, len(c.Args())) for i, v := range c.Args() { name := path.Base(v) payload := r.GetPayload(name) if payload == nil { payload, err = getJobPayloadFromFile(v) if err != nil { fmt.Println(err.Error()) return } err = r.CreatePayload(payload) if err != nil { fmt.Printf("Creation of payload %s failed: %v\n", payload.Name, err) return } } payloads[i] = *payload } requirements := parseRequirements(c.String("require")) // TODO: This must be done in a transaction! for _, jp := range payloads { j := job.NewJob(jp.Name, requirements, &jp, nil) err := r.CreateJob(j) if err != nil { fmt.Printf("Creation of job %s failed: %v\n", j.Name, err) } } }
func startUnitAction(c *cli.Context) { var err error // If signing is explicitly set to on, verification will be done also. toSign := c.Bool("sign") var sc *sign.SignatureCreator var sv *sign.SignatureVerifier if toSign { var err error sc, err = sign.NewSignatureCreatorFromSSHAgent() if err != nil { fmt.Println("Fail to create SignatureCreator:", err) return } sv, err = sign.NewSignatureVerifierFromSSHAgent() if err != nil { fmt.Println("Fail to create SignatureVerifier:", err) return } } payloads := make([]job.JobPayload, len(c.Args())) for i, v := range c.Args() { name := path.Base(v) payload := registryCtl.GetPayload(name) if payload == nil { payload, err = getJobPayloadFromFile(v) if err != nil { fmt.Println(err.Error()) return } err = registryCtl.CreatePayload(payload) if err != nil { fmt.Printf("Creation of payload %s failed: %v\n", payload.Name, err) return } if toSign { s, err := sc.SignPayload(payload) if err != nil { fmt.Printf("Creation of sign for payload %s failed: %v\n", payload.Name, err) return } registryCtl.CreateSignatureSet(s) } } if toSign { s := registryCtl.GetSignatureSetOfPayload(name) ok, err := sv.VerifyPayload(payload, s) if !ok || err != nil { fmt.Printf("Check of payload %s failed: %v\n", payload.Name, err) return } } payloads[i] = *payload } requirements := parseRequirements(c.String("require")) // TODO: This must be done in a transaction! for _, jp := range payloads { j := job.NewJob(jp.Name, requirements, &jp, nil) err := registryCtl.CreateJob(j) if err != nil { fmt.Printf("Creation of job %s failed: %v\n", j.Name, err) } } }
func TestSystemdUnitFlow(t *testing.T) { uDir, err := ioutil.TempDir("", "fleet-") if err != nil { t.Fatalf("Failed creating tempdir: %v", err) } defer os.RemoveAll(uDir) mgr, err := systemd.NewSystemdUnitManager(uDir, false) if err != nil { t.Fatalf("Failed initializing SystemdUnitManager: %v", err) } units, err := mgr.Units() if err != nil { t.Fatalf("Failed calling Units(): %v", err) } if len(units) > 0 { t.Fatalf("Expected no units to be returned, got %v", units) } contents := `[Service] ExecStart=/usr/bin/sleep 3000 ` name := fmt.Sprintf("fleet-unit-%d.service", rand.Int63()) uf, err := unit.NewUnitFile(contents) if err != nil { t.Fatalf("Invalid unit file: %v", err) } hash := uf.Hash().String() j := job.NewJob(name, *uf) if err := mgr.Load(j.Name, j.Unit); err != nil { t.Fatalf("Failed loading job: %v", err) } units, err = mgr.Units() if err != nil { t.Fatalf("Failed calling Units(): %v", err) } if !reflect.DeepEqual([]string{name}, units) { t.Fatalf("Expected [hello.service], got %v", units) } err = waitForUnitState(mgr, name, unit.UnitState{"loaded", "inactive", "dead", "", hash, ""}) if err != nil { t.Error(err.Error()) } mgr.TriggerStart(name) err = waitForUnitState(mgr, name, unit.UnitState{"loaded", "active", "running", "", hash, ""}) if err != nil { t.Error(err.Error()) } mgr.TriggerStop(name) mgr.Unload(name) units, err = mgr.Units() if err != nil { t.Fatalf("Failed calling Units(): %v", err) } if len(units) > 0 { t.Fatalf("Expected no units to be returned, got %v", units) } }