Skip to content
This repository was archived by the owner on Jan 30, 2020. It is now read-only.

Commit 70e2326

Browse files
author
Dongsu Park
committed
Merge pull request #1695 from endocode/dongsu/agent-conflict-multi-units
agent: allow HasConflict() to handle multiple values defined in Conflicts
2 parents be0c3c2 + e06ce1d commit 70e2326

9 files changed

+257
-17
lines changed

agent/reconcile_test.go

+24
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,30 @@ func TestAbleToRun(t *testing.T) {
388388
want: job.JobActionUnschedule,
389389
},
390390

391+
// conflicts found
392+
{
393+
dState: &AgentState{
394+
MState: &machine.MachineState{ID: "123"},
395+
Units: map[string]*job.Unit{
396+
"ping.service": &job.Unit{Name: "ping.service"},
397+
},
398+
},
399+
job: newTestJobWithXFleetValues(t, "Conflicts=pong.service\nConflicts=ping.service"),
400+
want: job.JobActionUnschedule,
401+
},
402+
403+
// conflicts found
404+
{
405+
dState: &AgentState{
406+
MState: &machine.MachineState{ID: "123"},
407+
Units: map[string]*job.Unit{
408+
"ping.service": &job.Unit{Name: "ping.service"},
409+
},
410+
},
411+
job: newTestJobWithXFleetValues(t, "Conflicts=pong.service ping.service"),
412+
want: job.JobActionUnschedule,
413+
},
414+
391415
// no replaces found
392416
{
393417
dState: &AgentState{

agent/state.go

+26-15
Original file line numberDiff line numberDiff line change
@@ -39,31 +39,42 @@ func (as *AgentState) unitScheduled(name string) bool {
3939
return as.Units[name] != nil
4040
}
4141

42+
func hasStringInSlice(inSlice []string, unitName string) bool {
43+
for _, elem := range inSlice {
44+
if globMatches(elem, unitName) {
45+
return true
46+
}
47+
}
48+
return false
49+
}
50+
4251
// HasConflict determines whether there are any known conflicts with the given Unit
43-
func (as *AgentState) HasConflict(pUnitName string, pConflicts []string) (found bool, conflict string) {
52+
func (as *AgentState) HasConflict(pUnitName string, pConflicts []string) (bool, []string) {
53+
found := false
54+
conflicts := []string{}
55+
4456
for _, eUnit := range as.Units {
4557
if pUnitName == eUnit.Name {
4658
continue
4759
}
4860

49-
for _, pConflict := range pConflicts {
50-
if globMatches(pConflict, eUnit.Name) {
51-
found = true
52-
conflict = eUnit.Name
53-
return
54-
}
61+
if hasStringInSlice(eUnit.Conflicts(), pUnitName) {
62+
conflicts = append(conflicts, pUnitName)
63+
found = true
64+
break
5565
}
56-
57-
for _, eConflict := range eUnit.Conflicts() {
58-
if globMatches(eConflict, pUnitName) {
59-
found = true
60-
conflict = eUnit.Name
61-
return
62-
}
66+
if hasStringInSlice(pConflicts, eUnit.Name) {
67+
conflicts = append(conflicts, eUnit.Name)
68+
found = true
69+
break
6370
}
6471
}
6572

66-
return
73+
if !found {
74+
return false, []string{}
75+
}
76+
77+
return true, conflicts
6778
}
6879

6980
// hasReplace determines whether there are any known replaces with the given Unit

agent/state_test.go

+80
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,86 @@ func TestHasConflicts(t *testing.T) {
8282
want: true,
8383
conflict: "bar.service",
8484
},
85+
86+
// existing job has conflict with new job,
87+
// one of multiple conflicts defined in a single line
88+
{
89+
cState: &AgentState{
90+
MState: &machine.MachineState{ID: "XXX"},
91+
Units: map[string]*job.Unit{
92+
"bar.service": &job.Unit{
93+
Name: "bar.service",
94+
Unit: fleetUnit(t, "Conflicts=foo.service bar.service"),
95+
},
96+
},
97+
},
98+
job: &job.Job{
99+
Name: "foo.service",
100+
Unit: unit.UnitFile{},
101+
},
102+
want: true,
103+
conflict: "bar.service",
104+
},
105+
106+
// existing job has conflict with new job,
107+
// one of multiple conflicts over multiple lines
108+
{
109+
cState: &AgentState{
110+
MState: &machine.MachineState{ID: "XXX"},
111+
Units: map[string]*job.Unit{
112+
"bar.service": &job.Unit{
113+
Name: "bar.service",
114+
Unit: fleetUnit(t, "Conflicts=foo.service\nConflicts=bar.service"),
115+
},
116+
},
117+
},
118+
job: &job.Job{
119+
Name: "foo.service",
120+
Unit: unit.UnitFile{},
121+
},
122+
want: true,
123+
conflict: "bar.service",
124+
},
125+
126+
// new job has conflict with existing job,
127+
// one of multiple conflicts defined in a single line
128+
{
129+
cState: &AgentState{
130+
MState: &machine.MachineState{ID: "XXX"},
131+
Units: map[string]*job.Unit{
132+
"bar.service": &job.Unit{
133+
Name: "bar.service",
134+
Unit: unit.UnitFile{},
135+
},
136+
},
137+
},
138+
job: &job.Job{
139+
Name: "foo.service",
140+
Unit: fleetUnit(t, "Conflicts=foo.service bar.service"),
141+
},
142+
want: true,
143+
conflict: "bar.service",
144+
},
145+
146+
// new job has conflict with existing job,
147+
// one of multiple conflicts over multiple lines
148+
{
149+
cState: &AgentState{
150+
MState: &machine.MachineState{ID: "XXX"},
151+
Units: map[string]*job.Unit{
152+
"bar.service": &job.Unit{
153+
Name: "bar.service",
154+
Unit: unit.UnitFile{},
155+
},
156+
},
157+
},
158+
job: &job.Job{
159+
Name: "foo.service",
160+
Unit: fleetUnit(t, "Conflicts=foo.service\nConflicts=bar.service"),
161+
},
162+
want: true,
163+
conflict: "bar.service",
164+
},
85165
}
86166

87167
for i, tt := range tests {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[Unit]
2+
Description=Test Unit
3+
4+
[Service]
5+
ExecStart=/bin/bash -c "while true; do echo Hello, World!; sleep 1; done"
6+
7+
[X-Fleet]
8+
Conflicts=conflict.2.service conflict.1.service conflict.0.service
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[Unit]
2+
Description=Test Unit
3+
4+
[Service]
5+
ExecStart=/bin/bash -c "while true; do echo Hello, World!; sleep 1; done"
6+
7+
[X-Fleet]
8+
Conflicts=conflict.2.service conflict.1.service conflict.0.service
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[Unit]
2+
Description=Test Unit
3+
4+
[Service]
5+
ExecStart=/bin/bash -c "while true; do echo Hello, World!; sleep 1; done"
6+
7+
[X-Fleet]
8+
Conflicts=conflict.2.service
9+
Conflicts=conflict.1.service
10+
Conflicts=conflict.0.service

functional/scheduling_test.go

+68
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,74 @@ func TestScheduleOneWayConflict(t *testing.T) {
333333

334334
}
335335

336+
// Start 3 services that conflict with one another.
337+
func TestScheduleMultipleConflicts(t *testing.T) {
338+
cluster, err := platform.NewNspawnCluster("smoke")
339+
if err != nil {
340+
t.Fatal(err)
341+
}
342+
defer cluster.Destroy(t)
343+
344+
// Start with a simple three-node cluster
345+
members, err := platform.CreateNClusterMembers(cluster, 3)
346+
if err != nil {
347+
t.Fatal(err)
348+
}
349+
m0 := members[0]
350+
machines, err := cluster.WaitForNMachines(m0, 3)
351+
if err != nil {
352+
t.Fatal(err)
353+
}
354+
355+
// Ensure we can SSH into each machine using fleetctl
356+
for _, machine := range machines {
357+
if stdout, stderr, err := cluster.Fleetctl(m0, "--strict-host-key-checking=false", "ssh", machine, "uptime"); err != nil {
358+
t.Errorf("Unable to SSH into fleet machine: \nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err)
359+
}
360+
}
361+
362+
for i := 0; i < 3; i++ {
363+
unit := fmt.Sprintf("fixtures/units/conflict.multiple.%d.service", i)
364+
stdout, stderr, err := cluster.Fleetctl(m0, "start", "--no-block", unit)
365+
if err != nil {
366+
t.Errorf("Failed starting unit %s: \nstdout: %s\nstderr: %s\nerr: %v", unit, stdout, stderr, err)
367+
}
368+
}
369+
370+
// All 3 services should be visible immediately and 3 should become
371+
// ACTIVE shortly thereafter
372+
stdout, stderr, err := cluster.Fleetctl(m0, "list-unit-files", "--no-legend")
373+
if err != nil {
374+
t.Fatalf("Failed to run list-unit-files:\nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err)
375+
}
376+
units := strings.Split(strings.TrimSpace(stdout), "\n")
377+
if len(units) != 3 {
378+
t.Fatalf("Did not find five units in cluster: \n%s", stdout)
379+
}
380+
active, err := cluster.WaitForNActiveUnits(m0, 3)
381+
if err != nil {
382+
t.Fatal(err)
383+
}
384+
states, err := util.ActiveToSingleStates(active)
385+
if err != nil {
386+
t.Fatal(err)
387+
}
388+
389+
machineSet := make(map[string]bool)
390+
391+
for unit, unitState := range states {
392+
if len(unitState.Machine) == 0 {
393+
t.Errorf("Unit %s is not reporting machine", unit)
394+
}
395+
396+
machineSet[unitState.Machine] = true
397+
}
398+
399+
if len(machineSet) != 3 {
400+
t.Errorf("3 active units not running on 3 unique machines")
401+
}
402+
}
403+
336404
// TestScheduleReplace starts 3 units, followed by starting another unit
337405
// that replaces the 1st unit. Then it verifies that the original unit
338406
// got rescheduled on a different machine.

job/job.go

+18-2
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,13 @@ func (j *Job) ValidateRequirements() error {
220220
// machine as this Job.
221221
func (j *Job) Conflicts() []string {
222222
conflicts := make([]string, 0)
223-
conflicts = append(conflicts, j.requirements()[deprecatedXPrefix+fleetConflicts]...)
224-
conflicts = append(conflicts, j.requirements()[fleetConflicts]...)
223+
224+
ldConflicts := splitCombine(j.requirements()[deprecatedXPrefix+fleetConflicts])
225+
conflicts = append(conflicts, ldConflicts...)
226+
227+
dConflicts := splitCombine(j.requirements()[fleetConflicts])
228+
conflicts = append(conflicts, dConflicts...)
229+
225230
return conflicts
226231
}
227232

@@ -332,3 +337,14 @@ func isTruthyValue(s string) bool {
332337
chl := strings.ToLower(s)
333338
return chl == "true" || chl == "yes" || chl == "1" || chl == "on" || chl == "t"
334339
}
340+
341+
// splitCombine retrieves each word from an input string slice, to put each
342+
// one again into a single slice.
343+
func splitCombine(inStrs []string) []string {
344+
outStrs := make([]string, 0)
345+
for _, str := range inStrs {
346+
inStrs := strings.Fields(str)
347+
outStrs = append(outStrs, inStrs...)
348+
}
349+
return outStrs
350+
}

job/job_test.go

+15
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,21 @@ Description=Testing
7777
[X-Fleet]
7878
Conflicts=*bar*
7979
`, []string{"*bar*"}},
80+
{``, []string{}},
81+
{`[Unit]
82+
Description=Testing
83+
84+
[X-Fleet]
85+
Conflicts=*bar* *foo*
86+
`, []string{"*bar*", "*foo*"}},
87+
{``, []string{}},
88+
{`[Unit]
89+
Description=Testing
90+
91+
[X-Fleet]
92+
Conflicts=*bar*
93+
Conflicts=*foo*
94+
`, []string{"*bar*", "*foo*"}},
8095
}
8196
for i, tt := range testCases {
8297
j := NewJob("echo.service", *newUnit(t, tt.contents))

0 commit comments

Comments
 (0)