github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/worker/gclient/connection/connection.go (about)

     1  package connection
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"context"
     7  	"encoding/json"
     8  	"errors"
     9  	"fmt"
    10  	"io"
    11  	"net"
    12  	"net/url"
    13  	"strings"
    14  	"time"
    15  
    16  	"code.cloudfoundry.org/garden"
    17  	"code.cloudfoundry.org/garden/routes"
    18  	"code.cloudfoundry.org/garden/transport"
    19  	"code.cloudfoundry.org/lager"
    20  	"github.com/tedsuo/rata"
    21  )
    22  
    23  var ErrDisconnected = errors.New("disconnected")
    24  var ErrInvalidMessage = errors.New("invalid message payload")
    25  
    26  //go:generate counterfeiter . Connection
    27  type Connection interface {
    28  	Ping() error
    29  
    30  	Capacity() (garden.Capacity, error)
    31  
    32  	Create(spec garden.ContainerSpec) (string, error)
    33  	List(properties garden.Properties) ([]string, error)
    34  
    35  	// Destroys the container with the given handle. If the container cannot be
    36  	// found, garden.ContainerNotFoundError is returned. If deletion fails for another
    37  	// reason, another error type is returned.
    38  	Destroy(handle string) error
    39  
    40  	Stop(handle string, kill bool) error
    41  
    42  	Info(handle string) (garden.ContainerInfo, error)
    43  	BulkInfo(handles []string) (map[string]garden.ContainerInfoEntry, error)
    44  	BulkMetrics(handles []string) (map[string]garden.ContainerMetricsEntry, error)
    45  
    46  	StreamIn(handle string, spec garden.StreamInSpec) error
    47  	StreamOut(handle string, spec garden.StreamOutSpec) (io.ReadCloser, error)
    48  
    49  	CurrentBandwidthLimits(handle string) (garden.BandwidthLimits, error)
    50  	CurrentCPULimits(handle string) (garden.CPULimits, error)
    51  	CurrentDiskLimits(handle string) (garden.DiskLimits, error)
    52  	CurrentMemoryLimits(handle string) (garden.MemoryLimits, error)
    53  
    54  	// NOTE: Contexts are passed in to Run/Attach as they call WorkerHijackStreamer.Hijack()
    55  	//		 Hijack() spawns multiple dedicated connections that need to get cleaned up in error
    56  	//       scenarios. We use the cancelFunc associated to the context in order to close connections
    57  	Run(ctx context.Context, handle string, spec garden.ProcessSpec, io garden.ProcessIO) (garden.Process, error)
    58  	Attach(ctx context.Context, handle string, processID string, io garden.ProcessIO) (garden.Process, error)
    59  
    60  	NetIn(handle string, hostPort, containerPort uint32) (uint32, uint32, error)
    61  	NetOut(handle string, rule garden.NetOutRule) error
    62  	BulkNetOut(handle string, rules []garden.NetOutRule) error
    63  
    64  	SetGraceTime(handle string, graceTime time.Duration) error
    65  
    66  	Properties(handle string) (garden.Properties, error)
    67  	Property(handle string, name string) (string, error)
    68  	SetProperty(handle string, name string, value string) error
    69  
    70  	Metrics(handle string) (garden.Metrics, error)
    71  	RemoveProperty(handle string, name string) error
    72  }
    73  
    74  //go:generate counterfeiter . HijackStreamer
    75  type HijackStreamer interface {
    76  	Stream(handler string, body io.Reader, params rata.Params, query url.Values, contentType string) (io.ReadCloser, error)
    77  	Hijack(ctx context.Context, handler string, body io.Reader, params rata.Params, query url.Values, contentType string) (net.Conn, *bufio.Reader, error)
    78  }
    79  
    80  type connection struct {
    81  	hijacker HijackStreamer
    82  	log      lager.Logger
    83  }
    84  
    85  type Error struct {
    86  	StatusCode int
    87  	Message    string
    88  }
    89  
    90  func (err Error) Error() string {
    91  	return err.Message
    92  }
    93  
    94  func NewWithHijacker(hijacker HijackStreamer, log lager.Logger) Connection {
    95  	return &connection{
    96  		hijacker: hijacker,
    97  		log:      log,
    98  	}
    99  }
   100  
   101  func (c *connection) Ping() error {
   102  	return c.do(routes.Ping, nil, &struct{}{}, nil, nil)
   103  }
   104  
   105  func (c *connection) Capacity() (garden.Capacity, error) {
   106  	capacity := garden.Capacity{}
   107  	err := c.do(routes.Capacity, nil, &capacity, nil, nil)
   108  	if err != nil {
   109  		return garden.Capacity{}, err
   110  	}
   111  
   112  	return capacity, nil
   113  }
   114  
   115  func (c *connection) Create(spec garden.ContainerSpec) (string, error) {
   116  	res := struct {
   117  		Handle string `json:"handle"`
   118  	}{}
   119  
   120  	err := c.do(routes.Create, spec, &res, nil, nil)
   121  	if err != nil {
   122  		return "", err
   123  	}
   124  
   125  	return res.Handle, nil
   126  }
   127  
   128  func (c *connection) Stop(handle string, kill bool) error {
   129  	return c.do(
   130  		routes.Stop,
   131  		map[string]bool{
   132  			"kill": kill,
   133  		},
   134  		&struct{}{},
   135  		rata.Params{
   136  			"handle": handle,
   137  		},
   138  		nil,
   139  	)
   140  }
   141  
   142  func (c *connection) Destroy(handle string) error {
   143  	return c.do(
   144  		routes.Destroy,
   145  		nil,
   146  		&struct{}{},
   147  		rata.Params{
   148  			"handle": handle,
   149  		},
   150  		nil,
   151  	)
   152  }
   153  
   154  func (c *connection) Run(ctx context.Context, handle string, spec garden.ProcessSpec, processIO garden.ProcessIO) (garden.Process, error) {
   155  	reqBody := new(bytes.Buffer)
   156  
   157  	err := transport.WriteMessage(reqBody, spec)
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  
   162  	hijackedConn, hijackedResponseReader, err := c.hijacker.Hijack(
   163  		ctx,
   164  		routes.Run,
   165  		reqBody,
   166  		rata.Params{
   167  			"handle": handle,
   168  		},
   169  		nil,
   170  		"application/json",
   171  	)
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  
   176  	return c.streamProcess(ctx, handle, processIO, hijackedConn, hijackedResponseReader)
   177  }
   178  
   179  func (c *connection) Attach(ctx context.Context, handle string, processID string, processIO garden.ProcessIO) (garden.Process, error) {
   180  	reqBody := new(bytes.Buffer)
   181  
   182  	hijackedConn, hijackedResponseReader, err := c.hijacker.Hijack(
   183  		ctx,
   184  		routes.Attach,
   185  		reqBody,
   186  		rata.Params{
   187  			"handle": handle,
   188  			"pid":    processID,
   189  		},
   190  		nil,
   191  		"",
   192  	)
   193  	if err != nil {
   194  		return nil, err
   195  	}
   196  
   197  	return c.streamProcess(ctx, handle, processIO, hijackedConn, hijackedResponseReader)
   198  }
   199  
   200  func (c *connection) streamProcess(ctx context.Context, handle string, processIO garden.ProcessIO, hijackedConn net.Conn, hijackedResponseReader *bufio.Reader) (garden.Process, error) {
   201  	decoder := json.NewDecoder(hijackedResponseReader)
   202  
   203  	payload := &transport.ProcessPayload{}
   204  	if err := decoder.Decode(payload); err != nil {
   205  		return nil, err
   206  	}
   207  
   208  	processPipeline := &processStream{
   209  		processID: payload.ProcessID,
   210  		conn:      hijackedConn,
   211  	}
   212  
   213  	hijack := func(streamType string) (net.Conn, io.Reader, error) {
   214  		params := rata.Params{
   215  			"handle":   handle,
   216  			"pid":      processPipeline.ProcessID(),
   217  			"streamid": payload.StreamID,
   218  		}
   219  
   220  		return c.hijacker.Hijack(
   221  			ctx,
   222  			streamType,
   223  			nil,
   224  			params,
   225  			nil,
   226  			"application/json",
   227  		)
   228  	}
   229  
   230  	process := newProcess(payload.ProcessID, processPipeline)
   231  	streamHandler := newStreamHandler(c.log)
   232  	streamHandler.streamIn(processPipeline, processIO.Stdin)
   233  
   234  	var stdoutConn net.Conn
   235  	if processIO.Stdout != nil {
   236  		var (
   237  			stdout io.Reader
   238  			err    error
   239  		)
   240  		stdoutConn, stdout, err = hijack(routes.Stdout)
   241  		if err != nil {
   242  			werr := fmt.Errorf("connection: failed to hijack stream %s: %s", routes.Stdout, err)
   243  			process.exited(0, werr)
   244  			hijackedConn.Close()
   245  			return process, nil
   246  		}
   247  		streamHandler.streamOut(processIO.Stdout, stdout)
   248  	}
   249  
   250  	var stderrConn net.Conn
   251  	if processIO.Stderr != nil {
   252  		var (
   253  			stderr io.Reader
   254  			err    error
   255  		)
   256  		stderrConn, stderr, err = hijack(routes.Stderr)
   257  		if err != nil {
   258  			werr := fmt.Errorf("connection: failed to hijack stream %s: %s", routes.Stderr, err)
   259  			process.exited(0, werr)
   260  			hijackedConn.Close()
   261  			return process, nil
   262  		}
   263  		streamHandler.streamOut(processIO.Stderr, stderr)
   264  	}
   265  
   266  	go func() {
   267  		defer hijackedConn.Close()
   268  		if stdoutConn != nil {
   269  			defer stdoutConn.Close()
   270  		}
   271  		if stderrConn != nil {
   272  			defer stderrConn.Close()
   273  		}
   274  
   275  		select {
   276  		case <-ctx.Done():
   277  			process.exited(-1, fmt.Errorf("stdin/stdout/stderr streams were canceled by: %w", ctx.Err()))
   278  		case waitedFor := <-streamHandler.wait(decoder):
   279  			process.exited(waitedFor.exitCode, waitedFor.err)
   280  		}
   281  
   282  	}()
   283  
   284  	return process, nil
   285  }
   286  
   287  func (c *connection) NetIn(handle string, hostPort, containerPort uint32) (uint32, uint32, error) {
   288  	res := &transport.NetInResponse{}
   289  
   290  	err := c.do(
   291  		routes.NetIn,
   292  		&transport.NetInRequest{
   293  			Handle:        handle,
   294  			HostPort:      hostPort,
   295  			ContainerPort: containerPort,
   296  		},
   297  		res,
   298  		rata.Params{
   299  			"handle": handle,
   300  		},
   301  		nil,
   302  	)
   303  
   304  	if err != nil {
   305  		return 0, 0, err
   306  	}
   307  
   308  	return res.HostPort, res.ContainerPort, nil
   309  }
   310  
   311  func (c *connection) BulkNetOut(handle string, rules []garden.NetOutRule) error {
   312  	return c.do(
   313  		routes.BulkNetOut,
   314  		rules,
   315  		&struct{}{},
   316  		rata.Params{
   317  			"handle": handle,
   318  		},
   319  		nil,
   320  	)
   321  }
   322  
   323  func (c *connection) NetOut(handle string, rule garden.NetOutRule) error {
   324  	return c.do(
   325  		routes.NetOut,
   326  		rule,
   327  		&struct{}{},
   328  		rata.Params{
   329  			"handle": handle,
   330  		},
   331  		nil,
   332  	)
   333  }
   334  
   335  func (c *connection) Property(handle string, name string) (string, error) {
   336  	var res struct {
   337  		Value string `json:"value"`
   338  	}
   339  
   340  	err := c.do(
   341  		routes.Property,
   342  		nil,
   343  		&res,
   344  		rata.Params{
   345  			"handle": handle,
   346  			"key":    name,
   347  		},
   348  		nil,
   349  	)
   350  
   351  	return res.Value, err
   352  }
   353  
   354  func (c *connection) SetProperty(handle string, name string, value string) error {
   355  	err := c.do(
   356  		routes.SetProperty,
   357  		map[string]string{
   358  			"value": value,
   359  		},
   360  		&struct{}{},
   361  		rata.Params{
   362  			"handle": handle,
   363  			"key":    name,
   364  		},
   365  		nil,
   366  	)
   367  
   368  	if err != nil {
   369  		return err
   370  	}
   371  
   372  	return nil
   373  }
   374  
   375  func (c *connection) RemoveProperty(handle string, name string) error {
   376  	err := c.do(
   377  		routes.RemoveProperty,
   378  		nil,
   379  		&struct{}{},
   380  		rata.Params{
   381  			"handle": handle,
   382  			"key":    name,
   383  		},
   384  		nil,
   385  	)
   386  
   387  	if err != nil {
   388  		return err
   389  	}
   390  
   391  	return nil
   392  }
   393  
   394  func (c *connection) CurrentBandwidthLimits(handle string) (garden.BandwidthLimits, error) {
   395  	res := garden.BandwidthLimits{}
   396  
   397  	err := c.do(
   398  		routes.CurrentBandwidthLimits,
   399  		nil,
   400  		&res,
   401  		rata.Params{
   402  			"handle": handle,
   403  		},
   404  		nil,
   405  	)
   406  
   407  	return res, err
   408  }
   409  
   410  func (c *connection) CurrentCPULimits(handle string) (garden.CPULimits, error) {
   411  	res := garden.CPULimits{}
   412  
   413  	err := c.do(
   414  		routes.CurrentCPULimits,
   415  		nil,
   416  		&res,
   417  		rata.Params{
   418  			"handle": handle,
   419  		},
   420  		nil,
   421  	)
   422  
   423  	return res, err
   424  }
   425  
   426  func (c *connection) CurrentDiskLimits(handle string) (garden.DiskLimits, error) {
   427  	res := garden.DiskLimits{}
   428  
   429  	err := c.do(
   430  		routes.CurrentDiskLimits,
   431  		nil,
   432  		&res,
   433  		rata.Params{
   434  			"handle": handle,
   435  		},
   436  		nil,
   437  	)
   438  
   439  	return res, err
   440  }
   441  
   442  func (c *connection) CurrentMemoryLimits(handle string) (garden.MemoryLimits, error) {
   443  	res := garden.MemoryLimits{}
   444  
   445  	err := c.do(
   446  		routes.CurrentMemoryLimits,
   447  		nil,
   448  		&res,
   449  		rata.Params{
   450  			"handle": handle,
   451  		},
   452  		nil,
   453  	)
   454  
   455  	return res, err
   456  }
   457  
   458  func (c *connection) StreamIn(handle string, spec garden.StreamInSpec) error {
   459  	body, err := c.hijacker.Stream(
   460  		routes.StreamIn,
   461  		spec.TarStream,
   462  		rata.Params{
   463  			"handle": handle,
   464  		},
   465  		url.Values{
   466  			"user":        []string{spec.User},
   467  			"destination": []string{spec.Path},
   468  		},
   469  		"application/x-tar",
   470  	)
   471  	if err != nil {
   472  		return err
   473  	}
   474  
   475  	return body.Close()
   476  }
   477  
   478  func (c *connection) StreamOut(handle string, spec garden.StreamOutSpec) (io.ReadCloser, error) {
   479  	return c.hijacker.Stream(
   480  		routes.StreamOut,
   481  		nil,
   482  		rata.Params{
   483  			"handle": handle,
   484  		},
   485  		url.Values{
   486  			"user":   []string{spec.User},
   487  			"source": []string{spec.Path},
   488  		},
   489  		"",
   490  	)
   491  }
   492  
   493  func (c *connection) List(filterProperties garden.Properties) ([]string, error) {
   494  	values := url.Values{}
   495  	for name, val := range filterProperties {
   496  		values[name] = []string{val}
   497  	}
   498  
   499  	res := &struct {
   500  		Handles []string
   501  	}{}
   502  
   503  	if err := c.do(
   504  		routes.List,
   505  		nil,
   506  		&res,
   507  		nil,
   508  		values,
   509  	); err != nil {
   510  		return nil, err
   511  	}
   512  
   513  	return res.Handles, nil
   514  }
   515  
   516  func (c *connection) SetGraceTime(handle string, graceTime time.Duration) error {
   517  	return c.do(routes.SetGraceTime, graceTime, &struct{}{}, rata.Params{"handle": handle}, nil)
   518  }
   519  
   520  func (c *connection) Properties(handle string) (garden.Properties, error) {
   521  	res := make(garden.Properties)
   522  	err := c.do(routes.Properties, nil, &res, rata.Params{"handle": handle}, nil)
   523  	return res, err
   524  }
   525  
   526  func (c *connection) Metrics(handle string) (garden.Metrics, error) {
   527  	res := garden.Metrics{}
   528  	err := c.do(routes.Metrics, nil, &res, rata.Params{"handle": handle}, nil)
   529  	return res, err
   530  }
   531  
   532  func (c *connection) Info(handle string) (garden.ContainerInfo, error) {
   533  	res := garden.ContainerInfo{}
   534  
   535  	err := c.do(routes.Info, nil, &res, rata.Params{"handle": handle}, nil)
   536  	if err != nil {
   537  		return garden.ContainerInfo{}, err
   538  	}
   539  
   540  	return res, nil
   541  }
   542  
   543  func (c *connection) BulkInfo(handles []string) (map[string]garden.ContainerInfoEntry, error) {
   544  	res := make(map[string]garden.ContainerInfoEntry)
   545  	queryParams := url.Values{
   546  		"handles": []string{strings.Join(handles, ",")},
   547  	}
   548  	err := c.do(routes.BulkInfo, nil, &res, nil, queryParams)
   549  	return res, err
   550  }
   551  
   552  func (c *connection) BulkMetrics(handles []string) (map[string]garden.ContainerMetricsEntry, error) {
   553  	res := make(map[string]garden.ContainerMetricsEntry)
   554  	queryParams := url.Values{
   555  		"handles": []string{strings.Join(handles, ",")},
   556  	}
   557  	err := c.do(routes.BulkMetrics, nil, &res, nil, queryParams)
   558  	return res, err
   559  }
   560  
   561  func (c *connection) do(
   562  	handler string,
   563  	req, res interface{},
   564  	params rata.Params,
   565  	query url.Values,
   566  ) error {
   567  	var body io.Reader
   568  
   569  	if req != nil {
   570  		buf := new(bytes.Buffer)
   571  
   572  		err := transport.WriteMessage(buf, req)
   573  		if err != nil {
   574  			return err
   575  		}
   576  
   577  		body = buf
   578  	}
   579  
   580  	contentType := ""
   581  	if req != nil {
   582  		contentType = "application/json"
   583  	}
   584  
   585  	response, err := c.hijacker.Stream(
   586  		handler,
   587  		body,
   588  		params,
   589  		query,
   590  		contentType,
   591  	)
   592  	if err != nil {
   593  		return err
   594  	}
   595  
   596  	defer response.Close()
   597  
   598  	return json.NewDecoder(response).Decode(res)
   599  }