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