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

Commit 23d4b13

Browse files
author
Dongsu Park
committed
Merge pull request #1571 from endocode/dongsu/fleetd-conflict-global-unit
fleetd: support conflict in global unit
2 parents e28dbf4 + c2e360d commit 23d4b13

File tree

11 files changed

+126
-21
lines changed

11 files changed

+126
-21
lines changed

Diff for: Documentation/unit-files-and-scheduling.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Note that these requirements are derived directly from systemd, with the only ex
2222
| `MachineOf` | Limit eligible machines to the one that hosts a specific unit. |
2323
| `MachineMetadata` | Limit eligible machines to those with this specific metadata. |
2424
| `Conflicts` | Prevent a unit from being collocated with other units using glob-matching on the other unit names. |
25-
| `Global` | Schedule this unit on all agents in the cluster. A unit is considered invalid if options other than `MachineMetadata` are provided alongside `Global=true`. |
25+
| `Global` | Schedule this unit on those agents in the cluster, which satisfy the conditions of both `MachineMetadata` and `Conflicts` if any of them is also given. A unit is considered invalid if options other than `MachineMetadata` and `Conflicts` are provided alongside `Global=true`. If `MachineMetadata` is provided alongside `Global=true`, only the agents having the metadata can be scheduled on. If `Conflicts` is provided alongside `Global=true`, only the agents not having the conflicting units can be scheduled on. The conflicting units also can not be scheduled on the agents which already have the existing conflicting global unit.|
2626
| `Replaces` | Schedule a specified unit on another machine. A unit is considered invalid if options `Global` or `Conflicts` are provided alongside `Replaces=`. A circular replacement between multiple units is not allowed. |
2727

2828
See [more information][unit-scheduling] on these parameters and how they impact scheduling decisions.

Diff for: agent/reconcile.go

