github.com/adnan-c/fabric_e2e_couchdb@v0.6.1-preview.0.20170228180935-21ce6b23cf91/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.DockerVM{}
    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  }
   160  
   161  func (si StartImageReq) do(ctxt context.Context, v api.VM) VMCResp {
   162  	var resp VMCResp
   163  
   164  	if err := v.Start(ctxt, si.CCID, si.Args, si.Env, si.Builder); err != nil {
   165  		resp = VMCResp{Err: err}
   166  	} else {
   167  		resp = VMCResp{}
   168  	}
   169  
   170  	return resp
   171  }
   172  
   173  func (si StartImageReq) getCCID() ccintf.CCID {
   174  	return si.CCID
   175  }
   176  
   177  //StopImageReq - properties for stopping a container.
   178  type StopImageReq struct {
   179  	ccintf.CCID
   180  	Timeout uint
   181  	//by default we will kill the container after stopping
   182  	Dontkill bool
   183  	//by default we will remove the container after killing
   184  	Dontremove bool
   185  }
   186  
   187  func (si StopImageReq) do(ctxt context.Context, v api.VM) VMCResp {
   188  	var resp VMCResp
   189  
   190  	if err := v.Stop(ctxt, si.CCID, si.Timeout, si.Dontkill, si.Dontremove); err != nil {
   191  		resp = VMCResp{Err: err}
   192  	} else {
   193  		resp = VMCResp{}
   194  	}
   195  
   196  	return resp
   197  }
   198  
   199  func (si StopImageReq) getCCID() ccintf.CCID {
   200  	return si.CCID
   201  }
   202  
   203  //DestroyImageReq - properties for stopping a container.
   204  type DestroyImageReq struct {
   205  	ccintf.CCID
   206  	Timeout uint
   207  	Force   bool
   208  	NoPrune bool
   209  }
   210  
   211  func (di DestroyImageReq) do(ctxt context.Context, v api.VM) VMCResp {
   212  	var resp VMCResp
   213  
   214  	if err := v.Destroy(ctxt, di.CCID, di.Force, di.NoPrune); err != nil {
   215  		resp = VMCResp{Err: err}
   216  	} else {
   217  		resp = VMCResp{}
   218  	}
   219  
   220  	return resp
   221  }
   222  
   223  func (di DestroyImageReq) getCCID() ccintf.CCID {
   224  	return di.CCID
   225  }
   226  
   227  //VMCProcess should be used as follows
   228  //   . construct a context
   229  //   . construct req of the right type (e.g., CreateImageReq)
   230  //   . call it in a go routine
   231  //   . process response in the go routing
   232  //context can be cancelled. VMCProcess will try to cancel calling functions if it can
   233  //For instance docker clients api's such as BuildImage are not cancelable.
   234  //In all cases VMCProcess will wait for the called go routine to return
   235  func VMCProcess(ctxt context.Context, vmtype string, req VMCReqIntf) (interface{}, error) {
   236  	v := vmcontroller.newVM(vmtype)
   237  
   238  	if v == nil {
   239  		return nil, fmt.Errorf("Unknown VM type %s", vmtype)
   240  	}
   241  
   242  	c := make(chan struct{})
   243  	var resp interface{}
   244  	go func() {
   245  		defer close(c)
   246  
   247  		id, err := v.GetVMName(req.getCCID())
   248  		if err != nil {
   249  			resp = VMCResp{Err: err}
   250  			return
   251  		}
   252  		vmcontroller.lockContainer(id)
   253  		resp = req.do(ctxt, v)
   254  		vmcontroller.unlockContainer(id)
   255  	}()
   256  
   257  	select {
   258  	case <-c:
   259  		return resp, nil
   260  	case <-ctxt.Done():
   261  		//TODO cancel req.do ... (needed) ?
   262  		<-c
   263  		return nil, ctxt.Err()
   264  	}
   265  }