diff --git a/api/v1alpha1/aso_types.go b/api/v1alpha1/aso_types.go new file mode 100644 index 00000000000..e1ac0bf24a0 --- /dev/null +++ b/api/v1alpha1/aso_types.go @@ -0,0 +1,23 @@ +/* +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 v1alpha1 + +// AzureServiceOperatorsStatus (ASOStatus) defines the observed state of resource actions +type ASOStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file + Provisioning bool `json:"provisioning,omitempty"` + Provisioned bool `json:"provisioned,omitempty"` + State string `json:"state,omitempty"` + Message string `json:"message,omitempty"` +} \ No newline at end of file diff --git a/api/v1alpha1/azuresqlaction_types.go b/api/v1alpha1/azuresqlaction_types.go index 91b4863c7d9..77e2d296513 100644 --- a/api/v1alpha1/azuresqlaction_types.go +++ b/api/v1alpha1/azuresqlaction_types.go @@ -31,15 +31,6 @@ type AzureSqlActionSpec struct { ServerName string `json:"servername"` } -// AzureSqlActionStatus defines the observed state of AzureSqlAction -type AzureSqlActionStatus struct { - // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - // Important: Run "make" to regenerate code after modifying this file - Provisioning bool `json:"provisioning,omitempty"` - Provisioned bool `json:"provisioned,omitempty"` - Message string `json:"state,omitempty"` -} - // +kubebuilder:object:root=true // +kubebuilder:subresource:status @@ -49,7 +40,7 @@ type AzureSqlAction struct { metav1.ObjectMeta `json:"metadata,omitempty"` Spec AzureSqlActionSpec `json:"spec,omitempty"` - Status AzureSqlActionStatus `json:"status,omitempty"` + Status ASOStatus `json:"status,omitempty"` } // +kubebuilder:object:root=true diff --git a/api/v1alpha1/azuresqldatabase_types.go b/api/v1alpha1/azuresqldatabase_types.go index 52d2cecd358..33dc90a3d0b 100644 --- a/api/v1alpha1/azuresqldatabase_types.go +++ b/api/v1alpha1/azuresqldatabase_types.go @@ -32,14 +32,6 @@ type AzureSqlDatabaseSpec struct { Edition sql.DBEdition `json:"edition"` } -// AzureSqlDatabaseStatus defines the observed state of AzureSqlDatabase -type AzureSqlDatabaseStatus struct { - // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - // Important: Run "make" to regenerate code after modifying this file - Provisioning bool `json:"provisioning,omitempty"` - Provisioned bool `json:"provisioned,omitempty"` -} - // +kubebuilder:object:root=true // +kubebuilder:subresource:status // AzureSqlDatabase is the Schema for the azuresqldatabases API @@ -48,7 +40,7 @@ type AzureSqlDatabase struct { metav1.ObjectMeta `json:"metadata,omitempty"` Spec AzureSqlDatabaseSpec `json:"spec,omitempty"` - Status AzureSqlDatabaseStatus `json:"status,omitempty"` + Status ASOStatus `json:"status,omitempty"` } // +kubebuilder:object:root=true diff --git a/api/v1alpha1/azuresqlfirewallrule_types.go b/api/v1alpha1/azuresqlfirewallrule_types.go index 82ec98615ca..ad4f1486198 100644 --- a/api/v1alpha1/azuresqlfirewallrule_types.go +++ b/api/v1alpha1/azuresqlfirewallrule_types.go @@ -32,15 +32,6 @@ type AzureSqlFirewallRuleSpec struct { EndIPAddress string `json:"endipaddress,omitempty"` } -// AzureSqlFirewallRuleStatus defines the observed state of AzureSqlFirewallRule -type AzureSqlFirewallRuleStatus struct { - // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - // Important: Run "make" to regenerate code after modifying this file - Provisioning bool `json:"provisioning,omitempty"` - Provisioned bool `json:"provisioned,omitempty"` - Message string `json:"message,omitempty"` -} - // +kubebuilder:object:root=true // +kubebuilder:subresource:status // AzureSqlFirewallRule is the Schema for the azuresqlfirewallrules API @@ -49,7 +40,7 @@ type AzureSqlFirewallRule struct { metav1.ObjectMeta `json:"metadata,omitempty"` Spec AzureSqlFirewallRuleSpec `json:"spec,omitempty"` - Status AzureSqlFirewallRuleStatus `json:"status,omitempty"` + Status ASOStatus `json:"status,omitempty"` } // +kubebuilder:object:root=true diff --git a/api/v1alpha1/azuresqlserver_types.go b/api/v1alpha1/azuresqlserver_types.go index aa8cae11e4b..0d46795a84c 100644 --- a/api/v1alpha1/azuresqlserver_types.go +++ b/api/v1alpha1/azuresqlserver_types.go @@ -30,16 +30,6 @@ type AzureSqlServerSpec struct { ResourceGroup string `json:"resourcegroup,omitempty"` } -// AzureSqlServerStatus defines the observed state of AzureSqlServer -type AzureSqlServerStatus struct { - // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - // Important: Run "make" to regenerate code after modifying this file - Provisioning bool `json:"provisioning,omitempty"` - Provisioned bool `json:"provisioned,omitempty"` - State string `json:"state,omitempty"` - Message string `json:"message,omitempty"` -} - // +kubebuilder:object:root=true // +kubebuilder:subresource:status // AzureSqlServer is the Schema for the azuresqlservers API @@ -48,7 +38,7 @@ type AzureSqlServer struct { metav1.ObjectMeta `json:"metadata,omitempty"` Spec AzureSqlServerSpec `json:"spec,omitempty"` - Status AzureSqlServerStatus `json:"status,omitempty"` + Status ASOStatus `json:"status,omitempty"` } // +kubebuilder:object:root=true diff --git a/api/v1alpha1/consumergroup_types.go b/api/v1alpha1/consumergroup_types.go index 8bbe94ecd5e..094f11df4e7 100644 --- a/api/v1alpha1/consumergroup_types.go +++ b/api/v1alpha1/consumergroup_types.go @@ -33,14 +33,6 @@ type ConsumerGroupSpec struct { AzureConsumerGroupName string `json:"consumerGroupName,omitempty"` } -// ConsumerGroupStatus defines the observed state of ConsumerGroup -type ConsumerGroupStatus struct { - // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - // Important: Run "make" to regenerate code after modifying this file - Provisioning bool `json:"provisioning,omitempty"` - Provisioned bool `json:"provisioned,omitempty"` -} - // +kubebuilder:object:root=true // ConsumerGroup is the Schema for the consumergroups API @@ -49,7 +41,7 @@ type ConsumerGroup struct { metav1.ObjectMeta `json:"metadata,omitempty"` Spec ConsumerGroupSpec `json:"spec,omitempty"` - Status ConsumerGroupStatus `json:"status,omitempty"` + Status ASOStatus `json:"status,omitempty"` } // +kubebuilder:object:root=true diff --git a/api/v1alpha1/cosmosdb_types.go b/api/v1alpha1/cosmosdb_types.go index cc745e62085..ba6ac6b316e 100644 --- a/api/v1alpha1/cosmosdb_types.go +++ b/api/v1alpha1/cosmosdb_types.go @@ -78,18 +78,6 @@ type CosmosDBLocation struct { } */ -// CosmosDBStatus defines the observed state of CosmosDB -type CosmosDBStatus struct { - // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - // Important: Run "make" to regenerate code after modifying this file - - // DeploymentName string `json:"deploymentName,omitempty"` - // ProvisioningState string `json:"provisioningState,omitempty"` - // Generation int64 `json:"generation,omitempty"` - Provisioning bool `json:"provisioning,omitempty"` - Provisioned bool `json:"provisioned,omitempty"` -} - type CosmosDBOutput struct { CosmosDBName string `json:"cosmosDBName,omitempty"` PrimaryMasterKey string `json:"primaryMasterKey,omitempty"` @@ -112,7 +100,7 @@ type CosmosDB struct { metav1.ObjectMeta `json:"metadata,omitempty"` Spec CosmosDBSpec `json:"spec,omitempty"` - Status CosmosDBStatus `json:"status,omitempty"` + Status ASOStatus `json:"status,omitempty"` Output CosmosDBOutput `json:"output,omitempty"` AdditionalResources CosmosDBAdditionalResources `json:"additionalResources,omitempty"` } diff --git a/api/v1alpha1/eventhub_types.go b/api/v1alpha1/eventhub_types.go index ffb85a20a76..b7fc7c92e60 100644 --- a/api/v1alpha1/eventhub_types.go +++ b/api/v1alpha1/eventhub_types.go @@ -36,14 +36,6 @@ type EventhubSpec struct { SecretName string `json:"secretName,omitempty"` } -// EventhubStatus defines the observed state of Eventhub -type EventhubStatus struct { - // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - // Important: Run "make" to regenerate code after modifying this file - Provisioning bool `json:"provisioning,omitempty"` - Provisioned bool `json:"provisioned,omitempty"` -} - //EventhubAuthorizationRule defines the name and rights of the access policy type EventhubAuthorizationRule struct { // Name - Name of AuthorizationRule for eventhub @@ -113,7 +105,7 @@ type Eventhub struct { metav1.ObjectMeta `json:"metadata,omitempty"` Spec EventhubSpec `json:"spec,omitempty"` - Status EventhubStatus `json:"status,omitempty"` + Status ASOStatus `json:"status,omitempty"` } // +kubebuilder:object:root=true diff --git a/api/v1alpha1/eventhubnamespace_types.go b/api/v1alpha1/eventhubnamespace_types.go index 9b16720cfb0..8769dcb8b48 100644 --- a/api/v1alpha1/eventhubnamespace_types.go +++ b/api/v1alpha1/eventhubnamespace_types.go @@ -33,14 +33,6 @@ type EventhubNamespaceSpec struct { ResourceGroup string `json:"resourceGroup,omitempty"` } -// EventhubNamespaceStatus defines the observed state of EventhubNamespace -type EventhubNamespaceStatus struct { - // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - // Important: Run "make" to regenerate code after modifying this file - Provisioning bool `json:"provisioning,omitempty"` - Provisioned bool `json:"provisioned,omitempty"` -} - // +kubebuilder:object:root=true // EventhubNamespace is the Schema for the eventhubnamespaces API @@ -49,7 +41,7 @@ type EventhubNamespace struct { metav1.ObjectMeta `json:"metadata,omitempty"` Spec EventhubNamespaceSpec `json:"spec,omitempty"` - Status EventhubNamespaceStatus `json:"status,omitempty"` + Status ASOStatus `json:"status,omitempty"` } // +kubebuilder:object:root=true diff --git a/api/v1alpha1/rediscache_types.go b/api/v1alpha1/rediscache_types.go index cca75ded42c..d7db7e8dbc2 100644 --- a/api/v1alpha1/rediscache_types.go +++ b/api/v1alpha1/rediscache_types.go @@ -76,18 +76,6 @@ const ( P RedisCacheSkuFamily = "P" ) -// RedisCacheStatus defines the observed state of RedisCache -type RedisCacheStatus struct { - // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - // Important: Run "make" to regenerate code after modifying this file - - // DeploymentName string `json:"deploymentName,omitempty"` - // ProvisioningState string `json:"provisioningState,omitempty"` - // Generation int64 `json:"generation,omitempty"` - Provisioning bool `json:"provisioning,omitempty"` - Provisioned bool `json:"provisioned,omitempty"` -} - type RedisCacheOutput struct { RedisCacheName string `json:"redisCacheName,omitempty"` PrimaryKey string `json:"primaryKey,omitempty"` @@ -108,7 +96,7 @@ type RedisCache struct { metav1.ObjectMeta `json:"metadata,omitempty"` Spec RedisCacheSpec `json:"spec,omitempty"` - Status RedisCacheStatus `json:"status,omitempty"` + Status ASOStatus `json:"status,omitempty"` Output RedisCacheOutput `json:"output,omitempty"` AdditionalResources RedisCacheAdditionalResources `json:"additionalResources,omitempty"` } diff --git a/api/v1alpha1/resourcegroup_types.go b/api/v1alpha1/resourcegroup_types.go index caf0b3c94e4..1aa296447b0 100644 --- a/api/v1alpha1/resourcegroup_types.go +++ b/api/v1alpha1/resourcegroup_types.go @@ -30,14 +30,6 @@ type ResourceGroupSpec struct { Location string `json:"location"` } -// ResourceGroupStatus defines the observed state of ResourceGroup -type ResourceGroupStatus struct { - // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - // Important: Run "make" to regenerate code after modifying this file - Provisioning bool `json:"provisioning,omitempty"` - Provisioned bool `json:"provisioned,omitempty"` -} - // +kubebuilder:object:root=true // ResourceGroup is the Schema for the resourcegroups API @@ -47,7 +39,7 @@ type ResourceGroup struct { metav1.ObjectMeta `json:"metadata,omitempty"` Spec ResourceGroupSpec `json:"spec,omitempty"` - Status ResourceGroupStatus `json:"status,omitempty"` + Status ASOStatus `json:"status,omitempty"` } // +kubebuilder:object:root=true diff --git a/api/v1alpha1/storage_types.go b/api/v1alpha1/storage_types.go index 5346f06f9a1..5e63d2df5d3 100644 --- a/api/v1alpha1/storage_types.go +++ b/api/v1alpha1/storage_types.go @@ -78,18 +78,6 @@ type StorageKind string // +kubebuilder:validation:Enum=Cool;Hot type StorageAccessTier string -// StorageStatus defines the observed state of Storage -type StorageStatus struct { - // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - // Important: Run "make" to regenerate code after modifying this file - - // DeploymentName string `json:"deploymentName,omitempty"` - // ProvisioningState string `json:"provisioningState,omitempty"` - // Generation int64 `json:"generation,omitempty"` - Provisioning bool `json:"provisioning,omitempty"` - Provisioned bool `json:"provisioned,omitempty"` -} - // +kubebuilder:object:root=true // +kubebuilder:subresource:status @@ -99,7 +87,7 @@ type Storage struct { metav1.ObjectMeta `json:"metadata,omitempty"` Spec StorageSpec `json:"spec,omitempty"` - Status StorageStatus `json:"status,omitempty"` + Status ASOStatus `json:"status,omitempty"` Output StorageOutput `json:"output,omitempty"` AdditionalResources StorageAdditionalResources `json:"additionalResources,omitempty"` } diff --git a/controllers/azuresqlfirewallrule_controller.go b/controllers/azuresqlfirewallrule_controller.go index a45c528ab9f..f99efde8a4e 100644 --- a/controllers/azuresqlfirewallrule_controller.go +++ b/controllers/azuresqlfirewallrule_controller.go @@ -23,7 +23,6 @@ import ( "github.com/Azure/azure-service-operator/pkg/errhelp" helpers "github.com/Azure/azure-service-operator/pkg/helpers" sql "github.com/Azure/azure-service-operator/pkg/resourcemanager/sqlclient" - "github.com/go-logr/logr" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -33,6 +32,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" azurev1alpha1 "github.com/Azure/azure-service-operator/api/v1alpha1" + telemetry "github.com/Azure/azure-service-operator/pkg/telemetry" ) const azureSQLFirewallRuleFinalizerName = "azuresqlfirewallrule.finalizers.azure.com" @@ -40,41 +40,60 @@ const azureSQLFirewallRuleFinalizerName = "azuresqlfirewallrule.finalizers.azure // AzureSqlFirewallRuleReconciler reconciles a AzureSqlFirewallRule object type AzureSqlFirewallRuleReconciler struct { client.Client - Log logr.Logger - Recorder record.EventRecorder - Scheme *runtime.Scheme + Telemetry telemetry.PrometheusTelemetry + Recorder record.EventRecorder + Scheme *runtime.Scheme } // +kubebuilder:rbac:groups=azure.microsoft.com,resources=azuresqlfirewallrules,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=azure.microsoft.com,resources=azuresqlfirewallrules/status,verbs=get;update;patch -func (r *AzureSqlFirewallRuleReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { +func (r *AzureSqlFirewallRuleReconciler) Reconcile(req ctrl.Request) (result ctrl.Result, err error) { ctx := context.Background() - log := r.Log.WithValues("azuresqlfirewallrule", req.NamespacedName) // your logic here var instance azurev1alpha1.AzureSqlFirewallRule + // log operator start + r.Telemetry.LogStart() + defer func() { - if err := r.Status().Update(ctx, &instance); err != nil { + + // log failure / success + if err != nil { + r.Telemetry.LogError( + "Failure occured during reconcilliation", + err) + r.Telemetry.LogFailure() + } else if result.Requeue { + r.Telemetry.LogFailure() + } else { + r.Telemetry.LogSuccess() + } + + if errUpdate := r.Status().Update(ctx, &instance); errUpdate != nil { r.Recorder.Event(&instance, v1.EventTypeWarning, "Failed", "Unable to update instance") } }() - if err := r.Get(ctx, req.NamespacedName, &instance); err != nil { - log.Info("Unable to retrieve azure-sql-firewall-rule resource", "err", err.Error()) + if err = r.Get(ctx, req.NamespacedName, &instance); err != nil { // we'll ignore not-found errors, since they can't be fixed by an immediate // requeue (we'll need to wait for a new notification), and we can get them // on deleted requests. 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); err != nil { - msg := fmt.Sprintf("Delete AzureSqlFirewallRule failed with %s", err.Error()) - log.Info(msg) - instance.Status.Message = msg + if err = r.deleteExternal(&instance, sdkClient); err != nil { + instance.Status.Message = fmt.Sprintf("Delete AzureSqlFirewallRule failed with %s", err.Error()) return ctrl.Result{}, err } @@ -88,42 +107,23 @@ func (r *AzureSqlFirewallRuleReconciler) Reconcile(req ctrl.Request) (ctrl.Resul if !helpers.HasFinalizer(&instance, azureSQLFirewallRuleFinalizerName) { if err := r.addFinalizer(&instance); err != nil { - msg := fmt.Sprintf("Adding AzureSqlFirewallRule finalizer failed with %s", err.Error()) - log.Info(msg) - instance.Status.Message = msg - + instance.Status.Message = fmt.Sprintf("Adding AzureSqlFirewallRule finalizer failed with %s", err.Error()) return ctrl.Result{}, err } } if !instance.IsSubmitted() { r.Recorder.Event(&instance, v1.EventTypeNormal, "Submitting", "starting resource reconciliation for AzureSqlFirewallRule") - if err := r.reconcileExternal(&instance); err != nil { - - catch := []string{ - errhelp.ParentNotFoundErrorCode, - errhelp.ResourceGroupNotFoundErrorCode, - errhelp.NotFoundErrorCode, - errhelp.AsyncOpIncompleteError, - } - if azerr, ok := err.(*errhelp.AzureError); ok { - if helpers.ContainsString(catch, azerr.Type) { - msg := fmt.Sprintf("Got ignorable error of type %v", azerr.Type) - log.Info(msg) - instance.Status.Message = msg - - return ctrl.Result{Requeue: true, RequeueAfter: 30 * time.Second}, nil - } - } - return ctrl.Result{}, fmt.Errorf("error reconciling azure sql firewall rule in azure: %v", err) + if err := r.reconcileExternal(&instance, sdkClient); 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 } return ctrl.Result{}, nil } r.Recorder.Event(&instance, v1.EventTypeNormal, "Provisioned", "azuresqlfirewallrule "+instance.ObjectMeta.Name+" provisioned ") - msg := fmt.Sprintf("AzureSqlFirewallrule%s successfully provisioned", instance.ObjectMeta.Name) - log.Info(msg) - instance.Status.Message = msg + instance.Status.Message = fmt.Sprintf("AzureSqlFirewallrule %s successfully provisioned", instance.ObjectMeta.Name) return ctrl.Result{}, nil } @@ -134,46 +134,40 @@ func (r *AzureSqlFirewallRuleReconciler) SetupWithManager(mgr ctrl.Manager) erro Complete(r) } -func (r *AzureSqlFirewallRuleReconciler) reconcileExternal(instance *azurev1alpha1.AzureSqlFirewallRule) error { +func (r *AzureSqlFirewallRuleReconciler) reconcileExternal(instance *azurev1alpha1.AzureSqlFirewallRule, sdk sql.GoSDKClient) error { ctx := context.Background() - groupName := instance.Spec.ResourceGroup - server := instance.Spec.Server ruleName := instance.ObjectMeta.Name startIP := instance.Spec.StartIPAddress endIP := instance.Spec.EndIPAddress - sdkClient := sql.GoSDKClient{ - Ctx: ctx, - ResourceGroupName: groupName, - ServerName: server, - } - - r.Log.Info("Calling createorupdate Azure SQL firewall rule") + r.Telemetry.LogTrace( + "Status", + "Calling CreateOrUpdate Azure SQL firewall rule") //get owner instance of AzureSqlServer r.Recorder.Event(instance, v1.EventTypeNormal, "UpdatingOwner", "Updating owner AzureSqlServer instance") var ownerInstance azurev1alpha1.AzureSqlServer - azureSqlServerNamespacedName := types.NamespacedName{Name: server, Namespace: instance.Namespace} - err := r.Get(ctx, azureSqlServerNamespacedName, &ownerInstance) + azureSQLServerNamespacedName := types.NamespacedName{Name: sdk.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 msg := "Unable to get owner instance of AzureSqlServer" - r.Recorder.Event(instance, v1.EventTypeWarning, "Failed", msg) instance.Status.Message = msg + r.Recorder.Event(instance, v1.EventTypeWarning, "Failed", msg) } else { msg := "Got owner instance of Sql Server and assigning controller reference now" - r.Recorder.Event(instance, v1.EventTypeNormal, "OwnerAssign", msg) instance.Status.Message = msg + r.Recorder.Event(instance, v1.EventTypeNormal, "OwnerAssign", msg) innerErr := controllerutil.SetControllerReference(&ownerInstance, instance, r.Scheme) if innerErr != nil { msg := "Unable to set controller reference to AzureSqlServer" - r.Recorder.Event(instance, v1.EventTypeWarning, "Failed", msg) instance.Status.Message = msg + r.Recorder.Event(instance, v1.EventTypeWarning, "Failed", msg) } successmsg := "Owner instance assigned successfully" - r.Recorder.Event(instance, v1.EventTypeNormal, "OwnerAssign", successmsg) instance.Status.Message = successmsg + r.Recorder.Event(instance, v1.EventTypeNormal, "OwnerAssign", successmsg) } // write information back to instance @@ -181,17 +175,19 @@ func (r *AzureSqlFirewallRuleReconciler) reconcileExternal(instance *azurev1alph r.Recorder.Event(instance, v1.EventTypeWarning, "Failed", "Unable to update instance") } - _, err = sdkClient.CreateOrUpdateSQLFirewallRule(ruleName, startIP, endIP) + _, err = sdk.CreateOrUpdateSQLFirewallRule(ruleName, startIP, endIP) if err != nil { if errhelp.IsAsynchronousOperationNotComplete(err) || errhelp.IsGroupNotFound(err) { - r.Log.Info("Async operation not complete or group not found") + r.Telemetry.LogInfo( + "IgnorableError", + "Async operation not complete or group not found") instance.Status.Provisioning = true } return errhelp.NewAzureError(err) } - _, err = sdkClient.GetSQLFirewallRule(ruleName) + _, err = sdk.GetSQLFirewallRule(ruleName) if err != nil { return errhelp.NewAzureError(err) } @@ -202,20 +198,12 @@ func (r *AzureSqlFirewallRuleReconciler) reconcileExternal(instance *azurev1alph return nil } -func (r *AzureSqlFirewallRuleReconciler) deleteExternal(instance *azurev1alpha1.AzureSqlFirewallRule) error { - ctx := context.Background() - groupName := instance.Spec.ResourceGroup - server := instance.Spec.Server +func (r *AzureSqlFirewallRuleReconciler) deleteExternal(instance *azurev1alpha1.AzureSqlFirewallRule, sdk sql.GoSDKClient) error { ruleName := instance.ObjectMeta.Name - // create the Go SDK client with relevant info - sdk := sql.GoSDKClient{ - Ctx: ctx, - ResourceGroupName: groupName, - ServerName: server, - } - - r.Log.Info(fmt.Sprintf("deleting external resource: group/%s/server/%s/firewallrule/%s"+groupName, server, ruleName)) + r.Telemetry.LogTrace( + "Status", + fmt.Sprintf("deleting external resource: group/%s/server/%s/firewallrule/%s", sdk.ResourceGroupName, sdk.ServerName, ruleName)) err := sdk.DeleteSQLFirewallRule(ruleName) if err != nil { if errhelp.IsStatusCode204(err) { @@ -223,14 +211,13 @@ func (r *AzureSqlFirewallRuleReconciler) deleteExternal(instance *azurev1alpha1. return nil } msg := "Couldn't delete resouce in azure" - r.Recorder.Event(instance, v1.EventTypeWarning, "Failed", msg) instance.Status.Message = msg - + r.Recorder.Event(instance, v1.EventTypeWarning, "Failed", msg) return err } msg := fmt.Sprintf("Deleted %s", ruleName) - r.Recorder.Event(instance, v1.EventTypeNormal, "Deleted", msg) instance.Status.Message = msg + r.Recorder.Event(instance, v1.EventTypeNormal, "Deleted", msg) return nil } @@ -239,9 +226,7 @@ func (r *AzureSqlFirewallRuleReconciler) addFinalizer(instance *azurev1alpha1.Az helpers.AddFinalizer(instance, azureSQLFirewallRuleFinalizerName) err := r.Update(context.Background(), instance) if err != nil { - msg := fmt.Sprintf("Failed to update finalizer: %v", err) - instance.Status.Message = msg - + instance.Status.Message = fmt.Sprintf("Failed to update finalizer: %v", err) return fmt.Errorf("failed to update finalizer: %v", err) } r.Recorder.Event(instance, v1.EventTypeNormal, "Updated", fmt.Sprintf("finalizer %s added", azureSQLFirewallRuleFinalizerName)) diff --git a/main.go b/main.go index ced53bce116..496f16efa3b 100644 --- a/main.go +++ b/main.go @@ -30,6 +30,7 @@ import ( resourcemanagerstorage "github.com/Azure/azure-service-operator/pkg/resourcemanager/storages" azurev1alpha1 "github.com/Azure/azure-service-operator/api/v1alpha1" + telemetry "github.com/Azure/azure-service-operator/pkg/telemetry" kscheme "k8s.io/client-go/kubernetes/scheme" _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" ctrl "sigs.k8s.io/controller-runtime" @@ -37,6 +38,8 @@ import ( // +kubebuilder:scaffold:imports ) +const NameAzureSQLFirewallRuleOperator = "AzureSQLFirewallRuleOperator" + var ( masterURL, kubeconfig, resources, clusterName string cloudName, tenantID, subscriptionID, clientID, clientSecret string @@ -190,8 +193,11 @@ func main() { os.Exit(1) } if err = (&controllers.AzureSqlFirewallRuleReconciler{ - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("SqlFirewallRule"), + Client: mgr.GetClient(), + Telemetry: telemetry.InitializePrometheusDefault( + ctrl.Log.WithName("controllers").WithName(NameAzureSQLFirewallRuleOperator), + NameAzureSQLFirewallRuleOperator, + ), Recorder: mgr.GetEventRecorderFor("SqlFirewallRule-controller"), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { diff --git a/pkg/telemetry/base_telemetry.go b/pkg/telemetry/base_telemetry.go new file mode 100644 index 00000000000..838998b564d --- /dev/null +++ b/pkg/telemetry/base_telemetry.go @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft and contributors. All rights reserved. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +package telemetry + +// BaseTelemetry contains the helper functions for basic telemetry +type BaseTelemetry interface { + LogTrace(typeTrace string, message string) + LogInfo(typeInfo string, message string) + LogWarning(typeWarning string, message string) + LogError(message string, err error) + LogStart() + LogSuccess() + LogFailure() +} diff --git a/pkg/telemetry/prometheus_client.go b/pkg/telemetry/prometheus_client.go new file mode 100644 index 00000000000..91ced255c5e --- /dev/null +++ b/pkg/telemetry/prometheus_client.go @@ -0,0 +1,141 @@ +// Copyright (c) Microsoft and contributors. All rights reserved. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +package telemetry + +import ( + "time" + + "github.com/Azure/go-autorest/autorest/to" + + log "github.com/go-logr/logr" + "github.com/prometheus/client_golang/prometheus" +) + +var ( + namespace *string + subsystem *string + statusCounter *prometheus.CounterVec + activeGuage *prometheus.GaugeVec + successCounter *prometheus.CounterVec + failureCounter *prometheus.CounterVec + executionTime *prometheus.HistogramVec +) + +// defaultNamespace is the default namespace for this project +const defaultNamespace = "Azure" + +// defaultSubsystem is the default subsystem for this project +const defaultSubsystem = "Operators" + +// exeuctionTimeStart base time == 0 +const exeuctionTimeStart = 0 + +// exeuctionTimeWidth is the width of a bucket in the histogram, here it is 10s +const exeuctionTimeWidth = 10 + +// executionTimeBuckets is the number of buckets, here it 10 minutes worth of 10s buckets +const executionTimeBuckets = 6 * 10 + +// PrometheusClient stores information for the Prometheus implementation of telemetry +type PrometheusClient struct { + Logger log.Logger + Component string + StartTime time.Time +} + +// InitializePrometheusDefault initializes a Prometheus client +func InitializePrometheusDefault(logger log.Logger, component string) (client *PrometheusClient) { + return InitializePrometheus(logger, component, defaultNamespace, defaultSubsystem) +} + +// InitializePrometheus initializes a Prometheus client +func InitializePrometheus(logger log.Logger, component string, namespaceLog string, subsystemLog string) (client *PrometheusClient) { + + // initialize globals if neccessary + if namespace == nil { + namespace = to.StringPtr(namespaceLog) + subsystem = to.StringPtr(subsystemLog) + initializeGlobalPrometheusMetricsEtc() + } + + // create the client + return &PrometheusClient{ + Logger: logger, + Component: component, + StartTime: time.Now(), + } +} + +// initializeAllPrometheusMetricsEtc inits all counts +func initializeGlobalPrometheusMetricsEtc() { + + statusCounter = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: *namespace, + Subsystem: *subsystem, + Name: "Info", + }, + []string{ + "component", + "level", + "type", + "message", + }, + ) + prometheus.MustRegister(statusCounter) + + executionTime = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: *namespace, + Subsystem: *subsystem, + Name: "ExecutionTime", + Buckets: prometheus.LinearBuckets( + exeuctionTimeStart, + exeuctionTimeWidth, + executionTimeBuckets), + }, + []string{ + "component", + }, + ) + prometheus.MustRegister(executionTime) + + activeGuage = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Namespace: *namespace, + Subsystem: *subsystem, + Name: "ActiveGuage", + }, + []string{ + "component", + }, + ) + prometheus.MustRegister(activeGuage) + + successCounter = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: *namespace, + Subsystem: *subsystem, + Name: "Success", + }, + []string{ + "component", + }, + ) + prometheus.MustRegister(successCounter) + + failureCounter = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: *namespace, + Subsystem: *subsystem, + Name: "Failure", + }, + []string{ + "component", + }, + ) + prometheus.MustRegister(failureCounter) +} diff --git a/pkg/telemetry/prometheus_client_implementation.go b/pkg/telemetry/prometheus_client_implementation.go new file mode 100644 index 00000000000..73ba6c6d2f9 --- /dev/null +++ b/pkg/telemetry/prometheus_client_implementation.go @@ -0,0 +1,99 @@ +// Copyright (c) Microsoft and contributors. All rights reserved. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +package telemetry + +import ( + "time" + + "github.com/prometheus/client_golang/prometheus" +) + +// LogTrace logs a trace message, it does not send telemetry to Prometheus +func (client *PrometheusClient) LogTrace(typeTrace string, message string) { + client.Logger.Info(message, "Trace Type", typeTrace, "Component", client.Component) +} + +// LogInfo logs an informational message +func (client *PrometheusClient) LogInfo(typeInfo string, message string) { + client.Logger.Info(message, "Info Type", typeInfo, "Component", client.Component) + statusCounter.WithLabelValues(client.Component, "Info", typeInfo, message).Inc() +} + +// LogWarning logs a warning +func (client *PrometheusClient) LogWarning(typeWarning string, message string) { + // logs this as info as there's no go-logr warning level + client.Logger.Info(message, "Warning Type", typeWarning, "Component", client.Component) + statusCounter.WithLabelValues(client.Component, "Warning", typeWarning, message).Inc() +} + +// LogError logs an error +func (client *PrometheusClient) LogError(message string, err error) { + errorString := err.Error() + + // logs the error as info (eventhough there's an error) as this follows the previous pattern + client.Logger.Info(message, "Component", client.Component, "Error", errorString) + statusCounter.WithLabelValues(client.Component, "Error", "Error", errorString).Inc() +} + +// LogStart logs the start of a component +func (client *PrometheusClient) LogStart() { + + // mark now as the start time + client.StartTime = time.Now() + + // log that operator is running + client.Logger.Info("Component has started", "Component", client.Component) + activeGuage.WithLabelValues(client.Component).Inc() +} + +// logCompletedOperation logs a component as completed +func logCompleted(client *PrometheusClient) { + + // log that operator has completed + activeGuage.WithLabelValues(client.Component).Dec() + + // record the time for the histogram + timeNow := time.Now() + durationInSecs := timeNow.Sub(client.StartTime).Seconds() + client.Logger.Info("Component has completed", "Component", client.Component, "Duration", durationInSecs) + executionTime.WithLabelValues(client.Component).Observe(durationInSecs) +} + +// LogSuccess logs the successful completion of a component +func (client *PrometheusClient) LogSuccess() { + + // log the completion + logCompleted(client) + + // log success + client.Logger.Info("Component completed successfully", "Component", client.Component) + successCounter.WithLabelValues(client.Component).Inc() +} + +// LogFailure logs the successful completion of a component +func (client *PrometheusClient) LogFailure() { + + // log the completion + logCompleted(client) + + // log success + client.Logger.Info("Component completed unsuccessfully", "Component", client.Component) + failureCounter.WithLabelValues(client.Component).Inc() +} + +// CreateHistogram creates a histogram, start = what value the historgram starts at, width = how wide are the buckets, +// numberOfBuckets = the number of buckets in the histogram +func (client *PrometheusClient) CreateHistogram(name string, start float64, width float64, numberOfBuckets int) (histogram prometheus.Histogram) { + histogram = prometheus.NewHistogram( + prometheus.HistogramOpts{ + Namespace: *namespace, + Subsystem: *subsystem, + Name: name, + Buckets: prometheus.LinearBuckets(start, width, numberOfBuckets), + }) + + return histogram +} diff --git a/pkg/telemetry/prometheus_telemetry.go b/pkg/telemetry/prometheus_telemetry.go new file mode 100644 index 00000000000..7e2e7739ff9 --- /dev/null +++ b/pkg/telemetry/prometheus_telemetry.go @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft and contributors. All rights reserved. +// +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +package telemetry + +import ( + "github.com/prometheus/client_golang/prometheus" +) + +// PrometheusTelemetry contains the helper functions for Prometheus-based telemetry +type PrometheusTelemetry interface { + BaseTelemetry + CreateHistogram(name string, start float64, width float64, numberOfBuckets int) (histogram prometheus.Histogram) +}