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