diff --git a/Makefile b/Makefile index 5d8d9e48bd6..fec9e27faf0 100644 --- a/Makefile +++ b/Makefile @@ -34,13 +34,13 @@ api-test: generate fmt vet manifests # Run tests test: generate fmt vet manifests - TEST_USE_EXISTING_CLUSTER=false TEST_CONTROLLER_WITH_MOCKS=true go test -v -coverprofile=coverage.txt -covermode count ./api/... ./controllers/... ./pkg/resourcemanager/eventhubs/... ./pkg/resourcemanager/resourcegroups/... ./pkg/resourcemanager/storages/... 2>&1 | tee testlogs.txt + TEST_USE_EXISTING_CLUSTER=false TEST_CONTROLLER_WITH_MOCKS=true REQUEUE_AFTER=20 go test -v -coverprofile=coverage.txt -covermode count ./api/... ./controllers/... ./pkg/resourcemanager/eventhubs/... ./pkg/resourcemanager/resourcegroups/... ./pkg/resourcemanager/storages/... 2>&1 | tee testlogs.txt go-junit-report < testlogs.txt > report.xml go tool cover -html=coverage.txt -o cover.html # Run tests with existing cluster test-existing: generate fmt vet manifests - TEST_USE_EXISTING_CLUSTER=true TEST_CONTROLLER_WITH_MOCKS=false go test -v -coverprofile=coverage-existing.txt -covermode count ./api/... ./controllers/... ./pkg/resourcemanager/eventhubs/... ./pkg/resourcemanager/resourcegroups/... ./pkg/resourcemanager/storages/... 2>&1 | tee testlogs-existing.txt + TEST_USE_EXISTING_CLUSTER=true TEST_CONTROLLER_WITH_MOCKS=false REQUEUE_AFTER=20 go test -v -coverprofile=coverage-existing.txt -covermode count ./api/... ./controllers/... ./pkg/resourcemanager/eventhubs/... ./pkg/resourcemanager/resourcegroups/... ./pkg/resourcemanager/storages/... 2>&1 | tee testlogs-existing.txt go-junit-report < testlogs-existing.txt > report-existing.xml go tool cover -html=coverage-existing.txt -o cover-existing.html diff --git a/api/v1alpha1/azuresqldatabase_types.go b/api/v1alpha1/azuresqldatabase_types.go index 33dc90a3d0b..13aa7d26274 100644 --- a/api/v1alpha1/azuresqldatabase_types.go +++ b/api/v1alpha1/azuresqldatabase_types.go @@ -16,6 +16,7 @@ limitations under the License. package v1alpha1 import ( + helpers "github.com/Azure/azure-service-operator/pkg/helpers" sql "github.com/Azure/azure-service-operator/pkg/resourcemanager/sqlclient" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -59,3 +60,11 @@ func init() { func (s *AzureSqlDatabase) IsSubmitted() bool { return s.Status.Provisioned } + +func (s *AzureSqlDatabase) HasFinalizer(finalizerName string) bool { + return helpers.ContainsString(s.ObjectMeta.Finalizers, finalizerName) +} + +func (s *AzureSqlDatabase) IsBeingDeleted() bool { + return !s.ObjectMeta.DeletionTimestamp.IsZero() +} diff --git a/controllers/azuresqlaction_controller.go b/controllers/azuresqlaction_controller.go index efe84e84cf3..e562b032cb2 100644 --- a/controllers/azuresqlaction_controller.go +++ b/controllers/azuresqlaction_controller.go @@ -47,9 +47,10 @@ const AzureSqlActionFinalizerName = "azuresqlaction.finalizers.azure.com" // AzureSqlActionReconciler reconciles a AzureSqlAction object type AzureSqlActionReconciler struct { client.Client - Log logr.Logger - Recorder record.EventRecorder - Scheme *runtime.Scheme + Log logr.Logger + Recorder record.EventRecorder + Scheme *runtime.Scheme + ResourceClient sql.ResourceClient } // +kubebuilder:rbac:groups=azure.microsoft.com,resources=azuresqlactions,verbs=get;list;watch;create;update;patch;delete @@ -120,12 +121,6 @@ func (r *AzureSqlActionReconciler) reconcileExternal(instance *azurev1alpha1.Azu r.Recorder.Event(instance, corev1.EventTypeWarning, "Failed", "Unable to update instance") } - sdkClient := sql.GoSDKClient{ - Ctx: ctx, - ResourceGroupName: groupName, - ServerName: serverName, - } - //get owner instance of AzureSqlServer r.Recorder.Event(instance, corev1.EventTypeNormal, "UpdatingOwner", "Updating owner AzureSqlServer instance") var ownerInstance azurev1alpha1.AzureSqlServer @@ -149,7 +144,7 @@ func (r *AzureSqlActionReconciler) reconcileExternal(instance *azurev1alpha1.Azu } // Get the Sql Server instance that corresponds to the Server name in the spec for this action - server, err := sdkClient.GetServer() + server, err := r.ResourceClient.GetServer(ctx, groupName, serverName) if err != nil { if strings.Contains(err.Error(), "ResourceGroupNotFound") { r.Recorder.Event(instance, corev1.EventTypeWarning, "Failed", "Unable to get instance of AzureSqlServer: Resource group not found") @@ -172,8 +167,6 @@ func (r *AzureSqlActionReconciler) reconcileExternal(instance *azurev1alpha1.Azu } } - sdkClient.Location = *server.Location - // rollcreds action if strings.ToLower(instance.Spec.ActionName) == "rollcreds" { azureSqlServerProperties := sql.SQLServerProperties{ @@ -185,7 +178,7 @@ func (r *AzureSqlActionReconciler) reconcileExternal(instance *azurev1alpha1.Azu newPassword, _ := generateRandomPassword(passwordLength) azureSqlServerProperties.AdministratorLoginPassword = to.StringPtr(newPassword) - if _, err := sdkClient.CreateOrUpdateSQLServer(azureSqlServerProperties); err != nil { + if _, err := r.ResourceClient.CreateOrUpdateSQLServer(ctx, groupName, *server.Location, serverName, azureSqlServerProperties); err != nil { if !strings.Contains(err.Error(), "not complete") { r.Recorder.Event(instance, corev1.EventTypeWarning, "Failed", "Unable to provision or update instance") return errhelp.NewAzureError(err) diff --git a/controllers/azuresqldatabase_controller.go b/controllers/azuresqldatabase_controller.go index de5f2c596b4..3977875d428 100644 --- a/controllers/azuresqldatabase_controller.go +++ b/controllers/azuresqldatabase_controller.go @@ -18,6 +18,8 @@ package controllers import ( "context" "fmt" + "os" + "strconv" "time" "github.com/Azure/azure-service-operator/pkg/errhelp" @@ -37,14 +39,13 @@ import ( azurev1alpha1 "github.com/Azure/azure-service-operator/api/v1alpha1" ) -const azureSQLDatabaseFinalizerName = "azuresqldatabase.finalizers.azure.com" - // AzureSqlDatabaseReconciler reconciles a AzureSqlDatabase object type AzureSqlDatabaseReconciler struct { client.Client - Log logr.Logger - Recorder record.EventRecorder - Scheme *runtime.Scheme + Log logr.Logger + Recorder record.EventRecorder + Scheme *runtime.Scheme + ResourceClient sql.ResourceClient } // +kubebuilder:rbac:groups=azure.microsoft.com,resources=azuresqldatabases,verbs=get;list;watch;create;update;patch;delete @@ -65,27 +66,32 @@ func (r *AzureSqlDatabaseReconciler) Reconcile(req ctrl.Request) (ctrl.Result, e } if helpers.IsBeingDeleted(&instance) { - if helpers.HasFinalizer(&instance, azureSQLDatabaseFinalizerName) { + if helpers.HasFinalizer(&instance, AzureSQLDatabaseFinalizerName) { if err := r.deleteExternal(&instance); err != nil { log.Info("Delete AzureSqlDatabase failed with ", "err", err.Error()) return ctrl.Result{}, err } - helpers.RemoveFinalizer(&instance, azureSQLDatabaseFinalizerName) - if err := r.Update(context.Background(), &instance); err != nil { + helpers.RemoveFinalizer(&instance, AzureSQLDatabaseFinalizerName) + if err := r.Status().Update(context.Background(), &instance); err != nil { return ctrl.Result{}, err } } return ctrl.Result{}, nil } - if !helpers.HasFinalizer(&instance, azureSQLDatabaseFinalizerName) { + if !instance.HasFinalizer(AzureSQLDatabaseFinalizerName) { if err := r.addFinalizer(&instance); err != nil { log.Info("Adding AzureSqlDatabase finalizer failed with ", "error", err.Error()) return ctrl.Result{}, err } } + requeueAfter, err := strconv.Atoi(os.Getenv("REQUEUE_AFTER")) + if err != nil { + requeueAfter = 30 + } + if !instance.IsSubmitted() { r.Recorder.Event(&instance, corev1.EventTypeNormal, "Submitting", "starting resource reconciliation for AzureSqlDatabase") if err := r.reconcileExternal(&instance); err != nil { @@ -99,7 +105,7 @@ func (r *AzureSqlDatabaseReconciler) Reconcile(req ctrl.Request) (ctrl.Result, e if azerr, ok := err.(*errhelp.AzureError); ok { if helpers.ContainsString(catch, azerr.Type) { log.Info("Got ignorable error", "type", azerr.Type) - return ctrl.Result{Requeue: true, RequeueAfter: 30 * time.Second}, nil + return ctrl.Result{Requeue: true, RequeueAfter: time.Duration(requeueAfter) * time.Second}, nil } } return ctrl.Result{}, fmt.Errorf("error reconciling azure sql database in azure: %v", err) @@ -126,13 +132,6 @@ func (r *AzureSqlDatabaseReconciler) reconcileExternal(instance *azurev1alpha1.A dbName := instance.ObjectMeta.Name dbEdition := instance.Spec.Edition - sdkClient := sql.GoSDKClient{ - Ctx: ctx, - ResourceGroupName: groupName, - ServerName: server, - Location: location, - } - azureSqlDatabaseProperties := sql.SQLDatabaseProperties{ DatabaseName: dbName, Edition: dbEdition, @@ -159,11 +158,11 @@ func (r *AzureSqlDatabaseReconciler) reconcileExternal(instance *azurev1alpha1.A } // write information back to instance - if updateerr := r.Update(ctx, instance); updateerr != nil { + if updateerr := r.Status().Update(ctx, instance); updateerr != nil { r.Recorder.Event(instance, corev1.EventTypeWarning, "Failed", "Unable to update instance") } - _, err = sdkClient.CreateOrUpdateDB(azureSqlDatabaseProperties) + _, err = r.ResourceClient.CreateOrUpdateDB(ctx, groupName, location, server, azureSqlDatabaseProperties) if err != nil { if errhelp.IsAsynchronousOperationNotComplete(err) || errhelp.IsGroupNotFound(err) { r.Log.Info("Async operation not complete or group not found") @@ -176,7 +175,7 @@ func (r *AzureSqlDatabaseReconciler) reconcileExternal(instance *azurev1alpha1.A return errhelp.NewAzureError(err) } - _, err = sdkClient.GetDB(dbName) + _, err = r.ResourceClient.GetDB(ctx, groupName, server, dbName) if err != nil { return errhelp.NewAzureError(err) } @@ -193,21 +192,12 @@ func (r *AzureSqlDatabaseReconciler) reconcileExternal(instance *azurev1alpha1.A func (r *AzureSqlDatabaseReconciler) deleteExternal(instance *azurev1alpha1.AzureSqlDatabase) error { ctx := context.Background() - location := instance.Spec.Location groupName := instance.Spec.ResourceGroup server := instance.Spec.Server dbName := instance.ObjectMeta.Name - // create the Go SDK client with relevant info - sdk := sql.GoSDKClient{ - Ctx: ctx, - ResourceGroupName: groupName, - ServerName: server, - Location: location, - } - r.Log.Info(fmt.Sprintf("deleting external resource: group/%s/server/%s/database/%s"+groupName, server, dbName)) - _, err := sdk.DeleteDB(dbName) + _, err := r.ResourceClient.DeleteDB(ctx, groupName, server, dbName) if err != nil { if errhelp.IsStatusCode204(err) { r.Recorder.Event(instance, corev1.EventTypeWarning, "DoesNotExist", "Resource to delete does not exist") @@ -220,13 +210,3 @@ func (r *AzureSqlDatabaseReconciler) deleteExternal(instance *azurev1alpha1.Azur r.Recorder.Event(instance, corev1.EventTypeNormal, "Deleted", dbName+" deleted") return nil } - -func (r *AzureSqlDatabaseReconciler) addFinalizer(instance *azurev1alpha1.AzureSqlDatabase) error { - helpers.AddFinalizer(instance, azureSQLDatabaseFinalizerName) - err := r.Update(context.Background(), instance) - if err != nil { - return fmt.Errorf("failed to update finalizer: %v", err) - } - r.Recorder.Event(instance, corev1.EventTypeNormal, "Updated", fmt.Sprintf("finalizer %s added", azureSQLDatabaseFinalizerName)) - return nil -} diff --git a/controllers/azuresqldatabase_controller_finalizer.go b/controllers/azuresqldatabase_controller_finalizer.go new file mode 100644 index 00000000000..0925ceac3ba --- /dev/null +++ b/controllers/azuresqldatabase_controller_finalizer.go @@ -0,0 +1,36 @@ +/* +Copyright 2019 microsoft. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "context" + "fmt" + + azurev1alpha1 "github.com/Azure/azure-service-operator/api/v1alpha1" + helpers "github.com/Azure/azure-service-operator/pkg/helpers" +) + +const AzureSQLDatabaseFinalizerName = "azuresqldatabase.finalizers.azure.com" + +func (r *AzureSqlDatabaseReconciler) addFinalizer(instance *azurev1alpha1.AzureSqlDatabase) error { + helpers.AddFinalizer(instance, AzureSQLDatabaseFinalizerName) + if updateerr := r.Update(context.Background(), instance); updateerr != nil { + r.Recorder.Event(instance, "Warning", "Failed", "Failed to update finalizer") + } + r.Recorder.Event(instance, "Normal", "Updated", fmt.Sprintf("finalizer %s added", AzureSQLDatabaseFinalizerName)) + return nil +} diff --git a/controllers/azuresqldatabase_controller_test.go b/controllers/azuresqldatabase_controller_test.go new file mode 100644 index 00000000000..3209d2e1cc1 --- /dev/null +++ b/controllers/azuresqldatabase_controller_test.go @@ -0,0 +1,120 @@ +/* +Copyright 2019 microsoft. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "context" + + azurev1alpha1 "github.com/Azure/azure-service-operator/api/v1alpha1" + + helpers "github.com/Azure/azure-service-operator/pkg/helpers" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +var _ = Describe("AzureSqlDatabase Controller", func() { + + var rgName string + var rgLocation string + //var sqlName string + + BeforeEach(func() { + // Add any setup steps that needs to be executed before each test + rgName = tc.resourceGroupName + rgLocation = tc.resourceGroupLocation + }) + + AfterEach(func() { + // Add any teardown steps that needs to be executed after each test + }) + + // Add Tests for OpenAPI validation (or additonal CRD features) specified in + // your API definition. + // Avoid adding tests for vanilla CRUD operations because they would + // test Kubernetes API server, which isn't the goal here. + + Context("Create and Delete", func() { + It("should create and delete sql database in k8s", func() { + + randomName := helpers.RandomString(10) + sqlServerName := "t-sqlserver-dev-" + randomName + sqlDatabaseName := "t-sqldatabase-dev-" + randomName + + var err error + + // Create the SqlServer object and expect the Reconcile to be created + sqlServerInstance := &azurev1alpha1.AzureSqlServer{ + ObjectMeta: metav1.ObjectMeta{ + Name: sqlServerName, + Namespace: "default", + }, + Spec: azurev1alpha1.AzureSqlServerSpec{ + Location: rgLocation, + ResourceGroup: rgName, + }, + } + + err = tc.k8sClient.Create(context.Background(), sqlServerInstance) + Expect(err).NotTo(HaveOccurred()) + + // Create the SqlDatabase object and expect the Reconcile to be created + sqlDatabaseInstance := &azurev1alpha1.AzureSqlDatabase{ + ObjectMeta: metav1.ObjectMeta{ + Name: sqlDatabaseName, + Namespace: "default", + }, + Spec: azurev1alpha1.AzureSqlDatabaseSpec{ + Location: rgLocation, + ResourceGroup: rgName, + Server: sqlServerName, + Edition: 0, + }, + } + + err = tc.k8sClient.Create(context.Background(), sqlDatabaseInstance) + Expect(apierrors.IsInvalid(err)).To(Equal(false)) + Expect(err).NotTo(HaveOccurred()) + + sqlDatabaseNamespacedName := types.NamespacedName{Name: sqlDatabaseName, Namespace: "default"} + + Eventually(func() bool { + _ = tc.k8sClient.Get(context.Background(), sqlDatabaseNamespacedName, sqlDatabaseInstance) + return helpers.HasFinalizer(sqlDatabaseInstance, AzureSQLDatabaseFinalizerName) + }, tc.timeout, + ).Should(BeTrue()) + + Eventually(func() bool { + _ = tc.k8sClient.Get(context.Background(), sqlDatabaseNamespacedName, sqlDatabaseInstance) + return sqlDatabaseInstance.IsSubmitted() + }, tc.timeout, + ).Should(BeTrue()) + + err = tc.k8sClient.Delete(context.Background(), sqlDatabaseInstance) + Expect(err).NotTo(HaveOccurred()) + + Eventually(func() bool { + _ = tc.k8sClient.Get(context.Background(), sqlDatabaseNamespacedName, sqlDatabaseInstance) + return helpers.IsBeingDeleted(sqlDatabaseInstance) + }, tc.timeout, + ).Should(BeTrue()) + + }) + }) +}) diff --git a/controllers/azuresqlfirewallrule_controller.go b/controllers/azuresqlfirewallrule_controller.go index f99efde8a4e..af0b91b33dd 100644 --- a/controllers/azuresqlfirewallrule_controller.go +++ b/controllers/azuresqlfirewallrule_controller.go @@ -40,9 +40,10 @@ const azureSQLFirewallRuleFinalizerName = "azuresqlfirewallrule.finalizers.azure // AzureSqlFirewallRuleReconciler reconciles a AzureSqlFirewallRule object type AzureSqlFirewallRuleReconciler struct { client.Client - Telemetry telemetry.PrometheusTelemetry - Recorder record.EventRecorder - Scheme *runtime.Scheme + Telemetry telemetry.PrometheusTelemetry + Recorder record.EventRecorder + Scheme *runtime.Scheme + ResourceClient sql.ResourceClient } // +kubebuilder:rbac:groups=azure.microsoft.com,resources=azuresqlfirewallrules,verbs=get;list;watch;create;update;patch;delete @@ -83,16 +84,9 @@ func (r *AzureSqlFirewallRuleReconciler) Reconcile(req ctrl.Request) (result ctr return ctrl.Result{}, client.IgnoreNotFound(err) } - // init the resource manager for this reconcilliation - sdkClient := sql.GoSDKClient{ - Ctx: ctx, - ResourceGroupName: instance.Spec.ResourceGroup, - ServerName: instance.Spec.Server, - } - if helpers.IsBeingDeleted(&instance) { if helpers.HasFinalizer(&instance, azureSQLFirewallRuleFinalizerName) { - if err = r.deleteExternal(&instance, sdkClient); err != nil { + if err = r.deleteExternal(&instance); err != nil { instance.Status.Message = fmt.Sprintf("Delete AzureSqlFirewallRule failed with %s", err.Error()) return ctrl.Result{}, err } @@ -114,7 +108,7 @@ func (r *AzureSqlFirewallRuleReconciler) Reconcile(req ctrl.Request) (result ctr if !instance.IsSubmitted() { r.Recorder.Event(&instance, v1.EventTypeNormal, "Submitting", "starting resource reconciliation for AzureSqlFirewallRule") - if err := r.reconcileExternal(&instance, sdkClient); err != nil { + if err := r.reconcileExternal(&instance); err != nil { instance.Status.Message = fmt.Sprintf("Reconcile external failed with %s", err.Error()) r.Telemetry.LogError("Reconcile external failed", err) return ctrl.Result{Requeue: true, RequeueAfter: 30 * time.Second}, nil @@ -134,8 +128,10 @@ func (r *AzureSqlFirewallRuleReconciler) SetupWithManager(mgr ctrl.Manager) erro Complete(r) } -func (r *AzureSqlFirewallRuleReconciler) reconcileExternal(instance *azurev1alpha1.AzureSqlFirewallRule, sdk sql.GoSDKClient) error { +func (r *AzureSqlFirewallRuleReconciler) reconcileExternal(instance *azurev1alpha1.AzureSqlFirewallRule) error { ctx := context.Background() + groupName := instance.Spec.ResourceGroup + serverName := instance.Spec.Server ruleName := instance.ObjectMeta.Name startIP := instance.Spec.StartIPAddress endIP := instance.Spec.EndIPAddress @@ -147,7 +143,7 @@ func (r *AzureSqlFirewallRuleReconciler) reconcileExternal(instance *azurev1alph //get owner instance of AzureSqlServer r.Recorder.Event(instance, v1.EventTypeNormal, "UpdatingOwner", "Updating owner AzureSqlServer instance") var ownerInstance azurev1alpha1.AzureSqlServer - azureSQLServerNamespacedName := types.NamespacedName{Name: sdk.ServerName, Namespace: instance.Namespace} + azureSQLServerNamespacedName := types.NamespacedName{Name: serverName, Namespace: instance.Namespace} err := r.Get(ctx, azureSQLServerNamespacedName, &ownerInstance) if err != nil { //log error and kill it, as the parent might not exist in the cluster. It could have been created elsewhere or through the portal directly @@ -175,7 +171,7 @@ func (r *AzureSqlFirewallRuleReconciler) reconcileExternal(instance *azurev1alph r.Recorder.Event(instance, v1.EventTypeWarning, "Failed", "Unable to update instance") } - _, err = sdk.CreateOrUpdateSQLFirewallRule(ruleName, startIP, endIP) + _, err = r.ResourceClient.CreateOrUpdateSQLFirewallRule(ctx, groupName, serverName, ruleName, startIP, endIP) if err != nil { if errhelp.IsAsynchronousOperationNotComplete(err) || errhelp.IsGroupNotFound(err) { r.Telemetry.LogInfo( @@ -187,7 +183,7 @@ func (r *AzureSqlFirewallRuleReconciler) reconcileExternal(instance *azurev1alph return errhelp.NewAzureError(err) } - _, err = sdk.GetSQLFirewallRule(ruleName) + _, err = r.ResourceClient.GetSQLFirewallRule(ctx, groupName, serverName, ruleName) if err != nil { return errhelp.NewAzureError(err) } @@ -198,13 +194,16 @@ func (r *AzureSqlFirewallRuleReconciler) reconcileExternal(instance *azurev1alph return nil } -func (r *AzureSqlFirewallRuleReconciler) deleteExternal(instance *azurev1alpha1.AzureSqlFirewallRule, sdk sql.GoSDKClient) error { +func (r *AzureSqlFirewallRuleReconciler) deleteExternal(instance *azurev1alpha1.AzureSqlFirewallRule) error { + ctx := context.Background() + groupName := instance.Spec.ResourceGroup + serverName := instance.Spec.Server ruleName := instance.ObjectMeta.Name r.Telemetry.LogTrace( "Status", - fmt.Sprintf("deleting external resource: group/%s/server/%s/firewallrule/%s", sdk.ResourceGroupName, sdk.ServerName, ruleName)) - err := sdk.DeleteSQLFirewallRule(ruleName) + fmt.Sprintf("deleting external resource: group/%s/server/%s/firewallrule/%s", groupName, serverName, ruleName)) + err := r.ResourceClient.DeleteSQLFirewallRule(ctx, groupName, serverName, ruleName) if err != nil { if errhelp.IsStatusCode204(err) { r.Recorder.Event(instance, v1.EventTypeWarning, "DoesNotExist", "Resource to delete does not exist") diff --git a/controllers/azuresqlserver_controller.go b/controllers/azuresqlserver_controller.go index f28fd49b84f..fbce84339e3 100644 --- a/controllers/azuresqlserver_controller.go +++ b/controllers/azuresqlserver_controller.go @@ -44,9 +44,10 @@ import ( // AzureSqlServerReconciler reconciles an AzureSqlServer object type AzureSqlServerReconciler struct { client.Client - Log logr.Logger - Recorder record.EventRecorder - Scheme *runtime.Scheme + Log logr.Logger + Recorder record.EventRecorder + Scheme *runtime.Scheme + ResourceClient sql.ResourceClient } // Constants @@ -98,7 +99,7 @@ func (r *AzureSqlServerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, err } helpers.RemoveFinalizer(&instance, AzureSQLServerFinalizerName) - if err := r.Update(context.Background(), &instance); err != nil { + if err := r.Status().Update(context.Background(), &instance); err != nil { return ctrl.Result{}, err } } @@ -203,13 +204,6 @@ func (r *AzureSqlServerReconciler) reconcileExternal(instance *azurev1alpha1.Azu name := instance.ObjectMeta.Name groupName := instance.Spec.ResourceGroup - sdkClient := sql.GoSDKClient{ - Ctx: ctx, - ResourceGroupName: groupName, - ServerName: name, - Location: location, - } - //get owner instance of ResourceGroup r.Recorder.Event(instance, corev1.EventTypeNormal, "UpdatingOwner", "Updating owner ResourceGroup instance") var ownerInstance azurev1alpha1.ResourceGroup @@ -230,7 +224,7 @@ func (r *AzureSqlServerReconciler) reconcileExternal(instance *azurev1alpha1.Azu } // write information back to instance - if err := r.Update(ctx, instance); err != nil { + if err := r.Status().Update(ctx, instance); err != nil { r.Recorder.Event(instance, corev1.EventTypeWarning, "Failed", "Unable to update instance") } @@ -243,7 +237,7 @@ func (r *AzureSqlServerReconciler) reconcileExternal(instance *azurev1alpha1.Azu // create the sql server instance.Status.Provisioning = true - if _, err := sdkClient.CreateOrUpdateSQLServer(azureSqlServerProperties); err != nil { + if _, err := r.ResourceClient.CreateOrUpdateSQLServer(ctx, groupName, location, name, azureSqlServerProperties); err != nil { if !strings.Contains(err.Error(), "not complete") { msg := fmt.Sprintf("CreateOrUpdateSQLServer not complete: %v", err) instance.Status.Message = msg @@ -257,7 +251,7 @@ func (r *AzureSqlServerReconciler) reconcileExternal(instance *azurev1alpha1.Azu } _, createOrUpdateSecretErr := controllerutil.CreateOrUpdate(context.Background(), r.Client, secret, func() error { - r.Log.Info("Creating or updating secret with SQL Server credentials") + r.Log.Info("mutating secret bundle") innerErr := controllerutil.SetControllerReference(instance, secret, r.Scheme) if innerErr != nil { return innerErr @@ -278,18 +272,10 @@ func (r *AzureSqlServerReconciler) reconcileExternal(instance *azurev1alpha1.Azu func (r *AzureSqlServerReconciler) verifyExternal(instance *azurev1alpha1.AzureSqlServer) error { ctx := context.Background() - location := instance.Spec.Location name := instance.ObjectMeta.Name groupName := instance.Spec.ResourceGroup - sdkClient := sql.GoSDKClient{ - Ctx: ctx, - ResourceGroupName: groupName, - ServerName: name, - Location: location, - } - - serv, err := sdkClient.GetServer() + serv, err := r.ResourceClient.GetServer(ctx, groupName, name) if err != nil { azerr := errhelp.NewAzureError(err).(*errhelp.AzureError) if azerr.Type != errhelp.ResourceNotFound { @@ -323,16 +309,8 @@ func (r *AzureSqlServerReconciler) deleteExternal(instance *azurev1alpha1.AzureS ctx := context.Background() name := instance.ObjectMeta.Name groupName := instance.Spec.ResourceGroup - location := instance.Spec.Location - - sdkClient := sql.GoSDKClient{ - Ctx: ctx, - ResourceGroupName: groupName, - ServerName: name, - Location: location, - } - _, err := sdkClient.DeleteSQLServer() + _, err := r.ResourceClient.DeleteSQLServer(ctx, groupName, name) if err != nil { msg := fmt.Sprintf("Couldn't delete resource in Azure: %v", err) instance.Status.Message = msg diff --git a/controllers/azuresqlserver_controller_test.go b/controllers/azuresqlserver_controller_test.go new file mode 100644 index 00000000000..04ad68072e6 --- /dev/null +++ b/controllers/azuresqlserver_controller_test.go @@ -0,0 +1,113 @@ +/* +Copyright 2019 microsoft. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controllers + +import ( + "context" + + azurev1alpha1 "github.com/Azure/azure-service-operator/api/v1alpha1" + + helpers "github.com/Azure/azure-service-operator/pkg/helpers" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + v1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +var _ = Describe("AzureSqlServer Controller", func() { + + var rgName string + var rgLocation string + + BeforeEach(func() { + // Add any setup steps that needs to be executed before each test + rgName = tc.resourceGroupName + rgLocation = tc.resourceGroupLocation + }) + + AfterEach(func() { + // Add any teardown steps that needs to be executed after each test + }) + + // Add Tests for OpenAPI validation (or additonal CRD features) specified in + // your API definition. + // Avoid adding tests for vanilla CRUD operations because they would + // test Kubernetes API server, which isn't the goal here. + + Context("Create and Delete", func() { + It("should create and delete sql server in k8s", func() { + sqlServerName := "t-sqlserver-dev-" + helpers.RandomString(10) + + var err error + + // Create the SqlServer object and expect the Reconcile to be created + sqlServerInstance := &azurev1alpha1.AzureSqlServer{ + ObjectMeta: metav1.ObjectMeta{ + Name: sqlServerName, + Namespace: "default", + }, + Spec: azurev1alpha1.AzureSqlServerSpec{ + Location: rgLocation, + ResourceGroup: rgName, + }, + } + + err = tc.k8sClient.Create(context.Background(), sqlServerInstance) + Expect(apierrors.IsInvalid(err)).To(Equal(false)) + Expect(err).NotTo(HaveOccurred()) + + sqlServerNamespacedName := types.NamespacedName{Name: sqlServerName, Namespace: "default"} + + Eventually(func() bool { + _ = tc.k8sClient.Get(context.Background(), sqlServerNamespacedName, sqlServerInstance) + return helpers.HasFinalizer(sqlServerInstance, AzureSQLServerFinalizerName) + }, tc.timeout, + ).Should(BeTrue()) + + Eventually(func() bool { + _ = tc.k8sClient.Get(context.Background(), sqlServerNamespacedName, sqlServerInstance) + return sqlServerInstance.IsSubmitted() + }, tc.timeout, + ).Should(BeTrue()) + + //verify secret exists in k8s + secret := &v1.Secret{} + Eventually(func() bool { + err = tc.k8sClient.Get(context.Background(), types.NamespacedName{Name: sqlServerName, Namespace: sqlServerInstance.Namespace}, secret) + if err == nil { + if (secret.ObjectMeta.Name == sqlServerName) && (secret.ObjectMeta.Namespace == sqlServerInstance.Namespace) { + return true + } + } + return false + }, tc.timeout, + ).Should(BeTrue()) + + err = tc.k8sClient.Delete(context.Background(), sqlServerInstance) + Expect(err).NotTo(HaveOccurred()) + + Eventually(func() bool { + _ = tc.k8sClient.Get(context.Background(), sqlServerNamespacedName, sqlServerInstance) + return helpers.IsBeingDeleted(sqlServerInstance) + }, tc.timeout, + ).Should(BeTrue()) + + }) + }) +}) diff --git a/controllers/eventhub_controller.go b/controllers/eventhub_controller.go index 21cc65ca4ca..cc42ed6c196 100644 --- a/controllers/eventhub_controller.go +++ b/controllers/eventhub_controller.go @@ -19,6 +19,8 @@ import ( "context" "fmt" "net/http" + "os" + "strconv" "time" "github.com/Azure/azure-service-operator/pkg/resourcemanager/config" @@ -89,6 +91,11 @@ func (r *EventhubReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { return ctrl.Result{}, nil } + requeueAfter, err := strconv.Atoi(os.Getenv("REQUEUE_AFTER")) + if err != nil { + requeueAfter = 30 + } + if !instance.IsSubmitted() { err := r.reconcileExternal(&instance) if err != nil { @@ -100,7 +107,7 @@ func (r *EventhubReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { if azerr, ok := err.(*errhelp.AzureError); ok { if helpers.ContainsString(catch, azerr.Type) { log.Info("Got ignorable error", "type", azerr.Type) - return ctrl.Result{Requeue: true, RequeueAfter: 30 * time.Second}, nil + return ctrl.Result{Requeue: true, RequeueAfter: time.Duration(requeueAfter) * time.Second}, nil } } diff --git a/controllers/eventhubnamespace_controller.go b/controllers/eventhubnamespace_controller.go index aa18e040efb..3d15ae7218c 100644 --- a/controllers/eventhubnamespace_controller.go +++ b/controllers/eventhubnamespace_controller.go @@ -18,6 +18,8 @@ package controllers import ( "context" "fmt" + "os" + "strconv" "time" azurev1alpha1 "github.com/Azure/azure-service-operator/api/v1alpha1" @@ -26,13 +28,13 @@ import ( eventhubsresourcemanager "github.com/Azure/azure-service-operator/pkg/resourcemanager/eventhubs" "github.com/go-logr/logr" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" - v1 "k8s.io/api/core/v1" ) // EventhubNamespaceReconciler reconciles a EventhubNamespace object @@ -75,6 +77,11 @@ func (r *EventhubNamespaceReconciler) Reconcile(req ctrl.Request) (ctrl.Result, return ctrl.Result{}, nil } + requeueAfter, err := strconv.Atoi(os.Getenv("REQUEUE_AFTER")) + if err != nil { + requeueAfter = 30 + } + if !instance.IsSubmitted() { err := r.reconcileExternal(&instance) if err != nil { @@ -84,7 +91,7 @@ func (r *EventhubNamespaceReconciler) Reconcile(req ctrl.Request) (ctrl.Result, } if helpers.ContainsString(catch, err.(*errhelp.AzureError).Type) { log.Info("Got ignorable error", "type", err.(*errhelp.AzureError).Type) - return ctrl.Result{Requeue: true, RequeueAfter: 30 * time.Second}, nil + return ctrl.Result{Requeue: true, RequeueAfter: time.Duration(requeueAfter) * time.Second}, nil } return ctrl.Result{}, fmt.Errorf("error when creating resource in azure: %v", err) diff --git a/controllers/suite_test.go b/controllers/suite_test.go index 49e8eabcb22..64b3ec4082b 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -37,6 +37,10 @@ import ( resourcegroupsresourcemanager "github.com/Azure/azure-service-operator/pkg/resourcemanager/resourcegroups" resourcemanagerstorages "github.com/Azure/azure-service-operator/pkg/resourcemanager/storages" + resourcemanagersql "github.com/Azure/azure-service-operator/pkg/resourcemanager/sqlclient" + + resourcemanagersqlmock "github.com/Azure/azure-service-operator/pkg/resourcemanager/mock/sqlclient" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -74,7 +78,7 @@ type testContext struct { var tc testContext func TestAPIs(t *testing.T) { - t.Parallel() + //t.Parallel() RegisterFailHandler(Fail) RunSpecsWithDefaultAndCustomReporters(t, @@ -141,18 +145,22 @@ var _ = BeforeSuite(func() { var eventHubManagers resourcemanagereventhub.EventHubManagers var storageManagers resourcemanagerstorages.StorageManagers var keyVaultManager resourcemanagerkeyvaults.KeyVaultManager + var resourceClient resourcemanagersql.ResourceClient + if os.Getenv("TEST_CONTROLLER_WITH_MOCKS") == "false" { resourceGroupManager = resourcegroupsresourcemanager.AzureResourceGroupManager eventHubManagers = resourcemanagereventhub.AzureEventHubManagers storageManagers = resourcemanagerstorages.AzureStorageManagers keyVaultManager = resourcemanagerkeyvaults.AzureKeyVaultManager + resourceClient = &resourcemanagersql.GoSDKClient{} timeout = time.Second * 320 } else { resourceGroupManager = &resourcegroupsresourcemanagermock.MockResourceGroupManager{} eventHubManagers = resourcemanagereventhubmock.MockEventHubManagers storageManagers = resourcemanagerstoragesmock.MockStorageManagers keyVaultManager = &resourcemanagerkeyvaultsmock.MockKeyVaultManager{} - timeout = time.Second * 20 + resourceClient = &resourcemanagersqlmock.MockGoSDKClient{} + timeout = time.Second * 60 } err = (&KeyVaultReconciler{ @@ -196,6 +204,24 @@ var _ = BeforeSuite(func() { }).SetupWithManager(k8sManager) Expect(err).ToNot(HaveOccurred()) + err = (&AzureSqlServerReconciler{ + Client: k8sManager.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("AzureSqlServer"), + Recorder: k8sManager.GetEventRecorderFor("AzureSqlServer-controller"), + Scheme: scheme.Scheme, + ResourceClient: resourceClient, + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + + err = (&AzureSqlDatabaseReconciler{ + Client: k8sManager.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("AzureSqlDatabase"), + Recorder: k8sManager.GetEventRecorderFor("AzureSqlDatabase-controller"), + Scheme: scheme.Scheme, + ResourceClient: resourceClient, + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + go func() { err = k8sManager.Start(ctrl.SetupSignalHandler()) Expect(err).ToNot(HaveOccurred()) diff --git a/main.go b/main.go index 496f16efa3b..3b2be7f4c20 100644 --- a/main.go +++ b/main.go @@ -27,6 +27,7 @@ import ( resourcemanagereventhub "github.com/Azure/azure-service-operator/pkg/resourcemanager/eventhubs" resourcemanagerkeyvault "github.com/Azure/azure-service-operator/pkg/resourcemanager/keyvaults" resourcemanagerresourcegroup "github.com/Azure/azure-service-operator/pkg/resourcemanager/resourcegroups" + resourcemanagersql "github.com/Azure/azure-service-operator/pkg/resourcemanager/sqlclient" resourcemanagerstorage "github.com/Azure/azure-service-operator/pkg/resourcemanager/storages" azurev1alpha1 "github.com/Azure/azure-service-operator/api/v1alpha1" @@ -87,6 +88,7 @@ func main() { eventhubManagers := resourcemanagereventhub.AzureEventHubManagers storageManagers := resourcemanagerstorage.AzureStorageManagers keyVaultManager := resourcemanagerkeyvault.AzureKeyVaultManager + resourceClient := resourcemanagersql.GoSDKClient{} err = (&controllers.StorageReconciler{ Client: mgr.GetClient(), @@ -175,19 +177,21 @@ func main() { } if err = (&controllers.AzureSqlServerReconciler{ - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("AzureSqlServer"), - Recorder: mgr.GetEventRecorderFor("AzureSqlServer-controller"), - Scheme: mgr.GetScheme(), + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("AzureSqlServer"), + Recorder: mgr.GetEventRecorderFor("AzureSqlServer-controller"), + Scheme: mgr.GetScheme(), + ResourceClient: resourceClient, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "AzureSqlServer") os.Exit(1) } if err = (&controllers.AzureSqlDatabaseReconciler{ - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("AzureSqlDatabase"), - Recorder: mgr.GetEventRecorderFor("AzureSqlDatabase-controller"), - Scheme: mgr.GetScheme(), + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("AzureSqlDatabase"), + Recorder: mgr.GetEventRecorderFor("AzureSqlDatabase-controller"), + Scheme: mgr.GetScheme(), + ResourceClient: resourceClient, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "AzureSqlDatabase") os.Exit(1) @@ -198,17 +202,19 @@ func main() { ctrl.Log.WithName("controllers").WithName(NameAzureSQLFirewallRuleOperator), NameAzureSQLFirewallRuleOperator, ), - Recorder: mgr.GetEventRecorderFor("SqlFirewallRule-controller"), - Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorderFor("SqlFirewallRule-controller"), + Scheme: mgr.GetScheme(), + ResourceClient: resourceClient, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "SqlFirewallRule") os.Exit(1) } if err = (&controllers.AzureSqlActionReconciler{ - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("AzureSqlAction"), - Recorder: mgr.GetEventRecorderFor("AzureSqlAction-controller"), - Scheme: mgr.GetScheme(), + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("AzureSqlAction"), + Recorder: mgr.GetEventRecorderFor("AzureSqlAction-controller"), + Scheme: mgr.GetScheme(), + ResourceClient: resourceClient, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "AzureSqlAction") os.Exit(1) diff --git a/pkg/resourcemanager/mock/sqlclient/sqlclient_gosdk.go b/pkg/resourcemanager/mock/sqlclient/sqlclient_gosdk.go new file mode 100644 index 00000000000..36645c1d59f --- /dev/null +++ b/pkg/resourcemanager/mock/sqlclient/sqlclient_gosdk.go @@ -0,0 +1,133 @@ +/* +Copyright 2019 microsoft. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package sqlclient + +import ( + "context" + "net/http" + + "github.com/Azure/azure-sdk-for-go/services/preview/sql/mgmt/2015-05-01-preview/sql" + helpers "github.com/Azure/azure-service-operator/pkg/resourcemanager/mock/helpers" + sqlclient "github.com/Azure/azure-service-operator/pkg/resourcemanager/sqlclient" + "github.com/Azure/go-autorest/autorest" +) + +// MockGoSDKClient struct +type MockGoSDKClient struct { + Server sql.Server + Database sql.Database + FirewallRule sql.FirewallRule + DatabasesCreateOrUpdateFuture sql.DatabasesCreateOrUpdateFuture + FailoverGroupsCreateOrUpdateFuture sql.FailoverGroupsCreateOrUpdateFuture +} + +// CreateOrUpdateSQLServer creates a new sql server +func (sdk *MockGoSDKClient) CreateOrUpdateSQLServer(ctx context.Context, resourceGroupName string, location string, serverName string, properties sqlclient.SQLServerProperties) (result sql.Server, err error) { + var sqlServer = sql.Server{ + Response: helpers.GetRestResponse(http.StatusCreated), + } + + sdk.Server = sqlServer + + return sqlServer, nil +} + +//DeleteSQLServer return StatusOK +func (sdk *MockGoSDKClient) DeleteSQLServer(ctx context.Context, resourceGroupName string, serverName string) (result autorest.Response, err error) { + + return helpers.GetRestResponse(http.StatusOK), nil +} + +//GetServer get server +func (sdk *MockGoSDKClient) GetServer(ctx context.Context, resourceGroupName string, serverName string) (result sql.Server, err error) { + + state := "Ready" + serverProperties := sql.ServerProperties{State: &state} + var sqlServer = sql.Server{ + Response: helpers.GetRestResponse(http.StatusCreated), + ServerProperties: &serverProperties, + } + + sdk.Server = sqlServer + + return sqlServer, nil +} + +//CreateOrUpdateSQLFirewallRule create or +func (sdk *MockGoSDKClient) CreateOrUpdateSQLFirewallRule(ctx context.Context, resourceGroupName string, serverName string, ruleName string, startIP string, endIP string) (result bool, err error) { + + return true, nil +} + +//DeleteSQLFirewallRule delete sql firewall +func (sdk *MockGoSDKClient) DeleteSQLFirewallRule(ctx context.Context, resourceGroupName string, serverName string, ruleName string) (err error) { + return nil +} + +//DeleteDB delete database +func (sdk *MockGoSDKClient) DeleteDB(ctx context.Context, resourceGroupName string, serverName string, databaseName string) (result autorest.Response, err error) { + + return helpers.GetRestResponse(http.StatusOK), nil +} + +//GetSQLFirewallRule get sql firewall rule +func (sdk *MockGoSDKClient) GetSQLFirewallRule(ctx context.Context, resourceGroupName string, serverName string, ruleName string) (result sql.FirewallRule, err error) { + + var sqlFirewallRule = sql.FirewallRule{ + Response: helpers.GetRestResponse(http.StatusCreated), + } + + sdk.FirewallRule = sqlFirewallRule + + return sqlFirewallRule, nil +} + +//GetDB get database +func (sdk *MockGoSDKClient) GetDB(ctx context.Context, resourceGroupName string, serverName string, databaseName string) (sql.Database, error) { + + var sqlDatabase = sql.Database{ + Response: helpers.GetRestResponse(http.StatusCreated), + } + + sdk.Database = sqlDatabase + + return sqlDatabase, nil +} + +//CreateOrUpdateDB create or update DB +func (sdk *MockGoSDKClient) CreateOrUpdateDB(ctx context.Context, resourceGroupName string, location string, serverName string, properties sqlclient.SQLDatabaseProperties) (sql.DatabasesCreateOrUpdateFuture, error) { + + var sqlDatabasesCreateOrUpdateFuture = sql.DatabasesCreateOrUpdateFuture{} + + return sqlDatabasesCreateOrUpdateFuture, nil +} + +//CreateOrUpdateFailoverGroup create or update failover group +func (sdk *MockGoSDKClient) CreateOrUpdateFailoverGroup(ctx context.Context, resourceGroupName string, serverName string, failovergroupname string, properties sqlclient.SQLFailoverGroupProperties) (result sql.FailoverGroupsCreateOrUpdateFuture, err error) { + + var sqlFailoverGroupsCreateOrUpdateFuture = sql.FailoverGroupsCreateOrUpdateFuture{} + sdk.FailoverGroupsCreateOrUpdateFuture = sqlFailoverGroupsCreateOrUpdateFuture + + return sqlFailoverGroupsCreateOrUpdateFuture, nil + +} + +//DeleteFailoverGroup delete fail over group +func (sdk *MockGoSDKClient) DeleteFailoverGroup(ctx context.Context, resourceGroupName string, serverName string, failoverGroupName string) (result autorest.Response, err error) { + + return helpers.GetRestResponse(http.StatusOK), nil +} diff --git a/pkg/resourcemanager/sqlclient/endtoend_test.go b/pkg/resourcemanager/sqlclient/endtoend_test.go index 41b72c94fc3..e810b6b4bb6 100644 --- a/pkg/resourcemanager/sqlclient/endtoend_test.go +++ b/pkg/resourcemanager/sqlclient/endtoend_test.go @@ -36,12 +36,10 @@ func TestCreateOrUpdateSQLServer(t *testing.T) { } // create the Go SDK client with relevant info - sdk := GoSDKClient{ - Ctx: ctx, - ResourceGroupName: groupName, - ServerName: generateName("sqlsrvtest"), - Location: "eastus2", - } + sdk := GoSDKClient{} + + location := "eastus2" + serverName := generateName("sqlsrvtest") // create the server sqlServerProperties := SQLServerProperties{ @@ -52,7 +50,7 @@ func TestCreateOrUpdateSQLServer(t *testing.T) { // wait for server to be created, then only proceed once activated for { time.Sleep(time.Second) - server, err := sdk.CreateOrUpdateSQLServer(sqlServerProperties) + server, err := sdk.CreateOrUpdateSQLServer(ctx, groupName, location, serverName, sqlServerProperties) if err == nil { if *server.State == "Ready" { util.PrintAndLog("sql server ready") @@ -82,7 +80,7 @@ func TestCreateOrUpdateSQLServer(t *testing.T) { // wait for db to be created, then only proceed once activated for { time.Sleep(time.Second) - future, err := sdk.CreateOrUpdateDB(sqlDBProperties) + future, err := sdk.CreateOrUpdateDB(ctx, groupName, location, serverName, sqlDBProperties) if err == nil { db, err := future.Result(getGoDbClient()) if err == nil { @@ -108,7 +106,7 @@ func TestCreateOrUpdateSQLServer(t *testing.T) { // create a firewall rule util.PrintAndLog("creating firewall rule...") - _, err = sdk.CreateOrUpdateSQLFirewallRule("test-rule1", "1.1.1.1", "2.2.2.2") + _, err = sdk.CreateOrUpdateSQLFirewallRule(ctx, groupName, serverName, "test-rule1", "1.1.1.1", "2.2.2.2") if err != nil { util.PrintAndLog(fmt.Sprintf("cannot create firewall rule: %v", err)) t.FailNow() @@ -121,12 +119,7 @@ func TestCreateOrUpdateSQLServer(t *testing.T) { // create secondary SQL server // create the Go SDK client with relevant info secSrvName := generateName("sqlsrvsecondary") - sdk2 := GoSDKClient{ - Ctx: ctx, - ResourceGroupName: groupName, - ServerName: secSrvName, - Location: "westus", - } + secLocation := "westus" // create the server sqlServerProperties = SQLServerProperties{ @@ -137,7 +130,7 @@ func TestCreateOrUpdateSQLServer(t *testing.T) { // wait for server to be created, then only proceed once activated for { time.Sleep(time.Second) - server, err := sdk2.CreateOrUpdateSQLServer(sqlServerProperties) + server, err := sdk.CreateOrUpdateSQLServer(ctx, groupName, secLocation, secSrvName, sqlServerProperties) if err == nil { if *server.State == "Ready" { util.PrintAndLog("sql server ready") @@ -170,7 +163,7 @@ func TestCreateOrUpdateSQLServer(t *testing.T) { failoverGroupName := generateName("failovergroup") for { time.Sleep(time.Second) - _, err := sdk.CreateOrUpdateFailoverGroup(failoverGroupName, sqlFailoverGroupProperties) + _, err := sdk.CreateOrUpdateFailoverGroup(ctx, groupName, secSrvName, failoverGroupName, sqlFailoverGroupProperties) if err == nil { util.PrintAndLog(fmt.Sprintf("failover group created successfully %s", failoverGroupName)) break @@ -188,7 +181,7 @@ func TestCreateOrUpdateSQLServer(t *testing.T) { // delete firewall rule util.PrintAndLog("deleting firewall rule...") - err = sdk.DeleteSQLFirewallRule("test-rule1") + err = sdk.DeleteSQLFirewallRule(ctx, groupName, serverName, "test-rule1") if err != nil { util.PrintAndLog(fmt.Sprintf("cannot delete firewall rule: %v", err)) t.FailNow() @@ -197,7 +190,7 @@ func TestCreateOrUpdateSQLServer(t *testing.T) { // delete the failover group util.PrintAndLog("deleting failover group...") - response, err := sdk.DeleteFailoverGroup(failoverGroupName) + response, err := sdk.DeleteFailoverGroup(ctx, groupName, secSrvName, failoverGroupName) if err == nil { if response.StatusCode == 200 { util.PrintAndLog("failover group deleted") @@ -209,7 +202,7 @@ func TestCreateOrUpdateSQLServer(t *testing.T) { // delete the DB time.Sleep(time.Second) - response, err = sdk.DeleteDB("sqldatabase-sample") + response, err = sdk.DeleteDB(ctx, groupName, secSrvName, "sqldatabase-sample") if err == nil { if response.StatusCode == 200 { util.PrintAndLog("db deleted") @@ -221,7 +214,7 @@ func TestCreateOrUpdateSQLServer(t *testing.T) { // delete the server time.Sleep(time.Second) - response, err = sdk.DeleteSQLServer() + response, err = sdk.DeleteSQLServer(ctx, groupName, serverName) if err == nil { if response.StatusCode == 200 { util.PrintAndLog("sql server deleted") @@ -238,7 +231,7 @@ func TestCreateOrUpdateSQLServer(t *testing.T) { // delete the secondary server time.Sleep(time.Second) - response, err = sdk2.DeleteSQLServer() + response, err = sdk.DeleteSQLServer(ctx, groupName, secSrvName) if err == nil { if response.StatusCode == 200 { util.PrintAndLog("sql server deleted") diff --git a/pkg/resourcemanager/sqlclient/gosdkclient.go b/pkg/resourcemanager/sqlclient/gosdkclient.go index f5ddbb84e62..f9ffea06e71 100644 --- a/pkg/resourcemanager/sqlclient/gosdkclient.go +++ b/pkg/resourcemanager/sqlclient/gosdkclient.go @@ -5,12 +5,6 @@ package sqlclient -import "context" - // GoSDKClient is used to pass information to an implemetation of the ResourceClient interface that wraps calls to the Go SDK for Azure. type GoSDKClient struct { - Ctx context.Context - ResourceGroupName string - ServerName string - Location string } diff --git a/pkg/resourcemanager/sqlclient/resourceclient.go b/pkg/resourcemanager/sqlclient/resourceclient.go index 9cb308a736e..c8b5521a799 100644 --- a/pkg/resourcemanager/sqlclient/resourceclient.go +++ b/pkg/resourcemanager/sqlclient/resourceclient.go @@ -6,22 +6,23 @@ package sqlclient import ( + "context" + "github.com/Azure/azure-sdk-for-go/services/preview/sql/mgmt/2015-05-01-preview/sql" "github.com/Azure/go-autorest/autorest" ) // ResourceClient contains the helper functions for interacting with SQL servers / databases type ResourceClient interface { - CreateOrUpdateSQLServer(properties SQLServerProperties) (result sql.Server, err error) - CreateOrUpdateSQLFirewallRule(ruleName string, startIP string, endIP string) (result bool, err error) - CreateOrUpdateDB(properties SQLDatabaseProperties) (result sql.Database, err error) - CreateOrUpdateFailoverGroup(failovergroupname string, properties SQLFailoverGroupProperties) (result sql.FailoverGroup, err error) - GetServer() (result sql.Server, err error) - GetSQLFirewallRule(ruleName string) (result sql.FirewallRule, err error) - GetDB(databaseName string) (sql.Database, error) - DeleteDB(databaseName string) (result autorest.Response, err error) - DeleteSQLFirewallRule(ruleName string) (err error) - DeleteSQLServer() (result autorest.Response, err error) - DeleteFailoverGroup(failoverGroupName string) - CheckNameAvailability() (result AvailabilityResponse, err error) + CreateOrUpdateSQLServer(ctx context.Context, resourceGroupName string, location string, serverName string, properties SQLServerProperties) (result sql.Server, err error) + DeleteSQLServer(ctx context.Context, resourceGroupName string, serverName string) (result autorest.Response, err error) + GetServer(ctx context.Context, resourceGroupName string, serverName string) (result sql.Server, err error) + DeleteSQLFirewallRule(ctx context.Context, resourceGroupName string, serverName string, ruleName string) (err error) + CreateOrUpdateFailoverGroup(ctx context.Context, resourceGroupName string, serverName string, failovergroupname string, properties SQLFailoverGroupProperties) (result sql.FailoverGroupsCreateOrUpdateFuture, err error) + DeleteFailoverGroup(ctx context.Context, resourceGroupName string, serverName string, failoverGroupName string) (result autorest.Response, err error) + DeleteDB(ctx context.Context, resourceGroupName string, serverName string, databaseName string) (result autorest.Response, err error) + GetSQLFirewallRule(ctx context.Context, resourceGroupName string, serverName string, ruleName string) (result sql.FirewallRule, err error) + GetDB(ctx context.Context, resourceGroupName string, serverName string, databaseName string) (sql.Database, error) + CreateOrUpdateDB(ctx context.Context, resourceGroupName string, location string, serverName string, properties SQLDatabaseProperties) (sql.DatabasesCreateOrUpdateFuture, error) + CreateOrUpdateSQLFirewallRule(ctx context.Context, resourceGroupName string, serverName string, ruleName string, startIP string, endIP string) (result bool, err error) } diff --git a/pkg/resourcemanager/sqlclient/sqlclient_godsk.go b/pkg/resourcemanager/sqlclient/sqlclient_godsk.go index 6bf2d86bfad..e8f957b4614 100644 --- a/pkg/resourcemanager/sqlclient/sqlclient_godsk.go +++ b/pkg/resourcemanager/sqlclient/sqlclient_godsk.go @@ -50,17 +50,17 @@ func getGoFailoverGroupsClient() sql.FailoverGroupsClient { } // CreateOrUpdateSQLServer creates a SQL server in Azure -func (sdk GoSDKClient) CreateOrUpdateSQLServer(properties SQLServerProperties) (result sql.Server, err error) { +func (sdk GoSDKClient) CreateOrUpdateSQLServer(ctx context.Context, resourceGroupName string, location string, serverName string, properties SQLServerProperties) (result sql.Server, err error) { serversClient := getGoServersClient() serverProp := SQLServerPropertiesToServer(properties) // issue the creation future, err := serversClient.CreateOrUpdate( - sdk.Ctx, - sdk.ResourceGroupName, - sdk.ServerName, + ctx, + resourceGroupName, + serverName, sql.Server{ - Location: to.StringPtr(sdk.Location), + Location: to.StringPtr(location), ServerProperties: &serverProp, }) if err != nil { @@ -73,19 +73,19 @@ func (sdk GoSDKClient) CreateOrUpdateSQLServer(properties SQLServerProperties) ( // CreateOrUpdateSQLFirewallRule creates or updates a firewall rule // based on code from: https://github.com/Azure-Samples/azure-sdk-for-go-samples/blob/master/sql/sql.go#L111 // to allow allow Azure services to connect example: https://docs.microsoft.com/en-us/azure/sql-database/sql-database-firewall-configure#manage-firewall-rules-using-azure-cli -func (sdk GoSDKClient) CreateOrUpdateSQLFirewallRule(ruleName string, startIP string, endIP string) (result bool, err error) { +func (sdk GoSDKClient) CreateOrUpdateSQLFirewallRule(ctx context.Context, resourceGroupName string, serverName string, ruleName string, startIP string, endIP string) (result bool, err error) { // check to see if the server exists, if it doesn't then short-circuit - server, err := sdk.GetServer() + server, err := sdk.GetServer(ctx, resourceGroupName, serverName) if err != nil || *server.State != "Ready" { return false, err } firewallClient := getGoFirewallClient() _, err = firewallClient.CreateOrUpdate( - sdk.Ctx, - sdk.ResourceGroupName, - sdk.ServerName, + ctx, + resourceGroupName, + serverName, ruleName, sql.FirewallRule{ FirewallRuleProperties: &sql.FirewallRuleProperties{ @@ -103,34 +103,29 @@ func (sdk GoSDKClient) CreateOrUpdateSQLFirewallRule(ruleName string, startIP st } // CreateOrUpdateDB creates or updates a DB in Azure -func (sdk GoSDKClient) CreateOrUpdateDB(properties SQLDatabaseProperties) (sql.DatabasesCreateOrUpdateFuture, error) { +func (sdk GoSDKClient) CreateOrUpdateDB(ctx context.Context, resourceGroupName string, location string, serverName string, properties SQLDatabaseProperties) (sql.DatabasesCreateOrUpdateFuture, error) { dbClient := getGoDbClient() dbProp := SQLDatabasePropertiesToDatabase(properties) return dbClient.CreateOrUpdate( - sdk.Ctx, - sdk.ResourceGroupName, - sdk.ServerName, + ctx, + resourceGroupName, + serverName, properties.DatabaseName, sql.Database{ - Location: to.StringPtr(sdk.Location), + Location: to.StringPtr(location), DatabaseProperties: &dbProp, }) } // CreateOrUpdateFailoverGroup creates a failover group -func (sdk GoSDKClient) CreateOrUpdateFailoverGroup(failovergroupname string, properties SQLFailoverGroupProperties) (result sql.FailoverGroupsCreateOrUpdateFuture, err error) { +func (sdk GoSDKClient) CreateOrUpdateFailoverGroup(ctx context.Context, resourceGroupName string, serverName string, failovergroupname string, properties SQLFailoverGroupProperties) (result sql.FailoverGroupsCreateOrUpdateFuture, err error) { failoverGroupsClient := getGoFailoverGroupsClient() // Construct a PartnerInfo object from the server name // Get resource ID from the servername to use - secServerSDKClient := GoSDKClient{ - Ctx: context.Background(), - ResourceGroupName: properties.SecondaryServerResourceGroup, - ServerName: properties.SecondaryServerName, - Location: "", // We dont get the location from the user for the secondary server as it is not required - } - server, err := secServerSDKClient.GetServer() + + server, err := sdk.GetServer(ctx, properties.SecondaryServerResourceGroup, properties.SecondaryServerName) if err != nil { return result, nil } @@ -147,7 +142,7 @@ func (sdk GoSDKClient) CreateOrUpdateFailoverGroup(failovergroupname string, pro // Parse the Databases in the Databaselist and form array of Resource IDs for _, each := range properties.DatabaseList { - database, err := sdk.GetDB(each) + database, err := sdk.GetDB(ctx, resourceGroupName, serverName, each) if err != nil { return result, err } @@ -169,41 +164,41 @@ func (sdk GoSDKClient) CreateOrUpdateFailoverGroup(failovergroupname string, pro } return failoverGroupsClient.CreateOrUpdate( - sdk.Ctx, - sdk.ResourceGroupName, - sdk.ServerName, + ctx, + resourceGroupName, + serverName, failovergroupname, failoverGroup) } // GetSQLFirewallRule returns a firewall rule -func (sdk GoSDKClient) GetSQLFirewallRule(ruleName string) (result sql.FirewallRule, err error) { +func (sdk GoSDKClient) GetSQLFirewallRule(ctx context.Context, resourceGroupName string, serverName string, ruleName string) (result sql.FirewallRule, err error) { firewallClient := getGoFirewallClient() return firewallClient.Get( - sdk.Ctx, - sdk.ResourceGroupName, - sdk.ServerName, + ctx, + resourceGroupName, + serverName, ruleName, ) } // GetDB retrieves a database -func (sdk GoSDKClient) GetDB(databaseName string) (sql.Database, error) { +func (sdk GoSDKClient) GetDB(ctx context.Context, resourceGroupName string, serverName string, databaseName string) (sql.Database, error) { dbClient := getGoDbClient() return dbClient.Get( - sdk.Ctx, - sdk.ResourceGroupName, - sdk.ServerName, + ctx, + resourceGroupName, + serverName, databaseName, "serviceTierAdvisors, transparentDataEncryption", ) } // DeleteDB deletes a DB -func (sdk GoSDKClient) DeleteDB(databaseName string) (result autorest.Response, err error) { +func (sdk GoSDKClient) DeleteDB(ctx context.Context, resourceGroupName string, serverName string, databaseName string) (result autorest.Response, err error) { result = autorest.Response{ Response: &http.Response{ StatusCode: 200, @@ -211,22 +206,22 @@ func (sdk GoSDKClient) DeleteDB(databaseName string) (result autorest.Response, } // check to see if the server exists, if it doesn't then short-circuit - server, err := sdk.GetServer() + server, err := sdk.GetServer(ctx, resourceGroupName, serverName) if err != nil || *server.State != "Ready" { return result, nil } // check to see if the db exists, if it doesn't then short-circuit - _, err = sdk.GetDB(databaseName) + _, err = sdk.GetDB(ctx, resourceGroupName, serverName, databaseName) if err != nil { return result, nil } dbClient := getGoDbClient() result, err = dbClient.Delete( - sdk.Ctx, - sdk.ResourceGroupName, - sdk.ServerName, + ctx, + resourceGroupName, + serverName, databaseName, ) @@ -234,25 +229,25 @@ func (sdk GoSDKClient) DeleteDB(databaseName string) (result autorest.Response, } // DeleteSQLFirewallRule deletes a firewall rule -func (sdk GoSDKClient) DeleteSQLFirewallRule(ruleName string) (err error) { +func (sdk GoSDKClient) DeleteSQLFirewallRule(ctx context.Context, resourceGroupName string, serverName string, ruleName string) (err error) { // check to see if the server exists, if it doesn't then short-circuit - server, err := sdk.GetServer() + server, err := sdk.GetServer(ctx, resourceGroupName, serverName) if err != nil || *server.State != "Ready" { return nil } // check to see if the rule exists, if it doesn't then short-circuit - _, err = sdk.GetSQLFirewallRule(ruleName) + _, err = sdk.GetSQLFirewallRule(ctx, resourceGroupName, serverName, ruleName) if err != nil { return nil } firewallClient := getGoFirewallClient() _, err = firewallClient.Delete( - sdk.Ctx, - sdk.ResourceGroupName, - sdk.ServerName, + ctx, + resourceGroupName, + serverName, ruleName, ) @@ -260,7 +255,7 @@ func (sdk GoSDKClient) DeleteSQLFirewallRule(ruleName string) (err error) { } // DeleteSQLServer deletes a SQL server -func (sdk GoSDKClient) DeleteSQLServer() (result autorest.Response, err error) { +func (sdk GoSDKClient) DeleteSQLServer(ctx context.Context, resourceGroupName string, serverName string) (result autorest.Response, err error) { result = autorest.Response{ Response: &http.Response{ StatusCode: 200, @@ -268,16 +263,16 @@ func (sdk GoSDKClient) DeleteSQLServer() (result autorest.Response, err error) { } // check to see if the server exists, if it doesn't then short-circuit - _, err = sdk.GetServer() + _, err = sdk.GetServer(ctx, resourceGroupName, serverName) if err != nil { return result, nil } serversClient := getGoServersClient() future, err := serversClient.Delete( - sdk.Ctx, - sdk.ResourceGroupName, - sdk.ServerName, + ctx, + resourceGroupName, + serverName, ) if err != nil { return result, err @@ -287,19 +282,19 @@ func (sdk GoSDKClient) DeleteSQLServer() (result autorest.Response, err error) { } // GetFailoverGroup retrieves a failover group -func (sdk GoSDKClient) GetFailoverGroup(failovergroupname string) (sql.FailoverGroup, error) { +func (sdk GoSDKClient) GetFailoverGroup(ctx context.Context, resourceGroupName string, serverName string, failovergroupname string) (sql.FailoverGroup, error) { failoverGroupsClient := getGoFailoverGroupsClient() return failoverGroupsClient.Get( - sdk.Ctx, - sdk.ResourceGroupName, - sdk.ServerName, + ctx, + resourceGroupName, + serverName, failovergroupname, ) } // DeleteFailoverGroup deletes a failover group -func (sdk GoSDKClient) DeleteFailoverGroup(failoverGroupName string) (result autorest.Response, err error) { +func (sdk GoSDKClient) DeleteFailoverGroup(ctx context.Context, resourceGroupName string, serverName string, failoverGroupName string) (result autorest.Response, err error) { result = autorest.Response{ Response: &http.Response{ @@ -308,22 +303,22 @@ func (sdk GoSDKClient) DeleteFailoverGroup(failoverGroupName string) (result aut } // check to see if the server exists, if it doesn't then short-circuit - _, err = sdk.GetServer() + _, err = sdk.GetServer(ctx, resourceGroupName, serverName) if err != nil { return result, nil } // check to see if the failover group exists, if it doesn't then short-circuit - _, err = sdk.GetFailoverGroup(failoverGroupName) + _, err = sdk.GetFailoverGroup(ctx, resourceGroupName, serverName, failoverGroupName) if err != nil { return result, nil } failoverGroupsClient := getGoFailoverGroupsClient() future, err := failoverGroupsClient.Delete( - sdk.Ctx, - sdk.ResourceGroupName, - sdk.ServerName, + ctx, + resourceGroupName, + serverName, failoverGroupName, ) if err != nil { @@ -334,13 +329,13 @@ func (sdk GoSDKClient) DeleteFailoverGroup(failoverGroupName string) (result aut } // CheckNameAvailability determines whether a SQL resource can be created with the specified name -func (sdk GoSDKClient) CheckNameAvailability() (result AvailabilityResponse, err error) { +func (sdk GoSDKClient) CheckNameAvailability(ctx context.Context, resourceGroupName string, serverName string) (result AvailabilityResponse, err error) { serversClient := getGoServersClient() response, err := serversClient.CheckNameAvailability( - sdk.Ctx, + ctx, sql.CheckNameAvailabilityRequest{ - Name: to.StringPtr(sdk.ServerName), + Name: to.StringPtr(serverName), Type: to.StringPtr(typeOfService), }, ) @@ -352,12 +347,12 @@ func (sdk GoSDKClient) CheckNameAvailability() (result AvailabilityResponse, err } // GetServer returns a SQL server -func (sdk GoSDKClient) GetServer() (result sql.Server, err error) { +func (sdk GoSDKClient) GetServer(ctx context.Context, resourceGroupName string, serverName string) (result sql.Server, err error) { serversClient := getGoServersClient() return serversClient.Get( - sdk.Ctx, - sdk.ResourceGroupName, - sdk.ServerName, + ctx, + resourceGroupName, + serverName, ) }