Skip to content

Commit c2face0

Browse files
committed
pkg/storage/azure: support vnet and subnet discovery
1 parent 2d5ad04 commit c2face0

File tree

3 files changed

+175
-14
lines changed

3 files changed

+175
-14
lines changed

pkg/storage/azure/azure.go

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -550,22 +550,11 @@ func (d *driver) assurePrivateAccount(cfg *Azure, infra *configv1.Infrastructure
550550
// user did not request private storage account setup - skip.
551551
return "", nil
552552
}
553-
if d.Config.NetworkAccess.Internal.VNetName == "" || d.Config.NetworkAccess.Internal.SubnetName == "" {
554-
return "", fmt.Errorf("both vnetName and subnetName are required to setup a private storage account")
555-
}
556-
557-
var privateEndpointName string
558-
if d.Config.NetworkAccess.Internal != nil && d.Config.NetworkAccess.Internal.PrivateEndpointName != "" {
559-
privateEndpointName = d.Config.NetworkAccess.Internal.PrivateEndpointName
560-
} else {
561-
privateEndpointName = generateAccountName(infra.Status.InfrastructureName)
562-
}
563553

564554
environment, err := getEnvironmentByName(d.Config.CloudName)
565555
if err != nil {
566556
return "", err
567557
}
568-
569558
azclient, err := azureclient.New(&azureclient.Options{
570559
Environment: environment,
571560
TenantID: cfg.TenantID,
@@ -580,20 +569,49 @@ func (d *driver) assurePrivateAccount(cfg *Azure, infra *configv1.Infrastructure
580569
return "", err
581570
}
582571

572+
internalConfig := d.Config.NetworkAccess.Internal
573+
if internalConfig == nil {
574+
// avoid nil pointer checks everywhere - this will happen when the user
575+
// wants the operator to discover vnet and subnet names.
576+
internalConfig = &imageregistryv1.AzureNetworkAccessInternal{}
577+
}
578+
579+
privateEndpointName := internalConfig.PrivateEndpointName
580+
if privateEndpointName == "" {
581+
privateEndpointName = generateAccountName(infra.Status.InfrastructureName)
582+
}
583+
583584
// the last step in this function is to disable public network for the
584585
// storage account - if we already did that, then none of the steps
585586
// below need to be executed.
586587
if azclient.IsStorageAccountPrivate(d.Context, accountName) {
587588
return privateEndpointName, nil
588589
}
589590

591+
if internalConfig.VNetName == "" {
592+
tagKey := fmt.Sprintf("kubernetes.io_cluster.%s", infra.Status.InfrastructureName)
593+
vnet, err := azclient.GetVNetByTag(d.Context, tagKey, "owned", "shared")
594+
if err != nil {
595+
return "", fmt.Errorf("failed to discover vnet name, please provide network details manually: %q", err)
596+
}
597+
internalConfig.VNetName = *vnet.Name
598+
}
599+
600+
if internalConfig.SubnetName == "" {
601+
subnet, err := azclient.GetSubnetsByVNet(d.Context, internalConfig.VNetName)
602+
if err != nil {
603+
return "", fmt.Errorf("failed to discover subnet name, please provide network details manually: %q", err)
604+
}
605+
internalConfig.SubnetName = *subnet.Name
606+
}
607+
590608
klog.V(3).Infof("configuring private endpoint %q for storage account...", privateEndpointName)
591609
pe, err := azclient.CreatePrivateEndpoint(
592610
d.Context,
593611
&azureclient.PrivateEndpointCreateOptions{
594612
Location: cfg.Region,
595-
VNetName: d.Config.NetworkAccess.Internal.VNetName,
596-
SubnetName: d.Config.NetworkAccess.Internal.SubnetName,
613+
VNetName: internalConfig.VNetName,
614+
SubnetName: internalConfig.SubnetName,
597615
PrivateEndpointName: privateEndpointName,
598616
StorageAccountName: accountName,
599617
},
@@ -605,7 +623,7 @@ func (d *driver) assurePrivateAccount(cfg *Azure, infra *configv1.Infrastructure
605623

606624
klog.V(3).Info("configuring private DNS...")
607625
if err := azclient.ConfigurePrivateDNS(
608-
d.Context, pe, d.Config.NetworkAccess.Internal.VNetName, accountName,
626+
d.Context, pe, internalConfig.VNetName, accountName,
609627
); err != nil {
610628
return privateEndpointName, err
611629
}
@@ -621,6 +639,8 @@ func (d *driver) assurePrivateAccount(cfg *Azure, infra *configv1.Infrastructure
621639
accountName, privateEndpointName,
622640
)
623641

642+
d.Config.NetworkAccess.Internal = internalConfig
643+
624644
return privateEndpointName, nil
625645
}
626646

pkg/storage/azure/azureclient/azureclient.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,59 @@ func (c *Client) getStorageAccount(ctx context.Context, accountName string) (arm
138138
return resp.Account, nil
139139
}
140140

141+
func (c *Client) GetVNetByTag(ctx context.Context, tagKey string, tagValues ...string) (armnetwork.VirtualNetwork, error) {
142+
client, err := armnetwork.NewVirtualNetworksClient(c.subscriptionID, c.creds, c.clientOpts)
143+
if err != nil {
144+
return armnetwork.VirtualNetwork{}, fmt.Errorf("failed to create accounts client: %q", err)
145+
}
146+
147+
pager := client.NewListPager(c.resourceGroupName, nil)
148+
for pager.More() {
149+
page, err := pager.NextPage(ctx)
150+
if err != nil {
151+
return armnetwork.VirtualNetwork{}, err
152+
}
153+
for _, vnet := range page.Value {
154+
tag, ok := vnet.Tags[tagKey]
155+
if !ok {
156+
continue
157+
}
158+
for _, tagValue := range tagValues {
159+
if *tag == tagValue {
160+
return *vnet, nil
161+
}
162+
}
163+
}
164+
}
165+
166+
return armnetwork.VirtualNetwork{}, fmt.Errorf("vnet with tag '%s: %v' not found", tagKey, tagValues)
167+
}
168+
169+
func (c *Client) GetSubnetsByVNet(ctx context.Context, vnetName string) (armnetwork.Subnet, error) {
170+
client, err := armnetwork.NewSubnetsClient(c.subscriptionID, c.creds, c.clientOpts)
171+
if err != nil {
172+
return armnetwork.Subnet{}, fmt.Errorf("failed to create subnets client: %q", err)
173+
}
174+
175+
pager := client.NewListPager(c.resourceGroupName, vnetName, nil)
176+
for pager.More() {
177+
page, err := pager.NextPage(ctx)
178+
if err != nil {
179+
return armnetwork.Subnet{}, err
180+
}
181+
for _, subnet := range page.Value {
182+
// should we match the subnet name with the string "worker"?
183+
// does it even matter? (don't think so)
184+
//
185+
// return the first subnet.
186+
// unless each subnet in the cluster has strict access groups it
187+
// doesn't matter which subnet we choose (worker/master).
188+
return *subnet, nil
189+
}
190+
}
191+
return armnetwork.Subnet{}, fmt.Errorf("no subnets found on vnet %q", vnetName)
192+
}
193+
141194
func (c *Client) UpdateStorageAccountNetworkAccess(ctx context.Context, accountName string, allowPublicAccess bool) error {
142195
client, err := armstorage.NewAccountsClient(c.subscriptionID, c.creds, c.clientOpts)
143196
if err != nil {

test/e2e/azure_test.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,94 @@ func TestAzurePrivateStorageAccount(t *testing.T) {
237237
}
238238
}
239239

240+
func TestPrivateStorageAccountVNetSubnetDiscovery(t *testing.T) {
241+
ctx := context.Background()
242+
243+
kcfg, err := regopclient.GetConfig()
244+
if err != nil {
245+
t.Fatalf("Error building kubeconfig: %s", err)
246+
}
247+
248+
newMockLister, err := listers.NewMockLister(kcfg)
249+
if err != nil {
250+
t.Fatalf("unable to create mock lister: %v", err)
251+
}
252+
253+
mockLister, err := newMockLister.GetListers()
254+
if err != nil {
255+
t.Fatalf("unable to get listers from mock lister: %v", err)
256+
}
257+
258+
infra, err := util.GetInfrastructure(mockLister.StorageListers.Infrastructures)
259+
if err != nil {
260+
t.Fatalf("unable to get install configuration: %v", err)
261+
}
262+
263+
if infra.Status.PlatformStatus.Type != configapiv1.AzurePlatformType {
264+
t.Skip("skipping on non-Azure platform")
265+
}
266+
267+
te := framework.Setup(t)
268+
defer framework.TeardownImageRegistry(te)
269+
270+
framework.DeployImageRegistry(te, nil)
271+
framework.WaitUntilImageRegistryIsAvailable(te)
272+
framework.EnsureInternalRegistryHostnameIsSet(te)
273+
framework.EnsureClusterOperatorStatusIsSet(te)
274+
framework.EnsureOperatorIsNotHotLooping(te)
275+
276+
patch := fmt.Sprintf(
277+
`{"spec": {"storage": {"azure": {"networkAccess": {"type": "%s"}}}}}`,
278+
imageregistryv1.AzureNetworkAccessTypeInternal,
279+
)
280+
if _, err = te.Client().Configs().Patch(
281+
ctx,
282+
defaults.ImageRegistryResourceName,
283+
types.MergePatchType,
284+
[]byte(patch),
285+
metav1.PatchOptions{},
286+
); err != nil {
287+
t.Errorf("unable to patch image registry custom resource: %#v", err)
288+
}
289+
290+
azureConfig := &imageregistryv1.ImageRegistryConfigStorageAzure{}
291+
err = wait.PollUntilContextTimeout(ctx, time.Second, framework.AsyncOperationTimeout, true,
292+
func(ctx context.Context) (stop bool, err error) {
293+
cr, err := te.Client().Configs().Get(
294+
ctx,
295+
defaults.ImageRegistryResourceName,
296+
metav1.GetOptions{},
297+
)
298+
if err != nil {
299+
t.Logf(
300+
"unable to get custom resource %s/%s: %#v",
301+
defaults.ImageRegistryOperatorNamespace,
302+
defaults.ImageRegistryResourceName,
303+
err,
304+
)
305+
return false, nil
306+
}
307+
if cr.Spec.Storage.Azure.NetworkAccess.Internal == nil || cr.Spec.Storage.Azure.NetworkAccess.Internal.PrivateEndpointName == "" {
308+
// operator has not yet set the name - keep waiting
309+
return false, nil
310+
}
311+
azureConfig = cr.Spec.Storage.Azure
312+
t.Logf("PrivateEndpointName is %q", azureConfig.NetworkAccess.Internal.PrivateEndpointName)
313+
return true, nil
314+
},
315+
)
316+
if err != nil {
317+
t.Fatal(err)
318+
}
319+
320+
if azureConfig.NetworkAccess.Internal.VNetName == "" {
321+
t.Fatal("expected vnet name to be set in operator config but it was empty")
322+
}
323+
if azureConfig.NetworkAccess.Internal.SubnetName == "" {
324+
t.Fatal("expected subnet name to be set in operator config but it was empty")
325+
}
326+
}
327+
240328
func getEnvironmentByName(name string) (autorestazure.Environment, error) {
241329
if name == "" {
242330
return autorestazure.PublicCloud, nil

0 commit comments

Comments
 (0)