github.com/darrenli6/fabric-sdk-example@v0.0.0-20220109053535-94b13b56df8c/core/container/controller.go (about)

     1  /*
     2  Copyright IBM Corp. 2016 All Rights Reserved.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  		 http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package container
    18  
    19  import (
    20  	"fmt"
    21  	"io"
    22  	"sync"
    23  
    24  	"golang.org/x/net/context"
    25  
    26  	"github.com/hyperledger/fabric/core/container/api"
    27  	"github.com/hyperledger/fabric/core/container/ccintf"
    28  	"github.com/hyperledger/fabric/core/container/dockercontroller"
    29  	"github.com/hyperledger/fabric/core/container/inproccontroller"
    30  )
    31  
    32  type refCountedLock struct {
    33  	refCount int
    34  	lock     *sync.RWMutex
    35  }
    36  
    37  //VMController - manages VMs
    38  //   . abstract construction of different types of VMs (we only care about Docker for now)
    39  //   . manage lifecycle of VM (start with build, start, stop ...
    40  //     eventually probably need fine grained management)
    41  type VMController struct {
    42  	sync.RWMutex
    43  	// Handlers for each chaincode
    44  	containerLocks map[string]*refCountedLock
    45  }
    46  
    47  //singleton...acess through NewVMController
    48  var vmcontroller *VMController
    49  
    50  //constants for supported containers
    51  const (
    52  	DOCKER = "Docker"
    53  	SYSTEM = "System"
    54  )
    55  
    56  //NewVMController - creates/returns singleton
    57  func init() {
    58  	vmcontroller = new(VMController)
    59  	vmcontroller.containerLocks = make(map[string]*refCountedLock)
    60  }
    61  
    62  func (vmc *VMController) newVM(typ string) api.VM {
    63  	var (
    64  		v api.VM
    65  	)
    66  
    67  	switch typ {
    68  	case DOCKER:
    69  		v = dockercontroller.NewDockerVM()
    70  	case SYSTEM:
    71  		v = &inproccontroller.InprocVM{}
    72  	default:
    73  		v = &dockercontroller.DockerVM{}
    74  	}
    75  	return v
    76  }
    77  
    78  func (vmc *VMController) lockContainer(id string) {
    79  	//get the container lock under global lock
    80  	vmcontroller.Lock()
    81  	var refLck *refCountedLock
    82  	var ok bool
    83  	if refLck, ok = vmcontroller.containerLocks[id]; !ok {
    84  		refLck = &refCountedLock{refCount: 1, lock: &sync.RWMutex{}}
    85  		vmcontroller.containerLocks[id] = refLck
    86  	} else {
    87  		refLck.refCount++
    88  		vmLogger.Debugf("refcount %d (%s)", refLck.refCount, id)
    89  	}
    90  	vmcontroller.Unlock()
    91  	vmLogger.Debugf("waiting for container(%s) lock", id)
    92  	refLck.lock.Lock()
    93  	vmLogger.Debugf("got container (%s) lock", id)
    94  }
    95  
    96  func (vmc *VMController) unlockContainer(id string) {
    97  	vmcontroller.Lock()
    98  	if refLck, ok := vmcontroller.containerLocks[id]; ok {
    99  		if refLck.refCount <= 0 {
   100  			panic("refcnt <= 0")
   101  		}
   102  		refLck.lock.Unlock()
   103  		if refLck.refCount--; refLck.refCount == 0 {
   104  			vmLogger.Debugf("container lock deleted(%s)", id)
   105  			delete(vmcontroller.containerLocks, id)
   106  		}
   107  	} else {
   108  		vmLogger.Debugf("no lock to unlock(%s)!!", id)
   109  	}
   110  	vmcontroller.Unlock()
   111  }
   112  
   113  //VMCReqIntf - all requests should implement this interface.
   114  //The context should be passed and tested at each layer till we stop
   115  //note that we'd stop on the first method on the stack that does not
   116  //take context
   117  type VMCReqIntf interface {
   118  	do(ctxt context.Context, v api.VM) VMCResp
   119  	getCCID() ccintf.CCID
   120  }
   121  
   122  //VMCResp - response from requests. resp field is a anon interface.
   123  //It can hold any response. err should be tested first
   124  type VMCResp struct {
   125  	Err  error
   126  	Resp interface{}
   127  }
   128  
   129  //CreateImageReq - properties for creating an container image
   130  type CreateImageReq struct {
   131  	ccintf.CCID
   132  	Reader io.Reader
   133  	Args   []string
   134  	Env    []string
   135  }
   136  
   137  func (bp CreateImageReq) do(ctxt context.Context, v api.VM) VMCResp {
   138  	var resp VMCResp
   139  
   140  	if err := v.Deploy(ctxt, bp.CCID, bp.Args, bp.Env, bp.Reader); err != nil {
   141  		resp = VMCResp{Err: err}
   142  	} else {
   143  		resp = VMCResp{}
   144  	}
   145  
   146  	return resp
   147  }
   148  
   149  func (bp CreateImageReq) getCCID() ccintf.CCID {
   150  	return bp.CCID
   151  }
   152  
   153  //StartImageReq - properties for starting a container.
   154  type StartImageReq struct {
   155  	ccintf.CCID
   156  	Builder       api.BuildSpecFactory
   157  	Args          []string
   158  	Env           []string
   159  	PrelaunchFunc api.PrelaunchFunc
   160  }
   161  
   162  func (si StartImageReq) do(ctxt context.Context, v api.VM) VMCResp {
   163  	var resp VMCResp
   164  
   165  	if err := v.Start(ctxt, si.CCID, si.Args, si.Env, si.Builder, si.PrelaunchFunc); err != nil {
   166  		resp = VMCResp{Err: err}
   167  	} else {
   168  		resp = VMCResp{}
   169  	}
   170  
   171  	return resp
   172  }
   173  
   174  func (si StartImageReq) getCCID() ccintf.CCID {
   175  	return si.CCID
   176  }
   177  
   178  //StopImageReq - properties for stopping a container.
   179  type StopImageReq struct {
   180  	ccintf.CCID
   181  	Timeout uint
   182  	//by default we will kill the container after stopping
   183  	Dontkill bool
   184  	//by default we will remove the container after killing
   185  	Dontremove bool
   186  }
   187  
   188  func (si StopImageReq) do(ctxt context.Context, v api.VM) VMCResp {
   189  	var resp VMCResp
   190  
   191  	if err := v.Stop(ctxt, si.CCID, si.Timeout, si.Dontkill, si.Dontremove); err != nil {
   192  		resp = VMCResp{Err: err}
   193  	} else {
   194  		resp = VMCResp{}
   195  	}
   196  
   197  	return resp
   198  }
   199  
   200  func (si StopImageReq) getCCID() ccintf.CCID {
   201  	return si.CCID
   202  }
   203  
   204  //DestroyImageReq - properties for stopping a container.
   205  type DestroyImageReq struct {
   206  	ccintf.CCID
   207  	Timeout uint
   208  	Force   bool
   209  	NoPrune bool
   210  }
   211  
   212  func (di DestroyImageReq) do(ctxt context.Context, v api.VM) VMCResp {
   213  	var resp VMCResp
   214  
   215  	if err := v.Destroy(ctxt, di.CCID, di.Force, di.NoPrune); err != nil {
   216  		resp = VMCResp{Err: err}
   217  	} else {
   218  		resp = VMCResp{}
   219  	}
   220  
   221  	return resp
   222  }
   223  
   224  func (di DestroyImageReq) getCCID() ccintf.CCID {
   225  	return di.CCID
   226  }
   227  
   228  //VMCProcess should be used as follows
   229  //   . construct a context
   230  //   . construct req of the right type (e.g., CreateImageReq)
   231  //   . call it in a go routine
   232  //   . process response in the go routing
   233  //context can be cancelled. VMCProcess will try to cancel calling functions if it can
   234  //For instance docker clients api's such as BuildImage are not cancelable.
   235  //In all cases VMCProcess will wait for the called go routine to return
   236  func VMCProcess(ctxt context.Context, vmtype string, req VMCReqIntf) (interface{}, error) {
   237  	v := vmcontroller.newVM(vmtype)
   238  
   239  	if v == nil {
   240  		return nil, fmt.Errorf("Unknown VM type %s", vmtype)
   241  	}
   242  
   243  	c := make(chan struct{})
   244  	var resp interface{}
   245  	go func() {
   246  		defer close(c)
   247  
   248  		id, err := v.GetVMName(req.getCCID(), nil)
   249  		if err != nil {
   250  			resp = VMCResp{Err: err}
   251  			return
   252  		}
   253  		vmcontroller.lockContainer(id)
   254  		resp = req.do(ctxt, v)
   255  		vmcontroller.unlockContainer(id)
   256  	}()
   257  
   258  	select {
   259  	case <-c:
   260  		return resp, nil
   261  	case <-ctxt.Done():
   262  		//TODO cancel req.do ... (needed) ?
   263  		<-c
   264  		return nil, ctxt.Err()
   265  	}
   266  }