Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ required = [

[[constraint]]
name = "github.com/Azure/azure-sdk-for-go"
version = "25.1.0"
version = "v29.0.0"

[[constraint]]
name = "github.com/coreos/ignition"
Expand Down
1 change: 1 addition & 0 deletions cmd/openshift-install/destroy.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

assetstore "github.com/openshift/installer/pkg/asset/store"
"github.com/openshift/installer/pkg/destroy"
_ "github.com/openshift/installer/pkg/destroy/azure"
"github.com/openshift/installer/pkg/destroy/bootstrap"
_ "github.com/openshift/installer/pkg/destroy/libvirt"
_ "github.com/openshift/installer/pkg/destroy/openstack"
Expand Down
219 changes: 219 additions & 0 deletions pkg/destroy/azure/azure.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
package azure

import (
"context"
"fmt"
"net/http"
"sort"
"strings"
"time"

"github.com/Azure/azure-sdk-for-go/services/preview/dns/mgmt/2018-03-01-preview/dns"
"github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2018-05-01/resources"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/to"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"

session "github.com/openshift/installer/pkg/asset/installconfig/azure"
"github.com/openshift/installer/pkg/destroy"
"github.com/openshift/installer/pkg/types"
)

// ClusterUninstaller holds the various options for the cluster we want to delete.
type ClusterUninstaller struct {
SubscriptionID string
Authorizer autorest.Authorizer

InfraID string

Logger logrus.FieldLogger

resourceGroupsClient resources.GroupsGroupClient
zonesClient dns.ZonesClient
recordsClient dns.RecordSetsClient
}

func (o *ClusterUninstaller) configureClients() {
o.resourceGroupsClient = resources.NewGroupsGroupClient(o.SubscriptionID)
o.resourceGroupsClient.Authorizer = o.Authorizer

o.zonesClient = dns.NewZonesClient(o.SubscriptionID)
o.zonesClient.Authorizer = o.Authorizer

o.recordsClient = dns.NewRecordSetsClient(o.SubscriptionID)
o.recordsClient.Authorizer = o.Authorizer
}

// Run is the entrypoint to start the uninstall process.
func (o *ClusterUninstaller) Run() error {
o.configureClients()
group := o.InfraID + "-rg"
o.Logger.Debug("deleting public records")
if err := deletePublicRecords(context.TODO(), o.zonesClient, o.recordsClient, o.Logger, group); err != nil {
o.Logger.Debug(err)
return errors.Wrap(err, "failed to delete public DNS records")
Copy link
Member

Choose a reason for hiding this comment

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

This will fail instead of endlessly retrying (which is what the AWS destroyer does now). Sometimes (e.g. stuck resource), failing is good. Sometimes (e.g. network hiccup), retrying is good. Do we have a design preference? Or is this behavior going to be a per-platform choice?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Azure only makes 2 calls, delete public records and another is delete resource group. Also the underlying impl has retries for retryable errors..

So I think this is good start. and We can make it retryable on our end when we think that will become necessary.

}
o.Logger.Debug("deleting resource group")
if err := deleteResourceGroup(context.TODO(), o.resourceGroupsClient, o.Logger, group); err != nil {
o.Logger.Debug(err)
return errors.Wrap(err, "failed to delete resource group")
}

return nil
}

func deletePublicRecords(ctx context.Context, dnsClient dns.ZonesClient, recordsClient dns.RecordSetsClient, logger logrus.FieldLogger, rgName string) error {
ctx, cancel := context.WithTimeout(ctx, 10*time.Minute)
defer cancel()

// collect records from private zones in rgName
var errs []error
for zonesPage, err := dnsClient.ListByResourceGroup(ctx, rgName, to.Int32Ptr(100)); zonesPage.NotDone(); err = zonesPage.NextWithContext(ctx) {
if err != nil {
errs = append(errs, errors.Wrap(err, "failed to list private zone"))
continue
}
for _, zone := range zonesPage.Values() {
if zone.ZoneType == dns.Private {
if err := deletePublicRecordsForZone(ctx, dnsClient, recordsClient, logger, rgName, to.String(zone.Name)); err != nil {
errs = append(errs, errors.Wrapf(err, "failed to delete public records for %s", to.String(zone.Name)))
continue
}
}
}
}
return utilerrors.NewAggregate(errs)
}

func deletePublicRecordsForZone(ctx context.Context, dnsClient dns.ZonesClient, recordsClient dns.RecordSetsClient, logger logrus.FieldLogger, zoneGroup, zoneName string) error {
// collect all the records from the zoneName
allPrivateRecords := sets.NewString()
for recordPages, err := recordsClient.ListByDNSZone(ctx, zoneGroup, zoneName, to.Int32Ptr(100), ""); recordPages.NotDone(); err = recordPages.NextWithContext(ctx) {
if err != nil {
return err
}
for _, record := range recordPages.Values() {
if t := toRecordType(to.String(record.Type)); t == dns.SOA || t == dns.NS {
continue
}
allPrivateRecords.Insert(fmt.Sprintf("%s.%s", to.String(record.Name), zoneName))
}
}

sharedZones, err := getSharedDNSZones(ctx, dnsClient, zoneName)
if err != nil {
return errors.Wrapf(err, "failed to find shared zone for %s", zoneName)
}
for _, sharedZone := range sharedZones {
logger.Debugf("removing matching private records from %s", sharedZone.Name)
for recordPages, err := recordsClient.ListByDNSZone(ctx, sharedZone.Group, sharedZone.Name, to.Int32Ptr(100), ""); recordPages.NotDone(); err = recordPages.NextWithContext(ctx) {
if err != nil {
return err
}
for _, record := range recordPages.Values() {
if allPrivateRecords.Has(fmt.Sprintf("%s.%s", to.String(record.Name), sharedZone.Name)) {
resp, err := recordsClient.Delete(ctx, sharedZone.Group, sharedZone.Name, to.String(record.Name), toRecordType(to.String(record.Type)), "")
if err != nil {
if wasNotFound(resp.Response) {
logger.WithField("record", to.String(record.Name)).Debug("already deleted")
continue
}
return errors.Wrapf(err, "failed to delete record %s in zone %s", to.String(record.Name), sharedZone.Name)
}
logger.WithField("record", to.String(record.Name)).Info("deleted")
}
}
}
}
return nil
}

// getSharedDNSZones returns the all parent public dns zones for privZoneName in decreasing order of closeness.
func getSharedDNSZones(ctx context.Context, client dns.ZonesClient, privZoneName string) ([]dnsZone, error) {
domain := privZoneName
parents := sets.NewString(domain)
for {
idx := strings.Index(domain, ".")
if idx == -1 {
break
}
if len(domain[idx+1:]) > 0 {
parents.Insert(domain[idx+1:])
}
domain = domain[idx+1:]
}

allPublicZones := []dnsZone{}
for zonesPage, err := client.List(ctx, to.Int32Ptr(100)); zonesPage.NotDone(); err = zonesPage.NextWithContext(ctx) {
if err != nil {
return nil, err
}
for _, zone := range zonesPage.Values() {
if zone.ZoneType == dns.Public && parents.Has(to.String(zone.Name)) {
allPublicZones = append(allPublicZones, dnsZone{Name: to.String(zone.Name), ID: to.String(zone.ID), Group: groupFromID(to.String(zone.ID)), Public: true})
continue
}
}
}
sort.Slice(allPublicZones, func(i, j int) bool { return len(allPublicZones[i].Name) > len(allPublicZones[j].Name) })
return allPublicZones, nil
}

type dnsZone struct {
Copy link
Member

Choose a reason for hiding this comment

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

Do we want our own type, or can we use dns.Zone directly?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

[dns.Zone][https://godoc.org/github.com/Azure/azure-sdk-for-go/services/preview/dns/mgmt/2018-03-01-preview/dns#Zone] has pointers for fields, doesn't have the resource group (that needs to be inferred from ID).

This small spare struct stores the details in easy to use way.

Name string
ID string
Group string
Public bool
}

func groupFromID(id string) string {
return strings.Split(id, "/")[4]
}

func toRecordType(t string) dns.RecordType {
return dns.RecordType(strings.TrimPrefix(t, "Microsoft.Network/dnszones/"))
}

func deleteResourceGroup(ctx context.Context, client resources.GroupsGroupClient, logger logrus.FieldLogger, name string) error {
logger = logger.WithField("resource group", name)
ctx, cancel := context.WithTimeout(ctx, 30*time.Minute)
defer cancel()

delFuture, err := client.Delete(ctx, name)
if err != nil {
return err
}

err = delFuture.WaitForCompletionRef(ctx, client.Client)
if err != nil {
if wasNotFound(delFuture.Response()) {
logger.Debug("already deleted")
return nil
}
return errors.Wrapf(err, "failed to delete %s", name)
}
logger.Info("deleted")
return nil
}

// New returns azure Uninstaller from ClusterMetadata.
func New(logger logrus.FieldLogger, metadata *types.ClusterMetadata) (destroy.Destroyer, error) {
sess, err := session.GetSession()
if err != nil {
return nil, err
}

return &ClusterUninstaller{
SubscriptionID: sess.Credentials.SubscriptionID,
Authorizer: sess.Authorizer,
InfraID: metadata.InfraID,
Logger: logger,
}, nil
}

func wasNotFound(resp *http.Response) bool {
return resp != nil && resp.StatusCode == http.StatusNotFound
}
2 changes: 2 additions & 0 deletions pkg/destroy/azure/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package azure provides a cluster-destroyer for Azure clusters.
package azure
9 changes: 9 additions & 0 deletions pkg/destroy/azure/register.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package azure

import (
"github.com/openshift/installer/pkg/destroy"
)

func init() {
destroy.Registry["azure"] = New
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading