Skip to content

Commit 366c18d

Browse files
Merge pull request #1 from patrickdillon/openshift-ash-fork
UPSTREAM: 5532: Azure Stack Fork
2 parents b18718c + 02b0259 commit 366c18d

File tree

33 files changed

+162
-63
lines changed

33 files changed

+162
-63
lines changed

api/v1beta1/types_class.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ type AzureClusterClassSpec struct {
4848
// - GermanCloud: "AzureGermanCloud"
4949
// - PublicCloud: "AzurePublicCloud"
5050
// - USGovernmentCloud: "AzureUSGovernmentCloud"
51+
// - StackCloud: "HybridEnvironment"
5152
//
5253
// Note that values other than the default must also be accompanied by corresponding changes to the
5354
// aso-controller-settings Secret to configure ASO to refer to the non-Public cloud. ASO currently does
@@ -77,6 +78,12 @@ type AzureClusterClassSpec struct {
7778
// See: https://learn.microsoft.com/azure/reliability/availability-zones-overview
7879
// +optional
7980
FailureDomains clusterv1.FailureDomains `json:"failureDomains,omitempty"`
81+
82+
// ARMEndpoint specifies a URL for the ARM Resource Manager endpoint.
83+
// It may only be specified when the AzureEnvironment is set to AzureStackCloud,
84+
// in which case it is required.
85+
// +optional
86+
ARMEndpoint string `json:"armEndpoint,omitempty"`
8087
}
8188

8289
// AzureManagedControlPlaneClassSpec defines the AzureManagedControlPlane properties that may be shared across several azure managed control planes.
@@ -186,6 +193,7 @@ type AzureManagedControlPlaneClassSpec struct {
186193
// - PublicCloud: "AzurePublicCloud"
187194
// - USGovernmentCloud: "AzureUSGovernmentCloud"
188195
//
196+
//
189197
// Note that values other than the default must also be accompanied by corresponding changes to the
190198
// aso-controller-settings Secret to configure ASO to refer to the non-Public cloud. ASO currently does
191199
// not support referring to multiple different clouds in a single installation. The following fields must

azure/defaults.go

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
2828
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5"
2929
"github.com/Azure/azure-sdk-for-go/sdk/tracing/azotel"
30+
"github.com/Azure/go-autorest/autorest/azure"
3031
"go.opentelemetry.io/otel"
3132

3233
"sigs.k8s.io/cluster-api-provider-azure/util/tele"
@@ -44,6 +45,8 @@ const (
4445
ChinaCloudName = "AzureChinaCloud"
4546
// USGovernmentCloudName is the name of the Azure US Government cloud.
4647
USGovernmentCloudName = "AzureUSGovernmentCloud"
48+
// StackCloudName is the name for Azure Stack hybrid cloud environments.
49+
StackCloudName = "HybridEnvironment"
4750
)
4851

4952
const (
@@ -109,6 +112,16 @@ const (
109112
CustomHeaderPrefix = "infrastructure.cluster.x-k8s.io/custom-header-"
110113
)
111114

115+
const (
116+
// StackAPIVersion is the API version profile to set for ARM clients. See:
117+
// https://learn.microsoft.com/en-us/azure-stack/user/azure-stack-profiles-azure-resource-manager-versions?view=azs-2408#overview-of-the-2020-09-01-hybrid-profile
118+
StackAPIVersionProfile = "2020-06-01"
119+
120+
// StackDiskAPIVersionProfile is the API Version to set for the disk client.
121+
// API Version Profile "2020-06-01" is not supported for disks.
122+
StackDiskAPIVersionProfile = "2018-06-01"
123+
)
124+
112125
var (
113126
// LinuxBootstrapExtensionCommand is the command the VM bootstrap extension will execute to verify Linux nodes bootstrap completes successfully.
114127
LinuxBootstrapExtensionCommand = fmt.Sprintf("for i in $(seq 1 %d); do test -f %s && break; if [ $i -eq %d ]; then exit 1; else sleep %d; fi; done", bootstrapExtensionRetries, bootstrapSentinelFile, bootstrapExtensionRetries, bootstrapExtensionSleep)
@@ -357,7 +370,7 @@ func UserAgent() string {
357370
}
358371

359372
// ARMClientOptions returns default ARM client options for CAPZ SDK v2 requests.
360-
func ARMClientOptions(azureEnvironment string, extraPolicies ...policy.Policy) (*arm.ClientOptions, error) {
373+
func ARMClientOptions(azureEnvironment, armEndpoint string, extraPolicies ...policy.Policy) (*arm.ClientOptions, error) {
361374
opts := &arm.ClientOptions{}
362375

363376
switch azureEnvironment {
@@ -367,6 +380,21 @@ func ARMClientOptions(azureEnvironment string, extraPolicies ...policy.Policy) (
367380
opts.Cloud = cloud.AzureChina
368381
case USGovernmentCloudName:
369382
opts.Cloud = cloud.AzureGovernment
383+
case StackCloudName:
384+
cloudEnv, err := azure.EnvironmentFromURL(armEndpoint)
385+
if err != nil {
386+
return nil, fmt.Errorf("unable to get Azure Stack cloud environment: %w", err)
387+
}
388+
opts.APIVersion = StackAPIVersionProfile
389+
opts.Cloud = cloud.Configuration{
390+
ActiveDirectoryAuthorityHost: cloudEnv.ActiveDirectoryEndpoint,
391+
Services: map[cloud.ServiceName]cloud.ServiceConfiguration{
392+
cloud.ResourceManager: {
393+
Audience: cloudEnv.TokenAudience,
394+
Endpoint: cloudEnv.ResourceManagerEndpoint,
395+
},
396+
},
397+
}
370398
case "":
371399
// No cloud name provided, so leave at defaults.
372400
default:

azure/defaults_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ func TestARMClientOptions(t *testing.T) {
3838
tests := []struct {
3939
name string
4040
cloudName string
41+
armEndpoint string
4142
expectedCloud cloud.Configuration
4243
expectError bool
4344
}{
@@ -72,7 +73,7 @@ func TestARMClientOptions(t *testing.T) {
7273
t.Parallel()
7374
g := NewWithT(t)
7475

75-
opts, err := ARMClientOptions(tc.cloudName)
76+
opts, err := ARMClientOptions(tc.cloudName, tc.armEndpoint)
7677
if tc.expectError {
7778
g.Expect(err).To(HaveOccurred())
7879
return
@@ -99,7 +100,7 @@ func TestPerCallPolicies(t *testing.T) {
99100
defer server.Close()
100101

101102
// Call the factory function and ensure it has both PerCallPolicies.
102-
opts, err := ARMClientOptions("")
103+
opts, err := ARMClientOptions("", "")
103104
g.Expect(err).NotTo(HaveOccurred())
104105
g.Expect(opts.PerCallPolicies).To(HaveLen(2))
105106
g.Expect(opts.PerCallPolicies).To(ContainElement(BeAssignableToTypeOf(correlationIDPolicy{})))
@@ -184,7 +185,7 @@ func TestCustomPutPatchHeaderPolicy(t *testing.T) {
184185
// Create options with a custom PUT/PATCH header per-call policy
185186
getterMock := mock_azure.NewMockResourceSpecGetterWithHeaders(mockCtrl)
186187
getterMock.EXPECT().CustomHeaders().Return(tc.headers).AnyTimes()
187-
opts, err := ARMClientOptions("", CustomPutPatchHeaderPolicy{Headers: tc.headers})
188+
opts, err := ARMClientOptions("", "", CustomPutPatchHeaderPolicy{Headers: tc.headers})
188189
g.Expect(err).NotTo(HaveOccurred())
189190

190191
// Create a request

azure/errors.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ func ResourceNotFound(err error) bool {
3434
return errors.As(err, &rerr) && rerr.StatusCode == http.StatusNotFound
3535
}
3636

37+
// BadRequest parses an error to check if it its status code is Bad Request (400).
38+
func BadRequest(err error) bool {
39+
var rerr *azcore.ResponseError
40+
return errors.As(err, &rerr) && rerr.StatusCode == http.StatusBadRequest
41+
}
42+
3743
// VMDeletedError is returned when a virtual machine is deleted outside of capz.
3844
type VMDeletedError struct {
3945
ProviderID string

azure/scope/clients.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,12 @@ func (c *AzureClients) HashKey() string {
8181
return base64.URLEncoding.EncodeToString(hasher.Sum(nil))
8282
}
8383

84-
func (c *AzureClients) setCredentialsWithProvider(ctx context.Context, subscriptionID, environmentName string, credentialsProvider CredentialsProvider) error {
84+
func (c *AzureClients) setCredentialsWithProvider(ctx context.Context, subscriptionID, environmentName, armEndpoint string, credentialsProvider CredentialsProvider) error {
8585
if credentialsProvider == nil {
8686
return fmt.Errorf("credentials provider cannot have an empty value")
8787
}
8888

89-
settings, err := c.getSettingsFromEnvironment(environmentName)
89+
settings, err := c.getSettingsFromEnvironment(environmentName, armEndpoint)
9090
if err != nil {
9191
return err
9292
}
@@ -121,7 +121,7 @@ func (c *AzureClients) setCredentialsWithProvider(ctx context.Context, subscript
121121
return err
122122
}
123123

124-
func (c *AzureClients) getSettingsFromEnvironment(environmentName string) (s auth.EnvironmentSettings, err error) {
124+
func (c *AzureClients) getSettingsFromEnvironment(environmentName, armEndpoint string) (s auth.EnvironmentSettings, err error) {
125125
s = auth.EnvironmentSettings{
126126
Values: map[string]string{},
127127
}
@@ -138,6 +138,8 @@ func (c *AzureClients) getSettingsFromEnvironment(environmentName string) (s aut
138138
setValue(s, "AZURE_AD_RESOURCE")
139139
if v := s.Values["AZURE_ENVIRONMENT"]; v == "" {
140140
s.Environment = azureautorest.PublicCloud
141+
} else if len(armEndpoint) > 0 {
142+
s.Environment, err = azureautorest.EnvironmentFromURL(armEndpoint)
141143
} else {
142144
s.Environment, err = azureautorest.EnvironmentFromName(v)
143145
}

azure/scope/cluster.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ func NewClusterScope(ctx context.Context, params ClusterScopeParams) (*ClusterSc
8484
if err != nil {
8585
return nil, errors.Wrap(err, "failed to init credentials provider")
8686
}
87-
err = params.AzureClients.setCredentialsWithProvider(ctx, params.AzureCluster.Spec.SubscriptionID, params.AzureCluster.Spec.AzureEnvironment, credentialsProvider)
87+
spec := params.AzureCluster.Spec
88+
err = params.AzureClients.setCredentialsWithProvider(ctx, spec.SubscriptionID, spec.AzureEnvironment, spec.ARMEndpoint, credentialsProvider)
8889
if err != nil {
8990
return nil, errors.Wrap(err, "failed to configure azure settings and credentials for Identity")
9091
}
@@ -557,7 +558,7 @@ func (s *ClusterScope) VNetSpec() azure.ASOResourceSpecGetter[*asonetworkv1api20
557558

558559
// PrivateDNSSpec returns the private dns zone spec.
559560
func (s *ClusterScope) PrivateDNSSpec() (zoneSpec azure.ResourceSpecGetter, linkSpec, recordSpec []azure.ResourceSpecGetter) {
560-
if s.IsAPIServerPrivate() {
561+
if s.IsAPIServerPrivate() && !s.IsHybridEnvironment() {
561562
resourceGroup := s.ResourceGroup()
562563
if s.AzureCluster.Spec.NetworkSpec.PrivateDNSZoneResourceGroup != "" {
563564
resourceGroup = s.AzureCluster.Spec.NetworkSpec.PrivateDNSZoneResourceGroup
@@ -1233,3 +1234,8 @@ func (s *ClusterScope) getLastAppliedSecurityRules(nsgName string) map[string]in
12331234
}
12341235
return lastAppliedSecurityRules
12351236
}
1237+
1238+
// IsHybridEnvironment returns true if the cluster is running on Azure Stack.
1239+
func (s *ClusterScope) IsHybridEnvironment() bool {
1240+
return strings.EqualFold(s.Environment.Name, azure.StackCloudName)
1241+
}

azure/scope/machine.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,8 @@ func (m *MachineScope) InitMachineCache(ctx context.Context) error {
150150
}
151151

152152
m.cache.availabilitySetSKU, err = skuCache.Get(ctx, string(armcompute.AvailabilitySetSKUTypesAligned), resourceskus.AvailabilitySets)
153-
if err != nil {
153+
// Resource SKU API for availability sets may not be available in Azure Stack environments.
154+
if err != nil && !strings.EqualFold(m.CloudEnvironment(), "HybridEnvironment") {
154155
return errors.Wrapf(err, "failed to get availability set SKU %s in compute api", string(armcompute.AvailabilitySetSKUTypesAligned))
155156
}
156157
}
@@ -494,12 +495,13 @@ func (m *MachineScope) AvailabilitySetSpec() azure.ResourceSpecGetter {
494495
}
495496

496497
spec := &availabilitysets.AvailabilitySetSpec{
497-
Name: availabilitySetName,
498-
ResourceGroup: m.NodeResourceGroup(),
499-
ClusterName: m.ClusterName(),
500-
Location: m.Location(),
501-
SKU: nil,
502-
AdditionalTags: m.AdditionalTags(),
498+
Name: availabilitySetName,
499+
ResourceGroup: m.NodeResourceGroup(),
500+
ClusterName: m.ClusterName(),
501+
Location: m.Location(),
502+
CloudEnvironment: m.CloudEnvironment(),
503+
SKU: nil,
504+
AdditionalTags: m.AdditionalTags(),
503505
}
504506

505507
if m.cache != nil {

azure/scope/managedcontrolplane.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ func NewManagedControlPlaneScope(ctx context.Context, params ManagedControlPlane
9797
return nil, errors.Wrap(err, "failed to init credentials provider")
9898
}
9999

100-
if err := params.AzureClients.setCredentialsWithProvider(ctx, params.ControlPlane.Spec.SubscriptionID, params.ControlPlane.Spec.AzureEnvironment, credentialsProvider); err != nil {
100+
if err := params.AzureClients.setCredentialsWithProvider(ctx, params.ControlPlane.Spec.SubscriptionID, params.ControlPlane.Spec.AzureEnvironment, "", credentialsProvider); err != nil {
101101
return nil, errors.Wrap(err, "failed to configure azure settings and credentials for Identity")
102102
}
103103

azure/services/availabilitysets/client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ type AzureClient struct {
3434

3535
// NewClient creates a new availability sets client from an authorizer.
3636
func NewClient(auth azure.Authorizer) (*AzureClient, error) {
37-
opts, err := azure.ARMClientOptions(auth.CloudEnvironment())
37+
opts, err := azure.ARMClientOptions(auth.CloudEnvironment(), auth.BaseURI())
3838
if err != nil {
3939
return nil, errors.Wrap(err, "failed to create availabilitysets client options")
4040
}

azure/services/availabilitysets/spec.go

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,27 @@ package availabilitysets
1919
import (
2020
"context"
2121
"strconv"
22+
"strings"
2223

2324
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5"
2425
"github.com/pkg/errors"
2526
"k8s.io/utils/ptr"
2627

2728
infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
29+
"sigs.k8s.io/cluster-api-provider-azure/azure"
2830
"sigs.k8s.io/cluster-api-provider-azure/azure/converters"
2931
"sigs.k8s.io/cluster-api-provider-azure/azure/services/resourceskus"
3032
)
3133

3234
// AvailabilitySetSpec defines the specification for an availability set.
3335
type AvailabilitySetSpec struct {
34-
Name string
35-
ResourceGroup string
36-
ClusterName string
37-
Location string
38-
SKU *resourceskus.SKU
39-
AdditionalTags infrav1.Tags
36+
Name string
37+
ResourceGroup string
38+
ClusterName string
39+
Location string
40+
CloudEnvironment string
41+
SKU *resourceskus.SKU
42+
AdditionalTags infrav1.Tags
4043
}
4144

4245
// ResourceName returns the name of the availability set.
@@ -64,20 +67,10 @@ func (s *AvailabilitySetSpec) Parameters(_ context.Context, existing interface{}
6467
return nil, nil
6568
}
6669

67-
if s.SKU == nil {
68-
return nil, errors.New("unable to get required availability set SKU from machine cache")
69-
}
70-
71-
var faultDomainCount *int32
72-
faultDomainCountStr, ok := s.SKU.GetCapability(resourceskus.MaximumPlatformFaultDomainCount)
73-
if !ok {
74-
return nil, errors.Errorf("unable to get required availability set SKU capability %s", resourceskus.MaximumPlatformFaultDomainCount)
75-
}
76-
count, err := strconv.ParseInt(faultDomainCountStr, 10, 32)
70+
faultDomainCount, err := getFaultDomainCount(s.SKU, s.CloudEnvironment)
7771
if err != nil {
78-
return nil, errors.Wrapf(err, "unable to parse availability set fault domain count")
72+
return nil, err
7973
}
80-
faultDomainCount = ptr.To[int32](int32(count))
8174

8275
asParams := armcompute.AvailabilitySet{
8376
SKU: &armcompute.SKU{
@@ -98,3 +91,27 @@ func (s *AvailabilitySetSpec) Parameters(_ context.Context, existing interface{}
9891

9992
return asParams, nil
10093
}
94+
95+
func getFaultDomainCount(SKU *resourceskus.SKU, cloudEnvironment string) (*int32, error) {
96+
// Azure Stack environments may not implement the resource SKU API
97+
// for availability sets. Use a default value instead.
98+
if strings.EqualFold(cloudEnvironment, azure.StackCloudName) {
99+
return ptr.To(int32(2)), nil
100+
}
101+
102+
if SKU == nil {
103+
return nil, errors.New("unable to get required availability set SKU from machine cache")
104+
}
105+
106+
var faultDomainCount *int32
107+
faultDomainCountStr, ok := SKU.GetCapability(resourceskus.MaximumPlatformFaultDomainCount)
108+
if !ok {
109+
return nil, errors.Errorf("unable to get required availability set SKU capability %s", resourceskus.MaximumPlatformFaultDomainCount)
110+
}
111+
count, err := strconv.ParseInt(faultDomainCountStr, 10, 32)
112+
if err != nil {
113+
return nil, errors.Wrapf(err, "unable to parse availability set fault domain count")
114+
}
115+
faultDomainCount = ptr.To[int32](int32(count))
116+
return faultDomainCount, nil
117+
}

0 commit comments

Comments
 (0)