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 }