github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/container/lxc/mock/mock-lxc.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package mock
     5  
     6  import (
     7  	"fmt"
     8  	"io/ioutil"
     9  	"os"
    10  	"path/filepath"
    11  	"sync"
    12  
    13  	"github.com/juju/errors"
    14  	"github.com/juju/loggo"
    15  	"github.com/juju/testing"
    16  	"github.com/juju/utils"
    17  	"launchpad.net/golxc"
    18  
    19  	"github.com/juju/juju/container"
    20  )
    21  
    22  // This file provides a mock implementation of the golxc interfaces
    23  // ContainerFactory and Container.
    24  
    25  var logger = loggo.GetLogger("juju.container.lxc.mock")
    26  
    27  type Action int
    28  
    29  var transientErrorInjection chan error
    30  
    31  // PatchTransientErrorInjectionChannel sets the transientInjectionError
    32  // channel which can be used to inject errors into Start function for
    33  // testing purposes
    34  func PatchTransientErrorInjectionChannel(c chan error) func() {
    35  	return testing.PatchValue(&transientErrorInjection, c)
    36  }
    37  
    38  const (
    39  	// A container has been started.
    40  	Started Action = iota
    41  	// A container has been stopped.
    42  	Stopped
    43  	// A container has been created.
    44  	Created
    45  	// A container has been destroyed.
    46  	Destroyed
    47  	// A container has been cloned.
    48  	Cloned
    49  )
    50  
    51  func (action Action) String() string {
    52  	switch action {
    53  	case Started:
    54  		return "Started"
    55  	case Stopped:
    56  		return "Stopped"
    57  	case Created:
    58  		return "Created"
    59  	case Destroyed:
    60  		return "Destroyed"
    61  	case Cloned:
    62  		return "Cloned"
    63  	}
    64  	return "unknown"
    65  }
    66  
    67  type Event struct {
    68  	Action       Action
    69  	InstanceId   string
    70  	Args         []string
    71  	TemplateArgs []string
    72  	EnvArgs      []string
    73  }
    74  
    75  type ContainerFactory interface {
    76  	golxc.ContainerFactory
    77  
    78  	AddListener(chan<- Event)
    79  	RemoveListener(chan<- Event)
    80  }
    81  
    82  type mockFactory struct {
    83  	containerDir string
    84  	instances    map[string]golxc.Container
    85  	listeners    []chan<- Event
    86  	mutex        sync.Mutex
    87  }
    88  
    89  func MockFactory(containerDir string) ContainerFactory {
    90  	return &mockFactory{
    91  		containerDir: containerDir,
    92  		instances:    make(map[string]golxc.Container),
    93  	}
    94  }
    95  
    96  type mockContainer struct {
    97  	factory  *mockFactory
    98  	name     string
    99  	state    golxc.State
   100  	logFile  string
   101  	logLevel golxc.LogLevel
   102  }
   103  
   104  func (mock *mockContainer) getState() golxc.State {
   105  	mock.factory.mutex.Lock()
   106  	defer mock.factory.mutex.Unlock()
   107  	return mock.state
   108  }
   109  
   110  func (mock *mockContainer) setState(newState golxc.State) {
   111  	mock.factory.mutex.Lock()
   112  	defer mock.factory.mutex.Unlock()
   113  	mock.state = newState
   114  	logger.Debugf("container %q state change to %s", mock.name, string(newState))
   115  }
   116  
   117  // Name returns the name of the container.
   118  func (mock *mockContainer) Name() string {
   119  	return mock.name
   120  }
   121  
   122  func (mock *mockContainer) configFilename() string {
   123  	return filepath.Join(mock.factory.containerDir, mock.name, "config")
   124  }
   125  
   126  // Create creates a new container based on the given template.
   127  func (mock *mockContainer) Create(configFile, template string, extraArgs []string, templateArgs []string, envArgs []string) error {
   128  	if mock.getState() != golxc.StateUnknown {
   129  		return fmt.Errorf("container is already created")
   130  	}
   131  	mock.factory.instances[mock.name] = mock
   132  	// Create the container directory.
   133  	containerDir := filepath.Join(mock.factory.containerDir, mock.name)
   134  	if err := os.MkdirAll(containerDir, 0755); err != nil {
   135  		return errors.Trace(err)
   136  	}
   137  	if err := utils.CopyFile(mock.configFilename(), configFile); err != nil {
   138  		return errors.Trace(err)
   139  	}
   140  	mock.setState(golxc.StateStopped)
   141  	mock.factory.notify(eventArgs(Created, mock.name, extraArgs, templateArgs, envArgs))
   142  	return nil
   143  }
   144  
   145  // Start runs the container as a daemon.
   146  func (mock *mockContainer) Start(configFile, consoleFile string) error {
   147  	select {
   148  	case injectedError := <-transientErrorInjection:
   149  		return injectedError
   150  	default:
   151  	}
   152  
   153  	state := mock.getState()
   154  	if state == golxc.StateUnknown {
   155  		return fmt.Errorf("container has not been created")
   156  	} else if state == golxc.StateRunning {
   157  		return fmt.Errorf("container is already running")
   158  	}
   159  	ioutil.WriteFile(
   160  		filepath.Join(container.ContainerDir, mock.name, "console.log"),
   161  		[]byte("fake console.log"), 0644)
   162  	mock.setState(golxc.StateRunning)
   163  	mock.factory.notify(event(Started, mock.name))
   164  	return nil
   165  }
   166  
   167  // Stop terminates the running container.
   168  func (mock *mockContainer) Stop() error {
   169  	state := mock.getState()
   170  	if state == golxc.StateUnknown {
   171  		return fmt.Errorf("container has not been created")
   172  	} else if state == golxc.StateStopped {
   173  		return fmt.Errorf("container is already stopped")
   174  	}
   175  	mock.setState(golxc.StateStopped)
   176  	mock.factory.notify(event(Stopped, mock.name))
   177  	return nil
   178  }
   179  
   180  // Clone creates a copy of the container, giving the copy the specified name.
   181  func (mock *mockContainer) Clone(name string, extraArgs []string, templateArgs []string) (golxc.Container, error) {
   182  	state := mock.getState()
   183  	if state == golxc.StateUnknown {
   184  		return nil, fmt.Errorf("container has not been created")
   185  	} else if state == golxc.StateRunning {
   186  		return nil, fmt.Errorf("container is running, clone not possible")
   187  	}
   188  
   189  	container := &mockContainer{
   190  		factory:  mock.factory,
   191  		name:     name,
   192  		state:    golxc.StateStopped,
   193  		logLevel: golxc.LogWarning,
   194  	}
   195  	mock.factory.instances[name] = container
   196  
   197  	// Create the container directory.
   198  	containerDir := filepath.Join(mock.factory.containerDir, name)
   199  	if err := os.MkdirAll(containerDir, 0755); err != nil {
   200  		return nil, errors.Trace(err)
   201  	}
   202  	if err := utils.CopyFile(container.configFilename(), mock.configFilename()); err != nil {
   203  		return nil, errors.Trace(err)
   204  	}
   205  
   206  	mock.factory.notify(eventArgs(Cloned, mock.name, extraArgs, templateArgs, nil))
   207  	return container, nil
   208  }
   209  
   210  // Freeze freezes all the container's processes.
   211  func (mock *mockContainer) Freeze() error {
   212  	return nil
   213  }
   214  
   215  // Unfreeze thaws all frozen container's processes.
   216  func (mock *mockContainer) Unfreeze() error {
   217  	return nil
   218  }
   219  
   220  // Destroy stops and removes the container.
   221  func (mock *mockContainer) Destroy() error {
   222  	select {
   223  	case injectedError := <-transientErrorInjection:
   224  		return injectedError
   225  	default:
   226  	}
   227  
   228  	state := mock.getState()
   229  	// golxc destroy will stop the machine if it is running.
   230  	if state == golxc.StateRunning {
   231  		mock.Stop()
   232  	}
   233  	if state == golxc.StateUnknown {
   234  		return fmt.Errorf("container has not been created")
   235  	}
   236  	delete(mock.factory.instances, mock.name)
   237  	mock.setState(golxc.StateUnknown)
   238  	mock.factory.notify(event(Destroyed, mock.name))
   239  	return nil
   240  }
   241  
   242  // Wait waits for one of the specified container states.
   243  func (mock *mockContainer) Wait(states ...golxc.State) error {
   244  	return nil
   245  }
   246  
   247  // Info returns the status and the process id of the container.
   248  func (mock *mockContainer) Info() (golxc.State, int, error) {
   249  	pid := -1
   250  	state := mock.getState()
   251  	if state == golxc.StateRunning {
   252  		pid = 42
   253  	}
   254  	return state, pid, nil
   255  }
   256  
   257  // IsConstructed checks if the container image exists.
   258  func (mock *mockContainer) IsConstructed() bool {
   259  	return mock.getState() != golxc.StateUnknown
   260  }
   261  
   262  // IsRunning checks if the state of the container is 'RUNNING'.
   263  func (mock *mockContainer) IsRunning() bool {
   264  	return mock.getState() == golxc.StateRunning
   265  }
   266  
   267  // String returns information about the container, like the name, state,
   268  // and process id.
   269  func (mock *mockContainer) String() string {
   270  	state, pid, _ := mock.Info()
   271  	return fmt.Sprintf("<MockContainer %q, state: %s, pid %d>", mock.name, string(state), pid)
   272  }
   273  
   274  // LogFile returns the current filename used for the LogFile.
   275  func (mock *mockContainer) LogFile() string {
   276  	return mock.logFile
   277  }
   278  
   279  // LogLevel returns the current logging level (only used if the
   280  // LogFile is not "").
   281  func (mock *mockContainer) LogLevel() golxc.LogLevel {
   282  	return mock.logLevel
   283  }
   284  
   285  // SetLogFile sets both the LogFile and LogLevel.
   286  func (mock *mockContainer) SetLogFile(filename string, level golxc.LogLevel) {
   287  	mock.logFile = filename
   288  	mock.logLevel = level
   289  }
   290  
   291  func (mock *mockFactory) String() string {
   292  	return fmt.Sprintf("mock lxc factory")
   293  }
   294  
   295  func (mock *mockFactory) New(name string) golxc.Container {
   296  	mock.mutex.Lock()
   297  	defer mock.mutex.Unlock()
   298  	container, ok := mock.instances[name]
   299  	if ok {
   300  		return container
   301  	}
   302  	container = &mockContainer{
   303  		factory:  mock,
   304  		name:     name,
   305  		state:    golxc.StateUnknown,
   306  		logLevel: golxc.LogWarning,
   307  	}
   308  	mock.instances[name] = container
   309  	return container
   310  }
   311  
   312  func (mock *mockFactory) List() (result []golxc.Container, err error) {
   313  	for _, container := range mock.instances {
   314  		result = append(result, container)
   315  	}
   316  	return
   317  }
   318  
   319  func event(action Action, instanceId string) Event {
   320  	return Event{action, instanceId, nil, nil, nil}
   321  }
   322  
   323  func eventArgs(action Action, instanceId string, args, template, envArgs []string) Event {
   324  	return Event{action, instanceId, args, template, envArgs}
   325  }
   326  
   327  func (mock *mockFactory) notify(event Event) {
   328  	for _, c := range mock.listeners {
   329  		c <- event
   330  	}
   331  }
   332  
   333  func (mock *mockFactory) AddListener(listener chan<- Event) {
   334  	mock.listeners = append(mock.listeners, listener)
   335  }
   336  
   337  func (mock *mockFactory) RemoveListener(listener chan<- Event) {
   338  	pos := 0
   339  	for i, c := range mock.listeners {
   340  		if c == listener {
   341  			pos = i
   342  		}
   343  	}
   344  	mock.listeners = append(mock.listeners[:pos], mock.listeners[pos+1:]...)
   345  }