-
Notifications
You must be signed in to change notification settings - Fork 370
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
Implement antctl get pod-interface [podName] #334
Conversation
Thanks for your PR. The following commands are available:
These commands can only be run by members of the vmware-tanzu organization. |
/test-all |
/test-networkpolicy |
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.
I may be missing something, but it seems that the command should use the Namespace as well as the Pod name.
IMO, if I have 2 Pods with the same name in 2 different namespaces, I should be able to select a single one. It doesn't seem like it is the case right now. It seems that you stop at the first Pod for which the name matches?
Also, as I commented on the agent-info PR, will we be able to run this command from out-of-cluster (from out of the agent Pod)? That would be very useful.
pkg/agent/interfacestore/types.go
Outdated
@@ -72,6 +72,7 @@ type InterfaceStore interface { | |||
GetContainerInterface(podName string, podNamespace string) (*InterfaceConfig, bool) | |||
GetNodeTunnelInterface(nodeName string) (*InterfaceConfig, bool) | |||
GetContainerInterfaceNum() int | |||
GetContainerInterfaces() []*InterfaceConfig |
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.
maybe others will disagree but I would suggest having a more generic GetInterfacesByType(interfaceType InterfaceType)
interface method - like we have GetInterfaceKeysByType
below. Maybe it will help keep the number of methods for this interface "reasonable".
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 you mean make the command like antctl get pod podName -n nsName
? Do you use namespace to filter for CRD @mengdie-song ?
Then if user doesn't specify the namespace name, let's show all pods with different namespaces ?
Yes, we can run this command remotely with future implementation.
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.
@lzhecheng For CRD, currently we only have the field LocalPodNum to show the number of Pod. I did not distinguish them by namespaces. I can enhance this part if we want more information for local pods in CRD. But if the number of Pods is too large, I am afraid the CRD is not friendly way to show.
However, CLI can show more information than CRD if we want. Since this patch has already added a separate function GetContainerInterfaces to get details, we can get Pod namespace from InterfaceConfig if we would like to show it via CLI.
pkg/antctl/antctl.go
Outdated
@@ -76,6 +78,16 @@ var CommandList = &commandList{ | |||
CommandGroup: flat, | |||
AddonTransform: versionTransform, | |||
}, | |||
{ | |||
Use: "pod", | |||
Short: "Print pod's basic information", |
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.
I suggest using "Pod" and not "pod" for user-facing messages, for the sake of consistency.
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.
+1
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.
Reasonable. Updated.
pkg/antctl/handlers/pod.go
Outdated
@@ -0,0 +1,74 @@ | |||
// Copyright 2020 Antrea Authors |
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.
should the file be named "get_pod.go" instead since this is the "pod" subcommand in the "get" command group? Would it be possible to have a "pod" subcommand in another command group?
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.
Yes, it is possible. Updated.
pkg/antctl/antctl.go
Outdated
{ | ||
Use: "pod", | ||
Short: "Print pod's basic information", | ||
Long: "Print pod's basic information of interface config.", |
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.
suggestion: "Print information about the network interface(s) created by the Antrea agent for the specified Pod".
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.
Updated.
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.
A general question for this cmd - do we plan to add more information about the Pod later? If it is just about Pod interface info, maybe name it get pod-interface?
pkg/monitor/querier.go
Outdated
@@ -113,6 +115,11 @@ func (monitor *agentMonitor) GetLocalPodNum() int32 { | |||
return int32(monitor.interfaceStore.GetContainerInterfaceNum()) | |||
} | |||
|
|||
// GetContainerInterfaces gets the information of the pod with podName. | |||
func (monitor *agentMonitor) GetContainerInterfaces() []*interfacestore.InterfaceConfig { |
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.
I would stop adding more CLI code to Agent/ControllerMonitor which is originally for MonitorCRD.
But we can decide this together with other changes I suggested like extracting API server code out of CLI.
pkg/antctl/antctl.go
Outdated
@@ -76,6 +78,16 @@ var CommandList = &commandList{ | |||
CommandGroup: flat, | |||
AddonTransform: versionTransform, | |||
}, | |||
{ | |||
Use: "pod", | |||
Short: "Print pod's basic information", |
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.
+1
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.
Could you provide an example output of the command? I have three general questions:
- Should we add the namespace argument (like --namespace/-N just like kubectl).
- Should we support getting all Pods (Pod name is not specified).
- If the command is for Pod interface configuration only? If so should we name it get pod-interface; if not should we define a separate struct like PodInfo for future extension, instead of using InterfaceConfig directly.
@tnqn
I just put an example in the description of this PR.
|
afc56b7
to
53fc250
Compare
/test-networkpolicy |
e8f65f0
to
251059a
Compare
pkg/agent/apiserver/apiserver.go
Outdated
@@ -52,6 +53,7 @@ func (s *agentAPIServer) Run(stopCh <-chan struct{}) error { | |||
|
|||
func installHandlers(aq monitor.AgentQuerier, npq monitor.AgentNetworkPolicyInfoQuerier, s *genericapiserver.GenericAPIServer) { | |||
s.Handler.NonGoRestfulMux.HandleFunc("/agentinfo", agentinfo.HandleFunc(aq)) | |||
s.Handler.NonGoRestfulMux.HandleFunc("/getpodinterface", getpodinterface.HandleFunc(aq)) |
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.
Restful api doesn't have verb in url path, which should be expressed by http method.
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.
Updated.
hack/netpol/pkg/main/main.go
Outdated
@@ -150,19 +150,19 @@ func main() { | |||
failOnError(err) | |||
|
|||
testList := []*TestCase{ | |||
&TestCase{"DefaultDeny", testDefaultDeny()}, |
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.
I understand they are redundant but better to not touch unrelated code so that reviewers can focus on your change and there won't be surprise when seeing git history that some lines are changed in an unrelated PR.
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.
Removed since this is fixed in other PR.
m := make(map[string][]*interfacestore.InterfaceConfig) | ||
for _, v := range aq.GetInterfacesByType(interfacestore.ContainerInterface) { | ||
podName := (*v.ContainerInterfaceConfig).PodName | ||
vPodNS := (*v.ContainerInterfaceConfig).PodNamespace |
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.
why is there a "v" prefix for namespace only?
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.
There used to be other var named podNS so I added "v" to avoid conflict. Now it is ok to name it "podNS".
if (!doFilter || key[0] == podName) && ns[0] == vPodNS { | ||
m[podName] = append(m[podName], v) | ||
} | ||
fmt.Println("podName:", podName, "vPodNS:", vPodNS) |
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.
Why using Println in server code?
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.
It is not final code and only for debugging. Removed.
func HandleFunc(aq monitor.AgentQuerier) http.HandlerFunc { | ||
return func(w http.ResponseWriter, r *http.Request) { | ||
if aq == nil { | ||
w.WriteHeader(http.StatusNotImplemented) |
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.
This is a closure so aq
has been decided before serving requests. If aq
could possibly be nil, I would prefer we return error at the earliest place so that we don't need to check aq in every subroutine of the call stack.
Actually I checked agent.go has made sure aq cannot be nil so this check is not necessary.
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.
Thanks for reminding. Code is not updated according to refactored framework.
for _, v := range aq.GetInterfacesByType(interfacestore.ContainerInterface) { | ||
podName := (*v.ContainerInterfaceConfig).PodName | ||
vPodNS := (*v.ContainerInterfaceConfig).PodNamespace | ||
if (!doFilter || key[0] == podName) && ns[0] == vPodNS { |
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.
Is namespace a mandatory field? Is it safe to assume len(ns) > 0
?
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.
namespace has a default value and if no value after "-n", it will return Error: flag needs an argument: 'n' in -n
.
if err != nil { | ||
w.WriteHeader(http.StatusInternalServerError) | ||
} | ||
} |
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.
This can be simpler:
if doFilter and len(m) == 0 {
w.WriteHeader(http.StatusNotFound)
} else {
var pods []GetPodInterfaceResponse
for k, v := range m {
pods = append(pods, GetPodInterfaceResponse{k, v})
}
err := json.NewEncoder(w).Encode(pods)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
}
}
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.
Refactored.
return | ||
} | ||
|
||
m := make(map[string][]*interfacestore.InterfaceConfig) |
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.
Why using map and having the podname as the key? Is there any reason pods having same name but in different namespaces needs to be grouped together?
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.
Refactored.
@jianjuns, sorry for missing the notification. All sounds good to me. |
dfaefd5
to
96a3cb1
Compare
/test-e2e |
I followed @jianjuns 's suggestion and updated the code. |
/test-all |
// Response describes the internal response struct of pod-interface command. | ||
type Response struct { | ||
PodName string `json:"name,omitempty" yaml:"name,omitempty" antctl:"name,The name of the pod"` | ||
PodInterfaces []PodInterface `json:",inline" yaml:",inline"` |
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.
Again, why multiple interfaces with same Pod name are grouped together? They are logically unrelated, I think the previous flatten struct works.
podName := (*v.ContainerInterfaceConfig).PodName | ||
podNS := (*v.ContainerInterfaceConfig).PodNamespace | ||
if (len(name) == 0 || name == podName) && (len(ns) == 0 || ns == podNS) { | ||
m[podName] = append(m[podName], generatePodInterface(podName, v)) |
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.
ditto, is there any problem using a slice to keep result?
} | ||
} | ||
} else { | ||
var pods []Response |
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 3 branches can be simplified as:
if len(name) > 0 && len(m) == 0 {
w.WriteHeader(http.StatusNotFound)
return
}
var pods []Response
...
ab76612
to
8280564
Compare
Code updated to avoid grouping pods with same name but in different namespaces together. isSingle field in nonResourceEndpoint is turned into outputType, an enum including default, single and multiple. |
/test-all |
/test-e2e |
if len(name) > 0 && len(pods) == 0 { | ||
w.WriteHeader(http.StatusNotFound) | ||
} else { | ||
err := json.NewEncoder(w).Encode(pods) |
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.
nit: keep the normal code path at a minimal indentation, and indent the error handling, dealing with it first https://github.com/golang/go/wiki/CodeReviewComments#indent-error-flow
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.
Updated
pkg/antctl/command_definition.go
Outdated
type OutputType uint | ||
|
||
const ( | ||
dflt OutputType = iota |
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.
"default" is shorter than "multiple", why use short for it alone? and this short doesn't seem common.
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.
I think "default" is reserved? Let's change it to "defaultType"
pkg/antctl/command_definition.go
Outdated
@@ -62,7 +69,7 @@ var groupCommands = map[commandGroup]*cobra.Command{ | |||
} | |||
|
|||
type endpointResponder interface { | |||
single() bool | |||
output() OutputType |
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.
output sounds like an action, maybe "outputType" to avoid confusion.
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.
IMO, "output" sounds like a noun which is shorter like the original "single". Also, if changed, we will have outputType() as a function and a outputType as a field in nonResourceEndpoint, which is confusing I think.
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.
Of course it can sound like a noun but it means the output of the responder? I don't know if it's only me who will be confused by the function name. AFAIK, Output is widely used by exec, command, http request to get the output and the noun output is used to take the return value.
Besides, if output is really clear for the mean of outputType, why the type is named OutputType while the method is named output?
Defining a method to get the field with same name is quite common regardless of languages, it's even encouraged to do it in Golang (to save the prefix Get), I don't think it's confusing.
If you have a field called owner (lower case, unexported), the getter method should be called Owner (upper case, exported), not GetOwner.
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.
Thanks for sharing the doc. But outputType()
cannot work, it returns type nonResourceEndpoint has both field and method named outputType
. I choose OutputType()
instead.
expect string | ||
}{ | ||
"SingleObject": { | ||
"outputObject": { |
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.
This case is still testing single object, why change the case name?
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.
Updated. Thanks for reminding me cuz I changed it by mistake.
@@ -108,13 +108,13 @@ func TestCommandDefinitionGenerateExample(t *testing.T) { | |||
for k, tc := range map[string]struct { | |||
use string | |||
cmdChain string | |||
singleObject bool | |||
outputObject OutputType |
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.
why not use outputType as variable name?
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.
Updated.
pkg/antctl/command_definition.go
Outdated
// StringFlag represents a string flag | ||
StringFlag FlagType = iota | ||
// BoolFlag represents a boolean flag | ||
BoolFlag |
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 still need this?
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.
Indeed, BoolFlag is not applied by any commands now. However, I suggest keeping it since these newly-added types not just add support for "bool" flag but also it refactors the code to adapt more kinds of flags in the future. Unless there will only be string flag, I think it is worthwhile to keep these changes.
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.
Then we should add this the first time it is used, not assuming it can be used one day unpredictably. Some people are even dedicated to clean up functions, structs that are no longer used no matter how useful they used to be. We shouldn't do the opposite thing.
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.
Removed.
d2aa81a
to
92342ae
Compare
pkg/antctl/command_definition.go
Outdated
argMap[f.name] = vs | ||
continue | ||
} | ||
// flag is bool |
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.
are they still needed?
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.
Removed.
const ( | ||
defaultType OutputType = iota | ||
single | ||
multiple |
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.
I think it's worth to add comments for this values, it took me a while to understand, maybe others will have question too.
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.
Comments added.
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.
Overall, LGTM, some nits left.
pkg/monitor/querier.go
Outdated
@@ -118,6 +120,11 @@ func (monitor *agentMonitor) GetLocalPodNum() int32 { | |||
return int32(monitor.interfaceStore.GetContainerInterfaceNum()) | |||
} | |||
|
|||
// GetInterfacesByType gets the interface information by type. | |||
func (monitor *agentMonitor) GetInterfacesByType(interfaceType interfacestore.InterfaceType) []*interfacestore.InterfaceConfig { |
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.
@jianjuns Do you think we should access interfacestore in the handler directly?
pkg/antctl/command_definition.go
Outdated
defaultType OutputType = iota | ||
// single represents the output type is single item. | ||
single | ||
// multiple represents the output type is list. |
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.
nit, maybe add the modifier always
?
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.
Updated.
pkg/antctl/command_definition.go
Outdated
@@ -473,7 +496,7 @@ func (cd *commandDefinition) newCommandRunE(c *client) func(*cobra.Command, []st | |||
if err != nil { | |||
return err | |||
} | |||
return cd.output(resp, os.Stdout, formatterType(outputFormat), cd.getEndpoint().single() || argGet) | |||
return cd.output(resp, os.Stdout, formatterType(outputFormat), cd.getEndpoint().OutputType() != multiple && (cd.getEndpoint().OutputType() == single || argGet)) |
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.
nit: it would be better to have a variable for cd.getEndpoint().OutputType() != multiple && (cd.getEndpoint().OutputType() == single || argGet)
.
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.
Updated
isSingle bool | ||
path string | ||
params []flagInfo | ||
outputType OutputType |
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.
Have a question here: for registered APIGroup exposed via API Server, could we have the "single" case, but is the API path exposes a single object, but not a type of resources?
I am asking because I am adding the controller-info cmd which should be exposed via aggregated API, and wondering it should be declared as a resourceEndpoint or nonResourceEndpoint in antctl.go. The former does not have the "single" type now,
@weiqiangt @tnqn
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.
I think the controller-info
is a similar case of version
.
If you specified a resourceName, the antctl would always assume the response is single. The antctl does not know what the content is but just dispatch the response to the transform function according to definition.
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.
Sure. My question is could resourceEndpoint also have the "single" type case, or it should be always "multiple"?
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.
For now, if a resourceEndpoint specified resourceName
then it is single inexplicitly, otherwise, the antctl will only assume the response is single when running command with arg.
I don't think @lzhecheng implemented multiple for resourceEndpoint, but developers can make the both single transform func and list transform func expect same type to hack a multiple behavior.
* This command is under get group to get pod interface information. Users can filter the result with "podName" as an arg or "namespace" as an flag. The default namespace is "default". * Remove duplicated flag in commandDefinition. * Turn isSingle field in nonResourceEndpoint to outputType which is an enum, including default, single and multiple Co-Authored-By: Antonin Bas <[email protected]>
LGTM |
/test-all |
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.
LGTM
* This command is under get group to get pod interface information. Users can filter the result with "podName" as an arg or "namespace" as an flag. The default namespace is "default". * Remove duplicated flag in commandDefinition. * Turn isSingle field in nonResourceEndpoint to outputType which is an enum, including default, single and multiple Co-Authored-By: Antonin Bas <[email protected]>
This reverts commit eee580b.
This reverts commit eee580b.
Issue #275
Implemented antctl subcommand: pod-interface
Users can filter the result with "podName" as an arg or "namespace"
as an flag. The default namespace is "default".
The output should be like this: