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 }