Skip to content

Commit

Permalink
allow mysql server to be updated
Browse files Browse the repository at this point in the history
  • Loading branch information
frodopwns committed May 28, 2020
1 parent 98d8821 commit 5dc6371
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 117 deletions.
63 changes: 45 additions & 18 deletions pkg/resourcemanager/mysql/server/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ package server

import (
"context"
"strings"

mysql "github.com/Azure/azure-sdk-for-go/services/mysql/mgmt/2017-12-01/mysql"
"github.com/Azure/azure-service-operator/api/v1alpha2"
Expand Down Expand Up @@ -62,7 +61,7 @@ func (m *MySQLServerClient) CheckServerNameAvailability(ctx context.Context, ser

}

func (m *MySQLServerClient) CreateServerIfValid(ctx context.Context, instance v1alpha2.MySQLServer, tags map[string]*string, skuInfo mysql.Sku, adminlogin string, adminpassword string, createmode string) (pollingURL string, server mysql.Server, err error) {
func (m *MySQLServerClient) CreateServerIfValid(ctx context.Context, instance v1alpha2.MySQLServer, tags map[string]*string, skuInfo mysql.Sku, adminlogin string, adminpassword string, createmode mysql.CreateMode, hash string) (pollingURL string, server mysql.Server, err error) {

client := getMySQLServersClient()

Expand All @@ -72,7 +71,6 @@ func (m *MySQLServerClient) CreateServerIfValid(ctx context.Context, instance v1
return "", server, err
}

var result mysql.ServersCreateFuture
var serverProperties mysql.BasicServerPropertiesForCreate
var skuData *mysql.Sku
var storageProfile *mysql.StorageProfile
Expand All @@ -81,7 +79,7 @@ func (m *MySQLServerClient) CreateServerIfValid(ctx context.Context, instance v1
storageProfile = &obj
}

if strings.EqualFold(createmode, "replica") {
if createmode == mysql.CreateModeReplica {
serverProperties = &mysql.ServerPropertiesForReplica{
SourceServerID: to.StringPtr(instance.Spec.ReplicaProperties.SourceServerId),
CreateMode: mysql.CreateModeReplica,
Expand All @@ -100,20 +98,49 @@ func (m *MySQLServerClient) CreateServerIfValid(ctx context.Context, instance v1
skuData = &skuInfo
}

result, _ = client.Create(
ctx,
instance.Spec.ResourceGroup,
instance.Name,
mysql.ServerForCreate{
Location: &instance.Spec.Location,
Tags: tags,
Properties: serverProperties,
Sku: skuData,
},
)

res, err := result.Result(client)
return result.PollingURL(), res, err
if hash != instance.Status.SpecHash && instance.Status.SpecHash != "" {
res, err := client.Update(
ctx,
instance.Spec.ResourceGroup,
instance.Name,
mysql.ServerUpdateParameters{
Tags: tags,
Sku: skuData,
ServerUpdateParametersProperties: &mysql.ServerUpdateParametersProperties{
StorageProfile: storageProfile,
Version: mysql.ServerVersion(instance.Spec.ServerVersion),
SslEnforcement: mysql.SslEnforcementEnum(instance.Spec.SSLEnforcement),
},
},
)
if err != nil {
return "", mysql.Server{}, err
}

pollingURL = res.PollingURL()
server, err = res.Result(client)

} else {
res, err := client.Create(
ctx,
instance.Spec.ResourceGroup,
instance.Name,
mysql.ServerForCreate{
Location: &instance.Spec.Location,
Tags: tags,
Properties: serverProperties,
Sku: skuData,
},
)
if err != nil {
return "", mysql.Server{}, err
}

pollingURL = res.PollingURL()
server, err = res.Result(client)
}

return pollingURL, server, err

}

Expand Down
208 changes: 109 additions & 99 deletions pkg/resourcemanager/mysql/server/reconcile.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,13 @@ func (m *MySQLServerClient) Ensure(ctx context.Context, obj runtime.Object, opts
return true, err
}

createmode := "Default"
createmode := mysql.CreateModeDefault
if len(instance.Spec.CreateMode) != 0 {
createmode = instance.Spec.CreateMode
createmode = mysql.CreateMode(instance.Spec.CreateMode)
}

// If a replica is requested, ensure that source server is specified
if strings.EqualFold(createmode, "replica") {
if createmode == mysql.CreateModeReplica {
if len(instance.Spec.ReplicaProperties.SourceServerId) == 0 {
instance.Status.Message = "Replica requested but source server unspecified"
return true, nil
Expand All @@ -66,122 +66,132 @@ func (m *MySQLServerClient) Ensure(ctx context.Context, obj runtime.Object, opts
// convert kube labels to expected tag format
labels := helpers.LabelsToTags(instance.GetLabels())

// Check if this server already exists and its state if it does. This is required
// to overcome the issue with the lack of idempotence of the Create call
hash := ""
server, err := m.GetServer(ctx, instance.Spec.ResourceGroup, instance.Name)
if err != nil {
// handle failures in the async operation
if instance.Status.PollingURL != "" {
pClient := pollclient.NewPollClient()
res, err := pClient.Get(ctx, instance.Status.PollingURL)
if err != nil {
instance.Status.Provisioning = false
return false, err
}
hash := helpers.Hash256(instance.Spec)
if instance.Status.SpecHash == hash && (instance.Status.Provisioned || instance.Status.FailedProvisioning) {
instance.Status.RequestedAt = nil
return true, nil
} else if instance.Status.SpecHash != hash && !instance.Status.Provisioning {
instance.Status.Provisioned = false
}

if res.Status == "Failed" {
instance.Status.Provisioning = false
instance.Status.RequestedAt = nil
ignore := []string{
errhelp.SubscriptionDoesNotHaveServer,
errhelp.ServiceBusy,
if instance.Status.Provisioning {

// Check if this server already exists and its state if it does. This is required
// to overcome the issue with the lack of idempotence of the Create call
server, err := m.GetServer(ctx, instance.Spec.ResourceGroup, instance.Name)
if err != nil {
// handle failures in the async operation
if instance.Status.PollingURL != "" {
pClient := pollclient.NewPollClient()
res, err := pClient.Get(ctx, instance.Status.PollingURL)
if err != nil {
instance.Status.Provisioning = false
return false, err
}
if !helpers.ContainsString(ignore, res.Error.Code) {
instance.Status.Message = res.Error.Error()
return true, nil

if res.Status == "Failed" {
instance.Status.Provisioning = false
instance.Status.RequestedAt = nil
ignore := []string{
errhelp.SubscriptionDoesNotHaveServer,
errhelp.ServiceBusy,
}
if !helpers.ContainsString(ignore, res.Error.Code) {
instance.Status.Message = res.Error.Error()
return true, nil
}
}
}
}
} else {
instance.Status.State = string(server.UserVisibleState)
} else {
instance.Status.State = string(server.UserVisibleState)

hash = helpers.Hash256(instance.Spec)
if instance.Status.SpecHash == hash && (instance.Status.Provisioned || instance.Status.FailedProvisioning) {
instance.Status.RequestedAt = nil
return true, nil
}
if server.UserVisibleState == mysql.ServerStateReady {
// Update secret with FQ name of the server. We ignore the error.
m.UpdateServerNameInSecret(ctx, instance.Name, secret, *server.FullyQualifiedDomainName, instance)
hash = helpers.Hash256(instance.Spec)
if instance.Status.SpecHash == hash && (instance.Status.Provisioned || instance.Status.FailedProvisioning) {
instance.Status.RequestedAt = nil
return true, nil
}
if server.UserVisibleState == mysql.ServerStateReady {
// Update secret with FQ name of the server. We ignore the error.
m.UpdateServerNameInSecret(ctx, instance.Name, secret, *server.FullyQualifiedDomainName, instance)

instance.Status.Provisioned = true
instance.Status.Provisioning = false
instance.Status.Message = resourcemanager.SuccessMsg
instance.Status.ResourceId = *server.ID
instance.Status.State = string(server.UserVisibleState)
instance.Status.SpecHash = hash
return true, nil
instance.Status.Provisioned = true
instance.Status.Provisioning = false
instance.Status.Message = resourcemanager.SuccessMsg
instance.Status.ResourceId = *server.ID
instance.Status.State = string(server.UserVisibleState)
instance.Status.SpecHash = hash
return true, nil
}
}
}

// if the create has been sent with no error we need to wait before calling it again
// @todo set an appropriate time since create has been called to retry
if instance.Status.Provisioning {
return false, nil
}

adminlogin := string(secret["username"])
adminpassword := string(secret["password"])
skuInfo := mysql.Sku{
Name: to.StringPtr(instance.Spec.Sku.Name),
Tier: mysql.SkuTier(instance.Spec.Sku.Tier),
Capacity: to.Int32Ptr(instance.Spec.Sku.Capacity),
Size: to.StringPtr(instance.Spec.Sku.Size),
Family: to.StringPtr(instance.Spec.Sku.Family),
}
if !instance.Status.Provisioning && !instance.Status.Provisioned {
instance.Status.Provisioning = true
instance.Status.FailedProvisioning = false

adminlogin := string(secret["username"])
adminpassword := string(secret["password"])
skuInfo := mysql.Sku{
Name: to.StringPtr(instance.Spec.Sku.Name),
Tier: mysql.SkuTier(instance.Spec.Sku.Tier),
Capacity: to.Int32Ptr(instance.Spec.Sku.Capacity),
Size: to.StringPtr(instance.Spec.Sku.Size),
Family: to.StringPtr(instance.Spec.Sku.Family),
}

pollURL, server, err := m.CreateServerIfValid(
ctx,
*instance,
labels,
skuInfo,
adminlogin,
adminpassword,
createmode,
)
if err != nil {
// let the user know what happened
instance.Status.Message = errhelp.StripErrorIDs(err)
instance.Status.Provisioning = false
azerr := errhelp.NewAzureErrorAzureError(err)
pollURL, _, err := m.CreateServerIfValid(
ctx,
*instance,
labels,
skuInfo,
adminlogin,
adminpassword,
createmode,
hash,
)
if err != nil {
instance.Status.Message = errhelp.StripErrorIDs(err)
instance.Status.Provisioning = false

catchRequeue := []string{
errhelp.ResourceGroupNotFoundErrorCode,
errhelp.ParentNotFoundErrorCode,
errhelp.AsyncOpIncompleteError,
errhelp.SubscriptionDoesNotHaveServer,
errhelp.ServiceBusy,
}
catchUnrecoverable := []string{
errhelp.ProvisioningDisabled,
errhelp.LocationNotAvailableForResourceType,
errhelp.InvalidRequestContent,
errhelp.InternalServerError,
}
azerr := errhelp.NewAzureErrorAzureError(err)

catchInProgress := []string{
errhelp.AsyncOpIncompleteError,
errhelp.AlreadyExists,
}
catchKnownError := []string{
errhelp.ResourceGroupNotFoundErrorCode,
errhelp.ParentNotFoundErrorCode,
errhelp.NotFoundErrorCode,
errhelp.ServiceBusy,
errhelp.InternalServerError,
}

// handle the errors
if helpers.ContainsString(catchRequeue, azerr.Type) {
if azerr.Type == errhelp.AsyncOpIncompleteError {
// handle the errors
if helpers.ContainsString(catchInProgress, azerr.Type) {
if azerr.Type == errhelp.AsyncOpIncompleteError {
instance.Status.PollingURL = pollURL
}
instance.Status.Message = "Postgres server exists but may not be ready"
instance.Status.Provisioning = true
instance.Status.PollingURL = pollURL
return false, nil
}
return false, nil
}

if helpers.ContainsString(catchUnrecoverable, azerr.Type) {
// Unrecoverable error, so stop reconcilation
instance.Status.Message = "Reconcilation hit unrecoverable error: " + errhelp.StripErrorIDs(err)
if helpers.ContainsString(catchKnownError, azerr.Type) {
return false, nil
}

// serious error occured, end reconcilliation and mark it as failed
instance.Status.Message = errhelp.StripErrorIDs(err)
instance.Status.Provisioned = false
instance.Status.FailedProvisioning = true
return true, nil

}

// reconciliation not done and we don't know what happened
return false, err
instance.Status.Message = "request submitted to Azure"
}

instance.Status.Provisioning = true
instance.Status.Message = "Server request submitted to Azure"

return false, nil
}

Expand Down

0 comments on commit 5dc6371

Please sign in to comment.