-
Notifications
You must be signed in to change notification settings - Fork 656
Third Party Resources support #2090
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| package genericresource | ||
|
|
||
| import ( | ||
| "github.com/docker/swarmkit/api" | ||
| ) | ||
|
|
||
| // NewSet creates a set object | ||
| func NewSet(key string, vals ...string) []*api.GenericResource { | ||
| rs := make([]*api.GenericResource, 0, len(vals)) | ||
|
|
||
| for _, v := range vals { | ||
| rs = append(rs, NewString(key, v)) | ||
| } | ||
|
|
||
| return rs | ||
| } | ||
|
|
||
| // NewString creates a String resource | ||
| func NewString(key, val string) *api.GenericResource { | ||
| return &api.GenericResource{ | ||
| Resource: &api.GenericResource_Str{ | ||
| Str: &api.GenericString{ | ||
| Kind: key, | ||
| Value: val, | ||
| }, | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| // NewDiscrete creates a Discrete resource | ||
| func NewDiscrete(key string, val int64) *api.GenericResource { | ||
| return &api.GenericResource{ | ||
| Resource: &api.GenericResource_Discrete{ | ||
| Discrete: &api.GenericDiscrete{ | ||
| Kind: key, | ||
| Value: val, | ||
| }, | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| // GetResource returns resources from the "resources" parameter matching the kind key | ||
| func GetResource(kind string, resources []*api.GenericResource) []*api.GenericResource { | ||
| var res []*api.GenericResource | ||
|
|
||
| for _, r := range resources { | ||
| if Kind(r) != kind { | ||
| continue | ||
| } | ||
|
|
||
| res = append(res, r) | ||
| } | ||
|
|
||
| return res | ||
| } | ||
|
|
||
| // ConsumeNodeResources removes "res" from nodeAvailableResources | ||
| func ConsumeNodeResources(nodeAvailableResources *[]*api.GenericResource, res []*api.GenericResource) { | ||
| if nodeAvailableResources == nil { | ||
| return | ||
| } | ||
|
|
||
| w := 0 | ||
|
|
||
| loop: | ||
| for _, na := range *nodeAvailableResources { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the number of generic resources is high, this double loop may become heavy. Should we change the data structure to map to avoid double loops?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I implemented it as list because it kept the code clean. What do you think ?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It makes sense. However this data structure is a key piece in this implementation and I want to get it right from start. Performance issue may be hidden and we may not realize it. I also think map shouldn't be more complicated than slice in implementation. But I might miss something. Let me know.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well to implement a map we have two ways to do it:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As a side note these functions are really called once per task (once the node has been selected) The function that would be the bottleneck if we have performance issue is
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What you described makes sense. I prefer the manager(leader) keeps a map of resources and everything is done by lookup. But I don't want this to hold this long PR.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This is my ideal solution, but it's a big change and might make sense as a followup. I don't want to block the PR on it. |
||
| for _, r := range res { | ||
| if Kind(na) != Kind(r) { | ||
| continue | ||
| } | ||
|
|
||
| if remove(na, r) { | ||
| continue loop | ||
| } | ||
| // If this wasn't the right element then | ||
| // we need to continue | ||
| } | ||
|
|
||
| (*nodeAvailableResources)[w] = na | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The goal here is to move valid elements on top of deleted elements and at the end slice the array. |
||
| w++ | ||
| } | ||
|
|
||
| *nodeAvailableResources = (*nodeAvailableResources)[:w] | ||
| } | ||
|
|
||
| // Returns true if the element is to be removed from the list | ||
| func remove(na, r *api.GenericResource) bool { | ||
| switch tr := r.Resource.(type) { | ||
| case *api.GenericResource_Discrete: | ||
| if na.GetDiscrete() == nil { | ||
| return false // Type change, ignore | ||
| } | ||
|
|
||
| na.GetDiscrete().Value -= tr.Discrete.Value | ||
| if na.GetDiscrete().Value <= 0 { | ||
| return true | ||
| } | ||
| case *api.GenericResource_Str: | ||
| if na.GetStr() == nil { | ||
| return false // Type change, ignore | ||
| } | ||
|
|
||
| if tr.Str.Value != na.GetStr().Value { | ||
| return false // not the right item, ignore | ||
| } | ||
|
|
||
| return true | ||
| } | ||
|
|
||
| return false | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| package genericresource | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/docker/swarmkit/api" | ||
| "github.com/stretchr/testify/assert" | ||
| ) | ||
|
|
||
| func TestConsumeResourcesSingle(t *testing.T) { | ||
| nodeAvailableResources := NewSet("apple", "red", "orange", "blue") | ||
| res := NewSet("apple", "red") | ||
|
|
||
| ConsumeNodeResources(&nodeAvailableResources, res) | ||
| assert.Len(t, nodeAvailableResources, 2) | ||
|
|
||
| nodeAvailableResources = append(nodeAvailableResources, NewDiscrete("apple", 1)) | ||
| res = []*api.GenericResource{NewDiscrete("apple", 1)} | ||
|
|
||
| ConsumeNodeResources(&nodeAvailableResources, res) | ||
| assert.Len(t, nodeAvailableResources, 2) | ||
|
|
||
| nodeAvailableResources = append(nodeAvailableResources, NewDiscrete("apple", 4)) | ||
| res = []*api.GenericResource{NewDiscrete("apple", 1)} | ||
|
|
||
| ConsumeNodeResources(&nodeAvailableResources, res) | ||
| assert.Len(t, nodeAvailableResources, 3) | ||
| assert.Equal(t, int64(3), nodeAvailableResources[2].GetDiscrete().Value) | ||
| } | ||
|
|
||
| func TestConsumeResourcesMultiple(t *testing.T) { | ||
| nodeAvailableResources := NewSet("apple", "red", "orange", "blue", "green", "yellow") | ||
| nodeAvailableResources = append(nodeAvailableResources, NewDiscrete("orange", 5)) | ||
| nodeAvailableResources = append(nodeAvailableResources, NewDiscrete("banana", 3)) | ||
| nodeAvailableResources = append(nodeAvailableResources, NewSet("grape", "red", "orange", "blue", "green", "yellow")...) | ||
| nodeAvailableResources = append(nodeAvailableResources, NewDiscrete("cakes", 3)) | ||
|
|
||
| res := NewSet("apple", "red") | ||
| res = append(res, NewDiscrete("banana", 2)) | ||
| res = append(res, NewSet("apple", "green", "blue", "red")...) | ||
| res = append(res, NewSet("grape", "red", "blue", "red")...) | ||
| res = append(res, NewDiscrete("cakes", 3)) | ||
|
|
||
| ConsumeNodeResources(&nodeAvailableResources, res) | ||
| assert.Len(t, nodeAvailableResources, 7) | ||
|
|
||
| apples := GetResource("apple", nodeAvailableResources) | ||
| oranges := GetResource("orange", nodeAvailableResources) | ||
| bananas := GetResource("banana", nodeAvailableResources) | ||
| grapes := GetResource("grape", nodeAvailableResources) | ||
| assert.Len(t, apples, 2) | ||
| assert.Len(t, oranges, 1) | ||
| assert.Len(t, bananas, 1) | ||
| assert.Len(t, grapes, 3) | ||
|
|
||
| for _, k := range []string{"yellow", "orange"} { | ||
| assert.True(t, HasResource(NewString("apple", k), apples)) | ||
| } | ||
|
|
||
| for _, k := range []string{"yellow", "orange", "green"} { | ||
| assert.True(t, HasResource(NewString("grape", k), grapes)) | ||
| } | ||
|
|
||
| assert.Equal(t, int64(5), oranges[0].GetDiscrete().Value) | ||
| assert.Equal(t, int64(1), bananas[0].GetDiscrete().Value) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| package genericresource | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "strconv" | ||
| "strings" | ||
|
|
||
| "github.com/docker/swarmkit/api" | ||
| ) | ||
|
|
||
| func newParseError(format string, args ...interface{}) error { | ||
| return fmt.Errorf("could not parse GenericResource: "+format, args...) | ||
| } | ||
|
|
||
| // Parse parses the GenericResource resources given by the arguments | ||
| func Parse(cmd string) ([]*api.GenericResource, error) { | ||
| var rs []*api.GenericResource | ||
|
|
||
| for _, term := range strings.Split(cmd, ";") { | ||
| kva := strings.Split(term, "=") | ||
| if len(kva) != 2 { | ||
| return nil, newParseError("incorrect term %s, missing '=' or malformed expr", term) | ||
| } | ||
|
|
||
| key := strings.TrimSpace(kva[0]) | ||
| val := strings.TrimSpace(kva[1]) | ||
|
|
||
| u, err := strconv.ParseInt(val, 10, 64) | ||
| if err == nil { | ||
| if u < 0 { | ||
| return nil, newParseError("cannot ask for negative resource %s", key) | ||
| } | ||
| rs = append(rs, NewDiscrete(key, u)) | ||
| continue | ||
| } | ||
|
|
||
| if len(val) > 2 && val[0] == '{' && val[len(val)-1] == '}' { | ||
| val = val[1 : len(val)-1] | ||
| rs = append(rs, NewSet(key, strings.Split(val, ",")...)...) | ||
| continue | ||
| } | ||
|
|
||
| return nil, newParseError("could not parse expression '%s'", term) | ||
| } | ||
|
|
||
| return rs, nil | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| package genericresource | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/stretchr/testify/assert" | ||
| ) | ||
|
|
||
| func TestParseDiscrete(t *testing.T) { | ||
| res, err := Parse("apple=3") | ||
| assert.NoError(t, err) | ||
| assert.Equal(t, len(res), 1) | ||
|
|
||
| apples := GetResource("apple", res) | ||
| assert.Equal(t, len(apples), 1) | ||
| assert.Equal(t, apples[0].GetDiscrete().Value, int64(3)) | ||
| } | ||
|
|
||
| func TestParseStr(t *testing.T) { | ||
| res, err := Parse("orange={red,green,blue}") | ||
| assert.NoError(t, err) | ||
| assert.Equal(t, len(res), 3) | ||
|
|
||
| oranges := GetResource("orange", res) | ||
| assert.Equal(t, len(oranges), 3) | ||
| for _, k := range []string{"red", "green", "blue"} { | ||
| assert.True(t, HasResource(NewString("orange", k), oranges)) | ||
| } | ||
| } | ||
|
|
||
| func TestParseDiscreteAndStr(t *testing.T) { | ||
| res, err := Parse("orange={red,green,blue};apple=3") | ||
| assert.NoError(t, err) | ||
| assert.Equal(t, len(res), 4) | ||
|
|
||
| oranges := GetResource("orange", res) | ||
| assert.Equal(t, len(oranges), 3) | ||
| for _, k := range []string{"red", "green", "blue"} { | ||
| assert.True(t, HasResource(NewString("orange", k), oranges)) | ||
| } | ||
|
|
||
| apples := GetResource("apple", res) | ||
| assert.Equal(t, len(apples), 1) | ||
| assert.Equal(t, apples[0].GetDiscrete().Value, int64(3)) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we need to expose these functions outside of
genericresource?Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The NewError isn't used anywhere, the others are as they mask the type generated by the oneof