github.com/pachyderm/pachyderm@v1.13.4/src/server/pkg/serviceenv/service_env.go (about)

     1  package serviceenv
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"net"
     7  	"os"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/pachyderm/pachyderm/src/client"
    12  	"github.com/pachyderm/pachyderm/src/client/enterprise"
    13  	"github.com/pachyderm/pachyderm/src/client/pkg/errors"
    14  	"github.com/pachyderm/pachyderm/src/server/auth"
    15  	"github.com/pachyderm/pachyderm/src/server/pfs"
    16  	"github.com/pachyderm/pachyderm/src/server/pkg/backoff"
    17  	"github.com/pachyderm/pachyderm/src/server/pps"
    18  
    19  	etcd "github.com/coreos/etcd/clientv3"
    20  	loki "github.com/grafana/loki/pkg/logcli/client"
    21  	log "github.com/sirupsen/logrus"
    22  	"golang.org/x/net/context"
    23  	"golang.org/x/sync/errgroup"
    24  	kube "k8s.io/client-go/kubernetes"
    25  	"k8s.io/client-go/rest"
    26  )
    27  
    28  // ServiceEnv is a struct containing connections to other services in the
    29  // cluster. In pachd, there is only one instance of this struct, but tests may
    30  // create more, if they want to create multiple pachyderm "clusters" served in
    31  // separate goroutines.
    32  type ServiceEnv struct {
    33  	*Configuration
    34  
    35  	// pachAddress is the domain name or hostport where pachd can be reached
    36  	pachAddress string
    37  	// pachClient is the "template" client other clients returned by this library
    38  	// are based on. It contains the original GRPC client connection and has no
    39  	// ctx and therefore no auth credentials or cancellation
    40  	pachClient *client.APIClient
    41  	// pachEg coordinates the initialization of pachClient.  Note that ServiceEnv
    42  	// uses a separate error group for each client, rather than one for all
    43  	// three clients, so that pachd can initialize a ServiceEnv inside of its own
    44  	// initialization (if GetEtcdClient() blocked on intialization of 'pachClient'
    45  	// and pachd/main.go couldn't start the pachd server until GetEtcdClient() had
    46  	// returned, then pachd would be unable to start)
    47  	pachEg errgroup.Group
    48  
    49  	// etcdAddress is the domain name or hostport where etcd can be reached
    50  	etcdAddress string
    51  	// etcdClient is an etcd client that's shared by all users of this environment
    52  	etcdClient *etcd.Client
    53  	// etcdEg coordinates the initialization of etcdClient (see pachdEg)
    54  	etcdEg errgroup.Group
    55  
    56  	// kubeClient is a kubernetes client that, if initialized, is shared by all
    57  	// users of this environment
    58  	kubeClient *kube.Clientset
    59  	// kubeEg coordinates the initialization of kubeClient (see pachdEg)
    60  	kubeEg errgroup.Group
    61  
    62  	// lokiClient is a loki (log aggregator) client that is shared by all users
    63  	// of this environment, it doesn't require an initialization funcion, so
    64  	// there's no errgroup associated with it.
    65  	lokiClient *loki.Client
    66  
    67  	ppsServer        pps.InternalAPI
    68  	pfsServer        pfs.APIServer
    69  	enterpriseServer enterprise.APIServer
    70  	authServer       auth.APIServer
    71  }
    72  
    73  // InitPachOnlyEnv initializes this service environment. This dials a GRPC
    74  // connection to pachd only (in a background goroutine), and creates the
    75  // template pachClient used by future calls to GetPachClient.
    76  //
    77  // This call returns immediately, but GetPachClient will block
    78  // until the client is ready.
    79  func InitPachOnlyEnv(config *Configuration) *ServiceEnv {
    80  	env := &ServiceEnv{Configuration: config}
    81  	env.pachAddress = net.JoinHostPort("127.0.0.1", fmt.Sprintf("%d", env.PeerPort))
    82  	env.pachEg.Go(env.initPachClient)
    83  	return env // env is not ready yet
    84  }
    85  
    86  // InitServiceEnv initializes this service environment. This dials a GRPC
    87  // connection to pachd and etcd (in a background goroutine), and creates the
    88  // template pachClient used by future calls to GetPachClient.
    89  //
    90  // This call returns immediately, but GetPachClient and GetEtcdClient block
    91  // until their respective clients are ready.
    92  func InitServiceEnv(config *Configuration) *ServiceEnv {
    93  	env := InitPachOnlyEnv(config)
    94  	env.etcdAddress = fmt.Sprintf("http://%s", net.JoinHostPort(env.EtcdHost, env.EtcdPort))
    95  	env.etcdEg.Go(env.initEtcdClient)
    96  	if env.LokiHost != "" && env.LokiPort != "" {
    97  		env.lokiClient = &loki.Client{
    98  			Address: fmt.Sprintf("http://%s", net.JoinHostPort(env.LokiHost, env.LokiPort)),
    99  		}
   100  	}
   101  	return env // env is not ready yet
   102  }
   103  
   104  // InitWithKube is like InitServiceEnv, but also assumes that it's run inside
   105  // a kubernetes cluster and tries to connect to the kubernetes API server.
   106  func InitWithKube(config *Configuration) *ServiceEnv {
   107  	env := InitServiceEnv(config)
   108  	env.kubeEg.Go(env.initKubeClient)
   109  	return env // env is not ready yet
   110  }
   111  
   112  // InitPachOnlyTestEnv is like InitPachOnlyEnv, but initializes a pachd client
   113  // for tests (using client.NewForTest())
   114  func InitPachOnlyTestEnv(t *testing.T, config *Configuration) *ServiceEnv {
   115  	env := &ServiceEnv{Configuration: config}
   116  
   117  	var err error
   118  	env.pachClient, err = client.NewForTest()
   119  	if err != nil {
   120  		t.Fatalf("could not intialize pach client for test: %v", err)
   121  	}
   122  	return env // env is not ready yet
   123  }
   124  
   125  func (env *ServiceEnv) initPachClient() error {
   126  	// validate argument
   127  	if env.pachAddress == "" {
   128  		return errors.New("cannot initialize pach client with empty pach address")
   129  	}
   130  	// Initialize pach client
   131  	return backoff.Retry(func() error {
   132  		var err error
   133  		env.pachClient, err = client.NewFromAddress(env.pachAddress)
   134  		if err != nil {
   135  			return errors.Wrapf(err, "failed to initialize pach client")
   136  		}
   137  		return nil
   138  	}, backoff.RetryEvery(time.Second).For(5*time.Minute))
   139  }
   140  
   141  func (env *ServiceEnv) initEtcdClient() error {
   142  	// validate argument
   143  	if env.etcdAddress == "" {
   144  		return errors.New("cannot initialize pach client with empty pach address")
   145  	}
   146  	// Initialize etcd
   147  	return backoff.Retry(func() error {
   148  		var err error
   149  		env.etcdClient, err = etcd.New(etcd.Config{
   150  			Endpoints: []string{env.etcdAddress},
   151  			// Use a long timeout with Etcd so that Pachyderm doesn't crash loop
   152  			// while waiting for etcd to come up (makes startup net faster)
   153  			DialTimeout:        3 * time.Minute,
   154  			DialOptions:        client.DefaultDialOptions(), // SA1019 can't call grpc.Dial directly
   155  			MaxCallSendMsgSize: math.MaxInt32,
   156  			MaxCallRecvMsgSize: math.MaxInt32,
   157  		})
   158  		if err != nil {
   159  			return errors.Wrapf(err, "failed to initialize etcd client")
   160  		}
   161  		return nil
   162  	}, backoff.RetryEvery(time.Second).For(5*time.Minute))
   163  }
   164  
   165  func (env *ServiceEnv) initKubeClient() error {
   166  	return backoff.Retry(func() error {
   167  		// Get secure in-cluster config
   168  		var kubeAddr string
   169  		var ok bool
   170  		cfg, err := rest.InClusterConfig()
   171  		if err != nil {
   172  			// InClusterConfig failed, fall back to insecure config
   173  			log.Errorf("falling back to insecure kube client due to error from NewInCluster: %s", err)
   174  			kubeAddr, ok = os.LookupEnv("KUBERNETES_PORT_443_TCP_ADDR")
   175  			if !ok {
   176  				return errors.Wrapf(err, "can't fall back to insecure kube client due to missing env var (failed to retrieve in-cluster config")
   177  			}
   178  			cfg = &rest.Config{
   179  				Host: fmt.Sprintf("%s:443", kubeAddr),
   180  				TLSClientConfig: rest.TLSClientConfig{
   181  					Insecure: true,
   182  				},
   183  			}
   184  		}
   185  		env.kubeClient, err = kube.NewForConfig(cfg)
   186  		if err != nil {
   187  			return errors.Wrapf(err, "could not initialize kube client")
   188  		}
   189  		return nil
   190  	}, backoff.RetryEvery(time.Second).For(5*time.Minute))
   191  }
   192  
   193  // GetPachClient returns a pachd client with the same authentication
   194  // credentials and cancellation as 'ctx' (ensuring that auth credentials are
   195  // propagated through downstream RPCs).
   196  //
   197  // Functions that receive RPCs should call this to convert their RPC context to
   198  // a Pachyderm client, and internal Pachyderm calls should accept clients
   199  // returned by this call.
   200  //
   201  // (Warning) Do not call this function during server setup unless it is in a goroutine.
   202  // A Pachyderm client is not available until the server has been setup.
   203  func (env *ServiceEnv) GetPachClient(ctx context.Context) *client.APIClient {
   204  	if err := env.pachEg.Wait(); err != nil {
   205  		panic(err) // If env can't connect, there's no sensible way to recover
   206  	}
   207  	return env.pachClient.WithCtx(ctx)
   208  }
   209  
   210  // GetEtcdClient returns the already connected etcd client without modification.
   211  func (env *ServiceEnv) GetEtcdClient() *etcd.Client {
   212  	if err := env.etcdEg.Wait(); err != nil {
   213  		panic(err) // If env can't connect, there's no sensible way to recover
   214  	}
   215  	if env.etcdClient == nil {
   216  		panic("service env never connected to etcd")
   217  	}
   218  	return env.etcdClient
   219  }
   220  
   221  // GetKubeClient returns the already connected Kubernetes API client without
   222  // modification.
   223  func (env *ServiceEnv) GetKubeClient() *kube.Clientset {
   224  	if err := env.kubeEg.Wait(); err != nil {
   225  		panic(err) // If env can't connect, there's no sensible way to recover
   226  	}
   227  	if env.kubeClient == nil {
   228  		panic("service env never connected to kubernetes")
   229  	}
   230  	return env.kubeClient
   231  }
   232  
   233  // GetLokiClient returns the loki client, it doesn't require blocking on a
   234  // connection because the client is just a dumb struct with no init function.
   235  func (env *ServiceEnv) GetLokiClient() (*loki.Client, error) {
   236  	if env.lokiClient == nil {
   237  		return nil, errors.Errorf("loki not configured, is it running in the same namespace as pachd?")
   238  	}
   239  	return env.lokiClient, nil
   240  }
   241  
   242  // PpsServer returns the registered PPS APIServer
   243  func (env *ServiceEnv) PpsServer() pps.InternalAPI {
   244  	return env.ppsServer
   245  }
   246  
   247  // SetPpsServer registers a Pps APIServer with this service env
   248  func (env *ServiceEnv) SetPpsServer(s pps.InternalAPI) {
   249  	env.ppsServer = s
   250  }
   251  
   252  // PfsServer returns the registered PFS APIServer
   253  func (env *ServiceEnv) PfsServer() pfs.APIServer {
   254  	return env.pfsServer
   255  }
   256  
   257  // SetPfsServer registers a Pfs APIServer with this service env
   258  func (env *ServiceEnv) SetPfsServer(s pfs.APIServer) {
   259  	env.pfsServer = s
   260  }
   261  
   262  // EnterpriseServer returns the registered PFS APIServer
   263  func (env *ServiceEnv) EnterpriseServer() enterprise.APIServer {
   264  	return env.enterpriseServer
   265  }
   266  
   267  // SetEnterpriseServer registers a Enterprise APIServer with this service env
   268  func (env *ServiceEnv) SetEnterpriseServer(s enterprise.APIServer) {
   269  	env.enterpriseServer = s
   270  }
   271  
   272  // AuthServer returns the registered Auth APIServer
   273  func (env *ServiceEnv) AuthServer() auth.APIServer {
   274  	return env.authServer
   275  }
   276  
   277  // SetAuthServer registers a Auth APIServer with this service env
   278  func (env *ServiceEnv) SetAuthServer(s auth.APIServer) {
   279  	env.authServer = s
   280  }