Skip to content

Commit 2f0311d

Browse files
[feat] synchronize NC version with NMA programmed goal state (#3790)
* Make nma api calls and imds call to update delegated NC goal state update values add client test scenarios . . . . . * Address comments * fix lint error * address code review comments * update imds version and * Check imds version to confirm if imds changes are available * fix formatting errors * . * Update naming convenction * remove nmagent api check
1 parent 116b02a commit 2f0311d

File tree

6 files changed

+790
-23
lines changed

6 files changed

+790
-23
lines changed

cns/fakes/imdsclientfake.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package fakes
88

99
import (
1010
"context"
11+
"net"
1112

1213
"github.com/Azure/azure-container-networking/cns/imds"
1314
"github.com/Azure/azure-container-networking/cns/wireserver"
@@ -57,3 +58,40 @@ func (m *MockIMDSClient) GetVMUniqueID(ctx context.Context) (string, error) {
5758

5859
return "55b8499d-9b42-4f85-843f-24ff69f4a643", nil
5960
}
61+
62+
func (m *MockIMDSClient) GetNetworkInterfaces(ctx context.Context) ([]imds.NetworkInterface, error) {
63+
if ctx.Value(SimulateError) != nil {
64+
return nil, imds.ErrUnexpectedStatusCode
65+
}
66+
67+
// Parse MAC addresses for testing
68+
macAddr1, _ := net.ParseMAC("00:15:5d:01:02:01")
69+
macAddr2, _ := net.ParseMAC("00:15:5d:01:02:02")
70+
71+
// Return some mock network interfaces for testing
72+
return []imds.NetworkInterface{
73+
{
74+
InterfaceCompartmentID: "nc1",
75+
MacAddress: imds.HardwareAddr(macAddr1),
76+
},
77+
{
78+
InterfaceCompartmentID: "nc2",
79+
MacAddress: imds.HardwareAddr(macAddr2),
80+
},
81+
}, nil
82+
}
83+
84+
func (m *MockIMDSClient) GetIMDSVersions(ctx context.Context) (*imds.APIVersionsResponse, error) {
85+
if ctx.Value(SimulateError) != nil {
86+
return nil, imds.ErrUnexpectedStatusCode
87+
}
88+
89+
// Return supported API versions including the expected one
90+
return &imds.APIVersionsResponse{
91+
APIVersions: []string{
92+
"2017-03-01",
93+
"2021-01-01",
94+
"2025-07-24",
95+
},
96+
}, nil
97+
}

cns/imds/client.go

Lines changed: 120 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package imds
66
import (
77
"context"
88
"encoding/json"
9+
"net"
910
"net/http"
1011
"net/url"
1112

@@ -46,7 +47,10 @@ func RetryAttempts(attempts uint) ClientOption {
4647
const (
4748
vmUniqueIDProperty = "vmId"
4849
imdsComputePath = "/metadata/instance/compute"
49-
imdsComputeAPIVersion = "api-version=2021-01-01"
50+
imdsNetworkPath = "/metadata/instance/network"
51+
imdsVersionsPath = "/metadata/versions"
52+
imdsDefaultAPIVersion = "api-version=2021-01-01"
53+
imdsNCDetailsVersion = "api-version=2025-07-24"
5054
imdsFormatJSON = "format=json"
5155
metadataHeaderKey = "Metadata"
5256
metadataHeaderValue = "true"
@@ -79,7 +83,7 @@ func NewClient(opts ...ClientOption) *Client {
7983
func (c *Client) GetVMUniqueID(ctx context.Context) (string, error) {
8084
var vmUniqueID string
8185
err := retry.Do(func() error {
82-
computeDoc, err := c.getInstanceComputeMetadata(ctx)
86+
computeDoc, err := c.getInstanceMetadata(ctx, imdsComputePath, imdsDefaultAPIVersion)
8387
if err != nil {
8488
return errors.Wrap(err, "error getting IMDS compute metadata")
8589
}
@@ -102,14 +106,40 @@ func (c *Client) GetVMUniqueID(ctx context.Context) (string, error) {
102106
return vmUniqueID, nil
103107
}
104108

105-
func (c *Client) getInstanceComputeMetadata(ctx context.Context) (map[string]any, error) {
106-
imdsComputeURL, err := url.JoinPath(c.config.endpoint, imdsComputePath)
109+
func (c *Client) GetNetworkInterfaces(ctx context.Context) ([]NetworkInterface, error) {
110+
var networkData NetworkInterfaces
111+
err := retry.Do(func() error {
112+
networkInterfaces, err := c.getInstanceMetadata(ctx, imdsNetworkPath, imdsNCDetailsVersion)
113+
if err != nil {
114+
return errors.Wrap(err, "error getting IMDS network metadata")
115+
}
116+
117+
// Parse the network metadata to the expected structure
118+
jsonData, err := json.Marshal(networkInterfaces)
119+
if err != nil {
120+
return errors.Wrap(err, "error marshaling network metadata")
121+
}
122+
123+
if err := json.Unmarshal(jsonData, &networkData); err != nil {
124+
return errors.Wrap(err, "error unmarshaling network metadata")
125+
}
126+
return nil
127+
}, retry.Context(ctx), retry.Attempts(c.config.retryAttempts), retry.DelayType(retry.BackOffDelay))
107128
if err != nil {
108-
return nil, errors.Wrap(err, "unable to build path to IMDS compute metadata")
129+
return nil, errors.Wrap(err, "external call failed")
109130
}
110-
imdsComputeURL = imdsComputeURL + "?" + imdsComputeAPIVersion + "&" + imdsFormatJSON
111131

112-
req, err := http.NewRequestWithContext(ctx, http.MethodGet, imdsComputeURL, http.NoBody)
132+
return networkData.Interface, nil
133+
}
134+
135+
func (c *Client) getInstanceMetadata(ctx context.Context, imdsMetadataPath, imdsAPIVersion string) (map[string]any, error) {
136+
imdsRequestURL, err := url.JoinPath(c.config.endpoint, imdsMetadataPath)
137+
if err != nil {
138+
return nil, errors.Wrap(err, "unable to build path to IMDS metadata for path"+imdsMetadataPath)
139+
}
140+
imdsRequestURL = imdsRequestURL + "?" + imdsAPIVersion + "&" + imdsFormatJSON
141+
142+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, imdsRequestURL, http.NoBody)
113143
if err != nil {
114144
return nil, errors.Wrap(err, "error building IMDS http request")
115145
}
@@ -133,3 +163,86 @@ func (c *Client) getInstanceComputeMetadata(ctx context.Context) (map[string]any
133163

134164
return m, nil
135165
}
166+
167+
func (c *Client) GetIMDSVersions(ctx context.Context) (*APIVersionsResponse, error) {
168+
var versionsResp APIVersionsResponse
169+
err := retry.Do(func() error {
170+
// Build the URL for the versions endpoint
171+
imdsRequestURL, err := url.JoinPath(c.config.endpoint, imdsVersionsPath)
172+
if err != nil {
173+
return errors.Wrap(err, "unable to build path to IMDS versions endpoint")
174+
}
175+
176+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, imdsRequestURL, http.NoBody)
177+
if err != nil {
178+
return errors.Wrap(err, "error building IMDS versions http request")
179+
}
180+
181+
req.Header.Add(metadataHeaderKey, metadataHeaderValue)
182+
resp, err := c.cli.Do(req)
183+
if err != nil {
184+
return errors.Wrap(err, "error querying IMDS versions API")
185+
}
186+
defer resp.Body.Close()
187+
188+
if resp.StatusCode != http.StatusOK {
189+
return errors.Wrapf(ErrUnexpectedStatusCode, "unexpected status code %d", resp.StatusCode)
190+
}
191+
192+
if err := json.NewDecoder(resp.Body).Decode(&versionsResp); err != nil {
193+
return errors.Wrap(err, "error decoding IMDS versions response as json")
194+
}
195+
196+
return nil
197+
}, retry.Context(ctx), retry.Attempts(c.config.retryAttempts), retry.DelayType(retry.BackOffDelay))
198+
if err != nil {
199+
return nil, errors.Wrap(err, "exhausted retries querying IMDS versions")
200+
}
201+
202+
return &versionsResp, nil
203+
}
204+
205+
// Required for marshaling/unmarshaling of mac address
206+
type HardwareAddr net.HardwareAddr
207+
208+
func (h *HardwareAddr) MarshalJSON() ([]byte, error) {
209+
data, err := json.Marshal(net.HardwareAddr(*h).String())
210+
if err != nil {
211+
return nil, errors.Wrap(err, "failed to marshal hardware address")
212+
}
213+
return data, nil
214+
}
215+
216+
func (h *HardwareAddr) UnmarshalJSON(data []byte) error {
217+
var s string
218+
if err := json.Unmarshal(data, &s); err != nil {
219+
return errors.Wrap(err, "failed to unmarshal JSON data")
220+
}
221+
mac, err := net.ParseMAC(s)
222+
if err != nil {
223+
return errors.Wrap(err, "failed to parse MAC address")
224+
}
225+
*h = HardwareAddr(mac)
226+
return nil
227+
}
228+
229+
func (h *HardwareAddr) String() string {
230+
return net.HardwareAddr(*h).String()
231+
}
232+
233+
// NetworkInterface represents a network interface from IMDS
234+
type NetworkInterface struct {
235+
// IMDS returns compartment fields - these are mapped to NC ID and NC version
236+
MacAddress HardwareAddr `json:"macAddress"`
237+
InterfaceCompartmentID string `json:"interfaceCompartmentID,omitempty"`
238+
}
239+
240+
// NetworkInterfaces represents the network interfaces from IMDS
241+
type NetworkInterfaces struct {
242+
Interface []NetworkInterface `json:"interface"`
243+
}
244+
245+
// APIVersionsResponse represents versions form IMDS
246+
type APIVersionsResponse struct {
247+
APIVersions []string `json:"apiVersions"`
248+
}

0 commit comments

Comments
 (0)