Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Impl OverprovisionControl using OpenShift's ClusterResourceQuota #1282

Merged
merged 7 commits into from
Aug 27, 2021

Conversation

synarete
Copy link
Contributor

@synarete synarete commented Jul 25, 2021

The following patch-set implements storage-quota (a.k.a, OverprovisionControl) using OpenShift's ClusterResourceQuota mechanism. It enables new section in StorageCluster CRD called OverprovisionControl which allows end-users to define ClusterResourceQuota over OCS storage cluster (either as fixed value or as percentage of raw capacity).

Refs KNIP-1617

@openshift-ci openshift-ci bot requested review from jarrpa and obnoxxx July 25, 2021 14:44
@openshift-ci openshift-ci bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Aug 5, 2021
@openshift-ci openshift-ci bot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Aug 5, 2021
@synarete synarete force-pushed the storagequota branch 2 times, most recently from e1d5ead to 5c4768b Compare August 17, 2021 07:19
@openshift-ci openshift-ci bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Aug 18, 2021
@openshift-ci openshift-ci bot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Aug 19, 2021
@synarete
Copy link
Contributor Author

synarete commented Aug 22, 2021 via email

@synarete
Copy link
Contributor Author

synarete commented Aug 23, 2021 via email

Copy link
Contributor

@nbalacha nbalacha left a comment

Choose a reason for hiding this comment

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

First pass.

PVs overprovisioning relative to the effective usable storage capacity.
items:
description: OverprovisionControlSpec defines the allowed overprovisioning
PC consumtion from the underlying cluster. This may be either
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: typo: consumption

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ACK.

PC consumtion from the underlying cluster. This may be either
as an absolute value or as a percentage from the overall effective
capacity. When defined in terms of percentages, zero value indicates
no over-provisioning.
Copy link
Contributor

Choose a reason for hiding this comment

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

Does no over-provisioning mean that the limits are not checked or it is limited to 100% of the available capacity?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Following a review by @jarrpa, this need to fixed: either have explicit over-provision or none at all.

}
requestName := resourceRequestName(opc.StorageClassName)
storageQuota := &quotav1.ClusterResourceQuota{
TypeMeta: metav1.TypeMeta{APIVersion: quotav1.SchemeGroupVersion.String(), Kind: clusterResourceQuotaKind},
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think the TypeMeta is required here - that is automatically taken care of when you create the quotav1.ClusterResourceQuota.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You are correct! nice.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You are correct. Better to remove it

},
},
}
currentQuota, err := r.QuotaV1.ClusterResourceQuotas().Get(context.TODO(), storageQuota.Name, metav1.GetOptions{})
Copy link
Contributor

Choose a reason for hiding this comment

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

Wouldn't r.Client.Get() work here?

Copy link
Contributor Author

@synarete synarete Aug 25, 2021

Choose a reason for hiding this comment

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

It will not work. OpenShift's ClusterResourceQuota has its own client interface. I do not know why they did it this way, but I tried to follow their conventions. See for example:

https://github.com/openshift/oc/blob/master/pkg/helpers/describe/describer.go#L1749

https://github.com/openshift/oc/blob/master/pkg/cli/create/clusterquota.go#L140

Copy link
Member

Choose a reason for hiding this comment

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

This is infuriating and they should feel bad. 😤

Copy link
Contributor

Choose a reason for hiding this comment

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

It should work. Controller runtime client works with any runtime.Object provided that correct scheme is added.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@umangachapagain Could you please elaborate on how to add the correct scheme in this case?

Copy link
Contributor

Choose a reason for hiding this comment

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

Right, a search is basically a get call. For create you will anyway need to specify the namespace for either client, correct?

Copy link
Contributor

Choose a reason for hiding this comment

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

On second thoughts - In which namespace is the ocs operator creating these ClusterResourceQuotas?
Should it only create the ClusterResourceQuotas for the entries in the StorageCluster and create them in the openshift-storage namespace?
The users can create other ClusterResourceQuotas elsewhere but OCS should not care about those.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a very good question, which touches the core issue here: unlike all other resources in OCS, ClusterResourceQuota is not associated with any specific namespace. Think of it as "global" resource, which limits resource consumption from other (labled) namespaces.

