Skip to content
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

cmd: fix name prefix matching #1279

Closed
wants to merge 1 commit into from

Conversation

jinuxstyle
Copy link
Contributor

Currently if there are two services, and one service is named
busybox while the other is busybox-top. It is not possible to
remove the busybox service by giving its full name which is also
a common name prefix of both. The only way out is removing the
busybox-top service first or removing it by ID. This looks not an
acceptable behavior.

The same problem applies to clusters, nodes and networks.

Fix it by finding a full name match if prefix matches are more
than one.

Another way to fix it is that start a request with a full name
filter before doing it with a name prefix filter. But I think
this way just presents the same result but adds one more call
overhead to server.

Signed-off-by: Jin Xu jinuxstyle@hotmail.com

@codecov-io
Copy link

codecov-io commented Aug 1, 2016

Current coverage is 55.05% (diff: 100%)

Merging #1279 into master will decrease coverage by 0.08%

@@             master      #1279   diff @@
==========================================
  Files            81         81          
  Lines         12850      12850          
  Methods           0          0          
  Messages          0          0          
  Branches          0          0          
==========================================
- Hits           7085       7074    -11   
  Misses         4786       4786          
- Partials        979        990    +11   

Sunburst

Powered by Codecov. Last update 13db5d4...dfcd073

