Skip to content

Conversation

@Naveen-Gopu-F5
Copy link
Contributor

Proposed changes

UseCase
We need dataplane_api_key in authorization header to make api call to Dataplane API. We want to make api_endpoint value as dataplane api which is proxy/copy of nginx plus api that we use as part of NGINXaaS for Azure.

So, when the VMSS (azure scaling group) scales out/scales in, nginx-asg-sync make api calls to dataplane api which updates the upstream servers.

Upstream server update logs

Screenshot 2025-12-19 at 3 11 25 PM

Load balancing incase of 2 instances

Screenshot 2025-12-19 at 12 28 49 PM

Manually scaled to 4 instances

Load balancing after upstream servers updated by nginx-asg-sync

Screenshot 2025-12-19 at 12 23 44 PM

Checklist

Before creating a PR, run through this checklist and mark each as complete.

  • I have read the CONTRIBUTING doc
  • I have added tests that prove my fix is effective or that my feature works
  • I have checked that all unit tests pass after adding my changes
  • I have updated necessary documentation
  • I have rebased my branch onto main
  • I will ensure my PR is targeting the main branch and pulling from my branch from my own fork

@Naveen-Gopu-F5 Naveen-Gopu-F5 requested a review from a team as a code owner December 19, 2025 11:10
@github-actions
Copy link

github-actions bot commented Dec 19, 2025

🎉 Thank you for your contribution! It appears you have not yet signed the F5 Contributor License Agreement (CLA), which is required for your changes to be incorporated into an F5 Open Source Software (OSS) project. Please kindly read the F5 CLA and reply on a new comment with the following text to agree:


I have hereby read the F5 CLA and agree to its terms


You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot.

@github-actions github-actions bot added the documentation Pull requests/issues for documentation label Dec 19, 2025
@Naveen-Gopu-F5
Copy link
Contributor Author

Naveen-Gopu-F5 commented Dec 19, 2025

I have hereby read the F5 CLA and agree to its terms

@Naveen-Gopu-F5
Copy link
Contributor Author

recheck

Copy link

@fblr fblr left a comment

Choose a reason for hiding this comment

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

Hello,
You are suggesting a change to a generic open source project. I understand this is the change required by NGINXaaS, but you are actually adding an API key in the Authorization header - there is nothing N4A-specific here. Why not remove the data plane mentioning (DataplaneAPIKey -> APIKey and so on) from the code and leave it as an example in the description?
The second related problem: you unconditionally add a Content-Type header - please make it configurable.

Comment on lines +224 to +235
func NewHTTPClient(cfg *commonConfig) *http.Client {
headers := NewHeaders(cfg)
baseTransport := &http.Transport{}

return &http.Client{
Transport: &headerTransport{
headers: headers,
transport: baseTransport,
},
Timeout: connTimeoutInSecs * time.Second,
}
}
Copy link

Choose a reason for hiding this comment

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

we don't want to add a performance penalty if a customer is not using dataplane_api_key.

As written, all requests are going to jump through headerTransport, whether it needs to or not. This creates a little more pressure on the garbage collector and uses more compute than needed. Let's add a check in here to only add headers if we need to.

Maybe something like (untested):

Suggested change
func NewHTTPClient(cfg *commonConfig) *http.Client {
headers := NewHeaders(cfg)
baseTransport := &http.Transport{}
return &http.Client{
Transport: &headerTransport{
headers: headers,
transport: baseTransport,
},
Timeout: connTimeoutInSecs * time.Second,
}
}
func NewHTTPClient(cfg *commonConfig) *http.Client {
var rt http.RoundTripper = &http.Transport{}
if h := NewHeaders(cfg); len(h) > 0 {
rt = &headerTransport{headers: h, transport: rt}
}
return &http.Client{
Transport: rt
Timeout: connTimeoutInSecs * time.Second,
}
}

