From afe9596e92e65269bc63e28cbf04c1db1ed994b3 Mon Sep 17 00:00:00 2001 From: RavinderReddyF5 Date: Tue, 15 Jul 2025 00:32:21 +0530 Subject: [PATCH 1/2] adding test --- bigip.go | 50 ++++++++++++++++++++++---- bigiq_functional_test.go | 34 ++++++++++++++++++ go.mod | 2 +- ltm.go | 27 +++++++------- ltm_functional_test.go | 76 ++++++++++++++++++++++++++++++++++++++++ net_test.go | 16 +++++---- 6 files changed, 178 insertions(+), 27 deletions(-) create mode 100644 bigiq_functional_test.go create mode 100644 ltm_functional_test.go diff --git a/bigip.go b/bigip.go index 37fd906..9dd1fd3 100644 --- a/bigip.go +++ b/bigip.go @@ -19,6 +19,7 @@ import ( "errors" "fmt" "io" + "log" "net/http" "net/url" "os" @@ -153,26 +154,29 @@ func NewTokenSession(bigipConfig *Config) (b *BigIP, err error) { Timeout struct { Timeout int64 } + RefreshToken interface{} `json:"refreshToken,omitempty"` } type timeoutReq struct { Timeout int64 `json:"timeout"` } - // type timeoutResp struct { - // Timeout struct { - // Timeout int64 - // } - // } - + if bigipConfig.LoginReference == "" { + logInfo("LoginProviderName not set, defaulting to 'tmos'") + bigipConfig.LoginReference = "tmos" + } else { + logInfo("Using LoginProviderName: %s", bigipConfig.LoginReference) + } auth := authReq{ bigipConfig.Username, bigipConfig.Password, bigipConfig.LoginReference, } + logDebug("Auth request: %+v", auth) marshalJSONauth, err := json.Marshal(auth) if err != nil { + logError("Failed to marshal auth request: %v", err) return } @@ -182,6 +186,7 @@ func NewTokenSession(bigipConfig *Config) (b *BigIP, err error) { Body: string(marshalJSONauth), ContentType: "application/json", } + logDebug("APIRequest: %+v", req) b = NewSession(bigipConfig) if !bigipConfig.CertVerifyDisable { @@ -204,6 +209,7 @@ func NewTokenSession(bigipConfig *Config) (b *BigIP, err error) { b.Transport.TLSClientConfig.RootCAs = rootCAs } resp, err := b.APICall(req) + logDebug("APIResponse: %s", string(resp)) if err != nil { return } @@ -219,6 +225,15 @@ func NewTokenSession(bigipConfig *Config) (b *BigIP, err error) { return } + // Use authResp struct to detect BIG-IQ + if aresp.RefreshToken != nil { + logInfo("Detected BIG-IQ platform (RefreshToken present in authResp)") + // Optionally: b.Platform = "BIG-IQ" + } else { + logInfo("Detected BIG-IP platform (RefreshToken absent in authResp)") + // Optionally: b.Platform = "BIG-IP" + } + if aresp.Token.Token == "" { err = fmt.Errorf("unable to acquire authentication token") return @@ -227,7 +242,7 @@ func NewTokenSession(bigipConfig *Config) (b *BigIP, err error) { b.Token = aresp.Token.Token //Once we have obtained a token, we should actually apply the configured timeout to it - if time.Duration(aresp.Timeout.Timeout)*time.Second != bigipConfig.ConfigOptions.TokenTimeout { // The inital value is the max timespan + if aresp.RefreshToken == nil && time.Duration(aresp.Timeout.Timeout)*time.Second != bigipConfig.ConfigOptions.TokenTimeout { // The inital value is the max timespan timeout := timeoutReq{ int64(bigipConfig.ConfigOptions.TokenTimeout.Seconds()), } @@ -617,6 +632,12 @@ func (b *BigIP) getForEntity(e interface{}, path ...string) (error, bool) { } resp, err := b.APICall(req) + // add log for debugging + logDebug("APIRequest: %+v", req) + logDebug("APIResponse: %s", string(resp)) + // If we get an error, we need to check if it is a 404 error. + // check error code and response body + if err != nil { var reqError RequestError json.Unmarshal(resp, &reqError) @@ -736,3 +757,18 @@ func toBoolString(b bool, trueStr, falseStr string) string { } return falseStr } + +// Logging helpers +func logDebug(format string, v ...interface{}) { + if os.Getenv("LOG_LEVEL") == "DEBUG" { + log.Printf("[DEBUG] "+format, v...) + } +} +func logInfo(format string, v ...interface{}) { + if os.Getenv("LOG_LEVEL") == "INFO" || os.Getenv("LOG_LEVEL") == "DEBUG" { + log.Printf("[INFO] "+format, v...) + } +} +func logError(format string, v ...interface{}) { + log.Printf("[ERROR] "+format, v...) +} diff --git a/bigiq_functional_test.go b/bigiq_functional_test.go new file mode 100644 index 0000000..02587dd --- /dev/null +++ b/bigiq_functional_test.go @@ -0,0 +1,34 @@ +package bigip + +import ( + "os" + "testing" +) + +func TestGetManagedDevicesFunc(t *testing.T) { + host := os.Getenv("BIGIQ_HOST") + user := os.Getenv("BIGIQ_USER") + pass := os.Getenv("BIGIQ_PASSWORD") + if host == "" || user == "" || pass == "" { + t.Skip("Environment variables BIGIQ_HOST, BIGIQ_USER, BIGIQ_PASSWORD must be set") + } + b, err := NewTokenSession(&Config{ + Address: host, + Username: user, + Password: pass, + CertVerifyDisable: true, // Disable cert verification for testing + }) + if err != nil { + t.Fatalf("NewTokenSession failed: %v", err) + } + devices, err := b.GetManagedDevices() + if err != nil { + t.Fatalf("GetManagedDevices failed: %v", err) + } + if devices == nil || len(devices.DevicesInfo) == 0 { + t.Errorf("No managed devices found") + } + for _, d := range devices.DevicesInfo { + t.Logf("Managed Device: Address=%s, Hostname=%s, UUID=%s", d.Address, d.Hostname, d.UUID) + } +} diff --git a/go.mod b/go.mod index 508d569..f064edd 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/f5devcentral/go-bigip -go 1.20 +go 1.22 require github.com/stretchr/testify v1.2.1 diff --git a/ltm.go b/ltm.go index 6d9d2f0..f6948b5 100644 --- a/ltm.go +++ b/ltm.go @@ -1623,8 +1623,8 @@ type Originsrecords struct { } type Originsrecord struct { - Name string `json:"name"` - app_service string `json:"appService,omitempty"` + Name string `json:"name"` + AppService string `json:"appService,omitempty"` } func (p *Snat) MarshalJSON() ([]byte, error) { @@ -4259,17 +4259,7 @@ func (b *BigIP) AddRequestLogProfile(config *RequestLogProfile) error { return b.post(config, uriLtm, uriProfile, uriRequestLog) } -// DeleteRequestLogProfile removes a Request Log profile. -func (b *BigIP) DeleteRequestLogProfile(name string) error { - return b.delete(uriLtm, uriProfile, uriRequestLog, name) -} - -// ModifyRequestLogProfile allows you to change any attribute of a RequestLog profile. -// Fields that can be modified are referenced in the RequestLogProfile struct. -func (b *BigIP) ModifyRequestLogProfile(name string, config *RequestLogProfile) error { - return b.patch(config, uriLtm, uriProfile, uriRequestLog, name) -} - +// GetRequestLogProfile gets a request log profile by name. Returns nil if the request log profile does not exist func (b *BigIP) GetRequestLogProfile(name string) (*RequestLogProfile, error) { var requestLogProfile RequestLogProfile err, ok := b.getForEntity(&requestLogProfile, uriLtm, uriProfile, uriRequestLog, name) @@ -4284,6 +4274,17 @@ func (b *BigIP) GetRequestLogProfile(name string) (*RequestLogProfile, error) { return &requestLogProfile, nil } +// DeleteRequestLogProfile removes a Request Log profile. +func (b *BigIP) DeleteRequestLogProfile(name string) error { + return b.delete(uriLtm, uriProfile, uriRequestLog, name) +} + +// ModifyRequestLogProfile allows you to change any attribute of a RequestLog profile. +// Fields that can be modified are referenced in the RequestLogProfile struct. +func (b *BigIP) ModifyRequestLogProfile(name string, config *RequestLogProfile) error { + return b.patch(config, uriLtm, uriProfile, uriRequestLog, name) +} + type BotDefenseProfile struct { Name string `json:"name,omitempty"` Partition string `json:"partition,omitempty"` diff --git a/ltm_functional_test.go b/ltm_functional_test.go new file mode 100644 index 0000000..871c864 --- /dev/null +++ b/ltm_functional_test.go @@ -0,0 +1,76 @@ +package bigip + +import ( + "os" + "testing" +) + +// Functional test for IRules() using a real or integration environment +func TestFunctionalIRules(t *testing.T) { + address := os.Getenv("BIGIP_HOST") + username := os.Getenv("BIGIP_USER") + password := os.Getenv("BIGIP_PASSWORD") + t.Logf("BIGIP_HOST=%s, BIGIP_USER=%s", address, username) // Debug log for env vars + if address == "" || username == "" || password == "" { + t.Skip("BIGIP environment variables not set") + } + cfg := &Config{ + Address: address, + Username: username, + Password: password, + CertVerifyDisable: true, // Disable certificate validation for testing + } + t.Logf("Config: %+v", cfg) // Debug log for config + client, err := NewTokenSession(cfg) + if err != nil { + t.Fatalf("NewTokenSession error: %v", err) + } + + rules, err := client.IRules() + if err != nil { + t.Fatalf("IRules() error: %v", err) + } + if rules == nil || len(rules.IRules) == 0 { + t.Errorf("No iRules returned, got: %+v", rules) + } else { + for _, rule := range rules.IRules { + t.Logf("iRule: %+v", rule) + } + } +} + +// Functional test to get a specific iRule by name +// Functional test to test the IRule() method for fetching a specific iRule by name +func TestGetSpecificIRule(t *testing.T) { + address := os.Getenv("BIGIP_HOST") + username := os.Getenv("BIGIP_USER") + password := os.Getenv("BIGIP_PASSWORD") + + iruleName := "default_f5_healt" + t.Logf("BIGIP_HOST=%s, BIGIP_USER=%s, BIGIP_IRULE_NAME=%s", address, username, iruleName) + if address == "" || username == "" || password == "" || iruleName == "" { + t.Skip("BIGIP environment variables not set or iRule name missing") + } + cfg := &Config{ + Address: address, + Username: username, + Password: password, + CertVerifyDisable: true, + } + client, err := NewTokenSession(cfg) + if err != nil { + t.Fatalf("NewTokenSession error: %v", err) + } + + irule, err := client.IRule(iruleName) + if err != nil { + t.Fatalf("IRule error: %v", err) + } + if irule == nil { + t.Errorf("iRule '%s' not found", iruleName) + } else { + t.Logf("Found iRule '%s': %+v", iruleName, irule) + } +} + +// Add more functional tests for other LTM features as needed diff --git a/net_test.go b/net_test.go index 6970e40..01b3dbf 100644 --- a/net_test.go +++ b/net_test.go @@ -1,13 +1,13 @@ package bigip import ( + "io" "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" - "io/ioutil" ) type NetTestSuite struct { @@ -21,7 +21,7 @@ type NetTestSuite struct { func (s *NetTestSuite) SetupSuite() { s.Server = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - body, _ := ioutil.ReadAll(r.Body) + body, _ := io.ReadAll(r.Body) s.LastRequestBody = string(body) s.LastRequest = r if s.ResponseFunc != nil { @@ -29,7 +29,7 @@ func (s *NetTestSuite) SetupSuite() { } })) - s.Client = NewSession(s.Server.URL, "", "", "", nil) + s.Client = NewSession(&Config{Address: s.Server.URL}) } func (s *NetTestSuite) TearDownSuite() { @@ -131,7 +131,8 @@ func (s *NetTestSuite) TestSelfIPs() { } func (s *NetTestSuite) TestCreateSelfIP() { - err := s.Client.CreateSelfIP("0.0.0.0", "0.0.0.0/20", "vlan") + ip := &SelfIP{Name: "0.0.0.0", Address: "0.0.0.0/20", Vlan: "vlan"} + err := s.Client.CreateSelfIP(ip) assert.Nil(s.T(), err) assertRestCall(s, "POST", "/mgmt/tm/net/self", `{"name":"0.0.0.0","address":"0.0.0.0/20", "vlan":"vlan"}`) @@ -243,7 +244,9 @@ func (s *NetTestSuite) TestVlans() { } func (s *NetTestSuite) TestCreateVLan() { - err := s.Client.CreateVlan("name", 1) + vlan := &Vlan{Name: "name", Tag: 1} + // SFlow field uses the zero value (empty struct) + err := s.Client.CreateVlan(vlan) assert.Nil(s.T(), err) assertRestCall(s, "POST", "/mgmt/tm/net/vlan", `{"name":"name", "tag":1, "sflow":{}}`) @@ -295,7 +298,8 @@ func (s *NetTestSuite) TestRoutes() { } func (s *NetTestSuite) TestCreateRoute() { - err := s.Client.CreateRoute("default_route", "default", "0.0.0.0") + route := &Route{Name: "default_route", Network: "default", Gateway: "0.0.0.0"} + err := s.Client.CreateRoute(route) assert.Nil(s.T(), err) assertRestCall(s, "POST", "/mgmt/tm/net/route", `{"name":"default_route", "network":"default", "gw":"0.0.0.0"}`) From 21eadd8f0f11b3af981fa378b191f52fee9a0812 Mon Sep 17 00:00:00 2001 From: RavinderReddyF5 Date: Wed, 30 Jul 2025 21:33:07 +0530 Subject: [PATCH 2/2] adding fixes --- bigip.go | 37 ------------------------------------- ltm_irules_test.go | 0 2 files changed, 37 deletions(-) create mode 100644 ltm_irules_test.go diff --git a/bigip.go b/bigip.go index 9dd1fd3..b5fe5cc 100644 --- a/bigip.go +++ b/bigip.go @@ -19,7 +19,6 @@ import ( "errors" "fmt" "io" - "log" "net/http" "net/url" "os" @@ -160,33 +159,24 @@ func NewTokenSession(bigipConfig *Config) (b *BigIP, err error) { type timeoutReq struct { Timeout int64 `json:"timeout"` } - if bigipConfig.LoginReference == "" { - logInfo("LoginProviderName not set, defaulting to 'tmos'") bigipConfig.LoginReference = "tmos" - } else { - logInfo("Using LoginProviderName: %s", bigipConfig.LoginReference) } auth := authReq{ bigipConfig.Username, bigipConfig.Password, bigipConfig.LoginReference, } - logDebug("Auth request: %+v", auth) - marshalJSONauth, err := json.Marshal(auth) if err != nil { - logError("Failed to marshal auth request: %v", err) return } - req := &APIRequest{ Method: "post", URL: "mgmt/shared/authn/login", Body: string(marshalJSONauth), ContentType: "application/json", } - logDebug("APIRequest: %+v", req) b = NewSession(bigipConfig) if !bigipConfig.CertVerifyDisable { @@ -209,7 +199,6 @@ func NewTokenSession(bigipConfig *Config) (b *BigIP, err error) { b.Transport.TLSClientConfig.RootCAs = rootCAs } resp, err := b.APICall(req) - logDebug("APIResponse: %s", string(resp)) if err != nil { return } @@ -225,15 +214,6 @@ func NewTokenSession(bigipConfig *Config) (b *BigIP, err error) { return } - // Use authResp struct to detect BIG-IQ - if aresp.RefreshToken != nil { - logInfo("Detected BIG-IQ platform (RefreshToken present in authResp)") - // Optionally: b.Platform = "BIG-IQ" - } else { - logInfo("Detected BIG-IP platform (RefreshToken absent in authResp)") - // Optionally: b.Platform = "BIG-IP" - } - if aresp.Token.Token == "" { err = fmt.Errorf("unable to acquire authentication token") return @@ -633,8 +613,6 @@ func (b *BigIP) getForEntity(e interface{}, path ...string) (error, bool) { resp, err := b.APICall(req) // add log for debugging - logDebug("APIRequest: %+v", req) - logDebug("APIResponse: %s", string(resp)) // If we get an error, we need to check if it is a 404 error. // check error code and response body @@ -757,18 +735,3 @@ func toBoolString(b bool, trueStr, falseStr string) string { } return falseStr } - -// Logging helpers -func logDebug(format string, v ...interface{}) { - if os.Getenv("LOG_LEVEL") == "DEBUG" { - log.Printf("[DEBUG] "+format, v...) - } -} -func logInfo(format string, v ...interface{}) { - if os.Getenv("LOG_LEVEL") == "INFO" || os.Getenv("LOG_LEVEL") == "DEBUG" { - log.Printf("[INFO] "+format, v...) - } -} -func logError(format string, v ...interface{}) { - log.Printf("[ERROR] "+format, v...) -} diff --git a/ltm_irules_test.go b/ltm_irules_test.go new file mode 100644 index 0000000..e69de29