github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/common/resource.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package common
     5  
     6  import (
     7  	"fmt"
     8  	"strconv"
     9  	"sync"
    10  
    11  	"github.com/juju/juju/apiserver/facade"
    12  )
    13  
    14  // Resources holds all the resources for a connection.
    15  // It allows the registration of resources that will be cleaned
    16  // up when a connection terminates.
    17  type Resources struct {
    18  	mu        sync.Mutex
    19  	maxId     uint64
    20  	resources map[string]facade.Resource
    21  
    22  	// The stack is used to control the order of destruction.
    23  	// last registered, first stopped.
    24  	// XXX(fwereade): is this necessary only because we have
    25  	// Resource instead of Worker (which would let us kill them all,
    26  	// and wait for them all, without danger of races)?
    27  	stack []string
    28  }
    29  
    30  func NewResources() *Resources {
    31  	return &Resources{
    32  		resources: make(map[string]facade.Resource),
    33  	}
    34  }
    35  
    36  // Get returns the resource for the given id, or
    37  // nil if there is no such resource.
    38  func (rs *Resources) Get(id string) facade.Resource {
    39  	rs.mu.Lock()
    40  	defer rs.mu.Unlock()
    41  	return rs.resources[id]
    42  }
    43  
    44  // Register registers the given resource. It returns a unique
    45  // identifier for the resource which can then be used in
    46  // subsequent API requests to refer to the resource.
    47  func (rs *Resources) Register(r facade.Resource) string {
    48  	rs.mu.Lock()
    49  	defer rs.mu.Unlock()
    50  	rs.maxId++
    51  	id := strconv.FormatUint(rs.maxId, 10)
    52  	rs.resources[id] = r
    53  	rs.stack = append(rs.stack, id)
    54  	logger.Tracef("registered unnamed resource: %s", id)
    55  	return id
    56  }
    57  
    58  // RegisterNamed registers the given resource. Callers must supply a unique
    59  // name for the given resource. It is an error to try to register another
    60  // resource with the same name as an already registered name. (This could be
    61  // softened that you can overwrite an existing one and it will be Stopped and
    62  // replaced, but we don't have a need for that yet.)
    63  // It is also an error to supply a name that is an integer string, since that
    64  // collides with the auto-naming from Register.
    65  func (rs *Resources) RegisterNamed(name string, r facade.Resource) error {
    66  	rs.mu.Lock()
    67  	defer rs.mu.Unlock()
    68  	if _, err := strconv.Atoi(name); err == nil {
    69  		return fmt.Errorf("RegisterNamed does not allow integer names: %q", name)
    70  	}
    71  	if _, ok := rs.resources[name]; ok {
    72  		return fmt.Errorf("resource %q already registered", name)
    73  	}
    74  	rs.resources[name] = r
    75  	rs.stack = append(rs.stack, name)
    76  	logger.Tracef("registered named resource: %s", name)
    77  	return nil
    78  }
    79  
    80  // Stop stops the resource with the given id and unregisters it.
    81  // It returns any error from the underlying Stop call.
    82  // It does not return an error if the resource has already
    83  // been unregistered.
    84  func (rs *Resources) Stop(id string) error {
    85  	// We don't hold the mutex while calling Stop, because
    86  	// that might take a while and we don't want to
    87  	// stop all other resource manipulation while we do so.
    88  	// If resources.Stop is called concurrently, we'll get
    89  	// two concurrent calls to Stop, but that should fit
    90  	// well with the way we invariably implement Stop.
    91  	logger.Tracef("stopping resource: %s", id)
    92  	r := rs.Get(id)
    93  	if r == nil {
    94  		return nil
    95  	}
    96  	err := r.Stop()
    97  	rs.mu.Lock()
    98  	defer rs.mu.Unlock()
    99  	delete(rs.resources, id)
   100  	for pos := 0; pos < len(rs.stack); pos++ {
   101  		if rs.stack[pos] == id {
   102  			rs.stack = append(rs.stack[0:pos], rs.stack[pos+1:]...)
   103  			break
   104  		}
   105  	}
   106  	return err
   107  }
   108  
   109  // StopAll stops all the resources.
   110  func (rs *Resources) StopAll() {
   111  	rs.mu.Lock()
   112  	defer rs.mu.Unlock()
   113  	for i := len(rs.stack); i > 0; i-- {
   114  		id := rs.stack[i-1]
   115  		r := rs.resources[id]
   116  		logger.Tracef("stopping resource: %s", id)
   117  		if err := r.Stop(); err != nil {
   118  			logger.Errorf("error stopping %T resource: %v", r, err)
   119  		}
   120  	}
   121  	rs.resources = make(map[string]facade.Resource)
   122  	rs.stack = nil
   123  }
   124  
   125  // Count returns the number of resources currently held.
   126  func (rs *Resources) Count() int {
   127  	rs.mu.Lock()
   128  	defer rs.mu.Unlock()
   129  	return len(rs.resources)
   130  }
   131  
   132  // StringResource is just a regular 'string' that matches the Resource
   133  // interface.
   134  type StringResource string
   135  
   136  func (StringResource) Stop() error {
   137  	return nil
   138  }
   139  
   140  func (s StringResource) String() string {
   141  	return string(s)
   142  }
   143  
   144  // ValueResource is a Resource with a no-op Stop method, containing an
   145  // interface{} value.
   146  type ValueResource struct {
   147  	Value interface{}
   148  }
   149  
   150  func (r ValueResource) Stop() error {
   151  	return nil
   152  }