Comment on lines +203 to +214
func (t *headerTransport) RoundTrip(req *http.Request) (*http.Response, error) {
clonedReq := req.Clone(req.Context())

for key, values := range t.headers {
for _, value := range values {
clonedReq.Header.Add(key, value)
}
}

if len(clonedReq.Header) > maxHeaders {
return nil, fmt.Errorf("number of headers in request exceeds the maximum allowed (%d)", maxHeaders)
}
Copy link

Choose a reason for hiding this comment

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

it's nice to have these sanity checks at the top of function to conserve CPU. If we have too many headers, we've wasted some time cloning and adding.

I think we can tweak the condition and prevent some doomed allocations.

Something like (untested):

Suggested change
func (t *headerTransport) RoundTrip(req *http.Request) (*http.Response, error) {
clonedReq := req.Clone(req.Context())
for key, values := range t.headers {
for _, value := range values {
clonedReq.Header.Add(key, value)
}
}
if len(clonedReq.Header) > maxHeaders {
return nil, fmt.Errorf("number of headers in request exceeds the maximum allowed (%d)", maxHeaders)
}
func (t *headerTransport) RoundTrip(req *http.Request) (*http.Response, error) {
if len(req.Header) + len(t.headers) > maxHeaders {
return nil, fmt.Errorf("number of headers in request exceeds the maximum allowed (%d)", maxHeaders)
}
clonedReq := req.Clone(req.Context())
for key, values := range t.headers {
for _, value := range values {
clonedReq.Header.Add(key, value)
}
}

```
- The `api_endpoint` key defines the NGINX Plus API endpoint.
- The `dataplane_api_key` key (optional) defines the API key for authenticating with the [Dataplane API](https://docs.nginx.com/nginxaas/azure/loadbalancer-kubernetes/#view-nginxaas-data-plane-api-endpoint-using-the-azure-portal)
Copy link

Choose a reason for hiding this comment

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

let's get specific here

Suggested change
- The `dataplane_api_key` key (optional) defines the API key for authenticating with the [Dataplane API](https://docs.nginx.com/nginxaas/azure/loadbalancer-kubernetes/#view-nginxaas-data-plane-api-endpoint-using-the-azure-portal)
- The `dataplane_api_key` key (optional) defines the API key for authenticating with the [NGINXaaS For Azure Dataplane API](https://docs.nginx.com/nginxaas/azure/loadbalancer-kubernetes/#view-nginxaas-data-plane-api-endpoint-using-the-azure-portal).

Comment on lines +54 to +55
- The `dataplane_api_key` key (optional) defines the API key for authenticating with the [Dataplane API](https://docs.nginx.com/nginxaas/azure/loadbalancer-kubernetes/#view-nginxaas-data-plane-api-endpoint-using-the-azure-portal)
of [NGINXaaS for Azure](https://docs.nginx.com/nginxaas/azure).
Copy link

Choose a reason for hiding this comment

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

we can use the same phrasing in both places

Suggested change
- The `dataplane_api_key` key (optional) defines the API key for authenticating with the [Dataplane API](https://docs.nginx.com/nginxaas/azure/loadbalancer-kubernetes/#view-nginxaas-data-plane-api-endpoint-using-the-azure-portal)
of [NGINXaaS for Azure](https://docs.nginx.com/nginxaas/azure).
- The `dataplane_api_key` key (optional) defines the API key for authenticating with the [NGINXaaS for Azure Dataplane API](https://docs.nginx.com/nginxaas/azure/loadbalancer-kubernetes/#view-nginxaas-data-plane-api-endpoint-using-the-azure-portal).

}

// Helper function to check if a string contains a substring.
func containsString(s, substr string) bool {
Copy link

Choose a reason for hiding this comment

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

Comment on lines +244 to +246
} else {
log.Printf("[optional] DataplaneAPIKey not configured")
}
Copy link

Choose a reason for hiding this comment

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

this log message doesn't seem that useful

Suggested change
} else {
log.Printf("[optional] DataplaneAPIKey not configured")
}
}

Comment on lines +6 to +7
# optional dataplane_api_key
# dataplane_api_key: your-api-key
Copy link

Choose a reason for hiding this comment

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

alternate design idea:

What if instead of dataplane_api_key we kept it very generic, like additional_headers?

So for NGINXaaS integration the user would add:

additional_headers:
  Authorization: ApiKey $BASE_64_KEY

That'd keep the open source codebase very decoupled from our cloud offering, but put a little more work onto the user to get the headers right.

What do y'all think?

{
name: "with empty API key",
config: &commonConfig{
DataplaneAPIKey: "",
Copy link

Choose a reason for hiding this comment

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

due to golang zero values, this test case is the same as the "without API key" case, let's drop it. Each test case should improve our test code coverage, and if it doesn't then we don't need to keep it around.

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

Labels

documentation Pull requests/issues for documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants