diff --git a/pkg/antctl/antctl.go b/pkg/antctl/antctl.go index 6f04c85b564..95616ff8060 100644 --- a/pkg/antctl/antctl.go +++ b/pkg/antctl/antctl.go @@ -38,6 +38,10 @@ type transformedVersionResponse struct { AntctlVersion string `json:"antctlVersion" yaml:"antctlVersion"` } +type transformedAgentInfoResponse struct { + handlers.ComponentAgentInfoResponse `json:",inline" yaml:",inline"` +} + // versionTransform is the AddonTransform for the version command. This function // will try to parse the response as a ComponentVersionResponse and then populate // it with the version of antctl to a transformedVersionResponse object. @@ -76,6 +80,19 @@ var CommandList = &commandList{ CommandGroup: flat, AddonTransform: versionTransform, }, + { + Use: "agent-info", + Short: "Print agent's information", + Long: "Print agent's information of the antctl.", + HandlerFactory: new(handlers.AgentInfo), + GroupVersion: &systemGroup, + TransformedResponse: reflect.TypeOf(transformedAgentInfoResponse{}), + Agent: true, + Controller: false, + SingleObject: true, + CommandGroup: flat, + AddonTransform: nil, + }, }, codec: scheme.Codecs, } diff --git a/pkg/antctl/handlers/agentinfo.go b/pkg/antctl/handlers/agentinfo.go new file mode 100644 index 00000000000..536097a8243 --- /dev/null +++ b/pkg/antctl/handlers/agentinfo.go @@ -0,0 +1,57 @@ +// Copyright 2020 Antrea Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package handlers + +import ( + "encoding/json" + "net/http" + + "k8s.io/klog" + + "github.com/vmware-tanzu/antrea/pkg/monitor" +) + +var _ Factory = new(AgentInfo) + +// ComponentAgentInfoResponse describes the internal response struct of agent-info command. +// It only contains agent's node subnet and connection status of controller, OVSDB and openflow. +type ComponentAgentInfoResponse struct { + ControllerConn string `json:"controllerConn,omitempty" yaml:"controllerConn,omitempty"` + OVSDBConn string `json:"ovsdbConn,omitempty" yaml:"ovsdbConn,omitempty"` + OFConn string `json:"ofConn,omitempty" yaml:"ofConn,omitempty"` + Subnet string `json:"subnet,omitempty" yaml:"subnet,omitempty"` +} + +// AgentInfo is the implementation of the Factory interface for the agent-info command. +type AgentInfo struct{} + +// Handler returns the function which can handle queries issued by agent-info commands, +// the handler function populate component's agnet-info to the ComponentAgentInfoResponse. +func (v *AgentInfo) Handler(aq monitor.AgentQuerier, cq monitor.ControllerQuerier) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var m ComponentAgentInfoResponse + if aq != nil { + m.ControllerConn = aq.GetControllerConnection() + m.OVSDBConn = aq.GetOVSDBConnection() + m.OFConn = aq.GetOFConnection() + m.Subnet = aq.GetNodeSubnet() + } + err := json.NewEncoder(w).Encode(m) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + klog.Errorf("Error when encoding ComponentAgentInfoResponse to json: %v", err) + } + } +} diff --git a/pkg/antctl/handlers/agentinfo_test.go b/pkg/antctl/handlers/agentinfo_test.go new file mode 100644 index 00000000000..47f1c6cc590 --- /dev/null +++ b/pkg/antctl/handlers/agentinfo_test.go @@ -0,0 +1,64 @@ +// Copyright 2020 Antrea Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package handlers + +import ( + "net/http" + "net/http/httptest" + "testing" + + mockmonitor "github.com/vmware-tanzu/antrea/pkg/monitor/testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" +) + +func TestAgentInfo(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + testcases := map[string]struct { + controllerConn string + ovsdbConn string + ofConn string + subent string + expectedOutput string + expectedStatusCode int + }{ + "AgentInfo": { + controllerConn: "UP", + ovsdbConn: "UP", + ofConn: "UP", + subent: "192.168.1.0/24", + expectedOutput: "{\"controllerConn\":\"UP\",\"ovsdbConn\":\"UP\",\"ofConn\":\"UP\",\"subnet\":\"192.168.1.0/24\"}\n", + expectedStatusCode: http.StatusOK, + }, + } + for k, tc := range testcases { + t.Run(k, func(t *testing.T) { + req, err := http.NewRequest("GET", "/", nil) + assert.Nil(t, err) + recorder := httptest.NewRecorder() + aq := mockmonitor.NewMockAgentQuerier(ctrl) + aq.EXPECT().GetControllerConnection().Return(tc.controllerConn) + aq.EXPECT().GetOVSDBConnection().Return(tc.ovsdbConn) + aq.EXPECT().GetOFConnection().Return(tc.ofConn) + aq.EXPECT().GetNodeSubnet().Return(tc.subent) + new(AgentInfo).Handler(aq, nil).ServeHTTP(recorder, req) + assert.Equal(t, tc.expectedStatusCode, recorder.Code, k) + assert.Equal(t, tc.expectedOutput, recorder.Body.String(), k) + }) + } +} diff --git a/pkg/monitor/querier.go b/pkg/monitor/querier.go index 6458b3e58b3..7f801e3b67a 100644 --- a/pkg/monitor/querier.go +++ b/pkg/monitor/querier.go @@ -45,6 +45,10 @@ type AgentQuerier interface { Querier GetOVSFlowTable() map[string]int32 GetLocalPodNum() int32 + GetOVSDBConnection() string + GetOFConnection() string + GetControllerConnection() string + GetNodeSubnet() string } type ControllerQuerier interface { @@ -151,6 +155,35 @@ func (monitor *agentMonitor) GetAgentConditions(ovsConnected bool) []v1beta1.Age } } +// GetOVSDBConnection tells if OVSDB is connected. +func (monitor *agentMonitor) GetOVSDBConnection() string { + if _, err := monitor.ovsBridgeClient.GetOVSVersion(); err != nil { + return "DOWN" + } + return "UP" +} + +// GetOFConnection tells if openflow is connected. +func (monitor *agentMonitor) GetOFConnection() string { + if monitor.ofClient.IsConnected() { + return "UP" + } + return "DOWN" +} + +// GetControllerConnection tells if controller is connected. +func (monitor *agentMonitor) GetControllerConnection() string { + if !monitor.networkPolicyInfoQuerier.GetControllerConnectionStatus() { + return "DOWN" + } + return "UP" +} + +// GetNodeSubnet gets the node subnet. +func (monitor *agentMonitor) GetNodeSubnet() string { + return monitor.nodeSubnet +} + func (monitor *agentMonitor) GetVersion() string { return version.GetFullVersion() } diff --git a/pkg/monitor/testing/mock_monitor.go b/pkg/monitor/testing/mock_monitor.go index c8a73e90598..68b7fa04d75 100644 --- a/pkg/monitor/testing/mock_monitor.go +++ b/pkg/monitor/testing/mock_monitor.go @@ -49,6 +49,20 @@ func (m *MockAgentQuerier) EXPECT() *MockAgentQuerierMockRecorder { return m.recorder } +// GetControllerConnection mocks base method +func (m *MockAgentQuerier) GetControllerConnection() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetControllerConnection") + ret0, _ := ret[0].(string) + return ret0 +} + +// GetControllerConnection indicates an expected call of GetControllerConnection +func (mr *MockAgentQuerierMockRecorder) GetControllerConnection() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetControllerConnection", reflect.TypeOf((*MockAgentQuerier)(nil).GetControllerConnection)) +} + // GetLocalPodNum mocks base method func (m *MockAgentQuerier) GetLocalPodNum() int32 { m.ctrl.T.Helper() @@ -77,6 +91,48 @@ func (mr *MockAgentQuerierMockRecorder) GetNetworkPolicyControllerInfo() *gomock return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNetworkPolicyControllerInfo", reflect.TypeOf((*MockAgentQuerier)(nil).GetNetworkPolicyControllerInfo)) } +// GetNodeSubnet mocks base method +func (m *MockAgentQuerier) GetNodeSubnet() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetNodeSubnet") + ret0, _ := ret[0].(string) + return ret0 +} + +// GetNodeSubnet indicates an expected call of GetNodeSubnet +func (mr *MockAgentQuerierMockRecorder) GetNodeSubnet() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNodeSubnet", reflect.TypeOf((*MockAgentQuerier)(nil).GetNodeSubnet)) +} + +// GetOFConnection mocks base method +func (m *MockAgentQuerier) GetOFConnection() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetOFConnection") + ret0, _ := ret[0].(string) + return ret0 +} + +// GetOFConnection indicates an expected call of GetOFConnection +func (mr *MockAgentQuerierMockRecorder) GetOFConnection() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOFConnection", reflect.TypeOf((*MockAgentQuerier)(nil).GetOFConnection)) +} + +// GetOVSDBConnection mocks base method +func (m *MockAgentQuerier) GetOVSDBConnection() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetOVSDBConnection") + ret0, _ := ret[0].(string) + return ret0 +} + +// GetOVSDBConnection indicates an expected call of GetOVSDBConnection +func (mr *MockAgentQuerierMockRecorder) GetOVSDBConnection() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOVSDBConnection", reflect.TypeOf((*MockAgentQuerier)(nil).GetOVSDBConnection)) +} + // GetOVSFlowTable mocks base method func (m *MockAgentQuerier) GetOVSFlowTable() map[string]int32 { m.ctrl.T.Helper()