181 lines
5.1 KiB
Go
181 lines
5.1 KiB
Go
/*
|
|
Copyright IBM Corp. All Rights Reserved.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package container
|
|
|
|
import (
|
|
"io"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/hyperledger/fabric/common/flogging"
|
|
"github.com/hyperledger/fabric/core/chaincode/persistence"
|
|
"github.com/hyperledger/fabric/core/container/ccintf"
|
|
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
var vmLogger = flogging.MustGetLogger("container")
|
|
|
|
//go:generate counterfeiter -o mock/docker_builder.go --fake-name DockerBuilder . DockerBuilder
|
|
|
|
// DockerBuilder is what is exposed by the dockercontroller
|
|
type DockerBuilder interface {
|
|
Build(ccid string, metadata *persistence.ChaincodePackageMetadata, codePackageStream io.Reader) (Instance, error)
|
|
}
|
|
|
|
//go:generate counterfeiter -o mock/external_builder.go --fake-name ExternalBuilder . ExternalBuilder
|
|
|
|
// ExternalBuilder is what is exposed by the dockercontroller
|
|
type ExternalBuilder interface {
|
|
Build(ccid string, metadata []byte, codePackageStream io.Reader) (Instance, error)
|
|
}
|
|
|
|
//go:generate counterfeiter -o mock/instance.go --fake-name Instance . Instance
|
|
|
|
// Instance represents a built chaincode instance, because of the docker legacy, calling this a
|
|
// built 'container' would be very misleading, and going forward with the external launcher
|
|
// 'image' also seemed inappropriate. So, the vague 'Instance' is used here.
|
|
type Instance interface {
|
|
Start(peerConnection *ccintf.PeerConnection) error
|
|
ChaincodeServerInfo() (*ccintf.ChaincodeServerInfo, error)
|
|
Stop() error
|
|
Wait() (int, error)
|
|
}
|
|
|
|
type UninitializedInstance struct{}
|
|
|
|
func (UninitializedInstance) Start(peerConnection *ccintf.PeerConnection) error {
|
|
return errors.Errorf("instance has not yet been built, cannot be started")
|
|
}
|
|
|
|
func (UninitializedInstance) ChaincodeServerInfo() (*ccintf.ChaincodeServerInfo, error) {
|
|
return nil, errors.Errorf("instance has not yet been built, cannot get chaincode server info")
|
|
}
|
|
|
|
func (UninitializedInstance) Stop() error {
|
|
return errors.Errorf("instance has not yet been built, cannot be stopped")
|
|
}
|
|
|
|
func (UninitializedInstance) Wait() (int, error) {
|
|
return 0, errors.Errorf("instance has not yet been built, cannot wait")
|
|
}
|
|
|
|
//go:generate counterfeiter -o mock/package_provider.go --fake-name PackageProvider . PackageProvider
|
|
|
|
// PackageProvider gets chaincode packages from the filesystem.
|
|
type PackageProvider interface {
|
|
GetChaincodePackage(packageID string) (md *persistence.ChaincodePackageMetadata, mdBytes []byte, codeStream io.ReadCloser, err error)
|
|
}
|
|
|
|
type Router struct {
|
|
ExternalBuilder ExternalBuilder
|
|
DockerBuilder DockerBuilder
|
|
containers map[string]Instance
|
|
PackageProvider PackageProvider
|
|
mutex sync.Mutex
|
|
}
|
|
|
|
func (r *Router) getInstance(ccid string) Instance {
|
|
r.mutex.Lock()
|
|
defer r.mutex.Unlock()
|
|
|
|
// Note, to resolve the locking problem which existed in the previous code, we never delete
|
|
// references from the map. In this way, it is safe to release the lock and operate
|
|
// on the returned reference
|
|
vm, ok := r.containers[ccid]
|
|
if !ok {
|
|
return UninitializedInstance{}
|
|
}
|
|
|
|
return vm
|
|
}
|
|
|
|
func (r *Router) Build(ccid string) error {
|
|
var instance Instance
|
|
|
|
if r.ExternalBuilder != nil {
|
|
// for now, the package ID we retrieve from the FS is always the ccid
|
|
// the chaincode uses for registration
|
|
_, mdBytes, codeStream, err := r.PackageProvider.GetChaincodePackage(ccid)
|
|
if err != nil {
|
|
return errors.WithMessage(err, "failed to get chaincode package for external build")
|
|
}
|
|
defer codeStream.Close()
|
|
|
|
instance, err = r.ExternalBuilder.Build(ccid, mdBytes, codeStream)
|
|
if err != nil {
|
|
return errors.WithMessage(err, "external builder failed")
|
|
}
|
|
}
|
|
|
|
if instance == nil {
|
|
if r.DockerBuilder == nil {
|
|
return errors.New("no DockerBuilder, cannot build")
|
|
}
|
|
metadata, _, codeStream, err := r.PackageProvider.GetChaincodePackage(ccid)
|
|
if err != nil {
|
|
return errors.WithMessage(err, "failed to get chaincode package for docker build")
|
|
}
|
|
defer codeStream.Close()
|
|
|
|
instance, err = r.DockerBuilder.Build(ccid, metadata, codeStream)
|
|
if err != nil {
|
|
return errors.WithMessage(err, "docker build failed")
|
|
}
|
|
}
|
|
|
|
r.mutex.Lock()
|
|
defer r.mutex.Unlock()
|
|
if r.containers == nil {
|
|
r.containers = map[string]Instance{}
|
|
}
|
|
r.containers[ccid] = instance
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *Router) ChaincodeServerInfo(ccid string) (*ccintf.ChaincodeServerInfo, error) {
|
|
return r.getInstance(ccid).ChaincodeServerInfo()
|
|
}
|
|
|
|
func (r *Router) Start(ccid string, peerConnection *ccintf.PeerConnection) error {
|
|
return r.getInstance(ccid).Start(peerConnection)
|
|
}
|
|
|
|
func (r *Router) Stop(ccid string) error {
|
|
return r.getInstance(ccid).Stop()
|
|
}
|
|
|
|
func (r *Router) Wait(ccid string) (int, error) {
|
|
return r.getInstance(ccid).Wait()
|
|
}
|
|
|
|
func (r *Router) Shutdown(timeout time.Duration) {
|
|
var wg sync.WaitGroup
|
|
for ccid := range r.containers {
|
|
wg.Add(1)
|
|
go func(ccid string) {
|
|
defer wg.Done()
|
|
if err := r.Stop(ccid); err != nil {
|
|
vmLogger.Warnw("failed to stop chaincode", "ccid", ccid, "error", err)
|
|
}
|
|
}(ccid)
|
|
}
|
|
|
|
done := make(chan struct{})
|
|
go func() {
|
|
wg.Wait()
|
|
close(done)
|
|
}()
|
|
|
|
select {
|
|
case <-time.After(timeout):
|
|
vmLogger.Warning("timeout while stopping external chaincodes")
|
|
case <-done:
|
|
}
|
|
}
|