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

feat: improve refresh duration calculation #364

Merged
merged 2 commits into from
Nov 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 18 additions & 8 deletions internal/cloudsql/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
// 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 cloudsql

import (
Expand All @@ -28,11 +27,6 @@ import (
sqladmin "google.golang.org/api/sqladmin/v1beta4"
)

const (
// refreshBuffer is the amount of time before a result expires to start a new refresh attempt.
refreshBuffer = 5 * time.Minute
)

var (
// Instance connection name is the format <PROJECT>:<REGION>:<INSTANCE>
// Additionally, we have to support legacy "domain-scoped" projects (e.g. "google.com:PROJECT")
Expand Down Expand Up @@ -274,6 +268,22 @@ func (i *Instance) result(ctx context.Context) (*refreshOperation, error) {
return res, nil
}

// refreshDuration returns the duration to wait before starting the next
// refresh. Usually that duration will be half of the time until certificate
// expiration.
func refreshDuration(now, certExpiry time.Time) time.Duration {
d := certExpiry.Sub(now)
if d < time.Hour {
// Something is wrong with the certificate, refresh now.
if d < 5*time.Minute {
return 0
}
// Otherwise, wait five minutes before starting the refresh cycle.
return 5 * time.Minute
}
return d / 2
}

// scheduleRefresh schedules a refresh operation to be triggered after a given duration. The returned refreshOperation
// can be used to either Cancel or Wait for the operations result.
func (i *Instance) scheduleRefresh(d time.Duration) *refreshOperation {
Expand Down Expand Up @@ -306,8 +316,8 @@ func (i *Instance) scheduleRefresh(d time.Duration) *refreshOperation {
return
default:
}
nextRefresh := i.cur.expiry.Add(-refreshBuffer)
i.next = i.scheduleRefresh(time.Until(nextRefresh))
t := refreshDuration(time.Now(), i.cur.expiry)
i.next = i.scheduleRefresh(t)
})
return res
}
Expand Down
44 changes: 44 additions & 0 deletions internal/cloudsql/instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,3 +259,47 @@ func TestClose(t *testing.T) {
t.Fatalf("failed to retrieve connect info: %v", err)
}
}

func TestRefreshDuration(t *testing.T) {
now := time.Now()
tcs := []struct {
desc string
expiry time.Time
want time.Duration
}{
{
desc: "when expiration is greater than 1 hour",
expiry: now.Add(4 * time.Hour),
want: 2 * time.Hour,
},
{
desc: "when expiration is equal to 1 hour",
expiry: now.Add(time.Hour),
want: 30 * time.Minute,
},
{
desc: "when expiration is less than 1 hour, but greater than 5 minutes",
expiry: now.Add(6 * time.Minute),
want: 5 * time.Minute,
},
{
desc: "when expiration is less than 5 minutes",
expiry: now.Add(4 * time.Minute),
want: 0,
},
{
desc: "when expiration is now",
expiry: now,
want: 0,
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
got := refreshDuration(now, tc.expiry)
// round to the second to remove millisecond differences
if got.Round(time.Second) != tc.want {
t.Fatalf("time until refresh: want = %v, got = %v", tc.want, got)
}
})
}
}