...

Source file src/cloud.google.com/go/cloudsqlconn/internal/cloudsql/lazy.go

Documentation: cloud.google.com/go/cloudsqlconn/internal/cloudsql

     1  // Copyright 2024 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //	https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package cloudsql
    16  
    17  import (
    18  	"context"
    19  	"crypto/rsa"
    20  	"sync"
    21  	"time"
    22  
    23  	"cloud.google.com/go/cloudsqlconn/debug"
    24  	"cloud.google.com/go/cloudsqlconn/instance"
    25  	"golang.org/x/oauth2"
    26  	sqladmin "google.golang.org/api/sqladmin/v1beta4"
    27  )
    28  
    29  // LazyRefreshCache is caches connection info and refreshes the cache only when
    30  // a caller requests connection info and the current certificate is expired.
    31  type LazyRefreshCache struct {
    32  	connName        instance.ConnName
    33  	logger          debug.ContextLogger
    34  	key             *rsa.PrivateKey
    35  	r               refresher
    36  	mu              sync.Mutex
    37  	useIAMAuthNDial bool
    38  	needsRefresh    bool
    39  	cached          ConnectionInfo
    40  }
    41  
    42  // NewLazyRefreshCache initializes a new LazyRefreshCache.
    43  func NewLazyRefreshCache(
    44  	cn instance.ConnName,
    45  	l debug.ContextLogger,
    46  	client *sqladmin.Service,
    47  	key *rsa.PrivateKey,
    48  	_ time.Duration,
    49  	ts oauth2.TokenSource,
    50  	dialerID string,
    51  	useIAMAuthNDial bool,
    52  ) *LazyRefreshCache {
    53  	return &LazyRefreshCache{
    54  		connName: cn,
    55  		logger:   l,
    56  		key:      key,
    57  		r: newRefresher(
    58  			l,
    59  			client,
    60  			ts,
    61  			dialerID,
    62  		),
    63  		useIAMAuthNDial: useIAMAuthNDial,
    64  	}
    65  }
    66  
    67  // ConnectionInfo returns connection info for the associated instance. New
    68  // connection info is retrieved under two conditions:
    69  // - the current connection info's certificate has expired, or
    70  // - a caller has separately called ForceRefresh
    71  func (c *LazyRefreshCache) ConnectionInfo(
    72  	ctx context.Context,
    73  ) (ConnectionInfo, error) {
    74  	c.mu.Lock()
    75  	defer c.mu.Unlock()
    76  	// strip monotonic clock with UTC()
    77  	now := time.Now().UTC()
    78  	// Pad expiration with a buffer to give the client plenty of time to
    79  	// establish a connection to the server with the certificate.
    80  	exp := c.cached.Expiration.UTC().Add(-refreshBuffer)
    81  	if !c.needsRefresh && now.Before(exp) {
    82  		c.logger.Debugf(
    83  			ctx,
    84  			"[%v] Connection info is still valid, using cached info",
    85  			c.connName.String(),
    86  		)
    87  		return c.cached, nil
    88  	}
    89  
    90  	c.logger.Debugf(
    91  		ctx,
    92  		"[%v] Connection info refresh operation started",
    93  		c.connName.String(),
    94  	)
    95  	ci, err := c.r.ConnectionInfo(ctx, c.connName, c.key, c.useIAMAuthNDial)
    96  	if err != nil {
    97  		c.logger.Debugf(
    98  			ctx,
    99  			"[%v] Connection info refresh operation failed, err = %v",
   100  			c.connName.String(),
   101  			err,
   102  		)
   103  		return ConnectionInfo{}, err
   104  	}
   105  	c.logger.Debugf(
   106  		ctx,
   107  		"[%v] Connection info refresh operation complete",
   108  		c.connName.String(),
   109  	)
   110  	c.logger.Debugf(
   111  		ctx,
   112  		"[%v] Current certificate expiration = %v",
   113  		c.connName.String(),
   114  		ci.Expiration.UTC().Format(time.RFC3339),
   115  	)
   116  	c.cached = ci
   117  	c.needsRefresh = false
   118  	return ci, nil
   119  }
   120  
   121  // UpdateRefresh updates the refresh operation to either enable or disable IAM
   122  // authentication for the cached connection info.
   123  func (c *LazyRefreshCache) UpdateRefresh(useIAMAuthNDial *bool) {
   124  	c.mu.Lock()
   125  	defer c.mu.Unlock()
   126  	if useIAMAuthNDial != nil && *useIAMAuthNDial != c.useIAMAuthNDial {
   127  		c.useIAMAuthNDial = *useIAMAuthNDial
   128  		c.needsRefresh = true
   129  	}
   130  }
   131  
   132  // ForceRefresh invalidates the caches and configures the next call to
   133  // ConnectionInfo to retrieve a fresh connection info.
   134  func (c *LazyRefreshCache) ForceRefresh() {
   135  	c.mu.Lock()
   136  	defer c.mu.Unlock()
   137  	c.needsRefresh = true
   138  }
   139  
   140  // Close is a no-op and provided purely for a consistent interface with other
   141  // caching types.
   142  func (c *LazyRefreshCache) Close() error {
   143  	return nil
   144  }
   145  

View as plain text