+12-3
Original file line numberDiff line numberDiff line change
@@ -142,16 +142,25 @@ func desiredAgentState(a *Agent, reg registry.Registry) (*AgentState, error) {
142142
for _, u := range units {
143143
u := u
144144
md := u.RequiredTargetMetadata()
145-
if u.IsGlobal() && !machine.HasMetadata(&ms, md) {
146-
log.Debugf("Agent unable to run global unit %s: missing required metadata", u.Name)
147-
continue
145+
146+
if u.IsGlobal() {
147+
if !machine.HasMetadata(&ms, md) {
148+
log.Debugf("Agent unable to run global unit %s: missing required metadata", u.Name)
149+
continue
150+
}
148151
}
152+
149153
if !u.IsGlobal() {
150154
sUnit, ok := sUnitMap[u.Name]
151155
if !ok || sUnit.TargetMachineID == "" || sUnit.TargetMachineID != ms.ID {
152156
continue
153157
}
154158
}
159+
160+
if cExists, _ := as.HasConflict(u.Name, u.Conflicts()); cExists {
161+
continue
162+
}
163+
155164
as.Units[u.Name] = &u
156165
}
157166

Diff for: agent/state.go

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

42-
// 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) {
42+
// 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) {
4444
for _, eUnit := range as.Units {
4545
if pUnitName == eUnit.Name {
4646
continue
@@ -145,7 +145,7 @@ func (as *AgentState) AbleToRun(j *job.Job) (bool, string) {
145145
}
146146
}
147147

148-
if cExists, cJobName := as.hasConflict(j.Name, j.Conflicts()); cExists {
148+
if cExists, cJobName := as.HasConflict(j.Name, j.Conflicts()); cExists {
149149
return false, fmt.Sprintf("found conflict with locally-scheduled Unit(%s)", cJobName)
150150
}
151151

Diff for: agent/state_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ func TestHasConflicts(t *testing.T) {
8585
}
8686

8787
for i, tt := range tests {
88-
got, conflict := tt.cState.hasConflict(tt.job.Name, tt.job.Conflicts())
88+
got, conflict := tt.cState.HasConflict(tt.job.Name, tt.job.Conflicts())
8989
if got != tt.want {
9090
var msg string
9191
if tt.want == true {

Diff for: api/units.go

-2
Original file line numberDiff line numberDiff line change
@@ -259,8 +259,6 @@ func ValidateOptions(opts []*schema.UnitOption) error {
259259
return errors.New("MachineID cannot be used with Replaces")
260260
case isGlobal && hasPeers:
261261
return errors.New("Global cannot be used with Peers")
262-
case isGlobal && hasConflicts:
263-
return errors.New("Global cannot be used with Conflicts")
264262
case isGlobal && hasReplaces:
265263
return errors.New("Global cannot be used with Replaces")
266264
case hasConflicts && hasReplaces:

Diff for: api/units_test.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -650,7 +650,7 @@ func TestValidateOptions(t *testing.T) {
650650
},
651651
true,
652652
},
653-
// Global with Peers/Conflicts no good
653+
// Global with Conflicts is ok
654654
{
655655
[]*schema.UnitOption{
656656
&schema.UnitOption{
@@ -660,7 +660,7 @@ func TestValidateOptions(t *testing.T) {
660660
},
661661
makeConflictUO("foo.service"),
662662
},
663-
false,
663+
true,
664664
},
665665
{
666666
[]*schema.UnitOption{
@@ -671,8 +671,9 @@ func TestValidateOptions(t *testing.T) {
671671
},
672672
makeConflictUO("bar.service"),
673673
},
674-
false,
674+
true,
675675
},
676+
// Global with peer no good
676677
{
677678
[]*schema.UnitOption{
678679
&schema.UnitOption{

Diff for: engine/state.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,14 @@ func (cs *clusterState) agents() map[string]*agent.AgentState {
9393
for _, gu := range cs.gUnits {
9494
gu := gu
9595
for _, a := range agents {
96-
if machine.HasMetadata(a.MState, gu.RequiredTargetMetadata()) {
97-
a.Units[gu.Name] = gu
96+
if !machine.HasMetadata(a.MState, gu.RequiredTargetMetadata()) {
97+
continue
9898
}
99+
100+
if cExists, _ := a.HasConflict(gu.Name, gu.Conflicts()); cExists {
101+
continue
102+
}
103+
a.Units[gu.Name] = gu
99104
}
100105
}
101106

Diff for: fleetctl/fleetctl_test.go

-6
Original file line numberDiff line numberDiff line change
@@ -325,12 +325,6 @@ MachineOf=zxcvq`),
325325
"foo.service",
326326
newUnitFile(t, `[X-Fleet]
327327
Global=true
328-
Conflicts=bar`),
329-
},
330-
{
331-
"foo.service",
332-
newUnitFile(t, `[X-Fleet]
333-
Global=true
334328
Replaces=bar`),
335329
},
336330
{

Diff for: functional/fixtures/units/conflict-global.0.service

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
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+
Global=true
9+
Conflicts=conflict-global.*.service

Diff for: functional/fixtures/units/conflict-global.1.service

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
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+
Global=true
9+
Conflicts=conflict-global.*.service

Diff for: functional/scheduling_test.go

+80
Original file line numberDiff line numberDiff line change
@@ -631,3 +631,83 @@ func TestScheduleGlobalUnits(t *testing.T) {
631631
}
632632
}
633633
}
634+
635+
// TestScheduleGlobalConflicts starts 2 global units that conflict with each
636+
// other, and check if only the first one can be found.
637+
func TestScheduleGlobalConflicts(t *testing.T) {
638+
// Create a three-member cluster
639+
cluster, err := platform.NewNspawnCluster("smoke")
640+
if err != nil {
641+
t.Fatal(err)
642+
}
643+
defer cluster.Destroy(t)
644+
members, err := platform.CreateNClusterMembers(cluster, 3)
645+
if err != nil {
646+
t.Fatal(err)
647+
}
648+
m0 := members[0]
649+
machines, err := cluster.WaitForNMachines(m0, 3)
650+
if err != nil {
651+
t.Fatal(err)
652+
}
653+
654+
cfGlobal0 := "fixtures/units/conflict-global.0.service"
655+
cfGlobal1 := "fixtures/units/conflict-global.1.service"
656+
657+
// Launch a global unit
658+
stdout, stderr, err := cluster.Fleetctl(m0, "start", "--no-block", cfGlobal0)
659+
if err != nil {
660+
t.Fatalf("Failed starting units: \nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err)
661+
}
662+
663+
// the global unit should show up active on 3 machines
664+
_, err = cluster.WaitForNActiveUnits(m0, 3)
665+
if err != nil {
666+
t.Fatal(err)
667+
}
668+
669+
// Now add another global unit, which actually should not be started.
670+
stdout, stderr, err = cluster.Fleetctl(m0, "start", "--no-block", cfGlobal1)
671+
if err != nil {
672+
t.Fatalf("Failed starting unit: \nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err)
673+
}
674+
675+
// Should see only 3 units
676+
states, err := cluster.WaitForNActiveUnits(m0, 3)
677+
if err != nil {
678+
t.Fatal(err)
679+
}
680+
681+
// Each machine should have a single global unit conflict-global.0.service,
682+
// but not conflict-global.1.service.
683+
us0 := states[path.Base(cfGlobal0)]
684+
us1 := states[path.Base(cfGlobal1)]
685+
for _, mach := range machines {
686+
var found bool
687+
for _, state := range us0 {
688+
if state.Machine == mach {
689+
found = true
690+
break
691+
}
692+
}
693+
if !found {
694+
t.Fatalf("Did not find global unit on machine %v", mach)
695+
t.Logf("Found unit states:")
696+
for _, state := range states {
697+
t.Logf("%#v", state)
698+
}
699+
}
700+
701+
found = false
702+
for _, state := range us1 {
703+
if state.Machine == mach {
704+
found = true
705+
break
706+
}
707+
}
708+
if found {
709+
t.Fatalf("Did find global unit %s on machine %v", us1, mach)
710+
t.Logf("Global units were not conflicted as expected.")
711+
}
712+
}
713+
}

0 commit comments

Comments
 (0)