github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/core/container/container.go (about) 1 /* 2 Copyright hechain. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package container 8 9 import ( 10 "io" 11 "sync" 12 "time" 13 14 "github.com/hechain20/hechain/common/flogging" 15 "github.com/hechain20/hechain/core/chaincode/persistence" 16 "github.com/hechain20/hechain/core/container/ccintf" 17 18 "github.com/pkg/errors" 19 ) 20 21 var vmLogger = flogging.MustGetLogger("container") 22 23 //go:generate counterfeiter -o mock/docker_builder.go --fake-name DockerBuilder . DockerBuilder 24 25 // DockerBuilder is what is exposed by the dockercontroller 26 type DockerBuilder interface { 27 Build(ccid string, metadata *persistence.ChaincodePackageMetadata, codePackageStream io.Reader) (Instance, error) 28 } 29 30 //go:generate counterfeiter -o mock/external_builder.go --fake-name ExternalBuilder . ExternalBuilder 31 32 // ExternalBuilder is what is exposed by the dockercontroller 33 type ExternalBuilder interface { 34 Build(ccid string, metadata []byte, codePackageStream io.Reader) (Instance, error) 35 } 36 37 //go:generate counterfeiter -o mock/instance.go --fake-name Instance . Instance 38 39 // Instance represents a built chaincode instance, because of the docker legacy, calling this a 40 // built 'container' would be very misleading, and going forward with the external launcher 41 // 'image' also seemed inappropriate. So, the vague 'Instance' is used here. 42 type Instance interface { 43 Start(peerConnection *ccintf.PeerConnection) error 44 ChaincodeServerInfo() (*ccintf.ChaincodeServerInfo, error) 45 Stop() error 46 Wait() (int, error) 47 } 48 49 type UninitializedInstance struct{} 50 51 func (UninitializedInstance) Start(peerConnection *ccintf.PeerConnection) error { 52 return errors.Errorf("instance has not yet been built, cannot be started") 53 } 54 55 func (UninitializedInstance) ChaincodeServerInfo() (*ccintf.ChaincodeServerInfo, error) { 56 return nil, errors.Errorf("instance has not yet been built, cannot get chaincode server info") 57 } 58 59 func (UninitializedInstance) Stop() error { 60 return errors.Errorf("instance has not yet been built, cannot be stopped") 61 } 62 63 func (UninitializedInstance) Wait() (int, error) { 64 return 0, errors.Errorf("instance has not yet been built, cannot wait") 65 } 66 67 //go:generate counterfeiter -o mock/package_provider.go --fake-name PackageProvider . PackageProvider 68 69 // PackageProvider gets chaincode packages from the filesystem. 70 type PackageProvider interface { 71 GetChaincodePackage(packageID string) (md *persistence.ChaincodePackageMetadata, mdBytes []byte, codeStream io.ReadCloser, err error) 72 } 73 74 type Router struct { 75 ExternalBuilder ExternalBuilder 76 DockerBuilder DockerBuilder 77 containers map[string]Instance 78 PackageProvider PackageProvider 79 mutex sync.Mutex 80 } 81 82 func (r *Router) getInstance(ccid string) Instance { 83 r.mutex.Lock() 84 defer r.mutex.Unlock() 85 86 // Note, to resolve the locking problem which existed in the previous code, we never delete 87 // references from the map. In this way, it is safe to release the lock and operate 88 // on the returned reference 89 vm, ok := r.containers[ccid] 90 if !ok { 91 return UninitializedInstance{} 92 } 93 94 return vm 95 } 96 97 func (r *Router) Build(ccid string) error { 98 var instance Instance 99 100 if r.ExternalBuilder != nil { 101 // for now, the package ID we retrieve from the FS is always the ccid 102 // the chaincode uses for registration 103 _, mdBytes, codeStream, err := r.PackageProvider.GetChaincodePackage(ccid) 104 if err != nil { 105 return errors.WithMessage(err, "failed to get chaincode package for external build") 106 } 107 defer codeStream.Close() 108 109 instance, err = r.ExternalBuilder.Build(ccid, mdBytes, codeStream) 110 if err != nil { 111 return errors.WithMessage(err, "external builder failed") 112 } 113 } 114 115 if instance == nil { 116 if r.DockerBuilder == nil { 117 return errors.New("no DockerBuilder, cannot build") 118 } 119 metadata, _, codeStream, err := r.PackageProvider.GetChaincodePackage(ccid) 120 if err != nil { 121 return errors.WithMessage(err, "failed to get chaincode package for docker build") 122 } 123 defer codeStream.Close() 124 125 instance, err = r.DockerBuilder.Build(ccid, metadata, codeStream) 126 if err != nil { 127 return errors.WithMessage(err, "docker build failed") 128 } 129 } 130 131 r.mutex.Lock() 132 defer r.mutex.Unlock() 133 if r.containers == nil { 134 r.containers = map[string]Instance{} 135 } 136 r.containers[ccid] = instance 137 138 return nil 139 } 140 141 func (r *Router) ChaincodeServerInfo(ccid string) (*ccintf.ChaincodeServerInfo, error) { 142 return r.getInstance(ccid).ChaincodeServerInfo() 143 } 144 145 func (r *Router) Start(ccid string, peerConnection *ccintf.PeerConnection) error { 146 return r.getInstance(ccid).Start(peerConnection) 147 } 148 149 func (r *Router) Stop(ccid string) error { 150 return r.getInstance(ccid).Stop() 151 } 152 153 func (r *Router) Wait(ccid string) (int, error) { 154 return r.getInstance(ccid).Wait() 155 } 156 157 func (r *Router) Shutdown(timeout time.Duration) { 158 var wg sync.WaitGroup 159 for ccid := range r.containers { 160 wg.Add(1) 161 go func(ccid string) { 162 defer wg.Done() 163 if err := r.Stop(ccid); err != nil { 164 vmLogger.Warnw("failed to stop chaincode", "ccid", ccid, "error", err) 165 } 166 }(ccid) 167 } 168 169 done := make(chan struct{}) 170 go func() { 171 wg.Wait() 172 close(done) 173 }() 174 175 select { 176 case <-time.After(timeout): 177 vmLogger.Warning("timeout while stopping external chaincodes") 178 case <-done: 179 } 180 }