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 }