github.com/mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/virt/fake/fake_domain.go (about) 1 /* 2 Copyright 2017 Mirantis 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package fake 18 19 import ( 20 "fmt" 21 "log" 22 "path/filepath" 23 "sort" 24 "strings" 25 26 libvirtxml "github.com/libvirt/libvirt-go-xml" 27 28 testutils "github.com/Mirantis/virtlet/pkg/utils/testing" 29 "github.com/Mirantis/virtlet/pkg/virt" 30 ) 31 32 func mustMarshal(d libvirtxml.Document) string { 33 s, err := d.Marshal() 34 if err != nil { 35 log.Panicf("Error marshaling libvirt doc: %v", err) 36 } 37 return s 38 } 39 40 // FakeDomainConnection is a fake implementation of DomainConnection interface. 41 type FakeDomainConnection struct { 42 rec testutils.Recorder 43 domains map[string]*FakeDomain 44 domainsByUuid map[string]*FakeDomain 45 secretsByUsageName map[string]*FakeSecret 46 ignoreShutdown bool 47 useNonVolatileDomainDef bool 48 } 49 50 var _ virt.DomainConnection = &FakeDomainConnection{} 51 52 // NewFakeDomainConnection creates a new FakeDomainConnection using 53 // the specified Recorder to record any changes. 54 func NewFakeDomainConnection(rec testutils.Recorder) *FakeDomainConnection { 55 if rec == nil { 56 rec = testutils.NullRecorder 57 } 58 return &FakeDomainConnection{ 59 rec: rec, 60 domains: make(map[string]*FakeDomain), 61 domainsByUuid: make(map[string]*FakeDomain), 62 secretsByUsageName: make(map[string]*FakeSecret), 63 } 64 } 65 66 // UseNonVolatileDomainDef instructs the domains to fix volatile paths 67 // in the domain definitions returned by domains' XML() method. 68 func (dc *FakeDomainConnection) UseNonVolatileDomainDef() { 69 dc.useNonVolatileDomainDef = true 70 } 71 72 // SetIgnoreShutdown implements SetIgnoreShutdown method of DomainConnection interface. 73 func (dc *FakeDomainConnection) SetIgnoreShutdown(ignoreShutdown bool) { 74 dc.ignoreShutdown = ignoreShutdown 75 } 76 77 func (dc *FakeDomainConnection) removeDomain(d *FakeDomain) { 78 if _, found := dc.domains[d.def.Name]; !found { 79 log.Panicf("domain %q not found", d.def.Name) 80 } 81 delete(dc.domains, d.def.Name) 82 if _, found := dc.domainsByUuid[d.def.UUID]; !found { 83 log.Panicf("domain uuid %q not found (name %q)", d.def.UUID, d.def.Name) 84 } 85 delete(dc.domainsByUuid, d.def.UUID) 86 } 87 88 func (dc *FakeDomainConnection) removeSecret(s *FakeSecret) { 89 if _, found := dc.secretsByUsageName[s.usageName]; !found { 90 log.Panicf("secret %q not found", s.usageName) 91 } 92 delete(dc.secretsByUsageName, s.usageName) 93 } 94 95 // DefineDomain implements DefineDomain method of DomainConnection interface. 96 func (dc *FakeDomainConnection) DefineDomain(def *libvirtxml.Domain) (virt.Domain, error) { 97 def = copyDomain(def) 98 addPciRoot(def) 99 assignFakePCIAddressesToControllers(def) 100 // TODO: dump any ISOs mentioned in disks (Type=file) as json 101 // Include file name (base) in rec name 102 if _, found := dc.domains[def.Name]; found { 103 return nil, fmt.Errorf("domain %q already defined", def.Name) 104 } 105 if def.Name == "" { 106 return nil, fmt.Errorf("domain name cannot be empty") 107 } 108 if def.UUID == "" { 109 return nil, fmt.Errorf("domain %q has empty uuid", def.Name) 110 } 111 d := newFakeDomain(dc, def) 112 dc.domains[def.Name] = d 113 dc.domainsByUuid[def.UUID] = d 114 115 updatedDef := copyDomain(def) 116 removeVolatilePathsFromDomainDef(updatedDef) 117 dc.rec.Rec("DefineDomain", mustMarshal(updatedDef)) 118 return d, nil 119 } 120 121 // ListDomains implements ListDomains method of DomainConnection interface. 122 func (dc *FakeDomainConnection) ListDomains() ([]virt.Domain, error) { 123 r := make([]virt.Domain, len(dc.domains)) 124 names := make([]string, 0, len(dc.domains)) 125 for name := range dc.domains { 126 names = append(names, name) 127 } 128 sort.Strings(names) 129 for n, name := range names { 130 r[n] = dc.domains[name] 131 } 132 dc.rec.Rec("ListDomains", names) 133 return r, nil 134 } 135 136 // LookupDomainByName implements LookupDomainByName method of DomainConnection interface. 137 func (dc *FakeDomainConnection) LookupDomainByName(name string) (virt.Domain, error) { 138 if d, found := dc.domains[name]; found { 139 return d, nil 140 } 141 return nil, virt.ErrDomainNotFound 142 } 143 144 // LookupDomainByUUIDString implements LookupDomainByUUIDString method of DomainConnection interface. 145 func (dc *FakeDomainConnection) LookupDomainByUUIDString(uuid string) (virt.Domain, error) { 146 if d, found := dc.domainsByUuid[uuid]; found { 147 return d, nil 148 } 149 return nil, virt.ErrDomainNotFound 150 } 151 152 // DefineSecret implements DefineSecret method of DomainConnection interface. 153 func (dc *FakeDomainConnection) DefineSecret(def *libvirtxml.Secret) (virt.Secret, error) { 154 if def.UUID == "" { 155 return nil, fmt.Errorf("the secret has empty uuid") 156 } 157 if def.Usage.Name == "" { 158 return nil, fmt.Errorf("the secret has empty Usage name") 159 } 160 // clear secret uuid as it's generated randomly 161 def.UUID = "" 162 dc.rec.Rec("DefineSecret", mustMarshal(def)) 163 164 s := newFakeSecret(dc, def.Usage.Name) 165 dc.secretsByUsageName[def.Usage.Name] = s 166 return s, nil 167 } 168 169 // LookupSecretByUUIDString implements LookupSecretByUUIDString method of DomainConnection interface. 170 func (dc *FakeDomainConnection) LookupSecretByUUIDString(uuid string) (virt.Secret, error) { 171 return nil, virt.ErrSecretNotFound 172 } 173 174 // LookupSecretByUsageName implements LookupSecretByUsageName method of DomainConnection interface. 175 func (dc *FakeDomainConnection) LookupSecretByUsageName(usageType string, usageName string) (virt.Secret, error) { 176 if d, found := dc.secretsByUsageName[usageName]; found { 177 return d, nil 178 } 179 return nil, virt.ErrSecretNotFound 180 } 181 182 // FakeDomain is a fake implementation of Domain interface. 183 type FakeDomain struct { 184 rec testutils.Recorder 185 dc *FakeDomainConnection 186 removed bool 187 created bool 188 state virt.DomainState 189 def *libvirtxml.Domain 190 } 191 192 var _ virt.Domain = &FakeDomain{} 193 194 func newFakeDomain(dc *FakeDomainConnection, def *libvirtxml.Domain) *FakeDomain { 195 return &FakeDomain{ 196 rec: testutils.NewChildRecorder(dc.rec, def.Name), 197 dc: dc, 198 state: virt.DomainStateShutoff, 199 def: def, 200 } 201 } 202 203 // Create implements Create method of Domain interface. 204 func (d *FakeDomain) Create() error { 205 d.rec.Rec("Create", nil) 206 if d.def.Devices != nil { 207 for _, disk := range d.def.Devices.Disks { 208 if disk.Source == nil || disk.Source.File == nil { 209 continue 210 } 211 origPath := disk.Source.File.File 212 if filepath.Ext(origPath) == ".iso" || strings.HasPrefix(filepath.Base(origPath), "config-iso") { 213 m, err := testutils.IsoToMap(origPath) 214 if err != nil { 215 return fmt.Errorf("bad iso image: %q", origPath) 216 } 217 d.rec.Rec("iso image", m) 218 } 219 } 220 } 221 if d.removed { 222 return fmt.Errorf("Create() called on a removed (undefined) domain %q", d.def.Name) 223 } 224 if d.created { 225 return fmt.Errorf("trying to re-create domain %q", d.def.Name) 226 } 227 if d.state != virt.DomainStateShutoff { 228 return fmt.Errorf("invalid domain state %d", d.state) 229 } 230 d.created = true 231 d.state = virt.DomainStateRunning 232 return nil 233 } 234 235 // Destroy implements Destroy method of Domain interface. 236 func (d *FakeDomain) Destroy() error { 237 d.rec.Rec("Destroy", nil) 238 if d.removed { 239 return fmt.Errorf("Destroy() called on a removed (undefined) domain %q", d.def.Name) 240 } 241 d.state = virt.DomainStateShutoff 242 return nil 243 } 244 245 // Undefine implements Undefine method of Domain interface. 246 func (d *FakeDomain) Undefine() error { 247 d.rec.Rec("Undefine", nil) 248 if d.removed { 249 return fmt.Errorf("Undefine(): domain %q already removed", d.def.Name) 250 } 251 d.removed = true 252 d.dc.removeDomain(d) 253 return nil 254 } 255 256 // Shutdown implements Shutdown method of Domain interface. 257 func (d *FakeDomain) Shutdown() error { 258 if d.dc.ignoreShutdown { 259 d.rec.Rec("Shutdown", map[string]interface{}{"ignored": true}) 260 } else { 261 d.rec.Rec("Shutdown", nil) 262 } 263 if d.removed { 264 return fmt.Errorf("Shutdown() called on a removed (undefined) domain %q", d.def.Name) 265 } 266 if !d.dc.ignoreShutdown { 267 // TODO: need to test DomainStateShutdown stage too 268 d.state = virt.DomainStateShutoff 269 } 270 return nil 271 } 272 273 // State implements State method of Domain interface. 274 func (d *FakeDomain) State() (virt.DomainState, error) { 275 if d.removed { 276 return virt.DomainStateNoState, fmt.Errorf("State() called on a removed (undefined) domain %q", d.def.Name) 277 } 278 return d.state, nil 279 } 280 281 // UUIDString implements UUIDString method of Domain interface. 282 func (d *FakeDomain) UUIDString() (string, error) { 283 if d.removed { 284 return "", fmt.Errorf("UUIDString() called on a removed (undefined) domain %q", d.def.Name) 285 } 286 return d.def.UUID, nil 287 } 288 289 // Name implements Name method of Domain interface. 290 func (d *FakeDomain) Name() (string, error) { 291 return d.def.Name, nil 292 } 293 294 // XML implements XML method of Domain interface. 295 func (d *FakeDomain) XML() (*libvirtxml.Domain, error) { 296 if d.dc.useNonVolatileDomainDef { 297 def := copyDomain(d.def) 298 removeVolatilePathsFromDomainDef(def) 299 return def, nil 300 } 301 return d.def, nil 302 } 303 304 // GetCPUTime implements GetCPUTime of Domain interface. 305 func (d *FakeDomain) GetCPUTime() (uint64, error) { 306 return 0, nil 307 } 308 309 // GetRSS implements GetRSS of Domain interface. 310 func (d *FakeDomain) GetRSS() (uint64, error) { 311 return 0, nil 312 } 313 314 // FakeSecret is a fake implementation of Secret interace. 315 type FakeSecret struct { 316 rec testutils.Recorder 317 dc *FakeDomainConnection 318 usageName string 319 } 320 321 var _ virt.Secret = &FakeSecret{} 322 323 func newFakeSecret(dc *FakeDomainConnection, usageName string) *FakeSecret { 324 return &FakeSecret{ 325 rec: testutils.NewChildRecorder(dc.rec, "secret "+usageName), 326 dc: dc, 327 usageName: usageName, 328 } 329 } 330 331 // SetValue implements SetValue method of Secret interface. 332 func (s *FakeSecret) SetValue(value []byte) error { 333 s.rec.Rec("SetValue", fmt.Sprintf("% x", value)) 334 return nil 335 } 336 337 // Remove implements Remove method of Secret interface. 338 func (s *FakeSecret) Remove() error { 339 s.rec.Rec("Remove", nil) 340 s.dc.removeSecret(s) 341 return nil 342 } 343 344 func copyDomain(def *libvirtxml.Domain) *libvirtxml.Domain { 345 s, err := def.Marshal() 346 if err != nil { 347 log.Panicf("failed to marshal libvirt domain: %v", err) 348 } 349 var copy libvirtxml.Domain 350 if err := copy.Unmarshal(s); err != nil { 351 log.Panicf("failed to unmarshal libvirt domain: %v", err) 352 } 353 return © 354 } 355 356 func addPciRoot(def *libvirtxml.Domain) { 357 if def.Devices == nil { 358 def.Devices = &libvirtxml.DomainDeviceList{} 359 } 360 for _, c := range def.Devices.Controllers { 361 if c.Type == "pci" { 362 return 363 } 364 } 365 def.Devices.Controllers = append(def.Devices.Controllers, libvirtxml.DomainController{ 366 Type: "pci", 367 Model: "pci-root", 368 }) 369 } 370 371 func assignFakePCIAddressesToControllers(def *libvirtxml.Domain) { 372 if def.Devices == nil { 373 return 374 } 375 domain := uint(0) 376 bus := uint(0) 377 function := uint(0) 378 for n, c := range def.Devices.Controllers { 379 if c.Type == "pci" || c.Address != nil { 380 continue 381 } 382 slot := uint(n + 1) 383 // note that c is not a pointer 384 def.Devices.Controllers[n].Address = &libvirtxml.DomainAddress{ 385 PCI: &libvirtxml.DomainAddressPCI{ 386 Domain: &domain, 387 Bus: &bus, 388 Slot: &slot, 389 Function: &function, 390 }, 391 } 392 } 393 } 394 395 func removeVolatilePathsFromDomainDef(def *libvirtxml.Domain) { 396 if def.Devices == nil { 397 return 398 } 399 400 for _, disk := range def.Devices.Disks { 401 var toUpdate *string 402 switch { 403 case disk.Source == nil: 404 continue 405 case disk.Source.File != nil: 406 toUpdate = &disk.Source.File.File 407 case disk.Source.Block != nil: 408 toUpdate = &disk.Source.Block.Dev 409 default: 410 continue 411 } 412 *toUpdate = fixPath(*toUpdate) 413 } 414 415 for _, fs := range def.Devices.Filesystems { 416 if fs.Source != nil && fs.Source.Mount != nil { 417 fs.Source.Mount.Dir = fixPath(fs.Source.Mount.Dir) 418 } 419 } 420 }