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 }