for _, n := range rl.Nodes {
name := n.Spec.Annotations.Name
if name == "" && n.Description != nil {
name = n.Description.Hostname
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is searching by hostname something that we did before, or is this a new feature?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Searching by hostname is what the server part does.

@dperny
Copy link
Collaborator

dperny commented Aug 1, 2016

LGTM

@@ -138,6 +138,16 @@ func getNode(ctx context.Context, c api.ControlClient, input string) (*api.Node,
}

if l := len(rl.Nodes); l > 1 {
for _, n := range rl.Nodes {
name := n.Spec.Annotations.Name
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand why this is looking at n.Spec.Annotations.Name. It looks like the NamePrefixes only filter acts on n.Description.Hostname, so shouldn't this code only be checking Hostname?

Copy link
Contributor Author

@jinuxstyle jinuxstyle Aug 2, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. The server part only checks hostname when filtering. But the code logic is actually from here in cmd/swarmctl/node/list.go. Should I change them both to keep them consistent or just leave them as they are? I guess the intention of the code is that the Annotations.Name from Spec is always the first place to look for a name, like the names for services, etc.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. The use of Annotations.Name in "node ls" seems wrong. It looks like Docker prints the hostname in the list. I think we should update both of these to only use the hostname.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aaronlehmann We need to be able to let operators set the node name.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aaronlehmann Right. The Docker doesn't check Annotations.Name. I agree we should keep them consistent.

@stevvooe What do you mean? Do you agree that the client should use n.Description.Hostname directly for displaying node's name?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, both Docker client and swarmctl works without problem because node.Spec.Annotations.Name is never set to a valid name string. I don't know whether this will change in future. If yes, then swarmctl's way would be more preferable.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jinuxstyle This is an oversight. User should be able to set a name on a node and have it work correctly.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't seem to index nodes by the name in Annotations.Name. So it wouldn't be possible to look up a node by the Annotations.Name value without some API and store changes. This might be the right direction to go in, but I think it should be a separate PR and should be coordinated with changes to the Docker CLI. In the mean time, it doesn't make sense to me to display a name that can't be typed in to look up the node.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stevvooe The fact is that there is no way for user to set a name for a node explicitly. Is there any historical reason on this? And should we file a separate issue for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the mean time, it doesn't make sense to me to display a name that can't be typed in to look up the node.

@aaronlehmann What do you mean by saying "a name that can't be typed"? Currently it will always display the Hostname as the node's name because the Annotations.Name is an empty string so it uses the Hostname after doing the check.

@aaronlehmann
Copy link
Collaborator

The query is by hostname. So displaying Annotations.Name does no good unless there's a way to query by it.

@jinuxstyle
Copy link
Contributor Author

@aaronlehmann It does look a bit odd from implementation's perspective. The clients ask filtering with Name prefix which is usually intended for filtering nodes by their Annotaitons.Name, but server side does it by Hostname. It means clients have to know servers' implementation.

@jinuxstyle jinuxstyle force-pushed the fix-name-prefix-matching branch 2 times, most recently from 1e931ab to 2a644ad Compare August 5, 2016 00:29
@jinuxstyle
Copy link
Contributor Author

jinuxstyle commented Aug 5, 2016

@aaronlehmann I updated PR per your comments. I agree with your comments after thinking a bit more. We should not use the Spec's Annotations.Name of the node at all because it's never set by implementation.

@@ -32,6 +32,12 @@ func GetNetwork(ctx context.Context, c api.ControlClient, input string) (*api.Ne
}

if l := len(rl.Networks); l > 1 {
for _, n := range rl.Networks {
if n.Spec.Annotations.Name == input {
// Found a full name match
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to handle the case where there may be two matches. There should only be one exact match.

This code should not assume uniqueness, as that is a server-side policy.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stevvooe Is it possible that there are two or more matches for the same name? I suppose this would be a bug on the server side if it really happens.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just found that it is indeed possible that there are multiple nodes with the same name but in different status.
49kmbx2yxf63zn801joyunzm4 node-3 ACCEPTED READY ACTIVE
5a2m77pyawva7b5jjmzmmadjn node-3 ACCEPTED DOWN ACTIVE

Do you know why server side would allow the creation of nodes with the same name? Is it a bug?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you say "name", you mean "hostname", right? It's definitely possible for nodes to have the same hostname, and that's outside our control.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if it is a server-side bug, you need to handle the case where the name is non-unique.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a little surprise for me to know that the name could be non-unique. But it should not be a problem since the ID is still unique.

@stevvooe
Copy link
Contributor

Here is a description of the correct behavior: #1194 (comment).

@stevvooe
Copy link
Contributor

#1194 might be trying to address the same problem, but who know, because there is no description and no examples.

@jinuxstyle
Copy link
Contributor Author

@stevvooe Thanks for referencing to the #1194 which is very informative. I agree with the three cases you listed there. I can improve this PR to make the name prefix matching meet the three cases. If there are multiple full name matches, we should still fail out.

As for ID prefix matching, we can open another PR to do similar things as name prefix matching, or ask @doronp to revise #1194 because the logic is implemented there by getServiceByPrefixedID().

@jinuxstyle
Copy link
Contributor Author

I just read through the code for creating nodes, clusters, services and networks, all of them except creating nodes will check for the uniqueness of the name. So this PR would work after revising the node part. However, I think it's safer and more consistent to revise all of them.

Currently if there are two services, and one service is named
busybox while the other is busybox-top. It is not possible to
remove the busybox service by giving its fullname which is also
a common name prefix of both. The only way out is removing the
busybox-top service first. This looks not an acceptable behavior.

The same problem applies to clusters, nodes and networks.

Fix it by finding a full name match if prefix matches are more
than one.

Another way to fix it is that start a request with a full name
filter before doing it with a name prefix filter. But I think
this way just presents the same result but adds one more call
overhead to server.

Signed-off-by: Jin Xu <jinuxstyle@hotmail.com>
v3: fail out if there are multiple full name matches.
v2: do not use node's Spec.Annotations.Name as defaut
@jinuxstyle
Copy link
Contributor Author

PR updated per review comments: fail out if there are multiple full name matches.

@stevvooe
Copy link
Contributor

@jinuxstyle I think these need to also match the ID by prefix and try the name prefix.

@dperny
Copy link
Collaborator

dperny commented Aug 29, 2016

Would y'all say that this PR now supercedes #1194 ?

@stevvooe
Copy link
Contributor

@dperny That is a fair evaluation.

@dperny dperny mentioned this pull request Aug 29, 2016
@jinuxstyle
Copy link
Contributor Author

jinuxstyle commented Sep 10, 2016

@stevvooe If we need to support ID prefix match, we would have several more cases to cover which will adds complexity.

  1. ID prefix match (5 cases): err != nil, 0 match, 1 full ID match, 1 prefix match, 2 or more prefix matches.
  2. Name prefix match (6 cases): err != nil, 0 match, 1 full name match, 1 prefix match, 2 or more full name matches (only possible for nodes), 2 or more prefix matches.

To support both name and ID prefix match, we will have 30 (5 * 6) cases to cover.

I spent sometime to understand what you discussed in #1194 and sorted out following points. It can be done by at most two requests with ID and name prefix filters respectively.

  1. First request with ID prefix filter, and look for the only full ID match and return it if found.
  2. Then request with name prefix filter, and look for the only full name match and return it if found.
  3. If both requests return error, fail out, otherwise adds up the total number of matches.
  4. If the total number of matches is 0, return not found error
  5. else If it's 1, return the only match
  6. else return ambiguous error

Any comments?

@stevvooe
Copy link
Contributor

@jinuxstyle I'm confused. This is simple stuff.

To support both name and ID prefix match, we will have 30 (5 * 6) cases to cover.

This is an odd way to look at the problem and makes it much more complex than it is. Don't view this as a series of if statements, that will only make this problem untenable.

First step, have 1 function that lists the candidates based on the query. You will hit two indexes using prefix match on id and name. This same function can be used in several contexts to implement different behavior. The rpc that lists items should support OR over separate index sets, ensuring that this can be done in a single RPC. In #1194 (comment), I represented this as a compound index of id and name to the task. Make sure that you understand this point clearly.

All of our algorithms can be defined in terms of the guarantees provided by that matching function. We know that id is always unique match but name may have multiples. Our rules are going to disambiguate over that dataset leveraging that property.

We have two use cases under which we can employ this function:

  1. Identify unique matches. This is for commands that can only find a single match and ambiguity causes them to fail. This would be service update, service rm, etc.
  2. Return all the results matched by the query. This could be employed in listing scenarios, such as service ps, nodes ls, and service ls.

For the purposes of discussion, use case 1 is interesting.

We just need to define simple rules to make these matches. I made this very clear in #1194 (comment), but I'll repeat it here:

  1. Unique match for prefix or exact match on name or id succeeds.
  2. Multiple matches without exact match returns ambiguous error.
  3. Exact match under multiple items gets selected, favoring id.

View each step here as the action after applying a predicate operation on the dataset. If you think about this way, you'll avoid creating a cross-product of conditions that will lead to unmaintainable code.

@jinuxstyle
Copy link
Contributor Author

@stevvooe

This is an odd way to look at the problem and makes it much more complex than it is. Don't view this as a series of if statements, that will only make this problem untenable.

It's not that odd than you might think. With generalization, it could be implemented with at most 5 if statements and 2 loops for iterating over the id and name prefix matching results.

By the way, I have been thinking about why this function would have intended to support both ID and name matching, while the user interface is defined like following:
swarmctl service remove [flags]
By intuition, user would pass a ID but why underlying implementation would also regard it as a name. Would it be better to support name matching by --name or --filter?

The rpc that lists items should support OR over separate index sets, ensuring that this can be done in a single RPC.

This would introduce extensive logical changes to server side code because currently only AND over different filters is supported. Moreover it would not help much on reducing the complexity on client side. The client side will still need several if statements and one or two iterations. Lastly if we are not going to support multiple OR filters from user interface level, it would be meaningless to support OR logic on sever side.

@stevvooe
Copy link
Contributor

@jinuxstyle Please go back and read what I said again. I'm not sure you understood me correctly.

if statements really aren't a good way to approach a problem that is ultimately set-based. Typically, an if-based approach won't provide insight into your dataset and will lead to edge cases. A set-based approach ensures correctness. Once correctness is achieved, you can optimize.

This model is already widely used in git and docker, so I am very confused as to why I've had to spend so much time trying to communicate it.

This would introduce extensive logical changes to server side code because currently only AND over different filters is supported. Moreover it would not help much on reducing the complexity on client side. The client side will still need several if statements and one or two iterations. Lastly if we are not going to support multiple OR filters from user interface level, it would be meaningless to support OR logic on sever side.

Not really. Each component must already be OR, as that is the definition of a prefix. You just need to make sure that if both filters are defined, we match the union.

The problem we are dealing with here is correctness, not complexity. Follow the guidelines I've set and this system will be complete and correct.

@jinuxstyle
Copy link
Contributor Author

@stevvooe I understood what you described. I am just more prone to solve problems in a simpler way if other aspects doesn't make big difference. I agree you suggested approach is more general and powerful.

Not really. Each component must already be OR, as that is the definition of a prefix. You just need to make sure that if both filters are defined, we match the union.

Are you sure that each component must already be OR? As I saw in the server side code (e.g. ListServices) and test code, the current implementation only supports intersection for multiple filters.

Do you really think it's worth the changes?

@stevvooe
Copy link
Contributor

@jinuxstyle Set-based operations are simple.

From the code you sent, it looks like intersection isn't supported. Prefix is union by definition. You'll need to union the results from each filter. It's really not that complex.

Do you really think it's worth the changes?

Absolutely. The problem here is that we don't have a very powerful way to query the dataset. Also, "if-based" approaches tend to be extremely fragile.

Note that I am not saying "don't use if-statements". I am saying, model the problem as sets of tuples, build an index based on those tuples, query that index and process the results. If this is implemented as a series of server-side and client-side filters, that is fine.

@aluzzardi
Copy link
Member

@jinuxstyle @stevvooe ping?

@jinuxstyle
Copy link
Contributor Author

@aluzzardi I already have a solution that fixes the problem on client side only. It doesn't touch server side code, neither the desired solution by @stevvooe. Can I propose it?

BTW, I would not be able to do it based on the approach suggested by @stevvooe in short time, because I am no longer doing open source full time. I will still keep contributing but would be driven by my working needs or in spare time.

@AkihiroSuda
Copy link
Member

@jinuxstyle
Can you show us your solution?
Also, can you also look into moby/moby#27938 if you are interested in?

@stevvooe
Copy link
Contributor

@AkihiroSuda We are going to continue to see bugs and inconsistent results if we do not create a solution as described in #1279 (comment). Is there any way we can get this work done?

@aaronlehmann
Copy link
Collaborator

I don't think this is an issue anymore because swarmctl no longer uses NamePrefixes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants