github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/core/container/externalbuilder/instance.go (about)

     1  /*
     2  Copyright hechain. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package externalbuilder
     8  
     9  import (
    10  	"encoding/json"
    11  	"io/ioutil"
    12  	"os"
    13  	"os/exec"
    14  	"path/filepath"
    15  	"syscall"
    16  	"time"
    17  
    18  	"github.com/hechain20/hechain/core/container/ccintf"
    19  	"github.com/hechain20/hechain/internal/pkg/comm"
    20  	"github.com/pkg/errors"
    21  )
    22  
    23  const (
    24  	DialTimeout        = 3 * time.Second
    25  	CCServerReleaseDir = "chaincode/server"
    26  )
    27  
    28  type Instance struct {
    29  	PackageID   string
    30  	BldDir      string
    31  	ReleaseDir  string
    32  	Builder     *Builder
    33  	Session     *Session
    34  	TermTimeout time.Duration
    35  }
    36  
    37  // Duration used for the DialTimeout property
    38  type Duration time.Duration
    39  
    40  func (d Duration) MarshalJSON() ([]byte, error) {
    41  	return json.Marshal(time.Duration(d).String())
    42  }
    43  
    44  func (d *Duration) UnmarshalJSON(b []byte) error {
    45  	var v interface{}
    46  	if err := json.Unmarshal(b, &v); err != nil {
    47  		return err
    48  	}
    49  
    50  	switch value := v.(type) {
    51  	case float64:
    52  		*d = Duration(time.Duration(value))
    53  	case string:
    54  		dur, err := time.ParseDuration(value)
    55  		if err != nil {
    56  			return err
    57  		}
    58  		*d = Duration(dur)
    59  	default:
    60  		return errors.New("invalid duration")
    61  	}
    62  
    63  	return nil
    64  }
    65  
    66  // ChaincodeServerUserData holds "connection.json" information
    67  type ChaincodeServerUserData struct {
    68  	Address            string   `json:"address"`
    69  	DialTimeout        Duration `json:"dial_timeout"`
    70  	TLSRequired        bool     `json:"tls_required"`
    71  	ClientAuthRequired bool     `json:"client_auth_required"`
    72  	ClientKey          string   `json:"client_key"`  // PEM encoded client key
    73  	ClientCert         string   `json:"client_cert"` // PEM encoded client certificate
    74  	RootCert           string   `json:"root_cert"`   // PEM encoded peer chaincode certificate
    75  
    76  }
    77  
    78  func (c *ChaincodeServerUserData) ChaincodeServerInfo(cryptoDir string) (*ccintf.ChaincodeServerInfo, error) {
    79  	if c.Address == "" {
    80  		return nil, errors.New("chaincode address not provided")
    81  	}
    82  	connInfo := &ccintf.ChaincodeServerInfo{Address: c.Address}
    83  
    84  	connInfo.ClientConfig.DialTimeout = time.Duration(c.DialTimeout)
    85  	if connInfo.ClientConfig.DialTimeout == 0 {
    86  		connInfo.ClientConfig.DialTimeout = DialTimeout
    87  	}
    88  
    89  	// we can expose this if necessary
    90  	connInfo.ClientConfig.KaOpts = comm.DefaultKeepaliveOptions
    91  
    92  	if !c.TLSRequired {
    93  		return connInfo, nil
    94  	}
    95  	if c.ClientAuthRequired && c.ClientKey == "" {
    96  		return nil, errors.New("chaincode tls key not provided")
    97  	}
    98  	if c.ClientAuthRequired && c.ClientCert == "" {
    99  		return nil, errors.New("chaincode tls cert not provided")
   100  	}
   101  	if c.RootCert == "" {
   102  		return nil, errors.New("chaincode tls root cert not provided")
   103  	}
   104  
   105  	connInfo.ClientConfig.SecOpts.UseTLS = true
   106  
   107  	if c.ClientAuthRequired {
   108  		connInfo.ClientConfig.SecOpts.RequireClientCert = true
   109  		connInfo.ClientConfig.SecOpts.Certificate = []byte(c.ClientCert)
   110  		connInfo.ClientConfig.SecOpts.Key = []byte(c.ClientKey)
   111  	}
   112  
   113  	connInfo.ClientConfig.SecOpts.ServerRootCAs = [][]byte{[]byte(c.RootCert)}
   114  
   115  	return connInfo, nil
   116  }
   117  
   118  func (i *Instance) ChaincodeServerReleaseDir() string {
   119  	return filepath.Join(i.ReleaseDir, CCServerReleaseDir)
   120  }
   121  
   122  func (i *Instance) ChaincodeServerInfo() (*ccintf.ChaincodeServerInfo, error) {
   123  	ccinfoPath := filepath.Join(i.ChaincodeServerReleaseDir(), "connection.json")
   124  
   125  	_, err := os.Stat(ccinfoPath)
   126  
   127  	if os.IsNotExist(err) {
   128  		return nil, nil
   129  	}
   130  
   131  	if err != nil {
   132  		return nil, errors.WithMessage(err, "connection information not provided")
   133  	}
   134  	b, err := ioutil.ReadFile(ccinfoPath)
   135  	if err != nil {
   136  		return nil, errors.WithMessagef(err, "could not read '%s' for chaincode info", ccinfoPath)
   137  	}
   138  	ccdata := &ChaincodeServerUserData{}
   139  	err = json.Unmarshal(b, &ccdata)
   140  	if err != nil {
   141  		return nil, errors.WithMessagef(err, "malformed chaincode info at '%s'", ccinfoPath)
   142  	}
   143  
   144  	return ccdata.ChaincodeServerInfo(i.ChaincodeServerReleaseDir())
   145  }
   146  
   147  func (i *Instance) Start(peerConnection *ccintf.PeerConnection) error {
   148  	sess, err := i.Builder.Run(i.PackageID, i.BldDir, peerConnection)
   149  	if err != nil {
   150  		return errors.WithMessage(err, "could not execute run")
   151  	}
   152  	i.Session = sess
   153  	return nil
   154  }
   155  
   156  // Stop signals the process to terminate with SIGTERM. If the process doesn't
   157  // terminate within TermTimeout, the process is killed with SIGKILL.
   158  func (i *Instance) Stop() error {
   159  	if i.Session == nil {
   160  		return errors.Errorf("instance has not been started")
   161  	}
   162  
   163  	done := make(chan struct{})
   164  	go func() { i.Wait(); close(done) }()
   165  
   166  	i.Session.Signal(syscall.SIGTERM)
   167  	select {
   168  	case <-time.After(i.TermTimeout):
   169  		i.Session.Signal(syscall.SIGKILL)
   170  	case <-done:
   171  		return nil
   172  	}
   173  
   174  	select {
   175  	case <-time.After(5 * time.Second):
   176  		return errors.Errorf("failed to stop instance '%s'", i.PackageID)
   177  	case <-done:
   178  		return nil
   179  	}
   180  }
   181  
   182  func (i *Instance) Wait() (int, error) {
   183  	if i.Session == nil {
   184  		return -1, errors.Errorf("instance was not successfully started")
   185  	}
   186  
   187  	err := i.Session.Wait()
   188  	err = errors.Wrapf(err, "builder '%s' run failed", i.Builder.Name)
   189  	if exitErr, ok := errors.Cause(err).(*exec.ExitError); ok {
   190  		return exitErr.ExitCode(), err
   191  	}
   192  	return 0, err
   193  }