Skip to content

Commit eb0edda

Browse files
authored
Merge pull request #116 from dkennetzoracle/oke-provider
Add OKE (Oracle Cloud) provider for dranet
2 parents 254736d + feaffbe commit eb0edda

3 files changed

Lines changed: 276 additions & 2 deletions

File tree

pkg/cloudprovider/oke/oke.go

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/*
2+
Copyright The Kubernetes Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package oke
18+
19+
import (
20+
"context"
21+
"encoding/json"
22+
"fmt"
23+
"io"
24+
"net/http"
25+
"time"
26+
27+
"k8s.io/apimachinery/pkg/util/wait"
28+
"k8s.io/klog/v2"
29+
30+
resourceapi "k8s.io/api/resource/v1"
31+
"sigs.k8s.io/dranet/pkg/apis"
32+
"sigs.k8s.io/dranet/pkg/cloudprovider"
33+
)
34+
35+
const (
36+
OKEAttrPrefix = "oke.dra.net"
37+
38+
AttrOKEShape = OKEAttrPrefix + "/" + "shape"
39+
AttrOKEFaultDomain = OKEAttrPrefix + "/" + "faultDomain"
40+
AttrOKEAvailabilityDomain = OKEAttrPrefix + "/" + "availabilityDomain"
41+
42+
// imdsEndpoint is the Oracle Cloud Instance Metadata Service endpoint.
43+
imdsEndpoint = "http://169.254.169.254/opc/v2"
44+
)
45+
46+
// imdsInstanceMetadata contains the fields we care about from the OCI IMDS
47+
// instance metadata response.
48+
type imdsInstanceMetadata struct {
49+
Shape string `json:"shape"`
50+
FaultDomain string `json:"faultDomain"`
51+
AvailabilityDomain string `json:"availabilityDomain"`
52+
}
53+
54+
var _ cloudprovider.CloudInstance = (*OKEInstance)(nil)
55+
56+
// OKEInstance holds OCI/OKE specific instance data.
57+
type OKEInstance struct {
58+
Shape string
59+
FaultDomain string
60+
AvailabilityDomain string
61+
}
62+
63+
// GetDeviceAttributes returns OKE-specific attributes for a device.
64+
// These are node-level attributes applied to all devices since OCI IMDS
65+
// does not expose per-RDMA-NIC metadata.
66+
func (o *OKEInstance) GetDeviceAttributes(id cloudprovider.DeviceIdentifiers) map[resourceapi.QualifiedName]resourceapi.DeviceAttribute {
67+
attributes := make(map[resourceapi.QualifiedName]resourceapi.DeviceAttribute)
68+
69+
if o.Shape != "" {
70+
attributes[AttrOKEShape] = resourceapi.DeviceAttribute{StringValue: &o.Shape}
71+
}
72+
if o.FaultDomain != "" {
73+
attributes[AttrOKEFaultDomain] = resourceapi.DeviceAttribute{StringValue: &o.FaultDomain}
74+
}
75+
if o.AvailabilityDomain != "" {
76+
attributes[AttrOKEAvailabilityDomain] = resourceapi.DeviceAttribute{StringValue: &o.AvailabilityDomain}
77+
}
78+
79+
return attributes
80+
}
81+
82+
// GetDeviceConfig returns nil as OCI does not provide device-specific
83+
// network configuration through IMDS.
84+
func (o *OKEInstance) GetDeviceConfig(id cloudprovider.DeviceIdentifiers) *apis.NetworkConfig {
85+
return nil
86+
}
87+
88+
// OnOKE returns true if running on an Oracle Cloud Infrastructure instance.
89+
// Detection is done by probing the OCI IMDS v2 endpoint.
90+
func OnOKE(ctx context.Context) bool {
91+
pollCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
92+
defer cancel()
93+
return wait.PollUntilContextCancel(pollCtx, 1*time.Second, true, func(ctx context.Context) (bool, error) {
94+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, imdsEndpoint+"/instance/", nil)
95+
if err != nil {
96+
return false, nil
97+
}
98+
req.Header.Set("Authorization", "Bearer Oracle")
99+
100+
resp, err := http.DefaultClient.Do(req)
101+
if err != nil {
102+
return false, nil
103+
}
104+
defer resp.Body.Close()
105+
return resp.StatusCode == http.StatusOK, nil
106+
}) == nil
107+
}
108+
109+
// GetInstance retrieves OCI instance properties by querying the IMDS.
110+
func GetInstance(ctx context.Context) (cloudprovider.CloudInstance, error) {
111+
var instance *OKEInstance
112+
err := wait.PollUntilContextTimeout(ctx, 1*time.Second, 15*time.Second, true, func(ctx context.Context) (bool, error) {
113+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, imdsEndpoint+"/instance/", nil)
114+
if err != nil {
115+
klog.Infof("could not create OCI IMDS request ... retrying: %v", err)
116+
return false, nil
117+
}
118+
req.Header.Set("Authorization", "Bearer Oracle")
119+
120+
resp, err := http.DefaultClient.Do(req)
121+
if err != nil {
122+
klog.Infof("could not reach OCI IMDS ... retrying: %v", err)
123+
return false, nil
124+
}
125+
defer resp.Body.Close()
126+
127+
if resp.StatusCode != http.StatusOK {
128+
klog.Infof("OCI IMDS returned status %d ... retrying", resp.StatusCode)
129+
return false, nil
130+
}
131+
132+
body, err := io.ReadAll(resp.Body)
133+
if err != nil {
134+
klog.Infof("could not read OCI IMDS response ... retrying: %v", err)
135+
return false, nil
136+
}
137+
138+
var metadata imdsInstanceMetadata
139+
if err := json.Unmarshal(body, &metadata); err != nil {
140+
return false, fmt.Errorf("could not parse OCI IMDS response: %w", err)
141+
}
142+
143+
instance = &OKEInstance{
144+
Shape: metadata.Shape,
145+
FaultDomain: metadata.FaultDomain,
146+
AvailabilityDomain: metadata.AvailabilityDomain,
147+
}
148+
return true, nil
149+
})
150+
if err != nil {
151+
return nil, err
152+
}
153+
return instance, nil
154+
}

pkg/cloudprovider/oke/oke_test.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
Copyright The Kubernetes Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package oke
18+
19+
import (
20+
"testing"
21+
22+
"sigs.k8s.io/dranet/pkg/cloudprovider"
23+
24+
"github.com/google/go-cmp/cmp"
25+
"github.com/google/go-cmp/cmp/cmpopts"
26+
resourceapi "k8s.io/api/resource/v1"
27+
"k8s.io/utils/ptr"
28+
)
29+
30+
func TestGetDeviceAttributes(t *testing.T) {
31+
tests := []struct {
32+
name string
33+
instance *OKEInstance
34+
id cloudprovider.DeviceIdentifiers
35+
want map[resourceapi.QualifiedName]resourceapi.DeviceAttribute
36+
}{
37+
{
38+
name: "instance with all metadata",
39+
instance: &OKEInstance{
40+
Shape: "BM.GPU.H100.8",
41+
FaultDomain: "FAULT-DOMAIN-1",
42+
AvailabilityDomain: "TrcQ:US-ASHBURN-AD-2",
43+
},
44+
id: cloudprovider.DeviceIdentifiers{Name: "dev1"},
45+
want: map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
46+
AttrOKEShape: {StringValue: ptr.To("BM.GPU.H100.8")},
47+
AttrOKEFaultDomain: {StringValue: ptr.To("FAULT-DOMAIN-1")},
48+
AttrOKEAvailabilityDomain: {StringValue: ptr.To("TrcQ:US-ASHBURN-AD-2")},
49+
},
50+
},
51+
{
52+
name: "instance with only shape",
53+
instance: &OKEInstance{
54+
Shape: "VM.Standard.E3.Flex",
55+
FaultDomain: "",
56+
AvailabilityDomain: "",
57+
},
58+
id: cloudprovider.DeviceIdentifiers{Name: "dev1"},
59+
want: map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
60+
AttrOKEShape: {StringValue: ptr.To("VM.Standard.E3.Flex")},
61+
},
62+
},
63+
{
64+
name: "instance with no metadata",
65+
instance: &OKEInstance{
66+
Shape: "",
67+
FaultDomain: "",
68+
AvailabilityDomain: "",
69+
},
70+
id: cloudprovider.DeviceIdentifiers{Name: "dev1"},
71+
want: map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{},
72+
},
73+
{
74+
name: "attributes are node-level, same for any device identifier",
75+
instance: &OKEInstance{
76+
Shape: "BM.GPU.H100.8",
77+
FaultDomain: "FAULT-DOMAIN-3",
78+
AvailabilityDomain: "TrcQ:US-ASHBURN-AD-2",
79+
},
80+
id: cloudprovider.DeviceIdentifiers{
81+
Name: "pci-0000-0c-00-0",
82+
MAC: "a0:88:c2:a7:c5:04",
83+
PCIAddress: "0000:0c:00.0",
84+
},
85+
want: map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
86+
AttrOKEShape: {StringValue: ptr.To("BM.GPU.H100.8")},
87+
AttrOKEFaultDomain: {StringValue: ptr.To("FAULT-DOMAIN-3")},
88+
AttrOKEAvailabilityDomain: {StringValue: ptr.To("TrcQ:US-ASHBURN-AD-2")},
89+
},
90+
},
91+
}
92+
93+
for _, tt := range tests {
94+
t.Run(tt.name, func(t *testing.T) {
95+
got := tt.instance.GetDeviceAttributes(tt.id)
96+
if diff := cmp.Diff(tt.want, got, cmpopts.EquateEmpty()); diff != "" {
97+
t.Errorf("GetDeviceAttributes() returned unexpected diff (-want, +got):\n%s", diff)
98+
}
99+
})
100+
}
101+
}
102+
103+
func TestGetDeviceConfig(t *testing.T) {
104+
instance := &OKEInstance{
105+
Shape: "BM.GPU.H100.8",
106+
FaultDomain: "FAULT-DOMAIN-1",
107+
AvailabilityDomain: "TrcQ:US-ASHBURN-AD-2",
108+
}
109+
got := instance.GetDeviceConfig(cloudprovider.DeviceIdentifiers{Name: "dev1"})
110+
if got != nil {
111+
t.Errorf("GetDeviceConfig() = %v, want nil", got)
112+
}
113+
}

pkg/inventory/cloud.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,22 +29,29 @@ import (
2929
"sigs.k8s.io/dranet/pkg/cloudprovider"
3030
"sigs.k8s.io/dranet/pkg/cloudprovider/azure"
3131
"sigs.k8s.io/dranet/pkg/cloudprovider/gce"
32+
"sigs.k8s.io/dranet/pkg/cloudprovider/oke"
3233
)
3334

3435
// getInstanceProperties get the instace properties and stores them in a global variable to be used in discovery
3536
func getInstanceProperties(ctx context.Context) cloudprovider.CloudInstance {
3637
var err error
3738
var instance cloudprovider.CloudInstance
38-
if metadata.OnGCE() {
39+
switch {
40+
case metadata.OnGCE():
3941
// Get google compute instance metadata for network interfaces
4042
// https://cloud.google.com/compute/docs/metadata/predefined-metadata-keys
4143
klog.Infof("running on GCE")
4244
instance, err = gce.GetInstance(ctx)
43-
} else if azure.OnAzure(ctx) {
45+
case azure.OnAzure(ctx):
4446
// Get Azure instance metadata for placement group and VM size
4547
// https://learn.microsoft.com/en-us/azure/virtual-machines/instance-metadata-service
4648
klog.Infof("running on Azure")
4749
instance, err = azure.GetInstance(ctx)
50+
case oke.OnOKE(ctx):
51+
// Get OCI instance metadata for shape, fault domain, and availability domain
52+
// https://docs.oracle.com/en-us/iaas/Content/Compute/Tasks/gettingmetadata.htm
53+
klog.Infof("running on OKE")
54+
instance, err = oke.GetInstance(ctx)
4855
}
4956
if err != nil {
5057
klog.Infof("could not get instance properties: %v", err)

0 commit comments

Comments
 (0)