github.com/ahlemtn/fabric@v2.1.1+incompatible/core/container/externalbuilder/instance.go (about)

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