Copy link
Contributor

Choose a reason for hiding this comment

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

global resources exist even without OpenShift and ClusterResourceQuota. controller-runtime client can handle it. We do that for StorageClass.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK, it looks like it is doable after all (i.e., avoid ClusterResourceQuotas specific interface). Many thanks @nbalacha and @umangachapagain for your inputs

controllers/storagecluster/storagequota.go Outdated Show resolved Hide resolved
storageQuota.Spec.DeepCopyInto(&currentQuota.Spec)
_, err = r.QuotaV1.ClusterResourceQuotas().Update(context.TODO(), currentQuota, metav1.UpdateOptions{})
if err != nil {
r.Log.Error(err, "Update ClusterResourceQuota failed")
Copy link
Contributor

Choose a reason for hiding this comment

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

Please include the name of the ClusterResourceQuota here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ACK

}

func storageQuotaName(storageClassName string, idx int) string {
subKey := storageClassName
Copy link
Contributor

Choose a reason for hiding this comment

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

If idx is the index in the array this could be problematic if an entry in the middle is deleted. The names of the later entries would change post that.

controllers/storagecluster/storagequota.go Show resolved Hide resolved
const storageClassSuffix string = ".storageclass.storage.k8s.io/"

// V1ResourceByStorageClass returns a quota resource name by storage class.
func V1ResourceByStorageClass(storageClassName string, resourceName corev1.ResourceName) corev1.ResourceName {
Copy link
Contributor

Choose a reason for hiding this comment

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

How does this work if there are multiple quotas for the same storageClass but different projects.

Personally, I think importing the pkg is better than copying it.

Copy link
Member

Choose a reason for hiding this comment

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

Same. Bringing in additional dependencies is not a concern for me. Having copy-pasted code from another package is dangerous if the source ever changes, and that's a higher priority concern.

if hardLimit == nil {
continue
}
requestName := resourceRequestName(opc.StorageClassName)
Copy link
Contributor

Choose a reason for hiding this comment

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

Should there be a check to see if the storageclass is actually using OCS ceph provisioners?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Following a review by @jarrpa this will be fixed

@umangachapagain umangachapagain added this to the OCS 4.9 milestone Aug 25, 2021
api/v1/storagecluster_types.go Show resolved Hide resolved
@@ -391,3 +396,13 @@ type ArbiterSpec struct {
func init() {
SchemeBuilder.Register(&StorageCluster{}, &StorageClusterList{})
}

// OverprovisionControlSpec defines the allowed overprovisioning PC consumtion from the underlying cluster.
Copy link
Contributor

Choose a reason for hiding this comment

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

What is PC?

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// OverprovisionControlSpec defines the allowed overprovisioning PC consumtion from the underlying cluster.
// OverprovisionControlSpec defines the allowed overprovisioning PC consumption from the underlying cluster.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ACK

@@ -391,3 +396,13 @@ type ArbiterSpec struct {
func init() {
SchemeBuilder.Register(&StorageCluster{}, &StorageClusterList{})
}

// OverprovisionControlSpec defines the allowed overprovisioning PC consumtion from the underlying cluster.
// This may be either as an absolute value or as a percentage from the overall effective capacity.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// This may be either as an absolute value or as a percentage from the overall effective capacity.
// This may be an absolute value or a percentage of the overall effective capacity.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ACK


// OverprovisionControlSpec defines the allowed overprovisioning PC consumtion from the underlying cluster.
// This may be either as an absolute value or as a percentage from the overall effective capacity.
// When defined in terms of percentages, zero value indicates no over-provisioning.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// When defined in terms of percentages, zero value indicates no over-provisioning.
// When defined in percentage, zero value indicates no over-provisioning.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ACK

requestName := resourceRequestName(opc.StorageClassName)
storageQuota := &quotav1.ClusterResourceQuota{
TypeMeta: metav1.TypeMeta{APIVersion: quotav1.SchemeGroupVersion.String(), Kind: clusterResourceQuotaKind},
ObjectMeta: metav1.ObjectMeta{Name: storageQuotaName(opc.StorageClassName, idx)},
Copy link
Contributor

Choose a reason for hiding this comment

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

we have a separate file for all name generating functions. Move storageQuotaName there and follow similar naming.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ACK

// https://github.com/kubernetes/kubernetes/blob/v1.21.2/pkg/quota/v1/evaluator/core/persistent_volume_claims.go#L63
//
// Avoids importing "k8s.io/kubernetes/pkg/quota/v1/evaluator/core" and its chain of dependencies
// TODO: Ask Jose A.Rivera if he prefers import
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// TODO: Ask Jose A.Rivera if he prefers import

@jarrpa What do you prefer?

// storageClassSuffix is the suffix to the qualified portion of storage class resource name.
// For example, if you want to quota storage by storage class, you would have a declaration
// that follows <storage-class>.storageclass.storage.k8s.io/<resource>.
const storageClassSuffix string = ".storageclass.storage.k8s.io/"
Copy link
Contributor

Choose a reason for hiding this comment

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

Should probably move this and all name generation functions below to generate.go?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ACK

Comment on lines 141 to 146
func createFakeStorageClusterWithQuotaReconciler(t *testing.T, obj ...runtime.Object) *StorageClusterReconciler {
scheme := createFakeSchemeWithQuota(t)
client := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(obj...).Build()

return &StorageClusterReconciler{
Client: client,
QuotaV1: fakequota.NewSimpleClientset().QuotaV1(),
Scheme: scheme,
serverVersion: &k8sVersion.Info{},
Log: logf.Log.WithName("storagequota_test"),
platform: &Platform{platform: configv1.NonePlatformType},
}
}

func createFakeSchemeWithQuota(t *testing.T) *runtime.Scheme {
scheme := createFakeScheme(t)
err := quotav1.AddToScheme(scheme)
if err != nil {
assert.Fail(t, "failed to add quotav1 scheme")
}
return scheme
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Try reusing already available utilities rather than creating more.

Comment on lines 18 to 21
TypeMeta: metav1.TypeMeta{
APIVersion: "quota.openshift.io",
Kind: "ClusterResourceQuota",
},
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
TypeMeta: metav1.TypeMeta{
APIVersion: "quota.openshift.io",
Kind: "ClusterResourceQuota",
},

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ACK

Comment on lines 43 to 46
TypeMeta: metav1.TypeMeta{
APIVersion: "quota.openshift.io",
Kind: "ClusterResourceQuota",
},
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
TypeMeta: metav1.TypeMeta{
APIVersion: "quota.openshift.io",
Kind: "ClusterResourceQuota",
},

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ACK

},
},
}
currentQuota, err := r.QuotaV1.ClusterResourceQuotas().Get(context.TODO(), storageQuota.Name, metav1.GetOptions{})
Copy link
Member

Choose a reason for hiding this comment

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

This is infuriating and they should feel bad. 😤

controllers/storagecluster/storagequota.go Outdated Show resolved Hide resolved
const storageClassSuffix string = ".storageclass.storage.k8s.io/"

// V1ResourceByStorageClass returns a quota resource name by storage class.
func V1ResourceByStorageClass(storageClassName string, resourceName corev1.ResourceName) corev1.ResourceName {
Copy link
Member

Choose a reason for hiding this comment

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

Same. Bringing in additional dependencies is not a concern for me. Having copy-pasted code from another package is dangerous if the source ever changes, and that's a higher priority concern.

Comment on lines 141 to 146
func createFakeStorageClusterWithQuotaReconciler(t *testing.T, obj ...runtime.Object) *StorageClusterReconciler {
scheme := createFakeSchemeWithQuota(t)
client := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(obj...).Build()

return &StorageClusterReconciler{
Client: client,
QuotaV1: fakequota.NewSimpleClientset().QuotaV1(),
Scheme: scheme,
serverVersion: &k8sVersion.Info{},
Log: logf.Log.WithName("storagequota_test"),
platform: &Platform{platform: configv1.NonePlatformType},
}
}

func createFakeSchemeWithQuota(t *testing.T) *runtime.Scheme {
scheme := createFakeScheme(t)
err := quotav1.AddToScheme(scheme)
if err != nil {
assert.Fail(t, "failed to add quotav1 scheme")
}
return scheme
}
Copy link
Member

Choose a reason for hiding this comment

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

Don't do this, we want to avoid this kind of duplication if possible. See the following commit for an example: 837ae4b

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ACK

Comment on lines 114 to 127
var obj ocsStorageQuota
err := obj.ensureCreated(r, sc)
assert.NoError(t, err)
assert.Equal(t, len(listStorageQuotas(t, r)), 2)

sc.Spec.OverprovisionControl[1].Percentage = 60
err = obj.ensureCreated(r, sc)
assert.NoError(t, err)
assert.Equal(t, len(listStorageQuotas(t, r)), 2)

sc.Spec.OverprovisionControl[0].Capacity = &testQuantity1T
err = obj.ensureCreated(r, sc)
assert.NoError(t, err)
assert.Equal(t, len(listStorageQuotas(t, r)), 2)
Copy link
Member

Choose a reason for hiding this comment

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

For things like this, try to follow the test-cases format we're using elsewhere: https://github.com/openshift/ocs-operator/blob/master/controllers/storagecluster/storagecluster_controller_test.go#L248-L304 This helps reduce duplication of actual test code so extending them becomes much easier.

In this case, you'll want to supply the different OPCs as test variables. Also, be sure to call Created and Deleted for every test iteration so we have clean test environments.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ACK

Comment on lines 21 to 23
var testQuantity1T = resource.MustParse("1Ti")
var testQuantity2T = resource.MustParse("2Ti")
var testStorageClusterWithOverprovision = &api.StorageCluster{
Copy link
Member

Choose a reason for hiding this comment

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

s/test/mock, just out of convention.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ACK


var testQuantity1T = resource.MustParse("1Ti")
var testQuantity2T = resource.MustParse("2Ti")
var testStorageClusterWithOverprovision = &api.StorageCluster{
Copy link
Member

Choose a reason for hiding this comment

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

Rather than defining a new StorageCluster, define a valid StorageDeviceSets list with appropriate OPCs. Since you already have a function for the DeepCopy, you could do a DeepCopy of the existing mock StorageCluster and then DeepCopy the SDSs/OPCs into that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ACK

var obj ocsStorageQuota
err := obj.ensureCreated(r, sc)
assert.NoError(t, err)
assert.Equal(t, len(listStorageQuotas(t, r)), 2)
Copy link
Member

Choose a reason for hiding this comment

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

Try not to hardcode any values like this. You should be able to determine the expected count from the spec you're providing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ACK

Comment on lines 96 to 122
func TestStorageQuotaEnsureCreatedDeleted(t *testing.T) {
r := createFakeStorageClusterWithQuotaReconciler(t)
sc := createStorageClusterWithOverprovision()

var obj ocsStorageQuota
err := obj.ensureCreated(r, sc)
assert.NoError(t, err)
assert.Equal(t, len(listStorageQuotas(t, r)), 2)

err = obj.ensureDeleted(r, sc)
assert.NoError(t, err)
assert.Equal(t, len(listStorageQuotas(t, r)), 0)
}
Copy link
Member

Choose a reason for hiding this comment

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

This is a bit of a tricky situation for all parts of ocs-operator. But generally, you should not need a separate test function for testing create and delete. The test cases for the overall feature already contain the ensureCreated() and ensureDeleted() calls, so as long as you have proper error reporting we'll see what went wrong.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ACK

@jarrpa
Copy link
Member

jarrpa commented Aug 25, 2021

A few other top-level change requests:

  • Reorganize the commits roughly in the following fashion:
    1. count+replica split
    2. OPC implementation+API
    3. OPC unit tests
    4. OPC code generation
    5. OPC vendor changes
    6. OPC metics implementation
    7. OPC metrics vendor changes
  • Try and format the error messages with the following rules (general k8s conventions):
    • lowercase letter at beginning of string
    • General format of: error with resource %s: %v where %v is the err

@umangachapagain
Copy link
Contributor

A few other top-level change requests:
* Try and format the error messages with the following rules (general k8s conventions):

  * lowercase letter at beginning of string
  * General format of: `error with resource %s: %v` where `%v` is the `err`

This is for returning errors.
If you are logging errors, use sentence case. And provide more contextual information.

@openshift-ci openshift-ci bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Aug 26, 2021
@@ -72,6 +74,7 @@ type ImageMap struct {
//nolint
type StorageClusterReconciler struct {
client.Client
QuotaV1 quotav1.QuotaV1Interface
Copy link
Contributor

Choose a reason for hiding this comment

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

Please rename to QuotaClient. QuotaV1 is usually the format for the API itself

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ACK

@@ -6,6 +6,8 @@ import (

"github.com/go-logr/logr"
nbv1 "github.com/noobaa/noobaa-operator/v2/pkg/apis/noobaa/v1alpha1"

quotav1 "github.com/openshift/client-go/quota/clientset/versioned/typed/quota/v1"
Copy link
Contributor

Choose a reason for hiding this comment

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

Please import as quotaclient or similar

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ACK

@jarrpa jarrpa added mvp Required for the next minimum viable product. priority/0-critical Highest priority. Must be actively worked on as someone's top priority right now. labels Aug 26, 2021
@openshift-ci openshift-ci bot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Aug 26, 2021
@jarrpa jarrpa self-assigned this Aug 26, 2021
When calculating the usable capacity of a cluster, other modules will
need to take into account the same logic of count and replica. Avoid
cope duplication by exporting this logic via dedicated function.

Signed-off-by: Shachar Sharon <ssharon@redhat.com>
The core logic of creation ClusterResourceQuota for OCS storage-cluster.
For each OverprovisionControl entry in StorageCluster, define a
corresponding ClusterResourceQuota object using OpenShift's quota-client
mechanism.

The actual value is one of two cases:
 - Explicit hard-limit
 - Derived as percentage from the cluster's raw capacity

Enable ocsStorageQuota as part of resource-managers reconcile loop.
Enable apiGroup 'quota.openshift.io' with resources
clusterresourcequotas.

Signed-off-by: Shachar Sharon <ssharon@redhat.com>
@synarete
Copy link
Contributor Author

synarete commented Aug 27, 2021 via email

Using ClusterResourceQuota fake client to test overprovisionControl
functionality (Create/Update/Delete).

Signed-off-by: Shachar Sharon <ssharon@redhat.com>
Signed-off-by: Shachar Sharon <ssharon@redhat.com>
Signed-off-by: Shachar Sharon <ssharon@redhat.com>
Export storage clusterresourcequota HARD/USED metrics via Prometheus
exporter. For-each ClusterResourceQuota storage entry, export a pair on
hard-limit/used-count metrics.

Signed-off-by: Shachar Sharon <ssharon@redhat.com>
Signed-off-by: Shachar Sharon <ssharon@redhat.com>
@synarete
Copy link
Contributor Author

synarete commented Aug 27, 2021 via email

Copy link
Member

@jarrpa jarrpa left a comment

Choose a reason for hiding this comment

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

/lgtm

@jarrpa
Copy link
Member

jarrpa commented Aug 27, 2021

/retest-required

@openshift-ci openshift-ci bot added the lgtm Indicates that a PR is ready to be merged. label Aug 27, 2021
@openshift-ci
Copy link
Contributor

openshift-ci bot commented Aug 27, 2021

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: jarrpa

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Aug 27, 2021
@openshift-merge-robot openshift-merge-robot merged commit 39a6f7e into red-hat-storage:master Aug 27, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. lgtm Indicates that a PR is ready to be merged. mvp Required for the next minimum viable product. priority/0-critical Highest priority. Must be actively worked on as someone's top priority right now.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants