diff --git a/api/v1alpha1/rediscacheaction_types.go b/api/v1alpha1/rediscacheaction_types.go index 5522713e7ab..b738c9f8ccf 100644 --- a/api/v1alpha1/rediscacheaction_types.go +++ b/api/v1alpha1/rediscacheaction_types.go @@ -7,13 +7,16 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// +kubebuilder:validation:Enum=rollallkeys;rollprimarykey;rollsecondarykey +// +kubebuilder:validation:Enum=rollallkeys;rollprimarykey;rollsecondarykey;rebootallnodes;rebootprimarynode;rebootsecondarynode type RedisCacheActionName string const ( - RedisCacheActionNameRollAllKeys RedisCacheActionName = "rollallkeys" - RedisCacheActionNameRollPrimaryKey RedisCacheActionName = "rollprimarykey" - RedisCacheActionNameRollSecondaryKey RedisCacheActionName = "rollsecondarykey" + RedisCacheActionNameRollAllKeys RedisCacheActionName = "rollallkeys" + RedisCacheActionNameRollPrimaryKey RedisCacheActionName = "rollprimarykey" + RedisCacheActionNameRollSecondaryKey RedisCacheActionName = "rollsecondarykey" + RedisCacheActionNameRebootAllNodes RedisCacheActionName = "rebootallnodes" + RedisCacheActionNameRebootPrimaryNode RedisCacheActionName = "rebootprimarynode" + RedisCacheActionNameRebootSecondaryNode RedisCacheActionName = "rebootsecondarynode" ) // RedisCacheActionSpec defines the desired state of RedisCacheAction @@ -23,6 +26,7 @@ type RedisCacheActionSpec struct { ActionName RedisCacheActionName `json:"actionName"` SecretName string `json:"secretName,omitempty"` KeyVaultToStoreSecrets string `json:"keyVaultToStoreSecrets,omitempty"` + ShardID *int32 `json:"shardID,omitempty"` } // +kubebuilder:object:root=true diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 5a153bd6651..bee5efb4fd4 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -3075,7 +3075,7 @@ func (in *RedisCacheAction) DeepCopyInto(out *RedisCacheAction) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) in.Status.DeepCopyInto(&out.Status) } @@ -3132,6 +3132,11 @@ func (in *RedisCacheActionList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RedisCacheActionSpec) DeepCopyInto(out *RedisCacheActionSpec) { *out = *in + if in.ShardID != nil { + in, out := &in.ShardID, &out.ShardID + *out = new(int32) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RedisCacheActionSpec. diff --git a/config/samples/azure_v1alpha1_rediscacheaction.yaml b/config/samples/azure_v1alpha1_rediscacheaction.yaml index 5042f016712..ef44e53f26e 100644 --- a/config/samples/azure_v1alpha1_rediscacheaction.yaml +++ b/config/samples/azure_v1alpha1_rediscacheaction.yaml @@ -5,5 +5,5 @@ metadata: spec: resourceGroup: resourcegroup-azure-operators cacheName: rediscache-sample-1 - # possible values are 'rollallkeys', 'rollprimarykey', 'rollsecondarykey' + # possible values are 'rollallkeys', 'rollprimarykey', 'rollsecondarykey', 'rebootallnodes', 'rebootprimarynode', 'rebootsecondarynode' actionName: rollallkeys \ No newline at end of file diff --git a/docs/services/rediscache/rediscache.md b/docs/services/rediscache/rediscache.md index f51a5898472..8ee5426a7b3 100644 --- a/docs/services/rediscache/rediscache.md +++ b/docs/services/rediscache/rediscache.md @@ -61,6 +61,25 @@ The `redisCache` indicates the RedisCache on which you want to configure the new _Note:_ When the `startIP` and `endIP` are 0.0.0.0, it denotes a special case that adds a firewall rule to allow all Azure services to access the RedisCache. +## RedisCache action + +The RedisCache action allows you to regenerate keys and reboot the RedisCache cluster. + +Here is a [sample YAML](/config/samples/azure_v1alpha1_rediscacheaction.yaml) for RedisCache action. + +The `cacheName` indicates the RedisCache on which you want to perform the action and `resourceGroup` is the resource group of the RedisCache. The `actionName` corresponds to one of the supported actions listed below. + +### RedisCache action - Roll Keys +The `secretName` field is used to update the RedisCache secret. The `keyVaultToStoreSecrets` field is used to specify a KeyVault instance where the RedisCache secret exists. The following "roll" actions are supported: +- `rollprimarykey` - regenerates primary key and updates the secret +- `rollsecondarykey` - regenerates secondary key and updates the secret +- `rollallkeys` - regenerates primary and secondary keys and updates the secret + +### RedisCache action - Reboot +The `shardID` field is used to specify a specific RedisCache shard to reboot. The following "reboot" actions are supported: +- `rebootprimarynode` - reboots all primary nodes in the RedisCache cluster +- `rebootsecondarynode` - reboots all secondary nodes in the RedisCache cluster +- `rebootallnodes` - reboots all nodes (primary & secondary) in the RedisCache cluster ## Deploy, view and delete resources diff --git a/pkg/resourcemanager/rediscaches/actions/rediscacheaction_manager.go b/pkg/resourcemanager/rediscaches/actions/rediscacheaction_manager.go index ff47d73fc08..61737241c6b 100644 --- a/pkg/resourcemanager/rediscaches/actions/rediscacheaction_manager.go +++ b/pkg/resourcemanager/rediscaches/actions/rediscacheaction_manager.go @@ -15,6 +15,8 @@ type RedisCacheActionManager interface { RegenerateSecondaryAccessKey(ctx context.Context, resourceGroup string, cacheName string) (string, error) + ForceReboot(ctx context.Context, resourceGroup string, cacheName string) error + // also embed async client methods resourcemanager.ARMClient } diff --git a/pkg/resourcemanager/rediscaches/actions/rediscacheaction_reconcile.go b/pkg/resourcemanager/rediscaches/actions/rediscacheaction_reconcile.go index f4898558e98..0a9a554ca23 100644 --- a/pkg/resourcemanager/rediscaches/actions/rediscacheaction_reconcile.go +++ b/pkg/resourcemanager/rediscaches/actions/rediscacheaction_reconcile.go @@ -34,41 +34,52 @@ func (m *AzureRedisCacheActionManager) Ensure(ctx context.Context, obj runtime.O return true, nil } - rollAllKeys := instance.Spec.ActionName == v1alpha1.RedisCacheActionNameRollAllKeys + if isRedisCacheRollAction(instance.Spec.ActionName) { + rollAllKeys := instance.Spec.ActionName == v1alpha1.RedisCacheActionNameRollAllKeys + + if rollAllKeys || instance.Spec.ActionName == v1alpha1.RedisCacheActionNameRollPrimaryKey { + if err = m.RegeneratePrimaryAccessKey(ctx, instance.Spec.ResourceGroup, instance.Spec.CacheName); err != nil { + instance.Status.Message = err.Error() + return false, err + } + } + + if rollAllKeys || instance.Spec.ActionName == v1alpha1.RedisCacheActionNameRollSecondaryKey { + if err = m.RegenerateSecondaryAccessKey(ctx, instance.Spec.ResourceGroup, instance.Spec.CacheName); err != nil { + instance.Status.Message = err.Error() + return false, err + } + } - if rollAllKeys || instance.Spec.ActionName == v1alpha1.RedisCacheActionNameRollPrimaryKey { - if err = m.RegeneratePrimaryAccessKey(ctx, instance.Spec.ResourceGroup, instance.Spec.CacheName); err != nil { + cacheInstance := &v1alpha1.RedisCache{ + ObjectMeta: metav1.ObjectMeta{ + Name: instance.Spec.CacheName, + Namespace: instance.Namespace, + }, + Spec: v1alpha1.RedisCacheSpec{ + SecretName: instance.Spec.SecretName, + ResourceGroupName: instance.Spec.ResourceGroup, + }, + } + if err = m.ListKeysAndCreateSecrets(ctx, cacheInstance); err != nil { + instance.Status.Provisioning = true + instance.Status.Provisioned = false + instance.Status.FailedProvisioning = true instance.Status.Message = err.Error() return false, err } } - if rollAllKeys || instance.Spec.ActionName == v1alpha1.RedisCacheActionNameRollSecondaryKey { - if err = m.RegenerateSecondaryAccessKey(ctx, instance.Spec.ResourceGroup, instance.Spec.CacheName); err != nil { + if isRedisCacheRebootAction(instance.Spec.ActionName) { + err := m.ForceReboot(ctx, instance.Spec.ResourceGroup, instance.Spec.CacheName, instance.Spec.ActionName, instance.Spec.ShardID) + if err != nil { instance.Status.Message = err.Error() return false, err } } - // regenerate the secret - cacheInstance := &v1alpha1.RedisCache{ - ObjectMeta: metav1.ObjectMeta{ - Name: instance.Spec.CacheName, - Namespace: instance.Namespace, - }, - Spec: v1alpha1.RedisCacheSpec{ - SecretName: instance.Spec.SecretName, - ResourceGroupName: instance.Spec.ResourceGroup, - }, - } - if err = m.ListKeysAndCreateSecrets(ctx, cacheInstance); err != nil { - instance.Status.Provisioned = false - instance.Status.FailedProvisioning = true - instance.Status.Message = err.Error() - return false, err - } - // successful return + instance.Status.Provisioning = true instance.Status.Provisioned = true instance.Status.FailedProvisioning = false instance.Status.Message = resourcemanager.SuccessMsg @@ -120,3 +131,15 @@ func (m *AzureRedisCacheActionManager) convert(obj runtime.Object) (*v1alpha1.Re } return local, nil } + +func isRedisCacheRollAction(actionName v1alpha1.RedisCacheActionName) bool { + return actionName == v1alpha1.RedisCacheActionNameRollAllKeys || + actionName == v1alpha1.RedisCacheActionNameRollPrimaryKey || + actionName == v1alpha1.RedisCacheActionNameRollSecondaryKey +} + +func isRedisCacheRebootAction(actionName v1alpha1.RedisCacheActionName) bool { + return actionName == v1alpha1.RedisCacheActionNameRebootAllNodes || + actionName == v1alpha1.RedisCacheActionNameRebootPrimaryNode || + actionName == v1alpha1.RedisCacheActionNameRebootSecondaryNode +} diff --git a/pkg/resourcemanager/rediscaches/actions/rediscacheactions.go b/pkg/resourcemanager/rediscaches/actions/rediscacheactions.go index bfc64a7595a..85030a769e9 100644 --- a/pkg/resourcemanager/rediscaches/actions/rediscacheactions.go +++ b/pkg/resourcemanager/rediscaches/actions/rediscacheactions.go @@ -5,7 +5,9 @@ package actions import ( "context" + "fmt" + "github.com/Azure/azure-service-operator/api/v1alpha1" "github.com/Azure/azure-service-operator/pkg/resourcemanager/rediscaches" "github.com/Azure/azure-service-operator/pkg/secrets" @@ -62,3 +64,31 @@ func (r *AzureRedisCacheActionManager) RegenerateSecondaryAccessKey(ctx context. return nil } + +func (r *AzureRedisCacheActionManager) ForceReboot(ctx context.Context, resourceGroup string, cacheName string, actionName v1alpha1.RedisCacheActionName, shardID *int32) error { + client, err := r.GetRedisCacheClient() + if err != nil { + return err + } + + var rebootType model.RebootType + switch actionName { + case v1alpha1.RedisCacheActionNameRebootAllNodes: + rebootType = model.AllNodes + case v1alpha1.RedisCacheActionNameRebootPrimaryNode: + rebootType = model.PrimaryNode + case v1alpha1.RedisCacheActionNameRebootSecondaryNode: + rebootType = model.SecondaryNode + default: + return fmt.Errorf("%v is not a valid reboot action", actionName) + } + + _, err = client.ForceReboot(ctx, resourceGroup, cacheName, model.RebootParameters{ + RebootType: rebootType, + ShardID: shardID, + }) + if err != nil { + return err + } + return err +}