Add support for negated placement constraint (#1419)

This commit is contained in:
ichx
2021-08-27 22:49:55 +08:00
committed by GitHub
parent e3c78d7419
commit e82fe96c38
18 changed files with 529 additions and 40 deletions
+8 -2
View File
@@ -145,8 +145,8 @@ type ServiceConfig struct {
GroupAdd []int64 `compose:"group_add"`
Volumes []Volumes `compose:""`
Secrets []dockerCliTypes.ServiceSecretConfig
HealthChecks HealthChecks `compose:""`
Placement map[string]string `compose:""`
HealthChecks HealthChecks `compose:""`
Placement Placement `compose:""`
//This is for long LONG SYNTAX link(https://docs.docker.com/compose/compose-file/#long-syntax)
Configs []dockerCliTypes.ServiceConfigObjConfig `compose:""`
//This is for SHORT SYNTAX link(https://docs.docker.com/compose/compose-file/#configs)
@@ -203,6 +203,12 @@ type Volumes struct {
SelectorValue string // Value of the label selector
}
// Placement holds the placement struct of container
type Placement struct {
PositiveConstraints map[string]string
NegativeConstraints map[string]string
}
// GetConfigMapKeyFromMeta ...
// given a source name ,find the file and extract the filename which will be act as ConfigMap key
// return "" if not found
+22 -4
View File
@@ -500,11 +500,29 @@ func TestCheckPlacementCustomLabels(t *testing.T) {
"node.labels.monitor != xxx",
},
}
output := loadV3Placement(placement.Constraints)
output := loadV3Placement(placement)
expected := map[string]string{"something": "anything"}
expected := kobject.Placement{
PositiveConstraints: map[string]string{
"something": "anything",
},
NegativeConstraints: map[string]string{
"monitor": "xxx",
},
}
if output["something"] != expected["something"] {
t.Errorf("Expected %s, got %s", expected, output)
checkConstraints(t, "positive", output.PositiveConstraints, expected.PositiveConstraints)
checkConstraints(t, "negative", output.NegativeConstraints, expected.NegativeConstraints)
}
func checkConstraints(t *testing.T, caseName string, output, expected map[string]string) {
t.Log("Test case:", caseName)
if len(output) != len(expected) {
t.Errorf("constraints len is not equal, expected %d, got %d", len(expected), len(output))
}
for key := range output {
if output[key] != expected[key] {
t.Errorf("%s constraint is not equal, expected %s, got %s", key, expected[key], output[key])
}
}
}
+27 -11
View File
@@ -33,7 +33,7 @@ import (
"github.com/docker/cli/cli/compose/loader"
"github.com/docker/cli/cli/compose/types"
shlex "github.com/google/shlex"
"github.com/google/shlex"
"github.com/kubernetes/kompose/pkg/kobject"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
@@ -133,27 +133,43 @@ func parseV3(files []string) (kobject.KomposeObject, error) {
return komposeObject, nil
}
func loadV3Placement(constraints []string) map[string]string {
placement := make(map[string]string)
func loadV3Placement(placement types.Placement) kobject.Placement {
komposePlacement := kobject.Placement{
PositiveConstraints: make(map[string]string),
NegativeConstraints: make(map[string]string),
}
equal, notEqual := " == ", " != "
errMsg := " constraints in placement is not supported, only 'node.hostname', 'engine.labels.operatingsystem' and 'node.labels.xxx' (ex: node.labels.something == anything) is supported as a constraint "
for _, j := range constraints {
p := strings.Split(j, " == ")
for _, j := range placement.Constraints {
operator := equal
if strings.Contains(j, notEqual) {
operator = notEqual
}
p := strings.Split(j, operator)
if len(p) < 2 {
log.Warn(p[0], errMsg)
continue
}
var key string
if p[0] == "node.hostname" {
placement["kubernetes.io/hostname"] = p[1]
key = "kubernetes.io/hostname"
} else if p[0] == "engine.labels.operatingsystem" {
placement["beta.kubernetes.io/os"] = p[1]
key = "beta.kubernetes.io/os"
} else if strings.HasPrefix(p[0], "node.labels.") {
label := strings.TrimPrefix(p[0], "node.labels.")
placement[label] = p[1]
key = strings.TrimPrefix(p[0], "node.labels.")
} else {
log.Warn(p[0], errMsg)
continue
}
if operator == equal {
komposePlacement.PositiveConstraints[key] = p[1]
} else if operator == notEqual {
komposePlacement.NegativeConstraints[key] = p[1]
}
}
return placement
return komposePlacement
}
// Convert the Docker Compose v3 volumes to []string (the old way)
@@ -434,7 +450,7 @@ func dockerComposeToKomposeMapping(composeObject *types.Config) (kobject.Kompose
}
// placement:
serviceConfig.Placement = loadV3Placement(composeServiceConfig.Deploy.Placement.Constraints)
serviceConfig.Placement = loadV3Placement(composeServiceConfig.Deploy.Placement)
if composeServiceConfig.Deploy.UpdateConfig != nil {
serviceConfig.DeployUpdateConfig = *composeServiceConfig.Deploy.UpdateConfig
+1 -1
View File
@@ -534,7 +534,7 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic
template.Spec.Containers[0].Stdin = service.Stdin
template.Spec.Containers[0].TTY = service.Tty
template.Spec.Volumes = append(template.Spec.Volumes, volumes...)
template.Spec.NodeSelector = service.Placement
template.Spec.Affinity = ConfigAffinity(service)
// Configure the HealthCheck
// We check to see if it's blank
if !reflect.DeepEqual(service.HealthChecks.Liveness, kobject.HealthCheck{}) {
+37
View File
@@ -1020,6 +1020,43 @@ func ConfigEnvs(name string, service kobject.ServiceConfig, opt kobject.ConvertO
return envs, nil
}
// ConfigAffinity configures the Affinity.
func ConfigAffinity(service kobject.ServiceConfig) *api.Affinity {
positiveConstraints := configConstrains(service.Placement.PositiveConstraints, api.NodeSelectorOpIn)
negativeConstraints := configConstrains(service.Placement.NegativeConstraints, api.NodeSelectorOpNotIn)
if len(positiveConstraints) == 0 && len(negativeConstraints) == 0 {
return nil
}
return &api.Affinity{
NodeAffinity: &api.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{
NodeSelectorTerms: []api.NodeSelectorTerm{
{
MatchExpressions: append(positiveConstraints, negativeConstraints...),
},
},
},
},
}
}
func configConstrains(constrains map[string]string, operator api.NodeSelectorOperator) []api.NodeSelectorRequirement {
constraintsLen := len(constrains)
rs := make([]api.NodeSelectorRequirement, 0, constraintsLen)
if constraintsLen == 0 {
return rs
}
for k, v := range constrains {
r := api.NodeSelectorRequirement{
Key: k,
Operator: operator,
Values: []string{v},
}
rs = append(rs, r)
}
return rs
}
// CreateKubernetesObjects generates a Kubernetes artifact for each input type service
func (k *Kubernetes) CreateKubernetesObjects(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions) []runtime.Object {
var objects []runtime.Object
@@ -554,6 +554,52 @@ func TestConfigCapabilities(t *testing.T) {
}
}
func TestConfigAffinity(t *testing.T) {
testCases := map[string]struct {
service kobject.ServiceConfig
result *api.Affinity
}{
"ConfigAffinity": {
service: kobject.ServiceConfig{
Placement: kobject.Placement{
PositiveConstraints: map[string]string{
"foo": "bar",
},
NegativeConstraints: map[string]string{
"baz": "qux",
},
},
},
result: &api.Affinity{
NodeAffinity: &api.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{
NodeSelectorTerms: []api.NodeSelectorTerm{
{
MatchExpressions: []api.NodeSelectorRequirement{
{Key: "foo", Operator: api.NodeSelectorOpIn, Values: []string{"bar"}},
{Key: "baz", Operator: api.NodeSelectorOpNotIn, Values: []string{"qux"}},
},
},
},
},
},
},
},
"ConfigAffinity (nil)": {
kobject.ServiceConfig{},
nil,
},
}
for name, test := range testCases {
t.Log("Test case:", name)
result := ConfigAffinity(test.service)
if !reflect.DeepEqual(result, test.result) {
t.Errorf("Not expected result for ConfigAffinity")
}
}
}
func TestMultipleContainersInPod(t *testing.T) {
groupName := "pod_group"
containerName := ""
+2 -1
View File
@@ -48,7 +48,8 @@ func AddContainer(service kobject.ServiceConfig, opt kobject.ConvertOptions) Pod
Stdin: service.Stdin,
TTY: service.Tty,
})
podSpec.NodeSelector = service.Placement
podSpec.Affinity = ConfigAffinity(service)
}
}